summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-24 15:44:26 -1000
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-24 15:44:26 -1000
commit330517c63aa9b48227b6ab45f01003effac77ae4 (patch)
tree51b6e38214a50c0253eba6dc05eeed6ae0f5b995 /drivers
parent168151cdb0f93d26557f9f3c7aae659b8d6b1863 (diff)
parent985955d8e6b2b15cfd65b7ac6893e3a5a90c5870 (diff)
Merge tag 'char-misc-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc
Pull Char/Misc/IIO/others driver updates from Greg KH: "Here is the big set of char/misc/android/iio and other small driver subsystem patches for 7.4-rc1. Loads of stuff in here, larger than normal. Highlights include: - removal of the C binder code, the rust version is stable and will be used going forward in a few billion devices. Great job by those developers - loads and loads of IIO driver updates and feature additions - speakup driver fixes and updates - mhi driver updates - mei driver updates - xillybus driver fixes - comedi driver fixes - lots of coresight driver updates - Interconnect driver updates - nvmem driver updates and fixes - slimbus driver updates and fixes - spmi driver updates and fixes - lots of tiny misc and char driver fixes based on people scanning for things All of these have been in linux-next for a while with no reported issues, other than a merge conflict with one of the rust tree files, which should be simple to resolve" * tag 'char-misc-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (446 commits) rust_binder: wake epoll on self-work queued to poll thread rust_binder: avoid overflow in get_node_debug_info() MAINTAINERS: Add Abel as SPMI co-maintainer spmi: mtk-pmif: Fix clock reference leak in mtk_spmi_bus_probe() iio: adc: qcom-spmi-iadc: Remove regmap R/W wrapper functions iio: adc: qcom-spmi-iadc: Migrate to devm_spmi_subdevice_alloc_and_add() misc: qcom-coincell: Migrate to devm_spmi_subdevice_alloc_and_add() phy: qualcomm: eusb2-repeater: Migrate to devm_spmi_subdevice_alloc_and_add() power: reset: qcom-pon: Migrate to devm_spmi_subdevice_alloc_and_add() nvmem: qcom-spmi-sdam: Migrate to devm_spmi_subdevice_alloc_and_add() spmi: Add helper to get a parent SPMI device spmi: Implement spmi_subdevice_alloc_and_add() and devm variant spmi: Remove unneeded goto in spmi_device_add() error path spmi: Print error status with %pe format spmi: Remove redundant dev_name() print in spmi_device_add() spmi: Fix potential use-after-free by grabbing of_node reference spmi: mtk-pmif: Add workaround for FSM lockup/error in read operation MAINTAINERS: Add Qualcomm SPMI controller entry spmi: pmic-arb: return 0 in spmi_pmic_arb_register_buses() on success comedi: addi_apci_3xxx: fix subdevice count underallocation ...
Diffstat (limited to 'drivers')
-rw-r--r--drivers/accessibility/speakup/main.c2
-rw-r--r--drivers/accessibility/speakup/selection.c6
-rw-r--r--drivers/accessibility/speakup/serialio.c193
-rw-r--r--drivers/accessibility/speakup/serialio.h17
-rw-r--r--drivers/accessibility/speakup/speakup_acntpc.c1
-rw-r--r--drivers/accessibility/speakup/speakup_decpc.c1
-rw-r--r--drivers/accessibility/speakup/speakup_dtlk.c1
-rw-r--r--drivers/accessibility/speakup/speakup_keypc.c1
-rw-r--r--drivers/accessibility/speakup/spk_priv.h6
-rw-r--r--drivers/accessibility/speakup/varhandlers.c5
-rw-r--r--drivers/android/Kconfig41
-rw-r--r--drivers/android/Makefile3
-rw-r--r--drivers/android/binder.c7187
-rw-r--r--drivers/android/binder/Makefile4
-rw-r--r--drivers/android/binder/allocation.rs17
-rw-r--r--drivers/android/binder/context.rs8
-rw-r--r--drivers/android/binder/defs.rs3
-rw-r--r--drivers/android/binder/error.rs43
-rw-r--r--drivers/android/binder/freeze.rs17
-rw-r--r--drivers/android/binder/node.rs10
-rw-r--r--drivers/android/binder/node/wrapper.rs8
-rw-r--r--drivers/android/binder/page_range.rs6
-rw-r--r--drivers/android/binder/process.rs45
-rw-r--r--drivers/android/binder/range_alloc/array.rs2
-rw-r--r--drivers/android/binder/range_alloc/mod.rs2
-rw-r--r--drivers/android/binder/range_alloc/tree.rs2
-rw-r--r--drivers/android/binder/rust_binder_events.h42
-rw-r--r--drivers/android/binder/rust_binder_internal.h1
-rw-r--r--drivers/android/binder/rust_binder_main.rs45
-rw-r--r--drivers/android/binder/rust_binderfs.c21
-rw-r--r--drivers/android/binder/thread.rs176
-rw-r--r--drivers/android/binder/trace.rs37
-rw-r--r--drivers/android/binder/transaction.rs228
-rw-r--r--drivers/android/binder_alloc.c1398
-rw-r--r--drivers/android/binder_alloc.h189
-rw-r--r--drivers/android/binder_internal.h597
-rw-r--r--drivers/android/binder_netlink.c32
-rw-r--r--drivers/android/binder_netlink.h21
-rw-r--r--drivers/android/binder_trace.h448
-rw-r--r--drivers/android/binderfs.c785
-rw-r--r--drivers/android/dbitmap.h169
-rw-r--r--drivers/android/tests/.kunitconfig7
-rw-r--r--drivers/android/tests/Makefile6
-rw-r--r--drivers/android/tests/binder_alloc_kunit.c572
-rw-r--r--drivers/bus/mhi/ep/Kconfig2
-rw-r--r--drivers/bus/mhi/ep/Makefile1
-rw-r--r--drivers/bus/mhi/ep/clients/Kconfig16
-rw-r--r--drivers/bus/mhi/ep/clients/Makefile2
-rw-r--r--drivers/bus/mhi/ep/clients/loopback.c126
-rw-r--r--drivers/bus/mhi/ep/main.c32
-rw-r--r--drivers/bus/mhi/host/Kconfig1
-rw-r--r--drivers/bus/mhi/host/Makefile1
-rw-r--r--drivers/bus/mhi/host/clients/Kconfig17
-rw-r--r--drivers/bus/mhi/host/clients/Makefile2
-rw-r--r--drivers/bus/mhi/host/clients/loopback.c312
-rw-r--r--drivers/bus/mhi/host/debugfs.c4
-rw-r--r--drivers/bus/mhi/host/pci_generic.c22
-rw-r--r--drivers/char/scx200_gpio.c8
-rw-r--r--drivers/char/xilinx_hwicap/xilinx_hwicap.c18
-rw-r--r--drivers/char/xillybus/xillybus.h3
-rw-r--r--drivers/char/xillybus/xillybus_class.c18
-rw-r--r--drivers/char/xillybus/xillybus_class.h3
-rw-r--r--drivers/char/xillybus/xillybus_core.c98
-rw-r--r--drivers/char/xillybus/xillybus_of.c3
-rw-r--r--drivers/char/xillybus/xillyusb.c91
-rw-r--r--drivers/comedi/drivers/addi_apci_3xxx.c2
-rw-r--r--drivers/comedi/drivers/comedi_bond.c18
-rw-r--r--drivers/comedi/drivers/comedi_parport.c3
-rw-r--r--drivers/comedi/drivers/dmm32at.c3
-rw-r--r--drivers/comedi/drivers/dt2811.c2
-rw-r--r--drivers/comedi/drivers/dt2814.c3
-rw-r--r--drivers/comedi/drivers/ni_atmio16d.c4
-rw-r--r--drivers/comedi/drivers/pcmmio.c3
-rw-r--r--drivers/comedi/drivers/pcmuio.c5
-rw-r--r--drivers/firmware/stratix10-svc.c598
-rw-r--r--drivers/fpga/altera-cvp.c43
-rw-r--r--drivers/fpga/dfl.h1
-rw-r--r--drivers/fpga/fpga-mgr.c2
-rw-r--r--drivers/fpga/stratix10-soc.c2
-rw-r--r--drivers/fpga/xilinx-selectmap.c39
-rw-r--r--drivers/greybus/greybus_trace.h2
-rw-r--r--drivers/hwtracing/coresight/coresight-config.c18
-rw-r--r--drivers/hwtracing/coresight/coresight-config.h22
-rw-r--r--drivers/hwtracing/coresight/coresight-core.c1
-rw-r--r--drivers/hwtracing/coresight/coresight-etm.h48
-rw-r--r--drivers/hwtracing/coresight/coresight-etm3x-core.c196
-rw-r--r--drivers/hwtracing/coresight/coresight-etm3x-sysfs.c461
-rw-r--r--drivers/hwtracing/coresight/coresight-etm4x-cfg.c14
-rw-r--r--drivers/hwtracing/coresight/coresight-etm4x-core.c567
-rw-r--r--drivers/hwtracing/coresight/coresight-etm4x-sysfs.c961
-rw-r--r--drivers/hwtracing/coresight/coresight-etm4x.h199
-rw-r--r--drivers/hwtracing/coresight/coresight-sysfs.c15
-rw-r--r--drivers/hwtracing/coresight/coresight-tnoc.c2
-rw-r--r--drivers/hwtracing/coresight/coresight-trbe.c7
-rw-r--r--drivers/iio/accel/Kconfig7
-rw-r--r--drivers/iio/accel/adis16201.c133
-rw-r--r--drivers/iio/accel/adxl313_spi.c2
-rw-r--r--drivers/iio/accel/adxl367.c59
-rw-r--r--drivers/iio/accel/adxl372_spi.c2
-rw-r--r--drivers/iio/accel/adxl380.c7
-rw-r--r--drivers/iio/accel/adxl380_spi.c2
-rw-r--r--drivers/iio/accel/bma220_spi.c3
-rw-r--r--drivers/iio/accel/bma400_core.c2
-rw-r--r--drivers/iio/accel/kionix-kx022a.c22
-rw-r--r--drivers/iio/accel/kionix-kx022a.h2
-rw-r--r--drivers/iio/accel/kxcjk-1013.c1
-rw-r--r--drivers/iio/accel/mma8452.c145
-rw-r--r--drivers/iio/accel/mma9551.c1
-rw-r--r--drivers/iio/accel/mma9553.c1
-rw-r--r--drivers/iio/accel/sca3000.c2
-rw-r--r--drivers/iio/adc/Kconfig86
-rw-r--r--drivers/iio/adc/Makefile4
-rw-r--r--drivers/iio/adc/ad4080.c34
-rw-r--r--drivers/iio/adc/ad4134.c1
-rw-r--r--drivers/iio/adc/ad7173.c3
-rw-r--r--drivers/iio/adc/ad7192.c3
-rw-r--r--drivers/iio/adc/ad7606_spi.c6
-rw-r--r--drivers/iio/adc/ad7768-1.c3
-rw-r--r--drivers/iio/adc/ad7768.c1921
-rw-r--r--drivers/iio/adc/ade9000.c180
-rw-r--r--drivers/iio/adc/adi-axi-adc.c21
-rw-r--r--drivers/iio/adc/at91-sama5d2_adc.c10
-rw-r--r--drivers/iio/adc/axiado_saradc.c277
-rw-r--r--drivers/iio/adc/bcm_iproc_adc.c99
-rw-r--r--drivers/iio/adc/ltc2497-core.c297
-rw-r--r--drivers/iio/adc/ltc2497.c42
-rw-r--r--drivers/iio/adc/ltc2497.h31
-rw-r--r--drivers/iio/adc/max11205.c2
-rw-r--r--drivers/iio/adc/max14001.c1
-rw-r--r--drivers/iio/adc/max40080.c574
-rw-r--r--drivers/iio/adc/mcp3911.c2
-rw-r--r--drivers/iio/adc/meson_saradc.c2
-rw-r--r--drivers/iio/adc/nxp-sar-adc.c2
-rw-r--r--drivers/iio/adc/pac1934.c9
-rw-r--r--drivers/iio/adc/qcom-spmi-iadc.c116
-rw-r--r--drivers/iio/adc/rockchip_saradc.c16
-rw-r--r--drivers/iio/adc/rzg2l_adc.c29
-rw-r--r--drivers/iio/adc/rzt2h_adc.c499
-rw-r--r--drivers/iio/adc/sophgo-cv1800b-adc.c2
-rw-r--r--drivers/iio/adc/stm32-adc.c14
-rw-r--r--drivers/iio/adc/stm32-dfsdm-adc.c4
-rw-r--r--drivers/iio/adc/ti-adc128s052.c8
-rw-r--r--drivers/iio/adc/ti-ads1015.c30
-rw-r--r--drivers/iio/adc/ti-ads1018.c6
-rw-r--r--drivers/iio/adc/ti-ads1100.c188
-rw-r--r--drivers/iio/adc/ti-ads112c04.c524
-rw-r--r--drivers/iio/adc/ti-ads112c14.c1270
-rw-r--r--drivers/iio/adc/ti-ads131m02.c2
-rw-r--r--drivers/iio/adc/ti-ads7950.c4
-rw-r--r--drivers/iio/adc/ti_am335x_adc.c4
-rw-r--r--drivers/iio/amplifiers/ad8366.c2
-rw-r--r--drivers/iio/buffer/industrialio-buffer-dmaengine.c8
-rw-r--r--drivers/iio/chemical/ens160_core.c7
-rw-r--r--drivers/iio/chemical/sps30.c11
-rw-r--r--drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c2
-rw-r--r--drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c5
-rw-r--r--drivers/iio/dac/Kconfig50
-rw-r--r--drivers/iio/dac/Makefile5
-rw-r--r--drivers/iio/dac/ad3530r.c321
-rw-r--r--drivers/iio/dac/ad3552r.c2
-rw-r--r--drivers/iio/dac/ad5529r.c546
-rw-r--r--drivers/iio/dac/ad5755.c3
-rw-r--r--drivers/iio/dac/ad5758.c8
-rw-r--r--drivers/iio/dac/ad8460.c2
-rw-r--r--drivers/iio/dac/mcp47feb02-core.c (renamed from drivers/iio/dac/mcp47feb02.c)393
-rw-r--r--drivers/iio/dac/mcp47feb02-i2c.c145
-rw-r--r--drivers/iio/dac/mcp47feb02-spi.c145
-rw-r--r--drivers/iio/dac/mcp47feb02.h41
-rw-r--r--drivers/iio/dac/mcp4821.c7
-rw-r--r--drivers/iio/dummy/iio_simple_dummy.c2
-rw-r--r--drivers/iio/frequency/Kconfig21
-rw-r--r--drivers/iio/frequency/Makefile1
-rw-r--r--drivers/iio/frequency/ad9910.c2347
-rw-r--r--drivers/iio/frequency/adf4350.c2
-rw-r--r--drivers/iio/frequency/adf4377.c3
-rw-r--r--drivers/iio/gyro/adxrs290.c4
-rw-r--r--drivers/iio/gyro/bmg160_core.c4
-rw-r--r--drivers/iio/gyro/itg3200_buffer.c3
-rw-r--r--drivers/iio/humidity/Kconfig8
-rw-r--r--drivers/iio/humidity/am2315.c34
-rw-r--r--drivers/iio/humidity/ens210.c4
-rw-r--r--drivers/iio/humidity/hts221_core.c168
-rw-r--r--drivers/iio/humidity/hts221_i2c.c11
-rw-r--r--drivers/iio/humidity/hts221_spi.c11
-rw-r--r--drivers/iio/iio_core.h7
-rw-r--r--drivers/iio/imu/adis16400.c8
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600.h5
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c33
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c114
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_core.c26
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c24
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c16
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c16
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_core.c6
-rw-r--r--drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c4
-rw-r--r--drivers/iio/industrialio-backend.c33
-rw-r--r--drivers/iio/industrialio-core.c255
-rw-r--r--drivers/iio/industrialio-gts-helper.c2
-rw-r--r--drivers/iio/light/Kconfig14
-rw-r--r--drivers/iio/light/Makefile1
-rw-r--r--drivers/iio/light/apds9306.c2
-rw-r--r--drivers/iio/light/apds9999.c12
-rw-r--r--drivers/iio/light/cros_ec_light_prox.c2
-rw-r--r--drivers/iio/light/iqs621-als.c24
-rw-r--r--drivers/iio/light/isl29018.c7
-rw-r--r--drivers/iio/light/isl29028.c33
-rw-r--r--drivers/iio/light/lm3533-als.c11
-rw-r--r--drivers/iio/light/ltr390.c6
-rw-r--r--drivers/iio/light/ltr501.c113
-rw-r--r--drivers/iio/light/stk3310.c203
-rw-r--r--drivers/iio/light/tsl2583.c15
-rw-r--r--drivers/iio/light/tsl2772.c16
-rw-r--r--drivers/iio/light/vcnl4000.c3
-rw-r--r--drivers/iio/light/veml3328.c53
-rw-r--r--drivers/iio/light/veml6031x00.c1387
-rw-r--r--drivers/iio/magnetometer/ak8975.c234
-rw-r--r--drivers/iio/position/iqs624-pos.c24
-rw-r--r--drivers/iio/potentiometer/x9250.c7
-rw-r--r--drivers/iio/pressure/Kconfig2
-rw-r--r--drivers/iio/pressure/Makefile2
-rw-r--r--drivers/iio/pressure/bmp280-spi.c2
-rw-r--r--drivers/iio/pressure/cros_ec_baro.c2
-rw-r--r--drivers/iio/pressure/dps310.c313
-rw-r--r--drivers/iio/pressure/rohm-bm1390.c6
-rw-r--r--drivers/iio/proximity/aw96103.c2
-rw-r--r--drivers/iio/temperature/iqs620at-temp.c9
-rw-r--r--drivers/iio/temperature/ltc2983.c19
-rw-r--r--drivers/iio/temperature/mlx90632.c10
-rw-r--r--drivers/iio/temperature/mlx90635.c11
-rw-r--r--drivers/iio/temperature/tmp117.c18
-rw-r--r--drivers/iio/temperature/tsys01.c17
-rw-r--r--drivers/iio/temperature/tsys02d.c12
-rw-r--r--drivers/iio/test/Kconfig13
-rw-r--r--drivers/iio/test/Makefile1
-rw-r--r--drivers/iio/test/iio-test-channel-prefix.c307
-rw-r--r--drivers/iio/test/iio-test-rescale.c4
-rw-r--r--drivers/interconnect/core.c32
-rw-r--r--drivers/interconnect/mediatek/Makefile2
-rw-r--r--drivers/interconnect/qcom/Kconfig57
-rw-r--r--drivers/interconnect/qcom/Makefile2
-rw-r--r--drivers/interconnect/qcom/bcm-voter.c59
-rw-r--r--drivers/interconnect/qcom/bcm-voter.h1
-rw-r--r--drivers/interconnect/qcom/eliza.c18
-rw-r--r--drivers/interconnect/qcom/icc-common.h9
-rw-r--r--drivers/interconnect/qcom/icc-rpm.c105
-rw-r--r--drivers/interconnect/qcom/icc-rpm.h6
-rw-r--r--drivers/interconnect/qcom/icc-rpmh.c71
-rw-r--r--drivers/interconnect/qcom/kuno.c988
-rw-r--r--drivers/interconnect/qcom/milos.c66
-rw-r--r--drivers/interconnect/qcom/msm8976.c3
-rw-r--r--drivers/interconnect/qcom/msm8996.c1
-rw-r--r--drivers/interconnect/qcom/sm6350.c56
-rw-r--r--drivers/interconnect/qcom/sm8650.c70
-rw-r--r--drivers/interconnect/qcom/smd-rpm.c8
-rw-r--r--drivers/interconnect/samsung/exynos.c1
-rw-r--r--drivers/misc/Kconfig15
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/ad525x_dpot.c9
-rw-r--r--drivers/misc/altera-stapl/altera-jtag.c4
-rw-r--r--drivers/misc/cardreader/alcor_pci.c6
-rw-r--r--drivers/misc/cb710/core.c8
-rw-r--r--drivers/misc/cs5535-mfgpt.c33
-rw-r--r--drivers/misc/enclosure.c3
-rw-r--r--drivers/misc/fastrpc.c200
-rw-r--r--drivers/misc/gehc-achc.c2
-rw-r--r--drivers/misc/genwqe/card_base.c8
-rw-r--r--drivers/misc/genwqe/card_ddcb.c7
-rw-r--r--drivers/misc/genwqe/card_utils.c2
-rw-r--r--drivers/misc/ibmvmc.c7
-rw-r--r--drivers/misc/isl29003.c10
-rw-r--r--drivers/misc/issei/Kconfig1
-rw-r--r--drivers/misc/lis3lv02d/lis3lv02d_spi.c6
-rw-r--r--drivers/misc/mei/gsc-me.c2
-rw-r--r--drivers/misc/mei/hw-me.c99
-rw-r--r--drivers/misc/mei/init.c33
-rw-r--r--drivers/misc/mei/mei_dev.h1
-rw-r--r--drivers/misc/mei/pci-csc.c143
-rw-r--r--drivers/misc/mei/vsc-tp.c19
-rw-r--r--drivers/misc/nsm.c7
-rw-r--r--drivers/misc/pch_phub.c8
-rw-r--r--drivers/misc/phantom.c8
-rw-r--r--drivers/misc/qcom-coincell.c45
-rw-r--r--drivers/misc/tc9564-pci.c84
-rw-r--r--drivers/misc/tifm_7xx1.c8
-rw-r--r--drivers/misc/tifm_core.c1
-rw-r--r--drivers/misc/tsl2550.c13
-rw-r--r--drivers/misc/vmw_vmci/vmci_datagram.c2
-rw-r--r--drivers/misc/vmw_vmci/vmci_queue_pair.c2
-rw-r--r--drivers/nvmem/Kconfig15
-rw-r--r--drivers/nvmem/Makefile2
-rw-r--r--drivers/nvmem/core.c126
-rw-r--r--drivers/nvmem/internals.h5
-rw-r--r--drivers/nvmem/layouts.c13
-rw-r--r--drivers/nvmem/layouts/onie-tlv.c1
-rw-r--r--drivers/nvmem/layouts/sl28vpd.c1
-rw-r--r--drivers/nvmem/mt6323-efuse.c83
-rw-r--r--drivers/nvmem/qcom-spmi-sdam.c41
-rw-r--r--drivers/nvmem/rockchip-otp.c15
-rw-r--r--drivers/pci/quirks.c1
-rw-r--r--drivers/peci/controller/peci-aspeed.c8
-rw-r--r--drivers/peci/controller/peci-npcm.c6
-rw-r--r--drivers/phy/qualcomm/Kconfig2
-rw-r--r--drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c52
-rw-r--r--drivers/platform/goldfish/goldfish_pipe.c22
-rw-r--r--drivers/power/reset/qcom-pon.c31
-rw-r--r--drivers/pps/generators/pps_gen.c111
-rw-r--r--drivers/slimbus/messaging.c2
-rw-r--r--drivers/slimbus/qcom-ngd-ctrl.c82
-rw-r--r--drivers/slimbus/slimbus.h14
-rw-r--r--drivers/spmi/spmi-devres.c24
-rw-r--r--drivers/spmi/spmi-mtk-pmif.c42
-rw-r--r--drivers/spmi/spmi-pmic-arb.c2
-rw-r--r--drivers/spmi/spmi.c121
-rw-r--r--drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a2
-rw-r--r--drivers/staging/iio/Kconfig1
-rw-r--r--drivers/staging/iio/Makefile1
-rw-r--r--drivers/staging/iio/accel/Kconfig19
-rw-r--r--drivers/staging/iio/accel/Makefile6
-rw-r--r--drivers/staging/iio/accel/adis16203.c315
-rw-r--r--drivers/staging/iio/adc/ad7816.c33
-rw-r--r--drivers/staging/iio/frequency/ad9832.c6
-rw-r--r--drivers/staging/iio/frequency/ad9834.c6
322 files changed, 18673 insertions, 15825 deletions
diff --git a/drivers/accessibility/speakup/main.c b/drivers/accessibility/speakup/main.c
index e9b7c2761f6f..a7c14531f2ce 100644
--- a/drivers/accessibility/speakup/main.c
+++ b/drivers/accessibility/speakup/main.c
@@ -1980,7 +1980,7 @@ static int handle_goto(struct vc_data *vc, u_char type, u_char ch, u16 key)
maxlen = (*goto_buf >= '0') ? 3 : 4;
if ((ch == '+' || ch == '-') && num == 1)
return 1;
- if (ch >= '0' && ch <= '9' && num < maxlen)
+ if (isdigit(ch) && num < maxlen)
return 1;
if (num < maxlen - 1 || num > maxlen)
goto oops;
diff --git a/drivers/accessibility/speakup/selection.c b/drivers/accessibility/speakup/selection.c
index 1713ce4e0ba5..a04a7e8c307b 100644
--- a/drivers/accessibility/speakup/selection.c
+++ b/drivers/accessibility/speakup/selection.c
@@ -61,6 +61,9 @@ static struct speakup_selection_work speakup_sel_work = {
int speakup_set_selection(struct tty_struct *tty)
{
+ if (!tty)
+ return -ENODEV;
+
/* we get kref here first in order to avoid a subtle race when
* cancelling selection work. getting kref first establishes the
* invariant that if speakup_sel_work.tty is not NULL when
@@ -120,6 +123,9 @@ static struct speakup_selection_work speakup_paste_work = {
int speakup_paste_selection(struct tty_struct *tty)
{
+ if (!tty)
+ return -ENODEV;
+
tty_kref_get(tty);
if (cmpxchg(&speakup_paste_work.tty, NULL, tty)) {
tty_kref_put(tty);
diff --git a/drivers/accessibility/speakup/serialio.c b/drivers/accessibility/speakup/serialio.c
index 3418ea31d28f..c344296be9f6 100644
--- a/drivers/accessibility/speakup/serialio.c
+++ b/drivers/accessibility/speakup/serialio.c
@@ -8,22 +8,8 @@
#include "serialio.h"
#include <linux/serial_core.h>
-/* WARNING: Do not change this to <linux/serial.h> without testing that
- * SERIAL_PORT_DFNS does get defined to the appropriate value.
- */
-#include <asm/serial.h>
+#include <linux/serial.h>
-#ifndef SERIAL_PORT_DFNS
-#define SERIAL_PORT_DFNS
-#endif
-
-static void start_serial_interrupt(int irq);
-
-static const struct old_serial_port rs_table[] = {
- SERIAL_PORT_DFNS
-};
-
-static const struct old_serial_port *serstate;
static int timeouts;
static int spk_serial_out(struct spk_synth *in_synth, const char ch);
@@ -45,111 +31,6 @@ struct spk_io_ops spk_serial_io_ops = {
};
EXPORT_SYMBOL_GPL(spk_serial_io_ops);
-const struct old_serial_port *spk_serial_init(int index)
-{
- int baud = 9600, quot = 0;
- unsigned int cval = 0;
- int cflag = CREAD | HUPCL | CLOCAL | B9600 | CS8;
- const struct old_serial_port *ser;
- int err;
-
- if (index >= ARRAY_SIZE(rs_table)) {
- pr_info("no port info for ttyS%d\n", index);
- return NULL;
- }
- ser = rs_table + index;
-
- /* Divisor, byte size and parity */
- quot = ser->baud_base / baud;
- cval = cflag & (CSIZE | CSTOPB);
-#if defined(__powerpc__) || defined(__alpha__)
- cval >>= 8;
-#else /* !__powerpc__ && !__alpha__ */
- cval >>= 4;
-#endif /* !__powerpc__ && !__alpha__ */
- if (cflag & PARENB)
- cval |= UART_LCR_PARITY;
- if (!(cflag & PARODD))
- cval |= UART_LCR_EPAR;
- if (synth_request_region(ser->port, 8)) {
- /* try to take it back. */
- pr_info("Ports not available, trying to steal them\n");
- __release_region(&ioport_resource, ser->port, 8);
- err = synth_request_region(ser->port, 8);
- if (err) {
- pr_warn("Unable to allocate port at %x, errno %i",
- ser->port, err);
- return NULL;
- }
- }
-
- /* Disable UART interrupts, set DTR and RTS high
- * and set speed.
- */
- outb(cval | UART_LCR_DLAB, ser->port + UART_LCR); /* set DLAB */
- outb(quot & 0xff, ser->port + UART_DLL); /* LS of divisor */
- outb(quot >> 8, ser->port + UART_DLM); /* MS of divisor */
- outb(cval, ser->port + UART_LCR); /* reset DLAB */
-
- /* Turn off Interrupts */
- outb(0, ser->port + UART_IER);
- outb(UART_MCR_DTR | UART_MCR_RTS, ser->port + UART_MCR);
-
- /* If we read 0xff from the LSR, there is no UART here. */
- if (inb(ser->port + UART_LSR) == 0xff) {
- synth_release_region(ser->port, 8);
- serstate = NULL;
- return NULL;
- }
-
- mdelay(1);
- speakup_info.port_tts = ser->port;
- serstate = ser;
-
- start_serial_interrupt(ser->irq);
-
- return ser;
-}
-
-static irqreturn_t synth_readbuf_handler(int irq, void *dev_id)
-{
- unsigned long flags;
- int c;
-
- spin_lock_irqsave(&speakup_info.spinlock, flags);
- while (inb_p(speakup_info.port_tts + UART_LSR) & UART_LSR_DR) {
- c = inb_p(speakup_info.port_tts + UART_RX);
- synth->read_buff_add((u_char)c);
- }
- spin_unlock_irqrestore(&speakup_info.spinlock, flags);
- return IRQ_HANDLED;
-}
-
-static void start_serial_interrupt(int irq)
-{
- int rv;
-
- if (!synth->read_buff_add)
- return;
-
- rv = request_irq(irq, synth_readbuf_handler, IRQF_SHARED,
- "serial", (void *)synth_readbuf_handler);
-
- if (rv)
- pr_err("Unable to request Speakup serial I R Q\n");
- /* Set MCR */
- outb(UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2,
- speakup_info.port_tts + UART_MCR);
- /* Turn on Interrupts */
- outb(UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI,
- speakup_info.port_tts + UART_IER);
- inb(speakup_info.port_tts + UART_LSR);
- inb(speakup_info.port_tts + UART_RX);
- inb(speakup_info.port_tts + UART_IIR);
- inb(speakup_info.port_tts + UART_MSR);
- outb(1, speakup_info.port_tts + UART_FCR); /* Turn FIFO On */
-}
-
static void spk_serial_send_xchar(struct spk_synth *synth, char ch)
{
int timeout = SPK_XMITR_TIMEOUT;
@@ -169,50 +50,6 @@ static void spk_serial_tiocmset(struct spk_synth *in_synth, unsigned int set, un
outb((old & ~clear) | set, speakup_info.port_tts + UART_MCR);
}
-int spk_serial_synth_probe(struct spk_synth *synth)
-{
- const struct old_serial_port *ser;
- int failed = 0;
-
- if ((synth->ser >= SPK_LO_TTY) && (synth->ser <= SPK_HI_TTY)) {
- ser = spk_serial_init(synth->ser);
- if (!ser) {
- failed = -1;
- } else {
- outb_p(0, ser->port);
- mdelay(1);
- outb_p('\r', ser->port);
- }
- } else {
- failed = -1;
- pr_warn("ttyS%i is an invalid port\n", synth->ser);
- }
- if (failed) {
- pr_info("%s: not found\n", synth->long_name);
- return -ENODEV;
- }
- pr_info("%s: ttyS%i, Driver Version %s\n",
- synth->long_name, synth->ser, synth->version);
- synth->alive = 1;
- return 0;
-}
-EXPORT_SYMBOL_GPL(spk_serial_synth_probe);
-
-void spk_stop_serial_interrupt(void)
-{
- if (speakup_info.port_tts == 0)
- return;
-
- if (!synth->read_buff_add)
- return;
-
- /* Turn off interrupts */
- outb(0, speakup_info.port_tts + UART_IER);
- /* Free IRQ */
- free_irq(serstate->irq, (void *)synth_readbuf_handler);
-}
-EXPORT_SYMBOL_GPL(spk_stop_serial_interrupt);
-
static int spk_serial_wait_for_xmitr(struct spk_synth *in_synth)
{
int tmout = SPK_XMITR_TIMEOUT;
@@ -288,31 +125,3 @@ static int spk_serial_out(struct spk_synth *in_synth, const char ch)
}
return 0;
}
-
-const char *spk_serial_synth_immediate(struct spk_synth *synth,
- const char *buff)
-{
- u_char ch;
-
- while ((ch = *buff)) {
- if (ch == '\n')
- ch = synth->procspeech;
- if (spk_serial_wait_for_xmitr(synth))
- outb(ch, speakup_info.port_tts);
- else
- return buff;
- buff++;
- }
- return NULL;
-}
-EXPORT_SYMBOL_GPL(spk_serial_synth_immediate);
-
-void spk_serial_release(struct spk_synth *synth)
-{
- spk_stop_serial_interrupt();
- if (speakup_info.port_tts == 0)
- return;
- synth_release_region(speakup_info.port_tts, 8);
- speakup_info.port_tts = 0;
-}
-EXPORT_SYMBOL_GPL(spk_serial_release);
diff --git a/drivers/accessibility/speakup/serialio.h b/drivers/accessibility/speakup/serialio.h
index b4f9a1925b81..189c0dd9ce41 100644
--- a/drivers/accessibility/speakup/serialio.h
+++ b/drivers/accessibility/speakup/serialio.h
@@ -8,31 +8,14 @@
#include "spk_priv.h"
-/*
- * this is cut&paste from 8250.h. Get rid of the structure, the definitions
- * and this whole broken driver.
- */
-struct old_serial_port {
- unsigned int uart; /* unused */
- unsigned int baud_base;
- unsigned int port;
- unsigned int irq;
- upf_t flags; /* unused */
-};
-
/* countdown values for serial timeouts in us */
#define SPK_SERIAL_TIMEOUT SPK_SYNTH_TIMEOUT
/* countdown values transmitter/dsr timeouts in us */
#define SPK_XMITR_TIMEOUT 100000
/* countdown values cts timeouts in us */
#define SPK_CTS_TIMEOUT 100000
-/* check ttyS0 ... ttyS3 */
-#define SPK_LO_TTY 0
-#define SPK_HI_TTY 3
/* # of timeouts permitted before disable */
#define NUM_DISABLE_TIMEOUTS 3
-/* buffer timeout in ms */
-#define SPK_TIMEOUT 100
#define spk_serial_tx_busy() \
(!uart_lsr_tx_empty(inb(speakup_info.port_tts + UART_LSR)))
diff --git a/drivers/accessibility/speakup/speakup_acntpc.c b/drivers/accessibility/speakup/speakup_acntpc.c
index a27e6bbf05da..1cc6147cafac 100644
--- a/drivers/accessibility/speakup/speakup_acntpc.c
+++ b/drivers/accessibility/speakup/speakup_acntpc.c
@@ -305,7 +305,6 @@ static int synth_probe(struct spk_synth *synth)
static void accent_release(struct spk_synth *synth)
{
- spk_stop_serial_interrupt();
if (speakup_info.port_tts)
synth_release_region(speakup_info.port_tts - 1,
SYNTH_IO_EXTENT);
diff --git a/drivers/accessibility/speakup/speakup_decpc.c b/drivers/accessibility/speakup/speakup_decpc.c
index 1bf36d1e5477..4837cac8bafa 100644
--- a/drivers/accessibility/speakup/speakup_decpc.c
+++ b/drivers/accessibility/speakup/speakup_decpc.c
@@ -487,7 +487,6 @@ static int synth_probe(struct spk_synth *synth)
static void dtpc_release(struct spk_synth *synth)
{
- spk_stop_serial_interrupt();
if (speakup_info.port_tts)
synth_release_region(speakup_info.port_tts, SYNTH_IO_EXTENT);
speakup_info.port_tts = 0;
diff --git a/drivers/accessibility/speakup/speakup_dtlk.c b/drivers/accessibility/speakup/speakup_dtlk.c
index fa826568937b..0a57f7ea894c 100644
--- a/drivers/accessibility/speakup/speakup_dtlk.c
+++ b/drivers/accessibility/speakup/speakup_dtlk.c
@@ -377,7 +377,6 @@ static int synth_probe(struct spk_synth *synth)
static void dtlk_release(struct spk_synth *synth)
{
- spk_stop_serial_interrupt();
if (speakup_info.port_tts)
synth_release_region(speakup_info.port_tts - 1,
SYNTH_IO_EXTENT);
diff --git a/drivers/accessibility/speakup/speakup_keypc.c b/drivers/accessibility/speakup/speakup_keypc.c
index 9356f6379560..1e2edc5c4b8b 100644
--- a/drivers/accessibility/speakup/speakup_keypc.c
+++ b/drivers/accessibility/speakup/speakup_keypc.c
@@ -306,7 +306,6 @@ static int synth_probe(struct spk_synth *synth)
static void keynote_release(struct spk_synth *synth)
{
- spk_stop_serial_interrupt();
if (synth_port)
synth_release_region(synth_port, SYNTH_IO_EXTENT);
synth_port = 0;
diff --git a/drivers/accessibility/speakup/spk_priv.h b/drivers/accessibility/speakup/spk_priv.h
index 9da57ead17cb..704ff9f120d3 100644
--- a/drivers/accessibility/speakup/spk_priv.h
+++ b/drivers/accessibility/speakup/spk_priv.h
@@ -32,9 +32,6 @@
#define SYNTH_DEFAULT_DEV "ttyS0"
#define SYNTH_DEFAULT_SER 0
-const struct old_serial_port *spk_serial_init(int index);
-void spk_stop_serial_interrupt(void);
-void spk_serial_release(struct spk_synth *synth);
void spk_ttyio_release(struct spk_synth *synth);
void spk_ttyio_register_ldisc(void);
void spk_ttyio_unregister_ldisc(void);
@@ -49,10 +46,7 @@ ssize_t spk_var_show(struct kobject *kobj, struct kobj_attribute *attr,
ssize_t spk_var_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
-int spk_serial_synth_probe(struct spk_synth *synth);
int spk_ttyio_synth_probe(struct spk_synth *synth);
-const char *spk_serial_synth_immediate(struct spk_synth *synth,
- const char *buff);
const char *spk_ttyio_synth_immediate(struct spk_synth *synth,
const char *buff);
void spk_do_catch_up(struct spk_synth *synth);
diff --git a/drivers/accessibility/speakup/varhandlers.c b/drivers/accessibility/speakup/varhandlers.c
index 462f8d879053..c6d3b4cc1af8 100644
--- a/drivers/accessibility/speakup/varhandlers.c
+++ b/drivers/accessibility/speakup/varhandlers.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ctype.h>
+#include <linux/string.h>
#include "spk_types.h"
#include "spk_priv.h"
#include "speakup.h"
@@ -265,10 +266,10 @@ int spk_set_string_var(const char *page, struct st_var_header *var, int len)
if (!var->p_val)
var->p_val = var_data->u.s.default_val;
if (var->p_val != var_data->u.s.default_val)
- strcpy((char *)var->p_val, var_data->u.s.default_val);
+ strscpy((char *)var->p_val, var_data->u.s.default_val, MAXVARLEN + 1);
return -ERESTART;
} else if (var->p_val) {
- strcpy((char *)var->p_val, page);
+ strscpy((char *)var->p_val, page, MAXVARLEN + 1);
} else {
return -E2BIG;
}
diff --git a/drivers/android/Kconfig b/drivers/android/Kconfig
index 606a9d07f774..c920d52c0221 100644
--- a/drivers/android/Kconfig
+++ b/drivers/android/Kconfig
@@ -1,10 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
menu "Android"
-config ANDROID_BINDER_IPC
+config ANDROID_BINDER_IPC_RUST
bool "Android Binder IPC Driver"
depends on MMU
depends on NET
+ depends on RUST
default n
help
Binder is used in Android for both communication between processes,
@@ -14,34 +15,9 @@ config ANDROID_BINDER_IPC
Android process, using Binder to identify, invoke and pass arguments
between said processes.
-config ANDROID_BINDER_IPC_RUST
- bool "Rust version of Android Binder IPC Driver"
- depends on RUST && MMU && NET && !ANDROID_BINDER_IPC
- help
- This enables the Rust implementation of the Binder driver.
-
- Binder is used in Android for both communication between processes,
- and remote method invocation.
-
- This means one Android process can call a method/routine in another
- Android process, using Binder to identify, invoke and pass arguments
- between said processes.
-
-config ANDROID_BINDERFS
- bool "Android Binderfs filesystem"
- depends on ANDROID_BINDER_IPC
- default n
- help
- Binderfs is a pseudo-filesystem for the Android Binder IPC driver
- which can be mounted per-ipc namespace allowing to run multiple
- instances of Android.
- Each binderfs mount initially only contains a binder-control device.
- It can be used to dynamically allocate new binder IPC devices via
- ioctls.
-
config ANDROID_BINDER_DEVICES
string "Android Binder devices"
- depends on ANDROID_BINDER_IPC || ANDROID_BINDER_IPC_RUST
+ depends on ANDROID_BINDER_IPC_RUST
default "binder,hwbinder,vndbinder"
help
Default value for the binder.devices parameter.
@@ -51,15 +27,4 @@ config ANDROID_BINDER_DEVICES
created. Each binder device has its own context manager, and is
therefore logically separated from the other devices.
-config ANDROID_BINDER_ALLOC_KUNIT_TEST
- tristate "KUnit Tests for Android Binder Alloc" if !KUNIT_ALL_TESTS
- depends on ANDROID_BINDER_IPC && KUNIT
- default KUNIT_ALL_TESTS
- help
- This feature builds the binder alloc KUnit tests.
-
- Each test case runs using a pared-down binder_alloc struct and
- test-specific freelist, which allows this KUnit module to be loaded
- for testing without interfering with a running system.
-
endmenu
diff --git a/drivers/android/Makefile b/drivers/android/Makefile
index e0c650d3898e..f83d39d16f6e 100644
--- a/drivers/android/Makefile
+++ b/drivers/android/Makefile
@@ -1,7 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
ccflags-y += -I$(src) # needed for trace events
-obj-$(CONFIG_ANDROID_BINDERFS) += binderfs.o
-obj-$(CONFIG_ANDROID_BINDER_IPC) += binder.o binder_alloc.o binder_netlink.o
-obj-$(CONFIG_ANDROID_BINDER_ALLOC_KUNIT_TEST) += tests/
obj-$(CONFIG_ANDROID_BINDER_IPC_RUST) += binder/
diff --git a/drivers/android/binder.c b/drivers/android/binder.c
deleted file mode 100644
index bc8bc9ee43a5..000000000000
--- a/drivers/android/binder.c
+++ /dev/null
@@ -1,7187 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/* binder.c
- *
- * Android IPC Subsystem
- *
- * Copyright (C) 2007-2008 Google, Inc.
- */
-
-/*
- * Locking overview
- *
- * There are 3 main spinlocks which must be acquired in the
- * order shown:
- *
- * 1) proc->outer_lock : protects binder_ref
- * binder_proc_lock() and binder_proc_unlock() are
- * used to acq/rel.
- * 2) node->lock : protects most fields of binder_node.
- * binder_node_lock() and binder_node_unlock() are
- * used to acq/rel
- * 3) proc->inner_lock : protects the thread and node lists
- * (proc->threads, proc->waiting_threads, proc->nodes)
- * and all todo lists associated with the binder_proc
- * (proc->todo, thread->todo, proc->delivered_death and
- * node->async_todo), as well as thread->transaction_stack
- * binder_inner_proc_lock() and binder_inner_proc_unlock()
- * are used to acq/rel
- *
- * Any lock under procA must never be nested under any lock at the same
- * level or below on procB.
- *
- * Functions that require a lock held on entry indicate which lock
- * in the suffix of the function name:
- *
- * foo_olocked() : requires node->outer_lock
- * foo_nlocked() : requires node->lock
- * foo_ilocked() : requires proc->inner_lock
- * foo_oilocked(): requires proc->outer_lock and proc->inner_lock
- * foo_nilocked(): requires node->lock and proc->inner_lock
- * ...
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/fdtable.h>
-#include <linux/file.h>
-#include <linux/freezer.h>
-#include <linux/fs.h>
-#include <linux/list.h>
-#include <linux/miscdevice.h>
-#include <linux/module.h>
-#include <linux/mutex.h>
-#include <linux/nsproxy.h>
-#include <linux/poll.h>
-#include <linux/debugfs.h>
-#include <linux/rbtree.h>
-#include <linux/sched/signal.h>
-#include <linux/sched/mm.h>
-#include <linux/seq_file.h>
-#include <linux/string.h>
-#include <linux/uaccess.h>
-#include <linux/pid_namespace.h>
-#include <linux/security.h>
-#include <linux/spinlock.h>
-#include <linux/ratelimit.h>
-#include <linux/syscalls.h>
-#include <linux/task_work.h>
-#include <linux/sizes.h>
-#include <linux/ktime.h>
-
-#include <kunit/visibility.h>
-
-#include <uapi/linux/android/binder.h>
-
-#include <linux/cacheflush.h>
-
-#include "binder_netlink.h"
-#include "binder_internal.h"
-#include "binder_trace.h"
-
-static HLIST_HEAD(binder_deferred_list);
-static DEFINE_MUTEX(binder_deferred_lock);
-
-static HLIST_HEAD(binder_devices);
-static DEFINE_SPINLOCK(binder_devices_lock);
-
-static HLIST_HEAD(binder_procs);
-static DEFINE_MUTEX(binder_procs_lock);
-
-static HLIST_HEAD(binder_dead_nodes);
-static DEFINE_SPINLOCK(binder_dead_nodes_lock);
-
-static struct dentry *binder_debugfs_dir_entry_root;
-static struct dentry *binder_debugfs_dir_entry_proc;
-static atomic_t binder_last_id;
-
-static int proc_show(struct seq_file *m, void *unused);
-DEFINE_SHOW_ATTRIBUTE(proc);
-
-#define FORBIDDEN_MMAP_FLAGS (VM_WRITE)
-
-enum {
- BINDER_DEBUG_USER_ERROR = 1U << 0,
- BINDER_DEBUG_FAILED_TRANSACTION = 1U << 1,
- BINDER_DEBUG_DEAD_TRANSACTION = 1U << 2,
- BINDER_DEBUG_OPEN_CLOSE = 1U << 3,
- BINDER_DEBUG_DEAD_BINDER = 1U << 4,
- BINDER_DEBUG_DEATH_NOTIFICATION = 1U << 5,
- BINDER_DEBUG_READ_WRITE = 1U << 6,
- BINDER_DEBUG_USER_REFS = 1U << 7,
- BINDER_DEBUG_THREADS = 1U << 8,
- BINDER_DEBUG_TRANSACTION = 1U << 9,
- BINDER_DEBUG_TRANSACTION_COMPLETE = 1U << 10,
- BINDER_DEBUG_FREE_BUFFER = 1U << 11,
- BINDER_DEBUG_INTERNAL_REFS = 1U << 12,
- BINDER_DEBUG_PRIORITY_CAP = 1U << 13,
- BINDER_DEBUG_SPINLOCKS = 1U << 14,
-};
-static uint32_t binder_debug_mask = BINDER_DEBUG_USER_ERROR |
- BINDER_DEBUG_FAILED_TRANSACTION | BINDER_DEBUG_DEAD_TRANSACTION;
-module_param_named(debug_mask, binder_debug_mask, uint, 0644);
-
-char *binder_devices_param = CONFIG_ANDROID_BINDER_DEVICES;
-module_param_named(devices, binder_devices_param, charp, 0444);
-
-static DECLARE_WAIT_QUEUE_HEAD(binder_user_error_wait);
-static int binder_stop_on_user_error;
-
-static int binder_set_stop_on_user_error(const char *val,
- const struct kernel_param *kp)
-{
- int ret;
-
- ret = param_set_int(val, kp);
- if (binder_stop_on_user_error < 2)
- wake_up(&binder_user_error_wait);
- return ret;
-}
-module_param_call(stop_on_user_error, binder_set_stop_on_user_error,
- param_get_int, &binder_stop_on_user_error, 0644);
-
-static __printf(2, 3) void binder_debug(int mask, const char *format, ...)
-{
- struct va_format vaf;
- va_list args;
-
- if (binder_debug_mask & mask) {
- va_start(args, format);
- vaf.va = &args;
- vaf.fmt = format;
- pr_info_ratelimited("%pV", &vaf);
- va_end(args);
- }
-}
-
-#define binder_txn_error(x...) \
- binder_debug(BINDER_DEBUG_FAILED_TRANSACTION, x)
-
-static __printf(1, 2) void binder_user_error(const char *format, ...)
-{
- struct va_format vaf;
- va_list args;
-
- if (binder_debug_mask & BINDER_DEBUG_USER_ERROR) {
- va_start(args, format);
- vaf.va = &args;
- vaf.fmt = format;
- pr_info_ratelimited("%pV", &vaf);
- va_end(args);
- }
-
- if (binder_stop_on_user_error)
- binder_stop_on_user_error = 2;
-}
-
-#define binder_set_extended_error(ee, _id, _command, _param) \
- do { \
- (ee)->id = _id; \
- (ee)->command = _command; \
- (ee)->param = _param; \
- } while (0)
-
-#define to_flat_binder_object(hdr) \
- container_of(hdr, struct flat_binder_object, hdr)
-
-#define to_binder_fd_object(hdr) container_of(hdr, struct binder_fd_object, hdr)
-
-#define to_binder_buffer_object(hdr) \
- container_of(hdr, struct binder_buffer_object, hdr)
-
-#define to_binder_fd_array_object(hdr) \
- container_of(hdr, struct binder_fd_array_object, hdr)
-
-static struct binder_stats binder_stats;
-
-static inline void binder_stats_deleted(enum binder_stat_types type)
-{
- atomic_inc(&binder_stats.obj_deleted[type]);
-}
-
-static inline void binder_stats_created(enum binder_stat_types type)
-{
- atomic_inc(&binder_stats.obj_created[type]);
-}
-
-struct binder_transaction_log_entry {
- int debug_id;
- int debug_id_done;
- int call_type;
- int from_proc;
- int from_thread;
- int target_handle;
- int to_proc;
- int to_thread;
- int to_node;
- int data_size;
- int offsets_size;
- int return_error_line;
- uint32_t return_error;
- uint32_t return_error_param;
- char context_name[BINDERFS_MAX_NAME + 1];
-};
-
-struct binder_transaction_log {
- atomic_t cur;
- bool full;
- struct binder_transaction_log_entry entry[32];
-};
-
-static struct binder_transaction_log binder_transaction_log;
-static struct binder_transaction_log binder_transaction_log_failed;
-
-static struct binder_transaction_log_entry *binder_transaction_log_add(
- struct binder_transaction_log *log)
-{
- struct binder_transaction_log_entry *e;
- unsigned int cur = atomic_inc_return(&log->cur);
-
- if (cur >= ARRAY_SIZE(log->entry))
- log->full = true;
- e = &log->entry[cur % ARRAY_SIZE(log->entry)];
- WRITE_ONCE(e->debug_id_done, 0);
- /*
- * write-barrier to synchronize access to e->debug_id_done.
- * We make sure the initialized 0 value is seen before
- * memset() other fields are zeroed by memset.
- */
- smp_wmb();
- memset(e, 0, sizeof(*e));
- return e;
-}
-
-enum binder_deferred_state {
- BINDER_DEFERRED_FLUSH = 0x01,
- BINDER_DEFERRED_RELEASE = 0x02,
-};
-
-enum {
- BINDER_LOOPER_STATE_REGISTERED = 0x01,
- BINDER_LOOPER_STATE_ENTERED = 0x02,
- BINDER_LOOPER_STATE_EXITED = 0x04,
- BINDER_LOOPER_STATE_INVALID = 0x08,
- BINDER_LOOPER_STATE_WAITING = 0x10,
- BINDER_LOOPER_STATE_POLL = 0x20,
-};
-
-/**
- * binder_proc_lock() - Acquire outer lock for given binder_proc
- * @proc: struct binder_proc to acquire
- *
- * Acquires proc->outer_lock. Used to protect binder_ref
- * structures associated with the given proc.
- */
-#define binder_proc_lock(proc) _binder_proc_lock(proc, __LINE__)
-static void
-_binder_proc_lock(struct binder_proc *proc, int line)
- __acquires(&proc->outer_lock)
-{
- binder_debug(BINDER_DEBUG_SPINLOCKS,
- "%s: line=%d\n", __func__, line);
- spin_lock(&proc->outer_lock);
-}
-
-/**
- * binder_proc_unlock() - Release outer lock for given binder_proc
- * @proc: struct binder_proc to acquire
- *
- * Release lock acquired via binder_proc_lock()
- */
-#define binder_proc_unlock(proc) _binder_proc_unlock(proc, __LINE__)
-static void
-_binder_proc_unlock(struct binder_proc *proc, int line)
- __releases(&proc->outer_lock)
-{
- binder_debug(BINDER_DEBUG_SPINLOCKS,
- "%s: line=%d\n", __func__, line);
- spin_unlock(&proc->outer_lock);
-}
-
-/**
- * binder_inner_proc_lock() - Acquire inner lock for given binder_proc
- * @proc: struct binder_proc to acquire
- *
- * Acquires proc->inner_lock. Used to protect todo lists
- */
-#define binder_inner_proc_lock(proc) _binder_inner_proc_lock(proc, __LINE__)
-static void
-_binder_inner_proc_lock(struct binder_proc *proc, int line)
- __acquires(&proc->inner_lock)
-{
- binder_debug(BINDER_DEBUG_SPINLOCKS,
- "%s: line=%d\n", __func__, line);
- spin_lock(&proc->inner_lock);
-}
-
-/**
- * binder_inner_proc_unlock() - Release inner lock for given binder_proc
- * @proc: struct binder_proc to acquire
- *
- * Release lock acquired via binder_inner_proc_lock()
- */
-#define binder_inner_proc_unlock(proc) _binder_inner_proc_unlock(proc, __LINE__)
-static void
-_binder_inner_proc_unlock(struct binder_proc *proc, int line)
- __releases(&proc->inner_lock)
-{
- binder_debug(BINDER_DEBUG_SPINLOCKS,
- "%s: line=%d\n", __func__, line);
- spin_unlock(&proc->inner_lock);
-}
-
-/**
- * binder_node_lock() - Acquire spinlock for given binder_node
- * @node: struct binder_node to acquire
- *
- * Acquires node->lock. Used to protect binder_node fields
- */
-#define binder_node_lock(node) _binder_node_lock(node, __LINE__)
-static void
-_binder_node_lock(struct binder_node *node, int line)
- __acquires(&node->lock)
-{
- binder_debug(BINDER_DEBUG_SPINLOCKS,
- "%s: line=%d\n", __func__, line);
- spin_lock(&node->lock);
-}
-
-/**
- * binder_node_unlock() - Release spinlock for given binder_proc
- * @node: struct binder_node to acquire
- *
- * Release lock acquired via binder_node_lock()
- */
-#define binder_node_unlock(node) _binder_node_unlock(node, __LINE__)
-static void
-_binder_node_unlock(struct binder_node *node, int line)
- __releases(&node->lock)
-{
- binder_debug(BINDER_DEBUG_SPINLOCKS,
- "%s: line=%d\n", __func__, line);
- spin_unlock(&node->lock);
-}
-
-/**
- * binder_node_inner_lock() - Acquire node and inner locks
- * @node: struct binder_node to acquire
- *
- * Acquires node->lock. If node->proc also acquires
- * proc->inner_lock. Used to protect binder_node fields
- */
-#define binder_node_inner_lock(node) _binder_node_inner_lock(node, __LINE__)
-static void
-_binder_node_inner_lock(struct binder_node *node, int line)
- __acquires(&node->lock) __acquires(&node->proc->inner_lock)
-{
- binder_debug(BINDER_DEBUG_SPINLOCKS,
- "%s: line=%d\n", __func__, line);
- spin_lock(&node->lock);
- if (node->proc)
- binder_inner_proc_lock(node->proc);
- else
- /* annotation for sparse */
- __acquire(&node->proc->inner_lock);
-}
-
-/**
- * binder_node_inner_unlock() - Release node and inner locks
- * @node: struct binder_node to acquire
- *
- * Release lock acquired via binder_node_lock()
- */
-#define binder_node_inner_unlock(node) _binder_node_inner_unlock(node, __LINE__)
-static void
-_binder_node_inner_unlock(struct binder_node *node, int line)
- __releases(&node->lock) __releases(&node->proc->inner_lock)
-{
- struct binder_proc *proc = node->proc;
-
- binder_debug(BINDER_DEBUG_SPINLOCKS,
- "%s: line=%d\n", __func__, line);
- if (proc)
- binder_inner_proc_unlock(proc);
- else
- /* annotation for sparse */
- __release(&node->proc->inner_lock);
- spin_unlock(&node->lock);
-}
-
-static bool binder_worklist_empty_ilocked(struct list_head *list)
-{
- return list_empty(list);
-}
-
-/**
- * binder_worklist_empty() - Check if no items on the work list
- * @proc: binder_proc associated with list
- * @list: list to check
- *
- * Return: true if there are no items on list, else false
- */
-static bool binder_worklist_empty(struct binder_proc *proc,
- struct list_head *list)
-{
- bool ret;
-
- binder_inner_proc_lock(proc);
- ret = binder_worklist_empty_ilocked(list);
- binder_inner_proc_unlock(proc);
- return ret;
-}
-
-/**
- * binder_enqueue_work_ilocked() - Add an item to the work list
- * @work: struct binder_work to add to list
- * @target_list: list to add work to
- *
- * Adds the work to the specified list. Asserts that work
- * is not already on a list.
- *
- * Requires the proc->inner_lock to be held.
- */
-static void
-binder_enqueue_work_ilocked(struct binder_work *work,
- struct list_head *target_list)
-{
- BUG_ON(target_list == NULL);
- BUG_ON(work->entry.next && !list_empty(&work->entry));
- list_add_tail(&work->entry, target_list);
-}
-
-/**
- * binder_enqueue_deferred_thread_work_ilocked() - Add deferred thread work
- * @thread: thread to queue work to
- * @work: struct binder_work to add to list
- *
- * Adds the work to the todo list of the thread. Doesn't set the process_todo
- * flag, which means that (if it wasn't already set) the thread will go to
- * sleep without handling this work when it calls read.
- *
- * Requires the proc->inner_lock to be held.
- */
-static void
-binder_enqueue_deferred_thread_work_ilocked(struct binder_thread *thread,
- struct binder_work *work)
-{
- WARN_ON(!list_empty(&thread->waiting_thread_node));
- binder_enqueue_work_ilocked(work, &thread->todo);
-}
-
-/**
- * binder_enqueue_thread_work_ilocked() - Add an item to the thread work list
- * @thread: thread to queue work to
- * @work: struct binder_work to add to list
- *
- * Adds the work to the todo list of the thread, and enables processing
- * of the todo queue.
- *
- * Requires the proc->inner_lock to be held.
- */
-static void
-binder_enqueue_thread_work_ilocked(struct binder_thread *thread,
- struct binder_work *work)
-{
- WARN_ON(!list_empty(&thread->waiting_thread_node));
- binder_enqueue_work_ilocked(work, &thread->todo);
-
- /* (e)poll-based threads require an explicit wakeup signal when
- * queuing their own work; they rely on these events to consume
- * messages without I/O block. Without it, threads risk waiting
- * indefinitely without handling the work.
- */
- if (thread->looper & BINDER_LOOPER_STATE_POLL &&
- thread->pid == current->pid && !thread->process_todo)
- wake_up_interruptible_sync(&thread->wait);
-
- thread->process_todo = true;
-}
-
-/**
- * binder_enqueue_thread_work() - Add an item to the thread work list
- * @thread: thread to queue work to
- * @work: struct binder_work to add to list
- *
- * Adds the work to the todo list of the thread, and enables processing
- * of the todo queue.
- */
-static void
-binder_enqueue_thread_work(struct binder_thread *thread,
- struct binder_work *work)
-{
- binder_inner_proc_lock(thread->proc);
- binder_enqueue_thread_work_ilocked(thread, work);
- binder_inner_proc_unlock(thread->proc);
-}
-
-static void
-binder_dequeue_work_ilocked(struct binder_work *work)
-{
- list_del_init(&work->entry);
-}
-
-/**
- * binder_dequeue_work() - Removes an item from the work list
- * @proc: binder_proc associated with list
- * @work: struct binder_work to remove from list
- *
- * Removes the specified work item from whatever list it is on.
- * Can safely be called if work is not on any list.
- */
-static void
-binder_dequeue_work(struct binder_proc *proc, struct binder_work *work)
-{
- binder_inner_proc_lock(proc);
- binder_dequeue_work_ilocked(work);
- binder_inner_proc_unlock(proc);
-}
-
-static struct binder_work *binder_dequeue_work_head_ilocked(
- struct list_head *list)
-{
- struct binder_work *w;
-
- w = list_first_entry_or_null(list, struct binder_work, entry);
- if (w)
- list_del_init(&w->entry);
- return w;
-}
-
-static void
-binder_defer_work(struct binder_proc *proc, enum binder_deferred_state defer);
-static void binder_free_thread(struct binder_thread *thread);
-static void binder_free_proc(struct binder_proc *proc);
-static void binder_inc_node_tmpref_ilocked(struct binder_node *node);
-
-static bool binder_has_work_ilocked(struct binder_thread *thread,
- bool do_proc_work)
-{
- return thread->process_todo ||
- thread->looper_need_return ||
- (do_proc_work &&
- !binder_worklist_empty_ilocked(&thread->proc->todo));
-}
-
-static bool binder_has_work(struct binder_thread *thread, bool do_proc_work)
-{
- bool has_work;
-
- binder_inner_proc_lock(thread->proc);
- has_work = binder_has_work_ilocked(thread, do_proc_work);
- binder_inner_proc_unlock(thread->proc);
-
- return has_work;
-}
-
-static bool binder_available_for_proc_work_ilocked(struct binder_thread *thread)
-{
- return !thread->transaction_stack &&
- binder_worklist_empty_ilocked(&thread->todo);
-}
-
-static void binder_wakeup_poll_threads_ilocked(struct binder_proc *proc,
- bool sync)
-{
- struct rb_node *n;
- struct binder_thread *thread;
-
- for (n = rb_first(&proc->threads); n != NULL; n = rb_next(n)) {
- thread = rb_entry(n, struct binder_thread, rb_node);
- if (thread->looper & BINDER_LOOPER_STATE_POLL &&
- binder_available_for_proc_work_ilocked(thread)) {
- if (sync)
- wake_up_interruptible_sync(&thread->wait);
- else
- wake_up_interruptible(&thread->wait);
- }
- }
-}
-
-/**
- * binder_select_thread_ilocked() - selects a thread for doing proc work.
- * @proc: process to select a thread from
- *
- * Note that calling this function moves the thread off the waiting_threads
- * list, so it can only be woken up by the caller of this function, or a
- * signal. Therefore, callers *should* always wake up the thread this function
- * returns.
- *
- * Return: If there's a thread currently waiting for process work,
- * returns that thread. Otherwise returns NULL.
- */
-static struct binder_thread *
-binder_select_thread_ilocked(struct binder_proc *proc)
-{
- struct binder_thread *thread;
-
- assert_spin_locked(&proc->inner_lock);
- thread = list_first_entry_or_null(&proc->waiting_threads,
- struct binder_thread,
- waiting_thread_node);
-
- if (thread)
- list_del_init(&thread->waiting_thread_node);
-
- return thread;
-}
-
-/**
- * binder_wakeup_thread_ilocked() - wakes up a thread for doing proc work.
- * @proc: process to wake up a thread in
- * @thread: specific thread to wake-up (may be NULL)
- * @sync: whether to do a synchronous wake-up
- *
- * This function wakes up a thread in the @proc process.
- * The caller may provide a specific thread to wake-up in
- * the @thread parameter. If @thread is NULL, this function
- * will wake up threads that have called poll().
- *
- * Note that for this function to work as expected, callers
- * should first call binder_select_thread() to find a thread
- * to handle the work (if they don't have a thread already),
- * and pass the result into the @thread parameter.
- */
-static void binder_wakeup_thread_ilocked(struct binder_proc *proc,
- struct binder_thread *thread,
- bool sync)
-{
- assert_spin_locked(&proc->inner_lock);
-
- if (thread) {
- if (sync)
- wake_up_interruptible_sync(&thread->wait);
- else
- wake_up_interruptible(&thread->wait);
- return;
- }
-
- /* Didn't find a thread waiting for proc work; this can happen
- * in two scenarios:
- * 1. All threads are busy handling transactions
- * In that case, one of those threads should call back into
- * the kernel driver soon and pick up this work.
- * 2. Threads are using the (e)poll interface, in which case
- * they may be blocked on the waitqueue without having been
- * added to waiting_threads. For this case, we just iterate
- * over all threads not handling transaction work, and
- * wake them all up. We wake all because we don't know whether
- * a thread that called into (e)poll is handling non-binder
- * work currently.
- */
- binder_wakeup_poll_threads_ilocked(proc, sync);
-}
-
-static void binder_wakeup_proc_ilocked(struct binder_proc *proc)
-{
- struct binder_thread *thread = binder_select_thread_ilocked(proc);
-
- binder_wakeup_thread_ilocked(proc, thread, /* sync = */false);
-}
-
-static void binder_set_nice(long nice)
-{
- long min_nice;
-
- if (can_nice(current, nice)) {
- set_user_nice(current, nice);
- return;
- }
- min_nice = rlimit_to_nice(rlimit(RLIMIT_NICE));
- binder_debug(BINDER_DEBUG_PRIORITY_CAP,
- "%d: nice value %ld not allowed use %ld instead\n",
- current->pid, nice, min_nice);
- set_user_nice(current, min_nice);
- if (min_nice <= MAX_NICE)
- return;
- binder_user_error("%d RLIMIT_NICE not set\n", current->pid);
-}
-
-static struct binder_node *binder_get_node_ilocked(struct binder_proc *proc,
- binder_uintptr_t ptr)
-{
- struct rb_node *n = proc->nodes.rb_node;
- struct binder_node *node;
-
- assert_spin_locked(&proc->inner_lock);
-
- while (n) {
- node = rb_entry(n, struct binder_node, rb_node);
-
- if (ptr < node->ptr)
- n = n->rb_left;
- else if (ptr > node->ptr)
- n = n->rb_right;
- else {
- /*
- * take an implicit weak reference
- * to ensure node stays alive until
- * call to binder_put_node()
- */
- binder_inc_node_tmpref_ilocked(node);
- return node;
- }
- }
- return NULL;
-}
-
-static struct binder_node *binder_get_node(struct binder_proc *proc,
- binder_uintptr_t ptr)
-{
- struct binder_node *node;
-
- binder_inner_proc_lock(proc);
- node = binder_get_node_ilocked(proc, ptr);
- binder_inner_proc_unlock(proc);
- return node;
-}
-
-static struct binder_node *binder_init_node_ilocked(
- struct binder_proc *proc,
- struct binder_node *new_node,
- struct flat_binder_object *fp)
-{
- struct rb_node **p = &proc->nodes.rb_node;
- struct rb_node *parent = NULL;
- struct binder_node *node;
- binder_uintptr_t ptr = fp ? fp->binder : 0;
- binder_uintptr_t cookie = fp ? fp->cookie : 0;
- __u32 flags = fp ? fp->flags : 0;
-
- assert_spin_locked(&proc->inner_lock);
-
- while (*p) {
-
- parent = *p;
- node = rb_entry(parent, struct binder_node, rb_node);
-
- if (ptr < node->ptr)
- p = &(*p)->rb_left;
- else if (ptr > node->ptr)
- p = &(*p)->rb_right;
- else {
- /*
- * A matching node is already in
- * the rb tree. Abandon the init
- * and return it.
- */
- binder_inc_node_tmpref_ilocked(node);
- return node;
- }
- }
- node = new_node;
- binder_stats_created(BINDER_STAT_NODE);
- node->tmp_refs++;
- rb_link_node(&node->rb_node, parent, p);
- rb_insert_color(&node->rb_node, &proc->nodes);
- node->debug_id = atomic_inc_return(&binder_last_id);
- node->proc = proc;
- node->ptr = ptr;
- node->cookie = cookie;
- node->work.type = BINDER_WORK_NODE;
- node->min_priority = flags & FLAT_BINDER_FLAG_PRIORITY_MASK;
- node->accept_fds = !!(flags & FLAT_BINDER_FLAG_ACCEPTS_FDS);
- node->txn_security_ctx = !!(flags & FLAT_BINDER_FLAG_TXN_SECURITY_CTX);
- spin_lock_init(&node->lock);
- INIT_LIST_HEAD(&node->work.entry);
- INIT_LIST_HEAD(&node->async_todo);
- binder_debug(BINDER_DEBUG_INTERNAL_REFS,
- "%d:%d node %d u%016llx c%016llx created\n",
- proc->pid, current->pid, node->debug_id,
- (u64)node->ptr, (u64)node->cookie);
-
- return node;
-}
-
-static struct binder_node *binder_new_node(struct binder_proc *proc,
- struct flat_binder_object *fp)
-{
- struct binder_node *node;
- struct binder_node *new_node = kzalloc_obj(*node);
-
- if (!new_node)
- return NULL;
- binder_inner_proc_lock(proc);
- node = binder_init_node_ilocked(proc, new_node, fp);
- binder_inner_proc_unlock(proc);
- if (node != new_node)
- /*
- * The node was already added by another thread
- */
- kfree(new_node);
-
- return node;
-}
-
-static void binder_free_node(struct binder_node *node)
-{
- kfree(node);
- binder_stats_deleted(BINDER_STAT_NODE);
-}
-
-static int binder_inc_node_nilocked(struct binder_node *node, int strong,
- int internal,
- struct list_head *target_list)
-{
- struct binder_proc *proc = node->proc;
-
- assert_spin_locked(&node->lock);
- if (proc)
- assert_spin_locked(&proc->inner_lock);
- if (strong) {
- if (internal) {
- if (target_list == NULL &&
- node->internal_strong_refs == 0 &&
- !(node->proc &&
- node == node->proc->context->binder_context_mgr_node &&
- node->has_strong_ref)) {
- pr_err("invalid inc strong node for %d\n",
- node->debug_id);
- return -EINVAL;
- }
- node->internal_strong_refs++;
- } else
- node->local_strong_refs++;
- if (!node->has_strong_ref && target_list) {
- struct binder_thread *thread = container_of(target_list,
- struct binder_thread, todo);
- binder_dequeue_work_ilocked(&node->work);
- BUG_ON(&thread->todo != target_list);
- binder_enqueue_deferred_thread_work_ilocked(thread,
- &node->work);
- }
- } else {
- if (!internal)
- node->local_weak_refs++;
- if (!node->has_weak_ref && target_list && list_empty(&node->work.entry))
- binder_enqueue_work_ilocked(&node->work, target_list);
- }
- return 0;
-}
-
-static int binder_inc_node(struct binder_node *node, int strong, int internal,
- struct list_head *target_list)
-{
- int ret;
-
- binder_node_inner_lock(node);
- ret = binder_inc_node_nilocked(node, strong, internal, target_list);
- binder_node_inner_unlock(node);
-
- return ret;
-}
-
-static bool binder_dec_node_nilocked(struct binder_node *node,
- int strong, int internal)
-{
- struct binder_proc *proc = node->proc;
-
- assert_spin_locked(&node->lock);
- if (proc)
- assert_spin_locked(&proc->inner_lock);
- if (strong) {
- if (internal)
- node->internal_strong_refs--;
- else
- node->local_strong_refs--;
- if (node->local_strong_refs || node->internal_strong_refs)
- return false;
- } else {
- if (!internal)
- node->local_weak_refs--;
- if (node->local_weak_refs || node->tmp_refs ||
- !hlist_empty(&node->refs))
- return false;
- }
-
- if (proc && (node->has_strong_ref || node->has_weak_ref)) {
- if (list_empty(&node->work.entry)) {
- binder_enqueue_work_ilocked(&node->work, &proc->todo);
- binder_wakeup_proc_ilocked(proc);
- }
- } else {
- if (hlist_empty(&node->refs) && !node->local_strong_refs &&
- !node->local_weak_refs && !node->tmp_refs) {
- if (proc) {
- binder_dequeue_work_ilocked(&node->work);
- rb_erase(&node->rb_node, &proc->nodes);
- binder_debug(BINDER_DEBUG_INTERNAL_REFS,
- "refless node %d deleted\n",
- node->debug_id);
- } else {
- BUG_ON(!list_empty(&node->work.entry));
- spin_lock(&binder_dead_nodes_lock);
- /*
- * tmp_refs could have changed so
- * check it again
- */
- if (node->tmp_refs) {
- spin_unlock(&binder_dead_nodes_lock);
- return false;
- }
- hlist_del(&node->dead_node);
- spin_unlock(&binder_dead_nodes_lock);
- binder_debug(BINDER_DEBUG_INTERNAL_REFS,
- "dead node %d deleted\n",
- node->debug_id);
- }
- return true;
- }
- }
- return false;
-}
-
-static void binder_dec_node(struct binder_node *node, int strong, int internal)
-{
- bool free_node;
-
- binder_node_inner_lock(node);
- free_node = binder_dec_node_nilocked(node, strong, internal);
- binder_node_inner_unlock(node);
- if (free_node)
- binder_free_node(node);
-}
-
-static void binder_inc_node_tmpref_ilocked(struct binder_node *node)
-{
- /*
- * No call to binder_inc_node() is needed since we
- * don't need to inform userspace of any changes to
- * tmp_refs
- */
- node->tmp_refs++;
-}
-
-/**
- * binder_inc_node_tmpref() - take a temporary reference on node
- * @node: node to reference
- *
- * Take reference on node to prevent the node from being freed
- * while referenced only by a local variable. The inner lock is
- * needed to serialize with the node work on the queue (which
- * isn't needed after the node is dead). If the node is dead
- * (node->proc is NULL), use binder_dead_nodes_lock to protect
- * node->tmp_refs against dead-node-only cases where the node
- * lock cannot be acquired (eg traversing the dead node list to
- * print nodes)
- */
-static void binder_inc_node_tmpref(struct binder_node *node)
-{
- binder_node_lock(node);
- if (node->proc)
- binder_inner_proc_lock(node->proc);
- else
- spin_lock(&binder_dead_nodes_lock);
- binder_inc_node_tmpref_ilocked(node);
- if (node->proc)
- binder_inner_proc_unlock(node->proc);
- else
- spin_unlock(&binder_dead_nodes_lock);
- binder_node_unlock(node);
-}
-
-/**
- * binder_dec_node_tmpref() - remove a temporary reference on node
- * @node: node to reference
- *
- * Release temporary reference on node taken via binder_inc_node_tmpref()
- */
-static void binder_dec_node_tmpref(struct binder_node *node)
-{
- bool free_node;
-
- binder_node_inner_lock(node);
- if (!node->proc)
- spin_lock(&binder_dead_nodes_lock);
- else
- __acquire(&binder_dead_nodes_lock);
- node->tmp_refs--;
- BUG_ON(node->tmp_refs < 0);
- if (!node->proc)
- spin_unlock(&binder_dead_nodes_lock);
- else
- __release(&binder_dead_nodes_lock);
- /*
- * Call binder_dec_node() to check if all refcounts are 0
- * and cleanup is needed. Calling with strong=0 and internal=1
- * causes no actual reference to be released in binder_dec_node().
- * If that changes, a change is needed here too.
- */
- free_node = binder_dec_node_nilocked(node, 0, 1);
- binder_node_inner_unlock(node);
- if (free_node)
- binder_free_node(node);
-}
-
-static void binder_put_node(struct binder_node *node)
-{
- binder_dec_node_tmpref(node);
-}
-
-static struct binder_ref *binder_get_ref_olocked(struct binder_proc *proc,
- u32 desc, bool need_strong_ref)
-{
- struct rb_node *n = proc->refs_by_desc.rb_node;
- struct binder_ref *ref;
-
- while (n) {
- ref = rb_entry(n, struct binder_ref, rb_node_desc);
-
- if (desc < ref->data.desc) {
- n = n->rb_left;
- } else if (desc > ref->data.desc) {
- n = n->rb_right;
- } else if (need_strong_ref && !ref->data.strong) {
- binder_user_error("tried to use weak ref as strong ref\n");
- return NULL;
- } else {
- return ref;
- }
- }
- return NULL;
-}
-
-/* Find the smallest unused descriptor the "slow way" */
-static u32 slow_desc_lookup_olocked(struct binder_proc *proc, u32 offset)
-{
- struct binder_ref *ref;
- struct rb_node *n;
- u32 desc;
-
- desc = offset;
- for (n = rb_first(&proc->refs_by_desc); n; n = rb_next(n)) {
- ref = rb_entry(n, struct binder_ref, rb_node_desc);
- if (ref->data.desc > desc)
- break;
- desc = ref->data.desc + 1;
- }
-
- return desc;
-}
-
-/*
- * Find an available reference descriptor ID. The proc->outer_lock might
- * be released in the process, in which case -EAGAIN is returned and the
- * @desc should be considered invalid.
- */
-static int get_ref_desc_olocked(struct binder_proc *proc,
- struct binder_node *node,
- u32 *desc)
-{
- struct dbitmap *dmap = &proc->dmap;
- unsigned int nbits, offset;
- unsigned long *new, bit;
-
- /* 0 is reserved for the context manager */
- offset = (node == proc->context->binder_context_mgr_node) ? 0 : 1;
-
- if (!dbitmap_enabled(dmap)) {
- *desc = slow_desc_lookup_olocked(proc, offset);
- return 0;
- }
-
- if (dbitmap_acquire_next_zero_bit(dmap, offset, &bit) == 0) {
- *desc = bit;
- return 0;
- }
-
- /*
- * The dbitmap is full and needs to grow. The proc->outer_lock
- * is briefly released to allocate the new bitmap safely.
- */
- nbits = dbitmap_grow_nbits(dmap);
- binder_proc_unlock(proc);
- new = bitmap_zalloc(nbits, GFP_KERNEL);
- binder_proc_lock(proc);
- dbitmap_grow(dmap, new, nbits);
-
- return -EAGAIN;
-}
-
-/**
- * binder_get_ref_for_node_olocked() - get the ref associated with given node
- * @proc: binder_proc that owns the ref
- * @node: binder_node of target
- * @new_ref: newly allocated binder_ref to be initialized or %NULL
- *
- * Look up the ref for the given node and return it if it exists
- *
- * If it doesn't exist and the caller provides a newly allocated
- * ref, initialize the fields of the newly allocated ref and insert
- * into the given proc rb_trees and node refs list.
- *
- * Return: the ref for node. It is possible that another thread
- * allocated/initialized the ref first in which case the
- * returned ref would be different than the passed-in
- * new_ref. new_ref must be kfree'd by the caller in
- * this case.
- */
-static struct binder_ref *binder_get_ref_for_node_olocked(
- struct binder_proc *proc,
- struct binder_node *node,
- struct binder_ref *new_ref)
-{
- struct binder_ref *ref;
- struct rb_node *parent;
- struct rb_node **p;
- u32 desc;
-
-retry:
- p = &proc->refs_by_node.rb_node;
- parent = NULL;
- while (*p) {
- parent = *p;
- ref = rb_entry(parent, struct binder_ref, rb_node_node);
-
- if (node < ref->node)
- p = &(*p)->rb_left;
- else if (node > ref->node)
- p = &(*p)->rb_right;
- else
- return ref;
- }
- if (!new_ref)
- return NULL;
-
- /* might release the proc->outer_lock */
- if (get_ref_desc_olocked(proc, node, &desc) == -EAGAIN)
- goto retry;
-
- binder_stats_created(BINDER_STAT_REF);
- new_ref->data.debug_id = atomic_inc_return(&binder_last_id);
- new_ref->proc = proc;
- new_ref->node = node;
- rb_link_node(&new_ref->rb_node_node, parent, p);
- rb_insert_color(&new_ref->rb_node_node, &proc->refs_by_node);
-
- new_ref->data.desc = desc;
- p = &proc->refs_by_desc.rb_node;
- while (*p) {
- parent = *p;
- ref = rb_entry(parent, struct binder_ref, rb_node_desc);
-
- if (new_ref->data.desc < ref->data.desc)
- p = &(*p)->rb_left;
- else if (new_ref->data.desc > ref->data.desc)
- p = &(*p)->rb_right;
- else
- BUG();
- }
- rb_link_node(&new_ref->rb_node_desc, parent, p);
- rb_insert_color(&new_ref->rb_node_desc, &proc->refs_by_desc);
-
- binder_node_lock(node);
- hlist_add_head(&new_ref->node_entry, &node->refs);
-
- binder_debug(BINDER_DEBUG_INTERNAL_REFS,
- "%d new ref %d desc %d for node %d\n",
- proc->pid, new_ref->data.debug_id, new_ref->data.desc,
- node->debug_id);
- binder_node_unlock(node);
- return new_ref;
-}
-
-static void binder_cleanup_ref_olocked(struct binder_ref *ref)
-{
- struct dbitmap *dmap = &ref->proc->dmap;
- bool delete_node = false;
-
- binder_debug(BINDER_DEBUG_INTERNAL_REFS,
- "%d delete ref %d desc %d for node %d\n",
- ref->proc->pid, ref->data.debug_id, ref->data.desc,
- ref->node->debug_id);
-
- if (dbitmap_enabled(dmap))
- dbitmap_clear_bit(dmap, ref->data.desc);
- rb_erase(&ref->rb_node_desc, &ref->proc->refs_by_desc);
- rb_erase(&ref->rb_node_node, &ref->proc->refs_by_node);
-
- binder_node_inner_lock(ref->node);
- if (ref->data.strong)
- binder_dec_node_nilocked(ref->node, 1, 1);
-
- hlist_del(&ref->node_entry);
- delete_node = binder_dec_node_nilocked(ref->node, 0, 1);
- binder_node_inner_unlock(ref->node);
- /*
- * Clear ref->node unless we want the caller to free the node
- */
- if (!delete_node) {
- /*
- * The caller uses ref->node to determine
- * whether the node needs to be freed. Clear
- * it since the node is still alive.
- */
- ref->node = NULL;
- }
-
- if (ref->death) {
- binder_debug(BINDER_DEBUG_DEAD_BINDER,
- "%d delete ref %d desc %d has death notification\n",
- ref->proc->pid, ref->data.debug_id,
- ref->data.desc);
- binder_dequeue_work(ref->proc, &ref->death->work);
- binder_stats_deleted(BINDER_STAT_DEATH);
- }
-
- if (ref->freeze) {
- binder_dequeue_work(ref->proc, &ref->freeze->work);
- binder_stats_deleted(BINDER_STAT_FREEZE);
- }
-
- binder_stats_deleted(BINDER_STAT_REF);
-}
-
-/**
- * binder_inc_ref_olocked() - increment the ref for given handle
- * @ref: ref to be incremented
- * @strong: if true, strong increment, else weak
- * @target_list: list to queue node work on
- *
- * Increment the ref. @ref->proc->outer_lock must be held on entry
- *
- * Return: 0, if successful, else errno
- */
-static int binder_inc_ref_olocked(struct binder_ref *ref, int strong,
- struct list_head *target_list)
-{
- int ret;
-
- if (strong) {
- if (ref->data.strong == 0) {
- ret = binder_inc_node(ref->node, 1, 1, target_list);
- if (ret)
- return ret;
- }
- ref->data.strong++;
- } else {
- if (ref->data.weak == 0) {
- ret = binder_inc_node(ref->node, 0, 1, target_list);
- if (ret)
- return ret;
- }
- ref->data.weak++;
- }
- return 0;
-}
-
-/**
- * binder_dec_ref_olocked() - dec the ref for given handle
- * @ref: ref to be decremented
- * @strong: if true, strong decrement, else weak
- *
- * Decrement the ref.
- *
- * Return: %true if ref is cleaned up and ready to be freed.
- */
-static bool binder_dec_ref_olocked(struct binder_ref *ref, int strong)
-{
- if (strong) {
- if (ref->data.strong == 0) {
- binder_user_error("%d invalid dec strong, ref %d desc %d s %d w %d\n",
- ref->proc->pid, ref->data.debug_id,
- ref->data.desc, ref->data.strong,
- ref->data.weak);
- return false;
- }
- ref->data.strong--;
- if (ref->data.strong == 0)
- binder_dec_node(ref->node, strong, 1);
- } else {
- if (ref->data.weak == 0) {
- binder_user_error("%d invalid dec weak, ref %d desc %d s %d w %d\n",
- ref->proc->pid, ref->data.debug_id,
- ref->data.desc, ref->data.strong,
- ref->data.weak);
- return false;
- }
- ref->data.weak--;
- }
- if (ref->data.strong == 0 && ref->data.weak == 0) {
- binder_cleanup_ref_olocked(ref);
- return true;
- }
- return false;
-}
-
-/**
- * binder_get_node_from_ref() - get the node from the given proc/desc
- * @proc: proc containing the ref
- * @desc: the handle associated with the ref
- * @need_strong_ref: if true, only return node if ref is strong
- * @rdata: the id/refcount data for the ref
- *
- * Given a proc and ref handle, return the associated binder_node
- *
- * Return: a binder_node or NULL if not found or not strong when strong required
- */
-static struct binder_node *binder_get_node_from_ref(
- struct binder_proc *proc,
- u32 desc, bool need_strong_ref,
- struct binder_ref_data *rdata)
-{
- struct binder_node *node;
- struct binder_ref *ref;
-
- binder_proc_lock(proc);
- ref = binder_get_ref_olocked(proc, desc, need_strong_ref);
- if (!ref)
- goto err_no_ref;
- node = ref->node;
- /*
- * Take an implicit reference on the node to ensure
- * it stays alive until the call to binder_put_node()
- */
- binder_inc_node_tmpref(node);
- if (rdata)
- *rdata = ref->data;
- binder_proc_unlock(proc);
-
- return node;
-
-err_no_ref:
- binder_proc_unlock(proc);
- return NULL;
-}
-
-/**
- * binder_free_ref() - free the binder_ref
- * @ref: ref to free
- *
- * Free the binder_ref. Free the binder_node indicated by ref->node
- * (if non-NULL) and the binder_ref_death indicated by ref->death.
- */
-static void binder_free_ref(struct binder_ref *ref)
-{
- if (ref->node)
- binder_free_node(ref->node);
- kfree(ref->death);
- kfree(ref->freeze);
- kfree(ref);
-}
-
-/* shrink descriptor bitmap if needed */
-static void try_shrink_dmap(struct binder_proc *proc)
-{
- unsigned long *new;
- int nbits;
-
- binder_proc_lock(proc);
- nbits = dbitmap_shrink_nbits(&proc->dmap);
- binder_proc_unlock(proc);
-
- if (!nbits)
- return;
-
- new = bitmap_zalloc(nbits, GFP_KERNEL);
- binder_proc_lock(proc);
- dbitmap_shrink(&proc->dmap, new, nbits);
- binder_proc_unlock(proc);
-}
-
-/**
- * binder_update_ref_for_handle() - inc/dec the ref for given handle
- * @proc: proc containing the ref
- * @desc: the handle associated with the ref
- * @increment: true=inc reference, false=dec reference
- * @strong: true=strong reference, false=weak reference
- * @rdata: the id/refcount data for the ref
- *
- * Given a proc and ref handle, increment or decrement the ref
- * according to "increment" arg.
- *
- * Return: 0 if successful, else errno
- */
-static int binder_update_ref_for_handle(struct binder_proc *proc,
- uint32_t desc, bool increment, bool strong,
- struct binder_ref_data *rdata)
-{
- int ret = 0;
- struct binder_ref *ref;
- bool delete_ref = false;
-
- binder_proc_lock(proc);
- ref = binder_get_ref_olocked(proc, desc, strong);
- if (!ref) {
- ret = -EINVAL;
- goto err_no_ref;
- }
- if (increment)
- ret = binder_inc_ref_olocked(ref, strong, NULL);
- else
- delete_ref = binder_dec_ref_olocked(ref, strong);
-
- if (rdata)
- *rdata = ref->data;
- binder_proc_unlock(proc);
-
- if (delete_ref) {
- binder_free_ref(ref);
- try_shrink_dmap(proc);
- }
- return ret;
-
-err_no_ref:
- binder_proc_unlock(proc);
- return ret;
-}
-
-/**
- * binder_dec_ref_for_handle() - dec the ref for given handle
- * @proc: proc containing the ref
- * @desc: the handle associated with the ref
- * @strong: true=strong reference, false=weak reference
- * @rdata: the id/refcount data for the ref
- *
- * Just calls binder_update_ref_for_handle() to decrement the ref.
- *
- * Return: 0 if successful, else errno
- */
-static int binder_dec_ref_for_handle(struct binder_proc *proc,
- uint32_t desc, bool strong, struct binder_ref_data *rdata)
-{
- return binder_update_ref_for_handle(proc, desc, false, strong, rdata);
-}
-
-
-/**
- * binder_inc_ref_for_node() - increment the ref for given proc/node
- * @proc: proc containing the ref
- * @node: target node
- * @strong: true=strong reference, false=weak reference
- * @target_list: worklist to use if node is incremented
- * @rdata: the id/refcount data for the ref
- *
- * Given a proc and node, increment the ref. Create the ref if it
- * doesn't already exist
- *
- * Return: 0 if successful, else errno
- */
-static int binder_inc_ref_for_node(struct binder_proc *proc,
- struct binder_node *node,
- bool strong,
- struct list_head *target_list,
- struct binder_ref_data *rdata)
-{
- struct binder_ref *ref;
- struct binder_ref *new_ref = NULL;
- int ret = 0;
-
- binder_proc_lock(proc);
- ref = binder_get_ref_for_node_olocked(proc, node, NULL);
- if (!ref) {
- binder_proc_unlock(proc);
- new_ref = kzalloc_obj(*ref);
- if (!new_ref)
- return -ENOMEM;
- binder_proc_lock(proc);
- ref = binder_get_ref_for_node_olocked(proc, node, new_ref);
- }
- ret = binder_inc_ref_olocked(ref, strong, target_list);
- *rdata = ref->data;
- if (ret && ref == new_ref) {
- /*
- * Cleanup the failed reference here as the target
- * could now be dead and have already released its
- * references by now. Calling on the new reference
- * with strong=0 and a tmp_refs will not decrement
- * the node. The new_ref gets kfree'd below.
- */
- binder_cleanup_ref_olocked(new_ref);
- ref = NULL;
- }
-
- binder_proc_unlock(proc);
- if (new_ref && ref != new_ref)
- /*
- * Another thread created the ref first so
- * free the one we allocated
- */
- kfree(new_ref);
- return ret;
-}
-
-static void binder_pop_transaction_ilocked(struct binder_thread *target_thread,
- struct binder_transaction *t)
-{
- BUG_ON(!target_thread);
- assert_spin_locked(&target_thread->proc->inner_lock);
- BUG_ON(target_thread->transaction_stack != t);
- BUG_ON(target_thread->transaction_stack->from != target_thread);
- target_thread->transaction_stack =
- target_thread->transaction_stack->from_parent;
- t->from = NULL;
-}
-
-/**
- * binder_thread_dec_tmpref() - decrement thread->tmp_ref
- * @thread: thread to decrement
- *
- * A thread needs to be kept alive while being used to create or
- * handle a transaction. binder_get_txn_from() is used to safely
- * extract t->from from a binder_transaction and keep the thread
- * indicated by t->from from being freed. When done with that
- * binder_thread, this function is called to decrement the
- * tmp_ref and free if appropriate (thread has been released
- * and no transaction being processed by the driver)
- */
-static void binder_thread_dec_tmpref(struct binder_thread *thread)
-{
- /*
- * atomic is used to protect the counter value while
- * it cannot reach zero or thread->is_dead is false
- */
- binder_inner_proc_lock(thread->proc);
- atomic_dec(&thread->tmp_ref);
- if (thread->is_dead && !atomic_read(&thread->tmp_ref)) {
- binder_inner_proc_unlock(thread->proc);
- binder_free_thread(thread);
- return;
- }
- binder_inner_proc_unlock(thread->proc);
-}
-
-/**
- * binder_proc_dec_tmpref() - decrement proc->tmp_ref
- * @proc: proc to decrement
- *
- * A binder_proc needs to be kept alive while being used to create or
- * handle a transaction. proc->tmp_ref is incremented when
- * creating a new transaction or the binder_proc is currently in-use
- * by threads that are being released. When done with the binder_proc,
- * this function is called to decrement the counter and free the
- * proc if appropriate (proc has been released, all threads have
- * been released and not currently in-use to process a transaction).
- */
-static void binder_proc_dec_tmpref(struct binder_proc *proc)
-{
- binder_inner_proc_lock(proc);
- proc->tmp_ref--;
- if (proc->is_dead && RB_EMPTY_ROOT(&proc->threads) &&
- !proc->tmp_ref) {
- binder_inner_proc_unlock(proc);
- binder_free_proc(proc);
- return;
- }
- binder_inner_proc_unlock(proc);
-}
-
-/**
- * binder_get_txn_from() - safely extract the "from" thread in transaction
- * @t: binder transaction for t->from
- *
- * Atomically return the "from" thread and increment the tmp_ref
- * count for the thread to ensure it stays alive until
- * binder_thread_dec_tmpref() is called.
- *
- * Return: the value of t->from
- */
-static struct binder_thread *binder_get_txn_from(
- struct binder_transaction *t)
-{
- struct binder_thread *from;
-
- guard(spinlock)(&t->lock);
- from = t->from;
- if (from)
- atomic_inc(&from->tmp_ref);
- return from;
-}
-
-/**
- * binder_get_txn_from_and_acq_inner() - get t->from and acquire inner lock
- * @t: binder transaction for t->from
- *
- * Same as binder_get_txn_from() except it also acquires the proc->inner_lock
- * to guarantee that the thread cannot be released while operating on it.
- * The caller must call binder_inner_proc_unlock() to release the inner lock
- * as well as call binder_dec_thread_txn() to release the reference.
- *
- * Return: the value of t->from
- */
-static struct binder_thread *binder_get_txn_from_and_acq_inner(
- struct binder_transaction *t)
- __acquires(&t->from->proc->inner_lock)
-{
- struct binder_thread *from;
-
- from = binder_get_txn_from(t);
- if (!from) {
- __acquire(&from->proc->inner_lock);
- return NULL;
- }
- binder_inner_proc_lock(from->proc);
- if (t->from) {
- BUG_ON(from != t->from);
- return from;
- }
- binder_inner_proc_unlock(from->proc);
- __acquire(&from->proc->inner_lock);
- binder_thread_dec_tmpref(from);
- return NULL;
-}
-
-/**
- * binder_free_txn_fixups() - free unprocessed fd fixups
- * @t: binder transaction for t->from
- *
- * If the transaction is being torn down prior to being
- * processed by the target process, free all of the
- * fd fixups and fput the file structs. It is safe to
- * call this function after the fixups have been
- * processed -- in that case, the list will be empty.
- */
-static void binder_free_txn_fixups(struct binder_transaction *t)
-{
- struct binder_txn_fd_fixup *fixup, *tmp;
-
- list_for_each_entry_safe(fixup, tmp, &t->fd_fixups, fixup_entry) {
- fput(fixup->file);
- if (fixup->target_fd >= 0)
- put_unused_fd(fixup->target_fd);
- list_del(&fixup->fixup_entry);
- kfree(fixup);
- }
-}
-
-static void binder_txn_latency_free(struct binder_transaction *t)
-{
- int from_proc, from_thread, to_proc, to_thread;
-
- spin_lock(&t->lock);
- from_proc = t->from ? t->from->proc->pid : 0;
- from_thread = t->from ? t->from->pid : 0;
- to_proc = t->to_proc ? t->to_proc->pid : 0;
- to_thread = t->to_thread ? t->to_thread->pid : 0;
- spin_unlock(&t->lock);
-
- trace_binder_txn_latency_free(t, from_proc, from_thread, to_proc, to_thread);
-}
-
-static void binder_free_transaction(struct binder_transaction *t)
-{
- struct binder_thread *target_thread;
- struct binder_proc *target_proc;
-
- spin_lock(&t->lock);
- target_proc = t->to_proc;
- target_thread = t->to_thread;
- /*
- * Pin target_thread to keep target_proc alive. Undelivered
- * transactions with !target_thread are safe, as target_proc
- * can only be the current context there.
- */
- if (target_thread)
- atomic_inc(&target_thread->tmp_ref);
- spin_unlock(&t->lock);
-
- if (target_proc) {
- binder_inner_proc_lock(target_proc);
- target_proc->outstanding_txns--;
- if (target_proc->outstanding_txns < 0)
- pr_warn("%s: Unexpected outstanding_txns %d\n",
- __func__, target_proc->outstanding_txns);
- if (!target_proc->outstanding_txns && target_proc->is_frozen)
- wake_up_interruptible_all(&target_proc->freeze_wait);
- if (t->buffer)
- t->buffer->transaction = NULL;
- binder_inner_proc_unlock(target_proc);
- }
-
- if (target_thread)
- binder_thread_dec_tmpref(target_thread);
-
- if (trace_binder_txn_latency_free_enabled())
- binder_txn_latency_free(t);
- /*
- * If the transaction has no target_proc, then
- * t->buffer->transaction has already been cleared.
- */
- binder_free_txn_fixups(t);
- kfree(t);
- binder_stats_deleted(BINDER_STAT_TRANSACTION);
-}
-
-static void binder_send_failed_reply(struct binder_transaction *t,
- uint32_t error_code)
-{
- struct binder_thread *target_thread;
- struct binder_transaction *next;
-
- BUG_ON(t->flags & TF_ONE_WAY);
- while (1) {
- target_thread = binder_get_txn_from_and_acq_inner(t);
- if (target_thread) {
- binder_debug(BINDER_DEBUG_FAILED_TRANSACTION,
- "send failed reply for transaction %d to %d:%d\n",
- t->debug_id,
- target_thread->proc->pid,
- target_thread->pid);
-
- binder_pop_transaction_ilocked(target_thread, t);
- if (target_thread->reply_error.cmd == BR_OK) {
- target_thread->reply_error.cmd = error_code;
- binder_enqueue_thread_work_ilocked(
- target_thread,
- &target_thread->reply_error.work);
- wake_up_interruptible(&target_thread->wait);
- } else {
- /*
- * Cannot get here for normal operation, but
- * we can if multiple synchronous transactions
- * are sent without blocking for responses.
- * Just ignore the 2nd error in this case.
- */
- pr_warn("Unexpected reply error: %u\n",
- target_thread->reply_error.cmd);
- }
- binder_inner_proc_unlock(target_thread->proc);
- binder_thread_dec_tmpref(target_thread);
- binder_free_transaction(t);
- return;
- }
- __release(&target_thread->proc->inner_lock);
- next = t->from_parent;
-
- binder_debug(BINDER_DEBUG_FAILED_TRANSACTION,
- "send failed reply for transaction %d, target dead\n",
- t->debug_id);
-
- binder_free_transaction(t);
- if (next == NULL) {
- binder_debug(BINDER_DEBUG_DEAD_BINDER,
- "reply failed, no target thread at root\n");
- return;
- }
- t = next;
- binder_debug(BINDER_DEBUG_DEAD_BINDER,
- "reply failed, no target thread -- retry %d\n",
- t->debug_id);
- }
-}
-
-/**
- * binder_cleanup_transaction() - cleans up undelivered transaction
- * @t: transaction that needs to be cleaned up
- * @reason: reason the transaction wasn't delivered
- * @error_code: error to return to caller (if synchronous call)
- */
-static void binder_cleanup_transaction(struct binder_transaction *t,
- const char *reason,
- uint32_t error_code)
-{
- if (t->buffer->target_node && !(t->flags & TF_ONE_WAY)) {
- binder_send_failed_reply(t, error_code);
- } else {
- binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
- "undelivered transaction %d, %s\n",
- t->debug_id, reason);
- binder_free_transaction(t);
- }
-}
-
-/**
- * binder_get_object() - gets object and checks for valid metadata
- * @proc: binder_proc owning the buffer
- * @u: sender's user pointer to base of buffer
- * @buffer: binder_buffer that we're parsing.
- * @offset: offset in the @buffer at which to validate an object.
- * @object: struct binder_object to read into
- *
- * Copy the binder object at the given offset into @object. If @u is
- * provided then the copy is from the sender's buffer. If not, then
- * it is copied from the target's @buffer.
- *
- * Return: If there's a valid metadata object at @offset, the
- * size of that object. Otherwise, it returns zero. The object
- * is read into the struct binder_object pointed to by @object.
- */
-static size_t binder_get_object(struct binder_proc *proc,
- const void __user *u,
- struct binder_buffer *buffer,
- unsigned long offset,
- struct binder_object *object)
-{
- size_t read_size;
- struct binder_object_header *hdr;
- size_t object_size = 0;
-
- read_size = min_t(size_t, sizeof(*object), buffer->data_size - offset);
- if (offset > buffer->data_size || read_size < sizeof(*hdr) ||
- !IS_ALIGNED(offset, sizeof(u32)))
- return 0;
-
- if (u) {
- if (copy_from_user(object, u + offset, read_size))
- return 0;
- } else {
- if (binder_alloc_copy_from_buffer(&proc->alloc, object, buffer,
- offset, read_size))
- return 0;
- }
-
- /* Ok, now see if we read a complete object. */
- hdr = &object->hdr;
- switch (hdr->type) {
- case BINDER_TYPE_BINDER:
- case BINDER_TYPE_WEAK_BINDER:
- case BINDER_TYPE_HANDLE:
- case BINDER_TYPE_WEAK_HANDLE:
- object_size = sizeof(struct flat_binder_object);
- break;
- case BINDER_TYPE_FD:
- object_size = sizeof(struct binder_fd_object);
- break;
- case BINDER_TYPE_PTR:
- object_size = sizeof(struct binder_buffer_object);
- break;
- case BINDER_TYPE_FDA:
- object_size = sizeof(struct binder_fd_array_object);
- break;
- default:
- return 0;
- }
- if (offset <= buffer->data_size - object_size &&
- buffer->data_size >= object_size)
- return object_size;
- else
- return 0;
-}
-
-/**
- * binder_validate_ptr() - validates binder_buffer_object in a binder_buffer.
- * @proc: binder_proc owning the buffer
- * @b: binder_buffer containing the object
- * @object: struct binder_object to read into
- * @index: index in offset array at which the binder_buffer_object is
- * located
- * @start_offset: points to the start of the offset array
- * @object_offsetp: offset of @object read from @b
- * @num_valid: the number of valid offsets in the offset array
- *
- * Return: If @index is within the valid range of the offset array
- * described by @start and @num_valid, and if there's a valid
- * binder_buffer_object at the offset found in index @index
- * of the offset array, that object is returned. Otherwise,
- * %NULL is returned.
- * Note that the offset found in index @index itself is not
- * verified; this function assumes that @num_valid elements
- * from @start were previously verified to have valid offsets.
- * If @object_offsetp is non-NULL, then the offset within
- * @b is written to it.
- */
-static struct binder_buffer_object *binder_validate_ptr(
- struct binder_proc *proc,
- struct binder_buffer *b,
- struct binder_object *object,
- binder_size_t index,
- binder_size_t start_offset,
- binder_size_t *object_offsetp,
- binder_size_t num_valid)
-{
- size_t object_size;
- binder_size_t object_offset;
- unsigned long buffer_offset;
-
- if (index >= num_valid)
- return NULL;
-
- buffer_offset = start_offset + sizeof(binder_size_t) * index;
- if (binder_alloc_copy_from_buffer(&proc->alloc, &object_offset,
- b, buffer_offset,
- sizeof(object_offset)))
- return NULL;
- object_size = binder_get_object(proc, NULL, b, object_offset, object);
- if (!object_size || object->hdr.type != BINDER_TYPE_PTR)
- return NULL;
- if (object_offsetp)
- *object_offsetp = object_offset;
-
- return &object->bbo;
-}
-
-/**
- * binder_validate_fixup() - validates pointer/fd fixups happen in order.
- * @proc: binder_proc owning the buffer
- * @b: transaction buffer
- * @objects_start_offset: offset to start of objects buffer
- * @buffer_obj_offset: offset to binder_buffer_object in which to fix up
- * @fixup_offset: start offset in @buffer to fix up
- * @last_obj_offset: offset to last binder_buffer_object that we fixed
- * @last_min_offset: minimum fixup offset in object at @last_obj_offset
- *
- * Return: %true if a fixup in buffer @buffer at offset @offset is
- * allowed.
- *
- * For safety reasons, we only allow fixups inside a buffer to happen
- * at increasing offsets; additionally, we only allow fixup on the last
- * buffer object that was verified, or one of its parents.
- *
- * Example of what is allowed:
- *
- * A
- * B (parent = A, offset = 0)
- * C (parent = A, offset = 16)
- * D (parent = C, offset = 0)
- * E (parent = A, offset = 32) // min_offset is 16 (C.parent_offset)
- *
- * Examples of what is not allowed:
- *
- * Decreasing offsets within the same parent:
- * A
- * C (parent = A, offset = 16)
- * B (parent = A, offset = 0) // decreasing offset within A
- *
- * Referring to a parent that wasn't the last object or any of its parents:
- * A
- * B (parent = A, offset = 0)
- * C (parent = A, offset = 0)
- * C (parent = A, offset = 16)
- * D (parent = B, offset = 0) // B is not A or any of A's parents
- */
-static bool binder_validate_fixup(struct binder_proc *proc,
- struct binder_buffer *b,
- binder_size_t objects_start_offset,
- binder_size_t buffer_obj_offset,
- binder_size_t fixup_offset,
- binder_size_t last_obj_offset,
- binder_size_t last_min_offset)
-{
- if (!last_obj_offset) {
- /* Nothing to fix up in */
- return false;
- }
-
- while (last_obj_offset != buffer_obj_offset) {
- unsigned long buffer_offset;
- struct binder_object last_object;
- struct binder_buffer_object *last_bbo;
- size_t object_size = binder_get_object(proc, NULL, b,
- last_obj_offset,
- &last_object);
- if (object_size != sizeof(*last_bbo))
- return false;
-
- last_bbo = &last_object.bbo;
- /*
- * Safe to retrieve the parent of last_obj, since it
- * was already previously verified by the driver.
- */
- if ((last_bbo->flags & BINDER_BUFFER_FLAG_HAS_PARENT) == 0)
- return false;
- last_min_offset = last_bbo->parent_offset + sizeof(uintptr_t);
- buffer_offset = objects_start_offset +
- sizeof(binder_size_t) * last_bbo->parent;
- if (binder_alloc_copy_from_buffer(&proc->alloc,
- &last_obj_offset,
- b, buffer_offset,
- sizeof(last_obj_offset)))
- return false;
- }
- return (fixup_offset >= last_min_offset);
-}
-
-/**
- * struct binder_task_work_cb - for deferred close
- *
- * @twork: callback_head for task work
- * @file: file to close
- *
- * Structure to pass task work to be handled after
- * returning from binder_ioctl() via task_work_add().
- */
-struct binder_task_work_cb {
- struct callback_head twork;
- struct file *file;
-};
-
-/**
- * binder_do_fd_close() - close list of file descriptors
- * @twork: callback head for task work
- *
- * It is not safe to call ksys_close() during the binder_ioctl()
- * function if there is a chance that binder's own file descriptor
- * might be closed. This is to meet the requirements for using
- * fdget() (see comments for __fget_light()). Therefore use
- * task_work_add() to schedule the close operation once we have
- * returned from binder_ioctl(). This function is a callback
- * for that mechanism and does the actual ksys_close() on the
- * given file descriptor.
- */
-static void binder_do_fd_close(struct callback_head *twork)
-{
- struct binder_task_work_cb *twcb = container_of(twork,
- struct binder_task_work_cb, twork);
-
- fput(twcb->file);
- kfree(twcb);
-}
-
-/**
- * binder_deferred_fd_close() - schedule a close for the given file-descriptor
- * @fd: file-descriptor to close
- *
- * See comments in binder_do_fd_close(). This function is used to schedule
- * a file-descriptor to be closed after returning from binder_ioctl().
- */
-static void binder_deferred_fd_close(int fd)
-{
- struct binder_task_work_cb *twcb;
-
- twcb = kzalloc_obj(*twcb);
- if (!twcb)
- return;
- init_task_work(&twcb->twork, binder_do_fd_close);
- twcb->file = file_close_fd(fd);
- if (twcb->file) {
- // pin it until binder_do_fd_close(); see comments there
- get_file(twcb->file);
- filp_close(twcb->file, current->files);
- task_work_add(current, &twcb->twork, TWA_RESUME);
- } else {
- kfree(twcb);
- }
-}
-
-static void binder_transaction_buffer_release(struct binder_proc *proc,
- struct binder_thread *thread,
- struct binder_buffer *buffer,
- binder_size_t off_end_offset,
- bool is_failure)
-{
- int debug_id = buffer->debug_id;
- binder_size_t off_start_offset, buffer_offset;
-
- binder_debug(BINDER_DEBUG_TRANSACTION,
- "%d buffer release %d, size %zd-%zd, failed at %llx\n",
- proc->pid, buffer->debug_id,
- buffer->data_size, buffer->offsets_size,
- (unsigned long long)off_end_offset);
-
- if (buffer->target_node)
- binder_dec_node(buffer->target_node, 1, 0);
-
- off_start_offset = ALIGN(buffer->data_size, sizeof(void *));
-
- for (buffer_offset = off_start_offset; buffer_offset < off_end_offset;
- buffer_offset += sizeof(binder_size_t)) {
- struct binder_object_header *hdr;
- size_t object_size = 0;
- struct binder_object object;
- binder_size_t object_offset;
-
- if (!binder_alloc_copy_from_buffer(&proc->alloc, &object_offset,
- buffer, buffer_offset,
- sizeof(object_offset)))
- object_size = binder_get_object(proc, NULL, buffer,
- object_offset, &object);
- if (object_size == 0) {
- pr_err("transaction release %d bad object at offset %lld, size %zd\n",
- debug_id, (u64)object_offset, buffer->data_size);
- continue;
- }
- hdr = &object.hdr;
- switch (hdr->type) {
- case BINDER_TYPE_BINDER:
- case BINDER_TYPE_WEAK_BINDER: {
- struct flat_binder_object *fp;
- struct binder_node *node;
-
- fp = to_flat_binder_object(hdr);
- node = binder_get_node(proc, fp->binder);
- if (node == NULL) {
- pr_err("transaction release %d bad node %016llx\n",
- debug_id, (u64)fp->binder);
- break;
- }
- binder_debug(BINDER_DEBUG_TRANSACTION,
- " node %d u%016llx\n",
- node->debug_id, (u64)node->ptr);
- binder_dec_node(node, hdr->type == BINDER_TYPE_BINDER,
- 0);
- binder_put_node(node);
- } break;
- case BINDER_TYPE_HANDLE:
- case BINDER_TYPE_WEAK_HANDLE: {
- struct flat_binder_object *fp;
- struct binder_ref_data rdata;
- int ret;
-
- fp = to_flat_binder_object(hdr);
- ret = binder_dec_ref_for_handle(proc, fp->handle,
- hdr->type == BINDER_TYPE_HANDLE, &rdata);
-
- if (ret) {
- pr_err("transaction release %d bad handle %d, ret = %d\n",
- debug_id, fp->handle, ret);
- break;
- }
- binder_debug(BINDER_DEBUG_TRANSACTION,
- " ref %d desc %d\n",
- rdata.debug_id, rdata.desc);
- } break;
-
- case BINDER_TYPE_FD: {
- /*
- * No need to close the file here since user-space
- * closes it for successfully delivered
- * transactions. For transactions that weren't
- * delivered, the new fd was never allocated so
- * there is no need to close and the fput on the
- * file is done when the transaction is torn
- * down.
- */
- } break;
- case BINDER_TYPE_PTR:
- /*
- * Nothing to do here, this will get cleaned up when the
- * transaction buffer gets freed
- */
- break;
- case BINDER_TYPE_FDA: {
- struct binder_fd_array_object *fda;
- struct binder_buffer_object *parent;
- struct binder_object ptr_object;
- binder_size_t fda_offset;
- size_t fd_index;
- binder_size_t fd_buf_size;
- binder_size_t num_valid;
-
- if (is_failure) {
- /*
- * The fd fixups have not been applied so no
- * fds need to be closed.
- */
- continue;
- }
-
- num_valid = (buffer_offset - off_start_offset) /
- sizeof(binder_size_t);
- fda = to_binder_fd_array_object(hdr);
- parent = binder_validate_ptr(proc, buffer, &ptr_object,
- fda->parent,
- off_start_offset,
- NULL,
- num_valid);
- if (!parent) {
- pr_err("transaction release %d bad parent offset\n",
- debug_id);
- continue;
- }
- fd_buf_size = sizeof(u32) * fda->num_fds;
- if (fda->num_fds >= SIZE_MAX / sizeof(u32)) {
- pr_err("transaction release %d invalid number of fds (%lld)\n",
- debug_id, (u64)fda->num_fds);
- continue;
- }
- if (fd_buf_size > parent->length ||
- fda->parent_offset > parent->length - fd_buf_size) {
- /* No space for all file descriptors here. */
- pr_err("transaction release %d not enough space for %lld fds in buffer\n",
- debug_id, (u64)fda->num_fds);
- continue;
- }
- /*
- * the source data for binder_buffer_object is visible
- * to user-space and the @buffer element is the user
- * pointer to the buffer_object containing the fd_array.
- * Convert the address to an offset relative to
- * the base of the transaction buffer.
- */
- fda_offset = parent->buffer - buffer->user_data +
- fda->parent_offset;
- for (fd_index = 0; fd_index < fda->num_fds;
- fd_index++) {
- u32 fd;
- int err;
- binder_size_t offset = fda_offset +
- fd_index * sizeof(fd);
-
- err = binder_alloc_copy_from_buffer(
- &proc->alloc, &fd, buffer,
- offset, sizeof(fd));
- WARN_ON(err);
- if (!err) {
- binder_deferred_fd_close(fd);
- /*
- * Need to make sure the thread goes
- * back to userspace to complete the
- * deferred close
- */
- if (thread)
- thread->looper_need_return = true;
- }
- }
- } break;
- default:
- pr_err("transaction release %d bad object type %x\n",
- debug_id, hdr->type);
- break;
- }
- }
-}
-
-/* Clean up all the objects in the buffer */
-static inline void binder_release_entire_buffer(struct binder_proc *proc,
- struct binder_thread *thread,
- struct binder_buffer *buffer,
- bool is_failure)
-{
- binder_size_t off_end_offset;
-
- off_end_offset = ALIGN(buffer->data_size, sizeof(void *));
- off_end_offset += buffer->offsets_size;
-
- binder_transaction_buffer_release(proc, thread, buffer,
- off_end_offset, is_failure);
-}
-
-static int binder_translate_binder(struct flat_binder_object *fp,
- struct binder_transaction *t,
- struct binder_thread *thread)
-{
- struct binder_node *node;
- struct binder_proc *proc = thread->proc;
- struct binder_proc *target_proc = t->to_proc;
- struct binder_ref_data rdata;
- int ret = 0;
-
- node = binder_get_node(proc, fp->binder);
- if (!node) {
- node = binder_new_node(proc, fp);
- if (!node)
- return -ENOMEM;
- }
- if (fp->cookie != node->cookie) {
- binder_user_error("%d:%d sending u%016llx node %d, cookie mismatch %016llx != %016llx\n",
- proc->pid, thread->pid, (u64)fp->binder,
- node->debug_id, (u64)fp->cookie,
- (u64)node->cookie);
- ret = -EINVAL;
- goto done;
- }
- if (security_binder_transfer_binder(proc->cred, target_proc->cred)) {
- ret = -EPERM;
- goto done;
- }
-
- ret = binder_inc_ref_for_node(target_proc, node,
- fp->hdr.type == BINDER_TYPE_BINDER,
- &thread->todo, &rdata);
- if (ret)
- goto done;
-
- if (fp->hdr.type == BINDER_TYPE_BINDER)
- fp->hdr.type = BINDER_TYPE_HANDLE;
- else
- fp->hdr.type = BINDER_TYPE_WEAK_HANDLE;
- fp->binder = 0;
- fp->handle = rdata.desc;
- fp->cookie = 0;
-
- trace_binder_transaction_node_to_ref(t, node, &rdata);
- binder_debug(BINDER_DEBUG_TRANSACTION,
- " node %d u%016llx -> ref %d desc %d\n",
- node->debug_id, (u64)node->ptr,
- rdata.debug_id, rdata.desc);
-done:
- binder_put_node(node);
- return ret;
-}
-
-static int binder_translate_handle(struct flat_binder_object *fp,
- struct binder_transaction *t,
- struct binder_thread *thread)
-{
- struct binder_proc *proc = thread->proc;
- struct binder_proc *target_proc = t->to_proc;
- struct binder_node *node;
- struct binder_ref_data src_rdata;
- int ret = 0;
-
- node = binder_get_node_from_ref(proc, fp->handle,
- fp->hdr.type == BINDER_TYPE_HANDLE, &src_rdata);
- if (!node) {
- binder_user_error("%d:%d got transaction with invalid handle, %d\n",
- proc->pid, thread->pid, fp->handle);
- return -EINVAL;
- }
- if (security_binder_transfer_binder(proc->cred, target_proc->cred)) {
- ret = -EPERM;
- goto done;
- }
-
- binder_node_lock(node);
- if (node->proc == target_proc) {
- if (fp->hdr.type == BINDER_TYPE_HANDLE)
- fp->hdr.type = BINDER_TYPE_BINDER;
- else
- fp->hdr.type = BINDER_TYPE_WEAK_BINDER;
- fp->binder = node->ptr;
- fp->cookie = node->cookie;
- if (node->proc)
- binder_inner_proc_lock(node->proc);
- else
- __acquire(&node->proc->inner_lock);
- binder_inc_node_nilocked(node,
- fp->hdr.type == BINDER_TYPE_BINDER,
- 0, NULL);
- if (node->proc)
- binder_inner_proc_unlock(node->proc);
- else
- __release(&node->proc->inner_lock);
- trace_binder_transaction_ref_to_node(t, node, &src_rdata);
- binder_debug(BINDER_DEBUG_TRANSACTION,
- " ref %d desc %d -> node %d u%016llx\n",
- src_rdata.debug_id, src_rdata.desc, node->debug_id,
- (u64)node->ptr);
- binder_node_unlock(node);
- } else {
- struct binder_ref_data dest_rdata;
-
- binder_node_unlock(node);
- ret = binder_inc_ref_for_node(target_proc, node,
- fp->hdr.type == BINDER_TYPE_HANDLE,
- NULL, &dest_rdata);
- if (ret)
- goto done;
-
- fp->binder = 0;
- fp->handle = dest_rdata.desc;
- fp->cookie = 0;
- trace_binder_transaction_ref_to_ref(t, node, &src_rdata,
- &dest_rdata);
- binder_debug(BINDER_DEBUG_TRANSACTION,
- " ref %d desc %d -> ref %d desc %d (node %d)\n",
- src_rdata.debug_id, src_rdata.desc,
- dest_rdata.debug_id, dest_rdata.desc,
- node->debug_id);
- }
-done:
- binder_put_node(node);
- return ret;
-}
-
-static int binder_translate_fd(u32 fd, binder_size_t fd_offset,
- struct binder_transaction *t,
- struct binder_thread *thread,
- struct binder_transaction *in_reply_to)
-{
- struct binder_proc *proc = thread->proc;
- struct binder_proc *target_proc = t->to_proc;
- struct binder_txn_fd_fixup *fixup;
- struct file *file;
- int ret = 0;
- bool target_allows_fd;
-
- if (in_reply_to)
- target_allows_fd = !!(in_reply_to->flags & TF_ACCEPT_FDS);
- else
- target_allows_fd = t->buffer->target_node->accept_fds;
- if (!target_allows_fd) {
- binder_user_error("%d:%d got %s with fd, %d, but target does not allow fds\n",
- proc->pid, thread->pid,
- in_reply_to ? "reply" : "transaction",
- fd);
- ret = -EPERM;
- goto err_fd_not_accepted;
- }
-
- file = fget(fd);
- if (!file) {
- binder_user_error("%d:%d got transaction with invalid fd, %d\n",
- proc->pid, thread->pid, fd);
- ret = -EBADF;
- goto err_fget;
- }
- ret = security_binder_transfer_file(proc->cred, target_proc->cred, file);
- if (ret < 0) {
- ret = -EPERM;
- goto err_security;
- }
-
- /*
- * Add fixup record for this transaction. The allocation
- * of the fd in the target needs to be done from a
- * target thread.
- */
- fixup = kzalloc_obj(*fixup);
- if (!fixup) {
- ret = -ENOMEM;
- goto err_alloc;
- }
- fixup->file = file;
- fixup->offset = fd_offset;
- fixup->target_fd = -1;
- trace_binder_transaction_fd_send(t, fd, fixup->offset);
- list_add_tail(&fixup->fixup_entry, &t->fd_fixups);
-
- return ret;
-
-err_alloc:
-err_security:
- fput(file);
-err_fget:
-err_fd_not_accepted:
- return ret;
-}
-
-/**
- * struct binder_ptr_fixup - data to be fixed-up in target buffer
- * @offset: offset in target buffer to fixup
- * @skip_size: bytes to skip in copy (fixup will be written later)
- * @fixup_data: data to write at fixup offset
- * @node: list node
- *
- * This is used for the pointer fixup list (pf) which is created and consumed
- * during binder_transaction() and is only accessed locally. No
- * locking is necessary.
- *
- * The list is ordered by @offset.
- */
-struct binder_ptr_fixup {
- binder_size_t offset;
- size_t skip_size;
- binder_uintptr_t fixup_data;
- struct list_head node;
-};
-
-/**
- * struct binder_sg_copy - scatter-gather data to be copied
- * @offset: offset in target buffer
- * @sender_uaddr: user address in source buffer
- * @length: bytes to copy
- * @node: list node
- *
- * This is used for the sg copy list (sgc) which is created and consumed
- * during binder_transaction() and is only accessed locally. No
- * locking is necessary.
- *
- * The list is ordered by @offset.
- */
-struct binder_sg_copy {
- binder_size_t offset;
- const void __user *sender_uaddr;
- size_t length;
- struct list_head node;
-};
-
-/**
- * binder_do_deferred_txn_copies() - copy and fixup scatter-gather data
- * @alloc: binder_alloc associated with @buffer
- * @buffer: binder buffer in target process
- * @sgc_head: list_head of scatter-gather copy list
- * @pf_head: list_head of pointer fixup list
- *
- * Processes all elements of @sgc_head, applying fixups from @pf_head
- * and copying the scatter-gather data from the source process' user
- * buffer to the target's buffer. It is expected that the list creation
- * and processing all occurs during binder_transaction() so these lists
- * are only accessed in local context.
- *
- * Return: 0=success, else -errno
- */
-static int binder_do_deferred_txn_copies(struct binder_alloc *alloc,
- struct binder_buffer *buffer,
- struct list_head *sgc_head,
- struct list_head *pf_head)
-{
- int ret = 0;
- struct binder_sg_copy *sgc, *tmpsgc;
- struct binder_ptr_fixup *tmppf;
- struct binder_ptr_fixup *pf =
- list_first_entry_or_null(pf_head, struct binder_ptr_fixup,
- node);
-
- list_for_each_entry_safe(sgc, tmpsgc, sgc_head, node) {
- size_t bytes_copied = 0;
-
- while (bytes_copied < sgc->length) {
- size_t copy_size;
- size_t bytes_left = sgc->length - bytes_copied;
- size_t offset = sgc->offset + bytes_copied;
-
- /*
- * We copy up to the fixup (pointed to by pf)
- */
- copy_size = pf ? min(bytes_left, (size_t)pf->offset - offset)
- : bytes_left;
- if (!ret && copy_size)
- ret = binder_alloc_copy_user_to_buffer(
- alloc, buffer,
- offset,
- sgc->sender_uaddr + bytes_copied,
- copy_size);
- bytes_copied += copy_size;
- if (copy_size != bytes_left) {
- BUG_ON(!pf);
- /* we stopped at a fixup offset */
- if (pf->skip_size) {
- /*
- * we are just skipping. This is for
- * BINDER_TYPE_FDA where the translated
- * fds will be fixed up when we get
- * to target context.
- */
- bytes_copied += pf->skip_size;
- } else {
- /* apply the fixup indicated by pf */
- if (!ret)
- ret = binder_alloc_copy_to_buffer(
- alloc, buffer,
- pf->offset,
- &pf->fixup_data,
- sizeof(pf->fixup_data));
- bytes_copied += sizeof(pf->fixup_data);
- }
- list_del(&pf->node);
- kfree(pf);
- pf = list_first_entry_or_null(pf_head,
- struct binder_ptr_fixup, node);
- }
- }
- list_del(&sgc->node);
- kfree(sgc);
- }
- list_for_each_entry_safe(pf, tmppf, pf_head, node) {
- BUG_ON(pf->skip_size == 0);
- list_del(&pf->node);
- kfree(pf);
- }
- BUG_ON(!list_empty(sgc_head));
-
- return ret > 0 ? -EINVAL : ret;
-}
-
-/**
- * binder_cleanup_deferred_txn_lists() - free specified lists
- * @sgc_head: list_head of scatter-gather copy list
- * @pf_head: list_head of pointer fixup list
- *
- * Called to clean up @sgc_head and @pf_head if there is an
- * error.
- */
-static void binder_cleanup_deferred_txn_lists(struct list_head *sgc_head,
- struct list_head *pf_head)
-{
- struct binder_sg_copy *sgc, *tmpsgc;
- struct binder_ptr_fixup *pf, *tmppf;
-
- list_for_each_entry_safe(sgc, tmpsgc, sgc_head, node) {
- list_del(&sgc->node);
- kfree(sgc);
- }
- list_for_each_entry_safe(pf, tmppf, pf_head, node) {
- list_del(&pf->node);
- kfree(pf);
- }
-}
-
-/**
- * binder_defer_copy() - queue a scatter-gather buffer for copy
- * @sgc_head: list_head of scatter-gather copy list
- * @offset: binder buffer offset in target process
- * @sender_uaddr: user address in source process
- * @length: bytes to copy
- *
- * Specify a scatter-gather block to be copied. The actual copy must
- * be deferred until all the needed fixups are identified and queued.
- * Then the copy and fixups are done together so un-translated values
- * from the source are never visible in the target buffer.
- *
- * We are guaranteed that repeated calls to this function will have
- * monotonically increasing @offset values so the list will naturally
- * be ordered.
- *
- * Return: 0=success, else -errno
- */
-static int binder_defer_copy(struct list_head *sgc_head, binder_size_t offset,
- const void __user *sender_uaddr, size_t length)
-{
- struct binder_sg_copy *bc = kzalloc_obj(*bc);
-
- if (!bc)
- return -ENOMEM;
-
- bc->offset = offset;
- bc->sender_uaddr = sender_uaddr;
- bc->length = length;
- INIT_LIST_HEAD(&bc->node);
-
- /*
- * We are guaranteed that the deferred copies are in-order
- * so just add to the tail.
- */
- list_add_tail(&bc->node, sgc_head);
-
- return 0;
-}
-
-/**
- * binder_add_fixup() - queue a fixup to be applied to sg copy
- * @pf_head: list_head of binder ptr fixup list
- * @offset: binder buffer offset in target process
- * @fixup: bytes to be copied for fixup
- * @skip_size: bytes to skip when copying (fixup will be applied later)
- *
- * Add the specified fixup to a list ordered by @offset. When copying
- * the scatter-gather buffers, the fixup will be copied instead of
- * data from the source buffer. For BINDER_TYPE_FDA fixups, the fixup
- * will be applied later (in target process context), so we just skip
- * the bytes specified by @skip_size. If @skip_size is 0, we copy the
- * value in @fixup.
- *
- * This function is called *mostly* in @offset order, but there are
- * exceptions. Since out-of-order inserts are relatively uncommon,
- * we insert the new element by searching backward from the tail of
- * the list.
- *
- * Return: 0=success, else -errno
- */
-static int binder_add_fixup(struct list_head *pf_head, binder_size_t offset,
- binder_uintptr_t fixup, size_t skip_size)
-{
- struct binder_ptr_fixup *pf = kzalloc_obj(*pf);
- struct binder_ptr_fixup *tmppf;
-
- if (!pf)
- return -ENOMEM;
-
- pf->offset = offset;
- pf->fixup_data = fixup;
- pf->skip_size = skip_size;
- INIT_LIST_HEAD(&pf->node);
-
- /* Fixups are *mostly* added in-order, but there are some
- * exceptions. Look backwards through list for insertion point.
- */
- list_for_each_entry_reverse(tmppf, pf_head, node) {
- if (tmppf->offset < pf->offset) {
- list_add(&pf->node, &tmppf->node);
- return 0;
- }
- }
- /*
- * if we get here, then the new offset is the lowest so
- * insert at the head
- */
- list_add(&pf->node, pf_head);
- return 0;
-}
-
-static int binder_translate_fd_array(struct list_head *pf_head,
- struct binder_fd_array_object *fda,
- const void __user *sender_ubuffer,
- struct binder_buffer_object *parent,
- struct binder_buffer_object *sender_uparent,
- struct binder_transaction *t,
- struct binder_thread *thread,
- struct binder_transaction *in_reply_to)
-{
- binder_size_t fdi, fd_buf_size;
- binder_size_t fda_offset;
- const void __user *sender_ufda_base;
- struct binder_proc *proc = thread->proc;
- int ret;
-
- if (fda->num_fds == 0)
- return 0;
-
- fd_buf_size = sizeof(u32) * fda->num_fds;
- if (fda->num_fds >= SIZE_MAX / sizeof(u32)) {
- binder_user_error("%d:%d got transaction with invalid number of fds (%lld)\n",
- proc->pid, thread->pid, (u64)fda->num_fds);
- return -EINVAL;
- }
- if (fd_buf_size > parent->length ||
- fda->parent_offset > parent->length - fd_buf_size) {
- /* No space for all file descriptors here. */
- binder_user_error("%d:%d not enough space to store %lld fds in buffer\n",
- proc->pid, thread->pid, (u64)fda->num_fds);
- return -EINVAL;
- }
- /*
- * the source data for binder_buffer_object is visible
- * to user-space and the @buffer element is the user
- * pointer to the buffer_object containing the fd_array.
- * Convert the address to an offset relative to
- * the base of the transaction buffer.
- */
- fda_offset = parent->buffer - t->buffer->user_data +
- fda->parent_offset;
- sender_ufda_base = (void __user *)(uintptr_t)sender_uparent->buffer +
- fda->parent_offset;
-
- if (!IS_ALIGNED((unsigned long)fda_offset, sizeof(u32)) ||
- !IS_ALIGNED((unsigned long)sender_ufda_base, sizeof(u32))) {
- binder_user_error("%d:%d parent offset not aligned correctly.\n",
- proc->pid, thread->pid);
- return -EINVAL;
- }
- ret = binder_add_fixup(pf_head, fda_offset, 0, fda->num_fds * sizeof(u32));
- if (ret)
- return ret;
-
- for (fdi = 0; fdi < fda->num_fds; fdi++) {
- u32 fd;
- binder_size_t offset = fda_offset + fdi * sizeof(fd);
- binder_size_t sender_uoffset = fdi * sizeof(fd);
-
- ret = copy_from_user(&fd, sender_ufda_base + sender_uoffset, sizeof(fd));
- if (!ret)
- ret = binder_translate_fd(fd, offset, t, thread,
- in_reply_to);
- if (ret)
- return ret > 0 ? -EINVAL : ret;
- }
- return 0;
-}
-
-static int binder_fixup_parent(struct list_head *pf_head,
- struct binder_transaction *t,
- struct binder_thread *thread,
- struct binder_buffer_object *bp,
- binder_size_t off_start_offset,
- binder_size_t num_valid,
- binder_size_t last_fixup_obj_off,
- binder_size_t last_fixup_min_off)
-{
- struct binder_buffer_object *parent;
- struct binder_buffer *b = t->buffer;
- struct binder_proc *proc = thread->proc;
- struct binder_proc *target_proc = t->to_proc;
- struct binder_object object;
- binder_size_t buffer_offset;
- binder_size_t parent_offset;
-
- if (!(bp->flags & BINDER_BUFFER_FLAG_HAS_PARENT))
- return 0;
-
- parent = binder_validate_ptr(target_proc, b, &object, bp->parent,
- off_start_offset, &parent_offset,
- num_valid);
- if (!parent) {
- binder_user_error("%d:%d got transaction with invalid parent offset or type\n",
- proc->pid, thread->pid);
- return -EINVAL;
- }
-
- if (!binder_validate_fixup(target_proc, b, off_start_offset,
- parent_offset, bp->parent_offset,
- last_fixup_obj_off,
- last_fixup_min_off)) {
- binder_user_error("%d:%d got transaction with out-of-order buffer fixup\n",
- proc->pid, thread->pid);
- return -EINVAL;
- }
-
- if (parent->length < sizeof(binder_uintptr_t) ||
- bp->parent_offset > parent->length - sizeof(binder_uintptr_t)) {
- /* No space for a pointer here! */
- binder_user_error("%d:%d got transaction with invalid parent offset\n",
- proc->pid, thread->pid);
- return -EINVAL;
- }
-
- buffer_offset = bp->parent_offset + parent->buffer - b->user_data;
-
- return binder_add_fixup(pf_head, buffer_offset, bp->buffer, 0);
-}
-
-/**
- * binder_can_update_transaction() - Can a txn be superseded by an updated one?
- * @t1: the pending async txn in the frozen process
- * @t2: the new async txn to supersede the outdated pending one
- *
- * Return: true if t2 can supersede t1
- * false if t2 can not supersede t1
- */
-static bool binder_can_update_transaction(struct binder_transaction *t1,
- struct binder_transaction *t2)
-{
- if ((t1->flags & t2->flags & (TF_ONE_WAY | TF_UPDATE_TXN)) !=
- (TF_ONE_WAY | TF_UPDATE_TXN) || !t1->to_proc || !t2->to_proc)
- return false;
- if (t1->to_proc->tsk == t2->to_proc->tsk && t1->code == t2->code &&
- t1->flags == t2->flags && t1->buffer->pid == t2->buffer->pid &&
- t1->buffer->target_node->ptr == t2->buffer->target_node->ptr &&
- t1->buffer->target_node->cookie == t2->buffer->target_node->cookie)
- return true;
- return false;
-}
-
-/**
- * binder_find_outdated_transaction_ilocked() - Find the outdated transaction
- * @t: new async transaction
- * @target_list: list to find outdated transaction
- *
- * Return: the outdated transaction if found
- * NULL if no outdated transacton can be found
- *
- * Requires the proc->inner_lock to be held.
- */
-static struct binder_transaction *
-binder_find_outdated_transaction_ilocked(struct binder_transaction *t,
- struct list_head *target_list)
-{
- struct binder_work *w;
-
- list_for_each_entry(w, target_list, entry) {
- struct binder_transaction *t_queued;
-
- if (w->type != BINDER_WORK_TRANSACTION)
- continue;
- t_queued = container_of(w, struct binder_transaction, work);
- if (binder_can_update_transaction(t_queued, t))
- return t_queued;
- }
- return NULL;
-}
-
-/**
- * binder_proc_transaction() - sends a transaction to a process and wakes it up
- * @t: transaction to send
- * @proc: process to send the transaction to
- * @thread: thread in @proc to send the transaction to (may be NULL)
- *
- * This function queues a transaction to the specified process. It will try
- * to find a thread in the target process to handle the transaction and
- * wake it up. If no thread is found, the work is queued to the proc
- * waitqueue.
- *
- * If the @thread parameter is not NULL, the transaction is always queued
- * to the waitlist of that specific thread.
- *
- * Return: 0 if the transaction was successfully queued
- * BR_DEAD_REPLY if the target process or thread is dead
- * BR_FROZEN_REPLY if the target process or thread is frozen and
- * the sync transaction was rejected
- * BR_TRANSACTION_PENDING_FROZEN if the target process is frozen
- * and the async transaction was successfully queued
- */
-static int binder_proc_transaction(struct binder_transaction *t,
- struct binder_proc *proc,
- struct binder_thread *thread)
-{
- struct binder_node *node = t->buffer->target_node;
- bool oneway = !!(t->flags & TF_ONE_WAY);
- bool pending_async = false;
- struct binder_transaction *t_outdated = NULL;
- bool frozen = false;
-
- BUG_ON(!node);
- binder_node_lock(node);
- if (oneway) {
- BUG_ON(thread);
- if (node->has_async_transaction)
- pending_async = true;
- else
- node->has_async_transaction = true;
- }
-
- binder_inner_proc_lock(proc);
- if (proc->is_frozen) {
- frozen = true;
- proc->sync_recv |= !oneway;
- proc->async_recv |= oneway;
- }
-
- if ((frozen && !oneway) || proc->is_dead ||
- (thread && thread->is_dead)) {
- binder_inner_proc_unlock(proc);
- binder_node_unlock(node);
- return frozen ? BR_FROZEN_REPLY : BR_DEAD_REPLY;
- }
-
- if (!thread && !pending_async)
- thread = binder_select_thread_ilocked(proc);
-
- if (thread) {
- binder_enqueue_thread_work_ilocked(thread, &t->work);
- } else if (!pending_async) {
- binder_enqueue_work_ilocked(&t->work, &proc->todo);
- } else {
- if ((t->flags & TF_UPDATE_TXN) && frozen) {
- t_outdated = binder_find_outdated_transaction_ilocked(t,
- &node->async_todo);
- if (t_outdated) {
- binder_debug(BINDER_DEBUG_TRANSACTION,
- "txn %d supersedes %d\n",
- t->debug_id, t_outdated->debug_id);
- list_del_init(&t_outdated->work.entry);
- proc->outstanding_txns--;
- }
- }
- binder_enqueue_work_ilocked(&t->work, &node->async_todo);
- }
-
- if (!pending_async)
- binder_wakeup_thread_ilocked(proc, thread, !oneway /* sync */);
-
- proc->outstanding_txns++;
- binder_inner_proc_unlock(proc);
- binder_node_unlock(node);
-
- /*
- * To reduce potential contention, free the outdated transaction and
- * buffer after releasing the locks.
- */
- if (t_outdated) {
- struct binder_buffer *buffer = t_outdated->buffer;
-
- t_outdated->buffer = NULL;
- buffer->transaction = NULL;
- trace_binder_transaction_update_buffer_release(buffer);
- binder_release_entire_buffer(proc, NULL, buffer, true);
- binder_alloc_free_buf(&proc->alloc, buffer);
- binder_free_txn_fixups(t_outdated);
- kfree(t_outdated);
- binder_stats_deleted(BINDER_STAT_TRANSACTION);
- }
-
- if (oneway && frozen)
- return BR_TRANSACTION_PENDING_FROZEN;
-
- return 0;
-}
-
-/**
- * binder_get_node_refs_for_txn() - Get required refs on node for txn
- * @node: struct binder_node for which to get refs
- * @procp: returns @node->proc if valid
- * @error: if no @procp then returns BR_DEAD_REPLY
- *
- * User-space normally keeps the node alive when creating a transaction
- * since it has a reference to the target. The local strong ref keeps it
- * alive if the sending process dies before the target process processes
- * the transaction. If the source process is malicious or has a reference
- * counting bug, relying on the local strong ref can fail.
- *
- * Since user-space can cause the local strong ref to go away, we also take
- * a tmpref on the node to ensure it survives while we are constructing
- * the transaction. We also need a tmpref on the proc while we are
- * constructing the transaction, so we take that here as well.
- *
- * Return: The target_node with refs taken or NULL if no @node->proc is NULL.
- * Also sets @procp if valid. If the @node->proc is NULL indicating that the
- * target proc has died, @error is set to BR_DEAD_REPLY.
- */
-static struct binder_node *binder_get_node_refs_for_txn(
- struct binder_node *node,
- struct binder_proc **procp,
- uint32_t *error)
-{
- struct binder_node *target_node = NULL;
-
- binder_node_inner_lock(node);
- if (node->proc) {
- target_node = node;
- binder_inc_node_nilocked(node, 1, 0, NULL);
- binder_inc_node_tmpref_ilocked(node);
- node->proc->tmp_ref++;
- *procp = node->proc;
- } else
- *error = BR_DEAD_REPLY;
- binder_node_inner_unlock(node);
-
- return target_node;
-}
-
-static void binder_set_txn_from_error(struct binder_transaction *t, int id,
- uint32_t command, int32_t param)
-{
- struct binder_thread *from = binder_get_txn_from_and_acq_inner(t);
-
- if (!from) {
- /* annotation for sparse */
- __release(&from->proc->inner_lock);
- return;
- }
-
- /* don't override existing errors */
- if (from->ee.command == BR_OK)
- binder_set_extended_error(&from->ee, id, command, param);
- binder_inner_proc_unlock(from->proc);
- binder_thread_dec_tmpref(from);
-}
-
-/**
- * binder_netlink_report() - report a transaction failure via netlink
- * @proc: the binder proc sending the transaction
- * @t: the binder transaction that failed
- * @data_size: the user provided data size for the transaction
- * @error: enum binder_driver_return_protocol returned to sender
- *
- * Note that t->buffer is not safe to access here, as it may have been
- * released (or not yet allocated). Callers should guarantee all the
- * transaction items used here are safe to access.
- */
-static void binder_netlink_report(struct binder_proc *proc,
- struct binder_transaction *t,
- u32 data_size,
- u32 error)
-{
- const char *context = proc->context->name;
- struct sk_buff *skb;
- void *hdr;
-
- if (!genl_has_listeners(&binder_nl_family, &init_net,
- BINDER_NLGRP_REPORT))
- return;
-
- trace_binder_netlink_report(context, t, data_size, error);
-
- skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
- if (!skb)
- return;
-
- hdr = genlmsg_put(skb, 0, 0, &binder_nl_family, 0, BINDER_CMD_REPORT);
- if (!hdr)
- goto free_skb;
-
- if (nla_put_u32(skb, BINDER_A_REPORT_ERROR, error) ||
- nla_put_string(skb, BINDER_A_REPORT_CONTEXT, context) ||
- nla_put_u32(skb, BINDER_A_REPORT_FROM_PID, t->from_pid) ||
- nla_put_u32(skb, BINDER_A_REPORT_FROM_TID, t->from_tid))
- goto cancel_skb;
-
- if (t->to_proc &&
- nla_put_u32(skb, BINDER_A_REPORT_TO_PID, t->to_proc->pid))
- goto cancel_skb;
-
- if (t->to_thread &&
- nla_put_u32(skb, BINDER_A_REPORT_TO_TID, t->to_thread->pid))
- goto cancel_skb;
-
- if (t->is_reply && nla_put_flag(skb, BINDER_A_REPORT_IS_REPLY))
- goto cancel_skb;
-
- if (nla_put_u32(skb, BINDER_A_REPORT_FLAGS, t->flags) ||
- nla_put_u32(skb, BINDER_A_REPORT_CODE, t->code) ||
- nla_put_u32(skb, BINDER_A_REPORT_DATA_SIZE, data_size))
- goto cancel_skb;
-
- genlmsg_end(skb, hdr);
- genlmsg_multicast(&binder_nl_family, skb, 0, BINDER_NLGRP_REPORT,
- GFP_KERNEL);
- return;
-
-cancel_skb:
- genlmsg_cancel(skb, hdr);
-free_skb:
- nlmsg_free(skb);
-}
-
-static void binder_transaction(struct binder_proc *proc,
- struct binder_thread *thread,
- struct binder_transaction_data *tr, int reply,
- binder_size_t extra_buffers_size)
-{
- int ret;
- struct binder_transaction *t;
- struct binder_work *w;
- struct binder_work *tcomplete;
- binder_size_t buffer_offset = 0;
- binder_size_t off_start_offset, off_end_offset;
- binder_size_t off_min;
- binder_size_t sg_buf_offset, sg_buf_end_offset;
- binder_size_t user_offset = 0;
- struct binder_proc *target_proc = NULL;
- struct binder_thread *target_thread = NULL;
- struct binder_node *target_node = NULL;
- struct binder_transaction *in_reply_to = NULL;
- struct binder_transaction_log_entry *e;
- uint32_t return_error = 0;
- uint32_t return_error_param = 0;
- uint32_t return_error_line = 0;
- binder_size_t last_fixup_obj_off = 0;
- binder_size_t last_fixup_min_off = 0;
- struct binder_context *context = proc->context;
- int t_debug_id = atomic_inc_return(&binder_last_id);
- ktime_t t_start_time = ktime_get();
- struct lsm_context lsmctx = { };
- size_t lsmctx_aligned_size = 0;
- LIST_HEAD(sgc_head);
- LIST_HEAD(pf_head);
- const void __user *user_buffer = (const void __user *)
- (uintptr_t)tr->data.ptr.buffer;
-
- e = binder_transaction_log_add(&binder_transaction_log);
- e->debug_id = t_debug_id;
- e->call_type = reply ? 2 : !!(tr->flags & TF_ONE_WAY);
- e->from_proc = proc->pid;
- e->from_thread = thread->pid;
- e->target_handle = tr->target.handle;
- e->data_size = tr->data_size;
- e->offsets_size = tr->offsets_size;
- strscpy(e->context_name, proc->context->name, BINDERFS_MAX_NAME);
-
- binder_inner_proc_lock(proc);
- binder_set_extended_error(&thread->ee, t_debug_id, BR_OK, 0);
- binder_inner_proc_unlock(proc);
-
- t = kzalloc_obj(*t);
- if (!t) {
- binder_txn_error("%d:%d cannot allocate transaction\n",
- thread->pid, proc->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = -ENOMEM;
- return_error_line = __LINE__;
- goto err_alloc_t_failed;
- }
- INIT_LIST_HEAD(&t->fd_fixups);
- binder_stats_created(BINDER_STAT_TRANSACTION);
- spin_lock_init(&t->lock);
- t->debug_id = t_debug_id;
- t->start_time = t_start_time;
- t->from_pid = proc->pid;
- t->from_tid = thread->pid;
- t->sender_euid = current_euid();
- t->code = tr->code;
- t->flags = tr->flags;
- t->priority = task_nice(current);
- t->work.type = BINDER_WORK_TRANSACTION;
- t->is_async = !reply && (tr->flags & TF_ONE_WAY);
- t->is_reply = reply;
- if (!reply && !(tr->flags & TF_ONE_WAY))
- t->from = thread;
-
- if (reply) {
- binder_inner_proc_lock(proc);
- in_reply_to = thread->transaction_stack;
- if (in_reply_to == NULL) {
- binder_inner_proc_unlock(proc);
- binder_user_error("%d:%d got reply transaction with no transaction stack\n",
- proc->pid, thread->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EPROTO;
- return_error_line = __LINE__;
- goto err_empty_call_stack;
- }
- if (in_reply_to->to_thread != thread) {
- spin_lock(&in_reply_to->lock);
- binder_user_error("%d:%d got reply transaction with bad transaction stack, transaction %d has target %d:%d\n",
- proc->pid, thread->pid, in_reply_to->debug_id,
- in_reply_to->to_proc ?
- in_reply_to->to_proc->pid : 0,
- in_reply_to->to_thread ?
- in_reply_to->to_thread->pid : 0);
- spin_unlock(&in_reply_to->lock);
- binder_inner_proc_unlock(proc);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EPROTO;
- return_error_line = __LINE__;
- in_reply_to = NULL;
- goto err_bad_call_stack;
- }
- thread->transaction_stack = in_reply_to->to_parent;
- binder_inner_proc_unlock(proc);
- binder_set_nice(in_reply_to->saved_priority);
- target_thread = binder_get_txn_from_and_acq_inner(in_reply_to);
- if (target_thread == NULL) {
- /* annotation for sparse */
- __release(&target_thread->proc->inner_lock);
- binder_txn_error("%d:%d reply target not found\n",
- thread->pid, proc->pid);
- return_error = BR_DEAD_REPLY;
- return_error_line = __LINE__;
- goto err_dead_binder;
- }
- if (target_thread->transaction_stack != in_reply_to) {
- binder_user_error("%d:%d got reply transaction with bad target transaction stack %d, expected %d\n",
- proc->pid, thread->pid,
- target_thread->transaction_stack ?
- target_thread->transaction_stack->debug_id : 0,
- in_reply_to->debug_id);
- binder_inner_proc_unlock(target_thread->proc);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EPROTO;
- return_error_line = __LINE__;
- in_reply_to = NULL;
- target_thread = NULL;
- goto err_dead_binder;
- }
- target_proc = target_thread->proc;
- target_proc->tmp_ref++;
- binder_inner_proc_unlock(target_thread->proc);
- } else {
- if (tr->target.handle) {
- struct binder_ref *ref;
-
- /*
- * There must already be a strong ref
- * on this node. If so, do a strong
- * increment on the node to ensure it
- * stays alive until the transaction is
- * done.
- */
- binder_proc_lock(proc);
- ref = binder_get_ref_olocked(proc, tr->target.handle,
- true);
- if (ref) {
- target_node = binder_get_node_refs_for_txn(
- ref->node, &target_proc,
- &return_error);
- } else {
- binder_user_error("%d:%d got transaction to invalid handle, %u\n",
- proc->pid, thread->pid, tr->target.handle);
- return_error = BR_FAILED_REPLY;
- }
- binder_proc_unlock(proc);
- } else {
- mutex_lock(&context->context_mgr_node_lock);
- target_node = context->binder_context_mgr_node;
- if (target_node)
- target_node = binder_get_node_refs_for_txn(
- target_node, &target_proc,
- &return_error);
- else
- return_error = BR_DEAD_REPLY;
- mutex_unlock(&context->context_mgr_node_lock);
- if (target_node && target_proc->pid == proc->pid) {
- binder_user_error("%d:%d got transaction to context manager from process owning it\n",
- proc->pid, thread->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EINVAL;
- return_error_line = __LINE__;
- goto err_invalid_target_handle;
- }
- }
- if (!target_node) {
- binder_txn_error("%d:%d cannot find target node\n",
- proc->pid, thread->pid);
- /* return_error is set above */
- return_error_param = -EINVAL;
- return_error_line = __LINE__;
- goto err_dead_binder;
- }
- e->to_node = target_node->debug_id;
- if (WARN_ON(proc == target_proc)) {
- binder_txn_error("%d:%d self transactions not allowed\n",
- thread->pid, proc->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EINVAL;
- return_error_line = __LINE__;
- goto err_invalid_target_handle;
- }
- if (security_binder_transaction(proc->cred,
- target_proc->cred) < 0) {
- binder_txn_error("%d:%d transaction credentials failed\n",
- thread->pid, proc->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EPERM;
- return_error_line = __LINE__;
- goto err_invalid_target_handle;
- }
- binder_inner_proc_lock(proc);
-
- w = list_first_entry_or_null(&thread->todo,
- struct binder_work, entry);
- if (!(tr->flags & TF_ONE_WAY) && w &&
- w->type == BINDER_WORK_TRANSACTION) {
- /*
- * Do not allow new outgoing transaction from a
- * thread that has a transaction at the head of
- * its todo list. Only need to check the head
- * because binder_select_thread_ilocked picks a
- * thread from proc->waiting_threads to enqueue
- * the transaction, and nothing is queued to the
- * todo list while the thread is on waiting_threads.
- */
- binder_user_error("%d:%d new transaction not allowed when there is a transaction on thread todo\n",
- proc->pid, thread->pid);
- binder_inner_proc_unlock(proc);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EPROTO;
- return_error_line = __LINE__;
- goto err_bad_todo_list;
- }
-
- if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {
- struct binder_transaction *tmp;
-
- tmp = thread->transaction_stack;
- if (tmp->to_thread != thread) {
- spin_lock(&tmp->lock);
- binder_user_error("%d:%d got new transaction with bad transaction stack, transaction %d has target %d:%d\n",
- proc->pid, thread->pid, tmp->debug_id,
- tmp->to_proc ? tmp->to_proc->pid : 0,
- tmp->to_thread ?
- tmp->to_thread->pid : 0);
- spin_unlock(&tmp->lock);
- binder_inner_proc_unlock(proc);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EPROTO;
- return_error_line = __LINE__;
- goto err_bad_call_stack;
- }
- while (tmp) {
- struct binder_thread *from;
-
- spin_lock(&tmp->lock);
- from = tmp->from;
- if (from && from->proc == target_proc) {
- atomic_inc(&from->tmp_ref);
- target_thread = from;
- spin_unlock(&tmp->lock);
- break;
- }
- spin_unlock(&tmp->lock);
- tmp = tmp->from_parent;
- }
- }
- binder_inner_proc_unlock(proc);
- }
-
- t->to_proc = target_proc;
- t->to_thread = target_thread;
- if (target_thread)
- e->to_thread = target_thread->pid;
- e->to_proc = target_proc->pid;
-
- tcomplete = kzalloc_obj(*tcomplete);
- if (tcomplete == NULL) {
- binder_txn_error("%d:%d cannot allocate work for transaction\n",
- thread->pid, proc->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = -ENOMEM;
- return_error_line = __LINE__;
- goto err_alloc_tcomplete_failed;
- }
- binder_stats_created(BINDER_STAT_TRANSACTION_COMPLETE);
-
- if (reply)
- binder_debug(BINDER_DEBUG_TRANSACTION,
- "%d:%d BC_REPLY %d -> %d:%d, data size %lld-%lld-%lld\n",
- proc->pid, thread->pid, t->debug_id,
- target_proc->pid, target_thread->pid,
- (u64)tr->data_size, (u64)tr->offsets_size,
- (u64)extra_buffers_size);
- else
- binder_debug(BINDER_DEBUG_TRANSACTION,
- "%d:%d BC_TRANSACTION %d -> %d - node %d, data size %lld-%lld-%lld\n",
- proc->pid, thread->pid, t->debug_id,
- target_proc->pid, target_node->debug_id,
- (u64)tr->data_size, (u64)tr->offsets_size,
- (u64)extra_buffers_size);
-
- if (target_node && target_node->txn_security_ctx) {
- u32 secid;
-
- security_cred_getsecid(proc->cred, &secid);
- ret = security_secid_to_secctx(secid, &lsmctx);
- if (ret < 0) {
- binder_txn_error("%d:%d failed to get security context\n",
- thread->pid, proc->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = ret;
- return_error_line = __LINE__;
- goto err_get_secctx_failed;
- }
- lsmctx_aligned_size = ALIGN(lsmctx.len, sizeof(u64));
- extra_buffers_size += lsmctx_aligned_size;
- if (extra_buffers_size < lsmctx_aligned_size) {
- binder_txn_error("%d:%d integer overflow of extra_buffers_size\n",
- thread->pid, proc->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EINVAL;
- return_error_line = __LINE__;
- goto err_bad_extra_size;
- }
- }
-
- trace_binder_transaction(reply, t, target_node);
-
- t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size,
- tr->offsets_size, extra_buffers_size,
- !reply && (t->flags & TF_ONE_WAY));
- if (IS_ERR(t->buffer)) {
- char *s;
-
- ret = PTR_ERR(t->buffer);
- s = (ret == -ESRCH) ? ": vma cleared, target dead or dying"
- : (ret == -ENOSPC) ? ": no space left"
- : (ret == -ENOMEM) ? ": memory allocation failed"
- : "";
- binder_txn_error("cannot allocate buffer%s", s);
-
- return_error_param = PTR_ERR(t->buffer);
- return_error = return_error_param == -ESRCH ?
- BR_DEAD_REPLY : BR_FAILED_REPLY;
- return_error_line = __LINE__;
- t->buffer = NULL;
- goto err_binder_alloc_buf_failed;
- }
- if (lsmctx.context) {
- int err;
- size_t buf_offset = ALIGN(tr->data_size, sizeof(void *)) +
- ALIGN(tr->offsets_size, sizeof(void *)) +
- ALIGN(extra_buffers_size, sizeof(void *)) -
- lsmctx_aligned_size;
-
- t->security_ctx = t->buffer->user_data + buf_offset;
- err = binder_alloc_copy_to_buffer(&target_proc->alloc,
- t->buffer, buf_offset,
- lsmctx.context, lsmctx.len);
- if (err) {
- t->security_ctx = 0;
- WARN_ON(1);
- }
- security_release_secctx(&lsmctx);
- lsmctx.context = NULL;
- }
- t->buffer->debug_id = t->debug_id;
- t->buffer->transaction = t;
- t->buffer->target_node = target_node;
- t->buffer->clear_on_free = !!(t->flags & TF_CLEAR_BUF);
- trace_binder_transaction_alloc_buf(t->buffer);
-
- if (binder_alloc_copy_user_to_buffer(
- &target_proc->alloc,
- t->buffer,
- ALIGN(tr->data_size, sizeof(void *)),
- (const void __user *)
- (uintptr_t)tr->data.ptr.offsets,
- tr->offsets_size)) {
- binder_user_error("%d:%d got transaction with invalid offsets ptr\n",
- proc->pid, thread->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EFAULT;
- return_error_line = __LINE__;
- goto err_copy_data_failed;
- }
- if (!IS_ALIGNED(tr->offsets_size, sizeof(binder_size_t))) {
- binder_user_error("%d:%d got transaction with invalid offsets size, %lld\n",
- proc->pid, thread->pid, (u64)tr->offsets_size);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EINVAL;
- return_error_line = __LINE__;
- goto err_bad_offset;
- }
- if (!IS_ALIGNED(extra_buffers_size, sizeof(u64))) {
- binder_user_error("%d:%d got transaction with unaligned buffers size, %lld\n",
- proc->pid, thread->pid,
- (u64)extra_buffers_size);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EINVAL;
- return_error_line = __LINE__;
- goto err_bad_offset;
- }
- off_start_offset = ALIGN(tr->data_size, sizeof(void *));
- buffer_offset = off_start_offset;
- off_end_offset = off_start_offset + tr->offsets_size;
- sg_buf_offset = ALIGN(off_end_offset, sizeof(void *));
- sg_buf_end_offset = sg_buf_offset + extra_buffers_size -
- lsmctx_aligned_size;
- off_min = 0;
- for (buffer_offset = off_start_offset; buffer_offset < off_end_offset;
- buffer_offset += sizeof(binder_size_t)) {
- struct binder_object_header *hdr;
- size_t object_size;
- struct binder_object object;
- binder_size_t object_offset;
- binder_size_t copy_size;
-
- if (binder_alloc_copy_from_buffer(&target_proc->alloc,
- &object_offset,
- t->buffer,
- buffer_offset,
- sizeof(object_offset))) {
- binder_txn_error("%d:%d copy offset from buffer failed\n",
- thread->pid, proc->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EINVAL;
- return_error_line = __LINE__;
- goto err_bad_offset;
- }
-
- /*
- * Copy the source user buffer up to the next object
- * that will be processed.
- */
- copy_size = object_offset - user_offset;
- if (copy_size && (user_offset > object_offset ||
- object_offset > tr->data_size ||
- binder_alloc_copy_user_to_buffer(
- &target_proc->alloc,
- t->buffer, user_offset,
- user_buffer + user_offset,
- copy_size))) {
- binder_user_error("%d:%d got transaction with invalid data ptr\n",
- proc->pid, thread->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EFAULT;
- return_error_line = __LINE__;
- goto err_copy_data_failed;
- }
- object_size = binder_get_object(target_proc, user_buffer,
- t->buffer, object_offset, &object);
- if (object_size == 0 || object_offset < off_min) {
- binder_user_error("%d:%d got transaction with invalid offset (%lld, min %lld max %lld) or object.\n",
- proc->pid, thread->pid,
- (u64)object_offset,
- (u64)off_min,
- (u64)t->buffer->data_size);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EINVAL;
- return_error_line = __LINE__;
- goto err_bad_offset;
- }
- /*
- * Set offset to the next buffer fragment to be
- * copied
- */
- user_offset = object_offset + object_size;
-
- hdr = &object.hdr;
- off_min = object_offset + object_size;
- switch (hdr->type) {
- case BINDER_TYPE_BINDER:
- case BINDER_TYPE_WEAK_BINDER: {
- struct flat_binder_object *fp;
-
- fp = to_flat_binder_object(hdr);
- ret = binder_translate_binder(fp, t, thread);
-
- if (ret < 0 ||
- binder_alloc_copy_to_buffer(&target_proc->alloc,
- t->buffer,
- object_offset,
- fp, sizeof(*fp))) {
- binder_txn_error("%d:%d translate binder failed\n",
- thread->pid, proc->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = ret;
- return_error_line = __LINE__;
- goto err_translate_failed;
- }
- } break;
- case BINDER_TYPE_HANDLE:
- case BINDER_TYPE_WEAK_HANDLE: {
- struct flat_binder_object *fp;
-
- fp = to_flat_binder_object(hdr);
- ret = binder_translate_handle(fp, t, thread);
- if (ret < 0 ||
- binder_alloc_copy_to_buffer(&target_proc->alloc,
- t->buffer,
- object_offset,
- fp, sizeof(*fp))) {
- binder_txn_error("%d:%d translate handle failed\n",
- thread->pid, proc->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = ret;
- return_error_line = __LINE__;
- goto err_translate_failed;
- }
- } break;
-
- case BINDER_TYPE_FD: {
- struct binder_fd_object *fp = to_binder_fd_object(hdr);
- binder_size_t fd_offset = object_offset +
- (uintptr_t)&fp->fd - (uintptr_t)fp;
- int ret = binder_translate_fd(fp->fd, fd_offset, t,
- thread, in_reply_to);
-
- fp->pad_binder = 0;
- if (ret < 0 ||
- binder_alloc_copy_to_buffer(&target_proc->alloc,
- t->buffer,
- object_offset,
- fp, sizeof(*fp))) {
- binder_txn_error("%d:%d translate fd failed\n",
- thread->pid, proc->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = ret;
- return_error_line = __LINE__;
- goto err_translate_failed;
- }
- } break;
- case BINDER_TYPE_FDA: {
- struct binder_object ptr_object;
- binder_size_t parent_offset;
- struct binder_object user_object;
- size_t user_parent_size;
- struct binder_fd_array_object *fda =
- to_binder_fd_array_object(hdr);
- size_t num_valid = (buffer_offset - off_start_offset) /
- sizeof(binder_size_t);
- struct binder_buffer_object *parent =
- binder_validate_ptr(target_proc, t->buffer,
- &ptr_object, fda->parent,
- off_start_offset,
- &parent_offset,
- num_valid);
- if (!parent) {
- binder_user_error("%d:%d got transaction with invalid parent offset or type\n",
- proc->pid, thread->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EINVAL;
- return_error_line = __LINE__;
- goto err_bad_parent;
- }
- if (!binder_validate_fixup(target_proc, t->buffer,
- off_start_offset,
- parent_offset,
- fda->parent_offset,
- last_fixup_obj_off,
- last_fixup_min_off)) {
- binder_user_error("%d:%d got transaction with out-of-order buffer fixup\n",
- proc->pid, thread->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EINVAL;
- return_error_line = __LINE__;
- goto err_bad_parent;
- }
- /*
- * We need to read the user version of the parent
- * object to get the original user offset
- */
- user_parent_size =
- binder_get_object(proc, user_buffer, t->buffer,
- parent_offset, &user_object);
- if (user_parent_size != sizeof(user_object.bbo)) {
- binder_user_error("%d:%d invalid ptr object size: %zd vs %zd\n",
- proc->pid, thread->pid,
- user_parent_size,
- sizeof(user_object.bbo));
- return_error = BR_FAILED_REPLY;
- return_error_param = -EINVAL;
- return_error_line = __LINE__;
- goto err_bad_parent;
- }
- ret = binder_translate_fd_array(&pf_head, fda,
- user_buffer, parent,
- &user_object.bbo, t,
- thread, in_reply_to);
- if (!ret)
- ret = binder_alloc_copy_to_buffer(&target_proc->alloc,
- t->buffer,
- object_offset,
- fda, sizeof(*fda));
- if (ret) {
- binder_txn_error("%d:%d translate fd array failed\n",
- thread->pid, proc->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = ret > 0 ? -EINVAL : ret;
- return_error_line = __LINE__;
- goto err_translate_failed;
- }
- last_fixup_obj_off = parent_offset;
- last_fixup_min_off =
- fda->parent_offset + sizeof(u32) * fda->num_fds;
- } break;
- case BINDER_TYPE_PTR: {
- struct binder_buffer_object *bp =
- to_binder_buffer_object(hdr);
- size_t buf_left = sg_buf_end_offset - sg_buf_offset;
- size_t num_valid;
-
- if (bp->length > buf_left) {
- binder_user_error("%d:%d got transaction with too large buffer\n",
- proc->pid, thread->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EINVAL;
- return_error_line = __LINE__;
- goto err_bad_offset;
- }
- ret = binder_defer_copy(&sgc_head, sg_buf_offset,
- (const void __user *)(uintptr_t)bp->buffer,
- bp->length);
- if (ret) {
- binder_txn_error("%d:%d deferred copy failed\n",
- thread->pid, proc->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = ret;
- return_error_line = __LINE__;
- goto err_translate_failed;
- }
- /* Fixup buffer pointer to target proc address space */
- bp->buffer = t->buffer->user_data + sg_buf_offset;
- sg_buf_offset += ALIGN(bp->length, sizeof(u64));
-
- num_valid = (buffer_offset - off_start_offset) /
- sizeof(binder_size_t);
- ret = binder_fixup_parent(&pf_head, t,
- thread, bp,
- off_start_offset,
- num_valid,
- last_fixup_obj_off,
- last_fixup_min_off);
- if (ret < 0 ||
- binder_alloc_copy_to_buffer(&target_proc->alloc,
- t->buffer,
- object_offset,
- bp, sizeof(*bp))) {
- binder_txn_error("%d:%d failed to fixup parent\n",
- thread->pid, proc->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = ret;
- return_error_line = __LINE__;
- goto err_translate_failed;
- }
- last_fixup_obj_off = object_offset;
- last_fixup_min_off = 0;
- } break;
- default:
- binder_user_error("%d:%d got transaction with invalid object type, %x\n",
- proc->pid, thread->pid, hdr->type);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EINVAL;
- return_error_line = __LINE__;
- goto err_bad_object_type;
- }
- }
- /* Done processing objects, copy the rest of the buffer */
- if (binder_alloc_copy_user_to_buffer(
- &target_proc->alloc,
- t->buffer, user_offset,
- user_buffer + user_offset,
- tr->data_size - user_offset)) {
- binder_user_error("%d:%d got transaction with invalid data ptr\n",
- proc->pid, thread->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = -EFAULT;
- return_error_line = __LINE__;
- goto err_copy_data_failed;
- }
-
- ret = binder_do_deferred_txn_copies(&target_proc->alloc, t->buffer,
- &sgc_head, &pf_head);
- if (ret) {
- binder_user_error("%d:%d got transaction with invalid offsets ptr\n",
- proc->pid, thread->pid);
- return_error = BR_FAILED_REPLY;
- return_error_param = ret;
- return_error_line = __LINE__;
- goto err_copy_data_failed;
- }
- if (t->buffer->oneway_spam_suspect) {
- tcomplete->type = BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT;
- binder_netlink_report(proc, t, tr->data_size,
- BR_ONEWAY_SPAM_SUSPECT);
- } else {
- tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
- }
-
- if (reply) {
- binder_enqueue_thread_work(thread, tcomplete);
- binder_inner_proc_lock(target_proc);
- if (target_thread->is_dead) {
- return_error = BR_DEAD_REPLY;
- binder_inner_proc_unlock(target_proc);
- goto err_dead_proc_or_thread;
- }
- BUG_ON(t->buffer->async_transaction != 0);
- binder_pop_transaction_ilocked(target_thread, in_reply_to);
- binder_enqueue_thread_work_ilocked(target_thread, &t->work);
- target_proc->outstanding_txns++;
- binder_inner_proc_unlock(target_proc);
- wake_up_interruptible_sync(&target_thread->wait);
- binder_free_transaction(in_reply_to);
- } else if (!(t->flags & TF_ONE_WAY)) {
- BUG_ON(t->buffer->async_transaction != 0);
- binder_inner_proc_lock(proc);
- /*
- * Defer the TRANSACTION_COMPLETE, so we don't return to
- * userspace immediately; this allows the target process to
- * immediately start processing this transaction, reducing
- * latency. We will then return the TRANSACTION_COMPLETE when
- * the target replies (or there is an error).
- */
- binder_enqueue_deferred_thread_work_ilocked(thread, tcomplete);
- t->from_parent = thread->transaction_stack;
- thread->transaction_stack = t;
- binder_inner_proc_unlock(proc);
- return_error = binder_proc_transaction(t,
- target_proc, target_thread);
- if (return_error) {
- binder_inner_proc_lock(proc);
- binder_pop_transaction_ilocked(thread, t);
- binder_inner_proc_unlock(proc);
- goto err_dead_proc_or_thread;
- }
- } else {
- /*
- * Make a transaction copy. It is not safe to access 't' after
- * binder_proc_transaction() reported a pending frozen. The
- * target could thaw and consume the transaction at any point.
- * Instead, use a safe 't_copy' for binder_netlink_report().
- */
- struct binder_transaction t_copy = *t;
-
- BUG_ON(target_node == NULL);
- BUG_ON(t->buffer->async_transaction != 1);
- return_error = binder_proc_transaction(t, target_proc, NULL);
- /*
- * Let the caller know when async transaction reaches a frozen
- * process and is put in a pending queue, waiting for the target
- * process to be unfrozen.
- */
- if (return_error == BR_TRANSACTION_PENDING_FROZEN) {
- tcomplete->type = BINDER_WORK_TRANSACTION_PENDING;
- binder_netlink_report(proc, &t_copy, tr->data_size,
- return_error);
- }
- binder_enqueue_thread_work(thread, tcomplete);
- if (return_error &&
- return_error != BR_TRANSACTION_PENDING_FROZEN)
- goto err_dead_proc_or_thread;
- }
- if (target_thread)
- binder_thread_dec_tmpref(target_thread);
- binder_proc_dec_tmpref(target_proc);
- if (target_node)
- binder_dec_node_tmpref(target_node);
- /*
- * write barrier to synchronize with initialization
- * of log entry
- */
- smp_wmb();
- WRITE_ONCE(e->debug_id_done, t_debug_id);
- return;
-
-err_dead_proc_or_thread:
- binder_txn_error("%d:%d %s process or thread\n",
- proc->pid, thread->pid,
- return_error == BR_FROZEN_REPLY ? "frozen" : "dead");
- return_error_line = __LINE__;
- binder_dequeue_work(proc, tcomplete);
-err_translate_failed:
-err_bad_object_type:
-err_bad_offset:
-err_bad_parent:
-err_copy_data_failed:
- binder_cleanup_deferred_txn_lists(&sgc_head, &pf_head);
- binder_free_txn_fixups(t);
- trace_binder_transaction_failed_buffer_release(t->buffer);
- binder_transaction_buffer_release(target_proc, NULL, t->buffer,
- buffer_offset, true);
- if (target_node)
- binder_dec_node_tmpref(target_node);
- target_node = NULL;
- t->buffer->transaction = NULL;
- binder_alloc_free_buf(&target_proc->alloc, t->buffer);
-err_binder_alloc_buf_failed:
-err_bad_extra_size:
- if (lsmctx.context)
- security_release_secctx(&lsmctx);
-err_get_secctx_failed:
- kfree(tcomplete);
- binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
-err_alloc_tcomplete_failed:
- if (trace_binder_txn_latency_free_enabled())
- binder_txn_latency_free(t);
-err_bad_todo_list:
-err_bad_call_stack:
-err_empty_call_stack:
-err_dead_binder:
-err_invalid_target_handle:
- if (target_node) {
- binder_dec_node(target_node, 1, 0);
- binder_dec_node_tmpref(target_node);
- }
-
- binder_netlink_report(proc, t, tr->data_size, return_error);
- kfree(t);
- binder_stats_deleted(BINDER_STAT_TRANSACTION);
-err_alloc_t_failed:
-
- binder_debug(BINDER_DEBUG_FAILED_TRANSACTION,
- "%d:%d transaction %s to %d:%d failed %d/%d/%d, code %u size %lld-%lld line %d\n",
- proc->pid, thread->pid, reply ? "reply" :
- (tr->flags & TF_ONE_WAY ? "async" : "call"),
- target_proc ? target_proc->pid : 0,
- target_thread ? target_thread->pid : 0,
- t_debug_id, return_error, return_error_param,
- tr->code, (u64)tr->data_size, (u64)tr->offsets_size,
- return_error_line);
-
- if (target_thread)
- binder_thread_dec_tmpref(target_thread);
- if (target_proc)
- binder_proc_dec_tmpref(target_proc);
-
- {
- struct binder_transaction_log_entry *fe;
-
- e->return_error = return_error;
- e->return_error_param = return_error_param;
- e->return_error_line = return_error_line;
- fe = binder_transaction_log_add(&binder_transaction_log_failed);
- *fe = *e;
- /*
- * write barrier to synchronize with initialization
- * of log entry
- */
- smp_wmb();
- WRITE_ONCE(e->debug_id_done, t_debug_id);
- WRITE_ONCE(fe->debug_id_done, t_debug_id);
- }
-
- BUG_ON(thread->return_error.cmd != BR_OK);
- if (in_reply_to) {
- binder_set_txn_from_error(in_reply_to, t_debug_id,
- return_error, return_error_param);
- thread->return_error.cmd = BR_TRANSACTION_COMPLETE;
- binder_enqueue_thread_work(thread, &thread->return_error.work);
- binder_send_failed_reply(in_reply_to, return_error);
- } else {
- binder_inner_proc_lock(proc);
- binder_set_extended_error(&thread->ee, t_debug_id,
- return_error, return_error_param);
- binder_inner_proc_unlock(proc);
- thread->return_error.cmd = return_error;
- binder_enqueue_thread_work(thread, &thread->return_error.work);
- }
-}
-
-static int
-binder_request_freeze_notification(struct binder_proc *proc,
- struct binder_thread *thread,
- struct binder_handle_cookie *handle_cookie)
-{
- struct binder_ref_freeze *freeze;
- struct binder_ref *ref;
-
- freeze = kzalloc_obj(*freeze);
- if (!freeze)
- return -ENOMEM;
- binder_proc_lock(proc);
- ref = binder_get_ref_olocked(proc, handle_cookie->handle, false);
- if (!ref) {
- binder_user_error("%d:%d BC_REQUEST_FREEZE_NOTIFICATION invalid ref %d\n",
- proc->pid, thread->pid, handle_cookie->handle);
- binder_proc_unlock(proc);
- kfree(freeze);
- return -EINVAL;
- }
-
- binder_node_lock(ref->node);
- if (ref->freeze) {
- binder_user_error("%d:%d BC_REQUEST_FREEZE_NOTIFICATION already set\n",
- proc->pid, thread->pid);
- binder_node_unlock(ref->node);
- binder_proc_unlock(proc);
- kfree(freeze);
- return -EINVAL;
- }
-
- binder_stats_created(BINDER_STAT_FREEZE);
- INIT_LIST_HEAD(&freeze->work.entry);
- freeze->cookie = handle_cookie->cookie;
- freeze->work.type = BINDER_WORK_FROZEN_BINDER;
- ref->freeze = freeze;
-
- if (ref->node->proc) {
- binder_inner_proc_lock(ref->node->proc);
- freeze->is_frozen = ref->node->proc->is_frozen;
- binder_inner_proc_unlock(ref->node->proc);
-
- binder_inner_proc_lock(proc);
- binder_enqueue_work_ilocked(&freeze->work, &proc->todo);
- binder_wakeup_proc_ilocked(proc);
- binder_inner_proc_unlock(proc);
- }
-
- binder_node_unlock(ref->node);
- binder_proc_unlock(proc);
- return 0;
-}
-
-static int
-binder_clear_freeze_notification(struct binder_proc *proc,
- struct binder_thread *thread,
- struct binder_handle_cookie *handle_cookie)
-{
- struct binder_ref_freeze *freeze;
- struct binder_ref *ref;
-
- binder_proc_lock(proc);
- ref = binder_get_ref_olocked(proc, handle_cookie->handle, false);
- if (!ref) {
- binder_user_error("%d:%d BC_CLEAR_FREEZE_NOTIFICATION invalid ref %d\n",
- proc->pid, thread->pid, handle_cookie->handle);
- binder_proc_unlock(proc);
- return -EINVAL;
- }
-
- binder_node_lock(ref->node);
-
- if (!ref->freeze) {
- binder_user_error("%d:%d BC_CLEAR_FREEZE_NOTIFICATION freeze notification not active\n",
- proc->pid, thread->pid);
- binder_node_unlock(ref->node);
- binder_proc_unlock(proc);
- return -EINVAL;
- }
- freeze = ref->freeze;
- binder_inner_proc_lock(proc);
- if (freeze->cookie != handle_cookie->cookie) {
- binder_user_error("%d:%d BC_CLEAR_FREEZE_NOTIFICATION freeze notification cookie mismatch %016llx != %016llx\n",
- proc->pid, thread->pid, (u64)freeze->cookie,
- (u64)handle_cookie->cookie);
- binder_inner_proc_unlock(proc);
- binder_node_unlock(ref->node);
- binder_proc_unlock(proc);
- return -EINVAL;
- }
- ref->freeze = NULL;
- /*
- * Take the existing freeze object and overwrite its work type. There are three cases here:
- * 1. No pending notification. In this case just add the work to the queue.
- * 2. A notification was sent and is pending an ack from userspace. Once an ack arrives, we
- * should resend with the new work type.
- * 3. A notification is pending to be sent. Since the work is already in the queue, nothing
- * needs to be done here.
- */
- freeze->work.type = BINDER_WORK_CLEAR_FREEZE_NOTIFICATION;
- if (list_empty(&freeze->work.entry)) {
- binder_enqueue_work_ilocked(&freeze->work, &proc->todo);
- binder_wakeup_proc_ilocked(proc);
- } else if (freeze->sent) {
- freeze->resend = true;
- }
- binder_inner_proc_unlock(proc);
- binder_node_unlock(ref->node);
- binder_proc_unlock(proc);
- return 0;
-}
-
-static int
-binder_freeze_notification_done(struct binder_proc *proc,
- struct binder_thread *thread,
- binder_uintptr_t cookie)
-{
- struct binder_ref_freeze *freeze = NULL;
- struct binder_work *w;
-
- binder_inner_proc_lock(proc);
- list_for_each_entry(w, &proc->delivered_freeze, entry) {
- struct binder_ref_freeze *tmp_freeze =
- container_of(w, struct binder_ref_freeze, work);
-
- if (tmp_freeze->cookie == cookie) {
- freeze = tmp_freeze;
- break;
- }
- }
- if (!freeze) {
- binder_user_error("%d:%d BC_FREEZE_NOTIFICATION_DONE %016llx not found\n",
- proc->pid, thread->pid, (u64)cookie);
- binder_inner_proc_unlock(proc);
- return -EINVAL;
- }
- binder_dequeue_work_ilocked(&freeze->work);
- freeze->sent = false;
- if (freeze->resend) {
- freeze->resend = false;
- binder_enqueue_work_ilocked(&freeze->work, &proc->todo);
- binder_wakeup_proc_ilocked(proc);
- }
- binder_inner_proc_unlock(proc);
- return 0;
-}
-
-/**
- * binder_free_buf() - free the specified buffer
- * @proc: binder proc that owns buffer
- * @thread: binder thread performing the buffer release
- * @buffer: buffer to be freed
- * @is_failure: failed to send transaction
- *
- * If the buffer is for an async transaction, enqueue the next async
- * transaction from the node.
- *
- * Cleanup the buffer and free it.
- */
-static void
-binder_free_buf(struct binder_proc *proc,
- struct binder_thread *thread,
- struct binder_buffer *buffer, bool is_failure)
-{
- binder_inner_proc_lock(proc);
- if (buffer->transaction) {
- buffer->transaction->buffer = NULL;
- buffer->transaction = NULL;
- }
- binder_inner_proc_unlock(proc);
- if (buffer->async_transaction && buffer->target_node) {
- struct binder_node *buf_node;
- struct binder_work *w;
-
- buf_node = buffer->target_node;
- binder_node_inner_lock(buf_node);
- BUG_ON(!buf_node->has_async_transaction);
- BUG_ON(buf_node->proc != proc);
- w = binder_dequeue_work_head_ilocked(
- &buf_node->async_todo);
- if (!w) {
- buf_node->has_async_transaction = false;
- } else {
- binder_enqueue_work_ilocked(
- w, &proc->todo);
- binder_wakeup_proc_ilocked(proc);
- }
- binder_node_inner_unlock(buf_node);
- }
- trace_binder_transaction_buffer_release(buffer);
- binder_release_entire_buffer(proc, thread, buffer, is_failure);
- binder_alloc_free_buf(&proc->alloc, buffer);
-}
-
-static int binder_thread_write(struct binder_proc *proc,
- struct binder_thread *thread,
- binder_uintptr_t binder_buffer, size_t size,
- binder_size_t *consumed)
-{
- uint32_t cmd;
- struct binder_context *context = proc->context;
- void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
- void __user *ptr = buffer + *consumed;
- void __user *end = buffer + size;
-
- while (ptr < end && thread->return_error.cmd == BR_OK) {
- int ret;
-
- if (get_user(cmd, (uint32_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(uint32_t);
- trace_binder_command(cmd);
- if (_IOC_NR(cmd) < ARRAY_SIZE(binder_stats.bc)) {
- atomic_inc(&binder_stats.bc[_IOC_NR(cmd)]);
- atomic_inc(&proc->stats.bc[_IOC_NR(cmd)]);
- atomic_inc(&thread->stats.bc[_IOC_NR(cmd)]);
- }
- switch (cmd) {
- case BC_INCREFS:
- case BC_ACQUIRE:
- case BC_RELEASE:
- case BC_DECREFS: {
- uint32_t target;
- const char *debug_string;
- bool strong = cmd == BC_ACQUIRE || cmd == BC_RELEASE;
- bool increment = cmd == BC_INCREFS || cmd == BC_ACQUIRE;
- struct binder_ref_data rdata;
-
- if (get_user(target, (uint32_t __user *)ptr))
- return -EFAULT;
-
- ptr += sizeof(uint32_t);
- ret = -1;
- if (increment && !target) {
- struct binder_node *ctx_mgr_node;
-
- mutex_lock(&context->context_mgr_node_lock);
- ctx_mgr_node = context->binder_context_mgr_node;
- if (ctx_mgr_node) {
- if (ctx_mgr_node->proc == proc) {
- binder_user_error("%d:%d context manager tried to acquire desc 0\n",
- proc->pid, thread->pid);
- mutex_unlock(&context->context_mgr_node_lock);
- return -EINVAL;
- }
- ret = binder_inc_ref_for_node(
- proc, ctx_mgr_node,
- strong, NULL, &rdata);
- }
- mutex_unlock(&context->context_mgr_node_lock);
- }
- if (ret)
- ret = binder_update_ref_for_handle(
- proc, target, increment, strong,
- &rdata);
- if (!ret && rdata.desc != target) {
- binder_user_error("%d:%d tried to acquire reference to desc %d, got %d instead\n",
- proc->pid, thread->pid,
- target, rdata.desc);
- }
- switch (cmd) {
- case BC_INCREFS:
- debug_string = "IncRefs";
- break;
- case BC_ACQUIRE:
- debug_string = "Acquire";
- break;
- case BC_RELEASE:
- debug_string = "Release";
- break;
- case BC_DECREFS:
- default:
- debug_string = "DecRefs";
- break;
- }
- if (ret) {
- binder_user_error("%d:%d %s %d refcount change on invalid ref %d ret %d\n",
- proc->pid, thread->pid, debug_string,
- strong, target, ret);
- break;
- }
- binder_debug(BINDER_DEBUG_USER_REFS,
- "%d:%d %s ref %d desc %d s %d w %d\n",
- proc->pid, thread->pid, debug_string,
- rdata.debug_id, rdata.desc, rdata.strong,
- rdata.weak);
- break;
- }
- case BC_INCREFS_DONE:
- case BC_ACQUIRE_DONE: {
- binder_uintptr_t node_ptr;
- binder_uintptr_t cookie;
- struct binder_node *node;
- bool free_node;
-
- if (get_user(node_ptr, (binder_uintptr_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(binder_uintptr_t);
- if (get_user(cookie, (binder_uintptr_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(binder_uintptr_t);
- node = binder_get_node(proc, node_ptr);
- if (node == NULL) {
- binder_user_error("%d:%d %s u%016llx no match\n",
- proc->pid, thread->pid,
- cmd == BC_INCREFS_DONE ?
- "BC_INCREFS_DONE" :
- "BC_ACQUIRE_DONE",
- (u64)node_ptr);
- break;
- }
- if (cookie != node->cookie) {
- binder_user_error("%d:%d %s u%016llx node %d cookie mismatch %016llx != %016llx\n",
- proc->pid, thread->pid,
- cmd == BC_INCREFS_DONE ?
- "BC_INCREFS_DONE" : "BC_ACQUIRE_DONE",
- (u64)node_ptr, node->debug_id,
- (u64)cookie, (u64)node->cookie);
- binder_put_node(node);
- break;
- }
- binder_node_inner_lock(node);
- if (cmd == BC_ACQUIRE_DONE) {
- if (node->pending_strong_ref == 0) {
- binder_user_error("%d:%d BC_ACQUIRE_DONE node %d has no pending acquire request\n",
- proc->pid, thread->pid,
- node->debug_id);
- binder_node_inner_unlock(node);
- binder_put_node(node);
- break;
- }
- node->pending_strong_ref = 0;
- } else {
- if (node->pending_weak_ref == 0) {
- binder_user_error("%d:%d BC_INCREFS_DONE node %d has no pending increfs request\n",
- proc->pid, thread->pid,
- node->debug_id);
- binder_node_inner_unlock(node);
- binder_put_node(node);
- break;
- }
- node->pending_weak_ref = 0;
- }
- free_node = binder_dec_node_nilocked(node,
- cmd == BC_ACQUIRE_DONE, 0);
- WARN_ON(free_node);
- binder_debug(BINDER_DEBUG_USER_REFS,
- "%d:%d %s node %d ls %d lw %d tr %d\n",
- proc->pid, thread->pid,
- cmd == BC_INCREFS_DONE ? "BC_INCREFS_DONE" : "BC_ACQUIRE_DONE",
- node->debug_id, node->local_strong_refs,
- node->local_weak_refs, node->tmp_refs);
- binder_node_inner_unlock(node);
- binder_put_node(node);
- break;
- }
- case BC_ATTEMPT_ACQUIRE:
- pr_err("BC_ATTEMPT_ACQUIRE not supported\n");
- return -EINVAL;
- case BC_ACQUIRE_RESULT:
- pr_err("BC_ACQUIRE_RESULT not supported\n");
- return -EINVAL;
-
- case BC_FREE_BUFFER: {
- binder_uintptr_t data_ptr;
- struct binder_buffer *buffer;
-
- if (get_user(data_ptr, (binder_uintptr_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(binder_uintptr_t);
-
- buffer = binder_alloc_prepare_to_free(&proc->alloc,
- data_ptr);
- if (IS_ERR_OR_NULL(buffer)) {
- if (PTR_ERR(buffer) == -EPERM) {
- binder_user_error(
- "%d:%d BC_FREE_BUFFER matched unreturned or currently freeing buffer at offset %lx\n",
- proc->pid, thread->pid,
- (unsigned long)data_ptr - proc->alloc.vm_start);
- } else {
- binder_user_error(
- "%d:%d BC_FREE_BUFFER no match for buffer at offset %lx\n",
- proc->pid, thread->pid,
- (unsigned long)data_ptr - proc->alloc.vm_start);
- }
- break;
- }
- binder_debug(BINDER_DEBUG_FREE_BUFFER,
- "%d:%d BC_FREE_BUFFER at offset %lx found buffer %d for %s transaction\n",
- proc->pid, thread->pid,
- (unsigned long)data_ptr - proc->alloc.vm_start,
- buffer->debug_id,
- buffer->transaction ? "active" : "finished");
- binder_free_buf(proc, thread, buffer, false);
- break;
- }
-
- case BC_TRANSACTION_SG:
- case BC_REPLY_SG: {
- struct binder_transaction_data_sg tr;
-
- if (copy_from_user(&tr, ptr, sizeof(tr)))
- return -EFAULT;
- ptr += sizeof(tr);
- binder_transaction(proc, thread, &tr.transaction_data,
- cmd == BC_REPLY_SG, tr.buffers_size);
- break;
- }
- case BC_TRANSACTION:
- case BC_REPLY: {
- struct binder_transaction_data tr;
-
- if (copy_from_user(&tr, ptr, sizeof(tr)))
- return -EFAULT;
- ptr += sizeof(tr);
- binder_transaction(proc, thread, &tr,
- cmd == BC_REPLY, 0);
- break;
- }
-
- case BC_REGISTER_LOOPER:
- binder_debug(BINDER_DEBUG_THREADS,
- "%d:%d BC_REGISTER_LOOPER\n",
- proc->pid, thread->pid);
- binder_inner_proc_lock(proc);
- if (thread->looper & BINDER_LOOPER_STATE_ENTERED) {
- thread->looper |= BINDER_LOOPER_STATE_INVALID;
- binder_user_error("%d:%d ERROR: BC_REGISTER_LOOPER called after BC_ENTER_LOOPER\n",
- proc->pid, thread->pid);
- } else if (proc->requested_threads == 0) {
- thread->looper |= BINDER_LOOPER_STATE_INVALID;
- binder_user_error("%d:%d ERROR: BC_REGISTER_LOOPER called without request\n",
- proc->pid, thread->pid);
- } else {
- proc->requested_threads--;
- proc->requested_threads_started++;
- }
- thread->looper |= BINDER_LOOPER_STATE_REGISTERED;
- binder_inner_proc_unlock(proc);
- break;
- case BC_ENTER_LOOPER:
- binder_debug(BINDER_DEBUG_THREADS,
- "%d:%d BC_ENTER_LOOPER\n",
- proc->pid, thread->pid);
- if (thread->looper & BINDER_LOOPER_STATE_REGISTERED) {
- thread->looper |= BINDER_LOOPER_STATE_INVALID;
- binder_user_error("%d:%d ERROR: BC_ENTER_LOOPER called after BC_REGISTER_LOOPER\n",
- proc->pid, thread->pid);
- }
- thread->looper |= BINDER_LOOPER_STATE_ENTERED;
- break;
- case BC_EXIT_LOOPER:
- binder_debug(BINDER_DEBUG_THREADS,
- "%d:%d BC_EXIT_LOOPER\n",
- proc->pid, thread->pid);
- thread->looper |= BINDER_LOOPER_STATE_EXITED;
- break;
-
- case BC_REQUEST_DEATH_NOTIFICATION:
- case BC_CLEAR_DEATH_NOTIFICATION: {
- uint32_t target;
- binder_uintptr_t cookie;
- struct binder_ref *ref;
- struct binder_ref_death *death = NULL;
-
- if (get_user(target, (uint32_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(uint32_t);
- if (get_user(cookie, (binder_uintptr_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(binder_uintptr_t);
- if (cmd == BC_REQUEST_DEATH_NOTIFICATION) {
- /*
- * Allocate memory for death notification
- * before taking lock
- */
- death = kzalloc_obj(*death);
- if (death == NULL) {
- WARN_ON(thread->return_error.cmd !=
- BR_OK);
- thread->return_error.cmd = BR_ERROR;
- binder_enqueue_thread_work(
- thread,
- &thread->return_error.work);
- binder_debug(
- BINDER_DEBUG_FAILED_TRANSACTION,
- "%d:%d BC_REQUEST_DEATH_NOTIFICATION failed\n",
- proc->pid, thread->pid);
- break;
- }
- }
- binder_proc_lock(proc);
- ref = binder_get_ref_olocked(proc, target, false);
- if (ref == NULL) {
- binder_user_error("%d:%d %s invalid ref %d\n",
- proc->pid, thread->pid,
- cmd == BC_REQUEST_DEATH_NOTIFICATION ?
- "BC_REQUEST_DEATH_NOTIFICATION" :
- "BC_CLEAR_DEATH_NOTIFICATION",
- target);
- binder_proc_unlock(proc);
- kfree(death);
- break;
- }
-
- binder_debug(BINDER_DEBUG_DEATH_NOTIFICATION,
- "%d:%d %s %016llx ref %d desc %d s %d w %d for node %d\n",
- proc->pid, thread->pid,
- cmd == BC_REQUEST_DEATH_NOTIFICATION ?
- "BC_REQUEST_DEATH_NOTIFICATION" :
- "BC_CLEAR_DEATH_NOTIFICATION",
- (u64)cookie, ref->data.debug_id,
- ref->data.desc, ref->data.strong,
- ref->data.weak, ref->node->debug_id);
-
- binder_node_lock(ref->node);
- if (cmd == BC_REQUEST_DEATH_NOTIFICATION) {
- if (ref->death) {
- binder_user_error("%d:%d BC_REQUEST_DEATH_NOTIFICATION death notification already set\n",
- proc->pid, thread->pid);
- binder_node_unlock(ref->node);
- binder_proc_unlock(proc);
- kfree(death);
- break;
- }
- binder_stats_created(BINDER_STAT_DEATH);
- INIT_LIST_HEAD(&death->work.entry);
- death->cookie = cookie;
- ref->death = death;
- if (ref->node->proc == NULL) {
- ref->death->work.type = BINDER_WORK_DEAD_BINDER;
-
- binder_inner_proc_lock(proc);
- binder_enqueue_work_ilocked(
- &ref->death->work, &proc->todo);
- binder_wakeup_proc_ilocked(proc);
- binder_inner_proc_unlock(proc);
- }
- } else {
- if (ref->death == NULL) {
- binder_user_error("%d:%d BC_CLEAR_DEATH_NOTIFICATION death notification not active\n",
- proc->pid, thread->pid);
- binder_node_unlock(ref->node);
- binder_proc_unlock(proc);
- break;
- }
- death = ref->death;
- if (death->cookie != cookie) {
- binder_user_error("%d:%d BC_CLEAR_DEATH_NOTIFICATION death notification cookie mismatch %016llx != %016llx\n",
- proc->pid, thread->pid,
- (u64)death->cookie,
- (u64)cookie);
- binder_node_unlock(ref->node);
- binder_proc_unlock(proc);
- break;
- }
- ref->death = NULL;
- binder_inner_proc_lock(proc);
- if (list_empty(&death->work.entry)) {
- death->work.type = BINDER_WORK_CLEAR_DEATH_NOTIFICATION;
- if (thread->looper &
- (BINDER_LOOPER_STATE_REGISTERED |
- BINDER_LOOPER_STATE_ENTERED))
- binder_enqueue_thread_work_ilocked(
- thread,
- &death->work);
- else {
- binder_enqueue_work_ilocked(
- &death->work,
- &proc->todo);
- binder_wakeup_proc_ilocked(
- proc);
- }
- } else {
- BUG_ON(death->work.type != BINDER_WORK_DEAD_BINDER);
- death->work.type = BINDER_WORK_DEAD_BINDER_AND_CLEAR;
- }
- binder_inner_proc_unlock(proc);
- }
- binder_node_unlock(ref->node);
- binder_proc_unlock(proc);
- } break;
- case BC_DEAD_BINDER_DONE: {
- struct binder_work *w;
- binder_uintptr_t cookie;
- struct binder_ref_death *death = NULL;
-
- if (get_user(cookie, (binder_uintptr_t __user *)ptr))
- return -EFAULT;
-
- ptr += sizeof(cookie);
- binder_inner_proc_lock(proc);
- list_for_each_entry(w, &proc->delivered_death,
- entry) {
- struct binder_ref_death *tmp_death =
- container_of(w,
- struct binder_ref_death,
- work);
-
- if (tmp_death->cookie == cookie) {
- death = tmp_death;
- break;
- }
- }
- binder_debug(BINDER_DEBUG_DEAD_BINDER,
- "%d:%d BC_DEAD_BINDER_DONE %016llx found %p\n",
- proc->pid, thread->pid, (u64)cookie,
- death);
- if (death == NULL) {
- binder_user_error("%d:%d BC_DEAD_BINDER_DONE %016llx not found\n",
- proc->pid, thread->pid, (u64)cookie);
- binder_inner_proc_unlock(proc);
- break;
- }
- binder_dequeue_work_ilocked(&death->work);
- if (death->work.type == BINDER_WORK_DEAD_BINDER_AND_CLEAR) {
- death->work.type = BINDER_WORK_CLEAR_DEATH_NOTIFICATION;
- if (thread->looper &
- (BINDER_LOOPER_STATE_REGISTERED |
- BINDER_LOOPER_STATE_ENTERED))
- binder_enqueue_thread_work_ilocked(
- thread, &death->work);
- else {
- binder_enqueue_work_ilocked(
- &death->work,
- &proc->todo);
- binder_wakeup_proc_ilocked(proc);
- }
- }
- binder_inner_proc_unlock(proc);
- } break;
-
- case BC_REQUEST_FREEZE_NOTIFICATION: {
- struct binder_handle_cookie handle_cookie;
- int error;
-
- if (copy_from_user(&handle_cookie, ptr, sizeof(handle_cookie)))
- return -EFAULT;
- ptr += sizeof(handle_cookie);
- error = binder_request_freeze_notification(proc, thread,
- &handle_cookie);
- if (error)
- return error;
- } break;
-
- case BC_CLEAR_FREEZE_NOTIFICATION: {
- struct binder_handle_cookie handle_cookie;
- int error;
-
- if (copy_from_user(&handle_cookie, ptr, sizeof(handle_cookie)))
- return -EFAULT;
- ptr += sizeof(handle_cookie);
- error = binder_clear_freeze_notification(proc, thread, &handle_cookie);
- if (error)
- return error;
- } break;
-
- case BC_FREEZE_NOTIFICATION_DONE: {
- binder_uintptr_t cookie;
- int error;
-
- if (get_user(cookie, (binder_uintptr_t __user *)ptr))
- return -EFAULT;
-
- ptr += sizeof(cookie);
- error = binder_freeze_notification_done(proc, thread, cookie);
- if (error)
- return error;
- } break;
-
- default:
- pr_err("%d:%d unknown command %u\n",
- proc->pid, thread->pid, cmd);
- return -EINVAL;
- }
- *consumed = ptr - buffer;
- }
- return 0;
-}
-
-static void binder_stat_br(struct binder_proc *proc,
- struct binder_thread *thread, uint32_t cmd)
-{
- trace_binder_return(cmd);
- if (_IOC_NR(cmd) < ARRAY_SIZE(binder_stats.br)) {
- atomic_inc(&binder_stats.br[_IOC_NR(cmd)]);
- atomic_inc(&proc->stats.br[_IOC_NR(cmd)]);
- atomic_inc(&thread->stats.br[_IOC_NR(cmd)]);
- }
-}
-
-static int binder_put_node_cmd(struct binder_proc *proc,
- struct binder_thread *thread,
- void __user **ptrp,
- binder_uintptr_t node_ptr,
- binder_uintptr_t node_cookie,
- int node_debug_id,
- uint32_t cmd, const char *cmd_name)
-{
- void __user *ptr = *ptrp;
-
- if (put_user(cmd, (uint32_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(uint32_t);
-
- if (put_user(node_ptr, (binder_uintptr_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(binder_uintptr_t);
-
- if (put_user(node_cookie, (binder_uintptr_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(binder_uintptr_t);
-
- binder_stat_br(proc, thread, cmd);
- binder_debug(BINDER_DEBUG_USER_REFS, "%d:%d %s %d u%016llx c%016llx\n",
- proc->pid, thread->pid, cmd_name, node_debug_id,
- (u64)node_ptr, (u64)node_cookie);
-
- *ptrp = ptr;
- return 0;
-}
-
-static int binder_wait_for_work(struct binder_thread *thread,
- bool do_proc_work)
-{
- DEFINE_WAIT(wait);
- struct binder_proc *proc = thread->proc;
- int ret = 0;
-
- binder_inner_proc_lock(proc);
- for (;;) {
- prepare_to_wait(&thread->wait, &wait, TASK_INTERRUPTIBLE|TASK_FREEZABLE);
- if (binder_has_work_ilocked(thread, do_proc_work))
- break;
- if (do_proc_work)
- list_add(&thread->waiting_thread_node,
- &proc->waiting_threads);
- binder_inner_proc_unlock(proc);
- schedule();
- binder_inner_proc_lock(proc);
- list_del_init(&thread->waiting_thread_node);
- if (signal_pending(current)) {
- ret = -EINTR;
- break;
- }
- }
- finish_wait(&thread->wait, &wait);
- binder_inner_proc_unlock(proc);
-
- return ret;
-}
-
-/**
- * binder_apply_fd_fixups() - finish fd translation
- * @proc: binder_proc associated @t->buffer
- * @t: binder transaction with list of fd fixups
- *
- * Now that we are in the context of the transaction target
- * process, we can allocate and install fds. Process the
- * list of fds to translate and fixup the buffer with the
- * new fds first and only then install the files.
- *
- * If we fail to allocate an fd, skip the install and release
- * any fds that have already been allocated.
- *
- * Return: 0 on success, a negative errno code on failure.
- */
-static int binder_apply_fd_fixups(struct binder_proc *proc,
- struct binder_transaction *t)
-{
- struct binder_txn_fd_fixup *fixup, *tmp;
- int ret = 0;
-
- list_for_each_entry(fixup, &t->fd_fixups, fixup_entry) {
- int fd = get_unused_fd_flags(O_CLOEXEC);
-
- if (fd < 0) {
- binder_debug(BINDER_DEBUG_TRANSACTION,
- "failed fd fixup txn %d fd %d\n",
- t->debug_id, fd);
- ret = -ENOMEM;
- goto err;
- }
- binder_debug(BINDER_DEBUG_TRANSACTION,
- "fd fixup txn %d fd %d\n",
- t->debug_id, fd);
- trace_binder_transaction_fd_recv(t, fd, fixup->offset);
- fixup->target_fd = fd;
- if (binder_alloc_copy_to_buffer(&proc->alloc, t->buffer,
- fixup->offset, &fd,
- sizeof(u32))) {
- ret = -EINVAL;
- goto err;
- }
- }
- list_for_each_entry_safe(fixup, tmp, &t->fd_fixups, fixup_entry) {
- fd_install(fixup->target_fd, fixup->file);
- list_del(&fixup->fixup_entry);
- kfree(fixup);
- }
-
- return ret;
-
-err:
- binder_free_txn_fixups(t);
- return ret;
-}
-
-static int binder_thread_read(struct binder_proc *proc,
- struct binder_thread *thread,
- binder_uintptr_t binder_buffer, size_t size,
- binder_size_t *consumed, int non_block)
-{
- void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
- void __user *ptr = buffer + *consumed;
- void __user *end = buffer + size;
-
- int ret = 0;
- int wait_for_proc_work;
-
- if (*consumed == 0) {
- if (put_user(BR_NOOP, (uint32_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(uint32_t);
- }
-
-retry:
- binder_inner_proc_lock(proc);
- wait_for_proc_work = binder_available_for_proc_work_ilocked(thread);
- binder_inner_proc_unlock(proc);
-
- thread->looper |= BINDER_LOOPER_STATE_WAITING;
-
- trace_binder_wait_for_work(wait_for_proc_work,
- !!thread->transaction_stack,
- !binder_worklist_empty(proc, &thread->todo));
- if (wait_for_proc_work) {
- if (!(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
- BINDER_LOOPER_STATE_ENTERED))) {
- binder_user_error("%d:%d ERROR: Thread waiting for process work before calling BC_REGISTER_LOOPER or BC_ENTER_LOOPER (state %x)\n",
- proc->pid, thread->pid, thread->looper);
- wait_event_interruptible(binder_user_error_wait,
- binder_stop_on_user_error < 2);
- }
- binder_set_nice(proc->default_priority);
- }
-
- if (non_block) {
- if (!binder_has_work(thread, wait_for_proc_work))
- ret = -EAGAIN;
- } else {
- ret = binder_wait_for_work(thread, wait_for_proc_work);
- }
-
- thread->looper &= ~BINDER_LOOPER_STATE_WAITING;
-
- if (ret)
- return ret;
-
- while (1) {
- uint32_t cmd;
- struct binder_transaction_data_secctx tr;
- struct binder_transaction_data *trd = &tr.transaction_data;
- struct binder_work *w = NULL;
- struct list_head *list = NULL;
- struct binder_transaction *t = NULL;
- struct binder_thread *t_from;
- size_t trsize = sizeof(*trd);
-
- binder_inner_proc_lock(proc);
- if (!binder_worklist_empty_ilocked(&thread->todo))
- list = &thread->todo;
- else if (!binder_worklist_empty_ilocked(&proc->todo) &&
- wait_for_proc_work)
- list = &proc->todo;
- else {
- binder_inner_proc_unlock(proc);
-
- /* no data added */
- if (ptr - buffer == 4 && !thread->looper_need_return)
- goto retry;
- break;
- }
-
- if (end - ptr < sizeof(tr) + 4) {
- binder_inner_proc_unlock(proc);
- break;
- }
- w = binder_dequeue_work_head_ilocked(list);
- if (binder_worklist_empty_ilocked(&thread->todo))
- thread->process_todo = false;
-
- switch (w->type) {
- case BINDER_WORK_TRANSACTION: {
- binder_inner_proc_unlock(proc);
- t = container_of(w, struct binder_transaction, work);
- } break;
- case BINDER_WORK_RETURN_ERROR: {
- struct binder_error *e = container_of(
- w, struct binder_error, work);
-
- WARN_ON(e->cmd == BR_OK);
- binder_inner_proc_unlock(proc);
- if (put_user(e->cmd, (uint32_t __user *)ptr))
- return -EFAULT;
- cmd = e->cmd;
- e->cmd = BR_OK;
- ptr += sizeof(uint32_t);
-
- binder_stat_br(proc, thread, cmd);
- } break;
- case BINDER_WORK_TRANSACTION_COMPLETE:
- case BINDER_WORK_TRANSACTION_PENDING:
- case BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT: {
- if (proc->oneway_spam_detection_enabled &&
- w->type == BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT)
- cmd = BR_ONEWAY_SPAM_SUSPECT;
- else if (w->type == BINDER_WORK_TRANSACTION_PENDING)
- cmd = BR_TRANSACTION_PENDING_FROZEN;
- else
- cmd = BR_TRANSACTION_COMPLETE;
- binder_inner_proc_unlock(proc);
- kfree(w);
- binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
- if (put_user(cmd, (uint32_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(uint32_t);
-
- binder_stat_br(proc, thread, cmd);
- binder_debug(BINDER_DEBUG_TRANSACTION_COMPLETE,
- "%d:%d BR_TRANSACTION_COMPLETE\n",
- proc->pid, thread->pid);
- } break;
- case BINDER_WORK_NODE: {
- struct binder_node *node = container_of(w, struct binder_node, work);
- int strong, weak;
- binder_uintptr_t node_ptr = node->ptr;
- binder_uintptr_t node_cookie = node->cookie;
- int node_debug_id = node->debug_id;
- int has_weak_ref;
- int has_strong_ref;
- void __user *orig_ptr = ptr;
-
- BUG_ON(proc != node->proc);
- strong = node->internal_strong_refs ||
- node->local_strong_refs;
- weak = !hlist_empty(&node->refs) ||
- node->local_weak_refs ||
- node->tmp_refs || strong;
- has_strong_ref = node->has_strong_ref;
- has_weak_ref = node->has_weak_ref;
-
- if (weak && !has_weak_ref) {
- node->has_weak_ref = 1;
- node->pending_weak_ref = 1;
- node->local_weak_refs++;
- }
- if (strong && !has_strong_ref) {
- node->has_strong_ref = 1;
- node->pending_strong_ref = 1;
- node->local_strong_refs++;
- }
- if (!strong && has_strong_ref)
- node->has_strong_ref = 0;
- if (!weak && has_weak_ref)
- node->has_weak_ref = 0;
- if (!weak && !strong) {
- binder_debug(BINDER_DEBUG_INTERNAL_REFS,
- "%d:%d node %d u%016llx c%016llx deleted\n",
- proc->pid, thread->pid,
- node_debug_id,
- (u64)node_ptr,
- (u64)node_cookie);
- rb_erase(&node->rb_node, &proc->nodes);
- binder_inner_proc_unlock(proc);
- binder_node_lock(node);
- /*
- * Acquire the node lock before freeing the
- * node to serialize with other threads that
- * may have been holding the node lock while
- * decrementing this node (avoids race where
- * this thread frees while the other thread
- * is unlocking the node after the final
- * decrement)
- */
- binder_node_unlock(node);
- binder_free_node(node);
- } else
- binder_inner_proc_unlock(proc);
-
- if (weak && !has_weak_ref)
- ret = binder_put_node_cmd(
- proc, thread, &ptr, node_ptr,
- node_cookie, node_debug_id,
- BR_INCREFS, "BR_INCREFS");
- if (!ret && strong && !has_strong_ref)
- ret = binder_put_node_cmd(
- proc, thread, &ptr, node_ptr,
- node_cookie, node_debug_id,
- BR_ACQUIRE, "BR_ACQUIRE");
- if (!ret && !strong && has_strong_ref)
- ret = binder_put_node_cmd(
- proc, thread, &ptr, node_ptr,
- node_cookie, node_debug_id,
- BR_RELEASE, "BR_RELEASE");
- if (!ret && !weak && has_weak_ref)
- ret = binder_put_node_cmd(
- proc, thread, &ptr, node_ptr,
- node_cookie, node_debug_id,
- BR_DECREFS, "BR_DECREFS");
- if (orig_ptr == ptr)
- binder_debug(BINDER_DEBUG_INTERNAL_REFS,
- "%d:%d node %d u%016llx c%016llx state unchanged\n",
- proc->pid, thread->pid,
- node_debug_id,
- (u64)node_ptr,
- (u64)node_cookie);
- if (ret)
- return ret;
- } break;
- case BINDER_WORK_DEAD_BINDER:
- case BINDER_WORK_DEAD_BINDER_AND_CLEAR:
- case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: {
- struct binder_ref_death *death;
- uint32_t cmd;
- binder_uintptr_t cookie;
-
- death = container_of(w, struct binder_ref_death, work);
- if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION)
- cmd = BR_CLEAR_DEATH_NOTIFICATION_DONE;
- else
- cmd = BR_DEAD_BINDER;
- cookie = death->cookie;
-
- binder_debug(BINDER_DEBUG_DEATH_NOTIFICATION,
- "%d:%d %s %016llx\n",
- proc->pid, thread->pid,
- cmd == BR_DEAD_BINDER ?
- "BR_DEAD_BINDER" :
- "BR_CLEAR_DEATH_NOTIFICATION_DONE",
- (u64)cookie);
- if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION) {
- binder_inner_proc_unlock(proc);
- kfree(death);
- binder_stats_deleted(BINDER_STAT_DEATH);
- } else {
- binder_enqueue_work_ilocked(
- w, &proc->delivered_death);
- binder_inner_proc_unlock(proc);
- }
- if (put_user(cmd, (uint32_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(uint32_t);
- if (put_user(cookie,
- (binder_uintptr_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(binder_uintptr_t);
- binder_stat_br(proc, thread, cmd);
- if (cmd == BR_DEAD_BINDER)
- goto done; /* DEAD_BINDER notifications can cause transactions */
- } break;
-
- case BINDER_WORK_FROZEN_BINDER: {
- struct binder_ref_freeze *freeze;
- struct binder_frozen_state_info info;
-
- memset(&info, 0, sizeof(info));
- freeze = container_of(w, struct binder_ref_freeze, work);
- info.is_frozen = freeze->is_frozen;
- info.cookie = freeze->cookie;
- freeze->sent = true;
- binder_enqueue_work_ilocked(w, &proc->delivered_freeze);
- binder_inner_proc_unlock(proc);
-
- if (put_user(BR_FROZEN_BINDER, (uint32_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(uint32_t);
- if (copy_to_user(ptr, &info, sizeof(info)))
- return -EFAULT;
- ptr += sizeof(info);
- binder_stat_br(proc, thread, BR_FROZEN_BINDER);
- goto done; /* BR_FROZEN_BINDER notifications can cause transactions */
- } break;
-
- case BINDER_WORK_CLEAR_FREEZE_NOTIFICATION: {
- struct binder_ref_freeze *freeze =
- container_of(w, struct binder_ref_freeze, work);
- binder_uintptr_t cookie = freeze->cookie;
-
- binder_inner_proc_unlock(proc);
- kfree(freeze);
- binder_stats_deleted(BINDER_STAT_FREEZE);
- if (put_user(BR_CLEAR_FREEZE_NOTIFICATION_DONE, (uint32_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(uint32_t);
- if (put_user(cookie, (binder_uintptr_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(binder_uintptr_t);
- binder_stat_br(proc, thread, BR_CLEAR_FREEZE_NOTIFICATION_DONE);
- } break;
-
- default:
- binder_inner_proc_unlock(proc);
- pr_err("%d:%d: bad work type %d\n",
- proc->pid, thread->pid, w->type);
- break;
- }
-
- if (!t)
- continue;
-
- BUG_ON(t->buffer == NULL);
- if (t->buffer->target_node) {
- struct binder_node *target_node = t->buffer->target_node;
-
- trd->target.ptr = target_node->ptr;
- trd->cookie = target_node->cookie;
- t->saved_priority = task_nice(current);
- if (t->priority < target_node->min_priority &&
- !(t->flags & TF_ONE_WAY))
- binder_set_nice(t->priority);
- else if (!(t->flags & TF_ONE_WAY) ||
- t->saved_priority > target_node->min_priority)
- binder_set_nice(target_node->min_priority);
- cmd = BR_TRANSACTION;
- } else {
- trd->target.ptr = 0;
- trd->cookie = 0;
- cmd = BR_REPLY;
- }
- trd->code = t->code;
- trd->flags = t->flags;
- trd->sender_euid = from_kuid(current_user_ns(), t->sender_euid);
-
- t_from = binder_get_txn_from(t);
- if (t_from) {
- struct task_struct *sender = t_from->proc->tsk;
-
- trd->sender_pid =
- task_tgid_nr_ns(sender,
- task_active_pid_ns(current));
- } else {
- trd->sender_pid = 0;
- }
-
- ret = binder_apply_fd_fixups(proc, t);
- if (ret) {
- struct binder_buffer *buffer = t->buffer;
- bool oneway = !!(t->flags & TF_ONE_WAY);
- int tid = t->debug_id;
-
- if (t_from)
- binder_thread_dec_tmpref(t_from);
- buffer->transaction = NULL;
- binder_cleanup_transaction(t, "fd fixups failed",
- BR_FAILED_REPLY);
- binder_free_buf(proc, thread, buffer, true);
- binder_debug(BINDER_DEBUG_FAILED_TRANSACTION,
- "%d:%d %stransaction %d fd fixups failed %d/%d, line %d\n",
- proc->pid, thread->pid,
- oneway ? "async " :
- (cmd == BR_REPLY ? "reply " : ""),
- tid, BR_FAILED_REPLY, ret, __LINE__);
- if (cmd == BR_REPLY) {
- cmd = BR_FAILED_REPLY;
- if (put_user(cmd, (uint32_t __user *)ptr))
- return -EFAULT;
- ptr += sizeof(uint32_t);
- binder_stat_br(proc, thread, cmd);
- break;
- }
- continue;
- }
- trd->data_size = t->buffer->data_size;
- trd->offsets_size = t->buffer->offsets_size;
- trd->data.ptr.buffer = t->buffer->user_data;
- trd->data.ptr.offsets = trd->data.ptr.buffer +
- ALIGN(t->buffer->data_size,
- sizeof(void *));
-
- tr.secctx = t->security_ctx;
- if (t->security_ctx) {
- cmd = BR_TRANSACTION_SEC_CTX;
- trsize = sizeof(tr);
- }
- if (put_user(cmd, (uint32_t __user *)ptr)) {
- if (t_from)
- binder_thread_dec_tmpref(t_from);
-
- binder_cleanup_transaction(t, "put_user failed",
- BR_FAILED_REPLY);
-
- return -EFAULT;
- }
- ptr += sizeof(uint32_t);
- if (copy_to_user(ptr, &tr, trsize)) {
- if (t_from)
- binder_thread_dec_tmpref(t_from);
-
- binder_cleanup_transaction(t, "copy_to_user failed",
- BR_FAILED_REPLY);
-
- return -EFAULT;
- }
- ptr += trsize;
-
- trace_binder_transaction_received(t);
- binder_stat_br(proc, thread, cmd);
- binder_debug(BINDER_DEBUG_TRANSACTION,
- "%d:%d %s %d %d:%d, cmd %u size %zd-%zd\n",
- proc->pid, thread->pid,
- (cmd == BR_TRANSACTION) ? "BR_TRANSACTION" :
- (cmd == BR_TRANSACTION_SEC_CTX) ?
- "BR_TRANSACTION_SEC_CTX" : "BR_REPLY",
- t->debug_id, t_from ? t_from->proc->pid : 0,
- t_from ? t_from->pid : 0, cmd,
- t->buffer->data_size, t->buffer->offsets_size);
-
- if (t_from)
- binder_thread_dec_tmpref(t_from);
- t->buffer->allow_user_free = 1;
- if (cmd != BR_REPLY && !(t->flags & TF_ONE_WAY)) {
- binder_inner_proc_lock(thread->proc);
- t->to_parent = thread->transaction_stack;
- t->to_thread = thread;
- thread->transaction_stack = t;
- binder_inner_proc_unlock(thread->proc);
- } else {
- binder_free_transaction(t);
- }
- break;
- }
-
-done:
-
- *consumed = ptr - buffer;
- binder_inner_proc_lock(proc);
- if (proc->requested_threads == 0 &&
- list_empty(&thread->proc->waiting_threads) &&
- proc->requested_threads_started < proc->max_threads &&
- (thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
- BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */
- /*spawn a new thread if we leave this out */) {
- proc->requested_threads++;
- binder_inner_proc_unlock(proc);
- binder_debug(BINDER_DEBUG_THREADS,
- "%d:%d BR_SPAWN_LOOPER\n",
- proc->pid, thread->pid);
- if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))
- return -EFAULT;
- binder_stat_br(proc, thread, BR_SPAWN_LOOPER);
- } else
- binder_inner_proc_unlock(proc);
- return 0;
-}
-
-static void binder_release_work(struct binder_proc *proc,
- struct list_head *list)
-{
- struct binder_work *w;
- enum binder_work_type wtype;
-
- while (1) {
- binder_inner_proc_lock(proc);
- w = binder_dequeue_work_head_ilocked(list);
- wtype = w ? w->type : 0;
- binder_inner_proc_unlock(proc);
- if (!w)
- return;
-
- switch (wtype) {
- case BINDER_WORK_TRANSACTION: {
- struct binder_transaction *t;
-
- t = container_of(w, struct binder_transaction, work);
-
- binder_cleanup_transaction(t, "process died.",
- BR_DEAD_REPLY);
- } break;
- case BINDER_WORK_RETURN_ERROR: {
- struct binder_error *e = container_of(
- w, struct binder_error, work);
-
- binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
- "undelivered TRANSACTION_ERROR: %u\n",
- e->cmd);
- } break;
- case BINDER_WORK_TRANSACTION_PENDING:
- case BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT:
- case BINDER_WORK_TRANSACTION_COMPLETE: {
- binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
- "undelivered TRANSACTION_COMPLETE\n");
- kfree(w);
- binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
- } break;
- case BINDER_WORK_DEAD_BINDER_AND_CLEAR:
- case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: {
- struct binder_ref_death *death;
-
- death = container_of(w, struct binder_ref_death, work);
- binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
- "undelivered death notification, %016llx\n",
- (u64)death->cookie);
- kfree(death);
- binder_stats_deleted(BINDER_STAT_DEATH);
- } break;
- case BINDER_WORK_NODE:
- break;
- case BINDER_WORK_CLEAR_FREEZE_NOTIFICATION: {
- struct binder_ref_freeze *freeze;
-
- freeze = container_of(w, struct binder_ref_freeze, work);
- binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
- "undelivered freeze notification, %016llx\n",
- (u64)freeze->cookie);
- kfree(freeze);
- binder_stats_deleted(BINDER_STAT_FREEZE);
- } break;
- default:
- pr_err("unexpected work type, %d, not freed\n",
- wtype);
- break;
- }
- }
-
-}
-
-static struct binder_thread *binder_get_thread_ilocked(
- struct binder_proc *proc, struct binder_thread *new_thread)
-{
- struct binder_thread *thread = NULL;
- struct rb_node *parent = NULL;
- struct rb_node **p = &proc->threads.rb_node;
-
- while (*p) {
- parent = *p;
- thread = rb_entry(parent, struct binder_thread, rb_node);
-
- if (current->pid < thread->pid)
- p = &(*p)->rb_left;
- else if (current->pid > thread->pid)
- p = &(*p)->rb_right;
- else
- return thread;
- }
- if (!new_thread)
- return NULL;
- thread = new_thread;
- binder_stats_created(BINDER_STAT_THREAD);
- thread->proc = proc;
- thread->pid = current->pid;
- atomic_set(&thread->tmp_ref, 0);
- init_waitqueue_head(&thread->wait);
- INIT_LIST_HEAD(&thread->todo);
- rb_link_node(&thread->rb_node, parent, p);
- rb_insert_color(&thread->rb_node, &proc->threads);
- thread->looper_need_return = true;
- thread->return_error.work.type = BINDER_WORK_RETURN_ERROR;
- thread->return_error.cmd = BR_OK;
- thread->reply_error.work.type = BINDER_WORK_RETURN_ERROR;
- thread->reply_error.cmd = BR_OK;
- thread->ee.command = BR_OK;
- INIT_LIST_HEAD(&new_thread->waiting_thread_node);
- return thread;
-}
-
-static struct binder_thread *binder_get_thread(struct binder_proc *proc)
-{
- struct binder_thread *thread;
- struct binder_thread *new_thread;
-
- binder_inner_proc_lock(proc);
- thread = binder_get_thread_ilocked(proc, NULL);
- binder_inner_proc_unlock(proc);
- if (!thread) {
- new_thread = kzalloc_obj(*thread);
- if (new_thread == NULL)
- return NULL;
- binder_inner_proc_lock(proc);
- thread = binder_get_thread_ilocked(proc, new_thread);
- binder_inner_proc_unlock(proc);
- if (thread != new_thread)
- kfree(new_thread);
- }
- return thread;
-}
-
-static void binder_free_proc(struct binder_proc *proc)
-{
- struct binder_device *device;
-
- BUG_ON(!list_empty(&proc->todo));
- BUG_ON(!list_empty(&proc->delivered_death));
- if (proc->outstanding_txns)
- pr_warn("%s: Unexpected outstanding_txns %d\n",
- __func__, proc->outstanding_txns);
- device = container_of(proc->context, struct binder_device, context);
- if (refcount_dec_and_test(&device->ref)) {
- binder_remove_device(device);
- kfree(proc->context->name);
- kfree(device);
- }
- binder_alloc_deferred_release(&proc->alloc);
- put_task_struct(proc->tsk);
- put_cred(proc->cred);
- binder_stats_deleted(BINDER_STAT_PROC);
- dbitmap_free(&proc->dmap);
- kfree(proc);
-}
-
-static void binder_free_thread(struct binder_thread *thread)
-{
- BUG_ON(!list_empty(&thread->todo));
- binder_stats_deleted(BINDER_STAT_THREAD);
- binder_proc_dec_tmpref(thread->proc);
- kfree(thread);
-}
-
-static int binder_thread_release(struct binder_proc *proc,
- struct binder_thread *thread)
-{
- struct binder_transaction *t;
- struct binder_transaction *send_reply = NULL;
- int active_transactions = 0;
- struct binder_transaction *last_t = NULL;
-
- binder_inner_proc_lock(thread->proc);
- /*
- * take a ref on the proc so it survives
- * after we remove this thread from proc->threads.
- * The corresponding dec is when we actually
- * free the thread in binder_free_thread()
- */
- proc->tmp_ref++;
- /*
- * take a ref on this thread to ensure it
- * survives while we are releasing it
- */
- atomic_inc(&thread->tmp_ref);
- rb_erase(&thread->rb_node, &proc->threads);
- t = thread->transaction_stack;
- if (t) {
- spin_lock(&t->lock);
- if (t->to_thread == thread)
- send_reply = t;
- } else {
- __acquire(&t->lock);
- }
- thread->is_dead = true;
-
- while (t) {
- last_t = t;
- active_transactions++;
- binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
- "release %d:%d transaction %d %s, still active\n",
- proc->pid, thread->pid,
- t->debug_id,
- (t->to_thread == thread) ? "in" : "out");
-
- if (t->to_thread == thread) {
- thread->proc->outstanding_txns--;
- t->to_proc = NULL;
- t->to_thread = NULL;
- if (t->buffer) {
- t->buffer->transaction = NULL;
- t->buffer = NULL;
- }
- t = t->to_parent;
- } else if (t->from == thread) {
- t->from = NULL;
- t = t->from_parent;
- } else
- BUG();
- spin_unlock(&last_t->lock);
- if (t)
- spin_lock(&t->lock);
- else
- __acquire(&t->lock);
- }
- /* annotation for sparse, lock not acquired in last iteration above */
- __release(&t->lock);
-
- /*
- * If this thread used poll, make sure we remove the waitqueue from any
- * poll data structures holding it.
- */
- if (thread->looper & BINDER_LOOPER_STATE_POLL)
- wake_up_pollfree(&thread->wait);
-
- binder_inner_proc_unlock(thread->proc);
-
- /*
- * This is needed to avoid races between wake_up_pollfree() above and
- * someone else removing the last entry from the queue for other reasons
- * (e.g. ep_remove_wait_queue() being called due to an epoll file
- * descriptor being closed). Such other users hold an RCU read lock, so
- * we can be sure they're done after we call synchronize_rcu().
- */
- if (thread->looper & BINDER_LOOPER_STATE_POLL)
- synchronize_rcu();
-
- if (send_reply)
- binder_send_failed_reply(send_reply, BR_DEAD_REPLY);
- binder_release_work(proc, &thread->todo);
- binder_thread_dec_tmpref(thread);
- return active_transactions;
-}
-
-static __poll_t binder_poll(struct file *filp,
- struct poll_table_struct *wait)
-{
- struct binder_proc *proc = filp->private_data;
- struct binder_thread *thread = NULL;
- bool wait_for_proc_work;
-
- thread = binder_get_thread(proc);
- if (!thread)
- return EPOLLERR;
-
- binder_inner_proc_lock(thread->proc);
- thread->looper |= BINDER_LOOPER_STATE_POLL;
- wait_for_proc_work = binder_available_for_proc_work_ilocked(thread);
-
- binder_inner_proc_unlock(thread->proc);
-
- poll_wait(filp, &thread->wait, wait);
-
- if (binder_has_work(thread, wait_for_proc_work))
- return EPOLLIN;
-
- return 0;
-}
-
-static int binder_ioctl_write_read(struct file *filp, unsigned long arg,
- struct binder_thread *thread)
-{
- int ret = 0;
- struct binder_proc *proc = filp->private_data;
- void __user *ubuf = (void __user *)arg;
- struct binder_write_read bwr;
-
- if (copy_from_user(&bwr, ubuf, sizeof(bwr)))
- return -EFAULT;
-
- binder_debug(BINDER_DEBUG_READ_WRITE,
- "%d:%d write %lld at %016llx, read %lld at %016llx\n",
- proc->pid, thread->pid,
- (u64)bwr.write_size, (u64)bwr.write_buffer,
- (u64)bwr.read_size, (u64)bwr.read_buffer);
-
- if (bwr.write_size > 0) {
- ret = binder_thread_write(proc, thread,
- bwr.write_buffer,
- bwr.write_size,
- &bwr.write_consumed);
- trace_binder_write_done(ret);
- if (ret < 0) {
- bwr.read_consumed = 0;
- goto out;
- }
- }
- if (bwr.read_size > 0) {
- ret = binder_thread_read(proc, thread, bwr.read_buffer,
- bwr.read_size,
- &bwr.read_consumed,
- filp->f_flags & O_NONBLOCK);
- trace_binder_read_done(ret);
- binder_inner_proc_lock(proc);
- if (!binder_worklist_empty_ilocked(&proc->todo))
- binder_wakeup_proc_ilocked(proc);
- binder_inner_proc_unlock(proc);
- if (ret < 0)
- goto out;
- }
- binder_debug(BINDER_DEBUG_READ_WRITE,
- "%d:%d wrote %lld of %lld, read return %lld of %lld\n",
- proc->pid, thread->pid,
- (u64)bwr.write_consumed, (u64)bwr.write_size,
- (u64)bwr.read_consumed, (u64)bwr.read_size);
-out:
- if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
- ret = -EFAULT;
- return ret;
-}
-
-static int binder_ioctl_set_ctx_mgr(struct file *filp,
- struct flat_binder_object *fbo)
-{
- int ret = 0;
- struct binder_proc *proc = filp->private_data;
- struct binder_context *context = proc->context;
- struct binder_node *new_node;
- kuid_t curr_euid = current_euid();
-
- guard(mutex)(&context->context_mgr_node_lock);
- if (context->binder_context_mgr_node) {
- pr_err("BINDER_SET_CONTEXT_MGR already set\n");
- return -EBUSY;
- }
- ret = security_binder_set_context_mgr(proc->cred);
- if (ret < 0)
- return ret;
- if (uid_valid(context->binder_context_mgr_uid)) {
- if (!uid_eq(context->binder_context_mgr_uid, curr_euid)) {
- pr_err("BINDER_SET_CONTEXT_MGR bad uid %d != %d\n",
- from_kuid(&init_user_ns, curr_euid),
- from_kuid(&init_user_ns,
- context->binder_context_mgr_uid));
- return -EPERM;
- }
- } else {
- context->binder_context_mgr_uid = curr_euid;
- }
- new_node = binder_new_node(proc, fbo);
- if (!new_node)
- return -ENOMEM;
- binder_node_lock(new_node);
- new_node->local_weak_refs++;
- new_node->local_strong_refs++;
- new_node->has_strong_ref = 1;
- new_node->has_weak_ref = 1;
- context->binder_context_mgr_node = new_node;
- binder_node_unlock(new_node);
- binder_put_node(new_node);
- return ret;
-}
-
-static int binder_ioctl_get_node_info_for_ref(struct binder_proc *proc,
- struct binder_node_info_for_ref *info)
-{
- struct binder_node *node;
- struct binder_context *context = proc->context;
- __u32 handle = info->handle;
-
- if (info->strong_count || info->weak_count || info->reserved1 ||
- info->reserved2 || info->reserved3) {
- binder_user_error("%d BINDER_GET_NODE_INFO_FOR_REF: only handle may be non-zero.",
- proc->pid);
- return -EINVAL;
- }
-
- /* This ioctl may only be used by the context manager */
- mutex_lock(&context->context_mgr_node_lock);
- if (!context->binder_context_mgr_node ||
- context->binder_context_mgr_node->proc != proc) {
- mutex_unlock(&context->context_mgr_node_lock);
- return -EPERM;
- }
- mutex_unlock(&context->context_mgr_node_lock);
-
- node = binder_get_node_from_ref(proc, handle, true, NULL);
- if (!node)
- return -EINVAL;
-
- info->strong_count = node->local_strong_refs +
- node->internal_strong_refs;
- info->weak_count = node->local_weak_refs;
-
- binder_put_node(node);
-
- return 0;
-}
-
-static int binder_ioctl_get_node_debug_info(struct binder_proc *proc,
- struct binder_node_debug_info *info)
-{
- struct rb_node *n;
- binder_uintptr_t ptr = info->ptr;
-
- memset(info, 0, sizeof(*info));
-
- binder_inner_proc_lock(proc);
- for (n = rb_first(&proc->nodes); n != NULL; n = rb_next(n)) {
- struct binder_node *node = rb_entry(n, struct binder_node,
- rb_node);
- if (node->ptr > ptr) {
- info->ptr = node->ptr;
- info->cookie = node->cookie;
- info->has_strong_ref = node->has_strong_ref;
- info->has_weak_ref = node->has_weak_ref;
- break;
- }
- }
- binder_inner_proc_unlock(proc);
-
- return 0;
-}
-
-static bool binder_txns_pending_ilocked(struct binder_proc *proc)
-{
- struct rb_node *n;
- struct binder_thread *thread;
-
- if (proc->outstanding_txns > 0)
- return true;
-
- for (n = rb_first(&proc->threads); n; n = rb_next(n)) {
- thread = rb_entry(n, struct binder_thread, rb_node);
- if (thread->transaction_stack)
- return true;
- }
- return false;
-}
-
-static void binder_add_freeze_work(struct binder_proc *proc, bool is_frozen)
-{
- struct binder_node *prev = NULL;
- struct rb_node *n;
- struct binder_ref *ref;
-
- binder_inner_proc_lock(proc);
- for (n = rb_first(&proc->nodes); n; n = rb_next(n)) {
- struct binder_node *node;
-
- node = rb_entry(n, struct binder_node, rb_node);
- binder_inc_node_tmpref_ilocked(node);
- binder_inner_proc_unlock(proc);
- if (prev)
- binder_put_node(prev);
- binder_node_lock(node);
- hlist_for_each_entry(ref, &node->refs, node_entry) {
- /*
- * Need the node lock to synchronize
- * with new notification requests and the
- * inner lock to synchronize with queued
- * freeze notifications.
- */
- binder_inner_proc_lock(ref->proc);
- if (!ref->freeze) {
- binder_inner_proc_unlock(ref->proc);
- continue;
- }
- ref->freeze->work.type = BINDER_WORK_FROZEN_BINDER;
- if (list_empty(&ref->freeze->work.entry)) {
- ref->freeze->is_frozen = is_frozen;
- binder_enqueue_work_ilocked(&ref->freeze->work, &ref->proc->todo);
- binder_wakeup_proc_ilocked(ref->proc);
- } else {
- if (ref->freeze->sent && ref->freeze->is_frozen != is_frozen)
- ref->freeze->resend = true;
- ref->freeze->is_frozen = is_frozen;
- }
- binder_inner_proc_unlock(ref->proc);
- }
- prev = node;
- binder_node_unlock(node);
- binder_inner_proc_lock(proc);
- if (proc->is_dead)
- break;
- }
- binder_inner_proc_unlock(proc);
- if (prev)
- binder_put_node(prev);
-}
-
-static int binder_ioctl_freeze(struct binder_freeze_info *info,
- struct binder_proc *target_proc)
-{
- int ret = 0;
-
- if (!info->enable) {
- binder_inner_proc_lock(target_proc);
- target_proc->sync_recv = false;
- target_proc->async_recv = false;
- target_proc->is_frozen = false;
- binder_inner_proc_unlock(target_proc);
- binder_add_freeze_work(target_proc, false);
- return 0;
- }
-
- /*
- * Freezing the target. Prevent new transactions by
- * setting frozen state. If timeout specified, wait
- * for transactions to drain.
- */
- binder_inner_proc_lock(target_proc);
- target_proc->sync_recv = false;
- target_proc->async_recv = false;
- target_proc->is_frozen = true;
- binder_inner_proc_unlock(target_proc);
-
- if (info->timeout_ms > 0)
- ret = wait_event_interruptible_timeout(
- target_proc->freeze_wait,
- (!target_proc->outstanding_txns),
- msecs_to_jiffies(info->timeout_ms));
-
- /* Check pending transactions that wait for reply */
- if (ret >= 0) {
- binder_inner_proc_lock(target_proc);
- if (binder_txns_pending_ilocked(target_proc))
- ret = -EAGAIN;
- binder_inner_proc_unlock(target_proc);
- }
-
- if (ret < 0) {
- binder_inner_proc_lock(target_proc);
- target_proc->is_frozen = false;
- binder_inner_proc_unlock(target_proc);
- } else {
- binder_add_freeze_work(target_proc, true);
- }
-
- return ret;
-}
-
-static int binder_ioctl_get_freezer_info(
- struct binder_frozen_status_info *info)
-{
- struct binder_proc *target_proc;
- bool found = false;
- __u32 txns_pending;
-
- info->sync_recv = 0;
- info->async_recv = 0;
-
- mutex_lock(&binder_procs_lock);
- hlist_for_each_entry(target_proc, &binder_procs, proc_node) {
- if (target_proc->pid == info->pid) {
- found = true;
- binder_inner_proc_lock(target_proc);
- txns_pending = binder_txns_pending_ilocked(target_proc);
- info->sync_recv |= target_proc->sync_recv |
- (txns_pending << 1);
- info->async_recv |= target_proc->async_recv;
- binder_inner_proc_unlock(target_proc);
- }
- }
- mutex_unlock(&binder_procs_lock);
-
- if (!found)
- return -EINVAL;
-
- return 0;
-}
-
-static int binder_ioctl_get_extended_error(struct binder_thread *thread,
- void __user *ubuf)
-{
- struct binder_extended_error ee;
-
- binder_inner_proc_lock(thread->proc);
- ee = thread->ee;
- binder_set_extended_error(&thread->ee, 0, BR_OK, 0);
- binder_inner_proc_unlock(thread->proc);
-
- if (copy_to_user(ubuf, &ee, sizeof(ee)))
- return -EFAULT;
-
- return 0;
-}
-
-static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
-{
- int ret;
- struct binder_proc *proc = filp->private_data;
- struct binder_thread *thread;
- void __user *ubuf = (void __user *)arg;
-
- trace_binder_ioctl(cmd, arg);
-
- ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
- if (ret)
- goto err_unlocked;
-
- thread = binder_get_thread(proc);
- if (thread == NULL) {
- ret = -ENOMEM;
- goto err;
- }
-
- switch (cmd) {
- case BINDER_WRITE_READ:
- ret = binder_ioctl_write_read(filp, arg, thread);
- if (ret)
- goto err;
- break;
- case BINDER_SET_MAX_THREADS: {
- u32 max_threads;
-
- if (copy_from_user(&max_threads, ubuf,
- sizeof(max_threads))) {
- ret = -EINVAL;
- goto err;
- }
- binder_inner_proc_lock(proc);
- proc->max_threads = max_threads;
- binder_inner_proc_unlock(proc);
- break;
- }
- case BINDER_SET_CONTEXT_MGR_EXT: {
- struct flat_binder_object fbo;
-
- if (copy_from_user(&fbo, ubuf, sizeof(fbo))) {
- ret = -EINVAL;
- goto err;
- }
- ret = binder_ioctl_set_ctx_mgr(filp, &fbo);
- if (ret)
- goto err;
- break;
- }
- case BINDER_SET_CONTEXT_MGR:
- ret = binder_ioctl_set_ctx_mgr(filp, NULL);
- if (ret)
- goto err;
- break;
- case BINDER_THREAD_EXIT:
- binder_debug(BINDER_DEBUG_THREADS, "%d:%d exit\n",
- proc->pid, thread->pid);
- binder_thread_release(proc, thread);
- thread = NULL;
- break;
- case BINDER_VERSION: {
- struct binder_version __user *ver = ubuf;
-
- if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
- &ver->protocol_version)) {
- ret = -EINVAL;
- goto err;
- }
- break;
- }
- case BINDER_GET_NODE_INFO_FOR_REF: {
- struct binder_node_info_for_ref info;
-
- if (copy_from_user(&info, ubuf, sizeof(info))) {
- ret = -EFAULT;
- goto err;
- }
-
- ret = binder_ioctl_get_node_info_for_ref(proc, &info);
- if (ret < 0)
- goto err;
-
- if (copy_to_user(ubuf, &info, sizeof(info))) {
- ret = -EFAULT;
- goto err;
- }
-
- break;
- }
- case BINDER_GET_NODE_DEBUG_INFO: {
- struct binder_node_debug_info info;
-
- if (copy_from_user(&info, ubuf, sizeof(info))) {
- ret = -EFAULT;
- goto err;
- }
-
- ret = binder_ioctl_get_node_debug_info(proc, &info);
- if (ret < 0)
- goto err;
-
- if (copy_to_user(ubuf, &info, sizeof(info))) {
- ret = -EFAULT;
- goto err;
- }
- break;
- }
- case BINDER_FREEZE: {
- struct binder_freeze_info info;
- struct binder_proc **target_procs = NULL, *target_proc;
- int target_procs_count = 0, i = 0;
-
- ret = 0;
-
- if (copy_from_user(&info, ubuf, sizeof(info))) {
- ret = -EFAULT;
- goto err;
- }
-
- mutex_lock(&binder_procs_lock);
- hlist_for_each_entry(target_proc, &binder_procs, proc_node) {
- if (target_proc->pid == info.pid)
- target_procs_count++;
- }
-
- if (target_procs_count == 0) {
- mutex_unlock(&binder_procs_lock);
- ret = -EINVAL;
- goto err;
- }
-
- target_procs = kzalloc_objs(struct binder_proc *,
- target_procs_count);
-
- if (!target_procs) {
- mutex_unlock(&binder_procs_lock);
- ret = -ENOMEM;
- goto err;
- }
-
- hlist_for_each_entry(target_proc, &binder_procs, proc_node) {
- if (target_proc->pid != info.pid)
- continue;
-
- binder_inner_proc_lock(target_proc);
- target_proc->tmp_ref++;
- binder_inner_proc_unlock(target_proc);
-
- target_procs[i++] = target_proc;
- }
- mutex_unlock(&binder_procs_lock);
-
- for (i = 0; i < target_procs_count; i++) {
- if (ret >= 0)
- ret = binder_ioctl_freeze(&info,
- target_procs[i]);
-
- binder_proc_dec_tmpref(target_procs[i]);
- }
-
- kfree(target_procs);
-
- if (ret < 0)
- goto err;
- break;
- }
- case BINDER_GET_FROZEN_INFO: {
- struct binder_frozen_status_info info;
-
- if (copy_from_user(&info, ubuf, sizeof(info))) {
- ret = -EFAULT;
- goto err;
- }
-
- ret = binder_ioctl_get_freezer_info(&info);
- if (ret < 0)
- goto err;
-
- if (copy_to_user(ubuf, &info, sizeof(info))) {
- ret = -EFAULT;
- goto err;
- }
- break;
- }
- case BINDER_ENABLE_ONEWAY_SPAM_DETECTION: {
- uint32_t enable;
-
- if (copy_from_user(&enable, ubuf, sizeof(enable))) {
- ret = -EFAULT;
- goto err;
- }
- binder_inner_proc_lock(proc);
- proc->oneway_spam_detection_enabled = (bool)enable;
- binder_inner_proc_unlock(proc);
- break;
- }
- case BINDER_GET_EXTENDED_ERROR:
- ret = binder_ioctl_get_extended_error(thread, ubuf);
- if (ret < 0)
- goto err;
- break;
- default:
- ret = -EINVAL;
- goto err;
- }
- ret = 0;
-err:
- if (thread)
- thread->looper_need_return = false;
- wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
- if (ret && ret != -EINTR)
- pr_info("%d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret);
-err_unlocked:
- trace_binder_ioctl_done(ret);
- return ret;
-}
-
-static void binder_vma_open(struct vm_area_struct *vma)
-{
- struct binder_proc *proc = vma->vm_private_data;
-
- binder_debug(BINDER_DEBUG_OPEN_CLOSE,
- "%d open vm area %lx-%lx (%ld K) vma %lx pagep %lx\n",
- proc->pid, vma->vm_start, vma->vm_end,
- (vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags,
- (unsigned long)pgprot_val(vma->vm_page_prot));
-}
-
-static void binder_vma_close(struct vm_area_struct *vma)
-{
- struct binder_proc *proc = vma->vm_private_data;
-
- binder_debug(BINDER_DEBUG_OPEN_CLOSE,
- "%d close vm area %lx-%lx (%ld K) vma %lx pagep %lx\n",
- proc->pid, vma->vm_start, vma->vm_end,
- (vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags,
- (unsigned long)pgprot_val(vma->vm_page_prot));
- binder_alloc_vma_close(&proc->alloc);
-}
-
-VISIBLE_IF_KUNIT vm_fault_t binder_vm_fault(struct vm_fault *vmf)
-{
- return VM_FAULT_SIGBUS;
-}
-EXPORT_SYMBOL_IF_KUNIT(binder_vm_fault);
-
-static const struct vm_operations_struct binder_vm_ops = {
- .open = binder_vma_open,
- .close = binder_vma_close,
- .fault = binder_vm_fault,
-};
-
-static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
-{
- struct binder_proc *proc = filp->private_data;
-
- if (!same_thread_group(proc->tsk, current))
- return -EINVAL;
-
- binder_debug(BINDER_DEBUG_OPEN_CLOSE,
- "%s: %d %lx-%lx (%ld K) vma %lx pagep %lx\n",
- __func__, proc->pid, vma->vm_start, vma->vm_end,
- (vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags,
- (unsigned long)pgprot_val(vma->vm_page_prot));
-
- if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
- pr_err("%s: %d %lx-%lx %s failed %d\n", __func__,
- proc->pid, vma->vm_start, vma->vm_end, "bad vm_flags", -EPERM);
- return -EPERM;
- }
- vm_flags_mod(vma, VM_DONTCOPY | VM_MIXEDMAP, VM_MAYWRITE);
-
- vma->vm_ops = &binder_vm_ops;
- vma->vm_private_data = proc;
-
- return binder_alloc_mmap_handler(&proc->alloc, vma);
-}
-
-static int binder_open(struct inode *nodp, struct file *filp)
-{
- struct binder_proc *proc, *itr;
- struct binder_device *binder_dev;
- struct binderfs_info *info;
- struct dentry *binder_binderfs_dir_entry_proc = NULL;
- bool existing_pid = false;
-
- binder_debug(BINDER_DEBUG_OPEN_CLOSE, "%s: %d:%d\n", __func__,
- current->tgid, current->pid);
-
- proc = kzalloc_obj(*proc);
- if (proc == NULL)
- return -ENOMEM;
-
- dbitmap_init(&proc->dmap);
- spin_lock_init(&proc->inner_lock);
- spin_lock_init(&proc->outer_lock);
- proc->tsk = get_task_struct(current->group_leader);
- proc->pid = current->tgid;
- proc->cred = get_cred(filp->f_cred);
- INIT_LIST_HEAD(&proc->todo);
- init_waitqueue_head(&proc->freeze_wait);
- proc->default_priority = task_nice(current);
- /* binderfs stashes devices in i_private */
- if (is_binderfs_device(nodp)) {
- binder_dev = nodp->i_private;
- info = nodp->i_sb->s_fs_info;
- binder_binderfs_dir_entry_proc = info->proc_log_dir;
- } else {
- binder_dev = container_of(filp->private_data,
- struct binder_device, miscdev);
- }
- refcount_inc(&binder_dev->ref);
- proc->context = &binder_dev->context;
- binder_alloc_init(&proc->alloc);
-
- binder_stats_created(BINDER_STAT_PROC);
- INIT_LIST_HEAD(&proc->delivered_death);
- INIT_LIST_HEAD(&proc->delivered_freeze);
- INIT_LIST_HEAD(&proc->waiting_threads);
- filp->private_data = proc;
-
- mutex_lock(&binder_procs_lock);
- hlist_for_each_entry(itr, &binder_procs, proc_node) {
- if (itr->pid == proc->pid) {
- existing_pid = true;
- break;
- }
- }
- hlist_add_head(&proc->proc_node, &binder_procs);
- mutex_unlock(&binder_procs_lock);
-
- if (binder_debugfs_dir_entry_proc && !existing_pid) {
- char strbuf[11];
-
- snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
- /*
- * proc debug entries are shared between contexts.
- * Only create for the first PID to avoid debugfs log spamming
- * The printing code will anyway print all contexts for a given
- * PID so this is not a problem.
- */
- proc->debugfs_entry = debugfs_create_file(strbuf, 0444,
- binder_debugfs_dir_entry_proc,
- (void *)(unsigned long)proc->pid,
- &proc_fops);
- }
-
- if (binder_binderfs_dir_entry_proc && !existing_pid) {
- char strbuf[11];
- struct dentry *binderfs_entry;
-
- snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
- /*
- * Similar to debugfs, the process specific log file is shared
- * between contexts. Only create for the first PID.
- * This is ok since same as debugfs, the log file will contain
- * information on all contexts of a given PID.
- */
- binderfs_entry = binderfs_create_file(binder_binderfs_dir_entry_proc,
- strbuf, &proc_fops, (void *)(unsigned long)proc->pid);
- if (!IS_ERR(binderfs_entry)) {
- proc->binderfs_entry = binderfs_entry;
- } else {
- int error;
-
- error = PTR_ERR(binderfs_entry);
- pr_warn("Unable to create file %s in binderfs (error %d)\n",
- strbuf, error);
- }
- }
-
- return 0;
-}
-
-static int binder_flush(struct file *filp, fl_owner_t id)
-{
- struct binder_proc *proc = filp->private_data;
-
- binder_defer_work(proc, BINDER_DEFERRED_FLUSH);
-
- return 0;
-}
-
-static void binder_deferred_flush(struct binder_proc *proc)
-{
- struct rb_node *n;
- int wake_count = 0;
-
- binder_inner_proc_lock(proc);
- for (n = rb_first(&proc->threads); n != NULL; n = rb_next(n)) {
- struct binder_thread *thread = rb_entry(n, struct binder_thread, rb_node);
-
- thread->looper_need_return = true;
- if (thread->looper & BINDER_LOOPER_STATE_WAITING) {
- wake_up_interruptible(&thread->wait);
- wake_count++;
- }
- }
- binder_inner_proc_unlock(proc);
-
- binder_debug(BINDER_DEBUG_OPEN_CLOSE,
- "binder_flush: %d woke %d threads\n", proc->pid,
- wake_count);
-}
-
-static int binder_release(struct inode *nodp, struct file *filp)
-{
- struct binder_proc *proc = filp->private_data;
-
- debugfs_remove(proc->debugfs_entry);
-
- if (proc->binderfs_entry) {
- simple_recursive_removal(proc->binderfs_entry, NULL);
- proc->binderfs_entry = NULL;
- }
-
- binder_defer_work(proc, BINDER_DEFERRED_RELEASE);
-
- return 0;
-}
-
-static int binder_node_release(struct binder_node *node, int refs)
-{
- struct binder_ref *ref;
- int death = 0;
- struct binder_proc *proc = node->proc;
-
- binder_release_work(proc, &node->async_todo);
-
- binder_node_lock(node);
- binder_inner_proc_lock(proc);
- binder_dequeue_work_ilocked(&node->work);
- /*
- * The caller must have taken a temporary ref on the node,
- */
- BUG_ON(!node->tmp_refs);
- if (hlist_empty(&node->refs) && node->tmp_refs == 1) {
- binder_inner_proc_unlock(proc);
- binder_node_unlock(node);
- binder_free_node(node);
-
- return refs;
- }
-
- node->proc = NULL;
- node->local_strong_refs = 0;
- node->local_weak_refs = 0;
- binder_inner_proc_unlock(proc);
-
- spin_lock(&binder_dead_nodes_lock);
- hlist_add_head(&node->dead_node, &binder_dead_nodes);
- spin_unlock(&binder_dead_nodes_lock);
-
- hlist_for_each_entry(ref, &node->refs, node_entry) {
- refs++;
- /*
- * Need the node lock to synchronize
- * with new notification requests and the
- * inner lock to synchronize with queued
- * death notifications.
- */
- binder_inner_proc_lock(ref->proc);
- if (!ref->death) {
- binder_inner_proc_unlock(ref->proc);
- continue;
- }
-
- death++;
-
- BUG_ON(!list_empty(&ref->death->work.entry));
- ref->death->work.type = BINDER_WORK_DEAD_BINDER;
- binder_enqueue_work_ilocked(&ref->death->work,
- &ref->proc->todo);
- binder_wakeup_proc_ilocked(ref->proc);
- binder_inner_proc_unlock(ref->proc);
- }
-
- binder_debug(BINDER_DEBUG_DEAD_BINDER,
- "node %d now dead, refs %d, death %d\n",
- node->debug_id, refs, death);
- binder_node_unlock(node);
- binder_put_node(node);
-
- return refs;
-}
-
-static void binder_deferred_release(struct binder_proc *proc)
-{
- struct binder_context *context = proc->context;
- struct rb_node *n;
- int threads, nodes, incoming_refs, outgoing_refs, active_transactions;
-
- mutex_lock(&binder_procs_lock);
- hlist_del(&proc->proc_node);
- mutex_unlock(&binder_procs_lock);
-
- mutex_lock(&context->context_mgr_node_lock);
- if (context->binder_context_mgr_node &&
- context->binder_context_mgr_node->proc == proc) {
- binder_debug(BINDER_DEBUG_DEAD_BINDER,
- "%s: %d context_mgr_node gone\n",
- __func__, proc->pid);
- context->binder_context_mgr_node = NULL;
- }
- mutex_unlock(&context->context_mgr_node_lock);
- binder_inner_proc_lock(proc);
- /*
- * Make sure proc stays alive after we
- * remove all the threads
- */
- proc->tmp_ref++;
-
- proc->is_dead = true;
- proc->is_frozen = false;
- proc->sync_recv = false;
- proc->async_recv = false;
- threads = 0;
- active_transactions = 0;
- while ((n = rb_first(&proc->threads))) {
- struct binder_thread *thread;
-
- thread = rb_entry(n, struct binder_thread, rb_node);
- binder_inner_proc_unlock(proc);
- threads++;
- active_transactions += binder_thread_release(proc, thread);
- binder_inner_proc_lock(proc);
- }
-
- nodes = 0;
- incoming_refs = 0;
- while ((n = rb_first(&proc->nodes))) {
- struct binder_node *node;
-
- node = rb_entry(n, struct binder_node, rb_node);
- nodes++;
- /*
- * take a temporary ref on the node before
- * calling binder_node_release() which will either
- * kfree() the node or call binder_put_node()
- */
- binder_inc_node_tmpref_ilocked(node);
- rb_erase(&node->rb_node, &proc->nodes);
- binder_inner_proc_unlock(proc);
- incoming_refs = binder_node_release(node, incoming_refs);
- binder_inner_proc_lock(proc);
- }
- binder_inner_proc_unlock(proc);
-
- outgoing_refs = 0;
- binder_proc_lock(proc);
- while ((n = rb_first(&proc->refs_by_desc))) {
- struct binder_ref *ref;
-
- ref = rb_entry(n, struct binder_ref, rb_node_desc);
- outgoing_refs++;
- binder_cleanup_ref_olocked(ref);
- binder_proc_unlock(proc);
- binder_free_ref(ref);
- binder_proc_lock(proc);
- }
- binder_proc_unlock(proc);
-
- binder_release_work(proc, &proc->todo);
- binder_release_work(proc, &proc->delivered_death);
- binder_release_work(proc, &proc->delivered_freeze);
-
- binder_debug(BINDER_DEBUG_OPEN_CLOSE,
- "%s: %d threads %d, nodes %d (ref %d), refs %d, active transactions %d\n",
- __func__, proc->pid, threads, nodes, incoming_refs,
- outgoing_refs, active_transactions);
-
- binder_proc_dec_tmpref(proc);
-}
-
-static void binder_deferred_func(struct work_struct *work)
-{
- struct binder_proc *proc;
-
- int defer;
-
- do {
- mutex_lock(&binder_deferred_lock);
- if (!hlist_empty(&binder_deferred_list)) {
- proc = hlist_entry(binder_deferred_list.first,
- struct binder_proc, deferred_work_node);
- hlist_del_init(&proc->deferred_work_node);
- defer = proc->deferred_work;
- proc->deferred_work = 0;
- } else {
- proc = NULL;
- defer = 0;
- }
- mutex_unlock(&binder_deferred_lock);
-
- if (defer & BINDER_DEFERRED_FLUSH)
- binder_deferred_flush(proc);
-
- if (defer & BINDER_DEFERRED_RELEASE)
- binder_deferred_release(proc); /* frees proc */
- } while (proc);
-}
-static DECLARE_WORK(binder_deferred_work, binder_deferred_func);
-
-static void
-binder_defer_work(struct binder_proc *proc, enum binder_deferred_state defer)
-{
- guard(mutex)(&binder_deferred_lock);
- proc->deferred_work |= defer;
- if (hlist_unhashed(&proc->deferred_work_node)) {
- hlist_add_head(&proc->deferred_work_node,
- &binder_deferred_list);
- schedule_work(&binder_deferred_work);
- }
-}
-
-static void print_binder_transaction_ilocked(struct seq_file *m,
- struct binder_proc *proc,
- const char *prefix,
- struct binder_transaction *t)
-{
- struct binder_proc *to_proc;
- struct binder_buffer *buffer = t->buffer;
- ktime_t current_time = ktime_get();
-
- spin_lock(&t->lock);
- to_proc = t->to_proc;
- seq_printf(m,
- "%s %d: %pK from %d:%d to %d:%d code %x flags %x pri %ld a%d r%d elapsed %lldms",
- prefix, t->debug_id, t,
- t->from_pid,
- t->from_tid,
- to_proc ? to_proc->pid : 0,
- t->to_thread ? t->to_thread->pid : 0,
- t->code, t->flags, t->priority, t->is_async, t->is_reply,
- ktime_ms_delta(current_time, t->start_time));
- spin_unlock(&t->lock);
-
- if (proc != to_proc) {
- /*
- * Can only safely deref buffer if we are holding the
- * correct proc inner lock for this node
- */
- seq_puts(m, "\n");
- return;
- }
-
- if (buffer == NULL) {
- seq_puts(m, " buffer free\n");
- return;
- }
- if (buffer->target_node)
- seq_printf(m, " node %d", buffer->target_node->debug_id);
- seq_printf(m, " size %zd:%zd offset %lx\n",
- buffer->data_size, buffer->offsets_size,
- buffer->user_data - proc->alloc.vm_start);
-}
-
-static void print_binder_work_ilocked(struct seq_file *m,
- struct binder_proc *proc,
- const char *prefix,
- const char *transaction_prefix,
- struct binder_work *w, bool hash_ptrs)
-{
- struct binder_node *node;
- struct binder_transaction *t;
-
- switch (w->type) {
- case BINDER_WORK_TRANSACTION:
- t = container_of(w, struct binder_transaction, work);
- print_binder_transaction_ilocked(
- m, proc, transaction_prefix, t);
- break;
- case BINDER_WORK_RETURN_ERROR: {
- struct binder_error *e = container_of(
- w, struct binder_error, work);
-
- seq_printf(m, "%stransaction error: %u\n",
- prefix, e->cmd);
- } break;
- case BINDER_WORK_TRANSACTION_COMPLETE:
- seq_printf(m, "%stransaction complete\n", prefix);
- break;
- case BINDER_WORK_NODE:
- node = container_of(w, struct binder_node, work);
- if (hash_ptrs)
- seq_printf(m, "%snode work %d: u%p c%p\n",
- prefix, node->debug_id,
- (void *)(long)node->ptr,
- (void *)(long)node->cookie);
- else
- seq_printf(m, "%snode work %d: u%016llx c%016llx\n",
- prefix, node->debug_id,
- (u64)node->ptr, (u64)node->cookie);
- break;
- case BINDER_WORK_DEAD_BINDER:
- seq_printf(m, "%shas dead binder\n", prefix);
- break;
- case BINDER_WORK_DEAD_BINDER_AND_CLEAR:
- seq_printf(m, "%shas cleared dead binder\n", prefix);
- break;
- case BINDER_WORK_CLEAR_DEATH_NOTIFICATION:
- seq_printf(m, "%shas cleared death notification\n", prefix);
- break;
- case BINDER_WORK_FROZEN_BINDER:
- seq_printf(m, "%shas frozen binder\n", prefix);
- break;
- case BINDER_WORK_CLEAR_FREEZE_NOTIFICATION:
- seq_printf(m, "%shas cleared freeze notification\n", prefix);
- break;
- default:
- seq_printf(m, "%sunknown work: type %d\n", prefix, w->type);
- break;
- }
-}
-
-static void print_binder_thread_ilocked(struct seq_file *m,
- struct binder_thread *thread,
- bool print_always, bool hash_ptrs)
-{
- struct binder_transaction *t;
- struct binder_work *w;
- size_t start_pos = m->count;
- size_t header_pos;
-
- seq_printf(m, " thread %d: l %02x need_return %d tr %d\n",
- thread->pid, thread->looper,
- thread->looper_need_return,
- atomic_read(&thread->tmp_ref));
- header_pos = m->count;
- t = thread->transaction_stack;
- while (t) {
- if (t->from == thread) {
- print_binder_transaction_ilocked(m, thread->proc,
- " outgoing transaction", t);
- t = t->from_parent;
- } else if (t->to_thread == thread) {
- print_binder_transaction_ilocked(m, thread->proc,
- " incoming transaction", t);
- t = t->to_parent;
- } else {
- print_binder_transaction_ilocked(m, thread->proc,
- " bad transaction", t);
- t = NULL;
- }
- }
- list_for_each_entry(w, &thread->todo, entry) {
- print_binder_work_ilocked(m, thread->proc, " ",
- " pending transaction",
- w, hash_ptrs);
- }
- if (!print_always && m->count == header_pos)
- m->count = start_pos;
-}
-
-static void print_binder_node_nilocked(struct seq_file *m,
- struct binder_node *node,
- bool hash_ptrs)
-{
- struct binder_ref *ref;
- struct binder_work *w;
- int count;
-
- count = hlist_count_nodes(&node->refs);
-
- if (hash_ptrs)
- seq_printf(m, " node %d: u%p c%p", node->debug_id,
- (void *)(long)node->ptr, (void *)(long)node->cookie);
- else
- seq_printf(m, " node %d: u%016llx c%016llx", node->debug_id,
- (u64)node->ptr, (u64)node->cookie);
- seq_printf(m, " hs %d hw %d ls %d lw %d is %d iw %d tr %d",
- node->has_strong_ref, node->has_weak_ref,
- node->local_strong_refs, node->local_weak_refs,
- node->internal_strong_refs, count, node->tmp_refs);
- if (count) {
- seq_puts(m, " proc");
- hlist_for_each_entry(ref, &node->refs, node_entry)
- seq_printf(m, " %d", ref->proc->pid);
- }
- seq_puts(m, "\n");
- if (node->proc) {
- list_for_each_entry(w, &node->async_todo, entry)
- print_binder_work_ilocked(m, node->proc, " ",
- " pending async transaction",
- w, hash_ptrs);
- }
-}
-
-static void print_binder_ref_olocked(struct seq_file *m,
- struct binder_ref *ref)
-{
- binder_node_lock(ref->node);
- seq_printf(m, " ref %d: desc %d %snode %d s %d w %d d %pK\n",
- ref->data.debug_id, ref->data.desc,
- ref->node->proc ? "" : "dead ",
- ref->node->debug_id, ref->data.strong,
- ref->data.weak, ref->death);
- binder_node_unlock(ref->node);
-}
-
-/**
- * print_next_binder_node_ilocked() - Print binder_node from a locked list
- * @m: struct seq_file for output via seq_printf()
- * @proc: struct binder_proc we hold the inner_proc_lock to (if any)
- * @node: struct binder_node to print fields of
- * @prev_node: struct binder_node we hold a temporary reference to (if any)
- * @hash_ptrs: whether to hash @node's binder_uintptr_t fields
- *
- * Helper function to handle synchronization around printing a struct
- * binder_node while iterating through @proc->nodes or the dead nodes list.
- * Caller must hold either @proc->inner_lock (for live nodes) or
- * binder_dead_nodes_lock. This lock will be released during the body of this
- * function, but it will be reacquired before returning to the caller.
- *
- * Return: pointer to the struct binder_node we hold a tmpref on
- */
-static struct binder_node *
-print_next_binder_node_ilocked(struct seq_file *m, struct binder_proc *proc,
- struct binder_node *node,
- struct binder_node *prev_node, bool hash_ptrs)
-{
- /*
- * Take a temporary reference on the node so that isn't freed while
- * we print it.
- */
- binder_inc_node_tmpref_ilocked(node);
- /*
- * Live nodes need to drop the inner proc lock and dead nodes need to
- * drop the binder_dead_nodes_lock before trying to take the node lock.
- */
- if (proc)
- binder_inner_proc_unlock(proc);
- else
- spin_unlock(&binder_dead_nodes_lock);
- if (prev_node)
- binder_put_node(prev_node);
- binder_node_inner_lock(node);
- print_binder_node_nilocked(m, node, hash_ptrs);
- binder_node_inner_unlock(node);
- if (proc)
- binder_inner_proc_lock(proc);
- else
- spin_lock(&binder_dead_nodes_lock);
- return node;
-}
-
-static void print_binder_proc(struct seq_file *m, struct binder_proc *proc,
- bool print_all, bool hash_ptrs)
-{
- struct binder_work *w;
- struct rb_node *n;
- size_t start_pos = m->count;
- size_t header_pos;
- struct binder_node *last_node = NULL;
-
- seq_printf(m, "proc %d\n", proc->pid);
- seq_printf(m, "context %s\n", proc->context->name);
- header_pos = m->count;
-
- binder_inner_proc_lock(proc);
- for (n = rb_first(&proc->threads); n; n = rb_next(n))
- print_binder_thread_ilocked(m, rb_entry(n, struct binder_thread,
- rb_node), print_all, hash_ptrs);
-
- for (n = rb_first(&proc->nodes); n; n = rb_next(n)) {
- struct binder_node *node = rb_entry(n, struct binder_node,
- rb_node);
- if (!print_all && !node->has_async_transaction)
- continue;
-
- last_node = print_next_binder_node_ilocked(m, proc, node,
- last_node,
- hash_ptrs);
- }
- binder_inner_proc_unlock(proc);
- if (last_node)
- binder_put_node(last_node);
-
- if (print_all) {
- binder_proc_lock(proc);
- for (n = rb_first(&proc->refs_by_desc); n; n = rb_next(n))
- print_binder_ref_olocked(m, rb_entry(n,
- struct binder_ref,
- rb_node_desc));
- binder_proc_unlock(proc);
- }
- binder_alloc_print_allocated(m, &proc->alloc);
- binder_inner_proc_lock(proc);
- list_for_each_entry(w, &proc->todo, entry)
- print_binder_work_ilocked(m, proc, " ",
- " pending transaction", w,
- hash_ptrs);
- list_for_each_entry(w, &proc->delivered_death, entry) {
- seq_puts(m, " has delivered dead binder\n");
- break;
- }
- list_for_each_entry(w, &proc->delivered_freeze, entry) {
- seq_puts(m, " has delivered freeze binder\n");
- break;
- }
- binder_inner_proc_unlock(proc);
- if (!print_all && m->count == header_pos)
- m->count = start_pos;
-}
-
-static const char * const binder_return_strings[] = {
- "BR_ERROR",
- "BR_OK",
- "BR_TRANSACTION",
- "BR_REPLY",
- "BR_ACQUIRE_RESULT",
- "BR_DEAD_REPLY",
- "BR_TRANSACTION_COMPLETE",
- "BR_INCREFS",
- "BR_ACQUIRE",
- "BR_RELEASE",
- "BR_DECREFS",
- "BR_ATTEMPT_ACQUIRE",
- "BR_NOOP",
- "BR_SPAWN_LOOPER",
- "BR_FINISHED",
- "BR_DEAD_BINDER",
- "BR_CLEAR_DEATH_NOTIFICATION_DONE",
- "BR_FAILED_REPLY",
- "BR_FROZEN_REPLY",
- "BR_ONEWAY_SPAM_SUSPECT",
- "BR_TRANSACTION_PENDING_FROZEN",
- "BR_FROZEN_BINDER",
- "BR_CLEAR_FREEZE_NOTIFICATION_DONE",
-};
-
-static const char * const binder_command_strings[] = {
- "BC_TRANSACTION",
- "BC_REPLY",
- "BC_ACQUIRE_RESULT",
- "BC_FREE_BUFFER",
- "BC_INCREFS",
- "BC_ACQUIRE",
- "BC_RELEASE",
- "BC_DECREFS",
- "BC_INCREFS_DONE",
- "BC_ACQUIRE_DONE",
- "BC_ATTEMPT_ACQUIRE",
- "BC_REGISTER_LOOPER",
- "BC_ENTER_LOOPER",
- "BC_EXIT_LOOPER",
- "BC_REQUEST_DEATH_NOTIFICATION",
- "BC_CLEAR_DEATH_NOTIFICATION",
- "BC_DEAD_BINDER_DONE",
- "BC_TRANSACTION_SG",
- "BC_REPLY_SG",
- "BC_REQUEST_FREEZE_NOTIFICATION",
- "BC_CLEAR_FREEZE_NOTIFICATION",
- "BC_FREEZE_NOTIFICATION_DONE",
-};
-
-static const char * const binder_objstat_strings[] = {
- "proc",
- "thread",
- "node",
- "ref",
- "death",
- "transaction",
- "transaction_complete",
- "freeze",
-};
-
-static void print_binder_stats(struct seq_file *m, const char *prefix,
- struct binder_stats *stats)
-{
- int i;
-
- BUILD_BUG_ON(ARRAY_SIZE(stats->bc) !=
- ARRAY_SIZE(binder_command_strings));
- for (i = 0; i < ARRAY_SIZE(stats->bc); i++) {
- int temp = atomic_read(&stats->bc[i]);
-
- if (temp)
- seq_printf(m, "%s%s: %d\n", prefix,
- binder_command_strings[i], temp);
- }
-
- BUILD_BUG_ON(ARRAY_SIZE(stats->br) !=
- ARRAY_SIZE(binder_return_strings));
- for (i = 0; i < ARRAY_SIZE(stats->br); i++) {
- int temp = atomic_read(&stats->br[i]);
-
- if (temp)
- seq_printf(m, "%s%s: %d\n", prefix,
- binder_return_strings[i], temp);
- }
-
- BUILD_BUG_ON(ARRAY_SIZE(stats->obj_created) !=
- ARRAY_SIZE(binder_objstat_strings));
- BUILD_BUG_ON(ARRAY_SIZE(stats->obj_created) !=
- ARRAY_SIZE(stats->obj_deleted));
- for (i = 0; i < ARRAY_SIZE(stats->obj_created); i++) {
- int created = atomic_read(&stats->obj_created[i]);
- int deleted = atomic_read(&stats->obj_deleted[i]);
-
- if (created || deleted)
- seq_printf(m, "%s%s: active %d total %d\n",
- prefix,
- binder_objstat_strings[i],
- created - deleted,
- created);
- }
-}
-
-static void print_binder_proc_stats(struct seq_file *m,
- struct binder_proc *proc)
-{
- struct binder_work *w;
- struct binder_thread *thread;
- struct rb_node *n;
- int count, strong, weak, ready_threads;
- size_t free_async_space =
- binder_alloc_get_free_async_space(&proc->alloc);
-
- seq_printf(m, "proc %d\n", proc->pid);
- seq_printf(m, "context %s\n", proc->context->name);
- count = 0;
- ready_threads = 0;
- binder_inner_proc_lock(proc);
- for (n = rb_first(&proc->threads); n; n = rb_next(n))
- count++;
-
- list_for_each_entry(thread, &proc->waiting_threads, waiting_thread_node)
- ready_threads++;
-
- seq_printf(m, " threads: %d\n", count);
- seq_printf(m, " requested threads: %d+%d/%d\n"
- " ready threads %d\n"
- " free async space %zd\n", proc->requested_threads,
- proc->requested_threads_started, proc->max_threads,
- ready_threads,
- free_async_space);
- count = 0;
- for (n = rb_first(&proc->nodes); n; n = rb_next(n))
- count++;
- binder_inner_proc_unlock(proc);
- seq_printf(m, " nodes: %d\n", count);
- count = 0;
- strong = 0;
- weak = 0;
- binder_proc_lock(proc);
- for (n = rb_first(&proc->refs_by_desc); n; n = rb_next(n)) {
- struct binder_ref *ref = rb_entry(n, struct binder_ref,
- rb_node_desc);
- count++;
- strong += ref->data.strong;
- weak += ref->data.weak;
- }
- binder_proc_unlock(proc);
- seq_printf(m, " refs: %d s %d w %d\n", count, strong, weak);
-
- count = binder_alloc_get_allocated_count(&proc->alloc);
- seq_printf(m, " buffers: %d\n", count);
-
- binder_alloc_print_pages(m, &proc->alloc);
-
- count = 0;
- binder_inner_proc_lock(proc);
- list_for_each_entry(w, &proc->todo, entry) {
- if (w->type == BINDER_WORK_TRANSACTION)
- count++;
- }
- binder_inner_proc_unlock(proc);
- seq_printf(m, " pending transactions: %d\n", count);
-
- print_binder_stats(m, " ", &proc->stats);
-}
-
-static void print_binder_state(struct seq_file *m, bool hash_ptrs)
-{
- struct binder_proc *proc;
- struct binder_node *node;
- struct binder_node *last_node = NULL;
-
- seq_puts(m, "binder state:\n");
-
- spin_lock(&binder_dead_nodes_lock);
- if (!hlist_empty(&binder_dead_nodes))
- seq_puts(m, "dead nodes:\n");
- hlist_for_each_entry(node, &binder_dead_nodes, dead_node)
- last_node = print_next_binder_node_ilocked(m, NULL, node,
- last_node,
- hash_ptrs);
- spin_unlock(&binder_dead_nodes_lock);
- if (last_node)
- binder_put_node(last_node);
-
- mutex_lock(&binder_procs_lock);
- hlist_for_each_entry(proc, &binder_procs, proc_node)
- print_binder_proc(m, proc, true, hash_ptrs);
- mutex_unlock(&binder_procs_lock);
-}
-
-static void print_binder_transactions(struct seq_file *m, bool hash_ptrs)
-{
- struct binder_proc *proc;
-
- seq_puts(m, "binder transactions:\n");
- mutex_lock(&binder_procs_lock);
- hlist_for_each_entry(proc, &binder_procs, proc_node)
- print_binder_proc(m, proc, false, hash_ptrs);
- mutex_unlock(&binder_procs_lock);
-}
-
-static int state_show(struct seq_file *m, void *unused)
-{
- print_binder_state(m, false);
- return 0;
-}
-
-static int state_hashed_show(struct seq_file *m, void *unused)
-{
- print_binder_state(m, true);
- return 0;
-}
-
-static int stats_show(struct seq_file *m, void *unused)
-{
- struct binder_proc *proc;
-
- seq_puts(m, "binder stats:\n");
-
- print_binder_stats(m, "", &binder_stats);
-
- mutex_lock(&binder_procs_lock);
- hlist_for_each_entry(proc, &binder_procs, proc_node)
- print_binder_proc_stats(m, proc);
- mutex_unlock(&binder_procs_lock);
-
- return 0;
-}
-
-static int transactions_show(struct seq_file *m, void *unused)
-{
- print_binder_transactions(m, false);
- return 0;
-}
-
-static int transactions_hashed_show(struct seq_file *m, void *unused)
-{
- print_binder_transactions(m, true);
- return 0;
-}
-
-static int proc_show(struct seq_file *m, void *unused)
-{
- struct binder_proc *itr;
- int pid = (unsigned long)m->private;
-
- guard(mutex)(&binder_procs_lock);
- hlist_for_each_entry(itr, &binder_procs, proc_node) {
- if (itr->pid == pid) {
- seq_puts(m, "binder proc state:\n");
- print_binder_proc(m, itr, true, false);
- }
- }
-
- return 0;
-}
-
-static void print_binder_transaction_log_entry(struct seq_file *m,
- struct binder_transaction_log_entry *e)
-{
- int debug_id = READ_ONCE(e->debug_id_done);
- /*
- * read barrier to guarantee debug_id_done read before
- * we print the log values
- */
- smp_rmb();
- seq_printf(m,
- "%d: %s from %d:%d to %d:%d context %s node %d handle %d size %d:%d ret %d/%d l=%d",
- e->debug_id, (e->call_type == 2) ? "reply" :
- ((e->call_type == 1) ? "async" : "call "), e->from_proc,
- e->from_thread, e->to_proc, e->to_thread, e->context_name,
- e->to_node, e->target_handle, e->data_size, e->offsets_size,
- e->return_error, e->return_error_param,
- e->return_error_line);
- /*
- * read-barrier to guarantee read of debug_id_done after
- * done printing the fields of the entry
- */
- smp_rmb();
- seq_printf(m, debug_id && debug_id == READ_ONCE(e->debug_id_done) ?
- "\n" : " (incomplete)\n");
-}
-
-static int transaction_log_show(struct seq_file *m, void *unused)
-{
- struct binder_transaction_log *log = m->private;
- unsigned int log_cur = atomic_read(&log->cur);
- unsigned int count;
- unsigned int cur;
- int i;
-
- count = log_cur + 1;
- cur = count < ARRAY_SIZE(log->entry) && !log->full ?
- 0 : count % ARRAY_SIZE(log->entry);
- if (count > ARRAY_SIZE(log->entry) || log->full)
- count = ARRAY_SIZE(log->entry);
- for (i = 0; i < count; i++) {
- unsigned int index = cur++ % ARRAY_SIZE(log->entry);
-
- print_binder_transaction_log_entry(m, &log->entry[index]);
- }
- return 0;
-}
-
-const struct file_operations binder_fops = {
- .owner = THIS_MODULE,
- .poll = binder_poll,
- .unlocked_ioctl = binder_ioctl,
- .compat_ioctl = compat_ptr_ioctl,
- .mmap = binder_mmap,
- .open = binder_open,
- .flush = binder_flush,
- .release = binder_release,
-};
-
-DEFINE_SHOW_ATTRIBUTE(state);
-DEFINE_SHOW_ATTRIBUTE(state_hashed);
-DEFINE_SHOW_ATTRIBUTE(stats);
-DEFINE_SHOW_ATTRIBUTE(transactions);
-DEFINE_SHOW_ATTRIBUTE(transactions_hashed);
-DEFINE_SHOW_ATTRIBUTE(transaction_log);
-
-const struct binder_debugfs_entry binder_debugfs_entries[] = {
- {
- .name = "state",
- .mode = 0444,
- .fops = &state_fops,
- .data = NULL,
- },
- {
- .name = "state_hashed",
- .mode = 0444,
- .fops = &state_hashed_fops,
- .data = NULL,
- },
- {
- .name = "stats",
- .mode = 0444,
- .fops = &stats_fops,
- .data = NULL,
- },
- {
- .name = "transactions",
- .mode = 0444,
- .fops = &transactions_fops,
- .data = NULL,
- },
- {
- .name = "transactions_hashed",
- .mode = 0444,
- .fops = &transactions_hashed_fops,
- .data = NULL,
- },
- {
- .name = "transaction_log",
- .mode = 0444,
- .fops = &transaction_log_fops,
- .data = &binder_transaction_log,
- },
- {
- .name = "failed_transaction_log",
- .mode = 0444,
- .fops = &transaction_log_fops,
- .data = &binder_transaction_log_failed,
- },
- {} /* terminator */
-};
-
-void binder_add_device(struct binder_device *device)
-{
- guard(spinlock)(&binder_devices_lock);
- hlist_add_head(&device->hlist, &binder_devices);
-}
-
-void binder_remove_device(struct binder_device *device)
-{
- guard(spinlock)(&binder_devices_lock);
- hlist_del_init(&device->hlist);
-}
-
-static int __init init_binder_device(const char *name)
-{
- int ret;
- struct binder_device *binder_device;
-
- binder_device = kzalloc_obj(*binder_device);
- if (!binder_device)
- return -ENOMEM;
-
- binder_device->miscdev.fops = &binder_fops;
- binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
- binder_device->miscdev.name = name;
-
- refcount_set(&binder_device->ref, 1);
- binder_device->context.binder_context_mgr_uid = INVALID_UID;
- binder_device->context.name = name;
- mutex_init(&binder_device->context.context_mgr_node_lock);
-
- ret = misc_register(&binder_device->miscdev);
- if (ret < 0) {
- kfree(binder_device);
- return ret;
- }
-
- binder_add_device(binder_device);
-
- return ret;
-}
-
-static int __init binder_init(void)
-{
- int ret;
- char *device_name, *device_tmp;
- struct binder_device *device;
- struct hlist_node *tmp;
- char *device_names = NULL;
- const struct binder_debugfs_entry *db_entry;
-
- ret = binder_alloc_shrinker_init();
- if (ret)
- return ret;
-
- atomic_set(&binder_transaction_log.cur, ~0U);
- atomic_set(&binder_transaction_log_failed.cur, ~0U);
-
- binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL);
-
- binder_for_each_debugfs_entry(db_entry)
- debugfs_create_file(db_entry->name,
- db_entry->mode,
- binder_debugfs_dir_entry_root,
- db_entry->data,
- db_entry->fops);
-
- binder_debugfs_dir_entry_proc = debugfs_create_dir("proc",
- binder_debugfs_dir_entry_root);
-
- if (!IS_ENABLED(CONFIG_ANDROID_BINDERFS) &&
- strcmp(binder_devices_param, "") != 0) {
- /*
- * Copy the module_parameter string, because we don't want to
- * tokenize it in-place.
- */
- device_names = kstrdup(binder_devices_param, GFP_KERNEL);
- if (!device_names) {
- ret = -ENOMEM;
- goto err_alloc_device_names_failed;
- }
-
- device_tmp = device_names;
- while ((device_name = strsep(&device_tmp, ","))) {
- ret = init_binder_device(device_name);
- if (ret)
- goto err_init_binder_device_failed;
- }
- }
-
- ret = genl_register_family(&binder_nl_family);
- if (ret)
- goto err_init_binder_device_failed;
-
- ret = init_binderfs();
- if (ret)
- goto err_init_binderfs_failed;
-
- return ret;
-
-err_init_binderfs_failed:
- genl_unregister_family(&binder_nl_family);
-
-err_init_binder_device_failed:
- hlist_for_each_entry_safe(device, tmp, &binder_devices, hlist) {
- misc_deregister(&device->miscdev);
- binder_remove_device(device);
- kfree(device);
- }
-
- kfree(device_names);
-
-err_alloc_device_names_failed:
- debugfs_remove_recursive(binder_debugfs_dir_entry_root);
- binder_alloc_shrinker_exit();
-
- return ret;
-}
-
-device_initcall(binder_init);
-
-#define CREATE_TRACE_POINTS
-#include "binder_trace.h"
diff --git a/drivers/android/binder/Makefile b/drivers/android/binder/Makefile
index 7e0cd9782a8b..fe474628a94e 100644
--- a/drivers/android/binder/Makefile
+++ b/drivers/android/binder/Makefile
@@ -1,8 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
ccflags-y += -I$(src) # needed for trace events
-obj-$(CONFIG_ANDROID_BINDER_IPC_RUST) += rust_binder.o
-rust_binder-y := \
+obj-$(CONFIG_ANDROID_BINDER_IPC_RUST) += binder.o
+binder-y := \
rust_binder_main.o \
rust_binderfs.o \
rust_binder_events.o
diff --git a/drivers/android/binder/allocation.rs b/drivers/android/binder/allocation.rs
index 8151ba3ea7f4..927bf47d5d20 100644
--- a/drivers/android/binder/allocation.rs
+++ b/drivers/android/binder/allocation.rs
@@ -20,6 +20,7 @@ use crate::{
defs::*,
node::{Node, NodeRef},
process::Process,
+ trace::trace_transaction_failed_buffer_release,
DArc,
};
@@ -208,7 +209,7 @@ impl Allocation {
let num_close_on_free = files.iter().filter(|entry| entry.close_on_free).count();
let mut close_on_free = KVec::with_capacity(num_close_on_free, GFP_KERNEL)?;
- let mut reservations = KVec::with_capacity(files.len(), GFP_KERNEL)?;
+ let mut reservations = KVVec::with_capacity(files.len(), GFP_KERNEL)?;
for file_info in files {
let res = FileDescriptorReservation::get_unused_fd_flags(bindings::O_CLOEXEC)?;
let fd = res.reserved_fd();
@@ -325,6 +326,12 @@ impl core::ops::DerefMut for NewAllocation {
}
}
+impl Drop for NewAllocation {
+ fn drop(&mut self) {
+ trace_transaction_failed_buffer_release(self.debug_id);
+ }
+}
+
/// A view into the beginning of an allocation.
///
/// All attempts to read or write outside of the view will fail. To intentionally access outside of
@@ -503,7 +510,7 @@ impl BinderObject {
/// The closure should write the bytes for the object into the provided slice.
pub(crate) fn read_from_inner<R>(reader: R) -> Result<BinderObject>
where
- R: FnOnce(&mut [u8; size_of::<BinderObject>()]) -> Result<()>,
+ R: FnOnce(&mut [u8; size_of::<BinderObject>()]) -> Result,
{
let mut obj = MaybeUninit::<BinderObject>::zeroed();
@@ -567,7 +574,7 @@ impl BinderObject {
#[derive(Default)]
struct FileList {
- files_to_translate: KVec<FileEntry>,
+ files_to_translate: KVVec<FileEntry>,
close_on_free: KVec<u32>,
}
@@ -581,7 +588,7 @@ struct FileEntry {
}
pub(crate) struct TranslatedFds {
- reservations: KVec<Reservation>,
+ reservations: KVVec<Reservation>,
/// If commit is called, then these fds should be closed. (If commit is not called, then they
/// shouldn't be closed.)
close_on_free: FdsCloseOnFree,
@@ -595,7 +602,7 @@ struct Reservation {
impl TranslatedFds {
pub(crate) fn new() -> Self {
Self {
- reservations: KVec::new(),
+ reservations: KVVec::new(),
close_on_free: FdsCloseOnFree(KVec::new()),
}
}
diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs
index 431d6007a9b1..927b65ada836 100644
--- a/drivers/android/binder/context.rs
+++ b/drivers/android/binder/context.rs
@@ -134,13 +134,13 @@ impl Context {
}
pub(crate) fn get_manager_node(&self, strong: bool) -> Result<NodeRef, BinderError> {
- self.manager
+ Ok(self
+ .manager
.lock()
.node
.as_ref()
- .ok_or_else(BinderError::new_dead)?
- .clone(strong)
- .map_err(BinderError::from)
+ .ok_or_else(|| BinderError::new_dead())?
+ .clone(strong)?)
}
pub(crate) fn for_each_proc<F>(&self, mut func: F)
diff --git a/drivers/android/binder/defs.rs b/drivers/android/binder/defs.rs
index 8ac9bdd7a499..cc4becd6e168 100644
--- a/drivers/android/binder/defs.rs
+++ b/drivers/android/binder/defs.rs
@@ -77,7 +77,8 @@ pub_no_prefix!(
TF_ONE_WAY,
TF_ACCEPT_FDS,
TF_CLEAR_BUF,
- TF_UPDATE_TXN
+ TF_UPDATE_TXN,
+ TF_DEFER_COMPLETE,
);
pub(crate) use uapi::{
diff --git a/drivers/android/binder/error.rs b/drivers/android/binder/error.rs
index 1296072c35d9..89077c5baeaf 100644
--- a/drivers/android/binder/error.rs
+++ b/drivers/android/binder/error.rs
@@ -2,6 +2,7 @@
// Copyright (C) 2025 Google LLC.
+use core::panic::Location;
use kernel::fmt;
use kernel::prelude::*;
@@ -9,62 +10,96 @@ use crate::defs::*;
pub(crate) type BinderResult<T = ()> = core::result::Result<T, BinderError>;
+/// Wraps a `&'static Location` to format it as `filename.rs:line` without the directory prefix.
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+pub(crate) struct ErrorLocation(&'static Location<'static>);
+
+// SAFETY: `ErrorLocation` is `repr(transparent)` over a shared reference, which is part of the
+// option layout optimization guarantee, so all zeroes is a valid representation for `None`.
+unsafe impl pin_init::ZeroableOption for ErrorLocation {}
+
+impl ErrorLocation {
+ #[track_caller]
+ pub(crate) const fn caller() -> Self {
+ Self(Location::caller())
+ }
+}
+
+impl fmt::Display for ErrorLocation {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let file = match self.0.file().rsplit_once('/') {
+ Some((_, file)) => file,
+ None => self.0.file(),
+ };
+ write!(f, "{}:{}", file, self.0.line())
+ }
+}
+
/// An error that will be returned to userspace via the `BINDER_WRITE_READ` ioctl rather than via
/// errno.
pub(crate) struct BinderError {
pub(crate) reply: u32,
pub(crate) source: Option<Error>,
+ pub(crate) line: ErrorLocation,
}
impl BinderError {
+ #[track_caller]
pub(crate) fn new_dead() -> Self {
Self {
reply: BR_DEAD_REPLY,
source: None,
+ line: ErrorLocation::caller(),
}
}
+ #[track_caller]
pub(crate) fn new_frozen() -> Self {
Self {
reply: BR_FROZEN_REPLY,
source: None,
+ line: ErrorLocation::caller(),
}
}
+ #[track_caller]
pub(crate) fn new_frozen_oneway() -> Self {
Self {
reply: BR_TRANSACTION_PENDING_FROZEN,
source: None,
+ line: ErrorLocation::caller(),
}
}
-
- pub(crate) fn is_dead(&self) -> bool {
- self.reply == BR_DEAD_REPLY
- }
}
/// Convert an errno into a `BinderError` and store the errno used to construct it. The errno
/// should be stored as the thread's extended error when given to userspace.
impl From<Error> for BinderError {
+ #[track_caller]
fn from(source: Error) -> Self {
Self {
reply: BR_FAILED_REPLY,
source: Some(source),
+ line: ErrorLocation::caller(),
}
}
}
impl From<kernel::fs::file::BadFdError> for BinderError {
+ #[track_caller]
fn from(source: kernel::fs::file::BadFdError) -> Self {
BinderError::from(Error::from(source))
}
}
impl From<kernel::alloc::AllocError> for BinderError {
+ #[track_caller]
fn from(_: kernel::alloc::AllocError) -> Self {
Self {
reply: BR_FAILED_REPLY,
source: Some(ENOMEM),
+ line: ErrorLocation::caller(),
}
}
}
diff --git a/drivers/android/binder/freeze.rs b/drivers/android/binder/freeze.rs
index 7fbde3345ced..69e058e6d818 100644
--- a/drivers/android/binder/freeze.rs
+++ b/drivers/android/binder/freeze.rs
@@ -75,10 +75,8 @@ impl FreezeMessage {
}
fn init(ua: UninitFM, cookie: FreezeCookie, pid: i32) -> DLArc<FreezeMessage> {
- match ua.pin_init_with(DTRWrap::new(FreezeMessage { cookie, pid })) {
- Ok(msg) => ListArc::from(msg),
- Err(err) => match err {},
- }
+ let Ok(msg) = ua.pin_init_with(DTRWrap::new(FreezeMessage { cookie, pid }));
+ ListArc::from(msg)
}
}
@@ -155,7 +153,7 @@ impl DeliverToRead for FreezeMessage {
}
#[inline(never)]
- fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
+ fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result {
seq_print!(m, "{}has frozen binder\n", prefix);
Ok(())
}
@@ -177,10 +175,7 @@ impl FreezeListener {
}
impl Process {
- pub(crate) fn request_freeze_notif(
- self: &Arc<Self>,
- reader: &mut UserSliceReader,
- ) -> Result<()> {
+ pub(crate) fn request_freeze_notif(self: &Arc<Self>, reader: &mut UserSliceReader) -> Result {
let hc = reader.read::<BinderHandleCookie>()?;
let handle = hc.handle;
let cookie = FreezeCookie(hc.cookie);
@@ -272,7 +267,7 @@ impl Process {
Ok(())
}
- pub(crate) fn freeze_notif_done(self: &Arc<Self>, reader: &mut UserSliceReader) -> Result<()> {
+ pub(crate) fn freeze_notif_done(self: &Arc<Self>, reader: &mut UserSliceReader) -> Result {
let cookie = FreezeCookie(reader.read()?);
let alloc = FreezeMessage::new(GFP_KERNEL)?;
let mut node_refs_guard = self.node_refs.lock();
@@ -313,7 +308,7 @@ impl Process {
Ok(())
}
- pub(crate) fn clear_freeze_notif(self: &Arc<Self>, reader: &mut UserSliceReader) -> Result<()> {
+ pub(crate) fn clear_freeze_notif(self: &Arc<Self>, reader: &mut UserSliceReader) -> Result {
let hc = reader.read::<BinderHandleCookie>()?;
let handle = hc.handle;
let cookie = FreezeCookie(hc.cookie);
diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs
index 0c625a25cb73..efdafc202e27 100644
--- a/drivers/android/binder/node.rs
+++ b/drivers/android/binder/node.rs
@@ -255,11 +255,7 @@ impl Node {
}
#[inline(never)]
- pub(crate) fn full_debug_print(
- &self,
- m: &SeqFile,
- owner_inner: &mut ProcessInner,
- ) -> Result<()> {
+ pub(crate) fn full_debug_print(&self, m: &SeqFile, owner_inner: &mut ProcessInner) -> Result {
let inner = self.inner.access_mut(owner_inner);
seq_print!(
m,
@@ -759,7 +755,7 @@ impl DeliverToRead for Node {
}
#[inline(never)]
- fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
+ fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result {
seq_print!(
m,
"{}node work {}: u{:016x} c{:016x}\n",
@@ -1154,7 +1150,7 @@ impl DeliverToRead for NodeDeath {
}
#[inline(never)]
- fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
+ fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result {
let inner = self.inner.lock();
let dead_binder = inner.dead && !inner.notification_done;
diff --git a/drivers/android/binder/node/wrapper.rs b/drivers/android/binder/node/wrapper.rs
index 886626ca0d42..edfbccf6e6da 100644
--- a/drivers/android/binder/node/wrapper.rs
+++ b/drivers/android/binder/node/wrapper.rs
@@ -20,10 +20,8 @@ impl CritIncrWrapper {
}
pub(super) fn init(self, node: DArc<Node>) -> DLArc<dyn DeliverToRead> {
- match self.inner.pin_init_with(DTRWrap::new(NodeWrapper { node })) {
- Ok(initialized) => ListArc::from(initialized) as DLArc<dyn DeliverToRead>,
- Err(err) => match err {},
- }
+ let Ok(initialized) = self.inner.pin_init_with(DTRWrap::new(NodeWrapper { node }));
+ ListArc::from(initialized) as DLArc<dyn DeliverToRead>
}
}
@@ -96,7 +94,7 @@ impl DeliverToRead for NodeWrapper {
}
#[inline(never)]
- fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
+ fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result {
seq_print!(
m,
"{}node work {}: u{:016x} c{:016x}\n",
diff --git a/drivers/android/binder/page_range.rs b/drivers/android/binder/page_range.rs
index e2d46b4d1a29..b0c797523bc2 100644
--- a/drivers/android/binder/page_range.rs
+++ b/drivers/android/binder/page_range.rs
@@ -66,7 +66,7 @@ impl Shrinker {
}
/// Register this shrinker with the kernel.
- pub(crate) fn register(&'static self, name: &CStr) -> Result<()> {
+ pub(crate) fn register(&'static self, name: &CStr) -> Result {
// SAFETY: These fields are not yet used, so it's okay to zero them.
unsafe {
self.inner.get().write(ptr::null_mut());
@@ -352,7 +352,7 @@ impl ShrinkablePageRange {
/// Make sure that the given pages are allocated and mapped.
///
/// Must not be called from an atomic context.
- pub(crate) fn use_range(&self, start: usize, end: usize) -> Result<()> {
+ pub(crate) fn use_range(&self, start: usize, end: usize) -> Result {
if start >= end {
return Ok(());
}
@@ -398,7 +398,7 @@ impl ShrinkablePageRange {
///
/// Assumes that `i` is in bounds.
#[cold]
- unsafe fn use_page_slow(&self, i: usize) -> Result<()> {
+ unsafe fn use_page_slow(&self, i: usize) -> Result {
let new_page = Page::alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO)?;
let mm_mutex = self.mm_lock.lock();
diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs
index 65f719e46c3e..a2216c8f9648 100644
--- a/drivers/android/binder/process.rs
+++ b/drivers/android/binder/process.rs
@@ -548,7 +548,7 @@ impl Process {
}
#[inline(never)]
- pub(crate) fn debug_print_stats(&self, m: &SeqFile, ctx: &Context) -> Result<()> {
+ pub(crate) fn debug_print_stats(&self, m: &SeqFile, ctx: &Context) -> Result {
seq_print!(m, "proc {}\n", self.pid_in_current_ns());
seq_print!(m, "context {}\n", &*ctx.name);
@@ -596,7 +596,7 @@ impl Process {
}
#[inline(never)]
- pub(crate) fn debug_print(&self, m: &SeqFile, ctx: &Context, print_all: bool) -> Result<()> {
+ pub(crate) fn debug_print(&self, m: &SeqFile, ctx: &Context, print_all: bool) -> Result {
seq_print!(m, "proc {}\n", self.pid_in_current_ns());
seq_print!(m, "context {}\n", &*ctx.name);
@@ -686,8 +686,16 @@ impl Process {
pub(crate) fn get_work_or_register<'a>(
&'a self,
thread: &'a Arc<Thread>,
+ thread_has_deferred_work: bool,
) -> GetWorkOrRegister<'a> {
let mut inner = self.inner.lock();
+
+ if thread_has_deferred_work && !inner.work.is_empty() {
+ if let Some(work) = thread.pop_work_even_if_deferred() {
+ return GetWorkOrRegister::Work(work);
+ }
+ }
+
// Try to get work from the process queue.
if let Some(work) = inner.work.pop_front() {
return GetWorkOrRegister::Work(work);
@@ -894,11 +902,8 @@ impl Process {
let (info_proc, info_node) = {
let info_init = NodeRefInfo::new(node_ref, handle, self.into());
- match info.pin_init_with(info_init) {
- Ok(info) => ListArc::pair_from_pin_unique(info),
- // error is infallible
- Err(err) => match err {},
- }
+ let Ok(info) = info.pin_init_with(info_init);
+ ListArc::pair_from_pin_unique(info)
};
// Ensure the process is still alive while we insert a new reference.
@@ -1068,7 +1073,10 @@ impl Process {
let (new_alloc, addr) = loop {
let mut inner = self.inner.lock();
- let mapping = inner.mapping.as_mut().ok_or_else(BinderError::new_dead)?;
+ let mapping = inner
+ .mapping
+ .as_mut()
+ .ok_or_else(|| BinderError::new_dead())?;
let alloc_request = match mapping.alloc.reserve_new(reserve_new_args)? {
ReserveNew::Success(new_alloc) => break (new_alloc, mapping.address),
ReserveNew::NeedAlloc(request) => request,
@@ -1208,13 +1216,12 @@ impl Process {
let ptr = reader.read::<BinderNodeDebugInfo>()?.ptr;
let mut out = BinderNodeDebugInfo::default();
- {
+ if let Some(start_ptr) = ptr.checked_add(1) {
let inner = self.inner.lock();
- for (node_ptr, node) in &inner.nodes {
- if *node_ptr > ptr {
- node.populate_debug_info(&mut out, &inner);
- break;
- }
+ // cursor_lower_bound retrieves the "key" passed or the next existing larger key
+ if let Some(cursor) = inner.nodes.cursor_lower_bound(&start_ptr) {
+ let (_, node) = cursor.current();
+ node.populate_debug_info(&mut out, &inner);
}
}
@@ -1296,14 +1303,8 @@ impl Process {
return Ok(());
}
- let death = {
- let death_init = NodeDeath::new(info.node_ref().node.clone(), self.clone(), cookie);
- match death.pin_init_with(death_init) {
- Ok(death) => death,
- // error is infallible
- Err(err) => match err {},
- }
- };
+ let death_init = NodeDeath::new(info.node_ref().node.clone(), self.clone(), cookie);
+ let Ok(death) = death.pin_init_with(death_init);
// Register the death notification.
{
diff --git a/drivers/android/binder/range_alloc/array.rs b/drivers/android/binder/range_alloc/array.rs
index 081d19b09d4b..71bf49f9db0d 100644
--- a/drivers/android/binder/range_alloc/array.rs
+++ b/drivers/android/binder/range_alloc/array.rs
@@ -61,7 +61,7 @@ impl<T> ArrayRangeAllocator<T> {
self.ranges.len() == self.ranges.capacity()
}
- pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> {
+ pub(crate) fn debug_print(&self, m: &SeqFile) -> Result {
for range in &self.ranges {
seq_print!(
m,
diff --git a/drivers/android/binder/range_alloc/mod.rs b/drivers/android/binder/range_alloc/mod.rs
index 1f4734468ff1..bb6e0ec9650e 100644
--- a/drivers/android/binder/range_alloc/mod.rs
+++ b/drivers/android/binder/range_alloc/mod.rs
@@ -141,7 +141,7 @@ impl<T> RangeAllocator<T> {
}
}
- pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> {
+ pub(crate) fn debug_print(&self, m: &SeqFile) -> Result {
match &self.inner {
Impl::Empty(_size) => Ok(()),
Impl::Array(array) => array.debug_print(m),
diff --git a/drivers/android/binder/range_alloc/tree.rs b/drivers/android/binder/range_alloc/tree.rs
index 3e3b452909ed..f0cd46f223d5 100644
--- a/drivers/android/binder/range_alloc/tree.rs
+++ b/drivers/android/binder/range_alloc/tree.rs
@@ -111,7 +111,7 @@ impl<T> TreeRangeAllocator<T> {
.count()
}
- pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> {
+ pub(crate) fn debug_print(&self, m: &SeqFile) -> Result {
for desc in self.tree.values() {
let state = match &desc.state {
Some(state) => &state.0,
diff --git a/drivers/android/binder/rust_binder_events.h b/drivers/android/binder/rust_binder_events.h
index 1a446787c8b3..045ab77aa44b 100644
--- a/drivers/android/binder/rust_binder_events.h
+++ b/drivers/android/binder/rust_binder_events.h
@@ -177,6 +177,48 @@ TRACE_EVENT(binder_return,
"unknown")
);
+
+TRACE_EVENT(binder_transaction_alloc_buf,
+ TP_PROTO(int debug_id, size_t data_size, size_t offsets_size, size_t extra_buffers_size),
+ TP_ARGS(debug_id, data_size, offsets_size, extra_buffers_size),
+ TP_STRUCT__entry(
+ __field(int, debug_id)
+ __field(size_t, data_size)
+ __field(size_t, offsets_size)
+ __field(size_t, extra_buffers_size)
+ ),
+ TP_fast_assign(
+ __entry->debug_id = debug_id;
+ __entry->data_size = data_size;
+ __entry->offsets_size = offsets_size;
+ __entry->extra_buffers_size = extra_buffers_size;
+ ),
+ TP_printk("transaction=%d data_size=%zd offsets_size=%zd extra_buffers_size=%zd",
+ __entry->debug_id, __entry->data_size, __entry->offsets_size,
+ __entry->extra_buffers_size)
+);
+
+DECLARE_EVENT_CLASS(binder_buffer_release_class,
+ TP_PROTO(int debug_id),
+ TP_ARGS(debug_id),
+ TP_STRUCT__entry(
+ __field(int, debug_id)
+ ),
+ TP_fast_assign(
+ __entry->debug_id = debug_id;
+ ),
+ TP_printk("transaction=%d", __entry->debug_id)
+);
+
+#define DEFINE_RBINDER_FUNCTION_BUFFER_EVENT(name) \
+DEFINE_EVENT(binder_buffer_release_class, name, \
+ TP_PROTO(int debug_id), \
+ TP_ARGS(debug_id))
+
+DEFINE_RBINDER_FUNCTION_BUFFER_EVENT(binder_transaction_buffer_release);
+DEFINE_RBINDER_FUNCTION_BUFFER_EVENT(binder_transaction_failed_buffer_release);
+DEFINE_RBINDER_FUNCTION_BUFFER_EVENT(binder_transaction_update_buffer_release);
+
#endif /* _RUST_BINDER_TRACE_H */
/* This part must be outside protection */
diff --git a/drivers/android/binder/rust_binder_internal.h b/drivers/android/binder/rust_binder_internal.h
index 78288fe7964d..50a50df14c77 100644
--- a/drivers/android/binder/rust_binder_internal.h
+++ b/drivers/android/binder/rust_binder_internal.h
@@ -44,6 +44,7 @@ struct binder_device {
int rust_binder_stats_show(struct seq_file *m, void *unused);
int rust_binder_state_show(struct seq_file *m, void *unused);
int rust_binder_transactions_show(struct seq_file *m, void *unused);
+int rust_binder_transaction_log_show(struct seq_file *m, void *unused);
int rust_binder_proc_show(struct seq_file *m, void *pid);
extern const struct file_operations rust_binder_fops;
diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs
index 955c4c348f73..56c786475237 100644
--- a/drivers/android/binder/rust_binder_main.rs
+++ b/drivers/android/binder/rust_binder_main.rs
@@ -81,7 +81,7 @@ mod binderfs {
module! {
type: BinderModule,
- name: "rust_binder",
+ name: "binder",
authors: ["Wedson Almeida Filho", "Alice Ryhl"],
description: "Android Binder",
license: "GPL",
@@ -98,7 +98,12 @@ static RUST_BINDER_LAYOUT: rust_binder_layout = rust_binder_layout {
fn next_debug_id() -> usize {
static NEXT_DEBUG_ID: Atomic<usize> = Atomic::new(0);
- NEXT_DEBUG_ID.fetch_add(1, Relaxed)
+ loop {
+ let id = NEXT_DEBUG_ID.fetch_add(1, Relaxed);
+ if id != 0 {
+ return id;
+ }
+ }
}
/// Provides a single place to write Binder return values via the
@@ -158,7 +163,7 @@ trait DeliverToRead: ListArcSafe + Send + Sync {
/// Generally only set to true for non-oneway transactions.
fn should_sync_wakeup(&self) -> bool;
- fn debug_print(&self, m: &SeqFile, prefix: &str, transaction_prefix: &str) -> Result<()>;
+ fn debug_print(&self, m: &SeqFile, prefix: &str, transaction_prefix: &str) -> Result;
}
// Wrapper around a `DeliverToRead` with linked list links.
@@ -274,7 +279,7 @@ impl DeliverToRead for DeliverCode {
false
}
- fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
+ fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result {
seq_print!(m, "{}", prefix);
if self.skip.load(Relaxed) {
seq_print!(m, "(skipped) ");
@@ -305,6 +310,9 @@ impl kernel::Module for BinderModule {
// SAFETY: The module initializer never runs twice, so we only call this once.
unsafe { crate::context::CONTEXTS.init() };
+ crate::transaction::TRANSACTION_LOG.init()?;
+ crate::transaction::FAILED_TRANSACTION_LOG.init()?;
+
let netlink = crate::netlink::BINDER_NL_FAMILY.register()?;
BINDER_SHRINKER.register(c"android-binder")?;
@@ -545,7 +553,28 @@ unsafe extern "C" fn rust_binder_transactions_show(
0
}
-fn rust_binder_transactions_show_impl(m: &SeqFile) -> Result<()> {
+/// # Safety
+/// Only called by binderfs.
+#[no_mangle]
+unsafe extern "C" fn rust_binder_transaction_log_show(
+ ptr: *mut seq_file,
+ _: *mut kernel::ffi::c_void,
+) -> kernel::ffi::c_int {
+ // SAFETY: Accessing the private field of `seq_file` is okay.
+ let is_failed = !unsafe { (*ptr).private }.is_null();
+ // SAFETY: The caller ensures that the pointer is valid and exclusive for the duration in which
+ // this method is called.
+ let m = unsafe { SeqFile::from_raw(ptr) };
+ let log = if is_failed {
+ &transaction::FAILED_TRANSACTION_LOG
+ } else {
+ &transaction::TRANSACTION_LOG
+ };
+ log.debug_print(m);
+ 0
+}
+
+fn rust_binder_transactions_show_impl(m: &SeqFile) -> Result {
seq_print!(m, "binder transactions:\n");
let contexts = context::get_all_contexts()?;
for ctx in contexts {
@@ -558,7 +587,7 @@ fn rust_binder_transactions_show_impl(m: &SeqFile) -> Result<()> {
Ok(())
}
-fn rust_binder_stats_show_impl(m: &SeqFile) -> Result<()> {
+fn rust_binder_stats_show_impl(m: &SeqFile) -> Result {
seq_print!(m, "binder stats:\n");
stats::GLOBAL_STATS.debug_print("", m);
let contexts = context::get_all_contexts()?;
@@ -572,7 +601,7 @@ fn rust_binder_stats_show_impl(m: &SeqFile) -> Result<()> {
Ok(())
}
-fn rust_binder_state_show_impl(m: &SeqFile) -> Result<()> {
+fn rust_binder_state_show_impl(m: &SeqFile) -> Result {
seq_print!(m, "binder state:\n");
let contexts = context::get_all_contexts()?;
for ctx in contexts {
@@ -585,7 +614,7 @@ fn rust_binder_state_show_impl(m: &SeqFile) -> Result<()> {
Ok(())
}
-fn rust_binder_proc_show_impl(m: &SeqFile, pid: Pid) -> Result<()> {
+fn rust_binder_proc_show_impl(m: &SeqFile, pid: Pid) -> Result {
seq_print!(m, "binder proc state:\n");
let contexts = context::get_all_contexts()?;
for ctx in contexts {
diff --git a/drivers/android/binder/rust_binderfs.c b/drivers/android/binder/rust_binderfs.c
index d48895f84596..5f020226720d 100644
--- a/drivers/android/binder/rust_binderfs.c
+++ b/drivers/android/binder/rust_binderfs.c
@@ -46,10 +46,11 @@
DEFINE_SHOW_ATTRIBUTE(rust_binder_stats);
DEFINE_SHOW_ATTRIBUTE(rust_binder_state);
DEFINE_SHOW_ATTRIBUTE(rust_binder_transactions);
+DEFINE_SHOW_ATTRIBUTE(rust_binder_transaction_log);
DEFINE_SHOW_ATTRIBUTE(rust_binder_proc);
char *rust_binder_devices_param = CONFIG_ANDROID_BINDER_DEVICES;
-module_param_named(rust_devices, rust_binder_devices_param, charp, 0444);
+module_param_named(devices, rust_binder_devices_param, charp, 0444);
extern u32 rust_binder_debug_mask;
module_param_named(debug_mask, rust_binder_debug_mask, uint, 0644);
@@ -611,6 +612,24 @@ static int init_binder_logs(struct super_block *sb)
goto out;
}
+ dentry = rust_binderfs_create_file(binder_logs_root_dir,
+ "transaction_log",
+ &rust_binder_transaction_log_fops,
+ (void *)0);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto out;
+ }
+
+ dentry = rust_binderfs_create_file(binder_logs_root_dir,
+ "failed_transaction_log",
+ &rust_binder_transaction_log_fops,
+ (void *)1);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto out;
+ }
+
proc_log_dir = binderfs_create_dir(binder_logs_root_dir, "proc");
if (IS_ERR(proc_log_dir)) {
ret = PTR_ERR(proc_log_dir);
diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs
index e0ed20f510fd..f5965e838a96 100644
--- a/drivers/android/binder/thread.rs
+++ b/drivers/android/binder/thread.rs
@@ -30,6 +30,7 @@ use crate::{
process::{GetWorkOrRegister, Process},
ptr_align,
stats::GLOBAL_STATS,
+ trace::{trace_transaction_alloc_buf, trace_transaction_buffer_release},
transaction::{Transaction, TransactionFlag, TransactionFlags, TransactionInfo},
BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverCode, DeliverToRead,
};
@@ -344,13 +345,13 @@ impl InnerThread {
}
}
- fn push_return_work(&mut self, reply: u32) {
+ fn push_return_work(&mut self, reply: u32) -> PushWorkRes {
if let Ok(work) = ListArc::try_from_arc(self.return_work.clone()) {
work.set_error_code(reply);
- // Not notifying: Reply to current thread.
- let _ = self.push_work(work);
+ self.push_work(work)
} else {
pr_warn_ratelimited!("Thread return work is already in use.\n");
+ PushWorkRes::Ok
}
}
@@ -481,7 +482,7 @@ impl Thread {
}
#[inline(never)]
- pub(crate) fn debug_print(self: &Arc<Self>, m: &SeqFile, print_all: bool) -> Result<()> {
+ pub(crate) fn debug_print(self: &Arc<Self>, m: &SeqFile, print_all: bool) -> Result {
let inner = self.inner.lock();
if print_all || inner.current_transaction.is_some() || !inner.work_list.is_empty() {
@@ -584,9 +585,21 @@ impl Thread {
// mangled symbol names.
#[export_name = "rust_binder_wait"]
fn get_work(self: &Arc<Self>, wait: bool) -> Result<Option<DLArc<dyn DeliverToRead>>> {
+ let thread_has_deferred_work;
+
// Try to get work from the thread's work queue, using only a local lock.
{
let mut inner = self.inner.lock();
+
+ // The process_work_list boolean is used to make us go to sleep even if there is work
+ // in the thread todo-list, but it doesn't apply to the process todo-list. Furthermore,
+ // work in the thread todo-list must still be delivered before the process list.
+ //
+ // Thus, in some scenarios we must return the thread work now even if we were requested
+ // to wait. Adjust `process_work_list` to `true` accordingly.
+ inner.process_work_list |= inner.looper_need_return;
+ inner.process_work_list |= !wait;
+
if let Some(work) = inner.pop_work() {
return Ok(Some(work));
}
@@ -594,18 +607,26 @@ impl Thread {
drop(inner);
return Ok(self.process.get_work());
}
+
+ // Note that if the thread list is empty, then the call to `pop_work()` has changed
+ // `process_work_list` back to `false` even if we set it to `true` above.
+ thread_has_deferred_work = !inner.work_list.is_empty();
}
// If the caller doesn't want to wait, try to grab work from the process queue.
//
// We know nothing will have been queued directly to the thread queue because it is not in
- // a transaction and it is not in the process' ready list.
+ // a transaction and it is not in the process' ready list. We also know the thread list has
+ // no deferred work due to the `inner.process_work_list |= !wait` call above.
if !wait {
return self.process.get_work().ok_or(EAGAIN).map(Some);
}
// Get work from the process queue. If none is available, atomically register as ready.
- let reg = match self.process.get_work_or_register(self) {
+ let reg = match self
+ .process
+ .get_work_or_register(self, thread_has_deferred_work)
+ {
GetWorkOrRegister::Work(work) => return Ok(Some(work)),
GetWorkOrRegister::Register(reg) => reg,
};
@@ -621,14 +642,18 @@ impl Thread {
inner.looper_flags &= !(LooperFlag::Waiting | LooperFlag::WaitingProc);
if signal_pending || inner.looper_need_return {
- // We need to return now. We need to pull the thread off the list of ready threads
- // (by dropping `reg`), then check the state again after it's off the list to
- // ensure that something was not queued in the meantime. If something has been
- // queued, we just return it (instead of the error).
+ // We need to return now.
+ //
+ // We need to pull the thread off the list of ready threads (by dropping `reg`),
+ // then check the state again after it's off the list to ensure that something was
+ // not queued in the meantime. If something has been queued (or if there is
+ // deferred work), we just return it (instead of the error).
drop(inner);
drop(reg);
- let res = match self.inner.lock().pop_work() {
+ inner = self.inner.lock();
+ inner.process_work_list = true;
+ let res = match inner.pop_work() {
Some(work) => Ok(Some(work)),
None if signal_pending => Err(EINTR),
None => Ok(None),
@@ -669,8 +694,12 @@ impl Thread {
pub(crate) fn push_work_if_looper(&self, work: DLArc<dyn DeliverToRead>) -> BinderResult {
let mut inner = self.inner.lock();
if inner.is_looper() && !inner.is_dead {
- // Not notifying: Reply to current thread.
- let _ = inner.push_work(work);
+ // Not notifying work_condvar: Reply to current thread.
+ let res = inner.push_work(work);
+ drop(inner);
+ if matches!(res, PushWorkRes::OkNotifyPoll) {
+ self.process.notify_poll(false);
+ }
Ok(())
} else {
drop(inner);
@@ -683,7 +712,11 @@ impl Thread {
}
pub(crate) fn push_return_work(&self, reply: u32) {
- self.inner.lock().push_return_work(reply);
+ // Not notifying work_condvar: Reply to current thread.
+ let res = self.inner.lock().push_return_work(reply);
+ if matches!(res, PushWorkRes::OkNotifyPoll) {
+ self.process.notify_poll(false);
+ }
}
pub(crate) fn pop_work_even_if_deferred(&self) -> Option<DLArc<dyn DeliverToRead>> {
@@ -1046,17 +1079,9 @@ impl Thread {
size_of::<u64>(),
);
let secctx_off = aligned_data_size + offsets_size + buffers_size;
- let mut alloc = match to_process.buffer_alloc(debug_id, len, info) {
- Ok(alloc) => alloc,
- Err(err) => {
- pr_warn!(
- "Failed to allocate buffer. len:{}, is_oneway:{}",
- len,
- info.is_oneway(),
- );
- return Err(err);
- }
- };
+ let mut alloc = to_process.buffer_alloc(debug_id, len, info)?;
+
+ trace_transaction_alloc_buf(debug_id, data_size, offsets_size, buffers_size);
let mut buffer_reader = UserSlice::new(info.data_ptr, data_size).reader();
let mut end_of_previous_object = 0;
@@ -1262,7 +1287,7 @@ impl Thread {
cmd: u32,
reader: &mut UserSliceReader,
info: &mut TransactionInfo,
- ) -> Result<()> {
+ ) -> Result {
let td = match cmd {
BC_TRANSACTION | BC_REPLY => {
reader.read::<BinderTransactionData>()?.with_buffers_size(0)
@@ -1293,7 +1318,7 @@ impl Thread {
}
#[inline(never)]
- fn transaction(self: &Arc<Self>, cmd: u32, reader: &mut UserSliceReader) -> Result<()> {
+ fn transaction(self: &Arc<Self>, cmd: u32, reader: &mut UserSliceReader) -> Result {
let mut info = TransactionInfo::zeroed();
self.read_transaction_info(cmd, reader, &mut info)?;
@@ -1307,47 +1332,57 @@ impl Thread {
self.transaction_inner(&mut info)
};
+ // This runs when return work is passed to the caller. This is not
+ // always the same as the transaction failing, as reply errors are
+ // delivered to the remote process.
if let Err(err) = ret {
self.push_return_work(err.reply);
if err.reply != BR_TRANSACTION_COMPLETE {
info.reply = err.reply;
- if let Some(source) = &err.source {
- info.errno = source.to_errno();
+ info.error_line = Some(err.line);
+ if let Some(source) = err.source {
+ info.errno = Some(source);
- {
- let mut inner = self.inner.lock();
- inner.extended_error =
- ExtendedError::new(info.debug_id as u32, err.reply, source.to_errno());
- }
-
- binder_debug!(
- FailedTransaction,
- "transaction {} to {}:{} failed {:?}, code {} size {}-{}",
- if info.is_reply {
- "reply"
- } else if info.is_oneway() {
- "async"
- } else {
- "call"
- },
- info.to_pid,
- info.to_tid,
- err,
- info.code,
- info.data_size,
- info.offsets_size
- );
+ self.inner.lock().extended_error =
+ ExtendedError::new(info.debug_id as u32, err.reply, source.to_errno());
}
}
}
+ info.write_log(&self.process.ctx);
+
if info.oneway_spam_suspect {
// If this is both a oneway spam suspect and a failure, we report it twice. This is
// useful in case the transaction failed with BR_TRANSACTION_PENDING_FROZEN.
info.report_netlink(BR_ONEWAY_SPAM_SUSPECT, &self.process.ctx);
}
+ // This runs when the transaction failed.
if info.reply != 0 {
info.report_netlink(info.reply, &self.process.ctx);
+ if let Some(errno) = info.errno {
+ let error_line: &dyn kernel::fmt::Display = match &info.error_line {
+ Some(line) => line,
+ None => &0,
+ };
+ binder_debug!(
+ FailedTransaction,
+ "transaction {} to {}:{} failed {:?}, code {} size {}-{} line {}",
+ if info.is_reply {
+ "reply"
+ } else if info.is_oneway() {
+ "async"
+ } else {
+ "call"
+ },
+ info.to_pid,
+ info.to_tid,
+ errno,
+ info.code,
+ info.data_size,
+ info.offsets_size,
+ error_line
+ );
+ }
}
Ok(())
@@ -1356,6 +1391,7 @@ impl Thread {
fn transaction_inner(self: &Arc<Self>, info: &mut TransactionInfo) -> BinderResult {
let node_ref = self.process.get_transaction_node(info.target_handle)?;
info.to_pid = node_ref.node.owner.task.pid();
+ info.to_node_debug_id = node_ref.node.debug_id;
security::binder_transaction(&self.process.cred, &node_ref.node.owner.cred)?;
// TODO: We need to ensure that there isn't a pending transaction in the work queue. How
// could this happen?
@@ -1422,8 +1458,17 @@ impl Thread {
let process = orig.from.process.clone();
let allow_fds = orig.flags.contains(TransactionFlag::AcceptFds);
let reply = Transaction::new_reply(self, process, info, allow_fds)?;
- // Not notifying: Reply to current thread.
- let _ = self.inner.lock().push_work(completion);
+ if info.flags.contains(TransactionFlag::DeferComplete) {
+ // The flag is set. Perform a deferred push so that `read` can wait for the
+ // next incoming transaction without a userspace roundtrip.
+ self.inner.lock().push_work_deferred(completion);
+ } else {
+ // Not notifying work_condvar: Reply to current thread.
+ let res = self.inner.lock().push_work(completion);
+ if matches!(res, PushWorkRes::OkNotifyPoll) {
+ self.process.notify_poll(false);
+ }
+ }
orig.from.deliver_reply(Ok(reply), &orig, None);
Ok(())
})()
@@ -1431,17 +1476,13 @@ impl Thread {
// At this point we only return `BR_TRANSACTION_COMPLETE` to the caller, and we must let
// the sender know that the transaction has completed (with an error in this case).
- pr_warn!(
- "{}:{} reply to {} failed: {err:?}",
- info.from_pid,
- info.from_tid,
- info.to_pid
- );
let param = err.source.as_ref().map_or(0, |e| e.to_errno());
let ee = ExtendedError::new(info.debug_id as u32, err.reply, param);
orig.from
.deliver_reply(Err(BR_FAILED_REPLY), &orig, Some(ee));
info.reply = BR_FAILED_REPLY;
+ info.errno = err.source;
+ info.error_line = Some(err.line);
err.reply = BR_TRANSACTION_COMPLETE;
err
});
@@ -1452,6 +1493,7 @@ impl Thread {
fn oneway_transaction_inner(self: &Arc<Self>, info: &mut TransactionInfo) -> BinderResult {
let node_ref = self.process.get_transaction_node(info.target_handle)?;
info.to_pid = node_ref.node.owner.task.pid();
+ info.to_node_debug_id = node_ref.node.debug_id;
security::binder_transaction(&self.process.cred, &node_ref.node.owner.cred)?;
let transaction = Transaction::new(node_ref, None, self, info)?;
let code = if self.process.is_oneway_spam_detection_enabled() && info.oneway_spam_suspect {
@@ -1462,10 +1504,15 @@ impl Thread {
let list_completion =
DTRWrap::arc_try_new(DeliverCode::new(code, self.process.task.pid()))?;
let completion = list_completion.clone_arc();
- // Not notifying: Reply to current thread.
- let _ = self.inner.lock().push_work(list_completion);
+ // Not notifying work_condvar: Reply to current thread.
+ let res = self.inner.lock().push_work(list_completion);
match transaction.submit(info) {
- Ok(()) => Ok(()),
+ Ok(()) => {
+ if matches!(res, PushWorkRes::OkNotifyPoll) {
+ self.process.notify_poll(false);
+ }
+ Ok(())
+ }
Err(err) => {
completion.skip();
Err(err)
@@ -1495,6 +1542,7 @@ impl Thread {
if buffer.looper_need_return_on_free() {
self.inner.lock().looper_need_return = true;
}
+ trace_transaction_buffer_release(buffer.debug_id);
drop(buffer);
}
}
@@ -1750,7 +1798,7 @@ impl DeliverToRead for ThreadError {
false
}
- fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
+ fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result {
seq_print!(
m,
"{}transaction error: {}\n",
diff --git a/drivers/android/binder/trace.rs b/drivers/android/binder/trace.rs
index 06aabb3cc2f1..de2894bcb368 100644
--- a/drivers/android/binder/trace.rs
+++ b/drivers/android/binder/trace.rs
@@ -24,6 +24,11 @@ declare_trace! {
unsafe fn binder_transaction_fd_recv(t_debug_id: c_int, fd: c_int, offset: usize);
unsafe fn binder_command(cmd: u32);
unsafe fn binder_return(ret: u32);
+ unsafe fn binder_transaction_alloc_buf(
+ debug_id: c_int,data_size: usize, offset_size: usize, extra_buffers_size: usize);
+ unsafe fn binder_transaction_buffer_release(debug_id: c_int);
+ unsafe fn binder_transaction_failed_buffer_release(debug_id: c_int);
+ unsafe fn binder_transaction_update_buffer_release(debug_id: c_int);
}
#[inline]
@@ -105,3 +110,35 @@ pub(crate) fn trace_return(ret: u32) {
// SAFETY: This function is always safe to call.
unsafe { binder_return(ret) }
}
+#[inline]
+pub(crate) fn trace_transaction_alloc_buf(
+ debug_id: usize,
+ data_size: usize,
+ offset_size: usize,
+ extra_buffers_size: usize,
+) {
+ // SAFETY: The `data` pointer is valid.
+ unsafe {
+ binder_transaction_alloc_buf(
+ debug_id as c_int,
+ data_size,
+ offset_size,
+ extra_buffers_size,
+ )
+ }
+}
+#[inline]
+pub(crate) fn trace_transaction_buffer_release(debug_id: usize) {
+ // SAFETY: This function is always safe to call.
+ unsafe { binder_transaction_buffer_release(debug_id as c_int) }
+}
+#[inline]
+pub(crate) fn trace_transaction_failed_buffer_release(debug_id: usize) {
+ // SAFETY: This function is always safe to call.
+ unsafe { binder_transaction_failed_buffer_release(debug_id as c_int) }
+}
+#[inline]
+pub(crate) fn trace_transaction_update_buffer_release(debug_id: usize) {
+ // SAFETY: This function is always safe to call.
+ unsafe { binder_transaction_update_buffer_release(debug_id as c_int) }
+}
diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs
index 66045547c23a..48ac942bb75a 100644
--- a/drivers/android/binder/transaction.rs
+++ b/drivers/android/binder/transaction.rs
@@ -7,8 +7,9 @@ use kernel::{
prelude::*,
seq_file::SeqFile,
seq_print,
+ str::BStr,
sync::atomic::{ordering::Relaxed, Atomic},
- sync::{Arc, SpinLock},
+ sync::{Arc, SetOnce, SpinLock},
task::{Kuid, Pid},
time::{Instant, Monotonic},
types::ScopeGuard,
@@ -18,12 +19,13 @@ use kernel::{
use crate::{
allocation::{Allocation, TranslatedFds},
defs::*,
- error::{BinderError, BinderResult},
+ error::{BinderError, BinderResult, ErrorLocation},
netlink::Report,
node::{Node, NodeRef},
process::{Process, ProcessInner},
ptr_align,
thread::{PushWorkRes, Thread},
+ trace::trace_transaction_update_buffer_release,
BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead,
};
@@ -39,6 +41,7 @@ kernel::impl_flags!(
AcceptFds = TF_ACCEPT_FDS,
ClearBuf = TF_CLEAR_BUF,
UpdateTxn = TF_UPDATE_TXN,
+ DeferComplete = TF_DEFER_COMPLETE,
}
);
@@ -54,12 +57,199 @@ impl TransactionFlags {
}
}
+const LOG_SIZE: usize = 32;
+
+pub(crate) static TRANSACTION_LOG: TransactionLog = TransactionLog::new();
+pub(crate) static FAILED_TRANSACTION_LOG: TransactionLog = TransactionLog::new();
+
+#[derive(Copy, Clone)]
+enum CallType {
+ Call,
+ Async,
+ Reply,
+}
+
+#[derive(Copy, Clone)]
+pub(crate) struct TransactionLogEntry {
+ debug_id: usize,
+ call_type: CallType,
+ from_proc: Pid,
+ from_thread: Pid,
+ // The kernel ignores `target.handle` on replies (where `libbinder` passes `-1` / `0xffffffff`),
+ // and C Binder stores and prints this field as a signed `int` (`%d`).
+ target_handle: i32,
+ to_proc: Pid,
+ to_thread: Pid,
+ to_node: usize,
+ data_size: usize,
+ offsets_size: usize,
+ return_error_line: Option<ErrorLocation>,
+ return_error: u32,
+ return_error_param: i32,
+ context_name: [u8; 16],
+}
+
+impl TransactionLogEntry {
+ const fn empty() -> Self {
+ Self {
+ debug_id: 0,
+ call_type: CallType::Call,
+ from_proc: 0,
+ from_thread: 0,
+ target_handle: 0,
+ to_proc: 0,
+ to_thread: 0,
+ to_node: 0,
+ data_size: 0,
+ offsets_size: 0,
+ return_error_line: None,
+ return_error: 0,
+ return_error_param: 0,
+ context_name: [0; 16],
+ }
+ }
+
+ fn new(info: &TransactionInfo, ctx: &crate::Context) -> Self {
+ let call_type = if info.is_reply {
+ CallType::Reply
+ } else if info.is_oneway() {
+ CallType::Async
+ } else {
+ CallType::Call
+ };
+ let name_bytes = ctx.name.to_bytes();
+ let mut context_name = [0u8; 16];
+ let len = usize::min(name_bytes.len(), context_name.len());
+ context_name[..len].copy_from_slice(&name_bytes[..len]);
+
+ let failed = info.reply != 0 && info.reply != BR_TRANSACTION_PENDING_FROZEN;
+ Self {
+ debug_id: info.debug_id,
+ call_type,
+ from_proc: info.from_pid,
+ from_thread: info.from_tid,
+ target_handle: info.target_handle as i32,
+ to_proc: info.to_pid,
+ to_thread: info.to_tid,
+ to_node: info.to_node_debug_id,
+ data_size: info.data_size,
+ offsets_size: info.offsets_size,
+ return_error_line: if failed { info.error_line } else { None },
+ return_error: if failed { info.reply } else { 0 },
+ return_error_param: if failed {
+ info.errno.map_or(0, |e| e.to_errno())
+ } else {
+ 0
+ },
+ context_name,
+ }
+ }
+}
+
+#[pin_data]
+#[repr(align(64))]
+struct TransactionLogSlot {
+ #[pin]
+ entry: SpinLock<TransactionLogEntry>,
+}
+
+pub(crate) struct TransactionLog {
+ cur: Atomic<usize>,
+ entries: SetOnce<Pin<KBox<[TransactionLogSlot; LOG_SIZE]>>>,
+}
+
+impl TransactionLog {
+ const fn new() -> Self {
+ Self {
+ cur: Atomic::new(0),
+ entries: SetOnce::new(),
+ }
+ }
+
+ pub(crate) fn init(&self) -> Result {
+ let entries = KBox::pin_init(
+ pin_init::pin_init_array_from_fn(|_| {
+ pin_init!(TransactionLogSlot {
+ entry <- kernel::new_spinlock!(
+ TransactionLogEntry::empty(),
+ "TransactionLog::entries"
+ ),
+ })
+ }),
+ GFP_KERNEL,
+ )?;
+ self.entries.populate(entries);
+ Ok(())
+ }
+
+ fn add(&self, entry: &TransactionLogEntry) {
+ let Some(entries) = self.entries.as_ref() else {
+ return;
+ };
+ let idx = self.cur.fetch_add(1, Relaxed) % LOG_SIZE;
+ let mut slot = entries[idx].entry.lock();
+ // Avoid overwriting a newer entry if the ring buffer wraps around between `fetch_add` and
+ // acquiring the lock.
+ // CAST: Overflowing behavior of this cast is intentional to handle `debug_id` wrap-around.
+ let diff = slot.debug_id.wrapping_sub(entry.debug_id) as isize;
+ if slot.debug_id == 0 || diff < 0 {
+ *slot = *entry;
+ }
+ }
+
+ pub(crate) fn debug_print(&self, m: &SeqFile) {
+ let Some(entries) = self.entries.as_ref() else {
+ return;
+ };
+ let cur = self.cur.load(Relaxed);
+ for i in 0..LOG_SIZE {
+ let idx = cur.wrapping_add(i) % LOG_SIZE;
+ let entry = *entries[idx].entry.lock();
+ if entry.debug_id == 0 {
+ continue;
+ }
+ let call_type = match entry.call_type {
+ CallType::Call => "call ",
+ CallType::Async => "async",
+ CallType::Reply => "reply",
+ };
+ let ctx_name = match CStr::from_bytes_until_nul(&entry.context_name) {
+ Ok(cstr) => BStr::from_bytes(cstr.to_bytes()),
+ Err(_) => BStr::from_bytes(&entry.context_name),
+ };
+ let return_error_line: &dyn kernel::fmt::Display = match &entry.return_error_line {
+ Some(line) => line,
+ None => &0,
+ };
+ seq_print!(
+ m,
+ "{}: {} from {}:{} to {}:{} context {} node {} handle {} size {}:{} ret {}/{} l={}\n",
+ entry.debug_id,
+ call_type,
+ entry.from_proc,
+ entry.from_thread,
+ entry.to_proc,
+ entry.to_thread,
+ ctx_name,
+ entry.to_node,
+ entry.target_handle,
+ entry.data_size,
+ entry.offsets_size,
+ entry.return_error,
+ entry.return_error_param,
+ return_error_line,
+ );
+ }
+ }
+}
+
#[derive(Zeroable)]
pub(crate) struct TransactionInfo {
pub(crate) from_pid: Pid,
pub(crate) from_tid: Pid,
pub(crate) to_pid: Pid,
pub(crate) to_tid: Pid,
+ pub(crate) to_node_debug_id: usize,
pub(crate) code: u32,
pub(crate) flags: TransactionFlags,
pub(crate) data_ptr: UserPtr,
@@ -68,8 +258,9 @@ pub(crate) struct TransactionInfo {
pub(crate) offsets_size: usize,
pub(crate) buffers_size: usize,
pub(crate) target_handle: u32,
- pub(crate) errno: i32,
+ pub(crate) errno: Option<Error>,
pub(crate) reply: u32,
+ pub(crate) error_line: Option<ErrorLocation>,
pub(crate) oneway_spam_suspect: bool,
pub(crate) is_reply: bool,
pub(crate) debug_id: usize,
@@ -81,6 +272,14 @@ impl TransactionInfo {
self.flags.is_oneway()
}
+ pub(crate) fn write_log(&self, ctx: &crate::Context) {
+ let entry = TransactionLogEntry::new(self, ctx);
+ TRANSACTION_LOG.add(&entry);
+ if self.reply != 0 && self.reply != BR_TRANSACTION_PENDING_FROZEN {
+ FAILED_TRANSACTION_LOG.add(&entry);
+ }
+ }
+
pub(crate) fn report_netlink(&self, reply: u32, ctx: &crate::Context) {
if let Err(err) = self.report_netlink_inner(reply, ctx) {
pr_warn_ratelimited!(
@@ -166,21 +365,13 @@ impl Transaction {
let txn_security_ctx = node_ref.node.flags & FLAT_BINDER_FLAG_TXN_SECURITY_CTX != 0;
let mut txn_security_ctx_off = if txn_security_ctx { Some(0) } else { None };
let to = node_ref.node.owner.clone();
- let mut alloc = match from.copy_transaction_data(
+ let mut alloc = from.copy_transaction_data(
to.clone(),
info,
info.debug_id,
allow_fds,
txn_security_ctx_off.as_mut(),
- ) {
- Ok(alloc) => alloc,
- Err(err) => {
- if !err.is_dead() {
- pr_warn!("Failure in copy_transaction_data: {:?}", err);
- }
- return Err(err);
- }
- };
+ )?;
if info.is_oneway() {
if from_parent.is_some() {
pr_warn_ratelimited!("Oneway transaction should not be in a transaction stack.\n");
@@ -221,13 +412,7 @@ impl Transaction {
allow_fds: bool,
) -> BinderResult<DLArc<Self>> {
let mut alloc =
- match from.copy_transaction_data(to.clone(), info, info.debug_id, allow_fds, None) {
- Ok(alloc) => alloc,
- Err(err) => {
- pr_warn!("Failure in copy_transaction_data: {:?}", err);
- return Err(err);
- }
- };
+ from.copy_transaction_data(to.clone(), info, info.debug_id, allow_fds, None)?;
if info.flags.contains(TransactionFlag::ClearBuf) {
alloc.set_info_clear_on_drop();
}
@@ -355,6 +540,7 @@ impl Transaction {
if let Some(t_outdated) =
target_node.take_outdated_transaction(&self, &mut process_inner)
{
+ trace_transaction_update_buffer_release(t_outdated.debug_id);
let mut alloc_guard = t_outdated.allocation.lock();
if let Some(alloc) = (*alloc_guard).as_mut() {
// Take the oneway node to prevent `Allocation::drop` from calling
@@ -576,7 +762,7 @@ impl DeliverToRead for Transaction {
!self.flags.is_oneway()
}
- fn debug_print(&self, m: &SeqFile, _prefix: &str, tprefix: &str) -> Result<()> {
+ fn debug_print(&self, m: &SeqFile, _prefix: &str, tprefix: &str) -> Result {
self.debug_print_inner(m, tprefix);
Ok(())
}
diff --git a/drivers/android/binder_alloc.c b/drivers/android/binder_alloc.c
deleted file mode 100644
index d6eae0aa7085..000000000000
--- a/drivers/android/binder_alloc.c
+++ /dev/null
@@ -1,1398 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/* binder_alloc.c
- *
- * Android IPC Subsystem
- *
- * Copyright (C) 2007-2017 Google, Inc.
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/list.h>
-#include <linux/sched/mm.h>
-#include <linux/module.h>
-#include <linux/rtmutex.h>
-#include <linux/rbtree.h>
-#include <linux/seq_file.h>
-#include <linux/vmalloc.h>
-#include <linux/slab.h>
-#include <linux/sched.h>
-#include <linux/list_lru.h>
-#include <linux/ratelimit.h>
-#include <asm/cacheflush.h>
-#include <linux/uaccess.h>
-#include <linux/highmem.h>
-#include <linux/sizes.h>
-#include <kunit/visibility.h>
-#include "binder_alloc.h"
-#include "binder_trace.h"
-
-static struct list_lru binder_freelist;
-
-static DEFINE_MUTEX(binder_alloc_mmap_lock);
-
-enum {
- BINDER_DEBUG_USER_ERROR = 1U << 0,
- BINDER_DEBUG_OPEN_CLOSE = 1U << 1,
- BINDER_DEBUG_BUFFER_ALLOC = 1U << 2,
- BINDER_DEBUG_BUFFER_ALLOC_ASYNC = 1U << 3,
-};
-static uint32_t binder_alloc_debug_mask = BINDER_DEBUG_USER_ERROR;
-
-module_param_named(debug_mask, binder_alloc_debug_mask,
- uint, 0644);
-
-#define binder_alloc_debug(mask, x...) \
- do { \
- if (binder_alloc_debug_mask & mask) \
- pr_info_ratelimited(x); \
- } while (0)
-
-static struct binder_buffer *binder_buffer_next(struct binder_buffer *buffer)
-{
- return list_entry(buffer->entry.next, struct binder_buffer, entry);
-}
-
-static struct binder_buffer *binder_buffer_prev(struct binder_buffer *buffer)
-{
- return list_entry(buffer->entry.prev, struct binder_buffer, entry);
-}
-
-VISIBLE_IF_KUNIT size_t binder_alloc_buffer_size(struct binder_alloc *alloc,
- struct binder_buffer *buffer)
-{
- if (list_is_last(&buffer->entry, &alloc->buffers))
- return alloc->vm_start + alloc->buffer_size - buffer->user_data;
- return binder_buffer_next(buffer)->user_data - buffer->user_data;
-}
-EXPORT_SYMBOL_IF_KUNIT(binder_alloc_buffer_size);
-
-static void binder_insert_free_buffer(struct binder_alloc *alloc,
- struct binder_buffer *new_buffer)
-{
- struct rb_node **p = &alloc->free_buffers.rb_node;
- struct rb_node *parent = NULL;
- struct binder_buffer *buffer;
- size_t buffer_size;
- size_t new_buffer_size;
-
- BUG_ON(!new_buffer->free);
-
- new_buffer_size = binder_alloc_buffer_size(alloc, new_buffer);
-
- binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC,
- "%d: add free buffer, size %zd, at %p\n",
- alloc->pid, new_buffer_size, new_buffer);
-
- while (*p) {
- parent = *p;
- buffer = rb_entry(parent, struct binder_buffer, rb_node);
- BUG_ON(!buffer->free);
-
- buffer_size = binder_alloc_buffer_size(alloc, buffer);
-
- if (new_buffer_size < buffer_size)
- p = &parent->rb_left;
- else
- p = &parent->rb_right;
- }
- rb_link_node(&new_buffer->rb_node, parent, p);
- rb_insert_color(&new_buffer->rb_node, &alloc->free_buffers);
-}
-
-static void binder_insert_allocated_buffer_locked(
- struct binder_alloc *alloc, struct binder_buffer *new_buffer)
-{
- struct rb_node **p = &alloc->allocated_buffers.rb_node;
- struct rb_node *parent = NULL;
- struct binder_buffer *buffer;
-
- BUG_ON(new_buffer->free);
-
- while (*p) {
- parent = *p;
- buffer = rb_entry(parent, struct binder_buffer, rb_node);
- BUG_ON(buffer->free);
-
- if (new_buffer->user_data < buffer->user_data)
- p = &parent->rb_left;
- else if (new_buffer->user_data > buffer->user_data)
- p = &parent->rb_right;
- else
- BUG();
- }
- rb_link_node(&new_buffer->rb_node, parent, p);
- rb_insert_color(&new_buffer->rb_node, &alloc->allocated_buffers);
-}
-
-static struct binder_buffer *binder_alloc_prepare_to_free_locked(
- struct binder_alloc *alloc,
- unsigned long user_ptr)
-{
- struct rb_node *n = alloc->allocated_buffers.rb_node;
- struct binder_buffer *buffer;
-
- while (n) {
- buffer = rb_entry(n, struct binder_buffer, rb_node);
- BUG_ON(buffer->free);
-
- if (user_ptr < buffer->user_data) {
- n = n->rb_left;
- } else if (user_ptr > buffer->user_data) {
- n = n->rb_right;
- } else {
- /*
- * Guard against user threads attempting to
- * free the buffer when in use by kernel or
- * after it's already been freed.
- */
- if (!buffer->allow_user_free)
- return ERR_PTR(-EPERM);
- buffer->allow_user_free = 0;
- return buffer;
- }
- }
- return NULL;
-}
-
-/**
- * binder_alloc_prepare_to_free() - get buffer given user ptr
- * @alloc: binder_alloc for this proc
- * @user_ptr: User pointer to buffer data
- *
- * Validate userspace pointer to buffer data and return buffer corresponding to
- * that user pointer. Search the rb tree for buffer that matches user data
- * pointer.
- *
- * Return: Pointer to buffer or NULL
- */
-struct binder_buffer *binder_alloc_prepare_to_free(struct binder_alloc *alloc,
- unsigned long user_ptr)
-{
- guard(mutex)(&alloc->mutex);
- return binder_alloc_prepare_to_free_locked(alloc, user_ptr);
-}
-
-static inline void
-binder_set_installed_page(struct binder_alloc *alloc,
- unsigned long index,
- struct page *page)
-{
- /* Pairs with acquire in binder_get_installed_page() */
- smp_store_release(&alloc->pages[index], page);
-}
-
-static inline struct page *
-binder_get_installed_page(struct binder_alloc *alloc, unsigned long index)
-{
- /* Pairs with release in binder_set_installed_page() */
- return smp_load_acquire(&alloc->pages[index]);
-}
-
-static void binder_lru_freelist_add(struct binder_alloc *alloc,
- unsigned long start, unsigned long end)
-{
- unsigned long page_addr;
- struct page *page;
-
- trace_binder_update_page_range(alloc, false, start, end);
-
- for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
- size_t index;
- int ret;
-
- index = (page_addr - alloc->vm_start) / PAGE_SIZE;
- page = binder_get_installed_page(alloc, index);
- if (!page)
- continue;
-
- trace_binder_free_lru_start(alloc, index);
-
- ret = list_lru_add(alloc->freelist,
- page_to_lru(page),
- page_to_nid(page),
- NULL);
- WARN_ON(!ret);
-
- trace_binder_free_lru_end(alloc, index);
- }
-}
-
-static inline
-void binder_alloc_set_mapped(struct binder_alloc *alloc, bool state)
-{
- /* pairs with smp_load_acquire in binder_alloc_is_mapped() */
- smp_store_release(&alloc->mapped, state);
-}
-
-static inline bool binder_alloc_is_mapped(struct binder_alloc *alloc)
-{
- /* pairs with smp_store_release in binder_alloc_set_mapped() */
- return smp_load_acquire(&alloc->mapped);
-}
-
-static struct page *binder_page_lookup(struct binder_alloc *alloc,
- unsigned long addr)
-{
- struct mm_struct *mm = alloc->mm;
- struct page *page;
- long npages = 0;
-
- /*
- * Find an existing page in the remote mm. If missing,
- * don't attempt to fault-in just propagate an error.
- */
- mmap_read_lock(mm);
- if (binder_alloc_is_mapped(alloc))
- npages = get_user_pages_remote(mm, addr, 1, FOLL_NOFAULT,
- &page, NULL);
- mmap_read_unlock(mm);
-
- return npages > 0 ? page : NULL;
-}
-
-static int binder_page_insert(struct binder_alloc *alloc,
- unsigned long addr,
- struct page *page)
-{
- struct mm_struct *mm = alloc->mm;
- struct vm_area_struct *vma;
- int ret = -ESRCH;
-
- vma = vma_start_read_unlocked(mm, addr);
- if (!vma)
- return ret;
-
- if (binder_alloc_is_mapped(alloc))
- ret = vm_insert_page(vma, addr, page);
-
- vma_end_read(vma);
-
- return ret;
-}
-
-static struct page *binder_page_alloc(struct binder_alloc *alloc,
- unsigned long index)
-{
- struct binder_shrinker_mdata *mdata;
- struct page *page;
-
- page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
- if (!page)
- return NULL;
-
- /* allocate and install shrinker metadata under page->private */
- mdata = kzalloc_obj(*mdata);
- if (!mdata) {
- __free_page(page);
- return NULL;
- }
-
- mdata->alloc = alloc;
- mdata->page_index = index;
- INIT_LIST_HEAD(&mdata->lru);
- set_page_private(page, (unsigned long)mdata);
-
- return page;
-}
-
-static void binder_free_page(struct page *page)
-{
- kfree((struct binder_shrinker_mdata *)page_private(page));
- __free_page(page);
-}
-
-static int binder_install_single_page(struct binder_alloc *alloc,
- unsigned long index,
- unsigned long addr)
-{
- struct page *page;
- int ret;
-
- if (!mmget_not_zero(alloc->mm))
- return -ESRCH;
-
- page = binder_page_alloc(alloc, index);
- if (!page) {
- ret = -ENOMEM;
- goto out;
- }
-
- ret = binder_page_insert(alloc, addr, page);
- switch (ret) {
- case -EBUSY:
- /*
- * EBUSY is ok. Someone installed the pte first but the
- * alloc->pages[index] has not been updated yet. Discard
- * our page and look up the one already installed.
- */
- ret = 0;
- binder_free_page(page);
- page = binder_page_lookup(alloc, addr);
- if (!page) {
- pr_err("%d: failed to find page at offset %lx\n",
- alloc->pid, addr - alloc->vm_start);
- ret = -ESRCH;
- break;
- }
- fallthrough;
- case 0:
- /* Mark page installation complete and safe to use */
- binder_set_installed_page(alloc, index, page);
- break;
- default:
- binder_free_page(page);
- pr_err("%d: %s failed to insert page at offset %lx with %d\n",
- alloc->pid, __func__, addr - alloc->vm_start, ret);
- break;
- }
-out:
- mmput_async(alloc->mm);
- return ret;
-}
-
-static int binder_install_buffer_pages(struct binder_alloc *alloc,
- struct binder_buffer *buffer,
- size_t size)
-{
- unsigned long start, final;
- unsigned long page_addr;
-
- start = buffer->user_data & PAGE_MASK;
- final = PAGE_ALIGN(buffer->user_data + size);
-
- for (page_addr = start; page_addr < final; page_addr += PAGE_SIZE) {
- unsigned long index;
- int ret;
-
- index = (page_addr - alloc->vm_start) / PAGE_SIZE;
- if (binder_get_installed_page(alloc, index))
- continue;
-
- trace_binder_alloc_page_start(alloc, index);
-
- ret = binder_install_single_page(alloc, index, page_addr);
- if (ret)
- return ret;
-
- trace_binder_alloc_page_end(alloc, index);
- }
-
- return 0;
-}
-
-/* The range of pages should exclude those shared with other buffers */
-static void binder_lru_freelist_del(struct binder_alloc *alloc,
- unsigned long start, unsigned long end)
-{
- unsigned long page_addr;
- struct page *page;
-
- trace_binder_update_page_range(alloc, true, start, end);
-
- for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
- unsigned long index;
- bool on_lru;
-
- index = (page_addr - alloc->vm_start) / PAGE_SIZE;
- page = binder_get_installed_page(alloc, index);
-
- if (page) {
- trace_binder_alloc_lru_start(alloc, index);
-
- on_lru = list_lru_del(alloc->freelist,
- page_to_lru(page),
- page_to_nid(page),
- NULL);
- WARN_ON(!on_lru);
-
- trace_binder_alloc_lru_end(alloc, index);
- continue;
- }
-
- if (index + 1 > alloc->pages_high)
- alloc->pages_high = index + 1;
- }
-}
-
-static void debug_no_space_locked(struct binder_alloc *alloc)
-{
- size_t largest_alloc_size = 0;
- struct binder_buffer *buffer;
- size_t allocated_buffers = 0;
- size_t largest_free_size = 0;
- size_t total_alloc_size = 0;
- size_t total_free_size = 0;
- size_t free_buffers = 0;
- size_t buffer_size;
- struct rb_node *n;
-
- for (n = rb_first(&alloc->allocated_buffers); n; n = rb_next(n)) {
- buffer = rb_entry(n, struct binder_buffer, rb_node);
- buffer_size = binder_alloc_buffer_size(alloc, buffer);
- allocated_buffers++;
- total_alloc_size += buffer_size;
- if (buffer_size > largest_alloc_size)
- largest_alloc_size = buffer_size;
- }
-
- for (n = rb_first(&alloc->free_buffers); n; n = rb_next(n)) {
- buffer = rb_entry(n, struct binder_buffer, rb_node);
- buffer_size = binder_alloc_buffer_size(alloc, buffer);
- free_buffers++;
- total_free_size += buffer_size;
- if (buffer_size > largest_free_size)
- largest_free_size = buffer_size;
- }
-
- binder_alloc_debug(BINDER_DEBUG_USER_ERROR,
- "allocated: %zd (num: %zd largest: %zd), free: %zd (num: %zd largest: %zd)\n",
- total_alloc_size, allocated_buffers,
- largest_alloc_size, total_free_size,
- free_buffers, largest_free_size);
-}
-
-static bool debug_low_async_space_locked(struct binder_alloc *alloc)
-{
- /*
- * Find the amount and size of buffers allocated by the current caller;
- * The idea is that once we cross the threshold, whoever is responsible
- * for the low async space is likely to try to send another async txn,
- * and at some point we'll catch them in the act. This is more efficient
- * than keeping a map per pid.
- */
- struct binder_buffer *buffer;
- size_t total_alloc_size = 0;
- int pid = current->tgid;
- size_t num_buffers = 0;
- struct rb_node *n;
-
- /*
- * Only start detecting spammers once we have less than 20% of async
- * space left (which is less than 10% of total buffer size).
- */
- if (alloc->free_async_space >= alloc->buffer_size / 10) {
- alloc->oneway_spam_detected = false;
- return false;
- }
-
- for (n = rb_first(&alloc->allocated_buffers); n != NULL;
- n = rb_next(n)) {
- buffer = rb_entry(n, struct binder_buffer, rb_node);
- if (buffer->pid != pid)
- continue;
- if (!buffer->async_transaction)
- continue;
- total_alloc_size += binder_alloc_buffer_size(alloc, buffer);
- num_buffers++;
- }
-
- /*
- * Warn if this pid has more than 50 transactions, or more than 50% of
- * async space (which is 25% of total buffer size). Oneway spam is only
- * detected when the threshold is exceeded.
- */
- if (num_buffers > 50 || total_alloc_size > alloc->buffer_size / 4) {
- binder_alloc_debug(BINDER_DEBUG_USER_ERROR,
- "%d: pid %d spamming oneway? %zd buffers allocated for a total size of %zd\n",
- alloc->pid, pid, num_buffers, total_alloc_size);
- if (!alloc->oneway_spam_detected) {
- alloc->oneway_spam_detected = true;
- return true;
- }
- }
- return false;
-}
-
-/* Callers preallocate @new_buffer, it is freed by this function if unused */
-static struct binder_buffer *binder_alloc_new_buf_locked(
- struct binder_alloc *alloc,
- struct binder_buffer *new_buffer,
- size_t size,
- int is_async)
-{
- struct rb_node *n = alloc->free_buffers.rb_node;
- struct rb_node *best_fit = NULL;
- struct binder_buffer *buffer;
- unsigned long next_used_page;
- unsigned long curr_last_page;
- size_t buffer_size;
-
- if (is_async && alloc->free_async_space < size) {
- binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC,
- "%d: binder_alloc_buf size %zd failed, no async space left\n",
- alloc->pid, size);
- buffer = ERR_PTR(-ENOSPC);
- goto out;
- }
-
- while (n) {
- buffer = rb_entry(n, struct binder_buffer, rb_node);
- BUG_ON(!buffer->free);
- buffer_size = binder_alloc_buffer_size(alloc, buffer);
-
- if (size < buffer_size) {
- best_fit = n;
- n = n->rb_left;
- } else if (size > buffer_size) {
- n = n->rb_right;
- } else {
- best_fit = n;
- break;
- }
- }
-
- if (unlikely(!best_fit)) {
- binder_alloc_debug(BINDER_DEBUG_USER_ERROR,
- "%d: binder_alloc_buf size %zd failed, no address space\n",
- alloc->pid, size);
- debug_no_space_locked(alloc);
- buffer = ERR_PTR(-ENOSPC);
- goto out;
- }
-
- if (buffer_size != size) {
- /* Found an oversized buffer and needs to be split */
- buffer = rb_entry(best_fit, struct binder_buffer, rb_node);
- buffer_size = binder_alloc_buffer_size(alloc, buffer);
-
- WARN_ON(n || buffer_size == size);
- new_buffer->user_data = buffer->user_data + size;
- list_add(&new_buffer->entry, &buffer->entry);
- new_buffer->free = 1;
- binder_insert_free_buffer(alloc, new_buffer);
- new_buffer = NULL;
- }
-
- binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC,
- "%d: binder_alloc_buf size %zd got buffer %p size %zd\n",
- alloc->pid, size, buffer, buffer_size);
-
- /*
- * Now we remove the pages from the freelist. A clever calculation
- * with buffer_size determines if the last page is shared with an
- * adjacent in-use buffer. In such case, the page has been already
- * removed from the freelist so we trim our range short.
- */
- next_used_page = (buffer->user_data + buffer_size) & PAGE_MASK;
- curr_last_page = PAGE_ALIGN(buffer->user_data + size);
- binder_lru_freelist_del(alloc, PAGE_ALIGN(buffer->user_data),
- min(next_used_page, curr_last_page));
-
- rb_erase(&buffer->rb_node, &alloc->free_buffers);
- buffer->free = 0;
- buffer->allow_user_free = 0;
- binder_insert_allocated_buffer_locked(alloc, buffer);
- buffer->async_transaction = is_async;
- buffer->oneway_spam_suspect = false;
- if (is_async) {
- alloc->free_async_space -= size;
- binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC_ASYNC,
- "%d: binder_alloc_buf size %zd async free %zd\n",
- alloc->pid, size, alloc->free_async_space);
- if (debug_low_async_space_locked(alloc))
- buffer->oneway_spam_suspect = true;
- }
-
-out:
- /* Discard possibly unused new_buffer */
- kfree(new_buffer);
- return buffer;
-}
-
-/* Calculate the sanitized total size, returns 0 for invalid request */
-static inline size_t sanitized_size(size_t data_size,
- size_t offsets_size,
- size_t extra_buffers_size)
-{
- size_t total, tmp;
-
- /* Align to pointer size and check for overflows */
- tmp = ALIGN(data_size, sizeof(void *)) +
- ALIGN(offsets_size, sizeof(void *));
- if (tmp < data_size || tmp < offsets_size)
- return 0;
- total = tmp + ALIGN(extra_buffers_size, sizeof(void *));
- if (total < tmp || total < extra_buffers_size)
- return 0;
-
- /* Pad 0-sized buffers so they get a unique address */
- total = max(total, sizeof(void *));
-
- return total;
-}
-
-/**
- * binder_alloc_new_buf() - Allocate a new binder buffer
- * @alloc: binder_alloc for this proc
- * @data_size: size of user data buffer
- * @offsets_size: user specified buffer offset
- * @extra_buffers_size: size of extra space for meta-data (eg, security context)
- * @is_async: buffer for async transaction
- *
- * Allocate a new buffer given the requested sizes. Returns
- * the kernel version of the buffer pointer. The size allocated
- * is the sum of the three given sizes (each rounded up to
- * pointer-sized boundary)
- *
- * Return: The allocated buffer or %ERR_PTR(-errno) if error
- */
-struct binder_buffer *binder_alloc_new_buf(struct binder_alloc *alloc,
- size_t data_size,
- size_t offsets_size,
- size_t extra_buffers_size,
- int is_async)
-{
- struct binder_buffer *buffer, *next;
- size_t size;
- int ret;
-
- /* Check binder_alloc is fully initialized */
- if (!binder_alloc_is_mapped(alloc)) {
- binder_alloc_debug(BINDER_DEBUG_USER_ERROR,
- "%d: binder_alloc_buf, no vma\n",
- alloc->pid);
- return ERR_PTR(-ESRCH);
- }
-
- size = sanitized_size(data_size, offsets_size, extra_buffers_size);
- if (unlikely(!size)) {
- binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC,
- "%d: got transaction with invalid size %zd-%zd-%zd\n",
- alloc->pid, data_size, offsets_size,
- extra_buffers_size);
- return ERR_PTR(-EINVAL);
- }
-
- /* Preallocate the next buffer */
- next = kzalloc_obj(*next);
- if (!next)
- return ERR_PTR(-ENOMEM);
-
- mutex_lock(&alloc->mutex);
- buffer = binder_alloc_new_buf_locked(alloc, next, size, is_async);
- if (IS_ERR(buffer)) {
- mutex_unlock(&alloc->mutex);
- goto out;
- }
-
- buffer->data_size = data_size;
- buffer->offsets_size = offsets_size;
- buffer->extra_buffers_size = extra_buffers_size;
- buffer->pid = current->tgid;
- mutex_unlock(&alloc->mutex);
-
- ret = binder_install_buffer_pages(alloc, buffer, size);
- if (ret) {
- binder_alloc_free_buf(alloc, buffer);
- buffer = ERR_PTR(ret);
- }
-out:
- return buffer;
-}
-EXPORT_SYMBOL_IF_KUNIT(binder_alloc_new_buf);
-
-static unsigned long buffer_start_page(struct binder_buffer *buffer)
-{
- return buffer->user_data & PAGE_MASK;
-}
-
-static unsigned long prev_buffer_end_page(struct binder_buffer *buffer)
-{
- return (buffer->user_data - 1) & PAGE_MASK;
-}
-
-static void binder_delete_free_buffer(struct binder_alloc *alloc,
- struct binder_buffer *buffer)
-{
- struct binder_buffer *prev, *next;
-
- if (PAGE_ALIGNED(buffer->user_data))
- goto skip_freelist;
-
- BUG_ON(alloc->buffers.next == &buffer->entry);
- prev = binder_buffer_prev(buffer);
- BUG_ON(!prev->free);
- if (prev_buffer_end_page(prev) == buffer_start_page(buffer))
- goto skip_freelist;
-
- if (!list_is_last(&buffer->entry, &alloc->buffers)) {
- next = binder_buffer_next(buffer);
- if (buffer_start_page(next) == buffer_start_page(buffer))
- goto skip_freelist;
- }
-
- binder_lru_freelist_add(alloc, buffer_start_page(buffer),
- buffer_start_page(buffer) + PAGE_SIZE);
-skip_freelist:
- list_del(&buffer->entry);
- kfree(buffer);
-}
-
-static void binder_free_buf_locked(struct binder_alloc *alloc,
- struct binder_buffer *buffer)
-{
- size_t size, buffer_size;
-
- buffer_size = binder_alloc_buffer_size(alloc, buffer);
-
- size = ALIGN(buffer->data_size, sizeof(void *)) +
- ALIGN(buffer->offsets_size, sizeof(void *)) +
- ALIGN(buffer->extra_buffers_size, sizeof(void *));
-
- binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC,
- "%d: binder_free_buf %p size %zd buffer_size %zd\n",
- alloc->pid, buffer, size, buffer_size);
-
- BUG_ON(buffer->free);
- BUG_ON(size > buffer_size);
- BUG_ON(buffer->transaction != NULL);
- BUG_ON(buffer->user_data < alloc->vm_start);
- BUG_ON(buffer->user_data > alloc->vm_start + alloc->buffer_size);
-
- if (buffer->async_transaction) {
- alloc->free_async_space += buffer_size;
- binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC_ASYNC,
- "%d: binder_free_buf size %zd async free %zd\n",
- alloc->pid, size, alloc->free_async_space);
- }
-
- binder_lru_freelist_add(alloc, PAGE_ALIGN(buffer->user_data),
- (buffer->user_data + buffer_size) & PAGE_MASK);
-
- rb_erase(&buffer->rb_node, &alloc->allocated_buffers);
- buffer->free = 1;
- if (!list_is_last(&buffer->entry, &alloc->buffers)) {
- struct binder_buffer *next = binder_buffer_next(buffer);
-
- if (next->free) {
- rb_erase(&next->rb_node, &alloc->free_buffers);
- binder_delete_free_buffer(alloc, next);
- }
- }
- if (alloc->buffers.next != &buffer->entry) {
- struct binder_buffer *prev = binder_buffer_prev(buffer);
-
- if (prev->free) {
- binder_delete_free_buffer(alloc, buffer);
- rb_erase(&prev->rb_node, &alloc->free_buffers);
- buffer = prev;
- }
- }
- binder_insert_free_buffer(alloc, buffer);
-}
-
-/**
- * binder_alloc_get_page() - get kernel pointer for given buffer offset
- * @alloc: binder_alloc for this proc
- * @buffer: binder buffer to be accessed
- * @buffer_offset: offset into @buffer data
- * @pgoffp: address to copy final page offset to
- *
- * Lookup the struct page corresponding to the address
- * at @buffer_offset into @buffer->user_data. If @pgoffp is not
- * NULL, the byte-offset into the page is written there.
- *
- * The caller is responsible to ensure that the offset points
- * to a valid address within the @buffer and that @buffer is
- * not freeable by the user. Since it can't be freed, we are
- * guaranteed that the corresponding elements of @alloc->pages[]
- * cannot change.
- *
- * Return: struct page
- */
-static struct page *binder_alloc_get_page(struct binder_alloc *alloc,
- struct binder_buffer *buffer,
- binder_size_t buffer_offset,
- pgoff_t *pgoffp)
-{
- binder_size_t buffer_space_offset = buffer_offset +
- (buffer->user_data - alloc->vm_start);
- pgoff_t pgoff = buffer_space_offset & ~PAGE_MASK;
- size_t index = buffer_space_offset >> PAGE_SHIFT;
-
- *pgoffp = pgoff;
-
- return alloc->pages[index];
-}
-
-/**
- * binder_alloc_clear_buf() - zero out buffer
- * @alloc: binder_alloc for this proc
- * @buffer: binder buffer to be cleared
- *
- * memset the given buffer to 0
- */
-static void binder_alloc_clear_buf(struct binder_alloc *alloc,
- struct binder_buffer *buffer)
-{
- size_t bytes = binder_alloc_buffer_size(alloc, buffer);
- binder_size_t buffer_offset = 0;
-
- while (bytes) {
- unsigned long size;
- struct page *page;
- pgoff_t pgoff;
-
- page = binder_alloc_get_page(alloc, buffer,
- buffer_offset, &pgoff);
- size = min_t(size_t, bytes, PAGE_SIZE - pgoff);
- memset_page(page, pgoff, 0, size);
- bytes -= size;
- buffer_offset += size;
- }
-}
-
-/**
- * binder_alloc_free_buf() - free a binder buffer
- * @alloc: binder_alloc for this proc
- * @buffer: kernel pointer to buffer
- *
- * Free the buffer allocated via binder_alloc_new_buf()
- */
-void binder_alloc_free_buf(struct binder_alloc *alloc,
- struct binder_buffer *buffer)
-{
- /*
- * We could eliminate the call to binder_alloc_clear_buf()
- * from binder_alloc_deferred_release() by moving this to
- * binder_free_buf_locked(). However, that could
- * increase contention for the alloc mutex if clear_on_free
- * is used frequently for large buffers. The mutex is not
- * needed for correctness here.
- */
- if (buffer->clear_on_free) {
- binder_alloc_clear_buf(alloc, buffer);
- buffer->clear_on_free = false;
- }
- mutex_lock(&alloc->mutex);
- binder_free_buf_locked(alloc, buffer);
- mutex_unlock(&alloc->mutex);
-}
-EXPORT_SYMBOL_IF_KUNIT(binder_alloc_free_buf);
-
-/**
- * binder_alloc_mmap_handler() - map virtual address space for proc
- * @alloc: alloc structure for this proc
- * @vma: vma passed to mmap()
- *
- * Called by binder_mmap() to initialize the space specified in
- * vma for allocating binder buffers
- *
- * Return:
- * 0 = success
- * -EBUSY = address space already mapped
- * -ENOMEM = failed to map memory to given address space
- */
-int binder_alloc_mmap_handler(struct binder_alloc *alloc,
- struct vm_area_struct *vma)
-{
- struct binder_buffer *buffer;
- const char *failure_string;
- int ret;
-
- if (unlikely(vma->vm_mm != alloc->mm)) {
- ret = -EINVAL;
- failure_string = "invalid vma->vm_mm";
- goto err_invalid_mm;
- }
-
- mutex_lock(&binder_alloc_mmap_lock);
- if (alloc->buffer_size) {
- ret = -EBUSY;
- failure_string = "already mapped";
- goto err_already_mapped;
- }
- alloc->buffer_size = min_t(unsigned long, vma->vm_end - vma->vm_start,
- SZ_4M);
- mutex_unlock(&binder_alloc_mmap_lock);
-
- alloc->vm_start = vma->vm_start;
-
- alloc->pages = kvzalloc_objs(alloc->pages[0],
- alloc->buffer_size / PAGE_SIZE);
- if (!alloc->pages) {
- ret = -ENOMEM;
- failure_string = "alloc page array";
- goto err_alloc_pages_failed;
- }
-
- buffer = kzalloc_obj(*buffer);
- if (!buffer) {
- ret = -ENOMEM;
- failure_string = "alloc buffer struct";
- goto err_alloc_buf_struct_failed;
- }
-
- buffer->user_data = alloc->vm_start;
- list_add(&buffer->entry, &alloc->buffers);
- buffer->free = 1;
- binder_insert_free_buffer(alloc, buffer);
- alloc->free_async_space = alloc->buffer_size / 2;
-
- /* Signal binder_alloc is fully initialized */
- binder_alloc_set_mapped(alloc, true);
-
- return 0;
-
-err_alloc_buf_struct_failed:
- kvfree(alloc->pages);
- alloc->pages = NULL;
-err_alloc_pages_failed:
- alloc->vm_start = 0;
- mutex_lock(&binder_alloc_mmap_lock);
- alloc->buffer_size = 0;
-err_already_mapped:
- mutex_unlock(&binder_alloc_mmap_lock);
-err_invalid_mm:
- binder_alloc_debug(BINDER_DEBUG_USER_ERROR,
- "%s: %d %lx-%lx %s failed %d\n", __func__,
- alloc->pid, vma->vm_start, vma->vm_end,
- failure_string, ret);
- return ret;
-}
-EXPORT_SYMBOL_IF_KUNIT(binder_alloc_mmap_handler);
-
-void binder_alloc_deferred_release(struct binder_alloc *alloc)
-{
- struct rb_node *n;
- int buffers, page_count;
- struct binder_buffer *buffer;
-
- buffers = 0;
- mutex_lock(&alloc->mutex);
- BUG_ON(alloc->mapped);
-
- while ((n = rb_first(&alloc->allocated_buffers))) {
- buffer = rb_entry(n, struct binder_buffer, rb_node);
-
- /* Transaction should already have been freed */
- BUG_ON(buffer->transaction);
-
- if (buffer->clear_on_free) {
- binder_alloc_clear_buf(alloc, buffer);
- buffer->clear_on_free = false;
- }
- binder_free_buf_locked(alloc, buffer);
- buffers++;
- }
-
- while (!list_empty(&alloc->buffers)) {
- buffer = list_first_entry(&alloc->buffers,
- struct binder_buffer, entry);
- WARN_ON(!buffer->free);
-
- list_del(&buffer->entry);
- WARN_ON_ONCE(!list_empty(&alloc->buffers));
- kfree(buffer);
- }
-
- page_count = 0;
- if (alloc->pages) {
- int i;
-
- for (i = 0; i < alloc->buffer_size / PAGE_SIZE; i++) {
- struct page *page;
- bool on_lru;
-
- page = binder_get_installed_page(alloc, i);
- if (!page)
- continue;
-
- on_lru = list_lru_del(alloc->freelist,
- page_to_lru(page),
- page_to_nid(page),
- NULL);
- binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC,
- "%s: %d: page %d %s\n",
- __func__, alloc->pid, i,
- on_lru ? "on lru" : "active");
- binder_free_page(page);
- page_count++;
- }
- }
- mutex_unlock(&alloc->mutex);
- kvfree(alloc->pages);
- if (alloc->mm)
- mmdrop(alloc->mm);
-
- binder_alloc_debug(BINDER_DEBUG_OPEN_CLOSE,
- "%s: %d buffers %d, pages %d\n",
- __func__, alloc->pid, buffers, page_count);
-}
-EXPORT_SYMBOL_IF_KUNIT(binder_alloc_deferred_release);
-
-/**
- * binder_alloc_print_allocated() - print buffer info
- * @m: seq_file for output via seq_printf()
- * @alloc: binder_alloc for this proc
- *
- * Prints information about every buffer associated with
- * the binder_alloc state to the given seq_file
- */
-void binder_alloc_print_allocated(struct seq_file *m,
- struct binder_alloc *alloc)
-{
- struct binder_buffer *buffer;
- struct rb_node *n;
-
- guard(mutex)(&alloc->mutex);
- for (n = rb_first(&alloc->allocated_buffers); n; n = rb_next(n)) {
- buffer = rb_entry(n, struct binder_buffer, rb_node);
- seq_printf(m, " buffer %d: %lx size %zd:%zd:%zd %s\n",
- buffer->debug_id,
- buffer->user_data - alloc->vm_start,
- buffer->data_size, buffer->offsets_size,
- buffer->extra_buffers_size,
- buffer->transaction ? "active" : "delivered");
- }
-}
-
-/**
- * binder_alloc_print_pages() - print page usage
- * @m: seq_file for output via seq_printf()
- * @alloc: binder_alloc for this proc
- */
-void binder_alloc_print_pages(struct seq_file *m,
- struct binder_alloc *alloc)
-{
- struct page *page;
- int i;
- int active = 0;
- int lru = 0;
- int free = 0;
-
- mutex_lock(&alloc->mutex);
- /*
- * Make sure the binder_alloc is fully initialized, otherwise we might
- * read inconsistent state.
- */
- if (binder_alloc_is_mapped(alloc)) {
- for (i = 0; i < alloc->buffer_size / PAGE_SIZE; i++) {
- page = binder_get_installed_page(alloc, i);
- if (!page)
- free++;
- else if (list_empty(page_to_lru(page)))
- active++;
- else
- lru++;
- }
- }
- mutex_unlock(&alloc->mutex);
- seq_printf(m, " pages: %d:%d:%d\n", active, lru, free);
- seq_printf(m, " pages high watermark: %zu\n", alloc->pages_high);
-}
-
-/**
- * binder_alloc_get_allocated_count() - return count of buffers
- * @alloc: binder_alloc for this proc
- *
- * Return: count of allocated buffers
- */
-int binder_alloc_get_allocated_count(struct binder_alloc *alloc)
-{
- struct rb_node *n;
- int count = 0;
-
- guard(mutex)(&alloc->mutex);
- for (n = rb_first(&alloc->allocated_buffers); n != NULL; n = rb_next(n))
- count++;
- return count;
-}
-
-
-/**
- * binder_alloc_vma_close() - invalidate address space
- * @alloc: binder_alloc for this proc
- *
- * Called from binder_vma_close() when releasing address space.
- * Clears alloc->mapped to prevent new incoming transactions from
- * allocating more buffers.
- */
-void binder_alloc_vma_close(struct binder_alloc *alloc)
-{
- binder_alloc_set_mapped(alloc, false);
-}
-EXPORT_SYMBOL_IF_KUNIT(binder_alloc_vma_close);
-
-/**
- * binder_alloc_free_page() - shrinker callback to free pages
- * @item: item to free
- * @lru: list_lru instance of the item
- * @cb_arg: callback argument
- *
- * Called from list_lru_walk() in binder_shrink_scan() to free
- * up pages when the system is under memory pressure.
- */
-enum lru_status binder_alloc_free_page(struct list_head *item,
- struct list_lru_one *lru,
- void *cb_arg)
- __must_hold(&lru->lock)
-{
- struct binder_shrinker_mdata *mdata = container_of(item, typeof(*mdata), lru);
- struct binder_alloc *alloc = mdata->alloc;
- struct mm_struct *mm = alloc->mm;
- struct vm_area_struct *vma;
- struct page *page_to_free;
- unsigned long page_addr;
- size_t index;
-
- if (!mmget_not_zero(mm))
- goto err_mmget;
-
- index = mdata->page_index;
- page_addr = alloc->vm_start + index * PAGE_SIZE;
-
- /*
- * Attempt per-vma lock. This is essentially a
- * "trylock". It can fail even if the VMA exists
- * for 'page_addr'.
- */
- vma = lock_vma_under_rcu(mm, page_addr);
- if (!vma) {
- /*
- * If the vma exists, we can't continue because we cannot
- * remove the page from the vma. However, if the vma was
- * unmapped, it's okay to continue.
- */
- if (binder_alloc_is_mapped(alloc))
- goto err_vma_lock_failed;
- }
-
- if (!mutex_trylock(&alloc->mutex))
- goto err_get_alloc_mutex_failed;
-
- trace_binder_unmap_kernel_start(alloc, index);
-
- page_to_free = alloc->pages[index];
- binder_set_installed_page(alloc, index, NULL);
-
- trace_binder_unmap_kernel_end(alloc, index);
-
- list_lru_isolate(lru, item);
- spin_unlock(&lru->lock);
-
- /*
- * Since a binder_alloc can only be mapped once, we ensure
- * the vma corresponds to this mapping by checking whether
- * the binder_alloc is still mapped.
- */
- if (vma && binder_alloc_is_mapped(alloc)) {
- trace_binder_unmap_user_start(alloc, index);
-
- zap_vma_range(vma, page_addr, PAGE_SIZE);
-
- trace_binder_unmap_user_end(alloc, index);
- }
-
- mutex_unlock(&alloc->mutex);
- if (vma)
- vma_end_read(vma);
- mmput_async(mm);
- binder_free_page(page_to_free);
-
- return LRU_REMOVED_RETRY;
-
-err_get_alloc_mutex_failed:
- if (vma)
- vma_end_read(vma);
-err_vma_lock_failed:
- mmput_async(mm);
-err_mmget:
- return LRU_SKIP;
-}
-EXPORT_SYMBOL_IF_KUNIT(binder_alloc_free_page);
-
-static unsigned long
-binder_shrink_count(struct shrinker *shrink, struct shrink_control *sc)
-{
- return list_lru_count(&binder_freelist);
-}
-
-static unsigned long
-binder_shrink_scan(struct shrinker *shrink, struct shrink_control *sc)
-{
- return list_lru_walk(&binder_freelist, binder_alloc_free_page,
- NULL, sc->nr_to_scan);
-}
-
-static struct shrinker *binder_shrinker;
-
-VISIBLE_IF_KUNIT void __binder_alloc_init(struct binder_alloc *alloc,
- struct list_lru *freelist)
-{
- alloc->pid = current->tgid;
- alloc->mm = current->mm;
- mmgrab(alloc->mm);
- mutex_init(&alloc->mutex);
- INIT_LIST_HEAD(&alloc->buffers);
- alloc->freelist = freelist;
-}
-EXPORT_SYMBOL_IF_KUNIT(__binder_alloc_init);
-
-/**
- * binder_alloc_init() - called by binder_open() for per-proc initialization
- * @alloc: binder_alloc for this proc
- *
- * Called from binder_open() to initialize binder_alloc fields for
- * new binder proc
- */
-void binder_alloc_init(struct binder_alloc *alloc)
-{
- __binder_alloc_init(alloc, &binder_freelist);
-}
-
-int binder_alloc_shrinker_init(void)
-{
- int ret;
-
- ret = list_lru_init(&binder_freelist);
- if (ret)
- return ret;
-
- binder_shrinker = shrinker_alloc(0, "android-binder");
- if (!binder_shrinker) {
- list_lru_destroy(&binder_freelist);
- return -ENOMEM;
- }
-
- binder_shrinker->count_objects = binder_shrink_count;
- binder_shrinker->scan_objects = binder_shrink_scan;
-
- shrinker_register(binder_shrinker);
-
- return 0;
-}
-
-void binder_alloc_shrinker_exit(void)
-{
- shrinker_free(binder_shrinker);
- list_lru_destroy(&binder_freelist);
-}
-
-/**
- * check_buffer() - verify that buffer/offset is safe to access
- * @alloc: binder_alloc for this proc
- * @buffer: binder buffer to be accessed
- * @offset: offset into @buffer data
- * @bytes: bytes to access from offset
- *
- * Check that the @offset/@bytes are within the size of the given
- * @buffer and that the buffer is currently active and not freeable.
- * Offsets must also be multiples of sizeof(u32). The kernel is
- * allowed to touch the buffer in two cases:
- *
- * 1) when the buffer is being created:
- * (buffer->free == 0 && buffer->allow_user_free == 0)
- * 2) when the buffer is being torn down:
- * (buffer->free == 0 && buffer->transaction == NULL).
- *
- * Return: true if the buffer is safe to access
- */
-static inline bool check_buffer(struct binder_alloc *alloc,
- struct binder_buffer *buffer,
- binder_size_t offset, size_t bytes)
-{
- size_t buffer_size = binder_alloc_buffer_size(alloc, buffer);
-
- return buffer_size >= bytes &&
- offset <= buffer_size - bytes &&
- IS_ALIGNED(offset, sizeof(u32)) &&
- !buffer->free &&
- (!buffer->allow_user_free || !buffer->transaction);
-}
-
-/**
- * binder_alloc_copy_user_to_buffer() - copy src user to tgt user
- * @alloc: binder_alloc for this proc
- * @buffer: binder buffer to be accessed
- * @buffer_offset: offset into @buffer data
- * @from: userspace pointer to source buffer
- * @bytes: bytes to copy
- *
- * Copy bytes from source userspace to target buffer.
- *
- * Return: bytes remaining to be copied
- */
-unsigned long
-binder_alloc_copy_user_to_buffer(struct binder_alloc *alloc,
- struct binder_buffer *buffer,
- binder_size_t buffer_offset,
- const void __user *from,
- size_t bytes)
-{
- if (!check_buffer(alloc, buffer, buffer_offset, bytes))
- return bytes;
-
- while (bytes) {
- unsigned long size;
- unsigned long ret;
- struct page *page;
- pgoff_t pgoff;
- void *kptr;
-
- page = binder_alloc_get_page(alloc, buffer,
- buffer_offset, &pgoff);
- size = min_t(size_t, bytes, PAGE_SIZE - pgoff);
- kptr = kmap_local_page(page) + pgoff;
- ret = copy_from_user(kptr, from, size);
- kunmap_local(kptr);
- if (ret)
- return bytes - size + ret;
- bytes -= size;
- from += size;
- buffer_offset += size;
- }
- return 0;
-}
-
-static int binder_alloc_do_buffer_copy(struct binder_alloc *alloc,
- bool to_buffer,
- struct binder_buffer *buffer,
- binder_size_t buffer_offset,
- void *ptr,
- size_t bytes)
-{
- /* All copies must be 32-bit aligned and 32-bit size */
- if (!check_buffer(alloc, buffer, buffer_offset, bytes))
- return -EINVAL;
-
- while (bytes) {
- unsigned long size;
- struct page *page;
- pgoff_t pgoff;
-
- page = binder_alloc_get_page(alloc, buffer,
- buffer_offset, &pgoff);
- size = min_t(size_t, bytes, PAGE_SIZE - pgoff);
- if (to_buffer)
- memcpy_to_page(page, pgoff, ptr, size);
- else
- memcpy_from_page(ptr, page, pgoff, size);
- bytes -= size;
- pgoff = 0;
- ptr = ptr + size;
- buffer_offset += size;
- }
- return 0;
-}
-
-int binder_alloc_copy_to_buffer(struct binder_alloc *alloc,
- struct binder_buffer *buffer,
- binder_size_t buffer_offset,
- void *src,
- size_t bytes)
-{
- return binder_alloc_do_buffer_copy(alloc, true, buffer, buffer_offset,
- src, bytes);
-}
-
-int binder_alloc_copy_from_buffer(struct binder_alloc *alloc,
- void *dest,
- struct binder_buffer *buffer,
- binder_size_t buffer_offset,
- size_t bytes)
-{
- return binder_alloc_do_buffer_copy(alloc, false, buffer, buffer_offset,
- dest, bytes);
-}
diff --git a/drivers/android/binder_alloc.h b/drivers/android/binder_alloc.h
deleted file mode 100644
index d6f1f6f2d00e..000000000000
--- a/drivers/android/binder_alloc.h
+++ /dev/null
@@ -1,189 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * Copyright (C) 2017 Google, Inc.
- */
-
-#ifndef _LINUX_BINDER_ALLOC_H
-#define _LINUX_BINDER_ALLOC_H
-
-#include <linux/rbtree.h>
-#include <linux/list.h>
-#include <linux/mm.h>
-#include <linux/rtmutex.h>
-#include <linux/vmalloc.h>
-#include <linux/slab.h>
-#include <linux/list_lru.h>
-#include <uapi/linux/android/binder.h>
-
-struct binder_transaction;
-
-/**
- * struct binder_buffer - buffer used for binder transactions
- * @entry: entry alloc->buffers
- * @rb_node: node for allocated_buffers/free_buffers rb trees
- * @free: %true if buffer is free
- * @clear_on_free: %true if buffer must be zeroed after use
- * @allow_user_free: %true if user is allowed to free buffer
- * @async_transaction: %true if buffer is in use for an async txn
- * @oneway_spam_suspect: %true if total async allocate size just exceed
- * spamming detect threshold
- * @debug_id: unique ID for debugging
- * @transaction: pointer to associated struct binder_transaction
- * @target_node: struct binder_node associated with this buffer
- * @data_size: size of @transaction data
- * @offsets_size: size of array of offsets
- * @extra_buffers_size: size of space for other objects (like sg lists)
- * @user_data: user pointer to base of buffer space
- * @pid: pid to attribute the buffer to (caller)
- *
- * Bookkeeping structure for binder transaction buffers
- */
-struct binder_buffer {
- struct list_head entry; /* free and allocated entries by address */
- struct rb_node rb_node; /* free entry by size or allocated entry */
- /* by address */
- unsigned free:1;
- unsigned clear_on_free:1;
- unsigned allow_user_free:1;
- unsigned async_transaction:1;
- unsigned oneway_spam_suspect:1;
- unsigned debug_id:27;
- struct binder_transaction *transaction;
- struct binder_node *target_node;
- size_t data_size;
- size_t offsets_size;
- size_t extra_buffers_size;
- unsigned long user_data;
- int pid;
-};
-
-/**
- * struct binder_shrinker_mdata - binder metadata used to reclaim pages
- * @lru: LRU entry in binder_freelist
- * @alloc: binder_alloc owning the page to reclaim
- * @page_index: offset in @alloc->pages[] into the page to reclaim
- */
-struct binder_shrinker_mdata {
- struct list_head lru;
- struct binder_alloc *alloc;
- unsigned long page_index;
-};
-
-static inline struct list_head *page_to_lru(struct page *p)
-{
- struct binder_shrinker_mdata *mdata;
-
- mdata = (struct binder_shrinker_mdata *)page_private(p);
-
- return &mdata->lru;
-}
-
-/**
- * struct binder_alloc - per-binder proc state for binder allocator
- * @mutex: protects binder_alloc fields
- * @mm: copy of task->mm (invariant after open)
- * @vm_start: base of per-proc address space mapped via mmap
- * @buffers: list of all buffers for this proc
- * @free_buffers: rb tree of buffers available for allocation
- * sorted by size
- * @allocated_buffers: rb tree of allocated buffers sorted by address
- * @free_async_space: VA space available for async buffers. This is
- * initialized at mmap time to 1/2 the full VA space
- * @pages: array of struct page *
- * @freelist: lru list to use for free pages (invariant after init)
- * @buffer_size: size of address space specified via mmap
- * @pid: pid for associated binder_proc (invariant after init)
- * @pages_high: high watermark of offset in @pages
- * @mapped: whether the vm area is mapped, each binder instance is
- * allowed a single mapping throughout its lifetime
- * @oneway_spam_detected: %true if oneway spam detection fired, clear that
- * flag once the async buffer has returned to a healthy state
- *
- * Bookkeeping structure for per-proc address space management for binder
- * buffers. It is normally initialized during binder_init() and binder_mmap()
- * calls. The address space is used for both user-visible buffers and for
- * struct binder_buffer objects used to track the user buffers
- */
-struct binder_alloc {
- struct mutex mutex;
- struct mm_struct *mm;
- unsigned long vm_start;
- struct list_head buffers;
- struct rb_root free_buffers;
- struct rb_root allocated_buffers;
- size_t free_async_space;
- struct page **pages;
- struct list_lru *freelist;
- size_t buffer_size;
- int pid;
- size_t pages_high;
- bool mapped;
- bool oneway_spam_detected;
-};
-
-enum lru_status binder_alloc_free_page(struct list_head *item,
- struct list_lru_one *lru,
- void *cb_arg);
-struct binder_buffer *binder_alloc_new_buf(struct binder_alloc *alloc,
- size_t data_size,
- size_t offsets_size,
- size_t extra_buffers_size,
- int is_async);
-void binder_alloc_init(struct binder_alloc *alloc);
-int binder_alloc_shrinker_init(void);
-void binder_alloc_shrinker_exit(void);
-void binder_alloc_vma_close(struct binder_alloc *alloc);
-struct binder_buffer *
-binder_alloc_prepare_to_free(struct binder_alloc *alloc,
- unsigned long user_ptr);
-void binder_alloc_free_buf(struct binder_alloc *alloc,
- struct binder_buffer *buffer);
-int binder_alloc_mmap_handler(struct binder_alloc *alloc,
- struct vm_area_struct *vma);
-void binder_alloc_deferred_release(struct binder_alloc *alloc);
-int binder_alloc_get_allocated_count(struct binder_alloc *alloc);
-void binder_alloc_print_allocated(struct seq_file *m,
- struct binder_alloc *alloc);
-void binder_alloc_print_pages(struct seq_file *m,
- struct binder_alloc *alloc);
-
-/**
- * binder_alloc_get_free_async_space() - get free space available for async
- * @alloc: binder_alloc for this proc
- *
- * Return: the bytes remaining in the address-space for async transactions
- */
-static inline size_t
-binder_alloc_get_free_async_space(struct binder_alloc *alloc)
-{
- guard(mutex)(&alloc->mutex);
- return alloc->free_async_space;
-}
-
-unsigned long
-binder_alloc_copy_user_to_buffer(struct binder_alloc *alloc,
- struct binder_buffer *buffer,
- binder_size_t buffer_offset,
- const void __user *from,
- size_t bytes);
-
-int binder_alloc_copy_to_buffer(struct binder_alloc *alloc,
- struct binder_buffer *buffer,
- binder_size_t buffer_offset,
- void *src,
- size_t bytes);
-
-int binder_alloc_copy_from_buffer(struct binder_alloc *alloc,
- void *dest,
- struct binder_buffer *buffer,
- binder_size_t buffer_offset,
- size_t bytes);
-
-#if IS_ENABLED(CONFIG_KUNIT)
-void __binder_alloc_init(struct binder_alloc *alloc, struct list_lru *freelist);
-size_t binder_alloc_buffer_size(struct binder_alloc *alloc,
- struct binder_buffer *buffer);
-#endif
-
-#endif /* _LINUX_BINDER_ALLOC_H */
-
diff --git a/drivers/android/binder_internal.h b/drivers/android/binder_internal.h
deleted file mode 100644
index 342574bfd28a..000000000000
--- a/drivers/android/binder_internal.h
+++ /dev/null
@@ -1,597 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-
-#ifndef _LINUX_BINDER_INTERNAL_H
-#define _LINUX_BINDER_INTERNAL_H
-
-#include <linux/fs.h>
-#include <linux/list.h>
-#include <linux/miscdevice.h>
-#include <linux/mutex.h>
-#include <linux/refcount.h>
-#include <linux/stddef.h>
-#include <linux/types.h>
-#include <linux/uidgid.h>
-#include <uapi/linux/android/binderfs.h>
-#include "binder_alloc.h"
-#include "dbitmap.h"
-
-struct binder_context {
- struct binder_node *binder_context_mgr_node;
- struct mutex context_mgr_node_lock;
- kuid_t binder_context_mgr_uid;
- const char *name;
-};
-
-/**
- * struct binder_device - information about a binder device node
- * @hlist: list of binder devices
- * @miscdev: information about a binder character device node
- * @context: binder context information
- * @binderfs_inode: This is the inode of the root dentry of the super block
- * belonging to a binderfs mount.
- */
-struct binder_device {
- struct hlist_node hlist;
- struct miscdevice miscdev;
- struct binder_context context;
- struct inode *binderfs_inode;
- refcount_t ref;
-};
-
-/**
- * binderfs_mount_opts - mount options for binderfs
- * @max: maximum number of allocatable binderfs binder devices
- * @stats_mode: enable binder stats in binderfs.
- */
-struct binderfs_mount_opts {
- int max;
- int stats_mode;
-};
-
-/**
- * binderfs_info - information about a binderfs mount
- * @ipc_ns: The ipc namespace the binderfs mount belongs to.
- * @control_dentry: This records the dentry of this binderfs mount
- * binder-control device.
- * @root_uid: uid that needs to be used when a new binder device is
- * created.
- * @root_gid: gid that needs to be used when a new binder device is
- * created.
- * @mount_opts: The mount options in use.
- * @device_count: The current number of allocated binder devices.
- * @proc_log_dir: Pointer to the directory dentry containing process-specific
- * logs.
- */
-struct binderfs_info {
- struct ipc_namespace *ipc_ns;
- struct dentry *control_dentry;
- kuid_t root_uid;
- kgid_t root_gid;
- struct binderfs_mount_opts mount_opts;
- int device_count;
- struct dentry *proc_log_dir;
-};
-
-extern const struct file_operations binder_fops;
-
-extern char *binder_devices_param;
-
-#ifdef CONFIG_ANDROID_BINDERFS
-extern bool is_binderfs_device(const struct inode *inode);
-extern struct dentry *binderfs_create_file(struct dentry *dir, const char *name,
- const struct file_operations *fops,
- void *data);
-#else
-static inline bool is_binderfs_device(const struct inode *inode)
-{
- return false;
-}
-static inline struct dentry *binderfs_create_file(struct dentry *dir,
- const char *name,
- const struct file_operations *fops,
- void *data)
-{
- return NULL;
-}
-#endif
-
-#ifdef CONFIG_ANDROID_BINDERFS
-extern int __init init_binderfs(void);
-#else
-static inline int __init init_binderfs(void)
-{
- return 0;
-}
-#endif
-
-struct binder_debugfs_entry {
- const char *name;
- umode_t mode;
- const struct file_operations *fops;
- void *data;
-};
-
-extern const struct binder_debugfs_entry binder_debugfs_entries[];
-
-#define binder_for_each_debugfs_entry(entry) \
- for ((entry) = binder_debugfs_entries; \
- (entry)->name; \
- (entry)++)
-
-enum binder_stat_types {
- BINDER_STAT_PROC,
- BINDER_STAT_THREAD,
- BINDER_STAT_NODE,
- BINDER_STAT_REF,
- BINDER_STAT_DEATH,
- BINDER_STAT_TRANSACTION,
- BINDER_STAT_TRANSACTION_COMPLETE,
- BINDER_STAT_FREEZE,
- BINDER_STAT_COUNT
-};
-
-struct binder_stats {
- atomic_t br[_IOC_NR(BR_CLEAR_FREEZE_NOTIFICATION_DONE) + 1];
- atomic_t bc[_IOC_NR(BC_FREEZE_NOTIFICATION_DONE) + 1];
- atomic_t obj_created[BINDER_STAT_COUNT];
- atomic_t obj_deleted[BINDER_STAT_COUNT];
-};
-
-/**
- * struct binder_work - work enqueued on a worklist
- * @entry: node enqueued on list
- * @type: type of work to be performed
- *
- * There are separate work lists for proc, thread, and node (async).
- */
-struct binder_work {
- struct list_head entry;
-
- enum binder_work_type {
- BINDER_WORK_TRANSACTION = 1,
- BINDER_WORK_TRANSACTION_COMPLETE,
- BINDER_WORK_TRANSACTION_PENDING,
- BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT,
- BINDER_WORK_RETURN_ERROR,
- BINDER_WORK_NODE,
- BINDER_WORK_DEAD_BINDER,
- BINDER_WORK_DEAD_BINDER_AND_CLEAR,
- BINDER_WORK_CLEAR_DEATH_NOTIFICATION,
- BINDER_WORK_FROZEN_BINDER,
- BINDER_WORK_CLEAR_FREEZE_NOTIFICATION,
- } type;
-};
-
-struct binder_error {
- struct binder_work work;
- uint32_t cmd;
-};
-
-/**
- * struct binder_node - binder node bookkeeping
- * @debug_id: unique ID for debugging
- * (invariant after initialized)
- * @lock: lock for node fields
- * @work: worklist element for node work
- * (protected by @proc->inner_lock)
- * @rb_node: element for proc->nodes tree
- * (protected by @proc->inner_lock)
- * @dead_node: element for binder_dead_nodes list
- * (protected by binder_dead_nodes_lock)
- * @proc: binder_proc that owns this node
- * (invariant after initialized)
- * @refs: list of references on this node
- * (protected by @lock)
- * @internal_strong_refs: used to take strong references when
- * initiating a transaction
- * (protected by @proc->inner_lock if @proc
- * and by @lock)
- * @local_weak_refs: weak user refs from local process
- * (protected by @proc->inner_lock if @proc
- * and by @lock)
- * @local_strong_refs: strong user refs from local process
- * (protected by @proc->inner_lock if @proc
- * and by @lock)
- * @tmp_refs: temporary kernel refs
- * (protected by @proc->inner_lock while @proc
- * is valid, and by binder_dead_nodes_lock
- * if @proc is NULL. During inc/dec and node release
- * it is also protected by @lock to provide safety
- * as the node dies and @proc becomes NULL)
- * @ptr: userspace pointer for node
- * (invariant, no lock needed)
- * @cookie: userspace cookie for node
- * (invariant, no lock needed)
- * @has_strong_ref: userspace notified of strong ref
- * (protected by @proc->inner_lock if @proc
- * and by @lock)
- * @pending_strong_ref: userspace has acked notification of strong ref
- * (protected by @proc->inner_lock if @proc
- * and by @lock)
- * @has_weak_ref: userspace notified of weak ref
- * (protected by @proc->inner_lock if @proc
- * and by @lock)
- * @pending_weak_ref: userspace has acked notification of weak ref
- * (protected by @proc->inner_lock if @proc
- * and by @lock)
- * @has_async_transaction: async transaction to node in progress
- * (protected by @lock)
- * @accept_fds: file descriptor operations supported for node
- * (invariant after initialized)
- * @min_priority: minimum scheduling priority
- * (invariant after initialized)
- * @txn_security_ctx: require sender's security context
- * (invariant after initialized)
- * @async_todo: list of async work items
- * (protected by @proc->inner_lock)
- *
- * Bookkeeping structure for binder nodes.
- */
-struct binder_node {
- int debug_id;
- spinlock_t lock;
- struct binder_work work;
- union {
- struct rb_node rb_node;
- struct hlist_node dead_node;
- };
- struct binder_proc *proc;
- struct hlist_head refs;
- int internal_strong_refs;
- int local_weak_refs;
- int local_strong_refs;
- int tmp_refs;
- binder_uintptr_t ptr;
- binder_uintptr_t cookie;
- struct {
- /*
- * bitfield elements protected by
- * proc inner_lock
- */
- u8 has_strong_ref:1;
- u8 pending_strong_ref:1;
- u8 has_weak_ref:1;
- u8 pending_weak_ref:1;
- };
- struct {
- /*
- * invariant after initialization
- */
- u8 accept_fds:1;
- u8 txn_security_ctx:1;
- u8 min_priority;
- };
- bool has_async_transaction;
- struct list_head async_todo;
-};
-
-struct binder_ref_death {
- /**
- * @work: worklist element for death notifications
- * (protected by inner_lock of the proc that
- * this ref belongs to)
- */
- struct binder_work work;
- binder_uintptr_t cookie;
-};
-
-struct binder_ref_freeze {
- struct binder_work work;
- binder_uintptr_t cookie;
- bool is_frozen:1;
- bool sent:1;
- bool resend:1;
-};
-
-/**
- * struct binder_ref_data - binder_ref counts and id
- * @debug_id: unique ID for the ref
- * @desc: unique userspace handle for ref
- * @strong: strong ref count (debugging only if not locked)
- * @weak: weak ref count (debugging only if not locked)
- *
- * Structure to hold ref count and ref id information. Since
- * the actual ref can only be accessed with a lock, this structure
- * is used to return information about the ref to callers of
- * ref inc/dec functions.
- */
-struct binder_ref_data {
- int debug_id;
- uint32_t desc;
- int strong;
- int weak;
-};
-
-/**
- * struct binder_ref - struct to track references on nodes
- * @data: binder_ref_data containing id, handle, and current refcounts
- * @rb_node_desc: node for lookup by @data.desc in proc's rb_tree
- * @rb_node_node: node for lookup by @node in proc's rb_tree
- * @node_entry: list entry for node->refs list in target node
- * (protected by @node->lock)
- * @proc: binder_proc containing ref
- * @node: binder_node of target node. When cleaning up a
- * ref for deletion in binder_cleanup_ref, a non-NULL
- * @node indicates the node must be freed
- * @death: pointer to death notification (ref_death) if requested
- * (protected by @node->lock)
- * @freeze: pointer to freeze notification (ref_freeze) if requested
- * (protected by @node->lock)
- *
- * Structure to track references from procA to target node (on procB). This
- * structure is unsafe to access without holding @proc->outer_lock.
- */
-struct binder_ref {
- /* Lookups needed: */
- /* node + proc => ref (transaction) */
- /* desc + proc => ref (transaction, inc/dec ref) */
- /* node => refs + procs (proc exit) */
- struct binder_ref_data data;
- struct rb_node rb_node_desc;
- struct rb_node rb_node_node;
- struct hlist_node node_entry;
- struct binder_proc *proc;
- struct binder_node *node;
- struct binder_ref_death *death;
- struct binder_ref_freeze *freeze;
-};
-
-/**
- * struct binder_proc - binder process bookkeeping
- * @proc_node: element for binder_procs list
- * @threads: rbtree of binder_threads in this proc
- * (protected by @inner_lock)
- * @nodes: rbtree of binder nodes associated with
- * this proc ordered by node->ptr
- * (protected by @inner_lock)
- * @refs_by_desc: rbtree of refs ordered by ref->desc
- * (protected by @outer_lock)
- * @refs_by_node: rbtree of refs ordered by ref->node
- * (protected by @outer_lock)
- * @waiting_threads: threads currently waiting for proc work
- * (protected by @inner_lock)
- * @pid PID of group_leader of process
- * (invariant after initialized)
- * @tsk task_struct for group_leader of process
- * (invariant after initialized)
- * @cred struct cred associated with the `struct file`
- * in binder_open()
- * (invariant after initialized)
- * @deferred_work_node: element for binder_deferred_list
- * (protected by binder_deferred_lock)
- * @deferred_work: bitmap of deferred work to perform
- * (protected by binder_deferred_lock)
- * @outstanding_txns: number of transactions to be transmitted before
- * processes in freeze_wait are woken up
- * (protected by @inner_lock)
- * @is_dead: process is dead and awaiting free
- * when outstanding transactions are cleaned up
- * (protected by @inner_lock)
- * @is_frozen: process is frozen and unable to service
- * binder transactions
- * (protected by @inner_lock)
- * @sync_recv: process received sync transactions since last frozen
- * bit 0: received sync transaction after being frozen
- * bit 1: new pending sync transaction during freezing
- * (protected by @inner_lock)
- * @async_recv: process received async transactions since last frozen
- * (protected by @inner_lock)
- * @freeze_wait: waitqueue of processes waiting for all outstanding
- * transactions to be processed
- * (protected by @inner_lock)
- * @dmap dbitmap to manage available reference descriptors
- * (protected by @outer_lock)
- * @todo: list of work for this process
- * (protected by @inner_lock)
- * @stats: per-process binder statistics
- * (atomics, no lock needed)
- * @delivered_death: list of delivered death notification
- * (protected by @inner_lock)
- * @delivered_freeze: list of delivered freeze notification
- * (protected by @inner_lock)
- * @max_threads: cap on number of binder threads
- * (protected by @inner_lock)
- * @requested_threads: number of binder threads requested but not
- * yet started. In current implementation, can
- * only be 0 or 1.
- * (protected by @inner_lock)
- * @requested_threads_started: number binder threads started
- * (protected by @inner_lock)
- * @tmp_ref: temporary reference to indicate proc is in use
- * (protected by @inner_lock)
- * @default_priority: default scheduler priority
- * (invariant after initialized)
- * @debugfs_entry: debugfs node
- * @alloc: binder allocator bookkeeping
- * @context: binder_context for this proc
- * (invariant after initialized)
- * @inner_lock: can nest under outer_lock and/or node lock
- * @outer_lock: no nesting under innor or node lock
- * Lock order: 1) outer, 2) node, 3) inner
- * @binderfs_entry: process-specific binderfs log file
- * @oneway_spam_detection_enabled: process enabled oneway spam detection
- * or not
- *
- * Bookkeeping structure for binder processes
- */
-struct binder_proc {
- struct hlist_node proc_node;
- struct rb_root threads;
- struct rb_root nodes;
- struct rb_root refs_by_desc;
- struct rb_root refs_by_node;
- struct list_head waiting_threads;
- int pid;
- struct task_struct *tsk;
- const struct cred *cred;
- struct hlist_node deferred_work_node;
- int deferred_work;
- int outstanding_txns;
- bool is_dead;
- bool is_frozen;
- bool sync_recv;
- bool async_recv;
- wait_queue_head_t freeze_wait;
- struct dbitmap dmap;
- struct list_head todo;
- struct binder_stats stats;
- struct list_head delivered_death;
- struct list_head delivered_freeze;
- u32 max_threads;
- int requested_threads;
- int requested_threads_started;
- int tmp_ref;
- long default_priority;
- struct dentry *debugfs_entry;
- struct binder_alloc alloc;
- struct binder_context *context;
- spinlock_t inner_lock;
- spinlock_t outer_lock;
- struct dentry *binderfs_entry;
- bool oneway_spam_detection_enabled;
-};
-
-/**
- * struct binder_thread - binder thread bookkeeping
- * @proc: binder process for this thread
- * (invariant after initialization)
- * @rb_node: element for proc->threads rbtree
- * (protected by @proc->inner_lock)
- * @waiting_thread_node: element for @proc->waiting_threads list
- * (protected by @proc->inner_lock)
- * @pid: PID for this thread
- * (invariant after initialization)
- * @looper: bitmap of looping state
- * (only accessed by this thread)
- * @looper_needs_return: looping thread needs to exit driver
- * (no lock needed)
- * @transaction_stack: stack of in-progress transactions for this thread
- * (protected by @proc->inner_lock)
- * @todo: list of work to do for this thread
- * (protected by @proc->inner_lock)
- * @process_todo: whether work in @todo should be processed
- * (protected by @proc->inner_lock)
- * @return_error: transaction errors reported by this thread
- * (only accessed by this thread)
- * @reply_error: transaction errors reported by target thread
- * (protected by @proc->inner_lock)
- * @ee: extended error information from this thread
- * (protected by @proc->inner_lock)
- * @wait: wait queue for thread work
- * @stats: per-thread statistics
- * (atomics, no lock needed)
- * @tmp_ref: temporary reference to indicate thread is in use
- * (atomic since @proc->inner_lock cannot
- * always be acquired)
- * @is_dead: thread is dead and awaiting free
- * when outstanding transactions are cleaned up
- * (protected by @proc->inner_lock)
- *
- * Bookkeeping structure for binder threads.
- */
-struct binder_thread {
- struct binder_proc *proc;
- struct rb_node rb_node;
- struct list_head waiting_thread_node;
- int pid;
- int looper; /* only modified by this thread */
- bool looper_need_return; /* can be written by other thread */
- struct binder_transaction *transaction_stack;
- struct list_head todo;
- bool process_todo;
- struct binder_error return_error;
- struct binder_error reply_error;
- struct binder_extended_error ee;
- wait_queue_head_t wait;
- struct binder_stats stats;
- atomic_t tmp_ref;
- bool is_dead;
-};
-
-/**
- * struct binder_txn_fd_fixup - transaction fd fixup list element
- * @fixup_entry: list entry
- * @file: struct file to be associated with new fd
- * @offset: offset in buffer data to this fixup
- * @target_fd: fd to use by the target to install @file
- *
- * List element for fd fixups in a transaction. Since file
- * descriptors need to be allocated in the context of the
- * target process, we pass each fd to be processed in this
- * struct.
- */
-struct binder_txn_fd_fixup {
- struct list_head fixup_entry;
- struct file *file;
- size_t offset;
- int target_fd;
-};
-
-struct binder_transaction {
- int debug_id;
- struct binder_work work;
- struct binder_thread *from;
- pid_t from_pid;
- pid_t from_tid;
- struct binder_transaction *from_parent;
- struct binder_proc *to_proc;
- struct binder_thread *to_thread;
- struct binder_transaction *to_parent;
- unsigned is_async:1;
- unsigned is_reply:1;
-
- struct binder_buffer *buffer;
- unsigned int code;
- unsigned int flags;
- long priority;
- long saved_priority;
- kuid_t sender_euid;
- ktime_t start_time;
- struct list_head fd_fixups;
- binder_uintptr_t security_ctx;
- /**
- * @lock: protects @from, @to_proc, and @to_thread
- *
- * @from, @to_proc, and @to_thread can be set to NULL
- * during thread teardown
- */
- spinlock_t lock;
-};
-
-/**
- * struct binder_object - union of flat binder object types
- * @hdr: generic object header
- * @fbo: binder object (nodes and refs)
- * @fdo: file descriptor object
- * @bbo: binder buffer pointer
- * @fdao: file descriptor array
- *
- * Used for type-independent object copies
- */
-struct binder_object {
- union {
- struct binder_object_header hdr;
- struct flat_binder_object fbo;
- struct binder_fd_object fdo;
- struct binder_buffer_object bbo;
- struct binder_fd_array_object fdao;
- };
-};
-
-/**
- * Add a binder device to binder_devices
- * @device: the new binder device to add to the global list
- */
-void binder_add_device(struct binder_device *device);
-
-/**
- * Remove a binder device to binder_devices
- * @device: the binder device to remove from the global list
- */
-void binder_remove_device(struct binder_device *device);
-
-#if IS_ENABLED(CONFIG_KUNIT)
-vm_fault_t binder_vm_fault(struct vm_fault *vmf);
-#endif
-
-#endif /* _LINUX_BINDER_INTERNAL_H */
diff --git a/drivers/android/binder_netlink.c b/drivers/android/binder_netlink.c
deleted file mode 100644
index 81e8432b5904..000000000000
--- a/drivers/android/binder_netlink.c
+++ /dev/null
@@ -1,32 +0,0 @@
-// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
-/* Do not edit directly, auto-generated from: */
-/* Documentation/netlink/specs/binder.yaml */
-/* YNL-GEN kernel source */
-/* To regenerate run: tools/net/ynl/ynl-regen.sh */
-
-#include <net/netlink.h>
-#include <net/genetlink.h>
-
-#include "binder_netlink.h"
-
-#include <uapi/linux/android/binder_netlink.h>
-
-/* Ops table for binder */
-static const struct genl_split_ops binder_nl_ops[] = {
-};
-
-static const struct genl_multicast_group binder_nl_mcgrps[] = {
- [BINDER_NLGRP_REPORT] = { "report", },
-};
-
-struct genl_family binder_nl_family __ro_after_init = {
- .name = BINDER_FAMILY_NAME,
- .version = BINDER_FAMILY_VERSION,
- .netnsok = true,
- .parallel_ops = true,
- .module = THIS_MODULE,
- .split_ops = binder_nl_ops,
- .n_split_ops = ARRAY_SIZE(binder_nl_ops),
- .mcgrps = binder_nl_mcgrps,
- .n_mcgrps = ARRAY_SIZE(binder_nl_mcgrps),
-};
diff --git a/drivers/android/binder_netlink.h b/drivers/android/binder_netlink.h
deleted file mode 100644
index 57399942a5e3..000000000000
--- a/drivers/android/binder_netlink.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
-/* Do not edit directly, auto-generated from: */
-/* Documentation/netlink/specs/binder.yaml */
-/* YNL-GEN kernel header */
-/* To regenerate run: tools/net/ynl/ynl-regen.sh */
-
-#ifndef _LINUX_BINDER_GEN_H
-#define _LINUX_BINDER_GEN_H
-
-#include <net/netlink.h>
-#include <net/genetlink.h>
-
-#include <uapi/linux/android/binder_netlink.h>
-
-enum {
- BINDER_NLGRP_REPORT,
-};
-
-extern struct genl_family binder_nl_family;
-
-#endif /* _LINUX_BINDER_GEN_H */
diff --git a/drivers/android/binder_trace.h b/drivers/android/binder_trace.h
deleted file mode 100644
index fa5eb61cf580..000000000000
--- a/drivers/android/binder_trace.h
+++ /dev/null
@@ -1,448 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * Copyright (C) 2012 Google, Inc.
- */
-
-#undef TRACE_SYSTEM
-#define TRACE_SYSTEM binder
-
-#if !defined(_BINDER_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
-#define _BINDER_TRACE_H
-
-#include <linux/tracepoint.h>
-
-struct binder_buffer;
-struct binder_node;
-struct binder_proc;
-struct binder_alloc;
-struct binder_ref_data;
-struct binder_thread;
-struct binder_transaction;
-
-TRACE_EVENT(binder_ioctl,
- TP_PROTO(unsigned int cmd, unsigned long arg),
- TP_ARGS(cmd, arg),
-
- TP_STRUCT__entry(
- __field(unsigned int, cmd)
- __field(unsigned long, arg)
- ),
- TP_fast_assign(
- __entry->cmd = cmd;
- __entry->arg = arg;
- ),
- TP_printk("cmd=0x%x arg=0x%lx", __entry->cmd, __entry->arg)
-);
-
-DECLARE_EVENT_CLASS(binder_function_return_class,
- TP_PROTO(int ret),
- TP_ARGS(ret),
- TP_STRUCT__entry(
- __field(int, ret)
- ),
- TP_fast_assign(
- __entry->ret = ret;
- ),
- TP_printk("ret=%d", __entry->ret)
-);
-
-#define DEFINE_BINDER_FUNCTION_RETURN_EVENT(name) \
-DEFINE_EVENT(binder_function_return_class, name, \
- TP_PROTO(int ret), \
- TP_ARGS(ret))
-
-DEFINE_BINDER_FUNCTION_RETURN_EVENT(binder_ioctl_done);
-DEFINE_BINDER_FUNCTION_RETURN_EVENT(binder_write_done);
-DEFINE_BINDER_FUNCTION_RETURN_EVENT(binder_read_done);
-
-TRACE_EVENT(binder_wait_for_work,
- TP_PROTO(bool proc_work, bool transaction_stack, bool thread_todo),
- TP_ARGS(proc_work, transaction_stack, thread_todo),
-
- TP_STRUCT__entry(
- __field(bool, proc_work)
- __field(bool, transaction_stack)
- __field(bool, thread_todo)
- ),
- TP_fast_assign(
- __entry->proc_work = proc_work;
- __entry->transaction_stack = transaction_stack;
- __entry->thread_todo = thread_todo;
- ),
- TP_printk("proc_work=%d transaction_stack=%d thread_todo=%d",
- __entry->proc_work, __entry->transaction_stack,
- __entry->thread_todo)
-);
-
-TRACE_EVENT(binder_txn_latency_free,
- TP_PROTO(struct binder_transaction *t,
- int from_proc, int from_thread,
- int to_proc, int to_thread),
- TP_ARGS(t, from_proc, from_thread, to_proc, to_thread),
- TP_STRUCT__entry(
- __field(int, debug_id)
- __field(int, from_proc)
- __field(int, from_thread)
- __field(int, to_proc)
- __field(int, to_thread)
- __field(unsigned int, code)
- __field(unsigned int, flags)
- ),
- TP_fast_assign(
- __entry->debug_id = t->debug_id;
- __entry->from_proc = from_proc;
- __entry->from_thread = from_thread;
- __entry->to_proc = to_proc;
- __entry->to_thread = to_thread;
- __entry->code = t->code;
- __entry->flags = t->flags;
- ),
- TP_printk("transaction=%d from %d:%d to %d:%d flags=0x%x code=0x%x",
- __entry->debug_id, __entry->from_proc, __entry->from_thread,
- __entry->to_proc, __entry->to_thread, __entry->code,
- __entry->flags)
-);
-
-TRACE_EVENT(binder_transaction,
- TP_PROTO(bool reply, struct binder_transaction *t,
- struct binder_node *target_node),
- TP_ARGS(reply, t, target_node),
- TP_STRUCT__entry(
- __field(int, debug_id)
- __field(int, target_node)
- __field(int, to_proc)
- __field(int, to_thread)
- __field(int, reply)
- __field(unsigned int, code)
- __field(unsigned int, flags)
- ),
- TP_fast_assign(
- __entry->debug_id = t->debug_id;
- __entry->target_node = target_node ? target_node->debug_id : 0;
- __entry->to_proc = t->to_proc->pid;
- __entry->to_thread = t->to_thread ? t->to_thread->pid : 0;
- __entry->reply = reply;
- __entry->code = t->code;
- __entry->flags = t->flags;
- ),
- TP_printk("transaction=%d dest_node=%d dest_proc=%d dest_thread=%d reply=%d flags=0x%x code=0x%x",
- __entry->debug_id, __entry->target_node,
- __entry->to_proc, __entry->to_thread,
- __entry->reply, __entry->flags, __entry->code)
-);
-
-TRACE_EVENT(binder_transaction_received,
- TP_PROTO(struct binder_transaction *t),
- TP_ARGS(t),
-
- TP_STRUCT__entry(
- __field(int, debug_id)
- ),
- TP_fast_assign(
- __entry->debug_id = t->debug_id;
- ),
- TP_printk("transaction=%d", __entry->debug_id)
-);
-
-TRACE_EVENT(binder_transaction_node_to_ref,
- TP_PROTO(struct binder_transaction *t, struct binder_node *node,
- struct binder_ref_data *rdata),
- TP_ARGS(t, node, rdata),
-
- TP_STRUCT__entry(
- __field(int, debug_id)
- __field(int, node_debug_id)
- __field(binder_uintptr_t, node_ptr)
- __field(int, ref_debug_id)
- __field(uint32_t, ref_desc)
- ),
- TP_fast_assign(
- __entry->debug_id = t->debug_id;
- __entry->node_debug_id = node->debug_id;
- __entry->node_ptr = node->ptr;
- __entry->ref_debug_id = rdata->debug_id;
- __entry->ref_desc = rdata->desc;
- ),
- TP_printk("transaction=%d node=%d src_ptr=0x%016llx ==> dest_ref=%d dest_desc=%d",
- __entry->debug_id, __entry->node_debug_id,
- (u64)__entry->node_ptr,
- __entry->ref_debug_id, __entry->ref_desc)
-);
-
-TRACE_EVENT(binder_transaction_ref_to_node,
- TP_PROTO(struct binder_transaction *t, struct binder_node *node,
- struct binder_ref_data *rdata),
- TP_ARGS(t, node, rdata),
-
- TP_STRUCT__entry(
- __field(int, debug_id)
- __field(int, ref_debug_id)
- __field(uint32_t, ref_desc)
- __field(int, node_debug_id)
- __field(binder_uintptr_t, node_ptr)
- ),
- TP_fast_assign(
- __entry->debug_id = t->debug_id;
- __entry->ref_debug_id = rdata->debug_id;
- __entry->ref_desc = rdata->desc;
- __entry->node_debug_id = node->debug_id;
- __entry->node_ptr = node->ptr;
- ),
- TP_printk("transaction=%d node=%d src_ref=%d src_desc=%d ==> dest_ptr=0x%016llx",
- __entry->debug_id, __entry->node_debug_id,
- __entry->ref_debug_id, __entry->ref_desc,
- (u64)__entry->node_ptr)
-);
-
-TRACE_EVENT(binder_transaction_ref_to_ref,
- TP_PROTO(struct binder_transaction *t, struct binder_node *node,
- struct binder_ref_data *src_ref,
- struct binder_ref_data *dest_ref),
- TP_ARGS(t, node, src_ref, dest_ref),
-
- TP_STRUCT__entry(
- __field(int, debug_id)
- __field(int, node_debug_id)
- __field(int, src_ref_debug_id)
- __field(uint32_t, src_ref_desc)
- __field(int, dest_ref_debug_id)
- __field(uint32_t, dest_ref_desc)
- ),
- TP_fast_assign(
- __entry->debug_id = t->debug_id;
- __entry->node_debug_id = node->debug_id;
- __entry->src_ref_debug_id = src_ref->debug_id;
- __entry->src_ref_desc = src_ref->desc;
- __entry->dest_ref_debug_id = dest_ref->debug_id;
- __entry->dest_ref_desc = dest_ref->desc;
- ),
- TP_printk("transaction=%d node=%d src_ref=%d src_desc=%d ==> dest_ref=%d dest_desc=%d",
- __entry->debug_id, __entry->node_debug_id,
- __entry->src_ref_debug_id, __entry->src_ref_desc,
- __entry->dest_ref_debug_id, __entry->dest_ref_desc)
-);
-
-TRACE_EVENT(binder_transaction_fd_send,
- TP_PROTO(struct binder_transaction *t, int fd, size_t offset),
- TP_ARGS(t, fd, offset),
-
- TP_STRUCT__entry(
- __field(int, debug_id)
- __field(int, fd)
- __field(size_t, offset)
- ),
- TP_fast_assign(
- __entry->debug_id = t->debug_id;
- __entry->fd = fd;
- __entry->offset = offset;
- ),
- TP_printk("transaction=%d src_fd=%d offset=%zu",
- __entry->debug_id, __entry->fd, __entry->offset)
-);
-
-TRACE_EVENT(binder_transaction_fd_recv,
- TP_PROTO(struct binder_transaction *t, int fd, size_t offset),
- TP_ARGS(t, fd, offset),
-
- TP_STRUCT__entry(
- __field(int, debug_id)
- __field(int, fd)
- __field(size_t, offset)
- ),
- TP_fast_assign(
- __entry->debug_id = t->debug_id;
- __entry->fd = fd;
- __entry->offset = offset;
- ),
- TP_printk("transaction=%d dest_fd=%d offset=%zu",
- __entry->debug_id, __entry->fd, __entry->offset)
-);
-
-DECLARE_EVENT_CLASS(binder_buffer_class,
- TP_PROTO(struct binder_buffer *buf),
- TP_ARGS(buf),
- TP_STRUCT__entry(
- __field(int, debug_id)
- __field(size_t, data_size)
- __field(size_t, offsets_size)
- __field(size_t, extra_buffers_size)
- ),
- TP_fast_assign(
- __entry->debug_id = buf->debug_id;
- __entry->data_size = buf->data_size;
- __entry->offsets_size = buf->offsets_size;
- __entry->extra_buffers_size = buf->extra_buffers_size;
- ),
- TP_printk("transaction=%d data_size=%zd offsets_size=%zd extra_buffers_size=%zd",
- __entry->debug_id, __entry->data_size, __entry->offsets_size,
- __entry->extra_buffers_size)
-);
-
-DEFINE_EVENT(binder_buffer_class, binder_transaction_alloc_buf,
- TP_PROTO(struct binder_buffer *buffer),
- TP_ARGS(buffer));
-
-DEFINE_EVENT(binder_buffer_class, binder_transaction_buffer_release,
- TP_PROTO(struct binder_buffer *buffer),
- TP_ARGS(buffer));
-
-DEFINE_EVENT(binder_buffer_class, binder_transaction_failed_buffer_release,
- TP_PROTO(struct binder_buffer *buffer),
- TP_ARGS(buffer));
-
-DEFINE_EVENT(binder_buffer_class, binder_transaction_update_buffer_release,
- TP_PROTO(struct binder_buffer *buffer),
- TP_ARGS(buffer));
-
-TRACE_EVENT(binder_update_page_range,
- TP_PROTO(struct binder_alloc *alloc, bool allocate,
- unsigned long start, unsigned long end),
- TP_ARGS(alloc, allocate, start, end),
- TP_STRUCT__entry(
- __field(int, proc)
- __field(bool, allocate)
- __field(size_t, offset)
- __field(size_t, size)
- ),
- TP_fast_assign(
- __entry->proc = alloc->pid;
- __entry->allocate = allocate;
- __entry->offset = start - alloc->vm_start;
- __entry->size = end - start;
- ),
- TP_printk("proc=%d allocate=%d offset=%zu size=%zu",
- __entry->proc, __entry->allocate,
- __entry->offset, __entry->size)
-);
-
-DECLARE_EVENT_CLASS(binder_lru_page_class,
- TP_PROTO(const struct binder_alloc *alloc, size_t page_index),
- TP_ARGS(alloc, page_index),
- TP_STRUCT__entry(
- __field(int, proc)
- __field(size_t, page_index)
- ),
- TP_fast_assign(
- __entry->proc = alloc->pid;
- __entry->page_index = page_index;
- ),
- TP_printk("proc=%d page_index=%zu",
- __entry->proc, __entry->page_index)
-);
-
-DEFINE_EVENT(binder_lru_page_class, binder_alloc_lru_start,
- TP_PROTO(const struct binder_alloc *alloc, size_t page_index),
- TP_ARGS(alloc, page_index));
-
-DEFINE_EVENT(binder_lru_page_class, binder_alloc_lru_end,
- TP_PROTO(const struct binder_alloc *alloc, size_t page_index),
- TP_ARGS(alloc, page_index));
-
-DEFINE_EVENT(binder_lru_page_class, binder_free_lru_start,
- TP_PROTO(const struct binder_alloc *alloc, size_t page_index),
- TP_ARGS(alloc, page_index));
-
-DEFINE_EVENT(binder_lru_page_class, binder_free_lru_end,
- TP_PROTO(const struct binder_alloc *alloc, size_t page_index),
- TP_ARGS(alloc, page_index));
-
-DEFINE_EVENT(binder_lru_page_class, binder_alloc_page_start,
- TP_PROTO(const struct binder_alloc *alloc, size_t page_index),
- TP_ARGS(alloc, page_index));
-
-DEFINE_EVENT(binder_lru_page_class, binder_alloc_page_end,
- TP_PROTO(const struct binder_alloc *alloc, size_t page_index),
- TP_ARGS(alloc, page_index));
-
-DEFINE_EVENT(binder_lru_page_class, binder_unmap_user_start,
- TP_PROTO(const struct binder_alloc *alloc, size_t page_index),
- TP_ARGS(alloc, page_index));
-
-DEFINE_EVENT(binder_lru_page_class, binder_unmap_user_end,
- TP_PROTO(const struct binder_alloc *alloc, size_t page_index),
- TP_ARGS(alloc, page_index));
-
-DEFINE_EVENT(binder_lru_page_class, binder_unmap_kernel_start,
- TP_PROTO(const struct binder_alloc *alloc, size_t page_index),
- TP_ARGS(alloc, page_index));
-
-DEFINE_EVENT(binder_lru_page_class, binder_unmap_kernel_end,
- TP_PROTO(const struct binder_alloc *alloc, size_t page_index),
- TP_ARGS(alloc, page_index));
-
-TRACE_EVENT(binder_command,
- TP_PROTO(uint32_t cmd),
- TP_ARGS(cmd),
- TP_STRUCT__entry(
- __field(uint32_t, cmd)
- ),
- TP_fast_assign(
- __entry->cmd = cmd;
- ),
- TP_printk("cmd=0x%x %s",
- __entry->cmd,
- _IOC_NR(__entry->cmd) < ARRAY_SIZE(binder_command_strings) ?
- binder_command_strings[_IOC_NR(__entry->cmd)] :
- "unknown")
-);
-
-TRACE_EVENT(binder_return,
- TP_PROTO(uint32_t cmd),
- TP_ARGS(cmd),
- TP_STRUCT__entry(
- __field(uint32_t, cmd)
- ),
- TP_fast_assign(
- __entry->cmd = cmd;
- ),
- TP_printk("cmd=0x%x %s",
- __entry->cmd,
- _IOC_NR(__entry->cmd) < ARRAY_SIZE(binder_return_strings) ?
- binder_return_strings[_IOC_NR(__entry->cmd)] :
- "unknown")
-);
-
-TRACE_EVENT(binder_netlink_report,
- TP_PROTO(const char *context,
- struct binder_transaction *t,
- u32 data_size,
- u32 error),
- TP_ARGS(context, t, data_size, error),
- TP_STRUCT__entry(
- __field(const char *, context)
- __field(u32, error)
- __field(int, from_pid)
- __field(int, from_tid)
- __field(int, to_pid)
- __field(int, to_tid)
- __field(bool, is_reply)
- __field(unsigned int, flags)
- __field(unsigned int, code)
- __field(size_t, data_size)
- ),
- TP_fast_assign(
- __entry->context = context;
- __entry->error = error;
- __entry->from_pid = t->from_pid;
- __entry->from_tid = t->from_tid;
- __entry->to_pid = t->to_proc ? t->to_proc->pid : 0;
- __entry->to_tid = t->to_thread ? t->to_thread->pid : 0;
- __entry->is_reply = t->is_reply;
- __entry->flags = t->flags;
- __entry->code = t->code;
- __entry->data_size = data_size;
- ),
- TP_printk("from %d:%d to %d:%d context=%s error=%d is_reply=%d flags=0x%x code=0x%x size=%zu",
- __entry->from_pid, __entry->from_tid,
- __entry->to_pid, __entry->to_tid,
- __entry->context, __entry->error, __entry->is_reply,
- __entry->flags, __entry->code, __entry->data_size)
-);
-
-#endif /* _BINDER_TRACE_H */
-
-#undef TRACE_INCLUDE_PATH
-#undef TRACE_INCLUDE_FILE
-#define TRACE_INCLUDE_PATH .
-#define TRACE_INCLUDE_FILE binder_trace
-#include <trace/define_trace.h>
diff --git a/drivers/android/binderfs.c b/drivers/android/binderfs.c
deleted file mode 100644
index 1f0538767f95..000000000000
--- a/drivers/android/binderfs.c
+++ /dev/null
@@ -1,785 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-
-#include <linux/compiler_types.h>
-#include <linux/errno.h>
-#include <linux/fs.h>
-#include <linux/fsnotify.h>
-#include <linux/gfp.h>
-#include <linux/idr.h>
-#include <linux/init.h>
-#include <linux/ipc_namespace.h>
-#include <linux/kdev_t.h>
-#include <linux/kernel.h>
-#include <linux/list.h>
-#include <linux/namei.h>
-#include <linux/magic.h>
-#include <linux/major.h>
-#include <linux/miscdevice.h>
-#include <linux/module.h>
-#include <linux/mutex.h>
-#include <linux/mount.h>
-#include <linux/fs_parser.h>
-#include <linux/sched.h>
-#include <linux/seq_file.h>
-#include <linux/slab.h>
-#include <linux/spinlock_types.h>
-#include <linux/stddef.h>
-#include <linux/string.h>
-#include <linux/types.h>
-#include <linux/uaccess.h>
-#include <linux/user_namespace.h>
-#include <linux/xarray.h>
-#include <uapi/linux/android/binder.h>
-#include <uapi/linux/android/binderfs.h>
-
-#include "binder_internal.h"
-
-#define FIRST_INODE 1
-#define SECOND_INODE 2
-#define INODE_OFFSET 3
-#define BINDERFS_MAX_MINOR (1U << MINORBITS)
-/* Ensure that the initial ipc namespace always has devices available. */
-#define BINDERFS_MAX_MINOR_CAPPED (BINDERFS_MAX_MINOR - 4)
-
-static dev_t binderfs_dev;
-static DEFINE_MUTEX(binderfs_minors_mutex);
-static DEFINE_IDA(binderfs_minors);
-
-enum binderfs_param {
- Opt_max,
- Opt_stats_mode,
-};
-
-enum binderfs_stats_mode {
- binderfs_stats_mode_unset,
- binderfs_stats_mode_global,
-};
-
-struct binder_features {
- bool oneway_spam_detection;
- bool extended_error;
- bool freeze_notification;
- bool transaction_report;
-};
-
-static const struct constant_table binderfs_param_stats[] = {
- { "global", binderfs_stats_mode_global },
- {}
-};
-
-static const struct fs_parameter_spec binderfs_fs_parameters[] = {
- fsparam_u32("max", Opt_max),
- fsparam_enum("stats", Opt_stats_mode, binderfs_param_stats),
- {}
-};
-
-static struct binder_features binder_features = {
- .oneway_spam_detection = true,
- .extended_error = true,
- .freeze_notification = true,
- .transaction_report = true,
-};
-
-static inline struct binderfs_info *BINDERFS_SB(const struct super_block *sb)
-{
- return sb->s_fs_info;
-}
-
-bool is_binderfs_device(const struct inode *inode)
-{
- if (inode->i_sb->s_magic == BINDERFS_SUPER_MAGIC)
- return true;
-
- return false;
-}
-
-/**
- * binderfs_binder_device_create - allocate inode from super block of a
- * binderfs mount
- * @ref_inode: inode from which the super block will be taken
- * @userp: buffer to copy information about new device for userspace to
- * @req: struct binderfs_device as copied from userspace
- *
- * This function allocates a new binder_device and reserves a new minor
- * number for it.
- * Minor numbers are limited and tracked globally in binderfs_minors. The
- * function will stash a struct binder_device for the specific binder
- * device in i_private of the inode.
- * It will go on to allocate a new inode from the super block of the
- * filesystem mount, stash a struct binder_device in its i_private field
- * and attach a dentry to that inode.
- *
- * Return: 0 on success, negative errno on failure
- */
-static int binderfs_binder_device_create(struct inode *ref_inode,
- struct binderfs_device __user *userp,
- struct binderfs_device *req)
-{
- int minor, ret;
- struct dentry *dentry, *root;
- struct binder_device *device;
- char *name = NULL;
- struct inode *inode = NULL;
- struct super_block *sb = ref_inode->i_sb;
- struct binderfs_info *info = sb->s_fs_info;
-#if defined(CONFIG_IPC_NS)
- bool use_reserve = (info->ipc_ns == &init_ipc_ns);
-#else
- bool use_reserve = true;
-#endif
-
- /* Reserve new minor number for the new device. */
- mutex_lock(&binderfs_minors_mutex);
- if (++info->device_count <= info->mount_opts.max)
- minor = ida_alloc_max(&binderfs_minors,
- use_reserve ? BINDERFS_MAX_MINOR - 1 :
- BINDERFS_MAX_MINOR_CAPPED - 1,
- GFP_KERNEL);
- else
- minor = -ENOSPC;
- if (minor < 0) {
- --info->device_count;
- mutex_unlock(&binderfs_minors_mutex);
- return minor;
- }
- mutex_unlock(&binderfs_minors_mutex);
-
- ret = -ENOMEM;
- device = kzalloc_obj(*device);
- if (!device)
- goto err;
-
- inode = new_inode(sb);
- if (!inode)
- goto err;
-
- inode->i_ino = minor + INODE_OFFSET;
- simple_inode_init_ts(inode);
- init_special_inode(inode, S_IFCHR | 0600,
- MKDEV(MAJOR(binderfs_dev), minor));
- inode->i_fop = &binder_fops;
- inode->i_uid = info->root_uid;
- inode->i_gid = info->root_gid;
-
- req->name[BINDERFS_MAX_NAME] = '\0'; /* NUL-terminate */
- name = kstrdup(req->name, GFP_KERNEL);
- if (!name)
- goto err;
-
- refcount_set(&device->ref, 1);
- device->binderfs_inode = inode;
- device->context.binder_context_mgr_uid = INVALID_UID;
- device->context.name = name;
- device->miscdev.name = name;
- device->miscdev.minor = minor;
- mutex_init(&device->context.context_mgr_node_lock);
-
- req->major = MAJOR(binderfs_dev);
- req->minor = minor;
-
- if (userp && copy_to_user(userp, req, sizeof(*req))) {
- ret = -EFAULT;
- goto err;
- }
-
- root = sb->s_root;
- dentry = simple_start_creating(root, name);
- if (IS_ERR(dentry)) {
- ret = PTR_ERR(dentry);
- goto err;
- }
- inode->i_private = device;
- binder_add_device(device);
- d_make_persistent(dentry, inode);
- fsnotify_create(root->d_inode, dentry);
- simple_done_creating(dentry);
-
- return 0;
-
-err:
- kfree(name);
- kfree(device);
- mutex_lock(&binderfs_minors_mutex);
- --info->device_count;
- ida_free(&binderfs_minors, minor);
- mutex_unlock(&binderfs_minors_mutex);
- iput(inode);
-
- return ret;
-}
-
-/**
- * binder_ctl_ioctl - handle binder device node allocation requests
- * @file: The file pointer for the binder-control device node.
- * @cmd: The ioctl command.
- * @arg: The ioctl argument.
- *
- * The request handler for the binder-control device. All requests operate on
- * the binderfs mount the binder-control device resides in:
- * - BINDER_CTL_ADD
- * Allocate a new binder device.
- *
- * Return: %0 on success, negative errno on failure.
- */
-static long binder_ctl_ioctl(struct file *file, unsigned int cmd,
- unsigned long arg)
-{
- int ret = -EINVAL;
- struct inode *inode = file_inode(file);
- struct binderfs_device __user *device = (struct binderfs_device __user *)arg;
- struct binderfs_device device_req;
-
- switch (cmd) {
- case BINDER_CTL_ADD:
- ret = copy_from_user(&device_req, device, sizeof(device_req));
- if (ret) {
- ret = -EFAULT;
- break;
- }
-
- ret = binderfs_binder_device_create(inode, device, &device_req);
- break;
- default:
- break;
- }
-
- return ret;
-}
-
-static void binderfs_evict_inode(struct inode *inode)
-{
- struct binder_device *device = inode->i_private;
- struct binderfs_info *info = BINDERFS_SB(inode->i_sb);
-
- clear_inode(inode);
-
- if (!S_ISCHR(inode->i_mode) || !device)
- return;
-
- mutex_lock(&binderfs_minors_mutex);
- --info->device_count;
- ida_free(&binderfs_minors, device->miscdev.minor);
- mutex_unlock(&binderfs_minors_mutex);
-
- if (refcount_dec_and_test(&device->ref)) {
- binder_remove_device(device);
- kfree(device->context.name);
- kfree(device);
- }
-}
-
-static int binderfs_fs_context_parse_param(struct fs_context *fc,
- struct fs_parameter *param)
-{
- int opt;
- struct binderfs_mount_opts *ctx = fc->fs_private;
- struct fs_parse_result result;
-
- opt = fs_parse(fc, binderfs_fs_parameters, param, &result);
- if (opt < 0)
- return opt;
-
- switch (opt) {
- case Opt_max:
- if (result.uint_32 > BINDERFS_MAX_MINOR)
- return invalfc(fc, "Bad value for '%s'", param->key);
-
- ctx->max = result.uint_32;
- break;
- case Opt_stats_mode:
- if (!capable(CAP_SYS_ADMIN))
- return -EPERM;
-
- ctx->stats_mode = result.uint_32;
- break;
- default:
- return invalfc(fc, "Unsupported parameter '%s'", param->key);
- }
-
- return 0;
-}
-
-static int binderfs_fs_context_reconfigure(struct fs_context *fc)
-{
- struct binderfs_mount_opts *ctx = fc->fs_private;
- struct binderfs_info *info = BINDERFS_SB(fc->root->d_sb);
-
- if (info->mount_opts.stats_mode != ctx->stats_mode)
- return invalfc(fc, "Binderfs stats mode cannot be changed during a remount");
-
- info->mount_opts.stats_mode = ctx->stats_mode;
- info->mount_opts.max = ctx->max;
- return 0;
-}
-
-static int binderfs_show_options(struct seq_file *seq, struct dentry *root)
-{
- struct binderfs_info *info = BINDERFS_SB(root->d_sb);
-
- if (info->mount_opts.max <= BINDERFS_MAX_MINOR)
- seq_printf(seq, ",max=%d", info->mount_opts.max);
-
- switch (info->mount_opts.stats_mode) {
- case binderfs_stats_mode_unset:
- break;
- case binderfs_stats_mode_global:
- seq_printf(seq, ",stats=global");
- break;
- }
-
- return 0;
-}
-
-static const struct super_operations binderfs_super_ops = {
- .evict_inode = binderfs_evict_inode,
- .show_options = binderfs_show_options,
- .statfs = simple_statfs,
-};
-
-static inline bool is_binderfs_control_device(const struct dentry *dentry)
-{
- struct binderfs_info *info = dentry->d_sb->s_fs_info;
-
- return info->control_dentry == dentry;
-}
-
-static int binderfs_rename(const struct mnt_idmap *idmap,
- struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry,
- unsigned int flags)
-{
- if (is_binderfs_control_device(old_dentry) ||
- is_binderfs_control_device(new_dentry))
- return -EPERM;
-
- return simple_rename(idmap, old_dir, old_dentry, new_dir,
- new_dentry, flags);
-}
-
-static int binderfs_unlink(struct inode *dir, struct dentry *dentry)
-{
- if (is_binderfs_control_device(dentry))
- return -EPERM;
-
- return simple_unlink(dir, dentry);
-}
-
-static const struct file_operations binder_ctl_fops = {
- .owner = THIS_MODULE,
- .open = nonseekable_open,
- .unlocked_ioctl = binder_ctl_ioctl,
- .compat_ioctl = binder_ctl_ioctl,
- .llseek = noop_llseek,
-};
-
-/**
- * binderfs_binder_ctl_create - create a new binder-control device
- * @sb: super block of the binderfs mount
- *
- * This function creates a new binder-control device node in the binderfs mount
- * referred to by @sb.
- *
- * Return: 0 on success, negative errno on failure
- */
-static int binderfs_binder_ctl_create(struct super_block *sb)
-{
- int minor, ret;
- struct dentry *dentry;
- struct binder_device *device;
- struct inode *inode = NULL;
- struct dentry *root = sb->s_root;
- struct binderfs_info *info = sb->s_fs_info;
-#if defined(CONFIG_IPC_NS)
- bool use_reserve = (info->ipc_ns == &init_ipc_ns);
-#else
- bool use_reserve = true;
-#endif
-
- device = kzalloc_obj(*device);
- if (!device)
- return -ENOMEM;
-
- ret = -ENOMEM;
- inode = new_inode(sb);
- if (!inode)
- goto out;
-
- /* Reserve a new minor number for the new device. */
- mutex_lock(&binderfs_minors_mutex);
- minor = ida_alloc_max(&binderfs_minors,
- use_reserve ? BINDERFS_MAX_MINOR - 1 :
- BINDERFS_MAX_MINOR_CAPPED - 1,
- GFP_KERNEL);
- mutex_unlock(&binderfs_minors_mutex);
- if (minor < 0) {
- ret = minor;
- goto out;
- }
-
- inode->i_ino = SECOND_INODE;
- simple_inode_init_ts(inode);
- init_special_inode(inode, S_IFCHR | 0600,
- MKDEV(MAJOR(binderfs_dev), minor));
- inode->i_fop = &binder_ctl_fops;
- inode->i_uid = info->root_uid;
- inode->i_gid = info->root_gid;
-
- refcount_set(&device->ref, 1);
- device->binderfs_inode = inode;
- device->miscdev.minor = minor;
-
- dentry = d_alloc_name(root, "binder-control");
- if (!dentry)
- goto out;
-
- inode->i_private = device;
- info->control_dentry = dentry;
- d_make_persistent(dentry, inode);
- dput(dentry);
-
- return 0;
-
-out:
- kfree(device);
- iput(inode);
-
- return ret;
-}
-
-static const struct inode_operations binderfs_dir_inode_operations = {
- .lookup = simple_lookup,
- .rename = binderfs_rename,
- .unlink = binderfs_unlink,
-};
-
-static struct inode *binderfs_make_inode(struct super_block *sb, int mode)
-{
- struct inode *ret;
-
- ret = new_inode(sb);
- if (ret) {
- ret->i_ino = iunique(sb, BINDERFS_MAX_MINOR + INODE_OFFSET);
- ret->i_mode = mode;
- simple_inode_init_ts(ret);
- }
- return ret;
-}
-
-struct dentry *binderfs_create_file(struct dentry *parent, const char *name,
- const struct file_operations *fops,
- void *data)
-{
- struct dentry *dentry;
- struct inode *new_inode, *parent_inode;
- struct super_block *sb;
-
- parent_inode = d_inode(parent);
-
- dentry = simple_start_creating(parent, name);
- if (IS_ERR(dentry))
- return dentry;
-
- sb = parent_inode->i_sb;
- new_inode = binderfs_make_inode(sb, S_IFREG | 0444);
- if (!new_inode) {
- simple_done_creating(dentry);
- return ERR_PTR(-ENOMEM);
- }
-
- new_inode->i_fop = fops;
- new_inode->i_private = data;
- d_make_persistent(dentry, new_inode);
- fsnotify_create(parent_inode, dentry);
- simple_done_creating(dentry);
- return dentry; // borrowed
-}
-
-static struct dentry *binderfs_create_dir(struct dentry *parent,
- const char *name)
-{
- struct dentry *dentry;
- struct inode *new_inode, *parent_inode;
- struct super_block *sb;
-
- parent_inode = d_inode(parent);
-
- dentry = simple_start_creating(parent, name);
- if (IS_ERR(dentry))
- return dentry;
-
- sb = parent_inode->i_sb;
- new_inode = binderfs_make_inode(sb, S_IFDIR | 0755);
- if (!new_inode) {
- simple_done_creating(dentry);
- return ERR_PTR(-ENOMEM);
- }
-
- new_inode->i_fop = &simple_dir_operations;
- new_inode->i_op = &simple_dir_inode_operations;
-
- set_nlink(new_inode, 2);
- d_make_persistent(dentry, new_inode);
- inc_nlink(parent_inode);
- fsnotify_mkdir(parent_inode, dentry);
- simple_done_creating(dentry);
- return dentry;
-}
-
-static int binder_features_show(struct seq_file *m, void *unused)
-{
- bool *feature = m->private;
-
- seq_printf(m, "%d\n", *feature);
-
- return 0;
-}
-DEFINE_SHOW_ATTRIBUTE(binder_features);
-
-static int init_binder_features(struct super_block *sb)
-{
- struct dentry *dentry, *dir;
-
- dir = binderfs_create_dir(sb->s_root, "features");
- if (IS_ERR(dir))
- return PTR_ERR(dir);
-
- dentry = binderfs_create_file(dir, "oneway_spam_detection",
- &binder_features_fops,
- &binder_features.oneway_spam_detection);
- if (IS_ERR(dentry))
- return PTR_ERR(dentry);
-
- dentry = binderfs_create_file(dir, "extended_error",
- &binder_features_fops,
- &binder_features.extended_error);
- if (IS_ERR(dentry))
- return PTR_ERR(dentry);
-
- dentry = binderfs_create_file(dir, "freeze_notification",
- &binder_features_fops,
- &binder_features.freeze_notification);
- if (IS_ERR(dentry))
- return PTR_ERR(dentry);
-
- dentry = binderfs_create_file(dir, "transaction_report",
- &binder_features_fops,
- &binder_features.transaction_report);
- if (IS_ERR(dentry))
- return PTR_ERR(dentry);
-
- return 0;
-}
-
-static int init_binder_logs(struct super_block *sb)
-{
- struct dentry *binder_logs_root_dir, *dentry, *proc_log_dir;
- const struct binder_debugfs_entry *db_entry;
- struct binderfs_info *info;
- int ret = 0;
-
- binder_logs_root_dir = binderfs_create_dir(sb->s_root,
- "binder_logs");
- if (IS_ERR(binder_logs_root_dir)) {
- ret = PTR_ERR(binder_logs_root_dir);
- goto out;
- }
-
- binder_for_each_debugfs_entry(db_entry) {
- dentry = binderfs_create_file(binder_logs_root_dir,
- db_entry->name,
- db_entry->fops,
- db_entry->data);
- if (IS_ERR(dentry)) {
- ret = PTR_ERR(dentry);
- goto out;
- }
- }
-
- proc_log_dir = binderfs_create_dir(binder_logs_root_dir, "proc");
- if (IS_ERR(proc_log_dir)) {
- ret = PTR_ERR(proc_log_dir);
- goto out;
- }
- info = sb->s_fs_info;
- info->proc_log_dir = proc_log_dir;
-
-out:
- return ret;
-}
-
-static int binderfs_fill_super(struct super_block *sb, struct fs_context *fc)
-{
- int ret;
- struct binderfs_info *info;
- struct binderfs_mount_opts *ctx = fc->fs_private;
- struct inode *inode = NULL;
- struct binderfs_device device_info = {};
- const char *name;
- size_t len;
-
- sb->s_blocksize = PAGE_SIZE;
- sb->s_blocksize_bits = PAGE_SHIFT;
-
- /*
- * The binderfs filesystem can be mounted by userns root in a
- * non-initial userns. By default such mounts have the SB_I_NODEV flag
- * set in s_iflags to prevent security issues where userns root can
- * just create random device nodes via mknod() since it owns the
- * filesystem mount. But binderfs does not allow to create any files
- * including devices nodes. The only way to create binder devices nodes
- * is through the binder-control device which userns root is explicitly
- * allowed to do. So removing the SB_I_NODEV flag from s_iflags is both
- * necessary and safe.
- */
- sb->s_iflags &= ~SB_I_NODEV;
- sb->s_iflags |= SB_I_NOEXEC;
- sb->s_magic = BINDERFS_SUPER_MAGIC;
- sb->s_op = &binderfs_super_ops;
- sb->s_time_gran = 1;
-
- sb->s_fs_info = kzalloc_obj(struct binderfs_info);
- if (!sb->s_fs_info)
- return -ENOMEM;
- info = sb->s_fs_info;
-
- info->ipc_ns = get_ipc_ns(current->nsproxy->ipc_ns);
-
- info->root_gid = make_kgid(sb->s_user_ns, 0);
- if (!gid_valid(info->root_gid))
- info->root_gid = GLOBAL_ROOT_GID;
- info->root_uid = make_kuid(sb->s_user_ns, 0);
- if (!uid_valid(info->root_uid))
- info->root_uid = GLOBAL_ROOT_UID;
- info->mount_opts.max = ctx->max;
- info->mount_opts.stats_mode = ctx->stats_mode;
-
- inode = new_inode(sb);
- if (!inode)
- return -ENOMEM;
-
- inode->i_ino = FIRST_INODE;
- inode->i_fop = &simple_dir_operations;
- inode->i_mode = S_IFDIR | 0755;
- simple_inode_init_ts(inode);
- inode->i_op = &binderfs_dir_inode_operations;
- set_nlink(inode, 2);
-
- sb->s_root = d_make_root(inode);
- if (!sb->s_root)
- return -ENOMEM;
-
- ret = binderfs_binder_ctl_create(sb);
- if (ret)
- return ret;
-
- name = binder_devices_param;
- for (len = strcspn(name, ","); len > 0; len = strcspn(name, ",")) {
- strscpy(device_info.name, name, len + 1);
- ret = binderfs_binder_device_create(inode, NULL, &device_info);
- if (ret)
- return ret;
- name += len;
- if (*name == ',')
- name++;
- }
-
- ret = init_binder_features(sb);
- if (ret)
- return ret;
-
- if (info->mount_opts.stats_mode == binderfs_stats_mode_global)
- return init_binder_logs(sb);
-
- return 0;
-}
-
-static int binderfs_fs_context_get_tree(struct fs_context *fc)
-{
- return get_tree_nodev(fc, binderfs_fill_super);
-}
-
-static void binderfs_fs_context_free(struct fs_context *fc)
-{
- struct binderfs_mount_opts *ctx = fc->fs_private;
-
- kfree(ctx);
-}
-
-static const struct fs_context_operations binderfs_fs_context_ops = {
- .free = binderfs_fs_context_free,
- .get_tree = binderfs_fs_context_get_tree,
- .parse_param = binderfs_fs_context_parse_param,
- .reconfigure = binderfs_fs_context_reconfigure,
-};
-
-static int binderfs_init_fs_context(struct fs_context *fc)
-{
- struct binderfs_mount_opts *ctx;
-
- ctx = kzalloc_obj(struct binderfs_mount_opts);
- if (!ctx)
- return -ENOMEM;
-
- ctx->max = BINDERFS_MAX_MINOR;
- ctx->stats_mode = binderfs_stats_mode_unset;
-
- fc->fs_private = ctx;
- fc->ops = &binderfs_fs_context_ops;
-
- return 0;
-}
-
-static void binderfs_kill_super(struct super_block *sb)
-{
- struct binderfs_info *info = sb->s_fs_info;
-
- /*
- * During inode eviction struct binderfs_info is needed.
- * So first wipe the super_block then free struct binderfs_info.
- */
- kill_anon_super(sb);
-
- if (info && info->ipc_ns)
- put_ipc_ns(info->ipc_ns);
-
- kfree(info);
-}
-
-static struct file_system_type binder_fs_type = {
- .name = "binder",
- .init_fs_context = binderfs_init_fs_context,
- .parameters = binderfs_fs_parameters,
- .kill_sb = binderfs_kill_super,
- .fs_flags = FS_USERNS_MOUNT,
-};
-
-int __init init_binderfs(void)
-{
- int ret;
- const char *name;
- size_t len;
-
- /* Verify that the default binderfs device names are valid. */
- name = binder_devices_param;
- for (len = strcspn(name, ","); len > 0; len = strcspn(name, ",")) {
- if (len > BINDERFS_MAX_NAME)
- return -E2BIG;
- name += len;
- if (*name == ',')
- name++;
- }
-
- /* Allocate new major number for binderfs. */
- ret = alloc_chrdev_region(&binderfs_dev, 0, BINDERFS_MAX_MINOR,
- "binder");
- if (ret)
- return ret;
-
- ret = register_filesystem(&binder_fs_type);
- if (ret) {
- unregister_chrdev_region(binderfs_dev, BINDERFS_MAX_MINOR);
- return ret;
- }
-
- return ret;
-}
diff --git a/drivers/android/dbitmap.h b/drivers/android/dbitmap.h
deleted file mode 100644
index c7299ce8b374..000000000000
--- a/drivers/android/dbitmap.h
+++ /dev/null
@@ -1,169 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * Copyright 2024 Google LLC
- *
- * dbitmap - dynamically sized bitmap library.
- *
- * Used by the binder driver to optimize the allocation of the smallest
- * available descriptor ID. Each bit in the bitmap represents the state
- * of an ID.
- *
- * A dbitmap can grow or shrink as needed. This part has been designed
- * considering that users might need to briefly release their locks in
- * order to allocate memory for the new bitmap. These operations then,
- * are verified to determine if the grow or shrink is sill valid.
- *
- * This library does not provide protection against concurrent access
- * by itself. Binder uses the proc->outer_lock for this purpose.
- */
-
-#ifndef _LINUX_DBITMAP_H
-#define _LINUX_DBITMAP_H
-#include <linux/bitmap.h>
-
-#define NBITS_MIN BITS_PER_TYPE(unsigned long)
-
-struct dbitmap {
- unsigned int nbits;
- unsigned long *map;
-};
-
-static inline int dbitmap_enabled(struct dbitmap *dmap)
-{
- return !!dmap->nbits;
-}
-
-static inline void dbitmap_free(struct dbitmap *dmap)
-{
- dmap->nbits = 0;
- kfree(dmap->map);
- dmap->map = NULL;
-}
-
-/* Returns the nbits that a dbitmap can shrink to, 0 if not possible. */
-static inline unsigned int dbitmap_shrink_nbits(struct dbitmap *dmap)
-{
- unsigned int bit;
-
- if (dmap->nbits <= NBITS_MIN)
- return 0;
-
- /*
- * Determine if the bitmap can shrink based on the position of
- * its last set bit. If the bit is within the first quarter of
- * the bitmap then shrinking is possible. In this case, the
- * bitmap should shrink to half its current size.
- */
- bit = find_last_bit(dmap->map, dmap->nbits);
- if (bit < (dmap->nbits >> 2))
- return dmap->nbits >> 1;
-
- /* find_last_bit() returns dmap->nbits when no bits are set. */
- if (bit == dmap->nbits)
- return NBITS_MIN;
-
- return 0;
-}
-
-/* Replace the internal bitmap with a new one of different size */
-static inline void
-dbitmap_replace(struct dbitmap *dmap, unsigned long *new, unsigned int nbits)
-{
- bitmap_copy(new, dmap->map, min(dmap->nbits, nbits));
- kfree(dmap->map);
- dmap->map = new;
- dmap->nbits = nbits;
-}
-
-static inline void
-dbitmap_shrink(struct dbitmap *dmap, unsigned long *new, unsigned int nbits)
-{
- if (!new)
- return;
-
- /*
- * Verify that shrinking to @nbits is still possible. The @new
- * bitmap might have been allocated without locks, so this call
- * could now be outdated. In this case, free @new and move on.
- */
- if (!dbitmap_enabled(dmap) || dbitmap_shrink_nbits(dmap) != nbits) {
- kfree(new);
- return;
- }
-
- dbitmap_replace(dmap, new, nbits);
-}
-
-/* Returns the nbits that a dbitmap can grow to. */
-static inline unsigned int dbitmap_grow_nbits(struct dbitmap *dmap)
-{
- return dmap->nbits << 1;
-}
-
-static inline void
-dbitmap_grow(struct dbitmap *dmap, unsigned long *new, unsigned int nbits)
-{
- /*
- * Verify that growing to @nbits is still possible. The @new
- * bitmap might have been allocated without locks, so this call
- * could now be outdated. In this case, free @new and move on.
- */
- if (!dbitmap_enabled(dmap) || nbits <= dmap->nbits) {
- kfree(new);
- return;
- }
-
- /*
- * Check for ENOMEM after confirming the grow operation is still
- * required. This ensures we only disable the dbitmap when it's
- * necessary. Once the dbitmap is disabled, binder will fallback
- * to slow_desc_lookup_olocked().
- */
- if (!new) {
- dbitmap_free(dmap);
- return;
- }
-
- dbitmap_replace(dmap, new, nbits);
-}
-
-/*
- * Finds and sets the next zero bit in the bitmap. Upon success @bit
- * is populated with the index and 0 is returned. Otherwise, -ENOSPC
- * is returned to indicate that a dbitmap_grow() is needed.
- */
-static inline int
-dbitmap_acquire_next_zero_bit(struct dbitmap *dmap, unsigned long offset,
- unsigned long *bit)
-{
- unsigned long n;
-
- n = find_next_zero_bit(dmap->map, dmap->nbits, offset);
- if (n == dmap->nbits)
- return -ENOSPC;
-
- *bit = n;
- set_bit(n, dmap->map);
-
- return 0;
-}
-
-static inline void
-dbitmap_clear_bit(struct dbitmap *dmap, unsigned long bit)
-{
- clear_bit(bit, dmap->map);
-}
-
-static inline int dbitmap_init(struct dbitmap *dmap)
-{
- dmap->map = bitmap_zalloc(NBITS_MIN, GFP_KERNEL);
- if (!dmap->map) {
- dmap->nbits = 0;
- return -ENOMEM;
- }
-
- dmap->nbits = NBITS_MIN;
-
- return 0;
-}
-#endif
diff --git a/drivers/android/tests/.kunitconfig b/drivers/android/tests/.kunitconfig
deleted file mode 100644
index 39b76bab9d9a..000000000000
--- a/drivers/android/tests/.kunitconfig
+++ /dev/null
@@ -1,7 +0,0 @@
-#
-# Copyright 2025 Google LLC.
-#
-
-CONFIG_KUNIT=y
-CONFIG_ANDROID_BINDER_IPC=y
-CONFIG_ANDROID_BINDER_ALLOC_KUNIT_TEST=y
diff --git a/drivers/android/tests/Makefile b/drivers/android/tests/Makefile
deleted file mode 100644
index 27268418eb03..000000000000
--- a/drivers/android/tests/Makefile
+++ /dev/null
@@ -1,6 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-only
-#
-# Copyright 2025 Google LLC.
-#
-
-obj-$(CONFIG_ANDROID_BINDER_ALLOC_KUNIT_TEST) += binder_alloc_kunit.o
diff --git a/drivers/android/tests/binder_alloc_kunit.c b/drivers/android/tests/binder_alloc_kunit.c
deleted file mode 100644
index 7f9cc003bbe3..000000000000
--- a/drivers/android/tests/binder_alloc_kunit.c
+++ /dev/null
@@ -1,572 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Test cases for binder allocator code.
- *
- * Copyright 2025 Google LLC.
- * Author: Tiffany Yang <ynaffit@google.com>
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <kunit/test.h>
-#include <linux/anon_inodes.h>
-#include <linux/err.h>
-#include <linux/file.h>
-#include <linux/fs.h>
-#include <linux/mm.h>
-#include <linux/mman.h>
-#include <linux/seq_buf.h>
-#include <linux/sizes.h>
-
-#include "../binder_alloc.h"
-#include "../binder_internal.h"
-
-MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
-
-#define BINDER_MMAP_SIZE SZ_128K
-
-#define BUFFER_NUM 5
-#define BUFFER_MIN_SIZE (PAGE_SIZE / 8)
-
-#define FREESEQ_BUFLEN ((3 * BUFFER_NUM) + 1)
-
-#define ALIGN_TYPE_STRLEN (12)
-
-#define ALIGNMENTS_BUFLEN (((ALIGN_TYPE_STRLEN + 6) * BUFFER_NUM) + 1)
-
-#define PRINT_ALL_CASES (0)
-
-/* 5^5 alignment combinations * 2 places to share pages * 5! free sequences */
-#define TOTAL_EXHAUSTIVE_CASES (3125 * 2 * 120)
-
-/**
- * enum buf_end_align_type - Page alignment of a buffer
- * end with regard to the end of the previous buffer.
- *
- * In the pictures below, buf2 refers to the buffer we
- * are aligning. buf1 refers to previous buffer by addr.
- * Symbol [ means the start of a buffer, ] means the end
- * of a buffer, and | means page boundaries.
- */
-enum buf_end_align_type {
- /**
- * @SAME_PAGE_UNALIGNED: The end of this buffer is on
- * the same page as the end of the previous buffer and
- * is not page aligned. Examples:
- * buf1 ][ buf2 ][ ...
- * buf1 ]|[ buf2 ][ ...
- */
- SAME_PAGE_UNALIGNED = 0,
- /**
- * @SAME_PAGE_ALIGNED: When the end of the previous buffer
- * is not page aligned, the end of this buffer is on the
- * same page as the end of the previous buffer and is page
- * aligned. When the previous buffer is page aligned, the
- * end of this buffer is aligned to the next page boundary.
- * Examples:
- * buf1 ][ buf2 ]| ...
- * buf1 ]|[ buf2 ]| ...
- */
- SAME_PAGE_ALIGNED,
- /**
- * @NEXT_PAGE_UNALIGNED: The end of this buffer is on
- * the page next to the end of the previous buffer and
- * is not page aligned. Examples:
- * buf1 ][ buf2 | buf2 ][ ...
- * buf1 ]|[ buf2 | buf2 ][ ...
- */
- NEXT_PAGE_UNALIGNED,
- /**
- * @NEXT_PAGE_ALIGNED: The end of this buffer is on
- * the page next to the end of the previous buffer and
- * is page aligned. Examples:
- * buf1 ][ buf2 | buf2 ]| ...
- * buf1 ]|[ buf2 | buf2 ]| ...
- */
- NEXT_PAGE_ALIGNED,
- /**
- * @NEXT_NEXT_UNALIGNED: The end of this buffer is on
- * the page that follows the page after the end of the
- * previous buffer and is not page aligned. Examples:
- * buf1 ][ buf2 | buf2 | buf2 ][ ...
- * buf1 ]|[ buf2 | buf2 | buf2 ][ ...
- */
- NEXT_NEXT_UNALIGNED,
- /**
- * @LOOP_END: The number of enum values in &buf_end_align_type.
- * It is used for controlling loop termination.
- */
- LOOP_END,
-};
-
-static const char *const buf_end_align_type_strs[LOOP_END] = {
- [SAME_PAGE_UNALIGNED] = "SP_UNALIGNED",
- [SAME_PAGE_ALIGNED] = " SP_ALIGNED ",
- [NEXT_PAGE_UNALIGNED] = "NP_UNALIGNED",
- [NEXT_PAGE_ALIGNED] = " NP_ALIGNED ",
- [NEXT_NEXT_UNALIGNED] = "NN_UNALIGNED",
-};
-
-struct binder_alloc_test_case_info {
- char alignments[ALIGNMENTS_BUFLEN];
- struct seq_buf alignments_sb;
- size_t *buffer_sizes;
- int *free_sequence;
- bool front_pages;
-};
-
-static void stringify_free_seq(struct kunit *test, int *seq, struct seq_buf *sb)
-{
- int i;
-
- for (i = 0; i < BUFFER_NUM; i++)
- seq_buf_printf(sb, "[%d]", seq[i]);
-
- KUNIT_EXPECT_FALSE(test, seq_buf_has_overflowed(sb));
-}
-
-static void stringify_alignments(struct kunit *test, int *alignments,
- struct seq_buf *sb)
-{
- int i;
-
- for (i = 0; i < BUFFER_NUM; i++)
- seq_buf_printf(sb, "[ %d:%s ]", i,
- buf_end_align_type_strs[alignments[i]]);
-
- KUNIT_EXPECT_FALSE(test, seq_buf_has_overflowed(sb));
-}
-
-static bool check_buffer_pages_allocated(struct kunit *test,
- struct binder_alloc *alloc,
- struct binder_buffer *buffer,
- size_t size)
-{
- unsigned long page_addr;
- unsigned long end;
- int page_index;
-
- end = PAGE_ALIGN(buffer->user_data + size);
- page_addr = buffer->user_data;
- for (; page_addr < end; page_addr += PAGE_SIZE) {
- page_index = (page_addr - alloc->vm_start) / PAGE_SIZE;
- if (!alloc->pages[page_index] ||
- !list_empty(page_to_lru(alloc->pages[page_index]))) {
- kunit_err(test, "expect alloc but is %s at page index %d\n",
- alloc->pages[page_index] ?
- "lru" : "free", page_index);
- return false;
- }
- }
- return true;
-}
-
-static unsigned long binder_alloc_test_alloc_buf(struct kunit *test,
- struct binder_alloc *alloc,
- struct binder_buffer *buffers[],
- size_t *sizes, int *seq)
-{
- unsigned long failures = 0;
- int i;
-
- for (i = 0; i < BUFFER_NUM; i++) {
- buffers[i] = binder_alloc_new_buf(alloc, sizes[i], 0, 0, 0);
- if (IS_ERR(buffers[i]) ||
- !check_buffer_pages_allocated(test, alloc, buffers[i], sizes[i]))
- failures++;
- }
-
- return failures;
-}
-
-static unsigned long binder_alloc_test_free_buf(struct kunit *test,
- struct binder_alloc *alloc,
- struct binder_buffer *buffers[],
- size_t *sizes, int *seq, size_t end)
-{
- unsigned long failures = 0;
- int i;
-
- for (i = 0; i < BUFFER_NUM; i++)
- binder_alloc_free_buf(alloc, buffers[seq[i]]);
-
- for (i = 0; i <= (end - 1) / PAGE_SIZE; i++) {
- if (list_empty(page_to_lru(alloc->pages[i]))) {
- kunit_err(test, "expect lru but is %s at page index %d\n",
- alloc->pages[i] ? "alloc" : "free", i);
- failures++;
- }
- }
-
- return failures;
-}
-
-static unsigned long binder_alloc_test_free_page(struct kunit *test,
- struct binder_alloc *alloc)
-{
- unsigned long failures = 0;
- unsigned long count;
- int i;
-
- while ((count = list_lru_count(alloc->freelist))) {
- list_lru_walk(alloc->freelist, binder_alloc_free_page,
- NULL, count);
- }
-
- for (i = 0; i < (alloc->buffer_size / PAGE_SIZE); i++) {
- if (alloc->pages[i]) {
- kunit_err(test, "expect free but is %s at page index %d\n",
- list_empty(page_to_lru(alloc->pages[i])) ?
- "alloc" : "lru", i);
- failures++;
- }
- }
-
- return failures;
-}
-
-/* Executes one full test run for the given test case. */
-static bool binder_alloc_test_alloc_free(struct kunit *test,
- struct binder_alloc *alloc,
- struct binder_alloc_test_case_info *tc,
- size_t end)
-{
- unsigned long pages = PAGE_ALIGN(end) / PAGE_SIZE;
- struct binder_buffer *buffers[BUFFER_NUM];
- unsigned long failures;
- bool failed = false;
-
- failures = binder_alloc_test_alloc_buf(test, alloc, buffers,
- tc->buffer_sizes,
- tc->free_sequence);
- failed = failed || failures;
- KUNIT_EXPECT_EQ_MSG(test, failures, 0,
- "Initial allocation failed: %lu/%u buffers with errors",
- failures, BUFFER_NUM);
-
- failures = binder_alloc_test_free_buf(test, alloc, buffers,
- tc->buffer_sizes,
- tc->free_sequence, end);
- failed = failed || failures;
- KUNIT_EXPECT_EQ_MSG(test, failures, 0,
- "Initial buffers not freed correctly: %lu/%lu pages not on lru list",
- failures, pages);
-
- /* Allocate from lru. */
- failures = binder_alloc_test_alloc_buf(test, alloc, buffers,
- tc->buffer_sizes,
- tc->free_sequence);
- failed = failed || failures;
- KUNIT_EXPECT_EQ_MSG(test, failures, 0,
- "Reallocation failed: %lu/%u buffers with errors",
- failures, BUFFER_NUM);
-
- failures = list_lru_count(alloc->freelist);
- failed = failed || failures;
- KUNIT_EXPECT_EQ_MSG(test, failures, 0,
- "lru list should be empty after reallocation but still has %lu pages",
- failures);
-
- failures = binder_alloc_test_free_buf(test, alloc, buffers,
- tc->buffer_sizes,
- tc->free_sequence, end);
- failed = failed || failures;
- KUNIT_EXPECT_EQ_MSG(test, failures, 0,
- "Reallocated buffers not freed correctly: %lu/%lu pages not on lru list",
- failures, pages);
-
- failures = binder_alloc_test_free_page(test, alloc);
- failed = failed || failures;
- KUNIT_EXPECT_EQ_MSG(test, failures, 0,
- "Failed to clean up allocated pages: %lu/%lu pages still installed",
- failures, (alloc->buffer_size / PAGE_SIZE));
-
- return failed;
-}
-
-static bool is_dup(int *seq, int index, int val)
-{
- int i;
-
- for (i = 0; i < index; i++) {
- if (seq[i] == val)
- return true;
- }
- return false;
-}
-
-/* Generate BUFFER_NUM factorial free orders. */
-static void permute_frees(struct kunit *test, struct binder_alloc *alloc,
- struct binder_alloc_test_case_info *tc,
- unsigned long *runs, unsigned long *failures,
- int index, size_t end)
-{
- bool case_failed;
- int i;
-
- if (index == BUFFER_NUM) {
- DECLARE_SEQ_BUF(freeseq_sb, FREESEQ_BUFLEN);
-
- case_failed = binder_alloc_test_alloc_free(test, alloc, tc, end);
- *runs += 1;
- *failures += case_failed;
-
- if (case_failed || PRINT_ALL_CASES) {
- stringify_free_seq(test, tc->free_sequence,
- &freeseq_sb);
- kunit_err(test, "case %lu: [%s] | %s - %s - %s", *runs,
- case_failed ? "FAILED" : "PASSED",
- tc->front_pages ? "front" : "back ",
- seq_buf_str(&tc->alignments_sb),
- seq_buf_str(&freeseq_sb));
- }
-
- return;
- }
- for (i = 0; i < BUFFER_NUM; i++) {
- if (is_dup(tc->free_sequence, index, i))
- continue;
- tc->free_sequence[index] = i;
- permute_frees(test, alloc, tc, runs, failures, index + 1, end);
- }
-}
-
-static void gen_buf_sizes(struct kunit *test,
- struct binder_alloc *alloc,
- struct binder_alloc_test_case_info *tc,
- size_t *end_offset, unsigned long *runs,
- unsigned long *failures)
-{
- size_t last_offset, offset = 0;
- size_t front_sizes[BUFFER_NUM];
- size_t back_sizes[BUFFER_NUM];
- int seq[BUFFER_NUM] = {0};
- int i;
-
- tc->free_sequence = seq;
- for (i = 0; i < BUFFER_NUM; i++) {
- last_offset = offset;
- offset = end_offset[i];
- front_sizes[i] = offset - last_offset;
- back_sizes[BUFFER_NUM - i - 1] = front_sizes[i];
- }
- back_sizes[0] += alloc->buffer_size - end_offset[BUFFER_NUM - 1];
-
- /*
- * Buffers share the first or last few pages.
- * Only BUFFER_NUM - 1 buffer sizes are adjustable since
- * we need one giant buffer before getting to the last page.
- */
- tc->front_pages = true;
- tc->buffer_sizes = front_sizes;
- permute_frees(test, alloc, tc, runs, failures, 0,
- end_offset[BUFFER_NUM - 1]);
-
- tc->front_pages = false;
- tc->buffer_sizes = back_sizes;
- permute_frees(test, alloc, tc, runs, failures, 0, alloc->buffer_size);
-}
-
-static void gen_buf_offsets(struct kunit *test, struct binder_alloc *alloc,
- size_t *end_offset, int *alignments,
- unsigned long *runs, unsigned long *failures,
- int index)
-{
- size_t end, prev;
- int align;
-
- if (index == BUFFER_NUM) {
- struct binder_alloc_test_case_info tc = {0};
-
- seq_buf_init(&tc.alignments_sb, tc.alignments,
- ALIGNMENTS_BUFLEN);
- stringify_alignments(test, alignments, &tc.alignments_sb);
-
- gen_buf_sizes(test, alloc, &tc, end_offset, runs, failures);
- return;
- }
- prev = index == 0 ? 0 : end_offset[index - 1];
- end = prev;
-
- BUILD_BUG_ON(BUFFER_MIN_SIZE * BUFFER_NUM >= PAGE_SIZE);
-
- for (align = SAME_PAGE_UNALIGNED; align < LOOP_END; align++) {
- if (align % 2)
- end = ALIGN(end, PAGE_SIZE);
- else
- end += BUFFER_MIN_SIZE;
- end_offset[index] = end;
- alignments[index] = align;
- gen_buf_offsets(test, alloc, end_offset, alignments, runs,
- failures, index + 1);
- }
-}
-
-struct binder_alloc_test {
- struct binder_alloc alloc;
- struct list_lru binder_test_freelist;
- struct file *filp;
- unsigned long mmap_uaddr;
-};
-
-static void binder_alloc_test_init_freelist(struct kunit *test)
-{
- struct binder_alloc_test *priv = test->priv;
-
- KUNIT_EXPECT_PTR_EQ(test, priv->alloc.freelist,
- &priv->binder_test_freelist);
-}
-
-static void binder_alloc_test_mmap(struct kunit *test)
-{
- struct binder_alloc_test *priv = test->priv;
- struct binder_alloc *alloc = &priv->alloc;
- struct binder_buffer *buf;
- struct rb_node *n;
-
- KUNIT_EXPECT_EQ(test, alloc->mapped, true);
- KUNIT_EXPECT_EQ(test, alloc->buffer_size, BINDER_MMAP_SIZE);
-
- n = rb_first(&alloc->allocated_buffers);
- KUNIT_EXPECT_PTR_EQ(test, n, NULL);
-
- n = rb_first(&alloc->free_buffers);
- buf = rb_entry(n, struct binder_buffer, rb_node);
- KUNIT_EXPECT_EQ(test, binder_alloc_buffer_size(alloc, buf),
- BINDER_MMAP_SIZE);
- KUNIT_EXPECT_TRUE(test, list_is_last(&buf->entry, &alloc->buffers));
-}
-
-/**
- * binder_alloc_exhaustive_test() - Exhaustively test alloc and free of buffer pages.
- * @test: The test context object.
- *
- * Allocate BUFFER_NUM buffers to cover all page alignment cases,
- * then free them in all orders possible. Check that pages are
- * correctly allocated, put onto lru when buffers are freed, and
- * are freed when binder_alloc_free_page() is called.
- */
-static void binder_alloc_exhaustive_test(struct kunit *test)
-{
- struct binder_alloc_test *priv = test->priv;
- size_t end_offset[BUFFER_NUM];
- int alignments[BUFFER_NUM];
- unsigned long failures = 0;
- unsigned long runs = 0;
-
- gen_buf_offsets(test, &priv->alloc, end_offset, alignments, &runs,
- &failures, 0);
-
- KUNIT_EXPECT_EQ(test, runs, TOTAL_EXHAUSTIVE_CASES);
- KUNIT_EXPECT_EQ(test, failures, 0);
-}
-
-/* ===== End test cases ===== */
-
-static void binder_alloc_test_vma_close(struct vm_area_struct *vma)
-{
- struct binder_alloc *alloc = vma->vm_private_data;
-
- binder_alloc_vma_close(alloc);
-}
-
-static const struct vm_operations_struct binder_alloc_test_vm_ops = {
- .close = binder_alloc_test_vma_close,
- .fault = binder_vm_fault,
-};
-
-static int binder_alloc_test_mmap_handler(struct file *filp,
- struct vm_area_struct *vma)
-{
- struct binder_alloc *alloc = filp->private_data;
-
- vm_flags_mod(vma, VM_DONTCOPY | VM_MIXEDMAP, VM_MAYWRITE);
-
- vma->vm_ops = &binder_alloc_test_vm_ops;
- vma->vm_private_data = alloc;
-
- return binder_alloc_mmap_handler(alloc, vma);
-}
-
-static const struct file_operations binder_alloc_test_fops = {
- .mmap = binder_alloc_test_mmap_handler,
-};
-
-static int binder_alloc_test_init(struct kunit *test)
-{
- struct binder_alloc_test *priv;
- int ret;
-
- priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
- test->priv = priv;
-
- ret = list_lru_init(&priv->binder_test_freelist);
- if (ret) {
- kunit_err(test, "Failed to initialize test freelist\n");
- return ret;
- }
-
- /* __binder_alloc_init requires mm to be attached */
- ret = kunit_attach_mm();
- if (ret) {
- kunit_err(test, "Failed to attach mm\n");
- return ret;
- }
- __binder_alloc_init(&priv->alloc, &priv->binder_test_freelist);
-
- priv->filp = anon_inode_getfile("binder_alloc_kunit",
- &binder_alloc_test_fops, &priv->alloc,
- O_RDWR | O_CLOEXEC);
- if (IS_ERR_OR_NULL(priv->filp)) {
- kunit_err(test, "Failed to open binder alloc test driver file\n");
- return priv->filp ? PTR_ERR(priv->filp) : -ENOMEM;
- }
-
- priv->mmap_uaddr = kunit_vm_mmap(test, priv->filp, 0, BINDER_MMAP_SIZE,
- PROT_READ, MAP_PRIVATE | MAP_NORESERVE,
- 0);
- if (!priv->mmap_uaddr) {
- kunit_err(test, "Could not map the test's transaction memory\n");
- return -ENOMEM;
- }
-
- return 0;
-}
-
-static void binder_alloc_test_exit(struct kunit *test)
-{
- struct binder_alloc_test *priv = test->priv;
-
- /* Close the backing file to make sure binder_alloc_vma_close runs */
- if (!IS_ERR_OR_NULL(priv->filp))
- fput(priv->filp);
-
- if (priv->alloc.mm)
- binder_alloc_deferred_release(&priv->alloc);
-
- /* Make sure freelist is empty */
- KUNIT_EXPECT_EQ(test, list_lru_count(&priv->binder_test_freelist), 0);
- list_lru_destroy(&priv->binder_test_freelist);
-}
-
-static struct kunit_case binder_alloc_test_cases[] = {
- KUNIT_CASE(binder_alloc_test_init_freelist),
- KUNIT_CASE(binder_alloc_test_mmap),
- KUNIT_CASE_SLOW(binder_alloc_exhaustive_test),
- {}
-};
-
-static struct kunit_suite binder_alloc_test_suite = {
- .name = "binder_alloc",
- .test_cases = binder_alloc_test_cases,
- .init = binder_alloc_test_init,
- .exit = binder_alloc_test_exit,
-};
-
-kunit_test_suite(binder_alloc_test_suite);
-
-MODULE_AUTHOR("Tiffany Yang <ynaffit@google.com>");
-MODULE_DESCRIPTION("Binder Alloc KUnit tests");
-MODULE_LICENSE("GPL");
diff --git a/drivers/bus/mhi/ep/Kconfig b/drivers/bus/mhi/ep/Kconfig
index 90ab3b040672..9edb81b39890 100644
--- a/drivers/bus/mhi/ep/Kconfig
+++ b/drivers/bus/mhi/ep/Kconfig
@@ -8,3 +8,5 @@ config MHI_BUS_EP
MHI_BUS_EP implements the MHI protocol for the endpoint devices,
such as SDX55 modem connected to the host machine over PCIe.
+
+source "drivers/bus/mhi/ep/clients/Kconfig"
diff --git a/drivers/bus/mhi/ep/Makefile b/drivers/bus/mhi/ep/Makefile
index aad85f180b70..ab36ef2a40ab 100644
--- a/drivers/bus/mhi/ep/Makefile
+++ b/drivers/bus/mhi/ep/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_MHI_BUS_EP) += mhi_ep.o
mhi_ep-y := main.o mmio.o ring.o sm.o
+obj-y += clients/
diff --git a/drivers/bus/mhi/ep/clients/Kconfig b/drivers/bus/mhi/ep/clients/Kconfig
new file mode 100644
index 000000000000..4cf27184058c
--- /dev/null
+++ b/drivers/bus/mhi/ep/clients/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config MHI_BUS_EP_LOOPBACK
+ tristate "MHI Endpoint LOOPBACK client driver"
+ depends on MHI_BUS_EP
+ help
+ MHI Endpoint LOOPBACK client driver that binds to the MHI LOOPBACK
+ channel as defined in the MHI specification. The LOOPBACK channel is
+ implemented by MHI-based endpoint devices (modems, WLAN) in the field,
+ where the endpoint firmware echoes back whatever the host sends.
+
+ This driver receives data on the uplink channel and echoes it back on
+ the downlink channel for testing the MHI endpoint data path.
+
+ To compile this driver as a module, choose M here. The module
+ will be called mhi_ep_loopback.
diff --git a/drivers/bus/mhi/ep/clients/Makefile b/drivers/bus/mhi/ep/clients/Makefile
new file mode 100644
index 000000000000..71dc91cc63b0
--- /dev/null
+++ b/drivers/bus/mhi/ep/clients/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_MHI_BUS_EP_LOOPBACK) += mhi_ep_loopback.o
+mhi_ep_loopback-y += loopback.o
diff --git a/drivers/bus/mhi/ep/clients/loopback.c b/drivers/bus/mhi/ep/clients/loopback.c
new file mode 100644
index 000000000000..c7452428d0b7
--- /dev/null
+++ b/drivers/bus/mhi/ep/clients/loopback.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/mhi_ep.h>
+#include <linux/module.h>
+#include <linux/string.h>
+
+struct mhi_ep_loopback {
+ struct workqueue_struct *wq;
+ struct mhi_ep_device *mdev;
+};
+
+struct mhi_ep_loopback_work {
+ struct mhi_ep_device *mdev;
+ struct work_struct work;
+ void *buf;
+ size_t len;
+};
+
+static void mhi_ep_loopback_work_handler(struct work_struct *work)
+{
+ struct mhi_ep_loopback_work *mhi_ep_lb_work = container_of(work,
+ struct mhi_ep_loopback_work, work);
+ int ret;
+
+ ret = mhi_ep_queue_buf(mhi_ep_lb_work->mdev, mhi_ep_lb_work->buf,
+ mhi_ep_lb_work->len);
+ if (ret) {
+ dev_err(&mhi_ep_lb_work->mdev->dev, "Failed to queue buffer: %d\n", ret);
+ kfree(mhi_ep_lb_work->buf);
+ }
+
+ kfree(mhi_ep_lb_work);
+}
+
+static void mhi_ep_loopback_ul_callback(struct mhi_ep_device *mhi_dev,
+ struct mhi_result *mhi_res)
+{
+ struct mhi_ep_loopback *mhi_ep_lb = dev_get_drvdata(&mhi_dev->dev);
+ struct mhi_ep_loopback_work *mhi_ep_lb_work;
+ void *buf;
+
+ if (!mhi_ep_lb)
+ return;
+
+ if (!mhi_res->transaction_status) {
+ if (!mhi_res->bytes_xferd)
+ return;
+
+ buf = kmemdup(mhi_res->buf_addr, mhi_res->bytes_xferd, GFP_KERNEL);
+ if (!buf)
+ return;
+
+ mhi_ep_lb_work = kmalloc_obj(*mhi_ep_lb_work, GFP_KERNEL);
+ if (!mhi_ep_lb_work) {
+ kfree(buf);
+ return;
+ }
+
+ INIT_WORK(&mhi_ep_lb_work->work, mhi_ep_loopback_work_handler);
+ mhi_ep_lb_work->mdev = mhi_dev;
+ mhi_ep_lb_work->buf = buf;
+ mhi_ep_lb_work->len = mhi_res->bytes_xferd;
+
+ queue_work(mhi_ep_lb->wq, &mhi_ep_lb_work->work);
+ }
+}
+
+static void mhi_ep_loopback_dl_callback(struct mhi_ep_device *mhi_dev,
+ struct mhi_result *mhi_res)
+{
+ kfree(mhi_res->buf_addr);
+}
+
+static int mhi_ep_loopback_probe(struct mhi_ep_device *mhi_dev, const struct mhi_device_id *id)
+{
+ struct mhi_ep_loopback *mhi_ep_lb;
+
+ mhi_ep_lb = devm_kzalloc(&mhi_dev->dev, sizeof(*mhi_ep_lb), GFP_KERNEL);
+ if (!mhi_ep_lb)
+ return -ENOMEM;
+
+ mhi_ep_lb->wq = alloc_ordered_workqueue("mhi_ep_loopback", WQ_MEM_RECLAIM);
+ if (!mhi_ep_lb->wq) {
+ dev_err(&mhi_dev->dev, "Failed to create workqueue\n");
+ return -ENOMEM;
+ }
+
+ mhi_ep_lb->mdev = mhi_dev;
+ dev_set_drvdata(&mhi_dev->dev, mhi_ep_lb);
+
+ return 0;
+}
+
+static void mhi_ep_loopback_remove(struct mhi_ep_device *mhi_dev)
+{
+ struct mhi_ep_loopback *mhi_ep_lb = dev_get_drvdata(&mhi_dev->dev);
+
+ destroy_workqueue(mhi_ep_lb->wq);
+}
+
+static const struct mhi_device_id mhi_ep_loopback_id_table[] = {
+ { .chan = "LOOPBACK" },
+ { }
+};
+MODULE_DEVICE_TABLE(mhi, mhi_ep_loopback_id_table);
+
+static struct mhi_ep_driver mhi_ep_loopback_driver = {
+ .probe = mhi_ep_loopback_probe,
+ .remove = mhi_ep_loopback_remove,
+ .dl_xfer_cb = mhi_ep_loopback_dl_callback,
+ .ul_xfer_cb = mhi_ep_loopback_ul_callback,
+ .id_table = mhi_ep_loopback_id_table,
+ .driver = {
+ .name = "mhi_ep_loopback",
+ },
+};
+
+module_mhi_ep_driver(mhi_ep_loopback_driver);
+
+MODULE_AUTHOR("Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>");
+MODULE_AUTHOR("Sumit Kumar <sumit.kumar@oss.qualcomm.com>");
+MODULE_DESCRIPTION("MHI Endpoint Loopback driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/bus/mhi/ep/main.c b/drivers/bus/mhi/ep/main.c
index db7ee6547eef..4be20c368536 100644
--- a/drivers/bus/mhi/ep/main.c
+++ b/drivers/bus/mhi/ep/main.c
@@ -504,7 +504,7 @@ static int mhi_ep_process_ch_ring(struct mhi_ep_ring *ring)
return 0;
}
-static void mhi_ep_skb_completion(struct mhi_ep_buf_info *buf_info)
+static void mhi_ep_buf_completion(struct mhi_ep_buf_info *buf_info)
{
struct mhi_ep_device *mhi_dev = buf_info->mhi_dev;
struct mhi_ep_cntrl *mhi_cntrl = mhi_dev->mhi_cntrl;
@@ -534,20 +534,21 @@ static void mhi_ep_skb_completion(struct mhi_ep_buf_info *buf_info)
}
/* TODO: Handle partially formed TDs */
-int mhi_ep_queue_skb(struct mhi_ep_device *mhi_dev, struct sk_buff *skb)
+static int mhi_ep_queue(struct mhi_ep_device *mhi_dev, void *buf, size_t len,
+ void *cb_buf)
{
struct mhi_ep_cntrl *mhi_cntrl = mhi_dev->mhi_cntrl;
struct mhi_ep_chan *mhi_chan = mhi_dev->dl_chan;
struct device *dev = &mhi_chan->mhi_dev->dev;
struct mhi_ep_buf_info buf_info = {};
struct mhi_ring_element *el;
- u32 buf_left, read_offset;
+ size_t buf_left, read_offset;
struct mhi_ep_ring *ring;
size_t tr_len;
u32 tre_len;
int ret;
- buf_left = skb->len;
+ buf_left = len;
ring = &mhi_cntrl->mhi_chan[mhi_chan->chan].ring;
mutex_lock(&mhi_chan->lock);
@@ -570,13 +571,13 @@ int mhi_ep_queue_skb(struct mhi_ep_device *mhi_dev, struct sk_buff *skb)
tre_len = MHI_TRE_DATA_GET_LEN(el);
tr_len = min(buf_left, tre_len);
- read_offset = skb->len - buf_left;
+ read_offset = len - buf_left;
- buf_info.dev_addr = skb->data + read_offset;
+ buf_info.dev_addr = buf + read_offset;
buf_info.host_addr = MHI_TRE_DATA_GET_PTR(el);
buf_info.size = tr_len;
- buf_info.cb = mhi_ep_skb_completion;
- buf_info.cb_buf = skb;
+ buf_info.cb = mhi_ep_buf_completion;
+ buf_info.cb_buf = cb_buf;
buf_info.mhi_dev = mhi_dev;
/*
@@ -615,8 +616,19 @@ err_exit:
return ret;
}
+
+int mhi_ep_queue_skb(struct mhi_ep_device *mhi_dev, struct sk_buff *skb)
+{
+ return mhi_ep_queue(mhi_dev, skb->data, skb->len, skb);
+}
EXPORT_SYMBOL_GPL(mhi_ep_queue_skb);
+int mhi_ep_queue_buf(struct mhi_ep_device *mhi_dev, void *buf, size_t len)
+{
+ return mhi_ep_queue(mhi_dev, buf, len, buf);
+}
+EXPORT_SYMBOL_GPL(mhi_ep_queue_buf);
+
static int mhi_ep_cache_host_cfg(struct mhi_ep_cntrl *mhi_cntrl)
{
size_t cmd_ctx_host_size, ch_ctx_host_size, ev_ctx_host_size;
@@ -1534,8 +1546,8 @@ int mhi_ep_register_controller(struct mhi_ep_cntrl *mhi_cntrl,
mhi_cntrl->index = ret;
- irq_set_status_flags(mhi_cntrl->irq, IRQ_NOAUTOEN);
- ret = request_irq(mhi_cntrl->irq, mhi_ep_irq, IRQF_TRIGGER_HIGH,
+ ret = request_irq(mhi_cntrl->irq, mhi_ep_irq,
+ IRQF_TRIGGER_HIGH | IRQF_NO_AUTOEN,
"doorbell_irq", mhi_cntrl);
if (ret) {
dev_err(mhi_cntrl->cntrl_dev, "Failed to request Doorbell IRQ\n");
diff --git a/drivers/bus/mhi/host/Kconfig b/drivers/bus/mhi/host/Kconfig
index da5cd0c9fc62..627c57948235 100644
--- a/drivers/bus/mhi/host/Kconfig
+++ b/drivers/bus/mhi/host/Kconfig
@@ -29,3 +29,4 @@ config MHI_BUS_PCI_GENERIC
This driver provides MHI PCI controller driver for devices such as
Qualcomm SDX55 based PCIe modems.
+source "drivers/bus/mhi/host/clients/Kconfig"
diff --git a/drivers/bus/mhi/host/Makefile b/drivers/bus/mhi/host/Makefile
index 859c2f38451c..2a16008aeb38 100644
--- a/drivers/bus/mhi/host/Makefile
+++ b/drivers/bus/mhi/host/Makefile
@@ -4,3 +4,4 @@ mhi-$(CONFIG_MHI_BUS_DEBUG) += debugfs.o
obj-$(CONFIG_MHI_BUS_PCI_GENERIC) += mhi_pci_generic.o
mhi_pci_generic-y += pci_generic.o
+obj-y += clients/
diff --git a/drivers/bus/mhi/host/clients/Kconfig b/drivers/bus/mhi/host/clients/Kconfig
new file mode 100644
index 000000000000..8bb5715e61d0
--- /dev/null
+++ b/drivers/bus/mhi/host/clients/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config MHI_BUS_LOOPBACK
+ tristate "MHI LOOPBACK client driver"
+ depends on MHI_BUS
+ help
+ MHI LOOPBACK client driver that binds to the MHI LOOPBACK channel
+ as defined in the MHI specification. The LOOPBACK channel is
+ implemented by MHI-based devices (e.g. modems, WLAN) in the field,
+ where the endpoint firmware echoes back whatever the host sends.
+
+ This driver exposes a sysfs interface for testing MHI data path
+ integrity between host and endpoint. Users can configure the TRE
+ size and count, and trigger a loopback test.
+
+ To compile this driver as a module, choose M here. The module
+ will be called mhi_loopback.
diff --git a/drivers/bus/mhi/host/clients/Makefile b/drivers/bus/mhi/host/clients/Makefile
new file mode 100644
index 000000000000..3811b6928f42
--- /dev/null
+++ b/drivers/bus/mhi/host/clients/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_MHI_BUS_LOOPBACK) += mhi_loopback.o
+mhi_loopback-y += loopback.o
diff --git a/drivers/bus/mhi/host/clients/loopback.c b/drivers/bus/mhi/host/clients/loopback.c
new file mode 100644
index 000000000000..35312f025598
--- /dev/null
+++ b/drivers/bus/mhi/host/clients/loopback.c
@@ -0,0 +1,312 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/atomic.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/errno.h>
+#include <linux/mhi.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/random.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#define MHI_LOOPBACK_DEFAULT_TRE_SIZE 32
+#define MHI_LOOPBACK_DEFAULT_NUM_TRE 1
+#define MHI_LOOPBACK_TIMEOUT_MS 5000
+#define MHI_LOOPBACK_MAX_TRE_SIZE (SZ_64K - 1)
+
+struct mhi_loopback {
+ struct mhi_device *mdev;
+ /* Serializes the sysfs attributes against a running test */
+ struct mutex lb_mutex;
+ struct completion comp;
+ /* Counts pending DL completions; comp fires when it hits zero */
+ atomic_t tre_pending;
+ u32 num_tre;
+ u32 tre_size;
+};
+
+static ssize_t tre_size_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mhi_loopback *loopback = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", loopback->tre_size);
+}
+
+static ssize_t tre_size_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mhi_loopback *loopback = dev_get_drvdata(dev);
+ u32 val;
+
+ if (kstrtou32(buf, 0, &val))
+ return -EINVAL;
+
+ if (val == 0 || val > MHI_LOOPBACK_MAX_TRE_SIZE)
+ return -EINVAL;
+
+ guard(mutex)(&loopback->lb_mutex);
+ loopback->tre_size = val;
+
+ return count;
+}
+static DEVICE_ATTR_RW(tre_size);
+
+static ssize_t max_tre_size_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "%u\n", MHI_LOOPBACK_MAX_TRE_SIZE);
+}
+static DEVICE_ATTR_RO(max_tre_size);
+
+static int mhi_loopback_ring_capacity(struct mhi_loopback *loopback)
+{
+ return min(mhi_get_free_desc_count(loopback->mdev, DMA_TO_DEVICE),
+ mhi_get_free_desc_count(loopback->mdev, DMA_FROM_DEVICE));
+}
+
+static ssize_t max_num_tre_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mhi_loopback *loopback = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", mhi_loopback_ring_capacity(loopback));
+}
+static DEVICE_ATTR_RO(max_num_tre);
+
+static ssize_t num_tre_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mhi_loopback *loopback = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", loopback->num_tre);
+}
+
+static ssize_t num_tre_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mhi_loopback *loopback = dev_get_drvdata(dev);
+ u32 val;
+ int el_num;
+
+ if (kstrtou32(buf, 0, &val))
+ return -EINVAL;
+
+ if (val == 0)
+ return -EINVAL;
+
+ guard(mutex)(&loopback->lb_mutex);
+
+ el_num = mhi_loopback_ring_capacity(loopback);
+ if (val > el_num) {
+ dev_err(dev, "num_tre (%u) exceeds ring capacity (%d)\n", val, el_num);
+ return -EINVAL;
+ }
+
+ loopback->num_tre = val;
+
+ return count;
+}
+static DEVICE_ATTR_RW(num_tre);
+
+static void mhi_loopback_reset_chan(struct mhi_loopback *loopback)
+{
+ struct device *dev = &loopback->mdev->dev;
+ int ret;
+
+ mhi_unprepare_from_transfer(loopback->mdev);
+ ret = mhi_prepare_for_transfer(loopback->mdev);
+ if (ret)
+ dev_err(dev, "Failed to re-prepare channel for transfers: %d\n", ret);
+}
+
+static ssize_t start_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mhi_loopback *loopback = dev_get_drvdata(dev);
+ u32 total_size, tre_count, tre_size;
+ int i, ret;
+
+ guard(mutex)(&loopback->lb_mutex);
+
+ tre_size = loopback->tre_size;
+ tre_count = loopback->num_tre;
+ total_size = size_mul(tre_count, tre_size);
+
+ if (total_size > KMALLOC_MAX_SIZE)
+ return -EINVAL;
+
+ if (tre_count > mhi_loopback_ring_capacity(loopback)) {
+ dev_err(dev, "Not enough ring space for %u TREs\n", tre_count);
+ return -ENOSPC;
+ }
+
+ void *recv_buf __free(kfree) = kzalloc(total_size, GFP_KERNEL);
+ if (!recv_buf)
+ return -ENOMEM;
+
+ void *send_buf __free(kfree) = kzalloc(total_size, GFP_KERNEL);
+ if (!send_buf)
+ return -ENOMEM;
+
+ get_random_bytes(send_buf, total_size);
+
+ atomic_set(&loopback->tre_pending, tre_count);
+ reinit_completion(&loopback->comp);
+
+ for (i = 0; i < tre_count; i++) {
+ ret = mhi_queue_buf(loopback->mdev, DMA_FROM_DEVICE,
+ recv_buf + (i * tre_size), tre_size, MHI_EOT);
+ if (ret) {
+ dev_err(dev, "Unable to queue read TRE %d: %d\n", i, ret);
+ /*
+ * TREs already queued for this iteration still point at
+ * recv_buf, which is about to be freed. Reset the channel
+ * to reclaim them before returning.
+ */
+ mhi_loopback_reset_chan(loopback);
+ return ret;
+ }
+ }
+
+ for (i = 0; i < tre_count - 1; i++) {
+ ret = mhi_queue_buf(loopback->mdev, DMA_TO_DEVICE,
+ send_buf + (i * tre_size), tre_size, MHI_CHAIN);
+ if (ret) {
+ dev_err(dev, "Unable to queue send TRE %d: %d\n", i, ret);
+ /* Reclaim the queued DL and UL TREs before recv_buf/send_buf are freed */
+ mhi_loopback_reset_chan(loopback);
+ return ret;
+ }
+ }
+
+ ret = mhi_queue_buf(loopback->mdev, DMA_TO_DEVICE,
+ send_buf + (i * tre_size), tre_size, MHI_EOT);
+ if (ret) {
+ dev_err(dev, "Unable to queue final TRE: %d\n", ret);
+ /* Reclaim the queued DL and UL TREs before recv_buf/send_buf are freed */
+ mhi_loopback_reset_chan(loopback);
+ return ret;
+ }
+
+ if (!wait_for_completion_timeout(&loopback->comp,
+ msecs_to_jiffies(MHI_LOOPBACK_TIMEOUT_MS))) {
+ dev_err(dev, "Loopback test timed out\n");
+ mhi_loopback_reset_chan(loopback);
+ return -ETIMEDOUT;
+ }
+
+ if (memcmp(send_buf, recv_buf, total_size)) {
+ dev_err(dev, "Loopback data mismatch\n");
+ return -EIO;
+ }
+
+ return count;
+}
+static DEVICE_ATTR_WO(start);
+
+static void mhi_loopback_dl_callback(struct mhi_device *mhi_dev,
+ struct mhi_result *mhi_res)
+{
+ struct mhi_loopback *loopback = dev_get_drvdata(&mhi_dev->dev);
+
+ if (mhi_res->transaction_status && mhi_res->transaction_status != -ENOTCONN)
+ dev_err(&mhi_dev->dev, "DL callback error: status %d\n",
+ mhi_res->transaction_status);
+
+ if (atomic_dec_and_test(&loopback->tre_pending))
+ complete(&loopback->comp);
+}
+
+static void mhi_loopback_ul_callback(struct mhi_device *mhi_dev,
+ struct mhi_result *mhi_res)
+{
+ if (mhi_res->transaction_status && mhi_res->transaction_status != -ENOTCONN)
+ dev_err(&mhi_dev->dev, "UL callback error: status %d\n",
+ mhi_res->transaction_status);
+}
+
+static struct attribute *mhi_loopback_attrs[] = {
+ &dev_attr_tre_size.attr,
+ &dev_attr_max_tre_size.attr,
+ &dev_attr_num_tre.attr,
+ &dev_attr_max_num_tre.attr,
+ &dev_attr_start.attr,
+ NULL
+};
+
+static const struct attribute_group mhi_loopback_group = {
+ .attrs = mhi_loopback_attrs,
+};
+
+static const struct attribute_group *mhi_loopback_groups[] = {
+ &mhi_loopback_group,
+ NULL,
+};
+
+static int mhi_loopback_probe(struct mhi_device *mhi_dev,
+ const struct mhi_device_id *id)
+{
+ struct mhi_loopback *loopback;
+ int ret;
+
+ loopback = devm_kzalloc(&mhi_dev->dev, sizeof(*loopback), GFP_KERNEL);
+ if (!loopback)
+ return -ENOMEM;
+
+ loopback->mdev = mhi_dev;
+ loopback->tre_size = MHI_LOOPBACK_DEFAULT_TRE_SIZE;
+ loopback->num_tre = MHI_LOOPBACK_DEFAULT_NUM_TRE;
+
+ mutex_init(&loopback->lb_mutex);
+ init_completion(&loopback->comp);
+
+ dev_set_drvdata(&mhi_dev->dev, loopback);
+
+ ret = mhi_prepare_for_transfer(mhi_dev);
+ if (ret)
+ return dev_err_probe(&mhi_dev->dev, ret, "Failed to prepare for transfers\n");
+
+ return 0;
+}
+
+static void mhi_loopback_remove(struct mhi_device *mhi_dev)
+{
+ mhi_unprepare_from_transfer(mhi_dev);
+}
+
+static const struct mhi_device_id mhi_loopback_id_table[] = {
+ { .chan = "LOOPBACK" },
+ { }
+};
+MODULE_DEVICE_TABLE(mhi, mhi_loopback_id_table);
+
+static struct mhi_driver mhi_loopback_driver = {
+ .probe = mhi_loopback_probe,
+ .remove = mhi_loopback_remove,
+ .dl_xfer_cb = mhi_loopback_dl_callback,
+ .ul_xfer_cb = mhi_loopback_ul_callback,
+ .id_table = mhi_loopback_id_table,
+ .driver = {
+ .name = "mhi_loopback",
+ .dev_groups = mhi_loopback_groups,
+ },
+};
+
+module_mhi_driver(mhi_loopback_driver);
+
+MODULE_AUTHOR("Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>");
+MODULE_AUTHOR("Sumit Kumar <sumit.kumar@oss.qualcomm.com>");
+MODULE_DESCRIPTION("MHI Host Loopback Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/bus/mhi/host/debugfs.c b/drivers/bus/mhi/host/debugfs.c
index 39e45748a24c..93f458a2557d 100644
--- a/drivers/bus/mhi/host/debugfs.c
+++ b/drivers/bus/mhi/host/debugfs.c
@@ -72,7 +72,7 @@ static int mhi_debugfs_events_show(struct seq_file *m, void *d)
seq_printf(m, " rp: 0x%llx wp: 0x%llx", le64_to_cpu(er_ctxt->rp),
le64_to_cpu(er_ctxt->wp));
- seq_printf(m, " local rp: 0x%pK db: 0x%pad\n", ring->rp,
+ seq_printf(m, " local rp: 0x%p db: 0x%pad\n", ring->rp,
&mhi_event->db_cfg.db_val);
}
@@ -120,7 +120,7 @@ static int mhi_debugfs_channels_show(struct seq_file *m, void *d)
le64_to_cpu(chan_ctxt->rbase), le64_to_cpu(chan_ctxt->rlen),
le64_to_cpu(chan_ctxt->rp), le64_to_cpu(chan_ctxt->wp));
- seq_printf(m, " local rp: 0x%pK local wp: 0x%pK db: 0x%pad\n",
+ seq_printf(m, " local rp: 0x%p local wp: 0x%p db: 0x%pad\n",
ring->rp, ring->wp,
&mhi_chan->db_cfg.db_val);
}
diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c
index b636e2c23b4d..7193a384cca4 100644
--- a/drivers/bus/mhi/host/pci_generic.c
+++ b/drivers/bus/mhi/host/pci_generic.c
@@ -322,6 +322,8 @@ static const struct mhi_channel_config modem_qcom_v1_mhi_channels[] = {
MHI_CHANNEL_CONFIG_DL(15, "QMI", 4, 0),
MHI_CHANNEL_CONFIG_UL(20, "IPCR", 8, 0),
MHI_CHANNEL_CONFIG_DL(21, "IPCR", 8, 0),
+ MHI_CHANNEL_CONFIG_UL(32, "DUN", 32, 0),
+ MHI_CHANNEL_CONFIG_DL(33, "DUN", 32, 0),
MHI_CHANNEL_CONFIG_UL_FP(34, "FIREHOSE", 32, 0),
MHI_CHANNEL_CONFIG_DL_FP(35, "FIREHOSE", 32, 0),
MHI_CHANNEL_CONFIG_UL(46, "IP_SW0", 64, 2),
@@ -488,6 +490,16 @@ static const struct mhi_pci_dev_info mhi_quectel_rm5xx_info = {
.sideband_wake = true,
};
+static const struct mhi_pci_dev_info mhi_quectel_rm500q_info = {
+ .name = "quectel-rm500q",
+ .edl = "qcom/sdx55m/edl.mbn",
+ .config = &modem_quectel_em1xx_config,
+ .bar_num = MHI_PCI_DEFAULT_BAR_NUM,
+ .dma_data_width = 32,
+ .mru_default = 32768,
+ .sideband_wake = true,
+};
+
static const struct mhi_channel_config mhi_foxconn_sdx55_channels[] = {
MHI_CHANNEL_CONFIG_UL(0, "LOOPBACK", 32, 0),
MHI_CHANNEL_CONFIG_DL(1, "LOOPBACK", 32, 0),
@@ -732,6 +744,8 @@ static const struct mhi_channel_config mhi_sierra_em919x_channels[] = {
MHI_CHANNEL_CONFIG_DL(13, "MBIM", 128, 0),
MHI_CHANNEL_CONFIG_UL(14, "QMI", 32, 0),
MHI_CHANNEL_CONFIG_DL(15, "QMI", 32, 0),
+ MHI_CHANNEL_CONFIG_UL(18, "IP_CTRL", 32, 0),
+ MHI_CHANNEL_CONFIG_DL(19, "IP_CTRL", 32, 0),
MHI_CHANNEL_CONFIG_UL(32, "DUN", 32, 0),
MHI_CHANNEL_CONFIG_DL(33, "DUN", 32, 0),
MHI_CHANNEL_CONFIG_HW_UL(100, "IP_HW0", 512, 1),
@@ -976,6 +990,9 @@ static const struct pci_device_id mhi_pci_id_table[] = {
/* Telit FN980 hardware revision v1 */
{ PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0306, 0x1C5D, 0x2000),
.driver_data = (kernel_ulong_t) &mhi_telit_fn980_hw_v1_info },
+ /* Quectel RM500Q-AE (sdx55), uses the same vid:pid as qcom-sdx55m */
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0306, PCI_VENDOR_ID_QCOM, 0x5002),
+ .driver_data = (kernel_ulong_t) &mhi_quectel_rm500q_info },
{ PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x0306),
.driver_data = (kernel_ulong_t) &mhi_qcom_sdx55_info },
/* Telit FN990 */
@@ -1097,6 +1114,7 @@ struct mhi_pci_device {
struct work_struct recovery_work;
struct timer_list health_check_timer;
unsigned long status;
+ bool no_m3;
bool reset_on_remove;
};
@@ -1409,6 +1427,8 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
mhi_cntrl->mru = info->mru_default;
mhi_cntrl->name = info->name;
+ mhi_pdev->no_m3 = info->no_m3;
+
if (!pdev->is_virtfn)
mhi_pdev->reset_on_remove = info->reset_on_remove;
@@ -1497,7 +1517,7 @@ static void mhi_pci_remove(struct pci_dev *pdev)
}
/* balancing probe put_noidle */
- if (pci_pme_capable(pdev, PCI_D3hot))
+ if (pci_pme_capable(pdev, PCI_D3hot) && !mhi_pdev->no_m3)
pm_runtime_get_noresume(&pdev->dev);
if (mhi_pdev->reset_on_remove)
diff --git a/drivers/char/scx200_gpio.c b/drivers/char/scx200_gpio.c
index 700e6affea6f..3ec17832e9c9 100644
--- a/drivers/char/scx200_gpio.c
+++ b/drivers/char/scx200_gpio.c
@@ -107,10 +107,16 @@ static int __init scx200_gpio_init(void)
}
cdev_init(&scx200_gpio_cdev, &scx200_gpio_fileops);
- cdev_add(&scx200_gpio_cdev, devid, MAX_PINS);
+ rc = cdev_add(&scx200_gpio_cdev, devid, MAX_PINS);
+ if (rc) {
+ dev_err(&pdev->dev, "cdev_add err: %d\n", rc);
+ goto undo_chrdev_region;
+ }
return 0; /* succeed */
+undo_chrdev_region:
+ unregister_chrdev_region(devid, MAX_PINS);
undo_platform_device_add:
platform_device_del(pdev);
undo_malloc:
diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c
index 9bb5fa642fd8..bbeb9b6060dc 100644
--- a/drivers/char/xilinx_hwicap/xilinx_hwicap.c
+++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c
@@ -382,7 +382,7 @@ hwicap_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
4 - bytes_to_read);
} else {
/* Get new data from the ICAP, and return what was requested. */
- kbuf = (u32 *) get_zeroed_page(GFP_KERNEL);
+ kbuf = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!kbuf) {
status = -ENOMEM;
goto error;
@@ -412,13 +412,13 @@ hwicap_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
/* If we didn't read correctly, then bail out. */
if (status) {
- free_page((unsigned long)kbuf);
+ kfree(kbuf);
goto error;
}
/* If we fail to return the data to the user, then bail out. */
if (copy_to_user(buf, kbuf, bytes_to_read)) {
- free_page((unsigned long)kbuf);
+ kfree(kbuf);
status = -EFAULT;
goto error;
}
@@ -426,7 +426,7 @@ hwicap_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
kbuf,
bytes_remaining);
drvdata->read_buffer_in_use = bytes_remaining;
- free_page((unsigned long)kbuf);
+ kfree(kbuf);
}
status = bytes_to_read;
error:
@@ -457,7 +457,7 @@ hwicap_write(struct file *file, const char __user *buf,
goto error;
}
- kbuf = (u32 *) __get_free_page(GFP_KERNEL);
+ kbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!kbuf) {
status = -ENOMEM;
goto error;
@@ -479,13 +479,13 @@ hwicap_write(struct file *file, const char __user *buf,
(((char *)kbuf) + drvdata->write_buffer_in_use),
buf + written,
len - (drvdata->write_buffer_in_use))) {
- free_page((unsigned long)kbuf);
+ kfree(kbuf);
status = -EFAULT;
goto error;
}
} else {
if (copy_from_user(kbuf, buf + written, len)) {
- free_page((unsigned long)kbuf);
+ kfree(kbuf);
status = -EFAULT;
goto error;
}
@@ -495,7 +495,7 @@ hwicap_write(struct file *file, const char __user *buf,
kbuf, len >> 2);
if (status) {
- free_page((unsigned long)kbuf);
+ kfree(kbuf);
status = -EFAULT;
goto error;
}
@@ -516,7 +516,7 @@ hwicap_write(struct file *file, const char __user *buf,
}
}
- free_page((unsigned long)kbuf);
+ kfree(kbuf);
status = written;
error:
mutex_unlock(&drvdata->sem);
diff --git a/drivers/char/xillybus/xillybus.h b/drivers/char/xillybus/xillybus.h
index 51de7cbc579e..98c7ac4dd1f9 100644
--- a/drivers/char/xillybus/xillybus.h
+++ b/drivers/char/xillybus/xillybus.h
@@ -94,6 +94,9 @@ struct xilly_endpoint {
__iomem void *registers;
int fatal_error;
+ bool allow_isr;
+ spinlock_t allow_isr_lock;
+
struct mutex register_mutex;
wait_queue_head_t ep_wait;
diff --git a/drivers/char/xillybus/xillybus_class.c b/drivers/char/xillybus/xillybus_class.c
index 5e8f03b77064..f7e0da233e2a 100644
--- a/drivers/char/xillybus/xillybus_class.c
+++ b/drivers/char/xillybus/xillybus_class.c
@@ -57,6 +57,9 @@ int xillybus_init_chrdev(struct device *dev,
size_t namelen;
struct xilly_unit *unit, *u;
+ if (num_nodes <= 0 || num_nodes > XILLYBUS_MAX_NODES || !idt || !prefix || !dev)
+ return -ENODEV;
+
unit = kzalloc_obj(*unit);
if (!unit)
@@ -68,6 +71,12 @@ int xillybus_init_chrdev(struct device *dev,
snprintf(unit->name, UNITNAMELEN, "%s", prefix);
for (i = 0; enumerate; i++) {
+ if (i > 99) {
+ dev_err(dev, "Failed to obtain unique unit name\n");
+ rc = -ENODEV;
+ goto fail_obtain;
+ }
+
snprintf(unit->name, UNITNAMELEN, "%s_%02d",
prefix, i);
@@ -215,10 +224,15 @@ EXPORT_SYMBOL(xillybus_cleanup_chrdev);
int xillybus_find_inode(struct inode *inode,
void **private_data, int *index)
{
- int minor = iminor(inode);
- int major = imajor(inode);
+ int minor, major;
struct xilly_unit *unit = NULL, *iter;
+ if (!inode || !private_data || !index)
+ return -ENODEV;
+
+ minor = iminor(inode);
+ major = imajor(inode);
+
mutex_lock(&unit_mutex);
list_for_each_entry(iter, &unit_list, list_entry)
diff --git a/drivers/char/xillybus/xillybus_class.h b/drivers/char/xillybus/xillybus_class.h
index 5dbfdfc95c65..4dbed9adcaf8 100644
--- a/drivers/char/xillybus/xillybus_class.h
+++ b/drivers/char/xillybus/xillybus_class.h
@@ -8,6 +8,9 @@
#ifndef __XILLYBUS_CLASS_H
#define __XILLYBUS_CLASS_H
+#define XILLYBUS_MAX_NODES 1024
+#define XILLYBUS_MAX_IDT 1048576
+
#include <linux/types.h>
#include <linux/device.h>
#include <linux/fs.h>
diff --git a/drivers/char/xillybus/xillybus_core.c b/drivers/char/xillybus/xillybus_core.c
index 952ef149aba1..7189c6d97e7d 100644
--- a/drivers/char/xillybus/xillybus_core.c
+++ b/drivers/char/xillybus/xillybus_core.c
@@ -28,6 +28,7 @@
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
+#include <linux/sizes.h>
#include "xillybus.h"
#include "xillybus_class.h"
@@ -72,6 +73,8 @@ static struct workqueue_struct *xillybus_wq;
*
* rd_spinlock does the same with rd_*_buf_idx, rd_empty and end_offset.
*
+ * allow_isr_lock protects allow_isr.
+ *
* register_mutex is endpoint-specific, and is held when non-atomic
* register operations are performed. wr_mutex and rd_mutex may be
* held when register_mutex is taken, but none of the spinlocks. Note that
@@ -84,7 +87,8 @@ static struct workqueue_struct *xillybus_wq;
* Only interruptible blocking is allowed on mutexes and wait queues.
*
* All in all, the locking order goes (with skips allowed, of course):
- * wr_mutex -> rd_mutex -> register_mutex -> wr_spinlock -> rd_spinlock
+ * wr_mutex -> rd_mutex -> register_mutex ->
+ * allow_isr_lock -> wr_spinlock -> rd_spinlock
*/
static void malformed_message(struct xilly_endpoint *endpoint, u32 *buf)
@@ -119,6 +123,13 @@ irqreturn_t xillybus_isr(int irq, void *data)
unsigned int msg_channel, msg_bufno, msg_data, msg_dir;
struct xilly_channel *channel;
+ guard(spinlock)(&ep->allow_isr_lock);
+
+ if (!ep->allow_isr) {
+ dev_err_ratelimited(ep->dev, "Unexpected interrupt! Something is wrong with the hardware.\n");
+ return IRQ_NONE;
+ }
+
buf = ep->msgbuf_addr;
buf_size = ep->msg_buf_size/sizeof(u32);
@@ -137,6 +148,7 @@ irqreturn_t xillybus_isr(int irq, void *data)
if (++ep->failed_messages > 10) {
dev_err(ep->dev,
"Lost sync with interrupt messages. Stopping.\n");
+ return IRQ_NONE;
} else {
dma_sync_single_for_device(ep->dev,
ep->msgbuf_dma_addr,
@@ -284,6 +296,19 @@ irqreturn_t xillybus_isr(int irq, void *data)
EXPORT_SYMBOL(xillybus_isr);
/*
+ * xilly_allow_isr() is similar to enabling / disabling the interrupt,
+ * with the difference that if an interrupt is issued while ep->allow_isr
+ * is false, this is visible in the kernel log.
+ */
+
+static void xilly_allow_isr(struct xilly_endpoint *ep, bool newstate)
+{
+ guard(spinlock_irqsave)(&ep->allow_isr_lock);
+
+ ep->allow_isr = newstate;
+}
+
+/*
* A few trivial memory management functions.
* NOTE: These functions are used only on probe and remove, and therefore
* no locks are applied!
@@ -343,13 +368,20 @@ static int xilly_map_single(struct xilly_endpoint *ep,
static int xilly_get_dma_buffers(struct xilly_endpoint *ep,
struct xilly_alloc_state *s,
struct xilly_buffer **buffers,
- int bufnum, int bytebufsize)
+ unsigned int bufnum,
+ unsigned int bytebufsize)
{
int i, rc;
dma_addr_t dma_addr;
struct device *dev = ep->dev;
struct xilly_buffer *this_buffer = NULL; /* Init to silence warning */
+ if (bytebufsize == 0 || bytebufsize > SZ_1G) {
+ dev_err(ep->dev,
+ "Illegal buffer size requested in IDT. Aborting.\n");
+ return -ENODEV;
+ }
+
if (buffers) { /* Not the message buffer */
this_buffer = devm_kcalloc(dev, bufnum,
sizeof(struct xilly_buffer),
@@ -431,8 +463,8 @@ static int xilly_setupchannels(struct xilly_endpoint *ep,
struct device *dev = ep->dev;
int i, entry, rc;
struct xilly_channel *channel;
- int channelnum, bufnum, bufsize, format, is_writebuf;
- int bytebufsize;
+ unsigned int channelnum, bufnum, bufsize, format, is_writebuf;
+ unsigned int bytebufsize;
int synchronous, allowpartial, exclusive_open, seekable;
int supports_nonempty;
int msg_buf_done = 0;
@@ -512,7 +544,7 @@ static int xilly_setupchannels(struct xilly_endpoint *ep,
format = (chandesc[1] >> 4) & 0x03;
allowpartial = (chandesc[1] >> 6) & 0x01;
synchronous = (chandesc[1] >> 7) & 0x01;
- bufsize = 1 << (chandesc[2] & 0x1f);
+ bufsize = 1U << (chandesc[2] & 0x1f);
bufnum = 1 << (chandesc[3] & 0x0f);
exclusive_open = (chandesc[2] >> 7) & 0x01;
seekable = (chandesc[2] >> 6) & 0x01;
@@ -531,8 +563,7 @@ static int xilly_setupchannels(struct xilly_endpoint *ep,
channel->log2_element_size = ((format > 2) ?
2 : format);
- bytebufsize = bufsize *
- (1 << channel->log2_element_size);
+ bytebufsize = bufsize << channel->log2_element_size;
buffers = devm_kcalloc(dev, bufnum,
sizeof(struct xilly_buffer *),
@@ -589,7 +620,7 @@ static int xilly_setupchannels(struct xilly_endpoint *ep,
static int xilly_scan_idt(struct xilly_endpoint *endpoint,
struct xilly_idt_handle *idt_handle)
{
- int count = 0;
+ unsigned int count = 0;
unsigned char *idt = endpoint->channels[1]->wr_buffers[0]->addr;
unsigned char *end_of_idt = idt + endpoint->idtlen - 4;
unsigned char *scan;
@@ -623,6 +654,12 @@ static int xilly_scan_idt(struct xilly_endpoint *endpoint,
return -ENODEV;
}
+ if (count == 0 || count > XILLYBUS_MAX_NODES) {
+ dev_err(endpoint->dev,
+ "Unreasonable number of channels. Aborting.\n");
+ return -ENODEV;
+ }
+
idt_handle->entries = len >> 2;
endpoint->num_channels = count;
@@ -639,6 +676,8 @@ static int xilly_obtain_idt(struct xilly_endpoint *endpoint)
channel->wr_sleepy = 1;
+ xilly_allow_isr(endpoint, true);
+
iowrite32(1 |
(3 << 24), /* Opcode 3 for channel 0 = Send IDT */
endpoint->registers + fpga_buf_ctrl_reg);
@@ -647,6 +686,8 @@ static int xilly_obtain_idt(struct xilly_endpoint *endpoint)
(!channel->wr_sleepy),
XILLY_TIMEOUT);
+ xilly_allow_isr(endpoint, false);
+
if (t <= 0) {
dev_err(endpoint->dev, "Failed to obtain IDT. Aborting.\n");
@@ -694,7 +735,8 @@ static ssize_t xillybus_read(struct file *filp, char __user *userbuf,
unsigned long flags;
int bytes_done = 0;
int no_time_left = 0;
- long deadline, left_to_sleep;
+ unsigned long deadline;
+ long left_to_sleep;
struct xilly_channel *channel = filp->private_data;
int empty, reached_eof, exhausted, ready;
@@ -724,8 +766,18 @@ static ssize_t xillybus_read(struct file *filp, char __user *userbuf,
bufidx = channel->wr_host_buf_idx;
bufpos = channel->wr_host_buf_pos;
howmany = ((channel->wr_buffers[bufidx]->end_offset
- + 1) << channel->log2_element_size)
- - bufpos;
+ + 1) << channel->log2_element_size);
+
+ if (howmany > channel->wr_buf_size ||
+ howmany < bufpos) {
+ dev_err(channel->endpoint->dev,
+ "Illegal buffer fill level from hardware\n");
+ channel->endpoint->fatal_error = 1;
+ spin_unlock_irqrestore(&channel->wr_spinlock, flags);
+ break;
+ }
+
+ howmany -= bufpos;
/* Update wr_host_* to its post-operation state */
if (howmany > bytes_to_do) {
@@ -938,7 +990,7 @@ interrupted: /* Mutex is not held if got here */
return -EINTR;
}
- left_to_sleep = deadline - ((long) jiffies);
+ left_to_sleep = deadline - jiffies;
/*
* If our time is out, skip the waiting. We may miss wr_sleepy
@@ -1814,6 +1866,9 @@ struct xilly_endpoint *xillybus_init_endpoint(struct device *dev)
endpoint->failed_messages = 0;
endpoint->fatal_error = 0;
+ endpoint->allow_isr = false;
+ spin_lock_init(&endpoint->allow_isr_lock);
+
init_waitqueue_head(&endpoint->ep_wait);
mutex_init(&endpoint->register_mutex);
@@ -1826,6 +1881,9 @@ static int xilly_quiesce(struct xilly_endpoint *endpoint)
long t;
endpoint->idtlen = -1;
+ endpoint->num_channels = 0;
+
+ xilly_allow_isr(endpoint, true);
iowrite32((u32) (endpoint->dma_using_dac & 0x0001),
endpoint->registers + fpga_dma_control_reg);
@@ -1833,6 +1891,9 @@ static int xilly_quiesce(struct xilly_endpoint *endpoint)
t = wait_event_interruptible_timeout(endpoint->ep_wait,
(endpoint->idtlen >= 0),
XILLY_TIMEOUT);
+
+ xilly_allow_isr(endpoint, false);
+
if (t <= 0) {
dev_err(endpoint->dev,
"Failed to quiesce the device on exit.\n");
@@ -1886,6 +1947,8 @@ int xillybus_endpoint_discovery(struct xilly_endpoint *endpoint)
endpoint->idtlen = -1;
+ xilly_allow_isr(endpoint, true);
+
/*
* Set DMA 32/64 bit mode, quiesce the device (?!) and get IDT
* buffer size.
@@ -1896,11 +1959,19 @@ int xillybus_endpoint_discovery(struct xilly_endpoint *endpoint)
t = wait_event_interruptible_timeout(endpoint->ep_wait,
(endpoint->idtlen >= 0),
XILLY_TIMEOUT);
+
+ xilly_allow_isr(endpoint, false);
+
if (t <= 0) {
dev_err(endpoint->dev, "No response from FPGA. Aborting.\n");
return -ENODEV;
}
+ if (endpoint->idtlen < 4 || endpoint->idtlen > XILLYBUS_MAX_IDT) {
+ dev_err(endpoint->dev, "Invalid IDT length. Aborting.\n");
+ return -ENODEV;
+ }
+
/* Enable DMA */
iowrite32((u32) (0x0002 | (endpoint->dma_using_dac & 0x0001)),
endpoint->registers + fpga_dma_control_reg);
@@ -1917,6 +1988,7 @@ int xillybus_endpoint_discovery(struct xilly_endpoint *endpoint)
if (rc)
goto failed_idt;
+ /* xilly_obtain_idt() allows and then disallows the ISR */
rc = xilly_obtain_idt(endpoint);
if (rc)
goto failed_idt;
@@ -1935,6 +2007,8 @@ int xillybus_endpoint_discovery(struct xilly_endpoint *endpoint)
if (rc)
goto failed_idt;
+ xilly_allow_isr(endpoint, true);
+
rc = xillybus_init_chrdev(dev, &xillybus_fops,
endpoint->owner, endpoint,
idt_handle.names,
diff --git a/drivers/char/xillybus/xillybus_of.c b/drivers/char/xillybus/xillybus_of.c
index 46e1046abfca..44b0c754deb2 100644
--- a/drivers/char/xillybus/xillybus_of.c
+++ b/drivers/char/xillybus/xillybus_of.c
@@ -53,6 +53,9 @@ static int xilly_drv_probe(struct platform_device *op)
irq = platform_get_irq(op, 0);
+ if (irq < 0)
+ return irq;
+
rc = devm_request_irq(dev, irq, xillybus_isr, 0, xillyname, endpoint);
if (rc)
diff --git a/drivers/char/xillybus/xillyusb.c b/drivers/char/xillybus/xillyusb.c
index 34e7ad3bcab3..bd3babd39f35 100644
--- a/drivers/char/xillybus/xillyusb.c
+++ b/drivers/char/xillybus/xillyusb.c
@@ -24,6 +24,7 @@
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
+#include <linux/sizes.h>
#include <linux/crc32.h>
#include <linux/poll.h>
#include <linux/delay.h>
@@ -50,6 +51,7 @@ MODULE_LICENSE("GPL v2");
static const char xillyname[] = "xillyusb";
static unsigned int fifo_buf_order;
+static DEFINE_MUTEX(fifo_buf_order_mutex);
static struct workqueue_struct *wakeup_wq;
#define USB_VENDOR_ID_XILINX 0x03fd
@@ -71,7 +73,6 @@ struct xillyfifo {
unsigned int bufsize; /* In bytes, always a power of 2 */
unsigned int bufnum;
unsigned int size; /* Lazy: Equals bufsize * bufnum */
- unsigned int buf_order;
int fill; /* Number of bytes in the FIFO */
spinlock_t lock;
@@ -95,7 +96,6 @@ struct xillyusb_endpoint {
struct list_head filled_buffers;
spinlock_t buffers_lock; /* protect these two lists */
- unsigned int order;
unsigned int buffer_size;
unsigned int fill_mask;
@@ -370,37 +370,41 @@ static int fifo_init(struct xillyfifo *fifo,
unsigned int log2_size)
{
unsigned int log2_bufnum;
- unsigned int buf_order;
int i;
unsigned int log2_fifo_buf_size;
+ mutex_lock(&fifo_buf_order_mutex);
+
retry:
log2_fifo_buf_size = fifo_buf_order + PAGE_SHIFT;
if (log2_size > log2_fifo_buf_size) {
log2_bufnum = log2_size - log2_fifo_buf_size;
- buf_order = fifo_buf_order;
fifo->bufsize = 1 << log2_fifo_buf_size;
} else {
log2_bufnum = 0;
- buf_order = (log2_size > PAGE_SHIFT) ?
- log2_size - PAGE_SHIFT : 0;
fifo->bufsize = 1 << log2_size;
}
fifo->bufnum = 1 << log2_bufnum;
fifo->size = fifo->bufnum * fifo->bufsize;
- fifo->buf_order = buf_order;
+
+ if (!fifo->size || /* Unsigned integer overflow */
+ fifo->size > SZ_1G) { /* Avoid signed int issues */
+ mutex_unlock(&fifo_buf_order_mutex);
+ return -ENOMEM; /* Reported as greed for memory */
+ }
fifo->mem = kmalloc_array(fifo->bufnum, sizeof(void *), GFP_KERNEL);
- if (!fifo->mem)
+ if (!fifo->mem) {
+ mutex_unlock(&fifo_buf_order_mutex);
return -ENOMEM;
+ }
for (i = 0; i < fifo->bufnum; i++) {
- fifo->mem[i] = (void *)
- __get_free_pages(GFP_KERNEL, buf_order);
+ fifo->mem[i] = kmalloc(fifo->bufsize, GFP_KERNEL);
if (!fifo->mem[i])
goto memfail;
@@ -413,11 +417,13 @@ retry:
fifo->writebuf = 0;
spin_lock_init(&fifo->lock);
init_waitqueue_head(&fifo->waitq);
+
+ mutex_unlock(&fifo_buf_order_mutex);
return 0;
memfail:
for (i--; i >= 0; i--)
- free_pages((unsigned long)fifo->mem[i], buf_order);
+ kfree(fifo->mem[i]);
kfree(fifo->mem);
fifo->mem = NULL;
@@ -426,6 +432,7 @@ memfail:
fifo_buf_order--;
goto retry;
} else {
+ mutex_unlock(&fifo_buf_order_mutex);
return -ENOMEM;
}
}
@@ -438,7 +445,7 @@ static void fifo_mem_release(struct xillyfifo *fifo)
return;
for (i = 0; i < fifo->bufnum; i++)
- free_pages((unsigned long)fifo->mem[i], fifo->buf_order);
+ kfree(fifo->mem[i]);
kfree(fifo->mem);
}
@@ -477,7 +484,7 @@ static void endpoint_dealloc(struct xillyusb_endpoint *ep)
struct xillybuffer *xb =
list_entry(this, struct xillybuffer, entry);
- free_pages((unsigned long)xb->buf, ep->order);
+ kfree(xb->buf);
kfree(xb);
}
@@ -509,8 +516,7 @@ static struct xillyusb_endpoint
init_usb_anchor(&ep->anchor);
INIT_WORK(&ep->workitem, work);
- ep->order = order;
- ep->buffer_size = 1 << (PAGE_SHIFT + order);
+ ep->buffer_size = 1 << (PAGE_SHIFT + order);
ep->outstanding_urbs = 0;
ep->drained = true;
ep->wake_on_drain = false;
@@ -520,7 +526,6 @@ static struct xillyusb_endpoint
for (i = 0; i < bufnum; i++) {
struct xillybuffer *xb;
- unsigned long addr;
xb = kzalloc_obj(*xb);
@@ -529,15 +534,14 @@ static struct xillyusb_endpoint
return NULL;
}
- addr = __get_free_pages(GFP_KERNEL, order);
+ xb->buf = kmalloc(ep->buffer_size, GFP_KERNEL);
- if (!addr) {
+ if (!xb->buf) {
kfree(xb);
endpoint_dealloc(ep);
return NULL;
}
- xb->buf = (void *)addr;
xb->ep = ep;
list_add_tail(&xb->entry, &ep->buffers);
}
@@ -885,6 +889,7 @@ static int process_in_opcode(struct xillyusb_dev *xdev,
struct xillyusb_channel *chan;
struct device *dev = xdev->dev;
int chan_idx = chan_num >> 1;
+ struct xillyfifo *in_fifo;
if (chan_idx >= xdev->num_channels) {
dev_err(dev, "Received illegal channel ID %d from FPGA\n",
@@ -909,7 +914,10 @@ static int process_in_opcode(struct xillyusb_dev *xdev,
*/
smp_wmb();
WRITE_ONCE(chan->read_data_ok, 0);
- wake_up_interruptible(&chan->in_fifo->waitq);
+
+ in_fifo = READ_ONCE(chan->in_fifo);
+ if (in_fifo)
+ wake_up_interruptible(&in_fifo->waitq);
break;
case OPCODE_REACHED_CHECKPOINT:
@@ -1124,12 +1132,13 @@ unlock_done:
*/
static int flush_downstream(struct xillyusb_channel *chan,
- long timeout,
+ unsigned long timeout,
bool interruptible)
{
struct xillyusb_dev *xdev = chan->xdev;
int chan_num = chan->chan_idx << 1;
- long deadline, left_to_sleep;
+ unsigned long deadline;
+ long left_to_sleep;
int rc;
if (chan->flushed)
@@ -1138,7 +1147,8 @@ static int flush_downstream(struct xillyusb_channel *chan,
deadline = jiffies + 1 + timeout;
if (chan->flushing) {
- long cancel_deadline = jiffies + 1 + XILLY_RESPONSE_TIMEOUT;
+ unsigned long cancel_deadline =
+ jiffies + 1 + XILLY_RESPONSE_TIMEOUT;
chan->canceled = 0;
rc = xillyusb_send_opcode(xdev, chan_num,
@@ -1149,7 +1159,7 @@ static int flush_downstream(struct xillyusb_channel *chan,
/* Ignoring interrupts. Cancellation must be handled */
while (!chan->canceled) {
- left_to_sleep = cancel_deadline - ((long)jiffies);
+ left_to_sleep = cancel_deadline - jiffies;
if (left_to_sleep <= 0) {
report_io_error(xdev, -EIO);
@@ -1199,7 +1209,7 @@ static int flush_downstream(struct xillyusb_channel *chan,
}
while (chan->flushing) {
- left_to_sleep = deadline - ((long)jiffies);
+ left_to_sleep = deadline - jiffies;
if (left_to_sleep <= 0)
return -ETIMEDOUT;
@@ -1416,16 +1426,12 @@ unopen:
if (filp->f_mode & FMODE_WRITE)
chan->open_for_write = 0;
+unmutex_fail:
mutex_unlock(&chan->lock);
kref_put(&xdev->kref, cleanup_dev);
return rc;
-
-unmutex_fail:
- kref_put(&xdev->kref, cleanup_dev);
- mutex_unlock(&chan->lock);
- return rc;
}
static ssize_t xillyusb_read(struct file *filp, char __user *userbuf,
@@ -1436,7 +1442,8 @@ static ssize_t xillyusb_read(struct file *filp, char __user *userbuf,
struct xillyfifo *fifo = chan->in_fifo;
int chan_num = (chan->chan_idx << 1) | 1;
- long deadline, left_to_sleep;
+ unsigned long deadline;
+ long left_to_sleep;
int bytes_done = 0;
bool sent_set_push = false;
int rc;
@@ -1465,7 +1472,7 @@ static ssize_t xillyusb_read(struct file *filp, char __user *userbuf,
bytes_done += rc;
chan->in_consumed_bytes += rc;
- left_to_sleep = deadline - ((long)jiffies);
+ left_to_sleep = deadline - jiffies;
/*
* Some 32-bit arithmetic that may wrap. Note that
@@ -1508,8 +1515,8 @@ static ssize_t xillyusb_read(struct file *filp, char __user *userbuf,
*/
if (chan->read_data_ok &&
- (leap > (fifo->size >> 3) ||
- (checkpoint_for_complete && leap > 0))) {
+ (leap > 0 && (leap > (fifo->size >> 3) ||
+ checkpoint_for_complete))) {
chan->in_current_checkpoint = checkpoint;
rc = xillyusb_send_opcode(xdev, chan_num,
OPCODE_SET_CHECKPOINT,
@@ -1943,6 +1950,9 @@ static int setup_channels(struct xillyusb_dev *xdev,
struct xillyusb_channel *chan, *new_channels;
int i;
+ /* Don't let process_bulk_in() run while we change the channels */
+ guard(mutex)(&xdev->process_in_mutex);
+
chan = kzalloc_objs(*chan, num_channels);
if (!chan)
return -ENOMEM;
@@ -2067,6 +2077,13 @@ static int xillyusb_discovery(struct usb_interface *interface)
}
idt_len = READ_ONCE(idt_fifo.fill);
+
+ if (idt_len < 4 || idt_len > XILLYBUS_MAX_IDT) {
+ rc = -ENODEV;
+ dev_err(&interface->dev, "Invalid IDT length. Aborting.\n");
+ goto unfifo;
+ }
+
idt = kmalloc(idt_len, GFP_KERNEL);
if (!idt) {
@@ -2101,6 +2118,12 @@ static int xillyusb_discovery(struct usb_interface *interface)
goto unidt;
}
+ if (num_channels == 0 || num_channels > XILLYBUS_MAX_NODES) {
+ dev_err(&interface->dev, "Unreasonable number of channels. Aborting.\n");
+ rc = -ENODEV;
+ goto unidt;
+ }
+
rc = setup_channels(xdev, (void *)idt + 3, num_channels);
if (rc)
diff --git a/drivers/comedi/drivers/addi_apci_3xxx.c b/drivers/comedi/drivers/addi_apci_3xxx.c
index 695cce103177..1dcda82d9503 100644
--- a/drivers/comedi/drivers/addi_apci_3xxx.c
+++ b/drivers/comedi/drivers/addi_apci_3xxx.c
@@ -787,7 +787,7 @@ static int apci3xxx_auto_attach(struct comedi_device *dev,
dev->irq = pcidev->irq;
}
- n_subdevices = (board->ai_n_chan ? 0 : 1) + board->has_ao +
+ n_subdevices = (board->ai_n_chan ? 1 : 0) + board->has_ao +
board->has_dig_in + board->has_dig_out +
board->has_ttl_io;
ret = comedi_alloc_subdevices(dev, n_subdevices);
diff --git a/drivers/comedi/drivers/comedi_bond.c b/drivers/comedi/drivers/comedi_bond.c
index 8e10ecab4f0d..a10cfbd04c72 100644
--- a/drivers/comedi/drivers/comedi_bond.c
+++ b/drivers/comedi/drivers/comedi_bond.c
@@ -39,6 +39,7 @@
#include <linux/module.h>
#include <linux/string.h>
+#include <linux/seq_buf.h>
#include <linux/slab.h>
#include <linux/comedi.h>
#include <linux/comedi/comedilib.h>
@@ -170,10 +171,11 @@ static int do_dev_config(struct comedi_device *dev, struct comedi_devconfig *it)
{
struct comedi_bond_private *devpriv = dev->private;
DECLARE_BITMAP(devs_opened, COMEDI_NUM_BOARD_MINORS);
+ struct seq_buf s;
int i;
memset(&devs_opened, 0, sizeof(devs_opened));
- devpriv->name[0] = 0;
+ seq_buf_init(&s, devpriv->name, sizeof(devpriv->name));
/*
* Loop through all comedi devices specified on the command-line,
* building our device list.
@@ -250,15 +252,9 @@ static int do_dev_config(struct comedi_device *dev, struct comedi_devconfig *it)
}
devpriv->devs = devs;
devpriv->devs[devpriv->ndevs++] = bdev;
- {
- /* Append dev:subdev to devpriv->name */
- char buf[20];
-
- snprintf(buf, sizeof(buf), "%u:%u ",
- bdev->minor, bdev->subdev);
- strlcat(devpriv->name, buf,
- sizeof(devpriv->name));
- }
+
+ /* Append dev:subdev to devpriv->name */
+ seq_buf_printf(&s, "%u:%u ", bdev->minor, bdev->subdev);
}
}
@@ -267,6 +263,8 @@ static int do_dev_config(struct comedi_device *dev, struct comedi_devconfig *it)
return -EINVAL;
}
+ seq_buf_str(&s);
+
return 0;
}
diff --git a/drivers/comedi/drivers/comedi_parport.c b/drivers/comedi/drivers/comedi_parport.c
index 57ee3f9dfba2..94284ff157ac 100644
--- a/drivers/comedi/drivers/comedi_parport.c
+++ b/drivers/comedi/drivers/comedi_parport.c
@@ -243,7 +243,8 @@ static int parport_attach(struct comedi_device *dev,
outb(0, dev->iobase + PARPORT_DATA_REG);
outb(0, dev->iobase + PARPORT_CTRL_REG);
- if (it->options[1]) {
+ /* only ISA interrupts are valid on a parallel port */
+ if (it->options[1] >= 1 && it->options[1] <= 15) {
ret = request_irq(it->options[1], parport_interrupt, 0,
dev->board_name, dev);
if (ret == 0)
diff --git a/drivers/comedi/drivers/dmm32at.c b/drivers/comedi/drivers/dmm32at.c
index d1d9f75e168f..c6a6ba2899e6 100644
--- a/drivers/comedi/drivers/dmm32at.c
+++ b/drivers/comedi/drivers/dmm32at.c
@@ -600,7 +600,8 @@ static int dmm32at_attach(struct comedi_device *dev,
return ret;
}
- if (it->options[1]) {
+ /* only ISA interrupts are valid on this board */
+ if (it->options[1] >= 1 && it->options[1] <= 15) {
ret = request_irq(it->options[1], dmm32at_isr, 0,
dev->board_name, dev);
if (ret == 0)
diff --git a/drivers/comedi/drivers/dt2811.c b/drivers/comedi/drivers/dt2811.c
index 0438b8c90e44..53cb17993ae7 100644
--- a/drivers/comedi/drivers/dt2811.c
+++ b/drivers/comedi/drivers/dt2811.c
@@ -567,7 +567,7 @@ static int dt2811_attach(struct comedi_device *dev, struct comedi_devconfig *it)
dt2811_reset(dev);
/* IRQ's 2,3,5,7 are valid for async command support */
- if (it->options[1] <= 7 && (BIT(it->options[1]) & 0xac)) {
+ if (it->options[1] >= 0 && it->options[1] <= 7 && (BIT(it->options[1]) & 0xac)) {
ret = request_irq(it->options[1], dt2811_interrupt, 0,
dev->board_name, dev);
if (ret == 0)
diff --git a/drivers/comedi/drivers/dt2814.c b/drivers/comedi/drivers/dt2814.c
index caec16482afb..63b7f17691ff 100644
--- a/drivers/comedi/drivers/dt2814.c
+++ b/drivers/comedi/drivers/dt2814.c
@@ -316,7 +316,8 @@ static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it)
return -EIO;
}
- if (it->options[1]) {
+ /* only ISA interrupts are valid on this board */
+ if (it->options[1] >= 1 && it->options[1] <= 15) {
ret = request_irq(it->options[1], dt2814_interrupt, 0,
dev->board_name, dev);
if (ret == 0)
diff --git a/drivers/comedi/drivers/ni_atmio16d.c b/drivers/comedi/drivers/ni_atmio16d.c
index 6765cdc276ca..87fed16b112c 100644
--- a/drivers/comedi/drivers/ni_atmio16d.c
+++ b/drivers/comedi/drivers/ni_atmio16d.c
@@ -593,7 +593,9 @@ static int atmio16d_attach(struct comedi_device *dev,
/* reset the atmio16d hardware */
reset_atmio16d(dev);
- if (it->options[1]) {
+ /* only irqs 3, 4, 5, 6, 7, 9, 10, 11, 12, 14, and 15 are valid */
+ if (it->options[1] >= 3 && it->options[1] <= 15 &&
+ (1 << it->options[1]) & 0xdef8) {
ret = request_irq(it->options[1], atmio16d_interrupt, 0,
dev->board_name, dev);
if (ret == 0)
diff --git a/drivers/comedi/drivers/pcmmio.c b/drivers/comedi/drivers/pcmmio.c
index f42b7343b4e4..afe2bda91659 100644
--- a/drivers/comedi/drivers/pcmmio.c
+++ b/drivers/comedi/drivers/pcmmio.c
@@ -684,7 +684,8 @@ static int pcmmio_attach(struct comedi_device *dev, struct comedi_devconfig *it)
pcmmio_reset(dev);
- if (it->options[1]) {
+ /* the irq is configured in software, so any ISA irq is OK */
+ if (it->options[1] >= 1 && it->options[1] <= 15) {
ret = request_irq(it->options[1], interrupt_pcmmio, 0,
dev->board_name, dev);
if (ret == 0) {
diff --git a/drivers/comedi/drivers/pcmuio.c b/drivers/comedi/drivers/pcmuio.c
index d9995cbeecb6..da80bca0bb55 100644
--- a/drivers/comedi/drivers/pcmuio.c
+++ b/drivers/comedi/drivers/pcmuio.c
@@ -546,7 +546,8 @@ static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it)
pcmuio_reset(dev);
- if (it->options[1]) {
+ /* only ISA interrupts are valid on this board */
+ if (it->options[1] >= 1 && it->options[1] <= 15) {
/* request the irq for the 1st asic */
ret = request_irq(it->options[1], pcmuio_interrupt, 0,
dev->board_name, dev);
@@ -558,7 +559,7 @@ static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it)
if (it->options[2] == dev->irq) {
/* the same irq (or none) is used by both asics */
devpriv->irq2 = it->options[2];
- } else if (it->options[2]) {
+ } else if (it->options[2] >= 1 && it->options[2] <= 15) {
/* request the irq for the 2nd asic */
ret = request_irq(it->options[2], pcmuio_interrupt, 0,
dev->board_name, dev);
diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c
index f8c2da207cb4..88391ea161ed 100644
--- a/drivers/firmware/stratix10-svc.c
+++ b/drivers/firmware/stratix10-svc.c
@@ -7,15 +7,19 @@
#include <linux/atomic.h>
#include <linux/completion.h>
#include <linux/delay.h>
+#include <linux/dma-mapping.h>
#include <linux/genalloc.h>
#include <linux/hashtable.h>
#include <linux/idr.h>
+#include <linux/interrupt.h>
#include <linux/io.h>
+#include <linux/iommu.h>
#include <linux/kfifo.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
+#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
@@ -24,6 +28,11 @@
#include <linux/firmware/intel/stratix10-smc.h>
#include <linux/firmware/intel/stratix10-svc-client.h>
#include <linux/types.h>
+#include <linux/cpu.h>
+#include <linux/cpumask.h>
+#include <linux/of_device.h>
+#include <linux/reboot.h>
+#include <linux/workqueue.h>
/**
* SVC_NUM_DATA_IN_FIFO - number of struct stratix10_svc_data in the FIFO
@@ -34,16 +43,51 @@
* from the secure world for FPGA manager to reuse, or to free the buffer(s)
* when all bit-stream data had be send.
*
- * FPGA_CONFIG_STATUS_TIMEOUT_SEC - poll the FPGA configuration status,
- * service layer will return error to FPGA manager when timeout occurs,
- * timeout is set to 30 seconds (30 * 1000) at Intel Stratix10 SoC.
+ * FPGA_CONFIG_STATUS_TIMEOUT_SEC - overall poll budget for FPGA
+ * configuration / service completion status. Service layer returns
+ * error to the client when this timeout expires (30 seconds).
+ *
+ * SVC_POLL_INTERVAL_MS_FAST / SVC_POLL_INTERVAL_MS_SLOW - sleep between
+ * SMC status polls for the fast and slow phases.
+ *
+ * SVC_POLL_FAST_WINDOW_MS - duration of the fast-poll phase before
+ * switching to the slower interval (1 second).
+ *
+ * SVC_POLL_COUNT_FAST / SVC_POLL_COUNT_SLOW - iteration budgets derived
+ * from the intervals and overall timeout so the two phases still sum to
+ * FPGA_CONFIG_STATUS_TIMEOUT_SEC.
*/
#define SVC_NUM_DATA_IN_FIFO 8
#define SVC_NUM_CHANNEL 4
#define FPGA_CONFIG_DATA_CLAIM_TIMEOUT_MS 2000
#define FPGA_CONFIG_STATUS_TIMEOUT_SEC 30
+#define SVC_POLL_INTERVAL_MS_FAST 20
+#define SVC_POLL_INTERVAL_MS_SLOW 500
+#define SVC_POLL_FAST_WINDOW_MS 1000
+#define SVC_POLL_COUNT_FAST \
+ (SVC_POLL_FAST_WINDOW_MS / SVC_POLL_INTERVAL_MS_FAST)
+#define SVC_POLL_COUNT_SLOW \
+ (((FPGA_CONFIG_STATUS_TIMEOUT_SEC * MSEC_PER_SEC) - \
+ SVC_POLL_FAST_WINDOW_MS) / SVC_POLL_INTERVAL_MS_SLOW)
#define BYTE_TO_WORD_SIZE 4
+/*
+ * SVC_SDM_DMA_ADDR_BITS - constrains the IOVA allocated by
+ * dma_alloc_coherent() to 29 bits (0x0000_0000 - 0x1FFF_FFFF)
+ * when SMMU is active on Agilex5. The SDM accesses these buffers
+ * via the SMMU using IOVAs, so the 29-bit limit keeps IOVAs within
+ * the SDM's addressable window.
+ *
+ * SVC_SDM_DMA_ADDR_OFFSET - ATF on Agilex5 distinguishes
+ * SMMU-mapped buffers from direct physical addresses by the
+ * presence of this offset. The driver adds it to the IOVA before
+ * passing the address to ATF via SMC; ATF strips it, translates
+ * the remaining IOVA through the SMMU, and the SDM accesses the
+ * underlying physical memory.
+ */
+#define SVC_SDM_DMA_ADDR_BITS 29
+#define SVC_SDM_DMA_ADDR_OFFSET 0x80000000UL
+
/* stratix10 service layer clients */
#define STRATIX10_RSU "stratix10-rsu"
#define SOCFPGA_HWMON "socfpga-hwmon"
@@ -97,6 +141,19 @@
#define STRATIX10_GET_SDM_STATUS_CODE(status) \
(FIELD_GET(STRATIX10_SDM_STATUS_MASK, status))
+struct stratix10_svc_pdata {
+ bool needs_psci_cpu_off;
+ bool use_dma_mem;
+};
+
+static const struct stratix10_svc_pdata psci_cpu_off_pdata = {
+ .needs_psci_cpu_off = true,
+};
+
+static const struct stratix10_svc_pdata agilex5_pdata = {
+ .use_dma_mem = true,
+};
+
typedef void (svc_invoke_fn)(unsigned long, unsigned long, unsigned long,
unsigned long, unsigned long, unsigned long,
unsigned long, unsigned long,
@@ -137,18 +194,25 @@ struct stratix10_svc_sh_memory {
/**
* struct stratix10_svc_data_mem - service memory structure
* @vaddr: virtual address
- * @paddr: physical address
+ * @paddr: address passed to ATF via SMC and echoed back in completion
+ * notifications; used as the lookup key in svc_pa_to_va().
+ * On the SMMU path this is (IOVA + %SVC_SDM_DMA_ADDR_OFFSET);
+ * on the gen_pool path this equals the raw physical address.
* @size: size of memory
+ * @dma_addr: IOVA returned by dma_alloc_coherent(); used to free the
+ * mapping via dma_free_coherent() on the SMMU path.
* @node: link list head node
*
* This struct is used in a list that keeps track of buffers which have
* been allocated or freed from the memory pool. Service layer driver also
- * uses this struct to transfer physical address to virtual address.
+ * uses this struct to map the address returned by ATF back to a virtual
+ * address.
*/
struct stratix10_svc_data_mem {
void *vaddr;
phys_addr_t paddr;
size_t size;
+ dma_addr_t dma_addr;
struct list_head node;
};
@@ -219,6 +283,7 @@ struct stratix10_async_chan {
* @supported: Flag indicating whether the system supports async operations
* @initialized: Flag indicating whether the control structure has
* been initialized
+ * @irq: Interrupt request number associated with the asynchronous control
* @invoke_fn: Function pointer for invoking Stratix10 service calls
* to EL3 secure firmware
* @async_id_pool: Pointer to the ID pool used for asynchronous
@@ -229,12 +294,14 @@ struct stratix10_async_chan {
* structure
* @trx_list_lock: Spinlock for protecting the transaction list
* operations
+ * @async_work: Work structure for scheduling asynchronous work
* @trx_list: Hash table for managing asynchronous transactions
*/
struct stratix10_async_ctrl {
bool supported;
bool initialized;
+ int irq;
void (*invoke_fn)(struct stratix10_async_ctrl *actrl,
const struct arm_smccc_1_2_regs *args,
struct arm_smccc_1_2_regs *res);
@@ -243,6 +310,7 @@ struct stratix10_async_ctrl {
struct stratix10_async_chan *common_async_chan;
/* spinlock to protect trx_list hash table */
spinlock_t trx_list_lock;
+ struct work_struct async_work;
DECLARE_HASHTABLE(trx_list, ASYNC_TRX_HASH_BITS);
};
@@ -283,6 +351,16 @@ struct stratix10_svc_chan {
* @svc: manages the list of client svc drivers
* @sdm_lock: only allows a single command single response to SDM
* @actrl: async control structure
+ * @psci_reboot_nb: reboot notifier for PSCI secondary CPU offlining
+ * @use_dma_mem: when true, buffers are allocated via dma_alloc_coherent()
+ * instead of the ATF reserved-memory gen_pool.
+ * @dma_addr_offset: value added to the DMA address (IOVA) before passing it
+ * to ATF via SMC. ATF uses this offset to distinguish
+ * SMMU-mapped buffers from direct physical addresses; it
+ * strips the offset, translates the remaining IOVA through
+ * the SMMU, and the SDM accesses the underlying memory.
+ * Set to %SVC_SDM_DMA_ADDR_OFFSET on Agilex5 when SMMU is
+ * active; zero otherwise.
* @chans: array of service channels
*
* This struct is used to create communication channels for service clients, to
@@ -299,6 +377,9 @@ struct stratix10_svc_controller {
struct stratix10_svc *svc;
struct mutex sdm_lock;
struct stratix10_async_ctrl actrl;
+ struct notifier_block psci_reboot_nb;
+ bool use_dma_mem;
+ unsigned long dma_addr_offset;
struct stratix10_svc_chan chans[] __counted_by(num_chans);
};
@@ -324,11 +405,12 @@ static void *svc_pa_to_va(unsigned long addr)
pr_debug("claim back P-addr=0x%016x\n", (unsigned int)addr);
guard(mutex)(&svc_mem_lock);
- list_for_each_entry(pmem, &svc_data_mem, node)
+ list_for_each_entry(pmem, &svc_data_mem, node) {
if (pmem->paddr == addr)
return pmem->vaddr;
+ }
- /* physical address is not found */
+ /* address is not found */
return NULL;
}
@@ -362,11 +444,17 @@ static void svc_thread_cmd_data_claim(struct stratix10_svc_controller *ctrl,
break;
}
cb_data->status = BIT(SVC_STATUS_BUFFER_DONE);
- cb_data->kaddr1 = svc_pa_to_va(res.a1);
+ /*
+ * The firmware COMPLETED_WRITE response returns the
+ * raw IOVA (without dma_addr_offset). Add it back to
+ * match the key stored in pmem->paddr at allocation
+ * time. dma_addr_offset is zero on non-SMMU paths.
+ */
+ cb_data->kaddr1 = svc_pa_to_va(res.a1 + ctrl->dma_addr_offset);
cb_data->kaddr2 = (res.a2) ?
- svc_pa_to_va(res.a2) : NULL;
+ svc_pa_to_va(res.a2 + ctrl->dma_addr_offset) : NULL;
cb_data->kaddr3 = (res.a3) ?
- svc_pa_to_va(res.a3) : NULL;
+ svc_pa_to_va(res.a3 + ctrl->dma_addr_offset) : NULL;
p_data->chan->scl->receive_cb(p_data->chan->scl,
cb_data);
} else {
@@ -379,6 +467,52 @@ static void svc_thread_cmd_data_claim(struct stratix10_svc_controller *ctrl,
}
/**
+ * svc_cmd_poll_status() - poll secure-world service completion status
+ * @p_data: pointer to service data structure
+ * @ctrl: pointer to service layer controller
+ * @res: pointer to store the latest SMC response
+ * @poll_count: maximum number of SMC polls for this phase
+ * @poll_interval_in_ms: sleep duration between polls while status is busy
+ *
+ * Invokes the appropriate ISDONE / SERVICE_COMPLETED SMC until the secure
+ * world returns a terminal status (OK, ERROR, or REJECTED), or until
+ * @poll_count attempts have been exhausted.
+ *
+ * Return: true if a terminal status was received, false on poll budget
+ * exhaustion.
+ */
+static bool svc_cmd_poll_status(struct stratix10_svc_data *p_data,
+ struct stratix10_svc_controller *ctrl,
+ struct arm_smccc_res *res,
+ int poll_count, int poll_interval_in_ms)
+{
+ unsigned long a0, a1, a2;
+
+ a0 = INTEL_SIP_SMC_FPGA_CONFIG_ISDONE;
+ a1 = (unsigned long)p_data->paddr;
+ a2 = (unsigned long)p_data->size;
+
+ if (p_data->command == COMMAND_POLL_SERVICE_STATUS)
+ a0 = INTEL_SIP_SMC_SERVICE_COMPLETED;
+
+ while (poll_count--) {
+ ctrl->invoke_fn(a0, a1, a2, 0, 0, 0, 0, 0, res);
+ if (res->a0 == INTEL_SIP_SMC_STATUS_OK ||
+ res->a0 == INTEL_SIP_SMC_STATUS_ERROR ||
+ res->a0 == INTEL_SIP_SMC_STATUS_REJECTED)
+ return true;
+
+ /*
+ * request is still in progress, go to sleep then
+ * poll again
+ */
+ msleep(poll_interval_in_ms);
+ }
+
+ return false;
+}
+
+/**
* svc_thread_cmd_config_status() - check configuration status
* @ctrl: pointer to service layer controller
* @p_data: pointer to service data structure
@@ -392,8 +526,7 @@ static void svc_thread_cmd_config_status(struct stratix10_svc_controller *ctrl,
struct stratix10_svc_cb_data *cb_data)
{
struct arm_smccc_res res;
- int count_in_sec;
- unsigned long a0, a1, a2;
+ bool done;
cb_data->kaddr1 = NULL;
cb_data->kaddr2 = NULL;
@@ -402,34 +535,20 @@ static void svc_thread_cmd_config_status(struct stratix10_svc_controller *ctrl,
pr_debug("%s: polling config status\n", __func__);
- a0 = INTEL_SIP_SMC_FPGA_CONFIG_ISDONE;
- a1 = (unsigned long)p_data->paddr;
- a2 = (unsigned long)p_data->size;
-
- if (p_data->command == COMMAND_POLL_SERVICE_STATUS)
- a0 = INTEL_SIP_SMC_SERVICE_COMPLETED;
+ done = svc_cmd_poll_status(p_data, ctrl, &res, SVC_POLL_COUNT_FAST,
+ SVC_POLL_INTERVAL_MS_FAST);
+ /* Fall back to a longer interval if still busy after the fast window */
+ if (!done)
+ done = svc_cmd_poll_status(p_data, ctrl, &res,
+ SVC_POLL_COUNT_SLOW,
+ SVC_POLL_INTERVAL_MS_SLOW);
- count_in_sec = FPGA_CONFIG_STATUS_TIMEOUT_SEC;
- while (count_in_sec) {
- ctrl->invoke_fn(a0, a1, a2, 0, 0, 0, 0, 0, &res);
- if ((res.a0 == INTEL_SIP_SMC_STATUS_OK) ||
- (res.a0 == INTEL_SIP_SMC_STATUS_ERROR) ||
- (res.a0 == INTEL_SIP_SMC_STATUS_REJECTED))
- break;
-
- /*
- * request is still in progress, wait one second then
- * poll again
- */
- msleep(1000);
- count_in_sec--;
- }
-
- if (!count_in_sec) {
+ if (!done) {
pr_err("%s: poll status timeout\n", __func__);
cb_data->status = BIT(SVC_STATUS_BUSY);
} else if (res.a0 == INTEL_SIP_SMC_STATUS_OK) {
cb_data->status = BIT(SVC_STATUS_COMPLETED);
+ cb_data->kaddr1 = &res.a1;
cb_data->kaddr2 = (res.a2) ?
svc_pa_to_va(res.a2) : NULL;
cb_data->kaddr3 = (res.a3) ? &res.a3 : NULL;
@@ -869,20 +988,11 @@ static int svc_normal_to_secure_thread(void *data)
default:
pr_warn("Secure firmware doesn't support...\n");
- /*
- * be compatible with older version firmware which
- * doesn't support newer RSU commands
- */
- if ((pdata->command != COMMAND_RSU_UPDATE) &&
- (pdata->command != COMMAND_RSU_STATUS)) {
- cbdata->status =
- BIT(SVC_STATUS_NO_SUPPORT);
- cbdata->kaddr1 = NULL;
- cbdata->kaddr2 = NULL;
- cbdata->kaddr3 = NULL;
- pdata->chan->scl->receive_cb(
- pdata->chan->scl, cbdata);
- }
+ cbdata->status = BIT(SVC_STATUS_NO_SUPPORT);
+ cbdata->kaddr1 = NULL;
+ cbdata->kaddr2 = NULL;
+ cbdata->kaddr3 = NULL;
+ pdata->chan->scl->receive_cb(pdata->chan->scl, cbdata);
break;
}
@@ -1038,6 +1148,38 @@ svc_create_memory_pool(struct platform_device *pdev,
}
/**
+ * svc_setup_dma_memory() - configure the device for dynamic DMA allocation
+ * @pdev: pointer to service layer device
+ *
+ * Called instead of svc_get_sh_memory() + svc_create_memory_pool() when
+ * the device is behind an SMMU. Sets a 29-bit coherent DMA mask so that
+ * every subsequent dma_alloc_coherent() call yields an IOVA within the
+ * first 512MB (0x0000_0000 - 0x1FFF_FFFF). The driver then adds
+ * %SVC_SDM_DMA_ADDR_OFFSET to the IOVA before passing it to ATF; ATF
+ * strips the offset and uses the SMMU to translate the IOVA to the
+ * underlying physical memory for SDM access.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int svc_setup_dma_memory(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(SVC_SDM_DMA_ADDR_BITS));
+ if (ret) {
+ dev_err(dev,
+ "failed to set %u-bit DMA mask: %d\n",
+ SVC_SDM_DMA_ADDR_BITS, ret);
+ return ret;
+ }
+
+ dev_info(dev,
+ "SMMU enabled: using dynamic DMA allocation (IOVA range 0-512MB)\n");
+ return 0;
+}
+
+/**
* svc_smccc_smc() - secure monitor call between normal and secure world
* @a0: argument passed in registers 0
* @a1: argument passed in registers 1
@@ -1596,7 +1738,7 @@ int stratix10_svc_async_poll(struct stratix10_svc_chan *chan,
return -EAGAIN;
}
- dev_err(ctrl->dev,
+ dev_dbg(ctrl->dev,
"Failed to poll async message ,got status as %ld\n",
handle->res.a0);
return -EINVAL;
@@ -1659,14 +1801,96 @@ static inline void stratix10_smc_1_2(struct stratix10_async_ctrl *actrl,
arm_smccc_1_2_smc(args, res);
}
+static irqreturn_t stratix10_svc_async_irq_handler(int irq, void *dev_id)
+{
+ struct stratix10_svc_controller *ctrl = dev_id;
+ struct stratix10_async_ctrl *actrl = &ctrl->actrl;
+
+ /*
+ * Only disable the IRQ when work is newly queued. If the work is
+ * already pending/running, queue_work() returns false; an extra
+ * disable_irq_nosync() would leave the IRQ disabled permanently
+ * after enable_irq() in the work handler.
+ */
+ if (queue_work(system_bh_wq, &actrl->async_work))
+ disable_irq_nosync(irq);
+ return IRQ_HANDLED;
+}
+
+/**
+ * stratix10_async_workqueue_handler - Handler for the asynchronous
+ * workqueue in Stratix10 service controller.
+ * @work: Pointer to the work structure that contains the asynchronous
+ * workqueue handler.
+ *
+ * This function is the handler for the asynchronous workqueue. It performs
+ * the following tasks:
+ * - Invokes the asynchronous polling on interrupt supervisory call.
+ * - On success, it retrieves the bitmap of pending transactions from mailbox
+ * fifo in ATF.
+ * - It processes each pending transaction by calling the corresponding
+ * callback function.
+ *
+ * The function ensures that the IRQ is enabled after processing the
+ * transactions.
+ */
+static void stratix10_async_workqueue_handler(struct work_struct *work)
+{
+ struct stratix10_async_ctrl *actrl =
+ container_of(work, struct stratix10_async_ctrl, async_work);
+ struct arm_smccc_1_2_regs
+ args = { .a0 = INTEL_SIP_SMC_ASYNC_POLL_ON_IRQ }, res;
+ DECLARE_BITMAP(pend_on_irq, TOTAL_TRANSACTION_IDS);
+ struct stratix10_svc_async_handler *handler;
+ unsigned long transaction_id = 0;
+ u64 bitmap_array[4];
+ async_callback_t cb;
+ void *cb_arg;
+
+ actrl->invoke_fn(actrl, &args, &res);
+ if (res.a0 == INTEL_SIP_SMC_STATUS_OK) {
+ bitmap_array[0] = res.a1;
+ bitmap_array[1] = res.a2;
+ bitmap_array[2] = res.a3;
+ bitmap_array[3] = res.a4;
+ bitmap_from_arr64(pend_on_irq, bitmap_array, TOTAL_TRANSACTION_IDS);
+ do {
+ transaction_id = find_next_bit(pend_on_irq,
+ TOTAL_TRANSACTION_IDS,
+ transaction_id);
+ if (transaction_id >= TOTAL_TRANSACTION_IDS)
+ break;
+
+ cb = NULL;
+ cb_arg = NULL;
+ spin_lock(&actrl->trx_list_lock);
+ hash_for_each_possible(actrl->trx_list, handler,
+ next, transaction_id) {
+ if (handler->transaction_id == transaction_id) {
+ cb = handler->cb;
+ cb_arg = handler->cb_arg;
+ break;
+ }
+ }
+ spin_unlock(&actrl->trx_list_lock);
+ if (cb)
+ cb(cb_arg);
+
+ transaction_id++;
+ } while (transaction_id < TOTAL_TRANSACTION_IDS);
+ }
+ enable_irq(actrl->irq);
+}
+
/**
* stratix10_svc_async_init - Initialize the Stratix10 service
* controller for asynchronous operations.
* @controller: Pointer to the Stratix10 service controller structure.
*
* This function initializes the asynchronous service controller by
- * setting up the necessary data structures and initializing the
- * transaction list.
+ * setting up the necessary data structures ,initializing the
+ * transaction list and registering the IRQ handler for asynchronous
+ * transactions.
*
* Return: 0 on success, -EINVAL if the controller is NULL or already
* initialized, -ENOMEM if memory allocation fails,
@@ -1679,7 +1903,7 @@ static int stratix10_svc_async_init(struct stratix10_svc_controller *controller)
struct stratix10_async_ctrl *actrl;
struct arm_smccc_res res;
struct device *dev;
- int ret;
+ int ret, irq;
if (!controller)
return -EINVAL;
@@ -1727,6 +1951,22 @@ static int stratix10_svc_async_init(struct stratix10_svc_controller *controller)
hash_init(actrl->trx_list);
atomic_set(&actrl->common_achan_refcount, 0);
+ irq = of_irq_get(dev_of_node(dev), 0);
+ if (irq <= 0) {
+ dev_warn(dev, "Failed to get IRQ, falling back to polling mode\n");
+ } else {
+ ret = devm_request_any_context_irq(dev, irq, stratix10_svc_async_irq_handler,
+ IRQF_NO_AUTOEN, "stratix10_svc", controller);
+ if (ret == 0) {
+ dev_info(dev,
+ "Registered IRQ %d for sip async operations\n",
+ irq);
+ actrl->irq = irq;
+ INIT_WORK(&actrl->async_work, stratix10_async_workqueue_handler);
+ enable_irq(actrl->irq);
+ }
+ }
+
actrl->initialized = true;
return 0;
}
@@ -1736,13 +1976,14 @@ static int stratix10_svc_async_init(struct stratix10_svc_controller *controller)
* service controller
* @ctrl: Pointer to the stratix10_svc_controller structure
*
- * This function performs the necessary cleanup for the asynchronous
- * service controller. It checks if the controller is valid and if it
- * has been initialized. It then locks the transaction list and safely
- * removes and deallocates each handler in the list. The function also
- * removes any asynchronous clients associated with the controller's
- * channels and destroys the asynchronous ID pool. Finally, it resets
- * the asynchronous ID pool and invoke function pointers to NULL.
+ * This function performs the necessary cleanup for the asynchronous service
+ * controller. It checks if the controller is valid and if it has been
+ * initialized. Also If the controller has an IRQ assigned, it frees the IRQ
+ * and flushes any pending asynchronous work. It then locks the transaction
+ * list and safely removes and deallocates each handler in the list.
+ * The function also removes any asynchronous clients associated with the
+ * controller's channels and destroys the asynchronous ID pool. Finally, it
+ * resets the asynchronous ID pool and invoke function pointers to NULL.
*
* Return: 0 on success, -EINVAL if the controller is invalid or not
* initialized.
@@ -1764,6 +2005,13 @@ static int stratix10_svc_async_exit(struct stratix10_svc_controller *ctrl)
actrl->initialized = false;
+ if (actrl->irq > 0) {
+ disable_irq(actrl->irq);
+ flush_work(&actrl->async_work);
+ devm_free_irq(ctrl->dev, actrl->irq, ctrl);
+ actrl->irq = 0;
+ }
+
scoped_guard(spinlock_bh, &actrl->trx_list_lock) {
hash_for_each_safe(actrl->trx_list, i, tmp, handler, next) {
ida_free(&handler->achan->job_id_pool,
@@ -1943,34 +2191,50 @@ EXPORT_SYMBOL_GPL(stratix10_svc_done);
void *stratix10_svc_allocate_memory(struct stratix10_svc_chan *chan,
size_t size)
{
+ struct stratix10_svc_controller *ctrl = chan->ctrl;
struct stratix10_svc_data_mem *pmem;
- unsigned long va;
- phys_addr_t pa;
- struct gen_pool *genpool = chan->ctrl->genpool;
- size_t s = roundup(size, 1 << genpool->min_alloc_order);
+ struct gen_pool *genpool;
+ dma_addr_t dma_addr;
+ size_t s;
+ void *va;
pmem = kzalloc_obj(*pmem);
if (!pmem)
return ERR_PTR(-ENOMEM);
- guard(mutex)(&svc_mem_lock);
- va = gen_pool_alloc(genpool, s);
- if (!va) {
- kfree(pmem);
- return ERR_PTR(-ENOMEM);
- }
+ if (ctrl->use_dma_mem) {
+ va = dma_alloc_coherent(ctrl->dev, size, &dma_addr, GFP_KERNEL);
+ if (!va) {
+ kfree(pmem);
+ return ERR_PTR(-ENOMEM);
+ }
- memset((void *)va, 0, s);
- pa = gen_pool_virt_to_phys(genpool, va);
+ pmem->vaddr = va;
+ pmem->paddr = dma_addr + ctrl->dma_addr_offset;
+ pmem->dma_addr = dma_addr;
+ pmem->size = size;
+ } else {
+ genpool = ctrl->genpool;
+ s = roundup(size, 1 << genpool->min_alloc_order);
+
+ va = (void *)gen_pool_alloc(genpool, s);
+ if (!va) {
+ kfree(pmem);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ memset(va, 0, s);
+ pmem->vaddr = va;
+ pmem->paddr = gen_pool_virt_to_phys(genpool, (unsigned long)va);
+ pmem->size = s;
+ }
- pmem->vaddr = (void *)va;
- pmem->paddr = pa;
- pmem->size = s;
+ guard(mutex)(&svc_mem_lock);
list_add_tail(&pmem->node, &svc_data_mem);
- pr_debug("%s: %s: va=%p, pa=0x%016x\n", __func__,
- chan->name, pmem->vaddr, (unsigned int)pmem->paddr);
+ pr_debug("%s: %s: va=%p, addr=0x%016llx\n", __func__,
+ chan->name, pmem->vaddr, (unsigned long long)pmem->paddr);
- return (void *)va;
+ return va;
}
EXPORT_SYMBOL_GPL(stratix10_svc_allocate_memory);
@@ -1983,25 +2247,87 @@ EXPORT_SYMBOL_GPL(stratix10_svc_allocate_memory);
*/
void stratix10_svc_free_memory(struct stratix10_svc_chan *chan, void *kaddr)
{
+ struct stratix10_svc_controller *ctrl = chan->ctrl;
struct stratix10_svc_data_mem *pmem;
guard(mutex)(&svc_mem_lock);
- list_for_each_entry(pmem, &svc_data_mem, node)
- if (pmem->vaddr == kaddr) {
- gen_pool_free(chan->ctrl->genpool,
- (unsigned long)kaddr, pmem->size);
- pmem->vaddr = NULL;
- list_del(&pmem->node);
- kfree(pmem);
- return;
+ list_for_each_entry(pmem, &svc_data_mem, node) {
+ if (pmem->vaddr != kaddr)
+ continue;
+
+ if (ctrl->use_dma_mem) {
+ dma_free_coherent(ctrl->dev, pmem->size,
+ pmem->vaddr, pmem->dma_addr);
+ } else {
+ gen_pool_free(ctrl->genpool,
+ (unsigned long)kaddr, pmem->size);
}
+ list_del(&pmem->node);
+ kfree(pmem);
+ return;
+ }
+
+ dev_warn(ctrl->dev, "free of unknown buffer %p\n", kaddr);
}
EXPORT_SYMBOL_GPL(stratix10_svc_free_memory);
+static void psci_offline_secondary_cpus(struct stratix10_svc_controller *ctrl)
+{
+ cpumask_var_t mask;
+ int cpu, ret;
+
+ if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+ return;
+
+ /*
+ * Snapshot cpu_online_mask before the loop; remove_cpu() modifies it
+ * as each CPU is brought down. Always preserve CPU 0 (boot CPU) to
+ * run the reboot.
+ */
+ cpumask_copy(mask, cpu_online_mask);
+ cpumask_clear_cpu(0, mask);
+
+ /*
+ * Offlining is best-effort: if a CPU refuses to go down we log the
+ * error and continue so the remaining secondaries are still attempted
+ * and the warm reboot can proceed.
+ */
+ for_each_cpu(cpu, mask) {
+ ret = remove_cpu(cpu);
+ if (ret)
+ dev_err(ctrl->dev,
+ "psci_cpu_off: failed to offline CPU%d: %d\n",
+ cpu, ret);
+ }
+
+ free_cpumask_var(mask);
+}
+
+static int psci_cpu_off_reboot_notifier(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct stratix10_svc_controller *ctrl =
+ container_of(nb, struct stratix10_svc_controller, psci_reboot_nb);
+
+ if (reboot_mode != REBOOT_WARM)
+ return NOTIFY_DONE;
+
+ if (action == SYS_RESTART)
+ psci_offline_secondary_cpus(ctrl);
+
+ return NOTIFY_OK;
+}
+
+static void psci_cpu_off_teardown(struct stratix10_svc_controller *ctrl)
+{
+ unregister_reboot_notifier(&ctrl->psci_reboot_nb);
+}
+
static const struct of_device_id stratix10_svc_drv_match[] = {
- {.compatible = "intel,stratix10-svc"},
- {.compatible = "intel,agilex-svc"},
+ { .compatible = "intel,stratix10-svc", .data = &psci_cpu_off_pdata },
+ { .compatible = "intel,agilex-svc", .data = &psci_cpu_off_pdata },
+ { .compatible = "intel,agilex5-svc", .data = &agilex5_pdata },
{},
};
@@ -2012,13 +2338,38 @@ static const char * const chan_names[SVC_NUM_CHANNEL] = {
SVC_CLIENT_HWMON
};
+static void svc_data_mem_cleanup(void *data)
+{
+ struct stratix10_svc_controller *ctrl = data;
+ struct stratix10_svc_data_mem *pmem, *tmp;
+
+ guard(mutex)(&svc_mem_lock);
+
+ list_for_each_entry_safe(pmem, tmp, &svc_data_mem, node) {
+ dev_warn(ctrl->dev, "leaked svc buffer %p, freeing on unbind\n",
+ pmem->vaddr);
+ if (ctrl->use_dma_mem) {
+ dma_free_coherent(ctrl->dev, pmem->size,
+ pmem->vaddr, pmem->dma_addr);
+ } else {
+ gen_pool_free(ctrl->genpool,
+ (unsigned long)pmem->vaddr, pmem->size);
+ }
+ list_del(&pmem->node);
+ kfree(pmem);
+ }
+}
+
static int stratix10_svc_drv_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct stratix10_svc_controller *controller;
- struct gen_pool *genpool;
+ struct gen_pool *genpool = NULL;
struct stratix10_svc_sh_memory *sh_memory;
struct stratix10_svc *svc = NULL;
+ const struct stratix10_svc_pdata *pdata = of_device_get_match_data(dev);
+ struct arm_smccc_res res;
+ bool use_dma_mem = false;
svc_invoke_fn *invoke_fn;
size_t fifo_size;
@@ -2029,18 +2380,38 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev)
if (IS_ERR(invoke_fn))
return -EINVAL;
- sh_memory = devm_kzalloc(dev, sizeof(*sh_memory), GFP_KERNEL);
- if (!sh_memory)
- return -ENOMEM;
+ use_dma_mem = pdata && pdata->use_dma_mem;
- sh_memory->invoke_fn = invoke_fn;
- ret = svc_get_sh_memory(pdev, sh_memory);
- if (ret)
- return ret;
+ if (use_dma_mem) {
+ if (!iommu_get_domain_for_dev(dev)) {
+ dev_err(dev,
+ "SMMU is required for agilex5-svc but no IOMMU domain found\n");
+ dev_err(dev,
+ "Ensure the SMMU node is enabled in the device tree and 'iommus' is set for this node\n");
+ return -ENODEV;
+ }
- genpool = svc_create_memory_pool(pdev, sh_memory);
- if (IS_ERR(genpool))
- return PTR_ERR(genpool);
+ invoke_fn(INTEL_SIP_SMC_SDM_REMAPPER_CONFIG,
+ INTEL_SIP_SMC_SDM_REMAPPER_BYPASS,
+ 0, 0, 0, 0, 0, 0, &res);
+
+ ret = svc_setup_dma_memory(pdev);
+ if (ret)
+ return ret;
+ } else {
+ sh_memory = devm_kzalloc(dev, sizeof(*sh_memory), GFP_KERNEL);
+ if (!sh_memory)
+ return -ENOMEM;
+
+ sh_memory->invoke_fn = invoke_fn;
+ ret = svc_get_sh_memory(pdev, sh_memory);
+ if (ret)
+ return ret;
+
+ genpool = svc_create_memory_pool(pdev, sh_memory);
+ if (IS_ERR(genpool))
+ return PTR_ERR(genpool);
+ }
/* allocate service controller and supporting channel */
controller = devm_kzalloc(dev, struct_size(controller, chans, SVC_NUM_CHANNEL),
@@ -2055,16 +2426,36 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev)
controller->num_active_client = 0;
controller->genpool = genpool;
controller->invoke_fn = invoke_fn;
+ controller->use_dma_mem = use_dma_mem;
+ controller->dma_addr_offset = use_dma_mem ? SVC_SDM_DMA_ADDR_OFFSET : 0;
INIT_LIST_HEAD(&controller->node);
init_completion(&controller->complete_status);
+ if (use_dma_mem) {
+ ret = devm_add_action_or_reset(dev, svc_data_mem_cleanup, controller);
+ if (ret)
+ goto err_destroy_pool;
+ }
+
+ if (pdata && pdata->needs_psci_cpu_off) {
+ controller->psci_reboot_nb.notifier_call =
+ psci_cpu_off_reboot_notifier;
+ controller->psci_reboot_nb.priority = INT_MAX;
+
+ ret = register_reboot_notifier(&controller->psci_reboot_nb);
+ if (ret) {
+ dev_err(dev, "failed to register reboot notifier: %d\n", ret);
+ goto err_destroy_pool;
+ }
+ }
+
ret = stratix10_svc_async_init(controller);
if (ret == -EOPNOTSUPP) {
dev_info(dev, "Intel Service Layer Driver Initialized (sync-only mode)\n");
} else if (ret) {
dev_dbg(dev, "Intel Service Layer Driver: Error on stratix10_svc_async_init %d\n",
ret);
- goto err_destroy_pool;
+ goto err_free_notifier;
} else {
dev_info(dev, "Intel Service Layer Driver Initialized\n");
}
@@ -2150,8 +2541,11 @@ err_free_fifos:
while (i--)
kfifo_free(&controller->chans[i].svc_fifo);
stratix10_svc_async_exit(controller);
+err_free_notifier:
+ psci_cpu_off_teardown(controller);
err_destroy_pool:
- gen_pool_destroy(genpool);
+ if (genpool)
+ gen_pool_destroy(genpool);
return ret;
}
@@ -2166,6 +2560,8 @@ static void stratix10_svc_drv_remove(struct platform_device *pdev)
if (svc->stratix10_svc_hwmon)
platform_device_unregister(svc->stratix10_svc_hwmon);
+ psci_cpu_off_teardown(ctrl);
+
stratix10_svc_async_exit(ctrl);
of_platform_depopulate(ctrl->dev);
diff --git a/drivers/fpga/altera-cvp.c b/drivers/fpga/altera-cvp.c
index 070c025ee215..507e29d3a79f 100644
--- a/drivers/fpga/altera-cvp.c
+++ b/drivers/fpga/altera-cvp.c
@@ -280,6 +280,16 @@ static int altera_cvp_send_block(struct altera_cvp_conf *conf,
return 0;
}
+static void altera_cvp_disable_cvp_mode(struct altera_cvp_conf *conf)
+{
+ u32 val;
+
+ altera_read_config_dword(conf, VSE_CVP_MODE_CTRL, &val);
+ val &= ~VSE_CVP_MODE_CTRL_HIP_CLK_SEL;
+ val &= ~VSE_CVP_MODE_CTRL_CVP_MODE;
+ altera_write_config_dword(conf, VSE_CVP_MODE_CTRL, val);
+}
+
static int altera_cvp_teardown(struct fpga_manager *mgr,
struct fpga_image_info *info)
{
@@ -308,7 +318,29 @@ static int altera_cvp_teardown(struct fpga_manager *mgr,
ret = altera_cvp_wait_status(conf, VSE_CVP_STATUS_CFG_RDY, 0,
conf->priv->poll_time_us);
if (ret)
- dev_err(&mgr->dev, "CFG_RDY == 0 timeout\n");
+ dev_warn(&mgr->dev, "CFG_RDY == 0 timeout\n");
+
+ return ret;
+}
+
+static int altera_cvp_recovery(struct fpga_manager *mgr,
+ struct fpga_image_info *info)
+{
+ struct altera_cvp_conf *conf = mgr->priv;
+ int ret;
+
+ ret = altera_cvp_teardown(mgr, info);
+ if (ret) {
+ /*
+ * CVP_STATUS may get stuck if the device left user mode
+ * before the poll. Exit CvP mode to switch back the clock
+ * feeding CVP_STATUS, then try the teardown again.
+ */
+ altera_cvp_disable_cvp_mode(conf);
+ ret = altera_cvp_teardown(mgr, info);
+ if (ret)
+ dev_err(&mgr->dev, "Tear-down failed\n");
+ }
return ret;
}
@@ -345,7 +377,7 @@ static int altera_cvp_write_init(struct fpga_manager *mgr,
if (val & VSE_CVP_STATUS_CFG_RDY) {
dev_warn(&mgr->dev, "CvP already started, tear down first\n");
- ret = altera_cvp_teardown(mgr, info);
+ ret = altera_cvp_recovery(mgr, info);
if (ret)
return ret;
}
@@ -486,7 +518,7 @@ static int altera_cvp_write_complete(struct fpga_manager *mgr,
u32 mask, val;
int ret;
- ret = altera_cvp_teardown(mgr, info);
+ ret = altera_cvp_recovery(mgr, info);
if (ret)
return ret;
@@ -498,10 +530,7 @@ static int altera_cvp_write_complete(struct fpga_manager *mgr,
}
/* STEP 17 - reset CVP_MODE and HIP_CLK_SEL bit */
- altera_read_config_dword(conf, VSE_CVP_MODE_CTRL, &val);
- val &= ~VSE_CVP_MODE_CTRL_HIP_CLK_SEL;
- val &= ~VSE_CVP_MODE_CTRL_CVP_MODE;
- altera_write_config_dword(conf, VSE_CVP_MODE_CTRL, val);
+ altera_cvp_disable_cvp_mode(conf);
/* STEP 18 - poll PLD_CLK_IN_USE and USER_MODE bits */
mask = VSE_CVP_STATUS_PLD_CLK_IN_USE | VSE_CVP_STATUS_USERMODE;
diff --git a/drivers/fpga/dfl.h b/drivers/fpga/dfl.h
index 9549f63b00eb..9a65617bb870 100644
--- a/drivers/fpga/dfl.h
+++ b/drivers/fpga/dfl.h
@@ -23,7 +23,6 @@
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/io-64-nonatomic-lo-hi.h>
-#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/uuid.h>
diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c
index 2672070ced02..5a0b7780b335 100644
--- a/drivers/fpga/fpga-mgr.c
+++ b/drivers/fpga/fpga-mgr.c
@@ -744,7 +744,7 @@ EXPORT_SYMBOL_GPL(fpga_mgr_put);
* @mgr: fpga manager
*
* Given a pointer to FPGA Manager (from fpga_mgr_get() or
- * of_fpga_mgr_put()) attempt to get the mutex. The user should call
+ * of_fpga_mgr_get()) attempt to get the mutex. The user should call
* fpga_mgr_lock() and verify that it returns 0 before attempting to
* program the FPGA. Likewise, the user should call fpga_mgr_unlock
* when done programming the FPGA.
diff --git a/drivers/fpga/stratix10-soc.c b/drivers/fpga/stratix10-soc.c
index b8ec2e6f615f..5aed560050c2 100644
--- a/drivers/fpga/stratix10-soc.c
+++ b/drivers/fpga/stratix10-soc.c
@@ -286,7 +286,7 @@ static int s10_ops_write(struct fpga_manager *mgr, const char *buf,
/*
* Loop waiting for buffers to be returned. When a buffer is returned,
- * reuse it to send more data or free if if all data has been sent.
+ * reuse it to send more data or free it if all data has been sent.
*/
while (count > 0 || s10_free_buffer_count(mgr) != NUM_SVC_BUFS) {
reinit_completion(&priv->status_return_completion);
diff --git a/drivers/fpga/xilinx-selectmap.c b/drivers/fpga/xilinx-selectmap.c
index d0cbb5fdfe3a..ff167b23fded 100644
--- a/drivers/fpga/xilinx-selectmap.c
+++ b/drivers/fpga/xilinx-selectmap.c
@@ -19,6 +19,8 @@
struct xilinx_selectmap_conf {
struct xilinx_fpga_core core;
void __iomem *base;
+ struct gpio_desc *csi_b;
+ struct gpio_desc *rdwr_b;
};
#define to_xilinx_selectmap_conf(obj) \
@@ -30,16 +32,28 @@ static int xilinx_selectmap_write(struct xilinx_fpga_core *core,
struct xilinx_selectmap_conf *conf = to_xilinx_selectmap_conf(core);
size_t i;
+ /*
+ * Select this device and put its SelectMAP port into write mode.
+ *
+ * RDWR_B is set before CSI_B. UG470 describes in chapter
+ * "SelectMAP ABORT" that changing RDWR_B while the device is
+ * selected aborts the configuration on the next CCLK.
+ */
+ gpiod_set_value(conf->rdwr_b, 1);
+ gpiod_set_value(conf->csi_b, 1);
+
for (i = 0; i < count; ++i)
writeb(buf[i], conf->base);
+ gpiod_set_value(conf->csi_b, 0);
+ gpiod_set_value(conf->rdwr_b, 0);
+
return 0;
}
static int xilinx_selectmap_probe(struct platform_device *pdev)
{
struct xilinx_selectmap_conf *conf;
- struct gpio_desc *gpio;
void __iomem *base;
conf = devm_kzalloc(&pdev->dev, sizeof(*conf), GFP_KERNEL);
@@ -55,16 +69,23 @@ static int xilinx_selectmap_probe(struct platform_device *pdev)
"ioremap error\n");
conf->base = base;
- /* CSI_B is active low */
- gpio = devm_gpiod_get_optional(&pdev->dev, "csi", GPIOD_OUT_HIGH);
- if (IS_ERR(gpio))
- return dev_err_probe(&pdev->dev, PTR_ERR(gpio),
+ /*
+ * Request both signals deasserted, so a device sharing the SelectMAP
+ * bus with others stays off that bus until its bitstream is written.
+ *
+ * The value in the gpiod flags is logical, gpiolib drives the line
+ * high for GPIOD_OUT_LOW when the firmware describes it active low.
+ */
+ conf->csi_b = devm_gpiod_get_optional(&pdev->dev, "csi",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(conf->csi_b))
+ return dev_err_probe(&pdev->dev, PTR_ERR(conf->csi_b),
"Failed to get CSI_B gpio\n");
- /* RDWR_B is active low */
- gpio = devm_gpiod_get_optional(&pdev->dev, "rdwr", GPIOD_OUT_HIGH);
- if (IS_ERR(gpio))
- return dev_err_probe(&pdev->dev, PTR_ERR(gpio),
+ conf->rdwr_b = devm_gpiod_get_optional(&pdev->dev, "rdwr",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(conf->rdwr_b))
+ return dev_err_probe(&pdev->dev, PTR_ERR(conf->rdwr_b),
"Failed to get RDWR_B gpio\n");
return xilinx_core_probe(&conf->core);
diff --git a/drivers/greybus/greybus_trace.h b/drivers/greybus/greybus_trace.h
index 616a3bd61aa6..3c331e09ea83 100644
--- a/drivers/greybus/greybus_trace.h
+++ b/drivers/greybus/greybus_trace.h
@@ -339,7 +339,7 @@ DEFINE_INTERFACE_EVENT(gb_interface_create);
DEFINE_INTERFACE_EVENT(gb_interface_release);
/*
- * Occurs after an interface been registerd.
+ * Occurs after an interface been registered.
*/
DEFINE_INTERFACE_EVENT(gb_interface_add);
diff --git a/drivers/hwtracing/coresight/coresight-config.c b/drivers/hwtracing/coresight/coresight-config.c
index 4f72ae71b696..0e296289f286 100644
--- a/drivers/hwtracing/coresight/coresight-config.c
+++ b/drivers/hwtracing/coresight/coresight-config.c
@@ -73,13 +73,12 @@ static void cscfg_init_reg_param(struct cscfg_feature_csdev *feat_csdev,
/* set values into the driver locations referenced in cscfg_reg_csdev */
static int cscfg_set_on_enable(struct cscfg_feature_csdev *feat_csdev)
{
- unsigned long flags;
int i;
- raw_spin_lock_irqsave(feat_csdev->drv_spinlock, flags);
- for (i = 0; i < feat_csdev->nr_regs; i++)
- cscfg_set_reg(&feat_csdev->regs_csdev[i]);
- raw_spin_unlock_irqrestore(feat_csdev->drv_spinlock, flags);
+ scoped_guard(feat_csdev_lock, feat_csdev) {
+ for (i = 0; i < feat_csdev->nr_regs; i++)
+ cscfg_set_reg(&feat_csdev->regs_csdev[i]);
+ }
dev_dbg(&feat_csdev->csdev->dev, "Feature %s: %s",
feat_csdev->feat_desc->name, "set on enable");
return 0;
@@ -88,13 +87,12 @@ static int cscfg_set_on_enable(struct cscfg_feature_csdev *feat_csdev)
/* copy back values from the driver locations referenced in cscfg_reg_csdev */
static void cscfg_save_on_disable(struct cscfg_feature_csdev *feat_csdev)
{
- unsigned long flags;
int i;
- raw_spin_lock_irqsave(feat_csdev->drv_spinlock, flags);
- for (i = 0; i < feat_csdev->nr_regs; i++)
- cscfg_save_reg(&feat_csdev->regs_csdev[i]);
- raw_spin_unlock_irqrestore(feat_csdev->drv_spinlock, flags);
+ scoped_guard(feat_csdev_lock, feat_csdev) {
+ for (i = 0; i < feat_csdev->nr_regs; i++)
+ cscfg_save_reg(&feat_csdev->regs_csdev[i]);
+ }
dev_dbg(&feat_csdev->csdev->dev, "Feature %s: %s",
feat_csdev->feat_desc->name, "save on disable");
}
diff --git a/drivers/hwtracing/coresight/coresight-config.h b/drivers/hwtracing/coresight/coresight-config.h
index 90fd937d3bd8..ae6295e30813 100644
--- a/drivers/hwtracing/coresight/coresight-config.h
+++ b/drivers/hwtracing/coresight/coresight-config.h
@@ -7,6 +7,7 @@
#ifndef _CORESIGHT_CORESIGHT_CONFIG_H
#define _CORESIGHT_CORESIGHT_CONFIG_H
+#include <linux/cleanup.h>
#include <linux/coresight.h>
#include <linux/types.h>
@@ -259,4 +260,25 @@ void cscfg_csdev_disable_config(struct cscfg_config_csdev *config_csdev);
/* reset a feature to default values */
void cscfg_reset_feat(struct cscfg_feature_csdev *feat_csdev);
+#define feat_csdev_lock(feat_csdev, flags) \
+ do { \
+ raw_spinlock_t *__lock = feat_csdev->drv_spinlock; \
+ typecheck(unsigned long, flags); \
+ if (__lock) \
+ raw_spin_lock_irqsave(__lock, flags); \
+ } while (0)
+
+#define feat_csdev_unlock(feat_csdev, flags) \
+ do { \
+ raw_spinlock_t *__lock = feat_csdev->drv_spinlock; \
+ typecheck(unsigned long, flags); \
+ if (__lock) \
+ raw_spin_unlock_irqrestore(__lock, flags); \
+ } while (0)
+
+DEFINE_LOCK_GUARD_1(feat_csdev_lock, struct cscfg_feature_csdev,
+ feat_csdev_lock(_T->lock, _T->flags),
+ feat_csdev_unlock(_T->lock, _T->flags),
+ unsigned long flags)
+
#endif /* _CORESIGHT_CORESIGHT_CONFIG_H */
diff --git a/drivers/hwtracing/coresight/coresight-core.c b/drivers/hwtracing/coresight/coresight-core.c
index 928488a216d4..d2f9c1fb77d9 100644
--- a/drivers/hwtracing/coresight/coresight-core.c
+++ b/drivers/hwtracing/coresight/coresight-core.c
@@ -1543,6 +1543,7 @@ coresight_init_device(struct coresight_desc *desc)
csdev->ops = desc->ops;
csdev->access = desc->access;
csdev->orphan = true;
+ csdev->flags = desc->flags;
if (desc->flags & CORESIGHT_DESC_CPU_BOUND) {
csdev->cpu = desc->cpu;
diff --git a/drivers/hwtracing/coresight/coresight-etm.h b/drivers/hwtracing/coresight/coresight-etm.h
index 1d753cca2943..cf65d89a8e98 100644
--- a/drivers/hwtracing/coresight/coresight-etm.h
+++ b/drivers/hwtracing/coresight/coresight-etm.h
@@ -141,6 +141,30 @@
ETM_EVENT_NOT_A)
/**
+ * struct etm_caps - specifics ETM capabilities
+ * @port_size: port size as reported by ETMCR bit 4-6 and 21.
+ * @nr_addr_cmp:Number of pairs of address comparators as found in ETMCCR.
+ * @nr_cntr: Number of counters as found in ETMCCR bit 13-15.
+ * @nr_ext_inp: Number of external input as found in ETMCCR bit 17-19.
+ * @nr_ext_out: Number of external output as found in ETMCCR bit 20-22.
+ * @nr_ctxid_cmp: Number of contextID comparators as found in ETMCCR bit 24-25.
+ * @fifofull: FIFOFULL logic is present.
+ * @timestamp: Timestamping is implemented.
+ * @retstack: Return stack is implemented.
+ */
+struct etm_caps {
+ int port_size;
+ u8 nr_addr_cmp;
+ u8 nr_cntr;
+ u8 nr_ext_inp;
+ u8 nr_ext_out;
+ u8 nr_ctxid_cmp;
+ bool fifofull : 1;
+ bool timestamp : 1;
+ bool retstack : 1;
+};
+
+/**
* struct etm_config - configuration information related to an ETM
* @mode: controls various modes supported by this ETM/PTM.
* @ctrl: used in conjunction with @mode.
@@ -212,21 +236,15 @@ struct etm_config {
* @csdev: component vitals needed by the framework.
* @spinlock: only one at a time pls.
* @cpu: the cpu this component is affined to.
- * @port_size: port size as reported by ETMCR bit 4-6 and 21.
* @arch: ETM/PTM version number.
+ * @caps: ETM capabilities.
* @use_cpu14: true if management registers need to be accessed via CP14.
* @sticky_enable: true if ETM base configuration has been done.
* @boot_enable:true if we should start tracing at boot time.
* @os_unlock: true if access to management registers is allowed.
- * @nr_addr_cmp:Number of pairs of address comparators as found in ETMCCR.
- * @nr_cntr: Number of counters as found in ETMCCR bit 13-15.
- * @nr_ext_inp: Number of external input as found in ETMCCR bit 17-19.
- * @nr_ext_out: Number of external output as found in ETMCCR bit 20-22.
- * @nr_ctxid_cmp: Number of contextID comparators as found in ETMCCR bit 24-25.
- * @etmccr: value of register ETMCCR.
- * @etmccer: value of register ETMCCER.
* @traceid: value of the current ID for this component.
- * @config: structure holding configuration parameters.
+ * @curr_config: structure holding current running configuration.
+ * @sysfs_config: structure holding sysfs mode configuration.
*/
struct etm_drvdata {
struct csdev_access csa;
@@ -234,21 +252,15 @@ struct etm_drvdata {
struct coresight_device *csdev;
spinlock_t spinlock;
int cpu;
- int port_size;
u8 arch;
+ struct etm_caps caps;
bool use_cp14;
bool sticky_enable;
bool boot_enable;
bool os_unlock;
- u8 nr_addr_cmp;
- u8 nr_cntr;
- u8 nr_ext_inp;
- u8 nr_ext_out;
- u8 nr_ctxid_cmp;
- u32 etmccr;
- u32 etmccer;
u32 traceid;
- struct etm_config config;
+ struct etm_config curr_config;
+ struct etm_config sysfs_config;
};
static inline void etm_writel(struct etm_drvdata *drvdata,
diff --git a/drivers/hwtracing/coresight/coresight-etm3x-core.c b/drivers/hwtracing/coresight/coresight-etm3x-core.c
index 862ad0786699..b670a324bb41 100644
--- a/drivers/hwtracing/coresight/coresight-etm3x-core.c
+++ b/drivers/hwtracing/coresight/coresight-etm3x-core.c
@@ -308,7 +308,8 @@ void etm_config_trace_mode(struct etm_config *config)
static int etm_parse_event_config(struct etm_drvdata *drvdata,
struct perf_event *event)
{
- struct etm_config *config = &drvdata->config;
+ const struct etm_caps *caps = &drvdata->caps;
+ struct etm_config *curr_config = &drvdata->curr_config;
struct perf_event_attr *attr = &event->attr;
u8 ts_level;
@@ -316,28 +317,28 @@ static int etm_parse_event_config(struct etm_drvdata *drvdata,
return -EINVAL;
/* Clear configuration from previous run */
- memset(config, 0, sizeof(struct etm_config));
+ memset(curr_config, 0, sizeof(struct etm_config));
if (attr->exclude_kernel)
- config->mode = ETM_MODE_EXCL_KERN;
+ curr_config->mode = ETM_MODE_EXCL_KERN;
if (attr->exclude_user)
- config->mode = ETM_MODE_EXCL_USER;
+ curr_config->mode = ETM_MODE_EXCL_USER;
/* Always start from the default config */
- etm_set_default(config);
+ etm_set_default(curr_config);
/*
* By default the tracers are configured to trace the whole address
* range. Narrow the field only if requested by user space.
*/
- if (config->mode)
- etm_config_trace_mode(config);
+ if (curr_config->mode)
+ etm_config_trace_mode(curr_config);
- config->ctrl = 0;
+ curr_config->ctrl = 0;
if (ATTR_CFG_GET_FLD(attr, cycacc))
- config->ctrl |= ETMCR_CYC_ACC;
+ curr_config->ctrl |= ETMCR_CYC_ACC;
ts_level = max(ATTR_CFG_GET_FLD(attr, timestamp),
ATTR_CFG_GET_FLD(attr, deprecated_timestamp));
@@ -349,16 +350,15 @@ static int etm_parse_event_config(struct etm_drvdata *drvdata,
}
if (ts_level)
- config->ctrl |= ETMCR_TIMESTAMP_EN;
+ curr_config->ctrl |= ETMCR_TIMESTAMP_EN;
/*
* Possible to have cores with PTM (supports ret stack) and ETM (never
* has ret stack) on the same SoC. So only enable when it can be honored
* - trace will still continue normally otherwise.
*/
- if (ATTR_CFG_GET_FLD(attr, retstack) &&
- (drvdata->etmccer & ETMCCER_RETSTACK))
- config->ctrl |= ETMCR_RETURN_STACK;
+ if (ATTR_CFG_GET_FLD(attr, retstack) && (caps->retstack))
+ curr_config->ctrl |= ETMCR_RETURN_STACK;
return 0;
}
@@ -367,7 +367,8 @@ static int etm_enable_hw(struct etm_drvdata *drvdata)
{
int i, rc;
u32 etmcr;
- struct etm_config *config = &drvdata->config;
+ const struct etm_caps *caps = &drvdata->caps;
+ struct etm_config *curr_config = &drvdata->curr_config;
struct coresight_device *csdev = drvdata->csdev;
CS_UNLOCK(drvdata->csa.base);
@@ -388,41 +389,41 @@ static int etm_enable_hw(struct etm_drvdata *drvdata)
etmcr = etm_readl(drvdata, ETMCR);
/* Clear setting from a previous run if need be */
etmcr &= ~ETM3X_SUPPORTED_OPTIONS;
- etmcr |= drvdata->port_size;
+ etmcr |= caps->port_size;
etmcr |= ETMCR_ETM_EN;
- etm_writel(drvdata, config->ctrl | etmcr, ETMCR);
- etm_writel(drvdata, config->trigger_event, ETMTRIGGER);
- etm_writel(drvdata, config->startstop_ctrl, ETMTSSCR);
- etm_writel(drvdata, config->enable_event, ETMTEEVR);
- etm_writel(drvdata, config->enable_ctrl1, ETMTECR1);
- etm_writel(drvdata, config->fifofull_level, ETMFFLR);
- for (i = 0; i < drvdata->nr_addr_cmp; i++) {
- etm_writel(drvdata, config->addr_val[i], ETMACVRn(i));
- etm_writel(drvdata, config->addr_acctype[i], ETMACTRn(i));
+ etm_writel(drvdata, curr_config->ctrl | etmcr, ETMCR);
+ etm_writel(drvdata, curr_config->trigger_event, ETMTRIGGER);
+ etm_writel(drvdata, curr_config->startstop_ctrl, ETMTSSCR);
+ etm_writel(drvdata, curr_config->enable_event, ETMTEEVR);
+ etm_writel(drvdata, curr_config->enable_ctrl1, ETMTECR1);
+ etm_writel(drvdata, curr_config->fifofull_level, ETMFFLR);
+ for (i = 0; i < caps->nr_addr_cmp; i++) {
+ etm_writel(drvdata, curr_config->addr_val[i], ETMACVRn(i));
+ etm_writel(drvdata, curr_config->addr_acctype[i], ETMACTRn(i));
}
- for (i = 0; i < drvdata->nr_cntr; i++) {
- etm_writel(drvdata, config->cntr_rld_val[i], ETMCNTRLDVRn(i));
- etm_writel(drvdata, config->cntr_event[i], ETMCNTENRn(i));
- etm_writel(drvdata, config->cntr_rld_event[i],
+ for (i = 0; i < caps->nr_cntr; i++) {
+ etm_writel(drvdata, curr_config->cntr_rld_val[i], ETMCNTRLDVRn(i));
+ etm_writel(drvdata, curr_config->cntr_event[i], ETMCNTENRn(i));
+ etm_writel(drvdata, curr_config->cntr_rld_event[i],
ETMCNTRLDEVRn(i));
- etm_writel(drvdata, config->cntr_val[i], ETMCNTVRn(i));
+ etm_writel(drvdata, curr_config->cntr_val[i], ETMCNTVRn(i));
}
- etm_writel(drvdata, config->seq_12_event, ETMSQ12EVR);
- etm_writel(drvdata, config->seq_21_event, ETMSQ21EVR);
- etm_writel(drvdata, config->seq_23_event, ETMSQ23EVR);
- etm_writel(drvdata, config->seq_31_event, ETMSQ31EVR);
- etm_writel(drvdata, config->seq_32_event, ETMSQ32EVR);
- etm_writel(drvdata, config->seq_13_event, ETMSQ13EVR);
- etm_writel(drvdata, config->seq_curr_state, ETMSQR);
- for (i = 0; i < drvdata->nr_ext_out; i++)
+ etm_writel(drvdata, curr_config->seq_12_event, ETMSQ12EVR);
+ etm_writel(drvdata, curr_config->seq_21_event, ETMSQ21EVR);
+ etm_writel(drvdata, curr_config->seq_23_event, ETMSQ23EVR);
+ etm_writel(drvdata, curr_config->seq_31_event, ETMSQ31EVR);
+ etm_writel(drvdata, curr_config->seq_32_event, ETMSQ32EVR);
+ etm_writel(drvdata, curr_config->seq_13_event, ETMSQ13EVR);
+ etm_writel(drvdata, curr_config->seq_curr_state, ETMSQR);
+ for (i = 0; i < caps->nr_ext_out; i++)
etm_writel(drvdata, ETM_DEFAULT_EVENT_VAL, ETMEXTOUTEVRn(i));
- for (i = 0; i < drvdata->nr_ctxid_cmp; i++)
- etm_writel(drvdata, config->ctxid_pid[i], ETMCIDCVRn(i));
- etm_writel(drvdata, config->ctxid_mask, ETMCIDCMR);
- etm_writel(drvdata, config->sync_freq, ETMSYNCFR);
+ for (i = 0; i < caps->nr_ctxid_cmp; i++)
+ etm_writel(drvdata, curr_config->ctxid_pid[i], ETMCIDCVRn(i));
+ etm_writel(drvdata, curr_config->ctxid_mask, ETMCIDCMR);
+ etm_writel(drvdata, curr_config->sync_freq, ETMSYNCFR);
/* No external input selected */
etm_writel(drvdata, 0x0, ETMEXTINSELR);
- etm_writel(drvdata, config->timestamp_event, ETMTSEVR);
+ etm_writel(drvdata, curr_config->timestamp_event, ETMTSEVR);
/* No auxiliary control selected */
etm_writel(drvdata, 0x0, ETMAUXCR);
etm_writel(drvdata, drvdata->traceid, ETMTRACEIDR);
@@ -448,26 +449,22 @@ struct etm_enable_arg {
static void etm_enable_sysfs_smp_call(void *info)
{
struct etm_enable_arg *arg = info;
+ struct etm_drvdata *drvdata;
struct coresight_device *csdev;
if (WARN_ON(!arg))
return;
- csdev = arg->drvdata->csdev;
- if (!coresight_take_mode(csdev, CS_MODE_SYSFS)) {
- /* Someone is already using the tracer */
- arg->rc = -EBUSY;
- return;
- }
+ drvdata = arg->drvdata;
+ csdev = drvdata->csdev;
- arg->rc = etm_enable_hw(arg->drvdata);
+ drvdata->traceid = arg->path->trace_id;
- /* The tracer didn't start */
- if (arg->rc) {
- coresight_set_mode(csdev, CS_MODE_DISABLED);
+ arg->rc = etm_enable_hw(arg->drvdata);
+ if (arg->rc)
return;
- }
+ drvdata->sticky_enable = true;
csdev->path = arg->path;
}
@@ -512,34 +509,33 @@ static int etm_enable_sysfs(struct coresight_device *csdev, struct coresight_pat
struct etm_enable_arg arg = { };
int ret;
- spin_lock(&drvdata->spinlock);
-
- drvdata->traceid = path->trace_id;
-
- /*
- * Configure the ETM only if the CPU is online. If it isn't online
- * hw configuration will take place on the local CPU during bring up.
- */
- if (cpu_online(drvdata->cpu)) {
- arg.drvdata = drvdata;
- arg.path = path;
- ret = smp_call_function_single(drvdata->cpu,
- etm_enable_sysfs_smp_call, &arg, 1);
- if (!ret)
- ret = arg.rc;
- if (!ret)
- drvdata->sticky_enable = true;
- } else {
- ret = -ENODEV;
+ if (!coresight_take_mode(csdev, CS_MODE_SYSFS)) {
+ /* Someone is already using the tracer */
+ return -EBUSY;
}
- if (ret)
- etm_release_trace_id(drvdata);
+ arg.drvdata = drvdata;
+ arg.path = path;
- spin_unlock(&drvdata->spinlock);
+ scoped_guard(spinlock, &drvdata->spinlock) {
+ drvdata->curr_config = drvdata->sysfs_config;
+ }
+ ret = smp_call_function_single(drvdata->cpu,
+ etm_enable_sysfs_smp_call, &arg, 1);
if (!ret)
+ ret = arg.rc;
+ else
+ ret = -ENODEV;
+
+ if (!ret) {
dev_dbg(&csdev->dev, "ETM tracing enabled\n");
+ } else {
+ etm_release_trace_id(drvdata);
+ /* The tracer didn't start */
+ coresight_set_mode(csdev, CS_MODE_DISABLED);
+ }
+
return ret;
}
@@ -565,17 +561,18 @@ static int etm_enable(struct coresight_device *csdev, struct perf_event *event,
static void etm_disable_hw(struct etm_drvdata *drvdata)
{
int i;
- struct etm_config *config = &drvdata->config;
+ const struct etm_caps *caps = &drvdata->caps;
+ struct etm_config *curr_config = &drvdata->curr_config;
struct coresight_device *csdev = drvdata->csdev;
CS_UNLOCK(drvdata->csa.base);
etm_set_prog(drvdata);
/* Read back sequencer and counters for post trace analysis */
- config->seq_curr_state = (etm_readl(drvdata, ETMSQR) & ETM_SQR_MASK);
+ curr_config->seq_curr_state = (etm_readl(drvdata, ETMSQR) & ETM_SQR_MASK);
- for (i = 0; i < drvdata->nr_cntr; i++)
- config->cntr_val[i] = etm_readl(drvdata, ETMCNTVRn(i));
+ for (i = 0; i < caps->nr_cntr; i++)
+ curr_config->cntr_val[i] = etm_readl(drvdata, ETMCNTVRn(i));
etm_set_pwrdwn(drvdata);
coresight_disclaim_device_unlocked(csdev);
@@ -593,7 +590,6 @@ static void etm_disable_sysfs_smp_call(void *info)
etm_disable_hw(drvdata);
drvdata->csdev->path = NULL;
- coresight_set_mode(drvdata->csdev, CS_MODE_DISABLED);
}
static void etm_disable_perf(struct coresight_device *csdev)
@@ -630,8 +626,10 @@ static void etm_disable_perf(struct coresight_device *csdev)
static void etm_disable_sysfs(struct coresight_device *csdev)
{
struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
-
- spin_lock(&drvdata->spinlock);
+ const struct etm_caps *caps = &drvdata->caps;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
+ const struct etm_config *curr_config = &drvdata->curr_config;
+ int i;
/*
* Executing etm_disable_hw on the cpu whose ETM is being disabled
@@ -640,7 +638,14 @@ static void etm_disable_sysfs(struct coresight_device *csdev)
smp_call_function_single(drvdata->cpu, etm_disable_sysfs_smp_call,
drvdata, 1);
- spin_unlock(&drvdata->spinlock);
+ /*
+ * Userspace may read seq_curr_state and cntr_val through sysfs
+ * after the sysfs-session has been disabled.
+ */
+ for (i = 0; i < caps->nr_cntr; i++)
+ sysfs_config->cntr_val[i] = curr_config->cntr_val[i];
+
+ sysfs_config->seq_curr_state = curr_config->seq_curr_state;
/*
* we only release trace IDs when resetting sysfs.
@@ -649,6 +654,8 @@ static void etm_disable_sysfs(struct coresight_device *csdev)
* prior trace id allocation method
*/
+ coresight_set_mode(drvdata->csdev, CS_MODE_DISABLED);
+
dev_dbg(&csdev->dev, "ETM tracing disabled\n");
}
@@ -720,7 +727,9 @@ static void etm_init_arch_data(void *info)
{
u32 etmidr;
u32 etmccr;
+ u32 etmccer;
struct etm_drvdata *drvdata = info;
+ struct etm_caps *caps = &drvdata->caps;
/* Make sure all registers are accessible */
etm_os_unlock(drvdata);
@@ -745,16 +754,19 @@ static void etm_init_arch_data(void *info)
/* Find all capabilities */
etmidr = etm_readl(drvdata, ETMIDR);
drvdata->arch = BMVAL(etmidr, 4, 11);
- drvdata->port_size = etm_readl(drvdata, ETMCR) & PORT_SIZE_MASK;
+ caps->port_size = etm_readl(drvdata, ETMCR) & PORT_SIZE_MASK;
+
+ etmccer = etm_readl(drvdata, ETMCCER);
+ caps->timestamp = !!(etmccer & ETMCCER_TIMESTAMP);
+ caps->retstack = !!(etmccer & ETMCCER_RETSTACK);
- drvdata->etmccer = etm_readl(drvdata, ETMCCER);
etmccr = etm_readl(drvdata, ETMCCR);
- drvdata->etmccr = etmccr;
- drvdata->nr_addr_cmp = BMVAL(etmccr, 0, 3) * 2;
- drvdata->nr_cntr = BMVAL(etmccr, 13, 15);
- drvdata->nr_ext_inp = BMVAL(etmccr, 17, 19);
- drvdata->nr_ext_out = BMVAL(etmccr, 20, 22);
- drvdata->nr_ctxid_cmp = BMVAL(etmccr, 24, 25);
+ caps->fifofull = !!(etmccr & ETMCCR_FIFOFULL);
+ caps->nr_addr_cmp = BMVAL(etmccr, 0, 3) * 2;
+ caps->nr_cntr = BMVAL(etmccr, 13, 15);
+ caps->nr_ext_inp = BMVAL(etmccr, 17, 19);
+ caps->nr_ext_out = BMVAL(etmccr, 20, 22);
+ caps->nr_ctxid_cmp = BMVAL(etmccr, 24, 25);
coresight_clear_self_claim_tag_unlocked(&drvdata->csa);
etm_set_pwrdwn(drvdata);
@@ -832,7 +844,7 @@ static int etm_probe(struct amba_device *adev, const struct amba_id *id)
if (etm_arch_supported(drvdata->arch) == false)
return -EINVAL;
- etm_set_default(&drvdata->config);
+ etm_set_default(&drvdata->sysfs_config);
pdata = coresight_get_platform_data(dev);
if (IS_ERR(pdata))
diff --git a/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c b/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c
index b3c67e96a82a..d185fb8270e1 100644
--- a/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c
+++ b/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c
@@ -6,17 +6,56 @@
#include <linux/pid_namespace.h>
#include <linux/pm_runtime.h>
+#include <linux/smp.h>
#include <linux/sysfs.h>
#include "coresight-etm.h"
#include "coresight-priv.h"
+struct etm_readl_cslocked_arg {
+ struct etm_drvdata *drvdata;
+ u32 off;
+ unsigned long *val;
+};
+
+static void etm_readl_cslocked_smp_call(void *info)
+{
+ struct etm_readl_cslocked_arg *arg = info;
+
+ CS_UNLOCK(arg->drvdata->csa.base);
+ *arg->val = etm_readl(arg->drvdata, arg->off);
+ CS_LOCK(arg->drvdata->csa.base);
+}
+
+static int etm_readl_cslocked(struct etm_drvdata *drvdata, u32 off,
+ unsigned long *val)
+{
+ int ret;
+ struct etm_readl_cslocked_arg arg;
+
+ ret = pm_runtime_get_sync(drvdata->csdev->dev.parent);
+ if (ret < 0)
+ goto out;
+
+ arg.drvdata = drvdata;
+ arg.off = off;
+ arg.val = val;
+
+ ret = smp_call_function_single(drvdata->cpu,
+ etm_readl_cslocked_smp_call,
+ (void *)&arg, 1);
+out:
+ pm_runtime_put(drvdata->csdev->dev.parent);
+ return ret;
+}
+
static ssize_t nr_addr_cmp_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ const struct etm_caps *caps = &drvdata->caps;
- val = drvdata->nr_addr_cmp;
+ val = caps->nr_addr_cmp;
return sprintf(buf, "%#lx\n", val);
}
static DEVICE_ATTR_RO(nr_addr_cmp);
@@ -25,8 +64,9 @@ static ssize_t nr_cntr_show(struct device *dev,
struct device_attribute *attr, char *buf)
{ unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ const struct etm_caps *caps = &drvdata->caps;
- val = drvdata->nr_cntr;
+ val = caps->nr_cntr;
return sprintf(buf, "%#lx\n", val);
}
static DEVICE_ATTR_RO(nr_cntr);
@@ -37,7 +77,7 @@ static ssize_t nr_ctxid_cmp_show(struct device *dev,
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- val = drvdata->nr_ctxid_cmp;
+ val = drvdata->caps.nr_ctxid_cmp;
return sprintf(buf, "%#lx\n", val);
}
static DEVICE_ATTR_RO(nr_ctxid_cmp);
@@ -45,18 +85,13 @@ static DEVICE_ATTR_RO(nr_ctxid_cmp);
static ssize_t etmsr_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- unsigned long flags, val;
+ int ret;
+ unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- pm_runtime_get_sync(dev->parent);
- spin_lock_irqsave(&drvdata->spinlock, flags);
- CS_UNLOCK(drvdata->csa.base);
-
- val = etm_readl(drvdata, ETMSR);
-
- CS_LOCK(drvdata->csa.base);
- spin_unlock_irqrestore(&drvdata->spinlock, flags);
- pm_runtime_put(dev->parent);
+ ret = etm_readl_cslocked(drvdata, ETMSR, &val);
+ if (ret)
+ return ret;
return sprintf(buf, "%#lx\n", val);
}
@@ -69,22 +104,26 @@ static ssize_t reset_store(struct device *dev,
int i, ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
+ if (IS_ERR_OR_NULL(drvdata->csdev) ||
+ coresight_get_mode(drvdata->csdev) == CS_MODE_SYSFS)
+ return -EBUSY;
+
if (val) {
spin_lock(&drvdata->spinlock);
- memset(config, 0, sizeof(struct etm_config));
- config->mode = ETM_MODE_EXCLUDE;
- config->trigger_event = ETM_DEFAULT_EVENT_VAL;
- for (i = 0; i < drvdata->nr_addr_cmp; i++) {
- config->addr_type[i] = ETM_ADDR_TYPE_NONE;
+ memset(sysfs_config, 0, sizeof(struct etm_config));
+ sysfs_config->mode = ETM_MODE_EXCLUDE;
+ sysfs_config->trigger_event = ETM_DEFAULT_EVENT_VAL;
+ for (i = 0; i < drvdata->caps.nr_addr_cmp; i++) {
+ sysfs_config->addr_type[i] = ETM_ADDR_TYPE_NONE;
}
- etm_set_default(config);
+ etm_set_default(sysfs_config);
etm_release_trace_id(drvdata);
spin_unlock(&drvdata->spinlock);
}
@@ -98,9 +137,9 @@ static ssize_t mode_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
- val = config->mode;
+ val = sysfs_config->mode;
return sprintf(buf, "%#lx\n", val);
}
@@ -111,62 +150,63 @@ static ssize_t mode_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ const struct etm_caps *caps = &drvdata->caps;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
spin_lock(&drvdata->spinlock);
- config->mode = val & ETM_MODE_ALL;
+ sysfs_config->mode = val & ETM_MODE_ALL;
- if (config->mode & ETM_MODE_EXCLUDE)
- config->enable_ctrl1 |= ETMTECR1_INC_EXC;
+ if (sysfs_config->mode & ETM_MODE_EXCLUDE)
+ sysfs_config->enable_ctrl1 |= ETMTECR1_INC_EXC;
else
- config->enable_ctrl1 &= ~ETMTECR1_INC_EXC;
+ sysfs_config->enable_ctrl1 &= ~ETMTECR1_INC_EXC;
- if (config->mode & ETM_MODE_CYCACC)
- config->ctrl |= ETMCR_CYC_ACC;
+ if (sysfs_config->mode & ETM_MODE_CYCACC)
+ sysfs_config->ctrl |= ETMCR_CYC_ACC;
else
- config->ctrl &= ~ETMCR_CYC_ACC;
+ sysfs_config->ctrl &= ~ETMCR_CYC_ACC;
- if (config->mode & ETM_MODE_STALL) {
- if (!(drvdata->etmccr & ETMCCR_FIFOFULL)) {
+ if (sysfs_config->mode & ETM_MODE_STALL) {
+ if (!caps->fifofull) {
dev_warn(dev, "stall mode not supported\n");
ret = -EINVAL;
goto err_unlock;
}
- config->ctrl |= ETMCR_STALL_MODE;
+ sysfs_config->ctrl |= ETMCR_STALL_MODE;
} else
- config->ctrl &= ~ETMCR_STALL_MODE;
+ sysfs_config->ctrl &= ~ETMCR_STALL_MODE;
- if (config->mode & ETM_MODE_TIMESTAMP) {
- if (!(drvdata->etmccer & ETMCCER_TIMESTAMP)) {
+ if (sysfs_config->mode & ETM_MODE_TIMESTAMP) {
+ if (!caps->timestamp) {
dev_warn(dev, "timestamp not supported\n");
ret = -EINVAL;
goto err_unlock;
}
- config->ctrl |= ETMCR_TIMESTAMP_EN;
+ sysfs_config->ctrl |= ETMCR_TIMESTAMP_EN;
} else
- config->ctrl &= ~ETMCR_TIMESTAMP_EN;
+ sysfs_config->ctrl &= ~ETMCR_TIMESTAMP_EN;
- if (config->mode & ETM_MODE_CTXID)
- config->ctrl |= ETMCR_CTXID_SIZE;
+ if (sysfs_config->mode & ETM_MODE_CTXID)
+ sysfs_config->ctrl |= ETMCR_CTXID_SIZE;
else
- config->ctrl &= ~ETMCR_CTXID_SIZE;
+ sysfs_config->ctrl &= ~ETMCR_CTXID_SIZE;
- if (config->mode & ETM_MODE_BBROAD)
- config->ctrl |= ETMCR_BRANCH_BROADCAST;
+ if (sysfs_config->mode & ETM_MODE_BBROAD)
+ sysfs_config->ctrl |= ETMCR_BRANCH_BROADCAST;
else
- config->ctrl &= ~ETMCR_BRANCH_BROADCAST;
+ sysfs_config->ctrl &= ~ETMCR_BRANCH_BROADCAST;
- if (config->mode & ETM_MODE_RET_STACK)
- config->ctrl |= ETMCR_RETURN_STACK;
+ if (sysfs_config->mode & ETM_MODE_RET_STACK)
+ sysfs_config->ctrl |= ETMCR_RETURN_STACK;
else
- config->ctrl &= ~ETMCR_RETURN_STACK;
+ sysfs_config->ctrl &= ~ETMCR_RETURN_STACK;
- if (config->mode & (ETM_MODE_EXCL_KERN | ETM_MODE_EXCL_USER))
- etm_config_trace_mode(config);
+ if (sysfs_config->mode & (ETM_MODE_EXCL_KERN | ETM_MODE_EXCL_USER))
+ etm_config_trace_mode(sysfs_config);
spin_unlock(&drvdata->spinlock);
@@ -183,9 +223,9 @@ static ssize_t trigger_event_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
- val = config->trigger_event;
+ val = sysfs_config->trigger_event;
return sprintf(buf, "%#lx\n", val);
}
@@ -196,13 +236,13 @@ static ssize_t trigger_event_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
- config->trigger_event = val & ETM_EVENT_MASK;
+ sysfs_config->trigger_event = val & ETM_EVENT_MASK;
return size;
}
@@ -213,9 +253,9 @@ static ssize_t enable_event_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
- val = config->enable_event;
+ val = sysfs_config->enable_event;
return sprintf(buf, "%#lx\n", val);
}
@@ -226,13 +266,13 @@ static ssize_t enable_event_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
- config->enable_event = val & ETM_EVENT_MASK;
+ sysfs_config->enable_event = val & ETM_EVENT_MASK;
return size;
}
@@ -243,9 +283,9 @@ static ssize_t fifofull_level_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
- val = config->fifofull_level;
+ val = sysfs_config->fifofull_level;
return sprintf(buf, "%#lx\n", val);
}
@@ -256,13 +296,13 @@ static ssize_t fifofull_level_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
- config->fifofull_level = val;
+ sysfs_config->fifofull_level = val;
return size;
}
@@ -273,9 +313,9 @@ static ssize_t addr_idx_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
- val = config->addr_idx;
+ val = sysfs_config->addr_idx;
return sprintf(buf, "%#lx\n", val);
}
@@ -286,13 +326,14 @@ static ssize_t addr_idx_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ const struct etm_caps *caps = &drvdata->caps;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
- if (val >= drvdata->nr_addr_cmp)
+ if (val >= caps->nr_addr_cmp)
return -EINVAL;
/*
@@ -300,7 +341,7 @@ static ssize_t addr_idx_store(struct device *dev,
* dereferenced multiple times within a spinlock block elsewhere.
*/
spin_lock(&drvdata->spinlock);
- config->addr_idx = val;
+ sysfs_config->addr_idx = val;
spin_unlock(&drvdata->spinlock);
return size;
@@ -313,17 +354,17 @@ static ssize_t addr_single_show(struct device *dev,
u8 idx;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
- if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
- config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) {
+ idx = sysfs_config->addr_idx;
+ if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) {
spin_unlock(&drvdata->spinlock);
return -EINVAL;
}
- val = config->addr_val[idx];
+ val = sysfs_config->addr_val[idx];
spin_unlock(&drvdata->spinlock);
return sprintf(buf, "%#lx\n", val);
@@ -337,22 +378,22 @@ static ssize_t addr_single_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
- if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
- config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) {
+ idx = sysfs_config->addr_idx;
+ if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) {
spin_unlock(&drvdata->spinlock);
return -EINVAL;
}
- config->addr_val[idx] = val;
- config->addr_type[idx] = ETM_ADDR_TYPE_SINGLE;
+ sysfs_config->addr_val[idx] = val;
+ sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_SINGLE;
spin_unlock(&drvdata->spinlock);
return size;
@@ -365,24 +406,24 @@ static ssize_t addr_range_show(struct device *dev,
u8 idx;
unsigned long val1, val2;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
+ idx = sysfs_config->addr_idx;
if (idx % 2 != 0) {
spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- if (!((config->addr_type[idx] == ETM_ADDR_TYPE_NONE &&
- config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) ||
- (config->addr_type[idx] == ETM_ADDR_TYPE_RANGE &&
- config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) {
+ if (!((sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE &&
+ sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) ||
+ (sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_RANGE &&
+ sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) {
spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- val1 = config->addr_val[idx];
- val2 = config->addr_val[idx + 1];
+ val1 = sysfs_config->addr_val[idx];
+ val2 = sysfs_config->addr_val[idx + 1];
spin_unlock(&drvdata->spinlock);
return sprintf(buf, "%#lx %#lx\n", val1, val2);
@@ -395,7 +436,7 @@ static ssize_t addr_range_store(struct device *dev,
u8 idx;
unsigned long val1, val2;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
if (sscanf(buf, "%lx %lx", &val1, &val2) != 2)
return -EINVAL;
@@ -404,24 +445,24 @@ static ssize_t addr_range_store(struct device *dev,
return -EINVAL;
spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
+ idx = sysfs_config->addr_idx;
if (idx % 2 != 0) {
spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- if (!((config->addr_type[idx] == ETM_ADDR_TYPE_NONE &&
- config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) ||
- (config->addr_type[idx] == ETM_ADDR_TYPE_RANGE &&
- config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) {
+ if (!((sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE &&
+ sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) ||
+ (sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_RANGE &&
+ sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) {
spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- config->addr_val[idx] = val1;
- config->addr_type[idx] = ETM_ADDR_TYPE_RANGE;
- config->addr_val[idx + 1] = val2;
- config->addr_type[idx + 1] = ETM_ADDR_TYPE_RANGE;
- config->enable_ctrl1 |= (1 << (idx/2));
+ sysfs_config->addr_val[idx] = val1;
+ sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_RANGE;
+ sysfs_config->addr_val[idx + 1] = val2;
+ sysfs_config->addr_type[idx + 1] = ETM_ADDR_TYPE_RANGE;
+ sysfs_config->enable_ctrl1 |= (1 << (idx/2));
spin_unlock(&drvdata->spinlock);
return size;
@@ -434,17 +475,17 @@ static ssize_t addr_start_show(struct device *dev,
u8 idx;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
- if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
- config->addr_type[idx] == ETM_ADDR_TYPE_START)) {
+ idx = sysfs_config->addr_idx;
+ if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_START)) {
spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- val = config->addr_val[idx];
+ val = sysfs_config->addr_val[idx];
spin_unlock(&drvdata->spinlock);
return sprintf(buf, "%#lx\n", val);
@@ -458,24 +499,24 @@ static ssize_t addr_start_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
- if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
- config->addr_type[idx] == ETM_ADDR_TYPE_START)) {
+ idx = sysfs_config->addr_idx;
+ if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_START)) {
spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- config->addr_val[idx] = val;
- config->addr_type[idx] = ETM_ADDR_TYPE_START;
- config->startstop_ctrl |= (1 << idx);
- config->enable_ctrl1 |= ETMTECR1_START_STOP;
+ sysfs_config->addr_val[idx] = val;
+ sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_START;
+ sysfs_config->startstop_ctrl |= (1 << idx);
+ sysfs_config->enable_ctrl1 |= ETMTECR1_START_STOP;
spin_unlock(&drvdata->spinlock);
return size;
@@ -488,17 +529,17 @@ static ssize_t addr_stop_show(struct device *dev,
u8 idx;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
- if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
- config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) {
+ idx = sysfs_config->addr_idx;
+ if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) {
spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- val = config->addr_val[idx];
+ val = sysfs_config->addr_val[idx];
spin_unlock(&drvdata->spinlock);
return sprintf(buf, "%#lx\n", val);
@@ -512,24 +553,24 @@ static ssize_t addr_stop_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
- if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
- config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) {
+ idx = sysfs_config->addr_idx;
+ if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) {
spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- config->addr_val[idx] = val;
- config->addr_type[idx] = ETM_ADDR_TYPE_STOP;
- config->startstop_ctrl |= (1 << (idx + 16));
- config->enable_ctrl1 |= ETMTECR1_START_STOP;
+ sysfs_config->addr_val[idx] = val;
+ sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_STOP;
+ sysfs_config->startstop_ctrl |= (1 << (idx + 16));
+ sysfs_config->enable_ctrl1 |= ETMTECR1_START_STOP;
spin_unlock(&drvdata->spinlock);
return size;
@@ -541,10 +582,10 @@ static ssize_t addr_acctype_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
spin_lock(&drvdata->spinlock);
- val = config->addr_acctype[config->addr_idx];
+ val = sysfs_config->addr_acctype[sysfs_config->addr_idx];
spin_unlock(&drvdata->spinlock);
return sprintf(buf, "%#lx\n", val);
@@ -557,14 +598,14 @@ static ssize_t addr_acctype_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
spin_lock(&drvdata->spinlock);
- config->addr_acctype[config->addr_idx] = val;
+ sysfs_config->addr_acctype[sysfs_config->addr_idx] = val;
spin_unlock(&drvdata->spinlock);
return size;
@@ -576,9 +617,9 @@ static ssize_t cntr_idx_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
- val = config->cntr_idx;
+ val = sysfs_config->cntr_idx;
return sprintf(buf, "%#lx\n", val);
}
@@ -589,20 +630,21 @@ static ssize_t cntr_idx_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ const struct etm_caps *caps = &drvdata->caps;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
- if (val >= drvdata->nr_cntr)
+ if (val >= caps->nr_cntr)
return -EINVAL;
/*
* Use spinlock to ensure index doesn't change while it gets
* dereferenced multiple times within a spinlock block elsewhere.
*/
spin_lock(&drvdata->spinlock);
- config->cntr_idx = val;
+ sysfs_config->cntr_idx = val;
spin_unlock(&drvdata->spinlock);
return size;
@@ -614,10 +656,10 @@ static ssize_t cntr_rld_val_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
spin_lock(&drvdata->spinlock);
- val = config->cntr_rld_val[config->cntr_idx];
+ val = sysfs_config->cntr_rld_val[sysfs_config->cntr_idx];
spin_unlock(&drvdata->spinlock);
return sprintf(buf, "%#lx\n", val);
@@ -630,14 +672,14 @@ static ssize_t cntr_rld_val_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
spin_lock(&drvdata->spinlock);
- config->cntr_rld_val[config->cntr_idx] = val;
+ sysfs_config->cntr_rld_val[sysfs_config->cntr_idx] = val;
spin_unlock(&drvdata->spinlock);
return size;
@@ -649,10 +691,10 @@ static ssize_t cntr_event_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
spin_lock(&drvdata->spinlock);
- val = config->cntr_event[config->cntr_idx];
+ val = sysfs_config->cntr_event[sysfs_config->cntr_idx];
spin_unlock(&drvdata->spinlock);
return sprintf(buf, "%#lx\n", val);
@@ -665,14 +707,14 @@ static ssize_t cntr_event_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
spin_lock(&drvdata->spinlock);
- config->cntr_event[config->cntr_idx] = val & ETM_EVENT_MASK;
+ sysfs_config->cntr_event[sysfs_config->cntr_idx] = val & ETM_EVENT_MASK;
spin_unlock(&drvdata->spinlock);
return size;
@@ -684,10 +726,10 @@ static ssize_t cntr_rld_event_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
spin_lock(&drvdata->spinlock);
- val = config->cntr_rld_event[config->cntr_idx];
+ val = sysfs_config->cntr_rld_event[sysfs_config->cntr_idx];
spin_unlock(&drvdata->spinlock);
return sprintf(buf, "%#lx\n", val);
@@ -700,14 +742,14 @@ static ssize_t cntr_rld_event_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
spin_lock(&drvdata->spinlock);
- config->cntr_rld_event[config->cntr_idx] = val & ETM_EVENT_MASK;
+ sysfs_config->cntr_rld_event[sysfs_config->cntr_idx] = val & ETM_EVENT_MASK;
spin_unlock(&drvdata->spinlock);
return size;
@@ -717,16 +759,21 @@ static DEVICE_ATTR_RW(cntr_rld_event);
static ssize_t cntr_val_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
+ int ret;
u32 val;
+ unsigned long val2;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
if (!coresight_get_mode(drvdata->csdev)) {
spin_lock(&drvdata->spinlock);
- val = config->cntr_val[config->cntr_idx];
+ val = sysfs_config->cntr_val[sysfs_config->cntr_idx];
spin_unlock(&drvdata->spinlock);
} else {
- val = etm_readl(drvdata, ETMCNTVRn(config->cntr_idx));
+ ret = etm_readl_cslocked(drvdata, ETMCNTVRn(sysfs_config->cntr_idx), &val2);
+ if (ret)
+ return ret;
+ val = val2;
}
return sysfs_emit(buf, "%#x\n", val);
@@ -739,14 +786,17 @@ static ssize_t cntr_val_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
+ if (IS_ERR_OR_NULL(drvdata->csdev) ||
+ coresight_get_mode(drvdata->csdev) == CS_MODE_SYSFS)
+ return -EBUSY;
spin_lock(&drvdata->spinlock);
- config->cntr_val[config->cntr_idx] = val;
+ sysfs_config->cntr_val[sysfs_config->cntr_idx] = val;
spin_unlock(&drvdata->spinlock);
return size;
@@ -758,9 +808,9 @@ static ssize_t seq_12_event_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
- val = config->seq_12_event;
+ val = sysfs_config->seq_12_event;
return sprintf(buf, "%#lx\n", val);
}
@@ -771,13 +821,13 @@ static ssize_t seq_12_event_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
- config->seq_12_event = val & ETM_EVENT_MASK;
+ sysfs_config->seq_12_event = val & ETM_EVENT_MASK;
return size;
}
static DEVICE_ATTR_RW(seq_12_event);
@@ -787,9 +837,9 @@ static ssize_t seq_21_event_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
- val = config->seq_21_event;
+ val = sysfs_config->seq_21_event;
return sprintf(buf, "%#lx\n", val);
}
@@ -800,13 +850,13 @@ static ssize_t seq_21_event_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
- config->seq_21_event = val & ETM_EVENT_MASK;
+ sysfs_config->seq_21_event = val & ETM_EVENT_MASK;
return size;
}
static DEVICE_ATTR_RW(seq_21_event);
@@ -816,9 +866,9 @@ static ssize_t seq_23_event_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
- val = config->seq_23_event;
+ val = sysfs_config->seq_23_event;
return sprintf(buf, "%#lx\n", val);
}
@@ -829,13 +879,13 @@ static ssize_t seq_23_event_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
- config->seq_23_event = val & ETM_EVENT_MASK;
+ sysfs_config->seq_23_event = val & ETM_EVENT_MASK;
return size;
}
static DEVICE_ATTR_RW(seq_23_event);
@@ -845,9 +895,9 @@ static ssize_t seq_31_event_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
- val = config->seq_31_event;
+ val = sysfs_config->seq_31_event;
return sprintf(buf, "%#lx\n", val);
}
@@ -858,13 +908,13 @@ static ssize_t seq_31_event_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
- config->seq_31_event = val & ETM_EVENT_MASK;
+ sysfs_config->seq_31_event = val & ETM_EVENT_MASK;
return size;
}
static DEVICE_ATTR_RW(seq_31_event);
@@ -874,9 +924,9 @@ static ssize_t seq_32_event_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
- val = config->seq_32_event;
+ val = sysfs_config->seq_32_event;
return sprintf(buf, "%#lx\n", val);
}
@@ -887,13 +937,13 @@ static ssize_t seq_32_event_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
- config->seq_32_event = val & ETM_EVENT_MASK;
+ sysfs_config->seq_32_event = val & ETM_EVENT_MASK;
return size;
}
static DEVICE_ATTR_RW(seq_32_event);
@@ -903,9 +953,9 @@ static ssize_t seq_13_event_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
- val = config->seq_13_event;
+ val = sysfs_config->seq_13_event;
return sprintf(buf, "%#lx\n", val);
}
@@ -916,13 +966,13 @@ static ssize_t seq_13_event_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
- config->seq_13_event = val & ETM_EVENT_MASK;
+ sysfs_config->seq_13_event = val & ETM_EVENT_MASK;
return size;
}
static DEVICE_ATTR_RW(seq_13_event);
@@ -930,24 +980,20 @@ static DEVICE_ATTR_RW(seq_13_event);
static ssize_t seq_curr_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- unsigned long val, flags;
+ int ret;
+ unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
if (!coresight_get_mode(drvdata->csdev)) {
- val = config->seq_curr_state;
+ val = sysfs_config->seq_curr_state;
goto out;
}
- pm_runtime_get_sync(dev->parent);
- spin_lock_irqsave(&drvdata->spinlock, flags);
-
- CS_UNLOCK(drvdata->csa.base);
- val = (etm_readl(drvdata, ETMSQR) & ETM_SQR_MASK);
- CS_LOCK(drvdata->csa.base);
-
- spin_unlock_irqrestore(&drvdata->spinlock, flags);
- pm_runtime_put(dev->parent);
+ ret = etm_readl_cslocked(drvdata, ETMSQR, &val);
+ if (ret)
+ return ret;
+ val &= ETM_SQR_MASK;
out:
return sprintf(buf, "%#lx\n", val);
}
@@ -959,7 +1005,7 @@ static ssize_t seq_curr_state_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
@@ -968,7 +1014,7 @@ static ssize_t seq_curr_state_store(struct device *dev,
if (val > ETM_SEQ_STATE_MAX_VAL)
return -EINVAL;
- config->seq_curr_state = val;
+ sysfs_config->seq_curr_state = val;
return size;
}
@@ -979,9 +1025,9 @@ static ssize_t ctxid_idx_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
- val = config->ctxid_idx;
+ val = sysfs_config->ctxid_idx;
return sprintf(buf, "%#lx\n", val);
}
@@ -992,13 +1038,14 @@ static ssize_t ctxid_idx_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ const struct etm_caps *caps = &drvdata->caps;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
- if (val >= drvdata->nr_ctxid_cmp)
+ if (val >= caps->nr_ctxid_cmp)
return -EINVAL;
/*
@@ -1006,7 +1053,7 @@ static ssize_t ctxid_idx_store(struct device *dev,
* dereferenced multiple times within a spinlock block elsewhere.
*/
spin_lock(&drvdata->spinlock);
- config->ctxid_idx = val;
+ sysfs_config->ctxid_idx = val;
spin_unlock(&drvdata->spinlock);
return size;
@@ -1018,7 +1065,7 @@ static ssize_t ctxid_pid_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
/*
* Don't use contextID tracing if coming from a PID namespace. See
@@ -1028,7 +1075,7 @@ static ssize_t ctxid_pid_show(struct device *dev,
return -EINVAL;
spin_lock(&drvdata->spinlock);
- val = config->ctxid_pid[config->ctxid_idx];
+ val = sysfs_config->ctxid_pid[sysfs_config->ctxid_idx];
spin_unlock(&drvdata->spinlock);
return sprintf(buf, "%#lx\n", val);
@@ -1041,7 +1088,7 @@ static ssize_t ctxid_pid_store(struct device *dev,
int ret;
unsigned long pid;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
/*
* When contextID tracing is enabled the tracers will insert the
@@ -1060,7 +1107,7 @@ static ssize_t ctxid_pid_store(struct device *dev,
return ret;
spin_lock(&drvdata->spinlock);
- config->ctxid_pid[config->ctxid_idx] = pid;
+ sysfs_config->ctxid_pid[sysfs_config->ctxid_idx] = pid;
spin_unlock(&drvdata->spinlock);
return size;
@@ -1072,7 +1119,7 @@ static ssize_t ctxid_mask_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
/*
* Don't use contextID tracing if coming from a PID namespace. See
@@ -1081,7 +1128,7 @@ static ssize_t ctxid_mask_show(struct device *dev,
if (task_active_pid_ns(current) != &init_pid_ns)
return -EINVAL;
- val = config->ctxid_mask;
+ val = sysfs_config->ctxid_mask;
return sprintf(buf, "%#lx\n", val);
}
@@ -1092,7 +1139,7 @@ static ssize_t ctxid_mask_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
/*
* Don't use contextID tracing if coming from a PID namespace. See
@@ -1105,7 +1152,7 @@ static ssize_t ctxid_mask_store(struct device *dev,
if (ret)
return ret;
- config->ctxid_mask = val;
+ sysfs_config->ctxid_mask = val;
return size;
}
static DEVICE_ATTR_RW(ctxid_mask);
@@ -1115,9 +1162,9 @@ static ssize_t sync_freq_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
- val = config->sync_freq;
+ val = sysfs_config->sync_freq;
return sprintf(buf, "%#lx\n", val);
}
@@ -1128,13 +1175,13 @@ static ssize_t sync_freq_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
- config->sync_freq = val & ETM_SYNC_MASK;
+ sysfs_config->sync_freq = val & ETM_SYNC_MASK;
return size;
}
static DEVICE_ATTR_RW(sync_freq);
@@ -1144,9 +1191,9 @@ static ssize_t timestamp_event_show(struct device *dev,
{
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
- val = config->timestamp_event;
+ val = sysfs_config->timestamp_event;
return sprintf(buf, "%#lx\n", val);
}
@@ -1157,13 +1204,13 @@ static ssize_t timestamp_event_store(struct device *dev,
int ret;
unsigned long val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etm_config *config = &drvdata->config;
+ struct etm_config *sysfs_config = &drvdata->sysfs_config;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
- config->timestamp_event = val & ETM_EVENT_MASK;
+ sysfs_config->timestamp_event = val & ETM_EVENT_MASK;
return size;
}
static DEVICE_ATTR_RW(timestamp_event);
diff --git a/drivers/hwtracing/coresight/coresight-etm4x-cfg.c b/drivers/hwtracing/coresight/coresight-etm4x-cfg.c
index e1a59b434505..52cb036bb04b 100644
--- a/drivers/hwtracing/coresight/coresight-etm4x-cfg.c
+++ b/drivers/hwtracing/coresight/coresight-etm4x-cfg.c
@@ -47,7 +47,7 @@ static int etm4_cfg_map_reg_offset(struct etmv4_drvdata *drvdata,
struct cscfg_regval_csdev *reg_csdev, u32 offset)
{
int err = -EINVAL, idx;
- struct etmv4_config *drvcfg = &drvdata->config;
+ struct etmv4_config *drvcfg = &drvdata->curr_config;
u32 off_mask;
if (((offset >= TRCEVENTCTL0R) && (offset <= TRCVIPCSSCTLR)) ||
@@ -86,7 +86,6 @@ static int etm4_cfg_map_reg_offset(struct etmv4_drvdata *drvdata,
off_mask = (offset & GENMASK(11, 5));
do {
CHECKREGIDX(TRCSSCCRn(0), ss_ctrl, idx, off_mask);
- CHECKREGIDX(TRCSSCSRn(0), ss_status, idx, off_mask);
CHECKREGIDX(TRCSSPCICRn(0), ss_pe_cmp, idx, off_mask);
} while (0);
} else if ((offset >= TRCCIDCVRn(0)) && (offset <= TRCVMIDCVRn(7))) {
@@ -154,11 +153,14 @@ static int etm4_cfg_load_feature(struct coresight_device *csdev,
int i = 0, err = 0;
/*
- * essential we set the device spinlock - this is used in the generic
- * programming routines when copying values into the drvdata structures
- * via the pointers setup in etm4_cfg_map_reg_offset().
+ * drvdata structure of etm4 for configfs is curr_config and
+ * this curr_config is protected with csdev->mode by restricting
+ * access to curr_config after mode changed to PERF or SYSFS.
+ *
+ * Therefore, it doesn't need to protected with etmv4_drvdata->spinlock
+ * to feature applying and set the NULL.
*/
- feat_csdev->drv_spinlock = &drvdata->spinlock;
+ feat_csdev->drv_spinlock = NULL;
/* process the register descriptions */
for (i = 0; i < feat_csdev->nr_regs && !err; i++) {
diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c
index 2247ad55d444..e21be6468e95 100644
--- a/drivers/hwtracing/coresight/coresight-etm4x-core.c
+++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c
@@ -66,10 +66,12 @@ MODULE_PARM_DESC(pm_save_enable,
"Save/restore state on power down: 1 = never, 2 = self-hosted. MMIO and DT only.");
static struct etmv4_drvdata *etmdrvdata[NR_CPUS];
-static void etm4_set_default_config(struct etmv4_config *config);
+static void etm4_set_default_config(struct etmv4_config *config,
+ const struct etmv4_caps *caps);
static int etm4_set_event_filters(struct etmv4_drvdata *drvdata,
struct perf_event *event);
-static u64 etm4_get_access_type(struct etmv4_config *config);
+static u64 etm4_get_access_type(struct etmv4_config *config,
+ const struct etmv4_caps *caps);
static enum cpuhp_state hp_online;
@@ -92,9 +94,10 @@ static int etm4_probe_cpu(unsigned int cpu);
*/
static bool etm4x_sspcicrn_present(struct etmv4_drvdata *drvdata, int n)
{
- return (n < drvdata->nr_ss_cmp) &&
- drvdata->nr_pe_cmp &&
- (drvdata->config.ss_status[n] & TRCSSCSRn_PC);
+ const struct etmv4_caps *caps = &drvdata->caps;
+
+ return (n < caps->nr_ss_cmp) && caps->nr_pe_cmp &&
+ (drvdata->ss_status[n] & TRCSSCSRn_PC);
}
u64 etm4x_sysreg_read(u32 offset, bool _relaxed, bool _64bit)
@@ -164,17 +167,20 @@ static void ete_sysreg_write(u64 val, u32 offset, bool _relaxed, bool _64bit)
static void etm_detect_os_lock(struct etmv4_drvdata *drvdata,
struct csdev_access *csa)
{
+ struct etmv4_caps *caps = &drvdata->caps;
u32 oslsr = etm4x_relaxed_read32(csa, TRCOSLSR);
- drvdata->os_lock_model = ETM_OSLSR_OSLM(oslsr);
+ caps->os_lock_model = ETM_OSLSR_OSLM(oslsr);
}
static void etm_write_os_lock(struct etmv4_drvdata *drvdata,
struct csdev_access *csa, u32 val)
{
+ const struct etmv4_caps *caps = &drvdata->caps;
+
val = !!val;
- switch (drvdata->os_lock_model) {
+ switch (caps->os_lock_model) {
case ETM_OSLOCK_PRESENT:
etm4x_relaxed_write32(csa, val, TRCOSLAR);
break;
@@ -183,7 +189,7 @@ static void etm_write_os_lock(struct etmv4_drvdata *drvdata,
break;
default:
pr_warn_once("CPU%d: Unsupported Trace OSLock model: %x\n",
- smp_processor_id(), drvdata->os_lock_model);
+ smp_processor_id(), caps->os_lock_model);
fallthrough;
case ETM_OSLOCK_NI:
return;
@@ -264,10 +270,11 @@ static void etm4x_prohibit_trace(struct etmv4_drvdata *drvdata)
static u64 etm4x_get_kern_user_filter(struct etmv4_drvdata *drvdata)
{
u64 trfcr = drvdata->trfcr;
+ struct etmv4_config *curr_config = &drvdata->curr_config;
- if (drvdata->config.mode & ETM_MODE_EXCL_KERN)
+ if (curr_config->mode & ETM_MODE_EXCL_KERN)
trfcr &= ~TRFCR_EL1_ExTRE;
- if (drvdata->config.mode & ETM_MODE_EXCL_USER)
+ if (curr_config->mode & ETM_MODE_EXCL_USER)
trfcr &= ~TRFCR_EL1_E0TRE;
return trfcr;
@@ -275,7 +282,7 @@ static u64 etm4x_get_kern_user_filter(struct etmv4_drvdata *drvdata)
/*
* etm4x_allow_trace - Allow CPU tracing in the respective ELs,
- * as configured by the drvdata->config.mode for the current
+ * as configured by the drvdata->curr_config.mode for the current
* session. Even though we have TRCVICTLR bits to filter the
* trace in the ELs, it doesn't prevent the ETM from generating
* a packet (e.g, TraceInfo) that might contain the addresses from
@@ -286,12 +293,13 @@ static u64 etm4x_get_kern_user_filter(struct etmv4_drvdata *drvdata)
static void etm4x_allow_trace(struct etmv4_drvdata *drvdata)
{
u64 trfcr, guest_trfcr;
+ struct etmv4_config *curr_config = &drvdata->curr_config;
/* If the CPU doesn't support FEAT_TRF, nothing to do */
if (!drvdata->trfcr)
return;
- if (drvdata->config.mode & ETM_MODE_EXCL_HOST)
+ if (curr_config->mode & ETM_MODE_EXCL_HOST)
trfcr = drvdata->trfcr & ~(TRFCR_EL1_ExTRE | TRFCR_EL1_E0TRE);
else
trfcr = etm4x_get_kern_user_filter(drvdata);
@@ -299,7 +307,7 @@ static void etm4x_allow_trace(struct etmv4_drvdata *drvdata)
write_trfcr(trfcr);
/* Set filters for guests and pass to KVM */
- if (drvdata->config.mode & ETM_MODE_EXCL_GUEST)
+ if (curr_config->mode & ETM_MODE_EXCL_GUEST)
guest_trfcr = drvdata->trfcr & ~(TRFCR_EL1_ExTRE | TRFCR_EL1_E0TRE);
else
guest_trfcr = etm4x_get_kern_user_filter(drvdata);
@@ -492,7 +500,8 @@ static int etm4_enable_trace_unit(struct etmv4_drvdata *drvdata)
static int etm4_enable_hw(struct etmv4_drvdata *drvdata)
{
int i, rc;
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *curr_config = &drvdata->curr_config;
struct coresight_device *csdev = drvdata->csdev;
struct device *etm_dev = &csdev->dev;
struct csdev_access *csa = &csdev->access;
@@ -523,73 +532,73 @@ static int etm4_enable_hw(struct etmv4_drvdata *drvdata)
if (etm4x_wait_status(csa, TRCSTATR_IDLE_BIT, 1))
dev_err(etm_dev,
"timeout while waiting for Idle Trace Status\n");
- if (drvdata->nr_pe)
- etm4x_relaxed_write32(csa, config->pe_sel, TRCPROCSELR);
- etm4x_relaxed_write32(csa, config->cfg, TRCCONFIGR);
+ if (caps->nr_pe)
+ etm4x_relaxed_write32(csa, curr_config->pe_sel, TRCPROCSELR);
+ etm4x_relaxed_write32(csa, curr_config->cfg, TRCCONFIGR);
/* nothing specific implemented */
etm4x_relaxed_write32(csa, 0x0, TRCAUXCTLR);
- etm4x_relaxed_write32(csa, config->eventctrl0, TRCEVENTCTL0R);
- etm4x_relaxed_write32(csa, config->eventctrl1, TRCEVENTCTL1R);
- if (drvdata->stallctl)
- etm4x_relaxed_write32(csa, config->stall_ctrl, TRCSTALLCTLR);
- etm4x_relaxed_write32(csa, config->ts_ctrl, TRCTSCTLR);
- etm4x_relaxed_write32(csa, config->syncfreq, TRCSYNCPR);
- etm4x_relaxed_write32(csa, config->ccctlr, TRCCCCTLR);
- etm4x_relaxed_write32(csa, config->bb_ctrl, TRCBBCTLR);
+ etm4x_relaxed_write32(csa, curr_config->eventctrl0, TRCEVENTCTL0R);
+ etm4x_relaxed_write32(csa, curr_config->eventctrl1, TRCEVENTCTL1R);
+ if (caps->stallctl)
+ etm4x_relaxed_write32(csa, curr_config->stall_ctrl, TRCSTALLCTLR);
+ etm4x_relaxed_write32(csa, curr_config->ts_ctrl, TRCTSCTLR);
+ etm4x_relaxed_write32(csa, curr_config->syncfreq, TRCSYNCPR);
+ etm4x_relaxed_write32(csa, curr_config->ccctlr, TRCCCCTLR);
+ etm4x_relaxed_write32(csa, curr_config->bb_ctrl, TRCBBCTLR);
etm4x_relaxed_write32(csa, drvdata->trcid, TRCTRACEIDR);
- etm4x_relaxed_write32(csa, config->vinst_ctrl, TRCVICTLR);
- etm4x_relaxed_write32(csa, config->viiectlr, TRCVIIECTLR);
- etm4x_relaxed_write32(csa, config->vissctlr, TRCVISSCTLR);
- if (drvdata->nr_pe_cmp)
- etm4x_relaxed_write32(csa, config->vipcssctlr, TRCVIPCSSCTLR);
-
- for (i = 0; i < drvdata->nr_seq_ctrls; i++)
- etm4x_relaxed_write32(csa, config->seq_ctrl[i], TRCSEQEVRn(i));
- if (drvdata->nrseqstate) {
- etm4x_relaxed_write32(csa, config->seq_rst, TRCSEQRSTEVR);
- etm4x_relaxed_write32(csa, config->seq_state, TRCSEQSTR);
+ etm4x_relaxed_write32(csa, curr_config->vinst_ctrl, TRCVICTLR);
+ etm4x_relaxed_write32(csa, curr_config->viiectlr, TRCVIIECTLR);
+ etm4x_relaxed_write32(csa, curr_config->vissctlr, TRCVISSCTLR);
+ if (caps->nr_pe_cmp)
+ etm4x_relaxed_write32(csa, curr_config->vipcssctlr, TRCVIPCSSCTLR);
+
+ for (i = 0; i < caps->nr_seq_ctrls; i++)
+ etm4x_relaxed_write32(csa, curr_config->seq_ctrl[i], TRCSEQEVRn(i));
+ if (caps->nrseqstate) {
+ etm4x_relaxed_write32(csa, curr_config->seq_rst, TRCSEQRSTEVR);
+ etm4x_relaxed_write32(csa, curr_config->seq_state, TRCSEQSTR);
}
- if (drvdata->numextinsel)
- etm4x_relaxed_write32(csa, config->ext_inp, TRCEXTINSELR);
- for (i = 0; i < drvdata->nr_cntr; i++) {
- etm4x_relaxed_write32(csa, config->cntrldvr[i], TRCCNTRLDVRn(i));
- etm4x_relaxed_write32(csa, config->cntr_ctrl[i], TRCCNTCTLRn(i));
- etm4x_relaxed_write32(csa, config->cntr_val[i], TRCCNTVRn(i));
+ if (caps->numextinsel)
+ etm4x_relaxed_write32(csa, curr_config->ext_inp, TRCEXTINSELR);
+ for (i = 0; i < caps->nr_cntr; i++) {
+ etm4x_relaxed_write32(csa, curr_config->cntrldvr[i], TRCCNTRLDVRn(i));
+ etm4x_relaxed_write32(csa, curr_config->cntr_ctrl[i], TRCCNTCTLRn(i));
+ etm4x_relaxed_write32(csa, curr_config->cntr_val[i], TRCCNTVRn(i));
}
/*
* Resource selector pair 0 is always implemented and reserved. As
* such start at 2.
*/
- for (i = 2; i < drvdata->nr_resource * 2; i++)
- etm4x_relaxed_write32(csa, config->res_ctrl[i], TRCRSCTLRn(i));
-
- for (i = 0; i < drvdata->nr_ss_cmp; i++) {
- /* always clear status bit on restart if using single-shot */
- if (config->ss_ctrl[i] || config->ss_pe_cmp[i])
- config->ss_status[i] &= ~TRCSSCSRn_STATUS;
- etm4x_relaxed_write32(csa, config->ss_ctrl[i], TRCSSCCRn(i));
- etm4x_relaxed_write32(csa, config->ss_status[i], TRCSSCSRn(i));
+ for (i = 2; i < caps->nr_resource * 2; i++)
+ etm4x_relaxed_write32(csa, curr_config->res_ctrl[i], TRCRSCTLRn(i));
+
+ for (i = 0; i < caps->nr_ss_cmp; i++) {
+ /* always clear status and pending bits on restart if using single-shot */
+ if (curr_config->ss_ctrl[i] || curr_config->ss_pe_cmp[i])
+ drvdata->ss_status[i] &= ~(TRCSSCSRn_STATUS | TRCSSCSRn_PENDING);
+ etm4x_relaxed_write32(csa, curr_config->ss_ctrl[i], TRCSSCCRn(i));
+ etm4x_relaxed_write32(csa, drvdata->ss_status[i], TRCSSCSRn(i));
if (etm4x_sspcicrn_present(drvdata, i))
- etm4x_relaxed_write32(csa, config->ss_pe_cmp[i], TRCSSPCICRn(i));
+ etm4x_relaxed_write32(csa, curr_config->ss_pe_cmp[i], TRCSSPCICRn(i));
}
- for (i = 0; i < drvdata->nr_addr_cmp * 2; i++) {
- etm4x_relaxed_write64(csa, config->addr_val[i], TRCACVRn(i));
- etm4x_relaxed_write64(csa, config->addr_acc[i], TRCACATRn(i));
+ for (i = 0; i < caps->nr_addr_cmp * 2; i++) {
+ etm4x_relaxed_write64(csa, curr_config->addr_val[i], TRCACVRn(i));
+ etm4x_relaxed_write64(csa, curr_config->addr_acc[i], TRCACATRn(i));
}
- for (i = 0; i < drvdata->numcidc; i++)
- etm4x_relaxed_write64(csa, config->ctxid_pid[i], TRCCIDCVRn(i));
- etm4x_relaxed_write32(csa, config->ctxid_mask0, TRCCIDCCTLR0);
- if (drvdata->numcidc > 4)
- etm4x_relaxed_write32(csa, config->ctxid_mask1, TRCCIDCCTLR1);
-
- for (i = 0; i < drvdata->numvmidc; i++)
- etm4x_relaxed_write64(csa, config->vmid_val[i], TRCVMIDCVRn(i));
- etm4x_relaxed_write32(csa, config->vmid_mask0, TRCVMIDCCTLR0);
- if (drvdata->numvmidc > 4)
- etm4x_relaxed_write32(csa, config->vmid_mask1, TRCVMIDCCTLR1);
-
- if (!drvdata->skip_power_up) {
+ for (i = 0; i < caps->numcidc; i++)
+ etm4x_relaxed_write64(csa, curr_config->ctxid_pid[i], TRCCIDCVRn(i));
+ etm4x_relaxed_write32(csa, curr_config->ctxid_mask0, TRCCIDCCTLR0);
+ if (caps->numcidc > 4)
+ etm4x_relaxed_write32(csa, curr_config->ctxid_mask1, TRCCIDCCTLR1);
+
+ for (i = 0; i < caps->numvmidc; i++)
+ etm4x_relaxed_write64(csa, curr_config->vmid_val[i], TRCVMIDCVRn(i));
+ etm4x_relaxed_write32(csa, curr_config->vmid_mask0, TRCVMIDCCTLR0);
+ if (caps->numvmidc > 4)
+ etm4x_relaxed_write32(csa, curr_config->vmid_mask1, TRCVMIDCCTLR1);
+
+ if (!caps->skip_power_up) {
u32 trcpdcr = etm4x_relaxed_read32(csa, TRCPDCR);
/*
@@ -612,27 +621,42 @@ done:
static void etm4_enable_sysfs_smp_call(void *info)
{
struct etm4_enable_arg *arg = info;
+ struct etmv4_drvdata *drvdata;
struct coresight_device *csdev;
+ unsigned long cfg_hash;
+ int preset;
if (WARN_ON(!arg))
return;
- csdev = arg->drvdata->csdev;
- if (!coresight_take_mode(csdev, CS_MODE_SYSFS)) {
- /* Someone is already using the tracer */
- arg->rc = -EBUSY;
- return;
+ drvdata = arg->drvdata;
+ csdev = drvdata->csdev;
+
+ /* enable any config activated by configfs */
+ cscfg_config_sysfs_get_active_cfg(&cfg_hash, &preset);
+ if (cfg_hash) {
+ arg->rc = cscfg_csdev_enable_active_config(csdev,
+ cfg_hash,
+ preset);
+ if (arg->rc)
+ return;
}
- arg->rc = etm4_enable_hw(arg->drvdata);
+ drvdata->trcid = arg->path->trace_id;
- /* The tracer didn't start */
+ /* Tracer will never be paused in sysfs mode */
+ drvdata->paused = false;
+
+ arg->rc = etm4_enable_hw(drvdata);
if (arg->rc) {
- coresight_set_mode(csdev, CS_MODE_DISABLED);
+ cscfg_csdev_disable_active_config(csdev);
return;
}
+ drvdata->sticky_enable = true;
csdev->path = arg->path;
+
+ return;
}
/*
@@ -669,19 +693,20 @@ static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata,
{
int ctridx;
int rselector;
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *curr_config = &drvdata->curr_config;
/* No point in trying if we don't have at least one counter */
- if (!drvdata->nr_cntr)
+ if (!caps->nr_cntr)
return -EINVAL;
/* Find a counter that hasn't been initialised */
- for (ctridx = 0; ctridx < drvdata->nr_cntr; ctridx++)
- if (config->cntr_val[ctridx] == 0)
+ for (ctridx = 0; ctridx < caps->nr_cntr; ctridx++)
+ if (curr_config->cntr_val[ctridx] == 0)
break;
/* All the counters have been configured already, bail out */
- if (ctridx == drvdata->nr_cntr) {
+ if (ctridx == caps->nr_cntr) {
pr_debug("%s: no available counter found\n", __func__);
return -ENOSPC;
}
@@ -697,18 +722,18 @@ static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata,
* ETMIDR4 gives the number of resource selector _pairs_, hence multiply
* by 2.
*/
- for (rselector = 2; rselector < drvdata->nr_resource * 2; rselector++)
- if (!config->res_ctrl[rselector])
+ for (rselector = 2; rselector < caps->nr_resource * 2; rselector++)
+ if (!curr_config->res_ctrl[rselector])
break;
- if (rselector == drvdata->nr_resource * 2) {
+ if (rselector == caps->nr_resource * 2) {
pr_debug("%s: no available resource selector found\n",
__func__);
return -ENOSPC;
}
/* Initialise original and reload counter value. */
- config->cntr_val[ctridx] = config->cntrldvr[ctridx] = 1 << (ts_level - 1);
+ curr_config->cntr_val[ctridx] = curr_config->cntrldvr[ctridx] = 1 << (ts_level - 1);
/*
* Trace Counter Control Register TRCCNTCTLRn
@@ -718,11 +743,11 @@ static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata,
* RLDEVENT = RES_SEL_FALSE (0), reload on single false resource (never reload)
* CNTEVENT = RES_SEL_TRUE (1), count single fixed 'always true' resource (always decrement)
*/
- config->cntr_ctrl[ctridx] = TRCCNTCTLRn_RLDSELF |
- FIELD_PREP(TRCCNTCTLRn_RLDEVENT_MASK,
- etm4_res_sel_single(ETM4_RES_SEL_FALSE)) |
- FIELD_PREP(TRCCNTCTLRn_CNTEVENT_MASK,
- etm4_res_sel_single(ETM4_RES_SEL_TRUE));
+ curr_config->cntr_ctrl[ctridx] = TRCCNTCTLRn_RLDSELF |
+ FIELD_PREP(TRCCNTCTLRn_RLDEVENT_MASK,
+ etm4_res_sel_single(ETM4_RES_SEL_FALSE)) |
+ FIELD_PREP(TRCCNTCTLRn_CNTEVENT_MASK,
+ etm4_res_sel_single(ETM4_RES_SEL_TRUE));
/*
* Resource Selection Control Register TRCRSCTLRn
@@ -733,16 +758,16 @@ static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata,
* Multiple counters can be selected, and each bit signifies a counter,
* so set bit 'ctridx' to select our counter.
*/
- config->res_ctrl[rselector] = FIELD_PREP(TRCRSCTLRn_GROUP_MASK, 2) |
- FIELD_PREP(TRCRSCTLRn_SELECT_MASK, 1 << ctridx);
+ curr_config->res_ctrl[rselector] = FIELD_PREP(TRCRSCTLRn_GROUP_MASK, 2) |
+ FIELD_PREP(TRCRSCTLRn_SELECT_MASK, 1 << ctridx);
/*
* Global Timestamp Control Register TRCTSCTLR
*
* EVENT = generate timestamp on single resource 'rselector'
*/
- config->ts_ctrl = FIELD_PREP(TRCTSCTLR_EVENT_MASK,
- etm4_res_sel_single(rselector));
+ curr_config->ts_ctrl = FIELD_PREP(TRCTSCTLR_EVENT_MASK,
+ etm4_res_sel_single(rselector));
return 0;
}
@@ -752,7 +777,8 @@ static int etm4_parse_event_config(struct coresight_device *csdev,
{
int ret = 0;
struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *curr_config = &drvdata->curr_config;
struct perf_event_attr max_timestamp = {
.ATTR_CFG_FLD_timestamp_CFG = U64_MAX,
};
@@ -761,22 +787,22 @@ static int etm4_parse_event_config(struct coresight_device *csdev,
u8 ts_level;
/* Clear configuration from previous run */
- memset(config, 0, sizeof(struct etmv4_config));
+ memset(curr_config, 0, sizeof(struct etmv4_config));
if (attr->exclude_kernel)
- config->mode = ETM_MODE_EXCL_KERN;
+ curr_config->mode = ETM_MODE_EXCL_KERN;
if (attr->exclude_user)
- config->mode = ETM_MODE_EXCL_USER;
+ curr_config->mode = ETM_MODE_EXCL_USER;
if (attr->exclude_host)
- config->mode |= ETM_MODE_EXCL_HOST;
+ curr_config->mode |= ETM_MODE_EXCL_HOST;
if (attr->exclude_guest)
- config->mode |= ETM_MODE_EXCL_GUEST;
+ curr_config->mode |= ETM_MODE_EXCL_GUEST;
/* Always start from the default config */
- etm4_set_default_config(config);
+ etm4_set_default_config(curr_config, caps);
/* Configure filters specified on the perf cmd line, if any. */
ret = etm4_set_event_filters(drvdata, event);
@@ -785,14 +811,14 @@ static int etm4_parse_event_config(struct coresight_device *csdev,
/* Go from generic option to ETMv4 specifics */
if (ATTR_CFG_GET_FLD(attr, cycacc)) {
- config->cfg |= TRCCONFIGR_CCI;
+ curr_config->cfg |= TRCCONFIGR_CCI;
/* TRM: Must program this for cycacc to work */
cc_threshold = ATTR_CFG_GET_FLD(attr, cc_threshold);
if (!cc_threshold)
cc_threshold = ETM_CYC_THRESHOLD_DEFAULT;
- if (cc_threshold < drvdata->ccitmin)
- cc_threshold = drvdata->ccitmin;
- config->ccctlr = cc_threshold;
+ if (cc_threshold < caps->ccitmin)
+ cc_threshold = caps->ccitmin;
+ curr_config->ccctlr = cc_threshold;
}
ts_level = max(ATTR_CFG_GET_FLD(attr, timestamp),
@@ -814,14 +840,14 @@ static int etm4_parse_event_config(struct coresight_device *csdev,
}
/* bit[11], Global timestamp tracing bit */
- config->cfg |= TRCCONFIGR_TS;
+ curr_config->cfg |= TRCCONFIGR_TS;
}
/* Only trace contextID when runs in root PID namespace */
if (ATTR_CFG_GET_FLD(attr, contextid1) &&
task_is_in_init_pid_ns(current))
/* bit[6], Context ID tracing bit */
- config->cfg |= TRCCONFIGR_CID;
+ curr_config->cfg |= TRCCONFIGR_CID;
/*
* If set bit contextid2 in perf config, this asks to trace VMID for
@@ -835,17 +861,17 @@ static int etm4_parse_event_config(struct coresight_device *csdev,
}
/* Only trace virtual contextID when runs in root PID namespace */
if (task_is_in_init_pid_ns(current))
- config->cfg |= TRCCONFIGR_VMID | TRCCONFIGR_VMIDOPT;
+ curr_config->cfg |= TRCCONFIGR_VMID | TRCCONFIGR_VMIDOPT;
}
/* return stack - enable if selected and supported */
- if (ATTR_CFG_GET_FLD(attr, retstack) && drvdata->retstack)
+ if (ATTR_CFG_GET_FLD(attr, retstack) && caps->retstack)
/* bit[12], Return stack enable bit */
- config->cfg |= TRCCONFIGR_RS;
+ curr_config->cfg |= TRCCONFIGR_RS;
/* branch broadcast - enable if selected and supported */
if (ATTR_CFG_GET_FLD(attr, branch_broadcast)) {
- if (!drvdata->trcbb) {
+ if (!caps->trcbb) {
/*
* Missing BB support could cause silent decode errors
* so fail to open if it's not supported.
@@ -853,7 +879,7 @@ static int etm4_parse_event_config(struct coresight_device *csdev,
ret = -EINVAL;
goto out;
} else {
- config->cfg |= TRCCONFIGR_BB;
+ curr_config->cfg |= TRCCONFIGR_BB;
}
}
@@ -919,46 +945,36 @@ static int etm4_enable_sysfs(struct coresight_device *csdev, struct coresight_pa
{
struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
struct etm4_enable_arg arg = { };
- unsigned long cfg_hash;
- int ret, preset;
+ int ret;
- /* enable any config activated by configfs */
- cscfg_config_sysfs_get_active_cfg(&cfg_hash, &preset);
- if (cfg_hash) {
- ret = cscfg_csdev_enable_active_config(csdev, cfg_hash, preset);
- if (ret) {
- etm4_release_trace_id(drvdata);
- return ret;
- }
+ if (!coresight_take_mode(csdev, CS_MODE_SYSFS)) {
+ /* Someone is already using the tracer */
+ return -EBUSY;
}
- raw_spin_lock(&drvdata->spinlock);
-
- drvdata->trcid = path->trace_id;
-
- /* Tracer will never be paused in sysfs mode */
- drvdata->paused = false;
-
/*
* Executing etm4_enable_hw on the cpu whose ETM is being enabled
* ensures that register writes occur when cpu is powered.
*/
arg.drvdata = drvdata;
arg.path = path;
+
+ scoped_guard(raw_spinlock, &drvdata->spinlock) {
+ drvdata->curr_config = drvdata->sysfs_config;
+ }
+
ret = smp_call_function_single(drvdata->cpu,
etm4_enable_sysfs_smp_call, &arg, 1);
if (!ret)
ret = arg.rc;
- if (!ret)
- drvdata->sticky_enable = true;
-
- if (ret)
+ if (!ret) {
+ dev_dbg(&csdev->dev, "ETM tracing enabled\n");
+ } else {
etm4_release_trace_id(drvdata);
+ /* The tracer didn't start */
+ coresight_set_mode(csdev, CS_MODE_DISABLED);
+ }
- raw_spin_unlock(&drvdata->spinlock);
-
- if (!ret)
- dev_dbg(&csdev->dev, "ETM tracing enabled\n");
return ret;
}
@@ -1044,7 +1060,8 @@ static void etm4_disable_trace_unit(struct etmv4_drvdata *drvdata)
static void etm4_disable_hw(struct etmv4_drvdata *drvdata)
{
u32 control;
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *curr_config = &drvdata->curr_config;
struct coresight_device *csdev = drvdata->csdev;
struct csdev_access *csa = &csdev->access;
int i;
@@ -1052,7 +1069,7 @@ static void etm4_disable_hw(struct etmv4_drvdata *drvdata)
etm4_cs_unlock(drvdata, csa);
etm4_disable_arch_specific(drvdata);
- if (!drvdata->skip_power_up) {
+ if (!caps->skip_power_up) {
/* power can be removed from the trace unit now */
control = etm4x_relaxed_read32(csa, TRCPDCR);
control &= ~TRCPDCR_PU;
@@ -1062,17 +1079,21 @@ static void etm4_disable_hw(struct etmv4_drvdata *drvdata)
etm4_disable_trace_unit(drvdata);
/* read the status of the single shot comparators */
- for (i = 0; i < drvdata->nr_ss_cmp; i++) {
- config->ss_status[i] =
+ for (i = 0; i < caps->nr_ss_cmp; i++) {
+ drvdata->ss_status[i] =
etm4x_relaxed_read32(csa, TRCSSCSRn(i));
}
/* read back the current counter values */
- for (i = 0; i < drvdata->nr_cntr; i++) {
- config->cntr_val[i] =
- etm4x_relaxed_read32(csa, TRCCNTVRn(i));
+ for (i = 0; i < caps->nr_cntr; i++) {
+ curr_config->cntr_val[i] =
+ etm4x_relaxed_read32(csa, TRCCNTVRn(i));
}
+ /* read back the sequence state */
+ if (caps->nrseqstate)
+ curr_config->seq_state = etm4x_relaxed_read32(csa, TRCSEQSTR);
+
coresight_disclaim_device_unlocked(csdev);
etm4_cs_lock(drvdata, csa);
@@ -1086,8 +1107,9 @@ static void etm4_disable_sysfs_smp_call(void *info)
etm4_disable_hw(drvdata);
+ cscfg_csdev_disable_active_config(drvdata->csdev);
+
drvdata->csdev->path = NULL;
- coresight_set_mode(drvdata->csdev, CS_MODE_DISABLED);
}
static int etm4_disable_perf(struct coresight_device *csdev,
@@ -1130,8 +1152,10 @@ static int etm4_disable_perf(struct coresight_device *csdev,
static void etm4_disable_sysfs(struct coresight_device *csdev)
{
struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
-
- raw_spin_lock(&drvdata->spinlock);
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
+ const struct etmv4_config *curr_config = &drvdata->curr_config;
+ int i;
/*
* Executing etm4_disable_hw on the cpu whose ETM is being disabled
@@ -1140,9 +1164,16 @@ static void etm4_disable_sysfs(struct coresight_device *csdev)
smp_call_function_single(drvdata->cpu, etm4_disable_sysfs_smp_call,
drvdata, 1);
- raw_spin_unlock(&drvdata->spinlock);
+ /*
+ * Userspace may read cntr_val through sysfs after
+ * the sysfs-session has been disabled.
+ */
+ for (i = 0; i < caps->nr_cntr; i++)
+ sysfs_config->cntr_val[i] = curr_config->cntr_val[i];
- cscfg_csdev_disable_active_config(csdev);
+ sysfs_config->seq_state = curr_config->seq_state;
+
+ coresight_set_mode(drvdata->csdev, CS_MODE_DISABLED);
/*
* we only release trace IDs when resetting sysfs.
@@ -1353,7 +1384,7 @@ static struct midr_range etm_wrong_ccitmin_cpus[] = {
{},
};
-static void etm4_fixup_wrong_ccitmin(struct etmv4_drvdata *drvdata)
+static void etm4_fixup_wrong_ccitmin(struct etmv4_caps *caps)
{
/*
* Erratum affected cpus will read 256 as the minimum
@@ -1363,8 +1394,8 @@ static void etm4_fixup_wrong_ccitmin(struct etmv4_drvdata *drvdata)
* this problem.
*/
if (is_midr_in_range_list(etm_wrong_ccitmin_cpus)) {
- if (drvdata->ccitmin == 256)
- drvdata->ccitmin = 4;
+ if (caps->ccitmin == 256)
+ caps->ccitmin = 4;
}
}
@@ -1377,11 +1408,13 @@ static void etm4_init_arch_data(void *info)
u32 etmidr5;
struct etm4_init_arg *init_arg = info;
struct etmv4_drvdata *drvdata;
+ struct etmv4_caps *caps;
struct csdev_access *csa;
struct device *dev = init_arg->dev;
int i;
drvdata = dev_get_drvdata(init_arg->dev);
+ caps = &drvdata->caps;
csa = init_arg->csa;
/*
@@ -1394,7 +1427,7 @@ static void etm4_init_arch_data(void *info)
if (!csa->io_mem ||
fwnode_property_present(dev_fwnode(dev), "qcom,skip-power-up"))
- drvdata->skip_power_up = true;
+ caps->skip_power_up = true;
/* Detect the support for OS Lock before we actually use it */
etm_detect_os_lock(drvdata, csa);
@@ -1409,71 +1442,70 @@ static void etm4_init_arch_data(void *info)
etmidr0 = etm4x_relaxed_read32(csa, TRCIDR0);
/* INSTP0, bits[2:1] P0 tracing support field */
- drvdata->instrp0 = !!(FIELD_GET(TRCIDR0_INSTP0_MASK, etmidr0) == 0b11);
+ caps->instrp0 = !!(FIELD_GET(TRCIDR0_INSTP0_MASK, etmidr0) == 0b11);
/* TRCBB, bit[5] Branch broadcast tracing support bit */
- drvdata->trcbb = !!(etmidr0 & TRCIDR0_TRCBB);
+ caps->trcbb = !!(etmidr0 & TRCIDR0_TRCBB);
/* TRCCOND, bit[6] Conditional instruction tracing support bit */
- drvdata->trccond = !!(etmidr0 & TRCIDR0_TRCCOND);
+ caps->trccond = !!(etmidr0 & TRCIDR0_TRCCOND);
/* TRCCCI, bit[7] Cycle counting instruction bit */
- drvdata->trccci = !!(etmidr0 & TRCIDR0_TRCCCI);
+ caps->trccci = !!(etmidr0 & TRCIDR0_TRCCCI);
/* RETSTACK, bit[9] Return stack bit */
- drvdata->retstack = !!(etmidr0 & TRCIDR0_RETSTACK);
+ caps->retstack = !!(etmidr0 & TRCIDR0_RETSTACK);
/* NUMEVENT, bits[11:10] Number of events field */
- drvdata->nr_event = FIELD_GET(TRCIDR0_NUMEVENT_MASK, etmidr0);
+ caps->nr_event = FIELD_GET(TRCIDR0_NUMEVENT_MASK, etmidr0);
/* QSUPP, bits[16:15] Q element support field */
- drvdata->q_support = FIELD_GET(TRCIDR0_QSUPP_MASK, etmidr0);
- if (drvdata->q_support)
- drvdata->q_filt = !!(etmidr0 & TRCIDR0_QFILT);
+ caps->q_support = FIELD_GET(TRCIDR0_QSUPP_MASK, etmidr0);
+ if (caps->q_support)
+ caps->q_filt = !!(etmidr0 & TRCIDR0_QFILT);
/* TSSIZE, bits[28:24] Global timestamp size field */
- drvdata->ts_size = FIELD_GET(TRCIDR0_TSSIZE_MASK, etmidr0);
+ caps->ts_size = FIELD_GET(TRCIDR0_TSSIZE_MASK, etmidr0);
/* maximum size of resources */
etmidr2 = etm4x_relaxed_read32(csa, TRCIDR2);
/* CIDSIZE, bits[9:5] Indicates the Context ID size */
- drvdata->ctxid_size = FIELD_GET(TRCIDR2_CIDSIZE_MASK, etmidr2);
+ caps->ctxid_size = FIELD_GET(TRCIDR2_CIDSIZE_MASK, etmidr2);
/* VMIDSIZE, bits[14:10] Indicates the VMID size */
- drvdata->vmid_size = FIELD_GET(TRCIDR2_VMIDSIZE_MASK, etmidr2);
+ caps->vmid_size = FIELD_GET(TRCIDR2_VMIDSIZE_MASK, etmidr2);
/* CCSIZE, bits[28:25] size of the cycle counter in bits minus 12 */
- drvdata->ccsize = FIELD_GET(TRCIDR2_CCSIZE_MASK, etmidr2);
+ caps->ccsize = FIELD_GET(TRCIDR2_CCSIZE_MASK, etmidr2);
etmidr3 = etm4x_relaxed_read32(csa, TRCIDR3);
/* CCITMIN, bits[11:0] minimum threshold value that can be programmed */
- drvdata->ccitmin = FIELD_GET(TRCIDR3_CCITMIN_MASK, etmidr3);
- etm4_fixup_wrong_ccitmin(drvdata);
+ caps->ccitmin = FIELD_GET(TRCIDR3_CCITMIN_MASK, etmidr3);
+ etm4_fixup_wrong_ccitmin(caps);
/* EXLEVEL_S, bits[19:16] Secure state instruction tracing */
- drvdata->s_ex_level = FIELD_GET(TRCIDR3_EXLEVEL_S_MASK, etmidr3);
- drvdata->config.s_ex_level = drvdata->s_ex_level;
+ caps->s_ex_level = FIELD_GET(TRCIDR3_EXLEVEL_S_MASK, etmidr3);
/* EXLEVEL_NS, bits[23:20] Non-secure state instruction tracing */
- drvdata->ns_ex_level = FIELD_GET(TRCIDR3_EXLEVEL_NS_MASK, etmidr3);
+ caps->ns_ex_level = FIELD_GET(TRCIDR3_EXLEVEL_NS_MASK, etmidr3);
/*
* TRCERR, bit[24] whether a trace unit can trace a
* system error exception.
*/
- drvdata->trc_error = !!(etmidr3 & TRCIDR3_TRCERR);
+ caps->trc_error = !!(etmidr3 & TRCIDR3_TRCERR);
/* SYNCPR, bit[25] implementation has a fixed synchronization period? */
- drvdata->syncpr = !!(etmidr3 & TRCIDR3_SYNCPR);
+ caps->syncpr = !!(etmidr3 & TRCIDR3_SYNCPR);
/* STALLCTL, bit[26] is stall control implemented? */
- drvdata->stallctl = !!(etmidr3 & TRCIDR3_STALLCTL);
+ caps->stallctl = !!(etmidr3 & TRCIDR3_STALLCTL);
/* SYSSTALL, bit[27] implementation can support stall control? */
- drvdata->sysstall = !!(etmidr3 & TRCIDR3_SYSSTALL);
+ caps->sysstall = !!(etmidr3 & TRCIDR3_SYSSTALL);
/*
* NUMPROC - the number of PEs available for tracing, 5bits
* = TRCIDR3.bits[13:12]bits[30:28]
* bits[4:3] = TRCIDR3.bits[13:12] (since etm-v4.2, otherwise RES0)
* bits[3:0] = TRCIDR3.bits[30:28]
*/
- drvdata->nr_pe = (FIELD_GET(TRCIDR3_NUMPROC_HI_MASK, etmidr3) << 3) |
- FIELD_GET(TRCIDR3_NUMPROC_LO_MASK, etmidr3);
+ caps->nr_pe = (FIELD_GET(TRCIDR3_NUMPROC_HI_MASK, etmidr3) << 3) |
+ FIELD_GET(TRCIDR3_NUMPROC_LO_MASK, etmidr3);
/* NOOVERFLOW, bit[31] is trace overflow prevention supported */
- drvdata->nooverflow = !!(etmidr3 & TRCIDR3_NOOVERFLOW);
+ caps->nooverflow = !!(etmidr3 & TRCIDR3_NOOVERFLOW);
/* number of resources trace unit supports */
etmidr4 = etm4x_relaxed_read32(csa, TRCIDR4);
/* NUMACPAIRS, bits[0:3] number of addr comparator pairs for tracing */
- drvdata->nr_addr_cmp = FIELD_GET(TRCIDR4_NUMACPAIRS_MASK, etmidr4);
+ caps->nr_addr_cmp = FIELD_GET(TRCIDR4_NUMACPAIRS_MASK, etmidr4);
/* NUMPC, bits[15:12] number of PE comparator inputs for tracing */
- drvdata->nr_pe_cmp = FIELD_GET(TRCIDR4_NUMPC_MASK, etmidr4);
+ caps->nr_pe_cmp = FIELD_GET(TRCIDR4_NUMPC_MASK, etmidr4);
/*
* NUMRSPAIR, bits[19:16]
* The number of resource pairs conveyed by the HW starts at 0, i.e a
@@ -1484,62 +1516,64 @@ static void etm4_init_arch_data(void *info)
* the default TRUE and FALSE resource selectors are omitted.
* Otherwise for values 0x1 and above the number is N + 1 as per v4.2.
*/
- drvdata->nr_resource = FIELD_GET(TRCIDR4_NUMRSPAIR_MASK, etmidr4);
- if ((drvdata->arch < ETM_ARCH_V4_3) || (drvdata->nr_resource > 0))
- drvdata->nr_resource += 1;
+ caps->nr_resource = FIELD_GET(TRCIDR4_NUMRSPAIR_MASK, etmidr4);
+ if ((drvdata->arch < ETM_ARCH_V4_3) || (caps->nr_resource > 0))
+ caps->nr_resource += 1;
/*
* NUMSSCC, bits[23:20] the number of single-shot
* comparator control for tracing. Read any status regs as these
* also contain RO capability data.
*/
- drvdata->nr_ss_cmp = FIELD_GET(TRCIDR4_NUMSSCC_MASK, etmidr4);
- for (i = 0; i < drvdata->nr_ss_cmp; i++) {
- drvdata->config.ss_status[i] =
- etm4x_relaxed_read32(csa, TRCSSCSRn(i));
+ caps->nr_ss_cmp = FIELD_GET(TRCIDR4_NUMSSCC_MASK, etmidr4);
+ for (i = 0; i < caps->nr_ss_cmp; i++) {
+ drvdata->ss_status[i] = etm4x_relaxed_read32(csa, TRCSSCSRn(i));
}
/* NUMCIDC, bits[27:24] number of Context ID comparators for tracing */
- drvdata->numcidc = FIELD_GET(TRCIDR4_NUMCIDC_MASK, etmidr4);
+ caps->numcidc = FIELD_GET(TRCIDR4_NUMCIDC_MASK, etmidr4);
/* NUMVMIDC, bits[31:28] number of VMID comparators for tracing */
- drvdata->numvmidc = FIELD_GET(TRCIDR4_NUMVMIDC_MASK, etmidr4);
+ caps->numvmidc = FIELD_GET(TRCIDR4_NUMVMIDC_MASK, etmidr4);
etmidr5 = etm4x_relaxed_read32(csa, TRCIDR5);
/* NUMEXTIN, bits[8:0] number of external inputs implemented */
- drvdata->nr_ext_inp = FIELD_GET(TRCIDR5_NUMEXTIN_MASK, etmidr5);
- drvdata->numextinsel = FIELD_GET(TRCIDR5_NUMEXTINSEL_MASK, etmidr5);
+ caps->nr_ext_inp = FIELD_GET(TRCIDR5_NUMEXTIN_MASK, etmidr5);
+ caps->numextinsel = FIELD_GET(TRCIDR5_NUMEXTINSEL_MASK, etmidr5);
/* TRACEIDSIZE, bits[21:16] indicates the trace ID width */
- drvdata->trcid_size = FIELD_GET(TRCIDR5_TRACEIDSIZE_MASK, etmidr5);
+ caps->trcid_size = FIELD_GET(TRCIDR5_TRACEIDSIZE_MASK, etmidr5);
/* ATBTRIG, bit[22] implementation can support ATB triggers? */
- drvdata->atbtrig = !!(etmidr5 & TRCIDR5_ATBTRIG);
+ caps->atbtrig = !!(etmidr5 & TRCIDR5_ATBTRIG);
/*
* LPOVERRIDE, bit[23] implementation supports
* low-power state override
*/
- drvdata->lpoverride = (etmidr5 & TRCIDR5_LPOVERRIDE) && (!drvdata->skip_power_up);
+ caps->lpoverride = (etmidr5 & TRCIDR5_LPOVERRIDE) && (!caps->skip_power_up);
/* NUMSEQSTATE, bits[27:25] number of sequencer states implemented */
- drvdata->nrseqstate = FIELD_GET(TRCIDR5_NUMSEQSTATE_MASK, etmidr5);
- if (drvdata->nrseqstate)
- drvdata->nr_seq_ctrls = ETM_MAX_SEQ_TRANSITIONS;
+ caps->nrseqstate = FIELD_GET(TRCIDR5_NUMSEQSTATE_MASK, etmidr5);
+ if (caps->nrseqstate)
+ caps->nr_seq_ctrls = ETM_MAX_SEQ_TRANSITIONS;
/* NUMCNTR, bits[30:28] number of counters available for tracing */
- drvdata->nr_cntr = FIELD_GET(TRCIDR5_NUMCNTR_MASK, etmidr5);
+ caps->nr_cntr = FIELD_GET(TRCIDR5_NUMCNTR_MASK, etmidr5);
coresight_clear_self_claim_tag_unlocked(csa);
etm4_cs_lock(drvdata, csa);
cpu_detect_trace_filtering(drvdata);
}
-static u32 etm4_get_victlr_access_type(struct etmv4_config *config)
+static u32 etm4_get_victlr_access_type(struct etmv4_config *config,
+ const struct etmv4_caps *caps)
{
- return etm4_get_access_type(config) << __bf_shf(TRCVICTLR_EXLEVEL_MASK);
+ return etm4_get_access_type(config, caps) << __bf_shf(TRCVICTLR_EXLEVEL_MASK);
}
/* Set ELx trace filter access in the TRCVICTLR register */
-static void etm4_set_victlr_access(struct etmv4_config *config)
+static void etm4_set_victlr_access(struct etmv4_config *config,
+ const struct etmv4_caps *caps)
{
config->vinst_ctrl &= ~TRCVICTLR_EXLEVEL_MASK;
- config->vinst_ctrl |= etm4_get_victlr_access_type(config);
+ config->vinst_ctrl |= etm4_get_victlr_access_type(config, caps);
}
-static void etm4_set_default_config(struct etmv4_config *config)
+static void etm4_set_default_config(struct etmv4_config *config,
+ const struct etmv4_caps *caps)
{
/* disable all events tracing */
config->eventctrl0 = 0x0;
@@ -1558,7 +1592,7 @@ static void etm4_set_default_config(struct etmv4_config *config)
config->vinst_ctrl = FIELD_PREP(TRCVICTLR_EVENT_MASK, 0x01);
/* TRCVICTLR::EXLEVEL_NS:EXLEVELS: Set kernel / user filtering */
- etm4_set_victlr_access(config);
+ etm4_set_victlr_access(config, caps);
}
static u64 etm4_get_ns_access_type(struct etmv4_config *config)
@@ -1590,21 +1624,24 @@ static u64 etm4_get_ns_access_type(struct etmv4_config *config)
* This must be shifted to the corresponding register field
* for usage.
*/
-static u64 etm4_get_access_type(struct etmv4_config *config)
+static u64 etm4_get_access_type(struct etmv4_config *config,
+ const struct etmv4_caps *caps)
{
/* All Secure exception levels are excluded from the trace */
- return etm4_get_ns_access_type(config) | (u64)config->s_ex_level;
+ return etm4_get_ns_access_type(config) | (u64)caps->s_ex_level;
}
-static u64 etm4_get_comparator_access_type(struct etmv4_config *config)
+static u64 etm4_get_comparator_access_type(struct etmv4_config *config,
+ const struct etmv4_caps *caps)
{
- return etm4_get_access_type(config) << TRCACATR_EXLEVEL_SHIFT;
+ return etm4_get_access_type(config, caps) << TRCACATR_EXLEVEL_SHIFT;
}
static void etm4_set_comparator_filter(struct etmv4_config *config,
+ const struct etmv4_caps *caps,
u64 start, u64 stop, int comparator)
{
- u64 access_type = etm4_get_comparator_access_type(config);
+ u64 access_type = etm4_get_comparator_access_type(config, caps);
/* First half of default address comparator */
config->addr_val[comparator] = start;
@@ -1635,11 +1672,12 @@ static void etm4_set_comparator_filter(struct etmv4_config *config,
}
static void etm4_set_start_stop_filter(struct etmv4_config *config,
+ const struct etmv4_caps *caps,
u64 address, int comparator,
enum etm_addr_type type)
{
int shift;
- u64 access_type = etm4_get_comparator_access_type(config);
+ u64 access_type = etm4_get_comparator_access_type(config, caps);
/* Configure the comparator */
config->addr_val[comparator] = address;
@@ -1671,7 +1709,8 @@ static void etm4_set_default_filter(struct etmv4_config *config)
config->vissctlr = 0x0;
}
-static void etm4_set_default(struct etmv4_config *config)
+static void etm4_set_default(struct etmv4_config *config,
+ const struct etmv4_caps *caps)
{
if (WARN_ON_ONCE(!config))
return;
@@ -1683,27 +1722,27 @@ static void etm4_set_default(struct etmv4_config *config)
* full instruction trace - with a default filter for trace all
* achieved by having no filtering.
*/
- etm4_set_default_config(config);
+ etm4_set_default_config(config, caps);
etm4_set_default_filter(config);
}
static int etm4_get_next_comparator(struct etmv4_drvdata *drvdata, u32 type)
{
int nr_comparator, index = 0;
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *curr_config = &drvdata->curr_config;
/*
* nr_addr_cmp holds the number of comparator _pair_, so time 2
* for the total number of comparators.
*/
- nr_comparator = drvdata->nr_addr_cmp * 2;
+ nr_comparator = drvdata->caps.nr_addr_cmp * 2;
/* Go through the tally of comparators looking for a free one. */
while (index < nr_comparator) {
switch (type) {
case ETM_ADDR_TYPE_RANGE:
- if (config->addr_type[index] == ETM_ADDR_TYPE_NONE &&
- config->addr_type[index + 1] == ETM_ADDR_TYPE_NONE)
+ if (curr_config->addr_type[index] == ETM_ADDR_TYPE_NONE &&
+ curr_config->addr_type[index + 1] == ETM_ADDR_TYPE_NONE)
return index;
/* Address range comparators go in pairs */
@@ -1711,7 +1750,7 @@ static int etm4_get_next_comparator(struct etmv4_drvdata *drvdata, u32 type)
break;
case ETM_ADDR_TYPE_START:
case ETM_ADDR_TYPE_STOP:
- if (config->addr_type[index] == ETM_ADDR_TYPE_NONE)
+ if (curr_config->addr_type[index] == ETM_ADDR_TYPE_NONE)
return index;
/* Start/stop address can have odd indexes */
@@ -1731,7 +1770,8 @@ static int etm4_set_event_filters(struct etmv4_drvdata *drvdata,
{
int i, comparator, ret = 0;
u64 address;
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *curr_config = &drvdata->curr_config;
struct etm_filters *filters = event->hw.addr_filters;
if (!filters)
@@ -1760,7 +1800,7 @@ static int etm4_set_event_filters(struct etmv4_drvdata *drvdata,
switch (type) {
case ETM_ADDR_TYPE_RANGE:
- etm4_set_comparator_filter(config,
+ etm4_set_comparator_filter(curr_config, caps,
filter->start_addr,
filter->stop_addr,
comparator);
@@ -1768,10 +1808,10 @@ static int etm4_set_event_filters(struct etmv4_drvdata *drvdata,
* TRCVICTLR::SSSTATUS == 1, the start-stop logic is
* in the started state
*/
- config->vinst_ctrl |= TRCVICTLR_SSSTATUS;
+ curr_config->vinst_ctrl |= TRCVICTLR_SSSTATUS;
/* No start-stop filtering for ViewInst */
- config->vissctlr = 0x0;
+ curr_config->vissctlr = 0x0;
break;
case ETM_ADDR_TYPE_START:
case ETM_ADDR_TYPE_STOP:
@@ -1781,7 +1821,7 @@ static int etm4_set_event_filters(struct etmv4_drvdata *drvdata,
filter->stop_addr);
/* Configure comparator */
- etm4_set_start_stop_filter(config, address,
+ etm4_set_start_stop_filter(curr_config, caps, address,
comparator, type);
/*
@@ -1796,10 +1836,10 @@ static int etm4_set_event_filters(struct etmv4_drvdata *drvdata,
* etm4_disable_perf().
*/
if (filters->ssstatus)
- config->vinst_ctrl |= TRCVICTLR_SSSTATUS;
+ curr_config->vinst_ctrl |= TRCVICTLR_SSSTATUS;
/* No include/exclude filtering for ViewInst */
- config->viiectlr = 0x0;
+ curr_config->viiectlr = 0x0;
break;
default:
ret = -EINVAL;
@@ -1811,13 +1851,14 @@ static int etm4_set_event_filters(struct etmv4_drvdata *drvdata,
default_filter:
- etm4_set_default_filter(config);
+ etm4_set_default_filter(curr_config);
out:
return ret;
}
-void etm4_config_trace_mode(struct etmv4_config *config)
+void etm4_config_trace_mode(struct etmv4_config *config,
+ const struct etmv4_caps *caps)
{
u32 mode;
@@ -1831,7 +1872,7 @@ void etm4_config_trace_mode(struct etmv4_config *config)
if (!(mode & ETM_MODE_EXCL_KERN) && !(mode & ETM_MODE_EXCL_USER))
return;
- etm4_set_victlr_access(config);
+ etm4_set_victlr_access(config, caps);
}
static int etm4_online_cpu(unsigned int cpu)
@@ -1853,6 +1894,7 @@ static int etm4_cpu_save(struct coresight_device *csdev)
{
int i, ret = 0;
struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+ const struct etmv4_caps *caps = &drvdata->caps;
struct etmv4_save_state *state;
struct csdev_access *csa;
struct device *etm_dev;
@@ -1885,57 +1927,56 @@ static int etm4_cpu_save(struct coresight_device *csdev)
state = drvdata->save_state;
- if (drvdata->nr_pe)
+ if (caps->nr_pe)
state->trcprocselr = etm4x_read32(csa, TRCPROCSELR);
state->trcconfigr = etm4x_read32(csa, TRCCONFIGR);
state->trcauxctlr = etm4x_read32(csa, TRCAUXCTLR);
state->trceventctl0r = etm4x_read32(csa, TRCEVENTCTL0R);
state->trceventctl1r = etm4x_read32(csa, TRCEVENTCTL1R);
- if (drvdata->stallctl)
+ if (caps->stallctl)
state->trcstallctlr = etm4x_read32(csa, TRCSTALLCTLR);
state->trctsctlr = etm4x_read32(csa, TRCTSCTLR);
state->trcsyncpr = etm4x_read32(csa, TRCSYNCPR);
state->trcccctlr = etm4x_read32(csa, TRCCCCTLR);
state->trcbbctlr = etm4x_read32(csa, TRCBBCTLR);
state->trctraceidr = etm4x_read32(csa, TRCTRACEIDR);
- if (drvdata->q_filt)
+ if (caps->q_filt)
state->trcqctlr = etm4x_read32(csa, TRCQCTLR);
state->trcvictlr = etm4x_read32(csa, TRCVICTLR);
state->trcviiectlr = etm4x_read32(csa, TRCVIIECTLR);
state->trcvissctlr = etm4x_read32(csa, TRCVISSCTLR);
- if (drvdata->nr_pe_cmp)
+ if (caps->nr_pe_cmp)
state->trcvipcssctlr = etm4x_read32(csa, TRCVIPCSSCTLR);
- for (i = 0; i < drvdata->nr_seq_ctrls; i++)
+ for (i = 0; i < caps->nr_seq_ctrls; i++)
state->trcseqevr[i] = etm4x_read32(csa, TRCSEQEVRn(i));
-
- if (drvdata->nrseqstate) {
+ if (caps->nrseqstate) {
state->trcseqrstevr = etm4x_read32(csa, TRCSEQRSTEVR);
state->trcseqstr = etm4x_read32(csa, TRCSEQSTR);
}
- if (drvdata->numextinsel)
+ if (caps->numextinsel)
state->trcextinselr = etm4x_read32(csa, TRCEXTINSELR);
- for (i = 0; i < drvdata->nr_cntr; i++) {
+ for (i = 0; i < caps->nr_cntr; i++) {
state->trccntrldvr[i] = etm4x_read32(csa, TRCCNTRLDVRn(i));
state->trccntctlr[i] = etm4x_read32(csa, TRCCNTCTLRn(i));
state->trccntvr[i] = etm4x_read32(csa, TRCCNTVRn(i));
}
/* Resource selector pair 0 is reserved */
- for (i = 2; i < drvdata->nr_resource * 2; i++)
+ for (i = 2; i < caps->nr_resource * 2; i++)
state->trcrsctlr[i] = etm4x_read32(csa, TRCRSCTLRn(i));
- for (i = 0; i < drvdata->nr_ss_cmp; i++) {
+ for (i = 0; i < caps->nr_ss_cmp; i++) {
state->trcssccr[i] = etm4x_read32(csa, TRCSSCCRn(i));
state->trcsscsr[i] = etm4x_read32(csa, TRCSSCSRn(i));
if (etm4x_sspcicrn_present(drvdata, i))
state->trcsspcicr[i] = etm4x_read32(csa, TRCSSPCICRn(i));
}
- for (i = 0; i < drvdata->nr_addr_cmp * 2; i++) {
+ for (i = 0; i < caps->nr_addr_cmp * 2; i++) {
state->trcacvr[i] = etm4x_read64(csa, TRCACVRn(i));
state->trcacatr[i] = etm4x_read64(csa, TRCACATRn(i));
}
@@ -1947,23 +1988,23 @@ static int etm4_cpu_save(struct coresight_device *csdev)
* unit") of ARM IHI 0064D.
*/
- for (i = 0; i < drvdata->numcidc; i++)
+ for (i = 0; i < caps->numcidc; i++)
state->trccidcvr[i] = etm4x_read64(csa, TRCCIDCVRn(i));
- for (i = 0; i < drvdata->numvmidc; i++)
+ for (i = 0; i < caps->numvmidc; i++)
state->trcvmidcvr[i] = etm4x_read64(csa, TRCVMIDCVRn(i));
state->trccidcctlr0 = etm4x_read32(csa, TRCCIDCCTLR0);
- if (drvdata->numcidc > 4)
+ if (caps->numcidc > 4)
state->trccidcctlr1 = etm4x_read32(csa, TRCCIDCCTLR1);
state->trcvmidcctlr0 = etm4x_read32(csa, TRCVMIDCCTLR0);
- if (drvdata->numvmidc > 4)
+ if (caps->numvmidc > 4)
state->trcvmidcctlr1 = etm4x_read32(csa, TRCVMIDCCTLR1);
state->trcclaimset = etm4x_read32(csa, TRCCLAIMCLR);
- if (!drvdata->skip_power_up)
+ if (!caps->skip_power_up)
state->trcpdcr = etm4x_read32(csa, TRCPDCR);
/* wait for TRCSTATR.IDLE to go up */
@@ -1980,7 +2021,7 @@ static int etm4_cpu_save(struct coresight_device *csdev)
* potentially save power on systems that respect the TRCPDCR_PU
* despite requesting software to save/restore state.
*/
- if (!drvdata->skip_power_up)
+ if (!caps->skip_power_up)
etm4x_relaxed_write32(csa, (state->trcpdcr & ~TRCPDCR_PU),
TRCPDCR);
out:
@@ -1992,83 +2033,84 @@ static void etm4_cpu_restore(struct coresight_device *csdev)
{
int i;
struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+ const struct etmv4_caps *caps = &drvdata->caps;
struct etmv4_save_state *state = drvdata->save_state;
struct csdev_access *csa = &drvdata->csdev->access;
etm4_cs_unlock(drvdata, csa);
etm4x_relaxed_write32(csa, state->trcclaimset, TRCCLAIMSET);
- if (drvdata->nr_pe)
+ if (caps->nr_pe)
etm4x_relaxed_write32(csa, state->trcprocselr, TRCPROCSELR);
etm4x_relaxed_write32(csa, state->trcconfigr, TRCCONFIGR);
etm4x_relaxed_write32(csa, state->trcauxctlr, TRCAUXCTLR);
etm4x_relaxed_write32(csa, state->trceventctl0r, TRCEVENTCTL0R);
etm4x_relaxed_write32(csa, state->trceventctl1r, TRCEVENTCTL1R);
- if (drvdata->stallctl)
+ if (caps->stallctl)
etm4x_relaxed_write32(csa, state->trcstallctlr, TRCSTALLCTLR);
etm4x_relaxed_write32(csa, state->trctsctlr, TRCTSCTLR);
etm4x_relaxed_write32(csa, state->trcsyncpr, TRCSYNCPR);
etm4x_relaxed_write32(csa, state->trcccctlr, TRCCCCTLR);
etm4x_relaxed_write32(csa, state->trcbbctlr, TRCBBCTLR);
etm4x_relaxed_write32(csa, state->trctraceidr, TRCTRACEIDR);
- if (drvdata->q_filt)
+ if (caps->q_filt)
etm4x_relaxed_write32(csa, state->trcqctlr, TRCQCTLR);
etm4x_relaxed_write32(csa, state->trcvictlr, TRCVICTLR);
etm4x_relaxed_write32(csa, state->trcviiectlr, TRCVIIECTLR);
etm4x_relaxed_write32(csa, state->trcvissctlr, TRCVISSCTLR);
- if (drvdata->nr_pe_cmp)
+ if (caps->nr_pe_cmp)
etm4x_relaxed_write32(csa, state->trcvipcssctlr, TRCVIPCSSCTLR);
- for (i = 0; i < drvdata->nr_seq_ctrls; i++)
+ for (i = 0; i < caps->nr_seq_ctrls; i++)
etm4x_relaxed_write32(csa, state->trcseqevr[i], TRCSEQEVRn(i));
- if (drvdata->nrseqstate) {
+ if (caps->nrseqstate) {
etm4x_relaxed_write32(csa, state->trcseqrstevr, TRCSEQRSTEVR);
etm4x_relaxed_write32(csa, state->trcseqstr, TRCSEQSTR);
}
- if (drvdata->numextinsel)
+ if (caps->numextinsel)
etm4x_relaxed_write32(csa, state->trcextinselr, TRCEXTINSELR);
- for (i = 0; i < drvdata->nr_cntr; i++) {
+ for (i = 0; i < caps->nr_cntr; i++) {
etm4x_relaxed_write32(csa, state->trccntrldvr[i], TRCCNTRLDVRn(i));
etm4x_relaxed_write32(csa, state->trccntctlr[i], TRCCNTCTLRn(i));
etm4x_relaxed_write32(csa, state->trccntvr[i], TRCCNTVRn(i));
}
/* Resource selector pair 0 is reserved */
- for (i = 2; i < drvdata->nr_resource * 2; i++)
+ for (i = 2; i < caps->nr_resource * 2; i++)
etm4x_relaxed_write32(csa, state->trcrsctlr[i], TRCRSCTLRn(i));
- for (i = 0; i < drvdata->nr_ss_cmp; i++) {
+ for (i = 0; i < caps->nr_ss_cmp; i++) {
etm4x_relaxed_write32(csa, state->trcssccr[i], TRCSSCCRn(i));
etm4x_relaxed_write32(csa, state->trcsscsr[i], TRCSSCSRn(i));
if (etm4x_sspcicrn_present(drvdata, i))
etm4x_relaxed_write32(csa, state->trcsspcicr[i], TRCSSPCICRn(i));
}
- for (i = 0; i < drvdata->nr_addr_cmp * 2; i++) {
+ for (i = 0; i < caps->nr_addr_cmp * 2; i++) {
etm4x_relaxed_write64(csa, state->trcacvr[i], TRCACVRn(i));
etm4x_relaxed_write64(csa, state->trcacatr[i], TRCACATRn(i));
}
- for (i = 0; i < drvdata->numcidc; i++)
+ for (i = 0; i < caps->numcidc; i++)
etm4x_relaxed_write64(csa, state->trccidcvr[i], TRCCIDCVRn(i));
- for (i = 0; i < drvdata->numvmidc; i++)
+ for (i = 0; i < caps->numvmidc; i++)
etm4x_relaxed_write64(csa, state->trcvmidcvr[i], TRCVMIDCVRn(i));
etm4x_relaxed_write32(csa, state->trccidcctlr0, TRCCIDCCTLR0);
- if (drvdata->numcidc > 4)
+ if (caps->numcidc > 4)
etm4x_relaxed_write32(csa, state->trccidcctlr1, TRCCIDCCTLR1);
etm4x_relaxed_write32(csa, state->trcvmidcctlr0, TRCVMIDCCTLR0);
- if (drvdata->numvmidc > 4)
+ if (caps->numvmidc > 4)
etm4x_relaxed_write32(csa, state->trcvmidcctlr1, TRCVMIDCCTLR1);
etm4x_relaxed_write32(csa, state->trcclaimset, TRCCLAIMSET);
- if (!drvdata->skip_power_up)
+ if (!caps->skip_power_up)
etm4x_relaxed_write32(csa, state->trcpdcr, TRCPDCR);
/*
@@ -2142,6 +2184,7 @@ static int etm4_add_coresight_dev(struct etm4_init_arg *init_arg)
struct coresight_platform_data *pdata = NULL;
struct device *dev = init_arg->dev;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev);
+ const struct etmv4_caps *caps = &drvdata->caps;
struct coresight_desc desc = { 0 };
u8 major, minor;
char *type_name;
@@ -2171,7 +2214,7 @@ static int etm4_add_coresight_dev(struct etm4_init_arg *init_arg)
if (!desc.name)
return -ENOMEM;
- etm4_set_default(&drvdata->config);
+ etm4_set_default(&drvdata->sysfs_config, caps);
if (etm4x_always_pm_save(dev, init_arg->csa))
pm_save = true;
diff --git a/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c b/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c
index cc6cdd3ae29d..db9e603f97ba 100644
--- a/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c
+++ b/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c
@@ -16,15 +16,15 @@
static int etm4_set_mode_exclude(struct etmv4_drvdata *drvdata, bool exclude)
{
u8 idx;
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- idx = config->addr_idx;
+ idx = sysfs_config->addr_idx;
/*
* TRCACATRn.TYPE bit[1:0]: type of comparison
* the trace unit performs
*/
- if (FIELD_GET(TRCACATRn_TYPE_MASK, config->addr_acc[idx]) == TRCACATRn_TYPE_ADDR) {
+ if (FIELD_GET(TRCACATRn_TYPE_MASK, sysfs_config->addr_acc[idx]) == TRCACATRn_TYPE_ADDR) {
if (idx % 2 != 0)
return -EINVAL;
@@ -33,8 +33,8 @@ static int etm4_set_mode_exclude(struct etmv4_drvdata *drvdata, bool exclude)
* relevant bit of ViewInst Include/Exclude Control register
* for corresponding address comparator pair.
*/
- if (config->addr_type[idx] != ETM_ADDR_TYPE_RANGE ||
- config->addr_type[idx + 1] != ETM_ADDR_TYPE_RANGE)
+ if (sysfs_config->addr_type[idx] != ETM_ADDR_TYPE_RANGE ||
+ sysfs_config->addr_type[idx + 1] != ETM_ADDR_TYPE_RANGE)
return -EINVAL;
if (exclude == true) {
@@ -42,15 +42,15 @@ static int etm4_set_mode_exclude(struct etmv4_drvdata *drvdata, bool exclude)
* Set exclude bit and unset the include bit
* corresponding to comparator pair
*/
- config->viiectlr |= BIT(idx / 2 + 16);
- config->viiectlr &= ~BIT(idx / 2);
+ sysfs_config->viiectlr |= BIT(idx / 2 + 16);
+ sysfs_config->viiectlr &= ~BIT(idx / 2);
} else {
/*
* Set include bit and unset exclude bit
* corresponding to comparator pair
*/
- config->viiectlr |= BIT(idx / 2);
- config->viiectlr &= ~BIT(idx / 2 + 16);
+ sysfs_config->viiectlr |= BIT(idx / 2);
+ sysfs_config->viiectlr &= ~BIT(idx / 2 + 16);
}
}
return 0;
@@ -62,8 +62,9 @@ static ssize_t nr_pe_cmp_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ const struct etmv4_caps *caps = &drvdata->caps;
- val = drvdata->nr_pe_cmp;
+ val = caps->nr_pe_cmp;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
static DEVICE_ATTR_RO(nr_pe_cmp);
@@ -74,8 +75,9 @@ static ssize_t nr_addr_cmp_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ const struct etmv4_caps *caps = &drvdata->caps;
- val = drvdata->nr_addr_cmp;
+ val = caps->nr_addr_cmp;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
static DEVICE_ATTR_RO(nr_addr_cmp);
@@ -86,8 +88,9 @@ static ssize_t nr_cntr_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ const struct etmv4_caps *caps = &drvdata->caps;
- val = drvdata->nr_cntr;
+ val = caps->nr_cntr;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
static DEVICE_ATTR_RO(nr_cntr);
@@ -98,8 +101,9 @@ static ssize_t nr_ext_inp_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ const struct etmv4_caps *caps = &drvdata->caps;
- val = drvdata->nr_ext_inp;
+ val = caps->nr_ext_inp;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
static DEVICE_ATTR_RO(nr_ext_inp);
@@ -110,8 +114,9 @@ static ssize_t numcidc_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ const struct etmv4_caps *caps = &drvdata->caps;
- val = drvdata->numcidc;
+ val = caps->numcidc;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
static DEVICE_ATTR_RO(numcidc);
@@ -122,8 +127,9 @@ static ssize_t numvmidc_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ const struct etmv4_caps *caps = &drvdata->caps;
- val = drvdata->numvmidc;
+ val = caps->numvmidc;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
static DEVICE_ATTR_RO(numvmidc);
@@ -134,8 +140,9 @@ static ssize_t nrseqstate_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ const struct etmv4_caps *caps = &drvdata->caps;
- val = drvdata->nrseqstate;
+ val = caps->nrseqstate;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
static DEVICE_ATTR_RO(nrseqstate);
@@ -146,8 +153,9 @@ static ssize_t nr_resource_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ const struct etmv4_caps *caps = &drvdata->caps;
- val = drvdata->nr_resource;
+ val = caps->nr_resource;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
static DEVICE_ATTR_RO(nr_resource);
@@ -158,8 +166,9 @@ static ssize_t nr_ss_cmp_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ const struct etmv4_caps *caps = &drvdata->caps;
- val = drvdata->nr_ss_cmp;
+ val = caps->nr_ss_cmp;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
static DEVICE_ATTR_RO(nr_ss_cmp);
@@ -171,102 +180,103 @@ static ssize_t reset_store(struct device *dev,
int i;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
if (val)
- config->mode = 0x0;
+ sysfs_config->mode = 0x0;
/* Disable data tracing: do not trace load and store data transfers */
- config->mode &= ~(ETM_MODE_LOAD | ETM_MODE_STORE);
- config->cfg &= ~(TRCCONFIGR_INSTP0_LOAD | TRCCONFIGR_INSTP0_STORE);
+ sysfs_config->mode &= ~(ETM_MODE_LOAD | ETM_MODE_STORE);
+ sysfs_config->cfg &= ~(TRCCONFIGR_INSTP0_LOAD | TRCCONFIGR_INSTP0_STORE);
/* Disable data value and data address tracing */
- config->mode &= ~(ETM_MODE_DATA_TRACE_ADDR |
+ sysfs_config->mode &= ~(ETM_MODE_DATA_TRACE_ADDR |
ETM_MODE_DATA_TRACE_VAL);
- config->cfg &= ~(TRCCONFIGR_DA | TRCCONFIGR_DV);
+ sysfs_config->cfg &= ~(TRCCONFIGR_DA | TRCCONFIGR_DV);
/* Disable all events tracing */
- config->eventctrl0 = 0x0;
- config->eventctrl1 = 0x0;
+ sysfs_config->eventctrl0 = 0x0;
+ sysfs_config->eventctrl1 = 0x0;
/* Disable timestamp event */
- config->ts_ctrl = 0x0;
+ sysfs_config->ts_ctrl = 0x0;
/* Disable stalling */
- config->stall_ctrl = 0x0;
+ sysfs_config->stall_ctrl = 0x0;
/* Reset trace synchronization period to 2^8 = 256 bytes*/
- if (drvdata->syncpr == false)
- config->syncfreq = 0x8;
+ if (!caps->syncpr)
+ sysfs_config->syncfreq = 0x8;
/*
* Enable ViewInst to trace everything with start-stop logic in
* started state. ARM recommends start-stop logic is set before
* each trace run.
*/
- config->vinst_ctrl = FIELD_PREP(TRCVICTLR_EVENT_MASK, 0x01);
- if (drvdata->nr_addr_cmp > 0) {
- config->mode |= ETM_MODE_VIEWINST_STARTSTOP;
+ sysfs_config->vinst_ctrl = FIELD_PREP(TRCVICTLR_EVENT_MASK, 0x01);
+ if (caps->nr_addr_cmp > 0) {
+ sysfs_config->mode |= ETM_MODE_VIEWINST_STARTSTOP;
/* SSSTATUS, bit[9] */
- config->vinst_ctrl |= TRCVICTLR_SSSTATUS;
+ sysfs_config->vinst_ctrl |= TRCVICTLR_SSSTATUS;
}
/* No address range filtering for ViewInst */
- config->viiectlr = 0x0;
+ sysfs_config->viiectlr = 0x0;
/* No start-stop filtering for ViewInst */
- config->vissctlr = 0x0;
- config->vipcssctlr = 0x0;
+ sysfs_config->vissctlr = 0x0;
+ sysfs_config->vipcssctlr = 0x0;
/* Disable seq events */
- for (i = 0; i < drvdata->nr_seq_ctrls; i++)
- config->seq_ctrl[i] = 0x0;
- config->seq_rst = 0x0;
- config->seq_state = 0x0;
+ for (i = 0; i < caps->nr_seq_ctrls; i++)
+ sysfs_config->seq_ctrl[i] = 0x0;
+ sysfs_config->seq_rst = 0x0;
+ sysfs_config->seq_state = 0x0;
/* Disable external input events */
- config->ext_inp = 0x0;
+ sysfs_config->ext_inp = 0x0;
- config->cntr_idx = 0x0;
- for (i = 0; i < drvdata->nr_cntr; i++) {
- config->cntrldvr[i] = 0x0;
- config->cntr_ctrl[i] = 0x0;
- config->cntr_val[i] = 0x0;
+ sysfs_config->cntr_idx = 0x0;
+ for (i = 0; i < caps->nr_cntr; i++) {
+ sysfs_config->cntrldvr[i] = 0x0;
+ sysfs_config->cntr_ctrl[i] = 0x0;
+ sysfs_config->cntr_val[i] = 0x0;
}
- config->res_idx = 0x0;
- for (i = 2; i < 2 * drvdata->nr_resource; i++)
- config->res_ctrl[i] = 0x0;
+ sysfs_config->res_idx = 0x0;
+ for (i = 2; i < 2 * caps->nr_resource; i++)
+ sysfs_config->res_ctrl[i] = 0x0;
- config->ss_idx = 0x0;
- for (i = 0; i < drvdata->nr_ss_cmp; i++) {
- config->ss_ctrl[i] = 0x0;
- config->ss_pe_cmp[i] = 0x0;
+ sysfs_config->ss_idx = 0x0;
+ for (i = 0; i < caps->nr_ss_cmp; i++) {
+ sysfs_config->ss_ctrl[i] = 0x0;
+ sysfs_config->ss_pe_cmp[i] = 0x0;
}
- config->addr_idx = 0x0;
- for (i = 0; i < drvdata->nr_addr_cmp * 2; i++) {
- config->addr_val[i] = 0x0;
- config->addr_acc[i] = 0x0;
- config->addr_type[i] = ETM_ADDR_TYPE_NONE;
+ sysfs_config->addr_idx = 0x0;
+ for (i = 0; i < caps->nr_addr_cmp * 2; i++) {
+ sysfs_config->addr_val[i] = 0x0;
+ sysfs_config->addr_acc[i] = 0x0;
+ sysfs_config->addr_type[i] = ETM_ADDR_TYPE_NONE;
}
- config->ctxid_idx = 0x0;
- for (i = 0; i < drvdata->numcidc; i++)
- config->ctxid_pid[i] = 0x0;
+ sysfs_config->ctxid_idx = 0x0;
+ for (i = 0; i < caps->numcidc; i++)
+ sysfs_config->ctxid_pid[i] = 0x0;
- config->ctxid_mask0 = 0x0;
- config->ctxid_mask1 = 0x0;
+ sysfs_config->ctxid_mask0 = 0x0;
+ sysfs_config->ctxid_mask1 = 0x0;
- config->vmid_idx = 0x0;
- for (i = 0; i < drvdata->numvmidc; i++)
- config->vmid_val[i] = 0x0;
- config->vmid_mask0 = 0x0;
- config->vmid_mask1 = 0x0;
+ sysfs_config->vmid_idx = 0x0;
+ for (i = 0; i < caps->numvmidc; i++)
+ sysfs_config->vmid_val[i] = 0x0;
+ sysfs_config->vmid_mask0 = 0x0;
+ sysfs_config->vmid_mask1 = 0x0;
raw_spin_unlock(&drvdata->spinlock);
@@ -285,9 +295,9 @@ static ssize_t mode_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->mode;
+ val = sysfs_config->mode;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -297,147 +307,143 @@ static ssize_t mode_store(struct device *dev,
{
unsigned long val, mode;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- config->mode = val & ETMv4_MODE_ALL;
+ sysfs_config->mode = val & ETMv4_MODE_ALL;
- if (drvdata->instrp0 == true) {
+ if (caps->instrp0) {
/* start by clearing instruction P0 field */
- config->cfg &= ~TRCCONFIGR_INSTP0_LOAD_STORE;
- if (config->mode & ETM_MODE_LOAD)
+ sysfs_config->cfg &= ~TRCCONFIGR_INSTP0_LOAD_STORE;
+ if (sysfs_config->mode & ETM_MODE_LOAD)
/* 0b01 Trace load instructions as P0 instructions */
- config->cfg |= TRCCONFIGR_INSTP0_LOAD;
- if (config->mode & ETM_MODE_STORE)
+ sysfs_config->cfg |= TRCCONFIGR_INSTP0_LOAD;
+ if (sysfs_config->mode & ETM_MODE_STORE)
/* 0b10 Trace store instructions as P0 instructions */
- config->cfg |= TRCCONFIGR_INSTP0_STORE;
- if (config->mode & ETM_MODE_LOAD_STORE)
+ sysfs_config->cfg |= TRCCONFIGR_INSTP0_STORE;
+ if (sysfs_config->mode & ETM_MODE_LOAD_STORE)
/*
* 0b11 Trace load and store instructions
* as P0 instructions
*/
- config->cfg |= TRCCONFIGR_INSTP0_LOAD_STORE;
+ sysfs_config->cfg |= TRCCONFIGR_INSTP0_LOAD_STORE;
}
/* bit[3], Branch broadcast mode */
- if ((config->mode & ETM_MODE_BB) && (drvdata->trcbb == true))
- config->cfg |= TRCCONFIGR_BB;
+ if ((sysfs_config->mode & ETM_MODE_BB) && (caps->trcbb))
+ sysfs_config->cfg |= TRCCONFIGR_BB;
else
- config->cfg &= ~TRCCONFIGR_BB;
+ sysfs_config->cfg &= ~TRCCONFIGR_BB;
/* bit[4], Cycle counting instruction trace bit */
- if ((config->mode & ETMv4_MODE_CYCACC) &&
- (drvdata->trccci == true))
- config->cfg |= TRCCONFIGR_CCI;
+ if ((sysfs_config->mode & ETMv4_MODE_CYCACC) &&
+ (caps->trccci == true))
+ sysfs_config->cfg |= TRCCONFIGR_CCI;
else
- config->cfg &= ~TRCCONFIGR_CCI;
+ sysfs_config->cfg &= ~TRCCONFIGR_CCI;
/* bit[6], Context ID tracing bit */
- if ((config->mode & ETMv4_MODE_CTXID) && (drvdata->ctxid_size))
- config->cfg |= TRCCONFIGR_CID;
+ if ((sysfs_config->mode & ETMv4_MODE_CTXID) && (caps->ctxid_size))
+ sysfs_config->cfg |= TRCCONFIGR_CID;
else
- config->cfg &= ~TRCCONFIGR_CID;
+ sysfs_config->cfg &= ~TRCCONFIGR_CID;
- if ((config->mode & ETM_MODE_VMID) && (drvdata->vmid_size))
- config->cfg |= TRCCONFIGR_VMID;
+ if ((sysfs_config->mode & ETM_MODE_VMID) && (caps->vmid_size))
+ sysfs_config->cfg |= TRCCONFIGR_VMID;
else
- config->cfg &= ~TRCCONFIGR_VMID;
+ sysfs_config->cfg &= ~TRCCONFIGR_VMID;
/* bits[10:8], Conditional instruction tracing bit */
- mode = ETM_MODE_COND(config->mode);
- if (drvdata->trccond == true) {
- config->cfg &= ~TRCCONFIGR_COND_MASK;
- config->cfg |= mode << __bf_shf(TRCCONFIGR_COND_MASK);
+ mode = ETM_MODE_COND(sysfs_config->mode);
+ if (caps->trccond) {
+ sysfs_config->cfg &= ~TRCCONFIGR_COND_MASK;
+ sysfs_config->cfg |= mode << __bf_shf(TRCCONFIGR_COND_MASK);
}
/* bit[11], Global timestamp tracing bit */
- if ((config->mode & ETMv4_MODE_TIMESTAMP) && (drvdata->ts_size))
- config->cfg |= TRCCONFIGR_TS;
+ if ((sysfs_config->mode & ETMv4_MODE_TIMESTAMP) && (caps->ts_size))
+ sysfs_config->cfg |= TRCCONFIGR_TS;
else
- config->cfg &= ~TRCCONFIGR_TS;
+ sysfs_config->cfg &= ~TRCCONFIGR_TS;
/* bit[12], Return stack enable bit */
- if ((config->mode & ETM_MODE_RETURNSTACK) &&
- (drvdata->retstack == true))
- config->cfg |= TRCCONFIGR_RS;
+ if ((sysfs_config->mode & ETM_MODE_RETURNSTACK) && (caps->retstack))
+ sysfs_config->cfg |= TRCCONFIGR_RS;
else
- config->cfg &= ~TRCCONFIGR_RS;
+ sysfs_config->cfg &= ~TRCCONFIGR_RS;
/* bits[14:13], Q element enable field */
- mode = ETM_MODE_QELEM(config->mode);
+ mode = ETM_MODE_QELEM(sysfs_config->mode);
/* start by clearing QE bits */
- config->cfg &= ~(TRCCONFIGR_QE_W_COUNTS | TRCCONFIGR_QE_WO_COUNTS);
+ sysfs_config->cfg &= ~(TRCCONFIGR_QE_W_COUNTS | TRCCONFIGR_QE_WO_COUNTS);
/*
* if supported, Q elements with instruction counts are enabled.
* Always set the low bit for any requested mode. Valid combos are
* 0b00, 0b01 and 0b11.
*/
- if (mode && drvdata->q_support)
- config->cfg |= TRCCONFIGR_QE_W_COUNTS;
+ if (mode && caps->q_support)
+ sysfs_config->cfg |= TRCCONFIGR_QE_W_COUNTS;
/*
* if supported, Q elements with and without instruction
* counts are enabled
*/
- if ((mode & BIT(1)) && (drvdata->q_support & BIT(1)))
- config->cfg |= TRCCONFIGR_QE_WO_COUNTS;
+ if ((mode & BIT(1)) && (caps->q_support & BIT(1)))
+ sysfs_config->cfg |= TRCCONFIGR_QE_WO_COUNTS;
/* bit[11], AMBA Trace Bus (ATB) trigger enable bit */
- if ((config->mode & ETM_MODE_ATB_TRIGGER) &&
- (drvdata->atbtrig == true))
- config->eventctrl1 |= TRCEVENTCTL1R_ATB;
+ if ((sysfs_config->mode & ETM_MODE_ATB_TRIGGER) && (caps->atbtrig))
+ sysfs_config->eventctrl1 |= TRCEVENTCTL1R_ATB;
else
- config->eventctrl1 &= ~TRCEVENTCTL1R_ATB;
+ sysfs_config->eventctrl1 &= ~TRCEVENTCTL1R_ATB;
/* bit[12], Low-power state behavior override bit */
- if ((config->mode & ETM_MODE_LPOVERRIDE) &&
- (drvdata->lpoverride == true))
- config->eventctrl1 |= TRCEVENTCTL1R_LPOVERRIDE;
+ if ((sysfs_config->mode & ETM_MODE_LPOVERRIDE) && (caps->lpoverride))
+ sysfs_config->eventctrl1 |= TRCEVENTCTL1R_LPOVERRIDE;
else
- config->eventctrl1 &= ~TRCEVENTCTL1R_LPOVERRIDE;
+ sysfs_config->eventctrl1 &= ~TRCEVENTCTL1R_LPOVERRIDE;
/* bit[8], Instruction stall bit */
- if ((config->mode & ETM_MODE_ISTALL_EN) && (drvdata->stallctl == true))
- config->stall_ctrl |= TRCSTALLCTLR_ISTALL;
+ if ((sysfs_config->mode & ETM_MODE_ISTALL_EN) && (caps->stallctl))
+ sysfs_config->stall_ctrl |= TRCSTALLCTLR_ISTALL;
else
- config->stall_ctrl &= ~TRCSTALLCTLR_ISTALL;
+ sysfs_config->stall_ctrl &= ~TRCSTALLCTLR_ISTALL;
/* bit[10], Prioritize instruction trace bit */
- if (config->mode & ETM_MODE_INSTPRIO)
- config->stall_ctrl |= TRCSTALLCTLR_INSTPRIORITY;
+ if (sysfs_config->mode & ETM_MODE_INSTPRIO)
+ sysfs_config->stall_ctrl |= TRCSTALLCTLR_INSTPRIORITY;
else
- config->stall_ctrl &= ~TRCSTALLCTLR_INSTPRIORITY;
+ sysfs_config->stall_ctrl &= ~TRCSTALLCTLR_INSTPRIORITY;
/* bit[13], Trace overflow prevention bit */
- if ((config->mode & ETM_MODE_NOOVERFLOW) &&
- (drvdata->nooverflow == true))
- config->stall_ctrl |= TRCSTALLCTLR_NOOVERFLOW;
+ if ((sysfs_config->mode & ETM_MODE_NOOVERFLOW) && (caps->nooverflow))
+ sysfs_config->stall_ctrl |= TRCSTALLCTLR_NOOVERFLOW;
else
- config->stall_ctrl &= ~TRCSTALLCTLR_NOOVERFLOW;
+ sysfs_config->stall_ctrl &= ~TRCSTALLCTLR_NOOVERFLOW;
/* bit[9] Start/stop logic control bit */
- if (config->mode & ETM_MODE_VIEWINST_STARTSTOP)
- config->vinst_ctrl |= TRCVICTLR_SSSTATUS;
+ if (sysfs_config->mode & ETM_MODE_VIEWINST_STARTSTOP)
+ sysfs_config->vinst_ctrl |= TRCVICTLR_SSSTATUS;
else
- config->vinst_ctrl &= ~TRCVICTLR_SSSTATUS;
+ sysfs_config->vinst_ctrl &= ~TRCVICTLR_SSSTATUS;
/* bit[10], Whether a trace unit must trace a Reset exception */
- if (config->mode & ETM_MODE_TRACE_RESET)
- config->vinst_ctrl |= TRCVICTLR_TRCRESET;
+ if (sysfs_config->mode & ETM_MODE_TRACE_RESET)
+ sysfs_config->vinst_ctrl |= TRCVICTLR_TRCRESET;
else
- config->vinst_ctrl &= ~TRCVICTLR_TRCRESET;
+ sysfs_config->vinst_ctrl &= ~TRCVICTLR_TRCRESET;
/* bit[11], Whether a trace unit must trace a system error exception */
- if ((config->mode & ETM_MODE_TRACE_ERR) &&
- (drvdata->trc_error == true))
- config->vinst_ctrl |= TRCVICTLR_TRCERR;
+ if ((sysfs_config->mode & ETM_MODE_TRACE_ERR) && (caps->trc_error))
+ sysfs_config->vinst_ctrl |= TRCVICTLR_TRCERR;
else
- config->vinst_ctrl &= ~TRCVICTLR_TRCERR;
+ sysfs_config->vinst_ctrl &= ~TRCVICTLR_TRCERR;
- if (config->mode & (ETM_MODE_EXCL_KERN | ETM_MODE_EXCL_USER))
- etm4_config_trace_mode(config);
+ if (sysfs_config->mode & (ETM_MODE_EXCL_KERN | ETM_MODE_EXCL_USER))
+ etm4_config_trace_mode(sysfs_config, caps);
raw_spin_unlock(&drvdata->spinlock);
@@ -451,9 +457,9 @@ static ssize_t pe_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->pe_sel;
+ val = sysfs_config->pe_sel;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -463,18 +469,19 @@ static ssize_t pe_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- if (val > drvdata->nr_pe) {
+ if (val > caps->nr_pe) {
raw_spin_unlock(&drvdata->spinlock);
return -EINVAL;
}
- config->pe_sel = val;
+ sysfs_config->pe_sel = val;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -486,9 +493,9 @@ static ssize_t event_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->eventctrl0;
+ val = sysfs_config->eventctrl0;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -498,28 +505,29 @@ static ssize_t event_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- switch (drvdata->nr_event) {
+ switch (caps->nr_event) {
case 0x0:
/* EVENT0, bits[7:0] */
- config->eventctrl0 = val & 0xFF;
+ sysfs_config->eventctrl0 = val & 0xFF;
break;
case 0x1:
/* EVENT1, bits[15:8] */
- config->eventctrl0 = val & 0xFFFF;
+ sysfs_config->eventctrl0 = val & 0xFFFF;
break;
case 0x2:
/* EVENT2, bits[23:16] */
- config->eventctrl0 = val & 0xFFFFFF;
+ sysfs_config->eventctrl0 = val & 0xFFFFFF;
break;
case 0x3:
/* EVENT3, bits[31:24] */
- config->eventctrl0 = val;
+ sysfs_config->eventctrl0 = val;
break;
default:
break;
@@ -535,9 +543,9 @@ static ssize_t event_instren_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = FIELD_GET(TRCEVENTCTL1R_INSTEN_MASK, config->eventctrl1);
+ val = FIELD_GET(TRCEVENTCTL1R_INSTEN_MASK, sysfs_config->eventctrl1);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -547,32 +555,34 @@ static ssize_t event_instren_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
/* start by clearing all instruction event enable bits */
- config->eventctrl1 &= ~TRCEVENTCTL1R_INSTEN_MASK;
- switch (drvdata->nr_event) {
+ sysfs_config->eventctrl1 &= ~TRCEVENTCTL1R_INSTEN_MASK;
+ switch (caps->nr_event) {
case 0x0:
/* generate Event element for event 1 */
- config->eventctrl1 |= val & TRCEVENTCTL1R_INSTEN_1;
+ sysfs_config->eventctrl1 |= val & TRCEVENTCTL1R_INSTEN_1;
break;
case 0x1:
/* generate Event element for event 1 and 2 */
- config->eventctrl1 |= val & (TRCEVENTCTL1R_INSTEN_0 | TRCEVENTCTL1R_INSTEN_1);
+ sysfs_config->eventctrl1 |= val & (TRCEVENTCTL1R_INSTEN_0 |
+ TRCEVENTCTL1R_INSTEN_1);
break;
case 0x2:
/* generate Event element for event 1, 2 and 3 */
- config->eventctrl1 |= val & (TRCEVENTCTL1R_INSTEN_0 |
+ sysfs_config->eventctrl1 |= val & (TRCEVENTCTL1R_INSTEN_0 |
TRCEVENTCTL1R_INSTEN_1 |
TRCEVENTCTL1R_INSTEN_2);
break;
case 0x3:
/* generate Event element for all 4 events */
- config->eventctrl1 |= val & (TRCEVENTCTL1R_INSTEN_0 |
+ sysfs_config->eventctrl1 |= val & (TRCEVENTCTL1R_INSTEN_0 |
TRCEVENTCTL1R_INSTEN_1 |
TRCEVENTCTL1R_INSTEN_2 |
TRCEVENTCTL1R_INSTEN_3);
@@ -591,9 +601,9 @@ static ssize_t event_ts_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->ts_ctrl;
+ val = sysfs_config->ts_ctrl;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -603,14 +613,15 @@ static ssize_t event_ts_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
- if (!drvdata->ts_size)
+ if (!caps->ts_size)
return -EINVAL;
- config->ts_ctrl = val & ETMv4_EVENT_MASK;
+ sysfs_config->ts_ctrl = val & ETMv4_EVENT_MASK;
return size;
}
static DEVICE_ATTR_RW(event_ts);
@@ -621,9 +632,9 @@ static ssize_t syncfreq_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->syncfreq;
+ val = sysfs_config->syncfreq;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -633,14 +644,15 @@ static ssize_t syncfreq_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
- if (drvdata->syncpr == true)
+ if (caps->syncpr)
return -EINVAL;
- config->syncfreq = val & ETMv4_SYNC_MASK;
+ sysfs_config->syncfreq = val & ETMv4_SYNC_MASK;
return size;
}
static DEVICE_ATTR_RW(syncfreq);
@@ -651,9 +663,9 @@ static ssize_t cyc_threshold_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->ccctlr;
+ val = sysfs_config->ccctlr;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -663,17 +675,18 @@ static ssize_t cyc_threshold_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
/* mask off max threshold before checking min value */
val &= ETM_CYC_THRESHOLD_MASK;
- if (val < drvdata->ccitmin)
+ if (val < caps->ccitmin)
return -EINVAL;
- config->ccctlr = val;
+ sysfs_config->ccctlr = val;
return size;
}
static DEVICE_ATTR_RW(cyc_threshold);
@@ -684,9 +697,9 @@ static ssize_t bb_ctrl_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->bb_ctrl;
+ val = sysfs_config->bb_ctrl;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -696,13 +709,14 @@ static ssize_t bb_ctrl_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
- if (drvdata->trcbb == false)
+ if (!caps->trcbb)
return -EINVAL;
- if (!drvdata->nr_addr_cmp)
+ if (!caps->nr_addr_cmp)
return -EINVAL;
/*
@@ -713,7 +727,7 @@ static ssize_t bb_ctrl_store(struct device *dev,
if ((val & TRCBBCTLR_MODE) && (FIELD_GET(TRCBBCTLR_RANGE_MASK, val) == 0))
return -EINVAL;
- config->bb_ctrl = val & (TRCBBCTLR_MODE | TRCBBCTLR_RANGE_MASK);
+ sysfs_config->bb_ctrl = val & (TRCBBCTLR_MODE | TRCBBCTLR_RANGE_MASK);
return size;
}
static DEVICE_ATTR_RW(bb_ctrl);
@@ -724,9 +738,9 @@ static ssize_t event_vinst_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = FIELD_GET(TRCVICTLR_EVENT_MASK, config->vinst_ctrl);
+ val = FIELD_GET(TRCVICTLR_EVENT_MASK, sysfs_config->vinst_ctrl);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -736,15 +750,15 @@ static ssize_t event_vinst_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
val &= TRCVICTLR_EVENT_MASK >> __bf_shf(TRCVICTLR_EVENT_MASK);
- config->vinst_ctrl &= ~TRCVICTLR_EVENT_MASK;
- config->vinst_ctrl |= FIELD_PREP(TRCVICTLR_EVENT_MASK, val);
+ sysfs_config->vinst_ctrl &= ~TRCVICTLR_EVENT_MASK;
+ sysfs_config->vinst_ctrl |= FIELD_PREP(TRCVICTLR_EVENT_MASK, val);
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -756,9 +770,9 @@ static ssize_t s_exlevel_vinst_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = FIELD_GET(TRCVICTLR_EXLEVEL_S_MASK, config->vinst_ctrl);
+ val = FIELD_GET(TRCVICTLR_EXLEVEL_S_MASK, sysfs_config->vinst_ctrl);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -768,17 +782,18 @@ static ssize_t s_exlevel_vinst_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
/* clear all EXLEVEL_S bits */
- config->vinst_ctrl &= ~TRCVICTLR_EXLEVEL_S_MASK;
+ sysfs_config->vinst_ctrl &= ~TRCVICTLR_EXLEVEL_S_MASK;
/* enable instruction tracing for corresponding exception level */
- val &= drvdata->s_ex_level;
- config->vinst_ctrl |= val << __bf_shf(TRCVICTLR_EXLEVEL_S_MASK);
+ val &= caps->s_ex_level;
+ sysfs_config->vinst_ctrl |= val << __bf_shf(TRCVICTLR_EXLEVEL_S_MASK);
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -790,10 +805,10 @@ static ssize_t ns_exlevel_vinst_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
/* EXLEVEL_NS, bits[23:20] */
- val = FIELD_GET(TRCVICTLR_EXLEVEL_NS_MASK, config->vinst_ctrl);
+ val = FIELD_GET(TRCVICTLR_EXLEVEL_NS_MASK, sysfs_config->vinst_ctrl);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -803,17 +818,18 @@ static ssize_t ns_exlevel_vinst_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
/* clear EXLEVEL_NS bits */
- config->vinst_ctrl &= ~TRCVICTLR_EXLEVEL_NS_MASK;
+ sysfs_config->vinst_ctrl &= ~TRCVICTLR_EXLEVEL_NS_MASK;
/* enable instruction tracing for corresponding exception level */
- val &= drvdata->ns_ex_level;
- config->vinst_ctrl |= val << __bf_shf(TRCVICTLR_EXLEVEL_NS_MASK);
+ val &= caps->ns_ex_level;
+ sysfs_config->vinst_ctrl |= val << __bf_shf(TRCVICTLR_EXLEVEL_NS_MASK);
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -825,9 +841,9 @@ static ssize_t addr_idx_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->addr_idx;
+ val = sysfs_config->addr_idx;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -837,11 +853,12 @@ static ssize_t addr_idx_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
- if (val >= drvdata->nr_addr_cmp * 2)
+ if (val >= caps->nr_addr_cmp * 2)
return -EINVAL;
/*
@@ -849,7 +866,7 @@ static ssize_t addr_idx_store(struct device *dev,
* dereferenced multiple times within a spinlock block elsewhere.
*/
raw_spin_lock(&drvdata->spinlock);
- config->addr_idx = val;
+ sysfs_config->addr_idx = val;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -862,11 +879,11 @@ static ssize_t addr_instdatatype_show(struct device *dev,
ssize_t len;
u8 val, idx;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
- val = FIELD_GET(TRCACATRn_TYPE_MASK, config->addr_acc[idx]);
+ idx = sysfs_config->addr_idx;
+ val = FIELD_GET(TRCACATRn_TYPE_MASK, sysfs_config->addr_acc[idx]);
len = scnprintf(buf, PAGE_SIZE, "%s\n",
val == TRCACATRn_TYPE_ADDR ? "instr" :
(val == TRCACATRn_TYPE_DATA_LOAD_ADDR ? "data_load" :
@@ -883,7 +900,7 @@ static ssize_t addr_instdatatype_store(struct device *dev,
u8 idx;
char str[20] = "";
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (strlen(buf) >= 20)
return -EINVAL;
@@ -891,10 +908,10 @@ static ssize_t addr_instdatatype_store(struct device *dev,
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
+ idx = sysfs_config->addr_idx;
if (!strcmp(str, "instr"))
/* TYPE, bits[1:0] */
- config->addr_acc[idx] &= ~TRCACATRn_TYPE_MASK;
+ sysfs_config->addr_acc[idx] &= ~TRCACATRn_TYPE_MASK;
raw_spin_unlock(&drvdata->spinlock);
return size;
@@ -908,16 +925,16 @@ static ssize_t addr_single_show(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- idx = config->addr_idx;
+ idx = sysfs_config->addr_idx;
raw_spin_lock(&drvdata->spinlock);
- if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
- config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) {
+ if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) {
raw_spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- val = (unsigned long)config->addr_val[idx];
+ val = (unsigned long)sysfs_config->addr_val[idx];
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -929,21 +946,21 @@ static ssize_t addr_single_store(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
- if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
- config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) {
+ idx = sysfs_config->addr_idx;
+ if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) {
raw_spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- config->addr_val[idx] = (u64)val;
- config->addr_type[idx] = ETM_ADDR_TYPE_SINGLE;
+ sysfs_config->addr_val[idx] = (u64)val;
+ sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_SINGLE;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -956,24 +973,24 @@ static ssize_t addr_range_show(struct device *dev,
u8 idx;
unsigned long val1, val2;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
+ idx = sysfs_config->addr_idx;
if (idx % 2 != 0) {
raw_spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- if (!((config->addr_type[idx] == ETM_ADDR_TYPE_NONE &&
- config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) ||
- (config->addr_type[idx] == ETM_ADDR_TYPE_RANGE &&
- config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) {
+ if (!((sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE &&
+ sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) ||
+ (sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_RANGE &&
+ sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) {
raw_spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- val1 = (unsigned long)config->addr_val[idx];
- val2 = (unsigned long)config->addr_val[idx + 1];
+ val1 = (unsigned long)sysfs_config->addr_val[idx];
+ val2 = (unsigned long)sysfs_config->addr_val[idx + 1];
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx %#lx\n", val1, val2);
}
@@ -985,7 +1002,7 @@ static ssize_t addr_range_store(struct device *dev,
u8 idx;
unsigned long val1, val2;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
int elements, exclude;
elements = sscanf(buf, "%lx %lx %x", &val1, &val2, &exclude);
@@ -998,31 +1015,31 @@ static ssize_t addr_range_store(struct device *dev,
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
+ idx = sysfs_config->addr_idx;
if (idx % 2 != 0) {
raw_spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- if (!((config->addr_type[idx] == ETM_ADDR_TYPE_NONE &&
- config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) ||
- (config->addr_type[idx] == ETM_ADDR_TYPE_RANGE &&
- config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) {
+ if (!((sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE &&
+ sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) ||
+ (sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_RANGE &&
+ sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) {
raw_spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- config->addr_val[idx] = (u64)val1;
- config->addr_type[idx] = ETM_ADDR_TYPE_RANGE;
- config->addr_val[idx + 1] = (u64)val2;
- config->addr_type[idx + 1] = ETM_ADDR_TYPE_RANGE;
+ sysfs_config->addr_val[idx] = (u64)val1;
+ sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_RANGE;
+ sysfs_config->addr_val[idx + 1] = (u64)val2;
+ sysfs_config->addr_type[idx + 1] = ETM_ADDR_TYPE_RANGE;
/*
* Program include or exclude control bits for vinst or vdata
* whenever we change addr comparators to ETM_ADDR_TYPE_RANGE
* use supplied value, or default to bit set in 'mode'
*/
if (elements != 3)
- exclude = config->mode & ETM_MODE_EXCLUDE;
+ exclude = sysfs_config->mode & ETM_MODE_EXCLUDE;
etm4_set_mode_exclude(drvdata, exclude ? true : false);
raw_spin_unlock(&drvdata->spinlock);
@@ -1037,18 +1054,18 @@ static ssize_t addr_start_show(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
+ idx = sysfs_config->addr_idx;
- if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
- config->addr_type[idx] == ETM_ADDR_TYPE_START)) {
+ if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_START)) {
raw_spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- val = (unsigned long)config->addr_val[idx];
+ val = (unsigned long)sysfs_config->addr_val[idx];
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1060,26 +1077,27 @@ static ssize_t addr_start_store(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
- if (!drvdata->nr_addr_cmp) {
+ idx = sysfs_config->addr_idx;
+ if (!caps->nr_addr_cmp) {
raw_spin_unlock(&drvdata->spinlock);
return -EINVAL;
}
- if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
- config->addr_type[idx] == ETM_ADDR_TYPE_START)) {
+ if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_START)) {
raw_spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- config->addr_val[idx] = (u64)val;
- config->addr_type[idx] = ETM_ADDR_TYPE_START;
- config->vissctlr |= BIT(idx);
+ sysfs_config->addr_val[idx] = (u64)val;
+ sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_START;
+ sysfs_config->vissctlr |= BIT(idx);
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1092,18 +1110,18 @@ static ssize_t addr_stop_show(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
+ idx = sysfs_config->addr_idx;
- if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
- config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) {
+ if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) {
raw_spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- val = (unsigned long)config->addr_val[idx];
+ val = (unsigned long)sysfs_config->addr_val[idx];
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1115,26 +1133,27 @@ static ssize_t addr_stop_store(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
- if (!drvdata->nr_addr_cmp) {
+ idx = sysfs_config->addr_idx;
+ if (!caps->nr_addr_cmp) {
raw_spin_unlock(&drvdata->spinlock);
return -EINVAL;
}
- if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
- config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) {
+ if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE ||
+ sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) {
raw_spin_unlock(&drvdata->spinlock);
return -EPERM;
}
- config->addr_val[idx] = (u64)val;
- config->addr_type[idx] = ETM_ADDR_TYPE_STOP;
- config->vissctlr |= BIT(idx + 16);
+ sysfs_config->addr_val[idx] = (u64)val;
+ sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_STOP;
+ sysfs_config->vissctlr |= BIT(idx + 16);
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1147,12 +1166,12 @@ static ssize_t addr_ctxtype_show(struct device *dev,
ssize_t len;
u8 idx, val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
+ idx = sysfs_config->addr_idx;
/* CONTEXTTYPE, bits[3:2] */
- val = FIELD_GET(TRCACATRn_CONTEXTTYPE_MASK, config->addr_acc[idx]);
+ val = FIELD_GET(TRCACATRn_CONTEXTTYPE_MASK, sysfs_config->addr_acc[idx]);
len = scnprintf(buf, PAGE_SIZE, "%s\n", val == ETM_CTX_NONE ? "none" :
(val == ETM_CTX_CTXID ? "ctxid" :
(val == ETM_CTX_VMID ? "vmid" : "all")));
@@ -1167,7 +1186,8 @@ static ssize_t addr_ctxtype_store(struct device *dev,
u8 idx;
char str[10] = "";
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (strlen(buf) >= 10)
return -EINVAL;
@@ -1175,31 +1195,31 @@ static ssize_t addr_ctxtype_store(struct device *dev,
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
+ idx = sysfs_config->addr_idx;
if (!strcmp(str, "none"))
/* start by clearing context type bits */
- config->addr_acc[idx] &= ~TRCACATRn_CONTEXTTYPE_MASK;
+ sysfs_config->addr_acc[idx] &= ~TRCACATRn_CONTEXTTYPE_MASK;
else if (!strcmp(str, "ctxid")) {
/* 0b01 The trace unit performs a Context ID */
- if (drvdata->numcidc) {
- config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_CTXID;
- config->addr_acc[idx] &= ~TRCACATRn_CONTEXTTYPE_VMID;
+ if (caps->numcidc) {
+ sysfs_config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_CTXID;
+ sysfs_config->addr_acc[idx] &= ~TRCACATRn_CONTEXTTYPE_VMID;
}
} else if (!strcmp(str, "vmid")) {
/* 0b10 The trace unit performs a VMID */
- if (drvdata->numvmidc) {
- config->addr_acc[idx] &= ~TRCACATRn_CONTEXTTYPE_CTXID;
- config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_VMID;
+ if (caps->numvmidc) {
+ sysfs_config->addr_acc[idx] &= ~TRCACATRn_CONTEXTTYPE_CTXID;
+ sysfs_config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_VMID;
}
} else if (!strcmp(str, "all")) {
/*
* 0b11 The trace unit performs a Context ID
* comparison and a VMID
*/
- if (drvdata->numcidc)
- config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_CTXID;
- if (drvdata->numvmidc)
- config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_VMID;
+ if (caps->numcidc)
+ sysfs_config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_CTXID;
+ if (caps->numvmidc)
+ sysfs_config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_VMID;
}
raw_spin_unlock(&drvdata->spinlock);
return size;
@@ -1213,12 +1233,12 @@ static ssize_t addr_context_show(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
+ idx = sysfs_config->addr_idx;
/* context ID comparator bits[6:4] */
- val = FIELD_GET(TRCACATRn_CONTEXT_MASK, config->addr_acc[idx]);
+ val = FIELD_GET(TRCACATRn_CONTEXT_MASK, sysfs_config->addr_acc[idx]);
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1230,21 +1250,22 @@ static ssize_t addr_context_store(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
- if ((drvdata->numcidc <= 1) && (drvdata->numvmidc <= 1))
+ if ((caps->numcidc <= 1) && (caps->numvmidc <= 1))
return -EINVAL;
- if (val >= (drvdata->numcidc >= drvdata->numvmidc ?
- drvdata->numcidc : drvdata->numvmidc))
+ if (val >= (caps->numcidc >= caps->numvmidc ?
+ caps->numcidc : caps->numvmidc))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
+ idx = sysfs_config->addr_idx;
/* clear context ID comparator bits[6:4] */
- config->addr_acc[idx] &= ~TRCACATRn_CONTEXT_MASK;
- config->addr_acc[idx] |= val << __bf_shf(TRCACATRn_CONTEXT_MASK);
+ sysfs_config->addr_acc[idx] &= ~TRCACATRn_CONTEXT_MASK;
+ sysfs_config->addr_acc[idx] |= val << __bf_shf(TRCACATRn_CONTEXT_MASK);
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1257,11 +1278,11 @@ static ssize_t addr_exlevel_s_ns_show(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
- val = FIELD_GET(TRCACATRn_EXLEVEL_MASK, config->addr_acc[idx]);
+ idx = sysfs_config->addr_idx;
+ val = FIELD_GET(TRCACATRn_EXLEVEL_MASK, sysfs_config->addr_acc[idx]);
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1273,7 +1294,7 @@ static ssize_t addr_exlevel_s_ns_store(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 0, &val))
return -EINVAL;
@@ -1282,10 +1303,10 @@ static ssize_t addr_exlevel_s_ns_store(struct device *dev,
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
+ idx = sysfs_config->addr_idx;
/* clear Exlevel_ns & Exlevel_s bits[14:12, 11:8], bit[15] is res0 */
- config->addr_acc[idx] &= ~TRCACATRn_EXLEVEL_MASK;
- config->addr_acc[idx] |= val << __bf_shf(TRCACATRn_EXLEVEL_MASK);
+ sysfs_config->addr_acc[idx] &= ~TRCACATRn_EXLEVEL_MASK;
+ sysfs_config->addr_acc[idx] |= val << __bf_shf(TRCACATRn_EXLEVEL_MASK);
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1305,24 +1326,24 @@ static ssize_t addr_cmp_view_show(struct device *dev,
u8 idx, addr_type;
unsigned long addr_v, addr_v2, addr_ctrl;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
int size = 0;
bool exclude = false;
raw_spin_lock(&drvdata->spinlock);
- idx = config->addr_idx;
- addr_v = config->addr_val[idx];
- addr_ctrl = config->addr_acc[idx];
- addr_type = config->addr_type[idx];
+ idx = sysfs_config->addr_idx;
+ addr_v = sysfs_config->addr_val[idx];
+ addr_ctrl = sysfs_config->addr_acc[idx];
+ addr_type = sysfs_config->addr_type[idx];
if (addr_type == ETM_ADDR_TYPE_RANGE) {
if (idx & 0x1) {
idx -= 1;
addr_v2 = addr_v;
- addr_v = config->addr_val[idx];
+ addr_v = sysfs_config->addr_val[idx];
} else {
- addr_v2 = config->addr_val[idx + 1];
+ addr_v2 = sysfs_config->addr_val[idx + 1];
}
- exclude = config->viiectlr & BIT(idx / 2 + 16);
+ exclude = sysfs_config->viiectlr & BIT(idx / 2 + 16);
}
raw_spin_unlock(&drvdata->spinlock);
if (addr_type) {
@@ -1348,11 +1369,12 @@ static ssize_t vinst_pe_cmp_start_stop_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- if (!drvdata->nr_pe_cmp)
+ if (!caps->nr_pe_cmp)
return -EINVAL;
- val = config->vipcssctlr;
+ val = sysfs_config->vipcssctlr;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
static ssize_t vinst_pe_cmp_start_stop_store(struct device *dev,
@@ -1361,15 +1383,16 @@ static ssize_t vinst_pe_cmp_start_stop_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
- if (!drvdata->nr_pe_cmp)
+ if (!caps->nr_pe_cmp)
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- config->vipcssctlr = val;
+ sysfs_config->vipcssctlr = val;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1381,9 +1404,9 @@ static ssize_t seq_idx_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->seq_idx;
+ val = sysfs_config->seq_idx;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1393,13 +1416,14 @@ static ssize_t seq_idx_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- if (!drvdata->nr_seq_ctrls)
+ if (!caps->nr_seq_ctrls)
return -ENOTSUPP;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
- if (val >= drvdata->nr_seq_ctrls)
+ if (val >= caps->nr_seq_ctrls)
return -EINVAL;
/*
@@ -1407,7 +1431,7 @@ static ssize_t seq_idx_store(struct device *dev,
* dereferenced multiple times within a spinlock block elsewhere.
*/
raw_spin_lock(&drvdata->spinlock);
- config->seq_idx = val;
+ sysfs_config->seq_idx = val;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1419,9 +1443,9 @@ static ssize_t seq_state_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->seq_state;
+ val = sysfs_config->seq_state;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1431,14 +1455,18 @@ static ssize_t seq_state_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
- if (val >= drvdata->nrseqstate)
+ if (IS_ERR_OR_NULL(drvdata->csdev) ||
+ coresight_get_mode(drvdata->csdev) == CS_MODE_SYSFS)
+ return -EBUSY;
+ if (val >= caps->nrseqstate)
return -EINVAL;
- config->seq_state = val;
+ sysfs_config->seq_state = val;
return size;
}
static DEVICE_ATTR_RW(seq_state);
@@ -1450,11 +1478,11 @@ static ssize_t seq_event_show(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
raw_spin_lock(&drvdata->spinlock);
- idx = config->seq_idx;
- val = config->seq_ctrl[idx];
+ idx = sysfs_config->seq_idx;
+ val = sysfs_config->seq_ctrl[idx];
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1466,15 +1494,15 @@ static ssize_t seq_event_store(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->seq_idx;
+ idx = sysfs_config->seq_idx;
/* Seq control has two masks B[15:8] F[7:0] */
- config->seq_ctrl[idx] = val & 0xFFFF;
+ sysfs_config->seq_ctrl[idx] = val & 0xFFFF;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1486,9 +1514,9 @@ static ssize_t seq_reset_event_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->seq_rst;
+ val = sysfs_config->seq_rst;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1498,14 +1526,15 @@ static ssize_t seq_reset_event_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
- if (!(drvdata->nrseqstate))
+ if (!(caps->nrseqstate))
return -EINVAL;
- config->seq_rst = val & ETMv4_EVENT_MASK;
+ sysfs_config->seq_rst = val & ETMv4_EVENT_MASK;
return size;
}
static DEVICE_ATTR_RW(seq_reset_event);
@@ -1516,9 +1545,9 @@ static ssize_t cntr_idx_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->cntr_idx;
+ val = sysfs_config->cntr_idx;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1528,11 +1557,12 @@ static ssize_t cntr_idx_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
- if (val >= drvdata->nr_cntr)
+ if (val >= caps->nr_cntr)
return -EINVAL;
/*
@@ -1540,7 +1570,7 @@ static ssize_t cntr_idx_store(struct device *dev,
* dereferenced multiple times within a spinlock block elsewhere.
*/
raw_spin_lock(&drvdata->spinlock);
- config->cntr_idx = val;
+ sysfs_config->cntr_idx = val;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1553,11 +1583,11 @@ static ssize_t cntrldvr_show(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
raw_spin_lock(&drvdata->spinlock);
- idx = config->cntr_idx;
- val = config->cntrldvr[idx];
+ idx = sysfs_config->cntr_idx;
+ val = sysfs_config->cntrldvr[idx];
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1569,7 +1599,7 @@ static ssize_t cntrldvr_store(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
@@ -1577,8 +1607,8 @@ static ssize_t cntrldvr_store(struct device *dev,
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->cntr_idx;
- config->cntrldvr[idx] = val;
+ idx = sysfs_config->cntr_idx;
+ sysfs_config->cntrldvr[idx] = val;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1591,11 +1621,11 @@ static ssize_t cntr_val_show(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
raw_spin_lock(&drvdata->spinlock);
- idx = config->cntr_idx;
- val = config->cntr_val[idx];
+ idx = sysfs_config->cntr_idx;
+ val = sysfs_config->cntr_val[idx];
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1607,16 +1637,19 @@ static ssize_t cntr_val_store(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
if (val > ETM_CNTR_MAX_VAL)
return -EINVAL;
+ if (IS_ERR_OR_NULL(drvdata->csdev) ||
+ coresight_get_mode(drvdata->csdev) == CS_MODE_SYSFS)
+ return -EBUSY;
raw_spin_lock(&drvdata->spinlock);
- idx = config->cntr_idx;
- config->cntr_val[idx] = val;
+ idx = sysfs_config->cntr_idx;
+ sysfs_config->cntr_val[idx] = val;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1629,11 +1662,11 @@ static ssize_t cntr_ctrl_show(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
raw_spin_lock(&drvdata->spinlock);
- idx = config->cntr_idx;
- val = config->cntr_ctrl[idx];
+ idx = sysfs_config->cntr_idx;
+ val = sysfs_config->cntr_ctrl[idx];
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1645,14 +1678,14 @@ static ssize_t cntr_ctrl_store(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->cntr_idx;
- config->cntr_ctrl[idx] = val;
+ idx = sysfs_config->cntr_idx;
+ sysfs_config->cntr_ctrl[idx] = val;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1664,9 +1697,9 @@ static ssize_t res_idx_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->res_idx;
+ val = sysfs_config->res_idx;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1676,7 +1709,8 @@ static ssize_t res_idx_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
@@ -1684,7 +1718,7 @@ static ssize_t res_idx_store(struct device *dev,
* Resource selector pair 0 is always implemented and reserved,
* namely an idx with 0 and 1 is illegal.
*/
- if ((val < 2) || (val >= 2 * drvdata->nr_resource))
+ if ((val < 2) || (val >= 2 * caps->nr_resource))
return -EINVAL;
/*
@@ -1692,7 +1726,7 @@ static ssize_t res_idx_store(struct device *dev,
* dereferenced multiple times within a spinlock block elsewhere.
*/
raw_spin_lock(&drvdata->spinlock);
- config->res_idx = val;
+ sysfs_config->res_idx = val;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1705,11 +1739,11 @@ static ssize_t res_ctrl_show(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
raw_spin_lock(&drvdata->spinlock);
- idx = config->res_idx;
- val = config->res_ctrl[idx];
+ idx = sysfs_config->res_idx;
+ val = sysfs_config->res_ctrl[idx];
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1721,18 +1755,18 @@ static ssize_t res_ctrl_store(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->res_idx;
+ idx = sysfs_config->res_idx;
/* For odd idx pair inversal bit is RES0 */
if (idx % 2 != 0)
/* PAIRINV, bit[21] */
val &= ~TRCRSCTLRn_PAIRINV;
- config->res_ctrl[idx] = val & (TRCRSCTLRn_PAIRINV |
+ sysfs_config->res_ctrl[idx] = val & (TRCRSCTLRn_PAIRINV |
TRCRSCTLRn_INV |
TRCRSCTLRn_GROUP_MASK |
TRCRSCTLRn_SELECT_MASK);
@@ -1746,9 +1780,9 @@ static ssize_t sshot_idx_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->ss_idx;
+ val = sysfs_config->ss_idx;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1758,15 +1792,16 @@ static ssize_t sshot_idx_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
- if (val >= drvdata->nr_ss_cmp)
+ if (val >= caps->nr_ss_cmp)
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- config->ss_idx = val;
+ sysfs_config->ss_idx = val;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1778,10 +1813,10 @@ static ssize_t sshot_ctrl_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
raw_spin_lock(&drvdata->spinlock);
- val = config->ss_ctrl[config->ss_idx];
+ val = sysfs_config->ss_ctrl[sysfs_config->ss_idx];
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1793,16 +1828,16 @@ static ssize_t sshot_ctrl_store(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->ss_idx;
- config->ss_ctrl[idx] = FIELD_PREP(TRCSSCCRn_SAC_ARC_RST_MASK, val);
- /* must clear bit 31 in related status register on programming */
- config->ss_status[idx] &= ~TRCSSCSRn_STATUS;
+ idx = sysfs_config->ss_idx;
+ sysfs_config->ss_ctrl[idx] = FIELD_PREP(TRCSSCCRn_SAC_ARC_RST_MASK, val);
+ /* must clear bit 31 and 30 in related status register on programming */
+ drvdata->ss_status[idx] &= ~(TRCSSCSRn_STATUS | TRCSSCSRn_PENDING);
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1813,10 +1848,10 @@ static ssize_t sshot_status_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
raw_spin_lock(&drvdata->spinlock);
- val = config->ss_status[config->ss_idx];
+ val = drvdata->ss_status[sysfs_config->ss_idx];
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1828,10 +1863,10 @@ static ssize_t sshot_pe_ctrl_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
raw_spin_lock(&drvdata->spinlock);
- val = config->ss_pe_cmp[config->ss_idx];
+ val = sysfs_config->ss_pe_cmp[sysfs_config->ss_idx];
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1843,16 +1878,16 @@ static ssize_t sshot_pe_ctrl_store(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->ss_idx;
- config->ss_pe_cmp[idx] = FIELD_PREP(TRCSSPCICRn_PC_MASK, val);
- /* must clear bit 31 in related status register on programming */
- config->ss_status[idx] &= ~TRCSSCSRn_STATUS;
+ idx = sysfs_config->ss_idx;
+ sysfs_config->ss_pe_cmp[idx] = FIELD_PREP(TRCSSPCICRn_PC_MASK, val);
+ /* must clear bit 31 and 30 in related status register on programming */
+ drvdata->ss_status[idx] &= ~(TRCSSCSRn_STATUS | TRCSSCSRn_PENDING);
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1864,9 +1899,9 @@ static ssize_t ctxid_idx_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->ctxid_idx;
+ val = sysfs_config->ctxid_idx;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1876,11 +1911,12 @@ static ssize_t ctxid_idx_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
- if (val >= drvdata->numcidc)
+ if (val >= caps->numcidc)
return -EINVAL;
/*
@@ -1888,7 +1924,7 @@ static ssize_t ctxid_idx_store(struct device *dev,
* dereferenced multiple times within a spinlock block elsewhere.
*/
raw_spin_lock(&drvdata->spinlock);
- config->ctxid_idx = val;
+ sysfs_config->ctxid_idx = val;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1901,7 +1937,7 @@ static ssize_t ctxid_pid_show(struct device *dev,
u8 idx;
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
/*
* Don't use contextID tracing if coming from a PID namespace. See
@@ -1911,8 +1947,8 @@ static ssize_t ctxid_pid_show(struct device *dev,
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->ctxid_idx;
- val = (unsigned long)config->ctxid_pid[idx];
+ idx = sysfs_config->ctxid_idx;
+ val = (unsigned long)sysfs_config->ctxid_pid[idx];
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -1924,7 +1960,8 @@ static ssize_t ctxid_pid_store(struct device *dev,
u8 idx;
unsigned long pid;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
/*
* When contextID tracing is enabled the tracers will insert the
@@ -1943,14 +1980,14 @@ static ssize_t ctxid_pid_store(struct device *dev,
* ctxid comparator is implemented and ctxid is greater than 0 bits
* in length
*/
- if (!drvdata->ctxid_size || !drvdata->numcidc)
+ if (!caps->ctxid_size || !caps->numcidc)
return -EINVAL;
if (kstrtoul(buf, 16, &pid))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- idx = config->ctxid_idx;
- config->ctxid_pid[idx] = (u64)pid;
+ idx = sysfs_config->ctxid_idx;
+ sysfs_config->ctxid_pid[idx] = (u64)pid;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -1962,7 +1999,7 @@ static ssize_t ctxid_masks_show(struct device *dev,
{
unsigned long val1, val2;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
/*
* Don't use contextID tracing if coming from a PID namespace. See
@@ -1972,8 +2009,8 @@ static ssize_t ctxid_masks_show(struct device *dev,
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- val1 = config->ctxid_mask0;
- val2 = config->ctxid_mask1;
+ val1 = sysfs_config->ctxid_mask0;
+ val2 = sysfs_config->ctxid_mask1;
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx %#lx\n", val1, val2);
}
@@ -1985,7 +2022,8 @@ static ssize_t ctxid_masks_store(struct device *dev,
u8 i, j, maskbyte;
unsigned long val1, val2, mask;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
int nr_inputs;
/*
@@ -2000,11 +2038,11 @@ static ssize_t ctxid_masks_store(struct device *dev,
* ctxid comparator is implemented and ctxid is greater than 0 bits
* in length
*/
- if (!drvdata->ctxid_size || !drvdata->numcidc)
+ if (!caps->ctxid_size || !caps->numcidc)
return -EINVAL;
/* one mask if <= 4 comparators, two for up to 8 */
nr_inputs = sscanf(buf, "%lx %lx", &val1, &val2);
- if ((drvdata->numcidc > 4) && (nr_inputs != 2))
+ if ((caps->numcidc > 4) && (nr_inputs != 2))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
@@ -2012,42 +2050,42 @@ static ssize_t ctxid_masks_store(struct device *dev,
* each byte[0..3] controls mask value applied to ctxid
* comparator[0..3]
*/
- switch (drvdata->numcidc) {
+ switch (caps->numcidc) {
case 0x1:
/* COMP0, bits[7:0] */
- config->ctxid_mask0 = val1 & 0xFF;
+ sysfs_config->ctxid_mask0 = val1 & 0xFF;
break;
case 0x2:
/* COMP1, bits[15:8] */
- config->ctxid_mask0 = val1 & 0xFFFF;
+ sysfs_config->ctxid_mask0 = val1 & 0xFFFF;
break;
case 0x3:
/* COMP2, bits[23:16] */
- config->ctxid_mask0 = val1 & 0xFFFFFF;
+ sysfs_config->ctxid_mask0 = val1 & 0xFFFFFF;
break;
case 0x4:
/* COMP3, bits[31:24] */
- config->ctxid_mask0 = val1;
+ sysfs_config->ctxid_mask0 = val1;
break;
case 0x5:
/* COMP4, bits[7:0] */
- config->ctxid_mask0 = val1;
- config->ctxid_mask1 = val2 & 0xFF;
+ sysfs_config->ctxid_mask0 = val1;
+ sysfs_config->ctxid_mask1 = val2 & 0xFF;
break;
case 0x6:
/* COMP5, bits[15:8] */
- config->ctxid_mask0 = val1;
- config->ctxid_mask1 = val2 & 0xFFFF;
+ sysfs_config->ctxid_mask0 = val1;
+ sysfs_config->ctxid_mask1 = val2 & 0xFFFF;
break;
case 0x7:
/* COMP6, bits[23:16] */
- config->ctxid_mask0 = val1;
- config->ctxid_mask1 = val2 & 0xFFFFFF;
+ sysfs_config->ctxid_mask0 = val1;
+ sysfs_config->ctxid_mask1 = val2 & 0xFFFFFF;
break;
case 0x8:
/* COMP7, bits[31:24] */
- config->ctxid_mask0 = val1;
- config->ctxid_mask1 = val2;
+ sysfs_config->ctxid_mask0 = val1;
+ sysfs_config->ctxid_mask1 = val2;
break;
default:
break;
@@ -2058,8 +2096,8 @@ static ssize_t ctxid_masks_store(struct device *dev,
* For example, if bit[3] of ctxid_mask0 is 1, we must clear bits[31:24]
* of ctxid comparator0 value (corresponding to byte 0) register.
*/
- mask = config->ctxid_mask0;
- for (i = 0; i < drvdata->numcidc; i++) {
+ mask = sysfs_config->ctxid_mask0;
+ for (i = 0; i < caps->numcidc; i++) {
/* mask value of corresponding ctxid comparator */
maskbyte = mask & ETMv4_EVENT_MASK;
/*
@@ -2068,13 +2106,13 @@ static ssize_t ctxid_masks_store(struct device *dev,
*/
for (j = 0; j < 8; j++) {
if (maskbyte & 1)
- config->ctxid_pid[i] &= ~(0xFFUL << (j * 8));
+ sysfs_config->ctxid_pid[i] &= ~(0xFFUL << (j * 8));
maskbyte >>= 1;
}
/* Select the next ctxid comparator mask value */
if (i == 3)
/* ctxid comparators[4-7] */
- mask = config->ctxid_mask1;
+ mask = sysfs_config->ctxid_mask1;
else
mask >>= 0x8;
}
@@ -2090,9 +2128,9 @@ static ssize_t vmid_idx_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
- val = config->vmid_idx;
+ val = sysfs_config->vmid_idx;
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -2102,11 +2140,12 @@ static ssize_t vmid_idx_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
- if (val >= drvdata->numvmidc)
+ if (val >= caps->numvmidc)
return -EINVAL;
/*
@@ -2114,7 +2153,7 @@ static ssize_t vmid_idx_store(struct device *dev,
* dereferenced multiple times within a spinlock block elsewhere.
*/
raw_spin_lock(&drvdata->spinlock);
- config->vmid_idx = val;
+ sysfs_config->vmid_idx = val;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -2126,7 +2165,7 @@ static ssize_t vmid_val_show(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
/*
* Don't use virtual contextID tracing if coming from a PID namespace.
@@ -2136,7 +2175,7 @@ static ssize_t vmid_val_show(struct device *dev,
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- val = (unsigned long)config->vmid_val[config->vmid_idx];
+ val = (unsigned long)sysfs_config->vmid_val[sysfs_config->vmid_idx];
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
}
@@ -2147,7 +2186,8 @@ static ssize_t vmid_val_store(struct device *dev,
{
unsigned long val;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
/*
* Don't use virtual contextID tracing if coming from a PID namespace.
@@ -2160,13 +2200,13 @@ static ssize_t vmid_val_store(struct device *dev,
* only implemented when vmid tracing is enabled, i.e. at least one
* vmid comparator is implemented and at least 8 bit vmid size
*/
- if (!drvdata->vmid_size || !drvdata->numvmidc)
+ if (!caps->vmid_size || !caps->numvmidc)
return -EINVAL;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- config->vmid_val[config->vmid_idx] = (u64)val;
+ sysfs_config->vmid_val[sysfs_config->vmid_idx] = (u64)val;
raw_spin_unlock(&drvdata->spinlock);
return size;
}
@@ -2177,7 +2217,7 @@ static ssize_t vmid_masks_show(struct device *dev,
{
unsigned long val1, val2;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
/*
* Don't use virtual contextID tracing if coming from a PID namespace.
@@ -2187,8 +2227,8 @@ static ssize_t vmid_masks_show(struct device *dev,
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
- val1 = config->vmid_mask0;
- val2 = config->vmid_mask1;
+ val1 = sysfs_config->vmid_mask0;
+ val2 = sysfs_config->vmid_mask1;
raw_spin_unlock(&drvdata->spinlock);
return scnprintf(buf, PAGE_SIZE, "%#lx %#lx\n", val1, val2);
}
@@ -2200,7 +2240,8 @@ static ssize_t vmid_masks_store(struct device *dev,
u8 i, j, maskbyte;
unsigned long val1, val2, mask;
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
- struct etmv4_config *config = &drvdata->config;
+ const struct etmv4_caps *caps = &drvdata->caps;
+ struct etmv4_config *sysfs_config = &drvdata->sysfs_config;
int nr_inputs;
/*
@@ -2214,11 +2255,11 @@ static ssize_t vmid_masks_store(struct device *dev,
* only implemented when vmid tracing is enabled, i.e. at least one
* vmid comparator is implemented and at least 8 bit vmid size
*/
- if (!drvdata->vmid_size || !drvdata->numvmidc)
+ if (!caps->vmid_size || !caps->numvmidc)
return -EINVAL;
/* one mask if <= 4 comparators, two for up to 8 */
nr_inputs = sscanf(buf, "%lx %lx", &val1, &val2);
- if ((drvdata->numvmidc > 4) && (nr_inputs != 2))
+ if ((caps->numvmidc > 4) && (nr_inputs != 2))
return -EINVAL;
raw_spin_lock(&drvdata->spinlock);
@@ -2227,42 +2268,42 @@ static ssize_t vmid_masks_store(struct device *dev,
* each byte[0..3] controls mask value applied to vmid
* comparator[0..3]
*/
- switch (drvdata->numvmidc) {
+ switch (caps->numvmidc) {
case 0x1:
/* COMP0, bits[7:0] */
- config->vmid_mask0 = val1 & 0xFF;
+ sysfs_config->vmid_mask0 = val1 & 0xFF;
break;
case 0x2:
/* COMP1, bits[15:8] */
- config->vmid_mask0 = val1 & 0xFFFF;
+ sysfs_config->vmid_mask0 = val1 & 0xFFFF;
break;
case 0x3:
/* COMP2, bits[23:16] */
- config->vmid_mask0 = val1 & 0xFFFFFF;
+ sysfs_config->vmid_mask0 = val1 & 0xFFFFFF;
break;
case 0x4:
/* COMP3, bits[31:24] */
- config->vmid_mask0 = val1;
+ sysfs_config->vmid_mask0 = val1;
break;
case 0x5:
/* COMP4, bits[7:0] */
- config->vmid_mask0 = val1;
- config->vmid_mask1 = val2 & 0xFF;
+ sysfs_config->vmid_mask0 = val1;
+ sysfs_config->vmid_mask1 = val2 & 0xFF;
break;
case 0x6:
/* COMP5, bits[15:8] */
- config->vmid_mask0 = val1;
- config->vmid_mask1 = val2 & 0xFFFF;
+ sysfs_config->vmid_mask0 = val1;
+ sysfs_config->vmid_mask1 = val2 & 0xFFFF;
break;
case 0x7:
/* COMP6, bits[23:16] */
- config->vmid_mask0 = val1;
- config->vmid_mask1 = val2 & 0xFFFFFF;
+ sysfs_config->vmid_mask0 = val1;
+ sysfs_config->vmid_mask1 = val2 & 0xFFFFFF;
break;
case 0x8:
/* COMP7, bits[31:24] */
- config->vmid_mask0 = val1;
- config->vmid_mask1 = val2;
+ sysfs_config->vmid_mask0 = val1;
+ sysfs_config->vmid_mask1 = val2;
break;
default:
break;
@@ -2274,8 +2315,8 @@ static ssize_t vmid_masks_store(struct device *dev,
* For example, if bit[3] of vmid_mask0 is 1, we must clear bits[31:24]
* of vmid comparator0 value (corresponding to byte 0) register.
*/
- mask = config->vmid_mask0;
- for (i = 0; i < drvdata->numvmidc; i++) {
+ mask = sysfs_config->vmid_mask0;
+ for (i = 0; i < caps->numvmidc; i++) {
/* mask value of corresponding vmid comparator */
maskbyte = mask & ETMv4_EVENT_MASK;
/*
@@ -2284,13 +2325,13 @@ static ssize_t vmid_masks_store(struct device *dev,
*/
for (j = 0; j < 8; j++) {
if (maskbyte & 1)
- config->vmid_val[i] &= ~(0xFFUL << (j * 8));
+ sysfs_config->vmid_val[i] &= ~(0xFFUL << (j * 8));
maskbyte >>= 1;
}
/* Select the next vmid comparator mask value */
if (i == 3)
/* vmid comparators[4-7] */
- mask = config->vmid_mask1;
+ mask = sysfs_config->vmid_mask1;
else
mask >>= 0x8;
}
diff --git a/drivers/hwtracing/coresight/coresight-etm4x.h b/drivers/hwtracing/coresight/coresight-etm4x.h
index df9e0748d71e..5417280cdb27 100644
--- a/drivers/hwtracing/coresight/coresight-etm4x.h
+++ b/drivers/hwtracing/coresight/coresight-etm4x.h
@@ -213,6 +213,11 @@
#define TRCACATRn_EXLEVEL_MASK GENMASK(14, 8)
#define TRCSSCSRn_STATUS BIT(31)
+/*
+ * TRCSSCSR.PENDING is only implemented by ETE and is therefore not
+ * described in DEN0117. See Arm ARM (DDI0487) M.c, section D24.4.59.
+ */
+#define TRCSSCSRn_PENDING BIT(30)
#define TRCSSCCRn_SAC_ARC_RST_MASK GENMASK(24, 0)
#define TRCSSPCICRn_PC_MASK GENMASK(7, 0)
@@ -814,14 +819,105 @@ enum etm_impdef_type {
};
/**
+ * struct etmv4_caps - specifics ETM capabilities
+ * @nr_pe: The number of processing entity available for tracing.
+ * @nr_pe_cmp: The number of processing entity comparator inputs that are
+ * available for tracing.
+ * @nr_addr_cmp:Number of pairs of address comparators available
+ * as found in ETMIDR4 0-3.
+ * @nr_cntr: Number of counters as found in ETMIDR5 bit 28-30.
+ * @nr_ext_inp: Number of external input.
+ * @numcidc: Number of contextID comparators.
+ * @numextinsel: Number of external input selector resources.
+ * @numvmidc: Number of VMID comparators.
+ * @nrseqstate: The number of sequencer states that are implemented.
+ * @nr_seq_ctrls: The number of sequence state transition control registers.
+ * @nr_event: Indicates how many events the trace unit support.
+ * @nr_resource:The number of resource selection pairs available for tracing.
+ * @nr_ss_cmp: Number of single-shot comparator controls that are available.
+ * @trcid_size: Indicates the trace ID width.
+ * @ts_size: Global timestamp size field.
+ * @ctxid_size: Size of the context ID field to consider.
+ * @vmid_size: Size of the VM ID comparator to consider.
+ * @ccsize: Indicates the size of the cycle counter in bits.
+ * @ccitmin: minimum value that can be programmed in
+ * @s_ex_level: In secure state, indicates whether instruction tracing is
+ * supported for the corresponding Exception level.
+ * @ns_ex_level:In non-secure state, indicates whether instruction tracing is
+ * supported for the corresponding Exception level.
+ * @q_support: Q element support characteristics.
+ * @os_lock_model: OSLock model.
+ * @instrp0: Tracing of load and store instructions
+ * as P0 elements is supported.
+ * @q_filt: Q element filtering support, if Q elements are supported.
+ * @trcbb: Indicates if the trace unit supports branch broadcast tracing.
+ * @trccond: If the trace unit supports conditional
+ * instruction tracing.
+ * @retstack: Indicates if the implementation supports a return stack.
+ * @trccci: Indicates if the trace unit supports cycle counting
+ * for instruction.
+ * @trc_error: Whether a trace unit can trace a system
+ * error exception.
+ * @syncpr: Indicates if an implementation has a fixed
+ * synchronization period.
+ * @stallctl: If functionality that prevents trace unit buffer overflows
+ * is available.
+ * @sysstall: Does the system support stall control of the PE?
+ * @nooverflow: Indicate if overflow prevention is supported.
+ * @atbtrig: If the implementation can support ATB triggers
+ * @lpoverride: If the implementation can support low-power state over.
+ * @skip_power_up: Indicates if an implementation can skip powering up
+ * the trace unit.
+ */
+struct etmv4_caps {
+ u8 nr_pe;
+ u8 nr_pe_cmp;
+ u8 nr_addr_cmp;
+ u8 nr_cntr;
+ u8 nr_ext_inp;
+ u8 numcidc;
+ u8 numextinsel;
+ u8 numvmidc;
+ u8 nrseqstate;
+ u8 nr_seq_ctrls;
+ u8 nr_event;
+ u8 nr_resource;
+ u8 nr_ss_cmp;
+ u8 trcid_size;
+ u8 ts_size;
+ u8 ctxid_size;
+ u8 vmid_size;
+ u8 ccsize;
+ u16 ccitmin;
+ u8 s_ex_level;
+ u8 ns_ex_level;
+ u8 q_support;
+ u8 os_lock_model;
+ bool instrp0 : 1;
+ bool q_filt : 1;
+ bool trcbb : 1;
+ bool trccond : 1;
+ bool retstack : 1;
+ bool trccci : 1;
+ bool trc_error : 1;
+ bool syncpr : 1;
+ bool stallctl : 1;
+ bool sysstall : 1;
+ bool nooverflow : 1;
+ bool atbtrig : 1;
+ bool lpoverride : 1;
+ bool skip_power_up : 1;
+};
+
+/**
* struct etmv4_config - configuration information related to an ETMv4
* @mode: Controls various modes supported by this ETM.
* @pe_sel: Controls which PE to trace.
* @cfg: Controls the tracing options.
* @eventctrl0: Controls the tracing of arbitrary events.
* @eventctrl1: Controls the behavior of the events that @event_ctrl0 selects.
- * @stallctl: If functionality that prevents trace unit buffer overflows
- * is available.
+ * @stall_ctrl: Enables trace unit functionality that prevents trace
+ * unit buffer overflows.
* @ts_ctrl: Controls the insertion of global timestamps in the
* trace streams.
* @syncfreq: Controls how often trace synchronization requests occur.
@@ -860,7 +956,6 @@ enum etm_impdef_type {
* @vmid_mask0: VM ID comparator mask for comparator 0-3.
* @vmid_mask1: VM ID comparator mask for comparator 4-7.
* @ext_inp: External input selection.
- * @s_ex_level: Secure ELs where tracing is supported.
*/
struct etmv4_config {
u64 mode;
@@ -889,7 +984,6 @@ struct etmv4_config {
u32 res_ctrl[ETM_MAX_RES_SEL]; /* TRCRSCTLRn */
u8 ss_idx;
u32 ss_ctrl[ETM_MAX_SS_CMP];
- u32 ss_status[ETM_MAX_SS_CMP];
u32 ss_pe_cmp[ETM_MAX_SS_CMP];
u8 addr_idx;
u64 addr_val[ETM_MAX_SINGLE_ADDR_CMP];
@@ -904,7 +998,6 @@ struct etmv4_config {
u32 vmid_mask0;
u32 vmid_mask1;
u32 ext_inp;
- u8 s_ex_level;
};
/**
@@ -967,63 +1060,20 @@ struct etmv4_save_state {
* @mode: This tracer's mode, i.e sysFS, Perf or disabled.
* @cpu: The cpu this component is affined to.
* @arch: ETM architecture version.
- * @nr_pe: The number of processing entity available for tracing.
- * @nr_pe_cmp: The number of processing entity comparator inputs that are
- * available for tracing.
- * @nr_addr_cmp:Number of pairs of address comparators available
- * as found in ETMIDR4 0-3.
- * @nr_cntr: Number of counters as found in ETMIDR5 bit 28-30.
- * @nr_ext_inp: Number of external input.
- * @numcidc: Number of contextID comparators.
- * @numvmidc: Number of VMID comparators.
- * @nrseqstate: The number of sequencer states that are implemented.
- * @nr_seq_ctrls: The number of sequence state transition control registers.
- * @nr_event: Indicates how many events the trace unit support.
- * @nr_resource:The number of resource selection pairs available for tracing.
- * @nr_ss_cmp: Number of single-shot comparator controls that are available.
+ * @caps: ETM capabilities.
* @trcid: value of the current ID for this component.
- * @trcid_size: Indicates the trace ID width.
- * @ts_size: Global timestamp size field.
- * @ctxid_size: Size of the context ID field to consider.
- * @vmid_size: Size of the VM ID comparator to consider.
- * @ccsize: Indicates the size of the cycle counter in bits.
- * @ccitmin: minimum value that can be programmed in
- * @s_ex_level: In secure state, indicates whether instruction tracing is
- * supported for the corresponding Exception level.
- * @ns_ex_level:In non-secure state, indicates whether instruction tracing is
- * supported for the corresponding Exception level.
* @sticky_enable: true if ETM base configuration has been done.
* @boot_enable:True if we should start tracing at boot time.
* @os_unlock: True if access to management registers is allowed.
- * @instrp0: Tracing of load and store instructions
- * as P0 elements is supported.
- * @q_filt: Q element filtering support, if Q elements are supported.
- * @trcbb: Indicates if the trace unit supports branch broadcast tracing.
- * @trccond: If the trace unit supports conditional
- * instruction tracing.
- * @retstack: Indicates if the implementation supports a return stack.
- * @trccci: Indicates if the trace unit supports cycle counting
- * for instruction.
- * @q_support: Q element support characteristics.
- * @trc_error: Whether a trace unit can trace a system
- * error exception.
- * @syncpr: Indicates if an implementation has a fixed
- * synchronization period.
- * @stall_ctrl: Enables trace unit functionality that prevents trace
- * unit buffer overflows.
- * @sysstall: Does the system support stall control of the PE?
- * @nooverflow: Indicate if overflow prevention is supported.
- * @atbtrig: If the implementation can support ATB triggers
- * @lpoverride: If the implementation can support low-power state over.
* @trfcr: If the CPU supports FEAT_TRF, value of the TRFCR_ELx that
* allows tracing at all ELs. We don't want to compute this
* at runtime, due to the additional setting of TRFCR_CX when
* in EL2. Otherwise, 0.
- * @config: structure holding configuration parameters.
+ * @curr_config: structure holding current applied configuration.
+ * @sysfs_config: structure holding sysfs mode configuration.
* @save_state: State to be preserved across power loss
- * @skip_power_up: Indicates if an implementation can skip powering up
- * the trace unit.
* @paused: Indicates if the trace unit is paused.
+ * @ss_status: The status of the corresponding single-shot comparator.
* @arch_features: Bitmap of arch features of etmv4 devices.
*/
struct etmv4_drvdata {
@@ -1034,51 +1084,17 @@ struct etmv4_drvdata {
raw_spinlock_t spinlock;
int cpu;
u8 arch;
- u8 nr_pe;
- u8 nr_pe_cmp;
- u8 nr_addr_cmp;
- u8 nr_cntr;
- u8 nr_ext_inp;
- u8 numcidc;
- u8 numextinsel;
- u8 numvmidc;
- u8 nrseqstate;
- u8 nr_seq_ctrls;
- u8 nr_event;
- u8 nr_resource;
- u8 nr_ss_cmp;
+ struct etmv4_caps caps;
u8 trcid;
- u8 trcid_size;
- u8 ts_size;
- u8 ctxid_size;
- u8 vmid_size;
- u8 ccsize;
- u16 ccitmin;
- u8 s_ex_level;
- u8 ns_ex_level;
- u8 q_support;
- u8 os_lock_model;
bool sticky_enable : 1;
bool boot_enable : 1;
bool os_unlock : 1;
- bool instrp0 : 1;
- bool q_filt : 1;
- bool trcbb : 1;
- bool trccond : 1;
- bool retstack : 1;
- bool trccci : 1;
- bool trc_error : 1;
- bool syncpr : 1;
- bool stallctl : 1;
- bool sysstall : 1;
- bool nooverflow : 1;
- bool atbtrig : 1;
- bool lpoverride : 1;
- bool skip_power_up : 1;
bool paused : 1;
u64 trfcr;
- struct etmv4_config config;
+ struct etmv4_config curr_config;
+ struct etmv4_config sysfs_config;
struct etmv4_save_state *save_state;
+ u32 ss_status[ETM_MAX_SS_CMP];
DECLARE_BITMAP(arch_features, ETM4_IMPDEF_FEATURE_MAX);
};
@@ -1099,7 +1115,8 @@ enum etm_addr_ctxtype {
};
extern const struct attribute_group *coresight_etmv4_groups[];
-void etm4_config_trace_mode(struct etmv4_config *config);
+void etm4_config_trace_mode(struct etmv4_config *config,
+ const struct etmv4_caps *caps);
u64 etm4x_sysreg_read(u32 offset, bool _relaxed, bool _64bit);
void etm4x_sysreg_write(u64 val, u32 offset, bool _relaxed, bool _64bit);
diff --git a/drivers/hwtracing/coresight/coresight-sysfs.c b/drivers/hwtracing/coresight/coresight-sysfs.c
index 4b010f8bc4c0..75000fe64fda 100644
--- a/drivers/hwtracing/coresight/coresight-sysfs.c
+++ b/drivers/hwtracing/coresight/coresight-sysfs.c
@@ -346,16 +346,19 @@ static ssize_t label_show(struct device *dev,
}
static DEVICE_ATTR_RO(label);
-static umode_t label_is_visible(struct kobject *kobj,
- struct attribute *attr, int n)
+static umode_t coresight_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
{
struct device *dev = kobj_to_dev(kobj);
+ struct coresight_device *csdev = to_coresight_device(dev);
if (attr == &dev_attr_label.attr) {
if (fwnode_property_present(dev_fwnode(dev), "label"))
return attr->mode;
else
return 0;
+ } else if (csdev->flags & CORESIGHT_DESC_NO_SYSFS_MODE) {
+ return 0;
}
return attr->mode;
@@ -369,7 +372,7 @@ static struct attribute *coresight_sink_attrs[] = {
static struct attribute_group coresight_sink_group = {
.attrs = coresight_sink_attrs,
- .is_visible = label_is_visible,
+ .is_visible = coresight_attr_is_visible,
};
__ATTRIBUTE_GROUPS(coresight_sink);
@@ -381,7 +384,7 @@ static struct attribute *coresight_source_attrs[] = {
static struct attribute_group coresight_source_group = {
.attrs = coresight_source_attrs,
- .is_visible = label_is_visible,
+ .is_visible = coresight_attr_is_visible,
};
__ATTRIBUTE_GROUPS(coresight_source);
@@ -392,7 +395,7 @@ static struct attribute *coresight_link_attrs[] = {
static struct attribute_group coresight_link_group = {
.attrs = coresight_link_attrs,
- .is_visible = label_is_visible,
+ .is_visible = coresight_attr_is_visible,
};
__ATTRIBUTE_GROUPS(coresight_link);
@@ -403,7 +406,7 @@ static struct attribute *coresight_helper_attrs[] = {
static struct attribute_group coresight_helper_group = {
.attrs = coresight_helper_attrs,
- .is_visible = label_is_visible,
+ .is_visible = coresight_attr_is_visible,
};
__ATTRIBUTE_GROUPS(coresight_helper);
diff --git a/drivers/hwtracing/coresight/coresight-tnoc.c b/drivers/hwtracing/coresight/coresight-tnoc.c
index 9e8de4323d28..929d316b4b36 100644
--- a/drivers/hwtracing/coresight/coresight-tnoc.c
+++ b/drivers/hwtracing/coresight/coresight-tnoc.c
@@ -255,7 +255,7 @@ static void trace_noc_remove(struct amba_device *adev)
coresight_trace_id_put_system_id(drvdata->atid);
}
-static struct amba_id trace_noc_ids[] = {
+static const struct amba_id trace_noc_ids[] = {
{
.id = 0x000f0c00,
.mask = 0x00ffff00,
diff --git a/drivers/hwtracing/coresight/coresight-trbe.c b/drivers/hwtracing/coresight/coresight-trbe.c
index c7cbca45f2de..eca8e13a9744 100644
--- a/drivers/hwtracing/coresight/coresight-trbe.c
+++ b/drivers/hwtracing/coresight/coresight-trbe.c
@@ -1348,6 +1348,13 @@ static void arm_trbe_register_coresight_cpu(struct trbe_drvdata *drvdata, int cp
desc.dev = dev;
desc.cpu = cpu;
desc.flags = CORESIGHT_DESC_CPU_BOUND;
+ /*
+ * ETE isn't connected to TRBE with a link like other Coresight devices
+ * and the TRBE driver has been written to always assume Perf mode, so
+ * Prevent sysfs from being used.
+ */
+ desc.flags |= CORESIGHT_DESC_NO_SYSFS_MODE;
+
trbe_csdev = coresight_register(&desc);
if (IS_ERR(trbe_csdev))
goto cpu_clear;
diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig
index 4094299e2ed8..141ac984247c 100644
--- a/drivers/iio/accel/Kconfig
+++ b/drivers/iio/accel/Kconfig
@@ -7,13 +7,14 @@
menu "Accelerometers"
config ADIS16201
- tristate "Analog Devices ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer"
+ tristate "ADIS16201 Digital Inclinometer and similar"
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.
+ Say Y here to build support for Analog Devices Digital Inclinometers:
+ - ADIS16201
+ - ADIS16203
To compile this driver as a module, say M here: the module will
be called adis16201.
diff --git a/drivers/iio/accel/adis16201.c b/drivers/iio/accel/adis16201.c
index ba0f97944c6d..6d5a7b696112 100644
--- a/drivers/iio/accel/adis16201.c
+++ b/drivers/iio/accel/adis16201.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
- * ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer
+ * ADIS16201 Digital Inclinometer and similar
*
* Copyright 2010 Analog Devices Inc.
*/
@@ -63,6 +63,7 @@
#define ADIS16201_DIAG_STAT_REG 0x3C
#define ADIS16201_DIAG_STAT_ALARM2 BIT(9)
#define ADIS16201_DIAG_STAT_ALARM1 BIT(8)
+#define ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT 5
#define ADIS16201_DIAG_STAT_SPI_FAIL_BIT 3
#define ADIS16201_DIAG_STAT_FLASH_UPT_FAIL_BIT 2
/* Power supply above 3.625 V */
@@ -87,6 +88,21 @@ enum adis16201_scan {
ADIS16201_SCAN_TEMP,
};
+struct adis16201_chip_info {
+ const char *name;
+ const struct iio_chan_spec *arr_channels;
+ const struct adis_data *data;
+ unsigned int incli_scale_val2;
+ unsigned int read_bits_incli;
+ unsigned int num_channels;
+ u16 write_mask_incli;
+};
+
+struct adis16201_state {
+ struct adis adis;
+ const struct adis16201_chip_info *info;
+};
+
static const u8 adis16201_addresses[] = {
[ADIS16201_SCAN_ACC_X] = ADIS16201_XACCL_OFFS_REG,
[ADIS16201_SCAN_ACC_Y] = ADIS16201_YACCL_OFFS_REG,
@@ -99,7 +115,7 @@ static int adis16201_read_raw(struct iio_dev *indio_dev,
int *val, int *val2,
long mask)
{
- struct adis *st = iio_priv(indio_dev);
+ struct adis16201_state *st = iio_priv(indio_dev);
int ret;
int bits;
u8 addr;
@@ -137,7 +153,7 @@ static int adis16201_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_INT_PLUS_NANO;
case IIO_INCLI:
*val = 0;
- *val2 = 100000;
+ *val2 = st->info->incli_scale_val2;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
@@ -157,13 +173,13 @@ static int adis16201_read_raw(struct iio_dev *indio_dev,
bits = 12;
break;
case IIO_INCLI:
- bits = 9;
+ bits = st->info->read_bits_incli;
break;
default:
return -EINVAL;
}
addr = adis16201_addresses[chan->scan_index];
- ret = adis_read_reg_16(st, addr, &val16);
+ ret = adis_read_reg_16(&st->adis, addr, &val16);
if (ret)
return ret;
@@ -180,41 +196,49 @@ static int adis16201_write_raw(struct iio_dev *indio_dev,
int val2,
long mask)
{
- struct adis *st = iio_priv(indio_dev);
- int m;
+ struct adis16201_state *st = iio_priv(indio_dev);
if (mask != IIO_CHAN_INFO_CALIBBIAS)
return -EINVAL;
switch (chan->type) {
case IIO_ACCEL:
- m = GENMASK(11, 0);
+ val &= GENMASK(11, 0);
break;
case IIO_INCLI:
- m = GENMASK(8, 0);
+ val &= st->info->write_mask_incli;
break;
default:
return -EINVAL;
}
- return adis_write_reg_16(st, adis16201_addresses[chan->scan_index],
- val & m);
+ return adis_write_reg_16(&st->adis, adis16201_addresses[chan->scan_index],
+ val);
}
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)
+ ADIS_SUPPLY_CHAN(ADIS16201_SUPPLY_OUT_REG, ADIS16201_SCAN_SUPPLY, 0,
+ 12),
+ ADIS_AUX_ADC_CHAN(ADIS16201_AUX_ADC_REG, ADIS16201_SCAN_AUX_ADC, 0, 12),
+ ADIS_TEMP_CHAN(ADIS16201_TEMP_OUT_REG, ADIS16201_SCAN_TEMP, 0, 12),
+ IIO_CHAN_SOFT_TIMESTAMP(7),
+};
+
+static const struct iio_chan_spec adis16203_channels[] = {
+ ADIS_INCLI_CHAN(X, ADIS16201_XINCL_OUT_REG, ADIS16201_SCAN_INCLI_X,
+ BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14),
+ ADIS_SUPPLY_CHAN(ADIS16201_SUPPLY_OUT_REG, ADIS16201_SCAN_SUPPLY, 0, 12),
+ ADIS_AUX_ADC_CHAN(ADIS16201_AUX_ADC_REG, ADIS16201_SCAN_AUX_ADC, 0, 12),
+ ADIS_TEMP_CHAN(ADIS16201_TEMP_OUT_REG, ADIS16201_SCAN_TEMP, 0, 12),
+ IIO_CHAN_SOFT_TIMESTAMP(7),
};
static const struct iio_info adis16201_info = {
@@ -224,6 +248,7 @@ static const struct iio_info adis16201_info = {
};
static const char * const adis16201_status_error_msgs[] = {
+ [ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT] = "Self test failure",
[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",
@@ -254,10 +279,49 @@ static const struct adis_data adis16201_data = {
BIT(ADIS16201_DIAG_STAT_POWER_LOW_BIT),
};
+static const struct adis_data adis16203_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(ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT) |
+ 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 const struct adis16201_chip_info adis16201_chip_data = {
+ .arr_channels = adis16201_channels,
+ .incli_scale_val2 = 100000,
+ .write_mask_incli = GENMASK(8, 0),
+ .read_bits_incli = 9,
+ .num_channels = ARRAY_SIZE(adis16201_channels),
+ .name = "adis16201",
+ .data = &adis16201_data,
+};
+
+static const struct adis16201_chip_info adis16203_chip_data = {
+ .arr_channels = adis16203_channels,
+ .incli_scale_val2 = 25000,
+ .write_mask_incli = GENMASK(13, 0),
+ .read_bits_incli = 14,
+ .num_channels = ARRAY_SIZE(adis16203_channels),
+ .name = "adis16203",
+ .data = &adis16203_data,
+};
+
static int adis16201_probe(struct spi_device *spi)
{
struct iio_dev *indio_dev;
- struct adis *st;
+ struct adis16201_state *st;
int ret;
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
@@ -266,38 +330,57 @@ static int adis16201_probe(struct spi_device *spi)
st = iio_priv(indio_dev);
- indio_dev->name = spi->dev.driver->name;
+ st->info = spi_get_device_match_data(spi);
+ if (!st->info)
+ return -ENODATA;
+
+ indio_dev->name = st->info->name;
indio_dev->info = &adis16201_info;
- indio_dev->channels = adis16201_channels;
- indio_dev->num_channels = ARRAY_SIZE(adis16201_channels);
+ indio_dev->channels = st->info->arr_channels;
+ indio_dev->num_channels = st->info->num_channels;
indio_dev->modes = INDIO_DIRECT_MODE;
- ret = adis_init(st, indio_dev, spi, &adis16201_data);
+ ret = adis_init(&st->adis, indio_dev, spi, st->info->data);
if (ret)
return ret;
- ret = devm_adis_setup_buffer_and_trigger(st, indio_dev, NULL);
+ ret = devm_adis_setup_buffer_and_trigger(&st->adis, indio_dev, NULL);
if (ret)
return ret;
- ret = __adis_initial_startup(st);
+ ret = __adis_initial_startup(&st->adis);
if (ret)
return ret;
return devm_iio_device_register(&spi->dev, indio_dev);
}
+static const struct of_device_id adis16201_of_match[] = {
+ { .compatible = "adi,adis16201", .data = &adis16201_chip_data },
+ { .compatible = "adi,adis16203", .data = &adis16203_chip_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adis16201_of_match);
+
+static const struct spi_device_id adis16201_ids[] = {
+ { .name = "adis16201", .driver_data = (kernel_ulong_t)&adis16201_chip_data },
+ { .name = "adis16203", .driver_data = (kernel_ulong_t)&adis16203_chip_data },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, adis16201_ids);
+
static struct spi_driver adis16201_driver = {
.driver = {
.name = "adis16201",
+ .of_match_table = adis16201_of_match,
},
.probe = adis16201_probe,
+ .id_table = adis16201_ids,
};
module_spi_driver(adis16201_driver);
MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
-MODULE_DESCRIPTION("Analog Devices ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer");
+MODULE_DESCRIPTION("Analog Devices ADIS16201 Inclinometer and similar");
MODULE_LICENSE("GPL v2");
-MODULE_ALIAS("spi:adis16201");
MODULE_IMPORT_NS("IIO_ADISLIB");
diff --git a/drivers/iio/accel/adxl313_spi.c b/drivers/iio/accel/adxl313_spi.c
index a61295de104f..0f0da44461f7 100644
--- a/drivers/iio/accel/adxl313_spi.c
+++ b/drivers/iio/accel/adxl313_spi.c
@@ -79,6 +79,8 @@ static int adxl313_spi_probe(struct spi_device *spi)
return ret;
chip_data = spi_get_device_match_data(spi);
+ if (!chip_data)
+ return -ENODATA;
regmap = devm_regmap_init_spi(spi,
&adxl31x_spi_regmap_config[chip_data->type]);
diff --git a/drivers/iio/accel/adxl367.c b/drivers/iio/accel/adxl367.c
index 4ff1c7a0988b..026db73057c5 100644
--- a/drivers/iio/accel/adxl367.c
+++ b/drivers/iio/accel/adxl367.c
@@ -13,6 +13,7 @@
#include <linux/iio/sysfs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
+#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/unaligned.h>
@@ -81,6 +82,7 @@
#define ADXL367_SAMPLES_L_MASK GENMASK(7, 0)
#define ADXL367_REG_INT1_MAP 0x2A
+#define ADXL367_REG_INT2_MAP 0x2B
#define ADXL367_INT_INACT_MASK BIT(5)
#define ADXL367_INT_ACT_MASK BIT(4)
#define ADXL367_INT_FIFO_WATERMARK_MASK BIT(2)
@@ -168,6 +170,8 @@ struct adxl367_state {
enum adxl367_odr odr;
enum adxl367_range range;
+ u8 int_map_reg;
+
unsigned int act_threshold;
unsigned int act_time_ms;
unsigned int inact_threshold;
@@ -366,8 +370,7 @@ static int adxl367_set_act_interrupt_en(struct adxl367_state *st,
{
unsigned int mask = adxl367_act_int_mask_tbl[act];
- return regmap_update_bits(st->regmap, ADXL367_REG_INT1_MAP,
- mask, en ? mask : 0);
+ return regmap_assign_bits(st->regmap, st->int_map_reg, mask, en);
}
static int adxl367_get_act_interrupt_en(struct adxl367_state *st,
@@ -378,7 +381,7 @@ static int adxl367_get_act_interrupt_en(struct adxl367_state *st,
unsigned int val;
int ret;
- ret = regmap_read(st->regmap, ADXL367_REG_INT1_MAP, &val);
+ ret = regmap_read(st->regmap, st->int_map_reg, &val);
if (ret)
return ret;
@@ -401,9 +404,8 @@ static int adxl367_set_act_en(struct adxl367_state *st,
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);
+ return regmap_assign_bits(st->regmap, st->int_map_reg,
+ ADXL367_INT_FIFO_WATERMARK_MASK, en);
}
static int adxl367_get_fifo_mode(struct adxl367_state *st,
@@ -631,8 +633,7 @@ static int adxl367_set_odr(struct iio_dev *indio_dev, enum adxl367_odr odr)
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);
+ return regmap_assign_bits(st->regmap, reg, ADXL367_ADC_EN_MASK, en);
}
static int adxl367_set_temp_adc_reg_en(struct adxl367_state *st,
@@ -787,6 +788,13 @@ static bool adxl367_push_fifo_data(struct iio_dev *indio_dev, u8 status,
if (!FIELD_GET(ADXL367_STATUS_FIFO_FULL_MASK, status))
return false;
+ if (fifo_entries > ADXL367_FIFO_SIZE) {
+ dev_err_ratelimited(st->dev,
+ "FIFO entry count %u exceeds FIFO size %u\n",
+ fifo_entries, ADXL367_FIFO_SIZE);
+ return true;
+ }
+
fifo_entries -= fifo_entries % st->fifo_set_size;
ret = st->ops->read_fifo(st->context, st->fifo_buf, fifo_entries);
@@ -1426,6 +1434,36 @@ static int adxl367_setup(struct adxl367_state *st)
return adxl367_set_measure_en(st, true);
}
+static int adxl367_set_int_map_reg(struct adxl367_state *st, int irq)
+{
+ int ret;
+
+ /*
+ * Route the mappings to whichever of INT1/INT2 is named in the
+ * interrupt-names property. -EINVAL (property absent) and -ENODATA
+ * (name not listed) are not errors; anything else is.
+ */
+ ret = fwnode_irq_get_byname(dev_fwnode(st->dev), "INT1");
+ if (ret > 0) {
+ st->int_map_reg = ADXL367_REG_INT1_MAP;
+ return ret;
+ }
+ if (ret != -ENODATA && ret != -EINVAL)
+ return ret;
+
+ ret = fwnode_irq_get_byname(dev_fwnode(st->dev), "INT2");
+ if (ret > 0) {
+ st->int_map_reg = ADXL367_REG_INT2_MAP;
+ return ret;
+ }
+ if (ret != -ENODATA && ret != -EINVAL)
+ return ret;
+
+ /* No INT1/INT2 named: default to INT1 and the bus interrupt. */
+ st->int_map_reg = ADXL367_REG_INT1_MAP;
+ return irq;
+}
+
int adxl367_probe(struct device *dev, const struct adxl367_ops *ops,
void *context, struct regmap *regmap, int irq)
{
@@ -1455,6 +1493,11 @@ int adxl367_probe(struct device *dev, const struct adxl367_ops *ops,
indio_dev->info = &adxl367_info;
indio_dev->modes = INDIO_DIRECT_MODE;
+ ret = adxl367_set_int_map_reg(st, irq);
+ if (ret < 0)
+ return dev_err_probe(st->dev, ret, "Failed to get interrupt\n");
+ irq = ret;
+
ret = devm_regulator_bulk_get_enable(st->dev,
ARRAY_SIZE(regulator_names),
regulator_names);
diff --git a/drivers/iio/accel/adxl372_spi.c b/drivers/iio/accel/adxl372_spi.c
index 128e148c4e96..d54544d6df6f 100644
--- a/drivers/iio/accel/adxl372_spi.c
+++ b/drivers/iio/accel/adxl372_spi.c
@@ -25,6 +25,8 @@ static int adxl372_spi_probe(struct spi_device *spi)
struct regmap *regmap;
chip_info = spi_get_device_match_data(spi);
+ if (!chip_info)
+ return -ENODATA;
regmap = devm_regmap_init_spi(spi, &adxl372_spi_regmap_config);
if (IS_ERR(regmap))
diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c
index 7dca5523091f..8518ee23e114 100644
--- a/drivers/iio/accel/adxl380.c
+++ b/drivers/iio/accel/adxl380.c
@@ -966,6 +966,13 @@ static irqreturn_t adxl380_irq_handler(int irq, void *p)
if (ret)
return IRQ_HANDLED;
+ if (fifo_entries > ADXL380_FIFO_SAMPLES) {
+ dev_err_ratelimited(st->dev,
+ "FIFO entry count %u exceeds FIFO size %lu\n",
+ fifo_entries, ADXL380_FIFO_SAMPLES);
+ 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);
diff --git a/drivers/iio/accel/adxl380_spi.c b/drivers/iio/accel/adxl380_spi.c
index ae8467f4e6c0..8ffb3a8690a6 100644
--- a/drivers/iio/accel/adxl380_spi.c
+++ b/drivers/iio/accel/adxl380_spi.c
@@ -25,6 +25,8 @@ static int adxl380_spi_probe(struct spi_device *spi)
struct regmap *regmap;
chip_data = spi_get_device_match_data(spi);
+ if (!chip_data)
+ return -ENODATA;
regmap = devm_regmap_init_spi(spi, &adxl380_spi_regmap_config);
if (IS_ERR(regmap))
diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c
index d6c1087cc3c0..d7a95289df48 100644
--- a/drivers/iio/accel/bma220_spi.c
+++ b/drivers/iio/accel/bma220_spi.c
@@ -28,12 +28,13 @@ static const struct spi_device_id bma220_spi_id[] = {
{ .name = "bma220" },
{ }
};
+MODULE_DEVICE_TABLE(spi, bma220_spi_id);
static const struct acpi_device_id bma220_acpi_id[] = {
{ "BMA0220", 0 },
{ }
};
-MODULE_DEVICE_TABLE(spi, bma220_spi_id);
+MODULE_DEVICE_TABLE(acpi, bma220_acpi_id);
static const struct of_device_id bma220_of_spi_match[] = {
{ .compatible = "bosch,bma220" },
diff --git a/drivers/iio/accel/bma400_core.c b/drivers/iio/accel/bma400_core.c
index 7e3cff4cca91..6bc64ca87f4b 100644
--- a/drivers/iio/accel/bma400_core.c
+++ b/drivers/iio/accel/bma400_core.c
@@ -6,8 +6,6 @@
*
* TODO:
* - Support for power management
- * - Support events and interrupts
- * - Create channel for step count
* - Create channel for sensor time
*/
diff --git a/drivers/iio/accel/kionix-kx022a.c b/drivers/iio/accel/kionix-kx022a.c
index 9ff70a373141..add89512af79 100644
--- a/drivers/iio/accel/kionix-kx022a.c
+++ b/drivers/iio/accel/kionix-kx022a.c
@@ -301,11 +301,10 @@ struct kx022a_data {
__le16 *fifo_buffer;
/* 3 x 16bit accel data + timestamp */
- __le16 buffer[8] __aligned(IIO_DMA_MINALIGN);
struct {
__le16 channels[3];
aligned_s64 ts;
- } scan;
+ } scan __aligned(IIO_DMA_MINALIGN);
};
static const struct iio_mount_matrix *
@@ -611,14 +610,15 @@ static int kx022a_get_axis(struct kx022a_data *data,
struct iio_chan_spec const *chan,
int *val)
{
+ __le16 *buf = &data->scan.channels[0];
int ret;
- ret = regmap_bulk_read(data->regmap, chan->address, &data->buffer[0],
- sizeof(__le16));
+ ret = regmap_bulk_read(data->regmap, chan->address,
+ buf, sizeof(*buf));
if (ret)
return ret;
- *val = (s16)le16_to_cpu(data->buffer[0]);
+ *val = (s16)le16_to_cpup(buf);
return IIO_VAL_INT;
}
@@ -809,7 +809,7 @@ static int __kx022a_fifo_flush(struct iio_dev *idev, unsigned int samples,
* 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
+ * 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.
*/
@@ -864,7 +864,8 @@ static int __kx022a_fifo_flush(struct iio_dev *idev, unsigned int samples,
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);
+ iio_push_to_buffers_with_ts(idev, &data->scan,
+ sizeof(data->scan), tstamp);
tstamp += sample_period;
}
@@ -1047,12 +1048,13 @@ static irqreturn_t kx022a_trigger_handler(int irq, void *p)
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);
+ ret = regmap_bulk_read(data->regmap, data->chip_info->xout_l,
+ data->scan.channels, KX022A_FIFO_SAMPLES_SIZE_BYTES);
if (ret < 0)
goto err_read;
- iio_push_to_buffers_with_timestamp(idev, data->buffer, data->timestamp);
+ iio_push_to_buffers_with_ts(idev, &data->scan, sizeof(data->scan),
+ data->timestamp);
err_read:
iio_trigger_notify_done(idev->trig);
diff --git a/drivers/iio/accel/kionix-kx022a.h b/drivers/iio/accel/kionix-kx022a.h
index 0ed54f584223..a2d122c1e234 100644
--- a/drivers/iio/accel/kionix-kx022a.h
+++ b/drivers/iio/accel/kionix-kx022a.h
@@ -65,7 +65,7 @@
#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_IPOL_HIGH KX022A_MASK_IPOL
#define KX022A_MASK_ITYP BIT(3)
#define KX022A_ITYP_PULSE KX022A_MASK_ITYP
#define KX022A_ITYP_LEVEL 0
diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c
index 8994c9e8e048..b3d753bd9476 100644
--- a/drivers/iio/accel/kxcjk-1013.c
+++ b/drivers/iio/accel/kxcjk-1013.c
@@ -1547,6 +1547,7 @@ static void kxcjk1013_remove(struct i2c_client *client)
iio_device_unregister(indio_dev);
+ pm_runtime_dont_use_autosuspend(&client->dev);
pm_runtime_disable(&client->dev);
pm_runtime_set_suspended(&client->dev);
diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c
index 7d683686dd9d..fe62a903f0e2 100644
--- a/drivers/iio/accel/mma8452.c
+++ b/drivers/iio/accel/mma8452.c
@@ -18,6 +18,8 @@
* TODO: orientation events
*/
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/module.h>
@@ -110,8 +112,7 @@ struct mma8452_data {
u8 data_cfg;
const struct mma_chip_info *chip_info;
int sleep_val;
- struct regulator *vdd_reg;
- struct regulator *vddio_reg;
+ struct regulator_bulk_data regs[2];
/* Ensure correct alignment of time stamp when present */
struct {
@@ -222,15 +223,15 @@ static int mma8452_drdy(struct mma8452_data *data)
static int mma8452_set_runtime_pm_state(struct i2c_client *client, bool on)
{
#ifdef CONFIG_PM
+ struct device *dev = &client->dev;
int ret;
if (on)
- ret = pm_runtime_resume_and_get(&client->dev);
+ ret = pm_runtime_resume_and_get(dev);
else
- ret = pm_runtime_put_autosuspend(&client->dev);
+ ret = pm_runtime_put_autosuspend(dev);
if (ret < 0) {
- dev_err(&client->dev,
- "failed to change power state to %d\n", on);
+ dev_err(dev, "failed to change power state to %d\n", on);
return ret;
}
@@ -495,14 +496,14 @@ static int mma8452_read_raw(struct iio_dev *indio_dev,
int i, ret;
switch (mask) {
- case IIO_CHAN_INFO_RAW:
- if (!iio_device_claim_direct(indio_dev))
+ case IIO_CHAN_INFO_RAW: {
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
return -EBUSY;
- mutex_lock(&data->lock);
+ guard(mutex)(&data->lock);
+
ret = mma8452_read(data, buffer);
- mutex_unlock(&data->lock);
- iio_device_release_direct(indio_dev);
if (ret < 0)
return ret;
@@ -511,6 +512,7 @@ static int mma8452_read_raw(struct iio_dev *indio_dev,
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];
@@ -599,36 +601,30 @@ static int mma8452_change_config(struct mma8452_data *data, u8 reg, u8 val)
int ret;
int is_active;
- mutex_lock(&data->lock);
+ guard(mutex)(&data->lock);
is_active = mma8452_is_active(data);
- if (is_active < 0) {
- ret = is_active;
- goto fail;
- }
+ if (is_active < 0)
+ return is_active;
/* config can only be changed when in standby */
if (is_active > 0) {
ret = mma8452_standby(data);
if (ret < 0)
- goto fail;
+ return ret;
}
ret = i2c_smbus_write_byte_data(data->client, reg, val);
if (ret < 0)
- goto fail;
+ return ret;
if (is_active > 0) {
ret = mma8452_active(data);
if (ret < 0)
- goto fail;
+ return ret;
}
- ret = 0;
-fail:
- mutex_unlock(&data->lock);
-
- return ret;
+ return 0;
}
static int mma8452_set_power_mode(struct mma8452_data *data, u8 mode)
@@ -777,14 +773,11 @@ 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))
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
return -EBUSY;
- ret = __mma8452_write_raw(indio_dev, chan, val, val2, mask);
- iio_device_release_direct(indio_dev);
- return ret;
+ return __mma8452_write_raw(indio_dev, chan, val, val2, mask);
}
static int mma8452_get_event_regs(struct mma8452_data *data,
@@ -1552,7 +1545,7 @@ static int mma8452_probe(struct i2c_client *client)
struct iio_dev *indio_dev;
int ret;
- indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
@@ -1562,32 +1555,21 @@ static int mma8452_probe(struct i2c_client *client)
data->chip_info = i2c_get_match_data(client);
if (!data->chip_info)
- return dev_err_probe(&client->dev, -ENODEV,
- "unknown device model\n");
+ return dev_err_probe(dev, -ENODEV, "unknown device model\n");
- ret = iio_read_mount_matrix(&client->dev, &data->orientation);
+ ret = iio_read_mount_matrix(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);
+ data->regs[0].supply = "vdd";
+ data->regs[1].supply = "vddio";
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->regs), data->regs);
if (ret)
- return dev_err_probe(dev, ret, "failed to enable VDD regulator!\n");
+ return dev_err_probe(dev, ret, "failed to get regulators\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 = regulator_bulk_enable(ARRAY_SIZE(data->regs), data->regs);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable regulators\n");
ret = i2c_smbus_read_byte_data(client, MMA8452_WHO_AM_I);
if (ret < 0)
@@ -1608,7 +1590,7 @@ static int mma8452_probe(struct i2c_client *client)
goto disable_regulators;
}
- dev_info(&client->dev, "registering %s accelerometer; ID 0x%x\n",
+ dev_info(dev, "registering %s accelerometer; ID 0x%x\n",
data->chip_info->name, data->chip_info->chip_id);
i2c_set_clientdata(client, indio_dev);
@@ -1641,10 +1623,10 @@ static int mma8452_probe(struct i2c_client *client)
if (client->irq) {
int irq2;
- irq2 = fwnode_irq_get_byname(dev_fwnode(&client->dev), "INT2");
+ irq2 = fwnode_irq_get_byname(dev_fwnode(dev), "INT2");
if (irq2 == client->irq) {
- dev_dbg(&client->dev, "using interrupt line INT2\n");
+ dev_dbg(dev, "using interrupt line INT2\n");
} else {
ret = i2c_smbus_write_byte_data(client,
MMA8452_CTRL_REG5,
@@ -1652,7 +1634,7 @@ static int mma8452_probe(struct i2c_client *client)
if (ret < 0)
goto disable_regulators;
- dev_dbg(&client->dev, "using interrupt line INT1\n");
+ dev_dbg(dev, "using interrupt line INT1\n");
}
ret = i2c_smbus_write_byte_data(client,
@@ -1689,14 +1671,13 @@ static int mma8452_probe(struct i2c_client *client)
goto buffer_cleanup;
}
- ret = pm_runtime_set_active(&client->dev);
+ ret = pm_runtime_set_active(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);
+ pm_runtime_enable(dev);
+ pm_runtime_set_autosuspend_delay(dev, MMA8452_AUTO_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
ret = iio_device_register(indio_dev);
if (ret < 0)
@@ -1722,10 +1703,7 @@ trigger_cleanup:
mma8452_trigger_cleanup(indio_dev);
disable_regulators:
- regulator_disable(data->vddio_reg);
-
-disable_regulator_vdd:
- regulator_disable(data->vdd_reg);
+ regulator_bulk_disable(ARRAY_SIZE(data->regs), data->regs);
return ret;
}
@@ -1734,11 +1712,12 @@ 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);
+ struct device *dev = &client->dev;
iio_device_unregister(indio_dev);
- pm_runtime_disable(&client->dev);
- pm_runtime_set_suspended(&client->dev);
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
if (client->irq)
free_irq(client->irq, indio_dev);
@@ -1747,8 +1726,7 @@ static void mma8452_remove(struct i2c_client *client)
mma8452_trigger_cleanup(indio_dev);
mma8452_standby(iio_priv(indio_dev));
- regulator_disable(data->vddio_reg);
- regulator_disable(data->vdd_reg);
+ regulator_bulk_disable(ARRAY_SIZE(data->regs), data->regs);
}
#ifdef CONFIG_PM
@@ -1758,23 +1736,16 @@ static int mma8452_runtime_suspend(struct device *dev)
struct mma8452_data *data = iio_priv(indio_dev);
int ret;
- mutex_lock(&data->lock);
- ret = mma8452_standby(data);
- mutex_unlock(&data->lock);
+ scoped_guard(mutex, &data->lock)
+ ret = mma8452_standby(data);
if (ret < 0) {
- dev_err(&data->client->dev, "powering off device failed\n");
+ dev_err(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);
+ ret = regulator_bulk_disable(ARRAY_SIZE(data->regs), data->regs);
if (ret) {
- dev_err(dev, "failed to disable VDD regulator\n");
+ dev_err(dev, "failed to disable regulators\n");
return ret;
}
@@ -1787,16 +1758,9 @@ static int mma8452_runtime_resume(struct device *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);
+ ret = regulator_bulk_enable(ARRAY_SIZE(data->regs), data->regs);
if (ret) {
- dev_err(dev, "failed to enable VDDIO regulator\n");
- regulator_disable(data->vdd_reg);
+ dev_err(dev, "failed to enable regulators\n");
return ret;
}
@@ -1814,8 +1778,7 @@ static int mma8452_runtime_resume(struct device *dev)
return 0;
runtime_resume_failed:
- regulator_disable(data->vddio_reg);
- regulator_disable(data->vdd_reg);
+ regulator_bulk_disable(ARRAY_SIZE(data->regs), data->regs);
return ret;
}
diff --git a/drivers/iio/accel/mma9551.c b/drivers/iio/accel/mma9551.c
index 7e01427fd9c4..182f488edab1 100644
--- a/drivers/iio/accel/mma9551.c
+++ b/drivers/iio/accel/mma9551.c
@@ -500,6 +500,7 @@ static void mma9551_remove(struct i2c_client *client)
iio_device_unregister(indio_dev);
+ pm_runtime_dont_use_autosuspend(&client->dev);
pm_runtime_disable(&client->dev);
pm_runtime_set_suspended(&client->dev);
diff --git a/drivers/iio/accel/mma9553.c b/drivers/iio/accel/mma9553.c
index 8e7aaac89d47..5a36d415a837 100644
--- a/drivers/iio/accel/mma9553.c
+++ b/drivers/iio/accel/mma9553.c
@@ -1136,6 +1136,7 @@ static void mma9553_remove(struct i2c_client *client)
iio_device_unregister(indio_dev);
+ pm_runtime_dont_use_autosuspend(&client->dev);
pm_runtime_disable(&client->dev);
pm_runtime_set_suspended(&client->dev);
diff --git a/drivers/iio/accel/sca3000.c b/drivers/iio/accel/sca3000.c
index 8f1ac1d3305e..acd0890c78a5 100644
--- a/drivers/iio/accel/sca3000.c
+++ b/drivers/iio/accel/sca3000.c
@@ -1462,6 +1462,8 @@ static int sca3000_probe(struct spi_device *spi)
st->us = spi;
mutex_init(&st->lock);
st->info = spi_get_device_match_data(spi);
+ if (!st->info)
+ return -ENODATA;
indio_dev->name = st->info->name;
indio_dev->info = &sca3000_info;
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 415e519ad4eb..a802d8ee4dd9 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -105,7 +105,7 @@ config AD4080
called ad4080.
config AD4130
- tristate "Analog Device AD4130 ADC Driver"
+ tristate "Analog Devices AD4130 ADC Driver"
depends on SPI
depends on GPIOLIB
select IIO_BUFFER
@@ -121,7 +121,7 @@ config AD4130
called ad4130.
config AD4134
- tristate "Analog Device AD4134 ADC Driver"
+ tristate "Analog Devices AD4134 ADC Driver"
depends on SPI
select REGMAP_SPI
select CRC8
@@ -133,7 +133,7 @@ config AD4134
called ad4134_spi.
config AD4170_4
- tristate "Analog Device AD4170-4 ADC Driver"
+ tristate "Analog Devices AD4170-4 ADC Driver"
depends on SPI
select REGMAP_SPI
select IIO_BUFFER
@@ -164,7 +164,7 @@ config AD4691
called ad4691.
config AD4695
- tristate "Analog Device AD4695 ADC Driver"
+ tristate "Analog Devices AD4695 ADC Driver"
depends on SPI
select IIO_BUFFER
select IIO_BUFFER_DMAENGINE
@@ -179,7 +179,7 @@ config AD4695
called ad4695.
config AD4851
- tristate "Analog Device AD4851 DAS Driver"
+ tristate "Analog Devices AD4851 DAS Driver"
depends on SPI
depends on PWM
select REGMAP_SPI
@@ -347,7 +347,7 @@ config AD7380
called ad7380.
config AD7405
- tristate "Analog Device AD7405 ADC Driver"
+ tristate "Analog Devices AD7405 ADC Driver"
depends on IIO_BACKEND
help
Say yes here to build support for Analog Devices AD7405, ADUM7701,
@@ -431,6 +431,21 @@ config AD7766
To compile this driver as a module, choose M here: the module will be
called ad7766.
+config AD7768
+ tristate "Analog Devices AD7768/AD7768-4 ADC driver"
+ depends on SPI
+ select REGULATOR
+ select AUXILIARY_BUS
+ select IIO_BUFFER
+ select IIO_BACKEND
+ select REGMAP
+ help
+ Say yes here to build support for Analog Devices AD7768 and AD7768-4
+ SPI analog to digital converters.
+
+ To compile this driver as a module, choose M here: the module will be
+ called ad7768.
+
config AD7768_1
tristate "Analog Devices AD7768-1 ADC driver"
depends on SPI
@@ -576,20 +591,22 @@ config AD9467
called ad9467.
config ADE9000
- tristate "Analog Devices ADE9000 Multiphase Energy, and Power Quality Monitoring IC Driver"
+ tristate "Analog Devices 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.
+ Say yes here to build support for the following Analog Devices
+ highly accurate, multiphase energy and power quality monitoring
+ integrated circuits:
+ - ADE9000
+ - ADE9078
- The device features high-precision analog-to-digital converters
+ The devices feature 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.
+ frequency, and harmonic analysis. They support SPI communication
+ and provide buffered data output through the IIO framework.
To compile this driver as a module, choose M here: the module will
be called ade9000.
@@ -653,6 +670,17 @@ config AT91_SAMA5D2_ADC
To compile this driver as a module, choose M here: the module will be
called at91-sama5d2_adc.
+config AXIADO_SARADC
+ tristate "Axiado SARADC driver"
+ depends on ARCH_AXIADO || COMPILE_TEST
+ select REGMAP_MMIO
+ help
+ Say yes here to build support for the SARADC found in Axiado
+ SoCs.
+
+ To compile this driver as a module, choose M here: the module
+ will be called axiado_saradc.
+
config AXP20X_ADC
tristate "X-Powers AXP20X and AXP22X ADC driver"
depends on MFD_AXP20X
@@ -1116,6 +1144,17 @@ config MAX34408
To compile this driver as a module, choose M here: the module will be
called max34408.
+config MAX40080
+ tristate "Analog Devices MAX40080 Current Sense Amplifier"
+ depends on I2C
+ help
+ Say yes here to build support for the Analog Devices MAX40080
+ bidirectional current-sense amplifier with a 12-bit ADC and an I2C
+ interface.
+
+ To compile this driver as a module, choose M here: the module will be
+ called max40080.
+
config MAX77541_ADC
tristate "Analog Devices MAX77541 ADC driver"
depends on MFD_MAX77541
@@ -1551,6 +1590,8 @@ config RZT2H_ADC
tristate "Renesas RZ/T2H / RZ/N2H ADC driver"
depends on ARCH_RENESAS || COMPILE_TEST
select IIO_ADC_HELPER
+ select IIO_BUFFER
+ select IIO_KFIFO_BUF
help
Say yes here to build support for the ADC found in Renesas
RZ/T2H / RZ/N2H SoCs.
@@ -1801,11 +1842,14 @@ config TI_ADS1018
called ti-ads1018.
config TI_ADS1100
- tristate "Texas Instruments ADS1100 and ADS1000 ADC"
+ tristate "Texas Instruments ADS1100 and similar single channel I2C ADC"
depends on I2C
help
- If you say yes here you get support for Texas Instruments ADS1100 and
- ADS1000 ADC chips.
+ If you say yes here you get support for TI single channel I2C Analog
+ Devices.
+ * ADS1000 12-Bit, 128 SPS Analog-to-Digital Converter
+ * ADS1100 16-Bit, 128 SPS Analog-to-Digital Converter
+ * ADS1110 16-Bit, 240 SPS Analog-to-Digital Converter
This driver can also be built as a module. If so, the module will be
called ti-ads1100.
@@ -1822,6 +1866,16 @@ config TI_ADS1119
This driver can also be built as a module. If so, the module will be
called ti-ads1119.
+config TI_ADS112C04
+ tristate "Texas Instruments ADS112C04 ADC"
+ depends on I2C
+ help
+ If you say yes here you get support for Texas Instruments
+ ADS112C04 (16-bit) I2C analog to digital converters.
+
+ This driver can also be built as a module. If so, the module will be
+ called ti-ads112c04.
+
config TI_ADS112C14
tristate "Texas Instruments ADS112C14/ADS122C14"
depends on I2C
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index dcec0abb03b7..9e82c21dbd37 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -39,6 +39,7 @@ 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) += ad7768.o
obj-$(CONFIG_AD7768_1) += ad7768-1.o
obj-$(CONFIG_AD7779) += ad7779.o
obj-$(CONFIG_AD7780) += ad7780.o
@@ -55,6 +56,7 @@ 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_AXIADO_SARADC) += axiado_saradc.o
obj-$(CONFIG_AXP20X_ADC) += axp20x_adc.o
obj-$(CONFIG_AXP288_ADC) += axp288_adc.o
obj-$(CONFIG_BCM_IPROC_ADC) += bcm_iproc_adc.o
@@ -95,6 +97,7 @@ obj-$(CONFIG_MAX1241) += max1241.o
obj-$(CONFIG_MAX1363) += max1363.o
obj-$(CONFIG_MAX14001) += max14001.o
obj-$(CONFIG_MAX34408) += max34408.o
+obj-$(CONFIG_MAX40080) += max40080.o
obj-$(CONFIG_MAX77541_ADC) += max77541-adc.o
obj-$(CONFIG_MAX9611) += max9611.o
obj-$(CONFIG_MCP320X) += mcp320x.o
@@ -155,6 +158,7 @@ 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_ADS112C04) += ti-ads112c04.o
obj-$(CONFIG_TI_ADS112C14) += ti-ads112c14.o
obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o
obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o
diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c
index 04cd6628ebff..4ed82aa42400 100644
--- a/drivers/iio/adc/ad4080.c
+++ b/drivers/iio/adc/ad4080.c
@@ -137,6 +137,7 @@
#define AD4880_CHIP_ID 0x0059
#define AD4883_CHIP_ID 0x005B
#define AD4884_CHIP_ID 0x005C
+#define AD4885_CHIP_ID 0x005D
#define AD4080_MAX_CHANNELS 2
@@ -145,6 +146,10 @@
#define AD4080_MAX_SAMP_FREQ 40000000
#define AD4080_MIN_SAMP_FREQ 1250000
+/* debugfs direct_reg_access channel windowing: 0x0RR = ch0, 0x1RR = ch1 */
+#define AD4080_DEBUGFS_REG_CH_MSK GENMASK(15, 8)
+#define AD4080_DEBUGFS_REG_OFFSET_MSK GENMASK(7, 0)
+
enum ad4080_filter_type {
FILTER_NONE,
SINC_1,
@@ -210,11 +215,16 @@ 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);
+ unsigned int ch = FIELD_GET(AD4080_DEBUGFS_REG_CH_MSK, reg);
+ unsigned int offset = FIELD_GET(AD4080_DEBUGFS_REG_OFFSET_MSK, reg);
+
+ if (ch >= st->info->num_channels)
+ return -EINVAL;
if (readval)
- return regmap_read(st->regmap[0], reg, readval);
+ return regmap_read(st->regmap[ch], offset, readval);
- return regmap_write(st->regmap[0], reg, writeval);
+ return regmap_write(st->regmap[ch], offset, writeval);
}
static int ad4080_get_scale(struct ad4080_state *st, int *val, int *val2)
@@ -552,6 +562,11 @@ static const struct iio_chan_spec ad4884_channels[] = {
AD4880_CHANNEL_DEFINE(16, 16, 1),
};
+static const struct iio_chan_spec ad4885_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,
@@ -672,6 +687,16 @@ static const struct ad4080_chip_info ad4884_chip_info = {
.lvds_cnv_clk_cnt_max = 2,
};
+static const struct ad4080_chip_info ad4885_chip_info = {
+ .name = "ad4885",
+ .product_id = AD4885_CHIP_ID,
+ .scale_table = ad4080_scale_table,
+ .num_scales = ARRAY_SIZE(ad4080_scale_table),
+ .num_channels = 2,
+ .channels = ad4885_channels,
+ .lvds_cnv_clk_cnt_max = 8,
+};
+
static int ad4080_setup_channel(struct ad4080_state *st, unsigned int ch)
{
struct device *dev = regmap_get_device(st->regmap[ch]);
@@ -685,7 +710,8 @@ static int ad4080_setup_channel(struct ad4080_state *st, unsigned int ch)
return ret;
ret = regmap_write(st->regmap[ch], AD4080_REG_INTERFACE_CONFIG_A,
- AD4080_INTERFACE_CONFIG_A_SDO_ENABLE);
+ AD4080_INTERFACE_CONFIG_A_SDO_ENABLE |
+ AD4080_INTERFACE_CONFIG_A_ADDR_ASC);
if (ret)
return ret;
@@ -881,6 +907,7 @@ static const struct spi_device_id ad4080_id[] = {
{ .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 },
+ { .name = "ad4885", .driver_data = (kernel_ulong_t)&ad4885_chip_info },
{ }
};
MODULE_DEVICE_TABLE(spi, ad4080_id);
@@ -898,6 +925,7 @@ static const struct of_device_id ad4080_of_match[] = {
{ .compatible = "adi,ad4880", &ad4880_chip_info },
{ .compatible = "adi,ad4883", &ad4883_chip_info },
{ .compatible = "adi,ad4884", &ad4884_chip_info },
+ { .compatible = "adi,ad4885", &ad4885_chip_info },
{ }
};
MODULE_DEVICE_TABLE(of, ad4080_of_match);
diff --git a/drivers/iio/adc/ad4134.c b/drivers/iio/adc/ad4134.c
index 0490218bb0e9..70f80bf1825f 100644
--- a/drivers/iio/adc/ad4134.c
+++ b/drivers/iio/adc/ad4134.c
@@ -496,4 +496,3 @@ module_spi_driver(ad4134_driver);
MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@analog.com>");
MODULE_DESCRIPTION("Analog Devices AD4134 SPI driver");
MODULE_LICENSE("GPL");
-MODULE_IMPORT_NS("IIO_AD4134");
diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c
index faebb7b34a15..24904be5090a 100644
--- a/drivers/iio/adc/ad7173.c
+++ b/drivers/iio/adc/ad7173.c
@@ -1801,8 +1801,7 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev)
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);
+ chan_arr = devm_kcalloc(dev, sizeof(*chan_arr), st->num_channels, GFP_KERNEL);
if (!chan_arr)
return -ENOMEM;
diff --git a/drivers/iio/adc/ad7192.c b/drivers/iio/adc/ad7192.c
index d8f5886fcf69..8bd810546311 100644
--- a/drivers/iio/adc/ad7192.c
+++ b/drivers/iio/adc/ad7192.c
@@ -1406,6 +1406,9 @@ static int ad7192_probe(struct spi_device *spi)
st->int_vref_mv = ret == -ENODEV ? avdd_mv : ret / MILLI;
st->chip_info = spi_get_device_match_data(spi);
+ if (!st->chip_info)
+ return -ENODATA;
+
indio_dev->name = st->chip_info->name;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->info = st->chip_info->info;
diff --git a/drivers/iio/adc/ad7606_spi.c b/drivers/iio/adc/ad7606_spi.c
index 1ebdfb8580ab..8c479fa50a23 100644
--- a/drivers/iio/adc/ad7606_spi.c
+++ b/drivers/iio/adc/ad7606_spi.c
@@ -456,7 +456,11 @@ static const struct ad7606_bus_info ad7616_bus_info = {
static int ad7606_spi_probe(struct spi_device *spi)
{
- const struct ad7606_bus_info *bus_info = spi_get_device_match_data(spi);
+ const struct ad7606_bus_info *bus_info;
+
+ bus_info = spi_get_device_match_data(spi);
+ if (!bus_info)
+ return -ENODATA;
return ad7606_probe(&spi->dev, spi->irq, NULL,
bus_info->chip_info, bus_info->bops);
diff --git a/drivers/iio/adc/ad7768-1.c b/drivers/iio/adc/ad7768-1.c
index e9060c1bbe6f..3972821c295a 100644
--- a/drivers/iio/adc/ad7768-1.c
+++ b/drivers/iio/adc/ad7768-1.c
@@ -1829,6 +1829,9 @@ static int ad7768_probe(struct spi_device *spi)
}
st->chip = spi_get_device_match_data(spi);
+ if (!st->chip)
+ return -ENODATA;
+
st->spi = spi;
st->regmap = devm_regmap_init_spi(spi, &ad7768_regmap_config);
diff --git a/drivers/iio/adc/ad7768.c b/drivers/iio/adc/ad7768.c
new file mode 100644
index 000000000000..aa2db87a1b9f
--- /dev/null
+++ b/drivers/iio/adc/ad7768.c
@@ -0,0 +1,1921 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Analog Devices AD7768 ADC driver
+ *
+ * Copyright 2018-2026 Analog Devices Inc.
+ */
+
+#include <linux/array_size.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/log2.h>
+#include <linux/math.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regulator/driver.h>
+#include <linux/reset.h>
+#include <linux/spi/spi.h>
+#include <linux/time.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+#include <linux/units.h>
+
+#include <linux/iio/backend.h>
+#include <linux/iio/iio.h>
+
+#define AD7768_REG_CH_STANDBY 0x00
+
+#define AD7768_REG_CH_MODE(x) (0x01 + (x))
+#define AD7768_CH_MODE_FILTER_TYPE_MSK BIT(3)
+#define AD7768_CH_MODE_DEC_RATE_MSK GENMASK(2, 0)
+
+#define AD7768_REG_CH_MODE_SEL 0x03
+
+#define AD7768_REG_POWER_MODE 0x04
+#define AD7768_SLEEP_MODE_MSK BIT(7)
+#define AD7768_POWER_MODE_POWER_MODE_MSK GENMASK(5, 4)
+#define AD7768_POWER_MODE_POWER_MODE_LOW 0x0
+#define AD7768_POWER_MODE_POWER_MODE_MEDIAN 0x2
+#define AD7768_POWER_MODE_POWER_MODE_FAST 0x3
+#define AD7768_POWER_MODE_LVDS_ENABLE BIT(3)
+#define AD7768_POWER_MODE_MCLK_DIV_MSK GENMASK(1, 0)
+
+#define AD7768_REG_GENERAL_CONFIG 0x05
+#define AD7768_GEN_CONFIG_VCM_SEL_MSK GENMASK(1, 0)
+#define AD7768_GEN_CONFIG_VCM_PD BIT(4)
+
+#define AD7768_REG_DATA_CONTROL 0x06
+#define AD7768_DATA_CONTROL_SPI_RESET_1 0x03
+#define AD7768_DATA_CONTROL_SPI_RESET_2 0x02
+#define AD7768_DATA_CONTROL_SPI_SYNC BIT(7)
+
+#define AD7768_REG_INTERFACE_CFG 0x07
+#define AD7768_INTERFACE_CFG_CRC_SELECT_MSK GENMASK(3, 2)
+#define AD7768_INTERFACE_CFG_CRC_SELECT_4 0x1
+#define AD7768_INTERFACE_CFG_DCLK_DIV_MSK GENMASK(1, 0)
+#define AD7768_INTERFACE_CFG_DCLK_DIV_8 0x0
+#define AD7768_INTERFACE_CFG_DCLK_DIV_4 0x1
+#define AD7768_INTERFACE_CFG_DCLK_DIV_2 0x2
+#define AD7768_INTERFACE_CFG_DCLK_DIV_1 0x3
+
+#define AD7768_REG_REV_ID 0x0A
+#define AD7768_REV_ID_VAL 0x06
+
+#define AD7768_REG_GPIO_CONTROL 0x0E
+#define AD7768_GPIO_UGPIO_ENABLE BIT(7)
+#define AD7768_GPIO4_OUTPUT_ENABLE BIT(4)
+
+#define AD7768_REG_GPIO_WRITE 0x0F
+
+#define AD7768_REG_PRECHARGE_BUF1 0x11
+#define AD7768_REG_PRECHARGE_BUF2 0x12
+#define AD7768_PREBUF_POS_EN(ch) BIT((ch) * 2 + 0)
+#define AD7768_PREBUF_NEG_EN(ch) BIT((ch) * 2 + 1)
+
+#define AD7768_REG_REFP_BUF 0x13
+#define AD7768_REG_REFN_BUF 0x14
+
+#define AD7768_REG_OFFSET_BASE 0x1E
+#define AD7768_REG_GAIN_BASE 0x36
+#define AD7768_REG_PHASE_BASE 0x4E
+#define AD7768_REG_OFFSET(ch) (AD7768_REG_OFFSET_BASE + 3 * (ch))
+#define AD7768_REG_GAIN(ch) (AD7768_REG_GAIN_BASE + 3 * (ch))
+#define AD7768_REG_PHASE(ch) (AD7768_REG_PHASE_BASE + (ch))
+
+#define AD7768_REG_DIAGNOSTIC_RX 0x56
+
+#define AD7768_REG_CHOP_CTRL 0x59
+
+#define AD7768_SPI_READ_CMD BIT(15)
+#define AD7768_SPI_REG_MASK GENMASK(14, 8)
+#define AD7768_SPI_DATA_MASK GENMASK(7, 0)
+
+#define AD7768_SAMPLE_SIZE 32
+#define AD7768_MAX_DCLK_DIV 8
+#define AD7768_MIN_MCLK_FREQ_HZ (1150 * HZ_PER_KHZ)
+#define AD7768_MIN_XTAL_FREQ_HZ (8 * HZ_PER_MHZ)
+#define AD7768_MAX_MCLK_FREQ_HZ (34 * HZ_PER_MHZ)
+#define AD7768_MAX_FREQ_PER_MODE 6
+#define AD7768_MAX_CHANNEL 8
+#define AD7768_NUM_CHANNEL_MODES 2
+#define AD7768_CALIB_SIGN_BIT 23
+#define AD7768_CALIB_REG_MSK GENMASK(AD7768_CALIB_SIGN_BIT, 0)
+#define AD7768_WIDEBAND_SETTLING_SAMPLES 68
+#define AD7768_SINC5_SETTLING_SAMPLES 7
+
+enum ad7768_filter_type {
+ AD7768_FILTER_TYPE_WIDEBAND,
+ AD7768_FILTER_TYPE_SINC5,
+};
+
+enum ad7768_clock_source {
+ AD7768_CLOCK_SOURCE_MCLK,
+ AD7768_CLOCK_SOURCE_XTAL,
+ AD7768_CLOCK_SOURCE_LVDS,
+};
+
+struct ad7768_power_mode_info {
+ unsigned int mode;
+ unsigned int mclk_div;
+};
+
+static const struct ad7768_power_mode_info ad7768_power_modes[] = {
+ { .mode = AD7768_POWER_MODE_POWER_MODE_LOW, .mclk_div = 32 },
+ { .mode = AD7768_POWER_MODE_POWER_MODE_MEDIAN, .mclk_div = 8 },
+ { .mode = AD7768_POWER_MODE_POWER_MODE_FAST, .mclk_div = 4 },
+};
+
+#define AD7768_MAX_FREQS \
+ (AD7768_MAX_FREQ_PER_MODE + ARRAY_SIZE(ad7768_power_modes))
+
+struct ad7768_precharge_config {
+ bool prebufp_en;
+ bool prebufn_en;
+ bool refbufp;
+ bool refbufn;
+};
+
+struct ad7768_freq_config {
+ unsigned int freq_hz;
+ unsigned int dec_rate;
+};
+
+struct ad7768_convdelay_params {
+ unsigned int shift;
+ unsigned int max_raw;
+ u64 step_ps;
+};
+
+struct ad7768_avail_freq {
+ unsigned int n_freqs;
+ struct ad7768_freq_config freq_cfg[AD7768_MAX_FREQ_PER_MODE];
+};
+
+struct ad7768_chip_info {
+ const char *name;
+ const struct regmap_config *regmap_config;
+ const unsigned int *available_datalines;
+ unsigned int num_datalines;
+ unsigned int num_channels;
+ const u8 *chan_map;
+ u8 prebuf_split;
+};
+
+struct ad7768_state {
+ struct regmap *regmap;
+ /* Protects device register access and configuration. */
+ struct mutex lock;
+ struct clk *mclk;
+ unsigned int datalines;
+ enum ad7768_clock_source clock_source;
+ const struct ad7768_chip_info *chip_info;
+ struct ad7768_avail_freq avail_freq[ARRAY_SIZE(ad7768_power_modes)];
+ unsigned int n_freqs;
+ int freqs[AD7768_MAX_FREQS];
+ u64 ch_convdelay_ps[AD7768_MAX_CHANNEL];
+ unsigned int ch_freq[AD7768_MAX_CHANNEL];
+ unsigned int power_mode_idx;
+ enum ad7768_filter_type ch_filter[AD7768_MAX_CHANNEL];
+ struct iio_backend *back;
+ struct regulator_dev *vcm_rdev;
+ unsigned int avdd1_uV;
+ unsigned int vref_uV[2];
+
+ /* Used only in regmap_read() callback, hence guarded by regmap lock. */
+ __be16 d16 __aligned(IIO_DMA_MINALIGN);
+};
+
+static const unsigned int ad7768_vcm_voltage_table[] = {
+ 0, 1650000, 2500000, 2140000,
+};
+
+static int ad7768_vcm_list_voltage(struct regulator_dev *rdev,
+ unsigned int selector)
+{
+ struct ad7768_state *st = rdev_get_drvdata(rdev);
+
+ if (selector >= ARRAY_SIZE(ad7768_vcm_voltage_table))
+ return -EINVAL;
+
+ if (!selector)
+ return DIV_ROUND_CLOSEST(st->avdd1_uV, 2);
+
+ return ad7768_vcm_voltage_table[selector];
+}
+
+static int ad7768_vcm_enable(struct regulator_dev *rdev)
+{
+ struct ad7768_state *st = rdev_get_drvdata(rdev);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(regmap_get_device(st->regmap));
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_clear_bits(st->regmap, AD7768_REG_GENERAL_CONFIG,
+ AD7768_GEN_CONFIG_VCM_PD);
+ if (ret)
+ pm_runtime_put_autosuspend(regmap_get_device(st->regmap));
+
+ return ret;
+}
+
+static int ad7768_vcm_disable(struct regulator_dev *rdev)
+{
+ struct ad7768_state *st = rdev_get_drvdata(rdev);
+ int ret;
+
+ ret = regmap_set_bits(st->regmap, AD7768_REG_GENERAL_CONFIG,
+ AD7768_GEN_CONFIG_VCM_PD);
+ if (ret)
+ return ret;
+
+ pm_runtime_put_autosuspend(regmap_get_device(st->regmap));
+
+ return 0;
+}
+
+static int ad7768_vcm_is_enabled(struct regulator_dev *rdev)
+{
+ struct ad7768_state *st = rdev_get_drvdata(rdev);
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ ret = regmap_test_bits(st->regmap, AD7768_REG_GENERAL_CONFIG,
+ AD7768_GEN_CONFIG_VCM_PD);
+ if (ret < 0)
+ return ret;
+
+ return !ret;
+}
+
+static int ad7768_vcm_set_voltage_sel(struct regulator_dev *rdev,
+ unsigned int selector)
+{
+ struct ad7768_state *st = rdev_get_drvdata(rdev);
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(st->regmap, AD7768_REG_GENERAL_CONFIG,
+ AD7768_GEN_CONFIG_VCM_SEL_MSK,
+ FIELD_PREP(AD7768_GEN_CONFIG_VCM_SEL_MSK,
+ selector));
+}
+
+static int ad7768_vcm_get_voltage_sel(struct regulator_dev *rdev)
+{
+ struct ad7768_state *st = rdev_get_drvdata(rdev);
+ unsigned int val;
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(st->regmap, AD7768_REG_GENERAL_CONFIG, &val);
+ if (ret)
+ return ret;
+
+ return FIELD_GET(AD7768_GEN_CONFIG_VCM_SEL_MSK, val);
+}
+
+static const struct regulator_ops ad7768_vcm_ops = {
+ .enable = ad7768_vcm_enable,
+ .disable = ad7768_vcm_disable,
+ .is_enabled = ad7768_vcm_is_enabled,
+ .list_voltage = ad7768_vcm_list_voltage,
+ .set_voltage_sel = ad7768_vcm_set_voltage_sel,
+ .get_voltage_sel = ad7768_vcm_get_voltage_sel,
+};
+
+static const struct regulator_desc ad7768_vcm_desc = {
+ .name = "vcm",
+ .of_match = "vcm-output",
+ .regulators_node = "regulators",
+ .n_voltages = ARRAY_SIZE(ad7768_vcm_voltage_table),
+ .ops = &ad7768_vcm_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+};
+
+static int ad7768_register_vcm_regulator(struct device *dev,
+ struct ad7768_state *st)
+{
+ struct regulator_config config = {
+ .dev = dev,
+ .driver_data = st,
+ };
+ int ret;
+
+ /*
+ * Start with VCM disabled so each enabled state is paired with the
+ * runtime PM reference acquired by ad7768_vcm_enable().
+ */
+ ret = regmap_set_bits(st->regmap, AD7768_REG_GENERAL_CONFIG,
+ AD7768_GEN_CONFIG_VCM_PD);
+ if (ret)
+ return ret;
+
+ st->vcm_rdev = devm_regulator_register(dev, &ad7768_vcm_desc, &config);
+ return PTR_ERR_OR_ZERO(st->vcm_rdev);
+}
+
+static const unsigned int ad7768_dec_rate[AD7768_MAX_FREQ_PER_MODE] = {
+ 32, 64, 128, 256, 512, 1024,
+};
+
+static const unsigned int ad7768_available_datalines[] = {
+ 1, 2, 8,
+};
+
+static const unsigned int ad7768_4_available_datalines[] = {
+ 1, 4,
+};
+
+static const u8 ad7768_chan_map[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7,
+};
+
+static const u8 ad7768_4_chan_map[] = {
+ 0, 1, 4, 5,
+};
+
+static const char * const ad7768_supply_names[] = {
+ "avdd2", "iovdd",
+};
+
+static const char * const ad7768_vref_supply_names[][2] = {
+ { "ref1p", "ref1n" },
+ { "ref2p", "ref2n" },
+};
+
+static const char * const ad7768_clock_names[] = {
+ [AD7768_CLOCK_SOURCE_MCLK] = "mclk",
+ [AD7768_CLOCK_SOURCE_XTAL] = "xtal",
+ [AD7768_CLOCK_SOURCE_LVDS] = "lvds",
+};
+
+static u8 ad7768_channel_mask(const struct ad7768_state *st, u8 ch)
+{
+ return BIT(st->chip_info->chan_map[ch]);
+}
+
+static u8 ad7768_channel_mode_mask(const struct ad7768_state *st, u8 ch)
+{
+ /*
+ * AD7768-4 channels 2 and 3 use bits 4 and 5 for CH_x_MODE, while
+ * their CH_x_MDE_EN fields are in bits 2 and 3 and must be programmed
+ * to the same value. On AD7768, chan_map[] is an identity map, so the
+ * OR produces a single bit.
+ */
+ return BIT(ch) | ad7768_channel_mask(st, ch);
+}
+
+static u8 ad7768_all_standby_mask(const struct ad7768_state *st)
+{
+ return GENMASK(st->chip_info->num_channels - 1, 0);
+}
+
+static void ad7768_keep_xtal_channel_active(const struct ad7768_state *st,
+ unsigned long *standby_mask)
+{
+ /*
+ * Crystal excitation requires channel 4 on AD7768 or channel 2 on
+ * AD7768-4 to remain active.
+ */
+ if (st->clock_source == AD7768_CLOCK_SOURCE_XTAL)
+ __clear_bit(st->chip_info->num_channels / 2, standby_mask);
+}
+
+static unsigned int ad7768_offset_reg(const struct ad7768_state *st,
+ unsigned int ch)
+{
+ return AD7768_REG_OFFSET(st->chip_info->chan_map[ch]);
+}
+
+static unsigned int ad7768_gain_reg(const struct ad7768_state *st,
+ unsigned int ch)
+{
+ return AD7768_REG_GAIN(st->chip_info->chan_map[ch]);
+}
+
+static unsigned int ad7768_phase_reg(const struct ad7768_state *st,
+ unsigned int ch)
+{
+ return AD7768_REG_PHASE(st->chip_info->chan_map[ch]);
+}
+
+static u8 ad7768_precharge_buf1_mask(const struct ad7768_state *st, u16 val)
+{
+ return val & GENMASK(st->chip_info->prebuf_split - 1, 0);
+}
+
+static u8 ad7768_precharge_buf2_mask(const struct ad7768_state *st, u16 val)
+{
+ unsigned int split = st->chip_info->prebuf_split;
+
+ return field_get(GENMASK(2 * split - 1, split), val);
+}
+
+static int ad7768_regmap_read(void *context, const void *reg_buf,
+ size_t reg_size, void *val_buf, size_t val_size)
+{
+ struct ad7768_state *st = spi_get_drvdata(context);
+ struct spi_device *spi = context;
+ struct spi_transfer t[] = {
+ {
+ .tx_buf = &st->d16,
+ .len = sizeof(st->d16),
+ .cs_change = 1,
+ }, {
+ /*
+ * Register responses are delayed by one CS frame. While
+ * receiving the response to this read, the device also
+ * decodes another command on SDI. Repeat the read
+ * command to avoid sending an unspecified dummy
+ * command.
+ */
+ .tx_buf = &st->d16,
+ .rx_buf = &st->d16,
+ .len = sizeof(st->d16),
+ },
+ };
+ u8 *data_val = val_buf;
+ unsigned int reg;
+ int ret;
+
+ reg = *(const u8 *)reg_buf;
+
+ st->d16 = be16_replace_bits(cpu_to_be16(AD7768_SPI_READ_CMD), reg,
+ AD7768_SPI_REG_MASK);
+
+ ret = spi_sync_transfer(spi, t, ARRAY_SIZE(t));
+ if (ret)
+ return ret;
+
+ *data_val = be16_get_bits(st->d16, AD7768_SPI_DATA_MASK);
+
+ return 0;
+}
+
+static int ad7768_regmap_write(void *spi, const void *data, size_t count)
+{
+ return spi_write(spi, data, count);
+}
+
+static const struct regmap_bus ad7768_regmap_bus = {
+ .read = ad7768_regmap_read,
+ .write = ad7768_regmap_write,
+ .reg_format_endian_default = REGMAP_ENDIAN_BIG,
+ .val_format_endian_default = REGMAP_ENDIAN_BIG,
+};
+
+static bool ad7768_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case AD7768_REG_CH_STANDBY ... AD7768_REG_REV_ID:
+ case AD7768_REG_GPIO_CONTROL ... AD7768_REG_REFN_BUF:
+ case AD7768_REG_OFFSET(0) ... AD7768_REG_OFFSET(7) + 2:
+ case AD7768_REG_GAIN(0) ... AD7768_REG_GAIN(7) + 2:
+ case AD7768_REG_PHASE(0) ... AD7768_REG_PHASE(7):
+ case AD7768_REG_DIAGNOSTIC_RX ... AD7768_REG_CHOP_CTRL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool ad7768_4_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case AD7768_REG_CH_STANDBY ... AD7768_REG_REV_ID:
+ case AD7768_REG_GPIO_CONTROL ... AD7768_REG_REFN_BUF:
+ case AD7768_REG_OFFSET(0) ... AD7768_REG_OFFSET(1) + 2:
+ case AD7768_REG_OFFSET(4) ... AD7768_REG_OFFSET(5) + 2:
+ case AD7768_REG_GAIN(0) ... AD7768_REG_GAIN(1) + 2:
+ case AD7768_REG_GAIN(4) ... AD7768_REG_GAIN(5) + 2:
+ case AD7768_REG_PHASE(0) ... AD7768_REG_PHASE(1):
+ case AD7768_REG_PHASE(4) ... AD7768_REG_PHASE(5):
+ case AD7768_REG_DIAGNOSTIC_RX ... AD7768_REG_CHOP_CTRL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config ad7768_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = AD7768_REG_CHOP_CTRL,
+ /*
+ * Regmap bulk transfers use one starting address, while each register
+ * access requires a separate 16-bit SPI frame and reads use an off-frame
+ * response. Split bulk transfers into individual register accesses.
+ */
+ .use_single_read = true,
+ .use_single_write = true,
+ .readable_reg = ad7768_readable_reg,
+};
+
+static const struct regmap_config ad7768_4_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = AD7768_REG_CHOP_CTRL,
+ /*
+ * Regmap bulk transfers use one starting address, while each register
+ * access requires a separate 16-bit SPI frame and reads use an off-frame
+ * response. Split bulk transfers into individual register accesses. Do
+ * not use a register cache because some readable registers report live
+ * hardware state.
+ */
+ .use_single_read = true,
+ .use_single_write = true,
+ .readable_reg = ad7768_4_readable_reg,
+};
+
+static unsigned int ad7768_get_calib_reg_base(struct ad7768_state *st,
+ const struct iio_chan_spec *chan,
+ bool is_gain)
+{
+ if (is_gain)
+ return ad7768_gain_reg(st, chan->channel);
+
+ return ad7768_offset_reg(st, chan->channel);
+}
+
+static int ad7768_read_calib_value(struct ad7768_state *st,
+ unsigned int base_reg, bool is_signed,
+ int *val)
+{
+ unsigned int regval;
+ u8 data[3];
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = regmap_bulk_read(st->regmap, base_reg, data, sizeof(data));
+ if (ret)
+ return ret;
+
+ regval = get_unaligned_be24(data);
+ *val = is_signed ? sign_extend32(regval, AD7768_CALIB_SIGN_BIT) : regval;
+
+ return 0;
+}
+
+static int ad7768_write_calib_value(struct ad7768_state *st,
+ unsigned int base_reg, bool is_signed,
+ int val)
+{
+ u8 data[3];
+ int ret;
+
+ if (is_signed &&
+ val != sign_extend32(val, AD7768_CALIB_SIGN_BIT))
+ return -EINVAL;
+
+ if (!is_signed && (val < 0 || val > AD7768_CALIB_REG_MSK))
+ return -EINVAL;
+
+ put_unaligned_be24(val, data);
+
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&st->lock);
+
+ return regmap_bulk_write(st->regmap, base_reg, data, sizeof(data));
+}
+
+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;
+
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ if (readval)
+ return regmap_read(st->regmap, reg, readval);
+
+ return regmap_write(st->regmap, reg, writeval);
+}
+
+static int ad7768_sync(struct ad7768_state *st)
+{
+ int ret;
+
+ ret = regmap_clear_bits(st->regmap, AD7768_REG_DATA_CONTROL,
+ AD7768_DATA_CONTROL_SPI_SYNC);
+ if (ret)
+ return ret;
+
+ return regmap_set_bits(st->regmap, AD7768_REG_DATA_CONTROL,
+ AD7768_DATA_CONTROL_SPI_SYNC);
+}
+
+static int ad7768_set_power_mode(struct ad7768_state *st,
+ unsigned int mode_idx)
+{
+ const struct ad7768_power_mode_info *mode_info;
+ int ret;
+
+ mode_info = &ad7768_power_modes[mode_idx];
+ ret = regmap_update_bits(st->regmap, AD7768_REG_POWER_MODE,
+ AD7768_POWER_MODE_POWER_MODE_MSK |
+ AD7768_POWER_MODE_MCLK_DIV_MSK,
+ FIELD_PREP(AD7768_POWER_MODE_POWER_MODE_MSK,
+ mode_info->mode) |
+ FIELD_PREP(AD7768_POWER_MODE_MCLK_DIV_MSK,
+ mode_info->mode));
+ if (ret)
+ return ret;
+
+ ret = ad7768_sync(st);
+ if (ret)
+ return ret;
+
+ st->power_mode_idx = mode_idx;
+
+ return 0;
+}
+
+static const struct ad7768_freq_config *
+ad7768_find_freq_config(const struct ad7768_state *st,
+ unsigned int mode_idx, unsigned int freq)
+{
+ const struct ad7768_avail_freq *avail_freq = &st->avail_freq[mode_idx];
+
+ for (unsigned int i = 0; i < avail_freq->n_freqs; i++) {
+ if (freq == avail_freq->freq_cfg[i].freq_hz)
+ return &avail_freq->freq_cfg[i];
+ }
+
+ return NULL;
+}
+
+static int ad7768_set_clk_divs(struct ad7768_state *st, unsigned int freq)
+{
+ const struct ad7768_freq_config *freq_cfg;
+ unsigned int mclk, dclk, dclk_div;
+ unsigned int chan_per_doutx;
+ unsigned int dclk_div_reg;
+
+ freq_cfg = ad7768_find_freq_config(st, st->power_mode_idx, freq);
+ if (!freq_cfg)
+ return -EINVAL;
+
+ mclk = clk_get_rate(st->mclk);
+ chan_per_doutx = st->chip_info->num_channels / st->datalines;
+ if (!chan_per_doutx)
+ return -EINVAL;
+
+ dclk = freq_cfg->freq_hz * AD7768_SAMPLE_SIZE * chan_per_doutx;
+ if (dclk > mclk)
+ return -EINVAL;
+
+ dclk_div = DIV_ROUND_CLOSEST(mclk, dclk);
+
+ /* Set the divider to the next-lowest supported power of two. */
+ dclk_div = rounddown_pow_of_two(min(dclk_div, AD7768_MAX_DCLK_DIV));
+
+ switch (dclk_div) {
+ case 1:
+ dclk_div_reg = AD7768_INTERFACE_CFG_DCLK_DIV_1;
+ break;
+ case 2:
+ dclk_div_reg = AD7768_INTERFACE_CFG_DCLK_DIV_2;
+ break;
+ case 4:
+ dclk_div_reg = AD7768_INTERFACE_CFG_DCLK_DIV_4;
+ break;
+ case 8:
+ dclk_div_reg = AD7768_INTERFACE_CFG_DCLK_DIV_8;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return regmap_update_bits(st->regmap, AD7768_REG_INTERFACE_CFG,
+ AD7768_INTERFACE_CFG_DCLK_DIV_MSK,
+ FIELD_PREP(AD7768_INTERFACE_CFG_DCLK_DIV_MSK,
+ dclk_div_reg));
+}
+
+static int ad7768_set_mode_decimation(struct ad7768_state *st,
+ unsigned int freq, unsigned int mode)
+{
+ const struct ad7768_freq_config *freq_cfg;
+
+ freq_cfg = ad7768_find_freq_config(st, st->power_mode_idx, freq);
+ if (!freq_cfg)
+ return -EINVAL;
+
+ return regmap_update_bits(st->regmap, AD7768_REG_CH_MODE(mode),
+ AD7768_CH_MODE_DEC_RATE_MSK,
+ FIELD_PREP(AD7768_CH_MODE_DEC_RATE_MSK,
+ freq_cfg->dec_rate));
+}
+
+static int ad7768_set_lowest_noise_mode(struct ad7768_state *st,
+ const unsigned long *scan_mask)
+{
+ unsigned int mode_idx = ARRAY_SIZE(ad7768_power_modes);
+
+ /*
+ * The output data rate ranges overlap between the power modes. At a
+ * common ODR, the faster mode has lower noise, so prefer the fastest
+ * mode that supports every enabled channel.
+ */
+ while (mode_idx--) {
+ unsigned int channel;
+
+ for_each_set_bit(channel, scan_mask, st->chip_info->num_channels) {
+ if (!ad7768_find_freq_config(st, mode_idx,
+ st->ch_freq[channel]))
+ break;
+ }
+
+ /* All enabled channels support their requested frequency. */
+ if (channel == st->chip_info->num_channels)
+ return ad7768_set_power_mode(st, mode_idx);
+ }
+
+ return -EINVAL;
+}
+
+static bool ad7768_freq_supported_in_any_mode(const struct ad7768_state *st,
+ unsigned int freq)
+{
+ for (unsigned int mode_idx = 0;
+ mode_idx < ARRAY_SIZE(ad7768_power_modes); mode_idx++) {
+ if (ad7768_find_freq_config(st, mode_idx, freq))
+ return true;
+ }
+
+ return false;
+}
+
+static int ad7768_set_sampling_freq(struct iio_dev *indio_dev,
+ unsigned int freq, unsigned int channel)
+{
+ struct ad7768_state *st = iio_priv(indio_dev);
+
+ if (!freq || !ad7768_freq_supported_in_any_mode(st, freq))
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+ st->ch_freq[channel] = freq;
+
+ return 0;
+}
+
+static int ad7768_set_filter_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int mode)
+{
+ struct ad7768_state *st = iio_priv(indio_dev);
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ guard(mutex)(&st->lock);
+ st->ch_filter[chan->channel] = mode;
+
+ return 0;
+}
+
+static int ad7768_get_filter_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct ad7768_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+
+ return st->ch_filter[chan->channel];
+}
+
+static const char *const ad7768_filter_types[] = {
+ [AD7768_FILTER_TYPE_WIDEBAND] = "wideband",
+ [AD7768_FILTER_TYPE_SINC5] = "sinc5",
+};
+
+static const struct iio_enum ad7768_filter_types_enum = {
+ .items = ad7768_filter_types,
+ .num_items = ARRAY_SIZE(ad7768_filter_types),
+ .set = ad7768_set_filter_mode,
+ .get = ad7768_get_filter_mode,
+};
+
+static struct iio_chan_spec_ext_info ad7768_ext_info[] = {
+ IIO_ENUM("filter_type", IIO_SEPARATE, &ad7768_filter_types_enum),
+ IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ad7768_filter_types_enum),
+ { }
+};
+
+static int ad7768_find_matching_mode(const bool *mode_used,
+ const unsigned int *mode_freq,
+ const enum ad7768_filter_type *mode_filter,
+ unsigned int freq,
+ enum ad7768_filter_type filter)
+{
+ for (unsigned int mode = 0; mode < AD7768_NUM_CHANNEL_MODES; mode++) {
+ if (!mode_used[mode] ||
+ (mode_freq[mode] == freq && mode_filter[mode] == filter))
+ return mode;
+ }
+
+ return -EINVAL;
+}
+
+static int ad7768_get_convdelay_params(struct ad7768_state *st, unsigned int ch,
+ struct ad7768_convdelay_params *params)
+{
+ const struct ad7768_freq_config *freq_cfg;
+ unsigned int dec_rate;
+ unsigned int mclk_div;
+ unsigned long mclk;
+ u64 mult;
+
+ freq_cfg = ad7768_find_freq_config(st, st->power_mode_idx,
+ st->ch_freq[ch]);
+ if (!freq_cfg)
+ return -EINVAL;
+
+ dec_rate = ad7768_dec_rate[freq_cfg->dec_rate];
+ switch (dec_rate) {
+ case 32:
+ params->shift = 3;
+ params->max_raw = 31;
+ mult = 1;
+ break;
+ case 64:
+ params->shift = 2;
+ params->max_raw = 63;
+ mult = 1;
+ break;
+ case 128:
+ params->shift = 1;
+ params->max_raw = 127;
+ mult = 1;
+ break;
+ case 256:
+ params->shift = 0;
+ params->max_raw = 255;
+ mult = 1;
+ break;
+ case 512:
+ params->shift = 0;
+ params->max_raw = 255;
+ mult = 2;
+ break;
+ case 1024:
+ params->shift = 0;
+ params->max_raw = 255;
+ mult = 4;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mclk = clk_get_rate(st->mclk);
+ if (!mclk || mclk > U32_MAX)
+ return -EINVAL;
+
+ mclk_div = ad7768_power_modes[st->power_mode_idx].mclk_div;
+ params->step_ps =
+ DIV_ROUND_CLOSEST_ULL(mult * PSEC_PER_SEC * mclk_div, (u32)mclk);
+ if (params->step_ps > U32_MAX)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ad7768_set_channel_convdelay(struct ad7768_state *st,
+ unsigned int ch)
+{
+ u64 delay_ps = st->ch_convdelay_ps[ch];
+ struct ad7768_convdelay_params params;
+ u64 max_delay_ps;
+ u64 raw;
+ int ret;
+
+ ret = ad7768_get_convdelay_params(st, ch, &params);
+ if (ret)
+ return ret;
+
+ /*
+ * Validate the requested delay before rounding so an out-of-range value
+ * cannot round down into the valid range. Then verify that the rounded
+ * value fits in the hardware register.
+ */
+ max_delay_ps = (u64)params.max_raw * params.step_ps;
+ if (delay_ps > max_delay_ps)
+ return -EINVAL;
+
+ raw = DIV_ROUND_CLOSEST_ULL(delay_ps, (u32)params.step_ps);
+ if (raw > params.max_raw)
+ return -EINVAL;
+
+ return regmap_write(st->regmap, ad7768_phase_reg(st, ch),
+ raw << params.shift);
+}
+
+static void ad7768_filter_wait(const unsigned int *mode_freq,
+ const enum ad7768_filter_type *mode_filter,
+ const bool *mode_used)
+{
+ unsigned int t_settle_us = 0;
+
+ for (unsigned int mode = 0; mode < AD7768_NUM_CHANNEL_MODES; mode++) {
+ unsigned int t_mode_us;
+ u8 settling_samples;
+
+ if (!mode_used[mode] || !mode_freq[mode])
+ continue;
+
+ if (mode_filter[mode] == AD7768_FILTER_TYPE_SINC5)
+ settling_samples = AD7768_SINC5_SETTLING_SAMPLES;
+ else
+ settling_samples = AD7768_WIDEBAND_SETTLING_SAMPLES;
+
+ t_mode_us = DIV_ROUND_UP(settling_samples * USEC_PER_SEC,
+ mode_freq[mode]);
+ t_settle_us = max(t_settle_us, t_mode_us);
+ }
+
+ if (t_settle_us)
+ fsleep(t_settle_us);
+}
+
+static int ad7768_apply_channel_modes(struct iio_dev *indio_dev,
+ const unsigned long *scan_mask)
+{
+ enum ad7768_filter_type mode_filter[AD7768_NUM_CHANNEL_MODES] = { };
+ unsigned int mode_freq[AD7768_NUM_CHANNEL_MODES] = { };
+ bool mode_used[AD7768_NUM_CHANNEL_MODES] = { };
+ struct ad7768_state *st = iio_priv(indio_dev);
+ struct device *dev = indio_dev->dev.parent;
+ unsigned long channel_mask;
+ unsigned long standby_mask;
+ unsigned int max_freq;
+ unsigned int c;
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = ad7768_set_lowest_noise_mode(st, scan_mask);
+ if (ret == -EINVAL)
+ return dev_err_probe(dev, ret,
+ "No power mode supports all ODRs\n");
+ if (ret)
+ return ret;
+
+ channel_mask = ad7768_all_standby_mask(st);
+ standby_mask = channel_mask & ~*scan_mask;
+ ad7768_keep_xtal_channel_active(st, &standby_mask);
+
+ for_each_set_bit(c, scan_mask, st->chip_info->num_channels) {
+ unsigned int mask;
+ int mode;
+
+ mode = ad7768_find_matching_mode(mode_used, mode_freq,
+ mode_filter, st->ch_freq[c],
+ st->ch_filter[c]);
+ if (mode < 0)
+ return dev_err_probe(dev, -EINVAL,
+ "Over %d channel modes required\n",
+ AD7768_NUM_CHANNEL_MODES);
+
+ mode_freq[mode] = st->ch_freq[c];
+ mode_filter[mode] = st->ch_filter[c];
+ mode_used[mode] = true;
+
+ mask = ad7768_channel_mode_mask(st, c);
+ ret = regmap_assign_bits(st->regmap, AD7768_REG_CH_MODE_SEL,
+ mask, mode);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_update_bits(st->regmap, AD7768_REG_CH_STANDBY,
+ channel_mask, standby_mask);
+ if (ret)
+ return ret;
+
+ max_freq = 0;
+ for (unsigned int mode = 0; mode < AD7768_NUM_CHANNEL_MODES; mode++) {
+ if (!mode_used[mode])
+ continue;
+
+ ret = ad7768_set_mode_decimation(st, mode_freq[mode], mode);
+ if (ret)
+ return ret;
+
+ ret = regmap_assign_bits(st->regmap, AD7768_REG_CH_MODE(mode),
+ AD7768_CH_MODE_FILTER_TYPE_MSK,
+ mode_filter[mode]);
+ if (ret)
+ return ret;
+
+ max_freq = max(max_freq, mode_freq[mode]);
+ }
+
+ for_each_set_bit(c, scan_mask, st->chip_info->num_channels) {
+ ret = ad7768_set_channel_convdelay(st, c);
+ if (ret == -EINVAL)
+ return dev_err_probe(regmap_get_device(st->regmap), ret,
+ "Invalid conversion delay for channel %u\n",
+ c);
+ if (ret)
+ return ret;
+ }
+
+ ret = ad7768_set_clk_divs(st, max_freq);
+ if (ret)
+ return ret;
+
+ ret = ad7768_sync(st);
+ if (ret)
+ return ret;
+
+ /* Apply a filter settling time (datasheet Tables 28 and 29). */
+ ad7768_filter_wait(mode_freq, mode_filter, mode_used);
+
+ return 0;
+}
+
+static int ad7768_update_scan_mode(struct iio_dev *indio_dev,
+ const unsigned long *scan_mask)
+{
+ struct ad7768_state *st = iio_priv(indio_dev);
+ int ret;
+
+ ret = ad7768_apply_channel_modes(indio_dev, scan_mask);
+ if (ret)
+ return ret;
+
+ for (unsigned int ch = 0; ch < st->chip_info->num_channels; ch++) {
+ if (test_bit(ch, scan_mask))
+ ret = iio_backend_chan_enable(st->back, ch);
+ else
+ ret = iio_backend_chan_disable(st->back, ch);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct ad7768_chip_info ad7768_chip_info = {
+ .name = "ad7768",
+ .num_channels = 8,
+ .regmap_config = &ad7768_regmap_config,
+ .available_datalines = ad7768_available_datalines,
+ .num_datalines = ARRAY_SIZE(ad7768_available_datalines),
+ .chan_map = ad7768_chan_map,
+ .prebuf_split = 8,
+};
+
+static const struct ad7768_chip_info ad7768_4_chip_info = {
+ .name = "ad7768-4",
+ .num_channels = 4,
+ .regmap_config = &ad7768_4_regmap_config,
+ .available_datalines = ad7768_4_available_datalines,
+ .num_datalines = ARRAY_SIZE(ad7768_4_available_datalines),
+ .chan_map = ad7768_4_chan_map,
+ .prebuf_split = 4,
+};
+
+static int ad7768_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct ad7768_state *st = iio_priv(indio_dev);
+
+ return pm_runtime_resume_and_get(regmap_get_device(st->regmap));
+}
+
+static int ad7768_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct ad7768_state *st = iio_priv(indio_dev);
+
+ pm_runtime_put_autosuspend(regmap_get_device(st->regmap));
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops ad7768_buffer_ops = {
+ .preenable = ad7768_buffer_preenable,
+ .postdisable = ad7768_buffer_postdisable,
+};
+
+static int ad7768_read_scale(struct ad7768_state *st,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2)
+{
+ unsigned int vref_idx;
+
+ vref_idx = chan->channel >= st->chip_info->num_channels / 2;
+ *val = 2 * st->vref_uV[vref_idx] / (MICRO / MILLI);
+ *val2 = chan->scan_type.realbits;
+
+ return IIO_VAL_FRACTIONAL_LOG2;
+}
+
+static int ad7768_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long info)
+{
+ struct ad7768_state *st = iio_priv(indio_dev);
+ int ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_SCALE:
+ return ad7768_read_scale(st, chan, val, val2);
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ guard(mutex)(&st->lock);
+ *val = st->ch_freq[chan->channel];
+
+ return IIO_VAL_INT;
+ }
+
+ case IIO_CHAN_INFO_CALIBBIAS:
+ case IIO_CHAN_INFO_CALIBSCALE: {
+ bool is_gain = info == IIO_CHAN_INFO_CALIBSCALE;
+ unsigned int base_reg;
+
+ base_reg = ad7768_get_calib_reg_base(st, chan, is_gain);
+ ret = ad7768_read_calib_value(st, base_reg, !is_gain, val);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_CONVDELAY: {
+ guard(mutex)(&st->lock);
+ iio_val_s64_decompose(st->ch_convdelay_ps[chan->channel],
+ val, val2);
+
+ return IIO_VAL_DECIMAL64_PICO;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad7768_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, long info)
+{
+ switch (info) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ case IIO_CHAN_INFO_CALIBBIAS:
+ case IIO_CHAN_INFO_CALIBSCALE:
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_CONVDELAY:
+ return IIO_VAL_DECIMAL64_PICO;
+ default:
+ return -EINVAL;
+ }
+}
+
+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);
+ unsigned int base_reg;
+ s64 delay_ps;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ switch (info) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return ad7768_set_sampling_freq(indio_dev, val, chan->channel);
+ case IIO_CHAN_INFO_CALIBBIAS:
+ base_reg = ad7768_get_calib_reg_base(st, chan, false);
+ return ad7768_write_calib_value(st, base_reg, true, val);
+ case IIO_CHAN_INFO_CALIBSCALE:
+ base_reg = ad7768_get_calib_reg_base(st, chan, true);
+ return ad7768_write_calib_value(st, base_reg, false, val);
+ case IIO_CHAN_INFO_CONVDELAY: {
+ delay_ps = iio_val_s64_compose(val, val2);
+ if (delay_ps < 0)
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+ st->ch_convdelay_ps[chan->channel] = delay_ps;
+ return 0;
+ }
+ default:
+ 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);
+
+ if (info != IIO_CHAN_INFO_SAMP_FREQ)
+ return -EINVAL;
+
+ *vals = st->freqs;
+ *type = IIO_VAL_INT;
+ *length = st->n_freqs;
+
+ return IIO_AVAIL_LIST;
+}
+
+static const struct iio_info ad7768_info = {
+ .debugfs_reg_access = ad7768_reg_access,
+ .read_raw = ad7768_read_raw,
+ .write_raw_get_fmt = ad7768_write_raw_get_fmt,
+ .write_raw = ad7768_write_raw,
+ .read_avail = ad7768_read_avail,
+ .update_scan_mode = ad7768_update_scan_mode,
+};
+
+static int ad7768_gpio_adev_init(struct ad7768_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ struct spi_device *spi = to_spi_device(dev);
+ struct auxiliary_device *adev;
+ int id;
+
+ if (!device_property_read_bool(dev, "gpio-controller"))
+ return 0;
+
+ /*
+ * Use the SPI bus number and chip select to derive a stable per-device
+ * ID.
+ */
+ id = (spi->controller->bus_num << 8) | spi_get_chipselect(spi, 0);
+ adev = __devm_auxiliary_device_create(dev, KBUILD_MODNAME, "gpio",
+ NULL, id);
+ if (!adev)
+ return dev_err_probe(dev, -ENODEV,
+ "Failed to create GPIO auxiliary device\n");
+
+ return 0;
+}
+
+static int ad7768_configure_precharge_buffers(struct iio_dev *indio_dev,
+ struct ad7768_precharge_config *precharge_cfg)
+{
+ struct ad7768_state *st = iio_priv(indio_dev);
+ u8 prebuf1_val, prebuf2_val;
+ u16 prebuf_mask = 0;
+ u8 refbufp_val = 0;
+ u8 refbufn_val = 0;
+ int ret;
+
+ for (unsigned int ch = 0; ch < indio_dev->num_channels; ch++) {
+ u8 channel = indio_dev->channels[ch].channel;
+
+ if (precharge_cfg[channel].prebufp_en)
+ prebuf_mask |= AD7768_PREBUF_POS_EN(channel);
+
+ if (precharge_cfg[channel].prebufn_en)
+ prebuf_mask |= AD7768_PREBUF_NEG_EN(channel);
+
+ if (precharge_cfg[channel].refbufp)
+ refbufp_val |= ad7768_channel_mask(st, channel);
+
+ if (precharge_cfg[channel].refbufn)
+ refbufn_val |= ad7768_channel_mask(st, channel);
+ }
+
+ prebuf1_val = ad7768_precharge_buf1_mask(st, ~prebuf_mask);
+ prebuf2_val = ad7768_precharge_buf2_mask(st, ~prebuf_mask);
+
+ ret = regmap_write(st->regmap, AD7768_REG_PRECHARGE_BUF1, prebuf1_val);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD7768_REG_PRECHARGE_BUF2, prebuf2_val);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD7768_REG_REFP_BUF, refbufp_val);
+ if (ret)
+ return ret;
+
+ return regmap_write(st->regmap, AD7768_REG_REFN_BUF, refbufn_val);
+}
+
+static void ad7768_set_available_sampling_freqs(struct ad7768_state *st)
+{
+ unsigned int n_power_modes = ARRAY_SIZE(ad7768_power_modes);
+ unsigned int mclk = clk_get_rate(st->mclk);
+
+ for (unsigned int mode_idx = 0; mode_idx < n_power_modes; mode_idx++) {
+ unsigned int dec = ARRAY_SIZE(ad7768_dec_rate);
+ struct ad7768_avail_freq *avail_freq;
+ unsigned int div;
+
+ avail_freq = &st->avail_freq[mode_idx];
+ div = ad7768_power_modes[mode_idx].mclk_div;
+ while (dec--) {
+ struct ad7768_freq_config *freq_cfg;
+
+ freq_cfg = &avail_freq->freq_cfg[avail_freq->n_freqs++];
+ freq_cfg->dec_rate = dec;
+ freq_cfg->freq_hz = mclk / (ad7768_dec_rate[dec] * div);
+ }
+ }
+
+ /* One DOUT line cannot carry the AD7768 fast-mode x32 output rate. */
+ if (st->datalines == 1 &&
+ st->chip_info->num_channels == AD7768_MAX_CHANNEL)
+ st->avail_freq[n_power_modes - 1].n_freqs--;
+
+ for (unsigned int mode_idx = 0; mode_idx < n_power_modes; mode_idx++) {
+ const struct ad7768_avail_freq *avail_freq;
+
+ avail_freq = &st->avail_freq[mode_idx];
+ for (unsigned int i = 0; i < avail_freq->n_freqs; i++) {
+ unsigned int freq = avail_freq->freq_cfg[i].freq_hz;
+
+ if (st->n_freqs && st->freqs[st->n_freqs - 1] >= freq)
+ continue;
+
+ st->freqs[st->n_freqs++] = freq;
+ }
+ }
+}
+
+static int ad7768_init_sampling_freqs(struct ad7768_state *st)
+{
+ const struct ad7768_avail_freq *avail_freq;
+ unsigned int default_freq;
+ int ret;
+
+ ret = ad7768_set_power_mode(st, ARRAY_SIZE(ad7768_power_modes) - 1);
+ if (ret)
+ return ret;
+
+ avail_freq = &st->avail_freq[st->power_mode_idx];
+ default_freq = avail_freq->freq_cfg[avail_freq->n_freqs - 1].freq_hz;
+ for (unsigned int channel = 0;
+ channel < st->chip_info->num_channels; channel++) {
+ st->ch_freq[channel] = default_freq;
+ st->ch_filter[channel] = AD7768_FILTER_TYPE_WIDEBAND;
+ }
+
+ return 0;
+}
+
+static int ad7768_parse_config(struct iio_dev *indio_dev,
+ struct device *dev)
+{
+ struct ad7768_precharge_config precharge_cfg[AD7768_MAX_CHANNEL] = { };
+ struct ad7768_state *st = iio_priv(indio_dev);
+ const unsigned int *available_datalines;
+ const char *propname;
+ bool datalines_valid = false;
+ struct iio_chan_spec *chan;
+ unsigned int num_channels;
+ unsigned long standby_mask;
+ unsigned int len;
+ int chan_idx = 0;
+ int ret;
+
+ propname = "adi,data-lines-number";
+ num_channels = device_get_named_child_node_count(dev, "channel");
+ if (num_channels == 0)
+ return dev_err_probe(dev, -ENOENT, "No channel specified\n");
+ if (num_channels > st->chip_info->num_channels)
+ return dev_err_probe(dev, -ENOSPC, "Invalid number of channels\n");
+
+ chan = devm_kcalloc(dev, num_channels, sizeof(*chan), GFP_KERNEL);
+ if (!chan)
+ return -ENOMEM;
+
+ indio_dev->channels = chan;
+ indio_dev->num_channels = num_channels;
+
+ standby_mask = ad7768_all_standby_mask(st);
+ ad7768_keep_xtal_channel_active(st, &standby_mask);
+
+ ret = regmap_write(st->regmap, AD7768_REG_CH_STANDBY, standby_mask);
+ if (ret)
+ return ret;
+
+ device_for_each_named_child_node_scoped(dev, child, "channel") {
+ u32 channel;
+
+ ret = fwnode_property_read_u32(child, "reg", &channel);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to parse reg of %pfwP\n",
+ child);
+
+ if (channel >= st->chip_info->num_channels)
+ return dev_err_probe(dev, -ECHRNG,
+ "Invalid channel %u in firmware\n",
+ channel);
+
+ ret = regmap_clear_bits(st->regmap, AD7768_REG_CH_STANDBY,
+ BIT(channel));
+ if (ret)
+ return ret;
+
+ precharge_cfg[channel].prebufp_en =
+ fwnode_property_read_bool(child, "adi,prechargebuf-pos-enable");
+ precharge_cfg[channel].prebufn_en =
+ fwnode_property_read_bool(child, "adi,prechargebuf-neg-enable");
+ precharge_cfg[channel].refbufp =
+ fwnode_property_read_bool(child, "adi,refbuf-pos-enable");
+ precharge_cfg[channel].refbufn =
+ fwnode_property_read_bool(child, "adi,refbuf-neg-enable");
+
+ chan[chan_idx++] = (struct iio_chan_spec) {
+ .type = IIO_VOLTAGE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_CALIBBIAS) |
+ BIT(IIO_CHAN_INFO_CALIBSCALE) |
+ BIT(IIO_CHAN_INFO_CONVDELAY) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_separate_available =
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .indexed = 1,
+ .channel = channel,
+ .scan_index = channel,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 24,
+ .storagebits = 32,
+ },
+ .ext_info = ad7768_ext_info,
+ };
+ }
+
+ ret = ad7768_configure_precharge_buffers(indio_dev, precharge_cfg);
+ if (ret)
+ return ret;
+
+ available_datalines = st->chip_info->available_datalines;
+ len = st->chip_info->num_datalines;
+ if (device_property_present(dev, propname)) {
+ ret = device_property_read_u32(dev, propname, &st->datalines);
+ if (ret)
+ return dev_err_probe(dev, ret, "Invalid %s property\n",
+ propname);
+ } else {
+ st->datalines = available_datalines[len - 1];
+ }
+
+ for (unsigned int i = 0; i < len; i++) {
+ if (available_datalines[i] == st->datalines) {
+ datalines_valid = true;
+ break;
+ }
+ }
+
+ if (!datalines_valid)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid %s %u for %s\n", propname,
+ st->datalines, st->chip_info->name);
+
+ ad7768_set_available_sampling_freqs(st);
+
+ ret = ad7768_init_sampling_freqs(st);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to set power mode\n");
+
+ return 0;
+}
+
+static int ad7768_reset(struct ad7768_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ struct reset_control *reset_ctrl;
+ unsigned long reset_low_us;
+ unsigned long mclk;
+ int ret;
+
+ reset_ctrl = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(reset_ctrl))
+ return PTR_ERR(reset_ctrl);
+
+ if (reset_ctrl) {
+ mclk = clk_get_rate(st->mclk);
+ if (!mclk)
+ return -EINVAL;
+
+ /*
+ * Minimum RESET low pulse width: 2 x tMCLK
+ * (datasheet Table 1).
+ */
+ reset_low_us = DIV_ROUND_UP_ULL(2ULL * USEC_PER_SEC, mclk);
+
+ ret = reset_control_assert(reset_ctrl);
+ if (ret)
+ return ret;
+
+ fsleep(max(1UL, reset_low_us));
+
+ ret = reset_control_deassert(reset_ctrl);
+ if (ret)
+ return ret;
+ } else {
+ ret = regmap_write(st->regmap, AD7768_REG_DATA_CONTROL,
+ AD7768_DATA_CONTROL_SPI_RESET_1);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD7768_REG_DATA_CONTROL,
+ AD7768_DATA_CONTROL_SPI_RESET_2);
+ if (ret)
+ return ret;
+ }
+
+ /* ADC start-up time after reset: 1.66 ms max (datasheet Table 1) */
+ fsleep(1660);
+
+ return 0;
+}
+
+static void ad7768_disable_clk(void *clk)
+{
+ clk_disable_unprepare(clk);
+}
+
+static int ad7768_configure_xtal_clock(struct ad7768_state *st)
+{
+ int ret;
+
+ ret = regmap_set_bits(st->regmap, AD7768_REG_GPIO_WRITE, BIT(4));
+ if (ret)
+ return ret;
+
+ return regmap_set_bits(st->regmap, AD7768_REG_GPIO_CONTROL,
+ AD7768_GPIO_UGPIO_ENABLE |
+ AD7768_GPIO4_OUTPUT_ENABLE);
+}
+
+static int ad7768_enable_lvds_clock(struct ad7768_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ int ret;
+
+ ret = regmap_clear_bits(st->regmap, AD7768_REG_GPIO_WRITE, BIT(4));
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(st->regmap, AD7768_REG_GPIO_CONTROL,
+ AD7768_GPIO_UGPIO_ENABLE |
+ AD7768_GPIO4_OUTPUT_ENABLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(st->regmap, AD7768_REG_POWER_MODE,
+ AD7768_POWER_MODE_LVDS_ENABLE);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(st->mclk);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, ad7768_disable_clk, st->mclk);
+}
+
+static int ad7768_get_enable_vref(struct device *dev, unsigned int index)
+{
+ const char * const *supply = ad7768_vref_supply_names[index];
+ int refp_uV;
+ int refn_uV;
+
+ refp_uV = devm_regulator_get_enable_read_voltage(dev, supply[0]);
+ if (refp_uV < 0)
+ return dev_err_probe(dev, refp_uV,
+ "Failed to get %s supply voltage\n", supply[0]);
+
+ refn_uV = devm_regulator_get_enable_read_voltage(dev, supply[1]);
+ if (refn_uV == -ENODEV)
+ refn_uV = 0;
+ else if (refn_uV < 0)
+ return dev_err_probe(dev, refn_uV,
+ "Failed to get %s supply voltage\n", supply[1]);
+
+ if (refp_uV <= refn_uV)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid reference %u voltage\n", index + 1);
+
+ return refp_uV - refn_uV;
+}
+
+static int ad7768_enter_sleep(struct ad7768_state *st)
+{
+ return regmap_set_bits(st->regmap, AD7768_REG_POWER_MODE,
+ AD7768_SLEEP_MODE_MSK);
+}
+
+static void ad7768_power_off(void *data)
+{
+ struct ad7768_state *st = data;
+ struct device *dev = regmap_get_device(st->regmap);
+ int ret;
+
+ ret = ad7768_enter_sleep(st);
+ if (ret)
+ dev_err(dev, "Failed to put device to sleep\n");
+}
+
+static int ad7768_validate_mclk_rate(struct device *dev,
+ const struct ad7768_state *st)
+{
+ unsigned long min_rate = AD7768_MIN_MCLK_FREQ_HZ;
+ unsigned long rate = clk_get_rate(st->mclk);
+
+ if (st->clock_source == AD7768_CLOCK_SOURCE_XTAL)
+ min_rate = AD7768_MIN_XTAL_FREQ_HZ;
+
+ if (rate < min_rate || rate > AD7768_MAX_MCLK_FREQ_HZ)
+ return dev_err_probe(dev, -EINVAL,
+ "MCLK rate %lu Hz outside %lu-%lu Hz\n",
+ rate, min_rate, AD7768_MAX_MCLK_FREQ_HZ);
+
+ return 0;
+}
+
+static int ad7768_probe(struct spi_device *spi)
+{
+ unsigned int spi_readback, rev_id;
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ad7768_state *st;
+ const char *clock_name;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ spi_set_drvdata(spi, st);
+
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ st->chip_info = spi_get_device_match_data(spi);
+ if (!st->chip_info)
+ return dev_err_probe(dev, -ENODATA, "Failed to get match data\n");
+
+ ret = devm_regulator_get_enable_optional(dev, "avss");
+ if (ret == -ENODEV) {
+ /* AVSS may be connected directly to ground instead of a regulator. */
+ } else if (ret) {
+ return dev_err_probe(dev, ret, "Failed to enable AVSS supply\n");
+ }
+
+ ret = devm_regulator_get_enable_read_voltage(dev, "avdd1");
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to enable AVDD1 supply\n");
+
+ st->avdd1_uV = ret;
+
+ ret = devm_regulator_bulk_get_enable(dev,
+ ARRAY_SIZE(ad7768_supply_names),
+ ad7768_supply_names);
+ if (ret)
+ return ret;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(ad7768_vref_supply_names); i++) {
+ ret = ad7768_get_enable_vref(dev, i);
+ if (ret < 0)
+ return ret;
+
+ st->vref_uV[i] = ret;
+ }
+
+ ret = device_property_match_property_string(dev, "clock-names",
+ ad7768_clock_names,
+ ARRAY_SIZE(ad7768_clock_names));
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Invalid clock source\n");
+
+ st->clock_source = ret;
+ clock_name = ad7768_clock_names[st->clock_source];
+
+ /*
+ * The device must start on its internal clock. Keep the LVDS clock
+ * disabled until GPIO4 is driven low and the LVDS input is enabled in
+ * the power mode register.
+ */
+ switch (st->clock_source) {
+ case AD7768_CLOCK_SOURCE_MCLK:
+ case AD7768_CLOCK_SOURCE_XTAL:
+ st->mclk = devm_clk_get_enabled(dev, clock_name);
+ break;
+ case AD7768_CLOCK_SOURCE_LVDS:
+ st->mclk = devm_clk_get(dev, clock_name);
+ break;
+ }
+
+ if (IS_ERR(st->mclk))
+ return dev_err_probe(dev, PTR_ERR(st->mclk),
+ "Failed to get master clock\n");
+
+ ret = ad7768_validate_mclk_rate(dev, st);
+ if (ret)
+ return ret;
+
+ st->regmap = devm_regmap_init(dev, &ad7768_regmap_bus, spi,
+ st->chip_info->regmap_config);
+ if (IS_ERR(st->regmap))
+ return PTR_ERR(st->regmap);
+
+ ret = ad7768_reset(st);
+ if (ret)
+ return ret;
+
+ /* Discard the reset response with a dummy SPI register read. */
+ ret = regmap_read(st->regmap, AD7768_REG_REV_ID, &spi_readback);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(st->regmap, AD7768_REG_REV_ID, &rev_id);
+ if (ret)
+ return ret;
+
+ if (rev_id != AD7768_REV_ID_VAL)
+ dev_info(dev, "Unexpected revision ID 0x%02x\n", rev_id);
+
+ switch (st->clock_source) {
+ case AD7768_CLOCK_SOURCE_MCLK:
+ break;
+ case AD7768_CLOCK_SOURCE_XTAL:
+ ret = ad7768_configure_xtal_clock(st);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to configure crystal clock\n");
+ break;
+ case AD7768_CLOCK_SOURCE_LVDS:
+ ret = ad7768_enable_lvds_clock(st);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to enable LVDS clock\n");
+ break;
+ }
+
+ ret = devm_add_action_or_reset(dev, ad7768_power_off, st);
+ if (ret)
+ return ret;
+
+ ret = ad7768_parse_config(indio_dev, dev);
+ if (ret)
+ return ret;
+
+ /*
+ * Hardware supports CRC every 4 or 16 samples; the backend supports only
+ * 4-sample CRC.
+ */
+ ret = regmap_update_bits(st->regmap, AD7768_REG_INTERFACE_CFG,
+ AD7768_INTERFACE_CFG_CRC_SELECT_MSK,
+ FIELD_PREP(AD7768_INTERFACE_CFG_CRC_SELECT_MSK,
+ AD7768_INTERFACE_CFG_CRC_SELECT_4));
+ if (ret)
+ return ret;
+
+ indio_dev->name = st->chip_info->name;
+ indio_dev->info = &ad7768_info;
+ indio_dev->setup_ops = &ad7768_buffer_ops;
+
+ 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_num_lanes_set(st->back, st->datalines);
+ if (ret)
+ return ret;
+
+ ret = iio_backend_crc_enable(st->back);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_backend_enable(dev, st->back);
+ if (ret)
+ return ret;
+
+ pm_runtime_set_autosuspend_delay(dev, 2000);
+ pm_runtime_use_autosuspend(dev);
+ ret = devm_pm_runtime_set_active_enabled(dev);
+ if (ret)
+ return ret;
+
+ ret = ad7768_register_vcm_regulator(dev, st);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register VCM regulator\n");
+
+ ret = ad7768_gpio_adev_init(st);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static int ad7768_runtime_suspend(struct device *dev)
+{
+ struct ad7768_state *st = dev_get_drvdata(dev);
+
+ return ad7768_enter_sleep(st);
+}
+
+static int ad7768_runtime_resume(struct device *dev)
+{
+ struct ad7768_state *st = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regmap_clear_bits(st->regmap, AD7768_REG_POWER_MODE,
+ AD7768_SLEEP_MODE_MSK);
+ if (ret)
+ return ret;
+
+ /*
+ * The datasheet does not specify a wake-up time. Allow 20 ms for the
+ * ADC and digital clocks to restart.
+ */
+ fsleep(20 * USEC_PER_MSEC);
+
+ return 0;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(ad7768_pm_ops, ad7768_runtime_suspend,
+ ad7768_runtime_resume, NULL);
+
+static const struct of_device_id ad7768_of_match[] = {
+ { .compatible = "adi,ad7768", .data = &ad7768_chip_info },
+ { .compatible = "adi,ad7768-4", .data = &ad7768_4_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad7768_of_match);
+
+static const struct spi_device_id ad7768_spi_id[] = {
+ { .name = "ad7768", .driver_data = (kernel_ulong_t)&ad7768_chip_info },
+ { .name = "ad7768-4", .driver_data = (kernel_ulong_t)&ad7768_4_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ad7768_spi_id);
+
+static struct spi_driver ad7768_driver = {
+ .probe = ad7768_probe,
+ .driver = {
+ .name = "ad7768",
+ .of_match_table = ad7768_of_match,
+ .pm = pm_ptr(&ad7768_pm_ops),
+ },
+ .id_table = ad7768_spi_id,
+};
+module_spi_driver(ad7768_driver);
+
+MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>");
+MODULE_AUTHOR("Janani Sunil <janani.sunil@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD7768 ADC driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_BACKEND");
diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c
index 4fc0eb7e70a8..c4450f038898 100644
--- a/drivers/iio/adc/ade9000.c
+++ b/drivers/iio/adc/ade9000.c
@@ -242,14 +242,6 @@
#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
@@ -290,7 +282,29 @@ enum ade9000_wfb_cfg {
#define ADE9000_ADDR_ADJUST(addr, chan) \
(((chan) == 0 ? 0 : (chan) == 1 ? 2 : 4) << 4 | (addr))
+/**
+ * struct ade9000_chip_info - part-specific configuration
+ * @name: IIO device name reported to userspace
+ * @channels: channel specification for this part
+ * @num_channels: number of entries in @channels
+ * @rms_full_scale_codes: digital code produced at full-scale RMS input
+ * @watt_full_scale_codes: digital code produced at full-scale power input
+ * @pcf_full_scale_codes: digital code produced at full-scale xI_PCF/xV_PCF input
+ *
+ * The full-scale codes are taken from the respective datasheets and are used to
+ * derive the IIO scale of the raw measurement channels.
+ */
+struct ade9000_chip_info {
+ const char *name;
+ const struct iio_chan_spec *channels;
+ unsigned int num_channels;
+ unsigned int rms_full_scale_codes;
+ unsigned int watt_full_scale_codes;
+ unsigned int pcf_full_scale_codes;
+};
+
struct ade9000_state {
+ const struct ade9000_chip_info *info;
struct completion reset_completion;
struct mutex lock; /* Protects SPI transactions */
u8 wf_src;
@@ -488,6 +502,7 @@ static const struct iio_chan_spec_ext_info ade9000_ext_info[] = {
.scan_index = -1 \
}
+/* With swell/dip (sag) events - ADE9000 only */
#define ADE9000_ALTVOLTAGE_RMS_CHANNEL(num) { \
.type = IIO_ALTVOLTAGE, \
.channel = num, \
@@ -503,6 +518,20 @@ static const struct iio_chan_spec_ext_info ade9000_ext_info[] = {
.scan_index = -1 \
}
+/* Without swell/dip events - parts lacking DIP_LVL/SWELL_LVL hardware */
+#define ADE9000_ALTVOLTAGE_RMS_CHANNEL_NO_EVENTS(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), \
+ .scan_index = -1 \
+}
+
#define ADE9000_POWER_ACTIVE_CHANNEL(num) { \
.type = IIO_POWER, \
.channel = num, \
@@ -584,43 +613,82 @@ static const struct iio_chan_spec_ext_info ade9000_ext_info[] = {
.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),
+/*
+ * Declare a full channel array. @altvoltage_rms picks the RMS voltage channel
+ * variant so parts without dip/swell hardware (e.g. ADE9078) omit those events.
+ */
+#define ADE9000_DECLARE_CHANNELS(_name, altvoltage_rms) \
+ static const struct iio_chan_spec _name[] = { \
+ /* 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), \
+ altvoltage_rms(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), \
+ altvoltage_rms(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), \
+ altvoltage_rms(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), \
+ }
+
+ADE9000_DECLARE_CHANNELS(ade9000_channels, ADE9000_ALTVOLTAGE_RMS_CHANNEL);
+ADE9000_DECLARE_CHANNELS(ade9078_channels, ADE9000_ALTVOLTAGE_RMS_CHANNEL_NO_EVENTS);
+
+/*
+ * Full-scale codes referred from the respective datasheets. These are the
+ * digital codes produced when the ADC inputs are at full scale.
+ */
+static const struct ade9000_chip_info ade9000_chip_info = {
+ .name = "ade9000",
+ .channels = ade9000_channels,
+ .num_channels = ARRAY_SIZE(ade9000_channels),
+ .rms_full_scale_codes = 52866837,
+ .watt_full_scale_codes = 20694066,
+ .pcf_full_scale_codes = 74770000,
+};
+
+static const struct ade9000_chip_info ade9078_chip_info = {
+ .name = "ade9078",
+ .channels = ade9078_channels,
+ .num_channels = ARRAY_SIZE(ade9078_channels),
+ .rms_full_scale_codes = 52866837,
+ .watt_full_scale_codes = 20823646,
+ .pcf_full_scale_codes = 74680000,
};
static const struct reg_sequence ade9000_initialization_sequence[] = {
@@ -1064,7 +1132,7 @@ static int ade9000_read_raw(struct iio_dev *indio_dev,
case ADE9000_REG_CI_PCF:
case ADE9000_REG_CV_PCF:
*val = 1;
- *val2 = ADE9000_PCF_FULL_SCALE_CODES;
+ *val2 = st->info->pcf_full_scale_codes;
return IIO_VAL_FRACTIONAL;
case ADE9000_REG_AIRMS:
case ADE9000_REG_AVRMS:
@@ -1073,14 +1141,14 @@ static int ade9000_read_raw(struct iio_dev *indio_dev,
case ADE9000_REG_CIRMS:
case ADE9000_REG_CVRMS:
*val = 1;
- *val2 = ADE9000_RMS_FULL_SCALE_CODES;
+ *val2 = st->info->rms_full_scale_codes;
return IIO_VAL_FRACTIONAL;
default:
return -EINVAL;
}
case IIO_POWER:
*val = 1;
- *val2 = ADE9000_WATT_FULL_SCALE_CODES;
+ *val2 = st->info->watt_full_scale_codes;
return IIO_VAL_FRACTIONAL;
default:
break;
@@ -1693,6 +1761,10 @@ static int ade9000_probe(struct spi_device *spi)
st = iio_priv(indio_dev);
+ st->info = spi_get_device_match_data(spi);
+ if (!st->info)
+ return -ENODEV;
+
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");
@@ -1715,7 +1787,7 @@ static int ade9000_probe(struct spi_device *spi)
if (ret)
return ret;
- indio_dev->name = "ade9000";
+ indio_dev->name = st->info->name;
indio_dev->info = &ade9000_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->setup_ops = &ade9000_buffer_ops;
@@ -1738,8 +1810,8 @@ static int ade9000_probe(struct spi_device *spi)
if (ret)
return ret;
- indio_dev->channels = ade9000_channels;
- indio_dev->num_channels = ARRAY_SIZE(ade9000_channels);
+ indio_dev->channels = st->info->channels;
+ indio_dev->num_channels = st->info->num_channels;
ret = devm_iio_kfifo_buffer_setup(dev, indio_dev,
&ade9000_buffer_ops);
@@ -1770,13 +1842,15 @@ static int ade9000_probe(struct spi_device *spi)
};
static const struct spi_device_id ade9000_id[] = {
- { .name = "ade9000" },
+ { .name = "ade9000", .driver_data = (kernel_ulong_t)&ade9000_chip_info },
+ { .name = "ade9078", .driver_data = (kernel_ulong_t)&ade9078_chip_info },
{ }
};
MODULE_DEVICE_TABLE(spi, ade9000_id);
static const struct of_device_id ade9000_of_match[] = {
- { .compatible = "adi,ade9000" },
+ { .compatible = "adi,ade9000", .data = &ade9000_chip_info },
+ { .compatible = "adi,ade9078", .data = &ade9078_chip_info },
{ }
};
MODULE_DEVICE_TABLE(of, ade9000_of_match);
diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c
index fc7cc92b35f0..8b7c45d51dbe 100644
--- a/drivers/iio/adc/adi-axi-adc.c
+++ b/drivers/iio/adc/adi-axi-adc.c
@@ -47,6 +47,9 @@
#define ADI_AXI_ADC_CTRL_DDR_EDGESEL_MASK BIT(1)
#define ADI_AXI_ADC_REG_CNTRL_3 0x004c
+/* Bit 8 is common to all AXI ADC cores; bits 7-0 are per-HDL-project. */
+#define ADI_AXI_ADC_CNTRL_3_CRC_EN_MASK BIT(8)
+
#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
@@ -596,6 +599,22 @@ static int axi_adc_reg_access(struct iio_backend *back, unsigned int reg,
return regmap_write(st->regmap, reg, writeval);
}
+static int axi_adc_crc_enable(struct iio_backend *back)
+{
+ struct adi_axi_adc_state *st = iio_backend_get_priv(back);
+
+ return regmap_set_bits(st->regmap, ADI_AXI_ADC_REG_CNTRL_3,
+ ADI_AXI_ADC_CNTRL_3_CRC_EN_MASK);
+}
+
+static int axi_adc_crc_disable(struct iio_backend *back)
+{
+ struct adi_axi_adc_state *st = iio_backend_get_priv(back);
+
+ return regmap_clear_bits(st->regmap, ADI_AXI_ADC_REG_CNTRL_3,
+ ADI_AXI_ADC_CNTRL_3_CRC_EN_MASK);
+}
+
static const struct regmap_config axi_adc_regmap_config = {
.val_bits = 32,
.reg_bits = 32,
@@ -644,6 +663,8 @@ static const struct iio_backend_ops adi_axi_adc_ops = {
.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),
+ .crc_enable = axi_adc_crc_enable,
+ .crc_disable = axi_adc_crc_disable,
};
static const struct iio_backend_info adi_axi_adc_generic = {
diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c
index e8a5285bb6d4..78081cbfde5e 100644
--- a/drivers/iio/adc/at91-sama5d2_adc.c
+++ b/drivers/iio/adc/at91-sama5d2_adc.c
@@ -2016,6 +2016,8 @@ static void at91_adc_dma_init(struct at91_adc_state *st)
unsigned int pages = DIV_ROUND_UP(AT91_HWFIFO_MAX_SIZE *
sample_size * 2, PAGE_SIZE);
+ struct device *dma_dev;
+
if (st->dma_st.dma_chan)
return;
@@ -2026,7 +2028,8 @@ static void at91_adc_dma_init(struct at91_adc_state *st)
goto dma_exit;
}
- st->dma_st.rx_buf = dma_alloc_coherent(st->dma_st.dma_chan->device->dev,
+ dma_dev = dmaengine_get_dma_device(st->dma_st.dma_chan);
+ st->dma_st.rx_buf = dma_alloc_coherent(dma_dev,
pages * PAGE_SIZE,
&st->dma_st.rx_dma_buf,
GFP_KERNEL);
@@ -2054,7 +2057,7 @@ static void at91_adc_dma_init(struct at91_adc_state *st)
return;
dma_free_area:
- dma_free_coherent(st->dma_st.dma_chan->device->dev, pages * PAGE_SIZE,
+ dma_free_coherent(dma_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);
@@ -2078,7 +2081,7 @@ static void at91_adc_dma_disable(struct at91_adc_state *st)
/* 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,
+ dma_free_coherent(dmaengine_get_dma_device(st->dma_st.dma_chan), 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;
@@ -2487,6 +2490,7 @@ static void at91_adc_remove(struct platform_device *pdev)
pm_runtime_disable(st->dev);
pm_runtime_set_suspended(st->dev);
+ pm_runtime_dont_use_autosuspend(st->dev);
clk_disable_unprepare(st->per_clk);
regulator_disable(st->vref);
diff --git a/drivers/iio/adc/axiado_saradc.c b/drivers/iio/adc/axiado_saradc.c
new file mode 100644
index 000000000000..699ee31616fc
--- /dev/null
+++ b/drivers/iio/adc/axiado_saradc.c
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2021-2026 Axiado Corporation
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <linux/iio/iio.h>
+
+/* Register offsets */
+#define AX_SARADC_GLOBAL_CTRL_REG 0x0004
+#define AX_SARADC_MANUAL_CTRL_REG 0x0008
+#define AX_SARADC_DOUT_REG 0x001C
+
+/* GLOBAL_CTRL register fields */
+#define AX_SARADC_GLOBAL_CTRL_CH_EN_MASK GENMASK(31, 16)
+#define AX_SARADC_GLOBAL_CTRL_SAMPLE_MASK GENMASK(6, 5)
+#define AX_SARADC_GLOBAL_CTRL_MODE_MASK GENMASK(4, 3)
+#define AX_SARADC_GLOBAL_CTRL_PD BIT(2)
+#define AX_SARADC_GLOBAL_CTRL_ENABLE BIT(0)
+
+/* GLOBAL_CTRL SAMPLE_MASK field value: 0 selects 16 samples. */
+#define AX_SARADC_GLOBAL_CTRL_SAMPLE_16 0
+
+/* GLOBAL_CTRL MODE_MASK field value: 1 selects manual mode. */
+#define AX_SARADC_GLOBAL_CTRL_MODE_MANUAL 1
+
+/* MANUAL_CTRL register fields */
+#define AX_SARADC_MANUAL_CTRL_ENABLE BIT(0)
+#define AX_SARADC_MANUAL_CTRL_CH_SEL_MASK GENMASK(4, 1)
+
+#define AX_RESOLUTION_BITS 10
+#define AX_SARADC_CONV_CYCLES 13
+#define AX_SARADC_CONV_DELAY_MARGIN_US 10
+
+struct axiado_saradc {
+ struct regmap *regmap;
+ struct mutex lock; /* Serializes ADC conversions. */
+ unsigned long clk_rate;
+ int vref_uV;
+};
+
+/*
+ * Registers contain transient control, status, and conversion data,
+ * so accesses must always go directly to hardware.
+ */
+static const struct regmap_config axiado_saradc_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = AX_SARADC_DOUT_REG,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+};
+
+static int axiado_saradc_conversion(struct axiado_saradc *info,
+ struct iio_chan_spec const *chan, int *val)
+{
+ unsigned long usecs;
+ unsigned int regval;
+ int ret;
+
+ guard(mutex)(&info->lock);
+
+ /* Select the channel to be used and trigger conversion. */
+ ret = regmap_write(info->regmap, AX_SARADC_MANUAL_CTRL_REG,
+ AX_SARADC_MANUAL_CTRL_ENABLE |
+ FIELD_PREP(AX_SARADC_MANUAL_CTRL_CH_SEL_MASK, chan->channel));
+ if (ret)
+ return ret;
+
+ /* Hardware requires 13 conversion cycles at clk_rate. */
+ usecs = DIV_ROUND_UP(AX_SARADC_CONV_CYCLES * USEC_PER_SEC, info->clk_rate);
+ fsleep(usecs + AX_SARADC_CONV_DELAY_MARGIN_US);
+
+ ret = regmap_read(info->regmap, AX_SARADC_DOUT_REG, &regval);
+
+ /* Best effort to stop manual conversion. */
+ regmap_write(info->regmap, AX_SARADC_MANUAL_CTRL_REG, 0);
+
+ if (ret)
+ return ret;
+
+ *val = regval & GENMASK(AX_RESOLUTION_BITS - 1, 0);
+
+ return 0;
+}
+
+static int axiado_saradc_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct axiado_saradc *info = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = axiado_saradc_conversion(info, chan, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = info->vref_uV / (MICRO / MILLI);
+ *val2 = AX_RESOLUTION_BITS;
+ return IIO_VAL_FRACTIONAL_LOG2;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info axiado_saradc_iio_info = {
+ .read_raw = axiado_saradc_read_raw,
+};
+
+struct axiado_saradc_soc_data {
+ const char *name;
+ unsigned int num_channels;
+};
+
+static const struct axiado_saradc_soc_data ax3000_saradc_data = {
+ .name = "ax3000_saradc",
+ .num_channels = 16,
+};
+
+static const struct axiado_saradc_soc_data ax3005_saradc_data = {
+ .name = "ax3005_saradc",
+ .num_channels = 8,
+};
+
+#define AX_SARADC_CH(_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), \
+ .datasheet_name = "adc" #_index, \
+ }
+
+static const struct iio_chan_spec axiado_saradc_iio_channels[] = {
+ AX_SARADC_CH(0),
+ AX_SARADC_CH(1),
+ AX_SARADC_CH(2),
+ AX_SARADC_CH(3),
+ AX_SARADC_CH(4),
+ AX_SARADC_CH(5),
+ AX_SARADC_CH(6),
+ AX_SARADC_CH(7),
+ AX_SARADC_CH(8),
+ AX_SARADC_CH(9),
+ AX_SARADC_CH(10),
+ AX_SARADC_CH(11),
+ AX_SARADC_CH(12),
+ AX_SARADC_CH(13),
+ AX_SARADC_CH(14),
+ AX_SARADC_CH(15),
+};
+
+static void axiado_saradc_disable(void *map)
+{
+ regmap_write(map, AX_SARADC_GLOBAL_CTRL_REG, AX_SARADC_GLOBAL_CTRL_PD);
+}
+
+static int axiado_saradc_probe(struct platform_device *pdev)
+{
+ const struct axiado_saradc_soc_data *soc_data;
+ struct device *dev = &pdev->dev;
+ struct axiado_saradc *info;
+ struct iio_dev *indio_dev;
+ void __iomem *regs;
+ struct regmap *map;
+ struct clk *clk;
+ u32 regval;
+ 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 PTR_ERR(regs);
+
+ map = devm_regmap_init_mmio(dev, regs, &axiado_saradc_regmap_config);
+ if (IS_ERR(map))
+ return PTR_ERR(map);
+ info->regmap = map;
+
+ clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ info->clk_rate = clk_get_rate(clk);
+ if (!info->clk_rate)
+ return dev_err_probe(dev, -EINVAL, "invalid clock rate\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");
+
+ info->vref_uV = ret;
+
+ soc_data = device_get_match_data(dev);
+ if (!soc_data)
+ return dev_err_probe(dev, -ENODATA, "failed to get match data\n");
+
+ ret = devm_mutex_init(dev, &info->lock);
+ if (ret)
+ return ret;
+
+ regval = FIELD_PREP(AX_SARADC_GLOBAL_CTRL_CH_EN_MASK,
+ GENMASK(soc_data->num_channels - 1, 0)) |
+ FIELD_PREP(AX_SARADC_GLOBAL_CTRL_SAMPLE_MASK,
+ AX_SARADC_GLOBAL_CTRL_SAMPLE_16) |
+ FIELD_PREP(AX_SARADC_GLOBAL_CTRL_MODE_MASK,
+ AX_SARADC_GLOBAL_CTRL_MODE_MANUAL) |
+ AX_SARADC_GLOBAL_CTRL_ENABLE;
+
+ ret = regmap_write(map, AX_SARADC_GLOBAL_CTRL_REG, regval);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, axiado_saradc_disable, map);
+ if (ret)
+ return ret;
+
+ indio_dev->name = soc_data->name;
+ indio_dev->info = &axiado_saradc_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = axiado_saradc_iio_channels;
+ indio_dev->num_channels = soc_data->num_channels;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id axiado_saradc_match[] = {
+ {
+ .compatible = "axiado,ax3000-saradc",
+ .data = &ax3000_saradc_data,
+ },
+ {
+ .compatible = "axiado,ax3005-saradc",
+ .data = &ax3005_saradc_data,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, axiado_saradc_match);
+
+static struct platform_driver axiado_saradc_driver = {
+ .driver = {
+ .name = "axiado-saradc",
+ .of_match_table = axiado_saradc_match,
+ },
+ .probe = axiado_saradc_probe,
+};
+module_platform_driver(axiado_saradc_driver);
+
+MODULE_AUTHOR("Axiado Corporation");
+MODULE_DESCRIPTION("Axiado SARADC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/bcm_iproc_adc.c b/drivers/iio/adc/bcm_iproc_adc.c
index cf4738b16e62..ca81e0edd24d 100644
--- a/drivers/iio/adc/bcm_iproc_adc.c
+++ b/drivers/iio/adc/bcm_iproc_adc.c
@@ -3,14 +3,18 @@
* Copyright 2016 Broadcom
*/
-#include <linux/module.h>
-#include <linux/io.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
#include <linux/clk.h>
-#include <linux/mfd/syscon.h>
-#include <linux/regmap.h>
+#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
#include <linux/iio/iio.h>
@@ -106,7 +110,6 @@ do { \
struct iproc_adc_priv {
struct regmap *regmap;
- struct clk *adc_clk;
struct mutex mutex;
int irqno;
int chan_val;
@@ -448,6 +451,11 @@ static void iproc_adc_disable(struct iio_dev *indio_dev)
}
}
+static void iproc_adc_disable_action(void *indio_dev)
+{
+ iproc_adc_disable(indio_dev);
+}
+
static int iproc_adc_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val,
@@ -506,35 +514,31 @@ static int iproc_adc_probe(struct platform_device *pdev)
{
struct iproc_adc_priv *adc_priv;
struct iio_dev *indio_dev = NULL;
+ struct device *dev = &pdev->dev;
+ struct clk *adc_clk;
int ret;
- indio_dev = devm_iio_device_alloc(&pdev->dev,
- sizeof(*adc_priv));
+ indio_dev = devm_iio_device_alloc(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);
+ ret = devm_mutex_init(dev, &adc_priv->mutex);
+ if (ret)
+ return ret;
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;
- }
+ if (IS_ERR(adc_priv->regmap))
+ return dev_err_probe(dev, PTR_ERR(adc_priv->regmap),
+ "failed to get handle for tsc syscon\n");
- 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_clk = devm_clk_get_enabled(dev, "tsc_clk");
+ if (IS_ERR(adc_clk))
+ return dev_err_probe(dev, PTR_ERR(adc_clk), "failed to enable clock\n");
adc_priv->irqno = platform_get_irq(pdev, 0);
if (adc_priv->irqno < 0)
@@ -542,32 +546,23 @@ static int iproc_adc_probe(struct platform_device *pdev)
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;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to write IPROC_REGCTL2\n");
- ret = devm_request_threaded_irq(&pdev->dev, adc_priv->irqno,
+ ret = devm_request_threaded_irq(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);
+ if (ret)
return ret;
- }
- ret = clk_prepare_enable(adc_priv->adc_clk);
- if (ret) {
- dev_err(&pdev->dev,
- "clk_prepare_enable failed %d\n", ret);
+ ret = iproc_adc_enable(indio_dev);
+ if (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;
- }
+ ret = devm_add_action_or_reset(dev, iproc_adc_disable_action, indio_dev);
+ if (ret)
+ return ret;
indio_dev->name = "iproc-static-adc";
indio_dev->info = &iproc_adc_iio_info;
@@ -575,30 +570,7 @@ static int iproc_adc_probe(struct platform_device *pdev)
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);
+ return devm_iio_device_register(dev, indio_dev);
}
static const struct of_device_id iproc_adc_of_match[] = {
@@ -609,7 +581,6 @@ 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,
diff --git a/drivers/iio/adc/ltc2497-core.c b/drivers/iio/adc/ltc2497-core.c
index 2dc5c7044269..a6e34da83b81 100644
--- a/drivers/iio/adc/ltc2497-core.c
+++ b/drivers/iio/adc/ltc2497-core.c
@@ -9,9 +9,11 @@
#include <linux/delay.h>
#include <linux/iio/iio.h>
#include <linux/iio/driver.h>
+#include <linux/math64.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regulator/consumer.h>
+#include <linux/units.h>
#include "ltc2497.h"
@@ -19,24 +21,54 @@
#define LTC2497_DIFF 0
#define LTC2497_SIGN BIT(3)
-static int ltc2497core_wait_conv(struct ltc2497core_driverdata *ddata)
+/*
+ * Output-rate modes, indexed by ltc2497core_driverdata.speed_2x
+ * (0 = 1x, the power-on default; 1 = 2x, LTC2499 only). The advertised
+ * sampling_frequency and the conversion-time budget are two views of the same
+ * mode, so they are kept in lock-step here.
+ */
+static const int ltc2497core_samp_freq_avail[] = {
+ 6, 800000, /* 1x: ~6.8 Hz (1 / t_CONV_1 typ 146.9ms) */
+ 13, 600000, /* 2x: ~13.6 Hz (1 / t_CONV_2 typ 73.6ms) */
+};
+
+static const unsigned int ltc2497core_conv_time_ms_tbl[] = {
+ LTC2497_CONV_TIME_1X_MS, /* 1x */
+ LTC2499_CONV_TIME_2X_MS, /* 2x */
+};
+
+static unsigned int ltc2497core_conv_time_ms(struct ltc2497core_driverdata *ddata,
+ u8 address)
+{
+ /*
+ * SPD is ignored by the part during a temperature measurement: it
+ * always converts at 1x, so budget the 1x time regardless of the
+ * selected voltage-channel mode.
+ */
+ if (address == LTC2497_TEMP_ADDR)
+ return ltc2497core_conv_time_ms_tbl[0];
+
+ return ltc2497core_conv_time_ms_tbl[ddata->speed_2x];
+}
+
+static int ltc2497core_wait_conv(struct ltc2497core_driverdata *ddata,
+ unsigned int conv_time_ms)
{
s64 time_elapsed;
time_elapsed = ktime_ms_delta(ktime_get(), ddata->time_prev);
- if (time_elapsed < LTC2497_CONVERSION_TIME_MS) {
+ if (time_elapsed < conv_time_ms) {
/* delay if conversion time not passed
* since last read or write
*/
- if (msleep_interruptible(
- LTC2497_CONVERSION_TIME_MS - time_elapsed))
+ if (msleep_interruptible(conv_time_ms - time_elapsed))
return -ERESTARTSYS;
return 0;
}
- if (time_elapsed - LTC2497_CONVERSION_TIME_MS <= 0) {
+ if (time_elapsed - conv_time_ms <= 0) {
/* We're in automatic mode -
* so the last reading is still not outdated
*/
@@ -48,9 +80,18 @@ static int ltc2497core_wait_conv(struct ltc2497core_driverdata *ddata)
static int ltc2497core_read(struct ltc2497core_driverdata *ddata, u8 address, int *val)
{
+ unsigned int conv_time_ms = ltc2497core_conv_time_ms(ddata, address);
int ret;
- ret = ltc2497core_wait_conv(ddata);
+ /*
+ * Wait for the conversion currently in flight, whose duration was fixed
+ * by the mode active when it was started (ddata->conv_time_prev). This
+ * can be longer than the freshly selected mode's time - e.g. a 1x
+ * conversion is still running when the first 2x read arrives after a
+ * sampling_frequency change - and reprogramming the device before it
+ * finishes would be NACKed (-EIO).
+ */
+ ret = ltc2497core_wait_conv(ddata, ddata->conv_time_prev);
if (ret < 0)
return ret;
@@ -60,7 +101,17 @@ static int ltc2497core_read(struct ltc2497core_driverdata *ddata, u8 address, in
return ret;
ddata->addr_prev = address;
- if (msleep_interruptible(LTC2497_CONVERSION_TIME_MS))
+ /*
+ * The reprogram above starts a conversion in the new mode.
+ * Record its start time and duration before sleeping, so that if
+ * msleep_interruptible() is interrupted the conversion state is
+ * already consistent: the next retry then waits only the time
+ * remaining from the real start instead of from a stale
+ * time_prev, which would let it reprogram/read too early.
+ */
+ ddata->time_prev = ktime_get();
+ ddata->conv_time_prev = conv_time_ms;
+ if (msleep_interruptible(conv_time_ms))
return -ERESTARTSYS;
}
@@ -69,6 +120,8 @@ static int ltc2497core_read(struct ltc2497core_driverdata *ddata, u8 address, in
return ret;
ddata->time_prev = ktime_get();
+ /* The read above auto-starts the next conversion in the current mode. */
+ ddata->conv_time_prev = conv_time_ms;
return ret;
}
@@ -95,74 +148,209 @@ static int ltc2497core_read_raw(struct iio_dev *indio_dev,
if (ret < 0)
return ret;
- *val = ret / 1000;
- *val2 = ddata->chip_info->resolution + 1;
+ switch (chan->type) {
+ case IIO_TEMP:
+ /*
+ * raw is normalised to 2^(resolution + 1), i.e.
+ * raw = 2 * DATAOUT24, so the PTAT scale (datasheet
+ * Vref / 1570 per Kelvin) doubles its denominator and,
+ * in m°C, becomes Vref_uV / 3140000.
+ */
+ *val = ret;
+ *val2 = 3140000;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_VOLTAGE:
+ *val = ret / (MICRO / MILLI);
+ *val2 = ddata->chip_info->resolution + 1;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ return -EINVAL;
+ }
+
+ case IIO_CHAN_INFO_OFFSET:
+ switch (chan->type) {
+ case IIO_TEMP:
+ ret = regulator_get_voltage(ddata->ref);
+ if (ret < 0)
+ return ret;
+ if (ret == 0)
+ return -EINVAL;
+ /*
+ * 0 °C == 273.15 K must map to raw + offset such that
+ * (raw + offset) * scale == 0 m°C, i.e.
+ * offset = -273150 / scale
+ * = -273150 * 3140000 / Vref_uV
+ * Computed in 64-bit to avoid overflow.
+ */
+ *val = div_s64(ABSOLUTE_ZERO_MILLICELSIUS * 3140000LL, ret);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ /*
+ * Only advertised on the voltage channels of parts with a speed
+ * mode; the sampling frequency is a property of the selected 1x/2x
+ * mode, not of an individual conversion.
+ */
+ mutex_lock(&ddata->lock);
+ *val = ltc2497core_samp_freq_avail[ddata->speed_2x * 2];
+ *val2 = ltc2497core_samp_freq_avail[ddata->speed_2x * 2 + 1];
+ mutex_unlock(&ddata->lock);
- return IIO_VAL_FRACTIONAL_LOG2;
+ return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
-#define LTC2497_CHAN(_chan, _addr, _ds_name) { \
+static int ltc2497core_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 = ltc2497core_samp_freq_avail;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *length = ARRAY_SIZE(ltc2497core_samp_freq_avail);
+ return IIO_AVAIL_LIST;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ltc2497core_write_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);
+ unsigned int i;
+ bool speed_2x;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ /* Match the (val, val2) pair against the advertised rates. */
+ for (i = 0; i < ARRAY_SIZE(ltc2497core_samp_freq_avail); i += 2) {
+ if (val == ltc2497core_samp_freq_avail[i] &&
+ val2 == ltc2497core_samp_freq_avail[i + 1])
+ break;
+ }
+ if (i == ARRAY_SIZE(ltc2497core_samp_freq_avail))
+ return -EINVAL;
+
+ speed_2x = i / 2;
+
+ mutex_lock(&ddata->lock);
+ ddata->speed_2x = speed_2x;
+ /*
+ * The new speed only takes effect once the second command byte
+ * is reprogrammed, so force the next read to reprogram rather
+ * than reuse the value already latched for this address.
+ * LTC2497_CONFIG_DEFAULT is not a valid channel/temperature
+ * address, so it is a safe re-arm sentinel (as used at probe).
+ *
+ * A conversion started under the old speed may still be in
+ * flight; its own duration (conv_time_prev), not the new mode's,
+ * still gates the next reprogram, so the timing state is left
+ * untouched here.
+ */
+ ddata->addr_prev = LTC2497_CONFIG_DEFAULT;
+ mutex_unlock(&ddata->lock);
+
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+#define LTC2497_CHAN(_chan, _addr, _ds_name, _extra_mask) { \
.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), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | (_extra_mask), \
+ .info_mask_shared_by_type_available = (_extra_mask), \
.datasheet_name = (_ds_name), \
}
-#define LTC2497_CHAN_DIFF(_chan, _addr) { \
+#define LTC2497_CHAN_DIFF(_chan, _addr, _extra_mask) { \
.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), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | (_extra_mask), \
+ .info_mask_shared_by_type_available = (_extra_mask), \
.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),
-};
+/*
+ * SPD is ignored by the part during a temperature measurement, so the
+ * temperature channel carries no sampling-frequency attribute.
+ */
+#define LTC2497_TEMP_CHANNEL { \
+ .type = IIO_TEMP, \
+ .address = LTC2497_TEMP_ADDR, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OFFSET), \
+}
+
+/*
+ * Parts with a speed mode advertise sampling_frequency on their voltage
+ * channels; the two tables are otherwise identical.
+ */
+#define LTC2497_DEFINE_CHANNELS(_name, _extra_mask) \
+static const struct iio_chan_spec _name[] = { \
+ LTC2497_CHAN(0, LTC2497_SGL, "CH0", _extra_mask), \
+ LTC2497_CHAN(1, LTC2497_SGL, "CH1", _extra_mask), \
+ LTC2497_CHAN(2, LTC2497_SGL, "CH2", _extra_mask), \
+ LTC2497_CHAN(3, LTC2497_SGL, "CH3", _extra_mask), \
+ LTC2497_CHAN(4, LTC2497_SGL, "CH4", _extra_mask), \
+ LTC2497_CHAN(5, LTC2497_SGL, "CH5", _extra_mask), \
+ LTC2497_CHAN(6, LTC2497_SGL, "CH6", _extra_mask), \
+ LTC2497_CHAN(7, LTC2497_SGL, "CH7", _extra_mask), \
+ LTC2497_CHAN(8, LTC2497_SGL, "CH8", _extra_mask), \
+ LTC2497_CHAN(9, LTC2497_SGL, "CH9", _extra_mask), \
+ LTC2497_CHAN(10, LTC2497_SGL, "CH10", _extra_mask), \
+ LTC2497_CHAN(11, LTC2497_SGL, "CH11", _extra_mask), \
+ LTC2497_CHAN(12, LTC2497_SGL, "CH12", _extra_mask), \
+ LTC2497_CHAN(13, LTC2497_SGL, "CH13", _extra_mask), \
+ LTC2497_CHAN(14, LTC2497_SGL, "CH14", _extra_mask), \
+ LTC2497_CHAN(15, LTC2497_SGL, "CH15", _extra_mask), \
+ LTC2497_CHAN_DIFF(0, LTC2497_DIFF, _extra_mask), \
+ LTC2497_CHAN_DIFF(1, LTC2497_DIFF, _extra_mask), \
+ LTC2497_CHAN_DIFF(2, LTC2497_DIFF, _extra_mask), \
+ LTC2497_CHAN_DIFF(3, LTC2497_DIFF, _extra_mask), \
+ LTC2497_CHAN_DIFF(4, LTC2497_DIFF, _extra_mask), \
+ LTC2497_CHAN_DIFF(5, LTC2497_DIFF, _extra_mask), \
+ LTC2497_CHAN_DIFF(6, LTC2497_DIFF, _extra_mask), \
+ LTC2497_CHAN_DIFF(7, LTC2497_DIFF, _extra_mask), \
+ LTC2497_CHAN_DIFF(0, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \
+ LTC2497_CHAN_DIFF(1, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \
+ LTC2497_CHAN_DIFF(2, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \
+ LTC2497_CHAN_DIFF(3, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \
+ LTC2497_CHAN_DIFF(4, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \
+ LTC2497_CHAN_DIFF(5, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \
+ LTC2497_CHAN_DIFF(6, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \
+ LTC2497_CHAN_DIFF(7, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \
+ LTC2497_TEMP_CHANNEL, \
+}
+
+LTC2497_DEFINE_CHANNELS(ltc2497core_channel, 0);
+LTC2497_DEFINE_CHANNELS(ltc2497core_channel_samp_freq, BIT(IIO_CHAN_INFO_SAMP_FREQ));
static const struct iio_info ltc2497core_info = {
.read_raw = ltc2497core_read_raw,
+ .read_avail = ltc2497core_read_avail,
+ .write_raw = ltc2497core_write_raw,
};
int ltc2497core_probe(struct device *dev, struct iio_dev *indio_dev)
@@ -182,8 +370,15 @@ int ltc2497core_probe(struct device *dev, struct iio_dev *indio_dev)
indio_dev->info = &ltc2497core_info;
indio_dev->modes = INDIO_DIRECT_MODE;
- indio_dev->channels = ltc2497core_channel;
- indio_dev->num_channels = ARRAY_SIZE(ltc2497core_channel);
+ if (ddata->chip_info->has_speed_mode)
+ indio_dev->channels = ltc2497core_channel_samp_freq;
+ else
+ indio_dev->channels = ltc2497core_channel;
+ /* Only the ltc2499 has a temperature channel; it is the last entry. */
+ if (ddata->chip_info->has_temp)
+ indio_dev->num_channels = ARRAY_SIZE(ltc2497core_channel);
+ else
+ indio_dev->num_channels = ARRAY_SIZE(ltc2497core_channel) - 1;
ret = ddata->result_and_measure(ddata, LTC2497_CONFIG_DEFAULT, NULL);
if (ret < 0)
@@ -209,6 +404,8 @@ int ltc2497core_probe(struct device *dev, struct iio_dev *indio_dev)
ddata->addr_prev = LTC2497_CONFIG_DEFAULT;
ddata->time_prev = ktime_get();
+ /* Power-on default mode is 1x; a conversion is already in flight. */
+ ddata->conv_time_prev = LTC2497_CONV_TIME_1X_MS;
mutex_init(&ddata->lock);
diff --git a/drivers/iio/adc/ltc2497.c b/drivers/iio/adc/ltc2497.c
index c1668b5a351e..15458d4e21a1 100644
--- a/drivers/iio/adc/ltc2497.c
+++ b/drivers/iio/adc/ltc2497.c
@@ -84,6 +84,46 @@ static int ltc2497_result_and_measure(struct ltc2497core_driverdata *ddata,
return 0;
}
+ /*
+ * Parts with a second config byte (LTC2499: internal PTAT sensor and/or
+ * the 2x speed mode) latch their converter configuration from that byte
+ * and only re-evaluate it when EN2 is set; a single byte, or a second
+ * byte with EN2 = 0, means "keep previous". A one-byte channel select
+ * therefore cannot pull the device back out of temperature mode, so a
+ * voltage read after a temperature read would keep returning the PTAT
+ * result. Always drive the second byte with EN2 set on these parts:
+ * - temperature read: IM = 1 (SPD is ignored by the part in
+ * temperature mode and is left 0 here);
+ * - voltage read: IM = 0 (external input), plus SPD when 2x is
+ * selected.
+ * FA = FB = 0 keeps the power-on simultaneous 50/60Hz rejection, whose
+ * worst-case conversion time the driver's wait already covers.
+ *
+ * The byte could be skipped while the latched configuration is already
+ * the one wanted; it is sent on every conversion for simplicity.
+ */
+ if (ddata->chip_info->has_temp || ddata->chip_info->has_speed_mode) {
+ u8 cmd[2];
+
+ if (address == LTC2497_TEMP_ADDR) {
+ cmd[0] = LTC2497_ENABLE | LTC2497_CONFIG_DEFAULT;
+ cmd[1] = LTC2499_EN2 | LTC2499_IM;
+ } else {
+ cmd[0] = LTC2497_ENABLE | address;
+ cmd[1] = LTC2499_EN2;
+ if (ddata->speed_2x)
+ cmd[1] |= LTC2499_SPD;
+ }
+
+ ret = i2c_master_send(st->client, cmd, sizeof(cmd));
+ if (ret < 0) {
+ dev_err(&st->client->dev, "i2c transfer failed: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+ return 0;
+ }
+
ret = i2c_smbus_write_byte(st->client,
LTC2497_ENABLE | address);
if (ret)
@@ -137,6 +177,8 @@ static const struct ltc2497_chip_info ltc2497_info[] = {
[TYPE_LTC2499] = {
.resolution = 24,
.name = "ltc2499",
+ .has_temp = true,
+ .has_speed_mode = true,
},
};
diff --git a/drivers/iio/adc/ltc2497.h b/drivers/iio/adc/ltc2497.h
index 64e81c95a3dd..cee9a2f50b82 100644
--- a/drivers/iio/adc/ltc2497.h
+++ b/drivers/iio/adc/ltc2497.h
@@ -2,11 +2,35 @@
#define LTC2497_ENABLE 0xA0
#define LTC2497_CONFIG_DEFAULT LTC2497_ENABLE
-#define LTC2497_CONVERSION_TIME_MS 150ULL
+
+/*
+ * Conversion-time bounds used to gate reads. Each value is the datasheet
+ * t_CONV maximum, rounded UP to the next whole millisecond.
+ *
+ * The 2x mode (LTC2499_SPD, LTC2499 only) disables the offset auto-calibration
+ * to roughly double the output rate; adding the 2x wait time is what makes the
+ * SPD control actually faster.
+ */
+#define LTC2497_CONV_TIME_1X_MS 150ULL /* ceil(t_CONV_1 simult. max 149.9) */
+#define LTC2499_CONV_TIME_2X_MS 76ULL /* ceil(t_CONV_2 simult. max 75.1) */
+
+/*
+ * Sentinel passed as `address` to result_and_measure() to request a
+ * temperature conversion instead of a voltage channel. Valid channel
+ * addresses fit in 5 bits (0x00–0x1F), so 0xFF is unambiguous.
+ */
+#define LTC2497_TEMP_ADDR 0xFF
+
+/* Second config-byte bits (LTC2499 / LTC2493 only) */
+#define LTC2499_EN2 BIT(7) /* enable second config byte */
+#define LTC2499_IM BIT(6) /* 1 = measure internal temp sensor */
+#define LTC2499_SPD BIT(3) /* 1 = 2x output rate (offset cal off) */
struct ltc2497_chip_info {
- u32 resolution;
const char *name;
+ u32 resolution;
+ bool has_temp;
+ bool has_speed_mode; /* SPD bit in the 2nd config byte (LTC2499/LTC2493) */
};
struct ltc2497core_driverdata {
@@ -16,6 +40,9 @@ struct ltc2497core_driverdata {
struct mutex lock;
const struct ltc2497_chip_info *chip_info;
u8 addr_prev;
+ bool speed_2x; /* SPD: false = 1x (default), true = 2x */
+ /* Conversion time (ms) of the conversion currently in flight. */
+ unsigned int conv_time_prev;
int (*result_and_measure)(struct ltc2497core_driverdata *ddata,
u8 address, int *val);
};
diff --git a/drivers/iio/adc/max11205.c b/drivers/iio/adc/max11205.c
index 63b6561dcf47..53ac2f4f383e 100644
--- a/drivers/iio/adc/max11205.c
+++ b/drivers/iio/adc/max11205.c
@@ -117,6 +117,8 @@ static int max11205_probe(struct spi_device *spi)
ad_sd_init(&st->sd, indio_dev, spi, &max11205_sigma_delta_info);
st->chip_info = spi_get_device_match_data(spi);
+ if (!st->chip_info)
+ return -ENODATA;
indio_dev->name = st->chip_info->name;
indio_dev->modes = INDIO_DIRECT_MODE;
diff --git a/drivers/iio/adc/max14001.c b/drivers/iio/adc/max14001.c
index 58adeac62dde..7694fbb6a9fd 100644
--- a/drivers/iio/adc/max14001.c
+++ b/drivers/iio/adc/max14001.c
@@ -366,6 +366,7 @@ static const struct spi_device_id max14001_id_table[] = {
{ .name = "max14002", .driver_data = (kernel_ulong_t)&max14002_chip_info },
{ }
};
+MODULE_DEVICE_TABLE(spi, max14001_id_table);
static const struct of_device_id max14001_of_match[] = {
{ .compatible = "adi,max14001", .data = &max14001_chip_info },
diff --git a/drivers/iio/adc/max40080.c b/drivers/iio/adc/max40080.c
new file mode 100644
index 000000000000..c6946eb6ff5b
--- /dev/null
+++ b/drivers/iio/adc/max40080.c
@@ -0,0 +1,574 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * MAX40080 Digital Current-Sense Amplifier driver
+ *
+ * Copyright 2026 Analog Devices, Inc.
+ *
+ * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX40080.pdf
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/time.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <asm/byteorder.h>
+
+#include <linux/iio/iio.h>
+
+#define MAX40080_REG_CFG 0x00
+#define MAX40080_CFG_MODE_MSK GENMASK(2, 0)
+#define MAX40080_CFG_PEC_EN_MSK BIT(5)
+#define MAX40080_CFG_RANGE_MSK BIT(6)
+#define MAX40080_CFG_FILTER_MSK GENMASK(14, 12)
+
+#define MAX40080_REG_FIFO_CFG 0x0A
+#define MAX40080_FIFO_CFG_STORE_IV_MSK GENMASK(1, 0)
+
+#define MAX40080_REG_IV 0x10
+/* Current is a 13-bit two's-complement value (magnitude + sign bit). */
+#define MAX40080_IV_I_MSK GENMASK(12, 0)
+#define MAX40080_IV_I_SIGN_BIT 12
+#define MAX40080_IV_V_MAG_MSK GENMASK(27, 16)
+#define MAX40080_IV_VALID_MSK BIT(31)
+
+/* CFG.mode field values. */
+#define MAX40080_CFG_MODE_STDBY 0x00
+#define MAX40080_CFG_MODE_SINGLE 0x02
+
+/* CFG.range field values. */
+#define MAX40080_CFG_RANGE_50mV 0
+#define MAX40080_CFG_RANGE_10mV 1
+
+/* FIFO_CFG.store_iv field values. */
+#define MAX40080_FIFO_CFG_STORE_IV 0x02
+
+#define MAX40080_ADC_RES_BITS 12
+#define MAX40080_INTER_VREF_mV 1250
+#define MAX40080_V_BUFF_GAIN 30
+#define MAX40080_CSA_50mV_GAIN 25
+#define MAX40080_CSA_10mV_GAIN 125
+
+/*
+ * The RANGE field (CFG bit 6) selects one of two current-sense full-scale
+ * ranges (the MAX40080 supports exactly two: +/-50 mV and +/-10 mV). Indexed
+ * by the CFG.range field value.
+ */
+static const int max40080_csa_gain[] = {
+ [MAX40080_CFG_RANGE_50mV] = MAX40080_CSA_50mV_GAIN,
+ [MAX40080_CFG_RANGE_10mV] = MAX40080_CSA_10mV_GAIN,
+};
+
+struct max40080_state {
+ struct i2c_client *client;
+ /* Serializes read-modify-write access to the CFG register. */
+ struct mutex lock;
+ u32 shunt_resistor_uOhm;
+ /* Cached configuration: the selected RANGE index and oversampling ratio. */
+ unsigned int range;
+ int oversampling_ratio;
+ /*
+ * Precomputed current scale (mA per code) for each RANGE setting, as
+ * {integer, nano} pairs for IIO_VAL_INT_PLUS_NANO. The range is
+ * selected by writing the corresponding scale.
+ */
+ int current_scale[2][2];
+};
+
+static const int max40080_oversampling_avail[] = { 1, 8, 16, 32, 64, 128 };
+
+static int max40080_update_bits(struct max40080_state *st, u8 reg,
+ u16 mask, u16 val)
+{
+ int tmp;
+
+ tmp = i2c_smbus_read_word_data(st->client, reg);
+ if (tmp < 0)
+ return tmp;
+
+ tmp = (tmp & ~mask) | (val & mask);
+
+ return i2c_smbus_write_word_data(st->client, reg, tmp);
+}
+
+/*
+ * In single-measurement mode the device sits idle until it receives an SMBus
+ * Quick Command, then performs exactly one current and one voltage conversion
+ * and returns to idle. Triggering on demand this way (rather than running the
+ * FIFO continuously in active mode) means each read returns a fresh, coherent
+ * current/voltage pair instead of the oldest queued FIFO entry.
+ */
+static int max40080_trigger_measurement(struct max40080_state *st)
+{
+ return i2c_smbus_xfer(st->client->adapter, st->client->addr,
+ st->client->flags, I2C_SMBUS_WRITE, 0,
+ I2C_SMBUS_QUICK, NULL);
+}
+
+/*
+ * A single measurement holds the matched current/voltage pair in one 32-bit
+ * word (MAX40080_REG_IV). Reading all four bytes in one transaction returns
+ * both from the same conversion; reading the separate current (0x0C) and
+ * voltage (0x0E) registers would decorrelate the two channels.
+ *
+ * Unlike the word accesses used elsewhere, this is a plain I2C block read: the
+ * SMBus layer does not append or verify a PEC byte for it even when PEC is
+ * otherwise enabled for the device, so this transfer is not PEC protected.
+ */
+static int max40080_read_iv_once(struct max40080_state *st, u32 *iv)
+{
+ __le32 buf = 0;
+ int ret;
+
+ ret = i2c_smbus_read_i2c_block_data(st->client, MAX40080_REG_IV,
+ sizeof(buf), (u8 *)&buf);
+ if (ret < 0)
+ return ret;
+
+ *iv = le32_to_cpu(buf);
+
+ return 0;
+}
+
+static int max40080_read_iv(struct max40080_state *st, u32 *iv)
+{
+ u32 tmp = 0;
+ int ret, io_ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = max40080_trigger_measurement(st);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Wait for the conversion to complete by polling the FIFO valid bit
+ * (or bail out on an I2C error). Polling the device's own status makes
+ * this independent of the actual conversion time, which varies with the
+ * oversampling ratio and the bus speed. The timeout is only a safety
+ * ceiling: the worst case is the maximum 128x averaging on both the
+ * current and voltage channels at the slowest 15 ksps base rate plus
+ * the inter-channel switching time, i.e. roughly 20 ms; 50 ms leaves
+ * ample margin.
+ */
+ ret = read_poll_timeout(max40080_read_iv_once, io_ret,
+ io_ret || (tmp & MAX40080_IV_VALID_MSK),
+ 500, 50 * USEC_PER_MSEC, false, st, &tmp);
+ if (io_ret)
+ return io_ret;
+
+ /*
+ * Propagate the last-read value even on timeout so the caller can
+ * inspect it for debugging.
+ */
+ *iv = tmp;
+
+ return ret;
+}
+
+static int max40080_get_current(struct max40080_state *st, int *val)
+{
+ u32 iv;
+ int ret;
+
+ ret = max40080_read_iv(st, &iv);
+ if (ret)
+ return ret;
+
+ *val = sign_extend32(FIELD_GET(MAX40080_IV_I_MSK, iv),
+ MAX40080_IV_I_SIGN_BIT);
+
+ return 0;
+}
+
+static int max40080_get_voltage(struct max40080_state *st, int *val)
+{
+ u32 iv;
+ int ret;
+
+ ret = max40080_read_iv(st, &iv);
+ if (ret)
+ return ret;
+
+ *val = FIELD_GET(MAX40080_IV_V_MAG_MSK, iv);
+
+ return 0;
+}
+
+static int max40080_set_range(struct max40080_state *st, unsigned int range)
+{
+ int ret;
+
+ ret = max40080_update_bits(st, MAX40080_REG_CFG, MAX40080_CFG_RANGE_MSK,
+ FIELD_PREP(MAX40080_CFG_RANGE_MSK, range));
+ if (ret)
+ return ret;
+
+ WRITE_ONCE(st->range, range);
+
+ return 0;
+}
+
+/*
+ * Precompute the current scale (mA per code) for each RANGE setting as
+ * {integer, nano} pairs. The shunt drop for a full-scale code is
+ * Vref[mV] / (BIT(ADC_RES_BITS) * gain)
+ * and current = Vshunt / Rshunt, so with Rshunt in micro-ohms the scale in
+ * mA/code is
+ * Vref[mV] * NANO * MICRO / (BIT(ADC_RES_BITS) * gain * Rshunt[uOhm])
+ * expressed as an integer part plus a nano fractional part.
+ */
+static void max40080_calc_current_scale(struct max40080_state *st)
+{
+ u64 numerator, denominator;
+ u32 rem;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(max40080_csa_gain); i++) {
+ numerator = (u64)MAX40080_INTER_VREF_mV * NANO * MICRO;
+ denominator = BIT_ULL(MAX40080_ADC_RES_BITS) * max40080_csa_gain[i] *
+ st->shunt_resistor_uOhm;
+ numerator = div64_u64(numerator, denominator);
+ st->current_scale[i][0] = div_u64_rem(numerator, NANO, &rem);
+ st->current_scale[i][1] = rem;
+ }
+}
+
+/*
+ * max40080_oversampling_avail[] is ordered so that its index is the FILTER
+ * field value (index 0 = no averaging, index 1 = 8x, ...). Return that index
+ * for an exact match, or -EINVAL for a value that is not on the list.
+ */
+static int max40080_oversampling_to_filter(int val)
+{
+ for (unsigned int i = 0; i < ARRAY_SIZE(max40080_oversampling_avail); i++) {
+ if (max40080_oversampling_avail[i] == val)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int max40080_set_oversampling_ratio(struct max40080_state *st, int val)
+{
+ int filter;
+ int ret;
+
+ filter = max40080_oversampling_to_filter(val);
+ if (filter < 0)
+ return filter;
+
+ ret = max40080_update_bits(st, MAX40080_REG_CFG, MAX40080_CFG_FILTER_MSK,
+ FIELD_PREP(MAX40080_CFG_FILTER_MSK, filter));
+ if (ret)
+ return ret;
+
+ WRITE_ONCE(st->oversampling_ratio, val);
+
+ return 0;
+}
+
+static int max40080_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct max40080_state *st = iio_priv(indio_dev);
+ unsigned int range;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (chan->type == IIO_CURRENT) {
+ ret = max40080_get_current(st, val);
+ if (ret)
+ return ret;
+ } else if (chan->type == IIO_VOLTAGE) {
+ ret = max40080_get_voltage(st, val);
+ if (ret)
+ return ret;
+ }
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type == IIO_CURRENT) {
+ /*
+ * The selectable current-sense range is exposed through
+ * scale: each RANGE setting has its own precomputed
+ * mA-per-code value. Userspace picks the range by
+ * writing the matching scale.
+ *
+ * Use READ_ONCE to ensure the compiler reads st->range
+ * exactly once, so val and val2 come from the same
+ * setting even if a concurrent write changes st->range.
+ */
+ range = READ_ONCE(st->range);
+ *val = st->current_scale[range][0];
+ *val2 = st->current_scale[range][1];
+ return IIO_VAL_INT_PLUS_NANO;
+ }
+ /* voltage[mV] = raw * Vref[mV] * buffer_gain / BIT(ADC_RES_BITS) */
+ *val = MAX40080_INTER_VREF_mV * MAX40080_V_BUFF_GAIN;
+ *val2 = MAX40080_ADC_RES_BITS;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ *val = READ_ONCE(st->oversampling_ratio);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int max40080_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct max40080_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ /* Only the current channel has a selectable range/scale. */
+ if (chan->type != IIO_CURRENT)
+ return -EINVAL;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(max40080_csa_gain); i++) {
+ if (val == st->current_scale[i][0] &&
+ val2 == st->current_scale[i][1])
+ return max40080_set_range(st, i);
+ }
+
+ return -EINVAL;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ return max40080_set_oversampling_ratio(st, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int max40080_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 int max40080_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long info)
+{
+ struct max40080_state *st = iio_priv(indio_dev);
+
+ switch (info) {
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type != IIO_CURRENT)
+ return -EINVAL;
+
+ *vals = (int *)st->current_scale;
+ *length = ARRAY_SIZE(max40080_csa_gain) * 2;
+ *type = IIO_VAL_INT_PLUS_NANO;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ *vals = max40080_oversampling_avail;
+ *length = ARRAY_SIZE(max40080_oversampling_avail);
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int max40080_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+ unsigned int write_val, unsigned int *read_val)
+{
+ struct max40080_state *st = iio_priv(indio_dev);
+ int val;
+
+ if (read_val) {
+ val = i2c_smbus_read_word_data(st->client, reg);
+ if (val < 0)
+ return val;
+
+ *read_val = val;
+
+ return 0;
+ }
+
+ return i2c_smbus_write_word_data(st->client, reg, write_val);
+}
+
+static const struct iio_info max40080_info = {
+ .read_raw = max40080_read_raw,
+ .write_raw = max40080_write_raw,
+ .write_raw_get_fmt = max40080_write_raw_get_fmt,
+ .read_avail = max40080_read_avail,
+ .debugfs_reg_access = &max40080_reg_access,
+};
+
+static const struct iio_chan_spec max40080_channels[] = {
+ {
+ .type = IIO_CURRENT,
+ .indexed = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_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),
+ },
+ {
+ .type = IIO_VOLTAGE,
+ .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_OVERSAMPLING_RATIO),
+ .info_mask_shared_by_all_available =
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ },
+};
+
+/*
+ * Configure the device from the cached state. The device powers up in standby
+ * with PEC enabled (CFG POR = 0x0060), so PEC is kept enabled throughout.
+ */
+static int max40080_init(struct max40080_state *st)
+{
+ u16 fifo_cfg, cfg;
+ int ret, filter;
+
+ filter = max40080_oversampling_to_filter(st->oversampling_ratio);
+ if (filter < 0)
+ return filter;
+
+ /*
+ * Put the device in standby before (re)configuring the FIFO: the FIFO
+ * configuration register can only be written while the device is not
+ * converting.
+ */
+ cfg = FIELD_PREP(MAX40080_CFG_MODE_MSK, MAX40080_CFG_MODE_STDBY) |
+ FIELD_PREP(MAX40080_CFG_PEC_EN_MSK, 1);
+ ret = i2c_smbus_write_word_data(st->client, MAX40080_REG_CFG, cfg);
+ if (ret)
+ return ret;
+
+ /* Store a matched current+voltage pair per conversion. */
+ fifo_cfg = FIELD_PREP(MAX40080_FIFO_CFG_STORE_IV_MSK, MAX40080_FIFO_CFG_STORE_IV);
+ ret = i2c_smbus_write_word_data(st->client, MAX40080_REG_FIFO_CFG,
+ fifo_cfg);
+ if (ret)
+ return ret;
+
+ /*
+ * Use single-measurement mode: the device stays idle and converts once
+ * per SMBus Quick Command (see max40080_trigger_measurement()), so each
+ * read returns a fresh sample rather than a queued FIFO entry.
+ */
+ cfg = FIELD_PREP(MAX40080_CFG_MODE_MSK, MAX40080_CFG_MODE_SINGLE) |
+ FIELD_PREP(MAX40080_CFG_PEC_EN_MSK, 1) |
+ FIELD_PREP(MAX40080_CFG_RANGE_MSK, st->range) |
+ FIELD_PREP(MAX40080_CFG_FILTER_MSK, filter);
+
+ return i2c_smbus_write_word_data(st->client, MAX40080_REG_CFG, cfg);
+}
+
+static int max40080_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ const char *propname;
+ struct iio_dev *indio_dev;
+ struct max40080_state *st;
+ int ret;
+
+ /*
+ * The device powers up with PEC enabled (CFG POR = 0x0060) and rejects
+ * unprotected transactions, so PEC support is mandatory, along with
+ * word access, the I2C block read used for the current/voltage pair,
+ * and the Quick Command used to trigger a conversion.
+ */
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WORD_DATA |
+ I2C_FUNC_SMBUS_I2C_BLOCK |
+ I2C_FUNC_SMBUS_QUICK |
+ I2C_FUNC_SMBUS_PEC))
+ return -EOPNOTSUPP;
+
+ client->flags |= I2C_CLIENT_PEC;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->client = client;
+
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ propname = "shunt-resistor-micro-ohms";
+ ret = device_property_read_u32(dev, propname, &st->shunt_resistor_uOhm);
+ if (ret)
+ return dev_err_probe(dev, ret, "can't read %s\n", propname);
+ if (!st->shunt_resistor_uOhm)
+ return dev_err_probe(dev, -EINVAL, "%s must be non-zero\n", propname);
+
+ max40080_calc_current_scale(st);
+
+ /* Defaults: 50 mV range, no averaging. */
+ st->range = MAX40080_CFG_RANGE_50mV;
+ st->oversampling_ratio = 1;
+
+ indio_dev->name = "max40080";
+ indio_dev->info = &max40080_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = max40080_channels;
+ indio_dev->num_channels = ARRAY_SIZE(max40080_channels);
+
+ ret = max40080_init(st);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct i2c_device_id max40080_i2c_ids[] = {
+ { .name = "max40080" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max40080_i2c_ids);
+
+static const struct of_device_id max40080_of_match[] = {
+ { .compatible = "adi,max40080" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max40080_of_match);
+
+static struct i2c_driver max40080_driver = {
+ .driver = {
+ .name = "max40080",
+ .of_match_table = max40080_of_match,
+ },
+ .probe = max40080_probe,
+ .id_table = max40080_i2c_ids,
+};
+module_i2c_driver(max40080_driver);
+
+MODULE_AUTHOR("Ciprian Hegbeli <ciprian.hegbeli@analog.com>");
+MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>");
+MODULE_DESCRIPTION("Analog Devices MAX40080 current-sense amplifier driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/mcp3911.c b/drivers/iio/adc/mcp3911.c
index 797424f59f74..90167930f913 100644
--- a/drivers/iio/adc/mcp3911.c
+++ b/drivers/iio/adc/mcp3911.c
@@ -721,6 +721,8 @@ static int mcp3911_probe(struct spi_device *spi)
adc = iio_priv(indio_dev);
adc->spi = spi;
adc->chip = spi_get_device_match_data(spi);
+ if (!adc->chip)
+ return -ENODATA;
ret = devm_regulator_get_enable_read_voltage(dev, "vref");
if (ret < 0 && ret != -ENODEV)
diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c
index 000e39ca5c62..2b003fd5afeb 100644
--- a/drivers/iio/adc/meson_saradc.c
+++ b/drivers/iio/adc/meson_saradc.c
@@ -739,7 +739,7 @@ static int meson_sar_adc_clk_init(struct iio_dev *indio_dev,
{
struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
struct device *dev = indio_dev->dev.parent;
- struct clk_init_data init;
+ struct clk_init_data init = { };
const char *clk_parents[1];
init.name = devm_kasprintf(dev, GFP_KERNEL, "%s#adc_div", dev_name(dev));
diff --git a/drivers/iio/adc/nxp-sar-adc.c b/drivers/iio/adc/nxp-sar-adc.c
index 894d3211b283..2f716a2f97b8 100644
--- a/drivers/iio/adc/nxp-sar-adc.c
+++ b/drivers/iio/adc/nxp-sar-adc.c
@@ -604,7 +604,7 @@ static void nxp_sar_adc_dma_cb(void *data)
dma_buf = &info->dma_buf;
dma_samples = (u32 *)dma_buf->buf;
- dev_dma = info->dma_chan->device->dev;
+ dev_dma = dmaengine_get_dma_device(info->dma_chan);
/*
* DMA in some corner cases might have already be charged for
diff --git a/drivers/iio/adc/pac1934.c b/drivers/iio/adc/pac1934.c
index de59dc27c440..5e901ab25c8e 100644
--- a/drivers/iio/adc/pac1934.c
+++ b/drivers/iio/adc/pac1934.c
@@ -19,6 +19,7 @@
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
+#include <linux/kstrtox.h>
#include <linux/unaligned.h>
/*
@@ -494,11 +495,13 @@ static ssize_t pac1934_shunt_value_store(struct device *dev,
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;
+ unsigned int sh_val;
+ int ret;
- if (kstrtouint(buf, 10, &sh_val)) {
+ ret = kstrtouint(buf, 10, &sh_val);
+ if (ret) {
dev_err(dev, "Shunt value is not valid\n");
- return -EINVAL;
+ return ret;
}
scoped_guard(mutex, &info->lock)
diff --git a/drivers/iio/adc/qcom-spmi-iadc.c b/drivers/iio/adc/qcom-spmi-iadc.c
index 0ec3a0c4b1de..196525ba0b4b 100644
--- a/drivers/iio/adc/qcom-spmi-iadc.c
+++ b/drivers/iio/adc/qcom-spmi-iadc.c
@@ -16,6 +16,7 @@
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
+#include <linux/spmi.h>
/* IADC register and bit definition */
#define IADC_REVISION2 0x1
@@ -94,7 +95,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.
@@ -105,7 +105,6 @@
struct iadc_chip {
struct regmap *regmap;
struct device *dev;
- u16 base;
bool poll_eoc;
u32 rsense[2];
u16 offset[2];
@@ -114,77 +113,59 @@ struct iadc_chip {
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;
+ u32 data;
int ret;
- ret = iadc_write(iadc, IADC_SEC_ACCESS, IADC_SEC_ACCESS_DATA);
+ ret = regmap_write(iadc->regmap, IADC_SEC_ACCESS, IADC_SEC_ACCESS_DATA);
if (ret < 0)
return ret;
- ret = iadc_read(iadc, IADC_PERH_RESET_CTL3, &data);
+ ret = regmap_read(iadc->regmap, IADC_PERH_RESET_CTL3, &data);
if (ret < 0)
return ret;
- ret = iadc_write(iadc, IADC_SEC_ACCESS, IADC_SEC_ACCESS_DATA);
+ ret = regmap_write(iadc->regmap, 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);
+ return regmap_write(iadc->regmap, 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);
+ return regmap_write(iadc->regmap, 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;
+ u32 mode, sta1, chan, dig, en, req;
int ret;
- ret = iadc_read(iadc, IADC_MODE_CTL, &mode);
+ ret = regmap_read(iadc->regmap, IADC_MODE_CTL, &mode);
if (ret < 0)
return;
- ret = iadc_read(iadc, IADC_DIG_PARAM, &dig);
+ ret = regmap_read(iadc->regmap, IADC_DIG_PARAM, &dig);
if (ret < 0)
return;
- ret = iadc_read(iadc, IADC_CH_SEL_CTL, &chan);
+ ret = regmap_read(iadc->regmap, IADC_CH_SEL_CTL, &chan);
if (ret < 0)
return;
- ret = iadc_read(iadc, IADC_CONV_REQ, &req);
+ ret = regmap_read(iadc->regmap, IADC_CONV_REQ, &req);
if (ret < 0)
return;
- ret = iadc_read(iadc, IADC_STATUS1, &sta1);
+ ret = regmap_read(iadc->regmap, IADC_STATUS1, &sta1);
if (ret < 0)
return;
- ret = iadc_read(iadc, IADC_EN_CTL1, &en);
+ ret = regmap_read(iadc->regmap, IADC_EN_CTL1, &en);
if (ret < 0)
return;
@@ -200,34 +181,34 @@ static int iadc_configure(struct iadc_chip *iadc, int channel)
/* Mode selection */
mode = (IADC_OP_MODE_NORMAL << IADC_OP_MODE_SHIFT) | IADC_TRIM_EN;
- ret = iadc_write(iadc, IADC_MODE_CTL, mode);
+ ret = regmap_write(iadc->regmap, IADC_MODE_CTL, mode);
if (ret < 0)
return ret;
/* Channel selection */
- ret = iadc_write(iadc, IADC_CH_SEL_CTL, channel);
+ ret = regmap_write(iadc->regmap, 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);
+ ret = regmap_write(iadc->regmap, 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);
+ ret = regmap_write(iadc->regmap, 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);
+ ret = regmap_write(iadc->regmap, 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);
+ ret = regmap_write(iadc->regmap, IADC_FAST_AVG_EN, IADC_FAST_AVG_EN_SET);
else
- ret = iadc_write(iadc, IADC_FAST_AVG_EN, 0);
+ ret = regmap_write(iadc->regmap, IADC_FAST_AVG_EN, 0);
if (ret < 0)
return ret;
@@ -240,19 +221,19 @@ static int iadc_configure(struct iadc_chip *iadc, int channel)
return ret;
/* Request conversion */
- return iadc_write(iadc, IADC_CONV_REQ, IADC_CONV_REQ_SET);
+ return regmap_write(iadc->regmap, 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;
+ u32 sta1;
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);
+ ret = regmap_read(iadc->regmap, IADC_STATUS1, &sta1);
if (ret < 0)
return ret;
@@ -268,11 +249,6 @@ static int iadc_poll_wait_eoc(struct iadc_chip *iadc, unsigned int interval_us)
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;
@@ -297,7 +273,7 @@ static int iadc_do_conversion(struct iadc_chip *iadc, int chan, u16 *data)
}
if (!ret)
- ret = iadc_read_result(iadc, data);
+ ret = regmap_bulk_read(iadc->regmap, IADC_DATA, data, sizeof(*data));
exit:
iadc_set_state(iadc, false);
if (ret < 0)
@@ -393,33 +369,33 @@ static int iadc_update_offset(struct iadc_chip *iadc)
static int iadc_version_check(struct iadc_chip *iadc)
{
- u8 val;
+ u32 val;
int ret;
- ret = iadc_read(iadc, IADC_PERPH_TYPE, &val);
+ ret = regmap_read(iadc->regmap, 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);
+ dev_err(iadc->dev, "%u is not ADC\n", val);
return -EINVAL;
}
- ret = iadc_read(iadc, IADC_PERPH_SUBTYPE, &val);
+ ret = regmap_read(iadc->regmap, 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);
+ dev_err(iadc->dev, "%u is not IADC\n", val);
return -EINVAL;
}
- ret = iadc_read(iadc, IADC_REVISION2, &val);
+ ret = regmap_read(iadc->regmap, IADC_REVISION2, &val);
if (ret < 0)
return ret;
if (val < IADC_REVISION2_SUPPORTED_IADC) {
- dev_err(iadc->dev, "revision %d not supported\n", val);
+ dev_err(iadc->dev, "revision %u not supported\n", val);
return -EINVAL;
}
@@ -429,7 +405,7 @@ static int iadc_version_check(struct iadc_chip *iadc)
static int iadc_rsense_read(struct iadc_chip *iadc, struct device_node *node)
{
int ret, sign, int_sense;
- u8 deviation;
+ u32 deviation;
ret = of_property_read_u32(node, "qcom,external-resistor-micro-ohms",
&iadc->rsense[IADC_EXT_RSENSE]);
@@ -441,7 +417,7 @@ static int iadc_rsense_read(struct iadc_chip *iadc, struct device_node *node)
return -EINVAL;
}
- ret = iadc_read(iadc, IADC_NOMINAL_RSENSE, &deviation);
+ ret = regmap_read(iadc->regmap, IADC_NOMINAL_RSENSE, &deviation);
if (ret < 0)
return ret;
@@ -488,12 +464,19 @@ static void iadc_disable_irq_wake(void *data)
static int iadc_probe(struct platform_device *pdev)
{
+ struct regmap_config iadc_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .max_register = 0xff,
+ .fast_io = true,
+ };
struct device_node *node = pdev->dev.of_node;
struct device *dev = &pdev->dev;
+ struct spmi_subdevice *sub_sdev;
+ struct spmi_device *sparent;
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)
@@ -502,18 +485,24 @@ static int iadc_probe(struct platform_device *pdev)
iadc = iio_priv(indio_dev);
iadc->dev = dev;
- iadc->regmap = dev_get_regmap(dev->parent, NULL);
- if (!iadc->regmap)
+ sparent = spmi_get_parent_spmi_device(dev);
+ if (!sparent)
return -ENODEV;
+ sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent);
+ if (IS_ERR(sub_sdev))
+ return PTR_ERR(sub_sdev);
+
init_completion(&iadc->complete);
mutex_init(&iadc->lock);
- ret = of_property_read_u32(node, "reg", &res);
+ ret = device_property_read_u32(dev, "reg", &iadc_regmap_config.reg_base);
if (ret < 0)
return -ENODEV;
- iadc->base = res;
+ iadc->regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev, &iadc_regmap_config);
+ if (IS_ERR(iadc->regmap))
+ return PTR_ERR(iadc->regmap);
ret = iadc_version_check(iadc);
if (ret < 0)
@@ -596,3 +585,4 @@ MODULE_ALIAS("platform:qcom-spmi-iadc");
MODULE_DESCRIPTION("Qualcomm SPMI PMIC current ADC driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>");
+MODULE_IMPORT_NS("SPMI");
diff --git a/drivers/iio/adc/rockchip_saradc.c b/drivers/iio/adc/rockchip_saradc.c
index 0f0bf2906af0..5c45fae11890 100644
--- a/drivers/iio/adc/rockchip_saradc.c
+++ b/drivers/iio/adc/rockchip_saradc.c
@@ -348,6 +348,19 @@ static const struct rockchip_saradc_data rk3588_saradc_data = {
.read = rockchip_saradc_read_v2,
};
+static const struct iio_chan_spec rockchip_rv1106_saradc_iio_channels[] = {
+ SARADC_CHANNEL(0, "adc0", 10),
+ SARADC_CHANNEL(1, "adc1", 10),
+};
+
+static const struct rockchip_saradc_data rv1106_saradc_data = {
+ .channels = rockchip_rv1106_saradc_iio_channels,
+ .num_channels = ARRAY_SIZE(rockchip_rv1106_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",
@@ -370,6 +383,9 @@ static const struct of_device_id rockchip_saradc_match[] = {
}, {
.compatible = "rockchip,rk3588-saradc",
.data = &rk3588_saradc_data,
+ }, {
+ .compatible = "rockchip,rv1106-saradc",
+ .data = &rv1106_saradc_data,
},
{ }
};
diff --git a/drivers/iio/adc/rzg2l_adc.c b/drivers/iio/adc/rzg2l_adc.c
index 408fbf8c29cc..3b95f47445a8 100644
--- a/drivers/iio/adc/rzg2l_adc.c
+++ b/drivers/iio/adc/rzg2l_adc.c
@@ -62,7 +62,7 @@
* @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
+ * @max_channels: maximum number of channels
* @adivc: specifies if ADVIC register is available
*/
struct rzg2l_adc_hw_params {
@@ -70,7 +70,7 @@ struct rzg2l_adc_hw_params {
u16 adsmp_mask;
u16 adint_inten_mask;
u8 default_adcmp;
- u8 num_channels;
+ u8 max_channels;
bool adivc;
};
@@ -196,7 +196,7 @@ static int rzg2l_adc_conversion_setup(struct rzg2l_adc *adc, u8 ch)
/* Select analog input channel subjected to conversion. */
reg = rzg2l_adc_readl(adc, RZG2L_ADM(2));
- reg &= ~GENMASK(hw_params->num_channels - 1, 0);
+ reg &= ~GENMASK(hw_params->max_channels - 1, 0);
reg |= BIT(ch);
rzg2l_adc_writel(adc, RZG2L_ADM(2), reg);
@@ -307,11 +307,11 @@ static irqreturn_t rzg2l_adc_isr(int irq, void *dev_id)
return IRQ_HANDLED;
}
- intst = reg & GENMASK(hw_params->num_channels - 1, 0);
+ intst = reg & GENMASK(hw_params->max_channels - 1, 0);
if (!intst)
return IRQ_NONE;
- for_each_set_bit(ch, &intst, hw_params->num_channels)
+ for_each_set_bit(ch, &intst, hw_params->max_channels)
adc->last_val[ch] = rzg2l_adc_readl(adc, RZG2L_ADCR(ch)) & RZG2L_ADCR_AD_MASK;
/* clear the channel interrupt */
@@ -341,12 +341,12 @@ static int rzg2l_adc_parse_properties(struct platform_device *pdev, struct rzg2l
num_channels = devm_iio_adc_device_alloc_chaninfo_se(&pdev->dev,
&rzg2l_adc_chan_template,
- hw_params->num_channels - 1,
+ hw_params->max_channels - 1,
&chan_array);
if (num_channels < 0)
return num_channels;
- if (num_channels > hw_params->num_channels)
+ if (num_channels > hw_params->max_channels)
return dev_err_probe(&pdev->dev, -EINVAL,
"num of channel children out of range\n");
@@ -429,7 +429,7 @@ static int rzg2l_adc_probe(struct platform_device *pdev)
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)
+ if (!adc->hw_params || adc->hw_params->max_channels > RZG2L_ADC_MAX_CHANNELS)
return -EINVAL;
ret = rzg2l_adc_parse_properties(pdev, adc);
@@ -484,7 +484,7 @@ static int rzg2l_adc_probe(struct platform_device *pdev)
}
static const struct rzg2l_adc_hw_params rzg2l_hw_params = {
- .num_channels = 8,
+ .max_channels = 8,
.default_adcmp = 0xe,
.default_adsmp = { 0x578 },
.adsmp_mask = GENMASK(15, 0),
@@ -493,15 +493,24 @@ static const struct rzg2l_adc_hw_params rzg2l_hw_params = {
};
static const struct rzg2l_adc_hw_params rzg3s_hw_params = {
- .num_channels = 9,
+ .max_channels = 9,
.default_adcmp = 0x1d,
.default_adsmp = { 0x7f, 0xff },
.adsmp_mask = GENMASK(7, 0),
.adint_inten_mask = GENMASK(11, 0),
};
+static const struct rzg2l_adc_hw_params rzg3l_adc1_hw_params = {
+ .max_channels = 9,
+ .default_adcmp = 0x1d,
+ .default_adsmp = { 0x7f, 0xff },
+ .adsmp_mask = GENMASK(7, 0),
+ .adint_inten_mask = BIT(8),
+};
+
static const struct of_device_id rzg2l_adc_match[] = {
{ .compatible = "renesas,r9a08g045-adc", .data = &rzg3s_hw_params },
+ { .compatible = "renesas,r9a08g046-adc1", .data = &rzg3l_adc1_hw_params },
{ .compatible = "renesas,rzg2l-adc", .data = &rzg2l_hw_params },
{ }
};
diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c
index 4e0eb02d3d14..8ba7b0fde8a1 100644
--- a/drivers/iio/adc/rzt2h_adc.c
+++ b/drivers/iio/adc/rzt2h_adc.c
@@ -2,13 +2,19 @@
#include <linux/bitfield.h>
#include <linux/cleanup.h>
+#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
#include <linux/iio/adc-helpers.h>
+#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
+#include <linux/iio/kfifo_buf.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
+#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
@@ -18,6 +24,7 @@
#define RZT2H_ADCSR_ADIE_MASK BIT(12)
#define RZT2H_ADCSR_ADCS_MASK GENMASK(14, 13)
#define RZT2H_ADCSR_ADCS_SINGLE 0b00
+#define RZT2H_ADCSR_ADCS_CONTINUOUS 0b10
#define RZT2H_ADCSR_ADST_MASK BIT(15)
#define RZT2H_ADANSA0_REG 0x04
@@ -25,24 +32,71 @@
#define RZT2H_ADDR_REG(x) (0x20 + 0x2 * (x))
+#define RZT2H_ADSSTRn(n) (0xe0 + 0x1 * (n))
+
+/*
+ * Each A/D channel conversion takes a fixed base of 13 ADCLK cycles plus the
+ * sample time programmed in ADSSTRn, giving a conversion rate of
+ * ADCLK / (13 + ADSSTRn).
+ */
+#define RZT2H_ADC_CONV_CYCLES_BASE 13
+#define RZT2H_ADC_SST_MIN 0x7
+#define RZT2H_ADC_SST_MAX 0xff
+#define RZT2H_ADC_SST_DEFAULT 0xb
+
#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
+#define RZT2H_ADC_CHANNEL_BYTES sizeof(u16)
+#define RZT2H_ADC_DMA_PERIOD_SAMPLES 128
+#define RZT2H_ADC_DMA_PERIODS 64
+#define RZT2H_ADC_DMA_BUFFER_SAMPLES (RZT2H_ADC_DMA_PERIODS * \
+ RZT2H_ADC_DMA_PERIOD_SAMPLES)
+#define RZT2H_ADC_DMA_BUFFER_SIZE (RZT2H_ADC_DMA_BUFFER_SAMPLES * \
+ RZT2H_ADC_MAX_CHANNELS * \
+ RZT2H_ADC_CHANNEL_BYTES)
+
+struct rzt2h_adc_dma {
+ struct dma_chan *chan;
+ u16 *buf;
+ dma_addr_t addr;
+
+ unsigned int period_index;
+ unsigned int period_bytes;
+ unsigned int first_chan;
+ unsigned int sample_chans;
+
+ u8 gather[RZT2H_ADC_MAX_CHANNELS];
+ unsigned int gather_len;
+
+ atomic_t pending_periods;
+
+ wait_queue_head_t wq;
+ struct task_struct *thread;
+};
struct rzt2h_adc {
void __iomem *base;
struct device *dev;
+ phys_addr_t phys_base;
+ struct rzt2h_adc_dma dma;
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;
+ u16 buf[RZT2H_ADC_MAX_CHANNELS];
+
+ unsigned long adclk_rate;
+ int samp_freq_avail[3];
+ u8 sst[RZT2H_ADC_MAX_CHANNELS];
+
+ int irq;
};
static void rzt2h_adc_start(struct rzt2h_adc *adc, unsigned int conversion_type)
@@ -72,6 +126,11 @@ static void rzt2h_adc_stop(struct rzt2h_adc *adc)
writew(reg, adc->base + RZT2H_ADCSR_REG);
}
+static void rzt2h_adc_set_sst(struct rzt2h_adc *adc, unsigned int ch, u8 sst)
+{
+ writeb(sst, adc->base + RZT2H_ADSSTRn(ch));
+}
+
static int rzt2h_adc_read_single(struct rzt2h_adc *adc, unsigned int ch, int *val)
{
int ret;
@@ -87,13 +146,18 @@ static int rzt2h_adc_read_single(struct rzt2h_adc *adc, unsigned int ch, int *va
/* Enable a single channel */
writew(RZT2H_ADANSA0_CH_MASK(ch), adc->base + RZT2H_ADANSA0_REG);
+ rzt2h_adc_set_sst(adc, ch, adc->sst[ch]);
+
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.
+ * Conversion can take up to ~4.3us at the maximum configurable ADSSTRn
+ * (ADSSTRn + 13 cycles at 62.5 MHz), which rounds up to 1 jiffy. A bare
+ * 1-jiffy timeout can expire almost immediately if it's armed right
+ * before a tick, so add one more jiffy to guarantee the conversion time
+ * actually elapses.
*/
- ret = wait_for_completion_timeout(&adc->completion, usecs_to_jiffies(1));
+ ret = wait_for_completion_timeout(&adc->completion, usecs_to_jiffies(5) + 1);
if (!ret) {
ret = -ETIMEDOUT;
goto disable;
@@ -149,6 +213,281 @@ static int rzt2h_adc_calibrate(struct rzt2h_adc *adc)
return 0;
}
+static void rzt2h_adc_push_period(struct iio_dev *indio_dev, u16 *period,
+ dma_addr_t addr)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+ u16 *dst = adc->buf;
+ u16 *src = period;
+
+ dma_sync_single_for_cpu(adc->dev, addr, adc->dma.period_bytes,
+ DMA_FROM_DEVICE);
+
+ for (unsigned int sample = 0; sample < RZT2H_ADC_DMA_PERIOD_SAMPLES; sample++) {
+ for (unsigned int i = 0; i < adc->dma.gather_len; i++)
+ dst[i] = src[adc->dma.gather[i]];
+
+ src += adc->dma.sample_chans;
+
+ iio_push_to_buffers(indio_dev, adc->buf);
+ }
+}
+
+static void rzt2h_adc_advance_period_index(struct rzt2h_adc *adc, unsigned int i)
+{
+ adc->dma.period_index += i;
+ adc->dma.period_index %= RZT2H_ADC_DMA_PERIODS;
+}
+
+static void rzt2h_adc_dma_thread_loop(struct iio_dev *indio_dev)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+ int pending, drop;
+ dma_addr_t addr;
+ u16 *period;
+
+ pending = atomic_xchg(&adc->dma.pending_periods, 0);
+
+ if (pending >= RZT2H_ADC_DMA_PERIODS) {
+ /*
+ * The consumer fell a full buffer behind and the oldest periods
+ * have already been overwritten by the cyclic DMA transfer.
+ * Drop them and jump straight to the oldest period that has
+ * not been overwritten.
+ */
+ drop = pending - RZT2H_ADC_DMA_PERIODS + 1;
+
+ rzt2h_adc_advance_period_index(adc, drop);
+ pending -= drop;
+ }
+
+ for (unsigned int i = 0; i < pending; i++) {
+ unsigned int backlog = atomic_read(&adc->dma.pending_periods) +
+ pending - i;
+
+ /*
+ * Bail if enough new periods have completed since reading the
+ * pending_periods that the next period about to be read is at
+ * risk of being overwritten.
+ */
+ if (backlog >= RZT2H_ADC_DMA_PERIODS) {
+ rzt2h_adc_advance_period_index(adc, pending - i);
+ break;
+ }
+
+ period = adc->dma.buf + adc->dma.period_index *
+ RZT2H_ADC_DMA_PERIOD_SAMPLES * adc->dma.sample_chans;
+ addr = adc->dma.addr + adc->dma.period_index *
+ adc->dma.period_bytes;
+
+ rzt2h_adc_push_period(indio_dev, period, addr);
+ rzt2h_adc_advance_period_index(adc, 1);
+ }
+}
+
+static int rzt2h_adc_dma_thread(void *data)
+{
+ struct iio_dev *indio_dev = data;
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+
+ while (!kthread_should_stop()) {
+ wait_event_interruptible(adc->dma.wq,
+ atomic_read(&adc->dma.pending_periods) ||
+ kthread_should_stop());
+
+ if (kthread_should_stop())
+ break;
+
+ rzt2h_adc_dma_thread_loop(indio_dev);
+ }
+
+ return 0;
+}
+
+static void rzt2h_adc_dma_callback(void *data)
+{
+ struct iio_dev *indio_dev = data;
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+
+ atomic_inc(&adc->dma.pending_periods);
+ wake_up(&adc->dma.wq);
+}
+
+static void rzt2h_adc_dma_calc_layout(struct iio_dev *indio_dev)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+ unsigned int hi = 0, lo = RZT2H_ADC_MAX_CHANNELS - 1;
+ const struct iio_chan_spec *chan;
+ unsigned int sample_chans;
+ unsigned int first_chan;
+ unsigned int scan_index;
+ unsigned int swap;
+ unsigned int idx;
+
+ /* Find the lowest and highest enabled channel. */
+ iio_for_each_active_channel(indio_dev, scan_index) {
+ chan = &indio_dev->channels[scan_index];
+
+ lo = min_t(unsigned int, lo, chan->channel);
+ hi = max_t(unsigned int, hi, chan->channel);
+ }
+
+ /*
+ * The DMA has no scatter-gather and transfers must have a power-of-two
+ * width, so pick the smallest power-of-two-aligned block of channels
+ * that covers all enabled channels.
+ */
+ for (sample_chans = 1; sample_chans < RZT2H_ADC_MAX_CHANNELS; sample_chans <<= 1) {
+ first_chan = round_down(lo, sample_chans);
+
+ if (first_chan + sample_chans > hi)
+ break;
+ }
+
+ /*
+ * Build a table to map each enabled channel to its position in the
+ * transferred block, it will be used later to extract only the enabled
+ * channels out of it.
+ * The DMA moves data in 32-bit words, which swaps each pair of adjacent
+ * 16-bit channels. Undo it.
+ */
+ adc->dma.gather_len = 0;
+ swap = sample_chans > 1;
+ iio_for_each_active_channel(indio_dev, scan_index) {
+ chan = &indio_dev->channels[scan_index];
+ idx = chan->channel - first_chan;
+
+ adc->dma.gather[adc->dma.gather_len++] = idx ^ swap;
+ }
+
+ adc->dma.first_chan = first_chan;
+ adc->dma.sample_chans = sample_chans;
+ adc->dma.period_bytes = RZT2H_ADC_DMA_PERIOD_SAMPLES * sample_chans *
+ RZT2H_ADC_CHANNEL_BYTES;
+}
+
+static int rzt2h_adc_start_dma(struct iio_dev *indio_dev)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+ struct dma_async_tx_descriptor *desc;
+ struct dma_slave_config config;
+ unsigned int buffer_bytes;
+ dma_cookie_t cookie;
+ int ret;
+
+ rzt2h_adc_dma_calc_layout(indio_dev);
+
+ config = (struct dma_slave_config) {
+ .src_addr = adc->phys_base + RZT2H_ADDR_REG(adc->dma.first_chan),
+ .src_addr_width = adc->dma.sample_chans * RZT2H_ADC_CHANNEL_BYTES,
+ };
+
+ buffer_bytes = RZT2H_ADC_DMA_PERIODS * adc->dma.period_bytes;
+
+ ret = dmaengine_slave_config(adc->dma.chan, &config);
+ if (ret)
+ return ret;
+
+ desc = dmaengine_prep_dma_cyclic(adc->dma.chan, adc->dma.addr,
+ buffer_bytes, adc->dma.period_bytes,
+ DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
+ if (!desc)
+ return -EBUSY;
+
+ desc->callback = rzt2h_adc_dma_callback;
+ desc->callback_param = indio_dev;
+
+ cookie = dmaengine_submit(desc);
+ ret = dma_submit_error(cookie);
+ if (ret) {
+ dmaengine_terminate_sync(adc->dma.chan);
+ return ret;
+ }
+
+ adc->dma.thread = kthread_run(rzt2h_adc_dma_thread, indio_dev,
+ "rzt2h-adc-dma");
+ if (IS_ERR(adc->dma.thread)) {
+ dmaengine_terminate_sync(adc->dma.chan);
+ return PTR_ERR(adc->dma.thread);
+ }
+
+ disable_irq(adc->irq);
+
+ dma_async_issue_pending(adc->dma.chan);
+
+ return 0;
+}
+
+static int rzt2h_adc_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+ const struct iio_chan_spec *chan;
+ struct device *dev = adc->dev;
+ unsigned int scan_index;
+ u16 val = 0;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ iio_for_each_active_channel(indio_dev, scan_index) {
+ chan = &indio_dev->channels[scan_index];
+ val |= RZT2H_ADANSA0_CH_MASK(chan->channel);
+ rzt2h_adc_set_sst(adc, chan->channel, adc->sst[chan->channel]);
+ }
+
+ writew(val, adc->base + RZT2H_ADANSA0_REG);
+
+ adc->dma.period_index = 0;
+ atomic_set(&adc->dma.pending_periods, 0);
+
+ ret = rzt2h_adc_start_dma(indio_dev);
+ if (ret) {
+ pm_runtime_put_autosuspend(dev);
+ return ret;
+ }
+
+ rzt2h_adc_start(adc, RZT2H_ADCSR_ADCS_CONTINUOUS);
+
+ return 0;
+}
+
+static int rzt2h_adc_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+ struct device *dev = adc->dev;
+
+ rzt2h_adc_stop(adc);
+
+ dmaengine_terminate_sync(adc->dma.chan);
+
+ kthread_stop(adc->dma.thread);
+
+ enable_irq(adc->irq);
+
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+}
+
+static int rzt2h_adc_sst_to_freq(struct rzt2h_adc *adc, u8 sst)
+{
+ return adc->adclk_rate / (RZT2H_ADC_CONV_CYCLES_BASE + sst);
+}
+
+static u8 rzt2h_adc_freq_to_sst(struct rzt2h_adc *adc, int freq)
+{
+ unsigned int cycles;
+
+ cycles = DIV_ROUND_CLOSEST(adc->adclk_rate, freq);
+ cycles = clamp_val(cycles,
+ RZT2H_ADC_CONV_CYCLES_BASE + RZT2H_ADC_SST_MIN,
+ RZT2H_ADC_CONV_CYCLES_BASE + RZT2H_ADC_SST_MAX);
+
+ return cycles - RZT2H_ADC_CONV_CYCLES_BASE;
+}
+
static int rzt2h_adc_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
@@ -156,19 +495,76 @@ static int rzt2h_adc_read_raw(struct iio_dev *indio_dev,
struct rzt2h_adc *adc = iio_priv(indio_dev);
switch (mask) {
- case IIO_CHAN_INFO_RAW:
+ case IIO_CHAN_INFO_RAW: {
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
return rzt2h_adc_read_single(adc, chan->channel, val);
+ }
case IIO_CHAN_INFO_SCALE:
*val = 1800;
*val2 = 12;
return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = rzt2h_adc_sst_to_freq(adc, adc->sst[chan->channel]);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int rzt2h_adc_write_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_SAMP_FREQ: {
+ if (val <= 0)
+ return -EINVAL;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ adc->sst[chan->channel] = rzt2h_adc_freq_to_sst(adc, val);
+
+ return 0;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int rzt2h_adc_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = adc->samp_freq_avail;
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(adc->samp_freq_avail);
+ return IIO_AVAIL_RANGE;
default:
return -EINVAL;
}
}
+static const struct iio_buffer_setup_ops rzt2h_adc_buffer_setup_ops = {
+ .postenable = rzt2h_adc_buffer_postenable,
+ .predisable = rzt2h_adc_buffer_predisable,
+};
+
static const struct iio_info rzt2h_adc_iio_info = {
.read_raw = rzt2h_adc_read_raw,
+ .write_raw = rzt2h_adc_write_raw,
+ .read_avail = rzt2h_adc_read_avail,
};
static irqreturn_t rzt2h_adc_isr(int irq, void *private)
@@ -183,14 +579,21 @@ static irqreturn_t rzt2h_adc_isr(int irq, void *private)
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),
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
.type = IIO_VOLTAGE,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 12,
+ .storagebits = 16,
+ .endianness = IIO_CPU,
+ },
};
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,
@@ -203,19 +606,64 @@ static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc)
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;
+ for (unsigned int i = 0; i < adc->num_channels; i++) {
+ adc->sst[chan_array[i].channel] = RZT2H_ADC_SST_DEFAULT;
+
+ chan_array[i].scan_index = i;
+ }
return 0;
}
+static void rzt2h_adc_free_dma_buf(void *p)
+{
+ struct rzt2h_adc *adc = p;
+
+ dma_free_noncoherent(adc->dev, RZT2H_ADC_DMA_BUFFER_SIZE,
+ adc->dma.buf, adc->dma.addr, DMA_FROM_DEVICE);
+}
+
+static int rzt2h_adc_setup_dma(struct iio_dev *indio_dev)
+{
+ struct rzt2h_adc *adc = iio_priv(indio_dev);
+ struct device *dev = adc->dev;
+ int ret;
+
+ adc->dma.chan = devm_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\n");
+
+ adc->dma.chan = NULL;
+ return 0;
+ }
+
+ adc->dma.buf = dma_alloc_noncoherent(dev, RZT2H_ADC_DMA_BUFFER_SIZE,
+ &adc->dma.addr, DMA_FROM_DEVICE,
+ GFP_KERNEL);
+ if (!adc->dma.buf)
+ return -ENOMEM;
+
+ dma_sync_single_for_device(dev, adc->dma.addr, RZT2H_ADC_DMA_BUFFER_SIZE,
+ DMA_FROM_DEVICE);
+
+ ret = devm_add_action_or_reset(dev, rzt2h_adc_free_dma_buf, adc);
+ if (ret)
+ return ret;
+
+ return devm_iio_kfifo_buffer_setup_ext(dev, indio_dev,
+ &rzt2h_adc_buffer_setup_ops, NULL);
+}
+
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;
+ struct resource *res;
+ struct clk *adclk;
+ int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
if (!indio_dev)
@@ -224,6 +672,7 @@ static int rzt2h_adc_probe(struct platform_device *pdev)
adc = iio_priv(indio_dev);
adc->dev = dev;
init_completion(&adc->completion);
+ init_waitqueue_head(&adc->dma.wq);
ret = devm_mutex_init(dev, &adc->lock);
if (ret)
@@ -235,21 +684,35 @@ static int rzt2h_adc_probe(struct platform_device *pdev)
if (ret)
return ret;
- adc->base = devm_platform_ioremap_resource(pdev, 0);
+ adc->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(adc->base))
return PTR_ERR(adc->base);
+ adc->phys_base = res->start;
+
+ adclk = devm_clk_get(dev, "adclk");
+ if (IS_ERR(adclk))
+ return dev_err_probe(dev, PTR_ERR(adclk), "failed to get adclk\n");
+
+ adc->adclk_rate = clk_get_rate(adclk);
+ if (!adc->adclk_rate)
+ return dev_err_probe(dev, -EINVAL, "invalid adclk rate\n");
+
+ adc->samp_freq_avail[0] = rzt2h_adc_sst_to_freq(adc, RZT2H_ADC_SST_MAX);
+ adc->samp_freq_avail[1] = 1;
+ adc->samp_freq_avail[2] = rzt2h_adc_sst_to_freq(adc, RZT2H_ADC_SST_MIN);
+
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;
+ adc->irq = platform_get_irq_byname(pdev, "adi");
+ if (adc->irq < 0)
+ return adc->irq;
- ret = devm_request_irq(dev, irq, rzt2h_adc_isr, 0, dev_name(dev), adc);
+ ret = devm_request_irq(dev, adc->irq, rzt2h_adc_isr, 0, dev_name(dev), adc);
if (ret)
return ret;
@@ -259,6 +722,10 @@ static int rzt2h_adc_probe(struct platform_device *pdev)
indio_dev->channels = adc->channels;
indio_dev->num_channels = adc->num_channels;
+ ret = rzt2h_adc_setup_dma(indio_dev);
+ if (ret)
+ return ret;
+
return devm_iio_device_register(dev, indio_dev);
}
diff --git a/drivers/iio/adc/sophgo-cv1800b-adc.c b/drivers/iio/adc/sophgo-cv1800b-adc.c
index bdc3e1326a9a..c6eed09acfcd 100644
--- a/drivers/iio/adc/sophgo-cv1800b-adc.c
+++ b/drivers/iio/adc/sophgo-cv1800b-adc.c
@@ -182,6 +182,8 @@ static int cv1800b_adc_probe(struct platform_device *pdev)
return PTR_ERR(saradc->regs);
saradc->irq = platform_get_irq_optional(pdev, 0);
+ if (saradc->irq < 0 && saradc->irq != -ENXIO)
+ return saradc->irq;
if (saradc->irq > 0) {
init_completion(&saradc->completion);
ret = devm_request_irq(dev, saradc->irq,
diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c
index 90f0e257e6d1..600f975f1d7b 100644
--- a/drivers/iio/adc/stm32-adc.c
+++ b/drivers/iio/adc/stm32-adc.c
@@ -2465,6 +2465,7 @@ 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 = { };
+ struct device *dma_dev;
int ret;
adc->dma_chan = dma_request_chan(dev, "rx");
@@ -2479,7 +2480,8 @@ static int stm32_adc_dma_request(struct device *dev, struct iio_dev *indio_dev)
return 0;
}
- adc->rx_buf = dma_alloc_coherent(adc->dma_chan->device->dev,
+ dma_dev = dmaengine_get_dma_device(adc->dma_chan);
+ adc->rx_buf = dma_alloc_coherent(dma_dev,
STM32_DMA_BUFFER_SIZE,
&adc->rx_dma_buf, GFP_KERNEL);
if (!adc->rx_buf) {
@@ -2499,7 +2501,7 @@ static int stm32_adc_dma_request(struct device *dev, struct iio_dev *indio_dev)
return 0;
err_free:
- dma_free_coherent(adc->dma_chan->device->dev, STM32_DMA_BUFFER_SIZE,
+ dma_free_coherent(dma_dev, STM32_DMA_BUFFER_SIZE,
adc->rx_buf, adc->rx_dma_buf);
err_release:
dma_release_channel(adc->dma_chan);
@@ -2626,7 +2628,7 @@ err_buffer_cleanup:
err_dma_disable:
if (adc->dma_chan) {
- dma_free_coherent(adc->dma_chan->device->dev,
+ dma_free_coherent(dmaengine_get_dma_device(adc->dma_chan),
STM32_DMA_BUFFER_SIZE,
adc->rx_buf, adc->rx_dma_buf);
dma_release_channel(adc->dma_chan);
@@ -2649,7 +2651,7 @@ static void stm32_adc_remove(struct platform_device *pdev)
pm_runtime_put_noidle(&pdev->dev);
iio_triggered_buffer_cleanup(indio_dev);
if (adc->dma_chan) {
- dma_free_coherent(adc->dma_chan->device->dev,
+ dma_free_coherent(dmaengine_get_dma_device(adc->dma_chan),
STM32_DMA_BUFFER_SIZE,
adc->rx_buf, adc->rx_dma_buf);
dma_release_channel(adc->dma_chan);
@@ -2734,7 +2736,7 @@ static const struct stm32_adc_cfg stm32h7_adc_cfg = {
.set_ovs = stm32h7_adc_set_ovs,
};
-static const unsigned int stm32_adc_min_ts_mp1[] = { 100, 100, 100, 4300, 9800 };
+static const unsigned int stm32_adc_min_ts_mp1[] = { 100, 0, 0, 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 = {
@@ -2756,7 +2758,7 @@ static const struct stm32_adc_cfg stm32mp1_adc_cfg = {
.set_ovs = stm32h7_adc_set_ovs,
};
-static const unsigned int stm32_adc_min_ts_mp13[] = { 100, 0, 0, 4300, 9800 };
+static const unsigned int stm32_adc_min_ts_mp13[] = { 1000, 1000, 1000, 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 = {
diff --git a/drivers/iio/adc/stm32-dfsdm-adc.c b/drivers/iio/adc/stm32-dfsdm-adc.c
index 00f05e167afc..674767da3ac3 100644
--- a/drivers/iio/adc/stm32-dfsdm-adc.c
+++ b/drivers/iio/adc/stm32-dfsdm-adc.c
@@ -1484,7 +1484,7 @@ 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,
+ dma_free_coherent(dmaengine_get_dma_device(adc->dma_chan),
DFSDM_DMA_BUFFER_SIZE,
adc->rx_buf, adc->dma_buf);
dma_release_channel(adc->dma_chan);
@@ -1504,7 +1504,7 @@ static int stm32_dfsdm_dma_request(struct device *dev,
return ret;
}
- adc->rx_buf = dma_alloc_coherent(adc->dma_chan->device->dev,
+ adc->rx_buf = dma_alloc_coherent(dmaengine_get_dma_device(adc->dma_chan),
DFSDM_DMA_BUFFER_SIZE,
&adc->dma_buf, GFP_KERNEL);
if (!adc->rx_buf) {
diff --git a/drivers/iio/adc/ti-adc128s052.c b/drivers/iio/adc/ti-adc128s052.c
index 1899813c1aee..f9a995ff403e 100644
--- a/drivers/iio/adc/ti-adc128s052.c
+++ b/drivers/iio/adc/ti-adc128s052.c
@@ -195,12 +195,14 @@ static int adc128_probe(struct spi_device *spi)
adc = iio_priv(indio_dev);
adc->spi = spi;
- indio_dev->name = spi_get_device_id(spi)->name;
+ config = spi_get_device_match_data(spi);
+ if (!config)
+ return -ENODATA;
+
+ indio_dev->name = spi->modalias;
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;
diff --git a/drivers/iio/adc/ti-ads1015.c b/drivers/iio/adc/ti-ads1015.c
index 0fbfa4e499aa..0f9244107c6d 100644
--- a/drivers/iio/adc/ti-ads1015.c
+++ b/drivers/iio/adc/ti-ads1015.c
@@ -233,6 +233,7 @@ static const struct iio_event_spec ads1015_events[] = {
struct ads1015_channel_data {
unsigned int pga;
unsigned int data_rate;
+ const char *label;
};
struct ads1015_thresh_data {
@@ -586,6 +587,15 @@ static int ads1015_read_raw(struct iio_dev *indio_dev,
}
}
+static int ads1015_read_label(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, char *label)
+{
+ struct ads1015_data *data = iio_priv(indio_dev);
+
+ return sysfs_emit(label, "%s\n",
+ data->channel_data[chan->address].label);
+}
+
static int ads1015_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int val,
int val2, long mask)
@@ -843,6 +853,7 @@ static const struct iio_buffer_setup_ops ads1015_buffer_setup_ops = {
static const struct iio_info ads1015_info = {
.read_avail = ads1015_read_avail,
.read_raw = ads1015_read_raw,
+ .read_label = ads1015_read_label,
.write_raw = ads1015_write_raw,
.read_event_value = ads1015_read_event,
.write_event_value = ads1015_write_event,
@@ -853,6 +864,7 @@ static const struct iio_info ads1015_info = {
static const struct iio_info tla2024_info = {
.read_avail = ads1015_read_avail,
.read_raw = ads1015_read_raw,
+ .read_label = ads1015_read_label,
.write_raw = ads1015_write_raw,
};
@@ -861,7 +873,9 @@ 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;
+ const char *label;
int i = -1;
+ int ret;
device_for_each_child_node_scoped(dev, node) {
u32 pval;
@@ -897,6 +911,15 @@ static int ads1015_client_get_channels_config(struct i2c_client *client)
}
}
+ if (fwnode_property_present(node, "label")) {
+ ret = fwnode_property_read_string(node, "label", &label);
+ if (ret) {
+ dev_err(dev, "invalid label on %pfw\n", node);
+ return ret;
+ }
+ data->channel_data[channel].label = label;
+ }
+
data->channel_data[channel].pga = pga;
data->channel_data[channel].data_rate = data_rate;
@@ -985,6 +1008,13 @@ static int ads1015_probe(struct i2c_client *client)
/* we need to keep this ABI the same as used by hwmon ADS1015 driver */
ads1015_get_channels_config(client);
+ /* Channels without a label property fall back to the datasheet name */
+ for (i = 0; i < ADS1015_CHANNELS; i++) {
+ if (!data->channel_data[i].label)
+ data->channel_data[i].label =
+ indio_dev->channels[i].datasheet_name;
+ }
+
data->regmap = devm_regmap_init_i2c(client, chip->has_comparator ?
&ads1015_regmap_config :
&tla2024_regmap_config);
diff --git a/drivers/iio/adc/ti-ads1018.c b/drivers/iio/adc/ti-ads1018.c
index d9ec4a8d7ce5..8f2d03eee77b 100644
--- a/drivers/iio/adc/ti-ads1018.c
+++ b/drivers/iio/adc/ti-ads1018.c
@@ -624,12 +624,16 @@ static int ads1018_trigger_setup(struct iio_dev *indio_dev)
static int ads1018_spi_probe(struct spi_device *spi)
{
- const struct ads1018_chip_info *info = spi_get_device_match_data(spi);
+ const struct ads1018_chip_info *info;
struct device *dev = &spi->dev;
struct iio_dev *indio_dev;
struct ads1018 *ads1018;
int ret;
+ info = spi_get_device_match_data(spi);
+ if (!info)
+ return -ENODATA;
+
indio_dev = devm_iio_device_alloc(dev, sizeof(*ads1018));
if (!indio_dev)
return -ENOMEM;
diff --git a/drivers/iio/adc/ti-ads1100.c b/drivers/iio/adc/ti-ads1100.c
index 9fe8d54cce83..9957c4d813fb 100644
--- a/drivers/iio/adc/ti-ads1100.c
+++ b/drivers/iio/adc/ti-ads1100.c
@@ -5,7 +5,7 @@
* Copyright (c) 2023, Topic Embedded Products
*
* Datasheet: https://www.ti.com/lit/gpn/ads1100
- * IIO driver for ADS1100 and ADS1000 ADC 16-bit I2C
+ * IIO driver for ADS1100 and similar single channel ADC 16-bit I2C
*/
#include <linux/bitfield.h>
@@ -15,10 +15,12 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
+#include <linux/iopoll.h>
#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
+#include <linux/time.h>
#include <linux/units.h>
#include <linux/iio/iio.h>
@@ -39,17 +41,44 @@
#define ADS1100_SINGLESHOT ADS1100_CFG_SC
#define ADS1100_SLEEP_DELAY_MS 2000
+#define ADS1110_INTERNAL_REF_mV 2048
static const int ads1100_data_rate[] = { 128, 32, 16, 8 };
+static const int ads1110_data_rate[] = { 240, 60, 30, 15 };
static const int ads1100_data_rate_bits[] = { 12, 14, 15, 16 };
+/* Timeout based on the minimum sample rate of 8 SPS (7500ms) */
+#define ADS1100_MAX_DRDY_TIMEOUT_US (7500 * USEC_PER_MSEC)
+
+struct ads1100_config {
+ const char *name;
+ const int *available_data_rate_Hz;
+ const int data_rate_count;
+ bool has_internal_vref_only;
+};
+
+static const struct ads1100_config ads1100_config = {
+ .name = "ads1100",
+ .available_data_rate_Hz = ads1100_data_rate,
+ .data_rate_count = ARRAY_SIZE(ads1100_data_rate),
+ .has_internal_vref_only = false,
+};
+
+static const struct ads1100_config ads1110_config = {
+ .name = "ads1110",
+ .available_data_rate_Hz = ads1110_data_rate,
+ .data_rate_count = ARRAY_SIZE(ads1110_data_rate),
+ .has_internal_vref_only = true,
+};
+
struct ads1100_data {
struct i2c_client *client;
struct regulator *reg_vdd;
struct mutex lock;
int scale_avail[2 * 4]; /* 4 gain settings */
+ const struct ads1100_config *chip_info;
u8 config;
- bool supports_data_rate; /* Only the ADS1100 can select the rate */
+ bool supports_data_rate;
};
static const struct iio_chan_spec ads1100_channel = {
@@ -85,6 +114,20 @@ static int ads1100_set_config_bits(struct ads1100_data *data, u8 mask, u8 value)
return 0;
};
+static int ads1100_get_vref_millivolts(struct ads1100_data *data)
+{
+ int voltage_uV;
+
+ if (data->chip_info->has_internal_vref_only)
+ return ADS1110_INTERNAL_REF_mV;
+
+ voltage_uV = regulator_get_voltage(data->reg_vdd);
+ if (voltage_uV < 0)
+ return voltage_uV;
+
+ return voltage_uV / (MICRO / MILLI);
+}
+
static int ads1100_data_bits(struct ads1100_data *data)
{
return ads1100_data_rate_bits[FIELD_GET(ADS1100_DR_MASK, data->config)];
@@ -123,10 +166,86 @@ static int ads1100_get_adc_result(struct ads1100_data *data, int chan, int *val)
return 0;
}
+static int ads1100_conversion_busy(struct ads1100_data *data)
+{
+ u8 buffer[3];
+ int ret;
+
+ ret = i2c_master_recv(data->client, (char *)&buffer, sizeof(buffer));
+ if (ret < 0) {
+ dev_err(&data->client->dev, "I2C read fail: %d\n", ret);
+ return ret;
+ }
+
+ return FIELD_GET(ADS1100_CFG_ST_BSY, buffer[2]);
+}
+
+static int ads1100_wait_single_conversion(struct ads1100_data *data)
+{
+ int data_rate_index = FIELD_GET(ADS1100_DR_MASK, data->config);
+ int data_rate_Hz = data->chip_info->available_data_rate_Hz[data_rate_index];
+ unsigned long poll_us = DIV_ROUND_CLOSEST(USEC_PER_SEC, data_rate_Hz) / 4;
+ int busy;
+ int ret;
+
+ ret = readx_poll_timeout(ads1100_conversion_busy, data,
+ busy, busy <= 0,
+ poll_us, ADS1100_MAX_DRDY_TIMEOUT_US);
+ if (busy < 0)
+ return busy;
+
+ return ret;
+}
+
+static int ads1100_start_single_conversion(struct ads1100_data *data)
+{
+ u8 config = data->config | ADS1100_CFG_SC;
+ int ret;
+
+ ret = i2c_master_send(data->client, &config, sizeof(config));
+ if (ret < 0) {
+ dev_err(&data->client->dev, "I2C write fail: %d\n", ret);
+ return ret;
+ }
+ /* Need to wait because of change from continuous to single mode */
+ ret = ads1100_wait_single_conversion(data);
+ if (ret)
+ return ret;
+
+ config |= ADS1100_CFG_ST_BSY;
+
+ ret = i2c_master_send(data->client, &config, sizeof(config));
+ if (ret < 0) {
+ dev_err(&data->client->dev, "I2C write fail: %d\n", ret);
+ return ret;
+ }
+
+ /* No need to cache it, it's status bit */
+ data->config = config & ~ADS1100_CFG_ST_BSY;
+
+ return 0;
+}
+
+static int ads1100_poll_data_ready(struct ads1100_data *data)
+{
+ int ret;
+
+ ret = ads1100_start_single_conversion(data);
+ if (ret)
+ return ret;
+
+ ret = ads1100_wait_single_conversion(data);
+ if (ret)
+ return ret;
+
+ return ads1100_set_config_bits(data, ADS1100_CFG_SC, ADS1100_CONTINUOUS);
+}
+
static int ads1100_set_scale(struct ads1100_data *data, int val, int val2)
{
int microvolts;
int gain;
+ int ret;
/* With Vdd between 2.7 and 5V, the scale is always below 1 */
if (val)
@@ -135,7 +254,12 @@ static int ads1100_set_scale(struct ads1100_data *data, int val, int val2)
if (!val2)
return -EINVAL;
- microvolts = regulator_get_voltage(data->reg_vdd);
+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(&data->client->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ microvolts = ads1100_get_vref_millivolts(data) * (MICRO / MILLI);
/*
* val2 is in 'micro' units, n = val2 / 1000000
* result must be millivolts, d = microvolts / 1000
@@ -149,32 +273,40 @@ static int ads1100_set_scale(struct ads1100_data *data, int val, int val2)
ads1100_set_config_bits(data, ADS1100_PGA_MASK, ffs(gain) - 1);
- return 0;
+ return ads1100_poll_data_ready(data);
}
static int ads1100_set_data_rate(struct ads1100_data *data, int chan, int rate)
{
unsigned int i;
unsigned int size;
+ int ret;
- size = data->supports_data_rate ? ARRAY_SIZE(ads1100_data_rate) : 1;
+ size = data->supports_data_rate ? data->chip_info->data_rate_count : 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));
+ if (data->chip_info->available_data_rate_Hz[i] == rate)
+ break;
}
- return -EINVAL;
-}
+ if (i == size)
+ return -EINVAL;
-static int ads1100_get_vdd_millivolts(struct ads1100_data *data)
-{
- return regulator_get_voltage(data->reg_vdd) / (MICRO / MILLI);
+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(&data->client->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ ret = ads1100_set_config_bits(data, ADS1100_DR_MASK,
+ FIELD_PREP(ADS1100_DR_MASK, i));
+ if (ret)
+ return ret;
+
+ return ads1100_poll_data_ready(data);
}
static void ads1100_calc_scale_avail(struct ads1100_data *data)
{
- int millivolts = ads1100_get_vdd_millivolts(data);
+ int millivolts = ads1100_get_vref_millivolts(data);
unsigned int i;
for (i = 0; i < ARRAY_SIZE(data->scale_avail) / 2; i++) {
@@ -196,9 +328,9 @@ static int ads1100_read_avail(struct iio_dev *indio_dev,
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
*type = IIO_VAL_INT;
- *vals = ads1100_data_rate;
+ *vals = data->chip_info->available_data_rate_Hz;
if (data->supports_data_rate)
- *length = ARRAY_SIZE(ads1100_data_rate);
+ *length = data->chip_info->data_rate_count;
else
*length = 1;
return IIO_AVAIL_LIST;
@@ -218,6 +350,7 @@ static int ads1100_read_raw(struct iio_dev *indio_dev,
{
int ret;
struct ads1100_data *data = iio_priv(indio_dev);
+ int data_rate_index;
guard(mutex)(&data->lock);
switch (mask) {
@@ -233,12 +366,12 @@ static int ads1100_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
/* full-scale is the supply voltage in millivolts */
- *val = ads1100_get_vdd_millivolts(data);
+ *val = ads1100_get_vref_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)];
+ data_rate_index = FIELD_GET(ADS1100_DR_MASK, data->config);
+ *val = data->chip_info->available_data_rate_Hz[data_rate_index];
return IIO_VAL_INT;
default:
return -EINVAL;
@@ -318,7 +451,12 @@ static int ads1100_probe(struct i2c_client *client)
data->client = client;
mutex_init(&data->lock);
- indio_dev->name = "ads1100";
+ data->chip_info = i2c_get_match_data(client);
+ if (!data->chip_info)
+ return dev_err_probe(dev, -ENODATA,
+ "Can't get device data from firmware\n");
+
+ indio_dev->name = data->chip_info->name;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = &ads1100_channel;
indio_dev->num_channels = 1;
@@ -400,16 +538,18 @@ static DEFINE_RUNTIME_DEV_PM_OPS(ads1100_pm_ops,
NULL);
static const struct i2c_device_id ads1100_id[] = {
- { .name = "ads1100" },
- { .name = "ads1000" },
+ { .name = "ads1000", .driver_data = (kernel_ulong_t)&ads1100_config },
+ { .name = "ads1100", .driver_data = (kernel_ulong_t)&ads1100_config },
+ { .name = "ads1110", .driver_data = (kernel_ulong_t)&ads1110_config },
{ }
};
MODULE_DEVICE_TABLE(i2c, ads1100_id);
static const struct of_device_id ads1100_of_match[] = {
- {.compatible = "ti,ads1100" },
- {.compatible = "ti,ads1000" },
+ { .compatible = "ti,ads1000", .data = &ads1100_config },
+ { .compatible = "ti,ads1100", .data = &ads1100_config },
+ { .compatible = "ti,ads1110", .data = &ads1110_config },
{ }
};
diff --git a/drivers/iio/adc/ti-ads112c04.c b/drivers/iio/adc/ti-ads112c04.c
new file mode 100644
index 000000000000..acf7209db761
--- /dev/null
+++ b/drivers/iio/adc/ti-ads112c04.c
@@ -0,0 +1,524 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Texas Instruments ADS112C04 16-bit I2C ADC driver
+ *
+ * Copyright (c) 2026 Kyle Hsieh <kylehsieh1995@gmail.com>
+ *
+ * Datasheet: https://www.ti.com/lit/ds/symlink/ads112c04.pdf
+ * Based on TI Reference Code and standard Linux IIO framework.
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <linux/iio/iio.h>
+
+#define ADS112C04_CMD_RESET 0x06
+#define ADS112C04_CMD_START_SYNC 0x08
+#define ADS112C04_CMD_RDATA 0x10
+#define ADS112C04_CMD_RREG(reg) (0x20 | ((reg) << 2))
+#define ADS112C04_CMD_WREG(reg) (0x40 | ((reg) << 2))
+
+#define ADS112C04_REG_CONFIG0 0x00
+#define ADS112C04_CONF0_MUX GENMASK(7, 4)
+#define ADS112C04_CONF0_MUX_AIN0_AIN1 0
+#define ADS112C04_CONF0_MUX_AIN_SINGLE_BASE 8
+#define ADS112C04_CONF0_GAIN GENMASK(3, 1)
+#define ADS112C04_CONF0_GAIN_X1 0
+#define ADS112C04_CONF0_PGA_BYPASS BIT(0)
+
+#define ADS112C04_REG_CONFIG1 0x01
+#define ADS112C04_CONF1_DR GENMASK(7, 5)
+#define ADS112C04_CONF1_DR_20SPS 0
+#define ADS112C04_CONF1_MODE BIT(4)
+#define ADS112C04_CONF1_MODE_NORMAL 0
+#define ADS112C04_CONF1_CM BIT(3)
+#define ADS112C04_CONF1_CM_SINGLE_SHOT 0
+#define ADS112C04_CONF1_VREF GENMASK(2, 1)
+#define ADS112C04_CONF1_VREF_INTERNAL 0
+#define ADS112C04_CONF1_VREF_EXTERNAL 1
+#define ADS112C04_CONF1_VREF_AVDD 2
+#define ADS112C04_CONF1_TS BIT(0)
+#define ADS112C04_CONF1_TS_DISABLED 0
+
+#define ADS112C04_REG_CONFIG2 0x02
+#define ADS112C04_CONF2_DRDY BIT(7)
+
+#define ADS112C04_INT_REF_mV 2048
+
+#define ADS112C04_MAX_CHANNELS 12
+
+#define ADS112C04_RESOLUTION_BITS 16
+
+enum {
+ ADS112C04_VREF_SOURCE_INTERNAL,
+ ADS112C04_VREF_SOURCE_EXTERNAL,
+ ADS112C04_VREF_SOURCE_AVDD,
+};
+
+static const char * const ads112c04_vref_names[] = {
+ [ADS112C04_VREF_SOURCE_INTERNAL] = "internal",
+ [ADS112C04_VREF_SOURCE_EXTERNAL] = "external",
+ [ADS112C04_VREF_SOURCE_AVDD] = "avdd",
+};
+
+static const u8 ads112c04_vref_reg_val[] = {
+ [ADS112C04_VREF_SOURCE_INTERNAL] = ADS112C04_CONF1_VREF_INTERNAL,
+ [ADS112C04_VREF_SOURCE_EXTERNAL] = ADS112C04_CONF1_VREF_EXTERNAL,
+ [ADS112C04_VREF_SOURCE_AVDD] = ADS112C04_CONF1_VREF_AVDD,
+};
+
+/* Indexed by [AINP][AINN], -1 means the combination is not available. */
+static const s8 ads112c04_diff_mux[4][4] = {
+ { -1, 0, 1, 2 },
+ { 3, -1, 4, 5 },
+ { -1, -1, -1, 6 },
+ { -1, -1, 7, -1 },
+};
+
+struct ads112c04_state {
+ struct i2c_client *client;
+ /* Protects concurrent ADC reads and device configuration */
+ struct mutex lock;
+ struct completion completion;
+ u32 avdd_mV;
+ u32 ext_ref_mV;
+ u8 *vref_source; /* one entry per channel, indexed by scan order */
+ u8 config0;
+ u8 config1;
+};
+
+static int ads112c04_write_cmd(struct i2c_client *client, u8 cmd)
+{
+ return i2c_smbus_write_byte(client, cmd);
+}
+
+static int ads112c04_read_reg(struct i2c_client *client, u8 reg, u8 *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, ADS112C04_CMD_RREG(reg));
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+
+ return 0;
+}
+
+static int ads112c04_write_reg(struct i2c_client *client, u8 reg, u8 val)
+{
+ return i2c_smbus_write_byte_data(client, ADS112C04_CMD_WREG(reg), val);
+}
+
+static int ads112c04_wait_for_data(struct ads112c04_state *st)
+{
+ int ret, err;
+ u8 val;
+
+ if (st->client->irq > 0) {
+ /* Timeout is 100ms (slowest data rate is 20 SPS) */
+ if (!wait_for_completion_timeout(&st->completion, msecs_to_jiffies(100)))
+ return -ETIMEDOUT;
+
+ return 0;
+ }
+
+ ret = read_poll_timeout(ads112c04_read_reg, err,
+ err < 0 || (val & ADS112C04_CONF2_DRDY),
+ 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC, false,
+ st->client, ADS112C04_REG_CONFIG2, &val);
+ if (err < 0)
+ return err;
+
+ return ret;
+}
+
+static int ads112c04_read_data(struct ads112c04_state *st, int *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_word_swapped(st->client, ADS112C04_CMD_RDATA);
+ if (ret < 0)
+ return ret;
+
+ *val = sign_extend32(ret, ADS112C04_RESOLUTION_BITS - 1);
+
+ return 0;
+}
+
+static int ads112c04_get_adc_result(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val)
+{
+ struct ads112c04_state *st = iio_priv(indio_dev);
+ unsigned int idx = chan - indio_dev->channels;
+ u8 new_config0, new_config1;
+ int ret;
+
+ new_config0 = st->config0;
+ FIELD_MODIFY(ADS112C04_CONF0_MUX, &new_config0, chan->address);
+
+ if (st->config0 != new_config0) {
+ ret = ads112c04_write_reg(st->client, ADS112C04_REG_CONFIG0,
+ new_config0);
+ if (ret < 0)
+ return ret;
+
+ st->config0 = new_config0;
+ }
+
+ new_config1 = st->config1;
+ FIELD_MODIFY(ADS112C04_CONF1_VREF, &new_config1,
+ ads112c04_vref_reg_val[st->vref_source[idx]]);
+
+ if (st->config1 != new_config1) {
+ ret = ads112c04_write_reg(st->client, ADS112C04_REG_CONFIG1,
+ new_config1);
+ if (ret < 0)
+ return ret;
+
+ st->config1 = new_config1;
+ }
+
+ reinit_completion(&st->completion);
+
+ ret = ads112c04_write_cmd(st->client, ADS112C04_CMD_START_SYNC);
+ if (ret < 0)
+ return ret;
+
+ ret = ads112c04_wait_for_data(st);
+ if (ret < 0)
+ return ret;
+
+ return ads112c04_read_data(st, val);
+}
+
+static int ads112c04_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct ads112c04_state *st = iio_priv(indio_dev);
+ unsigned int idx = chan - indio_dev->channels;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&st->lock);
+ ret = ads112c04_get_adc_result(indio_dev, chan, val);
+ mutex_unlock(&st->lock);
+
+ if (ret < 0)
+ return ret;
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ switch (st->vref_source[idx]) {
+ case ADS112C04_VREF_SOURCE_EXTERNAL:
+ *val = st->ext_ref_mV;
+ break;
+ case ADS112C04_VREF_SOURCE_AVDD:
+ *val = st->avdd_mV;
+ break;
+ default:
+ *val = ADS112C04_INT_REF_mV;
+ break;
+ }
+ *val2 = ADS112C04_RESOLUTION_BITS - 1;
+ return IIO_VAL_FRACTIONAL_LOG2;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static irqreturn_t ads112c04_irq_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct ads112c04_state *st = iio_priv(indio_dev);
+
+ complete(&st->completion);
+
+ return IRQ_HANDLED;
+}
+
+static const struct iio_info ads112c04_info = {
+ .read_raw = ads112c04_read_raw,
+};
+
+static int ads112c04_parse_vref_source(struct fwnode_handle *child)
+{
+ if (!fwnode_property_present(child, "reference-sources"))
+ return ADS112C04_VREF_SOURCE_INTERNAL;
+
+ return fwnode_property_match_property_string(child, "reference-sources",
+ ads112c04_vref_names,
+ ARRAY_SIZE(ads112c04_vref_names));
+}
+
+static int ads112c04_parse_channels(struct iio_dev *indio_dev,
+ bool *need_avdd_ref, bool *need_ext_ref)
+{
+ struct device *dev = indio_dev->dev.parent;
+ struct ads112c04_state *st = iio_priv(indio_dev);
+ struct iio_chan_spec *channels;
+ u32 num_channels, pair[2], channel;
+ unsigned int i;
+ int ret;
+
+ num_channels = device_get_named_child_node_count(dev, "channel");
+ if (!num_channels)
+ return dev_err_probe(dev, -EINVAL, "no channel subnodes found\n");
+
+ if (num_channels > ADS112C04_MAX_CHANNELS)
+ return dev_err_probe(dev, -EINVAL,
+ "num of channel nodes exceeds %d\n",
+ ADS112C04_MAX_CHANNELS);
+
+ channels = devm_kcalloc(dev, num_channels, sizeof(*channels), GFP_KERNEL);
+ if (!channels)
+ return -ENOMEM;
+
+ st->vref_source = devm_kcalloc(dev, num_channels,
+ sizeof(*st->vref_source), GFP_KERNEL);
+ if (!st->vref_source)
+ return -ENOMEM;
+
+ i = 0;
+ device_for_each_named_child_node_scoped(dev, child, "channel") {
+ const char *s_chan = "single-channel", *d_chan = "diff-channels";
+ struct iio_chan_spec *spec = &channels[i];
+
+ if (fwnode_property_present(child, "excitation-channels"))
+ return dev_err_probe(dev, -EOPNOTSUPP,
+ "excitation-channels is not supported yet\n");
+
+ ret = ads112c04_parse_vref_source(child);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "invalid reference-sources value\n");
+
+ st->vref_source[i] = ret;
+
+ *need_ext_ref |= st->vref_source[i] == ADS112C04_VREF_SOURCE_EXTERNAL;
+ *need_avdd_ref |= st->vref_source[i] == ADS112C04_VREF_SOURCE_AVDD;
+
+ /*
+ * REVISIT: when ti,refp-refn-resistor-ohms is implemented, a
+ * channel using an external resistor reference is effectively
+ * a resistance measurement and should use IIO_RESISTANCE.
+ */
+ spec->type = IIO_VOLTAGE;
+ spec->indexed = 1;
+ spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE);
+
+ if (fwnode_property_present(child, s_chan)) {
+ ret = fwnode_property_read_u32(child, s_chan, &channel);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read %s property\n", s_chan);
+
+ if (channel > 3)
+ return dev_err_probe(dev, -EINVAL, "%s must be 0-3\n", s_chan);
+
+ spec->channel = channel;
+ spec->address = ADS112C04_CONF0_MUX_AIN_SINGLE_BASE + channel;
+ } else if (fwnode_property_present(child, d_chan)) {
+ ret = fwnode_property_read_u32_array(child, d_chan, pair, ARRAY_SIZE(pair));
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read %s property\n", d_chan);
+
+ if (pair[0] > 3 || pair[1] > 3)
+ return dev_err_probe(dev, -EINVAL, "%s must be 0-3\n", d_chan);
+
+ spec->channel = pair[0];
+ spec->channel2 = pair[1];
+ spec->differential = 1;
+
+ if (ads112c04_diff_mux[pair[0]][pair[1]] < 0)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid %s combination\n", d_chan);
+
+ spec->address = ads112c04_diff_mux[pair[0]][pair[1]];
+ } else {
+ return dev_err_probe(dev, -EINVAL,
+ "channel node must have %s or %s\n", s_chan, d_chan);
+ }
+
+ i++;
+ }
+
+ indio_dev->channels = channels;
+ indio_dev->num_channels = i;
+
+ return 0;
+}
+
+static int ads112c04_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct iio_dev *indio_dev;
+ struct ads112c04_state *st;
+ struct reset_control *reset;
+ bool need_avdd_ref = false, need_ext_ref = false;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->client = client;
+
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ init_completion(&st->completion);
+
+ indio_dev->name = "ads112c04";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &ads112c04_info;
+
+ /* Forward compatibility checks for unimplemented DT properties */
+ if (device_property_present(dev, "refn-supply") ||
+ device_property_present(dev, "ti,refp-refn-resistor-ohms"))
+ return dev_err_probe(dev, -EOPNOTSUPP,
+ "refn-supply and external resistors are not supported yet\n");
+
+ ret = ads112c04_parse_channels(indio_dev, &need_avdd_ref, &need_ext_ref);
+ if (ret)
+ return ret;
+
+ 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");
+
+ st->avdd_mV = ret / (MICRO / MILLI);
+ } else {
+ ret = devm_regulator_get_enable(dev, "avdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
+ }
+
+ ret = devm_regulator_get_enable(dev, "dvdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get dvdd 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");
+
+ st->ext_ref_mV = ret / (MICRO / MILLI);
+ }
+
+ if (need_ext_ref && !st->ext_ref_mV)
+ return dev_err_probe(dev, -EINVAL,
+ "external reference measurements require refp-supply\n");
+
+ /* Datasheet: POR releases ~500us after supplies are stable */
+ fsleep(500);
+
+ reset = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(reset))
+ return dev_err_probe(dev, PTR_ERR(reset), "failed to get reset\n");
+
+ if (reset) {
+ /* Datasheet: tw(RSL), the RESET low pulse, is 250ns minimum */
+ fsleep(1);
+
+ ret = reset_control_deassert(reset);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to deassert reset\n");
+ } else {
+ ret = ads112c04_write_cmd(client, ADS112C04_CMD_RESET);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Datasheet: td(RSSTA) is 100ns minimum after the RESET rising edge */
+ fsleep(1);
+
+ /*
+ * Initialize CONFIG0 with all fields explicit: gain of 1 with the PGA
+ * bypassed, which allows full-scale single-ended measurements. The MUX
+ * field is updated per channel before each conversion.
+ */
+ st->config0 = FIELD_PREP(ADS112C04_CONF0_MUX,
+ ADS112C04_CONF0_MUX_AIN0_AIN1) |
+ FIELD_PREP(ADS112C04_CONF0_GAIN,
+ ADS112C04_CONF0_GAIN_X1) |
+ ADS112C04_CONF0_PGA_BYPASS;
+
+ ret = ads112c04_write_reg(client, ADS112C04_REG_CONFIG0, st->config0);
+ if (ret)
+ return ret;
+
+ st->config1 = FIELD_PREP(ADS112C04_CONF1_DR,
+ ADS112C04_CONF1_DR_20SPS) |
+ FIELD_PREP(ADS112C04_CONF1_MODE,
+ ADS112C04_CONF1_MODE_NORMAL) |
+ FIELD_PREP(ADS112C04_CONF1_CM,
+ ADS112C04_CONF1_CM_SINGLE_SHOT) |
+ FIELD_PREP(ADS112C04_CONF1_VREF,
+ ADS112C04_CONF1_VREF_INTERNAL) |
+ FIELD_PREP(ADS112C04_CONF1_TS,
+ ADS112C04_CONF1_TS_DISABLED);
+
+ ret = ads112c04_write_reg(client, ADS112C04_REG_CONFIG1, st->config1);
+ if (ret)
+ return ret;
+
+ if (client->irq > 0) {
+ ret = devm_request_irq(dev, client->irq, ads112c04_irq_handler, 0,
+ indio_dev->name, indio_dev);
+ if (ret)
+ return ret;
+ }
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct i2c_device_id ads112c04_id[] = {
+ { .name = "ads112c04" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ads112c04_id);
+
+static const struct of_device_id ads112c04_of_match[] = {
+ { .compatible = "ti,ads112c04" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ads112c04_of_match);
+
+static struct i2c_driver ads112c04_driver = {
+ .driver = {
+ .name = "ads112c04",
+ .of_match_table = ads112c04_of_match,
+ },
+ .probe = ads112c04_probe,
+ .id_table = ads112c04_id,
+};
+module_i2c_driver(ads112c04_driver);
+
+MODULE_AUTHOR("Kyle Hsieh <kylehsieh1995@gmail.com>");
+MODULE_DESCRIPTION("Texas Instruments ADS112C04 ADC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index 60eab4852ba2..3c877126b0be 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -9,7 +9,10 @@
*/
#include <linux/bitfield.h>
+#include <linux/bitmap.h>
#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
#include <linux/crc8.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
@@ -17,8 +20,10 @@
#include <linux/i2c.h>
#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
+#include <linux/interrupt.h>
#include <linux/math64.h>
#include <linux/minmax.h>
#include <linux/module.h>
@@ -72,7 +77,14 @@
#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_BOCS_DISABLED 0
+#define ADS112C14_DEVICE_CFG_BOCS_200_nA 1
+#define ADS112C14_DEVICE_CFG_BOCS_1_uA 2
+#define ADS112C14_DEVICE_CFG_BOCS_10_uA 3
+
#define ADS112C14_DEVICE_CFG_CLK_SEL BIT(3)
+#define ADS112C14_DEVICE_CFG_CLK_SEL_INTERNAL 0
+#define ADS112C14_DEVICE_CFG_CLK_SEL_EXTERNAL 1
#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
@@ -82,6 +94,14 @@
#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_DATA_RATE_CFG_FLTR_OSR_16 0
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_32 1
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_128 2
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_256 3
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_512 4
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024 5
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS 6
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS 7
#define ADS112C14_REG_MUX_CFG 0x07
#define ADS112C14_MUX_CFG_AINP GENMASK(7, 4)
@@ -117,9 +137,15 @@
#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_GPIO_CFG_GPIO_CFG_DISABLED 0
+#define ADS112C14_GPIO_CFG_GPIO_CFG_INPUT 1
+#define ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_PUSH_PULL 2
+#define ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_OPEN_DRAIN 3
#define ADS112C14_REG_GPIO_DATA_OUTPUT 0x0C
#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC BIT(7)
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC_DAT_OUT 0
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC_DRDY 1
#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)
@@ -163,6 +189,63 @@ static const u32 ads112c14_pga_gains_x10[] = {
200, 320, 500, 640, 1000, 1280, 2000, 2560, /* 8 - 15 */
};
+#define ADS112C14_INTERNAL_CLK_Hz (4096 * HZ_PER_KHZ)
+
+/* Index corresponds to first 2 ADS112C14_DATA_RATE_CFG_FLTR_OSR values. */
+static const int ads112c14_sinc4_osr_available[] = {
+ 16, 32
+};
+
+/* Index corresponds to next 4 ADS112C14_DATA_RATE_CFG_FLTR_OSR values. */
+static const int ads112c14_sinc4_sinc1_osr_available[] = {
+ 128, 256, 512, 1024
+};
+
+/* Index corresponds to ADS112C14_DEVICE_CFG_SPEED_MODE value. */
+static const int ads112c14_sinc4_sinc1_pf1_20sps_osr_available[] = {
+ 1600, 12800, 25600, 51200
+};
+
+/* Index corresponds to ADS112C14_DEVICE_CFG_SPEED_MODE value. */
+static const int ads112c14_sinc4_sinc1_pf1_25sps_osr_available[] = {
+ 1280, 10240, 20480, 40960
+};
+
+/* Index corresponds to ADS112C14_DEVICE_CFG_SPEED_MODE value. */
+static const int ads112c14_fmod_div[] = {
+ 128, 16, 8, 4
+};
+
+#define ADS112C14_DELAY_MAX FIELD_MAX(ADS112C14_DATA_RATE_CFG_DELAY)
+
+/* Table 7-6 latency in t_MOD for OSR [16, 32, 128, 256, 512, 1024]. */
+static const int ads112c14_sinc_latency_tmod[][ARRAY_SIZE(ads112c14_fmod_div)] = {
+ { 80, 88, 88, 104 },
+ { 144, 152, 152, 168 },
+ { 240, 248, 248, 264 },
+ { 368, 376, 376, 392 },
+ { 624, 632, 632, 648 },
+ { 1136, 1144, 1144, 1160 },
+};
+
+/* Table 7-7 latency in t_MOD for output data rates [25SPS, 20SPS]. */
+static const int ads112c14_fir_latency_tmod[][ARRAY_SIZE(ads112c14_fmod_div)] = {
+ { 1416, 10384, 20624, 41120 },
+ { 1736, 12944, 25744, 51360 },
+};
+
+enum ads112c14_filter_type {
+ ADS112C14_FILTER_TYPE_SINC4,
+ ADS112C14_FILTER_TYPE_SINC4_SINC1,
+ ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1,
+};
+
+static const char * const ads112c14_filter_type_names[] = {
+ [ADS112C14_FILTER_TYPE_SINC4] = "sinc4",
+ [ADS112C14_FILTER_TYPE_SINC4_SINC1] = "sinc4+sinc1",
+ [ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1] = "sinc4+sinc1+pf1",
+};
+
#define ADS112C14_I2C_CRC8_POLYNOMIAL 0x07
DECLARE_CRC8_TABLE(ads112c14_crc8_table);
@@ -184,6 +267,8 @@ enum {
ADS112C14_SYS_MON_CHANNEL_SHORT,
};
+static const struct iio_chan_spec_ext_info ads112c14_ext_info[];
+
static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
{
.type = IIO_TEMP,
@@ -192,7 +277,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
.address = 2,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
| BIT(IIO_CHAN_INFO_SCALE)
- | BIT(IIO_CHAN_INFO_OFFSET),
+ | BIT(IIO_CHAN_INFO_OFFSET)
+ | BIT(IIO_CHAN_INFO_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .ext_info = ads112c14_ext_info,
},
{
.type = IIO_VOLTAGE,
@@ -200,7 +290,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
.channel = ADS112C14_SYS_MON_CHANNEL_EXT_REF,
.address = 3,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
- | BIT(IIO_CHAN_INFO_SCALE),
+ | 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_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .ext_info = ads112c14_ext_info,
},
{
.type = IIO_VOLTAGE,
@@ -208,7 +303,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
.channel = ADS112C14_SYS_MON_CHANNEL_AVDD,
.address = 4,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
- | BIT(IIO_CHAN_INFO_SCALE),
+ | 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_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .ext_info = ads112c14_ext_info,
},
{
.type = IIO_VOLTAGE,
@@ -216,7 +316,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
.channel = ADS112C14_SYS_MON_CHANNEL_DVDD,
.address = 5,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
- | BIT(IIO_CHAN_INFO_SCALE),
+ | 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_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .ext_info = ads112c14_ext_info,
},
{
.type = IIO_VOLTAGE,
@@ -226,8 +331,13 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
.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),
+ | 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_SCALE)
+ | BIT(IIO_CHAN_INFO_SAMP_FREQ)
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .ext_info = ads112c14_ext_info,
},
};
@@ -241,16 +351,28 @@ struct ads112c14_measurement {
u8 idac2_mux;
u8 iadc_count;
u8 gain_val;
+ u8 burnout;
bool global_chop;
bool bipolar;
int scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2];
};
+struct ads112c14_channel_state {
+ u8 speed_mode;
+ u8 filter_osr;
+ u8 delay;
+};
+
struct ads112c14_data {
const struct ads112c14_chip_info *chip_info;
struct regmap *regmap;
+ struct iio_trigger *drdy_trig;
/* Synchronizes access to register value fields. */
struct mutex lock;
+ long fclk_Hz;
+ int drdy_irq;
+ struct completion drdy_completion;
+ bool continuous_mode;
bool i2c_crc_enabled;
u32 avdd_uV;
u32 ext_ref_uV;
@@ -258,13 +380,36 @@ struct ads112c14_data {
bool refn_is_gnd;
u32 ext_ref_ohms;
struct ads112c14_measurement *measurements;
+ struct ads112c14_channel_state *channel_states;
u32 num_measurements;
u8 sys_mon_chan_short_gain_val;
int sys_mon_chan_short_scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2];
+ int sinc4_sample_rate_available[ARRAY_SIZE(ads112c14_sinc4_osr_available)][ARRAY_SIZE(ads112c14_fmod_div)][2];
+ int sinc4_sinc1_sample_rate_available[ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available)][ARRAY_SIZE(ads112c14_fmod_div)][2];
+ int sinc4_sinc1_pf1_sample_rate_available[2][2];
+ int sinc_settling_time_range_available[ARRAY_SIZE(ads112c14_sinc_latency_tmod)][ARRAY_SIZE(ads112c14_fmod_div)][3][2];
+ int fir_settling_time_range_available[ARRAY_SIZE(ads112c14_fir_latency_tmod)][ARRAY_SIZE(ads112c14_fmod_div)][3][2];
IIO_DECLARE_BUFFER_WITH_TS(__be32, scan, ADS112C14_MAX_MEASUREMENT_CHANNELS +
ARRAY_SIZE(ads112c14_sys_mon_channels));
};
+static irqreturn_t ads112c14_drdy_irq_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct ads112c14_data *data = iio_priv(indio_dev);
+
+ if (READ_ONCE(data->continuous_mode))
+ iio_trigger_poll(data->drdy_trig);
+ else
+ complete(&data->drdy_completion);
+
+ return IRQ_HANDLED;
+}
+
+static const struct iio_trigger_ops ads112c14_trigger_ops = {
+ .validate_device = iio_trigger_validate_own_device,
+};
+
static bool ads112c14_writeable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
@@ -429,13 +574,250 @@ static const struct regmap_config ads112c14_regmap_config = {
.cache_type = REGCACHE_MAPLE,
};
+static int ads112c14_get_osr(struct ads112c14_channel_state *channel_state)
+{
+ u8 i;
+
+ switch (channel_state->filter_osr) {
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32:
+ i = channel_state->filter_osr;
+ return ads112c14_sinc4_osr_available[i];
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
+ i = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128;
+ return ads112c14_sinc4_sinc1_osr_available[i];
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS:
+ i = channel_state->speed_mode;
+ return ads112c14_sinc4_sinc1_pf1_25sps_osr_available[i];
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS:
+ i = channel_state->speed_mode;
+ return ads112c14_sinc4_sinc1_pf1_20sps_osr_available[i];
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ads112c14_get_fmod_Hz(struct ads112c14_data *data,
+ struct ads112c14_channel_state *channel_state)
+{
+ return data->fclk_Hz / ads112c14_fmod_div[channel_state->speed_mode];
+}
+
+static int ads112c14_delay_to_tmod(u8 delay)
+{
+ if (!delay)
+ return 0;
+
+ return BIT(delay - 1);
+}
+
+static int ads112c14_get_latency_tmod(struct ads112c14_channel_state *channel_state)
+{
+ u8 speed_mode = channel_state->speed_mode;
+ u8 filter_osr = channel_state->filter_osr;
+
+ if (speed_mode >= ARRAY_SIZE(ads112c14_fmod_div))
+ return -EINVAL;
+
+ if (filter_osr <= ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024)
+ return ads112c14_sinc_latency_tmod[filter_osr][speed_mode];
+
+ if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS)
+ return ads112c14_fir_latency_tmod[0][speed_mode];
+
+ if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS)
+ return ads112c14_fir_latency_tmod[1][speed_mode];
+
+ return -EINVAL;
+}
+
+static int ads112c14_get_settling_time_us(struct ads112c14_data *data,
+ struct ads112c14_channel_state *channel_state,
+ u8 delay, u32 *settling_time_us)
+{
+ int fmod_Hz, latency_tmod;
+ u64 total_tmod;
+
+ fmod_Hz = ads112c14_get_fmod_Hz(data, channel_state);
+ if (fmod_Hz <= 0)
+ return -EINVAL;
+
+ latency_tmod = ads112c14_get_latency_tmod(channel_state);
+ if (latency_tmod < 0)
+ return latency_tmod;
+
+ total_tmod = latency_tmod + ads112c14_delay_to_tmod(delay);
+ *settling_time_us = div64_u64(total_tmod * USEC_PER_SEC, fmod_Hz);
+
+ return 0;
+}
+
+static int ads112c14_find_delay_for_settling_time_us(struct ads112c14_data *data,
+ struct ads112c14_channel_state *channel_state,
+ s64 settling_time_us, u8 *delay)
+{
+ u64 delay_us, delay_tmod_needed;
+ u32 fixed_latency_us;
+ int ret, fmod_Hz;
+ u8 i;
+
+ ret = ads112c14_get_settling_time_us(data, channel_state, 0, &fixed_latency_us);
+ if (ret)
+ return ret;
+
+ if (settling_time_us <= fixed_latency_us) {
+ *delay = 0;
+ return 0;
+ }
+
+ fmod_Hz = ads112c14_get_fmod_Hz(data, channel_state);
+ if (fmod_Hz <= 0)
+ return -EINVAL;
+
+ delay_us = settling_time_us - fixed_latency_us;
+ delay_tmod_needed = DIV_ROUND_UP_ULL(delay_us * fmod_Hz, USEC_PER_SEC);
+
+ for (i = 1; i < ADS112C14_DELAY_MAX; i++) {
+ if (ads112c14_delay_to_tmod(i) >= delay_tmod_needed)
+ break;
+ }
+
+ *delay = i;
+
+ return 0;
+}
+
+static ssize_t ads112c14_read_settling_time(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ struct ads112c14_channel_state *channel_state;
+ u32 settling_time_us;
+ int vals[2];
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ ret = ads112c14_get_settling_time_us(data, channel_state,
+ channel_state->delay,
+ &settling_time_us);
+ if (ret)
+ return ret;
+
+ iio_val_s64_decompose(settling_time_us, &vals[0], &vals[1]);
+
+ return iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(vals), vals);
+}
+
+static ssize_t ads112c14_write_settling_time(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ struct ads112c14_channel_state *channel_state;
+ s64 settling_time_us;
+ int integer;
+ int fract;
+ u8 delay;
+ int ret;
+
+ ret = iio_str_to_fixpoint(buf, 100000, &integer, &fract);
+ if (ret)
+ return ret;
+
+ settling_time_us = integer * MICRO + fract;
+ if (settling_time_us < 0)
+ return -EINVAL;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ ret = ads112c14_find_delay_for_settling_time_us(data, channel_state,
+ settling_time_us, &delay);
+ if (ret)
+ return ret;
+
+ channel_state->delay = delay;
+
+ return len;
+}
+
+static ssize_t ads112c14_read_settling_time_available(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ struct ads112c14_channel_state *channel_state;
+ u8 filter_osr, speed_mode;
+ const int (*range)[2];
+ size_t len = 0;
+ int i;
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ filter_osr = channel_state->filter_osr;
+ speed_mode = channel_state->speed_mode;
+
+ if (speed_mode >= ARRAY_SIZE(ads112c14_fmod_div))
+ return -EINVAL;
+
+ if (filter_osr <= ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024)
+ range = data->sinc_settling_time_range_available[filter_osr][speed_mode];
+ else if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS)
+ range = data->fir_settling_time_range_available[0][speed_mode];
+ else if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS)
+ range = data->fir_settling_time_range_available[1][speed_mode];
+ else
+ return -EINVAL;
+
+ len += sysfs_emit_at(buf, len, "[");
+ for (i = 0; i < 3; i++) {
+ s64 range_val;
+ s32 int_val, rem;
+
+ range_val = iio_val_s64_compose(range[i][0], range[i][1]);
+ int_val = div_s64_rem(range_val, MICRO, &rem);
+
+ if (i)
+ len += sysfs_emit_at(buf, len, " ");
+
+ len += sysfs_emit_at(buf, len, "%d.%06d", int_val, rem);
+ }
+ len += sysfs_emit_at(buf, len, "]\n");
+
+ return len;
+}
+
static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data,
- const struct iio_chan_spec *chan)
+ const struct iio_chan_spec *chan,
+ bool en_burnout)
{
struct ads112c14_measurement *measurement = &data->measurements[chan->scan_index];
+ struct ads112c14_channel_state *channel_state;
u32 refp_buf_en, refn_buf_en, ref_val, ref_sel;
int ret;
+ channel_state = &data->channel_states[chan->scan_index];
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
+ ADS112C14_DEVICE_CFG_SPEED_MODE,
+ FIELD_PREP(ADS112C14_DEVICE_CFG_SPEED_MODE,
+ channel_state->speed_mode));
+ if (ret)
+ return 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) |
@@ -482,9 +864,16 @@ static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data,
return ret;
ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG,
- ADS112C14_DATA_RATE_CFG_GC_EN,
+ ADS112C14_DATA_RATE_CFG_DELAY |
+ ADS112C14_DATA_RATE_CFG_GC_EN |
+ ADS112C14_DATA_RATE_CFG_FLTR_OSR,
+ FIELD_PREP(ADS112C14_DATA_RATE_CFG_DELAY,
+ channel_state->delay) |
FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN,
- measurement->global_chop));
+ (measurement->global_chop &&
+ !en_burnout) ? 1 : 0) |
+ FIELD_PREP(ADS112C14_DATA_RATE_CFG_FLTR_OSR,
+ channel_state->filter_osr));
if (ret)
return ret;
@@ -527,9 +916,12 @@ static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data,
static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
const struct iio_chan_spec *chan)
{
+ struct ads112c14_channel_state *channel_state;
u32 gain_val;
int ret;
+ channel_state = &data->channel_states[chan->scan_index];
+
/*
* NB: IDAC registers are left as-is in case they are generating current
* needed for the external reference measurement.
@@ -556,6 +948,25 @@ static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
if (ret)
return ret;
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
+ ADS112C14_DEVICE_CFG_SPEED_MODE,
+ FIELD_PREP(ADS112C14_DEVICE_CFG_SPEED_MODE,
+ channel_state->speed_mode));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG,
+ ADS112C14_DATA_RATE_CFG_DELAY |
+ ADS112C14_DATA_RATE_CFG_GC_EN |
+ ADS112C14_DATA_RATE_CFG_FLTR_OSR,
+ FIELD_PREP(ADS112C14_DATA_RATE_CFG_DELAY,
+ channel_state->delay) |
+ FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN, 0) |
+ FIELD_PREP(ADS112C14_DATA_RATE_CFG_FLTR_OSR,
+ channel_state->filter_osr));
+ 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
@@ -581,35 +992,108 @@ static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
return 0;
}
+static int ads112c14_prepare_channel(struct ads112c14_data *data,
+ const struct iio_chan_spec *chan,
+ bool en_burnout)
+{
+ if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE)
+ return ads112c14_prepare_measurement_channel(data, chan, en_burnout);
+
+ return ads112c14_prepare_sys_mon_channel(data, chan);
+}
+
+static int ads112c14_scan_read(struct ads112c14_data *data, u8 *buf)
+{
+ struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap));
+ int ret;
+ u8 len;
+
+ len = BITS_TO_BYTES(data->chip_info->resolution_bits);
+ if (data->i2c_crc_enabled)
+ len += 1;
+
+ ret = i2c_smbus_read_i2c_block_data(client, ADS112C14_CMD_RDATA, len, buf);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int ads112c14_wait_for_conversion_irq(struct ads112c14_data *data,
+ u32 settle_time_us)
+{
+ unsigned long remaining;
+ int ret;
+
+ reinit_completion(&data->drdy_completion);
+
+ ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
+ ADS112C14_CONVERSION_CTRL_START);
+ if (ret)
+ return ret;
+
+ /* Give it 1ms more than calculated settling time. */
+ remaining = wait_for_completion_timeout(&data->drdy_completion,
+ usecs_to_jiffies(settle_time_us +
+ 1 * USEC_PER_MSEC));
+
+ return remaining ? 0 : -ETIMEDOUT;
+}
+
+static int ads112c14_wait_for_conversion_poll(struct ads112c14_data *data,
+ u32 settle_time_us)
+{
+ u32 reg_val;
+ int ret;
+
+ ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
+ ADS112C14_CONVERSION_CTRL_START);
+ if (ret)
+ return ret;
+
+ /* Give it 1ms more than calculated settling time. */
+ return regmap_read_poll_timeout(data->regmap,
+ ADS112C14_REG_STATUS_MSB, reg_val,
+ FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val),
+ 1 * USEC_PER_MSEC,
+ settle_time_us + 1 * USEC_PER_MSEC);
+}
+
static int ads112c14_single_conversion(struct ads112c14_data *data,
const struct iio_chan_spec *chan,
- u8 *buf, bool for_scan)
+ u8 *buf, bool en_burnout, bool for_scan)
{
struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap));
- u32 reg_val;
+ struct ads112c14_channel_state *channel_state;
+ u32 settle_time_us;
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 = ads112c14_prepare_channel(data, chan, en_burnout);
+ if (ret)
+ return ret;
- ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
- ADS112C14_CONVERSION_CTRL_START);
+ channel_state = &data->channel_states[chan->scan_index];
+ ret = ads112c14_get_settling_time_us(data, channel_state,
+ channel_state->delay,
+ &settle_time_us);
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);
+ ret = regmap_test_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG,
+ ADS112C14_DATA_RATE_CFG_GC_EN);
+ if (ret < 0)
+ return ret;
+
+ /* Input chopping doubles the settling time. */
+ if (ret)
+ settle_time_us *= 2;
+
+ if (data->drdy_irq)
+ ret = ads112c14_wait_for_conversion_irq(data, settle_time_us);
+ else
+ ret = ads112c14_wait_for_conversion_poll(data, settle_time_us);
if (ret)
return ret;
@@ -619,23 +1103,21 @@ static int ads112c14_single_conversion(struct ads112c14_data *data,
* 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;
- }
+ if (for_scan)
+ return ads112c14_scan_read(data, buf);
return ads112c14_i2c_read_bytes(client, ADS112C14_CMD_RDATA, buf,
BITS_TO_BYTES(data->chip_info->resolution_bits),
data->i2c_crc_enabled);
}
+static bool ads112c14_using_drdy_trigger(struct iio_dev *indio_dev)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+
+ return data->drdy_trig && indio_dev->trig == data->drdy_trig;
+}
+
static int ads112c14_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
@@ -665,7 +1147,7 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
if (IIO_DEV_ACQUIRE_FAILED(claim))
return -EBUSY;
- ret = ads112c14_single_conversion(data, chan, buf, false);
+ ret = ads112c14_single_conversion(data, chan, buf, false, false);
if (ret)
return ret;
@@ -743,6 +1225,45 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
*/
*val = div_s64((s64)(25 * 405 - 119500) * BIT(fsr_bits), vref_uV);
return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ struct ads112c14_channel_state *channel_state;
+ const int (*available)[2];
+ u8 i, j;
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ switch (channel_state->filter_osr) {
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32:
+ j = channel_state->filter_osr;
+ available = data->sinc4_sample_rate_available[j];
+ i = channel_state->speed_mode;
+ break;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
+ j = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128;
+ available = data->sinc4_sinc1_sample_rate_available[j];
+ i = channel_state->speed_mode;
+ break;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS:
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS:
+ available = data->sinc4_sinc1_pf1_sample_rate_available;
+ i = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *val = available[i][0];
+ *val2 = available[i][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO: {
+ guard(mutex)(&data->lock);
+
+ *val = ads112c14_get_osr(&data->channel_states[chan->scan_index]);
+ return IIO_VAL_INT;
+ }
default:
return -EINVAL;
}
@@ -753,6 +1274,9 @@ static int ads112c14_read_avail(struct iio_dev *indio_dev,
int *type, int *length, long mask)
{
struct ads112c14_data *data = iio_priv(indio_dev);
+ struct ads112c14_channel_state *channel_state;
+
+ channel_state = &data->channel_states[chan->scan_index];
switch (mask) {
case IIO_CHAN_INFO_SCALE:
@@ -778,6 +1302,58 @@ static int ads112c14_read_avail(struct iio_dev *indio_dev,
}
return -EINVAL;
+
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ guard(mutex)(&data->lock);
+
+ switch (channel_state->filter_osr) {
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32:
+ *vals = (const int *)data->sinc4_sample_rate_available[channel_state->filter_osr];
+ *length = 2 * ARRAY_SIZE(data->sinc4_sample_rate_available[0]);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
+ *vals = (const int *)data->sinc4_sinc1_sample_rate_available[channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128];
+ *length = 2 * ARRAY_SIZE(data->sinc4_sinc1_sample_rate_available[0]);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS...ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS:
+ *vals = (const int *)data->sinc4_sinc1_pf1_sample_rate_available;
+ *length = 2 * ARRAY_SIZE(data->sinc4_sinc1_pf1_sample_rate_available);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+ }
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO: {
+ guard(mutex)(&data->lock);
+
+ switch (channel_state->filter_osr) {
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32:
+ *vals = ads112c14_sinc4_osr_available;
+ *length = ARRAY_SIZE(ads112c14_sinc4_osr_available);
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_LIST;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
+ *vals = ads112c14_sinc4_sinc1_osr_available;
+ *length = ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available);
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_LIST;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS:
+ *vals = ads112c14_sinc4_sinc1_pf1_25sps_osr_available;
+ *length = ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_25sps_osr_available);
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_LIST;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS:
+ *vals = ads112c14_sinc4_sinc1_pf1_20sps_osr_available;
+ *length = ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_20sps_osr_available);
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+ }
default:
return -EINVAL;
}
@@ -822,6 +1398,99 @@ static int ads112c14_write_raw(struct iio_dev *indio_dev,
return -EINVAL;
}
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ struct ads112c14_channel_state *channel_state;
+ const int (*available)[2];
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ switch (channel_state->filter_osr) {
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
+ if (channel_state->filter_osr < ADS112C14_DATA_RATE_CFG_FLTR_OSR_128) {
+ u8 idx = channel_state->filter_osr;
+
+ available = data->sinc4_sample_rate_available[idx];
+ } else {
+ u8 idx = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128;
+
+ available = data->sinc4_sinc1_sample_rate_available[idx];
+ }
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_fmod_div); i++) {
+ if (val == available[i][0] && val2 == available[i][1]) {
+ channel_state->speed_mode = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS:
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: {
+ available = data->sinc4_sinc1_pf1_sample_rate_available;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(data->sinc4_sinc1_pf1_sample_rate_available); i++) {
+ if (val == available[i][0] && val2 == available[i][1]) {
+ channel_state->filter_osr = i + ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+ }
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO: {
+ struct ads112c14_channel_state *channel_state;
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ switch (channel_state->filter_osr) {
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32:
+ for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_osr_available); i++) {
+ if (val == ads112c14_sinc4_osr_available[i]) {
+ channel_state->filter_osr = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
+ for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available); i++) {
+ if (val == ads112c14_sinc4_sinc1_osr_available[i]) {
+ channel_state->filter_osr = i + ADS112C14_DATA_RATE_CFG_FLTR_OSR_128;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS:
+ for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_25sps_osr_available); i++) {
+ if (val == ads112c14_sinc4_sinc1_pf1_25sps_osr_available[i]) {
+ channel_state->speed_mode = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS:
+ for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_20sps_osr_available); i++) {
+ if (val == ads112c14_sinc4_sinc1_pf1_20sps_osr_available[i]) {
+ channel_state->speed_mode = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
+ }
default:
return -EINVAL;
}
@@ -839,6 +1508,19 @@ static int ads112c14_write_raw_get_fmt(struct iio_dev *indio_dev,
}
}
+static int ads112c14_update_scan_mode(struct iio_dev *indio_dev,
+ const unsigned long *scan_mask)
+{
+ /* Only continuous mode is limited to a single channel. */
+ if (!ads112c14_using_drdy_trigger(indio_dev))
+ return 0;
+
+ if (!iio_validate_scan_mask_onehot(indio_dev, scan_mask))
+ return -EINVAL;
+
+ return 0;
+}
+
static int ads112c14_debugfs_reg_access(struct iio_dev *indio_dev,
unsigned int reg,
unsigned int writeval,
@@ -893,6 +1575,19 @@ static int ads112c14_read_label(struct iio_dev *indio_dev,
return sysfs_emit(label, "%s\n", label_source);
}
+static const struct iio_chan_spec *
+ads112c14_first_active_channel(struct iio_dev *indio_dev)
+{
+ unsigned int scan_mask_len = iio_get_masklength(indio_dev);
+ unsigned int i;
+
+ i = find_first_bit(indio_dev->active_scan_mask, scan_mask_len);
+ if (i == scan_mask_len)
+ return NULL;
+
+ return &indio_dev->channels[i];
+}
+
static irqreturn_t ads112c14_trigger_handler(int irq, void *private)
{
struct iio_poll_func *pf = private;
@@ -902,12 +1597,32 @@ static irqreturn_t ads112c14_trigger_handler(int irq, void *private)
u32 i;
int ret;
+ if (ads112c14_using_drdy_trigger(indio_dev)) {
+ const struct iio_chan_spec *chan;
+
+ chan = ads112c14_first_active_channel(indio_dev);
+ if (!chan)
+ goto out;
+
+ ret = ads112c14_scan_read(data, (u8 *)&data->scan[0]);
+ 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);
+ goto out;
+ }
+
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);
+ false, true);
if (ret) {
dev_err_once(indio_dev->dev.parent,
"failed to read channel %d: %pe; additional errors will be suppressed\n",
@@ -929,10 +1644,258 @@ static const struct iio_info ads112c14_info = {
.read_avail = ads112c14_read_avail,
.write_raw = ads112c14_write_raw,
.write_raw_get_fmt = ads112c14_write_raw_get_fmt,
+ .update_scan_mode = ads112c14_update_scan_mode,
.debugfs_reg_access = ads112c14_debugfs_reg_access,
.read_label = ads112c14_read_label,
};
+static int ads112c14_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ const struct iio_chan_spec *chan;
+ int ret;
+
+ if (!ads112c14_using_drdy_trigger(indio_dev))
+ return 0;
+
+ chan = ads112c14_first_active_channel(indio_dev);
+ if (!chan)
+ return -EINVAL;
+
+ guard(mutex)(&data->lock);
+
+ ret = ads112c14_prepare_channel(data, chan, false);
+ if (ret)
+ return ret;
+
+ 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_CONTINUOUS));
+ if (ret)
+ return ret;
+
+ WRITE_ONCE(data->continuous_mode, true);
+
+ ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
+ ADS112C14_CONVERSION_CTRL_START);
+ if (ret) {
+ WRITE_ONCE(data->continuous_mode, false);
+ 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));
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ads112c14_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (!ads112c14_using_drdy_trigger(indio_dev))
+ return 0;
+
+ guard(mutex)(&data->lock);
+
+ WRITE_ONCE(data->continuous_mode, false);
+
+ ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
+ ADS112C14_CONVERSION_CTRL_STOP);
+ if (ret)
+ return ret;
+
+ return 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));
+}
+
+static const struct iio_buffer_setup_ops ads112c14_buffer_setup_ops = {
+ .postenable = ads112c14_buffer_postenable,
+ .predisable = ads112c14_buffer_predisable,
+};
+
+static ssize_t ads112c14_read_burnout_raw(struct iio_dev *indio_dev,
+ uintptr_t private,
+ struct iio_chan_spec const *chan,
+ char *buf)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ struct ads112c14_measurement *measurement;
+ int ret, ret2, val;
+ u8 raw_buf[3];
+
+ if (chan->channel >= ADS112C14_SYS_MON_CHANNEL_BASE)
+ return -EINVAL;
+
+ measurement = &data->measurements[chan->scan_index];
+ if (!measurement->burnout)
+ return -EINVAL;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
+ ADS112C14_DEVICE_CFG_BOCS,
+ FIELD_PREP(ADS112C14_DEVICE_CFG_BOCS,
+ measurement->burnout));
+ if (ret)
+ return ret;
+
+ ret = ads112c14_single_conversion(data, chan, raw_buf, true, false);
+
+ /*
+ * Important to always turn off burnout current even if the conversion
+ * fails so that it does not affect subsequent measurements.
+ */
+ ret2 = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
+ ADS112C14_DEVICE_CFG_BOCS,
+ FIELD_PREP(ADS112C14_DEVICE_CFG_BOCS,
+ ADS112C14_DEVICE_CFG_BOCS_DISABLED));
+ if (ret < 0)
+ return ret;
+ if (ret2)
+ return ret2;
+
+ switch (data->chip_info->resolution_bits) {
+ case 16:
+ val = get_unaligned_be16(raw_buf);
+ break;
+ case 24:
+ val = get_unaligned_be24(raw_buf);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (measurement->bipolar)
+ val = sign_extend32(val, data->chip_info->resolution_bits - 1);
+
+ return sysfs_emit(buf, "%d\n", val);
+}
+
+static int ads112c14_get_filter_type_from_state(struct ads112c14_channel_state *channel_state)
+{
+ switch (channel_state->filter_osr) {
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32:
+ return ADS112C14_FILTER_TYPE_SINC4;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024:
+ return ADS112C14_FILTER_TYPE_SINC4_SINC1;
+ case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS...ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS:
+ return ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ads112c14_set_filter_type(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ unsigned int val)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ struct ads112c14_channel_state *channel_state;
+ int ret;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ ret = ads112c14_get_filter_type_from_state(channel_state);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * channel_state->filter_osr affects multiple attributes, so don't modify
+ * it if the filter type is already set to the requested value.
+ */
+ if (ret == val)
+ return 0;
+
+ /* Otherwise, pick an arbitrary default for each type. */
+ switch (val) {
+ case ADS112C14_FILTER_TYPE_SINC4:
+ channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_16;
+ break;
+ case ADS112C14_FILTER_TYPE_SINC4_SINC1:
+ channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_128;
+ break;
+ case ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1:
+ channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ads112c14_get_filter_type(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ struct ads112c14_channel_state *channel_state;
+
+ guard(mutex)(&data->lock);
+
+ channel_state = &data->channel_states[chan->scan_index];
+
+ return ads112c14_get_filter_type_from_state(channel_state);
+}
+
+static const struct iio_enum ads112c14_filter_type_enum = {
+ .items = ads112c14_filter_type_names,
+ .num_items = ARRAY_SIZE(ads112c14_filter_type_names),
+ .set = ads112c14_set_filter_type,
+ .get = ads112c14_get_filter_type,
+};
+
+static const struct iio_chan_spec_ext_info ads112c14_ext_info[] = {
+ {
+ .name = "settlingtime",
+ .read = ads112c14_read_settling_time,
+ .write = ads112c14_write_settling_time,
+ .shared = IIO_SEPARATE,
+ },
+ {
+ .name = "settlingtime_available",
+ .read = ads112c14_read_settling_time_available,
+ .shared = IIO_SEPARATE,
+ },
+ IIO_ENUM("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
+ IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
+ { }
+};
+
+static const struct iio_chan_spec_ext_info ads112c14_ext_info_burnout[] = {
+ {
+ .name = "burnoutraw",
+ .read = ads112c14_read_burnout_raw,
+ },
+ {
+ .name = "settlingtime",
+ .read = ads112c14_read_settling_time,
+ .write = ads112c14_write_settling_time,
+ .shared = IIO_SEPARATE,
+ },
+ {
+ .name = "settlingtime_available",
+ .read = ads112c14_read_settling_time_available,
+ .shared = IIO_SEPARATE,
+ },
+ IIO_ENUM("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
+ IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum),
+ { }
+};
+
static int ads112c14_populate_idac_mag(u32 current_nA, u8 *idac_mag)
{
u32 current_uA = current_nA / (NANO / MICRO);
@@ -955,7 +1918,7 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
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];
+ u32 num_child_nodes, num_data_chans, i, pair[2];
int ret;
*need_avdd_ref = false;
@@ -968,8 +1931,15 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
if (!data->measurements)
return -ENOMEM;
- channels = devm_kcalloc(dev, num_child_nodes +
- ARRAY_SIZE(ads112c14_sys_mon_channels) + 1,
+ num_data_chans = num_child_nodes + ARRAY_SIZE(ads112c14_sys_mon_channels);
+
+ data->channel_states = devm_kcalloc(dev, num_data_chans,
+ sizeof(*data->channel_states),
+ GFP_KERNEL);
+ if (!data->channel_states)
+ return -ENOMEM;
+
+ channels = devm_kcalloc(dev, num_data_chans + 1,
sizeof(*channels), GFP_KERNEL);
if (!channels)
return -ENOMEM;
@@ -978,10 +1948,13 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
device_for_each_named_child_node_scoped(dev, child, "channel") {
struct ads112c14_measurement *measurement = &data->measurements[i];
struct iio_chan_spec *spec = &channels[i];
+ const char *propname;
spec->indexed = 1;
spec->scan_index = i;
+ spec->ext_info = ads112c14_ext_info;
measurement->gain_val = 1;
+ data->channel_states[i].filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_16;
if (fwnode_property_present(child, "label")) {
ret = fwnode_property_read_string(child, "label", &measurement->label);
@@ -1101,6 +2074,35 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
measurement->global_chop = fwnode_property_read_bool(child,
"input-chopping");
+ propname = "burn-out-current-nanoamp";
+ if (fwnode_property_present(child, propname)) {
+ u32 burnout_nA;
+
+ ret = fwnode_property_read_u32(child, propname, &burnout_nA);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read %s property\n",
+ propname);
+
+ switch (burnout_nA) {
+ case 200:
+ measurement->burnout = ADS112C14_DEVICE_CFG_BOCS_200_nA;
+ break;
+ case 1000:
+ measurement->burnout = ADS112C14_DEVICE_CFG_BOCS_1_uA;
+ break;
+ case 10000:
+ measurement->burnout = ADS112C14_DEVICE_CFG_BOCS_10_uA;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "invalid %s value\n", propname);
+ }
+
+ if (measurement->burnout != ADS112C14_DEVICE_CFG_BOCS_DISABLED)
+ spec->ext_info = ads112c14_ext_info_burnout;
+ }
+
if (fwnode_property_present(child, "reference-sources")) {
ret = fwnode_property_match_property_string(child,
"reference-sources", ads112c14_vref_source_names,
@@ -1117,8 +2119,13 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
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);
+ spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO);
+ spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO);
/*
* If reference source is resistor rather than voltage supply,
@@ -1152,6 +2159,10 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev,
for (u32 j = 0; j < ARRAY_SIZE(ads112c14_sys_mon_channels); j++) {
struct iio_chan_spec *spec = &channels[i];
+ struct ads112c14_channel_state *channel_state;
+
+ channel_state = &data->channel_states[i];
+ channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_16;
/* Update the template that was already copied with dynamic values. */
spec->scan_index = i;
@@ -1190,6 +2201,94 @@ static void ads112c14_populate_scale_available(int (*scale_avail)[2],
}
}
+static void ads112c14_populate_odr_tables(struct ads112c14_data *data)
+{
+ int *available;
+ u32 osr, fmod_Hz;
+ u64 odr_uHz;
+ u32 rem;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_osr_available); i++) {
+ osr = ads112c14_sinc4_osr_available[i];
+
+ for (unsigned int j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) {
+ fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j];
+ odr_uHz = div_u64((u64)fmod_Hz * MICRO, osr);
+ available = data->sinc4_sample_rate_available[i][j];
+ available[0] = div_u64_rem(odr_uHz, MICRO, &rem);
+ available[1] = rem;
+ }
+ }
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available); i++) {
+ osr = ads112c14_sinc4_sinc1_osr_available[i];
+
+ for (unsigned int j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) {
+ fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j];
+ odr_uHz = div_u64((u64)fmod_Hz * MICRO, osr);
+ available = data->sinc4_sinc1_sample_rate_available[i][j];
+ available[0] = div_u64_rem(odr_uHz, MICRO, &rem);
+ available[1] = rem;
+ }
+ }
+
+ odr_uHz = div_u64((u64)25 * data->fclk_Hz * MICRO, ADS112C14_INTERNAL_CLK_Hz);
+ available = data->sinc4_sinc1_pf1_sample_rate_available[0];
+ available[0] = div_u64_rem(odr_uHz, MICRO, &rem);
+ available[1] = rem;
+
+ odr_uHz = div_u64((u64)20 * data->fclk_Hz * MICRO, ADS112C14_INTERNAL_CLK_Hz);
+ available = data->sinc4_sinc1_pf1_sample_rate_available[1];
+ available[0] = div_u64_rem(odr_uHz, MICRO, &rem);
+ available[1] = rem;
+}
+
+static void ads112c14_populate_settling_range_tables(struct ads112c14_data *data)
+{
+ s32 (*avail)[2];
+ u32 i, j;
+
+ for (i = 0; i < ARRAY_SIZE(ads112c14_sinc_latency_tmod); i++) {
+ for (j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) {
+ u64 fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j];
+ u64 start_tmod = ads112c14_sinc_latency_tmod[i][j];
+ u64 step_tmod = ads112c14_delay_to_tmod(1);
+ u64 stop_tmod = start_tmod + ads112c14_delay_to_tmod(ADS112C14_DELAY_MAX);
+ s64 start_us, step_us, stop_us;
+
+ start_us = DIV_ROUND_CLOSEST_ULL(start_tmod * USEC_PER_SEC, fmod_Hz);
+ step_us = DIV_ROUND_CLOSEST_ULL(step_tmod * USEC_PER_SEC, fmod_Hz);
+ stop_us = DIV_ROUND_CLOSEST_ULL(stop_tmod * USEC_PER_SEC, fmod_Hz);
+
+ avail = data->sinc_settling_time_range_available[i][j];
+
+ iio_val_s64_decompose(start_us, &avail[0][0], &avail[0][1]);
+ iio_val_s64_decompose(step_us, &avail[1][0], &avail[1][1]);
+ iio_val_s64_decompose(stop_us, &avail[2][0], &avail[2][1]);
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ads112c14_fir_latency_tmod); i++) {
+ for (j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) {
+ u64 fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j];
+ u64 start_tmod = ads112c14_fir_latency_tmod[i][j];
+ u64 step_tmod = ads112c14_delay_to_tmod(1);
+ u64 stop_tmod = start_tmod + ads112c14_delay_to_tmod(ADS112C14_DELAY_MAX);
+ s64 start_us, step_us, stop_us;
+
+ start_us = DIV_ROUND_CLOSEST_ULL(start_tmod * USEC_PER_SEC, fmod_Hz);
+ step_us = DIV_ROUND_CLOSEST_ULL(step_tmod * USEC_PER_SEC, fmod_Hz);
+ stop_us = DIV_ROUND_CLOSEST_ULL(stop_tmod * USEC_PER_SEC, fmod_Hz);
+
+ avail = data->fir_settling_time_range_available[i][j];
+
+ iio_val_s64_decompose(start_us, &avail[0][0], &avail[0][1]);
+ iio_val_s64_decompose(step_us, &avail[1][0], &avail[1][1]);
+ iio_val_s64_decompose(stop_us, &avail[2][0], &avail[2][1]);
+ }
+ }
+}
+
static void ads112c14_populate_tables(struct ads112c14_data *data)
{
u32 full_scale, fsr_bits;
@@ -1227,6 +2326,8 @@ static void ads112c14_populate_tables(struct ads112c14_data *data)
ads112c14_populate_scale_available(data->sys_mon_chan_short_scale_available,
full_scale, fsr_bits);
+ ads112c14_populate_odr_tables(data);
+ ads112c14_populate_settling_range_tables(data);
}
static int ads112c14_probe(struct i2c_client *client)
@@ -1235,6 +2336,7 @@ static int ads112c14_probe(struct i2c_client *client)
const struct ads112c14_chip_info *info;
struct iio_dev *indio_dev;
struct ads112c14_data *data;
+ struct clk *clk;
bool need_avdd_ref, need_ext_ref;
u32 refp_uV = 0;
u32 refn_uV = 0;
@@ -1327,6 +2429,18 @@ static int ads112c14_probe(struct i2c_client *client)
return dev_err_probe(dev, -EINVAL,
"external reference measurements require either refp-supply or ti,refp-refn-resistor-ohms property\n");
+ clk = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "failed to get clk\n");
+
+ if (clk) {
+ data->fclk_Hz = clk_get_rate(clk);
+ if (!data->fclk_Hz)
+ return dev_err_probe(dev, -EINVAL, "clk rate is 0\n");
+ } else {
+ data->fclk_Hz = ADS112C14_INTERNAL_CLK_Hz;
+ }
+
/* It takes some time for the internal reference to stabilize. */
fsleep(10 * USEC_PER_MSEC);
@@ -1391,6 +2505,71 @@ static int ads112c14_probe(struct i2c_client *client)
if (ret)
return ret;
+ if (fwnode_property_match_string(dev_fwnode(dev), "interrupt-names", "drdy") >= 0) {
+ data->drdy_irq = fwnode_irq_get_byname(dev_fwnode(dev), "drdy");
+ if (data->drdy_irq < 0)
+ return dev_err_probe(dev, data->drdy_irq,
+ "failed to get drdy interrupt\n");
+
+ if (clk)
+ return dev_err_probe(dev, -EINVAL,
+ "cannot use both DRDY and CLK - they share the same pin\n");
+
+ /*
+ * REVISIT: would probably need to implement a pin controller in
+ * order to support open drain option here.
+ */
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_CFG,
+ ADS112C14_GPIO_CFG_GPIO3_CFG,
+ FIELD_PREP(ADS112C14_GPIO_CFG_GPIO3_CFG,
+ ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_PUSH_PULL));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_DATA_OUTPUT,
+ ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC,
+ FIELD_PREP(ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC,
+ ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC_DRDY));
+ if (ret)
+ return ret;
+
+ init_completion(&data->drdy_completion);
+
+ data->drdy_trig = devm_iio_trigger_alloc(dev, "%s-dev%d-drdy",
+ info->name,
+ iio_device_id(indio_dev));
+ if (!data->drdy_trig)
+ return -ENOMEM;
+
+ data->drdy_trig->ops = &ads112c14_trigger_ops;
+ iio_trigger_set_drvdata(data->drdy_trig, indio_dev);
+
+ ret = devm_iio_trigger_register(dev, data->drdy_trig);
+ if (ret)
+ return ret;
+
+ ret = devm_request_irq(dev, data->drdy_irq, ads112c14_drdy_irq_handler,
+ 0, dev_name(dev), indio_dev);
+ if (ret)
+ return ret;
+ }
+
+ if (clk) {
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_CFG,
+ ADS112C14_GPIO_CFG_GPIO3_CFG,
+ FIELD_PREP(ADS112C14_GPIO_CFG_GPIO3_CFG,
+ ADS112C14_GPIO_CFG_GPIO_CFG_INPUT));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
+ ADS112C14_DEVICE_CFG_CLK_SEL,
+ FIELD_PREP(ADS112C14_DEVICE_CFG_CLK_SEL,
+ ADS112C14_DEVICE_CFG_CLK_SEL_EXTERNAL));
+ if (ret)
+ return ret;
+ }
+
ads112c14_populate_tables(data);
indio_dev->name = info->name;
@@ -1399,7 +2578,8 @@ static int ads112c14_probe(struct i2c_client *client)
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
iio_pollfunc_store_time,
- ads112c14_trigger_handler, NULL);
+ ads112c14_trigger_handler,
+ &ads112c14_buffer_setup_ops);
if (ret)
return ret;
diff --git a/drivers/iio/adc/ti-ads131m02.c b/drivers/iio/adc/ti-ads131m02.c
index 2f8f75c8216b..7ecfb27fd7ef 100644
--- a/drivers/iio/adc/ti-ads131m02.c
+++ b/drivers/iio/adc/ti-ads131m02.c
@@ -897,6 +897,8 @@ static int ads131m_probe(struct spi_device *spi)
indio_dev->info = &ads131m_info;
config = spi_get_device_match_data(spi);
+ if (!config)
+ return -ENODATA;
priv->config = config;
indio_dev->name = config->name;
diff --git a/drivers/iio/adc/ti-ads7950.c b/drivers/iio/adc/ti-ads7950.c
index d27c1f2885fd..0d5708e0b831 100644
--- a/drivers/iio/adc/ti-ads7950.c
+++ b/drivers/iio/adc/ti-ads7950.c
@@ -527,8 +527,10 @@ static int ti_ads7950_probe(struct spi_device *spi)
st->spi = spi;
info = spi_get_device_match_data(spi);
+ if (!info)
+ return -ENODATA;
- indio_dev->name = spi_get_device_id(spi)->name;
+ indio_dev->name = spi->modalias;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = info->channels;
indio_dev->num_channels = info->num_channels;
diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c
index 1516dd332f90..e53c839e87ef 100644
--- a/drivers/iio/adc/ti_am335x_adc.c
+++ b/drivers/iio/adc/ti_am335x_adc.c
@@ -548,7 +548,7 @@ static int tiadc_request_dma(struct platform_device *pdev,
}
/* RX buffer */
- dma->buf = dma_alloc_coherent(dma->chan->device->dev, DMA_BUFFER_SIZE,
+ dma->buf = dma_alloc_coherent(dmaengine_get_dma_device(dma->chan), DMA_BUFFER_SIZE,
&dma->addr, GFP_KERNEL);
if (!dma->buf)
goto err;
@@ -688,7 +688,7 @@ static void tiadc_remove(struct platform_device *pdev)
u32 step_en;
if (dma->chan) {
- dma_free_coherent(dma->chan->device->dev, DMA_BUFFER_SIZE,
+ dma_free_coherent(dmaengine_get_dma_device(dma->chan), DMA_BUFFER_SIZE,
dma->buf, dma->addr);
dma_release_channel(dma->chan);
}
diff --git a/drivers/iio/amplifiers/ad8366.c b/drivers/iio/amplifiers/ad8366.c
index e12f8604f59f..9adff120de1a 100644
--- a/drivers/iio/amplifiers/ad8366.c
+++ b/drivers/iio/amplifiers/ad8366.c
@@ -326,6 +326,8 @@ static int ad8366_probe(struct spi_device *spi)
st->spi = spi;
st->info = spi_get_device_match_data(spi);
+ if (!st->info)
+ return -ENODATA;
enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH);
if (IS_ERR(enable_gpio))
diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
index 31ed2e3e7f52..b9f8b5b3aa6b 100644
--- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
+++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
@@ -88,9 +88,6 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue,
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
@@ -257,6 +254,7 @@ static const struct iio_dev_attr *iio_dmaengine_buffer_attrs[] = {
*/
static struct iio_buffer *iio_dmaengine_buffer_alloc(struct dma_chan *chan)
{
+ struct device *dma_dev = dmaengine_get_dma_device(chan);
struct dmaengine_buffer *dmaengine_buffer;
unsigned int width, src_width, dest_width;
struct dma_slave_caps caps;
@@ -284,9 +282,9 @@ static struct iio_buffer *iio_dmaengine_buffer_alloc(struct dma_chan *chan)
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);
+ dmaengine_buffer->max_size = dma_get_max_seg_size(dma_dev);
- iio_dma_buffer_init(&dmaengine_buffer->queue, chan->device->dev,
+ iio_dma_buffer_init(&dmaengine_buffer->queue, dma_dev,
&iio_dmaengine_default_ops);
dmaengine_buffer->queue.buffer.attrs = iio_dmaengine_buffer_attrs;
diff --git a/drivers/iio/chemical/ens160_core.c b/drivers/iio/chemical/ens160_core.c
index 7c1ffda38b99..5e2b15463c99 100644
--- a/drivers/iio/chemical/ens160_core.c
+++ b/drivers/iio/chemical/ens160_core.c
@@ -283,12 +283,7 @@ static int ens160_set_trigger_state(struct iio_trigger *trig, bool state)
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);
+ return regmap_assign_bits(data->regmap, ENS160_REG_CONFIG, int_bits, state);
}
static const struct iio_trigger_ops ens160_trigger_ops = {
diff --git a/drivers/iio/chemical/sps30.c b/drivers/iio/chemical/sps30.c
index 8e15baa31423..b47d08ab4d0f 100644
--- a/drivers/iio/chemical/sps30.c
+++ b/drivers/iio/chemical/sps30.c
@@ -15,6 +15,7 @@
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/kernel.h>
+#include <linux/kstrtox.h>
#include <linux/module.h>
#include "sps30.h"
@@ -193,7 +194,10 @@ static ssize_t start_cleaning_store(struct device *dev,
struct sps30_state *state = iio_priv(indio_dev);
int val, ret;
- if (kstrtoint(buf, 0, &val) || val != 1)
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return ret;
+ if (val != 1)
return -EINVAL;
guard(mutex)(&state->lock);
@@ -230,8 +234,9 @@ static ssize_t cleaning_period_store(struct device *dev, struct device_attribute
struct sps30_state *state = iio_priv(indio_dev);
int val, ret;
- if (kstrtoint(buf, 0, &val))
- return -EINVAL;
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return ret;
if ((val < SPS30_AUTO_CLEANING_PERIOD_MIN) ||
(val > SPS30_AUTO_CLEANING_PERIOD_MAX))
diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c
index b971f8b646be..551b86236abc 100644
--- a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c
+++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c
@@ -311,7 +311,7 @@ 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,
+ .pm = pm_sleep_ptr(&cros_ec_sensors_pm_ops),
},
.probe = cros_ec_sensors_probe,
.id_table = cros_ec_sensors_ids,
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
index 5133755c2ea6..d03e40c91582 100644
--- a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c
+++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c
@@ -862,7 +862,7 @@ int cros_ec_sensors_core_write(struct cros_ec_sensors_core_state *st,
}
EXPORT_SYMBOL_GPL(cros_ec_sensors_core_write);
-static int __maybe_unused cros_ec_sensors_resume(struct device *dev)
+static int 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);
@@ -879,8 +879,7 @@ static int __maybe_unused cros_ec_sensors_resume(struct device *dev)
return ret;
}
-SIMPLE_DEV_PM_OPS(cros_ec_sensors_pm_ops, NULL, cros_ec_sensors_resume);
-EXPORT_SYMBOL_GPL(cros_ec_sensors_pm_ops);
+EXPORT_GPL_SIMPLE_DEV_PM_OPS(cros_ec_sensors_pm_ops, NULL, cros_ec_sensors_resume);
MODULE_DESCRIPTION("ChromeOS EC sensor hub core functions");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
index 3b3c77d8b3e1..e373885e7c7a 100644
--- a/drivers/iio/dac/Kconfig
+++ b/drivers/iio/dac/Kconfig
@@ -16,6 +16,8 @@ config AD3530R
- AD3530/AD3530R (8-channel)
- AD3531/AD3531R (4-channel)
- AD3532/AD3532R (16-channel)
+ - AD3536R (16-channel, 12-bit)
+ - AD5710R/AD5711R (8-channel configurable IDAC/VDAC)
To compile this driver as a module, choose M here: the
module will be called ad3530r.
@@ -137,6 +139,22 @@ config AD5449
To compile this driver as a module, choose M here: the
module will be called ad5449.
+config AD5529R
+ tristate "Analog Devices AD5529R High Voltage DAC driver"
+ depends on SPI_MASTER
+ select REGMAP_SPI
+ help
+ Say yes here to build support for Analog Devices AD5529R 16-Channel,
+ 12-Bit/16-Bit, 40V High Voltage Precision Digital to Analog Converter.
+
+ The device features multiple output voltage ranges from -20V to +20V,
+ built-in 4.096V voltage reference, and digital functions including
+ toggle, dither, and ramp modes. Supports both 12-bit and 16-bit
+ resolution variants.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad5529r.
+
config AD5592R_BASE
tristate
@@ -593,9 +611,13 @@ config MCP47A1
will be called mcp47a1.
config MCP47FEB02
- tristate "MCP47F(E/V)B01/02/04/08/11/12/14/18/21/22/24/28 DAC driver"
+ tristate
+
+config MCP47FEB02_I2C
+ tristate "MCP47F(E/V)B01/02/04/08/11/12/14/18/21/22/24/28 I2C DAC driver"
depends on I2C
select REGMAP_I2C
+ select MCP47FEB02
help
Say yes here if you want to build the driver for the Microchip:
- 8-bit DAC:
@@ -611,7 +633,31 @@ config MCP47FEB02
(DAC) with I2C interface.
To compile this driver as a module, choose M here: the module
- will be called mcp47feb02.
+ will be called mcp47feb02_i2c and you will also get
+ mcp47feb02_core for the core module.
+
+config MCP47FEB02_SPI
+ tristate "MCP48F(E/V)B01/02/04/08/11/12/14/18/21/22/24/28 SPI DAC driver"
+ depends on SPI
+ select REGMAP_SPI
+ select MCP47FEB02
+ help
+ Say yes here if you want to build the driver for the Microchip:
+ - 8-bit DAC:
+ MCP48FEB01, MCP48FEB02, MCP48FEB04, MCP48FEB08,
+ MCP48FVB01, MCP48FVB02, MCP48FVB04, MCP48FVB08
+ - 10-bit DAC:
+ MCP48FEB11, MCP48FEB12, MCP48FEB14, MCP48FEB18,
+ MCP48FVB11, MCP48FVB12, MCP48FVB14, MCP48FVB18
+ - 12-bit DAC:
+ MCP48FEB21, MCP48FEB22, MCP48FEB24, MCP48FEB28,
+ MCP48FVB21, MCP48FVB22, MCP48FVB24, MCP48FVB28
+ having 1 to 8 channels, 8/10/12-bit digital-to-analog converter
+ (DAC) with SPI interface.
+
+ To compile this driver as a module, choose M here: the module
+ will be called mcp47feb02_spi and you will also get
+ mcp47feb02_core for the core module.
config MCP4821
tristate "MCP4801/02/11/12/21/22 DAC driver"
diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile
index 992f8930f95c..2cd0fe23db9e 100644
--- a/drivers/iio/dac/Makefile
+++ b/drivers/iio/dac/Makefile
@@ -18,6 +18,7 @@ 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_AD5529R) += ad5529r.o
obj-$(CONFIG_AD5592R_BASE) += ad5592r-base.o
obj-$(CONFIG_AD5592R) += ad5592r.o
obj-$(CONFIG_AD5593R) += ad5593r.o
@@ -56,7 +57,9 @@ 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_MCP47FEB02) += mcp47feb02-core.o
+obj-$(CONFIG_MCP47FEB02_I2C) += mcp47feb02-i2c.o
+obj-$(CONFIG_MCP47FEB02_SPI) += mcp47feb02-spi.o
obj-$(CONFIG_MCP4821) += mcp4821.o
obj-$(CONFIG_MCP4922) += mcp4922.o
obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o
diff --git a/drivers/iio/dac/ad3530r.c b/drivers/iio/dac/ad3530r.c
index 06b7f280f61b..9e043639801d 100644
--- a/drivers/iio/dac/ad3530r.c
+++ b/drivers/iio/dac/ad3530r.c
@@ -3,8 +3,11 @@
* 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
+ * AD3536R 16-channel, 12-bit Voltage Output DAC Driver
+ * AD5710R 8-channel, 16-bit Configurable IDAC/VDAC Driver
+ * AD5711R 8-channel, 12-bit Configurable IDAC/VDAC Driver
*
- * Copyright 2025 Analog Devices Inc.
+ * Copyright 2025-2026 Analog Devices Inc.
*/
#include <linux/array_size.h>
@@ -27,6 +30,8 @@
#include <linux/types.h>
#include <linux/units.h>
+#include <dt-bindings/iio/addac/adi,ad74413r.h>
+
#define AD3530R_INTERFACE_CONFIG_A 0x00
#define AD3530R_OUTPUT_OPERATING_MODE_0 0x20
#define AD3530R_OUTPUT_OPERATING_MODE_1 0x21
@@ -39,6 +44,8 @@
#define AD3531R_SW_LDAC_TRIG_A 0xDD
#define AD3531R_INPUT_CH 0xE3
+#define AD5710R_CHN_VMODE_EN 0xFF
+
/* AD3532R/AD3532 bank 0 registers (channels 0-7) */
#define AD3532R_INTERFACE_CONFIG_A_0 0x1000
#define AD3532R_OUTPUT_OPERATING_MODE_0 0x1020
@@ -61,14 +68,14 @@
#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 AD5710R_CHN_VMODE_EN_BIT(chan) BIT(chan)
#define AD3530R_SW_RESET (BIT(7) | BIT(0))
#define AD3530R_INTERNAL_VREF_mV 2500
+#define AD5710R_INTERNAL_IREF_mA 50
#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
@@ -99,7 +106,9 @@ struct ad3530r_chip_info {
unsigned int num_channels;
unsigned int num_banks;
unsigned int num_op_mode_regs;
+ unsigned int resolution;
bool internal_ref_support;
+ bool configurable_ch_func;
};
struct ad3530r_state {
@@ -195,6 +204,23 @@ static const struct iio_enum ad3532r_powerdown_mode_enum = {
.set = ad3530r_set_powerdown_mode,
};
+static ssize_t ad5710r_read_powerdown_mode(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct ad3530r_state *st = iio_priv(indio_dev);
+ int ret;
+
+ /* Voltage output pulls to 15kohm_to_gnd, current goes three_state. */
+ ret = regmap_test_bits(st->regmap, AD5710R_CHN_VMODE_EN,
+ AD5710R_CHN_VMODE_EN_BIT(chan->channel));
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", ret ? "15kohm_to_gnd" : "three_state");
+}
+
static ssize_t ad3530r_get_dac_powerdown(struct iio_dev *indio_dev,
uintptr_t private,
const struct iio_chan_spec *chan,
@@ -278,6 +304,61 @@ static ssize_t ad3532r_set_dac_powerdown(struct iio_dev *indio_dev,
return len;
}
+static void ad5710r_get_op_mode_reg(unsigned int channel, unsigned int *reg,
+ unsigned int *mask)
+{
+ unsigned int local_ch, reg_in_bank, ch_in_reg;
+
+ local_ch = channel % AD3530R_CH_PER_BANK;
+ reg_in_bank = local_ch / AD3530R_CH_PER_REG;
+ ch_in_reg = local_ch % AD3530R_CH_PER_REG;
+
+ *reg = AD3530R_OUTPUT_OPERATING_MODE_0 + reg_in_bank;
+ *mask = AD3530R_OP_MODE_CHAN_MSK(ch_in_reg);
+}
+
+static ssize_t ad5710r_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);
+ unsigned int reg, mask;
+ int ret;
+
+ ad5710r_get_op_mode_reg(chan->channel, &reg, &mask);
+
+ ret = regmap_test_bits(st->regmap, reg, field_prep(mask, 1));
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", ret);
+}
+
+static ssize_t ad5710r_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 reg, mask;
+ bool powerdown;
+ int ret;
+
+ ret = kstrtobool(buf, &powerdown);
+ if (ret)
+ return ret;
+
+ ad5710r_get_op_mode_reg(chan->channel, &reg, &mask);
+
+ ret = regmap_update_bits(st->regmap, reg, mask,
+ field_prep(mask, powerdown));
+ if (ret)
+ return ret;
+
+ return len;
+}
+
static int ad3530r_trigger_sw_ldac_reg(unsigned int channel)
{
return AD3530R_SW_LDAC_TRIG_A;
@@ -310,7 +391,7 @@ static int ad3530r_dac_write(struct ad3530r_state *st, unsigned int chan,
int ret;
guard(mutex)(&st->lock);
- st->buf = cpu_to_be16(val);
+ st->buf = cpu_to_be16(val << (16 - st->chip_info->resolution));
ret = regmap_bulk_write(st->regmap, st->chip_info->input_ch_reg(chan),
&st->buf, sizeof(st->buf));
@@ -340,12 +421,15 @@ static int ad3530r_read_raw(struct iio_dev *indio_dev,
if (ret)
return ret;
- *val = FIELD_GET(AD3530R_REG_VAL_MASK, be16_to_cpu(st->buf));
+ *val = be16_to_cpu(st->buf) >> (16 - st->chip_info->resolution);
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
- *val = st->vref_mV;
- *val2 = 16;
+ if (chan->type == IIO_CURRENT)
+ *val = AD5710R_INTERNAL_IREF_mA;
+ else
+ *val = st->vref_mV;
+ *val2 = st->chip_info->resolution;
return IIO_VAL_FRACTIONAL_LOG2;
default:
@@ -361,7 +445,7 @@ static int ad3530r_write_raw(struct iio_dev *indio_dev,
switch (info) {
case IIO_CHAN_INFO_RAW:
- if (val < 0 || val > AD3530R_DAC_MAX_VAL)
+ if (val < 0 || val > GENMASK(st->chip_info->resolution - 1, 0))
return -EINVAL;
return ad3530r_dac_write(st, chan->channel, val);
@@ -420,6 +504,21 @@ static const struct iio_chan_spec_ext_info ad3532r_ext_info[] = {
{ }
};
+static const struct iio_chan_spec_ext_info ad5710r_ext_info[] = {
+ {
+ .name = "powerdown",
+ .shared = IIO_SEPARATE,
+ .read = ad5710r_get_dac_powerdown,
+ .write = ad5710r_set_dac_powerdown,
+ },
+ {
+ .name = "powerdown_mode",
+ .shared = IIO_SEPARATE,
+ .read = ad5710r_read_powerdown_mode,
+ },
+ { }
+};
+
#define AD3530R_CHAN(_chan, _ext_info) \
{ \
.type = IIO_VOLTAGE, \
@@ -468,6 +567,17 @@ static const struct iio_chan_spec ad3532r_channels[] = {
AD3530R_CHAN(15, ad3532r_ext_info),
};
+static const struct iio_chan_spec ad5710r_channels[] = {
+ AD3530R_CHAN(0, ad5710r_ext_info),
+ AD3530R_CHAN(1, ad5710r_ext_info),
+ AD3530R_CHAN(2, ad5710r_ext_info),
+ AD3530R_CHAN(3, ad5710r_ext_info),
+ AD3530R_CHAN(4, ad5710r_ext_info),
+ AD3530R_CHAN(5, ad5710r_ext_info),
+ AD3530R_CHAN(6, ad5710r_ext_info),
+ AD3530R_CHAN(7, ad5710r_ext_info),
+};
+
static const unsigned int ad3530r_if_config[] = {
AD3530R_INTERFACE_CONFIG_A,
};
@@ -523,8 +633,15 @@ static const struct regmap_config ad3532r_regmap_config = {
.max_register = AD3532R_MAX_REG_ADDR,
};
+static const struct regmap_config ad5710r_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .max_register = AD5710R_CHN_VMODE_EN,
+};
+
static const struct ad3530r_chip_info ad3530_chip = {
.name = "ad3530",
+ .resolution = 16,
.channels = ad3530r_channels,
.regmap_config = &ad3530r_regmap_config,
.num_channels = ARRAY_SIZE(ad3530r_channels),
@@ -537,10 +654,12 @@ static const struct ad3530r_chip_info ad3530_chip = {
.num_banks = ARRAY_SIZE(ad3530r_if_config),
.num_op_mode_regs = ARRAY_SIZE(ad3530r_op_mode),
.internal_ref_support = false,
+ .configurable_ch_func = false,
};
static const struct ad3530r_chip_info ad3530r_chip = {
.name = "ad3530r",
+ .resolution = 16,
.channels = ad3530r_channels,
.regmap_config = &ad3530r_regmap_config,
.num_channels = ARRAY_SIZE(ad3530r_channels),
@@ -553,10 +672,12 @@ static const struct ad3530r_chip_info ad3530r_chip = {
.num_banks = ARRAY_SIZE(ad3530r_if_config),
.num_op_mode_regs = ARRAY_SIZE(ad3530r_op_mode),
.internal_ref_support = true,
+ .configurable_ch_func = false,
};
static const struct ad3530r_chip_info ad3531_chip = {
.name = "ad3531",
+ .resolution = 16,
.channels = ad3531r_channels,
.regmap_config = &ad3530r_regmap_config,
.num_channels = ARRAY_SIZE(ad3531r_channels),
@@ -569,10 +690,12 @@ static const struct ad3530r_chip_info ad3531_chip = {
.num_banks = ARRAY_SIZE(ad3530r_if_config),
.num_op_mode_regs = ARRAY_SIZE(ad3531r_op_mode),
.internal_ref_support = false,
+ .configurable_ch_func = false,
};
static const struct ad3530r_chip_info ad3531r_chip = {
.name = "ad3531r",
+ .resolution = 16,
.channels = ad3531r_channels,
.regmap_config = &ad3530r_regmap_config,
.num_channels = ARRAY_SIZE(ad3531r_channels),
@@ -585,10 +708,12 @@ static const struct ad3530r_chip_info ad3531r_chip = {
.num_banks = ARRAY_SIZE(ad3530r_if_config),
.num_op_mode_regs = ARRAY_SIZE(ad3531r_op_mode),
.internal_ref_support = true,
+ .configurable_ch_func = false,
};
static const struct ad3530r_chip_info ad3532_chip = {
.name = "ad3532",
+ .resolution = 16,
.channels = ad3532r_channels,
.regmap_config = &ad3532r_regmap_config,
.num_channels = ARRAY_SIZE(ad3532r_channels),
@@ -601,10 +726,12 @@ static const struct ad3530r_chip_info ad3532_chip = {
.num_banks = ARRAY_SIZE(ad3532r_if_config),
.num_op_mode_regs = ARRAY_SIZE(ad3532r_op_mode),
.internal_ref_support = false,
+ .configurable_ch_func = false,
};
static const struct ad3530r_chip_info ad3532r_chip = {
.name = "ad3532r",
+ .resolution = 16,
.channels = ad3532r_channels,
.regmap_config = &ad3532r_regmap_config,
.num_channels = ARRAY_SIZE(ad3532r_channels),
@@ -617,8 +744,141 @@ static const struct ad3530r_chip_info ad3532r_chip = {
.num_banks = ARRAY_SIZE(ad3532r_if_config),
.num_op_mode_regs = ARRAY_SIZE(ad3532r_op_mode),
.internal_ref_support = true,
+ .configurable_ch_func = false,
};
+static const struct ad3530r_chip_info ad3536r_chip = {
+ .name = "ad3536r",
+ .resolution = 12,
+ .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,
+ .configurable_ch_func = false,
+};
+
+static const struct ad3530r_chip_info ad5710r_chip = {
+ .name = "ad5710r",
+ .resolution = 16,
+ .regmap_config = &ad5710r_regmap_config,
+ .num_channels = ARRAY_SIZE(ad5710r_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,
+ .configurable_ch_func = true,
+};
+
+static const struct ad3530r_chip_info ad5711r_chip = {
+ .name = "ad5711r",
+ .resolution = 12,
+ .regmap_config = &ad5710r_regmap_config,
+ .num_channels = ARRAY_SIZE(ad5710r_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,
+ .configurable_ch_func = true,
+};
+
+static int ad3530r_parse_channel_cfg(struct iio_dev *indio_dev)
+{
+ struct ad3530r_state *st = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(st->regmap);
+ struct iio_chan_spec *channels;
+ unsigned int num_chan, i;
+ int ret;
+
+ num_chan = device_get_child_node_count(dev);
+ if (!num_chan)
+ return dev_err_probe(dev, -ENOENT, "No channels configured\n");
+
+ channels = devm_kcalloc(dev, num_chan, sizeof(*channels), GFP_KERNEL);
+ if (!channels)
+ return -ENOMEM;
+
+ i = 0;
+ device_for_each_child_node_scoped(dev, child) {
+ unsigned int mode_reg, mode_mask, ch_func;
+ enum iio_chan_type chan_type;
+ u32 reg;
+
+ ret = fwnode_property_read_u32(child, "reg", &reg);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to read reg property of %pfwP\n",
+ child);
+
+ if (reg >= st->chip_info->num_channels)
+ return dev_err_probe(dev, -ECHRNG,
+ "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:
+ ret = regmap_set_bits(st->regmap, AD5710R_CHN_VMODE_EN,
+ AD5710R_CHN_VMODE_EN_BIT(reg));
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to set voltage output for %pfwP\n",
+ child);
+
+ 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);
+ }
+
+ channels[i] = ad5710r_channels[reg];
+ channels[i].type = chan_type;
+ i++;
+
+ ad5710r_get_op_mode_reg(reg, &mode_reg, &mode_mask);
+
+ /* Enable the channel in normal operation mode */
+ ret = regmap_update_bits(st->regmap, mode_reg, mode_mask,
+ field_prep(mode_mask, AD3530R_NORMAL_OP));
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to set normal operating mode for %pfwP\n",
+ child);
+ }
+
+ indio_dev->channels = channels;
+ indio_dev->num_channels = num_chan;
+
+ return 0;
+}
+
static int ad3530r_set_reg_bank_bits(const struct ad3530r_state *st,
const unsigned int *regs,
unsigned int num_regs,
@@ -651,8 +911,9 @@ static int ad3530r_write_reg_banks(const struct ad3530r_state *st,
return 0;
}
-static int ad3530r_setup(struct ad3530r_state *st, int external_vref_uV)
+static int ad3530r_setup(struct iio_dev *indio_dev, int external_vref_uV)
{
+ struct ad3530r_state *st = iio_priv(indio_dev);
const struct ad3530r_chip_info *chip_info = st->chip_info;
struct device *dev = regmap_get_device(st->regmap);
struct gpio_desc *reset_gpio;
@@ -702,19 +963,29 @@ static int ad3530r_setup(struct ad3530r_state *st, int external_vref_uV)
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);
+ if (chip_info->configurable_ch_func) {
+ /* Channels and their operating mode are configured from DT */
+ ret = ad3530r_parse_channel_cfg(indio_dev);
+ if (ret)
+ return ret;
+ } else {
+ /* 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, chip_info->op_mode,
+ chip_info->num_op_mode_regs, val);
+ if (ret)
+ return ret;
- 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 < chip_info->num_channels; i++)
+ st->chan[i].powerdown_mode = AD3530R_POWERDOWN_32K;
- for (unsigned int i = 0; i < st->chip_info->num_channels; i++)
- st->chan[i].powerdown_mode = AD3530R_POWERDOWN_32K;
+ indio_dev->channels = chip_info->channels;
+ indio_dev->num_channels = chip_info->num_channels;
+ }
st->ldac_gpio = devm_gpiod_get_optional(dev, "ldac", GPIOD_OUT_LOW);
if (IS_ERR(st->ldac_gpio))
@@ -772,15 +1043,13 @@ static int ad3530r_probe(struct spi_device *spi)
if (!st->chip_info->internal_ref_support && external_vref_uV == 0)
return -ENODEV;
- ret = ad3530r_setup(st, external_vref_uV);
+ ret = ad3530r_setup(indio_dev, 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);
}
@@ -792,6 +1061,9 @@ static const struct spi_device_id ad3530r_id[] = {
{ .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 },
+ { .name = "ad3536r", .driver_data = (kernel_ulong_t)&ad3536r_chip },
+ { .name = "ad5710r", .driver_data = (kernel_ulong_t)&ad5710r_chip },
+ { .name = "ad5711r", .driver_data = (kernel_ulong_t)&ad5711r_chip },
{ }
};
MODULE_DEVICE_TABLE(spi, ad3530r_id);
@@ -803,6 +1075,9 @@ static const struct of_device_id ad3530r_of_match[] = {
{ .compatible = "adi,ad3531r", .data = &ad3531r_chip },
{ .compatible = "adi,ad3532", .data = &ad3532_chip },
{ .compatible = "adi,ad3532r", .data = &ad3532r_chip },
+ { .compatible = "adi,ad3536r", .data = &ad3536r_chip },
+ { .compatible = "adi,ad5710r", .data = &ad5710r_chip },
+ { .compatible = "adi,ad5711r", .data = &ad5711r_chip },
{ }
};
MODULE_DEVICE_TABLE(of, ad3530r_of_match);
diff --git a/drivers/iio/dac/ad3552r.c b/drivers/iio/dac/ad3552r.c
index 16db94fef9d4..039af514adde 100644
--- a/drivers/iio/dac/ad3552r.c
+++ b/drivers/iio/dac/ad3552r.c
@@ -696,6 +696,6 @@ static struct spi_driver ad3552r_driver = {
module_spi_driver(ad3552r_driver);
MODULE_AUTHOR("Mihail Chindris <mihail.chindris@analog.com>");
-MODULE_DESCRIPTION("Analog Device AD3552R DAC");
+MODULE_DESCRIPTION("Analog Devices AD3552R DAC");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("IIO_AD3552R");
diff --git a/drivers/iio/dac/ad5529r.c b/drivers/iio/dac/ad5529r.c
new file mode 100644
index 000000000000..8154b87ff831
--- /dev/null
+++ b/drivers/iio/dac/ad5529r.c
@@ -0,0 +1,546 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AD5529R Digital-to-Analog Converter Driver
+ * 16-Channel, 12/16-Bit, 40V High Voltage Precision DAC
+ *
+ * Copyright 2026 Analog Devices Inc.
+ * Author: Janani Sunil <janani.sunil@analog.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <linux/iio/iio.h>
+
+#define AD5529R_REG_INTERFACE_CONFIG_A 0x00
+#define AD5529R_INTERFACE_CONFIG_A_SW_RESET (BIT(7) | BIT(0))
+#define AD5529R_INTERFACE_CONFIG_A_ADDR_ASCENSION BIT(5)
+#define AD5529R_INTERFACE_CONFIG_A_SDO_ENABLE BIT(4)
+#define AD5529R_REG_DEVICE_CONFIG 0x02
+#define AD5529R_REG_CHIP_GRADE 0x06
+#define AD5529R_REG_SCRATCH_PAD 0x0A
+#define AD5529R_REG_SPI_REVISION 0x0B
+#define AD5529R_REG_VENDOR_H 0x0D
+#define AD5529R_REG_STREAM_MODE 0x0E
+#define AD5529R_REG_INTERFACE_STATUS_A 0x11
+#define AD5529R_REG_MULTI_DAC_CH_SEL 0x14
+#define AD5529R_REG_OUT_RANGE_BASE 0x3C
+#define AD5529R_REG_OUT_RANGE(ch) (AD5529R_REG_OUT_RANGE_BASE + (ch) * 2)
+#define AD5529R_REG_DAC_INPUT_A_BASE 0x148
+#define AD5529R_REG_DAC_INPUT_A(ch) (AD5529R_REG_DAC_INPUT_A_BASE + (ch) * 2)
+#define AD5529R_REG_DAC_DATA_READBACK_BASE 0x16A
+#define AD5529R_REG_TSENS_ALERT_FLAG 0x18C
+#define AD5529R_REG_TSENS_SHTD_FLAG 0x18E
+#define AD5529R_REG_FUNC_BUSY 0x1A0
+#define AD5529R_REG_REF_SEL 0x1A2
+#define AD5529R_REF_SEL_INTERNAL_REF BIT(0)
+#define AD5529R_REG_INIT_CRC_ERR_STAT 0x1A4
+#define AD5529R_REG_MULTI_DAC_HOTPATH_SW_LDAC 0x1A8
+
+#define AD5529R_MAX_REGISTER 0x232
+#define AD5529R_8BIT_REG_MAX 0x13
+#define AD5529R_MAX_CHANNELS 16
+#define AD5529R_SPI_READ_FLAG 0x80
+#define AD5529R_ADDR_SHIFT 12
+
+struct ad5529r_model_data {
+ const char *model_name;
+ unsigned int resolution;
+};
+
+#define AD5529R_DAC_CHANNEL(chan) ((struct iio_chan_spec) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .output = 1, \
+ .channel = (chan), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OFFSET), \
+})
+
+static const char * const ad5529r_supply_names[] = {
+ "vdd-io",
+ "vdd-5v",
+ "vdd-hv0",
+ "vdd-hv1",
+ "vdd-hv2",
+ "vdd-hv3",
+};
+
+static const char * const ad5529r_vss_supply_names[] = {
+ "vss-hv0",
+ "vss-hv1",
+ "vss-hv2",
+ "vss-hv3",
+};
+
+static const struct ad5529r_model_data ad5529r_16bit_model_data = {
+ .model_name = "ad5529r-16",
+ .resolution = 16,
+};
+
+static const struct ad5529r_model_data ad5529r_12bit_model_data = {
+ .model_name = "ad5529r-12",
+ .resolution = 12,
+};
+
+enum ad5529r_output_range {
+ AD5529R_RANGE_0V_5V,
+ AD5529R_RANGE_0V_10V,
+ AD5529R_RANGE_0V_20V,
+ AD5529R_RANGE_0V_40V,
+ AD5529R_RANGE_M5V_5V,
+ AD5529R_RANGE_M10V_10V,
+ AD5529R_RANGE_M15V_15V,
+ AD5529R_RANGE_M20V_20V,
+};
+
+static const s32 ad5529r_output_ranges_mV[8][2] = {
+ [AD5529R_RANGE_0V_5V] = { 0, 5000 },
+ [AD5529R_RANGE_0V_10V] = { 0, 10000 },
+ [AD5529R_RANGE_0V_20V] = { 0, 20000 },
+ [AD5529R_RANGE_0V_40V] = { 0, 40000 },
+ [AD5529R_RANGE_M5V_5V] = { -5000, 5000 },
+ [AD5529R_RANGE_M10V_10V] = { -10000, 10000 },
+ [AD5529R_RANGE_M15V_15V] = { -15000, 15000 },
+ [AD5529R_RANGE_M20V_20V] = { -20000, 20000 },
+};
+
+struct ad5529r_state {
+ const struct ad5529r_model_data *model_data;
+ struct regmap *regmap_8bit;
+ struct regmap *regmap_16bit;
+ struct iio_chan_spec channels[AD5529R_MAX_CHANNELS];
+ unsigned int num_channels;
+ enum ad5529r_output_range output_range_idx[AD5529R_MAX_CHANNELS];
+};
+
+static const struct regmap_range ad5529r_8bit_readable_ranges[] = {
+ regmap_reg_range(AD5529R_REG_INTERFACE_CONFIG_A, AD5529R_REG_CHIP_GRADE),
+ regmap_reg_range(AD5529R_REG_SCRATCH_PAD, AD5529R_REG_VENDOR_H),
+ regmap_reg_range(AD5529R_REG_STREAM_MODE, AD5529R_REG_INTERFACE_STATUS_A),
+};
+
+static const struct regmap_range ad5529r_16bit_readable_ranges[] = {
+ regmap_reg_range(AD5529R_REG_MULTI_DAC_CH_SEL, AD5529R_REG_INIT_CRC_ERR_STAT),
+ regmap_reg_range(AD5529R_REG_MULTI_DAC_HOTPATH_SW_LDAC, AD5529R_MAX_REGISTER),
+};
+
+static const struct regmap_access_table ad5529r_8bit_readable_table = {
+ .yes_ranges = ad5529r_8bit_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(ad5529r_8bit_readable_ranges),
+};
+
+static const struct regmap_access_table ad5529r_16bit_readable_table = {
+ .yes_ranges = ad5529r_16bit_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(ad5529r_16bit_readable_ranges),
+};
+
+static const struct regmap_range ad5529r_8bit_read_only_ranges[] = {
+ regmap_reg_range(AD5529R_REG_DEVICE_CONFIG, AD5529R_REG_CHIP_GRADE),
+ regmap_reg_range(AD5529R_REG_SPI_REVISION, AD5529R_REG_VENDOR_H),
+};
+
+static const struct regmap_range ad5529r_16bit_read_only_ranges[] = {
+ regmap_reg_range(AD5529R_REG_DAC_DATA_READBACK_BASE,
+ AD5529R_REG_DAC_DATA_READBACK_BASE +
+ (AD5529R_MAX_CHANNELS - 1) * 2),
+ regmap_reg_range(AD5529R_REG_TSENS_ALERT_FLAG, AD5529R_REG_TSENS_SHTD_FLAG),
+ regmap_reg_range(AD5529R_REG_FUNC_BUSY, AD5529R_REG_FUNC_BUSY),
+ regmap_reg_range(AD5529R_REG_INIT_CRC_ERR_STAT, AD5529R_REG_INIT_CRC_ERR_STAT),
+};
+
+static const struct regmap_access_table ad5529r_8bit_writeable_table = {
+ .no_ranges = ad5529r_8bit_read_only_ranges,
+ .n_no_ranges = ARRAY_SIZE(ad5529r_8bit_read_only_ranges),
+};
+
+static const struct regmap_access_table ad5529r_16bit_writeable_table = {
+ .no_ranges = ad5529r_16bit_read_only_ranges,
+ .n_no_ranges = ARRAY_SIZE(ad5529r_16bit_read_only_ranges),
+};
+
+static struct regmap *ad5529r_get_regmap(struct ad5529r_state *st,
+ unsigned int reg)
+{
+ if (reg <= AD5529R_8BIT_REG_MAX)
+ return st->regmap_8bit;
+
+ return st->regmap_16bit;
+}
+
+static int ad5529r_reset(struct ad5529r_state *st)
+{
+ struct regmap *map = st->regmap_8bit;
+ struct device *dev = regmap_get_device(map);
+ struct reset_control *rst;
+ int ret;
+
+ rst = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(rst))
+ return PTR_ERR(rst);
+
+ if (rst) {
+ ret = reset_control_assert(rst);
+ if (ret)
+ return ret;
+
+ /* Minimum reset low width (t_reset) is 20 ns per datasheet. */
+ ndelay(20);
+
+ ret = reset_control_deassert(rst);
+ if (ret)
+ return ret;
+ } else {
+ ret = regmap_write(map, AD5529R_REG_INTERFACE_CONFIG_A,
+ AD5529R_INTERFACE_CONFIG_A_SW_RESET);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * Wait 10 ms for digital initialization to complete.
+ * Per datasheet, Interface Status A register NOT_READY_ERR bit is
+ * set if SPI transactions are attempted before digital initialization
+ * completes.
+ */
+ fsleep(10 * USEC_PER_MSEC);
+
+ return regmap_write(map, AD5529R_REG_INTERFACE_CONFIG_A,
+ AD5529R_INTERFACE_CONFIG_A_SDO_ENABLE |
+ AD5529R_INTERFACE_CONFIG_A_ADDR_ASCENSION);
+}
+
+static int ad5529r_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct ad5529r_state *st = iio_priv(indio_dev);
+ enum ad5529r_output_range range_idx;
+ unsigned int reg_addr, reg_val_h;
+ int ret, span_mV;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ /*
+ * Read from DAC_INPUT_A register rather than DAC_DATA_READBACK.
+ * The DAC operates in transparent mode and directly reflects
+ * whatever value is written to the INPUT_A register.
+ */
+ reg_addr = AD5529R_REG_DAC_INPUT_A(chan->channel);
+ ret = regmap_read(st->regmap_16bit, reg_addr, &reg_val_h);
+ if (ret)
+ return ret;
+
+ *val = reg_val_h;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ range_idx = st->output_range_idx[chan->channel];
+
+ /*
+ * The datasheet specifies a 4.096 V external reference,
+ * matching the nominal output voltage of the internal
+ * reference.
+ */
+ span_mV = ad5529r_output_ranges_mV[range_idx][1] -
+ ad5529r_output_ranges_mV[range_idx][0];
+ *val = span_mV;
+ *val2 = st->model_data->resolution;
+
+ return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_CHAN_INFO_OFFSET:
+ range_idx = st->output_range_idx[chan->channel];
+
+ if (ad5529r_output_ranges_mV[range_idx][0] < 0)
+ *val = -BIT(st->model_data->resolution - 1);
+ else
+ *val = 0;
+
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad5529r_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct ad5529r_state *st = iio_priv(indio_dev);
+ unsigned int reg_addr;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (!in_range(val, 0, BIT(st->model_data->resolution)))
+ return -EINVAL;
+
+ reg_addr = AD5529R_REG_DAC_INPUT_A(chan->channel);
+
+ return regmap_write(st->regmap_16bit, reg_addr, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad5529r_find_output_range(const s32 *vals)
+{
+ for (unsigned int i = 0; i < ARRAY_SIZE(ad5529r_output_ranges_mV); i++) {
+ const s32 *range = ad5529r_output_ranges_mV[i];
+
+ if (vals[0] == range[0] * (MICRO / MILLI) &&
+ vals[1] == range[1] * (MICRO / MILLI))
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int ad5529r_parse_channel_ranges(struct device *dev,
+ struct ad5529r_state *st)
+{
+ unsigned long channel_mask = 0;
+
+ device_for_each_child_node_scoped(dev, child) {
+ int ret, range_idx;
+ u32 ch;
+
+ if (st->num_channels == ARRAY_SIZE(st->channels))
+ return dev_err_probe(dev, -ENOSPC, "Too many channels\n");
+
+ ret = fwnode_property_read_u32(child, "reg", &ch);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Missing reg property in channel node\n");
+
+ if (ch >= AD5529R_MAX_CHANNELS)
+ return dev_err_probe(dev, -ECHRNG,
+ "Channel %u exceeds maximum %u\n",
+ ch, AD5529R_MAX_CHANNELS - 1);
+
+ if (__test_and_set_bit(ch, &channel_mask))
+ return dev_err_probe(dev, -EEXIST,
+ "Duplicate channel %u\n", ch);
+
+ if (fwnode_property_present(child, "output-range-microvolt")) {
+ s32 vals[2];
+
+ /*
+ * DT stores cells as raw 32-bit values; signed endpoints are
+ * encoded by dtc in two's-complement and then interpreted
+ * here as s32.
+ */
+ ret = fwnode_property_read_u32_array(child,
+ "output-range-microvolt",
+ (u32 *)vals, ARRAY_SIZE(vals));
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to read range for ch %u\n",
+ ch);
+
+ range_idx = ad5529r_find_output_range(vals);
+ if (range_idx < 0)
+ return dev_err_probe(dev, range_idx,
+ "Invalid range [%d %d] for ch %u\n",
+ vals[0], vals[1], ch);
+ } else {
+ range_idx = AD5529R_RANGE_0V_5V;
+ }
+
+ st->output_range_idx[ch] = range_idx;
+ ret = regmap_write(st->regmap_16bit,
+ AD5529R_REG_OUT_RANGE(ch), range_idx);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to configure range for ch %u\n",
+ ch);
+
+ st->channels[st->num_channels++] = AD5529R_DAC_CHANNEL(ch);
+ }
+
+ return 0;
+}
+
+static int ad5529r_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg,
+ unsigned int writeval,
+ unsigned int *readval)
+{
+ struct ad5529r_state *st = iio_priv(indio_dev);
+
+ if (readval)
+ return regmap_read(ad5529r_get_regmap(st, reg), reg, readval);
+
+ return regmap_write(ad5529r_get_regmap(st, reg), reg, writeval);
+}
+
+static const struct iio_info ad5529r_info = {
+ .read_raw = ad5529r_read_raw,
+ .write_raw = ad5529r_write_raw,
+ .debugfs_reg_access = ad5529r_reg_access,
+};
+
+static int ad5529r_probe(struct spi_device *spi)
+{
+ struct regmap_config regmap_16bit_cfg;
+ struct regmap_config regmap_8bit_cfg;
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ad5529r_state *st;
+ bool external_vref;
+ u32 dev_addr;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ st->model_data = spi_get_device_match_data(spi);
+ if (!st->model_data)
+ return dev_err_probe(dev, -ENODATA,
+ "Failed to identify device variant\n");
+
+ if (device_property_present(dev, "spi-device-addr")) {
+ ret = device_property_read_u32(dev, "spi-device-addr", &dev_addr);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to read spi-device-addr\n");
+
+ if (dev_addr > 3)
+ return dev_err_probe(dev, -EADDRNOTAVAIL,
+ "spi-device-addr %u out of range [0, 3]\n",
+ dev_addr);
+ } else {
+ dev_addr = 0;
+ }
+
+ regmap_8bit_cfg = (struct regmap_config) {
+ .name = "ad5529r-8bit",
+ .reg_bits = 16,
+ .val_bits = 8,
+ .max_register = AD5529R_8BIT_REG_MAX,
+ .read_flag_mask = AD5529R_SPI_READ_FLAG,
+ .rd_table = &ad5529r_8bit_readable_table,
+ .wr_table = &ad5529r_8bit_writeable_table,
+ .reg_base = dev_addr << AD5529R_ADDR_SHIFT,
+ };
+ regmap_16bit_cfg = (struct regmap_config) {
+ .name = "ad5529r-16bit",
+ .reg_bits = 16,
+ .val_bits = 16,
+ .max_register = AD5529R_MAX_REGISTER,
+ .read_flag_mask = AD5529R_SPI_READ_FLAG,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+ .rd_table = &ad5529r_16bit_readable_table,
+ .wr_table = &ad5529r_16bit_writeable_table,
+ .reg_stride = 2,
+ .reg_base = dev_addr << AD5529R_ADDR_SHIFT,
+ };
+
+ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad5529r_supply_names),
+ ad5529r_supply_names);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get and enable regulators\n");
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(ad5529r_vss_supply_names); i++) {
+ ret = devm_regulator_get_enable_optional(dev,
+ ad5529r_vss_supply_names[i]);
+ if (ret && ret != -ENODEV)
+ return dev_err_probe(dev, ret,
+ "Failed to get and enable %s regulator\n",
+ ad5529r_vss_supply_names[i]);
+ }
+
+ ret = devm_regulator_get_enable_optional(dev, "vref");
+ if (ret == -ENODEV)
+ external_vref = false;
+ else if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get and enable vref regulator\n");
+ else
+ external_vref = true;
+
+ /* Wait 10 ms after power-up before the first SPI transaction. */
+ fsleep(10 * USEC_PER_MSEC);
+
+ st->regmap_8bit = devm_regmap_init_spi(spi, &regmap_8bit_cfg);
+ if (IS_ERR(st->regmap_8bit))
+ return dev_err_probe(dev, PTR_ERR(st->regmap_8bit),
+ "Failed to initialize 8-bit regmap\n");
+
+ st->regmap_16bit = devm_regmap_init_spi(spi, &regmap_16bit_cfg);
+ if (IS_ERR(st->regmap_16bit))
+ return dev_err_probe(dev, PTR_ERR(st->regmap_16bit),
+ "Failed to initialize 16-bit regmap\n");
+
+ ret = ad5529r_reset(st);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to reset device\n");
+
+ ret = regmap_assign_bits(st->regmap_16bit, AD5529R_REG_REF_SEL,
+ AD5529R_REF_SEL_INTERNAL_REF,
+ !external_vref);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to configure reference\n");
+
+ ret = ad5529r_parse_channel_ranges(dev, st);
+ if (ret)
+ return ret;
+
+ indio_dev->name = st->model_data->model_name;
+ indio_dev->info = &ad5529r_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = st->channels;
+ indio_dev->num_channels = st->num_channels;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id ad5529r_of_match[] = {
+ { .compatible = "adi,ad5529r-16", .data = &ad5529r_16bit_model_data },
+ { .compatible = "adi,ad5529r-12", .data = &ad5529r_12bit_model_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad5529r_of_match);
+
+static const struct spi_device_id ad5529r_id[] = {
+ {
+ .name = "ad5529r-16",
+ .driver_data = (kernel_ulong_t)&ad5529r_16bit_model_data,
+ },
+ {
+ .name = "ad5529r-12",
+ .driver_data = (kernel_ulong_t)&ad5529r_12bit_model_data,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ad5529r_id);
+
+static struct spi_driver ad5529r_driver = {
+ .driver = {
+ .name = "ad5529r",
+ .of_match_table = ad5529r_of_match,
+ },
+ .probe = ad5529r_probe,
+ .id_table = ad5529r_id,
+};
+module_spi_driver(ad5529r_driver);
+
+MODULE_AUTHOR("Janani Sunil <janani.sunil@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD5529R 12/16-bit DAC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/dac/ad5755.c b/drivers/iio/dac/ad5755.c
index 0828b9909099..730289dd8ef6 100644
--- a/drivers/iio/dac/ad5755.c
+++ b/drivers/iio/dac/ad5755.c
@@ -819,6 +819,9 @@ static int ad5755_probe(struct spi_device *spi)
spi_set_drvdata(spi, indio_dev);
st->chip_info = spi_get_device_match_data(spi);
+ if (!st->chip_info)
+ return -ENODATA;
+
st->spi = spi;
st->pwr_down = 0xf;
diff --git a/drivers/iio/dac/ad5758.c b/drivers/iio/dac/ad5758.c
index 44efa373cd80..1e21ad2a050d 100644
--- a/drivers/iio/dac/ad5758.c
+++ b/drivers/iio/dac/ad5758.c
@@ -9,6 +9,7 @@
#include <linux/bsearch.h>
#include <linux/delay.h>
#include <linux/kernel.h>
+#include <linux/math64.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/spi/spi.h>
@@ -117,7 +118,7 @@ struct ad5758_state {
unsigned int dc_dc_ilim;
unsigned int slew_time;
bool pwr_down;
- __be32 d32[3];
+ __be32 d32[3] __aligned(IIO_DMA_MINALIGN);
};
/*
@@ -540,7 +541,7 @@ static int ad5758_read_raw(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_OFFSET:
min = st->out_range.min;
max = st->out_range.max;
- *val = ((min * (1 << 16)) / (max - min)) / 1000;
+ *val = div_s64((s64)min * (1 << 16), max - min);
return IIO_VAL_INT;
default:
return -EINVAL;
@@ -556,6 +557,9 @@ static int ad5758_write_raw(struct iio_dev *indio_dev,
switch (info) {
case IIO_CHAN_INFO_RAW:
+ if (val < 0 || val > U16_MAX)
+ return -EINVAL;
+
mutex_lock(&st->lock);
ret = ad5758_spi_reg_write(st, AD5758_DAC_INPUT, val);
mutex_unlock(&st->lock);
diff --git a/drivers/iio/dac/ad8460.c b/drivers/iio/dac/ad8460.c
index a2292810e4cf..469294fd1783 100644
--- a/drivers/iio/dac/ad8460.c
+++ b/drivers/iio/dac/ad8460.c
@@ -304,7 +304,7 @@ static ssize_t ad8460_write_powerdown(struct iio_dev *indio_dev, uintptr_t priva
* 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
+ * as well as the HV driver. Quiescent current decreases and output is
* floating (high impedance).
*/
diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02-core.c
index 8640b0ef4433..88bffbab0854 100644
--- a/drivers/iio/dac/mcp47feb02.c
+++ b/drivers/iio/dac/mcp47feb02-core.c
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface
+ * IIO driver for MCP47FEB02 Multi-Channel DAC with I2C and SPI interface
*
- * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
+ * Copyright (C) 2025-2026 Microchip Technology Inc. and its subsidiaries
*
* Author: Ariana Lazar <ariana.lazar@microchip.com>
*
@@ -16,7 +16,6 @@
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/err.h>
-#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/kstrtox.h>
@@ -29,6 +28,8 @@
#include <linux/types.h>
#include <linux/units.h>
+#include "mcp47feb02.h"
+
/* 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
@@ -36,7 +37,6 @@
#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)
@@ -49,12 +49,10 @@
#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)))
+#define MCP47FEB02_VREF_PD_MASK(ch) (GENMASK(1, 0) << (2 * (ch)))
-/* Gain Control and I2C Slave Address Reguster fields */
+/* Gain Control and I2C Slave Address Register 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)
@@ -92,214 +90,6 @@ static const char * const mcp47feb02_powerdown_modes[] = {
};
/**
- * 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
@@ -379,7 +169,7 @@ static const struct regmap_access_table mcp47feb02_volatile_table = {
.n_yes_ranges = ARRAY_SIZE(mcp47feb02_volatile_ranges),
};
-static const struct regmap_config mcp47feb02_regmap_config = {
+const struct regmap_config mcp47feb02_regmap_config = {
.name = "mcp47feb02_regmap",
.reg_bits = 8,
.val_bits = 16,
@@ -391,6 +181,7 @@ static const struct regmap_config mcp47feb02_regmap_config = {
.cache_type = REGCACHE_MAPLE,
.val_format_endian = REGMAP_ENDIAN_BIG,
};
+EXPORT_SYMBOL_NS_GPL(mcp47feb02_regmap_config, "IIO_MCP47FEB02");
/* For devices that doesn't have nonvolatile memory */
static const struct regmap_range mcp47fvb02_readable_ranges[] = {
@@ -421,7 +212,7 @@ static const struct regmap_access_table mcp47fvb02_volatile_table = {
.n_yes_ranges = ARRAY_SIZE(mcp47fvb02_volatile_ranges),
};
-static const struct regmap_config mcp47fvb02_regmap_config = {
+const struct regmap_config mcp47fvb02_regmap_config = {
.name = "mcp47fvb02_regmap",
.reg_bits = 8,
.val_bits = 16,
@@ -433,6 +224,7 @@ static const struct regmap_config mcp47fvb02_regmap_config = {
.cache_type = REGCACHE_MAPLE,
.val_format_endian = REGMAP_ENDIAN_BIG,
};
+EXPORT_SYMBOL_NS_GPL(mcp47fvb02_regmap_config, "IIO_MCP47FEB02");
static int mcp47feb02_write_to_eeprom(struct mcp47feb02_data *data, unsigned int reg,
unsigned int val)
@@ -443,12 +235,10 @@ static int mcp47feb02_write_to_eeprom(struct mcp47feb02_data *data, unsigned int
* 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);
+ 1 * USEC_PER_MSEC, 150 * USEC_PER_MSEC);
if (ret)
return ret;
@@ -468,7 +258,9 @@ static ssize_t store_eeprom_store(struct device *dev, struct device_attribute *a
return ret;
if (!state)
- return 0;
+ return len;
+
+ guard(mutex)(&data->lock);
/*
* Verify DAC Wiper and DAC Configuration are unlocked. If both are disabled,
@@ -508,7 +300,7 @@ static ssize_t store_eeprom_store(struct device *dev, struct device_attribute *a
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);
+ 1 * USEC_PER_MSEC, 150 * USEC_PER_MSEC);
if (ret)
return ret;
@@ -551,11 +343,13 @@ static int mcp47feb02_suspend(struct device *dev)
for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) {
u8 pd_mode;
+ u32 mask;
data->chdata[ch].powerdown = true;
pd_mode = data->chdata[ch].powerdown_mode + 1;
+ mask = MCP47FEB02_VREF_PD_MASK(ch);
ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR,
- DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode));
+ mask, field_prep(mask, pd_mode));
if (ret)
return ret;
@@ -576,30 +370,30 @@ static int mcp47feb02_resume(struct device *dev)
guard(mutex)(&data->lock);
for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) {
- u8 pd_mode;
+ u32 mask;
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;
+ mask = MCP47FEB02_VREF_PD_MASK(ch);
ret = regmap_update_bits(data->regmap, MCP47FEB02_VREF_REG_ADDR,
- DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode));
+ mask, field_prep(mask, data->chdata[ch].ref_mode));
if (ret)
return ret;
+ mask = DAC_GAIN_MASK(ch);
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));
+ mask, field_prep(mask, data->chdata[ch].use_2x_gain));
if (ret)
return ret;
+ mask = MCP47FEB02_VREF_PD_MASK(ch);
ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR,
- DAC_CTRL_MASK(ch),
- DAC_CTRL_VAL(ch, MCP47FEB02_NORMAL_OPERATION));
+ mask, field_prep(mask, MCP47FEB02_NORMAL_OPERATION));
if (ret)
return ret;
}
@@ -642,6 +436,7 @@ static ssize_t mcp47feb02_write_powerdown(struct iio_dev *indio_dev, uintptr_t p
u32 reg = ch->address;
u8 tmp_pd_mode;
bool state;
+ u32 mask;
int ret;
guard(mutex)(&data->lock);
@@ -655,8 +450,9 @@ static ssize_t mcp47feb02_write_powerdown(struct iio_dev *indio_dev, uintptr_t p
* 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;
+ mask = MCP47FEB02_VREF_PD_MASK(reg);
ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR,
- DAC_CTRL_MASK(reg), DAC_CTRL_VAL(reg, tmp_pd_mode));
+ mask, field_prep(mask, tmp_pd_mode));
if (ret)
return ret;
@@ -665,7 +461,8 @@ static ssize_t mcp47feb02_write_powerdown(struct iio_dev *indio_dev, uintptr_t p
return len;
}
-static DEFINE_SIMPLE_DEV_PM_OPS(mcp47feb02_pm_ops, mcp47feb02_suspend, mcp47feb02_resume);
+EXPORT_NS_SIMPLE_DEV_PM_OPS(mcp47feb02_pm_ops, mcp47feb02_suspend,
+ mcp47feb02_resume, IIO_MCP47FEB02);
static const struct iio_enum mcp47febxx_powerdown_mode_enum = {
.items = mcp47feb02_powerdown_modes,
@@ -800,6 +597,7 @@ static int mcp47feb02_check_scale(struct mcp47feb02_data *data, int val, int val
static int mcp47feb02_ch_scale(struct mcp47feb02_data *data, int ch, int scale)
{
int tmp_val, ret;
+ u32 mask;
if (scale == MCP47FEB02_SCALE_VDD) {
tmp_val = MCP47FEB02_VREF_VDD;
@@ -821,8 +619,9 @@ static int mcp47feb02_ch_scale(struct mcp47feb02_data *data, int ch, int scale)
tmp_val = MCP47FEB02_INTERNAL_BAND_GAP;
}
+ mask = MCP47FEB02_VREF_PD_MASK(ch);
ret = regmap_update_bits(data->regmap, MCP47FEB02_VREF_REG_ADDR,
- DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, tmp_val));
+ mask, field_prep(mask, tmp_val));
if (ret)
return ret;
@@ -845,6 +644,7 @@ static int mcp47feb02_ch_scale(struct mcp47feb02_data *data, int ch, int scale)
static int mcp47feb02_set_scale(struct mcp47feb02_data *data, int ch, int scale)
{
int tmp_val, ret;
+ u32 mask;
ret = mcp47feb02_ch_scale(data, ch, scale);
if (ret)
@@ -855,8 +655,9 @@ static int mcp47feb02_set_scale(struct mcp47feb02_data *data, int ch, int scale)
else
tmp_val = MCP47FEB02_GAIN_BIT_X1;
+ mask = DAC_GAIN_MASK(ch);
ret = regmap_update_bits(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR,
- DAC_GAIN_MASK(ch), DAC_GAIN_VAL(ch, tmp_val));
+ mask, field_prep(mask, tmp_val));
if (ret)
return ret;
@@ -922,7 +723,10 @@ static int mcp47feb02_read_label(struct iio_dev *indio_dev, struct iio_chan_spec
{
struct mcp47feb02_data *data = iio_priv(indio_dev);
- return sysfs_emit(label, "%s\n", data->labels[ch->address]);
+ if (data->labels[ch->address])
+ return sysfs_emit(label, "%s\n", data->labels[ch->address]);
+
+ return -ENOENT;
}
static const struct iio_info mcp47feb02_info = {
@@ -975,10 +779,11 @@ static int mcp47feb02_parse_fw(struct iio_dev *indio_dev,
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));
+ if (fwnode_property_present(child, "label")) {
+ ret = fwnode_property_read_string(child, "label", &data->labels[reg]);
+ if (ret)
+ dev_warn_probe(dev, ret, "%pfw: invalid label\n", child);
+ }
chanspec.address = reg;
chanspec.channel = reg;
@@ -1016,13 +821,17 @@ static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data)
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;
+ unsigned int pd_tmp, dac_val;
+
+ ret = regmap_read(data->regmap, REG_ADDR(i), &dac_val);
+ if (ret)
+ return ret;
+ data->chdata[i].dac_data = dac_val;
- 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;
+ data->chdata[i].ref_mode = field_get(MCP47FEB02_VREF_PD_MASK(i), vref_ch);
+ data->chdata[i].use_2x_gain = field_get(DAC_GAIN_MASK(i), gain_ch);
/*
* Inform the user that the current voltage reference read from the volatile
@@ -1065,7 +874,7 @@ static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data)
break;
}
- pd_tmp = (pd_ch >> (2 * i)) & MCP47FEB02_DAC_CTRL_MASK;
+ pd_tmp = field_get(MCP47FEB02_VREF_PD_MASK(i), pd_ch);
data->chdata[i].powerdown_mode = pd_tmp ? (pd_tmp - 1) : pd_tmp;
data->chdata[i].powerdown = !!(data->chdata[i].powerdown_mode);
}
@@ -1090,10 +899,10 @@ static int mcp47feb02_init_ch_scales(struct mcp47feb02_data *data, int vdd_uV,
return 0;
}
-static int mcp47feb02_probe(struct i2c_client *client)
+int mcp47feb02_common_probe(const struct mcp47feb02_features *chip_features,
+ struct regmap *regmap)
{
- const struct mcp47feb02_features *chip_features;
- struct device *dev = &client->dev;
+ struct device *dev = regmap_get_device(regmap);
struct mcp47feb02_data *data;
struct iio_dev *indio_dev;
int vref1_uV, vref_uV, vdd_uV, ret;
@@ -1102,22 +911,16 @@ static int mcp47feb02_probe(struct i2c_client *client)
if (!indio_dev)
return -ENOMEM;
- data = iio_priv(indio_dev);
- chip_features = i2c_get_match_data(client);
- if (!chip_features)
- return -EINVAL;
+ dev_set_drvdata(dev, indio_dev);
+ data = iio_priv(indio_dev);
data->chip_features = chip_features;
+ data->regmap = regmap;
- if (chip_features->have_eeprom) {
- data->regmap = devm_regmap_init_i2c(client, &mcp47feb02_regmap_config);
+ if (chip_features->have_eeprom)
indio_dev->info = &mcp47feb02_info;
- } else {
- data->regmap = devm_regmap_init_i2c(client, &mcp47fvb02_regmap_config);
+ else
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;
@@ -1174,76 +977,8 @@ static int mcp47feb02_probe(struct i2c_client *client)
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);
+EXPORT_SYMBOL_NS(mcp47feb02_common_probe, "IIO_MCP47FEB02");
MODULE_AUTHOR("Ariana Lazar <ariana.lazar@microchip.com>");
-MODULE_DESCRIPTION("IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface");
+MODULE_DESCRIPTION("IIO driver for MCP47FEB02/MCP48FEB02 Multi-Channel DAC");
MODULE_LICENSE("GPL");
diff --git a/drivers/iio/dac/mcp47feb02-i2c.c b/drivers/iio/dac/mcp47feb02-i2c.c
new file mode 100644
index 000000000000..7ec0cccad79b
--- /dev/null
+++ b/drivers/iio/dac/mcp47feb02-i2c.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface
+ *
+ * Copyright (C) 2025-2026 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Ariana Lazar <ariana.lazar@microchip.com>
+ *
+ * Datasheet links for devices with I2C interface:
+ * [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 <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include "mcp47feb02.h"
+
+/* Parts with EEPROM memory */
+MCP47FEB02_CHIP_INFO(mcp47feb01, 1, 8, false, true);
+MCP47FEB02_CHIP_INFO(mcp47feb02, 2, 8, false, true);
+MCP47FEB02_CHIP_INFO(mcp47feb04, 4, 8, true, true);
+MCP47FEB02_CHIP_INFO(mcp47feb08, 8, 8, true, true);
+MCP47FEB02_CHIP_INFO(mcp47feb11, 1, 10, false, true);
+MCP47FEB02_CHIP_INFO(mcp47feb12, 2, 10, false, true);
+MCP47FEB02_CHIP_INFO(mcp47feb14, 4, 10, true, true);
+MCP47FEB02_CHIP_INFO(mcp47feb18, 8, 10, true, true);
+MCP47FEB02_CHIP_INFO(mcp47feb21, 1, 12, false, true);
+MCP47FEB02_CHIP_INFO(mcp47feb22, 2, 12, false, true);
+MCP47FEB02_CHIP_INFO(mcp47feb24, 4, 12, true, true);
+MCP47FEB02_CHIP_INFO(mcp47feb28, 8, 12, true, true);
+
+/* Parts without EEPROM memory */
+MCP47FEB02_CHIP_INFO(mcp47fvb01, 1, 8, false, false);
+MCP47FEB02_CHIP_INFO(mcp47fvb02, 2, 8, false, false);
+MCP47FEB02_CHIP_INFO(mcp47fvb04, 4, 8, true, false);
+MCP47FEB02_CHIP_INFO(mcp47fvb08, 8, 8, true, false);
+MCP47FEB02_CHIP_INFO(mcp47fvb11, 1, 10, false, false);
+MCP47FEB02_CHIP_INFO(mcp47fvb12, 2, 10, false, false);
+MCP47FEB02_CHIP_INFO(mcp47fvb14, 4, 10, true, false);
+MCP47FEB02_CHIP_INFO(mcp47fvb18, 8, 10, true, false);
+MCP47FEB02_CHIP_INFO(mcp47fvb21, 1, 12, false, false);
+MCP47FEB02_CHIP_INFO(mcp47fvb22, 2, 12, false, false);
+MCP47FEB02_CHIP_INFO(mcp47fvb24, 4, 12, true, false);
+MCP47FEB02_CHIP_INFO(mcp47fvb28, 8, 12, true, false);
+
+static int mcp47feb02_i2c_probe(struct i2c_client *client)
+{
+ const struct mcp47feb02_features *chip_features;
+ struct device *dev = &client->dev;
+ struct regmap *regmap;
+
+ chip_features = i2c_get_match_data(client);
+ if (!chip_features)
+ return dev_err_probe(dev, -ENODATA, "No match data found\n");
+
+ if (chip_features->have_eeprom)
+ regmap = devm_regmap_init_i2c(client, &mcp47feb02_regmap_config);
+ else
+ regmap = devm_regmap_init_i2c(client, &mcp47fvb02_regmap_config);
+
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Error initializing I2C regmap\n");
+
+ return mcp47feb02_common_probe(chip_features, regmap);
+}
+
+static const struct i2c_device_id mcp47feb02_i2c_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_i2c_id);
+
+static const struct of_device_id mcp47feb02_of_i2c_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_i2c_match);
+
+static struct i2c_driver mcp47feb02_i2c_driver = {
+ .driver = {
+ .name = "mcp47feb02",
+ .of_match_table = mcp47feb02_of_i2c_match,
+ .pm = pm_sleep_ptr(&mcp47feb02_pm_ops),
+ },
+ .probe = mcp47feb02_i2c_probe,
+ .id_table = mcp47feb02_i2c_id,
+};
+module_i2c_driver(mcp47feb02_i2c_driver);
+
+MODULE_AUTHOR("Ariana Lazar <ariana.lazar@microchip.com>");
+MODULE_DESCRIPTION("IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_MCP47FEB02");
diff --git a/drivers/iio/dac/mcp47feb02-spi.c b/drivers/iio/dac/mcp47feb02-spi.c
new file mode 100644
index 000000000000..974f8fe1b86e
--- /dev/null
+++ b/drivers/iio/dac/mcp47feb02-spi.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * IIO driver for MCP48FEB02 Multi-Channel DAC with SPI interface
+ *
+ * Copyright (C) 2025-2026 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Ariana Lazar <ariana.lazar@microchip.com>
+ *
+ * Datasheet links for devices with SPI interface:
+ * [MCP48FEBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005429B.pdf
+ * [MCP48FVBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005466A.pdf
+ * [MCP48FxBx4/8] https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP48FXBX4-8-Family-Data-Sheet-DS20006362A.pdf
+ */
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+
+#include "mcp47feb02.h"
+
+/* Parts with EEPROM memory */
+MCP47FEB02_CHIP_INFO(mcp48feb01, 1, 8, false, true);
+MCP47FEB02_CHIP_INFO(mcp48feb02, 2, 8, false, true);
+MCP47FEB02_CHIP_INFO(mcp48feb04, 4, 8, true, true);
+MCP47FEB02_CHIP_INFO(mcp48feb08, 8, 8, true, true);
+MCP47FEB02_CHIP_INFO(mcp48feb11, 1, 10, false, true);
+MCP47FEB02_CHIP_INFO(mcp48feb12, 2, 10, false, true);
+MCP47FEB02_CHIP_INFO(mcp48feb14, 4, 10, true, true);
+MCP47FEB02_CHIP_INFO(mcp48feb18, 8, 10, true, true);
+MCP47FEB02_CHIP_INFO(mcp48feb21, 1, 12, false, true);
+MCP47FEB02_CHIP_INFO(mcp48feb22, 2, 12, false, true);
+MCP47FEB02_CHIP_INFO(mcp48feb24, 4, 12, true, true);
+MCP47FEB02_CHIP_INFO(mcp48feb28, 8, 12, true, true);
+
+/* Parts without EEPROM memory */
+MCP47FEB02_CHIP_INFO(mcp48fvb01, 1, 8, false, false);
+MCP47FEB02_CHIP_INFO(mcp48fvb02, 2, 8, false, false);
+MCP47FEB02_CHIP_INFO(mcp48fvb04, 4, 8, true, false);
+MCP47FEB02_CHIP_INFO(mcp48fvb08, 8, 8, true, false);
+MCP47FEB02_CHIP_INFO(mcp48fvb11, 1, 10, false, false);
+MCP47FEB02_CHIP_INFO(mcp48fvb12, 2, 10, false, false);
+MCP47FEB02_CHIP_INFO(mcp48fvb14, 4, 10, true, false);
+MCP47FEB02_CHIP_INFO(mcp48fvb18, 8, 10, true, false);
+MCP47FEB02_CHIP_INFO(mcp48fvb21, 1, 12, false, false);
+MCP47FEB02_CHIP_INFO(mcp48fvb22, 2, 12, false, false);
+MCP47FEB02_CHIP_INFO(mcp48fvb24, 4, 12, true, false);
+MCP47FEB02_CHIP_INFO(mcp48fvb28, 8, 12, true, false);
+
+static int mcp47feb02_spi_probe(struct spi_device *spi)
+{
+ const struct mcp47feb02_features *chip_features;
+ struct device *dev = &spi->dev;
+ struct regmap *regmap;
+
+ chip_features = spi_get_device_match_data(spi);
+ if (!chip_features)
+ return dev_err_probe(dev, -ENODATA, "No match data found\n");
+
+ if (chip_features->have_eeprom)
+ regmap = devm_regmap_init_spi(spi, &mcp47feb02_regmap_config);
+ else
+ regmap = devm_regmap_init_spi(spi, &mcp47fvb02_regmap_config);
+
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Error initializing SPI regmap\n");
+
+ return mcp47feb02_common_probe(chip_features, regmap);
+}
+
+static const struct spi_device_id mcp47feb02_spi_id[] = {
+ { .name = "mcp48feb01", .driver_data = (kernel_ulong_t)&mcp48feb01_chip_features },
+ { .name = "mcp48feb02", .driver_data = (kernel_ulong_t)&mcp48feb02_chip_features },
+ { .name = "mcp48feb04", .driver_data = (kernel_ulong_t)&mcp48feb04_chip_features },
+ { .name = "mcp48feb08", .driver_data = (kernel_ulong_t)&mcp48feb08_chip_features },
+ { .name = "mcp48feb11", .driver_data = (kernel_ulong_t)&mcp48feb11_chip_features },
+ { .name = "mcp48feb12", .driver_data = (kernel_ulong_t)&mcp48feb12_chip_features },
+ { .name = "mcp48feb14", .driver_data = (kernel_ulong_t)&mcp48feb14_chip_features },
+ { .name = "mcp48feb18", .driver_data = (kernel_ulong_t)&mcp48feb18_chip_features },
+ { .name = "mcp48feb21", .driver_data = (kernel_ulong_t)&mcp48feb21_chip_features },
+ { .name = "mcp48feb22", .driver_data = (kernel_ulong_t)&mcp48feb22_chip_features },
+ { .name = "mcp48feb24", .driver_data = (kernel_ulong_t)&mcp48feb24_chip_features },
+ { .name = "mcp48feb28", .driver_data = (kernel_ulong_t)&mcp48feb28_chip_features },
+ { .name = "mcp48fvb01", .driver_data = (kernel_ulong_t)&mcp48fvb01_chip_features },
+ { .name = "mcp48fvb02", .driver_data = (kernel_ulong_t)&mcp48fvb02_chip_features },
+ { .name = "mcp48fvb04", .driver_data = (kernel_ulong_t)&mcp48fvb04_chip_features },
+ { .name = "mcp48fvb08", .driver_data = (kernel_ulong_t)&mcp48fvb08_chip_features },
+ { .name = "mcp48fvb11", .driver_data = (kernel_ulong_t)&mcp48fvb11_chip_features },
+ { .name = "mcp48fvb12", .driver_data = (kernel_ulong_t)&mcp48fvb12_chip_features },
+ { .name = "mcp48fvb14", .driver_data = (kernel_ulong_t)&mcp48fvb14_chip_features },
+ { .name = "mcp48fvb18", .driver_data = (kernel_ulong_t)&mcp48fvb18_chip_features },
+ { .name = "mcp48fvb21", .driver_data = (kernel_ulong_t)&mcp48fvb21_chip_features },
+ { .name = "mcp48fvb22", .driver_data = (kernel_ulong_t)&mcp48fvb22_chip_features },
+ { .name = "mcp48fvb24", .driver_data = (kernel_ulong_t)&mcp48fvb24_chip_features },
+ { .name = "mcp48fvb28", .driver_data = (kernel_ulong_t)&mcp48fvb28_chip_features },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, mcp47feb02_spi_id);
+
+static const struct of_device_id mcp47feb02_of_spi_match[] = {
+ { .compatible = "microchip,mcp48feb01", .data = &mcp48feb01_chip_features },
+ { .compatible = "microchip,mcp48feb02", .data = &mcp48feb02_chip_features },
+ { .compatible = "microchip,mcp48feb04", .data = &mcp48feb04_chip_features },
+ { .compatible = "microchip,mcp48feb08", .data = &mcp48feb08_chip_features },
+ { .compatible = "microchip,mcp48feb11", .data = &mcp48feb11_chip_features },
+ { .compatible = "microchip,mcp48feb12", .data = &mcp48feb12_chip_features },
+ { .compatible = "microchip,mcp48feb14", .data = &mcp48feb14_chip_features },
+ { .compatible = "microchip,mcp48feb18", .data = &mcp48feb18_chip_features },
+ { .compatible = "microchip,mcp48feb21", .data = &mcp48feb21_chip_features },
+ { .compatible = "microchip,mcp48feb22", .data = &mcp48feb22_chip_features },
+ { .compatible = "microchip,mcp48feb24", .data = &mcp48feb24_chip_features },
+ { .compatible = "microchip,mcp48feb28", .data = &mcp48feb28_chip_features },
+ { .compatible = "microchip,mcp48fvb01", .data = &mcp48fvb01_chip_features },
+ { .compatible = "microchip,mcp48fvb02", .data = &mcp48fvb02_chip_features },
+ { .compatible = "microchip,mcp48fvb04", .data = &mcp48fvb04_chip_features },
+ { .compatible = "microchip,mcp48fvb08", .data = &mcp48fvb08_chip_features },
+ { .compatible = "microchip,mcp48fvb11", .data = &mcp48fvb11_chip_features },
+ { .compatible = "microchip,mcp48fvb12", .data = &mcp48fvb12_chip_features },
+ { .compatible = "microchip,mcp48fvb14", .data = &mcp48fvb14_chip_features },
+ { .compatible = "microchip,mcp48fvb18", .data = &mcp48fvb18_chip_features },
+ { .compatible = "microchip,mcp48fvb21", .data = &mcp48fvb21_chip_features },
+ { .compatible = "microchip,mcp48fvb22", .data = &mcp48fvb22_chip_features },
+ { .compatible = "microchip,mcp48fvb24", .data = &mcp48fvb24_chip_features },
+ { .compatible = "microchip,mcp48fvb28", .data = &mcp48fvb28_chip_features },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mcp47feb02_of_spi_match);
+
+static struct spi_driver mcp47feb02_spi_driver = {
+ .driver = {
+ .name = "mcp47feb02",
+ .of_match_table = mcp47feb02_of_spi_match,
+ .pm = pm_sleep_ptr(&mcp47feb02_pm_ops),
+ },
+ .probe = mcp47feb02_spi_probe,
+ .id_table = mcp47feb02_spi_id,
+};
+module_spi_driver(mcp47feb02_spi_driver);
+
+MODULE_AUTHOR("Ariana Lazar <ariana.lazar@microchip.com>");
+MODULE_DESCRIPTION("IIO driver for MCP48FEB02 Multi-Channel DAC with SPI interface");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_MCP47FEB02");
diff --git a/drivers/iio/dac/mcp47feb02.h b/drivers/iio/dac/mcp47feb02.h
new file mode 100644
index 000000000000..37fa4497c1c1
--- /dev/null
+++ b/drivers/iio/dac/mcp47feb02.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+#ifndef __DRIVERS_IIO_DAC_MCP47FEB02_H__
+#define __DRIVERS_IIO_DAC_MCP47FEB02_H__
+
+#include <linux/pm.h>
+#include <linux/types.h>
+
+#define MCP47FEB02_CHIP_INFO(_name, _channels, _res, _vref1, _eeprom) \
+static const struct mcp47feb02_features _name##_chip_features = { \
+ .name = #_name, \
+ .phys_channels = _channels, \
+ .resolution = _res, \
+ .have_ext_vref1 = _vref1, \
+ .have_eeprom = _eeprom, \
+}
+
+/**
+ * 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;
+};
+
+extern const struct regmap_config mcp47feb02_regmap_config;
+extern const struct regmap_config mcp47fvb02_regmap_config;
+
+int mcp47feb02_common_probe(const struct mcp47feb02_features *chip_features,
+ struct regmap *regmap);
+
+extern const struct dev_pm_ops mcp47feb02_pm_ops;
+
+#endif /* __DRIVERS_IIO_DAC_MCP47FEB02_H__ */
diff --git a/drivers/iio/dac/mcp4821.c b/drivers/iio/dac/mcp4821.c
index 8d8c00cd60e9..59defb1b5e62 100644
--- a/drivers/iio/dac/mcp4821.c
+++ b/drivers/iio/dac/mcp4821.c
@@ -255,9 +255,13 @@ static const struct iio_info mcp4821_info = {
static int mcp4821_probe(struct spi_device *spi)
{
+ const struct mcp4821_chip_info *info;
struct iio_dev *indio_dev;
struct mcp4821_state *state;
- const struct mcp4821_chip_info *info;
+
+ info = spi_get_device_match_data(spi);
+ if (!info)
+ return -ENODATA;
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*state));
if (indio_dev == NULL)
@@ -268,7 +272,6 @@ static int mcp4821_probe(struct spi_device *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;
diff --git a/drivers/iio/dummy/iio_simple_dummy.c b/drivers/iio/dummy/iio_simple_dummy.c
index 19fcdbbc11c6..28ae1e7b593d 100644
--- a/drivers/iio/dummy/iio_simple_dummy.c
+++ b/drivers/iio/dummy/iio_simple_dummy.c
@@ -116,7 +116,7 @@ static const struct iio_chan_spec iio_dummy_channels[] = {
BIT(IIO_CHAN_INFO_OFFSET) |
/*
* in_voltage0_scale
- * Multipler for userspace to apply post offset
+ * Multiplier for userspace to apply post offset
* when converting to standard units (microvolts)
*/
BIT(IIO_CHAN_INFO_SCALE),
diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig
index 90c6304c4bcd..994d199080eb 100644
--- a/drivers/iio/frequency/Kconfig
+++ b/drivers/iio/frequency/Kconfig
@@ -23,6 +23,27 @@ config AD9523
endmenu
+menu "Direct Digital Synthesis"
+
+config AD9910
+ tristate "Analog Devices AD9910 Direct Digital Synthesizer"
+ depends on SPI
+ depends on GPIOLIB
+ select CRC32
+ select FW_LOADER
+ select FW_UPLOAD
+ help
+ Say yes here to build support for Analog Devices AD9910
+ 1 GSPS, 14-Bit DDS with integrated DAC.
+
+ Supports single tone mode with 8 configurable profiles
+ and digital ramp generation.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad9910.
+
+endmenu
+
#
# Phase-Locked Loop (PLL) frequency synthesizers
#
diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile
index 53b4d01414d8..e3b1d36ed620 100644
--- a/drivers/iio/frequency/Makefile
+++ b/drivers/iio/frequency/Makefile
@@ -5,6 +5,7 @@
# When adding new entries keep the list in alphabetical order
obj-$(CONFIG_AD9523) += ad9523.o
+obj-$(CONFIG_AD9910) += ad9910.o
obj-$(CONFIG_ADF41513) += adf41513.o
obj-$(CONFIG_ADF4350) += adf4350.o
obj-$(CONFIG_ADF4371) += adf4371.o
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
new file mode 100644
index 000000000000..04a4e184b873
--- /dev/null
+++ b/drivers/iio/frequency/ad9910.c
@@ -0,0 +1,2347 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AD9910 SPI DDS (Direct Digital Synthesizer) driver
+ *
+ * Copyright 2026 Analog Devices Inc.
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/crc32.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device-id/of.h>
+#include <linux/device-id/spi.h>
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/log2.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/seq_file.h>
+#include <linux/spi/spi.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/units.h>
+#include <linux/unaligned.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+/* Register addresses */
+#define AD9910_REG_CFR1 0x00
+#define AD9910_REG_CFR2 0x01
+#define AD9910_REG_CFR3 0x02
+#define AD9910_REG_AUX_DAC 0x03
+#define AD9910_REG_IO_UPDATE_RATE 0x04
+#define AD9910_REG_FTW 0x07
+#define AD9910_REG_POW 0x08
+#define AD9910_REG_ASF 0x09
+#define AD9910_REG_MULTICHIP_SYNC 0x0A
+#define AD9910_REG_DRG_LIMIT 0x0B
+#define AD9910_REG_DRG_STEP 0x0C
+#define AD9910_REG_DRG_RATE 0x0D
+#define AD9910_REG_PROFILE0 0x0E
+#define AD9910_REG_PROFILE1 0x0F
+#define AD9910_REG_PROFILE2 0x10
+#define AD9910_REG_PROFILE3 0x11
+#define AD9910_REG_PROFILE4 0x12
+#define AD9910_REG_PROFILE5 0x13
+#define AD9910_REG_PROFILE6 0x14
+#define AD9910_REG_PROFILE7 0x15
+#define AD9910_REG_RAM 0x16
+
+#define AD9910_REG_NUM_CACHED 0x16
+#define AD9910_REG_PROFILE(x) (AD9910_REG_PROFILE0 + (x))
+
+/* CFR1 bit definitions */
+#define AD9910_CFR1_RAM_ENABLE_MSK BIT(31)
+#define AD9910_CFR1_RAM_PLAYBACK_DEST_MSK GENMASK(30, 29)
+#define AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK BIT(23)
+#define AD9910_CFR1_INV_SINC_EN_MSK BIT(22)
+#define AD9910_CFR1_INT_PROFILE_CTL_MSK GENMASK(20, 17)
+#define AD9910_CFR1_SELECT_SINE_MSK BIT(16)
+#define AD9910_CFR1_LOAD_LRR_IO_UPDATE_MSK BIT(15)
+#define AD9910_CFR1_AUTOCLR_DIG_RAMP_ACCUM_MSK BIT(14)
+#define AD9910_CFR1_AUTOCLR_PHASE_ACCUM_MSK BIT(13)
+#define AD9910_CFR1_CLEAR_DIG_RAMP_ACCUM_MSK BIT(12)
+#define AD9910_CFR1_CLEAR_PHASE_ACCUM_MSK BIT(11)
+#define AD9910_CFR1_LOAD_ARR_IO_UPDATE_MSK BIT(10)
+#define AD9910_CFR1_OSK_ENABLE_MSK BIT(9)
+#define AD9910_CFR1_SELECT_AUTO_OSK_MSK BIT(8)
+#define AD9910_CFR1_DIGITAL_POWER_DOWN_MSK BIT(7)
+#define AD9910_CFR1_DAC_POWER_DOWN_MSK BIT(6)
+#define AD9910_CFR1_REFCLK_INPUT_POWER_DOWN_MSK BIT(5)
+#define AD9910_CFR1_AUX_DAC_POWER_DOWN_MSK BIT(4)
+#define AD9910_CFR1_EXT_POWER_DOWN_CTL_MSK BIT(3)
+#define AD9910_CFR1_SDIO_INPUT_ONLY_MSK BIT(1)
+#define AD9910_CFR1_LSB_FIRST_MSK BIT(0)
+
+/* DIGITAL and REFCLK_INPUT powerdown bits are effective without I/O update */
+#define AD9910_CFR1_SW0_POWER_DOWN_MSK (AD9910_CFR1_DIGITAL_POWER_DOWN_MSK | \
+ AD9910_CFR1_REFCLK_INPUT_POWER_DOWN_MSK)
+/* DAC and AUX_DAC powerdown require I/O update */
+#define AD9910_CFR1_SW1_POWER_DOWN_MSK (AD9910_CFR1_DAC_POWER_DOWN_MSK | \
+ AD9910_CFR1_AUX_DAC_POWER_DOWN_MSK)
+
+#define AD9910_CFR1_SW_POWER_DOWN_MSK (AD9910_CFR1_SW0_POWER_DOWN_MSK | \
+ AD9910_CFR1_SW1_POWER_DOWN_MSK)
+
+/* CFR2 bit definitions */
+#define AD9910_CFR2_AMP_SCALE_SINGLE_TONE_MSK BIT(24)
+#define AD9910_CFR2_INTERNAL_IO_UPDATE_MSK BIT(23)
+#define AD9910_CFR2_SYNC_CLK_EN_MSK BIT(22)
+#define AD9910_CFR2_DRG_DEST_MSK GENMASK(21, 20)
+#define AD9910_CFR2_DRG_ENABLE_MSK BIT(19)
+#define AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK BIT(18)
+#define AD9910_CFR2_DRG_NO_DWELL_LOW_MSK BIT(17)
+#define AD9910_CFR2_DRG_NO_DWELL_MSK GENMASK(18, 17)
+#define AD9910_CFR2_READ_EFFECTIVE_FTW_MSK BIT(16)
+#define AD9910_CFR2_IO_UPDATE_RATE_CTL_MSK GENMASK(15, 14)
+#define AD9910_CFR2_PDCLK_ENABLE_MSK BIT(11)
+#define AD9910_CFR2_PDCLK_INVERT_MSK BIT(10)
+#define AD9910_CFR2_TXENABLE_INVERT_MSK BIT(9)
+#define AD9910_CFR2_MATCHED_LATENCY_EN_MSK BIT(7)
+#define AD9910_CFR2_DATA_ASM_HOLD_LAST_MSK BIT(6)
+#define AD9910_CFR2_SYNC_TIMING_VAL_DISABLE_MSK BIT(5)
+#define AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK BIT(4)
+#define AD9910_CFR2_FM_GAIN_MSK GENMASK(3, 0)
+
+/* CFR3 bit definitions */
+#define AD9910_CFR3_OPEN_MSK (BIT(27) | GENMASK(18, 16))
+#define AD9910_CFR3_DRV0_MSK GENMASK(29, 28)
+#define AD9910_CFR3_VCO_SEL_MSK GENMASK(26, 24)
+#define AD9910_CFR3_ICP_MSK GENMASK(21, 19)
+#define AD9910_CFR3_REFCLK_DIV_BYPASS_MSK BIT(15)
+#define AD9910_CFR3_REFCLK_DIV_RESETB_MSK BIT(14)
+#define AD9910_CFR3_PFD_RESET_MSK BIT(10)
+#define AD9910_CFR3_PLL_EN_MSK BIT(8)
+#define AD9910_CFR3_N_MSK GENMASK(7, 1)
+
+/* Auxiliary DAC Control Register Bits */
+#define AD9910_AUX_DAC_FSC_MSK GENMASK(7, 0)
+
+/* POW Register Bits */
+#define AD9910_POW_PP_LSB_MSK GENMASK(7, 0)
+
+/* ASF Register Bits */
+#define AD9910_ASF_RAMP_RATE_MSK GENMASK(31, 16)
+#define AD9910_ASF_SCALE_FACTOR_MSK GENMASK(15, 2)
+#define AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK GENMASK(7, 2)
+#define AD9910_ASF_STEP_SIZE_MSK GENMASK(1, 0)
+
+/* Multichip Sync Register Bits */
+#define AD9910_MC_SYNC_VALIDATION_DELAY_MSK GENMASK(31, 28)
+#define AD9910_MC_SYNC_RECEIVER_ENABLE_MSK BIT(27)
+#define AD9910_MC_SYNC_GENERATOR_ENABLE_MSK BIT(26)
+#define AD9910_MC_SYNC_GENERATOR_POLARITY_MSK BIT(25)
+#define AD9910_MC_SYNC_STATE_PRESET_MSK GENMASK(23, 18)
+#define AD9910_MC_SYNC_OUTPUT_DELAY_MSK GENMASK(15, 11)
+#define AD9910_MC_SYNC_INPUT_DELAY_MSK GENMASK(7, 3)
+
+/* Digital Ramp Limit Register */
+#define AD9910_DRG_LIMIT_UPPER_MSK GENMASK_ULL(63, 32)
+#define AD9910_DRG_LIMIT_LOWER_MSK GENMASK_ULL(31, 0)
+
+/* Digital Ramp Step Register */
+#define AD9910_DRG_STEP_DEC_MSK GENMASK_ULL(63, 32)
+#define AD9910_DRG_STEP_INC_MSK GENMASK_ULL(31, 0)
+
+/* Digital Ramp Rate Register */
+#define AD9910_DRG_RATE_DEC_MSK GENMASK(31, 16)
+#define AD9910_DRG_RATE_INC_MSK GENMASK(15, 0)
+
+/* Profile Register Format (Single Tone Mode) */
+#define AD9910_PROFILE_ST_ASF_MSK GENMASK_ULL(61, 48)
+#define AD9910_PROFILE_ST_POW_MSK GENMASK_ULL(47, 32)
+#define AD9910_PROFILE_ST_FTW_MSK GENMASK_ULL(31, 0)
+
+/* Profile Register Format (RAM Mode) */
+#define AD9910_PROFILE_RAM_OPEN_MSK GENMASK_ULL(61, 56)
+#define AD9910_PROFILE_RAM_STEP_RATE_MSK GENMASK_ULL(55, 40)
+#define AD9910_PROFILE_RAM_END_ADDR_MSK GENMASK_ULL(39, 30)
+#define AD9910_PROFILE_RAM_START_ADDR_MSK GENMASK_ULL(23, 14)
+#define AD9910_PROFILE_RAM_NO_DWELL_HIGH_MSK BIT_ULL(5)
+#define AD9910_PROFILE_RAM_ZERO_CROSSING_MSK BIT_ULL(3)
+#define AD9910_PROFILE_RAM_MODE_CONTROL_MSK GENMASK_ULL(2, 0)
+
+/* Device constants */
+#define AD9910_PI_NANORAD 3141592653UL
+#define AD9910_PI_PICORAD 3141592653590ULL
+
+#define AD9910_MAX_SYSCLK_HZ (1000 * HZ_PER_MHZ)
+#define AD9910_MAX_PHASE_MICRORAD (AD9910_PI_NANORAD / 500)
+
+#define AD9910_ASF_MAX FIELD_MAX(AD9910_PROFILE_ST_ASF_MSK)
+#define AD9910_ASF_PP_LSB_MAX FIELD_MAX(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK)
+#define AD9910_POW_MAX FIELD_MAX(AD9910_PROFILE_ST_POW_MSK)
+#define AD9910_POW_PP_LSB_MAX FIELD_MAX(AD9910_POW_PP_LSB_MSK)
+#define AD9910_STEP_RATE_MAX FIELD_MAX(AD9910_DRG_RATE_DEC_MSK)
+#define AD9910_NUM_PROFILES 8
+
+#define AD9910_RAM_FW_MAGIC 0x00AD9910
+#define AD9910_RAM_FW_V1 0x0001
+#define AD9910_RAM_SIZE_MAX_WORDS 1024
+#define AD9910_RAM_WORD_SIZE sizeof(u32)
+#define AD9910_RAM_SIZE_MAX_BYTES (AD9910_RAM_SIZE_MAX_WORDS * AD9910_RAM_WORD_SIZE)
+#define AD9910_RAM_ADDR_MAX (AD9910_RAM_SIZE_MAX_WORDS - 1)
+
+#define AD9910_RAM_ENABLED(st) \
+ FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, (st)->reg[AD9910_REG_CFR1].val32)
+
+#define AD9910_DEST_FREQUENCY 0
+#define AD9910_DEST_PHASE 1
+#define AD9910_DEST_AMPLITUDE 2
+#define AD9910_DEST_POLAR 3
+
+/* PLL constants */
+#define AD9910_PLL_MIN_N 12
+#define AD9910_PLL_MAX_N 127
+
+#define AD9910_PLL_IN_MIN_FREQ_HZ (3307 * HZ_PER_KHZ)
+#define AD9910_PLL_IN_MAX_FREQ_HZ (60 * HZ_PER_MHZ)
+
+#define AD9910_PLL_OUT_MIN_FREQ_HZ (420 * HZ_PER_MHZ)
+#define AD9910_PLL_OUT_MAX_FREQ_HZ AD9910_MAX_SYSCLK_HZ
+
+#define AD9910_VCO0_RANGE_AUTO_MAX_HZ (457 * HZ_PER_MHZ)
+#define AD9910_VCO1_RANGE_AUTO_MAX_HZ (530 * HZ_PER_MHZ)
+#define AD9910_VCO2_RANGE_AUTO_MAX_HZ (632 * HZ_PER_MHZ)
+#define AD9910_VCO3_RANGE_AUTO_MAX_HZ (775 * HZ_PER_MHZ)
+#define AD9910_VCO4_RANGE_AUTO_MAX_HZ (897 * HZ_PER_MHZ)
+#define AD9910_VCO_RANGE_NUM 6
+
+#define AD9910_ICP_MIN_uA 212
+#define AD9910_ICP_MAX_uA 387
+#define AD9910_ICP_STEP_uA 25
+
+#define AD9910_DAC_IOUT_MAX_uA 31590
+#define AD9910_DAC_IOUT_DEFAULT_uA 20070
+#define AD9910_DAC_IOUT_MIN_uA 8640
+
+/* altcurrent ABI is in mA */
+#define AD9910_NANO_MILLIAMP_PER_MICROAMP 1000000UL
+#define AD9910_PICO_MILLIAMP_PER_MICROAMP 1000000000UL
+
+#define AD9910_REFDIV2_MIN_FREQ_HZ (25 * HZ_PER_MHZ)
+#define AD9910_REFDIV2_MAX_FREQ_HZ (1900 * HZ_PER_MHZ)
+
+#define AD9910_WAKEUP_DELAY_us 1000 /* 1ms: datasheet Table 1 */
+#define AD9910_RESET_DELAY_us 1 /* 5 sysclk cycles: < 1us */
+
+#define AD9910_SPI_DATA_IDX 1
+#define AD9910_SPI_DATA_LEN_MAX AD9910_RAM_SIZE_MAX_BYTES
+#define AD9910_SPI_MESSAGE_LEN_MAX (AD9910_SPI_DATA_IDX + AD9910_SPI_DATA_LEN_MAX)
+#define AD9910_SPI_READ_MSK BIT(7)
+#define AD9910_SPI_ADDR_MSK GENMASK(4, 0)
+
+/**
+ * enum ad9910_channel - AD9910 channel identifiers in priority order
+ *
+ * @AD9910_CHANNEL_PHY: Physical output channel
+ * @AD9910_CHANNEL_PROFILE_0: Profile 0 output channel
+ * @AD9910_CHANNEL_PROFILE_1: Profile 1 output channel
+ * @AD9910_CHANNEL_PROFILE_2: Profile 2 output channel
+ * @AD9910_CHANNEL_PROFILE_3: Profile 3 output channel
+ * @AD9910_CHANNEL_PROFILE_4: Profile 4 output channel
+ * @AD9910_CHANNEL_PROFILE_5: Profile 5 output channel
+ * @AD9910_CHANNEL_PROFILE_6: Profile 6 output channel
+ * @AD9910_CHANNEL_PROFILE_7: Profile 7 output channel
+ * @AD9910_CHANNEL_PARALLEL: Parallel Data output channel
+ * @AD9910_CHANNEL_PARALLEL_POLAR: Parallel Polar Data output channel
+ * @AD9910_CHANNEL_DRG: Digital Ramp Generator output channel
+ * @AD9910_CHANNEL_DRG_RAMP_UP: DRG ramp up channel
+ * @AD9910_CHANNEL_DRG_RAMP_DOWN: DRG ramp down channel
+ * @AD9910_CHANNEL_RAM: RAM control output channel
+ * @AD9910_CHANNEL_OSK: Output Shift Keying output channel
+ */
+enum ad9910_channel {
+ AD9910_CHANNEL_PHY = 100,
+ AD9910_CHANNEL_PROFILE_0 = 110,
+ AD9910_CHANNEL_PROFILE_1 = 111,
+ AD9910_CHANNEL_PROFILE_2 = 112,
+ AD9910_CHANNEL_PROFILE_3 = 113,
+ AD9910_CHANNEL_PROFILE_4 = 114,
+ AD9910_CHANNEL_PROFILE_5 = 115,
+ AD9910_CHANNEL_PROFILE_6 = 116,
+ AD9910_CHANNEL_PROFILE_7 = 117,
+ AD9910_CHANNEL_PARALLEL = 120,
+ AD9910_CHANNEL_PARALLEL_POLAR = 121,
+ AD9910_CHANNEL_DRG = 130,
+ AD9910_CHANNEL_DRG_RAMP_UP = 131,
+ AD9910_CHANNEL_DRG_RAMP_DOWN = 132,
+ AD9910_CHANNEL_RAM = 140,
+ AD9910_CHANNEL_OSK = 150,
+};
+
+/**
+ * struct ad9910_ram_fw - AD9910 RAM firmware format
+ * @magic: Magic number for RAM firmware validation
+ * @version: Firmware version number
+ * @wcount: Number of RAM words to be written
+ * @crc: CRC32 checksum of the RAM data for integrity verification
+ * @cfr1: Value of CFR1 register to be configured (not all fields are
+ * used, but this is included here for convenience)
+ * @profiles: Array of RAM profile configurations
+ * @words: Array of RAM words to be written. Data pattern should be set in
+ * reverse order and wcount specifies the number of words in this
+ * array
+ */
+struct ad9910_ram_fw {
+ __be32 magic;
+ __be16 version;
+ __be16 wcount;
+ __be32 crc;
+ __be32 cfr1;
+ __be64 profiles[AD9910_NUM_PROFILES];
+ __be32 words[] __counted_by_be(wcount);
+} __packed;
+
+enum {
+ AD9910_CHAN_IDX_PHY,
+ AD9910_CHAN_IDX_PROFILE_0,
+ AD9910_CHAN_IDX_PROFILE_1,
+ AD9910_CHAN_IDX_PROFILE_2,
+ AD9910_CHAN_IDX_PROFILE_3,
+ AD9910_CHAN_IDX_PROFILE_4,
+ AD9910_CHAN_IDX_PROFILE_5,
+ AD9910_CHAN_IDX_PROFILE_6,
+ AD9910_CHAN_IDX_PROFILE_7,
+ AD9910_CHAN_IDX_PARALLEL_AMP,
+ AD9910_CHAN_IDX_PARALLEL_PHASE,
+ AD9910_CHAN_IDX_PARALLEL_FREQ,
+ AD9910_CHAN_IDX_PARALLEL_POLAR_AMP,
+ AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE,
+ AD9910_CHAN_IDX_DRG_FREQ,
+ AD9910_CHAN_IDX_DRG_PHASE,
+ AD9910_CHAN_IDX_DRG_AMP,
+ AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP,
+ AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN,
+ AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP,
+ AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN,
+ AD9910_CHAN_IDX_DRG_AMP_RAMP_UP,
+ AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN,
+ AD9910_CHAN_IDX_RAM,
+ AD9910_CHAN_IDX_OSK,
+};
+
+enum {
+ AD9910_POWERDOWN,
+ AD9910_DWELL_EN,
+ AD9910_ROC,
+ AD9910_ROC_AVAIL,
+};
+
+struct ad9910_data {
+ u32 sysclk_freq_hz;
+ u32 output_current_uA;
+
+ u16 pll_charge_pump_current;
+ u8 refclk_out_drv;
+ bool pll_enabled;
+};
+
+union ad9910_reg {
+ u64 val64;
+ u32 val32;
+ u16 val16;
+};
+
+struct ad9910_state {
+ struct spi_device *spi;
+ struct clk *refclk;
+ struct fw_upload *ram_fwu;
+
+ struct gpio_desc *gpio_pwdown;
+ struct gpio_desc *gpio_update;
+ struct gpio_descs *gpio_profile;
+
+ /* cached registers */
+ union ad9910_reg reg[AD9910_REG_NUM_CACHED];
+
+ /*
+ * alternate profile registers used to store RAM profile settings when
+ * RAM mode is disabled and Single Tone profile settings when RAM mode
+ * is enabled.
+ */
+ u64 reg_profile[AD9910_NUM_PROFILES];
+
+ /* Lock for accessing device registers and state variables */
+ struct mutex lock;
+
+ struct ad9910_data data;
+ u8 profile;
+
+ bool ram_fwu_cancel;
+ char ram_fwu_name[20];
+
+ /*
+ * RAM loading requires a reasonable amount of bytes, at the same time
+ * DMA capable SPI drivers requires the transfer buffers to live in
+ * their own cache lines.
+ */
+ u8 tx_buf[AD9910_SPI_MESSAGE_LEN_MAX] __aligned(IIO_DMA_MINALIGN);
+};
+
+/**
+ * ad9910_rational_scale() - Perform scaling of input given a reference.
+ * @input: The input value to be scaled.
+ * @scale: The numerator of the scaling factor.
+ * @reference: The denominator of the scaling factor.
+ *
+ * Closest rounding with mul_u64_add_u64_div_u64
+ *
+ * Return: The scaled value.
+ */
+static u64 ad9910_rational_scale(u64 input, u64 scale, u64 reference)
+{
+ return mul_u64_add_u64_div_u64(input, scale, reference >> 1, reference);
+}
+
+static inline u64 ad9910_ram_profile_val(struct ad9910_state *st)
+{
+ if (AD9910_RAM_ENABLED(st))
+ return st->reg[AD9910_REG_PROFILE(st->profile)].val64;
+ else
+ return st->reg_profile[st->profile];
+}
+
+static inline u64 ad9910_st_profile_val(struct ad9910_state *st, u8 profile)
+{
+ if (AD9910_RAM_ENABLED(st))
+ return st->reg_profile[profile];
+ else
+ return st->reg[AD9910_REG_PROFILE(profile)].val64;
+}
+
+static int ad9910_io_update(struct ad9910_state *st)
+{
+ if (st->gpio_update) {
+ gpiod_set_value_cansleep(st->gpio_update, 1);
+ fsleep(1);
+ gpiod_set_value_cansleep(st->gpio_update, 0);
+ }
+
+ return 0;
+}
+
+static int ad9910_spi_read(struct ad9910_state *st, u8 reg, void *data,
+ size_t len)
+{
+ u8 inst = AD9910_SPI_READ_MSK | FIELD_PREP(AD9910_SPI_ADDR_MSK, reg);
+
+ return spi_write_then_read(st->spi, &inst, sizeof(inst), data, len);
+}
+
+static int ad9910_spi_write(struct ad9910_state *st, u8 reg, size_t len,
+ bool update)
+{
+ int ret;
+
+ st->tx_buf[0] = FIELD_PREP(AD9910_SPI_ADDR_MSK, reg);
+ ret = spi_write(st->spi, st->tx_buf, AD9910_SPI_DATA_IDX + len);
+ if (ret)
+ return ret;
+
+ if (update)
+ return ad9910_io_update(st);
+
+ return 0;
+}
+
+#define AD9910_REG_READ_FN(nb) \
+static int ad9910_reg##nb##_read(struct ad9910_state *st, u8 reg, \
+ u##nb * data) \
+{ \
+ __be##nb be_data; \
+ int ret; \
+ \
+ ret = ad9910_spi_read(st, reg, &be_data, sizeof(be_data)); \
+ if (ret) \
+ return ret; \
+ \
+ *data = be##nb##_to_cpu(be_data); \
+ return ret; \
+}
+
+AD9910_REG_READ_FN(16)
+AD9910_REG_READ_FN(32)
+AD9910_REG_READ_FN(64)
+
+#define AD9910_REG_WRITE_FN(nb) \
+static int ad9910_reg##nb##_write(struct ad9910_state *st, u8 reg, \
+ u##nb data, bool update) \
+{ \
+ int ret; \
+ \
+ put_unaligned_be##nb(data, &st->tx_buf[AD9910_SPI_DATA_IDX]); \
+ ret = ad9910_spi_write(st, reg, sizeof(data), update); \
+ if (ret) \
+ return ret; \
+ \
+ st->reg[reg].val##nb = data; \
+ return ret; \
+}
+
+AD9910_REG_WRITE_FN(16)
+AD9910_REG_WRITE_FN(32)
+AD9910_REG_WRITE_FN(64)
+
+#define AD9910_REG_UPDATE_FN(nb) \
+static int ad9910_reg##nb##_update(struct ad9910_state *st, \
+ u8 reg, u##nb mask, \
+ u##nb data, bool update) \
+{ \
+ u##nb reg_val = (st->reg[reg].val##nb & ~mask) | (data & mask); \
+ \
+ if (reg_val == st->reg[reg].val##nb && !update) \
+ return 0; \
+ \
+ return ad9910_reg##nb##_write(st, reg, reg_val, update); \
+}
+
+AD9910_REG_UPDATE_FN(16)
+AD9910_REG_UPDATE_FN(32)
+AD9910_REG_UPDATE_FN(64)
+
+static int ad9910_set_dac_current(struct ad9910_state *st, u32 val_uA,
+ bool update)
+{
+ u32 code;
+
+ /* FSC = (86.4 / Rset) * (1 + CODE/96) where Rset = 10k ohms */
+ val_uA = clamp(val_uA, AD9910_DAC_IOUT_MIN_uA, AD9910_DAC_IOUT_MAX_uA);
+ code = DIV_ROUND_CLOSEST(val_uA - AD9910_DAC_IOUT_MIN_uA, 90);
+ st->data.output_current_uA = AD9910_DAC_IOUT_MIN_uA + code * 90;
+
+ return ad9910_reg32_write(st, AD9910_REG_AUX_DAC, code, update);
+}
+
+static int ad9910_set_sysclk_freq(struct ad9910_state *st, u32 freq_hz,
+ bool update)
+{
+ struct device *dev = &st->spi->dev;
+ unsigned long refclk_freq_hz;
+ u32 sysclk_freq_hz;
+ u32 tmp32, vco_sel;
+ int ret;
+
+ if (!freq_hz || freq_hz > AD9910_MAX_SYSCLK_HZ)
+ return -EINVAL;
+
+ refclk_freq_hz = clk_get_rate(st->refclk);
+ if (st->data.pll_enabled) {
+ if (refclk_freq_hz < AD9910_PLL_IN_MIN_FREQ_HZ ||
+ refclk_freq_hz > AD9910_PLL_IN_MAX_FREQ_HZ) {
+ dev_err(dev,
+ "REF_CLK freq %lu Hz is out of PLL input range\n",
+ refclk_freq_hz);
+ return -ERANGE;
+ }
+
+ tmp32 = DIV_ROUND_CLOSEST(freq_hz, refclk_freq_hz);
+ tmp32 = clamp(tmp32, DIV_ROUND_UP(AD9910_PLL_OUT_MIN_FREQ_HZ, refclk_freq_hz),
+ AD9910_PLL_OUT_MAX_FREQ_HZ / refclk_freq_hz);
+ tmp32 = clamp(tmp32, AD9910_PLL_MIN_N, AD9910_PLL_MAX_N);
+ sysclk_freq_hz = refclk_freq_hz * tmp32;
+
+ if (sysclk_freq_hz <= AD9910_VCO0_RANGE_AUTO_MAX_HZ)
+ vco_sel = 0;
+ else if (sysclk_freq_hz <= AD9910_VCO1_RANGE_AUTO_MAX_HZ)
+ vco_sel = 1;
+ else if (sysclk_freq_hz <= AD9910_VCO2_RANGE_AUTO_MAX_HZ)
+ vco_sel = 2;
+ else if (sysclk_freq_hz <= AD9910_VCO3_RANGE_AUTO_MAX_HZ)
+ vco_sel = 3;
+ else if (sysclk_freq_hz <= AD9910_VCO4_RANGE_AUTO_MAX_HZ)
+ vco_sel = 4;
+ else
+ vco_sel = 5;
+
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR3,
+ AD9910_CFR3_N_MSK | AD9910_CFR3_VCO_SEL_MSK,
+ FIELD_PREP(AD9910_CFR3_N_MSK, tmp32) |
+ FIELD_PREP(AD9910_CFR3_VCO_SEL_MSK, vco_sel),
+ update);
+ if (ret)
+ return ret;
+ } else {
+ if (refclk_freq_hz < AD9910_REFDIV2_MIN_FREQ_HZ ||
+ refclk_freq_hz > AD9910_REFDIV2_MAX_FREQ_HZ) {
+ dev_err(dev,
+ "REF_CLK freq %lu Hz is out of divider range\n",
+ refclk_freq_hz);
+ return -ERANGE;
+ }
+
+ tmp32 = DIV_ROUND_CLOSEST(refclk_freq_hz, freq_hz);
+ tmp32 = clamp(tmp32, 1U, 2U);
+ sysclk_freq_hz = refclk_freq_hz / tmp32;
+ tmp32 = AD9910_CFR3_VCO_SEL_MSK |
+ FIELD_PREP(AD9910_CFR3_REFCLK_DIV_BYPASS_MSK, tmp32 % 2);
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR3,
+ AD9910_CFR3_VCO_SEL_MSK |
+ AD9910_CFR3_REFCLK_DIV_BYPASS_MSK,
+ tmp32, update);
+ if (ret)
+ return ret;
+ }
+
+ st->data.sysclk_freq_hz = sysclk_freq_hz;
+
+ return 0;
+}
+
+static int ad9910_profile_set(struct ad9910_state *st, u8 profile)
+{
+ DECLARE_BITMAP(values, BITS_PER_TYPE(profile));
+
+ st->profile = profile;
+ values[0] = profile;
+ gpiod_multi_set_value_cansleep(st->gpio_profile, values);
+
+ return 0;
+}
+
+static bool ad9910_sw_powerdown_get(struct ad9910_state *st)
+{
+ return FIELD_GET(AD9910_CFR1_SW_POWER_DOWN_MSK,
+ st->reg[AD9910_REG_CFR1].val32) ? true : false;
+}
+
+static int ad9910_sw_powerdown_set(struct ad9910_state *st, bool enable)
+{
+ int ret;
+
+ if (ad9910_sw_powerdown_get(st) == enable)
+ return 0;
+
+ /*
+ * When powering down, the DAC and AUX_DAC (SW1) must be powered down
+ * first, as they require an I/O update to take effect. The opposite is
+ * true when powering up, the DAC and AUX_DAC must be powered up last,
+ * i.e., after the DIGITAL and REFCLK_INPUT (SW0) power up.
+ */
+ if (enable) {
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_SW1_POWER_DOWN_MSK,
+ AD9910_CFR1_SW1_POWER_DOWN_MSK,
+ true);
+ if (ret)
+ return ret;
+
+ return ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_SW0_POWER_DOWN_MSK,
+ AD9910_CFR1_SW0_POWER_DOWN_MSK,
+ false);
+ }
+
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_SW0_POWER_DOWN_MSK, 0, false);
+ if (ret)
+ return ret;
+
+ return ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_SW1_POWER_DOWN_MSK, 0, true);
+}
+
+static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ int val;
+
+ guard(mutex)(&st->lock);
+
+ switch (private) {
+ case AD9910_POWERDOWN:
+ val = ad9910_sw_powerdown_get(st);
+ break;
+ case AD9910_DWELL_EN:
+ if (chan->channel == AD9910_CHANNEL_DRG_RAMP_UP)
+ val = FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK,
+ st->reg[AD9910_REG_CFR2].val32) ? 0 : 1;
+ else
+ val = FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_LOW_MSK,
+ st->reg[AD9910_REG_CFR2].val32) ? 0 : 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return iio_format_value(buf, IIO_VAL_INT, 1, &val);
+}
+
+static ssize_t ad9910_ext_info_write(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ u32 val32;
+ int ret;
+
+ ret = kstrtou32(buf, 10, &val32);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&st->lock);
+
+ switch (private) {
+ case AD9910_POWERDOWN:
+ ret = ad9910_sw_powerdown_set(st, val32 ? true : false);
+ if (ret)
+ return ret;
+ break;
+ case AD9910_DWELL_EN:
+ if (chan->channel == AD9910_CHANNEL_DRG_RAMP_UP) {
+ val32 = val32 ? 0 : AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK;
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR2,
+ AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK,
+ val32, true);
+ if (ret)
+ return ret;
+ } else {
+ val32 = val32 ? 0 : AD9910_CFR2_DRG_NO_DWELL_LOW_MSK;
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR2,
+ AD9910_CFR2_DRG_NO_DWELL_LOW_MSK,
+ val32, true);
+ if (ret)
+ return ret;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return len;
+}
+
+static ssize_t ad9910_drg_roc_read(struct iio_dev *indio_dev, uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ u64 roc64;
+ u32 rate;
+
+ guard(mutex)(&st->lock);
+
+ switch (chan->channel) {
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ roc64 = FIELD_GET(AD9910_DRG_STEP_INC_MSK,
+ st->reg[AD9910_REG_DRG_STEP].val64);
+ rate = FIELD_GET(AD9910_DRG_RATE_INC_MSK,
+ st->reg[AD9910_REG_DRG_RATE].val32);
+ break;
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ roc64 = FIELD_GET(AD9910_DRG_STEP_DEC_MSK,
+ st->reg[AD9910_REG_DRG_STEP].val64);
+ rate = FIELD_GET(AD9910_DRG_RATE_DEC_MSK,
+ st->reg[AD9910_REG_DRG_RATE].val32);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!rate)
+ return -ERANGE;
+
+ roc64 *= st->data.sysclk_freq_hz;
+ return sysfs_emit(buf, "%llu\n", div_u64(roc64, 4 * rate));
+}
+
+static ssize_t ad9910_drg_roc_write(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ u64 tmp64;
+ u32 rate;
+ int ret;
+
+ ret = kstrtou64(buf, 10, &tmp64);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&st->lock);
+
+ switch (chan->channel) {
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ rate = FIELD_GET(AD9910_DRG_RATE_INC_MSK,
+ st->reg[AD9910_REG_DRG_RATE].val32);
+ if (!rate)
+ return -ERANGE;
+
+ tmp64 = ad9910_rational_scale(tmp64, 4 * rate, st->data.sysclk_freq_hz);
+ tmp64 = min_t(u64, tmp64, U32_MAX);
+
+ ret = ad9910_reg64_update(st, AD9910_REG_DRG_STEP,
+ AD9910_DRG_STEP_INC_MSK,
+ FIELD_PREP(AD9910_DRG_STEP_INC_MSK, tmp64),
+ true);
+ if (ret)
+ return ret;
+ break;
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ rate = FIELD_GET(AD9910_DRG_RATE_DEC_MSK,
+ st->reg[AD9910_REG_DRG_RATE].val32);
+ if (!rate)
+ return -ERANGE;
+
+ tmp64 = ad9910_rational_scale(tmp64, 4 * rate, st->data.sysclk_freq_hz);
+ tmp64 = min_t(u64, tmp64, U32_MAX);
+
+ ret = ad9910_reg64_update(st, AD9910_REG_DRG_STEP,
+ AD9910_DRG_STEP_DEC_MSK,
+ FIELD_PREP(AD9910_DRG_STEP_DEC_MSK, tmp64),
+ true);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return len;
+}
+
+static const u32 ad9910_osk_raw_step[] = {
+ 0, /* no step: manual mode (NOT pin controlled) */
+ 1, /* step size 1: automatic mode (pin controlled) */
+ 2, /* step size 2: automatic mode (pin controlled) */
+ 4, /* step size 4: automatic mode (pin controlled) */
+ 8, /* step size 8: automatic mode (pin controlled) */
+ GENMASK(13, 0), /* max step: manual mode (pin controlled) */
+};
+
+static ssize_t ad9910_osk_attrs_read(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ bool auto_en, pinctrl_en;
+ u32 rate, step;
+ u64 roc64;
+
+ guard(mutex)(&st->lock);
+
+ rate = FIELD_GET(AD9910_ASF_RAMP_RATE_MSK, st->reg[AD9910_REG_ASF].val32);
+ if (!rate)
+ return -ERANGE;
+
+ switch (private) {
+ case AD9910_ROC:
+ auto_en = FIELD_GET(AD9910_CFR1_SELECT_AUTO_OSK_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ pinctrl_en = FIELD_GET(AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ if (auto_en) {
+ step = FIELD_GET(AD9910_ASF_STEP_SIZE_MSK,
+ st->reg[AD9910_REG_ASF].val32);
+ step = ad9910_osk_raw_step[step + 1];
+ } else if (pinctrl_en) {
+ step = ad9910_osk_raw_step[ARRAY_SIZE(ad9910_osk_raw_step) - 1];
+ } else {
+ step = ad9910_osk_raw_step[0];
+ }
+
+ roc64 = div_u64((u64)step * st->data.sysclk_freq_hz, 4 * rate);
+ return sysfs_emit(buf, "%llu\n", roc64);
+ case AD9910_ROC_AVAIL: {
+ ssize_t len = 0;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(ad9910_osk_raw_step); i++) {
+ roc64 = div_u64((u64)ad9910_osk_raw_step[i] * st->data.sysclk_freq_hz,
+ 4 * rate);
+ len += sysfs_emit_at(buf, len, "%llu ", roc64);
+ }
+
+ buf[len - 1] = '\n'; /* replace last space with a newline */
+ return len;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static ssize_t ad9910_osk_attrs_write(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ u32 idx, reg_val, rate;
+ u64 step;
+ int ret;
+
+ ret = kstrtou64(buf, 10, &step);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&st->lock);
+
+ rate = FIELD_GET(AD9910_ASF_RAMP_RATE_MSK, st->reg[AD9910_REG_ASF].val32);
+ if (!rate)
+ return -ERANGE;
+
+ switch (private) {
+ case AD9910_ROC:
+ step = ad9910_rational_scale(step, 4 * rate,
+ st->data.sysclk_freq_hz);
+ step = min(step, AD9910_ASF_MAX);
+ idx = find_closest(step, ad9910_osk_raw_step,
+ ARRAY_SIZE(ad9910_osk_raw_step));
+ if (idx == ARRAY_SIZE(ad9910_osk_raw_step) - 1) {
+ /* manual mode: pin-controlled */
+ reg_val = AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK;
+ } else if (idx == 0) {
+ /* manual mode, NOT pin-controlled */
+ reg_val = 0;
+ } else {
+ /* auto mode: pin-controlled */
+ reg_val = FIELD_PREP(AD9910_ASF_STEP_SIZE_MSK, idx - 1);
+ ret = ad9910_reg32_update(st, AD9910_REG_ASF,
+ AD9910_ASF_STEP_SIZE_MSK,
+ reg_val, false);
+ if (ret)
+ return ret;
+
+ reg_val = AD9910_CFR1_SELECT_AUTO_OSK_MSK;
+ }
+
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_SELECT_AUTO_OSK_MSK |
+ AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK,
+ reg_val, true);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return len;
+}
+
+static const struct iio_chan_spec_ext_info ad9910_phy_ext_info[] = {
+ {
+ .name = "powerdown",
+ .read = ad9910_ext_info_read,
+ .write = ad9910_ext_info_write,
+ .private = AD9910_POWERDOWN,
+ .shared = IIO_SEPARATE,
+ },
+ { }
+};
+
+static const struct iio_chan_spec_ext_info ad9910_drg_ramp_ext_info[] = {
+ {
+ .name = "dwell_en",
+ .read = ad9910_ext_info_read,
+ .write = ad9910_ext_info_write,
+ .private = AD9910_DWELL_EN,
+ .shared = IIO_SEPARATE,
+ },
+ {
+ .name = "raw_roc",
+ .read = ad9910_drg_roc_read,
+ .write = ad9910_drg_roc_write,
+ .private = AD9910_ROC,
+ .shared = IIO_SEPARATE,
+ },
+ { }
+};
+
+static const struct iio_chan_spec_ext_info ad9910_osk_ext_info[] = {
+ {
+ .name = "raw_roc",
+ .read = ad9910_osk_attrs_read,
+ .write = ad9910_osk_attrs_write,
+ .private = AD9910_ROC,
+ .shared = IIO_SEPARATE,
+ },
+ {
+ .name = "raw_roc_available",
+ .read = ad9910_osk_attrs_read,
+ .private = AD9910_ROC_AVAIL,
+ .shared = IIO_SEPARATE,
+ },
+ { }
+};
+
+#define AD9910_PROFILE_CHAN(idx) { \
+ .type = IIO_ALTCURRENT, \
+ .indexed = 1, \
+ .output = 1, \
+ .channel = AD9910_CHANNEL_PROFILE_ ## idx, \
+ .address = AD9910_CHAN_IDX_PROFILE_ ## idx, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | \
+ BIT(IIO_CHAN_INFO_FREQUENCY) | \
+ BIT(IIO_CHAN_INFO_PHASE) | \
+ BIT(IIO_CHAN_INFO_RAW), \
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], \
+}
+
+static const struct iio_chan_spec ad9910_channels[] = {
+ [AD9910_CHAN_IDX_PHY] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_PHY,
+ .address = AD9910_CHAN_IDX_PHY,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+ .ext_info = ad9910_phy_ext_info,
+ },
+ [AD9910_CHAN_IDX_PROFILE_0] = AD9910_PROFILE_CHAN(0),
+ [AD9910_CHAN_IDX_PROFILE_1] = AD9910_PROFILE_CHAN(1),
+ [AD9910_CHAN_IDX_PROFILE_2] = AD9910_PROFILE_CHAN(2),
+ [AD9910_CHAN_IDX_PROFILE_3] = AD9910_PROFILE_CHAN(3),
+ [AD9910_CHAN_IDX_PROFILE_4] = AD9910_PROFILE_CHAN(4),
+ [AD9910_CHAN_IDX_PROFILE_5] = AD9910_PROFILE_CHAN(5),
+ [AD9910_CHAN_IDX_PROFILE_6] = AD9910_PROFILE_CHAN(6),
+ [AD9910_CHAN_IDX_PROFILE_7] = AD9910_PROFILE_CHAN(7),
+ [AD9910_CHAN_IDX_PARALLEL_AMP] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_PARALLEL,
+ .address = AD9910_CHAN_IDX_PARALLEL_AMP,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_PARALLEL_PHASE] = {
+ .type = IIO_PHASE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_PARALLEL,
+ .address = AD9910_CHAN_IDX_PARALLEL_PHASE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_PARALLEL_FREQ] = {
+ .type = IIO_FREQUENCY,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_PARALLEL,
+ .address = AD9910_CHAN_IDX_PARALLEL_FREQ,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_PARALLEL_POLAR_AMP] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_PARALLEL_POLAR,
+ .address = AD9910_CHAN_IDX_PARALLEL_POLAR_AMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE] = {
+ .type = IIO_PHASE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_PARALLEL_POLAR,
+ .address = AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_DRG_FREQ] = {
+ .type = IIO_FREQUENCY,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG,
+ .address = AD9910_CHAN_IDX_DRG_FREQ,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_DRG_PHASE] = {
+ .type = IIO_PHASE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG,
+ .address = AD9910_CHAN_IDX_DRG_PHASE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_DRG_AMP] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG,
+ .address = AD9910_CHAN_IDX_DRG_AMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP] = {
+ .type = IIO_FREQUENCY,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG_RAMP_UP,
+ .address = AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_drg_ramp_ext_info,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_FREQ],
+ },
+ [AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN] = {
+ .type = IIO_FREQUENCY,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
+ .address = AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_drg_ramp_ext_info,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_FREQ],
+ },
+ [AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP] = {
+ .type = IIO_PHASE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG_RAMP_UP,
+ .address = AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_drg_ramp_ext_info,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_PHASE],
+ },
+ [AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN] = {
+ .type = IIO_PHASE,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
+ .address = AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_drg_ramp_ext_info,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_PHASE],
+ },
+ [AD9910_CHAN_IDX_DRG_AMP_RAMP_UP] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG_RAMP_UP,
+ .address = AD9910_CHAN_IDX_DRG_AMP_RAMP_UP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_drg_ramp_ext_info,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_AMP],
+ },
+ [AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
+ .address = AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_drg_ramp_ext_info,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_AMP],
+ },
+ [AD9910_CHAN_IDX_RAM] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_RAM,
+ .address = AD9910_CHAN_IDX_RAM,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_FREQUENCY) |
+ BIT(IIO_CHAN_INFO_PHASE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+ [AD9910_CHAN_IDX_OSK] = {
+ .type = IIO_ALTCURRENT,
+ .indexed = 1,
+ .output = 1,
+ .channel = AD9910_CHANNEL_OSK,
+ .address = AD9910_CHAN_IDX_OSK,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .ext_info = ad9910_osk_ext_info,
+ .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY],
+ },
+};
+
+static int ad9910_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long info)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ u64 tmp64;
+ u32 tmp32;
+
+ guard(mutex)(&st->lock);
+
+ switch (info) {
+ case IIO_CHAN_INFO_ENABLE:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ if (ad9910_sw_powerdown_get(st)) {
+ *val = 0;
+ } else {
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+ *val = (tmp32 == st->profile);
+ }
+ break;
+ case AD9910_CHANNEL_DRG:
+ tmp32 = FIELD_GET(AD9910_CFR2_DRG_DEST_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ if (tmp32 == (chan->address - AD9910_CHAN_IDX_DRG_FREQ))
+ *val = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ else
+ *val = 0;
+ break;
+ case AD9910_CHANNEL_RAM:
+ *val = FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ break;
+ case AD9910_CHANNEL_OSK:
+ *val = FIELD_GET(AD9910_CFR1_OSK_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_FREQUENCY:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+ tmp64 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
+ ad9910_st_profile_val(st, tmp32));
+ break;
+ case AD9910_CHANNEL_RAM:
+ tmp64 = st->reg[AD9910_REG_FTW].val32;
+ break;
+ default:
+ return -EINVAL;
+ }
+ tmp64 *= st->data.sysclk_freq_hz;
+ *val = tmp64 >> 32;
+ *val2 = ((tmp64 & GENMASK_ULL(31, 0)) * MICRO) >> 32;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_PHASE:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+ tmp64 = FIELD_GET(AD9910_PROFILE_ST_POW_MSK,
+ ad9910_st_profile_val(st, tmp32));
+ break;
+ case AD9910_CHANNEL_RAM:
+ tmp64 = st->reg[AD9910_REG_POW].val16;
+ break;
+ default:
+ return -EINVAL;
+ }
+ tmp32 = (tmp64 * AD9910_MAX_PHASE_MICRORAD) >> 16;
+ *val = tmp32 / MICRO;
+ *val2 = tmp32 % MICRO;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+ *val = FIELD_GET(AD9910_PROFILE_ST_ASF_MSK,
+ ad9910_st_profile_val(st, tmp32));
+ return IIO_VAL_INT;
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ tmp64 = FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK,
+ st->reg[AD9910_REG_DRG_LIMIT].val64);
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_INT_64;
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ tmp64 = FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK,
+ st->reg[AD9910_REG_DRG_LIMIT].val64);
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_INT_64;
+ case AD9910_CHANNEL_OSK:
+ *val = FIELD_GET(AD9910_ASF_SCALE_FACTOR_MSK,
+ st->reg[AD9910_REG_ASF].val32);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PHY:
+ *val = st->data.sysclk_freq_hz;
+ return IIO_VAL_INT;
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ tmp32 = FIELD_GET(AD9910_DRG_RATE_INC_MSK,
+ st->reg[AD9910_REG_DRG_RATE].val32);
+ break;
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ tmp32 = FIELD_GET(AD9910_DRG_RATE_DEC_MSK,
+ st->reg[AD9910_REG_DRG_RATE].val32);
+ break;
+ case AD9910_CHANNEL_RAM:
+ tmp32 = FIELD_GET(AD9910_PROFILE_RAM_STEP_RATE_MSK,
+ ad9910_ram_profile_val(st));
+ break;
+ case AD9910_CHANNEL_OSK:
+ tmp32 = FIELD_GET(AD9910_ASF_RAMP_RATE_MSK,
+ st->reg[AD9910_REG_ASF].val32);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (!tmp32)
+ return -ERANGE;
+ tmp32 *= 4;
+ *val = st->data.sysclk_freq_hz / tmp32;
+ *val2 = div_u64((u64)(st->data.sysclk_freq_hz % tmp32) * MICRO, tmp32);
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->address) {
+ case AD9910_CHAN_IDX_PHY:
+ tmp64 = (u64)st->data.output_current_uA *
+ AD9910_NANO_MILLIAMP_PER_MICROAMP;
+ *val = 0;
+ *val2 = tmp64 >> 14;
+ return IIO_VAL_INT_PLUS_NANO;
+ case AD9910_CHAN_IDX_PARALLEL_PHASE:
+ *val = 0;
+ *val2 = AD9910_PI_NANORAD >> 15;
+ return IIO_VAL_INT_PLUS_NANO;
+ case AD9910_CHAN_IDX_PARALLEL_FREQ:
+ tmp32 = FIELD_GET(AD9910_CFR2_FM_GAIN_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ tmp64 = (u64)st->data.sysclk_freq_hz << tmp32;
+ tmp64 = ad9910_rational_scale(tmp64, NANO, BIT_ULL(32));
+ *val = div_s64_rem(tmp64, NANO, val2);
+ return IIO_VAL_INT_PLUS_NANO;
+ case AD9910_CHAN_IDX_PARALLEL_POLAR_AMP:
+ tmp64 = (u64)st->data.output_current_uA *
+ AD9910_NANO_MILLIAMP_PER_MICROAMP;
+ *val = 0;
+ *val2 = tmp64 >> 8;
+ return IIO_VAL_INT_PLUS_NANO;
+ case AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE:
+ *val = 0;
+ *val2 = AD9910_PI_NANORAD >> 7;
+ return IIO_VAL_INT_PLUS_NANO;
+ case AD9910_CHAN_IDX_DRG_FREQ:
+ tmp64 = ad9910_rational_scale(st->data.sysclk_freq_hz,
+ PICO, BIT_ULL(32));
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_DECIMAL64_PICO;
+ case AD9910_CHAN_IDX_DRG_PHASE:
+ tmp64 = DIV_U64_ROUND_CLOSEST(AD9910_PI_PICORAD, BIT(31));
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_DECIMAL64_PICO;
+ case AD9910_CHAN_IDX_DRG_AMP:
+ tmp64 = (u64)st->data.output_current_uA *
+ AD9910_PICO_MILLIAMP_PER_MICROAMP;
+ tmp64 = DIV_U64_ROUND_CLOSEST(tmp64 >> 1, BIT(31));
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_DECIMAL64_PICO;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ switch (chan->address) {
+ case AD9910_CHAN_IDX_PARALLEL_FREQ:
+ tmp64 = (u64)st->reg[AD9910_REG_FTW].val32 * MICRO;
+ tmp64 >>= FIELD_GET(AD9910_CFR2_FM_GAIN_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_DECIMAL64_MICRO;
+ case AD9910_CHAN_IDX_PARALLEL_POLAR_AMP:
+ tmp32 = FIELD_GET(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK,
+ st->reg[AD9910_REG_ASF].val32);
+ iio_val_s64_decompose(MICRO * tmp32 >> 6, val, val2);
+ return IIO_VAL_DECIMAL64_MICRO;
+ case AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE:
+ tmp32 = FIELD_GET(AD9910_POW_PP_LSB_MSK,
+ st->reg[AD9910_REG_POW].val16);
+ iio_val_s64_decompose(MICRO * tmp32 >> 8, val, val2);
+ return IIO_VAL_DECIMAL64_MICRO;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad9910_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long info)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ u64 tmp64;
+ u32 tmp32;
+ int ret, i;
+
+ guard(mutex)(&st->lock);
+
+ switch (info) {
+ case IIO_CHAN_INFO_ENABLE:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+ if (!val) {
+ if (tmp32 != st->profile)
+ return 0; /* nothing to do */
+
+ return ad9910_sw_powerdown_set(st, true);
+ }
+
+ ret = ad9910_sw_powerdown_set(st, false);
+ if (ret)
+ return ret;
+
+ return ad9910_profile_set(st, tmp32);
+ case AD9910_CHANNEL_DRG:
+ tmp32 = chan->address - AD9910_CHAN_IDX_DRG_FREQ;
+ if (val) {
+ tmp32 = AD9910_CFR2_DRG_ENABLE_MSK |
+ FIELD_PREP(AD9910_CFR2_DRG_DEST_MSK, tmp32);
+ } else {
+ if (tmp32 != FIELD_GET(AD9910_CFR2_DRG_DEST_MSK,
+ st->reg[AD9910_REG_CFR2].val32))
+ return 0; /* nothing to do */
+ tmp32 = 0;
+ }
+
+ return ad9910_reg32_update(st, AD9910_REG_CFR2,
+ AD9910_CFR2_DRG_DEST_MSK |
+ AD9910_CFR2_DRG_ENABLE_MSK,
+ tmp32, true);
+ case AD9910_CHANNEL_RAM:
+ if (AD9910_RAM_ENABLED(st) == !!val)
+ return 0;
+
+ /* swap profile configs */
+ for (i = 0; i < AD9910_NUM_PROFILES; i++) {
+ tmp64 = st->reg[AD9910_REG_PROFILE(i)].val64;
+ ret = ad9910_reg64_write(st,
+ AD9910_REG_PROFILE(i),
+ st->reg_profile[i],
+ false);
+ if (ret)
+ break;
+ st->reg_profile[i] = tmp64;
+ }
+
+ if (ret) {
+ /*
+ * After the write failure, profiles 0..i-1 were
+ * already swapped in SW, but Hw registers are
+ * still pending an IO update, so swap them back
+ * in SW to keep the state consistent.
+ */
+ while (i--) {
+ tmp64 = st->reg[AD9910_REG_PROFILE(i)].val64;
+ st->reg[AD9910_REG_PROFILE(i)].val64 = st->reg_profile[i];
+ st->reg_profile[i] = tmp64;
+ }
+ return ret;
+ }
+
+ tmp32 = FIELD_PREP(AD9910_CFR1_RAM_ENABLE_MSK, !!val);
+ return ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_RAM_ENABLE_MSK,
+ tmp32, true);
+ case AD9910_CHANNEL_OSK:
+ tmp32 = FIELD_PREP(AD9910_CFR1_OSK_ENABLE_MSK, !!val);
+ return ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_OSK_ENABLE_MSK,
+ tmp32, true);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_FREQUENCY:
+ if (val < 0 || val2 < 0 || val >= st->data.sysclk_freq_hz / 2)
+ return -EINVAL;
+
+ tmp64 = ad9910_rational_scale((u64)val * MICRO + val2, BIT_ULL(32),
+ (u64)MICRO * st->data.sysclk_freq_hz);
+ tmp64 = min_t(u64, tmp64, U32_MAX);
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+ if (AD9910_RAM_ENABLED(st)) {
+ FIELD_MODIFY(AD9910_PROFILE_ST_FTW_MSK,
+ &st->reg_profile[tmp32], tmp64);
+ return 0;
+ }
+ tmp64 = FIELD_PREP(AD9910_PROFILE_ST_FTW_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32),
+ AD9910_PROFILE_ST_FTW_MSK,
+ tmp64, true);
+ case AD9910_CHANNEL_RAM:
+ return ad9910_reg32_write(st, AD9910_REG_FTW, tmp64, true);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_PHASE:
+ if (val < 0 || val2 < 0)
+ return -EINVAL;
+
+ tmp64 = (u64)val * MICRO + val2;
+ if (tmp64 >= AD9910_MAX_PHASE_MICRORAD)
+ return -EINVAL;
+
+ tmp64 = DIV_U64_ROUND_CLOSEST(tmp64 << 16, AD9910_MAX_PHASE_MICRORAD);
+ tmp64 = min(tmp64, AD9910_POW_MAX);
+
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+
+ if (AD9910_RAM_ENABLED(st)) {
+ FIELD_MODIFY(AD9910_PROFILE_ST_POW_MSK,
+ &st->reg_profile[tmp32], tmp64);
+ return 0;
+ }
+
+ tmp64 = FIELD_PREP(AD9910_PROFILE_ST_POW_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32),
+ AD9910_PROFILE_ST_POW_MSK,
+ tmp64, true);
+ case AD9910_CHANNEL_RAM:
+ return ad9910_reg16_write(st, AD9910_REG_POW, tmp64, true);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ if (val < 0)
+ return -EINVAL;
+
+ tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+ tmp64 = min_t(u64, val, AD9910_ASF_MAX);
+
+ if (AD9910_RAM_ENABLED(st)) {
+ FIELD_MODIFY(AD9910_PROFILE_ST_ASF_MSK,
+ &st->reg_profile[tmp32], tmp64);
+ return 0;
+ }
+
+ tmp64 = FIELD_PREP(AD9910_PROFILE_ST_ASF_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32),
+ AD9910_PROFILE_ST_ASF_MSK,
+ tmp64, true);
+ case AD9910_CHANNEL_DRG_RAMP_UP: {
+ s64 val64 = iio_val_s64_compose(val, val2);
+
+ if (val64 < 0)
+ return -EINVAL;
+
+ tmp64 = min_t(u64, val64, U32_MAX);
+ tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_UPPER_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+ AD9910_DRG_LIMIT_UPPER_MSK,
+ tmp64, true);
+ }
+ case AD9910_CHANNEL_DRG_RAMP_DOWN: {
+ s64 val64 = iio_val_s64_compose(val, val2);
+
+ if (val64 < 0)
+ return -EINVAL;
+
+ tmp64 = min_t(u64, val64, U32_MAX);
+ tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_LOWER_MSK, tmp64);
+ return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+ AD9910_DRG_LIMIT_LOWER_MSK,
+ tmp64, true);
+ }
+ case AD9910_CHANNEL_OSK:
+ if (val < 0)
+ return -EINVAL;
+
+ tmp32 = min_t(u32, val, AD9910_ASF_MAX);
+ tmp32 = FIELD_PREP(AD9910_ASF_SCALE_FACTOR_MSK, tmp32);
+ return ad9910_reg32_update(st, AD9910_REG_ASF,
+ AD9910_ASF_SCALE_FACTOR_MSK,
+ tmp32, true);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (chan->channel == AD9910_CHANNEL_PHY)
+ return ad9910_set_sysclk_freq(st, val, true);
+
+ if (val < 0 || val2 < 0 || val > st->data.sysclk_freq_hz / 4)
+ return -EINVAL;
+
+ tmp64 = ((u64)val * MICRO + val2) * 4;
+ if (!tmp64)
+ return -EINVAL;
+
+ tmp64 = DIV64_U64_ROUND_CLOSEST((u64)st->data.sysclk_freq_hz * MICRO, tmp64);
+ tmp32 = clamp(tmp64, 1U, AD9910_STEP_RATE_MAX);
+
+ switch (chan->channel) {
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ tmp32 = FIELD_PREP(AD9910_DRG_RATE_INC_MSK, tmp32);
+ return ad9910_reg32_update(st, AD9910_REG_DRG_RATE,
+ AD9910_DRG_RATE_INC_MSK,
+ tmp32, true);
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ tmp32 = FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, tmp32);
+ return ad9910_reg32_update(st, AD9910_REG_DRG_RATE,
+ AD9910_DRG_RATE_DEC_MSK,
+ tmp32, true);
+ case AD9910_CHANNEL_RAM:
+ if (!AD9910_RAM_ENABLED(st)) {
+ FIELD_MODIFY(AD9910_PROFILE_RAM_STEP_RATE_MSK,
+ &st->reg_profile[st->profile], tmp32);
+ return 0;
+ }
+
+ tmp64 = FIELD_PREP(AD9910_PROFILE_RAM_STEP_RATE_MSK, tmp32);
+ return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
+ AD9910_PROFILE_RAM_STEP_RATE_MSK,
+ tmp64, true);
+ case AD9910_CHANNEL_OSK:
+ return ad9910_reg32_update(st, AD9910_REG_ASF,
+ AD9910_ASF_RAMP_RATE_MSK,
+ FIELD_PREP(AD9910_ASF_RAMP_RATE_MSK, tmp32),
+ true);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->address) {
+ case AD9910_CHAN_IDX_PHY:
+ if (val != 0 || val2 < 0)
+ return -EINVAL;
+
+ tmp32 = DIV_U64_ROUND_CLOSEST((u64)val2 << 14,
+ AD9910_NANO_MILLIAMP_PER_MICROAMP);
+ return ad9910_set_dac_current(st, tmp32, true);
+ case AD9910_CHAN_IDX_PARALLEL_FREQ:
+ if (val < 0 || val2 < 0)
+ return -EINVAL;
+
+ tmp64 = ad9910_rational_scale((u64)val * NANO + val2, BIT_ULL(32),
+ (u64)st->data.sysclk_freq_hz * NANO);
+ tmp64 = roundup_pow_of_two(clamp(tmp64, 1ULL, BIT_ULL(15)));
+ tmp32 = FIELD_PREP(AD9910_CFR2_FM_GAIN_MSK, ilog2(tmp64));
+ return ad9910_reg32_update(st, AD9910_REG_CFR2,
+ AD9910_CFR2_FM_GAIN_MSK,
+ tmp32, true);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET: {
+ s64 val64 = iio_val_s64_compose(val, val2);
+
+ if (val64 < 0)
+ return -EINVAL;
+
+ tmp64 = val64;
+ switch (chan->address) {
+ case AD9910_CHAN_IDX_PARALLEL_FREQ:
+ tmp32 = BIT(FIELD_GET(AD9910_CFR2_FM_GAIN_MSK,
+ st->reg[AD9910_REG_CFR2].val32));
+ tmp64 = ad9910_rational_scale(tmp64, tmp32, MICRO);
+ tmp64 = min_t(u64, tmp64, U32_MAX);
+ return ad9910_reg32_write(st, AD9910_REG_FTW, tmp64, true);
+ case AD9910_CHAN_IDX_PARALLEL_POLAR_AMP:
+ tmp64 = ad9910_rational_scale(tmp64, BIT(6), MICRO);
+ tmp64 = min_t(u64, tmp64, AD9910_ASF_PP_LSB_MAX);
+ tmp32 = FIELD_PREP(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK, tmp64);
+ return ad9910_reg32_update(st, AD9910_REG_ASF,
+ AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK,
+ tmp32, true);
+ case AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE:
+ tmp64 = ad9910_rational_scale(tmp64, BIT(8), MICRO);
+ tmp64 = min_t(u64, tmp64, AD9910_POW_PP_LSB_MAX);
+ tmp32 = FIELD_PREP(AD9910_POW_PP_LSB_MSK, tmp64);
+ return ad9910_reg16_update(st, AD9910_REG_POW,
+ AD9910_POW_PP_LSB_MSK,
+ tmp32, true);
+ default:
+ return -EINVAL;
+ }
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_ENABLE:
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_FREQUENCY:
+ case IIO_CHAN_INFO_PHASE:
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+ case AD9910_CHANNEL_OSK:
+ return IIO_VAL_INT;
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ return IIO_VAL_INT_64;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (chan->channel == AD9910_CHANNEL_PHY)
+ return IIO_VAL_INT;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_CHAN_INFO_OFFSET:
+ return IIO_VAL_DECIMAL64_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad9910_debugfs_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg, u64 writeval,
+ u64 *readval)
+{
+ struct ad9910_state *st = iio_priv(indio_dev);
+ union ad9910_reg tmp;
+ int ret;
+
+ if (reg >= AD9910_REG_RAM)
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+
+ switch (reg) {
+ case AD9910_REG_DRG_LIMIT:
+ case AD9910_REG_DRG_STEP:
+ case AD9910_REG_PROFILE0 ... AD9910_REG_PROFILE7:
+ if (!readval)
+ return ad9910_reg64_write(st, reg, writeval, true);
+
+ ret = ad9910_reg64_read(st, reg, &tmp.val64);
+ if (ret)
+ return ret;
+ *readval = tmp.val64;
+ return 0;
+ case AD9910_REG_POW:
+ if (!readval)
+ return ad9910_reg16_write(st, reg, writeval, true);
+
+ ret = ad9910_reg16_read(st, reg, &tmp.val16);
+ if (ret)
+ return ret;
+ *readval = tmp.val16;
+ return 0;
+ default:
+ if (!readval)
+ return ad9910_reg32_write(st, reg, writeval, true);
+
+ ret = ad9910_reg32_read(st, reg, &tmp.val32);
+ if (ret)
+ return ret;
+ *readval = tmp.val32;
+ return 0;
+ }
+}
+
+static const char * const ad9910_channel_str[] = {
+ [AD9910_CHAN_IDX_PHY] = "phy",
+ [AD9910_CHAN_IDX_PROFILE_0] = "profile0",
+ [AD9910_CHAN_IDX_PROFILE_1] = "profile1",
+ [AD9910_CHAN_IDX_PROFILE_2] = "profile2",
+ [AD9910_CHAN_IDX_PROFILE_3] = "profile3",
+ [AD9910_CHAN_IDX_PROFILE_4] = "profile4",
+ [AD9910_CHAN_IDX_PROFILE_5] = "profile5",
+ [AD9910_CHAN_IDX_PROFILE_6] = "profile6",
+ [AD9910_CHAN_IDX_PROFILE_7] = "profile7",
+ [AD9910_CHAN_IDX_PARALLEL_AMP] = "parallel_amplitude",
+ [AD9910_CHAN_IDX_PARALLEL_PHASE] = "parallel_phase",
+ [AD9910_CHAN_IDX_PARALLEL_FREQ] = "parallel_frequency",
+ [AD9910_CHAN_IDX_PARALLEL_POLAR_AMP] = "parallel_polar_amplitude",
+ [AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE] = "parallel_polar_phase",
+ [AD9910_CHAN_IDX_DRG_FREQ] = "drg_frequency",
+ [AD9910_CHAN_IDX_DRG_PHASE] = "drg_phase",
+ [AD9910_CHAN_IDX_DRG_AMP] = "drg_amplitude",
+ [AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP] = "drg_rising_frequency",
+ [AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN] = "drg_falling_frequency",
+ [AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP] = "drg_rising_phase",
+ [AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN] = "drg_falling_phase",
+ [AD9910_CHAN_IDX_DRG_AMP_RAMP_UP] = "drg_rising_amplitude",
+ [AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN] = "drg_falling_amplitude",
+ [AD9910_CHAN_IDX_RAM] = "ram",
+ [AD9910_CHAN_IDX_OSK] = "osk",
+};
+
+static int ad9910_read_label(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ char *label)
+{
+ return sysfs_emit(label, "%s\n", ad9910_channel_str[chan->address]);
+}
+
+static enum fw_upload_err ad9910_ram_fwu_prepare(struct fw_upload *fw_upload,
+ const u8 *data, u32 size)
+{
+ struct ad9910_state *st = fw_upload->dd_handle;
+ const struct ad9910_ram_fw *fw_data = (const struct ad9910_ram_fw *)data;
+ size_t wcount, bcount;
+
+ scoped_guard(mutex, &st->lock) {
+ /* cancel requests before prepare() are ignored */
+ st->ram_fwu_cancel = false;
+ }
+
+ if (size < sizeof(struct ad9910_ram_fw))
+ return FW_UPLOAD_ERR_INVALID_SIZE;
+
+ if (get_unaligned_be32(&fw_data->magic) != AD9910_RAM_FW_MAGIC)
+ return FW_UPLOAD_ERR_FW_INVALID;
+
+ if (get_unaligned_be16(&fw_data->version) != AD9910_RAM_FW_V1)
+ return FW_UPLOAD_ERR_FW_INVALID;
+
+ wcount = get_unaligned_be16(&fw_data->wcount);
+ bcount = size - sizeof(struct ad9910_ram_fw);
+ if (wcount > AD9910_RAM_SIZE_MAX_WORDS ||
+ bcount != (wcount * AD9910_RAM_WORD_SIZE))
+ return FW_UPLOAD_ERR_INVALID_SIZE;
+
+ bcount += sizeof(fw_data->cfr1) + sizeof(fw_data->profiles);
+ if (crc32(0, &fw_data->cfr1, bcount) != get_unaligned_be32(&fw_data->crc))
+ return FW_UPLOAD_ERR_FW_INVALID;
+
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err ad9910_ram_fwu_write(struct fw_upload *fw_upload,
+ const u8 *data, u32 offset,
+ u32 size, u32 *written)
+{
+ const struct ad9910_ram_fw *fw_data = (const struct ad9910_ram_fw *)data;
+ struct ad9910_state *st = fw_upload->dd_handle;
+ int ret, ret2, idx, wcount;
+ u64 tmp64, backup;
+
+ if (offset != 0)
+ return FW_UPLOAD_ERR_INVALID_SIZE;
+
+ guard(mutex)(&st->lock);
+
+ if (st->ram_fwu_cancel)
+ return FW_UPLOAD_ERR_CANCELED;
+
+ if (AD9910_RAM_ENABLED(st))
+ return FW_UPLOAD_ERR_HW_ERROR;
+
+ for (idx = 0; idx < AD9910_NUM_PROFILES; idx++)
+ st->reg_profile[idx] = get_unaligned_be64(&fw_data->profiles[idx]) |
+ AD9910_PROFILE_RAM_OPEN_MSK;
+
+ ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
+ AD9910_CFR1_RAM_PLAYBACK_DEST_MSK |
+ AD9910_CFR1_INT_PROFILE_CTL_MSK,
+ get_unaligned_be32(&fw_data->cfr1), true);
+ if (ret)
+ return FW_UPLOAD_ERR_RW_ERROR;
+
+ wcount = get_unaligned_be16(&fw_data->wcount);
+ if (!wcount) {
+ *written = size;
+ return FW_UPLOAD_ERR_NONE; /* nothing else to write */
+ }
+
+ ret = ad9910_profile_set(st, st->profile);
+ if (ret)
+ return FW_UPLOAD_ERR_HW_ERROR;
+
+ /* backup profile register and update it with required address range */
+ backup = st->reg[AD9910_REG_PROFILE(st->profile)].val64;
+ tmp64 = AD9910_PROFILE_RAM_STEP_RATE_MSK |
+ FIELD_PREP(AD9910_PROFILE_RAM_START_ADDR_MSK, 0) |
+ FIELD_PREP(AD9910_PROFILE_RAM_END_ADDR_MSK, wcount - 1);
+ ret = ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), tmp64, true);
+ if (ret)
+ return FW_UPLOAD_ERR_RW_ERROR;
+
+ /* copy data words into spi buffer and transfer */
+ memcpy(&st->tx_buf[1], fw_data->words, wcount * AD9910_RAM_WORD_SIZE);
+ ret = ad9910_spi_write(st, AD9910_REG_RAM,
+ wcount * AD9910_RAM_WORD_SIZE, false);
+
+ /* restore active single tone profile regardless */
+ st->reg[AD9910_REG_PROFILE(st->profile)].val64 = backup;
+ ret2 = ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), backup, true);
+ if (ret || ret2)
+ return FW_UPLOAD_ERR_RW_ERROR;
+
+ *written = size;
+
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err ad9910_ram_fwu_poll_complete(struct fw_upload *fw_upload)
+{
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static void ad9910_ram_fwu_cancel(struct fw_upload *fw_upload)
+{
+ struct ad9910_state *st = fw_upload->dd_handle;
+
+ guard(mutex)(&st->lock);
+ st->ram_fwu_cancel = true;
+}
+
+static void ad9910_ram_fwu_unregister(void *data)
+{
+ firmware_upload_unregister(data);
+}
+
+static const struct fw_upload_ops ad9910_ram_fwu_ops = {
+ .prepare = ad9910_ram_fwu_prepare,
+ .write = ad9910_ram_fwu_write,
+ .poll_complete = ad9910_ram_fwu_poll_complete,
+ .cancel = ad9910_ram_fwu_cancel,
+};
+
+static const struct iio_info ad9910_info = {
+ .read_raw = ad9910_read_raw,
+ .write_raw = ad9910_write_raw,
+ .write_raw_get_fmt = ad9910_write_raw_get_fmt,
+ .read_label = ad9910_read_label,
+ .debugfs_reg64_access = &ad9910_debugfs_reg_access,
+};
+
+static int ad9910_cfg_sysclk(struct ad9910_state *st, bool update)
+{
+ u32 cfr3 = AD9910_CFR3_OPEN_MSK;
+ u32 tmp32;
+
+ cfr3 |= FIELD_PREP(AD9910_CFR3_DRV0_MSK, st->data.refclk_out_drv);
+
+ if (st->data.pll_enabled) {
+ tmp32 = st->data.pll_charge_pump_current - AD9910_ICP_MIN_uA;
+ tmp32 = DIV_ROUND_CLOSEST(tmp32, AD9910_ICP_STEP_uA);
+ cfr3 |= FIELD_PREP(AD9910_CFR3_ICP_MSK, tmp32) |
+ AD9910_CFR3_PLL_EN_MSK;
+ } else {
+ cfr3 |= AD9910_CFR3_ICP_MSK |
+ AD9910_CFR3_REFCLK_DIV_RESETB_MSK |
+ AD9910_CFR3_PFD_RESET_MSK;
+ }
+ st->reg[AD9910_REG_CFR3].val32 = cfr3;
+
+ return ad9910_set_sysclk_freq(st, AD9910_MAX_SYSCLK_HZ, update);
+}
+
+static int ad9910_parse_fw(struct ad9910_state *st)
+{
+ static const char * const refclk_out_drv0[] = {
+ "disabled", "low", "medium", "high",
+ };
+ struct device *dev = &st->spi->dev;
+ const char *prop;
+ u32 tmp;
+ int ret;
+
+ st->data.pll_enabled = device_property_read_bool(dev, "adi,pll-enable");
+ if (st->data.pll_enabled) {
+ prop = "adi,charge-pump-current-microamp";
+ if (device_property_present(dev, prop)) {
+ ret = device_property_read_u32(dev, prop, &tmp);
+ if (ret)
+ return dev_err_probe(dev, ret, "property read: %s\n", prop);
+
+ if (tmp < AD9910_ICP_MIN_uA || tmp > AD9910_ICP_MAX_uA)
+ return dev_err_probe(dev, -ERANGE,
+ "invalid charge pump current %u\n", tmp);
+ } else {
+ tmp = AD9910_ICP_MIN_uA;
+ }
+ st->data.pll_charge_pump_current = tmp;
+
+ prop = "adi,refclk-out-drive-strength";
+ if (device_property_present(dev, prop)) {
+ ret = device_property_match_property_string(dev, prop,
+ refclk_out_drv0,
+ ARRAY_SIZE(refclk_out_drv0));
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "property read: %s\n", prop);
+
+ st->data.refclk_out_drv = ret;
+ }
+ }
+
+ return 0;
+}
+
+static void ad9910_sw_powerdown_action(void *data)
+{
+ ad9910_sw_powerdown_set(data, true);
+}
+
+static void ad9910_hw_powerdown_action(void *data)
+{
+ struct ad9910_state *st = data;
+
+ gpiod_set_value_cansleep(st->gpio_pwdown, 1);
+}
+
+static int ad9910_setup(struct device *dev, struct ad9910_state *st,
+ struct reset_control *dev_rst)
+{
+ int ret;
+
+ ret = reset_control_assert(dev_rst);
+ if (ret)
+ return ret;
+
+ fsleep(AD9910_RESET_DELAY_us);
+
+ ret = reset_control_deassert(dev_rst);
+ if (ret)
+ return ret;
+
+ ret = ad9910_reg32_write(st, AD9910_REG_CFR1,
+ (st->spi->mode & SPI_3WIRE ? 0 :
+ AD9910_CFR1_SDIO_INPUT_ONLY_MSK), false);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, ad9910_sw_powerdown_action, st);
+ if (ret)
+ return ret;
+
+ ret = ad9910_reg32_write(st, AD9910_REG_CFR2,
+ AD9910_CFR2_AMP_SCALE_SINGLE_TONE_MSK |
+ AD9910_CFR2_SYNC_TIMING_VAL_DISABLE_MSK |
+ AD9910_CFR2_DRG_NO_DWELL_MSK |
+ AD9910_CFR2_DATA_ASM_HOLD_LAST_MSK |
+ AD9910_CFR2_SYNC_CLK_EN_MSK |
+ AD9910_CFR2_PDCLK_ENABLE_MSK, false);
+ if (ret)
+ return ret;
+
+ ret = ad9910_cfg_sysclk(st, false);
+ if (ret)
+ return ret;
+
+ ret = ad9910_set_dac_current(st, AD9910_DAC_IOUT_DEFAULT_uA, false);
+ if (ret)
+ return ret;
+
+ /* configure step rate with default values */
+ ret = ad9910_reg32_write(st, AD9910_REG_ASF,
+ FIELD_PREP(AD9910_ASF_RAMP_RATE_MSK, 1),
+ false);
+ if (ret)
+ return ret;
+
+ ret = ad9910_reg32_write(st, AD9910_REG_DRG_RATE,
+ FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, 1) |
+ FIELD_PREP(AD9910_DRG_RATE_INC_MSK, 1),
+ false);
+ if (ret)
+ return ret;
+
+ for (unsigned int i = 0; i < AD9910_NUM_PROFILES; i++) {
+ st->reg_profile[i] = AD9910_PROFILE_RAM_OPEN_MSK;
+ st->reg_profile[i] |= FIELD_PREP(AD9910_PROFILE_RAM_STEP_RATE_MSK, 1);
+ st->reg_profile[i] |= FIELD_PREP(AD9910_PROFILE_RAM_END_ADDR_MSK,
+ AD9910_RAM_ADDR_MAX);
+ }
+
+ return ad9910_io_update(st);
+}
+
+static inline const char *ad9910_frequency_source_get(struct ad9910_state *st)
+{
+ bool ram_en, mode_en;
+
+ guard(mutex)(&st->lock);
+
+ /* RAM enabled and data destination is frequency */
+ ram_en = AD9910_RAM_ENABLED(st);
+ if (ram_en && AD9910_DEST_FREQUENCY ==
+ FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK,
+ st->reg[AD9910_REG_CFR1].val32))
+ return ad9910_channel_str[AD9910_CHAN_IDX_RAM];
+
+ /* DRG enabled and data destination is frequency */
+ mode_en = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ if (mode_en && AD9910_DEST_FREQUENCY ==
+ FIELD_GET(AD9910_CFR2_DRG_DEST_MSK,
+ st->reg[AD9910_REG_CFR2].val32))
+ return ad9910_channel_str[AD9910_CHAN_IDX_DRG_FREQ];
+
+ /* FTW: RAM enabled and data destination is phase, amplitude, or polar */
+ if (ram_en)
+ return ad9910_channel_str[AD9910_CHAN_IDX_RAM];
+
+ /* single tone profiles */
+ return ad9910_channel_str[AD9910_CHAN_IDX_PROFILE_0 + st->profile];
+}
+
+static int ad9910_frequency_source_show(struct seq_file *s, void *ignored)
+{
+ seq_printf(s, "%s\n", ad9910_frequency_source_get(s->private));
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(ad9910_frequency_source);
+
+static inline const char *ad9910_phase_source_get(struct ad9910_state *st)
+{
+ bool ram_en, mode_en;
+ u32 destination;
+
+ guard(mutex)(&st->lock);
+
+ /* RAM enabled and data destination is phase or polar */
+ ram_en = AD9910_RAM_ENABLED(st);
+ if (ram_en) {
+ destination = FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ if (destination == AD9910_DEST_PHASE ||
+ destination == AD9910_DEST_POLAR)
+ return ad9910_channel_str[AD9910_CHAN_IDX_RAM];
+ }
+
+ /* DRG enabled and data destination is phase */
+ mode_en = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ if (mode_en && AD9910_DEST_PHASE ==
+ FIELD_GET(AD9910_CFR2_DRG_DEST_MSK,
+ st->reg[AD9910_REG_CFR2].val32))
+ return ad9910_channel_str[AD9910_CHAN_IDX_DRG_PHASE];
+
+ /* POW: RAM enabled and data destination is frequency or amplitude */
+ if (ram_en)
+ return ad9910_channel_str[AD9910_CHAN_IDX_RAM];
+
+ /* single tone profiles */
+ return ad9910_channel_str[AD9910_CHAN_IDX_PROFILE_0 + st->profile];
+}
+
+static int ad9910_phase_source_show(struct seq_file *s, void *ignored)
+{
+ seq_printf(s, "%s\n", ad9910_phase_source_get(s->private));
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(ad9910_phase_source);
+
+static inline const char *ad9910_amplitude_source_get(struct ad9910_state *st)
+{
+ bool ram_en, mode_en;
+ u32 destination;
+
+ guard(mutex)(&st->lock);
+
+ /* OSK enabled */
+ mode_en = FIELD_GET(AD9910_CFR1_OSK_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ if (mode_en)
+ return ad9910_channel_str[AD9910_CHAN_IDX_OSK];
+
+ /* RAM enabled and data destination is amplitude or polar */
+ ram_en = AD9910_RAM_ENABLED(st);
+ if (ram_en) {
+ destination = FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK,
+ st->reg[AD9910_REG_CFR1].val32);
+ if (destination == AD9910_DEST_AMPLITUDE ||
+ destination == AD9910_DEST_POLAR)
+ return ad9910_channel_str[AD9910_CHAN_IDX_RAM];
+ }
+
+ /* DRG enabled and data destination is amplitude */
+ mode_en = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ if (mode_en && AD9910_DEST_AMPLITUDE ==
+ FIELD_GET(AD9910_CFR2_DRG_DEST_MSK,
+ st->reg[AD9910_REG_CFR2].val32))
+ return ad9910_channel_str[AD9910_CHAN_IDX_DRG_AMP];
+
+ /* only way to control amplitude at this point is through OSK */
+ if (ram_en)
+ return ad9910_channel_str[AD9910_CHAN_IDX_OSK];
+
+ /* single tone profiles */
+ return ad9910_channel_str[AD9910_CHAN_IDX_PROFILE_0 + st->profile];
+}
+
+static int ad9910_amplitude_source_show(struct seq_file *s, void *ignored)
+{
+ seq_printf(s, "%s\n", ad9910_amplitude_source_get(s->private));
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(ad9910_amplitude_source);
+
+static inline void ad9910_debugfs_init(struct ad9910_state *st,
+ struct iio_dev *indio_dev)
+{
+ struct dentry *d = iio_get_debugfs_dentry(indio_dev);
+
+ debugfs_create_file("frequency_source", 0400, d, st,
+ &ad9910_frequency_source_fops);
+ debugfs_create_file("phase_source", 0400, d, st,
+ &ad9910_phase_source_fops);
+ debugfs_create_file("amplitude_source", 0400, d, st,
+ &ad9910_amplitude_source_fops);
+}
+
+static int ad9910_probe(struct spi_device *spi)
+{
+ static const char * const supplies[] = {
+ "dvdd-io33", "avdd33", "dvdd18", "avdd18",
+ };
+ struct device *dev = &spi->dev;
+ struct reset_control *dev_rst;
+ struct gpio_desc *io_rst_gpio;
+ struct iio_dev *indio_dev;
+ struct ad9910_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 = "ad9910";
+ indio_dev->info = &ad9910_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = ad9910_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ad9910_channels);
+
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(supplies), supplies);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get regulators\n");
+
+ st->refclk = devm_clk_get_enabled(dev, "ref_clk");
+ if (IS_ERR(st->refclk))
+ return dev_err_probe(dev, PTR_ERR(st->refclk),
+ "Failed to get reference clock\n");
+
+ dev_rst = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(dev_rst))
+ return dev_err_probe(dev, PTR_ERR(dev_rst),
+ "failed to get device reset control\n");
+
+ /*
+ * The IO RESET pin is not used in this driver, as we assume that all
+ * SPI transfers are complete, but if it is wired up, we need to make
+ * sure it is not floating. We can use either a reset controller or a
+ * GPIO for this.
+ */
+ io_rst_gpio = devm_gpiod_get_optional(dev, "io-reset", GPIOD_OUT_LOW);
+ if (IS_ERR(io_rst_gpio))
+ return dev_err_probe(dev, PTR_ERR(io_rst_gpio),
+ "failed to get io reset gpio\n");
+
+ st->gpio_update = devm_gpiod_get_optional(dev, "update", GPIOD_OUT_LOW);
+ if (IS_ERR(st->gpio_update))
+ return dev_err_probe(dev, PTR_ERR(st->gpio_update),
+ "failed to get update gpio\n");
+
+ st->gpio_profile = devm_gpiod_get_array_optional(dev, "profile",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(st->gpio_profile))
+ return dev_err_probe(dev, PTR_ERR(st->gpio_profile),
+ "failed to get profile gpios\n");
+
+ if (st->gpio_profile && st->gpio_profile->ndescs != 3)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid number of profile gpios\n");
+
+ st->gpio_pwdown = devm_gpiod_get_optional(dev, "powerdown",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(st->gpio_pwdown))
+ return dev_err_probe(dev, PTR_ERR(st->gpio_pwdown),
+ "failed to get powerdown gpio\n");
+
+ ret = devm_add_action_or_reset(dev, ad9910_hw_powerdown_action, st);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to add hw powerdown action\n");
+
+ fsleep(AD9910_WAKEUP_DELAY_us);
+
+ ret = ad9910_parse_fw(st);
+ if (ret)
+ return ret;
+
+ ret = ad9910_setup(dev, st, dev_rst);
+ if (ret)
+ return dev_err_probe(dev, ret, "device setup failed\n");
+
+ snprintf(st->ram_fwu_name, sizeof(st->ram_fwu_name), "%s:ram",
+ dev_name(&indio_dev->dev));
+ st->ram_fwu = firmware_upload_register(THIS_MODULE, dev, st->ram_fwu_name,
+ &ad9910_ram_fwu_ops, st);
+ if (IS_ERR(st->ram_fwu))
+ return dev_err_probe(dev, PTR_ERR(st->ram_fwu),
+ "failed to register ram upload ops\n");
+
+ ret = devm_add_action_or_reset(dev, ad9910_ram_fwu_unregister, st->ram_fwu);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to add ram upload unregister action\n");
+
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret)
+ return ret;
+
+ ad9910_debugfs_init(st, indio_dev);
+
+ return 0;
+}
+
+static const struct spi_device_id ad9910_id[] = {
+ { .name = "ad9910" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ad9910_id);
+
+static const struct of_device_id ad9910_of_match[] = {
+ { .compatible = "adi,ad9910" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad9910_of_match);
+
+static struct spi_driver ad9910_driver = {
+ .driver = {
+ .name = "ad9910",
+ .of_match_table = ad9910_of_match,
+ },
+ .probe = ad9910_probe,
+ .id_table = ad9910_id,
+};
+module_spi_driver(ad9910_driver);
+
+MODULE_AUTHOR("Rodrigo Alencar <rodrigo.alencar@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD9910 DDS driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/frequency/adf4350.c b/drivers/iio/frequency/adf4350.c
index 315317d6eec4..ec45c0f65fe3 100644
--- a/drivers/iio/frequency/adf4350.c
+++ b/drivers/iio/frequency/adf4350.c
@@ -461,7 +461,7 @@ static const struct clk_ops adf4350_clk_ops = {
static int adf4350_clk_register(struct adf4350_state *st)
{
struct spi_device *spi = st->spi;
- struct clk_init_data init;
+ struct clk_init_data init = { };
struct clk *clk;
const char *parent_name;
int ret;
diff --git a/drivers/iio/frequency/adf4377.c b/drivers/iio/frequency/adf4377.c
index 7385be62fa6a..52266f83877a 100644
--- a/drivers/iio/frequency/adf4377.c
+++ b/drivers/iio/frequency/adf4377.c
@@ -1060,6 +1060,9 @@ static int adf4377_probe(struct spi_device *spi)
st->regmap = regmap;
st->spi = spi;
st->chip_info = spi_get_device_match_data(spi);
+ if (!st->chip_info)
+ return -ENODATA;
+
mutex_init(&st->lock);
ret = adf4377_properties_parse(st);
diff --git a/drivers/iio/gyro/adxrs290.c b/drivers/iio/gyro/adxrs290.c
index 563de2724396..b61cc81122a6 100644
--- a/drivers/iio/gyro/adxrs290.c
+++ b/drivers/iio/gyro/adxrs290.c
@@ -502,8 +502,8 @@ static irqreturn_t adxrs290_trigger_handler(int irq, void *p)
if (ret < 0)
break;
- iio_push_to_buffers_with_timestamp(indio_dev, &st->buffer,
- pf->timestamp);
+ iio_push_to_buffers_with_ts(indio_dev, &st->buffer,
+ sizeof(st->buffer), pf->timestamp);
} while (0);
iio_trigger_notify_done(indio_dev->trig);
diff --git a/drivers/iio/gyro/bmg160_core.c b/drivers/iio/gyro/bmg160_core.c
index d611341a0e2a..b007f46d3fd6 100644
--- a/drivers/iio/gyro/bmg160_core.c
+++ b/drivers/iio/gyro/bmg160_core.c
@@ -897,8 +897,8 @@ static irqreturn_t bmg160_trigger_handler(int irq, void *p)
if (ret < 0)
goto err;
- iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
- pf->timestamp);
+ iio_push_to_buffers_with_ts(indio_dev, &data->scan,
+ sizeof(data->scan), pf->timestamp);
err:
iio_trigger_notify_done(indio_dev->trig);
diff --git a/drivers/iio/gyro/itg3200_buffer.c b/drivers/iio/gyro/itg3200_buffer.c
index 87efa2c74ca4..426d04d496b4 100644
--- a/drivers/iio/gyro/itg3200_buffer.c
+++ b/drivers/iio/gyro/itg3200_buffer.c
@@ -59,7 +59,8 @@ static irqreturn_t itg3200_trigger_handler(int irq, void *p)
if (ret < 0)
goto error_ret;
- iio_push_to_buffers_with_timestamp(indio_dev, &scan, pf->timestamp);
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
+ pf->timestamp);
error_ret:
iio_trigger_notify_done(indio_dev->trig);
diff --git a/drivers/iio/humidity/Kconfig b/drivers/iio/humidity/Kconfig
index 54f11f000b6f..13e267943f67 100644
--- a/drivers/iio/humidity/Kconfig
+++ b/drivers/iio/humidity/Kconfig
@@ -5,13 +5,15 @@
menu "Humidity sensors"
config AM2315
- tristate "Aosong AM2315 relative humidity and temperature sensor"
+ tristate "Aosong AM2315 and similar 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.
+ If you say yes here you get support for the Aosong relative
+ humidity and ambient temperature sensors:
+ - AM2315
+ - AM2320
This driver can also be built as a module. If so, the module will
be called am2315.
diff --git a/drivers/iio/humidity/am2315.c b/drivers/iio/humidity/am2315.c
index f29baa251f9f..5c7582f3a4f9 100644
--- a/drivers/iio/humidity/am2315.c
+++ b/drivers/iio/humidity/am2315.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
- * Aosong AM2315 relative humidity and temperature
+ * Aosong AM2315 and similar relative humidity and temperature
*
* Copyright (c) 2016, Intel Corporation.
*
@@ -27,7 +27,17 @@
#define AM2315_TEMP_OFFSET 4
#define AM2315_ALL_CHANNEL_MASK GENMASK(1, 0)
-#define AM2315_DRIVER_NAME "am2315"
+struct am2315_chip_info {
+ const char *name;
+};
+
+static const struct am2315_chip_info am2315_chip_info = {
+ .name = "am2315",
+};
+
+static const struct am2315_chip_info am2320_chip_info = {
+ .name = "am2320",
+};
struct am2315_data {
struct i2c_client *client;
@@ -220,9 +230,14 @@ static const struct iio_info am2315_info = {
static int am2315_probe(struct i2c_client *client)
{
int ret;
+ const struct am2315_chip_info *chip_info;
struct iio_dev *indio_dev;
struct am2315_data *data;
+ chip_info = i2c_get_match_data(client);
+ if (!chip_info)
+ return -ENODATA;
+
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
@@ -233,7 +248,7 @@ static int am2315_probe(struct i2c_client *client)
mutex_init(&data->lock);
indio_dev->info = &am2315_info;
- indio_dev->name = AM2315_DRIVER_NAME;
+ indio_dev->name = chip_info->name;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = am2315_channels;
indio_dev->num_channels = ARRAY_SIZE(am2315_channels);
@@ -249,8 +264,16 @@ static int am2315_probe(struct i2c_client *client)
return devm_iio_device_register(&client->dev, indio_dev);
}
+static const struct of_device_id am2315_of_match[] = {
+ { .compatible = "aosong,am2315", .data = &am2315_chip_info },
+ { .compatible = "aosong,am2320", .data = &am2320_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, am2315_of_match);
+
static const struct i2c_device_id am2315_i2c_id[] = {
- { .name = "am2315" },
+ { .name = "am2315", .driver_data = (kernel_ulong_t)&am2315_chip_info },
+ { .name = "am2320", .driver_data = (kernel_ulong_t)&am2320_chip_info },
{ }
};
MODULE_DEVICE_TABLE(i2c, am2315_i2c_id);
@@ -258,6 +281,7 @@ MODULE_DEVICE_TABLE(i2c, am2315_i2c_id);
static struct i2c_driver am2315_driver = {
.driver = {
.name = "am2315",
+ .of_match_table = am2315_of_match,
},
.probe = am2315_probe,
.id_table = am2315_i2c_id,
@@ -266,5 +290,5 @@ static struct i2c_driver am2315_driver = {
module_i2c_driver(am2315_driver);
MODULE_AUTHOR("Tiberiu Breana <tiberiu.a.breana@intel.com>");
-MODULE_DESCRIPTION("Aosong AM2315 relative humidity and temperature");
+MODULE_DESCRIPTION("Aosong AM2315 and similar relative humidity and temperature");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/humidity/ens210.c b/drivers/iio/humidity/ens210.c
index 81276195152b..4bdd37e7227b 100644
--- a/drivers/iio/humidity/ens210.c
+++ b/drivers/iio/humidity/ens210.c
@@ -202,8 +202,8 @@ static int ens210_probe(struct i2c_client *client)
int ret;
if (!i2c_check_functionality(client->adapter,
- I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
- I2C_FUNC_SMBUS_WRITE_BYTE |
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_READ_WORD_DATA |
I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
return dev_err_probe(&client->dev, -EOPNOTSUPP,
"adapter does not support some i2c transactions\n");
diff --git a/drivers/iio/humidity/hts221_core.c b/drivers/iio/humidity/hts221_core.c
index bfeb0a60d3af..47a10884d812 100644
--- a/drivers/iio/humidity/hts221_core.c
+++ b/drivers/iio/humidity/hts221_core.c
@@ -10,7 +10,6 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
-#include <linux/iio/sysfs.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/regmap.h>
@@ -49,7 +48,7 @@ struct hts221_odr {
struct hts221_avg {
u8 addr;
u8 mask;
- u16 avg_avl[HTS221_AVG_DEPTH];
+ int avg_avl[HTS221_AVG_DEPTH];
};
static const struct hts221_odr hts221_odr_table[] = {
@@ -58,6 +57,8 @@ static const struct hts221_odr hts221_odr_table[] = {
{ 13, 0x03 }, /* 12.5Hz */
};
+static const int hts221_odr_avail[] = { 1, 7, 13 };
+
static const struct hts221_avg hts221_avg_list[] = {
{
.addr = 0x10,
@@ -97,7 +98,11 @@ static const struct iio_chan_spec hts221_channels[] = {
BIT(IIO_CHAN_INFO_OFFSET) |
BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .info_mask_separate_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),
.scan_index = 0,
.scan_type = {
.sign = 's',
@@ -113,7 +118,11 @@ static const struct iio_chan_spec hts221_channels[] = {
BIT(IIO_CHAN_INFO_OFFSET) |
BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .info_mask_separate_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),
.scan_index = 1,
.scan_type = {
.sign = 's',
@@ -127,19 +136,15 @@ static const struct iio_chan_spec hts221_channels[] = {
static int hts221_check_whoami(struct hts221_hw *hw)
{
+ struct device *dev = hw->dev;
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 (err < 0)
+ return dev_err_probe(dev, err, "failed to read whoami register\n");
- if (data != HTS221_REG_WHOAMI_VAL) {
- dev_err(hw->dev, "wrong whoami {%02x vs %02x}\n",
- data, HTS221_REG_WHOAMI_VAL);
- return -ENODEV;
- }
+ if (data != HTS221_REG_WHOAMI_VAL)
+ dev_info(dev, "unexpected whoami 0x%02x, continuing\n", data);
return 0;
}
@@ -192,53 +197,35 @@ static int hts221_update_avg(struct hts221_hw *hw,
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)
+static int hts221_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
{
- 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;
+ switch (mask) {
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ switch (chan->type) {
+ case IIO_HUMIDITYRELATIVE:
+ *vals = hts221_avg_list[HTS221_SENSOR_H].avg_avl;
+ *length = ARRAY_SIZE(hts221_avg_list[HTS221_SENSOR_H].avg_avl);
+ break;
+ case IIO_TEMP:
+ *vals = hts221_avg_list[HTS221_SENSOR_T].avg_avl;
+ *length = ARRAY_SIZE(hts221_avg_list[HTS221_SENSOR_T].avg_avl);
+ break;
+ default:
+ return -EINVAL;
+ }
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = hts221_odr_avail;
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(hts221_odr_avail);
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
}
int hts221_set_enable(struct hts221_hw *hw, bool enable)
@@ -258,6 +245,7 @@ int hts221_set_enable(struct hts221_hw *hw, bool enable)
static int hts221_parse_temp_caldata(struct hts221_hw *hw)
{
+ struct device *dev = hw->dev;
int err, *slope, *b_gen, cal0, cal1;
s16 cal_x0, cal_x1, cal_y0, cal_y1;
__le16 val;
@@ -288,10 +276,20 @@ static int hts221_parse_temp_caldata(struct hts221_hw *hw)
return err;
cal_x1 = le16_to_cpu(val);
+ if (cal_x1 == cal_x0)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid temperature calibration points (x0 %d, x1 %d)\n",
+ cal_x0, cal_x1);
+
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);
+ if (!*slope)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid temperature calibration slope (y0 %d, y1 %d)\n",
+ cal_y0, cal_y1);
+
*b_gen = (((s32)cal_x1 * cal_y0 - (s32)cal_x0 * cal_y1) * 1000) /
(cal_x1 - cal_x0);
*b_gen *= 8;
@@ -301,6 +299,7 @@ static int hts221_parse_temp_caldata(struct hts221_hw *hw)
static int hts221_parse_rh_caldata(struct hts221_hw *hw)
{
+ struct device *dev = hw->dev;
int err, *slope, *b_gen, data;
s16 cal_x0, cal_x1, cal_y0, cal_y1;
__le16 val;
@@ -327,10 +326,20 @@ static int hts221_parse_rh_caldata(struct hts221_hw *hw)
return err;
cal_x1 = le16_to_cpu(val);
+ if (cal_x1 == cal_x0)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid rh calibration points (x0 %d, x1 %d)\n",
+ cal_x0, cal_x1);
+
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);
+ if (!*slope)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid rh calibration slope (y0 %d, y1 %d)\n",
+ cal_y0, cal_y1);
+
*b_gen = (((s32)cal_x1 * cal_y0 - (s32)cal_x0 * cal_y1) * 1000) /
(cal_x1 - cal_x0);
*b_gen *= 8;
@@ -521,27 +530,10 @@ static int hts221_validate_trigger(struct iio_dev *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,
+ .read_avail = hts221_read_avail,
.validate_trigger = hts221_validate_trigger,
};
@@ -608,33 +600,23 @@ int hts221_probe(struct device *dev, int irq, const char *name,
/* 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;
- }
+ if (err < 0)
+ return dev_err_probe(dev, err, "failed to get rh calibration data\n");
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;
- }
+ if (err < 0)
+ return dev_err_probe(dev, err, "failed to set rh oversampling ratio\n");
/* 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;
- }
+ if (err < 0)
+ return dev_err_probe(dev, err, "failed to get temperature calibration data\n");
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 (err < 0)
+ return dev_err_probe(dev, err, "failed to set temperature oversampling ratio\n");
if (hw->irq > 0) {
err = hts221_allocate_buffers(iio_dev);
diff --git a/drivers/iio/humidity/hts221_i2c.c b/drivers/iio/humidity/hts221_i2c.c
index 40276abc5d2e..e6dc9532cfbd 100644
--- a/drivers/iio/humidity/hts221_i2c.c
+++ b/drivers/iio/humidity/hts221_i2c.c
@@ -26,17 +26,14 @@ static const struct regmap_config hts221_i2c_regmap_config = {
static int hts221_i2c_probe(struct i2c_client *client)
{
+ struct device *dev = &client->dev;
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);
- }
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to register i2c regmap\n");
- return hts221_probe(&client->dev, client->irq,
- client->name, regmap);
+ return hts221_probe(dev, client->irq, client->name, regmap);
}
static const struct acpi_device_id hts221_acpi_match[] = {
diff --git a/drivers/iio/humidity/hts221_spi.c b/drivers/iio/humidity/hts221_spi.c
index f962842cc71d..f2c0b1a81a8f 100644
--- a/drivers/iio/humidity/hts221_spi.c
+++ b/drivers/iio/humidity/hts221_spi.c
@@ -27,17 +27,14 @@ static const struct regmap_config hts221_spi_regmap_config = {
static int hts221_spi_probe(struct spi_device *spi)
{
+ struct device *dev = &spi->dev;
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);
- }
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to register spi regmap\n");
- return hts221_probe(&spi->dev, spi->irq,
- spi->modalias, regmap);
+ return hts221_probe(dev, spi->irq, spi->modalias, regmap);
}
static const struct of_device_id hts221_spi_of_match[] = {
diff --git a/drivers/iio/iio_core.h b/drivers/iio/iio_core.h
index b7d5f4f0fada..59fa1bd9924b 100644
--- a/drivers/iio/iio_core.h
+++ b/drivers/iio/iio_core.h
@@ -109,4 +109,11 @@ 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);
+#if IS_ENABLED(CONFIG_KUNIT)
+ssize_t __iio_chan_prefix_emit(struct device *dev,
+ const struct iio_chan_spec *chan,
+ enum iio_shared_by shared_by,
+ char *buf, size_t len);
+#endif
+
#endif
diff --git a/drivers/iio/imu/adis16400.c b/drivers/iio/imu/adis16400.c
index 8f664d055e9d..79f2974d3777 100644
--- a/drivers/iio/imu/adis16400.c
+++ b/drivers/iio/imu/adis16400.c
@@ -420,7 +420,10 @@ static int adis16400_initial_setup(struct iio_dev *indio_dev)
else
st->adis.spi->max_speed_hz = ADIS16400_SPI_FAST;
st->adis.spi->mode = SPI_MODE_3;
- spi_setup(st->adis.spi);
+
+ ret = spi_setup(st->adis.spi);
+ if (ret)
+ return ret;
ret = __adis_initial_startup(&st->adis);
if (ret)
@@ -1150,6 +1153,9 @@ static int adis16400_probe(struct spi_device *spi)
/* setup the industrialio driver allocated elements */
st->variant = spi_get_device_match_data(spi);
+ if (!st->variant)
+ return -ENODATA;
+
indio_dev->name = spi_get_device_id(spi)->name;
indio_dev->channels = st->variant->channels;
indio_dev->num_channels = st->variant->num_channels;
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h
index b55d993f0264..e56943931b6a 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600.h
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h
@@ -179,6 +179,7 @@ struct inv_icm42600_state {
struct {
s64 gyro;
s64 accel;
+ bool accel_force_odr;
} timestamp;
struct inv_icm42600_apex apex;
struct inv_icm42600_fifo fifo;
@@ -356,8 +357,8 @@ struct inv_icm42600_sensor_state {
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
+/* INV_ICM42600_FIFO_WATERMARK_MAX / 16 = 116 */
+#define INV_ICM42600_FIFO_WATERMARK_MAX_SAMPLES 116
#define INV_ICM42600_REG_INT_CONFIG1 0x0064
#define INV_ICM42600_INT_CONFIG1_TPULSE_DURATION BIT(6)
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
index 4b0e3cd8a506..cc8c074777c9 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
@@ -254,15 +254,23 @@ static int inv_icm42600_accel_update_scan_mode(struct iio_dev *indio_dev,
fifo_en |= INV_ICM42600_SENSOR_ACCEL;
}
+ /*
+ * Sleep maximum stabilization time before enabling data in FIFO.
+ * We need to release the driver lock to not block gyro data processing.
+ * There is no possible race here since we are under IIO mutex locked.
+ */
+ sleep = max(sleep_accel, sleep_temp);
+ if (sleep) {
+ mutex_unlock(&st->lock);
+ msleep(sleep);
+ mutex_lock(&st->lock);
+ }
+
/* 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;
}
@@ -1227,14 +1235,16 @@ int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev)
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)
+ if (odr & INV_ICM42600_SENSOR_ACCEL) {
inv_sensors_timestamp_apply_odr(ts, st->fifo.period,
st->fifo.nb.total, no);
+ st->timestamp.accel_force_odr = false;
+ }
+
+ /* skip packet if no accel data or data is invalid */
+ if (accel == NULL || !inv_icm42600_fifo_is_data_valid(accel))
+ continue;
memcpy(&buffer.accel, accel, sizeof(buffer.accel));
/* convert 8 bits FIFO temperature in high resolution format */
@@ -1243,5 +1253,10 @@ int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev)
iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val);
}
+ if (st->timestamp.accel_force_odr) {
+ inv_sensors_timestamp_apply_odr(ts, 0, 0, 0);
+ st->timestamp.accel_force_odr = false;
+ }
+
return 0;
}
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
index 998d312f7bde..2bf9fc29d7d2 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
@@ -5,6 +5,7 @@
#include <linux/delay.h>
#include <linux/device.h>
+#include <linux/gcd.h>
#include <linux/kernel.h>
#include <linux/minmax.h>
#include <linux/mutex.h>
@@ -131,13 +132,16 @@ int inv_icm42600_buffer_set_fifo_en(struct inv_icm42600_state *st,
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;
+ /*
+ * Always enable/disable all bits to ensure we can flawlessly add
+ * accel/gyro data in the FIFO while it is running.
+ */
+ if (fifo_en)
+ val = INV_ICM42600_FIFO_CONFIG1_TEMP_EN |
+ INV_ICM42600_FIFO_CONFIG1_GYRO_EN |
+ INV_ICM42600_FIFO_CONFIG1_ACCEL_EN;
+ else
+ val = 0;
ret = regmap_update_bits(st->map, INV_ICM42600_REG_FIFO_CONFIG1, mask, val);
if (ret)
@@ -149,19 +153,6 @@ int inv_icm42600_buffer_set_fifo_en(struct inv_icm42600_state *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)
{
@@ -185,15 +176,14 @@ static unsigned int inv_icm42600_wm_truncate(unsigned int watermark,
*
* 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.)
+ * latency, we should need to use the smallest latency value. But it is not as
+ * simple as choosing the smallest watermark value. Latency depends on watermark
+ * and ODR and IIO buffer watermark adds another requirement. The required steps:
+ * 1) compute gyro and accel periods and latencies
+ * 2) Use the smallest period and the GCD of the latencies. GCD is required
+ * because of the IIO buffer watermark that will prevent send of data if not
+ * crossed. Thus accel and gyro watermarks must be a multiple of the watermark
+ * value. Computing the GCD gives us the biggest value that meets this criteria.
* 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
@@ -203,13 +193,13 @@ 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 period_gyro, period_accel, period;
u32 latency_gyro, latency_accel, latency;
bool restore;
__le16 raw_wm;
int ret;
- packet_size = inv_icm42600_get_packet_size(st->fifo.en);
+ packet_size = INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE;
/* compute sensors latency, depending on sensor watermark and odr */
wm_gyro = inv_icm42600_wm_truncate(st->fifo.watermark.gyro, packet_size);
@@ -231,22 +221,17 @@ int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st)
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;
+ /*
+ * In case of both accel and gyro enabled, we need to use the
+ * shortest period and the gcd of the latencies. Gcd is required
+ * because of the IIO buffer watermark that will prevent data
+ * sending if we are not crossing the watermark level.
+ */
+ period = min(period_gyro, period_accel);
+ latency = gcd(latency_gyro, latency_accel);
+ watermark = max(latency / period, 1);
+ st->fifo.watermark.eff_gyro = max(latency / period_gyro, 1);
+ st->fifo.watermark.eff_accel = max(latency / period_accel, 1);
}
/* compute watermark value in bytes */
@@ -399,6 +384,8 @@ static int inv_icm42600_buffer_postdisable(struct iio_dev *indio_dev)
mutex_lock(&st->lock);
inv_sensors_timestamp_apply_odr(ts, 0, 0, 0);
+ if (sensor == INV_ICM42600_SENSOR_ACCEL)
+ st->timestamp.accel_force_odr = false;
ret = inv_icm42600_buffer_set_fifo_en(st, st->fifo.en & ~sensor);
if (ret)
@@ -492,24 +479,21 @@ int inv_icm42600_buffer_fifo_read(struct inv_icm42600_state *st,
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 watermark samples for interrupt case (max = 0) or read FIFO count */
+ if (max == 0) {
+ st->fifo.count = st->fifo.watermark.value *
+ INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE;
+ } else {
+ 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;
+ max_count = max * INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE;
+ st->fifo.count = min(be16_to_cpup(raw_fifo_count), max_count);
+ if (st->fifo.count == 0)
+ return 0;
+ }
/* read all FIFO data in internal buffer */
ret = regmap_noinc_read(st->map, INV_ICM42600_REG_FIFO_DATA,
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
index dc97d8a274e3..cd2e53416f5b 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
@@ -8,6 +8,7 @@
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/kernel.h>
+#include <linux/limits.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pm_runtime.h>
@@ -324,6 +325,15 @@ int inv_icm42600_set_accel_conf(struct inv_icm42600_state *st,
break;
}
+ /*
+ * If we change mode from low-power to low-noise because of odr,
+ * we need to force odr change.
+ */
+ if (oldconf->mode == INV_ICM42600_SENSOR_MODE_LOW_POWER &&
+ conf->mode == INV_ICM42600_SENSOR_MODE_LOW_NOISE &&
+ conf->odr != oldconf->odr)
+ st->timestamp.accel_force_odr = true;
+
/* set ACCEL_CONFIG0 register (accel fullscale & odr) */
if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) {
val = INV_ICM42600_ACCEL_CONFIG0_FS(conf->fs) |
@@ -509,10 +519,20 @@ static int inv_icm42600_setup(struct inv_icm42600_state *st,
ret = regmap_read(st->map, INV_ICM42600_REG_WHOAMI, &val);
if (ret)
return ret;
+
+ /*
+ * 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 %#04x expected %#04x (%s)\n",
+ val, hw->whoami, hw->name);
+
if (val != hw->whoami) {
- dev_err(dev, "invalid whoami %#02x expected %#02x (%s)\n",
- val, hw->whoami, hw->name);
- return -ENODEV;
+ dev_info(dev,
+ "device id %#04x is not the %#04x associated with the FW-specified device (%s), probably using a valid fallback compatible\n",
+ val, hw->whoami, hw->name);
}
st->name = hw->name;
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
index 253bf571439d..4e20d61d897b 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
@@ -126,15 +126,23 @@ static int inv_icm42600_gyro_update_scan_mode(struct iio_dev *indio_dev,
fifo_en |= INV_ICM42600_SENSOR_GYRO;
}
+ /*
+ * Sleep maximum stabilization time before enabling data in FIFO.
+ * We need to release the driver lock to not block accel data processing.
+ * There is no possible race here since we are under IIO mutex locked.
+ */
+ sleep = max(sleep_gyro, sleep_temp);
+ if (sleep) {
+ mutex_unlock(&st->lock);
+ msleep(sleep);
+ mutex_lock(&st->lock);
+ }
+
/* 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;
}
@@ -808,15 +816,15 @@ int inv_icm42600_gyro_parse_fifo(struct iio_dev *indio_dev)
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);
+ /* skip packet if no gyro data or data is invalid */
+ if (gyro == NULL || !inv_icm42600_fifo_is_data_valid(gyro))
+ continue;
+
memcpy(&buffer.gyro, gyro, sizeof(buffer.gyro));
/* convert 8 bits FIFO temperature in high resolution format */
buffer.temp = temp ? (*temp * 64) : 0;
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c
index 28552d2db91d..e44ed6dcde02 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c
@@ -77,10 +77,10 @@ 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 },
+ { .name = "icm42686", .driver_data = INV_CHIP_ICM42686 },
+ { .name = "icm42688", .driver_data = INV_CHIP_ICM42688 },
{ }
};
MODULE_DEVICE_TABLE(i2c, inv_icm42600_id);
@@ -96,17 +96,17 @@ static const struct of_device_id inv_icm42600_of_matches[] = {
.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,
+ }, {
+ .compatible = "invensense,icm42686",
+ .data = (void *)INV_CHIP_ICM42686,
+ }, {
+ .compatible = "invensense,icm42688",
+ .data = (void *)INV_CHIP_ICM42688,
},
{ }
};
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c
index faf743bc6444..3e2f4eb42e97 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c
@@ -74,10 +74,10 @@ 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 },
+ { .name = "icm42686", .driver_data = INV_CHIP_ICM42686 },
+ { .name = "icm42688", .driver_data = INV_CHIP_ICM42688 },
{ }
};
MODULE_DEVICE_TABLE(spi, inv_icm42600_id);
@@ -93,17 +93,17 @@ static const struct of_device_id inv_icm42600_of_matches[] = {
.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,
+ }, {
+ .compatible = "invensense,icm42686",
+ .data = (void *)INV_CHIP_ICM42686,
+ }, {
+ .compatible = "invensense,icm42688",
+ .data = (void *)INV_CHIP_ICM42688,
},
{ }
};
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
index 5796896d54cd..38c3f3b398d4 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
@@ -13,6 +13,7 @@
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/acpi.h>
+#include <linux/kstrtox.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/math64.h>
@@ -1303,8 +1304,9 @@ inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr,
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;
+ result = kstrtoint(buf, 10, &fifo_rate);
+ if (result)
+ return result;
if (fifo_rate < INV_MPU6050_MIN_FIFO_RATE ||
fifo_rate > INV_MPU6050_MAX_FIFO_RATE)
return -EINVAL;
diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c
index d6a1eeb151ca..cbc47fa5b101 100644
--- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c
+++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c
@@ -766,8 +766,8 @@ st_lsm6dsx_shub_alloc_iiodev(struct st_lsm6dsx_hw *hw,
IIO_CHAN_SOFT_TIMESTAMP(3),
};
- ext_channels = devm_kzalloc(hw->dev, sizeof(magn_channels),
- GFP_KERNEL);
+ ext_channels = devm_kcalloc(hw->dev, ARRAY_SIZE(magn_channels),
+ sizeof(*ext_channels), GFP_KERNEL);
if (!ext_channels)
return NULL;
diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c
index f7a4be8ec320..fbf5c39d7655 100644
--- a/drivers/iio/industrialio-backend.c
+++ b/drivers/iio/industrialio-backend.c
@@ -887,6 +887,39 @@ int iio_backend_num_lanes_set(struct iio_backend *back, unsigned int num_lanes)
EXPORT_SYMBOL_NS_GPL(iio_backend_num_lanes_set, "IIO_BACKEND");
/**
+ * iio_backend_crc_enable - Enable CRC handling
+ * @back: Backend device
+ *
+ * On input data paths, enable backend handling of CRC data generated by the
+ * frontend. On output data paths, enable the backend to generate CRC data for
+ * the frontend to verify.
+ *
+ * RETURNS:
+ * 0 on success, negative error number on failure.
+ */
+int iio_backend_crc_enable(struct iio_backend *back)
+{
+ return iio_backend_op_call(back, crc_enable);
+}
+EXPORT_SYMBOL_NS_GPL(iio_backend_crc_enable, "IIO_BACKEND");
+
+/**
+ * iio_backend_crc_disable - Disable CRC handling
+ * @back: Backend device
+ *
+ * On input data paths, disable backend handling of CRC data generated by the
+ * frontend. On output data paths, disable CRC generation by the backend.
+ *
+ * RETURNS:
+ * 0 on success, negative error number on failure.
+ */
+int iio_backend_crc_disable(struct iio_backend *back)
+{
+ return iio_backend_op_call(back, crc_disable);
+}
+EXPORT_SYMBOL_NS_GPL(iio_backend_crc_disable, "IIO_BACKEND");
+
+/**
* iio_backend_ddr_enable - Enable interface DDR (Double Data Rate) mode
* @back: Backend device
*
diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index 767a7794624a..d73f2541375f 100644
--- a/drivers/iio/industrialio-core.c
+++ b/drivers/iio/industrialio-core.c
@@ -9,12 +9,15 @@
#define pr_fmt(fmt) "iio-core: " fmt
+#include <kunit/visibility.h>
+
#include <linux/anon_inodes.h>
#include <linux/cdev.h>
#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/err.h>
+#include <linux/export.h>
#include <linux/fs.h>
#include <linux/idr.h>
#include <linux/kdev_t.h>
@@ -25,6 +28,7 @@
#include <linux/poll.h>
#include <linux/property.h>
#include <linux/sched.h>
+#include <linux/seq_buf.h>
#include <linux/slab.h>
#include <linux/wait.h>
@@ -100,6 +104,7 @@ static const char * const iio_chan_type_name_spec[] = {
[IIO_ALTCURRENT] = "altcurrent",
[IIO_COVERAGE] = "coverage",
[IIO_VOLUMEFLOW] = "volumeflow",
+ [IIO_FREQUENCY] = "frequency",
};
static const char * const iio_modifier_names[] = {
@@ -199,6 +204,63 @@ static const char * const iio_chan_info_postfix[] = {
[IIO_CHAN_INFO_CONVDELAY] = "convdelay",
[IIO_CHAN_INFO_POWERFACTOR] = "powerfactor",
};
+
+VISIBLE_IF_KUNIT
+ssize_t __iio_chan_prefix_emit(struct device *dev,
+ const struct iio_chan_spec *chan,
+ enum iio_shared_by shared_by,
+ char *buf, size_t len)
+{
+ const char *type = iio_chan_type_name_spec[chan->type];
+ const char *dir = iio_direction[chan->output];
+ struct seq_buf s;
+
+ seq_buf_init(&s, buf, len);
+
+ switch (shared_by) {
+ case IIO_SHARED_BY_ALL:
+ break;
+ case IIO_SHARED_BY_DIR:
+ seq_buf_printf(&s, "%s", dir);
+ break;
+ case IIO_SHARED_BY_TYPE:
+ seq_buf_printf(&s, "%s_%s", dir, type);
+ if (chan->differential)
+ seq_buf_printf(&s, "-%s", type);
+ break;
+ case IIO_SEPARATE:
+ /* Validate differential channel settings */
+ if (chan->differential) {
+ if (!chan->indexed) {
+ dev_err(dev, "Differential channels must be indexed\n");
+ return -EINVAL;
+ }
+ if (chan->modified) {
+ dev_err(dev, "Differential channels can not have modifier\n");
+ return -EINVAL;
+ }
+ }
+
+ seq_buf_printf(&s, "%s_%s", dir, type);
+
+ if (chan->indexed)
+ seq_buf_printf(&s, "%d", chan->channel);
+
+ if (chan->differential)
+ seq_buf_printf(&s, "-%s%d", type, chan->channel2);
+
+ if (chan->modified)
+ seq_buf_printf(&s, "_%s", iio_modifier_names[chan->channel2]);
+
+ if (chan->extend_name)
+ seq_buf_printf(&s, "_%s", chan->extend_name);
+ break;
+ }
+
+ return seq_buf_has_overflowed(&s) ? -EOVERFLOW : s.len;
+}
+EXPORT_SYMBOL_IF_KUNIT(__iio_chan_prefix_emit);
+
/**
* iio_device_id() - query the unique ID for the device
* @indio_dev: Device structure whose ID is being queried
@@ -341,7 +403,8 @@ s64 iio_get_time_ns(const struct iio_dev *indio_dev)
case CLOCK_TAI:
return ktime_get_clocktai_ns();
default:
- BUG();
+ WARN_ON_ONCE(1);
+ return 0;
}
}
EXPORT_SYMBOL(iio_get_time_ns);
@@ -388,6 +451,7 @@ static ssize_t iio_debugfs_read_reg(struct file *file, char __user *userbuf,
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;
+ u64 val64 = 0;
int ret;
if (*ppos > 0)
@@ -395,9 +459,17 @@ static ssize_t iio_debugfs_read_reg(struct file *file, char __user *userbuf,
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 (indio_dev->info->debugfs_reg64_access) {
+ ret = indio_dev->info->debugfs_reg64_access(indio_dev,
+ iio_dev_opaque->cached_reg_addr,
+ 0, &val64);
+ } else {
+ ret = indio_dev->info->debugfs_reg_access(indio_dev,
+ iio_dev_opaque->cached_reg_addr,
+ 0, &val);
+ val64 = val;
+ }
+
if (ret) {
dev_err(indio_dev->dev.parent, "%s: read failed\n", __func__);
return ret;
@@ -405,7 +477,7 @@ static ssize_t iio_debugfs_read_reg(struct file *file, char __user *userbuf,
iio_dev_opaque->read_buf_len = snprintf(iio_dev_opaque->read_buf,
sizeof(iio_dev_opaque->read_buf),
- "0x%X\n", val);
+ "0x%llX\n", val64);
return simple_read_from_buffer(userbuf, count, ppos,
iio_dev_opaque->read_buf,
@@ -417,8 +489,9 @@ static ssize_t iio_debugfs_write_reg(struct file *file,
{
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;
+ unsigned int reg;
char buf[80];
+ u64 val64;
int ret;
if (*ppos != 0 || count >= sizeof(buf))
@@ -431,7 +504,7 @@ static ssize_t iio_debugfs_write_reg(struct file *file,
buf[ret] = '\0';
- ret = sscanf(buf, "%i %i", &reg, &val);
+ ret = sscanf(buf, "%i %lli", &reg, &val64);
switch (ret) {
case 1:
@@ -439,8 +512,12 @@ static ssize_t iio_debugfs_write_reg(struct file *file,
break;
case 2:
iio_dev_opaque->cached_reg_addr = reg;
- ret = indio_dev->info->debugfs_reg_access(indio_dev, reg,
- val, NULL);
+ if (indio_dev->info->debugfs_reg64_access)
+ ret = indio_dev->info->debugfs_reg64_access(indio_dev, reg,
+ val64, NULL);
+ else
+ ret = indio_dev->info->debugfs_reg_access(indio_dev, reg,
+ val64, NULL);
if (ret) {
dev_err(indio_dev->dev.parent, "%s: write failed\n",
__func__);
@@ -471,7 +548,8 @@ 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)
+ if (!indio_dev->info->debugfs_reg_access &&
+ !indio_dev->info->debugfs_reg64_access)
return;
if (!iio_debugfs_dentry)
@@ -781,6 +859,20 @@ static ssize_t iio_read_channel_label(struct device *dev,
to_iio_dev_attr(attr)->c, buf);
}
+static ssize_t iio_read_channel_parent(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ const struct iio_chan_spec *chan = to_iio_dev_attr(attr)->c->parent;
+ ssize_t len;
+
+ len = __iio_chan_prefix_emit(dev, chan, IIO_SEPARATE, buf, PAGE_SIZE - 1);
+ if (len < 0)
+ return len;
+
+ return len + sysfs_emit_at(buf, len, "\n");
+}
+
static ssize_t iio_read_channel_info(struct device *dev,
struct device_attribute *attr,
char *buf)
@@ -1075,7 +1167,8 @@ static ssize_t iio_write_channel_info(struct device *dev,
}
static
-int __iio_device_attr_init(struct device_attribute *dev_attr,
+int __iio_device_attr_init(struct device *dev,
+ struct device_attribute *dev_attr,
const char *postfix,
struct iio_chan_spec const *chan,
ssize_t (*readfunc)(struct device *dev,
@@ -1087,105 +1180,21 @@ int __iio_device_attr_init(struct device_attribute *dev_attr,
size_t len),
enum iio_shared_by shared_by)
{
- int ret = 0;
- char *name = NULL;
- char *full_postfix;
+ char prefix[NAME_MAX + 1];
+ ssize_t ret;
sysfs_attr_init(&dev_attr->attr);
- /* Build up postfix of <extend_name>_<modifier>_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;
+ ret = __iio_chan_prefix_emit(dev, chan, shared_by, prefix, sizeof(prefix));
+ if (ret < 0)
+ return ret;
- 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 (ret)
+ dev_attr->attr.name = kasprintf(GFP_KERNEL, "%s_%s", prefix, postfix);
+ else
+ dev_attr->attr.name = kstrdup(postfix, GFP_KERNEL);
+ if (!dev_attr->attr.name)
+ return -ENOMEM;
if (readfunc) {
dev_attr->attr.mode |= 0444;
@@ -1197,10 +1206,7 @@ int __iio_device_attr_init(struct device_attribute *dev_attr,
dev_attr->store = writefunc;
}
-error_free_full_postfix:
- kfree(full_postfix);
-
- return ret;
+ return 0;
}
static void __iio_device_attr_deinit(struct device_attribute *dev_attr)
@@ -1229,7 +1235,7 @@ int __iio_add_chan_devattr(const char *postfix,
iio_attr = kzalloc_obj(*iio_attr);
if (iio_attr == NULL)
return -ENOMEM;
- ret = __iio_device_attr_init(&iio_attr->dev_attr,
+ ret = __iio_device_attr_init(dev, &iio_attr->dev_attr,
postfix, chan,
readfunc, writefunc, shared_by);
if (ret)
@@ -1281,6 +1287,25 @@ static int iio_device_add_channel_label(struct iio_dev *indio_dev,
return 1;
}
+static int iio_device_add_channel_parent_attr(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 (!chan->parent)
+ return 0;
+
+ ret = __iio_add_chan_devattr("parent", chan,
+ &iio_read_channel_parent, 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,
@@ -1419,6 +1444,11 @@ static int iio_device_add_channel_sysfs(struct iio_dev *indio_dev,
return ret;
attrcount += ret;
+ ret = iio_device_add_channel_parent_attr(indio_dev, chan);
+ if (ret < 0)
+ return ret;
+ attrcount += ret;
+
if (chan->ext_info) {
unsigned int i = 0;
@@ -1518,7 +1548,8 @@ static ssize_t current_timestamp_clock_show(struct device *dev,
case CLOCK_TAI:
break;
default:
- BUG();
+ WARN_ON_ONCE(1);
+ return -EINVAL;
}
return sysfs_emit(buf, "%s\n", clock_names[clk]);
diff --git a/drivers/iio/industrialio-gts-helper.c b/drivers/iio/industrialio-gts-helper.c
index 4f52dc373abf..af6ed37fb34a 100644
--- a/drivers/iio/industrialio-gts-helper.c
+++ b/drivers/iio/industrialio-gts-helper.c
@@ -5,7 +5,7 @@
*/
#include <linux/device.h>
-#include <linux/errno.h>
+#include <linux/err.h>
#include <linux/export.h>
#include <linux/minmax.h>
#include <linux/module.h>
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index f23bbce12c72..462fff1ddef2 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -741,6 +741,20 @@ config VEML6030
To compile this driver as a module, choose M here: the
module will be called veml6030.
+config VEML6031X00
+ tristate "VEML6031X00 ambient light sensor series"
+ select REGMAP_I2C
+ select IIO_GTS_HELPER
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ depends on I2C
+ help
+ Say Y here if you want to build a driver for the Vishay VEML6031X00
+ ambient light sensor series.
+
+ To compile this driver as a module, choose M here: the
+ module will be called veml6031x00.
+
config VEML6040
tristate "VEML6040 RGBW light sensor"
select REGMAP_I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index 64e354c49ed8..6672ed01a819 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -68,6 +68,7 @@ obj-$(CONFIG_VCNL4035) += vcnl4035.o
obj-$(CONFIG_VEML3235) += veml3235.o
obj-$(CONFIG_VEML3328) += veml3328.o
obj-$(CONFIG_VEML6030) += veml6030.o
+obj-$(CONFIG_VEML6031X00) += veml6031x00.o
obj-$(CONFIG_VEML6040) += veml6040.o
obj-$(CONFIG_VEML6046X00) += veml6046x00.o
obj-$(CONFIG_VEML6070) += veml6070.o
diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c
index f681b02e4922..7dd41136734f 100644
--- a/drivers/iio/light/apds9306.c
+++ b/drivers/iio/light/apds9306.c
@@ -61,7 +61,7 @@
#define APDS9306_NUM_REPEAT_RATES 7
#define APDS9306_INT_SRC_CLEAR 0
#define APDS9306_INT_SRC_ALS 1
-#define APDS9306_SAMP_FREQ_10HZ 0
+#define APDS9306_SAMP_FREQ_10HZ 2
/**
* struct part_id_gts_multiplier - Part no. and corresponding gts multiplier
diff --git a/drivers/iio/light/apds9999.c b/drivers/iio/light/apds9999.c
index 43fa9992c9c2..62620c4a2fbc 100644
--- a/drivers/iio/light/apds9999.c
+++ b/drivers/iio/light/apds9999.c
@@ -100,10 +100,6 @@ static int apds9999_init(struct apds9999_data *data)
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) |
@@ -121,8 +117,12 @@ static int apds9999_init(struct apds9999_data *data)
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);
+ ret = i2c_smbus_write_byte_data(client, APDS9999_REG_MAIN_CTRL,
+ APDS9999_MAIN_CTRL_LS_EN);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, apds9999_standby, client);
}
static int apds9999_read_channel(struct apds9999_data *data, u8 reg,
diff --git a/drivers/iio/light/cros_ec_light_prox.c b/drivers/iio/light/cros_ec_light_prox.c
index 7ab565b1fb9f..b1c92de7922a 100644
--- a/drivers/iio/light/cros_ec_light_prox.c
+++ b/drivers/iio/light/cros_ec_light_prox.c
@@ -249,7 +249,7 @@ 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,
+ .pm = pm_sleep_ptr(&cros_ec_sensors_pm_ops),
},
.probe = cros_ec_light_prox_probe,
.id_table = cros_ec_light_prox_ids,
diff --git a/drivers/iio/light/iqs621-als.c b/drivers/iio/light/iqs621-als.c
index cd5843e3e2c3..f9d215ef1970 100644
--- a/drivers/iio/light/iqs621-als.c
+++ b/drivers/iio/light/iqs621-als.c
@@ -179,19 +179,6 @@ static int iqs621_als_notifier(struct notifier_block *notifier,
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)
@@ -563,19 +550,14 @@ static int iqs621_als_probe(struct platform_device *pdev)
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);
+ ret = devm_blocking_notifier_chain_register(&pdev->dev,
+ &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);
}
diff --git a/drivers/iio/light/isl29018.c b/drivers/iio/light/isl29018.c
index 759cb71ed1c5..7f7a563df4d9 100644
--- a/drivers/iio/light/isl29018.c
+++ b/drivers/iio/light/isl29018.c
@@ -10,6 +10,7 @@
#include <linux/i2c.h>
#include <linux/err.h>
+#include <linux/kstrtox.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/delay.h>
@@ -339,9 +340,11 @@ static ssize_t proximity_on_chip_ambient_infrared_suppression_store
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct isl29018_chip *chip = iio_priv(indio_dev);
int val;
+ int ret;
- if (kstrtoint(buf, 10, &val))
- return -EINVAL;
+ ret = kstrtoint(buf, 10, &val);
+ if (ret)
+ return ret;
if (!(val == 0 || val == 1))
return -EINVAL;
diff --git a/drivers/iio/light/isl29028.c b/drivers/iio/light/isl29028.c
index 33deb1726689..e481ac908fc1 100644
--- a/drivers/iio/light/isl29028.c
+++ b/drivers/iio/light/isl29028.c
@@ -342,8 +342,9 @@ static int isl29028_write_raw(struct iio_dev *indio_dev,
struct device *dev = regmap_get_device(chip->regmap);
int ret;
- ret = pm_runtime_resume_and_get(dev);
- if (ret < 0)
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
return ret;
mutex_lock(&chip->lock);
@@ -392,14 +393,7 @@ static int isl29028_write_raw(struct iio_dev *indio_dev,
mutex_unlock(&chip->lock);
- if (ret < 0)
- return ret;
-
- ret = pm_runtime_put_autosuspend(dev);
- if (ret < 0)
- return ret;
-
- return 0;
+ return ret;
}
static int isl29028_read_raw(struct iio_dev *indio_dev,
@@ -408,10 +402,11 @@ static int isl29028_read_raw(struct iio_dev *indio_dev,
{
struct isl29028_chip *chip = iio_priv(indio_dev);
struct device *dev = regmap_get_device(chip->regmap);
- int ret, pm_ret;
+ int ret;
- ret = pm_runtime_resume_and_get(dev);
- if (ret < 0)
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
return ret;
mutex_lock(&chip->lock);
@@ -461,18 +456,6 @@ static int isl29028_read_raw(struct iio_dev *indio_dev,
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;
}
diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c
index 99f0b903018c..5db9591a85b9 100644
--- a/drivers/iio/light/lm3533-als.c
+++ b/drivers/iio/light/lm3533-als.c
@@ -13,6 +13,7 @@
#include <linux/io.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
+#include <linux/kstrtox.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/mfd/core.h>
@@ -434,8 +435,9 @@ static ssize_t store_thresh_either_en(struct device *dev,
if (!als->irq)
return -EBUSY;
- if (kstrtoul(buf, 0, &enable))
- return -EINVAL;
+ ret = kstrtoul(buf, 0, &enable);
+ if (ret)
+ return ret;
int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
@@ -542,8 +544,9 @@ static ssize_t store_als_attr(struct device *dev,
u8 val;
int ret;
- if (kstrtou8(buf, 0, &val))
- return -EINVAL;
+ ret = kstrtou8(buf, 0, &val);
+ if (ret)
+ return ret;
switch (als_attr->type) {
case LM3533_ATTR_TYPE_TARGET:
diff --git a/drivers/iio/light/ltr390.c b/drivers/iio/light/ltr390.c
index bc031f2c3141..f1bb066248e6 100644
--- a/drivers/iio/light/ltr390.c
+++ b/drivers/iio/light/ltr390.c
@@ -10,12 +10,6 @@
*
* 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 <linux/bitfield.h>
diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c
index fa0d16890c8b..302be2fc314c 100644
--- a/drivers/iio/light/ltr501.c
+++ b/drivers/iio/light/ltr501.c
@@ -9,6 +9,7 @@
* TODO: IR LED characteristics
*/
+#include <linux/array_size.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/err.h>
@@ -90,10 +91,11 @@ struct ltr501_samp_table {
#define LTR501_RESERVED_GAIN -1
enum {
- ltr501 = 0,
- ltr559,
ltr301,
ltr303,
+ ltr329,
+ ltr501,
+ ltr559,
};
struct ltr501_gain {
@@ -140,6 +142,7 @@ struct ltr501_chip_info {
u8 als_mode_active;
u8 als_gain_mask;
u8 als_gain_shift;
+ bool no_irq_support;
struct iio_chan_spec const *channels;
const int no_channels;
const struct iio_info *info;
@@ -178,6 +181,11 @@ static const struct ltr501_samp_table ltr501_ps_samp_table[] = {
{500000, 2000000}
};
+static bool ltr501_has_irq_support(const struct ltr501_chip_info *info)
+{
+ return !info->no_irq_support;
+}
+
static int ltr501_match_samp_freq(const struct ltr501_samp_table *tab,
int len, int val, int val2)
{
@@ -478,7 +486,7 @@ static int ltr501_write_intr_prst(struct ltr501_data *data,
if (ret < 0)
return ret;
- /* period should be atleast equal to sampling period */
+ /* period should be at least equal to sampling period */
if (period < samp_period)
return -EINVAL;
@@ -498,7 +506,7 @@ static int ltr501_write_intr_prst(struct ltr501_data *data,
if (ret < 0)
return ret;
- /* period should be atleast equal to rate */
+ /* period should be at least equal to rate */
if (period < samp_period)
return -EINVAL;
@@ -821,6 +829,9 @@ static int __ltr501_write_raw(struct iio_dev *indio_dev,
if (ret < 0)
return ret;
+ if (!ltr501_has_irq_support(info))
+ return ret;
+
/* update persistence count when changing frequency */
ret = ltr501_write_intr_prst(data, chan->type,
0, data->als_period);
@@ -840,6 +851,9 @@ static int __ltr501_write_raw(struct iio_dev *indio_dev,
if (ret < 0)
return ret;
+ if (!ltr501_has_irq_support(info))
+ return ret;
+
/* update persistence count when changing frequency */
ret = ltr501_write_intr_prst(data, chan->type,
0, data->ps_period);
@@ -1205,6 +1219,42 @@ static const struct iio_info ltr301_info = {
};
static const struct ltr501_chip_info ltr501_chip_info_tbl[] = {
+ [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 = &ltr301_info,
+ .info_no_irq = &ltr301_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 = &ltr301_info,
+ .info_no_irq = &ltr301_info_no_irq,
+ .channels = ltr301_channels,
+ .no_channels = ARRAY_SIZE(ltr301_channels),
+ },
+ [ltr329] = {
+ .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,
+ .no_irq_support = true,
+ .info_no_irq = &ltr301_info_no_irq,
+ .channels = ltr301_channels,
+ .no_channels = ARRAY_SIZE(ltr301_channels),
+ },
[ltr501] = {
.partid = 0x08,
.als_gain = ltr501_als_gain_tbl,
@@ -1233,30 +1283,6 @@ static const struct ltr501_chip_info ltr501_chip_info_tbl[] = {
.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 = &ltr301_info,
- .info_no_irq = &ltr301_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 = &ltr301_info,
- .info_no_irq = &ltr301_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)
@@ -1369,13 +1395,15 @@ static int ltr501_init(struct ltr501_data *data)
data->ps_contr = status | LTR501_CONTR_ACTIVE;
- ret = ltr501_read_intr_prst(data, IIO_INTENSITY, &data->als_period);
- if (ret < 0)
- return ret;
+ if (ltr501_has_irq_support(data->chip_info)) {
+ 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;
+ 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);
}
@@ -1530,6 +1558,11 @@ static int ltr501_probe(struct i2c_client *client)
if (ret < 0)
return ret;
+ if (!ltr501_has_irq_support(data->chip_info) && client->irq > 0) {
+ client->irq = 0;
+ dev_warn(&client->dev, "chip doesn't support IRQ");
+ }
+
if (client->irq > 0) {
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL, ltr501_interrupt_handler,
@@ -1538,7 +1571,7 @@ static int ltr501_probe(struct i2c_client *client)
"ltr501_thresh_event",
indio_dev);
if (ret)
- return ret;
+ goto powerdown_on_error;
} else {
indio_dev->info = data->chip_info->info_no_irq;
}
@@ -1597,19 +1630,21 @@ static const struct acpi_device_id ltr_acpi_match[] = {
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 },
+ { .name = "ltr329", .driver_data = ltr329 },
+ { .name = "ltr501", .driver_data = ltr501 },
+ { .name = "ltr559", .driver_data = ltr559 },
{ }
};
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", },
+ { .compatible = "liteon,ltr329", },
+ { .compatible = "liteon,ltr501", },
+ { .compatible = "liteon,ltr559", },
{ }
};
MODULE_DEVICE_TABLE(of, ltr501_of_match);
diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c
index 7c8a1d2b2ed0..0a16e326e939 100644
--- a/drivers/iio/light/stk3310.c
+++ b/drivers/iio/light/stk3310.c
@@ -40,7 +40,13 @@
#define STK3310_REG_PS_DATA_LSB 0x12
#define STK3310_REG_ALS_DATA_MSB 0x13
#define STK3310_REG_ALS_DATA_LSB 0x14
+#define STK36C61_REG_RED_DATA_MSB 0x15
+#define STK36C61_REG_GREEN_DATA_MSB 0x17
+#define STK36C61_REG_BLUE_DATA_MSB 0x19
+#define STK36C61_REG_CLEAR_DATA_MSB 0x1B
+#define STK36C61_REG_CLEAR_DATA_LSB 0x1C
#define STK3310_REG_ID 0x3E
+#define STK36C61_REG_GAINCTRL 0x4E
#define STK3310_MAX_REG 0x80
#define STK3310_STATE_EN_PS BIT(0)
@@ -54,6 +60,7 @@
#define STK3311S34_CHIP_ID_VAL 0x1E
#define STK3311X_CHIP_ID_VAL 0x12
#define STK3335_CHIP_ID_VAL 0x51
+#define STK36C61_CHIP_ID_VAL 0x95
#define STK3310_PSINT_EN 0x01
#define STK3310_PS_MAX_VAL 0xFFFF
@@ -83,6 +90,8 @@ 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_clear_gain =
+ REG_FIELD(STK36C61_REG_GAINCTRL, 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 =
@@ -102,6 +111,7 @@ static const u8 stk3310_chip_ids[] = {
STK3311X_CHIP_ID_VAL,
STK3311_CHIP_ID_VAL,
STK3335_CHIP_ID_VAL,
+ STK36C61_CHIP_ID_VAL,
};
/* Estimate maximum proximity values with regard to measurement scale. */
@@ -132,6 +142,7 @@ struct stk3310_data {
struct regmap_field *reg_state;
struct regmap_field *reg_als_gain;
struct regmap_field *reg_ps_gain;
+ struct regmap_field *reg_clear_gain;
struct regmap_field *reg_als_it;
struct regmap_field *reg_ps_it;
struct regmap_field *reg_int_ps;
@@ -181,24 +192,78 @@ static const struct iio_chan_spec_ext_info stk3310_ext_info[] = {
{ }
};
+#define STK3310_LIGHT_CHANNEL { \
+ .type = IIO_LIGHT, \
+ .address = STK3310_REG_ALS_DATA_MSB, \
+ .info_mask_separate = \
+ BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_INT_TIME), \
+}
+
+#define STK3310_PROXIMITY_CHANNEL { \
+ .type = IIO_PROXIMITY, \
+ .address = STK3310_REG_PS_DATA_MSB, \
+ .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, \
+}
+
+#define STK36C61_INTENSITY_CHANNEL(_mod, _reg) { \
+ .type = IIO_INTENSITY, \
+ .address = _reg, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_LIGHT_##_mod, \
+ .info_mask_separate = \
+ BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_type = \
+ BIT(IIO_CHAN_INFO_INT_TIME), \
+ .info_mask_shared_by_type_available = \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_INT_TIME), \
+}
+
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,
- }
+ STK3310_LIGHT_CHANNEL,
+ STK3310_PROXIMITY_CHANNEL,
+};
+
+static const struct iio_chan_spec stk36c61_channels[] = {
+ STK3310_LIGHT_CHANNEL,
+ STK3310_PROXIMITY_CHANNEL,
+ STK36C61_INTENSITY_CHANNEL(RED, STK36C61_REG_RED_DATA_MSB),
+ STK36C61_INTENSITY_CHANNEL(GREEN, STK36C61_REG_GREEN_DATA_MSB),
+ STK36C61_INTENSITY_CHANNEL(BLUE, STK36C61_REG_BLUE_DATA_MSB),
+ STK36C61_INTENSITY_CHANNEL(CLEAR, STK36C61_REG_CLEAR_DATA_MSB),
+};
+
+/**
+ * struct stk3310_chip_info - chip-specific data
+ * @name: device name reported to the IIO core
+ * @channels: channel specification
+ * @num_channels: number of channels
+ */
+struct stk3310_chip_info {
+ const char *name;
+ const struct iio_chan_spec *channels __counted_by_ptr(num_channels);
+ unsigned int num_channels;
+};
+
+static const struct stk3310_chip_info stk3310_chip_info = {
+ .name = STK3310_DRIVER_NAME,
+ .channels = stk3310_channels,
+ .num_channels = ARRAY_SIZE(stk3310_channels),
+};
+
+static const struct stk3310_chip_info stk36c61_chip_info = {
+ .name = "stk36c61",
+ .channels = stk36c61_channels,
+ .num_channels = ARRAY_SIZE(stk36c61_channels),
};
static IIO_CONST_ATTR(in_illuminance_scale_available, STK3310_SCALE_AVAILABLE);
@@ -370,25 +435,21 @@ 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;
+ struct regmap *map = data->regmap;
- if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY)
+ if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY &&
+ chan->type != IIO_INTENSITY)
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));
+ ret = regmap_bulk_read(map, chan->address, &buf, sizeof(buf));
if (ret < 0) {
dev_err(&client->dev, "register read failed\n");
mutex_unlock(&data->lock);
@@ -398,10 +459,10 @@ static int stk3310_read_raw(struct iio_dev *indio_dev,
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
+ if (chan->type == IIO_PROXIMITY)
ret = regmap_field_read(data->reg_ps_it, &index);
+ else
+ ret = regmap_field_read(data->reg_als_it, &index);
if (ret < 0)
return ret;
@@ -409,10 +470,12 @@ static int stk3310_read_raw(struct iio_dev *indio_dev,
*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
+ if (chan->type == IIO_PROXIMITY)
ret = regmap_field_read(data->reg_ps_gain, &index);
+ else if (chan->channel2 == IIO_MOD_LIGHT_CLEAR)
+ ret = regmap_field_read(data->reg_clear_gain, &index);
+ else
+ ret = regmap_field_read(data->reg_als_gain, &index);
if (ret < 0)
return ret;
@@ -432,7 +495,8 @@ static int stk3310_write_raw(struct iio_dev *indio_dev,
int index;
struct stk3310_data *data = iio_priv(indio_dev);
- if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY)
+ if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY &&
+ chan->type != IIO_INTENSITY)
return -EINVAL;
switch (mask) {
@@ -443,10 +507,10 @@ static int stk3310_write_raw(struct iio_dev *indio_dev,
if (index < 0)
return -EINVAL;
mutex_lock(&data->lock);
- if (chan->type == IIO_LIGHT)
- ret = regmap_field_write(data->reg_als_it, index);
- else
+ if (chan->type == IIO_PROXIMITY)
ret = regmap_field_write(data->reg_ps_it, index);
+ else
+ ret = regmap_field_write(data->reg_als_it, index);
if (ret < 0)
dev_err(&data->client->dev,
"sensor configuration failed\n");
@@ -460,10 +524,12 @@ static int stk3310_write_raw(struct iio_dev *indio_dev,
if (index < 0)
return -EINVAL;
mutex_lock(&data->lock);
- if (chan->type == IIO_LIGHT)
- ret = regmap_field_write(data->reg_als_gain, index);
- else
+ if (chan->type == IIO_PROXIMITY)
ret = regmap_field_write(data->reg_ps_gain, index);
+ else if (chan->channel2 == IIO_MOD_LIGHT_CLEAR)
+ ret = regmap_field_write(data->reg_clear_gain, index);
+ else
+ ret = regmap_field_write(data->reg_als_gain, index);
if (ret < 0)
dev_err(&data->client->dev,
"sensor configuration failed\n");
@@ -474,9 +540,31 @@ static int stk3310_write_raw(struct iio_dev *indio_dev,
return -EINVAL;
}
+static int stk3310_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 *)stk3310_scale_table;
+ *length = 2 * ARRAY_SIZE(stk3310_scale_table);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_INT_TIME:
+ *vals = (const int *)stk3310_it_table;
+ *length = 2 * ARRAY_SIZE(stk3310_it_table);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
static const struct iio_info stk3310_info = {
.read_raw = stk3310_read_raw,
.write_raw = stk3310_write_raw,
+ .read_avail = stk3310_read_avail,
.attrs = &stk3310_attribute_group,
.read_event_value = stk3310_read_event,
.write_event_value = stk3310_write_event,
@@ -548,6 +636,7 @@ 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 STK36C61_REG_RED_DATA_MSB ... STK36C61_REG_CLEAR_DATA_LSB:
case STK3310_REG_PS_DATA_LSB:
case STK3310_REG_PS_DATA_MSB:
case STK3310_REG_FLAG:
@@ -582,6 +671,7 @@ static int stk3310_regmap_init(struct stk3310_data *data)
STK3310_REGFIELD(state);
STK3310_REGFIELD(als_gain);
STK3310_REGFIELD(ps_gain);
+ STK3310_REGFIELD(clear_gain);
STK3310_REGFIELD(als_it);
STK3310_REGFIELD(ps_it);
STK3310_REGFIELD(int_ps);
@@ -635,10 +725,16 @@ out:
static int stk3310_probe(struct i2c_client *client)
{
+ const struct stk3310_chip_info *chip_info;
+ struct device *dev = &client->dev;
int ret;
struct iio_dev *indio_dev;
struct stk3310_data *data;
+ chip_info = i2c_get_match_data(client);
+ if (!chip_info)
+ return dev_err_probe(dev, -ENODATA, "missing driver data\n");
+
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
@@ -657,10 +753,10 @@ static int stk3310_probe(struct i2c_client *client)
return ret;
indio_dev->info = &stk3310_info;
- indio_dev->name = STK3310_DRIVER_NAME;
+ indio_dev->name = chip_info->name;
indio_dev->modes = INDIO_DIRECT_MODE;
- indio_dev->channels = stk3310_channels;
- indio_dev->num_channels = ARRAY_SIZE(stk3310_channels);
+ indio_dev->channels = chip_info->channels;
+ indio_dev->num_channels = chip_info->num_channels;
ret = stk3310_init(indio_dev);
if (ret < 0)
@@ -766,28 +862,29 @@ 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" },
+ { .name = "stk3013", .driver_data = (kernel_ulong_t)&stk3310_chip_info },
+ { .name = "stk3310", .driver_data = (kernel_ulong_t)&stk3310_chip_info },
+ { .name = "stk3311", .driver_data = (kernel_ulong_t)&stk3310_chip_info },
+ { .name = "stk3335", .driver_data = (kernel_ulong_t)&stk3310_chip_info },
+ { .name = "stk36c61", .driver_data = (kernel_ulong_t)&stk36c61_chip_info },
{ }
};
MODULE_DEVICE_TABLE(i2c, stk3310_i2c_id);
static const struct acpi_device_id stk3310_acpi_id[] = {
- {"STK3013", 0},
- {"STK3310", 0},
- {"STK3311", 0},
+ { .id = "STK3013", .driver_data = (kernel_ulong_t)&stk3310_chip_info },
+ { .id = "STK3310", .driver_data = (kernel_ulong_t)&stk3310_chip_info },
+ { .id = "STK3311", .driver_data = (kernel_ulong_t)&stk3310_chip_info },
{ }
};
-
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", },
+ { .compatible = "sensortek,stk3013", .data = &stk3310_chip_info },
+ { .compatible = "sensortek,stk3310", .data = &stk3310_chip_info },
+ { .compatible = "sensortek,stk3311", .data = &stk3310_chip_info },
+ { .compatible = "sensortek,stk3335", .data = &stk3310_chip_info },
+ { .compatible = "sensortek,stk36c61", .data = &stk36c61_chip_info },
{ }
};
MODULE_DEVICE_TABLE(of, stk3310_of_match);
diff --git a/drivers/iio/light/tsl2583.c b/drivers/iio/light/tsl2583.c
index 53fd423aa7ae..2d041b7530e6 100644
--- a/drivers/iio/light/tsl2583.c
+++ b/drivers/iio/light/tsl2583.c
@@ -7,10 +7,11 @@
* Copyright (c) 2016-2017 Brian Masney <masneyb@onstation.org>
*/
-#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/errno.h>
#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/kstrtox.h>
#include <linux/string.h>
#include <linux/mutex.h>
#include <linux/unistd.h>
@@ -483,9 +484,12 @@ static ssize_t in_illuminance_input_target_store(struct device *dev,
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct tsl2583_chip *chip = iio_priv(indio_dev);
- int value;
+ int value, ret;
- if (kstrtoint(buf, 0, &value) || !value)
+ ret = kstrtoint(buf, 0, &value);
+ if (ret)
+ return ret;
+ if (!value)
return -EINVAL;
mutex_lock(&chip->als_mutex);
@@ -503,7 +507,10 @@ static ssize_t in_illuminance_calibrate_store(struct device *dev,
struct tsl2583_chip *chip = iio_priv(indio_dev);
int value, ret;
- if (kstrtoint(buf, 0, &value) || value != 1)
+ ret = kstrtoint(buf, 0, &value);
+ if (ret)
+ return ret;
+ if (value != 1)
return -EINVAL;
mutex_lock(&chip->als_mutex);
diff --git a/drivers/iio/light/tsl2772.c b/drivers/iio/light/tsl2772.c
index 4486a1d9d84d..c8a7afc65c1f 100644
--- a/drivers/iio/light/tsl2772.c
+++ b/drivers/iio/light/tsl2772.c
@@ -13,6 +13,7 @@
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
+#include <linux/kstrtox.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/property.h>
@@ -951,8 +952,9 @@ static ssize_t in_illuminance0_target_input_store(struct device *dev,
u16 value;
int ret;
- if (kstrtou16(buf, 0, &value))
- return -EINVAL;
+ ret = kstrtou16(buf, 0, &value);
+ if (ret)
+ return ret;
chip->settings.als_cal_target = value;
ret = tsl2772_invoke_change(indio_dev);
@@ -970,7 +972,10 @@ static ssize_t in_illuminance0_calibrate_store(struct device *dev,
bool value;
int ret;
- if (kstrtobool(buf, &value) || !value)
+ ret = kstrtobool(buf, &value);
+ if (ret)
+ return ret;
+ if (!value)
return -EINVAL;
ret = tsl2772_als_calibrate(indio_dev);
@@ -1061,7 +1066,10 @@ static ssize_t in_proximity0_calibrate_store(struct device *dev,
bool value;
int ret;
- if (kstrtobool(buf, &value) || !value)
+ ret = kstrtobool(buf, &value);
+ if (ret)
+ return ret;
+ if (!value)
return -EINVAL;
ret = tsl2772_prox_cal(indio_dev);
diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c
index 336468d59ea7..911f8d1c74d4 100644
--- a/drivers/iio/light/vcnl4000.c
+++ b/drivers/iio/light/vcnl4000.c
@@ -26,6 +26,7 @@
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
+#include <linux/sysfs.h>
#include <linux/units.h>
#include <linux/iio/buffer.h>
@@ -1504,7 +1505,7 @@ static ssize_t vcnl4000_read_near_level(struct iio_dev *indio_dev,
{
struct vcnl4000_data *data = iio_priv(indio_dev);
- return sprintf(buf, "%u\n", data->near_level);
+ return sysfs_emit(buf, "%u\n", data->near_level);
}
static irqreturn_t vcnl4010_irq_thread(int irq, void *p)
diff --git a/drivers/iio/light/veml3328.c b/drivers/iio/light/veml3328.c
index 7ff1753925c4..df36c48204be 100644
--- a/drivers/iio/light/veml3328.c
+++ b/drivers/iio/light/veml3328.c
@@ -32,6 +32,11 @@
#define VEML3328_REG_DATA_B 0x07
#define VEML3328_REG_DATA_IR 0x08
+#define VEML3328_CONT_IT_50MSECS 0
+#define VEML3328_CONT_IT_100MSECS 1
+#define VEML3328_CONT_IT_200MSECS 2
+#define VEML3328_CONT_IT_400MSECS 3
+
#define VEML3328_CONF_IT_MASK GENMASK(5, 4)
#define VEML3328_CONF_GAIN_MASK GENMASK(11, 10)
@@ -89,21 +94,40 @@ static const struct iio_chan_spec veml3328_channels[] = {
* 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 },
+static const int veml3328_scale_vals[4][4][2] = {
+ [VEML3328_CONT_IT_50MSECS] = {
+ { 0, 768000 },
+ { 0, 384000 },
+ { 0, 192000 },
+ { 0, 96000 },
+ },
+ [VEML3328_CONT_IT_100MSECS] = {
+ { 0, 384000 },
+ { 0, 192000 },
+ { 0, 96000 },
+ { 0, 48000 },
+ },
+ [VEML3328_CONT_IT_200MSECS] = {
+ { 0, 192000 },
+ { 0, 96000 },
+ { 0, 48000 },
+ { 0, 24000 },
+ },
+ [VEML3328_CONT_IT_400MSECS] = {
+ { 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 },
+ [VEML3328_CONT_IT_50MSECS] = { 0, 50 * USEC_PER_MSEC },
+ [VEML3328_CONT_IT_100MSECS] = { 0, 100 * USEC_PER_MSEC },
+ [VEML3328_CONT_IT_200MSECS] = { 0, 200 * USEC_PER_MSEC },
+ [VEML3328_CONT_IT_400MSECS] = { 0, 400 * USEC_PER_MSEC },
};
static int veml3328_power_down(struct veml3328_data *data)
@@ -184,9 +208,8 @@ static int veml3328_read_raw(struct iio_dev *indio_dev,
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];
+ *val = veml3328_scale_vals[it_inx][gain_inx][0];
+ *val2 = veml3328_scale_vals[it_inx][gain_inx][1];
return IIO_VAL_INT_PLUS_MICRO;
@@ -282,8 +305,8 @@ static int veml3328_write_raw(struct iio_dev *indio_dev,
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])
+ if (val == veml3328_scale_vals[it_inx][i][0] &&
+ val2 == veml3328_scale_vals[it_inx][i][1])
break;
}
diff --git a/drivers/iio/light/veml6031x00.c b/drivers/iio/light/veml6031x00.c
new file mode 100644
index 000000000000..f6308498e422
--- /dev/null
+++ b/drivers/iio/light/veml6031x00.c
@@ -0,0 +1,1387 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * VEML6031X00 Ambient Light Sensor
+ *
+ * Copyright (c) 2026, Javier Carrasco <javier.carrasco.cruz@gmail.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/limits.h>
+#include <linux/lockdep.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+
+#include <asm/byteorder.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/events.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/iio-gts-helper.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
+/* Device registers */
+#define VEML6031X00_REG_CONF0 0x00
+#define VEML6031X00_REG_CONF1 0x01
+#define VEML6031X00_REG_WH_L 0x04
+#define VEML6031X00_REG_WH_H 0x05
+#define VEML6031X00_REG_WL_L 0x06
+#define VEML6031X00_REG_WL_H 0x07
+#define VEML6031X00_REG_ALS_L 0x10
+#define VEML6031X00_REG_ALS_H 0x11
+#define VEML6031X00_REG_IR_L 0x12
+#define VEML6031X00_REG_IR_H 0x13
+#define VEML6031X00_REG_ID_L 0x14
+#define VEML6031X00_REG_ID_H 0x15
+#define VEML6031X00_REG_INT 0x17
+
+/* Bit masks for specific functionality */
+#define VEML6031X00_CONF0_SD BIT(0)
+#define VEML6031X00_CONF0_AF_TRIG BIT(2)
+#define VEML6031X00_CONF0_AF BIT(3)
+#define VEML6031X00_CONF1_ALS_CAL BIT(0)
+#define VEML6031X00_CONF1_IR_SD BIT(7)
+#define VEML6031X00_INT_TH_H BIT(1)
+#define VEML6031X00_INT_TH_L BIT(2)
+#define VEML6031X00_INT_DRDY BIT(3)
+#define VEML6031X00_INT_MASK \
+ (VEML6031X00_INT_TH_L | VEML6031X00_INT_TH_H | VEML6031X00_INT_DRDY)
+
+#define VEML6031X00_GAIN_SEL(pd_div4, gain) (((pd_div4) << 2) | (gain))
+
+enum veml6031x00_scan {
+ VEML6031X00_SCAN_ALS,
+ VEML6031X00_SCAN_IR,
+ VEML6031X00_SCAN_TIMESTAMP,
+};
+
+struct veml6031x00_rf {
+ struct regmap_field *gain;
+ struct regmap_field *int_en;
+ struct regmap_field *it;
+ struct regmap_field *pd_div4;
+ struct regmap_field *pers;
+};
+
+struct veml6031x00_chip {
+ const char *name;
+ const unsigned int part_id;
+};
+
+struct veml6031x00_data {
+ struct iio_gts gts;
+ struct regmap *regmap;
+ struct iio_trigger *trig;
+ struct veml6031x00_rf rf;
+ const struct veml6031x00_chip *chip;
+ /*
+ * Serialize access to scale register fields scattered across multiple
+ * registers (rf.gain, rf.pd_div4, rf.it) to read and write them as a
+ * consistent set.
+ */
+ struct mutex scale_lock;
+ /*
+ * Serialize access to irq enable/disable by events and trigger
+ * (shared line).
+ */
+ struct mutex irq_lock;
+ unsigned int interrupt_users;
+ bool ev_en;
+ bool trig_en;
+};
+
+static const struct iio_itime_sel_mul veml6031x00_it_sel[] = {
+ GAIN_SCALE_ITIME_US(3125, 0, 1),
+ GAIN_SCALE_ITIME_US(6250, 1, 2),
+ GAIN_SCALE_ITIME_US(12500, 2, 4),
+ GAIN_SCALE_ITIME_US(25000, 3, 8),
+ GAIN_SCALE_ITIME_US(50000, 4, 16),
+ GAIN_SCALE_ITIME_US(100000, 5, 32),
+ GAIN_SCALE_ITIME_US(200000, 6, 64),
+ GAIN_SCALE_ITIME_US(400000, 7, 128),
+};
+
+/*
+ * The gain selector encodes (PD_D4 << 2) | GAIN to identify each gain setting.
+ * 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.
+ * TODO: add support for GAIN_0_165 and GAIN_0_660
+ */
+#define VEML6031X00_SEL_GAIN_0_125 0x07
+#define VEML6031X00_SEL_GAIN_0_250 0x04
+#define VEML6031X00_SEL_GAIN_0_500 0x03
+#define VEML6031X00_SEL_GAIN_1_000 0x00
+#define VEML6031X00_SEL_GAIN_2_000 0x01
+static const struct iio_gain_sel_pair veml6031x00_gain_sel[] = {
+ GAIN_SCALE_GAIN(1, VEML6031X00_SEL_GAIN_0_125),
+ GAIN_SCALE_GAIN(2, VEML6031X00_SEL_GAIN_0_250),
+ GAIN_SCALE_GAIN(4, VEML6031X00_SEL_GAIN_0_500),
+ GAIN_SCALE_GAIN(8, VEML6031X00_SEL_GAIN_1_000),
+ GAIN_SCALE_GAIN(16, VEML6031X00_SEL_GAIN_2_000),
+};
+
+static IIO_CONST_ATTR(in_illuminance_thresh_either_period_available, "1 2 4 8");
+
+static struct attribute *veml6031x00_event_attributes[] = {
+ &iio_const_attr_in_illuminance_thresh_either_period_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group veml6031x00_event_attr_group = {
+ .attrs = veml6031x00_event_attributes,
+};
+
+/*
+ * The shutdown bits (SD and ALS_IR_SD) are in different registers, and both
+ * must be updated when changing the device power state.
+ */
+static int veml6031x00_set_power(struct veml6031x00_data *data, bool state)
+{
+ int ret;
+
+ ret = regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0,
+ VEML6031X00_CONF0_SD, !state);
+ if (ret)
+ return ret;
+
+ return regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF1,
+ VEML6031X00_CONF1_IR_SD, !state);
+}
+
+static void veml6031x00_als_shutdown_action(void *data)
+{
+ struct veml6031x00_data *priv = data;
+ struct device *dev = regmap_get_device(priv->regmap);
+ int ret;
+
+ ret = veml6031x00_set_power(priv, false);
+ if (ret)
+ dev_warn(dev, "Failed to shut down device: %d\n", ret);
+}
+
+static const struct iio_event_spec veml6031x00_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),
+ },
+};
+
+static const struct iio_chan_spec veml6031x00_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .address = VEML6031X00_REG_ALS_L,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .scan_index = VEML6031X00_SCAN_ALS,
+ .scan_type = {
+ .format = IIO_SCAN_FORMAT_UNSIGNED_INT,
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_LE,
+ },
+ },
+ {
+ .type = IIO_INTENSITY,
+ .address = VEML6031X00_REG_IR_L,
+ .modified = 1,
+ .channel2 = IIO_MOD_LIGHT_IR,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .scan_index = VEML6031X00_SCAN_IR,
+ .scan_type = {
+ .format = IIO_SCAN_FORMAT_UNSIGNED_INT,
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_LE,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(VEML6031X00_SCAN_TIMESTAMP),
+};
+
+static const struct iio_chan_spec veml6031x00_channels_irq[] = {
+ {
+ .type = IIO_LIGHT,
+ .address = VEML6031X00_REG_ALS_L,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .event_spec = veml6031x00_event_spec,
+ .num_event_specs = ARRAY_SIZE(veml6031x00_event_spec),
+ .scan_index = VEML6031X00_SCAN_ALS,
+ .scan_type = {
+ .format = IIO_SCAN_FORMAT_UNSIGNED_INT,
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_LE,
+ },
+ },
+ {
+ .type = IIO_INTENSITY,
+ .address = VEML6031X00_REG_IR_L,
+ .modified = 1,
+ .channel2 = IIO_MOD_LIGHT_IR,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .scan_index = VEML6031X00_SCAN_IR,
+ .scan_type = {
+ .format = IIO_SCAN_FORMAT_UNSIGNED_INT,
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_LE,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(VEML6031X00_SCAN_TIMESTAMP),
+};
+
+static const struct regmap_range veml6031x00_readable_ranges[] = {
+ regmap_reg_range(VEML6031X00_REG_CONF0, VEML6031X00_REG_CONF1),
+ regmap_reg_range(VEML6031X00_REG_WH_L, VEML6031X00_REG_WL_H),
+ regmap_reg_range(VEML6031X00_REG_ALS_L, VEML6031X00_REG_ID_H),
+ regmap_reg_range(VEML6031X00_REG_INT, VEML6031X00_REG_INT),
+};
+
+static const struct regmap_access_table veml6031x00_readable_table = {
+ .yes_ranges = veml6031x00_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(veml6031x00_readable_ranges),
+};
+
+static const struct regmap_range veml6031x00_writable_ranges[] = {
+ regmap_reg_range(VEML6031X00_REG_CONF0, VEML6031X00_REG_CONF1),
+ regmap_reg_range(VEML6031X00_REG_WH_L, VEML6031X00_REG_WL_H),
+};
+
+static const struct regmap_access_table veml6031x00_writable_table = {
+ .yes_ranges = veml6031x00_writable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(veml6031x00_writable_ranges),
+};
+
+static const struct regmap_range veml6031x00_volatile_ranges[] = {
+ /* AF_TRIG in CONF0 is volatile */
+ regmap_reg_range(VEML6031X00_REG_CONF0, VEML6031X00_REG_CONF0),
+ regmap_reg_range(VEML6031X00_REG_ALS_L, VEML6031X00_REG_IR_H),
+ regmap_reg_range(VEML6031X00_REG_INT, VEML6031X00_REG_INT),
+};
+
+static const struct regmap_access_table veml6031x00_volatile_table = {
+ .yes_ranges = veml6031x00_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(veml6031x00_volatile_ranges),
+};
+
+static const struct regmap_range veml6031x00_precious_ranges[] = {
+ regmap_reg_range(VEML6031X00_REG_INT, VEML6031X00_REG_INT),
+};
+
+static const struct regmap_access_table veml6031x00_precious_table = {
+ .yes_ranges = veml6031x00_precious_ranges,
+ .n_yes_ranges = ARRAY_SIZE(veml6031x00_precious_ranges),
+};
+
+static const struct regmap_config veml6031x00_regmap_config = {
+ .name = "veml6031x00_regmap",
+ .reg_bits = 8,
+ .val_bits = 8,
+ .rd_table = &veml6031x00_readable_table,
+ .wr_table = &veml6031x00_writable_table,
+ .volatile_table = &veml6031x00_volatile_table,
+ .precious_table = &veml6031x00_precious_table,
+ .max_register = VEML6031X00_REG_INT,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static const struct reg_field veml6031x00_rf_int_en =
+ REG_FIELD(VEML6031X00_REG_CONF0, 1, 1);
+
+static const struct reg_field veml6031x00_rf_it =
+ REG_FIELD(VEML6031X00_REG_CONF0, 4, 6);
+
+static const struct reg_field veml6031x00_rf_pers =
+ REG_FIELD(VEML6031X00_REG_CONF1, 1, 2);
+
+static const struct reg_field veml6031x00_rf_gain =
+ REG_FIELD(VEML6031X00_REG_CONF1, 3, 4);
+
+static const struct reg_field veml6031x00_rf_pd_div4 =
+ REG_FIELD(VEML6031X00_REG_CONF1, 6, 6);
+
+static int veml6031x00_regfield_init(struct veml6031x00_data *data)
+{
+ struct regmap *map = data->regmap;
+ struct device *dev = regmap_get_device(map);
+ struct regmap_field *rm_field;
+ struct veml6031x00_rf *rf = &data->rf;
+
+ rm_field = devm_regmap_field_alloc(dev, map, veml6031x00_rf_gain);
+ if (IS_ERR(rm_field))
+ return PTR_ERR(rm_field);
+ rf->gain = rm_field;
+
+ rm_field = devm_regmap_field_alloc(dev, map, veml6031x00_rf_it);
+ if (IS_ERR(rm_field))
+ return PTR_ERR(rm_field);
+ rf->it = rm_field;
+
+ rm_field = devm_regmap_field_alloc(dev, map, veml6031x00_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, map, veml6031x00_rf_pd_div4);
+ if (IS_ERR(rm_field))
+ return PTR_ERR(rm_field);
+ rf->pd_div4 = rm_field;
+
+ rm_field = devm_regmap_field_alloc(dev, map, veml6031x00_rf_pers);
+ if (IS_ERR(rm_field))
+ return PTR_ERR(rm_field);
+ rf->pers = rm_field;
+
+ return 0;
+}
+
+static int __veml6031x00_get_it(struct veml6031x00_data *data, int *val2)
+{
+ unsigned int it_idx;
+ int ret;
+
+ 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;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int veml6031x00_get_it(struct veml6031x00_data *data, int *val2)
+{
+ guard(mutex)(&data->scale_lock);
+
+ return __veml6031x00_get_it(data, val2);
+}
+
+static int veml6031x00_write_gain(struct veml6031x00_data *data, unsigned int gain)
+{
+ int ret;
+
+ ret = regmap_field_write(data->rf.pd_div4, gain >> 2);
+ if (ret)
+ return ret;
+
+ return regmap_field_write(data->rf.gain, gain & 0x03);
+}
+
+static int veml6031x00_set_it(struct iio_dev *iio, int val, int val2)
+{
+ struct veml6031x00_data *data = iio_priv(iio);
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret, gain_sel, new_gain, prev_gain, prev_it;
+ unsigned int gain_reg, it_idx, pd_div4;
+ struct iio_gts *gts = &data->gts;
+ bool gain_in_range;
+
+ if (val || !iio_gts_valid_time(gts, val2))
+ return -EINVAL;
+
+ guard(mutex)(&data->scale_lock);
+
+ ret = regmap_field_read(data->rf.it, &it_idx);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_read(data->rf.gain, &gain_reg);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_read(data->rf.pd_div4, &pd_div4);
+ if (ret)
+ return ret;
+
+ prev_it = iio_gts_find_int_time_by_sel(gts, it_idx);
+ if (prev_it < 0)
+ return prev_it;
+
+ if (prev_it == val2)
+ return 0;
+
+ gain_sel = VEML6031X00_GAIN_SEL(pd_div4, gain_reg);
+ prev_gain = iio_gts_find_gain_by_sel(gts, gain_sel);
+ if (prev_gain < 0)
+ return prev_gain;
+
+ ret = iio_gts_find_new_gain_by_gain_time_min(gts, prev_gain, prev_it, val2,
+ &new_gain, &gain_in_range);
+ if (ret)
+ return ret;
+
+ if (!gain_in_range)
+ dev_dbg(dev, "Optimal gain out of range\n");
+
+ ret = iio_gts_find_sel_by_int_time(gts, val2);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_field_write(data->rf.it, ret);
+ if (ret)
+ return ret;
+
+ gain_sel = iio_gts_find_sel_by_gain(gts, new_gain);
+ if (gain_sel < 0)
+ return gain_sel;
+
+ return veml6031x00_write_gain(data, gain_sel);
+}
+
+static int veml6031x00_read_period(struct iio_dev *iio, int *val)
+{
+ struct veml6031x00_data *data = iio_priv(iio);
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_field_read(data->rf.pers, &regval);
+ if (ret)
+ return ret;
+
+ *val = BIT(regval);
+
+ return IIO_VAL_INT;
+}
+
+static int veml6031x00_write_period(struct iio_dev *iio, int val)
+{
+ struct veml6031x00_data *data = iio_priv(iio);
+
+ if (val < 0 || val > 8 || !is_power_of_2(val))
+ return -EINVAL;
+
+ return regmap_field_write(data->rf.pers, ilog2(val));
+}
+
+static int veml6031x00_set_scale(struct iio_dev *iio, int val, int val2)
+{
+ struct veml6031x00_data *data = iio_priv(iio);
+ struct iio_gts *gts = &data->gts;
+ int gain_sel, it_sel, ret;
+
+ ret = iio_gts_find_gain_time_sel_for_scale(gts, val, val2, &gain_sel, &it_sel);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&data->scale_lock);
+
+ ret = regmap_field_write(data->rf.it, it_sel);
+ if (ret)
+ return ret;
+
+ return veml6031x00_write_gain(data, gain_sel);
+}
+
+static int veml6031x00_get_scale(struct veml6031x00_data *data, int *val, int *val2)
+{
+ unsigned int gain_reg, pd_div4, it_reg;
+ int gain, it, ret, sel;
+
+ scoped_guard(mutex, &data->scale_lock) {
+ ret = regmap_field_read(data->rf.gain, &gain_reg);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_read(data->rf.pd_div4, &pd_div4);
+ if (ret)
+ return ret;
+
+ sel = VEML6031X00_GAIN_SEL(pd_div4, gain_reg);
+ gain = iio_gts_find_gain_by_sel(&data->gts, sel);
+ if (gain < 0)
+ return gain;
+
+ ret = regmap_field_read(data->rf.it, &it_reg);
+ if (ret)
+ return ret;
+ }
+
+ it = iio_gts_find_int_time_by_sel(&data->gts, it_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 veml6031x00_read_th(struct iio_dev *iio, enum iio_event_direction dir,
+ int *val, int *val2)
+{
+ struct veml6031x00_data *data = iio_priv(iio);
+ __le16 regval;
+ int ret;
+
+ if (dir == IIO_EV_DIR_RISING)
+ ret = regmap_bulk_read(data->regmap, VEML6031X00_REG_WH_L,
+ &regval, sizeof(regval));
+ else
+ ret = regmap_bulk_read(data->regmap, VEML6031X00_REG_WL_L,
+ &regval, sizeof(regval));
+ if (ret)
+ return ret;
+
+ *val = le16_to_cpu(regval);
+
+ return IIO_VAL_INT;
+}
+
+static int veml6031x00_write_th(struct iio_dev *iio, enum iio_event_direction dir,
+ int val, int val2)
+{
+ struct veml6031x00_data *data = iio_priv(iio);
+ struct device *dev = regmap_get_device(data->regmap);
+ __le16 regval;
+ int ret;
+
+ if (val < 0 || val > U16_MAX || val2)
+ return -EINVAL;
+
+ regval = cpu_to_le16(val);
+
+ if (dir == IIO_EV_DIR_RISING) {
+ ret = regmap_bulk_write(data->regmap, VEML6031X00_REG_WH_L,
+ &regval, sizeof(regval));
+ if (ret)
+ dev_dbg(dev, "Failed to set high threshold %d\n", ret);
+ } else {
+ ret = regmap_bulk_write(data->regmap, VEML6031X00_REG_WL_L,
+ &regval, sizeof(regval));
+ if (ret)
+ dev_dbg(dev, "Failed to set low threshold %d\n", ret);
+ }
+
+ return ret;
+}
+
+static int veml6031x00_single_read(struct iio_dev *iio, enum iio_chan_type type,
+ int *val)
+{
+ struct veml6031x00_data *data = iio_priv(iio);
+ unsigned int addr;
+ int it_usec, ret;
+ __le16 regval;
+
+ switch (type) {
+ case IIO_LIGHT:
+ addr = VEML6031X00_REG_ALS_L;
+ break;
+ case IIO_INTENSITY:
+ addr = VEML6031X00_REG_IR_L;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(iio, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ guard(mutex)(&data->scale_lock);
+
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(data->regmap), pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ ret = __veml6031x00_get_it(data, &it_usec);
+ if (ret < 0)
+ return ret;
+
+ /* integration time + 10% to ensure completion */
+ fsleep(it_usec + (it_usec / 10));
+
+ ret = regmap_bulk_read(data->regmap, addr, &regval, sizeof(regval));
+ if (ret)
+ return ret;
+
+ *val = le16_to_cpu(regval);
+
+ return IIO_VAL_INT;
+}
+
+static int veml6031x00_read_raw(struct iio_dev *iio,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct veml6031x00_data *data = iio_priv(iio);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return veml6031x00_single_read(iio, chan->type, val);
+ case IIO_CHAN_INFO_INT_TIME:
+ *val = 0;
+ return veml6031x00_get_it(data, val2);
+ case IIO_CHAN_INFO_SCALE:
+ return veml6031x00_get_scale(data, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int veml6031x00_read_avail(struct iio_dev *iio,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct veml6031x00_data *data = iio_priv(iio);
+
+ 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 veml6031x00_write_raw(struct iio_dev *iio,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ IIO_DEV_ACQUIRE_DIRECT_MODE(iio, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_INT_TIME:
+ return veml6031x00_set_it(iio, val, val2);
+ case IIO_CHAN_INFO_SCALE:
+ return veml6031x00_set_scale(iio, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int veml6031x00_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 veml6031x00_set_interrupt(struct veml6031x00_data *data, bool state)
+ __must_hold(&data->irq_lock)
+{
+ int ret;
+
+ lockdep_assert_held(&data->irq_lock);
+
+ if (state) {
+ data->interrupt_users++;
+ if (data->interrupt_users > 1)
+ return 0;
+ } else {
+ if (!data->interrupt_users)
+ return 0;
+
+ data->interrupt_users--;
+ if (data->interrupt_users > 0)
+ return 0;
+ }
+
+ ret = regmap_field_write(data->rf.int_en, state);
+ if (ret) {
+ if (state)
+ data->interrupt_users--;
+ else
+ data->interrupt_users++;
+ }
+
+ return ret;
+}
+
+static int veml6031x00_read_event_val(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)
+{
+ switch (type) {
+ case IIO_EV_TYPE_THRESH:
+ if (dir == IIO_EV_DIR_EITHER && info == IIO_EV_INFO_PERIOD)
+ return veml6031x00_read_period(iio, val);
+
+ return veml6031x00_read_th(iio, dir, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int veml6031x00_write_event_val(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)
+{
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ return veml6031x00_write_th(iio, dir, val, val2);
+ case IIO_EV_INFO_PERIOD:
+ return veml6031x00_write_period(iio, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int veml6031x00_read_event_config(struct iio_dev *iio,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ struct veml6031x00_data *data = iio_priv(iio);
+
+ guard(mutex)(&data->irq_lock);
+
+ return data->ev_en;
+}
+
+static int veml6031x00_event_enable(struct veml6031x00_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = veml6031x00_set_interrupt(data, true);
+ if (ret) {
+ pm_runtime_put_autosuspend(dev);
+ return ret;
+ }
+
+ data->ev_en = true;
+
+ return 0;
+}
+
+static int veml6031x00_event_disable(struct veml6031x00_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+
+ ret = veml6031x00_set_interrupt(data, false);
+ if (ret)
+ return ret;
+
+ data->ev_en = false;
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+}
+
+static int veml6031x00_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 veml6031x00_data *data = iio_priv(iio);
+
+ guard(mutex)(&data->irq_lock);
+
+ /* avoid multiple increments/decrements from one source */
+ if (state == data->ev_en)
+ return 0;
+
+ if (state)
+ return veml6031x00_event_enable(data);
+
+ return veml6031x00_event_disable(data);
+}
+
+static void veml6031x00_disable_event_action(void *arg)
+{
+ struct veml6031x00_data *data = arg;
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+
+ guard(mutex)(&data->irq_lock);
+
+ if (!data->ev_en)
+ return;
+
+ ret = veml6031x00_set_interrupt(data, false);
+ if (ret)
+ dev_err(dev, "Failed to disable events: %d\n", ret);
+ else
+ data->ev_en = false;
+
+ pm_runtime_put_sync(dev);
+}
+
+static const struct iio_info veml6031x00_info = {
+ .read_raw = veml6031x00_read_raw,
+ .read_avail = veml6031x00_read_avail,
+ .write_raw = veml6031x00_write_raw,
+ .write_raw_get_fmt = veml6031x00_write_raw_get_fmt,
+ .read_event_value = veml6031x00_read_event_val,
+ .write_event_value = veml6031x00_write_event_val,
+ .read_event_config = veml6031x00_read_event_config,
+ .write_event_config = veml6031x00_write_event_config,
+ .event_attrs = &veml6031x00_event_attr_group,
+};
+
+static const struct iio_info veml6031x00_info_no_irq = {
+ .read_raw = veml6031x00_read_raw,
+ .read_avail = veml6031x00_read_avail,
+ .write_raw = veml6031x00_write_raw,
+ .write_raw_get_fmt = veml6031x00_write_raw_get_fmt,
+};
+
+static irqreturn_t veml6031x00_irq(int irq, void *private)
+{
+ struct iio_dev *iio = private;
+ struct veml6031x00_data *data = iio_priv(iio);
+ struct device *dev = regmap_get_device(data->regmap);
+ s64 timestamp;
+ unsigned int regval;
+ int ret;
+ bool trigger_poll;
+
+ ret = pm_runtime_get_if_active(dev);
+ if (ret <= 0)
+ return IRQ_NONE;
+
+ scoped_guard(mutex, &data->irq_lock) {
+ ret = regmap_read(data->regmap, VEML6031X00_REG_INT, &regval);
+ if (ret) {
+ dev_dbg(dev, "Failed to read interrupt register %d\n", ret);
+ pm_runtime_put(dev);
+ return IRQ_NONE;
+ }
+
+ if (!(regval & VEML6031X00_INT_MASK)) {
+ pm_runtime_put(dev);
+ return IRQ_NONE;
+ }
+
+ timestamp = iio_get_time_ns(iio);
+
+ if (data->ev_en &&
+ (regval & (VEML6031X00_INT_TH_H | VEML6031X00_INT_TH_L))) {
+ if (regval & VEML6031X00_INT_TH_H)
+ iio_push_event(iio,
+ IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_RISING),
+ timestamp);
+ if (regval & VEML6031X00_INT_TH_L)
+ iio_push_event(iio,
+ IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_FALLING),
+ timestamp);
+ }
+
+ trigger_poll = data->trig_en && (regval & VEML6031X00_INT_DRDY);
+ }
+
+ /*
+ * iio_trigger_poll_nested() must be called with irq_lock released:
+ * it runs trig_handler() synchronously in this thread, which calls
+ * reenable() on completion, and that callback also takes irq_lock.
+ * iio_pollfunc_store_time() is not called for our own trigger, so the
+ * timestamp is stored here.
+ */
+ if (trigger_poll) {
+ iio->pollfunc->timestamp = timestamp;
+ iio_trigger_poll_nested(data->trig);
+ }
+
+ pm_runtime_put(dev);
+
+ return IRQ_HANDLED;
+}
+
+static int veml6031x00_buffer_preenable(struct iio_dev *iio)
+{
+ struct veml6031x00_data *data = iio_priv(iio);
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret, it_usec;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = veml6031x00_get_it(data, &it_usec);
+ if (ret < 0) {
+ pm_runtime_put_autosuspend(dev);
+ return ret;
+ }
+
+ /*
+ * Wait one integration period + 10% margin so the first triggered
+ * read does not race with the sensor completing its first conversion
+ * after power-on.
+ */
+ fsleep(it_usec + (it_usec / 10));
+
+ return 0;
+}
+
+static int veml6031x00_buffer_postdisable(struct iio_dev *iio)
+{
+ struct veml6031x00_data *data = iio_priv(iio);
+
+ pm_runtime_put_autosuspend(regmap_get_device(data->regmap));
+
+ return 0;
+}
+
+static int __veml6031x00_set_trigger_state(struct veml6031x00_data *data,
+ bool state)
+ __must_hold(&data->irq_lock)
+{
+ int ret;
+
+ lockdep_assert_held(&data->irq_lock);
+
+ ret = veml6031x00_set_interrupt(data, state);
+ if (ret)
+ return ret;
+
+ /* The AF bit must be updated before updating AF_TRIG */
+ ret = regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0,
+ VEML6031X00_CONF0_AF, state);
+ if (ret)
+ goto disable_trigger;
+
+ ret = regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0,
+ VEML6031X00_CONF0_AF_TRIG, state);
+ if (ret) {
+ regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0,
+ VEML6031X00_CONF0_AF, false);
+ goto disable_trigger;
+ }
+
+ data->trig_en = state;
+
+ return 0;
+
+disable_trigger:
+ data->trig_en = false;
+ if (state)
+ veml6031x00_set_interrupt(data, false);
+ return ret;
+}
+
+static int veml6031x00_set_trigger_state(struct iio_trigger *trig, bool state)
+{
+ struct iio_dev *iio = iio_trigger_get_drvdata(trig);
+ struct veml6031x00_data *data = iio_priv(iio);
+
+ guard(mutex)(&data->irq_lock);
+
+ if (state == data->trig_en)
+ return 0;
+
+ return __veml6031x00_set_trigger_state(data, state);
+}
+
+static const struct iio_buffer_setup_ops veml6031x00_buffer_setup_ops = {
+ .preenable = veml6031x00_buffer_preenable,
+ .postdisable = veml6031x00_buffer_postdisable,
+};
+
+static void veml6031x00_trigger_reenable(struct iio_trigger *trig)
+{
+ struct iio_dev *iio = iio_trigger_get_drvdata(trig);
+ struct veml6031x00_data *data = iio_priv(iio);
+ int ret;
+
+ guard(mutex)(&data->irq_lock);
+
+ if (!data->trig_en)
+ return;
+
+ ret = regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0,
+ VEML6031X00_CONF0_AF_TRIG, true);
+ if (ret)
+ dev_err(regmap_get_device(data->regmap),
+ "Failed to reenable trigger: %d\n", ret);
+}
+
+static const struct iio_trigger_ops veml6031x00_trigger_ops = {
+ .validate_device = iio_trigger_validate_own_device,
+ .set_trigger_state = veml6031x00_set_trigger_state,
+ .reenable = veml6031x00_trigger_reenable,
+};
+
+static irqreturn_t veml6031x00_trig_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *iio = pf->indio_dev;
+ struct veml6031x00_data *data = iio_priv(iio);
+ struct regmap *map = data->regmap;
+ IIO_DECLARE_BUFFER_WITH_TS(__le16, scan, 2) = { };
+ unsigned int i;
+ int ch, ret;
+
+ if (test_bit(VEML6031X00_SCAN_ALS, iio->active_scan_mask) &&
+ test_bit(VEML6031X00_SCAN_IR, iio->active_scan_mask)) {
+ ret = regmap_bulk_read(map, VEML6031X00_REG_ALS_L,
+ scan, 2 * sizeof(*scan));
+ if (ret)
+ goto done;
+ } else {
+ i = 0;
+ iio_for_each_active_channel(iio, ch) {
+ ret = regmap_bulk_read(map, iio->channels[ch].address,
+ &scan[i++], sizeof(*scan));
+ if (ret)
+ goto done;
+ }
+ }
+
+ iio_push_to_buffers_with_ts(iio, scan, sizeof(scan), pf->timestamp);
+
+done:
+ iio_trigger_notify_done(iio->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int veml6031x00_validate_part_id(struct veml6031x00_data *data)
+{
+ struct regmap *map = data->regmap;
+ struct device *dev = regmap_get_device(map);
+ unsigned int part_id;
+ int ret;
+ __le16 regval;
+
+ ret = regmap_bulk_read(map, VEML6031X00_REG_ID_L, &regval, sizeof(regval));
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read ID\n");
+
+ part_id = le16_to_cpu(regval);
+ if (part_id != data->chip->part_id)
+ dev_info(dev, "Unknown or unexpected ID %04x\n", part_id);
+
+ return 0;
+}
+
+static int veml6031x00_hw_init(struct veml6031x00_data *data)
+{
+ struct regmap *map = data->regmap;
+ struct device *dev = regmap_get_device(map);
+ unsigned int val;
+ __le16 regval;
+ int ret;
+
+ ret = regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0,
+ VEML6031X00_CONF0_AF | VEML6031X00_CONF0_AF_TRIG, 0);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to disable trigger\n");
+
+ ret = regmap_set_bits(data->regmap, VEML6031X00_REG_CONF1,
+ VEML6031X00_CONF1_ALS_CAL);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable internal calibration\n");
+
+ regval = 0;
+ ret = regmap_bulk_write(map, VEML6031X00_REG_WL_L, &regval, sizeof(regval));
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to set low threshold\n");
+
+ regval = cpu_to_le16(U16_MAX);
+ ret = regmap_bulk_write(map, VEML6031X00_REG_WH_L, &regval, sizeof(regval));
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to set high threshold\n");
+
+ ret = regmap_field_write(data->rf.int_en, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(map, VEML6031X00_REG_INT, &val);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to clear interrupts\n");
+
+ return 0;
+}
+
+static int veml6031x00_setup_trigger(struct iio_dev *iio)
+{
+ struct veml6031x00_data *data = iio_priv(iio);
+ struct device *dev = regmap_get_device(data->regmap);
+
+ data->trig = devm_iio_trigger_alloc(dev, "%s-drdy%d",
+ iio->name, iio_device_id(iio));
+ if (!data->trig)
+ return -ENOMEM;
+
+ data->trig->ops = &veml6031x00_trigger_ops;
+ iio_trigger_set_drvdata(data->trig, iio);
+
+ return devm_iio_trigger_register(dev, data->trig);
+}
+
+static int veml6031x00_init_iiodev(struct i2c_client *i2c, struct iio_dev *iio)
+{
+ struct veml6031x00_data *data = iio_priv(iio);
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+
+ iio->name = data->chip->name;
+ iio->modes = INDIO_DIRECT_MODE;
+
+ if (!i2c->irq) {
+ iio->channels = veml6031x00_channels;
+ iio->num_channels = ARRAY_SIZE(veml6031x00_channels);
+ iio->info = &veml6031x00_info_no_irq;
+
+ return 0;
+ }
+
+ iio->channels = veml6031x00_channels_irq;
+ iio->num_channels = ARRAY_SIZE(veml6031x00_channels_irq);
+ iio->info = &veml6031x00_info;
+
+ ret = veml6031x00_setup_trigger(iio);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, veml6031x00_disable_event_action, data);
+}
+
+static int veml6031x00_probe(struct i2c_client *i2c)
+{
+ struct device *dev = &i2c->dev;
+ struct veml6031x00_data *data;
+ struct iio_dev *iio;
+ int ret;
+
+ iio = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!iio)
+ return -ENOMEM;
+
+ data = iio_priv(iio);
+ i2c_set_clientdata(i2c, iio);
+
+ data->chip = i2c_get_match_data(i2c);
+ if (!data->chip)
+ return dev_err_probe(dev, -ENODATA, "Failed to get chip data\n");
+
+ ret = devm_mutex_init(dev, &data->scale_lock);
+ if (ret)
+ return ret;
+
+ ret = devm_mutex_init(dev, &data->irq_lock);
+ if (ret)
+ return ret;
+
+ data->regmap = devm_regmap_init_i2c(i2c, &veml6031x00_regmap_config);
+ if (IS_ERR(data->regmap))
+ return dev_err_probe(dev, PTR_ERR(data->regmap),
+ "Failed to set regmap\n");
+
+ ret = veml6031x00_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");
+
+ /* The device starts in power down mode by default */
+ ret = veml6031x00_set_power(data, true);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to power on the device\n");
+
+ ret = devm_add_action_or_reset(dev, veml6031x00_als_shutdown_action, data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add shutdown action\n");
+
+ pm_runtime_set_autosuspend_delay(dev, 2000);
+ pm_runtime_use_autosuspend(dev);
+ ret = devm_pm_runtime_set_active_enabled(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable runtime PM\n");
+
+ ret = veml6031x00_validate_part_id(data);
+ if (ret)
+ return ret;
+
+ ret = veml6031x00_hw_init(data);
+ if (ret)
+ return ret;
+
+ /* Max resolution = 6.9632 lx/cnt for gain = 0.125 and IT = 3.125ms */
+ ret = devm_iio_init_iio_gts(dev, 6, 963200000,
+ veml6031x00_gain_sel,
+ ARRAY_SIZE(veml6031x00_gain_sel),
+ veml6031x00_it_sel,
+ ARRAY_SIZE(veml6031x00_it_sel),
+ &data->gts);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to init IIO GTS\n");
+
+ ret = veml6031x00_init_iiodev(i2c, iio);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to initialize IIO device\n");
+
+ ret = devm_iio_triggered_buffer_setup(dev, iio,
+ iio_pollfunc_store_time,
+ veml6031x00_trig_handler,
+ &veml6031x00_buffer_setup_ops);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register triggered buffer\n");
+
+ if (i2c->irq) {
+ ret = devm_request_threaded_irq(dev, i2c->irq, NULL, veml6031x00_irq,
+ IRQF_ONESHOT, iio->name, iio);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request IRQ\n");
+ }
+
+ ret = devm_iio_device_register(dev, iio);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register IIO device\n");
+
+ return 0;
+}
+
+static int veml6031x00_runtime_suspend(struct device *dev)
+{
+ struct veml6031x00_data *data = iio_priv(dev_get_drvdata(dev));
+
+ return veml6031x00_set_power(data, false);
+}
+
+static int veml6031x00_runtime_resume(struct device *dev)
+{
+ struct veml6031x00_data *data = iio_priv(dev_get_drvdata(dev));
+
+ return veml6031x00_set_power(data, true);
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(veml6031x00_pm_ops,
+ veml6031x00_runtime_suspend,
+ veml6031x00_runtime_resume,
+ NULL);
+
+static const struct veml6031x00_chip veml6031x00_chip = {
+ .name = "veml6031x00",
+ .part_id = 0x0001,
+};
+
+static const struct veml6031x00_chip veml6031x01_chip = {
+ .name = "veml6031x01",
+ .part_id = 0x0001,
+};
+
+static const struct veml6031x00_chip veml60311x00_chip = {
+ .name = "veml60311x00",
+ .part_id = 0x1001,
+};
+
+static const struct veml6031x00_chip veml60311x01_chip = {
+ .name = "veml60311x01",
+ .part_id = 0x1001,
+};
+
+static const struct of_device_id veml6031x00_of_match[] = {
+ {
+ .compatible = "vishay,veml6031x00",
+ .data = &veml6031x00_chip,
+ },
+ {
+ .compatible = "vishay,veml6031x01",
+ .data = &veml6031x01_chip,
+ },
+ {
+ .compatible = "vishay,veml60311x00",
+ .data = &veml60311x00_chip,
+ },
+ {
+ .compatible = "vishay,veml60311x01",
+ .data = &veml60311x01_chip,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, veml6031x00_of_match);
+
+static const struct i2c_device_id veml6031x00_id[] = {
+ {
+ .name = "veml6031x00",
+ .driver_data = (kernel_ulong_t)&veml6031x00_chip,
+ },
+ {
+ .name = "veml6031x01",
+ .driver_data = (kernel_ulong_t)&veml6031x01_chip,
+ },
+ {
+ .name = "veml60311x00",
+ .driver_data = (kernel_ulong_t)&veml60311x00_chip,
+ },
+ {
+ .name = "veml60311x01",
+ .driver_data = (kernel_ulong_t)&veml60311x01_chip,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, veml6031x00_id);
+
+static struct i2c_driver veml6031x00_driver = {
+ .driver = {
+ .name = "veml6031x00",
+ .of_match_table = veml6031x00_of_match,
+ .pm = pm_ptr(&veml6031x00_pm_ops),
+ },
+ .probe = veml6031x00_probe,
+ .id_table = veml6031x00_id,
+};
+module_i2c_driver(veml6031x00_driver);
+
+MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gmail.com>");
+MODULE_DESCRIPTION("VEML6031X00 Ambient Light Sensor");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_GTS_HELPER");
diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c
index 8b0c07f82602..cc5be1fa91ea 100644
--- a/drivers/iio/magnetometer/ak8975.c
+++ b/drivers/iio/magnetometer/ak8975.c
@@ -44,8 +44,7 @@
#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_ST1_DRDY_MASK BIT(0)
#define AK8975_REG_HXL 0x03
#define AK8975_REG_HXH 0x04
@@ -54,15 +53,12 @@
#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_DERR_MASK BIT(2)
-#define AK8975_REG_ST2_HOFL_SHIFT 3
-#define AK8975_REG_ST2_HOFL_MASK (1 << AK8975_REG_ST2_HOFL_SHIFT)
+#define AK8975_REG_ST2_HOFL_MASK BIT(3)
#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_MASK GENMASK(3, 0)
#define AK8975_REG_CNTL_MODE_POWER_DOWN 0x00
#define AK8975_REG_CNTL_MODE_ONCE 0x01
#define AK8975_REG_CNTL_MODE_SELF_TEST 0x08
@@ -94,8 +90,7 @@
#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_ST1_DRDY_MASK BIT(0)
#define AK09912_REG_HXL 0x11
#define AK09912_REG_HXH 0x12
@@ -106,8 +101,7 @@
#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_ST2_HOFL_MASK BIT(3)
#define AK09912_REG_CNTL1 0x30
@@ -116,8 +110,7 @@
#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_CNTL2_MODE_MASK GENMASK(4, 0)
#define AK09912_REG_CNTL3 0x32
@@ -237,6 +230,13 @@ enum ak_ctrl_mode {
MODE_END,
};
+enum ak_chan_index {
+ AK8975_CHAN_X,
+ AK8975_CHAN_Y,
+ AK8975_CHAN_Z,
+ AK8975_CHAN_TS,
+};
+
struct ak_def {
enum asahi_compass_chipset type;
long (*raw_to_gauss)(u16 data);
@@ -418,6 +418,7 @@ struct ak8975_data {
wait_queue_head_t data_ready_queue;
unsigned long flags;
u8 cntl_cache;
+ bool powered_on;
struct iio_mount_matrix orientation;
struct regulator *vdd;
struct regulator *vid;
@@ -430,20 +431,19 @@ struct ak8975_data {
};
/* Enable attached power regulator if any. */
-static int ak8975_power_on(const struct ak8975_data *data)
+static int ak8975_power_on(struct ak8975_data *data)
{
+ struct device *dev = &data->client->dev;
int ret;
ret = regulator_enable(data->vdd);
if (ret) {
- dev_warn(&data->client->dev,
- "Failed to enable specified Vdd supply\n");
+ dev_warn(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");
+ dev_warn(dev, "Failed to enable specified Vid supply\n");
regulator_disable(data->vdd);
return ret;
}
@@ -457,16 +457,23 @@ static int ak8975_power_on(const struct ak8975_data *data)
*/
fsleep(500);
+ data->powered_on = true;
+
return 0;
}
/* Disable attached power regulator if any. */
-static void ak8975_power_off(const struct ak8975_data *data)
+static void ak8975_power_off(struct ak8975_data *data)
{
+ if (!data->powered_on)
+ return;
+
gpiod_set_value_cansleep(data->reset_gpiod, 1);
regulator_disable(data->vid);
regulator_disable(data->vdd);
+
+ data->powered_on = false;
}
/*
@@ -494,14 +501,10 @@ static int ak8975_who_i_am(const struct ak8975_data *data,
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 (ret < 0)
+ return dev_err_probe(&client->dev, ret, "Error reading WIA\n");
+ if (ret != sizeof(wia_val))
+ return dev_err_probe(&client->dev, -EIO, "Error reading WIA\n");
if (wia_val[0] != AK8975_DEVICE_ID)
return -ENODEV;
@@ -577,6 +580,7 @@ static irqreturn_t ak8975_irq_handler(int irq, void *data)
static int ak8975_setup_irq(struct ak8975_data *data)
{
struct i2c_client *client = data->client;
+ struct device *dev = &client->dev;
int irq;
int ret;
@@ -587,9 +591,8 @@ static int ak8975_setup_irq(struct ak8975_data *data)
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);
+ ret = devm_request_irq(dev, irq, ak8975_irq_handler, IRQF_TRIGGER_RISING,
+ dev_name(dev), data);
if (ret)
return ret;
@@ -605,43 +608,33 @@ static int ak8975_setup_irq(struct ak8975_data *data)
static int ak8975_setup(struct ak8975_data *data)
{
struct i2c_client *client = data->client;
+ struct device *dev = &client->dev;
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;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Error in setting fuse access mode\n");
/* 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;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Not able to read asa data\n");
+ if (ret != sizeof(data->asa))
+ return dev_err_probe(dev, -EIO, "Error reading asa data\n");
/* 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 (ret < 0)
+ return dev_err_probe(dev, ret, "Error in setting power-down mode\n");
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;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Error setting data ready interrupt\n");
}
data->raw_to_gauss[0] = data->def->raw_to_gauss(data->asa[0]);
@@ -745,10 +738,11 @@ 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;
+ struct device *dev = &data->client->dev;
__le16 rval;
int ret;
- pm_runtime_get_sync(&data->client->dev);
+ pm_runtime_get_sync(dev);
mutex_lock(&data->lock);
@@ -770,20 +764,20 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val)
/* 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");
+ dev_err(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);
+ dev_err(dev, "ST2 status error 0x%x\n", ret);
ret = -EINVAL;
goto exit;
}
mutex_unlock(&data->lock);
- pm_runtime_put_autosuspend(&data->client->dev);
+ pm_runtime_put_autosuspend(dev);
/* Swap bytes and convert to valid range. */
*val = clamp_t(s16, le16_to_cpu(rval), -def->range, def->range);
@@ -792,8 +786,8 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val)
exit:
mutex_unlock(&data->lock);
- pm_runtime_put_autosuspend(&data->client->dev);
- dev_err(&client->dev, "Error in reading axis\n");
+ pm_runtime_put_autosuspend(dev);
+ dev_err(dev, "Error in reading axis\n");
return ret;
}
@@ -848,11 +842,16 @@ static const struct iio_chan_spec_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),
+ AK8975_CHANNEL(X, AK8975_CHAN_X),
+ AK8975_CHANNEL(Y, AK8975_CHAN_Y),
+ AK8975_CHANNEL(Z, AK8975_CHAN_Z),
+ IIO_CHAN_SOFT_TIMESTAMP(AK8975_CHAN_TS),
};
-static const unsigned long ak8975_scan_masks[] = { 0x7, 0 };
+static const unsigned long ak8975_scan_masks[] = {
+ BIT(AK8975_CHAN_X) | BIT(AK8975_CHAN_Y) | BIT(AK8975_CHAN_Z),
+ 0
+};
static const struct iio_info ak8975_info = {
.read_raw = &ak8975_read_raw,
@@ -934,9 +933,23 @@ static const struct iio_buffer_setup_ops ak8975_buffer_setup_ops = {
.preenable = ak8975_buffer_preenable,
.postdisable = ak8975_buffer_postdisable,
};
+
+static void devm_ak8975_power_off(void *data)
+{
+ struct ak8975_data *ak = data;
+
+ if (!ak->powered_on)
+ return;
+
+ /* Soft-stop the chip before hard-stopping the regulators */
+ ak8975_set_mode(data, POWER_DOWN);
+ ak8975_power_off(data);
+}
+
static int ak8975_probe(struct i2c_client *client)
{
const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct device *dev = &client->dev;
struct ak8975_data *data;
struct iio_dev *indio_dev;
struct gpio_desc *eoc_gpiod;
@@ -949,7 +962,7 @@ static int ak8975_probe(struct i2c_client *client)
* 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);
+ eoc_gpiod = devm_gpiod_get_optional(dev, NULL, GPIOD_IN);
if (IS_ERR(eoc_gpiod))
return PTR_ERR(eoc_gpiod);
gpiod_set_consumer_name(eoc_gpiod, "ak_8975");
@@ -959,13 +972,12 @@ static int ak8975_probe(struct i2c_client *client)
* 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);
+ reset_gpiod = devm_gpiod_get_optional(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));
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (indio_dev == NULL)
return -ENOMEM;
@@ -977,7 +989,7 @@ static int ak8975_probe(struct i2c_client *client)
data->reset_gpiod = reset_gpiod;
data->eoc_irq = 0;
- ret = iio_read_mount_matrix(&client->dev, &data->orientation);
+ ret = iio_read_mount_matrix(dev, &data->orientation);
if (ret)
return ret;
@@ -987,16 +999,16 @@ static int ak8975_probe(struct i2c_client *client)
return -ENODEV;
/* If enumerated via firmware node, fix the ABI */
- if (dev_fwnode(&client->dev))
- name = dev_name(&client->dev);
+ if (dev_fwnode(dev))
+ name = dev_name(dev);
else
name = id->name;
/* Fetch the regulators */
- data->vdd = devm_regulator_get(&client->dev, "vdd");
+ data->vdd = devm_regulator_get(dev, "vdd");
if (IS_ERR(data->vdd))
return PTR_ERR(data->vdd);
- data->vid = devm_regulator_get(&client->dev, "vid");
+ data->vid = devm_regulator_get(dev, "vid");
if (IS_ERR(data->vid))
return PTR_ERR(data->vid);
@@ -1004,21 +1016,25 @@ static int ak8975_probe(struct i2c_client *client)
if (ret)
return ret;
+ ret = devm_add_action_or_reset(dev, devm_ak8975_power_off, 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);
+ if (ret)
+ return dev_err_probe(dev, ret, "Unexpected device\n");
+
+ dev_dbg(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;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "%s initialization fails\n", name);
+
+ ret = devm_mutex_init(dev, &data->lock);
+ if (ret)
+ return ret;
- mutex_init(&data->lock);
indio_dev->channels = ak8975_channels;
indio_dev->num_channels = ARRAY_SIZE(ak8975_channels);
indio_dev->info = &ak8975_info;
@@ -1026,52 +1042,29 @@ static int ak8975_probe(struct i2c_client *client)
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 = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
+ ak8975_handle_trigger,
+ &ak8975_buffer_setup_ops);
+ if (ret)
+ return dev_err_probe(dev, ret, "triggered buffer setup failed\n");
- ret = iio_device_register(indio_dev);
- if (ret) {
- dev_err(&client->dev, "device register failed\n");
- goto cleanup_buffer;
- }
+ pm_runtime_set_active(dev);
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "device register failed\n");
- /* 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);
+ pm_runtime_set_autosuspend_delay(dev, 50);
+ pm_runtime_use_autosuspend(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)
@@ -1084,7 +1077,7 @@ static int ak8975_runtime_suspend(struct device *dev)
/* 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");
+ dev_err(dev, "Error in setting power-down mode\n");
return ret;
}
/* Next cut the regulators */
@@ -1108,7 +1101,7 @@ static int ak8975_runtime_resume(struct device *dev)
*/
ret = ak8975_set_mode(data, POWER_DOWN);
if (ret < 0) {
- dev_err(&client->dev, "Error in setting power-down mode\n");
+ dev_err(dev, "Error in setting power-down mode\n");
return ret;
}
@@ -1165,7 +1158,6 @@ static struct i2c_driver ak8975_driver = {
.acpi_match_table = ak_acpi_match,
},
.probe = ak8975_probe,
- .remove = ak8975_remove,
.id_table = ak8975_id,
};
module_i2c_driver(ak8975_driver);
diff --git a/drivers/iio/position/iqs624-pos.c b/drivers/iio/position/iqs624-pos.c
index 8239239c6ee2..d2aa2df71457 100644
--- a/drivers/iio/position/iqs624-pos.c
+++ b/drivers/iio/position/iqs624-pos.c
@@ -96,19 +96,6 @@ static int iqs624_pos_notifier(struct notifier_block *notifier,
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;
@@ -255,19 +242,14 @@ static int iqs624_pos_probe(struct platform_device *pdev)
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);
+ ret = devm_blocking_notifier_chain_register(&pdev->dev,
+ &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);
}
diff --git a/drivers/iio/potentiometer/x9250.c b/drivers/iio/potentiometer/x9250.c
index 974e4447fc2c..675b78677baa 100644
--- a/drivers/iio/potentiometer/x9250.c
+++ b/drivers/iio/potentiometer/x9250.c
@@ -155,10 +155,15 @@ static const char *const x9250_regulator_names[] = {
static int x9250_probe(struct spi_device *spi)
{
+ const struct x9250_cfg *cfg;
struct iio_dev *indio_dev;
struct x9250 *x9250;
int ret;
+ cfg = spi_get_device_match_data(spi);
+ if (!cfg)
+ return -ENODATA;
+
ret = devm_regulator_bulk_get_enable(&spi->dev, ARRAY_SIZE(x9250_regulator_names),
x9250_regulator_names);
if (ret)
@@ -176,7 +181,7 @@ static int x9250_probe(struct spi_device *spi)
x9250 = iio_priv(indio_dev);
x9250->spi = spi;
- x9250->cfg = spi_get_device_match_data(spi);
+ x9250->cfg = cfg;
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),
diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig
index 838a8340c4c0..cef8b90b9ae7 100644
--- a/drivers/iio/pressure/Kconfig
+++ b/drivers/iio/pressure/Kconfig
@@ -112,6 +112,8 @@ config DPS310
tristate "Infineon DPS310 pressure and temperature sensor"
depends on I2C
select REGMAP_I2C
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
help
Support for the Infineon DPS310 digital barometric pressure sensor.
It can be accessed over I2C bus.
diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile
index bc0d11a20acc..4b1a05f7a0bd 100644
--- a/drivers/iio/pressure/Makefile
+++ b/drivers/iio/pressure/Makefile
@@ -3,6 +3,8 @@
# Makefile for industrial I/O pressure drivers
#
+CONTEXT_ANALYSIS_dps310.o := y
+
# When adding new entries keep the list in alphabetical order
obj-$(CONFIG_ABP060MG) += abp060mg.o
obj-$(CONFIG_ABP2030PA) += abp2030pa.o
diff --git a/drivers/iio/pressure/bmp280-spi.c b/drivers/iio/pressure/bmp280-spi.c
index a02a621b8015..95d9635f4711 100644
--- a/drivers/iio/pressure/bmp280-spi.c
+++ b/drivers/iio/pressure/bmp280-spi.c
@@ -83,6 +83,8 @@ static int bmp280_spi_probe(struct spi_device *spi)
struct regmap *regmap;
chip_info = spi_get_device_match_data(spi);
+ if (!chip_info)
+ return -ENODATA;
if (chip_info->spi_read_extra_byte)
bmp_regmap_bus = &bmp380_regmap_bus;
diff --git a/drivers/iio/pressure/cros_ec_baro.c b/drivers/iio/pressure/cros_ec_baro.c
index 6a567b6075d9..e4bc4df48d01 100644
--- a/drivers/iio/pressure/cros_ec_baro.c
+++ b/drivers/iio/pressure/cros_ec_baro.c
@@ -192,7 +192,7 @@ 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,
+ .pm = pm_sleep_ptr(&cros_ec_sensors_pm_ops),
},
.probe = cros_ec_baro_probe,
.id_table = cros_ec_baro_ids,
diff --git a/drivers/iio/pressure/dps310.c b/drivers/iio/pressure/dps310.c
index 45bdb8c7670f..85df58ac1809 100644
--- a/drivers/iio/pressure/dps310.c
+++ b/drivers/iio/pressure/dps310.c
@@ -14,14 +14,19 @@
* - Optionally support the FIFO
*/
+#include <linux/cleanup.h>
#include <linux/i2c.h>
#include <linux/limits.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/regmap.h>
+#include <linux/unaligned.h>
+#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
#define DPS310_DEV_NAME "dps310"
@@ -50,9 +55,9 @@
#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_PRS_SHIFT_EN BIT(2)
+#define DPS310_FIFO_EN BIT(1)
+#define DPS310_SPI_EN BIT(0)
#define DPS310_RESET 0x0c
#define DPS310_RESET_MAGIC 0x09
#define DPS310_COEF_BASE 0x10
@@ -92,19 +97,51 @@ struct dps310_data {
bool timeout_recovery_failed;
};
+enum dps310_scan_index {
+ DPS310_SCAN_TEMP,
+ DPS310_SCAN_PRESSURE,
+ DPS310_SCAN_TIMESTAMP,
+};
+
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),
+ .scan_index = DPS310_SCAN_TEMP,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 32,
+ .storagebits = 32,
+ .endianness = IIO_CPU,
+ },
},
{
.type = IIO_PRESSURE,
+ /*
+ * _raw here is already compensated (section 4.9.1, which needs
+ * a temperature too) and in Pa; _scale converts to the kPa the
+ * ABI wants. _processed predates buffers and has to stay.
+ *
+ * Do not copy this into other drivers. A _raw that is not the
+ * raw register value is only tolerable because the alternative
+ * is losing resolution or breaking existing _processed users.
+ */
.info_mask_separate = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) |
BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_PROCESSED),
+ .scan_index = DPS310_SCAN_PRESSURE,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 32,
+ .storagebits = 32,
+ .endianness = IIO_CPU,
+ },
},
+ IIO_CHAN_SOFT_TIMESTAMP(DPS310_SCAN_TIMESTAMP),
};
/* To be called after checking the COEF_RDY bit in MEAS_CFG */
@@ -286,8 +323,8 @@ static int dps310_get_temp_precision(struct dps310_data *data, int *val)
return 0;
}
-/* Called with lock held */
static int dps310_set_pres_precision(struct dps310_data *data, int val)
+ __must_hold(&data->lock)
{
int rc;
u8 shift_en;
@@ -305,8 +342,8 @@ static int dps310_set_pres_precision(struct dps310_data *data, int val)
DPS310_PRS_PRC_BITS, ilog2(val));
}
-/* Called with lock held */
static int dps310_set_temp_precision(struct dps310_data *data, int val)
+ __must_hold(&data->lock)
{
int rc;
u8 shift_en;
@@ -324,8 +361,8 @@ static int dps310_set_temp_precision(struct dps310_data *data, int val)
DPS310_TMP_PRC_BITS, ilog2(val));
}
-/* Called with lock held */
static int dps310_set_pres_samp_freq(struct dps310_data *data, int freq)
+ __must_hold(&data->lock)
{
u8 val;
@@ -338,8 +375,8 @@ static int dps310_set_pres_samp_freq(struct dps310_data *data, int freq)
DPS310_PRS_RATE_BITS, val);
}
-/* Called with lock held */
static int dps310_set_temp_samp_freq(struct dps310_data *data, int freq)
+ __must_hold(&data->lock)
{
u8 val;
@@ -438,6 +475,7 @@ static int dps310_ready_status(struct dps310_data *data, int ready_bit, int time
}
static int dps310_ready(struct dps310_data *data, int ready_bit, int timeout)
+ __must_hold(&data->lock)
{
int rc;
@@ -463,82 +501,83 @@ static int dps310_ready(struct dps310_data *data, int ready_bit, int timeout)
return 0;
}
-static int dps310_read_pres_raw(struct dps310_data *data)
+static int dps310_read_pres_raw_locked(struct dps310_data *data)
+ __must_hold(&data->lock)
{
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;
+ return rc;
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;
+ return rc;
rc = regmap_bulk_read(data->regmap, DPS310_PRS_BASE, val, sizeof(val));
if (rc < 0)
- goto done;
+ return rc;
- raw = (val[0] << 16) | (val[1] << 8) | val[2];
- data->pressure_raw = sign_extend32(raw, 23);
+ data->pressure_raw = sign_extend32(get_unaligned_be24(val), 23);
-done:
- mutex_unlock(&data->lock);
- return rc;
+ return 0;
}
-/* Called with lock held */
static int dps310_read_temp_ready(struct dps310_data *data)
+ __must_hold(&data->lock)
{
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);
+ data->temp_raw = sign_extend32(get_unaligned_be24(val), 23);
return 0;
}
-static int dps310_read_temp_raw(struct dps310_data *data)
+static int dps310_read_temp_raw_locked(struct dps310_data *data)
+ __must_hold(&data->lock)
{
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;
+ return rc;
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;
+ return rc;
- rc = dps310_read_temp_ready(data);
+ return dps310_read_temp_ready(data);
+}
-done:
- mutex_unlock(&data->lock);
- return rc;
+/* Best effort: on error the previous temperature stands */
+static void dps310_refresh_temp_locked(struct dps310_data *data)
+ __must_hold(&data->lock)
+{
+ int rc;
+ int t_ready;
+
+ rc = regmap_read(data->regmap, DPS310_MEAS_CFG, &t_ready);
+ if (rc)
+ return;
+
+ if (t_ready & DPS310_TMP_RDY)
+ dps310_read_temp_ready(data);
}
static bool dps310_is_writeable_reg(struct device *dev, unsigned int reg)
@@ -580,59 +619,52 @@ 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))
+ /* Reconfiguring mid-capture would change the values being captured */
+ IIO_DEV_ACQUIRE_DIRECT_MODE(iio, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ ACQUIRE(mutex_intr, lock)(&data->lock);
+ if (ACQUIRE_ERR(mutex_intr, &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;
+ return dps310_set_pres_samp_freq(data, val);
case IIO_TEMP:
- rc = dps310_set_temp_samp_freq(data, val);
- break;
+ return dps310_set_temp_samp_freq(data, val);
default:
- rc = -EINVAL;
- break;
+ return -EINVAL;
}
- break;
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
switch (chan->type) {
case IIO_PRESSURE:
- rc = dps310_set_pres_precision(data, val);
- break;
+ return dps310_set_pres_precision(data, val);
case IIO_TEMP:
- rc = dps310_set_temp_precision(data, val);
- break;
+ return dps310_set_temp_precision(data, val);
default:
- rc = -EINVAL;
- break;
+ return -EINVAL;
}
- break;
default:
- rc = -EINVAL;
- break;
+ return -EINVAL;
}
-
- mutex_unlock(&data->lock);
- return rc;
}
static int dps310_calculate_pressure(struct dps310_data *data, int *val)
+ __must_hold(&data->lock)
{
int i;
int rc;
- int t_ready;
int kpi;
int kti;
s64 rem = 0ULL;
@@ -656,15 +688,6 @@ static int dps310_calculate_pressure(struct dps310_data *data, int *val)
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;
@@ -710,6 +733,23 @@ static int dps310_calculate_pressure(struct dps310_data *data, int *val)
return 0;
}
+static int dps310_read_pressure_value(struct dps310_data *data, int *val)
+{
+ int rc;
+
+ ACQUIRE(mutex_intr, lock)(&data->lock);
+ if (ACQUIRE_ERR(mutex_intr, &lock))
+ return -EINTR;
+
+ rc = dps310_read_pres_raw_locked(data);
+ if (rc)
+ return rc;
+
+ dps310_refresh_temp_locked(data);
+
+ return dps310_calculate_pressure(data, val);
+}
+
static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2,
long mask)
{
@@ -723,18 +763,27 @@ static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2,
return IIO_VAL_INT;
- case IIO_CHAN_INFO_PROCESSED:
- rc = dps310_read_pres_raw(data);
+ case IIO_CHAN_INFO_RAW:
+ rc = dps310_read_pressure_value(data, val);
if (rc)
return rc;
- rc = dps310_calculate_pressure(data, val);
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_PROCESSED:
+ rc = dps310_read_pressure_value(data, val);
if (rc)
return rc;
*val2 = 1000; /* Convert Pa to KPa per IIO ABI */
return IIO_VAL_FRACTIONAL;
+ case IIO_CHAN_INFO_SCALE:
+ /* The raw value is in Pa, the ABI wants kPa */
+ *val = 1;
+ *val2 = 1000;
+ return IIO_VAL_FRACTIONAL;
+
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
rc = dps310_get_pres_precision(data, val);
if (rc)
@@ -747,6 +796,7 @@ static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2,
}
static int dps310_calculate_temp(struct dps310_data *data, int *val)
+ __must_hold(&data->lock)
{
s64 c0;
s64 t;
@@ -768,6 +818,21 @@ static int dps310_calculate_temp(struct dps310_data *data, int *val)
return 0;
}
+static int dps310_read_temp_value(struct dps310_data *data, int *val)
+{
+ int rc;
+
+ ACQUIRE(mutex_intr, lock)(&data->lock);
+ if (ACQUIRE_ERR(mutex_intr, &lock))
+ return -EINTR;
+
+ rc = dps310_read_temp_raw_locked(data);
+ if (rc)
+ return rc;
+
+ return dps310_calculate_temp(data, val);
+}
+
static int dps310_read_temp(struct dps310_data *data, int *val, int *val2,
long mask)
{
@@ -782,11 +847,7 @@ static int dps310_read_temp(struct dps310_data *data, int *val, int *val2,
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);
+ rc = dps310_read_temp_value(data, val);
if (rc)
return rc;
@@ -804,12 +865,10 @@ static int dps310_read_temp(struct dps310_data *data, int *val, int *val2,
}
}
-static int dps310_read_raw(struct iio_dev *iio,
- struct iio_chan_spec const *chan,
- int *val, int *val2, long mask)
+static int dps310_read_channel(struct dps310_data *data,
+ 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);
@@ -822,6 +881,87 @@ static int dps310_read_raw(struct iio_dev *iio,
}
}
+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 (mask) {
+ case IIO_CHAN_INFO_RAW:
+ case IIO_CHAN_INFO_PROCESSED: {
+ /* This consumes the measurement the capture path reads */
+ IIO_DEV_ACQUIRE_DIRECT_MODE(iio, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ return dps310_read_channel(data, chan, val, val2, mask);
+ }
+ default:
+ return dps310_read_channel(data, chan, val, val2, mask);
+ }
+}
+
+static int dps310_fill_channels(struct dps310_data *data,
+ const unsigned long *scan_mask,
+ s32 channels[2])
+ __must_hold(&data->lock)
+{
+ unsigned int i;
+ int rc;
+
+ /* Compensation needs a temperature, so it is sampled either way */
+ rc = dps310_read_temp_raw_locked(data);
+ if (rc)
+ return rc;
+
+ i = 0;
+ if (test_bit(DPS310_SCAN_TEMP, scan_mask)) {
+ /* Millidegrees Celsius */
+ rc = dps310_calculate_temp(data, &channels[i++]);
+ if (rc)
+ return rc;
+ }
+
+ if (test_bit(DPS310_SCAN_PRESSURE, scan_mask)) {
+ rc = dps310_read_pres_raw_locked(data);
+ if (rc)
+ return rc;
+
+ /* Pascals, see the channel definition */
+ rc = dps310_calculate_pressure(data, &channels[i++]);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static irqreturn_t dps310_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *iio = pf->indio_dev;
+ struct dps310_data *data = iio_priv(iio);
+ struct {
+ s32 channels[2];
+ aligned_s64 timestamp;
+ } scan = { };
+ int rc;
+
+ mutex_lock(&data->lock);
+ rc = dps310_fill_channels(data, iio->active_scan_mask, scan.channels);
+ mutex_unlock(&data->lock);
+ if (rc)
+ goto err;
+
+ iio_push_to_buffers_with_ts(iio, &scan, sizeof(scan), iio_get_time_ns(iio));
+
+err:
+ iio_trigger_notify_done(iio->trig);
+
+ return IRQ_HANDLED;
+}
+
static void dps310_reset(void *action_data)
{
struct dps310_data *data = action_data;
@@ -845,11 +985,12 @@ static const struct iio_info dps310_info = {
static int dps310_probe(struct i2c_client *client)
{
+ struct device *dev = &client->dev;
struct dps310_data *data;
struct iio_dev *iio;
int rc;
- iio = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ iio = devm_iio_device_alloc(dev, sizeof(*data));
if (!iio)
return -ENOMEM;
@@ -868,7 +1009,7 @@ static int dps310_probe(struct i2c_client *client)
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);
+ rc = devm_add_action_or_reset(dev, dps310_reset, data);
if (rc)
return rc;
@@ -876,7 +1017,17 @@ static int dps310_probe(struct i2c_client *client)
if (rc)
return rc;
- rc = devm_iio_device_register(&client->dev, iio);
+ /*
+ * The device measures continuously in background mode, so a capture is
+ * just a read of the latest results. The trigger is not aligned with
+ * the measurements, so the timestamp is taken in the handler.
+ */
+ rc = devm_iio_triggered_buffer_setup(dev, iio, NULL,
+ dps310_trigger_handler, NULL);
+ if (rc)
+ return rc;
+
+ rc = devm_iio_device_register(dev, iio);
if (rc)
return rc;
diff --git a/drivers/iio/pressure/rohm-bm1390.c b/drivers/iio/pressure/rohm-bm1390.c
index f9deb48e6a41..7a6a16cffb66 100644
--- a/drivers/iio/pressure/rohm-bm1390.c
+++ b/drivers/iio/pressure/rohm-bm1390.c
@@ -267,7 +267,7 @@ static int bm1390_read_data(struct bm1390_data *data,
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.
+ * data-sheet a one-shot mode can't be used with IIR filter.
*/
ret = bm1390_meas_set(data, BM1390_MEAS_MODE_CONTINUOUS);
if (ret)
@@ -479,6 +479,7 @@ static const struct iio_info bm1390_info = {
static int bm1390_chip_init(struct bm1390_data *data)
{
+ u8 regval;
int ret;
ret = regmap_write_bits(data->regmap, BM1390_REG_POWER,
@@ -512,8 +513,9 @@ static int bm1390_chip_init(struct bm1390_data *data)
* Default to use IIR filter in "middle" mode. Also the AVE_NUM must
* be fixed when IIR is in use.
*/
+ regval = FIELD_PREP(BM1390_MASK_AVE_NUM, BM1390_IIR_AVE_NUM);
ret = regmap_update_bits(data->regmap, BM1390_REG_MODE_CTRL,
- BM1390_MASK_AVE_NUM, BM1390_IIR_AVE_NUM);
+ BM1390_MASK_AVE_NUM, regval);
if (ret)
return ret;
diff --git a/drivers/iio/proximity/aw96103.c b/drivers/iio/proximity/aw96103.c
index 8352d51e5128..e3ab55170b6c 100644
--- a/drivers/iio/proximity/aw96103.c
+++ b/drivers/iio/proximity/aw96103.c
@@ -687,7 +687,7 @@ static irqreturn_t aw96103_irq(int irq, void *data)
iio_get_time_ns(indio_dev));
break;
default:
- return IRQ_HANDLED;
+ continue;
}
aw96103->channels_arr[i].old_irq_status = curr_status;
}
diff --git a/drivers/iio/temperature/iqs620at-temp.c b/drivers/iio/temperature/iqs620at-temp.c
index e2f878d57af7..3a3b1f256585 100644
--- a/drivers/iio/temperature/iqs620at-temp.c
+++ b/drivers/iio/temperature/iqs620at-temp.c
@@ -5,13 +5,18 @@
* Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
*/
+#include <linux/array_size.h>
+#include <linux/bits.h>
#include <linux/device.h>
-#include <linux/iio/iio.h>
-#include <linux/kernel.h>
#include <linux/mfd/iqs62x.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include <asm/byteorder.h>
+
+#include <linux/iio/iio.h>
#define IQS620_TEMP_UI_OUT 0x1A
diff --git a/drivers/iio/temperature/ltc2983.c b/drivers/iio/temperature/ltc2983.c
index 6efb5252a773..6655453c9abf 100644
--- a/drivers/iio/temperature/ltc2983.c
+++ b/drivers/iio/temperature/ltc2983.c
@@ -5,23 +5,34 @@
*
* Copyright 2019 Analog Devices Inc.
*/
+
+#include <linux/array_size.h>
#include <linux/bitfield.h>
+#include <linux/bitops.h>
#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
-#include <linux/errno.h>
-#include <linux/kernel.h>
-#include <linux/iio/iio.h>
+#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/list.h>
+#include <linux/math64.h>
#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
#include <linux/spi/spi.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
#include <asm/byteorder.h>
-#include <linux/unaligned.h>
+
+#include <linux/iio/iio.h>
/* register map */
#define LTC2983_STATUS_REG 0x0000
diff --git a/drivers/iio/temperature/mlx90632.c b/drivers/iio/temperature/mlx90632.c
index 2fde48f337e2..74ae36f74e8b 100644
--- a/drivers/iio/temperature/mlx90632.c
+++ b/drivers/iio/temperature/mlx90632.c
@@ -6,21 +6,25 @@
*
* Driver for the Melexis MLX90632 I2C 16-bit IR thermopile sensor
*/
+
+#include <linux/array_size.h>
#include <linux/bitfield.h>
+#include <linux/bits.h>
#include <linux/delay.h>
+#include <linux/dev_printk.h>
#include <linux/device.h>
#include <linux/err.h>
-#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/iopoll.h>
#include <linux/jiffies.h>
-#include <linux/kernel.h>
#include <linux/limits.h>
-#include <linux/module.h>
#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
+#include <linux/types.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
diff --git a/drivers/iio/temperature/mlx90635.c b/drivers/iio/temperature/mlx90635.c
index 0f31879a4f64..48c760c980f3 100644
--- a/drivers/iio/temperature/mlx90635.c
+++ b/drivers/iio/temperature/mlx90635.c
@@ -6,21 +6,24 @@
*
* Driver for the Melexis MLX90635 I2C 16-bit IR thermopile sensor
*/
+
+#include <linux/array_size.h>
#include <linux/bitfield.h>
+#include <linux/bits.h>
#include <linux/delay.h>
+#include <linux/dev_printk.h>
#include <linux/device.h>
#include <linux/err.h>
-#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/iopoll.h>
#include <linux/jiffies.h>
-#include <linux/kernel.h>
-#include <linux/limits.h>
-#include <linux/module.h>
#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
+#include <linux/types.h>
#include <linux/iio/iio.h>
diff --git a/drivers/iio/temperature/tmp117.c b/drivers/iio/temperature/tmp117.c
index 74cb8d62bef3..2d0e79416434 100644
--- a/drivers/iio/temperature/tmp117.c
+++ b/drivers/iio/temperature/tmp117.c
@@ -10,16 +10,17 @@
*/
#include <linux/array_size.h>
+#include <linux/bitops.h>
#include <linux/delay.h>
+#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/i2c.h>
-#include <linux/module.h>
-#include <linux/bitops.h>
-#include <linux/types.h>
-#include <linux/kernel.h>
#include <linux/limits.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
#include <linux/property.h>
#include <linux/regulator/consumer.h>
+#include <linux/types.h>
#include <linux/iio/iio.h>
@@ -97,15 +98,20 @@ static int tmp117_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec
{
struct tmp117_data *data = iio_priv(indio_dev);
s16 off;
+ int ret;
switch (mask) {
case IIO_CHAN_INFO_CALIBBIAS:
off = clamp_t(int, val, S16_MIN, S16_MAX);
if (off == data->calibbias)
return 0;
+
+ ret = i2c_smbus_write_word_swapped(data->client, TMP117_REG_TEMP_OFFSET, off);
+ if (ret)
+ return ret;
+
data->calibbias = off;
- return i2c_smbus_write_word_swapped(data->client,
- TMP117_REG_TEMP_OFFSET, off);
+ return 0;
default:
return -EINVAL;
diff --git a/drivers/iio/temperature/tsys01.c b/drivers/iio/temperature/tsys01.c
index 28b3ce022ce7..b19b4a3ef038 100644
--- a/drivers/iio/temperature/tsys01.c
+++ b/drivers/iio/temperature/tsys01.c
@@ -8,14 +8,19 @@
* http://www.meas-spec.com/downloads/TSYS01_Digital_Temperature_Sensor.pdf
*/
-#include <linux/iio/iio.h>
-#include <linux/iio/sysfs.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
#include <linux/device.h>
-#include <linux/mutex.h>
+#include <linux/i2c.h>
+#include <linux/math64.h>
#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/stat.h>
+#include <linux/mutex.h>
+#include <linux/sprintf.h>
+#include <linux/types.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
#include "../common/ms_sensors/ms_sensors_i2c.h"
/* TSYS01 Commands */
diff --git a/drivers/iio/temperature/tsys02d.c b/drivers/iio/temperature/tsys02d.c
index 3ef72347456e..2c67faebd90c 100644
--- a/drivers/iio/temperature/tsys02d.c
+++ b/drivers/iio/temperature/tsys02d.c
@@ -10,11 +10,17 @@
* http://www.meas-spec.com/downloads/Digital_Sensor_TSYS02D.pdf
*/
-#include <linux/init.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/dev_printk.h>
#include <linux/device.h>
-#include <linux/kernel.h>
-#include <linux/stat.h>
+#include <linux/i2c.h>
#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/stat.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
diff --git a/drivers/iio/test/Kconfig b/drivers/iio/test/Kconfig
index 4fc17dd0dcd7..04930b4fdd5d 100644
--- a/drivers/iio/test/Kconfig
+++ b/drivers/iio/test/Kconfig
@@ -4,6 +4,19 @@
#
# Keep in alphabetical order
+config IIO_CHANNEL_PREFIX_KUNIT_TEST
+ tristate "Test IIO channel prefix" if !KUNIT_ALL_TESTS
+ depends on KUNIT && IIO
+ default KUNIT_ALL_TESTS
+ help
+ Build unit tests for __iio_chan_prefix_emit(), the helper that
+ builds IIO sysfs attribute name prefixes from an iio_chan_spec.
+
+ 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_GTS_KUNIT_TEST
tristate "Test IIO gain-time-scale helpers" if !KUNIT_ALL_TESTS
depends on KUNIT
diff --git a/drivers/iio/test/Makefile b/drivers/iio/test/Makefile
index 0c846bc21acd..ba08afa7f3f2 100644
--- a/drivers/iio/test/Makefile
+++ b/drivers/iio/test/Makefile
@@ -4,6 +4,7 @@
#
# Keep in alphabetical order
+obj-$(CONFIG_IIO_CHANNEL_PREFIX_KUNIT_TEST) += iio-test-channel-prefix.o
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
diff --git a/drivers/iio/test/iio-test-channel-prefix.c b/drivers/iio/test/iio-test-channel-prefix.c
new file mode 100644
index 000000000000..44c838fcd8d6
--- /dev/null
+++ b/drivers/iio/test/iio-test-channel-prefix.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Unit tests for IIO channel prefix generation.
+ */
+
+#include <kunit/test.h>
+#include <kunit/visibility.h>
+
+#include <linux/iio/iio.h>
+#include <linux/limits.h>
+#include <linux/string.h>
+
+#include "../iio_core.h"
+
+#define PREFIX_BUF_SIZE (NAME_MAX + 1)
+
+#define EXPECT_PREFIX(_test, _buf, _ret, _expected) do { \
+ struct kunit *__test = (_test); \
+ const char *__expected = (_expected); \
+ \
+ KUNIT_EXPECT_EQ(__test, (_ret), (ssize_t)strlen(__expected)); \
+ KUNIT_EXPECT_STREQ(__test, (_buf), __expected); \
+ } while (0)
+
+static char *iio_test_prefix_alloc(struct kunit *test)
+{
+ char *buf = kunit_kzalloc(test, PREFIX_BUF_SIZE, GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
+ return buf;
+}
+
+static void iio_test_prefix_shared_by_all(struct kunit *test)
+{
+ const struct iio_chan_spec chan = {
+ .type = IIO_VOLTAGE,
+ };
+ const struct iio_chan_spec chan_noisy = {
+ .type = IIO_ACCEL,
+ .output = 1,
+ .indexed = 1,
+ .modified = 1,
+ .channel = 7,
+ .channel2 = IIO_MOD_Z,
+ .extend_name = "supply",
+ };
+ char *buf = iio_test_prefix_alloc(test);
+ ssize_t ret;
+
+ ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SHARED_BY_ALL,
+ buf, PREFIX_BUF_SIZE);
+ EXPECT_PREFIX(test, buf, ret, "");
+
+ ret = __iio_chan_prefix_emit(NULL, &chan_noisy, IIO_SHARED_BY_ALL,
+ buf, PREFIX_BUF_SIZE);
+ EXPECT_PREFIX(test, buf, ret, "");
+}
+
+static void iio_test_prefix_shared_by_dir(struct kunit *test)
+{
+ const struct iio_chan_spec chan_in = {
+ .type = IIO_VOLTAGE,
+ .output = 0,
+ };
+ const struct iio_chan_spec chan_out = {
+ .type = IIO_VOLTAGE,
+ .output = 1,
+ };
+ const struct iio_chan_spec chan_in_noisy = {
+ .type = IIO_ACCEL,
+ .indexed = 1,
+ .modified = 1,
+ .channel = 5,
+ .channel2 = IIO_MOD_Y,
+ .extend_name = "supply",
+ };
+ char *buf = iio_test_prefix_alloc(test);
+ ssize_t ret;
+
+ ret = __iio_chan_prefix_emit(NULL, &chan_in, IIO_SHARED_BY_DIR,
+ buf, PREFIX_BUF_SIZE);
+ EXPECT_PREFIX(test, buf, ret, "in");
+
+ ret = __iio_chan_prefix_emit(NULL, &chan_out, IIO_SHARED_BY_DIR,
+ buf, PREFIX_BUF_SIZE);
+ EXPECT_PREFIX(test, buf, ret, "out");
+
+ ret = __iio_chan_prefix_emit(NULL, &chan_in_noisy, IIO_SHARED_BY_DIR,
+ buf, PREFIX_BUF_SIZE);
+ EXPECT_PREFIX(test, buf, ret, "in");
+}
+
+static void iio_test_prefix_shared_by_type(struct kunit *test)
+{
+ const struct iio_chan_spec chan = {
+ .type = IIO_VOLTAGE,
+ };
+ const struct iio_chan_spec chan_diff = {
+ .type = IIO_VOLTAGE,
+ .differential = 1,
+ };
+ const struct iio_chan_spec chan_noisy = {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .modified = 1,
+ .channel = 4,
+ .channel2 = IIO_MOD_X,
+ .extend_name = "supply",
+ };
+ char *buf = iio_test_prefix_alloc(test);
+ ssize_t ret;
+
+ ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SHARED_BY_TYPE,
+ buf, PREFIX_BUF_SIZE);
+ EXPECT_PREFIX(test, buf, ret, "in_voltage");
+
+ ret = __iio_chan_prefix_emit(NULL, &chan_diff, IIO_SHARED_BY_TYPE,
+ buf, PREFIX_BUF_SIZE);
+ EXPECT_PREFIX(test, buf, ret, "in_voltage-voltage");
+
+ ret = __iio_chan_prefix_emit(NULL, &chan_noisy, IIO_SHARED_BY_TYPE,
+ buf, PREFIX_BUF_SIZE);
+ EXPECT_PREFIX(test, buf, ret, "in_voltage");
+}
+
+static void iio_test_prefix_separate_simple(struct kunit *test)
+{
+ const struct iio_chan_spec chan = {
+ .type = IIO_TEMP,
+ };
+ char *buf = iio_test_prefix_alloc(test);
+ ssize_t ret;
+
+ ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE,
+ buf, PREFIX_BUF_SIZE);
+ EXPECT_PREFIX(test, buf, ret, "in_temp");
+}
+
+static void iio_test_prefix_separate_indexed(struct kunit *test)
+{
+ const struct iio_chan_spec chan = {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = 3,
+ };
+ char *buf = iio_test_prefix_alloc(test);
+ ssize_t ret;
+
+ ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE,
+ buf, PREFIX_BUF_SIZE);
+ EXPECT_PREFIX(test, buf, ret, "in_voltage3");
+}
+
+static void iio_test_prefix_separate_indexed_diff(struct kunit *test)
+{
+ const struct iio_chan_spec chan = {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .differential = 1,
+ .channel = 0,
+ .channel2 = 1,
+ };
+ char *buf = iio_test_prefix_alloc(test);
+ ssize_t ret;
+
+ ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE,
+ buf, PREFIX_BUF_SIZE);
+ EXPECT_PREFIX(test, buf, ret, "in_voltage0-voltage1");
+}
+
+static void iio_test_prefix_separate_modified(struct kunit *test)
+{
+ const struct iio_chan_spec chan = {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_X,
+ };
+ char *buf = iio_test_prefix_alloc(test);
+ ssize_t ret;
+
+ ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE,
+ buf, PREFIX_BUF_SIZE);
+ EXPECT_PREFIX(test, buf, ret, "in_accel_x");
+}
+
+static void iio_test_prefix_separate_indexed_modified(struct kunit *test)
+{
+ const struct iio_chan_spec chan = {
+ .type = IIO_ACCEL,
+ .indexed = 1,
+ .modified = 1,
+ .channel = 2,
+ .channel2 = IIO_MOD_Y,
+ };
+ char *buf = iio_test_prefix_alloc(test);
+ ssize_t ret;
+
+ ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE,
+ buf, PREFIX_BUF_SIZE);
+ EXPECT_PREFIX(test, buf, ret, "in_accel2_y");
+}
+
+static void iio_test_prefix_separate_extend_name(struct kunit *test)
+{
+ const struct iio_chan_spec chan = {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = 2,
+ .extend_name = "supply",
+ };
+ char *buf = iio_test_prefix_alloc(test);
+ ssize_t ret;
+
+ ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE,
+ buf, PREFIX_BUF_SIZE);
+ EXPECT_PREFIX(test, buf, ret, "in_voltage2_supply");
+}
+
+static void iio_test_prefix_output_separate(struct kunit *test)
+{
+ const struct iio_chan_spec chan = {
+ .type = IIO_VOLTAGE,
+ .output = 1,
+ .indexed = 1,
+ .channel = 0,
+ };
+ char *buf = iio_test_prefix_alloc(test);
+ ssize_t ret;
+
+ ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE,
+ buf, PREFIX_BUF_SIZE);
+ EXPECT_PREFIX(test, buf, ret, "out_voltage0");
+}
+
+static void iio_test_prefix_diff_unindexed_fails(struct kunit *test)
+{
+ const struct iio_chan_spec chan = {
+ .type = IIO_VOLTAGE,
+ .differential = 1,
+ };
+ char *buf = iio_test_prefix_alloc(test);
+ ssize_t ret;
+
+ ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE,
+ buf, PREFIX_BUF_SIZE);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
+static void iio_test_prefix_diff_modified_fails(struct kunit *test)
+{
+ const struct iio_chan_spec chan = {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .differential = 1,
+ .modified = 1,
+ .channel = 0,
+ .channel2 = 1,
+ };
+ char *buf = iio_test_prefix_alloc(test);
+ ssize_t ret;
+
+ ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE,
+ buf, PREFIX_BUF_SIZE);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
+static void iio_test_prefix_overflow(struct kunit *test)
+{
+ const struct iio_chan_spec chan = {
+ .type = IIO_VOLTAGE,
+ };
+ char small[4];
+ ssize_t ret;
+
+ ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SHARED_BY_TYPE,
+ small, sizeof(small));
+ KUNIT_EXPECT_EQ(test, ret, -EOVERFLOW);
+}
+
+static struct kunit_case iio_chan_prefix_test_cases[] = {
+ KUNIT_CASE(iio_test_prefix_shared_by_all),
+ KUNIT_CASE(iio_test_prefix_shared_by_dir),
+ KUNIT_CASE(iio_test_prefix_shared_by_type),
+ KUNIT_CASE(iio_test_prefix_separate_simple),
+ KUNIT_CASE(iio_test_prefix_separate_indexed),
+ KUNIT_CASE(iio_test_prefix_separate_indexed_diff),
+ KUNIT_CASE(iio_test_prefix_separate_modified),
+ KUNIT_CASE(iio_test_prefix_separate_indexed_modified),
+ KUNIT_CASE(iio_test_prefix_separate_extend_name),
+ KUNIT_CASE(iio_test_prefix_output_separate),
+ KUNIT_CASE(iio_test_prefix_diff_unindexed_fails),
+ KUNIT_CASE(iio_test_prefix_diff_modified_fails),
+ KUNIT_CASE(iio_test_prefix_overflow),
+ { }
+};
+
+static struct kunit_suite iio_channel_prefix_test_suite = {
+ .name = "iio-channel-prefix",
+ .test_cases = iio_chan_prefix_test_cases,
+};
+
+kunit_test_suite(iio_channel_prefix_test_suite);
+
+MODULE_AUTHOR("Rodrigo Alencar <rodrigo.alencar@analog.com>");
+MODULE_DESCRIPTION("Test IIO channel prefix generation");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
diff --git a/drivers/iio/test/iio-test-rescale.c b/drivers/iio/test/iio-test-rescale.c
index bcd4a607be19..de4a09f432c0 100644
--- a/drivers/iio/test/iio-test-rescale.c
+++ b/drivers/iio/test/iio-test-rescale.c
@@ -587,7 +587,7 @@ KUNIT_ARRAY_PARAM(iio_rescale_offset, offset_cases, case_to_desc);
* @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
+ * Returns 0 on success, or a negative error code if the string could not be
* parsed.
*/
static int iio_str_to_nano(const char *str, s64 *nano)
@@ -617,7 +617,7 @@ static int iio_str_to_nano(const char *str, s64 *nano)
* @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
+ * Returns a negative error code if the strings could 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)
diff --git a/drivers/interconnect/core.c b/drivers/interconnect/core.c
index 4aa991a54101..802f07e806d5 100644
--- a/drivers/interconnect/core.c
+++ b/drivers/interconnect/core.c
@@ -423,6 +423,17 @@ static void devm_icc_release(struct device *dev, void *res)
icc_put(*(struct icc_path **)res);
}
+/**
+ * devm_of_icc_get() - get a path handle from a DT node based on name
+ * @dev: device pointer for the consumer device
+ * @name: interconnect path name
+ *
+ * This function is the resource managed version of of_icc_get(). The path
+ * is released with icc_put() automatically when @dev is unbound.
+ *
+ * Return: icc_path pointer on success or ERR_PTR() on error. NULL is returned
+ * when the API is disabled or the "interconnects" DT property is missing.
+ */
struct icc_path *devm_of_icc_get(struct device *dev, const char *name)
{
struct icc_path **ptr, *path;
@@ -786,12 +797,33 @@ static int __icc_enable(struct icc_path *path, bool enable)
path->reqs[0].peak_bw);
}
+/**
+ * icc_enable() - enable a path
+ * @path: interconnect path
+ *
+ * Mark all requests on the path as enabled and reapply the bandwidth that
+ * was last set with icc_set_bw().
+ * Passing a NULL @path is a no-op and returns 0.
+ *
+ * Return: 0 on success, or an appropriate error code otherwise.
+ */
int icc_enable(struct icc_path *path)
{
return __icc_enable(path, true);
}
EXPORT_SYMBOL_GPL(icc_enable);
+/**
+ * icc_disable() - disable a path
+ * @path: interconnect path
+ *
+ * Mark all requests on the path as disabled, so that they no longer count
+ * towards the aggregated bandwidth, and reapply the constraints. The
+ * requested bandwidth is kept and restored by icc_enable().
+ * Passing a NULL @path is a no-op and returns 0.
+ *
+ * Return: 0 on success, or an appropriate error code otherwise.
+ */
int icc_disable(struct icc_path *path)
{
return __icc_enable(path, false);
diff --git a/drivers/interconnect/mediatek/Makefile b/drivers/interconnect/mediatek/Makefile
index 6bd656668f5d..64170ab163ff 100644
--- a/drivers/interconnect/mediatek/Makefile
+++ b/drivers/interconnect/mediatek/Makefile
@@ -3,4 +3,4 @@
obj-$(CONFIG_INTERCONNECT_MTK_DVFSRC_EMI) += icc-emi.o
obj-$(CONFIG_INTERCONNECT_MTK_MT8183) += mt8183.o
obj-$(CONFIG_INTERCONNECT_MTK_MT8195) += mt8195.o
-obj-$(CONFIG_INTERCONNECT_MTK_MT8195) += mt8196.o
+obj-$(CONFIG_INTERCONNECT_MTK_MT8196) += mt8196.o
diff --git a/drivers/interconnect/qcom/Kconfig b/drivers/interconnect/qcom/Kconfig
index b3187acf90b8..7f3a977e8f10 100644
--- a/drivers/interconnect/qcom/Kconfig
+++ b/drivers/interconnect/qcom/Kconfig
@@ -2,6 +2,7 @@
config INTERCONNECT_QCOM
tristate "Qualcomm Network-on-Chip interconnect drivers"
depends on ARCH_QCOM || COMPILE_TEST
+ default ARCH_QCOM
help
Support for Qualcomm's Network-on-Chip interconnect hardware.
@@ -14,6 +15,7 @@ config INTERCONNECT_QCOM_ELIZA
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on Eliza-based
platforms.
@@ -24,6 +26,7 @@ config INTERCONNECT_QCOM_GLYMUR
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on glymur-based
platforms.
@@ -31,8 +34,10 @@ config INTERCONNECT_QCOM_GLYMUR
config INTERCONNECT_QCOM_HAWI
tristate "Qualcomm HAWI interconnect driver"
depends on INTERCONNECT_QCOM_RPMH_POSSIBLE
+ depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on hawi-based
platforms.
@@ -43,13 +48,28 @@ config INTERCONNECT_QCOM_KAANAPALI
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on kaanapali-based
platforms.
+config INTERCONNECT_QCOM_KUNO
+ tristate "Qualcomm Kuno interconnect driver"
+ depends on INTERCONNECT_QCOM_RPMH_POSSIBLE
+ depends on ARM || COMPILE_TEST
+ select INTERCONNECT_QCOM_RPMH
+ select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
+ help
+ This is a driver for the Qualcomm Network-on-Chip on kuno-based
+ platforms. The interconnect provider collects and aggregates the
+ consumer bandwidth requests to satisfy constraints placed on the
+ Network-on-Chip performance states.
+
config INTERCONNECT_QCOM_MAILI
tristate "Qualcomm MAILI interconnect driver"
depends on INTERCONNECT_QCOM_RPMH_POSSIBLE
+ depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
default ARCH_QCOM
@@ -72,6 +92,7 @@ config INTERCONNECT_QCOM_MSM8916
depends on INTERCONNECT_QCOM
depends on QCOM_SMD_RPM
select INTERCONNECT_QCOM_SMD_RPM
+ default ARCH_QCOM if ARM64
help
This is a driver for the Qualcomm Network-on-Chip on msm8916-based
platforms.
@@ -100,6 +121,7 @@ config INTERCONNECT_QCOM_MSM8953
depends on QCOM_SMD_RPM
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_SMD_RPM
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on msm8953-based
platforms.
@@ -110,6 +132,7 @@ config INTERCONNECT_QCOM_MSM8974
depends on QCOM_SMD_RPM
depends on ARM || COMPILE_TEST
select INTERCONNECT_QCOM_SMD_RPM
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on msm8974-based
platforms.
@@ -130,6 +153,7 @@ config INTERCONNECT_QCOM_MSM8996
depends on QCOM_SMD_RPM
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_SMD_RPM
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on msm8996-based
platforms.
@@ -137,8 +161,10 @@ config INTERCONNECT_QCOM_MSM8996
config INTERCONNECT_QCOM_NORD
tristate "Qualcomm Nord interconnect driver"
depends on INTERCONNECT_QCOM_RPMH_POSSIBLE
+ depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on Nord-based
platforms. The topology consists of several NoCs controlled by
@@ -149,6 +175,7 @@ config INTERCONNECT_QCOM_OSM_L3
tristate "Qualcomm OSM L3 interconnect driver"
depends on INTERCONNECT_QCOM || COMPILE_TEST
depends on ARM64 || COMPILE_TEST
+ default ARCH_QCOM
help
Say y here to support the Operating State Manager (OSM) interconnect
driver which controls the scaling of L3 caches on Qualcomm SoCs.
@@ -159,6 +186,7 @@ config INTERCONNECT_QCOM_QCM2290
depends on QCOM_SMD_RPM
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_SMD_RPM
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on qcm2290-based
platforms.
@@ -169,6 +197,7 @@ config INTERCONNECT_QCOM_QCS404
depends on QCOM_SMD_RPM
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_SMD_RPM
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on qcs404-based
platforms.
@@ -179,6 +208,7 @@ config INTERCONNECT_QCOM_QCS615
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on qcs615-based
platforms.
@@ -189,6 +219,7 @@ config INTERCONNECT_QCOM_QCS8300
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Technologies, Inc. Network-on-Chip
on QCS8300-based platforms. The interconnect provider collects and
@@ -201,6 +232,7 @@ config INTERCONNECT_QCOM_QDU1000
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on QDU1000-based
and QRU1000-based platforms.
@@ -225,6 +257,7 @@ config INTERCONNECT_QCOM_SA8775P
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on sa8775p-based
platforms.
@@ -235,6 +268,7 @@ config INTERCONNECT_QCOM_SAR2130P
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on SAR2130P-based
platforms.
@@ -245,6 +279,7 @@ config INTERCONNECT_QCOM_SC7180
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on sc7180-based
platforms.
@@ -255,6 +290,7 @@ config INTERCONNECT_QCOM_SC7280
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on sc7280-based
platforms.
@@ -265,6 +301,7 @@ config INTERCONNECT_QCOM_SC8180X
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on sc8180x-based
platforms.
@@ -275,6 +312,7 @@ config INTERCONNECT_QCOM_SC8280XP
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on SC8280XP-based
platforms.
@@ -285,6 +323,7 @@ config INTERCONNECT_QCOM_SDM660
depends on ARM64 || COMPILE_TEST
depends on QCOM_SMD_RPM
select INTERCONNECT_QCOM_SMD_RPM
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on sdm660-based
platforms.
@@ -295,6 +334,7 @@ config INTERCONNECT_QCOM_SDM670
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on sdm670-based
platforms.
@@ -305,6 +345,7 @@ config INTERCONNECT_QCOM_SDM845
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on sdm845-based
platforms.
@@ -315,6 +356,7 @@ config INTERCONNECT_QCOM_SDX55
depends on ARM || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on sdx55-based
platforms.
@@ -335,6 +377,7 @@ config INTERCONNECT_QCOM_SDX75
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on sdx75-based
platforms.
@@ -343,6 +386,8 @@ config INTERCONNECT_QCOM_SHIKRA
tristate "Qualcomm SHIKRA interconnect driver"
depends on INTERCONNECT_QCOM
depends on QCOM_SMD_RPM
+ depends on ARM64 || COMPILE_TEST
+ default ARCH_QCOM
select INTERCONNECT_QCOM_SMD_RPM
help
This is a driver for the Qualcomm Network-on-Chip on shikra-based
@@ -354,6 +399,7 @@ config INTERCONNECT_QCOM_SM6115
depends on QCOM_SMD_RPM
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_SMD_RPM
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on sm6115-based
platforms.
@@ -364,6 +410,7 @@ config INTERCONNECT_QCOM_SM6350
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on sm6350-based
platforms.
@@ -374,6 +421,7 @@ config INTERCONNECT_QCOM_SM7150
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on sm7150-based
platforms.
@@ -384,6 +432,7 @@ config INTERCONNECT_QCOM_MILOS
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on Milos-based
platforms.
@@ -394,6 +443,7 @@ config INTERCONNECT_QCOM_SM8150
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on sm8150-based
platforms.
@@ -404,6 +454,7 @@ config INTERCONNECT_QCOM_SM8250
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on sm8250-based
platforms.
@@ -414,6 +465,7 @@ config INTERCONNECT_QCOM_SM8350
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on SM8350-based
platforms.
@@ -424,6 +476,7 @@ config INTERCONNECT_QCOM_SM8450
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on SM8450-based
platforms.
@@ -434,6 +487,7 @@ config INTERCONNECT_QCOM_SM8550
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on SM8550-based
platforms.
@@ -444,6 +498,7 @@ config INTERCONNECT_QCOM_SM8650
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on SM8650-based
platforms.
@@ -454,6 +509,7 @@ config INTERCONNECT_QCOM_SM8750
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on SM8750-based
platforms.
@@ -464,6 +520,7 @@ config INTERCONNECT_QCOM_X1E80100
depends on ARM64 || COMPILE_TEST
select INTERCONNECT_QCOM_RPMH
select INTERCONNECT_QCOM_BCM_VOTER
+ default ARCH_QCOM
help
This is a driver for the Qualcomm Network-on-Chip on X1E80100-based
platforms.
diff --git a/drivers/interconnect/qcom/Makefile b/drivers/interconnect/qcom/Makefile
index 5f139a370bbe..a71478aaf591 100644
--- a/drivers/interconnect/qcom/Makefile
+++ b/drivers/interconnect/qcom/Makefile
@@ -8,6 +8,7 @@ qnoc-eliza-objs := eliza.o
qnoc-glymur-objs := glymur.o
qnoc-hawi-objs := hawi.o
qnoc-kaanapali-objs := kaanapali.o
+qnoc-kuno-objs := kuno.o
qnoc-maili-objs := maili.o
qnoc-milos-objs := milos.o
qnoc-msm8909-objs := msm8909.o
@@ -57,6 +58,7 @@ obj-$(CONFIG_INTERCONNECT_QCOM_ELIZA) += qnoc-eliza.o
obj-$(CONFIG_INTERCONNECT_QCOM_GLYMUR) += qnoc-glymur.o
obj-$(CONFIG_INTERCONNECT_QCOM_HAWI) += qnoc-hawi.o
obj-$(CONFIG_INTERCONNECT_QCOM_KAANAPALI) += qnoc-kaanapali.o
+obj-$(CONFIG_INTERCONNECT_QCOM_KUNO) += qnoc-kuno.o
obj-$(CONFIG_INTERCONNECT_QCOM_MAILI) += qnoc-maili.o
obj-$(CONFIG_INTERCONNECT_QCOM_MILOS) += qnoc-milos.o
obj-$(CONFIG_INTERCONNECT_QCOM_MSM8909) += qnoc-msm8909.o
diff --git a/drivers/interconnect/qcom/bcm-voter.c b/drivers/interconnect/qcom/bcm-voter.c
index a2d437a05a11..0880a287b0c0 100644
--- a/drivers/interconnect/qcom/bcm-voter.c
+++ b/drivers/interconnect/qcom/bcm-voter.c
@@ -3,7 +3,6 @@
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
*/
-#include <asm/div64.h>
#include <linux/interconnect-provider.h>
#include <linux/list_sort.h>
#include <linux/module.h>
@@ -14,6 +13,7 @@
#include <soc/qcom/tcs.h>
#include "bcm-voter.h"
+#include "icc-common.h"
#include "icc-rpmh.h"
static LIST_HEAD(bcm_voters);
@@ -47,17 +47,6 @@ static int cmp_vcd(void *priv, const struct list_head *a, const struct list_head
return bcm_a->aux_data.vcd - bcm_b->aux_data.vcd;
}
-static u64 bcm_div(u64 num, u32 base)
-{
- /* Ensure that small votes aren't lost. */
- if (num && num < base)
- return 1;
-
- do_div(num, base);
-
- return num;
-}
-
/* BCMs with enable_mask use one-hot-encoding for on/off signaling */
static void bcm_aggregate_mask(struct qcom_icc_bcm *bcm)
{
@@ -99,20 +88,20 @@ static void bcm_aggregate(struct qcom_icc_bcm *bcm)
for (bucket = 0; bucket < QCOM_ICC_NUM_BUCKETS; bucket++) {
for (i = 0; i < bcm->num_nodes; i++) {
node = bcm->nodes[i];
- temp = bcm_div(node->sum_avg[bucket] * bcm->aux_data.width,
- node->buswidth * node->channels);
+ temp = qcom_bw_div(node->sum_avg[bucket] * le16_to_cpu(bcm->aux_data.width),
+ node->buswidth * node->channels);
agg_avg[bucket] = max(agg_avg[bucket], temp);
- temp = bcm_div(node->max_peak[bucket] * bcm->aux_data.width,
- node->buswidth);
+ temp = qcom_bw_div(node->max_peak[bucket] * le16_to_cpu(bcm->aux_data.width),
+ node->buswidth);
agg_peak[bucket] = max(agg_peak[bucket], temp);
}
temp = agg_avg[bucket] * bcm->vote_scale;
- bcm->vote_x[bucket] = bcm_div(temp, bcm->aux_data.unit);
+ bcm->vote_x[bucket] = qcom_bw_div(temp, le32_to_cpu(bcm->aux_data.unit));
temp = agg_peak[bucket] * bcm->vote_scale;
- bcm->vote_y[bucket] = bcm_div(temp, bcm->aux_data.unit);
+ bcm->vote_y[bucket] = qcom_bw_div(temp, le32_to_cpu(bcm->aux_data.unit));
}
if (bcm->keepalive && bcm->vote_x[QCOM_ICC_BUCKET_AMC] == 0 &&
@@ -262,6 +251,40 @@ void qcom_icc_bcm_voter_add(struct bcm_voter *voter, struct qcom_icc_bcm *bcm)
EXPORT_SYMBOL_GPL(qcom_icc_bcm_voter_add);
/**
+ * qcom_icc_bcm_get_bw - get current bcm vote
+ * @voter: voter used to query bcm
+ * @bcm: bcm to get current vote from
+ */
+void qcom_icc_bcm_get_bw(struct bcm_voter *voter,
+ struct qcom_icc_bcm *bcm)
+{
+ struct tcs_cmd cmd = { .addr = bcm->addr };
+ int ret, i;
+ u64 x, y;
+
+ mutex_lock(&voter->lock);
+
+ ret = rpmh_read(voter->dev, &cmd);
+ if (ret) {
+ pr_err("Error sending AMC RPMH requests (%d)\n", ret);
+ goto out;
+ }
+
+ x = FIELD_GET(BCM_TCS_CMD_VOTE_X_MASK, cmd.data);
+ y = FIELD_GET(BCM_TCS_CMD_VOTE_Y_MASK, cmd.data);
+
+ /* For boot-up, fill the AMC vote in all buckets */
+ for (i = 0; i < QCOM_ICC_NUM_BUCKETS; i++) {
+ bcm->vote_x[i] = x;
+ bcm->vote_y[i] = y;
+ }
+
+out:
+ mutex_unlock(&voter->lock);
+}
+EXPORT_SYMBOL_GPL(qcom_icc_bcm_get_bw);
+
+/**
* qcom_icc_bcm_voter_commit - generates and commits tcs cmds based on bcms
* @voter: voter that needs flushing
*
diff --git a/drivers/interconnect/qcom/bcm-voter.h b/drivers/interconnect/qcom/bcm-voter.h
index b4d36e349f3c..fc75d457dcc7 100644
--- a/drivers/interconnect/qcom/bcm-voter.h
+++ b/drivers/interconnect/qcom/bcm-voter.h
@@ -13,6 +13,7 @@
#include "icc-rpmh.h"
struct bcm_voter *of_bcm_voter_get(struct device *dev, const char *name);
+void qcom_icc_bcm_get_bw(struct bcm_voter *voter, struct qcom_icc_bcm *bcm);
void qcom_icc_bcm_voter_add(struct bcm_voter *voter, struct qcom_icc_bcm *bcm);
int qcom_icc_bcm_voter_commit(struct bcm_voter *voter);
diff --git a/drivers/interconnect/qcom/eliza.c b/drivers/interconnect/qcom/eliza.c
index 891e4e6e8ba8..ad03adef79ba 100644
--- a/drivers/interconnect/qcom/eliza.c
+++ b/drivers/interconnect/qcom/eliza.c
@@ -451,7 +451,7 @@ static struct qcom_icc_node qns_gem_noc_cnoc = {
.link_nodes = { &qnm_gemnoc_cnoc },
};
-static struct qcom_icc_qosbox alm_gpu_tcu_qos = {
+static const struct qcom_icc_qosbox alm_gpu_tcu_qos = {
.num_ports = 1,
.port_offsets = { 0x155000 },
.prio = 1,
@@ -468,7 +468,7 @@ static struct qcom_icc_node alm_gpu_tcu = {
.link_nodes = { &qns_gem_noc_cnoc, &qns_llcc },
};
-static struct qcom_icc_qosbox alm_sys_tcu_qos = {
+static const struct qcom_icc_qosbox alm_sys_tcu_qos = {
.num_ports = 1,
.port_offsets = { 0x157000 },
.prio = 6,
@@ -494,7 +494,7 @@ static struct qcom_icc_node chm_apps = {
&qns_pcie },
};
-static struct qcom_icc_qosbox qnm_gpu_qos = {
+static const struct qcom_icc_qosbox qnm_gpu_qos = {
.num_ports = 2,
.port_offsets = { 0x31000, 0xb1000 },
.prio = 0,
@@ -511,7 +511,7 @@ static struct qcom_icc_node qnm_gpu = {
.link_nodes = { &qns_gem_noc_cnoc, &qns_llcc },
};
-static struct qcom_icc_qosbox qnm_lpass_gemnoc_qos = {
+static const struct qcom_icc_qosbox qnm_lpass_gemnoc_qos = {
.num_ports = 1,
.port_offsets = { 0x159000 },
.prio = 0,
@@ -538,7 +538,7 @@ static struct qcom_icc_node qnm_mdsp = {
&qns_pcie },
};
-static struct qcom_icc_qosbox qnm_mnoc_hf_qos = {
+static const struct qcom_icc_qosbox qnm_mnoc_hf_qos = {
.num_ports = 2,
.port_offsets = { 0x33000, 0xb3000 },
.prio = 0,
@@ -555,7 +555,7 @@ static struct qcom_icc_node qnm_mnoc_hf = {
.link_nodes = { &qns_gem_noc_cnoc, &qns_llcc },
};
-static struct qcom_icc_qosbox qnm_mnoc_sf_qos = {
+static const struct qcom_icc_qosbox qnm_mnoc_sf_qos = {
.num_ports = 2,
.port_offsets = { 0x35000, 0xb5000 },
.prio = 0,
@@ -572,7 +572,7 @@ static struct qcom_icc_node qnm_mnoc_sf = {
.link_nodes = { &qns_gem_noc_cnoc, &qns_llcc },
};
-static struct qcom_icc_qosbox qnm_nsp_gemnoc_qos = {
+static const struct qcom_icc_qosbox qnm_nsp_gemnoc_qos = {
.num_ports = 2,
.port_offsets = { 0x37000, 0xb7000 },
.prio = 0,
@@ -590,7 +590,7 @@ static struct qcom_icc_node qnm_nsp_gemnoc = {
&qns_pcie },
};
-static struct qcom_icc_qosbox qnm_pcie_qos = {
+static const struct qcom_icc_qosbox qnm_pcie_qos = {
.num_ports = 1,
.port_offsets = { 0x15b000 },
.prio = 2,
@@ -688,7 +688,7 @@ static struct qcom_icc_node qnm_lpiaon_noc = {
.link_nodes = { &qns_lpass_ag_noc_gemnoc },
};
-static struct qcom_icc_qosbox qnm_camnoc_nrt_icp_sf_qos = {
+static const struct qcom_icc_qosbox qnm_camnoc_nrt_icp_sf_qos = {
.num_ports = 1,
.port_offsets = { 0x25000 },
.prio = 4,
diff --git a/drivers/interconnect/qcom/icc-common.h b/drivers/interconnect/qcom/icc-common.h
index 21c39b163948..af4b5a6f30b7 100644
--- a/drivers/interconnect/qcom/icc-common.h
+++ b/drivers/interconnect/qcom/icc-common.h
@@ -7,6 +7,15 @@
#define __DRIVERS_INTERCONNECT_QCOM_ICC_COMMON_H__
#include <linux/interconnect-provider.h>
+#include <linux/math64.h>
+
+static inline u64 qcom_bw_div(u64 num, u32 base)
+{
+ if (num && num < base)
+ return 1;
+
+ return div_u64(num, base);
+}
struct icc_node_data *qcom_icc_xlate_extended(const struct of_phandle_args *spec,
void *data);
diff --git a/drivers/interconnect/qcom/icc-rpm.c b/drivers/interconnect/qcom/icc-rpm.c
index ca4c8a944755..6f325789a96e 100644
--- a/drivers/interconnect/qcom/icc-rpm.c
+++ b/drivers/interconnect/qcom/icc-rpm.c
@@ -59,14 +59,31 @@ static int qcom_icc_set_qnoc_qos(struct icc_node *src)
int rc;
rc = regmap_update_bits(qp->regmap,
+ qp->qos_offset + QNOC_QOS_MCTL_LOWn_ADDR(qos->qos_port),
+ QNOC_QOS_MCTL_DFLT_PRIO_MASK,
+ qos->areq_prio << QNOC_QOS_MCTL_DFLT_PRIO_SHIFT);
+ if (rc)
+ return rc;
+
+ rc = regmap_update_bits(qp->regmap,
qp->qos_offset + QNOC_QOS_MCTL_LOWn_ADDR(qos->qos_port),
- QNOC_QOS_MCTL_DFLT_PRIO_MASK,
- qos->areq_prio << QNOC_QOS_MCTL_DFLT_PRIO_SHIFT);
+ QNOC_QOS_MCTL_URGFWD_EN_MASK,
+ !!qos->urg_fwd_en << QNOC_QOS_MCTL_URGFWD_EN_SHIFT);
+ if (rc)
+ return rc;
+
+ if (!qos->aux_qos_port)
+ return 0;
+
+ rc = regmap_update_bits(qp->regmap,
+ qp->qos_offset + QNOC_QOS_MCTL_LOWn_ADDR(qos->aux_qos_port),
+ QNOC_QOS_MCTL_DFLT_PRIO_MASK,
+ qos->areq_prio << QNOC_QOS_MCTL_DFLT_PRIO_SHIFT);
if (rc)
return rc;
return regmap_update_bits(qp->regmap,
- qp->qos_offset + QNOC_QOS_MCTL_LOWn_ADDR(qos->qos_port),
+ qp->qos_offset + QNOC_QOS_MCTL_LOWn_ADDR(qos->aux_qos_port),
QNOC_QOS_MCTL_URGFWD_EN_MASK,
!!qos->urg_fwd_en << QNOC_QOS_MCTL_URGFWD_EN_SHIFT);
}
@@ -75,8 +92,9 @@ static int qcom_icc_bimc_set_qos_health(struct qcom_icc_provider *qp,
struct qcom_icc_qos *qos,
int regnum)
{
- u32 val;
u32 mask;
+ u32 val;
+ int ret;
val = qos->prio_level;
mask = M_BKE_HEALTH_CFG_PRIOLVL_MASK;
@@ -90,8 +108,17 @@ static int qcom_icc_bimc_set_qos_health(struct qcom_icc_provider *qp,
mask |= M_BKE_HEALTH_CFG_LIMITCMDS_MASK;
}
+ ret = regmap_update_bits(qp->regmap,
+ qp->qos_offset + M_BKE_HEALTH_CFG_ADDR(regnum, qos->qos_port),
+ mask, val);
+ if (ret)
+ return ret;
+
+ if (!qos->aux_qos_port)
+ return 0;
+
return regmap_update_bits(qp->regmap,
- qp->qos_offset + M_BKE_HEALTH_CFG_ADDR(regnum, qos->qos_port),
+ qp->qos_offset + M_BKE_HEALTH_CFG_ADDR(regnum, qos->aux_qos_port),
mask, val);
}
@@ -102,7 +129,7 @@ static int qcom_icc_set_bimc_qos(struct icc_node *src)
struct icc_provider *provider;
u32 mode = NOC_QOS_MODE_BYPASS;
u32 val = 0;
- int i, rc = 0;
+ int i, rc;
qn = src->data;
provider = src->provider;
@@ -116,8 +143,7 @@ static int qcom_icc_set_bimc_qos(struct icc_node *src)
*/
if (mode != NOC_QOS_MODE_BYPASS) {
for (i = 3; i >= 0; i--) {
- rc = qcom_icc_bimc_set_qos_health(qp,
- &qn->qos, i);
+ rc = qcom_icc_bimc_set_qos_health(qp, &qn->qos, i);
if (rc)
return rc;
}
@@ -126,8 +152,17 @@ static int qcom_icc_set_bimc_qos(struct icc_node *src)
val = 1;
}
+ rc = regmap_update_bits(qp->regmap,
+ qp->qos_offset + M_BKE_EN_ADDR(qn->qos.qos_port),
+ M_BKE_EN_EN_BMASK, val);
+ if (rc)
+ return rc;
+
+ if (!qn->qos.aux_qos_port)
+ return 0;
+
return regmap_update_bits(qp->regmap,
- qp->qos_offset + M_BKE_EN_ADDR(qn->qos.qos_port),
+ qp->qos_offset + M_BKE_EN_ADDR(qn->qos.aux_qos_port),
M_BKE_EN_EN_BMASK, val);
}
@@ -145,8 +180,24 @@ static int qcom_icc_noc_set_qos_priority(struct qcom_icc_provider *qp,
if (rc)
return rc;
+ rc = regmap_update_bits(qp->regmap,
+ qp->qos_offset + NOC_QOS_PRIORITYn_ADDR(qos->qos_port),
+ NOC_QOS_PRIORITY_P0_MASK, qos->prio_level);
+ if (rc)
+ return rc;
+
+ if (!qos->aux_qos_port)
+ return 0;
+
+ val = qos->areq_prio << NOC_QOS_PRIORITY_P1_SHIFT;
+ rc = regmap_update_bits(qp->regmap,
+ qp->qos_offset + NOC_QOS_PRIORITYn_ADDR(qos->aux_qos_port),
+ NOC_QOS_PRIORITY_P1_MASK, val);
+ if (rc)
+ return rc;
+
return regmap_update_bits(qp->regmap,
- qp->qos_offset + NOC_QOS_PRIORITYn_ADDR(qos->qos_port),
+ qp->qos_offset + NOC_QOS_PRIORITYn_ADDR(qos->aux_qos_port),
NOC_QOS_PRIORITY_P0_MASK, qos->prio_level);
}
@@ -182,8 +233,17 @@ static int qcom_icc_set_noc_qos(struct icc_node *src)
/* How did we get here? */
}
+ rc = regmap_update_bits(qp->regmap,
+ qp->qos_offset + NOC_QOS_MODEn_ADDR(qn->qos.qos_port),
+ NOC_QOS_MODEn_MASK, mode);
+ if (rc)
+ return rc;
+
+ if (!qn->qos.aux_qos_port)
+ return 0;
+
return regmap_update_bits(qp->regmap,
- qp->qos_offset + NOC_QOS_MODEn_ADDR(qn->qos.qos_port),
+ qp->qos_offset + NOC_QOS_MODEn_ADDR(qn->qos.aux_qos_port),
NOC_QOS_MODEn_MASK, mode);
}
@@ -298,25 +358,25 @@ static u64 qcom_icc_calc_rate(struct qcom_icc_provider *qp, struct qcom_icc_node
u64 agg_avg_rate, agg_peak_rate, agg_rate;
if (qn->channels)
- agg_avg_rate = div_u64(qn->sum_avg[ctx], qn->channels);
+ agg_avg_rate = qcom_bw_div(qn->sum_avg[ctx], qn->channels);
else
agg_avg_rate = qn->sum_avg[ctx];
if (qn->ab_coeff) {
agg_avg_rate = agg_avg_rate * qn->ab_coeff;
- agg_avg_rate = div_u64(agg_avg_rate, 100);
+ agg_avg_rate = qcom_bw_div(agg_avg_rate, 100);
}
if (qn->ib_coeff) {
agg_peak_rate = qn->max_peak[ctx] * 100;
- agg_peak_rate = div_u64(agg_peak_rate, qn->ib_coeff);
+ agg_peak_rate = qcom_bw_div(agg_peak_rate, qn->ib_coeff);
} else {
agg_peak_rate = qn->max_peak[ctx];
}
agg_rate = max_t(u64, agg_avg_rate, agg_peak_rate);
- return div_u64(agg_rate, qn->buswidth);
+ return qcom_bw_div(agg_rate, qn->buswidth);
}
/**
@@ -402,7 +462,7 @@ static int qcom_icc_set(struct icc_node *src, struct icc_node *dst)
if (ret)
return ret;
- /* Cache the rate after we've successfully commited it to RPM */
+ /* Cache the rate after we've successfully committed it to RPM */
qp->bus_clk_rate[QCOM_SMD_RPM_ACTIVE_STATE] = active_rate;
}
@@ -412,7 +472,7 @@ static int qcom_icc_set(struct icc_node *src, struct icc_node *dst)
if (ret)
return ret;
- /* Cache the rate after we've successfully commited it to RPM */
+ /* Cache the rate after we've successfully committed it to RPM */
qp->bus_clk_rate[QCOM_SMD_RPM_SLEEP_STATE] = sleep_rate;
}
@@ -583,8 +643,15 @@ regmap_done:
node->data = qnodes[i];
icc_node_add(node, provider);
- for (j = 0; j < qnodes[i]->num_links; j++)
- icc_link_create(node, qnodes[i]->links[j]);
+ for (j = 0; j < qnodes[i]->num_links; j++) {
+ ret = icc_link_create(node, qnodes[i]->links[j]);
+ if (ret) {
+ icc_nodes_remove(provider);
+ clk_bulk_disable_unprepare(qp->num_intf_clks,
+ qp->intf_clks);
+ goto err_disable_unprepare_clk;
+ }
+ }
/* Set QoS registers (we only need to do it once, generally) */
if (qnodes[i]->qos.ap_owned &&
diff --git a/drivers/interconnect/qcom/icc-rpm.h b/drivers/interconnect/qcom/icc-rpm.h
index cdd5f444b152..d2c5f45eeedf 100644
--- a/drivers/interconnect/qcom/icc-rpm.h
+++ b/drivers/interconnect/qcom/icc-rpm.h
@@ -30,7 +30,7 @@ enum qcom_icc_type {
* @resource_type: RPM resource type of the clock resource
* @clock_id: index of the clock resource of a specific resource type
* @branch: whether the resource represents a branch clock
-*/
+ */
struct rpm_clk_resource {
u32 resource_type;
u32 clock_id;
@@ -77,6 +77,7 @@ struct qcom_icc_provider {
* @ap_owned: indicates if the node is owned by the AP or by the RPM
* @qos_mode: default qos mode for this node
* @qos_port: qos port number for finding qos registers of this node
+ * @aux_qos_port: optional QoS port in addition to @qos_port. Zero means "none".
* @urg_fwd_en: enable urgent forwarding
*/
struct qcom_icc_qos {
@@ -86,6 +87,7 @@ struct qcom_icc_qos {
bool ap_owned;
int qos_mode;
int qos_port;
+ u16 aux_qos_port;
bool urg_fwd_en;
};
@@ -167,7 +169,7 @@ int qnoc_probe(struct platform_device *pdev);
void qnoc_remove(struct platform_device *pdev);
bool qcom_icc_rpm_smd_available(void);
-int qcom_icc_rpm_smd_send(int ctx, int rsc_type, int id, u32 val);
+int qcom_icc_rpm_smd_send(int ctx, int rsc_type, int id, u64 val);
int qcom_icc_rpm_set_bus_rate(const struct rpm_clk_resource *clk, int ctx, u32 rate);
#endif
diff --git a/drivers/interconnect/qcom/icc-rpmh.c b/drivers/interconnect/qcom/icc-rpmh.c
index 3b445acefece..4a2a746a4e59 100644
--- a/drivers/interconnect/qcom/icc-rpmh.c
+++ b/drivers/interconnect/qcom/icc-rpmh.c
@@ -136,6 +136,61 @@ int qcom_icc_set(struct icc_node *src, struct icc_node *dst)
}
EXPORT_SYMBOL_GPL(qcom_icc_set);
+static int qcom_icc_get_bw(struct icc_node *node, u32 *avg, u32 *peak)
+{
+ struct qcom_icc_node *qn = node->data;
+ u32 avg_max = 0;
+ u32 peak_max = 0;
+ u64 x, y;
+ int i;
+
+ if (!qn->num_bcms) {
+ *avg = INT_MAX;
+ *peak = INT_MAX;
+
+ return 0;
+ }
+
+ for (i = 0; i < qn->num_bcms; ++i) {
+ struct qcom_icc_bcm *bcm = qn->bcms[i];
+
+ /* Use AMC vote for boot-up */
+ x = bcm->vote_x[QCOM_ICC_BUCKET_AMC];
+ y = bcm->vote_y[QCOM_ICC_BUCKET_AMC];
+
+ /* Consider enable mask and convert to INT_MAX */
+ if (bcm->enable_mask) {
+ if (x & bcm->enable_mask)
+ avg_max = INT_MAX;
+ if (y & bcm->enable_mask)
+ peak_max = INT_MAX;
+ } else {
+ if (x) {
+ x *= le32_to_cpu(bcm->aux_data.unit);
+ do_div(x, bcm->vote_scale);
+ x *= qn->buswidth * qn->channels;
+ do_div(x, le16_to_cpu(bcm->aux_data.width));
+
+ avg_max = max(avg_max, x);
+ }
+
+ if (y) {
+ y *= le32_to_cpu(bcm->aux_data.unit);
+ do_div(y, bcm->vote_scale);
+ y *= qn->buswidth;
+ do_div(y, le16_to_cpu(bcm->aux_data.width));
+
+ peak_max = max(peak_max, y);
+ }
+ }
+ }
+
+ *avg = avg_max;
+ *peak = peak_max;
+
+ return 0;
+}
+
/**
* qcom_icc_bcm_init - populates bcm aux data and connect qnodes
* @bcm: bcm to be initialized
@@ -173,10 +228,7 @@ int qcom_icc_bcm_init(struct qcom_icc_bcm *bcm, struct device *dev)
return -EINVAL;
}
- bcm->aux_data.unit = le32_to_cpu(data->unit);
- bcm->aux_data.width = le16_to_cpu(data->width);
- bcm->aux_data.vcd = data->vcd;
- bcm->aux_data.reserved = data->reserved;
+ bcm->aux_data = *data;
INIT_LIST_HEAD(&bcm->list);
INIT_LIST_HEAD(&bcm->ws_list);
@@ -255,6 +307,13 @@ int qcom_icc_rpmh_probe(struct platform_device *pdev)
provider = &qp->provider;
provider->dev = dev;
provider->set = qcom_icc_set;
+ /*
+ * Programming the QoS registers below requires the NoC to be clocked,
+ * which during probe is only guaranteed by the INT_MAX initial votes
+ * the interconnect core applies when get_bw() is absent.
+ */
+ if (!desc->config)
+ provider->get_bw = qcom_icc_get_bw;
provider->pre_aggregate = qcom_icc_pre_aggregate;
provider->aggregate = qcom_icc_aggregate;
provider->xlate_extended = qcom_icc_xlate_extended;
@@ -272,8 +331,10 @@ int qcom_icc_rpmh_probe(struct platform_device *pdev)
if (IS_ERR(qp->voter))
return PTR_ERR(qp->voter);
- for (i = 0; i < qp->num_bcms; i++)
+ for (i = 0; i < qp->num_bcms; i++) {
qcom_icc_bcm_init(qp->bcms[i], dev);
+ qcom_icc_bcm_get_bw(qp->voter, qp->bcms[i]);
+ }
for (i = 0; i < num_nodes; i++) {
qn = qnodes[i];
diff --git a/drivers/interconnect/qcom/kuno.c b/drivers/interconnect/qcom/kuno.c
new file mode 100644
index 000000000000..546622cf8cea
--- /dev/null
+++ b/drivers/interconnect/qcom/kuno.c
@@ -0,0 +1,988 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/device.h>
+#include <linux/interconnect.h>
+#include <linux/interconnect-provider.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/interconnect/qcom,kuno.h>
+
+#include "bcm-voter.h"
+#include "icc-rpmh.h"
+
+static struct qcom_icc_node qup0_core_slave = {
+ .name = "qup0_core_slave",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_ahb2_phy = {
+ .name = "qhs_ahb2_phy",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_aoss = {
+ .name = "qhs_aoss",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_apss = {
+ .name = "qhs_apss",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_audio = {
+ .name = "qhs_audio",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_clk_ctl = {
+ .name = "qhs_clk_ctl",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_cpr_cx = {
+ .name = "qhs_cpr_cx",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_cpr_mxa = {
+ .name = "qhs_cpr_mxa",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_cpr_mxc = {
+ .name = "qhs_cpr_mxc",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_crypto_cfg = {
+ .name = "qhs_crypto_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_emac0_cfg = {
+ .name = "qhs_emac0_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_imem_cfg = {
+ .name = "qhs_imem_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_ipa = {
+ .name = "qhs_ipa",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_ipc_router = {
+ .name = "qhs_ipc_router",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_mss_cfg = {
+ .name = "qhs_mss_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_pcie0_cfg = {
+ .name = "qhs_pcie0_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_pcie_rscc = {
+ .name = "qhs_pcie_rscc",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_pdm = {
+ .name = "qhs_pdm",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_pmu_wrapper_cfg = {
+ .name = "qhs_pmu_wrapper_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_prng = {
+ .name = "qhs_prng",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_qdss_cfg = {
+ .name = "qhs_qdss_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_qpic = {
+ .name = "qhs_qpic",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_qup0 = {
+ .name = "qhs_qup0",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_sdc4 = {
+ .name = "qhs_sdc4",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_spmi_vgi_coex = {
+ .name = "qhs_spmi_vgi_coex",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_tcsr = {
+ .name = "qhs_tcsr",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_tlmm = {
+ .name = "qhs_tlmm",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_tme_cfg = {
+ .name = "qhs_tme_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_usb3 = {
+ .name = "qhs_usb3",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_vsense_ctrl_cfg = {
+ .name = "qhs_vsense_ctrl_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qss_anoc_throttle_cfg = {
+ .name = "qss_anoc_throttle_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qxs_boot_imem = {
+ .name = "qxs_boot_imem",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qxs_imem = {
+ .name = "qxs_imem",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node xs_qdss_stm = {
+ .name = "xs_qdss_stm",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node xs_sys_tcu_cfg = {
+ .name = "xs_sys_tcu_cfg",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_lagg = {
+ .name = "qhs_lagg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qhs_mccc_master = {
+ .name = "qhs_mccc_master",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node ebi = {
+ .name = "ebi",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node srvc_memnoc = {
+ .name = "srvc_memnoc",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node xs_pcie_0 = {
+ .name = "xs_pcie_0",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 0,
+};
+
+static struct qcom_icc_node qup0_core_master = {
+ .name = "qup0_core_master",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = { &qup0_core_slave },
+};
+
+static struct qcom_icc_node qnm_cnoc = {
+ .name = "qnm_cnoc",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 2,
+ .link_nodes = { &qhs_lagg, &qhs_mccc_master },
+};
+
+static struct qcom_icc_node llcc_mc = {
+ .name = "llcc_mc",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = { &ebi },
+};
+
+static struct qcom_icc_node qnm_memnoc_cfg = {
+ .name = "qnm_memnoc_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = { &srvc_memnoc },
+};
+
+static struct qcom_icc_node qnm_memnoc_pcie = {
+ .name = "qnm_memnoc_pcie",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = { &xs_pcie_0 },
+};
+
+static struct qcom_icc_node xm_ipa2pcie = {
+ .name = "xm_ipa2pcie",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = { &xs_pcie_0 },
+};
+
+static struct qcom_icc_node qns_ddrss_cfg = {
+ .name = "qns_ddrss_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = { &qnm_cnoc },
+};
+
+static struct qcom_icc_node qns_llcc = {
+ .name = "qns_llcc",
+ .channels = 1,
+ .buswidth = 16,
+ .num_links = 1,
+ .link_nodes = { &llcc_mc },
+};
+
+static struct qcom_icc_node qns_pcie = {
+ .name = "qns_pcie",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = { &qnm_memnoc_pcie },
+};
+
+static struct qcom_icc_node qhm_pcie_rscc = {
+ .name = "qhm_pcie_rscc",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 34,
+ .link_nodes = { &qhs_ahb2_phy, &qhs_aoss,
+ &qhs_apss, &qhs_audio,
+ &qhs_clk_ctl, &qhs_cpr_cx,
+ &qhs_cpr_mxa, &qhs_cpr_mxc,
+ &qhs_crypto_cfg, &qhs_emac0_cfg,
+ &qhs_imem_cfg, &qhs_ipa,
+ &qhs_ipc_router, &qhs_mss_cfg,
+ &qhs_pcie0_cfg, &qhs_pdm,
+ &qhs_pmu_wrapper_cfg, &qhs_prng,
+ &qhs_qdss_cfg, &qhs_qpic,
+ &qhs_qup0, &qhs_sdc4,
+ &qhs_spmi_vgi_coex, &qhs_tcsr,
+ &qhs_tlmm, &qhs_tme_cfg,
+ &qhs_usb3, &qhs_vsense_ctrl_cfg,
+ &qns_ddrss_cfg, &qss_anoc_throttle_cfg,
+ &qxs_boot_imem, &qxs_imem,
+ &xs_qdss_stm, &xs_sys_tcu_cfg },
+};
+
+static struct qcom_icc_node qnm_memnoc_cnoc = {
+ .name = "qnm_memnoc_cnoc",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 35,
+ .link_nodes = { &qhs_ahb2_phy, &qhs_aoss,
+ &qhs_apss, &qhs_audio,
+ &qhs_clk_ctl, &qhs_cpr_cx,
+ &qhs_cpr_mxa, &qhs_cpr_mxc,
+ &qhs_crypto_cfg, &qhs_emac0_cfg,
+ &qhs_imem_cfg, &qhs_ipa,
+ &qhs_ipc_router, &qhs_mss_cfg,
+ &qhs_pcie0_cfg, &qhs_pcie_rscc,
+ &qhs_pdm, &qhs_pmu_wrapper_cfg,
+ &qhs_prng, &qhs_qdss_cfg,
+ &qhs_qpic, &qhs_qup0,
+ &qhs_sdc4, &qhs_spmi_vgi_coex,
+ &qhs_tcsr, &qhs_tlmm,
+ &qhs_tme_cfg, &qhs_usb3,
+ &qhs_vsense_ctrl_cfg, &qns_ddrss_cfg,
+ &qss_anoc_throttle_cfg, &qxs_boot_imem,
+ &qxs_imem, &xs_qdss_stm,
+ &xs_sys_tcu_cfg },
+};
+
+static struct qcom_icc_node qns_memnoc_cnoc = {
+ .name = "qns_memnoc_cnoc",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = { &qnm_memnoc_cnoc },
+};
+
+static struct qcom_icc_node alm_sys_tcu = {
+ .name = "alm_sys_tcu",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 2,
+ .link_nodes = { &qns_llcc, &qns_memnoc_cnoc },
+};
+
+static struct qcom_icc_node qnm_mdsp = {
+ .name = "qnm_mdsp",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 3,
+ .link_nodes = { &qns_llcc, &qns_memnoc_cnoc,
+ &qns_pcie },
+};
+
+static struct qcom_icc_node qnm_pcie = {
+ .name = "qnm_pcie",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 2,
+ .link_nodes = { &qns_llcc, &qns_memnoc_cnoc },
+};
+
+static struct qcom_icc_node qnm_snoc_sf = {
+ .name = "qnm_snoc_sf",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 2,
+ .link_nodes = { &qns_llcc, &qns_memnoc_cnoc },
+};
+
+static struct qcom_icc_node xm_apps0 = {
+ .name = "xm_apps0",
+ .channels = 1,
+ .buswidth = 16,
+ .num_links = 3,
+ .link_nodes = { &qns_llcc, &qns_memnoc_cnoc,
+ &qns_pcie },
+};
+
+static struct qcom_icc_node qns_pcie_memnoc = {
+ .name = "qns_pcie_memnoc",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = { &qnm_pcie },
+};
+
+static struct qcom_icc_node qns_memnoc_sf = {
+ .name = "qns_memnoc_sf",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = { &qnm_snoc_sf },
+};
+
+static struct qcom_icc_node xm_pcie3_0 = {
+ .name = "xm_pcie3_0",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = { &qns_pcie_memnoc },
+};
+
+static struct qcom_icc_node qhm_cpucp = {
+ .name = "qhm_cpucp",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = { &qns_memnoc_sf },
+};
+
+static struct qcom_icc_node qnm_aggre_noc = {
+ .name = "qnm_aggre_noc",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 2,
+ .link_nodes = { &qns_memnoc_sf, &xs_pcie_0 },
+};
+
+static struct qcom_icc_node qnm_cnoc_datapath = {
+ .name = "qnm_cnoc_datapath",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 2,
+ .link_nodes = { &qns_memnoc_sf, &xs_pcie_0 },
+};
+
+static struct qcom_icc_node qxm_mss_nav_ce = {
+ .name = "qxm_mss_nav_ce",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 2,
+ .link_nodes = { &qns_memnoc_sf, &xs_pcie_0 },
+};
+
+static struct qcom_icc_node qxm_tme = {
+ .name = "qxm_tme",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 2,
+ .link_nodes = { &qns_memnoc_sf, &xs_pcie_0 },
+};
+
+static struct qcom_icc_node qns_a1noc = {
+ .name = "qns_a1noc",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = { &qnm_aggre_noc },
+};
+
+static struct qcom_icc_node qns_snoc_datapath = {
+ .name = "qns_snoc_datapath",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = { &qnm_cnoc_datapath },
+};
+
+static struct qcom_icc_node qhm_audio = {
+ .name = "qhm_audio",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = { &qns_a1noc },
+};
+
+static struct qcom_icc_node qhm_qdss_bam = {
+ .name = "qhm_qdss_bam",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = { &qns_a1noc },
+};
+
+static struct qcom_icc_node qhm_qpic = {
+ .name = "qhm_qpic",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = { &qns_a1noc },
+};
+
+static struct qcom_icc_node qhm_qup0 = {
+ .name = "qhm_qup0",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = { &qns_a1noc },
+};
+
+static struct qcom_icc_node qxm_crypto = {
+ .name = "qxm_crypto",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = { &qns_a1noc },
+};
+
+static struct qcom_icc_node qxm_ipa = {
+ .name = "qxm_ipa",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = { &qns_a1noc },
+};
+
+static struct qcom_icc_node xm_emac_0 = {
+ .name = "xm_emac_0",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = { &qns_a1noc },
+};
+
+static struct qcom_icc_node xm_qdss_etr0 = {
+ .name = "xm_qdss_etr0",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = { &qns_a1noc },
+};
+
+static struct qcom_icc_node xm_qdss_etr1 = {
+ .name = "xm_qdss_etr1",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = { &qns_a1noc },
+};
+
+static struct qcom_icc_node xm_sdc4 = {
+ .name = "xm_sdc4",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = { &qns_a1noc },
+};
+
+static struct qcom_icc_node xm_usb3 = {
+ .name = "xm_usb3",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = { &qns_a1noc },
+};
+
+static struct qcom_icc_node xm_qdss_dap = {
+ .name = "xm_qdss_dap",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 36,
+ .link_nodes = { &qhs_ahb2_phy, &qhs_aoss,
+ &qhs_apss, &qhs_audio,
+ &qhs_clk_ctl, &qhs_cpr_cx,
+ &qhs_cpr_mxa, &qhs_cpr_mxc,
+ &qhs_crypto_cfg, &qhs_emac0_cfg,
+ &qhs_imem_cfg, &qhs_ipa,
+ &qhs_ipc_router, &qhs_mss_cfg,
+ &qhs_pcie0_cfg, &qhs_pcie_rscc,
+ &qhs_pdm, &qhs_pmu_wrapper_cfg,
+ &qhs_prng, &qhs_qdss_cfg,
+ &qhs_qpic, &qhs_qup0,
+ &qhs_sdc4, &qhs_spmi_vgi_coex,
+ &qhs_tcsr, &qhs_tlmm,
+ &qhs_tme_cfg, &qhs_usb3,
+ &qhs_vsense_ctrl_cfg, &qns_ddrss_cfg,
+ &qns_snoc_datapath, &qss_anoc_throttle_cfg,
+ &qxs_boot_imem, &qxs_imem,
+ &xs_qdss_stm, &xs_sys_tcu_cfg },
+};
+
+static struct qcom_icc_bcm bcm_acv = {
+ .name = "ACV",
+ .enable_mask = 0x8,
+ .num_nodes = 1,
+ .nodes = { &ebi },
+};
+
+static struct qcom_icc_bcm bcm_ce0 = {
+ .name = "CE0",
+ .num_nodes = 1,
+ .nodes = { &qxm_crypto },
+};
+
+static struct qcom_icc_bcm bcm_cn0 = {
+ .name = "CN0",
+ .keepalive = true,
+ .enable_mask = 0x1,
+ .num_nodes = 39,
+ .nodes = { &qhm_pcie_rscc, &qnm_memnoc_cnoc,
+ &xm_qdss_dap, &qhs_ahb2_phy,
+ &qhs_aoss, &qhs_apss,
+ &qhs_audio, &qhs_clk_ctl,
+ &qhs_cpr_cx, &qhs_cpr_mxa,
+ &qhs_cpr_mxc, &qhs_crypto_cfg,
+ &qhs_emac0_cfg, &qhs_imem_cfg,
+ &qhs_ipa, &qhs_ipc_router,
+ &qhs_mss_cfg, &qhs_pcie0_cfg,
+ &qhs_pcie_rscc, &qhs_pdm,
+ &qhs_pmu_wrapper_cfg, &qhs_prng,
+ &qhs_qdss_cfg, &qhs_qpic,
+ &qhs_qup0, &qhs_sdc4,
+ &qhs_spmi_vgi_coex, &qhs_tcsr,
+ &qhs_tlmm, &qhs_tme_cfg,
+ &qhs_usb3, &qhs_vsense_ctrl_cfg,
+ &qns_ddrss_cfg, &qns_snoc_datapath,
+ &qss_anoc_throttle_cfg, &qxs_boot_imem,
+ &qxs_imem, &xs_qdss_stm,
+ &xs_sys_tcu_cfg },
+};
+
+static struct qcom_icc_bcm bcm_mc0 = {
+ .name = "MC0",
+ .keepalive = true,
+ .num_nodes = 1,
+ .nodes = { &ebi },
+};
+
+static struct qcom_icc_bcm bcm_qup0 = {
+ .name = "QUP0",
+ .vote_scale = 1,
+ .num_nodes = 1,
+ .nodes = { &qup0_core_slave },
+};
+
+static struct qcom_icc_bcm bcm_sh0 = {
+ .name = "SH0",
+ .keepalive = true,
+ .num_nodes = 1,
+ .nodes = { &qns_llcc },
+};
+
+static struct qcom_icc_bcm bcm_sh1 = {
+ .name = "SH1",
+ .enable_mask = 0x1,
+ .num_nodes = 2,
+ .nodes = { &qns_memnoc_cnoc, &qns_pcie },
+};
+
+static struct qcom_icc_bcm bcm_sn0 = {
+ .name = "SN0",
+ .keepalive = true,
+ .num_nodes = 1,
+ .nodes = { &qns_memnoc_sf },
+};
+
+static struct qcom_icc_bcm bcm_sn1 = {
+ .name = "SN1",
+ .enable_mask = 0x1,
+ .num_nodes = 1,
+ .nodes = { &xm_pcie3_0 },
+};
+
+static struct qcom_icc_bcm bcm_sn2 = {
+ .name = "SN2",
+ .num_nodes = 2,
+ .nodes = { &qns_a1noc, &qnm_aggre_noc },
+};
+
+static struct qcom_icc_bcm bcm_sn4 = {
+ .name = "SN4",
+ .num_nodes = 1,
+ .nodes = { &qxm_mss_nav_ce },
+};
+
+static struct qcom_icc_bcm bcm_sn7 = {
+ .name = "SN7",
+ .num_nodes = 1,
+ .nodes = { &xs_pcie_0 },
+};
+
+static struct qcom_icc_bcm * const aggre_noc_bcms[] = {
+ &bcm_ce0,
+};
+
+static struct qcom_icc_node * const aggre_noc_nodes[] = {
+ [MASTER_AUDIO] = &qhm_audio,
+ [MASTER_QDSS_BAM] = &qhm_qdss_bam,
+ [MASTER_QPIC] = &qhm_qpic,
+ [MASTER_QUP_0] = &qhm_qup0,
+ [MASTER_CRYPTO] = &qxm_crypto,
+ [MASTER_IPA] = &qxm_ipa,
+ [MASTER_EMAC] = &xm_emac_0,
+ [MASTER_QDSS_ETR] = &xm_qdss_etr0,
+ [MASTER_QDSS_ETR_1] = &xm_qdss_etr1,
+ [MASTER_SDCC_4] = &xm_sdc4,
+ [MASTER_USB3_0] = &xm_usb3,
+ [SLAVE_A1NOC_CFG] = &qns_a1noc,
+};
+
+static const struct qcom_icc_desc kuno_aggre_noc = {
+ .nodes = aggre_noc_nodes,
+ .num_nodes = ARRAY_SIZE(aggre_noc_nodes),
+ .bcms = aggre_noc_bcms,
+ .num_bcms = ARRAY_SIZE(aggre_noc_bcms),
+};
+
+static struct qcom_icc_bcm * const clk_virt_bcms[] = {
+ &bcm_qup0,
+};
+
+static struct qcom_icc_node * const clk_virt_nodes[] = {
+ [MASTER_QUP_CORE_0] = &qup0_core_master,
+ [SLAVE_QUP_CORE_0] = &qup0_core_slave,
+};
+
+static const struct qcom_icc_desc kuno_clk_virt = {
+ .nodes = clk_virt_nodes,
+ .num_nodes = ARRAY_SIZE(clk_virt_nodes),
+ .bcms = clk_virt_bcms,
+ .num_bcms = ARRAY_SIZE(clk_virt_bcms),
+};
+
+static struct qcom_icc_bcm * const cnoc_main_bcms[] = {
+ &bcm_cn0,
+};
+
+static struct qcom_icc_node * const cnoc_main_nodes[] = {
+ [MASTER_PCIE_RSCC] = &qhm_pcie_rscc,
+ [MASTER_GEM_NOC_CNOC] = &qnm_memnoc_cnoc,
+ [MASTER_QDSS_DAP] = &xm_qdss_dap,
+ [SLAVE_AHB2PHY] = &qhs_ahb2_phy,
+ [SLAVE_AOSS] = &qhs_aoss,
+ [SLAVE_APPSS] = &qhs_apss,
+ [SLAVE_AUDIO] = &qhs_audio,
+ [SLAVE_CLK_CTL] = &qhs_clk_ctl,
+ [SLAVE_RBCPR_CX_CFG] = &qhs_cpr_cx,
+ [SLAVE_RBCPR_MXA_CFG] = &qhs_cpr_mxa,
+ [SLAVE_RBCPR_MXC_CFG] = &qhs_cpr_mxc,
+ [SLAVE_CRYPTO_0_CFG] = &qhs_crypto_cfg,
+ [SLAVE_EMAC_CFG] = &qhs_emac0_cfg,
+ [SLAVE_IMEM_CFG] = &qhs_imem_cfg,
+ [SLAVE_IPA_CFG] = &qhs_ipa,
+ [SLAVE_IPC_ROUTER_CFG] = &qhs_ipc_router,
+ [SLAVE_CNOC_MSS] = &qhs_mss_cfg,
+ [SLAVE_PCIE_0_CFG] = &qhs_pcie0_cfg,
+ [SLAVE_PCIE_RSC_CFG] = &qhs_pcie_rscc,
+ [SLAVE_PDM] = &qhs_pdm,
+ [SLAVE_PMU_WRAPPER_CFG] = &qhs_pmu_wrapper_cfg,
+ [SLAVE_PRNG] = &qhs_prng,
+ [SLAVE_QDSS_CFG] = &qhs_qdss_cfg,
+ [SLAVE_QPIC] = &qhs_qpic,
+ [SLAVE_QUP_0] = &qhs_qup0,
+ [SLAVE_SDCC_4] = &qhs_sdc4,
+ [SLAVE_SPMI_VGI_COEX] = &qhs_spmi_vgi_coex,
+ [SLAVE_TCSR] = &qhs_tcsr,
+ [SLAVE_TLMM] = &qhs_tlmm,
+ [SLAVE_TME_CFG] = &qhs_tme_cfg,
+ [SLAVE_USB3] = &qhs_usb3,
+ [SLAVE_VSENSE_CTRL_CFG] = &qhs_vsense_ctrl_cfg,
+ [SLAVE_DDRSS_CFG] = &qns_ddrss_cfg,
+ [SLAVE_SNOC_CNOC] = &qns_snoc_datapath,
+ [SLAVE_ANOC_THROTTLE_CFG] = &qss_anoc_throttle_cfg,
+ [SLAVE_BOOT_IMEM] = &qxs_boot_imem,
+ [SLAVE_IMEM] = &qxs_imem,
+ [SLAVE_QDSS_STM] = &xs_qdss_stm,
+ [SLAVE_TCU] = &xs_sys_tcu_cfg,
+};
+
+static const struct qcom_icc_desc kuno_cnoc_main = {
+ .nodes = cnoc_main_nodes,
+ .num_nodes = ARRAY_SIZE(cnoc_main_nodes),
+ .bcms = cnoc_main_bcms,
+ .num_bcms = ARRAY_SIZE(cnoc_main_bcms),
+};
+
+static struct qcom_icc_node * const dc_noc_nodes[] = {
+ [MASTER_CNOC_DC_NOC] = &qnm_cnoc,
+ [SLAVE_LAGG_CFG] = &qhs_lagg,
+ [SLAVE_MCCC_MASTER] = &qhs_mccc_master,
+};
+
+static const struct qcom_icc_desc kuno_dc_noc = {
+ .nodes = dc_noc_nodes,
+ .num_nodes = ARRAY_SIZE(dc_noc_nodes),
+};
+
+static struct qcom_icc_bcm * const mc_virt_bcms[] = {
+ &bcm_acv,
+ &bcm_mc0,
+};
+
+static struct qcom_icc_node * const mc_virt_nodes[] = {
+ [MASTER_LLCC] = &llcc_mc,
+ [SLAVE_EBI1] = &ebi,
+};
+
+static const struct qcom_icc_desc kuno_mc_virt = {
+ .nodes = mc_virt_nodes,
+ .num_nodes = ARRAY_SIZE(mc_virt_nodes),
+ .bcms = mc_virt_bcms,
+ .num_bcms = ARRAY_SIZE(mc_virt_bcms),
+};
+
+static struct qcom_icc_bcm * const mem_noc_bcms[] = {
+ &bcm_sh0,
+ &bcm_sh1,
+};
+
+static struct qcom_icc_node * const mem_noc_nodes[] = {
+ [MASTER_SYS_TCU] = &alm_sys_tcu,
+ [MASTER_MSS_PROC] = &qnm_mdsp,
+ [MASTER_GEM_NOC_CFG] = &qnm_memnoc_cfg,
+ [MASTER_ANOC_PCIE_GEM_NOC] = &qnm_pcie,
+ [MASTER_SNOC_SF_MEM_NOC] = &qnm_snoc_sf,
+ [MASTER_APPSS_PROC] = &xm_apps0,
+ [SLAVE_LLCC] = &qns_llcc,
+ [SLAVE_GEM_NOC_CNOC] = &qns_memnoc_cnoc,
+ [SLAVE_MEM_NOC_PCIE_SNOC] = &qns_pcie,
+ [SLAVE_SERVICE_GEM_NOC] = &srvc_memnoc,
+};
+
+static const struct qcom_icc_desc kuno_mem_noc = {
+ .nodes = mem_noc_nodes,
+ .num_nodes = ARRAY_SIZE(mem_noc_nodes),
+ .bcms = mem_noc_bcms,
+ .num_bcms = ARRAY_SIZE(mem_noc_bcms),
+};
+
+static struct qcom_icc_bcm * const pcie_anoc_bcms[] = {
+ &bcm_sn1,
+};
+
+static struct qcom_icc_node * const pcie_anoc_nodes[] = {
+ [MASTER_PCIE_0] = &xm_pcie3_0,
+ [SLAVE_ANOC_PCIE_GEM_NOC] = &qns_pcie_memnoc,
+};
+
+static const struct qcom_icc_desc kuno_pcie_anoc = {
+ .nodes = pcie_anoc_nodes,
+ .num_nodes = ARRAY_SIZE(pcie_anoc_nodes),
+ .bcms = pcie_anoc_bcms,
+ .num_bcms = ARRAY_SIZE(pcie_anoc_bcms),
+};
+
+static struct qcom_icc_bcm * const system_noc_bcms[] = {
+ &bcm_sn0,
+ &bcm_sn2,
+ &bcm_sn4,
+ &bcm_sn7,
+};
+
+static struct qcom_icc_node * const system_noc_nodes[] = {
+ [MASTER_CPUCP] = &qhm_cpucp,
+ [MASTER_ANOC_SNOC] = &qnm_aggre_noc,
+ [MASTER_CNOC_SNOC] = &qnm_cnoc_datapath,
+ [MASTER_GEM_NOC_PCIE_SNOC] = &qnm_memnoc_pcie,
+ [MASTER_MSS_NAV] = &qxm_mss_nav_ce,
+ [MASTER_TME] = &qxm_tme,
+ [MASTER_IPA_PCIE] = &xm_ipa2pcie,
+ [SLAVE_SNOC_MEM_NOC_SF] = &qns_memnoc_sf,
+ [SLAVE_PCIE_0] = &xs_pcie_0,
+};
+
+static const struct qcom_icc_desc kuno_system_noc = {
+ .nodes = system_noc_nodes,
+ .num_nodes = ARRAY_SIZE(system_noc_nodes),
+ .bcms = system_noc_bcms,
+ .num_bcms = ARRAY_SIZE(system_noc_bcms),
+};
+
+static const struct of_device_id qnoc_of_match[] = {
+ { .compatible = "qcom,kuno-aggre-noc", .data = &kuno_aggre_noc },
+ { .compatible = "qcom,kuno-clk-virt", .data = &kuno_clk_virt },
+ { .compatible = "qcom,kuno-cnoc-main", .data = &kuno_cnoc_main },
+ { .compatible = "qcom,kuno-dc-noc", .data = &kuno_dc_noc },
+ { .compatible = "qcom,kuno-mc-virt", .data = &kuno_mc_virt },
+ { .compatible = "qcom,kuno-mem-noc", .data = &kuno_mem_noc },
+ { .compatible = "qcom,kuno-pcie-anoc", .data = &kuno_pcie_anoc },
+ { .compatible = "qcom,kuno-system-noc", .data = &kuno_system_noc },
+ { }
+};
+MODULE_DEVICE_TABLE(of, qnoc_of_match);
+
+static struct platform_driver qnoc_driver = {
+ .probe = qcom_icc_rpmh_probe,
+ .remove = qcom_icc_rpmh_remove,
+ .driver = {
+ .name = "qnoc-kuno",
+ .of_match_table = qnoc_of_match,
+ .sync_state = icc_sync_state,
+ },
+};
+
+static int __init qnoc_driver_init(void)
+{
+ return platform_driver_register(&qnoc_driver);
+}
+core_initcall(qnoc_driver_init);
+
+static void __exit qnoc_driver_exit(void)
+{
+ platform_driver_unregister(&qnoc_driver);
+}
+module_exit(qnoc_driver_exit);
+
+MODULE_DESCRIPTION("Kuno NoC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/interconnect/qcom/milos.c b/drivers/interconnect/qcom/milos.c
index d010b106728a..8b5b5ee53e3b 100644
--- a/drivers/interconnect/qcom/milos.c
+++ b/drivers/interconnect/qcom/milos.c
@@ -137,7 +137,7 @@ static struct qcom_icc_node srvc_pcie_aggre_noc;
static struct qcom_icc_node qns_gemnoc_gc;
static struct qcom_icc_node qns_gemnoc_sf;
-static struct qcom_icc_qosbox qhm_qup1_qos = {
+static const struct qcom_icc_qosbox qhm_qup1_qos = {
.num_ports = 1,
.port_offsets = { 0xc000 },
.prio = 2,
@@ -154,7 +154,7 @@ static struct qcom_icc_node qhm_qup1 = {
.link_nodes = { &qns_a1noc_snoc },
};
-static struct qcom_icc_qosbox xm_ufs_mem_qos = {
+static const struct qcom_icc_qosbox xm_ufs_mem_qos = {
.num_ports = 1,
.port_offsets = { 0xf200 },
.prio = 2,
@@ -171,7 +171,7 @@ static struct qcom_icc_node xm_ufs_mem = {
.link_nodes = { &qns_a1noc_snoc },
};
-static struct qcom_icc_qosbox xm_usb3_0_qos = {
+static const struct qcom_icc_qosbox xm_usb3_0_qos = {
.num_ports = 1,
.port_offsets = { 0x10000 },
.prio = 2,
@@ -188,7 +188,7 @@ static struct qcom_icc_node xm_usb3_0 = {
.link_nodes = { &qns_a1noc_snoc },
};
-static struct qcom_icc_qosbox qhm_qdss_bam_qos = {
+static const struct qcom_icc_qosbox qhm_qdss_bam_qos = {
.num_ports = 1,
.port_offsets = { 0x14000 },
.prio = 2,
@@ -205,7 +205,7 @@ static struct qcom_icc_node qhm_qdss_bam = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox qhm_qspi_qos = {
+static const struct qcom_icc_qosbox qhm_qspi_qos = {
.num_ports = 1,
.port_offsets = { 0x12000 },
.prio = 2,
@@ -222,7 +222,7 @@ static struct qcom_icc_node qhm_qspi = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox qhm_qup0_qos = {
+static const struct qcom_icc_qosbox qhm_qup0_qos = {
.num_ports = 1,
.port_offsets = { 0x13000 },
.prio = 2,
@@ -239,7 +239,7 @@ static struct qcom_icc_node qhm_qup0 = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox qxm_crypto_qos = {
+static const struct qcom_icc_qosbox qxm_crypto_qos = {
.num_ports = 1,
.port_offsets = { 0x15000 },
.prio = 2,
@@ -256,7 +256,7 @@ static struct qcom_icc_node qxm_crypto = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox qxm_ipa_qos = {
+static const struct qcom_icc_qosbox qxm_ipa_qos = {
.num_ports = 1,
.port_offsets = { 0x16000 },
.prio = 2,
@@ -273,7 +273,7 @@ static struct qcom_icc_node qxm_ipa = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox xm_qdss_etr_0_qos = {
+static const struct qcom_icc_qosbox xm_qdss_etr_0_qos = {
.num_ports = 1,
.port_offsets = { 0x17000 },
.prio = 2,
@@ -290,7 +290,7 @@ static struct qcom_icc_node xm_qdss_etr_0 = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox xm_qdss_etr_1_qos = {
+static const struct qcom_icc_qosbox xm_qdss_etr_1_qos = {
.num_ports = 1,
.port_offsets = { 0x18000 },
.prio = 2,
@@ -307,7 +307,7 @@ static struct qcom_icc_node xm_qdss_etr_1 = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox xm_sdc1_qos = {
+static const struct qcom_icc_qosbox xm_sdc1_qos = {
.num_ports = 1,
.port_offsets = { 0x1a000 },
.prio = 2,
@@ -324,7 +324,7 @@ static struct qcom_icc_node xm_sdc1 = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox xm_sdc2_qos = {
+static const struct qcom_icc_qosbox xm_sdc2_qos = {
.num_ports = 1,
.port_offsets = { 0x19000 },
.prio = 2,
@@ -404,7 +404,7 @@ static struct qcom_icc_node qnm_gemnoc_pcie = {
.link_nodes = { &xs_pcie_0, &xs_pcie_1 },
};
-static struct qcom_icc_qosbox alm_gpu_tcu_qos = {
+static const struct qcom_icc_qosbox alm_gpu_tcu_qos = {
.num_ports = 1,
.port_offsets = { 0xf1000 },
.prio = 1,
@@ -421,7 +421,7 @@ static struct qcom_icc_node alm_gpu_tcu = {
.link_nodes = { &qns_gem_noc_cnoc, &qns_llcc },
};
-static struct qcom_icc_qosbox alm_sys_tcu_qos = {
+static const struct qcom_icc_qosbox alm_sys_tcu_qos = {
.num_ports = 1,
.port_offsets = { 0xf3000 },
.prio = 6,
@@ -447,7 +447,7 @@ static struct qcom_icc_node chm_apps = {
&qns_pcie },
};
-static struct qcom_icc_qosbox qnm_gpu_qos = {
+static const struct qcom_icc_qosbox qnm_gpu_qos = {
.num_ports = 2,
.port_offsets = { 0x31000, 0x71000 },
.prio = 0,
@@ -464,7 +464,7 @@ static struct qcom_icc_node qnm_gpu = {
.link_nodes = { &qns_gem_noc_cnoc, &qns_llcc },
};
-static struct qcom_icc_qosbox qnm_lpass_gemnoc_qos = {
+static const struct qcom_icc_qosbox qnm_lpass_gemnoc_qos = {
.num_ports = 1,
.port_offsets = { 0xf5000 },
.prio = 0,
@@ -491,7 +491,7 @@ static struct qcom_icc_node qnm_mdsp = {
&qns_pcie },
};
-static struct qcom_icc_qosbox qnm_mnoc_hf_qos = {
+static const struct qcom_icc_qosbox qnm_mnoc_hf_qos = {
.num_ports = 2,
.port_offsets = { 0x33000, 0x73000 },
.prio = 0,
@@ -508,7 +508,7 @@ static struct qcom_icc_node qnm_mnoc_hf = {
.link_nodes = { &qns_gem_noc_cnoc, &qns_llcc },
};
-static struct qcom_icc_qosbox qnm_mnoc_sf_qos = {
+static const struct qcom_icc_qosbox qnm_mnoc_sf_qos = {
.num_ports = 2,
.port_offsets = { 0x35000, 0x75000 },
.prio = 0,
@@ -525,7 +525,7 @@ static struct qcom_icc_node qnm_mnoc_sf = {
.link_nodes = { &qns_gem_noc_cnoc, &qns_llcc },
};
-static struct qcom_icc_qosbox qnm_nsp_gemnoc_qos = {
+static const struct qcom_icc_qosbox qnm_nsp_gemnoc_qos = {
.num_ports = 2,
.port_offsets = { 0x37000, 0x77000 },
.prio = 0,
@@ -543,7 +543,7 @@ static struct qcom_icc_node qnm_nsp_gemnoc = {
&qns_pcie },
};
-static struct qcom_icc_qosbox qnm_pcie_qos = {
+static const struct qcom_icc_qosbox qnm_pcie_qos = {
.num_ports = 1,
.port_offsets = { 0xf7000 },
.prio = 2,
@@ -560,7 +560,7 @@ static struct qcom_icc_node qnm_pcie = {
.link_nodes = { &qns_gem_noc_cnoc, &qns_llcc },
};
-static struct qcom_icc_qosbox qnm_snoc_gc_qos = {
+static const struct qcom_icc_qosbox qnm_snoc_gc_qos = {
.num_ports = 1,
.port_offsets = { 0xf9000 },
.prio = 0,
@@ -577,7 +577,7 @@ static struct qcom_icc_node qnm_snoc_gc = {
.link_nodes = { &qns_llcc },
};
-static struct qcom_icc_qosbox qnm_snoc_sf_qos = {
+static const struct qcom_icc_qosbox qnm_snoc_sf_qos = {
.num_ports = 1,
.port_offsets = { 0xfb000 },
.prio = 0,
@@ -620,7 +620,7 @@ static struct qcom_icc_node llcc_mc = {
.link_nodes = { &ebi },
};
-static struct qcom_icc_qosbox qnm_camnoc_hf_qos = {
+static const struct qcom_icc_qosbox qnm_camnoc_hf_qos = {
.num_ports = 2,
.port_offsets = { 0xa8000, 0xa9000 },
.prio = 0,
@@ -637,7 +637,7 @@ static struct qcom_icc_node qnm_camnoc_hf = {
.link_nodes = { &qns_mem_noc_hf },
};
-static struct qcom_icc_qosbox qnm_camnoc_icp_qos = {
+static const struct qcom_icc_qosbox qnm_camnoc_icp_qos = {
.num_ports = 1,
.port_offsets = { 0x2a000 },
.prio = 5,
@@ -654,7 +654,7 @@ static struct qcom_icc_node qnm_camnoc_icp = {
.link_nodes = { &qns_mem_noc_sf },
};
-static struct qcom_icc_qosbox qnm_camnoc_sf_qos = {
+static const struct qcom_icc_qosbox qnm_camnoc_sf_qos = {
.num_ports = 2,
.port_offsets = { 0x2b000, 0x2c000 },
.prio = 0,
@@ -671,7 +671,7 @@ static struct qcom_icc_node qnm_camnoc_sf = {
.link_nodes = { &qns_mem_noc_sf },
};
-static struct qcom_icc_qosbox qnm_mdp_qos = {
+static const struct qcom_icc_qosbox qnm_mdp_qos = {
.num_ports = 1,
.port_offsets = { 0xad000 },
.prio = 0,
@@ -688,7 +688,7 @@ static struct qcom_icc_node qnm_mdp = {
.link_nodes = { &qns_mem_noc_hf },
};
-static struct qcom_icc_qosbox qnm_video_qos = {
+static const struct qcom_icc_qosbox qnm_video_qos = {
.num_ports = 1,
.port_offsets = { 0x30000 },
.prio = 0,
@@ -737,7 +737,7 @@ static struct qcom_icc_node qsm_pcie_anoc_cfg = {
.link_nodes = { &srvc_pcie_aggre_noc },
};
-static struct qcom_icc_qosbox xm_pcie3_0_qos = {
+static const struct qcom_icc_qosbox xm_pcie3_0_qos = {
.num_ports = 1,
.port_offsets = { 0xb000 },
.prio = 2,
@@ -754,7 +754,7 @@ static struct qcom_icc_node xm_pcie3_0 = {
.link_nodes = { &qns_pcie_mem_noc },
};
-static struct qcom_icc_qosbox xm_pcie3_1_qos = {
+static const struct qcom_icc_qosbox xm_pcie3_1_qos = {
.num_ports = 1,
.port_offsets = { 0xc000 },
.prio = 2,
@@ -787,7 +787,7 @@ static struct qcom_icc_node qnm_aggre2_noc = {
.link_nodes = { &qns_gemnoc_sf },
};
-static struct qcom_icc_qosbox qnm_apss_noc_qos = {
+static const struct qcom_icc_qosbox qnm_apss_noc_qos = {
.num_ports = 1,
.port_offsets = { 0x1c000 },
.prio = 2,
@@ -804,7 +804,7 @@ static struct qcom_icc_node qnm_apss_noc = {
.link_nodes = { &qns_gemnoc_sf },
};
-static struct qcom_icc_qosbox qnm_cnoc_data_qos = {
+static const struct qcom_icc_qosbox qnm_cnoc_data_qos = {
.num_ports = 1,
.port_offsets = { 0x1d000 },
.prio = 2,
@@ -821,7 +821,7 @@ static struct qcom_icc_node qnm_cnoc_data = {
.link_nodes = { &qns_gemnoc_sf },
};
-static struct qcom_icc_qosbox qxm_pimem_qos = {
+static const struct qcom_icc_qosbox qxm_pimem_qos = {
.num_ports = 1,
.port_offsets = { 0x1e000 },
.prio = 2,
@@ -838,7 +838,7 @@ static struct qcom_icc_node qxm_pimem = {
.link_nodes = { &qns_gemnoc_gc },
};
-static struct qcom_icc_qosbox xm_gic_qos = {
+static const struct qcom_icc_qosbox xm_gic_qos = {
.num_ports = 1,
.port_offsets = { 0x1f000 },
.prio = 2,
diff --git a/drivers/interconnect/qcom/msm8976.c b/drivers/interconnect/qcom/msm8976.c
index c219dcfd43b6..11100264ef69 100644
--- a/drivers/interconnect/qcom/msm8976.c
+++ b/drivers/interconnect/qcom/msm8976.c
@@ -454,7 +454,8 @@ static struct qcom_icc_node mas_oxili = {
.qos.qos_mode = NOC_QOS_MODE_BYPASS,
.qos.areq_prio = 0,
.qos.prio_level = 0,
- .qos.qos_port = 16, /* [16, 17] */
+ .qos.qos_port = 16,
+ .qos.aux_qos_port = 17,
.num_links = ARRAY_SIZE(mas_oxili_links),
.links = mas_oxili_links,
};
diff --git a/drivers/interconnect/qcom/msm8996.c b/drivers/interconnect/qcom/msm8996.c
index 882f9d2b6d4e..7fc808cc5975 100644
--- a/drivers/interconnect/qcom/msm8996.c
+++ b/drivers/interconnect/qcom/msm8996.c
@@ -501,6 +501,7 @@ static struct qcom_icc_node mas_venus = {
.qos.areq_prio = 0,
.qos.prio_level = 0,
.qos.qos_port = 3,
+ .qos.aux_qos_port = 4,
.num_links = ARRAY_SIZE(mas_mnoc_bimc_common_links),
.links = mas_mnoc_bimc_common_links
};
diff --git a/drivers/interconnect/qcom/sm6350.c b/drivers/interconnect/qcom/sm6350.c
index c098c608b836..8680d242ccfa 100644
--- a/drivers/interconnect/qcom/sm6350.c
+++ b/drivers/interconnect/qcom/sm6350.c
@@ -149,7 +149,7 @@ static struct qcom_icc_node qhm_a1noc_cfg = {
.link_nodes = { &srvc_aggre1_noc },
};
-static struct qcom_icc_qosbox qhm_qup_0_qos = {
+static const struct qcom_icc_qosbox qhm_qup_0_qos = {
.num_ports = 1,
.port_offsets = { 0xa000 },
.prio = 2,
@@ -165,7 +165,7 @@ static struct qcom_icc_node qhm_qup_0 = {
.link_nodes = { &qns_a1noc_snoc },
};
-static struct qcom_icc_qosbox xm_emmc_qos = {
+static const struct qcom_icc_qosbox xm_emmc_qos = {
.num_ports = 1,
.port_offsets = { 0x7000 },
.prio = 2,
@@ -181,7 +181,7 @@ static struct qcom_icc_node xm_emmc = {
.link_nodes = { &qns_a1noc_snoc },
};
-static struct qcom_icc_qosbox xm_ufs_mem_qos = {
+static const struct qcom_icc_qosbox xm_ufs_mem_qos = {
.num_ports = 1,
.port_offsets = { 0x8000 },
.prio = 4,
@@ -205,7 +205,7 @@ static struct qcom_icc_node qhm_a2noc_cfg = {
.link_nodes = { &srvc_aggre2_noc },
};
-static struct qcom_icc_qosbox qhm_qdss_bam_qos = {
+static const struct qcom_icc_qosbox qhm_qdss_bam_qos = {
.num_ports = 1,
.port_offsets = { 0xb000 },
.prio = 2,
@@ -221,7 +221,7 @@ static struct qcom_icc_node qhm_qdss_bam = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox qhm_qup_1_qos = {
+static const struct qcom_icc_qosbox qhm_qup_1_qos = {
.num_ports = 1,
.port_offsets = { 0x9000 },
.prio = 2,
@@ -236,7 +236,7 @@ static struct qcom_icc_node qhm_qup_1 = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox qxm_crypto_qos = {
+static const struct qcom_icc_qosbox qxm_crypto_qos = {
.num_ports = 1,
.port_offsets = { 0x6000 },
.prio = 2,
@@ -252,7 +252,7 @@ static struct qcom_icc_node qxm_crypto = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox qxm_ipa_qos = {
+static const struct qcom_icc_qosbox qxm_ipa_qos = {
.num_ports = 1,
.port_offsets = { 0x7000 },
.prio = 2,
@@ -268,7 +268,7 @@ static struct qcom_icc_node qxm_ipa = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox xm_qdss_etr_qos = {
+static const struct qcom_icc_qosbox xm_qdss_etr_qos = {
.num_ports = 1,
.port_offsets = { 0xc000 },
.prio = 2,
@@ -284,7 +284,7 @@ static struct qcom_icc_node xm_qdss_etr = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox xm_sdc2_qos = {
+static const struct qcom_icc_qosbox xm_sdc2_qos = {
.num_ports = 1,
.port_offsets = { 0x18000 },
.prio = 2,
@@ -300,7 +300,7 @@ static struct qcom_icc_node xm_sdc2 = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox xm_usb3_0_qos = {
+static const struct qcom_icc_qosbox xm_usb3_0_qos = {
.num_ports = 1,
.port_offsets = { 0xd000 },
.prio = 2,
@@ -356,7 +356,7 @@ static struct qcom_icc_node qup1_core_master = {
.link_nodes = { &qup1_core_slave },
};
-static struct qcom_icc_qosbox qnm_npu_qos = {
+static const struct qcom_icc_qosbox qnm_npu_qos = {
.num_ports = 2,
.port_offsets = { 0xf000, 0x11000 },
.prio = 0,
@@ -372,7 +372,7 @@ static struct qcom_icc_node qnm_npu = {
.link_nodes = { &qns_cdsp_gemnoc },
};
-static struct qcom_icc_qosbox qxm_npu_dsp_qos = {
+static const struct qcom_icc_qosbox qxm_npu_dsp_qos = {
.num_ports = 1,
.port_offsets = { 0x13000 },
.prio = 0,
@@ -495,7 +495,7 @@ static struct qcom_icc_node qhm_cnoc_dc_noc = {
&qhs_gemnoc },
};
-static struct qcom_icc_qosbox acm_apps_qos = {
+static const struct qcom_icc_qosbox acm_apps_qos = {
.num_ports = 2,
.port_offsets = { 0x2f100, 0x2f000 },
.prio = 0,
@@ -512,7 +512,7 @@ static struct qcom_icc_node acm_apps = {
&qns_gem_noc_snoc },
};
-static struct qcom_icc_qosbox acm_sys_tcu_qos = {
+static const struct qcom_icc_qosbox acm_sys_tcu_qos = {
.num_ports = 1,
.port_offsets = { 0x35000 },
.prio = 6,
@@ -539,7 +539,7 @@ static struct qcom_icc_node qhm_gemnoc_cfg = {
&qhs_mdsp_ms_mpu_cfg },
};
-static struct qcom_icc_qosbox qnm_cmpnoc_qos = {
+static const struct qcom_icc_qosbox qnm_cmpnoc_qos = {
.num_ports = 1,
.port_offsets = { 0x2e000 },
.prio = 0,
@@ -556,7 +556,7 @@ static struct qcom_icc_node qnm_cmpnoc = {
&qns_gem_noc_snoc },
};
-static struct qcom_icc_qosbox qnm_mnoc_hf_qos = {
+static const struct qcom_icc_qosbox qnm_mnoc_hf_qos = {
.num_ports = 1,
.port_offsets = { 0x30000 },
.prio = 0,
@@ -573,7 +573,7 @@ static struct qcom_icc_node qnm_mnoc_hf = {
&qns_gem_noc_snoc },
};
-static struct qcom_icc_qosbox qnm_mnoc_sf_qos = {
+static const struct qcom_icc_qosbox qnm_mnoc_sf_qos = {
.num_ports = 1,
.port_offsets = { 0x34000 },
.prio = 0,
@@ -590,7 +590,7 @@ static struct qcom_icc_node qnm_mnoc_sf = {
&qns_gem_noc_snoc },
};
-static struct qcom_icc_qosbox qnm_snoc_gc_qos = {
+static const struct qcom_icc_qosbox qnm_snoc_gc_qos = {
.num_ports = 1,
.port_offsets = { 0x32000 },
.prio = 0,
@@ -606,7 +606,7 @@ static struct qcom_icc_node qnm_snoc_gc = {
.link_nodes = { &qns_llcc },
};
-static struct qcom_icc_qosbox qnm_snoc_sf_qos = {
+static const struct qcom_icc_qosbox qnm_snoc_sf_qos = {
.num_ports = 1,
.port_offsets = { 0x31000 },
.prio = 0,
@@ -622,7 +622,7 @@ static struct qcom_icc_node qnm_snoc_sf = {
.link_nodes = { &qns_llcc },
};
-static struct qcom_icc_qosbox qxm_gpu_qos = {
+static const struct qcom_icc_qosbox qxm_gpu_qos = {
.num_ports = 2,
.port_offsets = { 0x33000, 0x33080 },
.prio = 0,
@@ -655,7 +655,7 @@ static struct qcom_icc_node qhm_mnoc_cfg = {
.link_nodes = { &srvc_mnoc },
};
-static struct qcom_icc_qosbox qnm_video0_qos = {
+static const struct qcom_icc_qosbox qnm_video0_qos = {
.num_ports = 1,
.port_offsets = { 0xf000 },
.prio = 2,
@@ -671,7 +671,7 @@ static struct qcom_icc_node qnm_video0 = {
.link_nodes = { &qns_mem_noc_sf },
};
-static struct qcom_icc_qosbox qnm_video_cvp_qos = {
+static const struct qcom_icc_qosbox qnm_video_cvp_qos = {
.num_ports = 1,
.port_offsets = { 0xe000 },
.prio = 5,
@@ -687,7 +687,7 @@ static struct qcom_icc_node qnm_video_cvp = {
.link_nodes = { &qns_mem_noc_sf },
};
-static struct qcom_icc_qosbox qxm_camnoc_hf_qos = {
+static const struct qcom_icc_qosbox qxm_camnoc_hf_qos = {
.num_ports = 2,
.port_offsets = { 0xa000, 0xb000 },
.prio = 3,
@@ -703,7 +703,7 @@ static struct qcom_icc_node qxm_camnoc_hf = {
.link_nodes = { &qns_mem_noc_hf },
};
-static struct qcom_icc_qosbox qxm_camnoc_icp_qos = {
+static const struct qcom_icc_qosbox qxm_camnoc_icp_qos = {
.num_ports = 1,
.port_offsets = { 0xd000 },
.prio = 5,
@@ -719,7 +719,7 @@ static struct qcom_icc_node qxm_camnoc_icp = {
.link_nodes = { &qns_mem_noc_sf },
};
-static struct qcom_icc_qosbox qxm_camnoc_sf_qos = {
+static const struct qcom_icc_qosbox qxm_camnoc_sf_qos = {
.num_ports = 1,
.port_offsets = { 0x9000 },
.prio = 3,
@@ -735,7 +735,7 @@ static struct qcom_icc_node qxm_camnoc_sf = {
.link_nodes = { &qns_mem_noc_sf },
};
-static struct qcom_icc_qosbox qxm_mdp0_qos = {
+static const struct qcom_icc_qosbox qxm_mdp0_qos = {
.num_ports = 1,
.port_offsets = { 0xc000 },
.prio = 3,
@@ -822,7 +822,7 @@ static struct qcom_icc_node qnm_gemnoc = {
&xs_qdss_stm },
};
-static struct qcom_icc_qosbox qxm_pimem_qos = {
+static const struct qcom_icc_qosbox qxm_pimem_qos = {
.num_ports = 1,
.port_offsets = { 0xd000 },
.prio = 2,
@@ -839,7 +839,7 @@ static struct qcom_icc_node qxm_pimem = {
&qxs_imem },
};
-static struct qcom_icc_qosbox xm_gic_qos = {
+static const struct qcom_icc_qosbox xm_gic_qos = {
.num_ports = 1,
.port_offsets = { 0xb000 },
.prio = 3,
diff --git a/drivers/interconnect/qcom/sm8650.c b/drivers/interconnect/qcom/sm8650.c
index cf3ae734d4c3..1967475b9132 100644
--- a/drivers/interconnect/qcom/sm8650.c
+++ b/drivers/interconnect/qcom/sm8650.c
@@ -154,7 +154,7 @@ static const struct regmap_config icc_regmap_config = {
.fast_io = true,
};
-static struct qcom_icc_qosbox qhm_qspi_qos = {
+static const struct qcom_icc_qosbox qhm_qspi_qos = {
.num_ports = 1,
.port_offsets = { 0xc000 },
.prio = 2,
@@ -171,7 +171,7 @@ static struct qcom_icc_node qhm_qspi = {
.link_nodes = { &qns_a1noc_snoc },
};
-static struct qcom_icc_qosbox qhm_qup1_qos = {
+static const struct qcom_icc_qosbox qhm_qup1_qos = {
.num_ports = 1,
.port_offsets = { 0xd000 },
.prio = 2,
@@ -196,7 +196,7 @@ static struct qcom_icc_node qxm_qup02 = {
.link_nodes = { &qns_a1noc_snoc },
};
-static struct qcom_icc_qosbox xm_sdc4_qos = {
+static const struct qcom_icc_qosbox xm_sdc4_qos = {
.num_ports = 1,
.port_offsets = { 0xe000 },
.prio = 2,
@@ -213,7 +213,7 @@ static struct qcom_icc_node xm_sdc4 = {
.link_nodes = { &qns_a1noc_snoc },
};
-static struct qcom_icc_qosbox xm_ufs_mem_qos = {
+static const struct qcom_icc_qosbox xm_ufs_mem_qos = {
.num_ports = 1,
.port_offsets = { 0xf000 },
.prio = 2,
@@ -230,7 +230,7 @@ static struct qcom_icc_node xm_ufs_mem = {
.link_nodes = { &qns_a1noc_snoc },
};
-static struct qcom_icc_qosbox xm_usb3_0_qos = {
+static const struct qcom_icc_qosbox xm_usb3_0_qos = {
.num_ports = 1,
.port_offsets = { 0x10000 },
.prio = 2,
@@ -247,7 +247,7 @@ static struct qcom_icc_node xm_usb3_0 = {
.link_nodes = { &qns_a1noc_snoc },
};
-static struct qcom_icc_qosbox qhm_qdss_bam_qos = {
+static const struct qcom_icc_qosbox qhm_qdss_bam_qos = {
.num_ports = 1,
.port_offsets = { 0x12000 },
.prio = 2,
@@ -264,7 +264,7 @@ static struct qcom_icc_node qhm_qdss_bam = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox qhm_qup2_qos = {
+static const struct qcom_icc_qosbox qhm_qup2_qos = {
.num_ports = 1,
.port_offsets = { 0x13000 },
.prio = 2,
@@ -281,7 +281,7 @@ static struct qcom_icc_node qhm_qup2 = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox qxm_crypto_qos = {
+static const struct qcom_icc_qosbox qxm_crypto_qos = {
.num_ports = 1,
.port_offsets = { 0x15000 },
.prio = 2,
@@ -298,7 +298,7 @@ static struct qcom_icc_node qxm_crypto = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox qxm_ipa_qos = {
+static const struct qcom_icc_qosbox qxm_ipa_qos = {
.num_ports = 1,
.port_offsets = { 0x16000 },
.prio = 2,
@@ -323,7 +323,7 @@ static struct qcom_icc_node qxm_sp = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox xm_qdss_etr_0_qos = {
+static const struct qcom_icc_qosbox xm_qdss_etr_0_qos = {
.num_ports = 1,
.port_offsets = { 0x17000 },
.prio = 2,
@@ -340,7 +340,7 @@ static struct qcom_icc_node xm_qdss_etr_0 = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox xm_qdss_etr_1_qos = {
+static const struct qcom_icc_qosbox xm_qdss_etr_1_qos = {
.num_ports = 1,
.port_offsets = { 0x18000 },
.prio = 2,
@@ -357,7 +357,7 @@ static struct qcom_icc_node xm_qdss_etr_1 = {
.link_nodes = { &qns_a2noc_snoc },
};
-static struct qcom_icc_qosbox xm_sdc2_qos = {
+static const struct qcom_icc_qosbox xm_sdc2_qos = {
.num_ports = 1,
.port_offsets = { 0x19000 },
.prio = 2,
@@ -448,7 +448,7 @@ static struct qcom_icc_node qnm_gemnoc_pcie = {
.link_nodes = { &xs_pcie_0, &xs_pcie_1 },
};
-static struct qcom_icc_qosbox alm_gpu_tcu_qos = {
+static const struct qcom_icc_qosbox alm_gpu_tcu_qos = {
.num_ports = 1,
.port_offsets = { 0xbf000 },
.prio = 1,
@@ -465,7 +465,7 @@ static struct qcom_icc_node alm_gpu_tcu = {
.link_nodes = { &qns_gem_noc_cnoc, &qns_llcc },
};
-static struct qcom_icc_qosbox alm_sys_tcu_qos = {
+static const struct qcom_icc_qosbox alm_sys_tcu_qos = {
.num_ports = 1,
.port_offsets = { 0xc1000 },
.prio = 6,
@@ -482,7 +482,7 @@ static struct qcom_icc_node alm_sys_tcu = {
.link_nodes = { &qns_gem_noc_cnoc, &qns_llcc },
};
-static struct qcom_icc_qosbox alm_ubwc_p_tcu_qos = {
+static const struct qcom_icc_qosbox alm_ubwc_p_tcu_qos = {
.num_ports = 1,
.port_offsets = { 0xc5000 },
.prio = 1,
@@ -508,7 +508,7 @@ static struct qcom_icc_node chm_apps = {
&qns_pcie },
};
-static struct qcom_icc_qosbox qnm_gpu_qos = {
+static const struct qcom_icc_qosbox qnm_gpu_qos = {
.num_ports = 2,
.port_offsets = { 0x31000, 0x71000 },
.prio = 0,
@@ -525,7 +525,7 @@ static struct qcom_icc_node qnm_gpu = {
.link_nodes = { &qns_gem_noc_cnoc, &qns_llcc },
};
-static struct qcom_icc_qosbox qnm_lpass_gemnoc_qos = {
+static const struct qcom_icc_qosbox qnm_lpass_gemnoc_qos = {
.num_ports = 1,
.port_offsets = { 0xb5000 },
.prio = 0,
@@ -552,7 +552,7 @@ static struct qcom_icc_node qnm_mdsp = {
&qns_pcie },
};
-static struct qcom_icc_qosbox qnm_mnoc_hf_qos = {
+static const struct qcom_icc_qosbox qnm_mnoc_hf_qos = {
.num_ports = 2,
.port_offsets = { 0x33000, 0x73000 },
.prio = 0,
@@ -569,7 +569,7 @@ static struct qcom_icc_node qnm_mnoc_hf = {
.link_nodes = { &qns_gem_noc_cnoc, &qns_llcc },
};
-static struct qcom_icc_qosbox qnm_mnoc_sf_qos = {
+static const struct qcom_icc_qosbox qnm_mnoc_sf_qos = {
.num_ports = 2,
.port_offsets = { 0x35000, 0x75000 },
.prio = 0,
@@ -586,7 +586,7 @@ static struct qcom_icc_node qnm_mnoc_sf = {
.link_nodes = { &qns_gem_noc_cnoc, &qns_llcc },
};
-static struct qcom_icc_qosbox qnm_nsp_gemnoc_qos = {
+static const struct qcom_icc_qosbox qnm_nsp_gemnoc_qos = {
.num_ports = 2,
.port_offsets = { 0x37000, 0x77000 },
.prio = 0,
@@ -604,7 +604,7 @@ static struct qcom_icc_node qnm_nsp_gemnoc = {
&qns_pcie },
};
-static struct qcom_icc_qosbox qnm_pcie_qos = {
+static const struct qcom_icc_qosbox qnm_pcie_qos = {
.num_ports = 1,
.port_offsets = { 0xb7000 },
.prio = 2,
@@ -621,7 +621,7 @@ static struct qcom_icc_node qnm_pcie = {
.link_nodes = { &qns_gem_noc_cnoc, &qns_llcc },
};
-static struct qcom_icc_qosbox qnm_snoc_sf_qos = {
+static const struct qcom_icc_qosbox qnm_snoc_sf_qos = {
.num_ports = 1,
.port_offsets = { 0xbb000 },
.prio = 0,
@@ -639,7 +639,7 @@ static struct qcom_icc_node qnm_snoc_sf = {
&qns_pcie },
};
-static struct qcom_icc_qosbox qnm_ubwc_p_qos = {
+static const struct qcom_icc_qosbox qnm_ubwc_p_qos = {
.num_ports = 1,
.port_offsets = { 0xc3000 },
.prio = 1,
@@ -656,7 +656,7 @@ static struct qcom_icc_node qnm_ubwc_p = {
.link_nodes = { &qns_llcc },
};
-static struct qcom_icc_qosbox xm_gic_qos = {
+static const struct qcom_icc_qosbox xm_gic_qos = {
.num_ports = 1,
.port_offsets = { 0xb9000 },
.prio = 4,
@@ -705,7 +705,7 @@ static struct qcom_icc_node llcc_mc = {
.link_nodes = { &ebi },
};
-static struct qcom_icc_qosbox qnm_camnoc_hf_qos = {
+static const struct qcom_icc_qosbox qnm_camnoc_hf_qos = {
.num_ports = 2,
.port_offsets = { 0x28000, 0x29000 },
.prio = 0,
@@ -722,7 +722,7 @@ static struct qcom_icc_node qnm_camnoc_hf = {
.link_nodes = { &qns_mem_noc_hf },
};
-static struct qcom_icc_qosbox qnm_camnoc_icp_qos = {
+static const struct qcom_icc_qosbox qnm_camnoc_icp_qos = {
.num_ports = 1,
.port_offsets = { 0x2a000 },
.prio = 4,
@@ -739,7 +739,7 @@ static struct qcom_icc_node qnm_camnoc_icp = {
.link_nodes = { &qns_mem_noc_sf },
};
-static struct qcom_icc_qosbox qnm_camnoc_sf_qos = {
+static const struct qcom_icc_qosbox qnm_camnoc_sf_qos = {
.num_ports = 2,
.port_offsets = { 0x2b000, 0x2c000 },
.prio = 0,
@@ -756,7 +756,7 @@ static struct qcom_icc_node qnm_camnoc_sf = {
.link_nodes = { &qns_mem_noc_sf },
};
-static struct qcom_icc_qosbox qnm_mdp_qos = {
+static const struct qcom_icc_qosbox qnm_mdp_qos = {
.num_ports = 2,
.port_offsets = { 0x2d000, 0x2e000 },
.prio = 0,
@@ -781,7 +781,7 @@ static struct qcom_icc_node qnm_vapss_hcp = {
.link_nodes = { &qns_mem_noc_sf },
};
-static struct qcom_icc_qosbox qnm_video_qos = {
+static const struct qcom_icc_qosbox qnm_video_qos = {
.num_ports = 2,
.port_offsets = { 0x30000, 0x31000 },
.prio = 0,
@@ -798,7 +798,7 @@ static struct qcom_icc_node qnm_video = {
.link_nodes = { &qns_mem_noc_sf },
};
-static struct qcom_icc_qosbox qnm_video_cv_cpu_qos = {
+static const struct qcom_icc_qosbox qnm_video_cv_cpu_qos = {
.num_ports = 1,
.port_offsets = { 0x32000 },
.prio = 4,
@@ -815,7 +815,7 @@ static struct qcom_icc_node qnm_video_cv_cpu = {
.link_nodes = { &qns_mem_noc_sf },
};
-static struct qcom_icc_qosbox qnm_video_cvp_qos = {
+static const struct qcom_icc_qosbox qnm_video_cvp_qos = {
.num_ports = 2,
.port_offsets = { 0x33000, 0x34000 },
.prio = 0,
@@ -832,7 +832,7 @@ static struct qcom_icc_node qnm_video_cvp = {
.link_nodes = { &qns_mem_noc_sf },
};
-static struct qcom_icc_qosbox qnm_video_v_cpu_qos = {
+static const struct qcom_icc_qosbox qnm_video_v_cpu_qos = {
.num_ports = 1,
.port_offsets = { 0x35000 },
.prio = 4,
@@ -873,7 +873,7 @@ static struct qcom_icc_node qsm_pcie_anoc_cfg = {
.link_nodes = { &srvc_pcie_aggre_noc },
};
-static struct qcom_icc_qosbox xm_pcie3_0_qos = {
+static const struct qcom_icc_qosbox xm_pcie3_0_qos = {
.num_ports = 1,
.port_offsets = { 0xb000 },
.prio = 3,
@@ -890,7 +890,7 @@ static struct qcom_icc_node xm_pcie3_0 = {
.link_nodes = { &qns_pcie_mem_noc },
};
-static struct qcom_icc_qosbox xm_pcie3_1_qos = {
+static const struct qcom_icc_qosbox xm_pcie3_1_qos = {
.num_ports = 1,
.port_offsets = { 0xc000 },
.prio = 2,
@@ -923,7 +923,7 @@ static struct qcom_icc_node qnm_aggre2_noc = {
.link_nodes = { &qns_gemnoc_sf },
};
-static struct qcom_icc_qosbox qnm_apss_noc_qos = {
+static const struct qcom_icc_qosbox qnm_apss_noc_qos = {
.num_ports = 1,
.port_offsets = { 0x1c000 },
.prio = 2,
diff --git a/drivers/interconnect/qcom/smd-rpm.c b/drivers/interconnect/qcom/smd-rpm.c
index dbc7ae50b02b..6ac9aef942ad 100644
--- a/drivers/interconnect/qcom/smd-rpm.c
+++ b/drivers/interconnect/qcom/smd-rpm.c
@@ -20,7 +20,7 @@ static struct qcom_smd_rpm *icc_smd_rpm;
struct icc_rpm_smd_req {
__le32 key;
__le32 nbytes;
- __le32 value;
+ __le64 value;
};
bool qcom_icc_rpm_smd_available(void)
@@ -29,12 +29,12 @@ bool qcom_icc_rpm_smd_available(void)
}
EXPORT_SYMBOL_GPL(qcom_icc_rpm_smd_available);
-int qcom_icc_rpm_smd_send(int ctx, int rsc_type, int id, u32 val)
+int qcom_icc_rpm_smd_send(int ctx, int rsc_type, int id, u64 val)
{
struct icc_rpm_smd_req req = {
.key = cpu_to_le32(RPM_KEY_BW),
- .nbytes = cpu_to_le32(sizeof(u32)),
- .value = cpu_to_le32(val),
+ .nbytes = cpu_to_le32(sizeof(u64)),
+ .value = cpu_to_le64(val),
};
return qcom_rpm_smd_write(icc_smd_rpm, ctx, rsc_type, id, &req,
diff --git a/drivers/interconnect/samsung/exynos.c b/drivers/interconnect/samsung/exynos.c
index 8e8f56186a36..9a4789fadac2 100644
--- a/drivers/interconnect/samsung/exynos.c
+++ b/drivers/interconnect/samsung/exynos.c
@@ -98,6 +98,7 @@ static void exynos_generic_icc_remove(struct platform_device *pdev)
struct exynos_icc_priv *priv = platform_get_drvdata(pdev);
icc_provider_deregister(&priv->provider);
+ dev_pm_qos_remove_request(&priv->qos_req);
icc_nodes_remove(&priv->provider);
}
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 7364931dad3a..c40e89cc5724 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -256,6 +256,8 @@ config HP_ILO
config QCOM_COINCELL
tristate "Qualcomm coincell charger support"
depends on MFD_SPMI_PMIC || COMPILE_TEST
+ depends on SPMI
+ select REGMAP_SPMI
help
This driver supports the coincell block found inside of
Qualcomm PMICs. The coincell charger provides a means to
@@ -568,6 +570,19 @@ config MCHP_LAN966X_PCI
- lan966x-miim (MDIO_MSCC_MIIM)
- lan966x-switch (LAN966X_SWITCH)
+config TC9564_PCI
+ tristate "Toshiba TC9564 PCI function support"
+ depends on PCI_DYNAMIC_OF_NODES
+ depends on PCI_QUIRKS
+ default m
+ help
+ This enables support for two PCI functions implemented by the
+ embedded PCIe endpoint in the Toshiba TC9564 SoC. A platform
+ driver uses a pci-ep-bus node in devicetree to provide MMIO
+ access to other SoC devices through PCI function BARs. A PCI
+ quirk causes the ranges property in the endpoint's devicetree
+ node to be updated dynamically.
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index e8d8d5d88c0d..7cc3d615d37a 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -71,3 +71,4 @@ obj-y += keba/
obj-y += amd-sbi/
obj-$(CONFIG_MISC_RP1) += rp1/
obj-$(CONFIG_INTEL_SSEI) += issei/
+obj-$(CONFIG_TC9564_PCI) += tc9564-pci.o
diff --git a/drivers/misc/ad525x_dpot.c b/drivers/misc/ad525x_dpot.c
index a4e22fd4a107..79054250976e 100644
--- a/drivers/misc/ad525x_dpot.c
+++ b/drivers/misc/ad525x_dpot.c
@@ -451,10 +451,8 @@ static ssize_t sysfs_set_reg(struct device *dev,
int err;
if (reg & DPOT_ADDR_OTP_EN) {
- if (sysfs_streq(buf, "enabled"))
- set_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask);
- else
- clear_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask);
+ assign_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask,
+ sysfs_streq(buf, "enabled"));
return count;
}
@@ -699,9 +697,6 @@ static umode_t ad525x_is_visible(struct kobject *kobj, struct attribute *attr,
struct dpot_data *data = dev_get_drvdata(dev);
int rdac;
- if (!data)
- return 0;
-
rdac = ad525x_attr_index(attr, dpot_attrib_wipers);
if (rdac >= 0)
return data->wipers & BIT(rdac) ? attr->mode : 0;
diff --git a/drivers/misc/altera-stapl/altera-jtag.c b/drivers/misc/altera-stapl/altera-jtag.c
index 27e8e0c9e8cf..0238600107b0 100644
--- a/drivers/misc/altera-stapl/altera-jtag.c
+++ b/drivers/misc/altera-stapl/altera-jtag.c
@@ -17,9 +17,9 @@
#include "altera-jtag.h"
#define alt_jtag_io(a, b, c)\
- astate->config->jtag_io(astate->config->dev, a, b, c);
+ astate->config->jtag_io(astate->config->dev, a, b, c)
-#define alt_malloc(a) kzalloc(a, GFP_KERNEL);
+#define alt_malloc(a) kzalloc(a, GFP_KERNEL)
/*
* This structure shows, for each JTAG state, which state is reached after
diff --git a/drivers/misc/cardreader/alcor_pci.c b/drivers/misc/cardreader/alcor_pci.c
index 9e0f4f91763e..e76fd4b5d6cc 100644
--- a/drivers/misc/cardreader/alcor_pci.c
+++ b/drivers/misc/cardreader/alcor_pci.c
@@ -188,7 +188,6 @@ static void alcor_pci_remove(struct pci_dev *pdev)
pci_set_drvdata(pdev, NULL);
}
-#ifdef CONFIG_PM_SLEEP
static int alcor_suspend(struct device *dev)
{
return 0;
@@ -203,9 +202,8 @@ static int alcor_resume(struct device *dev)
return 0;
}
-#endif /* CONFIG_PM_SLEEP */
-static SIMPLE_DEV_PM_OPS(alcor_pci_pm_ops, alcor_suspend, alcor_resume);
+static DEFINE_SIMPLE_DEV_PM_OPS(alcor_pci_pm_ops, alcor_suspend, alcor_resume);
static struct pci_driver alcor_driver = {
.name = DRV_NAME_ALCOR_PCI,
@@ -213,7 +211,7 @@ static struct pci_driver alcor_driver = {
.probe = alcor_pci_probe,
.remove = alcor_pci_remove,
.driver = {
- .pm = &alcor_pci_pm_ops
+ .pm = pm_sleep_ptr(&alcor_pci_pm_ops)
},
};
diff --git a/drivers/misc/cb710/core.c b/drivers/misc/cb710/core.c
index 8e0d11e59c4e..2ab6f6884766 100644
--- a/drivers/misc/cb710/core.c
+++ b/drivers/misc/cb710/core.c
@@ -166,7 +166,7 @@ void cb710_set_irq_handler(struct cb710_slot *slot,
}
EXPORT_SYMBOL_GPL(cb710_set_irq_handler);
-static int __maybe_unused cb710_suspend(struct device *dev_d)
+static int cb710_suspend(struct device *dev_d)
{
struct pci_dev *pdev = to_pci_dev(dev_d);
struct cb710_chip *chip = pci_get_drvdata(pdev);
@@ -175,7 +175,7 @@ static int __maybe_unused cb710_suspend(struct device *dev_d)
return 0;
}
-static int __maybe_unused cb710_resume(struct device *dev_d)
+static int cb710_resume(struct device *dev_d)
{
struct pci_dev *pdev = to_pci_dev(dev_d);
struct cb710_chip *chip = pci_get_drvdata(pdev);
@@ -296,14 +296,14 @@ static const struct pci_device_id cb710_pci_tbl[] = {
{ }
};
-static SIMPLE_DEV_PM_OPS(cb710_pm_ops, cb710_suspend, cb710_resume);
+static DEFINE_SIMPLE_DEV_PM_OPS(cb710_pm_ops, cb710_suspend, cb710_resume);
static struct pci_driver cb710_driver = {
.name = KBUILD_MODNAME,
.id_table = cb710_pci_tbl,
.probe = cb710_probe,
.remove = cb710_remove_one,
- .driver.pm = &cb710_pm_ops,
+ .driver.pm = pm_sleep_ptr(&cb710_pm_ops),
};
static int __init cb710_init_module(void)
diff --git a/drivers/misc/cs5535-mfgpt.c b/drivers/misc/cs5535-mfgpt.c
index cdd0e7bda68d..9abfc44f70f4 100644
--- a/drivers/misc/cs5535-mfgpt.c
+++ b/drivers/misc/cs5535-mfgpt.c
@@ -45,7 +45,8 @@ static struct cs5535_mfgpt_chip {
int cs5535_mfgpt_toggle_event(struct cs5535_mfgpt_timer *timer, int cmp,
int event, int enable)
{
- uint32_t msr, mask, value, dummy;
+ uint32_t msr, mask;
+ struct msr val;
int shift = (cmp == MFGPT_CMP1) ? 0 : 8;
if (!timer) {
@@ -82,14 +83,14 @@ int cs5535_mfgpt_toggle_event(struct cs5535_mfgpt_timer *timer, int cmp,
return -EIO;
}
- rdmsr(msr, value, dummy);
+ rdmsrq(msr, val.q);
if (enable)
- value |= mask;
+ val.l |= mask;
else
- value &= ~mask;
+ val.l &= ~mask;
- wrmsr(msr, value, dummy);
+ wrmsrq(msr, val.q);
return 0;
}
EXPORT_SYMBOL_GPL(cs5535_mfgpt_toggle_event);
@@ -97,7 +98,7 @@ EXPORT_SYMBOL_GPL(cs5535_mfgpt_toggle_event);
int cs5535_mfgpt_set_irq(struct cs5535_mfgpt_timer *timer, int cmp, int *irq,
int enable)
{
- uint32_t zsel, lpc, dummy;
+ struct msr zsel, lpc;
int shift;
if (!timer) {
@@ -113,30 +114,31 @@ int cs5535_mfgpt_set_irq(struct cs5535_mfgpt_timer *timer, int cmp, int *irq,
* IRQ of the 1st. This can only happen if forcing an IRQ, calling this
* with *irq==0 is safe. Currently there _are_ no 2 drivers.
*/
- rdmsr(MSR_PIC_ZSEL_LOW, zsel, dummy);
+ rdmsrq(MSR_PIC_ZSEL_LOW, zsel.q);
shift = ((cmp == MFGPT_CMP1 ? 0 : 4) + timer->nr % 4) * 4;
- if (((zsel >> shift) & 0xF) == 2)
+ if (((zsel.l >> shift) & 0xF) == 2)
return -EIO;
/* Choose IRQ: if none supplied, keep IRQ already set or use default */
if (!*irq)
- *irq = (zsel >> shift) & 0xF;
+ *irq = (zsel.l >> shift) & 0xF;
if (!*irq)
*irq = CONFIG_CS5535_MFGPT_DEFAULT_IRQ;
/* Can't use IRQ if it's 0 (=disabled), 2, or routed to LPC */
if (*irq < 1 || *irq == 2 || *irq > 15)
return -EIO;
- rdmsr(MSR_PIC_IRQM_LPC, lpc, dummy);
- if (lpc & (1 << *irq))
+ rdmsrq(MSR_PIC_IRQM_LPC, lpc.q);
+ if (lpc.l & (1 << *irq))
return -EIO;
/* All chosen and checked - go for it */
if (cs5535_mfgpt_toggle_event(timer, cmp, MFGPT_EVENT_IRQ, enable))
return -EIO;
if (enable) {
- zsel = (zsel & ~(0xF << shift)) | (*irq << shift);
- wrmsr(MSR_PIC_ZSEL_LOW, zsel, dummy);
+ zsel.l = (zsel.l & ~(0xF << shift)) | (*irq << shift);
+ zsel.h = lpc.h;
+ wrmsrq(MSR_PIC_ZSEL_LOW, zsel.q);
}
return 0;
@@ -249,11 +251,8 @@ EXPORT_SYMBOL_GPL(cs5535_mfgpt_write);
*/
static void reset_all_timers(void)
{
- uint32_t val, dummy;
-
/* The following undocumented bit resets the MFGPT timers */
- val = 0xFF; dummy = 0;
- wrmsr(MSR_MFGPT_SETUP, val, dummy);
+ wrmsrq(MSR_MFGPT_SETUP, 0xff);
}
/*
diff --git a/drivers/misc/enclosure.c b/drivers/misc/enclosure.c
index de457378c501..bdbff5178ec7 100644
--- a/drivers/misc/enclosure.c
+++ b/drivers/misc/enclosure.c
@@ -148,8 +148,7 @@ enclosure_register(struct device *dev, const char *name, int components,
return edev;
err:
- put_device(edev->edev.parent);
- kfree(edev);
+ put_device(&edev->edev);
return ERR_PTR(err);
}
EXPORT_SYMBOL_GPL(enclosure_register);
diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c
index d4fac2caca86..af18ff1992ee 100644
--- a/drivers/misc/fastrpc.c
+++ b/drivers/misc/fastrpc.c
@@ -70,8 +70,6 @@
#define ADSP_MMAP_HEAP_ADDR 4
/* MAP static DMA buffer on DSP User PD */
#define ADSP_MMAP_DMA_BUFFER 6
-/* Add memory to static PD pool protection thru hypervisor */
-#define ADSP_MMAP_REMOTE_HEAP_ADDR 8
/* Add memory to userPD pool, for user heap */
#define ADSP_MMAP_ADD_PAGES 0x1000
/* Add memory to userPD pool, for LLC heap */
@@ -314,10 +312,14 @@ struct fastrpc_channel_ctx {
struct kref refcount;
/* Flag if dsp attributes are cached */
bool valid_attributes;
+ /* Flag if audio PD init mem was allocated */
+ bool audio_init_mem;
+ /* Audio PD reserved remote heap region */
+ phys_addr_t remote_heap_addr;
+ u64 remote_heap_size;
u32 dsp_attributes[FASTRPC_MAX_DSP_ATTRIBUTES];
struct fastrpc_device *secure_fdevice;
struct fastrpc_device *fdevice;
- struct fastrpc_buf *remote_heap;
struct list_head invoke_interrupted_mmaps;
bool secure;
bool unsigned_support;
@@ -927,7 +929,7 @@ static int fastrpc_map_attach(struct fastrpc_user *fl, int fd,
dev_dbg(sess->dev, "Bad size passed len 0x%llx map size 0x%llx\n",
len, map->size);
err = -EINVAL;
- goto map_err;
+ goto get_err;
}
map->va = sg_virt(map->table->sgl);
map->len = len;
@@ -950,7 +952,7 @@ static int fastrpc_map_attach(struct fastrpc_user *fl, int fd,
dev_err(sess->dev,
"Failed to assign memory with dma_addr %pad size 0x%llx err %d\n",
&map->dma_addr, map->len, err);
- goto map_err;
+ goto get_err;
}
}
spin_lock(&fl->lock);
@@ -1454,15 +1456,24 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl,
struct fastrpc_init_create_static init;
struct fastrpc_invoke_args *args;
struct fastrpc_phy_page pages[1];
+ struct fastrpc_channel_ctx *cctx = fl->cctx;
char *name;
int err;
- bool scm_done = false;
struct {
int client_id;
u32 namelen;
u32 pageslen;
} inbuf;
u32 sc;
+ unsigned long flags;
+ bool sent_heap = false;
+
+ if (!cctx->remote_heap_addr || !cctx->remote_heap_size) {
+ err = -ENOMEM;
+ dev_err(fl->sctx->dev,
+ "remote heap memory region is not added\n");
+ return err;
+ }
args = kzalloc_objs(*args, FASTRPC_CREATE_STATIC_PROCESS_NARGS);
if (!args)
@@ -1486,31 +1497,6 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl,
inbuf.client_id = fl->client_id;
inbuf.namelen = init.namelen;
inbuf.pageslen = 0;
- if (!fl->cctx->remote_heap) {
- err = fastrpc_remote_heap_alloc(fl, fl->sctx->dev, init.memlen,
- &fl->cctx->remote_heap);
- if (err)
- goto err_name;
-
- /* Map if we have any heap VMIDs associated with this ADSP Static Process. */
- if (fl->cctx->vmcount) {
- u64 src_perms = BIT(QCOM_SCM_VMID_HLOS);
-
- err = qcom_scm_assign_mem(fl->cctx->remote_heap->dma_addr,
- (u64)fl->cctx->remote_heap->size,
- &src_perms,
- fl->cctx->vmperms, fl->cctx->vmcount);
- if (err) {
- dev_err(fl->sctx->dev,
- "Failed to assign memory with dma_addr %pad size 0x%llx err %d\n",
- &fl->cctx->remote_heap->dma_addr,
- fl->cctx->remote_heap->size, err);
- goto err_map;
- }
- scm_done = true;
- inbuf.pageslen = 1;
- }
- }
fl->pd = USER_PD;
@@ -1522,8 +1508,25 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl,
args[1].length = inbuf.namelen;
args[1].fd = -1;
- pages[0].addr = fl->cctx->remote_heap->dma_addr;
- pages[0].size = fl->cctx->remote_heap->size;
+ /*
+ * Audio PD is a static PD and retains the remote heap
+ * information across daemon restarts. Therefore only
+ * the first attach should provide heap information to
+ * DSP. Subsequent attaches reuse the previously
+ * initialized memory pool.
+ */
+ spin_lock_irqsave(&cctx->lock, flags);
+ if (!cctx->audio_init_mem) {
+ pages[0].addr = cctx->remote_heap_addr;
+ pages[0].size = cctx->remote_heap_size;
+ cctx->audio_init_mem = true;
+ inbuf.pageslen = 1;
+ sent_heap = true;
+ } else {
+ pages[0].addr = 0;
+ pages[0].size = 0;
+ }
+ spin_unlock_irqrestore(&cctx->lock, flags);
args[2].ptr = (u64)(uintptr_t) pages;
args[2].length = sizeof(*pages);
@@ -1541,27 +1544,11 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl,
return 0;
err_invoke:
- if (fl->cctx->vmcount && scm_done) {
- u64 src_perms = 0;
- struct qcom_scm_vmperm dst_perms;
- u32 i;
-
- for (i = 0; i < fl->cctx->vmcount; i++)
- src_perms |= BIT(fl->cctx->vmperms[i].vmid);
-
- dst_perms.vmid = QCOM_SCM_VMID_HLOS;
- dst_perms.perm = QCOM_SCM_PERM_RWX;
- err = qcom_scm_assign_mem(fl->cctx->remote_heap->dma_addr,
- (u64)fl->cctx->remote_heap->size,
- &src_perms, &dst_perms, 1);
- if (err)
- dev_err(fl->sctx->dev, "Failed to assign memory dma_addr %pad size 0x%llx err %d\n",
- &fl->cctx->remote_heap->dma_addr, fl->cctx->remote_heap->size, err);
+ if (sent_heap) {
+ spin_lock_irqsave(&cctx->lock, flags);
+ cctx->audio_init_mem = false;
+ spin_unlock_irqrestore(&cctx->lock, flags);
}
-err_map:
- fastrpc_buf_free(fl->cctx->remote_heap);
- fl->cctx->remote_heap = NULL;
-err_name:
kfree(name);
err:
kfree(args);
@@ -1783,7 +1770,7 @@ static int fastrpc_device_open(struct inode *inode, struct file *filp)
fl->sctx = fastrpc_session_alloc(fl);
if (!fl->sctx) {
- dev_err(&cctx->rpdev->dev, "No session available\n");
+ dev_err_ratelimited(&cctx->rpdev->dev, "No session available\n");
mutex_destroy(&fl->mutex);
kfree(fl);
fastrpc_channel_ctx_put(cctx);
@@ -1937,7 +1924,7 @@ static int fastrpc_get_info_from_kernel(struct fastrpc_ioctl_capability *cap,
kfree(dsp_attributes);
return -EOPNOTSUPP;
} else if (err) {
- dev_err(&cctx->rpdev->dev, "Error: dsp information is incorrect err: %d\n", err);
+ dev_dbg(&cctx->rpdev->dev, "Error: dsp information is incorrect err: %d\n", err);
kfree(dsp_attributes);
return err;
}
@@ -2086,7 +2073,7 @@ static int fastrpc_req_mmap(struct fastrpc_user *fl, char __user *argp)
if (copy_from_user(&req, argp, sizeof(req)))
return -EFAULT;
- if (req.flags != ADSP_MMAP_ADD_PAGES && req.flags != ADSP_MMAP_REMOTE_HEAP_ADDR) {
+ if (req.flags != ADSP_MMAP_ADD_PAGES) {
dev_err(dev, "flag not supported 0x%x\n", req.flags);
return -EINVAL;
@@ -2097,10 +2084,7 @@ static int fastrpc_req_mmap(struct fastrpc_user *fl, char __user *argp)
return -EINVAL;
}
- if (req.flags == ADSP_MMAP_REMOTE_HEAP_ADDR)
- err = fastrpc_remote_heap_alloc(fl, dev, req.size, &buf);
- else
- err = fastrpc_buf_alloc(fl, dev, req.size, &buf);
+ err = fastrpc_buf_alloc(fl, dev, req.size, &buf);
if (err) {
dev_err(dev, "failed to allocate buffer\n");
@@ -2139,20 +2123,6 @@ static int fastrpc_req_mmap(struct fastrpc_user *fl, char __user *argp)
/* let the client know the address to use */
req.vaddrout = rsp_msg.vaddr;
- /* Add memory to static PD pool, protection thru hypervisor */
- if (req.flags == ADSP_MMAP_REMOTE_HEAP_ADDR && fl->cctx->vmcount) {
- u64 src_perms = BIT(QCOM_SCM_VMID_HLOS);
-
- err = qcom_scm_assign_mem(buf->dma_addr, (u64)buf->size,
- &src_perms, fl->cctx->vmperms, fl->cctx->vmcount);
- if (err) {
- dev_err(fl->sctx->dev,
- "Failed to assign memory dma_addr %pad size 0x%llx err %d",
- &buf->dma_addr, buf->size, err);
- goto err_assign;
- }
- }
-
spin_lock(&fl->lock);
list_add_tail(&buf->node, &fl->mmaps);
spin_unlock(&fl->lock);
@@ -2533,6 +2503,45 @@ static const struct of_device_id fastrpc_poll_supported_machines[] __maybe_unuse
{},
};
+static int fastrpc_init_reserved_mem(struct fastrpc_channel_ctx *cctx,
+ struct device *rdev, u32 domain_id)
+{
+ struct resource res;
+ u64 src_perms;
+ int err;
+
+ /* Only SDSP and ADSP domains use a reserved remote heap region */
+ if (domain_id != SDSP_DOMAIN_ID && domain_id != ADSP_DOMAIN_ID)
+ return 0;
+
+ err = of_reserved_mem_region_to_resource(rdev->of_node, 0, &res);
+ if (err) {
+ /*
+ * The reserved-memory region is optional at probe time. For
+ * the Audio PD (ADSP) domain its absence is validated later in
+ * fastrpc_init_create_static_process(), so keep probing here.
+ */
+ return 0;
+ }
+
+ /*
+ * Audio PD (ADSP) is a static PD: cache the region so its lifetime
+ * is tied to the rpmsg channel instead of being controlled by
+ * userspace.
+ */
+ if (domain_id == ADSP_DOMAIN_ID) {
+ cctx->remote_heap_addr = res.start;
+ cctx->remote_heap_size = resource_size(&res);
+ }
+
+ if (!cctx->vmcount)
+ return 0;
+
+ src_perms = BIT(QCOM_SCM_VMID_HLOS);
+ return qcom_scm_assign_mem(res.start, resource_size(&res), &src_perms,
+ cctx->vmperms, cctx->vmcount);
+}
+
static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev)
{
struct device *rdev = &rpdev->dev;
@@ -2558,8 +2567,8 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev)
return -EINVAL;
}
- if (of_reserved_mem_device_init_by_idx(rdev, rdev->of_node, 0))
- dev_info(rdev, "no reserved DMA memory for FASTRPC\n");
+ if (devm_of_reserved_mem_device_init(rdev))
+ dev_dbg(rdev, "no reserved DMA memory for FASTRPC\n");
vmcount = of_property_read_variable_u32_array(rdev->of_node,
"qcom,vmids", &vmids[0], 0, FASTRPC_MAX_VMIDS);
@@ -2580,21 +2589,9 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev)
}
}
- if (domain_id == SDSP_DOMAIN_ID) {
- struct resource res;
- u64 src_perms;
-
- err = of_reserved_mem_region_to_resource(rdev->of_node, 0, &res);
- if (!err) {
- src_perms = BIT(QCOM_SCM_VMID_HLOS);
-
- err = qcom_scm_assign_mem(res.start, resource_size(&res), &src_perms,
- data->vmperms, data->vmcount);
- if (err)
- goto err_free_data;
- }
-
- }
+ err = fastrpc_init_reserved_mem(data, rdev, domain_id);
+ if (err)
+ goto err_free_data;
secure_dsp = !(of_property_read_bool(rdev->of_node, "qcom,non-secure-domain"));
data->secure = secure_dsp;
@@ -2694,8 +2691,25 @@ static void fastrpc_rpmsg_remove(struct rpmsg_device *rpdev)
list_for_each_entry_safe(buf, b, &cctx->invoke_interrupted_mmaps, node)
list_del(&buf->node);
- if (cctx->remote_heap)
- fastrpc_buf_free(cctx->remote_heap);
+ if (cctx->remote_heap_size && cctx->vmcount) {
+ u64 src_perms = 0;
+ int err, i;
+ struct qcom_scm_vmperm dst_perms;
+
+ for (i = 0; i < cctx->vmcount; i++)
+ src_perms |= BIT(cctx->vmperms[i].vmid);
+
+ dst_perms.vmid = QCOM_SCM_VMID_HLOS;
+ dst_perms.perm = QCOM_SCM_PERM_RWX;
+
+ err = qcom_scm_assign_mem(cctx->remote_heap_addr,
+ cctx->remote_heap_size, &src_perms,
+ &dst_perms, 1);
+ if (err)
+ dev_err(&rpdev->dev,
+ "Failed to assign memory back to HLOS: addr %pa size %#llx err %d\n",
+ &cctx->remote_heap_addr, cctx->remote_heap_size, err);
+ }
of_platform_depopulate(&rpdev->dev);
diff --git a/drivers/misc/gehc-achc.c b/drivers/misc/gehc-achc.c
index 9792189c0923..2c1222edb5dc 100644
--- a/drivers/misc/gehc-achc.c
+++ b/drivers/misc/gehc-achc.c
@@ -369,7 +369,7 @@ static int ezport_firmware_flash_data(struct spi_device *spi,
dev_dbg(&spi->dev, "EzPort verify flashed data...\n");
ret = ezport_firmware_compare_data(spi, data, size);
- /* allow missing FW verfication in secure mode */
+ /* allow missing FW verification in secure mode */
if (ret == -EACCES)
ret = 0;
diff --git a/drivers/misc/genwqe/card_base.c b/drivers/misc/genwqe/card_base.c
index e602eb3c51fa..3eb843af31a5 100644
--- a/drivers/misc/genwqe/card_base.c
+++ b/drivers/misc/genwqe/card_base.c
@@ -286,7 +286,7 @@ int genwqe_flash_readback_fails(struct genwqe_dev *cd)
* @cd: GenWQE device information
*
* Note: From a design perspective it turned out to be a bad idea to
- * use codes here to specifiy the frequency/speed values. An old
+ * use codes here to specify the frequency/speed values. An old
* driver cannot understand new codes and is therefore always a
* problem. Better is to measure out the value or put the
* speed/frequency directly into a register which is always a valid
@@ -904,7 +904,7 @@ static int genwqe_reload_bistream(struct genwqe_dev *cd)
*
* Condition for the health-thread to trigger:
* a) when a kthread_stop() request comes in or
- * b) a critical GFIR occured
+ * b) a critical GFIR occurred
*
* Informational GFIRs are checked and potentially printed in
* GENWQE_HEALTH_CHECK_INTERVAL seconds.
@@ -1210,10 +1210,10 @@ static int genwqe_probe(struct pci_dev *pci_dev,
}
/**
- * genwqe_remove() - Called when device is removed (hot-plugable)
+ * genwqe_remove() - Called when device is removed (hot-pluggable)
* @pci_dev: PCI device information struct
*
- * Or when driver is unloaded respecitively when unbind is done.
+ * Or when driver is unloaded respectively when unbind is done.
*/
static void genwqe_remove(struct pci_dev *pci_dev)
{
diff --git a/drivers/misc/genwqe/card_ddcb.c b/drivers/misc/genwqe/card_ddcb.c
index 969178573940..ebe1029c9fb5 100644
--- a/drivers/misc/genwqe/card_ddcb.c
+++ b/drivers/misc/genwqe/card_ddcb.c
@@ -460,7 +460,7 @@ pick_next_one:
/**
* __genwqe_wait_ddcb(): Waits until DDCB is completed
* @cd: pointer to genwqe device descriptor
- * @req: pointer to requsted DDCB parameters
+ * @req: pointer to requested DDCB parameters
*
* The Service Layer will update the RETC in DDCB when processing is
* pending or done.
@@ -543,7 +543,7 @@ int __genwqe_wait_ddcb(struct genwqe_dev *cd, struct ddcb_requ *req)
return -EINVAL;
}
- /* Severe error occured. Driver is forced to stop operation */
+ /* Severe error occurred. Driver is forced to stop operation */
if (cd->card_state != GENWQE_CARD_USED) {
dev_err(&pci_dev->dev,
"[%s] err: DDCB#%d forced to stop (rc=%d)\n",
@@ -1103,6 +1103,9 @@ static void free_ddcb_queue(struct genwqe_dev *cd, struct ddcb_queue *queue)
queue_size = roundup(queue->ddcb_max * sizeof(struct ddcb), PAGE_SIZE);
+ kfree(queue->ddcb_waitqs);
+ queue->ddcb_waitqs = NULL;
+
kfree(queue->ddcb_req);
queue->ddcb_req = NULL;
diff --git a/drivers/misc/genwqe/card_utils.c b/drivers/misc/genwqe/card_utils.c
index e3b5fb337182..7c57cb881d7f 100644
--- a/drivers/misc/genwqe/card_utils.c
+++ b/drivers/misc/genwqe/card_utils.c
@@ -1003,7 +1003,7 @@ u64 genwqe_read_vreg(struct genwqe_dev *cd, u32 reg, int func)
* @cd: genwqe device descriptor
*
* Note: From a design perspective it turned out to be a bad idea to
- * use codes here to specifiy the frequency/speed values. An old
+ * use codes here to specify the frequency/speed values. An old
* driver cannot understand new codes and is therefore always a
* problem. Better is to measure out the value or put the
* speed/frequency directly into a register which is always a valid
diff --git a/drivers/misc/ibmvmc.c b/drivers/misc/ibmvmc.c
index da82b1edb7c9..6afef0788b90 100644
--- a/drivers/misc/ibmvmc.c
+++ b/drivers/misc/ibmvmc.c
@@ -154,7 +154,7 @@ static void ibmvmc_release_crq_queue(struct crq_server_adapter *adapter)
dma_unmap_single(adapter->dev,
queue->msg_token,
queue->size * sizeof(*queue->msgs), DMA_BIDIRECTIONAL);
- free_page((unsigned long)queue->msgs);
+ kfree(queue->msgs);
}
/**
@@ -2130,7 +2130,7 @@ static int ibmvmc_init_crq_queue(struct crq_server_adapter *adapter)
int rc = 0;
int retrc = 0;
- queue->msgs = (struct ibmvmc_crq_msg *)get_zeroed_page(GFP_KERNEL);
+ queue->msgs = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!queue->msgs)
goto malloc_failed;
@@ -2176,6 +2176,7 @@ static int ibmvmc_init_crq_queue(struct crq_server_adapter *adapter)
rc = vio_enable_interrupts(vdev);
if (rc != 0) {
dev_err(adapter->dev, "Error %d enabling interrupts!!!\n", rc);
+ free_irq(vdev->irq, (void *)adapter);
goto req_irq_failed;
}
@@ -2192,7 +2193,7 @@ reg_crq_failed:
queue->msg_token,
queue->size * sizeof(*queue->msgs), DMA_BIDIRECTIONAL);
map_failed:
- free_page((unsigned long)queue->msgs);
+ kfree(queue->msgs);
malloc_failed:
return -ENOMEM;
}
diff --git a/drivers/misc/isl29003.c b/drivers/misc/isl29003.c
index c9b526a212d0..4e162905d0c0 100644
--- a/drivers/misc/isl29003.c
+++ b/drivers/misc/isl29003.c
@@ -416,7 +416,6 @@ static void isl29003_remove(struct i2c_client *client)
kfree(i2c_get_clientdata(client));
}
-#ifdef CONFIG_PM_SLEEP
static int isl29003_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
@@ -441,12 +440,7 @@ static int isl29003_resume(struct device *dev)
data->power_state_before_suspend);
}
-static SIMPLE_DEV_PM_OPS(isl29003_pm_ops, isl29003_suspend, isl29003_resume);
-#define ISL29003_PM_OPS (&isl29003_pm_ops)
-
-#else
-#define ISL29003_PM_OPS NULL
-#endif /* CONFIG_PM_SLEEP */
+static DEFINE_SIMPLE_DEV_PM_OPS(isl29003_pm_ops, isl29003_suspend, isl29003_resume);
static const struct i2c_device_id isl29003_id[] = {
{ .name = "isl29003" },
@@ -457,7 +451,7 @@ MODULE_DEVICE_TABLE(i2c, isl29003_id);
static struct i2c_driver isl29003_driver = {
.driver = {
.name = ISL29003_DRV_NAME,
- .pm = ISL29003_PM_OPS,
+ .pm = pm_sleep_ptr(&isl29003_pm_ops),
},
.probe = isl29003_probe,
.remove = isl29003_remove,
diff --git a/drivers/misc/issei/Kconfig b/drivers/misc/issei/Kconfig
index 12a3fd809969..dfcfa2d30b9e 100644
--- a/drivers/misc/issei/Kconfig
+++ b/drivers/misc/issei/Kconfig
@@ -2,6 +2,7 @@
# Copyright (C) 2023-2026 Intel Corporation
config INTEL_SSEI
tristate "Intel Silicon Security Engine Interface"
+ depends on X86
help
The ISSEI (Intel Silicon Security Engine Interface)
subsystem provides a communication channel between the host and the
diff --git a/drivers/misc/lis3lv02d/lis3lv02d_spi.c b/drivers/misc/lis3lv02d/lis3lv02d_spi.c
index 5bf95176a00e..7ef073414673 100644
--- a/drivers/misc/lis3lv02d/lis3lv02d_spi.c
+++ b/drivers/misc/lis3lv02d/lis3lv02d_spi.c
@@ -105,7 +105,6 @@ static void lis302dl_spi_remove(struct spi_device *spi)
lis3lv02d_remove_fs(&lis3_dev);
}
-#ifdef CONFIG_PM_SLEEP
static int lis3lv02d_spi_suspend(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
@@ -127,15 +126,14 @@ static int lis3lv02d_spi_resume(struct device *dev)
return 0;
}
-#endif
-static SIMPLE_DEV_PM_OPS(lis3lv02d_spi_pm, lis3lv02d_spi_suspend,
+static DEFINE_SIMPLE_DEV_PM_OPS(lis3lv02d_spi_pm, lis3lv02d_spi_suspend,
lis3lv02d_spi_resume);
static struct spi_driver lis302dl_spi_driver = {
.driver = {
.name = DRV_NAME,
- .pm = &lis3lv02d_spi_pm,
+ .pm = pm_sleep_ptr(&lis3lv02d_spi_pm),
.of_match_table = of_match_ptr(lis302dl_spi_dt_ids),
},
.probe = lis302dl_spi_probe,
diff --git a/drivers/misc/mei/gsc-me.c b/drivers/misc/mei/gsc-me.c
index 376840b2c354..b38ddb04f2e8 100644
--- a/drivers/misc/mei/gsc-me.c
+++ b/drivers/misc/mei/gsc-me.c
@@ -242,7 +242,7 @@ static int __maybe_unused mei_gsc_pm_runtime_resume(struct device *device)
if (mei_me_hw_use_polling(hw)) {
hw->is_active = true;
- wake_up(&hw->wait_active);
+ wake_up_interruptible(&hw->wait_active);
}
mutex_unlock(&dev->device_lock);
diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c
index e7fbc02fb70f..1c3ab9eb8b1b 100644
--- a/drivers/misc/mei/hw-me.c
+++ b/drivers/misc/mei/hw-me.c
@@ -5,6 +5,7 @@
*/
#include <linux/bitfield.h>
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>
@@ -1290,28 +1291,15 @@ irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id)
}
EXPORT_SYMBOL_GPL(mei_me_irq_quick_handler);
-/**
- * mei_me_irq_thread_handler - function called after ISR to handle the interrupt
- * processing.
- *
- * @irq: The irq number
- * @dev_id: pointer to the device structure
- *
- * Return: irqreturn_t
- *
- */
-irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
+static irqreturn_t __mei_me_irq_thread_handler(struct mei_device *dev)
{
- struct mei_device *dev = (struct mei_device *) dev_id;
struct list_head cmpl_list;
bool pg_blocked;
s32 slots;
u32 hcsr;
int rets = 0;
- dev_dbg(&dev->dev, "function called after ISR to handle the interrupt processing.\n");
/* initialize our complete list */
- mutex_lock(&dev->device_lock);
hcsr = mei_hcsr_read(dev);
me_intr_clear(dev, hcsr);
@@ -1406,13 +1394,33 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
end:
dev_dbg(&dev->dev, "interrupt thread end ret = %d\n", rets);
mei_me_intr_enable(dev);
- mutex_unlock(&dev->device_lock);
return IRQ_HANDLED;
}
+
+/**
+ * mei_me_irq_thread_handler - function called after ISR to handle the interrupt
+ * processing.
+ *
+ * @irq: The irq number
+ * @dev_id: pointer to the device structure
+ *
+ * Return: irqreturn_t
+ *
+ */
+irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
+{
+ struct mei_device *dev = (struct mei_device *) dev_id;
+
+ dev_dbg(&dev->dev, "function called after ISR to handle the interrupt processing.\n");
+
+ guard(mutex)(&dev->device_lock);
+
+ return __mei_me_irq_thread_handler(dev);
+}
EXPORT_SYMBOL_GPL(mei_me_irq_thread_handler);
-#define MEI_POLLING_TIMEOUT_ACTIVE 100
-#define MEI_POLLING_TIMEOUT_IDLE 500
+#define MEI_POLL_ACTIVE_MS 20
+#define MEI_POLL_IDLE_MS 500
/**
* mei_me_polling_thread - interrupt register polling thread
@@ -1420,48 +1428,59 @@ EXPORT_SYMBOL_GPL(mei_me_irq_thread_handler);
* @_dev: mei device
*
* The thread monitors the interrupt source register and calls
- * mei_me_irq_thread_handler() to handle the firmware
+ * __mei_me_irq_thread_handler() to handle the firmware
* input.
*
- * The function polls in MEI_POLLING_TIMEOUT_ACTIVE timeout
+ * The function polls in MEI_POLL_ACTIVE_MS timeout
* in case there was an event, in idle case the polling
- * time increases yet again by MEI_POLLING_TIMEOUT_ACTIVE
- * up to MEI_POLLING_TIMEOUT_IDLE.
+ * time increases yet again by MEI_POLL_ACTIVE_MS
+ * up to MEI_POLL_IDLE_MS.
*
* Return: always 0
*/
int mei_me_polling_thread(void *_dev)
{
struct mei_device *dev = _dev;
- irqreturn_t irq_ret;
- long polling_timeout = MEI_POLLING_TIMEOUT_ACTIVE;
+ long polling_timeout = MEI_POLL_ACTIVE_MS;
+ bool have_interrupt;
dev_dbg(&dev->dev, "kernel thread is running\n");
while (!kthread_should_stop()) {
struct mei_me_hw *hw = to_me_hw(dev);
u32 hcsr;
- wait_event_timeout(hw->wait_active,
- hw->is_active || kthread_should_stop(),
- msecs_to_jiffies(MEI_POLLING_TIMEOUT_IDLE));
+ if (!hw->is_active)
+ wait_event_interruptible(hw->wait_active,
+ hw->is_active || kthread_should_stop());
+ else
+ wait_event_interruptible_timeout(hw->wait_active,
+ hw->is_active || kthread_should_stop(),
+ msecs_to_jiffies(MEI_POLL_IDLE_MS));
if (kthread_should_stop())
break;
- hcsr = mei_hcsr_read(dev);
- if (me_intr_src(hcsr)) {
- polling_timeout = MEI_POLLING_TIMEOUT_ACTIVE;
- irq_ret = mei_me_irq_thread_handler(1, dev);
- if (irq_ret != IRQ_HANDLED)
- dev_err(&dev->dev, "irq_ret %d\n", irq_ret);
- } else {
- /*
- * Increase timeout by MEI_POLLING_TIMEOUT_ACTIVE
- * up to MEI_POLLING_TIMEOUT_IDLE
- */
- polling_timeout = clamp_val(polling_timeout + MEI_POLLING_TIMEOUT_ACTIVE,
- MEI_POLLING_TIMEOUT_ACTIVE,
- MEI_POLLING_TIMEOUT_IDLE);
+ scoped_guard(mutex, &dev->device_lock) {
+ if (!hw->is_active) {
+ have_interrupt = false;
+ } else {
+ hcsr = mei_hcsr_read(dev);
+ have_interrupt = !!me_intr_src(hcsr);
+ }
+
+ if (have_interrupt) {
+ polling_timeout = MEI_POLL_ACTIVE_MS;
+ dev_dbg(&dev->dev, "polling interrupt processing\n");
+ __mei_me_irq_thread_handler(dev);
+ } else {
+ /*
+ * Increase timeout by MEI_POLL_ACTIVE_MS
+ * up to MEI_POLL_IDLE_MS
+ */
+ polling_timeout = clamp_val(polling_timeout + MEI_POLL_ACTIVE_MS,
+ MEI_POLL_ACTIVE_MS,
+ MEI_POLL_IDLE_MS);
+ }
}
schedule_timeout_interruptible(msecs_to_jiffies(polling_timeout));
diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c
index 766f119f7ed0..82444d67db2a 100644
--- a/drivers/misc/mei/init.c
+++ b/drivers/misc/mei/init.c
@@ -305,10 +305,8 @@ static void mei_reset_work(struct work_struct *work)
schedule_work(&dev->reset_work);
}
-void mei_stop(struct mei_device *dev)
+static void __mei_stop(struct mei_device *dev)
{
- dev_dbg(&dev->dev, "stopping the device.\n");
-
mutex_lock(&dev->device_lock);
mei_set_devstate(dev, MEI_DEV_POWERING_DOWN);
mutex_unlock(&dev->device_lock);
@@ -318,6 +316,35 @@ void mei_stop(struct mei_device *dev)
mutex_unlock(&dev->device_lock);
mei_cancel_work(dev);
+}
+
+/**
+ * mei_stop_fast - stop driver, clean bus and disable driver without resetting HW link
+ *
+ * @dev: the device structure
+ */
+void mei_stop_fast(struct mei_device *dev)
+{
+ dev_dbg(&dev->dev, "stopping the device fast.\n");
+
+ __mei_stop(dev);
+
+ mutex_lock(&dev->device_lock);
+ mei_set_devstate(dev, MEI_DEV_DISABLED);
+ mutex_unlock(&dev->device_lock);
+}
+EXPORT_SYMBOL_GPL(mei_stop_fast);
+
+/**
+ * mei_stop - stop driver, clean bus and disable driver with resetting HW link
+ *
+ * @dev: the device structure
+ */
+void mei_stop(struct mei_device *dev)
+{
+ dev_dbg(&dev->dev, "stopping the device.\n");
+
+ __mei_stop(dev);
mei_clear_interrupts(dev);
mei_synchronize_irq(dev);
diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h
index e651b06704a1..79a7c3e63397 100644
--- a/drivers/misc/mei/mei_dev.h
+++ b/drivers/misc/mei/mei_dev.h
@@ -736,6 +736,7 @@ int mei_reset(struct mei_device *dev);
int mei_start(struct mei_device *dev);
int mei_restart(struct mei_device *dev);
void mei_stop(struct mei_device *dev);
+void mei_stop_fast(struct mei_device *dev);
void mei_cancel_work(struct mei_device *dev);
void mei_set_devstate(struct mei_device *dev, enum mei_dev_state state);
diff --git a/drivers/misc/mei/pci-csc.c b/drivers/misc/mei/pci-csc.c
index 6e1e8008f267..fc09cfb5cdc7 100644
--- a/drivers/misc/mei/pci-csc.c
+++ b/drivers/misc/mei/pci-csc.c
@@ -11,6 +11,7 @@
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
+#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
@@ -26,6 +27,10 @@
#define MEI_CSC_HECI2_OFFSET 0x1000
+static bool use_polling = true;
+module_param(use_polling, bool, 0600);
+MODULE_PARM_DESC(use_polling, "Use polling instead of interrupts");
+
static int mei_csc_read_fws(const struct mei_device *mdev, int where, const char *name, u32 *val)
{
struct mei_me_hw *hw = to_me_hw(mdev);
@@ -87,20 +92,40 @@ static int mei_csc_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
pci_set_drvdata(pdev, mdev);
- err = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_INTX | PCI_IRQ_MSI);
- if (err < 0) {
- dev_err_probe(dev, err, "Failed to allocate IRQ.\n");
- goto err_mei_unreg;
+ if (use_polling) {
+ dev_dbg(dev, "Using polling thread\n");
+ hw->irq = -1;
}
- hw->irq = pci_irq_vector(pdev, 0);
-
- /* request and enable interrupt */
- err = request_threaded_irq(hw->irq,
- mei_me_irq_quick_handler, mei_me_irq_thread_handler,
- IRQF_SHARED | IRQF_ONESHOT, KBUILD_MODNAME, mdev);
- if (err)
- goto err_free_irq_vectors;
+ /* use polling */
+ if (mei_me_hw_use_polling(hw)) {
+ mei_disable_interrupts(mdev);
+ mei_clear_interrupts(mdev);
+ init_waitqueue_head(&hw->wait_active);
+ hw->is_active = true; /* start in active mode for initialization */
+ hw->polling_thread = kthread_run(mei_me_polling_thread, mdev,
+ "kmecscirqd/%s", dev_name(dev));
+ if (IS_ERR(hw->polling_thread)) {
+ err = PTR_ERR(hw->polling_thread);
+ dev_err_probe(dev, err, "unable to create kernel thread.\n");
+ goto err_mei_unreg;
+ }
+ } else {
+ err = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_INTX | PCI_IRQ_MSI);
+ if (err < 0) {
+ dev_err_probe(dev, err, "Failed to allocate IRQ.\n");
+ goto err_mei_unreg;
+ }
+
+ hw->irq = pci_irq_vector(pdev, 0);
+
+ /* request and enable interrupt */
+ err = request_threaded_irq(hw->irq,
+ mei_me_irq_quick_handler, mei_me_irq_thread_handler,
+ IRQF_SHARED | IRQF_ONESHOT, KBUILD_MODNAME, mdev);
+ if (err)
+ goto err_free_irq_vectors;
+ }
/*
* Continue to char device setup in spite of firmware handshake failure.
@@ -126,7 +151,8 @@ static int mei_csc_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
return 0;
err_free_irq_vectors:
- pci_free_irq_vectors(pdev);
+ if (!mei_me_hw_use_polling(hw))
+ pci_free_irq_vectors(pdev);
err_mei_unreg:
mei_deregister(mdev);
return err;
@@ -141,9 +167,14 @@ static void mei_csc_shutdown(struct pci_dev *pdev)
mei_stop(mdev);
+ if (mei_me_hw_use_polling(hw))
+ kthread_stop(hw->polling_thread);
+
mei_disable_interrupts(mdev);
- free_irq(hw->irq, mdev);
- pci_free_irq_vectors(pdev);
+ if (!mei_me_hw_use_polling(hw)) {
+ free_irq(hw->irq, mdev);
+ pci_free_irq_vectors(pdev);
+ }
}
static void mei_csc_remove(struct pci_dev *pdev)
@@ -210,6 +241,9 @@ static int mei_csc_pm_runtime_suspend(struct device *dev)
return -EAGAIN;
hw->pg_state = MEI_PG_ON;
+ if (mei_me_hw_use_polling(hw))
+ hw->is_active = false;
+
return 0;
}
@@ -219,8 +253,13 @@ static int mei_csc_pm_runtime_resume(struct device *dev)
struct mei_me_hw *hw = to_me_hw(mdev);
irqreturn_t irq_ret;
- scoped_guard(mutex, &mdev->device_lock)
+ scoped_guard(mutex, &mdev->device_lock) {
hw->pg_state = MEI_PG_OFF;
+ if (mei_me_hw_use_polling(hw)) {
+ hw->is_active = true;
+ wake_up_interruptible(&hw->wait_active);
+ }
+ }
/* Process all queues that wait for resume */
irq_ret = mei_me_irq_thread_handler(1, mdev);
@@ -230,6 +269,77 @@ static int mei_csc_pm_runtime_resume(struct device *dev)
return 0;
}
+static pci_ers_result_t mei_csc_pci_error_detected(struct pci_dev *pdev, pci_channel_state_t state)
+{
+ struct mei_device *mdev = pci_get_drvdata(pdev);
+ struct mei_me_hw *hw = to_me_hw(mdev);
+
+ dev_info(&pdev->dev, "error recovery: error detected. state %d\n", state);
+
+ scoped_guard(mutex, &mdev->device_lock)
+ if (mei_me_hw_use_polling(hw))
+ hw->is_active = false;
+
+ mei_synchronize_irq(mdev);
+ mei_stop_fast(mdev);
+ pci_disable_device(pdev);
+
+ switch (state) {
+ case pci_channel_io_normal:
+ return PCI_ERS_RESULT_CAN_RECOVER;
+ case pci_channel_io_perm_failure:
+ return PCI_ERS_RESULT_DISCONNECT;
+ case pci_channel_io_frozen:
+ return PCI_ERS_RESULT_NEED_RESET;
+ default:
+ dev_err(&pdev->dev, "Unknown state %d\n", state);
+ return PCI_ERS_RESULT_NEED_RESET;
+ }
+}
+
+static pci_ers_result_t mei_csc_pci_error_slot_reset(struct pci_dev *pdev)
+{
+ int err;
+
+ pci_restore_state(pdev);
+ pci_set_master(pdev);
+
+ err = pci_enable_device(pdev);
+ if (err < 0) {
+ dev_err(&pdev->dev, "Cannot re-enable PCI device after reset. err = %d\n", err);
+ return PCI_ERS_RESULT_DISCONNECT;
+ }
+
+ return PCI_ERS_RESULT_RECOVERED;
+}
+
+static void mei_csc_pci_error_resume(struct pci_dev *pdev)
+{
+ struct mei_device *mdev = pci_get_drvdata(pdev);
+ struct mei_me_hw *hw = to_me_hw(mdev);
+
+ dev_info(&pdev->dev, "error recovery: resume\n");
+
+ scoped_guard(mutex, &mdev->device_lock) {
+ if (mei_me_hw_use_polling(hw)) {
+ hw->is_active = true;
+ wake_up_interruptible(&hw->wait_active);
+ }
+ }
+
+ if (mei_restart(mdev))
+ return;
+
+ /* Start timer if stopped in error */
+ schedule_delayed_work(&mdev->timer_work, HZ);
+}
+
+static const struct pci_error_handlers mei_csc_pci_error_handlers = {
+ .error_detected = mei_csc_pci_error_detected,
+ .slot_reset = mei_csc_pci_error_slot_reset,
+ .resume = mei_csc_pci_error_resume,
+};
+
static const struct dev_pm_ops mei_csc_pm_ops = {
.prepare = pm_sleep_ptr(mei_csc_pci_prepare),
.complete = pm_sleep_ptr(mei_csc_pci_complete),
@@ -250,6 +360,7 @@ static struct pci_driver mei_csc_driver = {
.probe = mei_csc_probe,
.remove = mei_csc_remove,
.shutdown = mei_csc_shutdown,
+ .err_handler = &mei_csc_pci_error_handlers,
.driver = {
.pm = &mei_csc_pm_ops,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
diff --git a/drivers/misc/mei/vsc-tp.c b/drivers/misc/mei/vsc-tp.c
index 432bc59f11f9..5410ec1c9328 100644
--- a/drivers/misc/mei/vsc-tp.c
+++ b/drivers/misc/mei/vsc-tp.c
@@ -293,7 +293,7 @@ int vsc_tp_xfer(struct vsc_tp *tp, u8 cmd, const void *obuf, size_t olen,
pkt->hdr.seq = cpu_to_le32(++tp->seq);
memcpy(pkt->buf, obuf, olen);
- crc = ~crc32(~0, (u8 *)pkt, sizeof(pkt) + olen);
+ crc = ~crc32(~0, (u8 *)pkt, sizeof(pkt->hdr) + olen);
memcpy(pkt->buf + olen, &crc, sizeof(crc));
ret = vsc_tp_wakeup_request(tp);
@@ -496,30 +496,30 @@ static int vsc_tp_probe(struct spi_device *spi)
init_waitqueue_head(&tp->xfer_wait);
tp->spi = spi;
+ mutex_init(&tp->mutex);
+ mutex_init(&tp->event_notify_mutex);
+ INIT_WORK(&tp->event_work, vsc_tp_event_work);
+
irq_set_status_flags(spi->irq, IRQ_DISABLE_UNLAZY);
ret = request_threaded_irq(spi->irq, NULL, vsc_tp_isr,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
dev_name(dev), tp);
if (ret)
- return ret;
-
- mutex_init(&tp->mutex);
- mutex_init(&tp->event_notify_mutex);
- INIT_WORK(&tp->event_work, vsc_tp_event_work);
+ goto err_destroy_lock;
/* only one child acpi device */
ret = acpi_dev_for_each_child(ACPI_COMPANION(dev),
vsc_tp_match_any, &adev);
if (!ret) {
ret = -ENODEV;
- goto err_destroy_lock;
+ goto err_free_irq;
}
pinfo.fwnode = acpi_fwnode_handle(adev);
pdev = platform_device_register_full(&pinfo);
if (IS_ERR(pdev)) {
ret = PTR_ERR(pdev);
- goto err_destroy_lock;
+ goto err_free_irq;
}
tp->pdev = pdev;
@@ -527,9 +527,10 @@ static int vsc_tp_probe(struct spi_device *spi)
return 0;
-err_destroy_lock:
+err_free_irq:
free_irq(spi->irq, tp);
+err_destroy_lock:
cancel_work_sync(&tp->event_work);
mutex_destroy(&tp->event_notify_mutex);
mutex_destroy(&tp->mutex);
diff --git a/drivers/misc/nsm.c b/drivers/misc/nsm.c
index a3a9f9bfc6b7..b6d3fa79a5e8 100644
--- a/drivers/misc/nsm.c
+++ b/drivers/misc/nsm.c
@@ -117,7 +117,7 @@ static int cbor_object_get_array(u8 *cbor_object, size_t cbor_object_size, u8 **
array_len_p = &cbor_object[1];
switch (cbor_short_size) {
- case CBOR_SHORT_SIZE_MAX_VALUE: /* short encoding */
+ case 0 ... CBOR_SHORT_SIZE_MAX_VALUE: /* short encoding */
array_len = cbor_short_size;
break;
case CBOR_LONG_SIZE_U8:
@@ -132,6 +132,8 @@ static int cbor_object_get_array(u8 *cbor_object, size_t cbor_object_size, u8 **
case CBOR_LONG_SIZE_U64:
array_len = be64_to_cpup((__be64 *)array_len_p);
break;
+ default:
+ return -EFAULT;
}
if (cbor_object_size < array_offset)
@@ -439,6 +441,8 @@ static int nsm_device_probe(struct virtio_device *vdev)
mutex_init(&nsm->lock);
+ virtio_device_ready(vdev);
+
/* Register as hwrng provider */
nsm->hwrng = (struct hwrng) {
.read = nsm_rng_read,
@@ -471,6 +475,7 @@ static int nsm_device_probe(struct virtio_device *vdev)
err_misc:
hwrng_unregister(&nsm->hwrng);
err_hwrng:
+ virtio_reset_device(vdev);
vdev->config->del_vqs(vdev);
err_init_vq:
return rc;
diff --git a/drivers/misc/pch_phub.c b/drivers/misc/pch_phub.c
index 15785597da40..5d3c5a298ba4 100644
--- a/drivers/misc/pch_phub.c
+++ b/drivers/misc/pch_phub.c
@@ -705,14 +705,14 @@ static void pch_phub_remove(struct pci_dev *pdev)
kfree(chip);
}
-static int __maybe_unused pch_phub_suspend(struct device *dev_d)
+static int pch_phub_suspend(struct device *dev_d)
{
device_wakeup_disable(dev_d);
return 0;
}
-static int __maybe_unused pch_phub_resume(struct device *dev_d)
+static int pch_phub_resume(struct device *dev_d)
{
device_wakeup_disable(dev_d);
@@ -729,14 +729,14 @@ static const struct pci_device_id pch_phub_pcidev_id[] = {
};
MODULE_DEVICE_TABLE(pci, pch_phub_pcidev_id);
-static SIMPLE_DEV_PM_OPS(pch_phub_pm_ops, pch_phub_suspend, pch_phub_resume);
+static DEFINE_SIMPLE_DEV_PM_OPS(pch_phub_pm_ops, pch_phub_suspend, pch_phub_resume);
static struct pci_driver pch_phub_driver = {
.name = "pch_phub",
.id_table = pch_phub_pcidev_id,
.probe = pch_phub_probe,
.remove = pch_phub_remove,
- .driver.pm = &pch_phub_pm_ops,
+ .driver.pm = pm_sleep_ptr(&pch_phub_pm_ops),
};
module_pci_driver(pch_phub_driver);
diff --git a/drivers/misc/phantom.c b/drivers/misc/phantom.c
index 331cae539290..ed57de14b876 100644
--- a/drivers/misc/phantom.c
+++ b/drivers/misc/phantom.c
@@ -459,7 +459,7 @@ static void phantom_remove(struct pci_dev *pdev)
pci_disable_device(pdev);
}
-static int __maybe_unused phantom_suspend(struct device *dev_d)
+static int phantom_suspend(struct device *dev_d)
{
struct phantom_device *dev = dev_get_drvdata(dev_d);
@@ -471,7 +471,7 @@ static int __maybe_unused phantom_suspend(struct device *dev_d)
return 0;
}
-static int __maybe_unused phantom_resume(struct device *dev_d)
+static int phantom_resume(struct device *dev_d)
{
struct phantom_device *dev = dev_get_drvdata(dev_d);
@@ -488,14 +488,14 @@ static struct pci_device_id phantom_pci_tbl[] = {
};
MODULE_DEVICE_TABLE(pci, phantom_pci_tbl);
-static SIMPLE_DEV_PM_OPS(phantom_pm_ops, phantom_suspend, phantom_resume);
+static DEFINE_SIMPLE_DEV_PM_OPS(phantom_pm_ops, phantom_suspend, phantom_resume);
static struct pci_driver phantom_pci_driver = {
.name = "phantom",
.id_table = phantom_pci_tbl,
.probe = phantom_probe,
.remove = phantom_remove,
- .driver.pm = &phantom_pm_ops,
+ .driver.pm = pm_sleep_ptr(&phantom_pm_ops),
};
static CLASS_ATTR_STRING(version, 0444, PHANTOM_VERSION);
diff --git a/drivers/misc/qcom-coincell.c b/drivers/misc/qcom-coincell.c
index b58bd34fc471..8da3736652c3 100644
--- a/drivers/misc/qcom-coincell.c
+++ b/drivers/misc/qcom-coincell.c
@@ -9,11 +9,11 @@
#include <linux/of.h>
#include <linux/regmap.h>
#include <linux/platform_device.h>
+#include <linux/spmi.h>
struct qcom_coincell {
struct device *dev;
struct regmap *regmap;
- u32 base_addr;
};
#define QCOM_COINCELL_REG_RSET 0x44
@@ -35,7 +35,7 @@ static int qcom_coincell_chgr_config(struct qcom_coincell *chgr, int rset,
/* if disabling, just do that and skip other operations */
if (!enable)
return regmap_write(chgr->regmap,
- chgr->base_addr + QCOM_COINCELL_REG_ENABLE, 0);
+ QCOM_COINCELL_REG_ENABLE, 0);
/* find index for current-limiting resistor */
for (i = 0; i < ARRAY_SIZE(qcom_rset_map); i++)
@@ -58,7 +58,7 @@ static int qcom_coincell_chgr_config(struct qcom_coincell *chgr, int rset,
}
rc = regmap_write(chgr->regmap,
- chgr->base_addr + QCOM_COINCELL_REG_RSET, i);
+ QCOM_COINCELL_REG_RSET, i);
if (rc) {
/*
* This is mainly to flag a bad base_addr (reg) from dts.
@@ -71,19 +71,28 @@ static int qcom_coincell_chgr_config(struct qcom_coincell *chgr, int rset,
}
rc = regmap_write(chgr->regmap,
- chgr->base_addr + QCOM_COINCELL_REG_VSET, j);
+ QCOM_COINCELL_REG_VSET, j);
if (rc)
return rc;
/* set 'enable' register */
return regmap_write(chgr->regmap,
- chgr->base_addr + QCOM_COINCELL_REG_ENABLE,
+ QCOM_COINCELL_REG_ENABLE,
QCOM_COINCELL_ENABLE);
}
static int qcom_coincell_probe(struct platform_device *pdev)
{
- struct device_node *node = pdev->dev.of_node;
+ struct regmap_config qcom_coincell_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .max_register = 0xff,
+ .fast_io = true,
+ };
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node;
+ struct spmi_subdevice *sub_sdev;
+ struct spmi_device *sparent;
struct qcom_coincell chgr;
u32 rset = 0;
u32 vset = 0;
@@ -92,16 +101,25 @@ static int qcom_coincell_probe(struct platform_device *pdev)
chgr.dev = &pdev->dev;
- chgr.regmap = dev_get_regmap(pdev->dev.parent, NULL);
- if (!chgr.regmap) {
- dev_err(chgr.dev, "Unable to get regmap\n");
- return -EINVAL;
- }
-
- rc = of_property_read_u32(node, "reg", &chgr.base_addr);
+ rc = device_property_read_u32(dev, "reg", &qcom_coincell_regmap_config.reg_base);
if (rc)
return rc;
+ sparent = spmi_get_parent_spmi_device(dev);
+ if (!sparent)
+ return -ENODEV;
+
+ sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent);
+ if (IS_ERR(sub_sdev))
+ return PTR_ERR(sub_sdev);
+
+ chgr.regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev,
+ &qcom_coincell_regmap_config);
+ if (IS_ERR(chgr.regmap)) {
+ dev_err(chgr.dev, "Unable to get regmap\n");
+ return PTR_ERR(chgr.regmap);
+ }
+
enable = !of_property_read_bool(node, "qcom,charger-disable");
if (enable) {
@@ -142,3 +160,4 @@ module_platform_driver(qcom_coincell_driver);
MODULE_DESCRIPTION("Qualcomm PMIC coincell charger driver");
MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("SPMI");
diff --git a/drivers/misc/tc9564-pci.c b/drivers/misc/tc9564-pci.c
new file mode 100644
index 000000000000..e4d00dfecfca
--- /dev/null
+++ b/drivers/misc/tc9564-pci.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2026 by RISCstar Solutions Corporation. All rights reserved.
+ */
+
+/*
+ * The Toshiba TC9564 implements a PCIe Gen 3 switch that connects an
+ * upstream x4 port to three downstream PCIe ports. Two of the downstream
+ * ports are external, and the third is internal, implementing a PCIe
+ * endpoint which implements two PCIe functions. Each PCIe function drives
+ * a Synopsys XGMAC Ethernet interface capable of 10 Gbps operation.
+ *
+ * The TC9564 implements other functionality, including an embedded MCU,
+ * a UART, a GPIO controller, a reset controller, a clock controller, and
+ * interrupt handling. These features are separate from (and in some
+ * cases used by) both Ethernet XGMACs. Each Ethernet MAC must be
+ * attached to a working PHY for it to be functional, and for this
+ * reason either of them (or both!) might not be usable/used.
+ *
+ * This PCI driver binds to the Toshiba TC9564 (physical) PCI function
+ * (VID 0x1179, DID 0x0220).
+ */
+
+#include <linux/device.h>
+#include <linux/irqdomain.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/pci.h>
+
+static void tc9564_function_remove(struct pci_dev *pdev)
+{
+ of_platform_depopulate(&pdev->dev);
+ pci_clear_master(pdev);
+ pci_disable_device(pdev);
+}
+
+static int
+tc9564_function_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np;
+ int ret;
+
+ /* Despite being a PCI device, we require devicetree */
+ np = dev_of_node(dev);
+ if (!np)
+ return dev_err_probe(dev, -EINVAL, "no devicetree node\n");
+
+ ret = pci_enable_device(pdev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable device\n");
+
+ pci_set_master(pdev);
+
+ /* Scan for pci-ep-bus (simple-bus) nodes and probe their sub-devices */
+ ret = of_platform_default_populate(np, NULL, dev);
+ if (ret)
+ goto err_depopulate;
+
+ return 0;
+
+err_depopulate:
+ tc9564_function_remove(pdev);
+
+ return dev_err_probe(dev, ret, "failed to populate platform bus\n");
+}
+
+static const struct pci_device_id tc9564_function_id_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_TOSHIBA, 0x0220), },
+ { },
+};
+MODULE_DEVICE_TABLE(pci, tc9564_function_id_table);
+
+static struct pci_driver tc9564_function_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = tc9564_function_id_table,
+ .probe = tc9564_function_probe,
+ .remove = tc9564_function_remove,
+};
+module_pci_driver(tc9564_function_driver);
+
+MODULE_DESCRIPTION("Toshiba TC9564 PCIe Embedded Function Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/tifm_7xx1.c b/drivers/misc/tifm_7xx1.c
index 4677d5a30941..50e7dc502f16 100644
--- a/drivers/misc/tifm_7xx1.c
+++ b/drivers/misc/tifm_7xx1.c
@@ -207,7 +207,7 @@ static void tifm_7xx1_switch_media(struct work_struct *work)
spin_unlock_irqrestore(&fm->lock, flags);
}
-static int __maybe_unused tifm_7xx1_suspend(struct device *dev_d)
+static int tifm_7xx1_suspend(struct device *dev_d)
{
struct pci_dev *dev = to_pci_dev(dev_d);
struct tifm_adapter *fm = pci_get_drvdata(dev);
@@ -224,7 +224,7 @@ static int __maybe_unused tifm_7xx1_suspend(struct device *dev_d)
return 0;
}
-static int __maybe_unused tifm_7xx1_resume(struct device *dev_d)
+static int tifm_7xx1_resume(struct device *dev_d)
{
struct pci_dev *dev = to_pci_dev(dev_d);
struct tifm_adapter *fm = pci_get_drvdata(dev);
@@ -406,14 +406,14 @@ static const struct pci_device_id tifm_7xx1_pci_tbl[] = {
{ }
};
-static SIMPLE_DEV_PM_OPS(tifm_7xx1_pm_ops, tifm_7xx1_suspend, tifm_7xx1_resume);
+static DEFINE_SIMPLE_DEV_PM_OPS(tifm_7xx1_pm_ops, tifm_7xx1_suspend, tifm_7xx1_resume);
static struct pci_driver tifm_7xx1_driver = {
.name = DRIVER_NAME,
.id_table = tifm_7xx1_pci_tbl,
.probe = tifm_7xx1_probe,
.remove = tifm_7xx1_remove,
- .driver.pm = &tifm_7xx1_pm_ops,
+ .driver.pm = pm_sleep_ptr(&tifm_7xx1_pm_ops),
};
module_pci_driver(tifm_7xx1_driver);
diff --git a/drivers/misc/tifm_core.c b/drivers/misc/tifm_core.c
index aac8f0933e43..03d1324bc253 100644
--- a/drivers/misc/tifm_core.c
+++ b/drivers/misc/tifm_core.c
@@ -97,7 +97,6 @@ static void tifm_device_remove(struct device *dev)
sock->card_event = tifm_dummy_event;
sock->data_event = tifm_dummy_event;
drv->remove(sock);
- sock->dev.driver = NULL;
}
put_device(dev);
diff --git a/drivers/misc/tsl2550.c b/drivers/misc/tsl2550.c
index c871ea860bb5..df6931ef267e 100644
--- a/drivers/misc/tsl2550.c
+++ b/drivers/misc/tsl2550.c
@@ -398,8 +398,6 @@ static void tsl2550_remove(struct i2c_client *client)
kfree(i2c_get_clientdata(client));
}
-#ifdef CONFIG_PM_SLEEP
-
static int tsl2550_suspend(struct device *dev)
{
return tsl2550_set_power_state(to_i2c_client(dev), 0);
@@ -410,14 +408,7 @@ static int tsl2550_resume(struct device *dev)
return tsl2550_set_power_state(to_i2c_client(dev), 1);
}
-static SIMPLE_DEV_PM_OPS(tsl2550_pm_ops, tsl2550_suspend, tsl2550_resume);
-#define TSL2550_PM_OPS (&tsl2550_pm_ops)
-
-#else
-
-#define TSL2550_PM_OPS NULL
-
-#endif /* CONFIG_PM_SLEEP */
+static DEFINE_SIMPLE_DEV_PM_OPS(tsl2550_pm_ops, tsl2550_suspend, tsl2550_resume);
static const struct i2c_device_id tsl2550_id[] = {
{ .name = "tsl2550" },
@@ -435,7 +426,7 @@ static struct i2c_driver tsl2550_driver = {
.driver = {
.name = TSL2550_DRV_NAME,
.of_match_table = tsl2550_of_match,
- .pm = TSL2550_PM_OPS,
+ .pm = pm_sleep_ptr(&tsl2550_pm_ops),
},
.probe = tsl2550_probe,
.remove = tsl2550_remove,
diff --git a/drivers/misc/vmw_vmci/vmci_datagram.c b/drivers/misc/vmw_vmci/vmci_datagram.c
index 6193a22c3ab5..73b43b097f92 100644
--- a/drivers/misc/vmw_vmci/vmci_datagram.c
+++ b/drivers/misc/vmw_vmci/vmci_datagram.c
@@ -434,7 +434,7 @@ EXPORT_SYMBOL_GPL(vmci_datagram_create_handle_priv);
* @out_handle: vmci_handle that is populated as a result of this function.
*
* Creates a host context datagram endpoint and returns a handle to
- * it. Same as vmci_datagram_create_handle_priv without the priviledge
+ * it. Same as vmci_datagram_create_handle_priv without the privilege
* flags argument.
*/
int vmci_datagram_create_handle(u32 resource_id,
diff --git a/drivers/misc/vmw_vmci/vmci_queue_pair.c b/drivers/misc/vmw_vmci/vmci_queue_pair.c
index 46bba9bef553..e1a11820be01 100644
--- a/drivers/misc/vmw_vmci/vmci_queue_pair.c
+++ b/drivers/misc/vmw_vmci/vmci_queue_pair.c
@@ -2668,7 +2668,7 @@ static ssize_t qp_dequeue_locked(struct vmci_queue *produce_q,
* @consume_qsize: Desired size of the consumer queue.
* @peer: ContextID of the peer.
* @flags: VMCI flags.
- * @priv_flags: VMCI priviledge flags.
+ * @priv_flags: VMCI privilege flags.
*
* This is the client interface for allocating the memory for a
* vmci_qp structure and then attaching to the underlying
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index c36c2a4c2a0b..d2f5bb236663 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -328,6 +328,17 @@ config NVMEM_MTK_EFUSE
This driver can also be built as a module. If so, the module
will be called efuse-mtk.
+config NVMEM_MT6323_EFUSE
+ tristate "MediaTek MT6323 PMIC EFUSE support"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on MFD_MT6397
+ help
+ This is a driver to access hardware related data like sensor
+ calibration, etc.
+
+ This driver can also be built as a module. If so, the module
+ will be called efuse-mt6323.
+
config NVMEM_MXS_OCOTP
tristate "Freescale MXS On-Chip OTP Memory Support"
depends on ARCH_MXS || COMPILE_TEST
@@ -468,6 +479,7 @@ config NVMEM_SNVS_LPGPR
config NVMEM_SPMI_SDAM
tristate "SPMI SDAM Support"
depends on SPMI
+ select REGMAP_SPMI
help
This driver supports the Shared Direct Access Memory Module on
Qualcomm Technologies, Inc. PMICs. It provides the clients
@@ -541,8 +553,9 @@ config NVMEM_U_BOOT_ENV
config NVMEM_UNIPHIER_EFUSE
tristate "UniPhier SoCs eFuse support"
- depends on ARCH_UNIPHIER || COMPILE_TEST
+ depends on ARCH_UNIPHIER || ARCH_K3 || COMPILE_TEST
depends on HAS_IOMEM
+ default y if ARCH_K3
help
This is a simple driver to dump specified values of UniPhier SoC
from eFuse.
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index 2bbfb9ff1885..5e70bad9f0f9 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -53,6 +53,8 @@ obj-$(CONFIG_NVMEM_MESON_MX_EFUSE) += nvmem_meson_mx_efuse.o
nvmem_meson_mx_efuse-y := meson-mx-efuse.o
obj-$(CONFIG_NVMEM_MICROCHIP_OTPC) += nvmem-microchip-otpc.o
nvmem-microchip-otpc-y := microchip-otpc.o
+obj-$(CONFIG_NVMEM_MT6323_EFUSE) += nvmem_mt6323-efuse.o
+nvmem_mt6323-efuse-y := mt6323-efuse.o
obj-$(CONFIG_NVMEM_MTK_EFUSE) += nvmem_mtk-efuse.o
nvmem_mtk-efuse-y := mtk-efuse.o
obj-$(CONFIG_NVMEM_MXS_OCOTP) += nvmem-mxs-ocotp.o
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 0556d140170a..7d4cce8e2742 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -57,7 +57,12 @@ static BLOCKING_NOTIFIER_HEAD(nvmem_notifier);
static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
void *val, size_t bytes)
{
- struct nvmem_operations *ops = nvmem->ops;
+ struct nvmem_operations *ops;
+
+ guard(srcu)(&nvmem->srcu);
+ ops = srcu_dereference(nvmem->ops, &nvmem->srcu);
+ if (!ops)
+ return -ENODEV;
if (!ops->reg_read)
return -EOPNOTSUPP;
@@ -68,9 +73,14 @@ static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
static int __nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset,
void *val, size_t bytes)
{
- struct nvmem_operations *ops = nvmem->ops;
+ struct nvmem_operations *ops;
int ret, wr_ok;
+ guard(srcu)(&nvmem->srcu);
+ ops = srcu_dereference(nvmem->ops, &nvmem->srcu);
+ if (!ops)
+ return -ENODEV;
+
if (!ops->reg_write)
return -EOPNOTSUPP;
@@ -289,7 +299,7 @@ static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj,
static umode_t nvmem_bin_attr_get_umode(struct nvmem_device *nvmem)
{
- struct nvmem_operations *ops = nvmem->ops;
+ struct nvmem_operations *ops = rcu_dereference_raw(nvmem->ops);
umode_t mode = 0400;
@@ -333,7 +343,7 @@ static umode_t nvmem_attr_is_visible(struct kobject *kobj,
{
struct device *dev = kobj_to_dev(kobj);
struct nvmem_device *nvmem = to_nvmem_device(dev);
- struct nvmem_operations *ops = nvmem->ops;
+ struct nvmem_operations *ops = rcu_dereference_raw(nvmem->ops);
/*
* If the device has no .reg_write operation, do not allow
@@ -485,7 +495,7 @@ static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem)
/* Allocate an array of attributes with a sentinel */
ncells = list_count_nodes(&nvmem->cells);
pattrs = devm_kcalloc(&nvmem->dev, ncells + 1,
- sizeof(struct bin_attribute *), GFP_KERNEL);
+ sizeof(*pattrs), GFP_KERNEL);
if (!pattrs)
return -ENOMEM;
@@ -535,24 +545,6 @@ static void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem)
#endif /* CONFIG_NVMEM_SYSFS */
-static void nvmem_release(struct device *dev)
-{
- struct nvmem_device *nvmem = to_nvmem_device(dev);
-
- ida_free(&nvmem_ida, nvmem->id);
- gpiod_put(nvmem->wp_gpio);
- kfree(nvmem->ops);
- kfree(nvmem);
-}
-
-static const struct device_type nvmem_provider_type = {
- .release = nvmem_release,
-};
-
-static const struct bus_type nvmem_bus_type = {
- .name = "nvmem",
-};
-
static void nvmem_cell_entry_drop(struct nvmem_cell_entry *cell)
{
blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_REMOVE, cell);
@@ -571,6 +563,25 @@ static void nvmem_device_remove_all_cells(const struct nvmem_device *nvmem)
nvmem_cell_entry_drop(cell);
}
+static void nvmem_release(struct device *dev)
+{
+ struct nvmem_device *nvmem = to_nvmem_device(dev);
+
+ gpiod_put(nvmem->wp_gpio);
+ nvmem_device_remove_all_cells(nvmem);
+ ida_free(&nvmem_ida, nvmem->id);
+ cleanup_srcu_struct(&nvmem->srcu);
+ kfree(nvmem);
+}
+
+static const struct device_type nvmem_provider_type = {
+ .release = nvmem_release,
+};
+
+static const struct bus_type nvmem_bus_type = {
+ .name = "nvmem",
+};
+
static void nvmem_cell_entry_add(struct nvmem_cell_entry *cell)
{
scoped_guard(mutex, &nvmem_mutex)
@@ -918,7 +929,21 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
nvmem->dev.type = &nvmem_provider_type;
nvmem->dev.bus = &nvmem_bus_type;
nvmem->dev.parent = config->dev;
- nvmem->ops = ops;
+ INIT_LIST_HEAD(&nvmem->cells);
+
+ /*
+ * Must happen before we assign the release() callback in
+ * device_initialize().
+ */
+ rval = init_srcu_struct(&nvmem->srcu);
+ if (rval) {
+ ida_free(&nvmem_ida, nvmem->id);
+ kfree(ops);
+ kfree(nvmem);
+ return ERR_PTR(rval);
+ }
+
+ rcu_assign_pointer(nvmem->ops, ops);
device_initialize(&nvmem->dev);
@@ -931,8 +956,6 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
goto err_put_device;
}
- kref_init(&nvmem->refcnt);
- INIT_LIST_HEAD(&nvmem->cells);
nvmem->fixup_dt_cell_info = config->fixup_dt_cell_info;
ops->reg_read = config->reg_read;
@@ -993,20 +1016,20 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
if (config->cells) {
rval = nvmem_add_cells(nvmem, config->cells, config->ncells);
if (rval)
- goto err_remove_cells;
+ goto err_remove_compat;
}
if (config->add_legacy_fixed_of_cells) {
rval = nvmem_add_cells_from_legacy_of(nvmem);
if (rval)
- goto err_remove_cells;
+ goto err_remove_compat;
}
dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name);
rval = device_add(&nvmem->dev);
if (rval)
- goto err_remove_cells;
+ goto err_remove_compat;
rval = nvmem_populate_layout(nvmem);
if (rval)
@@ -1032,40 +1055,40 @@ err_destroy_layout:
#endif
err_remove_dev:
device_del(&nvmem->dev);
-err_remove_cells:
- nvmem_device_remove_all_cells(nvmem);
+err_remove_compat:
nvmem_sysfs_remove_compat(nvmem);
err_put_device:
+ ops = rcu_replace_pointer(nvmem->ops, NULL, true);
+ synchronize_srcu(&nvmem->srcu);
put_device(&nvmem->dev);
+ kfree(ops);
return ERR_PTR(rval);
}
EXPORT_SYMBOL_GPL(nvmem_register);
-static void nvmem_device_release(struct kref *kref)
+/**
+ * nvmem_unregister() - Unregister previously registered nvmem device
+ *
+ * @nvmem: Pointer to previously registered nvmem device.
+ */
+void nvmem_unregister(struct nvmem_device *nvmem)
{
- struct nvmem_device *nvmem;
+ struct nvmem_operations *ops;
- nvmem = container_of(kref, struct nvmem_device, refcnt);
+ if (!nvmem)
+ return;
blocking_notifier_call_chain(&nvmem_notifier, NVMEM_REMOVE, nvmem);
- nvmem_sysfs_remove_compat(nvmem);
+ ops = rcu_replace_pointer(nvmem->ops, NULL, true);
+ synchronize_srcu(&nvmem->srcu);
- nvmem_device_remove_all_cells(nvmem);
+ nvmem_sysfs_remove_compat(nvmem);
nvmem_destroy_layout(nvmem);
- device_unregister(&nvmem->dev);
-}
+ kfree(ops);
-/**
- * nvmem_unregister() - Unregister previously registered nvmem device
- *
- * @nvmem: Pointer to previously registered nvmem device.
- */
-void nvmem_unregister(struct nvmem_device *nvmem)
-{
- if (nvmem)
- kref_put(&nvmem->refcnt, nvmem_device_release);
+ device_unregister(&nvmem->dev);
}
EXPORT_SYMBOL_GPL(nvmem_unregister);
@@ -1126,8 +1149,6 @@ static struct nvmem_device *nvmem_device_match(void *data,
return ERR_PTR(-EINVAL);
}
- kref_get(&nvmem->refcnt);
-
return nvmem;
}
@@ -1243,9 +1264,8 @@ EXPORT_SYMBOL_GPL(devm_nvmem_device_put);
*/
void nvmem_device_put(struct nvmem_device *nvmem)
{
- put_device(&nvmem->dev);
module_put(nvmem->owner);
- kref_put(&nvmem->refcnt, nvmem_device_release);
+ put_device(&nvmem->dev);
}
EXPORT_SYMBOL_GPL(nvmem_device_put);
@@ -1598,12 +1618,14 @@ static void nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void
*p = *b++ >> bit_offset;
/* setup rest of the bytes if any */
- for (i = 1; i < cell->bytes; i++) {
+ for (i = 1; i < (cell->bytes - bytes_offset); i++) {
/* Get bits from next byte and shift them towards msb */
*p++ |= *b << (BITS_PER_BYTE - bit_offset);
*p = *b++ >> bit_offset;
}
+ /* point to end of the buffer unused bits will be cleared */
+ p = buf + cell->bytes - 1;
} else if (p != b) {
memmove(p, b, cell->bytes - bytes_offset);
p += cell->bytes - 1;
diff --git a/drivers/nvmem/internals.h b/drivers/nvmem/internals.h
index 4e610deeaa7b..bc7a99f5aefb 100644
--- a/drivers/nvmem/internals.h
+++ b/drivers/nvmem/internals.h
@@ -6,6 +6,7 @@
#include <linux/device.h>
#include <linux/nvmem-consumer.h>
#include <linux/nvmem-provider.h>
+#include <linux/srcu.h>
/* Hold pointers to callbacks owned by the nvmem provider module. */
struct nvmem_operations {
@@ -16,10 +17,10 @@ struct nvmem_operations {
struct nvmem_device {
struct module *owner;
struct device dev;
+ struct srcu_struct srcu;
int stride;
int word_size;
int id;
- struct kref refcnt;
size_t size;
bool read_only;
bool root_only;
@@ -34,7 +35,7 @@ struct nvmem_device {
unsigned int nkeepout;
struct gpio_desc *wp_gpio;
struct nvmem_layout *layout;
- struct nvmem_operations *ops;
+ struct nvmem_operations __rcu *ops;
void *priv;
bool sysfs_cells_populated;
};
diff --git a/drivers/nvmem/layouts.c b/drivers/nvmem/layouts.c
index 07a34be9669c..409909e3244f 100644
--- a/drivers/nvmem/layouts.c
+++ b/drivers/nvmem/layouts.c
@@ -156,7 +156,18 @@ static int nvmem_layout_bus_populate(struct nvmem_device *nvmem,
struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem)
{
- return of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout");
+ struct device_node *np;
+
+ /* Search for nvmem-layout child */
+ np = of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout");
+ if (np)
+ return np;
+
+ /* The nvmem of_node is itself a fixed-layout node */
+ if (of_device_is_compatible(nvmem->dev.of_node, "fixed-layout"))
+ return of_node_get(nvmem->dev.of_node);
+
+ return NULL;
}
EXPORT_SYMBOL_GPL(of_nvmem_layout_get_container);
diff --git a/drivers/nvmem/layouts/onie-tlv.c b/drivers/nvmem/layouts/onie-tlv.c
index 8b0f3c1b8a0e..4da3a704388e 100644
--- a/drivers/nvmem/layouts/onie-tlv.c
+++ b/drivers/nvmem/layouts/onie-tlv.c
@@ -128,6 +128,7 @@ static int onie_tlv_add_cells(struct device *dev, struct nvmem_device *nvmem,
ret = nvmem_add_one_cell(nvmem, &cell);
if (ret) {
+ of_node_put(cell.np);
of_node_put(layout);
return ret;
}
diff --git a/drivers/nvmem/layouts/sl28vpd.c b/drivers/nvmem/layouts/sl28vpd.c
index e93b020b0836..79de1e6947d0 100644
--- a/drivers/nvmem/layouts/sl28vpd.c
+++ b/drivers/nvmem/layouts/sl28vpd.c
@@ -126,6 +126,7 @@ static int sl28vpd_add_cells(struct nvmem_layout *layout)
ret = nvmem_add_one_cell(nvmem, &info);
if (ret) {
+ of_node_put(info.np);
of_node_put(layout_np);
return ret;
}
diff --git a/drivers/nvmem/mt6323-efuse.c b/drivers/nvmem/mt6323-efuse.c
new file mode 100644
index 000000000000..de5e30215c22
--- /dev/null
+++ b/drivers/nvmem/mt6323-efuse.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2026 Roman Vivchar <rva333@protonmail.com>
+ */
+
+#include <linux/err.h>
+#include <linux/mfd/mt6397/core.h>
+#include <linux/module.h>
+#include <linux/nvmem-provider.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include <linux/mfd/mt6323/registers.h>
+
+#define MT6323_EFUSE_DOUT_BASE MT6323_EFUSE_DOUT_0_15
+#define MT6323_EFUSE_SIZE 24
+
+static int mt6323_efuse_read(void *context, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct regmap *map = context;
+ u16 *buf = val;
+ u32 tmp;
+ int ret;
+
+ /*
+ * A manual loop using regmap_read is required because PWRAP is not
+ * a continuous MMIO space, but rather a FSM that doesn't implement the
+ * necessary read callback for the regmap_read_raw and regmap_read_bulk
+ * functions.
+ */
+ for (size_t i = 0; i < bytes; i += sizeof(*buf)) {
+ ret = regmap_read(map, MT6323_EFUSE_DOUT_BASE + offset + i, &tmp);
+ if (ret)
+ return ret;
+
+ *buf++ = tmp;
+ }
+
+ return 0;
+}
+
+static int mt6323_efuse_probe(struct platform_device *pdev)
+{
+ struct mt6397_chip *mt6323 = dev_get_drvdata(pdev->dev.parent);
+ struct device *dev = &pdev->dev;
+ struct nvmem_config config = {
+ .name = "mt6323-efuse",
+ .stride = 2,
+ .word_size = 2,
+ .size = MT6323_EFUSE_SIZE,
+ .reg_read = mt6323_efuse_read,
+ };
+ struct nvmem_device *nvmem;
+
+ if (!mt6323 || !mt6323->regmap)
+ return dev_err_probe(dev, -ENODEV, "failed to get parent\n");
+
+ config.dev = dev;
+ config.priv = mt6323->regmap;
+
+ nvmem = devm_nvmem_register(dev, &config);
+ return PTR_ERR_OR_ZERO(nvmem);
+}
+
+static const struct of_device_id mt6323_efuse_of_match[] = {
+ { .compatible = "mediatek,mt6323-efuse" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mt6323_efuse_of_match);
+
+static struct platform_driver mt6323_efuse_driver = {
+ .probe = mt6323_efuse_probe,
+ .driver = {
+ .name = "mt6323-efuse",
+ .of_match_table = mt6323_efuse_of_match,
+ },
+};
+module_platform_driver(mt6323_efuse_driver);
+
+MODULE_DESCRIPTION("MediaTek MT6323 PMIC EFUSE driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/nvmem/qcom-spmi-sdam.c b/drivers/nvmem/qcom-spmi-sdam.c
index 4f1cca6eab71..2aec95dff05d 100644
--- a/drivers/nvmem/qcom-spmi-sdam.c
+++ b/drivers/nvmem/qcom-spmi-sdam.c
@@ -9,6 +9,7 @@
#include <linux/nvmem-provider.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
+#include <linux/spmi.h>
#define SDAM_MEM_START 0x40
#define REGISTER_MAP_ID 0x40
@@ -20,7 +21,6 @@
struct sdam_chip {
struct regmap *regmap;
struct nvmem_config sdam_config;
- unsigned int base;
unsigned int size;
};
@@ -73,7 +73,7 @@ static int sdam_read(void *priv, unsigned int offset, void *val,
return -EINVAL;
}
- rc = regmap_bulk_read(sdam->regmap, sdam->base + offset, val, bytes);
+ rc = regmap_bulk_read(sdam->regmap, offset, val, bytes);
if (rc < 0)
dev_err(dev, "Failed to read SDAM offset %#x len=%zd, rc=%d\n",
offset, bytes, rc);
@@ -100,7 +100,7 @@ static int sdam_write(void *priv, unsigned int offset, void *val,
return -EINVAL;
}
- rc = regmap_bulk_write(sdam->regmap, sdam->base + offset, val, bytes);
+ rc = regmap_bulk_write(sdam->regmap, offset, val, bytes);
if (rc < 0)
dev_err(dev, "Failed to write SDAM offset %#x len=%zd, rc=%d\n",
offset, bytes, rc);
@@ -110,8 +110,17 @@ static int sdam_write(void *priv, unsigned int offset, void *val,
static int sdam_probe(struct platform_device *pdev)
{
+ struct regmap_config sdam_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .max_register = 0xff,
+ .fast_io = true,
+ };
struct sdam_chip *sdam;
struct nvmem_device *nvmem;
+ struct spmi_device *sparent;
+ struct spmi_subdevice *sub_sdev;
+ struct device *dev = &pdev->dev;
unsigned int val;
int rc;
@@ -119,19 +128,26 @@ static int sdam_probe(struct platform_device *pdev)
if (!sdam)
return -ENOMEM;
- sdam->regmap = dev_get_regmap(pdev->dev.parent, NULL);
- if (!sdam->regmap) {
- dev_err(&pdev->dev, "Failed to get regmap handle\n");
- return -ENXIO;
- }
+ sparent = spmi_get_parent_spmi_device(dev);
+ if (!sparent)
+ return -ENODEV;
- rc = of_property_read_u32(pdev->dev.of_node, "reg", &sdam->base);
+ sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent);
+ if (IS_ERR(sub_sdev))
+ return PTR_ERR(sub_sdev);
+
+ rc = device_property_read_u32(dev, "reg", &sdam_regmap_config.reg_base);
if (rc < 0) {
- dev_err(&pdev->dev, "Failed to get SDAM base, rc=%d\n", rc);
+ dev_err(dev, "Failed to get SDAM base, rc=%d\n", rc);
return -EINVAL;
}
- rc = regmap_read(sdam->regmap, sdam->base + SDAM_SIZE, &val);
+ sdam->regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev, &sdam_regmap_config);
+ if (IS_ERR(sdam->regmap))
+ return dev_err_probe(dev, PTR_ERR(sdam->regmap),
+ "Failed to get regmap handle\n");
+
+ rc = regmap_read(sdam->regmap, SDAM_SIZE, &val);
if (rc < 0) {
dev_err(&pdev->dev, "Failed to read SDAM_SIZE rc=%d\n", rc);
return -EINVAL;
@@ -159,7 +175,7 @@ static int sdam_probe(struct platform_device *pdev)
}
dev_dbg(&pdev->dev,
"SDAM base=%#x size=%u registered successfully\n",
- sdam->base, sdam->size);
+ sdam_regmap_config.reg_base, sdam->size);
return 0;
}
@@ -181,3 +197,4 @@ module_platform_driver(sdam_driver);
MODULE_DESCRIPTION("QCOM SPMI SDAM driver");
MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("SPMI");
diff --git a/drivers/nvmem/rockchip-otp.c b/drivers/nvmem/rockchip-otp.c
index 2c0feb036f3f..f8a8c7cece98 100644
--- a/drivers/nvmem/rockchip-otp.c
+++ b/drivers/nvmem/rockchip-otp.c
@@ -12,6 +12,7 @@
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/nvmem-provider.h>
#include <linux/reset.h>
#include <linux/slab.h>
@@ -80,6 +81,8 @@ struct rockchip_otp {
void __iomem *base;
struct reset_control *rst;
const struct rockchip_data *data;
+ /* Serializes access to the OTP controller state machine */
+ struct mutex mutex;
struct clk_bulk_data clks[];
};
@@ -272,10 +275,12 @@ static int rockchip_otp_read(void *context, unsigned int offset,
if (!otp->data || !otp->data->reg_read)
return -EINVAL;
+ mutex_lock(&otp->mutex);
+
ret = clk_bulk_prepare_enable(otp->data->num_clks, otp->clks);
if (ret < 0) {
dev_err(otp->dev, "failed to prepare/enable clks\n");
- return ret;
+ goto unlock;
}
offset += otp->data->read_offset;
@@ -308,6 +313,9 @@ static int rockchip_otp_read(void *context, unsigned int offset,
err:
clk_bulk_disable_unprepare(otp->data->num_clks, otp->clks);
+unlock:
+ mutex_unlock(&otp->mutex);
+
return ret;
}
@@ -431,6 +439,11 @@ static int rockchip_otp_probe(struct platform_device *pdev)
otp->data = data;
otp->dev = dev;
+
+ ret = devm_mutex_init(dev, &otp->mutex);
+ if (ret)
+ return ret;
+
otp->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(otp->base))
return dev_err_probe(dev, PTR_ERR(otp->base),
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index c51c59115a0c..7f1f47ea2ee4 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -6562,6 +6562,7 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_XILINX, 0x5020, of_pci_make_dev_node);
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_XILINX, 0x5021, of_pci_make_dev_node);
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_REDHAT, 0x0005, of_pci_make_dev_node);
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_EFAR, 0x9660, of_pci_make_dev_node);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_TOSHIBA, 0x0220, of_pci_make_dev_node);
/*
* Devices known to require a longer delay before first config space access
diff --git a/drivers/peci/controller/peci-aspeed.c b/drivers/peci/controller/peci-aspeed.c
index a0c99ecf7f38..1c747ce8840b 100644
--- a/drivers/peci/controller/peci-aspeed.c
+++ b/drivers/peci/controller/peci-aspeed.c
@@ -413,7 +413,7 @@ static struct clk *devm_aspeed_peci_register_clk_div(struct device *dev, struct
struct aspeed_peci *priv)
{
struct clk_aspeed_peci *peci_clk;
- struct clk_init_data init;
+ struct clk_init_data init = {};
const char *parent_name;
char name[32];
int ret;
@@ -529,14 +529,14 @@ static int aspeed_peci_probe(struct platform_device *pdev)
if (priv->irq < 0)
return priv->irq;
+ init_completion(&priv->xfer_complete);
+ spin_lock_init(&priv->lock);
+
ret = devm_request_irq(&pdev->dev, priv->irq, aspeed_peci_irq_handler,
0, "peci-aspeed", priv);
if (ret)
return ret;
- init_completion(&priv->xfer_complete);
- spin_lock_init(&priv->lock);
-
priv->rst = devm_reset_control_get(&pdev->dev, NULL);
if (IS_ERR(priv->rst))
return dev_err_probe(priv->dev, PTR_ERR(priv->rst),
diff --git a/drivers/peci/controller/peci-npcm.c b/drivers/peci/controller/peci-npcm.c
index 931868991241..3cc4e959aff3 100644
--- a/drivers/peci/controller/peci-npcm.c
+++ b/drivers/peci/controller/peci-npcm.c
@@ -253,14 +253,14 @@ static int npcm_peci_probe(struct platform_device *pdev)
if (priv->irq < 0)
return priv->irq;
+ init_completion(&priv->xfer_complete);
+ spin_lock_init(&priv->lock);
+
ret = devm_request_irq(&pdev->dev, priv->irq, npcm_peci_irq_handler,
0, "peci-npcm-irq", priv);
if (ret)
return ret;
- init_completion(&priv->xfer_complete);
- spin_lock_init(&priv->lock);
-
ret = npcm_peci_init_ctrl(priv);
if (ret)
return ret;
diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig
index d910a5d1a1ac..c5e260a4c7f8 100644
--- a/drivers/phy/qualcomm/Kconfig
+++ b/drivers/phy/qualcomm/Kconfig
@@ -138,7 +138,9 @@ config PHY_QCOM_QUSB2
config PHY_QCOM_EUSB2_REPEATER
tristate "Qualcomm PMIC eUSB2 Repeater Driver"
depends on OF && (ARCH_QCOM || COMPILE_TEST)
+ depends on SPMI
select GENERIC_PHY
+ select REGMAP_SPMI
help
Enable support for the USB high-speed eUSB2 repeater on Qualcomm
PMICs. The repeater is paired with a Synopsys or M31 eUSB2 Phy
diff --git a/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c b/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c
index efeec4709a15..89bc1c89fc85 100644
--- a/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c
+++ b/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c
@@ -9,6 +9,7 @@
#include <linux/regmap.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
+#include <linux/spmi.h>
/* eUSB2 status registers */
#define EUSB2_RPTR_STATUS 0x08
@@ -66,7 +67,6 @@ struct eusb2_repeater {
struct phy *phy;
struct regulator_bulk_data *vregs;
const struct eusb2_repeater_cfg *cfg;
- u32 base;
enum phy_mode mode;
};
@@ -143,7 +143,6 @@ static int eusb2_repeater_init(struct phy *phy)
struct eusb2_repeater *rptr = phy_get_drvdata(phy);
struct device_node *np = rptr->dev->of_node;
struct regmap *regmap = rptr->regmap;
- u32 base = rptr->base;
u32 poll_val;
s32 dt_val;
int ret;
@@ -154,37 +153,37 @@ static int eusb2_repeater_init(struct phy *phy)
if (ret)
return ret;
- regmap_write(regmap, base + EUSB2_EN_CTL1, EUSB2_RPTR_EN);
+ regmap_write(regmap, EUSB2_EN_CTL1, EUSB2_RPTR_EN);
/* Write registers from init table */
for (int i = 0; i < rptr->cfg->init_tbl_num; i++)
- regmap_write(regmap, base + rptr->cfg->init_tbl[i].reg,
+ regmap_write(regmap, rptr->cfg->init_tbl[i].reg,
rptr->cfg->init_tbl[i].value);
/* Override registers from devicetree values */
if (!of_property_read_u8(np, "qcom,tune-usb2-preem", &val))
- regmap_write(regmap, base + EUSB2_TUNE_USB2_PREEM, val);
+ regmap_write(regmap, EUSB2_TUNE_USB2_PREEM, val);
if (!of_property_read_u8(np, "qcom,tune-usb2-disc-thres", &val))
- regmap_write(regmap, base + EUSB2_TUNE_HSDISC, val);
+ regmap_write(regmap, EUSB2_TUNE_HSDISC, val);
if (!of_property_read_u8(np, "qcom,tune-usb2-amplitude", &val))
- regmap_write(regmap, base + EUSB2_TUNE_IUSB2, val);
+ regmap_write(regmap, EUSB2_TUNE_IUSB2, val);
if (!of_property_read_u8(np, "qcom,tune-res-fsdif", &val))
- regmap_write(regmap, base + EUSB2_TUNE_RES_FSDIF, val);
+ regmap_write(regmap, EUSB2_TUNE_RES_FSDIF, val);
if (!of_property_read_s32(np, "qcom,squelch-detector-bp", &dt_val)) {
for (i = 0; i < ARRAY_SIZE(squelch_detector); i++) {
if (squelch_detector[i] == dt_val) {
- regmap_write(regmap, base + EUSB2_TUNE_SQUELCH_U, i);
+ regmap_write(regmap, EUSB2_TUNE_SQUELCH_U, i);
break;
}
}
}
/* Wait for status OK */
- ret = regmap_read_poll_timeout(regmap, base + EUSB2_RPTR_STATUS, poll_val,
+ ret = regmap_read_poll_timeout(regmap, EUSB2_RPTR_STATUS, poll_val,
poll_val & RPTR_OK, 10, 5);
if (ret)
dev_err(rptr->dev, "initialization timed-out\n");
@@ -197,7 +196,6 @@ static int eusb2_repeater_set_mode(struct phy *phy,
{
struct eusb2_repeater *rptr = phy_get_drvdata(phy);
struct regmap *regmap = rptr->regmap;
- u32 base = rptr->base;
switch (mode) {
case PHY_MODE_USB_HOST:
@@ -206,8 +204,8 @@ static int eusb2_repeater_set_mode(struct phy *phy,
* per eUSB 1.2 Spec. Below implement software workaround until
* PHY and controller is fixing seen observation.
*/
- regmap_write(regmap, base + EUSB2_FORCE_EN_5, F_CLK_19P2M_EN);
- regmap_write(regmap, base + EUSB2_FORCE_VAL_5, V_CLK_19P2M_EN);
+ regmap_write(regmap, EUSB2_FORCE_EN_5, F_CLK_19P2M_EN);
+ regmap_write(regmap, EUSB2_FORCE_VAL_5, V_CLK_19P2M_EN);
break;
case PHY_MODE_USB_DEVICE:
/*
@@ -216,8 +214,8 @@ static int eusb2_repeater_set_mode(struct phy *phy,
* repeater doesn't clear previous value due to shared
* regulators (say host <-> device mode switch).
*/
- regmap_write(regmap, base + EUSB2_FORCE_EN_5, 0);
- regmap_write(regmap, base + EUSB2_FORCE_VAL_5, 0);
+ regmap_write(regmap, EUSB2_FORCE_EN_5, 0);
+ regmap_write(regmap, EUSB2_FORCE_VAL_5, 0);
break;
default:
return -EINVAL;
@@ -242,11 +240,18 @@ static const struct phy_ops eusb2_repeater_ops = {
static int eusb2_repeater_probe(struct platform_device *pdev)
{
+ struct regmap_config eusb2_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .max_register = 0xff,
+ .fast_io = true,
+ };
+ struct spmi_device *sparent;
struct eusb2_repeater *rptr;
+ struct spmi_subdevice *sub_sdev;
struct device *dev = &pdev->dev;
struct phy_provider *phy_provider;
struct device_node *np = dev->of_node;
- u32 res;
int ret;
rptr = devm_kzalloc(dev, sizeof(*rptr), GFP_KERNEL);
@@ -260,15 +265,21 @@ static int eusb2_repeater_probe(struct platform_device *pdev)
if (!rptr->cfg)
return -EINVAL;
- rptr->regmap = dev_get_regmap(dev->parent, NULL);
- if (!rptr->regmap)
+ sparent = spmi_get_parent_spmi_device(dev);
+ if (!sparent)
return -ENODEV;
- ret = of_property_read_u32(np, "reg", &res);
+ sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent);
+ if (IS_ERR(sub_sdev))
+ return PTR_ERR(sub_sdev);
+
+ ret = device_property_read_u32(dev, "reg", &eusb2_regmap_config.reg_base);
if (ret < 0)
return ret;
- rptr->base = res;
+ rptr->regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev, &eusb2_regmap_config);
+ if (IS_ERR(rptr->regmap))
+ return PTR_ERR(rptr->regmap);
ret = eusb2_repeater_init_vregs(rptr);
if (ret < 0) {
@@ -335,3 +346,4 @@ module_platform_driver(eusb2_repeater_driver);
MODULE_DESCRIPTION("Qualcomm PMIC eUSB2 Repeater driver");
MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("SPMI");
diff --git a/drivers/platform/goldfish/goldfish_pipe.c b/drivers/platform/goldfish/goldfish_pipe.c
index fa241ca8feb0..3f34db896ece 100644
--- a/drivers/platform/goldfish/goldfish_pipe.c
+++ b/drivers/platform/goldfish/goldfish_pipe.c
@@ -708,12 +708,11 @@ static int goldfish_pipe_open(struct inode *inode, struct file *file)
init_waitqueue_head(&pipe->wake_queue);
/*
- * Command buffer needs to be allocated on its own page to make sure
- * it is physically contiguous in host's address space.
+ * The command buffer is passed to the host as a physical address, so
+ * it must be physically contiguous, which kmalloc() guarantees.
*/
BUILD_BUG_ON(sizeof(struct goldfish_pipe_command) > PAGE_SIZE);
- pipe->command_buffer =
- (struct goldfish_pipe_command *)__get_free_page(GFP_KERNEL);
+ pipe->command_buffer = kmalloc_obj(*pipe->command_buffer);
if (!pipe->command_buffer) {
status = -ENOMEM;
goto err_pipe;
@@ -749,7 +748,7 @@ err_cmd:
dev->pipes[id] = NULL;
err_id_locked:
spin_unlock_irqrestore(&dev->lock, flags);
- free_page((unsigned long)pipe->command_buffer);
+ kfree(pipe->command_buffer);
err_pipe:
kfree(pipe);
return status;
@@ -770,7 +769,7 @@ static int goldfish_pipe_release(struct inode *inode, struct file *filp)
spin_unlock_irqrestore(&dev->lock, flags);
filp->private_data = NULL;
- free_page((unsigned long)pipe->command_buffer);
+ kfree(pipe->command_buffer);
kfree(pipe);
return 0;
}
@@ -833,13 +832,12 @@ static int goldfish_pipe_device_init(struct platform_device *pdev,
/*
* We're going to pass two buffers, open_command_params and
- * signalled_pipe_buffers, to the host. This means each of those buffers
- * needs to be contained in a single physical page. The easiest choice
- * is to just allocate a page and place the buffers in it.
+ * signalled_pipe_buffers, to the host as physical addresses. This means
+ * each of those buffers needs to be physically contiguous, which
+ * kmalloc() guarantees.
*/
BUILD_BUG_ON(sizeof(struct goldfish_pipe_dev_buffers) > PAGE_SIZE);
- dev->buffers = (struct goldfish_pipe_dev_buffers *)
- __get_free_page(GFP_KERNEL);
+ dev->buffers = kmalloc_obj(*dev->buffers);
if (!dev->buffers) {
kfree(dev->pipes);
misc_deregister(&dev->miscdev);
@@ -867,7 +865,7 @@ static void goldfish_pipe_device_deinit(struct platform_device *pdev,
{
misc_deregister(&dev->miscdev);
kfree(dev->pipes);
- free_page((unsigned long)dev->buffers);
+ kfree(dev->buffers);
}
static int goldfish_pipe_probe(struct platform_device *pdev)
diff --git a/drivers/power/reset/qcom-pon.c b/drivers/power/reset/qcom-pon.c
index 7e108982a582..cd7ba680b841 100644
--- a/drivers/power/reset/qcom-pon.c
+++ b/drivers/power/reset/qcom-pon.c
@@ -11,6 +11,7 @@
#include <linux/reboot.h>
#include <linux/reboot-mode.h>
#include <linux/regmap.h>
+#include <linux/spmi.h>
#define PON_SOFT_RB_SPARE 0x8f
@@ -22,7 +23,6 @@
struct qcom_pon {
struct device *dev;
struct regmap *regmap;
- u32 baseaddr;
struct reboot_mode_driver reboot_mode;
long reason_shift;
};
@@ -35,7 +35,7 @@ static int qcom_pon_reboot_mode_write(struct reboot_mode_driver *reboot,
int ret;
ret = regmap_update_bits(pon->regmap,
- pon->baseaddr + PON_SOFT_RB_SPARE,
+ PON_SOFT_RB_SPARE,
GENMASK(7, pon->reason_shift),
magic << pon->reason_shift);
if (ret < 0)
@@ -46,6 +46,15 @@ static int qcom_pon_reboot_mode_write(struct reboot_mode_driver *reboot,
static int qcom_pon_probe(struct platform_device *pdev)
{
+ struct regmap_config qcom_pon_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .max_register = 0xff,
+ .fast_io = true,
+ };
+ struct device *dev = &pdev->dev;
+ struct spmi_subdevice *sub_sdev;
+ struct spmi_device *sparent;
struct qcom_pon *pon;
long reason_shift;
int error;
@@ -56,17 +65,22 @@ static int qcom_pon_probe(struct platform_device *pdev)
pon->dev = &pdev->dev;
- pon->regmap = dev_get_regmap(pdev->dev.parent, NULL);
- if (!pon->regmap) {
- dev_err(&pdev->dev, "failed to locate regmap\n");
+ sparent = spmi_get_parent_spmi_device(dev);
+ if (!sparent)
return -ENODEV;
- }
- error = of_property_read_u32(pdev->dev.of_node, "reg",
- &pon->baseaddr);
+ sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent);
+ if (IS_ERR(sub_sdev))
+ return PTR_ERR(sub_sdev);
+
+ error = device_property_read_u32(dev, "reg", &qcom_pon_regmap_config.reg_base);
if (error)
return error;
+ pon->regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev, &qcom_pon_regmap_config);
+ if (IS_ERR(pon->regmap))
+ return PTR_ERR(pon->regmap);
+
reason_shift = (long)of_device_get_match_data(&pdev->dev);
if (reason_shift != NO_REASON_SHIFT) {
@@ -106,3 +120,4 @@ module_platform_driver(qcom_pon_driver);
MODULE_DESCRIPTION("Qualcomm Power On driver");
MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("SPMI");
diff --git a/drivers/pps/generators/pps_gen.c b/drivers/pps/generators/pps_gen.c
index 5e207c75e340..474e17c36458 100644
--- a/drivers/pps/generators/pps_gen.c
+++ b/drivers/pps/generators/pps_gen.c
@@ -63,23 +63,34 @@ static long pps_gen_cdev_ioctl(struct file *file,
switch (cmd) {
case PPS_GEN_SETENABLE:
- dev_dbg(pps_gen->dev, "PPS_GEN_SETENABLE\n");
+ dev_dbg(&pps_gen->dev, "PPS_GEN_SETENABLE\n");
ret = get_user(status, uiuarg);
if (ret)
return -EFAULT;
- ret = pps_gen->info->enable(pps_gen, status);
- if (ret)
- return ret;
- pps_gen->enabled = status;
+ scoped_guard(mutex, &pps_gen->info_lock) {
+ if (!pps_gen->info)
+ return -ENODEV;
+
+ ret = pps_gen->info->enable(pps_gen, status);
+ if (ret)
+ return ret;
+ pps_gen->enabled = status;
+ }
break;
case PPS_GEN_USESYSTEMCLOCK:
- dev_dbg(pps_gen->dev, "PPS_GEN_USESYSTEMCLOCK\n");
+ dev_dbg(&pps_gen->dev, "PPS_GEN_USESYSTEMCLOCK\n");
+
+ scoped_guard(mutex, &pps_gen->info_lock) {
+ if (!pps_gen->info)
+ return -ENODEV;
+ status = pps_gen->info->use_system_clock;
+ }
- ret = put_user(pps_gen->info->use_system_clock, uiuarg);
+ ret = put_user(status, uiuarg);
if (ret)
return -EFAULT;
@@ -89,14 +100,17 @@ static long pps_gen_cdev_ioctl(struct file *file,
struct pps_gen_event info;
unsigned int ev = pps_gen->last_ev;
- dev_dbg(pps_gen->dev, "PPS_GEN_FETCHEVENT\n");
+ dev_dbg(&pps_gen->dev, "PPS_GEN_FETCHEVENT\n");
ret = wait_event_interruptible(pps_gen->queue,
- ev != pps_gen->last_ev);
+ ev != pps_gen->last_ev ||
+ !READ_ONCE(pps_gen->info));
if (ret == -ERESTARTSYS) {
- dev_dbg(pps_gen->dev, "pending signal caught\n");
+ dev_dbg(&pps_gen->dev, "pending signal caught\n");
return -EINTR;
}
+ if (!READ_ONCE(pps_gen->info))
+ return -ENODEV;
spin_lock_irq(&pps_gen->lock);
info.sequence = pps_gen->sequence;
@@ -121,7 +135,7 @@ static int pps_gen_cdev_open(struct inode *inode, struct file *file)
struct pps_gen_device *pps_gen = container_of(inode->i_cdev,
struct pps_gen_device, cdev);
- get_device(pps_gen->dev);
+ get_device(&pps_gen->dev);
file->private_data = pps_gen;
return 0;
}
@@ -130,7 +144,7 @@ static int pps_gen_cdev_release(struct inode *inode, struct file *file)
{
struct pps_gen_device *pps_gen = file->private_data;
- put_device(pps_gen->dev);
+ put_device(&pps_gen->dev);
return 0;
}
@@ -151,19 +165,15 @@ static void pps_gen_device_destruct(struct device *dev)
{
struct pps_gen_device *pps_gen = dev_get_drvdata(dev);
- cdev_del(&pps_gen->cdev);
-
pr_debug("deallocating pps-gen%d\n", pps_gen->id);
ida_free(&pps_gen_ida, pps_gen->id);
- kfree(dev);
kfree(pps_gen);
}
static int pps_gen_register_cdev(struct pps_gen_device *pps_gen)
{
int err;
- dev_t devt;
err = ida_alloc_max(&pps_gen_ida, PPS_GEN_MAX_SOURCES - 1, GFP_KERNEL);
if (err < 0) {
@@ -171,46 +181,73 @@ static int pps_gen_register_cdev(struct pps_gen_device *pps_gen)
pr_err("too many PPS sources in the system\n");
err = -EBUSY;
}
+ kfree(pps_gen);
return err;
}
pps_gen->id = err;
- devt = MKDEV(MAJOR(pps_gen_devt), pps_gen->id);
+ /*
+ * From here on pps_gen belongs to its device and is freed by
+ * pps_gen_device_destruct(). The cdev holds a reference to the
+ * device, so pps_gen stays around until the last file is closed.
+ */
+ device_initialize(&pps_gen->dev);
+ pps_gen->dev.class = &pps_gen_class;
+ pps_gen->dev.parent = pps_gen->info->parent;
+ pps_gen->dev.devt = MKDEV(MAJOR(pps_gen_devt), pps_gen->id);
+ pps_gen->dev.release = pps_gen_device_destruct;
+ dev_set_drvdata(&pps_gen->dev, pps_gen);
cdev_init(&pps_gen->cdev, &pps_gen_cdev_fops);
pps_gen->cdev.owner = pps_gen->info->owner;
- err = cdev_add(&pps_gen->cdev, devt, 1);
+ err = dev_set_name(&pps_gen->dev, "pps-gen%d", pps_gen->id);
+ if (err)
+ goto put_dev;
+
+ err = cdev_device_add(&pps_gen->cdev, &pps_gen->dev);
if (err) {
pr_err("failed to add char device %d:%d\n",
MAJOR(pps_gen_devt), pps_gen->id);
- goto free_ida;
+ goto put_dev;
}
- pps_gen->dev = device_create(&pps_gen_class, pps_gen->info->parent, devt,
- pps_gen, "pps-gen%d", pps_gen->id);
- if (IS_ERR(pps_gen->dev)) {
- err = PTR_ERR(pps_gen->dev);
- goto del_cdev;
- }
- pps_gen->dev->release = pps_gen_device_destruct;
- dev_set_drvdata(pps_gen->dev, pps_gen);
pr_debug("generator got cdev (%d:%d)\n",
MAJOR(pps_gen_devt), pps_gen->id);
return 0;
-del_cdev:
- cdev_del(&pps_gen->cdev);
-free_ida:
- ida_free(&pps_gen_ida, pps_gen->id);
+put_dev:
+ put_device(&pps_gen->dev);
return err;
}
static void pps_gen_unregister_cdev(struct pps_gen_device *pps_gen)
{
pr_debug("unregistering pps-gen%d\n", pps_gen->id);
- device_destroy(&pps_gen_class, pps_gen->dev->devt);
+ cdev_device_del(&pps_gen->cdev, &pps_gen->dev);
+
+ /*
+ * An open file keeps pps_gen around, but the driver may free info as
+ * soon as we return. The sysfs files are gone now, so wait for the
+ * ioctls using info and make later ones fail.
+ *
+ * The driver may have stopped the generator before calling us, but
+ * userspace could have enabled it again since. Nothing can enable it
+ * after this point, so stop it here for good.
+ */
+ scoped_guard(mutex, &pps_gen->info_lock) {
+ if (pps_gen->enabled) {
+ pps_gen->info->enable(pps_gen, false);
+ pps_gen->enabled = false;
+ }
+ WRITE_ONCE(pps_gen->info, NULL);
+ }
+
+ /* Wake up the readers in PPS_GEN_FETCHEVENT, they fail now as well */
+ wake_up_interruptible_all(&pps_gen->queue);
+
+ put_device(&pps_gen->dev);
}
/*
@@ -241,21 +278,19 @@ struct pps_gen_device *pps_gen_register_source(const struct pps_gen_source_info
pps_gen->info = info;
pps_gen->enabled = false;
+ mutex_init(&pps_gen->info_lock);
init_waitqueue_head(&pps_gen->queue);
spin_lock_init(&pps_gen->lock);
- /* Create the char device */
+ /* Create the char device, this frees pps_gen on failure */
err = pps_gen_register_cdev(pps_gen);
if (err < 0) {
pr_err(" unable to create char device\n");
- goto kfree_pps_gen;
+ goto pps_gen_register_source_exit;
}
return pps_gen;
-kfree_pps_gen:
- kfree(pps_gen);
-
pps_gen_register_source_exit:
pr_err("unable to register generator\n");
@@ -289,7 +324,7 @@ void pps_gen_event(struct pps_gen_device *pps_gen,
{
unsigned long flags;
- dev_dbg(pps_gen->dev, "PPS generator event %u\n", event);
+ dev_dbg(&pps_gen->dev, "PPS generator event %u\n", event);
spin_lock_irqsave(&pps_gen->lock, flags);
diff --git a/drivers/slimbus/messaging.c b/drivers/slimbus/messaging.c
index e2dbe4a66b70..ee127ef42046 100644
--- a/drivers/slimbus/messaging.c
+++ b/drivers/slimbus/messaging.c
@@ -139,7 +139,7 @@ int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn)
if (need_tid) {
ret = slim_alloc_txn_tid(ctrl, txn);
if (ret)
- return ret;
+ goto slim_xfer_err;
if (!txn->msg->comp)
txn->comp = &done;
diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c
index 6e89415712ce..eb983b6d1815 100644
--- a/drivers/slimbus/qcom-ngd-ctrl.c
+++ b/drivers/slimbus/qcom-ngd-ctrl.c
@@ -2,6 +2,7 @@
// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
// Copyright (c) 2018, Linaro Limited
+#include <linux/bitfield.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/init.h>
@@ -87,6 +88,10 @@
#define SLIM_ROOT_FREQ 24576000
#define LADDR_RETRY 5
+#define SLIM_CHAN_CTRL_CMD GENMASK(7, 6)
+#define SLIM_CHAN_CTRL_LADDR GENMASK(4, 0)
+#define SLIM_MSG_HDR_LEN 4
+
/* Per spec.max 40 bytes per received message */
#define SLIM_MSGQ_BUF_LEN 40
#define QCOM_SLIM_NGD_DESC_NUM 32
@@ -1086,6 +1091,78 @@ static int qcom_slim_ngd_enable_stream(struct slim_stream_runtime *rt)
return ret;
}
+static int qcom_slim_ngd_disable_stream(struct slim_stream_runtime *rt)
+{
+ struct slim_device *sdev = rt->dev;
+ struct slim_controller *ctrl = sdev->ctrl;
+ struct slim_msg_txn txn = { 0 };
+ struct slim_val_inf msg = { 0 };
+ u8 wbuf[SLIM_MSGQ_BUF_LEN];
+ u8 rbuf[SLIM_MSGQ_BUF_LEN];
+ int i, ret;
+
+ txn.mt = SLIM_MSG_MT_DEST_REFERRED_USER;
+ txn.dt = SLIM_MSG_DEST_LOGICALADDR;
+ txn.la = SLIM_LA_MGR;
+ txn.ec = 0;
+ txn.msg = &msg;
+ txn.msg->num_bytes = 0;
+ txn.msg->wbuf = wbuf;
+ txn.msg->rbuf = rbuf;
+
+ for (i = 0; i < rt->num_ports; i++) {
+ struct slim_port *port = &rt->ports[i];
+
+ if (txn.msg->num_bytes == 0) {
+ wbuf[txn.msg->num_bytes++] =
+ FIELD_PREP(SLIM_CHAN_CTRL_CMD, SLIM_CH_REMOVE) |
+ FIELD_PREP(SLIM_CHAN_CTRL_LADDR, sdev->laddr);
+
+ ret = slim_alloc_txn_tid(ctrl, &txn);
+ if (ret) {
+ dev_err(&sdev->dev, "Fail to allocate TID ret:%d\n", ret);
+ return ret;
+ }
+ wbuf[txn.msg->num_bytes++] = txn.tid;
+ }
+ wbuf[txn.msg->num_bytes++] = port->ch.id;
+ }
+
+ txn.mc = SLIM_USR_MC_CHAN_CTRL;
+ txn.rl = txn.msg->num_bytes + SLIM_MSG_HDR_LEN;
+ ret = qcom_slim_ngd_xfer_msg_sync(ctrl, &txn);
+ if (ret) {
+ slim_free_txn_tid(ctrl, &txn);
+ /* Controller restarting, the channels are gone anyway */
+ if (ret == -EREMOTEIO)
+ return 0;
+ dev_err(&sdev->dev, "TX timed out: MC: 0x%x, mt: 0x%x, laddr: 0x%x, ret: %d\n",
+ txn.mc, txn.mt, sdev->laddr, ret);
+ return ret;
+ }
+
+ txn.mc = SLIM_USR_MC_RECONFIG_NOW;
+ txn.msg->num_bytes = 2;
+ wbuf[1] = sdev->laddr;
+ txn.rl = txn.msg->num_bytes + SLIM_MSG_HDR_LEN;
+
+ ret = slim_alloc_txn_tid(ctrl, &txn);
+ if (ret) {
+ dev_err(&sdev->dev, "Fail to allocate TID ret:%d\n", ret);
+ return ret;
+ }
+
+ wbuf[0] = txn.tid;
+ ret = qcom_slim_ngd_xfer_msg_sync(ctrl, &txn);
+ if (ret) {
+ slim_free_txn_tid(ctrl, &txn);
+ dev_err(&sdev->dev, "TX timed out: MC: 0x%x, mt: 0x%x, laddr: 0x%x, ret: %d\n",
+ txn.mc, txn.mt, sdev->laddr, ret);
+ }
+
+ return ret;
+}
+
static int qcom_slim_ngd_get_laddr(struct slim_controller *ctrl,
struct slim_eaddr *ea, u8 *laddr)
{
@@ -1539,7 +1616,7 @@ static int of_qcom_slim_ngd_register(struct device *parent,
kfree(ngd);
return ret;
}
- platform_device_set_of_node(ngd->pdev, node);
+ platform_device_set_fwnode(ngd->pdev, of_fwnode_handle(node));
ctrl->ngd = ngd;
ret = platform_device_add(ngd->pdev);
@@ -1614,7 +1691,7 @@ static int qcom_slim_ngd_ctrl_probe(struct platform_device *pdev)
IRQF_TRIGGER_HIGH | IRQF_NO_AUTOEN,
"slim-ngd", ctrl);
if (ret)
- return dev_err_probe(&pdev->dev, ret, "request IRQ failed\n");
+ return ret;
ctrl->dev = dev;
ctrl->framer.rootfreq = SLIM_ROOT_FREQ >> 3;
@@ -1625,6 +1702,7 @@ static int qcom_slim_ngd_ctrl_probe(struct platform_device *pdev)
ctrl->ctrl.clkgear = SLIM_MAX_CLK_GEAR;
ctrl->ctrl.get_laddr = qcom_slim_ngd_get_laddr;
ctrl->ctrl.enable_stream = qcom_slim_ngd_enable_stream;
+ ctrl->ctrl.disable_stream = qcom_slim_ngd_disable_stream;
ctrl->ctrl.xfer_msg = qcom_slim_ngd_xfer_msg;
ctrl->ctrl.wakeup = NULL;
ctrl->state = QCOM_SLIM_NGD_CTRL_DOWN;
diff --git a/drivers/slimbus/slimbus.h b/drivers/slimbus/slimbus.h
index 00a7f112574b..8e34f30601d3 100644
--- a/drivers/slimbus/slimbus.h
+++ b/drivers/slimbus/slimbus.h
@@ -317,6 +317,18 @@ enum slim_transport_protocol {
};
/**
+ * enum slim_ch_control: Channel control.
+ * @SLIM_CH_ACTIVATE: Schedules channel or group of channels in the TDM frame.
+ * @SLIM_CH_SUSPEND: Keeps the TDM schedule but halts data transfer.
+ * @SLIM_CH_REMOVE: Drops the channel or group from the TDM frame.
+ */
+enum slim_ch_control {
+ SLIM_CH_ACTIVATE,
+ SLIM_CH_SUSPEND,
+ SLIM_CH_REMOVE,
+};
+
+/**
* struct slim_stream_runtime - SLIMbus stream runtime instance
*
* @name: Name of the stream
@@ -325,7 +337,7 @@ enum slim_transport_protocol {
* @prot: Transport protocol used in this stream
* @rate: Data rate of samples *
* @bps: bits per sample
- * @ratem: rate multipler which is super frame rate/data rate
+ * @ratem: rate multiplier which is super frame rate/data rate
* @num_ports: number of ports
* @ports: pointer to instance of ports
* @node: list head for stream associated with slim device.
diff --git a/drivers/spmi/spmi-devres.c b/drivers/spmi/spmi-devres.c
index 62c4b3f24d06..c3e889fe1b6e 100644
--- a/drivers/spmi/spmi-devres.c
+++ b/drivers/spmi/spmi-devres.c
@@ -60,5 +60,29 @@ int devm_spmi_controller_add(struct device *parent, struct spmi_controller *ctrl
}
EXPORT_SYMBOL_GPL(devm_spmi_controller_add);
+static void devm_spmi_subdevice_remove(void *sub_sdev)
+{
+ spmi_subdevice_remove(sub_sdev);
+}
+
+struct spmi_subdevice *devm_spmi_subdevice_alloc_and_add(struct device *dev,
+ struct spmi_device *sparent)
+{
+ struct spmi_subdevice *sub_sdev;
+ int ret;
+
+ sub_sdev = spmi_subdevice_alloc_and_add(sparent);
+ if (IS_ERR(sub_sdev))
+ return sub_sdev;
+
+ ret = devm_add_action_or_reset(dev, devm_spmi_subdevice_remove, sub_sdev);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return sub_sdev;
+}
+EXPORT_SYMBOL_NS_GPL(devm_spmi_subdevice_alloc_and_add, "SPMI");
+
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SPMI devres helpers");
+MODULE_IMPORT_NS("SPMI");
diff --git a/drivers/spmi/spmi-mtk-pmif.c b/drivers/spmi/spmi-mtk-pmif.c
index 1048420b5afb..833c2b30d09f 100644
--- a/drivers/spmi/spmi-mtk-pmif.c
+++ b/drivers/spmi/spmi-mtk-pmif.c
@@ -21,6 +21,7 @@
#define SWINF_WFVLDCLR 0x06
#define GET_SWINF(x) (((x) >> 1) & 0x7)
+#define GET_SWINFERR(x) (((x) >> 18) & 0x1)
#define PMIF_CMD_REG_0 0
#define PMIF_CMD_REG 1
@@ -349,6 +350,7 @@ static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid,
struct pmif *arb = to_mtk_pmif(ctrl);
struct ch_reg *inf_reg;
int ret;
+ u8 retry = 0;
u32 data, cmd;
unsigned long flags;
@@ -385,17 +387,35 @@ static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid,
return ret;
}
- /* Send the command. */
cmd = (opc << 30) | (sid << 24) | ((len - 1) << 16) | addr;
- pmif_writel(arb, pbus, cmd, inf_reg->ch_send);
+ do {
+ /* Send the command. */
+ pmif_writel(arb, pbus, cmd, inf_reg->ch_send);
+
+ /*
+ * Wait for Software Interface FSM state to be WFVLDCLR or to
+ * return an error.
+ *
+ * If this is WFVLDCLR, read the data and clear the valid flag;
+ * If error or timeout, retry for a maximum of 3 times as a
+ * workaround for an hardware issue.
+ */
+ ret = readl_poll_timeout_atomic(pbus->base + arb->data->regs[inf_reg->ch_sta],
+ data,
+ GET_SWINF(data) == SWINF_WFVLDCLR ||
+ GET_SWINFERR(data),
+ PMIF_DELAY_US, PMIF_TIMEOUT_US / 2);
+ if (ret < 0)
+ continue;
+
+ if (GET_SWINFERR(data)) {
+ ret = -EIO;
+ continue;
+ }
+
+ break;
+ } while (++retry < 3);
- /*
- * Wait for Software Interface FSM state to be WFVLDCLR,
- * read the data and clear the valid flag.
- */
- ret = readl_poll_timeout_atomic(pbus->base + arb->data->regs[inf_reg->ch_sta],
- data, GET_SWINF(data) == SWINF_WFVLDCLR,
- PMIF_DELAY_US, PMIF_TIMEOUT_US);
if (ret < 0) {
raw_spin_unlock_irqrestore(&pbus->lock, flags);
dev_err(&ctrl->dev, "failed to wait for SWINF_WFVLDCLR\n");
@@ -726,9 +746,11 @@ static int mtk_spmi_bus_probe(struct platform_device *pdev,
for (i = 0; i < pbus->nclks; i++) {
pbus->clks[i].id = pmif_clock_names[i];
pbus->clks[i].clk = of_clk_get_by_name(node, pbus->clks[i].id);
- if (IS_ERR(pbus->clks[i].clk))
+ if (IS_ERR(pbus->clks[i].clk)) {
+ clk_bulk_put(i, pbus->clks);
return dev_err_probe(&pdev->dev, PTR_ERR(pbus->clks[i].clk),
"Failed to get clocks\n");
+ }
}
err = clk_bulk_prepare_enable(pbus->nclks, pbus->clks);
diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c
index 2e2cb4774103..402ffeaff8e2 100644
--- a/drivers/spmi/spmi-pmic-arb.c
+++ b/drivers/spmi/spmi-pmic-arb.c
@@ -2060,7 +2060,7 @@ static int spmi_pmic_arb_register_buses(struct spmi_pmic_arb *pmic_arb,
}
}
- return ret;
+ return 0;
}
static void spmi_pmic_arb_deregister_buses(struct spmi_pmic_arb *pmic_arb)
diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c
index 57b7c0cb4240..ec97f12046f5 100644
--- a/drivers/spmi/spmi.c
+++ b/drivers/spmi/spmi.c
@@ -19,11 +19,13 @@
static bool is_registered;
static DEFINE_IDA(ctrl_ida);
+static DEFINE_IDA(spmi_subdevice_ida);
static void spmi_dev_release(struct device *dev)
{
struct spmi_device *sdev = to_spmi_device(dev);
+ of_node_put(dev->of_node);
kfree(sdev);
}
@@ -31,6 +33,19 @@ static const struct device_type spmi_dev_type = {
.release = spmi_dev_release,
};
+static void spmi_subdev_release(struct device *dev)
+{
+ struct spmi_device *sdev = to_spmi_device(dev);
+ struct spmi_subdevice *sub_sdev = container_of(sdev, struct spmi_subdevice, sdev);
+
+ ida_free(&spmi_subdevice_ida, sub_sdev->devid);
+ kfree(sub_sdev);
+}
+
+static const struct device_type spmi_subdev_type = {
+ .release = spmi_subdev_release,
+};
+
static void spmi_ctrl_release(struct device *dev)
{
struct spmi_controller *ctrl = to_spmi_controller(dev);
@@ -68,15 +83,12 @@ int spmi_device_add(struct spmi_device *sdev)
err = device_add(&sdev->dev);
if (err < 0) {
- dev_err(&sdev->dev, "Can't add %s, status %d\n",
- dev_name(&sdev->dev), err);
- goto err_device_add;
+ dev_err(&sdev->dev, "Can't add device, status %pe\n", ERR_PTR(err));
+ return err;
}
- dev_dbg(&sdev->dev, "device %s registered\n", dev_name(&sdev->dev));
-
-err_device_add:
- return err;
+ dev_dbg(&sdev->dev, "device registered\n");
+ return 0;
}
EXPORT_SYMBOL_GPL(spmi_device_add);
@@ -90,6 +102,18 @@ void spmi_device_remove(struct spmi_device *sdev)
}
EXPORT_SYMBOL_GPL(spmi_device_remove);
+/**
+ * spmi_subdevice_remove() - Remove an SPMI subdevice
+ * @sub_sdev: spmi_device to be removed
+ */
+void spmi_subdevice_remove(struct spmi_subdevice *sub_sdev)
+{
+ struct spmi_device *sdev = &sub_sdev->sdev;
+
+ device_unregister(&sdev->dev);
+}
+EXPORT_SYMBOL_NS_GPL(spmi_subdevice_remove, "SPMI");
+
static inline int
spmi_cmd(struct spmi_controller *ctrl, u8 opcode, u8 sid)
{
@@ -408,6 +432,29 @@ struct spmi_device *spmi_find_device_by_of_node(struct device_node *np)
EXPORT_SYMBOL_GPL(spmi_find_device_by_of_node);
/**
+ * spmi_get_parent_spmi_device() - get the parent SPMI device from current dev
+ * @dev: pointer to a subdevice on SPMI bus
+ *
+ * Checks if the passed device is a child of an SPMI device and returns a
+ * handle to the parent SPMI device without incrementing any refcount.
+ *
+ * Return: Handle to the parent SPMI device or NULL
+ */
+struct spmi_device *spmi_get_parent_spmi_device(struct device *dev)
+{
+ struct device *parent;
+
+ if (dev && dev->parent) {
+ parent = dev->parent;
+
+ if (parent->type == &spmi_dev_type)
+ return to_spmi_device(parent);
+ }
+ return NULL;
+}
+EXPORT_SYMBOL_NS_GPL(spmi_get_parent_spmi_device, "SPMI");
+
+/**
* spmi_device_alloc() - Allocate a new SPMI device
* @ctrl: associated controller
*
@@ -432,6 +479,58 @@ struct spmi_device *spmi_device_alloc(struct spmi_controller *ctrl)
EXPORT_SYMBOL_GPL(spmi_device_alloc);
/**
+ * spmi_subdevice_alloc_and_add(): Allocate and add a new SPMI sub-device
+ * @sparent: SPMI parent device with previously registered SPMI controller
+ *
+ * Returns:
+ * Pointer to newly allocated SPMI sub-device for success or error pointer.
+ */
+struct spmi_subdevice *spmi_subdevice_alloc_and_add(struct spmi_device *sparent)
+{
+ struct spmi_subdevice *sub_sdev;
+ struct spmi_device *sdev;
+ int ret;
+
+ sub_sdev = kzalloc_obj(*sub_sdev);
+ if (!sub_sdev)
+ return ERR_PTR(-ENOMEM);
+
+ sdev = &sub_sdev->sdev;
+ sdev->ctrl = sparent->ctrl;
+ sdev->usid = sparent->usid;
+
+ ret = ida_alloc(&spmi_subdevice_ida, GFP_KERNEL);
+ if (ret < 0) {
+ kfree(sub_sdev);
+ return ERR_PTR(ret);
+ }
+ sub_sdev->devid = ret;
+
+ device_initialize(&sdev->dev);
+ sdev->dev.parent = &sparent->dev;
+ sdev->dev.bus = &spmi_bus_type;
+ sdev->dev.type = &spmi_subdev_type;
+
+ ret = dev_set_name(&sdev->dev, "%u-%02x.%u.auto",
+ sdev->ctrl->nr, sdev->usid, sub_sdev->devid);
+ if (ret)
+ goto err_put_dev;
+
+ ret = device_add(&sdev->dev);
+ if (ret) {
+ dev_err(&sdev->dev, "Can't add device, status %pe\n", ERR_PTR(ret));
+ goto err_put_dev;
+ }
+
+ return sub_sdev;
+
+err_put_dev:
+ put_device(&sdev->dev);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_NS_GPL(spmi_subdevice_alloc_and_add, "SPMI");
+
+/**
* spmi_controller_alloc() - Allocate a new SPMI controller
* @parent: parent device
* @size: size of private data
@@ -494,8 +593,8 @@ static void of_spmi_register_devices(struct spmi_controller *ctrl)
err = of_property_read_u32_array(node, "reg", reg, 2);
if (err) {
dev_err(&ctrl->dev,
- "node %pOF err (%d) does not have 'reg' property\n",
- node, err);
+ "node %pOF err (%pe) does not have 'reg' property\n",
+ node, ERR_PTR(err));
continue;
}
@@ -517,13 +616,13 @@ static void of_spmi_register_devices(struct spmi_controller *ctrl)
if (!sdev)
continue;
- device_set_node(&sdev->dev, of_fwnode_handle(node));
+ device_set_node(&sdev->dev, of_fwnode_handle(of_node_get(node)));
sdev->usid = (u8)reg[0];
err = spmi_device_add(sdev);
if (err) {
dev_err(&sdev->dev,
- "failure adding device. status %d\n", err);
+ "failure adding device. status %pe\n", ERR_PTR(err));
spmi_device_put(sdev);
}
}
diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a b/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a
index 863d3856718a..d19db00acd1d 100644
--- a/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a
+++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a
@@ -4,7 +4,7 @@ Contact: linux-iio@vger.kernel.org
Description:
Writing 1 enables the cell balance output switch corresponding
to input Y. Writing 0 disables it. If the inY-inZ_balance_timer
- is set to a none zero value, the corresponding switch will
+ is set to a non-zero value, the corresponding switch will
enable for the programmed amount of time, before it
automatically disables.
diff --git a/drivers/staging/iio/Kconfig b/drivers/staging/iio/Kconfig
index a60631c1f449..bf6addf2974f 100644
--- a/drivers/staging/iio/Kconfig
+++ b/drivers/staging/iio/Kconfig
@@ -5,7 +5,6 @@
menu "IIO staging drivers"
depends on IIO
-source "drivers/staging/iio/accel/Kconfig"
source "drivers/staging/iio/adc/Kconfig"
source "drivers/staging/iio/addac/Kconfig"
source "drivers/staging/iio/frequency/Kconfig"
diff --git a/drivers/staging/iio/Makefile b/drivers/staging/iio/Makefile
index 628583535393..739e4cbf9a7a 100644
--- a/drivers/staging/iio/Makefile
+++ b/drivers/staging/iio/Makefile
@@ -3,7 +3,6 @@
# Makefile for the industrial I/O core.
#
-obj-y += accel/
obj-y += adc/
obj-y += addac/
obj-y += frequency/
diff --git a/drivers/staging/iio/accel/Kconfig b/drivers/staging/iio/accel/Kconfig
deleted file mode 100644
index cee51f64bc4b..000000000000
--- a/drivers/staging/iio/accel/Kconfig
+++ /dev/null
@@ -1,19 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0
-#
-# Accelerometer drivers
-#
-menu "Accelerometers"
-
-config ADIS16203
- tristate "Analog Devices ADIS16203 Programmable 360 Degrees Inclinometer"
- 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 adis16203 Programmable
- 360 Degrees Inclinometer.
-
- To compile this driver as a module, say M here: the module will be
- called adis16203.
-
-endmenu
diff --git a/drivers/staging/iio/accel/Makefile b/drivers/staging/iio/accel/Makefile
deleted file mode 100644
index acac7bc9b9c0..000000000000
--- a/drivers/staging/iio/accel/Makefile
+++ /dev/null
@@ -1,6 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0
-#
-# Makefile for industrial I/O accelerometer drivers
-#
-
-obj-$(CONFIG_ADIS16203) += adis16203.o
diff --git a/drivers/staging/iio/accel/adis16203.c b/drivers/staging/iio/accel/adis16203.c
deleted file mode 100644
index 830ff38fda92..000000000000
--- a/drivers/staging/iio/accel/adis16203.c
+++ /dev/null
@@ -1,315 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * ADIS16203 Programmable 360 Degrees Inclinometer
- *
- * Copyright 2010 Analog Devices Inc.
- */
-
-#include <linux/device.h>
-
-#include <linux/iio/iio.h>
-#include <linux/iio/imu/adis.h>
-
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/spi/spi.h>
-
-#define ADIS16203_STARTUP_DELAY 220 /* ms */
-
-/* Flash memory write count */
-#define ADIS16203_FLASH_CNT 0x00
-
-/* Output, power supply */
-#define ADIS16203_SUPPLY_OUT 0x02
-
-/* Output, auxiliary ADC input */
-#define ADIS16203_AUX_ADC 0x08
-
-/* Output, temperature */
-#define ADIS16203_TEMP_OUT 0x0A
-
-/* Output, x-axis inclination */
-#define ADIS16203_XINCL_OUT 0x0C
-
-/* Output, y-axis inclination */
-#define ADIS16203_YINCL_OUT 0x0E
-
-/* Incline null calibration */
-#define ADIS16203_INCL_NULL 0x18
-
-/* Alarm 1 amplitude threshold */
-#define ADIS16203_ALM_MAG1 0x20
-
-/* Alarm 2 amplitude threshold */
-#define ADIS16203_ALM_MAG2 0x22
-
-/* Alarm 1, sample period */
-#define ADIS16203_ALM_SMPL1 0x24
-
-/* Alarm 2, sample period */
-#define ADIS16203_ALM_SMPL2 0x26
-
-/* Alarm control */
-#define ADIS16203_ALM_CTRL 0x28
-
-/* Auxiliary DAC data */
-#define ADIS16203_AUX_DAC 0x30
-
-/* General-purpose digital input/output control */
-#define ADIS16203_GPIO_CTRL 0x32
-
-/* Miscellaneous control */
-#define ADIS16203_MSC_CTRL 0x34
-
-/* Internal sample period (rate) control */
-#define ADIS16203_SMPL_PRD 0x36
-
-/* Operation, filter configuration */
-#define ADIS16203_AVG_CNT 0x38
-
-/* Operation, sleep mode control */
-#define ADIS16203_SLP_CNT 0x3A
-
-/* Diagnostics, system status register */
-#define ADIS16203_DIAG_STAT 0x3C
-
-/* Operation, system command register */
-#define ADIS16203_GLOB_CMD 0x3E
-
-/* MSC_CTRL */
-
-/* Self-test at power-on: 1 = disabled, 0 = enabled */
-#define ADIS16203_MSC_CTRL_PWRUP_SELF_TEST BIT(10)
-
-/* Reverses rotation of both inclination outputs */
-#define ADIS16203_MSC_CTRL_REVERSE_ROT_EN BIT(9)
-
-/* Self-test enable */
-#define ADIS16203_MSC_CTRL_SELF_TEST_EN BIT(8)
-
-/* Data-ready enable: 1 = enabled, 0 = disabled */
-#define ADIS16203_MSC_CTRL_DATA_RDY_EN BIT(2)
-
-/* Data-ready polarity: 1 = active high, 0 = active low */
-#define ADIS16203_MSC_CTRL_ACTIVE_HIGH BIT(1)
-
-/* Data-ready line selection: 1 = DIO1, 0 = DIO0 */
-#define ADIS16203_MSC_CTRL_DATA_RDY_DIO1 BIT(0)
-
-/* DIAG_STAT */
-
-/* Alarm 2 status: 1 = alarm active, 0 = alarm inactive */
-#define ADIS16203_DIAG_STAT_ALARM2 BIT(9)
-
-/* Alarm 1 status: 1 = alarm active, 0 = alarm inactive */
-#define ADIS16203_DIAG_STAT_ALARM1 BIT(8)
-
-/* Self-test diagnostic error flag */
-#define ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT 5
-
-/* SPI communications failure */
-#define ADIS16203_DIAG_STAT_SPI_FAIL_BIT 3
-
-/* Flash update failure */
-#define ADIS16203_DIAG_STAT_FLASH_UPT_BIT 2
-
-/* Power supply above 3.625 V */
-#define ADIS16203_DIAG_STAT_POWER_HIGH_BIT 1
-
-/* Power supply below 2.975 V */
-#define ADIS16203_DIAG_STAT_POWER_LOW_BIT 0
-
-/* GLOB_CMD */
-
-#define ADIS16203_GLOB_CMD_SW_RESET BIT(7)
-#define ADIS16203_GLOB_CMD_CLEAR_STAT BIT(4)
-#define ADIS16203_GLOB_CMD_FACTORY_CAL BIT(1)
-
-#define ADIS16203_ERROR_ACTIVE BIT(14)
-
-enum adis16203_scan {
- ADIS16203_SCAN_INCLI_X,
- ADIS16203_SCAN_INCLI_Y,
- ADIS16203_SCAN_SUPPLY,
- ADIS16203_SCAN_AUX_ADC,
- ADIS16203_SCAN_TEMP,
-};
-
-#define DRIVER_NAME "adis16203"
-
-static const u8 adis16203_addresses[] = {
- [ADIS16203_SCAN_INCLI_X] = ADIS16203_INCL_NULL,
-};
-
-static int adis16203_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);
- /* currently only one writable parameter which keeps this simple */
- u8 addr = adis16203_addresses[chan->scan_index];
-
- return adis_write_reg_16(st, addr, val & 0x3FFF);
-}
-
-static int adis16203_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;
- u8 addr;
- s16 val16;
-
- switch (mask) {
- case IIO_CHAN_INFO_RAW:
- return adis_single_conversion(indio_dev, chan,
- ADIS16203_ERROR_ACTIVE, val);
- case IIO_CHAN_INFO_SCALE:
- switch (chan->type) {
- case IIO_VOLTAGE:
- if (chan->channel == 0) {
- *val = 1;
- *val2 = 220000; /* 1.22 mV */
- } else {
- *val = 0;
- *val2 = 610000; /* 0.61 mV */
- }
- return IIO_VAL_INT_PLUS_MICRO;
- case IIO_TEMP:
- *val = -470; /* -0.47 C */
- *val2 = 0;
- return IIO_VAL_INT_PLUS_MICRO;
- case IIO_INCLI:
- *val = 0;
- *val2 = 25000; /* 0.025 degree */
- return IIO_VAL_INT_PLUS_MICRO;
- default:
- return -EINVAL;
- }
- case IIO_CHAN_INFO_OFFSET:
- *val = 25000 / -470 - 1278; /* 25 C = 1278 */
- return IIO_VAL_INT;
- case IIO_CHAN_INFO_CALIBBIAS:
- addr = adis16203_addresses[chan->scan_index];
- ret = adis_read_reg_16(st, addr, &val16);
- if (ret)
- return ret;
- *val = sign_extend32(val16, 13);
- return IIO_VAL_INT;
- default:
- return -EINVAL;
- }
-}
-
-static const struct iio_chan_spec adis16203_channels[] = {
- ADIS_SUPPLY_CHAN(ADIS16203_SUPPLY_OUT, ADIS16203_SCAN_SUPPLY, 0, 12),
- ADIS_AUX_ADC_CHAN(ADIS16203_AUX_ADC, ADIS16203_SCAN_AUX_ADC, 0, 12),
- ADIS_INCLI_CHAN(X, ADIS16203_XINCL_OUT, ADIS16203_SCAN_INCLI_X,
- BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14),
- /* Fixme: Not what it appears to be - see data sheet */
- ADIS_INCLI_CHAN(Y, ADIS16203_YINCL_OUT, ADIS16203_SCAN_INCLI_Y,
- 0, 0, 14),
- ADIS_TEMP_CHAN(ADIS16203_TEMP_OUT, ADIS16203_SCAN_TEMP, 0, 12),
- IIO_CHAN_SOFT_TIMESTAMP(5),
-};
-
-static const struct iio_info adis16203_info = {
- .read_raw = adis16203_read_raw,
- .write_raw = adis16203_write_raw,
- .update_scan_mode = adis_update_scan_mode,
-};
-
-static const char * const adis16203_status_error_msgs[] = {
- [ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT] = "Self test failure",
- [ADIS16203_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure",
- [ADIS16203_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed",
- [ADIS16203_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V",
- [ADIS16203_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 2.975V",
-};
-
-static const struct adis_timeout adis16203_timeouts = {
- .reset_ms = ADIS16203_STARTUP_DELAY,
- .sw_reset_ms = ADIS16203_STARTUP_DELAY,
- .self_test_ms = ADIS16203_STARTUP_DELAY
-};
-
-static const struct adis_data adis16203_data = {
- .read_delay = 20,
- .msc_ctrl_reg = ADIS16203_MSC_CTRL,
- .glob_cmd_reg = ADIS16203_GLOB_CMD,
- .diag_stat_reg = ADIS16203_DIAG_STAT,
-
- .self_test_mask = ADIS16203_MSC_CTRL_SELF_TEST_EN,
- .self_test_reg = ADIS16203_MSC_CTRL,
- .self_test_no_autoclear = true,
- .timeouts = &adis16203_timeouts,
-
- .status_error_msgs = adis16203_status_error_msgs,
- .status_error_mask = BIT(ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT) |
- BIT(ADIS16203_DIAG_STAT_SPI_FAIL_BIT) |
- BIT(ADIS16203_DIAG_STAT_FLASH_UPT_BIT) |
- BIT(ADIS16203_DIAG_STAT_POWER_HIGH_BIT) |
- BIT(ADIS16203_DIAG_STAT_POWER_LOW_BIT),
-};
-
-static int adis16203_probe(struct spi_device *spi)
-{
- int ret;
- struct iio_dev *indio_dev;
- struct adis *st;
-
- /* 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);
-
- indio_dev->name = spi->dev.driver->name;
- indio_dev->channels = adis16203_channels;
- indio_dev->num_channels = ARRAY_SIZE(adis16203_channels);
- indio_dev->info = &adis16203_info;
- indio_dev->modes = INDIO_DIRECT_MODE;
-
- ret = adis_init(st, indio_dev, spi, &adis16203_data);
- if (ret)
- return ret;
-
- ret = devm_adis_setup_buffer_and_trigger(st, indio_dev, NULL);
- if (ret)
- return ret;
-
- /* Get the device into a sane initial state */
- ret = __adis_initial_startup(st);
- if (ret)
- return ret;
-
- return devm_iio_device_register(&spi->dev, indio_dev);
-}
-
-static const struct of_device_id adis16203_of_match[] = {
- { .compatible = "adi,adis16203" },
- { }
-};
-
-MODULE_DEVICE_TABLE(of, adis16203_of_match);
-
-static struct spi_driver adis16203_driver = {
- .driver = {
- .name = "adis16203",
- .of_match_table = adis16203_of_match,
- },
- .probe = adis16203_probe,
-};
-module_spi_driver(adis16203_driver);
-
-MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
-MODULE_DESCRIPTION("Analog Devices ADIS16203 Programmable 360 Degrees Inclinometer");
-MODULE_LICENSE("GPL v2");
-MODULE_ALIAS("spi:adis16203");
-MODULE_IMPORT_NS("IIO_ADISLIB");
diff --git a/drivers/staging/iio/adc/ad7816.c b/drivers/staging/iio/adc/ad7816.c
index 30644d2d7c54..2a15d7598abb 100644
--- a/drivers/staging/iio/adc/ad7816.c
+++ b/drivers/staging/iio/adc/ad7816.c
@@ -5,19 +5,21 @@
* Copyright 2010 Analog Devices Inc.
*/
-#include <linux/interrupt.h>
-#include <linux/gpio/consumer.h>
+#include <linux/cleanup.h>
#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/sysfs.h>
#include <linux/list.h>
-#include <linux/spi/spi.h>
#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/sysfs.h>
+#include <linux/iio/events.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
-#include <linux/iio/events.h>
/*
* AD7816 config masks
@@ -47,6 +49,7 @@ struct ad7816_chip_info {
struct gpio_desc *rdwr_pin;
struct gpio_desc *convert_pin;
struct gpio_desc *busy_pin;
+ struct mutex lock; /* protect device state during SPI transfers */
u8 oti_data[AD7816_CS_MAX + 1];
u8 channel_id; /* 0 always be temperature */
u8 mode;
@@ -67,9 +70,11 @@ static int ad7816_spi_read(struct ad7816_chip_info *chip, u16 *data)
int ret;
__be16 buf;
+ guard(mutex)(&chip->lock);
+
gpiod_set_value(chip->rdwr_pin, 1);
gpiod_set_value(chip->rdwr_pin, 0);
- ret = spi_write(spi_dev, &chip->channel_id, sizeof(chip->channel_id));
+ ret = spi_write_then_read(spi_dev, &chip->channel_id, sizeof(chip->channel_id), NULL, 0);
if (ret < 0) {
dev_err(&spi_dev->dev, "SPI channel setting error\n");
return ret;
@@ -91,7 +96,7 @@ static int ad7816_spi_read(struct ad7816_chip_info *chip, u16 *data)
gpiod_set_value(chip->rdwr_pin, 0);
gpiod_set_value(chip->rdwr_pin, 1);
- ret = spi_read(spi_dev, &buf, sizeof(*data));
+ ret = spi_write_then_read(spi_dev, NULL, 0, &buf, sizeof(buf));
if (ret < 0) {
dev_err(&spi_dev->dev, "SPI data read error\n");
return ret;
@@ -107,9 +112,11 @@ static int ad7816_spi_write(struct ad7816_chip_info *chip, u8 data)
struct spi_device *spi_dev = chip->spi_dev;
int ret;
+ guard(mutex)(&chip->lock);
+
gpiod_set_value(chip->rdwr_pin, 1);
gpiod_set_value(chip->rdwr_pin, 0);
- ret = spi_write(spi_dev, &data, sizeof(data));
+ ret = spi_write_then_read(spi_dev, &data, sizeof(data), NULL, 0);
if (ret < 0)
dev_err(&spi_dev->dev, "SPI oti data write error\n");
@@ -136,6 +143,8 @@ static ssize_t ad7816_store_mode(struct device *dev,
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct ad7816_chip_info *chip = iio_priv(indio_dev);
+ guard(mutex)(&chip->lock);
+
if (strcmp(buf, "full") == 0) {
gpiod_set_value(chip->rdwr_pin, 1);
chip->mode = AD7816_FULL;
@@ -200,6 +209,8 @@ static ssize_t ad7816_store_channel(struct device *dev,
return -EINVAL;
}
+ guard(mutex)(&chip->lock);
+
chip->channel_id = data;
return len;
@@ -360,6 +371,10 @@ static int ad7816_probe(struct spi_device *spi_dev)
return -ENOMEM;
chip = iio_priv(indio_dev);
+ ret = devm_mutex_init(&spi_dev->dev, &chip->lock);
+ if (ret)
+ return ret;
+
chip->spi_dev = spi_dev;
for (i = 0; i <= AD7816_CS_MAX; i++)
chip->oti_data[i] = 203;
diff --git a/drivers/staging/iio/frequency/ad9832.c b/drivers/staging/iio/frequency/ad9832.c
index a78249812b26..35f823f24f9b 100644
--- a/drivers/staging/iio/frequency/ad9832.c
+++ b/drivers/staging/iio/frequency/ad9832.c
@@ -118,6 +118,12 @@ static unsigned long ad9832_calc_freqreg(unsigned long mclk, unsigned long fout)
{
u64 freqreg = (u64)fout << AD9832_FREQ_BITS;
+ /*
+ * mclk is an unsigned long, which triggers a Coccinelle false positive
+ * warning about using do_div() for 64-by-32 division. However, mclk
+ * for this hardware will always fit within 32 bits, so do_div() is
+ * safe to use here.
+ */
do_div(freqreg, mclk);
return freqreg;
}
diff --git a/drivers/staging/iio/frequency/ad9834.c b/drivers/staging/iio/frequency/ad9834.c
index 0d20f796e0f0..4cebb9d0d924 100644
--- a/drivers/staging/iio/frequency/ad9834.c
+++ b/drivers/staging/iio/frequency/ad9834.c
@@ -103,6 +103,12 @@ static unsigned int ad9834_calc_freqreg(unsigned long mclk, unsigned long fout)
{
unsigned long long freqreg = (u64)fout * (u64)BIT(AD9834_FREQ_BITS);
+ /*
+ * mclk is an unsigned long, which triggers a Coccinelle false positive
+ * warning about using a do_div() for 64-by-32 division. However, mclk
+ * for this hardware will always fit within 32 bits, so do_div() is
+ * safe to use here.
+ */
do_div(freqreg, mclk);
return freqreg;
}