summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/android/Kconfig16
-rw-r--r--drivers/android/Makefile3
-rw-r--r--drivers/android/binder.c160
-rw-r--r--drivers/android/binder/Makefile9
-rw-r--r--drivers/android/binder/allocation.rs602
-rw-r--r--drivers/android/binder/context.rs180
-rw-r--r--drivers/android/binder/deferred_close.rs204
-rw-r--r--drivers/android/binder/defs.rs182
-rw-r--r--drivers/android/binder/error.rs99
-rw-r--r--drivers/android/binder/freeze.rs388
-rw-r--r--drivers/android/binder/node.rs1131
-rw-r--r--drivers/android/binder/node/wrapper.rs78
-rw-r--r--drivers/android/binder/page_range.rs734
-rw-r--r--drivers/android/binder/page_range_helper.c24
-rw-r--r--drivers/android/binder/page_range_helper.h15
-rw-r--r--drivers/android/binder/process.rs1696
-rw-r--r--drivers/android/binder/range_alloc/array.rs251
-rw-r--r--drivers/android/binder/range_alloc/mod.rs329
-rw-r--r--drivers/android/binder/range_alloc/tree.rs488
-rw-r--r--drivers/android/binder/rust_binder.h23
-rw-r--r--drivers/android/binder/rust_binder_events.c59
-rw-r--r--drivers/android/binder/rust_binder_events.h36
-rw-r--r--drivers/android/binder/rust_binder_internal.h87
-rw-r--r--drivers/android/binder/rust_binder_main.rs627
-rw-r--r--drivers/android/binder/rust_binderfs.c850
-rw-r--r--drivers/android/binder/stats.rs89
-rw-r--r--drivers/android/binder/thread.rs1596
-rw-r--r--drivers/android/binder/trace.rs16
-rw-r--r--drivers/android/binder/transaction.rs456
-rw-r--r--drivers/android/binder_internal.h4
-rw-r--r--drivers/android/binder_netlink.c31
-rw-r--r--drivers/android/binder_netlink.h20
-rw-r--r--drivers/android/binder_trace.h37
-rw-r--r--drivers/android/binderfs.c8
-rw-r--r--drivers/android/dbitmap.h1
-rw-r--r--drivers/bus/mhi/ep/main.c37
-rw-r--r--drivers/bus/mhi/host/init.c5
-rw-r--r--drivers/bus/mhi/host/internal.h3
-rw-r--r--drivers/bus/mhi/host/main.c1
-rw-r--r--drivers/bus/mhi/host/pci_generic.c85
-rw-r--r--drivers/bus/mhi/host/pm.c29
-rw-r--r--drivers/cdx/cdx_msi.c1
-rw-r--r--drivers/char/Makefile1
-rw-r--r--drivers/char/adi.c2
-rw-r--r--drivers/char/hpet.c2
-rw-r--r--drivers/char/misc.c21
-rw-r--r--drivers/char/misc_minor_kunit.c (renamed from drivers/misc/misc_minor_kunit.c)95
-rw-r--r--drivers/comedi/Kconfig9
-rw-r--r--drivers/comedi/drivers/Makefile1
-rw-r--r--drivers/comedi/drivers/adl_pci7250.c220
-rw-r--r--drivers/counter/ti-ecap-capture.c12
-rw-r--r--drivers/extcon/Kconfig13
-rw-r--r--drivers/extcon/Makefile1
-rw-r--r--drivers/extcon/extcon-adc-jack.c2
-rw-r--r--drivers/extcon/extcon-axp288.c2
-rw-r--r--drivers/extcon/extcon-fsa9480.c2
-rw-r--r--drivers/extcon/extcon-max14526.c302
-rw-r--r--drivers/extcon/extcon-qcom-spmi-misc.c2
-rw-r--r--drivers/gpio/Kconfig11
-rw-r--r--drivers/gpio/Makefile1
-rw-r--r--drivers/gpio/gpio-usbio.c247
-rw-r--r--drivers/greybus/svc.c3
-rw-r--r--drivers/hid/Kconfig11
-rw-r--r--drivers/hid/Makefile1
-rw-r--r--drivers/hid/hid-asus.c6
-rw-r--r--drivers/hid/hid-core.c44
-rw-r--r--drivers/hid/hid-haptic.c580
-rw-r--r--drivers/hid/hid-haptic.h127
-rw-r--r--drivers/hid/hid-ids.h4
-rw-r--r--drivers/hid/hid-input.c18
-rw-r--r--drivers/hid/hid-multitouch.c47
-rw-r--r--drivers/hid/hid-playstation.c1073
-rw-r--r--drivers/hid/hid-quirks.c2
-rw-r--r--drivers/hid/hid-steelseries.c108
-rw-r--r--drivers/hid/hid-uclogic-params.c10
-rw-r--r--drivers/hid/hid-universal-pidff.c57
-rw-r--r--drivers/hid/hidraw.c224
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-acpi.c8
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-core.c28
-rw-r--r--drivers/hid/i2c-hid/i2c-hid.h2
-rw-r--r--drivers/hid/intel-ish-hid/ipc/ipc.c17
-rw-r--r--drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c39
-rw-r--r--drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h24
-rw-r--r--drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c3
-rw-r--r--drivers/hid/usbhid/hid-pidff.c716
-rw-r--r--drivers/hid/usbhid/hid-pidff.h2
-rw-r--r--drivers/hwtracing/coresight/Kconfig12
-rw-r--r--drivers/hwtracing/coresight/Makefile1
-rw-r--r--drivers/hwtracing/coresight/coresight-catu.c53
-rw-r--r--drivers/hwtracing/coresight/coresight-catu.h1
-rw-r--r--drivers/hwtracing/coresight/coresight-core.c54
-rw-r--r--drivers/hwtracing/coresight/coresight-cpu-debug.c41
-rw-r--r--drivers/hwtracing/coresight/coresight-ctcu-core.c24
-rw-r--r--drivers/hwtracing/coresight/coresight-etb10.c18
-rw-r--r--drivers/hwtracing/coresight/coresight-etm-perf.c4
-rw-r--r--drivers/hwtracing/coresight/coresight-etm3x-core.c17
-rw-r--r--drivers/hwtracing/coresight/coresight-etm4x-core.c44
-rw-r--r--drivers/hwtracing/coresight/coresight-etm4x-sysfs.c1
-rw-r--r--drivers/hwtracing/coresight/coresight-etm4x.h6
-rw-r--r--drivers/hwtracing/coresight/coresight-funnel.c66
-rw-r--r--drivers/hwtracing/coresight/coresight-replicator.c63
-rw-r--r--drivers/hwtracing/coresight/coresight-stm.c42
-rw-r--r--drivers/hwtracing/coresight/coresight-syscfg.c2
-rw-r--r--drivers/hwtracing/coresight/coresight-sysfs.c71
-rw-r--r--drivers/hwtracing/coresight/coresight-tmc-core.c70
-rw-r--r--drivers/hwtracing/coresight/coresight-tmc.h2
-rw-r--r--drivers/hwtracing/coresight/coresight-tnoc.c246
-rw-r--r--drivers/hwtracing/coresight/coresight-tpda.c3
-rw-r--r--drivers/hwtracing/coresight/coresight-tpiu.c36
-rw-r--r--drivers/hwtracing/coresight/coresight-trbe.c9
-rw-r--r--drivers/hwtracing/coresight/ultrasoc-smb.h1
-rw-r--r--drivers/i2c/busses/Kconfig11
-rw-r--r--drivers/i2c/busses/Makefile1
-rw-r--r--drivers/i2c/busses/i2c-usbio.c320
-rw-r--r--drivers/iio/accel/adxl345_core.c782
-rw-r--r--drivers/iio/accel/bma180.c13
-rw-r--r--drivers/iio/accel/bma220_spi.c4
-rw-r--r--drivers/iio/accel/bmc150-accel-core.c7
-rw-r--r--drivers/iio/accel/bmi088-accel-core.c3
-rw-r--r--drivers/iio/accel/dmard06.c4
-rw-r--r--drivers/iio/accel/dmard09.c4
-rw-r--r--drivers/iio/accel/dmard10.c4
-rw-r--r--drivers/iio/accel/fxls8962af-core.c1
-rw-r--r--drivers/iio/accel/kxcjk-1013.c4
-rw-r--r--drivers/iio/accel/kxsd9.c3
-rw-r--r--drivers/iio/accel/mc3230.c4
-rw-r--r--drivers/iio/accel/mma7660.c4
-rw-r--r--drivers/iio/accel/mma8452.c7
-rw-r--r--drivers/iio/accel/mma9551_core.c5
-rw-r--r--drivers/iio/accel/msa311.c16
-rw-r--r--drivers/iio/accel/stk8312.c4
-rw-r--r--drivers/iio/accel/stk8ba50.c4
-rw-r--r--drivers/iio/adc/88pm886-gpadc.c393
-rw-r--r--drivers/iio/adc/Kconfig54
-rw-r--r--drivers/iio/adc/Makefile4
-rw-r--r--drivers/iio/adc/ab8500-gpadc.c1
-rw-r--r--drivers/iio/adc/ad4130.c3
-rw-r--r--drivers/iio/adc/ad7124.c563
-rw-r--r--drivers/iio/adc/ad7173.c221
-rw-r--r--drivers/iio/adc/ad7476.c461
-rw-r--r--drivers/iio/adc/ad7768-1.c37
-rw-r--r--drivers/iio/adc/ad7779.c192
-rw-r--r--drivers/iio/adc/ad7949.c4
-rw-r--r--drivers/iio/adc/ad799x.c30
-rw-r--r--drivers/iio/adc/ade9000.c1799
-rw-r--r--drivers/iio/adc/adi-axi-adc.c1
-rw-r--r--drivers/iio/adc/at91-sama5d2_adc.c13
-rw-r--r--drivers/iio/adc/bcm_iproc_adc.c4
-rw-r--r--drivers/iio/adc/cpcap-adc.c6
-rw-r--r--drivers/iio/adc/da9150-gpadc.c5
-rw-r--r--drivers/iio/adc/dln2-adc.c9
-rw-r--r--drivers/iio/adc/exynos_adc.c286
-rw-r--r--drivers/iio/adc/hx711.c2
-rw-r--r--drivers/iio/adc/imx7d_adc.c4
-rw-r--r--drivers/iio/adc/imx8qxp-adc.c6
-rw-r--r--drivers/iio/adc/imx93_adc.c26
-rw-r--r--drivers/iio/adc/intel_dc_ti_adc.c328
-rw-r--r--drivers/iio/adc/mcp3564.c2
-rw-r--r--drivers/iio/adc/meson_saradc.c2
-rw-r--r--drivers/iio/adc/mt6577_auxadc.c3
-rw-r--r--drivers/iio/adc/mxs-lradc-adc.c4
-rw-r--r--drivers/iio/adc/pac1921.c3
-rw-r--r--drivers/iio/adc/pac1934.c31
-rw-r--r--drivers/iio/adc/palmas_gpadc.c4
-rw-r--r--drivers/iio/adc/rcar-gyroadc.c8
-rw-r--r--drivers/iio/adc/rn5t618-adc.c4
-rw-r--r--drivers/iio/adc/rockchip_saradc.c6
-rw-r--r--drivers/iio/adc/rohm-bd79112.c556
-rw-r--r--drivers/iio/adc/rzg2l_adc.c2
-rw-r--r--drivers/iio/adc/spear_adc.c12
-rw-r--r--drivers/iio/adc/stm32-adc-core.c1
-rw-r--r--drivers/iio/adc/stm32-adc.c7
-rw-r--r--drivers/iio/adc/stm32-dfsdm-adc.c4
-rw-r--r--drivers/iio/adc/stmpe-adc.c4
-rw-r--r--drivers/iio/adc/sun4i-gpadc-iio.c3
-rw-r--r--drivers/iio/adc/ti-adc081c.c40
-rw-r--r--drivers/iio/adc/ti-adc084s021.c4
-rw-r--r--drivers/iio/adc/ti-adc12138.c30
-rw-r--r--drivers/iio/adc/ti-adc128s052.c132
-rw-r--r--drivers/iio/adc/ti-ads1015.c6
-rw-r--r--drivers/iio/adc/ti-ads1100.c1
-rw-r--r--drivers/iio/adc/ti-ads1119.c11
-rw-r--r--drivers/iio/adc/ti-ads131e08.c8
-rw-r--r--drivers/iio/adc/ti-ads7924.c9
-rw-r--r--drivers/iio/adc/ti-tsc2046.c6
-rw-r--r--drivers/iio/adc/ti_am335x_adc.c5
-rw-r--r--drivers/iio/adc/twl4030-madc.c4
-rw-r--r--drivers/iio/adc/vf610_adc.c2
-rw-r--r--drivers/iio/adc/viperboard_adc.c4
-rw-r--r--drivers/iio/adc/xilinx-ams.c47
-rw-r--r--drivers/iio/buffer/industrialio-buffer-cb.c1
-rw-r--r--drivers/iio/chemical/atlas-sensor.c2
-rw-r--r--drivers/iio/chemical/bme680_core.c3
-rw-r--r--drivers/iio/chemical/ens160_core.c3
-rw-r--r--drivers/iio/chemical/scd30_core.c2
-rw-r--r--drivers/iio/common/hid-sensors/hid-sensor-trigger.c1
-rw-r--r--drivers/iio/common/scmi_sensors/scmi_iio.c10
-rw-r--r--drivers/iio/dac/ad5360.c2
-rw-r--r--drivers/iio/dac/ad5380.c4
-rw-r--r--drivers/iio/dac/ad5421.c2
-rw-r--r--drivers/iio/dac/ad5764.c4
-rw-r--r--drivers/iio/dac/ad5791.c4
-rw-r--r--drivers/iio/dac/ds4424.c4
-rw-r--r--drivers/iio/dac/stm32-dac.c19
-rw-r--r--drivers/iio/dac/ti-dac7311.c4
-rw-r--r--drivers/iio/frequency/adf4350.c23
-rw-r--r--drivers/iio/gyro/bmg160_core.c4
-rw-r--r--drivers/iio/gyro/fxas21002c_core.c2
-rw-r--r--drivers/iio/gyro/mpu3050-core.c3
-rw-r--r--drivers/iio/gyro/mpu3050-i2c.c1
-rw-r--r--drivers/iio/health/afe4403.c48
-rw-r--r--drivers/iio/health/afe4404.c48
-rw-r--r--drivers/iio/humidity/am2315.c4
-rw-r--r--drivers/iio/humidity/dht11.c4
-rw-r--r--drivers/iio/imu/adis16475.c1
-rw-r--r--drivers/iio/imu/bmi270/bmi270_i2c.c2
-rw-r--r--drivers/iio/imu/bmi323/bmi323_core.c3
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600.h1
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c29
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c65
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_core.c117
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c29
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c1
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_core.c6
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c1
-rw-r--r--drivers/iio/imu/kmx61.c6
-rw-r--r--drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c14
-rw-r--r--drivers/iio/industrialio-core.c11
-rw-r--r--drivers/iio/inkern.c81
-rw-r--r--drivers/iio/light/Kconfig13
-rw-r--r--drivers/iio/light/Makefile1
-rw-r--r--drivers/iio/light/acpi-als.c19
-rw-r--r--drivers/iio/light/adjd_s311.c12
-rw-r--r--drivers/iio/light/al3000a.c2
-rw-r--r--drivers/iio/light/apds9306.c4
-rw-r--r--drivers/iio/light/apds9960.c1
-rw-r--r--drivers/iio/light/bh1745.c7
-rw-r--r--drivers/iio/light/bh1780.c1
-rw-r--r--drivers/iio/light/gp2ap002.c2
-rw-r--r--drivers/iio/light/hid-sensor-als.c5
-rw-r--r--drivers/iio/light/isl29028.c11
-rw-r--r--drivers/iio/light/isl29125.c14
-rw-r--r--drivers/iio/light/ltr390.c197
-rw-r--r--drivers/iio/light/ltr501.c4
-rw-r--r--drivers/iio/light/ltrf216a.c1
-rw-r--r--drivers/iio/light/max44000.c18
-rw-r--r--drivers/iio/light/opt4001.c3
-rw-r--r--drivers/iio/light/opt4060.c7
-rw-r--r--drivers/iio/light/pa12203001.c11
-rw-r--r--drivers/iio/light/rohm-bu27034.c3
-rw-r--r--drivers/iio/light/rpr0521.c10
-rw-r--r--drivers/iio/light/si1145.c5
-rw-r--r--drivers/iio/light/st_uvis25.h5
-rw-r--r--drivers/iio/light/st_uvis25_core.c12
-rw-r--r--drivers/iio/light/stk3310.c4
-rw-r--r--drivers/iio/light/tcs3414.c15
-rw-r--r--drivers/iio/light/tcs3472.c14
-rw-r--r--drivers/iio/light/tsl2583.c12
-rw-r--r--drivers/iio/light/tsl2591.c2
-rw-r--r--drivers/iio/light/us5182d.c12
-rw-r--r--drivers/iio/light/vcnl4000.c22
-rw-r--r--drivers/iio/light/vcnl4035.c11
-rw-r--r--drivers/iio/light/veml6030.c2
-rw-r--r--drivers/iio/light/veml6040.c3
-rw-r--r--drivers/iio/light/veml6046x00.c1030
-rw-r--r--drivers/iio/light/vl6180.c16
-rw-r--r--drivers/iio/magnetometer/Kconfig15
-rw-r--r--drivers/iio/magnetometer/Makefile2
-rw-r--r--drivers/iio/magnetometer/ak8974.c2
-rw-r--r--drivers/iio/magnetometer/ak8975.c1
-rw-r--r--drivers/iio/magnetometer/als31300.c5
-rw-r--r--drivers/iio/magnetometer/bmc150_magn.c13
-rw-r--r--drivers/iio/magnetometer/tlv493d.c526
-rw-r--r--drivers/iio/magnetometer/tmag5273.c5
-rw-r--r--drivers/iio/magnetometer/yamaha-yas530.c2
-rw-r--r--drivers/iio/potentiostat/lmp91000.c4
-rw-r--r--drivers/iio/pressure/bmp280-core.c8
-rw-r--r--drivers/iio/pressure/dlhl60d.c4
-rw-r--r--drivers/iio/pressure/icp10100.c1
-rw-r--r--drivers/iio/pressure/mpl115.c2
-rw-r--r--drivers/iio/pressure/zpa2326.c2
-rw-r--r--drivers/iio/proximity/d3323aa.c3
-rw-r--r--drivers/iio/proximity/hx9023s.c3
-rw-r--r--drivers/iio/proximity/irsd200.c6
-rw-r--r--drivers/iio/proximity/mb1232.c15
-rw-r--r--drivers/iio/proximity/ping.c4
-rw-r--r--drivers/iio/proximity/pulsedlight-lidar-lite-v2.c16
-rw-r--r--drivers/iio/proximity/srf04.c8
-rw-r--r--drivers/iio/proximity/srf08.c18
-rw-r--r--drivers/iio/proximity/sx9500.c27
-rw-r--r--drivers/iio/proximity/vl53l0x-i2c.c27
-rw-r--r--drivers/iio/temperature/Kconfig8
-rw-r--r--drivers/iio/temperature/mcp9600.c151
-rw-r--r--drivers/iio/temperature/mlx90614.c1
-rw-r--r--drivers/iio/temperature/mlx90632.c5
-rw-r--r--drivers/iio/temperature/mlx90635.c9
-rw-r--r--drivers/iio/test/Kconfig12
-rw-r--r--drivers/iio/test/Makefile1
-rw-r--r--drivers/iio/test/iio-test-multiply.c212
-rw-r--r--drivers/input/input-mt.c14
-rw-r--r--drivers/interconnect/core.c2
-rw-r--r--drivers/interconnect/qcom/Kconfig9
-rw-r--r--drivers/interconnect/qcom/Makefile2
-rw-r--r--drivers/interconnect/qcom/glymur.c2543
-rw-r--r--drivers/interconnect/qcom/icc-rpmh.h2
-rw-r--r--drivers/media/usb/uvc/uvc_driver.c4
-rw-r--r--drivers/media/usb/uvc/uvc_video.c24
-rw-r--r--drivers/media/usb/uvc/uvcvideo.h4
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/ad525x_dpot.c7
-rw-r--r--drivers/misc/amd-sbi/Kconfig1
-rw-r--r--drivers/misc/apds990x.c1
-rw-r--r--drivers/misc/cardreader/rts5227.c13
-rw-r--r--drivers/misc/cardreader/rts5228.c12
-rw-r--r--drivers/misc/cardreader/rts5249.c16
-rw-r--r--drivers/misc/cardreader/rts5264.c20
-rw-r--r--drivers/misc/cardreader/rts5264.h1
-rw-r--r--drivers/misc/cardreader/rtsx_pcr.h2
-rw-r--r--drivers/misc/dw-xdata-pcie.c5
-rw-r--r--drivers/misc/eeprom/Kconfig18
-rw-r--r--drivers/misc/eeprom/Makefile1
-rw-r--r--drivers/misc/eeprom/at25.c67
-rw-r--r--drivers/misc/eeprom/m24lr.c606
-rw-r--r--drivers/misc/fastrpc.c54
-rw-r--r--drivers/misc/genwqe/card_ddcb.c2
-rw-r--r--drivers/misc/hisi_hikey_usb.c3
-rw-r--r--drivers/misc/ibmasm/ibmasmfs.c12
-rw-r--r--drivers/misc/lis3lv02d/Kconfig4
-rw-r--r--drivers/misc/mei/bus-fixup.c6
-rw-r--r--drivers/misc/mei/bus.c26
-rw-r--r--drivers/misc/mei/client.c82
-rw-r--r--drivers/misc/mei/client.h6
-rw-r--r--drivers/misc/mei/dma-ring.c8
-rw-r--r--drivers/misc/mei/gsc-me.c20
-rw-r--r--drivers/misc/mei/hbm.c121
-rw-r--r--drivers/misc/mei/hw-me.c153
-rw-r--r--drivers/misc/mei/hw-txe.c60
-rw-r--r--drivers/misc/mei/hw.h2
-rw-r--r--drivers/misc/mei/init.c66
-rw-r--r--drivers/misc/mei/interrupt.c45
-rw-r--r--drivers/misc/mei/main.c137
-rw-r--r--drivers/misc/mei/mei_dev.h24
-rw-r--r--drivers/misc/mei/pci-me.c20
-rw-r--r--drivers/misc/mei/pci-txe.c4
-rw-r--r--drivers/misc/mei/platform-vsc.c26
-rw-r--r--drivers/mtd/chips/cfi_probe.c2
-rw-r--r--drivers/mtd/chips/jedec_probe.c4
-rw-r--r--drivers/mtd/ftl.c2
-rw-r--r--drivers/mtd/hyperbus/hbmc-am654.c1
-rw-r--r--drivers/mtd/lpddr/lpddr_cmds.c10
-rw-r--r--drivers/mtd/lpddr/qinfo_probe.c4
-rw-r--r--drivers/mtd/mtdcore.c61
-rw-r--r--drivers/mtd/mtdoops.c5
-rw-r--r--drivers/mtd/mtdswap.c4
-rw-r--r--drivers/mtd/nand/Kconfig8
-rw-r--r--drivers/mtd/nand/Makefile1
-rw-r--r--drivers/mtd/nand/core.c131
-rw-r--r--drivers/mtd/nand/ecc-mxic.c14
-rw-r--r--drivers/mtd/nand/ecc-realtek.c464
-rw-r--r--drivers/mtd/nand/ecc.c2
-rw-r--r--drivers/mtd/nand/onenand/onenand_omap2.c1
-rw-r--r--drivers/mtd/nand/qpic_common.c6
-rw-r--r--drivers/mtd/nand/raw/Kconfig34
-rw-r--r--drivers/mtd/nand/raw/Makefile3
-rw-r--r--drivers/mtd/nand/raw/atmel/nand-controller.c17
-rw-r--r--drivers/mtd/nand/raw/atmel/pmecc.c1
-rw-r--r--drivers/mtd/nand/raw/fsmc_nand.c6
-rw-r--r--drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c14
-rw-r--r--drivers/mtd/nand/raw/loongson-nand-controller.c1024
-rw-r--r--drivers/mtd/nand/raw/loongson1-nand-controller.c836
-rw-r--r--drivers/mtd/nand/raw/nand_base.c131
-rw-r--r--drivers/mtd/nand/raw/nandsim.c7
-rw-r--r--drivers/mtd/nand/raw/omap2.c27
-rw-r--r--drivers/mtd/nand/raw/pl35x-nand-controller.c3
-rw-r--r--drivers/mtd/nand/raw/rockchip-nand-controller.c1
-rw-r--r--drivers/mtd/nand/raw/s3c2410.c1230
-rw-r--r--drivers/mtd/nand/raw/stm32_fmc2_nand.c1
-rw-r--r--drivers/mtd/nand/raw/sunxi_nand.c1
-rw-r--r--drivers/mtd/nand/spi/Makefile2
-rw-r--r--drivers/mtd/nand/spi/core.c75
-rw-r--r--drivers/mtd/nand/spi/fmsh.c74
-rw-r--r--drivers/mtd/nand/spi/gigadevice.c107
-rw-r--r--drivers/mtd/rfd_ftl.c4
-rw-r--r--drivers/mtd/spi-nor/core.c145
-rw-r--r--drivers/nvmem/Kconfig21
-rw-r--r--drivers/nvmem/Makefile4
-rw-r--r--drivers/nvmem/an8855-efuse.c68
-rw-r--r--drivers/nvmem/s32g-ocotp-nvmem.c100
-rw-r--r--drivers/parisc/eisa_eeprom.c2
-rw-r--r--drivers/pci/quirks.c2
-rw-r--r--drivers/peci/controller/peci-npcm.c1
-rw-r--r--drivers/platform/arm64/Kconfig20
-rw-r--r--drivers/platform/arm64/Makefile1
-rw-r--r--drivers/platform/arm64/lenovo-thinkpad-t14s.c616
-rw-r--r--drivers/platform/x86/Kconfig12
-rw-r--r--drivers/platform/x86/Makefile1
-rw-r--r--drivers/platform/x86/amd/hsmp/acpi.c4
-rw-r--r--drivers/platform/x86/amd/hsmp/plat.c4
-rw-r--r--drivers/platform/x86/amd/pmf/acpi.c87
-rw-r--r--drivers/platform/x86/amd/pmf/pmf.h77
-rw-r--r--drivers/platform/x86/amd/pmf/spc.c80
-rw-r--r--drivers/platform/x86/amd/pmf/sps.c2
-rw-r--r--drivers/platform/x86/amd/pmf/tee-if.c22
-rw-r--r--drivers/platform/x86/barco-p50-gpio.c104
-rw-r--r--drivers/platform/x86/dell/dell_rbu.c8
-rw-r--r--drivers/platform/x86/intel/int3472/discrete.c58
-rw-r--r--drivers/platform/x86/intel/pmc/Makefile2
-rw-r--r--drivers/platform/x86/intel/pmc/arl.c4
-rw-r--r--drivers/platform/x86/intel/pmc/core.c185
-rw-r--r--drivers/platform/x86/intel/pmc/core.h27
-rw-r--r--drivers/platform/x86/intel/pmc/lnl.c18
-rw-r--r--drivers/platform/x86/intel/pmc/mtl.c2
-rw-r--r--drivers/platform/x86/intel/pmc/ptl.c37
-rw-r--r--drivers/platform/x86/intel/pmc/ssram_telemetry.c1
-rw-r--r--drivers/platform/x86/intel/pmc/tgl.c4
-rw-r--r--drivers/platform/x86/intel/pmc/wcl.c486
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c76
-rw-r--r--drivers/platform/x86/lenovo/think-lmi.c94
-rw-r--r--drivers/platform/x86/lenovo/think-lmi.h14
-rw-r--r--drivers/platform/x86/lenovo/wmi-capdata01.c2
-rw-r--r--drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c5
-rw-r--r--drivers/platform/x86/meraki-mx100.c404
-rw-r--r--drivers/platform/x86/pcengines-apuv2.c192
-rw-r--r--drivers/platform/x86/portwell-ec.c194
-rw-r--r--drivers/platform/x86/quickstart.c10
-rw-r--r--drivers/platform/x86/redmi-wmi.c130
-rw-r--r--drivers/platform/x86/x86-android-tablets/Makefile2
-rw-r--r--drivers/platform/x86/x86-android-tablets/acer.c247
-rw-r--r--drivers/platform/x86/x86-android-tablets/asus.c108
-rw-r--r--drivers/platform/x86/x86-android-tablets/core.c121
-rw-r--r--drivers/platform/x86/x86-android-tablets/dmi.c12
-rw-r--r--drivers/platform/x86/x86-android-tablets/lenovo.c291
-rw-r--r--drivers/platform/x86/x86-android-tablets/other.c334
-rw-r--r--drivers/platform/x86/x86-android-tablets/shared-psy-info.c34
-rw-r--r--drivers/platform/x86/x86-android-tablets/shared-psy-info.h8
-rw-r--r--drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c2
-rw-r--r--drivers/platform/x86/x86-android-tablets/x86-android-tablets.h28
-rw-r--r--drivers/platform/x86/xiaomi-wmi.c10
-rw-r--r--drivers/pps/kapi.c5
-rw-r--r--drivers/pps/pps.c5
-rw-r--r--drivers/remoteproc/da8xx_remoteproc.c57
-rw-r--r--drivers/remoteproc/imx_dsp_rproc.c45
-rw-r--r--drivers/remoteproc/imx_rproc.c449
-rw-r--r--drivers/remoteproc/imx_rproc.h7
-rw-r--r--drivers/remoteproc/keystone_remoteproc.c95
-rw-r--r--drivers/remoteproc/pru_rproc.c3
-rw-r--r--drivers/remoteproc/qcom_q6v5.c8
-rw-r--r--drivers/remoteproc/qcom_q6v5_mss.c11
-rw-r--r--drivers/remoteproc/qcom_q6v5_pas.c32
-rw-r--r--drivers/remoteproc/ti_k3_common.c21
-rw-r--r--drivers/remoteproc/ti_k3_dsp_remoteproc.c2
-rw-r--r--drivers/remoteproc/ti_k3_r5_remoteproc.c2
-rw-r--r--drivers/remoteproc/wkup_m3_rproc.c69
-rw-r--r--drivers/rpmsg/qcom_glink_native.c2
-rw-r--r--drivers/rpmsg/qcom_smd.c4
-rw-r--r--drivers/rpmsg/rpmsg_char.c3
-rw-r--r--drivers/rpmsg/rpmsg_core.c5
-rw-r--r--drivers/s390/char/con3270.c18
-rw-r--r--drivers/siox/siox-bus-gpio.c3
-rw-r--r--drivers/slimbus/Kconfig7
-rw-r--r--drivers/slimbus/Makefile3
-rw-r--r--drivers/slimbus/messaging.c4
-rw-r--r--drivers/slimbus/qcom-ctrl.c735
-rw-r--r--drivers/staging/axis-fifo/axis-fifo.c37
-rw-r--r--drivers/staging/gpib/agilent_82357a/agilent_82357a.c18
-rw-r--r--drivers/staging/gpib/agilent_82357a/agilent_82357a.h10
-rw-r--r--drivers/staging/gpib/cb7210/cb7210.h4
-rw-r--r--drivers/staging/gpib/cec/cec_gpib.c2
-rw-r--r--drivers/staging/gpib/common/gpib_os.c2
-rw-r--r--drivers/staging/gpib/common/iblib.c2
-rw-r--r--drivers/staging/gpib/eastwood/fluke_gpib.c2
-rw-r--r--drivers/staging/gpib/fmh_gpib/fmh_gpib.c2
-rw-r--r--drivers/staging/gpib/gpio/gpib_bitbang.c16
-rw-r--r--drivers/staging/gpib/hp_82341/hp_82341.c12
-rw-r--r--drivers/staging/gpib/hp_82341/hp_82341.h40
-rw-r--r--drivers/staging/gpib/include/amccs5933.h4
-rw-r--r--drivers/staging/gpib/include/gpib_types.h3
-rw-r--r--drivers/staging/gpib/include/nec7210.h26
-rw-r--r--drivers/staging/gpib/include/nec7210_registers.h4
-rw-r--r--drivers/staging/gpib/include/plx9050.h8
-rw-r--r--drivers/staging/gpib/include/tms9914.h90
-rw-r--r--drivers/staging/gpib/include/tnt4882_registers.h22
-rw-r--r--drivers/staging/gpib/ines/ines.h12
-rw-r--r--drivers/staging/gpib/ines/ines_gpib.c4
-rw-r--r--drivers/staging/gpib/nec7210/nec7210.c6
-rw-r--r--drivers/staging/gpib/ni_usb/ni_usb_gpib.c10
-rw-r--r--drivers/staging/gpib/ni_usb/ni_usb_gpib.h10
-rw-r--r--drivers/staging/gpib/pc2/pc2_gpib.c4
-rw-r--r--drivers/staging/gpib/tms9914/tms9914.c10
-rw-r--r--drivers/staging/gpib/tnt4882/mite.h10
-rw-r--r--drivers/staging/gpib/tnt4882/tnt4882_gpib.c5
-rw-r--r--drivers/staging/iio/adc/ad7816.c2
-rw-r--r--drivers/staging/octeon/ethernet-tx.c40
-rw-r--r--drivers/staging/octeon/octeon-stubs.h134
-rw-r--r--drivers/staging/rtl8723bs/Makefile2
-rw-r--r--drivers/staging/rtl8723bs/core/rtw_ap.c8
-rw-r--r--drivers/staging/rtl8723bs/core/rtw_efuse.c169
-rw-r--r--drivers/staging/rtl8723bs/core/rtw_mlme.c231
-rw-r--r--drivers/staging/rtl8723bs/core/rtw_mlme_ext.c130
-rw-r--r--drivers/staging/rtl8723bs/core/rtw_pwrctrl.c10
-rw-r--r--drivers/staging/rtl8723bs/core/rtw_recv.c194
-rw-r--r--drivers/staging/rtl8723bs/core/rtw_security.c78
-rw-r--r--drivers/staging/rtl8723bs/core/rtw_sta_mgt.c6
-rw-r--r--drivers/staging/rtl8723bs/core/rtw_wlan_util.c60
-rw-r--r--drivers/staging/rtl8723bs/core/rtw_xmit.c2
-rw-r--r--drivers/staging/rtl8723bs/hal/hal_com_phycfg.c5
-rw-r--r--drivers/staging/rtl8723bs/hal/hal_pwr_seq.c2
-rw-r--r--drivers/staging/rtl8723bs/hal/odm.c152
-rw-r--r--drivers/staging/rtl8723bs/hal/odm.h6
-rw-r--r--drivers/staging/rtl8723bs/hal/rtl8723b_hal_init.c368
-rw-r--r--drivers/staging/rtl8723bs/hal/rtl8723bs_recv.c6
-rw-r--r--drivers/staging/rtl8723bs/hal/rtl8723bs_xmit.c2
-rw-r--r--drivers/staging/rtl8723bs/hal/sdio_halinit.c1
-rw-r--r--drivers/staging/rtl8723bs/include/basic_types.h41
-rw-r--r--drivers/staging/rtl8723bs/include/drv_types.h2
-rw-r--r--drivers/staging/rtl8723bs/include/hal_intf.h7
-rw-r--r--drivers/staging/rtl8723bs/include/mlme_osdep.h19
-rw-r--r--drivers/staging/rtl8723bs/include/recv_osdep.h40
-rw-r--r--drivers/staging/rtl8723bs/include/rtl8723b_hal.h2
-rw-r--r--drivers/staging/rtl8723bs/include/rtw_efuse.h15
-rw-r--r--drivers/staging/rtl8723bs/include/rtw_mlme.h1
-rw-r--r--drivers/staging/rtl8723bs/include/rtw_mlme_ext.h2
-rw-r--r--drivers/staging/rtl8723bs/include/rtw_recv.h4
-rw-r--r--drivers/staging/rtl8723bs/os_dep/mlme_linux.c179
-rw-r--r--drivers/staging/rtl8723bs/os_dep/recv_linux.c225
-rw-r--r--drivers/staging/sm750fb/sm750.h6
-rw-r--r--drivers/staging/sm750fb/sm750_accel.c8
-rw-r--r--drivers/staging/sm750fb/sm750_hw.c4
-rw-r--r--drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h2
-rw-r--r--drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c2
-rw-r--r--drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h2
-rw-r--r--drivers/thunderbolt/Kconfig4
-rw-r--r--drivers/thunderbolt/acpi.c28
-rw-r--r--drivers/thunderbolt/cap.c49
-rw-r--r--drivers/thunderbolt/clx.c12
-rw-r--r--drivers/thunderbolt/ctl.c33
-rw-r--r--drivers/thunderbolt/ctl.h1
-rw-r--r--drivers/thunderbolt/debugfs.c3
-rw-r--r--drivers/thunderbolt/dma_port.c21
-rw-r--r--drivers/thunderbolt/domain.c73
-rw-r--r--drivers/thunderbolt/eeprom.c4
-rw-r--r--drivers/thunderbolt/lc.c58
-rw-r--r--drivers/thunderbolt/nhi.c20
-rw-r--r--drivers/thunderbolt/nhi_regs.h6
-rw-r--r--drivers/thunderbolt/nvm.c42
-rw-r--r--drivers/thunderbolt/path.c14
-rw-r--r--drivers/thunderbolt/property.c38
-rw-r--r--drivers/thunderbolt/retimer.c7
-rw-r--r--drivers/thunderbolt/switch.c140
-rw-r--r--drivers/thunderbolt/tb.c32
-rw-r--r--drivers/thunderbolt/tb.h45
-rw-r--r--drivers/thunderbolt/tmu.c16
-rw-r--r--drivers/thunderbolt/tunnel.c90
-rw-r--r--drivers/thunderbolt/tunnel.h9
-rw-r--r--drivers/thunderbolt/usb4.c346
-rw-r--r--drivers/thunderbolt/usb4_port.c7
-rw-r--r--drivers/thunderbolt/xdomain.c53
-rw-r--r--drivers/tty/hvc/hvc_console.c2
-rw-r--r--drivers/tty/mxser.c259
-rw-r--r--drivers/tty/n_gsm.c25
-rw-r--r--drivers/tty/serdev/core.c11
-rw-r--r--drivers/tty/serial/8250/8250.h5
-rw-r--r--drivers/tty/serial/8250/8250_core.c93
-rw-r--r--drivers/tty/serial/8250/8250_omap.c181
-rw-r--r--drivers/tty/serial/8250/8250_platform.c87
-rw-r--r--drivers/tty/serial/8250/8250_port.c298
-rw-r--r--drivers/tty/serial/8250/8250_rsa.c7
-rw-r--r--drivers/tty/serial/Kconfig12
-rw-r--r--drivers/tty/serial/ip22zilog.c352
-rw-r--r--drivers/tty/serial/max3100.c2
-rw-r--r--drivers/tty/serial/max310x.c28
-rw-r--r--drivers/tty/serial/msm_serial.c2
-rw-r--r--drivers/tty/serial/mvebu-uart.c10
-rw-r--r--drivers/tty/serial/qcom_geni_serial.c147
-rw-r--r--drivers/tty/serial/sc16is7xx.c2
-rw-r--r--drivers/tty/serial/serial_core.c143
-rw-r--r--drivers/tty/sysrq.c3
-rw-r--r--drivers/tty/tty_port.c168
-rw-r--r--drivers/tty/vt/consolemap.c116
-rw-r--r--drivers/tty/vt/selection.c20
-rw-r--r--drivers/tty/vt/vc_screen.c74
-rw-r--r--drivers/tty/vt/vt.c247
-rw-r--r--drivers/tty/vt/vt_ioctl.c194
-rw-r--r--drivers/uio/uio_aec.c2
-rw-r--r--drivers/uio/uio_cif.c2
-rw-r--r--drivers/uio/uio_dmem_genirq.c23
-rw-r--r--drivers/uio/uio_hv_generic.c7
-rw-r--r--drivers/uio/uio_netx.c2
-rw-r--r--drivers/uio/uio_pdrv_genirq.c24
-rw-r--r--drivers/uio/uio_sercos3.c2
-rw-r--r--drivers/usb/cdns3/cdns3-trace.h61
-rw-r--r--drivers/usb/cdns3/cdnsp-gadget.c8
-rw-r--r--drivers/usb/cdns3/cdnsp-pci.c5
-rw-r--r--drivers/usb/cdns3/cdnsp-trace.h25
-rw-r--r--drivers/usb/class/usblp.c3
-rw-r--r--drivers/usb/core/Makefile1
-rw-r--r--drivers/usb/core/config.c4
-rw-r--r--drivers/usb/core/driver.c58
-rw-r--r--drivers/usb/core/generic.c2
-rw-r--r--drivers/usb/core/offload.c136
-rw-r--r--drivers/usb/core/urb.c14
-rw-r--r--drivers/usb/core/usb.c51
-rw-r--r--drivers/usb/dwc2/params.c26
-rw-r--r--drivers/usb/dwc3/Kconfig11
-rw-r--r--drivers/usb/dwc3/Makefile1
-rw-r--r--drivers/usb/dwc3/core.c2
-rw-r--r--drivers/usb/dwc3/core.h26
-rw-r--r--drivers/usb/dwc3/debug.h18
-rw-r--r--drivers/usb/dwc3/debugfs.c12
-rw-r--r--drivers/usb/dwc3/drd.c1
-rw-r--r--drivers/usb/dwc3/dwc3-generic-plat.c166
-rw-r--r--drivers/usb/dwc3/dwc3-qcom.c174
-rw-r--r--drivers/usb/dwc3/gadget.c1
-rw-r--r--drivers/usb/dwc3/trace.h17
-rw-r--r--drivers/usb/gadget/configfs.c2
-rw-r--r--drivers/usb/gadget/function/f_acm.c42
-rw-r--r--drivers/usb/gadget/function/f_ecm.c48
-rw-r--r--drivers/usb/gadget/function/f_fs.c8
-rw-r--r--drivers/usb/gadget/function/f_hid.c4
-rw-r--r--drivers/usb/gadget/function/f_ncm.c81
-rw-r--r--drivers/usb/gadget/function/f_rndis.c85
-rw-r--r--drivers/usb/gadget/udc/cdns2/cdns2-trace.h69
-rw-r--r--drivers/usb/gadget/udc/core.c4
-rw-r--r--drivers/usb/gadget/udc/tegra-xudc.c3
-rw-r--r--drivers/usb/gadget/udc/trace.h5
-rw-r--r--drivers/usb/host/Kconfig2
-rw-r--r--drivers/usb/host/max3421-hcd.c2
-rw-r--r--drivers/usb/host/ohci-s3c2410.c8
-rw-r--r--drivers/usb/host/xhci-caps.h2
-rw-r--r--drivers/usb/host/xhci-mem.c85
-rw-r--r--drivers/usb/host/xhci-pci.c42
-rw-r--r--drivers/usb/host/xhci-plat.c57
-rw-r--r--drivers/usb/host/xhci-plat.h2
-rw-r--r--drivers/usb/host/xhci-rcar-regs.h49
-rw-r--r--drivers/usb/host/xhci-rcar.c100
-rw-r--r--drivers/usb/host/xhci-ring.c30
-rw-r--r--drivers/usb/host/xhci-rzg3e-regs.h12
-rw-r--r--drivers/usb/host/xhci-sideband.c36
-rw-r--r--drivers/usb/host/xhci-tegra.c84
-rw-r--r--drivers/usb/host/xhci-trace.h34
-rw-r--r--drivers/usb/host/xhci.c16
-rw-r--r--drivers/usb/host/xhci.h5
-rw-r--r--drivers/usb/misc/Kconfig19
-rw-r--r--drivers/usb/misc/Makefile1
-rw-r--r--drivers/usb/misc/qcom_eud.c36
-rw-r--r--drivers/usb/misc/usb251xb.c108
-rw-r--r--drivers/usb/misc/usbio.c749
-rw-r--r--drivers/usb/mon/mon_bin.c14
-rw-r--r--drivers/usb/musb/musb_dsps.c2
-rw-r--r--drivers/usb/phy/phy-twl6030-usb.c3
-rw-r--r--drivers/usb/renesas_usbhs/common.c4
-rw-r--r--drivers/usb/serial/option.c6
-rw-r--r--drivers/usb/serial/oti6858.c2
-rw-r--r--drivers/usb/storage/realtek_cr.c4
-rw-r--r--drivers/usb/typec/mux/tusb1046.c2
-rw-r--r--drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c2
-rw-r--r--drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c2
-rw-r--r--drivers/usb/typec/tcpm/tcpci.c33
-rw-r--r--drivers/usb/typec/tipd/core.c557
-rw-r--r--drivers/usb/typec/tipd/tps6598x.h5
-rw-r--r--drivers/usb/typec/tipd/trace.h39
-rw-r--r--drivers/usb/typec/ucsi/debugfs.c31
-rw-r--r--drivers/usb/typec/ucsi/ucsi.c17
-rw-r--r--drivers/usb/typec/ucsi/ucsi.h13
-rw-r--r--drivers/usb/typec/ucsi/ucsi_stm32g0.c7
-rw-r--r--drivers/usb/usbip/vhci_hcd.c22
-rw-r--r--drivers/w1/masters/matrox_w1.c10
667 files changed, 37589 insertions, 12273 deletions
diff --git a/drivers/android/Kconfig b/drivers/android/Kconfig
index 5b3b8041f827..e2e402c9d175 100644
--- a/drivers/android/Kconfig
+++ b/drivers/android/Kconfig
@@ -4,6 +4,7 @@ menu "Android"
config ANDROID_BINDER_IPC
bool "Android Binder IPC Driver"
depends on MMU
+ depends on NET
default n
help
Binder is used in Android for both communication between processes,
@@ -13,6 +14,19 @@ 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 && !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
@@ -27,7 +41,7 @@ config ANDROID_BINDERFS
config ANDROID_BINDER_DEVICES
string "Android Binder devices"
- depends on ANDROID_BINDER_IPC
+ depends on ANDROID_BINDER_IPC || ANDROID_BINDER_IPC_RUST
default "binder,hwbinder,vndbinder"
help
Default value for the binder.devices parameter.
diff --git a/drivers/android/Makefile b/drivers/android/Makefile
index c5d47be0276c..e0c650d3898e 100644
--- a/drivers/android/Makefile
+++ b/drivers/android/Makefile
@@ -2,5 +2,6 @@
ccflags-y += -I$(src) # needed for trace events
obj-$(CONFIG_ANDROID_BINDERFS) += binderfs.o
-obj-$(CONFIG_ANDROID_BINDER_IPC) += binder.o binder_alloc.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
index 312b462e349d..8c99ceaa303b 100644
--- a/drivers/android/binder.c
+++ b/drivers/android/binder.c
@@ -74,6 +74,7 @@
#include <linux/cacheflush.h>
+#include "binder_netlink.h"
#include "binder_internal.h"
#include "binder_trace.h"
@@ -2993,6 +2994,69 @@ static void binder_set_txn_from_error(struct binder_transaction *t, int id,
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
+ */
+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,
@@ -3042,6 +3106,32 @@ static void binder_transaction(struct binder_proc *proc,
binder_set_extended_error(&thread->ee, t_debug_id, BR_OK, 0);
binder_inner_proc_unlock(proc);
+ t = kzalloc(sizeof(*t), GFP_KERNEL);
+ 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 = task_euid(proc->tsk);
+ 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;
@@ -3228,24 +3318,13 @@ static void binder_transaction(struct binder_proc *proc,
}
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;
- /* TODO: reuse incoming transaction for reply */
- t = kzalloc(sizeof(*t), GFP_KERNEL);
- if (t == NULL) {
- 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);
-
tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
if (tcomplete == NULL) {
binder_txn_error("%d:%d cannot allocate work for transaction\n",
@@ -3257,9 +3336,6 @@ static void binder_transaction(struct binder_proc *proc,
}
binder_stats_created(BINDER_STAT_TRANSACTION_COMPLETE);
- t->debug_id = t_debug_id;
- t->start_time = t_start_time;
-
if (reply)
binder_debug(BINDER_DEBUG_TRANSACTION,
"%d:%d BC_REPLY %d -> %d:%d, data size %lld-%lld-%lld\n",
@@ -3275,19 +3351,6 @@ static void binder_transaction(struct binder_proc *proc,
(u64)tr->data_size, (u64)tr->offsets_size,
(u64)extra_buffers_size);
- if (!reply && !(tr->flags & TF_ONE_WAY))
- t->from = thread;
- else
- t->from = NULL;
- t->from_pid = proc->pid;
- t->from_tid = thread->pid;
- t->sender_euid = task_euid(proc->tsk);
- t->to_proc = target_proc;
- t->to_thread = target_thread;
- t->code = tr->code;
- t->flags = tr->flags;
- t->priority = task_nice(current);
-
if (target_node && target_node->txn_security_ctx) {
u32 secid;
size_t added_size;
@@ -3680,11 +3743,13 @@ static void binder_transaction(struct binder_proc *proc,
return_error_line = __LINE__;
goto err_copy_data_failed;
}
- if (t->buffer->oneway_spam_suspect)
+ if (t->buffer->oneway_spam_suspect) {
tcomplete->type = BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT;
- else
+ binder_netlink_report(proc, t, tr->data_size,
+ BR_ONEWAY_SPAM_SUSPECT);
+ } else {
tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
- t->work.type = BINDER_WORK_TRANSACTION;
+ }
if (reply) {
binder_enqueue_thread_work(thread, tcomplete);
@@ -3712,7 +3777,6 @@ static void binder_transaction(struct binder_proc *proc,
* the target replies (or there is an error).
*/
binder_enqueue_deferred_thread_work_ilocked(thread, tcomplete);
- t->need_reply = 1;
t->from_parent = thread->transaction_stack;
thread->transaction_stack = t;
binder_inner_proc_unlock(proc);
@@ -3733,8 +3797,11 @@ static void binder_transaction(struct binder_proc *proc,
* process and is put in a pending queue, waiting for the target
* process to be unfrozen.
*/
- if (return_error == BR_TRANSACTION_PENDING_FROZEN)
+ if (return_error == BR_TRANSACTION_PENDING_FROZEN) {
tcomplete->type = BINDER_WORK_TRANSACTION_PENDING;
+ binder_netlink_report(proc, t, tr->data_size,
+ return_error);
+ }
binder_enqueue_thread_work(thread, tcomplete);
if (return_error &&
return_error != BR_TRANSACTION_PENDING_FROZEN)
@@ -3783,9 +3850,6 @@ err_get_secctx_failed:
err_alloc_tcomplete_failed:
if (trace_binder_txn_latency_free_enabled())
binder_txn_latency_free(t);
- kfree(t);
- binder_stats_deleted(BINDER_STAT_TRANSACTION);
-err_alloc_t_failed:
err_bad_todo_list:
err_bad_call_stack:
err_empty_call_stack:
@@ -3796,6 +3860,11 @@ err_invalid_target_handle:
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" :
@@ -6324,13 +6393,13 @@ static void print_binder_transaction_ilocked(struct seq_file *m,
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 r%d elapsed %lldms",
+ "%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->need_reply,
+ t->code, t->flags, t->priority, t->is_async, t->is_reply,
ktime_ms_delta(current_time, t->start_time));
spin_unlock(&t->lock);
@@ -7062,12 +7131,19 @@ static int __init binder_init(void)
}
}
- ret = init_binderfs();
+ 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);
@@ -7088,5 +7164,3 @@ device_initcall(binder_init);
#define CREATE_TRACE_POINTS
#include "binder_trace.h"
-
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/android/binder/Makefile b/drivers/android/binder/Makefile
new file mode 100644
index 000000000000..09eabb527fa0
--- /dev/null
+++ b/drivers/android/binder/Makefile
@@ -0,0 +1,9 @@
+# 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 := \
+ rust_binder_main.o \
+ rust_binderfs.o \
+ rust_binder_events.o \
+ page_range_helper.o
diff --git a/drivers/android/binder/allocation.rs b/drivers/android/binder/allocation.rs
new file mode 100644
index 000000000000..7f65a9c3a0e5
--- /dev/null
+++ b/drivers/android/binder/allocation.rs
@@ -0,0 +1,602 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+use core::mem::{size_of, size_of_val, MaybeUninit};
+use core::ops::Range;
+
+use kernel::{
+ bindings,
+ fs::file::{File, FileDescriptorReservation},
+ prelude::*,
+ sync::{aref::ARef, Arc},
+ transmute::{AsBytes, FromBytes},
+ uaccess::UserSliceReader,
+ uapi,
+};
+
+use crate::{
+ deferred_close::DeferredFdCloser,
+ defs::*,
+ node::{Node, NodeRef},
+ process::Process,
+ DArc,
+};
+
+#[derive(Default)]
+pub(crate) struct AllocationInfo {
+ /// Range within the allocation where we can find the offsets to the object descriptors.
+ pub(crate) offsets: Option<Range<usize>>,
+ /// The target node of the transaction this allocation is associated to.
+ /// Not set for replies.
+ pub(crate) target_node: Option<NodeRef>,
+ /// When this allocation is dropped, call `pending_oneway_finished` on the node.
+ ///
+ /// This is used to serialize oneway transaction on the same node. Binder guarantees that
+ /// oneway transactions to the same node are delivered sequentially in the order they are sent.
+ pub(crate) oneway_node: Option<DArc<Node>>,
+ /// Zero the data in the buffer on free.
+ pub(crate) clear_on_free: bool,
+ /// List of files embedded in this transaction.
+ file_list: FileList,
+}
+
+/// Represents an allocation that the kernel is currently using.
+///
+/// When allocations are idle, the range allocator holds the data related to them.
+///
+/// # Invariants
+///
+/// This allocation corresponds to an allocation in the range allocator, so the relevant pages are
+/// marked in use in the page range.
+pub(crate) struct Allocation {
+ pub(crate) offset: usize,
+ size: usize,
+ pub(crate) ptr: usize,
+ pub(crate) process: Arc<Process>,
+ allocation_info: Option<AllocationInfo>,
+ free_on_drop: bool,
+ pub(crate) oneway_spam_detected: bool,
+ #[allow(dead_code)]
+ pub(crate) debug_id: usize,
+}
+
+impl Allocation {
+ pub(crate) fn new(
+ process: Arc<Process>,
+ debug_id: usize,
+ offset: usize,
+ size: usize,
+ ptr: usize,
+ oneway_spam_detected: bool,
+ ) -> Self {
+ Self {
+ process,
+ offset,
+ size,
+ ptr,
+ debug_id,
+ oneway_spam_detected,
+ allocation_info: None,
+ free_on_drop: true,
+ }
+ }
+
+ fn size_check(&self, offset: usize, size: usize) -> Result {
+ let overflow_fail = offset.checked_add(size).is_none();
+ let cmp_size_fail = offset.wrapping_add(size) > self.size;
+ if overflow_fail || cmp_size_fail {
+ return Err(EFAULT);
+ }
+ Ok(())
+ }
+
+ pub(crate) fn copy_into(
+ &self,
+ reader: &mut UserSliceReader,
+ offset: usize,
+ size: usize,
+ ) -> Result {
+ self.size_check(offset, size)?;
+
+ // SAFETY: While this object exists, the range allocator will keep the range allocated, and
+ // in turn, the pages will be marked as in use.
+ unsafe {
+ self.process
+ .pages
+ .copy_from_user_slice(reader, self.offset + offset, size)
+ }
+ }
+
+ pub(crate) fn read<T: FromBytes>(&self, offset: usize) -> Result<T> {
+ self.size_check(offset, size_of::<T>())?;
+
+ // SAFETY: While this object exists, the range allocator will keep the range allocated, and
+ // in turn, the pages will be marked as in use.
+ unsafe { self.process.pages.read(self.offset + offset) }
+ }
+
+ pub(crate) fn write<T: ?Sized>(&self, offset: usize, obj: &T) -> Result {
+ self.size_check(offset, size_of_val::<T>(obj))?;
+
+ // SAFETY: While this object exists, the range allocator will keep the range allocated, and
+ // in turn, the pages will be marked as in use.
+ unsafe { self.process.pages.write(self.offset + offset, obj) }
+ }
+
+ pub(crate) fn fill_zero(&self) -> Result {
+ // SAFETY: While this object exists, the range allocator will keep the range allocated, and
+ // in turn, the pages will be marked as in use.
+ unsafe { self.process.pages.fill_zero(self.offset, self.size) }
+ }
+
+ pub(crate) fn keep_alive(mut self) {
+ self.process
+ .buffer_make_freeable(self.offset, self.allocation_info.take());
+ self.free_on_drop = false;
+ }
+
+ pub(crate) fn set_info(&mut self, info: AllocationInfo) {
+ self.allocation_info = Some(info);
+ }
+
+ pub(crate) fn get_or_init_info(&mut self) -> &mut AllocationInfo {
+ self.allocation_info.get_or_insert_with(Default::default)
+ }
+
+ pub(crate) fn set_info_offsets(&mut self, offsets: Range<usize>) {
+ self.get_or_init_info().offsets = Some(offsets);
+ }
+
+ pub(crate) fn set_info_oneway_node(&mut self, oneway_node: DArc<Node>) {
+ self.get_or_init_info().oneway_node = Some(oneway_node);
+ }
+
+ pub(crate) fn set_info_clear_on_drop(&mut self) {
+ self.get_or_init_info().clear_on_free = true;
+ }
+
+ pub(crate) fn set_info_target_node(&mut self, target_node: NodeRef) {
+ self.get_or_init_info().target_node = Some(target_node);
+ }
+
+ /// Reserve enough space to push at least `num_fds` fds.
+ pub(crate) fn info_add_fd_reserve(&mut self, num_fds: usize) -> Result {
+ self.get_or_init_info()
+ .file_list
+ .files_to_translate
+ .reserve(num_fds, GFP_KERNEL)?;
+
+ Ok(())
+ }
+
+ pub(crate) fn info_add_fd(
+ &mut self,
+ file: ARef<File>,
+ buffer_offset: usize,
+ close_on_free: bool,
+ ) -> Result {
+ self.get_or_init_info().file_list.files_to_translate.push(
+ FileEntry {
+ file,
+ buffer_offset,
+ close_on_free,
+ },
+ GFP_KERNEL,
+ )?;
+
+ Ok(())
+ }
+
+ pub(crate) fn set_info_close_on_free(&mut self, cof: FdsCloseOnFree) {
+ self.get_or_init_info().file_list.close_on_free = cof.0;
+ }
+
+ pub(crate) fn translate_fds(&mut self) -> Result<TranslatedFds> {
+ let file_list = match self.allocation_info.as_mut() {
+ Some(info) => &mut info.file_list,
+ None => return Ok(TranslatedFds::new()),
+ };
+
+ let files = core::mem::take(&mut file_list.files_to_translate);
+
+ 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)?;
+ for file_info in files {
+ let res = FileDescriptorReservation::get_unused_fd_flags(bindings::O_CLOEXEC)?;
+ let fd = res.reserved_fd();
+ self.write::<u32>(file_info.buffer_offset, &fd)?;
+
+ reservations.push(
+ Reservation {
+ res,
+ file: file_info.file,
+ },
+ GFP_KERNEL,
+ )?;
+ if file_info.close_on_free {
+ close_on_free.push(fd, GFP_KERNEL)?;
+ }
+ }
+
+ Ok(TranslatedFds {
+ reservations,
+ close_on_free: FdsCloseOnFree(close_on_free),
+ })
+ }
+
+ /// Should the looper return to userspace when freeing this allocation?
+ pub(crate) fn looper_need_return_on_free(&self) -> bool {
+ // Closing fds involves pushing task_work for execution when we return to userspace. Hence,
+ // we should return to userspace asap if we are closing fds.
+ match self.allocation_info {
+ Some(ref info) => !info.file_list.close_on_free.is_empty(),
+ None => false,
+ }
+ }
+}
+
+impl Drop for Allocation {
+ fn drop(&mut self) {
+ if !self.free_on_drop {
+ return;
+ }
+
+ if let Some(mut info) = self.allocation_info.take() {
+ if let Some(oneway_node) = info.oneway_node.as_ref() {
+ oneway_node.pending_oneway_finished();
+ }
+
+ info.target_node = None;
+
+ if let Some(offsets) = info.offsets.clone() {
+ let view = AllocationView::new(self, offsets.start);
+ for i in offsets.step_by(size_of::<usize>()) {
+ if view.cleanup_object(i).is_err() {
+ pr_warn!("Error cleaning up object at offset {}\n", i)
+ }
+ }
+ }
+
+ for &fd in &info.file_list.close_on_free {
+ let closer = match DeferredFdCloser::new(GFP_KERNEL) {
+ Ok(closer) => closer,
+ Err(kernel::alloc::AllocError) => {
+ // Ignore allocation failures.
+ break;
+ }
+ };
+
+ // Here, we ignore errors. The operation can fail if the fd is not valid, or if the
+ // method is called from a kthread. However, this is always called from a syscall,
+ // so the latter case cannot happen, and we don't care about the first case.
+ let _ = closer.close_fd(fd);
+ }
+
+ if info.clear_on_free {
+ if let Err(e) = self.fill_zero() {
+ pr_warn!("Failed to clear data on free: {:?}", e);
+ }
+ }
+ }
+
+ self.process.buffer_raw_free(self.ptr);
+ }
+}
+
+/// A wrapper around `Allocation` that is being created.
+///
+/// If the allocation is destroyed while wrapped in this wrapper, then the allocation will be
+/// considered to be part of a failed transaction. Successful transactions avoid that by calling
+/// `success`, which skips the destructor.
+#[repr(transparent)]
+pub(crate) struct NewAllocation(pub(crate) Allocation);
+
+impl NewAllocation {
+ pub(crate) fn success(self) -> Allocation {
+ // This skips the destructor.
+ //
+ // SAFETY: This type is `#[repr(transparent)]`, so the layout matches.
+ unsafe { core::mem::transmute(self) }
+ }
+}
+
+impl core::ops::Deref for NewAllocation {
+ type Target = Allocation;
+ fn deref(&self) -> &Allocation {
+ &self.0
+ }
+}
+
+impl core::ops::DerefMut for NewAllocation {
+ fn deref_mut(&mut self) -> &mut Allocation {
+ &mut self.0
+ }
+}
+
+/// 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
+/// this view, use the `alloc` field of this struct directly.
+pub(crate) struct AllocationView<'a> {
+ pub(crate) alloc: &'a mut Allocation,
+ limit: usize,
+}
+
+impl<'a> AllocationView<'a> {
+ pub(crate) fn new(alloc: &'a mut Allocation, limit: usize) -> Self {
+ AllocationView { alloc, limit }
+ }
+
+ pub(crate) fn read<T: FromBytes>(&self, offset: usize) -> Result<T> {
+ if offset.checked_add(size_of::<T>()).ok_or(EINVAL)? > self.limit {
+ return Err(EINVAL);
+ }
+ self.alloc.read(offset)
+ }
+
+ pub(crate) fn write<T: AsBytes>(&self, offset: usize, obj: &T) -> Result {
+ if offset.checked_add(size_of::<T>()).ok_or(EINVAL)? > self.limit {
+ return Err(EINVAL);
+ }
+ self.alloc.write(offset, obj)
+ }
+
+ pub(crate) fn copy_into(
+ &self,
+ reader: &mut UserSliceReader,
+ offset: usize,
+ size: usize,
+ ) -> Result {
+ if offset.checked_add(size).ok_or(EINVAL)? > self.limit {
+ return Err(EINVAL);
+ }
+ self.alloc.copy_into(reader, offset, size)
+ }
+
+ pub(crate) fn transfer_binder_object(
+ &self,
+ offset: usize,
+ obj: &uapi::flat_binder_object,
+ strong: bool,
+ node_ref: NodeRef,
+ ) -> Result {
+ let mut newobj = FlatBinderObject::default();
+ let node = node_ref.node.clone();
+ if Arc::ptr_eq(&node_ref.node.owner, &self.alloc.process) {
+ // The receiving process is the owner of the node, so send it a binder object (instead
+ // of a handle).
+ let (ptr, cookie) = node.get_id();
+ newobj.hdr.type_ = if strong {
+ BINDER_TYPE_BINDER
+ } else {
+ BINDER_TYPE_WEAK_BINDER
+ };
+ newobj.flags = obj.flags;
+ newobj.__bindgen_anon_1.binder = ptr as _;
+ newobj.cookie = cookie as _;
+ self.write(offset, &newobj)?;
+ // Increment the user ref count on the node. It will be decremented as part of the
+ // destruction of the buffer, when we see a binder or weak-binder object.
+ node.update_refcount(true, 1, strong);
+ } else {
+ // The receiving process is different from the owner, so we need to insert a handle to
+ // the binder object.
+ let handle = self
+ .alloc
+ .process
+ .as_arc_borrow()
+ .insert_or_update_handle(node_ref, false)?;
+ newobj.hdr.type_ = if strong {
+ BINDER_TYPE_HANDLE
+ } else {
+ BINDER_TYPE_WEAK_HANDLE
+ };
+ newobj.flags = obj.flags;
+ newobj.__bindgen_anon_1.handle = handle;
+ if self.write(offset, &newobj).is_err() {
+ // Decrement ref count on the handle we just created.
+ let _ = self
+ .alloc
+ .process
+ .as_arc_borrow()
+ .update_ref(handle, false, strong);
+ return Err(EINVAL);
+ }
+ }
+
+ Ok(())
+ }
+
+ fn cleanup_object(&self, index_offset: usize) -> Result {
+ let offset = self.alloc.read(index_offset)?;
+ let header = self.read::<BinderObjectHeader>(offset)?;
+ match header.type_ {
+ BINDER_TYPE_WEAK_BINDER | BINDER_TYPE_BINDER => {
+ let obj = self.read::<FlatBinderObject>(offset)?;
+ let strong = header.type_ == BINDER_TYPE_BINDER;
+ // SAFETY: The type is `BINDER_TYPE_{WEAK_}BINDER`, so the `binder` field is
+ // populated.
+ let ptr = unsafe { obj.__bindgen_anon_1.binder };
+ let cookie = obj.cookie;
+ self.alloc.process.update_node(ptr, cookie, strong);
+ Ok(())
+ }
+ BINDER_TYPE_WEAK_HANDLE | BINDER_TYPE_HANDLE => {
+ let obj = self.read::<FlatBinderObject>(offset)?;
+ let strong = header.type_ == BINDER_TYPE_HANDLE;
+ // SAFETY: The type is `BINDER_TYPE_{WEAK_}HANDLE`, so the `handle` field is
+ // populated.
+ let handle = unsafe { obj.__bindgen_anon_1.handle };
+ self.alloc
+ .process
+ .as_arc_borrow()
+ .update_ref(handle, false, strong)
+ }
+ _ => Ok(()),
+ }
+ }
+}
+
+/// A binder object as it is serialized.
+///
+/// # Invariants
+///
+/// All bytes must be initialized, and the value of `self.hdr.type_` must be one of the allowed
+/// types.
+#[repr(C)]
+pub(crate) union BinderObject {
+ hdr: uapi::binder_object_header,
+ fbo: uapi::flat_binder_object,
+ fdo: uapi::binder_fd_object,
+ bbo: uapi::binder_buffer_object,
+ fdao: uapi::binder_fd_array_object,
+}
+
+/// A view into a `BinderObject` that can be used in a match statement.
+pub(crate) enum BinderObjectRef<'a> {
+ Binder(&'a mut uapi::flat_binder_object),
+ Handle(&'a mut uapi::flat_binder_object),
+ Fd(&'a mut uapi::binder_fd_object),
+ Ptr(&'a mut uapi::binder_buffer_object),
+ Fda(&'a mut uapi::binder_fd_array_object),
+}
+
+impl BinderObject {
+ pub(crate) fn read_from(reader: &mut UserSliceReader) -> Result<BinderObject> {
+ let object = Self::read_from_inner(|slice| {
+ let read_len = usize::min(slice.len(), reader.len());
+ reader.clone_reader().read_slice(&mut slice[..read_len])?;
+ Ok(())
+ })?;
+
+ // If we used a object type smaller than the largest object size, then we've read more
+ // bytes than we needed to. However, we used `.clone_reader()` to avoid advancing the
+ // original reader. Now, we call `skip` so that the caller's reader is advanced by the
+ // right amount.
+ //
+ // The `skip` call fails if the reader doesn't have `size` bytes available. This could
+ // happen if the type header corresponds to an object type that is larger than the rest of
+ // the reader.
+ //
+ // Any extra bytes beyond the size of the object are inaccessible after this call, so
+ // reading them again from the `reader` later does not result in TOCTOU bugs.
+ reader.skip(object.size())?;
+
+ Ok(object)
+ }
+
+ /// Use the provided reader closure to construct a `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<()>,
+ {
+ let mut obj = MaybeUninit::<BinderObject>::zeroed();
+
+ // SAFETY: The lengths of `BinderObject` and `[u8; size_of::<BinderObject>()]` are equal,
+ // and the byte array has an alignment requirement of one, so the pointer cast is okay.
+ // Additionally, `obj` was initialized to zeros, so the byte array will not be
+ // uninitialized.
+ (reader)(unsafe { &mut *obj.as_mut_ptr().cast() })?;
+
+ // SAFETY: The entire object is initialized, so accessing this field is safe.
+ let type_ = unsafe { obj.assume_init_ref().hdr.type_ };
+ if Self::type_to_size(type_).is_none() {
+ // The value of `obj.hdr_type_` was invalid.
+ return Err(EINVAL);
+ }
+
+ // SAFETY: All bytes are initialized (since we zeroed them at the start) and we checked
+ // that `self.hdr.type_` is one of the allowed types, so the type invariants are satisfied.
+ unsafe { Ok(obj.assume_init()) }
+ }
+
+ pub(crate) fn as_ref(&mut self) -> BinderObjectRef<'_> {
+ use BinderObjectRef::*;
+ // SAFETY: The constructor ensures that all bytes of `self` are initialized, and all
+ // variants of this union accept all initialized bit patterns.
+ unsafe {
+ match self.hdr.type_ {
+ BINDER_TYPE_WEAK_BINDER | BINDER_TYPE_BINDER => Binder(&mut self.fbo),
+ BINDER_TYPE_WEAK_HANDLE | BINDER_TYPE_HANDLE => Handle(&mut self.fbo),
+ BINDER_TYPE_FD => Fd(&mut self.fdo),
+ BINDER_TYPE_PTR => Ptr(&mut self.bbo),
+ BINDER_TYPE_FDA => Fda(&mut self.fdao),
+ // SAFETY: By the type invariant, the value of `self.hdr.type_` cannot have any
+ // other value than the ones checked above.
+ _ => core::hint::unreachable_unchecked(),
+ }
+ }
+ }
+
+ pub(crate) fn size(&self) -> usize {
+ // SAFETY: The entire object is initialized, so accessing this field is safe.
+ let type_ = unsafe { self.hdr.type_ };
+
+ // SAFETY: The type invariants guarantee that the type field is correct.
+ unsafe { Self::type_to_size(type_).unwrap_unchecked() }
+ }
+
+ fn type_to_size(type_: u32) -> Option<usize> {
+ match type_ {
+ BINDER_TYPE_WEAK_BINDER => Some(size_of::<uapi::flat_binder_object>()),
+ BINDER_TYPE_BINDER => Some(size_of::<uapi::flat_binder_object>()),
+ BINDER_TYPE_WEAK_HANDLE => Some(size_of::<uapi::flat_binder_object>()),
+ BINDER_TYPE_HANDLE => Some(size_of::<uapi::flat_binder_object>()),
+ BINDER_TYPE_FD => Some(size_of::<uapi::binder_fd_object>()),
+ BINDER_TYPE_PTR => Some(size_of::<uapi::binder_buffer_object>()),
+ BINDER_TYPE_FDA => Some(size_of::<uapi::binder_fd_array_object>()),
+ _ => None,
+ }
+ }
+}
+
+#[derive(Default)]
+struct FileList {
+ files_to_translate: KVec<FileEntry>,
+ close_on_free: KVec<u32>,
+}
+
+struct FileEntry {
+ /// The file for which a descriptor will be created in the recipient process.
+ file: ARef<File>,
+ /// The offset in the buffer where the file descriptor is stored.
+ buffer_offset: usize,
+ /// Whether this fd should be closed when the allocation is freed.
+ close_on_free: bool,
+}
+
+pub(crate) struct TranslatedFds {
+ reservations: KVec<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,
+}
+
+struct Reservation {
+ res: FileDescriptorReservation,
+ file: ARef<File>,
+}
+
+impl TranslatedFds {
+ pub(crate) fn new() -> Self {
+ Self {
+ reservations: KVec::new(),
+ close_on_free: FdsCloseOnFree(KVec::new()),
+ }
+ }
+
+ pub(crate) fn commit(self) -> FdsCloseOnFree {
+ for entry in self.reservations {
+ entry.res.fd_install(entry.file);
+ }
+
+ self.close_on_free
+ }
+}
+
+pub(crate) struct FdsCloseOnFree(KVec<u32>);
diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs
new file mode 100644
index 000000000000..3d135ec03ca7
--- /dev/null
+++ b/drivers/android/binder/context.rs
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+use kernel::{
+ error::Error,
+ list::{List, ListArc, ListLinks},
+ prelude::*,
+ security,
+ str::{CStr, CString},
+ sync::{Arc, Mutex},
+ task::Kuid,
+};
+
+use crate::{error::BinderError, node::NodeRef, process::Process};
+
+kernel::sync::global_lock! {
+ // SAFETY: We call `init` in the module initializer, so it's initialized before first use.
+ pub(crate) unsafe(uninit) static CONTEXTS: Mutex<ContextList> = ContextList {
+ list: List::new(),
+ };
+}
+
+pub(crate) struct ContextList {
+ list: List<Context>,
+}
+
+pub(crate) fn get_all_contexts() -> Result<KVec<Arc<Context>>> {
+ let lock = CONTEXTS.lock();
+
+ let count = lock.list.iter().count();
+
+ let mut ctxs = KVec::with_capacity(count, GFP_KERNEL)?;
+ for ctx in &lock.list {
+ ctxs.push(Arc::from(ctx), GFP_KERNEL)?;
+ }
+ Ok(ctxs)
+}
+
+/// This struct keeps track of the processes using this context, and which process is the context
+/// manager.
+struct Manager {
+ node: Option<NodeRef>,
+ uid: Option<Kuid>,
+ all_procs: List<Process>,
+}
+
+/// There is one context per binder file (/dev/binder, /dev/hwbinder, etc)
+#[pin_data]
+pub(crate) struct Context {
+ #[pin]
+ manager: Mutex<Manager>,
+ pub(crate) name: CString,
+ #[pin]
+ links: ListLinks,
+}
+
+kernel::list::impl_list_arc_safe! {
+ impl ListArcSafe<0> for Context { untracked; }
+}
+kernel::list::impl_list_item! {
+ impl ListItem<0> for Context {
+ using ListLinks { self.links };
+ }
+}
+
+impl Context {
+ pub(crate) fn new(name: &CStr) -> Result<Arc<Self>> {
+ let name = CString::try_from(name)?;
+ let list_ctx = ListArc::pin_init::<Error>(
+ try_pin_init!(Context {
+ name,
+ links <- ListLinks::new(),
+ manager <- kernel::new_mutex!(Manager {
+ all_procs: List::new(),
+ node: None,
+ uid: None,
+ }, "Context::manager"),
+ }),
+ GFP_KERNEL,
+ )?;
+
+ let ctx = list_ctx.clone_arc();
+ CONTEXTS.lock().list.push_back(list_ctx);
+
+ Ok(ctx)
+ }
+
+ /// Called when the file for this context is unlinked.
+ ///
+ /// No-op if called twice.
+ pub(crate) fn deregister(&self) {
+ // SAFETY: We never add the context to any other linked list than this one, so it is either
+ // in this list, or not in any list.
+ unsafe { CONTEXTS.lock().list.remove(self) };
+ }
+
+ pub(crate) fn register_process(self: &Arc<Self>, proc: ListArc<Process>) {
+ if !Arc::ptr_eq(self, &proc.ctx) {
+ pr_err!("Context::register_process called on the wrong context.");
+ return;
+ }
+ self.manager.lock().all_procs.push_back(proc);
+ }
+
+ pub(crate) fn deregister_process(self: &Arc<Self>, proc: &Process) {
+ if !Arc::ptr_eq(self, &proc.ctx) {
+ pr_err!("Context::deregister_process called on the wrong context.");
+ return;
+ }
+ // SAFETY: We just checked that this is the right list.
+ unsafe { self.manager.lock().all_procs.remove(proc) };
+ }
+
+ pub(crate) fn set_manager_node(&self, node_ref: NodeRef) -> Result {
+ let mut manager = self.manager.lock();
+ if manager.node.is_some() {
+ pr_warn!("BINDER_SET_CONTEXT_MGR already set");
+ return Err(EBUSY);
+ }
+ security::binder_set_context_mgr(&node_ref.node.owner.cred)?;
+
+ // If the context manager has been set before, ensure that we use the same euid.
+ let caller_uid = Kuid::current_euid();
+ if let Some(ref uid) = manager.uid {
+ if *uid != caller_uid {
+ return Err(EPERM);
+ }
+ }
+
+ manager.node = Some(node_ref);
+ manager.uid = Some(caller_uid);
+ Ok(())
+ }
+
+ pub(crate) fn unset_manager_node(&self) {
+ let node_ref = self.manager.lock().node.take();
+ drop(node_ref);
+ }
+
+ pub(crate) fn get_manager_node(&self, strong: bool) -> Result<NodeRef, BinderError> {
+ self.manager
+ .lock()
+ .node
+ .as_ref()
+ .ok_or_else(BinderError::new_dead)?
+ .clone(strong)
+ .map_err(BinderError::from)
+ }
+
+ pub(crate) fn for_each_proc<F>(&self, mut func: F)
+ where
+ F: FnMut(&Process),
+ {
+ let lock = self.manager.lock();
+ for proc in &lock.all_procs {
+ func(&proc);
+ }
+ }
+
+ pub(crate) fn get_all_procs(&self) -> Result<KVec<Arc<Process>>> {
+ let lock = self.manager.lock();
+ let count = lock.all_procs.iter().count();
+
+ let mut procs = KVec::with_capacity(count, GFP_KERNEL)?;
+ for proc in &lock.all_procs {
+ procs.push(Arc::from(proc), GFP_KERNEL)?;
+ }
+ Ok(procs)
+ }
+
+ pub(crate) fn get_procs_with_pid(&self, pid: i32) -> Result<KVec<Arc<Process>>> {
+ let orig = self.get_all_procs()?;
+ let mut backing = KVec::with_capacity(orig.len(), GFP_KERNEL)?;
+ for proc in orig.into_iter().filter(|proc| proc.task.pid() == pid) {
+ backing.push(proc, GFP_KERNEL)?;
+ }
+ Ok(backing)
+ }
+}
diff --git a/drivers/android/binder/deferred_close.rs b/drivers/android/binder/deferred_close.rs
new file mode 100644
index 000000000000..ac895c04d0cb
--- /dev/null
+++ b/drivers/android/binder/deferred_close.rs
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+//! Logic for closing files in a deferred manner.
+//!
+//! This file could make sense to have in `kernel::fs`, but it was rejected for being too
+//! Binder-specific.
+
+use core::mem::MaybeUninit;
+use kernel::{
+ alloc::{AllocError, Flags},
+ bindings,
+ prelude::*,
+};
+
+/// Helper used for closing file descriptors in a way that is safe even if the file is currently
+/// held using `fdget`.
+///
+/// Additional motivation can be found in commit 80cd795630d6 ("binder: fix use-after-free due to
+/// ksys_close() during fdget()") and in the comments on `binder_do_fd_close`.
+pub(crate) struct DeferredFdCloser {
+ inner: KBox<DeferredFdCloserInner>,
+}
+
+/// SAFETY: This just holds an allocation with no real content, so there's no safety issue with
+/// moving it across threads.
+unsafe impl Send for DeferredFdCloser {}
+/// SAFETY: This just holds an allocation with no real content, so there's no safety issue with
+/// moving it across threads.
+unsafe impl Sync for DeferredFdCloser {}
+
+/// # Invariants
+///
+/// If the `file` pointer is non-null, then it points at a `struct file` and owns a refcount to
+/// that file.
+#[repr(C)]
+struct DeferredFdCloserInner {
+ twork: MaybeUninit<bindings::callback_head>,
+ file: *mut bindings::file,
+}
+
+impl DeferredFdCloser {
+ /// Create a new [`DeferredFdCloser`].
+ pub(crate) fn new(flags: Flags) -> Result<Self, AllocError> {
+ Ok(Self {
+ // INVARIANT: The `file` pointer is null, so the type invariant does not apply.
+ inner: KBox::new(
+ DeferredFdCloserInner {
+ twork: MaybeUninit::uninit(),
+ file: core::ptr::null_mut(),
+ },
+ flags,
+ )?,
+ })
+ }
+
+ /// Schedule a task work that closes the file descriptor when this task returns to userspace.
+ ///
+ /// Fails if this is called from a context where we cannot run work when returning to
+ /// userspace. (E.g., from a kthread.)
+ pub(crate) fn close_fd(self, fd: u32) -> Result<(), DeferredFdCloseError> {
+ use bindings::task_work_notify_mode_TWA_RESUME as TWA_RESUME;
+
+ // In this method, we schedule the task work before closing the file. This is because
+ // scheduling a task work is fallible, and we need to know whether it will fail before we
+ // attempt to close the file.
+
+ // Task works are not available on kthreads.
+ let current = kernel::current!();
+
+ // Check if this is a kthread.
+ // SAFETY: Reading `flags` from a task is always okay.
+ if unsafe { ((*current.as_ptr()).flags & bindings::PF_KTHREAD) != 0 } {
+ return Err(DeferredFdCloseError::TaskWorkUnavailable);
+ }
+
+ // Transfer ownership of the box's allocation to a raw pointer. This disables the
+ // destructor, so we must manually convert it back to a KBox to drop it.
+ //
+ // Until we convert it back to a `KBox`, there are no aliasing requirements on this
+ // pointer.
+ let inner = KBox::into_raw(self.inner);
+
+ // The `callback_head` field is first in the struct, so this cast correctly gives us a
+ // pointer to the field.
+ let callback_head = inner.cast::<bindings::callback_head>();
+ // SAFETY: This pointer offset operation does not go out-of-bounds.
+ let file_field = unsafe { core::ptr::addr_of_mut!((*inner).file) };
+
+ let current = current.as_ptr();
+
+ // SAFETY: This function currently has exclusive access to the `DeferredFdCloserInner`, so
+ // it is okay for us to perform unsynchronized writes to its `callback_head` field.
+ unsafe { bindings::init_task_work(callback_head, Some(Self::do_close_fd)) };
+
+ // SAFETY: This inserts the `DeferredFdCloserInner` into the task workqueue for the current
+ // task. If this operation is successful, then this transfers exclusive ownership of the
+ // `callback_head` field to the C side until it calls `do_close_fd`, and we don't touch or
+ // invalidate the field during that time.
+ //
+ // When the C side calls `do_close_fd`, the safety requirements of that method are
+ // satisfied because when a task work is executed, the callback is given ownership of the
+ // pointer.
+ //
+ // The file pointer is currently null. If it is changed to be non-null before `do_close_fd`
+ // is called, then that change happens due to the write at the end of this function, and
+ // that write has a safety comment that explains why the refcount can be dropped when
+ // `do_close_fd` runs.
+ let res = unsafe { bindings::task_work_add(current, callback_head, TWA_RESUME) };
+
+ if res != 0 {
+ // SAFETY: Scheduling the task work failed, so we still have ownership of the box, so
+ // we may destroy it.
+ unsafe { drop(KBox::from_raw(inner)) };
+
+ return Err(DeferredFdCloseError::TaskWorkUnavailable);
+ }
+
+ // This removes the fd from the fd table in `current`. The file is not fully closed until
+ // `filp_close` is called. We are given ownership of one refcount to the file.
+ //
+ // SAFETY: This is safe no matter what `fd` is. If the `fd` is valid (that is, if the
+ // pointer is non-null), then we call `filp_close` on the returned pointer as required by
+ // `file_close_fd`.
+ let file = unsafe { bindings::file_close_fd(fd) };
+ if file.is_null() {
+ // We don't clean up the task work since that might be expensive if the task work queue
+ // is long. Just let it execute and let it clean up for itself.
+ return Err(DeferredFdCloseError::BadFd);
+ }
+
+ // Acquire a second refcount to the file.
+ //
+ // SAFETY: The `file` pointer points at a file with a non-zero refcount.
+ unsafe { bindings::get_file(file) };
+
+ // This method closes the fd, consuming one of our two refcounts. There could be active
+ // light refcounts created from that fd, so we must ensure that the file has a positive
+ // refcount for the duration of those active light refcounts. We do that by holding on to
+ // the second refcount until the current task returns to userspace.
+ //
+ // SAFETY: The `file` pointer is valid. Passing `current->files` as the file table to close
+ // it in is correct, since we just got the `fd` from `file_close_fd` which also uses
+ // `current->files`.
+ //
+ // Note: fl_owner_t is currently a void pointer.
+ unsafe { bindings::filp_close(file, (*current).files as bindings::fl_owner_t) };
+
+ // We update the file pointer that the task work is supposed to fput. This transfers
+ // ownership of our last refcount.
+ //
+ // INVARIANT: This changes the `file` field of a `DeferredFdCloserInner` from null to
+ // non-null. This doesn't break the type invariant for `DeferredFdCloserInner` because we
+ // still own a refcount to the file, so we can pass ownership of that refcount to the
+ // `DeferredFdCloserInner`.
+ //
+ // When `do_close_fd` runs, it must be safe for it to `fput` the refcount. However, this is
+ // the case because all light refcounts that are associated with the fd we closed
+ // previously must be dropped when `do_close_fd`, since light refcounts must be dropped
+ // before returning to userspace.
+ //
+ // SAFETY: Task works are executed on the current thread right before we return to
+ // userspace, so this write is guaranteed to happen before `do_close_fd` is called, which
+ // means that a race is not possible here.
+ unsafe { *file_field = file };
+
+ Ok(())
+ }
+
+ /// # Safety
+ ///
+ /// The provided pointer must point at the `twork` field of a `DeferredFdCloserInner` stored in
+ /// a `KBox`, and the caller must pass exclusive ownership of that `KBox`. Furthermore, if the
+ /// file pointer is non-null, then it must be okay to release the refcount by calling `fput`.
+ unsafe extern "C" fn do_close_fd(inner: *mut bindings::callback_head) {
+ // SAFETY: The caller just passed us ownership of this box.
+ let inner = unsafe { KBox::from_raw(inner.cast::<DeferredFdCloserInner>()) };
+ if !inner.file.is_null() {
+ // SAFETY: By the type invariants, we own a refcount to this file, and the caller
+ // guarantees that dropping the refcount now is okay.
+ unsafe { bindings::fput(inner.file) };
+ }
+ // The allocation is freed when `inner` goes out of scope.
+ }
+}
+
+/// Represents a failure to close an fd in a deferred manner.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub(crate) enum DeferredFdCloseError {
+ /// Closing the fd failed because we were unable to schedule a task work.
+ TaskWorkUnavailable,
+ /// Closing the fd failed because the fd does not exist.
+ BadFd,
+}
+
+impl From<DeferredFdCloseError> for Error {
+ fn from(err: DeferredFdCloseError) -> Error {
+ match err {
+ DeferredFdCloseError::TaskWorkUnavailable => ESRCH,
+ DeferredFdCloseError::BadFd => EBADF,
+ }
+ }
+}
diff --git a/drivers/android/binder/defs.rs b/drivers/android/binder/defs.rs
new file mode 100644
index 000000000000..33f51b4139c7
--- /dev/null
+++ b/drivers/android/binder/defs.rs
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+use core::mem::MaybeUninit;
+use core::ops::{Deref, DerefMut};
+use kernel::{
+ transmute::{AsBytes, FromBytes},
+ uapi::{self, *},
+};
+
+macro_rules! pub_no_prefix {
+ ($prefix:ident, $($newname:ident),+ $(,)?) => {
+ $(pub(crate) const $newname: u32 = kernel::macros::concat_idents!($prefix, $newname);)+
+ };
+}
+
+pub_no_prefix!(
+ binder_driver_return_protocol_,
+ BR_TRANSACTION,
+ BR_TRANSACTION_SEC_CTX,
+ BR_REPLY,
+ BR_DEAD_REPLY,
+ BR_FAILED_REPLY,
+ BR_FROZEN_REPLY,
+ BR_NOOP,
+ BR_SPAWN_LOOPER,
+ BR_TRANSACTION_COMPLETE,
+ BR_TRANSACTION_PENDING_FROZEN,
+ BR_ONEWAY_SPAM_SUSPECT,
+ BR_OK,
+ BR_ERROR,
+ BR_INCREFS,
+ BR_ACQUIRE,
+ BR_RELEASE,
+ BR_DECREFS,
+ BR_DEAD_BINDER,
+ BR_CLEAR_DEATH_NOTIFICATION_DONE,
+ BR_FROZEN_BINDER,
+ BR_CLEAR_FREEZE_NOTIFICATION_DONE,
+);
+
+pub_no_prefix!(
+ binder_driver_command_protocol_,
+ BC_TRANSACTION,
+ BC_TRANSACTION_SG,
+ BC_REPLY,
+ BC_REPLY_SG,
+ BC_FREE_BUFFER,
+ BC_ENTER_LOOPER,
+ BC_EXIT_LOOPER,
+ BC_REGISTER_LOOPER,
+ BC_INCREFS,
+ BC_ACQUIRE,
+ BC_RELEASE,
+ BC_DECREFS,
+ BC_INCREFS_DONE,
+ BC_ACQUIRE_DONE,
+ BC_REQUEST_DEATH_NOTIFICATION,
+ BC_CLEAR_DEATH_NOTIFICATION,
+ BC_DEAD_BINDER_DONE,
+ BC_REQUEST_FREEZE_NOTIFICATION,
+ BC_CLEAR_FREEZE_NOTIFICATION,
+ BC_FREEZE_NOTIFICATION_DONE,
+);
+
+pub_no_prefix!(
+ flat_binder_object_flags_,
+ FLAT_BINDER_FLAG_ACCEPTS_FDS,
+ FLAT_BINDER_FLAG_TXN_SECURITY_CTX
+);
+
+pub_no_prefix!(
+ transaction_flags_,
+ TF_ONE_WAY,
+ TF_ACCEPT_FDS,
+ TF_CLEAR_BUF,
+ TF_UPDATE_TXN
+);
+
+pub(crate) use uapi::{
+ BINDER_TYPE_BINDER, BINDER_TYPE_FD, BINDER_TYPE_FDA, BINDER_TYPE_HANDLE, BINDER_TYPE_PTR,
+ BINDER_TYPE_WEAK_BINDER, BINDER_TYPE_WEAK_HANDLE,
+};
+
+macro_rules! decl_wrapper {
+ ($newname:ident, $wrapped:ty) => {
+ // Define a wrapper around the C type. Use `MaybeUninit` to enforce that the value of
+ // padding bytes must be preserved.
+ #[derive(Copy, Clone)]
+ #[repr(transparent)]
+ pub(crate) struct $newname(MaybeUninit<$wrapped>);
+
+ // SAFETY: This macro is only used with types where this is ok.
+ unsafe impl FromBytes for $newname {}
+ // SAFETY: This macro is only used with types where this is ok.
+ unsafe impl AsBytes for $newname {}
+
+ impl Deref for $newname {
+ type Target = $wrapped;
+ fn deref(&self) -> &Self::Target {
+ // SAFETY: We use `MaybeUninit` only to preserve padding. The value must still
+ // always be valid.
+ unsafe { self.0.assume_init_ref() }
+ }
+ }
+
+ impl DerefMut for $newname {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ // SAFETY: We use `MaybeUninit` only to preserve padding. The value must still
+ // always be valid.
+ unsafe { self.0.assume_init_mut() }
+ }
+ }
+
+ impl Default for $newname {
+ fn default() -> Self {
+ // Create a new value of this type where all bytes (including padding) are zeroed.
+ Self(MaybeUninit::zeroed())
+ }
+ }
+ };
+}
+
+decl_wrapper!(BinderNodeDebugInfo, uapi::binder_node_debug_info);
+decl_wrapper!(BinderNodeInfoForRef, uapi::binder_node_info_for_ref);
+decl_wrapper!(FlatBinderObject, uapi::flat_binder_object);
+decl_wrapper!(BinderFdObject, uapi::binder_fd_object);
+decl_wrapper!(BinderFdArrayObject, uapi::binder_fd_array_object);
+decl_wrapper!(BinderObjectHeader, uapi::binder_object_header);
+decl_wrapper!(BinderBufferObject, uapi::binder_buffer_object);
+decl_wrapper!(BinderTransactionData, uapi::binder_transaction_data);
+decl_wrapper!(
+ BinderTransactionDataSecctx,
+ uapi::binder_transaction_data_secctx
+);
+decl_wrapper!(BinderTransactionDataSg, uapi::binder_transaction_data_sg);
+decl_wrapper!(BinderWriteRead, uapi::binder_write_read);
+decl_wrapper!(BinderVersion, uapi::binder_version);
+decl_wrapper!(BinderFrozenStatusInfo, uapi::binder_frozen_status_info);
+decl_wrapper!(BinderFreezeInfo, uapi::binder_freeze_info);
+decl_wrapper!(BinderFrozenStateInfo, uapi::binder_frozen_state_info);
+decl_wrapper!(BinderHandleCookie, uapi::binder_handle_cookie);
+decl_wrapper!(ExtendedError, uapi::binder_extended_error);
+
+impl BinderVersion {
+ pub(crate) fn current() -> Self {
+ Self(MaybeUninit::new(uapi::binder_version {
+ protocol_version: BINDER_CURRENT_PROTOCOL_VERSION as _,
+ }))
+ }
+}
+
+impl BinderTransactionData {
+ pub(crate) fn with_buffers_size(self, buffers_size: u64) -> BinderTransactionDataSg {
+ BinderTransactionDataSg(MaybeUninit::new(uapi::binder_transaction_data_sg {
+ transaction_data: *self,
+ buffers_size,
+ }))
+ }
+}
+
+impl BinderTransactionDataSecctx {
+ /// View the inner data as wrapped in `BinderTransactionData`.
+ pub(crate) fn tr_data(&mut self) -> &mut BinderTransactionData {
+ // SAFETY: Transparent wrapper is safe to transmute.
+ unsafe {
+ &mut *(&mut self.transaction_data as *mut uapi::binder_transaction_data
+ as *mut BinderTransactionData)
+ }
+ }
+}
+
+impl ExtendedError {
+ pub(crate) fn new(id: u32, command: u32, param: i32) -> Self {
+ Self(MaybeUninit::new(uapi::binder_extended_error {
+ id,
+ command,
+ param,
+ }))
+ }
+}
diff --git a/drivers/android/binder/error.rs b/drivers/android/binder/error.rs
new file mode 100644
index 000000000000..9921827267d0
--- /dev/null
+++ b/drivers/android/binder/error.rs
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+use kernel::prelude::*;
+
+use crate::defs::*;
+
+pub(crate) type BinderResult<T = ()> = core::result::Result<T, BinderError>;
+
+/// 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,
+ source: Option<Error>,
+}
+
+impl BinderError {
+ pub(crate) fn new_dead() -> Self {
+ Self {
+ reply: BR_DEAD_REPLY,
+ source: None,
+ }
+ }
+
+ pub(crate) fn new_frozen() -> Self {
+ Self {
+ reply: BR_FROZEN_REPLY,
+ source: None,
+ }
+ }
+
+ pub(crate) fn new_frozen_oneway() -> Self {
+ Self {
+ reply: BR_TRANSACTION_PENDING_FROZEN,
+ source: None,
+ }
+ }
+
+ pub(crate) fn is_dead(&self) -> bool {
+ self.reply == BR_DEAD_REPLY
+ }
+
+ pub(crate) fn as_errno(&self) -> kernel::ffi::c_int {
+ self.source.unwrap_or(EINVAL).to_errno()
+ }
+
+ pub(crate) fn should_pr_warn(&self) -> bool {
+ self.source.is_some()
+ }
+}
+
+/// 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 {
+ fn from(source: Error) -> Self {
+ Self {
+ reply: BR_FAILED_REPLY,
+ source: Some(source),
+ }
+ }
+}
+
+impl From<kernel::fs::file::BadFdError> for BinderError {
+ fn from(source: kernel::fs::file::BadFdError) -> Self {
+ BinderError::from(Error::from(source))
+ }
+}
+
+impl From<kernel::alloc::AllocError> for BinderError {
+ fn from(_: kernel::alloc::AllocError) -> Self {
+ Self {
+ reply: BR_FAILED_REPLY,
+ source: Some(ENOMEM),
+ }
+ }
+}
+
+impl core::fmt::Debug for BinderError {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self.reply {
+ BR_FAILED_REPLY => match self.source.as_ref() {
+ Some(source) => f
+ .debug_struct("BR_FAILED_REPLY")
+ .field("source", source)
+ .finish(),
+ None => f.pad("BR_FAILED_REPLY"),
+ },
+ BR_DEAD_REPLY => f.pad("BR_DEAD_REPLY"),
+ BR_FROZEN_REPLY => f.pad("BR_FROZEN_REPLY"),
+ BR_TRANSACTION_PENDING_FROZEN => f.pad("BR_TRANSACTION_PENDING_FROZEN"),
+ BR_TRANSACTION_COMPLETE => f.pad("BR_TRANSACTION_COMPLETE"),
+ _ => f
+ .debug_struct("BinderError")
+ .field("reply", &self.reply)
+ .finish(),
+ }
+ }
+}
diff --git a/drivers/android/binder/freeze.rs b/drivers/android/binder/freeze.rs
new file mode 100644
index 000000000000..e68c3c8bc55a
--- /dev/null
+++ b/drivers/android/binder/freeze.rs
@@ -0,0 +1,388 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+use kernel::{
+ alloc::AllocError,
+ list::ListArc,
+ prelude::*,
+ rbtree::{self, RBTreeNodeReservation},
+ seq_file::SeqFile,
+ seq_print,
+ sync::{Arc, UniqueArc},
+ uaccess::UserSliceReader,
+};
+
+use crate::{
+ defs::*, node::Node, process::Process, thread::Thread, BinderReturnWriter, DArc, DLArc,
+ DTRWrap, DeliverToRead,
+};
+
+#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
+pub(crate) struct FreezeCookie(u64);
+
+/// Represents a listener for changes to the frozen state of a process.
+pub(crate) struct FreezeListener {
+ /// The node we are listening for.
+ pub(crate) node: DArc<Node>,
+ /// The cookie of this freeze listener.
+ cookie: FreezeCookie,
+ /// What value of `is_frozen` did we most recently tell userspace about?
+ last_is_frozen: Option<bool>,
+ /// We sent a `BR_FROZEN_BINDER` and we are waiting for `BC_FREEZE_NOTIFICATION_DONE` before
+ /// sending any other commands.
+ is_pending: bool,
+ /// Userspace sent `BC_CLEAR_FREEZE_NOTIFICATION` and we need to reply with
+ /// `BR_CLEAR_FREEZE_NOTIFICATION_DONE` as soon as possible. If `is_pending` is set, then we
+ /// must wait for it to be unset before we can reply.
+ is_clearing: bool,
+ /// Number of cleared duplicates that can't be deleted until userspace sends
+ /// `BC_FREEZE_NOTIFICATION_DONE`.
+ num_pending_duplicates: u64,
+ /// Number of cleared duplicates that can be deleted.
+ num_cleared_duplicates: u64,
+}
+
+impl FreezeListener {
+ /// Is it okay to create a new listener with the same cookie as this one for the provided node?
+ ///
+ /// Under some scenarios, userspace may delete a freeze listener and immediately recreate it
+ /// with the same cookie. This results in duplicate listeners. To avoid issues with ambiguity,
+ /// we allow this only if the new listener is for the same node, and we also require that the
+ /// old listener has already been cleared.
+ fn allow_duplicate(&self, node: &DArc<Node>) -> bool {
+ Arc::ptr_eq(&self.node, node) && self.is_clearing
+ }
+}
+
+type UninitFM = UniqueArc<core::mem::MaybeUninit<DTRWrap<FreezeMessage>>>;
+
+/// Represents a notification that the freeze state has changed.
+pub(crate) struct FreezeMessage {
+ cookie: FreezeCookie,
+}
+
+kernel::list::impl_list_arc_safe! {
+ impl ListArcSafe<0> for FreezeMessage {
+ untracked;
+ }
+}
+
+impl FreezeMessage {
+ fn new(flags: kernel::alloc::Flags) -> Result<UninitFM, AllocError> {
+ UniqueArc::new_uninit(flags)
+ }
+
+ fn init(ua: UninitFM, cookie: FreezeCookie) -> DLArc<FreezeMessage> {
+ match ua.pin_init_with(DTRWrap::new(FreezeMessage { cookie })) {
+ Ok(msg) => ListArc::from(msg),
+ Err(err) => match err {},
+ }
+ }
+}
+
+impl DeliverToRead for FreezeMessage {
+ fn do_work(
+ self: DArc<Self>,
+ thread: &Thread,
+ writer: &mut BinderReturnWriter<'_>,
+ ) -> Result<bool> {
+ let _removed_listener;
+ let mut node_refs = thread.process.node_refs.lock();
+ let Some(mut freeze_entry) = node_refs.freeze_listeners.find_mut(&self.cookie) else {
+ return Ok(true);
+ };
+ let freeze = freeze_entry.get_mut();
+
+ if freeze.num_cleared_duplicates > 0 {
+ freeze.num_cleared_duplicates -= 1;
+ drop(node_refs);
+ writer.write_code(BR_CLEAR_FREEZE_NOTIFICATION_DONE)?;
+ writer.write_payload(&self.cookie.0)?;
+ return Ok(true);
+ }
+
+ if freeze.is_pending {
+ return Ok(true);
+ }
+ if freeze.is_clearing {
+ _removed_listener = freeze_entry.remove_node();
+ drop(node_refs);
+ writer.write_code(BR_CLEAR_FREEZE_NOTIFICATION_DONE)?;
+ writer.write_payload(&self.cookie.0)?;
+ Ok(true)
+ } else {
+ let is_frozen = freeze.node.owner.inner.lock().is_frozen;
+ if freeze.last_is_frozen == Some(is_frozen) {
+ return Ok(true);
+ }
+
+ let mut state_info = BinderFrozenStateInfo::default();
+ state_info.is_frozen = is_frozen as u32;
+ state_info.cookie = freeze.cookie.0;
+ freeze.is_pending = true;
+ freeze.last_is_frozen = Some(is_frozen);
+ drop(node_refs);
+
+ writer.write_code(BR_FROZEN_BINDER)?;
+ writer.write_payload(&state_info)?;
+ // BR_FROZEN_BINDER notifications can cause transactions
+ Ok(false)
+ }
+ }
+
+ fn cancel(self: DArc<Self>) {}
+
+ fn should_sync_wakeup(&self) -> bool {
+ false
+ }
+
+ #[inline(never)]
+ fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
+ seq_print!(m, "{}has frozen binder\n", prefix);
+ Ok(())
+ }
+}
+
+impl FreezeListener {
+ pub(crate) fn on_process_exit(&self, proc: &Arc<Process>) {
+ if !self.is_clearing {
+ self.node.remove_freeze_listener(proc);
+ }
+ }
+}
+
+impl Process {
+ 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);
+
+ let msg = FreezeMessage::new(GFP_KERNEL)?;
+ let alloc = RBTreeNodeReservation::new(GFP_KERNEL)?;
+
+ let mut node_refs_guard = self.node_refs.lock();
+ let node_refs = &mut *node_refs_guard;
+ let Some(info) = node_refs.by_handle.get_mut(&handle) else {
+ pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION invalid ref {}\n", handle);
+ return Err(EINVAL);
+ };
+ if info.freeze().is_some() {
+ pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION already set\n");
+ return Err(EINVAL);
+ }
+ let node_ref = info.node_ref();
+ let freeze_entry = node_refs.freeze_listeners.entry(cookie);
+
+ if let rbtree::Entry::Occupied(ref dupe) = freeze_entry {
+ if !dupe.get().allow_duplicate(&node_ref.node) {
+ pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION duplicate cookie\n");
+ return Err(EINVAL);
+ }
+ }
+
+ // All failure paths must come before this call, and all modifications must come after this
+ // call.
+ node_ref.node.add_freeze_listener(self, GFP_KERNEL)?;
+
+ match freeze_entry {
+ rbtree::Entry::Vacant(entry) => {
+ entry.insert(
+ FreezeListener {
+ cookie,
+ node: node_ref.node.clone(),
+ last_is_frozen: None,
+ is_pending: false,
+ is_clearing: false,
+ num_pending_duplicates: 0,
+ num_cleared_duplicates: 0,
+ },
+ alloc,
+ );
+ }
+ rbtree::Entry::Occupied(mut dupe) => {
+ let dupe = dupe.get_mut();
+ if dupe.is_pending {
+ dupe.num_pending_duplicates += 1;
+ } else {
+ dupe.num_cleared_duplicates += 1;
+ }
+ dupe.last_is_frozen = None;
+ dupe.is_pending = false;
+ dupe.is_clearing = false;
+ }
+ }
+
+ *info.freeze() = Some(cookie);
+ let msg = FreezeMessage::init(msg, cookie);
+ drop(node_refs_guard);
+ let _ = self.push_work(msg);
+ Ok(())
+ }
+
+ 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();
+ let node_refs = &mut *node_refs_guard;
+ let Some(freeze) = node_refs.freeze_listeners.get_mut(&cookie) else {
+ pr_warn!("BC_FREEZE_NOTIFICATION_DONE {:016x} not found\n", cookie.0);
+ return Err(EINVAL);
+ };
+ let mut clear_msg = None;
+ if freeze.num_pending_duplicates > 0 {
+ clear_msg = Some(FreezeMessage::init(alloc, cookie));
+ freeze.num_pending_duplicates -= 1;
+ freeze.num_cleared_duplicates += 1;
+ } else {
+ if !freeze.is_pending {
+ pr_warn!(
+ "BC_FREEZE_NOTIFICATION_DONE {:016x} not pending\n",
+ cookie.0
+ );
+ return Err(EINVAL);
+ }
+ if freeze.is_clearing {
+ // Immediately send another FreezeMessage for BR_CLEAR_FREEZE_NOTIFICATION_DONE.
+ clear_msg = Some(FreezeMessage::init(alloc, cookie));
+ }
+ freeze.is_pending = false;
+ }
+ drop(node_refs_guard);
+ if let Some(clear_msg) = clear_msg {
+ let _ = self.push_work(clear_msg);
+ }
+ Ok(())
+ }
+
+ 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);
+
+ let alloc = FreezeMessage::new(GFP_KERNEL)?;
+ let mut node_refs_guard = self.node_refs.lock();
+ let node_refs = &mut *node_refs_guard;
+ let Some(info) = node_refs.by_handle.get_mut(&handle) else {
+ pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION invalid ref {}\n", handle);
+ return Err(EINVAL);
+ };
+ let Some(info_cookie) = info.freeze() else {
+ pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION freeze notification not active\n");
+ return Err(EINVAL);
+ };
+ if *info_cookie != cookie {
+ pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION freeze notification cookie mismatch\n");
+ return Err(EINVAL);
+ }
+ let Some(listener) = node_refs.freeze_listeners.get_mut(&cookie) else {
+ pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION invalid cookie {}\n", handle);
+ return Err(EINVAL);
+ };
+ listener.is_clearing = true;
+ listener.node.remove_freeze_listener(self);
+ *info.freeze() = None;
+ let mut msg = None;
+ if !listener.is_pending {
+ msg = Some(FreezeMessage::init(alloc, cookie));
+ }
+ drop(node_refs_guard);
+
+ if let Some(msg) = msg {
+ let _ = self.push_work(msg);
+ }
+ Ok(())
+ }
+
+ fn get_freeze_cookie(&self, node: &DArc<Node>) -> Option<FreezeCookie> {
+ let node_refs = &mut *self.node_refs.lock();
+ let handle = node_refs.by_node.get(&node.global_id())?;
+ let node_ref = node_refs.by_handle.get_mut(handle)?;
+ *node_ref.freeze()
+ }
+
+ /// Creates a vector of every freeze listener on this process.
+ ///
+ /// Returns pairs of the remote process listening for notifications and the local node it is
+ /// listening on.
+ #[expect(clippy::type_complexity)]
+ fn find_freeze_recipients(&self) -> Result<KVVec<(DArc<Node>, Arc<Process>)>, AllocError> {
+ // Defined before `inner` to drop after releasing spinlock if `push_within_capacity` fails.
+ let mut node_proc_pair;
+
+ // We pre-allocate space for up to 8 recipients before we take the spinlock. However, if
+ // the allocation fails, use a vector with a capacity of zero instead of failing. After
+ // all, there might not be any freeze listeners, in which case this operation could still
+ // succeed.
+ let mut recipients =
+ KVVec::with_capacity(8, GFP_KERNEL).unwrap_or_else(|_err| KVVec::new());
+
+ let mut inner = self.lock_with_nodes();
+ let mut curr = inner.nodes.cursor_front();
+ while let Some(cursor) = curr {
+ let (key, node) = cursor.current();
+ let key = *key;
+ let list = node.freeze_list(&inner.inner);
+ let len = list.len();
+
+ if recipients.spare_capacity_mut().len() < len {
+ drop(inner);
+ recipients.reserve(len, GFP_KERNEL)?;
+ inner = self.lock_with_nodes();
+ // Find the node we were looking at and try again. If the set of nodes was changed,
+ // then just proceed to the next node. This is ok because we don't guarantee the
+ // inclusion of nodes that are added or removed in parallel with this operation.
+ curr = inner.nodes.cursor_lower_bound(&key);
+ continue;
+ }
+
+ for proc in list {
+ node_proc_pair = (node.clone(), proc.clone());
+ recipients
+ .push_within_capacity(node_proc_pair)
+ .map_err(|_| {
+ pr_err!(
+ "push_within_capacity failed even though we checked the capacity\n"
+ );
+ AllocError
+ })?;
+ }
+
+ curr = cursor.move_next();
+ }
+ Ok(recipients)
+ }
+
+ /// Prepare allocations for sending freeze messages.
+ pub(crate) fn prepare_freeze_messages(&self) -> Result<FreezeMessages, AllocError> {
+ let recipients = self.find_freeze_recipients()?;
+ let mut batch = KVVec::with_capacity(recipients.len(), GFP_KERNEL)?;
+ for (node, proc) in recipients {
+ let Some(cookie) = proc.get_freeze_cookie(&node) else {
+ // If the freeze listener was removed in the meantime, just discard the
+ // notification.
+ continue;
+ };
+ let msg_alloc = FreezeMessage::new(GFP_KERNEL)?;
+ let msg = FreezeMessage::init(msg_alloc, cookie);
+ batch.push((proc, msg), GFP_KERNEL)?;
+ }
+
+ Ok(FreezeMessages { batch })
+ }
+}
+
+pub(crate) struct FreezeMessages {
+ batch: KVVec<(Arc<Process>, DLArc<FreezeMessage>)>,
+}
+
+impl FreezeMessages {
+ pub(crate) fn send_messages(self) {
+ for (proc, msg) in self.batch {
+ let _ = proc.push_work(msg);
+ }
+ }
+}
diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs
new file mode 100644
index 000000000000..ade895ef791e
--- /dev/null
+++ b/drivers/android/binder/node.rs
@@ -0,0 +1,1131 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+use kernel::{
+ list::{AtomicTracker, List, ListArc, ListLinks, TryNewListArc},
+ prelude::*,
+ seq_file::SeqFile,
+ seq_print,
+ sync::lock::{spinlock::SpinLockBackend, Guard},
+ sync::{Arc, LockedBy, SpinLock},
+};
+
+use crate::{
+ defs::*,
+ error::BinderError,
+ process::{NodeRefInfo, Process, ProcessInner},
+ thread::Thread,
+ transaction::Transaction,
+ BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead,
+};
+
+use core::mem;
+
+mod wrapper;
+pub(crate) use self::wrapper::CritIncrWrapper;
+
+#[derive(Debug)]
+pub(crate) struct CouldNotDeliverCriticalIncrement;
+
+/// Keeps track of how this node is scheduled.
+///
+/// There are two ways to schedule a node to a work list. Just schedule the node itself, or
+/// allocate a wrapper that references the node and schedule the wrapper. These wrappers exists to
+/// make it possible to "move" a node from one list to another - when `do_work` is called directly
+/// on the `Node`, then it's a no-op if there's also a pending wrapper.
+///
+/// Wrappers are generally only needed for zero-to-one refcount increments, and there are two cases
+/// of this: weak increments and strong increments. We call such increments "critical" because it
+/// is critical that they are delivered to the thread doing the increment. Some examples:
+///
+/// * One thread makes a zero-to-one strong increment, and another thread makes a zero-to-one weak
+/// increment. Delivering the node to the thread doing the weak increment is wrong, since the
+/// thread doing the strong increment may have ended a long time ago when the command is actually
+/// processed by userspace.
+///
+/// * We have a weak reference and are about to drop it on one thread. But then another thread does
+/// a zero-to-one strong increment. If the strong increment gets sent to the thread that was
+/// about to drop the weak reference, then the strong increment could be processed after the
+/// other thread has already exited, which would be too late.
+///
+/// Note that trying to create a `ListArc` to the node can succeed even if `has_normal_push` is
+/// set. This is because another thread might just have popped the node from a todo list, but not
+/// yet called `do_work`. However, if `has_normal_push` is false, then creating a `ListArc` should
+/// always succeed.
+///
+/// Like the other fields in `NodeInner`, the delivery state is protected by the process lock.
+struct DeliveryState {
+ /// Is the `Node` currently scheduled?
+ has_pushed_node: bool,
+
+ /// Is a wrapper currently scheduled?
+ ///
+ /// The wrapper is used only for strong zero2one increments.
+ has_pushed_wrapper: bool,
+
+ /// Is the currently scheduled `Node` scheduled due to a weak zero2one increment?
+ ///
+ /// Weak zero2one operations are always scheduled using the `Node`.
+ has_weak_zero2one: bool,
+
+ /// Is the currently scheduled wrapper/`Node` scheduled due to a strong zero2one increment?
+ ///
+ /// If `has_pushed_wrapper` is set, then the strong zero2one increment was scheduled using the
+ /// wrapper. Otherwise, `has_pushed_node` must be set and it was scheduled using the `Node`.
+ has_strong_zero2one: bool,
+}
+
+impl DeliveryState {
+ fn should_normal_push(&self) -> bool {
+ !self.has_pushed_node && !self.has_pushed_wrapper
+ }
+
+ fn did_normal_push(&mut self) {
+ assert!(self.should_normal_push());
+ self.has_pushed_node = true;
+ }
+
+ fn should_push_weak_zero2one(&self) -> bool {
+ !self.has_weak_zero2one && !self.has_strong_zero2one
+ }
+
+ fn can_push_weak_zero2one_normally(&self) -> bool {
+ !self.has_pushed_node
+ }
+
+ fn did_push_weak_zero2one(&mut self) {
+ assert!(self.should_push_weak_zero2one());
+ assert!(self.can_push_weak_zero2one_normally());
+ self.has_pushed_node = true;
+ self.has_weak_zero2one = true;
+ }
+
+ fn should_push_strong_zero2one(&self) -> bool {
+ !self.has_strong_zero2one
+ }
+
+ fn can_push_strong_zero2one_normally(&self) -> bool {
+ !self.has_pushed_node
+ }
+
+ fn did_push_strong_zero2one(&mut self) {
+ assert!(self.should_push_strong_zero2one());
+ assert!(self.can_push_strong_zero2one_normally());
+ self.has_pushed_node = true;
+ self.has_strong_zero2one = true;
+ }
+
+ fn did_push_strong_zero2one_wrapper(&mut self) {
+ assert!(self.should_push_strong_zero2one());
+ assert!(!self.can_push_strong_zero2one_normally());
+ self.has_pushed_wrapper = true;
+ self.has_strong_zero2one = true;
+ }
+}
+
+struct CountState {
+ /// The reference count.
+ count: usize,
+ /// Whether the process that owns this node thinks that we hold a refcount on it. (Note that
+ /// even if count is greater than one, we only increment it once in the owning process.)
+ has_count: bool,
+}
+
+impl CountState {
+ fn new() -> Self {
+ Self {
+ count: 0,
+ has_count: false,
+ }
+ }
+}
+
+struct NodeInner {
+ /// Strong refcounts held on this node by `NodeRef` objects.
+ strong: CountState,
+ /// Weak refcounts held on this node by `NodeRef` objects.
+ weak: CountState,
+ delivery_state: DeliveryState,
+ /// The binder driver guarantees that oneway transactions sent to the same node are serialized,
+ /// that is, userspace will not be given the next one until it has finished processing the
+ /// previous oneway transaction. This is done to avoid the case where two oneway transactions
+ /// arrive in opposite order from the order in which they were sent. (E.g., they could be
+ /// delivered to two different threads, which could appear as-if they were sent in opposite
+ /// order.)
+ ///
+ /// To fix that, we store pending oneway transactions in a separate list in the node, and don't
+ /// deliver the next oneway transaction until userspace signals that it has finished processing
+ /// the previous oneway transaction by calling the `BC_FREE_BUFFER` ioctl.
+ oneway_todo: List<DTRWrap<Transaction>>,
+ /// Keeps track of whether this node has a pending oneway transaction.
+ ///
+ /// When this is true, incoming oneway transactions are stored in `oneway_todo`, instead of
+ /// being delivered directly to the process.
+ has_oneway_transaction: bool,
+ /// List of processes to deliver a notification to when this node is destroyed (usually due to
+ /// the process dying).
+ death_list: List<DTRWrap<NodeDeath>, 1>,
+ /// List of processes to deliver freeze notifications to.
+ freeze_list: KVVec<Arc<Process>>,
+ /// The number of active BR_INCREFS or BR_ACQUIRE operations. (should be maximum two)
+ ///
+ /// If this is non-zero, then we postpone any BR_RELEASE or BR_DECREFS notifications until the
+ /// active operations have ended. This avoids the situation an increment and decrement get
+ /// reordered from userspace's perspective.
+ active_inc_refs: u8,
+ /// List of `NodeRefInfo` objects that reference this node.
+ refs: List<NodeRefInfo, { NodeRefInfo::LIST_NODE }>,
+}
+
+#[pin_data]
+pub(crate) struct Node {
+ pub(crate) debug_id: usize,
+ ptr: u64,
+ pub(crate) cookie: u64,
+ pub(crate) flags: u32,
+ pub(crate) owner: Arc<Process>,
+ inner: LockedBy<NodeInner, ProcessInner>,
+ #[pin]
+ links_track: AtomicTracker,
+}
+
+kernel::list::impl_list_arc_safe! {
+ impl ListArcSafe<0> for Node {
+ tracked_by links_track: AtomicTracker;
+ }
+}
+
+// Make `oneway_todo` work.
+kernel::list::impl_list_item! {
+ impl ListItem<0> for DTRWrap<Transaction> {
+ using ListLinks { self.links.inner };
+ }
+}
+
+impl Node {
+ pub(crate) fn new(
+ ptr: u64,
+ cookie: u64,
+ flags: u32,
+ owner: Arc<Process>,
+ ) -> impl PinInit<Self> {
+ pin_init!(Self {
+ inner: LockedBy::new(
+ &owner.inner,
+ NodeInner {
+ strong: CountState::new(),
+ weak: CountState::new(),
+ delivery_state: DeliveryState {
+ has_pushed_node: false,
+ has_pushed_wrapper: false,
+ has_weak_zero2one: false,
+ has_strong_zero2one: false,
+ },
+ death_list: List::new(),
+ oneway_todo: List::new(),
+ freeze_list: KVVec::new(),
+ has_oneway_transaction: false,
+ active_inc_refs: 0,
+ refs: List::new(),
+ },
+ ),
+ debug_id: super::next_debug_id(),
+ ptr,
+ cookie,
+ flags,
+ owner,
+ links_track <- AtomicTracker::new(),
+ })
+ }
+
+ pub(crate) fn has_oneway_transaction(&self, owner_inner: &mut ProcessInner) -> bool {
+ let inner = self.inner.access_mut(owner_inner);
+ inner.has_oneway_transaction
+ }
+
+ #[inline(never)]
+ 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,
+ " node {}: u{:016x} c{:016x} hs {} hw {} cs {} cw {}",
+ self.debug_id,
+ self.ptr,
+ self.cookie,
+ inner.strong.has_count,
+ inner.weak.has_count,
+ inner.strong.count,
+ inner.weak.count,
+ );
+ if !inner.refs.is_empty() {
+ seq_print!(m, " proc");
+ for node_ref in &inner.refs {
+ seq_print!(m, " {}", node_ref.process.task.pid());
+ }
+ }
+ seq_print!(m, "\n");
+ for t in &inner.oneway_todo {
+ t.debug_print_inner(m, " pending async transaction ");
+ }
+ Ok(())
+ }
+
+ /// Insert the `NodeRef` into this `refs` list.
+ ///
+ /// # Safety
+ ///
+ /// It must be the case that `info.node_ref.node` is this node.
+ pub(crate) unsafe fn insert_node_info(
+ &self,
+ info: ListArc<NodeRefInfo, { NodeRefInfo::LIST_NODE }>,
+ ) {
+ self.inner
+ .access_mut(&mut self.owner.inner.lock())
+ .refs
+ .push_front(info);
+ }
+
+ /// Insert the `NodeRef` into this `refs` list.
+ ///
+ /// # Safety
+ ///
+ /// It must be the case that `info.node_ref.node` is this node.
+ pub(crate) unsafe fn remove_node_info(
+ &self,
+ info: &NodeRefInfo,
+ ) -> Option<ListArc<NodeRefInfo, { NodeRefInfo::LIST_NODE }>> {
+ // SAFETY: We always insert `NodeRefInfo` objects into the `refs` list of the node that it
+ // references in `info.node_ref.node`. That is this node, so `info` cannot possibly be in
+ // the `refs` list of another node.
+ unsafe {
+ self.inner
+ .access_mut(&mut self.owner.inner.lock())
+ .refs
+ .remove(info)
+ }
+ }
+
+ /// An id that is unique across all binder nodes on the system. Used as the key in the
+ /// `by_node` map.
+ pub(crate) fn global_id(&self) -> usize {
+ self as *const Node as usize
+ }
+
+ pub(crate) fn get_id(&self) -> (u64, u64) {
+ (self.ptr, self.cookie)
+ }
+
+ pub(crate) fn add_death(
+ &self,
+ death: ListArc<DTRWrap<NodeDeath>, 1>,
+ guard: &mut Guard<'_, ProcessInner, SpinLockBackend>,
+ ) {
+ self.inner.access_mut(guard).death_list.push_back(death);
+ }
+
+ pub(crate) fn inc_ref_done_locked(
+ self: &DArc<Node>,
+ _strong: bool,
+ owner_inner: &mut ProcessInner,
+ ) -> Option<DLArc<Node>> {
+ let inner = self.inner.access_mut(owner_inner);
+ if inner.active_inc_refs == 0 {
+ pr_err!("inc_ref_done called when no active inc_refs");
+ return None;
+ }
+
+ inner.active_inc_refs -= 1;
+ if inner.active_inc_refs == 0 {
+ // Having active inc_refs can inhibit dropping of ref-counts. Calculate whether we
+ // would send a refcount decrement, and if so, tell the caller to schedule us.
+ let strong = inner.strong.count > 0;
+ let has_strong = inner.strong.has_count;
+ let weak = strong || inner.weak.count > 0;
+ let has_weak = inner.weak.has_count;
+
+ let should_drop_weak = !weak && has_weak;
+ let should_drop_strong = !strong && has_strong;
+
+ // If we want to drop the ref-count again, tell the caller to schedule a work node for
+ // that.
+ let need_push = should_drop_weak || should_drop_strong;
+
+ if need_push && inner.delivery_state.should_normal_push() {
+ let list_arc = ListArc::try_from_arc(self.clone()).ok().unwrap();
+ inner.delivery_state.did_normal_push();
+ Some(list_arc)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+
+ pub(crate) fn update_refcount_locked(
+ self: &DArc<Node>,
+ inc: bool,
+ strong: bool,
+ count: usize,
+ owner_inner: &mut ProcessInner,
+ ) -> Option<DLArc<Node>> {
+ let is_dead = owner_inner.is_dead;
+ let inner = self.inner.access_mut(owner_inner);
+
+ // Get a reference to the state we'll update.
+ let state = if strong {
+ &mut inner.strong
+ } else {
+ &mut inner.weak
+ };
+
+ // Update the count and determine whether we need to push work.
+ let need_push = if inc {
+ state.count += count;
+ // TODO: This method shouldn't be used for zero-to-one increments.
+ !is_dead && !state.has_count
+ } else {
+ if state.count < count {
+ pr_err!("Failure: refcount underflow!");
+ return None;
+ }
+ state.count -= count;
+ !is_dead && state.count == 0 && state.has_count
+ };
+
+ if need_push && inner.delivery_state.should_normal_push() {
+ let list_arc = ListArc::try_from_arc(self.clone()).ok().unwrap();
+ inner.delivery_state.did_normal_push();
+ Some(list_arc)
+ } else {
+ None
+ }
+ }
+
+ pub(crate) fn incr_refcount_allow_zero2one(
+ self: &DArc<Self>,
+ strong: bool,
+ owner_inner: &mut ProcessInner,
+ ) -> Result<Option<DLArc<Node>>, CouldNotDeliverCriticalIncrement> {
+ let is_dead = owner_inner.is_dead;
+ let inner = self.inner.access_mut(owner_inner);
+
+ // Get a reference to the state we'll update.
+ let state = if strong {
+ &mut inner.strong
+ } else {
+ &mut inner.weak
+ };
+
+ // Update the count and determine whether we need to push work.
+ state.count += 1;
+ if is_dead || state.has_count {
+ return Ok(None);
+ }
+
+ // Userspace needs to be notified of this.
+ if !strong && inner.delivery_state.should_push_weak_zero2one() {
+ assert!(inner.delivery_state.can_push_weak_zero2one_normally());
+ let list_arc = ListArc::try_from_arc(self.clone()).ok().unwrap();
+ inner.delivery_state.did_push_weak_zero2one();
+ Ok(Some(list_arc))
+ } else if strong && inner.delivery_state.should_push_strong_zero2one() {
+ if inner.delivery_state.can_push_strong_zero2one_normally() {
+ let list_arc = ListArc::try_from_arc(self.clone()).ok().unwrap();
+ inner.delivery_state.did_push_strong_zero2one();
+ Ok(Some(list_arc))
+ } else {
+ state.count -= 1;
+ Err(CouldNotDeliverCriticalIncrement)
+ }
+ } else {
+ // Work is already pushed, and we don't need to push again.
+ Ok(None)
+ }
+ }
+
+ pub(crate) fn incr_refcount_allow_zero2one_with_wrapper(
+ self: &DArc<Self>,
+ strong: bool,
+ wrapper: CritIncrWrapper,
+ owner_inner: &mut ProcessInner,
+ ) -> Option<DLArc<dyn DeliverToRead>> {
+ match self.incr_refcount_allow_zero2one(strong, owner_inner) {
+ Ok(Some(node)) => Some(node as _),
+ Ok(None) => None,
+ Err(CouldNotDeliverCriticalIncrement) => {
+ assert!(strong);
+ let inner = self.inner.access_mut(owner_inner);
+ inner.strong.count += 1;
+ inner.delivery_state.did_push_strong_zero2one_wrapper();
+ Some(wrapper.init(self.clone()))
+ }
+ }
+ }
+
+ pub(crate) fn update_refcount(self: &DArc<Self>, inc: bool, count: usize, strong: bool) {
+ self.owner
+ .inner
+ .lock()
+ .update_node_refcount(self, inc, strong, count, None);
+ }
+
+ pub(crate) fn populate_counts(
+ &self,
+ out: &mut BinderNodeInfoForRef,
+ guard: &Guard<'_, ProcessInner, SpinLockBackend>,
+ ) {
+ let inner = self.inner.access(guard);
+ out.strong_count = inner.strong.count as _;
+ out.weak_count = inner.weak.count as _;
+ }
+
+ pub(crate) fn populate_debug_info(
+ &self,
+ out: &mut BinderNodeDebugInfo,
+ guard: &Guard<'_, ProcessInner, SpinLockBackend>,
+ ) {
+ out.ptr = self.ptr as _;
+ out.cookie = self.cookie as _;
+ let inner = self.inner.access(guard);
+ if inner.strong.has_count {
+ out.has_strong_ref = 1;
+ }
+ if inner.weak.has_count {
+ out.has_weak_ref = 1;
+ }
+ }
+
+ pub(crate) fn force_has_count(&self, guard: &mut Guard<'_, ProcessInner, SpinLockBackend>) {
+ let inner = self.inner.access_mut(guard);
+ inner.strong.has_count = true;
+ inner.weak.has_count = true;
+ }
+
+ fn write(&self, writer: &mut BinderReturnWriter<'_>, code: u32) -> Result {
+ writer.write_code(code)?;
+ writer.write_payload(&self.ptr)?;
+ writer.write_payload(&self.cookie)?;
+ Ok(())
+ }
+
+ pub(crate) fn submit_oneway(
+ &self,
+ transaction: DLArc<Transaction>,
+ guard: &mut Guard<'_, ProcessInner, SpinLockBackend>,
+ ) -> Result<(), (BinderError, DLArc<dyn DeliverToRead>)> {
+ if guard.is_dead {
+ return Err((BinderError::new_dead(), transaction));
+ }
+
+ let inner = self.inner.access_mut(guard);
+ if inner.has_oneway_transaction {
+ inner.oneway_todo.push_back(transaction);
+ } else {
+ inner.has_oneway_transaction = true;
+ guard.push_work(transaction)?;
+ }
+ Ok(())
+ }
+
+ pub(crate) fn release(&self) {
+ let mut guard = self.owner.inner.lock();
+ while let Some(work) = self.inner.access_mut(&mut guard).oneway_todo.pop_front() {
+ drop(guard);
+ work.into_arc().cancel();
+ guard = self.owner.inner.lock();
+ }
+
+ let death_list = core::mem::take(&mut self.inner.access_mut(&mut guard).death_list);
+ drop(guard);
+ for death in death_list {
+ death.into_arc().set_dead();
+ }
+ }
+
+ pub(crate) fn pending_oneway_finished(&self) {
+ let mut guard = self.owner.inner.lock();
+ if guard.is_dead {
+ // Cleanup will happen in `Process::deferred_release`.
+ return;
+ }
+
+ let inner = self.inner.access_mut(&mut guard);
+
+ let transaction = inner.oneway_todo.pop_front();
+ inner.has_oneway_transaction = transaction.is_some();
+ if let Some(transaction) = transaction {
+ match guard.push_work(transaction) {
+ Ok(()) => {}
+ Err((_err, work)) => {
+ // Process is dead.
+ // This shouldn't happen due to the `is_dead` check, but if it does, just drop
+ // the transaction and return.
+ drop(guard);
+ drop(work);
+ }
+ }
+ }
+ }
+
+ /// Finds an outdated transaction that the given transaction can replace.
+ ///
+ /// If one is found, it is removed from the list and returned.
+ pub(crate) fn take_outdated_transaction(
+ &self,
+ new: &Transaction,
+ guard: &mut Guard<'_, ProcessInner, SpinLockBackend>,
+ ) -> Option<DLArc<Transaction>> {
+ let inner = self.inner.access_mut(guard);
+ let mut cursor = inner.oneway_todo.cursor_front();
+ while let Some(next) = cursor.peek_next() {
+ if new.can_replace(&next) {
+ return Some(next.remove());
+ }
+ cursor.move_next();
+ }
+ None
+ }
+
+ /// This is split into a separate function since it's called by both `Node::do_work` and
+ /// `NodeWrapper::do_work`.
+ fn do_work_locked(
+ &self,
+ writer: &mut BinderReturnWriter<'_>,
+ mut guard: Guard<'_, ProcessInner, SpinLockBackend>,
+ ) -> Result<bool> {
+ let inner = self.inner.access_mut(&mut guard);
+ let strong = inner.strong.count > 0;
+ let has_strong = inner.strong.has_count;
+ let weak = strong || inner.weak.count > 0;
+ let has_weak = inner.weak.has_count;
+
+ if weak && !has_weak {
+ inner.weak.has_count = true;
+ inner.active_inc_refs += 1;
+ }
+
+ if strong && !has_strong {
+ inner.strong.has_count = true;
+ inner.active_inc_refs += 1;
+ }
+
+ let no_active_inc_refs = inner.active_inc_refs == 0;
+ let should_drop_weak = no_active_inc_refs && (!weak && has_weak);
+ let should_drop_strong = no_active_inc_refs && (!strong && has_strong);
+ if should_drop_weak {
+ inner.weak.has_count = false;
+ }
+ if should_drop_strong {
+ inner.strong.has_count = false;
+ }
+ if no_active_inc_refs && !weak {
+ // Remove the node if there are no references to it.
+ guard.remove_node(self.ptr);
+ }
+ drop(guard);
+
+ if weak && !has_weak {
+ self.write(writer, BR_INCREFS)?;
+ }
+ if strong && !has_strong {
+ self.write(writer, BR_ACQUIRE)?;
+ }
+ if should_drop_strong {
+ self.write(writer, BR_RELEASE)?;
+ }
+ if should_drop_weak {
+ self.write(writer, BR_DECREFS)?;
+ }
+
+ Ok(true)
+ }
+
+ pub(crate) fn add_freeze_listener(
+ &self,
+ process: &Arc<Process>,
+ flags: kernel::alloc::Flags,
+ ) -> Result {
+ let mut vec_alloc = KVVec::<Arc<Process>>::new();
+ loop {
+ let mut guard = self.owner.inner.lock();
+ // Do not check for `guard.dead`. The `dead` flag that matters here is the owner of the
+ // listener, no the target.
+ let inner = self.inner.access_mut(&mut guard);
+ let len = inner.freeze_list.len();
+ if len >= inner.freeze_list.capacity() {
+ if len >= vec_alloc.capacity() {
+ drop(guard);
+ vec_alloc = KVVec::with_capacity((1 + len).next_power_of_two(), flags)?;
+ continue;
+ }
+ mem::swap(&mut inner.freeze_list, &mut vec_alloc);
+ for elem in vec_alloc.drain_all() {
+ inner.freeze_list.push_within_capacity(elem)?;
+ }
+ }
+ inner.freeze_list.push_within_capacity(process.clone())?;
+ return Ok(());
+ }
+ }
+
+ pub(crate) fn remove_freeze_listener(&self, p: &Arc<Process>) {
+ let _unused_capacity;
+ let mut guard = self.owner.inner.lock();
+ let inner = self.inner.access_mut(&mut guard);
+ let len = inner.freeze_list.len();
+ inner.freeze_list.retain(|proc| !Arc::ptr_eq(proc, p));
+ if len == inner.freeze_list.len() {
+ pr_warn!(
+ "Could not remove freeze listener for {}\n",
+ p.pid_in_current_ns()
+ );
+ }
+ if inner.freeze_list.is_empty() {
+ _unused_capacity = mem::replace(&mut inner.freeze_list, KVVec::new());
+ }
+ }
+
+ pub(crate) fn freeze_list<'a>(&'a self, guard: &'a ProcessInner) -> &'a [Arc<Process>] {
+ &self.inner.access(guard).freeze_list
+ }
+}
+
+impl DeliverToRead for Node {
+ fn do_work(
+ self: DArc<Self>,
+ _thread: &Thread,
+ writer: &mut BinderReturnWriter<'_>,
+ ) -> Result<bool> {
+ let mut owner_inner = self.owner.inner.lock();
+ let inner = self.inner.access_mut(&mut owner_inner);
+
+ assert!(inner.delivery_state.has_pushed_node);
+ if inner.delivery_state.has_pushed_wrapper {
+ // If the wrapper is scheduled, then we are either a normal push or weak zero2one
+ // increment, and the wrapper is a strong zero2one increment, so the wrapper always
+ // takes precedence over us.
+ assert!(inner.delivery_state.has_strong_zero2one);
+ inner.delivery_state.has_pushed_node = false;
+ inner.delivery_state.has_weak_zero2one = false;
+ return Ok(true);
+ }
+
+ inner.delivery_state.has_pushed_node = false;
+ inner.delivery_state.has_weak_zero2one = false;
+ inner.delivery_state.has_strong_zero2one = false;
+
+ self.do_work_locked(writer, owner_inner)
+ }
+
+ fn cancel(self: DArc<Self>) {}
+
+ fn should_sync_wakeup(&self) -> bool {
+ false
+ }
+
+ #[inline(never)]
+ fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
+ seq_print!(
+ m,
+ "{}node work {}: u{:016x} c{:016x}\n",
+ prefix,
+ self.debug_id,
+ self.ptr,
+ self.cookie,
+ );
+ Ok(())
+ }
+}
+
+/// Represents something that holds one or more ref-counts to a `Node`.
+///
+/// Whenever process A holds a refcount to a node owned by a different process B, then process A
+/// will store a `NodeRef` that refers to the `Node` in process B. When process A releases the
+/// refcount, we destroy the NodeRef, which decrements the ref-count in process A.
+///
+/// This type is also used for some other cases. For example, a transaction allocation holds a
+/// refcount on the target node, and this is implemented by storing a `NodeRef` in the allocation
+/// so that the destructor of the allocation will drop a refcount of the `Node`.
+pub(crate) struct NodeRef {
+ pub(crate) node: DArc<Node>,
+ /// How many times does this NodeRef hold a refcount on the Node?
+ strong_node_count: usize,
+ weak_node_count: usize,
+ /// How many times does userspace hold a refcount on this NodeRef?
+ strong_count: usize,
+ weak_count: usize,
+}
+
+impl NodeRef {
+ pub(crate) fn new(node: DArc<Node>, strong_count: usize, weak_count: usize) -> Self {
+ Self {
+ node,
+ strong_node_count: strong_count,
+ weak_node_count: weak_count,
+ strong_count,
+ weak_count,
+ }
+ }
+
+ pub(crate) fn absorb(&mut self, mut other: Self) {
+ assert!(
+ Arc::ptr_eq(&self.node, &other.node),
+ "absorb called with differing nodes"
+ );
+ self.strong_node_count += other.strong_node_count;
+ self.weak_node_count += other.weak_node_count;
+ self.strong_count += other.strong_count;
+ self.weak_count += other.weak_count;
+ other.strong_count = 0;
+ other.weak_count = 0;
+ other.strong_node_count = 0;
+ other.weak_node_count = 0;
+
+ if self.strong_node_count >= 2 || self.weak_node_count >= 2 {
+ let mut guard = self.node.owner.inner.lock();
+ let inner = self.node.inner.access_mut(&mut guard);
+
+ if self.strong_node_count >= 2 {
+ inner.strong.count -= self.strong_node_count - 1;
+ self.strong_node_count = 1;
+ assert_ne!(inner.strong.count, 0);
+ }
+ if self.weak_node_count >= 2 {
+ inner.weak.count -= self.weak_node_count - 1;
+ self.weak_node_count = 1;
+ assert_ne!(inner.weak.count, 0);
+ }
+ }
+ }
+
+ pub(crate) fn get_count(&self) -> (usize, usize) {
+ (self.strong_count, self.weak_count)
+ }
+
+ pub(crate) fn clone(&self, strong: bool) -> Result<NodeRef> {
+ if strong && self.strong_count == 0 {
+ return Err(EINVAL);
+ }
+ Ok(self
+ .node
+ .owner
+ .inner
+ .lock()
+ .new_node_ref(self.node.clone(), strong, None))
+ }
+
+ /// Updates (increments or decrements) the number of references held against the node. If the
+ /// count being updated transitions from 0 to 1 or from 1 to 0, the node is notified by having
+ /// its `update_refcount` function called.
+ ///
+ /// Returns whether `self` should be removed (when both counts are zero).
+ pub(crate) fn update(&mut self, inc: bool, strong: bool) -> bool {
+ if strong && self.strong_count == 0 {
+ return false;
+ }
+ let (count, node_count, other_count) = if strong {
+ (
+ &mut self.strong_count,
+ &mut self.strong_node_count,
+ self.weak_count,
+ )
+ } else {
+ (
+ &mut self.weak_count,
+ &mut self.weak_node_count,
+ self.strong_count,
+ )
+ };
+ if inc {
+ if *count == 0 {
+ *node_count = 1;
+ self.node.update_refcount(true, 1, strong);
+ }
+ *count += 1;
+ } else {
+ if *count == 0 {
+ pr_warn!(
+ "pid {} performed invalid decrement on ref\n",
+ kernel::current!().pid()
+ );
+ return false;
+ }
+ *count -= 1;
+ if *count == 0 {
+ self.node.update_refcount(false, *node_count, strong);
+ *node_count = 0;
+ return other_count == 0;
+ }
+ }
+ false
+ }
+}
+
+impl Drop for NodeRef {
+ // This destructor is called conditionally from `Allocation::drop`. That branch is often
+ // mispredicted. Inlining this method call reduces the cost of those branch mispredictions.
+ #[inline(always)]
+ fn drop(&mut self) {
+ if self.strong_node_count > 0 {
+ self.node
+ .update_refcount(false, self.strong_node_count, true);
+ }
+ if self.weak_node_count > 0 {
+ self.node
+ .update_refcount(false, self.weak_node_count, false);
+ }
+ }
+}
+
+struct NodeDeathInner {
+ dead: bool,
+ cleared: bool,
+ notification_done: bool,
+ /// Indicates whether the normal flow was interrupted by removing the handle. In this case, we
+ /// need behave as if the death notification didn't exist (i.e., we don't deliver anything to
+ /// the user.
+ aborted: bool,
+}
+
+/// Used to deliver notifications when a process dies.
+///
+/// A process can request to be notified when a process dies using `BC_REQUEST_DEATH_NOTIFICATION`.
+/// This will make the driver send a `BR_DEAD_BINDER` to userspace when the process dies (or
+/// immediately if it is already dead). Userspace is supposed to respond with `BC_DEAD_BINDER_DONE`
+/// once it has processed the notification.
+///
+/// Userspace can unregister from death notifications using the `BC_CLEAR_DEATH_NOTIFICATION`
+/// command. In this case, the kernel will respond with `BR_CLEAR_DEATH_NOTIFICATION_DONE` once the
+/// notification has been removed. Note that if the remote process dies before the kernel has
+/// responded with `BR_CLEAR_DEATH_NOTIFICATION_DONE`, then the kernel will still send a
+/// `BR_DEAD_BINDER`, which userspace must be able to process. In this case, the kernel will wait
+/// for the `BC_DEAD_BINDER_DONE` command before it sends `BR_CLEAR_DEATH_NOTIFICATION_DONE`.
+///
+/// Note that even if the kernel sends a `BR_DEAD_BINDER`, this does not remove the death
+/// notification. Userspace must still remove it manually using `BC_CLEAR_DEATH_NOTIFICATION`.
+///
+/// If a process uses `BC_RELEASE` to destroy its last refcount on a node that has an active death
+/// registration, then the death registration is immediately deleted (we implement this using the
+/// `aborted` field). However, userspace is not supposed to delete a `NodeRef` without first
+/// deregistering death notifications, so this codepath is not executed under normal circumstances.
+#[pin_data]
+pub(crate) struct NodeDeath {
+ node: DArc<Node>,
+ process: Arc<Process>,
+ pub(crate) cookie: u64,
+ #[pin]
+ links_track: AtomicTracker<0>,
+ /// Used by the owner `Node` to store a list of registered death notifications.
+ ///
+ /// # Invariants
+ ///
+ /// Only ever used with the `death_list` list of `self.node`.
+ #[pin]
+ death_links: ListLinks<1>,
+ /// Used by the process to keep track of the death notifications for which we have sent a
+ /// `BR_DEAD_BINDER` but not yet received a `BC_DEAD_BINDER_DONE`.
+ ///
+ /// # Invariants
+ ///
+ /// Only ever used with the `delivered_deaths` list of `self.process`.
+ #[pin]
+ delivered_links: ListLinks<2>,
+ #[pin]
+ delivered_links_track: AtomicTracker<2>,
+ #[pin]
+ inner: SpinLock<NodeDeathInner>,
+}
+
+impl NodeDeath {
+ /// Constructs a new node death notification object.
+ pub(crate) fn new(
+ node: DArc<Node>,
+ process: Arc<Process>,
+ cookie: u64,
+ ) -> impl PinInit<DTRWrap<Self>> {
+ DTRWrap::new(pin_init!(
+ Self {
+ node,
+ process,
+ cookie,
+ links_track <- AtomicTracker::new(),
+ death_links <- ListLinks::new(),
+ delivered_links <- ListLinks::new(),
+ delivered_links_track <- AtomicTracker::new(),
+ inner <- kernel::new_spinlock!(NodeDeathInner {
+ dead: false,
+ cleared: false,
+ notification_done: false,
+ aborted: false,
+ }, "NodeDeath::inner"),
+ }
+ ))
+ }
+
+ /// Sets the cleared flag to `true`.
+ ///
+ /// It removes `self` from the node's death notification list if needed.
+ ///
+ /// Returns whether it needs to be queued.
+ pub(crate) fn set_cleared(self: &DArc<Self>, abort: bool) -> bool {
+ let (needs_removal, needs_queueing) = {
+ // Update state and determine if we need to queue a work item. We only need to do it
+ // when the node is not dead or if the user already completed the death notification.
+ let mut inner = self.inner.lock();
+ if abort {
+ inner.aborted = true;
+ }
+ if inner.cleared {
+ // Already cleared.
+ return false;
+ }
+ inner.cleared = true;
+ (!inner.dead, !inner.dead || inner.notification_done)
+ };
+
+ // Remove death notification from node.
+ if needs_removal {
+ let mut owner_inner = self.node.owner.inner.lock();
+ let node_inner = self.node.inner.access_mut(&mut owner_inner);
+ // SAFETY: A `NodeDeath` is never inserted into the death list of any node other than
+ // its owner, so it is either in this death list or in no death list.
+ unsafe { node_inner.death_list.remove(self) };
+ }
+ needs_queueing
+ }
+
+ /// Sets the 'notification done' flag to `true`.
+ pub(crate) fn set_notification_done(self: DArc<Self>, thread: &Thread) {
+ let needs_queueing = {
+ let mut inner = self.inner.lock();
+ inner.notification_done = true;
+ inner.cleared
+ };
+ if needs_queueing {
+ if let Some(death) = ListArc::try_from_arc_or_drop(self) {
+ let _ = thread.push_work_if_looper(death);
+ }
+ }
+ }
+
+ /// Sets the 'dead' flag to `true` and queues work item if needed.
+ pub(crate) fn set_dead(self: DArc<Self>) {
+ let needs_queueing = {
+ let mut inner = self.inner.lock();
+ if inner.cleared {
+ false
+ } else {
+ inner.dead = true;
+ true
+ }
+ };
+ if needs_queueing {
+ // Push the death notification to the target process. There is nothing else to do if
+ // it's already dead.
+ if let Some(death) = ListArc::try_from_arc_or_drop(self) {
+ let process = death.process.clone();
+ let _ = process.push_work(death);
+ }
+ }
+ }
+}
+
+kernel::list::impl_list_arc_safe! {
+ impl ListArcSafe<0> for NodeDeath {
+ tracked_by links_track: AtomicTracker;
+ }
+}
+
+kernel::list::impl_list_arc_safe! {
+ impl ListArcSafe<1> for DTRWrap<NodeDeath> { untracked; }
+}
+kernel::list::impl_list_item! {
+ impl ListItem<1> for DTRWrap<NodeDeath> {
+ using ListLinks { self.wrapped.death_links };
+ }
+}
+
+kernel::list::impl_list_arc_safe! {
+ impl ListArcSafe<2> for DTRWrap<NodeDeath> {
+ tracked_by wrapped: NodeDeath;
+ }
+}
+kernel::list::impl_list_arc_safe! {
+ impl ListArcSafe<2> for NodeDeath {
+ tracked_by delivered_links_track: AtomicTracker<2>;
+ }
+}
+kernel::list::impl_list_item! {
+ impl ListItem<2> for DTRWrap<NodeDeath> {
+ using ListLinks { self.wrapped.delivered_links };
+ }
+}
+
+impl DeliverToRead for NodeDeath {
+ fn do_work(
+ self: DArc<Self>,
+ _thread: &Thread,
+ writer: &mut BinderReturnWriter<'_>,
+ ) -> Result<bool> {
+ let done = {
+ let inner = self.inner.lock();
+ if inner.aborted {
+ return Ok(true);
+ }
+ inner.cleared && (!inner.dead || inner.notification_done)
+ };
+
+ let cookie = self.cookie;
+ let cmd = if done {
+ BR_CLEAR_DEATH_NOTIFICATION_DONE
+ } else {
+ let process = self.process.clone();
+ let mut process_inner = process.inner.lock();
+ let inner = self.inner.lock();
+ if inner.aborted {
+ return Ok(true);
+ }
+ // We're still holding the inner lock, so it cannot be aborted while we insert it into
+ // the delivered list.
+ process_inner.death_delivered(self.clone());
+ BR_DEAD_BINDER
+ };
+
+ writer.write_code(cmd)?;
+ writer.write_payload(&cookie)?;
+ // DEAD_BINDER notifications can cause transactions, so stop processing work items when we
+ // get to a death notification.
+ Ok(cmd != BR_DEAD_BINDER)
+ }
+
+ fn cancel(self: DArc<Self>) {}
+
+ fn should_sync_wakeup(&self) -> bool {
+ false
+ }
+
+ #[inline(never)]
+ fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
+ let inner = self.inner.lock();
+
+ let dead_binder = inner.dead && !inner.notification_done;
+
+ if dead_binder {
+ if inner.cleared {
+ seq_print!(m, "{}has cleared dead binder\n", prefix);
+ } else {
+ seq_print!(m, "{}has dead binder\n", prefix);
+ }
+ } else {
+ seq_print!(m, "{}has cleared death notification\n", prefix);
+ }
+
+ Ok(())
+ }
+}
diff --git a/drivers/android/binder/node/wrapper.rs b/drivers/android/binder/node/wrapper.rs
new file mode 100644
index 000000000000..43294c050502
--- /dev/null
+++ b/drivers/android/binder/node/wrapper.rs
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+use kernel::{list::ListArc, prelude::*, seq_file::SeqFile, seq_print, sync::UniqueArc};
+
+use crate::{node::Node, thread::Thread, BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead};
+
+use core::mem::MaybeUninit;
+
+pub(crate) struct CritIncrWrapper {
+ inner: UniqueArc<MaybeUninit<DTRWrap<NodeWrapper>>>,
+}
+
+impl CritIncrWrapper {
+ pub(crate) fn new() -> Result<Self> {
+ Ok(CritIncrWrapper {
+ inner: UniqueArc::new_uninit(GFP_KERNEL)?,
+ })
+ }
+
+ 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 _,
+ Err(err) => match err {},
+ }
+ }
+}
+
+struct NodeWrapper {
+ node: DArc<Node>,
+}
+
+kernel::list::impl_list_arc_safe! {
+ impl ListArcSafe<0> for NodeWrapper {
+ untracked;
+ }
+}
+
+impl DeliverToRead for NodeWrapper {
+ fn do_work(
+ self: DArc<Self>,
+ _thread: &Thread,
+ writer: &mut BinderReturnWriter<'_>,
+ ) -> Result<bool> {
+ let node = &self.node;
+ let mut owner_inner = node.owner.inner.lock();
+ let inner = node.inner.access_mut(&mut owner_inner);
+
+ let ds = &mut inner.delivery_state;
+
+ assert!(ds.has_pushed_wrapper);
+ assert!(ds.has_strong_zero2one);
+ ds.has_pushed_wrapper = false;
+ ds.has_strong_zero2one = false;
+
+ node.do_work_locked(writer, owner_inner)
+ }
+
+ fn cancel(self: DArc<Self>) {}
+
+ fn should_sync_wakeup(&self) -> bool {
+ false
+ }
+
+ #[inline(never)]
+ fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
+ seq_print!(
+ m,
+ "{}node work {}: u{:016x} c{:016x}\n",
+ prefix,
+ self.node.debug_id,
+ self.node.ptr,
+ self.node.cookie,
+ );
+ Ok(())
+ }
+}
diff --git a/drivers/android/binder/page_range.rs b/drivers/android/binder/page_range.rs
new file mode 100644
index 000000000000..9379038f61f5
--- /dev/null
+++ b/drivers/android/binder/page_range.rs
@@ -0,0 +1,734 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+//! This module has utilities for managing a page range where unused pages may be reclaimed by a
+//! vma shrinker.
+
+// To avoid deadlocks, locks are taken in the order:
+//
+// 1. mmap lock
+// 2. spinlock
+// 3. lru spinlock
+//
+// The shrinker will use trylock methods because it locks them in a different order.
+
+use core::{
+ marker::PhantomPinned,
+ mem::{size_of, size_of_val, MaybeUninit},
+ ptr,
+};
+
+use kernel::{
+ bindings,
+ error::Result,
+ ffi::{c_ulong, c_void},
+ mm::{virt, Mm, MmWithUser},
+ new_mutex, new_spinlock,
+ page::{Page, PAGE_SHIFT, PAGE_SIZE},
+ prelude::*,
+ str::CStr,
+ sync::{aref::ARef, Mutex, SpinLock},
+ task::Pid,
+ transmute::FromBytes,
+ types::Opaque,
+ uaccess::UserSliceReader,
+};
+
+/// Represents a shrinker that can be registered with the kernel.
+///
+/// Each shrinker can be used by many `ShrinkablePageRange` objects.
+#[repr(C)]
+pub(crate) struct Shrinker {
+ inner: Opaque<*mut bindings::shrinker>,
+ list_lru: Opaque<bindings::list_lru>,
+}
+
+// SAFETY: The shrinker and list_lru are thread safe.
+unsafe impl Send for Shrinker {}
+// SAFETY: The shrinker and list_lru are thread safe.
+unsafe impl Sync for Shrinker {}
+
+impl Shrinker {
+ /// Create a new shrinker.
+ ///
+ /// # Safety
+ ///
+ /// Before using this shrinker with a `ShrinkablePageRange`, the `register` method must have
+ /// been called exactly once, and it must not have returned an error.
+ pub(crate) const unsafe fn new() -> Self {
+ Self {
+ inner: Opaque::uninit(),
+ list_lru: Opaque::uninit(),
+ }
+ }
+
+ /// Register this shrinker with the kernel.
+ 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());
+ self.list_lru.get().write_bytes(0, 1);
+ }
+
+ // SAFETY: The field is not yet used, so we can initialize it.
+ let ret = unsafe { bindings::__list_lru_init(self.list_lru.get(), false, ptr::null_mut()) };
+ if ret != 0 {
+ return Err(Error::from_errno(ret));
+ }
+
+ // SAFETY: The `name` points at a valid c string.
+ let shrinker = unsafe { bindings::shrinker_alloc(0, name.as_char_ptr()) };
+ if shrinker.is_null() {
+ // SAFETY: We initialized it, so its okay to destroy it.
+ unsafe { bindings::list_lru_destroy(self.list_lru.get()) };
+ return Err(Error::from_errno(ret));
+ }
+
+ // SAFETY: We're about to register the shrinker, and these are the fields we need to
+ // initialize. (All other fields are already zeroed.)
+ unsafe {
+ (&raw mut (*shrinker).count_objects).write(Some(rust_shrink_count));
+ (&raw mut (*shrinker).scan_objects).write(Some(rust_shrink_scan));
+ (&raw mut (*shrinker).private_data).write(self.list_lru.get().cast());
+ }
+
+ // SAFETY: The new shrinker has been fully initialized, so we can register it.
+ unsafe { bindings::shrinker_register(shrinker) };
+
+ // SAFETY: This initializes the pointer to the shrinker so that we can use it.
+ unsafe { self.inner.get().write(shrinker) };
+
+ Ok(())
+ }
+}
+
+/// A container that manages a page range in a vma.
+///
+/// The pages can be thought of as an array of booleans of whether the pages are usable. The
+/// methods `use_range` and `stop_using_range` set all booleans in a range to true or false
+/// respectively. Initially, no pages are allocated. When a page is not used, it is not freed
+/// immediately. Instead, it is made available to the memory shrinker to free it if the device is
+/// under memory pressure.
+///
+/// It's okay for `use_range` and `stop_using_range` to race with each other, although there's no
+/// way to know whether an index ends up with true or false if a call to `use_range` races with
+/// another call to `stop_using_range` on a given index.
+///
+/// It's also okay for the two methods to race with themselves, e.g. if two threads call
+/// `use_range` on the same index, then that's fine and neither call will return until the page is
+/// allocated and mapped.
+///
+/// The methods that read or write to a range require that the page is marked as in use. So it is
+/// _not_ okay to call `stop_using_range` on a page that is in use by the methods that read or
+/// write to the page.
+#[pin_data(PinnedDrop)]
+pub(crate) struct ShrinkablePageRange {
+ /// Shrinker object registered with the kernel.
+ shrinker: &'static Shrinker,
+ /// Pid using this page range. Only used as debugging information.
+ pid: Pid,
+ /// The mm for the relevant process.
+ mm: ARef<Mm>,
+ /// Used to synchronize calls to `vm_insert_page` and `zap_page_range_single`.
+ #[pin]
+ mm_lock: Mutex<()>,
+ /// Spinlock protecting changes to pages.
+ #[pin]
+ lock: SpinLock<Inner>,
+
+ /// Must not move, since page info has pointers back.
+ #[pin]
+ _pin: PhantomPinned,
+}
+
+struct Inner {
+ /// Array of pages.
+ ///
+ /// Since this is also accessed by the shrinker, we can't use a `Box`, which asserts exclusive
+ /// ownership. To deal with that, we manage it using raw pointers.
+ pages: *mut PageInfo,
+ /// Length of the `pages` array.
+ size: usize,
+ /// The address of the vma to insert the pages into.
+ vma_addr: usize,
+}
+
+// SAFETY: proper locking is in place for `Inner`
+unsafe impl Send for Inner {}
+
+type StableMmGuard =
+ kernel::sync::lock::Guard<'static, (), kernel::sync::lock::mutex::MutexBackend>;
+
+/// An array element that describes the current state of a page.
+///
+/// There are three states:
+///
+/// * Free. The page is None. The `lru` element is not queued.
+/// * Available. The page is Some. The `lru` element is queued to the shrinker's lru.
+/// * Used. The page is Some. The `lru` element is not queued.
+///
+/// When an element is available, the shrinker is able to free the page.
+#[repr(C)]
+struct PageInfo {
+ lru: bindings::list_head,
+ page: Option<Page>,
+ range: *const ShrinkablePageRange,
+}
+
+impl PageInfo {
+ /// # Safety
+ ///
+ /// The caller ensures that writing to `me.page` is ok, and that the page is not currently set.
+ unsafe fn set_page(me: *mut PageInfo, page: Page) {
+ // SAFETY: This pointer offset is in bounds.
+ let ptr = unsafe { &raw mut (*me).page };
+
+ // SAFETY: The pointer is valid for writing, so also valid for reading.
+ if unsafe { (*ptr).is_some() } {
+ pr_err!("set_page called when there is already a page");
+ // SAFETY: We will initialize the page again below.
+ unsafe { ptr::drop_in_place(ptr) };
+ }
+
+ // SAFETY: The pointer is valid for writing.
+ unsafe { ptr::write(ptr, Some(page)) };
+ }
+
+ /// # Safety
+ ///
+ /// The caller ensures that reading from `me.page` is ok for the duration of 'a.
+ unsafe fn get_page<'a>(me: *const PageInfo) -> Option<&'a Page> {
+ // SAFETY: This pointer offset is in bounds.
+ let ptr = unsafe { &raw const (*me).page };
+
+ // SAFETY: The pointer is valid for reading.
+ unsafe { (*ptr).as_ref() }
+ }
+
+ /// # Safety
+ ///
+ /// The caller ensures that writing to `me.page` is ok for the duration of 'a.
+ unsafe fn take_page(me: *mut PageInfo) -> Option<Page> {
+ // SAFETY: This pointer offset is in bounds.
+ let ptr = unsafe { &raw mut (*me).page };
+
+ // SAFETY: The pointer is valid for reading.
+ unsafe { (*ptr).take() }
+ }
+
+ /// Add this page to the lru list, if not already in the list.
+ ///
+ /// # Safety
+ ///
+ /// The pointer must be valid, and it must be the right shrinker and nid.
+ unsafe fn list_lru_add(me: *mut PageInfo, nid: i32, shrinker: &'static Shrinker) {
+ // SAFETY: This pointer offset is in bounds.
+ let lru_ptr = unsafe { &raw mut (*me).lru };
+ // SAFETY: The lru pointer is valid, and we're not using it with any other lru list.
+ unsafe { bindings::list_lru_add(shrinker.list_lru.get(), lru_ptr, nid, ptr::null_mut()) };
+ }
+
+ /// Remove this page from the lru list, if it is in the list.
+ ///
+ /// # Safety
+ ///
+ /// The pointer must be valid, and it must be the right shrinker and nid.
+ unsafe fn list_lru_del(me: *mut PageInfo, nid: i32, shrinker: &'static Shrinker) {
+ // SAFETY: This pointer offset is in bounds.
+ let lru_ptr = unsafe { &raw mut (*me).lru };
+ // SAFETY: The lru pointer is valid, and we're not using it with any other lru list.
+ unsafe { bindings::list_lru_del(shrinker.list_lru.get(), lru_ptr, nid, ptr::null_mut()) };
+ }
+}
+
+impl ShrinkablePageRange {
+ /// Create a new `ShrinkablePageRange` using the given shrinker.
+ pub(crate) fn new(shrinker: &'static Shrinker) -> impl PinInit<Self, Error> {
+ try_pin_init!(Self {
+ shrinker,
+ pid: kernel::current!().pid(),
+ mm: ARef::from(&**kernel::current!().mm().ok_or(ESRCH)?),
+ mm_lock <- new_mutex!((), "ShrinkablePageRange::mm"),
+ lock <- new_spinlock!(Inner {
+ pages: ptr::null_mut(),
+ size: 0,
+ vma_addr: 0,
+ }, "ShrinkablePageRange"),
+ _pin: PhantomPinned,
+ })
+ }
+
+ pub(crate) fn stable_trylock_mm(&self) -> Option<StableMmGuard> {
+ // SAFETY: This extends the duration of the reference. Since this call happens before
+ // `mm_lock` is taken in the destructor of `ShrinkablePageRange`, the destructor will block
+ // until the returned guard is dropped. This ensures that the guard is valid until dropped.
+ let mm_lock = unsafe { &*ptr::from_ref(&self.mm_lock) };
+
+ mm_lock.try_lock()
+ }
+
+ /// Register a vma with this page range. Returns the size of the region.
+ pub(crate) fn register_with_vma(&self, vma: &virt::VmaNew) -> Result<usize> {
+ let num_bytes = usize::min(vma.end() - vma.start(), bindings::SZ_4M as usize);
+ let num_pages = num_bytes >> PAGE_SHIFT;
+
+ if !ptr::eq::<Mm>(&*self.mm, &**vma.mm()) {
+ pr_debug!("Failed to register with vma: invalid vma->vm_mm");
+ return Err(EINVAL);
+ }
+ if num_pages == 0 {
+ pr_debug!("Failed to register with vma: size zero");
+ return Err(EINVAL);
+ }
+
+ let mut pages = KVVec::<PageInfo>::with_capacity(num_pages, GFP_KERNEL)?;
+
+ // SAFETY: This just initializes the pages array.
+ unsafe {
+ let self_ptr = self as *const ShrinkablePageRange;
+ for i in 0..num_pages {
+ let info = pages.as_mut_ptr().add(i);
+ (&raw mut (*info).range).write(self_ptr);
+ (&raw mut (*info).page).write(None);
+ let lru = &raw mut (*info).lru;
+ (&raw mut (*lru).next).write(lru);
+ (&raw mut (*lru).prev).write(lru);
+ }
+ }
+
+ let mut inner = self.lock.lock();
+ if inner.size > 0 {
+ pr_debug!("Failed to register with vma: already registered");
+ drop(inner);
+ return Err(EBUSY);
+ }
+
+ inner.pages = pages.into_raw_parts().0;
+ inner.size = num_pages;
+ inner.vma_addr = vma.start();
+
+ Ok(num_pages)
+ }
+
+ /// 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<()> {
+ if start >= end {
+ return Ok(());
+ }
+ let mut inner = self.lock.lock();
+ assert!(end <= inner.size);
+
+ for i in start..end {
+ // SAFETY: This pointer offset is in bounds.
+ let page_info = unsafe { inner.pages.add(i) };
+
+ // SAFETY: The pointer is valid, and we hold the lock so reading from the page is okay.
+ if let Some(page) = unsafe { PageInfo::get_page(page_info) } {
+ // Since we're going to use the page, we should remove it from the lru list so that
+ // the shrinker will not free it.
+ //
+ // SAFETY: The pointer is valid, and this is the right shrinker.
+ //
+ // The shrinker can't free the page between the check and this call to
+ // `list_lru_del` because we hold the lock.
+ unsafe { PageInfo::list_lru_del(page_info, page.nid(), self.shrinker) };
+ } else {
+ // We have to allocate a new page. Use the slow path.
+ drop(inner);
+ // SAFETY: `i < end <= inner.size` so `i` is in bounds.
+ match unsafe { self.use_page_slow(i) } {
+ Ok(()) => {}
+ Err(err) => {
+ pr_warn!("Error in use_page_slow: {:?}", err);
+ return Err(err);
+ }
+ }
+ inner = self.lock.lock();
+ }
+ }
+ Ok(())
+ }
+
+ /// Mark the given page as in use, slow path.
+ ///
+ /// Must not be called from an atomic context.
+ ///
+ /// # Safety
+ ///
+ /// Assumes that `i` is in bounds.
+ #[cold]
+ 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();
+ let inner = self.lock.lock();
+
+ // SAFETY: This pointer offset is in bounds.
+ let page_info = unsafe { inner.pages.add(i) };
+
+ // SAFETY: The pointer is valid, and we hold the lock so reading from the page is okay.
+ if let Some(page) = unsafe { PageInfo::get_page(page_info) } {
+ // The page was already there, or someone else added the page while we didn't hold the
+ // spinlock.
+ //
+ // SAFETY: The pointer is valid, and this is the right shrinker.
+ //
+ // The shrinker can't free the page between the check and this call to
+ // `list_lru_del` because we hold the lock.
+ unsafe { PageInfo::list_lru_del(page_info, page.nid(), self.shrinker) };
+ return Ok(());
+ }
+
+ let vma_addr = inner.vma_addr;
+ // Release the spinlock while we insert the page into the vma.
+ drop(inner);
+
+ // No overflow since we stay in bounds of the vma.
+ let user_page_addr = vma_addr + (i << PAGE_SHIFT);
+
+ // We use `mmput_async` when dropping the `mm` because `use_page_slow` is usually used from
+ // a remote process. If the call to `mmput` races with the process shutting down, then the
+ // caller of `use_page_slow` becomes responsible for cleaning up the `mm`, which doesn't
+ // happen until it returns to userspace. However, the caller might instead go to sleep and
+ // wait for the owner of the `mm` to wake it up, which doesn't happen because it's in the
+ // middle of a shutdown process that won't complete until the `mm` is dropped. This can
+ // amount to a deadlock.
+ //
+ // Using `mmput_async` avoids this, because then the `mm` cleanup is instead queued to a
+ // workqueue.
+ MmWithUser::into_mmput_async(self.mm.mmget_not_zero().ok_or(ESRCH)?)
+ .mmap_read_lock()
+ .vma_lookup(vma_addr)
+ .ok_or(ESRCH)?
+ .as_mixedmap_vma()
+ .ok_or(ESRCH)?
+ .vm_insert_page(user_page_addr, &new_page)
+ .inspect_err(|err| {
+ pr_warn!(
+ "Failed to vm_insert_page({}): vma_addr:{} i:{} err:{:?}",
+ user_page_addr,
+ vma_addr,
+ i,
+ err
+ )
+ })?;
+
+ let inner = self.lock.lock();
+
+ // SAFETY: The `page_info` pointer is valid and currently does not have a page. The page
+ // can be written to since we hold the lock.
+ //
+ // We released and reacquired the spinlock since we checked that the page is null, but we
+ // always hold the mm_lock mutex when setting the page to a non-null value, so it's not
+ // possible for someone else to have changed it since our check.
+ unsafe { PageInfo::set_page(page_info, new_page) };
+
+ drop(inner);
+ drop(mm_mutex);
+
+ Ok(())
+ }
+
+ /// If the given page is in use, then mark it as available so that the shrinker can free it.
+ ///
+ /// May be called from an atomic context.
+ pub(crate) fn stop_using_range(&self, start: usize, end: usize) {
+ if start >= end {
+ return;
+ }
+ let inner = self.lock.lock();
+ assert!(end <= inner.size);
+
+ for i in (start..end).rev() {
+ // SAFETY: The pointer is in bounds.
+ let page_info = unsafe { inner.pages.add(i) };
+
+ // SAFETY: Okay for reading since we have the lock.
+ if let Some(page) = unsafe { PageInfo::get_page(page_info) } {
+ // SAFETY: The pointer is valid, and it's the right shrinker.
+ unsafe { PageInfo::list_lru_add(page_info, page.nid(), self.shrinker) };
+ }
+ }
+ }
+
+ /// Helper for reading or writing to a range of bytes that may overlap with several pages.
+ ///
+ /// # Safety
+ ///
+ /// All pages touched by this operation must be in use for the duration of this call.
+ unsafe fn iterate<T>(&self, mut offset: usize, mut size: usize, mut cb: T) -> Result
+ where
+ T: FnMut(&Page, usize, usize) -> Result,
+ {
+ if size == 0 {
+ return Ok(());
+ }
+
+ let (pages, num_pages) = {
+ let inner = self.lock.lock();
+ (inner.pages, inner.size)
+ };
+ let num_bytes = num_pages << PAGE_SHIFT;
+
+ // Check that the request is within the buffer.
+ if offset.checked_add(size).ok_or(EFAULT)? > num_bytes {
+ return Err(EFAULT);
+ }
+
+ let mut page_index = offset >> PAGE_SHIFT;
+ offset &= PAGE_SIZE - 1;
+ while size > 0 {
+ let available = usize::min(size, PAGE_SIZE - offset);
+ // SAFETY: The pointer is in bounds.
+ let page_info = unsafe { pages.add(page_index) };
+ // SAFETY: The caller guarantees that this page is in the "in use" state for the
+ // duration of this call to `iterate`, so nobody will change the page.
+ let page = unsafe { PageInfo::get_page(page_info) };
+ if page.is_none() {
+ pr_warn!("Page is null!");
+ }
+ let page = page.ok_or(EFAULT)?;
+ cb(page, offset, available)?;
+ size -= available;
+ page_index += 1;
+ offset = 0;
+ }
+ Ok(())
+ }
+
+ /// Copy from userspace into this page range.
+ ///
+ /// # Safety
+ ///
+ /// All pages touched by this operation must be in use for the duration of this call.
+ pub(crate) unsafe fn copy_from_user_slice(
+ &self,
+ reader: &mut UserSliceReader,
+ offset: usize,
+ size: usize,
+ ) -> Result {
+ // SAFETY: `self.iterate` has the same safety requirements as `copy_from_user_slice`.
+ unsafe {
+ self.iterate(offset, size, |page, offset, to_copy| {
+ page.copy_from_user_slice_raw(reader, offset, to_copy)
+ })
+ }
+ }
+
+ /// Copy from this page range into kernel space.
+ ///
+ /// # Safety
+ ///
+ /// All pages touched by this operation must be in use for the duration of this call.
+ pub(crate) unsafe fn read<T: FromBytes>(&self, offset: usize) -> Result<T> {
+ let mut out = MaybeUninit::<T>::uninit();
+ let mut out_offset = 0;
+ // SAFETY: `self.iterate` has the same safety requirements as `read`.
+ unsafe {
+ self.iterate(offset, size_of::<T>(), |page, offset, to_copy| {
+ // SAFETY: The sum of `offset` and `to_copy` is bounded by the size of T.
+ let obj_ptr = (out.as_mut_ptr() as *mut u8).add(out_offset);
+ // SAFETY: The pointer points is in-bounds of the `out` variable, so it is valid.
+ page.read_raw(obj_ptr, offset, to_copy)?;
+ out_offset += to_copy;
+ Ok(())
+ })?;
+ }
+ // SAFETY: We just initialised the data.
+ Ok(unsafe { out.assume_init() })
+ }
+
+ /// Copy from kernel space into this page range.
+ ///
+ /// # Safety
+ ///
+ /// All pages touched by this operation must be in use for the duration of this call.
+ pub(crate) unsafe fn write<T: ?Sized>(&self, offset: usize, obj: &T) -> Result {
+ let mut obj_offset = 0;
+ // SAFETY: `self.iterate` has the same safety requirements as `write`.
+ unsafe {
+ self.iterate(offset, size_of_val(obj), |page, offset, to_copy| {
+ // SAFETY: The sum of `offset` and `to_copy` is bounded by the size of T.
+ let obj_ptr = (obj as *const T as *const u8).add(obj_offset);
+ // SAFETY: We have a reference to the object, so the pointer is valid.
+ page.write_raw(obj_ptr, offset, to_copy)?;
+ obj_offset += to_copy;
+ Ok(())
+ })
+ }
+ }
+
+ /// Write zeroes to the given range.
+ ///
+ /// # Safety
+ ///
+ /// All pages touched by this operation must be in use for the duration of this call.
+ pub(crate) unsafe fn fill_zero(&self, offset: usize, size: usize) -> Result {
+ // SAFETY: `self.iterate` has the same safety requirements as `copy_into`.
+ unsafe {
+ self.iterate(offset, size, |page, offset, len| {
+ page.fill_zero_raw(offset, len)
+ })
+ }
+ }
+}
+
+#[pinned_drop]
+impl PinnedDrop for ShrinkablePageRange {
+ fn drop(self: Pin<&mut Self>) {
+ let (pages, size) = {
+ let lock = self.lock.lock();
+ (lock.pages, lock.size)
+ };
+
+ if size == 0 {
+ return;
+ }
+
+ // Note: This call is also necessary for the safety of `stable_trylock_mm`.
+ let mm_lock = self.mm_lock.lock();
+
+ // This is the destructor, so unlike the other methods, we only need to worry about races
+ // with the shrinker here. Since we hold the `mm_lock`, we also can't race with the
+ // shrinker, and after this loop, the shrinker will not access any of our pages since we
+ // removed them from the lru list.
+ for i in 0..size {
+ // SAFETY: Loop is in-bounds of the size.
+ let p_ptr = unsafe { pages.add(i) };
+ // SAFETY: No other readers, so we can read.
+ if let Some(p) = unsafe { PageInfo::get_page(p_ptr) } {
+ // SAFETY: The pointer is valid and it's the right shrinker.
+ unsafe { PageInfo::list_lru_del(p_ptr, p.nid(), self.shrinker) };
+ }
+ }
+
+ drop(mm_lock);
+
+ // SAFETY: `pages` was allocated as an `KVVec<PageInfo>` with capacity `size`. Furthermore,
+ // all `size` elements are initialized. Also, the array is no longer shared with the
+ // shrinker due to the above loop.
+ drop(unsafe { KVVec::from_raw_parts(pages, size, size) });
+ }
+}
+
+/// # Safety
+/// Called by the shrinker.
+#[no_mangle]
+unsafe extern "C" fn rust_shrink_count(
+ shrink: *mut bindings::shrinker,
+ _sc: *mut bindings::shrink_control,
+) -> c_ulong {
+ // SAFETY: We can access our own private data.
+ let list_lru = unsafe { (*shrink).private_data.cast::<bindings::list_lru>() };
+ // SAFETY: Accessing the lru list is okay. Just an FFI call.
+ unsafe { bindings::list_lru_count(list_lru) }
+}
+
+/// # Safety
+/// Called by the shrinker.
+#[no_mangle]
+unsafe extern "C" fn rust_shrink_scan(
+ shrink: *mut bindings::shrinker,
+ sc: *mut bindings::shrink_control,
+) -> c_ulong {
+ // SAFETY: We can access our own private data.
+ let list_lru = unsafe { (*shrink).private_data.cast::<bindings::list_lru>() };
+ // SAFETY: Caller guarantees that it is safe to read this field.
+ let nr_to_scan = unsafe { (*sc).nr_to_scan };
+ // SAFETY: Accessing the lru list is okay. Just an FFI call.
+ unsafe {
+ bindings::list_lru_walk(
+ list_lru,
+ Some(bindings::rust_shrink_free_page_wrap),
+ ptr::null_mut(),
+ nr_to_scan,
+ )
+ }
+}
+
+const LRU_SKIP: bindings::lru_status = bindings::lru_status_LRU_SKIP;
+const LRU_REMOVED_ENTRY: bindings::lru_status = bindings::lru_status_LRU_REMOVED_RETRY;
+
+/// # Safety
+/// Called by the shrinker.
+#[no_mangle]
+unsafe extern "C" fn rust_shrink_free_page(
+ item: *mut bindings::list_head,
+ lru: *mut bindings::list_lru_one,
+ _cb_arg: *mut c_void,
+) -> bindings::lru_status {
+ // Fields that should survive after unlocking the lru lock.
+ let page;
+ let page_index;
+ let mm;
+ let mmap_read;
+ let mm_mutex;
+ let vma_addr;
+
+ {
+ // CAST: The `list_head` field is first in `PageInfo`.
+ let info = item as *mut PageInfo;
+ // SAFETY: The `range` field of `PageInfo` is immutable.
+ let range = unsafe { &*((*info).range) };
+
+ mm = match range.mm.mmget_not_zero() {
+ Some(mm) => MmWithUser::into_mmput_async(mm),
+ None => return LRU_SKIP,
+ };
+
+ mm_mutex = match range.stable_trylock_mm() {
+ Some(guard) => guard,
+ None => return LRU_SKIP,
+ };
+
+ mmap_read = match mm.mmap_read_trylock() {
+ Some(guard) => guard,
+ None => return LRU_SKIP,
+ };
+
+ // We can't lock it normally here, since we hold the lru lock.
+ let inner = match range.lock.try_lock() {
+ Some(inner) => inner,
+ None => return LRU_SKIP,
+ };
+
+ // SAFETY: The item is in this lru list, so it's okay to remove it.
+ unsafe { bindings::list_lru_isolate(lru, item) };
+
+ // SAFETY: Both pointers are in bounds of the same allocation.
+ page_index = unsafe { info.offset_from(inner.pages) } as usize;
+
+ // SAFETY: We hold the spinlock, so we can take the page.
+ //
+ // This sets the page pointer to zero before we unmap it from the vma. However, we call
+ // `zap_page_range` before we release the mmap lock, so `use_page_slow` will not be able to
+ // insert a new page until after our call to `zap_page_range`.
+ page = unsafe { PageInfo::take_page(info) };
+ vma_addr = inner.vma_addr;
+
+ // From this point on, we don't access this PageInfo or ShrinkablePageRange again, because
+ // they can be freed at any point after we unlock `lru_lock`. This is with the exception of
+ // `mm_mutex` which is kept alive by holding the lock.
+ }
+
+ // SAFETY: The lru lock is locked when this method is called.
+ unsafe { bindings::spin_unlock(&raw mut (*lru).lock) };
+
+ if let Some(vma) = mmap_read.vma_lookup(vma_addr) {
+ let user_page_addr = vma_addr + (page_index << PAGE_SHIFT);
+ vma.zap_page_range_single(user_page_addr, PAGE_SIZE);
+ }
+
+ drop(mmap_read);
+ drop(mm_mutex);
+ drop(mm);
+ drop(page);
+
+ // SAFETY: We just unlocked the lru lock, but it should be locked when we return.
+ unsafe { bindings::spin_lock(&raw mut (*lru).lock) };
+
+ LRU_REMOVED_ENTRY
+}
diff --git a/drivers/android/binder/page_range_helper.c b/drivers/android/binder/page_range_helper.c
new file mode 100644
index 000000000000..496887723ee0
--- /dev/null
+++ b/drivers/android/binder/page_range_helper.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* C helper for page_range.rs to work around a CFI violation.
+ *
+ * Bindgen currently pretends that `enum lru_status` is the same as an integer.
+ * This assumption is fine ABI-wise, but once you add CFI to the mix, it
+ * triggers a CFI violation because `enum lru_status` gets a different CFI tag.
+ *
+ * This file contains a workaround until bindgen can be fixed.
+ *
+ * Copyright (C) 2025 Google LLC.
+ */
+#include "page_range_helper.h"
+
+unsigned int rust_shrink_free_page(struct list_head *item,
+ struct list_lru_one *list,
+ void *cb_arg);
+
+enum lru_status
+rust_shrink_free_page_wrap(struct list_head *item, struct list_lru_one *list,
+ void *cb_arg)
+{
+ return rust_shrink_free_page(item, list, cb_arg);
+}
diff --git a/drivers/android/binder/page_range_helper.h b/drivers/android/binder/page_range_helper.h
new file mode 100644
index 000000000000..18dd2dd117b2
--- /dev/null
+++ b/drivers/android/binder/page_range_helper.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2025 Google, Inc.
+ */
+
+#ifndef _LINUX_PAGE_RANGE_HELPER_H
+#define _LINUX_PAGE_RANGE_HELPER_H
+
+#include <linux/list_lru.h>
+
+enum lru_status
+rust_shrink_free_page_wrap(struct list_head *item, struct list_lru_one *list,
+ void *cb_arg);
+
+#endif /* _LINUX_PAGE_RANGE_HELPER_H */
diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs
new file mode 100644
index 000000000000..f13a747e784c
--- /dev/null
+++ b/drivers/android/binder/process.rs
@@ -0,0 +1,1696 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+//! This module defines the `Process` type, which represents a process using a particular binder
+//! context.
+//!
+//! The `Process` object keeps track of all of the resources that this process owns in the binder
+//! context.
+//!
+//! There is one `Process` object for each binder fd that a process has opened, so processes using
+//! several binder contexts have several `Process` objects. This ensures that the contexts are
+//! fully separated.
+
+use core::mem::take;
+
+use kernel::{
+ bindings,
+ cred::Credential,
+ error::Error,
+ fs::file::{self, File},
+ list::{List, ListArc, ListArcField, ListLinks},
+ mm,
+ prelude::*,
+ rbtree::{self, RBTree, RBTreeNode, RBTreeNodeReservation},
+ seq_file::SeqFile,
+ seq_print,
+ sync::poll::PollTable,
+ sync::{
+ lock::{spinlock::SpinLockBackend, Guard},
+ Arc, ArcBorrow, CondVar, CondVarTimeoutResult, Mutex, SpinLock, UniqueArc,
+ },
+ task::Task,
+ types::ARef,
+ uaccess::{UserSlice, UserSliceReader},
+ uapi,
+ workqueue::{self, Work},
+};
+
+use crate::{
+ allocation::{Allocation, AllocationInfo, NewAllocation},
+ context::Context,
+ defs::*,
+ error::{BinderError, BinderResult},
+ node::{CouldNotDeliverCriticalIncrement, CritIncrWrapper, Node, NodeDeath, NodeRef},
+ page_range::ShrinkablePageRange,
+ range_alloc::{RangeAllocator, ReserveNew, ReserveNewArgs},
+ stats::BinderStats,
+ thread::{PushWorkRes, Thread},
+ BinderfsProcFile, DArc, DLArc, DTRWrap, DeliverToRead,
+};
+
+#[path = "freeze.rs"]
+mod freeze;
+use self::freeze::{FreezeCookie, FreezeListener};
+
+struct Mapping {
+ address: usize,
+ alloc: RangeAllocator<AllocationInfo>,
+}
+
+impl Mapping {
+ fn new(address: usize, size: usize) -> Self {
+ Self {
+ address,
+ alloc: RangeAllocator::new(size),
+ }
+ }
+}
+
+// bitflags for defer_work.
+const PROC_DEFER_FLUSH: u8 = 1;
+const PROC_DEFER_RELEASE: u8 = 2;
+
+/// The fields of `Process` protected by the spinlock.
+pub(crate) struct ProcessInner {
+ is_manager: bool,
+ pub(crate) is_dead: bool,
+ threads: RBTree<i32, Arc<Thread>>,
+ /// INVARIANT: Threads pushed to this list must be owned by this process.
+ ready_threads: List<Thread>,
+ nodes: RBTree<u64, DArc<Node>>,
+ mapping: Option<Mapping>,
+ work: List<DTRWrap<dyn DeliverToRead>>,
+ delivered_deaths: List<DTRWrap<NodeDeath>, 2>,
+
+ /// The number of requested threads that haven't registered yet.
+ requested_thread_count: u32,
+ /// The maximum number of threads used by the process thread pool.
+ max_threads: u32,
+ /// The number of threads the started and registered with the thread pool.
+ started_thread_count: u32,
+
+ /// Bitmap of deferred work to do.
+ defer_work: u8,
+
+ /// Number of transactions to be transmitted before processes in freeze_wait
+ /// are woken up.
+ outstanding_txns: u32,
+ /// Process is frozen and unable to service binder transactions.
+ pub(crate) is_frozen: bool,
+ /// Process received sync transactions since last frozen.
+ pub(crate) sync_recv: bool,
+ /// Process received async transactions since last frozen.
+ pub(crate) async_recv: bool,
+ pub(crate) binderfs_file: Option<BinderfsProcFile>,
+ /// Check for oneway spam
+ oneway_spam_detection_enabled: bool,
+}
+
+impl ProcessInner {
+ fn new() -> Self {
+ Self {
+ is_manager: false,
+ is_dead: false,
+ threads: RBTree::new(),
+ ready_threads: List::new(),
+ mapping: None,
+ nodes: RBTree::new(),
+ work: List::new(),
+ delivered_deaths: List::new(),
+ requested_thread_count: 0,
+ max_threads: 0,
+ started_thread_count: 0,
+ defer_work: 0,
+ outstanding_txns: 0,
+ is_frozen: false,
+ sync_recv: false,
+ async_recv: false,
+ binderfs_file: None,
+ oneway_spam_detection_enabled: false,
+ }
+ }
+
+ /// Schedule the work item for execution on this process.
+ ///
+ /// If any threads are ready for work, then the work item is given directly to that thread and
+ /// it is woken up. Otherwise, it is pushed to the process work list.
+ ///
+ /// This call can fail only if the process is dead. In this case, the work item is returned to
+ /// the caller so that the caller can drop it after releasing the inner process lock. This is
+ /// necessary since the destructor of `Transaction` will take locks that can't necessarily be
+ /// taken while holding the inner process lock.
+ pub(crate) fn push_work(
+ &mut self,
+ work: DLArc<dyn DeliverToRead>,
+ ) -> Result<(), (BinderError, DLArc<dyn DeliverToRead>)> {
+ // Try to find a ready thread to which to push the work.
+ if let Some(thread) = self.ready_threads.pop_front() {
+ // Push to thread while holding state lock. This prevents the thread from giving up
+ // (for example, because of a signal) when we're about to deliver work.
+ match thread.push_work(work) {
+ PushWorkRes::Ok => Ok(()),
+ PushWorkRes::FailedDead(work) => Err((BinderError::new_dead(), work)),
+ }
+ } else if self.is_dead {
+ Err((BinderError::new_dead(), work))
+ } else {
+ let sync = work.should_sync_wakeup();
+
+ // 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.
+ self.work.push_back(work);
+
+ // Wake up polling threads, if any.
+ for thread in self.threads.values() {
+ thread.notify_if_poll_ready(sync);
+ }
+
+ Ok(())
+ }
+ }
+
+ pub(crate) fn remove_node(&mut self, ptr: u64) {
+ self.nodes.remove(&ptr);
+ }
+
+ /// Updates the reference count on the given node.
+ pub(crate) fn update_node_refcount(
+ &mut self,
+ node: &DArc<Node>,
+ inc: bool,
+ strong: bool,
+ count: usize,
+ othread: Option<&Thread>,
+ ) {
+ let push = node.update_refcount_locked(inc, strong, count, self);
+
+ // If we decided that we need to push work, push either to the process or to a thread if
+ // one is specified.
+ if let Some(node) = push {
+ if let Some(thread) = othread {
+ thread.push_work_deferred(node);
+ } else {
+ let _ = self.push_work(node);
+ // Nothing to do: `push_work` may fail if the process is dead, but that's ok as in
+ // that case, it doesn't care about the notification.
+ }
+ }
+ }
+
+ pub(crate) fn new_node_ref(
+ &mut self,
+ node: DArc<Node>,
+ strong: bool,
+ thread: Option<&Thread>,
+ ) -> NodeRef {
+ self.update_node_refcount(&node, true, strong, 1, thread);
+ let strong_count = if strong { 1 } else { 0 };
+ NodeRef::new(node, strong_count, 1 - strong_count)
+ }
+
+ pub(crate) fn new_node_ref_with_thread(
+ &mut self,
+ node: DArc<Node>,
+ strong: bool,
+ thread: &Thread,
+ wrapper: Option<CritIncrWrapper>,
+ ) -> Result<NodeRef, CouldNotDeliverCriticalIncrement> {
+ let push = match wrapper {
+ None => node
+ .incr_refcount_allow_zero2one(strong, self)?
+ .map(|node| node as _),
+ Some(wrapper) => node.incr_refcount_allow_zero2one_with_wrapper(strong, wrapper, self),
+ };
+ if let Some(node) = push {
+ thread.push_work_deferred(node);
+ }
+ let strong_count = if strong { 1 } else { 0 };
+ Ok(NodeRef::new(node, strong_count, 1 - strong_count))
+ }
+
+ /// Returns an existing node with the given pointer and cookie, if one exists.
+ ///
+ /// Returns an error if a node with the given pointer but a different cookie exists.
+ fn get_existing_node(&self, ptr: u64, cookie: u64) -> Result<Option<DArc<Node>>> {
+ match self.nodes.get(&ptr) {
+ None => Ok(None),
+ Some(node) => {
+ let (_, node_cookie) = node.get_id();
+ if node_cookie == cookie {
+ Ok(Some(node.clone()))
+ } else {
+ Err(EINVAL)
+ }
+ }
+ }
+ }
+
+ fn register_thread(&mut self) -> bool {
+ if self.requested_thread_count == 0 {
+ return false;
+ }
+
+ self.requested_thread_count -= 1;
+ self.started_thread_count += 1;
+ true
+ }
+
+ /// Finds a delivered death notification with the given cookie, removes it from the thread's
+ /// delivered list, and returns it.
+ fn pull_delivered_death(&mut self, cookie: u64) -> Option<DArc<NodeDeath>> {
+ let mut cursor = self.delivered_deaths.cursor_front();
+ while let Some(next) = cursor.peek_next() {
+ if next.cookie == cookie {
+ return Some(next.remove().into_arc());
+ }
+ cursor.move_next();
+ }
+ None
+ }
+
+ pub(crate) fn death_delivered(&mut self, death: DArc<NodeDeath>) {
+ if let Some(death) = ListArc::try_from_arc_or_drop(death) {
+ self.delivered_deaths.push_back(death);
+ } else {
+ pr_warn!("Notification added to `delivered_deaths` twice.");
+ }
+ }
+
+ pub(crate) fn add_outstanding_txn(&mut self) {
+ self.outstanding_txns += 1;
+ }
+
+ fn txns_pending_locked(&self) -> bool {
+ if self.outstanding_txns > 0 {
+ return true;
+ }
+ for thread in self.threads.values() {
+ if thread.has_current_transaction() {
+ return true;
+ }
+ }
+ false
+ }
+}
+
+/// Used to keep track of a node that this process has a handle to.
+#[pin_data]
+pub(crate) struct NodeRefInfo {
+ debug_id: usize,
+ /// The refcount that this process owns to the node.
+ node_ref: ListArcField<NodeRef, { Self::LIST_PROC }>,
+ death: ListArcField<Option<DArc<NodeDeath>>, { Self::LIST_PROC }>,
+ /// Cookie of the active freeze listener for this node.
+ freeze: ListArcField<Option<FreezeCookie>, { Self::LIST_PROC }>,
+ /// Used to store this `NodeRefInfo` in the node's `refs` list.
+ #[pin]
+ links: ListLinks<{ Self::LIST_NODE }>,
+ /// The handle for this `NodeRefInfo`.
+ handle: u32,
+ /// The process that has a handle to the node.
+ pub(crate) process: Arc<Process>,
+}
+
+impl NodeRefInfo {
+ /// The id used for the `Node::refs` list.
+ pub(crate) const LIST_NODE: u64 = 0x2da16350fb724a10;
+ /// The id used for the `ListArc` in `ProcessNodeRefs`.
+ const LIST_PROC: u64 = 0xd703a5263dcc8650;
+
+ fn new(node_ref: NodeRef, handle: u32, process: Arc<Process>) -> impl PinInit<Self> {
+ pin_init!(Self {
+ debug_id: super::next_debug_id(),
+ node_ref: ListArcField::new(node_ref),
+ death: ListArcField::new(None),
+ freeze: ListArcField::new(None),
+ links <- ListLinks::new(),
+ handle,
+ process,
+ })
+ }
+
+ kernel::list::define_list_arc_field_getter! {
+ pub(crate) fn death(&mut self<{Self::LIST_PROC}>) -> &mut Option<DArc<NodeDeath>> { death }
+ pub(crate) fn freeze(&mut self<{Self::LIST_PROC}>) -> &mut Option<FreezeCookie> { freeze }
+ pub(crate) fn node_ref(&mut self<{Self::LIST_PROC}>) -> &mut NodeRef { node_ref }
+ pub(crate) fn node_ref2(&self<{Self::LIST_PROC}>) -> &NodeRef { node_ref }
+ }
+}
+
+kernel::list::impl_list_arc_safe! {
+ impl ListArcSafe<{Self::LIST_NODE}> for NodeRefInfo { untracked; }
+ impl ListArcSafe<{Self::LIST_PROC}> for NodeRefInfo { untracked; }
+}
+kernel::list::impl_list_item! {
+ impl ListItem<{Self::LIST_NODE}> for NodeRefInfo {
+ using ListLinks { self.links };
+ }
+}
+
+/// Keeps track of references this process has to nodes owned by other processes.
+///
+/// TODO: Currently, the rbtree requires two allocations per node reference, and two tree
+/// traversals to look up a node by `Node::global_id`. Once the rbtree is more powerful, these
+/// extra costs should be eliminated.
+struct ProcessNodeRefs {
+ /// Used to look up nodes using the 32-bit id that this process knows it by.
+ by_handle: RBTree<u32, ListArc<NodeRefInfo, { NodeRefInfo::LIST_PROC }>>,
+ /// Used to look up nodes without knowing their local 32-bit id. The usize is the address of
+ /// the underlying `Node` struct as returned by `Node::global_id`.
+ by_node: RBTree<usize, u32>,
+ /// Used to look up a `FreezeListener` by cookie.
+ ///
+ /// There might be multiple freeze listeners for the same node, but at most one of them is
+ /// active.
+ freeze_listeners: RBTree<FreezeCookie, FreezeListener>,
+}
+
+impl ProcessNodeRefs {
+ fn new() -> Self {
+ Self {
+ by_handle: RBTree::new(),
+ by_node: RBTree::new(),
+ freeze_listeners: RBTree::new(),
+ }
+ }
+}
+
+/// A process using binder.
+///
+/// Strictly speaking, there can be multiple of these per process. There is one for each binder fd
+/// that a process has opened, so processes using several binder contexts have several `Process`
+/// objects. This ensures that the contexts are fully separated.
+#[pin_data]
+pub(crate) struct Process {
+ pub(crate) ctx: Arc<Context>,
+
+ // The task leader (process).
+ pub(crate) task: ARef<Task>,
+
+ // Credential associated with file when `Process` is created.
+ pub(crate) cred: ARef<Credential>,
+
+ #[pin]
+ pub(crate) inner: SpinLock<ProcessInner>,
+
+ #[pin]
+ pub(crate) pages: ShrinkablePageRange,
+
+ // Waitqueue of processes waiting for all outstanding transactions to be
+ // processed.
+ #[pin]
+ freeze_wait: CondVar,
+
+ // Node references are in a different lock to avoid recursive acquisition when
+ // incrementing/decrementing a node in another process.
+ #[pin]
+ node_refs: Mutex<ProcessNodeRefs>,
+
+ // Work node for deferred work item.
+ #[pin]
+ defer_work: Work<Process>,
+
+ // Links for process list in Context.
+ #[pin]
+ links: ListLinks,
+
+ pub(crate) stats: BinderStats,
+}
+
+kernel::impl_has_work! {
+ impl HasWork<Process> for Process { self.defer_work }
+}
+
+kernel::list::impl_list_arc_safe! {
+ impl ListArcSafe<0> for Process { untracked; }
+}
+kernel::list::impl_list_item! {
+ impl ListItem<0> for Process {
+ using ListLinks { self.links };
+ }
+}
+
+impl workqueue::WorkItem for Process {
+ type Pointer = Arc<Process>;
+
+ fn run(me: Arc<Self>) {
+ let defer;
+ {
+ let mut inner = me.inner.lock();
+ defer = inner.defer_work;
+ inner.defer_work = 0;
+ }
+
+ if defer & PROC_DEFER_FLUSH != 0 {
+ me.deferred_flush();
+ }
+ if defer & PROC_DEFER_RELEASE != 0 {
+ me.deferred_release();
+ }
+ }
+}
+
+impl Process {
+ fn new(ctx: Arc<Context>, cred: ARef<Credential>) -> Result<Arc<Self>> {
+ let current = kernel::current!();
+ let list_process = ListArc::pin_init::<Error>(
+ try_pin_init!(Process {
+ ctx,
+ cred,
+ inner <- kernel::new_spinlock!(ProcessInner::new(), "Process::inner"),
+ pages <- ShrinkablePageRange::new(&super::BINDER_SHRINKER),
+ node_refs <- kernel::new_mutex!(ProcessNodeRefs::new(), "Process::node_refs"),
+ freeze_wait <- kernel::new_condvar!("Process::freeze_wait"),
+ task: current.group_leader().into(),
+ defer_work <- kernel::new_work!("Process::defer_work"),
+ links <- ListLinks::new(),
+ stats: BinderStats::new(),
+ }),
+ GFP_KERNEL,
+ )?;
+
+ let process = list_process.clone_arc();
+ process.ctx.register_process(list_process);
+
+ Ok(process)
+ }
+
+ pub(crate) fn pid_in_current_ns(&self) -> kernel::task::Pid {
+ self.task.tgid_nr_ns(None)
+ }
+
+ #[inline(never)]
+ 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);
+
+ let inner = self.inner.lock();
+ seq_print!(m, " threads: {}\n", inner.threads.iter().count());
+ seq_print!(
+ m,
+ " requested threads: {}+{}/{}\n",
+ inner.requested_thread_count,
+ inner.started_thread_count,
+ inner.max_threads,
+ );
+ if let Some(mapping) = &inner.mapping {
+ seq_print!(
+ m,
+ " free oneway space: {}\n",
+ mapping.alloc.free_oneway_space()
+ );
+ seq_print!(m, " buffers: {}\n", mapping.alloc.count_buffers());
+ }
+ seq_print!(
+ m,
+ " outstanding transactions: {}\n",
+ inner.outstanding_txns
+ );
+ seq_print!(m, " nodes: {}\n", inner.nodes.iter().count());
+ drop(inner);
+
+ {
+ let mut refs = self.node_refs.lock();
+ let (mut count, mut weak, mut strong) = (0, 0, 0);
+ for r in refs.by_handle.values_mut() {
+ let node_ref = r.node_ref();
+ let (nstrong, nweak) = node_ref.get_count();
+ count += 1;
+ weak += nweak;
+ strong += nstrong;
+ }
+ seq_print!(m, " refs: {count} s {strong} w {weak}\n");
+ }
+
+ self.stats.debug_print(" ", m);
+
+ Ok(())
+ }
+
+ #[inline(never)]
+ 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);
+
+ let mut all_threads = KVec::new();
+ let mut all_nodes = KVec::new();
+ loop {
+ let inner = self.inner.lock();
+ let num_threads = inner.threads.iter().count();
+ let num_nodes = inner.nodes.iter().count();
+
+ if all_threads.capacity() < num_threads || all_nodes.capacity() < num_nodes {
+ drop(inner);
+ all_threads.reserve(num_threads, GFP_KERNEL)?;
+ all_nodes.reserve(num_nodes, GFP_KERNEL)?;
+ continue;
+ }
+
+ for thread in inner.threads.values() {
+ assert!(all_threads.len() < all_threads.capacity());
+ let _ = all_threads.push(thread.clone(), GFP_ATOMIC);
+ }
+
+ for node in inner.nodes.values() {
+ assert!(all_nodes.len() < all_nodes.capacity());
+ let _ = all_nodes.push(node.clone(), GFP_ATOMIC);
+ }
+
+ break;
+ }
+
+ for thread in all_threads {
+ thread.debug_print(m, print_all)?;
+ }
+
+ let mut inner = self.inner.lock();
+ for node in all_nodes {
+ if print_all || node.has_oneway_transaction(&mut inner) {
+ node.full_debug_print(m, &mut inner)?;
+ }
+ }
+ drop(inner);
+
+ if print_all {
+ let mut refs = self.node_refs.lock();
+ for r in refs.by_handle.values_mut() {
+ let node_ref = r.node_ref();
+ let dead = node_ref.node.owner.inner.lock().is_dead;
+ let (strong, weak) = node_ref.get_count();
+ let debug_id = node_ref.node.debug_id;
+
+ seq_print!(
+ m,
+ " ref {}: desc {} {}node {debug_id} s {strong} w {weak}",
+ r.debug_id,
+ r.handle,
+ if dead { "dead " } else { "" },
+ );
+ }
+ }
+
+ let inner = self.inner.lock();
+ for work in &inner.work {
+ work.debug_print(m, " ", " pending transaction ")?;
+ }
+ for _death in &inner.delivered_deaths {
+ seq_print!(m, " has delivered dead binder\n");
+ }
+ if let Some(mapping) = &inner.mapping {
+ mapping.alloc.debug_print(m)?;
+ }
+ drop(inner);
+
+ Ok(())
+ }
+
+ /// Attempts to fetch a work item from the process queue.
+ pub(crate) fn get_work(&self) -> Option<DLArc<dyn DeliverToRead>> {
+ self.inner.lock().work.pop_front()
+ }
+
+ /// Attempts to fetch a work item from the process queue. If none is available, it registers the
+ /// given thread as ready to receive work directly.
+ ///
+ /// This must only be called when the thread is not participating in a transaction chain; when
+ /// it is, work will always be delivered directly to the thread (and not through the process
+ /// queue).
+ pub(crate) fn get_work_or_register<'a>(
+ &'a self,
+ thread: &'a Arc<Thread>,
+ ) -> GetWorkOrRegister<'a> {
+ let mut inner = self.inner.lock();
+ // Try to get work from the process queue.
+ if let Some(work) = inner.work.pop_front() {
+ return GetWorkOrRegister::Work(work);
+ }
+
+ // Register the thread as ready.
+ GetWorkOrRegister::Register(Registration::new(thread, &mut inner))
+ }
+
+ fn get_current_thread(self: ArcBorrow<'_, Self>) -> Result<Arc<Thread>> {
+ let id = {
+ let current = kernel::current!();
+ if !core::ptr::eq(current.group_leader(), &*self.task) {
+ pr_err!("get_current_thread was called from the wrong process.");
+ return Err(EINVAL);
+ }
+ current.pid()
+ };
+
+ {
+ let inner = self.inner.lock();
+ if let Some(thread) = inner.threads.get(&id) {
+ return Ok(thread.clone());
+ }
+ }
+
+ // Allocate a new `Thread` without holding any locks.
+ let reservation = RBTreeNodeReservation::new(GFP_KERNEL)?;
+ let ta: Arc<Thread> = Thread::new(id, self.into())?;
+
+ let mut inner = self.inner.lock();
+ match inner.threads.entry(id) {
+ rbtree::Entry::Vacant(entry) => {
+ entry.insert(ta.clone(), reservation);
+ Ok(ta)
+ }
+ rbtree::Entry::Occupied(_entry) => {
+ pr_err!("Cannot create two threads with the same id.");
+ Err(EINVAL)
+ }
+ }
+ }
+
+ pub(crate) fn push_work(&self, work: DLArc<dyn DeliverToRead>) -> BinderResult {
+ // If push_work fails, drop the work item outside the lock.
+ let res = self.inner.lock().push_work(work);
+ match res {
+ Ok(()) => Ok(()),
+ Err((err, work)) => {
+ drop(work);
+ Err(err)
+ }
+ }
+ }
+
+ fn set_as_manager(
+ self: ArcBorrow<'_, Self>,
+ info: Option<FlatBinderObject>,
+ thread: &Thread,
+ ) -> Result {
+ let (ptr, cookie, flags) = if let Some(obj) = info {
+ (
+ // SAFETY: The object type for this ioctl is implicitly `BINDER_TYPE_BINDER`, so it
+ // is safe to access the `binder` field.
+ unsafe { obj.__bindgen_anon_1.binder },
+ obj.cookie,
+ obj.flags,
+ )
+ } else {
+ (0, 0, 0)
+ };
+ let node_ref = self.get_node(ptr, cookie, flags as _, true, thread)?;
+ let node = node_ref.node.clone();
+ self.ctx.set_manager_node(node_ref)?;
+ self.inner.lock().is_manager = true;
+
+ // Force the state of the node to prevent the delivery of acquire/increfs.
+ let mut owner_inner = node.owner.inner.lock();
+ node.force_has_count(&mut owner_inner);
+ Ok(())
+ }
+
+ fn get_node_inner(
+ self: ArcBorrow<'_, Self>,
+ ptr: u64,
+ cookie: u64,
+ flags: u32,
+ strong: bool,
+ thread: &Thread,
+ wrapper: Option<CritIncrWrapper>,
+ ) -> Result<Result<NodeRef, CouldNotDeliverCriticalIncrement>> {
+ // Try to find an existing node.
+ {
+ let mut inner = self.inner.lock();
+ if let Some(node) = inner.get_existing_node(ptr, cookie)? {
+ return Ok(inner.new_node_ref_with_thread(node, strong, thread, wrapper));
+ }
+ }
+
+ // Allocate the node before reacquiring the lock.
+ let node = DTRWrap::arc_pin_init(Node::new(ptr, cookie, flags, self.into()))?.into_arc();
+ let rbnode = RBTreeNode::new(ptr, node.clone(), GFP_KERNEL)?;
+ let mut inner = self.inner.lock();
+ if let Some(node) = inner.get_existing_node(ptr, cookie)? {
+ return Ok(inner.new_node_ref_with_thread(node, strong, thread, wrapper));
+ }
+
+ inner.nodes.insert(rbnode);
+ // This can only fail if someone has already pushed the node to a list, but we just created
+ // it and still hold the lock, so it can't fail right now.
+ let node_ref = inner
+ .new_node_ref_with_thread(node, strong, thread, wrapper)
+ .unwrap();
+
+ Ok(Ok(node_ref))
+ }
+
+ pub(crate) fn get_node(
+ self: ArcBorrow<'_, Self>,
+ ptr: u64,
+ cookie: u64,
+ flags: u32,
+ strong: bool,
+ thread: &Thread,
+ ) -> Result<NodeRef> {
+ let mut wrapper = None;
+ for _ in 0..2 {
+ match self.get_node_inner(ptr, cookie, flags, strong, thread, wrapper) {
+ Err(err) => return Err(err),
+ Ok(Ok(node_ref)) => return Ok(node_ref),
+ Ok(Err(CouldNotDeliverCriticalIncrement)) => {
+ wrapper = Some(CritIncrWrapper::new()?);
+ }
+ }
+ }
+ // We only get a `CouldNotDeliverCriticalIncrement` error if `wrapper` is `None`, so the
+ // loop should run at most twice.
+ unreachable!()
+ }
+
+ pub(crate) fn insert_or_update_handle(
+ self: ArcBorrow<'_, Process>,
+ node_ref: NodeRef,
+ is_mananger: bool,
+ ) -> Result<u32> {
+ {
+ let mut refs = self.node_refs.lock();
+
+ // Do a lookup before inserting.
+ if let Some(handle_ref) = refs.by_node.get(&node_ref.node.global_id()) {
+ let handle = *handle_ref;
+ let info = refs.by_handle.get_mut(&handle).unwrap();
+ info.node_ref().absorb(node_ref);
+ return Ok(handle);
+ }
+ }
+
+ // Reserve memory for tree nodes.
+ let reserve1 = RBTreeNodeReservation::new(GFP_KERNEL)?;
+ let reserve2 = RBTreeNodeReservation::new(GFP_KERNEL)?;
+ let info = UniqueArc::new_uninit(GFP_KERNEL)?;
+
+ let mut refs = self.node_refs.lock();
+
+ // Do a lookup again as node may have been inserted before the lock was reacquired.
+ if let Some(handle_ref) = refs.by_node.get(&node_ref.node.global_id()) {
+ let handle = *handle_ref;
+ let info = refs.by_handle.get_mut(&handle).unwrap();
+ info.node_ref().absorb(node_ref);
+ return Ok(handle);
+ }
+
+ // Find id.
+ let mut target: u32 = if is_mananger { 0 } else { 1 };
+ for handle in refs.by_handle.keys() {
+ if *handle > target {
+ break;
+ }
+ if *handle == target {
+ target = target.checked_add(1).ok_or(ENOMEM)?;
+ }
+ }
+
+ let gid = node_ref.node.global_id();
+ let (info_proc, info_node) = {
+ let info_init = NodeRefInfo::new(node_ref, target, self.into());
+ match info.pin_init_with(info_init) {
+ Ok(info) => ListArc::pair_from_pin_unique(info),
+ // error is infallible
+ Err(err) => match err {},
+ }
+ };
+
+ // Ensure the process is still alive while we insert a new reference.
+ //
+ // This releases the lock before inserting the nodes, but since `is_dead` is set as the
+ // first thing in `deferred_release`, process cleanup will not miss the items inserted into
+ // `refs` below.
+ if self.inner.lock().is_dead {
+ return Err(ESRCH);
+ }
+
+ // SAFETY: `info_proc` and `info_node` reference the same node, so we are inserting
+ // `info_node` into the right node's `refs` list.
+ unsafe { info_proc.node_ref2().node.insert_node_info(info_node) };
+
+ refs.by_node.insert(reserve1.into_node(gid, target));
+ refs.by_handle.insert(reserve2.into_node(target, info_proc));
+ Ok(target)
+ }
+
+ pub(crate) fn get_transaction_node(&self, handle: u32) -> BinderResult<NodeRef> {
+ // When handle is zero, try to get the context manager.
+ if handle == 0 {
+ Ok(self.ctx.get_manager_node(true)?)
+ } else {
+ Ok(self.get_node_from_handle(handle, true)?)
+ }
+ }
+
+ pub(crate) fn get_node_from_handle(&self, handle: u32, strong: bool) -> Result<NodeRef> {
+ self.node_refs
+ .lock()
+ .by_handle
+ .get_mut(&handle)
+ .ok_or(ENOENT)?
+ .node_ref()
+ .clone(strong)
+ }
+
+ pub(crate) fn remove_from_delivered_deaths(&self, death: &DArc<NodeDeath>) {
+ let mut inner = self.inner.lock();
+ // SAFETY: By the invariant on the `delivered_links` field, this is the right linked list.
+ let removed = unsafe { inner.delivered_deaths.remove(death) };
+ drop(inner);
+ drop(removed);
+ }
+
+ pub(crate) fn update_ref(
+ self: ArcBorrow<'_, Process>,
+ handle: u32,
+ inc: bool,
+ strong: bool,
+ ) -> Result {
+ if inc && handle == 0 {
+ if let Ok(node_ref) = self.ctx.get_manager_node(strong) {
+ if core::ptr::eq(&*self, &*node_ref.node.owner) {
+ return Err(EINVAL);
+ }
+ let _ = self.insert_or_update_handle(node_ref, true);
+ return Ok(());
+ }
+ }
+
+ // To preserve original binder behaviour, we only fail requests where the manager tries to
+ // increment references on itself.
+ let mut refs = self.node_refs.lock();
+ if let Some(info) = refs.by_handle.get_mut(&handle) {
+ if info.node_ref().update(inc, strong) {
+ // Clean up death if there is one attached to this node reference.
+ if let Some(death) = info.death().take() {
+ death.set_cleared(true);
+ self.remove_from_delivered_deaths(&death);
+ }
+
+ // Remove reference from process tables, and from the node's `refs` list.
+
+ // SAFETY: We are removing the `NodeRefInfo` from the right node.
+ unsafe { info.node_ref2().node.remove_node_info(info) };
+
+ let id = info.node_ref().node.global_id();
+ refs.by_handle.remove(&handle);
+ refs.by_node.remove(&id);
+ }
+ } else {
+ // All refs are cleared in process exit, so this warning is expected in that case.
+ if !self.inner.lock().is_dead {
+ pr_warn!("{}: no such ref {handle}\n", self.pid_in_current_ns());
+ }
+ }
+ Ok(())
+ }
+
+ /// Decrements the refcount of the given node, if one exists.
+ pub(crate) fn update_node(&self, ptr: u64, cookie: u64, strong: bool) {
+ let mut inner = self.inner.lock();
+ if let Ok(Some(node)) = inner.get_existing_node(ptr, cookie) {
+ inner.update_node_refcount(&node, false, strong, 1, None);
+ }
+ }
+
+ pub(crate) fn inc_ref_done(&self, reader: &mut UserSliceReader, strong: bool) -> Result {
+ let ptr = reader.read::<u64>()?;
+ let cookie = reader.read::<u64>()?;
+ let mut inner = self.inner.lock();
+ if let Ok(Some(node)) = inner.get_existing_node(ptr, cookie) {
+ if let Some(node) = node.inc_ref_done_locked(strong, &mut inner) {
+ // This only fails if the process is dead.
+ let _ = inner.push_work(node);
+ }
+ }
+ Ok(())
+ }
+
+ pub(crate) fn buffer_alloc(
+ self: &Arc<Self>,
+ debug_id: usize,
+ size: usize,
+ is_oneway: bool,
+ from_pid: i32,
+ ) -> BinderResult<NewAllocation> {
+ use kernel::page::PAGE_SIZE;
+
+ let mut reserve_new_args = ReserveNewArgs {
+ debug_id,
+ size,
+ is_oneway,
+ pid: from_pid,
+ ..ReserveNewArgs::default()
+ };
+
+ let (new_alloc, addr) = loop {
+ let mut inner = self.inner.lock();
+ 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,
+ };
+ drop(inner);
+ // We need to allocate memory and then call `reserve_new` again.
+ reserve_new_args = alloc_request.make_alloc()?;
+ };
+
+ let res = Allocation::new(
+ self.clone(),
+ debug_id,
+ new_alloc.offset,
+ size,
+ addr + new_alloc.offset,
+ new_alloc.oneway_spam_detected,
+ );
+
+ // This allocation will be marked as in use until the `Allocation` is used to free it.
+ //
+ // This method can't be called while holding a lock, so we release the lock first. It's
+ // okay for several threads to use the method on the same index at the same time. In that
+ // case, one of the calls will allocate the given page (if missing), and the other call
+ // will wait for the other call to finish allocating the page.
+ //
+ // We will not call `stop_using_range` in parallel with this on the same page, because the
+ // allocation can only be removed via the destructor of the `Allocation` object that we
+ // currently own.
+ match self.pages.use_range(
+ new_alloc.offset / PAGE_SIZE,
+ (new_alloc.offset + size).div_ceil(PAGE_SIZE),
+ ) {
+ Ok(()) => {}
+ Err(err) => {
+ pr_warn!("use_range failure {:?}", err);
+ return Err(err.into());
+ }
+ }
+
+ Ok(NewAllocation(res))
+ }
+
+ pub(crate) fn buffer_get(self: &Arc<Self>, ptr: usize) -> Option<Allocation> {
+ let mut inner = self.inner.lock();
+ let mapping = inner.mapping.as_mut()?;
+ let offset = ptr.checked_sub(mapping.address)?;
+ let (size, debug_id, odata) = mapping.alloc.reserve_existing(offset).ok()?;
+ let mut alloc = Allocation::new(self.clone(), debug_id, offset, size, ptr, false);
+ if let Some(data) = odata {
+ alloc.set_info(data);
+ }
+ Some(alloc)
+ }
+
+ pub(crate) fn buffer_raw_free(&self, ptr: usize) {
+ let mut inner = self.inner.lock();
+ if let Some(ref mut mapping) = &mut inner.mapping {
+ let offset = match ptr.checked_sub(mapping.address) {
+ Some(offset) => offset,
+ None => return,
+ };
+
+ let freed_range = match mapping.alloc.reservation_abort(offset) {
+ Ok(freed_range) => freed_range,
+ Err(_) => {
+ pr_warn!(
+ "Pointer {:x} failed to free, base = {:x}\n",
+ ptr,
+ mapping.address
+ );
+ return;
+ }
+ };
+
+ // No more allocations in this range. Mark them as not in use.
+ //
+ // Must be done before we release the lock so that `use_range` is not used on these
+ // indices until `stop_using_range` returns.
+ self.pages
+ .stop_using_range(freed_range.start_page_idx, freed_range.end_page_idx);
+ }
+ }
+
+ pub(crate) fn buffer_make_freeable(&self, offset: usize, mut data: Option<AllocationInfo>) {
+ let mut inner = self.inner.lock();
+ if let Some(ref mut mapping) = &mut inner.mapping {
+ if mapping.alloc.reservation_commit(offset, &mut data).is_err() {
+ pr_warn!("Offset {} failed to be marked freeable\n", offset);
+ }
+ }
+ }
+
+ fn create_mapping(&self, vma: &mm::virt::VmaNew) -> Result {
+ use kernel::page::PAGE_SIZE;
+ let size = usize::min(vma.end() - vma.start(), bindings::SZ_4M as usize);
+ let mapping = Mapping::new(vma.start(), size);
+ let page_count = self.pages.register_with_vma(vma)?;
+ if page_count * PAGE_SIZE != size {
+ return Err(EINVAL);
+ }
+
+ // Save range allocator for later.
+ self.inner.lock().mapping = Some(mapping);
+
+ Ok(())
+ }
+
+ fn version(&self, data: UserSlice) -> Result {
+ data.writer().write(&BinderVersion::current())
+ }
+
+ pub(crate) fn register_thread(&self) -> bool {
+ self.inner.lock().register_thread()
+ }
+
+ fn remove_thread(&self, thread: Arc<Thread>) {
+ self.inner.lock().threads.remove(&thread.id);
+ thread.release();
+ }
+
+ fn set_max_threads(&self, max: u32) {
+ self.inner.lock().max_threads = max;
+ }
+
+ fn set_oneway_spam_detection_enabled(&self, enabled: u32) {
+ self.inner.lock().oneway_spam_detection_enabled = enabled != 0;
+ }
+
+ pub(crate) fn is_oneway_spam_detection_enabled(&self) -> bool {
+ self.inner.lock().oneway_spam_detection_enabled
+ }
+
+ fn get_node_debug_info(&self, data: UserSlice) -> Result {
+ let (mut reader, mut writer) = data.reader_writer();
+
+ // Read the starting point.
+ let ptr = reader.read::<BinderNodeDebugInfo>()?.ptr;
+ let mut out = BinderNodeDebugInfo::default();
+
+ {
+ let inner = self.inner.lock();
+ for (node_ptr, node) in &inner.nodes {
+ if *node_ptr > ptr {
+ node.populate_debug_info(&mut out, &inner);
+ break;
+ }
+ }
+ }
+
+ writer.write(&out)
+ }
+
+ fn get_node_info_from_ref(&self, data: UserSlice) -> Result {
+ let (mut reader, mut writer) = data.reader_writer();
+ let mut out = reader.read::<BinderNodeInfoForRef>()?;
+
+ if out.strong_count != 0
+ || out.weak_count != 0
+ || out.reserved1 != 0
+ || out.reserved2 != 0
+ || out.reserved3 != 0
+ {
+ return Err(EINVAL);
+ }
+
+ // Only the context manager is allowed to use this ioctl.
+ if !self.inner.lock().is_manager {
+ return Err(EPERM);
+ }
+
+ {
+ let mut node_refs = self.node_refs.lock();
+ let node_info = node_refs.by_handle.get_mut(&out.handle).ok_or(ENOENT)?;
+ let node_ref = node_info.node_ref();
+ let owner_inner = node_ref.node.owner.inner.lock();
+ node_ref.node.populate_counts(&mut out, &owner_inner);
+ }
+
+ // Write the result back.
+ writer.write(&out)
+ }
+
+ pub(crate) fn needs_thread(&self) -> bool {
+ let mut inner = self.inner.lock();
+ let ret = inner.requested_thread_count == 0
+ && inner.ready_threads.is_empty()
+ && inner.started_thread_count < inner.max_threads;
+ if ret {
+ inner.requested_thread_count += 1
+ }
+ ret
+ }
+
+ pub(crate) fn request_death(
+ self: &Arc<Self>,
+ reader: &mut UserSliceReader,
+ thread: &Thread,
+ ) -> Result {
+ let handle: u32 = reader.read()?;
+ let cookie: u64 = reader.read()?;
+
+ // Queue BR_ERROR if we can't allocate memory for the death notification.
+ let death = UniqueArc::new_uninit(GFP_KERNEL).inspect_err(|_| {
+ thread.push_return_work(BR_ERROR);
+ })?;
+ let mut refs = self.node_refs.lock();
+ let Some(info) = refs.by_handle.get_mut(&handle) else {
+ pr_warn!("BC_REQUEST_DEATH_NOTIFICATION invalid ref {handle}\n");
+ return Ok(());
+ };
+
+ // Nothing to do if there is already a death notification request for this handle.
+ if info.death().is_some() {
+ pr_warn!("BC_REQUEST_DEATH_NOTIFICATION death notification already set\n");
+ 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 {},
+ }
+ };
+
+ // Register the death notification.
+ {
+ let owner = info.node_ref2().node.owner.clone();
+ let mut owner_inner = owner.inner.lock();
+ if owner_inner.is_dead {
+ let death = Arc::from(death);
+ *info.death() = Some(death.clone());
+ drop(owner_inner);
+ death.set_dead();
+ } else {
+ let death = ListArc::from(death);
+ *info.death() = Some(death.clone_arc());
+ info.node_ref().node.add_death(death, &mut owner_inner);
+ }
+ }
+ Ok(())
+ }
+
+ pub(crate) fn clear_death(&self, reader: &mut UserSliceReader, thread: &Thread) -> Result {
+ let handle: u32 = reader.read()?;
+ let cookie: u64 = reader.read()?;
+
+ let mut refs = self.node_refs.lock();
+ let Some(info) = refs.by_handle.get_mut(&handle) else {
+ pr_warn!("BC_CLEAR_DEATH_NOTIFICATION invalid ref {handle}\n");
+ return Ok(());
+ };
+
+ let Some(death) = info.death().take() else {
+ pr_warn!("BC_CLEAR_DEATH_NOTIFICATION death notification not active\n");
+ return Ok(());
+ };
+ if death.cookie != cookie {
+ *info.death() = Some(death);
+ pr_warn!("BC_CLEAR_DEATH_NOTIFICATION death notification cookie mismatch\n");
+ return Ok(());
+ }
+
+ // Update state and determine if we need to queue a work item. We only need to do it when
+ // the node is not dead or if the user already completed the death notification.
+ if death.set_cleared(false) {
+ if let Some(death) = ListArc::try_from_arc_or_drop(death) {
+ let _ = thread.push_work_if_looper(death);
+ }
+ }
+
+ Ok(())
+ }
+
+ pub(crate) fn dead_binder_done(&self, cookie: u64, thread: &Thread) {
+ if let Some(death) = self.inner.lock().pull_delivered_death(cookie) {
+ death.set_notification_done(thread);
+ }
+ }
+
+ /// Locks the spinlock and move the `nodes` rbtree out.
+ ///
+ /// This allows you to iterate through `nodes` while also allowing you to give other parts of
+ /// the codebase exclusive access to `ProcessInner`.
+ pub(crate) fn lock_with_nodes(&self) -> WithNodes<'_> {
+ let mut inner = self.inner.lock();
+ WithNodes {
+ nodes: take(&mut inner.nodes),
+ inner,
+ }
+ }
+
+ fn deferred_flush(&self) {
+ let inner = self.inner.lock();
+ for thread in inner.threads.values() {
+ thread.exit_looper();
+ }
+ }
+
+ fn deferred_release(self: Arc<Self>) {
+ let is_manager = {
+ let mut inner = self.inner.lock();
+ inner.is_dead = true;
+ inner.is_frozen = false;
+ inner.sync_recv = false;
+ inner.async_recv = false;
+ inner.is_manager
+ };
+
+ if is_manager {
+ self.ctx.unset_manager_node();
+ }
+
+ self.ctx.deregister_process(&self);
+
+ let binderfs_file = self.inner.lock().binderfs_file.take();
+ drop(binderfs_file);
+
+ // Release threads.
+ let threads = {
+ let mut inner = self.inner.lock();
+ let threads = take(&mut inner.threads);
+ let ready = take(&mut inner.ready_threads);
+ drop(inner);
+ drop(ready);
+
+ for thread in threads.values() {
+ thread.release();
+ }
+ threads
+ };
+
+ // Release nodes.
+ {
+ while let Some(node) = {
+ let mut lock = self.inner.lock();
+ lock.nodes.cursor_front().map(|c| c.remove_current().1)
+ } {
+ node.to_key_value().1.release();
+ }
+ }
+
+ // Clean up death listeners and remove nodes from external node info lists.
+ for info in self.node_refs.lock().by_handle.values_mut() {
+ // SAFETY: We are removing the `NodeRefInfo` from the right node.
+ unsafe { info.node_ref2().node.remove_node_info(info) };
+
+ // Remove all death notifications from the nodes (that belong to a different process).
+ let death = if let Some(existing) = info.death().take() {
+ existing
+ } else {
+ continue;
+ };
+ death.set_cleared(false);
+ }
+
+ // Clean up freeze listeners.
+ let freeze_listeners = take(&mut self.node_refs.lock().freeze_listeners);
+ for listener in freeze_listeners.values() {
+ listener.on_process_exit(&self);
+ }
+ drop(freeze_listeners);
+
+ // Release refs on foreign nodes.
+ {
+ let mut refs = self.node_refs.lock();
+ let by_handle = take(&mut refs.by_handle);
+ let by_node = take(&mut refs.by_node);
+ drop(refs);
+ drop(by_node);
+ drop(by_handle);
+ }
+
+ // Cancel all pending work items.
+ while let Some(work) = self.get_work() {
+ work.into_arc().cancel();
+ }
+
+ let delivered_deaths = take(&mut self.inner.lock().delivered_deaths);
+ drop(delivered_deaths);
+
+ // Free any resources kept alive by allocated buffers.
+ let omapping = self.inner.lock().mapping.take();
+ if let Some(mut mapping) = omapping {
+ let address = mapping.address;
+ mapping
+ .alloc
+ .take_for_each(|offset, size, debug_id, odata| {
+ let ptr = offset + address;
+ pr_warn!(
+ "{}: removing orphan mapping {offset}:{size}\n",
+ self.pid_in_current_ns()
+ );
+ let mut alloc =
+ Allocation::new(self.clone(), debug_id, offset, size, ptr, false);
+ if let Some(data) = odata {
+ alloc.set_info(data);
+ }
+ drop(alloc)
+ });
+ }
+
+ // calls to synchronize_rcu() in thread drop will happen here
+ drop(threads);
+ }
+
+ pub(crate) fn drop_outstanding_txn(&self) {
+ let wake = {
+ let mut inner = self.inner.lock();
+ if inner.outstanding_txns == 0 {
+ pr_err!("outstanding_txns underflow");
+ return;
+ }
+ inner.outstanding_txns -= 1;
+ inner.is_frozen && inner.outstanding_txns == 0
+ };
+
+ if wake {
+ self.freeze_wait.notify_all();
+ }
+ }
+
+ pub(crate) fn ioctl_freeze(&self, info: &BinderFreezeInfo) -> Result {
+ if info.enable == 0 {
+ let msgs = self.prepare_freeze_messages()?;
+ let mut inner = self.inner.lock();
+ inner.sync_recv = false;
+ inner.async_recv = false;
+ inner.is_frozen = false;
+ drop(inner);
+ msgs.send_messages();
+ return Ok(());
+ }
+
+ let mut inner = self.inner.lock();
+ inner.sync_recv = false;
+ inner.async_recv = false;
+ inner.is_frozen = true;
+
+ if info.timeout_ms > 0 {
+ let mut jiffies = kernel::time::msecs_to_jiffies(info.timeout_ms);
+ while jiffies > 0 {
+ if inner.outstanding_txns == 0 {
+ break;
+ }
+
+ match self
+ .freeze_wait
+ .wait_interruptible_timeout(&mut inner, jiffies)
+ {
+ CondVarTimeoutResult::Signal { .. } => {
+ inner.is_frozen = false;
+ return Err(ERESTARTSYS);
+ }
+ CondVarTimeoutResult::Woken { jiffies: remaining } => {
+ jiffies = remaining;
+ }
+ CondVarTimeoutResult::Timeout => {
+ jiffies = 0;
+ }
+ }
+ }
+ }
+
+ if inner.txns_pending_locked() {
+ inner.is_frozen = false;
+ Err(EAGAIN)
+ } else {
+ drop(inner);
+ match self.prepare_freeze_messages() {
+ Ok(batch) => {
+ batch.send_messages();
+ Ok(())
+ }
+ Err(kernel::alloc::AllocError) => {
+ self.inner.lock().is_frozen = false;
+ Err(ENOMEM)
+ }
+ }
+ }
+ }
+}
+
+fn get_frozen_status(data: UserSlice) -> Result {
+ let (mut reader, mut writer) = data.reader_writer();
+
+ let mut info = reader.read::<BinderFrozenStatusInfo>()?;
+ info.sync_recv = 0;
+ info.async_recv = 0;
+ let mut found = false;
+
+ for ctx in crate::context::get_all_contexts()? {
+ ctx.for_each_proc(|proc| {
+ if proc.task.pid() == info.pid as _ {
+ found = true;
+ let inner = proc.inner.lock();
+ let txns_pending = inner.txns_pending_locked();
+ info.async_recv |= inner.async_recv as u32;
+ info.sync_recv |= inner.sync_recv as u32;
+ info.sync_recv |= (txns_pending as u32) << 1;
+ }
+ });
+ }
+
+ if found {
+ writer.write(&info)?;
+ Ok(())
+ } else {
+ Err(EINVAL)
+ }
+}
+
+fn ioctl_freeze(reader: &mut UserSliceReader) -> Result {
+ let info = reader.read::<BinderFreezeInfo>()?;
+
+ // Very unlikely for there to be more than 3, since a process normally uses at most binder and
+ // hwbinder.
+ let mut procs = KVec::with_capacity(3, GFP_KERNEL)?;
+
+ let ctxs = crate::context::get_all_contexts()?;
+ for ctx in ctxs {
+ for proc in ctx.get_procs_with_pid(info.pid as i32)? {
+ procs.push(proc, GFP_KERNEL)?;
+ }
+ }
+
+ for proc in procs {
+ proc.ioctl_freeze(&info)?;
+ }
+ Ok(())
+}
+
+/// The ioctl handler.
+impl Process {
+ /// Ioctls that are write-only from the perspective of userspace.
+ ///
+ /// The kernel will only read from the pointer that userspace provided to us.
+ fn ioctl_write_only(
+ this: ArcBorrow<'_, Process>,
+ _file: &File,
+ cmd: u32,
+ reader: &mut UserSliceReader,
+ ) -> Result {
+ let thread = this.get_current_thread()?;
+ match cmd {
+ uapi::BINDER_SET_MAX_THREADS => this.set_max_threads(reader.read()?),
+ uapi::BINDER_THREAD_EXIT => this.remove_thread(thread),
+ uapi::BINDER_SET_CONTEXT_MGR => this.set_as_manager(None, &thread)?,
+ uapi::BINDER_SET_CONTEXT_MGR_EXT => {
+ this.set_as_manager(Some(reader.read()?), &thread)?
+ }
+ uapi::BINDER_ENABLE_ONEWAY_SPAM_DETECTION => {
+ this.set_oneway_spam_detection_enabled(reader.read()?)
+ }
+ uapi::BINDER_FREEZE => ioctl_freeze(reader)?,
+ _ => return Err(EINVAL),
+ }
+ Ok(())
+ }
+
+ /// Ioctls that are read/write from the perspective of userspace.
+ ///
+ /// The kernel will both read from and write to the pointer that userspace provided to us.
+ fn ioctl_write_read(
+ this: ArcBorrow<'_, Process>,
+ file: &File,
+ cmd: u32,
+ data: UserSlice,
+ ) -> Result {
+ let thread = this.get_current_thread()?;
+ let blocking = (file.flags() & file::flags::O_NONBLOCK) == 0;
+ match cmd {
+ uapi::BINDER_WRITE_READ => thread.write_read(data, blocking)?,
+ uapi::BINDER_GET_NODE_DEBUG_INFO => this.get_node_debug_info(data)?,
+ uapi::BINDER_GET_NODE_INFO_FOR_REF => this.get_node_info_from_ref(data)?,
+ uapi::BINDER_VERSION => this.version(data)?,
+ uapi::BINDER_GET_FROZEN_INFO => get_frozen_status(data)?,
+ uapi::BINDER_GET_EXTENDED_ERROR => thread.get_extended_error(data)?,
+ _ => return Err(EINVAL),
+ }
+ Ok(())
+ }
+}
+
+/// The file operations supported by `Process`.
+impl Process {
+ pub(crate) fn open(ctx: ArcBorrow<'_, Context>, file: &File) -> Result<Arc<Process>> {
+ Self::new(ctx.into(), ARef::from(file.cred()))
+ }
+
+ pub(crate) fn release(this: Arc<Process>, _file: &File) {
+ let binderfs_file;
+ let should_schedule;
+ {
+ let mut inner = this.inner.lock();
+ should_schedule = inner.defer_work == 0;
+ inner.defer_work |= PROC_DEFER_RELEASE;
+ binderfs_file = inner.binderfs_file.take();
+ }
+
+ if should_schedule {
+ // Ignore failures to schedule to the workqueue. Those just mean that we're already
+ // scheduled for execution.
+ let _ = workqueue::system().enqueue(this);
+ }
+
+ drop(binderfs_file);
+ }
+
+ pub(crate) fn flush(this: ArcBorrow<'_, Process>) -> Result {
+ let should_schedule;
+ {
+ let mut inner = this.inner.lock();
+ should_schedule = inner.defer_work == 0;
+ inner.defer_work |= PROC_DEFER_FLUSH;
+ }
+
+ if should_schedule {
+ // Ignore failures to schedule to the workqueue. Those just mean that we're already
+ // scheduled for execution.
+ let _ = workqueue::system().enqueue(Arc::from(this));
+ }
+ Ok(())
+ }
+
+ pub(crate) fn ioctl(this: ArcBorrow<'_, Process>, file: &File, cmd: u32, arg: usize) -> Result {
+ use kernel::ioctl::{_IOC_DIR, _IOC_SIZE};
+ use kernel::uapi::{_IOC_READ, _IOC_WRITE};
+
+ crate::trace::trace_ioctl(cmd, arg);
+
+ let user_slice = UserSlice::new(UserPtr::from_addr(arg), _IOC_SIZE(cmd));
+
+ const _IOC_READ_WRITE: u32 = _IOC_READ | _IOC_WRITE;
+
+ match _IOC_DIR(cmd) {
+ _IOC_WRITE => Self::ioctl_write_only(this, file, cmd, &mut user_slice.reader()),
+ _IOC_READ_WRITE => Self::ioctl_write_read(this, file, cmd, user_slice),
+ _ => Err(EINVAL),
+ }
+ }
+
+ pub(crate) fn compat_ioctl(
+ this: ArcBorrow<'_, Process>,
+ file: &File,
+ cmd: u32,
+ arg: usize,
+ ) -> Result {
+ Self::ioctl(this, file, cmd, arg)
+ }
+
+ pub(crate) fn mmap(
+ this: ArcBorrow<'_, Process>,
+ _file: &File,
+ vma: &mm::virt::VmaNew,
+ ) -> Result {
+ // We don't allow mmap to be used in a different process.
+ if !core::ptr::eq(kernel::current!().group_leader(), &*this.task) {
+ return Err(EINVAL);
+ }
+ if vma.start() == 0 {
+ return Err(EINVAL);
+ }
+
+ vma.try_clear_maywrite().map_err(|_| EPERM)?;
+ vma.set_dontcopy();
+ vma.set_mixedmap();
+
+ // TODO: Set ops. We need to learn when the user unmaps so that we can stop using it.
+ this.create_mapping(vma)
+ }
+
+ pub(crate) fn poll(
+ this: ArcBorrow<'_, Process>,
+ file: &File,
+ table: PollTable<'_>,
+ ) -> Result<u32> {
+ let thread = this.get_current_thread()?;
+ let (from_proc, mut mask) = thread.poll(file, table);
+ if mask == 0 && from_proc && !this.inner.lock().work.is_empty() {
+ mask |= bindings::POLLIN;
+ }
+ Ok(mask)
+ }
+}
+
+/// Represents that a thread has registered with the `ready_threads` list of its process.
+///
+/// The destructor of this type will unregister the thread from the list of ready threads.
+pub(crate) struct Registration<'a> {
+ thread: &'a Arc<Thread>,
+}
+
+impl<'a> Registration<'a> {
+ fn new(thread: &'a Arc<Thread>, guard: &mut Guard<'_, ProcessInner, SpinLockBackend>) -> Self {
+ assert!(core::ptr::eq(&thread.process.inner, guard.lock_ref()));
+ // INVARIANT: We are pushing this thread to the right `ready_threads` list.
+ if let Ok(list_arc) = ListArc::try_from_arc(thread.clone()) {
+ guard.ready_threads.push_front(list_arc);
+ } else {
+ // It is an error to hit this branch, and it should not be reachable. We try to do
+ // something reasonable when the failure path happens. Most likely, the thread in
+ // question will sleep forever.
+ pr_err!("Same thread registered with `ready_threads` twice.");
+ }
+ Self { thread }
+ }
+}
+
+impl Drop for Registration<'_> {
+ fn drop(&mut self) {
+ let mut inner = self.thread.process.inner.lock();
+ // SAFETY: The thread has the invariant that we never push it to any other linked list than
+ // the `ready_threads` list of its parent process. Therefore, the thread is either in that
+ // list, or in no list.
+ unsafe { inner.ready_threads.remove(self.thread) };
+ }
+}
+
+pub(crate) struct WithNodes<'a> {
+ pub(crate) inner: Guard<'a, ProcessInner, SpinLockBackend>,
+ pub(crate) nodes: RBTree<u64, DArc<Node>>,
+}
+
+impl Drop for WithNodes<'_> {
+ fn drop(&mut self) {
+ core::mem::swap(&mut self.nodes, &mut self.inner.nodes);
+ if self.nodes.iter().next().is_some() {
+ pr_err!("nodes array was modified while using lock_with_nodes\n");
+ }
+ }
+}
+
+pub(crate) enum GetWorkOrRegister<'a> {
+ Work(DLArc<dyn DeliverToRead>),
+ Register(Registration<'a>),
+}
diff --git a/drivers/android/binder/range_alloc/array.rs b/drivers/android/binder/range_alloc/array.rs
new file mode 100644
index 000000000000..07e1dec2ce63
--- /dev/null
+++ b/drivers/android/binder/range_alloc/array.rs
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+use kernel::{
+ page::{PAGE_MASK, PAGE_SIZE},
+ prelude::*,
+ seq_file::SeqFile,
+ seq_print,
+ task::Pid,
+};
+
+use crate::range_alloc::{DescriptorState, FreedRange, Range};
+
+/// Keeps track of allocations in a process' mmap.
+///
+/// Each process has an mmap where the data for incoming transactions will be placed. This struct
+/// keeps track of allocations made in the mmap. For each allocation, we store a descriptor that
+/// has metadata related to the allocation. We also keep track of available free space.
+pub(super) struct ArrayRangeAllocator<T> {
+ /// This stores all ranges that are allocated. Unlike the tree based allocator, we do *not*
+ /// store the free ranges.
+ ///
+ /// Sorted by offset.
+ pub(super) ranges: KVec<Range<T>>,
+ size: usize,
+ free_oneway_space: usize,
+}
+
+struct FindEmptyRes {
+ /// Which index in `ranges` should we insert the new range at?
+ ///
+ /// Inserting the new range at this index keeps `ranges` sorted.
+ insert_at_idx: usize,
+ /// Which offset should we insert the new range at?
+ insert_at_offset: usize,
+}
+
+impl<T> ArrayRangeAllocator<T> {
+ pub(crate) fn new(size: usize, alloc: EmptyArrayAlloc<T>) -> Self {
+ Self {
+ ranges: alloc.ranges,
+ size,
+ free_oneway_space: size / 2,
+ }
+ }
+
+ pub(crate) fn free_oneway_space(&self) -> usize {
+ self.free_oneway_space
+ }
+
+ pub(crate) fn count_buffers(&self) -> usize {
+ self.ranges.len()
+ }
+
+ pub(crate) fn total_size(&self) -> usize {
+ self.size
+ }
+
+ pub(crate) fn is_full(&self) -> bool {
+ self.ranges.len() == self.ranges.capacity()
+ }
+
+ pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> {
+ for range in &self.ranges {
+ seq_print!(
+ m,
+ " buffer {}: {} size {} pid {} oneway {}",
+ 0,
+ range.offset,
+ range.size,
+ range.state.pid(),
+ range.state.is_oneway(),
+ );
+ if let DescriptorState::Reserved(_) = range.state {
+ seq_print!(m, " reserved\n");
+ } else {
+ seq_print!(m, " allocated\n");
+ }
+ }
+ Ok(())
+ }
+
+ /// Find somewhere to put a new range.
+ ///
+ /// Unlike the tree implementation, we do not bother to find the smallest gap. The idea is that
+ /// fragmentation isn't a big issue when we don't have many ranges.
+ ///
+ /// Returns the index that the new range should have in `self.ranges` after insertion.
+ fn find_empty_range(&self, size: usize) -> Option<FindEmptyRes> {
+ let after_last_range = self.ranges.last().map(Range::endpoint).unwrap_or(0);
+
+ if size <= self.total_size() - after_last_range {
+ // We can put the range at the end, so just do that.
+ Some(FindEmptyRes {
+ insert_at_idx: self.ranges.len(),
+ insert_at_offset: after_last_range,
+ })
+ } else {
+ let mut end_of_prev = 0;
+ for (i, range) in self.ranges.iter().enumerate() {
+ // Does it fit before the i'th range?
+ if size <= range.offset - end_of_prev {
+ return Some(FindEmptyRes {
+ insert_at_idx: i,
+ insert_at_offset: end_of_prev,
+ });
+ }
+ end_of_prev = range.endpoint();
+ }
+ None
+ }
+ }
+
+ pub(crate) fn reserve_new(
+ &mut self,
+ debug_id: usize,
+ size: usize,
+ is_oneway: bool,
+ pid: Pid,
+ ) -> Result<usize> {
+ // Compute new value of free_oneway_space, which is set only on success.
+ let new_oneway_space = if is_oneway {
+ match self.free_oneway_space.checked_sub(size) {
+ Some(new_oneway_space) => new_oneway_space,
+ None => return Err(ENOSPC),
+ }
+ } else {
+ self.free_oneway_space
+ };
+
+ let FindEmptyRes {
+ insert_at_idx,
+ insert_at_offset,
+ } = self.find_empty_range(size).ok_or(ENOSPC)?;
+ self.free_oneway_space = new_oneway_space;
+
+ let new_range = Range {
+ offset: insert_at_offset,
+ size,
+ state: DescriptorState::new(is_oneway, debug_id, pid),
+ };
+ // Insert the value at the given index to keep the array sorted.
+ self.ranges
+ .insert_within_capacity(insert_at_idx, new_range)
+ .ok()
+ .unwrap();
+
+ Ok(insert_at_offset)
+ }
+
+ pub(crate) fn reservation_abort(&mut self, offset: usize) -> Result<FreedRange> {
+ // This could use a binary search, but linear scans are usually faster for small arrays.
+ let i = self
+ .ranges
+ .iter()
+ .position(|range| range.offset == offset)
+ .ok_or(EINVAL)?;
+ let range = &self.ranges[i];
+
+ if let DescriptorState::Allocated(_) = range.state {
+ return Err(EPERM);
+ }
+
+ let size = range.size;
+ let offset = range.offset;
+
+ if range.state.is_oneway() {
+ self.free_oneway_space += size;
+ }
+
+ // This computes the range of pages that are no longer used by *any* allocated range. The
+ // caller will mark them as unused, which means that they can be freed if the system comes
+ // under memory pressure.
+ let mut freed_range = FreedRange::interior_pages(offset, size);
+ #[expect(clippy::collapsible_if)] // reads better like this
+ if offset % PAGE_SIZE != 0 {
+ if i == 0 || self.ranges[i - 1].endpoint() <= (offset & PAGE_MASK) {
+ freed_range.start_page_idx -= 1;
+ }
+ }
+ if range.endpoint() % PAGE_SIZE != 0 {
+ let page_after = (range.endpoint() & PAGE_MASK) + PAGE_SIZE;
+ if i + 1 == self.ranges.len() || page_after <= self.ranges[i + 1].offset {
+ freed_range.end_page_idx += 1;
+ }
+ }
+
+ self.ranges.remove(i)?;
+ Ok(freed_range)
+ }
+
+ pub(crate) fn reservation_commit(&mut self, offset: usize, data: &mut Option<T>) -> Result {
+ // This could use a binary search, but linear scans are usually faster for small arrays.
+ let range = self
+ .ranges
+ .iter_mut()
+ .find(|range| range.offset == offset)
+ .ok_or(ENOENT)?;
+
+ let DescriptorState::Reserved(reservation) = &range.state else {
+ return Err(ENOENT);
+ };
+
+ range.state = DescriptorState::Allocated(reservation.clone().allocate(data.take()));
+ Ok(())
+ }
+
+ pub(crate) fn reserve_existing(&mut self, offset: usize) -> Result<(usize, usize, Option<T>)> {
+ // This could use a binary search, but linear scans are usually faster for small arrays.
+ let range = self
+ .ranges
+ .iter_mut()
+ .find(|range| range.offset == offset)
+ .ok_or(ENOENT)?;
+
+ let DescriptorState::Allocated(allocation) = &mut range.state else {
+ return Err(ENOENT);
+ };
+
+ let data = allocation.take();
+ let debug_id = allocation.reservation.debug_id;
+ range.state = DescriptorState::Reserved(allocation.reservation.clone());
+ Ok((range.size, debug_id, data))
+ }
+
+ pub(crate) fn take_for_each<F: Fn(usize, usize, usize, Option<T>)>(&mut self, callback: F) {
+ for range in self.ranges.iter_mut() {
+ if let DescriptorState::Allocated(allocation) = &mut range.state {
+ callback(
+ range.offset,
+ range.size,
+ allocation.reservation.debug_id,
+ allocation.data.take(),
+ );
+ }
+ }
+ }
+}
+
+pub(crate) struct EmptyArrayAlloc<T> {
+ ranges: KVec<Range<T>>,
+}
+
+impl<T> EmptyArrayAlloc<T> {
+ pub(crate) fn try_new(capacity: usize) -> Result<Self> {
+ Ok(Self {
+ ranges: KVec::with_capacity(capacity, GFP_KERNEL)?,
+ })
+ }
+}
diff --git a/drivers/android/binder/range_alloc/mod.rs b/drivers/android/binder/range_alloc/mod.rs
new file mode 100644
index 000000000000..2301e2bc1a1f
--- /dev/null
+++ b/drivers/android/binder/range_alloc/mod.rs
@@ -0,0 +1,329 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+use kernel::{page::PAGE_SIZE, prelude::*, seq_file::SeqFile, task::Pid};
+
+mod tree;
+use self::tree::{FromArrayAllocs, ReserveNewTreeAlloc, TreeRangeAllocator};
+
+mod array;
+use self::array::{ArrayRangeAllocator, EmptyArrayAlloc};
+
+enum DescriptorState<T> {
+ Reserved(Reservation),
+ Allocated(Allocation<T>),
+}
+
+impl<T> DescriptorState<T> {
+ fn new(is_oneway: bool, debug_id: usize, pid: Pid) -> Self {
+ DescriptorState::Reserved(Reservation {
+ debug_id,
+ is_oneway,
+ pid,
+ })
+ }
+
+ fn pid(&self) -> Pid {
+ match self {
+ DescriptorState::Reserved(inner) => inner.pid,
+ DescriptorState::Allocated(inner) => inner.reservation.pid,
+ }
+ }
+
+ fn is_oneway(&self) -> bool {
+ match self {
+ DescriptorState::Reserved(inner) => inner.is_oneway,
+ DescriptorState::Allocated(inner) => inner.reservation.is_oneway,
+ }
+ }
+}
+
+#[derive(Clone)]
+struct Reservation {
+ debug_id: usize,
+ is_oneway: bool,
+ pid: Pid,
+}
+
+impl Reservation {
+ fn allocate<T>(self, data: Option<T>) -> Allocation<T> {
+ Allocation {
+ data,
+ reservation: self,
+ }
+ }
+}
+
+struct Allocation<T> {
+ reservation: Reservation,
+ data: Option<T>,
+}
+
+impl<T> Allocation<T> {
+ fn deallocate(self) -> (Reservation, Option<T>) {
+ (self.reservation, self.data)
+ }
+
+ fn debug_id(&self) -> usize {
+ self.reservation.debug_id
+ }
+
+ fn take(&mut self) -> Option<T> {
+ self.data.take()
+ }
+}
+
+/// The array implementation must switch to the tree if it wants to go beyond this number of
+/// ranges.
+const TREE_THRESHOLD: usize = 8;
+
+/// Represents a range of pages that have just become completely free.
+#[derive(Copy, Clone)]
+pub(crate) struct FreedRange {
+ pub(crate) start_page_idx: usize,
+ pub(crate) end_page_idx: usize,
+}
+
+impl FreedRange {
+ fn interior_pages(offset: usize, size: usize) -> FreedRange {
+ FreedRange {
+ // Divide round up
+ start_page_idx: offset.div_ceil(PAGE_SIZE),
+ // Divide round down
+ end_page_idx: (offset + size) / PAGE_SIZE,
+ }
+ }
+}
+
+struct Range<T> {
+ offset: usize,
+ size: usize,
+ state: DescriptorState<T>,
+}
+
+impl<T> Range<T> {
+ fn endpoint(&self) -> usize {
+ self.offset + self.size
+ }
+}
+
+pub(crate) struct RangeAllocator<T> {
+ inner: Impl<T>,
+}
+
+enum Impl<T> {
+ Empty(usize),
+ Array(ArrayRangeAllocator<T>),
+ Tree(TreeRangeAllocator<T>),
+}
+
+impl<T> RangeAllocator<T> {
+ pub(crate) fn new(size: usize) -> Self {
+ Self {
+ inner: Impl::Empty(size),
+ }
+ }
+
+ pub(crate) fn free_oneway_space(&self) -> usize {
+ match &self.inner {
+ Impl::Empty(size) => size / 2,
+ Impl::Array(array) => array.free_oneway_space(),
+ Impl::Tree(tree) => tree.free_oneway_space(),
+ }
+ }
+
+ pub(crate) fn count_buffers(&self) -> usize {
+ match &self.inner {
+ Impl::Empty(_size) => 0,
+ Impl::Array(array) => array.count_buffers(),
+ Impl::Tree(tree) => tree.count_buffers(),
+ }
+ }
+
+ pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> {
+ match &self.inner {
+ Impl::Empty(_size) => Ok(()),
+ Impl::Array(array) => array.debug_print(m),
+ Impl::Tree(tree) => tree.debug_print(m),
+ }
+ }
+
+ /// Try to reserve a new buffer, using the provided allocation if necessary.
+ pub(crate) fn reserve_new(&mut self, mut args: ReserveNewArgs<T>) -> Result<ReserveNew<T>> {
+ match &mut self.inner {
+ Impl::Empty(size) => {
+ let empty_array = match args.empty_array_alloc.take() {
+ Some(empty_array) => ArrayRangeAllocator::new(*size, empty_array),
+ None => {
+ return Ok(ReserveNew::NeedAlloc(ReserveNewNeedAlloc {
+ args,
+ need_empty_array_alloc: true,
+ need_new_tree_alloc: false,
+ need_tree_alloc: false,
+ }))
+ }
+ };
+
+ self.inner = Impl::Array(empty_array);
+ self.reserve_new(args)
+ }
+ Impl::Array(array) if array.is_full() => {
+ let allocs = match args.new_tree_alloc {
+ Some(ref mut allocs) => allocs,
+ None => {
+ return Ok(ReserveNew::NeedAlloc(ReserveNewNeedAlloc {
+ args,
+ need_empty_array_alloc: false,
+ need_new_tree_alloc: true,
+ need_tree_alloc: true,
+ }))
+ }
+ };
+
+ let new_tree =
+ TreeRangeAllocator::from_array(array.total_size(), &mut array.ranges, allocs);
+
+ self.inner = Impl::Tree(new_tree);
+ self.reserve_new(args)
+ }
+ Impl::Array(array) => {
+ let offset =
+ array.reserve_new(args.debug_id, args.size, args.is_oneway, args.pid)?;
+ Ok(ReserveNew::Success(ReserveNewSuccess {
+ offset,
+ oneway_spam_detected: false,
+ _empty_array_alloc: args.empty_array_alloc,
+ _new_tree_alloc: args.new_tree_alloc,
+ _tree_alloc: args.tree_alloc,
+ }))
+ }
+ Impl::Tree(tree) => {
+ let alloc = match args.tree_alloc {
+ Some(alloc) => alloc,
+ None => {
+ return Ok(ReserveNew::NeedAlloc(ReserveNewNeedAlloc {
+ args,
+ need_empty_array_alloc: false,
+ need_new_tree_alloc: false,
+ need_tree_alloc: true,
+ }));
+ }
+ };
+ let (offset, oneway_spam_detected) =
+ tree.reserve_new(args.debug_id, args.size, args.is_oneway, args.pid, alloc)?;
+ Ok(ReserveNew::Success(ReserveNewSuccess {
+ offset,
+ oneway_spam_detected,
+ _empty_array_alloc: args.empty_array_alloc,
+ _new_tree_alloc: args.new_tree_alloc,
+ _tree_alloc: None,
+ }))
+ }
+ }
+ }
+
+ /// Deletes the allocations at `offset`.
+ pub(crate) fn reservation_abort(&mut self, offset: usize) -> Result<FreedRange> {
+ match &mut self.inner {
+ Impl::Empty(_size) => Err(EINVAL),
+ Impl::Array(array) => array.reservation_abort(offset),
+ Impl::Tree(tree) => {
+ let freed_range = tree.reservation_abort(offset)?;
+ if tree.is_empty() {
+ self.inner = Impl::Empty(tree.total_size());
+ }
+ Ok(freed_range)
+ }
+ }
+ }
+
+ /// Called when an allocation is no longer in use by the kernel.
+ ///
+ /// The value in `data` will be stored, if any. A mutable reference is used to avoid dropping
+ /// the `T` when an error is returned.
+ pub(crate) fn reservation_commit(&mut self, offset: usize, data: &mut Option<T>) -> Result {
+ match &mut self.inner {
+ Impl::Empty(_size) => Err(EINVAL),
+ Impl::Array(array) => array.reservation_commit(offset, data),
+ Impl::Tree(tree) => tree.reservation_commit(offset, data),
+ }
+ }
+
+ /// Called when the kernel starts using an allocation.
+ ///
+ /// Returns the size of the existing entry and the data associated with it.
+ pub(crate) fn reserve_existing(&mut self, offset: usize) -> Result<(usize, usize, Option<T>)> {
+ match &mut self.inner {
+ Impl::Empty(_size) => Err(EINVAL),
+ Impl::Array(array) => array.reserve_existing(offset),
+ Impl::Tree(tree) => tree.reserve_existing(offset),
+ }
+ }
+
+ /// Call the provided callback at every allocated region.
+ ///
+ /// This destroys the range allocator. Used only during shutdown.
+ pub(crate) fn take_for_each<F: Fn(usize, usize, usize, Option<T>)>(&mut self, callback: F) {
+ match &mut self.inner {
+ Impl::Empty(_size) => {}
+ Impl::Array(array) => array.take_for_each(callback),
+ Impl::Tree(tree) => tree.take_for_each(callback),
+ }
+ }
+}
+
+/// The arguments for `reserve_new`.
+#[derive(Default)]
+pub(crate) struct ReserveNewArgs<T> {
+ pub(crate) size: usize,
+ pub(crate) is_oneway: bool,
+ pub(crate) debug_id: usize,
+ pub(crate) pid: Pid,
+ pub(crate) empty_array_alloc: Option<EmptyArrayAlloc<T>>,
+ pub(crate) new_tree_alloc: Option<FromArrayAllocs<T>>,
+ pub(crate) tree_alloc: Option<ReserveNewTreeAlloc<T>>,
+}
+
+/// The return type of `ReserveNew`.
+pub(crate) enum ReserveNew<T> {
+ Success(ReserveNewSuccess<T>),
+ NeedAlloc(ReserveNewNeedAlloc<T>),
+}
+
+/// Returned by `reserve_new` when the reservation was successul.
+pub(crate) struct ReserveNewSuccess<T> {
+ pub(crate) offset: usize,
+ pub(crate) oneway_spam_detected: bool,
+
+ // If the user supplied an allocation that we did not end up using, then we return it here.
+ // The caller will kfree it outside of the lock.
+ _empty_array_alloc: Option<EmptyArrayAlloc<T>>,
+ _new_tree_alloc: Option<FromArrayAllocs<T>>,
+ _tree_alloc: Option<ReserveNewTreeAlloc<T>>,
+}
+
+/// Returned by `reserve_new` to request the caller to make an allocation before calling the method
+/// again.
+pub(crate) struct ReserveNewNeedAlloc<T> {
+ args: ReserveNewArgs<T>,
+ need_empty_array_alloc: bool,
+ need_new_tree_alloc: bool,
+ need_tree_alloc: bool,
+}
+
+impl<T> ReserveNewNeedAlloc<T> {
+ /// Make the necessary allocations for another call to `reserve_new`.
+ pub(crate) fn make_alloc(mut self) -> Result<ReserveNewArgs<T>> {
+ if self.need_empty_array_alloc && self.args.empty_array_alloc.is_none() {
+ self.args.empty_array_alloc = Some(EmptyArrayAlloc::try_new(TREE_THRESHOLD)?);
+ }
+ if self.need_new_tree_alloc && self.args.new_tree_alloc.is_none() {
+ self.args.new_tree_alloc = Some(FromArrayAllocs::try_new(TREE_THRESHOLD)?);
+ }
+ if self.need_tree_alloc && self.args.tree_alloc.is_none() {
+ self.args.tree_alloc = Some(ReserveNewTreeAlloc::try_new()?);
+ }
+ Ok(self.args)
+ }
+}
diff --git a/drivers/android/binder/range_alloc/tree.rs b/drivers/android/binder/range_alloc/tree.rs
new file mode 100644
index 000000000000..7b1a248fcb02
--- /dev/null
+++ b/drivers/android/binder/range_alloc/tree.rs
@@ -0,0 +1,488 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+use kernel::{
+ page::PAGE_SIZE,
+ prelude::*,
+ rbtree::{RBTree, RBTreeNode, RBTreeNodeReservation},
+ seq_file::SeqFile,
+ seq_print,
+ task::Pid,
+};
+
+use crate::range_alloc::{DescriptorState, FreedRange, Range};
+
+/// Keeps track of allocations in a process' mmap.
+///
+/// Each process has an mmap where the data for incoming transactions will be placed. This struct
+/// keeps track of allocations made in the mmap. For each allocation, we store a descriptor that
+/// has metadata related to the allocation. We also keep track of available free space.
+pub(super) struct TreeRangeAllocator<T> {
+ /// This collection contains descriptors for *both* ranges containing an allocation, *and* free
+ /// ranges between allocations. The free ranges get merged, so there are never two free ranges
+ /// next to each other.
+ tree: RBTree<usize, Descriptor<T>>,
+ /// Contains an entry for every free range in `self.tree`. This tree sorts the ranges by size,
+ /// letting us look up the smallest range whose size is at least some lower bound.
+ free_tree: RBTree<FreeKey, ()>,
+ size: usize,
+ free_oneway_space: usize,
+}
+
+impl<T> TreeRangeAllocator<T> {
+ pub(crate) fn from_array(
+ size: usize,
+ ranges: &mut KVec<Range<T>>,
+ alloc: &mut FromArrayAllocs<T>,
+ ) -> Self {
+ let mut tree = TreeRangeAllocator {
+ tree: RBTree::new(),
+ free_tree: RBTree::new(),
+ size,
+ free_oneway_space: size / 2,
+ };
+
+ let mut free_offset = 0;
+ for range in ranges.drain_all() {
+ let free_size = range.offset - free_offset;
+ if free_size > 0 {
+ let free_node = alloc.free_tree.pop().unwrap();
+ tree.free_tree
+ .insert(free_node.into_node((free_size, free_offset), ()));
+ let tree_node = alloc.tree.pop().unwrap();
+ tree.tree.insert(
+ tree_node.into_node(free_offset, Descriptor::new(free_offset, free_size)),
+ );
+ }
+ free_offset = range.endpoint();
+
+ if range.state.is_oneway() {
+ tree.free_oneway_space = tree.free_oneway_space.saturating_sub(range.size);
+ }
+
+ let free_res = alloc.free_tree.pop().unwrap();
+ let tree_node = alloc.tree.pop().unwrap();
+ let mut desc = Descriptor::new(range.offset, range.size);
+ desc.state = Some((range.state, free_res));
+ tree.tree.insert(tree_node.into_node(range.offset, desc));
+ }
+
+ // After the last range, we may need a free range.
+ if free_offset < size {
+ let free_size = size - free_offset;
+ let free_node = alloc.free_tree.pop().unwrap();
+ tree.free_tree
+ .insert(free_node.into_node((free_size, free_offset), ()));
+ let tree_node = alloc.tree.pop().unwrap();
+ tree.tree
+ .insert(tree_node.into_node(free_offset, Descriptor::new(free_offset, free_size)));
+ }
+
+ tree
+ }
+
+ pub(crate) fn is_empty(&self) -> bool {
+ let mut tree_iter = self.tree.values();
+ // There's always at least one range, because index zero is either the start of a free or
+ // allocated range.
+ let first_value = tree_iter.next().unwrap();
+ if tree_iter.next().is_some() {
+ // There are never two free ranges next to each other, so if there is more than one
+ // descriptor, then at least one of them must hold an allocated range.
+ return false;
+ }
+ // There is only one descriptor. Return true if it is for a free range.
+ first_value.state.is_none()
+ }
+
+ pub(crate) fn total_size(&self) -> usize {
+ self.size
+ }
+
+ pub(crate) fn free_oneway_space(&self) -> usize {
+ self.free_oneway_space
+ }
+
+ pub(crate) fn count_buffers(&self) -> usize {
+ self.tree
+ .values()
+ .filter(|desc| desc.state.is_some())
+ .count()
+ }
+
+ pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> {
+ for desc in self.tree.values() {
+ let state = match &desc.state {
+ Some(state) => &state.0,
+ None => continue,
+ };
+ seq_print!(
+ m,
+ " buffer: {} size {} pid {}",
+ desc.offset,
+ desc.size,
+ state.pid(),
+ );
+ if state.is_oneway() {
+ seq_print!(m, " oneway");
+ }
+ match state {
+ DescriptorState::Reserved(_res) => {
+ seq_print!(m, " reserved\n");
+ }
+ DescriptorState::Allocated(_alloc) => {
+ seq_print!(m, " allocated\n");
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn find_best_match(&mut self, size: usize) -> Option<&mut Descriptor<T>> {
+ let free_cursor = self.free_tree.cursor_lower_bound(&(size, 0))?;
+ let ((_, offset), ()) = free_cursor.current();
+ self.tree.get_mut(offset)
+ }
+
+ /// Try to reserve a new buffer, using the provided allocation if necessary.
+ pub(crate) fn reserve_new(
+ &mut self,
+ debug_id: usize,
+ size: usize,
+ is_oneway: bool,
+ pid: Pid,
+ alloc: ReserveNewTreeAlloc<T>,
+ ) -> Result<(usize, bool)> {
+ // Compute new value of free_oneway_space, which is set only on success.
+ let new_oneway_space = if is_oneway {
+ match self.free_oneway_space.checked_sub(size) {
+ Some(new_oneway_space) => new_oneway_space,
+ None => return Err(ENOSPC),
+ }
+ } else {
+ self.free_oneway_space
+ };
+
+ // Start detecting spammers once we have less than 20%
+ // of async space left (which is less than 10% of total
+ // buffer size).
+ //
+ // (This will short-circut, so `low_oneway_space` is
+ // only called when necessary.)
+ let oneway_spam_detected =
+ is_oneway && new_oneway_space < self.size / 10 && self.low_oneway_space(pid);
+
+ let (found_size, found_off, tree_node, free_tree_node) = match self.find_best_match(size) {
+ None => {
+ pr_warn!("ENOSPC from range_alloc.reserve_new - size: {}", size);
+ return Err(ENOSPC);
+ }
+ Some(desc) => {
+ let found_size = desc.size;
+ let found_offset = desc.offset;
+
+ // In case we need to break up the descriptor
+ let new_desc = Descriptor::new(found_offset + size, found_size - size);
+ let (tree_node, free_tree_node, desc_node_res) = alloc.initialize(new_desc);
+
+ desc.state = Some((
+ DescriptorState::new(is_oneway, debug_id, pid),
+ desc_node_res,
+ ));
+ desc.size = size;
+
+ (found_size, found_offset, tree_node, free_tree_node)
+ }
+ };
+ self.free_oneway_space = new_oneway_space;
+ self.free_tree.remove(&(found_size, found_off));
+
+ if found_size != size {
+ self.tree.insert(tree_node);
+ self.free_tree.insert(free_tree_node);
+ }
+
+ Ok((found_off, oneway_spam_detected))
+ }
+
+ pub(crate) fn reservation_abort(&mut self, offset: usize) -> Result<FreedRange> {
+ let mut cursor = self.tree.cursor_lower_bound(&offset).ok_or_else(|| {
+ pr_warn!(
+ "EINVAL from range_alloc.reservation_abort - offset: {}",
+ offset
+ );
+ EINVAL
+ })?;
+
+ let (_, desc) = cursor.current_mut();
+
+ if desc.offset != offset {
+ pr_warn!(
+ "EINVAL from range_alloc.reservation_abort - offset: {}",
+ offset
+ );
+ return Err(EINVAL);
+ }
+
+ let (reservation, free_node_res) = desc.try_change_state(|state| match state {
+ Some((DescriptorState::Reserved(reservation), free_node_res)) => {
+ (None, Ok((reservation, free_node_res)))
+ }
+ None => {
+ pr_warn!(
+ "EINVAL from range_alloc.reservation_abort - offset: {}",
+ offset
+ );
+ (None, Err(EINVAL))
+ }
+ allocated => {
+ pr_warn!(
+ "EPERM from range_alloc.reservation_abort - offset: {}",
+ offset
+ );
+ (allocated, Err(EPERM))
+ }
+ })?;
+
+ let mut size = desc.size;
+ let mut offset = desc.offset;
+ let free_oneway_space_add = if reservation.is_oneway { size } else { 0 };
+
+ self.free_oneway_space += free_oneway_space_add;
+
+ let mut freed_range = FreedRange::interior_pages(offset, size);
+ // Compute how large the next free region needs to be to include one more page in
+ // the newly freed range.
+ let add_next_page_needed = match (offset + size) % PAGE_SIZE {
+ 0 => usize::MAX,
+ unalign => PAGE_SIZE - unalign,
+ };
+ // Compute how large the previous free region needs to be to include one more page
+ // in the newly freed range.
+ let add_prev_page_needed = match offset % PAGE_SIZE {
+ 0 => usize::MAX,
+ unalign => unalign,
+ };
+
+ // Merge next into current if next is free
+ let remove_next = match cursor.peek_next() {
+ Some((_, next)) if next.state.is_none() => {
+ if next.size >= add_next_page_needed {
+ freed_range.end_page_idx += 1;
+ }
+ self.free_tree.remove(&(next.size, next.offset));
+ size += next.size;
+ true
+ }
+ _ => false,
+ };
+
+ if remove_next {
+ let (_, desc) = cursor.current_mut();
+ desc.size = size;
+ cursor.remove_next();
+ }
+
+ // Merge current into prev if prev is free
+ match cursor.peek_prev_mut() {
+ Some((_, prev)) if prev.state.is_none() => {
+ if prev.size >= add_prev_page_needed {
+ freed_range.start_page_idx -= 1;
+ }
+ // merge previous with current, remove current
+ self.free_tree.remove(&(prev.size, prev.offset));
+ offset = prev.offset;
+ size += prev.size;
+ prev.size = size;
+ cursor.remove_current();
+ }
+ _ => {}
+ };
+
+ self.free_tree
+ .insert(free_node_res.into_node((size, offset), ()));
+
+ Ok(freed_range)
+ }
+
+ pub(crate) fn reservation_commit(&mut self, offset: usize, data: &mut Option<T>) -> Result {
+ let desc = self.tree.get_mut(&offset).ok_or(ENOENT)?;
+
+ desc.try_change_state(|state| match state {
+ Some((DescriptorState::Reserved(reservation), free_node_res)) => (
+ Some((
+ DescriptorState::Allocated(reservation.allocate(data.take())),
+ free_node_res,
+ )),
+ Ok(()),
+ ),
+ other => (other, Err(ENOENT)),
+ })
+ }
+
+ /// Takes an entry at the given offset from [`DescriptorState::Allocated`] to
+ /// [`DescriptorState::Reserved`].
+ ///
+ /// Returns the size of the existing entry and the data associated with it.
+ pub(crate) fn reserve_existing(&mut self, offset: usize) -> Result<(usize, usize, Option<T>)> {
+ let desc = self.tree.get_mut(&offset).ok_or_else(|| {
+ pr_warn!(
+ "ENOENT from range_alloc.reserve_existing - offset: {}",
+ offset
+ );
+ ENOENT
+ })?;
+
+ let (debug_id, data) = desc.try_change_state(|state| match state {
+ Some((DescriptorState::Allocated(allocation), free_node_res)) => {
+ let (reservation, data) = allocation.deallocate();
+ let debug_id = reservation.debug_id;
+ (
+ Some((DescriptorState::Reserved(reservation), free_node_res)),
+ Ok((debug_id, data)),
+ )
+ }
+ other => {
+ pr_warn!(
+ "ENOENT from range_alloc.reserve_existing - offset: {}",
+ offset
+ );
+ (other, Err(ENOENT))
+ }
+ })?;
+
+ Ok((desc.size, debug_id, data))
+ }
+
+ /// Call the provided callback at every allocated region.
+ ///
+ /// This destroys the range allocator. Used only during shutdown.
+ pub(crate) fn take_for_each<F: Fn(usize, usize, usize, Option<T>)>(&mut self, callback: F) {
+ for (_, desc) in self.tree.iter_mut() {
+ if let Some((DescriptorState::Allocated(allocation), _)) = &mut desc.state {
+ callback(
+ desc.offset,
+ desc.size,
+ allocation.debug_id(),
+ allocation.take(),
+ );
+ }
+ }
+ }
+
+ /// 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 transaction,
+ /// and at some point we'll catch them in the act. This is more efficient
+ /// than keeping a map per pid.
+ fn low_oneway_space(&self, calling_pid: Pid) -> bool {
+ let mut total_alloc_size = 0;
+ let mut num_buffers = 0;
+ for (_, desc) in self.tree.iter() {
+ if let Some((state, _)) = &desc.state {
+ if state.is_oneway() && state.pid() == calling_pid {
+ total_alloc_size += desc.size;
+ num_buffers += 1;
+ }
+ }
+ }
+
+ // 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.
+ num_buffers > 50 || total_alloc_size > self.size / 4
+ }
+}
+
+type TreeDescriptorState<T> = (DescriptorState<T>, FreeNodeRes);
+struct Descriptor<T> {
+ size: usize,
+ offset: usize,
+ state: Option<TreeDescriptorState<T>>,
+}
+
+impl<T> Descriptor<T> {
+ fn new(offset: usize, size: usize) -> Self {
+ Self {
+ size,
+ offset,
+ state: None,
+ }
+ }
+
+ fn try_change_state<F, Data>(&mut self, f: F) -> Result<Data>
+ where
+ F: FnOnce(Option<TreeDescriptorState<T>>) -> (Option<TreeDescriptorState<T>>, Result<Data>),
+ {
+ let (new_state, result) = f(self.state.take());
+ self.state = new_state;
+ result
+ }
+}
+
+// (Descriptor.size, Descriptor.offset)
+type FreeKey = (usize, usize);
+type FreeNodeRes = RBTreeNodeReservation<FreeKey, ()>;
+
+/// An allocation for use by `reserve_new`.
+pub(crate) struct ReserveNewTreeAlloc<T> {
+ tree_node_res: RBTreeNodeReservation<usize, Descriptor<T>>,
+ free_tree_node_res: FreeNodeRes,
+ desc_node_res: FreeNodeRes,
+}
+
+impl<T> ReserveNewTreeAlloc<T> {
+ pub(crate) fn try_new() -> Result<Self> {
+ let tree_node_res = RBTreeNodeReservation::new(GFP_KERNEL)?;
+ let free_tree_node_res = RBTreeNodeReservation::new(GFP_KERNEL)?;
+ let desc_node_res = RBTreeNodeReservation::new(GFP_KERNEL)?;
+ Ok(Self {
+ tree_node_res,
+ free_tree_node_res,
+ desc_node_res,
+ })
+ }
+
+ fn initialize(
+ self,
+ desc: Descriptor<T>,
+ ) -> (
+ RBTreeNode<usize, Descriptor<T>>,
+ RBTreeNode<FreeKey, ()>,
+ FreeNodeRes,
+ ) {
+ let size = desc.size;
+ let offset = desc.offset;
+ (
+ self.tree_node_res.into_node(offset, desc),
+ self.free_tree_node_res.into_node((size, offset), ()),
+ self.desc_node_res,
+ )
+ }
+}
+
+/// An allocation for creating a tree from an `ArrayRangeAllocator`.
+pub(crate) struct FromArrayAllocs<T> {
+ tree: KVec<RBTreeNodeReservation<usize, Descriptor<T>>>,
+ free_tree: KVec<RBTreeNodeReservation<FreeKey, ()>>,
+}
+
+impl<T> FromArrayAllocs<T> {
+ pub(crate) fn try_new(len: usize) -> Result<Self> {
+ let num_descriptors = 2 * len + 1;
+
+ let mut tree = KVec::with_capacity(num_descriptors, GFP_KERNEL)?;
+ for _ in 0..num_descriptors {
+ tree.push(RBTreeNodeReservation::new(GFP_KERNEL)?, GFP_KERNEL)?;
+ }
+
+ let mut free_tree = KVec::with_capacity(num_descriptors, GFP_KERNEL)?;
+ for _ in 0..num_descriptors {
+ free_tree.push(RBTreeNodeReservation::new(GFP_KERNEL)?, GFP_KERNEL)?;
+ }
+
+ Ok(Self { tree, free_tree })
+ }
+}
diff --git a/drivers/android/binder/rust_binder.h b/drivers/android/binder/rust_binder.h
new file mode 100644
index 000000000000..31806890ed1a
--- /dev/null
+++ b/drivers/android/binder/rust_binder.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2025 Google, Inc.
+ */
+
+#ifndef _LINUX_RUST_BINDER_H
+#define _LINUX_RUST_BINDER_H
+
+#include <uapi/linux/android/binder.h>
+#include <uapi/linux/android/binderfs.h>
+
+/*
+ * These symbols are exposed by `rust_binderfs.c` and exist here so that Rust
+ * Binder can call them.
+ */
+int init_rust_binderfs(void);
+
+struct dentry;
+struct inode;
+struct dentry *rust_binderfs_create_proc_file(struct inode *nodp, int pid);
+void rust_binderfs_remove_file(struct dentry *dentry);
+
+#endif
diff --git a/drivers/android/binder/rust_binder_events.c b/drivers/android/binder/rust_binder_events.c
new file mode 100644
index 000000000000..488b1470060c
--- /dev/null
+++ b/drivers/android/binder/rust_binder_events.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* rust_binder_events.c
+ *
+ * Rust Binder tracepoints.
+ *
+ * Copyright 2025 Google LLC
+ */
+
+#include "rust_binder.h"
+
+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",
+};
+
+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"
+};
+
+#define CREATE_TRACE_POINTS
+#define CREATE_RUST_TRACE_POINTS
+#include "rust_binder_events.h"
diff --git a/drivers/android/binder/rust_binder_events.h b/drivers/android/binder/rust_binder_events.h
new file mode 100644
index 000000000000..2f3efbf9dba6
--- /dev/null
+++ b/drivers/android/binder/rust_binder_events.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Google, Inc.
+ */
+
+#undef TRACE_SYSTEM
+#undef TRACE_INCLUDE_FILE
+#undef TRACE_INCLUDE_PATH
+#define TRACE_SYSTEM rust_binder
+#define TRACE_INCLUDE_FILE rust_binder_events
+#define TRACE_INCLUDE_PATH ../drivers/android/binder
+
+#if !defined(_RUST_BINDER_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _RUST_BINDER_TRACE_H
+
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(rust_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)
+);
+
+#endif /* _RUST_BINDER_TRACE_H */
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
diff --git a/drivers/android/binder/rust_binder_internal.h b/drivers/android/binder/rust_binder_internal.h
new file mode 100644
index 000000000000..78288fe7964d
--- /dev/null
+++ b/drivers/android/binder/rust_binder_internal.h
@@ -0,0 +1,87 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* rust_binder_internal.h
+ *
+ * This file contains internal data structures used by Rust Binder. Mostly,
+ * these are type definitions used only by binderfs or things that Rust Binder
+ * define and export to binderfs.
+ *
+ * It does not include things exported by binderfs to Rust Binder since this
+ * file is not included as input to bindgen.
+ *
+ * Copyright (C) 2025 Google LLC.
+ */
+
+#ifndef _LINUX_RUST_BINDER_INTERNAL_H
+#define _LINUX_RUST_BINDER_INTERNAL_H
+
+#define RUST_BINDERFS_SUPER_MAGIC 0x6c6f6f71
+
+#include <linux/seq_file.h>
+#include <uapi/linux/android/binder.h>
+#include <uapi/linux/android/binderfs.h>
+
+/*
+ * The internal data types in the Rust Binder driver are opaque to C, so we use
+ * void pointer typedefs for these types.
+ */
+typedef void *rust_binder_context;
+
+/**
+ * struct binder_device - information about a binder device node
+ * @minor: the minor number used by this device
+ * @ctx: the Rust Context used by this device, or null for binder-control
+ *
+ * This is used as the private data for files directly in binderfs, but not
+ * files in the binder_logs subdirectory. This struct owns a refcount on `ctx`
+ * and the entry for `minor` in `binderfs_minors`. For binder-control `ctx` is
+ * null.
+ */
+struct binder_device {
+ int minor;
+ rust_binder_context ctx;
+};
+
+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_proc_show(struct seq_file *m, void *pid);
+
+extern const struct file_operations rust_binder_fops;
+rust_binder_context rust_binder_new_context(char *name);
+void rust_binder_remove_context(rust_binder_context device);
+
+/**
+ * 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;
+};
+
+#endif /* _LINUX_RUST_BINDER_INTERNAL_H */
diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs
new file mode 100644
index 000000000000..6773b7c273ec
--- /dev/null
+++ b/drivers/android/binder/rust_binder_main.rs
@@ -0,0 +1,627 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+//! Binder -- the Android IPC mechanism.
+#![recursion_limit = "256"]
+#![allow(
+ clippy::as_underscore,
+ clippy::ref_as_ptr,
+ clippy::ptr_as_ptr,
+ clippy::cast_lossless
+)]
+
+use kernel::{
+ bindings::{self, seq_file},
+ fs::File,
+ list::{ListArc, ListArcSafe, ListLinksSelfPtr, TryNewListArc},
+ prelude::*,
+ seq_file::SeqFile,
+ seq_print,
+ sync::poll::PollTable,
+ sync::Arc,
+ task::Pid,
+ transmute::AsBytes,
+ types::ForeignOwnable,
+ uaccess::UserSliceWriter,
+};
+
+use crate::{context::Context, page_range::Shrinker, process::Process, thread::Thread};
+
+use core::{
+ ptr::NonNull,
+ sync::atomic::{AtomicBool, AtomicUsize, Ordering},
+};
+
+mod allocation;
+mod context;
+mod deferred_close;
+mod defs;
+mod error;
+mod node;
+mod page_range;
+mod process;
+mod range_alloc;
+mod stats;
+mod thread;
+mod trace;
+mod transaction;
+
+#[allow(warnings)] // generated bindgen code
+mod binderfs {
+ use kernel::bindings::{dentry, inode};
+
+ extern "C" {
+ pub fn init_rust_binderfs() -> kernel::ffi::c_int;
+ }
+ extern "C" {
+ pub fn rust_binderfs_create_proc_file(
+ nodp: *mut inode,
+ pid: kernel::ffi::c_int,
+ ) -> *mut dentry;
+ }
+ extern "C" {
+ pub fn rust_binderfs_remove_file(dentry: *mut dentry);
+ }
+ pub type rust_binder_context = *mut kernel::ffi::c_void;
+ #[repr(C)]
+ #[derive(Copy, Clone)]
+ pub struct binder_device {
+ pub minor: kernel::ffi::c_int,
+ pub ctx: rust_binder_context,
+ }
+ impl Default for binder_device {
+ fn default() -> Self {
+ let mut s = ::core::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::core::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+ }
+}
+
+module! {
+ type: BinderModule,
+ name: "rust_binder",
+ authors: ["Wedson Almeida Filho", "Alice Ryhl"],
+ description: "Android Binder",
+ license: "GPL",
+}
+
+fn next_debug_id() -> usize {
+ static NEXT_DEBUG_ID: AtomicUsize = AtomicUsize::new(0);
+
+ NEXT_DEBUG_ID.fetch_add(1, Ordering::Relaxed)
+}
+
+/// Provides a single place to write Binder return values via the
+/// supplied `UserSliceWriter`.
+pub(crate) struct BinderReturnWriter<'a> {
+ writer: UserSliceWriter,
+ thread: &'a Thread,
+}
+
+impl<'a> BinderReturnWriter<'a> {
+ fn new(writer: UserSliceWriter, thread: &'a Thread) -> Self {
+ BinderReturnWriter { writer, thread }
+ }
+
+ /// Write a return code back to user space.
+ /// Should be a `BR_` constant from [`defs`] e.g. [`defs::BR_TRANSACTION_COMPLETE`].
+ fn write_code(&mut self, code: u32) -> Result {
+ stats::GLOBAL_STATS.inc_br(code);
+ self.thread.process.stats.inc_br(code);
+ self.writer.write(&code)
+ }
+
+ /// Write something *other than* a return code to user space.
+ fn write_payload<T: AsBytes>(&mut self, payload: &T) -> Result {
+ self.writer.write(payload)
+ }
+
+ fn len(&self) -> usize {
+ self.writer.len()
+ }
+}
+
+/// Specifies how a type should be delivered to the read part of a BINDER_WRITE_READ ioctl.
+///
+/// When a value is pushed to the todo list for a process or thread, it is stored as a trait object
+/// with the type `Arc<dyn DeliverToRead>`. Trait objects are a Rust feature that lets you
+/// implement dynamic dispatch over many different types. This lets us store many different types
+/// in the todo list.
+trait DeliverToRead: ListArcSafe + Send + Sync {
+ /// Performs work. Returns true if remaining work items in the queue should be processed
+ /// immediately, or false if it should return to caller before processing additional work
+ /// items.
+ fn do_work(
+ self: DArc<Self>,
+ thread: &Thread,
+ writer: &mut BinderReturnWriter<'_>,
+ ) -> Result<bool>;
+
+ /// Cancels the given work item. This is called instead of [`DeliverToRead::do_work`] when work
+ /// won't be delivered.
+ fn cancel(self: DArc<Self>);
+
+ /// Should we use `wake_up_interruptible_sync` or `wake_up_interruptible` when scheduling this
+ /// work item?
+ ///
+ /// 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<()>;
+}
+
+// Wrapper around a `DeliverToRead` with linked list links.
+#[pin_data]
+struct DTRWrap<T: ?Sized> {
+ #[pin]
+ links: ListLinksSelfPtr<DTRWrap<dyn DeliverToRead>>,
+ #[pin]
+ wrapped: T,
+}
+kernel::list::impl_list_arc_safe! {
+ impl{T: ListArcSafe + ?Sized} ListArcSafe<0> for DTRWrap<T> {
+ tracked_by wrapped: T;
+ }
+}
+kernel::list::impl_list_item! {
+ impl ListItem<0> for DTRWrap<dyn DeliverToRead> {
+ using ListLinksSelfPtr { self.links };
+ }
+}
+
+impl<T: ?Sized> core::ops::Deref for DTRWrap<T> {
+ type Target = T;
+ fn deref(&self) -> &T {
+ &self.wrapped
+ }
+}
+
+type DArc<T> = kernel::sync::Arc<DTRWrap<T>>;
+type DLArc<T> = kernel::list::ListArc<DTRWrap<T>>;
+
+impl<T: ListArcSafe> DTRWrap<T> {
+ fn new(val: impl PinInit<T>) -> impl PinInit<Self> {
+ pin_init!(Self {
+ links <- ListLinksSelfPtr::new(),
+ wrapped <- val,
+ })
+ }
+
+ fn arc_try_new(val: T) -> Result<DLArc<T>, kernel::alloc::AllocError> {
+ ListArc::pin_init(
+ try_pin_init!(Self {
+ links <- ListLinksSelfPtr::new(),
+ wrapped: val,
+ }),
+ GFP_KERNEL,
+ )
+ .map_err(|_| kernel::alloc::AllocError)
+ }
+
+ fn arc_pin_init(init: impl PinInit<T>) -> Result<DLArc<T>, kernel::error::Error> {
+ ListArc::pin_init(
+ try_pin_init!(Self {
+ links <- ListLinksSelfPtr::new(),
+ wrapped <- init,
+ }),
+ GFP_KERNEL,
+ )
+ }
+}
+
+struct DeliverCode {
+ code: u32,
+ skip: AtomicBool,
+}
+
+kernel::list::impl_list_arc_safe! {
+ impl ListArcSafe<0> for DeliverCode { untracked; }
+}
+
+impl DeliverCode {
+ fn new(code: u32) -> Self {
+ Self {
+ code,
+ skip: AtomicBool::new(false),
+ }
+ }
+
+ /// Disable this DeliverCode and make it do nothing.
+ ///
+ /// This is used instead of removing it from the work list, since `LinkedList::remove` is
+ /// unsafe, whereas this method is not.
+ fn skip(&self) {
+ self.skip.store(true, Ordering::Relaxed);
+ }
+}
+
+impl DeliverToRead for DeliverCode {
+ fn do_work(
+ self: DArc<Self>,
+ _thread: &Thread,
+ writer: &mut BinderReturnWriter<'_>,
+ ) -> Result<bool> {
+ if !self.skip.load(Ordering::Relaxed) {
+ writer.write_code(self.code)?;
+ }
+ Ok(true)
+ }
+
+ fn cancel(self: DArc<Self>) {}
+
+ fn should_sync_wakeup(&self) -> bool {
+ false
+ }
+
+ fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
+ seq_print!(m, "{}", prefix);
+ if self.skip.load(Ordering::Relaxed) {
+ seq_print!(m, "(skipped) ");
+ }
+ if self.code == defs::BR_TRANSACTION_COMPLETE {
+ seq_print!(m, "transaction complete\n");
+ } else {
+ seq_print!(m, "transaction error: {}\n", self.code);
+ }
+ Ok(())
+ }
+}
+
+fn ptr_align(value: usize) -> Option<usize> {
+ let size = core::mem::size_of::<usize>() - 1;
+ Some(value.checked_add(size)? & !size)
+}
+
+// SAFETY: We call register in `init`.
+static BINDER_SHRINKER: Shrinker = unsafe { Shrinker::new() };
+
+struct BinderModule {}
+
+impl kernel::Module for BinderModule {
+ fn init(_module: &'static kernel::ThisModule) -> Result<Self> {
+ // SAFETY: The module initializer never runs twice, so we only call this once.
+ unsafe { crate::context::CONTEXTS.init() };
+
+ pr_warn!("Loaded Rust Binder.");
+
+ BINDER_SHRINKER.register(kernel::c_str!("android-binder"))?;
+
+ // SAFETY: The module is being loaded, so we can initialize binderfs.
+ unsafe { kernel::error::to_result(binderfs::init_rust_binderfs())? };
+
+ Ok(Self {})
+ }
+}
+
+/// Makes the inner type Sync.
+#[repr(transparent)]
+pub struct AssertSync<T>(T);
+// SAFETY: Used only to insert `file_operations` into a global, which is safe.
+unsafe impl<T> Sync for AssertSync<T> {}
+
+/// File operations that rust_binderfs.c can use.
+#[no_mangle]
+#[used]
+pub static rust_binder_fops: AssertSync<kernel::bindings::file_operations> = {
+ // SAFETY: All zeroes is safe for the `file_operations` type.
+ let zeroed_ops = unsafe { core::mem::MaybeUninit::zeroed().assume_init() };
+
+ let ops = kernel::bindings::file_operations {
+ owner: THIS_MODULE.as_ptr(),
+ poll: Some(rust_binder_poll),
+ unlocked_ioctl: Some(rust_binder_unlocked_ioctl),
+ compat_ioctl: Some(rust_binder_compat_ioctl),
+ mmap: Some(rust_binder_mmap),
+ open: Some(rust_binder_open),
+ release: Some(rust_binder_release),
+ flush: Some(rust_binder_flush),
+ ..zeroed_ops
+ };
+ AssertSync(ops)
+};
+
+/// # Safety
+/// Only called by binderfs.
+#[no_mangle]
+unsafe extern "C" fn rust_binder_new_context(
+ name: *const kernel::ffi::c_char,
+) -> *mut kernel::ffi::c_void {
+ // SAFETY: The caller will always provide a valid c string here.
+ let name = unsafe { kernel::str::CStr::from_char_ptr(name) };
+ match Context::new(name) {
+ Ok(ctx) => Arc::into_foreign(ctx),
+ Err(_err) => core::ptr::null_mut(),
+ }
+}
+
+/// # Safety
+/// Only called by binderfs.
+#[no_mangle]
+unsafe extern "C" fn rust_binder_remove_context(device: *mut kernel::ffi::c_void) {
+ if !device.is_null() {
+ // SAFETY: The caller ensures that the `device` pointer came from a previous call to
+ // `rust_binder_new_device`.
+ let ctx = unsafe { Arc::<Context>::from_foreign(device) };
+ ctx.deregister();
+ drop(ctx);
+ }
+}
+
+/// # Safety
+/// Only called by binderfs.
+unsafe extern "C" fn rust_binder_open(
+ inode: *mut bindings::inode,
+ file_ptr: *mut bindings::file,
+) -> kernel::ffi::c_int {
+ // SAFETY: The `rust_binderfs.c` file ensures that `i_private` is set to a
+ // `struct binder_device`.
+ let device = unsafe { (*inode).i_private } as *const binderfs::binder_device;
+
+ assert!(!device.is_null());
+
+ // SAFETY: The `rust_binderfs.c` file ensures that `device->ctx` holds a binder context when
+ // using the rust binder fops.
+ let ctx = unsafe { Arc::<Context>::borrow((*device).ctx) };
+
+ // SAFETY: The caller provides a valid file pointer to a new `struct file`.
+ let file = unsafe { File::from_raw_file(file_ptr) };
+ let process = match Process::open(ctx, file) {
+ Ok(process) => process,
+ Err(err) => return err.to_errno(),
+ };
+
+ // SAFETY: This is an `inode` for a newly created binder file.
+ match unsafe { BinderfsProcFile::new(inode, process.task.pid()) } {
+ Ok(Some(file)) => process.inner.lock().binderfs_file = Some(file),
+ Ok(None) => { /* pid already exists */ }
+ Err(err) => return err.to_errno(),
+ }
+
+ // SAFETY: This file is associated with Rust binder, so we own the `private_data` field.
+ unsafe { (*file_ptr).private_data = process.into_foreign() };
+ 0
+}
+
+/// # Safety
+/// Only called by binderfs.
+unsafe extern "C" fn rust_binder_release(
+ _inode: *mut bindings::inode,
+ file: *mut bindings::file,
+) -> kernel::ffi::c_int {
+ // SAFETY: We previously set `private_data` in `rust_binder_open`.
+ let process = unsafe { Arc::<Process>::from_foreign((*file).private_data) };
+ // SAFETY: The caller ensures that the file is valid.
+ let file = unsafe { File::from_raw_file(file) };
+ Process::release(process, file);
+ 0
+}
+
+/// # Safety
+/// Only called by binderfs.
+unsafe extern "C" fn rust_binder_compat_ioctl(
+ file: *mut bindings::file,
+ cmd: kernel::ffi::c_uint,
+ arg: kernel::ffi::c_ulong,
+) -> kernel::ffi::c_long {
+ // SAFETY: We previously set `private_data` in `rust_binder_open`.
+ let f = unsafe { Arc::<Process>::borrow((*file).private_data) };
+ // SAFETY: The caller ensures that the file is valid.
+ match Process::compat_ioctl(f, unsafe { File::from_raw_file(file) }, cmd as _, arg as _) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno() as isize,
+ }
+}
+
+/// # Safety
+/// Only called by binderfs.
+unsafe extern "C" fn rust_binder_unlocked_ioctl(
+ file: *mut bindings::file,
+ cmd: kernel::ffi::c_uint,
+ arg: kernel::ffi::c_ulong,
+) -> kernel::ffi::c_long {
+ // SAFETY: We previously set `private_data` in `rust_binder_open`.
+ let f = unsafe { Arc::<Process>::borrow((*file).private_data) };
+ // SAFETY: The caller ensures that the file is valid.
+ match Process::ioctl(f, unsafe { File::from_raw_file(file) }, cmd as _, arg as _) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno() as isize,
+ }
+}
+
+/// # Safety
+/// Only called by binderfs.
+unsafe extern "C" fn rust_binder_mmap(
+ file: *mut bindings::file,
+ vma: *mut bindings::vm_area_struct,
+) -> kernel::ffi::c_int {
+ // SAFETY: We previously set `private_data` in `rust_binder_open`.
+ let f = unsafe { Arc::<Process>::borrow((*file).private_data) };
+ // SAFETY: The caller ensures that the vma is valid.
+ let area = unsafe { kernel::mm::virt::VmaNew::from_raw(vma) };
+ // SAFETY: The caller ensures that the file is valid.
+ match Process::mmap(f, unsafe { File::from_raw_file(file) }, area) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno(),
+ }
+}
+
+/// # Safety
+/// Only called by binderfs.
+unsafe extern "C" fn rust_binder_poll(
+ file: *mut bindings::file,
+ wait: *mut bindings::poll_table_struct,
+) -> bindings::__poll_t {
+ // SAFETY: We previously set `private_data` in `rust_binder_open`.
+ let f = unsafe { Arc::<Process>::borrow((*file).private_data) };
+ // SAFETY: The caller ensures that the file is valid.
+ let fileref = unsafe { File::from_raw_file(file) };
+ // SAFETY: The caller ensures that the `PollTable` is valid.
+ match Process::poll(f, fileref, unsafe { PollTable::from_raw(wait) }) {
+ Ok(v) => v,
+ Err(_) => bindings::POLLERR,
+ }
+}
+
+/// # Safety
+/// Only called by binderfs.
+unsafe extern "C" fn rust_binder_flush(
+ file: *mut bindings::file,
+ _id: bindings::fl_owner_t,
+) -> kernel::ffi::c_int {
+ // SAFETY: We previously set `private_data` in `rust_binder_open`.
+ let f = unsafe { Arc::<Process>::borrow((*file).private_data) };
+ match Process::flush(f) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno(),
+ }
+}
+
+/// # Safety
+/// Only called by binderfs.
+#[no_mangle]
+unsafe extern "C" fn rust_binder_stats_show(
+ ptr: *mut seq_file,
+ _: *mut kernel::ffi::c_void,
+) -> kernel::ffi::c_int {
+ // 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) };
+ if let Err(err) = rust_binder_stats_show_impl(m) {
+ seq_print!(m, "failed to generate state: {:?}\n", err);
+ }
+ 0
+}
+
+/// # Safety
+/// Only called by binderfs.
+#[no_mangle]
+unsafe extern "C" fn rust_binder_state_show(
+ ptr: *mut seq_file,
+ _: *mut kernel::ffi::c_void,
+) -> kernel::ffi::c_int {
+ // 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) };
+ if let Err(err) = rust_binder_state_show_impl(m) {
+ seq_print!(m, "failed to generate state: {:?}\n", err);
+ }
+ 0
+}
+
+/// # Safety
+/// Only called by binderfs.
+#[no_mangle]
+unsafe extern "C" fn rust_binder_proc_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 pid = (unsafe { (*ptr).private }) as usize as Pid;
+ // 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) };
+ if let Err(err) = rust_binder_proc_show_impl(m, pid) {
+ seq_print!(m, "failed to generate state: {:?}\n", err);
+ }
+ 0
+}
+
+/// # Safety
+/// Only called by binderfs.
+#[no_mangle]
+unsafe extern "C" fn rust_binder_transactions_show(
+ ptr: *mut seq_file,
+ _: *mut kernel::ffi::c_void,
+) -> kernel::ffi::c_int {
+ // 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) };
+ if let Err(err) = rust_binder_transactions_show_impl(m) {
+ seq_print!(m, "failed to generate state: {:?}\n", err);
+ }
+ 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 {
+ let procs = ctx.get_all_procs()?;
+ for proc in procs {
+ proc.debug_print(m, &ctx, false)?;
+ seq_print!(m, "\n");
+ }
+ }
+ Ok(())
+}
+
+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()?;
+ for ctx in contexts {
+ let procs = ctx.get_all_procs()?;
+ for proc in procs {
+ proc.debug_print_stats(m, &ctx)?;
+ seq_print!(m, "\n");
+ }
+ }
+ Ok(())
+}
+
+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 {
+ let procs = ctx.get_all_procs()?;
+ for proc in procs {
+ proc.debug_print(m, &ctx, true)?;
+ seq_print!(m, "\n");
+ }
+ }
+ Ok(())
+}
+
+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 {
+ let procs = ctx.get_procs_with_pid(pid)?;
+ for proc in procs {
+ proc.debug_print(m, &ctx, true)?;
+ seq_print!(m, "\n");
+ }
+ }
+ Ok(())
+}
+
+struct BinderfsProcFile(NonNull<bindings::dentry>);
+
+// SAFETY: Safe to drop any thread.
+unsafe impl Send for BinderfsProcFile {}
+
+impl BinderfsProcFile {
+ /// # Safety
+ ///
+ /// Takes an inode from a newly created binder file.
+ unsafe fn new(nodp: *mut bindings::inode, pid: i32) -> Result<Option<Self>> {
+ // SAFETY: The caller passes an `inode` for a newly created binder file.
+ let dentry = unsafe { binderfs::rust_binderfs_create_proc_file(nodp, pid) };
+ match kernel::error::from_err_ptr(dentry) {
+ Ok(dentry) => Ok(NonNull::new(dentry).map(Self)),
+ Err(err) if err == EEXIST => Ok(None),
+ Err(err) => Err(err),
+ }
+ }
+}
+
+impl Drop for BinderfsProcFile {
+ fn drop(&mut self) {
+ // SAFETY: This is a dentry from `rust_binderfs_remove_file` that has not been deleted yet.
+ unsafe { binderfs::rust_binderfs_remove_file(self.0.as_ptr()) };
+ }
+}
diff --git a/drivers/android/binder/rust_binderfs.c b/drivers/android/binder/rust_binderfs.c
new file mode 100644
index 000000000000..6b497146b698
--- /dev/null
+++ b/drivers/android/binder/rust_binderfs.c
@@ -0,0 +1,850 @@
+// 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/asm-generic/errno-base.h>
+#include <uapi/linux/android/binder.h>
+#include <uapi/linux/android/binderfs.h>
+
+#include "rust_binder.h"
+#include "rust_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)
+
+DEFINE_SHOW_ATTRIBUTE(rust_binder_stats);
+DEFINE_SHOW_ATTRIBUTE(rust_binder_state);
+DEFINE_SHOW_ATTRIBUTE(rust_binder_transactions);
+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);
+
+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;
+};
+
+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,
+};
+
+static inline struct binderfs_info *BINDERFS_SB(const struct super_block *sb)
+{
+ return sb->s_fs_info;
+}
+
+/**
+ * binderfs_binder_device_create - allocate inode from super block of a
+ * binderfs mount
+ * @ref_inode: inode from wich 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 = NULL;
+ rust_binder_context ctx = 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 :
+ BINDERFS_MAX_MINOR_CAPPED,
+ 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(sizeof(*device), GFP_KERNEL);
+ if (!device)
+ goto err;
+
+ req->name[BINDERFS_MAX_NAME] = '\0'; /* NUL-terminate */
+
+ ctx = rust_binder_new_context(req->name);
+ if (!ctx)
+ 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 = &rust_binder_fops;
+ inode->i_uid = info->root_uid;
+ inode->i_gid = info->root_gid;
+
+ req->major = MAJOR(binderfs_dev);
+ req->minor = minor;
+ device->ctx = ctx;
+ device->minor = minor;
+
+ if (userp && copy_to_user(userp, req, sizeof(*req))) {
+ ret = -EFAULT;
+ goto err;
+ }
+
+ root = sb->s_root;
+ inode_lock(d_inode(root));
+
+ /* look it up */
+ dentry = lookup_noperm(&QSTR(req->name), root);
+ if (IS_ERR(dentry)) {
+ inode_unlock(d_inode(root));
+ ret = PTR_ERR(dentry);
+ goto err;
+ }
+
+ if (d_really_is_positive(dentry)) {
+ /* already exists */
+ dput(dentry);
+ inode_unlock(d_inode(root));
+ ret = -EEXIST;
+ goto err;
+ }
+
+ inode->i_private = device;
+ d_instantiate(dentry, inode);
+ fsnotify_create(root->d_inode, dentry);
+ inode_unlock(d_inode(root));
+
+ return 0;
+
+err:
+ kfree(device);
+ rust_binder_remove_context(ctx);
+ 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
+ *
+ * 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->minor);
+ mutex_unlock(&binderfs_minors_mutex);
+
+ /* ctx is null for binder-control, but this function ignores null pointers */
+ rust_binder_remove_context(device->ctx);
+
+ 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_puts(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(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(sizeof(*device), GFP_KERNEL);
+ if (!device)
+ return -ENOMEM;
+
+ /* If we have already created a binder-control node, return. */
+ if (info->control_dentry) {
+ ret = 0;
+ goto out;
+ }
+
+ 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 :
+ BINDERFS_MAX_MINOR_CAPPED,
+ 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;
+
+ device->minor = minor;
+ device->ctx = NULL;
+
+ dentry = d_alloc_name(root, "binder-control");
+ if (!dentry)
+ goto out;
+
+ inode->i_private = device;
+ info->control_dentry = dentry;
+ d_add(dentry, inode);
+
+ 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;
+}
+
+static struct dentry *binderfs_create_dentry(struct dentry *parent,
+ const char *name)
+{
+ struct dentry *dentry;
+
+ dentry = lookup_noperm(&QSTR(name), parent);
+ if (IS_ERR(dentry))
+ return dentry;
+
+ /* Return error if the file/dir already exists. */
+ if (d_really_is_positive(dentry)) {
+ dput(dentry);
+ return ERR_PTR(-EEXIST);
+ }
+
+ return dentry;
+}
+
+void rust_binderfs_remove_file(struct dentry *dentry)
+{
+ struct inode *parent_inode;
+
+ parent_inode = d_inode(dentry->d_parent);
+ inode_lock(parent_inode);
+ if (simple_positive(dentry)) {
+ dget(dentry);
+ simple_unlink(parent_inode, dentry);
+ d_delete(dentry);
+ dput(dentry);
+ }
+ inode_unlock(parent_inode);
+}
+
+static struct dentry *rust_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);
+ inode_lock(parent_inode);
+
+ dentry = binderfs_create_dentry(parent, name);
+ if (IS_ERR(dentry))
+ goto out;
+
+ sb = parent_inode->i_sb;
+ new_inode = binderfs_make_inode(sb, S_IFREG | 0444);
+ if (!new_inode) {
+ dput(dentry);
+ dentry = ERR_PTR(-ENOMEM);
+ goto out;
+ }
+
+ new_inode->i_fop = fops;
+ new_inode->i_private = data;
+ d_instantiate(dentry, new_inode);
+ fsnotify_create(parent_inode, dentry);
+
+out:
+ inode_unlock(parent_inode);
+ return dentry;
+}
+
+struct dentry *rust_binderfs_create_proc_file(struct inode *nodp, int pid)
+{
+ struct binderfs_info *info = nodp->i_sb->s_fs_info;
+ struct dentry *dir = info->proc_log_dir;
+ char strbuf[20 + 1];
+ void *data = (void *)(unsigned long) pid;
+
+ if (!dir)
+ return NULL;
+
+ snprintf(strbuf, sizeof(strbuf), "%u", pid);
+ return rust_binderfs_create_file(dir, strbuf, &rust_binder_proc_fops, data);
+}
+
+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);
+ inode_lock(parent_inode);
+
+ dentry = binderfs_create_dentry(parent, name);
+ if (IS_ERR(dentry))
+ goto out;
+
+ sb = parent_inode->i_sb;
+ new_inode = binderfs_make_inode(sb, S_IFDIR | 0755);
+ if (!new_inode) {
+ dput(dentry);
+ dentry = ERR_PTR(-ENOMEM);
+ goto out;
+ }
+
+ new_inode->i_fop = &simple_dir_operations;
+ new_inode->i_op = &simple_dir_inode_operations;
+
+ set_nlink(new_inode, 2);
+ d_instantiate(dentry, new_inode);
+ inc_nlink(parent_inode);
+ fsnotify_mkdir(parent_inode, dentry);
+
+out:
+ inode_unlock(parent_inode);
+ 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 = rust_binderfs_create_file(dir, "oneway_spam_detection",
+ &binder_features_fops,
+ &binder_features.oneway_spam_detection);
+ if (IS_ERR(dentry))
+ return PTR_ERR(dentry);
+
+ dentry = rust_binderfs_create_file(dir, "extended_error",
+ &binder_features_fops,
+ &binder_features.extended_error);
+ if (IS_ERR(dentry))
+ return PTR_ERR(dentry);
+
+ dentry = rust_binderfs_create_file(dir, "freeze_notification",
+ &binder_features_fops,
+ &binder_features.freeze_notification);
+ 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;
+ 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;
+ }
+
+ dentry = rust_binderfs_create_file(binder_logs_root_dir, "stats",
+ &rust_binder_stats_fops, NULL);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto out;
+ }
+
+ dentry = rust_binderfs_create_file(binder_logs_root_dir, "state",
+ &rust_binder_state_fops, NULL);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto out;
+ }
+
+ dentry = rust_binderfs_create_file(binder_logs_root_dir, "transactions",
+ &rust_binder_transactions_fops, NULL);
+ 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 = RUST_BINDERFS_SUPER_MAGIC;
+ sb->s_op = &binderfs_super_ops;
+ sb->s_time_gran = 1;
+
+ sb->s_fs_info = kzalloc(sizeof(struct binderfs_info), GFP_KERNEL);
+ 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 = rust_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(sizeof(struct binderfs_mount_opts), GFP_KERNEL);
+ 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_litter_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_rust_binderfs(void)
+{
+ int ret;
+ const char *name;
+ size_t len;
+
+ /* Verify that the default binderfs device names are valid. */
+ name = rust_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,
+ "rust_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/binder/stats.rs b/drivers/android/binder/stats.rs
new file mode 100644
index 000000000000..a83ec111d2cb
--- /dev/null
+++ b/drivers/android/binder/stats.rs
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+//! Keep track of statistics for binder_logs.
+
+use crate::defs::*;
+use core::sync::atomic::{AtomicU32, Ordering::Relaxed};
+use kernel::{ioctl::_IOC_NR, seq_file::SeqFile, seq_print};
+
+const BC_COUNT: usize = _IOC_NR(BC_REPLY_SG) as usize + 1;
+const BR_COUNT: usize = _IOC_NR(BR_TRANSACTION_PENDING_FROZEN) as usize + 1;
+
+pub(crate) static GLOBAL_STATS: BinderStats = BinderStats::new();
+
+pub(crate) struct BinderStats {
+ bc: [AtomicU32; BC_COUNT],
+ br: [AtomicU32; BR_COUNT],
+}
+
+impl BinderStats {
+ pub(crate) const fn new() -> Self {
+ #[expect(clippy::declare_interior_mutable_const)]
+ const ZERO: AtomicU32 = AtomicU32::new(0);
+
+ Self {
+ bc: [ZERO; BC_COUNT],
+ br: [ZERO; BR_COUNT],
+ }
+ }
+
+ pub(crate) fn inc_bc(&self, bc: u32) {
+ let idx = _IOC_NR(bc) as usize;
+ if let Some(bc_ref) = self.bc.get(idx) {
+ bc_ref.fetch_add(1, Relaxed);
+ }
+ }
+
+ pub(crate) fn inc_br(&self, br: u32) {
+ let idx = _IOC_NR(br) as usize;
+ if let Some(br_ref) = self.br.get(idx) {
+ br_ref.fetch_add(1, Relaxed);
+ }
+ }
+
+ pub(crate) fn debug_print(&self, prefix: &str, m: &SeqFile) {
+ for (i, cnt) in self.bc.iter().enumerate() {
+ let cnt = cnt.load(Relaxed);
+ if cnt > 0 {
+ seq_print!(m, "{}{}: {}\n", prefix, command_string(i), cnt);
+ }
+ }
+ for (i, cnt) in self.br.iter().enumerate() {
+ let cnt = cnt.load(Relaxed);
+ if cnt > 0 {
+ seq_print!(m, "{}{}: {}\n", prefix, return_string(i), cnt);
+ }
+ }
+ }
+}
+
+mod strings {
+ use core::str::from_utf8_unchecked;
+ use kernel::str::CStr;
+
+ extern "C" {
+ static binder_command_strings: [*const u8; super::BC_COUNT];
+ static binder_return_strings: [*const u8; super::BR_COUNT];
+ }
+
+ pub(super) fn command_string(i: usize) -> &'static str {
+ // SAFETY: Accessing `binder_command_strings` is always safe.
+ let c_str_ptr = unsafe { binder_command_strings[i] };
+ // SAFETY: The `binder_command_strings` array only contains nul-terminated strings.
+ let bytes = unsafe { CStr::from_char_ptr(c_str_ptr) }.as_bytes();
+ // SAFETY: The `binder_command_strings` array only contains strings with ascii-chars.
+ unsafe { from_utf8_unchecked(bytes) }
+ }
+
+ pub(super) fn return_string(i: usize) -> &'static str {
+ // SAFETY: Accessing `binder_return_strings` is always safe.
+ let c_str_ptr = unsafe { binder_return_strings[i] };
+ // SAFETY: The `binder_command_strings` array only contains nul-terminated strings.
+ let bytes = unsafe { CStr::from_char_ptr(c_str_ptr) }.as_bytes();
+ // SAFETY: The `binder_command_strings` array only contains strings with ascii-chars.
+ unsafe { from_utf8_unchecked(bytes) }
+ }
+}
+use strings::{command_string, return_string};
diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs
new file mode 100644
index 000000000000..7e34ccd394f8
--- /dev/null
+++ b/drivers/android/binder/thread.rs
@@ -0,0 +1,1596 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+//! This module defines the `Thread` type, which represents a userspace thread that is using
+//! binder.
+//!
+//! The `Process` object stores all of the threads in an rb tree.
+
+use kernel::{
+ bindings,
+ fs::{File, LocalFile},
+ list::{AtomicTracker, List, ListArc, ListLinks, TryNewListArc},
+ prelude::*,
+ security,
+ seq_file::SeqFile,
+ seq_print,
+ sync::poll::{PollCondVar, PollTable},
+ sync::{Arc, SpinLock},
+ task::Task,
+ types::ARef,
+ uaccess::UserSlice,
+ uapi,
+};
+
+use crate::{
+ allocation::{Allocation, AllocationView, BinderObject, BinderObjectRef, NewAllocation},
+ defs::*,
+ error::BinderResult,
+ process::{GetWorkOrRegister, Process},
+ ptr_align,
+ stats::GLOBAL_STATS,
+ transaction::Transaction,
+ BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverCode, DeliverToRead,
+};
+
+use core::{
+ mem::size_of,
+ sync::atomic::{AtomicU32, Ordering},
+};
+
+/// Stores the layout of the scatter-gather entries. This is used during the `translate_objects`
+/// call and is discarded when it returns.
+struct ScatterGatherState {
+ /// A struct that tracks the amount of unused buffer space.
+ unused_buffer_space: UnusedBufferSpace,
+ /// Scatter-gather entries to copy.
+ sg_entries: KVec<ScatterGatherEntry>,
+ /// Indexes into `sg_entries` corresponding to the last binder_buffer_object that
+ /// was processed and all of its ancestors. The array is in sorted order.
+ ancestors: KVec<usize>,
+}
+
+/// This entry specifies an additional buffer that should be copied using the scatter-gather
+/// mechanism.
+struct ScatterGatherEntry {
+ /// The index in the offset array of the BINDER_TYPE_PTR that this entry originates from.
+ obj_index: usize,
+ /// Offset in target buffer.
+ offset: usize,
+ /// User address in source buffer.
+ sender_uaddr: usize,
+ /// Number of bytes to copy.
+ length: usize,
+ /// The minimum offset of the next fixup in this buffer.
+ fixup_min_offset: usize,
+ /// The offsets within this buffer that contain pointers which should be translated.
+ pointer_fixups: KVec<PointerFixupEntry>,
+}
+
+/// This entry specifies that a fixup should happen at `target_offset` of the
+/// buffer. If `skip` is nonzero, then the fixup is a `binder_fd_array_object`
+/// and is applied later. Otherwise if `skip` is zero, then the size of the
+/// fixup is `sizeof::<u64>()` and `pointer_value` is written to the buffer.
+struct PointerFixupEntry {
+ /// The number of bytes to skip, or zero for a `binder_buffer_object` fixup.
+ skip: usize,
+ /// The translated pointer to write when `skip` is zero.
+ pointer_value: u64,
+ /// The offset at which the value should be written. The offset is relative
+ /// to the original buffer.
+ target_offset: usize,
+}
+
+/// Return type of `apply_and_validate_fixup_in_parent`.
+struct ParentFixupInfo {
+ /// The index of the parent buffer in `sg_entries`.
+ parent_sg_index: usize,
+ /// The number of ancestors of the buffer.
+ ///
+ /// The buffer is considered an ancestor of itself, so this is always at
+ /// least one.
+ num_ancestors: usize,
+ /// New value of `fixup_min_offset` if this fixup is applied.
+ new_min_offset: usize,
+ /// The offset of the fixup in the target buffer.
+ target_offset: usize,
+}
+
+impl ScatterGatherState {
+ /// Called when a `binder_buffer_object` or `binder_fd_array_object` tries
+ /// to access a region in its parent buffer. These accesses have various
+ /// restrictions, which this method verifies.
+ ///
+ /// The `parent_offset` and `length` arguments describe the offset and
+ /// length of the access in the parent buffer.
+ ///
+ /// # Detailed restrictions
+ ///
+ /// Obviously the fixup must be in-bounds for the parent buffer.
+ ///
+ /// 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
+ ///
+ /// Arcerring 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
+ fn validate_parent_fixup(
+ &self,
+ parent: usize,
+ parent_offset: usize,
+ length: usize,
+ ) -> Result<ParentFixupInfo> {
+ // Using `position` would also be correct, but `rposition` avoids
+ // quadratic running times.
+ let ancestors_i = self
+ .ancestors
+ .iter()
+ .copied()
+ .rposition(|sg_idx| self.sg_entries[sg_idx].obj_index == parent)
+ .ok_or(EINVAL)?;
+ let sg_idx = self.ancestors[ancestors_i];
+ let sg_entry = match self.sg_entries.get(sg_idx) {
+ Some(sg_entry) => sg_entry,
+ None => {
+ pr_err!(
+ "self.ancestors[{}] is {}, but self.sg_entries.len() is {}",
+ ancestors_i,
+ sg_idx,
+ self.sg_entries.len()
+ );
+ return Err(EINVAL);
+ }
+ };
+ if sg_entry.fixup_min_offset > parent_offset {
+ pr_warn!(
+ "validate_parent_fixup: fixup_min_offset={}, parent_offset={}",
+ sg_entry.fixup_min_offset,
+ parent_offset
+ );
+ return Err(EINVAL);
+ }
+ let new_min_offset = parent_offset.checked_add(length).ok_or(EINVAL)?;
+ if new_min_offset > sg_entry.length {
+ pr_warn!(
+ "validate_parent_fixup: new_min_offset={}, sg_entry.length={}",
+ new_min_offset,
+ sg_entry.length
+ );
+ return Err(EINVAL);
+ }
+ let target_offset = sg_entry.offset.checked_add(parent_offset).ok_or(EINVAL)?;
+ // The `ancestors_i + 1` operation can't overflow since the output of the addition is at
+ // most `self.ancestors.len()`, which also fits in a usize.
+ Ok(ParentFixupInfo {
+ parent_sg_index: sg_idx,
+ num_ancestors: ancestors_i + 1,
+ new_min_offset,
+ target_offset,
+ })
+ }
+}
+
+/// Keeps track of how much unused buffer space is left. The initial amount is the number of bytes
+/// requested by the user using the `buffers_size` field of `binder_transaction_data_sg`. Each time
+/// we translate an object of type `BINDER_TYPE_PTR`, some of the unused buffer space is consumed.
+struct UnusedBufferSpace {
+ /// The start of the remaining space.
+ offset: usize,
+ /// The end of the remaining space.
+ limit: usize,
+}
+impl UnusedBufferSpace {
+ /// Claim the next `size` bytes from the unused buffer space. The offset for the claimed chunk
+ /// into the buffer is returned.
+ fn claim_next(&mut self, size: usize) -> Result<usize> {
+ // We require every chunk to be aligned.
+ let size = ptr_align(size).ok_or(EINVAL)?;
+ let new_offset = self.offset.checked_add(size).ok_or(EINVAL)?;
+
+ if new_offset <= self.limit {
+ let offset = self.offset;
+ self.offset = new_offset;
+ Ok(offset)
+ } else {
+ Err(EINVAL)
+ }
+ }
+}
+
+pub(crate) enum PushWorkRes {
+ Ok,
+ FailedDead(DLArc<dyn DeliverToRead>),
+}
+
+impl PushWorkRes {
+ fn is_ok(&self) -> bool {
+ match self {
+ PushWorkRes::Ok => true,
+ PushWorkRes::FailedDead(_) => false,
+ }
+ }
+}
+
+/// The fields of `Thread` protected by the spinlock.
+struct InnerThread {
+ /// Determines the looper state of the thread. It is a bit-wise combination of the constants
+ /// prefixed with `LOOPER_`.
+ looper_flags: u32,
+
+ /// Determines whether the looper should return.
+ looper_need_return: bool,
+
+ /// Determines if thread is dead.
+ is_dead: bool,
+
+ /// Work item used to deliver error codes to the thread that started a transaction. Stored here
+ /// so that it can be reused.
+ reply_work: DArc<ThreadError>,
+
+ /// Work item used to deliver error codes to the current thread. Stored here so that it can be
+ /// reused.
+ return_work: DArc<ThreadError>,
+
+ /// Determines whether the work list below should be processed. When set to false, `work_list`
+ /// is treated as if it were empty.
+ process_work_list: bool,
+ /// List of work items to deliver to userspace.
+ work_list: List<DTRWrap<dyn DeliverToRead>>,
+ current_transaction: Option<DArc<Transaction>>,
+
+ /// Extended error information for this thread.
+ extended_error: ExtendedError,
+}
+
+const LOOPER_REGISTERED: u32 = 0x01;
+const LOOPER_ENTERED: u32 = 0x02;
+const LOOPER_EXITED: u32 = 0x04;
+const LOOPER_INVALID: u32 = 0x08;
+const LOOPER_WAITING: u32 = 0x10;
+const LOOPER_WAITING_PROC: u32 = 0x20;
+const LOOPER_POLL: u32 = 0x40;
+
+impl InnerThread {
+ fn new() -> Result<Self> {
+ fn next_err_id() -> u32 {
+ static EE_ID: AtomicU32 = AtomicU32::new(0);
+ EE_ID.fetch_add(1, Ordering::Relaxed)
+ }
+
+ Ok(Self {
+ looper_flags: 0,
+ looper_need_return: false,
+ is_dead: false,
+ process_work_list: false,
+ reply_work: ThreadError::try_new()?,
+ return_work: ThreadError::try_new()?,
+ work_list: List::new(),
+ current_transaction: None,
+ extended_error: ExtendedError::new(next_err_id(), BR_OK, 0),
+ })
+ }
+
+ fn pop_work(&mut self) -> Option<DLArc<dyn DeliverToRead>> {
+ if !self.process_work_list {
+ return None;
+ }
+
+ let ret = self.work_list.pop_front();
+ self.process_work_list = !self.work_list.is_empty();
+ ret
+ }
+
+ fn push_work(&mut self, work: DLArc<dyn DeliverToRead>) -> PushWorkRes {
+ if self.is_dead {
+ PushWorkRes::FailedDead(work)
+ } else {
+ self.work_list.push_back(work);
+ self.process_work_list = true;
+ PushWorkRes::Ok
+ }
+ }
+
+ fn push_reply_work(&mut self, code: u32) {
+ if let Ok(work) = ListArc::try_from_arc(self.reply_work.clone()) {
+ work.set_error_code(code);
+ self.push_work(work);
+ } else {
+ pr_warn!("Thread reply work is already in use.");
+ }
+ }
+
+ fn push_return_work(&mut self, reply: u32) {
+ if let Ok(work) = ListArc::try_from_arc(self.return_work.clone()) {
+ work.set_error_code(reply);
+ self.push_work(work);
+ } else {
+ pr_warn!("Thread return work is already in use.");
+ }
+ }
+
+ /// Used to push work items that do not need to be processed immediately and can wait until the
+ /// thread gets another work item.
+ fn push_work_deferred(&mut self, work: DLArc<dyn DeliverToRead>) {
+ self.work_list.push_back(work);
+ }
+
+ /// Fetches the transaction this thread can reply to. If the thread has a pending transaction
+ /// (that it could respond to) but it has also issued a transaction, it must first wait for the
+ /// previously-issued transaction to complete.
+ ///
+ /// The `thread` parameter should be the thread containing this `ThreadInner`.
+ fn pop_transaction_to_reply(&mut self, thread: &Thread) -> Result<DArc<Transaction>> {
+ let transaction = self.current_transaction.take().ok_or(EINVAL)?;
+ if core::ptr::eq(thread, transaction.from.as_ref()) {
+ self.current_transaction = Some(transaction);
+ return Err(EINVAL);
+ }
+ // Find a new current transaction for this thread.
+ self.current_transaction = transaction.find_from(thread).cloned();
+ Ok(transaction)
+ }
+
+ fn pop_transaction_replied(&mut self, transaction: &DArc<Transaction>) -> bool {
+ match self.current_transaction.take() {
+ None => false,
+ Some(old) => {
+ if !Arc::ptr_eq(transaction, &old) {
+ self.current_transaction = Some(old);
+ return false;
+ }
+ self.current_transaction = old.clone_next();
+ true
+ }
+ }
+ }
+
+ fn looper_enter(&mut self) {
+ self.looper_flags |= LOOPER_ENTERED;
+ if self.looper_flags & LOOPER_REGISTERED != 0 {
+ self.looper_flags |= LOOPER_INVALID;
+ }
+ }
+
+ fn looper_register(&mut self, valid: bool) {
+ self.looper_flags |= LOOPER_REGISTERED;
+ if !valid || self.looper_flags & LOOPER_ENTERED != 0 {
+ self.looper_flags |= LOOPER_INVALID;
+ }
+ }
+
+ fn looper_exit(&mut self) {
+ self.looper_flags |= LOOPER_EXITED;
+ }
+
+ /// Determines whether the thread is part of a pool, i.e., if it is a looper.
+ fn is_looper(&self) -> bool {
+ self.looper_flags & (LOOPER_ENTERED | LOOPER_REGISTERED) != 0
+ }
+
+ /// Determines whether the thread should attempt to fetch work items from the process queue.
+ /// This is generally case when the thread is registered as a looper and not part of a
+ /// transaction stack. But if there is local work, we want to return to userspace before we
+ /// deliver any remote work.
+ fn should_use_process_work_queue(&self) -> bool {
+ self.current_transaction.is_none() && !self.process_work_list && self.is_looper()
+ }
+
+ fn poll(&mut self) -> u32 {
+ self.looper_flags |= LOOPER_POLL;
+ if self.process_work_list || self.looper_need_return {
+ bindings::POLLIN
+ } else {
+ 0
+ }
+ }
+}
+
+/// This represents a thread that's used with binder.
+#[pin_data]
+pub(crate) struct Thread {
+ pub(crate) id: i32,
+ pub(crate) process: Arc<Process>,
+ pub(crate) task: ARef<Task>,
+ #[pin]
+ inner: SpinLock<InnerThread>,
+ #[pin]
+ work_condvar: PollCondVar,
+ /// Used to insert this thread into the process' `ready_threads` list.
+ ///
+ /// INVARIANT: May never be used for any other list than the `self.process.ready_threads`.
+ #[pin]
+ links: ListLinks,
+ #[pin]
+ links_track: AtomicTracker,
+}
+
+kernel::list::impl_list_arc_safe! {
+ impl ListArcSafe<0> for Thread {
+ tracked_by links_track: AtomicTracker;
+ }
+}
+kernel::list::impl_list_item! {
+ impl ListItem<0> for Thread {
+ using ListLinks { self.links };
+ }
+}
+
+impl Thread {
+ pub(crate) fn new(id: i32, process: Arc<Process>) -> Result<Arc<Self>> {
+ let inner = InnerThread::new()?;
+
+ Arc::pin_init(
+ try_pin_init!(Thread {
+ id,
+ process,
+ task: ARef::from(&**kernel::current!()),
+ inner <- kernel::new_spinlock!(inner, "Thread::inner"),
+ work_condvar <- kernel::new_poll_condvar!("Thread::work_condvar"),
+ links <- ListLinks::new(),
+ links_track <- AtomicTracker::new(),
+ }),
+ GFP_KERNEL,
+ )
+ }
+
+ #[inline(never)]
+ 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() {
+ seq_print!(
+ m,
+ " thread {}: l {:02x} need_return {}\n",
+ self.id,
+ inner.looper_flags,
+ inner.looper_need_return,
+ );
+ }
+
+ let mut t_opt = inner.current_transaction.as_ref();
+ while let Some(t) = t_opt {
+ if Arc::ptr_eq(&t.from, self) {
+ t.debug_print_inner(m, " outgoing transaction ");
+ t_opt = t.from_parent.as_ref();
+ } else if Arc::ptr_eq(&t.to, &self.process) {
+ t.debug_print_inner(m, " incoming transaction ");
+ t_opt = t.find_from(self);
+ } else {
+ t.debug_print_inner(m, " bad transaction ");
+ t_opt = None;
+ }
+ }
+
+ for work in &inner.work_list {
+ work.debug_print(m, " ", " pending transaction ")?;
+ }
+ Ok(())
+ }
+
+ pub(crate) fn get_extended_error(&self, data: UserSlice) -> Result {
+ let mut writer = data.writer();
+ let ee = self.inner.lock().extended_error;
+ writer.write(&ee)?;
+ Ok(())
+ }
+
+ pub(crate) fn set_current_transaction(&self, transaction: DArc<Transaction>) {
+ self.inner.lock().current_transaction = Some(transaction);
+ }
+
+ pub(crate) fn has_current_transaction(&self) -> bool {
+ self.inner.lock().current_transaction.is_some()
+ }
+
+ /// Attempts to fetch a work item from the thread-local queue. The behaviour if the queue is
+ /// empty depends on `wait`: if it is true, the function waits for some work to be queued (or a
+ /// signal); otherwise it returns indicating that none is available.
+ fn get_work_local(self: &Arc<Self>, wait: bool) -> Result<Option<DLArc<dyn DeliverToRead>>> {
+ {
+ let mut inner = self.inner.lock();
+ if inner.looper_need_return {
+ return Ok(inner.pop_work());
+ }
+ }
+
+ // Try once if the caller does not want to wait.
+ if !wait {
+ return self.inner.lock().pop_work().ok_or(EAGAIN).map(Some);
+ }
+
+ // Loop waiting only on the local queue (i.e., not registering with the process queue).
+ let mut inner = self.inner.lock();
+ loop {
+ if let Some(work) = inner.pop_work() {
+ return Ok(Some(work));
+ }
+
+ inner.looper_flags |= LOOPER_WAITING;
+ let signal_pending = self.work_condvar.wait_interruptible_freezable(&mut inner);
+ inner.looper_flags &= !LOOPER_WAITING;
+
+ if signal_pending {
+ return Err(EINTR);
+ }
+ if inner.looper_need_return {
+ return Ok(None);
+ }
+ }
+ }
+
+ /// Attempts to fetch a work item from the thread-local queue, falling back to the process-wide
+ /// queue if none is available locally.
+ ///
+ /// This must only be called when the thread is not participating in a transaction chain. If it
+ /// is, the local version (`get_work_local`) should be used instead.
+ fn get_work(self: &Arc<Self>, wait: bool) -> Result<Option<DLArc<dyn DeliverToRead>>> {
+ // Try to get work from the thread's work queue, using only a local lock.
+ {
+ let mut inner = self.inner.lock();
+ if let Some(work) = inner.pop_work() {
+ return Ok(Some(work));
+ }
+ if inner.looper_need_return {
+ drop(inner);
+ return Ok(self.process.get_work());
+ }
+ }
+
+ // 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.
+ 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) {
+ GetWorkOrRegister::Work(work) => return Ok(Some(work)),
+ GetWorkOrRegister::Register(reg) => reg,
+ };
+
+ let mut inner = self.inner.lock();
+ loop {
+ if let Some(work) = inner.pop_work() {
+ return Ok(Some(work));
+ }
+
+ inner.looper_flags |= LOOPER_WAITING | LOOPER_WAITING_PROC;
+ let signal_pending = self.work_condvar.wait_interruptible_freezable(&mut inner);
+ inner.looper_flags &= !(LOOPER_WAITING | LOOPER_WAITING_PROC);
+
+ 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).
+ drop(inner);
+ drop(reg);
+
+ let res = match self.inner.lock().pop_work() {
+ Some(work) => Ok(Some(work)),
+ None if signal_pending => Err(EINTR),
+ None => Ok(None),
+ };
+ return res;
+ }
+ }
+ }
+
+ /// Push the provided work item to be delivered to user space via this thread.
+ ///
+ /// Returns whether the item was successfully pushed. This can only fail if the thread is dead.
+ pub(crate) fn push_work(&self, work: DLArc<dyn DeliverToRead>) -> PushWorkRes {
+ let sync = work.should_sync_wakeup();
+
+ let res = self.inner.lock().push_work(work);
+
+ if res.is_ok() {
+ if sync {
+ self.work_condvar.notify_sync();
+ } else {
+ self.work_condvar.notify_one();
+ }
+ }
+
+ res
+ }
+
+ /// Attempts to push to given work item to the thread if it's a looper thread (i.e., if it's
+ /// part of a thread pool) and is alive. Otherwise, push the work item to the process instead.
+ 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 {
+ inner.push_work(work);
+ Ok(())
+ } else {
+ drop(inner);
+ self.process.push_work(work)
+ }
+ }
+
+ pub(crate) fn push_work_deferred(&self, work: DLArc<dyn DeliverToRead>) {
+ self.inner.lock().push_work_deferred(work);
+ }
+
+ pub(crate) fn push_return_work(&self, reply: u32) {
+ self.inner.lock().push_return_work(reply);
+ }
+
+ fn translate_object(
+ &self,
+ obj_index: usize,
+ offset: usize,
+ object: BinderObjectRef<'_>,
+ view: &mut AllocationView<'_>,
+ allow_fds: bool,
+ sg_state: &mut ScatterGatherState,
+ ) -> BinderResult {
+ match object {
+ BinderObjectRef::Binder(obj) => {
+ let strong = obj.hdr.type_ == BINDER_TYPE_BINDER;
+ // SAFETY: `binder` is a `binder_uintptr_t`; any bit pattern is a valid
+ // representation.
+ let ptr = unsafe { obj.__bindgen_anon_1.binder } as _;
+ let cookie = obj.cookie as _;
+ let flags = obj.flags as _;
+ let node = self
+ .process
+ .as_arc_borrow()
+ .get_node(ptr, cookie, flags, strong, self)?;
+ security::binder_transfer_binder(&self.process.cred, &view.alloc.process.cred)?;
+ view.transfer_binder_object(offset, obj, strong, node)?;
+ }
+ BinderObjectRef::Handle(obj) => {
+ let strong = obj.hdr.type_ == BINDER_TYPE_HANDLE;
+ // SAFETY: `handle` is a `u32`; any bit pattern is a valid representation.
+ let handle = unsafe { obj.__bindgen_anon_1.handle } as _;
+ let node = self.process.get_node_from_handle(handle, strong)?;
+ security::binder_transfer_binder(&self.process.cred, &view.alloc.process.cred)?;
+ view.transfer_binder_object(offset, obj, strong, node)?;
+ }
+ BinderObjectRef::Fd(obj) => {
+ if !allow_fds {
+ return Err(EPERM.into());
+ }
+
+ // SAFETY: `fd` is a `u32`; any bit pattern is a valid representation.
+ let fd = unsafe { obj.__bindgen_anon_1.fd };
+ let file = LocalFile::fget(fd)?;
+ // SAFETY: The binder driver never calls `fdget_pos` and this code runs from an
+ // ioctl, so there are no active calls to `fdget_pos` on this thread.
+ let file = unsafe { LocalFile::assume_no_fdget_pos(file) };
+ security::binder_transfer_file(
+ &self.process.cred,
+ &view.alloc.process.cred,
+ &file,
+ )?;
+
+ let mut obj_write = BinderFdObject::default();
+ obj_write.hdr.type_ = BINDER_TYPE_FD;
+ // This will be overwritten with the actual fd when the transaction is received.
+ obj_write.__bindgen_anon_1.fd = u32::MAX;
+ obj_write.cookie = obj.cookie;
+ view.write::<BinderFdObject>(offset, &obj_write)?;
+
+ const FD_FIELD_OFFSET: usize =
+ core::mem::offset_of!(uapi::binder_fd_object, __bindgen_anon_1.fd);
+
+ let field_offset = offset + FD_FIELD_OFFSET;
+
+ view.alloc.info_add_fd(file, field_offset, false)?;
+ }
+ BinderObjectRef::Ptr(obj) => {
+ let obj_length = obj.length.try_into().map_err(|_| EINVAL)?;
+ let alloc_offset = match sg_state.unused_buffer_space.claim_next(obj_length) {
+ Ok(alloc_offset) => alloc_offset,
+ Err(err) => {
+ pr_warn!(
+ "Failed to claim space for a BINDER_TYPE_PTR. (offset: {}, limit: {}, size: {})",
+ sg_state.unused_buffer_space.offset,
+ sg_state.unused_buffer_space.limit,
+ obj_length,
+ );
+ return Err(err.into());
+ }
+ };
+
+ let sg_state_idx = sg_state.sg_entries.len();
+ sg_state.sg_entries.push(
+ ScatterGatherEntry {
+ obj_index,
+ offset: alloc_offset,
+ sender_uaddr: obj.buffer as _,
+ length: obj_length,
+ pointer_fixups: KVec::new(),
+ fixup_min_offset: 0,
+ },
+ GFP_KERNEL,
+ )?;
+
+ let buffer_ptr_in_user_space = (view.alloc.ptr + alloc_offset) as u64;
+
+ if obj.flags & uapi::BINDER_BUFFER_FLAG_HAS_PARENT == 0 {
+ sg_state.ancestors.clear();
+ sg_state.ancestors.push(sg_state_idx, GFP_KERNEL)?;
+ } else {
+ // Another buffer also has a pointer to this buffer, and we need to fixup that
+ // pointer too.
+
+ let parent_index = usize::try_from(obj.parent).map_err(|_| EINVAL)?;
+ let parent_offset = usize::try_from(obj.parent_offset).map_err(|_| EINVAL)?;
+
+ let info = sg_state.validate_parent_fixup(
+ parent_index,
+ parent_offset,
+ size_of::<u64>(),
+ )?;
+
+ sg_state.ancestors.truncate(info.num_ancestors);
+ sg_state.ancestors.push(sg_state_idx, GFP_KERNEL)?;
+
+ let parent_entry = match sg_state.sg_entries.get_mut(info.parent_sg_index) {
+ Some(parent_entry) => parent_entry,
+ None => {
+ pr_err!(
+ "validate_parent_fixup returned index out of bounds for sg.entries"
+ );
+ return Err(EINVAL.into());
+ }
+ };
+
+ parent_entry.fixup_min_offset = info.new_min_offset;
+ parent_entry.pointer_fixups.push(
+ PointerFixupEntry {
+ skip: 0,
+ pointer_value: buffer_ptr_in_user_space,
+ target_offset: info.target_offset,
+ },
+ GFP_KERNEL,
+ )?;
+ }
+
+ let mut obj_write = BinderBufferObject::default();
+ obj_write.hdr.type_ = BINDER_TYPE_PTR;
+ obj_write.flags = obj.flags;
+ obj_write.buffer = buffer_ptr_in_user_space;
+ obj_write.length = obj.length;
+ obj_write.parent = obj.parent;
+ obj_write.parent_offset = obj.parent_offset;
+ view.write::<BinderBufferObject>(offset, &obj_write)?;
+ }
+ BinderObjectRef::Fda(obj) => {
+ if !allow_fds {
+ return Err(EPERM.into());
+ }
+ let parent_index = usize::try_from(obj.parent).map_err(|_| EINVAL)?;
+ let parent_offset = usize::try_from(obj.parent_offset).map_err(|_| EINVAL)?;
+ let num_fds = usize::try_from(obj.num_fds).map_err(|_| EINVAL)?;
+ let fds_len = num_fds.checked_mul(size_of::<u32>()).ok_or(EINVAL)?;
+
+ let info = sg_state.validate_parent_fixup(parent_index, parent_offset, fds_len)?;
+ view.alloc.info_add_fd_reserve(num_fds)?;
+
+ sg_state.ancestors.truncate(info.num_ancestors);
+ let parent_entry = match sg_state.sg_entries.get_mut(info.parent_sg_index) {
+ Some(parent_entry) => parent_entry,
+ None => {
+ pr_err!(
+ "validate_parent_fixup returned index out of bounds for sg.entries"
+ );
+ return Err(EINVAL.into());
+ }
+ };
+
+ parent_entry.fixup_min_offset = info.new_min_offset;
+ parent_entry
+ .pointer_fixups
+ .push(
+ PointerFixupEntry {
+ skip: fds_len,
+ pointer_value: 0,
+ target_offset: info.target_offset,
+ },
+ GFP_KERNEL,
+ )
+ .map_err(|_| ENOMEM)?;
+
+ let fda_uaddr = parent_entry
+ .sender_uaddr
+ .checked_add(parent_offset)
+ .ok_or(EINVAL)?;
+ let mut fda_bytes = KVec::new();
+ UserSlice::new(UserPtr::from_addr(fda_uaddr as _), fds_len)
+ .read_all(&mut fda_bytes, GFP_KERNEL)?;
+
+ if fds_len != fda_bytes.len() {
+ pr_err!("UserSlice::read_all returned wrong length in BINDER_TYPE_FDA");
+ return Err(EINVAL.into());
+ }
+
+ for i in (0..fds_len).step_by(size_of::<u32>()) {
+ let fd = {
+ let mut fd_bytes = [0u8; size_of::<u32>()];
+ fd_bytes.copy_from_slice(&fda_bytes[i..i + size_of::<u32>()]);
+ u32::from_ne_bytes(fd_bytes)
+ };
+
+ let file = LocalFile::fget(fd)?;
+ // SAFETY: The binder driver never calls `fdget_pos` and this code runs from an
+ // ioctl, so there are no active calls to `fdget_pos` on this thread.
+ let file = unsafe { LocalFile::assume_no_fdget_pos(file) };
+ security::binder_transfer_file(
+ &self.process.cred,
+ &view.alloc.process.cred,
+ &file,
+ )?;
+
+ // The `validate_parent_fixup` call ensuers that this addition will not
+ // overflow.
+ view.alloc.info_add_fd(file, info.target_offset + i, true)?;
+ }
+ drop(fda_bytes);
+
+ let mut obj_write = BinderFdArrayObject::default();
+ obj_write.hdr.type_ = BINDER_TYPE_FDA;
+ obj_write.num_fds = obj.num_fds;
+ obj_write.parent = obj.parent;
+ obj_write.parent_offset = obj.parent_offset;
+ view.write::<BinderFdArrayObject>(offset, &obj_write)?;
+ }
+ }
+ Ok(())
+ }
+
+ fn apply_sg(&self, alloc: &mut Allocation, sg_state: &mut ScatterGatherState) -> BinderResult {
+ for sg_entry in &mut sg_state.sg_entries {
+ let mut end_of_previous_fixup = sg_entry.offset;
+ let offset_end = sg_entry.offset.checked_add(sg_entry.length).ok_or(EINVAL)?;
+
+ let mut reader =
+ UserSlice::new(UserPtr::from_addr(sg_entry.sender_uaddr), sg_entry.length).reader();
+ for fixup in &mut sg_entry.pointer_fixups {
+ let fixup_len = if fixup.skip == 0 {
+ size_of::<u64>()
+ } else {
+ fixup.skip
+ };
+
+ let target_offset_end = fixup.target_offset.checked_add(fixup_len).ok_or(EINVAL)?;
+ if fixup.target_offset < end_of_previous_fixup || offset_end < target_offset_end {
+ pr_warn!(
+ "Fixups oob {} {} {} {}",
+ fixup.target_offset,
+ end_of_previous_fixup,
+ offset_end,
+ target_offset_end
+ );
+ return Err(EINVAL.into());
+ }
+
+ let copy_off = end_of_previous_fixup;
+ let copy_len = fixup.target_offset - end_of_previous_fixup;
+ if let Err(err) = alloc.copy_into(&mut reader, copy_off, copy_len) {
+ pr_warn!("Failed copying into alloc: {:?}", err);
+ return Err(err.into());
+ }
+ if fixup.skip == 0 {
+ let res = alloc.write::<u64>(fixup.target_offset, &fixup.pointer_value);
+ if let Err(err) = res {
+ pr_warn!("Failed copying ptr into alloc: {:?}", err);
+ return Err(err.into());
+ }
+ }
+ if let Err(err) = reader.skip(fixup_len) {
+ pr_warn!("Failed skipping {} from reader: {:?}", fixup_len, err);
+ return Err(err.into());
+ }
+ end_of_previous_fixup = target_offset_end;
+ }
+ let copy_off = end_of_previous_fixup;
+ let copy_len = offset_end - end_of_previous_fixup;
+ if let Err(err) = alloc.copy_into(&mut reader, copy_off, copy_len) {
+ pr_warn!("Failed copying remainder into alloc: {:?}", err);
+ return Err(err.into());
+ }
+ }
+ Ok(())
+ }
+
+ /// This method copies the payload of a transaction into the target process.
+ ///
+ /// The resulting payload will have several different components, which will be stored next to
+ /// each other in the allocation. Furthermore, various objects can be embedded in the payload,
+ /// and those objects have to be translated so that they make sense to the target transaction.
+ pub(crate) fn copy_transaction_data(
+ &self,
+ to_process: Arc<Process>,
+ tr: &BinderTransactionDataSg,
+ debug_id: usize,
+ allow_fds: bool,
+ txn_security_ctx_offset: Option<&mut usize>,
+ ) -> BinderResult<NewAllocation> {
+ let trd = &tr.transaction_data;
+ let is_oneway = trd.flags & TF_ONE_WAY != 0;
+ let mut secctx = if let Some(offset) = txn_security_ctx_offset {
+ let secid = self.process.cred.get_secid();
+ let ctx = match security::SecurityCtx::from_secid(secid) {
+ Ok(ctx) => ctx,
+ Err(err) => {
+ pr_warn!("Failed to get security ctx for id {}: {:?}", secid, err);
+ return Err(err.into());
+ }
+ };
+ Some((offset, ctx))
+ } else {
+ None
+ };
+
+ let data_size = trd.data_size.try_into().map_err(|_| EINVAL)?;
+ let aligned_data_size = ptr_align(data_size).ok_or(EINVAL)?;
+ let offsets_size = trd.offsets_size.try_into().map_err(|_| EINVAL)?;
+ let aligned_offsets_size = ptr_align(offsets_size).ok_or(EINVAL)?;
+ let buffers_size = tr.buffers_size.try_into().map_err(|_| EINVAL)?;
+ let aligned_buffers_size = ptr_align(buffers_size).ok_or(EINVAL)?;
+ let aligned_secctx_size = match secctx.as_ref() {
+ Some((_offset, ctx)) => ptr_align(ctx.len()).ok_or(EINVAL)?,
+ None => 0,
+ };
+
+ // This guarantees that at least `sizeof(usize)` bytes will be allocated.
+ let len = usize::max(
+ aligned_data_size
+ .checked_add(aligned_offsets_size)
+ .and_then(|sum| sum.checked_add(aligned_buffers_size))
+ .and_then(|sum| sum.checked_add(aligned_secctx_size))
+ .ok_or(ENOMEM)?,
+ size_of::<usize>(),
+ );
+ let secctx_off = aligned_data_size + aligned_offsets_size + aligned_buffers_size;
+ let mut alloc =
+ match to_process.buffer_alloc(debug_id, len, is_oneway, self.process.task.pid()) {
+ Ok(alloc) => alloc,
+ Err(err) => {
+ pr_warn!(
+ "Failed to allocate buffer. len:{}, is_oneway:{}",
+ len,
+ is_oneway
+ );
+ return Err(err);
+ }
+ };
+
+ // SAFETY: This accesses a union field, but it's okay because the field's type is valid for
+ // all bit-patterns.
+ let trd_data_ptr = unsafe { &trd.data.ptr };
+ let mut buffer_reader =
+ UserSlice::new(UserPtr::from_addr(trd_data_ptr.buffer as _), data_size).reader();
+ let mut end_of_previous_object = 0;
+ let mut sg_state = None;
+
+ // Copy offsets if there are any.
+ if offsets_size > 0 {
+ {
+ let mut reader =
+ UserSlice::new(UserPtr::from_addr(trd_data_ptr.offsets as _), offsets_size)
+ .reader();
+ alloc.copy_into(&mut reader, aligned_data_size, offsets_size)?;
+ }
+
+ let offsets_start = aligned_data_size;
+ let offsets_end = aligned_data_size + aligned_offsets_size;
+
+ // This state is used for BINDER_TYPE_PTR objects.
+ let sg_state = sg_state.insert(ScatterGatherState {
+ unused_buffer_space: UnusedBufferSpace {
+ offset: offsets_end,
+ limit: len,
+ },
+ sg_entries: KVec::new(),
+ ancestors: KVec::new(),
+ });
+
+ // Traverse the objects specified.
+ let mut view = AllocationView::new(&mut alloc, data_size);
+ for (index, index_offset) in (offsets_start..offsets_end)
+ .step_by(size_of::<usize>())
+ .enumerate()
+ {
+ let offset = view.alloc.read(index_offset)?;
+
+ if offset < end_of_previous_object {
+ pr_warn!("Got transaction with invalid offset.");
+ return Err(EINVAL.into());
+ }
+
+ // Copy data between two objects.
+ if end_of_previous_object < offset {
+ view.copy_into(
+ &mut buffer_reader,
+ end_of_previous_object,
+ offset - end_of_previous_object,
+ )?;
+ }
+
+ let mut object = BinderObject::read_from(&mut buffer_reader)?;
+
+ match self.translate_object(
+ index,
+ offset,
+ object.as_ref(),
+ &mut view,
+ allow_fds,
+ sg_state,
+ ) {
+ Ok(()) => end_of_previous_object = offset + object.size(),
+ Err(err) => {
+ pr_warn!("Error while translating object.");
+ return Err(err);
+ }
+ }
+
+ // Update the indexes containing objects to clean up.
+ let offset_after_object = index_offset + size_of::<usize>();
+ view.alloc
+ .set_info_offsets(offsets_start..offset_after_object);
+ }
+ }
+
+ // Copy remaining raw data.
+ alloc.copy_into(
+ &mut buffer_reader,
+ end_of_previous_object,
+ data_size - end_of_previous_object,
+ )?;
+
+ if let Some(sg_state) = sg_state.as_mut() {
+ if let Err(err) = self.apply_sg(&mut alloc, sg_state) {
+ pr_warn!("Failure in apply_sg: {:?}", err);
+ return Err(err);
+ }
+ }
+
+ if let Some((off_out, secctx)) = secctx.as_mut() {
+ if let Err(err) = alloc.write(secctx_off, secctx.as_bytes()) {
+ pr_warn!("Failed to write security context: {:?}", err);
+ return Err(err.into());
+ }
+ **off_out = secctx_off;
+ }
+ Ok(alloc)
+ }
+
+ fn unwind_transaction_stack(self: &Arc<Self>) {
+ let mut thread = self.clone();
+ while let Ok(transaction) = {
+ let mut inner = thread.inner.lock();
+ inner.pop_transaction_to_reply(thread.as_ref())
+ } {
+ let reply = Err(BR_DEAD_REPLY);
+ if !transaction.from.deliver_single_reply(reply, &transaction) {
+ break;
+ }
+
+ thread = transaction.from.clone();
+ }
+ }
+
+ pub(crate) fn deliver_reply(
+ &self,
+ reply: Result<DLArc<Transaction>, u32>,
+ transaction: &DArc<Transaction>,
+ ) {
+ if self.deliver_single_reply(reply, transaction) {
+ transaction.from.unwind_transaction_stack();
+ }
+ }
+
+ /// Delivers a reply to the thread that started a transaction. The reply can either be a
+ /// reply-transaction or an error code to be delivered instead.
+ ///
+ /// Returns whether the thread is dead. If it is, the caller is expected to unwind the
+ /// transaction stack by completing transactions for threads that are dead.
+ fn deliver_single_reply(
+ &self,
+ reply: Result<DLArc<Transaction>, u32>,
+ transaction: &DArc<Transaction>,
+ ) -> bool {
+ if let Ok(transaction) = &reply {
+ transaction.set_outstanding(&mut self.process.inner.lock());
+ }
+
+ {
+ let mut inner = self.inner.lock();
+ if !inner.pop_transaction_replied(transaction) {
+ return false;
+ }
+
+ if inner.is_dead {
+ return true;
+ }
+
+ match reply {
+ Ok(work) => {
+ inner.push_work(work);
+ }
+ Err(code) => inner.push_reply_work(code),
+ }
+ }
+
+ // Notify the thread now that we've released the inner lock.
+ self.work_condvar.notify_sync();
+ false
+ }
+
+ /// Determines if the given transaction is the current transaction for this thread.
+ fn is_current_transaction(&self, transaction: &DArc<Transaction>) -> bool {
+ let inner = self.inner.lock();
+ match &inner.current_transaction {
+ None => false,
+ Some(current) => Arc::ptr_eq(current, transaction),
+ }
+ }
+
+ /// Determines the current top of the transaction stack. It fails if the top is in another
+ /// thread (i.e., this thread belongs to a stack but it has called another thread). The top is
+ /// [`None`] if the thread is not currently participating in a transaction stack.
+ fn top_of_transaction_stack(&self) -> Result<Option<DArc<Transaction>>> {
+ let inner = self.inner.lock();
+ if let Some(cur) = &inner.current_transaction {
+ if core::ptr::eq(self, cur.from.as_ref()) {
+ pr_warn!("got new transaction with bad transaction stack");
+ return Err(EINVAL);
+ }
+ Ok(Some(cur.clone()))
+ } else {
+ Ok(None)
+ }
+ }
+
+ fn transaction<T>(self: &Arc<Self>, tr: &BinderTransactionDataSg, inner: T)
+ where
+ T: FnOnce(&Arc<Self>, &BinderTransactionDataSg) -> BinderResult,
+ {
+ if let Err(err) = inner(self, tr) {
+ if err.should_pr_warn() {
+ let mut ee = self.inner.lock().extended_error;
+ ee.command = err.reply;
+ ee.param = err.as_errno();
+ pr_warn!(
+ "Transaction failed: {:?} my_pid:{}",
+ err,
+ self.process.pid_in_current_ns()
+ );
+ }
+
+ self.push_return_work(err.reply);
+ }
+ }
+
+ fn transaction_inner(self: &Arc<Self>, tr: &BinderTransactionDataSg) -> BinderResult {
+ // SAFETY: Handle's type has no invalid bit patterns.
+ let handle = unsafe { tr.transaction_data.target.handle };
+ let node_ref = self.process.get_transaction_node(handle)?;
+ 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?
+ let top = self.top_of_transaction_stack()?;
+ let list_completion = DTRWrap::arc_try_new(DeliverCode::new(BR_TRANSACTION_COMPLETE))?;
+ let completion = list_completion.clone_arc();
+ let transaction = Transaction::new(node_ref, top, self, tr)?;
+
+ // Check that the transaction stack hasn't changed while the lock was released, then update
+ // it with the new transaction.
+ {
+ let mut inner = self.inner.lock();
+ if !transaction.is_stacked_on(&inner.current_transaction) {
+ pr_warn!("Transaction stack changed during transaction!");
+ return Err(EINVAL.into());
+ }
+ inner.current_transaction = Some(transaction.clone_arc());
+ // We push the completion as a deferred work so that we wait for the reply before
+ // returning to userland.
+ inner.push_work_deferred(list_completion);
+ }
+
+ if let Err(e) = transaction.submit() {
+ completion.skip();
+ // Define `transaction` first to drop it after `inner`.
+ let transaction;
+ let mut inner = self.inner.lock();
+ transaction = inner.current_transaction.take().unwrap();
+ inner.current_transaction = transaction.clone_next();
+ Err(e)
+ } else {
+ Ok(())
+ }
+ }
+
+ fn reply_inner(self: &Arc<Self>, tr: &BinderTransactionDataSg) -> BinderResult {
+ let orig = self.inner.lock().pop_transaction_to_reply(self)?;
+ if !orig.from.is_current_transaction(&orig) {
+ return Err(EINVAL.into());
+ }
+
+ // We need to complete the transaction even if we cannot complete building the reply.
+ let out = (|| -> BinderResult<_> {
+ let completion = DTRWrap::arc_try_new(DeliverCode::new(BR_TRANSACTION_COMPLETE))?;
+ let process = orig.from.process.clone();
+ let allow_fds = orig.flags & TF_ACCEPT_FDS != 0;
+ let reply = Transaction::new_reply(self, process, tr, allow_fds)?;
+ self.inner.lock().push_work(completion);
+ orig.from.deliver_reply(Ok(reply), &orig);
+ Ok(())
+ })()
+ .map_err(|mut err| {
+ // 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!(
+ "Failure {:?} during reply - delivering BR_FAILED_REPLY to sender.",
+ err
+ );
+ let reply = Err(BR_FAILED_REPLY);
+ orig.from.deliver_reply(reply, &orig);
+ err.reply = BR_TRANSACTION_COMPLETE;
+ err
+ });
+
+ out
+ }
+
+ fn oneway_transaction_inner(self: &Arc<Self>, tr: &BinderTransactionDataSg) -> BinderResult {
+ // SAFETY: The `handle` field is valid for all possible byte values, so reading from the
+ // union is okay.
+ let handle = unsafe { tr.transaction_data.target.handle };
+ let node_ref = self.process.get_transaction_node(handle)?;
+ security::binder_transaction(&self.process.cred, &node_ref.node.owner.cred)?;
+ let transaction = Transaction::new(node_ref, None, self, tr)?;
+ let code = if self.process.is_oneway_spam_detection_enabled()
+ && transaction.oneway_spam_detected
+ {
+ BR_ONEWAY_SPAM_SUSPECT
+ } else {
+ BR_TRANSACTION_COMPLETE
+ };
+ let list_completion = DTRWrap::arc_try_new(DeliverCode::new(code))?;
+ let completion = list_completion.clone_arc();
+ self.inner.lock().push_work(list_completion);
+ match transaction.submit() {
+ Ok(()) => Ok(()),
+ Err(err) => {
+ completion.skip();
+ Err(err)
+ }
+ }
+ }
+
+ fn write(self: &Arc<Self>, req: &mut BinderWriteRead) -> Result {
+ let write_start = req.write_buffer.wrapping_add(req.write_consumed);
+ let write_len = req.write_size.saturating_sub(req.write_consumed);
+ let mut reader =
+ UserSlice::new(UserPtr::from_addr(write_start as _), write_len as _).reader();
+
+ while reader.len() >= size_of::<u32>() && self.inner.lock().return_work.is_unused() {
+ let before = reader.len();
+ let cmd = reader.read::<u32>()?;
+ GLOBAL_STATS.inc_bc(cmd);
+ self.process.stats.inc_bc(cmd);
+ match cmd {
+ BC_TRANSACTION => {
+ let tr = reader.read::<BinderTransactionData>()?.with_buffers_size(0);
+ if tr.transaction_data.flags & TF_ONE_WAY != 0 {
+ self.transaction(&tr, Self::oneway_transaction_inner);
+ } else {
+ self.transaction(&tr, Self::transaction_inner);
+ }
+ }
+ BC_TRANSACTION_SG => {
+ let tr = reader.read::<BinderTransactionDataSg>()?;
+ if tr.transaction_data.flags & TF_ONE_WAY != 0 {
+ self.transaction(&tr, Self::oneway_transaction_inner);
+ } else {
+ self.transaction(&tr, Self::transaction_inner);
+ }
+ }
+ BC_REPLY => {
+ let tr = reader.read::<BinderTransactionData>()?.with_buffers_size(0);
+ self.transaction(&tr, Self::reply_inner)
+ }
+ BC_REPLY_SG => {
+ let tr = reader.read::<BinderTransactionDataSg>()?;
+ self.transaction(&tr, Self::reply_inner)
+ }
+ BC_FREE_BUFFER => {
+ let buffer = self.process.buffer_get(reader.read()?);
+ if let Some(buffer) = &buffer {
+ if buffer.looper_need_return_on_free() {
+ self.inner.lock().looper_need_return = true;
+ }
+ }
+ drop(buffer);
+ }
+ BC_INCREFS => {
+ self.process
+ .as_arc_borrow()
+ .update_ref(reader.read()?, true, false)?
+ }
+ BC_ACQUIRE => {
+ self.process
+ .as_arc_borrow()
+ .update_ref(reader.read()?, true, true)?
+ }
+ BC_RELEASE => {
+ self.process
+ .as_arc_borrow()
+ .update_ref(reader.read()?, false, true)?
+ }
+ BC_DECREFS => {
+ self.process
+ .as_arc_borrow()
+ .update_ref(reader.read()?, false, false)?
+ }
+ BC_INCREFS_DONE => self.process.inc_ref_done(&mut reader, false)?,
+ BC_ACQUIRE_DONE => self.process.inc_ref_done(&mut reader, true)?,
+ BC_REQUEST_DEATH_NOTIFICATION => self.process.request_death(&mut reader, self)?,
+ BC_CLEAR_DEATH_NOTIFICATION => self.process.clear_death(&mut reader, self)?,
+ BC_DEAD_BINDER_DONE => self.process.dead_binder_done(reader.read()?, self),
+ BC_REGISTER_LOOPER => {
+ let valid = self.process.register_thread();
+ self.inner.lock().looper_register(valid);
+ }
+ BC_ENTER_LOOPER => self.inner.lock().looper_enter(),
+ BC_EXIT_LOOPER => self.inner.lock().looper_exit(),
+ BC_REQUEST_FREEZE_NOTIFICATION => self.process.request_freeze_notif(&mut reader)?,
+ BC_CLEAR_FREEZE_NOTIFICATION => self.process.clear_freeze_notif(&mut reader)?,
+ BC_FREEZE_NOTIFICATION_DONE => self.process.freeze_notif_done(&mut reader)?,
+
+ // Fail if given an unknown error code.
+ // BC_ATTEMPT_ACQUIRE and BC_ACQUIRE_RESULT are no longer supported.
+ _ => return Err(EINVAL),
+ }
+ // Update the number of write bytes consumed.
+ req.write_consumed += (before - reader.len()) as u64;
+ }
+
+ Ok(())
+ }
+
+ fn read(self: &Arc<Self>, req: &mut BinderWriteRead, wait: bool) -> Result {
+ let read_start = req.read_buffer.wrapping_add(req.read_consumed);
+ let read_len = req.read_size.saturating_sub(req.read_consumed);
+ let mut writer = BinderReturnWriter::new(
+ UserSlice::new(UserPtr::from_addr(read_start as _), read_len as _).writer(),
+ self,
+ );
+ let (in_pool, use_proc_queue) = {
+ let inner = self.inner.lock();
+ (inner.is_looper(), inner.should_use_process_work_queue())
+ };
+
+ let getter = if use_proc_queue {
+ Self::get_work
+ } else {
+ Self::get_work_local
+ };
+
+ // Reserve some room at the beginning of the read buffer so that we can send a
+ // BR_SPAWN_LOOPER if we need to.
+ let mut has_noop_placeholder = false;
+ if req.read_consumed == 0 {
+ if let Err(err) = writer.write_code(BR_NOOP) {
+ pr_warn!("Failure when writing BR_NOOP at beginning of buffer.");
+ return Err(err);
+ }
+ has_noop_placeholder = true;
+ }
+
+ // Loop doing work while there is room in the buffer.
+ let initial_len = writer.len();
+ while writer.len() >= size_of::<uapi::binder_transaction_data_secctx>() + 4 {
+ match getter(self, wait && initial_len == writer.len()) {
+ Ok(Some(work)) => match work.into_arc().do_work(self, &mut writer) {
+ Ok(true) => {}
+ Ok(false) => break,
+ Err(err) => {
+ return Err(err);
+ }
+ },
+ Ok(None) => {
+ break;
+ }
+ Err(err) => {
+ // Propagate the error if we haven't written anything else.
+ if err != EINTR && err != EAGAIN {
+ pr_warn!("Failure in work getter: {:?}", err);
+ }
+ if initial_len == writer.len() {
+ return Err(err);
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ req.read_consumed += read_len - writer.len() as u64;
+
+ // Write BR_SPAWN_LOOPER if the process needs more threads for its pool.
+ if has_noop_placeholder && in_pool && self.process.needs_thread() {
+ let mut writer =
+ UserSlice::new(UserPtr::from_addr(req.read_buffer as _), req.read_size as _)
+ .writer();
+ writer.write(&BR_SPAWN_LOOPER)?;
+ }
+ Ok(())
+ }
+
+ pub(crate) fn write_read(self: &Arc<Self>, data: UserSlice, wait: bool) -> Result {
+ let (mut reader, mut writer) = data.reader_writer();
+ let mut req = reader.read::<BinderWriteRead>()?;
+
+ // Go through the write buffer.
+ let mut ret = Ok(());
+ if req.write_size > 0 {
+ ret = self.write(&mut req);
+ if let Err(err) = ret {
+ pr_warn!(
+ "Write failure {:?} in pid:{}",
+ err,
+ self.process.pid_in_current_ns()
+ );
+ req.read_consumed = 0;
+ writer.write(&req)?;
+ self.inner.lock().looper_need_return = false;
+ return ret;
+ }
+ }
+
+ // Go through the work queue.
+ if req.read_size > 0 {
+ ret = self.read(&mut req, wait);
+ if ret.is_err() && ret != Err(EINTR) {
+ pr_warn!(
+ "Read failure {:?} in pid:{}",
+ ret,
+ self.process.pid_in_current_ns()
+ );
+ }
+ }
+
+ // Write the request back so that the consumed fields are visible to the caller.
+ writer.write(&req)?;
+
+ self.inner.lock().looper_need_return = false;
+
+ ret
+ }
+
+ pub(crate) fn poll(&self, file: &File, table: PollTable<'_>) -> (bool, u32) {
+ table.register_wait(file, &self.work_condvar);
+ let mut inner = self.inner.lock();
+ (inner.should_use_process_work_queue(), inner.poll())
+ }
+
+ /// Make the call to `get_work` or `get_work_local` return immediately, if any.
+ pub(crate) fn exit_looper(&self) {
+ let mut inner = self.inner.lock();
+ let should_notify = inner.looper_flags & LOOPER_WAITING != 0;
+ if should_notify {
+ inner.looper_need_return = true;
+ }
+ drop(inner);
+
+ if should_notify {
+ self.work_condvar.notify_one();
+ }
+ }
+
+ pub(crate) fn notify_if_poll_ready(&self, sync: bool) {
+ // Determine if we need to notify. This requires the lock.
+ let inner = self.inner.lock();
+ let notify = inner.looper_flags & LOOPER_POLL != 0 && inner.should_use_process_work_queue();
+ drop(inner);
+
+ // Now that the lock is no longer held, notify the waiters if we have to.
+ if notify {
+ if sync {
+ self.work_condvar.notify_sync();
+ } else {
+ self.work_condvar.notify_one();
+ }
+ }
+ }
+
+ pub(crate) fn release(self: &Arc<Self>) {
+ self.inner.lock().is_dead = true;
+
+ //self.work_condvar.clear();
+ self.unwind_transaction_stack();
+
+ // Cancel all pending work items.
+ while let Ok(Some(work)) = self.get_work_local(false) {
+ work.into_arc().cancel();
+ }
+ }
+}
+
+#[pin_data]
+struct ThreadError {
+ error_code: AtomicU32,
+ #[pin]
+ links_track: AtomicTracker,
+}
+
+impl ThreadError {
+ fn try_new() -> Result<DArc<Self>> {
+ DTRWrap::arc_pin_init(pin_init!(Self {
+ error_code: AtomicU32::new(BR_OK),
+ links_track <- AtomicTracker::new(),
+ }))
+ .map(ListArc::into_arc)
+ }
+
+ fn set_error_code(&self, code: u32) {
+ self.error_code.store(code, Ordering::Relaxed);
+ }
+
+ fn is_unused(&self) -> bool {
+ self.error_code.load(Ordering::Relaxed) == BR_OK
+ }
+}
+
+impl DeliverToRead for ThreadError {
+ fn do_work(
+ self: DArc<Self>,
+ _thread: &Thread,
+ writer: &mut BinderReturnWriter<'_>,
+ ) -> Result<bool> {
+ let code = self.error_code.load(Ordering::Relaxed);
+ self.error_code.store(BR_OK, Ordering::Relaxed);
+ writer.write_code(code)?;
+ Ok(true)
+ }
+
+ fn cancel(self: DArc<Self>) {}
+
+ fn should_sync_wakeup(&self) -> bool {
+ false
+ }
+
+ fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
+ seq_print!(
+ m,
+ "{}transaction error: {}\n",
+ prefix,
+ self.error_code.load(Ordering::Relaxed)
+ );
+ Ok(())
+ }
+}
+
+kernel::list::impl_list_arc_safe! {
+ impl ListArcSafe<0> for ThreadError {
+ tracked_by links_track: AtomicTracker;
+ }
+}
diff --git a/drivers/android/binder/trace.rs b/drivers/android/binder/trace.rs
new file mode 100644
index 000000000000..af0e4392805e
--- /dev/null
+++ b/drivers/android/binder/trace.rs
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+use kernel::ffi::{c_uint, c_ulong};
+use kernel::tracepoint::declare_trace;
+
+declare_trace! {
+ unsafe fn rust_binder_ioctl(cmd: c_uint, arg: c_ulong);
+}
+
+#[inline]
+pub(crate) fn trace_ioctl(cmd: u32, arg: usize) {
+ // SAFETY: Always safe to call.
+ unsafe { rust_binder_ioctl(cmd, arg as c_ulong) }
+}
diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs
new file mode 100644
index 000000000000..02512175d622
--- /dev/null
+++ b/drivers/android/binder/transaction.rs
@@ -0,0 +1,456 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+use core::sync::atomic::{AtomicBool, Ordering};
+use kernel::{
+ prelude::*,
+ seq_file::SeqFile,
+ seq_print,
+ sync::{Arc, SpinLock},
+ task::Kuid,
+ time::{Instant, Monotonic},
+ types::ScopeGuard,
+};
+
+use crate::{
+ allocation::{Allocation, TranslatedFds},
+ defs::*,
+ error::{BinderError, BinderResult},
+ node::{Node, NodeRef},
+ process::{Process, ProcessInner},
+ ptr_align,
+ thread::{PushWorkRes, Thread},
+ BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead,
+};
+
+#[pin_data(PinnedDrop)]
+pub(crate) struct Transaction {
+ pub(crate) debug_id: usize,
+ target_node: Option<DArc<Node>>,
+ pub(crate) from_parent: Option<DArc<Transaction>>,
+ pub(crate) from: Arc<Thread>,
+ pub(crate) to: Arc<Process>,
+ #[pin]
+ allocation: SpinLock<Option<Allocation>>,
+ is_outstanding: AtomicBool,
+ code: u32,
+ pub(crate) flags: u32,
+ data_size: usize,
+ offsets_size: usize,
+ data_address: usize,
+ sender_euid: Kuid,
+ txn_security_ctx_off: Option<usize>,
+ pub(crate) oneway_spam_detected: bool,
+ start_time: Instant<Monotonic>,
+}
+
+kernel::list::impl_list_arc_safe! {
+ impl ListArcSafe<0> for Transaction { untracked; }
+}
+
+impl Transaction {
+ pub(crate) fn new(
+ node_ref: NodeRef,
+ from_parent: Option<DArc<Transaction>>,
+ from: &Arc<Thread>,
+ tr: &BinderTransactionDataSg,
+ ) -> BinderResult<DLArc<Self>> {
+ let debug_id = super::next_debug_id();
+ let trd = &tr.transaction_data;
+ let allow_fds = node_ref.node.flags & FLAT_BINDER_FLAG_ACCEPTS_FDS != 0;
+ 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(
+ to.clone(),
+ tr,
+ 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);
+ }
+ };
+ let oneway_spam_detected = alloc.oneway_spam_detected;
+ if trd.flags & TF_ONE_WAY != 0 {
+ if from_parent.is_some() {
+ pr_warn!("Oneway transaction should not be in a transaction stack.");
+ return Err(EINVAL.into());
+ }
+ alloc.set_info_oneway_node(node_ref.node.clone());
+ }
+ if trd.flags & TF_CLEAR_BUF != 0 {
+ alloc.set_info_clear_on_drop();
+ }
+ let target_node = node_ref.node.clone();
+ alloc.set_info_target_node(node_ref);
+ let data_address = alloc.ptr;
+
+ Ok(DTRWrap::arc_pin_init(pin_init!(Transaction {
+ debug_id,
+ target_node: Some(target_node),
+ from_parent,
+ sender_euid: from.process.task.euid(),
+ from: from.clone(),
+ to,
+ code: trd.code,
+ flags: trd.flags,
+ data_size: trd.data_size as _,
+ offsets_size: trd.offsets_size as _,
+ data_address,
+ allocation <- kernel::new_spinlock!(Some(alloc.success()), "Transaction::new"),
+ is_outstanding: AtomicBool::new(false),
+ txn_security_ctx_off,
+ oneway_spam_detected,
+ start_time: Instant::now(),
+ }))?)
+ }
+
+ pub(crate) fn new_reply(
+ from: &Arc<Thread>,
+ to: Arc<Process>,
+ tr: &BinderTransactionDataSg,
+ allow_fds: bool,
+ ) -> BinderResult<DLArc<Self>> {
+ let debug_id = super::next_debug_id();
+ let trd = &tr.transaction_data;
+ let mut alloc = match from.copy_transaction_data(to.clone(), tr, debug_id, allow_fds, None)
+ {
+ Ok(alloc) => alloc,
+ Err(err) => {
+ pr_warn!("Failure in copy_transaction_data: {:?}", err);
+ return Err(err);
+ }
+ };
+ let oneway_spam_detected = alloc.oneway_spam_detected;
+ if trd.flags & TF_CLEAR_BUF != 0 {
+ alloc.set_info_clear_on_drop();
+ }
+ Ok(DTRWrap::arc_pin_init(pin_init!(Transaction {
+ debug_id,
+ target_node: None,
+ from_parent: None,
+ sender_euid: from.process.task.euid(),
+ from: from.clone(),
+ to,
+ code: trd.code,
+ flags: trd.flags,
+ data_size: trd.data_size as _,
+ offsets_size: trd.offsets_size as _,
+ data_address: alloc.ptr,
+ allocation <- kernel::new_spinlock!(Some(alloc.success()), "Transaction::new"),
+ is_outstanding: AtomicBool::new(false),
+ txn_security_ctx_off: None,
+ oneway_spam_detected,
+ start_time: Instant::now(),
+ }))?)
+ }
+
+ #[inline(never)]
+ pub(crate) fn debug_print_inner(&self, m: &SeqFile, prefix: &str) {
+ seq_print!(
+ m,
+ "{}{}: from {}:{} to {} code {:x} flags {:x} elapsed {}ms",
+ prefix,
+ self.debug_id,
+ self.from.process.task.pid(),
+ self.from.id,
+ self.to.task.pid(),
+ self.code,
+ self.flags,
+ self.start_time.elapsed().as_millis(),
+ );
+ if let Some(target_node) = &self.target_node {
+ seq_print!(m, " node {}", target_node.debug_id);
+ }
+ seq_print!(m, " size {}:{}\n", self.data_size, self.offsets_size);
+ }
+
+ /// Determines if the transaction is stacked on top of the given transaction.
+ pub(crate) fn is_stacked_on(&self, onext: &Option<DArc<Self>>) -> bool {
+ match (&self.from_parent, onext) {
+ (None, None) => true,
+ (Some(from_parent), Some(next)) => Arc::ptr_eq(from_parent, next),
+ _ => false,
+ }
+ }
+
+ /// Returns a pointer to the next transaction on the transaction stack, if there is one.
+ pub(crate) fn clone_next(&self) -> Option<DArc<Self>> {
+ Some(self.from_parent.as_ref()?.clone())
+ }
+
+ /// Searches in the transaction stack for a thread that belongs to the target process. This is
+ /// useful when finding a target for a new transaction: if the node belongs to a process that
+ /// is already part of the transaction stack, we reuse the thread.
+ fn find_target_thread(&self) -> Option<Arc<Thread>> {
+ let mut it = &self.from_parent;
+ while let Some(transaction) = it {
+ if Arc::ptr_eq(&transaction.from.process, &self.to) {
+ return Some(transaction.from.clone());
+ }
+ it = &transaction.from_parent;
+ }
+ None
+ }
+
+ /// Searches in the transaction stack for a transaction originating at the given thread.
+ pub(crate) fn find_from(&self, thread: &Thread) -> Option<&DArc<Transaction>> {
+ let mut it = &self.from_parent;
+ while let Some(transaction) = it {
+ if core::ptr::eq(thread, transaction.from.as_ref()) {
+ return Some(transaction);
+ }
+
+ it = &transaction.from_parent;
+ }
+ None
+ }
+
+ pub(crate) fn set_outstanding(&self, to_process: &mut ProcessInner) {
+ // No race because this method is only called once.
+ if !self.is_outstanding.load(Ordering::Relaxed) {
+ self.is_outstanding.store(true, Ordering::Relaxed);
+ to_process.add_outstanding_txn();
+ }
+ }
+
+ /// Decrement `outstanding_txns` in `to` if it hasn't already been decremented.
+ fn drop_outstanding_txn(&self) {
+ // No race because this is called at most twice, and one of the calls are in the
+ // destructor, which is guaranteed to not race with any other operations on the
+ // transaction. It also cannot race with `set_outstanding`, since submission happens
+ // before delivery.
+ if self.is_outstanding.load(Ordering::Relaxed) {
+ self.is_outstanding.store(false, Ordering::Relaxed);
+ self.to.drop_outstanding_txn();
+ }
+ }
+
+ /// Submits the transaction to a work queue. Uses a thread if there is one in the transaction
+ /// stack, otherwise uses the destination process.
+ ///
+ /// Not used for replies.
+ pub(crate) fn submit(self: DLArc<Self>) -> BinderResult {
+ // Defined before `process_inner` so that the destructor runs after releasing the lock.
+ let mut _t_outdated;
+
+ let oneway = self.flags & TF_ONE_WAY != 0;
+ let process = self.to.clone();
+ let mut process_inner = process.inner.lock();
+
+ self.set_outstanding(&mut process_inner);
+
+ if oneway {
+ if let Some(target_node) = self.target_node.clone() {
+ if process_inner.is_frozen {
+ process_inner.async_recv = true;
+ if self.flags & TF_UPDATE_TXN != 0 {
+ if let Some(t_outdated) =
+ target_node.take_outdated_transaction(&self, &mut process_inner)
+ {
+ // Save the transaction to be dropped after locks are released.
+ _t_outdated = t_outdated;
+ }
+ }
+ }
+ match target_node.submit_oneway(self, &mut process_inner) {
+ Ok(()) => {}
+ Err((err, work)) => {
+ drop(process_inner);
+ // Drop work after releasing process lock.
+ drop(work);
+ return Err(err);
+ }
+ }
+
+ if process_inner.is_frozen {
+ return Err(BinderError::new_frozen_oneway());
+ } else {
+ return Ok(());
+ }
+ } else {
+ pr_err!("Failed to submit oneway transaction to node.");
+ }
+ }
+
+ if process_inner.is_frozen {
+ process_inner.sync_recv = true;
+ return Err(BinderError::new_frozen());
+ }
+
+ let res = if let Some(thread) = self.find_target_thread() {
+ match thread.push_work(self) {
+ PushWorkRes::Ok => Ok(()),
+ PushWorkRes::FailedDead(me) => Err((BinderError::new_dead(), me)),
+ }
+ } else {
+ process_inner.push_work(self)
+ };
+ drop(process_inner);
+
+ match res {
+ Ok(()) => Ok(()),
+ Err((err, work)) => {
+ // Drop work after releasing process lock.
+ drop(work);
+ Err(err)
+ }
+ }
+ }
+
+ /// Check whether one oneway transaction can supersede another.
+ pub(crate) fn can_replace(&self, old: &Transaction) -> bool {
+ if self.from.process.task.pid() != old.from.process.task.pid() {
+ return false;
+ }
+
+ if self.flags & old.flags & (TF_ONE_WAY | TF_UPDATE_TXN) != (TF_ONE_WAY | TF_UPDATE_TXN) {
+ return false;
+ }
+
+ let target_node_match = match (self.target_node.as_ref(), old.target_node.as_ref()) {
+ (None, None) => true,
+ (Some(tn1), Some(tn2)) => Arc::ptr_eq(tn1, tn2),
+ _ => false,
+ };
+
+ self.code == old.code && self.flags == old.flags && target_node_match
+ }
+
+ fn prepare_file_list(&self) -> Result<TranslatedFds> {
+ let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?;
+
+ match alloc.translate_fds() {
+ Ok(translated) => {
+ *self.allocation.lock() = Some(alloc);
+ Ok(translated)
+ }
+ Err(err) => {
+ // Free the allocation eagerly.
+ drop(alloc);
+ Err(err)
+ }
+ }
+ }
+}
+
+impl DeliverToRead for Transaction {
+ fn do_work(
+ self: DArc<Self>,
+ thread: &Thread,
+ writer: &mut BinderReturnWriter<'_>,
+ ) -> Result<bool> {
+ let send_failed_reply = ScopeGuard::new(|| {
+ if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 {
+ let reply = Err(BR_FAILED_REPLY);
+ self.from.deliver_reply(reply, &self);
+ }
+ self.drop_outstanding_txn();
+ });
+
+ let files = if let Ok(list) = self.prepare_file_list() {
+ list
+ } else {
+ // On failure to process the list, we send a reply back to the sender and ignore the
+ // transaction on the recipient.
+ return Ok(true);
+ };
+
+ let mut tr_sec = BinderTransactionDataSecctx::default();
+ let tr = tr_sec.tr_data();
+ if let Some(target_node) = &self.target_node {
+ let (ptr, cookie) = target_node.get_id();
+ tr.target.ptr = ptr as _;
+ tr.cookie = cookie as _;
+ };
+ tr.code = self.code;
+ tr.flags = self.flags;
+ tr.data_size = self.data_size as _;
+ tr.data.ptr.buffer = self.data_address as _;
+ tr.offsets_size = self.offsets_size as _;
+ if tr.offsets_size > 0 {
+ tr.data.ptr.offsets = (self.data_address + ptr_align(self.data_size).unwrap()) as _;
+ }
+ tr.sender_euid = self.sender_euid.into_uid_in_current_ns();
+ tr.sender_pid = 0;
+ if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 {
+ // Not a reply and not one-way.
+ tr.sender_pid = self.from.process.pid_in_current_ns();
+ }
+ let code = if self.target_node.is_none() {
+ BR_REPLY
+ } else if self.txn_security_ctx_off.is_some() {
+ BR_TRANSACTION_SEC_CTX
+ } else {
+ BR_TRANSACTION
+ };
+
+ // Write the transaction code and data to the user buffer.
+ writer.write_code(code)?;
+ if let Some(off) = self.txn_security_ctx_off {
+ tr_sec.secctx = (self.data_address + off) as u64;
+ writer.write_payload(&tr_sec)?;
+ } else {
+ writer.write_payload(&*tr)?;
+ }
+
+ let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?;
+
+ // Dismiss the completion of transaction with a failure. No failure paths are allowed from
+ // here on out.
+ send_failed_reply.dismiss();
+
+ // Commit files, and set FDs in FDA to be closed on buffer free.
+ let close_on_free = files.commit();
+ alloc.set_info_close_on_free(close_on_free);
+
+ // It is now the user's responsibility to clear the allocation.
+ alloc.keep_alive();
+
+ self.drop_outstanding_txn();
+
+ // When this is not a reply and not a oneway transaction, update `current_transaction`. If
+ // it's a reply, `current_transaction` has already been updated appropriately.
+ if self.target_node.is_some() && tr_sec.transaction_data.flags & TF_ONE_WAY == 0 {
+ thread.set_current_transaction(self);
+ }
+
+ Ok(false)
+ }
+
+ fn cancel(self: DArc<Self>) {
+ let allocation = self.allocation.lock().take();
+ drop(allocation);
+
+ // If this is not a reply or oneway transaction, then send a dead reply.
+ if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 {
+ let reply = Err(BR_DEAD_REPLY);
+ self.from.deliver_reply(reply, &self);
+ }
+
+ self.drop_outstanding_txn();
+ }
+
+ fn should_sync_wakeup(&self) -> bool {
+ self.flags & TF_ONE_WAY == 0
+ }
+
+ fn debug_print(&self, m: &SeqFile, _prefix: &str, tprefix: &str) -> Result<()> {
+ self.debug_print_inner(m, tprefix);
+ Ok(())
+ }
+}
+
+#[pinned_drop]
+impl PinnedDrop for Transaction {
+ fn drop(self: Pin<&mut Self>) {
+ self.drop_outstanding_txn();
+ }
+}
diff --git a/drivers/android/binder_internal.h b/drivers/android/binder_internal.h
index 8b08976146ba..342574bfd28a 100644
--- a/drivers/android/binder_internal.h
+++ b/drivers/android/binder_internal.h
@@ -537,8 +537,8 @@ struct binder_transaction {
struct binder_proc *to_proc;
struct binder_thread *to_thread;
struct binder_transaction *to_parent;
- unsigned need_reply:1;
- /* unsigned is_dead:1; */ /* not used at the moment */
+ unsigned is_async:1;
+ unsigned is_reply:1;
struct binder_buffer *buffer;
unsigned int code;
diff --git a/drivers/android/binder_netlink.c b/drivers/android/binder_netlink.c
new file mode 100644
index 000000000000..d05397a50ca6
--- /dev/null
+++ b/drivers/android/binder_netlink.c
@@ -0,0 +1,31 @@
+// 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 */
+
+#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
new file mode 100644
index 000000000000..882c7a6b537e
--- /dev/null
+++ b/drivers/android/binder_netlink.h
@@ -0,0 +1,20 @@
+/* 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 */
+
+#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
index 97a78e5623db..fa5eb61cf580 100644
--- a/drivers/android/binder_trace.h
+++ b/drivers/android/binder_trace.h
@@ -402,6 +402,43 @@ TRACE_EVENT(binder_return,
"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
diff --git a/drivers/android/binderfs.c b/drivers/android/binderfs.c
index 0d9d95a7fb60..be8e64eb39ec 100644
--- a/drivers/android/binderfs.c
+++ b/drivers/android/binderfs.c
@@ -59,6 +59,7 @@ struct binder_features {
bool oneway_spam_detection;
bool extended_error;
bool freeze_notification;
+ bool transaction_report;
};
static const struct constant_table binderfs_param_stats[] = {
@@ -76,6 +77,7 @@ 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)
@@ -601,6 +603,12 @@ static int init_binder_features(struct super_block *sb)
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;
}
diff --git a/drivers/android/dbitmap.h b/drivers/android/dbitmap.h
index 956f1bd087d1..c7299ce8b374 100644
--- a/drivers/android/dbitmap.h
+++ b/drivers/android/dbitmap.h
@@ -37,6 +37,7 @@ 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. */
diff --git a/drivers/bus/mhi/ep/main.c b/drivers/bus/mhi/ep/main.c
index b3eafcf2a2c5..cdea24e92919 100644
--- a/drivers/bus/mhi/ep/main.c
+++ b/drivers/bus/mhi/ep/main.c
@@ -403,17 +403,13 @@ static int mhi_ep_read_channel(struct mhi_ep_cntrl *mhi_cntrl,
{
struct mhi_ep_chan *mhi_chan = &mhi_cntrl->mhi_chan[ring->ch_id];
struct device *dev = &mhi_cntrl->mhi_dev->dev;
- size_t tr_len, read_offset, write_offset;
+ size_t tr_len, read_offset;
struct mhi_ep_buf_info buf_info = {};
u32 len = MHI_EP_DEFAULT_MTU;
struct mhi_ring_element *el;
- bool tr_done = false;
void *buf_addr;
- u32 buf_left;
int ret;
- buf_left = len;
-
do {
/* Don't process the transfer ring if the channel is not in RUNNING state */
if (mhi_chan->state != MHI_CH_STATE_RUNNING) {
@@ -426,24 +422,23 @@ static int mhi_ep_read_channel(struct mhi_ep_cntrl *mhi_cntrl,
/* Check if there is data pending to be read from previous read operation */
if (mhi_chan->tre_bytes_left) {
dev_dbg(dev, "TRE bytes remaining: %u\n", mhi_chan->tre_bytes_left);
- tr_len = min(buf_left, mhi_chan->tre_bytes_left);
+ tr_len = min(len, mhi_chan->tre_bytes_left);
} else {
mhi_chan->tre_loc = MHI_TRE_DATA_GET_PTR(el);
mhi_chan->tre_size = MHI_TRE_DATA_GET_LEN(el);
mhi_chan->tre_bytes_left = mhi_chan->tre_size;
- tr_len = min(buf_left, mhi_chan->tre_size);
+ tr_len = min(len, mhi_chan->tre_size);
}
read_offset = mhi_chan->tre_size - mhi_chan->tre_bytes_left;
- write_offset = len - buf_left;
buf_addr = kmem_cache_zalloc(mhi_cntrl->tre_buf_cache, GFP_KERNEL);
if (!buf_addr)
return -ENOMEM;
buf_info.host_addr = mhi_chan->tre_loc + read_offset;
- buf_info.dev_addr = buf_addr + write_offset;
+ buf_info.dev_addr = buf_addr;
buf_info.size = tr_len;
buf_info.cb = mhi_ep_read_completion;
buf_info.cb_buf = buf_addr;
@@ -459,16 +454,12 @@ static int mhi_ep_read_channel(struct mhi_ep_cntrl *mhi_cntrl,
goto err_free_buf_addr;
}
- buf_left -= tr_len;
mhi_chan->tre_bytes_left -= tr_len;
- if (!mhi_chan->tre_bytes_left) {
- if (MHI_TRE_DATA_GET_IEOT(el))
- tr_done = true;
-
+ if (!mhi_chan->tre_bytes_left)
mhi_chan->rd_offset = (mhi_chan->rd_offset + 1) % ring->ring_size;
- }
- } while (buf_left && !tr_done);
+ /* Read until the some buffer is left or the ring becomes not empty */
+ } while (!mhi_ep_queue_is_empty(mhi_chan->mhi_dev, DMA_TO_DEVICE));
return 0;
@@ -502,15 +493,11 @@ static int mhi_ep_process_ch_ring(struct mhi_ep_ring *ring)
mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result);
} else {
/* UL channel */
- do {
- ret = mhi_ep_read_channel(mhi_cntrl, ring);
- if (ret < 0) {
- dev_err(&mhi_chan->mhi_dev->dev, "Failed to read channel\n");
- return ret;
- }
-
- /* Read until the ring becomes empty */
- } while (!mhi_ep_queue_is_empty(mhi_chan->mhi_dev, DMA_TO_DEVICE));
+ ret = mhi_ep_read_channel(mhi_cntrl, ring);
+ if (ret < 0) {
+ dev_err(&mhi_chan->mhi_dev->dev, "Failed to read channel\n");
+ return ret;
+ }
}
return 0;
diff --git a/drivers/bus/mhi/host/init.c b/drivers/bus/mhi/host/init.c
index 7f72aab38ce9..099be8dd1900 100644
--- a/drivers/bus/mhi/host/init.c
+++ b/drivers/bus/mhi/host/init.c
@@ -194,7 +194,6 @@ static void mhi_deinit_free_irq(struct mhi_controller *mhi_cntrl)
static int mhi_init_irq_setup(struct mhi_controller *mhi_cntrl)
{
struct mhi_event *mhi_event = mhi_cntrl->mhi_event;
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
unsigned long irq_flags = IRQF_SHARED | IRQF_NO_SUSPEND;
int i, ret;
@@ -221,7 +220,7 @@ static int mhi_init_irq_setup(struct mhi_controller *mhi_cntrl)
continue;
if (mhi_event->irq >= mhi_cntrl->nr_irqs) {
- dev_err(dev, "irq %d not available for event ring\n",
+ dev_err(mhi_cntrl->cntrl_dev, "irq %d not available for event ring\n",
mhi_event->irq);
ret = -EINVAL;
goto error_request;
@@ -232,7 +231,7 @@ static int mhi_init_irq_setup(struct mhi_controller *mhi_cntrl)
irq_flags,
"mhi", mhi_event);
if (ret) {
- dev_err(dev, "Error requesting irq:%d for ev:%d\n",
+ dev_err(mhi_cntrl->cntrl_dev, "Error requesting irq:%d for ev:%d\n",
mhi_cntrl->irq[mhi_event->irq], i);
goto error_request;
}
diff --git a/drivers/bus/mhi/host/internal.h b/drivers/bus/mhi/host/internal.h
index 034be33565b7..7937bb1f742c 100644
--- a/drivers/bus/mhi/host/internal.h
+++ b/drivers/bus/mhi/host/internal.h
@@ -170,6 +170,8 @@ enum mhi_pm_state {
MHI_PM_IN_ERROR_STATE(pm_state))
#define MHI_PM_IN_SUSPEND_STATE(pm_state) (pm_state & \
(MHI_PM_M3_ENTER | MHI_PM_M3))
+#define MHI_PM_FATAL_ERROR(pm_state) ((pm_state == MHI_PM_FW_DL_ERR) || \
+ (pm_state >= MHI_PM_SYS_ERR_FAIL))
#define NR_OF_CMD_RINGS 1
#define CMD_EL_PER_RING 128
@@ -403,6 +405,7 @@ int mhi_process_data_event_ring(struct mhi_controller *mhi_cntrl,
struct mhi_event *mhi_event, u32 event_quota);
int mhi_process_ctrl_ev_ring(struct mhi_controller *mhi_cntrl,
struct mhi_event *mhi_event, u32 event_quota);
+void mhi_uevent_notify(struct mhi_controller *mhi_cntrl, enum mhi_ee_type ee);
/* ISR handlers */
irqreturn_t mhi_irq_handler(int irq_number, void *dev);
diff --git a/drivers/bus/mhi/host/main.c b/drivers/bus/mhi/host/main.c
index 52bef663e182..861551274319 100644
--- a/drivers/bus/mhi/host/main.c
+++ b/drivers/bus/mhi/host/main.c
@@ -512,6 +512,7 @@ irqreturn_t mhi_intvec_threaded_handler(int irq_number, void *priv)
if (mhi_cntrl->rddm_image && mhi_is_active(mhi_cntrl)) {
mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_EE_RDDM);
mhi_cntrl->ee = ee;
+ mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee);
wake_up_all(&mhi_cntrl->state_event);
}
break;
diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c
index 4edb5bb476ba..b188bbf7de04 100644
--- a/drivers/bus/mhi/host/pci_generic.c
+++ b/drivers/bus/mhi/host/pci_generic.c
@@ -34,28 +34,34 @@
/**
* struct mhi_pci_dev_info - MHI PCI device specific information
* @config: MHI controller configuration
+ * @vf_config: MHI controller configuration for Virtual function (optional)
* @name: name of the PCI module
* @fw: firmware path (if any)
* @edl: emergency download mode firmware path (if any)
* @edl_trigger: capable of triggering EDL mode in the device (if supported)
* @bar_num: PCI base address register to use for MHI MMIO register space
* @dma_data_width: DMA transfer word size (32 or 64 bits)
+ * @vf_dma_data_width: DMA transfer word size for VF's (optional)
* @mru_default: default MRU size for MBIM network packets
* @sideband_wake: Devices using dedicated sideband GPIO for wakeup instead
* of inband wake support (such as sdx24)
* @no_m3: M3 not supported
+ * @reset_on_remove: Set true for devices that require SoC during driver removal
*/
struct mhi_pci_dev_info {
const struct mhi_controller_config *config;
+ const struct mhi_controller_config *vf_config;
const char *name;
const char *fw;
const char *edl;
bool edl_trigger;
unsigned int bar_num;
unsigned int dma_data_width;
+ unsigned int vf_dma_data_width;
unsigned int mru_default;
bool sideband_wake;
bool no_m3;
+ bool reset_on_remove;
};
#define MHI_CHANNEL_CONFIG_UL(ch_num, ch_name, el_count, ev_ring) \
@@ -296,8 +302,10 @@ static const struct mhi_pci_dev_info mhi_qcom_qdu100_info = {
.config = &mhi_qcom_qdu100_config,
.bar_num = MHI_PCI_DEFAULT_BAR_NUM,
.dma_data_width = 32,
+ .vf_dma_data_width = 40,
.sideband_wake = false,
.no_m3 = true,
+ .reset_on_remove = true,
};
static const struct mhi_channel_config mhi_qcom_sa8775p_channels[] = {
@@ -917,20 +925,8 @@ static const struct pci_device_id mhi_pci_id_table[] = {
/* Telit FE990A */
{ PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, 0x1c5d, 0x2015),
.driver_data = (kernel_ulong_t) &mhi_telit_fe990a_info },
- /* Foxconn T99W696.01, Lenovo Generic SKU */
- { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, 0xe142),
- .driver_data = (kernel_ulong_t) &mhi_foxconn_t99w696_info },
- /* Foxconn T99W696.02, Lenovo X1 Carbon SKU */
- { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, 0xe143),
- .driver_data = (kernel_ulong_t) &mhi_foxconn_t99w696_info },
- /* Foxconn T99W696.03, Lenovo X1 2in1 SKU */
- { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, 0xe144),
- .driver_data = (kernel_ulong_t) &mhi_foxconn_t99w696_info },
- /* Foxconn T99W696.04, Lenovo PRC SKU */
- { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, 0xe145),
- .driver_data = (kernel_ulong_t) &mhi_foxconn_t99w696_info },
- /* Foxconn T99W696.00, Foxconn SKU */
- { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, 0xe146),
+ /* Foxconn T99W696, all variants */
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, PCI_ANY_ID),
.driver_data = (kernel_ulong_t) &mhi_foxconn_t99w696_info },
{ PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x0308),
.driver_data = (kernel_ulong_t) &mhi_qcom_sdx65_info },
@@ -1037,6 +1033,7 @@ struct mhi_pci_device {
struct work_struct recovery_work;
struct timer_list health_check_timer;
unsigned long status;
+ bool reset_on_remove;
};
static int mhi_pci_read_reg(struct mhi_controller *mhi_cntrl,
@@ -1092,7 +1089,7 @@ static bool mhi_pci_is_alive(struct mhi_controller *mhi_cntrl)
struct pci_dev *pdev = to_pci_dev(mhi_cntrl->cntrl_dev);
u16 vendor = 0;
- if (pci_read_config_word(pdev, PCI_VENDOR_ID, &vendor))
+ if (pci_read_config_word(pci_physfn(pdev), PCI_VENDOR_ID, &vendor))
return false;
if (vendor == (u16) ~0 || vendor == 0)
@@ -1203,7 +1200,9 @@ static void mhi_pci_recovery_work(struct work_struct *work)
dev_warn(&pdev->dev, "device recovery started\n");
- timer_delete(&mhi_pdev->health_check_timer);
+ if (pdev->is_physfn)
+ timer_delete(&mhi_pdev->health_check_timer);
+
pm_runtime_forbid(&pdev->dev);
/* Clean up MHI state */
@@ -1230,7 +1229,10 @@ static void mhi_pci_recovery_work(struct work_struct *work)
dev_dbg(&pdev->dev, "Recovery completed\n");
set_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status);
- mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD);
+
+ if (pdev->is_physfn)
+ mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD);
+
return;
err_unprepare:
@@ -1301,6 +1303,7 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
const struct mhi_controller_config *mhi_cntrl_config;
struct mhi_pci_device *mhi_pdev;
struct mhi_controller *mhi_cntrl;
+ unsigned int dma_data_width;
int err;
dev_info(&pdev->dev, "MHI PCI device found: %s\n", info->name);
@@ -1311,14 +1314,24 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
return -ENOMEM;
INIT_WORK(&mhi_pdev->recovery_work, mhi_pci_recovery_work);
- timer_setup(&mhi_pdev->health_check_timer, health_check, 0);
- mhi_cntrl_config = info->config;
+ if (pdev->is_virtfn && info->vf_config)
+ mhi_cntrl_config = info->vf_config;
+ else
+ mhi_cntrl_config = info->config;
+
+ /* Initialize health check monitor only for Physical functions */
+ if (pdev->is_physfn)
+ timer_setup(&mhi_pdev->health_check_timer, health_check, 0);
+
mhi_cntrl = &mhi_pdev->mhi_cntrl;
+ dma_data_width = (pdev->is_virtfn && info->vf_dma_data_width) ?
+ info->vf_dma_data_width : info->dma_data_width;
+
mhi_cntrl->cntrl_dev = &pdev->dev;
mhi_cntrl->iova_start = 0;
- mhi_cntrl->iova_stop = (dma_addr_t)DMA_BIT_MASK(info->dma_data_width);
+ mhi_cntrl->iova_stop = (dma_addr_t)DMA_BIT_MASK(dma_data_width);
mhi_cntrl->fw_image = info->fw;
mhi_cntrl->edl_image = info->edl;
@@ -1330,6 +1343,9 @@ 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;
+ if (pdev->is_physfn)
+ mhi_pdev->reset_on_remove = info->reset_on_remove;
+
if (info->edl_trigger)
mhi_cntrl->edl_trigger = mhi_pci_generic_edl_trigger;
@@ -1339,7 +1355,7 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
mhi_cntrl->wake_toggle = mhi_pci_wake_toggle_nop;
}
- err = mhi_pci_claim(mhi_cntrl, info->bar_num, DMA_BIT_MASK(info->dma_data_width));
+ err = mhi_pci_claim(mhi_cntrl, info->bar_num, DMA_BIT_MASK(dma_data_width));
if (err)
return err;
@@ -1376,7 +1392,8 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
set_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status);
/* start health check */
- mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD);
+ if (pdev->is_physfn)
+ mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD);
/* Allow runtime suspend only if both PME from D3Hot and M3 are supported */
if (pci_pme_capable(pdev, PCI_D3hot) && !(info->no_m3)) {
@@ -1401,7 +1418,10 @@ static void mhi_pci_remove(struct pci_dev *pdev)
struct mhi_pci_device *mhi_pdev = pci_get_drvdata(pdev);
struct mhi_controller *mhi_cntrl = &mhi_pdev->mhi_cntrl;
- timer_delete_sync(&mhi_pdev->health_check_timer);
+ pci_disable_sriov(pdev);
+
+ if (pdev->is_physfn)
+ timer_delete_sync(&mhi_pdev->health_check_timer);
cancel_work_sync(&mhi_pdev->recovery_work);
if (test_and_clear_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status)) {
@@ -1413,6 +1433,9 @@ static void mhi_pci_remove(struct pci_dev *pdev)
if (pci_pme_capable(pdev, PCI_D3hot))
pm_runtime_get_noresume(&pdev->dev);
+ if (mhi_pdev->reset_on_remove)
+ mhi_soc_reset(mhi_cntrl);
+
mhi_unregister_controller(mhi_cntrl);
}
@@ -1429,7 +1452,8 @@ static void mhi_pci_reset_prepare(struct pci_dev *pdev)
dev_info(&pdev->dev, "reset\n");
- timer_delete(&mhi_pdev->health_check_timer);
+ if (pdev->is_physfn)
+ timer_delete(&mhi_pdev->health_check_timer);
/* Clean up MHI state */
if (test_and_clear_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status)) {
@@ -1474,7 +1498,8 @@ static void mhi_pci_reset_done(struct pci_dev *pdev)
}
set_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status);
- mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD);
+ if (pdev->is_physfn)
+ mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD);
}
static pci_ers_result_t mhi_pci_error_detected(struct pci_dev *pdev,
@@ -1539,7 +1564,9 @@ static int __maybe_unused mhi_pci_runtime_suspend(struct device *dev)
if (test_and_set_bit(MHI_PCI_DEV_SUSPENDED, &mhi_pdev->status))
return 0;
- timer_delete(&mhi_pdev->health_check_timer);
+ if (pdev->is_physfn)
+ timer_delete(&mhi_pdev->health_check_timer);
+
cancel_work_sync(&mhi_pdev->recovery_work);
if (!test_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status) ||
@@ -1590,7 +1617,8 @@ static int __maybe_unused mhi_pci_runtime_resume(struct device *dev)
}
/* Resume health check */
- mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD);
+ if (pdev->is_physfn)
+ mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD);
/* It can be a remote wakeup (no mhi runtime_get), update access time */
pm_runtime_mark_last_busy(dev);
@@ -1676,7 +1704,8 @@ static struct pci_driver mhi_pci_driver = {
.remove = mhi_pci_remove,
.shutdown = mhi_pci_shutdown,
.err_handler = &mhi_pci_err_handler,
- .driver.pm = &mhi_pci_pm_ops
+ .driver.pm = &mhi_pci_pm_ops,
+ .sriov_configure = pci_sriov_configure_simple,
};
module_pci_driver(mhi_pci_driver);
diff --git a/drivers/bus/mhi/host/pm.c b/drivers/bus/mhi/host/pm.c
index 33d92bf2fc3e..b4ef115189b5 100644
--- a/drivers/bus/mhi/host/pm.c
+++ b/drivers/bus/mhi/host/pm.c
@@ -418,6 +418,7 @@ static int mhi_pm_mission_mode_transition(struct mhi_controller *mhi_cntrl)
device_for_each_child(&mhi_cntrl->mhi_dev->dev, &current_ee,
mhi_destroy_device);
mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_EE_MISSION_MODE);
+ mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee);
/* Force MHI to be in M0 state before continuing */
ret = __mhi_device_get_sync(mhi_cntrl);
@@ -631,6 +632,8 @@ static void mhi_pm_sys_error_transition(struct mhi_controller *mhi_cntrl)
/* Wake up threads waiting for state transition */
wake_up_all(&mhi_cntrl->state_event);
+ mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee);
+
if (MHI_REG_ACCESS_VALID(prev_state)) {
/*
* If the device is in PBL or SBL, it will only respond to
@@ -829,6 +832,8 @@ void mhi_pm_st_worker(struct work_struct *work)
mhi_create_devices(mhi_cntrl);
if (mhi_cntrl->fbc_download)
mhi_download_amss_image(mhi_cntrl);
+
+ mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee);
break;
case DEV_ST_TRANSITION_MISSION_MODE:
mhi_pm_mission_mode_transition(mhi_cntrl);
@@ -838,6 +843,7 @@ void mhi_pm_st_worker(struct work_struct *work)
mhi_cntrl->ee = MHI_EE_FP;
write_unlock_irq(&mhi_cntrl->pm_lock);
mhi_create_devices(mhi_cntrl);
+ mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee);
break;
case DEV_ST_TRANSITION_READY:
mhi_ready_state_transition(mhi_cntrl);
@@ -1240,6 +1246,8 @@ static void __mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful,
write_unlock_irq(&mhi_cntrl->pm_lock);
mutex_unlock(&mhi_cntrl->pm_mutex);
+ mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee);
+
if (destroy_device)
mhi_queue_state_transition(mhi_cntrl,
DEV_ST_TRANSITION_DISABLE_DESTROY_DEVICE);
@@ -1279,7 +1287,7 @@ int mhi_sync_power_up(struct mhi_controller *mhi_cntrl)
mhi_cntrl->ready_timeout_ms : mhi_cntrl->timeout_ms;
wait_event_timeout(mhi_cntrl->state_event,
MHI_IN_MISSION_MODE(mhi_cntrl->ee) ||
- MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state),
+ MHI_PM_FATAL_ERROR(mhi_cntrl->pm_state),
msecs_to_jiffies(timeout_ms));
ret = (MHI_IN_MISSION_MODE(mhi_cntrl->ee)) ? 0 : -ETIMEDOUT;
@@ -1338,3 +1346,22 @@ void mhi_device_put(struct mhi_device *mhi_dev)
read_unlock_bh(&mhi_cntrl->pm_lock);
}
EXPORT_SYMBOL_GPL(mhi_device_put);
+
+void mhi_uevent_notify(struct mhi_controller *mhi_cntrl, enum mhi_ee_type ee)
+{
+ struct device *dev = &mhi_cntrl->mhi_dev->dev;
+ char *buf[2];
+ int ret;
+
+ buf[0] = kasprintf(GFP_KERNEL, "EXEC_ENV=%s", TO_MHI_EXEC_STR(ee));
+ buf[1] = NULL;
+
+ if (!buf[0])
+ return;
+
+ ret = kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, buf);
+ if (ret)
+ dev_err(dev, "Failed to send %s uevent\n", TO_MHI_EXEC_STR(ee));
+
+ kfree(buf[0]);
+}
diff --git a/drivers/cdx/cdx_msi.c b/drivers/cdx/cdx_msi.c
index 3388a5d1462c..91b95422b263 100644
--- a/drivers/cdx/cdx_msi.c
+++ b/drivers/cdx/cdx_msi.c
@@ -174,6 +174,7 @@ struct irq_domain *cdx_msi_domain_init(struct device *dev)
}
parent = irq_find_matching_fwnode(of_fwnode_handle(parent_node), DOMAIN_BUS_NEXUS);
+ of_node_put(parent_node);
if (!parent || !msi_get_domain_info(parent)) {
dev_err(dev, "unable to locate ITS domain\n");
return NULL;
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index e9b360cdc99a..1291369b9126 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -6,6 +6,7 @@
obj-y += mem.o random.o
obj-$(CONFIG_TTY_PRINTK) += ttyprintk.o
obj-y += misc.o
+obj-$(CONFIG_TEST_MISC_MINOR) += misc_minor_kunit.o
obj-$(CONFIG_ATARI_DSP56K) += dsp56k.o
obj-$(CONFIG_VIRTIO_CONSOLE) += virtio_console.o
obj-$(CONFIG_UV_MMTIMER) += uv_mmtimer.o
diff --git a/drivers/char/adi.c b/drivers/char/adi.c
index f9bec10a6064..4312b0cc391c 100644
--- a/drivers/char/adi.c
+++ b/drivers/char/adi.c
@@ -131,7 +131,7 @@ static ssize_t adi_write(struct file *file, const char __user *buf,
ssize_t ret;
int i;
- if (count <= 0)
+ if (count == 0)
return -EINVAL;
ver_buf_sz = min_t(size_t, count, MAX_BUF_SZ);
diff --git a/drivers/char/hpet.c b/drivers/char/hpet.c
index 0713ea2b2a51..4f5ccd3a1f56 100644
--- a/drivers/char/hpet.c
+++ b/drivers/char/hpet.c
@@ -867,7 +867,7 @@ int hpet_alloc(struct hpet_data *hdp)
printk(KERN_INFO "hpet%u: at MMIO 0x%lx, IRQ%s",
hpetp->hp_which, hdp->hd_phys_address,
- hpetp->hp_ntimer > 1 ? "s" : "");
+ str_plural(hpetp->hp_ntimer));
for (i = 0; i < hpetp->hp_ntimer; i++)
printk(KERN_CONT "%s %u", i > 0 ? "," : "", hdp->hd_irq[i]);
printk(KERN_CONT "\n");
diff --git a/drivers/char/misc.c b/drivers/char/misc.c
index 558302a64dd9..726516fb0a3b 100644
--- a/drivers/char/misc.c
+++ b/drivers/char/misc.c
@@ -132,7 +132,8 @@ static int misc_open(struct inode *inode, struct file *file)
break;
}
- if (!new_fops) {
+ /* Only request module for fixed minor code */
+ if (!new_fops && minor < MISC_DYNAMIC_MINOR) {
mutex_unlock(&misc_mtx);
request_module("char-major-%d-%d", MISC_MAJOR, minor);
mutex_lock(&misc_mtx);
@@ -144,10 +145,11 @@ static int misc_open(struct inode *inode, struct file *file)
new_fops = fops_get(iter->fops);
break;
}
- if (!new_fops)
- goto fail;
}
+ if (!new_fops)
+ goto fail;
+
/*
* Place the miscdevice in the file's
* private_data so it can be used by the
@@ -210,6 +212,12 @@ int misc_register(struct miscdevice *misc)
int err = 0;
bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);
+ if (misc->minor > MISC_DYNAMIC_MINOR) {
+ pr_err("Invalid fixed minor %d for miscdevice '%s'\n",
+ misc->minor, misc->name);
+ return -EINVAL;
+ }
+
INIT_LIST_HEAD(&misc->list);
mutex_lock(&misc_mtx);
@@ -275,13 +283,12 @@ EXPORT_SYMBOL(misc_register);
void misc_deregister(struct miscdevice *misc)
{
- if (WARN_ON(list_empty(&misc->list)))
- return;
-
mutex_lock(&misc_mtx);
- list_del(&misc->list);
+ list_del_init(&misc->list);
device_destroy(&misc_class, MKDEV(MISC_MAJOR, misc->minor));
misc_minor_free(misc->minor);
+ if (misc->minor > MISC_DYNAMIC_MINOR)
+ misc->minor = MISC_DYNAMIC_MINOR;
mutex_unlock(&misc_mtx);
}
EXPORT_SYMBOL(misc_deregister);
diff --git a/drivers/misc/misc_minor_kunit.c b/drivers/char/misc_minor_kunit.c
index 30eceac5f1b6..6fc8b05169c5 100644
--- a/drivers/misc/misc_minor_kunit.c
+++ b/drivers/char/misc_minor_kunit.c
@@ -7,12 +7,6 @@
#include <linux/file.h>
#include <linux/init_syscalls.h>
-/* dynamic minor (2) */
-static struct miscdevice dev_dynamic_minor = {
- .minor = 2,
- .name = "dev_dynamic_minor",
-};
-
/* static minor (LCD_MINOR) */
static struct miscdevice dev_static_minor = {
.minor = LCD_MINOR,
@@ -25,16 +19,6 @@ static struct miscdevice dev_misc_dynamic_minor = {
.name = "dev_misc_dynamic_minor",
};
-static void kunit_dynamic_minor(struct kunit *test)
-{
- int ret;
-
- ret = misc_register(&dev_dynamic_minor);
- KUNIT_EXPECT_EQ(test, 0, ret);
- KUNIT_EXPECT_EQ(test, 2, dev_dynamic_minor.minor);
- misc_deregister(&dev_dynamic_minor);
-}
-
static void kunit_static_minor(struct kunit *test)
{
int ret;
@@ -157,13 +141,7 @@ static bool is_valid_dynamic_minor(int minor)
{
if (minor < 0)
return false;
- if (minor == MISC_DYNAMIC_MINOR)
- return false;
- if (minor >= 0 && minor <= 15)
- return false;
- if (minor >= 128 && minor < MISC_DYNAMIC_MINOR)
- return false;
- return true;
+ return minor > MISC_DYNAMIC_MINOR;
}
static int miscdev_test_open(struct inode *inode, struct file *file)
@@ -557,7 +535,7 @@ static void __init miscdev_test_conflict(struct kunit *test)
*/
miscstat.minor = miscdyn.minor;
ret = misc_register(&miscstat);
- KUNIT_EXPECT_EQ(test, ret, -EBUSY);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
if (ret == 0)
misc_deregister(&miscstat);
@@ -590,8 +568,9 @@ static void __init miscdev_test_conflict_reverse(struct kunit *test)
misc_deregister(&miscdyn);
ret = misc_register(&miscstat);
- KUNIT_EXPECT_EQ(test, ret, 0);
- KUNIT_EXPECT_EQ(test, miscstat.minor, miscdyn.minor);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+ if (ret == 0)
+ misc_deregister(&miscstat);
/*
* Try to register a dynamic minor after registering a static minor
@@ -601,25 +580,81 @@ static void __init miscdev_test_conflict_reverse(struct kunit *test)
miscdyn.minor = MISC_DYNAMIC_MINOR;
ret = misc_register(&miscdyn);
KUNIT_EXPECT_EQ(test, ret, 0);
- KUNIT_EXPECT_NE(test, miscdyn.minor, miscstat.minor);
+ KUNIT_EXPECT_EQ(test, miscdyn.minor, miscstat.minor);
KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdyn.minor));
if (ret == 0)
misc_deregister(&miscdyn);
+}
- miscdev_test_can_open(test, &miscstat);
+/* Take minor(> MISC_DYNAMIC_MINOR) as invalid when register miscdevice */
+static void miscdev_test_invalid_input(struct kunit *test)
+{
+ struct miscdevice misc_test = {
+ .minor = MISC_DYNAMIC_MINOR + 1,
+ .name = "misc_test",
+ .fops = &miscdev_test_fops,
+ };
+ int ret;
- misc_deregister(&miscstat);
+ ret = misc_register(&misc_test);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+ if (ret == 0)
+ misc_deregister(&misc_test);
+}
+
+/*
+ * Verify if @miscdyn_a can still be registered successfully without
+ * reinitialization even if its minor ever owned was requested by
+ * another miscdevice such as @miscdyn_b.
+ */
+static void miscdev_test_dynamic_reentry(struct kunit *test)
+{
+ struct miscdevice miscdyn_a = {
+ .name = "miscdyn_a",
+ .minor = MISC_DYNAMIC_MINOR,
+ .fops = &miscdev_test_fops,
+ };
+ struct miscdevice miscdyn_b = {
+ .name = "miscdyn_b",
+ .minor = MISC_DYNAMIC_MINOR,
+ .fops = &miscdev_test_fops,
+ };
+ int ret, minor_a;
+
+ ret = misc_register(&miscdyn_a);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdyn_a.minor));
+ minor_a = miscdyn_a.minor;
+ if (ret != 0)
+ return;
+ misc_deregister(&miscdyn_a);
+
+ ret = misc_register(&miscdyn_b);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, miscdyn_b.minor, minor_a);
+ if (ret != 0)
+ return;
+
+ ret = misc_register(&miscdyn_a);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdyn_a.minor));
+ KUNIT_EXPECT_NE(test, miscdyn_a.minor, miscdyn_b.minor);
+ if (ret == 0)
+ misc_deregister(&miscdyn_a);
+
+ misc_deregister(&miscdyn_b);
}
static struct kunit_case test_cases[] = {
- KUNIT_CASE(kunit_dynamic_minor),
KUNIT_CASE(kunit_static_minor),
KUNIT_CASE(kunit_misc_dynamic_minor),
+ KUNIT_CASE(miscdev_test_invalid_input),
KUNIT_CASE_PARAM(miscdev_test_twice, miscdev_gen_params),
KUNIT_CASE_PARAM(miscdev_test_duplicate_minor, miscdev_gen_params),
KUNIT_CASE(miscdev_test_duplicate_name),
KUNIT_CASE(miscdev_test_duplicate_name_leak),
KUNIT_CASE_PARAM(miscdev_test_duplicate_error, miscdev_gen_params),
+ KUNIT_CASE(miscdev_test_dynamic_reentry),
{}
};
diff --git a/drivers/comedi/Kconfig b/drivers/comedi/Kconfig
index 93c68a40a17b..6dcc2567de6d 100644
--- a/drivers/comedi/Kconfig
+++ b/drivers/comedi/Kconfig
@@ -705,6 +705,15 @@ config COMEDI_ADL_PCI6208
To compile this driver as a module, choose M here: the module will be
called adl_pci6208.
+config COMEDI_ADL_PCI7250
+ tristate "ADLink PCI-7250 support"
+ help
+ Enable support for ADLink PCI-7250/LPCI-7250/LPCIe-7250 relay output
+ and isolated digital input boards.
+
+ To compile this driver as a module, choose M here: the module will be
+ called adl_pci7250.
+
config COMEDI_ADL_PCI7X3X
tristate "ADLink PCI-723X/743X isolated digital i/o board support"
depends on HAS_IOPORT
diff --git a/drivers/comedi/drivers/Makefile b/drivers/comedi/drivers/Makefile
index b24ac00cab73..7b99a431330d 100644
--- a/drivers/comedi/drivers/Makefile
+++ b/drivers/comedi/drivers/Makefile
@@ -73,6 +73,7 @@ obj-$(CONFIG_COMEDI_ADDI_APCI_3120) += addi_apci_3120.o
obj-$(CONFIG_COMEDI_ADDI_APCI_3501) += addi_apci_3501.o
obj-$(CONFIG_COMEDI_ADDI_APCI_3XXX) += addi_apci_3xxx.o
obj-$(CONFIG_COMEDI_ADL_PCI6208) += adl_pci6208.o
+obj-$(CONFIG_COMEDI_ADL_PCI7250) += adl_pci7250.o
obj-$(CONFIG_COMEDI_ADL_PCI7X3X) += adl_pci7x3x.o
obj-$(CONFIG_COMEDI_ADL_PCI8164) += adl_pci8164.o
obj-$(CONFIG_COMEDI_ADL_PCI9111) += adl_pci9111.o
diff --git a/drivers/comedi/drivers/adl_pci7250.c b/drivers/comedi/drivers/adl_pci7250.c
new file mode 100644
index 000000000000..78c85a402435
--- /dev/null
+++ b/drivers/comedi/drivers/adl_pci7250.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * adl_pci7250.c
+ *
+ * Comedi driver for ADLink PCI-7250 series cards.
+ *
+ * Copyright (C) 2015, 2025 Ian Abbott <abbotti@mev.co.uk>
+ */
+
+/*
+ * Driver: adl_pci7250
+ * Description: Driver for the ADLINK PCI-7250 relay output & digital input card
+ * Devices: [ADLINK] PCI-7250 (adl_pci7250) LPCI-7250 LPCIe-7250
+ * Author: Ian Abbott <abbotti@mev.co.uk>
+ * Status: works
+ * Updated: Mon, 02 Jun 2025 13:54:11 +0100
+ *
+ * The driver assumes that 3 PCI-7251 modules are fitted to the PCI-7250,
+ * giving 32 channels of relay outputs and 32 channels of isolated digital
+ * inputs. That is also the case for the LPCI-7250 and older LPCIe-7250
+ * cards although they do not physically support the PCI-7251 modules.
+ * Newer LPCIe-7250 cards have a different PCI subsystem device ID, so
+ * set the number of channels to 8 for these cards.
+ *
+ * Not fitting the PCI-7251 modules shouldn't do any harm, but the extra
+ * inputs and relay outputs won't work!
+ *
+ * Configuration Options: not applicable, uses PCI auto config
+ */
+
+#include <linux/module.h>
+#include <linux/comedi/comedi_pci.h>
+
+static unsigned char adl_pci7250_read8(struct comedi_device *dev,
+ unsigned int offset)
+{
+#ifdef CONFIG_HAS_IOPORT
+ if (!dev->mmio)
+ return inb(dev->iobase + offset);
+#endif
+ return readb(dev->mmio + offset);
+}
+
+static void adl_pci7250_write8(struct comedi_device *dev, unsigned int offset,
+ unsigned char val)
+{
+#ifdef CONFIG_HAS_IOPORT
+ if (!dev->mmio) {
+ outb(val, dev->iobase + offset);
+ return;
+ }
+#endif
+ writeb(val, dev->mmio + offset);
+}
+
+static int adl_pci7250_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int mask = comedi_dio_update_state(s, data);
+
+ if (mask) {
+ unsigned int state = s->state;
+ unsigned int i;
+
+ for (i = 0; i * 8 < s->n_chan; i++) {
+ if ((mask & 0xffu) != 0) {
+ /* write relay data to even offset registers */
+ adl_pci7250_write8(dev, i * 2, state & 0xffu);
+ }
+ state >>= 8;
+ mask >>= 8;
+ }
+ }
+
+ data[1] = s->state;
+
+ return 2;
+}
+
+static int adl_pci7250_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int value = 0;
+ unsigned int i;
+
+ for (i = 0; i * 8 < s->n_chan; i++) {
+ /* read DI value from odd offset registers */
+ value |= (unsigned int)adl_pci7250_read8(dev, i * 2 + 1) <<
+ (i * 8);
+ }
+
+ data[1] = value;
+
+ return 2;
+}
+
+static int pci7250_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ unsigned int max_chans;
+ unsigned int i;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ if (pci_resource_len(pcidev, 2) < 8)
+ return -ENXIO;
+
+ /*
+ * Newer LPCIe-7250 boards use MMIO. Older LPCIe-7250, LPCI-7250, and
+ * PCI-7250 boards use Port I/O.
+ */
+ if (pci_resource_flags(pcidev, 2) & IORESOURCE_MEM) {
+ dev->mmio = pci_ioremap_bar(pcidev, 2);
+ if (!dev->mmio)
+ return -ENOMEM;
+ } else if (IS_ENABLED(CONFIG_HAS_IOPORT)) {
+ dev->iobase = pci_resource_start(pcidev, 2);
+ } else {
+ dev_err(dev->class_dev,
+ "error! need I/O port support\n");
+ return -ENXIO;
+ }
+
+ if (pcidev->subsystem_device == 0x7000) {
+ /*
+ * This is a newer LPCIe-7250 variant and cannot possibly
+ * have PCI-7251 modules fitted, so limit the number of
+ * channels to 8.
+ */
+ max_chans = 8;
+ } else {
+ /*
+ * It is unknown whether the board is a PCI-7250, an LPCI-7250,
+ * or an older LPCIe-7250 variant, so treat it as a PCI-7250
+ * and assume it can have PCI-7251 modules fitted to increase
+ * the number of channels to a maximum of 32.
+ */
+ max_chans = 32;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ /* Relay digital output. */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = max_chans;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = adl_pci7250_do_insn_bits;
+ /* Read initial state of relays from the even offset registers. */
+ s->state = 0;
+ for (i = 0; i * 8 < max_chans; i++) {
+ s->state |= (unsigned int)adl_pci7250_read8(dev, i * 2) <<
+ (i * 8);
+ }
+
+ /* Isolated digital input. */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = max_chans;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = adl_pci7250_di_insn_bits;
+
+ return 0;
+}
+
+static struct comedi_driver adl_pci7250_driver = {
+ .driver_name = "adl_pci7250",
+ .module = THIS_MODULE,
+ .auto_attach = pci7250_auto_attach,
+ .detach = comedi_pci_detach,
+};
+
+static int adl_pci7250_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adl_pci7250_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id adl_pci7250_pci_table[] = {
+#ifdef CONFIG_HAS_IOPORT
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050,
+ 0x9999, 0x7250) },
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_ADLINK, 0x7250,
+ 0x9999, 0x7250) },
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_ADLINK, 0x7250,
+ PCI_VENDOR_ID_ADLINK, 0x7250) },
+#endif
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_ADLINK, 0x7250,
+ PCI_VENDOR_ID_ADLINK, 0x7000) }, /* newer LPCIe-7250 */
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adl_pci7250_pci_table);
+
+static struct pci_driver adl_pci7250_pci_driver = {
+ .name = "adl_pci7250",
+ .id_table = adl_pci7250_pci_table,
+ .probe = adl_pci7250_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adl_pci7250_driver, adl_pci7250_pci_driver);
+
+MODULE_AUTHOR("Comedi https://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for ADLink PCI-7250 series boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/counter/ti-ecap-capture.c b/drivers/counter/ti-ecap-capture.c
index 3faaf7f60539..3586a7ab9887 100644
--- a/drivers/counter/ti-ecap-capture.c
+++ b/drivers/counter/ti-ecap-capture.c
@@ -465,11 +465,6 @@ static irqreturn_t ecap_cnt_isr(int irq, void *dev_id)
return IRQ_HANDLED;
}
-static void ecap_cnt_pm_disable(void *dev)
-{
- pm_runtime_disable(dev);
-}
-
static int ecap_cnt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -523,12 +518,9 @@ static int ecap_cnt_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, counter_dev);
- pm_runtime_enable(dev);
-
- /* Register a cleanup callback to care for disabling PM */
- ret = devm_add_action_or_reset(dev, ecap_cnt_pm_disable, dev);
+ ret = devm_pm_runtime_enable(dev);
if (ret)
- return dev_err_probe(dev, ret, "failed to add pm disable action\n");
+ return ret;
ret = devm_counter_add(dev, counter_dev);
if (ret)
diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
index a6f6d467aacf..aec46bf03302 100644
--- a/drivers/extcon/Kconfig
+++ b/drivers/extcon/Kconfig
@@ -134,6 +134,19 @@ config EXTCON_MAX8997
Maxim MAX8997 PMIC. The MAX8997 MUIC is a USB port accessory
detector and switch.
+config EXTCON_MAX14526
+ tristate "Maxim MAX14526 EXTCON Support"
+ depends on I2C
+ select IRQ_DOMAIN
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for the Maxim MAX14526
+ MUIC device. The MAX14526 MUIC is a USB port accessory
+ detector and switch. The MAX14526 is designed to simplify
+ interface requirements on portable devices by multiplexing
+ common inputs (USB, UART, Microphone, Stereo Audio and
+ Composite Video) on a single micro/mini USB connector.
+
config EXTCON_PALMAS
tristate "Palmas USB EXTCON support"
depends on MFD_PALMAS
diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
index 0d6d23faf748..6482f2bfd661 100644
--- a/drivers/extcon/Makefile
+++ b/drivers/extcon/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_EXTCON_MAX3355) += extcon-max3355.o
obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o
obj-$(CONFIG_EXTCON_MAX77843) += extcon-max77843.o
obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o
+obj-$(CONFIG_EXTCON_MAX14526) += extcon-max14526.o
obj-$(CONFIG_EXTCON_PALMAS) += extcon-palmas.o
obj-$(CONFIG_EXTCON_PTN5150) += extcon-ptn5150.o
obj-$(CONFIG_EXTCON_QCOM_SPMI_MISC) += extcon-qcom-spmi-misc.o
diff --git a/drivers/extcon/extcon-adc-jack.c b/drivers/extcon/extcon-adc-jack.c
index 46c40d85c2ac..7e3c9f38297b 100644
--- a/drivers/extcon/extcon-adc-jack.c
+++ b/drivers/extcon/extcon-adc-jack.c
@@ -164,6 +164,8 @@ static void adc_jack_remove(struct platform_device *pdev)
{
struct adc_jack_data *data = platform_get_drvdata(pdev);
+ if (data->wakeup_source)
+ device_init_wakeup(&pdev->dev, false);
free_irq(data->irq, data);
cancel_work_sync(&data->handler.work);
}
diff --git a/drivers/extcon/extcon-axp288.c b/drivers/extcon/extcon-axp288.c
index d3bcbe839c09..19856dddade6 100644
--- a/drivers/extcon/extcon-axp288.c
+++ b/drivers/extcon/extcon-axp288.c
@@ -470,7 +470,7 @@ static int axp288_extcon_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
- device_init_wakeup(dev, true);
+ devm_device_init_wakeup(dev);
platform_set_drvdata(pdev, info);
return 0;
diff --git a/drivers/extcon/extcon-fsa9480.c b/drivers/extcon/extcon-fsa9480.c
index b11b43171063..a031eb0914a0 100644
--- a/drivers/extcon/extcon-fsa9480.c
+++ b/drivers/extcon/extcon-fsa9480.c
@@ -317,7 +317,7 @@ static int fsa9480_probe(struct i2c_client *client)
return ret;
}
- device_init_wakeup(info->dev, true);
+ devm_device_init_wakeup(info->dev);
fsa9480_detect_dev(info);
return 0;
diff --git a/drivers/extcon/extcon-max14526.c b/drivers/extcon/extcon-max14526.c
new file mode 100644
index 000000000000..3750a5c20612
--- /dev/null
+++ b/drivers/extcon/extcon-max14526.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/device.h>
+#include <linux/devm-helpers.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/extcon-provider.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+
+/* I2C addresses of MUIC internal registers */
+#define MAX14526_DEVICE_ID 0x00
+#define MAX14526_ID 0x02
+
+/* CONTROL_1 register masks */
+#define MAX14526_CONTROL_1 0x01
+#define ID_2P2 BIT(6)
+#define ID_620 BIT(5)
+#define ID_200 BIT(4)
+#define VLDO BIT(3)
+#define SEMREN BIT(2)
+#define ADC_EN BIT(1)
+#define CP_EN BIT(0)
+
+/* CONTROL_2 register masks */
+#define MAX14526_CONTROL_2 0x02
+#define INTPOL BIT(7)
+#define INT_EN BIT(6)
+#define MIC_LP BIT(5)
+#define CP_AUD BIT(4)
+#define CHG_TYPE BIT(1)
+#define USB_DET_DIS BIT(0)
+
+/* SW_CONTROL register masks */
+#define MAX14526_SW_CONTROL 0x03
+#define SW_DATA 0x00
+#define SW_UART 0x01
+#define SW_AUDIO 0x02
+#define SW_OPEN 0x07
+
+/* INT_STATUS register masks */
+#define MAX14526_INT_STAT 0x04
+#define CHGDET BIT(7)
+#define MR_COMP BIT(6)
+#define SENDEND BIT(5)
+#define V_VBUS BIT(4)
+
+/* STATUS register masks */
+#define MAX14526_STATUS 0x05
+#define CPORT BIT(7)
+#define CHPORT BIT(6)
+#define C1COMP BIT(0)
+
+enum max14526_idno_resistance {
+ MAX14526_GND,
+ MAX14526_24KOHM,
+ MAX14526_56KOHM,
+ MAX14526_100KOHM,
+ MAX14526_130KOHM,
+ MAX14526_180KOHM,
+ MAX14526_240KOHM,
+ MAX14526_330KOHM,
+ MAX14526_430KOHM,
+ MAX14526_620KOHM,
+ MAX14526_910KOHM,
+ MAX14526_OPEN
+};
+
+enum max14526_field_idx {
+ VENDOR_ID, CHIP_REV, /* DEVID */
+ DM, DP, /* SW_CONTROL */
+ MAX14526_N_REGMAP_FIELDS
+};
+
+static const struct reg_field max14526_reg_field[MAX14526_N_REGMAP_FIELDS] = {
+ [VENDOR_ID] = REG_FIELD(MAX14526_DEVICE_ID, 4, 7),
+ [CHIP_REV] = REG_FIELD(MAX14526_DEVICE_ID, 0, 3),
+ [DM] = REG_FIELD(MAX14526_SW_CONTROL, 0, 2),
+ [DP] = REG_FIELD(MAX14526_SW_CONTROL, 3, 5),
+};
+
+struct max14526_data {
+ struct i2c_client *client;
+ struct extcon_dev *edev;
+
+ struct regmap *regmap;
+ struct regmap_field *rfield[MAX14526_N_REGMAP_FIELDS];
+
+ int last_state;
+ int cable;
+};
+
+enum max14526_muic_modes {
+ MAX14526_OTG = MAX14526_GND, /* no power */
+ MAX14526_MHL = MAX14526_56KOHM, /* no power */
+ MAX14526_OTG_Y = MAX14526_GND | V_VBUS,
+ MAX14526_MHL_CHG = MAX14526_GND | V_VBUS | CHGDET,
+ MAX14526_NONE = MAX14526_OPEN,
+ MAX14526_USB = MAX14526_OPEN | V_VBUS,
+ MAX14526_CHG = MAX14526_OPEN | V_VBUS | CHGDET,
+};
+
+static const unsigned int max14526_extcon_cable[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_CHG_USB_FAST,
+ EXTCON_DISP_MHL,
+ EXTCON_NONE,
+};
+
+static int max14526_ap_usb_mode(struct max14526_data *priv)
+{
+ struct device *dev = &priv->client->dev;
+ int ret;
+
+ /* Enable USB Path */
+ ret = regmap_field_write(priv->rfield[DM], SW_DATA);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(priv->rfield[DP], SW_DATA);
+ if (ret)
+ return ret;
+
+ /* Enable 200K, Charger Pump and ADC */
+ ret = regmap_write(priv->regmap, MAX14526_CONTROL_1,
+ ID_200 | ADC_EN | CP_EN);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev, "AP USB mode set\n");
+
+ return 0;
+}
+
+static irqreturn_t max14526_interrupt(int irq, void *dev_id)
+{
+ struct max14526_data *priv = dev_id;
+ struct device *dev = &priv->client->dev;
+ int state, ret;
+
+ /*
+ * Upon an MUIC IRQ (MUIC_INT_N falls), wait at least 70ms
+ * before reading INT_STAT and STATUS. After the reads,
+ * MUIC_INT_N returns to high (but the INT_STAT and STATUS
+ * contents will be held).
+ */
+ msleep(100);
+
+ ret = regmap_read(priv->regmap, MAX14526_INT_STAT, &state);
+ if (ret)
+ dev_err(dev, "failed to read MUIC state %d\n", ret);
+
+ if (state == priv->last_state)
+ return IRQ_HANDLED;
+
+ /* Detach previous device */
+ extcon_set_state_sync(priv->edev, priv->cable, false);
+
+ switch (state) {
+ case MAX14526_USB:
+ priv->cable = EXTCON_USB;
+ break;
+
+ case MAX14526_CHG:
+ priv->cable = EXTCON_CHG_USB_FAST;
+ break;
+
+ case MAX14526_OTG:
+ case MAX14526_OTG_Y:
+ priv->cable = EXTCON_USB_HOST;
+ break;
+
+ case MAX14526_MHL:
+ case MAX14526_MHL_CHG:
+ priv->cable = EXTCON_DISP_MHL;
+ break;
+
+ case MAX14526_NONE:
+ default:
+ priv->cable = EXTCON_NONE;
+ break;
+ }
+
+ extcon_set_state_sync(priv->edev, priv->cable, true);
+
+ priv->last_state = state;
+
+ return IRQ_HANDLED;
+}
+
+static const struct regmap_config max14526_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = MAX14526_STATUS,
+};
+
+static int max14526_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct max14526_data *priv;
+ int ret, dev_id, rev, i;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->client = client;
+ i2c_set_clientdata(client, priv);
+
+ priv->regmap = devm_regmap_init_i2c(client, &max14526_regmap_config);
+ if (IS_ERR(priv->regmap))
+ return dev_err_probe(dev, PTR_ERR(priv->regmap), "cannot allocate regmap\n");
+
+ for (i = 0; i < MAX14526_N_REGMAP_FIELDS; i++) {
+ priv->rfield[i] = devm_regmap_field_alloc(dev, priv->regmap,
+ max14526_reg_field[i]);
+ if (IS_ERR(priv->rfield[i]))
+ return dev_err_probe(dev, PTR_ERR(priv->rfield[i]),
+ "cannot allocate regmap field\n");
+ }
+
+ /* Detect if MUIC version is supported */
+ ret = regmap_field_read(priv->rfield[VENDOR_ID], &dev_id);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to read MUIC ID\n");
+
+ regmap_field_read(priv->rfield[CHIP_REV], &rev);
+
+ if (dev_id == MAX14526_ID)
+ dev_info(dev, "detected MAX14526 MUIC with id 0x%x, rev 0x%x\n", dev_id, rev);
+ else
+ dev_err_probe(dev, -EINVAL, "MUIC vendor id 0x%X is not recognized\n", dev_id);
+
+ priv->edev = devm_extcon_dev_allocate(dev, max14526_extcon_cable);
+ if (IS_ERR(priv->edev))
+ return dev_err_probe(dev, (IS_ERR(priv->edev)),
+ "failed to allocate extcon device\n");
+
+ ret = devm_extcon_dev_register(dev, priv->edev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to register extcon device\n");
+
+ ret = max14526_ap_usb_mode(priv);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to set AP USB mode\n");
+
+ regmap_write_bits(priv->regmap, MAX14526_CONTROL_2, INT_EN, INT_EN);
+ regmap_write_bits(priv->regmap, MAX14526_CONTROL_2, USB_DET_DIS, (u32)~USB_DET_DIS);
+
+ ret = devm_request_threaded_irq(dev, client->irq, NULL, &max14526_interrupt,
+ IRQF_ONESHOT | IRQF_SHARED, client->name, priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register IRQ\n");
+
+ irq_wake_thread(client->irq, priv);
+
+ return 0;
+}
+
+static int max14526_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct max14526_data *priv = i2c_get_clientdata(client);
+
+ irq_wake_thread(client->irq, priv);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(max14526_pm_ops, NULL, max14526_resume);
+
+static const struct of_device_id max14526_match[] = {
+ { .compatible = "maxim,max14526" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, max14526_match);
+
+static const struct i2c_device_id max14526_id[] = {
+ { "max14526" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max14526_id);
+
+static struct i2c_driver max14526_driver = {
+ .driver = {
+ .name = "max14526",
+ .of_match_table = max14526_match,
+ .pm = &max14526_pm_ops,
+ },
+ .probe = max14526_probe,
+ .id_table = max14526_id,
+};
+module_i2c_driver(max14526_driver);
+
+MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
+MODULE_DESCRIPTION("MAX14526 extcon driver to support MUIC");
+MODULE_LICENSE("GPL");
diff --git a/drivers/extcon/extcon-qcom-spmi-misc.c b/drivers/extcon/extcon-qcom-spmi-misc.c
index 53de581a393a..afaba5685c3d 100644
--- a/drivers/extcon/extcon-qcom-spmi-misc.c
+++ b/drivers/extcon/extcon-qcom-spmi-misc.c
@@ -155,7 +155,7 @@ static int qcom_usb_extcon_probe(struct platform_device *pdev)
}
platform_set_drvdata(pdev, info);
- device_init_wakeup(dev, 1);
+ devm_device_init_wakeup(dev);
/* Perform initial detection */
qcom_usb_extcon_detect_cable(&info->wq_detcable.work);
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index caeb7bee50cf..7ee3afbc2b05 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1951,6 +1951,17 @@ config GPIO_MPSSE
GPIO driver for FTDI's MPSSE interface. These can do input and
output. Each MPSSE provides 16 IO pins.
+config GPIO_USBIO
+ tristate "Intel USBIO GPIO support"
+ depends on USB_USBIO
+ default USB_USBIO
+ help
+ Select this option to enable GPIO driver for the INTEL
+ USBIO driver stack.
+
+ This driver can also be built as a module. If so, the module
+ will be called gpio_usbio.
+
endmenu
menu "Virtual GPIO drivers"
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 000fa2e397c2..ec296fa14bfd 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -194,6 +194,7 @@ obj-$(CONFIG_GPIO_TS5500) += gpio-ts5500.o
obj-$(CONFIG_GPIO_TWL4030) += gpio-twl4030.o
obj-$(CONFIG_GPIO_TWL6040) += gpio-twl6040.o
obj-$(CONFIG_GPIO_UNIPHIER) += gpio-uniphier.o
+obj-$(CONFIG_GPIO_USBIO) += gpio-usbio.o
obj-$(CONFIG_GPIO_VF610) += gpio-vf610.o
obj-$(CONFIG_GPIO_VIPERBOARD) += gpio-viperboard.o
obj-$(CONFIG_GPIO_VIRTUSER) += gpio-virtuser.o
diff --git a/drivers/gpio/gpio-usbio.c b/drivers/gpio/gpio-usbio.c
new file mode 100644
index 000000000000..e13c120824e3
--- /dev/null
+++ b/drivers/gpio/gpio-usbio.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Intel Corporation.
+ * Copyright (c) 2025 Red Hat, Inc.
+ */
+
+#include <linux/acpi.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/usb/usbio.h>
+
+struct usbio_gpio_bank {
+ u8 config[USBIO_GPIOSPERBANK];
+ u32 bitmap;
+};
+
+struct usbio_gpio {
+ struct mutex config_mutex; /* Protects banks[x].config */
+ struct usbio_gpio_bank banks[USBIO_MAX_GPIOBANKS];
+ struct gpio_chip gc;
+ struct auxiliary_device *adev;
+};
+
+static const struct acpi_device_id usbio_gpio_acpi_hids[] = {
+ { "INTC1007" }, /* MTL */
+ { "INTC10B2" }, /* ARL */
+ { "INTC10B5" }, /* LNL */
+ { "INTC10E2" }, /* PTL */
+ { }
+};
+
+static void usbio_gpio_get_bank_and_pin(struct gpio_chip *gc, unsigned int offset,
+ struct usbio_gpio_bank **bank_ret,
+ unsigned int *pin_ret)
+{
+ struct usbio_gpio *gpio = gpiochip_get_data(gc);
+ struct device *dev = &gpio->adev->dev;
+ struct usbio_gpio_bank *bank;
+ unsigned int pin;
+
+ bank = &gpio->banks[offset / USBIO_GPIOSPERBANK];
+ pin = offset % USBIO_GPIOSPERBANK;
+ if (~bank->bitmap & BIT(pin)) {
+ /* The FW bitmap sometimes is invalid, warn and continue */
+ dev_warn_once(dev, FW_BUG "GPIO %u is not in FW pins bitmap\n", offset);
+ }
+
+ *bank_ret = bank;
+ *pin_ret = pin;
+}
+
+static int usbio_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct usbio_gpio_bank *bank;
+ unsigned int pin;
+ u8 cfg;
+
+ usbio_gpio_get_bank_and_pin(gc, offset, &bank, &pin);
+
+ cfg = bank->config[pin] & USBIO_GPIO_PINMOD_MASK;
+
+ return (cfg == USBIO_GPIO_PINMOD_OUTPUT) ?
+ GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
+}
+
+static int usbio_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct usbio_gpio *gpio = gpiochip_get_data(gc);
+ struct usbio_gpio_bank *bank;
+ struct usbio_gpio_rw gbuf;
+ unsigned int pin;
+ int ret;
+
+ usbio_gpio_get_bank_and_pin(gc, offset, &bank, &pin);
+
+ gbuf.bankid = offset / USBIO_GPIOSPERBANK;
+ gbuf.pincount = 1;
+ gbuf.pin = pin;
+
+ ret = usbio_control_msg(gpio->adev, USBIO_PKTTYPE_GPIO, USBIO_GPIOCMD_READ,
+ &gbuf, sizeof(gbuf) - sizeof(gbuf.value),
+ &gbuf, sizeof(gbuf));
+ if (ret != sizeof(gbuf))
+ return (ret < 0) ? ret : -EPROTO;
+
+ return (le32_to_cpu(gbuf.value) >> pin) & 1;
+}
+
+static int usbio_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+ struct usbio_gpio *gpio = gpiochip_get_data(gc);
+ struct usbio_gpio_bank *bank;
+ struct usbio_gpio_rw gbuf;
+ unsigned int pin;
+
+ usbio_gpio_get_bank_and_pin(gc, offset, &bank, &pin);
+
+ gbuf.bankid = offset / USBIO_GPIOSPERBANK;
+ gbuf.pincount = 1;
+ gbuf.pin = pin;
+ gbuf.value = cpu_to_le32(value << pin);
+
+ return usbio_control_msg(gpio->adev, USBIO_PKTTYPE_GPIO, USBIO_GPIOCMD_WRITE,
+ &gbuf, sizeof(gbuf), NULL, 0);
+}
+
+static int usbio_gpio_update_config(struct gpio_chip *gc, unsigned int offset,
+ u8 mask, u8 value)
+{
+ struct usbio_gpio *gpio = gpiochip_get_data(gc);
+ struct usbio_gpio_bank *bank;
+ struct usbio_gpio_init gbuf;
+ unsigned int pin;
+
+ usbio_gpio_get_bank_and_pin(gc, offset, &bank, &pin);
+
+ guard(mutex)(&gpio->config_mutex);
+
+ bank->config[pin] &= ~mask;
+ bank->config[pin] |= value;
+
+ gbuf.bankid = offset / USBIO_GPIOSPERBANK;
+ gbuf.config = bank->config[pin];
+ gbuf.pincount = 1;
+ gbuf.pin = pin;
+
+ return usbio_control_msg(gpio->adev, USBIO_PKTTYPE_GPIO, USBIO_GPIOCMD_INIT,
+ &gbuf, sizeof(gbuf), NULL, 0);
+}
+
+static int usbio_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
+{
+ return usbio_gpio_update_config(gc, offset, USBIO_GPIO_PINMOD_MASK,
+ USBIO_GPIO_SET_PINMOD(USBIO_GPIO_PINMOD_INPUT));
+}
+
+static int usbio_gpio_direction_output(struct gpio_chip *gc,
+ unsigned int offset, int value)
+{
+ int ret;
+
+ ret = usbio_gpio_update_config(gc, offset, USBIO_GPIO_PINMOD_MASK,
+ USBIO_GPIO_SET_PINMOD(USBIO_GPIO_PINMOD_OUTPUT));
+ if (ret)
+ return ret;
+
+ return usbio_gpio_set(gc, offset, value);
+}
+
+static int usbio_gpio_set_config(struct gpio_chip *gc, unsigned int offset,
+ unsigned long config)
+{
+ u8 value;
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT:
+ value = USBIO_GPIO_SET_PINCFG(USBIO_GPIO_PINCFG_DEFAULT);
+ break;
+ case PIN_CONFIG_BIAS_PULL_UP:
+ value = USBIO_GPIO_SET_PINCFG(USBIO_GPIO_PINCFG_PULLUP);
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ value = USBIO_GPIO_SET_PINCFG(USBIO_GPIO_PINCFG_PULLDOWN);
+ break;
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ value = USBIO_GPIO_SET_PINCFG(USBIO_GPIO_PINCFG_PUSHPULL);
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ return usbio_gpio_update_config(gc, offset, USBIO_GPIO_PINCFG_MASK, value);
+}
+
+static int usbio_gpio_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *adev_id)
+{
+ struct usbio_gpio_bank_desc *bank_desc;
+ struct device *dev = &adev->dev;
+ struct usbio_gpio *gpio;
+ int bank, ret;
+
+ bank_desc = dev_get_platdata(dev);
+ if (!bank_desc)
+ return -EINVAL;
+
+ gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ ret = devm_mutex_init(dev, &gpio->config_mutex);
+ if (ret)
+ return ret;
+
+ gpio->adev = adev;
+
+ usbio_acpi_bind(gpio->adev, usbio_gpio_acpi_hids);
+
+ for (bank = 0; bank < USBIO_MAX_GPIOBANKS && bank_desc[bank].bmap; bank++)
+ gpio->banks[bank].bitmap = le32_to_cpu(bank_desc[bank].bmap);
+
+ gpio->gc.label = ACPI_COMPANION(dev) ?
+ acpi_dev_name(ACPI_COMPANION(dev)) : dev_name(dev);
+ gpio->gc.parent = dev;
+ gpio->gc.owner = THIS_MODULE;
+ gpio->gc.get_direction = usbio_gpio_get_direction;
+ gpio->gc.direction_input = usbio_gpio_direction_input;
+ gpio->gc.direction_output = usbio_gpio_direction_output;
+ gpio->gc.get = usbio_gpio_get;
+ gpio->gc.set = usbio_gpio_set;
+ gpio->gc.set_config = usbio_gpio_set_config;
+ gpio->gc.base = -1;
+ gpio->gc.ngpio = bank * USBIO_GPIOSPERBANK;
+ gpio->gc.can_sleep = true;
+
+ ret = devm_gpiochip_add_data(dev, &gpio->gc, gpio);
+ if (ret)
+ return ret;
+
+ if (has_acpi_companion(dev))
+ acpi_dev_clear_dependencies(ACPI_COMPANION(dev));
+
+ return 0;
+}
+
+static const struct auxiliary_device_id usbio_gpio_id_table[] = {
+ { "usbio.usbio-gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(auxiliary, usbio_gpio_id_table);
+
+static struct auxiliary_driver usbio_gpio_driver = {
+ .name = USBIO_GPIO_CLIENT,
+ .probe = usbio_gpio_probe,
+ .id_table = usbio_gpio_id_table
+};
+module_auxiliary_driver(usbio_gpio_driver);
+
+MODULE_DESCRIPTION("Intel USBIO GPIO driver");
+MODULE_AUTHOR("Israel Cepeda <israel.a.cepeda.lopez@intel.com>");
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("USBIO");
diff --git a/drivers/greybus/svc.c b/drivers/greybus/svc.c
index 4256467fcd35..35ea7147dca6 100644
--- a/drivers/greybus/svc.c
+++ b/drivers/greybus/svc.c
@@ -10,6 +10,7 @@
#include <linux/kstrtox.h>
#include <linux/workqueue.h>
#include <linux/greybus.h>
+#include <linux/string_choices.h>
#define SVC_INTF_EJECT_TIMEOUT 9000
#define SVC_INTF_ACTIVATE_TIMEOUT 6000
@@ -73,7 +74,7 @@ static ssize_t watchdog_show(struct device *dev, struct device_attribute *attr,
struct gb_svc *svc = to_gb_svc(dev);
return sprintf(buf, "%s\n",
- gb_svc_watchdog_enabled(svc) ? "enabled" : "disabled");
+ str_enabled_disabled(gb_svc_watchdog_enabled(svc)));
}
static ssize_t watchdog_store(struct device *dev,
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index b934523593d9..7ff85c7200e5 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -92,6 +92,17 @@ config HID_GENERIC
If unsure, say Y.
+config HID_HAPTIC
+ tristate "Haptic touchpad support"
+ default n
+ help
+ Support for touchpads with force sensors and haptic actuators instead of a
+ traditional button.
+ Adds extra parsing and FF device for the hid multitouch driver.
+ It can be used for Elan 2703 haptic touchpad.
+
+ If unsure, say N.
+
menu "Special HID drivers"
config HID_A4TECH
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 10ae5dedbd84..361a7daedeb8 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -4,6 +4,7 @@
#
hid-y := hid-core.o hid-input.o hid-quirks.o
hid-$(CONFIG_DEBUG_FS) += hid-debug.o
+hid-$(CONFIG_HID_HAPTIC) += hid-haptic.o
obj-$(CONFIG_HID_BPF) += bpf/
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 8db9d4e7c3b0..a444d41e53b6 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -1388,9 +1388,6 @@ static const struct hid_device_id asus_devices[] = {
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
- USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3),
- QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
- { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
@@ -1420,6 +1417,9 @@ static const struct hid_device_id asus_devices[] = {
* part, while letting hid-multitouch.c handle the touchpad.
*/
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+ USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO),
+ QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+ { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
{ }
};
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 5419a6c10907..a5b3a8ca2fcb 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -944,6 +944,15 @@ static int hid_scan_report(struct hid_device *hid)
hid->group = HID_GROUP_GENERIC;
/*
+ * In case we are re-scanning after a BPF has been loaded,
+ * we need to use the bpf report descriptor, not the original one.
+ */
+ if (hid->bpf_rdesc && hid->bpf_rsize) {
+ start = hid->bpf_rdesc;
+ end = start + hid->bpf_rsize;
+ }
+
+ /*
* The parsing is simpler than the one in hid_open_report() as we should
* be robust against hid errors. Those errors will be raised by
* hid_open_report() anyway.
@@ -2708,12 +2717,32 @@ static bool hid_check_device_match(struct hid_device *hdev,
return !hid_ignore_special_drivers && !(hdev->quirks & HID_QUIRK_IGNORE_SPECIAL_DRIVER);
}
+static void hid_set_group(struct hid_device *hdev)
+{
+ int ret;
+
+ if (hid_ignore_special_drivers) {
+ hdev->group = HID_GROUP_GENERIC;
+ } else if (!hdev->group &&
+ !(hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)) {
+ ret = hid_scan_report(hdev);
+ if (ret)
+ hid_warn(hdev, "bad device descriptor (%d)\n", ret);
+ }
+}
+
static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
{
const struct hid_device_id *id;
int ret;
if (!hdev->bpf_rsize) {
+ /* we keep a reference to the currently scanned report descriptor */
+ const __u8 *original_rdesc = hdev->bpf_rdesc;
+
+ if (!original_rdesc)
+ original_rdesc = hdev->dev_rdesc;
+
/* in case a bpf program gets detached, we need to free the old one */
hid_free_bpf_rdesc(hdev);
@@ -2723,6 +2752,12 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
/* call_hid_bpf_rdesc_fixup will always return a valid pointer */
hdev->bpf_rdesc = call_hid_bpf_rdesc_fixup(hdev, hdev->dev_rdesc,
&hdev->bpf_rsize);
+
+ /* the report descriptor changed, we need to re-scan it */
+ if (original_rdesc != hdev->bpf_rdesc) {
+ hdev->group = 0;
+ hid_set_group(hdev);
+ }
}
if (!hid_check_device_match(hdev, hdrv, &id))
@@ -2903,14 +2938,7 @@ int hid_add_device(struct hid_device *hdev)
/*
* Scan generic devices for group information
*/
- if (hid_ignore_special_drivers) {
- hdev->group = HID_GROUP_GENERIC;
- } else if (!hdev->group &&
- !(hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)) {
- ret = hid_scan_report(hdev);
- if (ret)
- hid_warn(hdev, "bad device descriptor (%d)\n", ret);
- }
+ hid_set_group(hdev);
hdev->id = atomic_inc_return(&id);
diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c
new file mode 100644
index 000000000000..aa090684c1f2
--- /dev/null
+++ b/drivers/hid/hid-haptic.c
@@ -0,0 +1,580 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HID Haptic support for Linux
+ *
+ * Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
+ */
+
+#include <linux/input/mt.h>
+#include <linux/module.h>
+
+#include "hid-haptic.h"
+
+void hid_haptic_feature_mapping(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_field *field, struct hid_usage *usage)
+{
+ u16 usage_hid;
+
+ if (usage->hid == HID_HP_AUTOTRIGGER) {
+ if (usage->usage_index >= field->report_count) {
+ dev_err(&hdev->dev,
+ "HID_HP_AUTOTRIGGER out of range\n");
+ return;
+ }
+
+ hid_device_io_start(hdev);
+ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT);
+ hid_hw_wait(hdev);
+ hid_device_io_stop(hdev);
+ haptic->default_auto_trigger =
+ field->value[usage->usage_index];
+ haptic->auto_trigger_report = field->report;
+ } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ORDINAL) {
+ usage_hid = usage->hid & HID_USAGE;
+ switch (field->logical) {
+ case HID_HP_WAVEFORMLIST:
+ if (usage_hid > haptic->max_waveform_id)
+ haptic->max_waveform_id = usage_hid;
+ break;
+ case HID_HP_DURATIONLIST:
+ if (usage_hid > haptic->max_duration_id)
+ haptic->max_duration_id = usage_hid;
+ break;
+ default:
+ break;
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(hid_haptic_feature_mapping);
+
+bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
+ struct hid_input *hi, struct hid_field *field)
+{
+ if (field->unit == HID_UNIT_GRAM || field->unit == HID_UNIT_NEWTON) {
+ haptic->force_logical_minimum = field->logical_minimum;
+ haptic->force_physical_minimum = field->physical_minimum;
+ haptic->force_resolution = input_abs_get_res(hi->input,
+ ABS_MT_PRESSURE);
+ return true;
+ }
+ return false;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_check_pressure_unit);
+
+int hid_haptic_input_mapping(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_input *hi,
+ struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max)
+{
+ if (usage->hid == HID_HP_MANUALTRIGGER) {
+ haptic->manual_trigger_report = field->report;
+ /* we don't really want to map these fields */
+ return -1;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_input_mapping);
+
+int hid_haptic_input_configured(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_input *hi)
+{
+
+ if (hi->application == HID_DG_TOUCHPAD) {
+ if (haptic->auto_trigger_report &&
+ haptic->manual_trigger_report) {
+ __set_bit(INPUT_PROP_HAPTIC_TOUCHPAD, hi->input->propbit);
+ return 1;
+ }
+ return 0;
+ }
+ return -1;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_input_configured);
+
+static void parse_auto_trigger_field(struct hid_haptic_device *haptic,
+ struct hid_field *field)
+{
+ int count = field->report_count;
+ int n;
+ u16 usage_hid;
+
+ for (n = 0; n < count; n++) {
+ switch (field->usage[n].hid & HID_USAGE_PAGE) {
+ case HID_UP_ORDINAL:
+ usage_hid = field->usage[n].hid & HID_USAGE;
+ switch (field->logical) {
+ case HID_HP_WAVEFORMLIST:
+ haptic->hid_usage_map[usage_hid] = field->value[n];
+ if (field->value[n] ==
+ (HID_HP_WAVEFORMPRESS & HID_USAGE)) {
+ haptic->press_ordinal = usage_hid;
+ } else if (field->value[n] ==
+ (HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
+ haptic->release_ordinal = usage_hid;
+ }
+ break;
+ case HID_HP_DURATIONLIST:
+ haptic->duration_map[usage_hid] =
+ field->value[n];
+ break;
+ default:
+ break;
+ }
+ break;
+ case HID_UP_HAPTIC:
+ switch (field->usage[n].hid) {
+ case HID_HP_WAVEFORMVENDORID:
+ haptic->vendor_id = field->value[n];
+ break;
+ case HID_HP_WAVEFORMVENDORPAGE:
+ haptic->vendor_page = field->value[n];
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ /* Should not really happen */
+ break;
+ }
+ }
+}
+
+static void fill_effect_buf(struct hid_haptic_device *haptic,
+ struct ff_haptic_effect *effect,
+ struct hid_haptic_effect *haptic_effect,
+ int waveform_ordinal)
+{
+ struct hid_report *rep = haptic->manual_trigger_report;
+ struct hid_usage *usage;
+ struct hid_field *field;
+ s32 value;
+ int i, j;
+ u8 *buf = haptic_effect->report_buf;
+
+ mutex_lock(&haptic->manual_trigger_mutex);
+ for (i = 0; i < rep->maxfield; i++) {
+ field = rep->field[i];
+ /* Ignore if report count is out of bounds. */
+ if (field->report_count < 1)
+ continue;
+
+ for (j = 0; j < field->maxusage; j++) {
+ usage = &field->usage[j];
+
+ switch (usage->hid) {
+ case HID_HP_INTENSITY:
+ if (effect->intensity > 100) {
+ value = field->logical_maximum;
+ } else {
+ value = field->logical_minimum +
+ effect->intensity *
+ (field->logical_maximum -
+ field->logical_minimum) / 100;
+ }
+ break;
+ case HID_HP_REPEATCOUNT:
+ value = effect->repeat_count;
+ break;
+ case HID_HP_RETRIGGERPERIOD:
+ value = effect->retrigger_period;
+ break;
+ case HID_HP_MANUALTRIGGER:
+ value = waveform_ordinal;
+ break;
+ default:
+ break;
+ }
+
+ field->value[j] = value;
+ }
+ }
+
+ hid_output_report(rep, buf);
+ mutex_unlock(&haptic->manual_trigger_mutex);
+}
+
+static void switch_mode(struct hid_device *hdev, struct hid_haptic_device *haptic,
+ int mode)
+{
+ struct hid_report *rep = haptic->auto_trigger_report;
+ struct hid_field *field;
+ s32 value;
+ int i, j;
+
+ if (mode == HID_HAPTIC_MODE_HOST)
+ value = HID_HAPTIC_ORDINAL_WAVEFORMSTOP;
+ else
+ value = haptic->default_auto_trigger;
+
+ mutex_lock(&haptic->auto_trigger_mutex);
+ for (i = 0; i < rep->maxfield; i++) {
+ field = rep->field[i];
+ /* Ignore if report count is out of bounds. */
+ if (field->report_count < 1)
+ continue;
+
+ for (j = 0; j < field->maxusage; j++) {
+ if (field->usage[j].hid == HID_HP_AUTOTRIGGER)
+ field->value[j] = value;
+ }
+ }
+
+ /* send the report */
+ hid_hw_request(hdev, rep, HID_REQ_SET_REPORT);
+ mutex_unlock(&haptic->auto_trigger_mutex);
+ haptic->mode = mode;
+}
+
+static int hid_haptic_upload_effect(struct input_dev *dev, struct ff_effect *effect,
+ struct ff_effect *old)
+{
+ struct hid_device *hdev = input_get_drvdata(dev);
+ struct ff_device *ff = dev->ff;
+ struct hid_haptic_device *haptic = ff->private;
+ int i, ordinal = 0;
+ bool switch_modes = false;
+
+ /* If vendor range, check vendor id and page */
+ if (effect->u.haptic.hid_usage >= (HID_HP_VENDORWAVEFORMMIN & HID_USAGE) &&
+ effect->u.haptic.hid_usage <= (HID_HP_VENDORWAVEFORMMAX & HID_USAGE) &&
+ (effect->u.haptic.vendor_id != haptic->vendor_id ||
+ effect->u.haptic.vendor_waveform_page != haptic->vendor_page))
+ return -EINVAL;
+
+ /* Check hid_usage */
+ for (i = 1; i <= haptic->max_waveform_id; i++) {
+ if (haptic->hid_usage_map[i] == effect->u.haptic.hid_usage) {
+ ordinal = i;
+ break;
+ }
+ }
+ if (ordinal < 1)
+ return -EINVAL;
+
+ /* Fill the buffer for the effect id */
+ fill_effect_buf(haptic, &effect->u.haptic, &haptic->effect[effect->id],
+ ordinal);
+
+ if (effect->u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE) ||
+ effect->u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE))
+ switch_modes = true;
+
+ /* If device is in autonomous mode, and the uploaded effect signals userspace
+ * wants control of the device, change modes
+ */
+ if (switch_modes && haptic->mode == HID_HAPTIC_MODE_DEVICE)
+ switch_mode(hdev, haptic, HID_HAPTIC_MODE_HOST);
+
+ return 0;
+}
+
+static int play_effect(struct hid_device *hdev, struct hid_haptic_device *haptic,
+ struct hid_haptic_effect *effect)
+{
+ int ret;
+
+ ret = hid_hw_output_report(hdev, effect->report_buf,
+ haptic->manual_trigger_report_len);
+ if (ret < 0) {
+ ret = hid_hw_raw_request(hdev,
+ haptic->manual_trigger_report->id,
+ effect->report_buf,
+ haptic->manual_trigger_report_len,
+ HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
+ }
+
+ return ret;
+}
+
+static void haptic_work_handler(struct work_struct *work)
+{
+
+ struct hid_haptic_effect *effect = container_of(work,
+ struct hid_haptic_effect,
+ work);
+ struct input_dev *dev = effect->input_dev;
+ struct hid_device *hdev = input_get_drvdata(dev);
+ struct hid_haptic_device *haptic = dev->ff->private;
+
+ mutex_lock(&haptic->manual_trigger_mutex);
+ if (effect != &haptic->stop_effect)
+ play_effect(hdev, haptic, &haptic->stop_effect);
+
+ play_effect(hdev, haptic, effect);
+ mutex_unlock(&haptic->manual_trigger_mutex);
+
+}
+
+static int hid_haptic_playback(struct input_dev *dev, int effect_id, int value)
+{
+ struct hid_haptic_device *haptic = dev->ff->private;
+
+ if (value)
+ queue_work(haptic->wq, &haptic->effect[effect_id].work);
+ else
+ queue_work(haptic->wq, &haptic->stop_effect.work);
+
+ return 0;
+}
+
+static void effect_set_default(struct ff_effect *effect)
+{
+ effect->type = FF_HAPTIC;
+ effect->id = -1;
+ effect->u.haptic.hid_usage = HID_HP_WAVEFORMNONE & HID_USAGE;
+ effect->u.haptic.intensity = 100;
+ effect->u.haptic.retrigger_period = 0;
+ effect->u.haptic.repeat_count = 0;
+}
+
+static int hid_haptic_erase(struct input_dev *dev, int effect_id)
+{
+ struct hid_haptic_device *haptic = dev->ff->private;
+ struct hid_device *hdev = input_get_drvdata(dev);
+ struct ff_effect effect;
+ int ordinal;
+
+ effect_set_default(&effect);
+
+ if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
+ ordinal = haptic->release_ordinal;
+ if (!ordinal) {
+ ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
+ if (haptic->mode == HID_HAPTIC_MODE_HOST)
+ switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
+ } else
+ effect.u.haptic.hid_usage = HID_HP_WAVEFORMRELEASE & HID_USAGE;
+
+ fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id],
+ ordinal);
+ } else if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE)) {
+ ordinal = haptic->press_ordinal;
+ if (!ordinal) {
+ ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
+ if (haptic->mode == HID_HAPTIC_MODE_HOST)
+ switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
+ }
+ else
+ effect.u.haptic.hid_usage = HID_HP_WAVEFORMPRESS & HID_USAGE;
+
+ fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id],
+ ordinal);
+ }
+
+ return 0;
+}
+
+static void hid_haptic_destroy(struct ff_device *ff)
+{
+ struct hid_haptic_device *haptic = ff->private;
+ struct hid_device *hdev = haptic->hdev;
+ int r;
+
+ if (hdev)
+ put_device(&hdev->dev);
+
+ kfree(haptic->stop_effect.report_buf);
+ haptic->stop_effect.report_buf = NULL;
+
+ if (haptic->effect) {
+ for (r = 0; r < ff->max_effects; r++)
+ kfree(haptic->effect[r].report_buf);
+ kfree(haptic->effect);
+ }
+ haptic->effect = NULL;
+
+ destroy_workqueue(haptic->wq);
+ haptic->wq = NULL;
+
+ kfree(haptic->duration_map);
+ haptic->duration_map = NULL;
+
+ kfree(haptic->hid_usage_map);
+ haptic->hid_usage_map = NULL;
+
+ module_put(THIS_MODULE);
+}
+
+int hid_haptic_init(struct hid_device *hdev,
+ struct hid_haptic_device **haptic_ptr)
+{
+ struct hid_haptic_device *haptic = *haptic_ptr;
+ struct input_dev *dev = NULL;
+ struct hid_input *hidinput;
+ struct ff_device *ff;
+ int ret = 0, r;
+ struct ff_haptic_effect stop_effect = {
+ .hid_usage = HID_HP_WAVEFORMSTOP & HID_USAGE,
+ };
+ const char *prefix = "hid-haptic";
+ char *name;
+ int (*flush)(struct input_dev *dev, struct file *file);
+ int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
+
+ haptic->hdev = hdev;
+ haptic->max_waveform_id = max(2u, haptic->max_waveform_id);
+ haptic->max_duration_id = max(2u, haptic->max_duration_id);
+
+ haptic->hid_usage_map = kcalloc(haptic->max_waveform_id + 1,
+ sizeof(u16), GFP_KERNEL);
+ if (!haptic->hid_usage_map) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+ haptic->duration_map = kcalloc(haptic->max_duration_id + 1,
+ sizeof(u32), GFP_KERNEL);
+ if (!haptic->duration_map) {
+ ret = -ENOMEM;
+ goto usage_map;
+ }
+
+ if (haptic->max_waveform_id != haptic->max_duration_id)
+ dev_warn(&hdev->dev,
+ "Haptic duration and waveform lists have different max id (%u and %u).\n",
+ haptic->max_duration_id, haptic->max_waveform_id);
+
+ haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMNONE] =
+ HID_HP_WAVEFORMNONE & HID_USAGE;
+ haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMSTOP] =
+ HID_HP_WAVEFORMSTOP & HID_USAGE;
+
+ mutex_init(&haptic->auto_trigger_mutex);
+ for (r = 0; r < haptic->auto_trigger_report->maxfield; r++)
+ parse_auto_trigger_field(haptic, haptic->auto_trigger_report->field[r]);
+
+ list_for_each_entry(hidinput, &hdev->inputs, list) {
+ if (hidinput->application == HID_DG_TOUCHPAD) {
+ dev = hidinput->input;
+ break;
+ }
+ }
+
+ if (!dev) {
+ dev_err(&hdev->dev, "Failed to find the input device\n");
+ ret = -ENODEV;
+ goto duration_map;
+ }
+
+ haptic->input_dev = dev;
+ haptic->manual_trigger_report_len =
+ hid_report_len(haptic->manual_trigger_report);
+ mutex_init(&haptic->manual_trigger_mutex);
+ name = kmalloc(strlen(prefix) + strlen(hdev->name) + 2, GFP_KERNEL);
+ if (name) {
+ sprintf(name, "%s %s", prefix, hdev->name);
+ haptic->wq = create_singlethread_workqueue(name);
+ kfree(name);
+ }
+ if (!haptic->wq) {
+ ret = -ENOMEM;
+ goto duration_map;
+ }
+ haptic->effect = kcalloc(FF_MAX_EFFECTS,
+ sizeof(struct hid_haptic_effect), GFP_KERNEL);
+ if (!haptic->effect) {
+ ret = -ENOMEM;
+ goto output_queue;
+ }
+ for (r = 0; r < FF_MAX_EFFECTS; r++) {
+ haptic->effect[r].report_buf =
+ hid_alloc_report_buf(haptic->manual_trigger_report,
+ GFP_KERNEL);
+ if (!haptic->effect[r].report_buf) {
+ dev_err(&hdev->dev,
+ "Failed to allocate a buffer for an effect.\n");
+ ret = -ENOMEM;
+ goto buffer_free;
+ }
+ haptic->effect[r].input_dev = dev;
+ INIT_WORK(&haptic->effect[r].work, haptic_work_handler);
+ }
+ haptic->stop_effect.report_buf =
+ hid_alloc_report_buf(haptic->manual_trigger_report,
+ GFP_KERNEL);
+ if (!haptic->stop_effect.report_buf) {
+ dev_err(&hdev->dev,
+ "Failed to allocate a buffer for stop effect.\n");
+ ret = -ENOMEM;
+ goto buffer_free;
+ }
+ haptic->stop_effect.input_dev = dev;
+ INIT_WORK(&haptic->stop_effect.work, haptic_work_handler);
+ fill_effect_buf(haptic, &stop_effect, &haptic->stop_effect,
+ HID_HAPTIC_ORDINAL_WAVEFORMSTOP);
+
+ input_set_capability(dev, EV_FF, FF_HAPTIC);
+
+ flush = dev->flush;
+ event = dev->event;
+ ret = input_ff_create(dev, FF_MAX_EFFECTS);
+ if (ret) {
+ dev_err(&hdev->dev, "Failed to create ff device.\n");
+ goto stop_buffer_free;
+ }
+
+ ff = dev->ff;
+ ff->private = haptic;
+ ff->upload = hid_haptic_upload_effect;
+ ff->playback = hid_haptic_playback;
+ ff->erase = hid_haptic_erase;
+ ff->destroy = hid_haptic_destroy;
+ if (!try_module_get(THIS_MODULE)) {
+ dev_err(&hdev->dev, "Failed to increase module count.\n");
+ goto input_free;
+ }
+ if (!get_device(&hdev->dev)) {
+ dev_err(&hdev->dev, "Failed to get hdev device.\n");
+ module_put(THIS_MODULE);
+ goto input_free;
+ }
+ return 0;
+
+input_free:
+ input_ff_destroy(dev);
+ /* Do not let double free happen, input_ff_destroy will call
+ * hid_haptic_destroy.
+ */
+ *haptic_ptr = NULL;
+ /* Restore dev flush and event */
+ dev->flush = flush;
+ dev->event = event;
+ return ret;
+stop_buffer_free:
+ kfree(haptic->stop_effect.report_buf);
+ haptic->stop_effect.report_buf = NULL;
+buffer_free:
+ while (--r >= 0)
+ kfree(haptic->effect[r].report_buf);
+ kfree(haptic->effect);
+ haptic->effect = NULL;
+output_queue:
+ destroy_workqueue(haptic->wq);
+ haptic->wq = NULL;
+duration_map:
+ kfree(haptic->duration_map);
+ haptic->duration_map = NULL;
+usage_map:
+ kfree(haptic->hid_usage_map);
+ haptic->hid_usage_map = NULL;
+exit:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_init);
+
+void hid_haptic_pressure_reset(struct hid_haptic_device *haptic)
+{
+ haptic->pressure_sum = 0;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_pressure_reset);
+
+void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
+ __s32 pressure)
+{
+ haptic->pressure_sum += pressure;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_pressure_increase);
diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h
new file mode 100644
index 000000000000..c6539ac04c1d
--- /dev/null
+++ b/drivers/hid/hid-haptic.h
@@ -0,0 +1,127 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * HID Haptic support for Linux
+ *
+ * Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
+ */
+
+#include <linux/hid.h>
+
+#define HID_HAPTIC_ORDINAL_WAVEFORMNONE 1
+#define HID_HAPTIC_ORDINAL_WAVEFORMSTOP 2
+
+#define HID_HAPTIC_MODE_DEVICE 0
+#define HID_HAPTIC_MODE_HOST 1
+
+struct hid_haptic_effect {
+ u8 *report_buf;
+ struct input_dev *input_dev;
+ struct work_struct work;
+ struct list_head control;
+ struct mutex control_mutex;
+};
+
+struct hid_haptic_effect_node {
+ struct list_head node;
+ struct file *file;
+};
+
+struct hid_haptic_device {
+ struct input_dev *input_dev;
+ struct hid_device *hdev;
+ struct hid_report *auto_trigger_report;
+ struct mutex auto_trigger_mutex;
+ struct workqueue_struct *wq;
+ struct hid_report *manual_trigger_report;
+ struct mutex manual_trigger_mutex;
+ size_t manual_trigger_report_len;
+ int pressed_state;
+ s32 pressure_sum;
+ s32 force_logical_minimum;
+ s32 force_physical_minimum;
+ s32 force_resolution;
+ u32 mode;
+ u32 default_auto_trigger;
+ u32 vendor_page;
+ u32 vendor_id;
+ u32 max_waveform_id;
+ u32 max_duration_id;
+ u16 *hid_usage_map;
+ u32 *duration_map;
+ u16 press_ordinal;
+ u16 release_ordinal;
+ struct hid_haptic_effect *effect;
+ struct hid_haptic_effect stop_effect;
+};
+
+#if IS_ENABLED(CONFIG_HID_HAPTIC)
+void hid_haptic_feature_mapping(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_field *field, struct hid_usage
+ *usage);
+bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
+ struct hid_input *hi, struct hid_field *field);
+int hid_haptic_input_mapping(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_input *hi,
+ struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max);
+int hid_haptic_input_configured(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_input *hi);
+int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr);
+void hid_haptic_handle_press_release(struct hid_haptic_device *haptic);
+void hid_haptic_pressure_reset(struct hid_haptic_device *haptic);
+void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
+ __s32 pressure);
+#else
+static inline
+void hid_haptic_feature_mapping(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_field *field, struct hid_usage
+ *usage)
+{}
+static inline
+bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
+ struct hid_input *hi, struct hid_field *field)
+{
+ return false;
+}
+static inline
+int hid_haptic_input_mapping(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_input *hi,
+ struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max)
+{
+ return 0;
+}
+static inline
+int hid_haptic_input_configured(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_input *hi)
+{
+ return 0;
+}
+static inline
+void hid_haptic_reset(struct hid_device *hdev, struct hid_haptic_device *haptic)
+{}
+static inline
+int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr)
+{
+ return 0;
+}
+static inline
+void hid_haptic_handle_press_release(struct hid_haptic_device *haptic) {}
+static inline
+bool hid_haptic_handle_input(struct hid_haptic_device *haptic)
+{
+ return false;
+}
+static inline
+void hid_haptic_pressure_reset(struct hid_haptic_device *haptic) {}
+static inline
+void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
+ __s32 pressure)
+{}
+#endif
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 149798754570..5721b8414bbd 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -223,7 +223,7 @@
#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD 0x1866
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2 0x19b6
-#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3 0x1a30
+#define USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO 0x1a30
#define USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR 0x18c6
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY 0x1abe
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X 0x1b4c
@@ -1296,6 +1296,8 @@
#define USB_VENDOR_ID_STEELSERIES 0x1038
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index f45f856a127f..5d7532d79d21 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -303,6 +303,19 @@ __s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code)
}
break;
+ case ABS_PRESSURE:
+ case ABS_MT_PRESSURE:
+ if (field->unit == HID_UNIT_NEWTON) {
+ /* Convert to grams, 1 newton is 101.97 grams */
+ prev = physical_extents;
+ physical_extents *= 10197;
+ if (physical_extents < prev)
+ return 0;
+ unit_exponent -= 2;
+ } else if (field->unit != HID_UNIT_GRAM) {
+ return 0;
+ }
+ break;
default:
return 0;
}
@@ -683,9 +696,10 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
if (field->report_count < 1)
goto ignore;
- /* only LED usages are supported in output fields */
+ /* only LED and HAPTIC usages are supported in output fields */
if (field->report_type == HID_OUTPUT_REPORT &&
- (usage->hid & HID_USAGE_PAGE) != HID_UP_LED) {
+ (usage->hid & HID_USAGE_PAGE) != HID_UP_LED &&
+ (usage->hid & HID_USAGE_PAGE) != HID_UP_HAPTIC) {
goto ignore;
}
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index 22c6314a8843..2879e65cf303 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -49,6 +49,8 @@ MODULE_LICENSE("GPL");
#include "hid-ids.h"
+#include "hid-haptic.h"
+
/* quirks to control the device */
#define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0)
#define MT_QUIRK_SLOT_IS_CONTACTID BIT(1)
@@ -168,11 +170,13 @@ struct mt_report_data {
struct mt_device {
struct mt_class mtclass; /* our mt device class */
struct timer_list release_timer; /* to release sticky fingers */
+ struct hid_haptic_device *haptic; /* haptic related configuration */
struct hid_device *hdev; /* hid_device we're attached to */
unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_*) */
__u8 inputmode_value; /* InputMode HID feature value */
__u8 maxcontacts;
bool is_buttonpad; /* is this device a button pad? */
+ bool is_haptic_touchpad; /* is this device a haptic touchpad? */
bool serial_maybe; /* need to check for serial protocol */
struct list_head applications;
@@ -533,6 +537,8 @@ static void mt_feature_mapping(struct hid_device *hdev,
mt_get_feature(hdev, field->report);
break;
}
+
+ hid_haptic_feature_mapping(hdev, td->haptic, field, usage);
}
static void set_abs(struct input_dev *input, unsigned int code,
@@ -888,6 +894,9 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
case HID_DG_TIPPRESSURE:
set_abs(hi->input, ABS_MT_PRESSURE, field,
cls->sn_pressure);
+ td->is_haptic_touchpad =
+ hid_haptic_check_pressure_unit(td->haptic,
+ hi, field);
MT_STORE_FIELD(p);
return 1;
case HID_DG_SCANTIME:
@@ -1008,6 +1017,8 @@ static void mt_sync_frame(struct mt_device *td, struct mt_application *app,
app->num_received = 0;
app->left_button_state = 0;
+ if (td->is_haptic_touchpad)
+ hid_haptic_pressure_reset(td->haptic);
if (test_bit(MT_IO_FLAGS_ACTIVE_SLOTS, &td->mt_io_flags))
set_bit(MT_IO_FLAGS_PENDING_SLOTS, &td->mt_io_flags);
@@ -1165,6 +1176,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
minor = minor >> 1;
}
+ if (td->is_haptic_touchpad)
+ hid_haptic_pressure_increase(td->haptic, *slot->p);
+
x = hdev->quirks & HID_QUIRK_X_INVERT ?
input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->x :
*slot->x;
@@ -1366,6 +1380,9 @@ static int mt_touch_input_configured(struct hid_device *hdev,
if (cls->is_indirect)
app->mt_flags |= INPUT_MT_POINTER;
+ if (td->is_haptic_touchpad)
+ app->mt_flags |= INPUT_MT_TOTAL_FORCE;
+
if (app->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP)
app->mt_flags |= INPUT_MT_DROP_UNUSED;
@@ -1401,6 +1418,7 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct mt_device *td = hid_get_drvdata(hdev);
struct mt_application *application;
struct mt_report_data *rdata;
+ int ret;
rdata = mt_find_report_data(td, field->report);
if (!rdata) {
@@ -1463,6 +1481,11 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
if (field->physical == HID_DG_STYLUS)
hi->application = HID_DG_STYLUS;
+ ret = hid_haptic_input_mapping(hdev, td->haptic, hi, field, usage, bit,
+ max);
+ if (ret != 0)
+ return ret;
+
/* let hid-core decide for the others */
return 0;
}
@@ -1685,6 +1708,14 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
struct hid_report *report;
int ret;
+ if (td->is_haptic_touchpad && (td->mtclass.name == MT_CLS_WIN_8 ||
+ td->mtclass.name == MT_CLS_WIN_8_FORCE_MULTI_INPUT)) {
+ if (hid_haptic_input_configured(hdev, td->haptic, hi) == 0)
+ td->is_haptic_touchpad = false;
+ } else {
+ td->is_haptic_touchpad = false;
+ }
+
list_for_each_entry(report, &hi->reports, hidinput_list) {
rdata = mt_find_report_data(td, report);
if (!rdata) {
@@ -1827,6 +1858,11 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
dev_err(&hdev->dev, "cannot allocate multitouch data\n");
return -ENOMEM;
}
+ td->haptic = devm_kzalloc(&hdev->dev, sizeof(*(td->haptic)), GFP_KERNEL);
+ if (!td->haptic)
+ return -ENOMEM;
+
+ td->haptic->hdev = hdev;
td->hdev = hdev;
td->mtclass = *mtclass;
td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN;
@@ -1895,6 +1931,17 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
+ if (td->is_haptic_touchpad) {
+ if (hid_haptic_init(hdev, &td->haptic)) {
+ dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n",
+ hdev->name);
+ td->is_haptic_touchpad = false;
+ devm_kfree(&hdev->dev, td->haptic);
+ }
+ } else {
+ devm_kfree(&hdev->dev, td->haptic);
+ }
+
return 0;
}
diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c
index 1468fb11e39d..63f6eb9030d1 100644
--- a/drivers/hid/hid-playstation.c
+++ b/drivers/hid/hid-playstation.c
@@ -5,7 +5,9 @@
* Copyright (c) 2020-2022 Sony Interactive Entertainment
*/
+#include <linux/bitfield.h>
#include <linux/bits.h>
+#include <linux/cleanup.h>
#include <linux/crc32.h>
#include <linux/device.h>
#include <linux/hid.h>
@@ -36,19 +38,19 @@ enum PS_TYPE {
struct ps_device {
struct list_head list;
struct hid_device *hdev;
- spinlock_t lock;
+ spinlock_t lock; /* Sync between event handler and workqueue */
- uint32_t player_id;
+ u32 player_id;
struct power_supply_desc battery_desc;
struct power_supply *battery;
- uint8_t battery_capacity;
+ u8 battery_capacity;
int battery_status;
const char *input_dev_name; /* Name of primary input device. */
- uint8_t mac_address[6]; /* Note: stored in little endian order. */
- uint32_t hw_version;
- uint32_t fw_version;
+ u8 mac_address[6]; /* Note: stored in little endian order. */
+ u32 hw_version;
+ u32 fw_version;
int (*parse_report)(struct ps_device *dev, struct hid_report *report, u8 *data, int size);
void (*remove)(struct ps_device *dev);
@@ -110,41 +112,62 @@ struct ps_led_info {
#define DS_BUTTONS2_TOUCHPAD BIT(1)
#define DS_BUTTONS2_MIC_MUTE BIT(2)
-/* Status field of DualSense input report. */
-#define DS_STATUS_BATTERY_CAPACITY GENMASK(3, 0)
-#define DS_STATUS_CHARGING GENMASK(7, 4)
-#define DS_STATUS_CHARGING_SHIFT 4
+/* Status fields of DualSense input report. */
+#define DS_STATUS0_BATTERY_CAPACITY GENMASK(3, 0)
+#define DS_STATUS0_CHARGING GENMASK(7, 4)
+#define DS_STATUS1_HP_DETECT BIT(0)
+#define DS_STATUS1_MIC_DETECT BIT(1)
+#define DS_STATUS1_JACK_DETECT (DS_STATUS1_HP_DETECT | DS_STATUS1_MIC_DETECT)
+#define DS_STATUS1_MIC_MUTE BIT(2)
/* Feature version from DualSense Firmware Info report. */
-#define DS_FEATURE_VERSION(major, minor) ((major & 0xff) << 8 | (minor & 0xff))
-
+#define DS_FEATURE_VERSION_MINOR GENMASK(7, 0)
+#define DS_FEATURE_VERSION_MAJOR GENMASK(15, 8)
+#define DS_FEATURE_VERSION(major, minor) (FIELD_PREP(DS_FEATURE_VERSION_MAJOR, major) | \
+ FIELD_PREP(DS_FEATURE_VERSION_MINOR, minor))
/*
* Status of a DualSense touch point contact.
* Contact IDs, with highest bit set are 'inactive'
* and any associated data is then invalid.
*/
-#define DS_TOUCH_POINT_INACTIVE BIT(7)
+#define DS_TOUCH_POINT_INACTIVE BIT(7)
+#define DS_TOUCH_POINT_X_LO GENMASK(7, 0)
+#define DS_TOUCH_POINT_X_HI GENMASK(11, 8)
+#define DS_TOUCH_POINT_X(hi, lo) (FIELD_PREP(DS_TOUCH_POINT_X_HI, hi) | \
+ FIELD_PREP(DS_TOUCH_POINT_X_LO, lo))
+#define DS_TOUCH_POINT_Y_LO GENMASK(3, 0)
+#define DS_TOUCH_POINT_Y_HI GENMASK(11, 4)
+#define DS_TOUCH_POINT_Y(hi, lo) (FIELD_PREP(DS_TOUCH_POINT_Y_HI, hi) | \
+ FIELD_PREP(DS_TOUCH_POINT_Y_LO, lo))
/* Magic value required in tag field of Bluetooth output report. */
-#define DS_OUTPUT_TAG 0x10
+#define DS_OUTPUT_TAG 0x10
+#define DS_OUTPUT_SEQ_TAG GENMASK(3, 0)
+#define DS_OUTPUT_SEQ_NO GENMASK(7, 4)
/* Flags for DualSense output report. */
-#define DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION BIT(0)
-#define DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT BIT(1)
-#define DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE BIT(0)
-#define DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE BIT(1)
-#define DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE BIT(2)
-#define DS_OUTPUT_VALID_FLAG1_RELEASE_LEDS BIT(3)
-#define DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE BIT(4)
-#define DS_OUTPUT_VALID_FLAG2_LIGHTBAR_SETUP_CONTROL_ENABLE BIT(1)
-#define DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2 BIT(2)
-#define DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE BIT(4)
-#define DS_OUTPUT_LIGHTBAR_SETUP_LIGHT_OUT BIT(1)
+#define DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION BIT(0)
+#define DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT BIT(1)
+#define DS_OUTPUT_VALID_FLAG0_SPEAKER_VOLUME_ENABLE BIT(5)
+#define DS_OUTPUT_VALID_FLAG0_MIC_VOLUME_ENABLE BIT(6)
+#define DS_OUTPUT_VALID_FLAG0_AUDIO_CONTROL_ENABLE BIT(7)
+#define DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE BIT(0)
+#define DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE BIT(1)
+#define DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE BIT(2)
+#define DS_OUTPUT_VALID_FLAG1_RELEASE_LEDS BIT(3)
+#define DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE BIT(4)
+#define DS_OUTPUT_VALID_FLAG1_AUDIO_CONTROL2_ENABLE BIT(7)
+#define DS_OUTPUT_VALID_FLAG2_LIGHTBAR_SETUP_CONTROL_ENABLE BIT(1)
+#define DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2 BIT(2)
+#define DS_OUTPUT_AUDIO_FLAGS_OUTPUT_PATH_SEL GENMASK(5, 4)
+#define DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN GENMASK(2, 0)
+#define DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE BIT(4)
+#define DS_OUTPUT_LIGHTBAR_SETUP_LIGHT_OUT BIT(1)
/* DualSense hardware limits */
#define DS_ACC_RES_PER_G 8192
-#define DS_ACC_RANGE (4*DS_ACC_RES_PER_G)
+#define DS_ACC_RANGE (4 * DS_ACC_RES_PER_G)
#define DS_GYRO_RES_PER_DEG_S 1024
-#define DS_GYRO_RANGE (2048*DS_GYRO_RES_PER_DEG_S)
+#define DS_GYRO_RANGE (2048 * DS_GYRO_RES_PER_DEG_S)
#define DS_TOUCHPAD_WIDTH 1920
#define DS_TOUCHPAD_HEIGHT 1080
@@ -153,9 +176,10 @@ struct dualsense {
struct input_dev *gamepad;
struct input_dev *sensors;
struct input_dev *touchpad;
+ struct input_dev *jack;
/* Update version is used as a feature/capability version. */
- uint16_t update_version;
+ u16 update_version;
/* Calibration data for accelerometer and gyroscope. */
struct ps_calibration_data accel_calib_data[3];
@@ -163,21 +187,26 @@ struct dualsense {
/* Timestamp for sensor data */
bool sensor_timestamp_initialized;
- uint32_t prev_sensor_timestamp;
- uint32_t sensor_timestamp_us;
+ u32 prev_sensor_timestamp;
+ u32 sensor_timestamp_us;
/* Compatible rumble state */
bool use_vibration_v2;
bool update_rumble;
- uint8_t motor_left;
- uint8_t motor_right;
+ u8 motor_left;
+ u8 motor_right;
/* RGB lightbar */
struct led_classdev_mc lightbar;
bool update_lightbar;
- uint8_t lightbar_red;
- uint8_t lightbar_green;
- uint8_t lightbar_blue;
+ u8 lightbar_red;
+ u8 lightbar_green;
+ u8 lightbar_blue;
+
+ /* Audio Jack plugged state */
+ u8 plugged_state;
+ u8 prev_plugged_state;
+ bool prev_plugged_state_valid;
/* Microphone */
bool update_mic_mute;
@@ -186,90 +215,94 @@ struct dualsense {
/* Player leds */
bool update_player_leds;
- uint8_t player_leds_state;
+ u8 player_leds_state;
struct led_classdev player_leds[5];
struct work_struct output_worker;
bool output_worker_initialized;
void *output_report_dmabuf;
- uint8_t output_seq; /* Sequence number for output report. */
+ u8 output_seq; /* Sequence number for output report. */
};
struct dualsense_touch_point {
- uint8_t contact;
- uint8_t x_lo;
- uint8_t x_hi:4, y_lo:4;
- uint8_t y_hi;
+ u8 contact;
+ u8 x_lo;
+ u8 x_hi:4, y_lo:4;
+ u8 y_hi;
} __packed;
static_assert(sizeof(struct dualsense_touch_point) == 4);
/* Main DualSense input report excluding any BT/USB specific headers. */
struct dualsense_input_report {
- uint8_t x, y;
- uint8_t rx, ry;
- uint8_t z, rz;
- uint8_t seq_number;
- uint8_t buttons[4];
- uint8_t reserved[4];
+ u8 x, y;
+ u8 rx, ry;
+ u8 z, rz;
+ u8 seq_number;
+ u8 buttons[4];
+ u8 reserved[4];
/* Motion sensors */
__le16 gyro[3]; /* x, y, z */
__le16 accel[3]; /* x, y, z */
__le32 sensor_timestamp;
- uint8_t reserved2;
+ u8 reserved2;
/* Touchpad */
struct dualsense_touch_point points[2];
- uint8_t reserved3[12];
- uint8_t status;
- uint8_t reserved4[10];
+ u8 reserved3[12];
+ u8 status[3];
+ u8 reserved4[8];
} __packed;
/* Common input report size shared equals the size of the USB report minus 1 byte for ReportID. */
static_assert(sizeof(struct dualsense_input_report) == DS_INPUT_REPORT_USB_SIZE - 1);
/* Common data between DualSense BT/USB main output report. */
struct dualsense_output_report_common {
- uint8_t valid_flag0;
- uint8_t valid_flag1;
+ u8 valid_flag0;
+ u8 valid_flag1;
/* For DualShock 4 compatibility mode. */
- uint8_t motor_right;
- uint8_t motor_left;
+ u8 motor_right;
+ u8 motor_left;
/* Audio controls */
- uint8_t reserved[4];
- uint8_t mute_button_led;
+ u8 headphone_volume; /* 0x0 - 0x7f */
+ u8 speaker_volume; /* 0x0 - 0xff */
+ u8 mic_volume; /* 0x0 - 0x40 */
+ u8 audio_control;
+ u8 mute_button_led;
- uint8_t power_save_control;
- uint8_t reserved2[28];
+ u8 power_save_control;
+ u8 reserved2[27];
+ u8 audio_control2;
/* LEDs and lightbar */
- uint8_t valid_flag2;
- uint8_t reserved3[2];
- uint8_t lightbar_setup;
- uint8_t led_brightness;
- uint8_t player_leds;
- uint8_t lightbar_red;
- uint8_t lightbar_green;
- uint8_t lightbar_blue;
+ u8 valid_flag2;
+ u8 reserved3[2];
+ u8 lightbar_setup;
+ u8 led_brightness;
+ u8 player_leds;
+ u8 lightbar_red;
+ u8 lightbar_green;
+ u8 lightbar_blue;
} __packed;
static_assert(sizeof(struct dualsense_output_report_common) == 47);
struct dualsense_output_report_bt {
- uint8_t report_id; /* 0x31 */
- uint8_t seq_tag;
- uint8_t tag;
+ u8 report_id; /* 0x31 */
+ u8 seq_tag;
+ u8 tag;
struct dualsense_output_report_common common;
- uint8_t reserved[24];
+ u8 reserved[24];
__le32 crc32;
} __packed;
static_assert(sizeof(struct dualsense_output_report_bt) == DS_OUTPUT_REPORT_BT_SIZE);
struct dualsense_output_report_usb {
- uint8_t report_id; /* 0x02 */
+ u8 report_id; /* 0x02 */
struct dualsense_output_report_common common;
- uint8_t reserved[15];
+ u8 reserved[15];
} __packed;
static_assert(sizeof(struct dualsense_output_report_usb) == DS_OUTPUT_REPORT_USB_SIZE);
@@ -279,8 +312,8 @@ static_assert(sizeof(struct dualsense_output_report_usb) == DS_OUTPUT_REPORT_USB
* This structure hide the differences between the two to simplify sending output reports.
*/
struct dualsense_output_report {
- uint8_t *data; /* Start of data */
- uint8_t len; /* Size of output report */
+ u8 *data; /* Start of data */
+ u8 len; /* Size of output report */
/* Points to Bluetooth data payload in case for a Bluetooth report else NULL. */
struct dualsense_output_report_bt *bt;
@@ -315,7 +348,9 @@ struct dualsense_output_report {
* Contact IDs, with highest bit set are 'inactive'
* and any associated data is then invalid.
*/
-#define DS4_TOUCH_POINT_INACTIVE BIT(7)
+#define DS4_TOUCH_POINT_INACTIVE BIT(7)
+#define DS4_TOUCH_POINT_X(hi, lo) DS_TOUCH_POINT_X(hi, lo)
+#define DS4_TOUCH_POINT_Y(hi, lo) DS_TOUCH_POINT_Y(hi, lo)
/* Status field of DualShock4 input report. */
#define DS4_STATUS0_BATTERY_CAPACITY GENMASK(3, 0)
@@ -323,7 +358,7 @@ struct dualsense_output_report {
/* Battery status within batery_status field. */
#define DS4_BATTERY_STATUS_FULL 11
/* Status1 bit2 contains dongle connection state:
- * 0 = connectd
+ * 0 = connected
* 1 = disconnected
*/
#define DS4_STATUS1_DONGLE_STATE BIT(2)
@@ -349,9 +384,9 @@ struct dualsense_output_report {
/* DualShock4 hardware limits */
#define DS4_ACC_RES_PER_G 8192
-#define DS4_ACC_RANGE (4*DS_ACC_RES_PER_G)
+#define DS4_ACC_RANGE (4 * DS_ACC_RES_PER_G)
#define DS4_GYRO_RES_PER_DEG_S 1024
-#define DS4_GYRO_RANGE (2048*DS_GYRO_RES_PER_DEG_S)
+#define DS4_GYRO_RANGE (2048 * DS_GYRO_RES_PER_DEG_S)
#define DS4_LIGHTBAR_MAX_BLINK 255 /* 255 centiseconds */
#define DS4_TOUCHPAD_WIDTH 1920
#define DS4_TOUCHPAD_HEIGHT 942
@@ -380,26 +415,26 @@ struct dualshock4 {
/* Timestamp for sensor data */
bool sensor_timestamp_initialized;
- uint32_t prev_sensor_timestamp;
- uint32_t sensor_timestamp_us;
+ u32 prev_sensor_timestamp;
+ u32 sensor_timestamp_us;
/* Bluetooth poll interval */
bool update_bt_poll_interval;
- uint8_t bt_poll_interval;
+ u8 bt_poll_interval;
bool update_rumble;
- uint8_t motor_left;
- uint8_t motor_right;
+ u8 motor_left;
+ u8 motor_right;
/* Lightbar leds */
bool update_lightbar;
bool update_lightbar_blink;
bool lightbar_enabled; /* For use by global LED control. */
- uint8_t lightbar_red;
- uint8_t lightbar_green;
- uint8_t lightbar_blue;
- uint8_t lightbar_blink_on; /* In increments of 10ms. */
- uint8_t lightbar_blink_off; /* In increments of 10ms. */
+ u8 lightbar_red;
+ u8 lightbar_green;
+ u8 lightbar_blue;
+ u8 lightbar_blink_on; /* In increments of 10ms. */
+ u8 lightbar_blink_off; /* In increments of 10ms. */
struct led_classdev lightbar_leds[4];
struct work_struct output_worker;
@@ -408,88 +443,88 @@ struct dualshock4 {
};
struct dualshock4_touch_point {
- uint8_t contact;
- uint8_t x_lo;
- uint8_t x_hi:4, y_lo:4;
- uint8_t y_hi;
+ u8 contact;
+ u8 x_lo;
+ u8 x_hi:4, y_lo:4;
+ u8 y_hi;
} __packed;
static_assert(sizeof(struct dualshock4_touch_point) == 4);
struct dualshock4_touch_report {
- uint8_t timestamp;
+ u8 timestamp;
struct dualshock4_touch_point points[2];
} __packed;
static_assert(sizeof(struct dualshock4_touch_report) == 9);
/* Main DualShock4 input report excluding any BT/USB specific headers. */
struct dualshock4_input_report_common {
- uint8_t x, y;
- uint8_t rx, ry;
- uint8_t buttons[3];
- uint8_t z, rz;
+ u8 x, y;
+ u8 rx, ry;
+ u8 buttons[3];
+ u8 z, rz;
/* Motion sensors */
__le16 sensor_timestamp;
- uint8_t sensor_temperature;
+ u8 sensor_temperature;
__le16 gyro[3]; /* x, y, z */
__le16 accel[3]; /* x, y, z */
- uint8_t reserved2[5];
+ u8 reserved2[5];
- uint8_t status[2];
- uint8_t reserved3;
+ u8 status[2];
+ u8 reserved3;
} __packed;
static_assert(sizeof(struct dualshock4_input_report_common) == 32);
struct dualshock4_input_report_usb {
- uint8_t report_id; /* 0x01 */
+ u8 report_id; /* 0x01 */
struct dualshock4_input_report_common common;
- uint8_t num_touch_reports;
+ u8 num_touch_reports;
struct dualshock4_touch_report touch_reports[3];
- uint8_t reserved[3];
+ u8 reserved[3];
} __packed;
static_assert(sizeof(struct dualshock4_input_report_usb) == DS4_INPUT_REPORT_USB_SIZE);
struct dualshock4_input_report_bt {
- uint8_t report_id; /* 0x11 */
- uint8_t reserved[2];
+ u8 report_id; /* 0x11 */
+ u8 reserved[2];
struct dualshock4_input_report_common common;
- uint8_t num_touch_reports;
+ u8 num_touch_reports;
struct dualshock4_touch_report touch_reports[4]; /* BT has 4 compared to 3 for USB */
- uint8_t reserved2[2];
+ u8 reserved2[2];
__le32 crc32;
} __packed;
static_assert(sizeof(struct dualshock4_input_report_bt) == DS4_INPUT_REPORT_BT_SIZE);
/* Common data between Bluetooth and USB DualShock4 output reports. */
struct dualshock4_output_report_common {
- uint8_t valid_flag0;
- uint8_t valid_flag1;
+ u8 valid_flag0;
+ u8 valid_flag1;
- uint8_t reserved;
+ u8 reserved;
- uint8_t motor_right;
- uint8_t motor_left;
+ u8 motor_right;
+ u8 motor_left;
- uint8_t lightbar_red;
- uint8_t lightbar_green;
- uint8_t lightbar_blue;
- uint8_t lightbar_blink_on;
- uint8_t lightbar_blink_off;
+ u8 lightbar_red;
+ u8 lightbar_green;
+ u8 lightbar_blue;
+ u8 lightbar_blink_on;
+ u8 lightbar_blink_off;
} __packed;
struct dualshock4_output_report_usb {
- uint8_t report_id; /* 0x5 */
+ u8 report_id; /* 0x5 */
struct dualshock4_output_report_common common;
- uint8_t reserved[21];
+ u8 reserved[21];
} __packed;
static_assert(sizeof(struct dualshock4_output_report_usb) == DS4_OUTPUT_REPORT_USB_SIZE);
struct dualshock4_output_report_bt {
- uint8_t report_id; /* 0x11 */
- uint8_t hw_control;
- uint8_t audio_control;
+ u8 report_id; /* 0x11 */
+ u8 hw_control;
+ u8 audio_control;
struct dualshock4_output_report_common common;
- uint8_t reserved[61];
+ u8 reserved[61];
__le32 crc32;
} __packed;
static_assert(sizeof(struct dualshock4_output_report_bt) == DS4_OUTPUT_REPORT_BT_SIZE);
@@ -500,8 +535,8 @@ static_assert(sizeof(struct dualshock4_output_report_bt) == DS4_OUTPUT_REPORT_BT
* This structure hide the differences between the two to simplify sending output reports.
*/
struct dualshock4_output_report {
- uint8_t *data; /* Start of data */
- uint8_t len; /* Size of output report */
+ u8 *data; /* Start of data */
+ u8 len; /* Size of output report */
/* Points to Bluetooth data payload in case for a Bluetooth report else NULL. */
struct dualshock4_output_report_bt *bt;
@@ -540,7 +575,7 @@ static const struct {int x; int y; } ps_gamepad_hat_mapping[] = {
static int dualshock4_get_calibration_data(struct dualshock4 *ds4);
static inline void dualsense_schedule_work(struct dualsense *ds);
static inline void dualshock4_schedule_work(struct dualshock4 *ds4);
-static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t green, uint8_t blue);
+static void dualsense_set_lightbar(struct dualsense *ds, u8 red, u8 green, u8 blue);
static void dualshock4_set_default_lightbar_colors(struct dualshock4 *ds4);
/*
@@ -552,26 +587,25 @@ static int ps_devices_list_add(struct ps_device *dev)
{
struct ps_device *entry;
- mutex_lock(&ps_devices_lock);
+ guard(mutex)(&ps_devices_lock);
+
list_for_each_entry(entry, &ps_devices_list, list) {
if (!memcmp(entry->mac_address, dev->mac_address, sizeof(dev->mac_address))) {
hid_err(dev->hdev, "Duplicate device found for MAC address %pMR.\n",
- dev->mac_address);
- mutex_unlock(&ps_devices_lock);
+ dev->mac_address);
return -EEXIST;
}
}
list_add_tail(&dev->list, &ps_devices_list);
- mutex_unlock(&ps_devices_lock);
return 0;
}
static int ps_devices_list_remove(struct ps_device *dev)
{
- mutex_lock(&ps_devices_lock);
+ guard(mutex)(&ps_devices_lock);
+
list_del(&dev->list);
- mutex_unlock(&ps_devices_lock);
return 0;
}
@@ -593,7 +627,8 @@ static void ps_device_release_player_id(struct ps_device *dev)
dev->player_id = U32_MAX;
}
-static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, const char *name_suffix)
+static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev,
+ const char *name_suffix)
{
struct input_dev *input_dev;
@@ -608,8 +643,8 @@ static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, const ch
input_dev->uniq = hdev->uniq;
if (name_suffix) {
- input_dev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", hdev->name,
- name_suffix);
+ input_dev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s",
+ hdev->name, name_suffix);
if (!input_dev->name)
return ERR_PTR(-ENOMEM);
} else {
@@ -629,19 +664,18 @@ static enum power_supply_property ps_power_supply_props[] = {
};
static int ps_battery_get_property(struct power_supply *psy,
- enum power_supply_property psp,
- union power_supply_propval *val)
+ enum power_supply_property psp,
+ union power_supply_propval *val)
{
struct ps_device *dev = power_supply_get_drvdata(psy);
- uint8_t battery_capacity;
+ u8 battery_capacity;
int battery_status;
- unsigned long flags;
int ret = 0;
- spin_lock_irqsave(&dev->lock, flags);
- battery_capacity = dev->battery_capacity;
- battery_status = dev->battery_status;
- spin_unlock_irqrestore(&dev->lock, flags);
+ scoped_guard(spinlock_irqsave, &dev->lock) {
+ battery_capacity = dev->battery_capacity;
+ battery_status = dev->battery_status;
+ }
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
@@ -675,7 +709,7 @@ static int ps_device_register_battery(struct ps_device *dev)
dev->battery_desc.num_properties = ARRAY_SIZE(ps_power_supply_props);
dev->battery_desc.get_property = ps_battery_get_property;
dev->battery_desc.name = devm_kasprintf(&dev->hdev->dev, GFP_KERNEL,
- "ps-controller-battery-%pMR", dev->mac_address);
+ "ps-controller-battery-%pMR", dev->mac_address);
if (!dev->battery_desc.name)
return -ENOMEM;
@@ -697,9 +731,9 @@ static int ps_device_register_battery(struct ps_device *dev)
}
/* Compute crc32 of HID data and compare against expected CRC. */
-static bool ps_check_crc32(uint8_t seed, uint8_t *data, size_t len, uint32_t report_crc)
+static bool ps_check_crc32(u8 seed, u8 *data, size_t len, u32 report_crc)
{
- uint32_t crc;
+ u32 crc;
crc = crc32_le(0xFFFFFFFF, &seed, 1);
crc = ~crc32_le(crc, data, len);
@@ -707,8 +741,9 @@ static bool ps_check_crc32(uint8_t seed, uint8_t *data, size_t len, uint32_t rep
return crc == report_crc;
}
-static struct input_dev *ps_gamepad_create(struct hid_device *hdev,
- int (*play_effect)(struct input_dev *, void *, struct ff_effect *))
+static struct input_dev *
+ps_gamepad_create(struct hid_device *hdev,
+ int (*play_effect)(struct input_dev *, void *, struct ff_effect *))
{
struct input_dev *gamepad;
unsigned int i;
@@ -745,8 +780,8 @@ static struct input_dev *ps_gamepad_create(struct hid_device *hdev,
return gamepad;
}
-static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *buf, size_t size,
- bool check_crc)
+static int ps_get_report(struct hid_device *hdev, u8 report_id, u8 *buf,
+ size_t size, bool check_crc)
{
int ret;
@@ -769,8 +804,8 @@ static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *bu
if (hdev->bus == BUS_BLUETOOTH && check_crc) {
/* Last 4 bytes contains crc32. */
- uint8_t crc_offset = size - 4;
- uint32_t report_crc = get_unaligned_le32(&buf[crc_offset]);
+ u8 crc_offset = size - 4;
+ u32 report_crc = get_unaligned_le32(&buf[crc_offset]);
if (!ps_check_crc32(PS_FEATURE_CRC32_SEED, buf, crc_offset, report_crc)) {
hid_err(hdev, "CRC check failed for reportID=%d\n", report_id);
@@ -782,17 +817,20 @@ static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *bu
}
static int ps_led_register(struct ps_device *ps_dev, struct led_classdev *led,
- const struct ps_led_info *led_info)
+ const struct ps_led_info *led_info)
{
int ret;
if (led_info->name) {
- led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL,
- "%s:%s:%s", ps_dev->input_dev_name, led_info->color, led_info->name);
+ led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL, "%s:%s:%s",
+ ps_dev->input_dev_name, led_info->color,
+ led_info->name);
} else {
- /* Backwards compatible mode for hid-sony, but not compliant with LED class spec. */
- led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL,
- "%s:%s", ps_dev->input_dev_name, led_info->color);
+ /* Backwards compatible mode for hid-sony, but not compliant
+ * with LED class spec.
+ */
+ led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL, "%s:%s",
+ ps_dev->input_dev_name, led_info->color);
}
if (!led->name)
@@ -816,7 +854,7 @@ static int ps_led_register(struct ps_device *ps_dev, struct led_classdev *led,
/* Register a DualSense/DualShock4 RGB lightbar represented by a multicolor LED. */
static int ps_lightbar_register(struct ps_device *ps_dev, struct led_classdev_mc *lightbar_mc_dev,
- int (*brightness_set)(struct led_classdev *, enum led_brightness))
+ int (*brightness_set)(struct led_classdev *, enum led_brightness))
{
struct hid_device *hdev = ps_dev->hdev;
struct mc_subled *mc_led_info;
@@ -837,7 +875,7 @@ static int ps_lightbar_register(struct ps_device *ps_dev, struct led_classdev_mc
led_cdev = &lightbar_mc_dev->led_cdev;
led_cdev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s:rgb:indicator",
- ps_dev->input_dev_name);
+ ps_dev->input_dev_name);
if (!led_cdev->name)
return -ENOMEM;
led_cdev->brightness = 255;
@@ -853,8 +891,8 @@ static int ps_lightbar_register(struct ps_device *ps_dev, struct led_classdev_mc
return 0;
}
-static struct input_dev *ps_sensors_create(struct hid_device *hdev, int accel_range, int accel_res,
- int gyro_range, int gyro_res)
+static struct input_dev *ps_sensors_create(struct hid_device *hdev, int accel_range,
+ int accel_res, int gyro_range, int gyro_res)
{
struct input_dev *sensors;
int ret;
@@ -890,8 +928,8 @@ static struct input_dev *ps_sensors_create(struct hid_device *hdev, int accel_ra
return sensors;
}
-static struct input_dev *ps_touchpad_create(struct hid_device *hdev, int width, int height,
- unsigned int num_contacts)
+static struct input_dev *ps_touchpad_create(struct hid_device *hdev, int width,
+ int height, unsigned int num_contacts)
{
struct input_dev *touchpad;
int ret;
@@ -918,9 +956,27 @@ static struct input_dev *ps_touchpad_create(struct hid_device *hdev, int width,
return touchpad;
}
+static struct input_dev *ps_headset_jack_create(struct hid_device *hdev)
+{
+ struct input_dev *jack;
+ int ret;
+
+ jack = ps_allocate_input_dev(hdev, "Headset Jack");
+ if (IS_ERR(jack))
+ return ERR_CAST(jack);
+
+ input_set_capability(jack, EV_SW, SW_HEADPHONE_INSERT);
+ input_set_capability(jack, EV_SW, SW_MICROPHONE_INSERT);
+
+ ret = input_register_device(jack);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return jack;
+}
+
static ssize_t firmware_version_show(struct device *dev,
- struct device_attribute
- *attr, char *buf)
+ struct device_attribute *attr, char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
struct ps_device *ps_dev = hid_get_drvdata(hdev);
@@ -931,8 +987,7 @@ static ssize_t firmware_version_show(struct device *dev,
static DEVICE_ATTR_RO(firmware_version);
static ssize_t hardware_version_show(struct device *dev,
- struct device_attribute
- *attr, char *buf)
+ struct device_attribute *attr, char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
struct ps_device *ps_dev = hid_get_drvdata(hdev);
@@ -963,14 +1018,14 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
int range_2g;
int ret = 0;
int i;
- uint8_t *buf;
+ u8 *buf;
buf = kzalloc(DS_FEATURE_REPORT_CALIBRATION_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_CALIBRATION, buf,
- DS_FEATURE_REPORT_CALIBRATION_SIZE, true);
+ DS_FEATURE_REPORT_CALIBRATION_SIZE, true);
if (ret) {
hid_err(ds->base.hdev, "Failed to retrieve DualSense calibration info: %d\n", ret);
goto err_free;
@@ -1001,19 +1056,19 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
speed_2x = (gyro_speed_plus + gyro_speed_minus);
ds->gyro_calib_data[0].abs_code = ABS_RX;
ds->gyro_calib_data[0].bias = 0;
- ds->gyro_calib_data[0].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S;
+ ds->gyro_calib_data[0].sens_numer = speed_2x * DS_GYRO_RES_PER_DEG_S;
ds->gyro_calib_data[0].sens_denom = abs(gyro_pitch_plus - gyro_pitch_bias) +
abs(gyro_pitch_minus - gyro_pitch_bias);
ds->gyro_calib_data[1].abs_code = ABS_RY;
ds->gyro_calib_data[1].bias = 0;
- ds->gyro_calib_data[1].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S;
+ ds->gyro_calib_data[1].sens_numer = speed_2x * DS_GYRO_RES_PER_DEG_S;
ds->gyro_calib_data[1].sens_denom = abs(gyro_yaw_plus - gyro_yaw_bias) +
abs(gyro_yaw_minus - gyro_yaw_bias);
ds->gyro_calib_data[2].abs_code = ABS_RZ;
ds->gyro_calib_data[2].bias = 0;
- ds->gyro_calib_data[2].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S;
+ ds->gyro_calib_data[2].sens_numer = speed_2x * DS_GYRO_RES_PER_DEG_S;
ds->gyro_calib_data[2].sens_denom = abs(gyro_roll_plus - gyro_roll_bias) +
abs(gyro_roll_minus - gyro_roll_bias);
@@ -1024,8 +1079,9 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
*/
for (i = 0; i < ARRAY_SIZE(ds->gyro_calib_data); i++) {
if (ds->gyro_calib_data[i].sens_denom == 0) {
- hid_warn(hdev, "Invalid gyro calibration data for axis (%d), disabling calibration.",
- ds->gyro_calib_data[i].abs_code);
+ hid_warn(hdev,
+ "Invalid gyro calibration data for axis (%d), disabling calibration.",
+ ds->gyro_calib_data[i].abs_code);
ds->gyro_calib_data[i].bias = 0;
ds->gyro_calib_data[i].sens_numer = DS_GYRO_RANGE;
ds->gyro_calib_data[i].sens_denom = S16_MAX;
@@ -1039,19 +1095,19 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
range_2g = acc_x_plus - acc_x_minus;
ds->accel_calib_data[0].abs_code = ABS_X;
ds->accel_calib_data[0].bias = acc_x_plus - range_2g / 2;
- ds->accel_calib_data[0].sens_numer = 2*DS_ACC_RES_PER_G;
+ ds->accel_calib_data[0].sens_numer = 2 * DS_ACC_RES_PER_G;
ds->accel_calib_data[0].sens_denom = range_2g;
range_2g = acc_y_plus - acc_y_minus;
ds->accel_calib_data[1].abs_code = ABS_Y;
ds->accel_calib_data[1].bias = acc_y_plus - range_2g / 2;
- ds->accel_calib_data[1].sens_numer = 2*DS_ACC_RES_PER_G;
+ ds->accel_calib_data[1].sens_numer = 2 * DS_ACC_RES_PER_G;
ds->accel_calib_data[1].sens_denom = range_2g;
range_2g = acc_z_plus - acc_z_minus;
ds->accel_calib_data[2].abs_code = ABS_Z;
ds->accel_calib_data[2].bias = acc_z_plus - range_2g / 2;
- ds->accel_calib_data[2].sens_numer = 2*DS_ACC_RES_PER_G;
+ ds->accel_calib_data[2].sens_numer = 2 * DS_ACC_RES_PER_G;
ds->accel_calib_data[2].sens_denom = range_2g;
/*
@@ -1061,8 +1117,9 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
*/
for (i = 0; i < ARRAY_SIZE(ds->accel_calib_data); i++) {
if (ds->accel_calib_data[i].sens_denom == 0) {
- hid_warn(hdev, "Invalid accelerometer calibration data for axis (%d), disabling calibration.",
- ds->accel_calib_data[i].abs_code);
+ hid_warn(hdev,
+ "Invalid accelerometer calibration data for axis (%d), disabling calibration.",
+ ds->accel_calib_data[i].abs_code);
ds->accel_calib_data[i].bias = 0;
ds->accel_calib_data[i].sens_numer = DS_ACC_RANGE;
ds->accel_calib_data[i].sens_denom = S16_MAX;
@@ -1074,10 +1131,9 @@ err_free:
return ret;
}
-
static int dualsense_get_firmware_info(struct dualsense *ds)
{
- uint8_t *buf;
+ u8 *buf;
int ret;
buf = kzalloc(DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE, GFP_KERNEL);
@@ -1085,7 +1141,7 @@ static int dualsense_get_firmware_info(struct dualsense *ds)
return -ENOMEM;
ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_FIRMWARE_INFO, buf,
- DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE, true);
+ DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE, true);
if (ret) {
hid_err(ds->base.hdev, "Failed to retrieve DualSense firmware info: %d\n", ret);
goto err_free;
@@ -1110,7 +1166,7 @@ err_free:
static int dualsense_get_mac_address(struct dualsense *ds)
{
- uint8_t *buf;
+ u8 *buf;
int ret = 0;
buf = kzalloc(DS_FEATURE_REPORT_PAIRING_INFO_SIZE, GFP_KERNEL);
@@ -1118,7 +1174,7 @@ static int dualsense_get_mac_address(struct dualsense *ds)
return -ENOMEM;
ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_PAIRING_INFO, buf,
- DS_FEATURE_REPORT_PAIRING_INFO_SIZE, true);
+ DS_FEATURE_REPORT_PAIRING_INFO_SIZE, true);
if (ret) {
hid_err(ds->base.hdev, "Failed to retrieve DualSense pairing info: %d\n", ret);
goto err_free;
@@ -1132,11 +1188,11 @@ err_free:
}
static int dualsense_lightbar_set_brightness(struct led_classdev *cdev,
- enum led_brightness brightness)
+ enum led_brightness brightness)
{
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
struct dualsense *ds = container_of(mc_cdev, struct dualsense, lightbar);
- uint8_t red, green, blue;
+ u8 red, green, blue;
led_mc_calc_color_components(mc_cdev, brightness);
red = mc_cdev->subled_info[0].brightness;
@@ -1159,27 +1215,25 @@ static int dualsense_player_led_set_brightness(struct led_classdev *led, enum le
{
struct hid_device *hdev = to_hid_device(led->dev->parent);
struct dualsense *ds = hid_get_drvdata(hdev);
- unsigned long flags;
unsigned int led_index;
- spin_lock_irqsave(&ds->base.lock, flags);
-
- led_index = led - ds->player_leds;
- if (value == LED_OFF)
- ds->player_leds_state &= ~BIT(led_index);
- else
- ds->player_leds_state |= BIT(led_index);
+ scoped_guard(spinlock_irqsave, &ds->base.lock) {
+ led_index = led - ds->player_leds;
+ if (value == LED_OFF)
+ ds->player_leds_state &= ~BIT(led_index);
+ else
+ ds->player_leds_state |= BIT(led_index);
- ds->update_player_leds = true;
- spin_unlock_irqrestore(&ds->base.lock, flags);
+ ds->update_player_leds = true;
+ }
dualsense_schedule_work(ds);
return 0;
}
-static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_output_report *rp,
- void *buf)
+static void dualsense_init_output_report(struct dualsense *ds,
+ struct dualsense_output_report *rp, void *buf)
{
struct hid_device *hdev = ds->base.hdev;
@@ -1194,7 +1248,8 @@ static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_
* Highest 4-bit is a sequence number, which needs to be increased
* every report. Lowest 4-bit is tag and can be zero for now.
*/
- bt->seq_tag = (ds->output_seq << 4) | 0x0;
+ bt->seq_tag = FIELD_PREP(DS_OUTPUT_SEQ_NO, ds->output_seq) |
+ FIELD_PREP(DS_OUTPUT_SEQ_TAG, 0x0);
if (++ds->output_seq == 16)
ds->output_seq = 0;
@@ -1219,12 +1274,10 @@ static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_
static inline void dualsense_schedule_work(struct dualsense *ds)
{
- unsigned long flags;
-
- spin_lock_irqsave(&ds->base.lock, flags);
- if (ds->output_worker_initialized)
- schedule_work(&ds->output_worker);
- spin_unlock_irqrestore(&ds->base.lock, flags);
+ /* Using scoped_guard() instead of guard() to make sparse happy */
+ scoped_guard(spinlock_irqsave, &ds->base.lock)
+ if (ds->output_worker_initialized)
+ schedule_work(&ds->output_worker);
}
/*
@@ -1232,14 +1285,14 @@ static inline void dualsense_schedule_work(struct dualsense *ds)
* for Bluetooth reports.
*/
static void dualsense_send_output_report(struct dualsense *ds,
- struct dualsense_output_report *report)
+ struct dualsense_output_report *report)
{
struct hid_device *hdev = ds->base.hdev;
/* Bluetooth packets need to be signed with a CRC in the last 4 bytes. */
if (report->bt) {
- uint32_t crc;
- uint8_t seed = PS_OUTPUT_CRC32_SEED;
+ u32 crc;
+ u8 seed = PS_OUTPUT_CRC32_SEED;
crc = crc32_le(0xFFFFFFFF, &seed, 1);
crc = ~crc32_le(crc, report->data, report->len - 4);
@@ -1255,74 +1308,125 @@ static void dualsense_output_worker(struct work_struct *work)
struct dualsense *ds = container_of(work, struct dualsense, output_worker);
struct dualsense_output_report report;
struct dualsense_output_report_common *common;
- unsigned long flags;
dualsense_init_output_report(ds, &report, ds->output_report_dmabuf);
common = report.common;
- spin_lock_irqsave(&ds->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds->base.lock) {
+ if (ds->update_rumble) {
+ /* Select classic rumble style haptics and enable it. */
+ common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT;
+ if (ds->use_vibration_v2)
+ common->valid_flag2 |= DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2;
+ else
+ common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION;
+ common->motor_left = ds->motor_left;
+ common->motor_right = ds->motor_right;
+ ds->update_rumble = false;
+ }
- if (ds->update_rumble) {
- /* Select classic rumble style haptics and enable it. */
- common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT;
- if (ds->use_vibration_v2)
- common->valid_flag2 |= DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2;
- else
- common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION;
- common->motor_left = ds->motor_left;
- common->motor_right = ds->motor_right;
- ds->update_rumble = false;
- }
+ if (ds->update_lightbar) {
+ common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE;
+ common->lightbar_red = ds->lightbar_red;
+ common->lightbar_green = ds->lightbar_green;
+ common->lightbar_blue = ds->lightbar_blue;
- if (ds->update_lightbar) {
- common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE;
- common->lightbar_red = ds->lightbar_red;
- common->lightbar_green = ds->lightbar_green;
- common->lightbar_blue = ds->lightbar_blue;
+ ds->update_lightbar = false;
+ }
- ds->update_lightbar = false;
- }
+ if (ds->update_player_leds) {
+ common->valid_flag1 |=
+ DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE;
+ common->player_leds = ds->player_leds_state;
- if (ds->update_player_leds) {
- common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE;
- common->player_leds = ds->player_leds_state;
+ ds->update_player_leds = false;
+ }
- ds->update_player_leds = false;
- }
+ if (ds->plugged_state != ds->prev_plugged_state) {
+ u8 val = ds->plugged_state & DS_STATUS1_HP_DETECT;
+
+ if (val != (ds->prev_plugged_state & DS_STATUS1_HP_DETECT)) {
+ common->valid_flag0 = DS_OUTPUT_VALID_FLAG0_AUDIO_CONTROL_ENABLE;
+ /*
+ * _--------> Output path setup in audio_flag0
+ * / _------> Headphone (HP) Left channel sink
+ * | / _----> Headphone (HP) Right channel sink
+ * | | / _--> Internal Speaker (SP) sink
+ * | | | /
+ * | | | | L/R - Left/Right channel source
+ * 0 L-R X X - Unrouted (muted) channel source
+ * 1 L-L X
+ * 2 L-L R
+ * 3 X-X R
+ */
+ if (val) {
+ /* Mute SP and route L+R channels to HP */
+ common->audio_control = 0;
+ } else {
+ /* Mute HP and route R channel to SP */
+ common->audio_control =
+ FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS_OUTPUT_PATH_SEL,
+ 0x3);
+ /*
+ * Set SP hardware volume to 100%.
+ * Note the accepted range seems to be [0x3d..0x64]
+ */
+ common->valid_flag0 |=
+ DS_OUTPUT_VALID_FLAG0_SPEAKER_VOLUME_ENABLE;
+ common->speaker_volume = 0x64;
+ /* Set SP preamp gain to +6dB */
+ common->valid_flag1 =
+ DS_OUTPUT_VALID_FLAG1_AUDIO_CONTROL2_ENABLE;
+ common->audio_control2 =
+ FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN,
+ 0x2);
+ }
- if (ds->update_mic_mute) {
- common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE;
- common->mute_button_led = ds->mic_muted;
+ input_report_switch(ds->jack, SW_HEADPHONE_INSERT, val);
+ }
- if (ds->mic_muted) {
- /* Disable microphone */
- common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE;
- common->power_save_control |= DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE;
- } else {
- /* Enable microphone */
- common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE;
- common->power_save_control &= ~DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE;
+ val = ds->plugged_state & DS_STATUS1_MIC_DETECT;
+ if (val != (ds->prev_plugged_state & DS_STATUS1_MIC_DETECT))
+ input_report_switch(ds->jack, SW_MICROPHONE_INSERT, val);
+
+ input_sync(ds->jack);
+ ds->prev_plugged_state = ds->plugged_state;
}
- ds->update_mic_mute = false;
- }
+ if (ds->update_mic_mute) {
+ common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE;
+ common->mute_button_led = ds->mic_muted;
+
+ if (ds->mic_muted) {
+ /* Disable microphone */
+ common->valid_flag1 |=
+ DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE;
+ common->power_save_control |= DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE;
+ } else {
+ /* Enable microphone */
+ common->valid_flag1 |=
+ DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE;
+ common->power_save_control &=
+ ~DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE;
+ }
- spin_unlock_irqrestore(&ds->base.lock, flags);
+ ds->update_mic_mute = false;
+ }
+ }
dualsense_send_output_report(ds, &report);
}
static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *report,
- u8 *data, int size)
+ u8 *data, int size)
{
struct hid_device *hdev = ps_dev->hdev;
struct dualsense *ds = container_of(ps_dev, struct dualsense, base);
struct dualsense_input_report *ds_report;
- uint8_t battery_data, battery_capacity, charging_status, value;
+ u8 battery_data, battery_capacity, charging_status, value;
int battery_status;
- uint32_t sensor_timestamp;
+ u32 sensor_timestamp;
bool btn_mic_state;
- unsigned long flags;
int i;
/*
@@ -1331,12 +1435,12 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
* the full report using reportID 49.
*/
if (hdev->bus == BUS_USB && report->id == DS_INPUT_REPORT_USB &&
- size == DS_INPUT_REPORT_USB_SIZE) {
+ size == DS_INPUT_REPORT_USB_SIZE) {
ds_report = (struct dualsense_input_report *)&data[1];
} else if (hdev->bus == BUS_BLUETOOTH && report->id == DS_INPUT_REPORT_BT &&
- size == DS_INPUT_REPORT_BT_SIZE) {
+ size == DS_INPUT_REPORT_BT_SIZE) {
/* Last 4 bytes of input report contain crc32 */
- uint32_t report_crc = get_unaligned_le32(&data[size - 4]);
+ u32 report_crc = get_unaligned_le32(&data[size - 4]);
if (!ps_check_crc32(PS_INPUT_CRC32_SEED, data, size - 4, report_crc)) {
hid_err(hdev, "DualSense input CRC's check failed\n");
@@ -1384,16 +1488,42 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
*/
btn_mic_state = !!(ds_report->buttons[2] & DS_BUTTONS2_MIC_MUTE);
if (btn_mic_state && !ds->last_btn_mic_state) {
- spin_lock_irqsave(&ps_dev->lock, flags);
- ds->update_mic_mute = true;
- ds->mic_muted = !ds->mic_muted; /* toggle */
- spin_unlock_irqrestore(&ps_dev->lock, flags);
+ scoped_guard(spinlock_irqsave, &ps_dev->lock) {
+ ds->update_mic_mute = true;
+ ds->mic_muted = !ds->mic_muted; /* toggle */
+ }
/* Schedule updating of microphone state at hardware level. */
dualsense_schedule_work(ds);
}
ds->last_btn_mic_state = btn_mic_state;
+ /*
+ * Parse HP/MIC plugged state data for USB use case, since Bluetooth
+ * audio is currently not supported.
+ */
+ if (hdev->bus == BUS_USB) {
+ value = ds_report->status[1] & DS_STATUS1_JACK_DETECT;
+
+ if (!ds->prev_plugged_state_valid) {
+ /* Initial handling of the plugged state report */
+ scoped_guard(spinlock_irqsave, &ps_dev->lock) {
+ ds->plugged_state = (~value) & DS_STATUS1_JACK_DETECT;
+ ds->prev_plugged_state_valid = true;
+ }
+ }
+
+ if (value != ds->plugged_state) {
+ scoped_guard(spinlock_irqsave, &ps_dev->lock) {
+ ds->prev_plugged_state = ds->plugged_state;
+ ds->plugged_state = value;
+ }
+
+ /* Schedule audio routing towards active endpoint. */
+ dualsense_schedule_work(ds);
+ }
+ }
+
/* Parse and calibrate gyroscope data. */
for (i = 0; i < ARRAY_SIZE(ds_report->gyro); i++) {
int raw_data = (short)le16_to_cpu(ds_report->gyro[i]);
@@ -1419,7 +1549,7 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
ds->sensor_timestamp_us = DIV_ROUND_CLOSEST(sensor_timestamp, 3);
ds->sensor_timestamp_initialized = true;
} else {
- uint32_t delta;
+ u32 delta;
if (ds->prev_sensor_timestamp > sensor_timestamp)
delta = (U32_MAX - ds->prev_sensor_timestamp + sensor_timestamp + 1);
@@ -1439,19 +1569,18 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
input_mt_report_slot_state(ds->touchpad, MT_TOOL_FINGER, active);
if (active) {
- int x = (point->x_hi << 8) | point->x_lo;
- int y = (point->y_hi << 4) | point->y_lo;
-
- input_report_abs(ds->touchpad, ABS_MT_POSITION_X, x);
- input_report_abs(ds->touchpad, ABS_MT_POSITION_Y, y);
+ input_report_abs(ds->touchpad, ABS_MT_POSITION_X,
+ DS_TOUCH_POINT_X(point->x_hi, point->x_lo));
+ input_report_abs(ds->touchpad, ABS_MT_POSITION_Y,
+ DS_TOUCH_POINT_Y(point->y_hi, point->y_lo));
}
}
input_mt_sync_frame(ds->touchpad);
input_report_key(ds->touchpad, BTN_LEFT, ds_report->buttons[2] & DS_BUTTONS2_TOUCHPAD);
input_sync(ds->touchpad);
- battery_data = ds_report->status & DS_STATUS_BATTERY_CAPACITY;
- charging_status = (ds_report->status & DS_STATUS_CHARGING) >> DS_STATUS_CHARGING_SHIFT;
+ battery_data = FIELD_GET(DS_STATUS0_BATTERY_CAPACITY, ds_report->status[0]);
+ charging_status = FIELD_GET(DS_STATUS0_CHARGING, ds_report->status[0]);
switch (charging_status) {
case 0x0:
@@ -1481,10 +1610,10 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
}
- spin_lock_irqsave(&ps_dev->lock, flags);
- ps_dev->battery_capacity = battery_capacity;
- ps_dev->battery_status = battery_status;
- spin_unlock_irqrestore(&ps_dev->lock, flags);
+ scoped_guard(spinlock_irqsave, &ps_dev->lock) {
+ ps_dev->battery_capacity = battery_capacity;
+ ps_dev->battery_status = battery_status;
+ }
return 0;
}
@@ -1493,16 +1622,15 @@ static int dualsense_play_effect(struct input_dev *dev, void *data, struct ff_ef
{
struct hid_device *hdev = input_get_drvdata(dev);
struct dualsense *ds = hid_get_drvdata(hdev);
- unsigned long flags;
if (effect->type != FF_RUMBLE)
return 0;
- spin_lock_irqsave(&ds->base.lock, flags);
- ds->update_rumble = true;
- ds->motor_left = effect->u.rumble.strong_magnitude / 256;
- ds->motor_right = effect->u.rumble.weak_magnitude / 256;
- spin_unlock_irqrestore(&ds->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds->base.lock) {
+ ds->update_rumble = true;
+ ds->motor_left = effect->u.rumble.strong_magnitude / 256;
+ ds->motor_right = effect->u.rumble.weak_magnitude / 256;
+ }
dualsense_schedule_work(ds);
return 0;
@@ -1511,11 +1639,9 @@ static int dualsense_play_effect(struct input_dev *dev, void *data, struct ff_ef
static void dualsense_remove(struct ps_device *ps_dev)
{
struct dualsense *ds = container_of(ps_dev, struct dualsense, base);
- unsigned long flags;
- spin_lock_irqsave(&ds->base.lock, flags);
- ds->output_worker_initialized = false;
- spin_unlock_irqrestore(&ds->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds->base.lock)
+ ds->output_worker_initialized = false;
cancel_work_sync(&ds->output_worker);
}
@@ -1523,9 +1649,9 @@ static void dualsense_remove(struct ps_device *ps_dev)
static int dualsense_reset_leds(struct dualsense *ds)
{
struct dualsense_output_report report;
- uint8_t *buf;
+ struct dualsense_output_report_bt *buf;
- buf = kzalloc(sizeof(struct dualsense_output_report_bt), GFP_KERNEL);
+ buf = kzalloc(sizeof(*buf), GFP_KERNEL);
if (!buf)
return -ENOMEM;
@@ -1545,16 +1671,14 @@ static int dualsense_reset_leds(struct dualsense *ds)
return 0;
}
-static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t green, uint8_t blue)
+static void dualsense_set_lightbar(struct dualsense *ds, u8 red, u8 green, u8 blue)
{
- unsigned long flags;
-
- spin_lock_irqsave(&ds->base.lock, flags);
- ds->update_lightbar = true;
- ds->lightbar_red = red;
- ds->lightbar_green = green;
- ds->lightbar_blue = blue;
- spin_unlock_irqrestore(&ds->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds->base.lock) {
+ ds->update_lightbar = true;
+ ds->lightbar_red = red;
+ ds->lightbar_green = green;
+ ds->lightbar_blue = blue;
+ }
dualsense_schedule_work(ds);
}
@@ -1575,7 +1699,7 @@ static void dualsense_set_player_leds(struct dualsense *ds)
BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0)
};
- uint8_t player_id = ds->base.player_id % ARRAY_SIZE(player_ids);
+ u8 player_id = ds->base.player_id % ARRAY_SIZE(player_ids);
ds->update_player_leds = true;
ds->player_leds_state = player_ids[player_id];
@@ -1586,7 +1710,7 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
{
struct dualsense *ds;
struct ps_device *ps_dev;
- uint8_t max_output_report_size;
+ u8 max_output_report_size;
int i, ret;
static const struct ps_led_info player_leds_info[] = {
@@ -1675,7 +1799,7 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
ps_dev->input_dev_name = dev_name(&ds->gamepad->dev);
ds->sensors = ps_sensors_create(hdev, DS_ACC_RANGE, DS_ACC_RES_PER_G,
- DS_GYRO_RANGE, DS_GYRO_RES_PER_DEG_S);
+ DS_GYRO_RANGE, DS_GYRO_RES_PER_DEG_S);
if (IS_ERR(ds->sensors)) {
ret = PTR_ERR(ds->sensors);
goto err;
@@ -1687,6 +1811,15 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
goto err;
}
+ /* Bluetooth audio is currently not supported. */
+ if (hdev->bus == BUS_USB) {
+ ds->jack = ps_headset_jack_create(hdev);
+ if (IS_ERR(ds->jack)) {
+ ret = PTR_ERR(ds->jack);
+ goto err;
+ }
+ }
+
ret = ps_device_register_battery(ps_dev);
if (ret)
goto err;
@@ -1729,7 +1862,7 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
* can change behavior.
*/
hid_info(hdev, "Registered DualSense controller hw_version=0x%08x fw_version=0x%08x\n",
- ds->base.hw_version, ds->base.fw_version);
+ ds->base.hw_version, ds->base.fw_version);
return &ds->base;
@@ -1741,7 +1874,6 @@ err:
static void dualshock4_dongle_calibration_work(struct work_struct *work)
{
struct dualshock4 *ds4 = container_of(work, struct dualshock4, dongle_hotplug_worker);
- unsigned long flags;
enum dualshock4_dongle_state dongle_state;
int ret;
@@ -1753,16 +1885,16 @@ static void dualshock4_dongle_calibration_work(struct work_struct *work)
* DS4 hotplug is detect from sony_raw_event as any issues
* are likely resolved then (the dongle is quite stupid).
*/
- hid_err(ds4->base.hdev, "DualShock 4 USB dongle: calibration failed, disabling device\n");
+ hid_err(ds4->base.hdev,
+ "DualShock 4 USB dongle: calibration failed, disabling device\n");
dongle_state = DONGLE_DISABLED;
} else {
hid_info(ds4->base.hdev, "DualShock 4 USB dongle: calibration completed\n");
dongle_state = DONGLE_CONNECTED;
}
- spin_lock_irqsave(&ds4->base.lock, flags);
- ds4->dongle_state = dongle_state;
- spin_unlock_irqrestore(&ds4->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds4->base.lock)
+ ds4->dongle_state = dongle_state;
}
static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
@@ -1779,7 +1911,7 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
int range_2g;
int ret = 0;
int i;
- uint8_t *buf;
+ u8 *buf;
if (ds4->base.hdev->bus == BUS_USB) {
int retries;
@@ -1798,14 +1930,17 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
*/
for (retries = 0; retries < 3; retries++) {
ret = ps_get_report(hdev, DS4_FEATURE_REPORT_CALIBRATION, buf,
- DS4_FEATURE_REPORT_CALIBRATION_SIZE, true);
+ DS4_FEATURE_REPORT_CALIBRATION_SIZE, true);
if (ret) {
if (retries < 2) {
- hid_warn(hdev, "Retrying DualShock 4 get calibration report (0x02) request\n");
+ hid_warn(hdev,
+ "Retrying DualShock 4 get calibration report (0x02) request\n");
continue;
}
- hid_warn(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
+ hid_warn(hdev,
+ "Failed to retrieve DualShock4 calibration info: %d\n",
+ ret);
ret = -EILSEQ;
goto transfer_failed;
} else {
@@ -1820,7 +1955,7 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
}
ret = ps_get_report(hdev, DS4_FEATURE_REPORT_CALIBRATION_BT, buf,
- DS4_FEATURE_REPORT_CALIBRATION_BT_SIZE, true);
+ DS4_FEATURE_REPORT_CALIBRATION_BT_SIZE, true);
if (ret) {
hid_warn(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
@@ -1867,19 +2002,19 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
speed_2x = (gyro_speed_plus + gyro_speed_minus);
ds4->gyro_calib_data[0].abs_code = ABS_RX;
ds4->gyro_calib_data[0].bias = 0;
- ds4->gyro_calib_data[0].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
+ ds4->gyro_calib_data[0].sens_numer = speed_2x * DS4_GYRO_RES_PER_DEG_S;
ds4->gyro_calib_data[0].sens_denom = abs(gyro_pitch_plus - gyro_pitch_bias) +
abs(gyro_pitch_minus - gyro_pitch_bias);
ds4->gyro_calib_data[1].abs_code = ABS_RY;
ds4->gyro_calib_data[1].bias = 0;
- ds4->gyro_calib_data[1].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
+ ds4->gyro_calib_data[1].sens_numer = speed_2x * DS4_GYRO_RES_PER_DEG_S;
ds4->gyro_calib_data[1].sens_denom = abs(gyro_yaw_plus - gyro_yaw_bias) +
abs(gyro_yaw_minus - gyro_yaw_bias);
ds4->gyro_calib_data[2].abs_code = ABS_RZ;
ds4->gyro_calib_data[2].bias = 0;
- ds4->gyro_calib_data[2].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
+ ds4->gyro_calib_data[2].sens_numer = speed_2x * DS4_GYRO_RES_PER_DEG_S;
ds4->gyro_calib_data[2].sens_denom = abs(gyro_roll_plus - gyro_roll_bias) +
abs(gyro_roll_minus - gyro_roll_bias);
@@ -1890,19 +2025,19 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
range_2g = acc_x_plus - acc_x_minus;
ds4->accel_calib_data[0].abs_code = ABS_X;
ds4->accel_calib_data[0].bias = acc_x_plus - range_2g / 2;
- ds4->accel_calib_data[0].sens_numer = 2*DS4_ACC_RES_PER_G;
+ ds4->accel_calib_data[0].sens_numer = 2 * DS4_ACC_RES_PER_G;
ds4->accel_calib_data[0].sens_denom = range_2g;
range_2g = acc_y_plus - acc_y_minus;
ds4->accel_calib_data[1].abs_code = ABS_Y;
ds4->accel_calib_data[1].bias = acc_y_plus - range_2g / 2;
- ds4->accel_calib_data[1].sens_numer = 2*DS4_ACC_RES_PER_G;
+ ds4->accel_calib_data[1].sens_numer = 2 * DS4_ACC_RES_PER_G;
ds4->accel_calib_data[1].sens_denom = range_2g;
range_2g = acc_z_plus - acc_z_minus;
ds4->accel_calib_data[2].abs_code = ABS_Z;
ds4->accel_calib_data[2].bias = acc_z_plus - range_2g / 2;
- ds4->accel_calib_data[2].sens_numer = 2*DS4_ACC_RES_PER_G;
+ ds4->accel_calib_data[2].sens_numer = 2 * DS4_ACC_RES_PER_G;
ds4->accel_calib_data[2].sens_denom = range_2g;
transfer_failed:
@@ -1914,8 +2049,9 @@ transfer_failed:
for (i = 0; i < ARRAY_SIZE(ds4->gyro_calib_data); i++) {
if (ds4->gyro_calib_data[i].sens_denom == 0) {
ds4->gyro_calib_data[i].abs_code = ABS_RX + i;
- hid_warn(hdev, "Invalid gyro calibration data for axis (%d), disabling calibration.",
- ds4->gyro_calib_data[i].abs_code);
+ hid_warn(hdev,
+ "Invalid gyro calibration data for axis (%d), disabling calibration.",
+ ds4->gyro_calib_data[i].abs_code);
ds4->gyro_calib_data[i].bias = 0;
ds4->gyro_calib_data[i].sens_numer = DS4_GYRO_RANGE;
ds4->gyro_calib_data[i].sens_denom = S16_MAX;
@@ -1930,8 +2066,9 @@ transfer_failed:
for (i = 0; i < ARRAY_SIZE(ds4->accel_calib_data); i++) {
if (ds4->accel_calib_data[i].sens_denom == 0) {
ds4->accel_calib_data[i].abs_code = ABS_X + i;
- hid_warn(hdev, "Invalid accelerometer calibration data for axis (%d), disabling calibration.",
- ds4->accel_calib_data[i].abs_code);
+ hid_warn(hdev,
+ "Invalid accelerometer calibration data for axis (%d), disabling calibration.",
+ ds4->accel_calib_data[i].abs_code);
ds4->accel_calib_data[i].bias = 0;
ds4->accel_calib_data[i].sens_numer = DS4_ACC_RANGE;
ds4->accel_calib_data[i].sens_denom = S16_MAX;
@@ -1943,7 +2080,7 @@ transfer_failed:
static int dualshock4_get_firmware_info(struct dualshock4 *ds4)
{
- uint8_t *buf;
+ u8 *buf;
int ret;
buf = kzalloc(DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE, GFP_KERNEL);
@@ -1954,7 +2091,7 @@ static int dualshock4_get_firmware_info(struct dualshock4 *ds4)
* lacks CRC support, so must be disabled in ps_get_report.
*/
ret = ps_get_report(ds4->base.hdev, DS4_FEATURE_REPORT_FIRMWARE_INFO, buf,
- DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE, false);
+ DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE, false);
if (ret) {
hid_err(ds4->base.hdev, "Failed to retrieve DualShock4 firmware info: %d\n", ret);
goto err_free;
@@ -1971,7 +2108,7 @@ err_free:
static int dualshock4_get_mac_address(struct dualshock4 *ds4)
{
struct hid_device *hdev = ds4->base.hdev;
- uint8_t *buf;
+ u8 *buf;
int ret = 0;
if (hdev->bus == BUS_USB) {
@@ -1980,7 +2117,7 @@ static int dualshock4_get_mac_address(struct dualshock4 *ds4)
return -ENOMEM;
ret = ps_get_report(hdev, DS4_FEATURE_REPORT_PAIRING_INFO, buf,
- DS4_FEATURE_REPORT_PAIRING_INFO_SIZE, false);
+ DS4_FEATURE_REPORT_PAIRING_INFO_SIZE, false);
if (ret) {
hid_err(hdev, "Failed to retrieve DualShock4 pairing info: %d\n", ret);
goto err_free;
@@ -1993,9 +2130,9 @@ static int dualshock4_get_mac_address(struct dualshock4 *ds4)
return -EINVAL;
ret = sscanf(hdev->uniq, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
- &ds4->base.mac_address[5], &ds4->base.mac_address[4],
- &ds4->base.mac_address[3], &ds4->base.mac_address[2],
- &ds4->base.mac_address[1], &ds4->base.mac_address[0]);
+ &ds4->base.mac_address[5], &ds4->base.mac_address[4],
+ &ds4->base.mac_address[3], &ds4->base.mac_address[2],
+ &ds4->base.mac_address[1], &ds4->base.mac_address[0]);
if (ret != sizeof(ds4->base.mac_address))
return -EINVAL;
@@ -2030,28 +2167,27 @@ static enum led_brightness dualshock4_led_get_brightness(struct led_classdev *le
}
static int dualshock4_led_set_blink(struct led_classdev *led, unsigned long *delay_on,
- unsigned long *delay_off)
+ unsigned long *delay_off)
{
struct hid_device *hdev = to_hid_device(led->dev->parent);
struct dualshock4 *ds4 = hid_get_drvdata(hdev);
- unsigned long flags;
- spin_lock_irqsave(&ds4->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds4->base.lock) {
+ if (!*delay_on && !*delay_off) {
+ /* Default to 1 Hz (50 centiseconds on, 50 centiseconds off). */
+ ds4->lightbar_blink_on = 50;
+ ds4->lightbar_blink_off = 50;
+ } else {
+ /* Blink delays in centiseconds. */
+ ds4->lightbar_blink_on = min_t(unsigned long, *delay_on / 10,
+ DS4_LIGHTBAR_MAX_BLINK);
+ ds4->lightbar_blink_off = min_t(unsigned long, *delay_off / 10,
+ DS4_LIGHTBAR_MAX_BLINK);
+ }
- if (!*delay_on && !*delay_off) {
- /* Default to 1 Hz (50 centiseconds on, 50 centiseconds off). */
- ds4->lightbar_blink_on = 50;
- ds4->lightbar_blink_off = 50;
- } else {
- /* Blink delays in centiseconds. */
- ds4->lightbar_blink_on = min_t(unsigned long, *delay_on/10, DS4_LIGHTBAR_MAX_BLINK);
- ds4->lightbar_blink_off = min_t(unsigned long, *delay_off/10, DS4_LIGHTBAR_MAX_BLINK);
+ ds4->update_lightbar_blink = true;
}
- ds4->update_lightbar_blink = true;
-
- spin_unlock_irqrestore(&ds4->base.lock, flags);
-
dualshock4_schedule_work(ds4);
/* Report scaled values back to LED subsystem */
@@ -2065,36 +2201,33 @@ static int dualshock4_led_set_brightness(struct led_classdev *led, enum led_brig
{
struct hid_device *hdev = to_hid_device(led->dev->parent);
struct dualshock4 *ds4 = hid_get_drvdata(hdev);
- unsigned long flags;
unsigned int led_index;
- spin_lock_irqsave(&ds4->base.lock, flags);
-
- led_index = led - ds4->lightbar_leds;
- switch (led_index) {
- case 0:
- ds4->lightbar_red = value;
- break;
- case 1:
- ds4->lightbar_green = value;
- break;
- case 2:
- ds4->lightbar_blue = value;
- break;
- case 3:
- ds4->lightbar_enabled = !!value;
-
- /* brightness = 0 also cancels blinking in Linux. */
- if (!ds4->lightbar_enabled) {
- ds4->lightbar_blink_off = 0;
- ds4->lightbar_blink_on = 0;
- ds4->update_lightbar_blink = true;
+ scoped_guard(spinlock_irqsave, &ds4->base.lock) {
+ led_index = led - ds4->lightbar_leds;
+ switch (led_index) {
+ case 0:
+ ds4->lightbar_red = value;
+ break;
+ case 1:
+ ds4->lightbar_green = value;
+ break;
+ case 2:
+ ds4->lightbar_blue = value;
+ break;
+ case 3:
+ ds4->lightbar_enabled = !!value;
+
+ /* brightness = 0 also cancels blinking in Linux. */
+ if (!ds4->lightbar_enabled) {
+ ds4->lightbar_blink_off = 0;
+ ds4->lightbar_blink_on = 0;
+ ds4->update_lightbar_blink = true;
+ }
}
- }
- ds4->update_lightbar = true;
-
- spin_unlock_irqrestore(&ds4->base.lock, flags);
+ ds4->update_lightbar = true;
+ }
dualshock4_schedule_work(ds4);
@@ -2102,7 +2235,7 @@ static int dualshock4_led_set_brightness(struct led_classdev *led, enum led_brig
}
static void dualshock4_init_output_report(struct dualshock4 *ds4,
- struct dualshock4_output_report *rp, void *buf)
+ struct dualshock4_output_report *rp, void *buf)
{
struct hid_device *hdev = ds4->base.hdev;
@@ -2136,66 +2269,63 @@ static void dualshock4_output_worker(struct work_struct *work)
struct dualshock4 *ds4 = container_of(work, struct dualshock4, output_worker);
struct dualshock4_output_report report;
struct dualshock4_output_report_common *common;
- unsigned long flags;
dualshock4_init_output_report(ds4, &report, ds4->output_report_dmabuf);
common = report.common;
- spin_lock_irqsave(&ds4->base.lock, flags);
-
- /*
- * Some 3rd party gamepads expect updates to rumble and lightbar
- * together, and setting one may cancel the other.
- *
- * Let's maximise compatibility by always sending rumble and lightbar
- * updates together, even when only one has been scheduled, resulting
- * in:
- *
- * ds4->valid_flag0 >= 0x03
- *
- * Hopefully this will maximise compatibility with third-party pads.
- *
- * Any further update bits, such as 0x04 for lightbar blinking, will
- * be or'd on top of this like before.
- */
- if (ds4->update_rumble || ds4->update_lightbar) {
- ds4->update_rumble = true; /* 0x01 */
- ds4->update_lightbar = true; /* 0x02 */
- }
+ scoped_guard(spinlock_irqsave, &ds4->base.lock) {
+ /*
+ * Some 3rd party gamepads expect updates to rumble and lightbar
+ * together, and setting one may cancel the other.
+ *
+ * Let's maximise compatibility by always sending rumble and lightbar
+ * updates together, even when only one has been scheduled, resulting
+ * in:
+ *
+ * ds4->valid_flag0 >= 0x03
+ *
+ * Hopefully this will maximise compatibility with third-party pads.
+ *
+ * Any further update bits, such as 0x04 for lightbar blinking, will
+ * be or'd on top of this like before.
+ */
+ if (ds4->update_rumble || ds4->update_lightbar) {
+ ds4->update_rumble = true; /* 0x01 */
+ ds4->update_lightbar = true; /* 0x02 */
+ }
- if (ds4->update_rumble) {
- /* Select classic rumble style haptics and enable it. */
- common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_MOTOR;
- common->motor_left = ds4->motor_left;
- common->motor_right = ds4->motor_right;
- ds4->update_rumble = false;
- }
+ if (ds4->update_rumble) {
+ /* Select classic rumble style haptics and enable it. */
+ common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_MOTOR;
+ common->motor_left = ds4->motor_left;
+ common->motor_right = ds4->motor_right;
+ ds4->update_rumble = false;
+ }
- if (ds4->update_lightbar) {
- common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED;
- /* Comptabile behavior with hid-sony, which used a dummy global LED to
- * allow enabling/disabling the lightbar. The global LED maps to
- * lightbar_enabled.
- */
- common->lightbar_red = ds4->lightbar_enabled ? ds4->lightbar_red : 0;
- common->lightbar_green = ds4->lightbar_enabled ? ds4->lightbar_green : 0;
- common->lightbar_blue = ds4->lightbar_enabled ? ds4->lightbar_blue : 0;
- ds4->update_lightbar = false;
- }
+ if (ds4->update_lightbar) {
+ common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED;
+ /* Compatible behavior with hid-sony, which used a dummy global LED to
+ * allow enabling/disabling the lightbar. The global LED maps to
+ * lightbar_enabled.
+ */
+ common->lightbar_red = ds4->lightbar_enabled ? ds4->lightbar_red : 0;
+ common->lightbar_green = ds4->lightbar_enabled ? ds4->lightbar_green : 0;
+ common->lightbar_blue = ds4->lightbar_enabled ? ds4->lightbar_blue : 0;
+ ds4->update_lightbar = false;
+ }
- if (ds4->update_lightbar_blink) {
- common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED_BLINK;
- common->lightbar_blink_on = ds4->lightbar_blink_on;
- common->lightbar_blink_off = ds4->lightbar_blink_off;
- ds4->update_lightbar_blink = false;
+ if (ds4->update_lightbar_blink) {
+ common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED_BLINK;
+ common->lightbar_blink_on = ds4->lightbar_blink_on;
+ common->lightbar_blink_off = ds4->lightbar_blink_off;
+ ds4->update_lightbar_blink = false;
+ }
}
- spin_unlock_irqrestore(&ds4->base.lock, flags);
-
/* Bluetooth packets need additional flags as well as a CRC in the last 4 bytes. */
if (report.bt) {
- uint32_t crc;
- uint8_t seed = PS_OUTPUT_CRC32_SEED;
+ u32 crc;
+ u8 seed = PS_OUTPUT_CRC32_SEED;
/* Hardware control flags need to set to let the device know
* there is HID data as well as CRC.
@@ -2217,16 +2347,15 @@ static void dualshock4_output_worker(struct work_struct *work)
}
static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *report,
- u8 *data, int size)
+ u8 *data, int size)
{
struct hid_device *hdev = ps_dev->hdev;
struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base);
struct dualshock4_input_report_common *ds4_report;
struct dualshock4_touch_report *touch_reports;
- uint8_t battery_capacity, num_touch_reports, value;
+ u8 battery_capacity, num_touch_reports, value;
int battery_status, i, j;
- uint16_t sensor_timestamp;
- unsigned long flags;
+ u16 sensor_timestamp;
bool is_minimal = false;
/*
@@ -2235,16 +2364,17 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
* the full report using reportID 17.
*/
if (hdev->bus == BUS_USB && report->id == DS4_INPUT_REPORT_USB &&
- size == DS4_INPUT_REPORT_USB_SIZE) {
- struct dualshock4_input_report_usb *usb = (struct dualshock4_input_report_usb *)data;
+ size == DS4_INPUT_REPORT_USB_SIZE) {
+ struct dualshock4_input_report_usb *usb =
+ (struct dualshock4_input_report_usb *)data;
ds4_report = &usb->common;
num_touch_reports = usb->num_touch_reports;
touch_reports = usb->touch_reports;
} else if (hdev->bus == BUS_BLUETOOTH && report->id == DS4_INPUT_REPORT_BT &&
- size == DS4_INPUT_REPORT_BT_SIZE) {
+ size == DS4_INPUT_REPORT_BT_SIZE) {
struct dualshock4_input_report_bt *bt = (struct dualshock4_input_report_bt *)data;
- uint32_t report_crc = get_unaligned_le32(&bt->crc32);
+ u32 report_crc = get_unaligned_le32(&bt->crc32);
/* Last 4 bytes of input report contains CRC. */
if (!ps_check_crc32(PS_INPUT_CRC32_SEED, data, size - 4, report_crc)) {
@@ -2325,16 +2455,16 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
/* Convert timestamp (in 5.33us unit) to timestamp_us */
sensor_timestamp = le16_to_cpu(ds4_report->sensor_timestamp);
if (!ds4->sensor_timestamp_initialized) {
- ds4->sensor_timestamp_us = DIV_ROUND_CLOSEST(sensor_timestamp*16, 3);
+ ds4->sensor_timestamp_us = DIV_ROUND_CLOSEST(sensor_timestamp * 16, 3);
ds4->sensor_timestamp_initialized = true;
} else {
- uint16_t delta;
+ u16 delta;
if (ds4->prev_sensor_timestamp > sensor_timestamp)
delta = (U16_MAX - ds4->prev_sensor_timestamp + sensor_timestamp + 1);
else
delta = sensor_timestamp - ds4->prev_sensor_timestamp;
- ds4->sensor_timestamp_us += DIV_ROUND_CLOSEST(delta*16, 3);
+ ds4->sensor_timestamp_us += DIV_ROUND_CLOSEST(delta * 16, 3);
}
ds4->prev_sensor_timestamp = sensor_timestamp;
input_event(ds4->sensors, EV_MSC, MSC_TIMESTAMP, ds4->sensor_timestamp_us);
@@ -2351,11 +2481,10 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
input_mt_report_slot_state(ds4->touchpad, MT_TOOL_FINGER, active);
if (active) {
- int x = (point->x_hi << 8) | point->x_lo;
- int y = (point->y_hi << 4) | point->y_lo;
-
- input_report_abs(ds4->touchpad, ABS_MT_POSITION_X, x);
- input_report_abs(ds4->touchpad, ABS_MT_POSITION_Y, y);
+ input_report_abs(ds4->touchpad, ABS_MT_POSITION_X,
+ DS4_TOUCH_POINT_X(point->x_hi, point->x_lo));
+ input_report_abs(ds4->touchpad, ABS_MT_POSITION_Y,
+ DS4_TOUCH_POINT_Y(point->y_hi, point->y_lo));
}
}
input_mt_sync_frame(ds4->touchpad);
@@ -2374,7 +2503,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
* - 15: charge error
*/
if (ds4_report->status[0] & DS4_STATUS0_CABLE_STATE) {
- uint8_t battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY;
+ u8 battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY;
if (battery_data < 10) {
/* Take the mid-point for each battery capacity value,
@@ -2395,7 +2524,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
}
} else {
- uint8_t battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY;
+ u8 battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY;
if (battery_data < 10)
battery_capacity = battery_data * 10 + 5;
@@ -2405,16 +2534,16 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
}
- spin_lock_irqsave(&ps_dev->lock, flags);
- ps_dev->battery_capacity = battery_capacity;
- ps_dev->battery_status = battery_status;
- spin_unlock_irqrestore(&ps_dev->lock, flags);
+ scoped_guard(spinlock_irqsave, &ps_dev->lock) {
+ ps_dev->battery_capacity = battery_capacity;
+ ps_dev->battery_status = battery_status;
+ }
return 0;
}
static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_report *report,
- u8 *data, int size)
+ u8 *data, int size)
{
struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base);
bool connected = false;
@@ -2425,8 +2554,8 @@ static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_r
* parsing code.
*/
if (data[0] == DS4_INPUT_REPORT_USB && size == DS4_INPUT_REPORT_USB_SIZE) {
- struct dualshock4_input_report_common *ds4_report = (struct dualshock4_input_report_common *)&data[1];
- unsigned long flags;
+ struct dualshock4_input_report_common *ds4_report =
+ (struct dualshock4_input_report_common *)&data[1];
connected = ds4_report->status[1] & DS4_STATUS1_DONGLE_STATE ? false : true;
@@ -2435,9 +2564,8 @@ static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_r
dualshock4_set_default_lightbar_colors(ds4);
- spin_lock_irqsave(&ps_dev->lock, flags);
- ds4->dongle_state = DONGLE_CALIBRATING;
- spin_unlock_irqrestore(&ps_dev->lock, flags);
+ scoped_guard(spinlock_irqsave, &ps_dev->lock)
+ ds4->dongle_state = DONGLE_CALIBRATING;
schedule_work(&ds4->dongle_hotplug_worker);
@@ -2449,9 +2577,8 @@ static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_r
ds4->dongle_state == DONGLE_DISABLED) && !connected) {
hid_info(ps_dev->hdev, "DualShock 4 USB dongle: controller disconnected\n");
- spin_lock_irqsave(&ps_dev->lock, flags);
- ds4->dongle_state = DONGLE_DISCONNECTED;
- spin_unlock_irqrestore(&ps_dev->lock, flags);
+ scoped_guard(spinlock_irqsave, &ps_dev->lock)
+ ds4->dongle_state = DONGLE_DISCONNECTED;
/* Return 0, so hidraw can get the report. */
return 0;
@@ -2473,16 +2600,15 @@ static int dualshock4_play_effect(struct input_dev *dev, void *data, struct ff_e
{
struct hid_device *hdev = input_get_drvdata(dev);
struct dualshock4 *ds4 = hid_get_drvdata(hdev);
- unsigned long flags;
if (effect->type != FF_RUMBLE)
return 0;
- spin_lock_irqsave(&ds4->base.lock, flags);
- ds4->update_rumble = true;
- ds4->motor_left = effect->u.rumble.strong_magnitude / 256;
- ds4->motor_right = effect->u.rumble.weak_magnitude / 256;
- spin_unlock_irqrestore(&ds4->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds4->base.lock) {
+ ds4->update_rumble = true;
+ ds4->motor_left = effect->u.rumble.strong_magnitude / 256;
+ ds4->motor_right = effect->u.rumble.weak_magnitude / 256;
+ }
dualshock4_schedule_work(ds4);
return 0;
@@ -2491,11 +2617,9 @@ static int dualshock4_play_effect(struct input_dev *dev, void *data, struct ff_e
static void dualshock4_remove(struct ps_device *ps_dev)
{
struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base);
- unsigned long flags;
- spin_lock_irqsave(&ds4->base.lock, flags);
- ds4->output_worker_initialized = false;
- spin_unlock_irqrestore(&ds4->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds4->base.lock)
+ ds4->output_worker_initialized = false;
cancel_work_sync(&ds4->output_worker);
@@ -2505,15 +2629,13 @@ static void dualshock4_remove(struct ps_device *ps_dev)
static inline void dualshock4_schedule_work(struct dualshock4 *ds4)
{
- unsigned long flags;
-
- spin_lock_irqsave(&ds4->base.lock, flags);
- if (ds4->output_worker_initialized)
- schedule_work(&ds4->output_worker);
- spin_unlock_irqrestore(&ds4->base.lock, flags);
+ /* Using scoped_guard() instead of guard() to make sparse happy */
+ scoped_guard(spinlock_irqsave, &ds4->base.lock)
+ if (ds4->output_worker_initialized)
+ schedule_work(&ds4->output_worker);
}
-static void dualshock4_set_bt_poll_interval(struct dualshock4 *ds4, uint8_t interval)
+static void dualshock4_set_bt_poll_interval(struct dualshock4 *ds4, u8 interval)
{
ds4->bt_poll_interval = interval;
ds4->update_bt_poll_interval = true;
@@ -2533,7 +2655,7 @@ static void dualshock4_set_default_lightbar_colors(struct dualshock4 *ds4)
{ 0x20, 0x00, 0x20 } /* Pink */
};
- uint8_t player_id = ds4->base.player_id % ARRAY_SIZE(player_colors);
+ u8 player_id = ds4->base.player_id % ARRAY_SIZE(player_colors);
ds4->lightbar_enabled = true;
ds4->lightbar_red = player_colors[player_id][0];
@@ -2548,7 +2670,7 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
{
struct dualshock4 *ds4;
struct ps_device *ps_dev;
- uint8_t max_output_report_size;
+ u8 max_output_report_size;
int i, ret;
/* The DualShock4 has an RGB lightbar, which the original hid-sony driver
@@ -2561,11 +2683,14 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
* existing applications (e.g. Android). Nothing matches against MAC address.
*/
static const struct ps_led_info lightbar_leds_info[] = {
- { NULL, "red", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness },
- { NULL, "green", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness },
- { NULL, "blue", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness },
- { NULL, "global", 1, dualshock4_led_get_brightness, dualshock4_led_set_brightness,
- dualshock4_led_set_blink },
+ { NULL, "red", 255, dualshock4_led_get_brightness,
+ dualshock4_led_set_brightness },
+ { NULL, "green", 255, dualshock4_led_get_brightness,
+ dualshock4_led_set_brightness },
+ { NULL, "blue", 255, dualshock4_led_get_brightness,
+ dualshock4_led_set_brightness },
+ { NULL, "global", 1, dualshock4_led_get_brightness,
+ dualshock4_led_set_brightness, dualshock4_led_set_blink },
};
ds4 = devm_kzalloc(&hdev->dev, sizeof(*ds4), GFP_KERNEL);
@@ -2635,7 +2760,7 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
ps_dev->input_dev_name = dev_name(&ds4->gamepad->dev);
ds4->sensors = ps_sensors_create(hdev, DS4_ACC_RANGE, DS4_ACC_RES_PER_G,
- DS4_GYRO_RANGE, DS4_GYRO_RES_PER_DEG_S);
+ DS4_GYRO_RANGE, DS4_GYRO_RES_PER_DEG_S);
if (IS_ERR(ds4->sensors)) {
ret = PTR_ERR(ds4->sensors);
goto err;
@@ -2674,7 +2799,7 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
* can change behavior.
*/
hid_info(hdev, "Registered DualShock4 controller hw_version=0x%08x fw_version=0x%08x\n",
- ds4->base.hw_version, ds4->base.fw_version);
+ ds4->base.hw_version, ds4->base.fw_version);
return &ds4->base;
err:
@@ -2683,7 +2808,7 @@ err:
}
static int ps_raw_event(struct hid_device *hdev, struct hid_report *report,
- u8 *data, int size)
+ u8 *data, int size)
{
struct ps_device *dev = hid_get_drvdata(hdev);
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index f619ed10535d..ffd034566e2e 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -695,6 +695,8 @@ static const struct hid_device_id hid_have_special_driver[] = {
#endif
#if IS_ENABLED(CONFIG_HID_STEELSERIES)
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9) },
#endif
#if IS_ENABLED(CONFIG_HID_SUNPLUS)
{ HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index d4bd7848b8c6..f98435631aa1 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -249,11 +249,11 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
{
int ret, i;
struct led_classdev *led;
+ struct steelseries_srws1_data *drv_data;
size_t name_sz;
char *name;
- struct steelseries_srws1_data *drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL);
-
+ drv_data = devm_kzalloc(&hdev->dev, sizeof(*drv_data), GFP_KERNEL);
if (drv_data == NULL) {
hid_err(hdev, "can't alloc SRW-S1 memory\n");
return -ENOMEM;
@@ -264,18 +264,18 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
- goto err_free;
+ goto err;
}
if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 16)) {
ret = -ENODEV;
- goto err_free;
+ goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
- goto err_free;
+ goto err;
}
/* register led subsystem */
@@ -288,10 +288,10 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
name_sz = strlen(hdev->uniq) + 16;
/* 'ALL', for setting all LEDs simultaneously */
- led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
+ led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
if (!led) {
hid_err(hdev, "can't allocate memory for LED ALL\n");
- goto err_led;
+ goto out;
}
name = (void *)(&led[1]);
@@ -303,16 +303,18 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
led->brightness_set = steelseries_srws1_led_all_set_brightness;
drv_data->led[SRWS1_NUMBER_LEDS] = led;
- ret = led_classdev_register(&hdev->dev, led);
- if (ret)
- goto err_led;
+ ret = devm_led_classdev_register(&hdev->dev, led);
+ if (ret) {
+ hid_err(hdev, "failed to register LED %d. Aborting.\n", SRWS1_NUMBER_LEDS);
+ goto out; /* let the driver continue without LEDs */
+ }
/* Each individual LED */
for (i = 0; i < SRWS1_NUMBER_LEDS; i++) {
- led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
+ led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
if (!led) {
hid_err(hdev, "can't allocate memory for LED %d\n", i);
- goto err_led;
+ break;
}
name = (void *)(&led[1]);
@@ -324,53 +326,18 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
led->brightness_set = steelseries_srws1_led_set_brightness;
drv_data->led[i] = led;
- ret = led_classdev_register(&hdev->dev, led);
+ ret = devm_led_classdev_register(&hdev->dev, led);
if (ret) {
hid_err(hdev, "failed to register LED %d. Aborting.\n", i);
-err_led:
- /* Deregister all LEDs (if any) */
- for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) {
- led = drv_data->led[i];
- drv_data->led[i] = NULL;
- if (!led)
- continue;
- led_classdev_unregister(led);
- kfree(led);
- }
- goto out; /* but let the driver continue without LEDs */
+ break; /* but let the driver continue without LEDs */
}
}
out:
return 0;
-err_free:
- kfree(drv_data);
+err:
return ret;
}
-
-static void steelseries_srws1_remove(struct hid_device *hdev)
-{
- int i;
- struct led_classdev *led;
-
- struct steelseries_srws1_data *drv_data = hid_get_drvdata(hdev);
-
- if (drv_data) {
- /* Deregister LEDs (if any) */
- for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) {
- led = drv_data->led[i];
- drv_data->led[i] = NULL;
- if (!led)
- continue;
- led_classdev_unregister(led);
- kfree(led);
- }
-
- }
-
- hid_hw_stop(hdev);
- kfree(drv_data);
-}
#endif
#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
@@ -405,13 +372,12 @@ static int steelseries_headset_request_battery(struct hid_device *hdev,
static void steelseries_headset_fetch_battery(struct hid_device *hdev)
{
- struct steelseries_device *sd = hid_get_drvdata(hdev);
int ret = 0;
- if (sd->quirks & STEELSERIES_ARCTIS_1)
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1)
ret = steelseries_headset_request_battery(hdev,
arctis_1_battery_request, sizeof(arctis_1_battery_request));
- else if (sd->quirks & STEELSERIES_ARCTIS_9)
+ else if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9)
ret = steelseries_headset_request_battery(hdev,
arctis_9_battery_request, sizeof(arctis_9_battery_request));
@@ -567,14 +533,7 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
struct steelseries_device *sd;
int ret;
- sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
- if (!sd)
- return -ENOMEM;
- hid_set_drvdata(hdev, sd);
- sd->hdev = hdev;
- sd->quirks = id->driver_data;
-
- if (sd->quirks & STEELSERIES_SRWS1) {
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
return steelseries_srws1_probe(hdev, id);
@@ -583,6 +542,13 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
#endif
}
+ sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
+ if (!sd)
+ return -ENOMEM;
+ hid_set_drvdata(hdev, sd);
+ sd->hdev = hdev;
+ sd->quirks = id->driver_data;
+
ret = hid_parse(hdev);
if (ret)
return ret;
@@ -610,17 +576,19 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
static void steelseries_remove(struct hid_device *hdev)
{
- struct steelseries_device *sd = hid_get_drvdata(hdev);
+ struct steelseries_device *sd;
unsigned long flags;
- if (sd->quirks & STEELSERIES_SRWS1) {
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
- steelseries_srws1_remove(hdev);
+ hid_hw_stop(hdev);
#endif
return;
}
+ sd = hid_get_drvdata(hdev);
+
spin_lock_irqsave(&sd->lock, flags);
sd->removed = true;
spin_unlock_irqrestore(&sd->lock, flags);
@@ -667,10 +635,10 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
unsigned long flags;
/* Not a headset */
- if (sd->quirks & STEELSERIES_SRWS1)
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
return 0;
- if (sd->quirks & STEELSERIES_ARCTIS_1) {
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
hid_dbg(sd->hdev,
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
@@ -688,7 +656,7 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
}
}
- if (sd->quirks & STEELSERIES_ARCTIS_9) {
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) {
hid_dbg(sd->hdev,
"Parsing raw event for Arctis 9 headset (%*ph)\n", size, read_buf);
if (size < ARCTIS_9_BATTERY_RESPONSE_LEN) {
@@ -757,11 +725,11 @@ static const struct hid_device_id steelseries_devices[] = {
.driver_data = STEELSERIES_SRWS1 },
{ /* SteelSeries Arctis 1 Wireless for XBox */
- HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, 0x12b6),
- .driver_data = STEELSERIES_ARCTIS_1 },
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1),
+ .driver_data = STEELSERIES_ARCTIS_1 },
{ /* SteelSeries Arctis 9 Wireless for XBox */
- HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, 0x12c2),
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = STEELSERIES_ARCTIS_9 },
{ }
diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c
index 4a17f7332c3f..ffa14a4621ef 100644
--- a/drivers/hid/hid-uclogic-params.c
+++ b/drivers/hid/hid-uclogic-params.c
@@ -20,6 +20,7 @@
#include <linux/ctype.h>
#include <linux/string.h>
#include <linux/unaligned.h>
+#include <linux/string_choices.h>
/**
* uclogic_params_pen_inrange_to_str() - Convert a pen in-range reporting type
@@ -59,7 +60,7 @@ static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev,
size_t i;
hid_dbg(hdev, "\t.usage_invalid = %s\n",
- (pen->usage_invalid ? "true" : "false"));
+ str_true_false(pen->usage_invalid));
hid_dbg(hdev, "\t.desc_ptr = %p\n", pen->desc_ptr);
hid_dbg(hdev, "\t.desc_size = %u\n", pen->desc_size);
hid_dbg(hdev, "\t.id = %u\n", pen->id);
@@ -74,9 +75,9 @@ static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev,
hid_dbg(hdev, "\t.inrange = %s\n",
uclogic_params_pen_inrange_to_str(pen->inrange));
hid_dbg(hdev, "\t.fragmented_hires = %s\n",
- (pen->fragmented_hires ? "true" : "false"));
+ str_true_false(pen->fragmented_hires));
hid_dbg(hdev, "\t.tilt_y_flipped = %s\n",
- (pen->tilt_y_flipped ? "true" : "false"));
+ str_true_false(pen->tilt_y_flipped));
}
/**
@@ -119,8 +120,7 @@ void uclogic_params_hid_dbg(const struct hid_device *hdev,
{
size_t i;
- hid_dbg(hdev, ".invalid = %s\n",
- params->invalid ? "true" : "false");
+ hid_dbg(hdev, ".invalid = %s\n", str_true_false(params->invalid));
hid_dbg(hdev, ".desc_ptr = %p\n", params->desc_ptr);
hid_dbg(hdev, ".desc_size = %u\n", params->desc_size);
hid_dbg(hdev, ".pen = {\n");
diff --git a/drivers/hid/hid-universal-pidff.c b/drivers/hid/hid-universal-pidff.c
index 554a6559aeb7..549dac555d40 100644
--- a/drivers/hid/hid-universal-pidff.c
+++ b/drivers/hid/hid-universal-pidff.c
@@ -8,12 +8,12 @@
* Copyright (c) 2024, 2025 Tomasz Pakuła
*/
+#include "hid-ids.h"
+#include "usbhid/hid-pidff.h"
#include <linux/device.h>
#include <linux/hid.h>
-#include <linux/module.h>
#include <linux/input-event-codes.h>
-#include "hid-ids.h"
-#include "usbhid/hid-pidff.h"
+#include <linux/module.h>
#define JOY_RANGE (BTN_DEAD - BTN_JOYSTICK + 1)
@@ -21,8 +21,10 @@
* Map buttons manually to extend the default joystick button limit
*/
static int universal_pidff_input_mapping(struct hid_device *hdev,
- struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
- unsigned long **bit, int *max)
+ struct hid_input *hi,
+ struct hid_field *field,
+ struct hid_usage *usage,
+ unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON)
return 0;
@@ -126,65 +128,64 @@ static int universal_pidff_input_configured(struct hid_device *hdev,
if (!test_bit(axis, input->absbit))
continue;
- input_set_abs_params(input, axis,
- input->absinfo[axis].minimum,
- input->absinfo[axis].maximum,
- axis == ABS_X ? 0 : 8, 0);
+ input_set_abs_params(input, axis, input->absinfo[axis].minimum,
+ input->absinfo[axis].maximum,
+ axis == ABS_X ? 0 : 8, 0);
}
/* Remove fuzz and deadzone from the second joystick axis */
if (hdev->vendor == USB_VENDOR_ID_FFBEAST &&
hdev->product == USB_DEVICE_ID_FFBEAST_JOYSTICK)
input_set_abs_params(input, ABS_Y,
- input->absinfo[ABS_Y].minimum,
- input->absinfo[ABS_Y].maximum, 0, 0);
+ input->absinfo[ABS_Y].minimum,
+ input->absinfo[ABS_Y].maximum, 0, 0);
return 0;
}
static const struct hid_device_id universal_pidff_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3),
- .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3_2),
- .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5),
- .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5_2),
- .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9),
- .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9_2),
- .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12),
- .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12_2),
- .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21),
- .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21_2),
- .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
{ HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C5) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C12) },
{ HID_USB_DEVICE(USB_VENDOR_ID_VRS, USB_DEVICE_ID_VRS_DFP),
- .driver_data = HID_PIDFF_QUIRK_PERMISSIVE_CONTROL },
+ .driver_data = HID_PIDFF_QUIRK_PERMISSIVE_CONTROL },
{ HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_JOYSTICK), },
{ HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_RUDDER), },
{ HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_WHEEL) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V10),
- .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
+ .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
{ HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12),
- .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
+ .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
{ HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE),
- .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
+ .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
{ HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE_2),
- .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
+ .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
{ HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_LITE_STAR_GT987),
- .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
+ .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_INVICTA) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_FORTE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_LA_PRIMA) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_TONY_KANAAN) },
- { }
+ {}
};
MODULE_DEVICE_TABLE(hid, universal_pidff_devices);
diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
index c887f48756f4..bbd6f23bce78 100644
--- a/drivers/hid/hidraw.c
+++ b/drivers/hid/hidraw.c
@@ -394,27 +394,15 @@ static int hidraw_revoke(struct hidraw_list *list)
return 0;
}
-static long hidraw_ioctl(struct file *file, unsigned int cmd,
- unsigned long arg)
+static long hidraw_fixed_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd,
+ void __user *arg)
{
- struct inode *inode = file_inode(file);
- unsigned int minor = iminor(inode);
- long ret = 0;
- struct hidraw *dev;
- struct hidraw_list *list = file->private_data;
- void __user *user_arg = (void __user*) arg;
-
- down_read(&minors_rwsem);
- dev = hidraw_table[minor];
- if (!dev || !dev->exist || hidraw_is_revoked(list)) {
- ret = -ENODEV;
- goto out;
- }
+ struct hid_device *hid = dev->hid;
switch (cmd) {
case HIDIOCGRDESCSIZE:
- if (put_user(dev->hid->rsize, (int __user *)arg))
- ret = -EFAULT;
+ if (put_user(hid->rsize, (int __user *)arg))
+ return -EFAULT;
break;
case HIDIOCGRDESC:
@@ -422,113 +410,145 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
__u32 len;
if (get_user(len, (int __user *)arg))
- ret = -EFAULT;
- else if (len > HID_MAX_DESCRIPTOR_SIZE - 1)
- ret = -EINVAL;
- else if (copy_to_user(user_arg + offsetof(
- struct hidraw_report_descriptor,
- value[0]),
- dev->hid->rdesc,
- min(dev->hid->rsize, len)))
- ret = -EFAULT;
+ return -EFAULT;
+
+ if (len > HID_MAX_DESCRIPTOR_SIZE - 1)
+ return -EINVAL;
+
+ if (copy_to_user(arg + offsetof(
+ struct hidraw_report_descriptor,
+ value[0]),
+ hid->rdesc,
+ min(hid->rsize, len)))
+ return -EFAULT;
+
break;
}
case HIDIOCGRAWINFO:
{
struct hidraw_devinfo dinfo;
- dinfo.bustype = dev->hid->bus;
- dinfo.vendor = dev->hid->vendor;
- dinfo.product = dev->hid->product;
- if (copy_to_user(user_arg, &dinfo, sizeof(dinfo)))
- ret = -EFAULT;
+ dinfo.bustype = hid->bus;
+ dinfo.vendor = hid->vendor;
+ dinfo.product = hid->product;
+ if (copy_to_user(arg, &dinfo, sizeof(dinfo)))
+ return -EFAULT;
break;
}
case HIDIOCREVOKE:
{
- if (user_arg)
- ret = -EINVAL;
- else
- ret = hidraw_revoke(list);
- break;
+ struct hidraw_list *list = file->private_data;
+
+ if (arg)
+ return -EINVAL;
+
+ return hidraw_revoke(list);
}
default:
- {
- struct hid_device *hid = dev->hid;
- if (_IOC_TYPE(cmd) != 'H') {
- ret = -EINVAL;
- break;
- }
+ /*
+ * None of the above ioctls can return -EAGAIN, so
+ * use it as a marker that we need to check variable
+ * length ioctls.
+ */
+ return -EAGAIN;
+ }
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) {
- int len = _IOC_SIZE(cmd);
- ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
- break;
- }
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) {
- int len = _IOC_SIZE(cmd);
- ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT);
- break;
- }
+ return 0;
+}
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSINPUT(0))) {
- int len = _IOC_SIZE(cmd);
- ret = hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT);
- break;
- }
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGINPUT(0))) {
- int len = _IOC_SIZE(cmd);
- ret = hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT);
- break;
- }
+static long hidraw_rw_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd,
+ void __user *user_arg)
+{
+ int len = _IOC_SIZE(cmd);
+
+ switch (cmd & ~IOCSIZE_MASK) {
+ case HIDIOCSFEATURE(0):
+ return hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
+ case HIDIOCGFEATURE(0):
+ return hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT);
+ case HIDIOCSINPUT(0):
+ return hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT);
+ case HIDIOCGINPUT(0):
+ return hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT);
+ case HIDIOCSOUTPUT(0):
+ return hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT);
+ case HIDIOCGOUTPUT(0):
+ return hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT);
+ }
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSOUTPUT(0))) {
- int len = _IOC_SIZE(cmd);
- ret = hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT);
- break;
- }
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGOUTPUT(0))) {
- int len = _IOC_SIZE(cmd);
- ret = hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT);
- break;
- }
+ return -EINVAL;
+}
- /* Begin Read-only ioctls. */
- if (_IOC_DIR(cmd) != _IOC_READ) {
- ret = -EINVAL;
- break;
- }
+static long hidraw_ro_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd,
+ void __user *user_arg)
+{
+ struct hid_device *hid = dev->hid;
+ int len = _IOC_SIZE(cmd);
+ int field_len;
+
+ switch (cmd & ~IOCSIZE_MASK) {
+ case HIDIOCGRAWNAME(0):
+ field_len = strlen(hid->name) + 1;
+ if (len > field_len)
+ len = field_len;
+ return copy_to_user(user_arg, hid->name, len) ? -EFAULT : len;
+ case HIDIOCGRAWPHYS(0):
+ field_len = strlen(hid->phys) + 1;
+ if (len > field_len)
+ len = field_len;
+ return copy_to_user(user_arg, hid->phys, len) ? -EFAULT : len;
+ case HIDIOCGRAWUNIQ(0):
+ field_len = strlen(hid->uniq) + 1;
+ if (len > field_len)
+ len = field_len;
+ return copy_to_user(user_arg, hid->uniq, len) ? -EFAULT : len;
+ }
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) {
- int len = strlen(hid->name) + 1;
- if (len > _IOC_SIZE(cmd))
- len = _IOC_SIZE(cmd);
- ret = copy_to_user(user_arg, hid->name, len) ?
- -EFAULT : len;
- break;
- }
+ return -EINVAL;
+}
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) {
- int len = strlen(hid->phys) + 1;
- if (len > _IOC_SIZE(cmd))
- len = _IOC_SIZE(cmd);
- ret = copy_to_user(user_arg, hid->phys, len) ?
- -EFAULT : len;
- break;
- }
+static long hidraw_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct inode *inode = file_inode(file);
+ unsigned int minor = iminor(inode);
+ struct hidraw *dev;
+ struct hidraw_list *list = file->private_data;
+ void __user *user_arg = (void __user *)arg;
+ int ret;
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWUNIQ(0))) {
- int len = strlen(hid->uniq) + 1;
- if (len > _IOC_SIZE(cmd))
- len = _IOC_SIZE(cmd);
- ret = copy_to_user(user_arg, hid->uniq, len) ?
- -EFAULT : len;
- break;
- }
- }
+ down_read(&minors_rwsem);
+ dev = hidraw_table[minor];
+ if (!dev || !dev->exist || hidraw_is_revoked(list)) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (_IOC_TYPE(cmd) != 'H') {
+ ret = -EINVAL;
+ goto out;
+ }
+ if (_IOC_NR(cmd) > HIDIOCTL_LAST || _IOC_NR(cmd) == 0) {
ret = -ENOTTY;
+ goto out;
}
+
+ ret = hidraw_fixed_size_ioctl(file, dev, cmd, user_arg);
+ if (ret != -EAGAIN)
+ goto out;
+
+ switch (_IOC_DIR(cmd)) {
+ case (_IOC_READ | _IOC_WRITE):
+ ret = hidraw_rw_variable_size_ioctl(file, dev, cmd, user_arg);
+ break;
+ case _IOC_READ:
+ ret = hidraw_ro_variable_size_ioctl(file, dev, cmd, user_arg);
+ break;
+ default:
+ /* Any other IOC_DIR is wrong */
+ ret = -EINVAL;
+ }
+
out:
up_read(&minors_rwsem);
return ret;
diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c
index 1b49243adb16..abd700a101f4 100644
--- a/drivers/hid/i2c-hid/i2c-hid-acpi.c
+++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c
@@ -76,6 +76,13 @@ static int i2c_hid_acpi_get_descriptor(struct i2c_hid_acpi *ihid_acpi)
return hid_descriptor_address;
}
+static void i2c_hid_acpi_restore_sequence(struct i2chid_ops *ops)
+{
+ struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops);
+
+ i2c_hid_acpi_get_descriptor(ihid_acpi);
+}
+
static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops)
{
struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops);
@@ -96,6 +103,7 @@ static int i2c_hid_acpi_probe(struct i2c_client *client)
ihid_acpi->adev = ACPI_COMPANION(dev);
ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail;
+ ihid_acpi->ops.restore_sequence = i2c_hid_acpi_restore_sequence;
ret = i2c_hid_acpi_get_descriptor(ihid_acpi);
if (ret < 0)
diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
index 30ebde1273be..63f46a2e5788 100644
--- a/drivers/hid/i2c-hid/i2c-hid-core.c
+++ b/drivers/hid/i2c-hid/i2c-hid-core.c
@@ -961,6 +961,14 @@ static void i2c_hid_core_shutdown_tail(struct i2c_hid *ihid)
ihid->ops->shutdown_tail(ihid->ops);
}
+static void i2c_hid_core_restore_sequence(struct i2c_hid *ihid)
+{
+ if (!ihid->ops->restore_sequence)
+ return;
+
+ ihid->ops->restore_sequence(ihid->ops);
+}
+
static int i2c_hid_core_suspend(struct i2c_hid *ihid, bool force_poweroff)
{
struct i2c_client *client = ihid->client;
@@ -1370,8 +1378,26 @@ static int i2c_hid_core_pm_resume(struct device *dev)
return i2c_hid_core_resume(ihid);
}
+static int i2c_hid_core_pm_restore(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct i2c_hid *ihid = i2c_get_clientdata(client);
+
+ if (ihid->is_panel_follower)
+ return 0;
+
+ i2c_hid_core_restore_sequence(ihid);
+
+ return i2c_hid_core_resume(ihid);
+}
+
const struct dev_pm_ops i2c_hid_core_pm = {
- SYSTEM_SLEEP_PM_OPS(i2c_hid_core_pm_suspend, i2c_hid_core_pm_resume)
+ .suspend = pm_sleep_ptr(i2c_hid_core_pm_suspend),
+ .resume = pm_sleep_ptr(i2c_hid_core_pm_resume),
+ .freeze = pm_sleep_ptr(i2c_hid_core_pm_suspend),
+ .thaw = pm_sleep_ptr(i2c_hid_core_pm_resume),
+ .poweroff = pm_sleep_ptr(i2c_hid_core_pm_suspend),
+ .restore = pm_sleep_ptr(i2c_hid_core_pm_restore),
};
EXPORT_SYMBOL_GPL(i2c_hid_core_pm);
diff --git a/drivers/hid/i2c-hid/i2c-hid.h b/drivers/hid/i2c-hid/i2c-hid.h
index 2c7b66d5caa0..1724a435c783 100644
--- a/drivers/hid/i2c-hid/i2c-hid.h
+++ b/drivers/hid/i2c-hid/i2c-hid.h
@@ -27,11 +27,13 @@ static inline u32 i2c_hid_get_dmi_quirks(const u16 vendor, const u16 product)
* @power_up: do sequencing to power up the device.
* @power_down: do sequencing to power down the device.
* @shutdown_tail: called at the end of shutdown.
+ * @restore_sequence: hibernation restore sequence.
*/
struct i2chid_ops {
int (*power_up)(struct i2chid_ops *ops);
void (*power_down)(struct i2chid_ops *ops);
void (*shutdown_tail)(struct i2chid_ops *ops);
+ void (*restore_sequence)(struct i2chid_ops *ops);
};
int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c
index 4c861119e97a..3ddaa2cd39d5 100644
--- a/drivers/hid/intel-ish-hid/ipc/ipc.c
+++ b/drivers/hid/intel-ish-hid/ipc/ipc.c
@@ -498,6 +498,7 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
{
uint32_t reset_id;
unsigned long flags;
+ int ret;
/* Read reset ID */
reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF;
@@ -510,12 +511,11 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
/* ISHTP notification in IPC_RESET */
ishtp_reset_handler(dev);
- if (!ish_is_input_ready(dev))
- timed_wait_for_timeout(dev, WAIT_FOR_INPUT_RDY,
- TIME_SLICE_FOR_INPUT_RDY_MS, TIMEOUT_FOR_INPUT_RDY_MS);
-
+ ret = timed_wait_for_timeout(dev, WAIT_FOR_INPUT_RDY,
+ TIME_SLICE_FOR_INPUT_RDY_MS,
+ TIMEOUT_FOR_INPUT_RDY_MS);
/* ISH FW is dead */
- if (!ish_is_input_ready(dev))
+ if (ret)
return -EPIPE;
/* Send clock sync at once after reset */
@@ -531,9 +531,10 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
sizeof(uint32_t));
/* Wait for ISH FW'es ILUP and ISHTP_READY */
- timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY,
- TIME_SLICE_FOR_FW_RDY_MS, TIMEOUT_FOR_FW_RDY_MS);
- if (!ishtp_fw_is_ready(dev)) {
+ ret = timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY,
+ TIME_SLICE_FOR_FW_RDY_MS,
+ TIMEOUT_FOR_FW_RDY_MS);
+ if (ret) {
/* ISH FW is dead */
uint32_t ish_status;
diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c
index a2643ae790d6..8433a991e7f4 100644
--- a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c
+++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c
@@ -23,6 +23,7 @@
static struct quicki2c_ddata ptl_ddata = {
.max_detect_size = MAX_RX_DETECT_SIZE_PTL,
+ .max_interrupt_delay = MAX_RX_INTERRUPT_DELAY,
};
/* THC QuickI2C ACPI method to get device properties */
@@ -200,6 +201,21 @@ static int quicki2c_get_acpi_resources(struct quicki2c_device *qcdev)
return -EOPNOTSUPP;
}
+ if (qcdev->ddata) {
+ qcdev->i2c_max_frame_size_enable = i2c_config.FSEN;
+ qcdev->i2c_int_delay_enable = i2c_config.INDE;
+
+ if (i2c_config.FSVL <= qcdev->ddata->max_detect_size)
+ qcdev->i2c_max_frame_size = i2c_config.FSVL;
+ else
+ qcdev->i2c_max_frame_size = qcdev->ddata->max_detect_size;
+
+ if (i2c_config.INDV <= qcdev->ddata->max_interrupt_delay)
+ qcdev->i2c_int_delay = i2c_config.INDV;
+ else
+ qcdev->i2c_int_delay = qcdev->ddata->max_interrupt_delay;
+ }
+
return 0;
}
@@ -441,17 +457,24 @@ static void quicki2c_dma_adv_enable(struct quicki2c_device *qcdev)
* max input length <= THC detect capability, enable the feature with device
* max input length.
*/
- if (qcdev->ddata->max_detect_size >=
- le16_to_cpu(qcdev->dev_desc.max_input_len)) {
- thc_i2c_set_rx_max_size(qcdev->thc_hw,
- le16_to_cpu(qcdev->dev_desc.max_input_len));
+ if (qcdev->i2c_max_frame_size_enable) {
+ if (qcdev->i2c_max_frame_size >=
+ le16_to_cpu(qcdev->dev_desc.max_input_len)) {
+ thc_i2c_set_rx_max_size(qcdev->thc_hw,
+ le16_to_cpu(qcdev->dev_desc.max_input_len));
+ } else {
+ dev_warn(qcdev->dev,
+ "Max frame size is smaller than hid max input length!");
+ thc_i2c_set_rx_max_size(qcdev->thc_hw,
+ le16_to_cpu(qcdev->i2c_max_frame_size));
+ }
thc_i2c_rx_max_size_enable(qcdev->thc_hw, true);
}
/* If platform supports interrupt delay feature, enable it with given delay */
- if (qcdev->ddata->interrupt_delay) {
+ if (qcdev->i2c_int_delay_enable) {
thc_i2c_set_rx_int_delay(qcdev->thc_hw,
- qcdev->ddata->interrupt_delay);
+ qcdev->i2c_int_delay * 10);
thc_i2c_rx_int_delay_enable(qcdev->thc_hw, true);
}
}
@@ -464,10 +487,10 @@ static void quicki2c_dma_adv_enable(struct quicki2c_device *qcdev)
*/
static void quicki2c_dma_adv_disable(struct quicki2c_device *qcdev)
{
- if (qcdev->ddata->max_detect_size)
+ if (qcdev->i2c_max_frame_size_enable)
thc_i2c_rx_max_size_enable(qcdev->thc_hw, false);
- if (qcdev->ddata->interrupt_delay)
+ if (qcdev->i2c_int_delay_enable)
thc_i2c_rx_int_delay_enable(qcdev->thc_hw, false);
}
diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h
index 4e60a7de4727..2cb5471a8133 100644
--- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h
+++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h
@@ -40,6 +40,8 @@
/* PTL Max packet size detection capability is 255 Bytes */
#define MAX_RX_DETECT_SIZE_PTL 255
+/* Max interrupt delay capability is 2.56ms */
+#define MAX_RX_INTERRUPT_DELAY 256
/* Default interrupt delay is 1ms, suitable for most devices */
#define DEFAULT_INTERRUPT_DELAY_US (1 * USEC_PER_MSEC)
@@ -103,6 +105,10 @@ struct quicki2c_subip_acpi_parameter {
* @HMTD: High Speed Mode Plus (3.4Mbits/sec) Serial Data Line Transmit HOLD Period
* @HMRD: High Speed Mode Plus (3.4Mbits/sec) Serial Data Line Receive HOLD Period
* @HMSL: Maximum length (in ic_clk_cycles) of suppressed spikes in High Speed Mode
+ * @FSEN: Maximum Frame Size Feature Enable Control
+ * @FSVL: Maximum Frame Size Value (unit in Bytes)
+ * @INDE: Interrupt Delay Feature Enable Control
+ * @INDV: Interrupt Delay Value (unit in 10 us)
*
* Those properties get from QUICKI2C_ACPI_METHOD_NAME_ISUB method, used for
* I2C timing configure.
@@ -129,17 +135,22 @@ struct quicki2c_subip_acpi_config {
u64 HMTD;
u64 HMRD;
u64 HMSL;
+
+ u64 FSEN;
+ u64 FSVL;
+ u64 INDE;
+ u64 INDV;
u8 reserved;
};
/**
* struct quicki2c_ddata - Driver specific data for quicki2c device
* @max_detect_size: Identify max packet size detect for rx
- * @interrupt_delay: Identify interrupt detect delay for rx
+ * @interrupt_delay: Identify max interrupt detect delay for rx
*/
struct quicki2c_ddata {
u32 max_detect_size;
- u32 interrupt_delay;
+ u32 max_interrupt_delay;
};
struct device;
@@ -172,6 +183,10 @@ struct acpi_device;
* @report_len: The length of input/output report packet
* @reset_ack_wq: Workqueue for waiting reset response from device
* @reset_ack: Indicate reset response received or not
+ * @i2c_max_frame_size_enable: Indicate max frame size feature enabled or not
+ * @i2c_max_frame_size: Max RX frame size (unit in Bytes)
+ * @i2c_int_delay_enable: Indicate interrupt delay feature enabled or not
+ * @i2c_int_delay: Interrupt detection delay value (unit in 10 us)
*/
struct quicki2c_device {
struct device *dev;
@@ -202,6 +217,11 @@ struct quicki2c_device {
wait_queue_head_t reset_ack_wq;
bool reset_ack;
+
+ u32 i2c_max_frame_size_enable;
+ u32 i2c_max_frame_size;
+ u32 i2c_int_delay_enable;
+ u32 i2c_int_delay;
};
#endif /* _QUICKI2C_DEV_H_ */
diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c
index e1cb9b117ebc..636a68306501 100644
--- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c
+++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c
@@ -4,6 +4,7 @@
#include <linux/bitfield.h>
#include <linux/math.h>
#include <linux/regmap.h>
+#include <linux/string_choices.h>
#include "intel-thc-dev.h"
#include "intel-thc-hw.h"
@@ -664,7 +665,7 @@ int thc_interrupt_quiesce(const struct thc_device *dev, bool int_quiesce)
if (ret) {
dev_err_once(dev->dev,
"Timeout while waiting THC idle, target quiesce state = %s\n",
- int_quiesce ? "true" : "false");
+ str_true_false(int_quiesce));
return ret;
}
diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c
index 614a20b62023..edd61ef50e16 100644
--- a/drivers/hid/usbhid/hid-pidff.c
+++ b/drivers/hid/usbhid/hid-pidff.c
@@ -9,12 +9,11 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include "hid-pidff.h"
+#include <linux/hid.h>
#include <linux/input.h>
+#include <linux/minmax.h>
#include <linux/slab.h>
#include <linux/usb.h>
-#include <linux/hid.h>
-#include <linux/minmax.h>
-
#define PID_EFFECTS_MAX 64
#define PID_INFINITE U16_MAX
@@ -33,7 +32,7 @@
#define PID_DEVICE_CONTROL 6
#define PID_CREATE_NEW_EFFECT 7
-#define PID_REQUIRED_REPORTS 7
+#define PID_REQUIRED_REPORTS 8
#define PID_SET_ENVELOPE 8
#define PID_SET_CONDITION 9
@@ -51,6 +50,7 @@ static const u8 pidff_reports[] = {
/* PID special fields */
#define PID_EFFECT_TYPE 0x25
+#define PID_AXES_ENABLE 0x55
#define PID_DIRECTION 0x57
#define PID_EFFECT_OPERATION_ARRAY 0x78
#define PID_BLOCK_LOAD_STATUS 0x8b
@@ -141,37 +141,74 @@ static const u8 pidff_effect_types[] = {
#define PID_BLOCK_LOAD_SUCCESS 0
#define PID_BLOCK_LOAD_FULL 1
#define PID_BLOCK_LOAD_ERROR 2
-static const u8 pidff_block_load_status[] = { 0x8c, 0x8d, 0x8e};
+static const u8 pidff_block_load_status[] = { 0x8c, 0x8d, 0x8e };
#define PID_EFFECT_START 0
#define PID_EFFECT_STOP 1
static const u8 pidff_effect_operation_status[] = { 0x79, 0x7b };
-/* Polar direction 90 degrees (East) */
-#define PIDFF_FIXED_WHEEL_DIRECTION 0x4000
+#define PID_DIRECTION_NORTH 0x0000
+#define PID_DIRECTION_EAST 0x4000
+#define PID_DIRECTION_SOUTH 0x8000
+#define PID_DIRECTION_WEST 0xc000
+
+#define PIDFF_FIXED_WHEEL_DIRECTION PID_DIRECTION_EAST
+
+/* AXES_ENABLE and DIRECTION axes */
+enum pid_axes {
+ PID_AXIS_X,
+ PID_AXIS_Y,
+ PID_AXIS_Z,
+ PID_AXIS_RX,
+ PID_AXIS_RY,
+ PID_AXIS_RZ,
+ PID_AXIS_SLIDER,
+ PID_AXIS_DIAL,
+ PID_AXIS_WHEEL,
+ PID_AXES_COUNT,
+};
+static const u8 pidff_direction_axis[] = {
+ HID_USAGE & HID_GD_X,
+ HID_USAGE & HID_GD_Y,
+ HID_USAGE & HID_GD_Z,
+ HID_USAGE & HID_GD_RX,
+ HID_USAGE & HID_GD_RY,
+ HID_USAGE & HID_GD_RZ,
+ HID_USAGE & HID_GD_SLIDER,
+ HID_USAGE & HID_GD_DIAL,
+ HID_USAGE & HID_GD_WHEEL,
+};
struct pidff_usage {
struct hid_field *field;
s32 *value;
};
+struct pidff_effect {
+ int pid_id;
+ int is_infinite;
+ unsigned int loop_count;
+};
+
struct pidff_device {
struct hid_device *hid;
- struct hid_report *reports[sizeof(pidff_reports)];
+ struct hid_report *reports[ARRAY_SIZE(pidff_reports)];
- struct pidff_usage set_effect[sizeof(pidff_set_effect)];
- struct pidff_usage set_envelope[sizeof(pidff_set_envelope)];
- struct pidff_usage set_condition[sizeof(pidff_set_condition)];
- struct pidff_usage set_periodic[sizeof(pidff_set_periodic)];
- struct pidff_usage set_constant[sizeof(pidff_set_constant)];
- struct pidff_usage set_ramp[sizeof(pidff_set_ramp)];
+ struct pidff_usage set_effect[ARRAY_SIZE(pidff_set_effect)];
+ struct pidff_usage set_envelope[ARRAY_SIZE(pidff_set_envelope)];
+ struct pidff_usage set_condition[ARRAY_SIZE(pidff_set_condition)];
+ struct pidff_usage set_periodic[ARRAY_SIZE(pidff_set_periodic)];
+ struct pidff_usage set_constant[ARRAY_SIZE(pidff_set_constant)];
+ struct pidff_usage set_ramp[ARRAY_SIZE(pidff_set_ramp)];
- struct pidff_usage device_gain[sizeof(pidff_device_gain)];
- struct pidff_usage block_load[sizeof(pidff_block_load)];
- struct pidff_usage pool[sizeof(pidff_pool)];
- struct pidff_usage effect_operation[sizeof(pidff_effect_operation)];
- struct pidff_usage block_free[sizeof(pidff_block_free)];
+ struct pidff_usage device_gain[ARRAY_SIZE(pidff_device_gain)];
+ struct pidff_usage block_load[ARRAY_SIZE(pidff_block_load)];
+ struct pidff_usage pool[ARRAY_SIZE(pidff_pool)];
+ struct pidff_usage effect_operation[ARRAY_SIZE(pidff_effect_operation)];
+ struct pidff_usage block_free[ARRAY_SIZE(pidff_block_free)];
+
+ struct pidff_effect effect[PID_EFFECTS_MAX];
/*
* Special field is a field that is not composed of
@@ -184,6 +221,7 @@ struct pidff_device {
/* Special fields in set_effect */
struct hid_field *set_effect_type;
struct hid_field *effect_direction;
+ struct hid_field *axes_enable;
/* Special field in device_control */
struct hid_field *device_control;
@@ -194,17 +232,86 @@ struct pidff_device {
/* Special field in effect_operation */
struct hid_field *effect_operation_status;
- int control_id[sizeof(pidff_device_control)];
- int type_id[sizeof(pidff_effect_types)];
- int status_id[sizeof(pidff_block_load_status)];
- int operation_id[sizeof(pidff_effect_operation_status)];
-
- int pid_id[PID_EFFECTS_MAX];
+ int control_id[ARRAY_SIZE(pidff_device_control)];
+ int type_id[ARRAY_SIZE(pidff_effect_types)];
+ int status_id[ARRAY_SIZE(pidff_block_load_status)];
+ int operation_id[ARRAY_SIZE(pidff_effect_operation_status)];
+ int direction_axis_id[ARRAY_SIZE(pidff_direction_axis)];
u32 quirks;
u8 effect_count;
+ u8 axis_count;
};
+static int pidff_is_effect_conditional(struct ff_effect *effect)
+{
+ return effect->type == FF_SPRING ||
+ effect->type == FF_DAMPER ||
+ effect->type == FF_INERTIA ||
+ effect->type == FF_FRICTION;
+}
+
+static int pidff_is_duration_infinite(u16 duration)
+{
+ return duration == FF_INFINITE || duration == PID_INFINITE;
+}
+
+/*
+ * Get PID effect index from FF effect type.
+ * Return 0 if invalid.
+ */
+static int pidff_effect_ff_to_pid(struct ff_effect *effect)
+{
+ switch (effect->type) {
+ case FF_CONSTANT:
+ return PID_CONSTANT;
+ case FF_RAMP:
+ return PID_RAMP;
+ case FF_SPRING:
+ return PID_SPRING;
+ case FF_DAMPER:
+ return PID_DAMPER;
+ case FF_INERTIA:
+ return PID_INERTIA;
+ case FF_FRICTION:
+ return PID_FRICTION;
+ case FF_PERIODIC:
+ switch (effect->u.periodic.waveform) {
+ case FF_SQUARE:
+ return PID_SQUARE;
+ case FF_TRIANGLE:
+ return PID_TRIANGLE;
+ case FF_SINE:
+ return PID_SINE;
+ case FF_SAW_UP:
+ return PID_SAW_UP;
+ case FF_SAW_DOWN:
+ return PID_SAW_DOWN;
+ }
+ }
+ pr_err("invalid effect type\n");
+ return -EINVAL;
+}
+
+/*
+ * Get effect id in the device descriptor.
+ * Return 0 if invalid.
+ */
+static int pidff_get_effect_type_id(struct pidff_device *pidff,
+ struct ff_effect *effect)
+{
+ int id = pidff_effect_ff_to_pid(effect);
+
+ if (id < 0)
+ return 0;
+
+ if (effect->type == FF_PERIODIC &&
+ pidff->quirks & HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY)
+ id = PID_SINE;
+
+ return pidff->type_id[id];
+}
+
/*
* Clamp value for a given field
*/
@@ -219,7 +326,7 @@ static s32 pidff_clamp(s32 i, struct hid_field *field)
static int pidff_rescale(int i, int max, struct hid_field *field)
{
return i * (field->logical_maximum - field->logical_minimum) / max +
- field->logical_minimum;
+ field->logical_minimum;
}
/*
@@ -265,28 +372,24 @@ static void pidff_set_signed(struct pidff_usage *usage, s16 value)
else {
if (value < 0)
usage->value[0] =
- pidff_rescale(-value, -S16_MIN, usage->field);
+ pidff_rescale(-value, -S16_MIN, usage->field);
else
usage->value[0] =
- pidff_rescale(value, S16_MAX, usage->field);
+ pidff_rescale(value, S16_MAX, usage->field);
}
pr_debug("calculated from %d to %d\n", value, usage->value[0]);
}
static void pidff_set_time(struct pidff_usage *usage, u16 time)
{
- usage->value[0] = pidff_clamp(
- pidff_rescale_time(time, usage->field), usage->field);
+ usage->value[0] = pidff_clamp(pidff_rescale_time(time, usage->field),
+ usage->field);
}
static void pidff_set_duration(struct pidff_usage *usage, u16 duration)
{
- /* Infinite value conversion from Linux API -> PID */
- if (duration == FF_INFINITE)
- duration = PID_INFINITE;
-
/* PID defines INFINITE as the max possible value for duration field */
- if (duration == PID_INFINITE) {
+ if (pidff_is_duration_infinite(duration)) {
usage->value[0] = (1U << usage->field->report_size) - 1;
return;
}
@@ -294,6 +397,43 @@ static void pidff_set_duration(struct pidff_usage *usage, u16 duration)
pidff_set_time(usage, duration);
}
+static void pidff_set_effect_direction(struct pidff_device *pidff,
+ struct ff_effect *effect)
+{
+ u16 direction = effect->direction;
+ int direction_enable = 1;
+
+ /* Use fixed direction if needed */
+ if (pidff->quirks & HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION &&
+ pidff_is_effect_conditional(effect))
+ direction = PIDFF_FIXED_WHEEL_DIRECTION;
+
+ pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = direction_enable;
+ pidff->effect_direction->value[0] =
+ pidff_rescale(direction, U16_MAX, pidff->effect_direction);
+
+ if (direction_enable)
+ return;
+
+ /*
+ * For use with improved FFB API
+ * We want to read the selected axes and their direction from the effect
+ * struct and only enable those. For now, enable all axes.
+ *
+ */
+ for (int i = 0; i < PID_AXES_COUNT; i++) {
+ /* HID index starts with 1 */
+ int index = pidff->direction_axis_id[i] - 1;
+
+ if (index < 0)
+ continue;
+
+ pidff->axes_enable->value[index] = 1;
+ pidff->effect_direction->value[index] = pidff_rescale(
+ direction, U16_MAX, pidff->effect_direction);
+ }
+}
+
/*
* Send envelope report to the device
*/
@@ -313,16 +453,12 @@ static void pidff_set_envelope_report(struct pidff_device *pidff,
pidff->set_envelope[PID_FADE_LEVEL].field);
pidff_set_time(&pidff->set_envelope[PID_ATTACK_TIME],
- envelope->attack_length);
+ envelope->attack_length);
pidff_set_time(&pidff->set_envelope[PID_FADE_TIME],
- envelope->fade_length);
-
- hid_dbg(pidff->hid, "attack %u => %d\n",
- envelope->attack_level,
- pidff->set_envelope[PID_ATTACK_LEVEL].value[0]);
+ envelope->fade_length);
hid_hw_request(pidff->hid, pidff->reports[PID_SET_ENVELOPE],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
}
/*
@@ -331,7 +467,7 @@ static void pidff_set_envelope_report(struct pidff_device *pidff,
static int pidff_needs_set_envelope(struct ff_envelope *envelope,
struct ff_envelope *old)
{
- bool needs_new_envelope;
+ int needs_new_envelope;
needs_new_envelope = envelope->attack_level != 0 ||
envelope->fade_level != 0 ||
@@ -339,8 +475,7 @@ static int pidff_needs_set_envelope(struct ff_envelope *envelope,
envelope->fade_length != 0;
if (!needs_new_envelope)
- return false;
-
+ return 0;
if (!old)
return needs_new_envelope;
@@ -353,8 +488,8 @@ static int pidff_needs_set_envelope(struct ff_envelope *envelope,
/*
* Send constant force report to the device
*/
-static void pidff_set_constant_force_report(struct pidff_device *pidff,
- struct ff_effect *effect)
+static void pidff_set_constant_report(struct pidff_device *pidff,
+ struct ff_effect *effect)
{
pidff->set_constant[PID_EFFECT_BLOCK_INDEX].value[0] =
pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
@@ -362,7 +497,7 @@ static void pidff_set_constant_force_report(struct pidff_device *pidff,
effect->u.constant.level);
hid_hw_request(pidff->hid, pidff->reports[PID_SET_CONSTANT],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
}
/*
@@ -386,28 +521,23 @@ static void pidff_set_effect_report(struct pidff_device *pidff,
pidff->create_new_effect_type->value[0];
pidff_set_duration(&pidff->set_effect[PID_DURATION],
- effect->replay.length);
+ effect->replay.length);
pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = effect->trigger.button;
pidff_set_time(&pidff->set_effect[PID_TRIGGER_REPEAT_INT],
- effect->trigger.interval);
+ effect->trigger.interval);
pidff->set_effect[PID_GAIN].value[0] =
pidff->set_effect[PID_GAIN].field->logical_maximum;
- pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = 1;
- /* Use fixed direction if needed */
- pidff->effect_direction->value[0] = pidff_rescale(
- pidff->quirks & HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION ?
- PIDFF_FIXED_WHEEL_DIRECTION : effect->direction,
- U16_MAX, pidff->effect_direction);
+ pidff_set_effect_direction(pidff, effect);
/* Omit setting delay field if it's missing */
if (!(pidff->quirks & HID_PIDFF_QUIRK_MISSING_DELAY))
pidff_set_time(&pidff->set_effect[PID_START_DELAY],
- effect->replay.delay);
+ effect->replay.delay);
hid_hw_request(pidff->hid, pidff->reports[PID_SET_EFFECT],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
}
/*
@@ -437,10 +567,10 @@ static void pidff_set_periodic_report(struct pidff_device *pidff,
effect->u.periodic.offset);
pidff_set(&pidff->set_periodic[PID_PHASE], effect->u.periodic.phase);
pidff_set_time(&pidff->set_periodic[PID_PERIOD],
- effect->u.periodic.period);
+ effect->u.periodic.period);
hid_hw_request(pidff->hid, pidff->reports[PID_SET_PERIODIC],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
}
/*
@@ -487,7 +617,7 @@ static void pidff_set_condition_report(struct pidff_device *pidff,
pidff_set(&pidff->set_condition[PID_DEAD_BAND],
effect->u.condition[i].deadband);
hid_hw_request(pidff->hid, pidff->reports[PID_SET_CONDITION],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
}
}
@@ -518,8 +648,8 @@ static int pidff_needs_set_condition(struct ff_effect *effect,
/*
* Send ramp force report to the device
*/
-static void pidff_set_ramp_force_report(struct pidff_device *pidff,
- struct ff_effect *effect)
+static void pidff_set_ramp_report(struct pidff_device *pidff,
+ struct ff_effect *effect)
{
pidff->set_ramp[PID_EFFECT_BLOCK_INDEX].value[0] =
pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
@@ -528,7 +658,7 @@ static void pidff_set_ramp_force_report(struct pidff_device *pidff,
pidff_set_signed(&pidff->set_ramp[PID_RAMP_END],
effect->u.ramp.end_level);
hid_hw_request(pidff->hid, pidff->reports[PID_SET_RAMP],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
}
/*
@@ -550,7 +680,7 @@ static void pidff_set_gain_report(struct pidff_device *pidff, u16 gain)
pidff_set(&pidff->device_gain[PID_DEVICE_GAIN_FIELD], gain);
hid_hw_request(pidff->hid, pidff->reports[PID_DEVICE_GAIN],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
}
/*
@@ -558,8 +688,7 @@ static void pidff_set_gain_report(struct pidff_device *pidff, u16 gain)
*/
static void pidff_set_device_control(struct pidff_device *pidff, int field)
{
- int i, index;
- int field_index = pidff->control_id[field];
+ const int field_index = pidff->control_id[field];
if (field_index < 1)
return;
@@ -569,8 +698,9 @@ static void pidff_set_device_control(struct pidff_device *pidff, int field)
hid_dbg(pidff->hid, "DEVICE_CONTROL is a bitmask\n");
/* Clear current bitmask */
- for (i = 0; i < sizeof(pidff_device_control); i++) {
- index = pidff->control_id[i];
+ for (int i = 0; i < ARRAY_SIZE(pidff_device_control); i++) {
+ int index = pidff->control_id[i];
+
if (index < 1)
continue;
@@ -585,16 +715,8 @@ static void pidff_set_device_control(struct pidff_device *pidff, int field)
hid_hw_request(pidff->hid, pidff->reports[PID_DEVICE_CONTROL], HID_REQ_SET_REPORT);
hid_hw_wait(pidff->hid);
-}
-
-/*
- * Modify actuators state
- */
-static void pidff_set_actuators(struct pidff_device *pidff, bool enable)
-{
- hid_dbg(pidff->hid, "%s actuators\n", enable ? "Enable" : "Disable");
- pidff_set_device_control(pidff,
- enable ? PID_ENABLE_ACTUATORS : PID_DISABLE_ACTUATORS);
+ hid_dbg(pidff->hid, "Device control command 0x%02x sent",
+ pidff_device_control[field]);
}
/*
@@ -608,7 +730,7 @@ static void pidff_reset(struct pidff_device *pidff)
pidff->effect_count = 0;
pidff_set_device_control(pidff, PID_STOP_ALL_EFFECTS);
- pidff_set_actuators(pidff, 1);
+ pidff_set_device_control(pidff, PID_ENABLE_ACTUATORS);
}
/*
@@ -644,32 +766,25 @@ static void pidff_fetch_pool(struct pidff_device *pidff)
*/
static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum)
{
- int j;
-
- if (!pidff->effect_count)
- pidff_reset(pidff);
-
pidff->create_new_effect_type->value[0] = efnum;
hid_hw_request(pidff->hid, pidff->reports[PID_CREATE_NEW_EFFECT],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
hid_dbg(pidff->hid, "create_new_effect sent, type: %d\n", efnum);
pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0;
pidff->block_load_status->value[0] = 0;
hid_hw_wait(pidff->hid);
- for (j = 0; j < 60; j++) {
+ for (int i = 0; i < 60; i++) {
hid_dbg(pidff->hid, "pid_block_load requested\n");
hid_hw_request(pidff->hid, pidff->reports[PID_BLOCK_LOAD],
- HID_REQ_GET_REPORT);
+ HID_REQ_GET_REPORT);
hid_hw_wait(pidff->hid);
if (pidff->block_load_status->value[0] ==
pidff->status_id[PID_BLOCK_LOAD_SUCCESS]) {
hid_dbg(pidff->hid, "device reported free memory: %d bytes\n",
pidff->block_load[PID_RAM_POOL_AVAILABLE].value ?
pidff->block_load[PID_RAM_POOL_AVAILABLE].value[0] : -1);
-
- pidff->effect_count++;
return 0;
}
if (pidff->block_load_status->value[0] ==
@@ -689,6 +804,12 @@ static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum)
return -EIO;
}
+static int pidff_needs_playback(struct pidff_device *pidff, int effect_id, int n)
+{
+ return pidff->effect[effect_id].is_infinite ||
+ pidff->effect[effect_id].loop_count != n;
+}
+
/*
* Play the effect with PID id n times
*/
@@ -696,6 +817,9 @@ static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n)
{
pidff->effect_operation[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id;
+ hid_dbg(pidff->hid, "%s PID effect %d", n == 0 ? "stopping" : "playing",
+ pid_id);
+
if (n == 0) {
pidff->effect_operation_status->value[0] =
pidff->operation_id[PID_EFFECT_STOP];
@@ -707,7 +831,7 @@ static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n)
}
hid_hw_request(pidff->hid, pidff->reports[PID_EFFECT_OPERATION],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
}
/*
@@ -717,7 +841,14 @@ static int pidff_playback(struct input_dev *dev, int effect_id, int value)
{
struct pidff_device *pidff = dev->ff->private;
- pidff_playback_pid(pidff, pidff->pid_id[effect_id], value);
+ if (!pidff_needs_playback(pidff, effect_id, value))
+ return 0;
+
+ hid_dbg(pidff->hid, "requesting %s on FF effect %d",
+ value == 0 ? "stop" : "playback", effect_id);
+
+ pidff->effect[effect_id].loop_count = value;
+ pidff_playback_pid(pidff, pidff->effect[effect_id].pid_id, value);
return 0;
}
@@ -729,10 +860,7 @@ static void pidff_erase_pid(struct pidff_device *pidff, int pid_id)
{
pidff->block_free[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id;
hid_hw_request(pidff->hid, pidff->reports[PID_BLOCK_FREE],
- HID_REQ_SET_REPORT);
-
- if (pidff->effect_count > 0)
- pidff->effect_count--;
+ HID_REQ_SET_REPORT);
}
/*
@@ -741,10 +869,9 @@ static void pidff_erase_pid(struct pidff_device *pidff, int pid_id)
static int pidff_erase_effect(struct input_dev *dev, int effect_id)
{
struct pidff_device *pidff = dev->ff->private;
- int pid_id = pidff->pid_id[effect_id];
+ int pid_id = pidff->effect[effect_id].pid_id;
- hid_dbg(pidff->hid, "starting to erase %d/%d\n",
- effect_id, pidff->pid_id[effect_id]);
+ hid_dbg(pidff->hid, "starting to erase %d/%d\n", effect_id, pid_id);
/*
* Wait for the queue to clear. We do not want
@@ -754,139 +881,83 @@ static int pidff_erase_effect(struct input_dev *dev, int effect_id)
pidff_playback_pid(pidff, pid_id, 0);
pidff_erase_pid(pidff, pid_id);
+ if (pidff->effect_count > 0)
+ pidff->effect_count--;
+
+ hid_dbg(pidff->hid, "current effect count: %d", pidff->effect_count);
return 0;
}
+#define PIDFF_SET_REPORT_IF_NEEDED(type, effect, old) \
+ ({ if (!old || pidff_needs_set_## type(effect, old)) \
+ pidff_set_ ##type## _report(pidff, effect); })
+
+#define PIDFF_SET_ENVELOPE_IF_NEEDED(type, effect, old) \
+ ({ if (pidff_needs_set_envelope(&effect->u.type.envelope, \
+ old ? &old->u.type.envelope : NULL)) \
+ pidff_set_envelope_report(pidff, &effect->u.type.envelope); })
+
/*
* Effect upload handler
*/
-static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
+static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *new,
struct ff_effect *old)
{
struct pidff_device *pidff = dev->ff->private;
- int type_id;
- int error;
+ const int type_id = pidff_get_effect_type_id(pidff, new);
- pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0;
- if (old) {
- pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] =
- pidff->pid_id[effect->id];
+ if (!type_id) {
+ hid_err(pidff->hid, "effect type not supported\n");
+ return -EINVAL;
}
- switch (effect->type) {
+ if (!pidff->effect_count)
+ pidff_reset(pidff);
+
+ if (!old) {
+ int error = pidff_request_effect_upload(pidff, type_id);
+
+ if (error)
+ return error;
+
+ pidff->effect_count++;
+ hid_dbg(pidff->hid, "current effect count: %d", pidff->effect_count);
+ pidff->effect[new->id].loop_count = 0;
+ pidff->effect[new->id].pid_id =
+ pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+ }
+
+ pidff->effect[new->id].is_infinite =
+ pidff_is_duration_infinite(new->replay.length);
+
+ pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] =
+ pidff->effect[new->id].pid_id;
+
+ PIDFF_SET_REPORT_IF_NEEDED(effect, new, old);
+ switch (new->type) {
case FF_CONSTANT:
- if (!old) {
- error = pidff_request_effect_upload(pidff,
- pidff->type_id[PID_CONSTANT]);
- if (error)
- return error;
- }
- if (!old || pidff_needs_set_effect(effect, old))
- pidff_set_effect_report(pidff, effect);
- if (!old || pidff_needs_set_constant(effect, old))
- pidff_set_constant_force_report(pidff, effect);
- if (pidff_needs_set_envelope(&effect->u.constant.envelope,
- old ? &old->u.constant.envelope : NULL))
- pidff_set_envelope_report(pidff, &effect->u.constant.envelope);
+ PIDFF_SET_REPORT_IF_NEEDED(constant, new, old);
+ PIDFF_SET_ENVELOPE_IF_NEEDED(constant, new, old);
break;
case FF_PERIODIC:
- if (!old) {
- switch (effect->u.periodic.waveform) {
- case FF_SQUARE:
- type_id = PID_SQUARE;
- break;
- case FF_TRIANGLE:
- type_id = PID_TRIANGLE;
- break;
- case FF_SINE:
- type_id = PID_SINE;
- break;
- case FF_SAW_UP:
- type_id = PID_SAW_UP;
- break;
- case FF_SAW_DOWN:
- type_id = PID_SAW_DOWN;
- break;
- default:
- hid_err(pidff->hid, "invalid waveform\n");
- return -EINVAL;
- }
-
- if (pidff->quirks & HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY)
- type_id = PID_SINE;
-
- error = pidff_request_effect_upload(pidff,
- pidff->type_id[type_id]);
- if (error)
- return error;
- }
- if (!old || pidff_needs_set_effect(effect, old))
- pidff_set_effect_report(pidff, effect);
- if (!old || pidff_needs_set_periodic(effect, old))
- pidff_set_periodic_report(pidff, effect);
- if (pidff_needs_set_envelope(&effect->u.periodic.envelope,
- old ? &old->u.periodic.envelope : NULL))
- pidff_set_envelope_report(pidff, &effect->u.periodic.envelope);
+ PIDFF_SET_REPORT_IF_NEEDED(periodic, new, old);
+ PIDFF_SET_ENVELOPE_IF_NEEDED(periodic, new, old);
break;
case FF_RAMP:
- if (!old) {
- error = pidff_request_effect_upload(pidff,
- pidff->type_id[PID_RAMP]);
- if (error)
- return error;
- }
- if (!old || pidff_needs_set_effect(effect, old))
- pidff_set_effect_report(pidff, effect);
- if (!old || pidff_needs_set_ramp(effect, old))
- pidff_set_ramp_force_report(pidff, effect);
- if (pidff_needs_set_envelope(&effect->u.ramp.envelope,
- old ? &old->u.ramp.envelope : NULL))
- pidff_set_envelope_report(pidff, &effect->u.ramp.envelope);
+ PIDFF_SET_REPORT_IF_NEEDED(ramp, new, old);
+ PIDFF_SET_ENVELOPE_IF_NEEDED(ramp, new, old);
break;
case FF_SPRING:
case FF_DAMPER:
case FF_INERTIA:
case FF_FRICTION:
- if (!old) {
- switch (effect->type) {
- case FF_SPRING:
- type_id = PID_SPRING;
- break;
- case FF_DAMPER:
- type_id = PID_DAMPER;
- break;
- case FF_INERTIA:
- type_id = PID_INERTIA;
- break;
- case FF_FRICTION:
- type_id = PID_FRICTION;
- break;
- }
- error = pidff_request_effect_upload(pidff,
- pidff->type_id[type_id]);
- if (error)
- return error;
- }
- if (!old || pidff_needs_set_effect(effect, old))
- pidff_set_effect_report(pidff, effect);
- if (!old || pidff_needs_set_condition(effect, old))
- pidff_set_condition_report(pidff, effect);
+ PIDFF_SET_REPORT_IF_NEEDED(condition, new, old);
break;
-
- default:
- hid_err(pidff->hid, "invalid type\n");
- return -EINVAL;
}
-
- if (!old)
- pidff->pid_id[effect->id] =
- pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
-
hid_dbg(pidff->hid, "uploaded\n");
-
return 0;
}
@@ -924,7 +995,7 @@ static void pidff_autocenter(struct pidff_device *pidff, u16 magnitude)
pidff->set_effect[PID_START_DELAY].value[0] = 0;
hid_hw_request(pidff->hid, pidff->reports[PID_SET_EFFECT],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
}
/*
@@ -936,56 +1007,85 @@ static void pidff_set_autocenter(struct input_dev *dev, u16 magnitude)
}
/*
+ * Find specific usage in a given hid_field
+ */
+static int pidff_find_usage(struct hid_field *fld, unsigned int usage_code)
+{
+ for (int i = 0; i < fld->maxusage; i++) {
+ if (fld->usage[i].hid == usage_code)
+ return i;
+ }
+ return -1;
+}
+
+/*
+ * Find hid_field with a specific usage. Return the usage index as well
+ */
+static int pidff_find_field_with_usage(int *usage_index,
+ struct hid_report *report,
+ unsigned int usage_code)
+{
+ for (int i = 0; i < report->maxfield; i++) {
+ struct hid_field *fld = report->field[i];
+
+ if (fld->maxusage != fld->report_count) {
+ pr_debug("maxusage and report_count do not match, skipping\n");
+ continue;
+ }
+
+ int index = pidff_find_usage(fld, usage_code);
+
+ if (index >= 0) {
+ *usage_index = index;
+ return i;
+ }
+ }
+ return -1;
+}
+
+/*
* Find fields from a report and fill a pidff_usage
*/
static int pidff_find_fields(struct pidff_usage *usage, const u8 *table,
- struct hid_report *report, int count, int strict)
+ struct hid_report *report, int count, int strict,
+ u32 *quirks)
{
+ const u8 block_offset = pidff_set_condition[PID_PARAM_BLOCK_OFFSET];
+ const u8 delay = pidff_set_effect[PID_START_DELAY];
+
if (!report) {
pr_debug("%s, null report\n", __func__);
return -1;
}
- int i, j, k, found;
- int return_value = 0;
+ for (int i = 0; i < count; i++) {
+ int index;
+ int found = pidff_find_field_with_usage(&index, report,
+ HID_UP_PID | table[i]);
- for (k = 0; k < count; k++) {
- found = 0;
- for (i = 0; i < report->maxfield; i++) {
- if (report->field[i]->maxusage !=
- report->field[i]->report_count) {
- pr_debug("maxusage and report_count do not match, skipping\n");
- continue;
- }
- for (j = 0; j < report->field[i]->maxusage; j++) {
- if (report->field[i]->usage[j].hid ==
- (HID_UP_PID | table[k])) {
- pr_debug("found %d at %d->%d\n",
- k, i, j);
- usage[k].field = report->field[i];
- usage[k].value =
- &report->field[i]->value[j];
- found = 1;
- break;
- }
- }
- if (found)
- break;
+ if (found >= 0) {
+ pr_debug("found %d at %d->%d\n", i, found, index);
+ usage[i].field = report->field[found];
+ usage[i].value = &report->field[found]->value[index];
+ continue;
}
- if (!found && table[k] == pidff_set_effect[PID_START_DELAY]) {
+
+ if (table[i] == delay) {
pr_debug("Delay field not found, but that's OK\n");
pr_debug("Setting MISSING_DELAY quirk\n");
- return_value |= HID_PIDFF_QUIRK_MISSING_DELAY;
- } else if (!found && table[k] == pidff_set_condition[PID_PARAM_BLOCK_OFFSET]) {
+ *quirks |= HID_PIDFF_QUIRK_MISSING_DELAY;
+
+ } else if (table[i] == block_offset) {
pr_debug("PBO field not found, but that's OK\n");
pr_debug("Setting MISSING_PBO quirk\n");
- return_value |= HID_PIDFF_QUIRK_MISSING_PBO;
- } else if (!found && strict) {
- pr_debug("failed to locate %d\n", k);
+ *quirks |= HID_PIDFF_QUIRK_MISSING_PBO;
+
+ } else if (strict) {
+ pr_debug("failed to locate %d\n", i);
return -1;
}
}
- return return_value;
+ return 0;
}
/*
@@ -995,7 +1095,7 @@ static int pidff_check_usage(int usage)
{
int i;
- for (i = 0; i < sizeof(pidff_reports); i++)
+ for (i = 0; i < ARRAY_SIZE(pidff_reports); i++)
if (usage == (HID_UP_PID | pidff_reports[i]))
return i;
@@ -1050,9 +1150,7 @@ static void pidff_find_reports(struct hid_device *hid, int report_type,
*/
static int pidff_reports_ok(struct pidff_device *pidff)
{
- int i;
-
- for (i = 0; i <= PID_REQUIRED_REPORTS; i++) {
+ for (int i = 0; i < PID_REQUIRED_REPORTS; i++) {
if (!pidff->reports[i]) {
hid_dbg(pidff->hid, "%d missing\n", i);
return 0;
@@ -1073,9 +1171,7 @@ static struct hid_field *pidff_find_special_field(struct hid_report *report,
return NULL;
}
- int i;
-
- for (i = 0; i < report->maxfield; i++) {
+ for (int i = 0; i < report->maxfield; i++) {
if (report->field[i]->logical == (HID_UP_PID | usage) &&
report->field[i]->report_count > 0) {
if (!enforce_min ||
@@ -1093,27 +1189,29 @@ static struct hid_field *pidff_find_special_field(struct hid_report *report,
* Fill a pidff->*_id struct table
*/
static int pidff_find_special_keys(int *keys, struct hid_field *fld,
- const u8 *usagetable, int count)
+ const u8 *usagetable, int count,
+ unsigned int usage_page)
{
-
- int i, j;
int found = 0;
- for (i = 0; i < count; i++) {
- for (j = 0; j < fld->maxusage; j++) {
- if (fld->usage[j].hid == (HID_UP_PID | usagetable[i])) {
- keys[i] = j + 1;
- found++;
- break;
- }
- }
+ if (!fld)
+ return 0;
+
+ for (int i = 0; i < count; i++) {
+ keys[i] = pidff_find_usage(fld, usage_page | usagetable[i]) + 1;
+ if (keys[i])
+ found++;
}
return found;
}
#define PIDFF_FIND_SPECIAL_KEYS(keys, field, name) \
pidff_find_special_keys(pidff->keys, pidff->field, pidff_ ## name, \
- sizeof(pidff_ ## name))
+ ARRAY_SIZE(pidff_ ## name), HID_UP_PID)
+
+#define PIDFF_FIND_GENERAL_DESKTOP(keys, field, name) \
+ pidff_find_special_keys(pidff->keys, pidff->field, pidff_ ## name, \
+ ARRAY_SIZE(pidff_ ## name), HID_UP_GENDESK)
/*
* Find and check the special fields
@@ -1128,13 +1226,24 @@ static int pidff_find_special_fields(struct pidff_device *pidff)
pidff->set_effect_type =
pidff_find_special_field(pidff->reports[PID_SET_EFFECT],
PID_EFFECT_TYPE, 1);
+ pidff->axes_enable =
+ pidff_find_special_field(pidff->reports[PID_SET_EFFECT],
+ PID_AXES_ENABLE, 0);
pidff->effect_direction =
pidff_find_special_field(pidff->reports[PID_SET_EFFECT],
PID_DIRECTION, 0);
pidff->device_control =
pidff_find_special_field(pidff->reports[PID_DEVICE_CONTROL],
- PID_DEVICE_CONTROL_ARRAY,
- !(pidff->quirks & HID_PIDFF_QUIRK_PERMISSIVE_CONTROL));
+ PID_DEVICE_CONTROL_ARRAY, 1);
+
+ /* Detect and set permissive control quirk */
+ if (!pidff->device_control) {
+ pr_debug("Setting PERMISSIVE_CONTROL quirk\n");
+ pidff->quirks |= HID_PIDFF_QUIRK_PERMISSIVE_CONTROL;
+ pidff->device_control = pidff_find_special_field(
+ pidff->reports[PID_DEVICE_CONTROL],
+ PID_DEVICE_CONTROL_ARRAY, 0);
+ }
pidff->block_load_status =
pidff_find_special_field(pidff->reports[PID_BLOCK_LOAD],
@@ -1180,7 +1289,7 @@ static int pidff_find_special_fields(struct pidff_device *pidff)
if (PIDFF_FIND_SPECIAL_KEYS(status_id, block_load_status,
block_load_status) !=
- sizeof(pidff_block_load_status)) {
+ ARRAY_SIZE(pidff_block_load_status)) {
hid_err(pidff->hid,
"block load status identifiers not found\n");
return -1;
@@ -1188,11 +1297,37 @@ static int pidff_find_special_fields(struct pidff_device *pidff)
if (PIDFF_FIND_SPECIAL_KEYS(operation_id, effect_operation_status,
effect_operation_status) !=
- sizeof(pidff_effect_operation_status)) {
+ ARRAY_SIZE(pidff_effect_operation_status)) {
hid_err(pidff->hid, "effect operation identifiers not found\n");
return -1;
}
+ if (!pidff->axes_enable) {
+ hid_info(pidff->hid, "axes enable field not found!\n");
+ return 0;
+ }
+
+ hid_dbg(pidff->hid, "axes enable report count: %u\n",
+ pidff->axes_enable->report_count);
+
+ uint found = PIDFF_FIND_GENERAL_DESKTOP(direction_axis_id, axes_enable,
+ direction_axis);
+
+ pidff->axis_count = found;
+ hid_dbg(pidff->hid, "found direction axes: %u", found);
+
+ for (int i = 0; i < ARRAY_SIZE(pidff_direction_axis); i++) {
+ if (!pidff->direction_axis_id[i])
+ continue;
+
+ hid_dbg(pidff->hid, "axis %d, usage: 0x%04x, index: %d", i + 1,
+ pidff_direction_axis[i], pidff->direction_axis_id[i]);
+ }
+
+ if (pidff->axes_enable && found != pidff->axes_enable->report_count)
+ hid_warn(pidff->hid, "axes_enable: %u != direction axes: %u",
+ pidff->axes_enable->report_count, found);
+
return 0;
}
@@ -1204,7 +1339,7 @@ static int pidff_find_effects(struct pidff_device *pidff,
{
int i;
- for (i = 0; i < sizeof(pidff_effect_types); i++) {
+ for (i = 0; i < ARRAY_SIZE(pidff_effect_types); i++) {
int pidff_type = pidff->type_id[i];
if (pidff->set_effect_type->usage[pidff_type].hid !=
@@ -1254,26 +1389,17 @@ static int pidff_find_effects(struct pidff_device *pidff,
#define PIDFF_FIND_FIELDS(name, report, strict) \
pidff_find_fields(pidff->name, pidff_ ## name, \
pidff->reports[report], \
- sizeof(pidff_ ## name), strict)
+ ARRAY_SIZE(pidff_ ## name), strict, &pidff->quirks)
/*
* Fill and check the pidff_usages
*/
static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev)
{
- int status = 0;
-
- /* Save info about the device not having the DELAY ffb field. */
- status = PIDFF_FIND_FIELDS(set_effect, PID_SET_EFFECT, 1);
- if (status == -1) {
+ if (PIDFF_FIND_FIELDS(set_effect, PID_SET_EFFECT, 1)) {
hid_err(pidff->hid, "unknown set_effect report layout\n");
return -ENODEV;
}
- pidff->quirks |= status;
-
- if (status & HID_PIDFF_QUIRK_MISSING_DELAY)
- hid_dbg(pidff->hid, "Adding MISSING_DELAY quirk\n");
-
PIDFF_FIND_FIELDS(block_load, PID_BLOCK_LOAD, 0);
if (!pidff->block_load[PID_EFFECT_BLOCK_INDEX].value) {
@@ -1307,39 +1433,25 @@ static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev)
"has periodic effect but no envelope\n");
}
- if (test_bit(FF_CONSTANT, dev->ffbit) &&
- PIDFF_FIND_FIELDS(set_constant, PID_SET_CONSTANT, 1)) {
+ if (PIDFF_FIND_FIELDS(set_constant, PID_SET_CONSTANT, 1) &&
+ test_and_clear_bit(FF_CONSTANT, dev->ffbit))
hid_warn(pidff->hid, "unknown constant effect layout\n");
- clear_bit(FF_CONSTANT, dev->ffbit);
- }
- if (test_bit(FF_RAMP, dev->ffbit) &&
- PIDFF_FIND_FIELDS(set_ramp, PID_SET_RAMP, 1)) {
+ if (PIDFF_FIND_FIELDS(set_ramp, PID_SET_RAMP, 1) &&
+ test_and_clear_bit(FF_RAMP, dev->ffbit))
hid_warn(pidff->hid, "unknown ramp effect layout\n");
- clear_bit(FF_RAMP, dev->ffbit);
- }
-
- if (test_bit(FF_SPRING, dev->ffbit) ||
- test_bit(FF_DAMPER, dev->ffbit) ||
- test_bit(FF_FRICTION, dev->ffbit) ||
- test_bit(FF_INERTIA, dev->ffbit)) {
- status = PIDFF_FIND_FIELDS(set_condition, PID_SET_CONDITION, 1);
- if (status < 0) {
+ if (PIDFF_FIND_FIELDS(set_condition, PID_SET_CONDITION, 1)) {
+ if (test_and_clear_bit(FF_SPRING, dev->ffbit) ||
+ test_and_clear_bit(FF_DAMPER, dev->ffbit) ||
+ test_and_clear_bit(FF_FRICTION, dev->ffbit) ||
+ test_and_clear_bit(FF_INERTIA, dev->ffbit))
hid_warn(pidff->hid, "unknown condition effect layout\n");
- clear_bit(FF_SPRING, dev->ffbit);
- clear_bit(FF_DAMPER, dev->ffbit);
- clear_bit(FF_FRICTION, dev->ffbit);
- clear_bit(FF_INERTIA, dev->ffbit);
- }
- pidff->quirks |= status;
}
- if (test_bit(FF_PERIODIC, dev->ffbit) &&
- PIDFF_FIND_FIELDS(set_periodic, PID_SET_PERIODIC, 1)) {
+ if (PIDFF_FIND_FIELDS(set_periodic, PID_SET_PERIODIC, 1) &&
+ test_and_clear_bit(FF_PERIODIC, dev->ffbit))
hid_warn(pidff->hid, "unknown periodic effect layout\n");
- clear_bit(FF_PERIODIC, dev->ffbit);
- }
PIDFF_FIND_FIELDS(pool, PID_POOL, 0);
@@ -1392,8 +1504,8 @@ static int pidff_check_autocenter(struct pidff_device *pidff,
int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks)
{
struct pidff_device *pidff;
- struct hid_input *hidinput = list_entry(hid->inputs.next,
- struct hid_input, list);
+ struct hid_input *hidinput =
+ list_entry(hid->inputs.next, struct hid_input, list);
struct input_dev *dev = hidinput->input;
struct ff_device *ff;
int max_effects;
@@ -1473,14 +1585,14 @@ int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks)
ff->set_autocenter = pidff_set_autocenter;
ff->playback = pidff_playback;
- hid_info(dev, "Force feedback for USB HID PID devices by Anssi Hannula <anssi.hannula@gmail.com>\n");
- hid_dbg(dev, "Active quirks mask: 0x%x\n", pidff->quirks);
+ hid_info(dev, "Force feedback for USB HID PID devices by Anssi Hannula\n");
+ hid_dbg(dev, "Active quirks mask: 0x%08x\n", pidff->quirks);
hid_device_io_stop(hid);
return 0;
- fail:
+fail:
hid_device_io_stop(hid);
kfree(pidff);
diff --git a/drivers/hid/usbhid/hid-pidff.h b/drivers/hid/usbhid/hid-pidff.h
index a53a8b436baa..f321f675e131 100644
--- a/drivers/hid/usbhid/hid-pidff.h
+++ b/drivers/hid/usbhid/hid-pidff.h
@@ -16,7 +16,7 @@
#define HID_PIDFF_QUIRK_PERMISSIVE_CONTROL BIT(2)
/* Use fixed 0x4000 direction during SET_EFFECT report upload */
-#define HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION BIT(3)
+#define HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION BIT(3)
/* Force all periodic effects to be uploaded as SINE */
#define HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY BIT(4)
diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig
index f064e3d172b3..6a4239ebb582 100644
--- a/drivers/hwtracing/coresight/Kconfig
+++ b/drivers/hwtracing/coresight/Kconfig
@@ -268,4 +268,16 @@ config CORESIGHT_KUNIT_TESTS
Enable Coresight unit tests. Only useful for development and not
intended for production.
+config CORESIGHT_TNOC
+ tristate "Coresight Trace Network On Chip driver"
+ help
+ This driver provides support for Trace Network On Chip (TNOC) component.
+ TNOC is an interconnect used to collect traces from various subsystems
+ and transport to a coresight trace sink. It sits in the different
+ tiles of SOC and aggregates the trace local to the tile and transports
+ it another tile or to coresight trace sink eventually.
+
+ To compile this driver as a module, choose M here: the module will be
+ called coresight-tnoc.
+
endif
diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile
index 4e7cc3c5bf99..ab16d06783a5 100644
--- a/drivers/hwtracing/coresight/Makefile
+++ b/drivers/hwtracing/coresight/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_CORESIGHT_SINK_TPIU) += coresight-tpiu.o
obj-$(CONFIG_CORESIGHT_SINK_ETBV10) += coresight-etb10.o
obj-$(CONFIG_CORESIGHT_LINKS_AND_SINKS) += coresight-funnel.o \
coresight-replicator.o
+obj-$(CONFIG_CORESIGHT_TNOC) += coresight-tnoc.o
obj-$(CONFIG_CORESIGHT_SOURCE_ETM3X) += coresight-etm3x.o
coresight-etm3x-y := coresight-etm3x-core.o coresight-etm-cp14.o \
coresight-etm3x-sysfs.o
diff --git a/drivers/hwtracing/coresight/coresight-catu.c b/drivers/hwtracing/coresight/coresight-catu.c
index 5058432233da..a3ccb7034ae1 100644
--- a/drivers/hwtracing/coresight/coresight-catu.c
+++ b/drivers/hwtracing/coresight/coresight-catu.c
@@ -515,11 +515,21 @@ static int __catu_probe(struct device *dev, struct resource *res)
{
int ret = 0;
u32 dma_mask;
- struct catu_drvdata *drvdata = dev_get_drvdata(dev);
+ struct catu_drvdata *drvdata;
struct coresight_desc catu_desc;
struct coresight_platform_data *pdata = NULL;
void __iomem *base;
+ drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, drvdata);
+
+ ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk);
+ if (ret)
+ return ret;
+
catu_desc.name = coresight_alloc_device_name(&catu_devs, dev);
if (!catu_desc.name)
return -ENOMEM;
@@ -576,14 +586,8 @@ out:
static int catu_probe(struct amba_device *adev, const struct amba_id *id)
{
- struct catu_drvdata *drvdata;
int ret;
- drvdata = devm_kzalloc(&adev->dev, sizeof(*drvdata), GFP_KERNEL);
- if (!drvdata)
- return -ENOMEM;
-
- amba_set_drvdata(adev, drvdata);
ret = __catu_probe(&adev->dev, &adev->res);
if (!ret)
pm_runtime_put(&adev->dev);
@@ -623,29 +627,16 @@ static struct amba_driver catu_driver = {
static int catu_platform_probe(struct platform_device *pdev)
{
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- struct catu_drvdata *drvdata;
int ret = 0;
- drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
- if (!drvdata)
- return -ENOMEM;
-
- drvdata->pclk = coresight_get_enable_apb_pclk(&pdev->dev);
- if (IS_ERR(drvdata->pclk))
- return -ENODEV;
-
pm_runtime_get_noresume(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
- dev_set_drvdata(&pdev->dev, drvdata);
ret = __catu_probe(&pdev->dev, res);
pm_runtime_put(&pdev->dev);
- if (ret) {
+ if (ret)
pm_runtime_disable(&pdev->dev);
- if (!IS_ERR_OR_NULL(drvdata->pclk))
- clk_put(drvdata->pclk);
- }
return ret;
}
@@ -659,8 +650,6 @@ static void catu_platform_remove(struct platform_device *pdev)
__catu_remove(&pdev->dev);
pm_runtime_disable(&pdev->dev);
- if (!IS_ERR_OR_NULL(drvdata->pclk))
- clk_put(drvdata->pclk);
}
#ifdef CONFIG_PM
@@ -668,18 +657,26 @@ static int catu_runtime_suspend(struct device *dev)
{
struct catu_drvdata *drvdata = dev_get_drvdata(dev);
- if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_disable_unprepare(drvdata->pclk);
+ clk_disable_unprepare(drvdata->atclk);
+ clk_disable_unprepare(drvdata->pclk);
+
return 0;
}
static int catu_runtime_resume(struct device *dev)
{
struct catu_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret;
- if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_prepare_enable(drvdata->pclk);
- return 0;
+ ret = clk_prepare_enable(drvdata->pclk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(drvdata->atclk);
+ if (ret)
+ clk_disable_unprepare(drvdata->pclk);
+
+ return ret;
}
#endif
diff --git a/drivers/hwtracing/coresight/coresight-catu.h b/drivers/hwtracing/coresight/coresight-catu.h
index 755776cd19c5..6e6b7aac206d 100644
--- a/drivers/hwtracing/coresight/coresight-catu.h
+++ b/drivers/hwtracing/coresight/coresight-catu.h
@@ -62,6 +62,7 @@
struct catu_drvdata {
struct clk *pclk;
+ struct clk *atclk;
void __iomem *base;
struct coresight_device *csdev;
int irq;
diff --git a/drivers/hwtracing/coresight/coresight-core.c b/drivers/hwtracing/coresight/coresight-core.c
index fa758cc21827..3267192f0c1c 100644
--- a/drivers/hwtracing/coresight/coresight-core.c
+++ b/drivers/hwtracing/coresight/coresight-core.c
@@ -3,6 +3,8 @@
* Copyright (c) 2012, The Linux Foundation. All rights reserved.
*/
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
#include <linux/build_bug.h>
#include <linux/kernel.h>
#include <linux/init.h>
@@ -1374,8 +1376,9 @@ struct coresight_device *coresight_register(struct coresight_desc *desc)
goto out_unlock;
}
- if (csdev->type == CORESIGHT_DEV_TYPE_SINK ||
- csdev->type == CORESIGHT_DEV_TYPE_LINKSINK) {
+ if ((csdev->type == CORESIGHT_DEV_TYPE_SINK ||
+ csdev->type == CORESIGHT_DEV_TYPE_LINKSINK) &&
+ sink_ops(csdev)->alloc_buffer) {
ret = etm_perf_add_symlink_sink(csdev);
if (ret) {
@@ -1698,6 +1701,53 @@ int coresight_etm_get_trace_id(struct coresight_device *csdev, enum cs_mode mode
}
EXPORT_SYMBOL_GPL(coresight_etm_get_trace_id);
+/*
+ * Attempt to find and enable programming clock (pclk) and trace clock (atclk)
+ * for the given device.
+ *
+ * For ACPI devices, clocks are controlled by firmware, so bail out early in
+ * this case. Also, skip enabling pclk if the clock is managed by the AMBA
+ * bus driver instead.
+ *
+ * atclk is an optional clock, it will be only enabled when it is existed.
+ * Otherwise, a NULL pointer will be returned to caller.
+ *
+ * Returns: '0' on Success; Error code otherwise.
+ */
+int coresight_get_enable_clocks(struct device *dev, struct clk **pclk,
+ struct clk **atclk)
+{
+ WARN_ON(!pclk);
+
+ if (has_acpi_companion(dev))
+ return 0;
+
+ if (!dev_is_amba(dev)) {
+ /*
+ * "apb_pclk" is the default clock name for an Arm Primecell
+ * peripheral, while "apb" is used only by the CTCU driver.
+ *
+ * For easier maintenance, CoreSight drivers should use
+ * "apb_pclk" as the programming clock name.
+ */
+ *pclk = devm_clk_get_optional_enabled(dev, "apb_pclk");
+ if (!*pclk)
+ *pclk = devm_clk_get_optional_enabled(dev, "apb");
+ if (IS_ERR(*pclk))
+ return PTR_ERR(*pclk);
+ }
+
+ /* Initialization of atclk is skipped if it is a NULL pointer. */
+ if (atclk) {
+ *atclk = devm_clk_get_optional_enabled(dev, "atclk");
+ if (IS_ERR(*atclk))
+ return PTR_ERR(*atclk);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(coresight_get_enable_clocks);
+
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Pratik Patel <pratikp@codeaurora.org>");
MODULE_AUTHOR("Mathieu Poirier <mathieu.poirier@linaro.org>");
diff --git a/drivers/hwtracing/coresight/coresight-cpu-debug.c b/drivers/hwtracing/coresight/coresight-cpu-debug.c
index a871d997330b..5f21366406aa 100644
--- a/drivers/hwtracing/coresight/coresight-cpu-debug.c
+++ b/drivers/hwtracing/coresight/coresight-cpu-debug.c
@@ -562,10 +562,20 @@ static void debug_func_exit(void)
static int __debug_probe(struct device *dev, struct resource *res)
{
- struct debug_drvdata *drvdata = dev_get_drvdata(dev);
+ struct debug_drvdata *drvdata;
void __iomem *base;
int ret;
+ drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, drvdata);
+
+ ret = coresight_get_enable_clocks(dev, &drvdata->pclk, NULL);
+ if (ret)
+ return ret;
+
drvdata->cpu = coresight_get_cpu(dev);
if (drvdata->cpu < 0)
return drvdata->cpu;
@@ -625,13 +635,6 @@ err:
static int debug_probe(struct amba_device *adev, const struct amba_id *id)
{
- struct debug_drvdata *drvdata;
-
- drvdata = devm_kzalloc(&adev->dev, sizeof(*drvdata), GFP_KERNEL);
- if (!drvdata)
- return -ENOMEM;
-
- amba_set_drvdata(adev, drvdata);
return __debug_probe(&adev->dev, &adev->res);
}
@@ -690,18 +693,8 @@ static struct amba_driver debug_driver = {
static int debug_platform_probe(struct platform_device *pdev)
{
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- struct debug_drvdata *drvdata;
int ret = 0;
- drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
- if (!drvdata)
- return -ENOMEM;
-
- drvdata->pclk = coresight_get_enable_apb_pclk(&pdev->dev);
- if (IS_ERR(drvdata->pclk))
- return -ENODEV;
-
- dev_set_drvdata(&pdev->dev, drvdata);
pm_runtime_get_noresume(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
@@ -710,8 +703,6 @@ static int debug_platform_probe(struct platform_device *pdev)
if (ret) {
pm_runtime_put_noidle(&pdev->dev);
pm_runtime_disable(&pdev->dev);
- if (!IS_ERR_OR_NULL(drvdata->pclk))
- clk_put(drvdata->pclk);
}
return ret;
}
@@ -725,8 +716,6 @@ static void debug_platform_remove(struct platform_device *pdev)
__debug_remove(&pdev->dev);
pm_runtime_disable(&pdev->dev);
- if (!IS_ERR_OR_NULL(drvdata->pclk))
- clk_put(drvdata->pclk);
}
#ifdef CONFIG_ACPI
@@ -742,8 +731,8 @@ static int debug_runtime_suspend(struct device *dev)
{
struct debug_drvdata *drvdata = dev_get_drvdata(dev);
- if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_disable_unprepare(drvdata->pclk);
+ clk_disable_unprepare(drvdata->pclk);
+
return 0;
}
@@ -751,9 +740,7 @@ static int debug_runtime_resume(struct device *dev)
{
struct debug_drvdata *drvdata = dev_get_drvdata(dev);
- if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_prepare_enable(drvdata->pclk);
- return 0;
+ return clk_prepare_enable(drvdata->pclk);
}
#endif
diff --git a/drivers/hwtracing/coresight/coresight-ctcu-core.c b/drivers/hwtracing/coresight/coresight-ctcu-core.c
index c6bafc96db96..c586495e9a08 100644
--- a/drivers/hwtracing/coresight/coresight-ctcu-core.c
+++ b/drivers/hwtracing/coresight/coresight-ctcu-core.c
@@ -188,7 +188,7 @@ static int ctcu_probe(struct platform_device *pdev)
const struct ctcu_config *cfgs;
struct ctcu_drvdata *drvdata;
void __iomem *base;
- int i;
+ int i, ret;
desc.name = coresight_alloc_device_name(&ctcu_devs, dev);
if (!desc.name)
@@ -207,9 +207,9 @@ static int ctcu_probe(struct platform_device *pdev)
if (IS_ERR(base))
return PTR_ERR(base);
- drvdata->apb_clk = coresight_get_enable_apb_pclk(dev);
- if (IS_ERR(drvdata->apb_clk))
- return -ENODEV;
+ ret = coresight_get_enable_clocks(dev, &drvdata->apb_clk, NULL);
+ if (ret)
+ return ret;
cfgs = of_device_get_match_data(dev);
if (cfgs) {
@@ -233,12 +233,8 @@ static int ctcu_probe(struct platform_device *pdev)
desc.access = CSDEV_ACCESS_IOMEM(base);
drvdata->csdev = coresight_register(&desc);
- if (IS_ERR(drvdata->csdev)) {
- if (!IS_ERR_OR_NULL(drvdata->apb_clk))
- clk_put(drvdata->apb_clk);
-
+ if (IS_ERR(drvdata->csdev))
return PTR_ERR(drvdata->csdev);
- }
return 0;
}
@@ -275,8 +271,6 @@ static void ctcu_platform_remove(struct platform_device *pdev)
ctcu_remove(pdev);
pm_runtime_disable(&pdev->dev);
- if (!IS_ERR_OR_NULL(drvdata->apb_clk))
- clk_put(drvdata->apb_clk);
}
#ifdef CONFIG_PM
@@ -284,8 +278,7 @@ static int ctcu_runtime_suspend(struct device *dev)
{
struct ctcu_drvdata *drvdata = dev_get_drvdata(dev);
- if (drvdata && !IS_ERR_OR_NULL(drvdata->apb_clk))
- clk_disable_unprepare(drvdata->apb_clk);
+ clk_disable_unprepare(drvdata->apb_clk);
return 0;
}
@@ -294,10 +287,7 @@ static int ctcu_runtime_resume(struct device *dev)
{
struct ctcu_drvdata *drvdata = dev_get_drvdata(dev);
- if (drvdata && !IS_ERR_OR_NULL(drvdata->apb_clk))
- clk_prepare_enable(drvdata->apb_clk);
-
- return 0;
+ return clk_prepare_enable(drvdata->apb_clk);
}
#endif
diff --git a/drivers/hwtracing/coresight/coresight-etb10.c b/drivers/hwtracing/coresight/coresight-etb10.c
index d5efb085b30d..35db1b6093d1 100644
--- a/drivers/hwtracing/coresight/coresight-etb10.c
+++ b/drivers/hwtracing/coresight/coresight-etb10.c
@@ -730,12 +730,10 @@ static int etb_probe(struct amba_device *adev, const struct amba_id *id)
if (!drvdata)
return -ENOMEM;
- drvdata->atclk = devm_clk_get(&adev->dev, "atclk"); /* optional */
- if (!IS_ERR(drvdata->atclk)) {
- ret = clk_prepare_enable(drvdata->atclk);
- if (ret)
- return ret;
- }
+ drvdata->atclk = devm_clk_get_optional_enabled(dev, "atclk");
+ if (IS_ERR(drvdata->atclk))
+ return PTR_ERR(drvdata->atclk);
+
dev_set_drvdata(dev, drvdata);
/* validity for the resource is already checked by the AMBA core */
@@ -811,8 +809,7 @@ static int etb_runtime_suspend(struct device *dev)
{
struct etb_drvdata *drvdata = dev_get_drvdata(dev);
- if (drvdata && !IS_ERR(drvdata->atclk))
- clk_disable_unprepare(drvdata->atclk);
+ clk_disable_unprepare(drvdata->atclk);
return 0;
}
@@ -821,10 +818,7 @@ static int etb_runtime_resume(struct device *dev)
{
struct etb_drvdata *drvdata = dev_get_drvdata(dev);
- if (drvdata && !IS_ERR(drvdata->atclk))
- clk_prepare_enable(drvdata->atclk);
-
- return 0;
+ return clk_prepare_enable(drvdata->atclk);
}
#endif
diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.c b/drivers/hwtracing/coresight/coresight-etm-perf.c
index f1551c08ecb2..f677c08233ba 100644
--- a/drivers/hwtracing/coresight/coresight-etm-perf.c
+++ b/drivers/hwtracing/coresight/coresight-etm-perf.c
@@ -851,7 +851,7 @@ static ssize_t etm_perf_sink_name_show(struct device *dev,
struct dev_ext_attribute *ea;
ea = container_of(dattr, struct dev_ext_attribute, attr);
- return scnprintf(buf, PAGE_SIZE, "0x%lx\n", (unsigned long)(ea->var));
+ return scnprintf(buf, PAGE_SIZE, "0x%px\n", ea->var);
}
static struct dev_ext_attribute *
@@ -943,7 +943,7 @@ static ssize_t etm_perf_cscfg_event_show(struct device *dev,
struct dev_ext_attribute *ea;
ea = container_of(dattr, struct dev_ext_attribute, attr);
- return scnprintf(buf, PAGE_SIZE, "configid=0x%lx\n", (unsigned long)(ea->var));
+ return scnprintf(buf, PAGE_SIZE, "configid=0x%px\n", ea->var);
}
int etm_perf_add_symlink_cscfg(struct device *dev, struct cscfg_config_desc *config_desc)
diff --git a/drivers/hwtracing/coresight/coresight-etm3x-core.c b/drivers/hwtracing/coresight/coresight-etm3x-core.c
index 1c6204e14422..45630a1cd32f 100644
--- a/drivers/hwtracing/coresight/coresight-etm3x-core.c
+++ b/drivers/hwtracing/coresight/coresight-etm3x-core.c
@@ -832,12 +832,9 @@ static int etm_probe(struct amba_device *adev, const struct amba_id *id)
spin_lock_init(&drvdata->spinlock);
- drvdata->atclk = devm_clk_get(&adev->dev, "atclk"); /* optional */
- if (!IS_ERR(drvdata->atclk)) {
- ret = clk_prepare_enable(drvdata->atclk);
- if (ret)
- return ret;
- }
+ drvdata->atclk = devm_clk_get_optional_enabled(dev, "atclk");
+ if (IS_ERR(drvdata->atclk))
+ return PTR_ERR(drvdata->atclk);
drvdata->cpu = coresight_get_cpu(dev);
if (drvdata->cpu < 0)
@@ -928,8 +925,7 @@ static int etm_runtime_suspend(struct device *dev)
{
struct etm_drvdata *drvdata = dev_get_drvdata(dev);
- if (drvdata && !IS_ERR(drvdata->atclk))
- clk_disable_unprepare(drvdata->atclk);
+ clk_disable_unprepare(drvdata->atclk);
return 0;
}
@@ -938,10 +934,7 @@ static int etm_runtime_resume(struct device *dev)
{
struct etm_drvdata *drvdata = dev_get_drvdata(dev);
- if (drvdata && !IS_ERR(drvdata->atclk))
- clk_prepare_enable(drvdata->atclk);
-
- return 0;
+ return clk_prepare_enable(drvdata->atclk);
}
#endif
diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c
index 42e5d37403ad..020f070bf17d 100644
--- a/drivers/hwtracing/coresight/coresight-etm4x-core.c
+++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c
@@ -4,6 +4,7 @@
*/
#include <linux/acpi.h>
+#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/kernel.h>
#include <linux/kvm_host.h>
@@ -528,7 +529,8 @@ static int etm4_enable_hw(struct etmv4_drvdata *drvdata)
etm4x_relaxed_write32(csa, config->seq_rst, TRCSEQRSTEVR);
etm4x_relaxed_write32(csa, config->seq_state, TRCSEQSTR);
}
- etm4x_relaxed_write32(csa, config->ext_inp, TRCEXTINSELR);
+ 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));
@@ -1423,6 +1425,7 @@ static void etm4_init_arch_data(void *info)
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);
/* TRACEIDSIZE, bits[21:16] indicates the trace ID width */
drvdata->trcid_size = FIELD_GET(TRCIDR5_TRACEIDSIZE_MASK, etmidr5);
/* ATBTRIG, bit[22] implementation can support ATB triggers? */
@@ -1852,7 +1855,9 @@ static int __etm4_cpu_save(struct etmv4_drvdata *drvdata)
state->trcseqrstevr = etm4x_read32(csa, TRCSEQRSTEVR);
state->trcseqstr = etm4x_read32(csa, TRCSEQSTR);
}
- state->trcextinselr = etm4x_read32(csa, TRCEXTINSELR);
+
+ if (drvdata->numextinsel)
+ state->trcextinselr = etm4x_read32(csa, TRCEXTINSELR);
for (i = 0; i < drvdata->nr_cntr; i++) {
state->trccntrldvr[i] = etm4x_read32(csa, TRCCNTRLDVRn(i));
@@ -1984,7 +1989,8 @@ static void __etm4_cpu_restore(struct etmv4_drvdata *drvdata)
etm4x_relaxed_write32(csa, state->trcseqrstevr, TRCSEQRSTEVR);
etm4x_relaxed_write32(csa, state->trcseqstr, TRCSEQSTR);
}
- etm4x_relaxed_write32(csa, state->trcextinselr, TRCEXTINSELR);
+ if (drvdata->numextinsel)
+ etm4x_relaxed_write32(csa, state->trcextinselr, TRCEXTINSELR);
for (i = 0; i < drvdata->nr_cntr; i++) {
etm4x_relaxed_write32(csa, state->trccntrldvr[i], TRCCNTRLDVRn(i));
@@ -2211,10 +2217,15 @@ static int etm4_probe(struct device *dev)
struct csdev_access access = { 0 };
struct etm4_init_arg init_arg = { 0 };
struct etm4_init_arg *delayed;
+ int ret;
if (WARN_ON(!drvdata))
return -ENOMEM;
+ ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk);
+ if (ret)
+ return ret;
+
if (pm_save_enable == PARAM_PM_SAVE_FIRMWARE)
pm_save_enable = coresight_loses_context_with_cpu(dev) ?
PARAM_PM_SAVE_SELF_HOSTED : PARAM_PM_SAVE_NEVER;
@@ -2297,16 +2308,10 @@ static int etm4_probe_platform_dev(struct platform_device *pdev)
if (!drvdata)
return -ENOMEM;
- drvdata->pclk = coresight_get_enable_apb_pclk(&pdev->dev);
- if (IS_ERR(drvdata->pclk))
- return -ENODEV;
-
if (res) {
drvdata->base = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(drvdata->base)) {
- clk_put(drvdata->pclk);
+ if (IS_ERR(drvdata->base))
return PTR_ERR(drvdata->base);
- }
}
dev_set_drvdata(&pdev->dev, drvdata);
@@ -2413,9 +2418,6 @@ static void etm4_remove_platform_dev(struct platform_device *pdev)
if (drvdata)
etm4_remove_dev(drvdata);
pm_runtime_disable(&pdev->dev);
-
- if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_put(drvdata->pclk);
}
static const struct amba_id etm4_ids[] = {
@@ -2463,8 +2465,8 @@ static int etm4_runtime_suspend(struct device *dev)
{
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev);
- if (drvdata->pclk && !IS_ERR(drvdata->pclk))
- clk_disable_unprepare(drvdata->pclk);
+ clk_disable_unprepare(drvdata->atclk);
+ clk_disable_unprepare(drvdata->pclk);
return 0;
}
@@ -2472,11 +2474,17 @@ static int etm4_runtime_suspend(struct device *dev)
static int etm4_runtime_resume(struct device *dev)
{
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret;
- if (drvdata->pclk && !IS_ERR(drvdata->pclk))
- clk_prepare_enable(drvdata->pclk);
+ ret = clk_prepare_enable(drvdata->pclk);
+ if (ret)
+ return ret;
- return 0;
+ ret = clk_prepare_enable(drvdata->atclk);
+ if (ret)
+ clk_disable_unprepare(drvdata->pclk);
+
+ return ret;
}
#endif
diff --git a/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c b/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c
index ab251865b893..e9eeea6240d5 100644
--- a/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c
+++ b/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c
@@ -4,6 +4,7 @@
* Author: Mathieu Poirier <mathieu.poirier@linaro.org>
*/
+#include <linux/bitfield.h>
#include <linux/coresight.h>
#include <linux/pid_namespace.h>
#include <linux/pm_runtime.h>
diff --git a/drivers/hwtracing/coresight/coresight-etm4x.h b/drivers/hwtracing/coresight/coresight-etm4x.h
index ac649515054d..13ec9ecef46f 100644
--- a/drivers/hwtracing/coresight/coresight-etm4x.h
+++ b/drivers/hwtracing/coresight/coresight-etm4x.h
@@ -162,6 +162,7 @@
#define TRCIDR4_NUMVMIDC_MASK GENMASK(31, 28)
#define TRCIDR5_NUMEXTIN_MASK GENMASK(8, 0)
+#define TRCIDR5_NUMEXTINSEL_MASK GENMASK(11, 9)
#define TRCIDR5_TRACEIDSIZE_MASK GENMASK(21, 16)
#define TRCIDR5_ATBTRIG BIT(22)
#define TRCIDR5_LPOVERRIDE BIT(23)
@@ -919,7 +920,8 @@ struct etmv4_save_state {
/**
* struct etm4_drvdata - specifics associated to an ETM component
- * @pclk APB clock if present, otherwise NULL
+ * @pclk: APB clock if present, otherwise NULL
+ * @atclk: Optional clock for the core parts of the ETMv4.
* @base: Memory mapped base address for this component.
* @csdev: Component vitals needed by the framework.
* @spinlock: Only one at a time pls.
@@ -988,6 +990,7 @@ struct etmv4_save_state {
*/
struct etmv4_drvdata {
struct clk *pclk;
+ struct clk *atclk;
void __iomem *base;
struct coresight_device *csdev;
raw_spinlock_t spinlock;
@@ -999,6 +1002,7 @@ struct etmv4_drvdata {
u8 nr_cntr;
u8 nr_ext_inp;
u8 numcidc;
+ u8 numextinsel;
u8 numvmidc;
u8 nrseqstate;
u8 nr_event;
diff --git a/drivers/hwtracing/coresight/coresight-funnel.c b/drivers/hwtracing/coresight/coresight-funnel.c
index b1922dbe9292..3b248e54471a 100644
--- a/drivers/hwtracing/coresight/coresight-funnel.c
+++ b/drivers/hwtracing/coresight/coresight-funnel.c
@@ -213,11 +213,11 @@ ATTRIBUTE_GROUPS(coresight_funnel);
static int funnel_probe(struct device *dev, struct resource *res)
{
- int ret;
void __iomem *base;
struct coresight_platform_data *pdata = NULL;
struct funnel_drvdata *drvdata;
struct coresight_desc desc = { 0 };
+ int ret;
if (is_of_node(dev_fwnode(dev)) &&
of_device_is_compatible(dev->of_node, "arm,coresight-funnel"))
@@ -231,16 +231,9 @@ static int funnel_probe(struct device *dev, struct resource *res)
if (!drvdata)
return -ENOMEM;
- drvdata->atclk = devm_clk_get(dev, "atclk"); /* optional */
- if (!IS_ERR(drvdata->atclk)) {
- ret = clk_prepare_enable(drvdata->atclk);
- if (ret)
- return ret;
- }
-
- drvdata->pclk = coresight_get_enable_apb_pclk(dev);
- if (IS_ERR(drvdata->pclk))
- return -ENODEV;
+ ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk);
+ if (ret)
+ return ret;
/*
* Map the device base for dynamic-funnel, which has been
@@ -248,10 +241,8 @@ static int funnel_probe(struct device *dev, struct resource *res)
*/
if (res) {
base = devm_ioremap_resource(dev, res);
- if (IS_ERR(base)) {
- ret = PTR_ERR(base);
- goto out_disable_clk;
- }
+ if (IS_ERR(base))
+ return PTR_ERR(base);
drvdata->base = base;
desc.groups = coresight_funnel_groups;
desc.access = CSDEV_ACCESS_IOMEM(base);
@@ -261,10 +252,9 @@ static int funnel_probe(struct device *dev, struct resource *res)
dev_set_drvdata(dev, drvdata);
pdata = coresight_get_platform_data(dev);
- if (IS_ERR(pdata)) {
- ret = PTR_ERR(pdata);
- goto out_disable_clk;
- }
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+
dev->platform_data = pdata;
raw_spin_lock_init(&drvdata->spinlock);
@@ -274,19 +264,10 @@ static int funnel_probe(struct device *dev, struct resource *res)
desc.pdata = pdata;
desc.dev = dev;
drvdata->csdev = coresight_register(&desc);
- if (IS_ERR(drvdata->csdev)) {
- ret = PTR_ERR(drvdata->csdev);
- goto out_disable_clk;
- }
+ if (IS_ERR(drvdata->csdev))
+ return PTR_ERR(drvdata->csdev);
- ret = 0;
-
-out_disable_clk:
- if (ret && !IS_ERR_OR_NULL(drvdata->atclk))
- clk_disable_unprepare(drvdata->atclk);
- if (ret && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_disable_unprepare(drvdata->pclk);
- return ret;
+ return 0;
}
static int funnel_remove(struct device *dev)
@@ -303,11 +284,8 @@ static int funnel_runtime_suspend(struct device *dev)
{
struct funnel_drvdata *drvdata = dev_get_drvdata(dev);
- if (drvdata && !IS_ERR(drvdata->atclk))
- clk_disable_unprepare(drvdata->atclk);
-
- if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_disable_unprepare(drvdata->pclk);
+ clk_disable_unprepare(drvdata->atclk);
+ clk_disable_unprepare(drvdata->pclk);
return 0;
}
@@ -315,13 +293,17 @@ static int funnel_runtime_suspend(struct device *dev)
static int funnel_runtime_resume(struct device *dev)
{
struct funnel_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret;
- if (drvdata && !IS_ERR(drvdata->atclk))
- clk_prepare_enable(drvdata->atclk);
+ ret = clk_prepare_enable(drvdata->pclk);
+ if (ret)
+ return ret;
- if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_prepare_enable(drvdata->pclk);
- return 0;
+ ret = clk_prepare_enable(drvdata->atclk);
+ if (ret)
+ clk_disable_unprepare(drvdata->pclk);
+
+ return ret;
}
#endif
@@ -355,8 +337,6 @@ static void funnel_platform_remove(struct platform_device *pdev)
funnel_remove(&pdev->dev);
pm_runtime_disable(&pdev->dev);
- if (!IS_ERR_OR_NULL(drvdata->pclk))
- clk_put(drvdata->pclk);
}
static const struct of_device_id funnel_match[] = {
diff --git a/drivers/hwtracing/coresight/coresight-replicator.c b/drivers/hwtracing/coresight/coresight-replicator.c
index 06efd2b01a0f..e6472658235d 100644
--- a/drivers/hwtracing/coresight/coresight-replicator.c
+++ b/drivers/hwtracing/coresight/coresight-replicator.c
@@ -219,11 +219,11 @@ static const struct attribute_group *replicator_groups[] = {
static int replicator_probe(struct device *dev, struct resource *res)
{
- int ret = 0;
struct coresight_platform_data *pdata = NULL;
struct replicator_drvdata *drvdata;
struct coresight_desc desc = { 0 };
void __iomem *base;
+ int ret;
if (is_of_node(dev_fwnode(dev)) &&
of_device_is_compatible(dev->of_node, "arm,coresight-replicator"))
@@ -238,16 +238,9 @@ static int replicator_probe(struct device *dev, struct resource *res)
if (!drvdata)
return -ENOMEM;
- drvdata->atclk = devm_clk_get(dev, "atclk"); /* optional */
- if (!IS_ERR(drvdata->atclk)) {
- ret = clk_prepare_enable(drvdata->atclk);
- if (ret)
- return ret;
- }
-
- drvdata->pclk = coresight_get_enable_apb_pclk(dev);
- if (IS_ERR(drvdata->pclk))
- return -ENODEV;
+ ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk);
+ if (ret)
+ return ret;
/*
* Map the device base for dynamic-replicator, which has been
@@ -255,10 +248,8 @@ static int replicator_probe(struct device *dev, struct resource *res)
*/
if (res) {
base = devm_ioremap_resource(dev, res);
- if (IS_ERR(base)) {
- ret = PTR_ERR(base);
- goto out_disable_clk;
- }
+ if (IS_ERR(base))
+ return PTR_ERR(base);
drvdata->base = base;
desc.groups = replicator_groups;
desc.access = CSDEV_ACCESS_IOMEM(base);
@@ -272,10 +263,8 @@ static int replicator_probe(struct device *dev, struct resource *res)
dev_set_drvdata(dev, drvdata);
pdata = coresight_get_platform_data(dev);
- if (IS_ERR(pdata)) {
- ret = PTR_ERR(pdata);
- goto out_disable_clk;
- }
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
dev->platform_data = pdata;
raw_spin_lock_init(&drvdata->spinlock);
@@ -286,19 +275,11 @@ static int replicator_probe(struct device *dev, struct resource *res)
desc.dev = dev;
drvdata->csdev = coresight_register(&desc);
- if (IS_ERR(drvdata->csdev)) {
- ret = PTR_ERR(drvdata->csdev);
- goto out_disable_clk;
- }
+ if (IS_ERR(drvdata->csdev))
+ return PTR_ERR(drvdata->csdev);
replicator_reset(drvdata);
-
-out_disable_clk:
- if (ret && !IS_ERR_OR_NULL(drvdata->atclk))
- clk_disable_unprepare(drvdata->atclk);
- if (ret && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_disable_unprepare(drvdata->pclk);
- return ret;
+ return 0;
}
static int replicator_remove(struct device *dev)
@@ -335,8 +316,6 @@ static void replicator_platform_remove(struct platform_device *pdev)
replicator_remove(&pdev->dev);
pm_runtime_disable(&pdev->dev);
- if (!IS_ERR_OR_NULL(drvdata->pclk))
- clk_put(drvdata->pclk);
}
#ifdef CONFIG_PM
@@ -344,24 +323,26 @@ static int replicator_runtime_suspend(struct device *dev)
{
struct replicator_drvdata *drvdata = dev_get_drvdata(dev);
- if (drvdata && !IS_ERR(drvdata->atclk))
- clk_disable_unprepare(drvdata->atclk);
+ clk_disable_unprepare(drvdata->atclk);
+ clk_disable_unprepare(drvdata->pclk);
- if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_disable_unprepare(drvdata->pclk);
return 0;
}
static int replicator_runtime_resume(struct device *dev)
{
struct replicator_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret;
- if (drvdata && !IS_ERR(drvdata->atclk))
- clk_prepare_enable(drvdata->atclk);
+ ret = clk_prepare_enable(drvdata->pclk);
+ if (ret)
+ return ret;
- if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_prepare_enable(drvdata->pclk);
- return 0;
+ ret = clk_prepare_enable(drvdata->atclk);
+ if (ret)
+ clk_disable_unprepare(drvdata->pclk);
+
+ return ret;
}
#endif
diff --git a/drivers/hwtracing/coresight/coresight-stm.c b/drivers/hwtracing/coresight/coresight-stm.c
index e45c6c7204b4..e68529bf89c9 100644
--- a/drivers/hwtracing/coresight/coresight-stm.c
+++ b/drivers/hwtracing/coresight/coresight-stm.c
@@ -342,7 +342,7 @@ static int stm_generic_link(struct stm_data *stm_data,
{
struct stm_drvdata *drvdata = container_of(stm_data,
struct stm_drvdata, stm);
- if (!drvdata || !drvdata->csdev)
+ if (!drvdata->csdev)
return -EINVAL;
return coresight_enable_sysfs(drvdata->csdev);
@@ -353,7 +353,7 @@ static void stm_generic_unlink(struct stm_data *stm_data,
{
struct stm_drvdata *drvdata = container_of(stm_data,
struct stm_drvdata, stm);
- if (!drvdata || !drvdata->csdev)
+ if (!drvdata->csdev)
return;
coresight_disable_sysfs(drvdata->csdev);
@@ -384,7 +384,7 @@ static long stm_generic_set_options(struct stm_data *stm_data,
{
struct stm_drvdata *drvdata = container_of(stm_data,
struct stm_drvdata, stm);
- if (!(drvdata && coresight_get_mode(drvdata->csdev)))
+ if (!coresight_get_mode(drvdata->csdev))
return -EINVAL;
if (channel >= drvdata->numsp)
@@ -419,7 +419,7 @@ static ssize_t notrace stm_generic_packet(struct stm_data *stm_data,
struct stm_drvdata, stm);
unsigned int stm_flags;
- if (!(drvdata && coresight_get_mode(drvdata->csdev)))
+ if (!coresight_get_mode(drvdata->csdev))
return -EACCES;
if (channel >= drvdata->numsp)
@@ -842,16 +842,10 @@ static int __stm_probe(struct device *dev, struct resource *res)
if (!drvdata)
return -ENOMEM;
- drvdata->atclk = devm_clk_get(dev, "atclk"); /* optional */
- if (!IS_ERR(drvdata->atclk)) {
- ret = clk_prepare_enable(drvdata->atclk);
- if (ret)
- return ret;
- }
+ ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk);
+ if (ret)
+ return ret;
- drvdata->pclk = coresight_get_enable_apb_pclk(dev);
- if (IS_ERR(drvdata->pclk))
- return -ENODEV;
dev_set_drvdata(dev, drvdata);
base = devm_ioremap_resource(dev, res);
@@ -963,24 +957,26 @@ static int stm_runtime_suspend(struct device *dev)
{
struct stm_drvdata *drvdata = dev_get_drvdata(dev);
- if (drvdata && !IS_ERR(drvdata->atclk))
- clk_disable_unprepare(drvdata->atclk);
+ clk_disable_unprepare(drvdata->atclk);
+ clk_disable_unprepare(drvdata->pclk);
- if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_disable_unprepare(drvdata->pclk);
return 0;
}
static int stm_runtime_resume(struct device *dev)
{
struct stm_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(drvdata->pclk);
+ if (ret)
+ return ret;
- if (drvdata && !IS_ERR(drvdata->atclk))
- clk_prepare_enable(drvdata->atclk);
+ ret = clk_prepare_enable(drvdata->atclk);
+ if (ret)
+ clk_disable_unprepare(drvdata->pclk);
- if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_prepare_enable(drvdata->pclk);
- return 0;
+ return ret;
}
#endif
@@ -1033,8 +1029,6 @@ static void stm_platform_remove(struct platform_device *pdev)
__stm_remove(&pdev->dev);
pm_runtime_disable(&pdev->dev);
- if (!IS_ERR_OR_NULL(drvdata->pclk))
- clk_put(drvdata->pclk);
}
#ifdef CONFIG_ACPI
diff --git a/drivers/hwtracing/coresight/coresight-syscfg.c b/drivers/hwtracing/coresight/coresight-syscfg.c
index 83dad24e0116..6836b05986e8 100644
--- a/drivers/hwtracing/coresight/coresight-syscfg.c
+++ b/drivers/hwtracing/coresight/coresight-syscfg.c
@@ -395,7 +395,7 @@ static void cscfg_remove_owned_csdev_configs(struct coresight_device *csdev, voi
if (list_empty(&csdev->config_csdev_list))
return;
- guard(raw_spinlock_irqsave)(&csdev->cscfg_csdev_lock);
+ guard(raw_spinlock_irqsave)(&csdev->cscfg_csdev_lock);
list_for_each_entry_safe(config_csdev, tmp, &csdev->config_csdev_list, node) {
if (config_csdev->config_desc->load_owner == load_owner)
diff --git a/drivers/hwtracing/coresight/coresight-sysfs.c b/drivers/hwtracing/coresight/coresight-sysfs.c
index feadaf065b53..5e52324aa9ac 100644
--- a/drivers/hwtracing/coresight/coresight-sysfs.c
+++ b/drivers/hwtracing/coresight/coresight-sysfs.c
@@ -7,6 +7,7 @@
#include <linux/device.h>
#include <linux/idr.h>
#include <linux/kernel.h>
+#include <linux/property.h>
#include "coresight-priv.h"
#include "coresight-trace-id.h"
@@ -371,17 +372,81 @@ static ssize_t enable_source_store(struct device *dev,
}
static DEVICE_ATTR_RW(enable_source);
+static ssize_t label_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ const char *str;
+ int ret;
+
+ ret = fwnode_property_read_string(dev_fwnode(dev), "label", &str);
+ if (ret == 0)
+ return sysfs_emit(buf, "%s\n", str);
+ else
+ return ret;
+}
+static DEVICE_ATTR_RO(label);
+
+static umode_t label_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+
+ if (attr == &dev_attr_label.attr) {
+ if (fwnode_property_present(dev_fwnode(dev), "label"))
+ return attr->mode;
+ else
+ return 0;
+ }
+
+ return attr->mode;
+}
+
static struct attribute *coresight_sink_attrs[] = {
&dev_attr_enable_sink.attr,
+ &dev_attr_label.attr,
NULL,
};
-ATTRIBUTE_GROUPS(coresight_sink);
+
+static struct attribute_group coresight_sink_group = {
+ .attrs = coresight_sink_attrs,
+ .is_visible = label_is_visible,
+};
+__ATTRIBUTE_GROUPS(coresight_sink);
static struct attribute *coresight_source_attrs[] = {
&dev_attr_enable_source.attr,
+ &dev_attr_label.attr,
NULL,
};
-ATTRIBUTE_GROUPS(coresight_source);
+
+static struct attribute_group coresight_source_group = {
+ .attrs = coresight_source_attrs,
+ .is_visible = label_is_visible,
+};
+__ATTRIBUTE_GROUPS(coresight_source);
+
+static struct attribute *coresight_link_attrs[] = {
+ &dev_attr_label.attr,
+ NULL,
+};
+
+static struct attribute_group coresight_link_group = {
+ .attrs = coresight_link_attrs,
+ .is_visible = label_is_visible,
+};
+__ATTRIBUTE_GROUPS(coresight_link);
+
+static struct attribute *coresight_helper_attrs[] = {
+ &dev_attr_label.attr,
+ NULL,
+};
+
+static struct attribute_group coresight_helper_group = {
+ .attrs = coresight_helper_attrs,
+ .is_visible = label_is_visible,
+};
+__ATTRIBUTE_GROUPS(coresight_helper);
const struct device_type coresight_dev_type[] = {
[CORESIGHT_DEV_TYPE_SINK] = {
@@ -390,6 +455,7 @@ const struct device_type coresight_dev_type[] = {
},
[CORESIGHT_DEV_TYPE_LINK] = {
.name = "link",
+ .groups = coresight_link_groups,
},
[CORESIGHT_DEV_TYPE_LINKSINK] = {
.name = "linksink",
@@ -401,6 +467,7 @@ const struct device_type coresight_dev_type[] = {
},
[CORESIGHT_DEV_TYPE_HELPER] = {
.name = "helper",
+ .groups = coresight_helper_groups,
}
};
/* Ensure the enum matches the names and groups */
diff --git a/drivers/hwtracing/coresight/coresight-tmc-core.c b/drivers/hwtracing/coresight/coresight-tmc-core.c
index 88afb16bb6be..36599c431be6 100644
--- a/drivers/hwtracing/coresight/coresight-tmc-core.c
+++ b/drivers/hwtracing/coresight/coresight-tmc-core.c
@@ -24,6 +24,7 @@
#include <linux/pm_runtime.h>
#include <linux/of.h>
#include <linux/of_address.h>
+#include <linux/of_reserved_mem.h>
#include <linux/coresight.h>
#include <linux/amba/bus.h>
#include <linux/platform_device.h>
@@ -634,25 +635,14 @@ static int of_tmc_get_reserved_resource_by_name(struct device *dev,
const char *name,
struct resource *res)
{
- int index, rc = -ENODEV;
- struct device_node *node;
+ int rc = -ENODEV;
- if (!is_of_node(dev->fwnode))
- return -ENODEV;
-
- index = of_property_match_string(dev->of_node, "memory-region-names",
- name);
- if (index < 0)
- return rc;
-
- node = of_parse_phandle(dev->of_node, "memory-region", index);
- if (!node)
+ rc = of_reserved_mem_region_to_resource_byname(dev->of_node, name, res);
+ if (rc < 0)
return rc;
- if (!of_address_to_resource(node, 0, res) &&
- res->start != 0 && resource_size(res) != 0)
- rc = 0;
- of_node_put(node);
+ if (res->start == 0 || resource_size(res) == 0)
+ rc = -ENODEV;
return rc;
}
@@ -785,10 +775,20 @@ static int __tmc_probe(struct device *dev, struct resource *res)
u32 devid;
void __iomem *base;
struct coresight_platform_data *pdata = NULL;
- struct tmc_drvdata *drvdata = dev_get_drvdata(dev);
+ struct tmc_drvdata *drvdata;
struct coresight_desc desc = { 0 };
struct coresight_dev_list *dev_list = NULL;
+ drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, drvdata);
+
+ ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk);
+ if (ret)
+ return ret;
+
ret = -ENOMEM;
/* Validity for the resource is already checked by the AMBA core */
@@ -894,14 +894,8 @@ out:
static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
{
- struct tmc_drvdata *drvdata;
int ret;
- drvdata = devm_kzalloc(&adev->dev, sizeof(*drvdata), GFP_KERNEL);
- if (!drvdata)
- return -ENOMEM;
-
- amba_set_drvdata(adev, drvdata);
ret = __tmc_probe(&adev->dev, &adev->res);
if (!ret)
pm_runtime_put(&adev->dev);
@@ -978,18 +972,8 @@ static struct amba_driver tmc_driver = {
static int tmc_platform_probe(struct platform_device *pdev)
{
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- struct tmc_drvdata *drvdata;
int ret = 0;
- drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
- if (!drvdata)
- return -ENOMEM;
-
- drvdata->pclk = coresight_get_enable_apb_pclk(&pdev->dev);
- if (IS_ERR(drvdata->pclk))
- return -ENODEV;
-
- dev_set_drvdata(&pdev->dev, drvdata);
pm_runtime_get_noresume(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
@@ -1011,8 +995,6 @@ static void tmc_platform_remove(struct platform_device *pdev)
__tmc_remove(&pdev->dev);
pm_runtime_disable(&pdev->dev);
- if (!IS_ERR_OR_NULL(drvdata->pclk))
- clk_put(drvdata->pclk);
}
#ifdef CONFIG_PM
@@ -1020,18 +1002,26 @@ static int tmc_runtime_suspend(struct device *dev)
{
struct tmc_drvdata *drvdata = dev_get_drvdata(dev);
- if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_disable_unprepare(drvdata->pclk);
+ clk_disable_unprepare(drvdata->atclk);
+ clk_disable_unprepare(drvdata->pclk);
+
return 0;
}
static int tmc_runtime_resume(struct device *dev)
{
struct tmc_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret;
- if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_prepare_enable(drvdata->pclk);
- return 0;
+ ret = clk_prepare_enable(drvdata->pclk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(drvdata->atclk);
+ if (ret)
+ clk_disable_unprepare(drvdata->pclk);
+
+ return ret;
}
#endif
diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h
index 6541a27a018e..cbb4ba439158 100644
--- a/drivers/hwtracing/coresight/coresight-tmc.h
+++ b/drivers/hwtracing/coresight/coresight-tmc.h
@@ -210,6 +210,7 @@ struct tmc_resrv_buf {
/**
* struct tmc_drvdata - specifics associated to an TMC component
+ * @atclk: optional clock for the core parts of the TMC.
* @pclk: APB clock if present, otherwise NULL
* @base: memory mapped base address for this component.
* @csdev: component vitals needed by the framework.
@@ -244,6 +245,7 @@ struct tmc_resrv_buf {
* Used by ETR/ETF.
*/
struct tmc_drvdata {
+ struct clk *atclk;
struct clk *pclk;
void __iomem *base;
struct coresight_device *csdev;
diff --git a/drivers/hwtracing/coresight/coresight-tnoc.c b/drivers/hwtracing/coresight/coresight-tnoc.c
new file mode 100644
index 000000000000..ff9a0a9cfe96
--- /dev/null
+++ b/drivers/hwtracing/coresight/coresight-tnoc.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+ #include <linux/amba/bus.h>
+ #include <linux/coresight.h>
+ #include <linux/device.h>
+ #include <linux/io.h>
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+ #include <linux/of.h>
+ #include <linux/platform_device.h>
+
+#include "coresight-priv.h"
+#include "coresight-trace-id.h"
+
+#define TRACE_NOC_CTRL 0x008
+#define TRACE_NOC_XLD 0x010
+#define TRACE_NOC_FREQVAL 0x018
+#define TRACE_NOC_SYNCR 0x020
+
+/* Enable generation of output ATB traffic.*/
+#define TRACE_NOC_CTRL_PORTEN BIT(0)
+/* Sets the type of issued ATB FLAG packets.*/
+#define TRACE_NOC_CTRL_FLAGTYPE BIT(7)
+/* Sets the type of issued ATB FREQ packet*/
+#define TRACE_NOC_CTRL_FREQTYPE BIT(8)
+
+#define TRACE_NOC_SYNC_INTERVAL 0xFFFF
+
+/*
+ * struct trace_noc_drvdata - specifics associated to a trace noc component
+ * @base: memory mapped base address for this component.
+ * @dev: device node for trace_noc_drvdata.
+ * @csdev: component vitals needed by the framework.
+ * @spinlock: serialize enable/disable operation.
+ * @atid: id for the trace packet.
+ */
+struct trace_noc_drvdata {
+ void __iomem *base;
+ struct device *dev;
+ struct coresight_device *csdev;
+ spinlock_t spinlock;
+ u32 atid;
+};
+
+DEFINE_CORESIGHT_DEVLIST(trace_noc_devs, "traceNoc");
+
+static void trace_noc_enable_hw(struct trace_noc_drvdata *drvdata)
+{
+ u32 val;
+
+ /* Set ATID */
+ writel_relaxed(drvdata->atid, drvdata->base + TRACE_NOC_XLD);
+
+ /* Set the data word count between 'SYNC' packets */
+ writel_relaxed(TRACE_NOC_SYNC_INTERVAL, drvdata->base + TRACE_NOC_SYNCR);
+
+ /* Set the Control register:
+ * - Set the FLAG packets to 'FLAG' packets
+ * - Set the FREQ packets to 'FREQ_TS' packets
+ * - Enable generation of output ATB traffic
+ */
+
+ val = readl_relaxed(drvdata->base + TRACE_NOC_CTRL);
+
+ val &= ~TRACE_NOC_CTRL_FLAGTYPE;
+ val |= TRACE_NOC_CTRL_FREQTYPE;
+ val |= TRACE_NOC_CTRL_PORTEN;
+
+ writel(val, drvdata->base + TRACE_NOC_CTRL);
+}
+
+static int trace_noc_enable(struct coresight_device *csdev, struct coresight_connection *inport,
+ struct coresight_connection *outport)
+{
+ struct trace_noc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+
+ scoped_guard(spinlock, &drvdata->spinlock) {
+ if (csdev->refcnt == 0)
+ trace_noc_enable_hw(drvdata);
+
+ csdev->refcnt++;
+ }
+
+ dev_dbg(drvdata->dev, "Trace NOC is enabled\n");
+ return 0;
+}
+
+static void trace_noc_disable(struct coresight_device *csdev, struct coresight_connection *inport,
+ struct coresight_connection *outport)
+{
+ struct trace_noc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+
+ scoped_guard(spinlock, &drvdata->spinlock) {
+ if (--csdev->refcnt == 0)
+ writel(0x0, drvdata->base + TRACE_NOC_CTRL);
+ }
+ dev_dbg(drvdata->dev, "Trace NOC is disabled\n");
+}
+
+static int trace_noc_id(struct coresight_device *csdev, __maybe_unused enum cs_mode mode,
+ __maybe_unused struct coresight_device *sink)
+{
+ struct trace_noc_drvdata *drvdata;
+
+ drvdata = dev_get_drvdata(csdev->dev.parent);
+
+ return drvdata->atid;
+}
+
+static const struct coresight_ops_link trace_noc_link_ops = {
+ .enable = trace_noc_enable,
+ .disable = trace_noc_disable,
+};
+
+static const struct coresight_ops trace_noc_cs_ops = {
+ .trace_id = trace_noc_id,
+ .link_ops = &trace_noc_link_ops,
+};
+
+static int trace_noc_init_default_data(struct trace_noc_drvdata *drvdata)
+{
+ int atid;
+
+ atid = coresight_trace_id_get_system_id();
+ if (atid < 0)
+ return atid;
+
+ drvdata->atid = atid;
+
+ return 0;
+}
+
+static ssize_t traceid_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned long val;
+ struct trace_noc_drvdata *drvdata = dev_get_drvdata(dev->parent);
+
+ val = drvdata->atid;
+ return sprintf(buf, "%#lx\n", val);
+}
+static DEVICE_ATTR_RO(traceid);
+
+static struct attribute *coresight_tnoc_attrs[] = {
+ &dev_attr_traceid.attr,
+ NULL,
+};
+
+static const struct attribute_group coresight_tnoc_group = {
+ .attrs = coresight_tnoc_attrs,
+};
+
+static const struct attribute_group *coresight_tnoc_groups[] = {
+ &coresight_tnoc_group,
+ NULL,
+};
+
+static int trace_noc_probe(struct amba_device *adev, const struct amba_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct coresight_platform_data *pdata;
+ struct trace_noc_drvdata *drvdata;
+ struct coresight_desc desc = { 0 };
+ int ret;
+
+ desc.name = coresight_alloc_device_name(&trace_noc_devs, dev);
+ if (!desc.name)
+ return -ENOMEM;
+
+ pdata = coresight_get_platform_data(dev);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+ adev->dev.platform_data = pdata;
+
+ drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ drvdata->dev = &adev->dev;
+ dev_set_drvdata(dev, drvdata);
+
+ drvdata->base = devm_ioremap_resource(dev, &adev->res);
+ if (IS_ERR(drvdata->base))
+ return PTR_ERR(drvdata->base);
+
+ spin_lock_init(&drvdata->spinlock);
+
+ ret = trace_noc_init_default_data(drvdata);
+ if (ret)
+ return ret;
+
+ desc.ops = &trace_noc_cs_ops;
+ desc.type = CORESIGHT_DEV_TYPE_LINK;
+ desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_MERG;
+ desc.pdata = adev->dev.platform_data;
+ desc.dev = &adev->dev;
+ desc.access = CSDEV_ACCESS_IOMEM(drvdata->base);
+ desc.groups = coresight_tnoc_groups;
+ drvdata->csdev = coresight_register(&desc);
+ if (IS_ERR(drvdata->csdev)) {
+ coresight_trace_id_put_system_id(drvdata->atid);
+ return PTR_ERR(drvdata->csdev);
+ }
+ pm_runtime_put(&adev->dev);
+
+ return 0;
+}
+
+static void trace_noc_remove(struct amba_device *adev)
+{
+ struct trace_noc_drvdata *drvdata = dev_get_drvdata(&adev->dev);
+
+ coresight_unregister(drvdata->csdev);
+ coresight_trace_id_put_system_id(drvdata->atid);
+}
+
+static struct amba_id trace_noc_ids[] = {
+ {
+ .id = 0x000f0c00,
+ .mask = 0x00ffff00,
+ },
+ {
+ .id = 0x001f0c00,
+ .mask = 0x00ffff00,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(amba, trace_noc_ids);
+
+static struct amba_driver trace_noc_driver = {
+ .drv = {
+ .name = "coresight-trace-noc",
+ .suppress_bind_attrs = true,
+ },
+ .probe = trace_noc_probe,
+ .remove = trace_noc_remove,
+ .id_table = trace_noc_ids,
+};
+
+module_amba_driver(trace_noc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Trace NOC driver");
diff --git a/drivers/hwtracing/coresight/coresight-tpda.c b/drivers/hwtracing/coresight/coresight-tpda.c
index 0633f04beb24..333b3cb23685 100644
--- a/drivers/hwtracing/coresight/coresight-tpda.c
+++ b/drivers/hwtracing/coresight/coresight-tpda.c
@@ -71,6 +71,8 @@ static int tpdm_read_element_size(struct tpda_drvdata *drvdata,
if (tpdm_data->dsb) {
rc = fwnode_property_read_u32(dev_fwnode(csdev->dev.parent),
"qcom,dsb-element-bits", &drvdata->dsb_esize);
+ if (rc)
+ goto out;
}
if (tpdm_data->cmb) {
@@ -78,6 +80,7 @@ static int tpdm_read_element_size(struct tpda_drvdata *drvdata,
"qcom,cmb-element-bits", &drvdata->cmb_esize);
}
+out:
if (rc)
dev_warn_once(&csdev->dev,
"Failed to read TPDM Element size: %d\n", rc);
diff --git a/drivers/hwtracing/coresight/coresight-tpiu.c b/drivers/hwtracing/coresight/coresight-tpiu.c
index 3e0159288428..9463afdbda8a 100644
--- a/drivers/hwtracing/coresight/coresight-tpiu.c
+++ b/drivers/hwtracing/coresight/coresight-tpiu.c
@@ -128,11 +128,11 @@ static const struct coresight_ops tpiu_cs_ops = {
static int __tpiu_probe(struct device *dev, struct resource *res)
{
- int ret;
void __iomem *base;
struct coresight_platform_data *pdata = NULL;
struct tpiu_drvdata *drvdata;
struct coresight_desc desc = { 0 };
+ int ret;
desc.name = coresight_alloc_device_name(&tpiu_devs, dev);
if (!desc.name)
@@ -144,16 +144,10 @@ static int __tpiu_probe(struct device *dev, struct resource *res)
spin_lock_init(&drvdata->spinlock);
- drvdata->atclk = devm_clk_get(dev, "atclk"); /* optional */
- if (!IS_ERR(drvdata->atclk)) {
- ret = clk_prepare_enable(drvdata->atclk);
- if (ret)
- return ret;
- }
+ ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk);
+ if (ret)
+ return ret;
- drvdata->pclk = coresight_get_enable_apb_pclk(dev);
- if (IS_ERR(drvdata->pclk))
- return -ENODEV;
dev_set_drvdata(dev, drvdata);
/* Validity for the resource is already checked by the AMBA core */
@@ -212,24 +206,26 @@ static int tpiu_runtime_suspend(struct device *dev)
{
struct tpiu_drvdata *drvdata = dev_get_drvdata(dev);
- if (drvdata && !IS_ERR(drvdata->atclk))
- clk_disable_unprepare(drvdata->atclk);
+ clk_disable_unprepare(drvdata->atclk);
+ clk_disable_unprepare(drvdata->pclk);
- if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_disable_unprepare(drvdata->pclk);
return 0;
}
static int tpiu_runtime_resume(struct device *dev)
{
struct tpiu_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret;
- if (drvdata && !IS_ERR(drvdata->atclk))
- clk_prepare_enable(drvdata->atclk);
+ ret = clk_prepare_enable(drvdata->pclk);
+ if (ret)
+ return ret;
- if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk))
- clk_prepare_enable(drvdata->pclk);
- return 0;
+ ret = clk_prepare_enable(drvdata->atclk);
+ if (ret)
+ clk_disable_unprepare(drvdata->pclk);
+
+ return ret;
}
#endif
@@ -293,8 +289,6 @@ static void tpiu_platform_remove(struct platform_device *pdev)
__tpiu_remove(&pdev->dev);
pm_runtime_disable(&pdev->dev);
- if (!IS_ERR_OR_NULL(drvdata->pclk))
- clk_put(drvdata->pclk);
}
#ifdef CONFIG_ACPI
diff --git a/drivers/hwtracing/coresight/coresight-trbe.c b/drivers/hwtracing/coresight/coresight-trbe.c
index 8f426f94e32a..43643d2c5bdd 100644
--- a/drivers/hwtracing/coresight/coresight-trbe.c
+++ b/drivers/hwtracing/coresight/coresight-trbe.c
@@ -258,6 +258,7 @@ static void trbe_drain_and_disable_local(struct trbe_cpudata *cpudata)
static void trbe_reset_local(struct trbe_cpudata *cpudata)
{
write_sysreg_s(0, SYS_TRBLIMITR_EL1);
+ isb();
trbe_drain_buffer();
write_sysreg_s(0, SYS_TRBPTR_EL1);
write_sysreg_s(0, SYS_TRBBASER_EL1);
@@ -748,12 +749,12 @@ static void *arm_trbe_alloc_buffer(struct coresight_device *csdev,
buf = kzalloc_node(sizeof(*buf), GFP_KERNEL, trbe_alloc_node(event));
if (!buf)
- return ERR_PTR(-ENOMEM);
+ return NULL;
pglist = kcalloc(nr_pages, sizeof(*pglist), GFP_KERNEL);
if (!pglist) {
kfree(buf);
- return ERR_PTR(-ENOMEM);
+ return NULL;
}
for (i = 0; i < nr_pages; i++)
@@ -763,7 +764,7 @@ static void *arm_trbe_alloc_buffer(struct coresight_device *csdev,
if (!buf->trbe_base) {
kfree(pglist);
kfree(buf);
- return ERR_PTR(-ENOMEM);
+ return NULL;
}
buf->trbe_limit = buf->trbe_base + nr_pages * PAGE_SIZE;
buf->trbe_write = buf->trbe_base;
@@ -1280,7 +1281,7 @@ static void arm_trbe_register_coresight_cpu(struct trbe_drvdata *drvdata, int cp
* into the device for that purpose.
*/
desc.pdata = devm_kzalloc(dev, sizeof(*desc.pdata), GFP_KERNEL);
- if (IS_ERR(desc.pdata))
+ if (!desc.pdata)
goto cpu_clear;
desc.type = CORESIGHT_DEV_TYPE_SINK;
diff --git a/drivers/hwtracing/coresight/ultrasoc-smb.h b/drivers/hwtracing/coresight/ultrasoc-smb.h
index c4c111275627..323f0ccb6878 100644
--- a/drivers/hwtracing/coresight/ultrasoc-smb.h
+++ b/drivers/hwtracing/coresight/ultrasoc-smb.h
@@ -7,6 +7,7 @@
#ifndef _ULTRASOC_SMB_H
#define _ULTRASOC_SMB_H
+#include <linux/bitfield.h>
#include <linux/miscdevice.h>
#include <linux/spinlock.h>
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index ae8fcc864060..fd81e49638aa 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1368,6 +1368,17 @@ config I2C_NCT6694
This driver can also be built as a module. If so, the module will
be called i2c-nct6694.
+config I2C_USBIO
+ tristate "Intel USBIO I2C Adapter support"
+ depends on USB_USBIO
+ default USB_USBIO
+ help
+ Select this option to enable I2C driver for the INTEL
+ USBIO driver stack.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c_usbio.
+
config I2C_CP2615
tristate "Silicon Labs CP2615 USB sound card and I2C adapter"
depends on USB
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index fe8cf6325fc9..fb985769f5ff 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -136,6 +136,7 @@ obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o
obj-$(CONFIG_I2C_DLN2) += i2c-dln2.o
obj-$(CONFIG_I2C_LJCA) += i2c-ljca.o
obj-$(CONFIG_I2C_NCT6694) += i2c-nct6694.o
+obj-$(CONFIG_I2C_USBIO) += i2c-usbio.o
obj-$(CONFIG_I2C_CP2615) += i2c-cp2615.o
obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o
obj-$(CONFIG_I2C_PCI1XXXX) += i2c-mchp-pci1xxxx.o
diff --git a/drivers/i2c/busses/i2c-usbio.c b/drivers/i2c/busses/i2c-usbio.c
new file mode 100644
index 000000000000..d42f9ab6e9a5
--- /dev/null
+++ b/drivers/i2c/busses/i2c-usbio.c
@@ -0,0 +1,320 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Intel Corporation.
+ * Copyright (c) 2025 Red Hat, Inc.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/types.h>
+#include <linux/usb/usbio.h>
+
+#define I2C_RW_OVERHEAD (sizeof(struct usbio_bulk_packet) + sizeof(struct usbio_i2c_rw))
+
+struct usbio_i2c {
+ struct i2c_adapter adap;
+ struct auxiliary_device *adev;
+ struct usbio_i2c_rw *rwbuf;
+ unsigned long quirks;
+ u32 speed;
+ u16 txbuf_len;
+ u16 rxbuf_len;
+};
+
+static const struct acpi_device_id usbio_i2c_acpi_hids[] = {
+ { "INTC1008" }, /* MTL */
+ { "INTC10B3" }, /* ARL */
+ { "INTC10B6" }, /* LNL */
+ { "INTC10E3" }, /* PTL */
+ { }
+};
+
+static const u32 usbio_i2c_speeds[] = {
+ I2C_MAX_STANDARD_MODE_FREQ,
+ I2C_MAX_FAST_MODE_FREQ,
+ I2C_MAX_FAST_MODE_PLUS_FREQ,
+ I2C_MAX_HIGH_SPEED_MODE_FREQ
+};
+
+static void usbio_i2c_uninit(struct i2c_adapter *adap, struct i2c_msg *msg)
+{
+ struct usbio_i2c *i2c = i2c_get_adapdata(adap);
+ struct usbio_i2c_uninit ubuf;
+
+ ubuf.busid = i2c->adev->id;
+ ubuf.config = cpu_to_le16(msg->addr);
+
+ usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, USBIO_I2CCMD_UNINIT, true,
+ &ubuf, sizeof(ubuf), NULL, 0);
+}
+
+static int usbio_i2c_init(struct i2c_adapter *adap, struct i2c_msg *msg)
+{
+ struct usbio_i2c *i2c = i2c_get_adapdata(adap);
+ struct usbio_i2c_init ibuf;
+ void *reply_buf;
+ u16 reply_len;
+ int ret;
+
+ ibuf.busid = i2c->adev->id;
+ ibuf.config = cpu_to_le16(msg->addr);
+ ibuf.speed = cpu_to_le32(i2c->speed);
+
+ if (i2c->quirks & USBIO_QUIRK_I2C_NO_INIT_ACK) {
+ reply_buf = NULL;
+ reply_len = 0;
+ } else {
+ reply_buf = &ibuf;
+ reply_len = sizeof(ibuf);
+ }
+
+ ret = usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, USBIO_I2CCMD_INIT, true,
+ &ibuf, sizeof(ibuf), reply_buf, reply_len);
+ if (ret != sizeof(ibuf))
+ return (ret < 0) ? ret : -EIO;
+
+ return 0;
+}
+
+static int usbio_i2c_read(struct i2c_adapter *adap, struct i2c_msg *msg)
+{
+ struct usbio_i2c *i2c = i2c_get_adapdata(adap);
+ u16 rxchunk = i2c->rxbuf_len - I2C_RW_OVERHEAD;
+ struct usbio_i2c_rw *rbuf = i2c->rwbuf;
+ int ret;
+
+ rbuf->busid = i2c->adev->id;
+ rbuf->config = cpu_to_le16(msg->addr);
+ rbuf->size = cpu_to_le16(msg->len);
+
+ if (msg->len > rxchunk) {
+ /* Need to split the input buffer */
+ u16 len = 0;
+
+ do {
+ if (msg->len - len < rxchunk)
+ rxchunk = msg->len - len;
+
+ ret = usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C,
+ USBIO_I2CCMD_READ, true,
+ rbuf, len == 0 ? sizeof(*rbuf) : 0,
+ rbuf, sizeof(*rbuf) + rxchunk);
+ if (ret < 0)
+ return ret;
+
+ memcpy(&msg->buf[len], rbuf->data, rxchunk);
+ len += rxchunk;
+ } while (msg->len > len);
+
+ return 0;
+ }
+
+ ret = usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, USBIO_I2CCMD_READ, true,
+ rbuf, sizeof(*rbuf), rbuf, sizeof(*rbuf) + msg->len);
+ if (ret != sizeof(*rbuf) + msg->len)
+ return (ret < 0) ? ret : -EIO;
+
+ memcpy(msg->buf, rbuf->data, msg->len);
+
+ return 0;
+}
+
+static int usbio_i2c_write(struct i2c_adapter *adap, struct i2c_msg *msg)
+{
+ struct usbio_i2c *i2c = i2c_get_adapdata(adap);
+ u16 txchunk = i2c->txbuf_len - I2C_RW_OVERHEAD;
+ struct usbio_i2c_rw *wbuf = i2c->rwbuf;
+ int ret;
+
+ if (msg->len > txchunk) {
+ /* Need to split the output buffer */
+ u16 len = 0;
+
+ do {
+ wbuf->busid = i2c->adev->id;
+ wbuf->config = cpu_to_le16(msg->addr);
+
+ if (i2c->quirks & USBIO_QUIRK_I2C_USE_CHUNK_LEN)
+ wbuf->size = cpu_to_le16(txchunk);
+ else
+ wbuf->size = cpu_to_le16(msg->len);
+
+ memcpy(wbuf->data, &msg->buf[len], txchunk);
+ len += txchunk;
+
+ ret = usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C,
+ USBIO_I2CCMD_WRITE, msg->len == len,
+ wbuf, sizeof(*wbuf) + txchunk,
+ wbuf, sizeof(*wbuf));
+ if (ret < 0)
+ return ret;
+
+ if (msg->len - len < txchunk)
+ txchunk = msg->len - len;
+ } while (msg->len > len);
+
+ return 0;
+ }
+
+ wbuf->busid = i2c->adev->id;
+ wbuf->config = cpu_to_le16(msg->addr);
+ wbuf->size = cpu_to_le16(msg->len);
+ memcpy(wbuf->data, msg->buf, msg->len);
+
+ ret = usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, USBIO_I2CCMD_WRITE, true,
+ wbuf, sizeof(*wbuf) + msg->len, wbuf, sizeof(*wbuf));
+ if (ret != sizeof(*wbuf) || le16_to_cpu(wbuf->size) != msg->len)
+ return (ret < 0) ? ret : -EIO;
+
+ return 0;
+}
+
+static int usbio_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+ struct usbio_i2c *i2c = i2c_get_adapdata(adap);
+ int ret;
+
+ usbio_acquire(i2c->adev);
+
+ ret = usbio_i2c_init(adap, msgs);
+ if (ret)
+ goto out_release;
+
+ for (int i = 0; i < num; ret = ++i) {
+ if (msgs[i].flags & I2C_M_RD)
+ ret = usbio_i2c_read(adap, &msgs[i]);
+ else
+ ret = usbio_i2c_write(adap, &msgs[i]);
+
+ if (ret)
+ break;
+ }
+
+ usbio_i2c_uninit(adap, msgs);
+
+out_release:
+ usbio_release(i2c->adev);
+
+ return ret;
+}
+
+static u32 usbio_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_adapter_quirks usbio_i2c_quirks = {
+ .flags = I2C_AQ_NO_ZERO_LEN | I2C_AQ_NO_REP_START,
+ .max_read_len = SZ_4K,
+ .max_write_len = SZ_4K,
+};
+
+static const struct i2c_adapter_quirks usbio_i2c_quirks_max_rw_len52 = {
+ .flags = I2C_AQ_NO_ZERO_LEN | I2C_AQ_NO_REP_START,
+ .max_read_len = 52,
+ .max_write_len = 52,
+};
+
+static const struct i2c_algorithm usbio_i2c_algo = {
+ .master_xfer = usbio_i2c_xfer,
+ .functionality = usbio_i2c_func,
+};
+
+static int usbio_i2c_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *adev_id)
+{
+ struct usbio_i2c_bus_desc *i2c_desc;
+ struct device *dev = &adev->dev;
+ struct usbio_i2c *i2c;
+ u32 max_speed;
+ int ret;
+
+ i2c_desc = dev_get_platdata(dev);
+ if (!i2c_desc)
+ return -EINVAL;
+
+ i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
+ if (!i2c)
+ return -ENOMEM;
+
+ i2c->adev = adev;
+
+ usbio_acpi_bind(i2c->adev, usbio_i2c_acpi_hids);
+ usbio_get_txrxbuf_len(i2c->adev, &i2c->txbuf_len, &i2c->rxbuf_len);
+
+ i2c->rwbuf = devm_kzalloc(dev, max(i2c->txbuf_len, i2c->rxbuf_len), GFP_KERNEL);
+ if (!i2c->rwbuf)
+ return -ENOMEM;
+
+ i2c->quirks = usbio_get_quirks(i2c->adev);
+
+ max_speed = usbio_i2c_speeds[i2c_desc->caps & USBIO_I2C_BUS_MODE_CAP_MASK];
+ if (max_speed < I2C_MAX_FAST_MODE_FREQ &&
+ (i2c->quirks & USBIO_QUIRK_I2C_ALLOW_400KHZ))
+ max_speed = I2C_MAX_FAST_MODE_FREQ;
+
+ i2c->speed = i2c_acpi_find_bus_speed(dev);
+ if (!i2c->speed)
+ i2c->speed = I2C_MAX_STANDARD_MODE_FREQ;
+ else if (i2c->speed > max_speed) {
+ dev_warn(dev, "Invalid speed %u adjusting to bus max %u\n",
+ i2c->speed, max_speed);
+ i2c->speed = max_speed;
+ }
+
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.class = I2C_CLASS_HWMON;
+ i2c->adap.dev.parent = dev;
+ i2c->adap.algo = &usbio_i2c_algo;
+
+ if (i2c->quirks & USBIO_QUIRK_I2C_MAX_RW_LEN_52)
+ i2c->adap.quirks = &usbio_i2c_quirks_max_rw_len52;
+ else
+ i2c->adap.quirks = &usbio_i2c_quirks;
+
+ snprintf(i2c->adap.name, sizeof(i2c->adap.name), "%s.%d",
+ USBIO_I2C_CLIENT, i2c->adev->id);
+
+ device_set_node(&i2c->adap.dev, dev_fwnode(&adev->dev));
+
+ auxiliary_set_drvdata(adev, i2c);
+ i2c_set_adapdata(&i2c->adap, i2c);
+
+ ret = i2c_add_adapter(&i2c->adap);
+ if (ret)
+ return ret;
+
+ if (has_acpi_companion(&i2c->adap.dev))
+ acpi_dev_clear_dependencies(ACPI_COMPANION(&i2c->adap.dev));
+
+ return 0;
+}
+
+static void usbio_i2c_remove(struct auxiliary_device *adev)
+{
+ struct usbio_i2c *i2c = auxiliary_get_drvdata(adev);
+
+ i2c_del_adapter(&i2c->adap);
+}
+
+static const struct auxiliary_device_id usbio_i2c_id_table[] = {
+ { "usbio.usbio-i2c" },
+ { }
+};
+MODULE_DEVICE_TABLE(auxiliary, usbio_i2c_id_table);
+
+static struct auxiliary_driver usbio_i2c_driver = {
+ .name = USBIO_I2C_CLIENT,
+ .probe = usbio_i2c_probe,
+ .remove = usbio_i2c_remove,
+ .id_table = usbio_i2c_id_table
+};
+module_auxiliary_driver(usbio_i2c_driver);
+
+MODULE_DESCRIPTION("Intel USBIO I2C driver");
+MODULE_AUTHOR("Israel Cepeda <israel.a.cepeda.lopez@intel.com>");
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("USBIO");
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index b7dfd0007aa0..78e3f799ecc1 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -36,10 +36,29 @@
#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
#define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
#define ADXL345_REG_TAP_SUPPRESS BIT(3)
+#define ADXL345_POWER_CTL_INACT_MSK (ADXL345_POWER_CTL_AUTO_SLEEP | ADXL345_POWER_CTL_LINK)
#define ADXL345_TAP_Z_EN BIT(0)
#define ADXL345_TAP_Y_EN BIT(1)
#define ADXL345_TAP_X_EN BIT(2)
+#define ADXL345_REG_TAP_SUPPRESS BIT(3)
+
+#define ADXL345_INACT_Z_EN BIT(0)
+#define ADXL345_INACT_Y_EN BIT(1)
+#define ADXL345_INACT_X_EN BIT(2)
+#define ADXL345_REG_INACT_ACDC BIT(3)
+#define ADXL345_ACT_INACT_NO_AXIS_EN 0x00
+#define ADXL345_INACT_XYZ_EN (ADXL345_INACT_Z_EN | ADXL345_INACT_Y_EN | ADXL345_INACT_X_EN)
+
+#define ADXL345_ACT_Z_EN BIT(4)
+#define ADXL345_ACT_Y_EN BIT(5)
+#define ADXL345_ACT_X_EN BIT(6)
+#define ADXL345_REG_ACT_ACDC BIT(7)
+#define ADXL345_ACT_XYZ_EN (ADXL345_ACT_Z_EN | ADXL345_ACT_Y_EN | ADXL345_ACT_X_EN)
+
+#define ADXL345_COUPLING_DC 0
+#define ADXL345_COUPLING_AC 1
+#define ADXL345_REG_NO_ACDC 0x00
/* single/double tap */
enum adxl345_tap_type {
@@ -64,6 +83,39 @@ static const unsigned int adxl345_tap_time_reg[] = {
[ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
};
+/* activity/inactivity */
+enum adxl345_activity_type {
+ ADXL345_ACTIVITY,
+ ADXL345_INACTIVITY,
+ ADXL345_ACTIVITY_AC,
+ ADXL345_INACTIVITY_AC,
+ ADXL345_INACTIVITY_FF,
+};
+
+static const unsigned int adxl345_act_int_reg[] = {
+ [ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY,
+ [ADXL345_INACTIVITY] = ADXL345_INT_INACTIVITY,
+ [ADXL345_ACTIVITY_AC] = ADXL345_INT_ACTIVITY,
+ [ADXL345_INACTIVITY_AC] = ADXL345_INT_INACTIVITY,
+ [ADXL345_INACTIVITY_FF] = ADXL345_INT_FREE_FALL,
+};
+
+static const unsigned int adxl345_act_thresh_reg[] = {
+ [ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT,
+ [ADXL345_INACTIVITY] = ADXL345_REG_THRESH_INACT,
+ [ADXL345_ACTIVITY_AC] = ADXL345_REG_THRESH_ACT,
+ [ADXL345_INACTIVITY_AC] = ADXL345_REG_THRESH_INACT,
+ [ADXL345_INACTIVITY_FF] = ADXL345_REG_THRESH_FF,
+};
+
+static const unsigned int adxl345_act_acdc_msk[] = {
+ [ADXL345_ACTIVITY] = ADXL345_REG_ACT_ACDC,
+ [ADXL345_INACTIVITY] = ADXL345_REG_INACT_ACDC,
+ [ADXL345_ACTIVITY_AC] = ADXL345_REG_ACT_ACDC,
+ [ADXL345_INACTIVITY_AC] = ADXL345_REG_INACT_ACDC,
+ [ADXL345_INACTIVITY_FF] = ADXL345_REG_NO_ACDC,
+};
+
enum adxl345_odr {
ADXL345_ODR_0P10HZ = 0,
ADXL345_ODR_0P20HZ,
@@ -129,6 +181,14 @@ static const int adxl345_fullres_range_tbl[][2] = {
[ADXL345_16G_RANGE] = { 0, 38312 },
};
+/* scaling */
+static const int adxl345_range_factor_tbl[] = {
+ [ADXL345_2G_RANGE] = 1,
+ [ADXL345_4G_RANGE] = 2,
+ [ADXL345_8G_RANGE] = 4,
+ [ADXL345_16G_RANGE] = 8,
+};
+
struct adxl345_state {
const struct adxl345_chip_info *info;
struct regmap *regmap;
@@ -136,6 +196,9 @@ struct adxl345_state {
u8 watermark;
u8 fifo_mode;
+ u8 inact_threshold;
+ u32 inact_time_ms;
+
u32 tap_duration_us;
u32 tap_latent_us;
u32 tap_window_us;
@@ -145,6 +208,22 @@ struct adxl345_state {
static const struct iio_event_spec adxl345_events[] = {
{
+ /* activity */
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_shared_by_type =
+ BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE),
+ },
+ {
+ /* activity, ac bit set */
+ .type = IIO_EV_TYPE_MAG_ADAPTIVE,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_shared_by_type =
+ BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE),
+ },
+ {
/* single tap */
.type = IIO_EV_TYPE_GESTURE,
.dir = IIO_EV_DIR_SINGLETAP,
@@ -188,10 +267,39 @@ enum adxl345_chans {
chan_x, chan_y, chan_z,
};
+static const struct iio_event_spec adxl345_fake_chan_events[] = {
+ {
+ /* inactivity */
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type =
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD),
+ },
+ {
+ /* inactivity, AC bit set */
+ .type = IIO_EV_TYPE_MAG_ADAPTIVE,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type =
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD),
+ },
+};
+
static const struct iio_chan_spec adxl345_channels[] = {
ADXL345_CHANNEL(0, chan_x, X),
ADXL345_CHANNEL(1, chan_y, Y),
ADXL345_CHANNEL(2, chan_z, Z),
+ {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_X_AND_Y_AND_Z,
+ .scan_index = -1, /* Fake channel */
+ .event_spec = adxl345_fake_chan_events,
+ .num_event_specs = ARRAY_SIZE(adxl345_fake_chan_events),
+ },
};
static const unsigned long adxl345_scan_masks[] = {
@@ -237,6 +345,394 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
ADXL345_POWER_CTL_MEASURE, en);
}
+/* activity / inactivity */
+
+static int adxl345_set_inact_threshold(struct adxl345_state *st,
+ unsigned int threshold)
+{
+ int ret;
+
+ st->inact_threshold = min(U8_MAX, threshold);
+
+ ret = regmap_write(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_INACTIVITY],
+ st->inact_threshold);
+ if (ret)
+ return ret;
+
+ return regmap_write(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_INACTIVITY_FF],
+ st->inact_threshold);
+}
+
+static int adxl345_set_default_time(struct adxl345_state *st)
+{
+ int max_boundary = U8_MAX;
+ int min_boundary = 10;
+ enum adxl345_odr odr;
+ unsigned int regval;
+ unsigned int val;
+ int ret;
+
+ /* Generated inactivity time based on ODR */
+ ret = regmap_read(st->regmap, ADXL345_REG_BW_RATE, &regval);
+ if (ret)
+ return ret;
+
+ odr = FIELD_GET(ADXL345_BW_RATE_MSK, regval);
+ val = clamp(max_boundary - adxl345_odr_tbl[odr][0],
+ min_boundary, max_boundary);
+ st->inact_time_ms = MILLI * val;
+
+ /* Inactivity time in s */
+ return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val);
+}
+
+static int adxl345_set_inactivity_time(struct adxl345_state *st, u32 val_int)
+{
+ st->inact_time_ms = MILLI * val_int;
+
+ return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val_int);
+}
+
+static int adxl345_set_freefall_time(struct adxl345_state *st, u32 val_fract)
+{
+ /*
+ * Datasheet max. value is 255 * 5000 us = 1.275000 seconds.
+ *
+ * Recommended values between 100ms and 350ms (0x14 to 0x46)
+ */
+ st->inact_time_ms = DIV_ROUND_UP(val_fract, MILLI);
+
+ return regmap_write(st->regmap, ADXL345_REG_TIME_FF,
+ DIV_ROUND_CLOSEST(val_fract, 5));
+}
+
+/**
+ * adxl345_set_inact_time - Configure inactivity time explicitly or by ODR.
+ * @st: The sensor state instance.
+ * @val_int: The inactivity time, integer part.
+ * @val_fract: The inactivity time, fractional part when val_int is 0.
+ *
+ * Inactivity time can be configured between 1 and 255 seconds. If a user sets
+ * val_s to 0, a default inactivity time is calculated automatically (since 0 is
+ * also invalid and undefined by the sensor).
+ *
+ * In such cases, power consumption should be considered: the inactivity period
+ * should be shorter at higher sampling frequencies and longer at lower ones.
+ * Specifically, for frequencies above 255 Hz, the default is set to 10 seconds;
+ * for frequencies below 10 Hz, it defaults to 255 seconds.
+ *
+ * The calculation method subtracts the integer part of the configured sample
+ * frequency from 255 to estimate the inactivity time in seconds. Sub-Hertz
+ * values are ignored in this approximation. Since the recommended output data
+ * rates (ODRs) for features like activity/inactivity detection, sleep modes,
+ * and free fall range between 12.5 Hz and 400 Hz, frequencies outside this
+ * range will either use the defined boundary defaults or require explicit
+ * configuration via val_s.
+ *
+ * Return: 0 or error value.
+ */
+static int adxl345_set_inact_time(struct adxl345_state *st, u32 val_int,
+ u32 val_fract)
+{
+ if (val_int > 0) {
+ /* Time >= 1s, inactivity */
+ return adxl345_set_inactivity_time(st, val_int);
+ } else if (val_int == 0) {
+ if (val_fract > 0) {
+ /* Time < 1s, free-fall */
+ return adxl345_set_freefall_time(st, val_fract);
+ } else if (val_fract == 0) {
+ /* Time == 0.0s */
+ return adxl345_set_default_time(st);
+ }
+ }
+
+ /* Do not support negative or wrong input. */
+ return -EINVAL;
+}
+
+/**
+ * adxl345_is_act_inact_ac() - Verify if AC or DC coupling is currently enabled.
+ *
+ * @st: The device data.
+ * @type: The activity or inactivity type.
+ *
+ * Given a type of activity / inactivity combined with either AC coupling set or
+ * default to DC, this function verifies if the combination is currently
+ * configured, hence enabled or not.
+ *
+ * Return: true if configured coupling matches the provided type, else a negative
+ * error value.
+ */
+static int adxl345_is_act_inact_ac(struct adxl345_state *st,
+ enum adxl345_activity_type type)
+{
+ unsigned int regval;
+ bool coupling;
+ int ret;
+
+ if (type == ADXL345_INACTIVITY_FF)
+ return true;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &regval);
+ if (ret)
+ return ret;
+
+ coupling = adxl345_act_acdc_msk[type] & regval;
+
+ switch (type) {
+ case ADXL345_ACTIVITY:
+ case ADXL345_INACTIVITY:
+ return coupling == ADXL345_COUPLING_DC;
+ case ADXL345_ACTIVITY_AC:
+ case ADXL345_INACTIVITY_AC:
+ return coupling == ADXL345_COUPLING_AC;
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * adxl345_set_act_inact_ac() - Configure AC coupling or DC coupling.
+ *
+ * @st: The device data.
+ * @type: Provide a type of activity or inactivity.
+ * @cmd_en: enable or disable AC coupling.
+ *
+ * Enables AC coupling or DC coupling depending on the provided type argument.
+ * Note: Activity and inactivity can be either AC coupled or DC coupled not
+ * both at the same time.
+ *
+ * Return: 0 if successful, else error value.
+ */
+static int adxl345_set_act_inact_ac(struct adxl345_state *st,
+ enum adxl345_activity_type type,
+ bool cmd_en)
+{
+ unsigned int act_inact_ac;
+
+ if (type == ADXL345_ACTIVITY_AC || type == ADXL345_INACTIVITY_AC)
+ act_inact_ac = ADXL345_COUPLING_AC && cmd_en;
+ else
+ act_inact_ac = ADXL345_COUPLING_DC && cmd_en;
+
+ /*
+ * A setting of false selects dc-coupled operation, and a setting of
+ * true enables ac-coupled operation. In dc-coupled operation, the
+ * current acceleration magnitude is compared directly with
+ * ADXL345_REG_THRESH_ACT and ADXL345_REG_THRESH_INACT to determine
+ * whether activity or inactivity is detected.
+ *
+ * In ac-coupled operation for activity detection, the acceleration
+ * value at the start of activity detection is taken as a reference
+ * value. New samples of acceleration are then compared to this
+ * reference value, and if the magnitude of the difference exceeds the
+ * ADXL345_REG_THRESH_ACT value, the device triggers an activity
+ * interrupt.
+ *
+ * Similarly, in ac-coupled operation for inactivity detection, a
+ * reference value is used for comparison and is updated whenever the
+ * device exceeds the inactivity threshold. After the reference value
+ * is selected, the device compares the magnitude of the difference
+ * between the reference value and the current acceleration with
+ * ADXL345_REG_THRESH_INACT. If the difference is less than the value in
+ * ADXL345_REG_THRESH_INACT for the time in ADXL345_REG_TIME_INACT, the
+ * device is considered inactive and the inactivity interrupt is
+ * triggered. [quoted from p. 24, ADXL345 datasheet Rev. G]
+ *
+ * In a conclusion, the first acceleration snapshot sample which hit the
+ * threshold in a particular direction is always taken as acceleration
+ * reference value to that direction. Since for the hardware activity
+ * and inactivity depend on the x/y/z axis, so do ac and dc coupling.
+ * Note, this sw driver always enables or disables all three x/y/z axis
+ * for detection via act_axis_ctrl and inact_axis_ctrl, respectively.
+ * Where in dc-coupling samples are compared against the thresholds, in
+ * ac-coupling measurement difference to the first acceleration
+ * reference value are compared against the threshold. So, ac-coupling
+ * allows for a bit more dynamic compensation depending on the initial
+ * sample.
+ */
+ return regmap_assign_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL,
+ adxl345_act_acdc_msk[type], act_inact_ac);
+}
+
+static int adxl345_is_act_inact_en(struct adxl345_state *st,
+ enum adxl345_activity_type type)
+{
+ unsigned int axis_ctrl;
+ unsigned int regval;
+ bool int_en, en;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &axis_ctrl);
+ if (ret)
+ return ret;
+
+ /* Check if axis for activity are enabled */
+ switch (type) {
+ case ADXL345_ACTIVITY:
+ case ADXL345_ACTIVITY_AC:
+ en = FIELD_GET(ADXL345_ACT_XYZ_EN, axis_ctrl);
+ if (!en)
+ return false;
+ break;
+ case ADXL345_INACTIVITY:
+ case ADXL345_INACTIVITY_AC:
+ en = FIELD_GET(ADXL345_INACT_XYZ_EN, axis_ctrl);
+ if (!en)
+ return false;
+ break;
+ case ADXL345_INACTIVITY_FF:
+ en = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Check if specific interrupt is enabled */
+ ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, &regval);
+ if (ret)
+ return ret;
+
+ int_en = adxl345_act_int_reg[type] & regval;
+ if (!int_en)
+ return false;
+
+ /* Check if configured coupling matches provided type */
+ return adxl345_is_act_inact_ac(st, type);
+}
+
+static int adxl345_set_act_inact_linkbit(struct adxl345_state *st,
+ enum adxl345_activity_type type,
+ bool en)
+{
+ int act_ac_en, inact_ac_en;
+ int act_en, inact_en;
+
+ act_en = adxl345_is_act_inact_en(st, ADXL345_ACTIVITY);
+ if (act_en < 0)
+ return act_en;
+
+ act_ac_en = adxl345_is_act_inact_en(st, ADXL345_ACTIVITY_AC);
+ if (act_ac_en < 0)
+ return act_ac_en;
+
+ if (type == ADXL345_INACTIVITY_FF) {
+ inact_en = false;
+ } else {
+ inact_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY);
+ if (inact_en < 0)
+ return inact_en;
+
+ inact_ac_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY_AC);
+ if (inact_ac_en < 0)
+ return inact_ac_en;
+
+ inact_en = inact_en || inact_ac_en;
+ }
+
+ act_en = act_en || act_ac_en;
+
+ return regmap_assign_bits(st->regmap, ADXL345_REG_POWER_CTL,
+ ADXL345_POWER_CTL_INACT_MSK,
+ en && act_en && inact_en);
+}
+
+static int adxl345_set_act_inact_en(struct adxl345_state *st,
+ enum adxl345_activity_type type,
+ bool cmd_en)
+{
+ unsigned int axis_ctrl;
+ unsigned int threshold;
+ unsigned int period;
+ int ret;
+
+ if (cmd_en) {
+ /* When turning on, check if threshold is valid */
+ if (type == ADXL345_ACTIVITY || type == ADXL345_ACTIVITY_AC) {
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[type],
+ &threshold);
+ if (ret)
+ return ret;
+ } else {
+ threshold = st->inact_threshold;
+ }
+
+ if (!threshold) /* Just ignore the command if threshold is 0 */
+ return 0;
+
+ /* When turning on inactivity, check if inact time is valid */
+ if (type == ADXL345_INACTIVITY || type == ADXL345_INACTIVITY_AC) {
+ ret = regmap_read(st->regmap,
+ ADXL345_REG_TIME_INACT,
+ &period);
+ if (ret)
+ return ret;
+
+ if (!period)
+ return 0;
+ }
+ } else {
+ /*
+ * When turning off an activity, ensure that the correct
+ * coupling event is specified. This step helps prevent misuse -
+ * for example, if an AC-coupled activity is active and the
+ * current call attempts to turn off a DC-coupled activity, this
+ * inconsistency should be detected here.
+ */
+ if (adxl345_is_act_inact_ac(st, type) <= 0)
+ return 0;
+ }
+
+ /* Start modifying configuration registers */
+ ret = adxl345_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ /* Enable axis according to the command */
+ switch (type) {
+ case ADXL345_ACTIVITY:
+ case ADXL345_ACTIVITY_AC:
+ axis_ctrl = ADXL345_ACT_XYZ_EN;
+ break;
+ case ADXL345_INACTIVITY:
+ case ADXL345_INACTIVITY_AC:
+ axis_ctrl = ADXL345_INACT_XYZ_EN;
+ break;
+ case ADXL345_INACTIVITY_FF:
+ axis_ctrl = ADXL345_ACT_INACT_NO_AXIS_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = regmap_assign_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL,
+ axis_ctrl, cmd_en);
+ if (ret)
+ return ret;
+
+ /* Update AC/DC-coupling according to the command */
+ ret = adxl345_set_act_inact_ac(st, type, cmd_en);
+ if (ret)
+ return ret;
+
+ /* Enable the interrupt line, according to the command */
+ ret = regmap_assign_bits(st->regmap, ADXL345_REG_INT_ENABLE,
+ adxl345_act_int_reg[type], cmd_en);
+ if (ret)
+ return ret;
+
+ /* Set link-bit and auto-sleep only when ACT and INACT are enabled */
+ ret = adxl345_set_act_inact_linkbit(st, type, cmd_en);
+ if (ret)
+ return ret;
+
+ return adxl345_set_measure_en(st, true);
+}
+
/* tap */
static int _adxl345_set_tap_int(struct adxl345_state *st,
@@ -368,9 +864,8 @@ static int adxl345_set_doubletap_en(struct adxl345_state *st, bool en)
* Generally suppress detection of spikes during the latency period as
* double taps here, this is fully optional for double tap detection
*/
- ret = regmap_update_bits(st->regmap, ADXL345_REG_TAP_AXIS,
- ADXL345_REG_TAP_SUPPRESS_MSK,
- en ? ADXL345_REG_TAP_SUPPRESS : 0x00);
+ ret = regmap_assign_bits(st->regmap, ADXL345_REG_TAP_AXIS,
+ ADXL345_REG_TAP_SUPPRESS, en);
if (ret)
return ret;
@@ -466,9 +961,16 @@ static int adxl345_find_odr(struct adxl345_state *st, int val,
static int adxl345_set_odr(struct adxl345_state *st, enum adxl345_odr odr)
{
- return regmap_update_bits(st->regmap, ADXL345_REG_BW_RATE,
+ int ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL345_REG_BW_RATE,
ADXL345_BW_RATE_MSK,
FIELD_PREP(ADXL345_BW_RATE_MSK, odr));
+ if (ret)
+ return ret;
+
+ /* update inactivity time by ODR */
+ return adxl345_set_inact_time(st, 0, 0);
}
static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
@@ -489,9 +991,43 @@ static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range)
{
- return regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
+ unsigned int act_threshold, inact_threshold;
+ unsigned int range_old;
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_DATA_FORMAT, &regval);
+ if (ret)
+ return ret;
+ range_old = FIELD_GET(ADXL345_DATA_FORMAT_RANGE, regval);
+
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_ACTIVITY],
+ &act_threshold);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
ADXL345_DATA_FORMAT_RANGE,
FIELD_PREP(ADXL345_DATA_FORMAT_RANGE, range));
+ if (ret)
+ return ret;
+
+ act_threshold = act_threshold * adxl345_range_factor_tbl[range_old]
+ / adxl345_range_factor_tbl[range];
+ act_threshold = min(U8_MAX, max(1, act_threshold));
+
+ inact_threshold = st->inact_threshold;
+ inact_threshold = inact_threshold * adxl345_range_factor_tbl[range_old]
+ / adxl345_range_factor_tbl[range];
+ inact_threshold = min(U8_MAX, max(1, inact_threshold));
+
+ ret = regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_ACTIVITY],
+ act_threshold);
+ if (ret)
+ return ret;
+
+ return adxl345_set_inact_threshold(st, inact_threshold);
}
static int adxl345_read_avail(struct iio_dev *indio_dev,
@@ -624,6 +1160,37 @@ static int adxl345_write_raw(struct iio_dev *indio_dev,
return adxl345_set_measure_en(st, true);
}
+static int adxl345_read_mag_config(struct adxl345_state *st,
+ enum iio_event_direction dir,
+ enum adxl345_activity_type type_act,
+ enum adxl345_activity_type type_inact)
+{
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return !!adxl345_is_act_inact_en(st, type_act);
+ case IIO_EV_DIR_FALLING:
+ return !!adxl345_is_act_inact_en(st, type_inact);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl345_write_mag_config(struct adxl345_state *st,
+ enum iio_event_direction dir,
+ enum adxl345_activity_type type_act,
+ enum adxl345_activity_type type_inact,
+ bool state)
+{
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl345_set_act_inact_en(st, type_act, state);
+ case IIO_EV_DIR_FALLING:
+ return adxl345_set_act_inact_en(st, type_inact, state);
+ default:
+ return -EINVAL;
+ }
+}
+
static int adxl345_read_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
@@ -634,6 +1201,14 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev,
int ret;
switch (type) {
+ case IIO_EV_TYPE_MAG:
+ return adxl345_read_mag_config(st, dir,
+ ADXL345_ACTIVITY,
+ ADXL345_INACTIVITY);
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ return adxl345_read_mag_config(st, dir,
+ ADXL345_ACTIVITY_AC,
+ ADXL345_INACTIVITY_AC);
case IIO_EV_TYPE_GESTURE:
switch (dir) {
case IIO_EV_DIR_SINGLETAP:
@@ -665,6 +1240,16 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
struct adxl345_state *st = iio_priv(indio_dev);
switch (type) {
+ case IIO_EV_TYPE_MAG:
+ return adxl345_write_mag_config(st, dir,
+ ADXL345_ACTIVITY,
+ ADXL345_INACTIVITY,
+ state);
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ return adxl345_write_mag_config(st, dir,
+ ADXL345_ACTIVITY_AC,
+ ADXL345_INACTIVITY_AC,
+ state);
case IIO_EV_TYPE_GESTURE:
switch (dir) {
case IIO_EV_DIR_SINGLETAP:
@@ -679,6 +1264,72 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev,
}
}
+static int adxl345_read_mag_value(struct adxl345_state *st,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ enum adxl345_activity_type type_act,
+ enum adxl345_activity_type type_inact,
+ int *val, int *val2)
+{
+ unsigned int threshold;
+ int ret;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[type_act],
+ &threshold);
+ if (ret)
+ return ret;
+ *val = 62500 * threshold;
+ *val2 = MICRO;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_DIR_FALLING:
+ *val = 62500 * st->inact_threshold;
+ *val2 = MICRO;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ *val = st->inact_time_ms;
+ *val2 = MILLI;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl345_write_mag_value(struct adxl345_state *st,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ enum adxl345_activity_type type_act,
+ enum adxl345_activity_type type_inact,
+ int val, int val2)
+{
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ /* Scaling factor 62.5mg/LSB, i.e. ~16g corresponds to 0xff */
+ val = DIV_ROUND_CLOSEST(val * MICRO + val2, 62500);
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return regmap_write(st->regmap,
+ adxl345_act_thresh_reg[type_act],
+ val);
+ case IIO_EV_DIR_FALLING:
+ return adxl345_set_inact_threshold(st, val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ return adxl345_set_inact_time(st, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
static int adxl345_read_event_value(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
@@ -691,6 +1342,16 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev,
int ret;
switch (type) {
+ case IIO_EV_TYPE_MAG:
+ return adxl345_read_mag_value(st, dir, info,
+ ADXL345_ACTIVITY,
+ ADXL345_INACTIVITY,
+ val, val2);
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ return adxl345_read_mag_value(st, dir, info,
+ ADXL345_ACTIVITY_AC,
+ ADXL345_INACTIVITY_AC,
+ val, val2);
case IIO_EV_TYPE_GESTURE:
switch (info) {
case IIO_EV_INFO_VALUE:
@@ -741,6 +1402,22 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev,
return ret;
switch (type) {
+ case IIO_EV_TYPE_MAG:
+ ret = adxl345_write_mag_value(st, dir, info,
+ ADXL345_ACTIVITY,
+ ADXL345_INACTIVITY,
+ val, val2);
+ if (ret)
+ return ret;
+ break;
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ ret = adxl345_write_mag_value(st, dir, info,
+ ADXL345_ACTIVITY_AC,
+ ADXL345_INACTIVITY_AC,
+ val, val2);
+ if (ret)
+ return ret;
+ break;
case IIO_EV_TYPE_GESTURE:
switch (info) {
case IIO_EV_INFO_VALUE:
@@ -980,10 +1657,12 @@ static int adxl345_fifo_push(struct iio_dev *indio_dev,
}
static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
+ enum iio_modifier act_dir,
enum iio_modifier tap_dir)
{
s64 ts = iio_get_time_ns(indio_dev);
struct adxl345_state *st = iio_priv(indio_dev);
+ unsigned int regval;
int samples;
int ret = -ENOENT;
@@ -1007,6 +1686,68 @@ static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
return ret;
}
+ if (FIELD_GET(ADXL345_INT_ACTIVITY, int_stat)) {
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &regval);
+ if (ret)
+ return ret;
+
+ if (FIELD_GET(ADXL345_REG_ACT_ACDC, regval)) {
+ /* AC coupled */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, act_dir,
+ IIO_EV_TYPE_MAG_ADAPTIVE,
+ IIO_EV_DIR_RISING),
+ ts);
+
+ } else {
+ /* DC coupled, relying on THRESH */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, act_dir,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_RISING),
+ ts);
+ }
+ if (ret)
+ return ret;
+ }
+
+ if (FIELD_GET(ADXL345_INT_INACTIVITY, int_stat)) {
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &regval);
+ if (ret)
+ return ret;
+
+ if (FIELD_GET(ADXL345_REG_INACT_ACDC, regval)) {
+ /* AC coupled */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_AND_Y_AND_Z,
+ IIO_EV_TYPE_MAG_ADAPTIVE,
+ IIO_EV_DIR_FALLING),
+ ts);
+ } else {
+ /* DC coupled, relying on THRESH */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_AND_Y_AND_Z,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_FALLING),
+ ts);
+ }
+ if (ret)
+ return ret;
+ }
+
+ if (FIELD_GET(ADXL345_INT_FREE_FALL, int_stat)) {
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_AND_Y_AND_Z,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_FALLING),
+ ts);
+ if (ret)
+ return ret;
+ }
+
if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
samples = adxl345_get_samples(st);
if (samples < 0)
@@ -1034,6 +1775,7 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
struct adxl345_state *st = iio_priv(indio_dev);
unsigned int regval;
enum iio_modifier tap_dir = IIO_NO_MOD;
+ enum iio_modifier act_dir = IIO_NO_MOD;
u32 axis_ctrl;
int int_stat;
int ret;
@@ -1042,7 +1784,8 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
if (ret)
return IRQ_NONE;
- if (FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl)) {
+ if (FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl) ||
+ FIELD_GET(ADXL345_ACT_XYZ_EN, axis_ctrl)) {
ret = regmap_read(st->regmap, ADXL345_REG_ACT_TAP_STATUS, &regval);
if (ret)
return IRQ_NONE;
@@ -1053,12 +1796,19 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p)
tap_dir = IIO_MOD_Y;
else if (FIELD_GET(ADXL345_TAP_X_EN, regval))
tap_dir = IIO_MOD_X;
+
+ if (FIELD_GET(ADXL345_ACT_Z_EN, regval))
+ act_dir = IIO_MOD_Z;
+ else if (FIELD_GET(ADXL345_ACT_Y_EN, regval))
+ act_dir = IIO_MOD_Y;
+ else if (FIELD_GET(ADXL345_ACT_X_EN, regval))
+ act_dir = IIO_MOD_X;
}
if (regmap_read(st->regmap, ADXL345_REG_INT_SOURCE, &int_stat))
return IRQ_NONE;
- if (adxl345_push_event(indio_dev, int_stat, tap_dir))
+ if (adxl345_push_event(indio_dev, int_stat, act_dir, tap_dir))
goto err;
if (FIELD_GET(ADXL345_INT_OVERRUN, int_stat))
@@ -1226,6 +1976,24 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap,
if (ret)
return ret;
+ /*
+ * Initialized with sensible default values to streamline
+ * sensor operation. These defaults are partly derived from
+ * the previous input driver for the ADXL345 and partly
+ * based on the recommendations provided in the datasheet.
+ */
+ ret = regmap_write(st->regmap, ADXL345_REG_ACT_INACT_CTRL, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, ADXL345_REG_THRESH_ACT, 6);
+ if (ret)
+ return ret;
+
+ ret = adxl345_set_inact_threshold(st, 4);
+ if (ret)
+ return ret;
+
ret = regmap_write(st->regmap, ADXL345_REG_THRESH_TAP, tap_threshold);
if (ret)
return ret;
diff --git a/drivers/iio/accel/bma180.c b/drivers/iio/accel/bma180.c
index 4fccbcb76e04..8925f5279e62 100644
--- a/drivers/iio/accel/bma180.c
+++ b/drivers/iio/accel/bma180.c
@@ -139,11 +139,6 @@ struct bma180_data {
int scale;
int bw;
bool pmode;
- /* Ensure timestamp is naturally aligned */
- struct {
- s16 chan[4];
- aligned_s64 timestamp;
- } scan;
};
enum bma180_chan {
@@ -870,6 +865,10 @@ static irqreturn_t bma180_trigger_handler(int irq, void *p)
struct bma180_data *data = iio_priv(indio_dev);
s64 time_ns = iio_get_time_ns(indio_dev);
int bit, ret, i = 0;
+ struct {
+ s16 chan[4];
+ aligned_s64 timestamp;
+ } scan = { };
mutex_lock(&data->mutex);
@@ -879,12 +878,12 @@ static irqreturn_t bma180_trigger_handler(int irq, void *p)
mutex_unlock(&data->mutex);
goto err;
}
- data->scan.chan[i++] = ret;
+ scan.chan[i++] = ret;
}
mutex_unlock(&data->mutex);
- iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), time_ns);
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns);
err:
iio_trigger_notify_done(indio_dev->trig);
diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c
index 38f7498431ee..01592eebf05b 100644
--- a/drivers/iio/accel/bma220_spi.c
+++ b/drivers/iio/accel/bma220_spi.c
@@ -255,10 +255,8 @@ static int bma220_probe(struct spi_device *spi)
struct bma220_data *data;
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
- if (!indio_dev) {
- dev_err(&spi->dev, "iio allocation failed!\n");
+ if (!indio_dev)
return -ENOMEM;
- }
data = iio_priv(indio_dev);
data->spi_device = spi;
diff --git a/drivers/iio/accel/bmc150-accel-core.c b/drivers/iio/accel/bmc150-accel-core.c
index be5fbb0c5d29..3c5d1560b163 100644
--- a/drivers/iio/accel/bmc150-accel-core.c
+++ b/drivers/iio/accel/bmc150-accel-core.c
@@ -332,13 +332,10 @@ static int bmc150_accel_set_power_state(struct bmc150_accel_data *data, bool on)
struct device *dev = regmap_get_device(data->regmap);
int ret;
- if (on) {
+ if (on)
ret = pm_runtime_resume_and_get(dev);
- } else {
- pm_runtime_mark_last_busy(dev);
+ else
ret = pm_runtime_put_autosuspend(dev);
- }
-
if (ret < 0) {
dev_err(dev,
"Failed: %s for %d\n", __func__, on);
diff --git a/drivers/iio/accel/bmi088-accel-core.c b/drivers/iio/accel/bmi088-accel-core.c
index dea126f993c1..c7da90af0d2d 100644
--- a/drivers/iio/accel/bmi088-accel-core.c
+++ b/drivers/iio/accel/bmi088-accel-core.c
@@ -375,7 +375,6 @@ static int bmi088_accel_read_raw(struct iio_dev *indio_dev,
return -EINVAL;
out_read_raw_pm_put:
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
@@ -419,7 +418,6 @@ static int bmi088_accel_write_raw(struct iio_dev *indio_dev,
return ret;
ret = bmi088_accel_set_scale(data, val, val2);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
case IIO_CHAN_INFO_SAMP_FREQ:
@@ -428,7 +426,6 @@ static int bmi088_accel_write_raw(struct iio_dev *indio_dev,
return ret;
ret = bmi088_accel_set_sample_freq(data, val);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
default:
diff --git a/drivers/iio/accel/dmard06.c b/drivers/iio/accel/dmard06.c
index fb14894c66f9..33f225d73e7b 100644
--- a/drivers/iio/accel/dmard06.c
+++ b/drivers/iio/accel/dmard06.c
@@ -137,10 +137,8 @@ static int dmard06_probe(struct i2c_client *client)
}
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dmard06));
- if (!indio_dev) {
- dev_err(&client->dev, "Failed to allocate iio device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
dmard06 = iio_priv(indio_dev);
dmard06->client = client;
diff --git a/drivers/iio/accel/dmard09.c b/drivers/iio/accel/dmard09.c
index 4ec70ca6910d..d9290e3b9c46 100644
--- a/drivers/iio/accel/dmard09.c
+++ b/drivers/iio/accel/dmard09.c
@@ -95,10 +95,8 @@ static int dmard09_probe(struct i2c_client *client)
struct dmard09_data *data;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
- if (!indio_dev) {
- dev_err(&client->dev, "iio allocation failed\n");
+ if (!indio_dev)
return -ENOMEM;
- }
data = iio_priv(indio_dev);
data->client = client;
diff --git a/drivers/iio/accel/dmard10.c b/drivers/iio/accel/dmard10.c
index 71cd1928baa6..575e8510e1bd 100644
--- a/drivers/iio/accel/dmard10.c
+++ b/drivers/iio/accel/dmard10.c
@@ -191,10 +191,8 @@ static int dmard10_probe(struct i2c_client *client)
return (ret < 0) ? ret : -ENODEV;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
- if (!indio_dev) {
- dev_err(&client->dev, "iio allocation failed!\n");
+ if (!indio_dev)
return -ENOMEM;
- }
data = iio_priv(indio_dev);
data->client = client;
diff --git a/drivers/iio/accel/fxls8962af-core.c b/drivers/iio/accel/fxls8962af-core.c
index b10a30960e1e..8763e91c63d2 100644
--- a/drivers/iio/accel/fxls8962af-core.c
+++ b/drivers/iio/accel/fxls8962af-core.c
@@ -222,7 +222,6 @@ static int fxls8962af_power_off(struct fxls8962af_data *data)
struct device *dev = regmap_get_device(data->regmap);
int ret;
- pm_runtime_mark_last_busy(dev);
ret = pm_runtime_put_autosuspend(dev);
if (ret)
dev_err(dev, "failed to power off\n");
diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c
index 6aefe8221296..2823ddde4bf2 100644
--- a/drivers/iio/accel/kxcjk-1013.c
+++ b/drivers/iio/accel/kxcjk-1013.c
@@ -636,10 +636,8 @@ static int kxcjk1013_set_power_state(struct kxcjk1013_data *data, bool on)
if (on)
ret = pm_runtime_resume_and_get(&data->client->dev);
- else {
- pm_runtime_mark_last_busy(&data->client->dev);
+ else
ret = pm_runtime_put_autosuspend(&data->client->dev);
- }
if (ret < 0) {
dev_err(&data->client->dev,
"Failed: %s for %d\n", __func__, on);
diff --git a/drivers/iio/accel/kxsd9.c b/drivers/iio/accel/kxsd9.c
index cfc31265cdd0..4717d80fc24a 100644
--- a/drivers/iio/accel/kxsd9.c
+++ b/drivers/iio/accel/kxsd9.c
@@ -151,7 +151,6 @@ static int kxsd9_write_raw(struct iio_dev *indio_dev,
ret = kxsd9_write_scale(indio_dev, val2);
}
- pm_runtime_mark_last_busy(st->dev);
pm_runtime_put_autosuspend(st->dev);
return ret;
@@ -199,7 +198,6 @@ static int kxsd9_read_raw(struct iio_dev *indio_dev,
}
error_ret:
- pm_runtime_mark_last_busy(st->dev);
pm_runtime_put_autosuspend(st->dev);
return ret;
@@ -250,7 +248,6 @@ static int kxsd9_buffer_postdisable(struct iio_dev *indio_dev)
{
struct kxsd9_state *st = iio_priv(indio_dev);
- pm_runtime_mark_last_busy(st->dev);
pm_runtime_put_autosuspend(st->dev);
return 0;
diff --git a/drivers/iio/accel/mc3230.c b/drivers/iio/accel/mc3230.c
index e2853090fa6e..3e494f9ddc56 100644
--- a/drivers/iio/accel/mc3230.c
+++ b/drivers/iio/accel/mc3230.c
@@ -169,10 +169,8 @@ static int mc3230_probe(struct i2c_client *client)
}
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
- if (!indio_dev) {
- dev_err(&client->dev, "iio allocation failed!\n");
+ if (!indio_dev)
return -ENOMEM;
- }
data = iio_priv(indio_dev);
data->chip_info = chip_info;
diff --git a/drivers/iio/accel/mma7660.c b/drivers/iio/accel/mma7660.c
index d0a16f227903..be3213600cf4 100644
--- a/drivers/iio/accel/mma7660.c
+++ b/drivers/iio/accel/mma7660.c
@@ -192,10 +192,8 @@ static int mma7660_probe(struct i2c_client *client)
struct mma7660_data *data;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
- if (!indio_dev) {
- dev_err(&client->dev, "iio allocation failed!\n");
+ if (!indio_dev)
return -ENOMEM;
- }
data = iio_priv(indio_dev);
data->client = client;
diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c
index aba444a980d9..15172ba2972c 100644
--- a/drivers/iio/accel/mma8452.c
+++ b/drivers/iio/accel/mma8452.c
@@ -224,13 +224,10 @@ static int mma8452_set_runtime_pm_state(struct i2c_client *client, bool on)
#ifdef CONFIG_PM
int ret;
- if (on) {
+ if (on)
ret = pm_runtime_resume_and_get(&client->dev);
- } else {
- pm_runtime_mark_last_busy(&client->dev);
+ else
ret = pm_runtime_put_autosuspend(&client->dev);
- }
-
if (ret < 0) {
dev_err(&client->dev,
"failed to change power state to %d\n", on);
diff --git a/drivers/iio/accel/mma9551_core.c b/drivers/iio/accel/mma9551_core.c
index 3e7d9b79ed0e..2ccb1fb19b96 100644
--- a/drivers/iio/accel/mma9551_core.c
+++ b/drivers/iio/accel/mma9551_core.c
@@ -671,11 +671,8 @@ int mma9551_set_power_state(struct i2c_client *client, bool on)
if (on)
ret = pm_runtime_resume_and_get(&client->dev);
- else {
- pm_runtime_mark_last_busy(&client->dev);
+ else
ret = pm_runtime_put_autosuspend(&client->dev);
- }
-
if (ret < 0) {
dev_err(&client->dev,
"failed to change power state to %d\n", on);
diff --git a/drivers/iio/accel/msa311.c b/drivers/iio/accel/msa311.c
index 3e10225410e8..5eace0de3750 100644
--- a/drivers/iio/accel/msa311.c
+++ b/drivers/iio/accel/msa311.c
@@ -607,7 +607,6 @@ static int msa311_read_raw_data(struct iio_dev *indio_dev,
err = msa311_get_axis(msa311, chan, &axis);
mutex_unlock(&msa311->lock);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
iio_device_release_direct(indio_dev);
@@ -741,7 +740,6 @@ static int msa311_write_scale(struct iio_dev *indio_dev, int val, int val2)
break;
}
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
if (err)
@@ -781,7 +779,6 @@ static int msa311_write_samp_freq(struct iio_dev *indio_dev, int val, int val2)
break;
}
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
iio_device_release_direct(indio_dev);
@@ -832,7 +829,6 @@ static int msa311_debugfs_reg_access(struct iio_dev *indio_dev,
mutex_unlock(&msa311->lock);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
if (err)
@@ -855,7 +851,6 @@ static int msa311_buffer_postdisable(struct iio_dev *indio_dev)
struct msa311_priv *msa311 = iio_priv(indio_dev);
struct device *dev = msa311->dev;
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return 0;
@@ -990,7 +985,7 @@ static int msa311_check_partid(struct msa311_priv *msa311)
msa311->chip_name = devm_kasprintf(dev, GFP_KERNEL,
"msa311-%02x", partid);
if (!msa311->chip_name)
- return dev_err_probe(dev, -ENOMEM, "can't alloc chip name\n");
+ return -ENOMEM;
return 0;
}
@@ -1069,8 +1064,7 @@ static int msa311_setup_interrupts(struct msa311_priv *msa311)
trig = devm_iio_trigger_alloc(dev, "%s-new-data", msa311->chip_name);
if (!trig)
- return dev_err_probe(dev, -ENOMEM,
- "can't allocate newdata trigger\n");
+ return -ENOMEM;
msa311->new_data_trig = trig;
msa311->new_data_trig->ops = &msa311_new_data_trig_ops;
@@ -1153,8 +1147,7 @@ static int msa311_probe(struct i2c_client *i2c)
indio_dev = devm_iio_device_alloc(dev, sizeof(*msa311));
if (!indio_dev)
- return dev_err_probe(dev, -ENOMEM,
- "IIO device allocation failed\n");
+ return -ENOMEM;
msa311 = iio_priv(indio_dev);
msa311->dev = dev;
@@ -1195,7 +1188,7 @@ static int msa311_probe(struct i2c_client *i2c)
*/
err = devm_add_action_or_reset(dev, msa311_powerdown, msa311);
if (err)
- return dev_err_probe(dev, err, "can't add powerdown action\n");
+ return err;
err = pm_runtime_set_active(dev);
if (err)
@@ -1231,7 +1224,6 @@ static int msa311_probe(struct i2c_client *i2c)
if (err)
return err;
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
err = devm_iio_device_register(dev, indio_dev);
diff --git a/drivers/iio/accel/stk8312.c b/drivers/iio/accel/stk8312.c
index 89569ce221d7..f31c6ab3392d 100644
--- a/drivers/iio/accel/stk8312.c
+++ b/drivers/iio/accel/stk8312.c
@@ -504,10 +504,8 @@ static int stk8312_probe(struct i2c_client *client)
struct stk8312_data *data;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
- if (!indio_dev) {
- dev_err(&client->dev, "iio allocation failed!\n");
+ if (!indio_dev)
return -ENOMEM;
- }
data = iio_priv(indio_dev);
data->client = client;
diff --git a/drivers/iio/accel/stk8ba50.c b/drivers/iio/accel/stk8ba50.c
index c1d7e7dcb09b..384f1fbcbcb3 100644
--- a/drivers/iio/accel/stk8ba50.c
+++ b/drivers/iio/accel/stk8ba50.c
@@ -385,10 +385,8 @@ static int stk8ba50_probe(struct i2c_client *client)
struct stk8ba50_data *data;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
- if (!indio_dev) {
- dev_err(&client->dev, "iio allocation failed!\n");
+ if (!indio_dev)
return -ENOMEM;
- }
data = iio_priv(indio_dev);
data->client = client;
diff --git a/drivers/iio/adc/88pm886-gpadc.c b/drivers/iio/adc/88pm886-gpadc.c
new file mode 100644
index 000000000000..cffe35136685
--- /dev/null
+++ b/drivers/iio/adc/88pm886-gpadc.c
@@ -0,0 +1,393 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2025, Duje Mihanović <duje@dujemihanovic.xyz>
+ */
+
+#include <linux/bits.h>
+#include <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/math.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <asm/byteorder.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/types.h>
+
+#include <linux/mfd/88pm886.h>
+
+struct pm886_gpadc {
+ struct regmap *map;
+};
+
+enum pm886_gpadc_channel {
+ VSC_CHAN,
+ VCHG_PWR_CHAN,
+ VCF_OUT_CHAN,
+ VBAT_CHAN,
+ VBAT_SLP_CHAN,
+ VBUS_CHAN,
+
+ GPADC0_CHAN,
+ GPADC1_CHAN,
+ GPADC2_CHAN,
+ GPADC3_CHAN,
+
+ GND_DET1_CHAN,
+ GND_DET2_CHAN,
+ MIC_DET_CHAN,
+
+ TINT_CHAN,
+};
+
+static const int pm886_gpadc_regs[] = {
+ [VSC_CHAN] = PM886_REG_GPADC_VSC,
+ [VCHG_PWR_CHAN] = PM886_REG_GPADC_VCHG_PWR,
+ [VCF_OUT_CHAN] = PM886_REG_GPADC_VCF_OUT,
+ [VBAT_CHAN] = PM886_REG_GPADC_VBAT,
+ [VBAT_SLP_CHAN] = PM886_REG_GPADC_VBAT_SLP,
+ [VBUS_CHAN] = PM886_REG_GPADC_VBUS,
+
+ [GPADC0_CHAN] = PM886_REG_GPADC_GPADC0,
+ [GPADC1_CHAN] = PM886_REG_GPADC_GPADC1,
+ [GPADC2_CHAN] = PM886_REG_GPADC_GPADC2,
+ [GPADC3_CHAN] = PM886_REG_GPADC_GPADC3,
+
+ [GND_DET1_CHAN] = PM886_REG_GPADC_GND_DET1,
+ [GND_DET2_CHAN] = PM886_REG_GPADC_GND_DET2,
+ [MIC_DET_CHAN] = PM886_REG_GPADC_MIC_DET,
+
+ [TINT_CHAN] = PM886_REG_GPADC_TINT,
+};
+
+#define ADC_CHANNEL_VOLTAGE(index, lsb, name) \
+{ \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .channel = index, \
+ .address = lsb, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .datasheet_name = name, \
+}
+
+#define ADC_CHANNEL_RESISTANCE(index, lsb, name) \
+{ \
+ .type = IIO_RESISTANCE, \
+ .indexed = 1, \
+ .channel = index, \
+ .address = lsb, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
+ .datasheet_name = name, \
+}
+
+#define ADC_CHANNEL_TEMPERATURE(index, lsb, name) \
+{ \
+ .type = IIO_TEMP, \
+ .indexed = 1, \
+ .channel = index, \
+ .address = lsb, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OFFSET), \
+ .datasheet_name = name, \
+}
+
+static const struct iio_chan_spec pm886_gpadc_channels[] = {
+ ADC_CHANNEL_VOLTAGE(VSC_CHAN, 1367, "vsc"),
+ ADC_CHANNEL_VOLTAGE(VCHG_PWR_CHAN, 1709, "vchg_pwr"),
+ ADC_CHANNEL_VOLTAGE(VCF_OUT_CHAN, 1367, "vcf_out"),
+ ADC_CHANNEL_VOLTAGE(VBAT_CHAN, 1367, "vbat"),
+ ADC_CHANNEL_VOLTAGE(VBAT_SLP_CHAN, 1367, "vbat_slp"),
+ ADC_CHANNEL_VOLTAGE(VBUS_CHAN, 1709, "vbus"),
+
+ ADC_CHANNEL_RESISTANCE(GPADC0_CHAN, 342, "gpadc0"),
+ ADC_CHANNEL_RESISTANCE(GPADC1_CHAN, 342, "gpadc1"),
+ ADC_CHANNEL_RESISTANCE(GPADC2_CHAN, 342, "gpadc2"),
+ ADC_CHANNEL_RESISTANCE(GPADC3_CHAN, 342, "gpadc3"),
+
+ ADC_CHANNEL_VOLTAGE(GND_DET1_CHAN, 342, "gnddet1"),
+ ADC_CHANNEL_VOLTAGE(GND_DET2_CHAN, 342, "gnddet2"),
+ ADC_CHANNEL_VOLTAGE(MIC_DET_CHAN, 1367, "mic_det"),
+
+ ADC_CHANNEL_TEMPERATURE(TINT_CHAN, 104, "tint"),
+};
+
+static const struct regmap_config pm886_gpadc_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = PM886_GPADC_MAX_REGISTER,
+};
+
+static int gpadc_get_raw(struct iio_dev *iio, enum pm886_gpadc_channel chan)
+{
+ struct pm886_gpadc *gpadc = iio_priv(iio);
+ __be16 buf;
+ int ret;
+
+ ret = regmap_bulk_read(gpadc->map, pm886_gpadc_regs[chan], &buf, sizeof(buf));
+ if (ret)
+ return ret;
+
+ return be16_to_cpu(buf) >> 4;
+}
+
+static int
+gpadc_set_bias(struct pm886_gpadc *gpadc, enum pm886_gpadc_channel chan, bool on)
+{
+ unsigned int gpadc_num = chan - GPADC0_CHAN;
+ unsigned int bits = BIT(gpadc_num + 4) | BIT(gpadc_num);
+
+ return regmap_assign_bits(gpadc->map, PM886_REG_GPADC_CONFIG(0x14), bits, on);
+}
+
+static int
+gpadc_find_bias_current(struct iio_dev *iio, struct iio_chan_spec const *chan,
+ unsigned int *raw_uV, unsigned int *raw_uA)
+{
+ struct pm886_gpadc *gpadc = iio_priv(iio);
+ unsigned int gpadc_num = chan->channel - GPADC0_CHAN;
+ unsigned int reg = PM886_REG_GPADC_CONFIG(0xb + gpadc_num);
+ unsigned long lsb = chan->address;
+ int ret;
+
+ for (unsigned int i = 0; i < PM886_GPADC_BIAS_LEVELS; i++) {
+ ret = regmap_update_bits(gpadc->map, reg, GENMASK(3, 0), i);
+ if (ret)
+ return ret;
+
+ /* Wait for the new bias level to apply. */
+ fsleep(5 * USEC_PER_MSEC);
+
+ *raw_uA = PM886_GPADC_INDEX_TO_BIAS_uA(i);
+ *raw_uV = gpadc_get_raw(iio, chan->channel) * lsb;
+
+ /*
+ * Vendor kernel errors out above 1.25 V, but testing shows
+ * that the resistance of the battery detection channel (GPADC2
+ * on coreprimevelte) reaches about 1.4 MΩ when the battery is
+ * removed, which can't be measured with such a low upper
+ * limit. Therefore, to be able to detect the battery without
+ * ugly externs as used in the vendor fuel gauge driver,
+ * increase this limit a bit.
+ */
+ if (WARN_ON(*raw_uV > 1500 * (MICRO / MILLI)))
+ return -EIO;
+
+ /*
+ * Vendor kernel errors out under 300 mV, but for the same
+ * reason as above (except the channel hovers around 3.5 kΩ
+ * with battery present) reduce this limit.
+ */
+ if (*raw_uV < 200 * (MICRO / MILLI)) {
+ dev_dbg(&iio->dev, "bad bias for chan %d: %d uA @ %d uV\n",
+ chan->channel, *raw_uA, *raw_uV);
+ continue;
+ }
+
+ dev_dbg(&iio->dev, "good bias for chan %d: %d uA @ %d uV\n",
+ chan->channel, *raw_uA, *raw_uV);
+ return 0;
+ }
+
+ dev_err(&iio->dev, "failed to find good bias for chan %d\n", chan->channel);
+ return -EINVAL;
+}
+
+static int
+gpadc_get_resistance_ohm(struct iio_dev *iio, struct iio_chan_spec const *chan)
+{
+ struct pm886_gpadc *gpadc = iio_priv(iio);
+ unsigned int raw_uV, raw_uA;
+ int ret;
+
+ ret = gpadc_set_bias(gpadc, chan->channel, true);
+ if (ret)
+ goto out;
+
+ ret = gpadc_find_bias_current(iio, chan, &raw_uV, &raw_uA);
+ if (ret)
+ goto out;
+
+ ret = DIV_ROUND_CLOSEST(raw_uV, raw_uA);
+out:
+ gpadc_set_bias(gpadc, chan->channel, false);
+ return ret;
+}
+
+static int
+__pm886_gpadc_read_raw(struct iio_dev *iio, struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ unsigned long lsb = chan->address;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ *val = gpadc_get_raw(iio, chan->channel);
+ if (*val < 0)
+ return *val;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = lsb;
+
+ if (chan->type == IIO_VOLTAGE) {
+ *val2 = MILLI;
+ return IIO_VAL_FRACTIONAL;
+ } else {
+ return IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ /* Raw value is 104 millikelvin/LSB, convert it to 104 millicelsius/LSB */
+ *val = ABSOLUTE_ZERO_MILLICELSIUS;
+ *val2 = lsb;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_CHAN_INFO_PROCESSED:
+ *val = gpadc_get_resistance_ohm(iio, chan);
+ if (*val < 0)
+ return *val;
+
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int pm886_gpadc_read_raw(struct iio_dev *iio, struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct device *dev = iio->dev.parent;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = __pm886_gpadc_read_raw(iio, chan, val, val2, mask);
+
+ pm_runtime_put_autosuspend(dev);
+ return ret;
+}
+
+static int pm886_gpadc_hw_enable(struct regmap *map)
+{
+ const u8 config[] = {
+ PM886_GPADC_CONFIG1_EN_ALL,
+ PM886_GPADC_CONFIG2_EN_ALL,
+ PM886_GPADC_GND_DET2_EN,
+ };
+ int ret;
+
+ /* Enable the ADC block. */
+ ret = regmap_set_bits(map, PM886_REG_GPADC_CONFIG(0x6), BIT(0));
+ if (ret)
+ return ret;
+
+ /* Enable all channels. */
+ return regmap_bulk_write(map, PM886_REG_GPADC_CONFIG(0x1), config, ARRAY_SIZE(config));
+}
+
+static int pm886_gpadc_hw_disable(struct regmap *map)
+{
+ return regmap_clear_bits(map, PM886_REG_GPADC_CONFIG(0x6), BIT(0));
+}
+
+static const struct iio_info pm886_gpadc_iio_info = {
+ .read_raw = pm886_gpadc_read_raw,
+};
+
+static int pm886_gpadc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pm886_chip *chip = dev_get_drvdata(dev->parent);
+ struct i2c_client *client = chip->client;
+ struct pm886_gpadc *gpadc;
+ struct i2c_client *page;
+ struct iio_dev *iio;
+ int ret;
+
+ iio = devm_iio_device_alloc(dev, sizeof(*gpadc));
+ if (!iio)
+ return -ENOMEM;
+
+ gpadc = iio_priv(iio);
+ dev_set_drvdata(dev, iio);
+
+ page = devm_i2c_new_dummy_device(dev, client->adapter,
+ client->addr + PM886_PAGE_OFFSET_GPADC);
+ if (IS_ERR(page))
+ return dev_err_probe(dev, PTR_ERR(page), "Failed to initialize GPADC page\n");
+
+ gpadc->map = devm_regmap_init_i2c(page, &pm886_gpadc_regmap_config);
+ if (IS_ERR(gpadc->map))
+ return dev_err_probe(dev, PTR_ERR(gpadc->map),
+ "Failed to initialize GPADC regmap\n");
+
+ iio->name = "88pm886-gpadc";
+ iio->modes = INDIO_DIRECT_MODE;
+ iio->info = &pm886_gpadc_iio_info;
+ iio->channels = pm886_gpadc_channels;
+ iio->num_channels = ARRAY_SIZE(pm886_gpadc_channels);
+ device_set_node(&iio->dev, dev_fwnode(dev->parent));
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable runtime PM\n");
+
+ pm_runtime_set_autosuspend_delay(dev, 50);
+ pm_runtime_use_autosuspend(dev);
+ ret = devm_iio_device_register(dev, iio);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register ADC\n");
+
+ return 0;
+}
+
+static int pm886_gpadc_runtime_resume(struct device *dev)
+{
+ struct iio_dev *iio = dev_get_drvdata(dev);
+ struct pm886_gpadc *gpadc = iio_priv(iio);
+
+ return pm886_gpadc_hw_enable(gpadc->map);
+}
+
+static int pm886_gpadc_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *iio = dev_get_drvdata(dev);
+ struct pm886_gpadc *gpadc = iio_priv(iio);
+
+ return pm886_gpadc_hw_disable(gpadc->map);
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(pm886_gpadc_pm_ops,
+ pm886_gpadc_runtime_suspend,
+ pm886_gpadc_runtime_resume, NULL);
+
+static const struct platform_device_id pm886_gpadc_id[] = {
+ { "88pm886-gpadc" },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, pm886_gpadc_id);
+
+static struct platform_driver pm886_gpadc_driver = {
+ .driver = {
+ .name = "88pm886-gpadc",
+ .pm = pm_ptr(&pm886_gpadc_pm_ops),
+ },
+ .probe = pm886_gpadc_probe,
+ .id_table = pm886_gpadc_id,
+};
+module_platform_driver(pm886_gpadc_driver);
+
+MODULE_AUTHOR("Duje Mihanović <duje@dujemihanovic.xyz>");
+MODULE_DESCRIPTION("Marvell 88PM886 GPADC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 24f2572c487e..58a14e6833f6 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -9,6 +9,19 @@ menu "Analog to digital converters"
config IIO_ADC_HELPER
tristate
+config 88PM886_GPADC
+ tristate "Marvell 88PM886 GPADC driver"
+ depends on MFD_88PM886_PMIC
+ default MFD_88PM886_PMIC
+ help
+ Say Y here to enable support for the GPADC (General Purpose ADC)
+ found on the Marvell 88PM886 PMIC. The GPADC measures various
+ internal voltages and temperatures, including (but not limited to)
+ system, battery and USB Vbus.
+
+ To compile this driver as a module, choose M here: the module will be
+ called 88pm886-gpadc.
+
config AB8500_GPADC
bool "ST-Ericsson AB8500 GPADC driver"
depends on AB8500_CORE && REGULATOR_AB8500
@@ -389,6 +402,7 @@ config AD7779
depends on SPI
select CRC8
select IIO_BUFFER
+ select IIO_BACKEND
help
Say yes here to build support for Analog Devices AD777X family
(AD7770, AD7771, AD7779) analog to digital converter (ADC).
@@ -507,6 +521,25 @@ config AD9467
To compile this driver as a module, choose M here: the module will be
called ad9467.
+config ADE9000
+ tristate "Analog Devices ADE9000 Multiphase Energy, and Power Quality Monitoring IC Driver"
+ depends on SPI
+ select REGMAP_SPI
+ select IIO_BUFFER
+ select IIO_KFIFO_BUF
+ help
+ Say yes here to build support for the Analog Devices ADE9000,
+ a highly accurate, multiphase energy and power quality monitoring
+ integrated circuit.
+
+ The device features high-precision analog-to-digital converters
+ and digital signal processing to compute RMS values, power factor,
+ frequency, and harmonic analysis. It supports SPI communication
+ and provides buffered data output through the IIO framework.
+
+ To compile this driver as a module, choose M here: the module will
+ be called ade9000.
+
config ADI_AXI_ADC
tristate "Analog Devices Generic AXI ADC IP core driver"
depends on MICROBLAZE || NIOS2 || ARCH_ZYNQ || ARCH_ZYNQMP || ARCH_INTEL_SOCFPGA || COMPILE_TEST
@@ -766,6 +799,17 @@ config INGENIC_ADC
This driver can also be built as a module. If so, the module will be
called ingenic_adc.
+config INTEL_DC_TI_ADC
+ tristate "Intel Bay Trail / Cherry Trail Dollar Cove TI ADC driver"
+ depends on INTEL_SOC_PMIC_CHTDC_TI
+ help
+ Say yes here to have support for the Dollar Cove TI PMIC ADC device.
+ Depending on platform configuration, this general purpose ADC can be
+ used for sensors such as battery voltage and thermal resistors.
+
+ To compile this driver as a module, choose M here: the module will be
+ called intel_dc_ti_adc.
+
config INTEL_MRFLD_ADC
tristate "Intel Merrifield Basin Cove ADC driver"
depends on INTEL_SOC_PMIC_MRFLD
@@ -1298,6 +1342,16 @@ config RN5T618_ADC
This driver can also be built as a module. If so, the module
will be called rn5t618-adc.
+config ROHM_BD79112
+ tristate "Rohm BD79112 ADC driver"
+ depends on SPI && GPIOLIB
+ select REGMAP_SPI
+ select IIO_ADC_HELPER
+ help
+ Say yes here to build support for the ROHM BD79112 ADC. The
+ ROHM BD79112 is a 12-bit, 32-channel, SAR ADC. Analog inputs
+ can also be used for GPIO.
+
config ROHM_BD79124
tristate "Rohm BD79124 ADC driver"
depends on I2C && GPIOLIB
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 1c6ca5fd4b6d..d008f78dc010 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -6,6 +6,7 @@
obj-$(CONFIG_IIO_ADC_HELPER) += industrialio-adc.o
# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_88PM886_GPADC) += 88pm886-gpadc.o
obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o
obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o
obj-$(CONFIG_AD4000) += ad4000.o
@@ -46,6 +47,7 @@ obj-$(CONFIG_AD7944) += ad7944.o
obj-$(CONFIG_AD7949) += ad7949.o
obj-$(CONFIG_AD799X) += ad799x.o
obj-$(CONFIG_AD9467) += ad9467.o
+obj-$(CONFIG_ADE9000) += ade9000.o
obj-$(CONFIG_ADI_AXI_ADC) += adi-axi-adc.o
obj-$(CONFIG_ASPEED_ADC) += aspeed_adc.o
obj-$(CONFIG_AT91_ADC) += at91_adc.o
@@ -70,6 +72,7 @@ obj-$(CONFIG_IMX8QXP_ADC) += imx8qxp-adc.o
obj-$(CONFIG_IMX93_ADC) += imx93_adc.o
obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o
obj-$(CONFIG_INGENIC_ADC) += ingenic-adc.o
+obj-$(CONFIG_INTEL_DC_TI_ADC) += intel_dc_ti_adc.o
obj-$(CONFIG_INTEL_MRFLD_ADC) += intel_mrfld_adc.o
obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
@@ -116,6 +119,7 @@ obj-$(CONFIG_QCOM_VADC_COMMON) += qcom-vadc-common.o
obj-$(CONFIG_RCAR_GYRO_ADC) += rcar-gyroadc.o
obj-$(CONFIG_RICHTEK_RTQ6056) += rtq6056.o
obj-$(CONFIG_RN5T618_ADC) += rn5t618-adc.o
+obj-$(CONFIG_ROHM_BD79112) += rohm-bd79112.o
obj-$(CONFIG_ROHM_BD79124) += rohm-bd79124.o
obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
obj-$(CONFIG_RZG2L_ADC) += rzg2l_adc.o
diff --git a/drivers/iio/adc/ab8500-gpadc.c b/drivers/iio/adc/ab8500-gpadc.c
index f3b057f92310..8eaa1dd6a89b 100644
--- a/drivers/iio/adc/ab8500-gpadc.c
+++ b/drivers/iio/adc/ab8500-gpadc.c
@@ -607,7 +607,6 @@ static int ab8500_gpadc_read(struct ab8500_gpadc *gpadc,
}
/* This eventually drops the regulator */
- pm_runtime_mark_last_busy(gpadc->dev);
pm_runtime_put_autosuspend(gpadc->dev);
return (high_data << 8) | low_data;
diff --git a/drivers/iio/adc/ad4130.c b/drivers/iio/adc/ad4130.c
index dcdb5778f7d6..5567ae5dee88 100644
--- a/drivers/iio/adc/ad4130.c
+++ b/drivers/iio/adc/ad4130.c
@@ -2035,8 +2035,7 @@ static int ad4130_probe(struct spi_device *spi)
ret = devm_add_action_or_reset(dev, ad4130_disable_regulators, st);
if (ret)
- return dev_err_probe(dev, ret,
- "Failed to add regulators disable action\n");
+ return ret;
ret = ad4130_soft_reset(st);
if (ret)
diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c
index 4d8c6bafd1c3..910b40393f77 100644
--- a/drivers/iio/adc/ad7124.c
+++ b/drivers/iio/adc/ad7124.c
@@ -3,21 +3,27 @@
* AD7124 SPI ADC driver
*
* Copyright 2018 Analog Devices Inc.
+ * Copyright 2025 BayLibre, SAS
*/
#include <linux/bitfield.h>
#include <linux/bitops.h>
+#include <linux/cleanup.h>
#include <linux/clk.h>
+#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/kfifo.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/property.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
+#include <linux/sprintf.h>
+#include <linux/units.h>
#include <linux/iio/iio.h>
#include <linux/iio/adc/ad_sigma_delta.h>
@@ -44,6 +50,11 @@
#define AD7124_STATUS_POR_FLAG BIT(4)
/* AD7124_ADC_CONTROL */
+#define AD7124_ADC_CONTROL_CLK_SEL GENMASK(1, 0)
+#define AD7124_ADC_CONTROL_CLK_SEL_INT 0
+#define AD7124_ADC_CONTROL_CLK_SEL_INT_OUT 1
+#define AD7124_ADC_CONTROL_CLK_SEL_EXT 2
+#define AD7124_ADC_CONTROL_CLK_SEL_EXT_DIV4 3
#define AD7124_ADC_CONTROL_MODE GENMASK(5, 2)
#define AD7124_ADC_CONTROL_MODE_CONTINUOUS 0
#define AD7124_ADC_CONTROL_MODE_SINGLE 1
@@ -84,14 +95,26 @@
#define AD7124_CONFIG_PGA GENMASK(2, 0)
/* AD7124_FILTER_X */
-#define AD7124_FILTER_FS GENMASK(10, 0)
#define AD7124_FILTER_FILTER GENMASK(23, 21)
#define AD7124_FILTER_FILTER_SINC4 0
#define AD7124_FILTER_FILTER_SINC3 2
+#define AD7124_FILTER_FILTER_SINC4_SINC1 4
+#define AD7124_FILTER_FILTER_SINC3_SINC1 5
+#define AD7124_FILTER_FILTER_SINC3_PF 7
+#define AD7124_FILTER_REJ60 BIT(20)
+#define AD7124_FILTER_POST_FILTER GENMASK(19, 17)
+#define AD7124_FILTER_POST_FILTER_47dB 2
+#define AD7124_FILTER_POST_FILTER_62dB 3
+#define AD7124_FILTER_POST_FILTER_86dB 5
+#define AD7124_FILTER_POST_FILTER_92dB 6
+#define AD7124_FILTER_SINGLE_CYCLE BIT(16)
+#define AD7124_FILTER_FS GENMASK(10, 0)
#define AD7124_MAX_CONFIGS 8
#define AD7124_MAX_CHANNELS 16
+#define AD7124_INT_CLK_HZ 614400
+
/* AD7124 input sources */
enum ad7124_ref_sel {
@@ -120,9 +143,9 @@ static const unsigned int ad7124_reg_size[] = {
};
static const int ad7124_master_clk_freq_hz[3] = {
- [AD7124_LOW_POWER] = 76800,
- [AD7124_MID_POWER] = 153600,
- [AD7124_FULL_POWER] = 614400,
+ [AD7124_LOW_POWER] = AD7124_INT_CLK_HZ / 8,
+ [AD7124_MID_POWER] = AD7124_INT_CLK_HZ / 4,
+ [AD7124_FULL_POWER] = AD7124_INT_CLK_HZ,
};
static const char * const ad7124_ref_names[] = {
@@ -138,9 +161,24 @@ struct ad7124_chip_info {
unsigned int num_inputs;
};
+enum ad7124_filter_type {
+ AD7124_FILTER_TYPE_SINC3,
+ AD7124_FILTER_TYPE_SINC3_PF1,
+ AD7124_FILTER_TYPE_SINC3_PF2,
+ AD7124_FILTER_TYPE_SINC3_PF3,
+ AD7124_FILTER_TYPE_SINC3_PF4,
+ AD7124_FILTER_TYPE_SINC3_REJ60,
+ AD7124_FILTER_TYPE_SINC3_SINC1,
+ AD7124_FILTER_TYPE_SINC4,
+ AD7124_FILTER_TYPE_SINC4_REJ60,
+ AD7124_FILTER_TYPE_SINC4_SINC1,
+};
+
struct ad7124_channel_config {
bool live;
unsigned int cfg_slot;
+ unsigned int requested_odr;
+ unsigned int requested_odr_micro;
/*
* Following fields are used to compare for equality. If you
* make adaptations in it, you most likely also have to adapt
@@ -153,9 +191,8 @@ struct ad7124_channel_config {
bool buf_negative;
unsigned int vref_mv;
unsigned int pga_bits;
- unsigned int odr;
unsigned int odr_sel_bits;
- unsigned int filter_type;
+ enum ad7124_filter_type filter_type;
unsigned int calibration_offset;
unsigned int calibration_gain;
);
@@ -174,7 +211,7 @@ struct ad7124_state {
struct ad_sigma_delta sd;
struct ad7124_channel *channels;
struct regulator *vref[4];
- struct clk *mclk;
+ u32 clk_hz;
unsigned int adc_control;
unsigned int num_channels;
struct mutex cfgs_lock; /* lock for configs access */
@@ -250,44 +287,117 @@ static int ad7124_set_mode(struct ad_sigma_delta *sd,
return ad_sd_write_reg(&st->sd, AD7124_ADC_CONTROL, 2, st->adc_control);
}
-static void ad7124_set_channel_odr(struct ad7124_state *st, unsigned int channel, unsigned int odr)
+static u32 ad7124_get_fclk_hz(struct ad7124_state *st)
+{
+ enum ad7124_power_mode power_mode;
+ u32 fclk_hz;
+
+ power_mode = FIELD_GET(AD7124_ADC_CONTROL_POWER_MODE, st->adc_control);
+ fclk_hz = st->clk_hz;
+
+ switch (power_mode) {
+ case AD7124_LOW_POWER:
+ fclk_hz /= 8;
+ break;
+ case AD7124_MID_POWER:
+ fclk_hz /= 4;
+ break;
+ default:
+ break;
+ }
+
+ return fclk_hz;
+}
+
+static u32 ad7124_get_fs_factor(struct ad7124_state *st, unsigned int channel)
{
- unsigned int fclk, odr_sel_bits;
+ enum ad7124_power_mode power_mode =
+ FIELD_GET(AD7124_ADC_CONTROL_POWER_MODE, st->adc_control);
+ u32 avg = power_mode == AD7124_LOW_POWER ? 8 : 16;
- fclk = clk_get_rate(st->mclk);
/*
- * FS[10:0] = fCLK / (fADC x 32) where:
+ * These are the "zero-latency" factors from the data sheet. For the
+ * sinc1 filters, these aren't documented, but derived by taking the
+ * single-channel formula from the sinc1 section of the data sheet and
+ * multiplying that by the sinc3/4 factor from the corresponding zero-
+ * latency sections.
+ */
+ switch (st->channels[channel].cfg.filter_type) {
+ case AD7124_FILTER_TYPE_SINC4:
+ case AD7124_FILTER_TYPE_SINC4_REJ60:
+ return 4 * 32;
+ case AD7124_FILTER_TYPE_SINC4_SINC1:
+ return 4 * avg * 32;
+ case AD7124_FILTER_TYPE_SINC3_SINC1:
+ return 3 * avg * 32;
+ default:
+ return 3 * 32;
+ }
+}
+
+static u32 ad7124_get_fadc_divisor(struct ad7124_state *st, unsigned int channel)
+{
+ u32 factor = ad7124_get_fs_factor(st, channel);
+
+ /*
+ * The output data rate (f_ADC) is f_CLK / divisor. We are returning
+ * the divisor.
+ */
+ return st->channels[channel].cfg.odr_sel_bits * factor;
+}
+
+static void ad7124_set_channel_odr(struct ad7124_state *st, unsigned int channel)
+{
+ struct ad7124_channel_config *cfg = &st->channels[channel].cfg;
+ unsigned int fclk, factor, divisor, odr_sel_bits;
+
+ fclk = ad7124_get_fclk_hz(st);
+ factor = ad7124_get_fs_factor(st, channel);
+
+ /*
+ * FS[10:0] = fCLK / (fADC x 32 * N) where:
* fADC is the output data rate
* fCLK is the master clock frequency
+ * N is number of conversions per sample (depends on filter type)
* FS[10:0] are the bits in the filter register
* FS[10:0] can have a value from 1 to 2047
*/
- odr_sel_bits = DIV_ROUND_CLOSEST(fclk, odr * 32);
- if (odr_sel_bits < 1)
- odr_sel_bits = 1;
- else if (odr_sel_bits > 2047)
- odr_sel_bits = 2047;
+ divisor = cfg->requested_odr * factor +
+ cfg->requested_odr_micro * factor / MICRO;
+ odr_sel_bits = clamp(DIV_ROUND_CLOSEST(fclk, divisor), 1, 2047);
if (odr_sel_bits != st->channels[channel].cfg.odr_sel_bits)
st->channels[channel].cfg.live = false;
- /* fADC = fCLK / (FS[10:0] x 32) */
- st->channels[channel].cfg.odr = DIV_ROUND_CLOSEST(fclk, odr_sel_bits * 32);
st->channels[channel].cfg.odr_sel_bits = odr_sel_bits;
}
-static int ad7124_get_3db_filter_freq(struct ad7124_state *st,
- unsigned int channel)
+static int ad7124_get_3db_filter_factor(struct ad7124_state *st,
+ unsigned int channel)
{
- unsigned int fadc;
+ struct ad7124_channel_config *cfg = &st->channels[channel].cfg;
- fadc = st->channels[channel].cfg.odr;
-
- switch (st->channels[channel].cfg.filter_type) {
- case AD7124_FILTER_FILTER_SINC3:
- return DIV_ROUND_CLOSEST(fadc * 272, 1000);
- case AD7124_FILTER_FILTER_SINC4:
- return DIV_ROUND_CLOSEST(fadc * 230, 1000);
+ /*
+ * 3dB point is the f_CLK rate times some factor. This functions returns
+ * the factor times 1000.
+ */
+ switch (cfg->filter_type) {
+ case AD7124_FILTER_TYPE_SINC3:
+ case AD7124_FILTER_TYPE_SINC3_REJ60:
+ case AD7124_FILTER_TYPE_SINC3_SINC1:
+ return 272;
+ case AD7124_FILTER_TYPE_SINC4:
+ case AD7124_FILTER_TYPE_SINC4_REJ60:
+ case AD7124_FILTER_TYPE_SINC4_SINC1:
+ return 230;
+ case AD7124_FILTER_TYPE_SINC3_PF1:
+ return 633;
+ case AD7124_FILTER_TYPE_SINC3_PF2:
+ return 605;
+ case AD7124_FILTER_TYPE_SINC3_PF3:
+ return 669;
+ case AD7124_FILTER_TYPE_SINC3_PF4:
+ return 759;
default:
return -EINVAL;
}
@@ -311,9 +421,8 @@ static struct ad7124_channel_config *ad7124_find_similar_live_cfg(struct ad7124_
bool buf_negative;
unsigned int vref_mv;
unsigned int pga_bits;
- unsigned int odr;
unsigned int odr_sel_bits;
- unsigned int filter_type;
+ enum ad7124_filter_type filter_type;
unsigned int calibration_offset;
unsigned int calibration_gain;
}));
@@ -328,7 +437,6 @@ static struct ad7124_channel_config *ad7124_find_similar_live_cfg(struct ad7124_
cfg->buf_negative == cfg_aux->buf_negative &&
cfg->vref_mv == cfg_aux->vref_mv &&
cfg->pga_bits == cfg_aux->pga_bits &&
- cfg->odr == cfg_aux->odr &&
cfg->odr_sel_bits == cfg_aux->odr_sel_bits &&
cfg->filter_type == cfg_aux->filter_type &&
cfg->calibration_offset == cfg_aux->calibration_offset &&
@@ -381,8 +489,9 @@ static int ad7124_init_config_vref(struct ad7124_state *st, struct ad7124_channe
static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_config *cfg,
unsigned int cfg_slot)
{
- unsigned int tmp;
- unsigned int val;
+ unsigned int val, filter;
+ unsigned int rej60 = 0;
+ unsigned int post = 0;
int ret;
cfg->cfg_slot = cfg_slot;
@@ -405,11 +514,60 @@ static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_co
if (ret < 0)
return ret;
- tmp = FIELD_PREP(AD7124_FILTER_FILTER, cfg->filter_type) |
- FIELD_PREP(AD7124_FILTER_FS, cfg->odr_sel_bits);
- return ad7124_spi_write_mask(st, AD7124_FILTER(cfg->cfg_slot),
- AD7124_FILTER_FILTER | AD7124_FILTER_FS,
- tmp, 3);
+ switch (cfg->filter_type) {
+ case AD7124_FILTER_TYPE_SINC3:
+ filter = AD7124_FILTER_FILTER_SINC3;
+ break;
+ case AD7124_FILTER_TYPE_SINC3_PF1:
+ filter = AD7124_FILTER_FILTER_SINC3_PF;
+ post = AD7124_FILTER_POST_FILTER_47dB;
+ break;
+ case AD7124_FILTER_TYPE_SINC3_PF2:
+ filter = AD7124_FILTER_FILTER_SINC3_PF;
+ post = AD7124_FILTER_POST_FILTER_62dB;
+ break;
+ case AD7124_FILTER_TYPE_SINC3_PF3:
+ filter = AD7124_FILTER_FILTER_SINC3_PF;
+ post = AD7124_FILTER_POST_FILTER_86dB;
+ break;
+ case AD7124_FILTER_TYPE_SINC3_PF4:
+ filter = AD7124_FILTER_FILTER_SINC3_PF;
+ post = AD7124_FILTER_POST_FILTER_92dB;
+ break;
+ case AD7124_FILTER_TYPE_SINC3_REJ60:
+ filter = AD7124_FILTER_FILTER_SINC3;
+ rej60 = 1;
+ break;
+ case AD7124_FILTER_TYPE_SINC3_SINC1:
+ filter = AD7124_FILTER_FILTER_SINC3_SINC1;
+ break;
+ case AD7124_FILTER_TYPE_SINC4:
+ filter = AD7124_FILTER_FILTER_SINC4;
+ break;
+ case AD7124_FILTER_TYPE_SINC4_REJ60:
+ filter = AD7124_FILTER_FILTER_SINC4;
+ rej60 = 1;
+ break;
+ case AD7124_FILTER_TYPE_SINC4_SINC1:
+ filter = AD7124_FILTER_FILTER_SINC4_SINC1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * NB: AD7124_FILTER_SINGLE_CYCLE is always set so that we get the same
+ * sampling frequency even when only one channel is enabled in a
+ * buffered read. If it was not set, the N in ad7124_set_channel_odr()
+ * would be 1 and we would get a faster sampling frequency than what
+ * was requested.
+ */
+ return ad_sd_write_reg(&st->sd, AD7124_FILTER(cfg->cfg_slot), 3,
+ FIELD_PREP(AD7124_FILTER_FILTER, filter) |
+ FIELD_PREP(AD7124_FILTER_REJ60, rej60) |
+ FIELD_PREP(AD7124_FILTER_POST_FILTER, post) |
+ AD7124_FILTER_SINGLE_CYCLE |
+ FIELD_PREP(AD7124_FILTER_FS, cfg->odr_sel_bits));
}
static struct ad7124_channel_config *ad7124_pop_config(struct ad7124_state *st)
@@ -576,6 +734,33 @@ static const struct ad_sigma_delta_info ad7124_sigma_delta_info = {
.num_resetclks = 64,
};
+static const int ad7124_voltage_scales[][2] = {
+ { 0, 1164 },
+ { 0, 2328 },
+ { 0, 4656 },
+ { 0, 9313 },
+ { 0, 18626 },
+ { 0, 37252 },
+ { 0, 74505 },
+ { 0, 149011 },
+ { 0, 298023 },
+};
+
+static int ad7124_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length, long info)
+{
+ switch (info) {
+ case IIO_CHAN_INFO_SCALE:
+ *vals = (const int *)ad7124_voltage_scales;
+ *type = IIO_VAL_INT_PLUS_NANO;
+ *length = ARRAY_SIZE(ad7124_voltage_scales) * 2;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
static int ad7124_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long info)
@@ -644,18 +829,59 @@ static int ad7124_read_raw(struct iio_dev *indio_dev,
return -EINVAL;
}
- case IIO_CHAN_INFO_SAMP_FREQ:
- mutex_lock(&st->cfgs_lock);
- *val = st->channels[chan->address].cfg.odr;
- mutex_unlock(&st->cfgs_lock);
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ struct ad7124_channel_config *cfg = &st->channels[chan->address].cfg;
- return IIO_VAL_INT;
- case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
- mutex_lock(&st->cfgs_lock);
- *val = ad7124_get_3db_filter_freq(st, chan->scan_index);
- mutex_unlock(&st->cfgs_lock);
+ guard(mutex)(&st->cfgs_lock);
- return IIO_VAL_INT;
+ switch (cfg->filter_type) {
+ case AD7124_FILTER_TYPE_SINC3:
+ case AD7124_FILTER_TYPE_SINC3_REJ60:
+ case AD7124_FILTER_TYPE_SINC3_SINC1:
+ case AD7124_FILTER_TYPE_SINC4:
+ case AD7124_FILTER_TYPE_SINC4_REJ60:
+ case AD7124_FILTER_TYPE_SINC4_SINC1:
+ *val = ad7124_get_fclk_hz(st);
+ *val2 = ad7124_get_fadc_divisor(st, chan->address);
+ return IIO_VAL_FRACTIONAL;
+ /*
+ * Post filters force the chip to a fixed rate. These are the
+ * single-channel rates from the data sheet divided by 3 for
+ * the multi-channel case (data sheet doesn't explicitly state
+ * this but confirmed through testing).
+ */
+ case AD7124_FILTER_TYPE_SINC3_PF1:
+ *val = 300;
+ *val2 = 33;
+ return IIO_VAL_FRACTIONAL;
+ case AD7124_FILTER_TYPE_SINC3_PF2:
+ *val = 25;
+ *val2 = 3;
+ return IIO_VAL_FRACTIONAL;
+ case AD7124_FILTER_TYPE_SINC3_PF3:
+ *val = 20;
+ *val2 = 3;
+ return IIO_VAL_FRACTIONAL;
+ case AD7124_FILTER_TYPE_SINC3_PF4:
+ *val = 50;
+ *val2 = 9;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ }
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: {
+ guard(mutex)(&st->cfgs_lock);
+
+ ret = ad7124_get_3db_filter_factor(st, chan->address);
+ if (ret < 0)
+ return ret;
+
+ /* 3dB point is the f_CLK rate times a fractional value */
+ *val = ret * ad7124_get_fclk_hz(st);
+ *val2 = MILLI * ad7124_get_fadc_divisor(st, chan->address);
+ return IIO_VAL_FRACTIONAL;
+ }
default:
return -EINVAL;
}
@@ -666,25 +892,24 @@ static int ad7124_write_raw(struct iio_dev *indio_dev,
int val, int val2, long info)
{
struct ad7124_state *st = iio_priv(indio_dev);
+ struct ad7124_channel_config *cfg = &st->channels[chan->address].cfg;
unsigned int res, gain, full_scale, vref;
- int ret = 0;
- mutex_lock(&st->cfgs_lock);
+ guard(mutex)(&st->cfgs_lock);
switch (info) {
case IIO_CHAN_INFO_SAMP_FREQ:
- if (val2 != 0 || val == 0) {
- ret = -EINVAL;
- break;
- }
+ if (val2 < 0 || val < 0 || (val2 == 0 && val == 0))
+ return -EINVAL;
- ad7124_set_channel_odr(st, chan->address, val);
- break;
+ cfg->requested_odr = val;
+ cfg->requested_odr_micro = val2;
+ ad7124_set_channel_odr(st, chan->address);
+
+ return 0;
case IIO_CHAN_INFO_SCALE:
- if (val != 0) {
- ret = -EINVAL;
- break;
- }
+ if (val != 0)
+ return -EINVAL;
if (st->channels[chan->address].cfg.bipolar)
full_scale = 1 << (chan->scan_type.realbits - 1);
@@ -700,13 +925,10 @@ static int ad7124_write_raw(struct iio_dev *indio_dev,
st->channels[chan->address].cfg.live = false;
st->channels[chan->address].cfg.pga_bits = res;
- break;
+ return 0;
default:
- ret = -EINVAL;
+ return -EINVAL;
}
-
- mutex_unlock(&st->cfgs_lock);
- return ret;
}
static int ad7124_reg_access(struct iio_dev *indio_dev,
@@ -730,18 +952,6 @@ static int ad7124_reg_access(struct iio_dev *indio_dev,
return ret;
}
-static IIO_CONST_ATTR(in_voltage_scale_available,
- "0.000001164 0.000002328 0.000004656 0.000009313 0.000018626 0.000037252 0.000074505 0.000149011 0.000298023");
-
-static struct attribute *ad7124_attributes[] = {
- &iio_const_attr_in_voltage_scale_available.dev_attr.attr,
- NULL,
-};
-
-static const struct attribute_group ad7124_attrs_group = {
- .attrs = ad7124_attributes,
-};
-
static int ad7124_update_scan_mode(struct iio_dev *indio_dev,
const unsigned long *scan_mask)
{
@@ -750,7 +960,8 @@ static int ad7124_update_scan_mode(struct iio_dev *indio_dev,
int ret;
int i;
- mutex_lock(&st->cfgs_lock);
+ guard(mutex)(&st->cfgs_lock);
+
for (i = 0; i < st->num_channels; i++) {
bit_set = test_bit(i, scan_mask);
if (bit_set)
@@ -758,25 +969,20 @@ static int ad7124_update_scan_mode(struct iio_dev *indio_dev,
else
ret = ad7124_spi_write_mask(st, AD7124_CHANNEL(i), AD7124_CHANNEL_ENABLE,
0, 2);
- if (ret < 0) {
- mutex_unlock(&st->cfgs_lock);
-
+ if (ret < 0)
return ret;
- }
}
- mutex_unlock(&st->cfgs_lock);
-
return 0;
}
static const struct iio_info ad7124_info = {
+ .read_avail = ad7124_read_avail,
.read_raw = ad7124_read_raw,
.write_raw = ad7124_write_raw,
.debugfs_reg_access = &ad7124_reg_access,
.validate_trigger = ad_sd_validate_trigger,
.update_scan_mode = ad7124_update_scan_mode,
- .attrs = &ad7124_attrs_group,
};
/* Only called during probe, so dev_err_probe() can be used */
@@ -944,6 +1150,52 @@ static const struct iio_enum ad7124_syscalib_mode_enum = {
.get = ad7124_get_syscalib_mode
};
+static const char * const ad7124_filter_types[] = {
+ [AD7124_FILTER_TYPE_SINC3] = "sinc3",
+ [AD7124_FILTER_TYPE_SINC3_PF1] = "sinc3+pf1",
+ [AD7124_FILTER_TYPE_SINC3_PF2] = "sinc3+pf2",
+ [AD7124_FILTER_TYPE_SINC3_PF3] = "sinc3+pf3",
+ [AD7124_FILTER_TYPE_SINC3_PF4] = "sinc3+pf4",
+ [AD7124_FILTER_TYPE_SINC3_REJ60] = "sinc3+rej60",
+ [AD7124_FILTER_TYPE_SINC3_SINC1] = "sinc3+sinc1",
+ [AD7124_FILTER_TYPE_SINC4] = "sinc4",
+ [AD7124_FILTER_TYPE_SINC4_REJ60] = "sinc4+rej60",
+ [AD7124_FILTER_TYPE_SINC4_SINC1] = "sinc4+sinc1",
+};
+
+static int ad7124_set_filter_type_attr(struct iio_dev *dev,
+ const struct iio_chan_spec *chan,
+ unsigned int value)
+{
+ struct ad7124_state *st = iio_priv(dev);
+ struct ad7124_channel_config *cfg = &st->channels[chan->address].cfg;
+
+ guard(mutex)(&st->cfgs_lock);
+
+ cfg->live = false;
+ cfg->filter_type = value;
+ ad7124_set_channel_odr(st, chan->address);
+
+ return 0;
+}
+
+static int ad7124_get_filter_type_attr(struct iio_dev *dev,
+ const struct iio_chan_spec *chan)
+{
+ struct ad7124_state *st = iio_priv(dev);
+
+ guard(mutex)(&st->cfgs_lock);
+
+ return st->channels[chan->address].cfg.filter_type;
+}
+
+static const struct iio_enum ad7124_filter_type_enum = {
+ .items = ad7124_filter_types,
+ .num_items = ARRAY_SIZE(ad7124_filter_types),
+ .set = ad7124_set_filter_type_attr,
+ .get = ad7124_get_filter_type_attr,
+};
+
static const struct iio_chan_spec_ext_info ad7124_calibsys_ext_info[] = {
{
.name = "sys_calibration",
@@ -954,6 +1206,9 @@ static const struct iio_chan_spec_ext_info ad7124_calibsys_ext_info[] = {
&ad7124_syscalib_mode_enum),
IIO_ENUM_AVAILABLE("sys_calibration_mode", IIO_SHARED_BY_TYPE,
&ad7124_syscalib_mode_enum),
+ IIO_ENUM("filter_type", IIO_SEPARATE, &ad7124_filter_type_enum),
+ IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE,
+ &ad7124_filter_type_enum),
{ }
};
@@ -966,6 +1221,7 @@ static const struct iio_chan_spec ad7124_channel_template = {
BIT(IIO_CHAN_INFO_OFFSET) |
BIT(IIO_CHAN_INFO_SAMP_FREQ) |
BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY),
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE),
.scan_type = {
.sign = 'u',
.realbits = 24,
@@ -1111,24 +1367,122 @@ static int ad7124_parse_channel_config(struct iio_dev *indio_dev,
static int ad7124_setup(struct ad7124_state *st)
{
struct device *dev = &st->sd.spi->dev;
- unsigned int fclk, power_mode;
+ unsigned int power_mode, clk_sel;
+ struct clk *mclk;
int i, ret;
- fclk = clk_get_rate(st->mclk);
- if (!fclk)
- return dev_err_probe(dev, -EINVAL, "Failed to get mclk rate\n");
+ /*
+ * Always use full power mode for max performance. If needed, the driver
+ * could be adapted to use a dynamic power mode based on the requested
+ * output data rate.
+ */
+ power_mode = AD7124_ADC_CONTROL_POWER_MODE_FULL;
+
+ /*
+ * This "mclk" business is needed for backwards compatibility with old
+ * devicetrees that specified a fake clock named "mclk" to select the
+ * power mode.
+ */
+ mclk = devm_clk_get_optional_enabled(dev, "mclk");
+ if (IS_ERR(mclk))
+ return dev_err_probe(dev, PTR_ERR(mclk), "Failed to get mclk\n");
+
+ if (mclk) {
+ unsigned long mclk_hz;
- /* The power mode changes the master clock frequency */
- power_mode = ad7124_find_closest_match(ad7124_master_clk_freq_hz,
- ARRAY_SIZE(ad7124_master_clk_freq_hz),
- fclk);
- if (fclk != ad7124_master_clk_freq_hz[power_mode]) {
- ret = clk_set_rate(st->mclk, fclk);
+ mclk_hz = clk_get_rate(mclk);
+ if (!mclk_hz)
+ return dev_err_probe(dev, -EINVAL,
+ "Failed to get mclk rate\n");
+
+ /*
+ * This logic is a bit backwards, which is why it is only here
+ * for backwards compatibility. The driver should be able to set
+ * the power mode as it sees fit and the f_clk/mclk rate should
+ * be dynamic accordingly. But here, we are selecting a fixed
+ * power mode based on the given "mclk" rate.
+ */
+ power_mode = ad7124_find_closest_match(ad7124_master_clk_freq_hz,
+ ARRAY_SIZE(ad7124_master_clk_freq_hz), mclk_hz);
+
+ if (mclk_hz != ad7124_master_clk_freq_hz[power_mode]) {
+ ret = clk_set_rate(mclk, mclk_hz);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to set mclk rate\n");
+ }
+
+ clk_sel = AD7124_ADC_CONTROL_CLK_SEL_INT;
+ st->clk_hz = AD7124_INT_CLK_HZ;
+ } else if (!device_property_present(dev, "clocks") &&
+ device_property_present(dev, "#clock-cells")) {
+#ifdef CONFIG_COMMON_CLK
+ struct clk_hw *clk_hw;
+
+ const char *name __free(kfree) = kasprintf(GFP_KERNEL, "%pfwP-clk",
+ dev_fwnode(dev));
+ if (!name)
+ return -ENOMEM;
+
+ clk_hw = devm_clk_hw_register_fixed_rate(dev, name, NULL, 0,
+ AD7124_INT_CLK_HZ);
+ if (IS_ERR(clk_hw))
+ return dev_err_probe(dev, PTR_ERR(clk_hw),
+ "Failed to register clock provider\n");
+
+ ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
+ clk_hw);
if (ret)
- return dev_err_probe(dev, ret, "Failed to set mclk rate\n");
+ return dev_err_probe(dev, ret,
+ "Failed to add clock provider\n");
+#endif
+
+ /*
+ * Treat the clock as always on. This way we don't have to deal
+ * with someone trying to enable/disable the clock while we are
+ * reading samples.
+ */
+ clk_sel = AD7124_ADC_CONTROL_CLK_SEL_INT_OUT;
+ st->clk_hz = AD7124_INT_CLK_HZ;
+ } else {
+ struct clk *clk;
+
+ clk = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk),
+ "Failed to get external clock\n");
+
+ if (clk) {
+ unsigned long clk_hz;
+
+ clk_hz = clk_get_rate(clk);
+ if (!clk_hz)
+ return dev_err_probe(dev, -EINVAL,
+ "Failed to get external clock rate\n");
+
+ /*
+ * The external clock may be 4x the nominal clock rate,
+ * in which case the ADC needs to be configured to
+ * divide it by 4. Using MEGA is a bit arbitrary, but
+ * the expected clock rates are either 614.4 kHz or
+ * 2.4576 MHz, so this should work.
+ */
+ if (clk_hz > (1 * HZ_PER_MHZ)) {
+ clk_sel = AD7124_ADC_CONTROL_CLK_SEL_EXT_DIV4;
+ st->clk_hz = clk_hz / 4;
+ } else {
+ clk_sel = AD7124_ADC_CONTROL_CLK_SEL_EXT;
+ st->clk_hz = clk_hz;
+ }
+ } else {
+ clk_sel = AD7124_ADC_CONTROL_CLK_SEL_INT;
+ st->clk_hz = AD7124_INT_CLK_HZ;
+ }
}
- /* Set the power mode */
+ st->adc_control &= ~AD7124_ADC_CONTROL_CLK_SEL;
+ st->adc_control |= FIELD_PREP(AD7124_ADC_CONTROL_CLK_SEL, clk_sel);
+
st->adc_control &= ~AD7124_ADC_CONTROL_POWER_MODE;
st->adc_control |= FIELD_PREP(AD7124_ADC_CONTROL_POWER_MODE, power_mode);
@@ -1138,17 +1492,22 @@ static int ad7124_setup(struct ad7124_state *st)
mutex_init(&st->cfgs_lock);
INIT_KFIFO(st->live_cfgs_fifo);
for (i = 0; i < st->num_channels; i++) {
+ struct ad7124_channel_config *cfg = &st->channels[i].cfg;
- ret = ad7124_init_config_vref(st, &st->channels[i].cfg);
+ ret = ad7124_init_config_vref(st, cfg);
if (ret < 0)
return ret;
+ /* Default filter type on the ADC after reset. */
+ cfg->filter_type = AD7124_FILTER_TYPE_SINC4;
+
/*
* 9.38 SPS is the minimum output data rate supported
* regardless of the selected power mode. Round it up to 10 and
* set all channels to this default value.
*/
- ad7124_set_channel_odr(st, i, 10);
+ cfg->requested_odr = 10;
+ ad7124_set_channel_odr(st, i);
}
ad7124_disable_all(&st->sd);
@@ -1300,13 +1659,9 @@ static int ad7124_probe(struct spi_device *spi)
ret = devm_add_action_or_reset(&spi->dev, ad7124_reg_disable,
st->vref[i]);
if (ret)
- return dev_err_probe(dev, ret, "Failed to register disable handler for regulator #%d\n", i);
+ return ret;
}
- st->mclk = devm_clk_get_enabled(&spi->dev, "mclk");
- if (IS_ERR(st->mclk))
- return dev_err_probe(dev, PTR_ERR(st->mclk), "Failed to get mclk\n");
-
ret = ad7124_soft_reset(st);
if (ret < 0)
return ret;
diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c
index 683146e83ab2..d36612352b44 100644
--- a/drivers/iio/adc/ad7173.c
+++ b/drivers/iio/adc/ad7173.c
@@ -8,6 +8,7 @@
* AD7175-8/AD7176-2/AD7177-2
*
* Copyright (C) 2015, 2024 Analog Devices, Inc.
+ * Copyright (C) 2025 BayLibre, SAS
*/
#include <linux/array_size.h>
@@ -149,7 +150,12 @@
(pin2) < st->info->num_voltage_in && \
(pin2) >= st->info->num_voltage_in_div)
-#define AD7173_FILTER_ODR0_MASK GENMASK(5, 0)
+#define AD7173_FILTER_SINC3_MAP BIT(15)
+#define AD7173_FILTER_SINC3_MAP_DIV GENMASK(14, 0)
+#define AD7173_FILTER_ENHFILTEN BIT(11)
+#define AD7173_FILTER_ENHFILT_MASK GENMASK(10, 8)
+#define AD7173_FILTER_ORDER BIT(6)
+#define AD7173_FILTER_ODR_MASK GENMASK(5, 0)
#define AD7173_MAX_CONFIGS 8
#define AD4111_OW_DET_THRSH_MV 300
@@ -190,6 +196,15 @@ struct ad7173_device_info {
u8 num_gpios;
};
+enum ad7173_filter_type {
+ AD7173_FILTER_SINC3,
+ AD7173_FILTER_SINC5_SINC1,
+ AD7173_FILTER_SINC5_SINC1_PF1,
+ AD7173_FILTER_SINC5_SINC1_PF2,
+ AD7173_FILTER_SINC5_SINC1_PF3,
+ AD7173_FILTER_SINC5_SINC1_PF4,
+};
+
struct ad7173_channel_config {
/* Openwire detection threshold */
unsigned int openwire_thrsh_raw;
@@ -205,8 +220,10 @@ struct ad7173_channel_config {
struct_group(config_props,
bool bipolar;
bool input_buf;
- u8 odr;
+ u16 sinc3_odr_div;
+ u8 sinc5_odr_index;
u8 ref_sel;
+ enum ad7173_filter_type filter_type;
);
};
@@ -266,6 +283,24 @@ static const unsigned int ad7175_sinc5_data_rates[] = {
5000, /* 20 */
};
+/**
+ * ad7173_sinc3_odr_div_from_odr() - Convert ODR to divider value
+ * @odr_millihz: ODR (sampling_frequency) in milliHz
+ * Returns: Divider value for SINC3 filter to pass.
+ */
+static u16 ad7173_sinc3_odr_div_from_odr(u32 odr_millihz)
+{
+ /*
+ * Divider is f_MOD (1 MHz) / 32 / ODR. ODR freq is in milliHz, so
+ * we need to convert f_MOD to the same units. When SING_CYC=1 or
+ * multiple channels are enabled (currently always the case), there
+ * is an additional factor of 3.
+ */
+ u32 div = DIV_ROUND_CLOSEST(MEGA * MILLI, odr_millihz * 32 * 3);
+ /* Avoid divide by 0 and limit to register field size. */
+ return clamp(div, 1U, AD7173_FILTER_SINC3_MAP_DIV);
+}
+
static unsigned int ad4111_current_channel_config[] = {
/* Ain sel: pos neg */
0x1E8, /* 15:IIN0+ 8:IIN0− */
@@ -369,7 +404,48 @@ static const struct iio_enum ad7173_syscalib_mode_enum = {
.get = ad7173_get_syscalib_mode
};
-static const struct iio_chan_spec_ext_info ad7173_calibsys_ext_info[] = {
+static const char * const ad7173_filter_types_str[] = {
+ [AD7173_FILTER_SINC3] = "sinc3",
+ [AD7173_FILTER_SINC5_SINC1] = "sinc5+sinc1",
+ [AD7173_FILTER_SINC5_SINC1_PF1] = "sinc5+sinc1+pf1",
+ [AD7173_FILTER_SINC5_SINC1_PF2] = "sinc5+sinc1+pf2",
+ [AD7173_FILTER_SINC5_SINC1_PF3] = "sinc5+sinc1+pf3",
+ [AD7173_FILTER_SINC5_SINC1_PF4] = "sinc5+sinc1+pf4",
+};
+
+static int ad7173_set_filter_type(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int val)
+{
+ struct ad7173_state *st = iio_priv(indio_dev);
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ st->channels[chan->address].cfg.filter_type = val;
+ st->channels[chan->address].cfg.live = false;
+
+ iio_device_release_direct(indio_dev);
+
+ return 0;
+}
+
+static int ad7173_get_filter_type(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct ad7173_state *st = iio_priv(indio_dev);
+
+ return st->channels[chan->address].cfg.filter_type;
+}
+
+static const struct iio_enum ad7173_filter_type_enum = {
+ .items = ad7173_filter_types_str,
+ .num_items = ARRAY_SIZE(ad7173_filter_types_str),
+ .set = ad7173_set_filter_type,
+ .get = ad7173_get_filter_type,
+};
+
+static const struct iio_chan_spec_ext_info ad7173_chan_spec_ext_info[] = {
{
.name = "sys_calibration",
.write = ad7173_write_syscalib,
@@ -379,6 +455,16 @@ static const struct iio_chan_spec_ext_info ad7173_calibsys_ext_info[] = {
&ad7173_syscalib_mode_enum),
IIO_ENUM_AVAILABLE("sys_calibration_mode", IIO_SHARED_BY_TYPE,
&ad7173_syscalib_mode_enum),
+ IIO_ENUM("filter_type", IIO_SEPARATE, &ad7173_filter_type_enum),
+ IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE,
+ &ad7173_filter_type_enum),
+ { }
+};
+
+static const struct iio_chan_spec_ext_info ad7173_temp_chan_spec_ext_info[] = {
+ IIO_ENUM("filter_type", IIO_SEPARATE, &ad7173_filter_type_enum),
+ IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE,
+ &ad7173_filter_type_enum),
{ }
};
@@ -582,14 +668,18 @@ static bool ad7173_is_setup_equal(const struct ad7173_channel_config *cfg1,
sizeof(struct {
bool bipolar;
bool input_buf;
- u8 odr;
+ u16 sinc3_odr_div;
+ u8 sinc5_odr_index;
u8 ref_sel;
+ enum ad7173_filter_type filter_type;
}));
return cfg1->bipolar == cfg2->bipolar &&
cfg1->input_buf == cfg2->input_buf &&
- cfg1->odr == cfg2->odr &&
- cfg1->ref_sel == cfg2->ref_sel;
+ cfg1->sinc3_odr_div == cfg2->sinc3_odr_div &&
+ cfg1->sinc5_odr_index == cfg2->sinc5_odr_index &&
+ cfg1->ref_sel == cfg2->ref_sel &&
+ cfg1->filter_type == cfg2->filter_type;
}
static struct ad7173_channel_config *
@@ -630,6 +720,7 @@ static int ad7173_load_config(struct ad7173_state *st,
{
unsigned int config;
int free_cfg_slot, ret;
+ u8 post_filter_enable, post_filter_select;
free_cfg_slot = ida_alloc_range(&st->cfg_slots_status, 0,
st->info->num_configs - 1, GFP_KERNEL);
@@ -649,8 +740,49 @@ static int ad7173_load_config(struct ad7173_state *st,
if (ret)
return ret;
+ /*
+ * When SINC3_MAP flag is enabled, the rest of the register has a
+ * different meaning. We are using this option to allow the most
+ * possible sampling frequencies with SINC3 filter.
+ */
+ if (cfg->filter_type == AD7173_FILTER_SINC3)
+ return ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(free_cfg_slot), 2,
+ FIELD_PREP(AD7173_FILTER_SINC3_MAP, 1) |
+ FIELD_PREP(AD7173_FILTER_SINC3_MAP_DIV,
+ cfg->sinc3_odr_div));
+
+ switch (cfg->filter_type) {
+ case AD7173_FILTER_SINC5_SINC1_PF1:
+ post_filter_enable = 1;
+ post_filter_select = 2;
+ break;
+ case AD7173_FILTER_SINC5_SINC1_PF2:
+ post_filter_enable = 1;
+ post_filter_select = 3;
+ break;
+ case AD7173_FILTER_SINC5_SINC1_PF3:
+ post_filter_enable = 1;
+ post_filter_select = 5;
+ break;
+ case AD7173_FILTER_SINC5_SINC1_PF4:
+ post_filter_enable = 1;
+ post_filter_select = 6;
+ break;
+ default:
+ post_filter_enable = 0;
+ post_filter_select = 0;
+ break;
+ }
+
return ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(free_cfg_slot), 2,
- AD7173_FILTER_ODR0_MASK & cfg->odr);
+ FIELD_PREP(AD7173_FILTER_SINC3_MAP, 0) |
+ FIELD_PREP(AD7173_FILTER_ENHFILT_MASK,
+ post_filter_enable) |
+ FIELD_PREP(AD7173_FILTER_ENHFILTEN,
+ post_filter_select) |
+ FIELD_PREP(AD7173_FILTER_ORDER, 0) |
+ FIELD_PREP(AD7173_FILTER_ODR_MASK,
+ cfg->sinc5_odr_index));
}
static int ad7173_config_channel(struct ad7173_state *st, int addr)
@@ -761,6 +893,7 @@ static const struct ad_sigma_delta_info ad7173_sigma_delta_info_4_slots = {
.set_mode = ad7173_set_mode,
.has_registers = true,
.has_named_irqs = true,
+ .supports_spi_offload = true,
.addr_shift = 0,
.read_mask = BIT(6),
.status_ch_mask = GENMASK(3, 0),
@@ -777,6 +910,7 @@ static const struct ad_sigma_delta_info ad7173_sigma_delta_info_8_slots = {
.set_mode = ad7173_set_mode,
.has_registers = true,
.has_named_irqs = true,
+ .supports_spi_offload = true,
.addr_shift = 0,
.read_mask = BIT(6),
.status_ch_mask = GENMASK(3, 0),
@@ -793,6 +927,7 @@ static const struct ad_sigma_delta_info ad7173_sigma_delta_info_16_slots = {
.set_mode = ad7173_set_mode,
.has_registers = true,
.has_named_irqs = true,
+ .supports_spi_offload = true,
.addr_shift = 0,
.read_mask = BIT(6),
.status_ch_mask = GENMASK(3, 0),
@@ -1180,7 +1315,14 @@ static int ad7173_read_raw(struct iio_dev *indio_dev,
return -EINVAL;
}
case IIO_CHAN_INFO_SAMP_FREQ:
- reg = st->channels[chan->address].cfg.odr;
+ if (st->channels[chan->address].cfg.filter_type == AD7173_FILTER_SINC3) {
+ /* Inverse operation of ad7173_sinc3_odr_div_from_odr() */
+ *val = MEGA;
+ *val2 = 3 * 32 * st->channels[chan->address].cfg.sinc3_odr_div;
+ return IIO_VAL_FRACTIONAL;
+ }
+
+ reg = st->channels[chan->address].cfg.sinc5_odr_index;
*val = st->info->sinc5_data_rates[reg] / MILLI;
*val2 = (st->info->sinc5_data_rates[reg] % MILLI) * (MICRO / MILLI);
@@ -1218,6 +1360,10 @@ static int ad7173_write_raw(struct iio_dev *indio_dev,
*
* This will cause the reading of CH1 to be actually done once every
* 200.16ms, an effective rate of 4.99sps.
+ *
+ * Both the sinc5 and sinc3 rates are set here so that if the filter
+ * type is changed, the requested rate will still be set (aside from
+ * rounding differences).
*/
case IIO_CHAN_INFO_SAMP_FREQ:
freq = val * MILLI + val2 / MILLI;
@@ -1226,7 +1372,8 @@ static int ad7173_write_raw(struct iio_dev *indio_dev,
break;
cfg = &st->channels[chan->address].cfg;
- cfg->odr = i;
+ cfg->sinc5_odr_index = i;
+ cfg->sinc3_odr_div = ad7173_sinc3_odr_div_from_odr(freq);
cfg->live = false;
break;
@@ -1243,17 +1390,40 @@ static int ad7173_update_scan_mode(struct iio_dev *indio_dev,
const unsigned long *scan_mask)
{
struct ad7173_state *st = iio_priv(indio_dev);
+ u16 sinc3_count = 0;
+ u16 sinc3_div = 0;
int i, j, k, ret;
for (i = 0; i < indio_dev->num_channels; i++) {
- if (test_bit(i, scan_mask))
+ const struct ad7173_channel_config *cfg = &st->channels[i].cfg;
+
+ if (test_bit(i, scan_mask)) {
+ if (cfg->filter_type == AD7173_FILTER_SINC3) {
+ sinc3_count++;
+
+ if (sinc3_div == 0) {
+ sinc3_div = cfg->sinc3_odr_div;
+ } else if (sinc3_div != cfg->sinc3_odr_div) {
+ dev_err(&st->sd.spi->dev,
+ "All enabled channels must have the same sampling_frequency for sinc3 filter_type\n");
+ return -EINVAL;
+ }
+ }
+
ret = ad7173_set_channel(&st->sd, i);
- else
+ } else {
ret = ad_sd_write_reg(&st->sd, AD7173_REG_CH(i), 2, 0);
+ }
if (ret < 0)
return ret;
}
+ if (sinc3_count && sinc3_count < bitmap_weight(scan_mask, indio_dev->num_channels)) {
+ dev_err(&st->sd.spi->dev,
+ "All enabled channels must have sinc3 filter_type\n");
+ return -EINVAL;
+ }
+
/*
* On some chips, there are more channels that setups, so if there were
* more unique setups requested than the number of available slots,
@@ -1396,7 +1566,7 @@ static const struct iio_chan_spec ad7173_channel_template = {
.storagebits = 32,
.endianness = IIO_BE,
},
- .ext_info = ad7173_calibsys_ext_info,
+ .ext_info = ad7173_chan_spec_ext_info,
};
static const struct iio_chan_spec ad7173_temp_iio_channel_template = {
@@ -1412,6 +1582,7 @@ static const struct iio_chan_spec ad7173_temp_iio_channel_template = {
.storagebits = 32,
.endianness = IIO_BE,
},
+ .ext_info = ad7173_temp_chan_spec_ext_info,
};
static void ad7173_disable_regulators(void *data)
@@ -1652,12 +1823,21 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev)
chan_st_priv->cfg.bipolar = false;
chan_st_priv->cfg.input_buf = st->info->has_input_buf;
chan_st_priv->cfg.ref_sel = AD7173_SETUP_REF_SEL_INT_REF;
- chan_st_priv->cfg.odr = st->info->odr_start_value;
+ chan_st_priv->cfg.sinc3_odr_div = ad7173_sinc3_odr_div_from_odr(
+ st->info->sinc5_data_rates[st->info->odr_start_value]
+ );
+ chan_st_priv->cfg.sinc5_odr_index = st->info->odr_start_value;
+ chan_st_priv->cfg.filter_type = AD7173_FILTER_SINC5_SINC1;
chan_st_priv->cfg.openwire_comp_chan = -1;
st->adc_mode |= AD7173_ADC_MODE_REF_EN;
if (st->info->data_reg_only_16bit)
chan_arr[chan_index].scan_type = ad4113_scan_type;
+ if (ad_sigma_delta_has_spi_offload(&st->sd)) {
+ chan_arr[chan_index].scan_type.storagebits = 32;
+ chan_arr[chan_index].scan_type.endianness = IIO_CPU;
+ }
+
chan_index++;
}
@@ -1719,7 +1899,11 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev)
chan->scan_index = chan_index;
chan->channel = ain[0];
chan_st_priv->cfg.input_buf = st->info->has_input_buf;
- chan_st_priv->cfg.odr = st->info->odr_start_value;
+ chan_st_priv->cfg.sinc3_odr_div = ad7173_sinc3_odr_div_from_odr(
+ st->info->sinc5_data_rates[st->info->odr_start_value]
+ );
+ chan_st_priv->cfg.sinc5_odr_index = st->info->odr_start_value;
+ chan_st_priv->cfg.filter_type = AD7173_FILTER_SINC5_SINC1;
chan_st_priv->cfg.openwire_comp_chan = -1;
chan_st_priv->cfg.bipolar = fwnode_property_read_bool(child, "bipolar");
@@ -1748,6 +1932,12 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev)
if (st->info->data_reg_only_16bit)
chan_arr[chan_index].scan_type = ad4113_scan_type;
+ /* Assuming SPI offload is ad411x_ad717x HDL project. */
+ if (ad_sigma_delta_has_spi_offload(&st->sd)) {
+ chan_arr[chan_index].scan_type.storagebits = 32;
+ chan_arr[chan_index].scan_type.endianness = IIO_CPU;
+ }
+
chan_index++;
}
return 0;
@@ -1780,8 +1970,7 @@ static int ad7173_fw_parse_device_config(struct iio_dev *indio_dev)
ret = devm_add_action_or_reset(dev, ad7173_disable_regulators, st);
if (ret)
- return dev_err_probe(dev, ret,
- "Failed to add regulators disable action\n");
+ return ret;
ret = device_property_match_property_string(dev, "clock-names",
ad7173_clk_sel,
diff --git a/drivers/iio/adc/ad7476.c b/drivers/iio/adc/ad7476.c
index aea734aa06bd..1bec6657394c 100644
--- a/drivers/iio/adc/ad7476.c
+++ b/drivers/iio/adc/ad7476.c
@@ -6,6 +6,7 @@
* Copyright 2010 Analog Devices Inc.
*/
+#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/slab.h>
@@ -27,22 +28,24 @@
struct ad7476_state;
struct ad7476_chip_info {
- unsigned int int_vref_uv;
+ unsigned int int_vref_mv;
struct iio_chan_spec channel[2];
- /* channels used when convst gpio is defined */
- struct iio_chan_spec convst_channel[2];
void (*reset)(struct ad7476_state *);
+ void (*conversion_pre_op)(struct ad7476_state *st);
+ void (*conversion_post_op)(struct ad7476_state *st);
bool has_vref;
bool has_vdrive;
+ bool convstart_required;
};
struct ad7476_state {
struct spi_device *spi;
const struct ad7476_chip_info *chip_info;
- struct regulator *ref_reg;
struct gpio_desc *convst_gpio;
struct spi_transfer xfer;
struct spi_message msg;
+ struct iio_chan_spec channel[2];
+ int scale_mv;
/*
* DMA (thus cache coherency maintenance) may require the
* transfer buffers to live in their own cache lines.
@@ -52,40 +55,29 @@ struct ad7476_state {
unsigned char data[ALIGN(2, sizeof(s64)) + sizeof(s64)] __aligned(IIO_DMA_MINALIGN);
};
-enum ad7476_supported_device_ids {
- ID_AD7091,
- ID_AD7091R,
- ID_AD7273,
- ID_AD7274,
- ID_AD7276,
- ID_AD7277,
- ID_AD7278,
- ID_AD7466,
- ID_AD7467,
- ID_AD7468,
- ID_AD7475,
- ID_AD7495,
- ID_AD7940,
- ID_ADC081S,
- ID_ADC101S,
- ID_ADC121S,
- ID_ADS7866,
- ID_ADS7867,
- ID_ADS7868,
- ID_LTC2314_14,
-};
-
static void ad7091_convst(struct ad7476_state *st)
{
if (!st->convst_gpio)
return;
- gpiod_set_value(st->convst_gpio, 0);
+ gpiod_set_value_cansleep(st->convst_gpio, 0);
udelay(1); /* CONVST pulse width: 10 ns min */
- gpiod_set_value(st->convst_gpio, 1);
+ gpiod_set_value_cansleep(st->convst_gpio, 1);
udelay(1); /* Conversion time: 650 ns max */
}
+static void bd79105_convst_disable(struct ad7476_state *st)
+{
+ gpiod_set_value_cansleep(st->convst_gpio, 0);
+}
+
+static void bd79105_convst_enable(struct ad7476_state *st)
+{
+ gpiod_set_value_cansleep(st->convst_gpio, 1);
+ /* Worst case, 2790 ns required for conversion */
+ ndelay(2790);
+}
+
static irqreturn_t ad7476_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
@@ -93,7 +85,8 @@ static irqreturn_t ad7476_trigger_handler(int irq, void *p)
struct ad7476_state *st = iio_priv(indio_dev);
int b_sent;
- ad7091_convst(st);
+ if (st->chip_info->conversion_pre_op)
+ st->chip_info->conversion_pre_op(st);
b_sent = spi_sync(st->spi, &st->msg);
if (b_sent < 0)
@@ -102,6 +95,8 @@ static irqreturn_t ad7476_trigger_handler(int irq, void *p)
iio_push_to_buffers_with_ts(indio_dev, st->data, sizeof(st->data),
iio_get_time_ns(indio_dev));
done:
+ if (st->chip_info->conversion_post_op)
+ st->chip_info->conversion_post_op(st);
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
@@ -117,12 +112,16 @@ static int ad7476_scan_direct(struct ad7476_state *st)
{
int ret;
- ad7091_convst(st);
+ if (st->chip_info->conversion_pre_op)
+ st->chip_info->conversion_pre_op(st);
ret = spi_sync(st->spi, &st->msg);
if (ret)
return ret;
+ if (st->chip_info->conversion_post_op)
+ st->chip_info->conversion_post_op(st);
+
return be16_to_cpup((__be16 *)st->data);
}
@@ -134,7 +133,6 @@ static int ad7476_read_raw(struct iio_dev *indio_dev,
{
int ret;
struct ad7476_state *st = iio_priv(indio_dev);
- int scale_uv;
switch (m) {
case IIO_CHAN_INFO_RAW:
@@ -145,18 +143,11 @@ static int ad7476_read_raw(struct iio_dev *indio_dev,
if (ret < 0)
return ret;
- *val = (ret >> st->chip_info->channel[0].scan_type.shift) &
- GENMASK(st->chip_info->channel[0].scan_type.realbits - 1, 0);
+ *val = (ret >> chan->scan_type.shift) &
+ GENMASK(chan->scan_type.realbits - 1, 0);
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
- if (st->ref_reg) {
- scale_uv = regulator_get_voltage(st->ref_reg);
- if (scale_uv < 0)
- return scale_uv;
- } else {
- scale_uv = st->chip_info->int_vref_uv;
- }
- *val = scale_uv / 1000;
+ *val = st->scale_mv;
*val2 = chan->scan_type.realbits;
return IIO_VAL_FRACTIONAL_LOG2;
}
@@ -185,125 +176,147 @@ static int ad7476_read_raw(struct iio_dev *indio_dev,
#define AD7940_CHAN(bits) _AD7476_CHAN((bits), 15 - (bits), \
BIT(IIO_CHAN_INFO_RAW))
#define AD7091R_CHAN(bits) _AD7476_CHAN((bits), 16 - (bits), 0)
-#define AD7091R_CONVST_CHAN(bits) _AD7476_CHAN((bits), 16 - (bits), \
- BIT(IIO_CHAN_INFO_RAW))
#define ADS786X_CHAN(bits) _AD7476_CHAN((bits), 12 - (bits), \
BIT(IIO_CHAN_INFO_RAW))
-static const struct ad7476_chip_info ad7476_chip_info_tbl[] = {
- [ID_AD7091] = {
- .channel[0] = AD7091R_CHAN(12),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- .convst_channel[0] = AD7091R_CONVST_CHAN(12),
- .convst_channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- .reset = ad7091_reset,
- },
- [ID_AD7091R] = {
- .channel[0] = AD7091R_CHAN(12),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- .convst_channel[0] = AD7091R_CONVST_CHAN(12),
- .convst_channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- .int_vref_uv = 2500000,
- .has_vref = true,
- .reset = ad7091_reset,
- },
- [ID_AD7273] = {
- .channel[0] = AD7940_CHAN(10),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- .has_vref = true,
- },
- [ID_AD7274] = {
- .channel[0] = AD7940_CHAN(12),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- .has_vref = true,
- },
- [ID_AD7276] = {
- .channel[0] = AD7940_CHAN(12),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- },
- [ID_AD7277] = {
- .channel[0] = AD7940_CHAN(10),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- },
- [ID_AD7278] = {
- .channel[0] = AD7940_CHAN(8),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- },
- [ID_AD7466] = {
- .channel[0] = AD7476_CHAN(12),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- },
- [ID_AD7467] = {
- .channel[0] = AD7476_CHAN(10),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- },
- [ID_AD7468] = {
- .channel[0] = AD7476_CHAN(8),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- },
- [ID_AD7475] = {
- .channel[0] = AD7476_CHAN(12),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- .has_vref = true,
- .has_vdrive = true,
- },
- [ID_AD7495] = {
- .channel[0] = AD7476_CHAN(12),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- .int_vref_uv = 2500000,
- .has_vdrive = true,
- },
- [ID_AD7940] = {
- .channel[0] = AD7940_CHAN(14),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- },
- [ID_ADC081S] = {
- .channel[0] = ADC081S_CHAN(8),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- },
- [ID_ADC101S] = {
- .channel[0] = ADC081S_CHAN(10),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- },
- [ID_ADC121S] = {
- .channel[0] = ADC081S_CHAN(12),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- },
- [ID_ADS7866] = {
- .channel[0] = ADS786X_CHAN(12),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- },
- [ID_ADS7867] = {
- .channel[0] = ADS786X_CHAN(10),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- },
- [ID_ADS7868] = {
- .channel[0] = ADS786X_CHAN(8),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- },
- [ID_LTC2314_14] = {
- .channel[0] = AD7940_CHAN(14),
- .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
- .has_vref = true,
- },
+static const struct ad7476_chip_info ad7091_chip_info = {
+ .channel[0] = AD7091R_CHAN(12),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+ .conversion_pre_op = ad7091_convst,
+ .reset = ad7091_reset,
};
-static const struct iio_info ad7476_info = {
- .read_raw = &ad7476_read_raw,
+static const struct ad7476_chip_info ad7091r_chip_info = {
+ .channel[0] = AD7091R_CHAN(12),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+ .conversion_pre_op = ad7091_convst,
+ .int_vref_mv = 2500,
+ .has_vref = true,
+ .reset = ad7091_reset,
};
-static void ad7476_reg_disable(void *data)
-{
- struct regulator *reg = data;
+static const struct ad7476_chip_info ad7273_chip_info = {
+ .channel[0] = AD7940_CHAN(10),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+ .has_vref = true,
+};
- regulator_disable(reg);
-}
+static const struct ad7476_chip_info ad7274_chip_info = {
+ .channel[0] = AD7940_CHAN(12),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+ .has_vref = true,
+};
+
+static const struct ad7476_chip_info ad7276_chip_info = {
+ .channel[0] = AD7940_CHAN(12),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct ad7476_chip_info ad7277_chip_info = {
+ .channel[0] = AD7940_CHAN(10),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct ad7476_chip_info ad7278_chip_info = {
+ .channel[0] = AD7940_CHAN(8),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct ad7476_chip_info ad7466_chip_info = {
+ .channel[0] = AD7476_CHAN(12),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct ad7476_chip_info ad7467_chip_info = {
+ .channel[0] = AD7476_CHAN(10),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct ad7476_chip_info ad7468_chip_info = {
+ .channel[0] = AD7476_CHAN(8),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct ad7476_chip_info ad7475_chip_info = {
+ .channel[0] = AD7476_CHAN(12),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+ .has_vref = true,
+ .has_vdrive = true,
+};
+
+static const struct ad7476_chip_info ad7495_chip_info = {
+ .channel[0] = AD7476_CHAN(12),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+ .int_vref_mv = 2500,
+ .has_vdrive = true,
+};
+
+static const struct ad7476_chip_info ad7940_chip_info = {
+ .channel[0] = AD7940_CHAN(14),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct ad7476_chip_info adc081s_chip_info = {
+ .channel[0] = ADC081S_CHAN(8),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct ad7476_chip_info adc101s_chip_info = {
+ .channel[0] = ADC081S_CHAN(10),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct ad7476_chip_info adc121s_chip_info = {
+ .channel[0] = ADC081S_CHAN(12),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct ad7476_chip_info ads7866_chip_info = {
+ .channel[0] = ADS786X_CHAN(12),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct ad7476_chip_info ads7867_chip_info = {
+ .channel[0] = ADS786X_CHAN(10),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct ad7476_chip_info ads7868_chip_info = {
+ .channel[0] = ADS786X_CHAN(8),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct ad7476_chip_info ltc2314_14_chip_info = {
+ .channel[0] = AD7940_CHAN(14),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+ .has_vref = true,
+};
+
+static const struct ad7476_chip_info bd79105_chip_info = {
+ .channel[0] = AD7091R_CHAN(16),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+ /*
+ * The BD79105 starts ADC data conversion when the CONVSTART line is
+ * set HIGH. The CONVSTART must be kept HIGH until the data has been
+ * read from the ADC.
+ */
+ .conversion_pre_op = bd79105_convst_enable,
+ .conversion_post_op = bd79105_convst_disable,
+ /* BD79105 won't do conversion without convstart */
+ .convstart_required = true,
+ .has_vref = true,
+ .has_vdrive = true,
+};
+
+static const struct iio_info ad7476_info = {
+ .read_raw = &ad7476_read_raw,
+};
static int ad7476_probe(struct spi_device *spi)
{
struct ad7476_state *st;
struct iio_dev *indio_dev;
- struct regulator *reg;
+ unsigned int i;
int ret;
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
@@ -311,61 +324,37 @@ static int ad7476_probe(struct spi_device *spi)
return -ENOMEM;
st = iio_priv(indio_dev);
- st->chip_info =
- &ad7476_chip_info_tbl[spi_get_device_id(spi)->driver_data];
- reg = devm_regulator_get(&spi->dev, "vcc");
- if (IS_ERR(reg))
- return PTR_ERR(reg);
+ st->chip_info = spi_get_device_match_data(spi);
+ if (!st->chip_info)
+ return -ENODEV;
- ret = regulator_enable(reg);
- if (ret)
- return ret;
-
- ret = devm_add_action_or_reset(&spi->dev, ad7476_reg_disable, reg);
- if (ret)
- return ret;
-
- /* Either vcc or vref (below) as appropriate */
- if (!st->chip_info->int_vref_uv)
- st->ref_reg = reg;
+ /* Use VCC for reference voltage if vref / internal vref aren't used */
+ if (!st->chip_info->int_vref_mv && !st->chip_info->has_vref) {
+ ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vcc");
+ if (ret < 0)
+ return ret;
+ st->scale_mv = ret / 1000;
+ } else {
+ ret = devm_regulator_get_enable(&spi->dev, "vcc");
+ if (ret < 0)
+ return ret;
+ }
if (st->chip_info->has_vref) {
-
- /* If a device has an internal reference vref is optional */
- if (st->chip_info->int_vref_uv) {
- reg = devm_regulator_get_optional(&spi->dev, "vref");
- if (IS_ERR(reg) && (PTR_ERR(reg) != -ENODEV))
- return PTR_ERR(reg);
- } else {
- reg = devm_regulator_get(&spi->dev, "vref");
- if (IS_ERR(reg))
- return PTR_ERR(reg);
- }
-
- if (!IS_ERR(reg)) {
- ret = regulator_enable(reg);
- if (ret)
- return ret;
-
- ret = devm_add_action_or_reset(&spi->dev,
- ad7476_reg_disable,
- reg);
- if (ret)
+ ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref");
+ if (ret < 0) {
+ /* Vref is optional if a device has an internal reference */
+ if (!st->chip_info->int_vref_mv || ret != -ENODEV)
return ret;
- st->ref_reg = reg;
} else {
- /*
- * Can only get here if device supports both internal
- * and external reference, but the regulator connected
- * to the external reference is not connected.
- * Set the reference regulator pointer to NULL to
- * indicate this.
- */
- st->ref_reg = NULL;
+ st->scale_mv = ret / 1000;
}
}
+ if (!st->scale_mv)
+ st->scale_mv = st->chip_info->int_vref_mv;
+
if (st->chip_info->has_vdrive) {
ret = devm_regulator_get_enable(&spi->dev, "vdrive");
if (ret)
@@ -378,20 +367,35 @@ static int ad7476_probe(struct spi_device *spi)
if (IS_ERR(st->convst_gpio))
return PTR_ERR(st->convst_gpio);
+ if (st->chip_info->convstart_required && !st->convst_gpio)
+ return dev_err_probe(&spi->dev, -EINVAL, "No convstart GPIO\n");
+
+ /*
+ * This will never happen. Unless someone changes the channel specs
+ * in this driver. And if someone does, without changing the loop
+ * below, then we'd better immediately produce a big fat error, before
+ * the change proceeds from that developer's table.
+ */
+ static_assert(ARRAY_SIZE(st->channel) == ARRAY_SIZE(st->chip_info->channel));
+ for (i = 0; i < ARRAY_SIZE(st->channel); i++) {
+ st->channel[i] = st->chip_info->channel[i];
+ if (st->convst_gpio)
+ __set_bit(IIO_CHAN_INFO_RAW,
+ &st->channel[i].info_mask_separate);
+ }
+
st->spi = spi;
indio_dev->name = spi_get_device_id(spi)->name;
indio_dev->modes = INDIO_DIRECT_MODE;
- indio_dev->channels = st->chip_info->channel;
- indio_dev->num_channels = 2;
+ indio_dev->channels = st->channel;
+ indio_dev->num_channels = ARRAY_SIZE(st->channel);
indio_dev->info = &ad7476_info;
- if (st->convst_gpio)
- indio_dev->channels = st->chip_info->convst_channel;
/* Setup default message */
st->xfer.rx_buf = &st->data;
- st->xfer.len = st->chip_info->channel[0].scan_type.storagebits / 8;
+ st->xfer.len = indio_dev->channels[0].scan_type.storagebits / 8;
spi_message_init(&st->msg);
spi_message_add_tail(&st->xfer, &st->msg);
@@ -408,41 +412,42 @@ static int ad7476_probe(struct spi_device *spi)
}
static const struct spi_device_id ad7476_id[] = {
- { "ad7091", ID_AD7091 },
- { "ad7091r", ID_AD7091R },
- { "ad7273", ID_AD7273 },
- { "ad7274", ID_AD7274 },
- { "ad7276", ID_AD7276},
- { "ad7277", ID_AD7277 },
- { "ad7278", ID_AD7278 },
- { "ad7466", ID_AD7466 },
- { "ad7467", ID_AD7467 },
- { "ad7468", ID_AD7468 },
- { "ad7475", ID_AD7475 },
- { "ad7476", ID_AD7466 },
- { "ad7476a", ID_AD7466 },
- { "ad7477", ID_AD7467 },
- { "ad7477a", ID_AD7467 },
- { "ad7478", ID_AD7468 },
- { "ad7478a", ID_AD7468 },
- { "ad7495", ID_AD7495 },
- { "ad7910", ID_AD7467 },
- { "ad7920", ID_AD7466 },
- { "ad7940", ID_AD7940 },
- { "adc081s", ID_ADC081S },
- { "adc101s", ID_ADC101S },
- { "adc121s", ID_ADC121S },
- { "ads7866", ID_ADS7866 },
- { "ads7867", ID_ADS7867 },
- { "ads7868", ID_ADS7868 },
+ { "ad7091", (kernel_ulong_t)&ad7091_chip_info },
+ { "ad7091r", (kernel_ulong_t)&ad7091r_chip_info },
+ { "ad7273", (kernel_ulong_t)&ad7273_chip_info },
+ { "ad7274", (kernel_ulong_t)&ad7274_chip_info },
+ { "ad7276", (kernel_ulong_t)&ad7276_chip_info },
+ { "ad7277", (kernel_ulong_t)&ad7277_chip_info },
+ { "ad7278", (kernel_ulong_t)&ad7278_chip_info },
+ { "ad7466", (kernel_ulong_t)&ad7466_chip_info },
+ { "ad7467", (kernel_ulong_t)&ad7467_chip_info },
+ { "ad7468", (kernel_ulong_t)&ad7468_chip_info },
+ { "ad7475", (kernel_ulong_t)&ad7475_chip_info },
+ { "ad7476", (kernel_ulong_t)&ad7466_chip_info },
+ { "ad7476a", (kernel_ulong_t)&ad7466_chip_info },
+ { "ad7477", (kernel_ulong_t)&ad7467_chip_info },
+ { "ad7477a", (kernel_ulong_t)&ad7467_chip_info },
+ { "ad7478", (kernel_ulong_t)&ad7468_chip_info },
+ { "ad7478a", (kernel_ulong_t)&ad7468_chip_info },
+ { "ad7495", (kernel_ulong_t)&ad7495_chip_info },
+ { "ad7910", (kernel_ulong_t)&ad7467_chip_info },
+ { "ad7920", (kernel_ulong_t)&ad7466_chip_info },
+ { "ad7940", (kernel_ulong_t)&ad7940_chip_info },
+ { "adc081s", (kernel_ulong_t)&adc081s_chip_info },
+ { "adc101s", (kernel_ulong_t)&adc101s_chip_info },
+ { "adc121s", (kernel_ulong_t)&adc121s_chip_info },
+ { "ads7866", (kernel_ulong_t)&ads7866_chip_info },
+ { "ads7867", (kernel_ulong_t)&ads7867_chip_info },
+ { "ads7868", (kernel_ulong_t)&ads7868_chip_info },
+ { "bd79105", (kernel_ulong_t)&bd79105_chip_info },
/*
* The ROHM BU79100G is identical to the TI's ADS7866 from the software
* point of view. The binding document mandates the ADS7866 to be
* marked as a fallback for the BU79100G, but we still need the SPI ID
* here to make the module loading work.
*/
- { "bu79100g", ID_ADS7866 },
- { "ltc2314-14", ID_LTC2314_14 },
+ { "bu79100g", (kernel_ulong_t)&ads7866_chip_info },
+ { "ltc2314-14", (kernel_ulong_t)&ltc2314_14_chip_info },
{ }
};
MODULE_DEVICE_TABLE(spi, ad7476_id);
diff --git a/drivers/iio/adc/ad7768-1.c b/drivers/iio/adc/ad7768-1.c
index ca8fa91796ca..872c88d0c86c 100644
--- a/drivers/iio/adc/ad7768-1.c
+++ b/drivers/iio/adc/ad7768-1.c
@@ -217,7 +217,7 @@ struct ad7768_state {
struct spi_device *spi;
struct regmap *regmap;
struct regmap *regmap24;
- struct regulator *vref;
+ int vref_uv;
struct regulator_dev *vcm_rdev;
unsigned int vcm_output_sel;
struct clk *mclk;
@@ -687,8 +687,6 @@ static int ad7768_set_freq(struct ad7768_state *st,
int ret;
freq = clamp(freq, 50, 1024000);
- if (freq == 0)
- return -EINVAL;
mclk_div = DIV_ROUND_CLOSEST(st->mclk_freq, freq * st->oversampling_ratio);
/* Find the closest match for the desired sampling frequency */
@@ -776,7 +774,7 @@ static int ad7768_read_raw(struct iio_dev *indio_dev,
{
struct ad7768_state *st = iio_priv(indio_dev);
const struct iio_scan_type *scan_type;
- int scale_uv, ret, temp;
+ int ret, temp;
scan_type = iio_get_current_scan_type(indio_dev, chan);
if (IS_ERR(scan_type))
@@ -797,11 +795,7 @@ static int ad7768_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
- scale_uv = regulator_get_voltage(st->vref);
- if (scale_uv < 0)
- return scale_uv;
-
- *val = (scale_uv * 2) / 1000;
+ *val = (st->vref_uv * 2) / 1000;
*val2 = scan_type->realbits;
return IIO_VAL_FRACTIONAL_LOG2;
@@ -1134,13 +1128,6 @@ static const struct iio_trigger_ops ad7768_trigger_ops = {
.validate_device = iio_trigger_validate_own_device,
};
-static void ad7768_regulator_disable(void *data)
-{
- struct ad7768_state *st = data;
-
- regulator_disable(st->vref);
-}
-
static int ad7768_set_channel_label(struct iio_dev *indio_dev,
int num_channels)
{
@@ -1372,19 +1359,11 @@ static int ad7768_probe(struct spi_device *spi)
return dev_err_probe(&spi->dev, PTR_ERR(st->regmap24),
"Failed to initialize regmap24");
- st->vref = devm_regulator_get(&spi->dev, "vref");
- if (IS_ERR(st->vref))
- return PTR_ERR(st->vref);
-
- ret = regulator_enable(st->vref);
- if (ret) {
- dev_err(&spi->dev, "Failed to enable specified vref supply\n");
- return ret;
- }
-
- ret = devm_add_action_or_reset(&spi->dev, ad7768_regulator_disable, st);
- if (ret)
- return ret;
+ ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref");
+ if (ret < 0)
+ return dev_err_probe(&spi->dev, ret,
+ "Failed to get VREF voltage\n");
+ st->vref_uv = ret;
st->mclk = devm_clk_get_enabled(&spi->dev, "mclk");
if (IS_ERR(st->mclk))
diff --git a/drivers/iio/adc/ad7779.c b/drivers/iio/adc/ad7779.c
index 845adc510239..aac5049c9a07 100644
--- a/drivers/iio/adc/ad7779.c
+++ b/drivers/iio/adc/ad7779.c
@@ -25,6 +25,7 @@
#include <linux/units.h>
#include <linux/iio/iio.h>
+#include <linux/iio/backend.h>
#include <linux/iio/buffer.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/trigger.h>
@@ -145,6 +146,7 @@ struct ad7779_state {
struct completion completion;
unsigned int sampling_freq;
enum ad7779_filter filter_enabled;
+ struct iio_backend *back;
/*
* DMA (thus cache coherency maintenance) requires the
* transfer buffers to live in their own cache lines.
@@ -630,12 +632,38 @@ static int ad7779_reset(struct iio_dev *indio_dev, struct gpio_desc *reset_gpio)
return ret;
}
+static int ad7779_update_scan_mode(struct iio_dev *indio_dev,
+ const unsigned long *scan_mask)
+{
+ struct ad7779_state *st = iio_priv(indio_dev);
+ unsigned int c;
+ int ret;
+
+ for (c = 0; c < AD7779_NUM_CHANNELS; c++) {
+ if (test_bit(c, scan_mask))
+ ret = iio_backend_chan_enable(st->back, c);
+ else
+ ret = iio_backend_chan_disable(st->back, c);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static const struct iio_info ad7779_info = {
.read_raw = ad7779_read_raw,
.write_raw = ad7779_write_raw,
.debugfs_reg_access = &ad7779_reg_access,
};
+static const struct iio_info ad7779_info_data = {
+ .read_raw = ad7779_read_raw,
+ .write_raw = ad7779_write_raw,
+ .debugfs_reg_access = &ad7779_reg_access,
+ .update_scan_mode = &ad7779_update_scan_mode,
+};
+
static const struct iio_enum ad7779_filter_enum = {
.items = ad7779_filter_type,
.num_items = ARRAY_SIZE(ad7779_filter_type),
@@ -752,6 +780,125 @@ static int ad7779_conf(struct ad7779_state *st, struct gpio_desc *start_gpio)
return 0;
}
+static int ad7779_set_data_lines(struct iio_dev *indio_dev, u32 num_lanes)
+{
+ struct ad7779_state *st = iio_priv(indio_dev);
+ int ret;
+
+ if (num_lanes != 1 && num_lanes != 2 && num_lanes != 4)
+ return -EINVAL;
+
+ ret = ad7779_set_sampling_frequency(st, num_lanes * AD7779_DEFAULT_SAMPLING_1LINE);
+ if (ret)
+ return ret;
+
+ ret = iio_backend_num_lanes_set(st->back, num_lanes);
+ if (ret)
+ return ret;
+
+ return ad7779_spi_write_mask(st, AD7779_REG_DOUT_FORMAT,
+ AD7779_DOUT_FORMAT_MSK,
+ FIELD_PREP(AD7779_DOUT_FORMAT_MSK, 2 - ilog2(num_lanes)));
+}
+
+static int ad7779_setup_channels(struct iio_dev *indio_dev, const struct ad7779_state *st)
+{
+ struct iio_chan_spec *channels;
+ struct device *dev = &st->spi->dev;
+
+ channels = devm_kmemdup_array(dev, st->chip_info->channels,
+ ARRAY_SIZE(ad7779_channels),
+ sizeof(*channels), GFP_KERNEL);
+ if (!channels)
+ return -ENOMEM;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(ad7779_channels); i++)
+ channels[i].scan_type.endianness = IIO_CPU;
+
+ indio_dev->channels = channels;
+ indio_dev->num_channels = ARRAY_SIZE(ad7779_channels);
+
+ return 0;
+}
+
+static int ad7779_setup_without_backend(struct ad7779_state *st, struct iio_dev *indio_dev)
+{
+ int ret;
+ struct device *dev = &st->spi->dev;
+
+ indio_dev->info = &ad7779_info;
+ indio_dev->channels = st->chip_info->channels;
+ indio_dev->num_channels = ARRAY_SIZE(ad7779_channels);
+
+ st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!st->trig)
+ return -ENOMEM;
+
+ st->trig->ops = &ad7779_trigger_ops;
+
+ iio_trigger_set_drvdata(st->trig, st);
+
+ ret = devm_request_irq(dev, st->spi->irq, iio_trigger_generic_data_rdy_poll,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN, indio_dev->name,
+ st->trig);
+ if (ret)
+ return dev_err_probe(dev, ret, "request IRQ %d failed\n",
+ st->spi->irq);
+
+ ret = devm_iio_trigger_register(dev, st->trig);
+ if (ret)
+ return ret;
+
+ indio_dev->trig = iio_trigger_get(st->trig);
+
+ init_completion(&st->completion);
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ &iio_pollfunc_store_time,
+ &ad7779_trigger_handler,
+ &ad7779_buffer_setup_ops);
+ if (ret)
+ return ret;
+
+ return ad7779_spi_write_mask(st, AD7779_REG_DOUT_FORMAT,
+ AD7779_DCLK_CLK_DIV_MSK,
+ FIELD_PREP(AD7779_DCLK_CLK_DIV_MSK, 7));
+}
+
+static int ad7779_setup_backend(struct ad7779_state *st, struct iio_dev *indio_dev)
+{
+ struct device *dev = &st->spi->dev;
+ int ret;
+ u32 num_lanes;
+
+ indio_dev->info = &ad7779_info_data;
+
+ ret = ad7779_setup_channels(indio_dev, st);
+ if (ret)
+ return ret;
+
+ st->back = devm_iio_backend_get(dev, NULL);
+ if (IS_ERR(st->back))
+ return dev_err_probe(dev, PTR_ERR(st->back),
+ "failed to get iio backend");
+
+ ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_backend_enable(dev, st->back);
+ if (ret)
+ return ret;
+
+ num_lanes = 4;
+ ret = device_property_read_u32(dev, "adi,num-lanes", &num_lanes);
+ if (ret && ret != -EINVAL)
+ return ret;
+
+ return ad7779_set_data_lines(indio_dev, num_lanes);
+}
+
static int ad7779_probe(struct spi_device *spi)
{
struct iio_dev *indio_dev;
@@ -760,9 +907,6 @@ static int ad7779_probe(struct spi_device *spi)
struct device *dev = &spi->dev;
int ret = -EINVAL;
- if (!spi->irq)
- return dev_err_probe(dev, ret, "DRDY irq not present\n");
-
indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
if (!indio_dev)
return -ENOMEM;
@@ -804,45 +948,12 @@ static int ad7779_probe(struct spi_device *spi)
return ret;
indio_dev->name = st->chip_info->name;
- indio_dev->info = &ad7779_info;
indio_dev->modes = INDIO_DIRECT_MODE;
- indio_dev->channels = st->chip_info->channels;
- indio_dev->num_channels = ARRAY_SIZE(ad7779_channels);
- st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
- iio_device_id(indio_dev));
- if (!st->trig)
- return -ENOMEM;
-
- st->trig->ops = &ad7779_trigger_ops;
-
- iio_trigger_set_drvdata(st->trig, st);
-
- ret = devm_request_irq(dev, spi->irq, iio_trigger_generic_data_rdy_poll,
- IRQF_ONESHOT | IRQF_NO_AUTOEN, indio_dev->name,
- st->trig);
- if (ret)
- return dev_err_probe(dev, ret, "request IRQ %d failed\n",
- st->spi->irq);
-
- ret = devm_iio_trigger_register(dev, st->trig);
- if (ret)
- return ret;
-
- indio_dev->trig = iio_trigger_get(st->trig);
-
- init_completion(&st->completion);
-
- ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
- &iio_pollfunc_store_time,
- &ad7779_trigger_handler,
- &ad7779_buffer_setup_ops);
- if (ret)
- return ret;
-
- ret = ad7779_spi_write_mask(st, AD7779_REG_DOUT_FORMAT,
- AD7779_DCLK_CLK_DIV_MSK,
- FIELD_PREP(AD7779_DCLK_CLK_DIV_MSK, 7));
+ if (device_property_present(dev, "io-backends"))
+ ret = ad7779_setup_backend(st, indio_dev);
+ else
+ ret = ad7779_setup_without_backend(st, indio_dev);
if (ret)
return ret;
@@ -936,3 +1047,4 @@ module_spi_driver(ad7779_driver);
MODULE_AUTHOR("Ramona Alexandra Nechita <ramona.nechita@analog.com>");
MODULE_DESCRIPTION("Analog Devices AD7779 ADC");
MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_BACKEND");
diff --git a/drivers/iio/adc/ad7949.c b/drivers/iio/adc/ad7949.c
index 202561cad401..b35d299a3977 100644
--- a/drivers/iio/adc/ad7949.c
+++ b/drivers/iio/adc/ad7949.c
@@ -316,10 +316,8 @@ static int ad7949_spi_probe(struct spi_device *spi)
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*ad7949_adc));
- if (!indio_dev) {
- dev_err(dev, "can not allocate iio device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
indio_dev->info = &ad7949_spi_info;
indio_dev->name = spi_get_device_id(spi)->name;
diff --git a/drivers/iio/adc/ad799x.c b/drivers/iio/adc/ad799x.c
index 9c02f9199139..108bb22162ef 100644
--- a/drivers/iio/adc/ad799x.c
+++ b/drivers/iio/adc/ad799x.c
@@ -114,11 +114,13 @@ struct ad799x_chip_config {
* @num_channels: number of channels
* @noirq_config: device configuration w/o IRQ
* @irq_config: device configuration w/IRQ
+ * @has_vref: device supports external reference voltage
*/
struct ad799x_chip_info {
int num_channels;
const struct ad799x_chip_config noirq_config;
const struct ad799x_chip_config irq_config;
+ bool has_vref;
};
struct ad799x_state {
@@ -604,6 +606,7 @@ static const struct iio_event_spec ad799x_events[] = {
static const struct ad799x_chip_info ad799x_chip_info_tbl[] = {
[ad7991] = {
.num_channels = 5,
+ .has_vref = true,
.noirq_config = {
.channel = {
AD799X_CHANNEL(0, 12),
@@ -617,6 +620,7 @@ static const struct ad799x_chip_info ad799x_chip_info_tbl[] = {
},
[ad7995] = {
.num_channels = 5,
+ .has_vref = true,
.noirq_config = {
.channel = {
AD799X_CHANNEL(0, 10),
@@ -630,6 +634,7 @@ static const struct ad799x_chip_info ad799x_chip_info_tbl[] = {
},
[ad7999] = {
.num_channels = 5,
+ .has_vref = true,
.noirq_config = {
.channel = {
AD799X_CHANNEL(0, 8),
@@ -687,6 +692,7 @@ static const struct ad799x_chip_info ad799x_chip_info_tbl[] = {
},
[ad7994] = {
.num_channels = 5,
+ .has_vref = true,
.noirq_config = {
.channel = {
AD799X_CHANNEL(0, 12),
@@ -809,32 +815,22 @@ static int ad799x_probe(struct i2c_client *client)
return ret;
/* check if an external reference is supplied */
- st->vref = devm_regulator_get_optional(&client->dev, "vref");
-
- if (IS_ERR(st->vref)) {
- if (PTR_ERR(st->vref) == -ENODEV) {
+ if (chip_info->has_vref) {
+ st->vref = devm_regulator_get_optional(&client->dev, "vref");
+ ret = PTR_ERR_OR_ZERO(st->vref);
+ if (ret) {
+ if (ret != -ENODEV)
+ goto error_disable_reg;
st->vref = NULL;
dev_info(&client->dev, "Using VCC reference voltage\n");
- } else {
- ret = PTR_ERR(st->vref);
- goto error_disable_reg;
}
- }
- if (st->vref) {
- /*
- * Use external reference voltage if supported by hardware.
- * This is optional if voltage / regulator present, use VCC otherwise.
- */
- if ((st->id == ad7991) || (st->id == ad7995) || (st->id == ad7999)) {
+ if (st->vref) {
dev_info(&client->dev, "Using external reference voltage\n");
extra_config |= AD7991_REF_SEL;
ret = regulator_enable(st->vref);
if (ret)
goto error_disable_reg;
- } else {
- st->vref = NULL;
- dev_warn(&client->dev, "Supplied reference not supported\n");
}
}
diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c
new file mode 100644
index 000000000000..94e05e11abd9
--- /dev/null
+++ b/drivers/iio/adc/ade9000.c
@@ -0,0 +1,1799 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/**
+ * ADE9000 driver
+ *
+ * Copyright 2025 Analog Devices Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/kfifo_buf.h>
+#include <linux/iio/events.h>
+#include <linux/interrupt.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/unaligned.h>
+
+/* Address of ADE9000 registers */
+#define ADE9000_REG_AIGAIN 0x000
+#define ADE9000_REG_AVGAIN 0x00B
+#define ADE9000_REG_AIRMSOS 0x00C
+#define ADE9000_REG_AVRMSOS 0x00D
+#define ADE9000_REG_APGAIN 0x00E
+#define ADE9000_REG_AWATTOS 0x00F
+#define ADE9000_REG_AVAROS 0x010
+#define ADE9000_REG_AFVAROS 0x012
+#define ADE9000_REG_CONFIG0 0x060
+#define ADE9000_REG_DICOEFF 0x072
+#define ADE9000_REG_AI_PCF 0x20A
+#define ADE9000_REG_AV_PCF 0x20B
+#define ADE9000_REG_AIRMS 0x20C
+#define ADE9000_REG_AVRMS 0x20D
+#define ADE9000_REG_AWATT 0x210
+#define ADE9000_REG_AVAR 0x211
+#define ADE9000_REG_AVA 0x212
+#define ADE9000_REG_AFVAR 0x214
+#define ADE9000_REG_APF 0x216
+#define ADE9000_REG_BI_PCF 0x22A
+#define ADE9000_REG_BV_PCF 0x22B
+#define ADE9000_REG_BIRMS 0x22C
+#define ADE9000_REG_BVRMS 0x22D
+#define ADE9000_REG_CI_PCF 0x24A
+#define ADE9000_REG_CV_PCF 0x24B
+#define ADE9000_REG_CIRMS 0x24C
+#define ADE9000_REG_CVRMS 0x24D
+#define ADE9000_REG_AWATT_ACC 0x2E5
+#define ADE9000_REG_AWATTHR_LO 0x2E6
+#define ADE9000_REG_AVAHR_LO 0x2FA
+#define ADE9000_REG_AFVARHR_LO 0x30E
+#define ADE9000_REG_BWATTHR_LO 0x322
+#define ADE9000_REG_BVAHR_LO 0x336
+#define ADE9000_REG_BFVARHR_LO 0x34A
+#define ADE9000_REG_CWATTHR_LO 0x35E
+#define ADE9000_REG_CVAHR_LO 0x372
+#define ADE9000_REG_CFVARHR_LO 0x386
+#define ADE9000_REG_STATUS0 0x402
+#define ADE9000_REG_STATUS1 0x403
+#define ADE9000_REG_MASK0 0x405
+#define ADE9000_REG_MASK1 0x406
+#define ADE9000_REG_EVENT_MASK 0x407
+#define ADE9000_REG_VLEVEL 0x40F
+#define ADE9000_REG_DIP_LVL 0x410
+#define ADE9000_REG_DIPA 0x411
+#define ADE9000_REG_DIPB 0x412
+#define ADE9000_REG_DIPC 0x413
+#define ADE9000_REG_SWELL_LVL 0x414
+#define ADE9000_REG_SWELLA 0x415
+#define ADE9000_REG_SWELLB 0x416
+#define ADE9000_REG_SWELLC 0x417
+#define ADE9000_REG_APERIOD 0x418
+#define ADE9000_REG_BPERIOD 0x419
+#define ADE9000_REG_CPERIOD 0x41A
+#define ADE9000_REG_RUN 0x480
+#define ADE9000_REG_CONFIG1 0x481
+#define ADE9000_REG_ACCMODE 0x492
+#define ADE9000_REG_CONFIG3 0x493
+#define ADE9000_REG_ZXTOUT 0x498
+#define ADE9000_REG_ZX_LP_SEL 0x49A
+#define ADE9000_REG_WFB_CFG 0x4A0
+#define ADE9000_REG_WFB_PG_IRQEN 0x4A1
+#define ADE9000_REG_WFB_TRG_CFG 0x4A2
+#define ADE9000_REG_WFB_TRG_STAT 0x4A3
+#define ADE9000_REG_CONFIG2 0x4AF
+#define ADE9000_REG_EP_CFG 0x4B0
+#define ADE9000_REG_EGY_TIME 0x4B2
+#define ADE9000_REG_PGA_GAIN 0x4B9
+#define ADE9000_REG_VERSION 0x4FE
+#define ADE9000_REG_WF_BUFF 0x800
+#define ADE9000_REG_WF_HALF_BUFF 0xC00
+
+#define ADE9000_REG_ADDR_MASK GENMASK(15, 4)
+#define ADE9000_REG_READ_BIT_MASK BIT(3)
+
+#define ADE9000_WF_CAP_EN_MASK BIT(4)
+#define ADE9000_WF_CAP_SEL_MASK BIT(5)
+#define ADE9000_WF_MODE_MASK GENMASK(7, 6)
+#define ADE9000_WF_SRC_MASK GENMASK(9, 8)
+#define ADE9000_WF_IN_EN_MASK BIT(12)
+
+/* External reference selection bit in CONFIG1 */
+#define ADE9000_EXT_REF_MASK BIT(15)
+
+/*
+ * Configuration registers
+ */
+#define ADE9000_PGA_GAIN 0x0000
+
+/* Default configuration */
+
+#define ADE9000_CONFIG0 0x00000000
+
+/* CF3/ZX pin outputs Zero crossing, CF4 = DREADY */
+#define ADE9000_CONFIG1 0x000E
+
+/* Default High pass corner frequency of 1.25Hz */
+#define ADE9000_CONFIG2 0x0A00
+
+/* Peak and overcurrent detection disabled */
+#define ADE9000_CONFIG3 0x0000
+
+/*
+ * 50Hz operation, 3P4W Wye configuration, signed accumulation
+ * 3P4W Wye = 3-Phase 4-Wire star configuration (3 phases + neutral wire)
+ * Clear bit 8 i.e. ACCMODE=0x00xx for 50Hz operation
+ * ACCMODE=0x0x9x for 3Wire delta when phase B is used as reference
+ * 3Wire delta = 3-Phase 3-Wire triangle configuration (3 phases, no neutral)
+ */
+#define ADE9000_ACCMODE 0x0000
+#define ADE9000_ACCMODE_60HZ 0x0100
+
+/*Line period and zero crossing obtained from VA */
+#define ADE9000_ZX_LP_SEL 0x0000
+
+/* Interrupt mask values for initialization */
+#define ADE9000_MASK0_ALL_INT_DIS 0
+#define ADE9000_MASK1_ALL_INT_DIS 0x00000000
+
+/* Events disabled */
+#define ADE9000_EVENT_DISABLE 0x00000000
+
+/*
+ * Assuming Vnom=1/2 of full scale.
+ * Refer to Technical reference manual for detailed calculations.
+ */
+#define ADE9000_VLEVEL 0x0022EA28
+
+/* Set DICOEFF= 0xFFFFE000 when integrator is enabled */
+#define ADE9000_DICOEFF 0x00000000
+
+/* DSP ON */
+#define ADE9000_RUN_ON 0xFFFFFFFF
+
+/*
+ * Energy Accumulation Settings
+ * Enable energy accumulation, accumulate samples at 8ksps
+ * latch energy accumulation after EGYRDY
+ * If accumulation is changed to half line cycle mode, change EGY_TIME
+ */
+#define ADE9000_EP_CFG 0x0011
+
+/* Accumulate 4000 samples */
+#define ADE9000_EGY_TIME 7999
+
+/*
+ * Constant Definitions
+ * ADE9000 FDSP: 8000sps, ADE9000 FDSP: 4000sps
+ */
+#define ADE9000_FDSP 4000
+#define ADE9000_DEFAULT_CLK_FREQ_HZ 24576000
+#define ADE9000_WFB_CFG 0x03E9
+#define ADE9000_WFB_PAGE_SIZE 128
+#define ADE9000_WFB_NR_OF_PAGES 16
+#define ADE9000_WFB_MAX_CHANNELS 8
+#define ADE9000_WFB_BYTES_IN_SAMPLE 4
+#define ADE9000_WFB_SAMPLES_IN_PAGE \
+ (ADE9000_WFB_PAGE_SIZE / ADE9000_WFB_MAX_CHANNELS)
+#define ADE9000_WFB_MAX_SAMPLES_CHAN \
+ (ADE9000_WFB_SAMPLES_IN_PAGE * ADE9000_WFB_NR_OF_PAGES)
+#define ADE9000_WFB_FULL_BUFF_NR_SAMPLES \
+ (ADE9000_WFB_PAGE_SIZE * ADE9000_WFB_NR_OF_PAGES)
+#define ADE9000_WFB_FULL_BUFF_SIZE \
+ (ADE9000_WFB_FULL_BUFF_NR_SAMPLES * ADE9000_WFB_BYTES_IN_SAMPLE)
+
+#define ADE9000_SWRST_BIT BIT(0)
+
+/* Status and Mask register bits*/
+#define ADE9000_ST0_WFB_TRIG_BIT BIT(16)
+#define ADE9000_ST0_PAGE_FULL_BIT BIT(17)
+#define ADE9000_ST0_EGYRDY BIT(0)
+
+#define ADE9000_ST1_ZXTOVA_BIT BIT(6)
+#define ADE9000_ST1_ZXTOVB_BIT BIT(7)
+#define ADE9000_ST1_ZXTOVC_BIT BIT(8)
+#define ADE9000_ST1_ZXVA_BIT BIT(9)
+#define ADE9000_ST1_ZXVB_BIT BIT(10)
+#define ADE9000_ST1_ZXVC_BIT BIT(11)
+#define ADE9000_ST1_ZXIA_BIT BIT(13)
+#define ADE9000_ST1_ZXIB_BIT BIT(14)
+#define ADE9000_ST1_ZXIC_BIT BIT(15)
+#define ADE9000_ST1_RSTDONE_BIT BIT(16)
+#define ADE9000_ST1_SEQERR_BIT BIT(18)
+#define ADE9000_ST1_SWELLA_BIT BIT(20)
+#define ADE9000_ST1_SWELLB_BIT BIT(21)
+#define ADE9000_ST1_SWELLC_BIT BIT(22)
+#define ADE9000_ST1_DIPA_BIT BIT(23)
+#define ADE9000_ST1_DIPB_BIT BIT(24)
+#define ADE9000_ST1_DIPC_BIT BIT(25)
+#define ADE9000_ST1_ERROR0_BIT BIT(28)
+#define ADE9000_ST1_ERROR1_BIT BIT(29)
+#define ADE9000_ST1_ERROR2_BIT BIT(30)
+#define ADE9000_ST1_ERROR3_BIT BIT(31)
+#define ADE9000_ST_ERROR \
+ (ADE9000_ST1_ERROR0 | ADE9000_ST1_ERROR1 | \
+ ADE9000_ST1_ERROR2 | ADE9000_ST1_ERROR3)
+#define ADE9000_ST1_CROSSING_FIRST 6
+#define ADE9000_ST1_CROSSING_DEPTH 25
+
+#define ADE9000_WFB_TRG_DIP_BIT BIT(0)
+#define ADE9000_WFB_TRG_SWELL_BIT BIT(1)
+#define ADE9000_WFB_TRG_ZXIA_BIT BIT(3)
+#define ADE9000_WFB_TRG_ZXIB_BIT BIT(4)
+#define ADE9000_WFB_TRG_ZXIC_BIT BIT(5)
+#define ADE9000_WFB_TRG_ZXVA_BIT BIT(6)
+#define ADE9000_WFB_TRG_ZXVB_BIT BIT(7)
+#define ADE9000_WFB_TRG_ZXVC_BIT BIT(8)
+
+/* Stop when waveform buffer is full */
+#define ADE9000_WFB_FULL_MODE 0x0
+/* Continuous fill—stop only on enabled trigger events */
+#define ADE9000_WFB_EN_TRIG_MODE 0x1
+/* Continuous filling—center capture around enabled trigger events */
+#define ADE9000_WFB_C_EN_TRIG_MODE 0x2
+/* Continuous fill—used as streaming mode for continuous data output */
+#define ADE9000_WFB_STREAMING_MODE 0x3
+
+#define ADE9000_LAST_PAGE_BIT BIT(15)
+#define ADE9000_MIDDLE_PAGE_BIT BIT(7)
+
+/*
+ * Full scale Codes referred from Datasheet. Respective digital codes are
+ * produced when ADC inputs are at full scale.
+ */
+#define ADE9000_RMS_FULL_SCALE_CODES 52866837
+#define ADE9000_WATT_FULL_SCALE_CODES 20694066
+#define ADE9000_PCF_FULL_SCALE_CODES 74770000
+
+/* Phase and channel definitions */
+#define ADE9000_PHASE_A_NR 0
+#define ADE9000_PHASE_B_NR 1
+#define ADE9000_PHASE_C_NR 2
+
+#define ADE9000_SCAN_POS_IA BIT(0)
+#define ADE9000_SCAN_POS_VA BIT(1)
+#define ADE9000_SCAN_POS_IB BIT(2)
+#define ADE9000_SCAN_POS_VB BIT(3)
+#define ADE9000_SCAN_POS_IC BIT(4)
+#define ADE9000_SCAN_POS_VC BIT(5)
+
+/* Waveform buffer configuration values */
+enum ade9000_wfb_cfg {
+ ADE9000_WFB_CFG_ALL_CHAN = 0x0,
+ ADE9000_WFB_CFG_IA_VA = 0x1,
+ ADE9000_WFB_CFG_IB_VB = 0x2,
+ ADE9000_WFB_CFG_IC_VC = 0x3,
+ ADE9000_WFB_CFG_IA = 0x8,
+ ADE9000_WFB_CFG_VA = 0x9,
+ ADE9000_WFB_CFG_IB = 0xA,
+ ADE9000_WFB_CFG_VB = 0xB,
+ ADE9000_WFB_CFG_IC = 0xC,
+ ADE9000_WFB_CFG_VC = 0xD,
+};
+
+#define ADE9000_PHASE_B_POS_BIT BIT(5)
+#define ADE9000_PHASE_C_POS_BIT BIT(6)
+
+#define ADE9000_MAX_PHASE_NR 3
+#define AD9000_CHANNELS_PER_PHASE 10
+
+/*
+ * Calculate register address for multi-phase device.
+ * Phase A (chan 0): base address + 0x00
+ * Phase B (chan 1): base address + 0x20
+ * Phase C (chan 2): base address + 0x40
+ */
+#define ADE9000_ADDR_ADJUST(addr, chan) \
+ (((chan) == 0 ? 0 : (chan) == 1 ? 2 : 4) << 4 | (addr))
+
+struct ade9000_state {
+ struct completion reset_completion;
+ struct mutex lock; /* Protects SPI transactions */
+ u8 wf_src;
+ u32 wfb_trg;
+ u8 wfb_nr_activ_chan;
+ u32 wfb_nr_samples;
+ struct spi_device *spi;
+ struct clk *clkin;
+ struct spi_transfer xfer[2];
+ struct spi_message spi_msg;
+ struct regmap *regmap;
+ union{
+ u8 byte[ADE9000_WFB_FULL_BUFF_SIZE];
+ __be32 word[ADE9000_WFB_FULL_BUFF_NR_SAMPLES];
+ } rx_buff __aligned(IIO_DMA_MINALIGN);
+ u8 tx_buff[2] __aligned(IIO_DMA_MINALIGN);
+ unsigned int bulk_read_buf[2];
+};
+
+struct ade9000_irq1_event {
+ u32 bit_mask;
+ enum iio_chan_type chan_type;
+ u32 channel;
+ enum iio_event_type event_type;
+ enum iio_event_direction event_dir;
+};
+
+static const struct ade9000_irq1_event ade9000_irq1_events[] = {
+ { ADE9000_ST1_ZXVA_BIT, IIO_VOLTAGE, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER },
+ { ADE9000_ST1_ZXIA_BIT, IIO_CURRENT, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER },
+ { ADE9000_ST1_ZXVB_BIT, IIO_VOLTAGE, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER },
+ { ADE9000_ST1_ZXIB_BIT, IIO_CURRENT, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER },
+ { ADE9000_ST1_ZXVC_BIT, IIO_VOLTAGE, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER },
+ { ADE9000_ST1_ZXIC_BIT, IIO_CURRENT, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER },
+ { ADE9000_ST1_SWELLA_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING },
+ { ADE9000_ST1_SWELLB_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING },
+ { ADE9000_ST1_SWELLC_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING },
+ { ADE9000_ST1_DIPA_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING },
+ { ADE9000_ST1_DIPB_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING },
+ { ADE9000_ST1_DIPC_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING },
+};
+
+/* Voltage events (zero crossing on instantaneous voltage) */
+static const struct iio_event_spec ade9000_voltage_events[] = {
+ {
+ /* Zero crossing detection - datasheet: ZXV interrupts */
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_EITHER,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+/* Current events (zero crossing on instantaneous current) */
+static const struct iio_event_spec ade9000_current_events[] = {
+ {
+ /* Zero crossing detection - datasheet: ZXI interrupts */
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_EITHER,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+/* RMS voltage events (swell/sag detection on RMS values) */
+static const struct iio_event_spec ade9000_rms_voltage_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING, /* RMS swell detection */
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE),
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING, /* RMS sag/dip detection */
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE),
+ },
+};
+
+static const char * const ade9000_filter_type_items[] = {
+ "sinc4", "sinc4+lp",
+};
+
+static const int ade9000_filter_type_values[] = {
+ 0, 2,
+};
+
+static int ade9000_filter_type_get(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct ade9000_state *st = iio_priv(indio_dev);
+ u32 val;
+ int ret;
+ unsigned int i;
+
+ ret = regmap_read(st->regmap, ADE9000_REG_WFB_CFG, &val);
+ if (ret)
+ return ret;
+
+ val = FIELD_GET(ADE9000_WF_SRC_MASK, val);
+
+ for (i = 0; i < ARRAY_SIZE(ade9000_filter_type_values); i++) {
+ if (ade9000_filter_type_values[i] == val)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int ade9000_filter_type_set(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int index)
+{
+ struct ade9000_state *st = iio_priv(indio_dev);
+ int ret, val;
+
+ if (index >= ARRAY_SIZE(ade9000_filter_type_values))
+ return -EINVAL;
+
+ val = ade9000_filter_type_values[index];
+
+ /* Update the WFB_CFG register with the new filter type */
+ ret = regmap_update_bits(st->regmap, ADE9000_REG_WFB_CFG,
+ ADE9000_WF_SRC_MASK,
+ FIELD_PREP(ADE9000_WF_SRC_MASK, val));
+ if (ret)
+ return ret;
+
+ /* Update cached value */
+ st->wf_src = val;
+
+ return 0;
+}
+
+static const struct iio_enum ade9000_filter_type_enum = {
+ .items = ade9000_filter_type_items,
+ .num_items = ARRAY_SIZE(ade9000_filter_type_items),
+ .get = ade9000_filter_type_get,
+ .set = ade9000_filter_type_set,
+};
+
+static const struct iio_chan_spec_ext_info ade9000_ext_info[] = {
+ IIO_ENUM("filter_type", IIO_SHARED_BY_ALL, &ade9000_filter_type_enum),
+ IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_ALL, &ade9000_filter_type_enum),
+ { }
+};
+
+#define ADE9000_CURRENT_CHANNEL(num) { \
+ .type = IIO_CURRENT, \
+ .channel = num, \
+ .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AI_PCF, num), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_CALIBSCALE), \
+ .event_spec = ade9000_current_events, \
+ .num_event_specs = ARRAY_SIZE(ade9000_current_events), \
+ .scan_index = num, \
+ .indexed = 1, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 32, \
+ .storagebits = 32, \
+ .endianness = IIO_BE, \
+ }, \
+}
+
+#define ADE9000_VOLTAGE_CHANNEL(num) { \
+ .type = IIO_VOLTAGE, \
+ .channel = num, \
+ .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AV_PCF, num), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_CALIBSCALE) | \
+ BIT(IIO_CHAN_INFO_FREQUENCY), \
+ .event_spec = ade9000_voltage_events, \
+ .num_event_specs = ARRAY_SIZE(ade9000_voltage_events), \
+ .scan_index = num + 1, /* interleave with current channels */ \
+ .indexed = 1, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 32, \
+ .storagebits = 32, \
+ .endianness = IIO_BE, \
+ }, \
+ .ext_info = ade9000_ext_info, \
+}
+
+#define ADE9000_ALTCURRENT_RMS_CHANNEL(num) { \
+ .type = IIO_ALTCURRENT, \
+ .channel = num, \
+ .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AIRMS, num), \
+ .channel2 = IIO_MOD_RMS, \
+ .modified = 1, \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS), \
+ .scan_index = -1 \
+}
+
+#define ADE9000_ALTVOLTAGE_RMS_CHANNEL(num) { \
+ .type = IIO_ALTVOLTAGE, \
+ .channel = num, \
+ .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVRMS, num), \
+ .channel2 = IIO_MOD_RMS, \
+ .modified = 1, \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS), \
+ .event_spec = ade9000_rms_voltage_events, \
+ .num_event_specs = ARRAY_SIZE(ade9000_rms_voltage_events), \
+ .scan_index = -1 \
+}
+
+#define ADE9000_POWER_ACTIVE_CHANNEL(num) { \
+ .type = IIO_POWER, \
+ .channel = num, \
+ .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AWATT, num), \
+ .channel2 = IIO_MOD_ACTIVE, \
+ .modified = 1, \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS) | \
+ BIT(IIO_CHAN_INFO_CALIBSCALE), \
+ .scan_index = -1 \
+}
+
+#define ADE9000_POWER_REACTIVE_CHANNEL(num) { \
+ .type = IIO_POWER, \
+ .channel = num, \
+ .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVAR, num), \
+ .channel2 = IIO_MOD_REACTIVE, \
+ .modified = 1, \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS), \
+ .scan_index = -1 \
+}
+
+#define ADE9000_POWER_APPARENT_CHANNEL(num) { \
+ .type = IIO_POWER, \
+ .channel = num, \
+ .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVA, num), \
+ .channel2 = IIO_MOD_APPARENT, \
+ .modified = 1, \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .scan_index = -1 \
+}
+
+ #define ADE9000_ENERGY_ACTIVE_CHANNEL(num, addr) { \
+ .type = IIO_ENERGY, \
+ .channel = num, \
+ .address = addr, \
+ .channel2 = IIO_MOD_ACTIVE, \
+ .modified = 1, \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .scan_index = -1 \
+}
+
+#define ADE9000_ENERGY_APPARENT_CHANNEL(num, addr) { \
+ .type = IIO_ENERGY, \
+ .channel = num, \
+ .address = addr, \
+ .channel2 = IIO_MOD_APPARENT, \
+ .modified = 1, \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .scan_index = -1 \
+}
+
+#define ADE9000_ENERGY_REACTIVE_CHANNEL(num, addr) { \
+ .type = IIO_ENERGY, \
+ .channel = num, \
+ .address = addr, \
+ .channel2 = IIO_MOD_REACTIVE, \
+ .modified = 1, \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .scan_index = -1 \
+}
+
+#define ADE9000_POWER_FACTOR_CHANNEL(num) { \
+ .type = IIO_POWER, \
+ .channel = num, \
+ .address = ADE9000_ADDR_ADJUST(ADE9000_REG_APF, num), \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_POWERFACTOR), \
+ .scan_index = -1 \
+}
+
+static const struct iio_chan_spec ade9000_channels[] = {
+ /* Phase A channels */
+ ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_A_NR),
+ ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_A_NR),
+ ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_A_NR),
+ ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_A_NR),
+ ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR),
+ ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR),
+ ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_A_NR),
+ ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AWATTHR_LO),
+ ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AVAHR_LO),
+ ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AFVARHR_LO),
+ ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_A_NR),
+ /* Phase B channels */
+ ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_B_NR),
+ ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_B_NR),
+ ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_B_NR),
+ ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_B_NR),
+ ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR),
+ ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR),
+ ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_B_NR),
+ ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BWATTHR_LO),
+ ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BVAHR_LO),
+ ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BFVARHR_LO),
+ ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_B_NR),
+ /* Phase C channels */
+ ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_C_NR),
+ ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_C_NR),
+ ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_C_NR),
+ ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_C_NR),
+ ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR),
+ ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR),
+ ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_C_NR),
+ ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CWATTHR_LO),
+ ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CVAHR_LO),
+ ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CFVARHR_LO),
+ ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_C_NR),
+};
+
+static const struct reg_sequence ade9000_initialization_sequence[] = {
+ { ADE9000_REG_PGA_GAIN, ADE9000_PGA_GAIN },
+ { ADE9000_REG_CONFIG0, ADE9000_CONFIG0 },
+ { ADE9000_REG_CONFIG1, ADE9000_CONFIG1 },
+ { ADE9000_REG_CONFIG2, ADE9000_CONFIG2 },
+ { ADE9000_REG_CONFIG3, ADE9000_CONFIG3 },
+ { ADE9000_REG_ACCMODE, ADE9000_ACCMODE },
+ { ADE9000_REG_ZX_LP_SEL, ADE9000_ZX_LP_SEL },
+ { ADE9000_REG_MASK0, ADE9000_MASK0_ALL_INT_DIS },
+ { ADE9000_REG_MASK1, ADE9000_MASK1_ALL_INT_DIS },
+ { ADE9000_REG_EVENT_MASK, ADE9000_EVENT_DISABLE },
+ { ADE9000_REG_WFB_CFG, ADE9000_WFB_CFG },
+ { ADE9000_REG_VLEVEL, ADE9000_VLEVEL },
+ { ADE9000_REG_DICOEFF, ADE9000_DICOEFF },
+ { ADE9000_REG_EGY_TIME, ADE9000_EGY_TIME },
+ { ADE9000_REG_EP_CFG, ADE9000_EP_CFG },
+ /* Clear all pending status bits by writing 1s */
+ { ADE9000_REG_STATUS0, GENMASK(31, 0) },
+ { ADE9000_REG_STATUS1, GENMASK(31, 0) },
+ { ADE9000_REG_RUN, ADE9000_RUN_ON }
+};
+
+static int ade9000_spi_write_reg(void *context, unsigned int reg,
+ unsigned int val)
+{
+ struct ade9000_state *st = context;
+ u8 tx_buf[6];
+ u16 addr;
+ int ret, len;
+
+ guard(mutex)(&st->lock);
+
+ addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, reg);
+ put_unaligned_be16(addr, tx_buf);
+
+ if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) {
+ put_unaligned_be16(val, &tx_buf[2]);
+ len = 4;
+ } else {
+ put_unaligned_be32(val, &tx_buf[2]);
+ len = 6;
+ }
+
+ ret = spi_write_then_read(st->spi, tx_buf, len, NULL, 0);
+ if (ret)
+ dev_err(&st->spi->dev, "problem when writing register 0x%x\n", reg);
+
+ return ret;
+}
+
+static int ade9000_spi_read_reg(void *context, unsigned int reg,
+ unsigned int *val)
+{
+ struct ade9000_state *st = context;
+ u8 tx_buf[2];
+ u8 rx_buf[4];
+ u16 addr;
+ int ret, rx_len;
+
+ guard(mutex)(&st->lock);
+
+ addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, reg) |
+ ADE9000_REG_READ_BIT_MASK;
+
+ put_unaligned_be16(addr, tx_buf);
+
+ /* Skip CRC bytes - only read actual data */
+ if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION)
+ rx_len = 2;
+ else
+ rx_len = 4;
+
+ ret = spi_write_then_read(st->spi, tx_buf, 2, rx_buf, rx_len);
+ if (ret) {
+ dev_err(&st->spi->dev, "error reading register 0x%x\n", reg);
+ return ret;
+ }
+
+ if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION)
+ *val = get_unaligned_be16(rx_buf);
+ else
+ *val = get_unaligned_be32(rx_buf);
+
+ return 0;
+}
+
+static bool ade9000_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ /* Interrupt/error status registers - volatile */
+ case ADE9000_REG_STATUS0:
+ case ADE9000_REG_STATUS1:
+ return true;
+ default:
+ /* All other registers are non-volatile */
+ return false;
+ }
+}
+
+static void ade9000_configure_scan(struct iio_dev *indio_dev, u32 wfb_addr)
+{
+ struct ade9000_state *st = iio_priv(indio_dev);
+ u16 addr;
+
+ addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, wfb_addr) |
+ ADE9000_REG_READ_BIT_MASK;
+
+ put_unaligned_be16(addr, st->tx_buff);
+
+ st->xfer[0].tx_buf = &st->tx_buff[0];
+ st->xfer[0].len = 2;
+
+ st->xfer[1].rx_buf = st->rx_buff.byte;
+
+ /* Always use streaming mode */
+ st->xfer[1].len = (st->wfb_nr_samples / 2) * 4;
+
+ spi_message_init_with_transfers(&st->spi_msg, st->xfer, ARRAY_SIZE(st->xfer));
+}
+
+static int ade9000_iio_push_streaming(struct iio_dev *indio_dev)
+{
+ struct ade9000_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ u32 current_page, i;
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = spi_sync(st->spi, &st->spi_msg);
+ if (ret) {
+ dev_err_ratelimited(dev, "SPI fail in trigger handler\n");
+ return ret;
+ }
+
+ /* In streaming mode, only half the buffer is filled per interrupt */
+ for (i = 0; i < st->wfb_nr_samples / 2; i += st->wfb_nr_activ_chan)
+ iio_push_to_buffers(indio_dev, &st->rx_buff.word[i]);
+
+ ret = regmap_read(st->regmap, ADE9000_REG_WFB_PG_IRQEN, &current_page);
+ if (ret) {
+ dev_err_ratelimited(dev, "IRQ0 WFB read fail\n");
+ return ret;
+ }
+
+ if (current_page & ADE9000_MIDDLE_PAGE_BIT) {
+ ret = regmap_write(st->regmap, ADE9000_REG_WFB_PG_IRQEN,
+ ADE9000_LAST_PAGE_BIT);
+ if (ret) {
+ dev_err_ratelimited(dev, "IRQ0 WFB write fail\n");
+ return ret;
+ }
+
+ ade9000_configure_scan(indio_dev,
+ ADE9000_REG_WF_HALF_BUFF);
+ } else {
+ ret = regmap_write(st->regmap, ADE9000_REG_WFB_PG_IRQEN,
+ ADE9000_MIDDLE_PAGE_BIT);
+ if (ret) {
+ dev_err_ratelimited(dev, "IRQ0 WFB write fail");
+ return IRQ_HANDLED;
+ }
+
+ ade9000_configure_scan(indio_dev, ADE9000_REG_WF_BUFF);
+ }
+
+ return 0;
+}
+
+static int ade9000_iio_push_buffer(struct iio_dev *indio_dev)
+{
+ struct ade9000_state *st = iio_priv(indio_dev);
+ int ret;
+ u32 i;
+
+ guard(mutex)(&st->lock);
+
+ ret = spi_sync(st->spi, &st->spi_msg);
+ if (ret) {
+ dev_err_ratelimited(&st->spi->dev,
+ "SPI fail in trigger handler\n");
+ return ret;
+ }
+
+ for (i = 0; i < st->wfb_nr_samples; i += st->wfb_nr_activ_chan)
+ iio_push_to_buffers(indio_dev, &st->rx_buff.word[i]);
+
+ return 0;
+}
+
+static irqreturn_t ade9000_irq0_thread(int irq, void *data)
+{
+ struct iio_dev *indio_dev = data;
+ struct ade9000_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ u32 handled_irq = 0;
+ u32 interrupts, status;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADE9000_REG_STATUS0, &status);
+ if (ret) {
+ dev_err_ratelimited(dev, "IRQ0 read status fail\n");
+ return IRQ_HANDLED;
+ }
+
+ ret = regmap_read(st->regmap, ADE9000_REG_MASK0, &interrupts);
+ if (ret) {
+ dev_err_ratelimited(dev, "IRQ0 read mask fail\n");
+ return IRQ_HANDLED;
+ }
+
+ if ((status & ADE9000_ST0_PAGE_FULL_BIT) &&
+ (interrupts & ADE9000_ST0_PAGE_FULL_BIT)) {
+ /* Always use streaming mode */
+ ret = ade9000_iio_push_streaming(indio_dev);
+ if (ret) {
+ dev_err_ratelimited(dev, "IRQ0 IIO push fail\n");
+ return IRQ_HANDLED;
+ }
+
+ handled_irq |= ADE9000_ST0_PAGE_FULL_BIT;
+ }
+
+ if ((status & ADE9000_ST0_WFB_TRIG_BIT) &&
+ (interrupts & ADE9000_ST0_WFB_TRIG_BIT)) {
+ ret = regmap_update_bits(st->regmap, ADE9000_REG_WFB_CFG,
+ ADE9000_WF_CAP_EN_MASK, 0);
+ if (ret) {
+ dev_err_ratelimited(dev, "IRQ0 WFB fail\n");
+ return IRQ_HANDLED;
+ }
+
+ if (iio_buffer_enabled(indio_dev)) {
+ ret = ade9000_iio_push_buffer(indio_dev);
+ if (ret) {
+ dev_err_ratelimited(dev,
+ "IRQ0 IIO push fail @ WFB TRIG\n");
+ return IRQ_HANDLED;
+ }
+ }
+
+ handled_irq |= ADE9000_ST0_WFB_TRIG_BIT;
+ }
+
+ ret = regmap_write(st->regmap, ADE9000_REG_STATUS0, handled_irq);
+ if (ret)
+ dev_err_ratelimited(dev, "IRQ0 write status fail\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t ade9000_irq1_thread(int irq, void *data)
+{
+ struct iio_dev *indio_dev = data;
+ struct ade9000_state *st = iio_priv(indio_dev);
+ unsigned int bit = ADE9000_ST1_CROSSING_FIRST;
+ s64 timestamp = iio_get_time_ns(indio_dev);
+ u32 handled_irq = 0;
+ u32 interrupts, result, status, tmp;
+ DECLARE_BITMAP(interrupt_bits, ADE9000_ST1_CROSSING_DEPTH);
+ const struct ade9000_irq1_event *event;
+ int ret, i;
+
+ if (!completion_done(&st->reset_completion)) {
+ ret = regmap_read(st->regmap, ADE9000_REG_STATUS1, &result);
+ if (ret) {
+ dev_err_ratelimited(&st->spi->dev, "IRQ1 read status fail\n");
+ return IRQ_HANDLED;
+ }
+
+ if (result & ADE9000_ST1_RSTDONE_BIT) {
+ complete(&st->reset_completion);
+ /* Clear the reset done status bit */
+ ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, ADE9000_ST1_RSTDONE_BIT);
+ if (ret)
+ dev_err_ratelimited(&st->spi->dev,
+ "IRQ1 clear reset status fail\n");
+ } else {
+ dev_err_ratelimited(&st->spi->dev,
+ "Error testing reset done\n");
+ }
+
+ return IRQ_HANDLED;
+ }
+
+ ret = regmap_read(st->regmap, ADE9000_REG_STATUS1, &status);
+ if (ret) {
+ dev_err_ratelimited(&st->spi->dev, "IRQ1 read status fail\n");
+ return IRQ_HANDLED;
+ }
+
+ ret = regmap_read(st->regmap, ADE9000_REG_MASK1, &interrupts);
+ if (ret) {
+ dev_err_ratelimited(&st->spi->dev, "IRQ1 read mask fail\n");
+ return IRQ_HANDLED;
+ }
+
+ bitmap_from_arr32(interrupt_bits, &interrupts, ADE9000_ST1_CROSSING_DEPTH);
+ for_each_set_bit_from(bit, interrupt_bits,
+ ADE9000_ST1_CROSSING_DEPTH) {
+ tmp = status & BIT(bit);
+ if (!tmp)
+ continue;
+
+ event = NULL;
+
+ /* Find corresponding event in lookup table */
+ for (i = 0; i < ARRAY_SIZE(ade9000_irq1_events); i++) {
+ if (ade9000_irq1_events[i].bit_mask == tmp) {
+ event = &ade9000_irq1_events[i];
+ break;
+ }
+ }
+
+ if (event) {
+ iio_push_event(indio_dev,
+ IIO_UNMOD_EVENT_CODE(event->chan_type,
+ event->channel,
+ event->event_type,
+ event->event_dir),
+ timestamp);
+ }
+ handled_irq |= tmp;
+ }
+
+ ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, handled_irq);
+ if (ret)
+ dev_err_ratelimited(&st->spi->dev, "IRQ1 write status fail\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t ade9000_dready_thread(int irq, void *data)
+{
+ struct iio_dev *indio_dev = data;
+
+ /* Handle data ready interrupt from C4/EVENT/DREADY pin */
+ if (!iio_device_claim_buffer_mode(indio_dev)) {
+ ade9000_iio_push_buffer(indio_dev);
+ iio_device_release_buffer_mode(indio_dev);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int ade9000_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long mask)
+{
+ struct ade9000_state *st = iio_priv(indio_dev);
+ unsigned int measured;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_FREQUENCY:
+ if (chan->type == IIO_VOLTAGE) {
+ int period_reg;
+ int period;
+
+ switch (chan->channel) {
+ case ADE9000_PHASE_A_NR:
+ period_reg = ADE9000_REG_APERIOD;
+ break;
+ case ADE9000_PHASE_B_NR:
+ period_reg = ADE9000_REG_BPERIOD;
+ break;
+ case ADE9000_PHASE_C_NR:
+ period_reg = ADE9000_REG_CPERIOD;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ret = regmap_read(st->regmap, period_reg, &period);
+ if (ret)
+ return ret;
+ /*
+ * Frequency = (4MHz * 65536) / (PERIOD + 1)
+ * 4MHz = ADC sample rate, 65536 = 2^16 period register scaling
+ * See ADE9000 datasheet section on period measurement
+ */
+ *val = 4000 * 65536;
+ *val2 = period + 1;
+ return IIO_VAL_FRACTIONAL;
+ }
+
+ return -EINVAL;
+ case IIO_CHAN_INFO_RAW:
+ if (chan->type == IIO_ENERGY) {
+ u16 lo_reg = chan->address;
+
+ ret = regmap_bulk_read(st->regmap, lo_reg,
+ st->bulk_read_buf, 2);
+ if (ret)
+ return ret;
+
+ *val = st->bulk_read_buf[0]; /* Lower 32 bits */
+ *val2 = st->bulk_read_buf[1]; /* Upper 32 bits */
+ return IIO_VAL_INT_64;
+ }
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = regmap_read(st->regmap, chan->address, &measured);
+ iio_device_release_direct(indio_dev);
+ if (ret)
+ return ret;
+
+ *val = measured;
+
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_POWERFACTOR:
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = regmap_read(st->regmap, chan->address, &measured);
+ iio_device_release_direct(indio_dev);
+ if (ret)
+ return ret;
+
+ *val = measured;
+
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_CURRENT:
+ case IIO_VOLTAGE:
+ case IIO_ALTVOLTAGE:
+ case IIO_ALTCURRENT:
+ switch (chan->address) {
+ case ADE9000_REG_AI_PCF:
+ case ADE9000_REG_AV_PCF:
+ case ADE9000_REG_BI_PCF:
+ case ADE9000_REG_BV_PCF:
+ case ADE9000_REG_CI_PCF:
+ case ADE9000_REG_CV_PCF:
+ *val = 1;
+ *val2 = ADE9000_PCF_FULL_SCALE_CODES;
+ return IIO_VAL_FRACTIONAL;
+ case ADE9000_REG_AIRMS:
+ case ADE9000_REG_AVRMS:
+ case ADE9000_REG_BIRMS:
+ case ADE9000_REG_BVRMS:
+ case ADE9000_REG_CIRMS:
+ case ADE9000_REG_CVRMS:
+ *val = 1;
+ *val2 = ADE9000_RMS_FULL_SCALE_CODES;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ case IIO_POWER:
+ *val = 1;
+ *val2 = ADE9000_WATT_FULL_SCALE_CODES;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ break;
+ }
+
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ade9000_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask)
+{
+ struct ade9000_state *st = iio_priv(indio_dev);
+ u32 tmp;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_CALIBBIAS:
+ switch (chan->type) {
+ case IIO_CURRENT:
+ return regmap_write(st->regmap,
+ ADE9000_ADDR_ADJUST(ADE9000_REG_AIRMSOS,
+ chan->channel), val);
+ case IIO_VOLTAGE:
+ case IIO_ALTVOLTAGE:
+ return regmap_write(st->regmap,
+ ADE9000_ADDR_ADJUST(ADE9000_REG_AVRMSOS,
+ chan->channel), val);
+ case IIO_POWER:
+ tmp = chan->address;
+ tmp &= ~ADE9000_PHASE_B_POS_BIT;
+ tmp &= ~ADE9000_PHASE_C_POS_BIT;
+
+ switch (tmp) {
+ case ADE9000_REG_AWATTOS:
+ return regmap_write(st->regmap,
+ ADE9000_ADDR_ADJUST(ADE9000_REG_AWATTOS,
+ chan->channel), val);
+ case ADE9000_REG_AVAR:
+ return regmap_write(st->regmap,
+ ADE9000_ADDR_ADJUST(ADE9000_REG_AVAROS,
+ chan->channel), val);
+ case ADE9000_REG_AFVAR:
+ return regmap_write(st->regmap,
+ ADE9000_ADDR_ADJUST(ADE9000_REG_AFVAROS,
+ chan->channel), val);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_CALIBSCALE:
+ /*
+ * Calibration gain registers for fine-tuning measurements.
+ * These are separate from PGA gain and applied in the digital domain.
+ */
+ switch (chan->type) {
+ case IIO_CURRENT:
+ return regmap_write(st->regmap,
+ ADE9000_ADDR_ADJUST(ADE9000_REG_AIGAIN,
+ chan->channel), val);
+ case IIO_VOLTAGE:
+ return regmap_write(st->regmap,
+ ADE9000_ADDR_ADJUST(ADE9000_REG_AVGAIN,
+ chan->channel), val);
+ case IIO_POWER:
+ return regmap_write(st->regmap,
+ ADE9000_ADDR_ADJUST(ADE9000_REG_APGAIN,
+ chan->channel), val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ /* Per-channel scales are read-only */
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ade9000_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg,
+ unsigned int tx_val,
+ unsigned int *rx_val)
+{
+ struct ade9000_state *st = iio_priv(indio_dev);
+
+ if (rx_val)
+ return regmap_read(st->regmap, reg, rx_val);
+
+ return regmap_write(st->regmap, reg, tx_val);
+}
+
+static int ade9000_read_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ struct ade9000_state *st = iio_priv(indio_dev);
+ u32 interrupts1;
+ int ret;
+
+ /* All events use MASK1 register */
+ ret = regmap_read(st->regmap, ADE9000_REG_MASK1, &interrupts1);
+ if (ret)
+ return ret;
+
+ switch (chan->channel) {
+ case ADE9000_PHASE_A_NR:
+ if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER)
+ return !!(interrupts1 & ADE9000_ST1_ZXVA_BIT);
+ else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER)
+ return !!(interrupts1 & ADE9000_ST1_ZXIA_BIT);
+ else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING)
+ return !!(interrupts1 & ADE9000_ST1_SWELLA_BIT);
+ else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING)
+ return !!(interrupts1 & ADE9000_ST1_DIPA_BIT);
+ dev_err_ratelimited(&indio_dev->dev,
+ "Invalid channel type %d or direction %d for phase A\n", chan->type, dir);
+ return -EINVAL;
+ case ADE9000_PHASE_B_NR:
+ if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER)
+ return !!(interrupts1 & ADE9000_ST1_ZXVB_BIT);
+ else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER)
+ return !!(interrupts1 & ADE9000_ST1_ZXIB_BIT);
+ else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING)
+ return !!(interrupts1 & ADE9000_ST1_SWELLB_BIT);
+ else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING)
+ return !!(interrupts1 & ADE9000_ST1_DIPB_BIT);
+ dev_err_ratelimited(&indio_dev->dev,
+ "Invalid channel type %d or direction %d for phase B\n", chan->type, dir);
+ return -EINVAL;
+ case ADE9000_PHASE_C_NR:
+ if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER)
+ return !!(interrupts1 & ADE9000_ST1_ZXVC_BIT);
+ else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER)
+ return !!(interrupts1 & ADE9000_ST1_ZXIC_BIT);
+ else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING)
+ return !!(interrupts1 & ADE9000_ST1_SWELLC_BIT);
+ else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING)
+ return !!(interrupts1 & ADE9000_ST1_DIPC_BIT);
+ dev_err_ratelimited(&indio_dev->dev,
+ "Invalid channel type %d or direction %d for phase C\n", chan->type, dir);
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ade9000_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ struct ade9000_state *st = iio_priv(indio_dev);
+ u32 bit_mask;
+ int ret;
+
+ /* Clear all pending events in STATUS1 register (write 1 to clear) */
+ ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, GENMASK(31, 0));
+ if (ret)
+ return ret;
+
+ /* Determine which interrupt bit to enable/disable */
+ switch (chan->channel) {
+ case ADE9000_PHASE_A_NR:
+ if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) {
+ bit_mask = ADE9000_ST1_ZXVA_BIT;
+ if (state)
+ st->wfb_trg |= ADE9000_WFB_TRG_ZXVA_BIT;
+ else
+ st->wfb_trg &= ~ADE9000_WFB_TRG_ZXVA_BIT;
+ } else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) {
+ bit_mask = ADE9000_ST1_ZXIA_BIT;
+ if (state)
+ st->wfb_trg |= ADE9000_WFB_TRG_ZXIA_BIT;
+ else
+ st->wfb_trg &= ~ADE9000_WFB_TRG_ZXIA_BIT;
+ } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) {
+ bit_mask = ADE9000_ST1_SWELLA_BIT;
+ if (state)
+ st->wfb_trg |= ADE9000_WFB_TRG_SWELL_BIT;
+ else
+ st->wfb_trg &= ~ADE9000_WFB_TRG_SWELL_BIT;
+ } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) {
+ bit_mask = ADE9000_ST1_DIPA_BIT;
+ if (state)
+ st->wfb_trg |= ADE9000_WFB_TRG_DIP_BIT;
+ else
+ st->wfb_trg &= ~ADE9000_WFB_TRG_DIP_BIT;
+ } else {
+ dev_err_ratelimited(&indio_dev->dev, "Invalid channel type %d or direction %d for phase A\n",
+ chan->type, dir);
+ return -EINVAL;
+ }
+ break;
+ case ADE9000_PHASE_B_NR:
+ if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) {
+ bit_mask = ADE9000_ST1_ZXVB_BIT;
+ if (state)
+ st->wfb_trg |= ADE9000_WFB_TRG_ZXVB_BIT;
+ else
+ st->wfb_trg &= ~ADE9000_WFB_TRG_ZXVB_BIT;
+ } else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) {
+ bit_mask = ADE9000_ST1_ZXIB_BIT;
+ if (state)
+ st->wfb_trg |= ADE9000_WFB_TRG_ZXIB_BIT;
+ else
+ st->wfb_trg &= ~ADE9000_WFB_TRG_ZXIB_BIT;
+ } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) {
+ bit_mask = ADE9000_ST1_SWELLB_BIT;
+ if (state)
+ st->wfb_trg |= ADE9000_WFB_TRG_SWELL_BIT;
+ else
+ st->wfb_trg &= ~ADE9000_WFB_TRG_SWELL_BIT;
+ } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) {
+ bit_mask = ADE9000_ST1_DIPB_BIT;
+ if (state)
+ st->wfb_trg |= ADE9000_WFB_TRG_DIP_BIT;
+ else
+ st->wfb_trg &= ~ADE9000_WFB_TRG_DIP_BIT;
+ } else {
+ dev_err_ratelimited(&indio_dev->dev,
+ "Invalid channel type %d or direction %d for phase B\n",
+ chan->type, dir);
+ return -EINVAL;
+ }
+ break;
+ case ADE9000_PHASE_C_NR:
+ if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) {
+ bit_mask = ADE9000_ST1_ZXVC_BIT;
+ if (state)
+ st->wfb_trg |= ADE9000_WFB_TRG_ZXVC_BIT;
+ else
+ st->wfb_trg &= ~ADE9000_WFB_TRG_ZXVC_BIT;
+ } else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) {
+ bit_mask = ADE9000_ST1_ZXIC_BIT;
+ if (state)
+ st->wfb_trg |= ADE9000_WFB_TRG_ZXIC_BIT;
+ else
+ st->wfb_trg &= ~ADE9000_WFB_TRG_ZXIC_BIT;
+ } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) {
+ bit_mask = ADE9000_ST1_SWELLC_BIT;
+ if (state)
+ st->wfb_trg |= ADE9000_WFB_TRG_SWELL_BIT;
+ else
+ st->wfb_trg &= ~ADE9000_WFB_TRG_SWELL_BIT;
+ } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) {
+ bit_mask = ADE9000_ST1_DIPC_BIT;
+ if (state)
+ st->wfb_trg |= ADE9000_WFB_TRG_DIP_BIT;
+ else
+ st->wfb_trg &= ~ADE9000_WFB_TRG_DIP_BIT;
+ } else {
+ dev_err_ratelimited(&indio_dev->dev,
+ "Invalid channel type %d or direction %d for phase C\n",
+ chan->type, dir);
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Set bits if enabling event, clear bits if disabling */
+ return regmap_assign_bits(st->regmap, ADE9000_REG_MASK1, bit_mask, state ? bit_mask : 0);
+}
+
+static int ade9000_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ struct ade9000_state *st = iio_priv(indio_dev);
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_FALLING:
+ return regmap_write(st->regmap, ADE9000_REG_DIP_LVL, val);
+ case IIO_EV_DIR_RISING:
+ return regmap_write(st->regmap, ADE9000_REG_SWELL_LVL, val);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ade9000_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct ade9000_state *st = iio_priv(indio_dev);
+ unsigned int data;
+ int ret;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_read(st->regmap, ADE9000_REG_DIP_LVL, &data);
+ if (ret)
+ return ret;
+ *val = data;
+ return IIO_VAL_INT;
+ case IIO_EV_DIR_RISING:
+ ret = regmap_read(st->regmap, ADE9000_REG_SWELL_LVL, &data);
+ if (ret)
+ return ret;
+ *val = data;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ade9000_waveform_buffer_config(struct iio_dev *indio_dev)
+{
+ struct ade9000_state *st = iio_priv(indio_dev);
+ u32 wfb_cfg_val;
+ u32 active_scans;
+
+ bitmap_to_arr32(&active_scans, indio_dev->active_scan_mask,
+ iio_get_masklength(indio_dev));
+
+ switch (active_scans) {
+ case ADE9000_SCAN_POS_IA | ADE9000_SCAN_POS_VA:
+ wfb_cfg_val = ADE9000_WFB_CFG_IA_VA;
+ st->wfb_nr_activ_chan = 2;
+ break;
+ case ADE9000_SCAN_POS_IB | ADE9000_SCAN_POS_VB:
+ wfb_cfg_val = ADE9000_WFB_CFG_IB_VB;
+ st->wfb_nr_activ_chan = 2;
+ break;
+ case ADE9000_SCAN_POS_IC | ADE9000_SCAN_POS_VC:
+ wfb_cfg_val = ADE9000_WFB_CFG_IC_VC;
+ st->wfb_nr_activ_chan = 2;
+ break;
+ case ADE9000_SCAN_POS_IA:
+ wfb_cfg_val = ADE9000_WFB_CFG_IA;
+ st->wfb_nr_activ_chan = 1;
+ break;
+ case ADE9000_SCAN_POS_VA:
+ wfb_cfg_val = ADE9000_WFB_CFG_VA;
+ st->wfb_nr_activ_chan = 1;
+ break;
+ case ADE9000_SCAN_POS_IB:
+ wfb_cfg_val = ADE9000_WFB_CFG_IB;
+ st->wfb_nr_activ_chan = 1;
+ break;
+ case ADE9000_SCAN_POS_VB:
+ wfb_cfg_val = ADE9000_WFB_CFG_VB;
+ st->wfb_nr_activ_chan = 1;
+ break;
+ case ADE9000_SCAN_POS_IC:
+ wfb_cfg_val = ADE9000_WFB_CFG_IC;
+ st->wfb_nr_activ_chan = 1;
+ break;
+ case ADE9000_SCAN_POS_VC:
+ wfb_cfg_val = ADE9000_WFB_CFG_VC;
+ st->wfb_nr_activ_chan = 1;
+ break;
+ case (ADE9000_SCAN_POS_IA | ADE9000_SCAN_POS_VA | ADE9000_SCAN_POS_IB |
+ ADE9000_SCAN_POS_VB | ADE9000_SCAN_POS_IC | ADE9000_SCAN_POS_VC):
+ wfb_cfg_val = ADE9000_WFB_CFG_ALL_CHAN;
+ st->wfb_nr_activ_chan = 6;
+ break;
+ default:
+ dev_err(&st->spi->dev, "Unsupported combination of scans\n");
+ return -EINVAL;
+ }
+
+ wfb_cfg_val |= FIELD_PREP(ADE9000_WF_SRC_MASK, st->wf_src);
+
+ return regmap_write(st->regmap, ADE9000_REG_WFB_CFG, wfb_cfg_val);
+}
+
+static int ade9000_waveform_buffer_interrupt_setup(struct ade9000_state *st)
+{
+ int ret;
+
+ ret = regmap_write(st->regmap, ADE9000_REG_WFB_TRG_CFG, 0x0);
+ if (ret)
+ return ret;
+
+ /* Always use streaming mode setup */
+ ret = regmap_write(st->regmap, ADE9000_REG_WFB_PG_IRQEN,
+ ADE9000_MIDDLE_PAGE_BIT);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, ADE9000_REG_STATUS0, GENMASK(31, 0));
+ if (ret)
+ return ret;
+
+ return regmap_set_bits(st->regmap, ADE9000_REG_MASK0,
+ ADE9000_ST0_PAGE_FULL_BIT);
+}
+
+static int ade9000_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct ade9000_state *st = iio_priv(indio_dev);
+ int ret;
+
+ ret = ade9000_waveform_buffer_config(indio_dev);
+ if (ret)
+ return ret;
+
+ st->wfb_nr_samples = ADE9000_WFB_MAX_SAMPLES_CHAN * st->wfb_nr_activ_chan;
+
+ ade9000_configure_scan(indio_dev, ADE9000_REG_WF_BUFF);
+
+ ret = ade9000_waveform_buffer_interrupt_setup(st);
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(st->regmap, ADE9000_REG_WFB_CFG,
+ ADE9000_WF_CAP_EN_MASK);
+ if (ret) {
+ dev_err(&st->spi->dev, "Post-enable waveform buffer enable fail\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ade9000_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct ade9000_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ u32 interrupts;
+ int ret;
+
+ ret = regmap_clear_bits(st->regmap, ADE9000_REG_WFB_CFG,
+ ADE9000_WF_CAP_EN_MASK);
+ if (ret) {
+ dev_err(dev, "Post-disable waveform buffer disable fail\n");
+ return ret;
+ }
+
+ ret = regmap_write(st->regmap, ADE9000_REG_WFB_TRG_CFG, 0x0);
+ if (ret)
+ return ret;
+
+ interrupts = ADE9000_ST0_WFB_TRIG_BIT | ADE9000_ST0_PAGE_FULL_BIT;
+
+ ret = regmap_clear_bits(st->regmap, ADE9000_REG_MASK0, interrupts);
+ if (ret) {
+ dev_err(dev, "Post-disable update maks0 fail\n");
+ return ret;
+ }
+
+ return regmap_write(st->regmap, ADE9000_REG_STATUS0, GENMASK(31, 0));
+}
+
+static const struct iio_buffer_setup_ops ade9000_buffer_ops = {
+ .preenable = &ade9000_buffer_preenable,
+ .postdisable = &ade9000_buffer_postdisable,
+};
+
+static int ade9000_reset(struct ade9000_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ struct gpio_desc *gpio_reset;
+ int ret;
+
+ gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(gpio_reset))
+ return PTR_ERR(gpio_reset);
+
+ /* Software reset via register if no GPIO available */
+ if (!gpio_reset) {
+ ret = regmap_set_bits(st->regmap, ADE9000_REG_CONFIG1,
+ ADE9000_SWRST_BIT);
+ if (ret)
+ return ret;
+ fsleep(90);
+ return 0;
+ }
+
+ /* Hardware reset via GPIO */
+ fsleep(10);
+ gpiod_set_value_cansleep(gpio_reset, 0);
+ fsleep(50000);
+
+ /* Only wait for completion if IRQ1 is available to signal reset done */
+ if (fwnode_irq_get_byname(dev_fwnode(dev), "irq1") >= 0) {
+ if (!wait_for_completion_timeout(&st->reset_completion,
+ msecs_to_jiffies(1000))) {
+ dev_err(dev, "Reset timeout after 1s\n");
+ return -ETIMEDOUT;
+ }
+ }
+ /* If no IRQ available, reset is already complete after the 50ms delay above */
+
+ return 0;
+}
+
+static int ade9000_setup(struct ade9000_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ int ret;
+
+ ret = regmap_multi_reg_write(st->regmap, ade9000_initialization_sequence,
+ ARRAY_SIZE(ade9000_initialization_sequence));
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write register sequence");
+
+ fsleep(2000);
+
+ return 0;
+}
+
+static const struct iio_info ade9000_info = {
+ .read_raw = ade9000_read_raw,
+ .write_raw = ade9000_write_raw,
+ .debugfs_reg_access = ade9000_reg_access,
+ .write_event_config = ade9000_write_event_config,
+ .read_event_config = ade9000_read_event_config,
+ .write_event_value = ade9000_write_event_value,
+ .read_event_value = ade9000_read_event_value,
+};
+
+static const struct regmap_config ade9000_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 32,
+ .max_register = 0x6bc,
+ .zero_flag_mask = true,
+ .cache_type = REGCACHE_RBTREE,
+ .reg_read = ade9000_spi_read_reg,
+ .reg_write = ade9000_spi_write_reg,
+ .volatile_reg = ade9000_is_volatile_reg,
+};
+
+static int ade9000_setup_clkout(struct device *dev, struct ade9000_state *st)
+{
+ struct clk_hw *clkout_hw;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_COMMON_CLK))
+ return 0;
+
+ /*
+ * Only provide clock output when using external CMOS clock.
+ * When using crystal, CLKOUT is connected to crystal and shouldn't
+ * be used as clock provider for other devices.
+ */
+ if (!device_property_present(dev, "#clock-cells") || !st->clkin)
+ return 0;
+
+ /* CLKOUT passes through CLKIN with divider of 1 */
+ clkout_hw = devm_clk_hw_register_divider(dev, "clkout", __clk_get_name(st->clkin),
+ CLK_SET_RATE_PARENT, NULL, 0, 1, 0, NULL);
+ if (IS_ERR(clkout_hw))
+ return dev_err_probe(dev, PTR_ERR(clkout_hw), "Failed to register clkout");
+
+ ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, clkout_hw);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add clock provider");
+
+ return 0;
+}
+
+static int ade9000_request_irq(struct device *dev, const char *name,
+ irq_handler_t handler, void *dev_id)
+{
+ int irq, ret;
+
+ irq = fwnode_irq_get_byname(dev_fwnode(dev), name);
+ if (irq == -EINVAL)
+ return 0; /* interrupts are optional */
+ if (irq < 0)
+ return dev_err_probe(dev, irq, "Failed to get %s irq", name);
+
+ ret = devm_request_threaded_irq(dev, irq, NULL, handler,
+ IRQF_ONESHOT, KBUILD_MODNAME, dev_id);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request %s irq", name);
+
+ return 0;
+}
+
+static int ade9000_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ade9000_state *st;
+ struct regmap *regmap;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ regmap = devm_regmap_init(dev, NULL, st, &ade9000_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Unable to allocate ADE9000 regmap");
+
+ st->regmap = regmap;
+ st->spi = spi;
+
+ init_completion(&st->reset_completion);
+
+ ret = ade9000_request_irq(dev, "irq0", ade9000_irq0_thread, indio_dev);
+ if (ret)
+ return ret;
+
+ ret = ade9000_request_irq(dev, "irq1", ade9000_irq1_thread, indio_dev);
+ if (ret)
+ return ret;
+
+ ret = ade9000_request_irq(dev, "dready", ade9000_dready_thread, indio_dev);
+ if (ret)
+ return ret;
+
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ /* External CMOS clock input (optional - crystal can be used instead) */
+ st->clkin = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(st->clkin))
+ return dev_err_probe(dev, PTR_ERR(st->clkin), "Failed to get and enable clkin");
+
+ ret = ade9000_setup_clkout(dev, st);
+ if (ret)
+ return ret;
+
+ indio_dev->name = "ade9000";
+ indio_dev->info = &ade9000_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->setup_ops = &ade9000_buffer_ops;
+
+ ret = devm_regulator_get_enable(&spi->dev, "vdd");
+ if (ret)
+ return dev_err_probe(&spi->dev, ret,
+ "Failed to get and enable vdd regulator\n");
+
+ indio_dev->channels = ade9000_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ade9000_channels);
+
+ ret = devm_iio_kfifo_buffer_setup(dev, indio_dev,
+ &ade9000_buffer_ops);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to setup IIO buffer");
+
+ ret = ade9000_reset(st);
+ if (ret)
+ return ret;
+
+ /* Configure reference selection if vref regulator is available */
+ ret = devm_regulator_get_enable_optional(dev, "vref");
+ if (ret != -ENODEV && ret >= 0) {
+ ret = regmap_set_bits(st->regmap, ADE9000_REG_CONFIG1,
+ ADE9000_EXT_REF_MASK);
+ if (ret)
+ return ret;
+ } else if (ret < 0 && ret != -ENODEV) {
+ return dev_err_probe(dev, ret,
+ "Failed to get and enable vref regulator\n");
+ }
+
+ ret = ade9000_setup(st);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+};
+
+static const struct spi_device_id ade9000_id[] = {
+ { "ade9000", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ade9000_id);
+
+static const struct of_device_id ade9000_of_match[] = {
+ { .compatible = "adi,ade9000" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ade9000_of_match);
+
+static struct spi_driver ade9000_driver = {
+ .driver = {
+ .name = "ade9000",
+ .of_match_table = ade9000_of_match,
+ },
+ .probe = ade9000_probe,
+ .id_table = ade9000_id,
+};
+module_spi_driver(ade9000_driver);
+
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADE9000");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c
index eb42e29960e4..14fa4238c2b9 100644
--- a/drivers/iio/adc/adi-axi-adc.c
+++ b/drivers/iio/adc/adi-axi-adc.c
@@ -618,6 +618,7 @@ static const struct iio_backend_ops adi_axi_adc_ops = {
.chan_status = axi_adc_chan_status,
.interface_type_get = axi_adc_interface_type_get,
.oversampling_ratio_set = axi_adc_oversampling_ratio_set,
+ .num_lanes_set = axi_adc_num_lanes_set,
.debugfs_reg_access = iio_backend_debugfs_ptr(axi_adc_reg_access),
.debugfs_print_chan_status = iio_backend_debugfs_ptr(axi_adc_debugfs_print_chan_status),
};
diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c
index c3450246730e..b4c36e6a7490 100644
--- a/drivers/iio/adc/at91-sama5d2_adc.c
+++ b/drivers/iio/adc/at91-sama5d2_adc.c
@@ -896,7 +896,6 @@ static int at91_adc_config_emr(struct at91_adc_state *st,
emr |= osr | AT91_SAMA5D2_TRACKX(trackx);
at91_adc_writel(st, EMR, emr);
- pm_runtime_mark_last_busy(st->dev);
pm_runtime_put_autosuspend(st->dev);
st->oversampling_ratio = oversampling_ratio;
@@ -971,7 +970,6 @@ static int at91_adc_configure_touch(struct at91_adc_state *st, bool state)
AT91_SAMA5D2_IER_PEN | AT91_SAMA5D2_IER_NOPEN);
at91_adc_writel(st, TSMR, 0);
- pm_runtime_mark_last_busy(st->dev);
pm_runtime_put_autosuspend(st->dev);
return 0;
}
@@ -1142,10 +1140,8 @@ static int at91_adc_configure_trigger(struct iio_trigger *trig, bool state)
at91_adc_configure_trigger_registers(st, state);
- if (!state) {
- pm_runtime_mark_last_busy(st->dev);
+ if (!state)
pm_runtime_put_autosuspend(st->dev);
- }
return 0;
}
@@ -1336,7 +1332,6 @@ static int at91_adc_buffer_prepare(struct iio_dev *indio_dev)
at91_adc_writel(st, IER, AT91_SAMA5D2_IER_DRDY);
pm_runtime_put:
- pm_runtime_mark_last_busy(st->dev);
pm_runtime_put_autosuspend(st->dev);
return ret;
}
@@ -1394,7 +1389,6 @@ static int at91_adc_buffer_postdisable(struct iio_dev *indio_dev)
if (st->dma_st.dma_chan)
dmaengine_terminate_sync(st->dma_st.dma_chan);
- pm_runtime_mark_last_busy(st->dev);
pm_runtime_put_autosuspend(st->dev);
return 0;
@@ -1603,7 +1597,6 @@ static void at91_adc_setup_samp_freq(struct iio_dev *indio_dev, unsigned freq,
mr |= AT91_SAMA5D2_MR_TRACKTIM(tracktim);
at91_adc_writel(st, MR, mr);
- pm_runtime_mark_last_busy(st->dev);
pm_runtime_put_autosuspend(st->dev);
dev_dbg(&indio_dev->dev, "freq: %u, startup: %u, prescal: %u, tracktim=%u\n",
@@ -1809,7 +1802,6 @@ static int at91_adc_read_info_raw(struct iio_dev *indio_dev,
at91_adc_readl(st, LCDR);
pm_runtime_put:
- pm_runtime_mark_last_busy(st->dev);
pm_runtime_put_autosuspend(st->dev);
return ret;
}
@@ -1890,7 +1882,6 @@ static int at91_adc_read_temp(struct iio_dev *indio_dev,
restore_config:
/* Revert previous settings. */
at91_adc_temp_sensor_configure(st, false);
- pm_runtime_mark_last_busy(st->dev);
pm_runtime_put_autosuspend(st->dev);
if (ret < 0)
return ret;
@@ -2465,7 +2456,6 @@ static int at91_adc_probe(struct platform_device *pdev)
dev_info(&pdev->dev, "version: %x\n",
readl_relaxed(st->base + st->soc_info.platform->layout->VERSION));
- pm_runtime_mark_last_busy(st->dev);
pm_runtime_put_autosuspend(st->dev);
return 0;
@@ -2567,7 +2557,6 @@ static int at91_adc_resume(struct device *dev)
at91_adc_configure_trigger_registers(st, true);
}
- pm_runtime_mark_last_busy(st->dev);
pm_runtime_put_autosuspend(st->dev);
return 0;
diff --git a/drivers/iio/adc/bcm_iproc_adc.c b/drivers/iio/adc/bcm_iproc_adc.c
index f258668b0dc7..6426c9e6ccc9 100644
--- a/drivers/iio/adc/bcm_iproc_adc.c
+++ b/drivers/iio/adc/bcm_iproc_adc.c
@@ -511,10 +511,8 @@ static int iproc_adc_probe(struct platform_device *pdev)
indio_dev = devm_iio_device_alloc(&pdev->dev,
sizeof(*adc_priv));
- if (!indio_dev) {
- dev_err(&pdev->dev, "failed to allocate iio device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
adc_priv = iio_priv(indio_dev);
platform_set_drvdata(pdev, indio_dev);
diff --git a/drivers/iio/adc/cpcap-adc.c b/drivers/iio/adc/cpcap-adc.c
index ba7cbd3b4822..d9ee2ea116a7 100644
--- a/drivers/iio/adc/cpcap-adc.c
+++ b/drivers/iio/adc/cpcap-adc.c
@@ -953,11 +953,9 @@ static int cpcap_adc_probe(struct platform_device *pdev)
int error;
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*ddata));
- if (!indio_dev) {
- dev_err(&pdev->dev, "failed to allocate iio device\n");
-
+ if (!indio_dev)
return -ENOMEM;
- }
+
ddata = iio_priv(indio_dev);
ddata->ato = device_get_match_data(&pdev->dev);
if (!ddata->ato)
diff --git a/drivers/iio/adc/da9150-gpadc.c b/drivers/iio/adc/da9150-gpadc.c
index b99291ce2a45..625e3a8e4d03 100644
--- a/drivers/iio/adc/da9150-gpadc.c
+++ b/drivers/iio/adc/da9150-gpadc.c
@@ -308,10 +308,9 @@ static int da9150_gpadc_probe(struct platform_device *pdev)
int irq, ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*gpadc));
- if (!indio_dev) {
- dev_err(&pdev->dev, "Failed to allocate IIO device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
+
gpadc = iio_priv(indio_dev);
gpadc->da9150 = da9150;
diff --git a/drivers/iio/adc/dln2-adc.c b/drivers/iio/adc/dln2-adc.c
index 5aea7644780f..eb902a946efe 100644
--- a/drivers/iio/adc/dln2-adc.c
+++ b/drivers/iio/adc/dln2-adc.c
@@ -584,10 +584,8 @@ static int dln2_adc_probe(struct platform_device *pdev)
int i, ret, chans;
indio_dev = devm_iio_device_alloc(dev, sizeof(*dln2));
- if (!indio_dev) {
- dev_err(dev, "failed allocating iio device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
dln2 = iio_priv(indio_dev);
dln2->pdev = pdev;
@@ -628,10 +626,9 @@ static int dln2_adc_probe(struct platform_device *pdev)
dln2->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
indio_dev->name,
iio_device_id(indio_dev));
- if (!dln2->trig) {
- dev_err(dev, "failed to allocate trigger\n");
+ if (!dln2->trig)
return -ENOMEM;
- }
+
iio_trigger_set_drvdata(dln2->trig, dln2);
ret = devm_iio_trigger_register(dev, dln2->trig);
if (ret) {
diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c
index 4614cf848535..1484adff00df 100644
--- a/drivers/iio/adc/exynos_adc.c
+++ b/drivers/iio/adc/exynos_adc.c
@@ -19,11 +19,9 @@
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/of.h>
-#include <linux/of_irq.h>
#include <linux/regulator/consumer.h>
#include <linux/of_platform.h>
#include <linux/err.h>
-#include <linux/input.h>
#include <linux/iio/iio.h>
#include <linux/iio/machine.h>
@@ -31,21 +29,14 @@
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
-#include <linux/platform_data/touchscreen-s3c2410.h>
-
/* S3C/EXYNOS4412/5250 ADC_V1 registers definitions */
#define ADC_V1_CON(x) ((x) + 0x00)
-#define ADC_V1_TSC(x) ((x) + 0x04)
#define ADC_V1_DLY(x) ((x) + 0x08)
#define ADC_V1_DATX(x) ((x) + 0x0C)
#define ADC_V1_DATY(x) ((x) + 0x10)
#define ADC_V1_UPDN(x) ((x) + 0x14)
#define ADC_V1_INTCLR(x) ((x) + 0x18)
#define ADC_V1_MUX(x) ((x) + 0x1c)
-#define ADC_V1_CLRINTPNDNUP(x) ((x) + 0x20)
-
-/* S3C2410 ADC registers definitions */
-#define ADC_S3C2410_MUX(x) ((x) + 0x18)
/* Future ADC_V2 registers definitions */
#define ADC_V2_CON1(x) ((x) + 0x00)
@@ -61,13 +52,8 @@
#define ADC_V1_CON_PRSCLV(x) (((x) & 0xFF) << 6)
#define ADC_V1_CON_STANDBY (1u << 2)
-/* Bit definitions for S3C2410 ADC */
+/* Bit definitions for S3C2410 / S3C6410 ADC */
#define ADC_S3C2410_CON_SELMUX(x) (((x) & 7) << 3)
-#define ADC_S3C2410_DATX_MASK 0x3FF
-#define ADC_S3C2416_CON_RES_SEL (1u << 3)
-
-/* touch screen always uses channel 0 */
-#define ADC_S3C2410_MUX_TS 0
/* ADCTSC Register Bits */
#define ADC_S3C2443_TSC_UD_SEN (1u << 8)
@@ -75,8 +61,6 @@
#define ADC_S3C2410_TSC_YP_SEN (1u << 6)
#define ADC_S3C2410_TSC_XM_SEN (1u << 5)
#define ADC_S3C2410_TSC_XP_SEN (1u << 4)
-#define ADC_S3C2410_TSC_PULL_UP_DISABLE (1u << 3)
-#define ADC_S3C2410_TSC_AUTO_PST (1u << 2)
#define ADC_S3C2410_TSC_XY_PST(x) (((x) & 0x3) << 0)
#define ADC_TSC_WAIT4INT (ADC_S3C2410_TSC_YM_SEN | \
@@ -84,12 +68,6 @@
ADC_S3C2410_TSC_XP_SEN | \
ADC_S3C2410_TSC_XY_PST(3))
-#define ADC_TSC_AUTOPST (ADC_S3C2410_TSC_YM_SEN | \
- ADC_S3C2410_TSC_YP_SEN | \
- ADC_S3C2410_TSC_XP_SEN | \
- ADC_S3C2410_TSC_AUTO_PST | \
- ADC_S3C2410_TSC_XY_PST(0))
-
/* Bit definitions for ADC_V2 */
#define ADC_V2_CON1_SOFT_RESET (1u << 2)
@@ -121,14 +99,11 @@
struct exynos_adc {
struct exynos_adc_data *data;
struct device *dev;
- struct input_dev *input;
void __iomem *regs;
struct regmap *pmu_map;
struct clk *clk;
struct clk *sclk;
unsigned int irq;
- unsigned int tsirq;
- unsigned int delay;
struct regulator *vdd;
struct completion completion;
@@ -136,12 +111,6 @@ struct exynos_adc {
u32 value;
unsigned int version;
- bool ts_enabled;
-
- bool read_ts;
- u32 ts_x;
- u32 ts_y;
-
/*
* Lock to protect from potential concurrent access to the
* completion callback during a manual conversion. For this driver
@@ -241,7 +210,7 @@ static void exynos_adc_v1_init_hw(struct exynos_adc *info)
writel(con1, ADC_V1_CON(info->regs));
/* set touchscreen delay */
- writel(info->delay, ADC_V1_DLY(info->regs));
+ writel(10000, ADC_V1_DLY(info->regs));
}
static void exynos_adc_v1_exit_hw(struct exynos_adc *info)
@@ -307,53 +276,6 @@ static const struct exynos_adc_data exynos_adc_s5pv210_data = {
.start_conv = exynos_adc_v1_start_conv,
};
-static void exynos_adc_s3c2416_start_conv(struct exynos_adc *info,
- unsigned long addr)
-{
- u32 con1;
-
- /* Enable 12 bit ADC resolution */
- con1 = readl(ADC_V1_CON(info->regs));
- con1 |= ADC_S3C2416_CON_RES_SEL;
- writel(con1, ADC_V1_CON(info->regs));
-
- /* Select channel for S3C2416 */
- writel(addr, ADC_S3C2410_MUX(info->regs));
-
- con1 = readl(ADC_V1_CON(info->regs));
- writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs));
-}
-
-static struct exynos_adc_data const exynos_adc_s3c2416_data = {
- .num_channels = MAX_ADC_V1_CHANNELS,
- .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */
-
- .init_hw = exynos_adc_v1_init_hw,
- .exit_hw = exynos_adc_v1_exit_hw,
- .start_conv = exynos_adc_s3c2416_start_conv,
-};
-
-static void exynos_adc_s3c2443_start_conv(struct exynos_adc *info,
- unsigned long addr)
-{
- u32 con1;
-
- /* Select channel for S3C2433 */
- writel(addr, ADC_S3C2410_MUX(info->regs));
-
- con1 = readl(ADC_V1_CON(info->regs));
- writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs));
-}
-
-static struct exynos_adc_data const exynos_adc_s3c2443_data = {
- .num_channels = MAX_ADC_V1_CHANNELS,
- .mask = ADC_S3C2410_DATX_MASK, /* 10 bit ADC resolution */
-
- .init_hw = exynos_adc_v1_init_hw,
- .exit_hw = exynos_adc_v1_exit_hw,
- .start_conv = exynos_adc_s3c2443_start_conv,
-};
-
static void exynos_adc_s3c64xx_start_conv(struct exynos_adc *info,
unsigned long addr)
{
@@ -365,15 +287,6 @@ static void exynos_adc_s3c64xx_start_conv(struct exynos_adc *info,
writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs));
}
-static struct exynos_adc_data const exynos_adc_s3c24xx_data = {
- .num_channels = MAX_ADC_V1_CHANNELS,
- .mask = ADC_S3C2410_DATX_MASK, /* 10 bit ADC resolution */
-
- .init_hw = exynos_adc_v1_init_hw,
- .exit_hw = exynos_adc_v1_exit_hw,
- .start_conv = exynos_adc_s3c64xx_start_conv,
-};
-
static struct exynos_adc_data const exynos_adc_s3c64xx_data = {
.num_channels = MAX_ADC_V1_CHANNELS,
.mask = ADC_DATX_MASK, /* 12 bit ADC resolution */
@@ -486,18 +399,6 @@ static const struct exynos_adc_data exynos7_adc_data = {
static const struct of_device_id exynos_adc_match[] = {
{
- .compatible = "samsung,s3c2410-adc",
- .data = &exynos_adc_s3c24xx_data,
- }, {
- .compatible = "samsung,s3c2416-adc",
- .data = &exynos_adc_s3c2416_data,
- }, {
- .compatible = "samsung,s3c2440-adc",
- .data = &exynos_adc_s3c24xx_data,
- }, {
- .compatible = "samsung,s3c2443-adc",
- .data = &exynos_adc_s3c2443_data,
- }, {
.compatible = "samsung,s3c6410-adc",
.data = &exynos_adc_s3c64xx_data,
}, {
@@ -580,55 +481,13 @@ static int exynos_read_raw(struct iio_dev *indio_dev,
return ret;
}
-static int exynos_read_s3c64xx_ts(struct iio_dev *indio_dev, int *x, int *y)
-{
- struct exynos_adc *info = iio_priv(indio_dev);
- unsigned long time_left;
- int ret;
-
- mutex_lock(&info->lock);
- info->read_ts = true;
-
- reinit_completion(&info->completion);
-
- writel(ADC_S3C2410_TSC_PULL_UP_DISABLE | ADC_TSC_AUTOPST,
- ADC_V1_TSC(info->regs));
-
- /* Select the ts channel to be used and Trigger conversion */
- info->data->start_conv(info, ADC_S3C2410_MUX_TS);
-
- time_left = wait_for_completion_timeout(&info->completion,
- EXYNOS_ADC_TIMEOUT);
- if (time_left == 0) {
- dev_warn(&indio_dev->dev, "Conversion timed out! Resetting\n");
- if (info->data->init_hw)
- info->data->init_hw(info);
- ret = -ETIMEDOUT;
- } else {
- *x = info->ts_x;
- *y = info->ts_y;
- ret = 0;
- }
-
- info->read_ts = false;
- mutex_unlock(&info->lock);
-
- return ret;
-}
-
static irqreturn_t exynos_adc_isr(int irq, void *dev_id)
{
struct exynos_adc *info = dev_id;
u32 mask = info->data->mask;
/* Read value */
- if (info->read_ts) {
- info->ts_x = readl(ADC_V1_DATX(info->regs));
- info->ts_y = readl(ADC_V1_DATY(info->regs));
- writel(ADC_TSC_WAIT4INT | ADC_S3C2443_TSC_UD_SEN, ADC_V1_TSC(info->regs));
- } else {
- info->value = readl(ADC_V1_DATX(info->regs)) & mask;
- }
+ info->value = readl(ADC_V1_DATX(info->regs)) & mask;
/* clear irq */
if (info->data->clear_irq)
@@ -639,46 +498,6 @@ static irqreturn_t exynos_adc_isr(int irq, void *dev_id)
return IRQ_HANDLED;
}
-/*
- * Here we (ab)use a threaded interrupt handler to stay running
- * for as long as the touchscreen remains pressed, we report
- * a new event with the latest data and then sleep until the
- * next timer tick. This mirrors the behavior of the old
- * driver, with much less code.
- */
-static irqreturn_t exynos_ts_isr(int irq, void *dev_id)
-{
- struct exynos_adc *info = dev_id;
- struct iio_dev *dev = dev_get_drvdata(info->dev);
- u32 x, y;
- bool pressed;
- int ret;
-
- while (READ_ONCE(info->ts_enabled)) {
- ret = exynos_read_s3c64xx_ts(dev, &x, &y);
- if (ret == -ETIMEDOUT)
- break;
-
- pressed = x & y & ADC_DATX_PRESSED;
- if (!pressed) {
- input_report_key(info->input, BTN_TOUCH, 0);
- input_sync(info->input);
- break;
- }
-
- input_report_abs(info->input, ABS_X, x & ADC_DATX_MASK);
- input_report_abs(info->input, ABS_Y, y & ADC_DATY_MASK);
- input_report_key(info->input, BTN_TOUCH, 1);
- input_sync(info->input);
-
- usleep_range(1000, 1100);
- }
-
- writel(0, ADC_V1_CLRINTPNDNUP(info->regs));
-
- return IRQ_HANDLED;
-}
-
static int exynos_adc_reg_access(struct iio_dev *indio_dev,
unsigned reg, unsigned writeval,
unsigned *readval)
@@ -730,78 +549,17 @@ static int exynos_adc_remove_devices(struct device *dev, void *c)
return 0;
}
-static int exynos_adc_ts_open(struct input_dev *dev)
-{
- struct exynos_adc *info = input_get_drvdata(dev);
-
- WRITE_ONCE(info->ts_enabled, true);
- enable_irq(info->tsirq);
-
- return 0;
-}
-
-static void exynos_adc_ts_close(struct input_dev *dev)
-{
- struct exynos_adc *info = input_get_drvdata(dev);
-
- WRITE_ONCE(info->ts_enabled, false);
- disable_irq(info->tsirq);
-}
-
-static int exynos_adc_ts_init(struct exynos_adc *info)
-{
- int ret;
-
- if (info->tsirq <= 0)
- return -ENODEV;
-
- info->input = input_allocate_device();
- if (!info->input)
- return -ENOMEM;
-
- info->input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
- info->input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
-
- input_set_abs_params(info->input, ABS_X, 0, 0x3FF, 0, 0);
- input_set_abs_params(info->input, ABS_Y, 0, 0x3FF, 0, 0);
-
- info->input->name = "S3C24xx TouchScreen";
- info->input->id.bustype = BUS_HOST;
- info->input->open = exynos_adc_ts_open;
- info->input->close = exynos_adc_ts_close;
-
- input_set_drvdata(info->input, info);
-
- ret = input_register_device(info->input);
- if (ret) {
- input_free_device(info->input);
- return ret;
- }
-
- ret = request_threaded_irq(info->tsirq, NULL, exynos_ts_isr,
- IRQF_ONESHOT | IRQF_NO_AUTOEN,
- "touchscreen", info);
- if (ret)
- input_unregister_device(info->input);
-
- return ret;
-}
-
static int exynos_adc_probe(struct platform_device *pdev)
{
struct exynos_adc *info = NULL;
struct device_node *np = pdev->dev.of_node;
- struct s3c2410_ts_mach_info *pdata = dev_get_platdata(&pdev->dev);
struct iio_dev *indio_dev = NULL;
- bool has_ts = false;
int ret;
int irq;
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct exynos_adc));
- if (!indio_dev) {
- dev_err(&pdev->dev, "failed allocating iio device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
info = iio_priv(indio_dev);
@@ -826,27 +584,10 @@ static int exynos_adc_probe(struct platform_device *pdev)
}
}
- /* leave out any TS related code if unreachable */
- if (IS_REACHABLE(CONFIG_INPUT)) {
- has_ts = of_property_read_bool(pdev->dev.of_node,
- "has-touchscreen") || pdata;
- }
-
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
info->irq = irq;
-
- if (has_ts) {
- irq = platform_get_irq(pdev, 1);
- if (irq == -EPROBE_DEFER)
- return irq;
-
- info->tsirq = irq;
- } else {
- info->tsirq = -1;
- }
-
info->dev = &pdev->dev;
init_completion(&info->completion);
@@ -910,16 +651,6 @@ static int exynos_adc_probe(struct platform_device *pdev)
if (info->data->init_hw)
info->data->init_hw(info);
- if (pdata)
- info->delay = pdata->delay;
- else
- info->delay = 10000;
-
- if (has_ts)
- ret = exynos_adc_ts_init(info);
- if (ret)
- goto err_iio;
-
ret = of_platform_populate(np, exynos_adc_match, NULL, &indio_dev->dev);
if (ret < 0) {
dev_err(&pdev->dev, "failed adding child nodes\n");
@@ -931,11 +662,6 @@ static int exynos_adc_probe(struct platform_device *pdev)
err_of_populate:
device_for_each_child(&indio_dev->dev, NULL,
exynos_adc_remove_devices);
- if (has_ts) {
- input_unregister_device(info->input);
- free_irq(info->tsirq, info);
- }
-err_iio:
iio_device_unregister(indio_dev);
err_irq:
free_irq(info->irq, info);
@@ -955,10 +681,6 @@ static void exynos_adc_remove(struct platform_device *pdev)
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct exynos_adc *info = iio_priv(indio_dev);
- if (IS_REACHABLE(CONFIG_INPUT) && info->input) {
- free_irq(info->tsirq, info);
- input_unregister_device(info->input);
- }
device_for_each_child(&indio_dev->dev, NULL,
exynos_adc_remove_devices);
iio_device_unregister(indio_dev);
diff --git a/drivers/iio/adc/hx711.c b/drivers/iio/adc/hx711.c
index 7235fa9e13d5..1db8b68a8f64 100644
--- a/drivers/iio/adc/hx711.c
+++ b/drivers/iio/adc/hx711.c
@@ -465,7 +465,7 @@ static int hx711_probe(struct platform_device *pdev)
indio_dev = devm_iio_device_alloc(dev, sizeof(struct hx711_data));
if (!indio_dev)
- return dev_err_probe(dev, -ENOMEM, "failed to allocate IIO device\n");
+ return -ENOMEM;
hx711_data = iio_priv(indio_dev);
hx711_data->dev = dev;
diff --git a/drivers/iio/adc/imx7d_adc.c b/drivers/iio/adc/imx7d_adc.c
index 09ce71f6e941..039c0387da23 100644
--- a/drivers/iio/adc/imx7d_adc.c
+++ b/drivers/iio/adc/imx7d_adc.c
@@ -482,10 +482,8 @@ static int imx7d_adc_probe(struct platform_device *pdev)
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*info));
- if (!indio_dev) {
- dev_err(&pdev->dev, "Failed allocating iio device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
info = iio_priv(indio_dev);
info->dev = dev;
diff --git a/drivers/iio/adc/imx8qxp-adc.c b/drivers/iio/adc/imx8qxp-adc.c
index be13a6ed7e00..6fc50394ad90 100644
--- a/drivers/iio/adc/imx8qxp-adc.c
+++ b/drivers/iio/adc/imx8qxp-adc.c
@@ -229,7 +229,6 @@ static int imx8qxp_adc_read_raw(struct iio_dev *indio_dev,
ret = wait_for_completion_interruptible_timeout(&adc->completion,
IMX8QXP_ADC_TIMEOUT);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_sync_autosuspend(dev);
if (ret == 0) {
@@ -295,7 +294,6 @@ static int imx8qxp_adc_reg_access(struct iio_dev *indio_dev, unsigned int reg,
*readval = readl(adc->regs + reg);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_sync_autosuspend(dev);
return 0;
@@ -315,10 +313,8 @@ static int imx8qxp_adc_probe(struct platform_device *pdev)
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
- if (!indio_dev) {
- dev_err(dev, "Failed allocating iio device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
adc = iio_priv(indio_dev);
adc->dev = dev;
diff --git a/drivers/iio/adc/imx93_adc.c b/drivers/iio/adc/imx93_adc.c
index 7feaafd2316f..787e80db5de3 100644
--- a/drivers/iio/adc/imx93_adc.c
+++ b/drivers/iio/adc/imx93_adc.c
@@ -32,12 +32,13 @@
#define IMX93_ADC_PCDR0 0x100
#define IMX93_ADC_PCDR1 0x104
#define IMX93_ADC_PCDR2 0x108
-#define IMX93_ADC_PCDR3 0x10c
+#define IMX93_ADC_PCDR3 0x10C
#define IMX93_ADC_PCDR4 0x110
#define IMX93_ADC_PCDR5 0x114
#define IMX93_ADC_PCDR6 0x118
-#define IMX93_ADC_PCDR7 0x11c
+#define IMX93_ADC_PCDR7 0x11C
#define IMX93_ADC_CALSTAT 0x39C
+#define IMX93_ADC_CALCFG0 0x3A0
/* ADC bit shift */
#define IMX93_ADC_MCR_MODE_MASK BIT(29)
@@ -58,6 +59,8 @@
#define IMX93_ADC_IMR_ECH_MASK BIT(0)
#define IMX93_ADC_PCDR_CDATA_MASK GENMASK(11, 0)
+#define IMX93_ADC_CALCFG0_LDFAIL_MASK BIT(4)
+
/* ADC status */
#define IMX93_ADC_MSR_ADCSTATUS_IDLE 0
#define IMX93_ADC_MSR_ADCSTATUS_POWER_DOWN 1
@@ -145,7 +148,7 @@ static void imx93_adc_config_ad_clk(struct imx93_adc *adc)
static int imx93_adc_calibration(struct imx93_adc *adc)
{
- u32 mcr, msr;
+ u32 mcr, msr, calcfg;
int ret;
/* make sure ADC in power down mode */
@@ -158,6 +161,11 @@ static int imx93_adc_calibration(struct imx93_adc *adc)
imx93_adc_power_up(adc);
+ /* Enable loading of calibrated values even in fail condition */
+ calcfg = readl(adc->regs + IMX93_ADC_CALCFG0);
+ calcfg |= IMX93_ADC_CALCFG0_LDFAIL_MASK;
+ writel(calcfg, adc->regs + IMX93_ADC_CALCFG0);
+
/*
* TODO: we use the default TSAMP/NRSMPL/AVGEN in MCR,
* can add the setting of these bit if need in future.
@@ -180,9 +188,13 @@ static int imx93_adc_calibration(struct imx93_adc *adc)
/* check whether calbration is success or not */
msr = readl(adc->regs + IMX93_ADC_MSR);
if (msr & IMX93_ADC_MSR_CALFAIL_MASK) {
+ /*
+ * Only give warning here, this means the noise of the
+ * reference voltage do not meet the requirement:
+ * ADC reference voltage Noise < 1.8V * 1/2^ENOB
+ * And the resault of ADC is not that accurate.
+ */
dev_warn(adc->dev, "ADC calibration failed!\n");
- imx93_adc_power_down(adc);
- return -EAGAIN;
}
return 0;
@@ -248,7 +260,6 @@ static int imx93_adc_read_raw(struct iio_dev *indio_dev,
mutex_lock(&adc->lock);
ret = imx93_adc_read_channel_conversion(adc, chan->channel, val);
mutex_unlock(&adc->lock);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_sync_autosuspend(dev);
if (ret < 0)
return ret;
@@ -308,8 +319,7 @@ static int imx93_adc_probe(struct platform_device *pdev)
indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
if (!indio_dev)
- return dev_err_probe(dev, -ENOMEM,
- "Failed allocating iio device\n");
+ return -ENOMEM;
adc = iio_priv(indio_dev);
adc->dev = dev;
diff --git a/drivers/iio/adc/intel_dc_ti_adc.c b/drivers/iio/adc/intel_dc_ti_adc.c
new file mode 100644
index 000000000000..0fe34f1c338e
--- /dev/null
+++ b/drivers/iio/adc/intel_dc_ti_adc.c
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Dollar Cove TI PMIC GPADC Driver
+ *
+ * Copyright (C) 2014 Intel Corporation (Ramakrishna Pallala <ramakrishna.pallala@intel.com>)
+ * Copyright (C) 2024 - 2025 Hans de Goede <hansg@kernel.org>
+ */
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/intel_soc_pmic.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/wait.h>
+
+#include <linux/iio/driver.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/machine.h>
+
+#define DC_TI_ADC_CNTL_REG 0x50
+#define DC_TI_ADC_START BIT(0)
+#define DC_TI_ADC_CH_SEL GENMASK(2, 1)
+#define DC_TI_ADC_EN BIT(5)
+#define DC_TI_ADC_EN_EXT_BPTH_BIAS BIT(6)
+
+#define DC_TI_VBAT_ZSE_GE_REG 0x53
+#define DC_TI_VBAT_GE GENMASK(3, 0)
+#define DC_TI_VBAT_ZSE GENMASK(7, 4)
+
+/* VBAT GE gain correction is in 0.0015 increments, ZSE is in 1.0 increments */
+#define DC_TI_VBAT_GE_STEP 15
+#define DC_TI_VBAT_GE_DIV 10000
+
+#define DC_TI_ADC_DATA_REG_CH(x) (0x54 + 2 * (x))
+
+enum dc_ti_adc_id {
+ DC_TI_ADC_VBAT,
+ DC_TI_ADC_PMICTEMP,
+ DC_TI_ADC_BATTEMP,
+ DC_TI_ADC_SYSTEMP0,
+};
+
+struct dc_ti_adc_info {
+ struct mutex lock; /* Protects against concurrent accesses to the ADC */
+ wait_queue_head_t wait;
+ struct device *dev;
+ struct regmap *regmap;
+ int vbat_zse;
+ int vbat_ge;
+ bool conversion_done;
+};
+
+static const struct iio_chan_spec dc_ti_adc_channels[] = {
+ {
+ .indexed = 1,
+ .type = IIO_VOLTAGE,
+ .channel = DC_TI_ADC_VBAT,
+ .address = DC_TI_ADC_DATA_REG_CH(0),
+ .datasheet_name = "CH0",
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_PROCESSED),
+ }, {
+ .indexed = 1,
+ .type = IIO_TEMP,
+ .channel = DC_TI_ADC_PMICTEMP,
+ .address = DC_TI_ADC_DATA_REG_CH(1),
+ .datasheet_name = "CH1",
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ }, {
+ .indexed = 1,
+ .type = IIO_TEMP,
+ .channel = DC_TI_ADC_BATTEMP,
+ .address = DC_TI_ADC_DATA_REG_CH(2),
+ .datasheet_name = "CH2",
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ }, {
+ .indexed = 1,
+ .type = IIO_TEMP,
+ .channel = DC_TI_ADC_SYSTEMP0,
+ .address = DC_TI_ADC_DATA_REG_CH(3),
+ .datasheet_name = "CH3",
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ }
+};
+
+static struct iio_map dc_ti_adc_default_maps[] = {
+ IIO_MAP("CH0", "chtdc_ti_battery", "VBAT"),
+ IIO_MAP("CH1", "chtdc_ti_battery", "PMICTEMP"),
+ IIO_MAP("CH2", "chtdc_ti_battery", "BATTEMP"),
+ IIO_MAP("CH3", "chtdc_ti_battery", "SYSTEMP0"),
+ { }
+};
+
+static irqreturn_t dc_ti_adc_isr(int irq, void *data)
+{
+ struct dc_ti_adc_info *info = data;
+
+ info->conversion_done = true;
+ wake_up(&info->wait);
+ return IRQ_HANDLED;
+}
+
+static int dc_ti_adc_scale(struct dc_ti_adc_info *info,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2)
+{
+ if (chan->channel != DC_TI_ADC_VBAT)
+ return -EINVAL;
+
+ /* Vbat ADC scale is 4.6875 mV / unit */
+ *val = 4;
+ *val2 = 687500;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int dc_ti_adc_raw_to_processed(struct dc_ti_adc_info *info,
+ struct iio_chan_spec const *chan,
+ int raw, int *val, int *val2)
+{
+ if (chan->channel != DC_TI_ADC_VBAT)
+ return -EINVAL;
+
+ /* Apply calibration */
+ raw -= info->vbat_zse;
+ raw = raw * (DC_TI_VBAT_GE_DIV - info->vbat_ge * DC_TI_VBAT_GE_STEP) /
+ DC_TI_VBAT_GE_DIV;
+ /* Vbat ADC scale is 4.6875 mV / unit */
+ raw *= 46875;
+
+ /* raw is now in 10000 units / mV, convert to milli + milli/1e6 */
+ *val = raw / 10000;
+ *val2 = (raw % 10000) * 100;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int dc_ti_adc_sample(struct dc_ti_adc_info *info,
+ struct iio_chan_spec const *chan, int *val)
+{
+ int ret, ch = chan->channel;
+ __be16 buf;
+
+ info->conversion_done = false;
+
+ /*
+ * As per TI (PMIC Vendor), the ADC enable and ADC start commands should
+ * not be sent together. Hence send the commands separately.
+ */
+ ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG, DC_TI_ADC_EN);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(info->regmap, DC_TI_ADC_CNTL_REG,
+ DC_TI_ADC_CH_SEL,
+ FIELD_PREP(DC_TI_ADC_CH_SEL, ch));
+ if (ret)
+ return ret;
+
+ /*
+ * As per PMIC Vendor, a minimum of 50 ųs delay is required between ADC
+ * Enable and ADC START commands. This is also recommended by Intel
+ * Hardware team after the timing analysis of GPADC signals. Since the
+ * I2C Write transaction to set the channel number also imparts 25 ųs
+ * delay, we need to wait for another 25 ųs before issuing ADC START.
+ */
+ fsleep(25);
+
+ ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG,
+ DC_TI_ADC_START);
+ if (ret)
+ return ret;
+
+ /* TI (PMIC Vendor) recommends 5 s timeout for conversion */
+ ret = wait_event_timeout(info->wait, info->conversion_done, 5 * HZ);
+ if (ret == 0) {
+ ret = -ETIMEDOUT;
+ goto disable_adc;
+ }
+
+ ret = regmap_bulk_read(info->regmap, chan->address, &buf, sizeof(buf));
+ if (ret)
+ goto disable_adc;
+
+ /* The ADC values are 10 bits wide */
+ *val = be16_to_cpu(buf) & GENMASK(9, 0);
+
+disable_adc:
+ regmap_clear_bits(info->regmap, DC_TI_ADC_CNTL_REG,
+ DC_TI_ADC_START | DC_TI_ADC_EN);
+ return ret;
+}
+
+static int dc_ti_adc_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct dc_ti_adc_info *info = iio_priv(indio_dev);
+ int ret;
+
+ if (mask == IIO_CHAN_INFO_SCALE)
+ return dc_ti_adc_scale(info, chan, val, val2);
+
+ guard(mutex)(&info->lock);
+
+ /*
+ * If channel BPTHERM has been selected, first enable the BPTHERM BIAS
+ * which provides the VREF Voltage reference to convert BPTHERM Input
+ * voltage to temperature.
+ */
+ if (chan->channel == DC_TI_ADC_BATTEMP) {
+ ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG,
+ DC_TI_ADC_EN_EXT_BPTH_BIAS);
+ if (ret)
+ return ret;
+ /*
+ * As per PMIC Vendor specifications, BPTHERM BIAS should be
+ * enabled 35 ms before ADC_EN command.
+ */
+ msleep(35);
+ }
+
+ ret = dc_ti_adc_sample(info, chan, val);
+
+ if (chan->channel == DC_TI_ADC_BATTEMP)
+ regmap_clear_bits(info->regmap, DC_TI_ADC_CNTL_REG,
+ DC_TI_ADC_EN_EXT_BPTH_BIAS);
+
+ if (ret)
+ return ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_PROCESSED:
+ return dc_ti_adc_raw_to_processed(info, chan, *val, val, val2);
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info dc_ti_adc_iio_info = {
+ .read_raw = dc_ti_adc_read_raw,
+};
+
+static int dc_ti_adc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent);
+ struct dc_ti_adc_info *info;
+ struct iio_dev *indio_dev;
+ unsigned int val;
+ int irq, ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*info));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ info = iio_priv(indio_dev);
+
+ ret = devm_mutex_init(dev, &info->lock);
+ if (ret)
+ return ret;
+
+ init_waitqueue_head(&info->wait);
+
+ info->dev = dev;
+ info->regmap = pmic->regmap;
+
+ indio_dev->name = "dc_ti_adc";
+ indio_dev->channels = dc_ti_adc_channels;
+ indio_dev->num_channels = ARRAY_SIZE(dc_ti_adc_channels);
+ indio_dev->info = &dc_ti_adc_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = regmap_read(info->regmap, DC_TI_VBAT_ZSE_GE_REG, &val);
+ if (ret)
+ return ret;
+
+ info->vbat_zse = sign_extend32(FIELD_GET(DC_TI_VBAT_ZSE, val), 3);
+ info->vbat_ge = sign_extend32(FIELD_GET(DC_TI_VBAT_GE, val), 3);
+
+ dev_dbg(dev, "vbat-zse %d vbat-ge %d\n", info->vbat_zse, info->vbat_ge);
+
+ ret = devm_iio_map_array_register(dev, indio_dev, dc_ti_adc_default_maps);
+ if (ret)
+ return ret;
+
+ ret = devm_request_threaded_irq(dev, irq, NULL, dc_ti_adc_isr,
+ IRQF_ONESHOT, indio_dev->name, info);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct platform_device_id dc_ti_adc_ids[] = {
+ { .name = "chtdc_ti_adc" },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, dc_ti_adc_ids);
+
+static struct platform_driver dc_ti_adc_driver = {
+ .driver = {
+ .name = "dc_ti_adc",
+ },
+ .probe = dc_ti_adc_probe,
+ .id_table = dc_ti_adc_ids,
+};
+module_platform_driver(dc_ti_adc_driver);
+
+MODULE_AUTHOR("Ramakrishna Pallala (Intel)");
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_DESCRIPTION("Intel Dollar Cove (TI) GPADC Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/mcp3564.c b/drivers/iio/adc/mcp3564.c
index a68f1cd6883e..cd679ff10a97 100644
--- a/drivers/iio/adc/mcp3564.c
+++ b/drivers/iio/adc/mcp3564.c
@@ -1019,7 +1019,7 @@ static int mcp3564_parse_fw_children(struct iio_dev *indio_dev)
channels = devm_kcalloc(dev, num_ch, sizeof(*channels), GFP_KERNEL);
if (!channels)
- return dev_err_probe(dev, -ENOMEM, "Can't allocate memory\n");
+ return -ENOMEM;
device_for_each_child_node_scoped(dev, child) {
node_name = fwnode_get_name(child);
diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c
index 4ff88603e4fc..f7e7172ef4f6 100644
--- a/drivers/iio/adc/meson_saradc.c
+++ b/drivers/iio/adc/meson_saradc.c
@@ -1357,7 +1357,7 @@ static int meson_sar_adc_probe(struct platform_device *pdev)
indio_dev = devm_iio_device_alloc(dev, sizeof(*priv));
if (!indio_dev)
- return dev_err_probe(dev, -ENOMEM, "failed allocating iio device\n");
+ return -ENOMEM;
priv = iio_priv(indio_dev);
init_completion(&priv->done);
diff --git a/drivers/iio/adc/mt6577_auxadc.c b/drivers/iio/adc/mt6577_auxadc.c
index 3343b54e8e44..fe9e3ece3fda 100644
--- a/drivers/iio/adc/mt6577_auxadc.c
+++ b/drivers/iio/adc/mt6577_auxadc.c
@@ -297,8 +297,7 @@ static int mt6577_auxadc_probe(struct platform_device *pdev)
ret = devm_add_action_or_reset(&pdev->dev, mt6577_power_off, adc_dev);
if (ret)
- return dev_err_probe(&pdev->dev, ret,
- "Failed to add action to managed power off\n");
+ return ret;
ret = devm_iio_device_register(&pdev->dev, indio_dev);
if (ret < 0)
diff --git a/drivers/iio/adc/mxs-lradc-adc.c b/drivers/iio/adc/mxs-lradc-adc.c
index 92baf3f5f560..dda5182a5076 100644
--- a/drivers/iio/adc/mxs-lradc-adc.c
+++ b/drivers/iio/adc/mxs-lradc-adc.c
@@ -697,10 +697,8 @@ static int mxs_lradc_adc_probe(struct platform_device *pdev)
/* Allocate the IIO device. */
iio = devm_iio_device_alloc(dev, sizeof(*adc));
- if (!iio) {
- dev_err(dev, "Failed to allocate IIO device\n");
+ if (!iio)
return -ENOMEM;
- }
adc = iio_priv(iio);
adc->lradc = lradc;
diff --git a/drivers/iio/adc/pac1921.c b/drivers/iio/adc/pac1921.c
index 72aa4ca2e5a4..35433250b008 100644
--- a/drivers/iio/adc/pac1921.c
+++ b/drivers/iio/adc/pac1921.c
@@ -1279,8 +1279,7 @@ static int pac1921_probe(struct i2c_client *client)
ret = devm_add_action_or_reset(dev, pac1921_regulator_disable,
priv->vdd);
if (ret)
- return dev_err_probe(dev, ret,
- "Cannot add action for vdd regulator disposal\n");
+ return ret;
msleep(PAC1921_POWERUP_TIME_MS);
diff --git a/drivers/iio/adc/pac1934.c b/drivers/iio/adc/pac1934.c
index 09fe88eb3fb0..48df16509260 100644
--- a/drivers/iio/adc/pac1934.c
+++ b/drivers/iio/adc/pac1934.c
@@ -88,6 +88,7 @@
#define PAC1934_VPOWER_3_ADDR 0x19
#define PAC1934_VPOWER_4_ADDR 0x1A
#define PAC1934_REFRESH_V_REG_ADDR 0x1F
+#define PAC1934_SLOW_REG_ADDR 0x20
#define PAC1934_CTRL_STAT_REGS_ADDR 0x1C
#define PAC1934_PID_REG_ADDR 0xFD
#define PAC1934_MID_REG_ADDR 0xFE
@@ -1265,8 +1266,23 @@ static int pac1934_chip_configure(struct pac1934_chip_info *info)
/* no SLOW triggered REFRESH, clear POR */
regs[PAC1934_SLOW_REG_OFF] = 0;
- ret = i2c_smbus_write_block_data(client, PAC1934_CTRL_STAT_REGS_ADDR,
- ARRAY_SIZE(regs), (u8 *)regs);
+ /*
+ * Write the three bytes sequentially, as the device does not support
+ * block write.
+ */
+ ret = i2c_smbus_write_byte_data(client, PAC1934_CTRL_STAT_REGS_ADDR,
+ regs[PAC1934_CHANNEL_DIS_REG_OFF]);
+ if (ret)
+ return ret;
+
+ ret = i2c_smbus_write_byte_data(client,
+ PAC1934_CTRL_STAT_REGS_ADDR + PAC1934_NEG_PWR_REG_OFF,
+ regs[PAC1934_NEG_PWR_REG_OFF]);
+ if (ret)
+ return ret;
+
+ ret = i2c_smbus_write_byte_data(client, PAC1934_SLOW_REG_ADDR,
+ regs[PAC1934_SLOW_REG_OFF]);
if (ret)
return ret;
@@ -1455,13 +1471,6 @@ static int pac1934_prep_custom_attributes(struct pac1934_chip_info *info,
return 0;
}
-static void pac1934_mutex_destroy(void *data)
-{
- struct mutex *lock = data;
-
- mutex_destroy(lock);
-}
-
static const struct iio_info pac1934_info = {
.read_raw = pac1934_read_raw,
.write_raw = pac1934_write_raw,
@@ -1520,9 +1529,7 @@ static int pac1934_probe(struct i2c_client *client)
return dev_err_probe(dev, ret,
"parameter parsing returned an error\n");
- mutex_init(&info->lock);
- ret = devm_add_action_or_reset(dev, pac1934_mutex_destroy,
- &info->lock);
+ ret = devm_mutex_init(dev, &info->lock);
if (ret < 0)
return ret;
diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c
index 7c01e33be04c..3f433064618e 100644
--- a/drivers/iio/adc/palmas_gpadc.c
+++ b/drivers/iio/adc/palmas_gpadc.c
@@ -885,10 +885,8 @@ static int palmas_gpadc_probe(struct platform_device *pdev)
return -EINVAL;
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc));
- if (!indio_dev) {
- dev_err(&pdev->dev, "iio_device_alloc failed\n");
+ if (!indio_dev)
return -ENOMEM;
- }
adc = iio_priv(indio_dev);
adc->dev = &pdev->dev;
diff --git a/drivers/iio/adc/rcar-gyroadc.c b/drivers/iio/adc/rcar-gyroadc.c
index cc326f21d398..3a17b3898bf6 100644
--- a/drivers/iio/adc/rcar-gyroadc.c
+++ b/drivers/iio/adc/rcar-gyroadc.c
@@ -163,12 +163,10 @@ static int rcar_gyroadc_set_power(struct rcar_gyroadc *priv, bool on)
{
struct device *dev = priv->dev;
- if (on) {
+ if (on)
return pm_runtime_resume_and_get(dev);
- } else {
- pm_runtime_mark_last_busy(dev);
- return pm_runtime_put_autosuspend(dev);
- }
+
+ return pm_runtime_put_autosuspend(dev);
}
static int rcar_gyroadc_read_raw(struct iio_dev *indio_dev,
diff --git a/drivers/iio/adc/rn5t618-adc.c b/drivers/iio/adc/rn5t618-adc.c
index d6f6b351f2af..f78fc795b69a 100644
--- a/drivers/iio/adc/rn5t618-adc.c
+++ b/drivers/iio/adc/rn5t618-adc.c
@@ -199,10 +199,8 @@ static int rn5t618_adc_probe(struct platform_device *pdev)
struct rn5t618 *rn5t618 = dev_get_drvdata(pdev->dev.parent);
iio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc));
- if (!iio_dev) {
- dev_err(&pdev->dev, "failed allocating iio device\n");
+ if (!iio_dev)
return -ENOMEM;
- }
adc = iio_priv(iio_dev);
adc->dev = &pdev->dev;
diff --git a/drivers/iio/adc/rockchip_saradc.c b/drivers/iio/adc/rockchip_saradc.c
index bd62daea0a3e..6721da0ed7bb 100644
--- a/drivers/iio/adc/rockchip_saradc.c
+++ b/drivers/iio/adc/rockchip_saradc.c
@@ -466,8 +466,7 @@ static int rockchip_saradc_probe(struct platform_device *pdev)
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
if (!indio_dev)
- return dev_err_probe(&pdev->dev, -ENOMEM,
- "failed allocating iio device\n");
+ return -ENOMEM;
info = iio_priv(indio_dev);
@@ -527,8 +526,7 @@ static int rockchip_saradc_probe(struct platform_device *pdev)
ret = devm_add_action_or_reset(&pdev->dev,
rockchip_saradc_regulator_disable, info);
if (ret)
- return dev_err_probe(&pdev->dev, ret,
- "failed to register devm action\n");
+ return ret;
ret = regulator_get_voltage(info->vref);
if (ret < 0)
diff --git a/drivers/iio/adc/rohm-bd79112.c b/drivers/iio/adc/rohm-bd79112.c
new file mode 100644
index 000000000000..d15e06c8b94d
--- /dev/null
+++ b/drivers/iio/adc/rohm-bd79112.c
@@ -0,0 +1,556 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ROHM ADC driver for BD79112 signal monitoring hub.
+ * Copyright (C) 2025, ROHM Semiconductor.
+ *
+ * SPI communication derived from ad7923.c and ti-ads7950.c
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/gpio/driver.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <asm/byteorder.h>
+
+#include <linux/iio/adc-helpers.h>
+#include <linux/iio/iio.h>
+
+#define BD79112_MAX_NUM_CHANNELS 32
+
+struct bd79112_data {
+ struct spi_device *spi;
+ struct regmap *map;
+ struct device *dev;
+ struct gpio_chip gc;
+ unsigned long gpio_valid_mask;
+ unsigned int vref_mv;
+ struct spi_transfer read_xfer[2];
+ struct spi_transfer write_xfer;
+ struct spi_message read_msg;
+ struct spi_message write_msg;
+ /* 16-bit TX, valid data in high byte */
+ u8 read_tx[2] __aligned(IIO_DMA_MINALIGN);
+ /* 8-bit address followed by 8-bit data */
+ u8 reg_write_tx[2];
+ /* 12-bit of ADC data or 8 bit of reg data */
+ __be16 read_rx;
+};
+
+/*
+ * The ADC data is read issuing SPI-command matching the channel number.
+ * We treat this as a register address.
+ */
+#define BD79112_REG_AGIO0A 0x00
+#define BD79112_REG_AGIO15B 0x1f
+
+/*
+ * ADC STATUS_FLAG appended to ADC data will be set, if the ADC result is being
+ * read for a channel, which input pin is muxed to be a GPIO.
+ */
+#define BD79112_ADC_STATUS_FLAG BIT(14)
+
+/*
+ * The BD79112 requires "R/W bit" to be set for SPI register (not ADC data)
+ * reads and an "IOSET bit" to be set for read/write operations (which aren't
+ * reading the ADC data).
+ */
+#define BD79112_BIT_RW BIT(4)
+#define BD79112_BIT_IO BIT(5)
+
+#define BD79112_REG_GPI_VALUE_B8_15 (BD79112_BIT_IO | 0x0)
+#define BD79112_REG_GPI_VALUE_B0_B7 (BD79112_BIT_IO | 0x1)
+#define BD79112_REG_GPI_VALUE_A8_15 (BD79112_BIT_IO | 0x2)
+#define BD79112_REG_GPI_VALUE_A0_A7 (BD79112_BIT_IO | 0x3)
+
+#define BD79112_REG_GPI_EN_B7_B15 (BD79112_BIT_IO | 0x4)
+#define BD79112_REG_GPI_EN_B0_B7 (BD79112_BIT_IO | 0x5)
+#define BD79112_REG_GPI_EN_A8_A15 (BD79112_BIT_IO | 0x6)
+#define BD79112_REG_GPI_EN_A0_A7 (BD79112_BIT_IO | 0x7)
+
+#define BD79112_REG_GPO_EN_B7_B15 (BD79112_BIT_IO | 0x8)
+#define BD79112_REG_GPO_EN_B0_B7 (BD79112_BIT_IO | 0x9)
+#define BD79112_REG_GPO_EN_A8_A15 (BD79112_BIT_IO | 0xa)
+#define BD79112_REG_GPO_EN_A0_A7 (BD79112_BIT_IO | 0xb)
+
+#define BD79112_NUM_GPIO_EN_REGS 8
+#define BD79112_FIRST_GPIO_EN_REG BD79112_REG_GPI_EN_B7_B15
+
+#define BD79112_REG_GPO_VALUE_B8_15 (BD79112_BIT_IO | 0xc)
+#define BD79112_REG_GPO_VALUE_B0_B7 (BD79112_BIT_IO | 0xd)
+#define BD79112_REG_GPO_VALUE_A8_15 (BD79112_BIT_IO | 0xe)
+#define BD79112_REG_GPO_VALUE_A0_A7 (BD79112_BIT_IO | 0xf)
+
+#define BD79112_REG_MAX BD79112_REG_GPO_VALUE_A0_A7
+
+/*
+ * Read transaction consists of two 16-bit sequences separated by CSB.
+ * For register read, 'IOSET' bit must be set. For ADC read, IOSET is cleared
+ * and ADDR equals the channel number (0 ... 31).
+ *
+ * First 16-bit sequence, MOSI as below, MISO data ignored:
+ * - SCK: | 1 | 2 | 3 | 4 | 5 .. 8 | 9 .. 16 |
+ * - MOSI:| 0 | 0 | IOSET | RW (1) | ADDR | 8'b0 |
+ *
+ * CSB released and re-acquired between these sequences
+ *
+ * Second 16-bit sequence, MISO as below, MOSI data ignored:
+ * For Register read data is 8 bits:
+ * - SCK: | 1 .. 8 | 9 .. 16 |
+ * - MISO:| 8'b0 | 8-bit data |
+ *
+ * For ADC read data is 12 bits:
+ * - SCK: | 1 | 2 | 3 4 | 4 .. 16 |
+ * - MISO:| 0 | STATUS_FLAG | 2'b0 | 12-bit data |
+ * The 'STATUS_FLAG' is set if the read input pin was configured as a GPIO.
+ */
+static int bd79112_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct bd79112_data *data = context;
+ int ret;
+
+ if (reg & BD79112_BIT_IO)
+ reg |= BD79112_BIT_RW;
+
+ data->read_tx[0] = reg;
+
+ ret = spi_sync(data->spi, &data->read_msg);
+ if (!ret)
+ *val = be16_to_cpu(data->read_rx);
+
+ return ret;
+}
+
+/*
+ * Write, single 16-bit sequence (broken down below):
+ *
+ * First 8-bit, MOSI as below, MISO data ignored:
+ * - SCK: | 1 | 2 | 3 | 4 | 5 .. 8 |
+ * - MOSI:| 0 | 0 |IOSET| RW(0) | ADDR |
+ *
+ * Last 8 SCK cycles (b8 ... b15), MISO contains register data, MOSI ignored.
+ * - SCK: | 9 .. 16 |
+ * - MISO:| data |
+ */
+static int bd79112_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct bd79112_data *data = context;
+
+ data->reg_write_tx[0] = reg;
+ data->reg_write_tx[1] = val;
+
+ return spi_sync(data->spi, &data->write_msg);
+}
+
+static int _get_gpio_reg(unsigned int offset, unsigned int base)
+{
+ int regoffset = offset / 8;
+
+ if (offset > 31)
+ return -EINVAL;
+
+ return base - regoffset;
+}
+
+#define GET_GPIO_BIT(offset) BIT((offset) % 8)
+#define GET_GPO_EN_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPO_EN_A0_A7)
+#define GET_GPI_EN_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPI_EN_A0_A7)
+#define GET_GPO_VAL_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPO_VALUE_A0_A7)
+#define GET_GPI_VAL_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPI_VALUE_A0_A7)
+
+static const struct regmap_range bd71815_volatile_ro_ranges[] = {
+ {
+ /* Read ADC data */
+ .range_min = BD79112_REG_AGIO0A,
+ .range_max = BD79112_REG_AGIO15B,
+ }, {
+ /* GPI state */
+ .range_min = BD79112_REG_GPI_VALUE_B8_15,
+ .range_max = BD79112_REG_GPI_VALUE_A0_A7,
+ },
+};
+
+static const struct regmap_access_table bd79112_volatile_regs = {
+ .yes_ranges = &bd71815_volatile_ro_ranges[0],
+ .n_yes_ranges = ARRAY_SIZE(bd71815_volatile_ro_ranges),
+};
+
+static const struct regmap_access_table bd79112_ro_regs = {
+ .no_ranges = &bd71815_volatile_ro_ranges[0],
+ .n_no_ranges = ARRAY_SIZE(bd71815_volatile_ro_ranges),
+};
+
+static const struct regmap_config bd79112_regmap = {
+ .reg_read = bd79112_reg_read,
+ .reg_write = bd79112_reg_write,
+ .volatile_table = &bd79112_volatile_regs,
+ .wr_table = &bd79112_ro_regs,
+ .cache_type = REGCACHE_MAPLE,
+ .max_register = BD79112_REG_MAX,
+};
+
+static int bd79112_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long m)
+{
+ struct bd79112_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (m) {
+ case IIO_CHAN_INFO_RAW:
+ ret = regmap_read(data->map, chan->channel, val);
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = data->vref_mv;
+ *val2 = 12;
+
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info bd79112_info = {
+ .read_raw = bd79112_read_raw,
+};
+
+static const struct iio_chan_spec bd79112_chan_template = {
+ .type = IIO_VOLTAGE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+ .indexed = 1,
+};
+
+static int bd79112_gpio_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ struct bd79112_data *data = gpiochip_get_data(gc);
+
+ *valid_mask = data->gpio_valid_mask;
+
+ return 0;
+}
+
+static int bd79112_gpio_dir_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct bd79112_data *data = gpiochip_get_data(gc);
+ unsigned int reg, bit, val;
+ int ret;
+
+ bit = GET_GPIO_BIT(offset);
+ reg = GET_GPO_EN_REG(offset);
+
+ ret = regmap_read(data->map, reg, &val);
+ if (ret)
+ return ret;
+
+ if (bit & val)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ reg = GET_GPI_EN_REG(offset);
+ ret = regmap_read(data->map, reg, &val);
+ if (ret)
+ return ret;
+
+ if (bit & val)
+ return GPIO_LINE_DIRECTION_IN;
+
+ /*
+ * Ouch. Seems the pin is ADC input - shouldn't happen as changing mux
+ * at runtime is not supported and non GPIO pins should be invalidated
+ * by the valid_mask at probe. Maybe someone wrote a register bypassing
+ * the driver?
+ */
+ dev_err(data->dev, "Pin not a GPIO\n");
+
+ return -EINVAL;
+}
+
+static int bd79112_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct bd79112_data *data = gpiochip_get_data(gc);
+ unsigned int reg, bit, val;
+ int ret;
+
+ bit = GET_GPIO_BIT(offset);
+ reg = GET_GPI_VAL_REG(offset);
+
+ ret = regmap_read(data->map, reg, &val);
+ if (ret)
+ return ret;
+
+ return !!(val & bit);
+}
+
+static int bd79112_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ struct bd79112_data *data = gpiochip_get_data(gc);
+ unsigned int reg, bit;
+
+ bit = GET_GPIO_BIT(offset);
+ reg = GET_GPO_VAL_REG(offset);
+
+ return regmap_assign_bits(data->map, reg, bit, value);
+}
+
+static int bd79112_gpio_set_multiple(struct gpio_chip *gc, unsigned long *mask,
+ unsigned long *bits)
+{
+ struct bd79112_data *data = gpiochip_get_data(gc);
+ unsigned long i, bank_mask;
+
+ for_each_set_clump8(i, bank_mask, mask, gc->ngpio) {
+ unsigned long bank_bits;
+ unsigned int reg;
+ int ret;
+
+ bank_bits = bitmap_get_value8(bits, i);
+ reg = BD79112_REG_GPO_VALUE_A0_A7 - i / 8;
+ ret = regmap_update_bits(data->map, reg, bank_mask, bank_bits);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int bd79112_gpio_dir_set(struct bd79112_data *data, unsigned int offset,
+ int dir)
+{
+ unsigned int gpi_reg, gpo_reg, bit;
+ int ret;
+
+ bit = GET_GPIO_BIT(offset);
+ gpi_reg = GET_GPI_EN_REG(offset);
+ gpo_reg = GET_GPO_EN_REG(offset);
+
+ if (dir == GPIO_LINE_DIRECTION_OUT) {
+ ret = regmap_clear_bits(data->map, gpi_reg, bit);
+ if (ret)
+ return ret;
+
+ return regmap_set_bits(data->map, gpo_reg, bit);
+ }
+
+ ret = regmap_set_bits(data->map, gpi_reg, bit);
+ if (ret)
+ return ret;
+
+ return regmap_clear_bits(data->map, gpo_reg, bit);
+}
+
+static int bd79112_gpio_input(struct gpio_chip *gc, unsigned int offset)
+{
+ struct bd79112_data *data = gpiochip_get_data(gc);
+
+ return bd79112_gpio_dir_set(data, offset, GPIO_LINE_DIRECTION_IN);
+}
+
+static int bd79112_gpio_output(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ struct bd79112_data *data = gpiochip_get_data(gc);
+ int ret;
+
+ ret = bd79112_gpio_set(gc, offset, value);
+ if (ret)
+ return ret;
+
+ return bd79112_gpio_dir_set(data, offset, GPIO_LINE_DIRECTION_OUT);
+}
+
+static const struct gpio_chip bd79112_gpio_chip = {
+ .label = "bd79112-gpio",
+ .get_direction = bd79112_gpio_dir_get,
+ .direction_input = bd79112_gpio_input,
+ .direction_output = bd79112_gpio_output,
+ .get = bd79112_gpio_get,
+ .set = bd79112_gpio_set,
+ .set_multiple = bd79112_gpio_set_multiple,
+ .init_valid_mask = bd79112_gpio_init_valid_mask,
+ .can_sleep = true,
+ .ngpio = 32,
+ .base = -1,
+};
+
+static unsigned int bd79112_get_gpio_pins(const struct iio_chan_spec *cs, int num_channels)
+{
+ unsigned int i, gpio_channels;
+
+ /*
+ * Let's initialize the mux config to say that all 32 channels are
+ * GPIOs. Then we can just loop through the iio_chan_spec and clear the
+ * bits for found ADC channels.
+ */
+ gpio_channels = GENMASK(31, 0);
+ for (i = 0; i < num_channels; i++)
+ gpio_channels &= ~BIT(cs[i].channel);
+
+ return gpio_channels;
+}
+
+/* ADC channels as named in the data-sheet */
+static const char * const bd79112_chan_names[] = {
+ "AGIO0A", "AGIO1A", "AGIO2A", "AGIO3A", /* 0 - 3 */
+ "AGIO4A", "AGIO5A", "AGIO6A", "AGIO7A", /* 4 - 7 */
+ "AGIO8A", "AGIO9A", "AGIO10A", "AGIO11A", /* 8 - 11 */
+ "AGIO12A", "AGIO13A", "AGIO14A", "AGIO15A", /* 12 - 15 */
+ "AGIO0B", "AGIO1B", "AGIO2B", "AGIO3B", /* 16 - 19 */
+ "AGIO4B", "AGIO5B", "AGIO6B", "AGIO7B", /* 20 - 23 */
+ "AGIO8B", "AGIO9B", "AGIO10B", "AGIO11B", /* 24 - 27 */
+ "AGIO12B", "AGIO13B", "AGIO14B", "AGIO15B", /* 28 - 31 */
+};
+
+static int bd79112_probe(struct spi_device *spi)
+{
+ struct bd79112_data *data;
+ struct iio_dev *iio_dev;
+ struct iio_chan_spec *cs;
+ struct device *dev = &spi->dev;
+ unsigned long gpio_pins, pin;
+ unsigned int i;
+ int ret;
+
+ iio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!iio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(iio_dev);
+ data->spi = spi;
+ data->dev = dev;
+ data->map = devm_regmap_init(dev, NULL, data, &bd79112_regmap);
+ if (IS_ERR(data->map))
+ return dev_err_probe(dev, PTR_ERR(data->map),
+ "Failed to initialize Regmap\n");
+
+ ret = devm_regulator_get_enable_read_voltage(dev, "vdd");
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to get the Vdd\n");
+
+ data->vref_mv = ret / 1000;
+
+ ret = devm_regulator_get_enable(dev, "iovdd");
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to enable I/O voltage\n");
+
+ data->read_xfer[0].tx_buf = &data->read_tx[0];
+ data->read_xfer[0].len = sizeof(data->read_tx);
+ data->read_xfer[0].cs_change = 1;
+ data->read_xfer[1].rx_buf = &data->read_rx;
+ data->read_xfer[1].len = sizeof(data->read_rx);
+ spi_message_init_with_transfers(&data->read_msg, data->read_xfer, 2);
+ ret = devm_spi_optimize_message(dev, spi, &data->read_msg);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to optimize SPI read message\n");
+
+ data->write_xfer.tx_buf = &data->reg_write_tx[0];
+ data->write_xfer.len = sizeof(data->reg_write_tx);
+ spi_message_init_with_transfers(&data->write_msg, &data->write_xfer, 1);
+ ret = devm_spi_optimize_message(dev, spi, &data->write_msg);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to optimize SPI write message\n");
+
+ ret = devm_iio_adc_device_alloc_chaninfo_se(dev, &bd79112_chan_template,
+ BD79112_MAX_NUM_CHANNELS - 1,
+ &cs);
+
+ /* Register all pins as GPIOs if there are no ADC channels */
+ if (ret == -ENOENT)
+ goto register_gpios;
+
+ if (ret < 0)
+ return ret;
+
+ iio_dev->num_channels = ret;
+ iio_dev->channels = cs;
+
+ for (i = 0; i < iio_dev->num_channels; i++)
+ cs[i].datasheet_name = bd79112_chan_names[cs[i].channel];
+
+ iio_dev->info = &bd79112_info;
+ iio_dev->name = "bd79112";
+ iio_dev->modes = INDIO_DIRECT_MODE;
+
+ /*
+ * Ensure all channels are ADCs. This allows us to register the IIO
+ * device early (before checking which pins are to be used for GPIO)
+ * without having to worry about some pins being initially used for
+ * GPIO.
+ */
+ for (i = 0; i < BD79112_NUM_GPIO_EN_REGS; i++) {
+ ret = regmap_write(data->map, BD79112_FIRST_GPIO_EN_REG + i, 0);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to initialize channels\n");
+ }
+
+ ret = devm_iio_device_register(data->dev, iio_dev);
+ if (ret)
+ return dev_err_probe(data->dev, ret, "Failed to register ADC\n");
+
+register_gpios:
+ gpio_pins = bd79112_get_gpio_pins(iio_dev->channels,
+ iio_dev->num_channels);
+
+ /* If all channels are reserved for ADC, then we're done. */
+ if (!gpio_pins)
+ return 0;
+
+ /* Default all the GPIO pins to GPI */
+ for_each_set_bit(pin, &gpio_pins, BD79112_MAX_NUM_CHANNELS) {
+ ret = bd79112_gpio_dir_set(data, pin, GPIO_LINE_DIRECTION_IN);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to mark pin as GPI\n");
+ }
+
+ data->gpio_valid_mask = gpio_pins;
+ data->gc = bd79112_gpio_chip;
+ data->gc.parent = dev;
+
+ return devm_gpiochip_add_data(dev, &data->gc, data);
+}
+
+static const struct of_device_id bd79112_of_match[] = {
+ { .compatible = "rohm,bd79112" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bd79112_of_match);
+
+static const struct spi_device_id bd79112_id[] = {
+ { "bd79112" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, bd79112_id);
+
+static struct spi_driver bd79112_driver = {
+ .driver = {
+ .name = "bd79112",
+ .of_match_table = bd79112_of_match,
+ },
+ .probe = bd79112_probe,
+ .id_table = bd79112_id,
+};
+module_spi_driver(bd79112_driver);
+
+MODULE_AUTHOR("Matti Vaittinen <mazziesaccount@gmail.com>");
+MODULE_DESCRIPTION("Driver for ROHM BD79112 ADC/GPIO");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_DRIVER");
diff --git a/drivers/iio/adc/rzg2l_adc.c b/drivers/iio/adc/rzg2l_adc.c
index cadb0446bc29..1010e0511b3e 100644
--- a/drivers/iio/adc/rzg2l_adc.c
+++ b/drivers/iio/adc/rzg2l_adc.c
@@ -248,7 +248,6 @@ static int rzg2l_adc_conversion(struct iio_dev *indio_dev, struct rzg2l_adc *adc
rzg2l_adc_start_stop(adc, false);
rpm_put:
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
@@ -410,7 +409,6 @@ static int rzg2l_adc_hw_init(struct device *dev, struct rzg2l_adc *adc)
rzg2l_adc_writel(adc, RZG2L_ADM(3), reg);
exit_hw_init:
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
diff --git a/drivers/iio/adc/spear_adc.c b/drivers/iio/adc/spear_adc.c
index e3a865c79686..50b0a607baeb 100644
--- a/drivers/iio/adc/spear_adc.c
+++ b/drivers/iio/adc/spear_adc.c
@@ -14,6 +14,7 @@
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/io.h>
+#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/completion.h>
@@ -29,9 +30,9 @@
/* Bit definitions for SPEAR_ADC_STATUS */
#define SPEAR_ADC_STATUS_START_CONVERSION BIT(0)
-#define SPEAR_ADC_STATUS_CHANNEL_NUM(x) ((x) << 1)
+#define SPEAR_ADC_STATUS_CHANNEL_NUM_MASK GENMASK(3, 1)
#define SPEAR_ADC_STATUS_ADC_ENABLE BIT(4)
-#define SPEAR_ADC_STATUS_AVG_SAMPLE(x) ((x) << 5)
+#define SPEAR_ADC_STATUS_AVG_SAMPLE_MASK GENMASK(8, 5)
#define SPEAR_ADC_STATUS_VREF_INTERNAL BIT(9)
#define SPEAR_ADC_DATA_MASK 0x03ff
@@ -157,8 +158,8 @@ static int spear_adc_read_raw(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_RAW:
mutex_lock(&st->lock);
- status = SPEAR_ADC_STATUS_CHANNEL_NUM(chan->channel) |
- SPEAR_ADC_STATUS_AVG_SAMPLE(st->avg_samples) |
+ status = FIELD_PREP(SPEAR_ADC_STATUS_CHANNEL_NUM_MASK, chan->channel) |
+ FIELD_PREP(SPEAR_ADC_STATUS_AVG_SAMPLE_MASK, st->avg_samples) |
SPEAR_ADC_STATUS_START_CONVERSION |
SPEAR_ADC_STATUS_ADC_ENABLE;
if (st->vref_external == 0)
@@ -274,8 +275,7 @@ static int spear_adc_probe(struct platform_device *pdev)
indio_dev = devm_iio_device_alloc(dev, sizeof(struct spear_adc_state));
if (!indio_dev)
- return dev_err_probe(dev, -ENOMEM,
- "failed allocating iio device\n");
+ return -ENOMEM;
st = iio_priv(indio_dev);
st->dev = dev;
diff --git a/drivers/iio/adc/stm32-adc-core.c b/drivers/iio/adc/stm32-adc-core.c
index 3d800762c5fc..e39a4c0db25e 100644
--- a/drivers/iio/adc/stm32-adc-core.c
+++ b/drivers/iio/adc/stm32-adc-core.c
@@ -794,7 +794,6 @@ static int stm32_adc_probe(struct platform_device *pdev)
goto err_irq_remove;
}
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return 0;
diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c
index b9f93116e114..2d7f88459c7c 100644
--- a/drivers/iio/adc/stm32-adc.c
+++ b/drivers/iio/adc/stm32-adc.c
@@ -1528,7 +1528,6 @@ static int stm32_adc_single_conv(struct iio_dev *indio_dev,
stm32_adc_conv_irq_disable(adc);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
@@ -1564,7 +1563,6 @@ static int stm32_adc_write_raw(struct iio_dev *indio_dev,
adc->cfg->set_ovs(indio_dev, idx);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
adc->ovs_idx = idx;
@@ -1759,7 +1757,6 @@ static int stm32_adc_update_scan_mode(struct iio_dev *indio_dev,
adc->num_conv = bitmap_weight(scan_mask, iio_get_masklength(indio_dev));
ret = stm32_adc_conf_scan_seq(indio_dev, scan_mask);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
@@ -1808,7 +1805,6 @@ static int stm32_adc_debugfs_reg_access(struct iio_dev *indio_dev,
else
*readval = stm32_adc_readl(adc, reg);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return 0;
@@ -1954,7 +1950,6 @@ static int stm32_adc_buffer_postenable(struct iio_dev *indio_dev)
err_clr_trig:
stm32_adc_set_trig(indio_dev, NULL);
err_pm_put:
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
@@ -1977,7 +1972,6 @@ static int stm32_adc_buffer_predisable(struct iio_dev *indio_dev)
if (stm32_adc_set_trig(indio_dev, NULL))
dev_err(&indio_dev->dev, "Can't clear trigger\n");
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return 0;
@@ -2614,7 +2608,6 @@ static int stm32_adc_probe(struct platform_device *pdev)
goto err_hw_stop;
}
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
if (IS_ENABLED(CONFIG_DEBUG_FS))
diff --git a/drivers/iio/adc/stm32-dfsdm-adc.c b/drivers/iio/adc/stm32-dfsdm-adc.c
index c2d21eecafe7..74b1b4dc6e81 100644
--- a/drivers/iio/adc/stm32-dfsdm-adc.c
+++ b/drivers/iio/adc/stm32-dfsdm-adc.c
@@ -1764,10 +1764,8 @@ static int stm32_dfsdm_adc_probe(struct platform_device *pdev)
dev_data = of_device_get_match_data(dev);
iio = devm_iio_device_alloc(dev, sizeof(*adc));
- if (!iio) {
- dev_err(dev, "%s: Failed to allocate IIO\n", __func__);
+ if (!iio)
return -ENOMEM;
- }
adc = iio_priv(iio);
adc->dfsdm = dev_get_drvdata(dev->parent);
diff --git a/drivers/iio/adc/stmpe-adc.c b/drivers/iio/adc/stmpe-adc.c
index b0add5a2eab5..8e26c47edc08 100644
--- a/drivers/iio/adc/stmpe-adc.c
+++ b/drivers/iio/adc/stmpe-adc.c
@@ -267,10 +267,8 @@ static int stmpe_adc_probe(struct platform_device *pdev)
return irq_adc;
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct stmpe_adc));
- if (!indio_dev) {
- dev_err(&pdev->dev, "failed allocating iio device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
info = iio_priv(indio_dev);
mutex_init(&info->lock);
diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c
index 6b8d6bee1873..479115ea50bf 100644
--- a/drivers/iio/adc/sun4i-gpadc-iio.c
+++ b/drivers/iio/adc/sun4i-gpadc-iio.c
@@ -154,7 +154,6 @@ static const struct regmap_config sun4i_gpadc_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
- .fast_io = true,
};
static int sun4i_prepare_for_irq(struct iio_dev *indio_dev, int channel,
@@ -245,7 +244,6 @@ static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val,
*val = info->temp_data;
ret = 0;
- pm_runtime_mark_last_busy(indio_dev->dev.parent);
err:
pm_runtime_put_autosuspend(indio_dev->dev.parent);
@@ -272,7 +270,6 @@ static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val)
regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, val);
- pm_runtime_mark_last_busy(indio_dev->dev.parent);
pm_runtime_put_autosuspend(indio_dev->dev.parent);
return 0;
diff --git a/drivers/iio/adc/ti-adc081c.c b/drivers/iio/adc/ti-adc081c.c
index 4f514db5c26e..8ef51c57912d 100644
--- a/drivers/iio/adc/ti-adc081c.c
+++ b/drivers/iio/adc/ti-adc081c.c
@@ -102,27 +102,23 @@ struct adcxx1c_model {
int bits;
};
-#define ADCxx1C_MODEL(_name, _bits) \
- { \
- .channels = _name ## _channels, \
- .bits = (_bits), \
- }
-
DEFINE_ADCxx1C_CHANNELS(adc081c, 8);
DEFINE_ADCxx1C_CHANNELS(adc101c, 10);
DEFINE_ADCxx1C_CHANNELS(adc121c, 12);
-/* Model ids are indexes in _models array */
-enum adcxx1c_model_id {
- ADC081C = 0,
- ADC101C = 1,
- ADC121C = 2,
+static const struct adcxx1c_model adc081c_model = {
+ .channels = adc081c_channels,
+ .bits = 8,
+};
+
+static const struct adcxx1c_model adc101c_model = {
+ .channels = adc101c_channels,
+ .bits = 10,
};
-static struct adcxx1c_model adcxx1c_models[] = {
- ADCxx1C_MODEL(adc081c, 8),
- ADCxx1C_MODEL(adc101c, 10),
- ADCxx1C_MODEL(adc121c, 12),
+static const struct adcxx1c_model adc121c_model = {
+ .channels = adc121c_channels,
+ .bits = 12,
};
static const struct iio_info adc081c_info = {
@@ -203,24 +199,24 @@ static int adc081c_probe(struct i2c_client *client)
}
static const struct i2c_device_id adc081c_id[] = {
- { "adc081c", (kernel_ulong_t)&adcxx1c_models[ADC081C] },
- { "adc101c", (kernel_ulong_t)&adcxx1c_models[ADC101C] },
- { "adc121c", (kernel_ulong_t)&adcxx1c_models[ADC121C] },
+ { "adc081c", (kernel_ulong_t)&adc081c_model },
+ { "adc101c", (kernel_ulong_t)&adc101c_model },
+ { "adc121c", (kernel_ulong_t)&adc121c_model },
{ }
};
MODULE_DEVICE_TABLE(i2c, adc081c_id);
static const struct acpi_device_id adc081c_acpi_match[] = {
/* Used on some AAEON boards */
- { "ADC081C", (kernel_ulong_t)&adcxx1c_models[ADC081C] },
+ { "ADC081C", (kernel_ulong_t)&adc081c_model },
{ }
};
MODULE_DEVICE_TABLE(acpi, adc081c_acpi_match);
static const struct of_device_id adc081c_of_match[] = {
- { .compatible = "ti,adc081c", .data = &adcxx1c_models[ADC081C] },
- { .compatible = "ti,adc101c", .data = &adcxx1c_models[ADC101C] },
- { .compatible = "ti,adc121c", .data = &adcxx1c_models[ADC121C] },
+ { .compatible = "ti,adc081c", .data = &adc081c_model },
+ { .compatible = "ti,adc101c", .data = &adc101c_model },
+ { .compatible = "ti,adc121c", .data = &adc121c_model },
{ }
};
MODULE_DEVICE_TABLE(of, adc081c_of_match);
diff --git a/drivers/iio/adc/ti-adc084s021.c b/drivers/iio/adc/ti-adc084s021.c
index 50a474f4d9f5..a100f770fa1c 100644
--- a/drivers/iio/adc/ti-adc084s021.c
+++ b/drivers/iio/adc/ti-adc084s021.c
@@ -200,10 +200,8 @@ static int adc084s021_probe(struct spi_device *spi)
int ret;
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc));
- if (!indio_dev) {
- dev_err(&spi->dev, "Failed to allocate IIO device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
adc = iio_priv(indio_dev);
adc->spi = spi;
diff --git a/drivers/iio/adc/ti-adc12138.c b/drivers/iio/adc/ti-adc12138.c
index 9dc465a10ffc..e5ec4b073daa 100644
--- a/drivers/iio/adc/ti-adc12138.c
+++ b/drivers/iio/adc/ti-adc12138.c
@@ -38,15 +38,13 @@ enum {
struct adc12138 {
struct spi_device *spi;
unsigned int id;
- /* conversion clock */
- struct clk *cclk;
/* positive analog voltage reference */
struct regulator *vref_p;
/* negative analog voltage reference */
struct regulator *vref_n;
struct mutex lock;
struct completion complete;
- /* The number of cclk periods for the S/H's acquisition time */
+ /* The number of conversion clock periods for the S/H's acquisition time */
unsigned int acquisition_time;
/*
* Maximum size needed: 16x 2 bytes ADC data + 8 bytes timestamp.
@@ -400,6 +398,7 @@ static int adc12138_probe(struct spi_device *spi)
{
struct iio_dev *indio_dev;
struct adc12138 *adc;
+ struct clk *cclk;
int ret;
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc));
@@ -435,9 +434,14 @@ static int adc12138_probe(struct spi_device *spi)
if (ret)
adc->acquisition_time = 10;
- adc->cclk = devm_clk_get(&spi->dev, NULL);
- if (IS_ERR(adc->cclk))
- return PTR_ERR(adc->cclk);
+ ret = devm_request_irq(&spi->dev, spi->irq, adc12138_eoc_handler,
+ IRQF_TRIGGER_RISING, indio_dev->name, indio_dev);
+ if (ret)
+ return ret;
+
+ cclk = devm_clk_get_enabled(&spi->dev, NULL);
+ if (IS_ERR(cclk))
+ return PTR_ERR(cclk);
adc->vref_p = devm_regulator_get(&spi->dev, "vref-p");
if (IS_ERR(adc->vref_p))
@@ -454,18 +458,9 @@ static int adc12138_probe(struct spi_device *spi)
return ret;
}
- ret = devm_request_irq(&spi->dev, spi->irq, adc12138_eoc_handler,
- IRQF_TRIGGER_RISING, indio_dev->name, indio_dev);
- if (ret)
- return ret;
-
- ret = clk_prepare_enable(adc->cclk);
- if (ret)
- return ret;
-
ret = regulator_enable(adc->vref_p);
if (ret)
- goto err_clk_disable;
+ return ret;
if (!IS_ERR(adc->vref_n)) {
ret = regulator_enable(adc->vref_n);
@@ -496,8 +491,6 @@ err_vref_n_disable:
regulator_disable(adc->vref_n);
err_vref_p_disable:
regulator_disable(adc->vref_p);
-err_clk_disable:
- clk_disable_unprepare(adc->cclk);
return ret;
}
@@ -512,7 +505,6 @@ static void adc12138_remove(struct spi_device *spi)
if (!IS_ERR(adc->vref_n))
regulator_disable(adc->vref_n);
regulator_disable(adc->vref_p);
- clk_disable_unprepare(adc->cclk);
}
static const struct of_device_id adc12138_dt_ids[] = {
diff --git a/drivers/iio/adc/ti-adc128s052.c b/drivers/iio/adc/ti-adc128s052.c
index 1b46a8155803..4ae65793ad9b 100644
--- a/drivers/iio/adc/ti-adc128s052.c
+++ b/drivers/iio/adc/ti-adc128s052.c
@@ -99,51 +99,83 @@ static int adc128_read_raw(struct iio_dev *indio_dev,
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \
}
-static const struct iio_chan_spec adc128s052_channels[] = {
+static const struct iio_chan_spec simple_1chan_adc_channels[] = {
+ ADC128_VOLTAGE_CHANNEL(0),
+};
+
+static const struct iio_chan_spec simple_2chan_adc_channels[] = {
ADC128_VOLTAGE_CHANNEL(0),
ADC128_VOLTAGE_CHANNEL(1),
- ADC128_VOLTAGE_CHANNEL(2),
- ADC128_VOLTAGE_CHANNEL(3),
- ADC128_VOLTAGE_CHANNEL(4),
- ADC128_VOLTAGE_CHANNEL(5),
- ADC128_VOLTAGE_CHANNEL(6),
- ADC128_VOLTAGE_CHANNEL(7),
};
-static const struct iio_chan_spec adc122s021_channels[] = {
+static const struct iio_chan_spec simple_4chan_adc_channels[] = {
ADC128_VOLTAGE_CHANNEL(0),
ADC128_VOLTAGE_CHANNEL(1),
+ ADC128_VOLTAGE_CHANNEL(2),
+ ADC128_VOLTAGE_CHANNEL(3),
};
-static const struct iio_chan_spec adc124s021_channels[] = {
+static const struct iio_chan_spec simple_8chan_adc_channels[] = {
ADC128_VOLTAGE_CHANNEL(0),
ADC128_VOLTAGE_CHANNEL(1),
ADC128_VOLTAGE_CHANNEL(2),
ADC128_VOLTAGE_CHANNEL(3),
+ ADC128_VOLTAGE_CHANNEL(4),
+ ADC128_VOLTAGE_CHANNEL(5),
+ ADC128_VOLTAGE_CHANNEL(6),
+ ADC128_VOLTAGE_CHANNEL(7),
};
static const char * const bd79104_regulators[] = { "iovdd" };
-static const struct adc128_configuration adc128_config[] = {
- {
- .channels = adc128s052_channels,
- .num_channels = ARRAY_SIZE(adc128s052_channels),
- .refname = "vref",
- }, {
- .channels = adc122s021_channels,
- .num_channels = ARRAY_SIZE(adc122s021_channels),
- .refname = "vref",
- }, {
- .channels = adc124s021_channels,
- .num_channels = ARRAY_SIZE(adc124s021_channels),
- .refname = "vref",
- }, {
- .channels = adc128s052_channels,
- .num_channels = ARRAY_SIZE(adc128s052_channels),
- .refname = "vdd",
- .other_regulators = &bd79104_regulators,
- .num_other_regulators = 1,
- },
+static const struct adc128_configuration adc122s_config = {
+ .channels = simple_2chan_adc_channels,
+ .num_channels = ARRAY_SIZE(simple_2chan_adc_channels),
+ .refname = "vref",
+};
+
+static const struct adc128_configuration adc124s_config = {
+ .channels = simple_4chan_adc_channels,
+ .num_channels = ARRAY_SIZE(simple_4chan_adc_channels),
+ .refname = "vref",
+};
+
+static const struct adc128_configuration adc128s_config = {
+ .channels = simple_8chan_adc_channels,
+ .num_channels = ARRAY_SIZE(simple_8chan_adc_channels),
+ .refname = "vref",
+};
+
+static const struct adc128_configuration bd79100_config = {
+ .channels = simple_1chan_adc_channels,
+ .num_channels = ARRAY_SIZE(simple_1chan_adc_channels),
+ .refname = "vdd",
+ .other_regulators = &bd79104_regulators,
+ .num_other_regulators = 1,
+};
+
+static const struct adc128_configuration bd79101_config = {
+ .channels = simple_2chan_adc_channels,
+ .num_channels = ARRAY_SIZE(simple_2chan_adc_channels),
+ .refname = "vdd",
+ .other_regulators = &bd79104_regulators,
+ .num_other_regulators = 1,
+};
+
+static const struct adc128_configuration bd79102_config = {
+ .channels = simple_4chan_adc_channels,
+ .num_channels = ARRAY_SIZE(simple_4chan_adc_channels),
+ .refname = "vdd",
+ .other_regulators = &bd79104_regulators,
+ .num_other_regulators = 1,
+};
+
+static const struct adc128_configuration bd79104_config = {
+ .channels = simple_8chan_adc_channels,
+ .num_channels = ARRAY_SIZE(simple_8chan_adc_channels),
+ .refname = "vdd",
+ .other_regulators = &bd79104_regulators,
+ .num_other_regulators = 1,
};
static const struct iio_info adc128_info = {
@@ -199,33 +231,41 @@ static int adc128_probe(struct spi_device *spi)
}
static const struct of_device_id adc128_of_match[] = {
- { .compatible = "ti,adc128s052", .data = &adc128_config[0] },
- { .compatible = "ti,adc122s021", .data = &adc128_config[1] },
- { .compatible = "ti,adc122s051", .data = &adc128_config[1] },
- { .compatible = "ti,adc122s101", .data = &adc128_config[1] },
- { .compatible = "ti,adc124s021", .data = &adc128_config[2] },
- { .compatible = "ti,adc124s051", .data = &adc128_config[2] },
- { .compatible = "ti,adc124s101", .data = &adc128_config[2] },
- { .compatible = "rohm,bd79104", .data = &adc128_config[3] },
+ { .compatible = "ti,adc128s052", .data = &adc128s_config },
+ { .compatible = "ti,adc122s021", .data = &adc122s_config },
+ { .compatible = "ti,adc122s051", .data = &adc122s_config },
+ { .compatible = "ti,adc122s101", .data = &adc122s_config },
+ { .compatible = "ti,adc124s021", .data = &adc124s_config },
+ { .compatible = "ti,adc124s051", .data = &adc124s_config },
+ { .compatible = "ti,adc124s101", .data = &adc124s_config },
+ { .compatible = "rohm,bd79100", .data = &bd79100_config },
+ { .compatible = "rohm,bd79101", .data = &bd79101_config },
+ { .compatible = "rohm,bd79102", .data = &bd79102_config },
+ { .compatible = "rohm,bd79103", .data = &bd79104_config },
+ { .compatible = "rohm,bd79104", .data = &bd79104_config },
{ }
};
MODULE_DEVICE_TABLE(of, adc128_of_match);
static const struct spi_device_id adc128_id[] = {
- { "adc128s052", (kernel_ulong_t)&adc128_config[0] },
- { "adc122s021", (kernel_ulong_t)&adc128_config[1] },
- { "adc122s051", (kernel_ulong_t)&adc128_config[1] },
- { "adc122s101", (kernel_ulong_t)&adc128_config[1] },
- { "adc124s021", (kernel_ulong_t)&adc128_config[2] },
- { "adc124s051", (kernel_ulong_t)&adc128_config[2] },
- { "adc124s101", (kernel_ulong_t)&adc128_config[2] },
- { "bd79104", (kernel_ulong_t)&adc128_config[3] },
+ { "adc128s052", (kernel_ulong_t)&adc128s_config },
+ { "adc122s021", (kernel_ulong_t)&adc122s_config },
+ { "adc122s051", (kernel_ulong_t)&adc122s_config },
+ { "adc122s101", (kernel_ulong_t)&adc122s_config },
+ { "adc124s021", (kernel_ulong_t)&adc124s_config },
+ { "adc124s051", (kernel_ulong_t)&adc124s_config },
+ { "adc124s101", (kernel_ulong_t)&adc124s_config },
+ { "bd79100", (kernel_ulong_t)&bd79100_config },
+ { "bd79101", (kernel_ulong_t)&bd79101_config },
+ { "bd79102", (kernel_ulong_t)&bd79102_config },
+ { "bd79103", (kernel_ulong_t)&bd79104_config },
+ { "bd79104", (kernel_ulong_t)&bd79104_config },
{ }
};
MODULE_DEVICE_TABLE(spi, adc128_id);
static const struct acpi_device_id adc128_acpi_match[] = {
- { "AANT1280", (kernel_ulong_t)&adc128_config[2] },
+ { "AANT1280", (kernel_ulong_t)&adc124s_config },
{ }
};
MODULE_DEVICE_TABLE(acpi, adc128_acpi_match);
diff --git a/drivers/iio/adc/ti-ads1015.c b/drivers/iio/adc/ti-ads1015.c
index 48549d617e5f..f2a93c63ca14 100644
--- a/drivers/iio/adc/ti-ads1015.c
+++ b/drivers/iio/adc/ti-ads1015.c
@@ -374,12 +374,10 @@ static int ads1015_set_power_state(struct ads1015_data *data, bool on)
int ret;
struct device *dev = regmap_get_device(data->regmap);
- if (on) {
+ if (on)
ret = pm_runtime_resume_and_get(dev);
- } else {
- pm_runtime_mark_last_busy(dev);
+ else
ret = pm_runtime_put_autosuspend(dev);
- }
return ret < 0 ? ret : 0;
}
diff --git a/drivers/iio/adc/ti-ads1100.c b/drivers/iio/adc/ti-ads1100.c
index b0790e300b18..aa8946063c7d 100644
--- a/drivers/iio/adc/ti-ads1100.c
+++ b/drivers/iio/adc/ti-ads1100.c
@@ -105,7 +105,6 @@ static int ads1100_get_adc_result(struct ads1100_data *data, int chan, int *val)
ret = i2c_master_recv(data->client, (char *)&buffer, sizeof(buffer));
- pm_runtime_mark_last_busy(&data->client->dev);
pm_runtime_put_autosuspend(&data->client->dev);
if (ret < 0) {
diff --git a/drivers/iio/adc/ti-ads1119.c b/drivers/iio/adc/ti-ads1119.c
index d2f86e1ec656..c9cedc59cdcd 100644
--- a/drivers/iio/adc/ti-ads1119.c
+++ b/drivers/iio/adc/ti-ads1119.c
@@ -291,7 +291,6 @@ static int ads1119_single_conversion(struct ads1119_state *st,
*val = sign_extend32(sample, chan->scan_type.realbits - 1);
ret = IIO_VAL_INT;
pdown:
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
@@ -470,7 +469,6 @@ static int ads1119_triggered_buffer_postdisable(struct iio_dev *indio_dev)
if (ret)
return ret;
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return 0;
@@ -693,8 +691,7 @@ static int ads1119_probe(struct i2c_client *client)
indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
if (!indio_dev)
- return dev_err_probe(dev, -ENOMEM,
- "Failed to allocate IIO device\n");
+ return -ENOMEM;
st = iio_priv(indio_dev);
st->client = client;
@@ -750,8 +747,7 @@ static int ads1119_probe(struct i2c_client *client)
indio_dev->name,
iio_device_id(indio_dev));
if (!st->trig)
- return dev_err_probe(dev, -ENOMEM,
- "Failed to allocate IIO trigger\n");
+ return -ENOMEM;
st->trig->ops = &ads1119_trigger_ops;
iio_trigger_set_drvdata(st->trig, indio_dev);
@@ -778,8 +774,7 @@ static int ads1119_probe(struct i2c_client *client)
ret = devm_add_action_or_reset(dev, ads1119_powerdown, st);
if (ret)
- return dev_err_probe(dev, ret,
- "Failed to add powerdown action\n");
+ return ret;
return devm_iio_device_register(dev, indio_dev);
}
diff --git a/drivers/iio/adc/ti-ads131e08.c b/drivers/iio/adc/ti-ads131e08.c
index b18f30d3fdbe..742acc6d8cf9 100644
--- a/drivers/iio/adc/ti-ads131e08.c
+++ b/drivers/iio/adc/ti-ads131e08.c
@@ -807,10 +807,8 @@ static int ads131e08_probe(struct spi_device *spi)
}
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
- if (!indio_dev) {
- dev_err(&spi->dev, "failed to allocate IIO device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
st = iio_priv(indio_dev);
st->info = info;
@@ -841,10 +839,8 @@ static int ads131e08_probe(struct spi_device *spi)
st->trig = devm_iio_trigger_alloc(&spi->dev, "%s-dev%d",
indio_dev->name, iio_device_id(indio_dev));
- if (!st->trig) {
- dev_err(&spi->dev, "failed to allocate IIO trigger\n");
+ if (!st->trig)
return -ENOMEM;
- }
st->trig->ops = &ads131e08_trigger_ops;
st->trig->dev.parent = &spi->dev;
diff --git a/drivers/iio/adc/ti-ads7924.c b/drivers/iio/adc/ti-ads7924.c
index b1f745f75dbe..bbcc4fc22b6e 100644
--- a/drivers/iio/adc/ti-ads7924.c
+++ b/drivers/iio/adc/ti-ads7924.c
@@ -355,8 +355,7 @@ static int ads7924_probe(struct i2c_client *client)
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
- return dev_err_probe(dev, -ENOMEM,
- "failed to allocate iio device\n");
+ return -ENOMEM;
data = iio_priv(indio_dev);
@@ -399,8 +398,7 @@ static int ads7924_probe(struct i2c_client *client)
ret = devm_add_action_or_reset(dev, ads7924_reg_disable, data->vref_reg);
if (ret)
- return dev_err_probe(dev, ret,
- "failed to add regulator disable action\n");
+ return ret;
ret = ads7924_reset(indio_dev);
if (ret < 0)
@@ -414,8 +412,7 @@ static int ads7924_probe(struct i2c_client *client)
ret = devm_add_action_or_reset(dev, ads7924_set_idle_mode, data);
if (ret)
- return dev_err_probe(dev, ret,
- "failed to add idle mode action\n");
+ return ret;
/* Use minimum signal acquire time. */
ret = regmap_update_bits(data->regmap, ADS7924_ACQCONFIG_REG,
diff --git a/drivers/iio/adc/ti-tsc2046.c b/drivers/iio/adc/ti-tsc2046.c
index 74471f08662e..8eb717b11cff 100644
--- a/drivers/iio/adc/ti-tsc2046.c
+++ b/drivers/iio/adc/ti-tsc2046.c
@@ -535,8 +535,7 @@ static enum hrtimer_restart tsc2046_adc_timer(struct hrtimer *hrtimer)
if (priv->poll_cnt < TI_TSC2046_POLL_CNT) {
priv->poll_cnt++;
hrtimer_start(&priv->trig_timer,
- ns_to_ktime(priv->scan_interval_us *
- NSEC_PER_USEC),
+ us_to_ktime(priv->scan_interval_us),
HRTIMER_MODE_REL_SOFT);
if (priv->poll_cnt >= TI_TSC2046_MIN_POLL_CNT) {
@@ -605,8 +604,7 @@ static void tsc2046_adc_reenable_trigger(struct iio_trigger *trig)
* many samples. Reduce the sample rate for default (touchscreen) use
* case.
*/
- tim = ns_to_ktime((priv->scan_interval_us - priv->time_per_scan_us) *
- NSEC_PER_USEC);
+ tim = us_to_ktime(priv->scan_interval_us - priv->time_per_scan_us);
hrtimer_start(&priv->trig_timer, tim, HRTIMER_MODE_REL_SOFT);
}
diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c
index fe1509d3b1e7..99f274adc870 100644
--- a/drivers/iio/adc/ti_am335x_adc.c
+++ b/drivers/iio/adc/ti_am335x_adc.c
@@ -631,10 +631,9 @@ static int tiadc_probe(struct platform_device *pdev)
}
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc_dev));
- if (!indio_dev) {
- dev_err(&pdev->dev, "failed to allocate iio device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
+
adc_dev = iio_priv(indio_dev);
adc_dev->mfd_tscadc = ti_tscadc_dev_get(pdev);
diff --git a/drivers/iio/adc/twl4030-madc.c b/drivers/iio/adc/twl4030-madc.c
index 0ea51ddeaa0a..fe3b31ec976e 100644
--- a/drivers/iio/adc/twl4030-madc.c
+++ b/drivers/iio/adc/twl4030-madc.c
@@ -758,10 +758,8 @@ static int twl4030_madc_probe(struct platform_device *pdev)
}
iio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*madc));
- if (!iio_dev) {
- dev_err(&pdev->dev, "failed allocating iio device\n");
+ if (!iio_dev)
return -ENOMEM;
- }
madc = iio_priv(iio_dev);
madc->dev = &pdev->dev;
diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c
index 1b3b1843a801..d7182ed0d2a7 100644
--- a/drivers/iio/adc/vf610_adc.c
+++ b/drivers/iio/adc/vf610_adc.c
@@ -832,7 +832,7 @@ static int vf610_adc_probe(struct platform_device *pdev)
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct vf610_adc));
if (!indio_dev)
- return dev_err_probe(&pdev->dev, -ENOMEM, "Failed allocating iio device\n");
+ return -ENOMEM;
info = iio_priv(indio_dev);
info->dev = &pdev->dev;
diff --git a/drivers/iio/adc/viperboard_adc.c b/drivers/iio/adc/viperboard_adc.c
index 1028b101cf56..9bb0b83c8f67 100644
--- a/drivers/iio/adc/viperboard_adc.c
+++ b/drivers/iio/adc/viperboard_adc.c
@@ -113,10 +113,8 @@ static int vprbrd_adc_probe(struct platform_device *pdev)
/* registering iio */
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc));
- if (!indio_dev) {
- dev_err(&pdev->dev, "failed allocating iio device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
adc = iio_priv(indio_dev);
adc->vb = vb;
diff --git a/drivers/iio/adc/xilinx-ams.c b/drivers/iio/adc/xilinx-ams.c
index 76dd0343f5f7..124470c92529 100644
--- a/drivers/iio/adc/xilinx-ams.c
+++ b/drivers/iio/adc/xilinx-ams.c
@@ -118,7 +118,7 @@
#define AMS_ALARM_THRESHOLD_OFF_10 0x10
#define AMS_ALARM_THRESHOLD_OFF_20 0x20
-#define AMS_ALARM_THR_DIRECT_MASK BIT(1)
+#define AMS_ALARM_THR_DIRECT_MASK BIT(0)
#define AMS_ALARM_THR_MIN 0x0000
#define AMS_ALARM_THR_MAX (BIT(16) - 1)
@@ -389,6 +389,29 @@ static void ams_update_pl_alarm(struct ams *ams, unsigned long alarm_mask)
ams_pl_update_reg(ams, AMS_REG_CONFIG3, AMS_REGCFG3_ALARM_MASK, cfg);
}
+static void ams_unmask(struct ams *ams)
+{
+ unsigned int status, unmask;
+
+ status = readl(ams->base + AMS_ISR_0);
+
+ /* Clear those bits which are not active anymore */
+ unmask = (ams->current_masked_alarm ^ status) & ams->current_masked_alarm;
+
+ /* Clear status of disabled alarm */
+ unmask |= ams->intr_mask;
+
+ ams->current_masked_alarm &= status;
+
+ /* Also clear those which are masked out anyway */
+ ams->current_masked_alarm &= ~ams->intr_mask;
+
+ /* Clear the interrupts before we unmask them */
+ writel(unmask, ams->base + AMS_ISR_0);
+
+ ams_update_intrmask(ams, ~AMS_ALARM_MASK, ~AMS_ALARM_MASK);
+}
+
static void ams_update_alarm(struct ams *ams, unsigned long alarm_mask)
{
unsigned long flags;
@@ -401,6 +424,7 @@ static void ams_update_alarm(struct ams *ams, unsigned long alarm_mask)
spin_lock_irqsave(&ams->intr_lock, flags);
ams_update_intrmask(ams, AMS_ISR0_ALARM_MASK, ~alarm_mask);
+ ams_unmask(ams);
spin_unlock_irqrestore(&ams->intr_lock, flags);
}
@@ -1035,28 +1059,9 @@ static void ams_handle_events(struct iio_dev *indio_dev, unsigned long events)
static void ams_unmask_worker(struct work_struct *work)
{
struct ams *ams = container_of(work, struct ams, ams_unmask_work.work);
- unsigned int status, unmask;
spin_lock_irq(&ams->intr_lock);
-
- status = readl(ams->base + AMS_ISR_0);
-
- /* Clear those bits which are not active anymore */
- unmask = (ams->current_masked_alarm ^ status) & ams->current_masked_alarm;
-
- /* Clear status of disabled alarm */
- unmask |= ams->intr_mask;
-
- ams->current_masked_alarm &= status;
-
- /* Also clear those which are masked out anyway */
- ams->current_masked_alarm &= ~ams->intr_mask;
-
- /* Clear the interrupts before we unmask them */
- writel(unmask, ams->base + AMS_ISR_0);
-
- ams_update_intrmask(ams, ~AMS_ALARM_MASK, ~AMS_ALARM_MASK);
-
+ ams_unmask(ams);
spin_unlock_irq(&ams->intr_lock);
/* If still pending some alarm re-trigger the timer */
diff --git a/drivers/iio/buffer/industrialio-buffer-cb.c b/drivers/iio/buffer/industrialio-buffer-cb.c
index 4befc9f55201..3e27385069ed 100644
--- a/drivers/iio/buffer/industrialio-buffer-cb.c
+++ b/drivers/iio/buffer/industrialio-buffer-cb.c
@@ -68,7 +68,6 @@ struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev,
cb_buff->private = private;
cb_buff->cb = cb;
cb_buff->buffer.access = &iio_cb_access;
- INIT_LIST_HEAD(&cb_buff->buffer.demux_list);
cb_buff->channels = iio_channel_get_all(dev);
if (IS_ERR(cb_buff->channels)) {
diff --git a/drivers/iio/chemical/atlas-sensor.c b/drivers/iio/chemical/atlas-sensor.c
index 1daaa36f87a9..8bbba85af699 100644
--- a/drivers/iio/chemical/atlas-sensor.c
+++ b/drivers/iio/chemical/atlas-sensor.c
@@ -425,7 +425,6 @@ static int atlas_buffer_predisable(struct iio_dev *indio_dev)
if (ret)
return ret;
- pm_runtime_mark_last_busy(&data->client->dev);
ret = pm_runtime_put_autosuspend(&data->client->dev);
if (ret)
return ret;
@@ -491,7 +490,6 @@ static int atlas_read_measurement(struct atlas_data *data, int reg, __be32 *val)
ret = regmap_bulk_read(data->regmap, reg, val, sizeof(*val));
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
diff --git a/drivers/iio/chemical/bme680_core.c b/drivers/iio/chemical/bme680_core.c
index 61d446fd456c..70f81c4a96ba 100644
--- a/drivers/iio/chemical/bme680_core.c
+++ b/drivers/iio/chemical/bme680_core.c
@@ -950,7 +950,6 @@ static int bme680_read_raw(struct iio_dev *indio_dev,
return ret;
ret = __bme680_read_raw(indio_dev, chan, val, val2, mask);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
@@ -1021,7 +1020,6 @@ static int bme680_write_raw(struct iio_dev *indio_dev,
return ret;
ret = __bme680_write_raw(indio_dev, chan, val, val2, mask);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
@@ -1140,7 +1138,6 @@ static int bme680_buffer_postdisable(struct iio_dev *indio_dev)
struct bme680_data *data = iio_priv(indio_dev);
struct device *dev = regmap_get_device(data->regmap);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return 0;
}
diff --git a/drivers/iio/chemical/ens160_core.c b/drivers/iio/chemical/ens160_core.c
index 6cec60074827..86bde4a91bf7 100644
--- a/drivers/iio/chemical/ens160_core.c
+++ b/drivers/iio/chemical/ens160_core.c
@@ -305,8 +305,7 @@ static int ens160_setup_trigger(struct iio_dev *indio_dev, int irq)
trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
iio_device_id(indio_dev));
if (!trig)
- return dev_err_probe(dev, -ENOMEM,
- "failed to allocate trigger\n");
+ return -ENOMEM;
trig->ops = &ens160_trigger_ops;
iio_trigger_set_drvdata(trig, indio_dev);
diff --git a/drivers/iio/chemical/scd30_core.c b/drivers/iio/chemical/scd30_core.c
index 5df1926cd5d9..a665fcb78806 100644
--- a/drivers/iio/chemical/scd30_core.c
+++ b/drivers/iio/chemical/scd30_core.c
@@ -635,7 +635,7 @@ static int scd30_setup_trigger(struct iio_dev *indio_dev)
trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
iio_device_id(indio_dev));
if (!trig)
- return dev_err_probe(dev, -ENOMEM, "failed to allocate trigger\n");
+ return -ENOMEM;
trig->ops = &scd30_trigger_ops;
iio_trigger_set_drvdata(trig, indio_dev);
diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c
index 48193937275b..5540e2d28f4a 100644
--- a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c
+++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c
@@ -163,7 +163,6 @@ int hid_sensor_power_state(struct hid_sensor_common *st, bool state)
ret = pm_runtime_resume_and_get(&st->pdev->dev);
} else {
atomic_dec(&st->user_requested_state);
- pm_runtime_mark_last_busy(&st->pdev->dev);
pm_runtime_use_autosuspend(&st->pdev->dev);
ret = pm_runtime_put_autosuspend(&st->pdev->dev);
}
diff --git a/drivers/iio/common/scmi_sensors/scmi_iio.c b/drivers/iio/common/scmi_sensors/scmi_iio.c
index da516c46e057..39c61c47022a 100644
--- a/drivers/iio/common/scmi_sensors/scmi_iio.c
+++ b/drivers/iio/common/scmi_sensors/scmi_iio.c
@@ -521,9 +521,9 @@ static int scmi_iio_set_sampling_freq_avail(struct iio_dev *iio_dev)
int i;
sensor->freq_avail =
- devm_kzalloc(&iio_dev->dev,
- sizeof(*sensor->freq_avail) *
- (sensor->sensor_info->intervals.count * 2),
+ devm_kcalloc(&iio_dev->dev,
+ array_size(sensor->sensor_info->intervals.count, 2),
+ sizeof(*sensor->freq_avail),
GFP_KERNEL);
if (!sensor->freq_avail)
return -ENOMEM;
@@ -597,8 +597,8 @@ scmi_alloc_iiodev(struct scmi_device *sdev,
iiodev->info = &scmi_iio_info;
iio_channels =
- devm_kzalloc(dev,
- sizeof(*iio_channels) * (iiodev->num_channels),
+ devm_kcalloc(dev, iiodev->num_channels,
+ sizeof(*iio_channels),
GFP_KERNEL);
if (!iio_channels)
return ERR_PTR(-ENOMEM);
diff --git a/drivers/iio/dac/ad5360.c b/drivers/iio/dac/ad5360.c
index a57b0a093112..8271849b1c83 100644
--- a/drivers/iio/dac/ad5360.c
+++ b/drivers/iio/dac/ad5360.c
@@ -262,7 +262,7 @@ static int ad5360_update_ctrl(struct iio_dev *indio_dev, unsigned int set,
unsigned int clr)
{
struct ad5360_state *st = iio_priv(indio_dev);
- unsigned int ret;
+ int ret;
mutex_lock(&st->lock);
diff --git a/drivers/iio/dac/ad5380.c b/drivers/iio/dac/ad5380.c
index 0ddce7b218e3..8b813cee7625 100644
--- a/drivers/iio/dac/ad5380.c
+++ b/drivers/iio/dac/ad5380.c
@@ -371,10 +371,8 @@ static int ad5380_probe(struct device *dev, struct regmap *regmap,
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
- if (indio_dev == NULL) {
- dev_err(dev, "Failed to allocate iio device\n");
+ if (indio_dev == NULL)
return -ENOMEM;
- }
st = iio_priv(indio_dev);
diff --git a/drivers/iio/dac/ad5421.c b/drivers/iio/dac/ad5421.c
index 1462ee640b16..d9d7031c4432 100644
--- a/drivers/iio/dac/ad5421.c
+++ b/drivers/iio/dac/ad5421.c
@@ -186,7 +186,7 @@ static int ad5421_update_ctrl(struct iio_dev *indio_dev, unsigned int set,
unsigned int clr)
{
struct ad5421_state *st = iio_priv(indio_dev);
- unsigned int ret;
+ int ret;
mutex_lock(&st->lock);
diff --git a/drivers/iio/dac/ad5764.c b/drivers/iio/dac/ad5764.c
index 26c049d5b73a..fbbd7105a80c 100644
--- a/drivers/iio/dac/ad5764.c
+++ b/drivers/iio/dac/ad5764.c
@@ -278,10 +278,8 @@ static int ad5764_probe(struct spi_device *spi)
int ret;
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
- if (indio_dev == NULL) {
- dev_err(&spi->dev, "Failed to allocate iio device\n");
+ if (indio_dev == NULL)
return -ENOMEM;
- }
st = iio_priv(indio_dev);
spi_set_drvdata(spi, indio_dev);
diff --git a/drivers/iio/dac/ad5791.c b/drivers/iio/dac/ad5791.c
index 41582f2b90fb..ae7297f08398 100644
--- a/drivers/iio/dac/ad5791.c
+++ b/drivers/iio/dac/ad5791.c
@@ -80,8 +80,6 @@ struct ad5791_chip_info {
/**
* struct ad5791_state - driver instance specific data
* @spi: spi_device
- * @reg_vdd: positive supply regulator
- * @reg_vss: negative supply regulator
* @gpio_reset: reset gpio
* @gpio_clear: clear gpio
* @gpio_ldac: load dac gpio
@@ -100,8 +98,6 @@ struct ad5791_chip_info {
*/
struct ad5791_state {
struct spi_device *spi;
- struct regulator *reg_vdd;
- struct regulator *reg_vss;
struct gpio_desc *gpio_reset;
struct gpio_desc *gpio_clear;
struct gpio_desc *gpio_ldac;
diff --git a/drivers/iio/dac/ds4424.c b/drivers/iio/dac/ds4424.c
index a26a99753418..a8198ba4f98a 100644
--- a/drivers/iio/dac/ds4424.c
+++ b/drivers/iio/dac/ds4424.c
@@ -221,10 +221,8 @@ static int ds4424_probe(struct i2c_client *client)
int ret;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
- if (!indio_dev) {
- dev_err(&client->dev, "iio dev alloc failed.\n");
+ if (!indio_dev)
return -ENOMEM;
- }
data = iio_priv(indio_dev);
i2c_set_clientdata(client, indio_dev);
diff --git a/drivers/iio/dac/stm32-dac.c b/drivers/iio/dac/stm32-dac.c
index 344388338d9b..b860e18d52a1 100644
--- a/drivers/iio/dac/stm32-dac.c
+++ b/drivers/iio/dac/stm32-dac.c
@@ -82,9 +82,11 @@ static int stm32_dac_set_enable_state(struct iio_dev *indio_dev, int ch,
ret = regmap_update_bits(dac->common->regmap, STM32_DAC_CR, msk, en);
mutex_unlock(&dac->lock);
- if (ret < 0) {
+ if (ret) {
dev_err(&indio_dev->dev, "%s failed\n", str_enable_disable(en));
- goto err_put_pm;
+ if (enable)
+ pm_runtime_put_autosuspend(dev);
+ return ret;
}
/*
@@ -95,20 +97,10 @@ static int stm32_dac_set_enable_state(struct iio_dev *indio_dev, int ch,
if (en && dac->common->hfsel)
udelay(1);
- if (!enable) {
- pm_runtime_mark_last_busy(dev);
+ if (!enable)
pm_runtime_put_autosuspend(dev);
- }
return 0;
-
-err_put_pm:
- if (enable) {
- pm_runtime_mark_last_busy(dev);
- pm_runtime_put_autosuspend(dev);
- }
-
- return ret;
}
static int stm32_dac_get_value(struct stm32_dac *dac, int channel, int *val)
@@ -349,7 +341,6 @@ static int stm32_dac_probe(struct platform_device *pdev)
if (ret)
goto err_pm_put;
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return 0;
diff --git a/drivers/iio/dac/ti-dac7311.c b/drivers/iio/dac/ti-dac7311.c
index 3d2ce61f0db6..5c1c5213962f 100644
--- a/drivers/iio/dac/ti-dac7311.c
+++ b/drivers/iio/dac/ti-dac7311.c
@@ -242,10 +242,8 @@ static int ti_dac_probe(struct spi_device *spi)
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*ti_dac));
- if (!indio_dev) {
- dev_err(dev, "can not allocate iio device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
spi->mode = SPI_MODE_1;
spi->bits_per_word = 16;
diff --git a/drivers/iio/frequency/adf4350.c b/drivers/iio/frequency/adf4350.c
index 47f1c7e9efa9..ed1741165f55 100644
--- a/drivers/iio/frequency/adf4350.c
+++ b/drivers/iio/frequency/adf4350.c
@@ -149,6 +149,19 @@ static int adf4350_set_freq(struct adf4350_state *st, unsigned long long freq)
if (freq > ADF4350_MAX_OUT_FREQ || freq < st->min_out_freq)
return -EINVAL;
+ st->r4_rf_div_sel = 0;
+
+ /*
+ * !\TODO: The below computation is making sure we get a power of 2
+ * shift (st->r4_rf_div_sel) so that freq becomes higher or equal to
+ * ADF4350_MIN_VCO_FREQ. This might be simplified with fls()/fls_long()
+ * and friends.
+ */
+ while (freq < ADF4350_MIN_VCO_FREQ) {
+ freq <<= 1;
+ st->r4_rf_div_sel++;
+ }
+
if (freq > ADF4350_MAX_FREQ_45_PRESC) {
prescaler = ADF4350_REG1_PRESCALER;
mdiv = 75;
@@ -157,13 +170,6 @@ static int adf4350_set_freq(struct adf4350_state *st, unsigned long long freq)
mdiv = 23;
}
- st->r4_rf_div_sel = 0;
-
- while (freq < ADF4350_MIN_VCO_FREQ) {
- freq <<= 1;
- st->r4_rf_div_sel++;
- }
-
/*
* Allow a predefined reference division factor
* if not set, compute our own
@@ -673,8 +679,7 @@ static int adf4350_probe(struct spi_device *spi)
ret = devm_add_action_or_reset(&spi->dev, adf4350_power_down, indio_dev);
if (ret)
- return dev_err_probe(&spi->dev, ret,
- "Failed to add action to managed power down\n");
+ return ret;
return devm_iio_device_register(&spi->dev, indio_dev);
}
diff --git a/drivers/iio/gyro/bmg160_core.c b/drivers/iio/gyro/bmg160_core.c
index 781d3e96645f..38394b5f3275 100644
--- a/drivers/iio/gyro/bmg160_core.c
+++ b/drivers/iio/gyro/bmg160_core.c
@@ -309,10 +309,8 @@ static int bmg160_set_power_state(struct bmg160_data *data, bool on)
if (on)
ret = pm_runtime_get_sync(dev);
- else {
- pm_runtime_mark_last_busy(dev);
+ else
ret = pm_runtime_put_autosuspend(dev);
- }
if (ret < 0) {
dev_err(dev, "Failed: bmg160_set_power_state for %d\n", on);
diff --git a/drivers/iio/gyro/fxas21002c_core.c b/drivers/iio/gyro/fxas21002c_core.c
index 754c8a564ba4..a88670207cec 100644
--- a/drivers/iio/gyro/fxas21002c_core.c
+++ b/drivers/iio/gyro/fxas21002c_core.c
@@ -373,8 +373,6 @@ static int fxas21002c_pm_put(struct fxas21002c_data *data)
{
struct device *dev = regmap_get_device(data->regmap);
- pm_runtime_mark_last_busy(dev);
-
return pm_runtime_put_autosuspend(dev);
}
diff --git a/drivers/iio/gyro/mpu3050-core.c b/drivers/iio/gyro/mpu3050-core.c
index 16553948c5c3..67ae7d1012bc 100644
--- a/drivers/iio/gyro/mpu3050-core.c
+++ b/drivers/iio/gyro/mpu3050-core.c
@@ -370,7 +370,6 @@ static int mpu3050_read_raw(struct iio_dev *indio_dev,
out_read_raw_unlock:
mutex_unlock(&mpu3050->lock);
- pm_runtime_mark_last_busy(mpu3050->dev);
pm_runtime_put_autosuspend(mpu3050->dev);
return ret;
@@ -662,7 +661,6 @@ static int mpu3050_buffer_postdisable(struct iio_dev *indio_dev)
{
struct mpu3050 *mpu3050 = iio_priv(indio_dev);
- pm_runtime_mark_last_busy(mpu3050->dev);
pm_runtime_put_autosuspend(mpu3050->dev);
return 0;
@@ -976,7 +974,6 @@ static int mpu3050_drdy_trigger_set_state(struct iio_trigger *trig,
if (ret)
dev_err(mpu3050->dev, "error resetting FIFO\n");
- pm_runtime_mark_last_busy(mpu3050->dev);
pm_runtime_put_autosuspend(mpu3050->dev);
mpu3050->hw_irq_trigger = false;
diff --git a/drivers/iio/gyro/mpu3050-i2c.c b/drivers/iio/gyro/mpu3050-i2c.c
index 8e284f47242c..092878f2c886 100644
--- a/drivers/iio/gyro/mpu3050-i2c.c
+++ b/drivers/iio/gyro/mpu3050-i2c.c
@@ -27,7 +27,6 @@ static int mpu3050_i2c_bypass_deselect(struct i2c_mux_core *mux, u32 chan_id)
{
struct mpu3050 *mpu3050 = i2c_mux_priv(mux);
- pm_runtime_mark_last_busy(mpu3050->dev);
pm_runtime_put_autosuspend(mpu3050->dev);
return 0;
}
diff --git a/drivers/iio/health/afe4403.c b/drivers/iio/health/afe4403.c
index 30d3f984b032..0e5a512e3bb8 100644
--- a/drivers/iio/health/afe4403.c
+++ b/drivers/iio/health/afe4403.c
@@ -58,7 +58,6 @@ static const struct reg_field afe4403_reg_fields[] = {
/**
* struct afe4403_data - AFE4403 device instance data
- * @dev: Device structure
* @spi: SPI device handle
* @regmap: Register map of the device
* @fields: Register fields of the device
@@ -68,7 +67,6 @@ static const struct reg_field afe4403_reg_fields[] = {
* @buffer: Used to construct data layout to push into IIO buffer.
*/
struct afe4403_data {
- struct device *dev;
struct spi_device *spi;
struct regmap *regmap;
struct regmap_field *fields[F_MAX_FIELDS];
@@ -460,63 +458,63 @@ static DEFINE_SIMPLE_DEV_PM_OPS(afe4403_pm_ops, afe4403_suspend,
static int afe4403_probe(struct spi_device *spi)
{
+ struct device *dev = &spi->dev;
struct iio_dev *indio_dev;
struct afe4403_data *afe;
int i, ret;
- indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*afe));
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*afe));
if (!indio_dev)
return -ENOMEM;
afe = iio_priv(indio_dev);
spi_set_drvdata(spi, indio_dev);
- afe->dev = &spi->dev;
afe->spi = spi;
afe->irq = spi->irq;
afe->regmap = devm_regmap_init_spi(spi, &afe4403_regmap_config);
if (IS_ERR(afe->regmap)) {
- dev_err(afe->dev, "Unable to allocate register map\n");
+ dev_err(dev, "Unable to allocate register map\n");
return PTR_ERR(afe->regmap);
}
for (i = 0; i < F_MAX_FIELDS; i++) {
- afe->fields[i] = devm_regmap_field_alloc(afe->dev, afe->regmap,
+ afe->fields[i] = devm_regmap_field_alloc(dev, afe->regmap,
afe4403_reg_fields[i]);
if (IS_ERR(afe->fields[i])) {
- dev_err(afe->dev, "Unable to allocate regmap fields\n");
+ dev_err(dev, "Unable to allocate regmap fields\n");
return PTR_ERR(afe->fields[i]);
}
}
- afe->regulator = devm_regulator_get(afe->dev, "tx_sup");
+ afe->regulator = devm_regulator_get(dev, "tx_sup");
if (IS_ERR(afe->regulator))
- return dev_err_probe(afe->dev, PTR_ERR(afe->regulator),
+ return dev_err_probe(dev, PTR_ERR(afe->regulator),
"Unable to get regulator\n");
ret = regulator_enable(afe->regulator);
if (ret) {
- dev_err(afe->dev, "Unable to enable regulator\n");
+ dev_err(dev, "Unable to enable regulator\n");
return ret;
}
- ret = devm_add_action_or_reset(afe->dev, afe4403_regulator_disable, afe->regulator);
+ ret = devm_add_action_or_reset(dev, afe4403_regulator_disable, afe->regulator);
if (ret) {
- dev_err(afe->dev, "Unable to add regulator disable action\n");
+ dev_err(dev, "Unable to add regulator disable action\n");
return ret;
}
ret = regmap_write(afe->regmap, AFE440X_CONTROL0,
AFE440X_CONTROL0_SW_RESET);
if (ret) {
- dev_err(afe->dev, "Unable to reset device\n");
+ dev_err(dev, "Unable to reset device\n");
return ret;
}
ret = regmap_multi_reg_write(afe->regmap, afe4403_reg_sequences,
ARRAY_SIZE(afe4403_reg_sequences));
if (ret) {
- dev_err(afe->dev, "Unable to set register defaults\n");
+ dev_err(dev, "Unable to set register defaults\n");
return ret;
}
@@ -527,45 +525,43 @@ static int afe4403_probe(struct spi_device *spi)
indio_dev->info = &afe4403_iio_info;
if (afe->irq > 0) {
- afe->trig = devm_iio_trigger_alloc(afe->dev,
+ afe->trig = devm_iio_trigger_alloc(dev,
"%s-dev%d",
indio_dev->name,
iio_device_id(indio_dev));
- if (!afe->trig) {
- dev_err(afe->dev, "Unable to allocate IIO trigger\n");
+ if (!afe->trig)
return -ENOMEM;
- }
iio_trigger_set_drvdata(afe->trig, indio_dev);
- ret = devm_iio_trigger_register(afe->dev, afe->trig);
+ ret = devm_iio_trigger_register(dev, afe->trig);
if (ret) {
- dev_err(afe->dev, "Unable to register IIO trigger\n");
+ dev_err(dev, "Unable to register IIO trigger\n");
return ret;
}
- ret = devm_request_threaded_irq(afe->dev, afe->irq,
+ ret = devm_request_threaded_irq(dev, afe->irq,
iio_trigger_generic_data_rdy_poll,
NULL, IRQF_ONESHOT,
AFE4403_DRIVER_NAME,
afe->trig);
if (ret) {
- dev_err(afe->dev, "Unable to request IRQ\n");
+ dev_err(dev, "Unable to request IRQ\n");
return ret;
}
}
- ret = devm_iio_triggered_buffer_setup(afe->dev, indio_dev,
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
&iio_pollfunc_store_time,
afe4403_trigger_handler, NULL);
if (ret) {
- dev_err(afe->dev, "Unable to setup buffer\n");
+ dev_err(dev, "Unable to setup buffer\n");
return ret;
}
- ret = devm_iio_device_register(afe->dev, indio_dev);
+ ret = devm_iio_device_register(dev, indio_dev);
if (ret) {
- dev_err(afe->dev, "Unable to register IIO device\n");
+ dev_err(dev, "Unable to register IIO device\n");
return ret;
}
diff --git a/drivers/iio/health/afe4404.c b/drivers/iio/health/afe4404.c
index b2727effecaa..768d794e574b 100644
--- a/drivers/iio/health/afe4404.c
+++ b/drivers/iio/health/afe4404.c
@@ -77,7 +77,6 @@ static const struct reg_field afe4404_reg_fields[] = {
/**
* struct afe4404_data - AFE4404 device instance data
- * @dev: Device structure
* @regmap: Register map of the device
* @fields: Register fields of the device
* @regulator: Pointer to the regulator for the IC
@@ -86,7 +85,6 @@ static const struct reg_field afe4404_reg_fields[] = {
* @buffer: Used to construct a scan to push to the iio buffer.
*/
struct afe4404_data {
- struct device *dev;
struct regmap *regmap;
struct regmap_field *fields[F_MAX_FIELDS];
struct regulator *regulator;
@@ -468,62 +466,62 @@ static DEFINE_SIMPLE_DEV_PM_OPS(afe4404_pm_ops, afe4404_suspend,
static int afe4404_probe(struct i2c_client *client)
{
+ struct device *dev = &client->dev;
struct iio_dev *indio_dev;
struct afe4404_data *afe;
int i, ret;
- indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*afe));
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*afe));
if (!indio_dev)
return -ENOMEM;
afe = iio_priv(indio_dev);
i2c_set_clientdata(client, indio_dev);
- afe->dev = &client->dev;
afe->irq = client->irq;
afe->regmap = devm_regmap_init_i2c(client, &afe4404_regmap_config);
if (IS_ERR(afe->regmap)) {
- dev_err(afe->dev, "Unable to allocate register map\n");
+ dev_err(dev, "Unable to allocate register map\n");
return PTR_ERR(afe->regmap);
}
for (i = 0; i < F_MAX_FIELDS; i++) {
- afe->fields[i] = devm_regmap_field_alloc(afe->dev, afe->regmap,
+ afe->fields[i] = devm_regmap_field_alloc(dev, afe->regmap,
afe4404_reg_fields[i]);
if (IS_ERR(afe->fields[i])) {
- dev_err(afe->dev, "Unable to allocate regmap fields\n");
+ dev_err(dev, "Unable to allocate regmap fields\n");
return PTR_ERR(afe->fields[i]);
}
}
- afe->regulator = devm_regulator_get(afe->dev, "tx_sup");
+ afe->regulator = devm_regulator_get(dev, "tx_sup");
if (IS_ERR(afe->regulator))
- return dev_err_probe(afe->dev, PTR_ERR(afe->regulator),
+ return dev_err_probe(dev, PTR_ERR(afe->regulator),
"Unable to get regulator\n");
ret = regulator_enable(afe->regulator);
if (ret) {
- dev_err(afe->dev, "Unable to enable regulator\n");
+ dev_err(dev, "Unable to enable regulator\n");
return ret;
}
- ret = devm_add_action_or_reset(afe->dev, afe4404_regulator_disable, afe->regulator);
+ ret = devm_add_action_or_reset(dev, afe4404_regulator_disable, afe->regulator);
if (ret) {
- dev_err(afe->dev, "Unable to enable regulator\n");
+ dev_err(dev, "Unable to enable regulator\n");
return ret;
}
ret = regmap_write(afe->regmap, AFE440X_CONTROL0,
AFE440X_CONTROL0_SW_RESET);
if (ret) {
- dev_err(afe->dev, "Unable to reset device\n");
+ dev_err(dev, "Unable to reset device\n");
return ret;
}
ret = regmap_multi_reg_write(afe->regmap, afe4404_reg_sequences,
ARRAY_SIZE(afe4404_reg_sequences));
if (ret) {
- dev_err(afe->dev, "Unable to set register defaults\n");
+ dev_err(dev, "Unable to set register defaults\n");
return ret;
}
@@ -534,45 +532,43 @@ static int afe4404_probe(struct i2c_client *client)
indio_dev->info = &afe4404_iio_info;
if (afe->irq > 0) {
- afe->trig = devm_iio_trigger_alloc(afe->dev,
+ afe->trig = devm_iio_trigger_alloc(dev,
"%s-dev%d",
indio_dev->name,
iio_device_id(indio_dev));
- if (!afe->trig) {
- dev_err(afe->dev, "Unable to allocate IIO trigger\n");
+ if (!afe->trig)
return -ENOMEM;
- }
iio_trigger_set_drvdata(afe->trig, indio_dev);
- ret = devm_iio_trigger_register(afe->dev, afe->trig);
+ ret = devm_iio_trigger_register(dev, afe->trig);
if (ret) {
- dev_err(afe->dev, "Unable to register IIO trigger\n");
+ dev_err(dev, "Unable to register IIO trigger\n");
return ret;
}
- ret = devm_request_threaded_irq(afe->dev, afe->irq,
+ ret = devm_request_threaded_irq(dev, afe->irq,
iio_trigger_generic_data_rdy_poll,
NULL, IRQF_ONESHOT,
AFE4404_DRIVER_NAME,
afe->trig);
if (ret) {
- dev_err(afe->dev, "Unable to request IRQ\n");
+ dev_err(dev, "Unable to request IRQ\n");
return ret;
}
}
- ret = devm_iio_triggered_buffer_setup(afe->dev, indio_dev,
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
&iio_pollfunc_store_time,
afe4404_trigger_handler, NULL);
if (ret) {
- dev_err(afe->dev, "Unable to setup buffer\n");
+ dev_err(dev, "Unable to setup buffer\n");
return ret;
}
- ret = devm_iio_device_register(afe->dev, indio_dev);
+ ret = devm_iio_device_register(dev, indio_dev);
if (ret) {
- dev_err(afe->dev, "Unable to register IIO device\n");
+ dev_err(dev, "Unable to register IIO device\n");
return ret;
}
diff --git a/drivers/iio/humidity/am2315.c b/drivers/iio/humidity/am2315.c
index f021c3e6d886..02ca23eb8991 100644
--- a/drivers/iio/humidity/am2315.c
+++ b/drivers/iio/humidity/am2315.c
@@ -224,10 +224,8 @@ static int am2315_probe(struct i2c_client *client)
struct am2315_data *data;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
- if (!indio_dev) {
- dev_err(&client->dev, "iio allocation failed!\n");
+ if (!indio_dev)
return -ENOMEM;
- }
data = iio_priv(indio_dev);
data->client = client;
diff --git a/drivers/iio/humidity/dht11.c b/drivers/iio/humidity/dht11.c
index 73d2033954e7..980cb946bbf7 100644
--- a/drivers/iio/humidity/dht11.c
+++ b/drivers/iio/humidity/dht11.c
@@ -294,10 +294,8 @@ static int dht11_probe(struct platform_device *pdev)
struct iio_dev *iio;
iio = devm_iio_device_alloc(dev, sizeof(*dht11));
- if (!iio) {
- dev_err(dev, "Failed to allocate IIO device\n");
+ if (!iio)
return -ENOMEM;
- }
dht11 = iio_priv(iio);
dht11->dev = dev;
diff --git a/drivers/iio/imu/adis16475.c b/drivers/iio/imu/adis16475.c
index 924395b7e3b4..ab39bea1e729 100644
--- a/drivers/iio/imu/adis16475.c
+++ b/drivers/iio/imu/adis16475.c
@@ -1930,7 +1930,6 @@ static int adis16475_config_irq_pin(struct adis16475 *st)
return 0;
}
-
static int adis16475_probe(struct spi_device *spi)
{
struct iio_dev *indio_dev;
diff --git a/drivers/iio/imu/bmi270/bmi270_i2c.c b/drivers/iio/imu/bmi270/bmi270_i2c.c
index c77839b03a96..b909a421ad01 100644
--- a/drivers/iio/imu/bmi270/bmi270_i2c.c
+++ b/drivers/iio/imu/bmi270/bmi270_i2c.c
@@ -41,6 +41,8 @@ static const struct i2c_device_id bmi270_i2c_id[] = {
static const struct acpi_device_id bmi270_acpi_match[] = {
/* GPD Win Mini, Aya Neo AIR Pro, OXP Mini Pro, etc. */
{ "BMI0160", (kernel_ulong_t)&bmi260_chip_info },
+ /* GPD Win Max 2 2023(sincice BIOS v0.40), etc. */
+ { "BMI0260", (kernel_ulong_t)&bmi260_chip_info },
{ }
};
diff --git a/drivers/iio/imu/bmi323/bmi323_core.c b/drivers/iio/imu/bmi323/bmi323_core.c
index fc54d464a3ae..6bcb9a436581 100644
--- a/drivers/iio/imu/bmi323/bmi323_core.c
+++ b/drivers/iio/imu/bmi323/bmi323_core.c
@@ -2112,8 +2112,7 @@ int bmi323_core_probe(struct device *dev)
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
- return dev_err_probe(dev, -ENOMEM,
- "Failed to allocate device\n");
+ return -ENOMEM;
ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names),
regulator_names);
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h
index 1430ab4f1dea..c8b48a5c5ed0 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600.h
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h
@@ -167,7 +167,6 @@ struct inv_icm42600_state {
enum inv_icm42600_chip chip;
const char *name;
struct regmap *map;
- struct regulator *vdd_supply;
struct regulator *vddio_supply;
int irq;
struct iio_mount_matrix orientation;
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
index 7a28051330b7..54760d8f92a2 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
@@ -315,7 +315,6 @@ static int inv_icm42600_accel_read_sensor(struct iio_dev *indio_dev,
ret = -EINVAL;
exit:
mutex_unlock(&st->lock);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
@@ -562,12 +561,10 @@ static int inv_icm42600_accel_write_scale(struct iio_dev *indio_dev,
conf.fs = idx / 2;
pm_runtime_get_sync(dev);
- mutex_lock(&st->lock);
- ret = inv_icm42600_set_accel_conf(st, &conf, NULL);
+ scoped_guard(mutex, &st->lock)
+ ret = inv_icm42600_set_accel_conf(st, &conf, NULL);
- mutex_unlock(&st->lock);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
@@ -675,7 +672,6 @@ static int inv_icm42600_accel_write_odr(struct iio_dev *indio_dev,
out_unlock:
mutex_unlock(&st->lock);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
@@ -727,7 +723,6 @@ static int inv_icm42600_accel_read_offset(struct inv_icm42600_state *st,
memcpy(data, st->buffer, sizeof(data));
mutex_unlock(&st->lock);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
if (ret)
return ret;
@@ -865,7 +860,6 @@ static int inv_icm42600_accel_write_offset(struct inv_icm42600_state *st,
out_unlock:
mutex_unlock(&st->lock);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
@@ -991,16 +985,11 @@ static int inv_icm42600_accel_hwfifo_set_watermark(struct iio_dev *indio_dev,
unsigned int val)
{
struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
- int ret;
- mutex_lock(&st->lock);
+ guard(mutex)(&st->lock);
st->fifo.watermark.accel = val;
- ret = inv_icm42600_buffer_update_watermark(st);
-
- mutex_unlock(&st->lock);
-
- return ret;
+ return inv_icm42600_buffer_update_watermark(st);
}
static int inv_icm42600_accel_hwfifo_flush(struct iio_dev *indio_dev,
@@ -1012,15 +1001,13 @@ static int inv_icm42600_accel_hwfifo_flush(struct iio_dev *indio_dev,
if (count == 0)
return 0;
- mutex_lock(&st->lock);
+ guard(mutex)(&st->lock);
ret = inv_icm42600_buffer_hwfifo_flush(st, count);
- if (!ret)
- ret = st->fifo.nb.accel;
-
- mutex_unlock(&st->lock);
+ if (ret)
+ return ret;
- return ret;
+ return st->fifo.nb.accel;
}
static int inv_icm42600_accel_read_event_config(struct iio_dev *indio_dev,
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
index 7c4ed981db04..ada968be954d 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/kernel.h>
#include <linux/device.h>
+#include <linux/minmax.h>
#include <linux/mutex.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
@@ -100,7 +101,7 @@ ssize_t inv_icm42600_fifo_decode_packet(const void *packet, const void **accel,
void inv_icm42600_buffer_update_fifo_period(struct inv_icm42600_state *st)
{
- u32 period_gyro, period_accel, period;
+ u32 period_gyro, period_accel;
if (st->fifo.en & INV_ICM42600_SENSOR_GYRO)
period_gyro = inv_icm42600_odr_to_period(st->conf.gyro.odr);
@@ -112,12 +113,7 @@ void inv_icm42600_buffer_update_fifo_period(struct inv_icm42600_state *st)
else
period_accel = U32_MAX;
- if (period_gyro <= period_accel)
- period = period_gyro;
- else
- period = period_accel;
-
- st->fifo.period = period;
+ st->fifo.period = min(period_gyro, period_accel);
}
int inv_icm42600_buffer_set_fifo_en(struct inv_icm42600_state *st,
@@ -204,7 +200,7 @@ 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, period;
+ u32 period_gyro, period_accel;
u32 latency_gyro, latency_accel, latency;
bool restore;
__le16 raw_wm;
@@ -237,13 +233,8 @@ int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st)
latency = latency_gyro - (latency_accel % latency_gyro);
else
latency = latency_accel - (latency_gyro % latency_accel);
- /* use the shortest period */
- if (period_gyro <= period_accel)
- period = period_gyro;
- else
- period = period_accel;
/* all this works because periods are multiple of each others */
- watermark = latency / period;
+ watermark = latency / min(period_gyro, period_accel);
if (watermark < 1)
watermark = 1;
/* update effective watermark */
@@ -292,9 +283,8 @@ static int inv_icm42600_buffer_preenable(struct iio_dev *indio_dev)
pm_runtime_get_sync(dev);
- mutex_lock(&st->lock);
+ guard(mutex)(&st->lock);
inv_sensors_timestamp_reset(ts);
- mutex_unlock(&st->lock);
return 0;
}
@@ -308,43 +298,39 @@ static int inv_icm42600_buffer_postenable(struct iio_dev *indio_dev)
struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
int ret;
- mutex_lock(&st->lock);
+ guard(mutex)(&st->lock);
- /* exit if FIFO is already on */
if (st->fifo.on) {
- ret = 0;
- goto out_on;
+ st->fifo.on++;
+ return 0;
}
/* set FIFO threshold interrupt */
ret = regmap_set_bits(st->map, INV_ICM42600_REG_INT_SOURCE0,
INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN);
if (ret)
- goto out_unlock;
+ return ret;
/* flush FIFO data */
ret = regmap_write(st->map, INV_ICM42600_REG_SIGNAL_PATH_RESET,
INV_ICM42600_SIGNAL_PATH_RESET_FIFO_FLUSH);
if (ret)
- goto out_unlock;
+ return ret;
/* set FIFO in streaming mode */
ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG,
INV_ICM42600_FIFO_CONFIG_STREAM);
if (ret)
- goto out_unlock;
+ return ret;
/* workaround: first read of FIFO count after reset is always 0 */
ret = regmap_bulk_read(st->map, INV_ICM42600_REG_FIFO_COUNT, st->buffer, 2);
if (ret)
- goto out_unlock;
+ return ret;
-out_on:
- /* increase FIFO on counter */
st->fifo.on++;
-out_unlock:
- mutex_unlock(&st->lock);
- return ret;
+
+ return 0;
}
static int inv_icm42600_buffer_predisable(struct iio_dev *indio_dev)
@@ -352,38 +338,34 @@ static int inv_icm42600_buffer_predisable(struct iio_dev *indio_dev)
struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
int ret;
- mutex_lock(&st->lock);
+ guard(mutex)(&st->lock);
- /* exit if there are several sensors using the FIFO */
if (st->fifo.on > 1) {
- ret = 0;
- goto out_off;
+ st->fifo.on--;
+ return 0;
}
/* set FIFO in bypass mode */
ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG,
INV_ICM42600_FIFO_CONFIG_BYPASS);
if (ret)
- goto out_unlock;
+ return ret;
/* flush FIFO data */
ret = regmap_write(st->map, INV_ICM42600_REG_SIGNAL_PATH_RESET,
INV_ICM42600_SIGNAL_PATH_RESET_FIFO_FLUSH);
if (ret)
- goto out_unlock;
+ return ret;
/* disable FIFO threshold interrupt */
ret = regmap_clear_bits(st->map, INV_ICM42600_REG_INT_SOURCE0,
INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN);
if (ret)
- goto out_unlock;
+ return ret;
-out_off:
- /* decrease FIFO on counter */
st->fifo.on--;
-out_unlock:
- mutex_unlock(&st->lock);
- return ret;
+
+ return 0;
}
static int inv_icm42600_buffer_postdisable(struct iio_dev *indio_dev)
@@ -439,7 +421,6 @@ out_unlock:
if (sleep)
msleep(sleep);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
index a4d42e7e2180..76eb22488e5f 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
@@ -439,18 +439,13 @@ int inv_icm42600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg,
unsigned int writeval, unsigned int *readval)
{
struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
- int ret;
- mutex_lock(&st->lock);
+ guard(mutex)(&st->lock);
if (readval)
- ret = regmap_read(st->map, reg, readval);
- else
- ret = regmap_write(st->map, reg, writeval);
+ return regmap_read(st->map, reg, readval);
- mutex_unlock(&st->lock);
-
- return ret;
+ return regmap_write(st->map, reg, writeval);
}
static int inv_icm42600_set_conf(struct inv_icm42600_state *st,
@@ -697,34 +692,15 @@ static int inv_icm42600_enable_regulator_vddio(struct inv_icm42600_state *st)
return 0;
}
-static void inv_icm42600_disable_vdd_reg(void *_data)
-{
- struct inv_icm42600_state *st = _data;
- const struct device *dev = regmap_get_device(st->map);
- int ret;
-
- ret = regulator_disable(st->vdd_supply);
- if (ret)
- dev_err(dev, "failed to disable vdd error %d\n", ret);
-}
-
static void inv_icm42600_disable_vddio_reg(void *_data)
{
struct inv_icm42600_state *st = _data;
- const struct device *dev = regmap_get_device(st->map);
- int ret;
-
- ret = regulator_disable(st->vddio_supply);
- if (ret)
- dev_err(dev, "failed to disable vddio error %d\n", ret);
-}
+ struct device *dev = regmap_get_device(st->map);
-static void inv_icm42600_disable_pm(void *_data)
-{
- struct device *dev = _data;
+ if (pm_runtime_status_suspended(dev))
+ return;
- pm_runtime_put_sync(dev);
- pm_runtime_disable(dev);
+ regulator_disable(st->vddio_supply);
}
int inv_icm42600_core_probe(struct regmap *regmap, int chip,
@@ -773,23 +749,17 @@ int inv_icm42600_core_probe(struct regmap *regmap, int chip,
return ret;
}
- st->vdd_supply = devm_regulator_get(dev, "vdd");
- if (IS_ERR(st->vdd_supply))
- return PTR_ERR(st->vdd_supply);
+ ret = devm_regulator_get_enable(dev, "vdd");
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get vdd regulator\n");
+
+ msleep(INV_ICM42600_POWER_UP_TIME_MS);
st->vddio_supply = devm_regulator_get(dev, "vddio");
if (IS_ERR(st->vddio_supply))
return PTR_ERR(st->vddio_supply);
- ret = regulator_enable(st->vdd_supply);
- if (ret)
- return ret;
- msleep(INV_ICM42600_POWER_UP_TIME_MS);
-
- ret = devm_add_action_or_reset(dev, inv_icm42600_disable_vdd_reg, st);
- if (ret)
- return ret;
-
ret = inv_icm42600_enable_regulator_vddio(st);
if (ret)
return ret;
@@ -824,16 +794,14 @@ int inv_icm42600_core_probe(struct regmap *regmap, int chip,
return ret;
/* setup runtime power management */
- ret = pm_runtime_set_active(dev);
+ ret = devm_pm_runtime_set_active_enabled(dev);
if (ret)
return ret;
- pm_runtime_get_noresume(dev);
- pm_runtime_enable(dev);
+
pm_runtime_set_autosuspend_delay(dev, INV_ICM42600_SUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(dev);
- pm_runtime_put(dev);
- return devm_add_action_or_reset(dev, inv_icm42600_disable_pm, dev);
+ return ret;
}
EXPORT_SYMBOL_NS_GPL(inv_icm42600_core_probe, "IIO_ICM42600");
@@ -849,22 +817,20 @@ static int inv_icm42600_suspend(struct device *dev)
int accel_conf;
int ret;
- mutex_lock(&st->lock);
+ guard(mutex)(&st->lock);
st->suspended.gyro = st->conf.gyro.mode;
st->suspended.accel = st->conf.accel.mode;
st->suspended.temp = st->conf.temp_en;
- if (pm_runtime_suspended(dev)) {
- ret = 0;
- goto out_unlock;
- }
+ if (pm_runtime_suspended(dev))
+ return 0;
/* disable FIFO data streaming */
if (st->fifo.on) {
ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG,
INV_ICM42600_FIFO_CONFIG_BYPASS);
if (ret)
- goto out_unlock;
+ return ret;
}
/* keep chip on and wake-up capable if APEX and wakeup on */
@@ -880,7 +846,7 @@ static int inv_icm42600_suspend(struct device *dev)
if (st->apex.wom.enable) {
ret = inv_icm42600_disable_wom(st);
if (ret)
- goto out_unlock;
+ return ret;
}
accel_conf = INV_ICM42600_SENSOR_MODE_OFF;
}
@@ -888,15 +854,13 @@ static int inv_icm42600_suspend(struct device *dev)
ret = inv_icm42600_set_pwr_mgmt0(st, INV_ICM42600_SENSOR_MODE_OFF,
accel_conf, false, NULL);
if (ret)
- goto out_unlock;
+ return ret;
/* disable vddio regulator if chip is sleeping */
if (!wakeup)
regulator_disable(st->vddio_supply);
-out_unlock:
- mutex_unlock(&st->lock);
- return ret;
+ return 0;
}
/*
@@ -912,7 +876,10 @@ static int inv_icm42600_resume(struct device *dev)
bool wakeup;
int ret;
- mutex_lock(&st->lock);
+ guard(mutex)(&st->lock);
+
+ if (pm_runtime_suspended(dev))
+ return 0;
/* check wakeup capability */
accel_dev = &st->indio_accel->dev;
@@ -924,25 +891,21 @@ static int inv_icm42600_resume(struct device *dev)
} else {
ret = inv_icm42600_enable_regulator_vddio(st);
if (ret)
- goto out_unlock;
+ return ret;
}
- pm_runtime_disable(dev);
- pm_runtime_set_active(dev);
- pm_runtime_enable(dev);
-
/* restore sensors state */
ret = inv_icm42600_set_pwr_mgmt0(st, st->suspended.gyro,
st->suspended.accel,
st->suspended.temp, NULL);
if (ret)
- goto out_unlock;
+ return ret;
/* restore APEX features if disabled */
if (!wakeup && st->apex.wom.enable) {
ret = inv_icm42600_enable_wom(st);
if (ret)
- goto out_unlock;
+ return ret;
}
/* restore FIFO data streaming */
@@ -953,9 +916,7 @@ static int inv_icm42600_resume(struct device *dev)
INV_ICM42600_FIFO_CONFIG_STREAM);
}
-out_unlock:
- mutex_unlock(&st->lock);
- return ret;
+ return 0;
}
/* Runtime suspend will turn off sensors that are enabled by iio devices. */
@@ -964,34 +925,28 @@ static int inv_icm42600_runtime_suspend(struct device *dev)
struct inv_icm42600_state *st = dev_get_drvdata(dev);
int ret;
- mutex_lock(&st->lock);
+ guard(mutex)(&st->lock);
/* disable all sensors */
ret = inv_icm42600_set_pwr_mgmt0(st, INV_ICM42600_SENSOR_MODE_OFF,
INV_ICM42600_SENSOR_MODE_OFF, false,
NULL);
if (ret)
- goto error_unlock;
+ return ret;
regulator_disable(st->vddio_supply);
-error_unlock:
- mutex_unlock(&st->lock);
- return ret;
+ return 0;
}
/* Sensors are enabled by iio devices, no need to turn them back on here. */
static int inv_icm42600_runtime_resume(struct device *dev)
{
struct inv_icm42600_state *st = dev_get_drvdata(dev);
- int ret;
-
- mutex_lock(&st->lock);
- ret = inv_icm42600_enable_regulator_vddio(st);
+ guard(mutex)(&st->lock);
- mutex_unlock(&st->lock);
- return ret;
+ return inv_icm42600_enable_regulator_vddio(st);
}
EXPORT_NS_GPL_DEV_PM_OPS(inv_icm42600_pm_ops, IIO_ICM42600) = {
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
index 9ba6f13628e6..7ef0a25ec74f 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
@@ -184,7 +184,6 @@ static int inv_icm42600_gyro_read_sensor(struct inv_icm42600_state *st,
ret = -EINVAL;
exit:
mutex_unlock(&st->lock);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
@@ -278,12 +277,10 @@ static int inv_icm42600_gyro_write_scale(struct iio_dev *indio_dev,
conf.fs = idx / 2;
pm_runtime_get_sync(dev);
- mutex_lock(&st->lock);
- ret = inv_icm42600_set_gyro_conf(st, &conf, NULL);
+ scoped_guard(mutex, &st->lock)
+ ret = inv_icm42600_set_gyro_conf(st, &conf, NULL);
- mutex_unlock(&st->lock);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
@@ -378,7 +375,6 @@ static int inv_icm42600_gyro_write_odr(struct iio_dev *indio_dev,
out_unlock:
mutex_unlock(&st->lock);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
@@ -430,7 +426,6 @@ static int inv_icm42600_gyro_read_offset(struct inv_icm42600_state *st,
memcpy(data, st->buffer, sizeof(data));
mutex_unlock(&st->lock);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
if (ret)
return ret;
@@ -567,7 +562,6 @@ static int inv_icm42600_gyro_write_offset(struct inv_icm42600_state *st,
out_unlock:
mutex_unlock(&st->lock);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
@@ -693,16 +687,11 @@ static int inv_icm42600_gyro_hwfifo_set_watermark(struct iio_dev *indio_dev,
unsigned int val)
{
struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
- int ret;
- mutex_lock(&st->lock);
+ guard(mutex)(&st->lock);
st->fifo.watermark.gyro = val;
- ret = inv_icm42600_buffer_update_watermark(st);
-
- mutex_unlock(&st->lock);
-
- return ret;
+ return inv_icm42600_buffer_update_watermark(st);
}
static int inv_icm42600_gyro_hwfifo_flush(struct iio_dev *indio_dev,
@@ -714,15 +703,13 @@ static int inv_icm42600_gyro_hwfifo_flush(struct iio_dev *indio_dev,
if (count == 0)
return 0;
- mutex_lock(&st->lock);
+ guard(mutex)(&st->lock);
ret = inv_icm42600_buffer_hwfifo_flush(st, count);
- if (!ret)
- ret = st->fifo.nb.gyro;
-
- mutex_unlock(&st->lock);
+ if (ret)
+ return ret;
- return ret;
+ return st->fifo.nb.gyro;
}
static const struct iio_info inv_icm42600_gyro_info = {
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c
index 271a4788604a..30f6a9595eea 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c
@@ -41,7 +41,6 @@ static int inv_icm42600_temp_read(struct inv_icm42600_state *st, s16 *temp)
exit:
mutex_unlock(&st->lock);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
index 39eb516acc73..b2fa1f4957a5 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
@@ -735,7 +735,6 @@ static int inv_mpu6050_read_channel_data(struct iio_dev *indio_dev,
break;
}
- pm_runtime_mark_last_busy(pdev);
pm_runtime_put_autosuspend(pdev);
return ret;
@@ -938,7 +937,6 @@ static int inv_mpu6050_write_raw(struct iio_dev *indio_dev,
break;
}
- pm_runtime_mark_last_busy(pdev);
pm_runtime_put_autosuspend(pdev);
error_write_raw_unlock:
mutex_unlock(&st->lock);
@@ -1146,14 +1144,12 @@ static int inv_mpu6050_enable_wom(struct inv_mpu6050_state *st, bool en)
st->chip_config.wom_en = false;
}
- pm_runtime_mark_last_busy(pdev);
pm_runtime_put_autosuspend(pdev);
}
return result;
error_suspend:
- pm_runtime_mark_last_busy(pdev);
pm_runtime_put_autosuspend(pdev);
return result;
}
@@ -1249,7 +1245,6 @@ static int inv_mpu6050_write_event_value(struct iio_dev *indio_dev,
value = (u64)val * 1000000ULL + (u64)val2;
result = inv_mpu6050_set_wom_threshold(st, value, INV_MPU6050_FREQ_DIVIDER(st));
- pm_runtime_mark_last_busy(pdev);
pm_runtime_put_autosuspend(pdev);
return result;
@@ -1357,7 +1352,6 @@ inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr,
if (result)
goto fifo_rate_fail_power_off;
- pm_runtime_mark_last_busy(pdev);
fifo_rate_fail_power_off:
pm_runtime_put_autosuspend(pdev);
fifo_rate_fail_unlock:
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c
index 5b1088cc3704..10a473342075 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c
@@ -194,7 +194,6 @@ static int inv_mpu6050_set_enable(struct iio_dev *indio_dev, bool enable)
result = inv_mpu6050_prepare_fifo(st, false);
if (result)
goto error_power_off;
- pm_runtime_mark_last_busy(pdev);
pm_runtime_put_autosuspend(pdev);
}
diff --git a/drivers/iio/imu/kmx61.c b/drivers/iio/imu/kmx61.c
index 55c82891e08c..3cd91d8a89ee 100644
--- a/drivers/iio/imu/kmx61.c
+++ b/drivers/iio/imu/kmx61.c
@@ -747,12 +747,10 @@ static int kmx61_set_power_state(struct kmx61_data *data, bool on, u8 device)
data->mag_ps = on;
}
- if (on) {
+ if (on)
ret = pm_runtime_resume_and_get(&data->client->dev);
- } else {
- pm_runtime_mark_last_busy(&data->client->dev);
+ else
ret = pm_runtime_put_autosuspend(&data->client->dev);
- }
if (ret < 0) {
dev_err(&data->client->dev,
"Failed: kmx61_set_power_state for %d, ret %d\n",
diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
index c65ad49829e7..d8cb4b0218d5 100644
--- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
+++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
@@ -2035,10 +2035,10 @@ st_lsm6dsx_sysfs_sampling_frequency_avail(struct device *dev,
odr_table = &sensor->hw->settings->odr_table[sensor->id];
for (i = 0; i < odr_table->odr_len; i++)
- len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%03d ",
- odr_table->odr_avl[i].milli_hz / 1000,
- odr_table->odr_avl[i].milli_hz % 1000);
- buf[len - 1] = '\n';
+ len += sysfs_emit_at(buf, len, "%d.%03d%c",
+ odr_table->odr_avl[i].milli_hz / 1000,
+ odr_table->odr_avl[i].milli_hz % 1000,
+ (i == odr_table->odr_len - 1) ? '\n' : ' ');
return len;
}
@@ -2054,9 +2054,9 @@ static ssize_t st_lsm6dsx_sysfs_scale_avail(struct device *dev,
fs_table = &hw->settings->fs_table[sensor->id];
for (i = 0; i < fs_table->fs_len; i++)
- len += scnprintf(buf + len, PAGE_SIZE - len, "0.%09u ",
- fs_table->fs_avl[i].gain);
- buf[len - 1] = '\n';
+ len += sysfs_emit_at(buf, len, "0.%09u%c",
+ fs_table->fs_avl[i].gain,
+ (i == fs_table->fs_len - 1) ? '\n' : ' ');
return len;
}
diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index 159d6c5ca3ce..88c3d585a1bd 100644
--- a/drivers/iio/industrialio-core.c
+++ b/drivers/iio/industrialio-core.c
@@ -97,6 +97,7 @@ static const char * const iio_chan_type_name_spec[] = {
[IIO_COLORTEMP] = "colortemp",
[IIO_CHROMATICITY] = "chromaticity",
[IIO_ATTENTION] = "attention",
+ [IIO_ALTCURRENT] = "altcurrent",
};
static const char * const iio_modifier_names[] = {
@@ -152,6 +153,10 @@ static const char * const iio_modifier_names[] = {
[IIO_MOD_PITCH] = "pitch",
[IIO_MOD_YAW] = "yaw",
[IIO_MOD_ROLL] = "roll",
+ [IIO_MOD_RMS] = "rms",
+ [IIO_MOD_ACTIVE] = "active",
+ [IIO_MOD_REACTIVE] = "reactive",
+ [IIO_MOD_APPARENT] = "apparent",
};
/* relies on pairs of these shared then separate */
@@ -189,6 +194,7 @@ static const char * const iio_chan_info_postfix[] = {
[IIO_CHAN_INFO_ZEROPOINT] = "zeropoint",
[IIO_CHAN_INFO_TROUGH] = "trough_raw",
[IIO_CHAN_INFO_CONVDELAY] = "convdelay",
+ [IIO_CHAN_INFO_POWERFACTOR] = "powerfactor",
};
/**
* iio_device_id() - query the unique ID for the device
@@ -790,6 +796,7 @@ static ssize_t iio_format_list(char *buf, const int *vals, int type, int length,
switch (type) {
case IIO_VAL_INT:
+ case IIO_VAL_CHAR:
stride = 1;
break;
default:
@@ -1243,7 +1250,7 @@ static int iio_device_add_channel_label(struct iio_dev *indio_dev,
static int iio_device_add_info_mask_type(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
enum iio_shared_by shared_by,
- const long *infomask)
+ const unsigned long *infomask)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
int i, ret, attrcount = 0;
@@ -1273,7 +1280,7 @@ static int iio_device_add_info_mask_type(struct iio_dev *indio_dev,
static int iio_device_add_info_mask_type_avail(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
enum iio_shared_by shared_by,
- const long *infomask)
+ const unsigned long *infomask)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
int i, ret, attrcount = 0;
diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
index c174ebb7d5e6..1e5eb5a41271 100644
--- a/drivers/iio/inkern.c
+++ b/drivers/iio/inkern.c
@@ -11,6 +11,7 @@
#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/slab.h>
+#include <linux/units.h>
#include <linux/iio/iio.h>
#include <linux/iio/iio-opaque.h>
@@ -598,6 +599,42 @@ int iio_read_channel_average_raw(struct iio_channel *chan, int *val)
}
EXPORT_SYMBOL_GPL(iio_read_channel_average_raw);
+int iio_multiply_value(int *result, s64 multiplier,
+ unsigned int type, int val, int val2)
+{
+ s64 denominator;
+
+ switch (type) {
+ case IIO_VAL_INT:
+ *result = multiplier * val;
+ return IIO_VAL_INT;
+ case IIO_VAL_INT_PLUS_MICRO:
+ case IIO_VAL_INT_PLUS_NANO:
+ switch (type) {
+ case IIO_VAL_INT_PLUS_MICRO:
+ denominator = MICRO;
+ break;
+ case IIO_VAL_INT_PLUS_NANO:
+ denominator = NANO;
+ break;
+ }
+ *result = multiplier * abs(val);
+ *result += div_s64(multiplier * abs(val2), denominator);
+ if (val < 0 || val2 < 0)
+ *result *= -1;
+ return IIO_VAL_INT;
+ case IIO_VAL_FRACTIONAL:
+ *result = div_s64(multiplier * val, val2);
+ return IIO_VAL_INT;
+ case IIO_VAL_FRACTIONAL_LOG2:
+ *result = (multiplier * val) >> val2;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+EXPORT_SYMBOL_NS_GPL(iio_multiply_value, "IIO_UNIT_TEST");
+
static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan,
int raw, int *processed,
unsigned int scale)
@@ -605,6 +642,7 @@ static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan,
int scale_type, scale_val, scale_val2;
int offset_type, offset_val, offset_val2;
s64 raw64 = raw;
+ int ret;
offset_type = iio_channel_read(chan, &offset_val, &offset_val2,
IIO_CHAN_INFO_OFFSET);
@@ -639,40 +677,14 @@ static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan,
* If no channel scaling is available apply consumer scale to
* raw value and return.
*/
- *processed = raw * scale;
+ *processed = raw64 * scale;
return 0;
}
- switch (scale_type) {
- case IIO_VAL_INT:
- *processed = raw64 * scale_val * scale;
- break;
- case IIO_VAL_INT_PLUS_MICRO:
- if (scale_val2 < 0)
- *processed = -raw64 * scale_val * scale;
- else
- *processed = raw64 * scale_val * scale;
- *processed += div_s64(raw64 * (s64)scale_val2 * scale,
- 1000000LL);
- break;
- case IIO_VAL_INT_PLUS_NANO:
- if (scale_val2 < 0)
- *processed = -raw64 * scale_val * scale;
- else
- *processed = raw64 * scale_val * scale;
- *processed += div_s64(raw64 * (s64)scale_val2 * scale,
- 1000000000LL);
- break;
- case IIO_VAL_FRACTIONAL:
- *processed = div_s64(raw64 * (s64)scale_val * scale,
- scale_val2);
- break;
- case IIO_VAL_FRACTIONAL_LOG2:
- *processed = (raw64 * (s64)scale_val * scale) >> scale_val2;
- break;
- default:
- return -EINVAL;
- }
+ ret = iio_multiply_value(processed, raw64 * scale,
+ scale_type, scale_val, scale_val2);
+ if (ret < 0)
+ return ret;
return 0;
}
@@ -714,20 +726,19 @@ int iio_read_channel_processed_scale(struct iio_channel *chan, int *val,
unsigned int scale)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
- int ret;
+ int ret, pval, pval2;
guard(mutex)(&iio_dev_opaque->info_exist_lock);
if (!chan->indio_dev->info)
return -ENODEV;
if (iio_channel_has_info(chan->channel, IIO_CHAN_INFO_PROCESSED)) {
- ret = iio_channel_read(chan, val, NULL,
+ ret = iio_channel_read(chan, &pval, &pval2,
IIO_CHAN_INFO_PROCESSED);
if (ret < 0)
return ret;
- *val *= scale;
- return ret;
+ return iio_multiply_value(val, scale, ret, pval, pval2);
} else {
ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW);
if (ret < 0)
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index 4a7d983c9cd4..ac1408d374c9 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -724,6 +724,19 @@ config VEML6040
To compile this driver as a module, choose M here: the
module will be called veml6040.
+config VEML6046X00
+ tristate "VEML6046X00 RGBIR color sensor"
+ select REGMAP_I2C
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ depends on I2C
+ help
+ Say Y here if you want to build a driver for the Vishay VEML6046X00
+ high accuracy RGBIR color sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called veml6046x00.
+
config VEML6070
tristate "VEML6070 UV A light sensor"
depends on I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index 8229ebe6edc4..c0048e0d5ca8 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_VCNL4035) += vcnl4035.o
obj-$(CONFIG_VEML3235) += veml3235.o
obj-$(CONFIG_VEML6030) += veml6030.o
obj-$(CONFIG_VEML6040) += veml6040.o
+obj-$(CONFIG_VEML6046X00) += veml6046x00.o
obj-$(CONFIG_VEML6070) += veml6070.o
obj-$(CONFIG_VEML6075) += veml6075.o
obj-$(CONFIG_VL6180) += vl6180.o
diff --git a/drivers/iio/light/acpi-als.c b/drivers/iio/light/acpi-als.c
index 032e6cae8b80..d5d1a8b9c035 100644
--- a/drivers/iio/light/acpi-als.c
+++ b/drivers/iio/light/acpi-als.c
@@ -49,20 +49,10 @@ static const struct iio_chan_spec acpi_als_channels[] = {
IIO_CHAN_SOFT_TIMESTAMP(1),
};
-/*
- * The event buffer contains timestamp and all the data from
- * the ACPI0008 block. There are multiple, but so far we only
- * support _ALI (illuminance): One channel, padding and timestamp.
- */
-#define ACPI_ALS_EVT_BUFFER_SIZE \
- (sizeof(s32) + sizeof(s32) + sizeof(s64))
-
struct acpi_als {
struct acpi_device *device;
struct mutex lock;
struct iio_trigger *trig;
-
- s32 evt_buffer[ACPI_ALS_EVT_BUFFER_SIZE / sizeof(s32)] __aligned(8);
};
/*
@@ -152,7 +142,10 @@ static irqreturn_t acpi_als_trigger_handler(int irq, void *p)
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct acpi_als *als = iio_priv(indio_dev);
- s32 *buffer = als->evt_buffer;
+ struct {
+ s32 light;
+ aligned_s64 ts;
+ } scan = { };
s32 val;
int ret;
@@ -161,7 +154,7 @@ static irqreturn_t acpi_als_trigger_handler(int irq, void *p)
ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &val);
if (ret < 0)
goto out;
- *buffer = val;
+ scan.light = val;
/*
* When coming from own trigger via polls, set polling function
@@ -174,7 +167,7 @@ static irqreturn_t acpi_als_trigger_handler(int irq, void *p)
if (!pf->timestamp)
pf->timestamp = iio_get_time_ns(indio_dev);
- iio_push_to_buffers_with_timestamp(indio_dev, buffer, pf->timestamp);
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp);
out:
mutex_unlock(&als->lock);
iio_trigger_notify_done(indio_dev->trig);
diff --git a/drivers/iio/light/adjd_s311.c b/drivers/iio/light/adjd_s311.c
index cf96e3dd8bc6..edb3d9dc8bed 100644
--- a/drivers/iio/light/adjd_s311.c
+++ b/drivers/iio/light/adjd_s311.c
@@ -54,10 +54,6 @@
struct adjd_s311_data {
struct i2c_client *client;
- struct {
- s16 chans[4];
- aligned_s64 ts;
- } scan;
};
enum adjd_s311_channel_idx {
@@ -120,6 +116,10 @@ static irqreturn_t adjd_s311_trigger_handler(int irq, void *p)
struct adjd_s311_data *data = iio_priv(indio_dev);
s64 time_ns = iio_get_time_ns(indio_dev);
int i, j = 0;
+ struct {
+ s16 chans[4];
+ aligned_s64 ts;
+ } scan = { };
int ret = adjd_s311_req_data(indio_dev);
if (ret < 0)
@@ -131,10 +131,10 @@ static irqreturn_t adjd_s311_trigger_handler(int irq, void *p)
if (ret < 0)
goto done;
- data->scan.chans[j++] = ret & ADJD_S311_DATA_MASK;
+ scan.chans[j++] = ret & ADJD_S311_DATA_MASK;
}
- iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, time_ns);
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns);
done:
iio_trigger_notify_done(indio_dev->trig);
diff --git a/drivers/iio/light/al3000a.c b/drivers/iio/light/al3000a.c
index 6f301c067045..9871096cbab3 100644
--- a/drivers/iio/light/al3000a.c
+++ b/drivers/iio/light/al3000a.c
@@ -94,7 +94,7 @@ static int al3000a_init(struct al3000a_data *data)
ret = devm_add_action_or_reset(dev, al3000a_set_pwr_off, data);
if (ret)
- return dev_err_probe(dev, ret, "failed to add action\n");
+ return ret;
ret = regmap_write(data->regmap, AL3000A_REG_SYSTEM, AL3000A_CONFIG_RESET);
if (ret)
diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c
index f676da245aa7..389125675caa 100644
--- a/drivers/iio/light/apds9306.c
+++ b/drivers/iio/light/apds9306.c
@@ -537,7 +537,6 @@ static int apds9306_read_data(struct apds9306_data *data, int *val, int reg)
*val = get_unaligned_le24(&buff);
- pm_runtime_mark_last_busy(data->dev);
pm_runtime_put_autosuspend(data->dev);
return 0;
@@ -1121,7 +1120,6 @@ static int apds9306_write_event_config(struct iio_dev *indio_dev,
if (ret)
return ret;
- pm_runtime_mark_last_busy(data->dev);
pm_runtime_put_autosuspend(data->dev);
return 0;
@@ -1309,7 +1307,7 @@ static int apds9306_probe(struct i2c_client *client)
ret = devm_add_action_or_reset(dev, apds9306_powerdown, data);
if (ret)
- return dev_err_probe(dev, ret, "failed to add action or reset\n");
+ return ret;
ret = devm_iio_device_register(dev, indio_dev);
if (ret)
diff --git a/drivers/iio/light/apds9960.c b/drivers/iio/light/apds9960.c
index b92d0fce5aec..79b202c59a0f 100644
--- a/drivers/iio/light/apds9960.c
+++ b/drivers/iio/light/apds9960.c
@@ -495,7 +495,6 @@ static int apds9960_set_power_state(struct apds9960_data *data, bool on)
usleep_range(data->als_adc_int_us,
APDS9960_MAX_INT_TIME_IN_US);
} else {
- pm_runtime_mark_last_busy(dev);
ret = pm_runtime_put_autosuspend(dev);
}
diff --git a/drivers/iio/light/bh1745.c b/drivers/iio/light/bh1745.c
index 4e9bd8f831f7..10b00344bbed 100644
--- a/drivers/iio/light/bh1745.c
+++ b/drivers/iio/light/bh1745.c
@@ -755,8 +755,8 @@ static irqreturn_t bh1745_trigger_handler(int interrupt, void *p)
scan.chans[j++] = value;
}
- iio_push_to_buffers_with_timestamp(indio_dev, &scan,
- iio_get_time_ns(indio_dev));
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
+ iio_get_time_ns(indio_dev));
err:
iio_trigger_notify_done(indio_dev->trig);
@@ -814,8 +814,7 @@ static int bh1745_init(struct bh1745_data *data)
ret = devm_add_action_or_reset(dev, bh1745_power_off, data);
if (ret)
- return dev_err_probe(dev, ret,
- "Failed to add action or reset\n");
+ return ret;
return 0;
}
diff --git a/drivers/iio/light/bh1780.c b/drivers/iio/light/bh1780.c
index c7c877d2fe67..5d3c6d5276ba 100644
--- a/drivers/iio/light/bh1780.c
+++ b/drivers/iio/light/bh1780.c
@@ -111,7 +111,6 @@ static int bh1780_read_raw(struct iio_dev *indio_dev,
value = bh1780_read_word(bh1780, BH1780_REG_DLOW);
if (value < 0)
return value;
- pm_runtime_mark_last_busy(&bh1780->client->dev);
pm_runtime_put_autosuspend(&bh1780->client->dev);
*val = value;
diff --git a/drivers/iio/light/gp2ap002.c b/drivers/iio/light/gp2ap002.c
index 42859e5b1089..a0d8a58f2704 100644
--- a/drivers/iio/light/gp2ap002.c
+++ b/drivers/iio/light/gp2ap002.c
@@ -271,7 +271,6 @@ static int gp2ap002_read_raw(struct iio_dev *indio_dev,
}
out:
- pm_runtime_mark_last_busy(gp2ap002->dev);
pm_runtime_put_autosuspend(gp2ap002->dev);
return ret;
@@ -353,7 +352,6 @@ static int gp2ap002_write_event_config(struct iio_dev *indio_dev,
pm_runtime_get_sync(gp2ap002->dev);
gp2ap002->enabled = true;
} else {
- pm_runtime_mark_last_busy(gp2ap002->dev);
pm_runtime_put_autosuspend(gp2ap002->dev);
gp2ap002->enabled = false;
}
diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c
index 830e5ae7f34a..384572844162 100644
--- a/drivers/iio/light/hid-sensor-als.c
+++ b/drivers/iio/light/hid-sensor-als.c
@@ -262,8 +262,9 @@ static int als_proc_event(struct hid_sensor_hub_device *hsdev,
if (!als_state->timestamp)
als_state->timestamp = iio_get_time_ns(indio_dev);
- iio_push_to_buffers_with_timestamp(indio_dev, &als_state->scan,
- als_state->timestamp);
+ iio_push_to_buffers_with_ts(indio_dev, &als_state->scan,
+ sizeof(als_state->scan),
+ als_state->timestamp);
als_state->timestamp = 0;
}
diff --git a/drivers/iio/light/isl29028.c b/drivers/iio/light/isl29028.c
index 0e4284823d44..374bccad9119 100644
--- a/drivers/iio/light/isl29028.c
+++ b/drivers/iio/light/isl29028.c
@@ -336,16 +336,11 @@ static int isl29028_ir_get(struct isl29028_chip *chip, int *ir_data)
static int isl29028_set_pm_runtime_busy(struct isl29028_chip *chip, bool on)
{
struct device *dev = regmap_get_device(chip->regmap);
- int ret;
- if (on) {
- ret = pm_runtime_resume_and_get(dev);
- } else {
- pm_runtime_mark_last_busy(dev);
- ret = pm_runtime_put_autosuspend(dev);
- }
+ if (on)
+ return pm_runtime_resume_and_get(dev);
- return ret;
+ return pm_runtime_put_autosuspend(dev);
}
/* Channel IO */
diff --git a/drivers/iio/light/isl29125.c b/drivers/iio/light/isl29125.c
index 6bc23b164cc5..3acb8a4f1d12 100644
--- a/drivers/iio/light/isl29125.c
+++ b/drivers/iio/light/isl29125.c
@@ -51,11 +51,6 @@
struct isl29125_data {
struct i2c_client *client;
u8 conf1;
- /* Ensure timestamp is naturally aligned */
- struct {
- u16 chans[3];
- aligned_s64 timestamp;
- } scan;
};
#define ISL29125_CHANNEL(_color, _si) { \
@@ -179,6 +174,11 @@ static irqreturn_t isl29125_trigger_handler(int irq, void *p)
struct iio_dev *indio_dev = pf->indio_dev;
struct isl29125_data *data = iio_priv(indio_dev);
int i, j = 0;
+ /* Ensure timestamp is naturally aligned */
+ struct {
+ u16 chans[3];
+ aligned_s64 timestamp;
+ } scan = { };
iio_for_each_active_channel(indio_dev, i) {
int ret = i2c_smbus_read_word_data(data->client,
@@ -186,10 +186,10 @@ static irqreturn_t isl29125_trigger_handler(int irq, void *p)
if (ret < 0)
goto done;
- data->scan.chans[j++] = ret;
+ scan.chans[j++] = ret;
}
- iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
iio_get_time_ns(indio_dev));
done:
diff --git a/drivers/iio/light/ltr390.c b/drivers/iio/light/ltr390.c
index ee59bbb8aa09..a2b804e9089a 100644
--- a/drivers/iio/light/ltr390.c
+++ b/drivers/iio/light/ltr390.c
@@ -26,6 +26,7 @@
#include <linux/math.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/iio/iio.h>
@@ -38,12 +39,21 @@
#define LTR390_ALS_UVS_GAIN 0x05
#define LTR390_PART_ID 0x06
#define LTR390_MAIN_STATUS 0x07
+
#define LTR390_ALS_DATA 0x0D
+#define LTR390_ALS_DATA_BYTE(n) (LTR390_ALS_DATA + (n))
+
#define LTR390_UVS_DATA 0x10
+#define LTR390_UVS_DATA_BYTE(n) (LTR390_UVS_DATA + (n))
+
#define LTR390_INT_CFG 0x19
#define LTR390_INT_PST 0x1A
+
#define LTR390_THRESH_UP 0x21
+#define LTR390_THRESH_UP_BYTE(n) (LTR390_THRESH_UP + (n))
+
#define LTR390_THRESH_LOW 0x24
+#define LTR390_THRESH_LOW_BYTE(n) (LTR390_THRESH_LOW + (n))
#define LTR390_PART_NUMBER_ID 0xb
#define LTR390_ALS_UVS_GAIN_MASK GENMASK(2, 0)
@@ -96,6 +106,32 @@ struct ltr390_data {
enum ltr390_mode mode;
int gain;
int int_time_us;
+ bool irq_enabled;
+};
+
+static const struct regmap_range ltr390_readable_reg_ranges[] = {
+ regmap_reg_range(LTR390_MAIN_CTRL, LTR390_MAIN_CTRL),
+ regmap_reg_range(LTR390_ALS_UVS_MEAS_RATE, LTR390_MAIN_STATUS),
+ regmap_reg_range(LTR390_ALS_DATA_BYTE(0), LTR390_UVS_DATA_BYTE(2)),
+ regmap_reg_range(LTR390_INT_CFG, LTR390_INT_PST),
+ regmap_reg_range(LTR390_THRESH_UP_BYTE(0), LTR390_THRESH_LOW_BYTE(2)),
+};
+
+static const struct regmap_access_table ltr390_readable_reg_table = {
+ .yes_ranges = ltr390_readable_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(ltr390_readable_reg_ranges),
+};
+
+static const struct regmap_range ltr390_writeable_reg_ranges[] = {
+ regmap_reg_range(LTR390_MAIN_CTRL, LTR390_MAIN_CTRL),
+ regmap_reg_range(LTR390_ALS_UVS_MEAS_RATE, LTR390_ALS_UVS_GAIN),
+ regmap_reg_range(LTR390_INT_CFG, LTR390_INT_PST),
+ regmap_reg_range(LTR390_THRESH_UP_BYTE(0), LTR390_THRESH_LOW_BYTE(2)),
+};
+
+static const struct regmap_access_table ltr390_writeable_reg_table = {
+ .yes_ranges = ltr390_writeable_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(ltr390_writeable_reg_ranges),
};
static const struct regmap_config ltr390_regmap_config = {
@@ -103,6 +139,9 @@ static const struct regmap_config ltr390_regmap_config = {
.reg_bits = 8,
.reg_stride = 1,
.val_bits = 8,
+ .max_register = LTR390_THRESH_LOW_BYTE(2),
+ .rd_table = &ltr390_readable_reg_table,
+ .wr_table = &ltr390_writeable_reg_table,
};
/* Sampling frequency is in mili Hz and mili Seconds */
@@ -178,9 +217,10 @@ static int ltr390_get_samp_freq_or_period(struct ltr390_data *data,
return ltr390_samp_freq_table[value][option];
}
-static int ltr390_read_raw(struct iio_dev *iio_device,
- struct iio_chan_spec const *chan, int *val,
- int *val2, long mask)
+
+static int ltr390_do_read_raw(struct iio_dev *iio_device,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
{
int ret;
struct ltr390_data *data = iio_priv(iio_device);
@@ -243,6 +283,27 @@ static int ltr390_read_raw(struct iio_dev *iio_device,
}
}
+static int ltr390_read_raw(struct iio_dev *iio_device,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ int ret;
+ struct ltr390_data *data = iio_priv(iio_device);
+ struct device *dev = &data->client->dev;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0) {
+ dev_err(dev, "runtime PM failed to resume: %d\n", ret);
+ return ret;
+ }
+
+ ret = ltr390_do_read_raw(iio_device, chan, val, val2, mask);
+
+ pm_runtime_put_autosuspend(dev);
+
+ return ret;
+}
+
/* integration time in us */
static const int ltr390_int_time_map_us[] = { 400000, 200000, 100000, 50000, 25000, 12500 };
static const int ltr390_gain_map[] = { 1, 3, 6, 9, 18 };
@@ -549,11 +610,11 @@ static int ltr390_read_event_config(struct iio_dev *indio_dev,
return FIELD_GET(LTR390_LS_INT_EN, status);
}
-static int ltr390_write_event_config(struct iio_dev *indio_dev,
- const struct iio_chan_spec *chan,
- enum iio_event_type type,
- enum iio_event_direction dir,
- bool state)
+static int ltr390_do_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
{
struct ltr390_data *data = iio_priv(indio_dev);
int ret;
@@ -561,7 +622,6 @@ static int ltr390_write_event_config(struct iio_dev *indio_dev,
if (!state)
return regmap_clear_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_EN);
- guard(mutex)(&data->lock);
ret = regmap_set_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_EN);
if (ret < 0)
return ret;
@@ -586,6 +646,51 @@ static int ltr390_write_event_config(struct iio_dev *indio_dev,
}
}
+static int ltr390_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ int ret;
+ struct ltr390_data *data = iio_priv(indio_dev);
+ struct device *dev = &data->client->dev;
+
+ guard(mutex)(&data->lock);
+
+ if (state && !data->irq_enabled) {
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0) {
+ dev_err(dev, "runtime PM failed to resume: %d\n", ret);
+ return ret;
+ }
+ data->irq_enabled = true;
+ }
+
+ ret = ltr390_do_event_config(indio_dev, chan, type, dir, state);
+
+ if (!state && data->irq_enabled) {
+ data->irq_enabled = false;
+ pm_runtime_put_autosuspend(dev);
+ }
+
+ return ret;
+}
+
+static int ltr390_debugfs_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg, unsigned int writeval,
+ unsigned int *readval)
+{
+ struct ltr390_data *data = iio_priv(indio_dev);
+
+ guard(mutex)(&data->lock);
+
+ if (readval)
+ return regmap_read(data->regmap, reg, readval);
+
+ return regmap_write(data->regmap, reg, writeval);
+}
+
static const struct iio_info ltr390_info = {
.read_raw = ltr390_read_raw,
.write_raw = ltr390_write_raw,
@@ -594,6 +699,7 @@ static const struct iio_info ltr390_info = {
.read_event_config = ltr390_read_event_config,
.write_event_value = ltr390_write_event_value,
.write_event_config = ltr390_write_event_config,
+ .debugfs_reg_access = ltr390_debugfs_reg_access,
};
static irqreturn_t ltr390_interrupt_handler(int irq, void *private)
@@ -628,6 +734,43 @@ static irqreturn_t ltr390_interrupt_handler(int irq, void *private)
return IRQ_HANDLED;
}
+static void ltr390_powerdown(void *priv)
+{
+ struct ltr390_data *data = priv;
+ struct device *dev = &data->client->dev;
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ /* Ensure that power off and interrupts are disabled */
+ if (data->irq_enabled) {
+ ret = regmap_clear_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_EN);
+ if (ret < 0)
+ dev_err(dev, "failed to disable interrupts\n");
+
+ data->irq_enabled = false;
+ pm_runtime_put_autosuspend(dev);
+ }
+
+ ret = regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE);
+ if (ret < 0)
+ dev_err(dev, "failed to disable sensor\n");
+}
+
+static int ltr390_pm_init(struct ltr390_data *data)
+{
+ int ret;
+ struct device *dev = &data->client->dev;
+
+ ret = devm_pm_runtime_set_active_enabled(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable runtime PM\n");
+
+ pm_runtime_set_autosuspend_delay(dev, 1000);
+ pm_runtime_use_autosuspend(dev);
+ return 0;
+}
+
static int ltr390_probe(struct i2c_client *client)
{
struct ltr390_data *data;
@@ -640,8 +783,9 @@ static int ltr390_probe(struct i2c_client *client)
if (!indio_dev)
return -ENOMEM;
- data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data = iio_priv(indio_dev);
data->regmap = devm_regmap_init_i2c(client, &ltr390_regmap_config);
if (IS_ERR(data->regmap))
return dev_err_probe(dev, PTR_ERR(data->regmap),
@@ -654,6 +798,8 @@ static int ltr390_probe(struct i2c_client *client)
data->gain = 3;
/* default mode for ltr390 is ALS mode */
data->mode = LTR390_SET_ALS_MODE;
+ /* default value of irq_enabled is false */
+ data->irq_enabled = false;
mutex_init(&data->lock);
@@ -681,6 +827,10 @@ static int ltr390_probe(struct i2c_client *client)
if (ret)
return dev_err_probe(dev, ret, "failed to enable the sensor\n");
+ ret = devm_add_action_or_reset(dev, ltr390_powerdown, data);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add action or reset\n");
+
if (client->irq) {
ret = devm_request_threaded_irq(dev, client->irq,
NULL, ltr390_interrupt_handler,
@@ -692,6 +842,10 @@ static int ltr390_probe(struct i2c_client *client)
"request irq (%d) failed\n", client->irq);
}
+ ret = ltr390_pm_init(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to initialize runtime PM\n");
+
return devm_iio_device_register(dev, indio_dev);
}
@@ -713,7 +867,26 @@ static int ltr390_resume(struct device *dev)
LTR390_SENSOR_ENABLE);
}
-static DEFINE_SIMPLE_DEV_PM_OPS(ltr390_pm_ops, ltr390_suspend, ltr390_resume);
+static int ltr390_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct ltr390_data *data = iio_priv(indio_dev);
+
+ return regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE);
+}
+
+static int ltr390_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct ltr390_data *data = iio_priv(indio_dev);
+
+ return regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE);
+}
+
+static const struct dev_pm_ops ltr390_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(ltr390_suspend, ltr390_resume)
+ RUNTIME_PM_OPS(ltr390_runtime_suspend, ltr390_runtime_resume, NULL)
+};
static const struct i2c_device_id ltr390_id[] = {
{ "ltr390" },
@@ -731,7 +904,7 @@ static struct i2c_driver ltr390_driver = {
.driver = {
.name = "ltr390",
.of_match_table = ltr390_of_table,
- .pm = pm_sleep_ptr(&ltr390_pm_ops),
+ .pm = pm_ptr(&ltr390_pm_ops),
},
.probe = ltr390_probe,
.id_table = ltr390_id,
diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c
index debf57a52d1c..022e0693983b 100644
--- a/drivers/iio/light/ltr501.c
+++ b/drivers/iio/light/ltr501.c
@@ -1315,8 +1315,8 @@ static irqreturn_t ltr501_trigger_handler(int irq, void *p)
scan.channels[j++] = psdata & LTR501_PS_DATA_MASK;
}
- iio_push_to_buffers_with_timestamp(indio_dev, &scan,
- iio_get_time_ns(indio_dev));
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
+ iio_get_time_ns(indio_dev));
done:
iio_trigger_notify_done(indio_dev->trig);
diff --git a/drivers/iio/light/ltrf216a.c b/drivers/iio/light/ltrf216a.c
index 61f57a82b872..5f27f754fe1c 100644
--- a/drivers/iio/light/ltrf216a.c
+++ b/drivers/iio/light/ltrf216a.c
@@ -208,7 +208,6 @@ static int ltrf216a_set_power_state(struct ltrf216a_data *data, bool on)
return ret;
}
} else {
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
}
diff --git a/drivers/iio/light/max44000.c b/drivers/iio/light/max44000.c
index e8b767680133..039d45af3a7f 100644
--- a/drivers/iio/light/max44000.c
+++ b/drivers/iio/light/max44000.c
@@ -75,11 +75,6 @@
struct max44000_data {
struct mutex lock;
struct regmap *regmap;
- /* Ensure naturally aligned timestamp */
- struct {
- u16 channels[2];
- aligned_s64 ts;
- } scan;
};
/* Default scale is set to the minimum of 0.03125 or 1 / (1 << 5) lux */
@@ -496,24 +491,29 @@ static irqreturn_t max44000_trigger_handler(int irq, void *p)
int index = 0;
unsigned int regval;
int ret;
+ struct {
+ u16 channels[2];
+ aligned_s64 ts;
+ } scan = { };
+
mutex_lock(&data->lock);
if (test_bit(MAX44000_SCAN_INDEX_ALS, indio_dev->active_scan_mask)) {
ret = max44000_read_alsval(data);
if (ret < 0)
goto out_unlock;
- data->scan.channels[index++] = ret;
+ scan.channels[index++] = ret;
}
if (test_bit(MAX44000_SCAN_INDEX_PRX, indio_dev->active_scan_mask)) {
ret = regmap_read(data->regmap, MAX44000_REG_PRX_DATA, &regval);
if (ret < 0)
goto out_unlock;
- data->scan.channels[index] = regval;
+ scan.channels[index] = regval;
}
mutex_unlock(&data->lock);
- iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
- iio_get_time_ns(indio_dev));
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
+ iio_get_time_ns(indio_dev));
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
diff --git a/drivers/iio/light/opt4001.c b/drivers/iio/light/opt4001.c
index ba4eb82d9bc2..95167273bb90 100644
--- a/drivers/iio/light/opt4001.c
+++ b/drivers/iio/light/opt4001.c
@@ -428,8 +428,7 @@ static int opt4001_probe(struct i2c_client *client)
opt4001_chip_off_action,
chip);
if (ret < 0)
- return dev_err_probe(&client->dev, ret,
- "Failed to setup power off action\n");
+ return ret;
return devm_iio_device_register(&client->dev, indio_dev);
}
diff --git a/drivers/iio/light/opt4060.c b/drivers/iio/light/opt4060.c
index 566f1bb8fe2a..981c704e7df5 100644
--- a/drivers/iio/light/opt4060.c
+++ b/drivers/iio/light/opt4060.c
@@ -1104,7 +1104,7 @@ static irqreturn_t opt4060_trigger_handler(int irq, void *p)
}
}
- iio_push_to_buffers_with_timestamp(idev, &raw, pf->timestamp);
+ iio_push_to_buffers_with_ts(idev, &raw, sizeof(raw), pf->timestamp);
err_read:
iio_trigger_notify_done(idev->trig);
return IRQ_HANDLED;
@@ -1212,7 +1212,7 @@ static int opt4060_setup_trigger(struct opt4060_chip *chip, struct iio_dev *idev
name = devm_kasprintf(chip->dev, GFP_KERNEL, "%s-opt4060",
dev_name(chip->dev));
if (!name)
- return dev_err_probe(chip->dev, -ENOMEM, "Failed to alloc chip name\n");
+ return -ENOMEM;
ret = devm_request_threaded_irq(chip->dev, chip->irq, NULL, opt4060_irq_thread,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
@@ -1299,8 +1299,7 @@ static int opt4060_probe(struct i2c_client *client)
ret = devm_add_action_or_reset(dev, opt4060_chip_off_action, chip);
if (ret < 0)
- return dev_err_probe(dev, ret,
- "Failed to setup power off action\n");
+ return ret;
ret = opt4060_setup_buffer(chip, indio_dev);
if (ret)
diff --git a/drivers/iio/light/pa12203001.c b/drivers/iio/light/pa12203001.c
index 8885852bef22..98a1f1624c75 100644
--- a/drivers/iio/light/pa12203001.c
+++ b/drivers/iio/light/pa12203001.c
@@ -185,15 +185,10 @@ static int pa12203001_set_power_state(struct pa12203001_data *data, bool on,
mutex_unlock(&data->lock);
}
- if (on) {
- ret = pm_runtime_resume_and_get(&data->client->dev);
+ if (on)
+ return pm_runtime_resume_and_get(&data->client->dev);
- } else {
- pm_runtime_mark_last_busy(&data->client->dev);
- ret = pm_runtime_put_autosuspend(&data->client->dev);
- }
-
- return ret;
+ return pm_runtime_put_autosuspend(&data->client->dev);
err:
mutex_unlock(&data->lock);
diff --git a/drivers/iio/light/rohm-bu27034.c b/drivers/iio/light/rohm-bu27034.c
index 7cec5e943373..28d111ac8c0a 100644
--- a/drivers/iio/light/rohm-bu27034.c
+++ b/drivers/iio/light/rohm-bu27034.c
@@ -1193,7 +1193,8 @@ static int bu27034_buffer_thread(void *arg)
*/
data->scan.mlux = (u32)mlux;
}
- iio_push_to_buffers_with_timestamp(idev, &data->scan, tstamp);
+ iio_push_to_buffers_with_ts(idev, &data->scan,
+ sizeof(data->scan), tstamp);
}
return 0;
diff --git a/drivers/iio/light/rpr0521.c b/drivers/iio/light/rpr0521.c
index c50183f07240..9341c1d58cbe 100644
--- a/drivers/iio/light/rpr0521.c
+++ b/drivers/iio/light/rpr0521.c
@@ -358,12 +358,10 @@ static int rpr0521_set_power_state(struct rpr0521_data *data, bool on,
* Note: If either measurement is re-enabled before _suspend(),
* both stay enabled until _suspend().
*/
- if (on) {
+ if (on)
ret = pm_runtime_resume_and_get(&data->client->dev);
- } else {
- pm_runtime_mark_last_busy(&data->client->dev);
+ else
ret = pm_runtime_put_autosuspend(&data->client->dev);
- }
if (ret < 0) {
dev_err(&data->client->dev,
"Failed: rpr0521_set_power_state for %d, ret %d\n",
@@ -457,8 +455,8 @@ static irqreturn_t rpr0521_trigger_consumer_handler(int irq, void *p)
data->scan.channels,
(3 * 2) + 1); /* 3 * 16-bit + (discarded) int clear reg. */
if (!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);
else
dev_err(&data->client->dev,
"Trigger consumer can't read from sensor.\n");
diff --git a/drivers/iio/light/si1145.c b/drivers/iio/light/si1145.c
index 4aa02afd853e..f8eb251eca8d 100644
--- a/drivers/iio/light/si1145.c
+++ b/drivers/iio/light/si1145.c
@@ -494,8 +494,9 @@ static irqreturn_t si1145_trigger_handler(int irq, void *private)
goto done;
}
- iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
- iio_get_time_ns(indio_dev));
+ iio_push_to_buffers_with_ts(indio_dev, data->buffer,
+ sizeof(data->buffer),
+ iio_get_time_ns(indio_dev));
done:
iio_trigger_notify_done(indio_dev->trig);
diff --git a/drivers/iio/light/st_uvis25.h b/drivers/iio/light/st_uvis25.h
index 1f93e3dc45c2..78bc56aad129 100644
--- a/drivers/iio/light/st_uvis25.h
+++ b/drivers/iio/light/st_uvis25.h
@@ -27,11 +27,6 @@ struct st_uvis25_hw {
struct iio_trigger *trig;
bool enabled;
int irq;
- /* Ensure timestamp is naturally aligned */
- struct {
- u8 chan;
- aligned_s64 ts;
- } scan;
};
extern const struct dev_pm_ops st_uvis25_pm_ops;
diff --git a/drivers/iio/light/st_uvis25_core.c b/drivers/iio/light/st_uvis25_core.c
index 124a8f9204a9..bcd729a9924e 100644
--- a/drivers/iio/light/st_uvis25_core.c
+++ b/drivers/iio/light/st_uvis25_core.c
@@ -234,15 +234,21 @@ static irqreturn_t st_uvis25_buffer_handler_thread(int irq, void *p)
struct st_uvis25_hw *hw = iio_priv(iio_dev);
unsigned int val;
int err;
+ /* Ensure timestamp is naturally aligned */
+ struct {
+ u8 chan;
+ aligned_s64 ts;
+ } scan = { };
+
err = regmap_read(hw->regmap, ST_UVIS25_REG_OUT_ADDR, &val);
if (err < 0)
goto out;
- hw->scan.chan = val;
+ scan.chan = val;
- iio_push_to_buffers_with_timestamp(iio_dev, &hw->scan,
- iio_get_time_ns(iio_dev));
+ iio_push_to_buffers_with_ts(iio_dev, &scan, sizeof(scan),
+ iio_get_time_ns(iio_dev));
out:
iio_trigger_notify_done(hw->trig);
diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c
index 81dd2bfc22c0..a75a83594a7e 100644
--- a/drivers/iio/light/stk3310.c
+++ b/drivers/iio/light/stk3310.c
@@ -607,10 +607,8 @@ static int stk3310_probe(struct i2c_client *client)
struct stk3310_data *data;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
- if (!indio_dev) {
- dev_err(&client->dev, "iio allocation failed!\n");
+ if (!indio_dev)
return -ENOMEM;
- }
data = iio_priv(indio_dev);
data->client = client;
diff --git a/drivers/iio/light/tcs3414.c b/drivers/iio/light/tcs3414.c
index 39268f855c77..5be461e6dbdb 100644
--- a/drivers/iio/light/tcs3414.c
+++ b/drivers/iio/light/tcs3414.c
@@ -53,11 +53,6 @@ struct tcs3414_data {
u8 control;
u8 gain;
u8 timing;
- /* Ensure timestamp is naturally aligned */
- struct {
- u16 chans[4];
- aligned_s64 timestamp;
- } scan;
};
#define TCS3414_CHANNEL(_color, _si, _addr) { \
@@ -204,6 +199,12 @@ static irqreturn_t tcs3414_trigger_handler(int irq, void *p)
struct iio_dev *indio_dev = pf->indio_dev;
struct tcs3414_data *data = iio_priv(indio_dev);
int i, j = 0;
+ /* Ensure timestamp is naturally aligned */
+ struct {
+ u16 chans[4];
+ aligned_s64 timestamp;
+ } scan = { };
+
iio_for_each_active_channel(indio_dev, i) {
int ret = i2c_smbus_read_word_data(data->client,
@@ -211,10 +212,10 @@ static irqreturn_t tcs3414_trigger_handler(int irq, void *p)
if (ret < 0)
goto done;
- data->scan.chans[j++] = ret;
+ scan.chans[j++] = ret;
}
- iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
iio_get_time_ns(indio_dev));
done:
diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c
index 0f8bf8503edd..12429a3261b3 100644
--- a/drivers/iio/light/tcs3472.c
+++ b/drivers/iio/light/tcs3472.c
@@ -64,11 +64,6 @@ struct tcs3472_data {
u8 control;
u8 atime;
u8 apers;
- /* Ensure timestamp is naturally aligned */
- struct {
- u16 chans[4];
- aligned_s64 timestamp;
- } scan;
};
static const struct iio_event_spec tcs3472_events[] = {
@@ -377,6 +372,11 @@ static irqreturn_t tcs3472_trigger_handler(int irq, void *p)
struct iio_dev *indio_dev = pf->indio_dev;
struct tcs3472_data *data = iio_priv(indio_dev);
int i, j = 0;
+ /* Ensure timestamp is naturally aligned */
+ struct {
+ u16 chans[4];
+ aligned_s64 timestamp;
+ } scan = { };
int ret = tcs3472_req_data(data);
if (ret < 0)
@@ -388,10 +388,10 @@ static irqreturn_t tcs3472_trigger_handler(int irq, void *p)
if (ret < 0)
goto done;
- data->scan.chans[j++] = ret;
+ scan.chans[j++] = ret;
}
- iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
iio_get_time_ns(indio_dev));
done:
diff --git a/drivers/iio/light/tsl2583.c b/drivers/iio/light/tsl2583.c
index fc3b0c4226be..8801a491de77 100644
--- a/drivers/iio/light/tsl2583.c
+++ b/drivers/iio/light/tsl2583.c
@@ -641,16 +641,10 @@ static const struct iio_chan_spec tsl2583_channels[] = {
static int tsl2583_set_pm_runtime_busy(struct tsl2583_chip *chip, bool on)
{
- int ret;
+ if (on)
+ return pm_runtime_resume_and_get(&chip->client->dev);
- if (on) {
- ret = pm_runtime_resume_and_get(&chip->client->dev);
- } else {
- pm_runtime_mark_last_busy(&chip->client->dev);
- ret = pm_runtime_put_autosuspend(&chip->client->dev);
- }
-
- return ret;
+ return pm_runtime_put_autosuspend(&chip->client->dev);
}
static int tsl2583_read_raw(struct iio_dev *indio_dev,
diff --git a/drivers/iio/light/tsl2591.c b/drivers/iio/light/tsl2591.c
index 08476f193a44..c5557867ea43 100644
--- a/drivers/iio/light/tsl2591.c
+++ b/drivers/iio/light/tsl2591.c
@@ -772,7 +772,6 @@ static int tsl2591_read_raw(struct iio_dev *indio_dev,
err_unlock:
mutex_unlock(&chip->als_mutex);
- pm_runtime_mark_last_busy(&client->dev);
pm_runtime_put_autosuspend(&client->dev);
return ret;
@@ -995,7 +994,6 @@ static int tsl2591_write_event_config(struct iio_dev *indio_dev,
pm_runtime_get_sync(&client->dev);
} else if (!state && chip->events_enabled) {
chip->events_enabled = false;
- pm_runtime_mark_last_busy(&client->dev);
pm_runtime_put_autosuspend(&client->dev);
}
diff --git a/drivers/iio/light/us5182d.c b/drivers/iio/light/us5182d.c
index 61a0957317a1..d2f5a44892a8 100644
--- a/drivers/iio/light/us5182d.c
+++ b/drivers/iio/light/us5182d.c
@@ -361,19 +361,13 @@ static int us5182d_shutdown_en(struct us5182d_data *data, u8 state)
static int us5182d_set_power_state(struct us5182d_data *data, bool on)
{
- int ret;
-
if (data->power_mode == US5182D_ONESHOT)
return 0;
- if (on) {
- ret = pm_runtime_resume_and_get(&data->client->dev);
- } else {
- pm_runtime_mark_last_busy(&data->client->dev);
- ret = pm_runtime_put_autosuspend(&data->client->dev);
- }
+ if (on)
+ return pm_runtime_resume_and_get(&data->client->dev);
- return ret;
+ return pm_runtime_put_autosuspend(&data->client->dev);
}
static int us5182d_read_value(struct us5182d_data *data,
diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c
index 90e7d4421abf..4dbb2294a843 100644
--- a/drivers/iio/light/vcnl4000.c
+++ b/drivers/iio/light/vcnl4000.c
@@ -576,16 +576,11 @@ static bool vcnl4010_is_in_periodic_mode(struct vcnl4000_data *data)
static int vcnl4000_set_pm_runtime_state(struct vcnl4000_data *data, bool on)
{
struct device *dev = &data->client->dev;
- int ret;
- if (on) {
- ret = pm_runtime_resume_and_get(dev);
- } else {
- pm_runtime_mark_last_busy(dev);
- ret = pm_runtime_put_autosuspend(dev);
- }
+ if (on)
+ return pm_runtime_resume_and_get(dev);
- return ret;
+ return pm_runtime_put_autosuspend(dev);
}
static int vcnl4040_read_als_it(struct vcnl4000_data *data, int *val, int *val2)
@@ -1662,7 +1657,10 @@ static irqreturn_t vcnl4010_trigger_handler(int irq, void *p)
struct iio_dev *indio_dev = pf->indio_dev;
struct vcnl4000_data *data = iio_priv(indio_dev);
const unsigned long *active_scan_mask = indio_dev->active_scan_mask;
- u16 buffer[8] __aligned(8) = {0}; /* 1x16-bit + naturally aligned ts */
+ struct {
+ u16 chan;
+ aligned_s64 ts;
+ } scan = { };
bool data_read = false;
unsigned long isr;
int val = 0;
@@ -1682,7 +1680,7 @@ static irqreturn_t vcnl4010_trigger_handler(int irq, void *p)
if (ret < 0)
goto end;
- buffer[0] = val;
+ scan.chan = val;
data_read = true;
}
}
@@ -1695,8 +1693,8 @@ static irqreturn_t vcnl4010_trigger_handler(int irq, void *p)
if (!data_read)
goto end;
- iio_push_to_buffers_with_timestamp(indio_dev, buffer,
- iio_get_time_ns(indio_dev));
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
+ iio_get_time_ns(indio_dev));
end:
iio_trigger_notify_done(indio_dev->trig);
diff --git a/drivers/iio/light/vcnl4035.c b/drivers/iio/light/vcnl4035.c
index 01bc99564f98..963747927425 100644
--- a/drivers/iio/light/vcnl4035.c
+++ b/drivers/iio/light/vcnl4035.c
@@ -141,17 +141,12 @@ static const struct iio_trigger_ops vcnl4035_trigger_ops = {
static int vcnl4035_set_pm_runtime_state(struct vcnl4035_data *data, bool on)
{
- int ret;
struct device *dev = &data->client->dev;
- if (on) {
- ret = pm_runtime_resume_and_get(dev);
- } else {
- pm_runtime_mark_last_busy(dev);
- ret = pm_runtime_put_autosuspend(dev);
- }
+ if (on)
+ return pm_runtime_resume_and_get(dev);
- return ret;
+ return pm_runtime_put_autosuspend(dev);
}
static int vcnl4035_read_info_raw(struct iio_dev *indio_dev,
diff --git a/drivers/iio/light/veml6030.c b/drivers/iio/light/veml6030.c
index 0945f146bedb..6bcacae3863c 100644
--- a/drivers/iio/light/veml6030.c
+++ b/drivers/iio/light/veml6030.c
@@ -903,7 +903,7 @@ static irqreturn_t veml6030_trigger_handler(int irq, void *p)
scan.chans[i++] = reg;
}
- iio_push_to_buffers_with_timestamp(iio, &scan, pf->timestamp);
+ iio_push_to_buffers_with_ts(iio, &scan, sizeof(scan), pf->timestamp);
done:
iio_trigger_notify_done(iio->trig);
diff --git a/drivers/iio/light/veml6040.c b/drivers/iio/light/veml6040.c
index 71a594b2ec85..f563f9f0ee67 100644
--- a/drivers/iio/light/veml6040.c
+++ b/drivers/iio/light/veml6040.c
@@ -219,8 +219,7 @@ static int veml6040_probe(struct i2c_client *client)
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
- return dev_err_probe(dev, -ENOMEM,
- "IIO device allocation failed\n");
+ return -ENOMEM;
regmap = devm_regmap_init_i2c(client, &veml6040_regmap_config);
if (IS_ERR(regmap))
diff --git a/drivers/iio/light/veml6046x00.c b/drivers/iio/light/veml6046x00.c
new file mode 100644
index 000000000000..e60f24d46e7b
--- /dev/null
+++ b/drivers/iio/light/veml6046x00.c
@@ -0,0 +1,1030 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * VEML6046X00 High Accuracy RGBIR Color Sensor
+ *
+ * Copyright (c) 2025 Andreas Klinger <ak@it-klinger.de>
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/time.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <asm/byteorder.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+/*
+ * Device registers
+ * Those which are accessed as bulk io are omitted
+ */
+#define VEML6046X00_REG_CONF0 0x00
+#define VEML6046X00_REG_CONF1 0x01
+#define VEML6046X00_REG_THDH 0x04
+#define VEML6046X00_REG_THDL 0x06
+#define VEML6046X00_REG_R 0x10
+#define VEML6046X00_REG_G 0x12
+#define VEML6046X00_REG_B 0x14
+#define VEML6046X00_REG_IR 0x16
+#define VEML6046X00_REG_ID 0x18
+#define VEML6046X00_REG_INT 0x1A
+#define VEML6046X00_REG_INT_H 0x1B
+
+/* Bit masks for specific functionality */
+#define VEML6046X00_CONF0_ON_0 BIT(0)
+#define VEML6046X00_CONF0_INT BIT(1)
+#define VEML6046X00_CONF0_AF_TRIG BIT(2)
+#define VEML6046X00_CONF0_AF BIT(3)
+#define VEML6046X00_CONF0_IT GENMASK(6, 4)
+#define VEML6046X00_CONF1_CAL BIT(0)
+#define VEML6046X00_CONF1_PERS GENMASK(2, 1)
+#define VEML6046X00_CONF1_GAIN GENMASK(4, 3)
+#define VEML6046X00_CONF1_PD_D2 BIT(6)
+#define VEML6046X00_CONF1_ON_1 BIT(7)
+#define VEML6046X00_INT_TH_H BIT(1)
+#define VEML6046X00_INT_TH_L BIT(2)
+#define VEML6046X00_INT_DRDY BIT(3)
+#define VEML6046X00_INT_MASK \
+ (VEML6046X00_INT_TH_H | VEML6046X00_INT_TH_L | VEML6046X00_INT_DRDY)
+
+#define VEML6046X00_GAIN_1 0x0
+#define VEML6046X00_GAIN_2 0x1
+#define VEML6046X00_GAIN_0_66 0x2
+#define VEML6046X00_GAIN_0_5 0x3
+
+#define VEML6046X00_PD_2_2 0x0
+#define VEML6046X00_PD_1_2 BIT(6)
+
+/* Autosuspend delay */
+#define VEML6046X00_AUTOSUSPEND_MS (3 * MSEC_PER_SEC)
+
+enum veml6046x00_scan {
+ VEML6046X00_SCAN_R,
+ VEML6046X00_SCAN_G,
+ VEML6046X00_SCAN_B,
+ VEML6046X00_SCAN_IR,
+ VEML6046X00_SCAN_TIMESTAMP,
+};
+
+/**
+ * struct veml6046x00_rf - Regmap field of configuration registers.
+ * @int_en: Interrupt enable of green channel.
+ * @mode: Mode of operation.
+ * Driver uses always Active force mode.
+ * @trig: Trigger to be set in active force mode for starting
+ * measurement.
+ * @it: Integration time.
+ * @pers: Persistense - Number of threshold crossing for triggering
+ * interrupt.
+ */
+struct veml6046x00_rf {
+ struct regmap_field *int_en;
+ struct regmap_field *mode;
+ struct regmap_field *trig;
+ struct regmap_field *it;
+ struct regmap_field *pers;
+};
+
+/**
+ * struct veml6046x00_data - Private data of driver.
+ * @regmap: Regmap definition of sensor.
+ * @trig: Industrial-IO trigger.
+ * @rf: Regmap field of configuration.
+ */
+struct veml6046x00_data {
+ struct regmap *regmap;
+ struct iio_trigger *trig;
+ struct veml6046x00_rf rf;
+};
+
+/**
+ * DOC: Valid integration times (IT)
+ *
+ * static const int veml6046x00_it contains the array with valid IT.
+ *
+ * Register value to be read or written in regmap_field it on veml6046x00 is
+ * identical with array index.
+ * This means there is no separate translation table between valid integration
+ * times and register values needed. The index of the array is identical with
+ * the register value.
+ *
+ * The array is in the form as expected by the callback of the sysfs attribute
+ * integration_time_available (IIO_CHAN_INFO_INT_TIME). So there is no
+ * additional conversion needed.
+ */
+static const int veml6046x00_it[][2] = {
+ { 0, 3125 },
+ { 0, 6250 },
+ { 0, 12500 },
+ { 0, 25000 },
+ { 0, 50000 },
+ { 0, 100000 },
+ { 0, 200000 },
+ { 0, 400000 },
+};
+
+/**
+ * DOC: Handling of gain and photodiode size (PD)
+ *
+ * Gains here in the driver are not exactly the same as in the datasheet of the
+ * sensor. The gain in the driver is a combination of the gain of the sensor
+ * with the photodiode size (PD).
+ * The following combinations are possible:
+ * gain(driver) = gain(sensor) * PD
+ * 0.25 = x0.5 * 1/2
+ * 0.33 = x0.66 * 1/2
+ * 0.5 = x0.5 * 2/2
+ * 0.66 = x0.66 * 2/2
+ * 1 = x1 * 2/2
+ * 2 = x2 * 2/2
+ */
+
+/**
+ * struct veml6046x00_gain_pd - Translation of gain and photodiode size (PD).
+ * @gain_sen: Gain used in the sensor as described in the datasheet of the
+ * sensor
+ * @pd: Photodiode size in the sensor
+ *
+ * This is the translation table from the gain used in the driver (and also used
+ * by the userspace interface in sysfs) to the gain and PD used in the sensor
+ * hardware.
+ *
+ * There are six gain values visible to the user (0.25 .. 2) which translate to
+ * two different gains in the sensor hardware (x0.5 .. x2) and two PD (1/2 and
+ * 2/2). Theoretical are there eight combinations, but gain values 0.5 and 1 are
+ * doubled and therefore the combination with the larger PD (2/2) is taken as
+ * more photodiode cells are supposed to deliver a more precise result.
+ */
+struct veml6046x00_gain_pd {
+ unsigned int gain_sen;
+ unsigned int pd;
+};
+
+static const struct veml6046x00_gain_pd veml6046x00_gain_pd[] = {
+ { .gain_sen = VEML6046X00_GAIN_0_5, .pd = VEML6046X00_PD_1_2 },
+ { .gain_sen = VEML6046X00_GAIN_0_66, .pd = VEML6046X00_PD_1_2 },
+ { .gain_sen = VEML6046X00_GAIN_0_5, .pd = VEML6046X00_PD_2_2 },
+ { .gain_sen = VEML6046X00_GAIN_0_66, .pd = VEML6046X00_PD_2_2 },
+ { .gain_sen = VEML6046X00_GAIN_1, .pd = VEML6046X00_PD_2_2 },
+ { .gain_sen = VEML6046X00_GAIN_2, .pd = VEML6046X00_PD_2_2 },
+};
+
+/**
+ * DOC: Factors for calculation of lux
+ *
+ * static const int veml6046x00_it_gains contains the factors for calculation of
+ * lux.
+ *
+ * Depending on the set up integration time (IT), gain and photodiode size (PD)
+ * the measured raw values are different if the light is constant. As the gain
+ * and PD are already coupled in the driver (see &struct veml6046x00_gain_pd)
+ * there are two dimensions remaining: IT and gain(driver).
+ *
+ * The array of available factors for a certain IT are grouped together in the
+ * same form as expected by the callback of scale_available
+ * (IIO_CHAN_INFO_SCALE).
+ *
+ * Factors for lux / raw count are taken directly from the datasheet.
+ */
+static const int veml6046x00_it_gains[][6][2] = {
+ /* integration time: 3.125 ms */
+ {
+ { 5, 376000 }, /* gain: x0.25 */
+ { 4, 72700 }, /* gain: x0.33 */
+ { 2, 688000 }, /* gain: x0.5 */
+ { 2, 36400 }, /* gain: x0.66 */
+ { 1, 344000 }, /* gain: x1 */
+ { 0, 672000 }, /* gain: x2 */
+ },
+ /* integration time: 6.25 ms */
+ {
+ { 2, 688000 }, /* gain: x0.25 */
+ { 2, 36350 }, /* gain: x0.33 */
+ { 1, 344000 }, /* gain: x0.5 */
+ { 1, 18200 }, /* gain: x0.66 */
+ { 0, 672000 }, /* gain: x1 */
+ { 0, 336000 }, /* gain: x2 */
+ },
+ /* integration time: 12.5 ms */
+ {
+ { 1, 344000 }, /* gain: x0.25 */
+ { 1, 18175 }, /* gain: x0.33 */
+ { 0, 672000 }, /* gain: x0.5 */
+ { 0, 509100 }, /* gain: x0.66 */
+ { 0, 336000 }, /* gain: x1 */
+ { 0, 168000 }, /* gain: x2 */
+ },
+ /* integration time: 25 ms */
+ {
+ { 0, 672000 }, /* gain: x0.25 */
+ { 0, 509087 }, /* gain: x0.33 */
+ { 0, 336000 }, /* gain: x0.5 */
+ { 0, 254550 }, /* gain: x0.66 */
+ { 0, 168000 }, /* gain: x1 */
+ { 0, 84000 }, /* gain: x2 */
+ },
+ /* integration time: 50 ms */
+ {
+ { 0, 336000 }, /* gain: x0.25 */
+ { 0, 254543 }, /* gain: x0.33 */
+ { 0, 168000 }, /* gain: x0.5 */
+ { 0, 127275 }, /* gain: x0.66 */
+ { 0, 84000 }, /* gain: x1 */
+ { 0, 42000 }, /* gain: x2 */
+ },
+ /* integration time: 100 ms */
+ {
+ { 0, 168000 }, /* gain: x0.25 */
+ { 0, 127271 }, /* gain: x0.33 */
+ { 0, 84000 }, /* gain: x0.5 */
+ { 0, 63637 }, /* gain: x0.66 */
+ { 0, 42000 }, /* gain: x1 */
+ { 0, 21000 }, /* gain: x2 */
+ },
+ /* integration time: 200 ms */
+ {
+ { 0, 84000 }, /* gain: x0.25 */
+ { 0, 63635 }, /* gain: x0.33 */
+ { 0, 42000 }, /* gain: x0.5 */
+ { 0, 31818 }, /* gain: x0.66 */
+ { 0, 21000 }, /* gain: x1 */
+ { 0, 10500 }, /* gain: x2 */
+ },
+ /* integration time: 400 ms */
+ {
+ { 0, 42000 }, /* gain: x0.25 */
+ { 0, 31817 }, /* gain: x0.33 */
+ { 0, 21000 }, /* gain: x0.5 */
+ { 0, 15909 }, /* gain: x0.66 */
+ { 0, 10500 }, /* gain: x1 */
+ { 0, 5250 }, /* gain: x2 */
+ },
+};
+
+/*
+ * Two bits (RGB_ON_0 and RGB_ON_1) must be cleared to power on the device.
+ */
+static int veml6046x00_power_on(struct veml6046x00_data *data)
+{
+ int ret;
+ struct device *dev = regmap_get_device(data->regmap);
+
+ ret = regmap_clear_bits(data->regmap, VEML6046X00_REG_CONF0,
+ VEML6046X00_CONF0_ON_0);
+ if (ret) {
+ dev_err(dev, "Failed to set bit for power on %d\n", ret);
+ return ret;
+ }
+
+ return regmap_clear_bits(data->regmap, VEML6046X00_REG_CONF1,
+ VEML6046X00_CONF1_ON_1);
+}
+
+/*
+ * Two bits (RGB_ON_0 and RGB_ON_1) must be set to power off the device.
+ */
+static int veml6046x00_shutdown(struct veml6046x00_data *data)
+{
+ int ret;
+ struct device *dev = regmap_get_device(data->regmap);
+
+ ret = regmap_set_bits(data->regmap, VEML6046X00_REG_CONF0,
+ VEML6046X00_CONF0_ON_0);
+ if (ret) {
+ dev_err(dev, "Failed to set bit for shutdown %d\n", ret);
+ return ret;
+ }
+
+ return regmap_set_bits(data->regmap, VEML6046X00_REG_CONF1,
+ VEML6046X00_CONF1_ON_1);
+}
+
+static void veml6046x00_shutdown_action(void *data)
+{
+ veml6046x00_shutdown(data);
+}
+
+static const struct iio_chan_spec veml6046x00_channels[] = {
+ {
+ .type = IIO_INTENSITY,
+ .address = VEML6046X00_REG_R,
+ .modified = 1,
+ .channel2 = IIO_MOD_LIGHT_RED,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .scan_index = VEML6046X00_SCAN_R,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_LE,
+ },
+ },
+ {
+ .type = IIO_INTENSITY,
+ .address = VEML6046X00_REG_G,
+ .modified = 1,
+ .channel2 = IIO_MOD_LIGHT_GREEN,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .scan_index = VEML6046X00_SCAN_G,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_LE,
+ },
+ },
+ {
+ .type = IIO_INTENSITY,
+ .address = VEML6046X00_REG_B,
+ .modified = 1,
+ .channel2 = IIO_MOD_LIGHT_BLUE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .scan_index = VEML6046X00_SCAN_B,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_LE,
+ },
+ },
+ {
+ .type = IIO_INTENSITY,
+ .address = VEML6046X00_REG_IR,
+ .modified = 1,
+ .channel2 = IIO_MOD_LIGHT_IR,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .scan_index = VEML6046X00_SCAN_IR,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_LE,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(VEML6046X00_SCAN_TIMESTAMP),
+};
+
+static const struct regmap_config veml6046x00_regmap_config = {
+ .name = "veml6046x00_regm",
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = VEML6046X00_REG_INT_H,
+};
+
+static const struct reg_field veml6046x00_rf_int_en =
+ REG_FIELD(VEML6046X00_REG_CONF0, 1, 1);
+
+static const struct reg_field veml6046x00_rf_trig =
+ REG_FIELD(VEML6046X00_REG_CONF0, 2, 2);
+
+static const struct reg_field veml6046x00_rf_mode =
+ REG_FIELD(VEML6046X00_REG_CONF0, 3, 3);
+
+static const struct reg_field veml6046x00_rf_it =
+ REG_FIELD(VEML6046X00_REG_CONF0, 4, 6);
+
+static const struct reg_field veml6046x00_rf_pers =
+ REG_FIELD(VEML6046X00_REG_CONF1, 1, 2);
+
+static int veml6046x00_regfield_init(struct veml6046x00_data *data)
+{
+ struct regmap *regmap = data->regmap;
+ struct device *dev = regmap_get_device(data->regmap);
+ struct regmap_field *rm_field;
+ struct veml6046x00_rf *rf = &data->rf;
+
+ rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_int_en);
+ if (IS_ERR(rm_field))
+ return PTR_ERR(rm_field);
+ rf->int_en = rm_field;
+
+ rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_mode);
+ if (IS_ERR(rm_field))
+ return PTR_ERR(rm_field);
+ rf->mode = rm_field;
+
+ rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_trig);
+ if (IS_ERR(rm_field))
+ return PTR_ERR(rm_field);
+ rf->trig = rm_field;
+
+ rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_it);
+ if (IS_ERR(rm_field))
+ return PTR_ERR(rm_field);
+ rf->it = rm_field;
+
+ rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_pers);
+ if (IS_ERR(rm_field))
+ return PTR_ERR(rm_field);
+ rf->pers = rm_field;
+
+ return 0;
+}
+
+static int veml6046x00_get_it_index(struct veml6046x00_data *data)
+{
+ int ret;
+ unsigned int reg;
+
+ ret = regmap_field_read(data->rf.it, &reg);
+ if (ret)
+ return ret;
+
+ /* register value is identical with index of array */
+ if (reg >= ARRAY_SIZE(veml6046x00_it))
+ return -EINVAL;
+
+ return reg;
+}
+
+static int veml6046x00_get_it_usec(struct veml6046x00_data *data, unsigned int *it_usec)
+{
+ int ret;
+ unsigned int reg;
+
+ ret = regmap_field_read(data->rf.it, &reg);
+ if (ret)
+ return ret;
+
+ if (reg >= ARRAY_SIZE(veml6046x00_it))
+ return -EINVAL;
+
+ *it_usec = veml6046x00_it[reg][1];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int veml6046x00_set_it(struct iio_dev *iio, int val, int val2)
+{
+ struct veml6046x00_data *data = iio_priv(iio);
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(veml6046x00_it); i++) {
+ if ((veml6046x00_it[i][0] == val) &&
+ (veml6046x00_it[i][1] == val2))
+ return regmap_field_write(data->rf.it, i);
+ }
+
+ return -EINVAL;
+}
+
+static int veml6046x00_get_val_gain_idx(struct veml6046x00_data *data, int val,
+ int val2)
+{
+ unsigned int i;
+ int it_idx;
+
+ it_idx = veml6046x00_get_it_index(data);
+ if (it_idx < 0)
+ return it_idx;
+
+ for (i = 0; i < ARRAY_SIZE(veml6046x00_it_gains[it_idx]); i++) {
+ if ((veml6046x00_it_gains[it_idx][i][0] == val) &&
+ (veml6046x00_it_gains[it_idx][i][1] == val2))
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int veml6046x00_get_gain_idx(struct veml6046x00_data *data)
+{
+ int ret;
+ unsigned int i, reg, reg_gain, reg_pd;
+
+ ret = regmap_read(data->regmap, VEML6046X00_REG_CONF1, &reg);
+ if (ret)
+ return ret;
+
+ reg_gain = FIELD_GET(VEML6046X00_CONF1_GAIN, reg);
+ reg_pd = reg & VEML6046X00_CONF1_PD_D2;
+
+ for (i = 0; i < ARRAY_SIZE(veml6046x00_gain_pd); i++) {
+ if ((veml6046x00_gain_pd[i].gain_sen == reg_gain) &&
+ (veml6046x00_gain_pd[i].pd == reg_pd))
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int veml6046x00_set_scale(struct iio_dev *iio, int val, int val2)
+{
+ struct veml6046x00_data *data = iio_priv(iio);
+ unsigned int new_scale;
+ int gain_idx;
+
+ gain_idx = veml6046x00_get_val_gain_idx(data, val, val2);
+ if (gain_idx < 0)
+ return gain_idx;
+
+ new_scale = FIELD_PREP(VEML6046X00_CONF1_GAIN,
+ veml6046x00_gain_pd[gain_idx].gain_sen) |
+ veml6046x00_gain_pd[gain_idx].pd;
+
+ return regmap_update_bits(data->regmap, VEML6046X00_REG_CONF1,
+ VEML6046X00_CONF1_GAIN |
+ VEML6046X00_CONF1_PD_D2,
+ new_scale);
+}
+
+static int veml6046x00_get_scale(struct veml6046x00_data *data,
+ int *val, int *val2)
+{
+ int gain_idx, it_idx;
+
+ gain_idx = veml6046x00_get_gain_idx(data);
+ if (gain_idx < 0)
+ return gain_idx;
+
+ it_idx = veml6046x00_get_it_index(data);
+ if (it_idx < 0)
+ return it_idx;
+
+ *val = veml6046x00_it_gains[it_idx][gain_idx][0];
+ *val2 = veml6046x00_it_gains[it_idx][gain_idx][1];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+/**
+ * veml6046x00_read_data_ready() - Read data ready bit
+ * @data: Private data.
+ *
+ * Helper function for reading data ready bit from interrupt register.
+ *
+ * Return:
+ * * %1 - Data is available (AF_DATA_READY is set)
+ * * %0 - No data available
+ * * %-EIO - Error during bulk read
+ */
+static int veml6046x00_read_data_ready(struct veml6046x00_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+ u8 reg[2];
+
+ /*
+ * Note from the vendor, but not explicitly in the datasheet: we
+ * should always read both registers together.
+ */
+ ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_INT,
+ &reg, sizeof(reg));
+ if (ret) {
+ dev_err(dev, "Failed to read interrupt register %d\n", ret);
+ return -EIO;
+ }
+
+ if (reg[1] & VEML6046X00_INT_DRDY)
+ return 1;
+
+ return 0;
+}
+
+/**
+ * veml6046x00_wait_data_available() - Wait until data is available
+ * @iio: Industrial IO.
+ * @usecs: Microseconds to wait for data.
+ *
+ * This function waits for a certain bit in the interrupt register which signals
+ * that there is data to be read available.
+ *
+ * It tries it two times with a waiting time of usecs in between.
+ *
+ * Return:
+ * * %1 - Data is available (AF_DATA_READY is set)
+ * * %0 - Timeout, no data available after usecs timeout
+ * * %-EIO - Error during bulk read
+ */
+static int veml6046x00_wait_data_available(struct iio_dev *iio, unsigned int usecs)
+{
+ struct veml6046x00_data *data = iio_priv(iio);
+ int ret;
+
+ ret = veml6046x00_read_data_ready(data);
+ if (ret)
+ return ret;
+
+ fsleep(usecs);
+ return veml6046x00_read_data_ready(data);
+}
+
+static int veml6046x00_single_read(struct iio_dev *iio,
+ enum iio_modifier modifier, int *val)
+{
+ struct veml6046x00_data *data = iio_priv(iio);
+ struct device *dev = regmap_get_device(data->regmap);
+ unsigned int addr, it_usec;
+ int ret;
+ __le16 reg;
+
+ switch (modifier) {
+ case IIO_MOD_LIGHT_RED:
+ addr = VEML6046X00_REG_R;
+ break;
+ case IIO_MOD_LIGHT_GREEN:
+ addr = VEML6046X00_REG_G;
+ break;
+ case IIO_MOD_LIGHT_BLUE:
+ addr = VEML6046X00_REG_B;
+ break;
+ case IIO_MOD_LIGHT_IR:
+ addr = VEML6046X00_REG_IR;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = veml6046x00_get_it_usec(data, &it_usec);
+ if (ret < 0) {
+ dev_err(dev, "Failed to get integration time ret: %d", ret);
+ goto out;
+ }
+
+ ret = regmap_field_write(data->rf.mode, 1);
+ if (ret) {
+ dev_err(dev, "Failed to write mode ret: %d", ret);
+ goto out;
+ }
+
+ ret = regmap_field_write(data->rf.trig, 1);
+ if (ret) {
+ dev_err(dev, "Failed to write trigger ret: %d", ret);
+ goto out;
+ }
+
+ /* integration time + 12.5 % to ensure completion */
+ fsleep(it_usec + it_usec / 8);
+
+ ret = veml6046x00_wait_data_available(iio, it_usec * 4);
+ if (ret < 0)
+ goto out;
+ if (ret == 0) {
+ ret = -EAGAIN;
+ goto out;
+ }
+
+ if (!iio_device_claim_direct(iio)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ret = regmap_bulk_read(data->regmap, addr, &reg, sizeof(reg));
+ iio_device_release_direct(iio);
+ if (ret)
+ goto out;
+
+ *val = le16_to_cpu(reg);
+
+ ret = IIO_VAL_INT;
+
+out:
+ pm_runtime_put_autosuspend(dev);
+
+ return ret;
+}
+
+static int veml6046x00_read_raw(struct iio_dev *iio,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct veml6046x00_data *data = iio_priv(iio);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (chan->type != IIO_INTENSITY)
+ return -EINVAL;
+ return veml6046x00_single_read(iio, chan->channel2, val);
+ case IIO_CHAN_INFO_INT_TIME:
+ *val = 0;
+ return veml6046x00_get_it_usec(data, val2);
+ case IIO_CHAN_INFO_SCALE:
+ return veml6046x00_get_scale(data, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int veml6046x00_read_avail(struct iio_dev *iio,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct veml6046x00_data *data = iio_priv(iio);
+ int it_idx;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_INT_TIME:
+ *vals = (int *)&veml6046x00_it;
+ *length = 2 * ARRAY_SIZE(veml6046x00_it);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_SCALE:
+ it_idx = veml6046x00_get_it_index(data);
+ if (it_idx < 0)
+ return it_idx;
+ *vals = (int *)&veml6046x00_it_gains[it_idx];
+ *length = 2 * ARRAY_SIZE(veml6046x00_it_gains[it_idx]);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int veml6046x00_write_raw(struct iio_dev *iio,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_INT_TIME:
+ return veml6046x00_set_it(iio, val, val2);
+ case IIO_CHAN_INFO_SCALE:
+ return veml6046x00_set_scale(iio, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info veml6046x00_info_no_irq = {
+ .read_raw = veml6046x00_read_raw,
+ .read_avail = veml6046x00_read_avail,
+ .write_raw = veml6046x00_write_raw,
+};
+
+static int veml6046x00_buffer_preenable(struct iio_dev *iio)
+{
+ struct veml6046x00_data *data = iio_priv(iio);
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+
+ ret = regmap_field_write(data->rf.mode, 0);
+ if (ret) {
+ dev_err(dev, "Failed to set mode %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_field_write(data->rf.trig, 0);
+ if (ret) {
+ /*
+ * no unrolling of mode as it is set appropriately with next
+ * single read.
+ */
+ dev_err(dev, "Failed to set trigger %d\n", ret);
+ return ret;
+ }
+
+ return pm_runtime_resume_and_get(dev);
+}
+
+static int veml6046x00_buffer_postdisable(struct iio_dev *iio)
+{
+ struct veml6046x00_data *data = iio_priv(iio);
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+
+ ret = regmap_field_write(data->rf.mode, 1);
+ if (ret) {
+ dev_err(dev, "Failed to set mode %d\n", ret);
+ return ret;
+ }
+
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops veml6046x00_buffer_setup_ops = {
+ .preenable = veml6046x00_buffer_preenable,
+ .postdisable = veml6046x00_buffer_postdisable,
+};
+
+static irqreturn_t veml6046x00_trig_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *iio = pf->indio_dev;
+ struct veml6046x00_data *data = iio_priv(iio);
+ int ret;
+ struct {
+ __le16 chans[4];
+ aligned_s64 timestamp;
+ } scan;
+
+ ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_R,
+ &scan.chans, sizeof(scan.chans));
+ if (ret)
+ goto done;
+
+ iio_push_to_buffers_with_ts(iio, &scan, sizeof(scan),
+ iio_get_time_ns(iio));
+
+done:
+ iio_trigger_notify_done(iio->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int veml6046x00_validate_part_id(struct veml6046x00_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ unsigned int part_id;
+ int ret;
+ __le16 reg;
+
+ ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_ID,
+ &reg, sizeof(reg));
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read ID\n");
+
+ part_id = le16_to_cpu(reg);
+ if (part_id != 0x01)
+ dev_info(dev, "Unknown ID %#04x\n", part_id);
+
+ return 0;
+}
+
+static int veml6046x00_setup_device(struct iio_dev *iio)
+{
+ struct veml6046x00_data *data = iio_priv(iio);
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+ __le16 reg16;
+
+ reg16 = cpu_to_le16(VEML6046X00_CONF0_AF);
+ ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_CONF0,
+ &reg16, sizeof(reg16));
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to set configuration\n");
+
+ reg16 = cpu_to_le16(0);
+ ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_THDL,
+ &reg16, sizeof(reg16));
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to set low threshold\n");
+
+ reg16 = cpu_to_le16(U16_MAX);
+ ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_THDH,
+ &reg16, sizeof(reg16));
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to set high threshold\n");
+
+ ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_INT,
+ &reg16, sizeof(reg16));
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to clear interrupts\n");
+
+ return 0;
+}
+
+static int veml6046x00_probe(struct i2c_client *i2c)
+{
+ struct device *dev = &i2c->dev;
+ struct veml6046x00_data *data;
+ struct iio_dev *iio;
+ struct regmap *regmap;
+ int ret;
+
+ regmap = devm_regmap_init_i2c(i2c, &veml6046x00_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to set regmap\n");
+
+ iio = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!iio)
+ return -ENOMEM;
+
+ data = iio_priv(iio);
+ /* struct iio_dev is retrieved via dev_get_drvdata(). */
+ i2c_set_clientdata(i2c, iio);
+ data->regmap = regmap;
+
+ ret = veml6046x00_regfield_init(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to init regfield\n");
+
+ ret = devm_regulator_get_enable(dev, "vdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable regulator\n");
+
+ /* bring device in a known state and switch device on */
+ ret = veml6046x00_setup_device(iio);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, veml6046x00_shutdown_action, data);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to add shut down action\n");
+
+ ret = pm_runtime_set_active(dev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to activate PM runtime\n");
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable PM runtime\n");
+
+ pm_runtime_get_noresume(dev);
+ pm_runtime_set_autosuspend_delay(dev, VEML6046X00_AUTOSUSPEND_MS);
+ pm_runtime_use_autosuspend(dev);
+
+ ret = veml6046x00_validate_part_id(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to validate device ID\n");
+
+ iio->name = "veml6046x00";
+ iio->channels = veml6046x00_channels;
+ iio->num_channels = ARRAY_SIZE(veml6046x00_channels);
+ iio->modes = INDIO_DIRECT_MODE;
+
+ iio->info = &veml6046x00_info_no_irq;
+
+ ret = devm_iio_triggered_buffer_setup(dev, iio, NULL,
+ veml6046x00_trig_handler,
+ &veml6046x00_buffer_setup_ops);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register triggered buffer");
+
+ pm_runtime_put_autosuspend(dev);
+
+ ret = devm_iio_device_register(dev, iio);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register iio device");
+
+ return 0;
+}
+
+static int veml6046x00_runtime_suspend(struct device *dev)
+{
+ struct veml6046x00_data *data = iio_priv(dev_get_drvdata(dev));
+
+ return veml6046x00_shutdown(data);
+}
+
+static int veml6046x00_runtime_resume(struct device *dev)
+{
+ struct veml6046x00_data *data = iio_priv(dev_get_drvdata(dev));
+
+ return veml6046x00_power_on(data);
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(veml6046x00_pm_ops,
+ veml6046x00_runtime_suspend,
+ veml6046x00_runtime_resume, NULL);
+
+static const struct of_device_id veml6046x00_of_match[] = {
+ { .compatible = "vishay,veml6046x00" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, veml6046x00_of_match);
+
+static const struct i2c_device_id veml6046x00_id[] = {
+ { "veml6046x00" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, veml6046x00_id);
+
+static struct i2c_driver veml6046x00_driver = {
+ .driver = {
+ .name = "veml6046x00",
+ .of_match_table = veml6046x00_of_match,
+ .pm = pm_ptr(&veml6046x00_pm_ops),
+ },
+ .probe = veml6046x00_probe,
+ .id_table = veml6046x00_id,
+};
+module_i2c_driver(veml6046x00_driver);
+
+MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>");
+MODULE_DESCRIPTION("VEML6046X00 RGBIR Color Sensor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/vl6180.c b/drivers/iio/light/vl6180.c
index cc4f2e5404aa..c1314b144367 100644
--- a/drivers/iio/light/vl6180.c
+++ b/drivers/iio/light/vl6180.c
@@ -96,11 +96,6 @@ struct vl6180_data {
unsigned int als_it_ms;
unsigned int als_meas_rate;
unsigned int range_meas_rate;
-
- struct {
- u16 chan[2];
- aligned_s64 timestamp;
- } scan;
};
enum { VL6180_ALS, VL6180_RANGE, VL6180_PROX };
@@ -545,6 +540,11 @@ static irqreturn_t vl6180_trigger_handler(int irq, void *priv)
struct vl6180_data *data = iio_priv(indio_dev);
s64 time_ns = iio_get_time_ns(indio_dev);
int ret, bit, i = 0;
+ struct {
+ u16 chan[2];
+ aligned_s64 timestamp;
+ } scan = { };
+
iio_for_each_active_channel(indio_dev, bit) {
if (vl6180_chan_regs_table[bit].word)
@@ -560,10 +560,10 @@ static irqreturn_t vl6180_trigger_handler(int irq, void *priv)
return IRQ_HANDLED;
}
- data->scan.chan[i++] = ret;
+ scan.chan[i++] = ret;
}
- iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, time_ns);
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns);
iio_trigger_notify_done(indio_dev->trig);
/* Clear the interrupt flag after data read */
@@ -722,7 +722,7 @@ static int vl6180_probe(struct i2c_client *client)
IRQF_ONESHOT,
indio_dev->name, indio_dev);
if (ret)
- return dev_err_probe(&client->dev, ret, "devm_request_irq error \n");
+ return dev_err_probe(&client->dev, ret, "devm_request_irq error\n");
init_completion(&data->completion);
diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig
index 3debf1320ad1..81b812a29044 100644
--- a/drivers/iio/magnetometer/Kconfig
+++ b/drivers/iio/magnetometer/Kconfig
@@ -123,7 +123,7 @@ config HID_SENSOR_MAGNETOMETER_3D
select IIO_BUFFER
select HID_SENSOR_IIO_COMMON
select HID_SENSOR_IIO_TRIGGER
- tristate "HID Magenetometer 3D"
+ tristate "HID Magnetometer 3D"
help
Say yes here to build support for the HID SENSOR
Magnetometer 3D.
@@ -173,6 +173,19 @@ config IIO_ST_MAGN_SPI_3AXIS
To compile this driver as a module, choose M here. The module
will be called st_magn_spi.
+config INFINEON_TLV493D
+ tristate "Infineon TLV493D Low-Power 3D Magnetic Sensor"
+ depends on I2C
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say Y here to add support for the Infineon TLV493D-A1B6 Low-
+ Power 3D Magnetic Sensor.
+
+ This driver can also be compiled as a module.
+ To compile this driver as a module, choose M here: the module
+ will be called tlv493d.
+
config SENSORS_HMC5843
tristate
select IIO_BUFFER
diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile
index 9297723a97d8..dfe970fcacb8 100644
--- a/drivers/iio/magnetometer/Makefile
+++ b/drivers/iio/magnetometer/Makefile
@@ -23,6 +23,8 @@ st_magn-$(CONFIG_IIO_BUFFER) += st_magn_buffer.o
obj-$(CONFIG_IIO_ST_MAGN_I2C_3AXIS) += st_magn_i2c.o
obj-$(CONFIG_IIO_ST_MAGN_SPI_3AXIS) += st_magn_spi.o
+obj-$(CONFIG_INFINEON_TLV493D) += tlv493d.o
+
obj-$(CONFIG_SENSORS_HMC5843) += hmc5843_core.o
obj-$(CONFIG_SENSORS_HMC5843_I2C) += hmc5843_i2c.o
obj-$(CONFIG_SENSORS_HMC5843_SPI) += hmc5843_spi.o
diff --git a/drivers/iio/magnetometer/ak8974.c b/drivers/iio/magnetometer/ak8974.c
index 947fe8a475f2..68ece700c7ce 100644
--- a/drivers/iio/magnetometer/ak8974.c
+++ b/drivers/iio/magnetometer/ak8974.c
@@ -583,7 +583,6 @@ static int ak8974_measure_channel(struct ak8974 *ak8974, unsigned long address,
*val = (s16)le16_to_cpu(hw_values[address]);
out_unlock:
mutex_unlock(&ak8974->lock);
- pm_runtime_mark_last_busy(&ak8974->i2c->dev);
pm_runtime_put_autosuspend(&ak8974->i2c->dev);
return ret;
@@ -678,7 +677,6 @@ static void ak8974_fill_buffer(struct iio_dev *indio_dev)
out_unlock:
mutex_unlock(&ak8974->lock);
- pm_runtime_mark_last_busy(&ak8974->i2c->dev);
pm_runtime_put_autosuspend(&ak8974->i2c->dev);
}
diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c
index a1e92b2abffd..3fd0171e5d69 100644
--- a/drivers/iio/magnetometer/ak8975.c
+++ b/drivers/iio/magnetometer/ak8975.c
@@ -775,7 +775,6 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val)
mutex_unlock(&data->lock);
- pm_runtime_mark_last_busy(&data->client->dev);
pm_runtime_put_autosuspend(&data->client->dev);
/* Swap bytes and convert to valid range. */
diff --git a/drivers/iio/magnetometer/als31300.c b/drivers/iio/magnetometer/als31300.c
index f72af829715f..2a2677428ed5 100644
--- a/drivers/iio/magnetometer/als31300.c
+++ b/drivers/iio/magnetometer/als31300.c
@@ -140,7 +140,6 @@ static int als31300_get_measure(struct als31300_data *data,
*z = ALS31300_DATA_Z_GET(buf);
out:
- pm_runtime_mark_last_busy(data->dev);
pm_runtime_put_autosuspend(data->dev);
return ret;
@@ -156,7 +155,6 @@ static int als31300_read_raw(struct iio_dev *indio_dev,
int ret;
switch (mask) {
- case IIO_CHAN_INFO_PROCESSED:
case IIO_CHAN_INFO_RAW:
ret = als31300_get_measure(data, &t, &x, &y, &z);
if (ret)
@@ -373,7 +371,7 @@ static int als31300_probe(struct i2c_client *i2c)
ret = devm_add_action_or_reset(dev, als31300_power_down, data);
if (ret)
- return dev_err_probe(dev, ret, "failed to add powerdown action\n");
+ return ret;
indio_dev->info = &als31300_info;
indio_dev->modes = INDIO_DIRECT_MODE;
@@ -401,7 +399,6 @@ static int als31300_probe(struct i2c_client *i2c)
pm_runtime_set_autosuspend_delay(dev, 200);
pm_runtime_use_autosuspend(dev);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
ret = devm_iio_device_register(dev, indio_dev);
diff --git a/drivers/iio/magnetometer/bmc150_magn.c b/drivers/iio/magnetometer/bmc150_magn.c
index 761daead5ada..6a73f6e2f1f0 100644
--- a/drivers/iio/magnetometer/bmc150_magn.c
+++ b/drivers/iio/magnetometer/bmc150_magn.c
@@ -257,22 +257,17 @@ static int bmc150_magn_set_power_mode(struct bmc150_magn_data *data,
static int bmc150_magn_set_power_state(struct bmc150_magn_data *data, bool on)
{
-#ifdef CONFIG_PM
- int ret;
+ int ret = 0;
- if (on) {
+ if (on)
ret = pm_runtime_resume_and_get(data->dev);
- } else {
- pm_runtime_mark_last_busy(data->dev);
- ret = pm_runtime_put_autosuspend(data->dev);
- }
-
+ else
+ pm_runtime_put_autosuspend(data->dev);
if (ret < 0) {
dev_err(data->dev,
"failed to change power state to %d\n", on);
return ret;
}
-#endif
return 0;
}
diff --git a/drivers/iio/magnetometer/tlv493d.c b/drivers/iio/magnetometer/tlv493d.c
new file mode 100644
index 000000000000..ec53fd40277b
--- /dev/null
+++ b/drivers/iio/magnetometer/tlv493d.c
@@ -0,0 +1,526 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the Infineon TLV493D Low-Power 3D Magnetic Sensor
+ *
+ * Copyright (C) 2025 Dixit Parmar <dixitparmar19@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/dev_printk.h>
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+/*
+ * TLV493D sensor I2C communication note:
+ *
+ * The sensor supports only direct byte-stream write starting from the
+ * register address 0x0. So for any modification to be made to any write
+ * registers, it must be written starting from the register address 0x0.
+ * I2C write operation should not contain the register address in the I2C
+ * frame, it should contain only raw byte stream for the write registers.
+ * I2C Frame: |S|SlaveAddr Wr|Ack|Byte[0]|Ack|Byte[1]|Ack|.....|Sp|
+ *
+ * Same as the write operation, reading from the sensor registers is also
+ * performed starting from the register address 0x0 for as many bytes as
+ * need to be read.
+ * I2C read operation should not contain the register address in the I2C frame.
+ * I2C Frame: |S|SlaveAddr Rd|Ack|Byte[0]|Ack|Byte[1]|Ack|.....|Sp|
+ */
+
+#define TLV493D_RD_REG_BX 0x00
+#define TLV493D_RD_REG_BY 0x01
+#define TLV493D_RD_REG_BZ 0x02
+#define TLV493D_RD_REG_TEMP 0x03
+#define TLV493D_RD_REG_BX2 0x04
+#define TLV493D_RD_REG_BZ2 0x05
+#define TLV493D_RD_REG_TEMP2 0x06
+#define TLV493D_RD_REG_RES1 0x07
+#define TLV493D_RD_REG_RES2 0x08
+#define TLV493D_RD_REG_RES3 0x09
+#define TLV493D_RD_REG_MAX 0x0a
+
+#define TLV493D_WR_REG_MODE1 0x01
+#define TLV493D_WR_REG_MODE2 0x03
+#define TLV493D_WR_REG_MAX 0x04
+
+#define TLV493D_BX_MAG_X_AXIS_MSB GENMASK(7, 0)
+#define TLV493D_BX2_MAG_X_AXIS_LSB GENMASK(7, 4)
+#define TLV493D_BY_MAG_Y_AXIS_MSB GENMASK(7, 0)
+#define TLV493D_BX2_MAG_Y_AXIS_LSB GENMASK(3, 0)
+#define TLV493D_BZ_MAG_Z_AXIS_MSB GENMASK(7, 0)
+#define TLV493D_BZ2_MAG_Z_AXIS_LSB GENMASK(3, 0)
+#define TLV493D_TEMP_TEMP_MSB GENMASK(7, 4)
+#define TLV493D_TEMP2_TEMP_LSB GENMASK(7, 0)
+#define TLV493D_TEMP_CHANNEL GENMASK(1, 0)
+#define TLV493D_MODE1_MOD_LOWFAST GENMASK(1, 0)
+#define TLV493D_MODE2_LP_PERIOD BIT(6)
+#define TLV493D_RD_REG_RES1_WR_MASK GENMASK(4, 3)
+#define TLV493D_RD_REG_RES2_WR_MASK GENMASK(7, 0)
+#define TLV493D_RD_REG_RES3_WR_MASK GENMASK(4, 0)
+
+enum tlv493d_channels {
+ TLV493D_AXIS_X,
+ TLV493D_AXIS_Y,
+ TLV493D_AXIS_Z,
+ TLV493D_TEMPERATURE,
+};
+
+enum tlv493d_op_mode {
+ TLV493D_OP_MODE_POWERDOWN,
+ TLV493D_OP_MODE_FAST,
+ TLV493D_OP_MODE_LOWPOWER,
+ TLV493D_OP_MODE_ULTRA_LOWPOWER,
+ TLV493D_OP_MODE_MASTERCONTROLLED,
+};
+
+struct tlv493d_data {
+ struct i2c_client *client;
+ /* protects from simultaneous sensor access and register readings */
+ struct mutex lock;
+ enum tlv493d_op_mode mode;
+ u8 wr_regs[TLV493D_WR_REG_MAX];
+};
+
+/*
+ * Different mode has different measurement sampling time, this time is
+ * used in deriving the sleep and timeout while reading the data from
+ * sensor in polling.
+ * Power-down mode: No measurement.
+ * Fast mode: Freq:3.3 KHz. Measurement time:305 usec.
+ * Low-power mode: Freq:100 Hz. Measurement time:10 msec.
+ * Ultra low-power mode: Freq:10 Hz. Measurement time:100 msec.
+ * Master controlled mode: Freq:3.3 Khz. Measurement time:305 usec.
+ */
+static const u32 tlv493d_sample_rate_us[] = {
+ [TLV493D_OP_MODE_POWERDOWN] = 0,
+ [TLV493D_OP_MODE_FAST] = 305,
+ [TLV493D_OP_MODE_LOWPOWER] = 10 * USEC_PER_MSEC,
+ [TLV493D_OP_MODE_ULTRA_LOWPOWER] = 100 * USEC_PER_MSEC,
+ [TLV493D_OP_MODE_MASTERCONTROLLED] = 305,
+};
+
+static int tlv493d_write_all_regs(struct tlv493d_data *data)
+{
+ int ret;
+ struct device *dev = &data->client->dev;
+
+ ret = i2c_master_send(data->client, data->wr_regs, ARRAY_SIZE(data->wr_regs));
+ if (ret < 0) {
+ dev_err(dev, "i2c write registers failed, error: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tlv493d_set_operating_mode(struct tlv493d_data *data, enum tlv493d_op_mode mode)
+{
+ u8 *mode1_cfg = &data->wr_regs[TLV493D_WR_REG_MODE1];
+ u8 *mode2_cfg = &data->wr_regs[TLV493D_WR_REG_MODE2];
+
+ switch (mode) {
+ case TLV493D_OP_MODE_POWERDOWN:
+ FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 0);
+ FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0);
+ break;
+
+ case TLV493D_OP_MODE_FAST:
+ FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 1);
+ FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0);
+ break;
+
+ case TLV493D_OP_MODE_LOWPOWER:
+ FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 2);
+ FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 1);
+ break;
+
+ case TLV493D_OP_MODE_ULTRA_LOWPOWER:
+ FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 2);
+ FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0);
+ break;
+
+ case TLV493D_OP_MODE_MASTERCONTROLLED:
+ FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 3);
+ FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0);
+ break;
+ }
+
+ return tlv493d_write_all_regs(data);
+}
+
+static s16 tlv493d_get_channel_data(u8 *b, enum tlv493d_channels ch)
+{
+ u16 val;
+
+ switch (ch) {
+ case TLV493D_AXIS_X:
+ val = FIELD_GET(TLV493D_BX_MAG_X_AXIS_MSB, b[TLV493D_RD_REG_BX]) << 4 |
+ FIELD_GET(TLV493D_BX2_MAG_X_AXIS_LSB, b[TLV493D_RD_REG_BX2]) >> 4;
+ break;
+ case TLV493D_AXIS_Y:
+ val = FIELD_GET(TLV493D_BY_MAG_Y_AXIS_MSB, b[TLV493D_RD_REG_BY]) << 4 |
+ FIELD_GET(TLV493D_BX2_MAG_Y_AXIS_LSB, b[TLV493D_RD_REG_BX2]);
+ break;
+ case TLV493D_AXIS_Z:
+ val = FIELD_GET(TLV493D_BZ_MAG_Z_AXIS_MSB, b[TLV493D_RD_REG_BZ]) << 4 |
+ FIELD_GET(TLV493D_BZ2_MAG_Z_AXIS_LSB, b[TLV493D_RD_REG_BZ2]);
+ break;
+ case TLV493D_TEMPERATURE:
+ val = FIELD_GET(TLV493D_TEMP_TEMP_MSB, b[TLV493D_RD_REG_TEMP]) << 8 |
+ FIELD_GET(TLV493D_TEMP2_TEMP_LSB, b[TLV493D_RD_REG_TEMP2]);
+ break;
+ }
+
+ return sign_extend32(val, 11);
+}
+
+static int tlv493d_get_measurements(struct tlv493d_data *data, s16 *x, s16 *y,
+ s16 *z, s16 *t)
+{
+ u8 buff[7] = {};
+ int err, ret;
+ struct device *dev = &data->client->dev;
+ u32 sleep_us = tlv493d_sample_rate_us[data->mode];
+
+ guard(mutex)(&data->lock);
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Poll until data is valid.
+ * For a valid data TLV493D_TEMP_CHANNEL bit of TLV493D_RD_REG_TEMP
+ * should be set to 0. The sampling time depends on the sensor mode.
+ * Poll 3x the time of the sampling time.
+ */
+ ret = read_poll_timeout(i2c_master_recv, err,
+ err || !FIELD_GET(TLV493D_TEMP_CHANNEL, buff[TLV493D_RD_REG_TEMP]),
+ sleep_us, 3 * sleep_us, false, data->client, buff,
+ ARRAY_SIZE(buff));
+ if (ret) {
+ dev_err(dev, "i2c read poll timeout, error:%d\n", ret);
+ goto out_put_autosuspend;
+ }
+ if (err < 0) {
+ dev_err(dev, "i2c read data failed, error:%d\n", err);
+ ret = err;
+ goto out_put_autosuspend;
+ }
+
+ *x = tlv493d_get_channel_data(buff, TLV493D_AXIS_X);
+ *y = tlv493d_get_channel_data(buff, TLV493D_AXIS_Y);
+ *z = tlv493d_get_channel_data(buff, TLV493D_AXIS_Z);
+ *t = tlv493d_get_channel_data(buff, TLV493D_TEMPERATURE);
+
+out_put_autosuspend:
+ pm_runtime_put_autosuspend(dev);
+ return ret;
+}
+
+static int tlv493d_init(struct tlv493d_data *data)
+{
+ int ret;
+ u8 buff[TLV493D_RD_REG_MAX];
+ struct device *dev = &data->client->dev;
+
+ /*
+ * The sensor initialization requires below steps to be followed,
+ * 1. Power-up sensor.
+ * 2. Read and store read-registers map (0x0-0x9).
+ * 3. Copy values from read reserved registers to write reserved fields
+ * (0x0-0x3).
+ * 4. Set operating mode.
+ * 5. Write to all registers.
+ */
+ ret = i2c_master_recv(data->client, buff, ARRAY_SIZE(buff));
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "i2c read failed\n");
+
+ /* Write register 0x0 is reserved. Does not require to be updated.*/
+ data->wr_regs[0] = 0;
+ data->wr_regs[1] = buff[TLV493D_RD_REG_RES1] & TLV493D_RD_REG_RES1_WR_MASK;
+ data->wr_regs[2] = buff[TLV493D_RD_REG_RES2] & TLV493D_RD_REG_RES2_WR_MASK;
+ data->wr_regs[3] = buff[TLV493D_RD_REG_RES3] & TLV493D_RD_REG_RES3_WR_MASK;
+
+ ret = tlv493d_set_operating_mode(data, data->mode);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to set operating mode\n");
+
+ return 0;
+}
+
+static int tlv493d_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan, int *val,
+ int *val2, long mask)
+{
+ struct tlv493d_data *data = iio_priv(indio_dev);
+ s16 x, y, z, t;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = tlv493d_get_measurements(data, &x, &y, &z, &t);
+ if (ret)
+ return ret;
+
+ switch (chan->address) {
+ case TLV493D_AXIS_X:
+ *val = x;
+ return IIO_VAL_INT;
+ case TLV493D_AXIS_Y:
+ *val = y;
+ return IIO_VAL_INT;
+ case TLV493D_AXIS_Z:
+ *val = z;
+ return IIO_VAL_INT;
+ case TLV493D_TEMPERATURE:
+ *val = t;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_MAGN:
+ /*
+ * Magnetic field scale: 0.0098 mTesla (i.e. 9.8 µT)
+ * Magnetic field in Gauss: mT * 10 = 0.098.
+ */
+ *val = 98;
+ *val2 = 1000;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_TEMP:
+ /*
+ * Temperature scale: 1.1 °C per LSB, expressed as 1100 m°C
+ * Returned as integer for IIO core to apply:
+ * temp = (raw + offset) * scale
+ */
+ *val = 1100;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ switch (chan->type) {
+ case IIO_TEMP:
+ /*
+ * Temperature offset includes sensor-specific raw offset
+ * plus compensation for +25°C bias in formula.
+ * offset = -raw_offset + (25000 / 1100)
+ * -340 + 22.72 = -317.28
+ */
+ *val = -31728;
+ *val2 = 100;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static irqreturn_t tlv493d_trigger_handler(int irq, void *ptr)
+{
+ int ret;
+ s16 x, y, z, t;
+ struct iio_poll_func *pf = ptr;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct tlv493d_data *data = iio_priv(indio_dev);
+ struct device *dev = &data->client->dev;
+ struct {
+ s16 channels[3];
+ s16 temperature;
+ aligned_s64 timestamp;
+ } scan;
+
+ ret = tlv493d_get_measurements(data, &x, &y, &z, &t);
+ if (ret) {
+ dev_err(dev, "failed to read sensor data\n");
+ goto out_trigger_notify;
+ }
+
+ scan.channels[0] = x;
+ scan.channels[1] = y;
+ scan.channels[2] = z;
+ scan.temperature = t;
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp);
+
+out_trigger_notify:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+#define TLV493D_AXIS_CHANNEL(axis, index) \
+ { \
+ .type = IIO_MAGN, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .address = index, \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 12, \
+ .storagebits = 16, \
+ .endianness = IIO_CPU, \
+ }, \
+ }
+
+static const struct iio_chan_spec tlv493d_channels[] = {
+ TLV493D_AXIS_CHANNEL(X, TLV493D_AXIS_X),
+ TLV493D_AXIS_CHANNEL(Y, TLV493D_AXIS_Y),
+ TLV493D_AXIS_CHANNEL(Z, TLV493D_AXIS_Z),
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_OFFSET),
+ .address = TLV493D_TEMPERATURE,
+ .scan_index = TLV493D_TEMPERATURE,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 12,
+ .storagebits = 16,
+ .endianness = IIO_CPU,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static const struct iio_info tlv493d_info = {
+ .read_raw = tlv493d_read_raw,
+};
+
+static const unsigned long tlv493d_scan_masks[] = { GENMASK(3, 0), 0 };
+
+static int tlv493d_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct iio_dev *indio_dev;
+ struct tlv493d_data *data;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ i2c_set_clientdata(client, indio_dev);
+
+ ret = devm_mutex_init(dev, &data->lock);
+ if (ret)
+ return ret;
+
+ ret = devm_regulator_get_enable(dev, "vdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable regulator\n");
+
+ /*
+ * Setting Sensor default operating mode to Master-Controlled mode since
+ * it performs measurement cycle only on-request and stays in Power-Down
+ * state until next cycle is initiated.
+ */
+ data->mode = TLV493D_OP_MODE_MASTERCONTROLLED;
+ ret = tlv493d_init(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to initialize\n");
+
+ indio_dev->info = &tlv493d_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->name = client->name;
+ indio_dev->channels = tlv493d_channels;
+ indio_dev->num_channels = ARRAY_SIZE(tlv493d_channels);
+ indio_dev->available_scan_masks = tlv493d_scan_masks;
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ tlv493d_trigger_handler,
+ NULL);
+ if (ret)
+ return dev_err_probe(dev, ret, "iio triggered buffer setup failed\n");
+
+ ret = pm_runtime_set_active(dev);
+ if (ret)
+ return ret;
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return ret;
+
+ pm_runtime_get_noresume(dev);
+ pm_runtime_set_autosuspend_delay(dev, 500);
+ pm_runtime_use_autosuspend(dev);
+
+ pm_runtime_put_autosuspend(dev);
+
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "iio device register failed\n");
+
+ return 0;
+}
+
+static int tlv493d_runtime_suspend(struct device *dev)
+{
+ struct tlv493d_data *data = iio_priv(dev_get_drvdata(dev));
+
+ return tlv493d_set_operating_mode(data, TLV493D_OP_MODE_POWERDOWN);
+}
+
+static int tlv493d_runtime_resume(struct device *dev)
+{
+ struct tlv493d_data *data = iio_priv(dev_get_drvdata(dev));
+
+ return tlv493d_set_operating_mode(data, data->mode);
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(tlv493d_pm_ops, tlv493d_runtime_suspend,
+ tlv493d_runtime_resume, NULL);
+
+static const struct i2c_device_id tlv493d_id[] = {
+ { "tlv493d" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tlv493d_id);
+
+static const struct of_device_id tlv493d_of_match[] = {
+ { .compatible = "infineon,tlv493d-a1b6" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tlv493d_of_match);
+
+static struct i2c_driver tlv493d_driver = {
+ .driver = {
+ .name = "tlv493d",
+ .of_match_table = tlv493d_of_match,
+ .pm = pm_ptr(&tlv493d_pm_ops),
+ },
+ .probe = tlv493d_probe,
+ .id_table = tlv493d_id,
+};
+module_i2c_driver(tlv493d_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Infineon TLV493D Low-Power 3D Magnetic Sensor");
+MODULE_AUTHOR("Dixit Parmar <dixitparmar19@gmail.com>");
diff --git a/drivers/iio/magnetometer/tmag5273.c b/drivers/iio/magnetometer/tmag5273.c
index 2ca5c26f0091..2adc3c036ab4 100644
--- a/drivers/iio/magnetometer/tmag5273.c
+++ b/drivers/iio/magnetometer/tmag5273.c
@@ -287,7 +287,6 @@ static int tmag5273_read_raw(struct iio_dev *indio_dev,
int ret;
switch (mask) {
- case IIO_CHAN_INFO_PROCESSED:
case IIO_CHAN_INFO_RAW:
ret = pm_runtime_resume_and_get(data->dev);
if (ret < 0)
@@ -295,7 +294,6 @@ static int tmag5273_read_raw(struct iio_dev *indio_dev,
ret = tmag5273_get_measure(data, &t, &x, &y, &z, &angle, &magnitude);
- pm_runtime_mark_last_busy(data->dev);
pm_runtime_put_autosuspend(data->dev);
if (ret)
@@ -642,7 +640,7 @@ static int tmag5273_probe(struct i2c_client *i2c)
*/
ret = devm_add_action_or_reset(dev, tmag5273_power_down, data);
if (ret)
- return dev_err_probe(dev, ret, "failed to add powerdown action\n");
+ return ret;
ret = pm_runtime_set_active(dev);
if (ret < 0)
@@ -668,7 +666,6 @@ static int tmag5273_probe(struct i2c_client *i2c)
indio_dev->channels = tmag5273_channels;
indio_dev->num_channels = ARRAY_SIZE(tmag5273_channels);
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
ret = devm_iio_device_register(dev, indio_dev);
diff --git a/drivers/iio/magnetometer/yamaha-yas530.c b/drivers/iio/magnetometer/yamaha-yas530.c
index 340607111d9a..d49e37edcbed 100644
--- a/drivers/iio/magnetometer/yamaha-yas530.c
+++ b/drivers/iio/magnetometer/yamaha-yas530.c
@@ -623,7 +623,6 @@ static int yas5xx_read_raw(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_RAW:
pm_runtime_get_sync(yas5xx->dev);
ret = ci->get_measure(yas5xx, &t, &x, &y, &z);
- pm_runtime_mark_last_busy(yas5xx->dev);
pm_runtime_put_autosuspend(yas5xx->dev);
if (ret)
return ret;
@@ -664,7 +663,6 @@ static void yas5xx_fill_buffer(struct iio_dev *indio_dev)
pm_runtime_get_sync(yas5xx->dev);
ret = ci->get_measure(yas5xx, &t, &x, &y, &z);
- pm_runtime_mark_last_busy(yas5xx->dev);
pm_runtime_put_autosuspend(yas5xx->dev);
if (ret) {
dev_err(yas5xx->dev, "error refilling buffer\n");
diff --git a/drivers/iio/potentiostat/lmp91000.c b/drivers/iio/potentiostat/lmp91000.c
index 030498d0b763..eccc2a34358f 100644
--- a/drivers/iio/potentiostat/lmp91000.c
+++ b/drivers/iio/potentiostat/lmp91000.c
@@ -321,10 +321,8 @@ static int lmp91000_probe(struct i2c_client *client)
data->trig = devm_iio_trigger_alloc(dev, "%s-mux%d",
indio_dev->name,
iio_device_id(indio_dev));
- if (!data->trig) {
- dev_err(dev, "cannot allocate iio trigger.\n");
+ if (!data->trig)
return -ENOMEM;
- }
init_completion(&data->completion);
diff --git a/drivers/iio/pressure/bmp280-core.c b/drivers/iio/pressure/bmp280-core.c
index 6cdc8ed53520..c04e8bb4c993 100644
--- a/drivers/iio/pressure/bmp280-core.c
+++ b/drivers/iio/pressure/bmp280-core.c
@@ -752,7 +752,6 @@ static int bmp280_read_raw(struct iio_dev *indio_dev,
pm_runtime_get_sync(data->dev);
ret = bmp280_read_raw_impl(indio_dev, chan, val, val2, mask);
- pm_runtime_mark_last_busy(data->dev);
pm_runtime_put_autosuspend(data->dev);
return ret;
@@ -927,7 +926,6 @@ static int bmp280_write_raw(struct iio_dev *indio_dev,
pm_runtime_get_sync(data->dev);
ret = bmp280_write_raw_impl(indio_dev, chan, val, val2, mask);
- pm_runtime_mark_last_busy(data->dev);
pm_runtime_put_autosuspend(data->dev);
return ret;
@@ -2255,7 +2253,6 @@ static int bmp580_nvmem_read(void *priv, unsigned int offset, void *val,
pm_runtime_get_sync(data->dev);
ret = bmp580_nvmem_read_impl(priv, offset, val, bytes);
- pm_runtime_mark_last_busy(data->dev);
pm_runtime_put_autosuspend(data->dev);
return ret;
@@ -2330,7 +2327,6 @@ static int bmp580_nvmem_write(void *priv, unsigned int offset, void *val,
pm_runtime_get_sync(data->dev);
ret = bmp580_nvmem_write_impl(priv, offset, val, bytes);
- pm_runtime_mark_last_busy(data->dev);
pm_runtime_put_autosuspend(data->dev);
return ret;
@@ -3120,7 +3116,6 @@ static int bmp280_buffer_postdisable(struct iio_dev *indio_dev)
{
struct bmp280_data *data = iio_priv(indio_dev);
- pm_runtime_mark_last_busy(data->dev);
pm_runtime_put_autosuspend(data->dev);
return 0;
@@ -3217,8 +3212,7 @@ int bmp280_common_probe(struct device *dev,
return dev_err_probe(dev, PTR_ERR(gpiod), "failed to get reset GPIO\n");
/* Deassert the signal */
- dev_info(dev, "release reset\n");
- gpiod_set_value(gpiod, 0);
+ gpiod_set_value_cansleep(gpiod, 0);
data->regmap = regmap;
diff --git a/drivers/iio/pressure/dlhl60d.c b/drivers/iio/pressure/dlhl60d.c
index 6a13cf2eaf50..8bad7162fec6 100644
--- a/drivers/iio/pressure/dlhl60d.c
+++ b/drivers/iio/pressure/dlhl60d.c
@@ -289,10 +289,8 @@ static int dlh_probe(struct i2c_client *client)
}
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st));
- if (!indio_dev) {
- dev_err(&client->dev, "failed to allocate iio device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
i2c_set_clientdata(client, indio_dev);
diff --git a/drivers/iio/pressure/icp10100.c b/drivers/iio/pressure/icp10100.c
index 1951c1cc84cf..3d83d0098a57 100644
--- a/drivers/iio/pressure/icp10100.c
+++ b/drivers/iio/pressure/icp10100.c
@@ -265,7 +265,6 @@ static int icp10100_get_measures(struct icp10100_state *st,
(be16_to_cpu(measures[1]) >> 8);
*temperature = be16_to_cpu(measures[2]);
- pm_runtime_mark_last_busy(&st->client->dev);
error_measure:
pm_runtime_put_autosuspend(&st->client->dev);
return ret;
diff --git a/drivers/iio/pressure/mpl115.c b/drivers/iio/pressure/mpl115.c
index 71beb28b7f2c..830a5065c008 100644
--- a/drivers/iio/pressure/mpl115.c
+++ b/drivers/iio/pressure/mpl115.c
@@ -108,7 +108,6 @@ static int mpl115_read_raw(struct iio_dev *indio_dev,
ret = mpl115_comp_pressure(data, val, val2);
if (ret < 0)
return ret;
- pm_runtime_mark_last_busy(data->dev);
pm_runtime_put_autosuspend(data->dev);
return IIO_VAL_INT_PLUS_MICRO;
@@ -118,7 +117,6 @@ static int mpl115_read_raw(struct iio_dev *indio_dev,
ret = mpl115_read_temp(data);
if (ret < 0)
return ret;
- pm_runtime_mark_last_busy(data->dev);
pm_runtime_put_autosuspend(data->dev);
*val = ret >> 6;
diff --git a/drivers/iio/pressure/zpa2326.c b/drivers/iio/pressure/zpa2326.c
index 6eef37c0952d..4923a558a26a 100644
--- a/drivers/iio/pressure/zpa2326.c
+++ b/drivers/iio/pressure/zpa2326.c
@@ -697,7 +697,6 @@ static void zpa2326_suspend(struct iio_dev *indio_dev)
zpa2326_sleep(indio_dev);
- pm_runtime_mark_last_busy(parent);
pm_runtime_put_autosuspend(parent);
}
@@ -708,7 +707,6 @@ static void zpa2326_init_runtime(struct device *parent)
pm_runtime_enable(parent);
pm_runtime_set_autosuspend_delay(parent, 1000);
pm_runtime_use_autosuspend(parent);
- pm_runtime_mark_last_busy(parent);
pm_runtime_put_autosuspend(parent);
}
diff --git a/drivers/iio/proximity/d3323aa.c b/drivers/iio/proximity/d3323aa.c
index d4c3dbea9bb0..30821f583454 100644
--- a/drivers/iio/proximity/d3323aa.c
+++ b/drivers/iio/proximity/d3323aa.c
@@ -722,8 +722,7 @@ static int d3323aa_probe(struct platform_device *pdev)
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
- return dev_err_probe(dev, -ENOMEM,
- "Could not allocate iio device\n");
+ return -ENOMEM;
data = iio_priv(indio_dev);
data->dev = dev;
diff --git a/drivers/iio/proximity/hx9023s.c b/drivers/iio/proximity/hx9023s.c
index 33781c314728..2918dfc0df54 100644
--- a/drivers/iio/proximity/hx9023s.c
+++ b/drivers/iio/proximity/hx9023s.c
@@ -1141,8 +1141,7 @@ static int hx9023s_probe(struct i2c_client *client)
indio_dev->name,
iio_device_id(indio_dev));
if (!data->trig)
- return dev_err_probe(dev, -ENOMEM,
- "iio trigger alloc failed\n");
+ return -ENOMEM;
data->trig->ops = &hx9023s_trigger_ops;
iio_trigger_set_drvdata(data->trig, indio_dev);
diff --git a/drivers/iio/proximity/irsd200.c b/drivers/iio/proximity/irsd200.c
index 253e4aef22fb..65af31d43453 100644
--- a/drivers/iio/proximity/irsd200.c
+++ b/drivers/iio/proximity/irsd200.c
@@ -862,8 +862,7 @@ static int irsd200_probe(struct i2c_client *client)
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
- return dev_err_probe(&client->dev, -ENOMEM,
- "Could not allocate iio device\n");
+ return -ENOMEM;
data = iio_priv(indio_dev);
data->dev = &client->dev;
@@ -916,8 +915,7 @@ static int irsd200_probe(struct i2c_client *client)
trigger = devm_iio_trigger_alloc(data->dev, "%s-dev%d", indio_dev->name,
iio_device_id(indio_dev));
if (!trigger)
- return dev_err_probe(data->dev, -ENOMEM,
- "Could not allocate iio trigger\n");
+ return -ENOMEM;
trigger->ops = &irsd200_trigger_ops;
iio_trigger_set_drvdata(trigger, data);
diff --git a/drivers/iio/proximity/mb1232.c b/drivers/iio/proximity/mb1232.c
index 01783486bc7d..34b49c54e68b 100644
--- a/drivers/iio/proximity/mb1232.c
+++ b/drivers/iio/proximity/mb1232.c
@@ -42,11 +42,6 @@ struct mb1232_data {
*/
struct completion ranging;
int irqnr;
- /* Ensure correct alignment of data to push to IIO buffer */
- struct {
- s16 distance;
- aligned_s64 ts;
- } scan;
};
static irqreturn_t mb1232_handle_irq(int irq, void *dev_id)
@@ -120,12 +115,16 @@ static irqreturn_t mb1232_trigger_handler(int irq, void *p)
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct mb1232_data *data = iio_priv(indio_dev);
+ struct {
+ s16 distance;
+ aligned_s64 ts;
+ } scan = { };
- data->scan.distance = mb1232_read_distance(data);
- if (data->scan.distance < 0)
+ scan.distance = mb1232_read_distance(data);
+ if (scan.distance < 0)
goto err;
- iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
pf->timestamp);
err:
diff --git a/drivers/iio/proximity/ping.c b/drivers/iio/proximity/ping.c
index c5b4e1378b7d..e3487094d7be 100644
--- a/drivers/iio/proximity/ping.c
+++ b/drivers/iio/proximity/ping.c
@@ -280,10 +280,8 @@ static int ping_probe(struct platform_device *pdev)
struct iio_dev *indio_dev;
indio_dev = devm_iio_device_alloc(dev, sizeof(struct ping_data));
- if (!indio_dev) {
- dev_err(dev, "failed to allocate IIO device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
data = iio_priv(indio_dev);
data->dev = dev;
diff --git a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c
index 1deaf70e92ce..21336b8f122a 100644
--- a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c
+++ b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c
@@ -43,12 +43,6 @@ struct lidar_data {
int (*xfer)(struct lidar_data *data, u8 reg, u8 *val, int len);
int i2c_enabled;
-
- /* Ensure timestamp is naturally aligned */
- struct {
- u16 chan;
- aligned_s64 timestamp;
- } scan;
};
static const struct iio_chan_spec lidar_channels[] = {
@@ -191,7 +185,6 @@ static int lidar_get_measurement(struct lidar_data *data, u16 *reg)
}
ret = -EIO;
}
- pm_runtime_mark_last_busy(&client->dev);
pm_runtime_put_autosuspend(&client->dev);
return ret;
@@ -235,11 +228,14 @@ static irqreturn_t lidar_trigger_handler(int irq, void *private)
struct iio_dev *indio_dev = pf->indio_dev;
struct lidar_data *data = iio_priv(indio_dev);
int ret;
+ struct {
+ u16 chan;
+ aligned_s64 timestamp;
+ } scan = { };
- ret = lidar_get_measurement(data, &data->scan.chan);
+ ret = lidar_get_measurement(data, &scan.chan);
if (!ret) {
- iio_push_to_buffers_with_ts(indio_dev, &data->scan,
- sizeof(data->scan),
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
iio_get_time_ns(indio_dev));
} else if (ret != -EINVAL) {
dev_err(&data->client->dev, "cannot read LIDAR measurement");
diff --git a/drivers/iio/proximity/srf04.c b/drivers/iio/proximity/srf04.c
index b059bac1078b..e97f9a20ac7a 100644
--- a/drivers/iio/proximity/srf04.c
+++ b/drivers/iio/proximity/srf04.c
@@ -117,10 +117,8 @@ static int srf04_read(struct srf04_data *data)
udelay(data->cfg->trigger_pulse_us);
gpiod_set_value(data->gpiod_trig, 0);
- if (data->gpiod_power) {
- pm_runtime_mark_last_busy(data->dev);
+ if (data->gpiod_power)
pm_runtime_put_autosuspend(data->dev);
- }
/* it should not take more than 20 ms until echo is rising */
ret = wait_for_completion_killable_timeout(&data->rising, HZ/50);
@@ -253,10 +251,8 @@ static int srf04_probe(struct platform_device *pdev)
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(struct srf04_data));
- if (!indio_dev) {
- dev_err(dev, "failed to allocate IIO device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
data = iio_priv(indio_dev);
data->dev = dev;
diff --git a/drivers/iio/proximity/srf08.c b/drivers/iio/proximity/srf08.c
index 6e32fdfd161b..d7e4cc48cfbf 100644
--- a/drivers/iio/proximity/srf08.c
+++ b/drivers/iio/proximity/srf08.c
@@ -63,12 +63,6 @@ struct srf08_data {
int range_mm;
struct mutex lock;
- /* Ensure timestamp is naturally aligned */
- struct {
- s16 chan;
- aligned_s64 timestamp;
- } scan;
-
/* Sensor-Type */
enum srf08_sensor_type sensor_type;
@@ -182,16 +176,18 @@ static irqreturn_t srf08_trigger_handler(int irq, void *p)
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct srf08_data *data = iio_priv(indio_dev);
- s16 sensor_data;
+ struct {
+ s16 chan;
+ aligned_s64 timestamp;
+ } scan = { };
- sensor_data = srf08_read_ranging(data);
- if (sensor_data < 0)
+ scan.chan = srf08_read_ranging(data);
+ if (scan.chan < 0)
goto err;
mutex_lock(&data->lock);
- data->scan.chan = sensor_data;
- iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
pf->timestamp);
mutex_unlock(&data->lock);
diff --git a/drivers/iio/proximity/sx9500.c b/drivers/iio/proximity/sx9500.c
index 05844f17a15f..6c67bae7488c 100644
--- a/drivers/iio/proximity/sx9500.c
+++ b/drivers/iio/proximity/sx9500.c
@@ -88,7 +88,6 @@ struct sx9500_data {
bool prox_stat[SX9500_NUM_CHANNELS];
bool event_enabled[SX9500_NUM_CHANNELS];
bool trigger_enabled;
- u16 *buffer;
/* Remember enabled channels and sample rate during suspend. */
unsigned int suspend_ctrl0;
struct completion completion;
@@ -578,22 +577,6 @@ out_unlock:
return ret;
}
-static int sx9500_update_scan_mode(struct iio_dev *indio_dev,
- const unsigned long *scan_mask)
-{
- struct sx9500_data *data = iio_priv(indio_dev);
-
- mutex_lock(&data->mutex);
- kfree(data->buffer);
- data->buffer = kzalloc(indio_dev->scan_bytes, GFP_KERNEL);
- mutex_unlock(&data->mutex);
-
- if (data->buffer == NULL)
- return -ENOMEM;
-
- return 0;
-}
-
static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(
"2.500000 3.333333 5 6.666666 8.333333 11.111111 16.666666 33.333333");
@@ -612,7 +595,6 @@ static const struct iio_info sx9500_info = {
.write_raw = &sx9500_write_raw,
.read_event_config = &sx9500_read_event_config,
.write_event_config = &sx9500_write_event_config,
- .update_scan_mode = &sx9500_update_scan_mode,
};
static int sx9500_set_trigger_state(struct iio_trigger *trig,
@@ -649,6 +631,10 @@ static irqreturn_t sx9500_trigger_handler(int irq, void *private)
struct iio_dev *indio_dev = pf->indio_dev;
struct sx9500_data *data = iio_priv(indio_dev);
int val, bit, ret, i = 0;
+ struct {
+ u16 chan[SX9500_NUM_CHANNELS];
+ aligned_s64 timestamp;
+ } scan = { };
mutex_lock(&data->mutex);
@@ -658,10 +644,10 @@ static irqreturn_t sx9500_trigger_handler(int irq, void *private)
if (ret < 0)
goto out;
- data->buffer[i++] = val;
+ scan.chan[i++] = val;
}
- iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
+ iio_push_to_buffers_with_timestamp(indio_dev, &scan,
iio_get_time_ns(indio_dev));
out:
@@ -984,7 +970,6 @@ static void sx9500_remove(struct i2c_client *client)
iio_triggered_buffer_cleanup(indio_dev);
if (client->irq > 0)
iio_trigger_unregister(data->trig);
- kfree(data->buffer);
}
static int sx9500_suspend(struct device *dev)
diff --git a/drivers/iio/proximity/vl53l0x-i2c.c b/drivers/iio/proximity/vl53l0x-i2c.c
index ef4aa7b2835e..ad3e46d47fa8 100644
--- a/drivers/iio/proximity/vl53l0x-i2c.c
+++ b/drivers/iio/proximity/vl53l0x-i2c.c
@@ -57,11 +57,6 @@ struct vl53l0x_data {
struct regulator *vdd_supply;
struct gpio_desc *reset_gpio;
struct iio_trigger *trig;
-
- struct {
- u16 chan;
- aligned_s64 timestamp;
- } scan;
};
static int vl53l0x_clear_irq(struct vl53l0x_data *data)
@@ -84,6 +79,10 @@ static irqreturn_t vl53l0x_trigger_handler(int irq, void *priv)
struct vl53l0x_data *data = iio_priv(indio_dev);
u8 buffer[12];
int ret;
+ struct {
+ u16 chan;
+ aligned_s64 timestamp;
+ } scan = { };
ret = i2c_smbus_read_i2c_block_data(data->client,
VL_REG_RESULT_RANGE_STATUS,
@@ -93,8 +92,8 @@ static irqreturn_t vl53l0x_trigger_handler(int irq, void *priv)
else if (ret != 12)
return -EREMOTEIO;
- data->scan.chan = get_unaligned_be16(&buffer[10]);
- iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
+ scan.chan = get_unaligned_be16(&buffer[10]);
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
iio_get_time_ns(indio_dev));
iio_trigger_notify_done(indio_dev->trig);
@@ -312,7 +311,6 @@ static int vl53l0x_probe(struct i2c_client *client)
{
struct vl53l0x_data *data;
struct iio_dev *indio_dev;
- int error;
int ret;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
@@ -345,15 +343,14 @@ static int vl53l0x_probe(struct i2c_client *client)
return dev_err_probe(&client->dev, PTR_ERR(data->reset_gpio),
"Cannot get reset GPIO\n");
- error = vl53l0x_power_on(data);
- if (error)
- return dev_err_probe(&client->dev, error,
+ ret = vl53l0x_power_on(data);
+ if (ret)
+ return dev_err_probe(&client->dev, ret,
"Failed to power on the chip\n");
- error = devm_add_action_or_reset(&client->dev, vl53l0x_power_off, data);
- if (error)
- return dev_err_probe(&client->dev, error,
- "Failed to install poweroff action\n");
+ ret = devm_add_action_or_reset(&client->dev, vl53l0x_power_off, data);
+ if (ret)
+ return ret;
indio_dev->name = "vl53l0x";
indio_dev->info = &vl53l0x_info;
diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig
index 1244d8e17d50..9328b2250ace 100644
--- a/drivers/iio/temperature/Kconfig
+++ b/drivers/iio/temperature/Kconfig
@@ -173,11 +173,13 @@ config MAX31865
will be called max31865.
config MCP9600
- tristate "MCP9600 thermocouple EMF converter"
+ tristate "MCP9600 and similar thermocouple EMF converters"
depends on I2C
help
- If you say yes here you get support for MCP9600
- thermocouple EMF converter connected via I2C.
+ If you say yes here you get support for...
+ - MCP9600
+ - MCP9601
+ ...thermocouple EMF converters connected via I2C.
This driver can also be built as a module. If so, the module
will be called mcp9600.
diff --git a/drivers/iio/temperature/mcp9600.c b/drivers/iio/temperature/mcp9600.c
index 6e9108d5cf75..aa42c2b1a369 100644
--- a/drivers/iio/temperature/mcp9600.c
+++ b/drivers/iio/temperature/mcp9600.c
@@ -22,26 +22,31 @@
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
+#include <dt-bindings/iio/temperature/thermocouple.h>
+
/* MCP9600 registers */
-#define MCP9600_HOT_JUNCTION 0x0
-#define MCP9600_COLD_JUNCTION 0x2
-#define MCP9600_STATUS 0x4
+#define MCP9600_HOT_JUNCTION 0x00
+#define MCP9600_COLD_JUNCTION 0x02
+#define MCP9600_STATUS 0x04
#define MCP9600_STATUS_ALERT(x) BIT(x)
-#define MCP9600_ALERT_CFG1 0x8
+#define MCP9600_SENSOR_CFG 0x05
+#define MCP9600_SENSOR_TYPE_MASK GENMASK(6, 4)
+#define MCP9600_ALERT_CFG1 0x08
#define MCP9600_ALERT_CFG(x) (MCP9600_ALERT_CFG1 + (x - 1))
#define MCP9600_ALERT_CFG_ENABLE BIT(0)
#define MCP9600_ALERT_CFG_ACTIVE_HIGH BIT(2)
#define MCP9600_ALERT_CFG_FALLING BIT(3)
#define MCP9600_ALERT_CFG_COLD_JUNCTION BIT(4)
-#define MCP9600_ALERT_HYSTERESIS1 0xc
+#define MCP9600_ALERT_HYSTERESIS1 0x0c
#define MCP9600_ALERT_HYSTERESIS(x) (MCP9600_ALERT_HYSTERESIS1 + (x - 1))
#define MCP9600_ALERT_LIMIT1 0x10
#define MCP9600_ALERT_LIMIT(x) (MCP9600_ALERT_LIMIT1 + (x - 1))
#define MCP9600_ALERT_LIMIT_MASK GENMASK(15, 2)
-#define MCP9600_DEVICE_ID 0x20
+#define MCP9600_DEVICE_ID 0x20
/* MCP9600 device id value */
-#define MCP9600_DEVICE_ID_MCP9600 0x40
+#define MCP9600_DEVICE_ID_MCP9600 0x40
+#define MCP9600_DEVICE_ID_MCP9601 0x41
#define MCP9600_ALERT_COUNT 4
@@ -65,6 +70,30 @@ static const char * const mcp9600_alert_name[MCP9600_ALERT_COUNT] = {
[MCP9600_ALERT4] = "alert4",
};
+/* Map between dt-bindings enum and the chip's type value */
+static const unsigned int mcp9600_type_map[] = {
+ [THERMOCOUPLE_TYPE_K] = 0,
+ [THERMOCOUPLE_TYPE_J] = 1,
+ [THERMOCOUPLE_TYPE_T] = 2,
+ [THERMOCOUPLE_TYPE_N] = 3,
+ [THERMOCOUPLE_TYPE_S] = 4,
+ [THERMOCOUPLE_TYPE_E] = 5,
+ [THERMOCOUPLE_TYPE_B] = 6,
+ [THERMOCOUPLE_TYPE_R] = 7,
+};
+
+/* Map thermocouple type to a char for iio info in sysfs */
+static const int mcp9600_tc_types[] = {
+ [THERMOCOUPLE_TYPE_K] = 'K',
+ [THERMOCOUPLE_TYPE_J] = 'J',
+ [THERMOCOUPLE_TYPE_T] = 'T',
+ [THERMOCOUPLE_TYPE_N] = 'N',
+ [THERMOCOUPLE_TYPE_S] = 'S',
+ [THERMOCOUPLE_TYPE_E] = 'E',
+ [THERMOCOUPLE_TYPE_B] = 'B',
+ [THERMOCOUPLE_TYPE_R] = 'R',
+};
+
static const struct iio_event_spec mcp9600_events[] = {
{
.type = IIO_EV_TYPE_THRESH,
@@ -82,12 +111,41 @@ static const struct iio_event_spec mcp9600_events[] = {
},
};
+struct mcp_chip_info {
+ u8 chip_id;
+ const char *chip_name;
+};
+
+struct mcp9600_data {
+ struct i2c_client *client;
+ u32 thermocouple_type;
+};
+
+static int mcp9600_config(struct mcp9600_data *data)
+{
+ struct i2c_client *client = data->client;
+ int ret;
+ u8 cfg;
+
+ cfg = FIELD_PREP(MCP9600_SENSOR_TYPE_MASK,
+ mcp9600_type_map[data->thermocouple_type]);
+
+ ret = i2c_smbus_write_byte_data(client, MCP9600_SENSOR_CFG, cfg);
+ if (ret < 0) {
+ dev_err(&client->dev, "Failed to set sensor configuration\n");
+ return ret;
+ }
+
+ return 0;
+}
+
#define MCP9600_CHANNELS(hj_num_ev, hj_ev_spec_off, cj_num_ev, cj_ev_spec_off) \
{ \
{ \
.type = IIO_TEMP, \
.address = MCP9600_HOT_JUNCTION, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_THERMOCOUPLE_TYPE) | \
BIT(IIO_CHAN_INFO_SCALE), \
.event_spec = &mcp9600_events[hj_ev_spec_off], \
.num_event_specs = hj_num_ev, \
@@ -123,10 +181,6 @@ static const struct iio_chan_spec mcp9600_channels[][2] = {
MCP9600_CHANNELS(2, 0, 2, 0), /* Alerts: 1 2 3 4 */
};
-struct mcp9600_data {
- struct i2c_client *client;
-};
-
static int mcp9600_read(struct mcp9600_data *data,
struct iio_chan_spec const *chan, int *val)
{
@@ -159,6 +213,9 @@ static int mcp9600_read_raw(struct iio_dev *indio_dev,
*val = 62;
*val2 = 500000;
return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_THERMOCOUPLE_TYPE:
+ *val = mcp9600_tc_types[data->thermocouple_type];
+ return IIO_VAL_CHAR;
default:
return -EINVAL;
}
@@ -416,45 +473,93 @@ static int mcp9600_probe_alerts(struct iio_dev *indio_dev)
static int mcp9600_probe(struct i2c_client *client)
{
+ struct device *dev = &client->dev;
+ const struct mcp_chip_info *chip_info;
struct iio_dev *indio_dev;
struct mcp9600_data *data;
- int ret, ch_sel;
+ int ch_sel, dev_id, ret;
+
+ chip_info = i2c_get_match_data(client);
+ if (!chip_info)
+ return dev_err_probe(dev, -ENODEV,
+ "No chip-info found for device\n");
+
+ dev_id = i2c_smbus_read_byte_data(client, MCP9600_DEVICE_ID);
+ if (dev_id < 0)
+ return dev_err_probe(dev, dev_id, "Failed to read device ID\n");
+
+ switch (dev_id) {
+ case MCP9600_DEVICE_ID_MCP9600:
+ case MCP9600_DEVICE_ID_MCP9601:
+ if (dev_id != chip_info->chip_id)
+ dev_warn(dev,
+ "Expected id %02x, but device responded with %02x\n",
+ chip_info->chip_id, dev_id);
+ break;
- ret = i2c_smbus_read_byte_data(client, MCP9600_DEVICE_ID);
- if (ret < 0)
- return dev_err_probe(&client->dev, ret, "Failed to read device ID\n");
- if (ret != MCP9600_DEVICE_ID_MCP9600)
- dev_warn(&client->dev, "Expected ID %x, got %x\n",
- MCP9600_DEVICE_ID_MCP9600, ret);
+ default:
+ dev_warn(dev, "Unknown id %x, using %x\n", dev_id,
+ chip_info->chip_id);
+ }
- indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->client = client;
+ /* Accept type from dt with default of Type-K. */
+ data->thermocouple_type = THERMOCOUPLE_TYPE_K;
+ ret = device_property_read_u32(dev, "thermocouple-type",
+ &data->thermocouple_type);
+ if (ret && ret != -EINVAL)
+ return dev_err_probe(dev, ret,
+ "Error reading thermocouple-type property\n");
+
+ if (data->thermocouple_type >= ARRAY_SIZE(mcp9600_type_map))
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid thermocouple-type property %u.\n",
+ data->thermocouple_type);
+
+ /* Set initial config. */
+ ret = mcp9600_config(data);
+ if (ret)
+ return ret;
+
ch_sel = mcp9600_probe_alerts(indio_dev);
if (ch_sel < 0)
return ch_sel;
indio_dev->info = &mcp9600_info;
- indio_dev->name = "mcp9600";
+ indio_dev->name = chip_info->chip_name;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = mcp9600_channels[ch_sel];
indio_dev->num_channels = ARRAY_SIZE(mcp9600_channels[ch_sel]);
- return devm_iio_device_register(&client->dev, indio_dev);
+ return devm_iio_device_register(dev, indio_dev);
}
+static const struct mcp_chip_info mcp9600_chip_info = {
+ .chip_id = MCP9600_DEVICE_ID_MCP9600,
+ .chip_name = "mcp9600",
+};
+
+static const struct mcp_chip_info mcp9601_chip_info = {
+ .chip_id = MCP9600_DEVICE_ID_MCP9601,
+ .chip_name = "mcp9601",
+};
+
static const struct i2c_device_id mcp9600_id[] = {
- { "mcp9600" },
+ { "mcp9600", .driver_data = (kernel_ulong_t)&mcp9600_chip_info },
+ { "mcp9601", .driver_data = (kernel_ulong_t)&mcp9601_chip_info },
{ }
};
MODULE_DEVICE_TABLE(i2c, mcp9600_id);
static const struct of_device_id mcp9600_of_match[] = {
- { .compatible = "microchip,mcp9600" },
+ { .compatible = "microchip,mcp9600", .data = &mcp9600_chip_info },
+ { .compatible = "microchip,mcp9601", .data = &mcp9601_chip_info },
{ }
};
MODULE_DEVICE_TABLE(of, mcp9600_of_match);
diff --git a/drivers/iio/temperature/mlx90614.c b/drivers/iio/temperature/mlx90614.c
index 740018d4b3df..8a44a00bfd5e 100644
--- a/drivers/iio/temperature/mlx90614.c
+++ b/drivers/iio/temperature/mlx90614.c
@@ -225,7 +225,6 @@ static void mlx90614_power_put(struct mlx90614_data *data)
if (!data->wakeup_gpio)
return;
- pm_runtime_mark_last_busy(&data->client->dev);
pm_runtime_put_autosuspend(&data->client->dev);
}
#else
diff --git a/drivers/iio/temperature/mlx90632.c b/drivers/iio/temperature/mlx90632.c
index ae4ea587e7f9..b44f7036c2cc 100644
--- a/drivers/iio/temperature/mlx90632.c
+++ b/drivers/iio/temperature/mlx90632.c
@@ -1043,7 +1043,6 @@ static int mlx90632_read_raw(struct iio_dev *indio_dev,
}
mlx90632_read_raw_pm:
- pm_runtime_mark_last_busy(&data->client->dev);
pm_runtime_put_autosuspend(&data->client->dev);
return ret;
}
@@ -1178,10 +1177,8 @@ static int mlx90632_probe(struct i2c_client *client)
int ret;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*mlx90632));
- if (!indio_dev) {
- dev_err(&client->dev, "Failed to allocate device\n");
+ if (!indio_dev)
return -ENOMEM;
- }
regmap = devm_regmap_init_i2c(client, &mlx90632_regmap);
if (IS_ERR(regmap)) {
diff --git a/drivers/iio/temperature/mlx90635.c b/drivers/iio/temperature/mlx90635.c
index f7f88498ba0e..1c8948ca54df 100644
--- a/drivers/iio/temperature/mlx90635.c
+++ b/drivers/iio/temperature/mlx90635.c
@@ -749,7 +749,6 @@ static int mlx90635_read_raw(struct iio_dev *indio_dev,
}
mlx90635_read_raw_pm:
- pm_runtime_mark_last_busy(&data->client->dev);
pm_runtime_put_autosuspend(&data->client->dev);
return ret;
}
@@ -939,7 +938,7 @@ static int mlx90635_probe(struct i2c_client *client)
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*mlx90635));
if (!indio_dev)
- return dev_err_probe(&client->dev, -ENOMEM, "failed to allocate device\n");
+ return -ENOMEM;
regmap = devm_regmap_init_i2c(client, &mlx90635_regmap);
if (IS_ERR(regmap))
@@ -977,8 +976,7 @@ static int mlx90635_probe(struct i2c_client *client)
ret = devm_add_action_or_reset(&client->dev, mlx90635_disable_regulator,
mlx90635);
if (ret < 0)
- return dev_err_probe(&client->dev, ret,
- "failed to setup regulator cleanup action\n");
+ return ret;
ret = mlx90635_wakeup(mlx90635);
if (ret < 0)
@@ -986,8 +984,7 @@ static int mlx90635_probe(struct i2c_client *client)
ret = devm_add_action_or_reset(&client->dev, mlx90635_sleep, mlx90635);
if (ret < 0)
- return dev_err_probe(&client->dev, ret,
- "failed to setup low power cleanup\n");
+ return ret;
ret = regmap_read(mlx90635->regmap_ee, MLX90635_EE_VERSION, &dsp_version);
if (ret < 0)
diff --git a/drivers/iio/test/Kconfig b/drivers/iio/test/Kconfig
index 7a181cac3cc9..6e65e929791c 100644
--- a/drivers/iio/test/Kconfig
+++ b/drivers/iio/test/Kconfig
@@ -41,3 +41,15 @@ config IIO_FORMAT_KUNIT_TEST
to the KUnit documentation in Documentation/dev-tools/kunit/.
If unsure, say N.
+
+config IIO_MULTIPLY_KUNIT_TEST
+ tristate "Test IIO multiply functions" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ build unit tests for the IIO multiply functions.
+
+ For more information on KUnit and unit tests in general, please refer
+ to the KUnit documentation in Documentation/dev-tools/kunit/.
+
+ If unsure, say N.
diff --git a/drivers/iio/test/Makefile b/drivers/iio/test/Makefile
index e9a4cf1ff57f..0c846bc21acd 100644
--- a/drivers/iio/test/Makefile
+++ b/drivers/iio/test/Makefile
@@ -7,4 +7,5 @@
obj-$(CONFIG_IIO_RESCALE_KUNIT_TEST) += iio-test-rescale.o
obj-$(CONFIG_IIO_FORMAT_KUNIT_TEST) += iio-test-format.o
obj-$(CONFIG_IIO_GTS_KUNIT_TEST) += iio-test-gts.o
+obj-$(CONFIG_IIO_MULTIPLY_KUNIT_TEST) += iio-test-multiply.o
CFLAGS_iio-test-format.o += $(DISABLE_STRUCTLEAK_PLUGIN)
diff --git a/drivers/iio/test/iio-test-multiply.c b/drivers/iio/test/iio-test-multiply.c
new file mode 100644
index 000000000000..432e279ffe5b
--- /dev/null
+++ b/drivers/iio/test/iio-test-multiply.c
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Unit tests for IIO multiply functions
+ *
+ * Copyright (c) 2025 Hans de Goede <hans@hansg.org>
+ * Based on iio-test-format.c which is:
+ * Copyright (c) 2020 Lars-Peter Clausen <lars@metafoo.de>
+ */
+
+#include <kunit/test.h>
+#include <linux/iio/consumer.h>
+#include <linux/math64.h>
+#include <linux/types.h>
+
+static void __iio_test_iio_multiply_value_integer(struct kunit *test, s64 multiplier)
+{
+ int ret, result, val;
+
+ val = 42;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT, val, 0);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, multiplier * val);
+
+ val = -23;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT, val, 0);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, multiplier * val);
+
+ val = 0;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT, val, 0);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, multiplier * val);
+}
+
+static void iio_test_iio_multiply_value_integer(struct kunit *test)
+{
+ __iio_test_iio_multiply_value_integer(test, 20);
+ __iio_test_iio_multiply_value_integer(test, -20);
+}
+
+static void __iio_test_iio_multiply_value_fixedpoint(struct kunit *test, s64 multiplier)
+{
+ int ret, result, val, val2;
+
+ /* positive >= 1 (1.5) */
+ val = 1;
+ val2 = 500000;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 15, 10));
+
+ val = 1;
+ val2 = 500000000;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 15, 10));
+
+ /* positive < 1 (0.5) */
+ val = 0;
+ val2 = 500000;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 5, 10));
+
+ val = 0;
+ val2 = 500000000;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 5, 10));
+
+ /* negative <= -1 (-1.5) */
+ val = -1;
+ val2 = 500000;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -15, 10));
+
+ val = -1;
+ val2 = 500000000;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -15, 10));
+
+ /* negative > -1 (-0.5) */
+ val = 0;
+ val2 = -500000;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -5, 10));
+
+ val = 0;
+ val2 = -500000000;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -5, 10));
+}
+
+static void iio_test_iio_multiply_value_fixedpoint(struct kunit *test)
+{
+ __iio_test_iio_multiply_value_fixedpoint(test, 20);
+ __iio_test_iio_multiply_value_fixedpoint(test, -20);
+}
+
+static void __iio_test_iio_multiply_value_fractional(struct kunit *test, s64 multiplier)
+{
+ int ret, result, val, val2;
+
+ /* positive < 1 (1/10)*/
+ val = 1;
+ val2 = 10;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2));
+
+ /* positive >= 1 (100/3)*/
+ val = 100;
+ val2 = 3;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2));
+
+ /* negative > -1 (-1/10) */
+ val = -1;
+ val2 = 10;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2));
+
+ /* negative <= -1 (-200/3)*/
+ val = -200;
+ val2 = 3;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2));
+
+ /* Zero (0/-10) */
+ val = 0;
+ val2 = -10;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2));
+}
+
+static void iio_test_iio_multiply_value_fractional(struct kunit *test)
+{
+ __iio_test_iio_multiply_value_fractional(test, 20);
+ __iio_test_iio_multiply_value_fractional(test, -20);
+}
+
+static void __iio_test_iio_multiply_value_fractional_log2(struct kunit *test, s64 multiplier)
+{
+ int ret, result, val, val2;
+
+ /* positive < 1 (123/1024) */
+ val = 123;
+ val2 = 10;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2);
+
+ /* positive >= 1 (1234567/1024) */
+ val = 1234567;
+ val2 = 10;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2);
+
+ /* negative > -1 (-123/1024) */
+ val = -123;
+ val2 = 10;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2);
+
+ /* negative <= -1 (-1234567/1024) */
+ val = -1234567;
+ val2 = 10;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2);
+
+ /* Zero (0/1024) */
+ val = 0;
+ val2 = 10;
+ ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2);
+ KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT);
+ KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2);
+}
+
+static void iio_test_iio_multiply_value_fractional_log2(struct kunit *test)
+{
+ __iio_test_iio_multiply_value_fractional_log2(test, 20);
+ __iio_test_iio_multiply_value_fractional_log2(test, -20);
+}
+
+static struct kunit_case iio_multiply_test_cases[] = {
+ KUNIT_CASE(iio_test_iio_multiply_value_integer),
+ KUNIT_CASE(iio_test_iio_multiply_value_fixedpoint),
+ KUNIT_CASE(iio_test_iio_multiply_value_fractional),
+ KUNIT_CASE(iio_test_iio_multiply_value_fractional_log2),
+ { }
+};
+
+static struct kunit_suite iio_multiply_test_suite = {
+ .name = "iio-multiply",
+ .test_cases = iio_multiply_test_cases,
+};
+kunit_test_suite(iio_multiply_test_suite);
+
+MODULE_AUTHOR("Hans de Goede <hans@hansg.org>");
+MODULE_DESCRIPTION("Test IIO multiply functions");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_UNIT_TEST");
diff --git a/drivers/input/input-mt.c b/drivers/input/input-mt.c
index 337006dd9dcf..09f518897d4a 100644
--- a/drivers/input/input-mt.c
+++ b/drivers/input/input-mt.c
@@ -198,6 +198,7 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
struct input_mt *mt = dev->mt;
struct input_mt_slot *oldest;
int oldid, count, i;
+ int p, reported_p = 0;
if (!mt)
return;
@@ -216,6 +217,13 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
oldest = ps;
oldid = id;
}
+ if (test_bit(ABS_MT_PRESSURE, dev->absbit)) {
+ p = input_mt_get_value(ps, ABS_MT_PRESSURE);
+ if (mt->flags & INPUT_MT_TOTAL_FORCE)
+ reported_p += p;
+ else if (oldid == id)
+ reported_p = p;
+ }
count++;
}
@@ -245,10 +253,8 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
input_event(dev, EV_ABS, ABS_X, x);
input_event(dev, EV_ABS, ABS_Y, y);
- if (test_bit(ABS_MT_PRESSURE, dev->absbit)) {
- int p = input_mt_get_value(oldest, ABS_MT_PRESSURE);
- input_event(dev, EV_ABS, ABS_PRESSURE, p);
- }
+ if (test_bit(ABS_MT_PRESSURE, dev->absbit))
+ input_event(dev, EV_ABS, ABS_PRESSURE, reported_p);
} else {
if (test_bit(ABS_MT_PRESSURE, dev->absbit))
input_event(dev, EV_ABS, ABS_PRESSURE, 0);
diff --git a/drivers/interconnect/core.c b/drivers/interconnect/core.c
index 3ebf37ddfc18..6cc979b26151 100644
--- a/drivers/interconnect/core.c
+++ b/drivers/interconnect/core.c
@@ -385,7 +385,7 @@ struct icc_node_data *of_icc_get_from_provider(const struct of_phandle_args *spe
mutex_lock(&icc_lock);
list_for_each_entry(provider, &icc_providers, provider_list) {
- if (provider->dev->of_node == spec->np) {
+ if (device_match_of_node(provider->dev, spec->np)) {
if (provider->xlate_extended) {
data = provider->xlate_extended(spec, provider->data);
if (!IS_ERR(data)) {
diff --git a/drivers/interconnect/qcom/Kconfig b/drivers/interconnect/qcom/Kconfig
index 31dc4781abef..5b4bb9f1382b 100644
--- a/drivers/interconnect/qcom/Kconfig
+++ b/drivers/interconnect/qcom/Kconfig
@@ -8,6 +8,15 @@ config INTERCONNECT_QCOM
config INTERCONNECT_QCOM_BCM_VOTER
tristate
+config INTERCONNECT_QCOM_GLYMUR
+ tristate "Qualcomm GLYMUR interconnect driver"
+ depends on INTERCONNECT_QCOM_RPMH_POSSIBLE
+ select INTERCONNECT_QCOM_RPMH
+ select INTERCONNECT_QCOM_BCM_VOTER
+ help
+ This is a driver for the Qualcomm Network-on-Chip on glymur-based
+ platforms.
+
config INTERCONNECT_QCOM_MSM8909
tristate "Qualcomm MSM8909 interconnect driver"
depends on INTERCONNECT_QCOM
diff --git a/drivers/interconnect/qcom/Makefile b/drivers/interconnect/qcom/Makefile
index f16ac242eba5..cf8cba73ee3e 100644
--- a/drivers/interconnect/qcom/Makefile
+++ b/drivers/interconnect/qcom/Makefile
@@ -4,6 +4,7 @@ obj-$(CONFIG_INTERCONNECT_QCOM) += interconnect_qcom.o
interconnect_qcom-y := icc-common.o
icc-bcm-voter-objs := bcm-voter.o
+qnoc-glymur-objs := glymur.o
qnoc-milos-objs := milos.o
qnoc-msm8909-objs := msm8909.o
qnoc-msm8916-objs := msm8916.o
@@ -46,6 +47,7 @@ qnoc-x1e80100-objs := x1e80100.o
icc-smd-rpm-objs := smd-rpm.o icc-rpm.o icc-rpm-clocks.o
obj-$(CONFIG_INTERCONNECT_QCOM_BCM_VOTER) += icc-bcm-voter.o
+obj-$(CONFIG_INTERCONNECT_QCOM_GLYMUR) += qnoc-glymur.o
obj-$(CONFIG_INTERCONNECT_QCOM_MILOS) += qnoc-milos.o
obj-$(CONFIG_INTERCONNECT_QCOM_MSM8909) += qnoc-msm8909.o
obj-$(CONFIG_INTERCONNECT_QCOM_MSM8916) += qnoc-msm8916.o
diff --git a/drivers/interconnect/qcom/glymur.c b/drivers/interconnect/qcom/glymur.c
new file mode 100644
index 000000000000..cf20b5752dbb
--- /dev/null
+++ b/drivers/interconnect/qcom/glymur.c
@@ -0,0 +1,2543 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/interconnect.h>
+#include <linux/interconnect-provider.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <dt-bindings/interconnect/qcom,glymur-rpmh.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,
+};
+
+static struct qcom_icc_node qup1_core_slave = {
+ .name = "qup1_core_slave",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qup2_core_slave = {
+ .name = "qup2_core_slave",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_ahb2phy0 = {
+ .name = "qhs_ahb2phy0",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_ahb2phy1 = {
+ .name = "qhs_ahb2phy1",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_ahb2phy2 = {
+ .name = "qhs_ahb2phy2",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_ahb2phy3 = {
+ .name = "qhs_ahb2phy3",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_av1_enc_cfg = {
+ .name = "qhs_av1_enc_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_camera_cfg = {
+ .name = "qhs_camera_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_clk_ctl = {
+ .name = "qhs_clk_ctl",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_crypto0_cfg = {
+ .name = "qhs_crypto0_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_display_cfg = {
+ .name = "qhs_display_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_gpuss_cfg = {
+ .name = "qhs_gpuss_cfg",
+ .channels = 1,
+ .buswidth = 8,
+};
+
+static struct qcom_icc_node qhs_imem_cfg = {
+ .name = "qhs_imem_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_pcie0_cfg = {
+ .name = "qhs_pcie0_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_pcie1_cfg = {
+ .name = "qhs_pcie1_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_pcie2_cfg = {
+ .name = "qhs_pcie2_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_pcie3a_cfg = {
+ .name = "qhs_pcie3a_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_pcie3b_cfg = {
+ .name = "qhs_pcie3b_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_pcie4_cfg = {
+ .name = "qhs_pcie4_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_pcie5_cfg = {
+ .name = "qhs_pcie5_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_pcie6_cfg = {
+ .name = "qhs_pcie6_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_pcie_rscc = {
+ .name = "qhs_pcie_rscc",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_pdm = {
+ .name = "qhs_pdm",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_prng = {
+ .name = "qhs_prng",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_qdss_cfg = {
+ .name = "qhs_qdss_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_qspi = {
+ .name = "qhs_qspi",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_qup0 = {
+ .name = "qhs_qup0",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_qup1 = {
+ .name = "qhs_qup1",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_qup2 = {
+ .name = "qhs_qup2",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_sdc2 = {
+ .name = "qhs_sdc2",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_sdc4 = {
+ .name = "qhs_sdc4",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_smmuv3_cfg = {
+ .name = "qhs_smmuv3_cfg",
+ .channels = 1,
+ .buswidth = 8,
+};
+
+static struct qcom_icc_node qhs_tcsr = {
+ .name = "qhs_tcsr",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_tlmm = {
+ .name = "qhs_tlmm",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_ufs_mem_cfg = {
+ .name = "qhs_ufs_mem_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_usb2_0_cfg = {
+ .name = "qhs_usb2_0_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_usb3_0_cfg = {
+ .name = "qhs_usb3_0_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_usb3_1_cfg = {
+ .name = "qhs_usb3_1_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_usb3_2_cfg = {
+ .name = "qhs_usb3_2_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_usb3_mp_cfg = {
+ .name = "qhs_usb3_mp_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_usb4_0_cfg = {
+ .name = "qhs_usb4_0_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_usb4_1_cfg = {
+ .name = "qhs_usb4_1_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_usb4_2_cfg = {
+ .name = "qhs_usb4_2_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_venus_cfg = {
+ .name = "qhs_venus_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qss_lpass_qtb_cfg = {
+ .name = "qss_lpass_qtb_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qss_nsp_qtb_cfg = {
+ .name = "qss_nsp_qtb_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node xs_qdss_stm = {
+ .name = "xs_qdss_stm",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node xs_sys_tcu_cfg = {
+ .name = "xs_sys_tcu_cfg",
+ .channels = 1,
+ .buswidth = 8,
+};
+
+static struct qcom_icc_node qhs_aoss = {
+ .name = "qhs_aoss",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_ipc_router = {
+ .name = "qhs_ipc_router",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_soccp = {
+ .name = "qhs_soccp",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_tme_cfg = {
+ .name = "qhs_tme_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qns_apss = {
+ .name = "qns_apss",
+ .channels = 1,
+ .buswidth = 8,
+};
+
+static struct qcom_icc_node qxs_boot_imem = {
+ .name = "qxs_boot_imem",
+ .channels = 1,
+ .buswidth = 16,
+};
+
+static struct qcom_icc_node qxs_imem = {
+ .name = "qxs_imem",
+ .channels = 1,
+ .buswidth = 8,
+};
+
+static struct qcom_icc_node ebi = {
+ .name = "ebi",
+ .channels = 12,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node srvc_mnoc = {
+ .name = "srvc_mnoc",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node srvc_nsinoc = {
+ .name = "srvc_nsinoc",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node srvc_pcie_east_aggre_noc = {
+ .name = "srvc_pcie_east_aggre_noc",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_hscnoc_pcie_east_ms_mpu_cfg = {
+ .name = "qhs_hscnoc_pcie_east_ms_mpu_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node srvc_pcie_east = {
+ .name = "srvc_pcie_east",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node xs_pcie_0 = {
+ .name = "xs_pcie_0",
+ .channels = 1,
+ .buswidth = 16,
+};
+
+static struct qcom_icc_node xs_pcie_1 = {
+ .name = "xs_pcie_1",
+ .channels = 1,
+ .buswidth = 32,
+};
+
+static struct qcom_icc_node xs_pcie_5 = {
+ .name = "xs_pcie_5",
+ .channels = 1,
+ .buswidth = 32,
+};
+
+static struct qcom_icc_node srvc_pcie_west_aggre_noc = {
+ .name = "srvc_pcie_west_aggre_noc",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node qhs_hscnoc_pcie_west_ms_mpu_cfg = {
+ .name = "qhs_hscnoc_pcie_west_ms_mpu_cfg",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node srvc_pcie_west = {
+ .name = "srvc_pcie_west",
+ .channels = 1,
+ .buswidth = 4,
+};
+
+static struct qcom_icc_node xs_pcie_2 = {
+ .name = "xs_pcie_2",
+ .channels = 1,
+ .buswidth = 16,
+};
+
+static struct qcom_icc_node xs_pcie_3a = {
+ .name = "xs_pcie_3a",
+ .channels = 1,
+ .buswidth = 64,
+};
+
+static struct qcom_icc_node xs_pcie_3b = {
+ .name = "xs_pcie_3b",
+ .channels = 1,
+ .buswidth = 32,
+};
+
+static struct qcom_icc_node xs_pcie_4 = {
+ .name = "xs_pcie_4",
+ .channels = 1,
+ .buswidth = 16,
+};
+
+static struct qcom_icc_node xs_pcie_6 = {
+ .name = "xs_pcie_6",
+ .channels = 1,
+ .buswidth = 16,
+};
+
+static struct qcom_icc_node qup0_core_master = {
+ .name = "qup0_core_master",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qup0_core_slave },
+};
+
+static struct qcom_icc_node qup1_core_master = {
+ .name = "qup1_core_master",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qup1_core_slave },
+};
+
+static struct qcom_icc_node qup2_core_master = {
+ .name = "qup2_core_master",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qup2_core_slave },
+};
+
+static struct qcom_icc_node llcc_mc = {
+ .name = "llcc_mc",
+ .channels = 12,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &ebi },
+};
+
+static struct qcom_icc_node qsm_mnoc_cfg = {
+ .name = "qsm_mnoc_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &srvc_mnoc },
+};
+
+static struct qcom_icc_node qsm_pcie_east_anoc_cfg = {
+ .name = "qsm_pcie_east_anoc_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &srvc_pcie_east_aggre_noc },
+};
+
+static struct qcom_icc_node qnm_hscnoc_pcie_east = {
+ .name = "qnm_hscnoc_pcie_east",
+ .channels = 1,
+ .buswidth = 32,
+ .num_links = 3,
+ .link_nodes = (struct qcom_icc_node *[]) { &xs_pcie_0, &xs_pcie_1,
+ &xs_pcie_5 },
+};
+
+static struct qcom_icc_node qsm_cnoc_pcie_east_slave_cfg = {
+ .name = "qsm_cnoc_pcie_east_slave_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 2,
+ .link_nodes = (struct qcom_icc_node *[]) { &qhs_hscnoc_pcie_east_ms_mpu_cfg,
+ &srvc_pcie_east },
+};
+
+static struct qcom_icc_node qsm_pcie_west_anoc_cfg = {
+ .name = "qsm_pcie_west_anoc_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &srvc_pcie_west_aggre_noc },
+};
+
+static struct qcom_icc_node qnm_hscnoc_pcie_west = {
+ .name = "qnm_hscnoc_pcie_west",
+ .channels = 1,
+ .buswidth = 32,
+ .num_links = 5,
+ .link_nodes = (struct qcom_icc_node *[]) { &xs_pcie_2, &xs_pcie_3a,
+ &xs_pcie_3b, &xs_pcie_4,
+ &xs_pcie_6 },
+};
+
+static struct qcom_icc_node qsm_cnoc_pcie_west_slave_cfg = {
+ .name = "qsm_cnoc_pcie_west_slave_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 2,
+ .link_nodes = (struct qcom_icc_node *[]) { &qhs_hscnoc_pcie_west_ms_mpu_cfg,
+ &srvc_pcie_west },
+};
+
+static struct qcom_icc_node qss_cnoc_pcie_slave_east_cfg = {
+ .name = "qss_cnoc_pcie_slave_east_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qsm_cnoc_pcie_east_slave_cfg },
+};
+
+static struct qcom_icc_node qss_cnoc_pcie_slave_west_cfg = {
+ .name = "qss_cnoc_pcie_slave_west_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qsm_cnoc_pcie_west_slave_cfg },
+};
+
+static struct qcom_icc_node qss_mnoc_cfg = {
+ .name = "qss_mnoc_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qsm_mnoc_cfg },
+};
+
+static struct qcom_icc_node qss_pcie_east_anoc_cfg = {
+ .name = "qss_pcie_east_anoc_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qsm_pcie_east_anoc_cfg },
+};
+
+static struct qcom_icc_node qss_pcie_west_anoc_cfg = {
+ .name = "qss_pcie_west_anoc_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qsm_pcie_west_anoc_cfg },
+};
+
+static struct qcom_icc_node qns_llcc = {
+ .name = "qns_llcc",
+ .channels = 12,
+ .buswidth = 16,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &llcc_mc },
+};
+
+static struct qcom_icc_node qns_pcie_east = {
+ .name = "qns_pcie_east",
+ .channels = 1,
+ .buswidth = 32,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_hscnoc_pcie_east },
+};
+
+static struct qcom_icc_node qns_pcie_west = {
+ .name = "qns_pcie_west",
+ .channels = 1,
+ .buswidth = 32,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_hscnoc_pcie_west },
+};
+
+static struct qcom_icc_node qsm_cfg = {
+ .name = "qsm_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 51,
+ .link_nodes = (struct qcom_icc_node *[]) { &qhs_ahb2phy0, &qhs_ahb2phy1,
+ &qhs_ahb2phy2, &qhs_ahb2phy3,
+ &qhs_av1_enc_cfg, &qhs_camera_cfg,
+ &qhs_clk_ctl, &qhs_crypto0_cfg,
+ &qhs_display_cfg, &qhs_gpuss_cfg,
+ &qhs_imem_cfg, &qhs_pcie0_cfg,
+ &qhs_pcie1_cfg, &qhs_pcie2_cfg,
+ &qhs_pcie3a_cfg, &qhs_pcie3b_cfg,
+ &qhs_pcie4_cfg, &qhs_pcie5_cfg,
+ &qhs_pcie6_cfg, &qhs_pcie_rscc,
+ &qhs_pdm, &qhs_prng,
+ &qhs_qdss_cfg, &qhs_qspi,
+ &qhs_qup0, &qhs_qup1,
+ &qhs_qup2, &qhs_sdc2,
+ &qhs_sdc4, &qhs_smmuv3_cfg,
+ &qhs_tcsr, &qhs_tlmm,
+ &qhs_ufs_mem_cfg, &qhs_usb2_0_cfg,
+ &qhs_usb3_0_cfg, &qhs_usb3_1_cfg,
+ &qhs_usb3_2_cfg, &qhs_usb3_mp_cfg,
+ &qhs_usb4_0_cfg, &qhs_usb4_1_cfg,
+ &qhs_usb4_2_cfg, &qhs_venus_cfg,
+ &qss_cnoc_pcie_slave_east_cfg, &qss_cnoc_pcie_slave_west_cfg,
+ &qss_lpass_qtb_cfg, &qss_mnoc_cfg,
+ &qss_nsp_qtb_cfg, &qss_pcie_east_anoc_cfg,
+ &qss_pcie_west_anoc_cfg, &xs_qdss_stm,
+ &xs_sys_tcu_cfg },
+};
+
+static struct qcom_icc_node xm_gic = {
+ .name = "xm_gic",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x33000 },
+ .prio = 0,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_llcc },
+};
+
+static struct qcom_icc_node qss_cfg = {
+ .name = "qss_cfg",
+ .channels = 1,
+ .buswidth = 4,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qsm_cfg },
+};
+
+static struct qcom_icc_node qnm_hscnoc_cnoc = {
+ .name = "qnm_hscnoc_cnoc",
+ .channels = 1,
+ .buswidth = 16,
+ .num_links = 8,
+ .link_nodes = (struct qcom_icc_node *[]) { &qhs_aoss, &qhs_ipc_router,
+ &qhs_soccp, &qhs_tme_cfg,
+ &qns_apss, &qss_cfg,
+ &qxs_boot_imem, &qxs_imem },
+};
+
+static struct qcom_icc_node qns_hscnoc_cnoc = {
+ .name = "qns_hscnoc_cnoc",
+ .channels = 1,
+ .buswidth = 16,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_hscnoc_cnoc },
+};
+
+static struct qcom_icc_node alm_gpu_tcu = {
+ .name = "alm_gpu_tcu",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x933000 },
+ .prio = 1,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 2,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc },
+};
+
+static struct qcom_icc_node alm_pcie_qtc = {
+ .name = "alm_pcie_qtc",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x51f000 },
+ .prio = 3,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 2,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc },
+};
+
+static struct qcom_icc_node alm_sys_tcu = {
+ .name = "alm_sys_tcu",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x51f080 },
+ .prio = 6,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 2,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc },
+};
+
+static struct qcom_icc_node chm_apps = {
+ .name = "chm_apps",
+ .channels = 6,
+ .buswidth = 32,
+ .num_links = 4,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc,
+ &qns_pcie_east, &qns_pcie_west },
+};
+
+static struct qcom_icc_node qnm_aggre_noc_east = {
+ .name = "qnm_aggre_noc_east",
+ .channels = 1,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x934000 },
+ .prio = 2,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 4,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc,
+ &qns_pcie_east, &qns_pcie_west },
+};
+
+static struct qcom_icc_node qnm_gpu = {
+ .name = "qnm_gpu",
+ .channels = 4,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 4,
+ .port_offsets = { 0x935000, 0x936000, 0x937000, 0x938000 },
+ .prio = 0,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 4,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc,
+ &qns_pcie_east, &qns_pcie_west },
+};
+
+static struct qcom_icc_node qnm_lpass = {
+ .name = "qnm_lpass",
+ .channels = 1,
+ .buswidth = 16,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x939000 },
+ .prio = 0,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 4,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc,
+ &qns_pcie_east, &qns_pcie_west },
+};
+
+static struct qcom_icc_node qnm_mnoc_hf = {
+ .name = "qnm_mnoc_hf",
+ .channels = 2,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 2,
+ .port_offsets = { 0x721000, 0x721080 },
+ .prio = 0,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 4,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc,
+ &qns_pcie_east, &qns_pcie_west },
+};
+
+static struct qcom_icc_node qnm_mnoc_sf = {
+ .name = "qnm_mnoc_sf",
+ .channels = 2,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 2,
+ .port_offsets = { 0x721100, 0x721180 },
+ .prio = 0,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 4,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc,
+ &qns_pcie_east, &qns_pcie_west },
+};
+
+static struct qcom_icc_node qnm_nsp_noc = {
+ .name = "qnm_nsp_noc",
+ .channels = 4,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 4,
+ .port_offsets = { 0x816000, 0x816080, 0x816100, 0x816180 },
+ .prio = 0,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 4,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc,
+ &qns_pcie_east, &qns_pcie_west },
+};
+
+static struct qcom_icc_node qnm_pcie_east = {
+ .name = "qnm_pcie_east",
+ .channels = 1,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x93a000 },
+ .prio = 2,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 2,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc },
+};
+
+static struct qcom_icc_node qnm_pcie_west = {
+ .name = "qnm_pcie_west",
+ .channels = 1,
+ .buswidth = 64,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x721200 },
+ .prio = 2,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 2,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc },
+};
+
+static struct qcom_icc_node qnm_snoc_sf = {
+ .name = "qnm_snoc_sf",
+ .channels = 1,
+ .buswidth = 64,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x51f100 },
+ .prio = 2,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 4,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc,
+ &qns_pcie_east, &qns_pcie_west },
+};
+
+static struct qcom_icc_node qxm_wlan_q6 = {
+ .name = "qxm_wlan_q6",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 4,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc,
+ &qns_pcie_east, &qns_pcie_west },
+};
+
+static struct qcom_icc_node qns_a4noc_hscnoc = {
+ .name = "qns_a4noc_hscnoc",
+ .channels = 1,
+ .buswidth = 32,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_aggre_noc_east },
+};
+
+static struct qcom_icc_node qns_lpass_ag_noc_gemnoc = {
+ .name = "qns_lpass_ag_noc_gemnoc",
+ .channels = 1,
+ .buswidth = 16,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_lpass },
+};
+
+static struct qcom_icc_node qns_mem_noc_hf = {
+ .name = "qns_mem_noc_hf",
+ .channels = 2,
+ .buswidth = 32,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_mnoc_hf },
+};
+
+static struct qcom_icc_node qns_mem_noc_sf = {
+ .name = "qns_mem_noc_sf",
+ .channels = 2,
+ .buswidth = 32,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_mnoc_sf },
+};
+
+static struct qcom_icc_node qns_nsp_hscnoc = {
+ .name = "qns_nsp_hscnoc",
+ .channels = 4,
+ .buswidth = 32,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_nsp_noc },
+};
+
+static struct qcom_icc_node qns_pcie_east_mem_noc = {
+ .name = "qns_pcie_east_mem_noc",
+ .channels = 1,
+ .buswidth = 32,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_pcie_east },
+};
+
+static struct qcom_icc_node qns_pcie_west_mem_noc = {
+ .name = "qns_pcie_west_mem_noc",
+ .channels = 1,
+ .buswidth = 64,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_pcie_west },
+};
+
+static struct qcom_icc_node qns_gemnoc_sf = {
+ .name = "qns_gemnoc_sf",
+ .channels = 1,
+ .buswidth = 64,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_snoc_sf },
+};
+
+static struct qcom_icc_node xm_usb3_0 = {
+ .name = "xm_usb3_0",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xa000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a4noc_hscnoc },
+};
+
+static struct qcom_icc_node xm_usb3_1 = {
+ .name = "xm_usb3_1",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xb000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a4noc_hscnoc },
+};
+
+static struct qcom_icc_node xm_usb4_0 = {
+ .name = "xm_usb4_0",
+ .channels = 1,
+ .buswidth = 16,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xc000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a4noc_hscnoc },
+};
+
+static struct qcom_icc_node xm_usb4_1 = {
+ .name = "xm_usb4_1",
+ .channels = 1,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xd000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a4noc_hscnoc },
+};
+
+static struct qcom_icc_node qnm_lpiaon_noc = {
+ .name = "qnm_lpiaon_noc",
+ .channels = 1,
+ .buswidth = 16,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_lpass_ag_noc_gemnoc },
+};
+
+static struct qcom_icc_node qnm_av1_enc = {
+ .name = "qnm_av1_enc",
+ .channels = 1,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x30000 },
+ .prio = 4,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf },
+};
+
+static struct qcom_icc_node qnm_camnoc_hf = {
+ .name = "qnm_camnoc_hf",
+ .channels = 2,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 2,
+ .port_offsets = { 0x29000, 0x2a000 },
+ .prio = 0,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_hf },
+};
+
+static struct qcom_icc_node qnm_camnoc_icp = {
+ .name = "qnm_camnoc_icp",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x2b000 },
+ .prio = 4,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf },
+};
+
+static struct qcom_icc_node qnm_camnoc_sf = {
+ .name = "qnm_camnoc_sf",
+ .channels = 2,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 2,
+ .port_offsets = { 0x2c000, 0x2d000 },
+ .prio = 0,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf },
+};
+
+static struct qcom_icc_node qnm_eva = {
+ .name = "qnm_eva",
+ .channels = 1,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x34000 },
+ .prio = 0,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf },
+};
+
+static struct qcom_icc_node qnm_mdp = {
+ .name = "qnm_mdp",
+ .channels = 2,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 2,
+ .port_offsets = { 0x2e000, 0x2f000 },
+ .prio = 0,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_hf },
+};
+
+static struct qcom_icc_node qnm_vapss_hcp = {
+ .name = "qnm_vapss_hcp",
+ .channels = 1,
+ .buswidth = 32,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf },
+};
+
+static struct qcom_icc_node qnm_video = {
+ .name = "qnm_video",
+ .channels = 4,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 4,
+ .port_offsets = { 0x31000, 0x32000, 0x37000, 0x38000 },
+ .prio = 0,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf },
+};
+
+static struct qcom_icc_node qnm_video_cv_cpu = {
+ .name = "qnm_video_cv_cpu",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x33000 },
+ .prio = 4,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf },
+};
+
+static struct qcom_icc_node qnm_video_v_cpu = {
+ .name = "qnm_video_v_cpu",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x35000 },
+ .prio = 4,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf },
+};
+
+static struct qcom_icc_node qnm_nsp = {
+ .name = "qnm_nsp",
+ .channels = 4,
+ .buswidth = 32,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_nsp_hscnoc },
+};
+
+static struct qcom_icc_node xm_pcie_0 = {
+ .name = "xm_pcie_0",
+ .channels = 1,
+ .buswidth = 16,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xb000 },
+ .prio = 2,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_east_mem_noc },
+};
+
+static struct qcom_icc_node xm_pcie_1 = {
+ .name = "xm_pcie_1",
+ .channels = 1,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xc000 },
+ .prio = 2,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_east_mem_noc },
+};
+
+static struct qcom_icc_node xm_pcie_5 = {
+ .name = "xm_pcie_5",
+ .channels = 1,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xd000 },
+ .prio = 2,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_east_mem_noc },
+};
+
+static struct qcom_icc_node xm_pcie_2 = {
+ .name = "xm_pcie_2",
+ .channels = 1,
+ .buswidth = 16,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xd000 },
+ .prio = 2,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_west_mem_noc },
+};
+
+static struct qcom_icc_node xm_pcie_3a = {
+ .name = "xm_pcie_3a",
+ .channels = 1,
+ .buswidth = 64,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xd200 },
+ .prio = 2,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_west_mem_noc },
+};
+
+static struct qcom_icc_node xm_pcie_3b = {
+ .name = "xm_pcie_3b",
+ .channels = 1,
+ .buswidth = 32,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xd400 },
+ .prio = 2,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_west_mem_noc },
+};
+
+static struct qcom_icc_node xm_pcie_4 = {
+ .name = "xm_pcie_4",
+ .channels = 1,
+ .buswidth = 16,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xd600 },
+ .prio = 2,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_west_mem_noc },
+};
+
+static struct qcom_icc_node xm_pcie_6 = {
+ .name = "xm_pcie_6",
+ .channels = 1,
+ .buswidth = 16,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xd800 },
+ .prio = 2,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 0,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_west_mem_noc },
+};
+
+static struct qcom_icc_node qnm_aggre1_noc = {
+ .name = "qnm_aggre1_noc",
+ .channels = 1,
+ .buswidth = 16,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_gemnoc_sf },
+};
+
+static struct qcom_icc_node qnm_aggre2_noc = {
+ .name = "qnm_aggre2_noc",
+ .channels = 1,
+ .buswidth = 16,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_gemnoc_sf },
+};
+
+static struct qcom_icc_node qnm_aggre3_noc = {
+ .name = "qnm_aggre3_noc",
+ .channels = 1,
+ .buswidth = 32,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_gemnoc_sf },
+};
+
+static struct qcom_icc_node qnm_nsi_noc = {
+ .name = "qnm_nsi_noc",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x1c000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_gemnoc_sf },
+};
+
+static struct qcom_icc_node qnm_oobmss = {
+ .name = "qnm_oobmss",
+ .channels = 1,
+ .buswidth = 16,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x1b000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_gemnoc_sf },
+};
+
+static struct qcom_icc_node qns_a1noc_snoc = {
+ .name = "qns_a1noc_snoc",
+ .channels = 1,
+ .buswidth = 16,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_aggre1_noc },
+};
+
+static struct qcom_icc_node qns_a2noc_snoc = {
+ .name = "qns_a2noc_snoc",
+ .channels = 1,
+ .buswidth = 16,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_aggre2_noc },
+};
+
+static struct qcom_icc_node qns_a3noc_snoc = {
+ .name = "qns_a3noc_snoc",
+ .channels = 1,
+ .buswidth = 32,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_aggre3_noc },
+};
+
+static struct qcom_icc_node qns_lpass_aggnoc = {
+ .name = "qns_lpass_aggnoc",
+ .channels = 1,
+ .buswidth = 16,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_lpiaon_noc },
+};
+
+static struct qcom_icc_node qns_system_noc = {
+ .name = "qns_system_noc",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_nsi_noc },
+};
+
+static struct qcom_icc_node qns_oobmss_snoc = {
+ .name = "qns_oobmss_snoc",
+ .channels = 1,
+ .buswidth = 16,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_oobmss },
+};
+
+static struct qcom_icc_node qxm_crypto = {
+ .name = "qxm_crypto",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xb000 },
+ .prio = 0,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a1noc_snoc },
+};
+
+static struct qcom_icc_node qxm_soccp = {
+ .name = "qxm_soccp",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xe000 },
+ .prio = 0,
+ .urg_fwd = 1,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a1noc_snoc },
+};
+
+static struct qcom_icc_node xm_qdss_etr_0 = {
+ .name = "xm_qdss_etr_0",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xc000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a1noc_snoc },
+};
+
+static struct qcom_icc_node xm_qdss_etr_1 = {
+ .name = "xm_qdss_etr_1",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xd000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a1noc_snoc },
+};
+
+static struct qcom_icc_node xm_ufs_mem = {
+ .name = "xm_ufs_mem",
+ .channels = 1,
+ .buswidth = 16,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0xa000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a2noc_snoc },
+};
+
+static struct qcom_icc_node xm_usb3_2 = {
+ .name = "xm_usb3_2",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x8000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a2noc_snoc },
+};
+
+static struct qcom_icc_node xm_usb4_2 = {
+ .name = "xm_usb4_2",
+ .channels = 1,
+ .buswidth = 16,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x9000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a2noc_snoc },
+};
+
+static struct qcom_icc_node qhm_qspi = {
+ .name = "qhm_qspi",
+ .channels = 1,
+ .buswidth = 4,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x10000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc },
+};
+
+static struct qcom_icc_node qhm_qup0 = {
+ .name = "qhm_qup0",
+ .channels = 1,
+ .buswidth = 4,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x11000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc },
+};
+
+static struct qcom_icc_node qhm_qup1 = {
+ .name = "qhm_qup1",
+ .channels = 1,
+ .buswidth = 4,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x12000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc },
+};
+
+static struct qcom_icc_node qhm_qup2 = {
+ .name = "qhm_qup2",
+ .channels = 1,
+ .buswidth = 4,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x13000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc },
+};
+
+static struct qcom_icc_node qxm_sp = {
+ .name = "qxm_sp",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc },
+};
+
+static struct qcom_icc_node xm_sdc2 = {
+ .name = "xm_sdc2",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x18000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc },
+};
+
+static struct qcom_icc_node xm_sdc4 = {
+ .name = "xm_sdc4",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x14000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc },
+};
+
+static struct qcom_icc_node xm_usb2_0 = {
+ .name = "xm_usb2_0",
+ .channels = 1,
+ .buswidth = 8,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x15000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc },
+};
+
+static struct qcom_icc_node xm_usb3_mp = {
+ .name = "xm_usb3_mp",
+ .channels = 1,
+ .buswidth = 16,
+ .qosbox = &(const struct qcom_icc_qosbox) {
+ .num_ports = 1,
+ .port_offsets = { 0x16000 },
+ .prio = 0,
+ .urg_fwd = 0,
+ .prio_fwd_disable = 1,
+ },
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc },
+};
+
+static struct qcom_icc_node qnm_lpass_lpinoc = {
+ .name = "qnm_lpass_lpinoc",
+ .channels = 1,
+ .buswidth = 16,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_lpass_aggnoc },
+};
+
+static struct qcom_icc_node xm_cpucp = {
+ .name = "xm_cpucp",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 2,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_system_noc, &srvc_nsinoc },
+};
+
+static struct qcom_icc_node xm_mem_sp = {
+ .name = "xm_mem_sp",
+ .channels = 1,
+ .buswidth = 8,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_oobmss_snoc },
+};
+
+static struct qcom_icc_node qns_lpi_aon_noc = {
+ .name = "qns_lpi_aon_noc",
+ .channels = 1,
+ .buswidth = 16,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qnm_lpass_lpinoc },
+};
+
+static struct qcom_icc_node qnm_lpinoc_dsp_qns4m = {
+ .name = "qnm_lpinoc_dsp_qns4m",
+ .channels = 1,
+ .buswidth = 16,
+ .num_links = 1,
+ .link_nodes = (struct qcom_icc_node *[]) { &qns_lpi_aon_noc },
+};
+
+static struct qcom_icc_bcm bcm_acv = {
+ .name = "ACV",
+ .enable_mask = BIT(3),
+ .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 = BIT(0),
+ .num_nodes = 60,
+ .nodes = { &qsm_cfg, &qhs_ahb2phy0,
+ &qhs_ahb2phy1, &qhs_ahb2phy2,
+ &qhs_ahb2phy3, &qhs_av1_enc_cfg,
+ &qhs_camera_cfg, &qhs_clk_ctl,
+ &qhs_crypto0_cfg, &qhs_gpuss_cfg,
+ &qhs_imem_cfg, &qhs_pcie0_cfg,
+ &qhs_pcie1_cfg, &qhs_pcie2_cfg,
+ &qhs_pcie3a_cfg, &qhs_pcie3b_cfg,
+ &qhs_pcie4_cfg, &qhs_pcie5_cfg,
+ &qhs_pcie6_cfg, &qhs_pcie_rscc,
+ &qhs_pdm, &qhs_prng,
+ &qhs_qdss_cfg, &qhs_qspi,
+ &qhs_qup0, &qhs_qup1,
+ &qhs_qup2, &qhs_sdc2,
+ &qhs_sdc4, &qhs_smmuv3_cfg,
+ &qhs_tcsr, &qhs_tlmm,
+ &qhs_ufs_mem_cfg, &qhs_usb2_0_cfg,
+ &qhs_usb3_0_cfg, &qhs_usb3_1_cfg,
+ &qhs_usb3_2_cfg, &qhs_usb3_mp_cfg,
+ &qhs_usb4_0_cfg, &qhs_usb4_1_cfg,
+ &qhs_usb4_2_cfg, &qhs_venus_cfg,
+ &qss_cnoc_pcie_slave_east_cfg, &qss_cnoc_pcie_slave_west_cfg,
+ &qss_lpass_qtb_cfg, &qss_mnoc_cfg,
+ &qss_nsp_qtb_cfg, &qss_pcie_east_anoc_cfg,
+ &qss_pcie_west_anoc_cfg, &xs_qdss_stm,
+ &xs_sys_tcu_cfg, &qnm_hscnoc_cnoc,
+ &qhs_aoss, &qhs_ipc_router,
+ &qhs_soccp, &qhs_tme_cfg,
+ &qns_apss, &qss_cfg,
+ &qxs_boot_imem, &qxs_imem },
+};
+
+static struct qcom_icc_bcm bcm_cn1 = {
+ .name = "CN1",
+ .num_nodes = 1,
+ .nodes = { &qhs_display_cfg },
+};
+
+static struct qcom_icc_bcm bcm_co0 = {
+ .name = "CO0",
+ .enable_mask = BIT(0),
+ .num_nodes = 2,
+ .nodes = { &qnm_nsp, &qns_nsp_hscnoc },
+};
+
+static struct qcom_icc_bcm bcm_lp0 = {
+ .name = "LP0",
+ .num_nodes = 2,
+ .nodes = { &qnm_lpass_lpinoc, &qns_lpass_aggnoc },
+};
+
+static struct qcom_icc_bcm bcm_mc0 = {
+ .name = "MC0",
+ .keepalive = true,
+ .num_nodes = 1,
+ .nodes = { &ebi },
+};
+
+static struct qcom_icc_bcm bcm_mm0 = {
+ .name = "MM0",
+ .num_nodes = 1,
+ .nodes = { &qns_mem_noc_hf },
+};
+
+static struct qcom_icc_bcm bcm_mm1 = {
+ .name = "MM1",
+ .enable_mask = BIT(0),
+ .num_nodes = 11,
+ .nodes = { &qnm_av1_enc, &qnm_camnoc_hf,
+ &qnm_camnoc_icp, &qnm_camnoc_sf,
+ &qnm_eva, &qnm_mdp,
+ &qnm_vapss_hcp, &qnm_video,
+ &qnm_video_cv_cpu, &qnm_video_v_cpu,
+ &qns_mem_noc_sf },
+};
+
+static struct qcom_icc_bcm bcm_qup0 = {
+ .name = "QUP0",
+ .vote_scale = 1,
+ .keepalive = true,
+ .num_nodes = 1,
+ .nodes = { &qup0_core_slave },
+};
+
+static struct qcom_icc_bcm bcm_qup1 = {
+ .name = "QUP1",
+ .vote_scale = 1,
+ .keepalive = true,
+ .num_nodes = 1,
+ .nodes = { &qup1_core_slave },
+};
+
+static struct qcom_icc_bcm bcm_qup2 = {
+ .name = "QUP2",
+ .vote_scale = 1,
+ .keepalive = true,
+ .num_nodes = 1,
+ .nodes = { &qup2_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 = BIT(0),
+ .num_nodes = 18,
+ .nodes = { &alm_gpu_tcu, &alm_pcie_qtc,
+ &alm_sys_tcu, &chm_apps,
+ &qnm_aggre_noc_east, &qnm_gpu,
+ &qnm_lpass, &qnm_mnoc_hf,
+ &qnm_mnoc_sf, &qnm_nsp_noc,
+ &qnm_pcie_east, &qnm_pcie_west,
+ &qnm_snoc_sf, &qxm_wlan_q6,
+ &xm_gic, &qns_hscnoc_cnoc,
+ &qns_pcie_east, &qns_pcie_west },
+};
+
+static struct qcom_icc_bcm bcm_sn0 = {
+ .name = "SN0",
+ .keepalive = true,
+ .num_nodes = 1,
+ .nodes = { &qns_gemnoc_sf },
+};
+
+static struct qcom_icc_bcm bcm_sn1 = {
+ .name = "SN1",
+ .enable_mask = BIT(0),
+ .num_nodes = 1,
+ .nodes = { &qnm_oobmss },
+};
+
+static struct qcom_icc_bcm bcm_sn2 = {
+ .name = "SN2",
+ .num_nodes = 1,
+ .nodes = { &qnm_aggre1_noc },
+};
+
+static struct qcom_icc_bcm bcm_sn3 = {
+ .name = "SN3",
+ .num_nodes = 1,
+ .nodes = { &qnm_aggre2_noc },
+};
+
+static struct qcom_icc_bcm bcm_sn4 = {
+ .name = "SN4",
+ .num_nodes = 1,
+ .nodes = { &qnm_aggre3_noc },
+};
+
+static struct qcom_icc_bcm bcm_sn5 = {
+ .name = "SN5",
+ .num_nodes = 1,
+ .nodes = { &qns_a4noc_hscnoc },
+};
+
+static struct qcom_icc_bcm bcm_sn6 = {
+ .name = "SN6",
+ .num_nodes = 4,
+ .nodes = { &qns_pcie_east_mem_noc, &qnm_hscnoc_pcie_east,
+ &qns_pcie_west_mem_noc, &qnm_hscnoc_pcie_west },
+};
+
+static struct qcom_icc_bcm * const aggre1_noc_bcms[] = {
+ &bcm_ce0,
+};
+
+static struct qcom_icc_node * const aggre1_noc_nodes[] = {
+ [MASTER_CRYPTO] = &qxm_crypto,
+ [MASTER_SOCCP_PROC] = &qxm_soccp,
+ [MASTER_QDSS_ETR] = &xm_qdss_etr_0,
+ [MASTER_QDSS_ETR_1] = &xm_qdss_etr_1,
+ [SLAVE_A1NOC_SNOC] = &qns_a1noc_snoc,
+};
+
+static const struct regmap_config glymur_aggre1_noc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x14400,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_aggre1_noc = {
+ .config = &glymur_aggre1_noc_regmap_config,
+ .nodes = aggre1_noc_nodes,
+ .num_nodes = ARRAY_SIZE(aggre1_noc_nodes),
+ .bcms = aggre1_noc_bcms,
+ .num_bcms = ARRAY_SIZE(aggre1_noc_bcms),
+ .alloc_dyn_id = true,
+};
+
+static struct qcom_icc_node * const aggre2_noc_nodes[] = {
+ [MASTER_UFS_MEM] = &xm_ufs_mem,
+ [MASTER_USB3_2] = &xm_usb3_2,
+ [MASTER_USB4_2] = &xm_usb4_2,
+ [SLAVE_A2NOC_SNOC] = &qns_a2noc_snoc,
+};
+
+static const struct regmap_config glymur_aggre2_noc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x14400,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_aggre2_noc = {
+ .config = &glymur_aggre2_noc_regmap_config,
+ .nodes = aggre2_noc_nodes,
+ .num_nodes = ARRAY_SIZE(aggre2_noc_nodes),
+ .alloc_dyn_id = true,
+ .qos_requires_clocks = true,
+};
+
+static struct qcom_icc_node * const aggre3_noc_nodes[] = {
+ [MASTER_QSPI_0] = &qhm_qspi,
+ [MASTER_QUP_0] = &qhm_qup0,
+ [MASTER_QUP_1] = &qhm_qup1,
+ [MASTER_QUP_2] = &qhm_qup2,
+ [MASTER_SP] = &qxm_sp,
+ [MASTER_SDCC_2] = &xm_sdc2,
+ [MASTER_SDCC_4] = &xm_sdc4,
+ [MASTER_USB2] = &xm_usb2_0,
+ [MASTER_USB3_MP] = &xm_usb3_mp,
+ [SLAVE_A3NOC_SNOC] = &qns_a3noc_snoc,
+};
+
+static const struct regmap_config glymur_aggre3_noc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x1d400,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_aggre3_noc = {
+ .config = &glymur_aggre3_noc_regmap_config,
+ .nodes = aggre3_noc_nodes,
+ .num_nodes = ARRAY_SIZE(aggre3_noc_nodes),
+ .alloc_dyn_id = true,
+};
+
+static struct qcom_icc_bcm * const aggre4_noc_bcms[] = {
+ &bcm_sn5,
+};
+
+static struct qcom_icc_node * const aggre4_noc_nodes[] = {
+ [MASTER_USB3_0] = &xm_usb3_0,
+ [MASTER_USB3_1] = &xm_usb3_1,
+ [MASTER_USB4_0] = &xm_usb4_0,
+ [MASTER_USB4_1] = &xm_usb4_1,
+ [SLAVE_A4NOC_HSCNOC] = &qns_a4noc_hscnoc,
+};
+
+static const struct regmap_config glymur_aggre4_noc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x14400,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_aggre4_noc = {
+ .config = &glymur_aggre4_noc_regmap_config,
+ .nodes = aggre4_noc_nodes,
+ .num_nodes = ARRAY_SIZE(aggre4_noc_nodes),
+ .bcms = aggre4_noc_bcms,
+ .num_bcms = ARRAY_SIZE(aggre4_noc_bcms),
+ .alloc_dyn_id = true,
+ .qos_requires_clocks = true,
+};
+
+static struct qcom_icc_bcm * const clk_virt_bcms[] = {
+ &bcm_qup0,
+ &bcm_qup1,
+ &bcm_qup2,
+};
+
+static struct qcom_icc_node * const clk_virt_nodes[] = {
+ [MASTER_QUP_CORE_0] = &qup0_core_master,
+ [MASTER_QUP_CORE_1] = &qup1_core_master,
+ [MASTER_QUP_CORE_2] = &qup2_core_master,
+ [SLAVE_QUP_CORE_0] = &qup0_core_slave,
+ [SLAVE_QUP_CORE_1] = &qup1_core_slave,
+ [SLAVE_QUP_CORE_2] = &qup2_core_slave,
+};
+
+static const struct qcom_icc_desc glymur_clk_virt = {
+ .nodes = clk_virt_nodes,
+ .num_nodes = ARRAY_SIZE(clk_virt_nodes),
+ .bcms = clk_virt_bcms,
+ .num_bcms = ARRAY_SIZE(clk_virt_bcms),
+ .alloc_dyn_id = true,
+};
+
+static struct qcom_icc_bcm * const cnoc_cfg_bcms[] = {
+ &bcm_cn0,
+ &bcm_cn1,
+};
+
+static struct qcom_icc_node * const cnoc_cfg_nodes[] = {
+ [MASTER_CNOC_CFG] = &qsm_cfg,
+ [SLAVE_AHB2PHY_SOUTH] = &qhs_ahb2phy0,
+ [SLAVE_AHB2PHY_NORTH] = &qhs_ahb2phy1,
+ [SLAVE_AHB2PHY_2] = &qhs_ahb2phy2,
+ [SLAVE_AHB2PHY_3] = &qhs_ahb2phy3,
+ [SLAVE_AV1_ENC_CFG] = &qhs_av1_enc_cfg,
+ [SLAVE_CAMERA_CFG] = &qhs_camera_cfg,
+ [SLAVE_CLK_CTL] = &qhs_clk_ctl,
+ [SLAVE_CRYPTO_0_CFG] = &qhs_crypto0_cfg,
+ [SLAVE_DISPLAY_CFG] = &qhs_display_cfg,
+ [SLAVE_GFX3D_CFG] = &qhs_gpuss_cfg,
+ [SLAVE_IMEM_CFG] = &qhs_imem_cfg,
+ [SLAVE_PCIE_0_CFG] = &qhs_pcie0_cfg,
+ [SLAVE_PCIE_1_CFG] = &qhs_pcie1_cfg,
+ [SLAVE_PCIE_2_CFG] = &qhs_pcie2_cfg,
+ [SLAVE_PCIE_3A_CFG] = &qhs_pcie3a_cfg,
+ [SLAVE_PCIE_3B_CFG] = &qhs_pcie3b_cfg,
+ [SLAVE_PCIE_4_CFG] = &qhs_pcie4_cfg,
+ [SLAVE_PCIE_5_CFG] = &qhs_pcie5_cfg,
+ [SLAVE_PCIE_6_CFG] = &qhs_pcie6_cfg,
+ [SLAVE_PCIE_RSCC] = &qhs_pcie_rscc,
+ [SLAVE_PDM] = &qhs_pdm,
+ [SLAVE_PRNG] = &qhs_prng,
+ [SLAVE_QDSS_CFG] = &qhs_qdss_cfg,
+ [SLAVE_QSPI_0] = &qhs_qspi,
+ [SLAVE_QUP_0] = &qhs_qup0,
+ [SLAVE_QUP_1] = &qhs_qup1,
+ [SLAVE_QUP_2] = &qhs_qup2,
+ [SLAVE_SDCC_2] = &qhs_sdc2,
+ [SLAVE_SDCC_4] = &qhs_sdc4,
+ [SLAVE_SMMUV3_CFG] = &qhs_smmuv3_cfg,
+ [SLAVE_TCSR] = &qhs_tcsr,
+ [SLAVE_TLMM] = &qhs_tlmm,
+ [SLAVE_UFS_MEM_CFG] = &qhs_ufs_mem_cfg,
+ [SLAVE_USB2] = &qhs_usb2_0_cfg,
+ [SLAVE_USB3_0] = &qhs_usb3_0_cfg,
+ [SLAVE_USB3_1] = &qhs_usb3_1_cfg,
+ [SLAVE_USB3_2] = &qhs_usb3_2_cfg,
+ [SLAVE_USB3_MP] = &qhs_usb3_mp_cfg,
+ [SLAVE_USB4_0] = &qhs_usb4_0_cfg,
+ [SLAVE_USB4_1] = &qhs_usb4_1_cfg,
+ [SLAVE_USB4_2] = &qhs_usb4_2_cfg,
+ [SLAVE_VENUS_CFG] = &qhs_venus_cfg,
+ [SLAVE_CNOC_PCIE_SLAVE_EAST_CFG] = &qss_cnoc_pcie_slave_east_cfg,
+ [SLAVE_CNOC_PCIE_SLAVE_WEST_CFG] = &qss_cnoc_pcie_slave_west_cfg,
+ [SLAVE_LPASS_QTB_CFG] = &qss_lpass_qtb_cfg,
+ [SLAVE_CNOC_MNOC_CFG] = &qss_mnoc_cfg,
+ [SLAVE_NSP_QTB_CFG] = &qss_nsp_qtb_cfg,
+ [SLAVE_PCIE_EAST_ANOC_CFG] = &qss_pcie_east_anoc_cfg,
+ [SLAVE_PCIE_WEST_ANOC_CFG] = &qss_pcie_west_anoc_cfg,
+ [SLAVE_QDSS_STM] = &xs_qdss_stm,
+ [SLAVE_TCU] = &xs_sys_tcu_cfg,
+};
+
+static const struct regmap_config glymur_cnoc_cfg_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x6600,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_cnoc_cfg = {
+ .config = &glymur_cnoc_cfg_regmap_config,
+ .nodes = cnoc_cfg_nodes,
+ .num_nodes = ARRAY_SIZE(cnoc_cfg_nodes),
+ .bcms = cnoc_cfg_bcms,
+ .num_bcms = ARRAY_SIZE(cnoc_cfg_bcms),
+ .alloc_dyn_id = true,
+};
+
+static struct qcom_icc_bcm * const cnoc_main_bcms[] = {
+ &bcm_cn0,
+};
+
+static struct qcom_icc_node * const cnoc_main_nodes[] = {
+ [MASTER_HSCNOC_CNOC] = &qnm_hscnoc_cnoc,
+ [SLAVE_AOSS] = &qhs_aoss,
+ [SLAVE_IPC_ROUTER_CFG] = &qhs_ipc_router,
+ [SLAVE_SOCCP] = &qhs_soccp,
+ [SLAVE_TME_CFG] = &qhs_tme_cfg,
+ [SLAVE_APPSS] = &qns_apss,
+ [SLAVE_CNOC_CFG] = &qss_cfg,
+ [SLAVE_BOOT_IMEM] = &qxs_boot_imem,
+ [SLAVE_IMEM] = &qxs_imem,
+};
+
+static const struct regmap_config glymur_cnoc_main_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x17080,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_cnoc_main = {
+ .config = &glymur_cnoc_main_regmap_config,
+ .nodes = cnoc_main_nodes,
+ .num_nodes = ARRAY_SIZE(cnoc_main_nodes),
+ .bcms = cnoc_main_bcms,
+ .num_bcms = ARRAY_SIZE(cnoc_main_bcms),
+ .alloc_dyn_id = true,
+};
+
+static struct qcom_icc_bcm * const hscnoc_bcms[] = {
+ &bcm_sh0,
+ &bcm_sh1,
+};
+
+static struct qcom_icc_node * const hscnoc_nodes[] = {
+ [MASTER_GPU_TCU] = &alm_gpu_tcu,
+ [MASTER_PCIE_TCU] = &alm_pcie_qtc,
+ [MASTER_SYS_TCU] = &alm_sys_tcu,
+ [MASTER_APPSS_PROC] = &chm_apps,
+ [MASTER_AGGRE_NOC_EAST] = &qnm_aggre_noc_east,
+ [MASTER_GFX3D] = &qnm_gpu,
+ [MASTER_LPASS_GEM_NOC] = &qnm_lpass,
+ [MASTER_MNOC_HF_MEM_NOC] = &qnm_mnoc_hf,
+ [MASTER_MNOC_SF_MEM_NOC] = &qnm_mnoc_sf,
+ [MASTER_COMPUTE_NOC] = &qnm_nsp_noc,
+ [MASTER_PCIE_EAST] = &qnm_pcie_east,
+ [MASTER_PCIE_WEST] = &qnm_pcie_west,
+ [MASTER_SNOC_SF_MEM_NOC] = &qnm_snoc_sf,
+ [MASTER_WLAN_Q6] = &qxm_wlan_q6,
+ [MASTER_GIC] = &xm_gic,
+ [SLAVE_HSCNOC_CNOC] = &qns_hscnoc_cnoc,
+ [SLAVE_LLCC] = &qns_llcc,
+ [SLAVE_PCIE_EAST] = &qns_pcie_east,
+ [SLAVE_PCIE_WEST] = &qns_pcie_west,
+};
+
+static const struct regmap_config glymur_hscnoc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x93a080,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_hscnoc = {
+ .config = &glymur_hscnoc_regmap_config,
+ .nodes = hscnoc_nodes,
+ .num_nodes = ARRAY_SIZE(hscnoc_nodes),
+ .bcms = hscnoc_bcms,
+ .num_bcms = ARRAY_SIZE(hscnoc_bcms),
+ .alloc_dyn_id = true,
+};
+
+static struct qcom_icc_node * const lpass_ag_noc_nodes[] = {
+ [MASTER_LPIAON_NOC] = &qnm_lpiaon_noc,
+ [SLAVE_LPASS_GEM_NOC] = &qns_lpass_ag_noc_gemnoc,
+};
+
+static const struct regmap_config glymur_lpass_ag_noc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0xe080,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_lpass_ag_noc = {
+ .config = &glymur_lpass_ag_noc_regmap_config,
+ .nodes = lpass_ag_noc_nodes,
+ .num_nodes = ARRAY_SIZE(lpass_ag_noc_nodes),
+ .alloc_dyn_id = true,
+};
+
+static struct qcom_icc_bcm * const lpass_lpiaon_noc_bcms[] = {
+ &bcm_lp0,
+};
+
+static struct qcom_icc_node * const lpass_lpiaon_noc_nodes[] = {
+ [MASTER_LPASS_LPINOC] = &qnm_lpass_lpinoc,
+ [SLAVE_LPIAON_NOC_LPASS_AG_NOC] = &qns_lpass_aggnoc,
+};
+
+static const struct regmap_config glymur_lpass_lpiaon_noc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x19080,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_lpass_lpiaon_noc = {
+ .config = &glymur_lpass_lpiaon_noc_regmap_config,
+ .nodes = lpass_lpiaon_noc_nodes,
+ .num_nodes = ARRAY_SIZE(lpass_lpiaon_noc_nodes),
+ .bcms = lpass_lpiaon_noc_bcms,
+ .num_bcms = ARRAY_SIZE(lpass_lpiaon_noc_bcms),
+ .alloc_dyn_id = true,
+};
+
+static struct qcom_icc_node * const lpass_lpicx_noc_nodes[] = {
+ [MASTER_LPASS_PROC] = &qnm_lpinoc_dsp_qns4m,
+ [SLAVE_LPICX_NOC_LPIAON_NOC] = &qns_lpi_aon_noc,
+};
+
+static const struct regmap_config glymur_lpass_lpicx_noc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x44080,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_lpass_lpicx_noc = {
+ .config = &glymur_lpass_lpicx_noc_regmap_config,
+ .nodes = lpass_lpicx_noc_nodes,
+ .num_nodes = ARRAY_SIZE(lpass_lpicx_noc_nodes),
+ .alloc_dyn_id = true,
+};
+
+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 glymur_mc_virt = {
+ .nodes = mc_virt_nodes,
+ .num_nodes = ARRAY_SIZE(mc_virt_nodes),
+ .bcms = mc_virt_bcms,
+ .num_bcms = ARRAY_SIZE(mc_virt_bcms),
+ .alloc_dyn_id = true,
+};
+
+static struct qcom_icc_bcm * const mmss_noc_bcms[] = {
+ &bcm_mm0,
+ &bcm_mm1,
+};
+
+static struct qcom_icc_node * const mmss_noc_nodes[] = {
+ [MASTER_AV1_ENC] = &qnm_av1_enc,
+ [MASTER_CAMNOC_HF] = &qnm_camnoc_hf,
+ [MASTER_CAMNOC_ICP] = &qnm_camnoc_icp,
+ [MASTER_CAMNOC_SF] = &qnm_camnoc_sf,
+ [MASTER_EVA] = &qnm_eva,
+ [MASTER_MDP] = &qnm_mdp,
+ [MASTER_CDSP_HCP] = &qnm_vapss_hcp,
+ [MASTER_VIDEO] = &qnm_video,
+ [MASTER_VIDEO_CV_PROC] = &qnm_video_cv_cpu,
+ [MASTER_VIDEO_V_PROC] = &qnm_video_v_cpu,
+ [MASTER_CNOC_MNOC_CFG] = &qsm_mnoc_cfg,
+ [SLAVE_MNOC_HF_MEM_NOC] = &qns_mem_noc_hf,
+ [SLAVE_MNOC_SF_MEM_NOC] = &qns_mem_noc_sf,
+ [SLAVE_SERVICE_MNOC] = &srvc_mnoc,
+};
+
+static const struct regmap_config glymur_mmss_noc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x5b800,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_mmss_noc = {
+ .config = &glymur_mmss_noc_regmap_config,
+ .nodes = mmss_noc_nodes,
+ .num_nodes = ARRAY_SIZE(mmss_noc_nodes),
+ .bcms = mmss_noc_bcms,
+ .num_bcms = ARRAY_SIZE(mmss_noc_bcms),
+ .alloc_dyn_id = true,
+};
+
+static struct qcom_icc_node * const nsinoc_nodes[] = {
+ [MASTER_CPUCP] = &xm_cpucp,
+ [SLAVE_NSINOC_SYSTEM_NOC] = &qns_system_noc,
+ [SLAVE_SERVICE_NSINOC] = &srvc_nsinoc,
+};
+
+static const struct regmap_config glymur_nsinoc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x14080,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_nsinoc = {
+ .config = &glymur_nsinoc_regmap_config,
+ .nodes = nsinoc_nodes,
+ .num_nodes = ARRAY_SIZE(nsinoc_nodes),
+ .alloc_dyn_id = true,
+};
+
+static struct qcom_icc_bcm * const nsp_noc_bcms[] = {
+ &bcm_co0,
+};
+
+static struct qcom_icc_node * const nsp_noc_nodes[] = {
+ [MASTER_CDSP_PROC] = &qnm_nsp,
+ [SLAVE_NSP0_HSC_NOC] = &qns_nsp_hscnoc,
+};
+
+static const struct regmap_config glymur_nsp_noc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x21280,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_nsp_noc = {
+ .config = &glymur_nsp_noc_regmap_config,
+ .nodes = nsp_noc_nodes,
+ .num_nodes = ARRAY_SIZE(nsp_noc_nodes),
+ .bcms = nsp_noc_bcms,
+ .num_bcms = ARRAY_SIZE(nsp_noc_bcms),
+ .alloc_dyn_id = true,
+};
+
+static struct qcom_icc_node * const oobm_ss_noc_nodes[] = {
+ [MASTER_OOBMSS_SP_PROC] = &xm_mem_sp,
+ [SLAVE_OOBMSS_SNOC] = &qns_oobmss_snoc,
+};
+
+static const struct regmap_config glymur_oobm_ss_noc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x1e080,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_oobm_ss_noc = {
+ .config = &glymur_oobm_ss_noc_regmap_config,
+ .nodes = oobm_ss_noc_nodes,
+ .num_nodes = ARRAY_SIZE(oobm_ss_noc_nodes),
+ .alloc_dyn_id = true,
+};
+
+static struct qcom_icc_bcm * const pcie_east_anoc_bcms[] = {
+ &bcm_sn6,
+};
+
+static struct qcom_icc_node * const pcie_east_anoc_nodes[] = {
+ [MASTER_PCIE_EAST_ANOC_CFG] = &qsm_pcie_east_anoc_cfg,
+ [MASTER_PCIE_0] = &xm_pcie_0,
+ [MASTER_PCIE_1] = &xm_pcie_1,
+ [MASTER_PCIE_5] = &xm_pcie_5,
+ [SLAVE_PCIE_EAST_MEM_NOC] = &qns_pcie_east_mem_noc,
+ [SLAVE_SERVICE_PCIE_EAST_AGGRE_NOC] = &srvc_pcie_east_aggre_noc,
+};
+
+static const struct regmap_config glymur_pcie_east_anoc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0xf300,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_pcie_east_anoc = {
+ .config = &glymur_pcie_east_anoc_regmap_config,
+ .nodes = pcie_east_anoc_nodes,
+ .num_nodes = ARRAY_SIZE(pcie_east_anoc_nodes),
+ .bcms = pcie_east_anoc_bcms,
+ .num_bcms = ARRAY_SIZE(pcie_east_anoc_bcms),
+ .alloc_dyn_id = true,
+ .qos_requires_clocks = true,
+};
+
+static struct qcom_icc_bcm * const pcie_east_slv_noc_bcms[] = {
+ &bcm_sn6,
+};
+
+static struct qcom_icc_node * const pcie_east_slv_noc_nodes[] = {
+ [MASTER_HSCNOC_PCIE_EAST] = &qnm_hscnoc_pcie_east,
+ [MASTER_CNOC_PCIE_EAST_SLAVE_CFG] = &qsm_cnoc_pcie_east_slave_cfg,
+ [SLAVE_HSCNOC_PCIE_EAST_MS_MPU_CFG] = &qhs_hscnoc_pcie_east_ms_mpu_cfg,
+ [SLAVE_SERVICE_PCIE_EAST] = &srvc_pcie_east,
+ [SLAVE_PCIE_0] = &xs_pcie_0,
+ [SLAVE_PCIE_1] = &xs_pcie_1,
+ [SLAVE_PCIE_5] = &xs_pcie_5,
+};
+
+static const struct regmap_config glymur_pcie_east_slv_noc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0xe080,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_pcie_east_slv_noc = {
+ .config = &glymur_pcie_east_slv_noc_regmap_config,
+ .nodes = pcie_east_slv_noc_nodes,
+ .num_nodes = ARRAY_SIZE(pcie_east_slv_noc_nodes),
+ .bcms = pcie_east_slv_noc_bcms,
+ .num_bcms = ARRAY_SIZE(pcie_east_slv_noc_bcms),
+ .alloc_dyn_id = true,
+};
+
+static struct qcom_icc_bcm * const pcie_west_anoc_bcms[] = {
+ &bcm_sn6,
+};
+
+static struct qcom_icc_node * const pcie_west_anoc_nodes[] = {
+ [MASTER_PCIE_WEST_ANOC_CFG] = &qsm_pcie_west_anoc_cfg,
+ [MASTER_PCIE_2] = &xm_pcie_2,
+ [MASTER_PCIE_3A] = &xm_pcie_3a,
+ [MASTER_PCIE_3B] = &xm_pcie_3b,
+ [MASTER_PCIE_4] = &xm_pcie_4,
+ [MASTER_PCIE_6] = &xm_pcie_6,
+ [SLAVE_PCIE_WEST_MEM_NOC] = &qns_pcie_west_mem_noc,
+ [SLAVE_SERVICE_PCIE_WEST_AGGRE_NOC] = &srvc_pcie_west_aggre_noc,
+};
+
+static const struct regmap_config glymur_pcie_west_anoc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0xf580,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_pcie_west_anoc = {
+ .config = &glymur_pcie_west_anoc_regmap_config,
+ .nodes = pcie_west_anoc_nodes,
+ .num_nodes = ARRAY_SIZE(pcie_west_anoc_nodes),
+ .bcms = pcie_west_anoc_bcms,
+ .num_bcms = ARRAY_SIZE(pcie_west_anoc_bcms),
+ .alloc_dyn_id = true,
+ .qos_requires_clocks = true,
+};
+
+static struct qcom_icc_bcm * const pcie_west_slv_noc_bcms[] = {
+ &bcm_sn6,
+};
+
+static struct qcom_icc_node * const pcie_west_slv_noc_nodes[] = {
+ [MASTER_HSCNOC_PCIE_WEST] = &qnm_hscnoc_pcie_west,
+ [MASTER_CNOC_PCIE_WEST_SLAVE_CFG] = &qsm_cnoc_pcie_west_slave_cfg,
+ [SLAVE_HSCNOC_PCIE_WEST_MS_MPU_CFG] = &qhs_hscnoc_pcie_west_ms_mpu_cfg,
+ [SLAVE_SERVICE_PCIE_WEST] = &srvc_pcie_west,
+ [SLAVE_PCIE_2] = &xs_pcie_2,
+ [SLAVE_PCIE_3A] = &xs_pcie_3a,
+ [SLAVE_PCIE_3B] = &xs_pcie_3b,
+ [SLAVE_PCIE_4] = &xs_pcie_4,
+ [SLAVE_PCIE_6] = &xs_pcie_6,
+};
+
+static const struct regmap_config glymur_pcie_west_slv_noc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0xf180,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_pcie_west_slv_noc = {
+ .config = &glymur_pcie_west_slv_noc_regmap_config,
+ .nodes = pcie_west_slv_noc_nodes,
+ .num_nodes = ARRAY_SIZE(pcie_west_slv_noc_nodes),
+ .bcms = pcie_west_slv_noc_bcms,
+ .num_bcms = ARRAY_SIZE(pcie_west_slv_noc_bcms),
+ .alloc_dyn_id = true,
+};
+
+static struct qcom_icc_bcm * const system_noc_bcms[] = {
+ &bcm_sn0,
+ &bcm_sn1,
+ &bcm_sn2,
+ &bcm_sn3,
+ &bcm_sn4,
+};
+
+static struct qcom_icc_node * const system_noc_nodes[] = {
+ [MASTER_A1NOC_SNOC] = &qnm_aggre1_noc,
+ [MASTER_A2NOC_SNOC] = &qnm_aggre2_noc,
+ [MASTER_A3NOC_SNOC] = &qnm_aggre3_noc,
+ [MASTER_NSINOC_SNOC] = &qnm_nsi_noc,
+ [MASTER_OOBMSS] = &qnm_oobmss,
+ [SLAVE_SNOC_GEM_NOC_SF] = &qns_gemnoc_sf,
+};
+
+static const struct regmap_config glymur_system_noc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x1c080,
+ .fast_io = true,
+};
+
+static const struct qcom_icc_desc glymur_system_noc = {
+ .config = &glymur_system_noc_regmap_config,
+ .nodes = system_noc_nodes,
+ .num_nodes = ARRAY_SIZE(system_noc_nodes),
+ .bcms = system_noc_bcms,
+ .num_bcms = ARRAY_SIZE(system_noc_bcms),
+ .alloc_dyn_id = true,
+};
+
+static const struct of_device_id qnoc_of_match[] = {
+ { .compatible = "qcom,glymur-aggre1-noc", .data = &glymur_aggre1_noc},
+ { .compatible = "qcom,glymur-aggre2-noc", .data = &glymur_aggre2_noc},
+ { .compatible = "qcom,glymur-aggre3-noc", .data = &glymur_aggre3_noc},
+ { .compatible = "qcom,glymur-aggre4-noc", .data = &glymur_aggre4_noc},
+ { .compatible = "qcom,glymur-clk-virt", .data = &glymur_clk_virt},
+ { .compatible = "qcom,glymur-cnoc-cfg", .data = &glymur_cnoc_cfg},
+ { .compatible = "qcom,glymur-cnoc-main", .data = &glymur_cnoc_main},
+ { .compatible = "qcom,glymur-hscnoc", .data = &glymur_hscnoc},
+ { .compatible = "qcom,glymur-lpass-ag-noc", .data = &glymur_lpass_ag_noc},
+ { .compatible = "qcom,glymur-lpass-lpiaon-noc", .data = &glymur_lpass_lpiaon_noc},
+ { .compatible = "qcom,glymur-lpass-lpicx-noc", .data = &glymur_lpass_lpicx_noc},
+ { .compatible = "qcom,glymur-mc-virt", .data = &glymur_mc_virt},
+ { .compatible = "qcom,glymur-mmss-noc", .data = &glymur_mmss_noc},
+ { .compatible = "qcom,glymur-nsinoc", .data = &glymur_nsinoc},
+ { .compatible = "qcom,glymur-nsp-noc", .data = &glymur_nsp_noc},
+ { .compatible = "qcom,glymur-oobm-ss-noc", .data = &glymur_oobm_ss_noc},
+ { .compatible = "qcom,glymur-pcie-east-anoc", .data = &glymur_pcie_east_anoc},
+ { .compatible = "qcom,glymur-pcie-east-slv-noc", .data = &glymur_pcie_east_slv_noc},
+ { .compatible = "qcom,glymur-pcie-west-anoc", .data = &glymur_pcie_west_anoc},
+ { .compatible = "qcom,glymur-pcie-west-slv-noc", .data = &glymur_pcie_west_slv_noc},
+ { .compatible = "qcom,glymur-system-noc", .data = &glymur_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-glymur",
+ .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("GLYMUR NoC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/interconnect/qcom/icc-rpmh.h b/drivers/interconnect/qcom/icc-rpmh.h
index bd8d730249b1..307f48412563 100644
--- a/drivers/interconnect/qcom/icc-rpmh.h
+++ b/drivers/interconnect/qcom/icc-rpmh.h
@@ -53,7 +53,7 @@ struct bcm_db {
u8 reserved;
};
-#define MAX_PORTS 2
+#define MAX_PORTS 4
/**
* struct qcom_icc_qosbox - Qualcomm specific QoS config
diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
index fa61f1d0ea2c..fb6afb8e84f0 100644
--- a/drivers/media/usb/uvc/uvc_driver.c
+++ b/drivers/media/usb/uvc/uvc_driver.c
@@ -538,7 +538,7 @@ static int uvc_parse_streaming(struct uvc_device *dev,
unsigned int nformats = 0, nframes = 0, nintervals = 0;
unsigned int size, i, n, p;
u32 *interval;
- u16 psize;
+ u32 psize;
int ret = -EINVAL;
if (intf->cur_altsetting->desc.bInterfaceSubClass
@@ -774,7 +774,7 @@ static int uvc_parse_streaming(struct uvc_device *dev,
streaming->header.bEndpointAddress);
if (ep == NULL)
continue;
- psize = uvc_endpoint_max_bpi(dev->udev, ep);
+ psize = usb_endpoint_max_periodic_payload(dev->udev, ep);
if (psize > streaming->maxpsize)
streaming->maxpsize = psize;
}
diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c
index 784be9330320..2094e059d7d3 100644
--- a/drivers/media/usb/uvc/uvc_video.c
+++ b/drivers/media/usb/uvc/uvc_video.c
@@ -1870,24 +1870,6 @@ static void uvc_video_stop_transfer(struct uvc_streaming *stream,
}
/*
- * Compute the maximum number of bytes per interval for an endpoint.
- */
-u16 uvc_endpoint_max_bpi(struct usb_device *dev, struct usb_host_endpoint *ep)
-{
- u16 psize;
-
- switch (dev->speed) {
- case USB_SPEED_SUPER:
- case USB_SPEED_SUPER_PLUS:
- return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval);
- default:
- psize = usb_endpoint_maxp(&ep->desc);
- psize *= usb_endpoint_maxp_mult(&ep->desc);
- return psize;
- }
-}
-
-/*
* Initialize isochronous URBs and allocate transfer buffers. The packet size
* is given by the endpoint.
*/
@@ -1897,10 +1879,10 @@ static int uvc_init_video_isoc(struct uvc_streaming *stream,
struct urb *urb;
struct uvc_urb *uvc_urb;
unsigned int npackets, i;
- u16 psize;
+ u32 psize;
u32 size;
- psize = uvc_endpoint_max_bpi(stream->dev->udev, ep);
+ psize = usb_endpoint_max_periodic_payload(stream->dev->udev, ep);
size = stream->ctrl.dwMaxVideoFrameSize;
npackets = uvc_alloc_urb_buffers(stream, size, psize, gfp_flags);
@@ -2043,7 +2025,7 @@ static int uvc_video_start_transfer(struct uvc_streaming *stream,
continue;
/* Check if the bandwidth is high enough. */
- psize = uvc_endpoint_max_bpi(stream->dev->udev, ep);
+ psize = usb_endpoint_max_periodic_payload(stream->dev->udev, ep);
if (psize >= bandwidth && psize < best_psize) {
altsetting = alts->desc.bAlternateSetting;
best_psize = psize;
diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
index 24292efbe47d..ed7bad31f75c 100644
--- a/drivers/media/usb/uvc/uvcvideo.h
+++ b/drivers/media/usb/uvc/uvcvideo.h
@@ -458,7 +458,7 @@ struct uvc_streaming {
struct usb_interface *intf;
int intfnum;
- u16 maxpsize;
+ u32 maxpsize;
struct uvc_streaming_header header;
enum v4l2_buf_type type;
@@ -797,8 +797,6 @@ void uvc_ctrl_cleanup_fh(struct uvc_fh *handle);
/* Utility functions */
struct usb_host_endpoint *uvc_find_endpoint(struct usb_host_interface *alts,
u8 epaddr);
-u16 uvc_endpoint_max_bpi(struct usb_device *dev, struct usb_host_endpoint *ep);
-
/* Quirks support */
void uvc_video_decode_isight(struct uvc_urb *uvc_urb,
struct uvc_buffer *buf,
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index e2e66f5f4fb8..b32a2597d246 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -23,7 +23,6 @@ obj-$(CONFIG_SENSORS_BH1770) += bh1770glc.o
obj-$(CONFIG_SENSORS_APDS990X) += apds990x.o
obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o
obj-$(CONFIG_KGDB_TESTS) += kgdbts.o
-obj-$(CONFIG_TEST_MISC_MINOR) += misc_minor_kunit.o
obj-$(CONFIG_SGI_XP) += sgi-xp/
obj-$(CONFIG_SGI_GRU) += sgi-gru/
obj-$(CONFIG_SMPRO_ERRMON) += smpro-errmon.o
diff --git a/drivers/misc/ad525x_dpot.c b/drivers/misc/ad525x_dpot.c
index 756ef6912b5a..04683b981e54 100644
--- a/drivers/misc/ad525x_dpot.c
+++ b/drivers/misc/ad525x_dpot.c
@@ -73,6 +73,7 @@
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
+#include <linux/string_choices.h>
#include "ad525x_dpot.h"
@@ -418,10 +419,8 @@ static ssize_t sysfs_show_reg(struct device *dev,
s32 value;
if (reg & DPOT_ADDR_OTP_EN)
- return sprintf(buf, "%s\n",
- test_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask) ?
- "enabled" : "disabled");
-
+ return sprintf(buf, "%s\n", str_enabled_disabled(
+ test_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask)));
mutex_lock(&data->update_lock);
value = dpot_read(data, reg);
diff --git a/drivers/misc/amd-sbi/Kconfig b/drivers/misc/amd-sbi/Kconfig
index 4840831c84ca..4aae0733d0fc 100644
--- a/drivers/misc/amd-sbi/Kconfig
+++ b/drivers/misc/amd-sbi/Kconfig
@@ -2,6 +2,7 @@
config AMD_SBRMI_I2C
tristate "AMD side band RMI support"
depends on I2C
+ select REGMAP_I2C
help
Side band RMI over I2C support for AMD out of band management.
diff --git a/drivers/misc/apds990x.c b/drivers/misc/apds990x.c
index e7d73c972f65..58946c4ff1a5 100644
--- a/drivers/misc/apds990x.c
+++ b/drivers/misc/apds990x.c
@@ -984,7 +984,6 @@ static ssize_t apds990x_power_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", !pm_runtime_suspended(dev));
- return 0;
}
static ssize_t apds990x_power_state_store(struct device *dev,
diff --git a/drivers/misc/cardreader/rts5227.c b/drivers/misc/cardreader/rts5227.c
index cd512284bfb3..46444bb47f65 100644
--- a/drivers/misc/cardreader/rts5227.c
+++ b/drivers/misc/cardreader/rts5227.c
@@ -79,6 +79,10 @@ static void rts5227_fetch_vendor_settings(struct rtsx_pcr *pcr)
pcr->sd30_drive_sel_3v3 = rtsx_reg_to_sd30_drive_sel_3v3(reg);
if (rtsx_reg_check_reverse_socket(reg))
pcr->flags |= PCR_REVERSE_SOCKET;
+ if (rtsx_reg_check_cd_reverse(reg))
+ pcr->option.sd_cd_reverse_en = 1;
+ if (rtsx_reg_check_wp_reverse(reg))
+ pcr->option.sd_wp_reverse_en = 1;
}
static void rts5227_init_from_cfg(struct rtsx_pcr *pcr)
@@ -127,8 +131,10 @@ static int rts5227_extra_init_hw(struct rtsx_pcr *pcr)
/* Configure force_clock_req */
if (pcr->flags & PCR_REVERSE_SOCKET)
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x30, 0x30);
- else
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x30, 0x00);
+ else {
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x20, option->sd_cd_reverse_en << 5);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x10, option->sd_wp_reverse_en << 4);
+ }
if (CHK_PCI_PID(pcr, 0x522A))
rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, RTS522A_AUTOLOAD_CFG1,
@@ -350,6 +356,8 @@ void rts5227_init_params(struct rtsx_pcr *pcr)
pcr->ms_pull_ctl_disable_tbl = rts5227_ms_pull_ctl_disable_tbl;
pcr->reg_pm_ctrl3 = PM_CTRL3;
+ pcr->option.sd_cd_reverse_en = 0;
+ pcr->option.sd_wp_reverse_en = 0;
}
static int rts522a_optimize_phy(struct rtsx_pcr *pcr)
@@ -508,5 +516,4 @@ void rts522a_init_params(struct rtsx_pcr *pcr)
pcr->hw_param.interrupt_en |= SD_OC_INT_EN;
pcr->hw_param.ocp_glitch = SD_OCP_GLITCH_10M;
pcr->option.sd_800mA_ocp_thd = RTS522A_OCP_THD_800;
-
}
diff --git a/drivers/misc/cardreader/rts5228.c b/drivers/misc/cardreader/rts5228.c
index 0c7f10bcf6f1..db7e735ac24f 100644
--- a/drivers/misc/cardreader/rts5228.c
+++ b/drivers/misc/cardreader/rts5228.c
@@ -84,6 +84,10 @@ static void rtsx5228_fetch_vendor_settings(struct rtsx_pcr *pcr)
pcr->sd30_drive_sel_3v3 = rtsx_reg_to_sd30_drive_sel_3v3(reg);
if (rtsx_reg_check_reverse_socket(reg))
pcr->flags |= PCR_REVERSE_SOCKET;
+ if (rtsx_reg_check_cd_reverse(reg))
+ pcr->option.sd_cd_reverse_en = 1;
+ if (rtsx_reg_check_wp_reverse(reg))
+ pcr->option.sd_wp_reverse_en = 1;
}
static int rts5228_optimize_phy(struct rtsx_pcr *pcr)
@@ -432,8 +436,10 @@ static int rts5228_extra_init_hw(struct rtsx_pcr *pcr)
if (pcr->flags & PCR_REVERSE_SOCKET)
rtsx_pci_write_register(pcr, PETXCFG, 0x30, 0x30);
- else
- rtsx_pci_write_register(pcr, PETXCFG, 0x30, 0x00);
+ else {
+ rtsx_pci_write_register(pcr, PETXCFG, 0x20, option->sd_cd_reverse_en << 5);
+ rtsx_pci_write_register(pcr, PETXCFG, 0x10, option->sd_wp_reverse_en << 4);
+ }
/*
* If u_force_clkreq_0 is enabled, CLKREQ# PIN will be forced
@@ -720,4 +726,6 @@ void rts5228_init_params(struct rtsx_pcr *pcr)
hw_param->interrupt_en |= SD_OC_INT_EN;
hw_param->ocp_glitch = SD_OCP_GLITCH_800U;
option->sd_800mA_ocp_thd = RTS5228_LDO1_OCP_THD_930;
+ option->sd_cd_reverse_en = 0;
+ option->sd_wp_reverse_en = 0;
}
diff --git a/drivers/misc/cardreader/rts5249.c b/drivers/misc/cardreader/rts5249.c
index 6c81040e18be..38aefd8db452 100644
--- a/drivers/misc/cardreader/rts5249.c
+++ b/drivers/misc/cardreader/rts5249.c
@@ -60,6 +60,7 @@ static void rtsx_base_fetch_vendor_settings(struct rtsx_pcr *pcr)
pci_read_config_dword(pdev, PCR_SETTING_REG1, &reg);
pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", PCR_SETTING_REG1, reg);
+ pci_write_config_dword(pdev, 0x718, 0x0007C000);
if (!rtsx_vendor_setting_valid(reg)) {
pcr_dbg(pcr, "skip fetch vendor setting\n");
@@ -82,6 +83,10 @@ static void rtsx_base_fetch_vendor_settings(struct rtsx_pcr *pcr)
pcr->sd30_drive_sel_3v3 = rtsx_reg_to_sd30_drive_sel_3v3(reg);
if (rtsx_reg_check_reverse_socket(reg))
pcr->flags |= PCR_REVERSE_SOCKET;
+ if (rtsx_reg_check_cd_reverse(reg))
+ pcr->option.sd_cd_reverse_en = 1;
+ if (rtsx_reg_check_wp_reverse(reg))
+ pcr->option.sd_wp_reverse_en = 1;
}
static void rts5249_init_from_cfg(struct rtsx_pcr *pcr)
@@ -254,9 +259,11 @@ static int rts5249_extra_init_hw(struct rtsx_pcr *pcr)
/* Configure driving */
rts5249_fill_driving(pcr, OUTPUT_3V3);
if (pcr->flags & PCR_REVERSE_SOCKET)
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0xB0, 0xB0);
- else
- rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0xB0, 0x80);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x30, 0x30);
+ else {
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x20, option->sd_cd_reverse_en << 5);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x10, option->sd_wp_reverse_en << 4);
+ }
rtsx_pci_send_cmd(pcr, CMD_TIMEOUT_DEF);
@@ -572,6 +579,9 @@ void rts5249_init_params(struct rtsx_pcr *pcr)
option->ltr_l1off_sspwrgate = LTR_L1OFF_SSPWRGATE_5249_DEF;
option->ltr_l1off_snooze_sspwrgate =
LTR_L1OFF_SNOOZE_SSPWRGATE_5249_DEF;
+
+ option->sd_cd_reverse_en = 0;
+ option->sd_wp_reverse_en = 0;
}
static int rts524a_write_phy(struct rtsx_pcr *pcr, u8 addr, u16 val)
diff --git a/drivers/misc/cardreader/rts5264.c b/drivers/misc/cardreader/rts5264.c
index d050c9fff7ac..99a2d5ea6421 100644
--- a/drivers/misc/cardreader/rts5264.c
+++ b/drivers/misc/cardreader/rts5264.c
@@ -527,8 +527,16 @@ static void rts5264_init_from_hw(struct rtsx_pcr *pcr)
pcr->rtd3_en = rts5264_reg_to_rtd3(lval2);
- if (rts5264_reg_check_reverse_socket(lval2))
- pcr->flags |= PCR_REVERSE_SOCKET;
+ if (rts5264_reg_check_reverse_socket(lval2)) {
+ if (is_version_higher_than(pcr, PID_5264, RTS5264_IC_VER_B))
+ pcr->option.sd_cd_reverse_en = 1;
+ else
+ pcr->flags |= PCR_REVERSE_SOCKET;
+ }
+
+ if (rts5264_reg_check_wp_reverse(lval2) &&
+ is_version_higher_than(pcr, PID_5264, RTS5264_IC_VER_B))
+ pcr->option.sd_wp_reverse_en = 1;
pci_read_config_dword(pdev, setting_reg1, &lval1);
pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", setting_reg1, lval1);
@@ -622,8 +630,10 @@ static int rts5264_extra_init_hw(struct rtsx_pcr *pcr)
if (pcr->flags & PCR_REVERSE_SOCKET)
rtsx_pci_write_register(pcr, PETXCFG, 0x30, 0x30);
- else
- rtsx_pci_write_register(pcr, PETXCFG, 0x30, 0x00);
+ else {
+ rtsx_pci_write_register(pcr, PETXCFG, 0x20, option->sd_cd_reverse_en << 5);
+ rtsx_pci_write_register(pcr, PETXCFG, 0x10, option->sd_wp_reverse_en << 4);
+ }
/*
* If u_force_clkreq_0 is enabled, CLKREQ# PIN will be forced
@@ -957,4 +967,6 @@ void rts5264_init_params(struct rtsx_pcr *pcr)
hw_param->interrupt_en |= (SD_OC_INT_EN | SD_OVP_INT_EN);
hw_param->ocp_glitch = SD_OCP_GLITCH_800U | SDVIO_OCP_GLITCH_800U;
option->sd_800mA_ocp_thd = RTS5264_LDO1_OCP_THD_1150;
+ option->sd_cd_reverse_en = 0;
+ option->sd_wp_reverse_en = 0;
}
diff --git a/drivers/misc/cardreader/rts5264.h b/drivers/misc/cardreader/rts5264.h
index f3e81daa708d..611ee253367c 100644
--- a/drivers/misc/cardreader/rts5264.h
+++ b/drivers/misc/cardreader/rts5264.h
@@ -14,6 +14,7 @@
#define rts5264_reg_to_aspm(reg) \
(((~(reg) >> 28) & 0x02) | (((reg) >> 28) & 0x01))
#define rts5264_reg_check_reverse_socket(reg) ((reg) & 0x04)
+#define rts5264_reg_check_wp_reverse(reg) ((reg) & 0x8000)
#define rts5264_reg_to_sd30_drive_sel_1v8(reg) (((reg) >> 22) & 0x03)
#define rts5264_reg_to_sd30_drive_sel_3v3(reg) (((reg) >> 16) & 0x03)
#define rts5264_reg_to_rtd3(reg) ((reg) & 0x08)
diff --git a/drivers/misc/cardreader/rtsx_pcr.h b/drivers/misc/cardreader/rtsx_pcr.h
index 8e5951b61143..40562ff2be13 100644
--- a/drivers/misc/cardreader/rtsx_pcr.h
+++ b/drivers/misc/cardreader/rtsx_pcr.h
@@ -100,6 +100,8 @@ static inline u8 map_sd_drive(int idx)
#define rtsx_reg_to_sd30_drive_sel_3v3(reg) (((reg) >> 5) & 0x03)
#define rtsx_reg_to_card_drive_sel(reg) ((((reg) >> 25) & 0x01) << 6)
#define rtsx_reg_check_reverse_socket(reg) ((reg) & 0x4000)
+#define rtsx_reg_check_cd_reverse(reg) ((reg) & 0x800000)
+#define rtsx_reg_check_wp_reverse(reg) ((reg) & 0x400000)
#define rts5209_reg_to_aspm(reg) (((reg) >> 5) & 0x03)
#define rts5209_reg_check_ms_pmos(reg) (!((reg) & 0x08))
#define rts5209_reg_to_sd30_drive_sel_1v8(reg) (((reg) >> 3) & 0x07)
diff --git a/drivers/misc/dw-xdata-pcie.c b/drivers/misc/dw-xdata-pcie.c
index efd0ca8cc925..a604c0e9c038 100644
--- a/drivers/misc/dw-xdata-pcie.c
+++ b/drivers/misc/dw-xdata-pcie.c
@@ -16,6 +16,7 @@
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/pci.h>
+#include <linux/string_choices.h>
#define DW_XDATA_DRIVER_NAME "dw-xdata-pcie"
@@ -132,7 +133,7 @@ static void dw_xdata_start(struct dw_xdata *dw, bool write)
if (!(status & STATUS_DONE))
dev_dbg(dev, "xData: started %s direction\n",
- write ? "write" : "read");
+ str_write_read(write));
}
static void dw_xdata_perf_meas(struct dw_xdata *dw, u64 *data, bool write)
@@ -195,7 +196,7 @@ static void dw_xdata_perf(struct dw_xdata *dw, u64 *rate, bool write)
mutex_unlock(&dw->mutex);
dev_dbg(dev, "xData: time=%llu us, %s=%llu MB/s\n",
- diff, write ? "write" : "read", *rate);
+ diff, str_write_read(write), *rate);
}
static struct dw_xdata *misc_dev_to_dw(struct miscdevice *misc_dev)
diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig
index 0bef5b93bd6d..4d0ce47aa282 100644
--- a/drivers/misc/eeprom/Kconfig
+++ b/drivers/misc/eeprom/Kconfig
@@ -120,4 +120,22 @@ config EEPROM_EE1004
This driver can also be built as a module. If so, the module
will be called ee1004.
+config EEPROM_M24LR
+ tristate "STMicroelectronics M24LR RFID/NFC EEPROM support"
+ depends on I2C && SYSFS
+ select REGMAP_I2C
+ select NVMEM
+ select NVMEM_SYSFS
+ help
+ This enables support for STMicroelectronics M24LR RFID/NFC EEPROM
+ chips. These dual-interface devices expose two I2C addresses:
+ one for EEPROM memory access and another for control and system
+ configuration (e.g. UID, password handling).
+
+ This driver provides a sysfs interface for control functions and
+ integrates with the nvmem subsystem for EEPROM access.
+
+ To compile this driver as a module, choose M here: the
+ module will be called m24lr.
+
endmenu
diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile
index 65794e526d5d..8f311fd6a4ce 100644
--- a/drivers/misc/eeprom/Makefile
+++ b/drivers/misc/eeprom/Makefile
@@ -7,3 +7,4 @@ obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o
obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o
obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o
obj-$(CONFIG_EEPROM_EE1004) += ee1004.o
+obj-$(CONFIG_EEPROM_M24LR) += m24lr.o
diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c
index 2d0492867054..e2868f7bdb03 100644
--- a/drivers/misc/eeprom/at25.c
+++ b/drivers/misc/eeprom/at25.c
@@ -379,37 +379,49 @@ static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip)
struct at25_data *at25 = container_of(chip, struct at25_data, chip);
u8 sernum[FM25_SN_LEN];
u8 id[FM25_ID_LEN];
+ u32 val;
int i;
strscpy(chip->name, "fm25", sizeof(chip->name));
- /* Get ID of chip */
- fm25_aux_read(at25, id, FM25_RDID, FM25_ID_LEN);
- /* There are inside-out FRAM variations, detect them and reverse the ID bytes */
- if (id[6] == 0x7f && id[2] == 0xc2)
- for (i = 0; i < ARRAY_SIZE(id) / 2; i++) {
- u8 tmp = id[i];
- int j = ARRAY_SIZE(id) - i - 1;
+ if (!device_property_read_u32(dev, "size", &val)) {
+ chip->byte_len = val;
+ } else {
+ /* Get ID of chip */
+ fm25_aux_read(at25, id, FM25_RDID, FM25_ID_LEN);
+ /* There are inside-out FRAM variations, detect them and reverse the ID bytes */
+ if (id[6] == 0x7f && id[2] == 0xc2)
+ for (i = 0; i < ARRAY_SIZE(id) / 2; i++) {
+ u8 tmp = id[i];
+ int j = ARRAY_SIZE(id) - i - 1;
+
+ id[i] = id[j];
+ id[j] = tmp;
+ }
+ if (id[6] != 0xc2) {
+ dev_err(dev, "Error: no Cypress FRAM with device ID (manufacturer ID bank 7: %02x)\n", id[6]);
+ return -ENODEV;
+ }
- id[i] = id[j];
- id[j] = tmp;
+ switch (id[7]) {
+ case 0x21 ... 0x26:
+ chip->byte_len = BIT(id[7] - 0x21 + 4) * 1024;
+ break;
+ case 0x2a ... 0x30:
+ /* CY15B116QN ... CY15B116QN */
+ chip->byte_len = BIT(((id[7] >> 1) & 0xf) + 13);
+ break;
+ default:
+ dev_err(dev, "Error: unsupported size (id %02x)\n", id[7]);
+ return -ENODEV;
}
- if (id[6] != 0xc2) {
- dev_err(dev, "Error: no Cypress FRAM (id %02x)\n", id[6]);
- return -ENODEV;
- }
- switch (id[7]) {
- case 0x21 ... 0x26:
- chip->byte_len = BIT(id[7] - 0x21 + 4) * 1024;
- break;
- case 0x2a ... 0x30:
- /* CY15B116QN ... CY15B116QN */
- chip->byte_len = BIT(((id[7] >> 1) & 0xf) + 13);
- break;
- default:
- dev_err(dev, "Error: unsupported size (id %02x)\n", id[7]);
- return -ENODEV;
+ if (id[8]) {
+ fm25_aux_read(at25, sernum, FM25_RDSN, FM25_SN_LEN);
+ /* Swap byte order */
+ for (i = 0; i < FM25_SN_LEN; i++)
+ at25->sernum[i] = sernum[FM25_SN_LEN - 1 - i];
+ }
}
if (chip->byte_len > 64 * 1024)
@@ -417,13 +429,6 @@ static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip)
else
chip->flags |= EE_ADDR2;
- if (id[8]) {
- fm25_aux_read(at25, sernum, FM25_RDSN, FM25_SN_LEN);
- /* Swap byte order */
- for (i = 0; i < FM25_SN_LEN; i++)
- at25->sernum[i] = sernum[FM25_SN_LEN - 1 - i];
- }
-
chip->page_size = PAGE_SIZE;
return 0;
}
diff --git a/drivers/misc/eeprom/m24lr.c b/drivers/misc/eeprom/m24lr.c
new file mode 100644
index 000000000000..7a9fd45a8e46
--- /dev/null
+++ b/drivers/misc/eeprom/m24lr.c
@@ -0,0 +1,606 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * m24lr.c - Sysfs control interface for ST M24LR series RFID/NFC chips
+ *
+ * Copyright (c) 2025 Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com>
+ *
+ * This driver implements both the sysfs-based control interface and EEPROM
+ * access for STMicroelectronics M24LR series chips (e.g., M24LR04E-R).
+ * It provides access to control registers for features such as password
+ * authentication, memory protection, and device configuration. In addition,
+ * it manages read and write operations to the EEPROM region of the chip.
+ */
+
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/nvmem-provider.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+
+#define M24LR_WRITE_TIMEOUT 25u
+#define M24LR_READ_TIMEOUT (M24LR_WRITE_TIMEOUT)
+
+/**
+ * struct m24lr_chip - describes chip-specific sysfs layout
+ * @sss_len: the length of the sss region
+ * @page_size: chip-specific limit on the maximum number of bytes allowed
+ * in a single write operation.
+ * @eeprom_size: size of the EEPROM in byte
+ *
+ * Supports multiple M24LR chip variants (e.g., M24LRxx) by allowing each
+ * to define its own set of sysfs attributes, depending on its available
+ * registers and features.
+ */
+struct m24lr_chip {
+ unsigned int sss_len;
+ unsigned int page_size;
+ unsigned int eeprom_size;
+};
+
+/**
+ * struct m24lr - core driver data for M24LR chip control
+ * @uid: 64 bits unique identifier stored in the device
+ * @sss_len: the length of the sss region
+ * @page_size: chip-specific limit on the maximum number of bytes allowed
+ * in a single write operation.
+ * @eeprom_size: size of the EEPROM in byte
+ * @ctl_regmap: regmap interface for accessing the system parameter sector
+ * @eeprom_regmap: regmap interface for accessing the EEPROM
+ * @lock: mutex to synchronize operations to the device
+ *
+ * Central data structure holding the state and resources used by the
+ * M24LR device driver.
+ */
+struct m24lr {
+ u64 uid;
+ unsigned int sss_len;
+ unsigned int page_size;
+ unsigned int eeprom_size;
+ struct regmap *ctl_regmap;
+ struct regmap *eeprom_regmap;
+ struct mutex lock; /* synchronize operations to the device */
+};
+
+static const struct regmap_range m24lr_ctl_vo_ranges[] = {
+ regmap_reg_range(0, 63),
+};
+
+static const struct regmap_access_table m24lr_ctl_vo_table = {
+ .yes_ranges = m24lr_ctl_vo_ranges,
+ .n_yes_ranges = ARRAY_SIZE(m24lr_ctl_vo_ranges),
+};
+
+static const struct regmap_config m24lr_ctl_regmap_conf = {
+ .name = "m24lr_ctl",
+ .reg_stride = 1,
+ .reg_bits = 16,
+ .val_bits = 8,
+ .disable_locking = false,
+ .cache_type = REGCACHE_RBTREE,/* Flat can't be used, there's huge gap */
+ .volatile_table = &m24lr_ctl_vo_table,
+};
+
+/* Chip descriptor for M24LR04E-R variant */
+static const struct m24lr_chip m24lr04e_r_chip = {
+ .page_size = 4,
+ .eeprom_size = 512,
+ .sss_len = 4,
+};
+
+/* Chip descriptor for M24LR16E-R variant */
+static const struct m24lr_chip m24lr16e_r_chip = {
+ .page_size = 4,
+ .eeprom_size = 2048,
+ .sss_len = 16,
+};
+
+/* Chip descriptor for M24LR64E-R variant */
+static const struct m24lr_chip m24lr64e_r_chip = {
+ .page_size = 4,
+ .eeprom_size = 8192,
+ .sss_len = 64,
+};
+
+static const struct i2c_device_id m24lr_ids[] = {
+ { "m24lr04e-r", (kernel_ulong_t)&m24lr04e_r_chip},
+ { "m24lr16e-r", (kernel_ulong_t)&m24lr16e_r_chip},
+ { "m24lr64e-r", (kernel_ulong_t)&m24lr64e_r_chip},
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, m24lr_ids);
+
+static const struct of_device_id m24lr_of_match[] = {
+ { .compatible = "st,m24lr04e-r", .data = &m24lr04e_r_chip},
+ { .compatible = "st,m24lr16e-r", .data = &m24lr16e_r_chip},
+ { .compatible = "st,m24lr64e-r", .data = &m24lr64e_r_chip},
+ { }
+};
+MODULE_DEVICE_TABLE(of, m24lr_of_match);
+
+/**
+ * m24lr_regmap_read - read data using regmap with retry on failure
+ * @regmap: regmap instance for the device
+ * @buf: buffer to store the read data
+ * @size: number of bytes to read
+ * @offset: starting register address
+ *
+ * Attempts to read a block of data from the device with retries and timeout.
+ * Some M24LR chips may transiently NACK reads (e.g., during internal write
+ * cycles), so this function retries with a short sleep until the timeout
+ * expires.
+ *
+ * Returns:
+ * Number of bytes read on success,
+ * -ETIMEDOUT if the read fails within the timeout window.
+ */
+static ssize_t m24lr_regmap_read(struct regmap *regmap, u8 *buf,
+ size_t size, unsigned int offset)
+{
+ int err;
+ unsigned long timeout, read_time;
+ ssize_t ret = -ETIMEDOUT;
+
+ timeout = jiffies + msecs_to_jiffies(M24LR_READ_TIMEOUT);
+ do {
+ read_time = jiffies;
+
+ err = regmap_bulk_read(regmap, offset, buf, size);
+ if (!err) {
+ ret = size;
+ break;
+ }
+
+ usleep_range(1000, 2000);
+ } while (time_before(read_time, timeout));
+
+ return ret;
+}
+
+/**
+ * m24lr_regmap_write - write data using regmap with retry on failure
+ * @regmap: regmap instance for the device
+ * @buf: buffer containing the data to write
+ * @size: number of bytes to write
+ * @offset: starting register address
+ *
+ * Attempts to write a block of data to the device with retries and a timeout.
+ * Some M24LR devices may NACK I2C writes while an internal write operation
+ * is in progress. This function retries the write operation with a short delay
+ * until it succeeds or the timeout is reached.
+ *
+ * Returns:
+ * Number of bytes written on success,
+ * -ETIMEDOUT if the write fails within the timeout window.
+ */
+static ssize_t m24lr_regmap_write(struct regmap *regmap, const u8 *buf,
+ size_t size, unsigned int offset)
+{
+ int err;
+ unsigned long timeout, write_time;
+ ssize_t ret = -ETIMEDOUT;
+
+ timeout = jiffies + msecs_to_jiffies(M24LR_WRITE_TIMEOUT);
+
+ do {
+ write_time = jiffies;
+
+ err = regmap_bulk_write(regmap, offset, buf, size);
+ if (!err) {
+ ret = size;
+ break;
+ }
+
+ usleep_range(1000, 2000);
+ } while (time_before(write_time, timeout));
+
+ return ret;
+}
+
+static ssize_t m24lr_read(struct m24lr *m24lr, u8 *buf, size_t size,
+ unsigned int offset, bool is_eeprom)
+{
+ struct regmap *regmap;
+ ssize_t ret;
+
+ if (is_eeprom)
+ regmap = m24lr->eeprom_regmap;
+ else
+ regmap = m24lr->ctl_regmap;
+
+ mutex_lock(&m24lr->lock);
+ ret = m24lr_regmap_read(regmap, buf, size, offset);
+ mutex_unlock(&m24lr->lock);
+
+ return ret;
+}
+
+/**
+ * m24lr_write - write buffer to M24LR device with page alignment handling
+ * @m24lr: pointer to driver context
+ * @buf: data buffer to write
+ * @size: number of bytes to write
+ * @offset: target register address in the device
+ * @is_eeprom: true if the write should target the EEPROM,
+ * false if it should target the system parameters sector.
+ *
+ * Writes data to the M24LR device using regmap, split into chunks no larger
+ * than page_size to respect device-specific write limitations (e.g., page
+ * size or I2C hold-time concerns). Each chunk is aligned to the page boundary
+ * defined by page_size.
+ *
+ * Returns:
+ * Total number of bytes written on success,
+ * A negative error code if any write fails.
+ */
+static ssize_t m24lr_write(struct m24lr *m24lr, const u8 *buf, size_t size,
+ unsigned int offset, bool is_eeprom)
+{
+ unsigned int n, next_sector;
+ struct regmap *regmap;
+ ssize_t ret = 0;
+ ssize_t err;
+
+ if (is_eeprom)
+ regmap = m24lr->eeprom_regmap;
+ else
+ regmap = m24lr->ctl_regmap;
+
+ n = min_t(unsigned int, size, m24lr->page_size);
+ next_sector = roundup(offset + 1, m24lr->page_size);
+ if (offset + n > next_sector)
+ n = next_sector - offset;
+
+ mutex_lock(&m24lr->lock);
+ while (n) {
+ err = m24lr_regmap_write(regmap, buf + offset, n, offset);
+ if (IS_ERR_VALUE(err)) {
+ if (!ret)
+ ret = err;
+
+ break;
+ }
+
+ offset += n;
+ size -= n;
+ ret += n;
+ n = min_t(unsigned int, size, m24lr->page_size);
+ }
+ mutex_unlock(&m24lr->lock);
+
+ return ret;
+}
+
+/**
+ * m24lr_write_pass - Write password to M24LR043-R using secure format
+ * @m24lr: Pointer to device control structure
+ * @buf: Input buffer containing hex-encoded password
+ * @count: Number of bytes in @buf
+ * @code: Operation code to embed between password copies
+ *
+ * This function parses a 4-byte password, encodes it in big-endian format,
+ * and constructs a 9-byte sequence of the form:
+ *
+ * [BE(password), code, BE(password)]
+ *
+ * The result is written to register 0x0900 (2304), which is the password
+ * register in M24LR04E-R chip.
+ *
+ * Return: Number of bytes written on success, or negative error code on failure
+ */
+static ssize_t m24lr_write_pass(struct m24lr *m24lr, const char *buf,
+ size_t count, u8 code)
+{
+ __be32 be_pass;
+ u8 output[9];
+ ssize_t ret;
+ u32 pass;
+ int err;
+
+ if (!count)
+ return -EINVAL;
+
+ if (count > 8)
+ return -EINVAL;
+
+ err = kstrtou32(buf, 16, &pass);
+ if (err)
+ return err;
+
+ be_pass = cpu_to_be32(pass);
+
+ memcpy(output, &be_pass, sizeof(be_pass));
+ output[4] = code;
+ memcpy(output + 5, &be_pass, sizeof(be_pass));
+
+ mutex_lock(&m24lr->lock);
+ ret = m24lr_regmap_write(m24lr->ctl_regmap, output, 9, 2304);
+ mutex_unlock(&m24lr->lock);
+
+ return ret;
+}
+
+static ssize_t m24lr_read_reg_le(struct m24lr *m24lr, u64 *val,
+ unsigned int reg_addr,
+ unsigned int reg_size)
+{
+ ssize_t ret;
+ __le64 input = 0;
+
+ ret = m24lr_read(m24lr, (u8 *)&input, reg_size, reg_addr, false);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ if (ret != reg_size)
+ return -EINVAL;
+
+ switch (reg_size) {
+ case 1:
+ *val = *(u8 *)&input;
+ break;
+ case 2:
+ *val = le16_to_cpu((__le16)input);
+ break;
+ case 4:
+ *val = le32_to_cpu((__le32)input);
+ break;
+ case 8:
+ *val = le64_to_cpu((__le64)input);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int m24lr_nvmem_read(void *priv, unsigned int offset, void *val,
+ size_t bytes)
+{
+ ssize_t err;
+ struct m24lr *m24lr = priv;
+
+ if (!bytes)
+ return bytes;
+
+ if (offset + bytes > m24lr->eeprom_size)
+ return -EINVAL;
+
+ err = m24lr_read(m24lr, val, bytes, offset, true);
+ if (IS_ERR_VALUE(err))
+ return err;
+
+ return 0;
+}
+
+static int m24lr_nvmem_write(void *priv, unsigned int offset, void *val,
+ size_t bytes)
+{
+ ssize_t err;
+ struct m24lr *m24lr = priv;
+
+ if (!bytes)
+ return -EINVAL;
+
+ if (offset + bytes > m24lr->eeprom_size)
+ return -EINVAL;
+
+ err = m24lr_write(m24lr, val, bytes, offset, true);
+ if (IS_ERR_VALUE(err))
+ return err;
+
+ return 0;
+}
+
+static ssize_t m24lr_ctl_sss_read(struct file *filep, struct kobject *kobj,
+ const struct bin_attribute *attr, char *buf,
+ loff_t offset, size_t count)
+{
+ struct m24lr *m24lr = attr->private;
+
+ if (!count)
+ return count;
+
+ if (size_add(offset, count) > m24lr->sss_len)
+ return -EINVAL;
+
+ return m24lr_read(m24lr, buf, count, offset, false);
+}
+
+static ssize_t m24lr_ctl_sss_write(struct file *filep, struct kobject *kobj,
+ const struct bin_attribute *attr, char *buf,
+ loff_t offset, size_t count)
+{
+ struct m24lr *m24lr = attr->private;
+
+ if (!count)
+ return -EINVAL;
+
+ if (size_add(offset, count) > m24lr->sss_len)
+ return -EINVAL;
+
+ return m24lr_write(m24lr, buf, count, offset, false);
+}
+static BIN_ATTR(sss, 0600, m24lr_ctl_sss_read, m24lr_ctl_sss_write, 0);
+
+static ssize_t new_pass_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev));
+
+ return m24lr_write_pass(m24lr, buf, count, 7);
+}
+static DEVICE_ATTR_WO(new_pass);
+
+static ssize_t unlock_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev));
+
+ return m24lr_write_pass(m24lr, buf, count, 9);
+}
+static DEVICE_ATTR_WO(unlock);
+
+static ssize_t uid_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev));
+
+ return sysfs_emit(buf, "%llx\n", m24lr->uid);
+}
+static DEVICE_ATTR_RO(uid);
+
+static ssize_t total_sectors_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev));
+
+ return sysfs_emit(buf, "%x\n", m24lr->sss_len);
+}
+static DEVICE_ATTR_RO(total_sectors);
+
+static struct attribute *m24lr_ctl_dev_attrs[] = {
+ &dev_attr_unlock.attr,
+ &dev_attr_new_pass.attr,
+ &dev_attr_uid.attr,
+ &dev_attr_total_sectors.attr,
+ NULL,
+};
+
+static const struct m24lr_chip *m24lr_get_chip(struct device *dev)
+{
+ const struct m24lr_chip *ret;
+ const struct i2c_device_id *id;
+
+ id = i2c_match_id(m24lr_ids, to_i2c_client(dev));
+
+ if (dev->of_node && of_match_device(m24lr_of_match, dev))
+ ret = of_device_get_match_data(dev);
+ else if (id)
+ ret = (void *)id->driver_data;
+ else
+ ret = acpi_device_get_match_data(dev);
+
+ return ret;
+}
+
+static int m24lr_probe(struct i2c_client *client)
+{
+ struct regmap_config eeprom_regmap_conf = {0};
+ struct nvmem_config nvmem_conf = {0};
+ struct device *dev = &client->dev;
+ struct i2c_client *eeprom_client;
+ const struct m24lr_chip *chip;
+ struct regmap *eeprom_regmap;
+ struct nvmem_device *nvmem;
+ struct regmap *ctl_regmap;
+ struct m24lr *m24lr;
+ u32 regs[2];
+ long err;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -EOPNOTSUPP;
+
+ chip = m24lr_get_chip(dev);
+ if (!chip)
+ return -ENODEV;
+
+ m24lr = devm_kzalloc(dev, sizeof(struct m24lr), GFP_KERNEL);
+ if (!m24lr)
+ return -ENOMEM;
+
+ err = device_property_read_u32_array(dev, "reg", regs, ARRAY_SIZE(regs));
+ if (err)
+ return dev_err_probe(dev, err, "Failed to read 'reg' property\n");
+
+ /* Create a second I2C client for the eeprom interface */
+ eeprom_client = devm_i2c_new_dummy_device(dev, client->adapter, regs[1]);
+ if (IS_ERR(eeprom_client))
+ return dev_err_probe(dev, PTR_ERR(eeprom_client),
+ "Failed to create dummy I2C client for the EEPROM\n");
+
+ ctl_regmap = devm_regmap_init_i2c(client, &m24lr_ctl_regmap_conf);
+ if (IS_ERR(ctl_regmap))
+ return dev_err_probe(dev, PTR_ERR(ctl_regmap),
+ "Failed to init regmap\n");
+
+ eeprom_regmap_conf.name = "m24lr_eeprom";
+ eeprom_regmap_conf.reg_bits = 16;
+ eeprom_regmap_conf.val_bits = 8;
+ eeprom_regmap_conf.disable_locking = true;
+ eeprom_regmap_conf.max_register = chip->eeprom_size - 1;
+
+ eeprom_regmap = devm_regmap_init_i2c(eeprom_client,
+ &eeprom_regmap_conf);
+ if (IS_ERR(eeprom_regmap))
+ return dev_err_probe(dev, PTR_ERR(eeprom_regmap),
+ "Failed to init regmap\n");
+
+ mutex_init(&m24lr->lock);
+ m24lr->sss_len = chip->sss_len;
+ m24lr->page_size = chip->page_size;
+ m24lr->eeprom_size = chip->eeprom_size;
+ m24lr->eeprom_regmap = eeprom_regmap;
+ m24lr->ctl_regmap = ctl_regmap;
+
+ nvmem_conf.dev = &eeprom_client->dev;
+ nvmem_conf.owner = THIS_MODULE;
+ nvmem_conf.type = NVMEM_TYPE_EEPROM;
+ nvmem_conf.reg_read = m24lr_nvmem_read;
+ nvmem_conf.reg_write = m24lr_nvmem_write;
+ nvmem_conf.size = chip->eeprom_size;
+ nvmem_conf.word_size = 1;
+ nvmem_conf.stride = 1;
+ nvmem_conf.priv = m24lr;
+
+ nvmem = devm_nvmem_register(dev, &nvmem_conf);
+ if (IS_ERR(nvmem))
+ return dev_err_probe(dev, PTR_ERR(nvmem),
+ "Failed to register nvmem\n");
+
+ i2c_set_clientdata(client, m24lr);
+ i2c_set_clientdata(eeprom_client, m24lr);
+
+ bin_attr_sss.size = chip->sss_len;
+ bin_attr_sss.private = m24lr;
+ err = sysfs_create_bin_file(&dev->kobj, &bin_attr_sss);
+ if (err)
+ return dev_err_probe(dev, err,
+ "Failed to create sss bin file\n");
+
+ /* test by reading the uid, if success store it */
+ err = m24lr_read_reg_le(m24lr, &m24lr->uid, 2324, sizeof(m24lr->uid));
+ if (IS_ERR_VALUE(err))
+ goto remove_bin_file;
+
+ return 0;
+
+remove_bin_file:
+ sysfs_remove_bin_file(&dev->kobj, &bin_attr_sss);
+
+ return err;
+}
+
+static void m24lr_remove(struct i2c_client *client)
+{
+ sysfs_remove_bin_file(&client->dev.kobj, &bin_attr_sss);
+}
+
+ATTRIBUTE_GROUPS(m24lr_ctl_dev);
+
+static struct i2c_driver m24lr_driver = {
+ .driver = {
+ .name = "m24lr",
+ .of_match_table = m24lr_of_match,
+ .dev_groups = m24lr_ctl_dev_groups,
+ },
+ .probe = m24lr_probe,
+ .remove = m24lr_remove,
+ .id_table = m24lr_ids,
+};
+module_i2c_driver(m24lr_driver);
+
+MODULE_AUTHOR("Abd-Alrhman Masalkhi");
+MODULE_DESCRIPTION("st m24lr control driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c
index 53e88a1bc430..8e1d97873423 100644
--- a/drivers/misc/fastrpc.c
+++ b/drivers/misc/fastrpc.c
@@ -27,8 +27,7 @@
#define MDSP_DOMAIN_ID (1)
#define SDSP_DOMAIN_ID (2)
#define CDSP_DOMAIN_ID (3)
-#define CDSP1_DOMAIN_ID (4)
-#define FASTRPC_DEV_MAX 5 /* adsp, mdsp, slpi, cdsp, cdsp1 */
+#define GDSP_DOMAIN_ID (4)
#define FASTRPC_MAX_SESSIONS 14
#define FASTRPC_MAX_VMIDS 16
#define FASTRPC_ALIGN 128
@@ -106,8 +105,6 @@
#define miscdev_to_fdevice(d) container_of(d, struct fastrpc_device, miscdev)
-static const char *domains[FASTRPC_DEV_MAX] = { "adsp", "mdsp",
- "sdsp", "cdsp", "cdsp1" };
struct fastrpc_phy_page {
u64 addr; /* physical address */
u64 size; /* size of contiguous region */
@@ -1723,7 +1720,6 @@ static int fastrpc_get_info_from_kernel(struct fastrpc_ioctl_capability *cap,
uint32_t attribute_id = cap->attribute_id;
uint32_t *dsp_attributes;
unsigned long flags;
- uint32_t domain = cap->domain;
int err;
spin_lock_irqsave(&cctx->lock, flags);
@@ -1741,7 +1737,7 @@ static int fastrpc_get_info_from_kernel(struct fastrpc_ioctl_capability *cap,
err = fastrpc_get_info_from_dsp(fl, dsp_attributes, FASTRPC_MAX_DSP_ATTRIBUTES);
if (err == DSP_UNSUPPORTED_API) {
dev_info(&cctx->rpdev->dev,
- "Warning: DSP capabilities not supported on domain: %d\n", domain);
+ "Warning: DSP capabilities not supported\n");
kfree(dsp_attributes);
return -EOPNOTSUPP;
} else if (err) {
@@ -1769,17 +1765,6 @@ static int fastrpc_get_dsp_info(struct fastrpc_user *fl, char __user *argp)
return -EFAULT;
cap.capability = 0;
- if (cap.domain >= FASTRPC_DEV_MAX) {
- dev_err(&fl->cctx->rpdev->dev, "Error: Invalid domain id:%d, err:%d\n",
- cap.domain, err);
- return -ECHRNG;
- }
-
- /* Fastrpc Capablities does not support modem domain */
- if (cap.domain == MDSP_DOMAIN_ID) {
- dev_err(&fl->cctx->rpdev->dev, "Error: modem not supported %d\n", err);
- return -ECHRNG;
- }
if (cap.attribute_id >= FASTRPC_MAX_DSP_ATTRIBUTES) {
dev_err(&fl->cctx->rpdev->dev, "Error: invalid attribute: %d, err: %d\n",
@@ -2255,6 +2240,22 @@ static int fastrpc_device_register(struct device *dev, struct fastrpc_channel_ct
return err;
}
+static int fastrpc_get_domain_id(const char *domain)
+{
+ if (!strncmp(domain, "adsp", 4))
+ return ADSP_DOMAIN_ID;
+ else if (!strncmp(domain, "cdsp", 4))
+ return CDSP_DOMAIN_ID;
+ else if (!strncmp(domain, "mdsp", 4))
+ return MDSP_DOMAIN_ID;
+ else if (!strncmp(domain, "sdsp", 4))
+ return SDSP_DOMAIN_ID;
+ else if (!strncmp(domain, "gdsp", 4))
+ return GDSP_DOMAIN_ID;
+
+ return -EINVAL;
+}
+
static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev)
{
struct device *rdev = &rpdev->dev;
@@ -2270,15 +2271,10 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev)
return err;
}
- for (i = 0; i < FASTRPC_DEV_MAX; i++) {
- if (!strcmp(domains[i], domain)) {
- domain_id = i;
- break;
- }
- }
+ domain_id = fastrpc_get_domain_id(domain);
if (domain_id < 0) {
- dev_info(rdev, "FastRPC Invalid Domain ID %d\n", domain_id);
+ dev_info(rdev, "FastRPC Domain %s not supported\n", domain);
return -EINVAL;
}
@@ -2325,21 +2321,21 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev)
case ADSP_DOMAIN_ID:
case MDSP_DOMAIN_ID:
case SDSP_DOMAIN_ID:
- /* Unsigned PD offloading is only supported on CDSP and CDSP1 */
+ /* Unsigned PD offloading is only supported on CDSP and GDSP */
data->unsigned_support = false;
- err = fastrpc_device_register(rdev, data, secure_dsp, domains[domain_id]);
+ err = fastrpc_device_register(rdev, data, secure_dsp, domain);
if (err)
goto err_free_data;
break;
case CDSP_DOMAIN_ID:
- case CDSP1_DOMAIN_ID:
+ case GDSP_DOMAIN_ID:
data->unsigned_support = true;
/* Create both device nodes so that we can allow both Signed and Unsigned PD */
- err = fastrpc_device_register(rdev, data, true, domains[domain_id]);
+ err = fastrpc_device_register(rdev, data, true, domain);
if (err)
goto err_free_data;
- err = fastrpc_device_register(rdev, data, false, domains[domain_id]);
+ err = fastrpc_device_register(rdev, data, false, domain);
if (err)
goto err_deregister_fdev;
break;
diff --git a/drivers/misc/genwqe/card_ddcb.c b/drivers/misc/genwqe/card_ddcb.c
index 500b1feaf1f6..fd7d5cd50d39 100644
--- a/drivers/misc/genwqe/card_ddcb.c
+++ b/drivers/misc/genwqe/card_ddcb.c
@@ -923,7 +923,7 @@ int __genwqe_execute_raw_ddcb(struct genwqe_dev *cd,
}
if (cmd->asv_length > DDCB_ASV_LENGTH) {
dev_err(&pci_dev->dev, "[%s] err: wrong asv_length of %d\n",
- __func__, cmd->asiv_length);
+ __func__, cmd->asv_length);
return -EINVAL;
}
rc = __genwqe_enqueue_ddcb(cd, req, f_flags);
diff --git a/drivers/misc/hisi_hikey_usb.c b/drivers/misc/hisi_hikey_usb.c
index ffe7b945a298..2c6e448a47f1 100644
--- a/drivers/misc/hisi_hikey_usb.c
+++ b/drivers/misc/hisi_hikey_usb.c
@@ -18,6 +18,7 @@
#include <linux/property.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
+#include <linux/string_choices.h>
#include <linux/usb/role.h>
#define DEVICE_DRIVER_NAME "hisi_hikey_usb"
@@ -67,7 +68,7 @@ static void hub_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, int value)
if (ret)
dev_err(hisi_hikey_usb->dev,
"Can't switch regulator state to %s\n",
- value ? "enabled" : "disabled");
+ str_enabled_disabled(value));
}
static void usb_switch_ctrl(struct hisi_hikey_usb *hisi_hikey_usb,
diff --git a/drivers/misc/ibmasm/ibmasmfs.c b/drivers/misc/ibmasm/ibmasmfs.c
index 5372ed2a363e..b26c930e3edb 100644
--- a/drivers/misc/ibmasm/ibmasmfs.c
+++ b/drivers/misc/ibmasm/ibmasmfs.c
@@ -525,15 +525,9 @@ static ssize_t remote_settings_file_write(struct file *file, const char __user *
if (*offset != 0)
return 0;
- buff = kzalloc (count + 1, GFP_KERNEL);
- if (!buff)
- return -ENOMEM;
-
-
- if (copy_from_user(buff, ubuff, count)) {
- kfree(buff);
- return -EFAULT;
- }
+ buff = memdup_user_nul(ubuff, count);
+ if (IS_ERR(buff))
+ return PTR_ERR(buff);
value = simple_strtoul(buff, NULL, 10);
writel(value, address);
diff --git a/drivers/misc/lis3lv02d/Kconfig b/drivers/misc/lis3lv02d/Kconfig
index 56005243a230..9d546a42a563 100644
--- a/drivers/misc/lis3lv02d/Kconfig
+++ b/drivers/misc/lis3lv02d/Kconfig
@@ -4,7 +4,7 @@
#
config SENSORS_LIS3_SPI
- tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (SPI)"
+ tristate "STMicroelectronics LIS3LV02Dx three-axis digital accelerometer (SPI)"
depends on !ACPI && SPI_MASTER && INPUT
select SENSORS_LIS3LV02D
help
@@ -20,7 +20,7 @@ config SENSORS_LIS3_SPI
is called lis3lv02d_spi.
config SENSORS_LIS3_I2C
- tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (I2C)"
+ tristate "STMicroelectronics LIS3LV02Dx three-axis digital accelerometer (I2C)"
depends on I2C && INPUT
select SENSORS_LIS3LV02D
help
diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c
index 90dba20b2de7..e6a1d3534663 100644
--- a/drivers/misc/mei/bus-fixup.c
+++ b/drivers/misc/mei/bus-fixup.c
@@ -386,7 +386,7 @@ static int mei_nfc_if_version(struct mei_cl *cl,
ret = __mei_cl_send(cl, (u8 *)&cmd, sizeof(cmd), 0,
MEI_CL_IO_TX_BLOCKING);
if (ret < 0) {
- dev_err(bus->dev, "Could not send IF version cmd ret = %d\n", ret);
+ dev_err(&bus->dev, "Could not send IF version cmd ret = %d\n", ret);
return ret;
}
@@ -401,14 +401,14 @@ static int mei_nfc_if_version(struct mei_cl *cl,
bytes_recv = __mei_cl_recv(cl, (u8 *)reply, if_version_length, &vtag,
0, 0);
if (bytes_recv < 0 || (size_t)bytes_recv < if_version_length) {
- dev_err(bus->dev, "Could not read IF version ret = %d\n", bytes_recv);
+ dev_err(&bus->dev, "Could not read IF version ret = %d\n", bytes_recv);
ret = -EIO;
goto err;
}
memcpy(ver, reply->data, sizeof(*ver));
- dev_info(bus->dev, "NFC MEI VERSION: IVN 0x%x Vendor ID 0x%x Type 0x%x\n",
+ dev_info(&bus->dev, "NFC MEI VERSION: IVN 0x%x Vendor ID 0x%x Type 0x%x\n",
ver->fw_ivn, ver->vendor_id, ver->radio_type);
err:
diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c
index 09aae8f9d225..2c810ab12e62 100644
--- a/drivers/misc/mei/bus.c
+++ b/drivers/misc/mei/bus.c
@@ -650,7 +650,7 @@ EXPORT_SYMBOL_GPL(mei_cldev_enabled);
*/
static bool mei_cl_bus_module_get(struct mei_cl_device *cldev)
{
- return try_module_get(cldev->bus->dev->driver->owner);
+ return try_module_get(cldev->bus->parent->driver->owner);
}
/**
@@ -660,7 +660,7 @@ static bool mei_cl_bus_module_get(struct mei_cl_device *cldev)
*/
static void mei_cl_bus_module_put(struct mei_cl_device *cldev)
{
- module_put(cldev->bus->dev->driver->owner);
+ module_put(cldev->bus->parent->driver->owner);
}
/**
@@ -827,7 +827,7 @@ int mei_cldev_enable(struct mei_cl_device *cldev)
ret = mei_cl_connect(cl, cldev->me_cl, NULL);
if (ret < 0) {
- dev_err(&cldev->dev, "cannot connect\n");
+ dev_dbg(&cldev->dev, "cannot connect\n");
mei_cl_bus_vtag_free(cldev);
}
@@ -1298,16 +1298,20 @@ static const struct bus_type mei_cl_bus_type = {
static struct mei_device *mei_dev_bus_get(struct mei_device *bus)
{
- if (bus)
- get_device(bus->dev);
+ if (bus) {
+ get_device(&bus->dev);
+ get_device(bus->parent);
+ }
return bus;
}
static void mei_dev_bus_put(struct mei_device *bus)
{
- if (bus)
- put_device(bus->dev);
+ if (bus) {
+ put_device(bus->parent);
+ put_device(&bus->dev);
+ }
}
static void mei_cl_bus_dev_release(struct device *dev)
@@ -1341,7 +1345,7 @@ static const struct device_type mei_cl_device_type = {
static inline void mei_cl_bus_set_name(struct mei_cl_device *cldev)
{
dev_set_name(&cldev->dev, "%s-%pUl",
- dev_name(cldev->bus->dev),
+ dev_name(cldev->bus->parent),
mei_me_cl_uuid(cldev->me_cl));
}
@@ -1370,7 +1374,7 @@ static struct mei_cl_device *mei_cl_bus_dev_alloc(struct mei_device *bus,
}
device_initialize(&cldev->dev);
- cldev->dev.parent = bus->dev;
+ cldev->dev.parent = bus->parent;
cldev->dev.bus = &mei_cl_bus_type;
cldev->dev.type = &mei_cl_device_type;
cldev->bus = mei_dev_bus_get(bus);
@@ -1505,7 +1509,7 @@ static void mei_cl_bus_dev_init(struct mei_device *bus,
WARN_ON(!mutex_is_locked(&bus->cl_bus_lock));
- dev_dbg(bus->dev, "initializing %pUl", mei_me_cl_uuid(me_cl));
+ dev_dbg(&bus->dev, "initializing %pUl", mei_me_cl_uuid(me_cl));
if (me_cl->bus_added)
return;
@@ -1556,7 +1560,7 @@ static void mei_cl_bus_rescan(struct mei_device *bus)
}
mutex_unlock(&bus->cl_bus_lock);
- dev_dbg(bus->dev, "rescan end");
+ dev_dbg(&bus->dev, "rescan end");
}
void mei_cl_bus_rescan_work(struct work_struct *work)
diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c
index 3db07d2a881f..159e8b841564 100644
--- a/drivers/misc/mei/client.c
+++ b/drivers/misc/mei/client.c
@@ -262,7 +262,7 @@ void mei_me_cl_rm_by_uuid(struct mei_device *dev, const uuid_le *uuid)
{
struct mei_me_client *me_cl;
- dev_dbg(dev->dev, "remove %pUl\n", uuid);
+ dev_dbg(&dev->dev, "remove %pUl\n", uuid);
down_write(&dev->me_clients_rwsem);
me_cl = __mei_me_cl_by_uuid(dev, uuid);
@@ -635,12 +635,12 @@ int mei_cl_link(struct mei_cl *cl)
id = find_first_zero_bit(dev->host_clients_map, MEI_CLIENTS_MAX);
if (id >= MEI_CLIENTS_MAX) {
- dev_err(dev->dev, "id exceeded %d", MEI_CLIENTS_MAX);
+ dev_err(&dev->dev, "id exceeded %d", MEI_CLIENTS_MAX);
return -EMFILE;
}
if (dev->open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) {
- dev_err(dev->dev, "open_handle_count exceeded %d",
+ dev_err(&dev->dev, "open_handle_count exceeded %d",
MEI_MAX_OPEN_HANDLE_COUNT);
return -EMFILE;
}
@@ -709,9 +709,9 @@ void mei_host_client_init(struct mei_device *dev)
schedule_work(&dev->bus_rescan_work);
- pm_runtime_mark_last_busy(dev->dev);
- dev_dbg(dev->dev, "rpm: autosuspend\n");
- pm_request_autosuspend(dev->dev);
+ pm_runtime_mark_last_busy(dev->parent);
+ dev_dbg(&dev->dev, "rpm: autosuspend\n");
+ pm_request_autosuspend(dev->parent);
}
/**
@@ -724,12 +724,12 @@ bool mei_hbuf_acquire(struct mei_device *dev)
{
if (mei_pg_state(dev) == MEI_PG_ON ||
mei_pg_in_transition(dev)) {
- dev_dbg(dev->dev, "device is in pg\n");
+ dev_dbg(&dev->dev, "device is in pg\n");
return false;
}
if (!dev->hbuf_is_ready) {
- dev_dbg(dev->dev, "hbuf is not ready\n");
+ dev_dbg(&dev->dev, "hbuf is not ready\n");
return false;
}
@@ -981,9 +981,9 @@ int mei_cl_disconnect(struct mei_cl *cl)
return 0;
}
- rets = pm_runtime_get(dev->dev);
+ rets = pm_runtime_get(dev->parent);
if (rets < 0 && rets != -EINPROGRESS) {
- pm_runtime_put_noidle(dev->dev);
+ pm_runtime_put_noidle(dev->parent);
cl_err(dev, cl, "rpm: get failed %d\n", rets);
return rets;
}
@@ -991,8 +991,8 @@ int mei_cl_disconnect(struct mei_cl *cl)
rets = __mei_cl_disconnect(cl);
cl_dbg(dev, cl, "rpm: autosuspend\n");
- pm_runtime_mark_last_busy(dev->dev);
- pm_runtime_put_autosuspend(dev->dev);
+ pm_runtime_mark_last_busy(dev->parent);
+ pm_runtime_put_autosuspend(dev->parent);
return rets;
}
@@ -1118,9 +1118,9 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
goto nortpm;
}
- rets = pm_runtime_get(dev->dev);
+ rets = pm_runtime_get(dev->parent);
if (rets < 0 && rets != -EINPROGRESS) {
- pm_runtime_put_noidle(dev->dev);
+ pm_runtime_put_noidle(dev->parent);
cl_err(dev, cl, "rpm: get failed %d\n", rets);
goto nortpm;
}
@@ -1167,8 +1167,8 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
rets = cl->status;
out:
cl_dbg(dev, cl, "rpm: autosuspend\n");
- pm_runtime_mark_last_busy(dev->dev);
- pm_runtime_put_autosuspend(dev->dev);
+ pm_runtime_mark_last_busy(dev->parent);
+ pm_runtime_put_autosuspend(dev->parent);
mei_io_cb_free(cb);
@@ -1517,9 +1517,9 @@ int mei_cl_notify_request(struct mei_cl *cl,
if (!mei_cl_is_connected(cl))
return -ENODEV;
- rets = pm_runtime_get(dev->dev);
+ rets = pm_runtime_get(dev->parent);
if (rets < 0 && rets != -EINPROGRESS) {
- pm_runtime_put_noidle(dev->dev);
+ pm_runtime_put_noidle(dev->parent);
cl_err(dev, cl, "rpm: get failed %d\n", rets);
return rets;
}
@@ -1554,8 +1554,8 @@ int mei_cl_notify_request(struct mei_cl *cl,
out:
cl_dbg(dev, cl, "rpm: autosuspend\n");
- pm_runtime_mark_last_busy(dev->dev);
- pm_runtime_put_autosuspend(dev->dev);
+ pm_runtime_mark_last_busy(dev->parent);
+ pm_runtime_put_autosuspend(dev->parent);
mei_io_cb_free(cb);
return rets;
@@ -1683,9 +1683,9 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp)
mei_cl_set_read_by_fp(cl, fp);
- rets = pm_runtime_get(dev->dev);
+ rets = pm_runtime_get(dev->parent);
if (rets < 0 && rets != -EINPROGRESS) {
- pm_runtime_put_noidle(dev->dev);
+ pm_runtime_put_noidle(dev->parent);
cl_err(dev, cl, "rpm: get failed %d\n", rets);
goto nortpm;
}
@@ -1702,8 +1702,8 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp)
out:
cl_dbg(dev, cl, "rpm: autosuspend\n");
- pm_runtime_mark_last_busy(dev->dev);
- pm_runtime_put_autosuspend(dev->dev);
+ pm_runtime_mark_last_busy(dev->parent);
+ pm_runtime_put_autosuspend(dev->parent);
nortpm:
if (rets)
mei_io_cb_free(cb);
@@ -1972,9 +1972,9 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, unsigned long time
blocking = cb->blocking;
data = buf->data;
- rets = pm_runtime_get(dev->dev);
+ rets = pm_runtime_get(dev->parent);
if (rets < 0 && rets != -EINPROGRESS) {
- pm_runtime_put_noidle(dev->dev);
+ pm_runtime_put_noidle(dev->parent);
cl_err(dev, cl, "rpm: get failed %zd\n", rets);
goto free;
}
@@ -2092,8 +2092,8 @@ out:
rets = buf_len;
err:
cl_dbg(dev, cl, "rpm: autosuspend\n");
- pm_runtime_mark_last_busy(dev->dev);
- pm_runtime_put_autosuspend(dev->dev);
+ pm_runtime_mark_last_busy(dev->parent);
+ pm_runtime_put_autosuspend(dev->parent);
free:
mei_io_cb_free(cb);
@@ -2119,8 +2119,8 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
if (waitqueue_active(&cl->tx_wait)) {
wake_up_interruptible(&cl->tx_wait);
} else {
- pm_runtime_mark_last_busy(dev->dev);
- pm_request_autosuspend(dev->dev);
+ pm_runtime_mark_last_busy(dev->parent);
+ pm_request_autosuspend(dev->parent);
}
break;
@@ -2251,7 +2251,7 @@ int mei_cl_irq_dma_unmap(struct mei_cl *cl, struct mei_cl_cb *cb,
static int mei_cl_dma_alloc(struct mei_cl *cl, u8 buf_id, size_t size)
{
- cl->dma.vaddr = dmam_alloc_coherent(cl->dev->dev, size,
+ cl->dma.vaddr = dmam_alloc_coherent(&cl->dev->dev, size,
&cl->dma.daddr, GFP_KERNEL);
if (!cl->dma.vaddr)
return -ENOMEM;
@@ -2265,7 +2265,7 @@ static int mei_cl_dma_alloc(struct mei_cl *cl, u8 buf_id, size_t size)
static void mei_cl_dma_free(struct mei_cl *cl)
{
cl->dma.buffer_id = 0;
- dmam_free_coherent(cl->dev->dev,
+ dmam_free_coherent(&cl->dev->dev,
cl->dma.size, cl->dma.vaddr, cl->dma.daddr);
cl->dma.size = 0;
cl->dma.vaddr = NULL;
@@ -2321,16 +2321,16 @@ int mei_cl_dma_alloc_and_map(struct mei_cl *cl, const struct file *fp,
return -EPROTO;
}
- rets = pm_runtime_get(dev->dev);
+ rets = pm_runtime_get(dev->parent);
if (rets < 0 && rets != -EINPROGRESS) {
- pm_runtime_put_noidle(dev->dev);
+ pm_runtime_put_noidle(dev->parent);
cl_err(dev, cl, "rpm: get failed %d\n", rets);
return rets;
}
rets = mei_cl_dma_alloc(cl, buffer_id, size);
if (rets) {
- pm_runtime_put_noidle(dev->dev);
+ pm_runtime_put_noidle(dev->parent);
return rets;
}
@@ -2366,8 +2366,8 @@ out:
mei_cl_dma_free(cl);
cl_dbg(dev, cl, "rpm: autosuspend\n");
- pm_runtime_mark_last_busy(dev->dev);
- pm_runtime_put_autosuspend(dev->dev);
+ pm_runtime_mark_last_busy(dev->parent);
+ pm_runtime_put_autosuspend(dev->parent);
mei_io_cb_free(cb);
return rets;
@@ -2406,9 +2406,9 @@ int mei_cl_dma_unmap(struct mei_cl *cl, const struct file *fp)
if (!cl->dma_mapped)
return -EPROTO;
- rets = pm_runtime_get(dev->dev);
+ rets = pm_runtime_get(dev->parent);
if (rets < 0 && rets != -EINPROGRESS) {
- pm_runtime_put_noidle(dev->dev);
+ pm_runtime_put_noidle(dev->parent);
cl_err(dev, cl, "rpm: get failed %d\n", rets);
return rets;
}
@@ -2444,8 +2444,8 @@ int mei_cl_dma_unmap(struct mei_cl *cl, const struct file *fp)
mei_cl_dma_free(cl);
out:
cl_dbg(dev, cl, "rpm: autosuspend\n");
- pm_runtime_mark_last_busy(dev->dev);
- pm_runtime_put_autosuspend(dev->dev);
+ pm_runtime_mark_last_busy(dev->parent);
+ pm_runtime_put_autosuspend(dev->parent);
mei_io_cb_free(cb);
return rets;
diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h
index 01ed26a148c4..031114478bcb 100644
--- a/drivers/misc/mei/client.h
+++ b/drivers/misc/mei/client.h
@@ -275,12 +275,12 @@ int mei_cl_dma_unmap(struct mei_cl *cl, const struct file *fp);
#define MEI_CL_PRM(cl) (cl)->host_client_id, mei_cl_me_id(cl)
#define cl_dbg(dev, cl, format, arg...) \
- dev_dbg((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg)
+ dev_dbg(&(dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg)
#define cl_warn(dev, cl, format, arg...) \
- dev_warn((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg)
+ dev_warn(&(dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg)
#define cl_err(dev, cl, format, arg...) \
- dev_err((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg)
+ dev_err(&(dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg)
#endif /* _MEI_CLIENT_H_ */
diff --git a/drivers/misc/mei/dma-ring.c b/drivers/misc/mei/dma-ring.c
index 651e77ef82bd..6277c4a5b0fd 100644
--- a/drivers/misc/mei/dma-ring.c
+++ b/drivers/misc/mei/dma-ring.c
@@ -30,7 +30,7 @@ static int mei_dmam_dscr_alloc(struct mei_device *dev,
if (dscr->vaddr)
return 0;
- dscr->vaddr = dmam_alloc_coherent(dev->dev, dscr->size, &dscr->daddr,
+ dscr->vaddr = dmam_alloc_coherent(dev->parent, dscr->size, &dscr->daddr,
GFP_KERNEL);
if (!dscr->vaddr)
return -ENOMEM;
@@ -50,7 +50,7 @@ static void mei_dmam_dscr_free(struct mei_device *dev,
if (!dscr->vaddr)
return;
- dmam_free_coherent(dev->dev, dscr->size, dscr->vaddr, dscr->daddr);
+ dmam_free_coherent(dev->parent, dscr->size, dscr->vaddr, dscr->daddr);
dscr->vaddr = NULL;
}
@@ -177,7 +177,7 @@ void mei_dma_ring_read(struct mei_device *dev, unsigned char *buf, u32 len)
if (WARN_ON(!ctrl))
return;
- dev_dbg(dev->dev, "reading from dma %u bytes\n", len);
+ dev_dbg(&dev->dev, "reading from dma %u bytes\n", len);
if (!len)
return;
@@ -254,7 +254,7 @@ void mei_dma_ring_write(struct mei_device *dev, unsigned char *buf, u32 len)
if (WARN_ON(!ctrl))
return;
- dev_dbg(dev->dev, "writing to dma %u bytes\n", len);
+ dev_dbg(&dev->dev, "writing to dma %u bytes\n", len);
hbuf_depth = mei_dma_ring_hbuf_depth(dev);
wr_idx = READ_ONCE(ctrl->hbuf_wr_idx) & (hbuf_depth - 1);
slots = mei_data2slots(len);
diff --git a/drivers/misc/mei/gsc-me.c b/drivers/misc/mei/gsc-me.c
index 5a8c26c3df13..93cba090ea08 100644
--- a/drivers/misc/mei/gsc-me.c
+++ b/drivers/misc/mei/gsc-me.c
@@ -106,11 +106,15 @@ static int mei_gsc_probe(struct auxiliary_device *aux_dev,
}
}
+ ret = mei_register(dev, device);
+ if (ret)
+ goto deinterrupt;
+
pm_runtime_get_noresume(device);
pm_runtime_set_active(device);
pm_runtime_enable(device);
- /* Continue to char device setup in spite of firmware handshake failure.
+ /* Continue in spite of firmware handshake failure.
* In order to provide access to the firmware status registers to the user
* space via sysfs.
*/
@@ -120,18 +124,12 @@ static int mei_gsc_probe(struct auxiliary_device *aux_dev,
pm_runtime_set_autosuspend_delay(device, MEI_GSC_RPM_TIMEOUT);
pm_runtime_use_autosuspend(device);
- ret = mei_register(dev, device);
- if (ret)
- goto register_err;
-
pm_runtime_put_noidle(device);
return 0;
-register_err:
- mei_stop(dev);
+deinterrupt:
if (!mei_me_hw_use_polling(hw))
devm_free_irq(device, hw->irq, dev);
-
err:
dev_err(device, "probe failed: %d\n", ret);
dev_set_drvdata(device, NULL);
@@ -152,13 +150,13 @@ static void mei_gsc_remove(struct auxiliary_device *aux_dev)
if (mei_me_hw_use_polling(hw))
kthread_stop(hw->polling_thread);
- mei_deregister(dev);
-
pm_runtime_disable(&aux_dev->dev);
mei_disable_interrupts(dev);
if (!mei_me_hw_use_polling(hw))
devm_free_irq(&aux_dev->dev, hw->irq, dev);
+
+ mei_deregister(dev);
}
static int __maybe_unused mei_gsc_pm_suspend(struct device *device)
@@ -252,7 +250,7 @@ static int __maybe_unused mei_gsc_pm_runtime_resume(struct device *device)
irq_ret = mei_me_irq_thread_handler(1, dev);
if (irq_ret != IRQ_HANDLED)
- dev_err(dev->dev, "thread handler fail %d\n", irq_ret);
+ dev_err(&dev->dev, "thread handler fail %d\n", irq_ret);
return 0;
}
diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c
index 4fe9a2752d43..ccd9df5d1c7d 100644
--- a/drivers/misc/mei/hbm.c
+++ b/drivers/misc/mei/hbm.c
@@ -239,7 +239,7 @@ int mei_hbm_start_wait(struct mei_device *dev)
if (ret == 0 && (dev->hbm_state <= MEI_HBM_STARTING)) {
dev->hbm_state = MEI_HBM_IDLE;
- dev_err(dev->dev, "waiting for mei start failed\n");
+ dev_err(&dev->dev, "waiting for mei start failed\n");
return -ETIME;
}
return 0;
@@ -271,8 +271,7 @@ int mei_hbm_start_req(struct mei_device *dev)
dev->hbm_state = MEI_HBM_IDLE;
ret = mei_hbm_write_message(dev, &mei_hdr, &req);
if (ret) {
- dev_err(dev->dev, "version message write failed: ret = %d\n",
- ret);
+ dev_err(&dev->dev, "version message write failed: ret = %d\n", ret);
return ret;
}
@@ -312,8 +311,7 @@ static int mei_hbm_dma_setup_req(struct mei_device *dev)
ret = mei_hbm_write_message(dev, &mei_hdr, &req);
if (ret) {
- dev_err(dev->dev, "dma setup request write failed: ret = %d.\n",
- ret);
+ dev_err(&dev->dev, "dma setup request write failed: ret = %d.\n", ret);
return ret;
}
@@ -351,8 +349,7 @@ static int mei_hbm_capabilities_req(struct mei_device *dev)
ret = mei_hbm_write_message(dev, &mei_hdr, &req);
if (ret) {
- dev_err(dev->dev,
- "capabilities request write failed: ret = %d.\n", ret);
+ dev_err(&dev->dev, "capabilities request write failed: ret = %d.\n", ret);
return ret;
}
@@ -386,8 +383,7 @@ static int mei_hbm_enum_clients_req(struct mei_device *dev)
ret = mei_hbm_write_message(dev, &mei_hdr, &req);
if (ret) {
- dev_err(dev->dev, "enumeration request write failed: ret = %d.\n",
- ret);
+ dev_err(&dev->dev, "enumeration request write failed: ret = %d.\n", ret);
return ret;
}
dev->hbm_state = MEI_HBM_ENUM_CLIENTS;
@@ -443,7 +439,7 @@ static int mei_hbm_add_cl_resp(struct mei_device *dev, u8 addr, u8 status)
struct hbm_add_client_response resp;
int ret;
- dev_dbg(dev->dev, "adding client response\n");
+ dev_dbg(&dev->dev, "adding client response\n");
mei_hbm_hdr(&mei_hdr, sizeof(resp));
@@ -454,8 +450,7 @@ static int mei_hbm_add_cl_resp(struct mei_device *dev, u8 addr, u8 status)
ret = mei_hbm_write_message(dev, &mei_hdr, &resp);
if (ret)
- dev_err(dev->dev, "add client response write failed: ret = %d\n",
- ret);
+ dev_err(&dev->dev, "add client response write failed: ret = %d\n", ret);
return ret;
}
@@ -752,7 +747,7 @@ static int mei_hbm_prop_req(struct mei_device *dev, unsigned long start_idx)
ret = mei_hbm_write_message(dev, &mei_hdr, &req);
if (ret) {
- dev_err(dev->dev, "properties request write failed: ret = %d\n",
+ dev_err(&dev->dev, "properties request write failed: ret = %d\n",
ret);
return ret;
}
@@ -788,7 +783,7 @@ int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd)
ret = mei_hbm_write_message(dev, &mei_hdr, &req);
if (ret)
- dev_err(dev->dev, "power gate command write failed.\n");
+ dev_err(&dev->dev, "power gate command write failed.\n");
return ret;
}
EXPORT_SYMBOL_GPL(mei_hbm_pg);
@@ -847,7 +842,7 @@ static int mei_hbm_add_single_tx_flow_ctrl_creds(struct mei_device *dev,
me_cl = mei_me_cl_by_id(dev, fctrl->me_addr);
if (!me_cl) {
- dev_err(dev->dev, "no such me client %d\n", fctrl->me_addr);
+ dev_err(&dev->dev, "no such me client %d\n", fctrl->me_addr);
return -ENOENT;
}
@@ -857,7 +852,7 @@ static int mei_hbm_add_single_tx_flow_ctrl_creds(struct mei_device *dev,
}
me_cl->tx_flow_ctrl_creds++;
- dev_dbg(dev->dev, "recv flow ctrl msg ME %d (single) creds = %d.\n",
+ dev_dbg(&dev->dev, "recv flow ctrl msg ME %d (single) creds = %d.\n",
fctrl->me_addr, me_cl->tx_flow_ctrl_creds);
rets = 0;
@@ -1085,7 +1080,7 @@ static int mei_hbm_pg_enter_res(struct mei_device *dev)
{
if (mei_pg_state(dev) != MEI_PG_OFF ||
dev->pg_event != MEI_PG_EVENT_WAIT) {
- dev_err(dev->dev, "hbm: pg entry response: state mismatch [%s, %d]\n",
+ dev_err(&dev->dev, "hbm: pg entry response: state mismatch [%s, %d]\n",
mei_pg_state_str(mei_pg_state(dev)), dev->pg_event);
return -EPROTO;
}
@@ -1103,7 +1098,7 @@ static int mei_hbm_pg_enter_res(struct mei_device *dev)
*/
void mei_hbm_pg_resume(struct mei_device *dev)
{
- pm_request_resume(dev->dev);
+ pm_request_resume(dev->parent);
}
EXPORT_SYMBOL_GPL(mei_hbm_pg_resume);
@@ -1119,7 +1114,7 @@ static int mei_hbm_pg_exit_res(struct mei_device *dev)
if (mei_pg_state(dev) != MEI_PG_ON ||
(dev->pg_event != MEI_PG_EVENT_WAIT &&
dev->pg_event != MEI_PG_EVENT_IDLE)) {
- dev_err(dev->dev, "hbm: pg exit response: state mismatch [%s, %d]\n",
+ dev_err(&dev->dev, "hbm: pg exit response: state mismatch [%s, %d]\n",
mei_pg_state_str(mei_pg_state(dev)), dev->pg_event);
return -EPROTO;
}
@@ -1276,19 +1271,19 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
* hbm is put to idle during system reset
*/
if (dev->hbm_state == MEI_HBM_IDLE) {
- dev_dbg(dev->dev, "hbm: state is idle ignore spurious messages\n");
+ dev_dbg(&dev->dev, "hbm: state is idle ignore spurious messages\n");
return 0;
}
switch (mei_msg->hbm_cmd) {
case HOST_START_RES_CMD:
- dev_dbg(dev->dev, "hbm: start: response message received.\n");
+ dev_dbg(&dev->dev, "hbm: start: response message received.\n");
dev->init_clients_timer = 0;
version_res = (struct hbm_host_version_response *)mei_msg;
- dev_dbg(dev->dev, "HBM VERSION: DRIVER=%02d:%02d DEVICE=%02d:%02d\n",
+ dev_dbg(&dev->dev, "HBM VERSION: DRIVER=%02d:%02d DEVICE=%02d:%02d\n",
HBM_MAJOR_VERSION, HBM_MINOR_VERSION,
version_res->me_max_version.major_version,
version_res->me_max_version.minor_version);
@@ -1304,11 +1299,11 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
}
if (!mei_hbm_version_is_supported(dev)) {
- dev_warn(dev->dev, "hbm: start: version mismatch - stopping the driver.\n");
+ dev_warn(&dev->dev, "hbm: start: version mismatch - stopping the driver.\n");
dev->hbm_state = MEI_HBM_STOPPED;
if (mei_hbm_stop_req(dev)) {
- dev_err(dev->dev, "hbm: start: failed to send stop request\n");
+ dev_err(&dev->dev, "hbm: start: failed to send stop request\n");
return -EIO;
}
break;
@@ -1320,10 +1315,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
dev->hbm_state != MEI_HBM_STARTING) {
if (dev->dev_state == MEI_DEV_POWER_DOWN ||
dev->dev_state == MEI_DEV_POWERING_DOWN) {
- dev_dbg(dev->dev, "hbm: start: on shutdown, ignoring\n");
+ dev_dbg(&dev->dev, "hbm: start: on shutdown, ignoring\n");
return 0;
}
- dev_err(dev->dev, "hbm: start: state mismatch, [%d, %d]\n",
+ dev_err(&dev->dev, "hbm: start: state mismatch, [%d, %d]\n",
dev->dev_state, dev->hbm_state);
return -EPROTO;
}
@@ -1337,7 +1332,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
if (dev->hbm_f_dr_supported) {
if (mei_dmam_ring_alloc(dev))
- dev_info(dev->dev, "running w/o dma ring\n");
+ dev_info(&dev->dev, "running w/o dma ring\n");
if (mei_dma_ring_is_allocated(dev)) {
if (mei_hbm_dma_setup_req(dev))
return -EIO;
@@ -1357,7 +1352,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
break;
case MEI_HBM_CAPABILITIES_RES_CMD:
- dev_dbg(dev->dev, "hbm: capabilities response: message received.\n");
+ dev_dbg(&dev->dev, "hbm: capabilities response: message received.\n");
dev->init_clients_timer = 0;
@@ -1365,10 +1360,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
dev->hbm_state != MEI_HBM_CAP_SETUP) {
if (dev->dev_state == MEI_DEV_POWER_DOWN ||
dev->dev_state == MEI_DEV_POWERING_DOWN) {
- dev_dbg(dev->dev, "hbm: capabilities response: on shutdown, ignoring\n");
+ dev_dbg(&dev->dev, "hbm: capabilities response: on shutdown, ignoring\n");
return 0;
}
- dev_err(dev->dev, "hbm: capabilities response: state mismatch, [%d, %d]\n",
+ dev_err(&dev->dev, "hbm: capabilities response: state mismatch, [%d, %d]\n",
dev->dev_state, dev->hbm_state);
return -EPROTO;
}
@@ -1384,7 +1379,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
if (dev->hbm_f_dr_supported) {
if (mei_dmam_ring_alloc(dev))
- dev_info(dev->dev, "running w/o dma ring\n");
+ dev_info(&dev->dev, "running w/o dma ring\n");
if (mei_dma_ring_is_allocated(dev)) {
if (mei_hbm_dma_setup_req(dev))
return -EIO;
@@ -1400,7 +1395,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
break;
case MEI_HBM_DMA_SETUP_RES_CMD:
- dev_dbg(dev->dev, "hbm: dma setup response: message received.\n");
+ dev_dbg(&dev->dev, "hbm: dma setup response: message received.\n");
dev->init_clients_timer = 0;
@@ -1408,10 +1403,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
dev->hbm_state != MEI_HBM_DR_SETUP) {
if (dev->dev_state == MEI_DEV_POWER_DOWN ||
dev->dev_state == MEI_DEV_POWERING_DOWN) {
- dev_dbg(dev->dev, "hbm: dma setup response: on shutdown, ignoring\n");
+ dev_dbg(&dev->dev, "hbm: dma setup response: on shutdown, ignoring\n");
return 0;
}
- dev_err(dev->dev, "hbm: dma setup response: state mismatch, [%d, %d]\n",
+ dev_err(&dev->dev, "hbm: dma setup response: state mismatch, [%d, %d]\n",
dev->dev_state, dev->hbm_state);
return -EPROTO;
}
@@ -1422,9 +1417,9 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
u8 status = dma_setup_res->status;
if (status == MEI_HBMS_NOT_ALLOWED) {
- dev_dbg(dev->dev, "hbm: dma setup not allowed\n");
+ dev_dbg(&dev->dev, "hbm: dma setup not allowed\n");
} else {
- dev_info(dev->dev, "hbm: dma setup response: failure = %d %s\n",
+ dev_info(&dev->dev, "hbm: dma setup response: failure = %d %s\n",
status,
mei_hbm_status_str(status));
}
@@ -1437,38 +1432,38 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
break;
case CLIENT_CONNECT_RES_CMD:
- dev_dbg(dev->dev, "hbm: client connect response: message received.\n");
+ dev_dbg(&dev->dev, "hbm: client connect response: message received.\n");
mei_hbm_cl_res(dev, cl_cmd, MEI_FOP_CONNECT);
break;
case CLIENT_DISCONNECT_RES_CMD:
- dev_dbg(dev->dev, "hbm: client disconnect response: message received.\n");
+ dev_dbg(&dev->dev, "hbm: client disconnect response: message received.\n");
mei_hbm_cl_res(dev, cl_cmd, MEI_FOP_DISCONNECT);
break;
case MEI_FLOW_CONTROL_CMD:
- dev_dbg(dev->dev, "hbm: client flow control response: message received.\n");
+ dev_dbg(&dev->dev, "hbm: client flow control response: message received.\n");
fctrl = (struct hbm_flow_control *)mei_msg;
mei_hbm_cl_tx_flow_ctrl_creds_res(dev, fctrl);
break;
case MEI_PG_ISOLATION_ENTRY_RES_CMD:
- dev_dbg(dev->dev, "hbm: power gate isolation entry response received\n");
+ dev_dbg(&dev->dev, "hbm: power gate isolation entry response received\n");
ret = mei_hbm_pg_enter_res(dev);
if (ret)
return ret;
break;
case MEI_PG_ISOLATION_EXIT_REQ_CMD:
- dev_dbg(dev->dev, "hbm: power gate isolation exit request received\n");
+ dev_dbg(&dev->dev, "hbm: power gate isolation exit request received\n");
ret = mei_hbm_pg_exit_res(dev);
if (ret)
return ret;
break;
case HOST_CLIENT_PROPERTIES_RES_CMD:
- dev_dbg(dev->dev, "hbm: properties response: message received.\n");
+ dev_dbg(&dev->dev, "hbm: properties response: message received.\n");
dev->init_clients_timer = 0;
@@ -1476,10 +1471,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
dev->hbm_state != MEI_HBM_CLIENT_PROPERTIES) {
if (dev->dev_state == MEI_DEV_POWER_DOWN ||
dev->dev_state == MEI_DEV_POWERING_DOWN) {
- dev_dbg(dev->dev, "hbm: properties response: on shutdown, ignoring\n");
+ dev_dbg(&dev->dev, "hbm: properties response: on shutdown, ignoring\n");
return 0;
}
- dev_err(dev->dev, "hbm: properties response: state mismatch, [%d, %d]\n",
+ dev_err(&dev->dev, "hbm: properties response: state mismatch, [%d, %d]\n",
dev->dev_state, dev->hbm_state);
return -EPROTO;
}
@@ -1487,10 +1482,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
props_res = (struct hbm_props_response *)mei_msg;
if (props_res->status == MEI_HBMS_CLIENT_NOT_FOUND) {
- dev_dbg(dev->dev, "hbm: properties response: %d CLIENT_NOT_FOUND\n",
+ dev_dbg(&dev->dev, "hbm: properties response: %d CLIENT_NOT_FOUND\n",
props_res->me_addr);
} else if (props_res->status) {
- dev_err(dev->dev, "hbm: properties response: wrong status = %d %s\n",
+ dev_err(&dev->dev, "hbm: properties response: wrong status = %d %s\n",
props_res->status,
mei_hbm_status_str(props_res->status));
return -EPROTO;
@@ -1505,7 +1500,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
break;
case HOST_ENUM_RES_CMD:
- dev_dbg(dev->dev, "hbm: enumeration response: message received\n");
+ dev_dbg(&dev->dev, "hbm: enumeration response: message received\n");
dev->init_clients_timer = 0;
@@ -1519,10 +1514,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
dev->hbm_state != MEI_HBM_ENUM_CLIENTS) {
if (dev->dev_state == MEI_DEV_POWER_DOWN ||
dev->dev_state == MEI_DEV_POWERING_DOWN) {
- dev_dbg(dev->dev, "hbm: enumeration response: on shutdown, ignoring\n");
+ dev_dbg(&dev->dev, "hbm: enumeration response: on shutdown, ignoring\n");
return 0;
}
- dev_err(dev->dev, "hbm: enumeration response: state mismatch, [%d, %d]\n",
+ dev_err(&dev->dev, "hbm: enumeration response: state mismatch, [%d, %d]\n",
dev->dev_state, dev->hbm_state);
return -EPROTO;
}
@@ -1536,77 +1531,77 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
break;
case HOST_STOP_RES_CMD:
- dev_dbg(dev->dev, "hbm: stop response: message received\n");
+ dev_dbg(&dev->dev, "hbm: stop response: message received\n");
dev->init_clients_timer = 0;
if (dev->hbm_state != MEI_HBM_STOPPED) {
- dev_err(dev->dev, "hbm: stop response: state mismatch, [%d, %d]\n",
+ dev_err(&dev->dev, "hbm: stop response: state mismatch, [%d, %d]\n",
dev->dev_state, dev->hbm_state);
return -EPROTO;
}
mei_set_devstate(dev, MEI_DEV_POWER_DOWN);
- dev_info(dev->dev, "hbm: stop response: resetting.\n");
+ dev_info(&dev->dev, "hbm: stop response: resetting.\n");
/* force the reset */
return -EPROTO;
case CLIENT_DISCONNECT_REQ_CMD:
- dev_dbg(dev->dev, "hbm: disconnect request: message received\n");
+ dev_dbg(&dev->dev, "hbm: disconnect request: message received\n");
disconnect_req = (struct hbm_client_connect_request *)mei_msg;
mei_hbm_fw_disconnect_req(dev, disconnect_req);
break;
case ME_STOP_REQ_CMD:
- dev_dbg(dev->dev, "hbm: stop request: message received\n");
+ dev_dbg(&dev->dev, "hbm: stop request: message received\n");
dev->hbm_state = MEI_HBM_STOPPED;
if (mei_hbm_stop_req(dev)) {
- dev_err(dev->dev, "hbm: stop request: failed to send stop request\n");
+ dev_err(&dev->dev, "hbm: stop request: failed to send stop request\n");
return -EIO;
}
break;
case MEI_HBM_ADD_CLIENT_REQ_CMD:
- dev_dbg(dev->dev, "hbm: add client request received\n");
+ dev_dbg(&dev->dev, "hbm: add client request received\n");
/*
* after the host receives the enum_resp
* message clients may be added or removed
*/
if (dev->hbm_state <= MEI_HBM_ENUM_CLIENTS ||
dev->hbm_state >= MEI_HBM_STOPPED) {
- dev_err(dev->dev, "hbm: add client: state mismatch, [%d, %d]\n",
+ dev_err(&dev->dev, "hbm: add client: state mismatch, [%d, %d]\n",
dev->dev_state, dev->hbm_state);
return -EPROTO;
}
add_cl_req = (struct hbm_add_client_request *)mei_msg;
ret = mei_hbm_fw_add_cl_req(dev, add_cl_req);
if (ret) {
- dev_err(dev->dev, "hbm: add client: failed to send response %d\n",
+ dev_err(&dev->dev, "hbm: add client: failed to send response %d\n",
ret);
return -EIO;
}
- dev_dbg(dev->dev, "hbm: add client request processed\n");
+ dev_dbg(&dev->dev, "hbm: add client request processed\n");
break;
case MEI_HBM_NOTIFY_RES_CMD:
- dev_dbg(dev->dev, "hbm: notify response received\n");
+ dev_dbg(&dev->dev, "hbm: notify response received\n");
mei_hbm_cl_res(dev, cl_cmd, notify_res_to_fop(cl_cmd));
break;
case MEI_HBM_NOTIFICATION_CMD:
- dev_dbg(dev->dev, "hbm: notification\n");
+ dev_dbg(&dev->dev, "hbm: notification\n");
mei_hbm_cl_notify(dev, cl_cmd);
break;
case MEI_HBM_CLIENT_DMA_MAP_RES_CMD:
- dev_dbg(dev->dev, "hbm: client dma map response: message received.\n");
+ dev_dbg(&dev->dev, "hbm: client dma map response: message received.\n");
client_dma_res = (struct hbm_client_dma_response *)mei_msg;
mei_hbm_cl_dma_map_res(dev, client_dma_res);
break;
case MEI_HBM_CLIENT_DMA_UNMAP_RES_CMD:
- dev_dbg(dev->dev, "hbm: client dma unmap response: message received.\n");
+ dev_dbg(&dev->dev, "hbm: client dma unmap response: message received.\n");
client_dma_res = (struct hbm_client_dma_response *)mei_msg;
mei_hbm_cl_dma_unmap_res(dev, client_dma_res);
break;
diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c
index d11a0740b47c..d4612c659784 100644
--- a/drivers/misc/mei/hw-me.c
+++ b/drivers/misc/mei/hw-me.c
@@ -84,7 +84,7 @@ static inline u32 mei_me_mecsr_read(const struct mei_device *dev)
u32 reg;
reg = mei_me_reg_read(to_me_hw(dev), ME_CSR_HA);
- trace_mei_reg_read(dev->dev, "ME_CSR_HA", ME_CSR_HA, reg);
+ trace_mei_reg_read(&dev->dev, "ME_CSR_HA", ME_CSR_HA, reg);
return reg;
}
@@ -101,7 +101,7 @@ static inline u32 mei_hcsr_read(const struct mei_device *dev)
u32 reg;
reg = mei_me_reg_read(to_me_hw(dev), H_CSR);
- trace_mei_reg_read(dev->dev, "H_CSR", H_CSR, reg);
+ trace_mei_reg_read(&dev->dev, "H_CSR", H_CSR, reg);
return reg;
}
@@ -114,7 +114,7 @@ static inline u32 mei_hcsr_read(const struct mei_device *dev)
*/
static inline void mei_hcsr_write(struct mei_device *dev, u32 reg)
{
- trace_mei_reg_write(dev->dev, "H_CSR", H_CSR, reg);
+ trace_mei_reg_write(&dev->dev, "H_CSR", H_CSR, reg);
mei_me_reg_write(to_me_hw(dev), H_CSR, reg);
}
@@ -156,7 +156,7 @@ static inline u32 mei_me_d0i3c_read(const struct mei_device *dev)
u32 reg;
reg = mei_me_reg_read(to_me_hw(dev), H_D0I3C);
- trace_mei_reg_read(dev->dev, "H_D0I3C", H_D0I3C, reg);
+ trace_mei_reg_read(&dev->dev, "H_D0I3C", H_D0I3C, reg);
return reg;
}
@@ -169,7 +169,7 @@ static inline u32 mei_me_d0i3c_read(const struct mei_device *dev)
*/
static inline void mei_me_d0i3c_write(struct mei_device *dev, u32 reg)
{
- trace_mei_reg_write(dev->dev, "H_D0I3C", H_D0I3C, reg);
+ trace_mei_reg_write(&dev->dev, "H_D0I3C", H_D0I3C, reg);
mei_me_reg_write(to_me_hw(dev), H_D0I3C, reg);
}
@@ -189,7 +189,7 @@ static int mei_me_trc_status(struct mei_device *dev, u32 *trc)
return -EOPNOTSUPP;
*trc = mei_me_reg_read(hw, ME_TRC);
- trace_mei_reg_read(dev->dev, "ME_TRC", ME_TRC, *trc);
+ trace_mei_reg_read(&dev->dev, "ME_TRC", ME_TRC, *trc);
return 0;
}
@@ -217,7 +217,7 @@ static int mei_me_fw_status(struct mei_device *dev,
for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) {
ret = hw->read_fws(dev, fw_src->status[i],
&fw_status->status[i]);
- trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HFS_X",
+ trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HFS_X",
fw_src->status[i],
fw_status->status[i]);
if (ret)
@@ -251,7 +251,7 @@ static int mei_me_hw_config(struct mei_device *dev)
reg = 0;
hw->read_fws(dev, PCI_CFG_HFS_1, &reg);
- trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HFS_1", PCI_CFG_HFS_1, reg);
+ trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HFS_1", PCI_CFG_HFS_1, reg);
hw->d0i3_supported =
((reg & PCI_CFG_HFS_1_D0I3_MSK) == PCI_CFG_HFS_1_D0I3_MSK);
@@ -447,7 +447,7 @@ static void mei_gsc_pxp_check(struct mei_device *dev)
return;
hw->read_fws(dev, PCI_CFG_HFS_5, &fwsts5);
- trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HFS_5", PCI_CFG_HFS_5, fwsts5);
+ trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HFS_5", PCI_CFG_HFS_5, fwsts5);
if ((fwsts5 & GSC_CFG_HFS_5_BOOT_TYPE_MSK) == GSC_CFG_HFS_5_BOOT_TYPE_PXP) {
if (dev->gsc_reset_to_pxp == MEI_DEV_RESET_TO_PXP_DEFAULT)
@@ -460,10 +460,10 @@ static void mei_gsc_pxp_check(struct mei_device *dev)
return;
if ((fwsts5 & GSC_CFG_HFS_5_BOOT_TYPE_MSK) == GSC_CFG_HFS_5_BOOT_TYPE_PXP) {
- dev_dbg(dev->dev, "pxp mode is ready 0x%08x\n", fwsts5);
+ dev_dbg(&dev->dev, "pxp mode is ready 0x%08x\n", fwsts5);
dev->pxp_mode = MEI_DEV_PXP_READY;
} else {
- dev_dbg(dev->dev, "pxp mode is not ready 0x%08x\n", fwsts5);
+ dev_dbg(&dev->dev, "pxp mode is not ready 0x%08x\n", fwsts5);
}
}
@@ -482,7 +482,7 @@ static int mei_me_hw_ready_wait(struct mei_device *dev)
dev->timeouts.hw_ready);
mutex_lock(&dev->device_lock);
if (!dev->recvd_hw_ready) {
- dev_err(dev->dev, "wait hw ready failed\n");
+ dev_err(&dev->dev, "wait hw ready failed\n");
return -ETIME;
}
@@ -494,43 +494,6 @@ static int mei_me_hw_ready_wait(struct mei_device *dev)
}
/**
- * mei_me_check_fw_reset - check for the firmware reset error and exception conditions
- *
- * @dev: mei device
- */
-static void mei_me_check_fw_reset(struct mei_device *dev)
-{
- struct mei_fw_status fw_status;
- char fw_sts_str[MEI_FW_STATUS_STR_SZ] = {0};
- int ret;
- u32 fw_pm_event = 0;
-
- if (!dev->saved_fw_status_flag)
- goto end;
-
- if (dev->gsc_reset_to_pxp == MEI_DEV_RESET_TO_PXP_PERFORMED) {
- ret = mei_fw_status(dev, &fw_status);
- if (!ret) {
- fw_pm_event = fw_status.status[1] & PCI_CFG_HFS_2_PM_EVENT_MASK;
- if (fw_pm_event != PCI_CFG_HFS_2_PM_CMOFF_TO_CMX_ERROR &&
- fw_pm_event != PCI_CFG_HFS_2_PM_CM_RESET_ERROR)
- goto end;
- } else {
- dev_err(dev->dev, "failed to read firmware status: %d\n", ret);
- }
- }
-
- mei_fw_status2str(&dev->saved_fw_status, fw_sts_str, sizeof(fw_sts_str));
- dev_warn(dev->dev, "unexpected reset: fw_pm_event = 0x%x, dev_state = %u fw status = %s\n",
- fw_pm_event, dev->saved_dev_state, fw_sts_str);
-
-end:
- if (dev->gsc_reset_to_pxp == MEI_DEV_RESET_TO_PXP_PERFORMED)
- dev->gsc_reset_to_pxp = MEI_DEV_RESET_TO_PXP_DONE;
- dev->saved_fw_status_flag = false;
-}
-
-/**
* mei_me_hw_start - hw start routine
*
* @dev: mei device
@@ -540,11 +503,12 @@ static int mei_me_hw_start(struct mei_device *dev)
{
int ret = mei_me_hw_ready_wait(dev);
- if (kind_is_gsc(dev) || kind_is_gscfi(dev))
- mei_me_check_fw_reset(dev);
+ if ((kind_is_gsc(dev) || kind_is_gscfi(dev)) &&
+ dev->gsc_reset_to_pxp == MEI_DEV_RESET_TO_PXP_PERFORMED)
+ dev->gsc_reset_to_pxp = MEI_DEV_RESET_TO_PXP_DONE;
if (ret)
return ret;
- dev_dbg(dev->dev, "hw is ready\n");
+ dev_dbg(&dev->dev, "hw is ready\n");
mei_me_host_set_ready(dev);
return ret;
@@ -644,14 +608,14 @@ static int mei_me_hbuf_write(struct mei_device *dev,
return -EINVAL;
if (!data && data_len) {
- dev_err(dev->dev, "wrong parameters null data with data_len = %zu\n", data_len);
+ dev_err(&dev->dev, "wrong parameters null data with data_len = %zu\n", data_len);
return -EINVAL;
}
- dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr));
+ dev_dbg(&dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr));
empty_slots = mei_hbuf_empty_slots(dev);
- dev_dbg(dev->dev, "empty slots = %d.\n", empty_slots);
+ dev_dbg(&dev->dev, "empty slots = %d.\n", empty_slots);
if (empty_slots < 0)
return -EOVERFLOW;
@@ -706,7 +670,7 @@ static int mei_me_count_full_read_slots(struct mei_device *dev)
if (filled_slots > buffer_depth)
return -EOVERFLOW;
- dev_dbg(dev->dev, "filled_slots =%08x\n", filled_slots);
+ dev_dbg(&dev->dev, "filled_slots =%08x\n", filled_slots);
return (int)filled_slots;
}
@@ -748,11 +712,11 @@ static void mei_me_pg_set(struct mei_device *dev)
u32 reg;
reg = mei_me_reg_read(hw, H_HPG_CSR);
- trace_mei_reg_read(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg);
+ trace_mei_reg_read(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg);
reg |= H_HPG_CSR_PGI;
- trace_mei_reg_write(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg);
+ trace_mei_reg_write(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg);
mei_me_reg_write(hw, H_HPG_CSR, reg);
}
@@ -767,13 +731,13 @@ static void mei_me_pg_unset(struct mei_device *dev)
u32 reg;
reg = mei_me_reg_read(hw, H_HPG_CSR);
- trace_mei_reg_read(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg);
+ trace_mei_reg_read(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg);
WARN(!(reg & H_HPG_CSR_PGI), "PGI is not set\n");
reg |= H_HPG_CSR_PGIHEXR;
- trace_mei_reg_write(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg);
+ trace_mei_reg_write(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg);
mei_me_reg_write(hw, H_HPG_CSR, reg);
}
@@ -905,7 +869,7 @@ static bool mei_me_pg_is_enabled(struct mei_device *dev)
return true;
notsupported:
- dev_dbg(dev->dev, "pg: not supported: d0i3 = %d HGP = %d hbm version %d.%d ?= %d.%d\n",
+ dev_dbg(&dev->dev, "pg: not supported: d0i3 = %d HGP = %d hbm version %d.%d ?= %d.%d\n",
hw->d0i3_supported,
!!(reg & ME_PGIC_HRA),
dev->version.major_version,
@@ -974,7 +938,7 @@ static int mei_me_d0i3_enter_sync(struct mei_device *dev)
reg = mei_me_d0i3c_read(dev);
if (reg & H_D0I3C_I3) {
/* we are in d0i3, nothing to do */
- dev_dbg(dev->dev, "d0i3 set not needed\n");
+ dev_dbg(&dev->dev, "d0i3 set not needed\n");
ret = 0;
goto on;
}
@@ -1003,7 +967,7 @@ static int mei_me_d0i3_enter_sync(struct mei_device *dev)
reg = mei_me_d0i3_set(dev, true);
if (!(reg & H_D0I3C_CIP)) {
- dev_dbg(dev->dev, "d0i3 enter wait not needed\n");
+ dev_dbg(&dev->dev, "d0i3 enter wait not needed\n");
ret = 0;
goto on;
}
@@ -1027,7 +991,7 @@ on:
hw->pg_state = MEI_PG_ON;
out:
dev->pg_event = MEI_PG_EVENT_IDLE;
- dev_dbg(dev->dev, "d0i3 enter ret = %d\n", ret);
+ dev_dbg(&dev->dev, "d0i3 enter ret = %d\n", ret);
return ret;
}
@@ -1049,7 +1013,7 @@ static int mei_me_d0i3_enter(struct mei_device *dev)
reg = mei_me_d0i3c_read(dev);
if (reg & H_D0I3C_I3) {
/* we are in d0i3, nothing to do */
- dev_dbg(dev->dev, "already d0i3 : set not needed\n");
+ dev_dbg(&dev->dev, "already d0i3 : set not needed\n");
goto on;
}
@@ -1057,7 +1021,7 @@ static int mei_me_d0i3_enter(struct mei_device *dev)
on:
hw->pg_state = MEI_PG_ON;
dev->pg_event = MEI_PG_EVENT_IDLE;
- dev_dbg(dev->dev, "d0i3 enter\n");
+ dev_dbg(&dev->dev, "d0i3 enter\n");
return 0;
}
@@ -1079,14 +1043,14 @@ static int mei_me_d0i3_exit_sync(struct mei_device *dev)
reg = mei_me_d0i3c_read(dev);
if (!(reg & H_D0I3C_I3)) {
/* we are not in d0i3, nothing to do */
- dev_dbg(dev->dev, "d0i3 exit not needed\n");
+ dev_dbg(&dev->dev, "d0i3 exit not needed\n");
ret = 0;
goto off;
}
reg = mei_me_d0i3_unset(dev);
if (!(reg & H_D0I3C_CIP)) {
- dev_dbg(dev->dev, "d0i3 exit wait not needed\n");
+ dev_dbg(&dev->dev, "d0i3 exit wait not needed\n");
ret = 0;
goto off;
}
@@ -1111,7 +1075,7 @@ off:
out:
dev->pg_event = MEI_PG_EVENT_IDLE;
- dev_dbg(dev->dev, "d0i3 exit ret = %d\n", ret);
+ dev_dbg(&dev->dev, "d0i3 exit ret = %d\n", ret);
return ret;
}
@@ -1154,7 +1118,7 @@ static void mei_me_d0i3_intr(struct mei_device *dev, u32 intr_source)
* force H_RDY because it could be
* wiped off during PG
*/
- dev_dbg(dev->dev, "d0i3 set host ready\n");
+ dev_dbg(&dev->dev, "d0i3 set host ready\n");
mei_me_host_set_ready(dev);
}
} else {
@@ -1170,7 +1134,7 @@ static void mei_me_d0i3_intr(struct mei_device *dev, u32 intr_source)
* we got here because of HW initiated exit from D0i3.
* Start runtime pm resume sequence to exit low power state.
*/
- dev_dbg(dev->dev, "d0i3 want resume\n");
+ dev_dbg(&dev->dev, "d0i3 want resume\n");
mei_hbm_pg_resume(dev);
}
}
@@ -1250,7 +1214,7 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable)
}
}
- pm_runtime_set_active(dev->dev);
+ pm_runtime_set_active(dev->parent);
hcsr = mei_hcsr_read(dev);
/* H_RST may be found lit before reset is started,
@@ -1259,7 +1223,7 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable)
* we need to clean H_RST bit to start a successful reset sequence.
*/
if ((hcsr & H_RST) == H_RST) {
- dev_warn(dev->dev, "H_RST is set = 0x%08X", hcsr);
+ dev_warn(&dev->dev, "H_RST is set = 0x%08X", hcsr);
hcsr &= ~H_RST;
mei_hcsr_set(dev, hcsr);
hcsr = mei_hcsr_read(dev);
@@ -1280,10 +1244,10 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable)
hcsr = mei_hcsr_read(dev);
if ((hcsr & H_RST) == 0)
- dev_warn(dev->dev, "H_RST is not set = 0x%08X", hcsr);
+ dev_warn(&dev->dev, "H_RST is not set = 0x%08X", hcsr);
if ((hcsr & H_RDY) == H_RDY)
- dev_warn(dev->dev, "H_RDY is not cleared 0x%08X", hcsr);
+ dev_warn(&dev->dev, "H_RDY is not cleared 0x%08X", hcsr);
if (!intr_enable) {
mei_me_hw_reset_release(dev);
@@ -1313,7 +1277,7 @@ irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id)
if (!me_intr_src(hcsr))
return IRQ_NONE;
- dev_dbg(dev->dev, "interrupt source 0x%08X\n", me_intr_src(hcsr));
+ dev_dbg(&dev->dev, "interrupt source 0x%08X\n", me_intr_src(hcsr));
/* disable interrupts on device */
me_intr_disable(dev, hcsr);
@@ -1339,7 +1303,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
u32 hcsr;
int rets = 0;
- dev_dbg(dev->dev, "function called after ISR to handle the interrupt processing.\n");
+ dev_dbg(&dev->dev, "function called after ISR to handle the interrupt processing.\n");
/* initialize our complete list */
mutex_lock(&dev->device_lock);
@@ -1351,10 +1315,10 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
/* check if ME wants a reset */
if (!mei_hw_is_ready(dev) && dev->dev_state != MEI_DEV_RESETTING) {
if (kind_is_gsc(dev) || kind_is_gscfi(dev)) {
- dev_dbg(dev->dev, "FW not ready: resetting: dev_state = %d\n",
+ dev_dbg(&dev->dev, "FW not ready: resetting: dev_state = %d\n",
dev->dev_state);
} else {
- dev_warn(dev->dev, "FW not ready: resetting: dev_state = %d\n",
+ dev_warn(&dev->dev, "FW not ready: resetting: dev_state = %d\n",
dev->dev_state);
}
if (dev->dev_state == MEI_DEV_POWERING_DOWN ||
@@ -1373,18 +1337,29 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
/* check if we need to start the dev */
if (!mei_host_is_ready(dev)) {
if (mei_hw_is_ready(dev)) {
- dev_dbg(dev->dev, "we need to start the dev.\n");
- dev->recvd_hw_ready = true;
- wake_up(&dev->wait_hw_ready);
+ /* synchronized by dev mutex */
+ if (waitqueue_active(&dev->wait_hw_ready)) {
+ dev_dbg(&dev->dev, "we need to start the dev.\n");
+ dev->recvd_hw_ready = true;
+ wake_up(&dev->wait_hw_ready);
+ } else if (dev->dev_state != MEI_DEV_UNINITIALIZED &&
+ dev->dev_state != MEI_DEV_POWERING_DOWN &&
+ dev->dev_state != MEI_DEV_POWER_DOWN) {
+ dev_dbg(&dev->dev, "Force link reset.\n");
+ schedule_work(&dev->reset_work);
+ } else {
+ dev_dbg(&dev->dev, "Ignore this interrupt in state = %d\n",
+ dev->dev_state);
+ }
} else {
- dev_dbg(dev->dev, "Spurious Interrupt\n");
+ dev_dbg(&dev->dev, "Spurious Interrupt\n");
}
goto end;
}
/* check slots available for reading */
slots = mei_count_full_read_slots(dev);
while (slots > 0) {
- dev_dbg(dev->dev, "slots to read = %08x\n", slots);
+ dev_dbg(&dev->dev, "slots to read = %08x\n", slots);
rets = mei_irq_read_handler(dev, &cmpl_list, &slots);
/* There is a race between ME write and interrupt delivery:
* Not all data is always available immediately after the
@@ -1394,7 +1369,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
break;
if (rets) {
- dev_err(dev->dev, "mei_irq_read_handler ret = %d, state = %d.\n",
+ dev_err(&dev->dev, "mei_irq_read_handler ret = %d, state = %d.\n",
rets, dev->dev_state);
if (dev->dev_state != MEI_DEV_RESETTING &&
dev->dev_state != MEI_DEV_DISABLED &&
@@ -1421,7 +1396,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
mei_irq_compl_handler(dev, &cmpl_list);
end:
- dev_dbg(dev->dev, "interrupt thread end ret = %d\n", rets);
+ dev_dbg(&dev->dev, "interrupt thread end ret = %d\n", rets);
mei_me_intr_enable(dev);
mutex_unlock(&dev->device_lock);
return IRQ_HANDLED;
@@ -1453,7 +1428,7 @@ int mei_me_polling_thread(void *_dev)
irqreturn_t irq_ret;
long polling_timeout = MEI_POLLING_TIMEOUT_ACTIVE;
- dev_dbg(dev->dev, "kernel thread is running\n");
+ dev_dbg(&dev->dev, "kernel thread is running\n");
while (!kthread_should_stop()) {
struct mei_me_hw *hw = to_me_hw(dev);
u32 hcsr;
@@ -1470,7 +1445,7 @@ int mei_me_polling_thread(void *_dev)
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);
+ dev_err(&dev->dev, "irq_ret %d\n", irq_ret);
} else {
/*
* Increase timeout by MEI_POLLING_TIMEOUT_ACTIVE
@@ -1804,7 +1779,7 @@ struct mei_device *mei_me_dev_init(struct device *parent,
struct mei_me_hw *hw;
int i;
- dev = devm_kzalloc(parent, sizeof(*dev) + sizeof(*hw), GFP_KERNEL);
+ dev = kzalloc(sizeof(*dev) + sizeof(*hw), GFP_KERNEL);
if (!dev)
return NULL;
diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c
index e9476f9ae25d..e4688c391027 100644
--- a/drivers/misc/mei/hw-txe.c
+++ b/drivers/misc/mei/hw-txe.c
@@ -160,7 +160,7 @@ static bool mei_txe_aliveness_set(struct mei_device *dev, u32 req)
struct mei_txe_hw *hw = to_txe_hw(dev);
bool do_req = hw->aliveness != req;
- dev_dbg(dev->dev, "Aliveness current=%d request=%d\n",
+ dev_dbg(&dev->dev, "Aliveness current=%d request=%d\n",
hw->aliveness, req);
if (do_req) {
dev->pg_event = MEI_PG_EVENT_WAIT;
@@ -227,7 +227,7 @@ static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected)
hw->aliveness = mei_txe_aliveness_get(dev);
if (hw->aliveness == expected) {
dev->pg_event = MEI_PG_EVENT_IDLE;
- dev_dbg(dev->dev, "aliveness settled after %lld usecs\n",
+ dev_dbg(&dev->dev, "aliveness settled after %lld usecs\n",
ktime_to_us(ktime_sub(ktime_get(), start)));
return 0;
}
@@ -235,7 +235,7 @@ static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected)
} while (ktime_before(ktime_get(), stop));
dev->pg_event = MEI_PG_EVENT_IDLE;
- dev_err(dev->dev, "aliveness timed out\n");
+ dev_err(&dev->dev, "aliveness timed out\n");
return -ETIME;
}
@@ -270,10 +270,10 @@ static int mei_txe_aliveness_wait(struct mei_device *dev, u32 expected)
ret = hw->aliveness == expected ? 0 : -ETIME;
if (ret)
- dev_warn(dev->dev, "aliveness timed out = %ld aliveness = %d event = %d\n",
+ dev_warn(&dev->dev, "aliveness timed out = %ld aliveness = %d event = %d\n",
err, hw->aliveness, dev->pg_event);
else
- dev_dbg(dev->dev, "aliveness settled after = %d msec aliveness = %d event = %d\n",
+ dev_dbg(&dev->dev, "aliveness settled after = %d msec aliveness = %d event = %d\n",
jiffies_to_msecs(timeout - err),
hw->aliveness, dev->pg_event);
@@ -438,7 +438,7 @@ static void mei_txe_intr_enable(struct mei_device *dev)
*/
static void mei_txe_synchronize_irq(struct mei_device *dev)
{
- struct pci_dev *pdev = to_pci_dev(dev->dev);
+ struct pci_dev *pdev = to_pci_dev(dev->parent);
synchronize_irq(pdev->irq);
}
@@ -464,7 +464,7 @@ static bool mei_txe_pending_interrupts(struct mei_device *dev)
TXE_INTR_OUT_DB));
if (ret) {
- dev_dbg(dev->dev,
+ dev_dbg(&dev->dev,
"Pending Interrupts InReady=%01d Readiness=%01d, Aliveness=%01d, OutDoor=%01d\n",
!!(hw->intr_cause & TXE_INTR_IN_READY),
!!(hw->intr_cause & TXE_INTR_READINESS),
@@ -612,7 +612,7 @@ static int mei_txe_readiness_wait(struct mei_device *dev)
msecs_to_jiffies(SEC_RESET_WAIT_TIMEOUT));
mutex_lock(&dev->device_lock);
if (!dev->recvd_hw_ready) {
- dev_err(dev->dev, "wait for readiness failed\n");
+ dev_err(&dev->dev, "wait for readiness failed\n");
return -ETIME;
}
@@ -638,7 +638,7 @@ static int mei_txe_fw_status(struct mei_device *dev,
struct mei_fw_status *fw_status)
{
const struct mei_fw_status *fw_src = &mei_txe_fw_sts;
- struct pci_dev *pdev = to_pci_dev(dev->dev);
+ struct pci_dev *pdev = to_pci_dev(dev->parent);
int ret;
int i;
@@ -649,7 +649,7 @@ static int mei_txe_fw_status(struct mei_device *dev,
for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) {
ret = pci_read_config_dword(pdev, fw_src->status[i],
&fw_status->status[i]);
- trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HSF_X",
+ trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HSF_X",
fw_src->status[i],
fw_status->status[i]);
if (ret)
@@ -677,7 +677,7 @@ static int mei_txe_hw_config(struct mei_device *dev)
hw->aliveness = mei_txe_aliveness_get(dev);
hw->readiness = mei_txe_readiness_get(dev);
- dev_dbg(dev->dev, "aliveness_resp = 0x%08x, readiness = 0x%08x.\n",
+ dev_dbg(&dev->dev, "aliveness_resp = 0x%08x, readiness = 0x%08x.\n",
hw->aliveness, hw->readiness);
return 0;
@@ -708,7 +708,7 @@ static int mei_txe_write(struct mei_device *dev,
if (WARN_ON(!hdr || !data || hdr_len & 0x3))
return -EINVAL;
- dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr));
+ dev_dbg(&dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr));
dw_cnt = mei_data2slots(hdr_len + data_len);
if (dw_cnt > slots)
@@ -724,7 +724,7 @@ static int mei_txe_write(struct mei_device *dev,
char fw_sts_str[MEI_FW_STATUS_STR_SZ];
mei_fw_status_str(dev, fw_sts_str, MEI_FW_STATUS_STR_SZ);
- dev_err(dev->dev, "Input is not ready %s\n", fw_sts_str);
+ dev_err(&dev->dev, "Input is not ready %s\n", fw_sts_str);
return -EAGAIN;
}
@@ -828,13 +828,13 @@ static int mei_txe_read(struct mei_device *dev,
reg_buf = (u32 *)buf;
rem = len & 0x3;
- dev_dbg(dev->dev, "buffer-length = %lu buf[0]0x%08X\n",
+ dev_dbg(&dev->dev, "buffer-length = %lu buf[0]0x%08X\n",
len, mei_txe_out_data_read(dev, 0));
for (i = 0; i < len / MEI_SLOT_SIZE; i++) {
/* skip header: index starts from 1 */
reg = mei_txe_out_data_read(dev, i + 1);
- dev_dbg(dev->dev, "buf[%d] = 0x%08X\n", i, reg);
+ dev_dbg(&dev->dev, "buf[%d] = 0x%08X\n", i, reg);
*reg_buf++ = reg;
}
@@ -879,7 +879,7 @@ static int mei_txe_hw_reset(struct mei_device *dev, bool intr_enable)
*/
if (aliveness_req != hw->aliveness)
if (mei_txe_aliveness_poll(dev, aliveness_req) < 0) {
- dev_err(dev->dev, "wait for aliveness settle failed ... bailing out\n");
+ dev_err(&dev->dev, "wait for aliveness settle failed ... bailing out\n");
return -EIO;
}
@@ -889,7 +889,7 @@ static int mei_txe_hw_reset(struct mei_device *dev, bool intr_enable)
if (aliveness_req) {
mei_txe_aliveness_set(dev, 0);
if (mei_txe_aliveness_poll(dev, 0) < 0) {
- dev_err(dev->dev, "wait for aliveness failed ... bailing out\n");
+ dev_err(&dev->dev, "wait for aliveness failed ... bailing out\n");
return -EIO;
}
}
@@ -921,7 +921,7 @@ static int mei_txe_hw_start(struct mei_device *dev)
ret = mei_txe_readiness_wait(dev);
if (ret < 0) {
- dev_err(dev->dev, "waiting for readiness failed\n");
+ dev_err(&dev->dev, "waiting for readiness failed\n");
return ret;
}
@@ -937,11 +937,11 @@ static int mei_txe_hw_start(struct mei_device *dev)
ret = mei_txe_aliveness_set_sync(dev, 1);
if (ret < 0) {
- dev_err(dev->dev, "wait for aliveness failed ... bailing out\n");
+ dev_err(&dev->dev, "wait for aliveness failed ... bailing out\n");
return ret;
}
- pm_runtime_set_active(dev->dev);
+ pm_runtime_set_active(dev->parent);
/* enable input ready interrupts:
* SEC_IPC_HOST_INT_MASK.IPC_INPUT_READY_INT_MASK
@@ -1049,7 +1049,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id)
s32 slots;
int rets = 0;
- dev_dbg(dev->dev, "irq thread: Interrupt Registers HHISR|HISR|SEC=%02X|%04X|%02X\n",
+ dev_dbg(&dev->dev, "irq thread: Interrupt Registers HHISR|HISR|SEC=%02X|%04X|%02X\n",
mei_txe_br_reg_read(hw, HHISR_REG),
mei_txe_br_reg_read(hw, HISR_REG),
mei_txe_sec_reg_read_silent(hw, SEC_IPC_HOST_INT_STATUS_REG));
@@ -1059,7 +1059,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id)
mutex_lock(&dev->device_lock);
INIT_LIST_HEAD(&cmpl_list);
- if (pci_dev_msi_enabled(to_pci_dev(dev->dev)))
+ if (pci_dev_msi_enabled(to_pci_dev(dev->parent)))
mei_txe_check_and_ack_intrs(dev, true);
/* show irq events */
@@ -1073,17 +1073,17 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id)
* or TXE driver resetting the HECI interface.
*/
if (test_and_clear_bit(TXE_INTR_READINESS_BIT, &hw->intr_cause)) {
- dev_dbg(dev->dev, "Readiness Interrupt was received...\n");
+ dev_dbg(&dev->dev, "Readiness Interrupt was received...\n");
/* Check if SeC is going through reset */
if (mei_txe_readiness_is_sec_rdy(hw->readiness)) {
- dev_dbg(dev->dev, "we need to start the dev.\n");
+ dev_dbg(&dev->dev, "we need to start the dev.\n");
dev->recvd_hw_ready = true;
} else {
dev->recvd_hw_ready = false;
if (dev->dev_state != MEI_DEV_RESETTING) {
- dev_warn(dev->dev, "FW not ready: resetting.\n");
+ dev_warn(&dev->dev, "FW not ready: resetting.\n");
schedule_work(&dev->reset_work);
goto end;
@@ -1100,7 +1100,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id)
if (test_and_clear_bit(TXE_INTR_ALIVENESS_BIT, &hw->intr_cause)) {
/* Clear the interrupt cause */
- dev_dbg(dev->dev,
+ dev_dbg(&dev->dev,
"Aliveness Interrupt: Status: %d\n", hw->aliveness);
dev->pg_event = MEI_PG_EVENT_RECEIVED;
if (waitqueue_active(&hw->wait_aliveness_resp))
@@ -1118,7 +1118,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id)
if (rets &&
(dev->dev_state != MEI_DEV_RESETTING &&
dev->dev_state != MEI_DEV_POWER_DOWN)) {
- dev_err(dev->dev,
+ dev_err(&dev->dev,
"mei_irq_read_handler ret = %d.\n", rets);
schedule_work(&dev->reset_work);
@@ -1136,7 +1136,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id)
dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
rets = mei_irq_write_handler(dev, &cmpl_list);
if (rets && rets != -EMSGSIZE)
- dev_err(dev->dev, "mei_irq_write_handler ret = %d.\n",
+ dev_err(&dev->dev, "mei_irq_write_handler ret = %d.\n",
rets);
dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
}
@@ -1144,7 +1144,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id)
mei_irq_compl_handler(dev, &cmpl_list);
end:
- dev_dbg(dev->dev, "interrupt thread end ret = %d\n", rets);
+ dev_dbg(&dev->dev, "interrupt thread end ret = %d\n", rets);
mutex_unlock(&dev->device_lock);
@@ -1197,7 +1197,7 @@ struct mei_device *mei_txe_dev_init(struct pci_dev *pdev)
struct mei_device *dev;
struct mei_txe_hw *hw;
- dev = devm_kzalloc(&pdev->dev, sizeof(*dev) + sizeof(*hw), GFP_KERNEL);
+ dev = kzalloc(sizeof(*dev) + sizeof(*hw), GFP_KERNEL);
if (!dev)
return NULL;
diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h
index 2e9cf6f4efb6..3771aa09c592 100644
--- a/drivers/misc/mei/hw.h
+++ b/drivers/misc/mei/hw.h
@@ -27,6 +27,8 @@
#define MKHI_RCV_TIMEOUT 500 /* receive timeout in msec */
#define MKHI_RCV_TIMEOUT_SLOW 10000 /* receive timeout in msec, slow FW */
+#define MEI_LINK_RESET_WAIT_TIMEOUT_MSEC 500 /* Max wait timeout for link reset, in msec */
+
/*
* FW page size for DMA allocations
*/
diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c
index 8ef2b1df8ac7..b789c4d9c709 100644
--- a/drivers/misc/mei/init.c
+++ b/drivers/misc/mei/init.c
@@ -89,22 +89,6 @@ void mei_cancel_work(struct mei_device *dev)
}
EXPORT_SYMBOL_GPL(mei_cancel_work);
-static void mei_save_fw_status(struct mei_device *dev)
-{
- struct mei_fw_status fw_status;
- int ret;
-
- ret = mei_fw_status(dev, &fw_status);
- if (ret) {
- dev_err(dev->dev, "failed to read firmware status: %d\n", ret);
- return;
- }
-
- dev->saved_dev_state = dev->dev_state;
- dev->saved_fw_status_flag = true;
- memcpy(&dev->saved_fw_status, &fw_status, sizeof(fw_status));
-}
-
/**
* mei_reset - resets host and fw.
*
@@ -126,11 +110,10 @@ int mei_reset(struct mei_device *dev)
mei_fw_status_str(dev, fw_sts_str, MEI_FW_STATUS_STR_SZ);
if (kind_is_gsc(dev) || kind_is_gscfi(dev)) {
- dev_dbg(dev->dev, "unexpected reset: dev_state = %s fw status = %s\n",
+ dev_dbg(&dev->dev, "unexpected reset: dev_state = %s fw status = %s\n",
mei_dev_state_str(state), fw_sts_str);
- mei_save_fw_status(dev);
} else {
- dev_warn(dev->dev, "unexpected reset: dev_state = %s fw status = %s\n",
+ dev_warn(&dev->dev, "unexpected reset: dev_state = %s fw status = %s\n",
mei_dev_state_str(state), fw_sts_str);
}
}
@@ -150,7 +133,7 @@ int mei_reset(struct mei_device *dev)
dev->reset_count++;
if (dev->reset_count > MEI_MAX_CONSEC_RESET) {
- dev_err(dev->dev, "reset: reached maximal consecutive resets: disabling the device\n");
+ dev_err(&dev->dev, "reset: reached maximal consecutive resets: disabling the device\n");
mei_set_devstate(dev, MEI_DEV_DISABLED);
return -ENODEV;
}
@@ -170,12 +153,12 @@ int mei_reset(struct mei_device *dev)
memset(dev->rd_msg_hdr, 0, sizeof(dev->rd_msg_hdr));
if (ret) {
- dev_err(dev->dev, "hw_reset failed ret = %d\n", ret);
+ dev_err(&dev->dev, "hw_reset failed ret = %d\n", ret);
return ret;
}
if (state == MEI_DEV_POWER_DOWN) {
- dev_dbg(dev->dev, "powering down: end of reset\n");
+ dev_dbg(&dev->dev, "powering down: end of reset\n");
mei_set_devstate(dev, MEI_DEV_DISABLED);
return 0;
}
@@ -185,21 +168,21 @@ int mei_reset(struct mei_device *dev)
char fw_sts_str[MEI_FW_STATUS_STR_SZ];
mei_fw_status_str(dev, fw_sts_str, MEI_FW_STATUS_STR_SZ);
- dev_err(dev->dev, "hw_start failed ret = %d fw status = %s\n", ret, fw_sts_str);
+ dev_err(&dev->dev, "hw_start failed ret = %d fw status = %s\n", ret, fw_sts_str);
return ret;
}
if (dev->dev_state != MEI_DEV_RESETTING) {
- dev_dbg(dev->dev, "wrong state = %d on link start\n", dev->dev_state);
+ dev_dbg(&dev->dev, "wrong state = %d on link start\n", dev->dev_state);
return 0;
}
- dev_dbg(dev->dev, "link is established start sending messages.\n");
+ dev_dbg(&dev->dev, "link is established start sending messages.\n");
mei_set_devstate(dev, MEI_DEV_INIT_CLIENTS);
ret = mei_hbm_start_req(dev);
if (ret) {
- dev_err(dev->dev, "hbm_start failed ret = %d\n", ret);
+ dev_err(&dev->dev, "hbm_start failed ret = %d\n", ret);
mei_set_devstate(dev, MEI_DEV_RESETTING);
return ret;
}
@@ -228,7 +211,7 @@ int mei_start(struct mei_device *dev)
if (ret)
goto err;
- dev_dbg(dev->dev, "reset in start the mei device.\n");
+ dev_dbg(&dev->dev, "reset in start the mei device.\n");
dev->reset_count = 0;
do {
@@ -236,27 +219,27 @@ int mei_start(struct mei_device *dev)
ret = mei_reset(dev);
if (ret == -ENODEV || dev->dev_state == MEI_DEV_DISABLED) {
- dev_err(dev->dev, "reset failed ret = %d", ret);
+ dev_err(&dev->dev, "reset failed ret = %d", ret);
goto err;
}
} while (ret);
if (mei_hbm_start_wait(dev)) {
- dev_err(dev->dev, "HBM haven't started");
+ dev_err(&dev->dev, "HBM haven't started");
goto err;
}
if (!mei_hbm_version_is_supported(dev)) {
- dev_dbg(dev->dev, "MEI start failed.\n");
+ dev_dbg(&dev->dev, "MEI start failed.\n");
goto err;
}
- dev_dbg(dev->dev, "link layer has been established.\n");
+ dev_dbg(&dev->dev, "link layer has been established.\n");
mutex_unlock(&dev->device_lock);
return 0;
err:
- dev_err(dev->dev, "link layer initialization failed.\n");
+ dev_err(&dev->dev, "link layer initialization failed.\n");
mei_set_devstate(dev, MEI_DEV_DISABLED);
mutex_unlock(&dev->device_lock);
return -ENODEV;
@@ -284,7 +267,7 @@ int mei_restart(struct mei_device *dev)
mutex_unlock(&dev->device_lock);
if (err == -ENODEV || dev->dev_state == MEI_DEV_DISABLED) {
- dev_err(dev->dev, "device disabled = %d\n", err);
+ dev_err(&dev->dev, "device disabled = %d\n", err);
return -ENODEV;
}
@@ -313,7 +296,7 @@ static void mei_reset_work(struct work_struct *work)
mutex_unlock(&dev->device_lock);
if (dev->dev_state == MEI_DEV_DISABLED) {
- dev_err(dev->dev, "device disabled = %d\n", ret);
+ dev_err(&dev->dev, "device disabled = %d\n", ret);
return;
}
@@ -324,7 +307,7 @@ static void mei_reset_work(struct work_struct *work)
void mei_stop(struct mei_device *dev)
{
- dev_dbg(dev->dev, "stopping the device.\n");
+ dev_dbg(&dev->dev, "stopping the device.\n");
mutex_lock(&dev->device_lock);
mei_set_devstate(dev, MEI_DEV_POWERING_DOWN);
@@ -365,7 +348,7 @@ bool mei_write_is_idle(struct mei_device *dev)
list_empty(&dev->write_list) &&
list_empty(&dev->write_waiting_list));
- dev_dbg(dev->dev, "write pg: is idle[%d] state=%s ctrl=%01d write=%01d wwait=%01d\n",
+ dev_dbg(&dev->dev, "write pg: is idle[%d] state=%s ctrl=%01d write=%01d wwait=%01d\n",
idle,
mei_dev_state_str(dev->dev_state),
list_empty(&dev->ctrl_wr_list),
@@ -380,12 +363,12 @@ EXPORT_SYMBOL_GPL(mei_write_is_idle);
* mei_device_init - initialize mei_device structure
*
* @dev: the mei device
- * @device: the device structure
+ * @parent: the parent device
* @slow_fw: configure longer timeouts as FW is slow
* @hw_ops: hw operations
*/
void mei_device_init(struct mei_device *dev,
- struct device *device,
+ struct device *parent,
bool slow_fw,
const struct mei_hw_ops *hw_ops)
{
@@ -399,7 +382,8 @@ void mei_device_init(struct mei_device *dev,
init_waitqueue_head(&dev->wait_hw_ready);
init_waitqueue_head(&dev->wait_pg);
init_waitqueue_head(&dev->wait_hbm_start);
- dev->dev_state = MEI_DEV_INITIALIZING;
+ dev->dev_state = MEI_DEV_UNINITIALIZED;
+ init_waitqueue_head(&dev->wait_dev_state);
dev->reset_count = 0;
INIT_LIST_HEAD(&dev->write_list);
@@ -426,7 +410,7 @@ void mei_device_init(struct mei_device *dev,
dev->pg_event = MEI_PG_EVENT_IDLE;
dev->ops = hw_ops;
- dev->dev = device;
+ dev->parent = parent;
dev->timeouts.hw_ready = mei_secs_to_jiffies(MEI_HW_READY_TIMEOUT);
dev->timeouts.connect = MEI_CONNECT_TIMEOUT;
@@ -442,6 +426,6 @@ void mei_device_init(struct mei_device *dev,
dev->timeouts.hbm = mei_secs_to_jiffies(MEI_HBM_TIMEOUT);
dev->timeouts.mkhi_recv = msecs_to_jiffies(MKHI_RCV_TIMEOUT);
}
+ dev->timeouts.link_reset_wait = msecs_to_jiffies(MEI_LINK_RESET_WAIT_TIMEOUT_MSEC);
}
EXPORT_SYMBOL_GPL(mei_device_init);
-
diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c
index d472f6bbe767..3aa66b6b0d36 100644
--- a/drivers/misc/mei/interrupt.c
+++ b/drivers/misc/mei/interrupt.c
@@ -76,7 +76,7 @@ static void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr,
* that length fits into rd_msg_buf
*/
mei_read_slots(dev, dev->rd_msg_buf, discard_len);
- dev_dbg(dev->dev, "discarding message " MEI_HDR_FMT "\n",
+ dev_dbg(&dev->dev, "discarding message " MEI_HDR_FMT "\n",
MEI_HDR_PRM(hdr));
}
@@ -229,8 +229,8 @@ static int mei_cl_irq_read_msg(struct mei_cl *cl,
cl_dbg(dev, cl, "completed read length = %zu\n", cb->buf_idx);
list_move_tail(&cb->list, cmpl_list);
} else {
- pm_runtime_mark_last_busy(dev->dev);
- pm_request_autosuspend(dev->dev);
+ pm_runtime_mark_last_busy(dev->parent);
+ pm_request_autosuspend(dev->parent);
}
return 0;
@@ -310,8 +310,8 @@ static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb,
return ret;
}
- pm_runtime_mark_last_busy(dev->dev);
- pm_request_autosuspend(dev->dev);
+ pm_runtime_mark_last_busy(dev->parent);
+ pm_request_autosuspend(dev->parent);
list_move_tail(&cb->list, &cl->rd_pending);
@@ -373,21 +373,21 @@ int mei_irq_read_handler(struct mei_device *dev,
dev->rd_msg_hdr[0] = mei_read_hdr(dev);
dev->rd_msg_hdr_count = 1;
(*slots)--;
- dev_dbg(dev->dev, "slots =%08x.\n", *slots);
+ dev_dbg(&dev->dev, "slots =%08x.\n", *slots);
ret = hdr_is_valid(dev->rd_msg_hdr[0]);
if (ret) {
- dev_err(dev->dev, "corrupted message header 0x%08X\n",
+ dev_err(&dev->dev, "corrupted message header 0x%08X\n",
dev->rd_msg_hdr[0]);
goto end;
}
}
mei_hdr = (struct mei_msg_hdr *)dev->rd_msg_hdr;
- dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr));
+ dev_dbg(&dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr));
if (mei_slots2data(*slots) < mei_hdr->length) {
- dev_err(dev->dev, "less data available than length=%08x.\n",
+ dev_err(&dev->dev, "less data available than length=%08x.\n",
*slots);
/* we can't read the message */
ret = -ENODATA;
@@ -402,18 +402,18 @@ int mei_irq_read_handler(struct mei_device *dev,
dev->rd_msg_hdr[1] = mei_read_hdr(dev);
dev->rd_msg_hdr_count++;
(*slots)--;
- dev_dbg(dev->dev, "extended header is %08x\n", dev->rd_msg_hdr[1]);
+ dev_dbg(&dev->dev, "extended header is %08x\n", dev->rd_msg_hdr[1]);
}
meta_hdr = ((struct mei_ext_meta_hdr *)&dev->rd_msg_hdr[1]);
if (check_add_overflow((u32)sizeof(*meta_hdr),
mei_slots2data(meta_hdr->size),
&hdr_size_ext)) {
- dev_err(dev->dev, "extended message size too big %d\n",
+ dev_err(&dev->dev, "extended message size too big %d\n",
meta_hdr->size);
return -EBADMSG;
}
if (hdr_size_left < hdr_size_ext) {
- dev_err(dev->dev, "corrupted message header len %d\n",
+ dev_err(&dev->dev, "corrupted message header len %d\n",
mei_hdr->length);
return -EBADMSG;
}
@@ -422,7 +422,7 @@ int mei_irq_read_handler(struct mei_device *dev,
ext_hdr_end = meta_hdr->size + 2;
for (i = dev->rd_msg_hdr_count; i < ext_hdr_end; i++) {
dev->rd_msg_hdr[i] = mei_read_hdr(dev);
- dev_dbg(dev->dev, "extended header %d is %08x\n", i,
+ dev_dbg(&dev->dev, "extended header %d is %08x\n", i,
dev->rd_msg_hdr[i]);
dev->rd_msg_hdr_count++;
(*slots)--;
@@ -431,7 +431,7 @@ int mei_irq_read_handler(struct mei_device *dev,
if (mei_hdr->dma_ring) {
if (hdr_size_left != sizeof(dev->rd_msg_hdr[ext_hdr_end])) {
- dev_err(dev->dev, "corrupted message header len %d\n",
+ dev_err(&dev->dev, "corrupted message header len %d\n",
mei_hdr->length);
return -EBADMSG;
}
@@ -446,8 +446,7 @@ int mei_irq_read_handler(struct mei_device *dev,
if (hdr_is_hbm(mei_hdr)) {
ret = mei_hbm_dispatch(dev, mei_hdr);
if (ret) {
- dev_dbg(dev->dev, "mei_hbm_dispatch failed ret = %d\n",
- ret);
+ dev_dbg(&dev->dev, "mei_hbm_dispatch failed ret = %d\n", ret);
goto end;
}
goto reset_slots;
@@ -474,7 +473,7 @@ int mei_irq_read_handler(struct mei_device *dev,
ret = 0;
goto reset_slots;
}
- dev_err(dev->dev, "no destination client found 0x%08X\n", dev->rd_msg_hdr[0]);
+ dev_err(&dev->dev, "no destination client found 0x%08X\n", dev->rd_msg_hdr[0]);
ret = -EBADMSG;
goto end;
@@ -485,7 +484,7 @@ reset_slots:
*slots = mei_count_full_read_slots(dev);
if (*slots == -EOVERFLOW) {
/* overflow - reset */
- dev_err(dev->dev, "resetting due to slots overflow.\n");
+ dev_err(&dev->dev, "resetting due to slots overflow.\n");
/* set the event since message has been read */
ret = -ERANGE;
goto end;
@@ -525,7 +524,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list)
return -EMSGSIZE;
/* complete all waiting for write CB */
- dev_dbg(dev->dev, "complete all waiting for write cb.\n");
+ dev_dbg(&dev->dev, "complete all waiting for write cb.\n");
list_for_each_entry_safe(cb, next, &dev->write_waiting_list, list) {
cl = cb->cl;
@@ -537,7 +536,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list)
}
/* complete control write list CB */
- dev_dbg(dev->dev, "complete control write list cb.\n");
+ dev_dbg(&dev->dev, "complete control write list cb.\n");
list_for_each_entry_safe(cb, next, &dev->ctrl_wr_list, list) {
cl = cb->cl;
switch (cb->fop_type) {
@@ -591,7 +590,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list)
}
/* complete write list CB */
- dev_dbg(dev->dev, "complete write list cb.\n");
+ dev_dbg(&dev->dev, "complete write list cb.\n");
list_for_each_entry_safe(cb, next, &dev->write_list, list) {
cl = cb->cl;
ret = mei_cl_irq_write(cl, cb, cmpl_list);
@@ -656,7 +655,7 @@ void mei_timer(struct work_struct *work)
if (dev->init_clients_timer) {
if (--dev->init_clients_timer == 0) {
- dev_err(dev->dev, "timer: init clients timeout hbm_state = %d.\n",
+ dev_err(&dev->dev, "timer: init clients timeout hbm_state = %d.\n",
dev->hbm_state);
mei_reset(dev);
goto out;
@@ -672,7 +671,7 @@ void mei_timer(struct work_struct *work)
list_for_each_entry(cl, &dev->file_list, link) {
if (cl->timer_count) {
if (--cl->timer_count == 0) {
- dev_err(dev->dev, "timer: connect/disconnect timeout.\n");
+ dev_err(&dev->dev, "timer: connect/disconnect timeout.\n");
mei_connect_timeout(cl);
goto out;
}
diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c
index 8a149a15b861..86a73684a373 100644
--- a/drivers/misc/mei/main.c
+++ b/drivers/misc/mei/main.c
@@ -51,12 +51,15 @@ static int mei_open(struct inode *inode, struct file *file)
int err;
- dev = container_of(inode->i_cdev, struct mei_device, cdev);
+ dev = idr_find(&mei_idr, iminor(inode));
+ if (!dev)
+ return -ENODEV;
+ get_device(&dev->dev);
mutex_lock(&dev->device_lock);
if (dev->dev_state != MEI_DEV_ENABLED) {
- dev_dbg(dev->dev, "dev_state != MEI_ENABLED dev_state = %s\n",
+ dev_dbg(&dev->dev, "dev_state != MEI_ENABLED dev_state = %s\n",
mei_dev_state_str(dev->dev_state));
err = -ENODEV;
goto err_unlock;
@@ -77,6 +80,7 @@ static int mei_open(struct inode *inode, struct file *file)
err_unlock:
mutex_unlock(&dev->device_lock);
+ put_device(&dev->dev);
return err;
}
@@ -152,6 +156,7 @@ out:
file->private_data = NULL;
mutex_unlock(&dev->device_lock);
+ put_device(&dev->dev);
return rets;
}
@@ -418,6 +423,7 @@ static int mei_ioctl_connect_client(struct file *file,
cl->state != MEI_FILE_DISCONNECTED)
return -EBUSY;
+retry:
/* find ME client we're trying to connect to */
me_cl = mei_me_cl_by_uuid(dev, in_client_uuid);
if (!me_cl) {
@@ -449,6 +455,28 @@ static int mei_ioctl_connect_client(struct file *file,
rets = mei_cl_connect(cl, me_cl, file);
+ if (rets && cl->status == -EFAULT &&
+ (dev->dev_state == MEI_DEV_RESETTING ||
+ dev->dev_state == MEI_DEV_INIT_CLIENTS)) {
+ /* in link reset, wait for it completion */
+ mutex_unlock(&dev->device_lock);
+ rets = wait_event_interruptible_timeout(dev->wait_dev_state,
+ dev->dev_state == MEI_DEV_ENABLED,
+ dev->timeouts.link_reset_wait);
+ mutex_lock(&dev->device_lock);
+ if (rets < 0) {
+ if (signal_pending(current))
+ rets = -EINTR;
+ goto end;
+ }
+ if (dev->dev_state != MEI_DEV_ENABLED) {
+ rets = -ETIME;
+ goto end;
+ }
+ mei_me_cl_put(me_cl);
+ goto retry;
+ }
+
end:
mei_me_cl_put(me_cl);
return rets;
@@ -477,7 +505,7 @@ static int mei_vt_support_check(struct mei_device *dev, const uuid_le *uuid)
me_cl = mei_me_cl_by_uuid(dev, uuid);
if (!me_cl) {
- dev_dbg(dev->dev, "Cannot connect to FW Client UUID = %pUl\n",
+ dev_dbg(&dev->dev, "Cannot connect to FW Client UUID = %pUl\n",
uuid);
return -ENOTTY;
}
@@ -641,7 +669,7 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data)
struct mei_cl *cl = file->private_data;
struct mei_connect_client_data conn;
struct mei_connect_client_data_vtag conn_vtag;
- const uuid_le *cl_uuid;
+ uuid_le cl_uuid;
struct mei_client *props;
u8 vtag;
u32 notify_get, notify_req;
@@ -669,18 +697,18 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data)
rets = -EFAULT;
goto out;
}
- cl_uuid = &conn.in_client_uuid;
+ cl_uuid = conn.in_client_uuid;
props = &conn.out_client_properties;
vtag = 0;
- rets = mei_vt_support_check(dev, cl_uuid);
+ rets = mei_vt_support_check(dev, &cl_uuid);
if (rets == -ENOTTY)
goto out;
if (!rets)
- rets = mei_ioctl_connect_vtag(file, cl_uuid, props,
+ rets = mei_ioctl_connect_vtag(file, &cl_uuid, props,
vtag);
else
- rets = mei_ioctl_connect_client(file, cl_uuid, props);
+ rets = mei_ioctl_connect_client(file, &cl_uuid, props);
if (rets)
goto out;
@@ -702,14 +730,14 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data)
goto out;
}
- cl_uuid = &conn_vtag.connect.in_client_uuid;
+ cl_uuid = conn_vtag.connect.in_client_uuid;
props = &conn_vtag.out_client_properties;
vtag = conn_vtag.connect.vtag;
- rets = mei_vt_support_check(dev, cl_uuid);
+ rets = mei_vt_support_check(dev, &cl_uuid);
if (rets == -EOPNOTSUPP)
cl_dbg(dev, cl, "FW Client %pUl does not support vtags\n",
- cl_uuid);
+ &cl_uuid);
if (rets)
goto out;
@@ -719,7 +747,7 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data)
goto out;
}
- rets = mei_ioctl_connect_vtag(file, cl_uuid, props, vtag);
+ rets = mei_ioctl_connect_vtag(file, &cl_uuid, props, vtag);
if (rets)
goto out;
@@ -1115,7 +1143,12 @@ void mei_set_devstate(struct mei_device *dev, enum mei_dev_state state)
dev->dev_state = state;
- clsdev = class_find_device_by_devt(&mei_class, dev->cdev.dev);
+ wake_up_interruptible_all(&dev->wait_dev_state);
+
+ if (!dev->cdev)
+ return;
+
+ clsdev = class_find_device_by_devt(&mei_class, dev->cdev->dev);
if (clsdev) {
sysfs_notify(&clsdev->kobj, NULL, "dev_state");
put_device(clsdev);
@@ -1191,7 +1224,7 @@ static int mei_minor_get(struct mei_device *dev)
if (ret >= 0)
dev->minor = ret;
else if (ret == -ENOSPC)
- dev_err(dev->dev, "too many mei devices\n");
+ dev_err(&dev->dev, "too many mei devices\n");
mutex_unlock(&mei_minor_lock);
return ret;
@@ -1200,56 +1233,81 @@ static int mei_minor_get(struct mei_device *dev)
/**
* mei_minor_free - mark device minor number as free
*
- * @dev: device pointer
+ * @minor: minor number to free
*/
-static void mei_minor_free(struct mei_device *dev)
+static void mei_minor_free(int minor)
{
mutex_lock(&mei_minor_lock);
- idr_remove(&mei_idr, dev->minor);
+ idr_remove(&mei_idr, minor);
mutex_unlock(&mei_minor_lock);
}
+static void mei_device_release(struct device *dev)
+{
+ kfree(dev_get_drvdata(dev));
+}
+
int mei_register(struct mei_device *dev, struct device *parent)
{
- struct device *clsdev; /* class device */
int ret, devno;
+ int minor;
ret = mei_minor_get(dev);
if (ret < 0)
return ret;
+ minor = dev->minor;
+
/* Fill in the data structures */
devno = MKDEV(MAJOR(mei_devt), dev->minor);
- cdev_init(&dev->cdev, &mei_fops);
- dev->cdev.owner = parent->driver->owner;
+
+ device_initialize(&dev->dev);
+ dev->dev.devt = devno;
+ dev->dev.class = &mei_class;
+ dev->dev.parent = parent;
+ dev->dev.groups = mei_groups;
+ dev->dev.release = mei_device_release;
+ dev_set_drvdata(&dev->dev, dev);
+
+ dev->cdev = cdev_alloc();
+ if (!dev->cdev) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ dev->cdev->ops = &mei_fops;
+ dev->cdev->owner = parent->driver->owner;
+ cdev_set_parent(dev->cdev, &dev->dev.kobj);
/* Add the device */
- ret = cdev_add(&dev->cdev, devno, 1);
+ ret = cdev_add(dev->cdev, devno, 1);
if (ret) {
- dev_err(parent, "unable to add device %d:%d\n",
+ dev_err(parent, "unable to add cdev for device %d:%d\n",
MAJOR(mei_devt), dev->minor);
- goto err_dev_add;
+ goto err_del_cdev;
}
- clsdev = device_create_with_groups(&mei_class, parent, devno,
- dev, mei_groups,
- "mei%d", dev->minor);
+ ret = dev_set_name(&dev->dev, "mei%d", dev->minor);
+ if (ret) {
+ dev_err(parent, "unable to set name to device %d:%d ret = %d\n",
+ MAJOR(mei_devt), dev->minor, ret);
+ goto err_del_cdev;
+ }
- if (IS_ERR(clsdev)) {
- dev_err(parent, "unable to create device %d:%d\n",
- MAJOR(mei_devt), dev->minor);
- ret = PTR_ERR(clsdev);
- goto err_dev_create;
+ ret = device_add(&dev->dev);
+ if (ret) {
+ dev_err(parent, "unable to add device %d:%d ret = %d\n",
+ MAJOR(mei_devt), dev->minor, ret);
+ goto err_del_cdev;
}
- mei_dbgfs_register(dev, dev_name(clsdev));
+ mei_dbgfs_register(dev, dev_name(&dev->dev));
return 0;
-err_dev_create:
- cdev_del(&dev->cdev);
-err_dev_add:
- mei_minor_free(dev);
+err_del_cdev:
+ cdev_del(dev->cdev);
+err:
+ mei_minor_free(minor);
return ret;
}
EXPORT_SYMBOL_GPL(mei_register);
@@ -1257,15 +1315,16 @@ EXPORT_SYMBOL_GPL(mei_register);
void mei_deregister(struct mei_device *dev)
{
int devno;
+ int minor = dev->minor;
- devno = dev->cdev.dev;
- cdev_del(&dev->cdev);
+ devno = dev->cdev->dev;
+ cdev_del(dev->cdev);
mei_dbgfs_deregister(dev);
device_destroy(&mei_class, devno);
- mei_minor_free(dev);
+ mei_minor_free(minor);
}
EXPORT_SYMBOL_GPL(mei_deregister);
diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h
index 37d7fb15cad7..0bf8d552c3ea 100644
--- a/drivers/misc/mei/mei_dev.h
+++ b/drivers/misc/mei/mei_dev.h
@@ -57,7 +57,8 @@ enum file_state {
/* MEI device states */
enum mei_dev_state {
- MEI_DEV_INITIALIZING = 0,
+ MEI_DEV_UNINITIALIZED = 0,
+ MEI_DEV_INITIALIZING,
MEI_DEV_INIT_CLIENTS,
MEI_DEV_ENABLED,
MEI_DEV_RESETTING,
@@ -465,13 +466,15 @@ struct mei_dev_timeouts {
unsigned int d0i3; /* D0i3 set/unset max response time, in jiffies */
unsigned long hbm; /* HBM operation timeout, in jiffies */
unsigned long mkhi_recv; /* receive timeout, in jiffies */
+ unsigned long link_reset_wait; /* link reset wait timeout, in jiffies */
};
/**
* struct mei_device - MEI private device struct
*
- * @dev : device on a bus
- * @cdev : character device
+ * @parent : device on a bus
+ * @dev : device object
+ * @cdev : character device pointer
* @minor : minor number allocated for device
*
* @write_list : write pending list
@@ -494,6 +497,7 @@ struct mei_dev_timeouts {
*
* @reset_count : number of consecutive resets
* @dev_state : device state
+ * @wait_dev_state: wait queue for device state change
* @hbm_state : state of host bus message protocol
* @pxp_mode : PXP device mode
* @init_clients_timer : HBM init handshake timeout
@@ -547,17 +551,15 @@ struct mei_dev_timeouts {
*
* @dbgfs_dir : debugfs mei root directory
*
- * @saved_fw_status : saved firmware status
- * @saved_dev_state : saved device state
- * @saved_fw_status_flag : flag indicating that firmware status was saved
* @gsc_reset_to_pxp : state of reset to the PXP mode
*
* @ops: : hw specific operations
* @hw : hw specific data
*/
struct mei_device {
- struct device *dev;
- struct cdev cdev;
+ struct device *parent;
+ struct device dev;
+ struct cdev *cdev;
int minor;
struct list_head write_list;
@@ -585,6 +587,7 @@ struct mei_device {
*/
unsigned long reset_count;
enum mei_dev_state dev_state;
+ wait_queue_head_t wait_dev_state;
enum mei_hbm_state hbm_state;
enum mei_dev_pxp_mode pxp_mode;
u16 init_clients_timer;
@@ -648,9 +651,6 @@ struct mei_device {
struct dentry *dbgfs_dir;
#endif /* CONFIG_DEBUG_FS */
- struct mei_fw_status saved_fw_status;
- enum mei_dev_state saved_dev_state;
- bool saved_fw_status_flag;
enum mei_dev_reset_to_pxp gsc_reset_to_pxp;
const struct mei_hw_ops *ops;
@@ -703,7 +703,7 @@ static inline u32 mei_slots2data(int slots)
* mei init function prototypes
*/
void mei_device_init(struct mei_device *dev,
- struct device *device,
+ struct device *parent,
bool slow_fw,
const struct mei_hw_ops *hw_ops);
int mei_reset(struct mei_device *dev);
diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c
index 3f9c60b579ae..b108a7c22388 100644
--- a/drivers/misc/mei/pci-me.c
+++ b/drivers/misc/mei/pci-me.c
@@ -143,7 +143,7 @@ static inline void mei_me_unset_pm_domain(struct mei_device *dev) {}
static int mei_me_read_fws(const struct mei_device *dev, int where, u32 *val)
{
- struct pci_dev *pdev = to_pci_dev(dev->dev);
+ struct pci_dev *pdev = to_pci_dev(dev->parent);
return pci_read_config_dword(pdev, where, val);
}
@@ -238,19 +238,19 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
goto end;
}
+ err = mei_register(dev, &pdev->dev);
+ if (err)
+ goto release_irq;
+
if (mei_start(dev)) {
dev_err(&pdev->dev, "init hw failure.\n");
err = -ENODEV;
- goto release_irq;
+ goto deregister;
}
pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_ME_RPM_TIMEOUT);
pm_runtime_use_autosuspend(&pdev->dev);
- err = mei_register(dev, &pdev->dev);
- if (err)
- goto stop;
-
pci_set_drvdata(pdev, dev);
/*
@@ -280,8 +280,8 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
return 0;
-stop:
- mei_stop(dev);
+deregister:
+ mei_deregister(dev);
release_irq:
mei_cancel_work(dev);
mei_disable_interrupts(dev);
@@ -475,7 +475,7 @@ static int mei_me_pm_runtime_resume(struct device *device)
*/
static inline void mei_me_set_pm_domain(struct mei_device *dev)
{
- struct pci_dev *pdev = to_pci_dev(dev->dev);
+ struct pci_dev *pdev = to_pci_dev(dev->parent);
if (pdev->dev.bus && pdev->dev.bus->pm) {
dev->pg_domain.ops = *pdev->dev.bus->pm;
@@ -496,7 +496,7 @@ static inline void mei_me_set_pm_domain(struct mei_device *dev)
static inline void mei_me_unset_pm_domain(struct mei_device *dev)
{
/* stop using pm callbacks if any */
- dev_pm_domain_set(dev->dev, NULL);
+ dev_pm_domain_set(dev->parent, NULL);
}
static const struct dev_pm_ops mei_me_pm_ops = {
diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c
index 2a584104ba38..c9eb5c5393e4 100644
--- a/drivers/misc/mei/pci-txe.c
+++ b/drivers/misc/mei/pci-txe.c
@@ -321,7 +321,7 @@ static int mei_txe_pm_runtime_resume(struct device *device)
*/
static inline void mei_txe_set_pm_domain(struct mei_device *dev)
{
- struct pci_dev *pdev = to_pci_dev(dev->dev);
+ struct pci_dev *pdev = to_pci_dev(dev->parent);
if (pdev->dev.bus && pdev->dev.bus->pm) {
dev->pg_domain.ops = *pdev->dev.bus->pm;
@@ -342,7 +342,7 @@ static inline void mei_txe_set_pm_domain(struct mei_device *dev)
static inline void mei_txe_unset_pm_domain(struct mei_device *dev)
{
/* stop using pm callbacks if any */
- dev_pm_domain_set(dev->dev, NULL);
+ dev_pm_domain_set(dev->parent, NULL);
}
static const struct dev_pm_ops mei_txe_pm_ops = {
diff --git a/drivers/misc/mei/platform-vsc.c b/drivers/misc/mei/platform-vsc.c
index b2b5a20ae3fa..288e7b72e942 100644
--- a/drivers/misc/mei/platform-vsc.c
+++ b/drivers/misc/mei/platform-vsc.c
@@ -152,7 +152,7 @@ static int mei_vsc_hw_start(struct mei_device *mei_dev)
MEI_VSC_POLL_TIMEOUT_US, true,
hw, &buf, sizeof(buf));
if (ret) {
- dev_err(mei_dev->dev, "wait fw ready failed: %d\n", ret);
+ dev_err(&mei_dev->dev, "wait fw ready failed: %d\n", ret);
return ret;
}
@@ -259,7 +259,7 @@ static int mei_vsc_hw_reset(struct mei_device *mei_dev, bool intr_enable)
if (!intr_enable)
return 0;
- return vsc_tp_init(hw->tp, mei_dev->dev);
+ return vsc_tp_init(hw->tp, mei_dev->parent);
}
static const struct mei_hw_ops mei_vsc_hw_ops = {
@@ -325,7 +325,7 @@ static void mei_vsc_event_cb(void *context)
mei_dev->hbuf_is_ready = mei_hbuf_is_ready(mei_dev);
ret = mei_irq_write_handler(mei_dev, &cmpl_list);
if (ret)
- dev_err(mei_dev->dev, "dispatch write request failed: %d\n", ret);
+ dev_err(&mei_dev->dev, "dispatch write request failed: %d\n", ret);
mei_dev->hbuf_is_ready = mei_hbuf_is_ready(mei_dev);
mei_irq_compl_handler(mei_dev, &cmpl_list);
@@ -343,12 +343,12 @@ static int mei_vsc_probe(struct platform_device *pdev)
if (!tp)
return dev_err_probe(dev, -ENODEV, "no platform data\n");
- mei_dev = devm_kzalloc(dev, size_add(sizeof(*mei_dev), sizeof(*hw)),
- GFP_KERNEL);
+ mei_dev = kzalloc(size_add(sizeof(*mei_dev), sizeof(*hw)), GFP_KERNEL);
if (!mei_dev)
return -ENOMEM;
mei_device_init(mei_dev, dev, false, &mei_vsc_hw_ops);
+
mei_dev->fw_f_fw_ver_supported = 0;
mei_dev->kind = "ivsc";
@@ -360,22 +360,22 @@ static int mei_vsc_probe(struct platform_device *pdev)
vsc_tp_register_event_cb(tp, mei_vsc_event_cb, mei_dev);
+ ret = mei_register(mei_dev, dev);
+ if (ret)
+ goto err_dereg;
+
ret = mei_start(mei_dev);
if (ret) {
dev_err_probe(dev, ret, "init hw failed\n");
goto err_cancel;
}
- ret = mei_register(mei_dev, dev);
- if (ret)
- goto err_stop;
-
- pm_runtime_enable(mei_dev->dev);
+ pm_runtime_enable(mei_dev->parent);
return 0;
-err_stop:
- mei_stop(mei_dev);
+err_dereg:
+ mei_deregister(mei_dev);
err_cancel:
mei_cancel_work(mei_dev);
@@ -392,7 +392,7 @@ static void mei_vsc_remove(struct platform_device *pdev)
struct mei_device *mei_dev = platform_get_drvdata(pdev);
struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
- pm_runtime_disable(mei_dev->dev);
+ pm_runtime_disable(mei_dev->parent);
mei_stop(mei_dev);
diff --git a/drivers/mtd/chips/cfi_probe.c b/drivers/mtd/chips/cfi_probe.c
index a04b6174181c..e254f9cd2796 100644
--- a/drivers/mtd/chips/cfi_probe.c
+++ b/drivers/mtd/chips/cfi_probe.c
@@ -208,7 +208,7 @@ static int __xipram cfi_chip_setup(struct map_info *map,
if (!num_erase_regions)
return 0;
- cfi->cfiq = kmalloc(sizeof(struct cfi_ident) + num_erase_regions * 4, GFP_KERNEL);
+ cfi->cfiq = kmalloc(struct_size(cfi->cfiq, EraseRegionInfo, num_erase_regions), GFP_KERNEL);
if (!cfi->cfiq)
return 0;
diff --git a/drivers/mtd/chips/jedec_probe.c b/drivers/mtd/chips/jedec_probe.c
index 23c32fe584b7..b285962eee2a 100644
--- a/drivers/mtd/chips/jedec_probe.c
+++ b/drivers/mtd/chips/jedec_probe.c
@@ -1953,7 +1953,7 @@ static void jedec_reset(u32 base, struct map_info *map, struct cfi_private *cfi)
* as they will ignore the writes and don't care what address
* the F0 is written to */
if (cfi->addr_unlock1) {
- pr_debug( "reset unlock called %x %x \n",
+ pr_debug("reset unlock called %x %x\n",
cfi->addr_unlock1,cfi->addr_unlock2);
cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL);
cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, cfi->device_type, NULL);
@@ -1985,7 +1985,7 @@ static int cfi_jedec_setup(struct map_info *map, struct cfi_private *cfi, int in
num_erase_regions = jedec_table[index].nr_regions;
- cfi->cfiq = kmalloc(sizeof(struct cfi_ident) + num_erase_regions * 4, GFP_KERNEL);
+ cfi->cfiq = kmalloc(struct_size(cfi->cfiq, EraseRegionInfo, num_erase_regions), GFP_KERNEL);
if (!cfi->cfiq) {
//xx printk(KERN_WARNING "%s: kmalloc failed for CFI ident structure\n", map->name);
return 0;
diff --git a/drivers/mtd/ftl.c b/drivers/mtd/ftl.c
index f2bd1984609c..59a901549257 100644
--- a/drivers/mtd/ftl.c
+++ b/drivers/mtd/ftl.c
@@ -263,7 +263,7 @@ static int build_maps(partition_t *part)
/* Set up virtual page map */
blocks = le32_to_cpu(header.FormattedSize) >> header.BlockSize;
- part->VirtualBlockMap = vmalloc(array_size(blocks, sizeof(uint32_t)));
+ part->VirtualBlockMap = vmalloc_array(blocks, sizeof(uint32_t));
if (!part->VirtualBlockMap)
goto out_XferInfo;
diff --git a/drivers/mtd/hyperbus/hbmc-am654.c b/drivers/mtd/hyperbus/hbmc-am654.c
index 82a1e7b7e4d8..9d31464046b2 100644
--- a/drivers/mtd/hyperbus/hbmc-am654.c
+++ b/drivers/mtd/hyperbus/hbmc-am654.c
@@ -272,5 +272,4 @@ module_platform_driver(am654_hbmc_platform_driver);
MODULE_DESCRIPTION("HBMC driver for AM654 SoC");
MODULE_LICENSE("GPL v2");
-MODULE_ALIAS("platform:hbmc-am654");
MODULE_AUTHOR("Vignesh Raghavendra <vigneshr@ti.com>");
diff --git a/drivers/mtd/lpddr/lpddr_cmds.c b/drivers/mtd/lpddr/lpddr_cmds.c
index 14e36ae71958..290fd0119e98 100644
--- a/drivers/mtd/lpddr/lpddr_cmds.c
+++ b/drivers/mtd/lpddr/lpddr_cmds.c
@@ -142,7 +142,7 @@ static int wait_for_ready(struct map_info *map, struct flchip *chip,
if (dsr & DSR_READY_STATUS)
break;
if (!timeo) {
- printk(KERN_ERR "%s: Flash timeout error state %d \n",
+ printk(KERN_ERR "%s: Flash timeout error state %d\n",
map->name, chip_state);
ret = -ETIME;
break;
@@ -186,7 +186,7 @@ static int wait_for_ready(struct map_info *map, struct flchip *chip,
if (dsr & DSR_ERR) {
/* Clear DSR*/
map_write(map, CMD(~(DSR_ERR)), map->pfow_base + PFOW_DSR);
- printk(KERN_WARNING"%s: Bad status on wait: 0x%x \n",
+ printk(KERN_WARNING"%s: Bad status on wait: 0x%x\n",
map->name, dsr);
print_drs_error(dsr);
ret = -EIO;
@@ -321,7 +321,7 @@ static int chip_ready(struct map_info *map, struct flchip *chip, int mode)
/* Resume and pretend we weren't here. */
put_chip(map, chip);
printk(KERN_ERR "%s: suspend operation failed."
- "State may be wrong \n", map->name);
+ "State may be wrong\n", map->name);
return -EIO;
}
chip->erase_suspended = 1;
@@ -468,7 +468,7 @@ static int do_write_buffer(struct map_info *map, struct flchip *chip,
chip->state = FL_WRITING;
ret = wait_for_ready(map, chip, (1<<lpddr->qinfo->ProgBufferTime));
if (ret) {
- printk(KERN_WARNING"%s Buffer program error: %d at %lx; \n",
+ printk(KERN_WARNING"%s Buffer program error: %d at %lx\n",
map->name, ret, adr);
goto out;
}
@@ -736,7 +736,7 @@ static int do_xxlock(struct mtd_info *mtd, loff_t adr, uint32_t len, int thunk)
ret = wait_for_ready(map, chip, 1);
if (ret) {
- printk(KERN_ERR "%s: block unlock error status %d \n",
+ printk(KERN_ERR "%s: block unlock error status %d\n",
map->name, ret);
goto out;
}
diff --git a/drivers/mtd/lpddr/qinfo_probe.c b/drivers/mtd/lpddr/qinfo_probe.c
index 137ae5f0a19b..42281e460c62 100644
--- a/drivers/mtd/lpddr/qinfo_probe.c
+++ b/drivers/mtd/lpddr/qinfo_probe.c
@@ -55,7 +55,7 @@ static long lpddr_get_qinforec_pos(struct map_info *map, char *id_str)
return minor | (major << bankwidth);
}
}
- printk(KERN_ERR"%s qinfo id string is wrong! \n", map->name);
+ printk(KERN_ERR"%s qinfo id string is wrong!\n", map->name);
BUG();
return -1;
}
@@ -112,7 +112,7 @@ static int lpddr_pfow_present(struct map_info *map, struct lpddr_private *lpddr)
return 1; /* "PFOW" is found */
out:
- printk(KERN_WARNING"%s: PFOW string at 0x%lx is not found \n",
+ printk(KERN_WARNING"%s: PFOW string at 0x%lx is not found\n",
map->name, map->pfow_base);
return 0;
}
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 5ba9a741f5ac..64808493b4f5 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -384,14 +384,64 @@ EXPORT_SYMBOL_GPL(mtd_check_expert_analysis_mode);
static struct dentry *dfs_dir_mtd;
+static int mtd_ooblayout_show(struct seq_file *s, void *p,
+ int (*iter)(struct mtd_info *, int section,
+ struct mtd_oob_region *region))
+{
+ struct mtd_info *mtd = s->private;
+ int section;
+
+ for (section = 0;; section++) {
+ struct mtd_oob_region region;
+ int err;
+
+ err = iter(mtd, section, &region);
+ if (err) {
+ if (err == -ERANGE)
+ break;
+
+ return err;
+ }
+
+ seq_printf(s, "%-3d %4u %4u\n", section, region.offset,
+ region.length);
+ }
+
+ return 0;
+}
+
+static int mtd_ooblayout_ecc_show(struct seq_file *s, void *p)
+{
+ return mtd_ooblayout_show(s, p, mtd_ooblayout_ecc);
+}
+DEFINE_SHOW_ATTRIBUTE(mtd_ooblayout_ecc);
+
+static int mtd_ooblayout_free_show(struct seq_file *s, void *p)
+{
+ return mtd_ooblayout_show(s, p, mtd_ooblayout_free);
+}
+DEFINE_SHOW_ATTRIBUTE(mtd_ooblayout_free);
+
static void mtd_debugfs_populate(struct mtd_info *mtd)
{
struct device *dev = &mtd->dev;
+ struct mtd_oob_region region;
if (IS_ERR_OR_NULL(dfs_dir_mtd))
return;
mtd->dbg.dfs_dir = debugfs_create_dir(dev_name(dev), dfs_dir_mtd);
+ if (IS_ERR_OR_NULL(mtd->dbg.dfs_dir))
+ return;
+
+ /* Create ooblayout files only if at least one region is present. */
+ if (mtd_ooblayout_ecc(mtd, 0, &region) == 0)
+ debugfs_create_file("ooblayout_ecc", 0444, mtd->dbg.dfs_dir,
+ mtd, &mtd_ooblayout_ecc_fops);
+
+ if (mtd_ooblayout_free(mtd, 0, &region) == 0)
+ debugfs_create_file("ooblayout_free", 0444, mtd->dbg.dfs_dir,
+ mtd, &mtd_ooblayout_free_fops);
}
#ifndef CONFIG_MMU
@@ -2339,6 +2389,7 @@ EXPORT_SYMBOL_GPL(mtd_block_isbad);
int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs)
{
struct mtd_info *master = mtd_get_master(mtd);
+ loff_t moffs;
int ret;
if (!master->_block_markbad)
@@ -2351,7 +2402,15 @@ int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs)
if (mtd->flags & MTD_SLC_ON_MLC_EMULATION)
ofs = (loff_t)mtd_div_by_eb(ofs, mtd) * master->erasesize;
- ret = master->_block_markbad(master, mtd_get_master_ofs(mtd, ofs));
+ moffs = mtd_get_master_ofs(mtd, ofs);
+
+ if (master->_block_isbad) {
+ ret = master->_block_isbad(master, moffs);
+ if (ret > 0)
+ return 0;
+ }
+
+ ret = master->_block_markbad(master, moffs);
if (ret)
return ret;
diff --git a/drivers/mtd/mtdoops.c b/drivers/mtd/mtdoops.c
index 7bf3777e1f13..b88083751a0c 100644
--- a/drivers/mtd/mtdoops.c
+++ b/drivers/mtd/mtdoops.c
@@ -356,9 +356,8 @@ static void mtdoops_notify_add(struct mtd_info *mtd)
/* oops_page_used is a bit field */
cxt->oops_page_used =
- vmalloc(array_size(sizeof(unsigned long),
- DIV_ROUND_UP(mtdoops_pages,
- BITS_PER_LONG)));
+ vmalloc_array(DIV_ROUND_UP(mtdoops_pages, BITS_PER_LONG),
+ sizeof(unsigned long));
if (!cxt->oops_page_used) {
pr_err("could not allocate page array\n");
return;
diff --git a/drivers/mtd/mtdswap.c b/drivers/mtd/mtdswap.c
index 680366616da2..d8f2e5be2d31 100644
--- a/drivers/mtd/mtdswap.c
+++ b/drivers/mtd/mtdswap.c
@@ -1285,11 +1285,11 @@ static int mtdswap_init(struct mtdswap_dev *d, unsigned int eblocks,
for (i = 0; i < MTDSWAP_TREE_CNT; i++)
d->trees[i].root = RB_ROOT;
- d->page_data = vmalloc(array_size(pages, sizeof(int)));
+ d->page_data = vmalloc_array(pages, sizeof(int));
if (!d->page_data)
goto page_data_fail;
- d->revmap = vmalloc(array_size(blocks, sizeof(int)));
+ d->revmap = vmalloc_array(blocks, sizeof(int));
if (!d->revmap)
goto revmap_fail;
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 5b0c2c95f10c..4a17271076bc 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -61,6 +61,14 @@ config MTD_NAND_ECC_MEDIATEK
help
This enables support for the hardware ECC engine from Mediatek.
+config MTD_NAND_ECC_REALTEK
+ tristate "Realtek RTL93xx hardware ECC engine"
+ depends on HAS_IOMEM
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
+ select MTD_NAND_ECC
+ help
+ This enables support for the hardware ECC engine from Realtek.
+
endmenu
endmenu
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 44913ff1bf12..2e0e56267718 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -3,6 +3,7 @@
nandcore-objs := core.o bbt.o
obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o
+obj-$(CONFIG_MTD_NAND_ECC_REALTEK) += ecc-realtek.o
obj-$(CONFIG_SPI_QPIC_SNAND) += qpic_common.o
obj-$(CONFIG_MTD_NAND_QCOM) += qpic_common.o
obj-y += onenand/
diff --git a/drivers/mtd/nand/core.c b/drivers/mtd/nand/core.c
index 7737b1a4a177..3e76d127715f 100644
--- a/drivers/mtd/nand/core.c
+++ b/drivers/mtd/nand/core.c
@@ -13,6 +13,137 @@
#include <linux/mtd/nand.h>
/**
+ * nand_check_erased_buf - check if a buffer contains (almost) only 0xff data
+ * @buf: buffer to test
+ * @len: buffer length
+ * @bitflips_threshold: maximum number of bitflips
+ *
+ * Check if a buffer contains only 0xff, which means the underlying region
+ * has been erased and is ready to be programmed.
+ * The bitflips_threshold specify the maximum number of bitflips before
+ * considering the region is not erased.
+ * Note: The logic of this function has been extracted from the memweight
+ * implementation, except that nand_check_erased_buf function exit before
+ * testing the whole buffer if the number of bitflips exceed the
+ * bitflips_threshold value.
+ *
+ * Returns a positive number of bitflips less than or equal to
+ * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
+ * threshold.
+ */
+static int nand_check_erased_buf(void *buf, int len, int bitflips_threshold)
+{
+ const unsigned char *bitmap = buf;
+ int bitflips = 0;
+ int weight;
+
+ for (; len && ((uintptr_t)bitmap) % sizeof(long);
+ len--, bitmap++) {
+ weight = hweight8(*bitmap);
+ bitflips += BITS_PER_BYTE - weight;
+ if (unlikely(bitflips > bitflips_threshold))
+ return -EBADMSG;
+ }
+
+ for (; len >= sizeof(long);
+ len -= sizeof(long), bitmap += sizeof(long)) {
+ unsigned long d = *((unsigned long *)bitmap);
+ if (d == ~0UL)
+ continue;
+ weight = hweight_long(d);
+ bitflips += BITS_PER_LONG - weight;
+ if (unlikely(bitflips > bitflips_threshold))
+ return -EBADMSG;
+ }
+
+ for (; len > 0; len--, bitmap++) {
+ weight = hweight8(*bitmap);
+ bitflips += BITS_PER_BYTE - weight;
+ if (unlikely(bitflips > bitflips_threshold))
+ return -EBADMSG;
+ }
+
+ return bitflips;
+}
+
+/**
+ * nand_check_erased_ecc_chunk - check if an ECC chunk contains (almost) only
+ * 0xff data
+ * @data: data buffer to test
+ * @datalen: data length
+ * @ecc: ECC buffer
+ * @ecclen: ECC length
+ * @extraoob: extra OOB buffer
+ * @extraooblen: extra OOB length
+ * @bitflips_threshold: maximum number of bitflips
+ *
+ * Check if a data buffer and its associated ECC and OOB data contains only
+ * 0xff pattern, which means the underlying region has been erased and is
+ * ready to be programmed.
+ * The bitflips_threshold specify the maximum number of bitflips before
+ * considering the region as not erased.
+ *
+ * Note:
+ * 1/ ECC algorithms are working on pre-defined block sizes which are usually
+ * different from the NAND page size. When fixing bitflips, ECC engines will
+ * report the number of errors per chunk, and the NAND core infrastructure
+ * expect you to return the maximum number of bitflips for the whole page.
+ * This is why you should always use this function on a single chunk and
+ * not on the whole page. After checking each chunk you should update your
+ * max_bitflips value accordingly.
+ * 2/ When checking for bitflips in erased pages you should not only check
+ * the payload data but also their associated ECC data, because a user might
+ * have programmed almost all bits to 1 but a few. In this case, we
+ * shouldn't consider the chunk as erased, and checking ECC bytes prevent
+ * this case.
+ * 3/ The extraoob argument is optional, and should be used if some of your OOB
+ * data are protected by the ECC engine.
+ * It could also be used if you support subpages and want to attach some
+ * extra OOB data to an ECC chunk.
+ *
+ * Returns a positive number of bitflips less than or equal to
+ * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
+ * threshold. In case of success, the passed buffers are filled with 0xff.
+ */
+int nand_check_erased_ecc_chunk(void *data, int datalen,
+ void *ecc, int ecclen,
+ void *extraoob, int extraooblen,
+ int bitflips_threshold)
+{
+ int data_bitflips = 0, ecc_bitflips = 0, extraoob_bitflips = 0;
+
+ data_bitflips = nand_check_erased_buf(data, datalen,
+ bitflips_threshold);
+ if (data_bitflips < 0)
+ return data_bitflips;
+
+ bitflips_threshold -= data_bitflips;
+
+ ecc_bitflips = nand_check_erased_buf(ecc, ecclen, bitflips_threshold);
+ if (ecc_bitflips < 0)
+ return ecc_bitflips;
+
+ bitflips_threshold -= ecc_bitflips;
+
+ extraoob_bitflips = nand_check_erased_buf(extraoob, extraooblen,
+ bitflips_threshold);
+ if (extraoob_bitflips < 0)
+ return extraoob_bitflips;
+
+ if (data_bitflips)
+ memset(data, 0xff, datalen);
+
+ if (ecc_bitflips)
+ memset(ecc, 0xff, ecclen);
+
+ if (extraoob_bitflips)
+ memset(extraoob, 0xff, extraooblen);
+
+ return data_bitflips + ecc_bitflips + extraoob_bitflips;
+}
+EXPORT_SYMBOL(nand_check_erased_ecc_chunk);
+
+/**
* nanddev_isbad() - Check if a block is bad
* @nand: NAND device
* @pos: position pointing to the block we want to check
diff --git a/drivers/mtd/nand/ecc-mxic.c b/drivers/mtd/nand/ecc-mxic.c
index 1bf9a5a64b87..60cdcb4175ef 100644
--- a/drivers/mtd/nand/ecc-mxic.c
+++ b/drivers/mtd/nand/ecc-mxic.c
@@ -322,14 +322,14 @@ static int mxic_ecc_init_ctx(struct nand_device *nand, struct device *dev)
sg_init_table(ctx->sg, 2);
/* Configuration dump and sanity checks */
- dev_err(dev, "DPE version number: %d\n",
+ dev_dbg(dev, "DPE version number: %d\n",
readl(mxic->regs + DP_VER) >> DP_VER_OFFSET);
- dev_err(dev, "Chunk size: %d\n", readl(mxic->regs + CHUNK_SIZE));
- dev_err(dev, "Main size: %d\n", readl(mxic->regs + MAIN_SIZE));
- dev_err(dev, "Spare size: %d\n", SPARE_SZ(spare_reg));
- dev_err(dev, "Rsv size: %ld\n", RSV_SZ(spare_reg));
- dev_err(dev, "Parity size: %d\n", ctx->parity_sz);
- dev_err(dev, "Meta size: %d\n", ctx->meta_sz);
+ dev_dbg(dev, "Chunk size: %d\n", readl(mxic->regs + CHUNK_SIZE));
+ dev_dbg(dev, "Main size: %d\n", readl(mxic->regs + MAIN_SIZE));
+ dev_dbg(dev, "Spare size: %d\n", SPARE_SZ(spare_reg));
+ dev_dbg(dev, "Rsv size: %ld\n", RSV_SZ(spare_reg));
+ dev_dbg(dev, "Parity size: %d\n", ctx->parity_sz);
+ dev_dbg(dev, "Meta size: %d\n", ctx->meta_sz);
if ((ctx->meta_sz + ctx->parity_sz + RSV_SZ(spare_reg)) !=
SPARE_SZ(spare_reg)) {
diff --git a/drivers/mtd/nand/ecc-realtek.c b/drivers/mtd/nand/ecc-realtek.c
new file mode 100644
index 000000000000..7d718934c909
--- /dev/null
+++ b/drivers/mtd/nand/ecc-realtek.c
@@ -0,0 +1,464 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support for Realtek hardware ECC engine in RTL93xx SoCs
+ */
+
+#include <linux/bitfield.h>
+#include <linux/dma-mapping.h>
+#include <linux/mtd/nand.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/*
+ * The Realtek ECC engine has two operation modes.
+ *
+ * - BCH6 : Generate 10 ECC bytes from 512 data bytes plus 6 free bytes
+ * - BCH12 : Generate 20 ECC bytes from 512 data bytes plus 6 free bytes
+ *
+ * It can run for arbitrary NAND flash chips with different block and OOB sizes. Currently there
+ * are only two known devices in the wild that have NAND flash and make use of this ECC engine
+ * (Linksys LGS328C & LGS352C). To keep compatibility with vendor firmware, new modes can only
+ * be added when new data layouts have been analyzed. For now allow BCH6 on flash with 2048 byte
+ * blocks and 64 bytes oob.
+ *
+ * This driver aligns with kernel ECC naming conventions. Neverthless a short notice on the
+ * Realtek naming conventions for the different structures in the OOB area.
+ *
+ * - BBI : Bad block indicator. The first two bytes of OOB. Protected by ECC!
+ * - tag : 6 User/free bytes. First tag "contains" 2 bytes BBI. Protected by ECC!
+ * - syndrome : ECC/parity bytes
+ *
+ * Altogether this gives currently the following block layout.
+ *
+ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
+ * | 512 | 512 | 512 | 512 | 2 | 4 | 6 | 6 | 6 | 10 | 10 | 10 | 10 |
+ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
+ * | data | data | data | data | BBI | free | free | free | free | ECC | ECC | ECC | ECC |
+ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
+ */
+
+#define RTL_ECC_ALLOWED_PAGE_SIZE 2048
+#define RTL_ECC_ALLOWED_OOB_SIZE 64
+#define RTL_ECC_ALLOWED_STRENGTH 6
+
+#define RTL_ECC_BLOCK_SIZE 512
+#define RTL_ECC_FREE_SIZE 6
+#define RTL_ECC_PARITY_SIZE_BCH6 10
+#define RTL_ECC_PARITY_SIZE_BCH12 20
+
+/*
+ * The engine is fed with two DMA regions. One for data (always 512 bytes) and one for free bytes
+ * and parity (either 16 bytes for BCH6 or 26 bytes for BCH12). Start and length of each must be
+ * aligned to a multiple of 4.
+ */
+
+#define RTL_ECC_DMA_FREE_PARITY_SIZE ALIGN(RTL_ECC_FREE_SIZE + RTL_ECC_PARITY_SIZE_BCH12, 4)
+#define RTL_ECC_DMA_SIZE (RTL_ECC_BLOCK_SIZE + RTL_ECC_DMA_FREE_PARITY_SIZE)
+
+#define RTL_ECC_CFG 0x00
+#define RTL_ECC_BCH6 0
+#define RTL_ECC_BCH12 BIT(28)
+#define RTL_ECC_DMA_PRECISE BIT(12)
+#define RTL_ECC_BURST_128 GENMASK(1, 0)
+#define RTL_ECC_DMA_TRIGGER 0x08
+#define RTL_ECC_OP_DECODE 0
+#define RTL_ECC_OP_ENCODE BIT(0)
+#define RTL_ECC_DMA_START 0x0c
+#define RTL_ECC_DMA_TAG 0x10
+#define RTL_ECC_STATUS 0x14
+#define RTL_ECC_CORR_COUNT GENMASK(19, 12)
+#define RTL_ECC_RESULT BIT(8)
+#define RTL_ECC_ALL_ONE BIT(4)
+#define RTL_ECC_OP_STATUS BIT(0)
+
+struct rtl_ecc_engine {
+ struct device *dev;
+ struct nand_ecc_engine engine;
+ struct mutex lock;
+ char *buf;
+ dma_addr_t buf_dma;
+ struct regmap *regmap;
+};
+
+struct rtl_ecc_ctx {
+ struct rtl_ecc_engine * rtlc;
+ struct nand_ecc_req_tweak_ctx req_ctx;
+ int steps;
+ int bch_mode;
+ int strength;
+ int parity_size;
+};
+
+static const struct regmap_config rtl_ecc_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static inline void *nand_to_ctx(struct nand_device *nand)
+{
+ return nand->ecc.ctx.priv;
+}
+
+static inline struct rtl_ecc_engine *nand_to_rtlc(struct nand_device *nand)
+{
+ struct nand_ecc_engine *eng = nand->ecc.engine;
+
+ return container_of(eng, struct rtl_ecc_engine, engine);
+}
+
+static int rtl_ecc_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ struct nand_device *nand = mtd_to_nanddev(mtd);
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
+
+ if (section < 0 || section >= ctx->steps)
+ return -ERANGE;
+
+ oobregion->offset = ctx->steps * RTL_ECC_FREE_SIZE + section * ctx->parity_size;
+ oobregion->length = ctx->parity_size;
+
+ return 0;
+}
+
+static int rtl_ecc_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ struct nand_device *nand = mtd_to_nanddev(mtd);
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
+ int bbm;
+
+ if (section < 0 || section >= ctx->steps)
+ return -ERANGE;
+
+ /* reserve 2 BBM bytes in first block */
+ bbm = section ? 0 : 2;
+ oobregion->offset = section * RTL_ECC_FREE_SIZE + bbm;
+ oobregion->length = RTL_ECC_FREE_SIZE - bbm;
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops rtl_ecc_ooblayout_ops = {
+ .ecc = rtl_ecc_ooblayout_ecc,
+ .free = rtl_ecc_ooblayout_free,
+};
+
+static void rtl_ecc_kick_engine(struct rtl_ecc_ctx *ctx, int operation)
+{
+ struct rtl_ecc_engine *rtlc = ctx->rtlc;
+
+ regmap_write(rtlc->regmap, RTL_ECC_CFG,
+ ctx->bch_mode | RTL_ECC_BURST_128 | RTL_ECC_DMA_PRECISE);
+
+ regmap_write(rtlc->regmap, RTL_ECC_DMA_START, rtlc->buf_dma);
+ regmap_write(rtlc->regmap, RTL_ECC_DMA_TAG, rtlc->buf_dma + RTL_ECC_BLOCK_SIZE);
+ regmap_write(rtlc->regmap, RTL_ECC_DMA_TRIGGER, operation);
+}
+
+static int rtl_ecc_wait_for_engine(struct rtl_ecc_ctx *ctx)
+{
+ struct rtl_ecc_engine *rtlc = ctx->rtlc;
+ int ret, status, bitflips;
+ bool all_one;
+
+ /*
+ * The ECC engine needs 6-8 us to encode/decode a BCH6 syndrome for 512 bytes of data
+ * and 6 free bytes. In case the NAND area has been erased and all data and oob is
+ * set to 0xff, decoding takes 30us (reason unknown). Although the engine can trigger
+ * interrupts when finished, use active polling for now. 12 us maximum wait time has
+ * proven to be a good tradeoff between performance and overhead.
+ */
+
+ ret = regmap_read_poll_timeout(rtlc->regmap, RTL_ECC_STATUS, status,
+ !(status & RTL_ECC_OP_STATUS), 12, 1000000);
+ if (ret)
+ return ret;
+
+ ret = FIELD_GET(RTL_ECC_RESULT, status);
+ all_one = FIELD_GET(RTL_ECC_ALL_ONE, status);
+ bitflips = FIELD_GET(RTL_ECC_CORR_COUNT, status);
+
+ /* For erased blocks (all bits one) error status can be ignored */
+ if (all_one)
+ ret = 0;
+
+ return ret ? -EBADMSG : bitflips;
+}
+
+static int rtl_ecc_run_engine(struct rtl_ecc_ctx *ctx, char *data, char *free,
+ char *parity, int operation)
+{
+ struct rtl_ecc_engine *rtlc = ctx->rtlc;
+ char *buf_parity = rtlc->buf + RTL_ECC_BLOCK_SIZE + RTL_ECC_FREE_SIZE;
+ char *buf_free = rtlc->buf + RTL_ECC_BLOCK_SIZE;
+ char *buf_data = rtlc->buf;
+ int ret;
+
+ mutex_lock(&rtlc->lock);
+
+ memcpy(buf_data, data, RTL_ECC_BLOCK_SIZE);
+ memcpy(buf_free, free, RTL_ECC_FREE_SIZE);
+ memcpy(buf_parity, parity, ctx->parity_size);
+
+ dma_sync_single_for_device(rtlc->dev, rtlc->buf_dma, RTL_ECC_DMA_SIZE, DMA_TO_DEVICE);
+ rtl_ecc_kick_engine(ctx, operation);
+ ret = rtl_ecc_wait_for_engine(ctx);
+ dma_sync_single_for_cpu(rtlc->dev, rtlc->buf_dma, RTL_ECC_DMA_SIZE, DMA_FROM_DEVICE);
+
+ if (ret >= 0) {
+ memcpy(data, buf_data, RTL_ECC_BLOCK_SIZE);
+ memcpy(free, buf_free, RTL_ECC_FREE_SIZE);
+ memcpy(parity, buf_parity, ctx->parity_size);
+ }
+
+ mutex_unlock(&rtlc->lock);
+
+ return ret;
+}
+
+static int rtl_ecc_prepare_io_req(struct nand_device *nand, struct nand_page_io_req *req)
+{
+ struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
+ char *data, *free, *parity;
+ int ret = 0;
+
+ if (req->mode == MTD_OPS_RAW)
+ return 0;
+
+ nand_ecc_tweak_req(&ctx->req_ctx, req);
+
+ if (req->type == NAND_PAGE_READ)
+ return 0;
+
+ free = req->oobbuf.in;
+ data = req->databuf.in;
+ parity = req->oobbuf.in + ctx->steps * RTL_ECC_FREE_SIZE;
+
+ for (int i = 0; i < ctx->steps; i++) {
+ ret |= rtl_ecc_run_engine(ctx, data, free, parity, RTL_ECC_OP_ENCODE);
+
+ free += RTL_ECC_FREE_SIZE;
+ data += RTL_ECC_BLOCK_SIZE;
+ parity += ctx->parity_size;
+ }
+
+ if (unlikely(ret))
+ dev_dbg(rtlc->dev, "ECC calculation failed\n");
+
+ return ret ? -EBADMSG : 0;
+}
+
+static int rtl_ecc_finish_io_req(struct nand_device *nand, struct nand_page_io_req *req)
+{
+ struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
+ char *data, *free, *parity;
+ bool failure = false;
+ int bitflips = 0;
+
+ if (req->mode == MTD_OPS_RAW)
+ return 0;
+
+ if (req->type == NAND_PAGE_WRITE) {
+ nand_ecc_restore_req(&ctx->req_ctx, req);
+ return 0;
+ }
+
+ free = req->oobbuf.in;
+ data = req->databuf.in;
+ parity = req->oobbuf.in + ctx->steps * RTL_ECC_FREE_SIZE;
+
+ for (int i = 0 ; i < ctx->steps; i++) {
+ int ret = rtl_ecc_run_engine(ctx, data, free, parity, RTL_ECC_OP_DECODE);
+
+ if (unlikely(ret < 0))
+ /* ECC totally fails for bitflips in erased blocks */
+ ret = nand_check_erased_ecc_chunk(data, RTL_ECC_BLOCK_SIZE,
+ parity, ctx->parity_size,
+ free, RTL_ECC_FREE_SIZE,
+ ctx->strength);
+ if (unlikely(ret < 0)) {
+ failure = true;
+ mtd->ecc_stats.failed++;
+ } else {
+ mtd->ecc_stats.corrected += ret;
+ bitflips = max_t(unsigned int, bitflips, ret);
+ }
+
+ free += RTL_ECC_FREE_SIZE;
+ data += RTL_ECC_BLOCK_SIZE;
+ parity += ctx->parity_size;
+ }
+
+ nand_ecc_restore_req(&ctx->req_ctx, req);
+
+ if (unlikely(failure))
+ dev_dbg(rtlc->dev, "ECC correction failed\n");
+ else if (unlikely(bitflips > 2))
+ dev_dbg(rtlc->dev, "%d bitflips detected\n", bitflips);
+
+ return failure ? -EBADMSG : bitflips;
+}
+
+static int rtl_ecc_check_support(struct nand_device *nand)
+{
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
+ struct device *dev = nand->ecc.engine->dev;
+
+ if (mtd->oobsize != RTL_ECC_ALLOWED_OOB_SIZE ||
+ mtd->writesize != RTL_ECC_ALLOWED_PAGE_SIZE) {
+ dev_err(dev, "only flash geometry data=%d, oob=%d supported\n",
+ RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_OOB_SIZE);
+ return -EINVAL;
+ }
+
+ if (nand->ecc.user_conf.algo != NAND_ECC_ALGO_BCH ||
+ nand->ecc.user_conf.strength != RTL_ECC_ALLOWED_STRENGTH ||
+ nand->ecc.user_conf.placement != NAND_ECC_PLACEMENT_OOB ||
+ nand->ecc.user_conf.step_size != RTL_ECC_BLOCK_SIZE) {
+ dev_err(dev, "only algo=bch, strength=%d, placement=oob, step=%d supported\n",
+ RTL_ECC_ALLOWED_STRENGTH, RTL_ECC_BLOCK_SIZE);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rtl_ecc_init_ctx(struct nand_device *nand)
+{
+ struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
+ struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
+ int strength = nand->ecc.user_conf.strength;
+ struct device *dev = nand->ecc.engine->dev;
+ struct rtl_ecc_ctx *ctx;
+ int ret;
+
+ ret = rtl_ecc_check_support(nand);
+ if (ret)
+ return ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ nand->ecc.ctx.priv = ctx;
+ mtd_set_ooblayout(mtd, &rtl_ecc_ooblayout_ops);
+
+ conf->algo = NAND_ECC_ALGO_BCH;
+ conf->strength = strength;
+ conf->step_size = RTL_ECC_BLOCK_SIZE;
+ conf->engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
+
+ ctx->rtlc = rtlc;
+ ctx->steps = mtd->writesize / RTL_ECC_BLOCK_SIZE;
+ ctx->strength = strength;
+ ctx->bch_mode = strength == 6 ? RTL_ECC_BCH6 : RTL_ECC_BCH12;
+ ctx->parity_size = strength == 6 ? RTL_ECC_PARITY_SIZE_BCH6 : RTL_ECC_PARITY_SIZE_BCH12;
+
+ ret = nand_ecc_init_req_tweaking(&ctx->req_ctx, nand);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev, "using bch%d with geometry data=%dx%d, free=%dx6, parity=%dx%d",
+ conf->strength, ctx->steps, conf->step_size,
+ ctx->steps, ctx->steps, ctx->parity_size);
+
+ return 0;
+}
+
+static void rtl_ecc_cleanup_ctx(struct nand_device *nand)
+{
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
+
+ if (ctx)
+ nand_ecc_cleanup_req_tweaking(&ctx->req_ctx);
+}
+
+static struct nand_ecc_engine_ops rtl_ecc_engine_ops = {
+ .init_ctx = rtl_ecc_init_ctx,
+ .cleanup_ctx = rtl_ecc_cleanup_ctx,
+ .prepare_io_req = rtl_ecc_prepare_io_req,
+ .finish_io_req = rtl_ecc_finish_io_req,
+};
+
+static int rtl_ecc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rtl_ecc_engine *rtlc;
+ void __iomem *base;
+ int ret;
+
+ rtlc = devm_kzalloc(dev, sizeof(*rtlc), GFP_KERNEL);
+ if (!rtlc)
+ return -ENOMEM;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ ret = devm_mutex_init(dev, &rtlc->lock);
+ if (ret)
+ return ret;
+
+ rtlc->regmap = devm_regmap_init_mmio(dev, base, &rtl_ecc_regmap_config);
+ if (IS_ERR(rtlc->regmap))
+ return PTR_ERR(rtlc->regmap);
+
+ /*
+ * Focus on simplicity and use a preallocated DMA buffer for data exchange with the
+ * engine. For now make it a noncoherent memory model as invalidating/flushing caches
+ * is faster than reading/writing uncached memory on the known architectures.
+ */
+
+ rtlc->buf = dma_alloc_noncoherent(dev, RTL_ECC_DMA_SIZE, &rtlc->buf_dma,
+ DMA_BIDIRECTIONAL, GFP_KERNEL);
+ if (IS_ERR(rtlc->buf))
+ return PTR_ERR(rtlc->buf);
+
+ rtlc->dev = dev;
+ rtlc->engine.dev = dev;
+ rtlc->engine.ops = &rtl_ecc_engine_ops;
+ rtlc->engine.integration = NAND_ECC_ENGINE_INTEGRATION_EXTERNAL;
+
+ nand_ecc_register_on_host_hw_engine(&rtlc->engine);
+
+ platform_set_drvdata(pdev, rtlc);
+
+ return 0;
+}
+
+static void rtl_ecc_remove(struct platform_device *pdev)
+{
+ struct rtl_ecc_engine *rtlc = platform_get_drvdata(pdev);
+
+ nand_ecc_unregister_on_host_hw_engine(&rtlc->engine);
+ dma_free_noncoherent(rtlc->dev, RTL_ECC_DMA_SIZE, rtlc->buf, rtlc->buf_dma,
+ DMA_BIDIRECTIONAL);
+}
+
+static const struct of_device_id rtl_ecc_of_ids[] = {
+ {
+ .compatible = "realtek,rtl9301-ecc",
+ },
+ { /* sentinel */ },
+};
+
+static struct platform_driver rtl_ecc_driver = {
+ .driver = {
+ .name = "rtl-nand-ecc-engine",
+ .of_match_table = rtl_ecc_of_ids,
+ },
+ .probe = rtl_ecc_probe,
+ .remove = rtl_ecc_remove,
+};
+module_platform_driver(rtl_ecc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Markus Stockhausen <markus.stockhausen@gmx.de>");
+MODULE_DESCRIPTION("Realtek NAND hardware ECC controller");
diff --git a/drivers/mtd/nand/ecc.c b/drivers/mtd/nand/ecc.c
index 8f996e8d61b8..6ccdff3fc913 100644
--- a/drivers/mtd/nand/ecc.c
+++ b/drivers/mtd/nand/ecc.c
@@ -552,7 +552,7 @@ void nand_ecc_tweak_req(struct nand_ecc_req_tweak_ctx *ctx,
memset(tweak->oobbuf.in, 0xFF, ctx->oob_buffer_size);
}
- /* Copy the data that must be writen in the bounce buffers, if needed */
+ /* Copy the data that must be written in the bounce buffers, if needed */
if (orig->type == NAND_PAGE_WRITE) {
if (ctx->bounce_data)
memcpy((void *)tweak->databuf.out + orig->dataoffs,
diff --git a/drivers/mtd/nand/onenand/onenand_omap2.c b/drivers/mtd/nand/onenand/onenand_omap2.c
index f9a386b69050..0793251ada3b 100644
--- a/drivers/mtd/nand/onenand/onenand_omap2.c
+++ b/drivers/mtd/nand/onenand/onenand_omap2.c
@@ -603,7 +603,6 @@ static struct platform_driver omap2_onenand_driver = {
module_platform_driver(omap2_onenand_driver);
-MODULE_ALIAS("platform:" DRIVER_NAME);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jarkko Lavinen <jarkko.lavinen@nokia.com>");
MODULE_DESCRIPTION("Glue layer for OneNAND flash on OMAP2 / OMAP3");
diff --git a/drivers/mtd/nand/qpic_common.c b/drivers/mtd/nand/qpic_common.c
index 8e604cc22ca3..db6c46a6fe01 100644
--- a/drivers/mtd/nand/qpic_common.c
+++ b/drivers/mtd/nand/qpic_common.c
@@ -89,10 +89,8 @@ void qcom_clear_bam_transaction(struct qcom_nand_controller *nandc)
memset(&bam_txn->bam_positions, 0, sizeof(bam_txn->bam_positions));
bam_txn->last_data_desc = NULL;
- sg_init_table(bam_txn->cmd_sgl, nandc->max_cwperpage *
- QPIC_PER_CW_CMD_SGL);
- sg_init_table(bam_txn->data_sgl, nandc->max_cwperpage *
- QPIC_PER_CW_DATA_SGL);
+ sg_init_table(bam_txn->cmd_sgl, bam_txn->cmd_sgl_nitems);
+ sg_init_table(bam_txn->data_sgl, bam_txn->data_sgl_nitems);
reinit_completion(&bam_txn->txn_done);
}
diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index 4b99d9c422c3..7408f34f0c68 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -77,32 +77,6 @@ config MTD_NAND_NDFC
help
NDFC Nand Flash Controllers are integrated in IBM/AMCC's 4xx SoCs
-config MTD_NAND_S3C2410
- tristate "Samsung S3C NAND controller"
- depends on ARCH_S3C64XX
- help
- This enables the NAND flash controller on the S3C24xx and S3C64xx
- SoCs
-
- No board specific support is done by this driver, each board
- must advertise a platform_device for the driver to attach.
-
-config MTD_NAND_S3C2410_DEBUG
- bool "Samsung S3C NAND controller debug"
- depends on MTD_NAND_S3C2410
- help
- Enable debugging of the S3C NAND driver
-
-config MTD_NAND_S3C2410_CLKSTOP
- bool "Samsung S3C NAND IDLE clock stop"
- depends on MTD_NAND_S3C2410
- default n
- help
- Stop the clock to the NAND controller when there is no chip
- selected to save power. This will mean there is a small delay
- when the is NAND chip selected or released, but will save
- approximately 5mA of power when there is nothing happening.
-
config MTD_NAND_SHARPSL
tristate "Sharp SL Series (C7xx + others) NAND controller"
depends on ARCH_PXA || COMPILE_TEST
@@ -462,12 +436,12 @@ config MTD_NAND_NUVOTON_MA35
Enables support for the NAND controller found on
the Nuvoton MA35 series SoCs.
-config MTD_NAND_LOONGSON1
- tristate "Loongson1 NAND controller"
- depends on LOONGSON1_APB_DMA || COMPILE_TEST
+config MTD_NAND_LOONGSON
+ tristate "Loongson NAND controller"
+ depends on LOONGSON1_APB_DMA || LOONGSON2_APB_DMA || COMPILE_TEST
select REGMAP_MMIO
help
- Enables support for NAND controller on Loongson1 SoCs.
+ Enables support for NAND controller on Loongson family chips.
comment "Misc"
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index 711d043ad4f8..619760138d32 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -9,7 +9,6 @@ obj-$(CONFIG_MTD_NAND_DENALI) += denali.o
obj-$(CONFIG_MTD_NAND_DENALI_PCI) += denali_pci.o
obj-$(CONFIG_MTD_NAND_DENALI_DT) += denali_dt.o
obj-$(CONFIG_MTD_NAND_AU1550) += au1550nd.o
-obj-$(CONFIG_MTD_NAND_S3C2410) += s3c2410.o
obj-$(CONFIG_MTD_NAND_DAVINCI) += davinci_nand.o
obj-$(CONFIG_MTD_NAND_DISKONCHIP) += diskonchip.o
obj-$(CONFIG_MTD_NAND_FSMC) += fsmc_nand.o
@@ -59,7 +58,7 @@ obj-$(CONFIG_MTD_NAND_ROCKCHIP) += rockchip-nand-controller.o
obj-$(CONFIG_MTD_NAND_PL35X) += pl35x-nand-controller.o
obj-$(CONFIG_MTD_NAND_RENESAS) += renesas-nand-controller.o
obj-$(CONFIG_MTD_NAND_NUVOTON_MA35) += nuvoton-ma35d1-nand-controller.o
-obj-$(CONFIG_MTD_NAND_LOONGSON1) += loongson1-nand-controller.o
+obj-$(CONFIG_MTD_NAND_LOONGSON) += loongson-nand-controller.o
nand-objs := nand_base.o nand_legacy.o nand_bbt.o nand_timings.o nand_ids.o
nand-objs += nand_onfi.o
diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c
index db94d14a3807..83ba4ebd02d4 100644
--- a/drivers/mtd/nand/raw/atmel/nand-controller.c
+++ b/drivers/mtd/nand/raw/atmel/nand-controller.c
@@ -1240,7 +1240,7 @@ static int atmel_smc_nand_prepare_smcconf(struct atmel_nand *nand,
const struct nand_interface_config *conf,
struct atmel_smc_cs_conf *smcconf)
{
- u32 ncycles, totalcycles, timeps, mckperiodps;
+ u32 ncycles, totalcycles, timeps, mckperiodps, pulse;
struct atmel_nand_controller *nc;
int ret;
@@ -1366,11 +1366,16 @@ static int atmel_smc_nand_prepare_smcconf(struct atmel_nand *nand,
ATMEL_SMC_MODE_TDFMODE_OPTIMIZED;
/*
- * Read pulse timing directly matches tRP:
+ * Read pulse timing would directly match tRP,
+ * but some NAND flash chips (S34ML01G2 and W29N02KVxxAF)
+ * do not work properly in timing mode 3.
+ * The workaround is to extend the SMC NRD pulse to meet tREA
+ * timing.
*
- * NRD_PULSE = tRP
+ * NRD_PULSE = max(tRP, tREA)
*/
- ncycles = DIV_ROUND_UP(conf->timings.sdr.tRP_min, mckperiodps);
+ pulse = max(conf->timings.sdr.tRP_min, conf->timings.sdr.tREA_max);
+ ncycles = DIV_ROUND_UP(pulse, mckperiodps);
totalcycles += ncycles;
ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NRD_SHIFT,
ncycles);
@@ -1858,7 +1863,7 @@ atmel_nand_controller_legacy_add_nands(struct atmel_nand_controller *nc)
static int atmel_nand_controller_add_nands(struct atmel_nand_controller *nc)
{
- struct device_node *np, *nand_np;
+ struct device_node *np;
struct device *dev = nc->dev;
int ret, reg_cells;
u32 val;
@@ -1885,7 +1890,7 @@ static int atmel_nand_controller_add_nands(struct atmel_nand_controller *nc)
reg_cells += val;
- for_each_child_of_node(np, nand_np) {
+ for_each_child_of_node_scoped(np, nand_np) {
struct atmel_nand *nand;
nand = atmel_nand_create(nc, nand_np, reg_cells);
diff --git a/drivers/mtd/nand/raw/atmel/pmecc.c b/drivers/mtd/nand/raw/atmel/pmecc.c
index 0b402823b619..1d0e93e4edb1 100644
--- a/drivers/mtd/nand/raw/atmel/pmecc.c
+++ b/drivers/mtd/nand/raw/atmel/pmecc.c
@@ -1010,4 +1010,3 @@ module_platform_driver(atmel_pmecc_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
MODULE_DESCRIPTION("PMECC engine driver");
-MODULE_ALIAS("platform:atmel_pmecc");
diff --git a/drivers/mtd/nand/raw/fsmc_nand.c b/drivers/mtd/nand/raw/fsmc_nand.c
index df61db8ce466..b13b2b0c3f30 100644
--- a/drivers/mtd/nand/raw/fsmc_nand.c
+++ b/drivers/mtd/nand/raw/fsmc_nand.c
@@ -876,10 +876,14 @@ static int fsmc_nand_probe_config_dt(struct platform_device *pdev,
if (!of_property_read_u32(np, "bank-width", &val)) {
if (val == 2) {
nand->options |= NAND_BUSWIDTH_16;
- } else if (val != 1) {
+ } else if (val == 1) {
+ nand->options |= NAND_BUSWIDTH_AUTO;
+ } else {
dev_err(&pdev->dev, "invalid bank-width %u\n", val);
return -EINVAL;
}
+ } else {
+ nand->options |= NAND_BUSWIDTH_AUTO;
}
if (of_property_read_bool(np, "nand-skip-bbtscan"))
diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
index f4e68008ea03..a750f5839e34 100644
--- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
+++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
@@ -145,6 +145,9 @@ err_clk:
return ret;
}
+#define gpmi_enable_clk(x) __gpmi_enable_clk(x, true)
+#define gpmi_disable_clk(x) __gpmi_enable_clk(x, false)
+
static int gpmi_init(struct gpmi_nand_data *this)
{
struct resources *r = &this->resources;
@@ -2765,6 +2768,11 @@ static int gpmi_nand_probe(struct platform_device *pdev)
pm_runtime_enable(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, 500);
pm_runtime_use_autosuspend(&pdev->dev);
+#ifndef CONFIG_PM
+ ret = gpmi_enable_clk(this);
+ if (ret)
+ goto exit_acquire_resources;
+#endif
ret = gpmi_init(this);
if (ret)
@@ -2800,6 +2808,9 @@ static void gpmi_nand_remove(struct platform_device *pdev)
release_resources(this);
pm_runtime_dont_use_autosuspend(&pdev->dev);
pm_runtime_disable(&pdev->dev);
+#ifndef CONFIG_PM
+ gpmi_disable_clk(this);
+#endif
}
static int gpmi_pm_suspend(struct device *dev)
@@ -2846,9 +2857,6 @@ static int gpmi_pm_resume(struct device *dev)
return 0;
}
-#define gpmi_enable_clk(x) __gpmi_enable_clk(x, true)
-#define gpmi_disable_clk(x) __gpmi_enable_clk(x, false)
-
static int gpmi_runtime_suspend(struct device *dev)
{
struct gpmi_nand_data *this = dev_get_drvdata(dev);
diff --git a/drivers/mtd/nand/raw/loongson-nand-controller.c b/drivers/mtd/nand/raw/loongson-nand-controller.c
new file mode 100644
index 000000000000..8490412d5be1
--- /dev/null
+++ b/drivers/mtd/nand/raw/loongson-nand-controller.c
@@ -0,0 +1,1024 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * NAND Controller Driver for Loongson family chips
+ *
+ * Copyright (C) 2015-2025 Keguang Zhang <keguang.zhang@gmail.com>
+ * Copyright (C) 2025 Binbin Zhou <zhoubinbin@loongson.cn>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/iopoll.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/rawnand.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/sizes.h>
+
+/* Loongson NAND Controller Registers */
+#define LOONGSON_NAND_CMD 0x0
+#define LOONGSON_NAND_ADDR1 0x4
+#define LOONGSON_NAND_ADDR2 0x8
+#define LOONGSON_NAND_TIMING 0xc
+#define LOONGSON_NAND_IDL 0x10
+#define LOONGSON_NAND_IDH_STATUS 0x14
+#define LOONGSON_NAND_PARAM 0x18
+#define LOONGSON_NAND_OP_NUM 0x1c
+#define LOONGSON_NAND_CS_RDY_MAP 0x20
+
+/* Bitfields of nand command register */
+#define LOONGSON_NAND_CMD_OP_DONE BIT(10)
+#define LOONGSON_NAND_CMD_OP_SPARE BIT(9)
+#define LOONGSON_NAND_CMD_OP_MAIN BIT(8)
+#define LOONGSON_NAND_CMD_STATUS BIT(7)
+#define LOONGSON_NAND_CMD_RESET BIT(6)
+#define LOONGSON_NAND_CMD_READID BIT(5)
+#define LOONGSON_NAND_CMD_BLOCKS_ERASE BIT(4)
+#define LOONGSON_NAND_CMD_ERASE BIT(3)
+#define LOONGSON_NAND_CMD_WRITE BIT(2)
+#define LOONGSON_NAND_CMD_READ BIT(1)
+#define LOONGSON_NAND_CMD_VALID BIT(0)
+
+/* Bitfields of nand cs/rdy map register */
+#define LOONGSON_NAND_MAP_CS1_SEL GENMASK(11, 8)
+#define LOONGSON_NAND_MAP_RDY1_SEL GENMASK(15, 12)
+#define LOONGSON_NAND_MAP_CS2_SEL GENMASK(19, 16)
+#define LOONGSON_NAND_MAP_RDY2_SEL GENMASK(23, 20)
+#define LOONGSON_NAND_MAP_CS3_SEL GENMASK(27, 24)
+#define LOONGSON_NAND_MAP_RDY3_SEL GENMASK(31, 28)
+
+#define LOONGSON_NAND_CS_SEL0 BIT(0)
+#define LOONGSON_NAND_CS_SEL1 BIT(1)
+#define LOONGSON_NAND_CS_SEL2 BIT(2)
+#define LOONGSON_NAND_CS_SEL3 BIT(3)
+#define LOONGSON_NAND_CS_RDY0 BIT(0)
+#define LOONGSON_NAND_CS_RDY1 BIT(1)
+#define LOONGSON_NAND_CS_RDY2 BIT(2)
+#define LOONGSON_NAND_CS_RDY3 BIT(3)
+
+/* Bitfields of nand timing register */
+#define LOONGSON_NAND_WAIT_CYCLE_MASK GENMASK(7, 0)
+#define LOONGSON_NAND_HOLD_CYCLE_MASK GENMASK(15, 8)
+
+/* Bitfields of nand parameter register */
+#define LOONGSON_NAND_CELL_SIZE_MASK GENMASK(11, 8)
+
+#define LOONGSON_NAND_COL_ADDR_CYC 2U
+#define LOONGSON_NAND_MAX_ADDR_CYC 5U
+
+#define LOONGSON_NAND_READ_ID_SLEEP_US 1000
+#define LOONGSON_NAND_READ_ID_TIMEOUT_US 5000
+
+#define BITS_PER_WORD (4 * BITS_PER_BYTE)
+
+/* Loongson-2K1000 NAND DMA routing register */
+#define LS2K1000_NAND_DMA_MASK GENMASK(2, 0)
+#define LS2K1000_DMA0_CONF 0x0
+#define LS2K1000_DMA1_CONF 0x1
+#define LS2K1000_DMA2_CONF 0x2
+#define LS2K1000_DMA3_CONF 0x3
+#define LS2K1000_DMA4_CONF 0x4
+
+struct loongson_nand_host;
+
+struct loongson_nand_op {
+ char addrs[LOONGSON_NAND_MAX_ADDR_CYC];
+ unsigned int naddrs;
+ unsigned int addrs_offset;
+ unsigned int aligned_offset;
+ unsigned int cmd_reg;
+ unsigned int row_start;
+ unsigned int rdy_timeout_ms;
+ unsigned int orig_len;
+ bool is_readid;
+ bool is_erase;
+ bool is_write;
+ bool is_read;
+ bool is_change_column;
+ size_t len;
+ char *buf;
+};
+
+struct loongson_nand_data {
+ unsigned int max_id_cycle;
+ unsigned int id_cycle_field;
+ unsigned int status_field;
+ unsigned int op_scope_field;
+ unsigned int hold_cycle;
+ unsigned int wait_cycle;
+ unsigned int nand_cs;
+ unsigned int dma_bits;
+ int (*dma_config)(struct device *dev);
+ void (*set_addr)(struct loongson_nand_host *host, struct loongson_nand_op *op);
+};
+
+struct loongson_nand_host {
+ struct device *dev;
+ struct nand_chip chip;
+ struct nand_controller controller;
+ const struct loongson_nand_data *data;
+ unsigned int addr_cs_field;
+ void __iomem *reg_base;
+ struct regmap *regmap;
+ /* DMA Engine stuff */
+ dma_addr_t dma_base;
+ struct dma_chan *dma_chan;
+ dma_cookie_t dma_cookie;
+ struct completion dma_complete;
+};
+
+static const struct regmap_config loongson_nand_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static int loongson_nand_op_cmd_mapping(struct nand_chip *chip, struct loongson_nand_op *op,
+ u8 opcode)
+{
+ struct loongson_nand_host *host = nand_get_controller_data(chip);
+
+ op->row_start = chip->page_shift + 1;
+
+ /* The controller abstracts the following NAND operations. */
+ switch (opcode) {
+ case NAND_CMD_STATUS:
+ op->cmd_reg = LOONGSON_NAND_CMD_STATUS;
+ break;
+ case NAND_CMD_RESET:
+ op->cmd_reg = LOONGSON_NAND_CMD_RESET;
+ break;
+ case NAND_CMD_READID:
+ op->is_readid = true;
+ op->cmd_reg = LOONGSON_NAND_CMD_READID;
+ break;
+ case NAND_CMD_ERASE1:
+ op->is_erase = true;
+ op->addrs_offset = LOONGSON_NAND_COL_ADDR_CYC;
+ break;
+ case NAND_CMD_ERASE2:
+ if (!op->is_erase)
+ return -EOPNOTSUPP;
+ /* During erasing, row_start differs from the default value. */
+ op->row_start = chip->page_shift;
+ op->cmd_reg = LOONGSON_NAND_CMD_ERASE;
+ break;
+ case NAND_CMD_SEQIN:
+ op->is_write = true;
+ break;
+ case NAND_CMD_PAGEPROG:
+ if (!op->is_write)
+ return -EOPNOTSUPP;
+ op->cmd_reg = LOONGSON_NAND_CMD_WRITE;
+ break;
+ case NAND_CMD_READ0:
+ op->is_read = true;
+ break;
+ case NAND_CMD_READSTART:
+ if (!op->is_read)
+ return -EOPNOTSUPP;
+ op->cmd_reg = LOONGSON_NAND_CMD_READ;
+ break;
+ case NAND_CMD_RNDOUT:
+ op->is_change_column = true;
+ break;
+ case NAND_CMD_RNDOUTSTART:
+ if (!op->is_change_column)
+ return -EOPNOTSUPP;
+ op->cmd_reg = LOONGSON_NAND_CMD_READ;
+ break;
+ default:
+ dev_dbg(host->dev, "unsupported opcode: %u\n", opcode);
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int loongson_nand_parse_instructions(struct nand_chip *chip, const struct nand_subop *subop,
+ struct loongson_nand_op *op)
+{
+ unsigned int op_id;
+ int ret;
+
+ for (op_id = 0; op_id < subop->ninstrs; op_id++) {
+ const struct nand_op_instr *instr = &subop->instrs[op_id];
+ unsigned int offset, naddrs;
+ const u8 *addrs;
+
+ switch (instr->type) {
+ case NAND_OP_CMD_INSTR:
+ ret = loongson_nand_op_cmd_mapping(chip, op, instr->ctx.cmd.opcode);
+ if (ret < 0)
+ return ret;
+
+ break;
+ case NAND_OP_ADDR_INSTR:
+ naddrs = nand_subop_get_num_addr_cyc(subop, op_id);
+ if (naddrs > LOONGSON_NAND_MAX_ADDR_CYC)
+ return -EOPNOTSUPP;
+ op->naddrs = naddrs;
+ offset = nand_subop_get_addr_start_off(subop, op_id);
+ addrs = &instr->ctx.addr.addrs[offset];
+ memcpy(op->addrs + op->addrs_offset, addrs, naddrs);
+ break;
+ case NAND_OP_DATA_IN_INSTR:
+ case NAND_OP_DATA_OUT_INSTR:
+ offset = nand_subop_get_data_start_off(subop, op_id);
+ op->orig_len = nand_subop_get_data_len(subop, op_id);
+ if (instr->type == NAND_OP_DATA_IN_INSTR)
+ op->buf = instr->ctx.data.buf.in + offset;
+ else if (instr->type == NAND_OP_DATA_OUT_INSTR)
+ op->buf = (void *)instr->ctx.data.buf.out + offset;
+
+ break;
+ case NAND_OP_WAITRDY_INSTR:
+ op->rdy_timeout_ms = instr->ctx.waitrdy.timeout_ms;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static void loongson_nand_set_addr_cs(struct loongson_nand_host *host)
+{
+ struct nand_chip *chip = &host->chip;
+ struct mtd_info *mtd = nand_to_mtd(chip);
+
+ if (!host->data->nand_cs)
+ return;
+
+ /*
+ * The Manufacturer/Chip ID read operation precedes attach_chip, at which point
+ * information such as NAND chip selection and capacity is unknown. As a
+ * workaround, we use 128MB cellsize (2KB pagesize) as a fallback.
+ */
+ if (!mtd->writesize)
+ host->addr_cs_field = GENMASK(17, 16);
+
+ regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR2, host->addr_cs_field,
+ host->data->nand_cs << __ffs(host->addr_cs_field));
+}
+
+static void ls1b_nand_set_addr(struct loongson_nand_host *host, struct loongson_nand_op *op)
+{
+ struct nand_chip *chip = &host->chip;
+ int i;
+
+ for (i = 0; i < LOONGSON_NAND_MAX_ADDR_CYC; i++) {
+ int shift, mask, val;
+
+ if (i < LOONGSON_NAND_COL_ADDR_CYC) {
+ shift = i * BITS_PER_BYTE;
+ mask = (u32)0xff << shift;
+ mask &= GENMASK(chip->page_shift, 0);
+ val = (u32)op->addrs[i] << shift;
+ regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR1, mask, val);
+ } else if (!op->is_change_column) {
+ shift = op->row_start + (i - LOONGSON_NAND_COL_ADDR_CYC) * BITS_PER_BYTE;
+ mask = (u32)0xff << shift;
+ val = (u32)op->addrs[i] << shift;
+ regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR1, mask, val);
+
+ if (i == 4) {
+ mask = (u32)0xff >> (BITS_PER_WORD - shift);
+ val = (u32)op->addrs[i] >> (BITS_PER_WORD - shift);
+ regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR2, mask, val);
+ }
+ }
+ }
+}
+
+static void ls1c_nand_set_addr(struct loongson_nand_host *host, struct loongson_nand_op *op)
+{
+ int i;
+
+ for (i = 0; i < LOONGSON_NAND_MAX_ADDR_CYC; i++) {
+ int shift, mask, val;
+
+ if (i < LOONGSON_NAND_COL_ADDR_CYC) {
+ shift = i * BITS_PER_BYTE;
+ mask = (u32)0xff << shift;
+ val = (u32)op->addrs[i] << shift;
+ regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR1, mask, val);
+ } else if (!op->is_change_column) {
+ shift = (i - LOONGSON_NAND_COL_ADDR_CYC) * BITS_PER_BYTE;
+ mask = (u32)0xff << shift;
+ val = (u32)op->addrs[i] << shift;
+ regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR2, mask, val);
+ }
+ }
+
+ loongson_nand_set_addr_cs(host);
+}
+
+static void loongson_nand_trigger_op(struct loongson_nand_host *host, struct loongson_nand_op *op)
+{
+ struct nand_chip *chip = &host->chip;
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ int col0 = op->addrs[0];
+ short col;
+
+ if (!IS_ALIGNED(col0, chip->buf_align)) {
+ col0 = ALIGN_DOWN(op->addrs[0], chip->buf_align);
+ op->aligned_offset = op->addrs[0] - col0;
+ op->addrs[0] = col0;
+ }
+
+ if (host->data->set_addr)
+ host->data->set_addr(host, op);
+
+ /* set operation length */
+ if (op->is_write || op->is_read || op->is_change_column)
+ op->len = ALIGN(op->orig_len + op->aligned_offset, chip->buf_align);
+ else if (op->is_erase)
+ op->len = 1;
+ else
+ op->len = op->orig_len;
+
+ writel(op->len, host->reg_base + LOONGSON_NAND_OP_NUM);
+
+ /* set operation area and scope */
+ col = op->addrs[1] << BITS_PER_BYTE | op->addrs[0];
+ if (op->orig_len && !op->is_readid) {
+ unsigned int op_scope = 0;
+
+ if (col < mtd->writesize) {
+ op->cmd_reg |= LOONGSON_NAND_CMD_OP_MAIN;
+ op_scope = mtd->writesize;
+ }
+
+ op->cmd_reg |= LOONGSON_NAND_CMD_OP_SPARE;
+ op_scope += mtd->oobsize;
+
+ op_scope <<= __ffs(host->data->op_scope_field);
+ regmap_update_bits(host->regmap, LOONGSON_NAND_PARAM,
+ host->data->op_scope_field, op_scope);
+ }
+
+ /* set command */
+ writel(op->cmd_reg, host->reg_base + LOONGSON_NAND_CMD);
+
+ /* trigger operation */
+ regmap_write_bits(host->regmap, LOONGSON_NAND_CMD, LOONGSON_NAND_CMD_VALID,
+ LOONGSON_NAND_CMD_VALID);
+}
+
+static int loongson_nand_wait_for_op_done(struct loongson_nand_host *host,
+ struct loongson_nand_op *op)
+{
+ unsigned int val;
+ int ret = 0;
+
+ if (op->rdy_timeout_ms) {
+ ret = regmap_read_poll_timeout(host->regmap, LOONGSON_NAND_CMD,
+ val, val & LOONGSON_NAND_CMD_OP_DONE,
+ 0, op->rdy_timeout_ms * MSEC_PER_SEC);
+ if (ret)
+ dev_err(host->dev, "operation failed\n");
+ }
+
+ return ret;
+}
+
+static void loongson_nand_dma_callback(void *data)
+{
+ struct loongson_nand_host *host = (struct loongson_nand_host *)data;
+ struct dma_chan *chan = host->dma_chan;
+ struct device *dev = chan->device->dev;
+ enum dma_status status;
+
+ status = dmaengine_tx_status(chan, host->dma_cookie, NULL);
+ if (likely(status == DMA_COMPLETE)) {
+ dev_dbg(dev, "DMA complete with cookie=%d\n", host->dma_cookie);
+ complete(&host->dma_complete);
+ } else {
+ dev_err(dev, "DMA error with cookie=%d\n", host->dma_cookie);
+ }
+}
+
+static int loongson_nand_dma_transfer(struct loongson_nand_host *host, struct loongson_nand_op *op)
+{
+ struct nand_chip *chip = &host->chip;
+ struct dma_chan *chan = host->dma_chan;
+ struct device *dev = chan->device->dev;
+ struct dma_async_tx_descriptor *desc;
+ enum dma_data_direction data_dir = op->is_write ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
+ enum dma_transfer_direction xfer_dir = op->is_write ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
+ void *buf = op->buf;
+ char *dma_buf = NULL;
+ dma_addr_t dma_addr;
+ int ret;
+
+ if (IS_ALIGNED((uintptr_t)buf, chip->buf_align) &&
+ IS_ALIGNED(op->orig_len, chip->buf_align)) {
+ dma_addr = dma_map_single(dev, buf, op->orig_len, data_dir);
+ if (dma_mapping_error(dev, dma_addr)) {
+ dev_err(dev, "failed to map DMA buffer\n");
+ return -ENXIO;
+ }
+ } else if (!op->is_write) {
+ dma_buf = dma_alloc_coherent(dev, op->len, &dma_addr, GFP_KERNEL);
+ if (!dma_buf)
+ return -ENOMEM;
+ } else {
+ dev_err(dev, "subpage writing not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ desc = dmaengine_prep_slave_single(chan, dma_addr, op->len, xfer_dir, DMA_PREP_INTERRUPT);
+ if (!desc) {
+ dev_err(dev, "failed to prepare DMA descriptor\n");
+ ret = -ENOMEM;
+ goto err;
+ }
+ desc->callback = loongson_nand_dma_callback;
+ desc->callback_param = host;
+
+ host->dma_cookie = dmaengine_submit(desc);
+ ret = dma_submit_error(host->dma_cookie);
+ if (ret) {
+ dev_err(dev, "failed to submit DMA descriptor\n");
+ goto err;
+ }
+
+ dev_dbg(dev, "issue DMA with cookie=%d\n", host->dma_cookie);
+ dma_async_issue_pending(chan);
+
+ if (!wait_for_completion_timeout(&host->dma_complete, msecs_to_jiffies(1000))) {
+ dmaengine_terminate_sync(chan);
+ reinit_completion(&host->dma_complete);
+ ret = -ETIMEDOUT;
+ goto err;
+ }
+
+ if (dma_buf)
+ memcpy(buf, dma_buf + op->aligned_offset, op->orig_len);
+err:
+ if (dma_buf)
+ dma_free_coherent(dev, op->len, dma_buf, dma_addr);
+ else
+ dma_unmap_single(dev, dma_addr, op->orig_len, data_dir);
+
+ return ret;
+}
+
+static int loongson_nand_data_type_exec(struct nand_chip *chip, const struct nand_subop *subop)
+{
+ struct loongson_nand_host *host = nand_get_controller_data(chip);
+ struct loongson_nand_op op = {};
+ int ret;
+
+ ret = loongson_nand_parse_instructions(chip, subop, &op);
+ if (ret)
+ return ret;
+
+ loongson_nand_trigger_op(host, &op);
+
+ ret = loongson_nand_dma_transfer(host, &op);
+ if (ret)
+ return ret;
+
+ return loongson_nand_wait_for_op_done(host, &op);
+}
+
+static int loongson_nand_misc_type_exec(struct nand_chip *chip, const struct nand_subop *subop,
+ struct loongson_nand_op *op)
+{
+ struct loongson_nand_host *host = nand_get_controller_data(chip);
+ int ret;
+
+ ret = loongson_nand_parse_instructions(chip, subop, op);
+ if (ret)
+ return ret;
+
+ loongson_nand_trigger_op(host, op);
+
+ return loongson_nand_wait_for_op_done(host, op);
+}
+
+static int loongson_nand_zerolen_type_exec(struct nand_chip *chip, const struct nand_subop *subop)
+{
+ struct loongson_nand_op op = {};
+
+ return loongson_nand_misc_type_exec(chip, subop, &op);
+}
+
+static int loongson_nand_read_id_type_exec(struct nand_chip *chip, const struct nand_subop *subop)
+{
+ struct loongson_nand_host *host = nand_get_controller_data(chip);
+ struct loongson_nand_op op = {};
+ int i, ret;
+ union {
+ char ids[6];
+ struct {
+ int idl;
+ u16 idh;
+ };
+ } nand_id;
+
+ ret = loongson_nand_misc_type_exec(chip, subop, &op);
+ if (ret)
+ return ret;
+
+ ret = regmap_read_poll_timeout(host->regmap, LOONGSON_NAND_IDL, nand_id.idl, nand_id.idl,
+ LOONGSON_NAND_READ_ID_SLEEP_US,
+ LOONGSON_NAND_READ_ID_TIMEOUT_US);
+ if (ret)
+ return ret;
+
+ nand_id.idh = readw(host->reg_base + LOONGSON_NAND_IDH_STATUS);
+
+ for (i = 0; i < min(host->data->max_id_cycle, op.orig_len); i++)
+ op.buf[i] = nand_id.ids[host->data->max_id_cycle - 1 - i];
+
+ return ret;
+}
+
+static int loongson_nand_read_status_type_exec(struct nand_chip *chip,
+ const struct nand_subop *subop)
+{
+ struct loongson_nand_host *host = nand_get_controller_data(chip);
+ struct loongson_nand_op op = {};
+ int val, ret;
+
+ ret = loongson_nand_misc_type_exec(chip, subop, &op);
+ if (ret)
+ return ret;
+
+ val = readl(host->reg_base + LOONGSON_NAND_IDH_STATUS);
+ val &= ~host->data->status_field;
+ op.buf[0] = val << ffs(host->data->status_field);
+
+ return ret;
+}
+
+static const struct nand_op_parser loongson_nand_op_parser = NAND_OP_PARSER(
+ NAND_OP_PARSER_PATTERN(
+ loongson_nand_read_id_type_exec,
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_ADDR_ELEM(false, LOONGSON_NAND_MAX_ADDR_CYC),
+ NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 8)),
+ NAND_OP_PARSER_PATTERN(
+ loongson_nand_read_status_type_exec,
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 1)),
+ NAND_OP_PARSER_PATTERN(
+ loongson_nand_zerolen_type_exec,
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)),
+ NAND_OP_PARSER_PATTERN(
+ loongson_nand_zerolen_type_exec,
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_ADDR_ELEM(false, LOONGSON_NAND_MAX_ADDR_CYC),
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)),
+ NAND_OP_PARSER_PATTERN(
+ loongson_nand_data_type_exec,
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_ADDR_ELEM(false, LOONGSON_NAND_MAX_ADDR_CYC),
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_WAITRDY_ELEM(true),
+ NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 0)),
+ NAND_OP_PARSER_PATTERN(
+ loongson_nand_data_type_exec,
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_ADDR_ELEM(false, LOONGSON_NAND_MAX_ADDR_CYC),
+ NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 0),
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)),
+ );
+
+static int loongson_nand_is_valid_cmd(u8 opcode)
+{
+ if (opcode == NAND_CMD_STATUS || opcode == NAND_CMD_RESET || opcode == NAND_CMD_READID)
+ return 0;
+
+ return -EOPNOTSUPP;
+}
+
+static int loongson_nand_is_valid_cmd_seq(u8 opcode1, u8 opcode2)
+{
+ if (opcode1 == NAND_CMD_RNDOUT && opcode2 == NAND_CMD_RNDOUTSTART)
+ return 0;
+
+ if (opcode1 == NAND_CMD_READ0 && opcode2 == NAND_CMD_READSTART)
+ return 0;
+
+ if (opcode1 == NAND_CMD_ERASE1 && opcode2 == NAND_CMD_ERASE2)
+ return 0;
+
+ if (opcode1 == NAND_CMD_SEQIN && opcode2 == NAND_CMD_PAGEPROG)
+ return 0;
+
+ return -EOPNOTSUPP;
+}
+
+static int loongson_nand_check_op(struct nand_chip *chip, const struct nand_operation *op)
+{
+ const struct nand_op_instr *instr1 = NULL, *instr2 = NULL;
+ int op_id;
+
+ for (op_id = 0; op_id < op->ninstrs; op_id++) {
+ const struct nand_op_instr *instr = &op->instrs[op_id];
+
+ if (instr->type == NAND_OP_CMD_INSTR) {
+ if (!instr1)
+ instr1 = instr;
+ else if (!instr2)
+ instr2 = instr;
+ else
+ break;
+ }
+ }
+
+ if (!instr1)
+ return -EOPNOTSUPP;
+
+ if (!instr2)
+ return loongson_nand_is_valid_cmd(instr1->ctx.cmd.opcode);
+
+ return loongson_nand_is_valid_cmd_seq(instr1->ctx.cmd.opcode, instr2->ctx.cmd.opcode);
+}
+
+static int loongson_nand_exec_op(struct nand_chip *chip, const struct nand_operation *op,
+ bool check_only)
+{
+ if (check_only)
+ return loongson_nand_check_op(chip, op);
+
+ return nand_op_parser_exec_op(chip, &loongson_nand_op_parser, op, check_only);
+}
+
+static int loongson_nand_get_chip_capacity(struct nand_chip *chip)
+{
+ struct loongson_nand_host *host = nand_get_controller_data(chip);
+ u64 chipsize = nanddev_target_size(&chip->base);
+ struct mtd_info *mtd = nand_to_mtd(chip);
+
+ switch (mtd->writesize) {
+ case SZ_512:
+ switch (chipsize) {
+ case SZ_8M:
+ host->addr_cs_field = GENMASK(15, 14);
+ return 0x9;
+ case SZ_16M:
+ host->addr_cs_field = GENMASK(16, 15);
+ return 0xa;
+ case SZ_32M:
+ host->addr_cs_field = GENMASK(17, 16);
+ return 0xb;
+ case SZ_64M:
+ host->addr_cs_field = GENMASK(18, 17);
+ return 0xc;
+ case SZ_128M:
+ host->addr_cs_field = GENMASK(19, 18);
+ return 0xd;
+ }
+ break;
+ case SZ_2K:
+ switch (chipsize) {
+ case SZ_128M:
+ host->addr_cs_field = GENMASK(17, 16);
+ return 0x0;
+ case SZ_256M:
+ host->addr_cs_field = GENMASK(18, 17);
+ return 0x1;
+ case SZ_512M:
+ host->addr_cs_field = GENMASK(19, 18);
+ return 0x2;
+ case SZ_1G:
+ host->addr_cs_field = GENMASK(20, 19);
+ return 0x3;
+ }
+ break;
+ case SZ_4K:
+ if (chipsize == SZ_2G) {
+ host->addr_cs_field = GENMASK(20, 19);
+ return 0x4;
+ }
+ break;
+ case SZ_8K:
+ switch (chipsize) {
+ case SZ_4G:
+ host->addr_cs_field = GENMASK(20, 19);
+ return 0x5;
+ case SZ_8G:
+ host->addr_cs_field = GENMASK(21, 20);
+ return 0x6;
+ case SZ_16G:
+ host->addr_cs_field = GENMASK(22, 21);
+ return 0x7;
+ }
+ break;
+ }
+
+ dev_err(host->dev, "Unsupported chip size: %llu MB with page size %u B\n",
+ chipsize, mtd->writesize);
+ return -EINVAL;
+}
+
+static int loongson_nand_attach_chip(struct nand_chip *chip)
+{
+ struct loongson_nand_host *host = nand_get_controller_data(chip);
+ int cell_size = loongson_nand_get_chip_capacity(chip);
+
+ if (cell_size < 0)
+ return cell_size;
+
+ switch (chip->ecc.engine_type) {
+ case NAND_ECC_ENGINE_TYPE_NONE:
+ break;
+ case NAND_ECC_ENGINE_TYPE_SOFT:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set cell size */
+ regmap_update_bits(host->regmap, LOONGSON_NAND_PARAM, LOONGSON_NAND_CELL_SIZE_MASK,
+ FIELD_PREP(LOONGSON_NAND_CELL_SIZE_MASK, cell_size));
+
+ regmap_update_bits(host->regmap, LOONGSON_NAND_TIMING, LOONGSON_NAND_HOLD_CYCLE_MASK,
+ FIELD_PREP(LOONGSON_NAND_HOLD_CYCLE_MASK, host->data->hold_cycle));
+
+ regmap_update_bits(host->regmap, LOONGSON_NAND_TIMING, LOONGSON_NAND_WAIT_CYCLE_MASK,
+ FIELD_PREP(LOONGSON_NAND_WAIT_CYCLE_MASK, host->data->wait_cycle));
+
+ chip->ecc.read_page_raw = nand_monolithic_read_page_raw;
+ chip->ecc.write_page_raw = nand_monolithic_write_page_raw;
+
+ return 0;
+}
+
+static const struct nand_controller_ops loongson_nand_controller_ops = {
+ .exec_op = loongson_nand_exec_op,
+ .attach_chip = loongson_nand_attach_chip,
+};
+
+static void loongson_nand_controller_cleanup(struct loongson_nand_host *host)
+{
+ if (host->dma_chan)
+ dma_release_channel(host->dma_chan);
+}
+
+static int ls2k1000_nand_apbdma_config(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ void __iomem *regs;
+ int val;
+
+ regs = devm_platform_ioremap_resource_byname(pdev, "dma-config");
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ val = readl(regs);
+ val |= FIELD_PREP(LS2K1000_NAND_DMA_MASK, LS2K1000_DMA0_CONF);
+ writel(val, regs);
+
+ return 0;
+}
+
+static int loongson_nand_controller_init(struct loongson_nand_host *host)
+{
+ struct device *dev = host->dev;
+ struct dma_chan *chan;
+ struct dma_slave_config cfg = {};
+ int ret, val;
+
+ host->regmap = devm_regmap_init_mmio(dev, host->reg_base, &loongson_nand_regmap_config);
+ if (IS_ERR(host->regmap))
+ return dev_err_probe(dev, PTR_ERR(host->regmap), "failed to init regmap\n");
+
+ if (host->data->id_cycle_field)
+ regmap_update_bits(host->regmap, LOONGSON_NAND_PARAM, host->data->id_cycle_field,
+ host->data->max_id_cycle << __ffs(host->data->id_cycle_field));
+
+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(host->data->dma_bits));
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to set DMA mask\n");
+
+ val = FIELD_PREP(LOONGSON_NAND_MAP_CS1_SEL, LOONGSON_NAND_CS_SEL1) |
+ FIELD_PREP(LOONGSON_NAND_MAP_RDY1_SEL, LOONGSON_NAND_CS_RDY1) |
+ FIELD_PREP(LOONGSON_NAND_MAP_CS2_SEL, LOONGSON_NAND_CS_SEL2) |
+ FIELD_PREP(LOONGSON_NAND_MAP_RDY2_SEL, LOONGSON_NAND_CS_RDY2) |
+ FIELD_PREP(LOONGSON_NAND_MAP_CS3_SEL, LOONGSON_NAND_CS_SEL3) |
+ FIELD_PREP(LOONGSON_NAND_MAP_RDY3_SEL, LOONGSON_NAND_CS_RDY3);
+
+ regmap_write(host->regmap, LOONGSON_NAND_CS_RDY_MAP, val);
+
+ if (host->data->dma_config) {
+ ret = host->data->dma_config(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to config DMA routing\n");
+ }
+
+ chan = dma_request_chan(dev, "rxtx");
+ if (IS_ERR(chan))
+ return dev_err_probe(dev, PTR_ERR(chan), "failed to request DMA channel\n");
+ host->dma_chan = chan;
+
+ cfg.src_addr = host->dma_base;
+ cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ cfg.dst_addr = host->dma_base;
+ cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ ret = dmaengine_slave_config(host->dma_chan, &cfg);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to config DMA channel\n");
+
+ init_completion(&host->dma_complete);
+
+ return 0;
+}
+
+static int loongson_nand_chip_init(struct loongson_nand_host *host)
+{
+ struct device *dev = host->dev;
+ int nchips = of_get_child_count(dev->of_node);
+ struct device_node *chip_np;
+ struct nand_chip *chip = &host->chip;
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ int ret;
+
+ if (nchips != 1)
+ return dev_err_probe(dev, -EINVAL, "Currently one NAND chip supported\n");
+
+ chip_np = of_get_next_child(dev->of_node, NULL);
+ if (!chip_np)
+ return dev_err_probe(dev, -ENODEV, "failed to get child node for NAND chip\n");
+
+ nand_set_flash_node(chip, chip_np);
+ of_node_put(chip_np);
+ if (!mtd->name)
+ return dev_err_probe(dev, -EINVAL, "Missing MTD label\n");
+
+ nand_set_controller_data(chip, host);
+ chip->controller = &host->controller;
+ chip->options = NAND_NO_SUBPAGE_WRITE | NAND_USES_DMA | NAND_BROKEN_XD;
+ chip->buf_align = 16;
+ mtd->dev.parent = dev;
+ mtd->owner = THIS_MODULE;
+
+ ret = nand_scan(chip, 1);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to scan NAND chip\n");
+
+ ret = mtd_device_register(mtd, NULL, 0);
+ if (ret) {
+ nand_cleanup(chip);
+ return dev_err_probe(dev, ret, "failed to register MTD device\n");
+ }
+
+ return 0;
+}
+
+static int loongson_nand_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct loongson_nand_data *data;
+ struct loongson_nand_host *host;
+ struct resource *res;
+ int ret;
+
+ data = of_device_get_match_data(dev);
+ if (!data)
+ return -ENODEV;
+
+ host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+
+ host->reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(host->reg_base))
+ return PTR_ERR(host->reg_base);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand-dma");
+ if (!res)
+ return dev_err_probe(dev, -EINVAL, "Missing 'nand-dma' in reg-names property\n");
+
+ host->dma_base = dma_map_resource(dev, res->start, resource_size(res),
+ DMA_BIDIRECTIONAL, 0);
+ if (dma_mapping_error(dev, host->dma_base))
+ return -ENXIO;
+
+ host->dev = dev;
+ host->data = data;
+ host->controller.ops = &loongson_nand_controller_ops;
+
+ nand_controller_init(&host->controller);
+
+ ret = loongson_nand_controller_init(host);
+ if (ret)
+ goto err;
+
+ ret = loongson_nand_chip_init(host);
+ if (ret)
+ goto err;
+
+ platform_set_drvdata(pdev, host);
+
+ return 0;
+err:
+ loongson_nand_controller_cleanup(host);
+
+ return ret;
+}
+
+static void loongson_nand_remove(struct platform_device *pdev)
+{
+ struct loongson_nand_host *host = platform_get_drvdata(pdev);
+ struct nand_chip *chip = &host->chip;
+ int ret;
+
+ ret = mtd_device_unregister(nand_to_mtd(chip));
+ WARN_ON(ret);
+ nand_cleanup(chip);
+ loongson_nand_controller_cleanup(host);
+}
+
+static const struct loongson_nand_data ls1b_nand_data = {
+ .max_id_cycle = 5,
+ .status_field = GENMASK(15, 8),
+ .hold_cycle = 0x2,
+ .wait_cycle = 0xc,
+ .dma_bits = 32,
+ .set_addr = ls1b_nand_set_addr,
+};
+
+static const struct loongson_nand_data ls1c_nand_data = {
+ .max_id_cycle = 6,
+ .id_cycle_field = GENMASK(14, 12),
+ .status_field = GENMASK(23, 16),
+ .op_scope_field = GENMASK(29, 16),
+ .hold_cycle = 0x2,
+ .wait_cycle = 0xc,
+ .dma_bits = 32,
+ .set_addr = ls1c_nand_set_addr,
+};
+
+static const struct loongson_nand_data ls2k0500_nand_data = {
+ .max_id_cycle = 6,
+ .id_cycle_field = GENMASK(14, 12),
+ .status_field = GENMASK(23, 16),
+ .op_scope_field = GENMASK(29, 16),
+ .hold_cycle = 0x4,
+ .wait_cycle = 0x12,
+ .dma_bits = 64,
+ .set_addr = ls1c_nand_set_addr,
+};
+
+static const struct loongson_nand_data ls2k1000_nand_data = {
+ .max_id_cycle = 6,
+ .id_cycle_field = GENMASK(14, 12),
+ .status_field = GENMASK(23, 16),
+ .op_scope_field = GENMASK(29, 16),
+ .hold_cycle = 0x4,
+ .wait_cycle = 0x12,
+ .nand_cs = 0x2,
+ .dma_bits = 64,
+ .dma_config = ls2k1000_nand_apbdma_config,
+ .set_addr = ls1c_nand_set_addr,
+};
+
+static const struct of_device_id loongson_nand_match[] = {
+ {
+ .compatible = "loongson,ls1b-nand-controller",
+ .data = &ls1b_nand_data,
+ },
+ {
+ .compatible = "loongson,ls1c-nand-controller",
+ .data = &ls1c_nand_data,
+ },
+ {
+ .compatible = "loongson,ls2k0500-nand-controller",
+ .data = &ls2k0500_nand_data,
+ },
+ {
+ .compatible = "loongson,ls2k1000-nand-controller",
+ .data = &ls2k1000_nand_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, loongson_nand_match);
+
+static struct platform_driver loongson_nand_driver = {
+ .probe = loongson_nand_probe,
+ .remove = loongson_nand_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = loongson_nand_match,
+ },
+};
+
+module_platform_driver(loongson_nand_driver);
+
+MODULE_AUTHOR("Keguang Zhang <keguang.zhang@gmail.com>");
+MODULE_AUTHOR("Binbin Zhou <zhoubinbin@loongson.cn>");
+MODULE_DESCRIPTION("Loongson NAND Controller Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/nand/raw/loongson1-nand-controller.c b/drivers/mtd/nand/raw/loongson1-nand-controller.c
deleted file mode 100644
index ef8e4f9ce287..000000000000
--- a/drivers/mtd/nand/raw/loongson1-nand-controller.c
+++ /dev/null
@@ -1,836 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * NAND Controller Driver for Loongson-1 SoC
- *
- * Copyright (C) 2015-2025 Keguang Zhang <keguang.zhang@gmail.com>
- */
-
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/dmaengine.h>
-#include <linux/dma-mapping.h>
-#include <linux/iopoll.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/rawnand.h>
-#include <linux/of.h>
-#include <linux/platform_device.h>
-#include <linux/regmap.h>
-#include <linux/sizes.h>
-
-/* Loongson-1 NAND Controller Registers */
-#define LS1X_NAND_CMD 0x0
-#define LS1X_NAND_ADDR1 0x4
-#define LS1X_NAND_ADDR2 0x8
-#define LS1X_NAND_TIMING 0xc
-#define LS1X_NAND_IDL 0x10
-#define LS1X_NAND_IDH_STATUS 0x14
-#define LS1X_NAND_PARAM 0x18
-#define LS1X_NAND_OP_NUM 0x1c
-
-/* NAND Command Register Bits */
-#define LS1X_NAND_CMD_OP_DONE BIT(10)
-#define LS1X_NAND_CMD_OP_SPARE BIT(9)
-#define LS1X_NAND_CMD_OP_MAIN BIT(8)
-#define LS1X_NAND_CMD_STATUS BIT(7)
-#define LS1X_NAND_CMD_RESET BIT(6)
-#define LS1X_NAND_CMD_READID BIT(5)
-#define LS1X_NAND_CMD_BLOCKS_ERASE BIT(4)
-#define LS1X_NAND_CMD_ERASE BIT(3)
-#define LS1X_NAND_CMD_WRITE BIT(2)
-#define LS1X_NAND_CMD_READ BIT(1)
-#define LS1X_NAND_CMD_VALID BIT(0)
-
-#define LS1X_NAND_WAIT_CYCLE_MASK GENMASK(7, 0)
-#define LS1X_NAND_HOLD_CYCLE_MASK GENMASK(15, 8)
-#define LS1X_NAND_CELL_SIZE_MASK GENMASK(11, 8)
-
-#define LS1X_NAND_COL_ADDR_CYC 2U
-#define LS1X_NAND_MAX_ADDR_CYC 5U
-
-#define BITS_PER_WORD (4 * BITS_PER_BYTE)
-
-struct ls1x_nand_host;
-
-struct ls1x_nand_op {
- char addrs[LS1X_NAND_MAX_ADDR_CYC];
- unsigned int naddrs;
- unsigned int addrs_offset;
- unsigned int aligned_offset;
- unsigned int cmd_reg;
- unsigned int row_start;
- unsigned int rdy_timeout_ms;
- unsigned int orig_len;
- bool is_readid;
- bool is_erase;
- bool is_write;
- bool is_read;
- bool is_change_column;
- size_t len;
- char *buf;
-};
-
-struct ls1x_nand_data {
- unsigned int status_field;
- unsigned int op_scope_field;
- unsigned int hold_cycle;
- unsigned int wait_cycle;
- void (*set_addr)(struct ls1x_nand_host *host, struct ls1x_nand_op *op);
-};
-
-struct ls1x_nand_host {
- struct device *dev;
- struct nand_chip chip;
- struct nand_controller controller;
- const struct ls1x_nand_data *data;
- void __iomem *reg_base;
- struct regmap *regmap;
- /* DMA Engine stuff */
- dma_addr_t dma_base;
- struct dma_chan *dma_chan;
- dma_cookie_t dma_cookie;
- struct completion dma_complete;
-};
-
-static const struct regmap_config ls1x_nand_regmap_config = {
- .reg_bits = 32,
- .val_bits = 32,
- .reg_stride = 4,
-};
-
-static int ls1x_nand_op_cmd_mapping(struct nand_chip *chip, struct ls1x_nand_op *op, u8 opcode)
-{
- struct ls1x_nand_host *host = nand_get_controller_data(chip);
-
- op->row_start = chip->page_shift + 1;
-
- /* The controller abstracts the following NAND operations. */
- switch (opcode) {
- case NAND_CMD_STATUS:
- op->cmd_reg = LS1X_NAND_CMD_STATUS;
- break;
- case NAND_CMD_RESET:
- op->cmd_reg = LS1X_NAND_CMD_RESET;
- break;
- case NAND_CMD_READID:
- op->is_readid = true;
- op->cmd_reg = LS1X_NAND_CMD_READID;
- break;
- case NAND_CMD_ERASE1:
- op->is_erase = true;
- op->addrs_offset = LS1X_NAND_COL_ADDR_CYC;
- break;
- case NAND_CMD_ERASE2:
- if (!op->is_erase)
- return -EOPNOTSUPP;
- /* During erasing, row_start differs from the default value. */
- op->row_start = chip->page_shift;
- op->cmd_reg = LS1X_NAND_CMD_ERASE;
- break;
- case NAND_CMD_SEQIN:
- op->is_write = true;
- break;
- case NAND_CMD_PAGEPROG:
- if (!op->is_write)
- return -EOPNOTSUPP;
- op->cmd_reg = LS1X_NAND_CMD_WRITE;
- break;
- case NAND_CMD_READ0:
- op->is_read = true;
- break;
- case NAND_CMD_READSTART:
- if (!op->is_read)
- return -EOPNOTSUPP;
- op->cmd_reg = LS1X_NAND_CMD_READ;
- break;
- case NAND_CMD_RNDOUT:
- op->is_change_column = true;
- break;
- case NAND_CMD_RNDOUTSTART:
- if (!op->is_change_column)
- return -EOPNOTSUPP;
- op->cmd_reg = LS1X_NAND_CMD_READ;
- break;
- default:
- dev_dbg(host->dev, "unsupported opcode: %u\n", opcode);
- return -EOPNOTSUPP;
- }
-
- return 0;
-}
-
-static int ls1x_nand_parse_instructions(struct nand_chip *chip,
- const struct nand_subop *subop, struct ls1x_nand_op *op)
-{
- unsigned int op_id;
- int ret;
-
- for (op_id = 0; op_id < subop->ninstrs; op_id++) {
- const struct nand_op_instr *instr = &subop->instrs[op_id];
- unsigned int offset, naddrs;
- const u8 *addrs;
-
- switch (instr->type) {
- case NAND_OP_CMD_INSTR:
- ret = ls1x_nand_op_cmd_mapping(chip, op, instr->ctx.cmd.opcode);
- if (ret < 0)
- return ret;
-
- break;
- case NAND_OP_ADDR_INSTR:
- naddrs = nand_subop_get_num_addr_cyc(subop, op_id);
- if (naddrs > LS1X_NAND_MAX_ADDR_CYC)
- return -EOPNOTSUPP;
- op->naddrs = naddrs;
- offset = nand_subop_get_addr_start_off(subop, op_id);
- addrs = &instr->ctx.addr.addrs[offset];
- memcpy(op->addrs + op->addrs_offset, addrs, naddrs);
- break;
- case NAND_OP_DATA_IN_INSTR:
- case NAND_OP_DATA_OUT_INSTR:
- offset = nand_subop_get_data_start_off(subop, op_id);
- op->orig_len = nand_subop_get_data_len(subop, op_id);
- if (instr->type == NAND_OP_DATA_IN_INSTR)
- op->buf = instr->ctx.data.buf.in + offset;
- else if (instr->type == NAND_OP_DATA_OUT_INSTR)
- op->buf = (void *)instr->ctx.data.buf.out + offset;
-
- break;
- case NAND_OP_WAITRDY_INSTR:
- op->rdy_timeout_ms = instr->ctx.waitrdy.timeout_ms;
- break;
- default:
- break;
- }
- }
-
- return 0;
-}
-
-static void ls1b_nand_set_addr(struct ls1x_nand_host *host, struct ls1x_nand_op *op)
-{
- struct nand_chip *chip = &host->chip;
- int i;
-
- for (i = 0; i < LS1X_NAND_MAX_ADDR_CYC; i++) {
- int shift, mask, val;
-
- if (i < LS1X_NAND_COL_ADDR_CYC) {
- shift = i * BITS_PER_BYTE;
- mask = (u32)0xff << shift;
- mask &= GENMASK(chip->page_shift, 0);
- val = (u32)op->addrs[i] << shift;
- regmap_update_bits(host->regmap, LS1X_NAND_ADDR1, mask, val);
- } else if (!op->is_change_column) {
- shift = op->row_start + (i - LS1X_NAND_COL_ADDR_CYC) * BITS_PER_BYTE;
- mask = (u32)0xff << shift;
- val = (u32)op->addrs[i] << shift;
- regmap_update_bits(host->regmap, LS1X_NAND_ADDR1, mask, val);
-
- if (i == 4) {
- mask = (u32)0xff >> (BITS_PER_WORD - shift);
- val = (u32)op->addrs[i] >> (BITS_PER_WORD - shift);
- regmap_update_bits(host->regmap, LS1X_NAND_ADDR2, mask, val);
- }
- }
- }
-}
-
-static void ls1c_nand_set_addr(struct ls1x_nand_host *host, struct ls1x_nand_op *op)
-{
- int i;
-
- for (i = 0; i < LS1X_NAND_MAX_ADDR_CYC; i++) {
- int shift, mask, val;
-
- if (i < LS1X_NAND_COL_ADDR_CYC) {
- shift = i * BITS_PER_BYTE;
- mask = (u32)0xff << shift;
- val = (u32)op->addrs[i] << shift;
- regmap_update_bits(host->regmap, LS1X_NAND_ADDR1, mask, val);
- } else if (!op->is_change_column) {
- shift = (i - LS1X_NAND_COL_ADDR_CYC) * BITS_PER_BYTE;
- mask = (u32)0xff << shift;
- val = (u32)op->addrs[i] << shift;
- regmap_update_bits(host->regmap, LS1X_NAND_ADDR2, mask, val);
- }
- }
-}
-
-static void ls1x_nand_trigger_op(struct ls1x_nand_host *host, struct ls1x_nand_op *op)
-{
- struct nand_chip *chip = &host->chip;
- struct mtd_info *mtd = nand_to_mtd(chip);
- int col0 = op->addrs[0];
- short col;
-
- if (!IS_ALIGNED(col0, chip->buf_align)) {
- col0 = ALIGN_DOWN(op->addrs[0], chip->buf_align);
- op->aligned_offset = op->addrs[0] - col0;
- op->addrs[0] = col0;
- }
-
- if (host->data->set_addr)
- host->data->set_addr(host, op);
-
- /* set operation length */
- if (op->is_write || op->is_read || op->is_change_column)
- op->len = ALIGN(op->orig_len + op->aligned_offset, chip->buf_align);
- else if (op->is_erase)
- op->len = 1;
- else
- op->len = op->orig_len;
-
- writel(op->len, host->reg_base + LS1X_NAND_OP_NUM);
-
- /* set operation area and scope */
- col = op->addrs[1] << BITS_PER_BYTE | op->addrs[0];
- if (op->orig_len && !op->is_readid) {
- unsigned int op_scope = 0;
-
- if (col < mtd->writesize) {
- op->cmd_reg |= LS1X_NAND_CMD_OP_MAIN;
- op_scope = mtd->writesize;
- }
-
- op->cmd_reg |= LS1X_NAND_CMD_OP_SPARE;
- op_scope += mtd->oobsize;
-
- op_scope <<= __ffs(host->data->op_scope_field);
- regmap_update_bits(host->regmap, LS1X_NAND_PARAM,
- host->data->op_scope_field, op_scope);
- }
-
- /* set command */
- writel(op->cmd_reg, host->reg_base + LS1X_NAND_CMD);
-
- /* trigger operation */
- regmap_write_bits(host->regmap, LS1X_NAND_CMD, LS1X_NAND_CMD_VALID, LS1X_NAND_CMD_VALID);
-}
-
-static int ls1x_nand_wait_for_op_done(struct ls1x_nand_host *host, struct ls1x_nand_op *op)
-{
- unsigned int val;
- int ret = 0;
-
- if (op->rdy_timeout_ms) {
- ret = regmap_read_poll_timeout(host->regmap, LS1X_NAND_CMD,
- val, val & LS1X_NAND_CMD_OP_DONE,
- 0, op->rdy_timeout_ms * MSEC_PER_SEC);
- if (ret)
- dev_err(host->dev, "operation failed\n");
- }
-
- return ret;
-}
-
-static void ls1x_nand_dma_callback(void *data)
-{
- struct ls1x_nand_host *host = (struct ls1x_nand_host *)data;
- struct dma_chan *chan = host->dma_chan;
- struct device *dev = chan->device->dev;
- enum dma_status status;
-
- status = dmaengine_tx_status(chan, host->dma_cookie, NULL);
- if (likely(status == DMA_COMPLETE)) {
- dev_dbg(dev, "DMA complete with cookie=%d\n", host->dma_cookie);
- complete(&host->dma_complete);
- } else {
- dev_err(dev, "DMA error with cookie=%d\n", host->dma_cookie);
- }
-}
-
-static int ls1x_nand_dma_transfer(struct ls1x_nand_host *host, struct ls1x_nand_op *op)
-{
- struct nand_chip *chip = &host->chip;
- struct dma_chan *chan = host->dma_chan;
- struct device *dev = chan->device->dev;
- struct dma_async_tx_descriptor *desc;
- enum dma_data_direction data_dir = op->is_write ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
- enum dma_transfer_direction xfer_dir = op->is_write ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
- void *buf = op->buf;
- char *dma_buf = NULL;
- dma_addr_t dma_addr;
- int ret;
-
- if (IS_ALIGNED((uintptr_t)buf, chip->buf_align) &&
- IS_ALIGNED(op->orig_len, chip->buf_align)) {
- dma_addr = dma_map_single(dev, buf, op->orig_len, data_dir);
- if (dma_mapping_error(dev, dma_addr)) {
- dev_err(dev, "failed to map DMA buffer\n");
- return -ENXIO;
- }
- } else if (!op->is_write) {
- dma_buf = dma_alloc_coherent(dev, op->len, &dma_addr, GFP_KERNEL);
- if (!dma_buf)
- return -ENOMEM;
- } else {
- dev_err(dev, "subpage writing not supported\n");
- return -EOPNOTSUPP;
- }
-
- desc = dmaengine_prep_slave_single(chan, dma_addr, op->len, xfer_dir, DMA_PREP_INTERRUPT);
- if (!desc) {
- dev_err(dev, "failed to prepare DMA descriptor\n");
- ret = -ENOMEM;
- goto err;
- }
- desc->callback = ls1x_nand_dma_callback;
- desc->callback_param = host;
-
- host->dma_cookie = dmaengine_submit(desc);
- ret = dma_submit_error(host->dma_cookie);
- if (ret) {
- dev_err(dev, "failed to submit DMA descriptor\n");
- goto err;
- }
-
- dev_dbg(dev, "issue DMA with cookie=%d\n", host->dma_cookie);
- dma_async_issue_pending(chan);
-
- if (!wait_for_completion_timeout(&host->dma_complete, msecs_to_jiffies(1000))) {
- dmaengine_terminate_sync(chan);
- reinit_completion(&host->dma_complete);
- ret = -ETIMEDOUT;
- goto err;
- }
-
- if (dma_buf)
- memcpy(buf, dma_buf + op->aligned_offset, op->orig_len);
-err:
- if (dma_buf)
- dma_free_coherent(dev, op->len, dma_buf, dma_addr);
- else
- dma_unmap_single(dev, dma_addr, op->orig_len, data_dir);
-
- return ret;
-}
-
-static int ls1x_nand_data_type_exec(struct nand_chip *chip, const struct nand_subop *subop)
-{
- struct ls1x_nand_host *host = nand_get_controller_data(chip);
- struct ls1x_nand_op op = {};
- int ret;
-
- ret = ls1x_nand_parse_instructions(chip, subop, &op);
- if (ret)
- return ret;
-
- ls1x_nand_trigger_op(host, &op);
-
- ret = ls1x_nand_dma_transfer(host, &op);
- if (ret)
- return ret;
-
- return ls1x_nand_wait_for_op_done(host, &op);
-}
-
-static int ls1x_nand_misc_type_exec(struct nand_chip *chip,
- const struct nand_subop *subop, struct ls1x_nand_op *op)
-{
- struct ls1x_nand_host *host = nand_get_controller_data(chip);
- int ret;
-
- ret = ls1x_nand_parse_instructions(chip, subop, op);
- if (ret)
- return ret;
-
- ls1x_nand_trigger_op(host, op);
-
- return ls1x_nand_wait_for_op_done(host, op);
-}
-
-static int ls1x_nand_zerolen_type_exec(struct nand_chip *chip, const struct nand_subop *subop)
-{
- struct ls1x_nand_op op = {};
-
- return ls1x_nand_misc_type_exec(chip, subop, &op);
-}
-
-static int ls1x_nand_read_id_type_exec(struct nand_chip *chip, const struct nand_subop *subop)
-{
- struct ls1x_nand_host *host = nand_get_controller_data(chip);
- struct ls1x_nand_op op = {};
- int i, ret;
- union {
- char ids[5];
- struct {
- int idl;
- char idh;
- };
- } nand_id;
-
- ret = ls1x_nand_misc_type_exec(chip, subop, &op);
- if (ret)
- return ret;
-
- nand_id.idl = readl(host->reg_base + LS1X_NAND_IDL);
- nand_id.idh = readb(host->reg_base + LS1X_NAND_IDH_STATUS);
-
- for (i = 0; i < min(sizeof(nand_id.ids), op.orig_len); i++)
- op.buf[i] = nand_id.ids[sizeof(nand_id.ids) - 1 - i];
-
- return ret;
-}
-
-static int ls1x_nand_read_status_type_exec(struct nand_chip *chip, const struct nand_subop *subop)
-{
- struct ls1x_nand_host *host = nand_get_controller_data(chip);
- struct ls1x_nand_op op = {};
- int val, ret;
-
- ret = ls1x_nand_misc_type_exec(chip, subop, &op);
- if (ret)
- return ret;
-
- val = readl(host->reg_base + LS1X_NAND_IDH_STATUS);
- val &= ~host->data->status_field;
- op.buf[0] = val << ffs(host->data->status_field);
-
- return ret;
-}
-
-static const struct nand_op_parser ls1x_nand_op_parser = NAND_OP_PARSER(
- NAND_OP_PARSER_PATTERN(
- ls1x_nand_read_id_type_exec,
- NAND_OP_PARSER_PAT_CMD_ELEM(false),
- NAND_OP_PARSER_PAT_ADDR_ELEM(false, LS1X_NAND_MAX_ADDR_CYC),
- NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 8)),
- NAND_OP_PARSER_PATTERN(
- ls1x_nand_read_status_type_exec,
- NAND_OP_PARSER_PAT_CMD_ELEM(false),
- NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 1)),
- NAND_OP_PARSER_PATTERN(
- ls1x_nand_zerolen_type_exec,
- NAND_OP_PARSER_PAT_CMD_ELEM(false),
- NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)),
- NAND_OP_PARSER_PATTERN(
- ls1x_nand_zerolen_type_exec,
- NAND_OP_PARSER_PAT_CMD_ELEM(false),
- NAND_OP_PARSER_PAT_ADDR_ELEM(false, LS1X_NAND_MAX_ADDR_CYC),
- NAND_OP_PARSER_PAT_CMD_ELEM(false),
- NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)),
- NAND_OP_PARSER_PATTERN(
- ls1x_nand_data_type_exec,
- NAND_OP_PARSER_PAT_CMD_ELEM(false),
- NAND_OP_PARSER_PAT_ADDR_ELEM(false, LS1X_NAND_MAX_ADDR_CYC),
- NAND_OP_PARSER_PAT_CMD_ELEM(false),
- NAND_OP_PARSER_PAT_WAITRDY_ELEM(true),
- NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 0)),
- NAND_OP_PARSER_PATTERN(
- ls1x_nand_data_type_exec,
- NAND_OP_PARSER_PAT_CMD_ELEM(false),
- NAND_OP_PARSER_PAT_ADDR_ELEM(false, LS1X_NAND_MAX_ADDR_CYC),
- NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 0),
- NAND_OP_PARSER_PAT_CMD_ELEM(false),
- NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)),
- );
-
-static int ls1x_nand_is_valid_cmd(u8 opcode)
-{
- if (opcode == NAND_CMD_STATUS || opcode == NAND_CMD_RESET || opcode == NAND_CMD_READID)
- return 0;
-
- return -EOPNOTSUPP;
-}
-
-static int ls1x_nand_is_valid_cmd_seq(u8 opcode1, u8 opcode2)
-{
- if (opcode1 == NAND_CMD_RNDOUT && opcode2 == NAND_CMD_RNDOUTSTART)
- return 0;
-
- if (opcode1 == NAND_CMD_READ0 && opcode2 == NAND_CMD_READSTART)
- return 0;
-
- if (opcode1 == NAND_CMD_ERASE1 && opcode2 == NAND_CMD_ERASE2)
- return 0;
-
- if (opcode1 == NAND_CMD_SEQIN && opcode2 == NAND_CMD_PAGEPROG)
- return 0;
-
- return -EOPNOTSUPP;
-}
-
-static int ls1x_nand_check_op(struct nand_chip *chip, const struct nand_operation *op)
-{
- const struct nand_op_instr *instr1 = NULL, *instr2 = NULL;
- int op_id;
-
- for (op_id = 0; op_id < op->ninstrs; op_id++) {
- const struct nand_op_instr *instr = &op->instrs[op_id];
-
- if (instr->type == NAND_OP_CMD_INSTR) {
- if (!instr1)
- instr1 = instr;
- else if (!instr2)
- instr2 = instr;
- else
- break;
- }
- }
-
- if (!instr1)
- return -EOPNOTSUPP;
-
- if (!instr2)
- return ls1x_nand_is_valid_cmd(instr1->ctx.cmd.opcode);
-
- return ls1x_nand_is_valid_cmd_seq(instr1->ctx.cmd.opcode, instr2->ctx.cmd.opcode);
-}
-
-static int ls1x_nand_exec_op(struct nand_chip *chip,
- const struct nand_operation *op, bool check_only)
-{
- if (check_only)
- return ls1x_nand_check_op(chip, op);
-
- return nand_op_parser_exec_op(chip, &ls1x_nand_op_parser, op, check_only);
-}
-
-static int ls1x_nand_attach_chip(struct nand_chip *chip)
-{
- struct ls1x_nand_host *host = nand_get_controller_data(chip);
- u64 chipsize = nanddev_target_size(&chip->base);
- int cell_size = 0;
-
- switch (chipsize) {
- case SZ_128M:
- cell_size = 0x0;
- break;
- case SZ_256M:
- cell_size = 0x1;
- break;
- case SZ_512M:
- cell_size = 0x2;
- break;
- case SZ_1G:
- cell_size = 0x3;
- break;
- case SZ_2G:
- cell_size = 0x4;
- break;
- case SZ_4G:
- cell_size = 0x5;
- break;
- case SZ_8G:
- cell_size = 0x6;
- break;
- case SZ_16G:
- cell_size = 0x7;
- break;
- default:
- dev_err(host->dev, "unsupported chip size: %llu MB\n", chipsize);
- return -EINVAL;
- }
-
- switch (chip->ecc.engine_type) {
- case NAND_ECC_ENGINE_TYPE_NONE:
- break;
- case NAND_ECC_ENGINE_TYPE_SOFT:
- break;
- default:
- return -EINVAL;
- }
-
- /* set cell size */
- regmap_update_bits(host->regmap, LS1X_NAND_PARAM, LS1X_NAND_CELL_SIZE_MASK,
- FIELD_PREP(LS1X_NAND_CELL_SIZE_MASK, cell_size));
-
- regmap_update_bits(host->regmap, LS1X_NAND_TIMING, LS1X_NAND_HOLD_CYCLE_MASK,
- FIELD_PREP(LS1X_NAND_HOLD_CYCLE_MASK, host->data->hold_cycle));
-
- regmap_update_bits(host->regmap, LS1X_NAND_TIMING, LS1X_NAND_WAIT_CYCLE_MASK,
- FIELD_PREP(LS1X_NAND_WAIT_CYCLE_MASK, host->data->wait_cycle));
-
- chip->ecc.read_page_raw = nand_monolithic_read_page_raw;
- chip->ecc.write_page_raw = nand_monolithic_write_page_raw;
-
- return 0;
-}
-
-static const struct nand_controller_ops ls1x_nand_controller_ops = {
- .exec_op = ls1x_nand_exec_op,
- .attach_chip = ls1x_nand_attach_chip,
-};
-
-static void ls1x_nand_controller_cleanup(struct ls1x_nand_host *host)
-{
- if (host->dma_chan)
- dma_release_channel(host->dma_chan);
-}
-
-static int ls1x_nand_controller_init(struct ls1x_nand_host *host)
-{
- struct device *dev = host->dev;
- struct dma_chan *chan;
- struct dma_slave_config cfg = {};
- int ret;
-
- host->regmap = devm_regmap_init_mmio(dev, host->reg_base, &ls1x_nand_regmap_config);
- if (IS_ERR(host->regmap))
- return dev_err_probe(dev, PTR_ERR(host->regmap), "failed to init regmap\n");
-
- chan = dma_request_chan(dev, "rxtx");
- if (IS_ERR(chan))
- return dev_err_probe(dev, PTR_ERR(chan), "failed to request DMA channel\n");
- host->dma_chan = chan;
-
- cfg.src_addr = host->dma_base;
- cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
- cfg.dst_addr = host->dma_base;
- cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
- ret = dmaengine_slave_config(host->dma_chan, &cfg);
- if (ret)
- return dev_err_probe(dev, ret, "failed to config DMA channel\n");
-
- init_completion(&host->dma_complete);
-
- return 0;
-}
-
-static int ls1x_nand_chip_init(struct ls1x_nand_host *host)
-{
- struct device *dev = host->dev;
- int nchips = of_get_child_count(dev->of_node);
- struct device_node *chip_np;
- struct nand_chip *chip = &host->chip;
- struct mtd_info *mtd = nand_to_mtd(chip);
- int ret;
-
- if (nchips != 1)
- return dev_err_probe(dev, -EINVAL, "Currently one NAND chip supported\n");
-
- chip_np = of_get_next_child(dev->of_node, NULL);
- if (!chip_np)
- return dev_err_probe(dev, -ENODEV, "failed to get child node for NAND chip\n");
-
- nand_set_flash_node(chip, chip_np);
- of_node_put(chip_np);
- if (!mtd->name)
- return dev_err_probe(dev, -EINVAL, "Missing MTD label\n");
-
- nand_set_controller_data(chip, host);
- chip->controller = &host->controller;
- chip->options = NAND_NO_SUBPAGE_WRITE | NAND_USES_DMA | NAND_BROKEN_XD;
- chip->buf_align = 16;
- mtd->dev.parent = dev;
- mtd->owner = THIS_MODULE;
-
- ret = nand_scan(chip, 1);
- if (ret)
- return dev_err_probe(dev, ret, "failed to scan NAND chip\n");
-
- ret = mtd_device_register(mtd, NULL, 0);
- if (ret) {
- nand_cleanup(chip);
- return dev_err_probe(dev, ret, "failed to register MTD device\n");
- }
-
- return 0;
-}
-
-static int ls1x_nand_probe(struct platform_device *pdev)
-{
- struct device *dev = &pdev->dev;
- const struct ls1x_nand_data *data;
- struct ls1x_nand_host *host;
- struct resource *res;
- int ret;
-
- data = of_device_get_match_data(dev);
- if (!data)
- return -ENODEV;
-
- host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
- if (!host)
- return -ENOMEM;
-
- host->reg_base = devm_platform_ioremap_resource(pdev, 0);
- if (IS_ERR(host->reg_base))
- return PTR_ERR(host->reg_base);
-
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand-dma");
- if (!res)
- return dev_err_probe(dev, -EINVAL, "Missing 'nand-dma' in reg-names property\n");
-
- host->dma_base = dma_map_resource(dev, res->start, resource_size(res),
- DMA_BIDIRECTIONAL, 0);
- if (dma_mapping_error(dev, host->dma_base))
- return -ENXIO;
-
- host->dev = dev;
- host->data = data;
- host->controller.ops = &ls1x_nand_controller_ops;
-
- nand_controller_init(&host->controller);
-
- ret = ls1x_nand_controller_init(host);
- if (ret)
- goto err;
-
- ret = ls1x_nand_chip_init(host);
- if (ret)
- goto err;
-
- platform_set_drvdata(pdev, host);
-
- return 0;
-err:
- ls1x_nand_controller_cleanup(host);
-
- return ret;
-}
-
-static void ls1x_nand_remove(struct platform_device *pdev)
-{
- struct ls1x_nand_host *host = platform_get_drvdata(pdev);
- struct nand_chip *chip = &host->chip;
- int ret;
-
- ret = mtd_device_unregister(nand_to_mtd(chip));
- WARN_ON(ret);
- nand_cleanup(chip);
- ls1x_nand_controller_cleanup(host);
-}
-
-static const struct ls1x_nand_data ls1b_nand_data = {
- .status_field = GENMASK(15, 8),
- .hold_cycle = 0x2,
- .wait_cycle = 0xc,
- .set_addr = ls1b_nand_set_addr,
-};
-
-static const struct ls1x_nand_data ls1c_nand_data = {
- .status_field = GENMASK(23, 16),
- .op_scope_field = GENMASK(29, 16),
- .hold_cycle = 0x2,
- .wait_cycle = 0xc,
- .set_addr = ls1c_nand_set_addr,
-};
-
-static const struct of_device_id ls1x_nand_match[] = {
- {
- .compatible = "loongson,ls1b-nand-controller",
- .data = &ls1b_nand_data,
- },
- {
- .compatible = "loongson,ls1c-nand-controller",
- .data = &ls1c_nand_data,
- },
- { /* sentinel */ }
-};
-MODULE_DEVICE_TABLE(of, ls1x_nand_match);
-
-static struct platform_driver ls1x_nand_driver = {
- .probe = ls1x_nand_probe,
- .remove = ls1x_nand_remove,
- .driver = {
- .name = KBUILD_MODNAME,
- .of_match_table = ls1x_nand_match,
- },
-};
-
-module_platform_driver(ls1x_nand_driver);
-
-MODULE_AUTHOR("Keguang Zhang <keguang.zhang@gmail.com>");
-MODULE_DESCRIPTION("Loongson-1 NAND Controller Driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c
index 13e4060bd1b6..c7d9501f646b 100644
--- a/drivers/mtd/nand/raw/nand_base.c
+++ b/drivers/mtd/nand/raw/nand_base.c
@@ -2784,137 +2784,6 @@ int nand_set_features(struct nand_chip *chip, int addr,
}
/**
- * nand_check_erased_buf - check if a buffer contains (almost) only 0xff data
- * @buf: buffer to test
- * @len: buffer length
- * @bitflips_threshold: maximum number of bitflips
- *
- * Check if a buffer contains only 0xff, which means the underlying region
- * has been erased and is ready to be programmed.
- * The bitflips_threshold specify the maximum number of bitflips before
- * considering the region is not erased.
- * Note: The logic of this function has been extracted from the memweight
- * implementation, except that nand_check_erased_buf function exit before
- * testing the whole buffer if the number of bitflips exceed the
- * bitflips_threshold value.
- *
- * Returns a positive number of bitflips less than or equal to
- * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
- * threshold.
- */
-static int nand_check_erased_buf(void *buf, int len, int bitflips_threshold)
-{
- const unsigned char *bitmap = buf;
- int bitflips = 0;
- int weight;
-
- for (; len && ((uintptr_t)bitmap) % sizeof(long);
- len--, bitmap++) {
- weight = hweight8(*bitmap);
- bitflips += BITS_PER_BYTE - weight;
- if (unlikely(bitflips > bitflips_threshold))
- return -EBADMSG;
- }
-
- for (; len >= sizeof(long);
- len -= sizeof(long), bitmap += sizeof(long)) {
- unsigned long d = *((unsigned long *)bitmap);
- if (d == ~0UL)
- continue;
- weight = hweight_long(d);
- bitflips += BITS_PER_LONG - weight;
- if (unlikely(bitflips > bitflips_threshold))
- return -EBADMSG;
- }
-
- for (; len > 0; len--, bitmap++) {
- weight = hweight8(*bitmap);
- bitflips += BITS_PER_BYTE - weight;
- if (unlikely(bitflips > bitflips_threshold))
- return -EBADMSG;
- }
-
- return bitflips;
-}
-
-/**
- * nand_check_erased_ecc_chunk - check if an ECC chunk contains (almost) only
- * 0xff data
- * @data: data buffer to test
- * @datalen: data length
- * @ecc: ECC buffer
- * @ecclen: ECC length
- * @extraoob: extra OOB buffer
- * @extraooblen: extra OOB length
- * @bitflips_threshold: maximum number of bitflips
- *
- * Check if a data buffer and its associated ECC and OOB data contains only
- * 0xff pattern, which means the underlying region has been erased and is
- * ready to be programmed.
- * The bitflips_threshold specify the maximum number of bitflips before
- * considering the region as not erased.
- *
- * Note:
- * 1/ ECC algorithms are working on pre-defined block sizes which are usually
- * different from the NAND page size. When fixing bitflips, ECC engines will
- * report the number of errors per chunk, and the NAND core infrastructure
- * expect you to return the maximum number of bitflips for the whole page.
- * This is why you should always use this function on a single chunk and
- * not on the whole page. After checking each chunk you should update your
- * max_bitflips value accordingly.
- * 2/ When checking for bitflips in erased pages you should not only check
- * the payload data but also their associated ECC data, because a user might
- * have programmed almost all bits to 1 but a few. In this case, we
- * shouldn't consider the chunk as erased, and checking ECC bytes prevent
- * this case.
- * 3/ The extraoob argument is optional, and should be used if some of your OOB
- * data are protected by the ECC engine.
- * It could also be used if you support subpages and want to attach some
- * extra OOB data to an ECC chunk.
- *
- * Returns a positive number of bitflips less than or equal to
- * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
- * threshold. In case of success, the passed buffers are filled with 0xff.
- */
-int nand_check_erased_ecc_chunk(void *data, int datalen,
- void *ecc, int ecclen,
- void *extraoob, int extraooblen,
- int bitflips_threshold)
-{
- int data_bitflips = 0, ecc_bitflips = 0, extraoob_bitflips = 0;
-
- data_bitflips = nand_check_erased_buf(data, datalen,
- bitflips_threshold);
- if (data_bitflips < 0)
- return data_bitflips;
-
- bitflips_threshold -= data_bitflips;
-
- ecc_bitflips = nand_check_erased_buf(ecc, ecclen, bitflips_threshold);
- if (ecc_bitflips < 0)
- return ecc_bitflips;
-
- bitflips_threshold -= ecc_bitflips;
-
- extraoob_bitflips = nand_check_erased_buf(extraoob, extraooblen,
- bitflips_threshold);
- if (extraoob_bitflips < 0)
- return extraoob_bitflips;
-
- if (data_bitflips)
- memset(data, 0xff, datalen);
-
- if (ecc_bitflips)
- memset(ecc, 0xff, ecclen);
-
- if (extraoob_bitflips)
- memset(extraoob, 0xff, extraooblen);
-
- return data_bitflips + ecc_bitflips + extraoob_bitflips;
-}
-EXPORT_SYMBOL(nand_check_erased_ecc_chunk);
-
-/**
* nand_read_page_raw_notsupp - dummy read raw page function
* @chip: nand chip info structure
* @buf: buffer to store read data
diff --git a/drivers/mtd/nand/raw/nandsim.c b/drivers/mtd/nand/raw/nandsim.c
index df48b7d01d16..84942e7e528f 100644
--- a/drivers/mtd/nand/raw/nandsim.c
+++ b/drivers/mtd/nand/raw/nandsim.c
@@ -552,9 +552,8 @@ static int __init ns_alloc_device(struct nandsim *ns)
err = -EINVAL;
goto err_close_filp;
}
- ns->pages_written =
- vzalloc(array_size(sizeof(unsigned long),
- BITS_TO_LONGS(ns->geom.pgnum)));
+ ns->pages_written = vcalloc(BITS_TO_LONGS(ns->geom.pgnum),
+ sizeof(unsigned long));
if (!ns->pages_written) {
NS_ERR("alloc_device: unable to allocate pages written array\n");
err = -ENOMEM;
@@ -578,7 +577,7 @@ err_close_filp:
return err;
}
- ns->pages = vmalloc(array_size(sizeof(union ns_mem), ns->geom.pgnum));
+ ns->pages = vmalloc_array(ns->geom.pgnum, sizeof(union ns_mem));
if (!ns->pages) {
NS_ERR("alloc_device: unable to allocate page array\n");
return -ENOMEM;
diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c
index b8af3a3533fc..39e297486721 100644
--- a/drivers/mtd/nand/raw/omap2.c
+++ b/drivers/mtd/nand/raw/omap2.c
@@ -1979,7 +1979,7 @@ static int omap_nand_attach_chip(struct nand_chip *chip)
err = rawnand_sw_bch_init(chip);
if (err) {
dev_err(dev, "Unable to use BCH library\n");
- return err;
+ goto err_put_elm_dev;
}
break;
@@ -2016,7 +2016,7 @@ static int omap_nand_attach_chip(struct nand_chip *chip)
err = rawnand_sw_bch_init(chip);
if (err) {
dev_err(dev, "unable to use BCH library\n");
- return err;
+ goto err_put_elm_dev;
}
break;
@@ -2054,7 +2054,8 @@ static int omap_nand_attach_chip(struct nand_chip *chip)
break;
default:
dev_err(dev, "Invalid or unsupported ECC scheme\n");
- return -EINVAL;
+ err = -EINVAL;
+ goto err_put_elm_dev;
}
if (elm_bch_strength >= 0) {
@@ -2073,7 +2074,7 @@ static int omap_nand_attach_chip(struct nand_chip *chip)
info->nsteps_per_eccpg, chip->ecc.size,
chip->ecc.bytes);
if (err < 0)
- return err;
+ goto err_put_elm_dev;
}
/* Check if NAND device's OOB is enough to store ECC signatures */
@@ -2083,10 +2084,24 @@ static int omap_nand_attach_chip(struct nand_chip *chip)
dev_err(dev,
"Not enough OOB bytes: required = %d, available=%d\n",
min_oobbytes, mtd->oobsize);
- return -EINVAL;
+ err = -EINVAL;
+ goto err_put_elm_dev;
}
return 0;
+
+err_put_elm_dev:
+ put_device(info->elm_dev);
+
+ return err;
+}
+
+static void omap_nand_detach_chip(struct nand_chip *chip)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct omap_nand_info *info = mtd_to_omap(mtd);
+
+ put_device(info->elm_dev);
}
static void omap_nand_data_in(struct nand_chip *chip, void *buf,
@@ -2187,6 +2202,7 @@ static int omap_nand_exec_op(struct nand_chip *chip,
static const struct nand_controller_ops omap_nand_controller_ops = {
.attach_chip = omap_nand_attach_chip,
+ .detach_chip = omap_nand_detach_chip,
.exec_op = omap_nand_exec_op,
};
@@ -2316,6 +2332,5 @@ static struct platform_driver omap_nand_driver = {
module_platform_driver(omap_nand_driver);
-MODULE_ALIAS("platform:" DRIVER_NAME);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Glue layer for NAND flash on TI OMAP boards");
diff --git a/drivers/mtd/nand/raw/pl35x-nand-controller.c b/drivers/mtd/nand/raw/pl35x-nand-controller.c
index 09440ed4652e..11bd90e3f18c 100644
--- a/drivers/mtd/nand/raw/pl35x-nand-controller.c
+++ b/drivers/mtd/nand/raw/pl35x-nand-controller.c
@@ -1137,7 +1137,7 @@ static int pl35x_nand_probe(struct platform_device *pdev)
struct device *smc_dev = pdev->dev.parent;
struct amba_device *smc_amba = to_amba_device(smc_dev);
struct pl35x_nandc *nfc;
- u32 ret;
+ int ret;
nfc = devm_kzalloc(&pdev->dev, sizeof(*nfc), GFP_KERNEL);
if (!nfc)
@@ -1193,6 +1193,5 @@ static struct platform_driver pl35x_nandc_driver = {
module_platform_driver(pl35x_nandc_driver);
MODULE_AUTHOR("Xilinx, Inc.");
-MODULE_ALIAS("platform:" PL35X_NANDC_DRIVER_NAME);
MODULE_DESCRIPTION("ARM PL35X NAND controller driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/nand/raw/rockchip-nand-controller.c b/drivers/mtd/nand/raw/rockchip-nand-controller.c
index c5d7cd8a6cab..9444ba02696d 100644
--- a/drivers/mtd/nand/raw/rockchip-nand-controller.c
+++ b/drivers/mtd/nand/raw/rockchip-nand-controller.c
@@ -1505,4 +1505,3 @@ module_platform_driver(rk_nfc_driver);
MODULE_LICENSE("Dual MIT/GPL");
MODULE_AUTHOR("Yifeng Zhao <yifeng.zhao@rock-chips.com>");
MODULE_DESCRIPTION("Rockchip Nand Flash Controller Driver");
-MODULE_ALIAS("platform:rockchip-nand-controller");
diff --git a/drivers/mtd/nand/raw/s3c2410.c b/drivers/mtd/nand/raw/s3c2410.c
deleted file mode 100644
index 229f2e87d56e..000000000000
--- a/drivers/mtd/nand/raw/s3c2410.c
+++ /dev/null
@@ -1,1230 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * Copyright © 2004-2008 Simtec Electronics
- * http://armlinux.simtec.co.uk/
- * Ben Dooks <ben@simtec.co.uk>
- *
- * Samsung S3C2410/S3C2440/S3C2412 NAND driver
-*/
-
-#define pr_fmt(fmt) "nand-s3c2410: " fmt
-
-#ifdef CONFIG_MTD_NAND_S3C2410_DEBUG
-#define DEBUG
-#endif
-
-#include <linux/module.h>
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/string.h>
-#include <linux/io.h>
-#include <linux/ioport.h>
-#include <linux/platform_device.h>
-#include <linux/delay.h>
-#include <linux/err.h>
-#include <linux/slab.h>
-#include <linux/clk.h>
-#include <linux/cpufreq.h>
-#include <linux/of.h>
-
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/rawnand.h>
-#include <linux/mtd/partitions.h>
-
-#include <linux/platform_data/mtd-nand-s3c2410.h>
-
-#define S3C2410_NFREG(x) (x)
-
-#define S3C2410_NFCONF S3C2410_NFREG(0x00)
-#define S3C2410_NFCMD S3C2410_NFREG(0x04)
-#define S3C2410_NFADDR S3C2410_NFREG(0x08)
-#define S3C2410_NFDATA S3C2410_NFREG(0x0C)
-#define S3C2410_NFSTAT S3C2410_NFREG(0x10)
-#define S3C2410_NFECC S3C2410_NFREG(0x14)
-#define S3C2440_NFCONT S3C2410_NFREG(0x04)
-#define S3C2440_NFCMD S3C2410_NFREG(0x08)
-#define S3C2440_NFADDR S3C2410_NFREG(0x0C)
-#define S3C2440_NFDATA S3C2410_NFREG(0x10)
-#define S3C2440_NFSTAT S3C2410_NFREG(0x20)
-#define S3C2440_NFMECC0 S3C2410_NFREG(0x2C)
-#define S3C2412_NFSTAT S3C2410_NFREG(0x28)
-#define S3C2412_NFMECC0 S3C2410_NFREG(0x34)
-#define S3C2410_NFCONF_EN (1<<15)
-#define S3C2410_NFCONF_INITECC (1<<12)
-#define S3C2410_NFCONF_nFCE (1<<11)
-#define S3C2410_NFCONF_TACLS(x) ((x)<<8)
-#define S3C2410_NFCONF_TWRPH0(x) ((x)<<4)
-#define S3C2410_NFCONF_TWRPH1(x) ((x)<<0)
-#define S3C2410_NFSTAT_BUSY (1<<0)
-#define S3C2440_NFCONF_TACLS(x) ((x)<<12)
-#define S3C2440_NFCONF_TWRPH0(x) ((x)<<8)
-#define S3C2440_NFCONF_TWRPH1(x) ((x)<<4)
-#define S3C2440_NFCONT_INITECC (1<<4)
-#define S3C2440_NFCONT_nFCE (1<<1)
-#define S3C2440_NFCONT_ENABLE (1<<0)
-#define S3C2440_NFSTAT_READY (1<<0)
-#define S3C2412_NFCONF_NANDBOOT (1<<31)
-#define S3C2412_NFCONT_INIT_MAIN_ECC (1<<5)
-#define S3C2412_NFCONT_nFCE0 (1<<1)
-#define S3C2412_NFSTAT_READY (1<<0)
-
-/* new oob placement block for use with hardware ecc generation
- */
-static int s3c2410_ooblayout_ecc(struct mtd_info *mtd, int section,
- struct mtd_oob_region *oobregion)
-{
- if (section)
- return -ERANGE;
-
- oobregion->offset = 0;
- oobregion->length = 3;
-
- return 0;
-}
-
-static int s3c2410_ooblayout_free(struct mtd_info *mtd, int section,
- struct mtd_oob_region *oobregion)
-{
- if (section)
- return -ERANGE;
-
- oobregion->offset = 8;
- oobregion->length = 8;
-
- return 0;
-}
-
-static const struct mtd_ooblayout_ops s3c2410_ooblayout_ops = {
- .ecc = s3c2410_ooblayout_ecc,
- .free = s3c2410_ooblayout_free,
-};
-
-/* controller and mtd information */
-
-struct s3c2410_nand_info;
-
-/**
- * struct s3c2410_nand_mtd - driver MTD structure
- * @chip: The NAND chip information.
- * @set: The platform information supplied for this set of NAND chips.
- * @info: Link back to the hardware information.
-*/
-struct s3c2410_nand_mtd {
- struct nand_chip chip;
- struct s3c2410_nand_set *set;
- struct s3c2410_nand_info *info;
-};
-
-enum s3c_cpu_type {
- TYPE_S3C2410,
- TYPE_S3C2412,
- TYPE_S3C2440,
-};
-
-enum s3c_nand_clk_state {
- CLOCK_DISABLE = 0,
- CLOCK_ENABLE,
- CLOCK_SUSPEND,
-};
-
-/* overview of the s3c2410 nand state */
-
-/**
- * struct s3c2410_nand_info - NAND controller state.
- * @controller: Base controller structure.
- * @mtds: An array of MTD instances on this controller.
- * @platform: The platform data for this board.
- * @device: The platform device we bound to.
- * @clk: The clock resource for this controller.
- * @regs: The area mapped for the hardware registers.
- * @sel_reg: Pointer to the register controlling the NAND selection.
- * @sel_bit: The bit in @sel_reg to select the NAND chip.
- * @mtd_count: The number of MTDs created from this controller.
- * @save_sel: The contents of @sel_reg to be saved over suspend.
- * @clk_rate: The clock rate from @clk.
- * @clk_state: The current clock state.
- * @cpu_type: The exact type of this controller.
- */
-struct s3c2410_nand_info {
- /* mtd info */
- struct nand_controller controller;
- struct s3c2410_nand_mtd *mtds;
- struct s3c2410_platform_nand *platform;
-
- /* device info */
- struct device *device;
- struct clk *clk;
- void __iomem *regs;
- void __iomem *sel_reg;
- int sel_bit;
- int mtd_count;
- unsigned long save_sel;
- unsigned long clk_rate;
- enum s3c_nand_clk_state clk_state;
-
- enum s3c_cpu_type cpu_type;
-};
-
-struct s3c24XX_nand_devtype_data {
- enum s3c_cpu_type type;
-};
-
-static const struct s3c24XX_nand_devtype_data s3c2410_nand_devtype_data = {
- .type = TYPE_S3C2410,
-};
-
-static const struct s3c24XX_nand_devtype_data s3c2412_nand_devtype_data = {
- .type = TYPE_S3C2412,
-};
-
-static const struct s3c24XX_nand_devtype_data s3c2440_nand_devtype_data = {
- .type = TYPE_S3C2440,
-};
-
-/* conversion functions */
-
-static struct s3c2410_nand_mtd *s3c2410_nand_mtd_toours(struct mtd_info *mtd)
-{
- return container_of(mtd_to_nand(mtd), struct s3c2410_nand_mtd,
- chip);
-}
-
-static struct s3c2410_nand_info *s3c2410_nand_mtd_toinfo(struct mtd_info *mtd)
-{
- return s3c2410_nand_mtd_toours(mtd)->info;
-}
-
-static struct s3c2410_nand_info *to_nand_info(struct platform_device *dev)
-{
- return platform_get_drvdata(dev);
-}
-
-static struct s3c2410_platform_nand *to_nand_plat(struct platform_device *dev)
-{
- return dev_get_platdata(&dev->dev);
-}
-
-static inline int allow_clk_suspend(struct s3c2410_nand_info *info)
-{
-#ifdef CONFIG_MTD_NAND_S3C2410_CLKSTOP
- return 1;
-#else
- return 0;
-#endif
-}
-
-/**
- * s3c2410_nand_clk_set_state - Enable, disable or suspend NAND clock.
- * @info: The controller instance.
- * @new_state: State to which clock should be set.
- */
-static void s3c2410_nand_clk_set_state(struct s3c2410_nand_info *info,
- enum s3c_nand_clk_state new_state)
-{
- if (!allow_clk_suspend(info) && new_state == CLOCK_SUSPEND)
- return;
-
- if (info->clk_state == CLOCK_ENABLE) {
- if (new_state != CLOCK_ENABLE)
- clk_disable_unprepare(info->clk);
- } else {
- if (new_state == CLOCK_ENABLE)
- clk_prepare_enable(info->clk);
- }
-
- info->clk_state = new_state;
-}
-
-/* timing calculations */
-
-#define NS_IN_KHZ 1000000
-
-/**
- * s3c_nand_calc_rate - calculate timing data.
- * @wanted: The cycle time in nanoseconds.
- * @clk: The clock rate in kHz.
- * @max: The maximum divider value.
- *
- * Calculate the timing value from the given parameters.
- */
-static int s3c_nand_calc_rate(int wanted, unsigned long clk, int max)
-{
- int result;
-
- result = DIV_ROUND_UP((wanted * clk), NS_IN_KHZ);
-
- pr_debug("result %d from %ld, %d\n", result, clk, wanted);
-
- if (result > max) {
- pr_err("%d ns is too big for current clock rate %ld\n",
- wanted, clk);
- return -1;
- }
-
- if (result < 1)
- result = 1;
-
- return result;
-}
-
-#define to_ns(ticks, clk) (((ticks) * NS_IN_KHZ) / (unsigned int)(clk))
-
-/* controller setup */
-
-/**
- * s3c2410_nand_setrate - setup controller timing information.
- * @info: The controller instance.
- *
- * Given the information supplied by the platform, calculate and set
- * the necessary timing registers in the hardware to generate the
- * necessary timing cycles to the hardware.
- */
-static int s3c2410_nand_setrate(struct s3c2410_nand_info *info)
-{
- struct s3c2410_platform_nand *plat = info->platform;
- int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4;
- int tacls, twrph0, twrph1;
- unsigned long clkrate = clk_get_rate(info->clk);
- unsigned long set, cfg, mask;
- unsigned long flags;
-
- /* calculate the timing information for the controller */
-
- info->clk_rate = clkrate;
- clkrate /= 1000; /* turn clock into kHz for ease of use */
-
- if (plat != NULL) {
- tacls = s3c_nand_calc_rate(plat->tacls, clkrate, tacls_max);
- twrph0 = s3c_nand_calc_rate(plat->twrph0, clkrate, 8);
- twrph1 = s3c_nand_calc_rate(plat->twrph1, clkrate, 8);
- } else {
- /* default timings */
- tacls = tacls_max;
- twrph0 = 8;
- twrph1 = 8;
- }
-
- if (tacls < 0 || twrph0 < 0 || twrph1 < 0) {
- dev_err(info->device, "cannot get suitable timings\n");
- return -EINVAL;
- }
-
- dev_info(info->device, "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns\n",
- tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate),
- twrph1, to_ns(twrph1, clkrate));
-
- switch (info->cpu_type) {
- case TYPE_S3C2410:
- mask = (S3C2410_NFCONF_TACLS(3) |
- S3C2410_NFCONF_TWRPH0(7) |
- S3C2410_NFCONF_TWRPH1(7));
- set = S3C2410_NFCONF_EN;
- set |= S3C2410_NFCONF_TACLS(tacls - 1);
- set |= S3C2410_NFCONF_TWRPH0(twrph0 - 1);
- set |= S3C2410_NFCONF_TWRPH1(twrph1 - 1);
- break;
-
- case TYPE_S3C2440:
- case TYPE_S3C2412:
- mask = (S3C2440_NFCONF_TACLS(tacls_max - 1) |
- S3C2440_NFCONF_TWRPH0(7) |
- S3C2440_NFCONF_TWRPH1(7));
-
- set = S3C2440_NFCONF_TACLS(tacls - 1);
- set |= S3C2440_NFCONF_TWRPH0(twrph0 - 1);
- set |= S3C2440_NFCONF_TWRPH1(twrph1 - 1);
- break;
-
- default:
- BUG();
- }
-
- local_irq_save(flags);
-
- cfg = readl(info->regs + S3C2410_NFCONF);
- cfg &= ~mask;
- cfg |= set;
- writel(cfg, info->regs + S3C2410_NFCONF);
-
- local_irq_restore(flags);
-
- dev_dbg(info->device, "NF_CONF is 0x%lx\n", cfg);
-
- return 0;
-}
-
-/**
- * s3c2410_nand_inithw - basic hardware initialisation
- * @info: The hardware state.
- *
- * Do the basic initialisation of the hardware, using s3c2410_nand_setrate()
- * to setup the hardware access speeds and set the controller to be enabled.
-*/
-static int s3c2410_nand_inithw(struct s3c2410_nand_info *info)
-{
- int ret;
-
- ret = s3c2410_nand_setrate(info);
- if (ret < 0)
- return ret;
-
- switch (info->cpu_type) {
- case TYPE_S3C2410:
- default:
- break;
-
- case TYPE_S3C2440:
- case TYPE_S3C2412:
- /* enable the controller and de-assert nFCE */
-
- writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT);
- }
-
- return 0;
-}
-
-/**
- * s3c2410_nand_select_chip - select the given nand chip
- * @this: NAND chip object.
- * @chip: The chip number.
- *
- * This is called by the MTD layer to either select a given chip for the
- * @mtd instance, or to indicate that the access has finished and the
- * chip can be de-selected.
- *
- * The routine ensures that the nFCE line is correctly setup, and any
- * platform specific selection code is called to route nFCE to the specific
- * chip.
- */
-static void s3c2410_nand_select_chip(struct nand_chip *this, int chip)
-{
- struct s3c2410_nand_info *info;
- struct s3c2410_nand_mtd *nmtd;
- unsigned long cur;
-
- nmtd = nand_get_controller_data(this);
- info = nmtd->info;
-
- if (chip != -1)
- s3c2410_nand_clk_set_state(info, CLOCK_ENABLE);
-
- cur = readl(info->sel_reg);
-
- if (chip == -1) {
- cur |= info->sel_bit;
- } else {
- if (nmtd->set != NULL && chip > nmtd->set->nr_chips) {
- dev_err(info->device, "invalid chip %d\n", chip);
- return;
- }
-
- if (info->platform != NULL) {
- if (info->platform->select_chip != NULL)
- (info->platform->select_chip) (nmtd->set, chip);
- }
-
- cur &= ~info->sel_bit;
- }
-
- writel(cur, info->sel_reg);
-
- if (chip == -1)
- s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND);
-}
-
-/* s3c2410_nand_hwcontrol
- *
- * Issue command and address cycles to the chip
-*/
-
-static void s3c2410_nand_hwcontrol(struct nand_chip *chip, int cmd,
- unsigned int ctrl)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-
- if (cmd == NAND_CMD_NONE)
- return;
-
- if (ctrl & NAND_CLE)
- writeb(cmd, info->regs + S3C2410_NFCMD);
- else
- writeb(cmd, info->regs + S3C2410_NFADDR);
-}
-
-/* command and control functions */
-
-static void s3c2440_nand_hwcontrol(struct nand_chip *chip, int cmd,
- unsigned int ctrl)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-
- if (cmd == NAND_CMD_NONE)
- return;
-
- if (ctrl & NAND_CLE)
- writeb(cmd, info->regs + S3C2440_NFCMD);
- else
- writeb(cmd, info->regs + S3C2440_NFADDR);
-}
-
-/* s3c2410_nand_devready()
- *
- * returns 0 if the nand is busy, 1 if it is ready
-*/
-
-static int s3c2410_nand_devready(struct nand_chip *chip)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
- return readb(info->regs + S3C2410_NFSTAT) & S3C2410_NFSTAT_BUSY;
-}
-
-static int s3c2440_nand_devready(struct nand_chip *chip)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
- return readb(info->regs + S3C2440_NFSTAT) & S3C2440_NFSTAT_READY;
-}
-
-static int s3c2412_nand_devready(struct nand_chip *chip)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
- return readb(info->regs + S3C2412_NFSTAT) & S3C2412_NFSTAT_READY;
-}
-
-/* ECC handling functions */
-
-static int s3c2410_nand_correct_data(struct nand_chip *chip, u_char *dat,
- u_char *read_ecc, u_char *calc_ecc)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
- unsigned int diff0, diff1, diff2;
- unsigned int bit, byte;
-
- pr_debug("%s(%p,%p,%p,%p)\n", __func__, mtd, dat, read_ecc, calc_ecc);
-
- diff0 = read_ecc[0] ^ calc_ecc[0];
- diff1 = read_ecc[1] ^ calc_ecc[1];
- diff2 = read_ecc[2] ^ calc_ecc[2];
-
- pr_debug("%s: rd %*phN calc %*phN diff %02x%02x%02x\n",
- __func__, 3, read_ecc, 3, calc_ecc,
- diff0, diff1, diff2);
-
- if (diff0 == 0 && diff1 == 0 && diff2 == 0)
- return 0; /* ECC is ok */
-
- /* sometimes people do not think about using the ECC, so check
- * to see if we have an 0xff,0xff,0xff read ECC and then ignore
- * the error, on the assumption that this is an un-eccd page.
- */
- if (read_ecc[0] == 0xff && read_ecc[1] == 0xff && read_ecc[2] == 0xff
- && info->platform->ignore_unset_ecc)
- return 0;
-
- /* Can we correct this ECC (ie, one row and column change).
- * Note, this is similar to the 256 error code on smartmedia */
-
- if (((diff0 ^ (diff0 >> 1)) & 0x55) == 0x55 &&
- ((diff1 ^ (diff1 >> 1)) & 0x55) == 0x55 &&
- ((diff2 ^ (diff2 >> 1)) & 0x55) == 0x55) {
- /* calculate the bit position of the error */
-
- bit = ((diff2 >> 3) & 1) |
- ((diff2 >> 4) & 2) |
- ((diff2 >> 5) & 4);
-
- /* calculate the byte position of the error */
-
- byte = ((diff2 << 7) & 0x100) |
- ((diff1 << 0) & 0x80) |
- ((diff1 << 1) & 0x40) |
- ((diff1 << 2) & 0x20) |
- ((diff1 << 3) & 0x10) |
- ((diff0 >> 4) & 0x08) |
- ((diff0 >> 3) & 0x04) |
- ((diff0 >> 2) & 0x02) |
- ((diff0 >> 1) & 0x01);
-
- dev_dbg(info->device, "correcting error bit %d, byte %d\n",
- bit, byte);
-
- dat[byte] ^= (1 << bit);
- return 1;
- }
-
- /* if there is only one bit difference in the ECC, then
- * one of only a row or column parity has changed, which
- * means the error is most probably in the ECC itself */
-
- diff0 |= (diff1 << 8);
- diff0 |= (diff2 << 16);
-
- /* equal to "(diff0 & ~(1 << __ffs(diff0)))" */
- if ((diff0 & (diff0 - 1)) == 0)
- return 1;
-
- return -1;
-}
-
-/* ECC functions
- *
- * These allow the s3c2410 and s3c2440 to use the controller's ECC
- * generator block to ECC the data as it passes through]
-*/
-
-static void s3c2410_nand_enable_hwecc(struct nand_chip *chip, int mode)
-{
- struct s3c2410_nand_info *info;
- unsigned long ctrl;
-
- info = s3c2410_nand_mtd_toinfo(nand_to_mtd(chip));
- ctrl = readl(info->regs + S3C2410_NFCONF);
- ctrl |= S3C2410_NFCONF_INITECC;
- writel(ctrl, info->regs + S3C2410_NFCONF);
-}
-
-static void s3c2412_nand_enable_hwecc(struct nand_chip *chip, int mode)
-{
- struct s3c2410_nand_info *info;
- unsigned long ctrl;
-
- info = s3c2410_nand_mtd_toinfo(nand_to_mtd(chip));
- ctrl = readl(info->regs + S3C2440_NFCONT);
- writel(ctrl | S3C2412_NFCONT_INIT_MAIN_ECC,
- info->regs + S3C2440_NFCONT);
-}
-
-static void s3c2440_nand_enable_hwecc(struct nand_chip *chip, int mode)
-{
- struct s3c2410_nand_info *info;
- unsigned long ctrl;
-
- info = s3c2410_nand_mtd_toinfo(nand_to_mtd(chip));
- ctrl = readl(info->regs + S3C2440_NFCONT);
- writel(ctrl | S3C2440_NFCONT_INITECC, info->regs + S3C2440_NFCONT);
-}
-
-static int s3c2410_nand_calculate_ecc(struct nand_chip *chip,
- const u_char *dat, u_char *ecc_code)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-
- ecc_code[0] = readb(info->regs + S3C2410_NFECC + 0);
- ecc_code[1] = readb(info->regs + S3C2410_NFECC + 1);
- ecc_code[2] = readb(info->regs + S3C2410_NFECC + 2);
-
- pr_debug("%s: returning ecc %*phN\n", __func__, 3, ecc_code);
-
- return 0;
-}
-
-static int s3c2412_nand_calculate_ecc(struct nand_chip *chip,
- const u_char *dat, u_char *ecc_code)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
- unsigned long ecc = readl(info->regs + S3C2412_NFMECC0);
-
- ecc_code[0] = ecc;
- ecc_code[1] = ecc >> 8;
- ecc_code[2] = ecc >> 16;
-
- pr_debug("%s: returning ecc %*phN\n", __func__, 3, ecc_code);
-
- return 0;
-}
-
-static int s3c2440_nand_calculate_ecc(struct nand_chip *chip,
- const u_char *dat, u_char *ecc_code)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
- unsigned long ecc = readl(info->regs + S3C2440_NFMECC0);
-
- ecc_code[0] = ecc;
- ecc_code[1] = ecc >> 8;
- ecc_code[2] = ecc >> 16;
-
- pr_debug("%s: returning ecc %06lx\n", __func__, ecc & 0xffffff);
-
- return 0;
-}
-
-/* over-ride the standard functions for a little more speed. We can
- * use read/write block to move the data buffers to/from the controller
-*/
-
-static void s3c2410_nand_read_buf(struct nand_chip *this, u_char *buf, int len)
-{
- readsb(this->legacy.IO_ADDR_R, buf, len);
-}
-
-static void s3c2440_nand_read_buf(struct nand_chip *this, u_char *buf, int len)
-{
- struct mtd_info *mtd = nand_to_mtd(this);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-
- readsl(info->regs + S3C2440_NFDATA, buf, len >> 2);
-
- /* cleanup if we've got less than a word to do */
- if (len & 3) {
- buf += len & ~3;
-
- for (; len & 3; len--)
- *buf++ = readb(info->regs + S3C2440_NFDATA);
- }
-}
-
-static void s3c2410_nand_write_buf(struct nand_chip *this, const u_char *buf,
- int len)
-{
- writesb(this->legacy.IO_ADDR_W, buf, len);
-}
-
-static void s3c2440_nand_write_buf(struct nand_chip *this, const u_char *buf,
- int len)
-{
- struct mtd_info *mtd = nand_to_mtd(this);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-
- writesl(info->regs + S3C2440_NFDATA, buf, len >> 2);
-
- /* cleanup any fractional write */
- if (len & 3) {
- buf += len & ~3;
-
- for (; len & 3; len--, buf++)
- writeb(*buf, info->regs + S3C2440_NFDATA);
- }
-}
-
-/* device management functions */
-
-static void s3c24xx_nand_remove(struct platform_device *pdev)
-{
- struct s3c2410_nand_info *info = to_nand_info(pdev);
-
- if (info == NULL)
- return;
-
- /* Release all our mtds and their partitions, then go through
- * freeing the resources used
- */
-
- if (info->mtds != NULL) {
- struct s3c2410_nand_mtd *ptr = info->mtds;
- int mtdno;
-
- for (mtdno = 0; mtdno < info->mtd_count; mtdno++, ptr++) {
- pr_debug("releasing mtd %d (%p)\n", mtdno, ptr);
- WARN_ON(mtd_device_unregister(nand_to_mtd(&ptr->chip)));
- nand_cleanup(&ptr->chip);
- }
- }
-
- /* free the common resources */
-
- if (!IS_ERR(info->clk))
- s3c2410_nand_clk_set_state(info, CLOCK_DISABLE);
-}
-
-static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info,
- struct s3c2410_nand_mtd *mtd,
- struct s3c2410_nand_set *set)
-{
- if (set) {
- struct mtd_info *mtdinfo = nand_to_mtd(&mtd->chip);
-
- mtdinfo->name = set->name;
-
- return mtd_device_register(mtdinfo, set->partitions,
- set->nr_partitions);
- }
-
- return -ENODEV;
-}
-
-static int s3c2410_nand_setup_interface(struct nand_chip *chip, int csline,
- const struct nand_interface_config *conf)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
- struct s3c2410_platform_nand *pdata = info->platform;
- const struct nand_sdr_timings *timings;
- int tacls;
-
- timings = nand_get_sdr_timings(conf);
- if (IS_ERR(timings))
- return -ENOTSUPP;
-
- tacls = timings->tCLS_min - timings->tWP_min;
- if (tacls < 0)
- tacls = 0;
-
- pdata->tacls = DIV_ROUND_UP(tacls, 1000);
- pdata->twrph0 = DIV_ROUND_UP(timings->tWP_min, 1000);
- pdata->twrph1 = DIV_ROUND_UP(timings->tCLH_min, 1000);
-
- return s3c2410_nand_setrate(info);
-}
-
-/**
- * s3c2410_nand_init_chip - initialise a single instance of an chip
- * @info: The base NAND controller the chip is on.
- * @nmtd: The new controller MTD instance to fill in.
- * @set: The information passed from the board specific platform data.
- *
- * Initialise the given @nmtd from the information in @info and @set. This
- * readies the structure for use with the MTD layer functions by ensuring
- * all pointers are setup and the necessary control routines selected.
- */
-static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
- struct s3c2410_nand_mtd *nmtd,
- struct s3c2410_nand_set *set)
-{
- struct device_node *np = info->device->of_node;
- struct nand_chip *chip = &nmtd->chip;
- void __iomem *regs = info->regs;
-
- nand_set_flash_node(chip, set->of_node);
-
- chip->legacy.write_buf = s3c2410_nand_write_buf;
- chip->legacy.read_buf = s3c2410_nand_read_buf;
- chip->legacy.select_chip = s3c2410_nand_select_chip;
- chip->legacy.chip_delay = 50;
- nand_set_controller_data(chip, nmtd);
- chip->options = set->options;
- chip->controller = &info->controller;
-
- /*
- * let's keep behavior unchanged for legacy boards booting via pdata and
- * auto-detect timings only when booting with a device tree.
- */
- if (!np)
- chip->options |= NAND_KEEP_TIMINGS;
-
- switch (info->cpu_type) {
- case TYPE_S3C2410:
- chip->legacy.IO_ADDR_W = regs + S3C2410_NFDATA;
- info->sel_reg = regs + S3C2410_NFCONF;
- info->sel_bit = S3C2410_NFCONF_nFCE;
- chip->legacy.cmd_ctrl = s3c2410_nand_hwcontrol;
- chip->legacy.dev_ready = s3c2410_nand_devready;
- break;
-
- case TYPE_S3C2440:
- chip->legacy.IO_ADDR_W = regs + S3C2440_NFDATA;
- info->sel_reg = regs + S3C2440_NFCONT;
- info->sel_bit = S3C2440_NFCONT_nFCE;
- chip->legacy.cmd_ctrl = s3c2440_nand_hwcontrol;
- chip->legacy.dev_ready = s3c2440_nand_devready;
- chip->legacy.read_buf = s3c2440_nand_read_buf;
- chip->legacy.write_buf = s3c2440_nand_write_buf;
- break;
-
- case TYPE_S3C2412:
- chip->legacy.IO_ADDR_W = regs + S3C2440_NFDATA;
- info->sel_reg = regs + S3C2440_NFCONT;
- info->sel_bit = S3C2412_NFCONT_nFCE0;
- chip->legacy.cmd_ctrl = s3c2440_nand_hwcontrol;
- chip->legacy.dev_ready = s3c2412_nand_devready;
-
- if (readl(regs + S3C2410_NFCONF) & S3C2412_NFCONF_NANDBOOT)
- dev_info(info->device, "System booted from NAND\n");
-
- break;
- }
-
- chip->legacy.IO_ADDR_R = chip->legacy.IO_ADDR_W;
-
- nmtd->info = info;
- nmtd->set = set;
-
- chip->ecc.engine_type = info->platform->engine_type;
-
- /*
- * If you use u-boot BBT creation code, specifying this flag will
- * let the kernel fish out the BBT from the NAND.
- */
- if (set->flash_bbt)
- chip->bbt_options |= NAND_BBT_USE_FLASH;
-}
-
-/**
- * s3c2410_nand_attach_chip - Init the ECC engine after NAND scan
- * @chip: The NAND chip
- *
- * This hook is called by the core after the identification of the NAND chip,
- * once the relevant per-chip information is up to date.. This call ensure that
- * we update the internal state accordingly.
- *
- * The internal state is currently limited to the ECC state information.
-*/
-static int s3c2410_nand_attach_chip(struct nand_chip *chip)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-
- switch (chip->ecc.engine_type) {
-
- case NAND_ECC_ENGINE_TYPE_NONE:
- dev_info(info->device, "ECC disabled\n");
- break;
-
- case NAND_ECC_ENGINE_TYPE_SOFT:
- /*
- * This driver expects Hamming based ECC when engine_type is set
- * to NAND_ECC_ENGINE_TYPE_SOFT. Force ecc.algo to
- * NAND_ECC_ALGO_HAMMING to avoid adding an extra ecc_algo field
- * to s3c2410_platform_nand.
- */
- chip->ecc.algo = NAND_ECC_ALGO_HAMMING;
- dev_info(info->device, "soft ECC\n");
- break;
-
- case NAND_ECC_ENGINE_TYPE_ON_HOST:
- chip->ecc.calculate = s3c2410_nand_calculate_ecc;
- chip->ecc.correct = s3c2410_nand_correct_data;
- chip->ecc.strength = 1;
-
- switch (info->cpu_type) {
- case TYPE_S3C2410:
- chip->ecc.hwctl = s3c2410_nand_enable_hwecc;
- chip->ecc.calculate = s3c2410_nand_calculate_ecc;
- break;
-
- case TYPE_S3C2412:
- chip->ecc.hwctl = s3c2412_nand_enable_hwecc;
- chip->ecc.calculate = s3c2412_nand_calculate_ecc;
- break;
-
- case TYPE_S3C2440:
- chip->ecc.hwctl = s3c2440_nand_enable_hwecc;
- chip->ecc.calculate = s3c2440_nand_calculate_ecc;
- break;
- }
-
- dev_dbg(info->device, "chip %p => page shift %d\n",
- chip, chip->page_shift);
-
- /* change the behaviour depending on whether we are using
- * the large or small page nand device */
- if (chip->page_shift > 10) {
- chip->ecc.size = 256;
- chip->ecc.bytes = 3;
- } else {
- chip->ecc.size = 512;
- chip->ecc.bytes = 3;
- mtd_set_ooblayout(nand_to_mtd(chip),
- &s3c2410_ooblayout_ops);
- }
-
- dev_info(info->device, "hardware ECC\n");
- break;
-
- default:
- dev_err(info->device, "invalid ECC mode!\n");
- return -EINVAL;
- }
-
- if (chip->bbt_options & NAND_BBT_USE_FLASH)
- chip->options |= NAND_SKIP_BBTSCAN;
-
- return 0;
-}
-
-static const struct nand_controller_ops s3c24xx_nand_controller_ops = {
- .attach_chip = s3c2410_nand_attach_chip,
- .setup_interface = s3c2410_nand_setup_interface,
-};
-
-static const struct of_device_id s3c24xx_nand_dt_ids[] = {
- {
- .compatible = "samsung,s3c2410-nand",
- .data = &s3c2410_nand_devtype_data,
- }, {
- /* also compatible with s3c6400 */
- .compatible = "samsung,s3c2412-nand",
- .data = &s3c2412_nand_devtype_data,
- }, {
- .compatible = "samsung,s3c2440-nand",
- .data = &s3c2440_nand_devtype_data,
- },
- { /* sentinel */ }
-};
-MODULE_DEVICE_TABLE(of, s3c24xx_nand_dt_ids);
-
-static int s3c24xx_nand_probe_dt(struct platform_device *pdev)
-{
- const struct s3c24XX_nand_devtype_data *devtype_data;
- struct s3c2410_platform_nand *pdata;
- struct s3c2410_nand_info *info = platform_get_drvdata(pdev);
- struct device_node *np = pdev->dev.of_node, *child;
- struct s3c2410_nand_set *sets;
-
- devtype_data = of_device_get_match_data(&pdev->dev);
- if (!devtype_data)
- return -ENODEV;
-
- info->cpu_type = devtype_data->type;
-
- pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
- if (!pdata)
- return -ENOMEM;
-
- pdev->dev.platform_data = pdata;
-
- pdata->nr_sets = of_get_child_count(np);
- if (!pdata->nr_sets)
- return 0;
-
- sets = devm_kcalloc(&pdev->dev, pdata->nr_sets, sizeof(*sets),
- GFP_KERNEL);
- if (!sets)
- return -ENOMEM;
-
- pdata->sets = sets;
-
- for_each_available_child_of_node(np, child) {
- sets->name = (char *)child->name;
- sets->of_node = child;
- sets->nr_chips = 1;
-
- of_node_get(child);
-
- sets++;
- }
-
- return 0;
-}
-
-static int s3c24xx_nand_probe_pdata(struct platform_device *pdev)
-{
- struct s3c2410_nand_info *info = platform_get_drvdata(pdev);
-
- info->cpu_type = platform_get_device_id(pdev)->driver_data;
-
- return 0;
-}
-
-/* s3c24xx_nand_probe
- *
- * called by device layer when it finds a device matching
- * one our driver can handled. This code checks to see if
- * it can allocate all necessary resources then calls the
- * nand layer to look for devices
-*/
-static int s3c24xx_nand_probe(struct platform_device *pdev)
-{
- struct s3c2410_platform_nand *plat;
- struct s3c2410_nand_info *info;
- struct s3c2410_nand_mtd *nmtd;
- struct s3c2410_nand_set *sets;
- struct resource *res;
- int err = 0;
- int size;
- int nr_sets;
- int setno;
-
- info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
- if (info == NULL) {
- err = -ENOMEM;
- goto exit_error;
- }
-
- platform_set_drvdata(pdev, info);
-
- nand_controller_init(&info->controller);
- info->controller.ops = &s3c24xx_nand_controller_ops;
-
- /* get the clock source and enable it */
-
- info->clk = devm_clk_get(&pdev->dev, "nand");
- if (IS_ERR(info->clk)) {
- dev_err(&pdev->dev, "failed to get clock\n");
- err = -ENOENT;
- goto exit_error;
- }
-
- s3c2410_nand_clk_set_state(info, CLOCK_ENABLE);
-
- if (pdev->dev.of_node)
- err = s3c24xx_nand_probe_dt(pdev);
- else
- err = s3c24xx_nand_probe_pdata(pdev);
-
- if (err)
- goto exit_error;
-
- plat = to_nand_plat(pdev);
-
- /* allocate and map the resource */
-
- /* currently we assume we have the one resource */
- res = pdev->resource;
- size = resource_size(res);
-
- info->device = &pdev->dev;
- info->platform = plat;
-
- info->regs = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(info->regs)) {
- err = PTR_ERR(info->regs);
- goto exit_error;
- }
-
- dev_dbg(&pdev->dev, "mapped registers at %p\n", info->regs);
-
- if (!plat->sets || plat->nr_sets < 1) {
- err = -EINVAL;
- goto exit_error;
- }
-
- sets = plat->sets;
- nr_sets = plat->nr_sets;
-
- info->mtd_count = nr_sets;
-
- /* allocate our information */
-
- size = nr_sets * sizeof(*info->mtds);
- info->mtds = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
- if (info->mtds == NULL) {
- err = -ENOMEM;
- goto exit_error;
- }
-
- /* initialise all possible chips */
-
- nmtd = info->mtds;
-
- for (setno = 0; setno < nr_sets; setno++, nmtd++, sets++) {
- struct mtd_info *mtd = nand_to_mtd(&nmtd->chip);
-
- pr_debug("initialising set %d (%p, info %p)\n",
- setno, nmtd, info);
-
- mtd->dev.parent = &pdev->dev;
- s3c2410_nand_init_chip(info, nmtd, sets);
-
- err = nand_scan(&nmtd->chip, sets ? sets->nr_chips : 1);
- if (err)
- goto exit_error;
-
- s3c2410_nand_add_partition(info, nmtd, sets);
- }
-
- /* initialise the hardware */
- err = s3c2410_nand_inithw(info);
- if (err != 0)
- goto exit_error;
-
- if (allow_clk_suspend(info)) {
- dev_info(&pdev->dev, "clock idle support enabled\n");
- s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND);
- }
-
- return 0;
-
- exit_error:
- s3c24xx_nand_remove(pdev);
-
- if (err == 0)
- err = -EINVAL;
- return err;
-}
-
-/* PM Support */
-#ifdef CONFIG_PM
-
-static int s3c24xx_nand_suspend(struct platform_device *dev, pm_message_t pm)
-{
- struct s3c2410_nand_info *info = platform_get_drvdata(dev);
-
- if (info) {
- info->save_sel = readl(info->sel_reg);
-
- /* For the moment, we must ensure nFCE is high during
- * the time we are suspended. This really should be
- * handled by suspending the MTDs we are using, but
- * that is currently not the case. */
-
- writel(info->save_sel | info->sel_bit, info->sel_reg);
-
- s3c2410_nand_clk_set_state(info, CLOCK_DISABLE);
- }
-
- return 0;
-}
-
-static int s3c24xx_nand_resume(struct platform_device *dev)
-{
- struct s3c2410_nand_info *info = platform_get_drvdata(dev);
- unsigned long sel;
-
- if (info) {
- s3c2410_nand_clk_set_state(info, CLOCK_ENABLE);
- s3c2410_nand_inithw(info);
-
- /* Restore the state of the nFCE line. */
-
- sel = readl(info->sel_reg);
- sel &= ~info->sel_bit;
- sel |= info->save_sel & info->sel_bit;
- writel(sel, info->sel_reg);
-
- s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND);
- }
-
- return 0;
-}
-
-#else
-#define s3c24xx_nand_suspend NULL
-#define s3c24xx_nand_resume NULL
-#endif
-
-/* driver device registration */
-
-static const struct platform_device_id s3c24xx_driver_ids[] = {
- {
- .name = "s3c2410-nand",
- .driver_data = TYPE_S3C2410,
- }, {
- .name = "s3c2440-nand",
- .driver_data = TYPE_S3C2440,
- }, {
- .name = "s3c2412-nand",
- .driver_data = TYPE_S3C2412,
- }, {
- .name = "s3c6400-nand",
- .driver_data = TYPE_S3C2412, /* compatible with 2412 */
- },
- { }
-};
-
-MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);
-
-static struct platform_driver s3c24xx_nand_driver = {
- .probe = s3c24xx_nand_probe,
- .remove = s3c24xx_nand_remove,
- .suspend = s3c24xx_nand_suspend,
- .resume = s3c24xx_nand_resume,
- .id_table = s3c24xx_driver_ids,
- .driver = {
- .name = "s3c24xx-nand",
- .of_match_table = s3c24xx_nand_dt_ids,
- },
-};
-
-module_platform_driver(s3c24xx_nand_driver);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
-MODULE_DESCRIPTION("S3C24XX MTD NAND driver");
diff --git a/drivers/mtd/nand/raw/stm32_fmc2_nand.c b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
index d957327fb4fa..c08d6b176372 100644
--- a/drivers/mtd/nand/raw/stm32_fmc2_nand.c
+++ b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
@@ -2158,7 +2158,6 @@ static struct platform_driver stm32_fmc2_nfc_driver = {
};
module_platform_driver(stm32_fmc2_nfc_driver);
-MODULE_ALIAS("platform:stm32_fmc2_nfc");
MODULE_AUTHOR("Christophe Kerello <christophe.kerello@st.com>");
MODULE_DESCRIPTION("STMicroelectronics STM32 FMC2 NFC driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 162cd5f4f234..f6a8e8ae819d 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -2205,4 +2205,3 @@ module_platform_driver(sunxi_nfc_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Boris BREZILLON");
MODULE_DESCRIPTION("Allwinner NAND Flash Controller driver");
-MODULE_ALIAS("platform:sunxi_nand");
diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile
index 258da42451a4..6d3d203df048 100644
--- a/drivers/mtd/nand/spi/Makefile
+++ b/drivers/mtd/nand/spi/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
spinand-objs := core.o otp.o
-spinand-objs += alliancememory.o ato.o esmt.o foresee.o gigadevice.o macronix.o
+spinand-objs += alliancememory.o ato.o esmt.o fmsh.o foresee.o gigadevice.o macronix.o
spinand-objs += micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o
obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index b0898990b2a5..f92133b8e1a6 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -430,8 +430,16 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand,
* Dirmap accesses are allowed to toggle the CS.
* Toggling the CS during a continuous read is forbidden.
*/
- if (nbytes && req->continuous)
- return -EIO;
+ if (nbytes && req->continuous) {
+ /*
+ * Spi controller with broken support of continuous
+ * reading was detected. Disable future use of
+ * continuous reading and return -EAGAIN to retry
+ * reading within regular mode.
+ */
+ spinand->cont_read_possible = false;
+ return -EAGAIN;
+ }
}
if (req->datalen)
@@ -899,10 +907,19 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t from,
old_stats = mtd->ecc_stats;
- if (spinand_use_cont_read(mtd, from, ops))
+ if (spinand_use_cont_read(mtd, from, ops)) {
ret = spinand_mtd_continuous_page_read(mtd, from, ops, &max_bitflips);
- else
+ if (ret == -EAGAIN && !spinand->cont_read_possible) {
+ /*
+ * Spi controller with broken support of continuous
+ * reading was detected (see spinand_read_from_cache_op()),
+ * repeat reading in regular mode.
+ */
+ ret = spinand_mtd_regular_page_read(mtd, from, ops, &max_bitflips);
+ }
+ } else {
ret = spinand_mtd_regular_page_read(mtd, from, ops, &max_bitflips);
+ }
if (ops->stats) {
ops->stats->uncorrectable_errors +=
@@ -1093,22 +1110,50 @@ static int spinand_mtd_block_isreserved(struct mtd_info *mtd, loff_t offs)
return ret;
}
+static struct spi_mem_dirmap_desc *spinand_create_rdesc(
+ struct spinand_device *spinand,
+ struct spi_mem_dirmap_info *info)
+{
+ struct nand_device *nand = spinand_to_nand(spinand);
+ struct spi_mem_dirmap_desc *desc = NULL;
+
+ if (spinand->cont_read_possible) {
+ /*
+ * spi controller may return an error if info->length is
+ * too large
+ */
+ info->length = nanddev_eraseblock_size(nand);
+ desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+ spinand->spimem, info);
+ }
+
+ if (IS_ERR_OR_NULL(desc)) {
+ /*
+ * continuous reading is not supported by flash or
+ * its spi controller, use regular reading
+ */
+ spinand->cont_read_possible = false;
+
+ info->length = nanddev_page_size(nand) +
+ nanddev_per_page_oobsize(nand);
+ desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+ spinand->spimem, info);
+ }
+
+ return desc;
+}
+
static int spinand_create_dirmap(struct spinand_device *spinand,
unsigned int plane)
{
struct nand_device *nand = spinand_to_nand(spinand);
- struct spi_mem_dirmap_info info = {
- .length = nanddev_page_size(nand) +
- nanddev_per_page_oobsize(nand),
- };
+ struct spi_mem_dirmap_info info = { 0 };
struct spi_mem_dirmap_desc *desc;
- if (spinand->cont_read_possible)
- info.length = nanddev_eraseblock_size(nand);
-
/* The plane number is passed in MSB just above the column address */
info.offset = plane << fls(nand->memorg.pagesize);
+ info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand);
info.op_tmpl = *spinand->op_templates.update_cache;
desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
spinand->spimem, &info);
@@ -1118,8 +1163,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
spinand->dirmaps[plane].wdesc = desc;
info.op_tmpl = *spinand->op_templates.read_cache;
- desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
- spinand->spimem, &info);
+ desc = spinand_create_rdesc(spinand, &info);
if (IS_ERR(desc))
return PTR_ERR(desc);
@@ -1132,6 +1176,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
return 0;
}
+ info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand);
info.op_tmpl = *spinand->op_templates.update_cache;
info.op_tmpl.data.ecc = true;
desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
@@ -1143,8 +1188,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
info.op_tmpl = *spinand->op_templates.read_cache;
info.op_tmpl.data.ecc = true;
- desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
- spinand->spimem, &info);
+ desc = spinand_create_rdesc(spinand, &info);
if (IS_ERR(desc))
return PTR_ERR(desc);
@@ -1184,6 +1228,7 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = {
&alliancememory_spinand_manufacturer,
&ato_spinand_manufacturer,
&esmt_c8_spinand_manufacturer,
+ &fmsh_spinand_manufacturer,
&foresee_spinand_manufacturer,
&gigadevice_spinand_manufacturer,
&macronix_spinand_manufacturer,
diff --git a/drivers/mtd/nand/spi/fmsh.c b/drivers/mtd/nand/spi/fmsh.c
new file mode 100644
index 000000000000..8b2097bfc771
--- /dev/null
+++ b/drivers/mtd/nand/spi/fmsh.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020-2021 Rockchip Electronics Co., Ltd.
+ *
+ * Author: Dingqiang Lin <jon.lin@rock-chips.com>
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/mtd/spinand.h>
+
+#define SPINAND_MFR_FMSH 0xA1
+
+static SPINAND_OP_VARIANTS(read_cache_variants,
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
+
+static SPINAND_OP_VARIANTS(write_cache_variants,
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
+
+static SPINAND_OP_VARIANTS(update_cache_variants,
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
+
+static int fm25s01a_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ return -ERANGE;
+}
+
+static int fm25s01a_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section)
+ return -ERANGE;
+
+ region->offset = 2;
+ region->length = 62;
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops fm25s01a_ooblayout = {
+ .ecc = fm25s01a_ooblayout_ecc,
+ .free = fm25s01a_ooblayout_free,
+};
+
+static const struct spinand_info fmsh_spinand_table[] = {
+ SPINAND_INFO("FM25S01A",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE4),
+ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
+ NAND_ECCREQ(1, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&fm25s01a_ooblayout, NULL)),
+};
+
+static const struct spinand_manufacturer_ops fmsh_spinand_manuf_ops = {
+};
+
+const struct spinand_manufacturer fmsh_spinand_manufacturer = {
+ .id = SPINAND_MFR_FMSH,
+ .name = "Fudan Micro",
+ .chips = fmsh_spinand_table,
+ .nchips = ARRAY_SIZE(fmsh_spinand_table),
+ .ops = &fmsh_spinand_manuf_ops,
+};
diff --git a/drivers/mtd/nand/spi/gigadevice.c b/drivers/mtd/nand/spi/gigadevice.c
index 93e40431dbe2..72ad36c9a126 100644
--- a/drivers/mtd/nand/spi/gigadevice.c
+++ b/drivers/mtd/nand/spi/gigadevice.c
@@ -4,6 +4,7 @@
* Chuanhong Guo <gch981213@gmail.com>
*/
+#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mtd/spinand.h>
@@ -23,6 +24,18 @@
#define GD5FXGQ4UXFXXG_STATUS_ECC_1_3_BITFLIPS (1 << 4)
#define GD5FXGQ4UXFXXG_STATUS_ECC_UNCOR_ERROR (7 << 4)
+/* Feature bit definitions */
+#define GD_FEATURE_NR BIT(3) /* Normal Read(1=normal,0=continuous) */
+#define GD_FEATURE_CRDC BIT(2) /* Continuous Read Dummy */
+
+/* ECC status extraction helpers */
+#define GD_ECCSR_LAST_PAGE(eccsr) FIELD_GET(GENMASK(3, 0), eccsr)
+#define GD_ECCSR_ACCUMULATED(eccsr) FIELD_GET(GENMASK(7, 4), eccsr)
+
+struct gigadevice_priv {
+ bool continuous_read;
+};
+
static SPINAND_OP_VARIANTS(read_cache_variants,
SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
@@ -63,6 +76,74 @@ static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
+static int gd5fxgm9_get_eccsr(struct spinand_device *spinand, u8 *eccsr)
+{
+ struct gigadevice_priv *priv = spinand->priv;
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0x7c, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_DUMMY(1, 1),
+ SPI_MEM_OP_DATA_IN(1, eccsr, 1));
+ int ret;
+
+ ret = spi_mem_exec_op(spinand->spimem, &op);
+ if (ret)
+ return ret;
+
+ if (priv->continuous_read)
+ *eccsr = GD_ECCSR_ACCUMULATED(*eccsr);
+ else
+ *eccsr = GD_ECCSR_LAST_PAGE(*eccsr);
+
+ return 0;
+}
+
+static int gd5fxgm9_ecc_get_status(struct spinand_device *spinand, u8 status)
+{
+ struct nand_device *nand = spinand_to_nand(spinand);
+ u8 eccsr;
+ int ret;
+
+ switch (status & STATUS_ECC_MASK) {
+ case STATUS_ECC_NO_BITFLIPS:
+ return 0;
+
+ case GD5FXGQ4XA_STATUS_ECC_1_7_BITFLIPS:
+ ret = gd5fxgm9_get_eccsr(spinand, spinand->scratchbuf);
+ if (ret)
+ return nanddev_get_ecc_conf(nand)->strength;
+
+ eccsr = *spinand->scratchbuf;
+ if (WARN_ON(!eccsr || eccsr > nanddev_get_ecc_conf(nand)->strength))
+ return nanddev_get_ecc_conf(nand)->strength;
+
+ return eccsr;
+
+ case GD5FXGQ4XA_STATUS_ECC_8_BITFLIPS:
+ return 8;
+
+ case STATUS_ECC_UNCOR_ERROR:
+ return -EBADMSG;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int gd5fxgm9_set_continuous_read(struct spinand_device *spinand, bool enable)
+{
+ struct gigadevice_priv *priv = spinand->priv;
+ int ret;
+
+ ret = spinand_upd_cfg(spinand, GD_FEATURE_NR,
+ enable ? 0 : GD_FEATURE_NR);
+ if (ret)
+ return ret;
+
+ priv->continuous_read = enable;
+
+ return 0;
+}
+
static int gd5fxgq4xa_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
@@ -542,7 +623,8 @@ static const struct spinand_info gigadevice_spinand_table[] = {
&update_cache_variants),
SPINAND_HAS_QE_BIT,
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
- gd5fxgq4uexxg_ecc_get_status)),
+ gd5fxgm9_ecc_get_status),
+ SPINAND_CONT_READ(gd5fxgm9_set_continuous_read)),
SPINAND_INFO("GD5F1GM9RExxG",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x81, 0x01),
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
@@ -552,10 +634,31 @@ static const struct spinand_info gigadevice_spinand_table[] = {
&update_cache_variants),
SPINAND_HAS_QE_BIT,
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
- gd5fxgq4uexxg_ecc_get_status)),
+ gd5fxgm9_ecc_get_status),
+ SPINAND_CONT_READ(gd5fxgm9_set_continuous_read)),
};
+static int gd5fxgm9_spinand_init(struct spinand_device *spinand)
+{
+ struct gigadevice_priv *priv;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ spinand->priv = priv;
+
+ return 0;
+}
+
+static void gd5fxgm9_spinand_cleanup(struct spinand_device *spinand)
+{
+ kfree(spinand->priv);
+}
+
static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = {
+ .init = gd5fxgm9_spinand_init,
+ .cleanup = gd5fxgm9_spinand_cleanup,
};
const struct spinand_manufacturer gigadevice_spinand_manufacturer = {
diff --git a/drivers/mtd/rfd_ftl.c b/drivers/mtd/rfd_ftl.c
index c546f8c5f24d..be26cc67a1c4 100644
--- a/drivers/mtd/rfd_ftl.c
+++ b/drivers/mtd/rfd_ftl.c
@@ -190,8 +190,8 @@ static int scan_header(struct partition *part)
if (!part->blocks)
goto err;
- part->sector_map = vmalloc(array_size(sizeof(u_long),
- part->sector_count));
+ part->sector_map = vmalloc_array(part->sector_count,
+ sizeof(u_long));
if (!part->sector_map)
goto err;
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index ac4b960101cc..20ea80450f22 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -2014,6 +2014,76 @@ static const struct flash_info *spi_nor_detect(struct spi_nor *nor)
return info;
}
+/*
+ * On Octal DTR capable flashes, reads cannot start or end at an odd
+ * address in Octal DTR mode. Extra bytes need to be read at the start
+ * or end to make sure both the start address and length remain even.
+ */
+static int spi_nor_octal_dtr_read(struct spi_nor *nor, loff_t from, size_t len,
+ u_char *buf)
+{
+ u_char *tmp_buf;
+ size_t tmp_len;
+ loff_t start, end;
+ int ret, bytes_read;
+
+ if (IS_ALIGNED(from, 2) && IS_ALIGNED(len, 2))
+ return spi_nor_read_data(nor, from, len, buf);
+ else if (IS_ALIGNED(from, 2) && len > PAGE_SIZE)
+ return spi_nor_read_data(nor, from, round_down(len, PAGE_SIZE),
+ buf);
+
+ tmp_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!tmp_buf)
+ return -ENOMEM;
+
+ start = round_down(from, 2);
+ end = round_up(from + len, 2);
+
+ /*
+ * Avoid allocating too much memory. The requested read length might be
+ * quite large. Allocating a buffer just as large (slightly bigger, in
+ * fact) would put unnecessary memory pressure on the system.
+ *
+ * For example if the read is from 3 to 1M, then this will read from 2
+ * to 4098. The reads from 4098 to 1M will then not need a temporary
+ * buffer so they can proceed as normal.
+ */
+ tmp_len = min_t(size_t, end - start, PAGE_SIZE);
+
+ ret = spi_nor_read_data(nor, start, tmp_len, tmp_buf);
+ if (ret == 0) {
+ ret = -EIO;
+ goto out;
+ }
+ if (ret < 0)
+ goto out;
+
+ /*
+ * More bytes are read than actually requested, but that number can't be
+ * reported to the calling function or it will confuse its calculations.
+ * Calculate how many of the _requested_ bytes were read.
+ */
+ bytes_read = ret;
+
+ if (from != start)
+ ret -= from - start;
+
+ /*
+ * Only account for extra bytes at the end if they were actually read.
+ * For example, if the total length was truncated because of temporary
+ * buffer size limit then the adjustment for the extra bytes at the end
+ * is not needed.
+ */
+ if (start + bytes_read == end)
+ ret -= end - (from + len);
+
+ memcpy(buf, tmp_buf + (from - start), ret);
+out:
+ kfree(tmp_buf);
+ return ret;
+}
+
static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
@@ -2031,7 +2101,11 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
while (len) {
loff_t addr = from;
- ret = spi_nor_read_data(nor, addr, len, buf);
+ if (nor->read_proto == SNOR_PROTO_8_8_8_DTR)
+ ret = spi_nor_octal_dtr_read(nor, addr, len, buf);
+ else
+ ret = spi_nor_read_data(nor, addr, len, buf);
+
if (ret == 0) {
/* We shouldn't see 0-length reads */
ret = -EIO;
@@ -2055,6 +2129,68 @@ read_err:
}
/*
+ * On Octal DTR capable flashes, writes cannot start or end at an odd address
+ * in Octal DTR mode. Extra 0xff bytes need to be appended or prepended to
+ * make sure the start address and end address are even. 0xff is used because
+ * on NOR flashes a program operation can only flip bits from 1 to 0, not the
+ * other way round. 0 to 1 flip needs to happen via erases.
+ */
+static int spi_nor_octal_dtr_write(struct spi_nor *nor, loff_t to, size_t len,
+ const u8 *buf)
+{
+ u8 *tmp_buf;
+ size_t bytes_written;
+ loff_t start, end;
+ int ret;
+
+ if (IS_ALIGNED(to, 2) && IS_ALIGNED(len, 2))
+ return spi_nor_write_data(nor, to, len, buf);
+
+ tmp_buf = kmalloc(nor->params->page_size, GFP_KERNEL);
+ if (!tmp_buf)
+ return -ENOMEM;
+
+ memset(tmp_buf, 0xff, nor->params->page_size);
+
+ start = round_down(to, 2);
+ end = round_up(to + len, 2);
+
+ memcpy(tmp_buf + (to - start), buf, len);
+
+ ret = spi_nor_write_data(nor, start, end - start, tmp_buf);
+ if (ret == 0) {
+ ret = -EIO;
+ goto out;
+ }
+ if (ret < 0)
+ goto out;
+
+ /*
+ * More bytes are written than actually requested, but that number can't
+ * be reported to the calling function or it will confuse its
+ * calculations. Calculate how many of the _requested_ bytes were
+ * written.
+ */
+ bytes_written = ret;
+
+ if (to != start)
+ ret -= to - start;
+
+ /*
+ * Only account for extra bytes at the end if they were actually
+ * written. For example, if for some reason the controller could only
+ * complete a partial write then the adjustment for the extra bytes at
+ * the end is not needed.
+ */
+ if (start + bytes_written == end)
+ ret -= end - (to + len);
+
+out:
+ kfree(tmp_buf);
+ return ret;
+}
+
+/*
* Write an address range to the nor chip. Data must be written in
* FLASH_PAGESIZE chunks. The address range may be any size provided
* it is within the physical boundaries.
@@ -2090,7 +2226,12 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
goto write_err;
}
- ret = spi_nor_write_data(nor, addr, page_remain, buf + i);
+ if (nor->write_proto == SNOR_PROTO_8_8_8_DTR)
+ ret = spi_nor_octal_dtr_write(nor, addr, page_remain,
+ buf + i);
+ else
+ ret = spi_nor_write_data(nor, addr, page_remain,
+ buf + i);
spi_nor_unlock_device(nor);
if (ret < 0)
goto write_err;
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index edd811444ce5..e0d88d3199c1 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -28,6 +28,17 @@ source "drivers/nvmem/layouts/Kconfig"
# Devices
+config NVMEM_AN8855_EFUSE
+ tristate "Airoha AN8855 eFuse support"
+ depends on MFD_AIROHA_AN8855 || COMPILE_TEST
+ help
+ Say y here to enable support for reading eFuses on Airoha AN8855
+ Switch. These are e.g. used to store factory programmed
+ calibration data required for the PHY.
+
+ This driver can also be built as a module. If so, the module will
+ be called nvmem-an8855-efuse.
+
config NVMEM_APPLE_EFUSES
tristate "Apple eFuse support"
depends on ARCH_APPLE || COMPILE_TEST
@@ -240,6 +251,16 @@ config NVMEM_NINTENDO_OTP
This driver can also be built as a module. If so, the module
will be called nvmem-nintendo-otp.
+config NVMEM_S32G_OCOTP
+ tristate "S32G SoC OCOTP support"
+ depends on ARCH_S32
+ help
+ This is a driver for the 'OCOTP' peripheral available on S32G
+ platforms.
+
+ If you say Y here, you will get support for the One Time
+ Programmable memory pages.
+
config NVMEM_QCOM_QFPROM
tristate "QCOM QFPROM Support"
depends on ARCH_QCOM || COMPILE_TEST
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index 2021d59688db..70a4464dcb1e 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -10,6 +10,8 @@ nvmem_layouts-y := layouts.o
obj-y += layouts/
# Devices
+obj-$(CONFIG_NVMEM_AN8855_EFUSE) += nvmem-an8855-efuse.o
+nvmem-an8855-efuse-y := an8855-efuse.o
obj-$(CONFIG_NVMEM_APPLE_EFUSES) += nvmem-apple-efuses.o
nvmem-apple-efuses-y := apple-efuses.o
obj-$(CONFIG_NVMEM_APPLE_SPMI) += apple_nvmem_spmi.o
@@ -79,6 +81,8 @@ obj-$(CONFIG_NVMEM_SUNPLUS_OCOTP) += nvmem_sunplus_ocotp.o
nvmem_sunplus_ocotp-y := sunplus-ocotp.o
obj-$(CONFIG_NVMEM_SUNXI_SID) += nvmem_sunxi_sid.o
nvmem_sunxi_sid-y := sunxi_sid.o
+obj-$(CONFIG_NVMEM_S32G_OCOTP) += nvmem-s32g-ocotp-nvmem.o
+nvmem-s32g-ocotp-nvmem-y := s32g-ocotp-nvmem.o
obj-$(CONFIG_NVMEM_U_BOOT_ENV) += nvmem_u-boot-env.o
nvmem_u-boot-env-y := u-boot-env.o
obj-$(CONFIG_NVMEM_UNIPHIER_EFUSE) += nvmem-uniphier-efuse.o
diff --git a/drivers/nvmem/an8855-efuse.c b/drivers/nvmem/an8855-efuse.c
new file mode 100644
index 000000000000..d1afde6f623f
--- /dev/null
+++ b/drivers/nvmem/an8855-efuse.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Airoha AN8855 Switch EFUSE Driver
+ */
+
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/nvmem-provider.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define AN8855_EFUSE_CELL 50
+
+#define AN8855_EFUSE_DATA0 0x1000a500
+#define AN8855_EFUSE_R50O GENMASK(30, 24)
+
+static int an8855_efuse_read(void *context, unsigned int offset,
+ void *val, size_t bytes)
+{
+ struct regmap *regmap = context;
+
+ return regmap_bulk_read(regmap, AN8855_EFUSE_DATA0 + offset,
+ val, bytes / sizeof(u32));
+}
+
+static int an8855_efuse_probe(struct platform_device *pdev)
+{
+ struct nvmem_config an8855_nvmem_config = {
+ .name = "an8855-efuse",
+ .size = AN8855_EFUSE_CELL * sizeof(u32),
+ .stride = sizeof(u32),
+ .word_size = sizeof(u32),
+ .reg_read = an8855_efuse_read,
+ };
+ struct device *dev = &pdev->dev;
+ struct nvmem_device *nvmem;
+ struct regmap *regmap;
+
+ /* Assign NVMEM priv to MFD regmap */
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap)
+ return -ENOENT;
+
+ an8855_nvmem_config.priv = regmap;
+ an8855_nvmem_config.dev = dev;
+ nvmem = devm_nvmem_register(dev, &an8855_nvmem_config);
+
+ return PTR_ERR_OR_ZERO(nvmem);
+}
+
+static const struct of_device_id an8855_efuse_of_match[] = {
+ { .compatible = "airoha,an8855-efuse", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, an8855_efuse_of_match);
+
+static struct platform_driver an8855_efuse_driver = {
+ .probe = an8855_efuse_probe,
+ .driver = {
+ .name = "an8855-efuse",
+ .of_match_table = an8855_efuse_of_match,
+ },
+};
+module_platform_driver(an8855_efuse_driver);
+
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_DESCRIPTION("Driver for AN8855 Switch EFUSE");
+MODULE_LICENSE("GPL");
diff --git a/drivers/nvmem/s32g-ocotp-nvmem.c b/drivers/nvmem/s32g-ocotp-nvmem.c
new file mode 100644
index 000000000000..119871ab3a94
--- /dev/null
+++ b/drivers/nvmem/s32g-ocotp-nvmem.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2023-2025 NXP
+ */
+
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/nvmem-provider.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+struct s32g_ocotp_priv {
+ struct device *dev;
+ void __iomem *base;
+};
+
+static int s32g_ocotp_read(void *context, unsigned int offset,
+ void *val, size_t bytes)
+{
+ struct s32g_ocotp_priv *s32g_data = context;
+ u32 *dst = val;
+
+ while (bytes >= sizeof(u32)) {
+ *dst++ = ioread32(s32g_data->base + offset);
+
+ bytes -= sizeof(u32);
+ offset += sizeof(u32);
+ }
+
+ return 0;
+}
+
+static struct nvmem_keepout s32g_keepouts[] = {
+ { .start = 0, .end = 520 },
+ { .start = 540, .end = 564 },
+ { .start = 596, .end = 664 },
+ { .start = 668, .end = 676 },
+ { .start = 684, .end = 732 },
+ { .start = 744, .end = 864 },
+ { .start = 908, .end = 924 },
+ { .start = 928, .end = 936 },
+ { .start = 948, .end = 964 },
+ { .start = 968, .end = 976 },
+ { .start = 984, .end = 1012 },
+};
+
+static struct nvmem_config s32g_ocotp_nvmem_config = {
+ .name = "s32g-ocotp",
+ .add_legacy_fixed_of_cells = true,
+ .read_only = true,
+ .word_size = 4,
+ .reg_read = s32g_ocotp_read,
+ .keepout = s32g_keepouts,
+ .nkeepout = ARRAY_SIZE(s32g_keepouts),
+};
+
+static const struct of_device_id ocotp_of_match[] = {
+ { .compatible = "nxp,s32g2-ocotp" },
+ { /* sentinel */ }
+};
+
+static int s32g_ocotp_probe(struct platform_device *pdev)
+{
+ struct s32g_ocotp_priv *s32g_data;
+ struct device *dev = &pdev->dev;
+ struct nvmem_device *nvmem;
+ struct resource *res;
+
+ s32g_data = devm_kzalloc(dev, sizeof(*s32g_data), GFP_KERNEL);
+ if (!s32g_data)
+ return -ENOMEM;
+
+ s32g_data->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(s32g_data->base))
+ return dev_err_probe(dev, PTR_ERR(s32g_data->base),
+ "Cannot map OCOTP device.\n");
+
+ s32g_data->dev = dev;
+ s32g_ocotp_nvmem_config.dev = dev;
+ s32g_ocotp_nvmem_config.priv = s32g_data;
+ s32g_ocotp_nvmem_config.size = resource_size(res);
+
+ nvmem = devm_nvmem_register(dev, &s32g_ocotp_nvmem_config);
+
+ return PTR_ERR_OR_ZERO(nvmem);
+}
+
+static struct platform_driver s32g_ocotp_driver = {
+ .probe = s32g_ocotp_probe,
+ .driver = {
+ .name = "s32g-ocotp",
+ .of_match_table = ocotp_of_match,
+ },
+};
+module_platform_driver(s32g_ocotp_driver);
+MODULE_AUTHOR("NXP");
+MODULE_DESCRIPTION("S32G OCOTP driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/parisc/eisa_eeprom.c b/drivers/parisc/eisa_eeprom.c
index 443b15422fc1..601cbb22574f 100644
--- a/drivers/parisc/eisa_eeprom.c
+++ b/drivers/parisc/eisa_eeprom.c
@@ -15,8 +15,6 @@
#include <linux/uaccess.h>
#include <asm/eisa_eeprom.h>
-#define EISA_EEPROM_MINOR 241
-
static loff_t eisa_eeprom_llseek(struct file *file, loff_t offset, int origin)
{
return fixed_size_llseek(file, offset, origin, HPEE_MAX_LENGTH);
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index d97335a40193..17315a825674 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -3829,7 +3829,7 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_MELLANOX, 0xcf80, quirk_no_pm_reset);
*/
static void quirk_thunderbolt_hotplug_msi(struct pci_dev *pdev)
{
- if (pdev->is_hotplug_bridge &&
+ if (pdev->is_pciehp &&
(pdev->device != PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C ||
pdev->revision <= 1))
pdev->no_msi = 1;
diff --git a/drivers/peci/controller/peci-npcm.c b/drivers/peci/controller/peci-npcm.c
index c77591ca583d..931868991241 100644
--- a/drivers/peci/controller/peci-npcm.c
+++ b/drivers/peci/controller/peci-npcm.c
@@ -221,7 +221,6 @@ static const struct regmap_config npcm_peci_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = NPCM_PECI_MAX_REG,
- .fast_io = true,
};
static const struct peci_controller_ops npcm_ops = {
diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig
index 06288aebc559..10f905d7d6bf 100644
--- a/drivers/platform/arm64/Kconfig
+++ b/drivers/platform/arm64/Kconfig
@@ -70,4 +70,24 @@ config EC_LENOVO_YOGA_C630
Say M or Y here to include this support.
+config EC_LENOVO_THINKPAD_T14S
+ tristate "Lenovo Thinkpad T14s Embedded Controller driver"
+ depends on ARCH_QCOM || COMPILE_TEST
+ depends on I2C
+ depends on INPUT
+ select INPUT_SPARSEKMAP
+ select LEDS_CLASS
+ select NEW_LEDS
+ select SND_CTL_LED if SND
+ help
+ Driver for the Embedded Controller in the Qualcomm Snapdragon-based
+ Lenovo Thinkpad T14s, which provides access to keyboard backlight
+ and status LEDs.
+
+ This driver provides support for the mentioned laptop where this
+ information is not properly exposed via the standard Qualcomm
+ devices.
+
+ Say M or Y here to include this support.
+
endif # ARM64_PLATFORM_DEVICES
diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile
index 46a99eba3264..60c131cff6a1 100644
--- a/drivers/platform/arm64/Makefile
+++ b/drivers/platform/arm64/Makefile
@@ -8,3 +8,4 @@
obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o
obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o
obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o
+obj-$(CONFIG_EC_LENOVO_THINKPAD_T14S) += lenovo-thinkpad-t14s.o
diff --git a/drivers/platform/arm64/lenovo-thinkpad-t14s.c b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
new file mode 100644
index 000000000000..1d5d11adaf32
--- /dev/null
+++ b/drivers/platform/arm64/lenovo-thinkpad-t14s.c
@@ -0,0 +1,616 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2025, Sebastian Reichel
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/interrupt.h>
+#include <linux/leds.h>
+#include <linux/lockdep.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define T14S_EC_CMD_ECRD 0x02
+#define T14S_EC_CMD_ECWR 0x03
+#define T14S_EC_CMD_EVT 0xf0
+
+#define T14S_EC_REG_LED 0x0c
+#define T14S_EC_REG_KBD_BL1 0x0d
+#define T14S_EC_REG_KBD_BL2 0xe1
+#define T14S_EC_KBD_BL1_MASK GENMASK_U8(7, 6)
+#define T14S_EC_KBD_BL2_MASK GENMASK_U8(3, 2)
+#define T14S_EC_REG_AUD 0x30
+#define T14S_EC_MIC_MUTE_LED BIT(5)
+#define T14S_EC_SPK_MUTE_LED BIT(6)
+
+#define T14S_EC_EVT_NONE 0x00
+#define T14S_EC_EVT_KEY_FN_4 0x13
+#define T14S_EC_EVT_KEY_FN_F7 0x16
+#define T14S_EC_EVT_KEY_FN_SPACE 0x1f
+#define T14S_EC_EVT_KEY_TP_DOUBLE_TAP 0x20
+#define T14S_EC_EVT_AC_CONNECTED 0x26
+#define T14S_EC_EVT_AC_DISCONNECTED 0x27
+#define T14S_EC_EVT_KEY_POWER 0x28
+#define T14S_EC_EVT_LID_OPEN 0x2a
+#define T14S_EC_EVT_LID_CLOSED 0x2b
+#define T14S_EC_EVT_THERMAL_TZ40 0x5c
+#define T14S_EC_EVT_THERMAL_TZ42 0x5d
+#define T14S_EC_EVT_THERMAL_TZ39 0x5e
+#define T14S_EC_EVT_KEY_FN_F12 0x62
+#define T14S_EC_EVT_KEY_FN_TAB 0x63
+#define T14S_EC_EVT_KEY_FN_F8 0x64
+#define T14S_EC_EVT_KEY_FN_F10 0x65
+#define T14S_EC_EVT_KEY_FN_F4 0x6a
+#define T14S_EC_EVT_KEY_FN_D 0x6b
+#define T14S_EC_EVT_KEY_FN_T 0x6c
+#define T14S_EC_EVT_KEY_FN_H 0x6d
+#define T14S_EC_EVT_KEY_FN_M 0x6e
+#define T14S_EC_EVT_KEY_FN_L 0x6f
+#define T14S_EC_EVT_KEY_FN_RIGHT_SHIFT 0x71
+#define T14S_EC_EVT_KEY_FN_ESC 0x74
+#define T14S_EC_EVT_KEY_FN_N 0x79
+#define T14S_EC_EVT_KEY_FN_F11 0x7a
+#define T14S_EC_EVT_KEY_FN_G 0x7e
+
+/* Hardware LED blink rate is 1 Hz (500ms off, 500ms on) */
+#define T14S_EC_BLINK_RATE_ON_OFF_MS 500
+
+/*
+ * Add a virtual offset on all key event codes for sparse keymap handling,
+ * since the sparse keymap infrastructure does not map some raw key event
+ * codes used by the EC. For example 0x16 (T14S_EC_EVT_KEY_FN_F7) is mapped
+ * to KEY_MUTE if no offset is applied.
+ */
+#define T14S_EC_KEY_EVT_OFFSET 0x1000
+#define T14S_EC_KEY_ENTRY(key, value) \
+ { KE_KEY, T14S_EC_KEY_EVT_OFFSET + T14S_EC_EVT_KEY_##key, { value } }
+
+enum t14s_ec_led_status_t {
+ T14S_EC_LED_OFF = 0x00,
+ T14S_EC_LED_ON = 0x80,
+ T14S_EC_LED_BLINK = 0xc0,
+};
+
+struct t14s_ec_led_classdev {
+ struct led_classdev led_classdev;
+ int led;
+ enum t14s_ec_led_status_t cache;
+ struct t14s_ec *ec;
+};
+
+struct t14s_ec {
+ struct regmap *regmap;
+ struct device *dev;
+ struct t14s_ec_led_classdev led_pwr_btn;
+ struct t14s_ec_led_classdev led_chrg_orange;
+ struct t14s_ec_led_classdev led_chrg_white;
+ struct t14s_ec_led_classdev led_lid_logo_dot;
+ struct led_classdev kbd_backlight;
+ struct led_classdev led_mic_mute;
+ struct led_classdev led_spk_mute;
+ struct input_dev *inputdev;
+};
+
+static const struct regmap_config t14s_ec_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xff,
+};
+
+static int t14s_ec_write(void *context, unsigned int reg,
+ unsigned int val)
+{
+ struct t14s_ec *ec = context;
+ struct i2c_client *client = to_i2c_client(ec->dev);
+ u8 buf[5] = {T14S_EC_CMD_ECWR, reg, 0x00, 0x01, val};
+ int ret;
+
+ ret = i2c_master_send(client, buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int t14s_ec_read(void *context, unsigned int reg,
+ unsigned int *val)
+{
+ struct t14s_ec *ec = context;
+ struct i2c_client *client = to_i2c_client(ec->dev);
+ u8 buf[4] = {T14S_EC_CMD_ECRD, reg, 0x00, 0x01};
+ struct i2c_msg request, response;
+ u8 result;
+ int ret;
+
+ request.addr = client->addr;
+ request.flags = I2C_M_STOP;
+ request.len = sizeof(buf);
+ request.buf = buf;
+ response.addr = client->addr;
+ response.flags = I2C_M_RD;
+ response.len = 1;
+ response.buf = &result;
+
+ i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT);
+
+ ret = __i2c_transfer(client->adapter, &request, 1);
+ if (ret < 0)
+ goto out;
+
+ ret = __i2c_transfer(client->adapter, &response, 1);
+ if (ret < 0)
+ goto out;
+
+ *val = result;
+ ret = 0;
+
+out:
+ i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT);
+ return ret;
+}
+
+static const struct regmap_bus t14s_ec_regmap_bus = {
+ .reg_write = t14s_ec_write,
+ .reg_read = t14s_ec_read,
+};
+
+static int t14s_ec_read_evt(struct t14s_ec *ec, u8 *val)
+{
+ struct i2c_client *client = to_i2c_client(ec->dev);
+ u8 buf[4] = {T14S_EC_CMD_EVT, 0x00, 0x00, 0x01};
+ struct i2c_msg request, response;
+ int ret;
+
+ request.addr = client->addr;
+ request.flags = I2C_M_STOP;
+ request.len = sizeof(buf);
+ request.buf = buf;
+ response.addr = client->addr;
+ response.flags = I2C_M_RD;
+ response.len = 1;
+ response.buf = val;
+
+ i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT);
+
+ ret = __i2c_transfer(client->adapter, &request, 1);
+ if (ret < 0)
+ goto out;
+
+ ret = __i2c_transfer(client->adapter, &response, 1);
+ if (ret < 0)
+ goto out;
+
+ ret = 0;
+
+out:
+ i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT);
+ return ret;
+}
+
+static int t14s_led_set_status(struct t14s_ec *ec,
+ struct t14s_ec_led_classdev *led,
+ const enum t14s_ec_led_status_t ledstatus)
+{
+ int ret;
+
+ ret = regmap_write(ec->regmap, T14S_EC_REG_LED,
+ led->led | ledstatus);
+ if (ret < 0)
+ return ret;
+
+ led->cache = ledstatus;
+ return 0;
+}
+
+static int t14s_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct t14s_ec_led_classdev *led = container_of(led_cdev,
+ struct t14s_ec_led_classdev, led_classdev);
+ enum t14s_ec_led_status_t new_state;
+
+ if (brightness == LED_OFF)
+ new_state = T14S_EC_LED_OFF;
+ else if (led->cache == T14S_EC_LED_BLINK)
+ new_state = T14S_EC_LED_BLINK;
+ else
+ new_state = T14S_EC_LED_ON;
+
+ return t14s_led_set_status(led->ec, led, new_state);
+}
+
+static int t14s_led_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct t14s_ec_led_classdev *led = container_of(led_cdev,
+ struct t14s_ec_led_classdev, led_classdev);
+
+ if (*delay_on == 0 && *delay_off == 0) {
+ /* Userspace does not provide a blink rate; we can choose it */
+ *delay_on = T14S_EC_BLINK_RATE_ON_OFF_MS;
+ *delay_off = T14S_EC_BLINK_RATE_ON_OFF_MS;
+ } else if ((*delay_on != T14S_EC_BLINK_RATE_ON_OFF_MS) ||
+ (*delay_off != T14S_EC_BLINK_RATE_ON_OFF_MS))
+ return -EINVAL;
+
+ return t14s_led_set_status(led->ec, led, T14S_EC_LED_BLINK);
+}
+
+static int t14s_init_led(struct t14s_ec *ec, struct t14s_ec_led_classdev *led,
+ u8 id, const char *name)
+{
+ led->led_classdev.name = name;
+ led->led_classdev.flags = LED_RETAIN_AT_SHUTDOWN;
+ led->led_classdev.max_brightness = 1;
+ led->led_classdev.brightness_set_blocking = t14s_led_brightness_set;
+ led->led_classdev.blink_set = t14s_led_blink_set;
+ led->ec = ec;
+ led->led = id;
+
+ return devm_led_classdev_register(ec->dev, &led->led_classdev);
+}
+
+static int t14s_leds_probe(struct t14s_ec *ec)
+{
+ int ret;
+
+ ret = t14s_init_led(ec, &ec->led_pwr_btn, 0, "platform::power");
+ if (ret)
+ return ret;
+
+ ret = t14s_init_led(ec, &ec->led_chrg_orange, 1,
+ "platform:amber:battery-charging");
+ if (ret)
+ return ret;
+
+ ret = t14s_init_led(ec, &ec->led_chrg_white, 2,
+ "platform:white:battery-full");
+ if (ret)
+ return ret;
+
+ ret = t14s_init_led(ec, &ec->led_lid_logo_dot, 10,
+ "platform::lid_logo_dot");
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int t14s_kbd_bl_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec,
+ kbd_backlight);
+ int ret;
+ u8 val;
+
+ val = FIELD_PREP(T14S_EC_KBD_BL1_MASK, brightness);
+ ret = regmap_update_bits(ec->regmap, T14S_EC_REG_KBD_BL1,
+ T14S_EC_KBD_BL1_MASK, val);
+ if (ret < 0)
+ return ret;
+
+ val = FIELD_PREP(T14S_EC_KBD_BL2_MASK, brightness);
+ ret = regmap_update_bits(ec->regmap, T14S_EC_REG_KBD_BL2,
+ T14S_EC_KBD_BL2_MASK, val);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static enum led_brightness t14s_kbd_bl_get(struct led_classdev *led_cdev)
+{
+ struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec,
+ kbd_backlight);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(ec->regmap, T14S_EC_REG_KBD_BL1, &val);
+ if (ret < 0)
+ return ret;
+
+ return FIELD_GET(T14S_EC_KBD_BL1_MASK, val);
+}
+
+static void t14s_kbd_bl_update(struct t14s_ec *ec)
+{
+ enum led_brightness brightness = t14s_kbd_bl_get(&ec->kbd_backlight);
+
+ led_classdev_notify_brightness_hw_changed(&ec->kbd_backlight, brightness);
+}
+
+static int t14s_kbd_backlight_probe(struct t14s_ec *ec)
+{
+ ec->kbd_backlight.name = "platform::kbd_backlight";
+ ec->kbd_backlight.flags = LED_BRIGHT_HW_CHANGED;
+ ec->kbd_backlight.max_brightness = 2;
+ ec->kbd_backlight.brightness_set_blocking = t14s_kbd_bl_set;
+ ec->kbd_backlight.brightness_get = t14s_kbd_bl_get;
+
+ return devm_led_classdev_register(ec->dev, &ec->kbd_backlight);
+}
+
+static enum led_brightness t14s_audio_led_get(struct t14s_ec *ec, u8 led_bit)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(ec->regmap, T14S_EC_REG_AUD, &val);
+ if (ret < 0)
+ return ret;
+
+ return !!(val & led_bit) ? LED_ON : LED_OFF;
+}
+
+static enum led_brightness t14s_audio_led_set(struct t14s_ec *ec,
+ u8 led_mask,
+ enum led_brightness brightness)
+{
+ return regmap_assign_bits(ec->regmap, T14S_EC_REG_AUD, led_mask, brightness > 0);
+}
+
+static enum led_brightness t14s_mic_mute_led_get(struct led_classdev *led_cdev)
+{
+ struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec,
+ led_mic_mute);
+
+ return t14s_audio_led_get(ec, T14S_EC_MIC_MUTE_LED);
+}
+
+static int t14s_mic_mute_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec,
+ led_mic_mute);
+
+ return t14s_audio_led_set(ec, T14S_EC_MIC_MUTE_LED, brightness);
+}
+
+static enum led_brightness t14s_spk_mute_led_get(struct led_classdev *led_cdev)
+{
+ struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec,
+ led_spk_mute);
+
+ return t14s_audio_led_get(ec, T14S_EC_SPK_MUTE_LED);
+}
+
+static int t14s_spk_mute_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec,
+ led_spk_mute);
+
+ return t14s_audio_led_set(ec, T14S_EC_SPK_MUTE_LED, brightness);
+}
+
+static int t14s_kbd_audio_led_probe(struct t14s_ec *ec)
+{
+ int ret;
+
+ ec->led_mic_mute.name = "platform::micmute";
+ ec->led_mic_mute.max_brightness = 1;
+ ec->led_mic_mute.default_trigger = "audio-micmute";
+ ec->led_mic_mute.brightness_set_blocking = t14s_mic_mute_led_set;
+ ec->led_mic_mute.brightness_get = t14s_mic_mute_led_get;
+
+ ec->led_spk_mute.name = "platform::mute";
+ ec->led_spk_mute.max_brightness = 1;
+ ec->led_spk_mute.default_trigger = "audio-mute";
+ ec->led_spk_mute.brightness_set_blocking = t14s_spk_mute_led_set;
+ ec->led_spk_mute.brightness_get = t14s_spk_mute_led_get;
+
+ ret = devm_led_classdev_register(ec->dev, &ec->led_mic_mute);
+ if (ret)
+ return ret;
+
+ return devm_led_classdev_register(ec->dev, &ec->led_spk_mute);
+}
+
+static const struct key_entry t14s_keymap[] = {
+ T14S_EC_KEY_ENTRY(FN_4, KEY_SLEEP),
+ T14S_EC_KEY_ENTRY(FN_N, KEY_VENDOR),
+ T14S_EC_KEY_ENTRY(FN_F4, KEY_MICMUTE),
+ T14S_EC_KEY_ENTRY(FN_F7, KEY_SWITCHVIDEOMODE),
+ T14S_EC_KEY_ENTRY(FN_F8, KEY_PERFORMANCE),
+ T14S_EC_KEY_ENTRY(FN_F10, KEY_SELECTIVE_SCREENSHOT),
+ T14S_EC_KEY_ENTRY(FN_F11, KEY_LINK_PHONE),
+ T14S_EC_KEY_ENTRY(FN_F12, KEY_BOOKMARKS),
+ T14S_EC_KEY_ENTRY(FN_SPACE, KEY_KBDILLUMTOGGLE),
+ T14S_EC_KEY_ENTRY(FN_ESC, KEY_FN_ESC),
+ T14S_EC_KEY_ENTRY(FN_TAB, KEY_ZOOM),
+ T14S_EC_KEY_ENTRY(FN_RIGHT_SHIFT, KEY_FN_RIGHT_SHIFT),
+ T14S_EC_KEY_ENTRY(TP_DOUBLE_TAP, KEY_PROG4),
+ { KE_END }
+};
+
+static int t14s_input_probe(struct t14s_ec *ec)
+{
+ int ret;
+
+ ec->inputdev = devm_input_allocate_device(ec->dev);
+ if (!ec->inputdev)
+ return -ENOMEM;
+
+ ec->inputdev->name = "ThinkPad Extra Buttons";
+ ec->inputdev->phys = "thinkpad/input0";
+ ec->inputdev->id.bustype = BUS_HOST;
+ ec->inputdev->dev.parent = ec->dev;
+
+ ret = sparse_keymap_setup(ec->inputdev, t14s_keymap, NULL);
+ if (ret)
+ return ret;
+
+ return input_register_device(ec->inputdev);
+}
+
+static irqreturn_t t14s_ec_irq_handler(int irq, void *data)
+{
+ struct t14s_ec *ec = data;
+ int ret;
+ u8 val;
+
+ ret = t14s_ec_read_evt(ec, &val);
+ if (ret < 0) {
+ dev_err(ec->dev, "Failed to read event\n");
+ return IRQ_HANDLED;
+ }
+
+ switch (val) {
+ case T14S_EC_EVT_NONE:
+ break;
+ case T14S_EC_EVT_KEY_FN_SPACE:
+ t14s_kbd_bl_update(ec);
+ fallthrough;
+ case T14S_EC_EVT_KEY_FN_F4:
+ case T14S_EC_EVT_KEY_FN_F7:
+ case T14S_EC_EVT_KEY_FN_4:
+ case T14S_EC_EVT_KEY_FN_F8:
+ case T14S_EC_EVT_KEY_FN_F12:
+ case T14S_EC_EVT_KEY_FN_TAB:
+ case T14S_EC_EVT_KEY_FN_F10:
+ case T14S_EC_EVT_KEY_FN_N:
+ case T14S_EC_EVT_KEY_FN_F11:
+ case T14S_EC_EVT_KEY_FN_ESC:
+ case T14S_EC_EVT_KEY_FN_RIGHT_SHIFT:
+ case T14S_EC_EVT_KEY_TP_DOUBLE_TAP:
+ sparse_keymap_report_event(ec->inputdev,
+ T14S_EC_KEY_EVT_OFFSET + val, 1, true);
+ break;
+ case T14S_EC_EVT_AC_CONNECTED:
+ dev_dbg(ec->dev, "AC connected\n");
+ break;
+ case T14S_EC_EVT_AC_DISCONNECTED:
+ dev_dbg(ec->dev, "AC disconnected\n");
+ break;
+ case T14S_EC_EVT_KEY_POWER:
+ dev_dbg(ec->dev, "power button\n");
+ break;
+ case T14S_EC_EVT_LID_OPEN:
+ dev_dbg(ec->dev, "LID open\n");
+ break;
+ case T14S_EC_EVT_LID_CLOSED:
+ dev_dbg(ec->dev, "LID closed\n");
+ break;
+ case T14S_EC_EVT_THERMAL_TZ40:
+ dev_dbg(ec->dev, "Thermal Zone 40 Status Change Event (CPU/GPU)\n");
+ break;
+ case T14S_EC_EVT_THERMAL_TZ42:
+ dev_dbg(ec->dev, "Thermal Zone 42 Status Change Event (Battery)\n");
+ break;
+ case T14S_EC_EVT_THERMAL_TZ39:
+ dev_dbg(ec->dev, "Thermal Zone 39 Status Change Event (CPU/GPU)\n");
+ break;
+ case T14S_EC_EVT_KEY_FN_G:
+ dev_dbg(ec->dev, "FN + G - toggle double-tapping\n");
+ break;
+ case T14S_EC_EVT_KEY_FN_L:
+ dev_dbg(ec->dev, "FN + L - low performance mode\n");
+ break;
+ case T14S_EC_EVT_KEY_FN_M:
+ dev_dbg(ec->dev, "FN + M - medium performance mode\n");
+ break;
+ case T14S_EC_EVT_KEY_FN_H:
+ dev_dbg(ec->dev, "FN + H - high performance mode\n");
+ break;
+ case T14S_EC_EVT_KEY_FN_T:
+ dev_dbg(ec->dev, "FN + T - toggle intelligent cooling mode\n");
+ break;
+ case T14S_EC_EVT_KEY_FN_D:
+ dev_dbg(ec->dev, "FN + D - toggle privacy guard mode\n");
+ break;
+ default:
+ dev_info(ec->dev, "Unknown EC event: 0x%02x\n", val);
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int t14s_ec_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct t14s_ec *ec;
+ int ret;
+
+ ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
+ if (!ec)
+ return -ENOMEM;
+
+ ec->dev = dev;
+
+ ec->regmap = devm_regmap_init(dev, &t14s_ec_regmap_bus,
+ ec, &t14s_ec_regmap_config);
+ if (IS_ERR(ec->regmap))
+ return dev_err_probe(dev, PTR_ERR(ec->regmap),
+ "Failed to init regmap\n");
+
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ t14s_ec_irq_handler,
+ IRQF_ONESHOT, dev_name(dev), ec);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to get IRQ\n");
+
+ ret = t14s_leds_probe(ec);
+ if (ret < 0)
+ return ret;
+
+ ret = t14s_kbd_backlight_probe(ec);
+ if (ret < 0)
+ return ret;
+
+ ret = t14s_kbd_audio_led_probe(ec);
+ if (ret < 0)
+ return ret;
+
+ ret = t14s_input_probe(ec);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Disable wakeup support by default, because the driver currently does
+ * not support masking any events and the laptop should not wake up when
+ * the LID is closed.
+ */
+ device_wakeup_disable(dev);
+
+ return 0;
+}
+
+static const struct of_device_id t14s_ec_of_match[] = {
+ { .compatible = "lenovo,thinkpad-t14s-ec" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, t14s_ec_of_match);
+
+static const struct i2c_device_id t14s_ec_i2c_id_table[] = {
+ { "thinkpad-t14s-ec", },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, t14s_ec_i2c_id_table);
+
+static struct i2c_driver t14s_ec_i2c_driver = {
+ .driver = {
+ .name = "thinkpad-t14s-ec",
+ .of_match_table = t14s_ec_of_match,
+ },
+ .probe = t14s_ec_probe,
+ .id_table = t14s_ec_i2c_id_table,
+};
+module_i2c_driver(t14s_ec_i2c_driver);
+
+MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
+MODULE_DESCRIPTION("Lenovo Thinkpad T14s Embedded Controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 6d238e120dce..46e62feeda3c 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -118,6 +118,18 @@ config XIAOMI_WMI
To compile this driver as a module, choose M here: the module will
be called xiaomi-wmi.
+config REDMI_WMI
+ tristate "Redmibook WMI key driver"
+ depends on ACPI_WMI
+ depends on INPUT
+ select INPUT_SPARSEKMAP
+ help
+ Say Y here if you want support for WMI-based hotkey events on
+ Xiaomi Redmibook devices.
+
+ To compile this driver as a module, choose M here: the module will
+ be called redmi-wmi.
+
config GIGABYTE_WMI
tristate "Gigabyte WMI temperature driver"
depends on ACPI_WMI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index a0c5848513e3..c7db2a88c11a 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o
obj-$(CONFIG_MXM_WMI) += mxm-wmi.o
obj-$(CONFIG_NVIDIA_WMI_EC_BACKLIGHT) += nvidia-wmi-ec-backlight.o
obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o
+obj-$(CONFIG_REDMI_WMI) += redmi-wmi.o
obj-$(CONFIG_GIGABYTE_WMI) += gigabyte-wmi.o
# Acer
diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c
index a94009203e01..d0b74d243ce4 100644
--- a/drivers/platform/x86/amd/hsmp/acpi.c
+++ b/drivers/platform/x86/amd/hsmp/acpi.c
@@ -495,12 +495,12 @@ static int init_acpi(struct device *dev)
if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) {
ret = hsmp_get_tbl_dram_base(sock_ind);
if (ret)
- dev_err(dev, "Failed to init metric table\n");
+ dev_info(dev, "Failed to init metric table\n");
}
ret = hsmp_create_sensor(dev, sock_ind);
if (ret)
- dev_err(dev, "Failed to register HSMP sensors with hwmon\n");
+ dev_info(dev, "Failed to register HSMP sensors with hwmon\n");
dev_set_drvdata(dev, &hsmp_pdev->sock[sock_ind]);
diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c
index 22f50b6235d6..e07f68575055 100644
--- a/drivers/platform/x86/amd/hsmp/plat.c
+++ b/drivers/platform/x86/amd/hsmp/plat.c
@@ -189,13 +189,13 @@ static int init_platform_device(struct device *dev)
if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) {
ret = hsmp_get_tbl_dram_base(i);
if (ret)
- dev_err(dev, "Failed to init metric table\n");
+ dev_info(dev, "Failed to init metric table\n");
}
/* Register with hwmon interface for reporting power */
ret = hsmp_create_sensor(dev, i);
if (ret)
- dev_err(dev, "Failed to register HSMP sensors with hwmon\n");
+ dev_info(dev, "Failed to register HSMP sensors with hwmon\n");
}
return 0;
diff --git a/drivers/platform/x86/amd/pmf/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c
index f75f7ecd8cd9..13c4fec2c7ef 100644
--- a/drivers/platform/x86/amd/pmf/acpi.c
+++ b/drivers/platform/x86/amd/pmf/acpi.c
@@ -161,6 +161,11 @@ int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index)
return !!(pdev->supported_func & BIT(index - 1));
}
+int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev)
+{
+ return !!(pdev->notifications & CUSTOM_BIOS_INPUT_BITS);
+}
+
int apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev,
struct amd_pmf_apts_granular_output *data, u32 apts_idx)
{
@@ -315,12 +320,26 @@ int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v
return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req));
}
+int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v1 *req)
+{
+ return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req));
+}
+
int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req)
{
return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS,
req, sizeof(*req));
}
+static void amd_pmf_handle_early_preq(struct amd_pmf_dev *pdev)
+{
+ if (!pdev->cb_flag)
+ return;
+
+ amd_pmf_invoke_cmd_enact(pdev);
+ pdev->cb_flag = false;
+}
+
static void apmf_event_handler_v2(acpi_handle handle, u32 event, void *data)
{
struct amd_pmf_dev *pmf_dev = data;
@@ -329,8 +348,32 @@ static void apmf_event_handler_v2(acpi_handle handle, u32 event, void *data)
guard(mutex)(&pmf_dev->cb_mutex);
ret = apmf_get_sbios_requests_v2(pmf_dev, &pmf_dev->req);
- if (ret)
+ if (ret) {
dev_err(pmf_dev->dev, "Failed to get v2 SBIOS requests: %d\n", ret);
+ return;
+ }
+
+ dev_dbg(pmf_dev->dev, "Pending request (preq): 0x%x\n", pmf_dev->req.pending_req);
+
+ amd_pmf_handle_early_preq(pmf_dev);
+}
+
+static void apmf_event_handler_v1(acpi_handle handle, u32 event, void *data)
+{
+ struct amd_pmf_dev *pmf_dev = data;
+ int ret;
+
+ guard(mutex)(&pmf_dev->cb_mutex);
+
+ ret = apmf_get_sbios_requests_v1(pmf_dev, &pmf_dev->req1);
+ if (ret) {
+ dev_err(pmf_dev->dev, "Failed to get v1 SBIOS requests: %d\n", ret);
+ return;
+ }
+
+ dev_dbg(pmf_dev->dev, "Pending request (preq1): 0x%x\n", pmf_dev->req1.pending_req);
+
+ amd_pmf_handle_early_preq(pmf_dev);
}
static void apmf_event_handler(acpi_handle handle, u32 event, void *data)
@@ -385,6 +428,7 @@ static int apmf_if_verify_interface(struct amd_pmf_dev *pdev)
pdev->pmf_if_version = output.version;
+ pdev->notifications = output.notification_mask;
return 0;
}
@@ -421,6 +465,11 @@ int apmf_get_dyn_slider_def_dc(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_
return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_DC, data, sizeof(*data));
}
+static apmf_event_handler_t apmf_event_handlers[] = {
+ [PMF_IF_V1] = apmf_event_handler_v1,
+ [PMF_IF_V2] = apmf_event_handler_v2,
+};
+
int apmf_install_handler(struct amd_pmf_dev *pmf_dev)
{
acpi_handle ahandle = ACPI_HANDLE(pmf_dev->dev);
@@ -440,13 +489,26 @@ int apmf_install_handler(struct amd_pmf_dev *pmf_dev)
apmf_event_handler(ahandle, 0, pmf_dev);
}
- if (pmf_dev->smart_pc_enabled && pmf_dev->pmf_if_version == PMF_IF_V2) {
+ if (!pmf_dev->smart_pc_enabled)
+ return -EINVAL;
+
+ switch (pmf_dev->pmf_if_version) {
+ case PMF_IF_V1:
+ if (!is_apmf_bios_input_notifications_supported(pmf_dev))
+ break;
+ fallthrough;
+ case PMF_IF_V2:
status = acpi_install_notify_handler(ahandle, ACPI_ALL_NOTIFY,
- apmf_event_handler_v2, pmf_dev);
+ apmf_event_handlers[pmf_dev->pmf_if_version], pmf_dev);
if (ACPI_FAILURE(status)) {
- dev_err(pmf_dev->dev, "failed to install notify handler for custom BIOS inputs\n");
+ dev_err(pmf_dev->dev,
+ "failed to install notify handler v%d for custom BIOS inputs\n",
+ pmf_dev->pmf_if_version);
return -ENODEV;
}
+ break;
+ default:
+ break;
}
return 0;
@@ -500,8 +562,21 @@ void apmf_acpi_deinit(struct amd_pmf_dev *pmf_dev)
is_apmf_func_supported(pmf_dev, APMF_FUNC_SBIOS_REQUESTS))
acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY, apmf_event_handler);
- if (pmf_dev->smart_pc_enabled && pmf_dev->pmf_if_version == PMF_IF_V2)
- acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY, apmf_event_handler_v2);
+ if (!pmf_dev->smart_pc_enabled)
+ return;
+
+ switch (pmf_dev->pmf_if_version) {
+ case PMF_IF_V1:
+ if (!is_apmf_bios_input_notifications_supported(pmf_dev))
+ break;
+ fallthrough;
+ case PMF_IF_V2:
+ acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY,
+ apmf_event_handlers[pmf_dev->pmf_if_version]);
+ break;
+ default:
+ break;
+ }
}
int apmf_acpi_init(struct amd_pmf_dev *pmf_dev)
diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h
index 45b60238d527..bd19f2a6bc78 100644
--- a/drivers/platform/x86/amd/pmf/pmf.h
+++ b/drivers/platform/x86/amd/pmf/pmf.h
@@ -93,6 +93,8 @@ struct cookie_header {
#define PMF_POLICY_BIOS_OUTPUT_1 10
#define PMF_POLICY_BIOS_OUTPUT_2 11
#define PMF_POLICY_P3T 38
+#define PMF_POLICY_PMF_PPT 54
+#define PMF_POLICY_PMF_PPT_APU_ONLY 55
#define PMF_POLICY_BIOS_OUTPUT_3 57
#define PMF_POLICY_BIOS_OUTPUT_4 58
#define PMF_POLICY_BIOS_OUTPUT_5 59
@@ -116,6 +118,9 @@ struct cookie_header {
#define PMF_IF_V2 2
#define APTS_MAX_STATES 16
+#define CUSTOM_BIOS_INPUT_BITS GENMASK(16, 7)
+
+typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data);
/* APTS PMF BIOS Interface */
struct amd_pmf_apts_output {
@@ -184,6 +189,24 @@ struct apmf_sbios_req {
u8 skin_temp_hs2;
} __packed;
+/* As per APMF spec 1.3 */
+struct apmf_sbios_req_v1 {
+ u16 size;
+ u32 pending_req;
+ u8 rsvd;
+ u8 cql_event;
+ u8 amt_event;
+ u32 fppt;
+ u32 sppt;
+ u32 sppt_apu_only;
+ u32 spl;
+ u32 stt_min_limit;
+ u8 skin_temp_apu;
+ u8 skin_temp_hs2;
+ u8 enable_cnqf;
+ u32 custom_policy[10];
+} __packed;
+
struct apmf_sbios_req_v2 {
u16 size;
u32 pending_req;
@@ -331,6 +354,10 @@ enum power_modes_v2 {
POWER_MODE_V2_MAX,
};
+struct pmf_bios_inputs_prev {
+ u32 custom_bios_inputs[10];
+};
+
struct amd_pmf_dev {
void __iomem *regbase;
void __iomem *smu_virt_addr;
@@ -375,6 +402,10 @@ struct amd_pmf_dev {
struct resource *res;
struct apmf_sbios_req_v2 req; /* To get custom bios pending request */
struct mutex cb_mutex;
+ u32 notifications;
+ struct apmf_sbios_req_v1 req1;
+ struct pmf_bios_inputs_prev cb_prev; /* To preserve custom BIOS inputs */
+ bool cb_flag; /* To handle first custom BIOS input */
};
struct apmf_sps_prop_granular_v2 {
@@ -621,14 +652,35 @@ enum ta_slider {
TA_MAX,
};
-enum apmf_smartpc_custom_bios_inputs {
- APMF_SMARTPC_CUSTOM_BIOS_INPUT1,
- APMF_SMARTPC_CUSTOM_BIOS_INPUT2,
+struct amd_pmf_pb_bitmap {
+ const char *name;
+ u32 bit_mask;
+};
+
+static const struct amd_pmf_pb_bitmap custom_bios_inputs[] __used = {
+ {"NOTIFY_CUSTOM_BIOS_INPUT1", BIT(5)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT2", BIT(6)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT3", BIT(7)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT4", BIT(8)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT5", BIT(9)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT6", BIT(10)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT7", BIT(11)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT8", BIT(12)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT9", BIT(13)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(14)},
};
-enum apmf_preq_smartpc {
- NOTIFY_CUSTOM_BIOS_INPUT1 = 5,
- NOTIFY_CUSTOM_BIOS_INPUT2,
+static const struct amd_pmf_pb_bitmap custom_bios_inputs_v1[] __used = {
+ {"NOTIFY_CUSTOM_BIOS_INPUT1", BIT(7)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT2", BIT(8)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT3", BIT(9)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT4", BIT(10)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT5", BIT(11)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT6", BIT(12)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT7", BIT(13)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT8", BIT(14)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT9", BIT(15)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(16)},
};
enum platform_type {
@@ -677,6 +729,8 @@ struct pmf_action_table {
u32 stt_skintemp_apu; /* in C */
u32 stt_skintemp_hs2; /* in C */
u32 p3t_limit; /* in mW */
+ u32 pmf_ppt; /* in mW */
+ u32 pmf_ppt_apu_only; /* in mW */
};
/* Input conditions */
@@ -686,8 +740,7 @@ struct ta_pmf_condition_info {
u32 power_slider;
u32 lid_state;
bool user_present;
- u32 bios_input1;
- u32 bios_input2;
+ u32 bios_input_1[2];
u32 monitor_count;
u32 rsvd2[2];
u32 bat_design;
@@ -711,7 +764,9 @@ struct ta_pmf_condition_info {
u32 workload_type;
u32 display_type;
u32 display_state;
- u32 rsvd5[150];
+ u32 rsvd5_1[17];
+ u32 bios_input_2[8];
+ u32 rsvd5[125];
};
struct ta_pmf_load_policy_table {
@@ -737,6 +792,7 @@ struct ta_pmf_enact_table {
struct ta_pmf_action {
u32 action_index;
u32 value;
+ u32 spl_arg;
};
/* Output actions from TA */
@@ -778,6 +834,7 @@ int apmf_os_power_slider_update(struct amd_pmf_dev *dev, u8 flag);
int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer);
int amd_pmf_notify_sbios_heartbeat_event_v2(struct amd_pmf_dev *dev, u8 flag);
u32 fixp_q88_fromint(u32 val);
+int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev);
/* SPS Layer */
int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf);
@@ -805,6 +862,7 @@ void amd_pmf_init_auto_mode(struct amd_pmf_dev *dev);
void amd_pmf_deinit_auto_mode(struct amd_pmf_dev *dev);
void amd_pmf_trans_automode(struct amd_pmf_dev *dev, int socket_power, ktime_t time_elapsed_ms);
int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req);
+int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v1 *req);
int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v2 *req);
void amd_pmf_update_2_cql(struct amd_pmf_dev *dev, bool is_cql_event);
@@ -828,5 +886,6 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq
/* Smart PC - TA interfaces */
void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
+int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev);
#endif /* PMF_H */
diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c
index 1d90f9382024..85192c7536b8 100644
--- a/drivers/platform/x86/amd/pmf/spc.c
+++ b/drivers/platform/x86/amd/pmf/spc.c
@@ -70,8 +70,22 @@ static const char *ta_slider_as_str(unsigned int state)
}
}
+static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index)
+{
+ switch (index) {
+ case 0 ... 1:
+ return in->ev_info.bios_input_1[index];
+ case 2 ... 9:
+ return in->ev_info.bios_input_2[index - 2];
+ default:
+ return 0;
+ }
+}
+
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
{
+ int i;
+
dev_dbg(dev->dev, "==== TA inputs START ====\n");
dev_dbg(dev->dev, "Slider State: %s\n", ta_slider_as_str(in->ev_info.power_slider));
dev_dbg(dev->dev, "Power Source: %s\n", amd_pmf_source_as_str(in->ev_info.power_source));
@@ -90,33 +104,81 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *
dev_dbg(dev->dev, "Platform type: %s\n", platform_type_as_str(in->ev_info.platform_type));
dev_dbg(dev->dev, "Laptop placement: %s\n",
laptop_placement_as_str(in->ev_info.device_state));
- dev_dbg(dev->dev, "Custom BIOS input1: %u\n", in->ev_info.bios_input1);
- dev_dbg(dev->dev, "Custom BIOS input2: %u\n", in->ev_info.bios_input2);
+ for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
+ dev_dbg(dev->dev, "Custom BIOS input%d: %u\n", i + 1,
+ amd_pmf_get_ta_custom_bios_inputs(in, i));
dev_dbg(dev->dev, "==== TA inputs END ====\n");
}
#else
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) {}
#endif
+/*
+ * This helper function sets the appropriate BIOS input value in the TA enact
+ * table based on the provided index. We need this approach because the custom
+ * BIOS input array is not continuous, due to the existing TA structure layout.
+ */
+static void amd_pmf_set_ta_custom_bios_input(struct ta_pmf_enact_table *in, int index, u32 value)
+{
+ switch (index) {
+ case 0 ... 1:
+ in->ev_info.bios_input_1[index] = value;
+ break;
+ case 2 ... 9:
+ in->ev_info.bios_input_2[index - 2] = value;
+ break;
+ default:
+ return;
+ }
+}
+
+static void amd_pmf_update_bios_inputs(struct amd_pmf_dev *pdev, u32 pending_req,
+ const struct amd_pmf_pb_bitmap *inputs,
+ const u32 *custom_policy, struct ta_pmf_enact_table *in)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) {
+ if (!(pending_req & inputs[i].bit_mask))
+ continue;
+ amd_pmf_set_ta_custom_bios_input(in, i, custom_policy[i]);
+ pdev->cb_prev.custom_bios_inputs[i] = custom_policy[i];
+ dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, custom_policy[i]);
+ }
+}
+
static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev,
struct ta_pmf_enact_table *in)
{
- if (!pdev->req.pending_req)
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
+ amd_pmf_set_ta_custom_bios_input(in, i, pdev->cb_prev.custom_bios_inputs[i]);
+
+ if (!(pdev->req.pending_req || pdev->req1.pending_req))
return;
- switch (pdev->req.pending_req) {
- case BIT(NOTIFY_CUSTOM_BIOS_INPUT1):
- in->ev_info.bios_input1 = pdev->req.custom_policy[APMF_SMARTPC_CUSTOM_BIOS_INPUT1];
+ if (!pdev->smart_pc_enabled)
+ return;
+
+ switch (pdev->pmf_if_version) {
+ case PMF_IF_V1:
+ if (!is_apmf_bios_input_notifications_supported(pdev))
+ return;
+ amd_pmf_update_bios_inputs(pdev, pdev->req1.pending_req, custom_bios_inputs_v1,
+ pdev->req1.custom_policy, in);
break;
- case BIT(NOTIFY_CUSTOM_BIOS_INPUT2):
- in->ev_info.bios_input2 = pdev->req.custom_policy[APMF_SMARTPC_CUSTOM_BIOS_INPUT2];
+ case PMF_IF_V2:
+ amd_pmf_update_bios_inputs(pdev, pdev->req.pending_req, custom_bios_inputs,
+ pdev->req.custom_policy, in);
break;
default:
- dev_dbg(pdev->dev, "Invalid preq for BIOS input: 0x%x\n", pdev->req.pending_req);
+ break;
}
/* Clear pending requests after handling */
memset(&pdev->req, 0, sizeof(pdev->req));
+ memset(&pdev->req1, 0, sizeof(pdev->req1));
}
static void amd_pmf_get_c0_residency(u16 *core_res, size_t size, struct ta_pmf_enact_table *in)
diff --git a/drivers/platform/x86/amd/pmf/sps.c b/drivers/platform/x86/amd/pmf/sps.c
index 49e14ca94a9e..c28f3c5744c2 100644
--- a/drivers/platform/x86/amd/pmf/sps.c
+++ b/drivers/platform/x86/amd/pmf/sps.c
@@ -283,7 +283,7 @@ int amd_pmf_set_sps_power_limits(struct amd_pmf_dev *pmf)
bool is_pprof_balanced(struct amd_pmf_dev *pmf)
{
- return (pmf->current_profile == PLATFORM_PROFILE_BALANCED) ? true : false;
+ return pmf->current_profile == PLATFORM_PROFILE_BALANCED;
}
static int amd_pmf_profile_get(struct device *dev,
diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c
index 4f626ebcb619..6e8116bef4f6 100644
--- a/drivers/platform/x86/amd/pmf/tee-if.c
+++ b/drivers/platform/x86/amd/pmf/tee-if.c
@@ -147,6 +147,22 @@ static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_
}
break;
+ case PMF_POLICY_PMF_PPT:
+ if (dev->prev_data->pmf_ppt != val) {
+ amd_pmf_send_cmd(dev, SET_PMF_PPT, false, val, NULL);
+ dev_dbg(dev->dev, "update PMF PPT: %u\n", val);
+ dev->prev_data->pmf_ppt = val;
+ }
+ break;
+
+ case PMF_POLICY_PMF_PPT_APU_ONLY:
+ if (dev->prev_data->pmf_ppt_apu_only != val) {
+ amd_pmf_send_cmd(dev, SET_PMF_PPT_APU_ONLY, false, val, NULL);
+ dev_dbg(dev->dev, "update PMF PPT APU ONLY: %u\n", val);
+ dev->prev_data->pmf_ppt_apu_only = val;
+ }
+ break;
+
case PMF_POLICY_SYSTEM_STATE:
switch (val) {
case 0:
@@ -209,7 +225,7 @@ static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_
}
}
-static int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev)
+int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev)
{
struct ta_pmf_shared_memory *ta_sm = NULL;
struct ta_pmf_enact_result *out = NULL;
@@ -561,8 +577,10 @@ int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev)
ret = amd_pmf_start_policy_engine(dev);
dev_dbg(dev->dev, "start policy engine ret: %d\n", ret);
status = ret == TA_PMF_TYPE_SUCCESS;
- if (status)
+ if (status) {
+ dev->cb_flag = true;
break;
+ }
amd_pmf_tee_deinit(dev);
}
diff --git a/drivers/platform/x86/barco-p50-gpio.c b/drivers/platform/x86/barco-p50-gpio.c
index 28012eebdb10..6f13e81f98fb 100644
--- a/drivers/platform/x86/barco-p50-gpio.c
+++ b/drivers/platform/x86/barco-p50-gpio.c
@@ -11,6 +11,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/delay.h>
+#include <linux/dev_printk.h>
#include <linux/dmi.h>
#include <linux/err.h>
#include <linux/io.h>
@@ -18,10 +19,11 @@
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
-#include <linux/gpio_keys.h>
#include <linux/gpio/driver.h>
#include <linux/gpio/machine.h>
-#include <linux/input.h>
+#include <linux/gpio/property.h>
+#include <linux/input-event-codes.h>
+#include <linux/property.h>
#define DRIVER_NAME "barco-p50-gpio"
@@ -78,44 +80,57 @@ static const char * const gpio_names[] = {
[P50_GPIO_LINE_BTN] = "identify-button",
};
-
-static struct gpiod_lookup_table p50_gpio_led_table = {
- .dev_id = "leds-gpio",
- .table = {
- GPIO_LOOKUP_IDX(DRIVER_NAME, P50_GPIO_LINE_LED, NULL, 0, GPIO_ACTIVE_HIGH),
- {}
- }
+static const struct software_node gpiochip_node = {
+ .name = DRIVER_NAME,
};
/* GPIO LEDs */
-static struct gpio_led leds[] = {
- { .name = "identify" }
+static const struct software_node gpio_leds_node = {
+ .name = "gpio-leds-identify",
};
-static struct gpio_led_platform_data leds_pdata = {
- .num_leds = ARRAY_SIZE(leds),
- .leds = leds,
+static const struct property_entry identify_led_props[] = {
+ PROPERTY_ENTRY_GPIO("gpios", &gpiochip_node, P50_GPIO_LINE_LED, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node identify_led_node = {
+ .parent = &gpio_leds_node,
+ .name = "identify",
+ .properties = identify_led_props,
};
/* GPIO keyboard */
-static struct gpio_keys_button buttons[] = {
- {
- .code = KEY_VENDOR,
- .gpio = P50_GPIO_LINE_BTN,
- .active_low = 1,
- .type = EV_KEY,
- .value = 1,
- },
+static const struct property_entry gpio_keys_props[] = {
+ PROPERTY_ENTRY_STRING("label", "identify"),
+ PROPERTY_ENTRY_U32("poll-interval", 100),
+ { }
};
-static struct gpio_keys_platform_data keys_pdata = {
- .buttons = buttons,
- .nbuttons = ARRAY_SIZE(buttons),
- .poll_interval = 100,
- .rep = 0,
- .name = "identify",
+static const struct software_node gpio_keys_node = {
+ .name = "gpio-keys-identify",
+ .properties = gpio_keys_props,
};
+static struct property_entry vendor_key_props[] = {
+ PROPERTY_ENTRY_U32("linux,code", KEY_VENDOR),
+ PROPERTY_ENTRY_GPIO("gpios", &gpiochip_node, P50_GPIO_LINE_BTN, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node vendor_key_node = {
+ .parent = &gpio_keys_node,
+ .properties = vendor_key_props,
+};
+
+static const struct software_node *p50_swnodes[] = {
+ &gpiochip_node,
+ &gpio_leds_node,
+ &identify_led_node,
+ &gpio_keys_node,
+ &vendor_key_node,
+ NULL
+};
/* low level access routines */
@@ -285,6 +300,16 @@ static int p50_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
static int p50_gpio_probe(struct platform_device *pdev)
{
+ struct platform_device_info key_info = {
+ .name = "gpio-keys-polled",
+ .id = PLATFORM_DEVID_NONE,
+ .parent = &pdev->dev,
+ };
+ struct platform_device_info led_info = {
+ .name = "leds-gpio",
+ .id = PLATFORM_DEVID_NONE,
+ .parent = &pdev->dev,
+ };
struct p50_gpio *p50;
struct resource *res;
int ret;
@@ -339,25 +364,20 @@ static int p50_gpio_probe(struct platform_device *pdev)
return ret;
}
- gpiod_add_lookup_table(&p50_gpio_led_table);
-
- p50->leds_pdev = platform_device_register_data(&pdev->dev,
- "leds-gpio", PLATFORM_DEVID_NONE, &leds_pdata, sizeof(leds_pdata));
+ ret = software_node_register_node_group(p50_swnodes);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to register software nodes");
+ led_info.fwnode = software_node_fwnode(&gpio_leds_node);
+ p50->leds_pdev = platform_device_register_full(&led_info);
if (IS_ERR(p50->leds_pdev)) {
ret = PTR_ERR(p50->leds_pdev);
dev_err(&pdev->dev, "Could not register leds-gpio: %d\n", ret);
goto err_leds;
}
- /* gpio-keys-polled uses old-style gpio interface, pass the right identifier */
- buttons[0].gpio += p50->gc.base;
-
- p50->keys_pdev =
- platform_device_register_data(&pdev->dev, "gpio-keys-polled",
- PLATFORM_DEVID_NONE,
- &keys_pdata, sizeof(keys_pdata));
-
+ key_info.fwnode = software_node_fwnode(&gpio_keys_node);
+ p50->keys_pdev = platform_device_register_full(&key_info);
if (IS_ERR(p50->keys_pdev)) {
ret = PTR_ERR(p50->keys_pdev);
dev_err(&pdev->dev, "Could not register gpio-keys-polled: %d\n", ret);
@@ -369,7 +389,7 @@ static int p50_gpio_probe(struct platform_device *pdev)
err_keys:
platform_device_unregister(p50->leds_pdev);
err_leds:
- gpiod_remove_lookup_table(&p50_gpio_led_table);
+ software_node_unregister_node_group(p50_swnodes);
return ret;
}
@@ -381,7 +401,7 @@ static void p50_gpio_remove(struct platform_device *pdev)
platform_device_unregister(p50->keys_pdev);
platform_device_unregister(p50->leds_pdev);
- gpiod_remove_lookup_table(&p50_gpio_led_table);
+ software_node_unregister_node_group(p50_swnodes);
}
static struct platform_driver p50_gpio_driver = {
diff --git a/drivers/platform/x86/dell/dell_rbu.c b/drivers/platform/x86/dell/dell_rbu.c
index 2a140d1c656a..403df9bd9522 100644
--- a/drivers/platform/x86/dell/dell_rbu.c
+++ b/drivers/platform/x86/dell/dell_rbu.c
@@ -232,7 +232,8 @@ static int packetize_data(const u8 *data, size_t length)
done = 1;
}
- if ((rc = create_packet(temp, packet_length)))
+ rc = create_packet(temp, packet_length);
+ if (rc)
return rc;
pr_debug("%p:%td\n", temp, (end - temp));
@@ -276,7 +277,7 @@ static int do_packet_read(char *data, struct packet_data *newpacket,
return bytes_copied;
}
-static int packet_read_list(char *data, size_t * pread_length)
+static int packet_read_list(char *data, size_t *pread_length)
{
struct packet_data *newpacket;
int temp_count = 0;
@@ -445,7 +446,8 @@ static ssize_t read_packet_data(char *buffer, loff_t pos, size_t count)
bytes_left = rbu_data.imagesize - pos;
data_length = min(bytes_left, count);
- if ((retval = packet_read_list(ptempBuf, &data_length)) < 0)
+ retval = packet_read_list(ptempBuf, &data_length);
+ if (retval < 0)
goto read_rbu_data_exit;
if ((pos + count) > rbu_data.imagesize) {
diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c
index bdfb8a800c54..1505fc3ef7a8 100644
--- a/drivers/platform/x86/intel/int3472/discrete.c
+++ b/drivers/platform/x86/intel/int3472/discrete.c
@@ -129,6 +129,7 @@ skl_int3472_gpiod_get_from_temp_lookup(struct int3472_discrete_device *int3472,
* @hid: The ACPI HID of the device without the instance number e.g. INT347E
* @type_from: The GPIO type from ACPI ?SDT
* @type_to: The assigned GPIO type, typically same as @type_from
+ * @enable_time_us: Enable time in usec for GPIOs mapped to regulators
* @con_id: The name of the GPIO for the device
* @polarity_low: GPIO_ACTIVE_LOW true if the @polarity_low is true,
* GPIO_ACTIVE_HIGH otherwise
@@ -138,18 +139,36 @@ struct int3472_gpio_map {
u8 type_from;
u8 type_to;
bool polarity_low;
+ unsigned int enable_time_us;
const char *con_id;
};
static const struct int3472_gpio_map int3472_gpio_map[] = {
- /* mt9m114 designs declare a powerdown pin which controls the regulators */
- { "INT33F0", INT3472_GPIO_TYPE_POWERDOWN, INT3472_GPIO_TYPE_POWER_ENABLE, false, "vdd" },
- /* ov7251 driver / DT-bindings expect "enable" as con_id for reset */
- { "INT347E", INT3472_GPIO_TYPE_RESET, INT3472_GPIO_TYPE_RESET, false, "enable" },
+ { /* mt9m114 designs declare a powerdown pin which controls the regulators */
+ .hid = "INT33F0",
+ .type_from = INT3472_GPIO_TYPE_POWERDOWN,
+ .type_to = INT3472_GPIO_TYPE_POWER_ENABLE,
+ .con_id = "vdd",
+ .enable_time_us = GPIO_REGULATOR_ENABLE_TIME,
+ },
+ { /* ov7251 driver / DT-bindings expect "enable" as con_id for reset */
+ .hid = "INT347E",
+ .type_from = INT3472_GPIO_TYPE_RESET,
+ .type_to = INT3472_GPIO_TYPE_RESET,
+ .con_id = "enable",
+ },
+ { /* ov08x40's handshake pin needs a 45 ms delay on some HP laptops */
+ .hid = "OVTI08F4",
+ .type_from = INT3472_GPIO_TYPE_HANDSHAKE,
+ .type_to = INT3472_GPIO_TYPE_HANDSHAKE,
+ .con_id = "dvdd",
+ .enable_time_us = 45 * USEC_PER_MSEC,
+ },
};
static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3472, u8 *type,
- const char **con_id, unsigned long *gpio_flags)
+ const char **con_id, unsigned long *gpio_flags,
+ unsigned int *enable_time_us)
{
struct acpi_device *adev = int3472->sensor;
unsigned int i;
@@ -173,9 +192,12 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3
*gpio_flags = int3472_gpio_map[i].polarity_low ?
GPIO_ACTIVE_LOW : GPIO_ACTIVE_HIGH;
*con_id = int3472_gpio_map[i].con_id;
+ *enable_time_us = int3472_gpio_map[i].enable_time_us;
return;
}
+ *enable_time_us = GPIO_REGULATOR_ENABLE_TIME;
+
switch (*type) {
case INT3472_GPIO_TYPE_RESET:
*con_id = "reset";
@@ -204,6 +226,8 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3
case INT3472_GPIO_TYPE_HANDSHAKE:
*con_id = "dvdd";
*gpio_flags = GPIO_ACTIVE_HIGH;
+ /* Setups using a handshake pin need 25 ms enable delay */
+ *enable_time_us = 25 * USEC_PER_MSEC;
break;
default:
*con_id = "unknown";
@@ -249,13 +273,15 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
void *data)
{
struct int3472_discrete_device *int3472 = data;
+ const char *second_sensor = NULL;
struct acpi_resource_gpio *agpio;
+ unsigned int enable_time_us;
u8 active_value, pin, type;
+ unsigned long gpio_flags;
union acpi_object *obj;
struct gpio_desc *gpio;
const char *err_msg;
const char *con_id;
- unsigned long gpio_flags;
int ret;
if (!acpi_gpio_get_io_resource(ares, &agpio))
@@ -278,7 +304,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
type = FIELD_GET(INT3472_GPIO_DSM_TYPE, obj->integer.value);
- int3472_get_con_id_and_polarity(int3472, &type, &con_id, &gpio_flags);
+ int3472_get_con_id_and_polarity(int3472, &type, &con_id, &gpio_flags, &enable_time_us);
pin = FIELD_GET(INT3472_GPIO_DSM_PIN, obj->integer.value);
/* Pin field is not really used under Windows and wraps around at 8 bits */
@@ -328,21 +354,13 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
break;
case INT3472_GPIO_TYPE_POWER_ENABLE:
- ret = skl_int3472_register_regulator(int3472, gpio,
- GPIO_REGULATOR_ENABLE_TIME,
- con_id,
- int3472->quirks.avdd_second_sensor);
- if (ret)
- err_msg = "Failed to map power-enable to sensor\n";
-
- break;
+ second_sensor = int3472->quirks.avdd_second_sensor;
+ fallthrough;
case INT3472_GPIO_TYPE_HANDSHAKE:
- /* Setups using a handshake pin need 25 ms enable delay */
- ret = skl_int3472_register_regulator(int3472, gpio,
- 25 * USEC_PER_MSEC,
- con_id, NULL);
+ ret = skl_int3472_register_regulator(int3472, gpio, enable_time_us,
+ con_id, second_sensor);
if (ret)
- err_msg = "Failed to map handshake to sensor\n";
+ err_msg = "Failed to register regulator\n";
break;
default: /* Never reached */
diff --git a/drivers/platform/x86/intel/pmc/Makefile b/drivers/platform/x86/intel/pmc/Makefile
index 5f68c8503a56..bb960c8721d7 100644
--- a/drivers/platform/x86/intel/pmc/Makefile
+++ b/drivers/platform/x86/intel/pmc/Makefile
@@ -4,7 +4,7 @@
#
intel_pmc_core-y := core.o spt.o cnp.o icl.o \
- tgl.o adl.o mtl.o arl.o lnl.o ptl.o
+ tgl.o adl.o mtl.o arl.o lnl.o ptl.o wcl.o
obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o
intel_pmc_core_pltdrv-y := pltdrv.o
obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core_pltdrv.o
diff --git a/drivers/platform/x86/intel/pmc/arl.c b/drivers/platform/x86/intel/pmc/arl.c
index 9d66d65e7577..17ad87b392ab 100644
--- a/drivers/platform/x86/intel/pmc/arl.c
+++ b/drivers/platform/x86/intel/pmc/arl.c
@@ -725,9 +725,11 @@ struct pmc_dev_info arl_pmc_dev = {
.dmu_guid = ARL_PMT_DMU_GUID,
.regmap_list = arl_pmc_info_list,
.map = &arl_socs_reg_map,
+ .sub_req_show = &pmc_core_substate_req_regs_fops,
.suspend = cnl_suspend,
.resume = arl_resume,
.init = arl_core_init,
+ .sub_req = pmc_core_pmt_get_lpm_req,
};
struct pmc_dev_info arl_h_pmc_dev = {
@@ -735,7 +737,9 @@ struct pmc_dev_info arl_h_pmc_dev = {
.dmu_guid = ARL_PMT_DMU_GUID,
.regmap_list = arl_pmc_info_list,
.map = &mtl_socm_reg_map,
+ .sub_req_show = &pmc_core_substate_req_regs_fops,
.suspend = cnl_suspend,
.resume = arl_h_resume,
.init = arl_h_core_init,
+ .sub_req = pmc_core_pmt_get_lpm_req,
};
diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
index d040290e80ff..ac3d19ae8c56 100644
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -11,6 +11,11 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+enum header_type {
+ HEADER_STATUS,
+ HEADER_VALUE,
+};
+
#include <linux/bitfield.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
@@ -828,19 +833,86 @@ static int pmc_core_substate_l_sts_regs_show(struct seq_file *s, void *unused)
}
DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_l_sts_regs);
-static void pmc_core_substate_req_header_show(struct seq_file *s, int pmc_index)
+static void pmc_core_substate_req_header_show(struct seq_file *s, int pmc_index,
+ enum header_type type)
{
struct pmc_dev *pmcdev = s->private;
int mode;
- seq_printf(s, "%30s |", "Element");
+ seq_printf(s, "%40s |", "Element");
pmc_for_each_mode(mode, pmcdev)
seq_printf(s, " %9s |", pmc_lpm_modes[mode]);
- seq_printf(s, " %9s |", "Status");
- seq_printf(s, " %11s |\n", "Live Status");
+ if (type == HEADER_STATUS) {
+ seq_printf(s, " %9s |", "Status");
+ seq_printf(s, " %11s |\n", "Live Status");
+ } else {
+ seq_printf(s, " %9s |\n", "Value");
+ }
}
+static int pmc_core_substate_blk_req_show(struct seq_file *s, void *unused)
+{
+ struct pmc_dev *pmcdev = s->private;
+ unsigned int pmc_idx;
+
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); pmc_idx++) {
+ const struct pmc_bit_map **maps;
+ unsigned int arr_size, r_idx;
+ u32 offset, counter;
+ u32 *lpm_req_regs;
+ struct pmc *pmc;
+
+ pmc = pmcdev->pmcs[pmc_idx];
+ if (!pmc || !pmc->lpm_req_regs)
+ continue;
+
+ lpm_req_regs = pmc->lpm_req_regs;
+ maps = pmc->map->s0ix_blocker_maps;
+ offset = pmc->map->s0ix_blocker_offset;
+ arr_size = pmc_core_lpm_get_arr_size(maps);
+
+ /* Display the header */
+ pmc_core_substate_req_header_show(s, pmc_idx, HEADER_VALUE);
+
+ for (r_idx = 0; r_idx < arr_size; r_idx++) {
+ const struct pmc_bit_map *map;
+
+ for (map = maps[r_idx]; map->name; map++) {
+ int mode;
+
+ if (!map->blk)
+ continue;
+
+ counter = pmc_core_reg_read(pmc, offset);
+ seq_printf(s, "pmc%u: %34s |", pmc_idx, map->name);
+ pmc_for_each_mode(mode, pmcdev) {
+ bool required = *lpm_req_regs & BIT(mode);
+
+ seq_printf(s, " %9s |", required ? "Required" : " ");
+ }
+ seq_printf(s, " %9u |\n", counter);
+ offset += map->blk * S0IX_BLK_SIZE;
+ lpm_req_regs++;
+ }
+ }
+ }
+ return 0;
+}
+
+static int pmc_core_substate_blk_req_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, pmc_core_substate_blk_req_show, inode->i_private);
+}
+
+const struct file_operations pmc_core_substate_blk_req_fops = {
+ .owner = THIS_MODULE,
+ .open = pmc_core_substate_blk_req_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
@@ -872,7 +944,7 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
continue;
/* Display the header */
- pmc_core_substate_req_header_show(s, pmc_index);
+ pmc_core_substate_req_header_show(s, pmc_index, HEADER_STATUS);
/* Loop over maps */
for (mp = 0; mp < num_maps; mp++) {
@@ -910,7 +982,7 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
}
/* Display the element name in the first column */
- seq_printf(s, "pmc%d: %26s |", pmc_index, map[i].name);
+ seq_printf(s, "pmc%d: %34s |", pmc_index, map[i].name);
/* Loop over the enabled states and display if required */
pmc_for_each_mode(mode, pmcdev) {
@@ -931,7 +1003,19 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
}
return 0;
}
-DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_req_regs);
+
+static int pmc_core_substate_req_regs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, pmc_core_substate_req_regs_show, inode->i_private);
+}
+
+const struct file_operations pmc_core_substate_req_regs_fops = {
+ .owner = THIS_MODULE,
+ .open = pmc_core_substate_req_regs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
static unsigned int pmc_core_get_crystal_freq(void)
{
@@ -1160,7 +1244,7 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
for (mode = 0; mode < LPM_MAX_NUM_MODES; mode++)
pri_order[mode_order[mode]] = mode;
else
- dev_warn(&pmcdev->pdev->dev,
+ dev_dbg(&pmcdev->pdev->dev,
"Assuming a default substate order for this platform\n");
/*
@@ -1264,7 +1348,7 @@ static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev)
debugfs_remove_recursive(pmcdev->dbgfs_dir);
}
-static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
+static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
{
struct pmc *primary_pmc = pmcdev->pmcs[PMC_IDX_MAIN];
struct dentry *dir;
@@ -1331,7 +1415,7 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
if (primary_pmc->lpm_req_regs) {
debugfs_create_file("substate_requirements", 0444,
pmcdev->dbgfs_dir, pmcdev,
- &pmc_core_substate_req_regs_fops);
+ pmc_dev_info->sub_req_show);
}
if (primary_pmc->map->pson_residency_offset && pmc_core_is_pson_residency_enabled(pmcdev)) {
@@ -1399,36 +1483,22 @@ static u32 pmc_core_find_guid(struct pmc_info *list, const struct pmc_reg_map *m
* +----+---------------------------------------------------------+
*
*/
-static int pmc_core_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct pci_dev *pcidev)
+int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct telem_endpoint *ep)
{
- struct telem_endpoint *ep;
const u8 *lpm_indices;
int num_maps, mode_offset = 0;
int ret, mode;
int lpm_size;
- u32 guid;
lpm_indices = pmc->map->lpm_reg_index;
num_maps = pmc->map->lpm_num_maps;
lpm_size = LPM_MAX_NUM_MODES * num_maps;
- guid = pmc_core_find_guid(pmcdev->regmap_list, pmc->map);
- if (!guid)
- return -ENXIO;
-
- ep = pmt_telem_find_and_register_endpoint(pcidev, guid, 0);
- if (IS_ERR(ep)) {
- dev_dbg(&pmcdev->pdev->dev, "couldn't get telem endpoint %pe", ep);
- return -EPROBE_DEFER;
- }
-
pmc->lpm_req_regs = devm_kzalloc(&pmcdev->pdev->dev,
lpm_size * sizeof(u32),
GFP_KERNEL);
- if (!pmc->lpm_req_regs) {
- ret = -ENOMEM;
- goto unregister_ep;
- }
+ if (!pmc->lpm_req_regs)
+ return -ENOMEM;
mode_offset = LPM_HEADER_OFFSET + LPM_MODE_OFFSET;
pmc_for_each_mode(mode, pmcdev) {
@@ -1442,34 +1512,74 @@ static int pmc_core_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct
if (ret) {
dev_err(&pmcdev->pdev->dev,
"couldn't read Low Power Mode requirements: %d\n", ret);
- goto unregister_ep;
+ return ret;
}
++req_offset;
}
mode_offset += LPM_REG_COUNT + LPM_MODE_OFFSET;
}
+ return ret;
+}
+
+int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc,
+ struct telem_endpoint *ep)
+{
+ u32 num_blocker, sample_offset;
+ unsigned int index;
+ u32 *req_offset;
+ int ret;
-unregister_ep:
- pmt_telem_unregister_endpoint(ep);
+ num_blocker = pmc->map->num_s0ix_blocker;
+ sample_offset = pmc->map->blocker_req_offset;
- return ret;
+ pmc->lpm_req_regs = devm_kcalloc(&pmcdev->pdev->dev, num_blocker,
+ sizeof(u32), GFP_KERNEL);
+ if (!pmc->lpm_req_regs)
+ return -ENOMEM;
+
+ req_offset = pmc->lpm_req_regs;
+ for (index = 0; index < num_blocker; index++, req_offset++) {
+ ret = pmt_telem_read32(ep, index + sample_offset, req_offset, 1);
+ if (ret) {
+ dev_err(&pmcdev->pdev->dev,
+ "couldn't read Low Power Mode requirements: %d\n", ret);
+ return ret;
+ }
+ }
+ return 0;
}
-static int pmc_core_ssram_get_lpm_reqs(struct pmc_dev *pmcdev, int func)
+static int pmc_core_get_telem_info(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
{
struct pci_dev *pcidev __free(pci_dev_put) = NULL;
+ struct telem_endpoint *ep;
unsigned int i;
+ u32 guid;
int ret;
- pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(20, func));
+ pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(20, pmc_dev_info->pci_func));
if (!pcidev)
return -ENODEV;
for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
- if (!pmcdev->pmcs[i])
+ struct pmc *pmc;
+
+ pmc = pmcdev->pmcs[i];
+ if (!pmc)
continue;
- ret = pmc_core_get_lpm_req(pmcdev, pmcdev->pmcs[i], pcidev);
+ guid = pmc_core_find_guid(pmcdev->regmap_list, pmc->map);
+ if (!guid)
+ return -ENXIO;
+
+ ep = pmt_telem_find_and_register_endpoint(pcidev, guid, 0);
+ if (IS_ERR(ep)) {
+ dev_dbg(&pmcdev->pdev->dev, "couldn't get telem endpoint %pe", ep);
+ return -EPROBE_DEFER;
+ }
+
+ ret = pmc_dev_info->sub_req(pmcdev, pmc, ep);
+ pmt_telem_unregister_endpoint(ep);
if (ret)
return ret;
}
@@ -1583,7 +1693,7 @@ int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
pmc_core_punit_pmt_init(pmcdev, pmc_dev_info->dmu_guid);
if (ssram) {
- ret = pmc_core_ssram_get_lpm_reqs(pmcdev, pmc_dev_info->pci_func);
+ ret = pmc_core_get_telem_info(pmcdev, pmc_dev_info);
if (ret)
goto unmap_regbase;
}
@@ -1632,6 +1742,7 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = {
X86_MATCH_VFM(INTEL_ARROWLAKE_U, &arl_h_pmc_dev),
X86_MATCH_VFM(INTEL_LUNARLAKE_M, &lnl_pmc_dev),
X86_MATCH_VFM(INTEL_PANTHERLAKE_L, &ptl_pmc_dev),
+ X86_MATCH_VFM(INTEL_WILDCATLAKE_L, &wcl_pmc_dev),
{}
};
@@ -1758,7 +1869,7 @@ static int pmc_core_probe(struct platform_device *pdev)
pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(primary_pmc);
pmc_core_do_dmi_quirks(primary_pmc);
- pmc_core_dbgfs_register(pmcdev);
+ pmc_core_dbgfs_register(pmcdev, pmc_dev_info);
pm_report_max_hw_sleep(FIELD_MAX(SLP_S0_RES_COUNTER_MASK) *
pmc_core_adjust_slp_s0_step(primary_pmc, 1));
diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h
index 4a94a4ee031e..f4dadb696a31 100644
--- a/drivers/platform/x86/intel/pmc/core.h
+++ b/drivers/platform/x86/intel/pmc/core.h
@@ -297,6 +297,12 @@ enum ppfear_regs {
#define PTL_PMC_LTR_CUR_ASLT 0x1C28
#define PTL_PMC_LTR_CUR_PLT 0x1C2C
#define PTL_PCD_PMC_MMIO_REG_LEN 0x31A8
+#define PTL_NUM_S0IX_BLOCKER 106
+#define PTL_BLK_REQ_OFFSET 55
+
+/* Wildcat Lake */
+#define WCL_PMC_LTR_RESERVED 0x1B64
+#define WCL_PCD_PMC_MMIO_REG_LEN 0x3178
/* SSRAM PMC Device ID */
/* LNL */
@@ -306,6 +312,9 @@ enum ppfear_regs {
#define PMC_DEVID_PTL_PCDH 0xe37f
#define PMC_DEVID_PTL_PCDP 0xe47f
+/* WCL */
+#define PMC_DEVID_WCL_PCDN 0x4d7f
+
/* ARL */
#define PMC_DEVID_ARL_SOCM 0x777f
#define PMC_DEVID_ARL_SOCS 0xae7f
@@ -344,6 +353,8 @@ struct pmc_bit_map {
* @pm_read_disable_bit: Bit index to read PMC_READ_DISABLE
* @slps0_dbg_offset: PWRMBASE offset to SLP_S0_DEBUG_REG*
* @s0ix_blocker_offset PWRMBASE offset to S0ix blocker counter
+ * @num_s0ix_blocker: Number of S0ix blockers
+ * @blocker_req_offset: Telemetry offset to S0ix blocker low power mode substate requirement table
*
* Each PCH has unique set of register offsets and bit indexes. This structure
* captures them to have a common implementation.
@@ -369,6 +380,8 @@ struct pmc_reg_map {
const u32 ltr_ignore_max;
const u32 pm_vric1_offset;
const u32 s0ix_blocker_offset;
+ const u32 num_s0ix_blocker;
+ const u32 blocker_req_offset;
/* Low Power Mode registers */
const int lpm_num_maps;
const int lpm_num_modes;
@@ -474,18 +487,22 @@ enum pmc_index {
* SSRAM support.
* @map: Pointer to a pmc_reg_map struct that contains platform
* specific attributes of the primary PMC
+ * @sub_req_show: File operations to show substate requirements
* @suspend: Function to perform platform specific suspend
* @resume: Function to perform platform specific resume
* @init: Function to perform platform specific init action
+ * @sub_req: Function to achieve low power mode substate requirements
*/
struct pmc_dev_info {
u8 pci_func;
u32 dmu_guid;
struct pmc_info *regmap_list;
const struct pmc_reg_map *map;
+ const struct file_operations *sub_req_show;
void (*suspend)(struct pmc_dev *pmcdev);
int (*resume)(struct pmc_dev *pmcdev);
int (*init)(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info);
+ int (*sub_req)(struct pmc_dev *pmcdev, struct pmc *pmc, struct telem_endpoint *ep);
};
extern const struct pmc_bit_map msr_map[];
@@ -505,6 +522,9 @@ extern const struct pmc_bit_map mtl_socm_vnn_misc_status_map[];
extern const struct pmc_bit_map mtl_socm_signal_status_map[];
extern const struct pmc_reg_map mtl_socm_reg_map;
extern const struct pmc_reg_map mtl_ioep_reg_map;
+extern const struct pmc_bit_map ptl_pcdp_clocksource_status_map[];
+extern const struct pmc_bit_map ptl_pcdp_vnn_req_status_3_map[];
+extern const struct pmc_bit_map ptl_pcdp_signal_status_map[];
void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev);
int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore);
@@ -528,9 +548,16 @@ extern struct pmc_dev_info arl_pmc_dev;
extern struct pmc_dev_info arl_h_pmc_dev;
extern struct pmc_dev_info lnl_pmc_dev;
extern struct pmc_dev_info ptl_pmc_dev;
+extern struct pmc_dev_info wcl_pmc_dev;
void cnl_suspend(struct pmc_dev *pmcdev);
int cnl_resume(struct pmc_dev *pmcdev);
+int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct telem_endpoint *ep);
+int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc,
+ struct telem_endpoint *ep);
+
+extern const struct file_operations pmc_core_substate_req_regs_fops;
+extern const struct file_operations pmc_core_substate_blk_req_fops;
#define pmc_for_each_mode(mode, pmcdev) \
for (unsigned int __i = 0, __cond; \
diff --git a/drivers/platform/x86/intel/pmc/lnl.c b/drivers/platform/x86/intel/pmc/lnl.c
index da513c234714..6fa027e7071f 100644
--- a/drivers/platform/x86/intel/pmc/lnl.c
+++ b/drivers/platform/x86/intel/pmc/lnl.c
@@ -13,6 +13,10 @@
#include "core.h"
+#define SOCM_LPM_REQ_GUID 0x15099748
+
+static const u8 LNL_LPM_REG_INDEX[] = {0, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20};
+
static const struct pmc_bit_map lnl_ltr_show_map[] = {
{"SOUTHPORT_A", CNP_PMC_LTR_SPA},
{"SOUTHPORT_B", CNP_PMC_LTR_SPB},
@@ -528,6 +532,16 @@ static const struct pmc_reg_map lnl_socm_reg_map = {
.lpm_live_status_offset = MTL_LPM_LIVE_STATUS_OFFSET,
.s0ix_blocker_maps = lnl_blk_maps,
.s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET,
+ .lpm_reg_index = LNL_LPM_REG_INDEX,
+};
+
+static struct pmc_info lnl_pmc_info_list[] = {
+ {
+ .guid = SOCM_LPM_REQ_GUID,
+ .devid = PMC_DEVID_LNL_SOCM,
+ .map = &lnl_socm_reg_map,
+ },
+ {}
};
#define LNL_NPU_PCI_DEV 0x643e
@@ -557,8 +571,12 @@ static int lnl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
}
struct pmc_dev_info lnl_pmc_dev = {
+ .pci_func = 2,
+ .regmap_list = lnl_pmc_info_list,
.map = &lnl_socm_reg_map,
+ .sub_req_show = &pmc_core_substate_req_regs_fops,
.suspend = cnl_suspend,
.resume = lnl_resume,
.init = lnl_core_init,
+ .sub_req = pmc_core_pmt_get_lpm_req,
};
diff --git a/drivers/platform/x86/intel/pmc/mtl.c b/drivers/platform/x86/intel/pmc/mtl.c
index faa13a7ee688..0b87e10f864e 100644
--- a/drivers/platform/x86/intel/pmc/mtl.c
+++ b/drivers/platform/x86/intel/pmc/mtl.c
@@ -997,7 +997,9 @@ struct pmc_dev_info mtl_pmc_dev = {
.dmu_guid = MTL_PMT_DMU_GUID,
.regmap_list = mtl_pmc_info_list,
.map = &mtl_socm_reg_map,
+ .sub_req_show = &pmc_core_substate_req_regs_fops,
.suspend = cnl_suspend,
.resume = mtl_resume,
.init = mtl_core_init,
+ .sub_req = pmc_core_pmt_get_lpm_req,
};
diff --git a/drivers/platform/x86/intel/pmc/ptl.c b/drivers/platform/x86/intel/pmc/ptl.c
index 394515af60d6..1b35b84e06fa 100644
--- a/drivers/platform/x86/intel/pmc/ptl.c
+++ b/drivers/platform/x86/intel/pmc/ptl.c
@@ -10,6 +10,17 @@
#include "core.h"
+/* PMC SSRAM PMT Telemetry GUIDS */
+#define PCDP_LPM_REQ_GUID 0x47179370
+
+/*
+ * Die Mapping to Product.
+ * Product PCDDie
+ * PTL-H PCD-H
+ * PTL-P PCD-P
+ * PTL-U PCD-P
+ */
+
static const struct pmc_bit_map ptl_pcdp_pfear_map[] = {
{"PMC_0", BIT(0)},
{"FUSE_OSSE", BIT(1)},
@@ -162,7 +173,7 @@ static const struct pmc_bit_map ptl_pcdp_ltr_show_map[] = {
{}
};
-static const struct pmc_bit_map ptl_pcdp_clocksource_status_map[] = {
+const struct pmc_bit_map ptl_pcdp_clocksource_status_map[] = {
{"AON2_OFF_STS", BIT(0), 1},
{"AON3_OFF_STS", BIT(1), 0},
{"AON4_OFF_STS", BIT(2), 1},
@@ -382,7 +393,7 @@ static const struct pmc_bit_map ptl_pcdp_vnn_req_status_2_map[] = {
{}
};
-static const struct pmc_bit_map ptl_pcdp_vnn_req_status_3_map[] = {
+const struct pmc_bit_map ptl_pcdp_vnn_req_status_3_map[] = {
{"DTS0_VNN_REQ_STS", BIT(7), 0},
{"GPIOCOM5_VNN_REQ_STS", BIT(11), 1},
{}
@@ -421,7 +432,7 @@ static const struct pmc_bit_map ptl_pcdp_vnn_misc_status_map[] = {
{}
};
-static const struct pmc_bit_map ptl_pcdp_signal_status_map[] = {
+const struct pmc_bit_map ptl_pcdp_signal_status_map[] = {
{"LSX_Wake0_STS", BIT(0), 0},
{"LSX_Wake1_STS", BIT(1), 0},
{"LSX_Wake2_STS", BIT(2), 0},
@@ -515,6 +526,22 @@ static const struct pmc_reg_map ptl_pcdp_reg_map = {
.lpm_live_status_offset = MTL_LPM_LIVE_STATUS_OFFSET,
.s0ix_blocker_maps = ptl_pcdp_blk_maps,
.s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET,
+ .num_s0ix_blocker = PTL_NUM_S0IX_BLOCKER,
+ .blocker_req_offset = PTL_BLK_REQ_OFFSET,
+};
+
+static struct pmc_info ptl_pmc_info_list[] = {
+ {
+ .guid = PCDP_LPM_REQ_GUID,
+ .devid = PMC_DEVID_PTL_PCDH,
+ .map = &ptl_pcdp_reg_map,
+ },
+ {
+ .guid = PCDP_LPM_REQ_GUID,
+ .devid = PMC_DEVID_PTL_PCDP,
+ .map = &ptl_pcdp_reg_map,
+ },
+ {}
};
#define PTL_NPU_PCI_DEV 0xb03e
@@ -543,8 +570,12 @@ static int ptl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
}
struct pmc_dev_info ptl_pmc_dev = {
+ .pci_func = 2,
+ .regmap_list = ptl_pmc_info_list,
.map = &ptl_pcdp_reg_map,
+ .sub_req_show = &pmc_core_substate_blk_req_fops,
.suspend = cnl_suspend,
.resume = ptl_resume,
.init = ptl_core_init,
+ .sub_req = pmc_core_pmt_get_blk_sub_req,
};
diff --git a/drivers/platform/x86/intel/pmc/ssram_telemetry.c b/drivers/platform/x86/intel/pmc/ssram_telemetry.c
index 93579152188e..03fad9331fc0 100644
--- a/drivers/platform/x86/intel/pmc/ssram_telemetry.c
+++ b/drivers/platform/x86/intel/pmc/ssram_telemetry.c
@@ -190,6 +190,7 @@ static const struct pci_device_id intel_pmc_ssram_telemetry_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_LNL_SOCM) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDH) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDP) },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_WCL_PCDN) },
{ }
};
MODULE_DEVICE_TABLE(pci, intel_pmc_ssram_telemetry_pci_ids);
diff --git a/drivers/platform/x86/intel/pmc/tgl.c b/drivers/platform/x86/intel/pmc/tgl.c
index 02e731ed3391..fc5b4cacc1c6 100644
--- a/drivers/platform/x86/intel/pmc/tgl.c
+++ b/drivers/platform/x86/intel/pmc/tgl.c
@@ -273,8 +273,8 @@ void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev)
addr = (u32 *)out_obj->buffer.pointer;
- lpm_req_regs = devm_kzalloc(&pdev->dev, lpm_size * sizeof(u32),
- GFP_KERNEL);
+ lpm_req_regs = devm_kcalloc(&pdev->dev, lpm_size, sizeof(u32),
+ GFP_KERNEL);
if (!lpm_req_regs)
goto free_acpi_obj;
diff --git a/drivers/platform/x86/intel/pmc/wcl.c b/drivers/platform/x86/intel/pmc/wcl.c
new file mode 100644
index 000000000000..85e90a639e65
--- /dev/null
+++ b/drivers/platform/x86/intel/pmc/wcl.c
@@ -0,0 +1,486 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file contains platform specific structure definitions
+ * and init function used by Wildcat Lake PCH.
+ *
+ * Copyright (c) 2025, Intel Corporation.
+ */
+
+#include <linux/bits.h>
+#include <linux/pci.h>
+
+#include "core.h"
+
+static const struct pmc_bit_map wcl_pcdn_pfear_map[] = {
+ {"PMC_0", BIT(0)},
+ {"FUSE_OSSE", BIT(1)},
+ {"ESPISPI", BIT(2)},
+ {"XHCI", BIT(3)},
+ {"SPA", BIT(4)},
+ {"RSVD", BIT(5)},
+ {"MPFPW2", BIT(6)},
+ {"GBE", BIT(7)},
+
+ {"SBR16B21", BIT(0)},
+ {"SBR16B5", BIT(1)},
+ {"SBR8B1", BIT(2)},
+ {"SBR8B0", BIT(3)},
+ {"P2SB0", BIT(4)},
+ {"D2D_DISP_1", BIT(5)},
+ {"LPSS", BIT(6)},
+ {"LPC", BIT(7)},
+
+ {"SMB", BIT(0)},
+ {"ISH", BIT(1)},
+ {"DBG_SBR16B", BIT(2)},
+ {"NPK_0", BIT(3)},
+ {"D2D_NOC_1", BIT(4)},
+ {"FIA_P", BIT(5)},
+ {"FUSE", BIT(6)},
+ {"DBG_PSF", BIT(7)},
+
+ {"DISP_PGA1", BIT(0)},
+ {"XDCI", BIT(1)},
+ {"EXI", BIT(2)},
+ {"CSE", BIT(3)},
+ {"KVMCC", BIT(4)},
+ {"PMT", BIT(5)},
+ {"CLINK", BIT(6)},
+ {"PTIO", BIT(7)},
+
+ {"USBR0", BIT(0)},
+ {"SBR16B22", BIT(1)},
+ {"SMT1", BIT(2)},
+ {"MPFPW1", BIT(3)},
+ {"SMS2", BIT(4)},
+ {"SMS1", BIT(5)},
+ {"CSMERTC", BIT(6)},
+ {"CSMEPSF", BIT(7)},
+
+ {"D2D_NOC_0", BIT(0)},
+ {"ESE", BIT(1)},
+ {"FIACPCB_P", BIT(2)},
+ {"RSVD", BIT(3)},
+ {"SBR8B2", BIT(4)},
+ {"OSSE_SMT1", BIT(5)},
+ {"D2D_DISP", BIT(6)},
+ {"P2SB1", BIT(7)},
+
+ {"U3FPW1", BIT(0)},
+ {"SBR16B3", BIT(1)},
+ {"PSF4", BIT(2)},
+ {"CNVI", BIT(3)},
+ {"UFSX2", BIT(4)},
+ {"ENDBG", BIT(5)},
+ {"DBC", BIT(6)},
+ {"SBRG", BIT(7)},
+
+ {"RSVD", BIT(0)},
+ {"NPK1", BIT(1)},
+ {"SBR16B7", BIT(2)},
+ {"SBR16B4", BIT(3)},
+ {"FIA_XG", BIT(4)},
+ {"PSF6", BIT(5)},
+ {"UFSPW1", BIT(6)},
+ {"FIA_U", BIT(7)},
+
+ {"PSF8", BIT(0)},
+ {"PSF0", BIT(1)},
+ {"RSVD", BIT(2)},
+ {"FIACPCB_U", BIT(3)},
+ {"TAM", BIT(4)},
+ {"SBR16B0", BIT(5)},
+ {"TBTLSX", BIT(6)},
+ {"THC0", BIT(7)},
+
+ {"THC1", BIT(0)},
+ {"PMC_1", BIT(1)},
+ {"FIACPCB_XG", BIT(2)},
+ {"TCSS", BIT(3)},
+ {"DISP_PGA", BIT(4)},
+ {"SBR16B20", BIT(5)},
+ {"SBR8B20", BIT(6)},
+ {"DBG_SBR", BIT(7)},
+
+ {"SPC", BIT(0)},
+ {"ACE_0", BIT(1)},
+ {"ACE_1", BIT(2)},
+ {"ACE_2", BIT(3)},
+ {"ACE_3", BIT(4)},
+ {"ACE_4", BIT(5)},
+ {"ACE_5", BIT(6)},
+ {"ACE_6", BIT(7)},
+
+ {"ACE_7", BIT(0)},
+ {"ACE_8", BIT(1)},
+ {"ACE_9", BIT(2)},
+ {"ACE_10", BIT(3)},
+ {"SBR16B2", BIT(4)},
+ {"SBR8B4", BIT(5)},
+ {"OSSE", BIT(6)},
+ {"SBR16B1", BIT(7)},
+ {}
+};
+
+static const struct pmc_bit_map *ext_wcl_pcdn_pfear_map[] = {
+ wcl_pcdn_pfear_map,
+ NULL
+};
+
+static const struct pmc_bit_map wcl_pcdn_ltr_show_map[] = {
+ {"SOUTHPORT_A", CNP_PMC_LTR_SPA},
+ {"RSVD", WCL_PMC_LTR_RESERVED},
+ {"SATA", CNP_PMC_LTR_SATA},
+ {"GIGABIT_ETHERNET", CNP_PMC_LTR_GBE},
+ {"XHCI", CNP_PMC_LTR_XHCI},
+ {"SOUTHPORT_F", ADL_PMC_LTR_SPF},
+ {"ME", CNP_PMC_LTR_ME},
+ {"SATA1", CNP_PMC_LTR_EVA},
+ {"SOUTHPORT_C", CNP_PMC_LTR_SPC},
+ {"HD_AUDIO", CNP_PMC_LTR_AZ},
+ {"CNV", CNP_PMC_LTR_CNV},
+ {"LPSS", CNP_PMC_LTR_LPSS},
+ {"SOUTHPORT_D", CNP_PMC_LTR_SPD},
+ {"SOUTHPORT_E", CNP_PMC_LTR_SPE},
+ {"SATA2", PTL_PMC_LTR_SATA2},
+ {"ESPI", CNP_PMC_LTR_ESPI},
+ {"SCC", CNP_PMC_LTR_SCC},
+ {"ISH", CNP_PMC_LTR_ISH},
+ {"UFSX2", CNP_PMC_LTR_UFSX2},
+ {"EMMC", CNP_PMC_LTR_EMMC},
+ {"WIGIG", ICL_PMC_LTR_WIGIG},
+ {"THC0", TGL_PMC_LTR_THC0},
+ {"THC1", TGL_PMC_LTR_THC1},
+ {"SOUTHPORT_G", MTL_PMC_LTR_SPG},
+ {"ESE", MTL_PMC_LTR_ESE},
+ {"IOE_PMC", MTL_PMC_LTR_IOE_PMC},
+ {"DMI3", ARL_PMC_LTR_DMI3},
+ {"OSSE", LNL_PMC_LTR_OSSE},
+
+ /* Below two cannot be used for LTR_IGNORE */
+ {"CURRENT_PLATFORM", PTL_PMC_LTR_CUR_PLT},
+ {"AGGREGATED_SYSTEM", PTL_PMC_LTR_CUR_ASLT},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_power_gating_status_0_map[] = {
+ {"PMC_PGD0_PG_STS", BIT(0), 0},
+ {"FUSE_OSSE_PGD0_PG_STS", BIT(1), 0},
+ {"ESPISPI_PGD0_PG_STS", BIT(2), 0},
+ {"XHCI_PGD0_PG_STS", BIT(3), 1},
+ {"SPA_PGD0_PG_STS", BIT(4), 1},
+ {"RSVD_5", BIT(5), 0},
+ {"MPFPW2_PGD0_PG_STS", BIT(6), 0},
+ {"GBE_PGD0_PG_STS", BIT(7), 1},
+ {"SBR16B21_PGD0_PG_STS", BIT(8), 0},
+ {"SBR16B5_PGD0_PG_STS", BIT(9), 0},
+ {"SBR8B1_PGD0_PG_STS", BIT(10), 0},
+ {"SBR8B0_PGD0_PG_STS", BIT(11), 0},
+ {"P2SB0_PG_STS", BIT(12), 1},
+ {"D2D_DISP_PGD1_PG_STS", BIT(13), 0},
+ {"LPSS_PGD0_PG_STS", BIT(14), 1},
+ {"LPC_PGD0_PG_STS", BIT(15), 0},
+ {"SMB_PGD0_PG_STS", BIT(16), 0},
+ {"ISH_PGD0_PG_STS", BIT(17), 0},
+ {"DBG_SBR16B_PGD0_PG_STS", BIT(18), 0},
+ {"NPK_PGD0_PG_STS", BIT(19), 0},
+ {"D2D_NOC_PGD1_PG_STS", BIT(20), 0},
+ {"FIA_P_PGD0_PG_STS", BIT(21), 0},
+ {"FUSE_PGD0_PG_STS", BIT(22), 0},
+ {"DBG_PSF_PGD0_PG_STS", BIT(23), 0},
+ {"DISP_PGA1_PGD0_PG_STS", BIT(24), 0},
+ {"XDCI_PGD0_PG_STS", BIT(25), 1},
+ {"EXI_PGD0_PG_STS", BIT(26), 0},
+ {"CSE_PGD0_PG_STS", BIT(27), 1},
+ {"KVMCC_PGD0_PG_STS", BIT(28), 1},
+ {"PMT_PGD0_PG_STS", BIT(29), 1},
+ {"CLINK_PGD0_PG_STS", BIT(30), 1},
+ {"PTIO_PGD0_PG_STS", BIT(31), 1},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_power_gating_status_1_map[] = {
+ {"USBR0_PGD0_PG_STS", BIT(0), 1},
+ {"SBR16B22_PGD0_PG_STS", BIT(1), 0},
+ {"SMT1_PGD0_PG_STS", BIT(2), 1},
+ {"MPFPW1_PGD0_PG_STS", BIT(3), 0},
+ {"SMS2_PGD0_PG_STS", BIT(4), 1},
+ {"SMS1_PGD0_PG_STS", BIT(5), 1},
+ {"CSMERTC_PGD0_PG_STS", BIT(6), 0},
+ {"CSMEPSF_PGD0_PG_STS", BIT(7), 0},
+ {"D2D_NOC_PGD0_PG_STS", BIT(8), 0},
+ {"ESE_PGD0_PG_STS", BIT(9), 1},
+ {"FIACPCB_P_PGD0_PG_STS", BIT(10), 0},
+ {"SBR8B2_PGD0_PG_STS", BIT(12), 0},
+ {"OSSE_SMT1_PGD0_PG_STS", BIT(13), 1},
+ {"D2D_DISP_PGD0_PG_STS", BIT(14), 0},
+ {"P2SB1_PGD0_PG_STS", BIT(15), 1},
+ {"U3FPW1_PGD0_PG_STS", BIT(16), 0},
+ {"SBR16B3_PGD0_PG_STS", BIT(17), 0},
+ {"PSF4_PGD0_PG_STS", BIT(18), 0},
+ {"CNVI_PGD0_PG_STS", BIT(19), 0},
+ {"UFSX2_PGD0_PG_STS", BIT(20), 1},
+ {"ENDBG_PGD0_PG_STS", BIT(21), 0},
+ {"DBC_PGD0_PG_STS", BIT(22), 0},
+ {"SBRG_PGD0_PG_STS", BIT(23), 0},
+ {"NPK_PGD1_PG_STS", BIT(25), 0},
+ {"SBR16B7_PGD0_PG_STS", BIT(26), 0},
+ {"SBR16B4_PGD0_PG_STS", BIT(27), 0},
+ {"FIA_XG_PSF_PGD0_PG_STS", BIT(28), 0},
+ {"PSF6_PGD0_PG_STS", BIT(29), 0},
+ {"UFSPW1_PGD0_PG_STS", BIT(30), 0},
+ {"FIA_U_PGD0_PG_STS", BIT(31), 0},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_power_gating_status_2_map[] = {
+ {"PSF8_PGD0_PG_STS", BIT(0), 0},
+ {"PSF0_PGD0_PG_STS", BIT(1), 0},
+ {"FIACPCB_U_PGD0_PG_STS", BIT(3), 0},
+ {"TAM_PGD0_PG_STS", BIT(4), 1},
+ {"SBR16B0_PGD0_PG_STS", BIT(5), 0},
+ {"TBTLSX_PGD0_PG_STS", BIT(6), 1},
+ {"THC0_PGD0_PG_STS", BIT(7), 1},
+ {"THC1_PGD0_PG_STS", BIT(8), 1},
+ {"PMC_PGD1_PG_STS", BIT(9), 0},
+ {"FIACPCB_XG_PGD0_PG_STS", BIT(10), 0},
+ {"TCSS_PGD0_PG_STS", BIT(11), 0},
+ {"DISP_PGA_PGD0_PG_STS", BIT(12), 0},
+ {"SBR8B4_PGD0_PG_STS", BIT(13), 0},
+ {"SBR8B20_PGD0_PG_STS", BIT(14), 0},
+ {"DBG_PGD0_PG_STS", BIT(15), 0},
+ {"SPC_PGD0_PG_STS", BIT(16), 1},
+ {"ACE_PGD0_PG_STS", BIT(17), 0},
+ {"ACE_PGD1_PG_STS", BIT(18), 0},
+ {"ACE_PGD2_PG_STS", BIT(19), 0},
+ {"ACE_PGD3_PG_STS", BIT(20), 0},
+ {"ACE_PGD4_PG_STS", BIT(21), 0},
+ {"ACE_PGD5_PG_STS", BIT(22), 0},
+ {"ACE_PGD6_PG_STS", BIT(23), 0},
+ {"ACE_PGD7_PG_STS", BIT(24), 0},
+ {"ACE_PGD8_PG_STS", BIT(25), 0},
+ {"ACE_PGD9_PG_STS", BIT(26), 0},
+ {"ACE_PGD10_PG_STS", BIT(27), 0},
+ {"SBR16B2_PG_PGD0_PG_STS", BIT(28), 0},
+ {"SBR16B20_PGD0_PG_STS", BIT(29), 0},
+ {"OSSE_PGD0_PG_STS", BIT(30), 1},
+ {"SBR16B1_PGD0_PG_STS", BIT(31), 0},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_d3_status_0_map[] = {
+ {"LPSS_D3_STS", BIT(3), 1},
+ {"XDCI_D3_STS", BIT(4), 1},
+ {"XHCI_D3_STS", BIT(5), 1},
+ {"SPA_D3_STS", BIT(12), 0},
+ {"SPC_D3_STS", BIT(14), 0},
+ {"OSSE_D3_STS", BIT(15), 0},
+ {"ESPISPI_D3_STS", BIT(18), 0},
+ {"PSTH_D3_STS", BIT(21), 0},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_d3_status_1_map[] = {
+ {"OSSE_SMT1_D3_STS", BIT(16), 0},
+ {"GBE_D3_STS", BIT(19), 0},
+ {"ITSS_D3_STS", BIT(23), 0},
+ {"CNVI_D3_STS", BIT(27), 0},
+ {"UFSX2_D3_STS", BIT(28), 0},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_d3_status_2_map[] = {
+ {"CSMERTC_D3_STS", BIT(1), 0},
+ {"ESE_D3_STS", BIT(2), 0},
+ {"CSE_D3_STS", BIT(4), 0},
+ {"KVMCC_D3_STS", BIT(5), 0},
+ {"USBR0_D3_STS", BIT(6), 0},
+ {"ISH_D3_STS", BIT(7), 0},
+ {"SMT1_D3_STS", BIT(8), 0},
+ {"SMT2_D3_STS", BIT(9), 0},
+ {"SMT3_D3_STS", BIT(10), 0},
+ {"CLINK_D3_STS", BIT(14), 0},
+ {"PTIO_D3_STS", BIT(16), 0},
+ {"PMT_D3_STS", BIT(17), 0},
+ {"SMS1_D3_STS", BIT(18), 0},
+ {"SMS2_D3_STS", BIT(19), 0},
+ {"OSSE_SMT2_D3_STS", BIT(22), 0},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_d3_status_3_map[] = {
+ {"THC0_D3_STS", BIT(14), 1},
+ {"THC1_D3_STS", BIT(15), 1},
+ {"OSSE_SMT3_D3_STS", BIT(16), 0},
+ {"ACE_D3_STS", BIT(23), 0},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_vnn_req_status_0_map[] = {
+ {"LPSS_VNN_REQ_STS", BIT(3), 1},
+ {"OSSE_VNN_REQ_STS", BIT(15), 1},
+ {"ESPISPI_VNN_REQ_STS", BIT(18), 1},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_vnn_req_status_1_map[] = {
+ {"NPK_VNN_REQ_STS", BIT(4), 1},
+ {"DFXAGG_VNN_REQ_STS", BIT(8), 0},
+ {"EXI_VNN_REQ_STS", BIT(9), 1},
+ {"OSSE_SMT1_VNN_REQ_STS", BIT(16), 1},
+ {"P2D_VNN_REQ_STS", BIT(18), 1},
+ {"GBE_VNN_REQ_STS", BIT(19), 1},
+ {"SMB_VNN_REQ_STS", BIT(25), 1},
+ {"LPC_VNN_REQ_STS", BIT(26), 0},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_vnn_req_status_2_map[] = {
+ {"CSMERTC_VNN_REQ_STS", BIT(1), 1},
+ {"ESE_VNN_REQ_STS", BIT(2), 1},
+ {"CSE_VNN_REQ_STS", BIT(4), 1},
+ {"ISH_VNN_REQ_STS", BIT(7), 1},
+ {"SMT1_VNN_REQ_STS", BIT(8), 1},
+ {"CLINK_VNN_REQ_STS", BIT(14), 1},
+ {"SMS1_VNN_REQ_STS", BIT(18), 1},
+ {"SMS2_VNN_REQ_STS", BIT(19), 1},
+ {"GPIOCOM4_VNN_REQ_STS", BIT(20), 1},
+ {"GPIOCOM3_VNN_REQ_STS", BIT(21), 1},
+ {"GPIOCOM1_VNN_REQ_STS", BIT(23), 1},
+ {"GPIOCOM0_VNN_REQ_STS", BIT(24), 1},
+ {"DISP_SHIM_VNN_REQ_STS", BIT(31), 1},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_vnn_misc_status_map[] = {
+ {"CPU_C10_REQ_STS", BIT(0), 0},
+ {"TS_OFF_REQ_STS", BIT(1), 0},
+ {"PNDE_MET_REQ_STS", BIT(2), 1},
+ {"FW_THROTTLE_ALLOWED_REQ_STS", BIT(4), 0},
+ {"VNN_SOC_REQ_STS", BIT(6), 1},
+ {"ISH_VNNAON_REQ_STS", BIT(7), 0},
+ {"D2D_NOC_CFI_QACTIVE_REQ_STS", BIT(8), 1},
+ {"D2D_NOC_GPSB_QACTIVE_REQ_STS", BIT(9), 1},
+ {"PLT_GREATER_REQ_STS", BIT(11), 1},
+ {"ALL_SBR_IDLE_REQ_STS", BIT(12), 0},
+ {"PMC_IDLE_FB_OCP_REQ_STS", BIT(13), 0},
+ {"PM_SYNC_STATES_REQ_STS", BIT(14), 0},
+ {"EA_REQ_STS", BIT(15), 0},
+ {"MPHY_CORE_OFF_REQ_STS", BIT(16), 0},
+ {"BRK_EV_EN_REQ_STS", BIT(17), 0},
+ {"AUTO_DEMO_EN_REQ_STS", BIT(18), 0},
+ {"ITSS_CLK_SRC_REQ_STS", BIT(19), 1},
+ {"ARC_IDLE_REQ_STS", BIT(21), 0},
+ {"FIA_DEEP_PM_REQ_STS", BIT(23), 0},
+ {"XDCI_ATTACHED_REQ_STS", BIT(24), 1},
+ {"ARC_INTERRUPT_WAKE_REQ_STS", BIT(25), 0},
+ {"D2D_DISP_DDI_QACTIVE_REQ_STS", BIT(26), 1},
+ {"PRE_WAKE0_REQ_STS", BIT(27), 1},
+ {"PRE_WAKE1_REQ_STS", BIT(28), 1},
+ {"PRE_WAKE2_REQ_STS", BIT(29), 1},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_rsc_status_map[] = {
+ {"Memory", 0, 1},
+ {"PSF0", 0, 1},
+ {"PSF6", 0, 1},
+ {"PSF8", 0, 1},
+ {"SAF_CFI_LINK", 0, 1},
+ {"SB", 0, 1},
+ {}
+};
+
+static const struct pmc_bit_map *wcl_pcdn_lpm_maps[] = {
+ ptl_pcdp_clocksource_status_map,
+ wcl_pcdn_power_gating_status_0_map,
+ wcl_pcdn_power_gating_status_1_map,
+ wcl_pcdn_power_gating_status_2_map,
+ wcl_pcdn_d3_status_0_map,
+ wcl_pcdn_d3_status_1_map,
+ wcl_pcdn_d3_status_2_map,
+ wcl_pcdn_d3_status_3_map,
+ wcl_pcdn_vnn_req_status_0_map,
+ wcl_pcdn_vnn_req_status_1_map,
+ wcl_pcdn_vnn_req_status_2_map,
+ ptl_pcdp_vnn_req_status_3_map,
+ wcl_pcdn_vnn_misc_status_map,
+ ptl_pcdp_signal_status_map,
+ NULL
+};
+
+static const struct pmc_bit_map *wcl_pcdn_blk_maps[] = {
+ wcl_pcdn_power_gating_status_0_map,
+ wcl_pcdn_power_gating_status_1_map,
+ wcl_pcdn_power_gating_status_2_map,
+ wcl_pcdn_rsc_status_map,
+ wcl_pcdn_vnn_req_status_0_map,
+ wcl_pcdn_vnn_req_status_1_map,
+ wcl_pcdn_vnn_req_status_2_map,
+ ptl_pcdp_vnn_req_status_3_map,
+ wcl_pcdn_d3_status_0_map,
+ wcl_pcdn_d3_status_1_map,
+ wcl_pcdn_d3_status_2_map,
+ wcl_pcdn_d3_status_3_map,
+ ptl_pcdp_clocksource_status_map,
+ wcl_pcdn_vnn_misc_status_map,
+ ptl_pcdp_signal_status_map,
+ NULL
+};
+
+static const struct pmc_reg_map wcl_pcdn_reg_map = {
+ .pfear_sts = ext_wcl_pcdn_pfear_map,
+ .slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET,
+ .slp_s0_res_counter_step = TGL_PMC_SLP_S0_RES_COUNTER_STEP,
+ .ltr_show_sts = wcl_pcdn_ltr_show_map,
+ .msr_sts = msr_map,
+ .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET,
+ .regmap_length = WCL_PCD_PMC_MMIO_REG_LEN,
+ .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A,
+ .ppfear_buckets = LNL_PPFEAR_NUM_ENTRIES,
+ .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET,
+ .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT,
+ .lpm_num_maps = PTL_LPM_NUM_MAPS,
+ .ltr_ignore_max = LNL_NUM_IP_IGN_ALLOWED,
+ .lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2,
+ .etr3_offset = ETR3_OFFSET,
+ .lpm_sts_latch_en_offset = MTL_LPM_STATUS_LATCH_EN_OFFSET,
+ .lpm_priority_offset = MTL_LPM_PRI_OFFSET,
+ .lpm_en_offset = MTL_LPM_EN_OFFSET,
+ .lpm_residency_offset = MTL_LPM_RESIDENCY_OFFSET,
+ .lpm_sts = wcl_pcdn_lpm_maps,
+ .lpm_status_offset = MTL_LPM_STATUS_OFFSET,
+ .lpm_live_status_offset = MTL_LPM_LIVE_STATUS_OFFSET,
+ .s0ix_blocker_maps = wcl_pcdn_blk_maps,
+ .s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET,
+};
+
+#define WCL_NPU_PCI_DEV 0xfd3e
+
+/*
+ * Set power state of select devices that do not have drivers to D3
+ * so that they do not block Package C entry.
+ */
+static void wcl_d3_fixup(void)
+{
+ pmc_core_set_device_d3(WCL_NPU_PCI_DEV);
+}
+
+static int wcl_resume(struct pmc_dev *pmcdev)
+{
+ wcl_d3_fixup();
+ return cnl_resume(pmcdev);
+}
+
+static int wcl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
+{
+ wcl_d3_fixup();
+ return generic_core_init(pmcdev, pmc_dev_info);
+}
+
+struct pmc_dev_info wcl_pmc_dev = {
+ .map = &wcl_pcdn_reg_map,
+ .suspend = cnl_suspend,
+ .resume = wcl_resume,
+ .init = wcl_core_init,
+};
diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c
index bfcf92aa4d69..1237d9570886 100644
--- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c
+++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c
@@ -374,6 +374,77 @@ static void uncore_set_agent_type(struct tpmi_uncore_cluster_info *cluster_info)
cluster_info->uncore_data.agent_type_mask = FIELD_GET(UNCORE_AGENT_TYPES, status);
}
+#define MAX_PARTITIONS 2
+
+/* IO domain ID start index for a partition */
+static u8 io_die_start[MAX_PARTITIONS];
+
+/* Next IO domain ID index after the current partition IO die IDs */
+static u8 io_die_index_next;
+
+/* Lock to protect io_die_start, io_die_index_next */
+static DEFINE_MUTEX(domain_lock);
+
+static void set_domain_id(int id, int num_resources,
+ struct oobmsm_plat_info *plat_info,
+ struct tpmi_uncore_cluster_info *cluster_info)
+{
+ u8 part_io_index, cdie_range, pkg_io_index, max_dies;
+
+ if (plat_info->partition >= MAX_PARTITIONS) {
+ cluster_info->uncore_data.domain_id = id;
+ return;
+ }
+
+ if (cluster_info->uncore_data.agent_type_mask & AGENT_TYPE_CORE) {
+ cluster_info->uncore_data.domain_id = cluster_info->cdie_id;
+ return;
+ }
+
+ /* Unlikely but cdie_mask may have holes, so take range */
+ cdie_range = fls(plat_info->cdie_mask) - ffs(plat_info->cdie_mask) + 1;
+ max_dies = topology_max_dies_per_package();
+
+ /*
+ * If the CPU doesn't enumerate dies, then use current cdie range
+ * as the max.
+ */
+ if (cdie_range > max_dies)
+ max_dies = cdie_range;
+
+ guard(mutex)(&domain_lock);
+
+ if (!io_die_index_next)
+ io_die_index_next = max_dies;
+
+ if (!io_die_start[plat_info->partition]) {
+ io_die_start[plat_info->partition] = io_die_index_next;
+ /*
+ * number of IO dies = num_resources - cdie_range. Hence
+ * next partition io_die_index_next is set after IO dies
+ * in the current partition.
+ */
+ io_die_index_next += (num_resources - cdie_range);
+ }
+
+ /*
+ * Index from IO die start within the partition:
+ * This is the first valid domain after the cdies.
+ * For example the current resource index 5 and cdies end at
+ * index 3 (cdie_cnt = 4). Then the IO only index 5 - 4 = 1.
+ */
+ part_io_index = id - cdie_range;
+
+ /*
+ * Add to the IO die start index for this partition in this package
+ * to make unique in the package.
+ */
+ pkg_io_index = io_die_start[plat_info->partition] + part_io_index;
+
+ /* Assign this to domain ID */
+ cluster_info->uncore_data.domain_id = pkg_io_index;
+}
+
/* Callback for sysfs read for TPMI uncore values. Called under mutex locks. */
static int uncore_read(struct uncore_data *data, unsigned int *value, enum uncore_index index)
{
@@ -610,11 +681,12 @@ static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_
cluster_info->uncore_data.package_id = pkg;
/* There are no dies like Cascade Lake */
cluster_info->uncore_data.die_id = 0;
- cluster_info->uncore_data.domain_id = i;
cluster_info->uncore_data.cluster_id = j;
set_cdie_id(i, cluster_info, plat_info);
+ set_domain_id(i, num_resources, plat_info, cluster_info);
+
cluster_info->uncore_root = tpmi_uncore;
if (TPMI_MINOR_VERSION(pd_info->ufs_header_ver) >= UNCORE_ELC_SUPPORTED_VERSION)
@@ -638,7 +710,7 @@ static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_
auxiliary_set_drvdata(auxdev, tpmi_uncore);
- if (topology_max_dies_per_package() > 1)
+ if (topology_max_dies_per_package() > 1 || plat_info->partition)
return 0;
tpmi_uncore->root_cluster.root_domain = true;
diff --git a/drivers/platform/x86/lenovo/think-lmi.c b/drivers/platform/x86/lenovo/think-lmi.c
index 0992b41b6221..540b472b1bf3 100644
--- a/drivers/platform/x86/lenovo/think-lmi.c
+++ b/drivers/platform/x86/lenovo/think-lmi.c
@@ -119,6 +119,7 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
* You must reboot the computer before the changes will take effect.
*/
#define LENOVO_SET_BIOS_CERT_GUID "26861C9F-47E9-44C4-BD8B-DFE7FA2610FE"
+#define LENOVO_TC_SET_BIOS_CERT_GUID "955aaf7d-8bc4-4f04-90aa-97469512f167"
/*
* Name: UpdateBiosCert
@@ -128,6 +129,7 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
* You must reboot the computer before the changes will take effect.
*/
#define LENOVO_UPDATE_BIOS_CERT_GUID "9AA3180A-9750-41F7-B9F7-D5D3B1BAC3CE"
+#define LENOVO_TC_UPDATE_BIOS_CERT_GUID "5f5bbbb2-c72f-4fb8-8129-228eef4fdbed"
/*
* Name: ClearBiosCert
@@ -137,6 +139,8 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
* You must reboot the computer before the changes will take effect.
*/
#define LENOVO_CLEAR_BIOS_CERT_GUID "B2BC39A7-78DD-4D71-B059-A510DEC44890"
+#define LENOVO_TC_CLEAR_BIOS_CERT_GUID "97849cb6-cb44-42d1-a750-26a596a9eec4"
+
/*
* Name: CertToPassword
* Description: Switch from certificate to password authentication.
@@ -145,6 +149,7 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
* You must reboot the computer before the changes will take effect.
*/
#define LENOVO_CERT_TO_PASSWORD_GUID "0DE8590D-5510-4044-9621-77C227F5A70D"
+#define LENOVO_TC_CERT_TO_PASSWORD_GUID "ef65480d-38c9-420d-b700-ab3d6c8ebaca"
/*
* Name: SetBiosSettingCert
@@ -153,6 +158,7 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
* Format: "Item,Value,Signature"
*/
#define LENOVO_SET_BIOS_SETTING_CERT_GUID "34A008CC-D205-4B62-9E67-31DFA8B90003"
+#define LENOVO_TC_SET_BIOS_SETTING_CERT_GUID "19ecba3b-b318-4192-a89b-43d94bc60cea"
/*
* Name: SaveBiosSettingCert
@@ -161,6 +167,7 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
* Format: "Signature"
*/
#define LENOVO_SAVE_BIOS_SETTING_CERT_GUID "C050FB9D-DF5F-4606-B066-9EFC401B2551"
+#define LENOVO_TC_SAVE_BIOS_SETTING_CERT_GUID "0afaf46f-7cca-450a-b455-a826a0bf1af5"
/*
* Name: CertThumbprint
@@ -177,12 +184,43 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
#define TLMI_CERT_SVC BIT(7) /* Admin Certificate Based */
#define TLMI_CERT_SMC BIT(8) /* System Certificate Based */
+static const struct tlmi_cert_guids thinkpad_cert_guid = {
+ .thumbprint = LENOVO_CERT_THUMBPRINT_GUID,
+ .set_bios_setting = LENOVO_SET_BIOS_SETTING_CERT_GUID,
+ .save_bios_setting = LENOVO_SAVE_BIOS_SETTING_CERT_GUID,
+ .cert_to_password = LENOVO_CERT_TO_PASSWORD_GUID,
+ .clear_bios_cert = LENOVO_CLEAR_BIOS_CERT_GUID,
+ .update_bios_cert = LENOVO_UPDATE_BIOS_CERT_GUID,
+ .set_bios_cert = LENOVO_SET_BIOS_CERT_GUID,
+};
+
+static const struct tlmi_cert_guids thinkcenter_cert_guid = {
+ .thumbprint = NULL,
+ .set_bios_setting = LENOVO_TC_SET_BIOS_SETTING_CERT_GUID,
+ .save_bios_setting = LENOVO_TC_SAVE_BIOS_SETTING_CERT_GUID,
+ .cert_to_password = LENOVO_TC_CERT_TO_PASSWORD_GUID,
+ .clear_bios_cert = LENOVO_TC_CLEAR_BIOS_CERT_GUID,
+ .update_bios_cert = LENOVO_TC_UPDATE_BIOS_CERT_GUID,
+ .set_bios_cert = LENOVO_TC_SET_BIOS_CERT_GUID,
+};
+
static const struct tlmi_err_codes tlmi_errs[] = {
{"Success", 0},
+ {"Set Certificate operation was successful.", 0},
{"Not Supported", -EOPNOTSUPP},
{"Invalid Parameter", -EINVAL},
{"Access Denied", -EACCES},
{"System Busy", -EBUSY},
+ {"Set Certificate operation failed with status:Invalid Parameter.", -EINVAL},
+ {"Set Certificate operation failed with status:Invalid certificate type.", -EINVAL},
+ {"Set Certificate operation failed with status:Invalid password format.", -EINVAL},
+ {"Set Certificate operation failed with status:Password retry count exceeded.", -EACCES},
+ {"Set Certificate operation failed with status:Password Invalid.", -EACCES},
+ {"Set Certificate operation failed with status:Operation aborted.", -EBUSY},
+ {"Set Certificate operation failed with status:No free slots to write.", -ENOSPC},
+ {"Set Certificate operation failed with status:Certificate not found.", -EEXIST},
+ {"Set Certificate operation failed with status:Internal error.", -EFAULT},
+ {"Set Certificate operation failed with status:Certificate too large.", -EFBIG},
};
static const char * const encoding_options[] = {
@@ -668,7 +706,10 @@ static ssize_t cert_thumbprint(char *buf, const char *arg, int count)
const union acpi_object *obj;
acpi_status status;
- status = wmi_evaluate_method(LENOVO_CERT_THUMBPRINT_GUID, 0, 0, &input, &output);
+ if (!tlmi_priv.cert_guid->thumbprint)
+ return -EOPNOTSUPP;
+
+ status = wmi_evaluate_method(tlmi_priv.cert_guid->thumbprint, 0, 0, &input, &output);
if (ACPI_FAILURE(status)) {
kfree(output.pointer);
return -EIO;
@@ -751,7 +792,7 @@ static ssize_t cert_to_password_store(struct kobject *kobj,
kfree_sensitive(passwd);
return -ENOMEM;
}
- ret = tlmi_simple_call(LENOVO_CERT_TO_PASSWORD_GUID, auth_str);
+ ret = tlmi_simple_call(tlmi_priv.cert_guid->cert_to_password, auth_str);
kfree(auth_str);
kfree_sensitive(passwd);
@@ -774,7 +815,7 @@ static ssize_t certificate_store(struct kobject *kobj,
char *auth_str, *new_cert;
const char *serial;
char *signature;
- char *guid;
+ const char *guid;
int ret;
if (!capable(CAP_SYS_ADMIN))
@@ -797,7 +838,7 @@ static ssize_t certificate_store(struct kobject *kobj,
if (!auth_str)
return -ENOMEM;
- ret = tlmi_simple_call(LENOVO_CLEAR_BIOS_CERT_GUID, auth_str);
+ ret = tlmi_simple_call(tlmi_priv.cert_guid->clear_bios_cert, auth_str);
kfree(auth_str);
return ret ?: count;
@@ -834,7 +875,7 @@ static ssize_t certificate_store(struct kobject *kobj,
kfree(new_cert);
return -EACCES;
}
- guid = LENOVO_UPDATE_BIOS_CERT_GUID;
+ guid = tlmi_priv.cert_guid->update_bios_cert;
/* Format: 'Certificate,Signature' */
auth_str = cert_command(setting, new_cert, signature);
} else {
@@ -845,9 +886,17 @@ static ssize_t certificate_store(struct kobject *kobj,
kfree(new_cert);
return -EACCES;
}
- guid = LENOVO_SET_BIOS_CERT_GUID;
- /* Format: 'Certificate, password' */
- auth_str = cert_command(setting, new_cert, setting->password);
+ guid = tlmi_priv.cert_guid->set_bios_cert;
+ if (tlmi_priv.thinkcenter_mode) {
+ /* Format: 'Certificate, password, encoding, kbdlang' */
+ auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s,%s", new_cert,
+ setting->password,
+ encoding_options[setting->encoding],
+ setting->kbdlang);
+ } else {
+ /* Format: 'Certificate, password' */
+ auth_str = cert_command(setting, new_cert, setting->password);
+ }
}
kfree(new_cert);
if (!auth_str)
@@ -1071,13 +1120,13 @@ static ssize_t current_value_store(struct kobject *kobj,
goto out;
}
- ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTING_CERT_GUID, set_str);
+ ret = tlmi_simple_call(tlmi_priv.cert_guid->set_bios_setting, set_str);
if (ret)
goto out;
if (tlmi_priv.save_mode == TLMI_SAVE_BULK)
tlmi_priv.save_required = true;
else
- ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID,
+ ret = tlmi_simple_call(tlmi_priv.cert_guid->save_bios_setting,
tlmi_priv.pwd_admin->save_signature);
} else if (tlmi_priv.opcode_support) {
/*
@@ -1282,7 +1331,7 @@ static ssize_t save_settings_store(struct kobject *kobj, struct kobj_attribute *
ret = -EINVAL;
goto out;
}
- ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID,
+ ret = tlmi_simple_call(tlmi_priv.cert_guid->save_bios_setting,
tlmi_priv.pwd_admin->save_signature);
if (ret)
goto out;
@@ -1583,6 +1632,15 @@ static int tlmi_analyze(struct wmi_device *wdev)
wmi_has_guid(LENOVO_SAVE_BIOS_SETTING_CERT_GUID))
tlmi_priv.certificate_support = true;
+ /* ThinkCenter uses different GUIDs for certificate support */
+ if (wmi_has_guid(LENOVO_TC_SET_BIOS_CERT_GUID) &&
+ wmi_has_guid(LENOVO_TC_SET_BIOS_SETTING_CERT_GUID) &&
+ wmi_has_guid(LENOVO_TC_SAVE_BIOS_SETTING_CERT_GUID)) {
+ tlmi_priv.certificate_support = true;
+ tlmi_priv.thinkcenter_mode = true;
+ pr_info("ThinkCenter modified support being used\n");
+ }
+
/*
* Try to find the number of valid settings of this machine
* and use it to create sysfs attributes.
@@ -1728,10 +1786,16 @@ static int tlmi_analyze(struct wmi_device *wdev)
}
if (tlmi_priv.certificate_support) {
- tlmi_priv.pwd_admin->cert_installed =
- tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SVC;
- tlmi_priv.pwd_system->cert_installed =
- tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SMC;
+ if (tlmi_priv.thinkcenter_mode) {
+ tlmi_priv.cert_guid = &thinkcenter_cert_guid;
+ tlmi_priv.pwd_admin->cert_installed = tlmi_priv.pwdcfg.core.password_mode;
+ } else {
+ tlmi_priv.cert_guid = &thinkpad_cert_guid;
+ tlmi_priv.pwd_admin->cert_installed =
+ tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SVC;
+ tlmi_priv.pwd_system->cert_installed =
+ tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SMC;
+ }
}
return 0;
diff --git a/drivers/platform/x86/lenovo/think-lmi.h b/drivers/platform/x86/lenovo/think-lmi.h
index 9b014644d316..017644323d46 100644
--- a/drivers/platform/x86/lenovo/think-lmi.h
+++ b/drivers/platform/x86/lenovo/think-lmi.h
@@ -41,6 +41,17 @@ enum save_mode {
TLMI_SAVE_SAVE,
};
+/* GUIDs can differ between platforms */
+struct tlmi_cert_guids {
+ const char *thumbprint;
+ const char *set_bios_setting;
+ const char *save_bios_setting;
+ const char *cert_to_password;
+ const char *clear_bios_cert;
+ const char *update_bios_cert;
+ const char *set_bios_cert;
+};
+
/* password configuration details */
#define TLMI_PWDCFG_MODE_LEGACY 0
#define TLMI_PWDCFG_MODE_PASSWORD 1
@@ -109,6 +120,7 @@ struct think_lmi {
enum save_mode save_mode;
bool save_required;
bool reboot_required;
+ bool thinkcenter_mode;
struct tlmi_attr_setting *setting[TLMI_SETTINGS_COUNT];
struct device *class_dev;
@@ -121,6 +133,8 @@ struct think_lmi {
struct tlmi_pwd_setting *pwd_system;
struct tlmi_pwd_setting *pwd_hdd;
struct tlmi_pwd_setting *pwd_nvme;
+
+ const struct tlmi_cert_guids *cert_guid;
};
#endif /* !_THINK_LMI_H_ */
diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.c b/drivers/platform/x86/lenovo/wmi-capdata01.c
index c922680b3cba..fc7e3454e71d 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata01.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata01.c
@@ -93,7 +93,7 @@ int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata0
continue;
memcpy(output, &list->data[idx], sizeof(list->data[idx]));
return 0;
- };
+ }
return -EINVAL;
}
diff --git a/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c b/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c
index 1b33c977f6d7..8551ab4d2c7d 100644
--- a/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c
+++ b/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c
@@ -255,6 +255,11 @@ static int yt2_1380_fc_pdev_probe(struct platform_device *pdev)
if (!serdev)
return -ENOMEM;
+ /* Propagate pdev-fwnode set by x86-android-tablets to serdev */
+ device_set_node(&serdev->dev, dev_fwnode(&pdev->dev));
+ /* The fwnode is a managed node, so it will be auto-put on serdev_device_put() */
+ fwnode_handle_get(dev_fwnode(&serdev->dev));
+
ret = serdev_device_add(serdev);
if (ret) {
serdev_device_put(serdev);
diff --git a/drivers/platform/x86/meraki-mx100.c b/drivers/platform/x86/meraki-mx100.c
index 3751ed36a980..8c5276d98512 100644
--- a/drivers/platform/x86/meraki-mx100.c
+++ b/drivers/platform/x86/meraki-mx100.c
@@ -15,135 +15,256 @@
#include <linux/dmi.h>
#include <linux/err.h>
-#include <linux/gpio_keys.h>
#include <linux/gpio/machine.h>
-#include <linux/input.h>
+#include <linux/gpio/property.h>
+#include <linux/input-event-codes.h>
#include <linux/io.h>
#include <linux/kernel.h>
-#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
#define TINK_GPIO_DRIVER_NAME "gpio_ich"
+static const struct software_node gpio_ich_node = {
+ .name = TINK_GPIO_DRIVER_NAME,
+};
+
/* LEDs */
-static const struct gpio_led tink_leds[] = {
- {
- .name = "mx100:green:internet",
- .default_trigger = "default-on",
- },
- {
- .name = "mx100:green:lan2",
- },
- {
- .name = "mx100:green:lan3",
- },
- {
- .name = "mx100:green:lan4",
- },
- {
- .name = "mx100:green:lan5",
- },
- {
- .name = "mx100:green:lan6",
- },
- {
- .name = "mx100:green:lan7",
- },
- {
- .name = "mx100:green:lan8",
- },
- {
- .name = "mx100:green:lan9",
- },
- {
- .name = "mx100:green:lan10",
- },
- {
- .name = "mx100:green:lan11",
- },
- {
- .name = "mx100:green:ha",
- },
- {
- .name = "mx100:orange:ha",
- },
- {
- .name = "mx100:green:usb",
- },
- {
- .name = "mx100:orange:usb",
- },
+static const struct software_node tink_gpio_leds_node = {
+ .name = "meraki-mx100-leds",
};
-static const struct gpio_led_platform_data tink_leds_pdata = {
- .num_leds = ARRAY_SIZE(tink_leds),
- .leds = tink_leds,
-};
-
-static struct gpiod_lookup_table tink_leds_table = {
- .dev_id = "leds-gpio",
- .table = {
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 11,
- NULL, 0, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 18,
- NULL, 1, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 20,
- NULL, 2, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 22,
- NULL, 3, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 23,
- NULL, 4, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 32,
- NULL, 5, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 34,
- NULL, 6, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 35,
- NULL, 7, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 36,
- NULL, 8, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 37,
- NULL, 9, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 48,
- NULL, 10, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 16,
- NULL, 11, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 7,
- NULL, 12, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 21,
- NULL, 13, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 19,
- NULL, 14, GPIO_ACTIVE_LOW),
- {} /* Terminating entry */
- }
+static const struct property_entry tink_internet_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:internet"),
+ PROPERTY_ENTRY_STRING("linux,default-trigger", "default-on"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 11, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node tink_internet_led_node = {
+ .name = "internet-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_internet_led_props,
+};
+
+static const struct property_entry tink_lan2_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan2"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 18, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan2_led_node = {
+ .name = "lan2-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan2_led_props,
+};
+
+static const struct property_entry tink_lan3_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan3"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 20, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan3_led_node = {
+ .name = "lan3-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan3_led_props,
+};
+
+static const struct property_entry tink_lan4_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan4"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 22, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan4_led_node = {
+ .name = "lan4-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan4_led_props,
+};
+
+static const struct property_entry tink_lan5_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan5"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 23, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan5_led_node = {
+ .name = "lan5-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan5_led_props,
+};
+
+static const struct property_entry tink_lan6_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan6"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 32, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan6_led_node = {
+ .name = "lan6-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan6_led_props,
+};
+
+static const struct property_entry tink_lan7_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan7"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 34, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan7_led_node = {
+ .name = "lan7-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan7_led_props,
+};
+
+static const struct property_entry tink_lan8_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan8"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 35, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan8_led_node = {
+ .name = "lan8-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan8_led_props,
+};
+
+static const struct property_entry tink_lan9_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan9"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 36, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan9_led_node = {
+ .name = "lan9-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan9_led_props,
+};
+
+static const struct property_entry tink_lan10_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan10"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 37, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan10_led_node = {
+ .name = "lan10-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan10_led_props,
+};
+
+static const struct property_entry tink_lan11_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan11"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 48, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan11_led_node = {
+ .name = "lan11-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan11_led_props,
+};
+
+static const struct property_entry tink_ha_green_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:ha"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 16, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node tink_ha_green_led_node = {
+ .name = "ha-green-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_ha_green_led_props,
+};
+
+static const struct property_entry tink_ha_orange_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:orange:ha"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 7, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node tink_ha_orange_led_node = {
+ .name = "ha-orange-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_ha_orange_led_props,
+};
+
+static const struct property_entry tink_usb_green_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:usb"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 21, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node tink_usb_green_led_node = {
+ .name = "usb-green-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_usb_green_led_props,
+};
+
+static const struct property_entry tink_usb_orange_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:orange:usb"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 19, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node tink_usb_orange_led_node = {
+ .name = "usb-orange-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_usb_orange_led_props,
};
/* Reset Button */
-static struct gpio_keys_button tink_buttons[] = {
- {
- .desc = "Reset",
- .type = EV_KEY,
- .code = KEY_RESTART,
- .active_low = 1,
- .debounce_interval = 100,
- },
+static const struct property_entry tink_gpio_keys_props[] = {
+ PROPERTY_ENTRY_U32("poll-interval", 20),
+ { }
};
-static const struct gpio_keys_platform_data tink_buttons_pdata = {
- .buttons = tink_buttons,
- .nbuttons = ARRAY_SIZE(tink_buttons),
- .poll_interval = 20,
- .rep = 0,
- .name = "mx100-keys",
+static const struct software_node tink_gpio_keys_node = {
+ .name = "mx100-keys",
+ .properties = tink_gpio_keys_props,
};
-static struct gpiod_lookup_table tink_keys_table = {
- .dev_id = "gpio-keys-polled",
- .table = {
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 60,
- NULL, 0, GPIO_ACTIVE_LOW),
- {} /* Terminating entry */
- }
+static const struct property_entry tink_reset_key_props[] = {
+ PROPERTY_ENTRY_U32("linux,code", KEY_RESTART),
+ PROPERTY_ENTRY_STRING("label", "Reset"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 60, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("linux,input-type", EV_KEY),
+ PROPERTY_ENTRY_U32("debounce-interval", 100),
+ { }
+};
+
+static const struct software_node tink_reset_key_node = {
+ .name = "reset",
+ .parent = &tink_gpio_keys_node,
+ .properties = tink_reset_key_props,
+};
+
+static const struct software_node *tink_swnodes[] = {
+ &gpio_ich_node,
+ /* LEDs nodes */
+ &tink_gpio_leds_node,
+ &tink_internet_led_node,
+ &tink_lan2_led_node,
+ &tink_lan3_led_node,
+ &tink_lan4_led_node,
+ &tink_lan5_led_node,
+ &tink_lan6_led_node,
+ &tink_lan7_led_node,
+ &tink_lan8_led_node,
+ &tink_lan9_led_node,
+ &tink_lan10_led_node,
+ &tink_lan11_led_node,
+ &tink_ha_green_led_node,
+ &tink_ha_orange_led_node,
+ &tink_usb_green_led_node,
+ &tink_usb_orange_led_node,
+ /* Keys nodes */
+ &tink_gpio_keys_node,
+ &tink_reset_key_node,
+ NULL
};
/* Board setup */
@@ -161,22 +282,17 @@ MODULE_DEVICE_TABLE(dmi, tink_systems);
static struct platform_device *tink_leds_pdev;
static struct platform_device *tink_keys_pdev;
-static struct platform_device * __init tink_create_dev(
- const char *name, const void *pdata, size_t sz)
-{
- struct platform_device *pdev;
-
- pdev = platform_device_register_data(NULL,
- name, PLATFORM_DEVID_NONE, pdata, sz);
- if (IS_ERR(pdev))
- pr_err("failed registering %s: %ld\n", name, PTR_ERR(pdev));
-
- return pdev;
-}
-
static int __init tink_board_init(void)
{
- int ret;
+ struct platform_device_info keys_info = {
+ .name = "gpio-keys-polled",
+ .id = PLATFORM_DEVID_NONE,
+ };
+ struct platform_device_info leds_info = {
+ .name = "leds-gpio",
+ .id = PLATFORM_DEVID_NONE,
+ };
+ int err;
if (!dmi_first_match(tink_systems))
return -ENODEV;
@@ -188,30 +304,35 @@ static int __init tink_board_init(void)
*/
outl(inl(0x530) | BIT(28), 0x530);
- gpiod_add_lookup_table(&tink_leds_table);
- gpiod_add_lookup_table(&tink_keys_table);
+ err = software_node_register_node_group(tink_swnodes);
+ if (err) {
+ pr_err("failed to register software nodes: %d\n", err);
+ return err;
+ }
- tink_leds_pdev = tink_create_dev("leds-gpio",
- &tink_leds_pdata, sizeof(tink_leds_pdata));
+ leds_info.fwnode = software_node_fwnode(&tink_gpio_leds_node);
+ tink_leds_pdev = platform_device_register_full(&leds_info);
if (IS_ERR(tink_leds_pdev)) {
- ret = PTR_ERR(tink_leds_pdev);
- goto err;
+ err = PTR_ERR(tink_leds_pdev);
+ pr_err("failed to create LED device: %d\n", err);
+ goto err_unregister_swnodes;
}
- tink_keys_pdev = tink_create_dev("gpio-keys-polled",
- &tink_buttons_pdata, sizeof(tink_buttons_pdata));
+ keys_info.fwnode = software_node_fwnode(&tink_gpio_keys_node);
+ tink_keys_pdev = platform_device_register_full(&keys_info);
if (IS_ERR(tink_keys_pdev)) {
- ret = PTR_ERR(tink_keys_pdev);
- platform_device_unregister(tink_leds_pdev);
- goto err;
+ err = PTR_ERR(tink_keys_pdev);
+ pr_err("failed to create key device: %d\n", err);
+ goto err_unregister_leds;
}
return 0;
-err:
- gpiod_remove_lookup_table(&tink_keys_table);
- gpiod_remove_lookup_table(&tink_leds_table);
- return ret;
+err_unregister_leds:
+ platform_device_unregister(tink_leds_pdev);
+err_unregister_swnodes:
+ software_node_unregister_node_group(tink_swnodes);
+ return err;
}
module_init(tink_board_init);
@@ -219,8 +340,7 @@ static void __exit tink_board_exit(void)
{
platform_device_unregister(tink_keys_pdev);
platform_device_unregister(tink_leds_pdev);
- gpiod_remove_lookup_table(&tink_keys_table);
- gpiod_remove_lookup_table(&tink_leds_table);
+ software_node_unregister_node_group(tink_swnodes);
}
module_exit(tink_board_exit);
diff --git a/drivers/platform/x86/pcengines-apuv2.c b/drivers/platform/x86/pcengines-apuv2.c
index 3aa63b18a2e1..3b086863c6ac 100644
--- a/drivers/platform/x86/pcengines-apuv2.c
+++ b/drivers/platform/x86/pcengines-apuv2.c
@@ -12,13 +12,13 @@
#include <linux/dmi.h>
#include <linux/err.h>
+#include <linux/gpio/machine.h>
+#include <linux/gpio/property.h>
+#include <linux/input-event-codes.h>
#include <linux/kernel.h>
-#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
-#include <linux/gpio_keys.h>
-#include <linux/gpio/machine.h>
-#include <linux/input.h>
+#include <linux/property.h>
#include <linux/platform_data/gpio/gpio-amd-fch.h>
/*
@@ -72,60 +72,91 @@ static const struct amd_fch_gpio_pdata board_apu2 = {
.gpio_names = apu2_gpio_names,
};
+static const struct software_node apu2_gpiochip_node = {
+ .name = AMD_FCH_GPIO_DRIVER_NAME,
+};
+
/* GPIO LEDs device */
+static const struct software_node apu2_leds_node = {
+ .name = "apu2-leds",
+};
-static const struct gpio_led apu2_leds[] = {
- { .name = "apu:green:1" },
- { .name = "apu:green:2" },
- { .name = "apu:green:3" },
+static const struct property_entry apu2_led1_props[] = {
+ PROPERTY_ENTRY_STRING("label", "apu:green:1"),
+ PROPERTY_ENTRY_GPIO("gpios", &apu2_gpiochip_node,
+ APU2_GPIO_LINE_LED1, GPIO_ACTIVE_LOW),
+ { }
};
-static const struct gpio_led_platform_data apu2_leds_pdata = {
- .num_leds = ARRAY_SIZE(apu2_leds),
- .leds = apu2_leds,
+static const struct software_node apu2_led1_swnode = {
+ .name = "led-1",
+ .parent = &apu2_leds_node,
+ .properties = apu2_led1_props,
};
-static struct gpiod_lookup_table gpios_led_table = {
- .dev_id = "leds-gpio",
- .table = {
- GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED1,
- NULL, 0, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED2,
- NULL, 1, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED3,
- NULL, 2, GPIO_ACTIVE_LOW),
- {} /* Terminating entry */
- }
+static const struct property_entry apu2_led2_props[] = {
+ PROPERTY_ENTRY_STRING("label", "apu:green:2"),
+ PROPERTY_ENTRY_GPIO("gpios", &apu2_gpiochip_node,
+ APU2_GPIO_LINE_LED2, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node apu2_led2_swnode = {
+ .name = "led-2",
+ .parent = &apu2_leds_node,
+ .properties = apu2_led2_props,
+};
+
+static const struct property_entry apu2_led3_props[] = {
+ PROPERTY_ENTRY_STRING("label", "apu:green:3"),
+ PROPERTY_ENTRY_GPIO("gpios", &apu2_gpiochip_node,
+ APU2_GPIO_LINE_LED3, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node apu2_led3_swnode = {
+ .name = "led-3",
+ .parent = &apu2_leds_node,
+ .properties = apu2_led3_props,
};
/* GPIO keyboard device */
+static const struct property_entry apu2_keys_props[] = {
+ PROPERTY_ENTRY_U32("poll-interval", 100),
+ { }
+};
-static struct gpio_keys_button apu2_keys_buttons[] = {
- {
- .code = KEY_RESTART,
- .active_low = 1,
- .desc = "front button",
- .type = EV_KEY,
- .debounce_interval = 10,
- .value = 1,
- },
+static const struct software_node apu2_keys_node = {
+ .name = "apu2-keys",
+ .properties = apu2_keys_props,
};
-static const struct gpio_keys_platform_data apu2_keys_pdata = {
- .buttons = apu2_keys_buttons,
- .nbuttons = ARRAY_SIZE(apu2_keys_buttons),
- .poll_interval = 100,
- .rep = 0,
- .name = "apu2-keys",
+static const struct property_entry apu2_front_button_props[] = {
+ PROPERTY_ENTRY_STRING("label", "front button"),
+ PROPERTY_ENTRY_U32("linux,code", KEY_RESTART),
+ PROPERTY_ENTRY_GPIO("gpios", &apu2_gpiochip_node,
+ APU2_GPIO_LINE_MODESW, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 10),
+ { }
};
-static struct gpiod_lookup_table gpios_key_table = {
- .dev_id = "gpio-keys-polled",
- .table = {
- GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_MODESW,
- NULL, 0, GPIO_ACTIVE_LOW),
- {} /* Terminating entry */
- }
+static const struct software_node apu2_front_button_swnode = {
+ .name = "front-button",
+ .parent = &apu2_keys_node,
+ .properties = apu2_front_button_props,
+};
+
+static const struct software_node *apu2_swnodes[] = {
+ &apu2_gpiochip_node,
+ /* LEDs nodes */
+ &apu2_leds_node,
+ &apu2_led1_swnode,
+ &apu2_led2_swnode,
+ &apu2_led3_swnode,
+ /* Keys nodes */
+ &apu2_keys_node,
+ &apu2_front_button_swnode,
+ NULL
};
/* Board setup */
@@ -222,23 +253,25 @@ static struct platform_device *apu_gpio_pdev;
static struct platform_device *apu_leds_pdev;
static struct platform_device *apu_keys_pdev;
-static struct platform_device * __init apu_create_pdev(
- const char *name,
- const void *pdata,
- size_t sz)
+static struct platform_device * __init apu_create_pdev(const char *name,
+ const void *data, size_t size,
+ const struct software_node *swnode)
{
+ struct platform_device_info pdev_info = {
+ .name = name,
+ .id = PLATFORM_DEVID_NONE,
+ .data = data,
+ .size_data = size,
+ .fwnode = software_node_fwnode(swnode),
+ };
struct platform_device *pdev;
+ int err;
- pdev = platform_device_register_resndata(NULL,
- name,
- PLATFORM_DEVID_NONE,
- NULL,
- 0,
- pdata,
- sz);
+ pdev = platform_device_register_full(&pdev_info);
- if (IS_ERR(pdev))
- pr_err("failed registering %s: %ld\n", name, PTR_ERR(pdev));
+ err = PTR_ERR_OR_ZERO(pdev);
+ if (err)
+ pr_err("failed registering %s: %d\n", name, err);
return pdev;
}
@@ -246,6 +279,7 @@ static struct platform_device * __init apu_create_pdev(
static int __init apu_board_init(void)
{
const struct dmi_system_id *id;
+ int err;
id = dmi_first_match(apu_gpio_dmi_table);
if (!id) {
@@ -253,35 +287,45 @@ static int __init apu_board_init(void)
return -ENODEV;
}
- gpiod_add_lookup_table(&gpios_led_table);
- gpiod_add_lookup_table(&gpios_key_table);
+ err = software_node_register_node_group(apu2_swnodes);
+ if (err) {
+ pr_err("failed to register software nodes: %d\n", err);
+ return err;
+ }
- apu_gpio_pdev = apu_create_pdev(
- AMD_FCH_GPIO_DRIVER_NAME,
- id->driver_data,
- sizeof(struct amd_fch_gpio_pdata));
+ apu_gpio_pdev = apu_create_pdev(AMD_FCH_GPIO_DRIVER_NAME,
+ id->driver_data, sizeof(struct amd_fch_gpio_pdata), NULL);
+ err = PTR_ERR_OR_ZERO(apu_gpio_pdev);
+ if (err)
+ goto err_unregister_swnodes;
- apu_leds_pdev = apu_create_pdev(
- "leds-gpio",
- &apu2_leds_pdata,
- sizeof(apu2_leds_pdata));
+ apu_leds_pdev = apu_create_pdev("leds-gpio", NULL, 0, &apu2_leds_node);
+ err = PTR_ERR_OR_ZERO(apu_leds_pdev);
+ if (err)
+ goto err_unregister_gpio;
- apu_keys_pdev = apu_create_pdev(
- "gpio-keys-polled",
- &apu2_keys_pdata,
- sizeof(apu2_keys_pdata));
+ apu_keys_pdev = apu_create_pdev("gpio-keys-polled", NULL, 0, &apu2_keys_node);
+ err = PTR_ERR_OR_ZERO(apu_keys_pdev);
+ if (err)
+ goto err_unregister_leds;
return 0;
+
+err_unregister_leds:
+ platform_device_unregister(apu_leds_pdev);
+err_unregister_gpio:
+ platform_device_unregister(apu_gpio_pdev);
+err_unregister_swnodes:
+ software_node_unregister_node_group(apu2_swnodes);
+ return err;
}
static void __exit apu_board_exit(void)
{
- gpiod_remove_lookup_table(&gpios_led_table);
- gpiod_remove_lookup_table(&gpios_key_table);
-
platform_device_unregister(apu_keys_pdev);
platform_device_unregister(apu_leds_pdev);
platform_device_unregister(apu_gpio_pdev);
+ software_node_unregister_node_group(apu2_swnodes);
}
module_init(apu_board_init);
diff --git a/drivers/platform/x86/portwell-ec.c b/drivers/platform/x86/portwell-ec.c
index 322f296e9315..ac506ea40eff 100644
--- a/drivers/platform/x86/portwell-ec.c
+++ b/drivers/platform/x86/portwell-ec.c
@@ -5,15 +5,13 @@
* Tested on:
* - Portwell NANO-6064
*
- * This driver provides support for GPIO and Watchdog Timer
- * functionalities of the Portwell boards with ITE embedded controller (EC).
+ * This driver supports Portwell boards with an ITE embedded controller (EC).
* The EC is accessed through I/O ports and provides:
+ * - Temperature and voltage readings (hwmon)
* - 8 GPIO pins for control and monitoring
* - Hardware watchdog with 1-15300 second timeout range
*
- * It integrates with the Linux GPIO and Watchdog subsystems, allowing
- * userspace interaction with EC GPIO pins and watchdog control,
- * ensuring system stability and configurability.
+ * It integrates with the Linux hwmon, GPIO and Watchdog subsystems.
*
* (C) Copyright 2025 Portwell, Inc.
* Author: Yen-Chi Huang (jesse.huang@portwell.com.tw)
@@ -22,16 +20,20 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
+#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/dmi.h>
#include <linux/gpio/driver.h>
+#include <linux/hwmon.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/pm.h>
#include <linux/sizes.h>
#include <linux/string.h>
+#include <linux/units.h>
#include <linux/watchdog.h>
#define PORTWELL_EC_IOSPACE 0xe300
@@ -41,6 +43,9 @@
#define PORTWELL_GPIO_DIR_REG 0x2b
#define PORTWELL_GPIO_VAL_REG 0x2c
+#define PORTWELL_HWMON_TEMP_NUM 3
+#define PORTWELL_HWMON_VOLT_NUM 5
+
#define PORTWELL_WDT_EC_CONFIG_ADDR 0x06
#define PORTWELL_WDT_CONFIG_ENABLE 0x1
#define PORTWELL_WDT_CONFIG_DISABLE 0x0
@@ -52,16 +57,60 @@
#define PORTWELL_EC_FW_VENDOR_LENGTH 3
#define PORTWELL_EC_FW_VENDOR_NAME "PWG"
+#define PORTWELL_EC_ADC_MAX 1023
+
static bool force;
module_param(force, bool, 0444);
MODULE_PARM_DESC(force, "Force loading EC driver without checking DMI boardname");
+/* A sensor's metadata (label, scale, and register) */
+struct pwec_sensor_prop {
+ const char *label;
+ u8 reg;
+ u32 scale;
+};
+
+/* Master configuration with properties for all possible sensors */
+static const struct {
+ const struct pwec_sensor_prop temp_props[PORTWELL_HWMON_TEMP_NUM];
+ const struct pwec_sensor_prop in_props[PORTWELL_HWMON_VOLT_NUM];
+} pwec_master_data = {
+ .temp_props = {
+ { "CPU Temperature", 0x00, 0 },
+ { "System Temperature", 0x02, 0 },
+ { "Aux Temperature", 0x04, 0 },
+ },
+ .in_props = {
+ { "Vcore", 0x20, 3000 },
+ { "3.3V", 0x22, 6000 },
+ { "5V", 0x24, 9600 },
+ { "12V", 0x30, 19800 },
+ { "VDIMM", 0x32, 3000 },
+ },
+};
+
+struct pwec_board_info {
+ u32 temp_mask; /* bit N = temperature channel N */
+ u32 in_mask; /* bit N = voltage channel N */
+};
+
+static const struct pwec_board_info pwec_board_info_default = {
+ .temp_mask = GENMASK(PORTWELL_HWMON_TEMP_NUM - 1, 0),
+ .in_mask = GENMASK(PORTWELL_HWMON_VOLT_NUM - 1, 0),
+};
+
+static const struct pwec_board_info pwec_board_info_nano = {
+ .temp_mask = BIT(0) | BIT(1),
+ .in_mask = GENMASK(4, 0),
+};
+
static const struct dmi_system_id pwec_dmi_table[] = {
{
.ident = "NANO-6064 series",
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "NANO-6064"),
},
+ .driver_data = (void *)&pwec_board_info_nano,
},
{ }
};
@@ -79,6 +128,20 @@ static u8 pwec_read(u8 address)
return inb(PORTWELL_EC_IOSPACE + address);
}
+/* Ensure consistent 16-bit read across potential MSB rollover. */
+static u16 pwec_read16_stable(u8 lsb_reg)
+{
+ u8 lsb, msb, old_msb;
+
+ do {
+ old_msb = pwec_read(lsb_reg + 1);
+ lsb = pwec_read(lsb_reg);
+ msb = pwec_read(lsb_reg + 1);
+ } while (msb != old_msb);
+
+ return (msb << 8) | lsb;
+}
+
/* GPIO functions */
static int pwec_gpio_get(struct gpio_chip *chip, unsigned int offset)
@@ -204,6 +267,81 @@ static struct watchdog_device ec_wdt_dev = {
.max_timeout = PORTWELL_WDT_EC_MAX_COUNT_SECOND,
};
+/* HWMON functions */
+
+static umode_t pwec_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct pwec_board_info *info = drvdata;
+
+ switch (type) {
+ case hwmon_temp:
+ return (info->temp_mask & BIT(channel)) ? 0444 : 0;
+ case hwmon_in:
+ return (info->in_mask & BIT(channel)) ? 0444 : 0;
+ default:
+ return 0;
+ }
+}
+
+static int pwec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ u16 tmp16;
+
+ switch (type) {
+ case hwmon_temp:
+ *val = pwec_read(pwec_master_data.temp_props[channel].reg) * MILLIDEGREE_PER_DEGREE;
+ return 0;
+ case hwmon_in:
+ tmp16 = pwec_read16_stable(pwec_master_data.in_props[channel].reg);
+ *val = (tmp16 * pwec_master_data.in_props[channel].scale) / PORTWELL_EC_ADC_MAX;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int pwec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_temp:
+ *str = pwec_master_data.temp_props[channel].label;
+ return 0;
+ case hwmon_in:
+ *str = pwec_master_data.in_props[channel].label;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static const struct hwmon_channel_info *pwec_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL),
+ NULL
+};
+
+static const struct hwmon_ops pwec_hwmon_ops = {
+ .is_visible = pwec_hwmon_is_visible,
+ .read = pwec_hwmon_read,
+ .read_string = pwec_hwmon_read_string,
+};
+
+static const struct hwmon_chip_info pwec_chip_info = {
+ .ops = &pwec_hwmon_ops,
+ .info = pwec_hwmon_info,
+};
+
static int pwec_firmware_vendor_check(void)
{
u8 buf[PORTWELL_EC_FW_VENDOR_LENGTH + 1];
@@ -218,6 +356,8 @@ static int pwec_firmware_vendor_check(void)
static int pwec_probe(struct platform_device *pdev)
{
+ struct device *hwmon_dev;
+ void *drvdata = dev_get_platdata(&pdev->dev);
int ret;
if (!devm_request_region(&pdev->dev, PORTWELL_EC_IOSPACE,
@@ -236,19 +376,40 @@ static int pwec_probe(struct platform_device *pdev)
return ret;
}
- ec_wdt_dev.parent = &pdev->dev;
- ret = devm_watchdog_register_device(&pdev->dev, &ec_wdt_dev);
- if (ret < 0) {
- dev_err(&pdev->dev, "failed to register Portwell EC Watchdog\n");
- return ret;
+ if (IS_REACHABLE(CONFIG_HWMON)) {
+ hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
+ "portwell_ec", drvdata, &pwec_chip_info, NULL);
+ ret = PTR_ERR_OR_ZERO(hwmon_dev);
+ if (ret)
+ return ret;
}
+ ec_wdt_dev.parent = &pdev->dev;
+ return devm_watchdog_register_device(&pdev->dev, &ec_wdt_dev);
+}
+
+static int pwec_suspend(struct device *dev)
+{
+ if (watchdog_active(&ec_wdt_dev))
+ return pwec_wdt_stop(&ec_wdt_dev);
+
return 0;
}
+static int pwec_resume(struct device *dev)
+{
+ if (watchdog_active(&ec_wdt_dev))
+ return pwec_wdt_start(&ec_wdt_dev);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(pwec_dev_pm_ops, pwec_suspend, pwec_resume);
+
static struct platform_driver pwec_driver = {
.driver = {
.name = "portwell-ec",
+ .pm = pm_sleep_ptr(&pwec_dev_pm_ops),
},
.probe = pwec_probe,
};
@@ -257,19 +418,26 @@ static struct platform_device *pwec_dev;
static int __init pwec_init(void)
{
+ const struct dmi_system_id *match;
+ const struct pwec_board_info *hwmon_data;
int ret;
- if (!dmi_check_system(pwec_dmi_table)) {
+ match = dmi_first_match(pwec_dmi_table);
+ if (!match) {
if (!force)
return -ENODEV;
- pr_warn("force load portwell-ec without DMI check\n");
+ hwmon_data = &pwec_board_info_default;
+ pr_warn("force load portwell-ec without DMI check, using full display config\n");
+ } else {
+ hwmon_data = match->driver_data;
}
ret = platform_driver_register(&pwec_driver);
if (ret)
return ret;
- pwec_dev = platform_device_register_simple("portwell-ec", -1, NULL, 0);
+ pwec_dev = platform_device_register_data(NULL, "portwell-ec", PLATFORM_DEVID_NONE,
+ hwmon_data, sizeof(*hwmon_data));
if (IS_ERR(pwec_dev)) {
platform_driver_unregister(&pwec_driver);
return PTR_ERR(pwec_dev);
diff --git a/drivers/platform/x86/quickstart.c b/drivers/platform/x86/quickstart.c
index c332c7cdaff5..acb58518be37 100644
--- a/drivers/platform/x86/quickstart.c
+++ b/drivers/platform/x86/quickstart.c
@@ -154,13 +154,6 @@ static void quickstart_notify_remove(void *context)
acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, quickstart_notify);
}
-static void quickstart_mutex_destroy(void *data)
-{
- struct mutex *lock = data;
-
- mutex_destroy(lock);
-}
-
static int quickstart_probe(struct platform_device *pdev)
{
struct quickstart_data *data;
@@ -179,8 +172,7 @@ static int quickstart_probe(struct platform_device *pdev)
data->dev = &pdev->dev;
dev_set_drvdata(&pdev->dev, data);
- mutex_init(&data->input_lock);
- ret = devm_add_action_or_reset(&pdev->dev, quickstart_mutex_destroy, &data->input_lock);
+ ret = devm_mutex_init(&pdev->dev, &data->input_lock);
if (ret < 0)
return ret;
diff --git a/drivers/platform/x86/redmi-wmi.c b/drivers/platform/x86/redmi-wmi.c
new file mode 100644
index 000000000000..949236b93a32
--- /dev/null
+++ b/drivers/platform/x86/redmi-wmi.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+/* WMI driver for Xiaomi Redmibooks */
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/unaligned.h>
+#include <linux/wmi.h>
+
+#include <uapi/linux/input-event-codes.h>
+
+#define WMI_REDMIBOOK_KEYBOARD_EVENT_GUID "46C93E13-EE9B-4262-8488-563BCA757FEF"
+
+#define AI_KEY_VALUE_MASK BIT(8)
+
+static const struct key_entry redmi_wmi_keymap[] = {
+ {KE_KEY, 0x00000201, {KEY_SELECTIVE_SCREENSHOT}},
+ {KE_KEY, 0x00000301, {KEY_ALL_APPLICATIONS}},
+ {KE_KEY, 0x00001b01, {KEY_SETUP}},
+
+ /* AI button has code for each position */
+ {KE_KEY, 0x00011801, {KEY_ASSISTANT}},
+ {KE_KEY, 0x00011901, {KEY_ASSISTANT}},
+
+ /* Keyboard backlight */
+ {KE_IGNORE, 0x00000501, {}},
+ {KE_IGNORE, 0x00800501, {}},
+ {KE_IGNORE, 0x00050501, {}},
+ {KE_IGNORE, 0x000a0501, {}},
+
+ {KE_END}
+};
+
+struct redmi_wmi {
+ struct input_dev *input_dev;
+ /* Protects the key event sequence */
+ struct mutex key_lock;
+};
+
+static int redmi_wmi_probe(struct wmi_device *wdev, const void *context)
+{
+ struct redmi_wmi *data;
+ int err;
+
+ /* Init dev */
+ data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ dev_set_drvdata(&wdev->dev, data);
+
+ err = devm_mutex_init(&wdev->dev, &data->key_lock);
+ if (err)
+ return err;
+
+ data->input_dev = devm_input_allocate_device(&wdev->dev);
+ if (!data->input_dev)
+ return -ENOMEM;
+
+ data->input_dev->name = "Redmibook WMI keys";
+ data->input_dev->phys = "wmi/input0";
+
+ err = sparse_keymap_setup(data->input_dev, redmi_wmi_keymap, NULL);
+ if (err)
+ return err;
+
+ return input_register_device(data->input_dev);
+}
+
+static void redmi_wmi_notify(struct wmi_device *wdev, union acpi_object *obj)
+{
+ struct key_entry *entry;
+ struct redmi_wmi *data = dev_get_drvdata(&wdev->dev);
+ bool autorelease = true;
+ u32 payload;
+ int value = 1;
+
+ if (obj->type != ACPI_TYPE_BUFFER) {
+ dev_err(&wdev->dev, "Bad response type %u\n", obj->type);
+ return;
+ }
+
+ if (obj->buffer.length < 32) {
+ dev_err(&wdev->dev, "Invalid buffer length %u\n", obj->buffer.length);
+ return;
+ }
+
+ payload = get_unaligned_le32(obj->buffer.pointer);
+ entry = sparse_keymap_entry_from_scancode(data->input_dev, payload);
+
+ if (!entry) {
+ dev_dbg(&wdev->dev, "Unknown WMI event with payload %u", payload);
+ return;
+ }
+
+ /* AI key quirk */
+ if (entry->keycode == KEY_ASSISTANT) {
+ value = !(payload & AI_KEY_VALUE_MASK);
+ autorelease = false;
+ }
+
+ guard(mutex)(&data->key_lock);
+ sparse_keymap_report_entry(data->input_dev, entry, value, autorelease);
+}
+
+static const struct wmi_device_id redmi_wmi_id_table[] = {
+ { WMI_REDMIBOOK_KEYBOARD_EVENT_GUID, NULL },
+ { }
+};
+
+static struct wmi_driver redmi_wmi_driver = {
+ .driver = {
+ .name = "redmi-wmi",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = redmi_wmi_id_table,
+ .probe = redmi_wmi_probe,
+ .notify = redmi_wmi_notify,
+ .no_singleton = true,
+};
+module_wmi_driver(redmi_wmi_driver);
+
+MODULE_DEVICE_TABLE(wmi, redmi_wmi_id_table);
+MODULE_AUTHOR("Gladyshev Ilya <foxido@foxido.dev>");
+MODULE_DESCRIPTION("Redmibook WMI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/x86-android-tablets/Makefile b/drivers/platform/x86/x86-android-tablets/Makefile
index 313be30548bc..a2cf8cbdb351 100644
--- a/drivers/platform/x86/x86-android-tablets/Makefile
+++ b/drivers/platform/x86/x86-android-tablets/Makefile
@@ -6,4 +6,4 @@
obj-$(CONFIG_X86_ANDROID_TABLETS) += vexia_atla10_ec.o
obj-$(CONFIG_X86_ANDROID_TABLETS) += x86-android-tablets.o
x86-android-tablets-y := core.o dmi.o shared-psy-info.o \
- asus.o lenovo.o other.o
+ acer.o asus.o lenovo.o other.o
diff --git a/drivers/platform/x86/x86-android-tablets/acer.c b/drivers/platform/x86/x86-android-tablets/acer.c
new file mode 100644
index 000000000000..d48c70ffd992
--- /dev/null
+++ b/drivers/platform/x86/x86-android-tablets/acer.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Board info for Acer X86 tablets which ship with Android as the factory image
+ * and which have broken DSDT tables. The factory kernels shipped on these
+ * devices typically have a bunch of things hardcoded, rather than specified
+ * in their DSDT.
+ *
+ * Copyright (C) 2021-2025 Hans de Goede <hansg@kernel.org>
+ */
+
+#include <linux/gpio/machine.h>
+#include <linux/gpio/property.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+#include "shared-psy-info.h"
+#include "x86-android-tablets.h"
+
+/* Acer Iconia One 8 A1-840 (non FHD version) */
+static const struct property_entry acer_a1_840_bq24190_props[] = {
+ PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node),
+ PROPERTY_ENTRY_BOOL("omit-battery-class"),
+ PROPERTY_ENTRY_BOOL("disable-reset"),
+ { }
+};
+
+static const struct software_node acer_a1_840_bq24190_node = {
+ .properties = acer_a1_840_bq24190_props,
+};
+
+static const struct property_entry acer_a1_840_touchscreen_props[] = {
+ PROPERTY_ENTRY_U32("touchscreen-size-x", 800),
+ PROPERTY_ENTRY_U32("touchscreen-size-y", 1280),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node acer_a1_840_touchscreen_node = {
+ .properties = acer_a1_840_touchscreen_props,
+};
+
+static const struct x86_i2c_client_info acer_a1_840_i2c_clients[] __initconst = {
+ {
+ /* BQ24297 charger IC */
+ .board_info = {
+ .type = "bq24297",
+ .addr = 0x6b,
+ .dev_name = "bq24297",
+ .swnode = &acer_a1_840_bq24190_node,
+ .platform_data = &bq24190_pdata,
+ },
+ .adapter_path = "\\_SB_.I2C1",
+ .irq_data = {
+ .type = X86_ACPI_IRQ_TYPE_GPIOINT,
+ .chip = "INT33FC:02",
+ .index = 2,
+ .trigger = ACPI_EDGE_SENSITIVE,
+ .polarity = ACPI_ACTIVE_LOW,
+ .con_id = "bq24297_irq",
+ },
+ }, {
+ /* MPU6515 sensors */
+ .board_info = {
+ .type = "mpu6515",
+ .addr = 0x69,
+ .dev_name = "mpu6515",
+ },
+ .adapter_path = "\\_SB_.I2C3",
+ .irq_data = {
+ .type = X86_ACPI_IRQ_TYPE_APIC,
+ .index = 0x47,
+ .trigger = ACPI_EDGE_SENSITIVE,
+ .polarity = ACPI_ACTIVE_HIGH,
+ },
+ }, {
+ /* FT5416 touchscreen controller */
+ .board_info = {
+ .type = "edt-ft5x06",
+ .addr = 0x38,
+ .dev_name = "ft5416",
+ .swnode = &acer_a1_840_touchscreen_node,
+ },
+ .adapter_path = "\\_SB_.I2C4",
+ .irq_data = {
+ .type = X86_ACPI_IRQ_TYPE_APIC,
+ .index = 0x45,
+ .trigger = ACPI_EDGE_SENSITIVE,
+ .polarity = ACPI_ACTIVE_HIGH,
+ },
+ }
+};
+
+static const struct property_entry acer_a1_840_int3496_props[] __initconst = {
+ PROPERTY_ENTRY_GPIO("mux-gpios", &baytrail_gpiochip_nodes[2], 1, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 18, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct platform_device_info acer_a1_840_pdevs[] __initconst = {
+ {
+ /* For micro USB ID pin handling */
+ .name = "intel-int3496",
+ .id = PLATFORM_DEVID_NONE,
+ .properties = acer_a1_840_int3496_props,
+ },
+};
+
+/* Properties for the Dollar Cove TI PMIC battery MFD child used as fuel-gauge */
+static const struct property_entry acer_a1_840_fg_props[] = {
+ PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node),
+ PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", bq24190_psy, 1),
+ PROPERTY_ENTRY_GPIO("charged-gpios", &baytrail_gpiochip_nodes[2], 10, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static struct device *acer_a1_840_fg_dev;
+static struct fwnode_handle *acer_a1_840_fg_node;
+
+static int __init acer_a1_840_init(struct device *dev)
+{
+ int ret;
+
+ acer_a1_840_fg_dev = bus_find_device_by_name(&platform_bus_type, NULL, "chtdc_ti_battery");
+ if (!acer_a1_840_fg_dev)
+ return dev_err_probe(dev, -EPROBE_DEFER, "getting chtdc_ti_battery dev\n");
+
+ acer_a1_840_fg_node = fwnode_create_software_node(acer_a1_840_fg_props, NULL);
+ if (IS_ERR(acer_a1_840_fg_node)) {
+ ret = PTR_ERR(acer_a1_840_fg_node);
+ goto err_put;
+ }
+
+ ret = device_add_software_node(acer_a1_840_fg_dev,
+ to_software_node(acer_a1_840_fg_node));
+ if (ret)
+ goto err_put;
+
+ return 0;
+
+err_put:
+ fwnode_handle_put(acer_a1_840_fg_node);
+ acer_a1_840_fg_node = NULL;
+ put_device(acer_a1_840_fg_dev);
+ acer_a1_840_fg_dev = NULL;
+ return ret;
+}
+
+static void acer_a1_840_exit(void)
+{
+ device_remove_software_node(acer_a1_840_fg_dev);
+ /*
+ * Skip fwnode_handle_put(acer_a1_840_fg_node), instead leak the node.
+ * The intel_dc_ti_battery driver may still reference the strdup-ed
+ * "supplied-from" string. This string will be free-ed if the node
+ * is released.
+ */
+ acer_a1_840_fg_node = NULL;
+ put_device(acer_a1_840_fg_dev);
+ acer_a1_840_fg_dev = NULL;
+}
+
+static const char * const acer_a1_840_modules[] __initconst = {
+ "bq24190_charger", /* For the Vbus regulator for intel-int3496 */
+ NULL
+};
+
+const struct x86_dev_info acer_a1_840_info __initconst = {
+ .i2c_client_info = acer_a1_840_i2c_clients,
+ .i2c_client_count = ARRAY_SIZE(acer_a1_840_i2c_clients),
+ .pdev_info = acer_a1_840_pdevs,
+ .pdev_count = ARRAY_SIZE(acer_a1_840_pdevs),
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
+ .swnode_group = generic_lipo_4v2_battery_swnodes,
+ .modules = acer_a1_840_modules,
+ .init = acer_a1_840_init,
+ .exit = acer_a1_840_exit,
+};
+
+/* Acer Iconia One 7 B1-750 has an Android factory image with everything hardcoded */
+static const char * const acer_b1_750_mount_matrix[] = {
+ "-1", "0", "0",
+ "0", "1", "0",
+ "0", "0", "1"
+};
+
+static const struct property_entry acer_b1_750_bma250e_props[] = {
+ PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", acer_b1_750_mount_matrix),
+ { }
+};
+
+static const struct software_node acer_b1_750_bma250e_node = {
+ .properties = acer_b1_750_bma250e_props,
+};
+
+static const struct property_entry acer_b1_750_novatek_props[] = {
+ PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node acer_b1_750_novatek_node = {
+ .properties = acer_b1_750_novatek_props,
+};
+
+static const struct x86_i2c_client_info acer_b1_750_i2c_clients[] __initconst = {
+ {
+ /* Novatek NVT-ts touchscreen */
+ .board_info = {
+ .type = "nt11205-ts",
+ .addr = 0x34,
+ .dev_name = "NVT-ts",
+ .swnode = &acer_b1_750_novatek_node,
+ },
+ .adapter_path = "\\_SB_.I2C4",
+ .irq_data = {
+ .type = X86_ACPI_IRQ_TYPE_GPIOINT,
+ .chip = "INT33FC:02",
+ .index = 3,
+ .trigger = ACPI_EDGE_SENSITIVE,
+ .polarity = ACPI_ACTIVE_LOW,
+ .con_id = "NVT-ts_irq",
+ },
+ }, {
+ /* BMA250E accelerometer */
+ .board_info = {
+ .type = "bma250e",
+ .addr = 0x18,
+ .swnode = &acer_b1_750_bma250e_node,
+ },
+ .adapter_path = "\\_SB_.I2C3",
+ .irq_data = {
+ .type = X86_ACPI_IRQ_TYPE_GPIOINT,
+ .chip = "INT33FC:02",
+ .index = 25,
+ .trigger = ACPI_LEVEL_SENSITIVE,
+ .polarity = ACPI_ACTIVE_HIGH,
+ .con_id = "bma250e_irq",
+ },
+ },
+};
+
+const struct x86_dev_info acer_b1_750_info __initconst = {
+ .i2c_client_info = acer_b1_750_i2c_clients,
+ .i2c_client_count = ARRAY_SIZE(acer_b1_750_i2c_clients),
+ .pdev_info = int3496_pdevs,
+ .pdev_count = 1,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
+};
diff --git a/drivers/platform/x86/x86-android-tablets/asus.c b/drivers/platform/x86/x86-android-tablets/asus.c
index 97cd14c1fd23..7d29c7654d21 100644
--- a/drivers/platform/x86/x86-android-tablets/asus.c
+++ b/drivers/platform/x86/x86-android-tablets/asus.c
@@ -5,36 +5,55 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#include <linux/gpio/machine.h>
-#include <linux/input.h>
+#include <linux/gpio/property.h>
+#include <linux/input-event-codes.h>
#include <linux/platform_device.h>
#include "shared-psy-info.h"
#include "x86-android-tablets.h"
/* Asus ME176C and TF103C tablets shared data */
-static struct gpiod_lookup_table int3496_gpo2_pin22_gpios = {
- .dev_id = "intel-int3496",
- .table = {
- GPIO_LOOKUP("INT33FC:02", 22, "id", GPIO_ACTIVE_HIGH),
- { }
- },
+static const struct property_entry asus_me176c_tf103c_int3496_props[] __initconst = {
+ PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 22, GPIO_ACTIVE_HIGH),
+ { }
};
-static const struct x86_gpio_button asus_me176c_tf103c_lid __initconst = {
- .button = {
- .code = SW_LID,
- .active_low = true,
- .desc = "lid_sw",
- .type = EV_SW,
- .wakeup = true,
- .debounce_interval = 50,
+static const struct platform_device_info asus_me176c_tf103c_pdevs[] __initconst = {
+ {
+ /* For micro USB ID pin handling */
+ .name = "intel-int3496",
+ .id = PLATFORM_DEVID_NONE,
+ .properties = asus_me176c_tf103c_int3496_props,
},
- .chip = "INT33FC:02",
- .pin = 12,
+};
+
+static const struct software_node asus_me176c_tf103c_gpio_keys_node = {
+ .name = "lid_sw",
+};
+
+static const struct property_entry asus_me176c_tf103c_lid_props[] = {
+ PROPERTY_ENTRY_U32("linux,input-type", EV_SW),
+ PROPERTY_ENTRY_U32("linux,code", SW_LID),
+ PROPERTY_ENTRY_STRING("label", "lid_sw"),
+ PROPERTY_ENTRY_GPIO("gpios", &baytrail_gpiochip_nodes[2], 12, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 50),
+ PROPERTY_ENTRY_BOOL("wakeup-source"),
+ { }
+};
+
+static const struct software_node asus_me176c_tf103c_lid_node = {
+ .parent = &asus_me176c_tf103c_gpio_keys_node,
+ .properties = asus_me176c_tf103c_lid_props,
+};
+
+static const struct software_node *asus_me176c_tf103c_lid_swnodes[] = {
+ &asus_me176c_tf103c_gpio_keys_node,
+ &asus_me176c_tf103c_lid_node,
+ NULL
};
/* Asus ME176C tablets have an Android factory image with everything hardcoded */
@@ -77,6 +96,16 @@ static const struct software_node asus_me176c_ug3105_node = {
.properties = asus_me176c_ug3105_props,
};
+static const struct property_entry asus_me176c_touchscreen_props[] = {
+ PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[0], 60, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("irq-gpios", &baytrail_gpiochip_nodes[2], 28, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node asus_me176c_touchscreen_node = {
+ .properties = asus_me176c_touchscreen_props,
+};
+
static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = {
{
/* bq24297 battery charger */
@@ -132,6 +161,7 @@ static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst =
.type = "GDIX1001:00",
.addr = 0x14,
.dev_name = "goodix_ts",
+ .swnode = &asus_me176c_touchscreen_node,
},
.adapter_path = "\\_SB_.I2C6",
.irq_data = {
@@ -152,33 +182,17 @@ static const struct x86_serdev_info asus_me176c_serdevs[] __initconst = {
},
};
-static struct gpiod_lookup_table asus_me176c_goodix_gpios = {
- .dev_id = "i2c-goodix_ts",
- .table = {
- GPIO_LOOKUP("INT33FC:00", 60, "reset", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FC:02", 28, "irq", GPIO_ACTIVE_HIGH),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const asus_me176c_gpios[] = {
- &int3496_gpo2_pin22_gpios,
- &asus_me176c_goodix_gpios,
- NULL
-};
-
const struct x86_dev_info asus_me176c_info __initconst = {
.i2c_client_info = asus_me176c_i2c_clients,
.i2c_client_count = ARRAY_SIZE(asus_me176c_i2c_clients),
- .pdev_info = int3496_pdevs,
- .pdev_count = 1,
+ .pdev_info = asus_me176c_tf103c_pdevs,
+ .pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs),
.serdev_info = asus_me176c_serdevs,
.serdev_count = ARRAY_SIZE(asus_me176c_serdevs),
- .gpio_button = &asus_me176c_tf103c_lid,
- .gpio_button_count = 1,
- .gpiod_lookup_tables = asus_me176c_gpios,
- .bat_swnode = &generic_lipo_hv_4v35_battery_node,
+ .gpio_button_swnodes = asus_me176c_tf103c_lid_swnodes,
+ .swnode_group = generic_lipo_hv_4v35_battery_swnodes,
.modules = bq24190_modules,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/* Asus TF103C tablets have an Android factory image with everything hardcoded */
@@ -293,19 +307,13 @@ static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst =
},
};
-static struct gpiod_lookup_table * const asus_tf103c_gpios[] = {
- &int3496_gpo2_pin22_gpios,
- NULL
-};
-
const struct x86_dev_info asus_tf103c_info __initconst = {
.i2c_client_info = asus_tf103c_i2c_clients,
.i2c_client_count = ARRAY_SIZE(asus_tf103c_i2c_clients),
- .pdev_info = int3496_pdevs,
- .pdev_count = 1,
- .gpio_button = &asus_me176c_tf103c_lid,
- .gpio_button_count = 1,
- .gpiod_lookup_tables = asus_tf103c_gpios,
- .bat_swnode = &generic_lipo_4v2_battery_node,
+ .pdev_info = asus_me176c_tf103c_pdevs,
+ .pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs),
+ .gpio_button_swnodes = asus_me176c_tf103c_lid_swnodes,
+ .swnode_group = generic_lipo_4v2_battery_swnodes,
.modules = bq24190_modules,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c
index 2a9c47178505..6588fae30356 100644
--- a/drivers/platform/x86/x86-android-tablets/core.c
+++ b/drivers/platform/x86/x86-android-tablets/core.c
@@ -5,7 +5,7 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -152,9 +152,9 @@ static struct i2c_client **i2c_clients;
static struct spi_device **spi_devs;
static struct platform_device **pdevs;
static struct serdev_device **serdevs;
-static struct gpio_keys_button *buttons;
-static struct gpiod_lookup_table * const *gpiod_lookup_tables;
-static const struct software_node *bat_swnode;
+static const struct software_node **gpio_button_swnodes;
+static const struct software_node **swnode_group;
+static const struct software_node **gpiochip_node_group;
static void (*exit_handler)(void);
static __init struct i2c_adapter *
@@ -265,8 +265,7 @@ static __init int x86_instantiate_spi_dev(const struct x86_dev_info *dev_info, i
spi_devs[idx] = spi_new_device(controller, &board_info);
put_device(&controller->dev);
if (!spi_devs[idx])
- return dev_err_probe(&controller->dev, -ENOMEM,
- "creating SPI-device %d\n", idx);
+ return -ENOMEM;
return 0;
}
@@ -277,8 +276,10 @@ get_serdev_controller_by_pci_parent(const struct x86_serdev_info *info)
struct pci_dev *pdev;
pdev = pci_get_domain_bus_and_slot(0, 0, info->ctrl.pci.devfn);
- if (!pdev)
- return ERR_PTR(-EPROBE_DEFER);
+ if (!pdev) {
+ pr_err("error could not get PCI serdev at devfn 0x%02x\n", info->ctrl.pci.devfn);
+ return ERR_PTR(-ENODEV);
+ }
/* This puts our reference on pdev and returns a ref on the ctrl */
return get_serdev_controller_from_parent(&pdev->dev, 0, info->ctrl_devname);
@@ -331,6 +332,34 @@ put_ctrl_dev:
return ret;
}
+const struct software_node baytrail_gpiochip_nodes[] = {
+ { .name = "INT33FC:00" },
+ { .name = "INT33FC:01" },
+ { .name = "INT33FC:02" },
+};
+
+static const struct software_node *baytrail_gpiochip_node_group[] = {
+ &baytrail_gpiochip_nodes[0],
+ &baytrail_gpiochip_nodes[1],
+ &baytrail_gpiochip_nodes[2],
+ NULL
+};
+
+const struct software_node cherryview_gpiochip_nodes[] = {
+ { .name = "INT33FF:00" },
+ { .name = "INT33FF:01" },
+ { .name = "INT33FF:02" },
+ { .name = "INT33FF:03" },
+};
+
+static const struct software_node *cherryview_gpiochip_node_group[] = {
+ &cherryview_gpiochip_nodes[0],
+ &cherryview_gpiochip_nodes[1],
+ &cherryview_gpiochip_nodes[2],
+ &cherryview_gpiochip_nodes[3],
+ NULL
+};
+
static void x86_android_tablet_remove(struct platform_device *pdev)
{
int i;
@@ -346,7 +375,6 @@ static void x86_android_tablet_remove(struct platform_device *pdev)
platform_device_unregister(pdevs[i]);
kfree(pdevs);
- kfree(buttons);
for (i = spi_dev_count - 1; i >= 0; i--)
spi_unregister_device(spi_devs[i]);
@@ -361,10 +389,9 @@ static void x86_android_tablet_remove(struct platform_device *pdev)
if (exit_handler)
exit_handler();
- for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++)
- gpiod_remove_lookup_table(gpiod_lookup_tables[i]);
-
- software_node_unregister(bat_swnode);
+ software_node_unregister_node_group(gpio_button_swnodes);
+ software_node_unregister_node_group(swnode_group);
+ software_node_unregister_node_group(gpiochip_node_group);
}
static __init int x86_android_tablet_probe(struct platform_device *pdev)
@@ -388,16 +415,28 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev)
for (i = 0; dev_info->modules && dev_info->modules[i]; i++)
request_module(dev_info->modules[i]);
- bat_swnode = dev_info->bat_swnode;
- if (bat_swnode) {
- ret = software_node_register(bat_swnode);
- if (ret)
- return ret;
+ switch (dev_info->gpiochip_type) {
+ case X86_GPIOCHIP_BAYTRAIL:
+ gpiochip_node_group = baytrail_gpiochip_node_group;
+ break;
+ case X86_GPIOCHIP_CHERRYVIEW:
+ gpiochip_node_group = cherryview_gpiochip_node_group;
+ break;
+ case X86_GPIOCHIP_UNSPECIFIED:
+ gpiochip_node_group = NULL;
+ break;
}
- gpiod_lookup_tables = dev_info->gpiod_lookup_tables;
- for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++)
- gpiod_add_lookup_table(gpiod_lookup_tables[i]);
+ ret = software_node_register_node_group(gpiochip_node_group);
+ if (ret)
+ return ret;
+
+ ret = software_node_register_node_group(dev_info->swnode_group);
+ if (ret) {
+ x86_android_tablet_remove(pdev);
+ return ret;
+ }
+ swnode_group = dev_info->swnode_group;
if (dev_info->init) {
ret = dev_info->init(&pdev->dev);
@@ -470,38 +509,22 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev)
}
}
- if (dev_info->gpio_button_count) {
- struct gpio_keys_platform_data pdata = { };
- struct gpio_desc *gpiod;
+ if (dev_info->gpio_button_swnodes) {
+ struct platform_device_info button_info = {
+ .name = "gpio-keys",
+ .id = PLATFORM_DEVID_AUTO,
+ };
- buttons = kcalloc(dev_info->gpio_button_count, sizeof(*buttons), GFP_KERNEL);
- if (!buttons) {
+ ret = software_node_register_node_group(dev_info->gpio_button_swnodes);
+ if (ret < 0) {
x86_android_tablet_remove(pdev);
- return -ENOMEM;
- }
-
- for (i = 0; i < dev_info->gpio_button_count; i++) {
- ret = x86_android_tablet_get_gpiod(dev_info->gpio_button[i].chip,
- dev_info->gpio_button[i].pin,
- dev_info->gpio_button[i].button.desc,
- false, GPIOD_IN, &gpiod);
- if (ret < 0) {
- x86_android_tablet_remove(pdev);
- return ret;
- }
-
- buttons[i] = dev_info->gpio_button[i].button;
- buttons[i].gpio = desc_to_gpio(gpiod);
- /* Release GPIO descriptor so that gpio-keys can request it */
- devm_gpiod_put(&x86_android_tablet_device->dev, gpiod);
+ return ret;
}
- pdata.buttons = buttons;
- pdata.nbuttons = dev_info->gpio_button_count;
+ gpio_button_swnodes = dev_info->gpio_button_swnodes;
- pdevs[pdev_count] = platform_device_register_data(&pdev->dev, "gpio-keys",
- PLATFORM_DEVID_AUTO,
- &pdata, sizeof(pdata));
+ button_info.fwnode = software_node_fwnode(dev_info->gpio_button_swnodes[0]);
+ pdevs[pdev_count] = platform_device_register_full(&button_info);
if (IS_ERR(pdevs[pdev_count])) {
ret = PTR_ERR(pdevs[pdev_count]);
x86_android_tablet_remove(pdev);
@@ -537,6 +560,6 @@ static void __exit x86_android_tablet_exit(void)
}
module_exit(x86_android_tablet_exit);
-MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
MODULE_DESCRIPTION("X86 Android tablets DSDT fixups driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/x86-android-tablets/dmi.c b/drivers/platform/x86/x86-android-tablets/dmi.c
index 278c6d151dc4..4a5720d6fc1d 100644
--- a/drivers/platform/x86/x86-android-tablets/dmi.c
+++ b/drivers/platform/x86/x86-android-tablets/dmi.c
@@ -5,7 +5,7 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#include <linux/dmi.h>
@@ -17,6 +17,16 @@
const struct dmi_system_id x86_android_tablet_ids[] __initconst = {
{
+ /* Acer Iconia One 8 A1-840 (non FHD version) */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "BayTrail"),
+ /* Above strings are too generic also match BIOS date */
+ DMI_MATCH(DMI_BIOS_DATE, "04/01/2014"),
+ },
+ .driver_data = (void *)&acer_a1_840_info,
+ },
+ {
/* Acer Iconia One 7 B1-750 */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c
index 1241a97cda39..e3d3a8290949 100644
--- a/drivers/platform/x86/x86-android-tablets/lenovo.c
+++ b/drivers/platform/x86/x86-android-tablets/lenovo.c
@@ -5,13 +5,15 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/efi.h>
#include <linux/gpio/machine.h>
+#include <linux/gpio/property.h>
+#include <linux/input-event-codes.h>
#include <linux/mfd/arizona/pdata.h>
#include <linux/mfd/arizona/registers.h>
#include <linux/mfd/intel_soc_pmic.h>
@@ -59,11 +61,30 @@ static struct lp855x_platform_data lenovo_lp8557_reg_only_pdata = {
.initial_brightness = 128,
};
+static const struct software_node arizona_gpiochip_node = {
+ .name = "arizona",
+};
+
+static const struct software_node crystalcove_gpiochip_node = {
+ .name = "gpio_crystalcove",
+};
+
/* Lenovo Yoga Book X90F / X90L's Android factory image has everything hardcoded */
+static const struct property_entry lenovo_yb1_x90_goodix_props[] = {
+ PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[1], 53, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("irq-gpios", &cherryview_gpiochip_nodes[1], 56, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node lenovo_yb1_x90_goodix_node = {
+ .properties = lenovo_yb1_x90_goodix_props,
+};
+
static const struct property_entry lenovo_yb1_x90_wacom_props[] = {
PROPERTY_ENTRY_U32("hid-descr-addr", 0x0001),
PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 150),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 82, GPIO_ACTIVE_LOW),
{ }
};
@@ -85,6 +106,7 @@ static const struct property_entry lenovo_yb1_x90_hideep_ts_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-y", 1920),
PROPERTY_ENTRY_U32("touchscreen-max-pressure", 16384),
PROPERTY_ENTRY_BOOL("hideep,force-native-protocol"),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 7, GPIO_ACTIVE_LOW),
{ }
};
@@ -108,6 +130,7 @@ static const struct x86_i2c_client_info lenovo_yb1_x90_i2c_clients[] __initconst
.type = "GDIX1001:00",
.addr = 0x14,
.dev_name = "goodix_ts",
+ .swnode = &lenovo_yb1_x90_goodix_node,
},
.adapter_path = "\\_SB_.PCI0.I2C2",
.irq_data = {
@@ -185,48 +208,33 @@ static const struct x86_serdev_info lenovo_yb1_x90_serdevs[] __initconst = {
},
};
-static const struct x86_gpio_button lenovo_yb1_x90_lid __initconst = {
- .button = {
- .code = SW_LID,
- .active_low = true,
- .desc = "lid_sw",
- .type = EV_SW,
- .wakeup = true,
- .debounce_interval = 50,
- },
- .chip = "INT33FF:02",
- .pin = 19,
-};
-
-static struct gpiod_lookup_table lenovo_yb1_x90_goodix_gpios = {
- .dev_id = "i2c-goodix_ts",
- .table = {
- GPIO_LOOKUP("INT33FF:01", 53, "reset", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FF:01", 56, "irq", GPIO_ACTIVE_HIGH),
- { }
- },
+/*
+ * Software node attached to gpio-keys device representing the LID and
+ * serving as a parent to software nodes representing individual keys/buttons
+ * as required by the device tree binding.
+ */
+static const struct software_node lenovo_lid_gpio_keys_node = {
+ .name = "lid_sw",
};
-static struct gpiod_lookup_table lenovo_yb1_x90_hideep_gpios = {
- .dev_id = "i2c-hideep_ts",
- .table = {
- GPIO_LOOKUP("INT33FF:00", 7, "reset", GPIO_ACTIVE_LOW),
- { }
- },
+static const struct property_entry lenovo_yb1_x90_lid_props[] = {
+ PROPERTY_ENTRY_U32("linux,input-type", EV_SW),
+ PROPERTY_ENTRY_U32("linux,code", SW_LID),
+ PROPERTY_ENTRY_STRING("label", "lid_sw"),
+ PROPERTY_ENTRY_GPIO("gpios", &cherryview_gpiochip_nodes[2], 19, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 50),
+ PROPERTY_ENTRY_BOOL("wakeup-source"),
+ { }
};
-static struct gpiod_lookup_table lenovo_yb1_x90_wacom_gpios = {
- .dev_id = "i2c-wacom",
- .table = {
- GPIO_LOOKUP("INT33FF:00", 82, "reset", GPIO_ACTIVE_LOW),
- { }
- },
+static const struct software_node lenovo_yb1_x90_lid_node = {
+ .parent = &lenovo_lid_gpio_keys_node,
+ .properties = lenovo_yb1_x90_lid_props,
};
-static struct gpiod_lookup_table * const lenovo_yb1_x90_gpios[] = {
- &lenovo_yb1_x90_hideep_gpios,
- &lenovo_yb1_x90_goodix_gpios,
- &lenovo_yb1_x90_wacom_gpios,
+static const struct software_node *lenovo_yb1_x90_lid_swnodes[] = {
+ &lenovo_lid_gpio_keys_node,
+ &lenovo_yb1_x90_lid_node,
NULL
};
@@ -256,9 +264,8 @@ const struct x86_dev_info lenovo_yogabook_x90_info __initconst = {
.pdev_count = ARRAY_SIZE(lenovo_yb1_x90_pdevs),
.serdev_info = lenovo_yb1_x90_serdevs,
.serdev_count = ARRAY_SIZE(lenovo_yb1_x90_serdevs),
- .gpio_button = &lenovo_yb1_x90_lid,
- .gpio_button_count = 1,
- .gpiod_lookup_tables = lenovo_yb1_x90_gpios,
+ .gpio_button_swnodes = lenovo_yb1_x90_lid_swnodes,
+ .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW,
.init = lenovo_yb1_x90_init,
};
@@ -294,17 +301,25 @@ static const struct software_node lenovo_yoga_tab2_830_1050_bq24190_node = {
.properties = lenovo_yoga_tab2_830_1050_bq24190_props,
};
-static const struct x86_gpio_button lenovo_yoga_tab2_830_1050_lid __initconst = {
- .button = {
- .code = SW_LID,
- .active_low = true,
- .desc = "lid_sw",
- .type = EV_SW,
- .wakeup = true,
- .debounce_interval = 50,
- },
- .chip = "INT33FC:02",
- .pin = 26,
+static const struct property_entry lenovo_yoga_tab2_830_1050_lid_props[] = {
+ PROPERTY_ENTRY_U32("linux,input-type", EV_SW),
+ PROPERTY_ENTRY_U32("linux,code", SW_LID),
+ PROPERTY_ENTRY_STRING("label", "lid_sw"),
+ PROPERTY_ENTRY_GPIO("gpios", &baytrail_gpiochip_nodes[2], 26, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 50),
+ PROPERTY_ENTRY_BOOL("wakeup-source"),
+ { }
+};
+
+static const struct software_node lenovo_yoga_tab2_830_1050_lid_node = {
+ .parent = &lenovo_lid_gpio_keys_node,
+ .properties = lenovo_yoga_tab2_830_1050_lid_props,
+};
+
+static const struct software_node *lenovo_yoga_tab2_830_1050_lid_swnodes[] = {
+ &lenovo_lid_gpio_keys_node,
+ &lenovo_yoga_tab2_830_1050_lid_node,
+ NULL
};
/* This gets filled by lenovo_yoga_tab2_830_1050_init() */
@@ -384,47 +399,65 @@ static struct x86_i2c_client_info lenovo_yoga_tab2_830_1050_i2c_clients[] __init
},
};
-static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_int3496_gpios = {
- .dev_id = "intel-int3496",
- .table = {
- GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_LOW),
- GPIO_LOOKUP("INT33FC:02", 24, "id", GPIO_ACTIVE_HIGH),
- { }
+static const struct property_entry lenovo_yoga_tab2_830_1050_int3496_props[] __initconst = {
+ PROPERTY_ENTRY_GPIO("mux-gpios", &baytrail_gpiochip_nodes[2], 1, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 24, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct platform_device_info lenovo_yoga_tab2_830_1050_pdevs[] __initconst = {
+ {
+ /* For micro USB ID pin handling */
+ .name = "intel-int3496",
+ .id = PLATFORM_DEVID_NONE,
+ .properties = lenovo_yoga_tab2_830_1050_int3496_props,
},
};
#define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00"
-static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_codec_gpios = {
- .dev_id = LENOVO_YOGA_TAB2_830_1050_CODEC_NAME,
- .table = {
- GPIO_LOOKUP("gpio_crystalcove", 3, "reset", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FC:01", 23, "wlf,ldoena", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("arizona", 2, "wlf,spkvdd-ena", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("arizona", 4, "wlf,micd-pol", GPIO_ACTIVE_LOW),
- { }
- },
+static const struct property_entry lenovo_yoga_tab2_830_1050_wm1502_props[] = {
+ PROPERTY_ENTRY_GPIO("reset-gpios",
+ &crystalcove_gpiochip_node, 3, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios",
+ &baytrail_gpiochip_nodes[1], 23, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios",
+ &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios",
+ &arizona_gpiochip_node, 4, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node lenovo_yoga_tab2_830_1050_wm5102 = {
+ .properties = lenovo_yoga_tab2_830_1050_wm1502_props,
};
-static struct gpiod_lookup_table * const lenovo_yoga_tab2_830_1050_gpios[] = {
- &lenovo_yoga_tab2_830_1050_int3496_gpios,
- &lenovo_yoga_tab2_830_1050_codec_gpios,
+static const struct software_node *lenovo_yoga_tab2_830_1050_swnodes[] = {
+ &crystalcove_gpiochip_node,
+ &arizona_gpiochip_node,
+ &lenovo_yoga_tab2_830_1050_wm5102,
+ &generic_lipo_hv_4v35_battery_node,
NULL
};
static int __init lenovo_yoga_tab2_830_1050_init(struct device *dev);
static void lenovo_yoga_tab2_830_1050_exit(void);
+static const char * const lenovo_yoga_tab2_modules[] __initconst = {
+ "spi_pxa2xx_platform", /* For the SPI codec device */
+ "bq24190_charger", /* For the Vbus regulator for int3496/lc824206xa */
+ NULL
+};
+
const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = {
.i2c_client_info = lenovo_yoga_tab2_830_1050_i2c_clients,
.i2c_client_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_i2c_clients),
- .pdev_info = int3496_pdevs,
- .pdev_count = 1,
- .gpio_button = &lenovo_yoga_tab2_830_1050_lid,
- .gpio_button_count = 1,
- .gpiod_lookup_tables = lenovo_yoga_tab2_830_1050_gpios,
- .bat_swnode = &generic_lipo_hv_4v35_battery_node,
- .modules = bq24190_modules,
+ .pdev_info = lenovo_yoga_tab2_830_1050_pdevs,
+ .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_pdevs),
+ .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes,
+ .swnode_group = lenovo_yoga_tab2_830_1050_swnodes,
+ .modules = lenovo_yoga_tab2_modules,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
.init = lenovo_yoga_tab2_830_1050_init,
.exit = lenovo_yoga_tab2_830_1050_exit,
};
@@ -481,6 +514,7 @@ static const struct pinctrl_map lenovo_yoga_tab2_830_1050_codec_pinctrl_map =
PIN_MAP_MUX_GROUP(LENOVO_YOGA_TAB2_830_1050_CODEC_NAME, "codec_32khz_clk",
"INT33FC:02", "pmu_clk2_grp", "pmu_clk");
+static struct device *lenovo_yoga_tab2_830_1050_codec_dev;
static struct pinctrl *lenovo_yoga_tab2_830_1050_codec_pinctrl;
static struct sys_off_handler *lenovo_yoga_tab2_830_1050_sys_off_handler;
@@ -507,12 +541,18 @@ static int __init lenovo_yoga_tab2_830_1050_init_codec(void)
goto err_unregister_mappings;
}
- /* We're done with the codec_dev now */
- put_device(codec_dev);
+ ret = device_add_software_node(codec_dev, &lenovo_yoga_tab2_830_1050_wm5102);
+ if (ret) {
+ ret = dev_err_probe(codec_dev, ret, "adding software node\n");
+ goto err_put_pinctrl;
+ }
+ lenovo_yoga_tab2_830_1050_codec_dev = codec_dev;
lenovo_yoga_tab2_830_1050_codec_pinctrl = pinctrl;
return 0;
+err_put_pinctrl:
+ pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl);
err_unregister_mappings:
pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map);
err_put_device:
@@ -560,10 +600,10 @@ static void lenovo_yoga_tab2_830_1050_exit(void)
{
unregister_sys_off_handler(lenovo_yoga_tab2_830_1050_sys_off_handler);
- if (lenovo_yoga_tab2_830_1050_codec_pinctrl) {
- pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl);
- pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map);
- }
+ device_remove_software_node(lenovo_yoga_tab2_830_1050_codec_dev);
+ pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl);
+ pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map);
+ put_device(lenovo_yoga_tab2_830_1050_codec_dev);
}
/*
@@ -718,19 +758,21 @@ static const struct x86_i2c_client_info lenovo_yoga_tab2_1380_i2c_clients[] __in
}
};
+static const struct property_entry lenovo_yoga_tab2_1380_fc_props[] __initconst = {
+ PROPERTY_ENTRY_GPIO("uart3_txd-gpios", &baytrail_gpiochip_nodes[0], 57, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("uart3_rxd-gpios", &baytrail_gpiochip_nodes[0], 61, GPIO_ACTIVE_HIGH),
+ { }
+};
+
static const struct platform_device_info lenovo_yoga_tab2_1380_pdevs[] __initconst = {
{
/* For the Tablet 2 Pro 1380's custom fast charging driver */
.name = "lenovo-yoga-tab2-pro-1380-fastcharger",
.id = PLATFORM_DEVID_NONE,
+ .properties = lenovo_yoga_tab2_1380_fc_props,
},
};
-static const char * const lenovo_yoga_tab2_1380_modules[] __initconst = {
- "bq24190_charger", /* For the Vbus regulator for lc824206xa */
- NULL
-};
-
static int __init lenovo_yoga_tab2_1380_init(struct device *dev)
{
int ret;
@@ -752,31 +794,15 @@ static int __init lenovo_yoga_tab2_1380_init(struct device *dev)
return 0;
}
-static struct gpiod_lookup_table lenovo_yoga_tab2_1380_fc_gpios = {
- .dev_id = "serial0-0",
- .table = {
- GPIO_LOOKUP("INT33FC:00", 57, "uart3_txd", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FC:00", 61, "uart3_rxd", GPIO_ACTIVE_HIGH),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const lenovo_yoga_tab2_1380_gpios[] = {
- &lenovo_yoga_tab2_830_1050_codec_gpios,
- &lenovo_yoga_tab2_1380_fc_gpios,
- NULL
-};
-
const struct x86_dev_info lenovo_yoga_tab2_1380_info __initconst = {
.i2c_client_info = lenovo_yoga_tab2_1380_i2c_clients,
.i2c_client_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_i2c_clients),
.pdev_info = lenovo_yoga_tab2_1380_pdevs,
.pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_pdevs),
- .gpio_button = &lenovo_yoga_tab2_830_1050_lid,
- .gpio_button_count = 1,
- .gpiod_lookup_tables = lenovo_yoga_tab2_1380_gpios,
- .bat_swnode = &generic_lipo_hv_4v35_battery_node,
- .modules = lenovo_yoga_tab2_1380_modules,
+ .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes,
+ .swnode_group = lenovo_yoga_tab2_830_1050_swnodes,
+ .modules = lenovo_yoga_tab2_modules,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
.init = lenovo_yoga_tab2_1380_init,
.exit = lenovo_yoga_tab2_830_1050_exit,
};
@@ -824,6 +850,7 @@ static const struct property_entry lenovo_yt3_hideep_ts_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 1600),
PROPERTY_ENTRY_U32("touchscreen-size-y", 2560),
PROPERTY_ENTRY_U32("touchscreen-max-pressure", 255),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 7, GPIO_ACTIVE_LOW),
{ }
};
@@ -958,12 +985,34 @@ static struct arizona_pdata lenovo_yt3_wm5102_pdata = {
},
};
+static const struct property_entry lenovo_yt3_wm1502_props[] = {
+ PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios",
+ &cherryview_gpiochip_nodes[0], 75, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios",
+ &cherryview_gpiochip_nodes[0], 81, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 82, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node lenovo_yt3_wm5102 = {
+ .properties = lenovo_yt3_wm1502_props,
+ .name = "wm5102",
+};
+
+static const struct software_node *lenovo_yt3_swnodes[] = {
+ &arizona_gpiochip_node,
+ &lenovo_yt3_wm5102,
+ NULL
+};
+
static const struct x86_spi_dev_info lenovo_yt3_spi_devs[] __initconst = {
{
/* WM5102 codec */
.board_info = {
.modalias = "wm5102",
.platform_data = &lenovo_yt3_wm5102_pdata,
+ .swnode = &lenovo_yt3_wm5102,
.max_speed_hz = 5000000,
},
.ctrl_path = "\\_SB_.PCI0.SPI1",
@@ -1013,28 +1062,8 @@ static int __init lenovo_yt3_init(struct device *dev)
return 0;
}
-static struct gpiod_lookup_table lenovo_yt3_hideep_gpios = {
- .dev_id = "i2c-hideep_ts",
- .table = {
- GPIO_LOOKUP("INT33FF:00", 7, "reset", GPIO_ACTIVE_LOW),
- { }
- },
-};
-
-static struct gpiod_lookup_table lenovo_yt3_wm5102_gpios = {
- .dev_id = "spi1.0",
- .table = {
- GPIO_LOOKUP("INT33FF:00", 75, "wlf,spkvdd-ena", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FF:00", 81, "wlf,ldoena", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FF:00", 82, "reset", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("arizona", 2, "wlf,micd-pol", GPIO_ACTIVE_HIGH),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const lenovo_yt3_gpios[] = {
- &lenovo_yt3_hideep_gpios,
- &lenovo_yt3_wm5102_gpios,
+static const char * const lenovo_yt3_modules[] __initconst = {
+ "spi_pxa2xx_platform", /* For the SPI codec device */
NULL
};
@@ -1043,6 +1072,8 @@ const struct x86_dev_info lenovo_yt3_info __initconst = {
.i2c_client_count = ARRAY_SIZE(lenovo_yt3_i2c_clients),
.spi_dev_info = lenovo_yt3_spi_devs,
.spi_dev_count = ARRAY_SIZE(lenovo_yt3_spi_devs),
- .gpiod_lookup_tables = lenovo_yt3_gpios,
+ .swnode_group = lenovo_yt3_swnodes,
+ .modules = lenovo_yt3_modules,
+ .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW,
.init = lenovo_yt3_init,
};
diff --git a/drivers/platform/x86/x86-android-tablets/other.c b/drivers/platform/x86/x86-android-tablets/other.c
index f7bd9f863c85..7532af2d72d1 100644
--- a/drivers/platform/x86/x86-android-tablets/other.c
+++ b/drivers/platform/x86/x86-android-tablets/other.c
@@ -5,12 +5,13 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#include <linux/acpi.h>
#include <linux/gpio/machine.h>
-#include <linux/input.h>
+#include <linux/gpio/property.h>
+#include <linux/input-event-codes.h>
#include <linux/leds.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
@@ -21,102 +22,38 @@
#include "shared-psy-info.h"
#include "x86-android-tablets.h"
-/* Acer Iconia One 7 B1-750 has an Android factory image with everything hardcoded */
-static const char * const acer_b1_750_mount_matrix[] = {
- "-1", "0", "0",
- "0", "1", "0",
- "0", "0", "1"
+/*
+ * Advantech MICA-071
+ * This is a standard Windows tablet, but it has an extra "quick launch" button
+ * which is not described in the ACPI tables in anyway.
+ * Use the x86-android-tablets infra to create a gpio-keys device for this.
+ */
+static const struct software_node advantech_mica_071_gpio_keys_node = {
+ .name = "prog1_key",
};
-static const struct property_entry acer_b1_750_bma250e_props[] = {
- PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", acer_b1_750_mount_matrix),
+static const struct property_entry advantech_mica_071_prog1_key_props[] = {
+ PROPERTY_ENTRY_U32("linux,code", KEY_PROG1),
+ PROPERTY_ENTRY_STRING("label", "prog1_key"),
+ PROPERTY_ENTRY_GPIO("gpios", &baytrail_gpiochip_nodes[0], 2, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 50),
{ }
};
-static const struct software_node acer_b1_750_bma250e_node = {
- .properties = acer_b1_750_bma250e_props,
+static const struct software_node advantech_mica_071_prog1_key_node = {
+ .parent = &advantech_mica_071_gpio_keys_node,
+ .properties = advantech_mica_071_prog1_key_props,
};
-static const struct x86_i2c_client_info acer_b1_750_i2c_clients[] __initconst = {
- {
- /* Novatek NVT-ts touchscreen */
- .board_info = {
- .type = "nt11205-ts",
- .addr = 0x34,
- .dev_name = "NVT-ts",
- },
- .adapter_path = "\\_SB_.I2C4",
- .irq_data = {
- .type = X86_ACPI_IRQ_TYPE_GPIOINT,
- .chip = "INT33FC:02",
- .index = 3,
- .trigger = ACPI_EDGE_SENSITIVE,
- .polarity = ACPI_ACTIVE_LOW,
- .con_id = "NVT-ts_irq",
- },
- }, {
- /* BMA250E accelerometer */
- .board_info = {
- .type = "bma250e",
- .addr = 0x18,
- .swnode = &acer_b1_750_bma250e_node,
- },
- .adapter_path = "\\_SB_.I2C3",
- .irq_data = {
- .type = X86_ACPI_IRQ_TYPE_GPIOINT,
- .chip = "INT33FC:02",
- .index = 25,
- .trigger = ACPI_LEVEL_SENSITIVE,
- .polarity = ACPI_ACTIVE_HIGH,
- .con_id = "bma250e_irq",
- },
- },
-};
-
-static struct gpiod_lookup_table acer_b1_750_nvt_ts_gpios = {
- .dev_id = "i2c-NVT-ts",
- .table = {
- GPIO_LOOKUP("INT33FC:01", 26, "reset", GPIO_ACTIVE_LOW),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const acer_b1_750_gpios[] = {
- &acer_b1_750_nvt_ts_gpios,
- &int3496_reference_gpios,
+static const struct software_node *advantech_mica_071_button_swnodes[] = {
+ &advantech_mica_071_gpio_keys_node,
+ &advantech_mica_071_prog1_key_node,
NULL
};
-const struct x86_dev_info acer_b1_750_info __initconst = {
- .i2c_client_info = acer_b1_750_i2c_clients,
- .i2c_client_count = ARRAY_SIZE(acer_b1_750_i2c_clients),
- .pdev_info = int3496_pdevs,
- .pdev_count = 1,
- .gpiod_lookup_tables = acer_b1_750_gpios,
-};
-
-/*
- * Advantech MICA-071
- * This is a standard Windows tablet, but it has an extra "quick launch" button
- * which is not described in the ACPI tables in anyway.
- * Use the x86-android-tablets infra to create a gpio-keys device for this.
- */
-static const struct x86_gpio_button advantech_mica_071_button __initconst = {
- .button = {
- .code = KEY_PROG1,
- .active_low = true,
- .desc = "prog1_key",
- .type = EV_KEY,
- .wakeup = false,
- .debounce_interval = 50,
- },
- .chip = "INT33FC:00",
- .pin = 2,
-};
-
const struct x86_dev_info advantech_mica_071_info __initconst = {
- .gpio_button = &advantech_mica_071_button,
- .gpio_button_count = 1,
+ .gpio_button_swnodes = advantech_mica_071_button_swnodes,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/*
@@ -212,36 +149,46 @@ const struct x86_dev_info chuwi_hi8_info __initconst = {
* in the button row with the power + volume-buttons labeled P and F.
* Use the x86-android-tablets infra to create a gpio-keys device for these.
*/
-static const struct x86_gpio_button cyberbook_t116_buttons[] __initconst = {
- {
- .button = {
- .code = KEY_PROG1,
- .active_low = true,
- .desc = "prog1_key",
- .type = EV_KEY,
- .wakeup = false,
- .debounce_interval = 50,
- },
- .chip = "INT33FF:00",
- .pin = 30,
- },
- {
- .button = {
- .code = KEY_PROG2,
- .active_low = true,
- .desc = "prog2_key",
- .type = EV_KEY,
- .wakeup = false,
- .debounce_interval = 50,
- },
- .chip = "INT33FF:03",
- .pin = 48,
- },
+static const struct software_node cyberbook_t116_gpio_keys_node = {
+ .name = "prog_keys",
+};
+
+static const struct property_entry cyberbook_t116_prog1_key_props[] = {
+ PROPERTY_ENTRY_U32("linux,code", KEY_PROG1),
+ PROPERTY_ENTRY_STRING("label", "prog1_key"),
+ PROPERTY_ENTRY_GPIO("gpios", &cherryview_gpiochip_nodes[0], 30, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 50),
+ { }
+};
+
+static const struct software_node cyberbook_t116_prog1_key_node = {
+ .parent = &cyberbook_t116_gpio_keys_node,
+ .properties = cyberbook_t116_prog1_key_props,
+};
+
+static const struct property_entry cyberbook_t116_prog2_key_props[] = {
+ PROPERTY_ENTRY_U32("linux,code", KEY_PROG2),
+ PROPERTY_ENTRY_STRING("label", "prog2_key"),
+ PROPERTY_ENTRY_GPIO("gpios", &cherryview_gpiochip_nodes[3], 48, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 50),
+ { }
+};
+
+static const struct software_node cyberbook_t116_prog2_key_node = {
+ .parent = &cyberbook_t116_gpio_keys_node,
+ .properties = cyberbook_t116_prog2_key_props,
+};
+
+static const struct software_node *cyberbook_t116_buttons_swnodes[] = {
+ &cyberbook_t116_gpio_keys_node,
+ &cyberbook_t116_prog1_key_node,
+ &cyberbook_t116_prog2_key_node,
+ NULL
};
const struct x86_dev_info cyberbook_t116_info __initconst = {
- .gpio_button = cyberbook_t116_buttons,
- .gpio_button_count = ARRAY_SIZE(cyberbook_t116_buttons),
+ .gpio_button_swnodes = cyberbook_t116_buttons_swnodes,
+ .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW,
};
#define CZC_EC_EXTRA_PORT 0x68
@@ -297,6 +244,8 @@ static const struct software_node medion_lifetab_s10346_accel_node = {
static const struct property_entry medion_lifetab_s10346_touchscreen_props[] = {
PROPERTY_ENTRY_BOOL("touchscreen-inverted-x"),
PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("irq-gpios", &baytrail_gpiochip_nodes[2], 3, GPIO_ACTIVE_HIGH),
{ }
};
@@ -340,24 +289,10 @@ static const struct x86_i2c_client_info medion_lifetab_s10346_i2c_clients[] __in
},
};
-static struct gpiod_lookup_table medion_lifetab_s10346_goodix_gpios = {
- .dev_id = "i2c-goodix_ts",
- .table = {
- GPIO_LOOKUP("INT33FC:01", 26, "reset", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FC:02", 3, "irq", GPIO_ACTIVE_HIGH),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const medion_lifetab_s10346_gpios[] = {
- &medion_lifetab_s10346_goodix_gpios,
- NULL
-};
-
const struct x86_dev_info medion_lifetab_s10346_info __initconst = {
.i2c_client_info = medion_lifetab_s10346_i2c_clients,
.i2c_client_count = ARRAY_SIZE(medion_lifetab_s10346_i2c_clients),
- .gpiod_lookup_tables = medion_lifetab_s10346_gpios,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/* Nextbook Ares 8 (BYT) tablets have an Android factory image with everything hardcoded */
@@ -416,17 +351,12 @@ static const struct x86_i2c_client_info nextbook_ares8_i2c_clients[] __initconst
},
};
-static struct gpiod_lookup_table * const nextbook_ares8_gpios[] = {
- &int3496_reference_gpios,
- NULL
-};
-
const struct x86_dev_info nextbook_ares8_info __initconst = {
.i2c_client_info = nextbook_ares8_i2c_clients,
.i2c_client_count = ARRAY_SIZE(nextbook_ares8_i2c_clients),
.pdev_info = int3496_pdevs,
.pdev_count = 1,
- .gpiod_lookup_tables = nextbook_ares8_gpios,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/* Nextbook Ares 8A (CHT) tablets have an Android factory image with everything hardcoded */
@@ -445,6 +375,17 @@ static const struct software_node nextbook_ares8a_accel_node = {
.properties = nextbook_ares8a_accel_props,
};
+static const struct property_entry nextbook_ares8a_ft5416_props[] = {
+ PROPERTY_ENTRY_U32("touchscreen-size-x", 800),
+ PROPERTY_ENTRY_U32("touchscreen-size-y", 1280),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[1], 25, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node nextbook_ares8a_ft5416_node = {
+ .properties = nextbook_ares8a_ft5416_props,
+};
+
static const struct x86_i2c_client_info nextbook_ares8a_i2c_clients[] __initconst = {
{
/* Freescale MMA8653FC accelerometer */
@@ -461,7 +402,7 @@ static const struct x86_i2c_client_info nextbook_ares8a_i2c_clients[] __initcons
.type = "edt-ft5x06",
.addr = 0x38,
.dev_name = "ft5416",
- .swnode = &nextbook_ares8_touchscreen_node,
+ .swnode = &nextbook_ares8a_ft5416_node,
},
.adapter_path = "\\_SB_.PCI0.I2C6",
.irq_data = {
@@ -475,23 +416,10 @@ static const struct x86_i2c_client_info nextbook_ares8a_i2c_clients[] __initcons
},
};
-static struct gpiod_lookup_table nextbook_ares8a_ft5416_gpios = {
- .dev_id = "i2c-ft5416",
- .table = {
- GPIO_LOOKUP("INT33FF:01", 25, "reset", GPIO_ACTIVE_LOW),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const nextbook_ares8a_gpios[] = {
- &nextbook_ares8a_ft5416_gpios,
- NULL
-};
-
const struct x86_dev_info nextbook_ares8a_info __initconst = {
.i2c_client_info = nextbook_ares8a_i2c_clients,
.i2c_client_count = ARRAY_SIZE(nextbook_ares8a_i2c_clients),
- .gpiod_lookup_tables = nextbook_ares8a_gpios,
+ .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW,
};
/*
@@ -500,22 +428,32 @@ const struct x86_dev_info nextbook_ares8a_info __initconst = {
* This button has a WMI interface, but that is broken. Instead of trying to
* use the broken WMI interface, instantiate a gpio-keys device for this.
*/
-static const struct x86_gpio_button peaq_c1010_button __initconst = {
- .button = {
- .code = KEY_SOUND,
- .active_low = true,
- .desc = "dolby_key",
- .type = EV_KEY,
- .wakeup = false,
- .debounce_interval = 50,
- },
- .chip = "INT33FC:00",
- .pin = 3,
+static const struct software_node peaq_c1010_gpio_keys_node = {
+ .name = "gpio_keys",
+};
+
+static const struct property_entry peaq_c1010_dolby_key_props[] = {
+ PROPERTY_ENTRY_U32("linux,code", KEY_SOUND),
+ PROPERTY_ENTRY_STRING("label", "dolby_key"),
+ PROPERTY_ENTRY_GPIO("gpios", &baytrail_gpiochip_nodes[0], 3, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 50),
+ { }
+};
+
+static const struct software_node peaq_c1010_dolby_key_node = {
+ .parent = &peaq_c1010_gpio_keys_node,
+ .properties = peaq_c1010_dolby_key_props,
+};
+
+static const struct software_node *peaq_c1010_button_swnodes[] = {
+ &peaq_c1010_gpio_keys_node,
+ &peaq_c1010_dolby_key_node,
+ NULL
};
const struct x86_dev_info peaq_c1010_info __initconst = {
- .gpio_button = &peaq_c1010_button,
- .gpio_button_count = 1,
+ .gpio_button_swnodes = peaq_c1010_button_swnodes,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/*
@@ -543,6 +481,8 @@ static const struct property_entry whitelabel_tm800a550l_goodix_props[] = {
PROPERTY_ENTRY_STRING("firmware-name", "gt912-tm800a550l.fw"),
PROPERTY_ENTRY_STRING("goodix,config-name", "gt912-tm800a550l.cfg"),
PROPERTY_ENTRY_U32("goodix,main-clk", 54),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("irq-gpios", &baytrail_gpiochip_nodes[2], 3, GPIO_ACTIVE_HIGH),
{ }
};
@@ -578,24 +518,10 @@ static const struct x86_i2c_client_info whitelabel_tm800a550l_i2c_clients[] __in
},
};
-static struct gpiod_lookup_table whitelabel_tm800a550l_goodix_gpios = {
- .dev_id = "i2c-goodix_ts",
- .table = {
- GPIO_LOOKUP("INT33FC:01", 26, "reset", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FC:02", 3, "irq", GPIO_ACTIVE_HIGH),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const whitelabel_tm800a550l_gpios[] = {
- &whitelabel_tm800a550l_goodix_gpios,
- NULL
-};
-
const struct x86_dev_info whitelabel_tm800a550l_info __initconst = {
.i2c_client_info = whitelabel_tm800a550l_i2c_clients,
.i2c_client_count = ARRAY_SIZE(whitelabel_tm800a550l_i2c_clients),
- .gpiod_lookup_tables = whitelabel_tm800a550l_gpios,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/*
@@ -605,6 +531,7 @@ const struct x86_dev_info whitelabel_tm800a550l_info __initconst = {
static const struct property_entry vexia_edu_atla10_5v_touchscreen_props[] = {
PROPERTY_ENTRY_U32("hid-descr-addr", 0x0000),
PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 120),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_LOW),
{ }
};
@@ -639,23 +566,10 @@ static const struct x86_i2c_client_info vexia_edu_atla10_5v_i2c_clients[] __init
}
};
-static struct gpiod_lookup_table vexia_edu_atla10_5v_ft5416_gpios = {
- .dev_id = "i2c-FTSC1000",
- .table = {
- GPIO_LOOKUP("INT33FC:01", 26, "reset", GPIO_ACTIVE_LOW),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const vexia_edu_atla10_5v_gpios[] = {
- &vexia_edu_atla10_5v_ft5416_gpios,
- NULL
-};
-
const struct x86_dev_info vexia_edu_atla10_5v_info __initconst = {
.i2c_client_info = vexia_edu_atla10_5v_i2c_clients,
.i2c_client_count = ARRAY_SIZE(vexia_edu_atla10_5v_i2c_clients),
- .gpiod_lookup_tables = vexia_edu_atla10_5v_gpios,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/*
@@ -691,6 +605,7 @@ static const struct software_node vexia_edu_atla10_9v_accel_node = {
static const struct property_entry vexia_edu_atla10_9v_touchscreen_props[] = {
PROPERTY_ENTRY_U32("hid-descr-addr", 0x0000),
PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 120),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[0], 60, GPIO_ACTIVE_LOW),
{ }
};
@@ -783,19 +698,6 @@ static const struct x86_serdev_info vexia_edu_atla10_9v_serdevs[] __initconst =
},
};
-static struct gpiod_lookup_table vexia_edu_atla10_9v_ft5416_gpios = {
- .dev_id = "i2c-FTSC1000",
- .table = {
- GPIO_LOOKUP("INT33FC:00", 60, "reset", GPIO_ACTIVE_LOW),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const vexia_edu_atla10_9v_gpios[] = {
- &vexia_edu_atla10_9v_ft5416_gpios,
- NULL
-};
-
static int __init vexia_edu_atla10_9v_init(struct device *dev)
{
struct pci_dev *pdev;
@@ -809,8 +711,10 @@ static int __init vexia_edu_atla10_9v_init(struct device *dev)
/* Reprobe the SDIO controller to enumerate the now enabled Wifi module */
pdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0x11, 0));
- if (!pdev)
- return -EPROBE_DEFER;
+ if (!pdev) {
+ pr_warn("Could not get PCI SDIO at devfn 0x%02x\n", PCI_DEVFN(0x11, 0));
+ return 0;
+ }
ret = device_reprobe(&pdev->dev);
if (ret)
@@ -825,9 +729,9 @@ const struct x86_dev_info vexia_edu_atla10_9v_info __initconst = {
.i2c_client_count = ARRAY_SIZE(vexia_edu_atla10_9v_i2c_clients),
.serdev_info = vexia_edu_atla10_9v_serdevs,
.serdev_count = ARRAY_SIZE(vexia_edu_atla10_9v_serdevs),
- .gpiod_lookup_tables = vexia_edu_atla10_9v_gpios,
.init = vexia_edu_atla10_9v_init,
.use_pci = true,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/*
@@ -923,7 +827,6 @@ static int xiaomi_mipad2_brightness_set(struct led_classdev *led_cdev,
static int __init xiaomi_mipad2_init(struct device *dev)
{
struct led_classdev *led_cdev;
- int ret;
xiaomi_mipad2_led_pwm = devm_pwm_get(dev, "pwm_soc_lpss_2");
if (IS_ERR(xiaomi_mipad2_led_pwm))
@@ -940,16 +843,7 @@ static int __init xiaomi_mipad2_init(struct device *dev)
/* Turn LED off during suspend */
led_cdev->flags = LED_CORE_SUSPENDRESUME;
- ret = devm_led_classdev_register(dev, led_cdev);
- if (ret)
- return dev_err_probe(dev, ret, "registering LED\n");
-
- return software_node_register_node_group(ktd2026_node_group);
-}
-
-static void xiaomi_mipad2_exit(void)
-{
- software_node_unregister_node_group(ktd2026_node_group);
+ return devm_led_classdev_register(dev, led_cdev);
}
/*
@@ -984,6 +878,6 @@ static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst
const struct x86_dev_info xiaomi_mipad2_info __initconst = {
.i2c_client_info = xiaomi_mipad2_i2c_clients,
.i2c_client_count = ARRAY_SIZE(xiaomi_mipad2_i2c_clients),
+ .swnode_group = ktd2026_node_group,
.init = xiaomi_mipad2_init,
- .exit = xiaomi_mipad2_exit,
};
diff --git a/drivers/platform/x86/x86-android-tablets/shared-psy-info.c b/drivers/platform/x86/x86-android-tablets/shared-psy-info.c
index fe34cedb6257..29fc466f76fe 100644
--- a/drivers/platform/x86/x86-android-tablets/shared-psy-info.c
+++ b/drivers/platform/x86/x86-android-tablets/shared-psy-info.c
@@ -5,16 +5,18 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#include <linux/gpio/machine.h>
+#include <linux/gpio/property.h>
#include <linux/platform_device.h>
#include <linux/power/bq24190_charger.h>
#include <linux/property.h>
#include <linux/regulator/machine.h>
#include "shared-psy-info.h"
+#include "x86-android-tablets.h"
/* Generic / shared charger / battery settings */
const char * const tusb1211_chg_det_psy[] = { "tusb1211-charger-detect" };
@@ -111,6 +113,11 @@ const struct software_node generic_lipo_4v2_battery_node = {
.properties = generic_lipo_4v2_battery_props,
};
+const struct software_node *generic_lipo_4v2_battery_swnodes[] = {
+ &generic_lipo_4v2_battery_node,
+ NULL
+};
+
/* LiPo HighVoltage (max 4.35V) settings used by most devs with a HV battery */
static const struct property_entry generic_lipo_hv_4v35_battery_props[] = {
PROPERTY_ENTRY_STRING("compatible", "simple-battery"),
@@ -131,6 +138,11 @@ const struct software_node generic_lipo_hv_4v35_battery_node = {
.properties = generic_lipo_hv_4v35_battery_props,
};
+const struct software_node *generic_lipo_hv_4v35_battery_swnodes[] = {
+ &generic_lipo_hv_4v35_battery_node,
+ NULL
+};
+
/* For enabling the bq24190 5V boost based on id-pin */
static struct regulator_consumer_supply intel_int3496_consumer = {
.supply = "vbus",
@@ -156,21 +168,19 @@ const char * const bq24190_modules[] __initconst = {
NULL
};
-/* Generic platform device array and GPIO lookup table for micro USB ID pin handling */
+static const struct property_entry int3496_reference_props[] __initconst = {
+ PROPERTY_ENTRY_GPIO("vbus-gpios", &baytrail_gpiochip_nodes[1], 15, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("mux-gpios", &baytrail_gpiochip_nodes[2], 1, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 18, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+/* Generic pdevs array and gpio-lookups for micro USB ID pin handling */
const struct platform_device_info int3496_pdevs[] __initconst = {
{
/* For micro USB ID pin handling */
.name = "intel-int3496",
.id = PLATFORM_DEVID_NONE,
- },
-};
-
-struct gpiod_lookup_table int3496_reference_gpios = {
- .dev_id = "intel-int3496",
- .table = {
- GPIO_LOOKUP("INT33FC:01", 15, "vbus", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FC:02", 18, "id", GPIO_ACTIVE_HIGH),
- { }
+ .properties = int3496_reference_props,
},
};
diff --git a/drivers/platform/x86/x86-android-tablets/shared-psy-info.h b/drivers/platform/x86/x86-android-tablets/shared-psy-info.h
index bcf9845ad275..149befba3330 100644
--- a/drivers/platform/x86/x86-android-tablets/shared-psy-info.h
+++ b/drivers/platform/x86/x86-android-tablets/shared-psy-info.h
@@ -5,13 +5,12 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#ifndef __PDX86_SHARED_PSY_INFO_H
#define __PDX86_SHARED_PSY_INFO_H
struct bq24190_platform_data;
-struct gpiod_lookup_table;
struct platform_device_info;
struct software_node;
@@ -21,13 +20,16 @@ extern const char * const bq25890_psy[];
extern const struct software_node fg_bq24190_supply_node;
extern const struct software_node fg_bq25890_supply_node;
+
extern const struct software_node generic_lipo_4v2_battery_node;
+extern const struct software_node *generic_lipo_4v2_battery_swnodes[];
+
extern const struct software_node generic_lipo_hv_4v35_battery_node;
+extern const struct software_node *generic_lipo_hv_4v35_battery_swnodes[];
extern struct bq24190_platform_data bq24190_pdata;
extern const char * const bq24190_modules[];
extern const struct platform_device_info int3496_pdevs[];
-extern struct gpiod_lookup_table int3496_reference_gpios;
#endif
diff --git a/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c b/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c
index 5d02af1c5aaa..2f8cd8d9e0ab 100644
--- a/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c
+++ b/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c
@@ -256,6 +256,6 @@ static struct i2c_driver atla10_ec_driver = {
};
module_i2c_driver(atla10_ec_driver);
-MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
MODULE_DESCRIPTION("Battery driver for Vexia EDU ATLA 10 tablet EC");
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h
index dcf8d49e3b5f..2498390958ad 100644
--- a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h
+++ b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h
@@ -5,19 +5,17 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#ifndef __PDX86_X86_ANDROID_TABLETS_H
#define __PDX86_X86_ANDROID_TABLETS_H
#include <linux/gpio/consumer.h>
-#include <linux/gpio_keys.h>
#include <linux/i2c.h>
#include <linux/irqdomain_defs.h>
#include <linux/spi/spi.h>
struct gpio_desc;
-struct gpiod_lookup_table;
struct platform_device_info;
struct software_node;
@@ -32,6 +30,12 @@ enum x86_acpi_irq_type {
X86_ACPI_IRQ_TYPE_PMIC,
};
+enum x86_gpiochip_type {
+ X86_GPIOCHIP_UNSPECIFIED = 0,
+ X86_GPIOCHIP_BAYTRAIL,
+ X86_GPIOCHIP_CHERRYVIEW,
+};
+
struct x86_acpi_irq_data {
char *chip; /* GPIO chip label (GPIOINT) or PMIC ACPI path (PMIC) */
enum x86_acpi_irq_type type;
@@ -76,29 +80,22 @@ struct x86_serdev_info {
const char *serdev_hid;
};
-struct x86_gpio_button {
- struct gpio_keys_button button;
- const char *chip;
- int pin;
-};
-
struct x86_dev_info {
const char * const *modules;
- const struct software_node *bat_swnode;
- struct gpiod_lookup_table * const *gpiod_lookup_tables;
+ const struct software_node **swnode_group;
const struct x86_i2c_client_info *i2c_client_info;
const struct x86_spi_dev_info *spi_dev_info;
const struct platform_device_info *pdev_info;
const struct x86_serdev_info *serdev_info;
- const struct x86_gpio_button *gpio_button;
+ const struct software_node **gpio_button_swnodes;
int i2c_client_count;
int spi_dev_count;
int pdev_count;
int serdev_count;
- int gpio_button_count;
int (*init)(struct device *dev);
void (*exit)(void);
bool use_pci;
+ enum x86_gpiochip_type gpiochip_type;
};
int x86_android_tablet_get_gpiod(const char *chip, int pin, const char *con_id,
@@ -106,10 +103,15 @@ int x86_android_tablet_get_gpiod(const char *chip, int pin, const char *con_id,
struct gpio_desc **desc);
int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data);
+/* Software nodes representing GPIO chips used by various tablets */
+extern const struct software_node baytrail_gpiochip_nodes[];
+extern const struct software_node cherryview_gpiochip_nodes[];
+
/*
* Extern declarations of x86_dev_info structs so there can be a single
* MODULE_DEVICE_TABLE(dmi, ...), while splitting the board descriptions.
*/
+extern const struct x86_dev_info acer_a1_840_info;
extern const struct x86_dev_info acer_b1_750_info;
extern const struct x86_dev_info advantech_mica_071_info;
extern const struct x86_dev_info asus_me176c_info;
diff --git a/drivers/platform/x86/xiaomi-wmi.c b/drivers/platform/x86/xiaomi-wmi.c
index cbed29ca502a..b892007b9863 100644
--- a/drivers/platform/x86/xiaomi-wmi.c
+++ b/drivers/platform/x86/xiaomi-wmi.c
@@ -26,13 +26,6 @@ struct xiaomi_wmi {
unsigned int key_code;
};
-static void xiaomi_mutex_destroy(void *data)
-{
- struct mutex *lock = data;
-
- mutex_destroy(lock);
-}
-
static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context)
{
struct xiaomi_wmi *data;
@@ -46,8 +39,7 @@ static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context)
return -ENOMEM;
dev_set_drvdata(&wdev->dev, data);
- mutex_init(&data->key_lock);
- ret = devm_add_action_or_reset(&wdev->dev, xiaomi_mutex_destroy, &data->key_lock);
+ ret = devm_mutex_init(&wdev->dev, &data->key_lock);
if (ret < 0)
return ret;
diff --git a/drivers/pps/kapi.c b/drivers/pps/kapi.c
index 92d1b62ea239..e9389876229e 100644
--- a/drivers/pps/kapi.c
+++ b/drivers/pps/kapi.c
@@ -109,16 +109,13 @@ struct pps_device *pps_register_source(struct pps_source_info *info,
if (err < 0) {
pr_err("%s: unable to create char device\n",
info->name);
- goto kfree_pps;
+ goto pps_register_source_exit;
}
dev_dbg(&pps->dev, "new PPS source %s\n", info->name);
return pps;
-kfree_pps:
- kfree(pps);
-
pps_register_source_exit:
pr_err("%s: unable to register source\n", info->name);
diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c
index 9463232af8d2..c6b8b6478276 100644
--- a/drivers/pps/pps.c
+++ b/drivers/pps/pps.c
@@ -374,6 +374,7 @@ int pps_register_cdev(struct pps_device *pps)
pps->info.name);
err = -EBUSY;
}
+ kfree(pps);
goto out_unlock;
}
pps->id = err;
@@ -383,13 +384,11 @@ int pps_register_cdev(struct pps_device *pps)
pps->dev.devt = MKDEV(pps_major, pps->id);
dev_set_drvdata(&pps->dev, pps);
dev_set_name(&pps->dev, "pps%d", pps->id);
+ pps->dev.release = pps_device_destruct;
err = device_register(&pps->dev);
if (err)
goto free_idr;
- /* Override the release function with our own */
- pps->dev.release = pps_device_destruct;
-
pr_debug("source %s got cdev (%d:%d)\n", pps->info.name, pps_major,
pps->id);
diff --git a/drivers/remoteproc/da8xx_remoteproc.c b/drivers/remoteproc/da8xx_remoteproc.c
index 93031f0867d1..e418a2bf5d2e 100644
--- a/drivers/remoteproc/da8xx_remoteproc.c
+++ b/drivers/remoteproc/da8xx_remoteproc.c
@@ -233,6 +233,13 @@ static int da8xx_rproc_get_internal_memories(struct platform_device *pdev,
return 0;
}
+static void da8xx_rproc_mem_release(void *data)
+{
+ struct device *dev = data;
+
+ of_reserved_mem_device_release(dev);
+}
+
static int da8xx_rproc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -274,14 +281,13 @@ static int da8xx_rproc_probe(struct platform_device *pdev)
ret = of_reserved_mem_device_init(dev);
if (ret)
return dev_err_probe(dev, ret, "device does not have specific CMA pool\n");
+ devm_add_action_or_reset(&pdev->dev, da8xx_rproc_mem_release, &pdev->dev);
}
- rproc = rproc_alloc(dev, "dsp", &da8xx_rproc_ops, da8xx_fw_name,
- sizeof(*drproc));
- if (!rproc) {
- ret = -ENOMEM;
- goto free_mem;
- }
+ rproc = devm_rproc_alloc(dev, "dsp", &da8xx_rproc_ops, da8xx_fw_name,
+ sizeof(*drproc));
+ if (!rproc)
+ return -ENOMEM;
/* error recovery is not supported at present */
rproc->recovery_disabled = true;
@@ -294,9 +300,7 @@ static int da8xx_rproc_probe(struct platform_device *pdev)
ret = da8xx_rproc_get_internal_memories(pdev, drproc);
if (ret)
- goto free_rproc;
-
- platform_set_drvdata(pdev, rproc);
+ return ret;
/* everything the ISR needs is now setup, so hook it up */
ret = devm_request_threaded_irq(dev, irq, da8xx_rproc_callback,
@@ -304,7 +308,7 @@ static int da8xx_rproc_probe(struct platform_device *pdev)
rproc);
if (ret) {
dev_err(dev, "devm_request_threaded_irq error: %d\n", ret);
- goto free_rproc;
+ return ret;
}
/*
@@ -314,7 +318,7 @@ static int da8xx_rproc_probe(struct platform_device *pdev)
*/
ret = reset_control_assert(dsp_reset);
if (ret)
- goto free_rproc;
+ return ret;
drproc->chipsig = chipsig;
drproc->bootreg = bootreg;
@@ -322,39 +326,13 @@ static int da8xx_rproc_probe(struct platform_device *pdev)
drproc->irq_data = irq_data;
drproc->irq = irq;
- ret = rproc_add(rproc);
+ ret = devm_rproc_add(dev, rproc);
if (ret) {
dev_err(dev, "rproc_add failed: %d\n", ret);
- goto free_rproc;
+ return ret;
}
return 0;
-
-free_rproc:
- rproc_free(rproc);
-free_mem:
- if (dev->of_node)
- of_reserved_mem_device_release(dev);
- return ret;
-}
-
-static void da8xx_rproc_remove(struct platform_device *pdev)
-{
- struct rproc *rproc = platform_get_drvdata(pdev);
- struct da8xx_rproc *drproc = rproc->priv;
- struct device *dev = &pdev->dev;
-
- /*
- * The devm subsystem might end up releasing things before
- * freeing the irq, thus allowing an interrupt to sneak in while
- * the device is being removed. This should prevent that.
- */
- disable_irq(drproc->irq);
-
- rproc_del(rproc);
- rproc_free(rproc);
- if (dev->of_node)
- of_reserved_mem_device_release(dev);
}
static const struct of_device_id davinci_rproc_of_match[] __maybe_unused = {
@@ -365,7 +343,6 @@ MODULE_DEVICE_TABLE(of, davinci_rproc_of_match);
static struct platform_driver da8xx_rproc_driver = {
.probe = da8xx_rproc_probe,
- .remove = da8xx_rproc_remove,
.driver = {
.name = "davinci-rproc",
.of_match_table = of_match_ptr(davinci_rproc_of_match),
diff --git a/drivers/remoteproc/imx_dsp_rproc.c b/drivers/remoteproc/imx_dsp_rproc.c
index 5ee622bf5352..6e78a01755c7 100644
--- a/drivers/remoteproc/imx_dsp_rproc.c
+++ b/drivers/remoteproc/imx_dsp_rproc.c
@@ -774,7 +774,6 @@ static int imx_dsp_rproc_prepare(struct rproc *rproc)
{
struct imx_dsp_rproc *priv = rproc->priv;
struct device *dev = rproc->dev.parent;
- struct rproc_mem_entry *carveout;
int ret;
ret = imx_dsp_rproc_add_carveout(priv);
@@ -785,15 +784,6 @@ static int imx_dsp_rproc_prepare(struct rproc *rproc)
pm_runtime_get_sync(dev);
- /*
- * Clear buffers after pm rumtime for internal ocram is not
- * accessible if power and clock are not enabled.
- */
- list_for_each_entry(carveout, &rproc->carveouts, node) {
- if (carveout->va)
- memset(carveout->va, 0, carveout->len);
- }
-
return 0;
}
@@ -1022,13 +1012,39 @@ static int imx_dsp_rproc_parse_fw(struct rproc *rproc, const struct firmware *fw
return 0;
}
+static int imx_dsp_rproc_load(struct rproc *rproc, const struct firmware *fw)
+{
+ struct imx_dsp_rproc *priv = rproc->priv;
+ const struct imx_dsp_rproc_dcfg *dsp_dcfg = priv->dsp_dcfg;
+ struct rproc_mem_entry *carveout;
+ int ret;
+
+ /* Reset DSP if needed */
+ if (dsp_dcfg->reset)
+ dsp_dcfg->reset(priv);
+ /*
+ * Clear buffers after pm rumtime for internal ocram is not
+ * accessible if power and clock are not enabled.
+ */
+ list_for_each_entry(carveout, &rproc->carveouts, node) {
+ if (carveout->va)
+ memset(carveout->va, 0, carveout->len);
+ }
+
+ ret = imx_dsp_rproc_elf_load_segments(rproc, fw);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
static const struct rproc_ops imx_dsp_rproc_ops = {
.prepare = imx_dsp_rproc_prepare,
.unprepare = imx_dsp_rproc_unprepare,
.start = imx_dsp_rproc_start,
.stop = imx_dsp_rproc_stop,
.kick = imx_dsp_rproc_kick,
- .load = imx_dsp_rproc_elf_load_segments,
+ .load = imx_dsp_rproc_load,
.parse_fw = imx_dsp_rproc_parse_fw,
.handle_rsc = imx_dsp_rproc_handle_rsc,
.find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table,
@@ -1189,6 +1205,8 @@ static int imx_dsp_rproc_probe(struct platform_device *pdev)
goto err_detach_domains;
}
+ rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_XTENSA);
+
pm_runtime_enable(dev);
return 0;
@@ -1214,7 +1232,6 @@ static int imx_dsp_runtime_resume(struct device *dev)
{
struct rproc *rproc = dev_get_drvdata(dev);
struct imx_dsp_rproc *priv = rproc->priv;
- const struct imx_dsp_rproc_dcfg *dsp_dcfg = priv->dsp_dcfg;
int ret;
/*
@@ -1235,10 +1252,6 @@ static int imx_dsp_runtime_resume(struct device *dev)
return ret;
}
- /* Reset DSP if needed */
- if (dsp_dcfg->reset)
- dsp_dcfg->reset(priv);
-
return 0;
}
diff --git a/drivers/remoteproc/imx_rproc.c b/drivers/remoteproc/imx_rproc.c
index a6eef0080ca9..bb25221a4a89 100644
--- a/drivers/remoteproc/imx_rproc.c
+++ b/drivers/remoteproc/imx_rproc.c
@@ -285,161 +285,101 @@ static const struct imx_rproc_att imx_rproc_att_imx6sx[] = {
{ 0x80000000, 0x80000000, 0x60000000, 0 },
};
-static const struct imx_rproc_dcfg imx_rproc_cfg_imx8mn_mmio = {
- .src_reg = IMX7D_SRC_SCR,
- .src_mask = IMX7D_M4_RST_MASK,
- .src_start = IMX7D_M4_START,
- .src_stop = IMX8M_M7_STOP,
- .gpr_reg = IMX8M_GPR22,
- .gpr_wait = IMX8M_GPR22_CM7_CPUWAIT,
- .att = imx_rproc_att_imx8mn,
- .att_size = ARRAY_SIZE(imx_rproc_att_imx8mn),
- .method = IMX_RPROC_MMIO,
-};
-
-static const struct imx_rproc_dcfg imx_rproc_cfg_imx8mn = {
- .att = imx_rproc_att_imx8mn,
- .att_size = ARRAY_SIZE(imx_rproc_att_imx8mn),
- .method = IMX_RPROC_SMC,
-};
-
-static const struct imx_rproc_dcfg imx_rproc_cfg_imx8mq = {
- .src_reg = IMX7D_SRC_SCR,
- .src_mask = IMX7D_M4_RST_MASK,
- .src_start = IMX7D_M4_START,
- .src_stop = IMX7D_M4_STOP,
- .att = imx_rproc_att_imx8mq,
- .att_size = ARRAY_SIZE(imx_rproc_att_imx8mq),
- .method = IMX_RPROC_MMIO,
-};
+static int imx_rproc_arm_smc_start(struct rproc *rproc)
+{
+ struct arm_smccc_res res;
-static const struct imx_rproc_dcfg imx_rproc_cfg_imx8qm = {
- .att = imx_rproc_att_imx8qm,
- .att_size = ARRAY_SIZE(imx_rproc_att_imx8qm),
- .method = IMX_RPROC_SCU_API,
-};
+ arm_smccc_smc(IMX_SIP_RPROC, IMX_SIP_RPROC_START, 0, 0, 0, 0, 0, 0, &res);
-static const struct imx_rproc_dcfg imx_rproc_cfg_imx8qxp = {
- .att = imx_rproc_att_imx8qxp,
- .att_size = ARRAY_SIZE(imx_rproc_att_imx8qxp),
- .method = IMX_RPROC_SCU_API,
-};
+ return res.a0;
+}
-static const struct imx_rproc_dcfg imx_rproc_cfg_imx8ulp = {
- .att = imx_rproc_att_imx8ulp,
- .att_size = ARRAY_SIZE(imx_rproc_att_imx8ulp),
- .method = IMX_RPROC_NONE,
-};
+static int imx_rproc_mmio_start(struct rproc *rproc)
+{
+ struct imx_rproc *priv = rproc->priv;
+ const struct imx_rproc_dcfg *dcfg = priv->dcfg;
-static const struct imx_rproc_dcfg imx_rproc_cfg_imx7ulp = {
- .att = imx_rproc_att_imx7ulp,
- .att_size = ARRAY_SIZE(imx_rproc_att_imx7ulp),
- .method = IMX_RPROC_NONE,
- .flags = IMX_RPROC_NEED_SYSTEM_OFF,
-};
+ if (priv->gpr)
+ return regmap_clear_bits(priv->gpr, dcfg->gpr_reg, dcfg->gpr_wait);
-static const struct imx_rproc_dcfg imx_rproc_cfg_imx7d = {
- .src_reg = IMX7D_SRC_SCR,
- .src_mask = IMX7D_M4_RST_MASK,
- .src_start = IMX7D_M4_START,
- .src_stop = IMX7D_M4_STOP,
- .att = imx_rproc_att_imx7d,
- .att_size = ARRAY_SIZE(imx_rproc_att_imx7d),
- .method = IMX_RPROC_MMIO,
-};
+ return regmap_update_bits(priv->regmap, dcfg->src_reg, dcfg->src_mask, dcfg->src_start);
+}
-static const struct imx_rproc_dcfg imx_rproc_cfg_imx6sx = {
- .src_reg = IMX6SX_SRC_SCR,
- .src_mask = IMX6SX_M4_RST_MASK,
- .src_start = IMX6SX_M4_START,
- .src_stop = IMX6SX_M4_STOP,
- .att = imx_rproc_att_imx6sx,
- .att_size = ARRAY_SIZE(imx_rproc_att_imx6sx),
- .method = IMX_RPROC_MMIO,
-};
+static int imx_rproc_scu_api_start(struct rproc *rproc)
+{
+ struct imx_rproc *priv = rproc->priv;
-static const struct imx_rproc_dcfg imx_rproc_cfg_imx93 = {
- .att = imx_rproc_att_imx93,
- .att_size = ARRAY_SIZE(imx_rproc_att_imx93),
- .method = IMX_RPROC_SMC,
-};
+ return imx_sc_pm_cpu_start(priv->ipc_handle, priv->rsrc_id, true, priv->entry);
+}
static int imx_rproc_start(struct rproc *rproc)
{
struct imx_rproc *priv = rproc->priv;
const struct imx_rproc_dcfg *dcfg = priv->dcfg;
struct device *dev = priv->dev;
- struct arm_smccc_res res;
int ret;
ret = imx_rproc_xtr_mbox_init(rproc, true);
if (ret)
return ret;
- switch (dcfg->method) {
- case IMX_RPROC_MMIO:
- if (priv->gpr) {
- ret = regmap_clear_bits(priv->gpr, dcfg->gpr_reg,
- dcfg->gpr_wait);
- } else {
- ret = regmap_update_bits(priv->regmap, dcfg->src_reg,
- dcfg->src_mask,
- dcfg->src_start);
- }
- break;
- case IMX_RPROC_SMC:
- arm_smccc_smc(IMX_SIP_RPROC, IMX_SIP_RPROC_START, 0, 0, 0, 0, 0, 0, &res);
- ret = res.a0;
- break;
- case IMX_RPROC_SCU_API:
- ret = imx_sc_pm_cpu_start(priv->ipc_handle, priv->rsrc_id, true, priv->entry);
- break;
- default:
+ if (!dcfg->ops || !dcfg->ops->start)
return -EOPNOTSUPP;
- }
+ ret = dcfg->ops->start(rproc);
if (ret)
dev_err(dev, "Failed to enable remote core!\n");
return ret;
}
-static int imx_rproc_stop(struct rproc *rproc)
+static int imx_rproc_arm_smc_stop(struct rproc *rproc)
{
struct imx_rproc *priv = rproc->priv;
- const struct imx_rproc_dcfg *dcfg = priv->dcfg;
- struct device *dev = priv->dev;
struct arm_smccc_res res;
+
+ arm_smccc_smc(IMX_SIP_RPROC, IMX_SIP_RPROC_STOP, 0, 0, 0, 0, 0, 0, &res);
+ if (res.a1)
+ dev_info(priv->dev, "Not in wfi, force stopped\n");
+
+ return res.a0;
+}
+
+static int imx_rproc_mmio_stop(struct rproc *rproc)
+{
+ struct imx_rproc *priv = rproc->priv;
+ const struct imx_rproc_dcfg *dcfg = priv->dcfg;
int ret;
- switch (dcfg->method) {
- case IMX_RPROC_MMIO:
- if (priv->gpr) {
- ret = regmap_set_bits(priv->gpr, dcfg->gpr_reg,
- dcfg->gpr_wait);
- if (ret) {
- dev_err(priv->dev,
- "Failed to quiescence M4 platform!\n");
- return ret;
- }
+ if (priv->gpr) {
+ ret = regmap_set_bits(priv->gpr, dcfg->gpr_reg, dcfg->gpr_wait);
+ if (ret) {
+ dev_err(priv->dev, "Failed to quiescence M4 platform!\n");
+ return ret;
}
+ }
+
+ return regmap_update_bits(priv->regmap, dcfg->src_reg, dcfg->src_mask, dcfg->src_stop);
+}
+
+static int imx_rproc_scu_api_stop(struct rproc *rproc)
+{
+ struct imx_rproc *priv = rproc->priv;
+
+ return imx_sc_pm_cpu_start(priv->ipc_handle, priv->rsrc_id, false, priv->entry);
+}
+
+static int imx_rproc_stop(struct rproc *rproc)
+{
+ struct imx_rproc *priv = rproc->priv;
+ const struct imx_rproc_dcfg *dcfg = priv->dcfg;
+ struct device *dev = priv->dev;
+ int ret;
- ret = regmap_update_bits(priv->regmap, dcfg->src_reg, dcfg->src_mask,
- dcfg->src_stop);
- break;
- case IMX_RPROC_SMC:
- arm_smccc_smc(IMX_SIP_RPROC, IMX_SIP_RPROC_STOP, 0, 0, 0, 0, 0, 0, &res);
- ret = res.a0;
- if (res.a1)
- dev_info(dev, "Not in wfi, force stopped\n");
- break;
- case IMX_RPROC_SCU_API:
- ret = imx_sc_pm_cpu_start(priv->ipc_handle, priv->rsrc_id, false, priv->entry);
- break;
- default:
+ if (!dcfg->ops || !dcfg->ops->stop)
return -EOPNOTSUPP;
- }
+ ret = dcfg->ops->stop(rproc);
if (ret)
dev_err(dev, "Failed to stop remote core\n");
else
@@ -922,84 +862,27 @@ static int imx_rproc_attach_pd(struct imx_rproc *priv)
return 0;
}
-static int imx_rproc_detect_mode(struct imx_rproc *priv)
+static int imx_rproc_arm_smc_detect_mode(struct rproc *rproc)
{
- struct regmap_config config = { .name = "imx-rproc" };
- const struct imx_rproc_dcfg *dcfg = priv->dcfg;
- struct device *dev = priv->dev;
- struct regmap *regmap;
+ struct imx_rproc *priv = rproc->priv;
struct arm_smccc_res res;
- int ret;
- u32 val;
- u8 pt;
- switch (dcfg->method) {
- case IMX_RPROC_NONE:
+ arm_smccc_smc(IMX_SIP_RPROC, IMX_SIP_RPROC_STARTED, 0, 0, 0, 0, 0, 0, &res);
+ if (res.a0)
priv->rproc->state = RPROC_DETACHED;
- return 0;
- case IMX_RPROC_SMC:
- arm_smccc_smc(IMX_SIP_RPROC, IMX_SIP_RPROC_STARTED, 0, 0, 0, 0, 0, 0, &res);
- if (res.a0)
- priv->rproc->state = RPROC_DETACHED;
- return 0;
- case IMX_RPROC_SCU_API:
- ret = imx_scu_get_handle(&priv->ipc_handle);
- if (ret)
- return ret;
- ret = of_property_read_u32(dev->of_node, "fsl,resource-id", &priv->rsrc_id);
- if (ret) {
- dev_err(dev, "No fsl,resource-id property\n");
- return ret;
- }
-
- if (priv->rsrc_id == IMX_SC_R_M4_1_PID0)
- priv->core_index = 1;
- else
- priv->core_index = 0;
- /*
- * If Mcore resource is not owned by Acore partition, It is kicked by ROM,
- * and Linux could only do IPC with Mcore and nothing else.
- */
- if (imx_sc_rm_is_resource_owned(priv->ipc_handle, priv->rsrc_id)) {
- if (of_property_read_u32(dev->of_node, "fsl,entry-address", &priv->entry))
- return -EINVAL;
-
- return imx_rproc_attach_pd(priv);
- }
-
- priv->rproc->state = RPROC_DETACHED;
- priv->rproc->recovery_disabled = false;
- rproc_set_feature(priv->rproc, RPROC_FEAT_ATTACH_ON_RECOVERY);
-
- /* Get partition id and enable irq in SCFW */
- ret = imx_sc_rm_get_resource_owner(priv->ipc_handle, priv->rsrc_id, &pt);
- if (ret) {
- dev_err(dev, "not able to get resource owner\n");
- return ret;
- }
-
- priv->rproc_pt = pt;
- priv->rproc_nb.notifier_call = imx_rproc_partition_notify;
-
- ret = imx_scu_irq_register_notifier(&priv->rproc_nb);
- if (ret) {
- dev_err(dev, "register scu notifier failed, %d\n", ret);
- return ret;
- }
-
- ret = imx_scu_irq_group_enable(IMX_SC_IRQ_GROUP_REBOOTED, BIT(priv->rproc_pt),
- true);
- if (ret) {
- imx_scu_irq_unregister_notifier(&priv->rproc_nb);
- dev_err(dev, "Enable irq failed, %d\n", ret);
- return ret;
- }
+ return 0;
+}
- return 0;
- default:
- break;
- }
+static int imx_rproc_mmio_detect_mode(struct rproc *rproc)
+{
+ const struct regmap_config config = { .name = "imx-rproc" };
+ struct imx_rproc *priv = rproc->priv;
+ const struct imx_rproc_dcfg *dcfg = priv->dcfg;
+ struct device *dev = priv->dev;
+ struct regmap *regmap;
+ u32 val;
+ int ret;
priv->gpr = syscon_regmap_lookup_by_phandle(dev->of_node, "fsl,iomuxc-gpr");
if (IS_ERR(priv->gpr))
@@ -1039,6 +922,85 @@ static int imx_rproc_detect_mode(struct imx_rproc *priv)
return 0;
}
+static int imx_rproc_scu_api_detect_mode(struct rproc *rproc)
+{
+ struct imx_rproc *priv = rproc->priv;
+ struct device *dev = priv->dev;
+ int ret;
+ u8 pt;
+
+ ret = imx_scu_get_handle(&priv->ipc_handle);
+ if (ret)
+ return ret;
+ ret = of_property_read_u32(dev->of_node, "fsl,resource-id", &priv->rsrc_id);
+ if (ret) {
+ dev_err(dev, "No fsl,resource-id property\n");
+ return ret;
+ }
+
+ if (priv->rsrc_id == IMX_SC_R_M4_1_PID0)
+ priv->core_index = 1;
+ else
+ priv->core_index = 0;
+
+ /*
+ * If Mcore resource is not owned by Acore partition, It is kicked by ROM,
+ * and Linux could only do IPC with Mcore and nothing else.
+ */
+ if (imx_sc_rm_is_resource_owned(priv->ipc_handle, priv->rsrc_id)) {
+ if (of_property_read_u32(dev->of_node, "fsl,entry-address", &priv->entry))
+ return -EINVAL;
+
+ return imx_rproc_attach_pd(priv);
+ }
+
+ priv->rproc->state = RPROC_DETACHED;
+ priv->rproc->recovery_disabled = false;
+ rproc_set_feature(priv->rproc, RPROC_FEAT_ATTACH_ON_RECOVERY);
+
+ /* Get partition id and enable irq in SCFW */
+ ret = imx_sc_rm_get_resource_owner(priv->ipc_handle, priv->rsrc_id, &pt);
+ if (ret) {
+ dev_err(dev, "not able to get resource owner\n");
+ return ret;
+ }
+
+ priv->rproc_pt = pt;
+ priv->rproc_nb.notifier_call = imx_rproc_partition_notify;
+
+ ret = imx_scu_irq_register_notifier(&priv->rproc_nb);
+ if (ret) {
+ dev_err(dev, "register scu notifier failed, %d\n", ret);
+ return ret;
+ }
+
+ ret = imx_scu_irq_group_enable(IMX_SC_IRQ_GROUP_REBOOTED, BIT(priv->rproc_pt),
+ true);
+ if (ret) {
+ imx_scu_irq_unregister_notifier(&priv->rproc_nb);
+ dev_err(dev, "Enable irq failed, %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imx_rproc_detect_mode(struct imx_rproc *priv)
+{
+ const struct imx_rproc_dcfg *dcfg = priv->dcfg;
+
+ /*
+ * To i.MX{7,8} ULP, Linux is under control of RTOS, no need
+ * dcfg->ops or dcfg->ops->detect_mode, it is state RPROC_DETACHED.
+ */
+ if (!dcfg->ops || !dcfg->ops->detect_mode) {
+ priv->rproc->state = RPROC_DETACHED;
+ return 0;
+ }
+
+ return dcfg->ops->detect_mode(priv->rproc);
+}
+
static int imx_rproc_clk_enable(struct imx_rproc *priv)
{
const struct imx_rproc_dcfg *dcfg = priv->dcfg;
@@ -1207,6 +1169,111 @@ static void imx_rproc_remove(struct platform_device *pdev)
destroy_workqueue(priv->workqueue);
}
+static const struct imx_rproc_plat_ops imx_rproc_ops_arm_smc = {
+ .start = imx_rproc_arm_smc_start,
+ .stop = imx_rproc_arm_smc_stop,
+ .detect_mode = imx_rproc_arm_smc_detect_mode,
+};
+
+static const struct imx_rproc_plat_ops imx_rproc_ops_mmio = {
+ .start = imx_rproc_mmio_start,
+ .stop = imx_rproc_mmio_stop,
+ .detect_mode = imx_rproc_mmio_detect_mode,
+};
+
+static const struct imx_rproc_plat_ops imx_rproc_ops_scu_api = {
+ .start = imx_rproc_scu_api_start,
+ .stop = imx_rproc_scu_api_stop,
+ .detect_mode = imx_rproc_scu_api_detect_mode,
+};
+
+static const struct imx_rproc_dcfg imx_rproc_cfg_imx8mn_mmio = {
+ .src_reg = IMX7D_SRC_SCR,
+ .src_mask = IMX7D_M4_RST_MASK,
+ .src_start = IMX7D_M4_START,
+ .src_stop = IMX8M_M7_STOP,
+ .gpr_reg = IMX8M_GPR22,
+ .gpr_wait = IMX8M_GPR22_CM7_CPUWAIT,
+ .att = imx_rproc_att_imx8mn,
+ .att_size = ARRAY_SIZE(imx_rproc_att_imx8mn),
+ .method = IMX_RPROC_MMIO,
+ .ops = &imx_rproc_ops_mmio,
+};
+
+static const struct imx_rproc_dcfg imx_rproc_cfg_imx8mn = {
+ .att = imx_rproc_att_imx8mn,
+ .att_size = ARRAY_SIZE(imx_rproc_att_imx8mn),
+ .method = IMX_RPROC_SMC,
+ .ops = &imx_rproc_ops_arm_smc,
+};
+
+static const struct imx_rproc_dcfg imx_rproc_cfg_imx8mq = {
+ .src_reg = IMX7D_SRC_SCR,
+ .src_mask = IMX7D_M4_RST_MASK,
+ .src_start = IMX7D_M4_START,
+ .src_stop = IMX7D_M4_STOP,
+ .att = imx_rproc_att_imx8mq,
+ .att_size = ARRAY_SIZE(imx_rproc_att_imx8mq),
+ .method = IMX_RPROC_MMIO,
+ .ops = &imx_rproc_ops_mmio,
+};
+
+static const struct imx_rproc_dcfg imx_rproc_cfg_imx8qm = {
+ .att = imx_rproc_att_imx8qm,
+ .att_size = ARRAY_SIZE(imx_rproc_att_imx8qm),
+ .method = IMX_RPROC_SCU_API,
+ .ops = &imx_rproc_ops_scu_api,
+};
+
+static const struct imx_rproc_dcfg imx_rproc_cfg_imx8qxp = {
+ .att = imx_rproc_att_imx8qxp,
+ .att_size = ARRAY_SIZE(imx_rproc_att_imx8qxp),
+ .method = IMX_RPROC_SCU_API,
+ .ops = &imx_rproc_ops_scu_api,
+};
+
+static const struct imx_rproc_dcfg imx_rproc_cfg_imx8ulp = {
+ .att = imx_rproc_att_imx8ulp,
+ .att_size = ARRAY_SIZE(imx_rproc_att_imx8ulp),
+ .method = IMX_RPROC_NONE,
+};
+
+static const struct imx_rproc_dcfg imx_rproc_cfg_imx7ulp = {
+ .att = imx_rproc_att_imx7ulp,
+ .att_size = ARRAY_SIZE(imx_rproc_att_imx7ulp),
+ .method = IMX_RPROC_NONE,
+ .flags = IMX_RPROC_NEED_SYSTEM_OFF,
+};
+
+static const struct imx_rproc_dcfg imx_rproc_cfg_imx7d = {
+ .src_reg = IMX7D_SRC_SCR,
+ .src_mask = IMX7D_M4_RST_MASK,
+ .src_start = IMX7D_M4_START,
+ .src_stop = IMX7D_M4_STOP,
+ .att = imx_rproc_att_imx7d,
+ .att_size = ARRAY_SIZE(imx_rproc_att_imx7d),
+ .method = IMX_RPROC_MMIO,
+ .ops = &imx_rproc_ops_mmio,
+};
+
+static const struct imx_rproc_dcfg imx_rproc_cfg_imx6sx = {
+ .src_reg = IMX6SX_SRC_SCR,
+ .src_mask = IMX6SX_M4_RST_MASK,
+ .src_start = IMX6SX_M4_START,
+ .src_stop = IMX6SX_M4_STOP,
+ .att = imx_rproc_att_imx6sx,
+ .att_size = ARRAY_SIZE(imx_rproc_att_imx6sx),
+ .method = IMX_RPROC_MMIO,
+ .ops = &imx_rproc_ops_mmio,
+};
+
+static const struct imx_rproc_dcfg imx_rproc_cfg_imx93 = {
+ .att = imx_rproc_att_imx93,
+ .att_size = ARRAY_SIZE(imx_rproc_att_imx93),
+ .method = IMX_RPROC_SMC,
+ .ops = &imx_rproc_ops_arm_smc,
+};
+
static const struct of_device_id imx_rproc_of_match[] = {
{ .compatible = "fsl,imx7ulp-cm4", .data = &imx_rproc_cfg_imx7ulp },
{ .compatible = "fsl,imx7d-cm4", .data = &imx_rproc_cfg_imx7d },
diff --git a/drivers/remoteproc/imx_rproc.h b/drivers/remoteproc/imx_rproc.h
index cfd38d37e146..3a9adaaf048b 100644
--- a/drivers/remoteproc/imx_rproc.h
+++ b/drivers/remoteproc/imx_rproc.h
@@ -31,6 +31,12 @@ enum imx_rproc_method {
/* dcfg flags */
#define IMX_RPROC_NEED_SYSTEM_OFF BIT(0)
+struct imx_rproc_plat_ops {
+ int (*start)(struct rproc *rproc);
+ int (*stop)(struct rproc *rproc);
+ int (*detect_mode)(struct rproc *rproc);
+};
+
struct imx_rproc_dcfg {
u32 src_reg;
u32 src_mask;
@@ -42,6 +48,7 @@ struct imx_rproc_dcfg {
size_t att_size;
enum imx_rproc_method method;
u32 flags;
+ const struct imx_rproc_plat_ops *ops;
};
#endif /* _IMX_RPROC_H */
diff --git a/drivers/remoteproc/keystone_remoteproc.c b/drivers/remoteproc/keystone_remoteproc.c
index 7b41b4547fa8..4d6550b48567 100644
--- a/drivers/remoteproc/keystone_remoteproc.c
+++ b/drivers/remoteproc/keystone_remoteproc.c
@@ -349,6 +349,20 @@ static int keystone_rproc_of_get_dev_syscon(struct platform_device *pdev,
return 0;
}
+static void keystone_rproc_mem_release(void *data)
+{
+ struct device *dev = data;
+
+ of_reserved_mem_device_release(dev);
+}
+
+static void keystone_rproc_pm_runtime_put(void *data)
+{
+ struct device *dev = data;
+
+ pm_runtime_put_sync(dev);
+}
+
static int keystone_rproc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -394,80 +408,58 @@ static int keystone_rproc_probe(struct platform_device *pdev)
return PTR_ERR(ksproc->reset);
/* enable clock for accessing DSP internal memories */
- pm_runtime_enable(dev);
+ ret = devm_pm_runtime_enable(dev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to enable runtime PM\n");
+
ret = pm_runtime_resume_and_get(dev);
- if (ret < 0) {
- dev_err(dev, "failed to enable clock, status = %d\n", ret);
- goto disable_rpm;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to enable clock\n");
+
+ ret = devm_add_action_or_reset(dev, keystone_rproc_pm_runtime_put, dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add disable pm devm action\n");
ret = keystone_rproc_of_get_memories(pdev, ksproc);
if (ret)
- goto disable_clk;
+ return ret;
ksproc->irq_ring = platform_get_irq_byname(pdev, "vring");
- if (ksproc->irq_ring < 0) {
- ret = ksproc->irq_ring;
- goto disable_clk;
- }
+ if (ksproc->irq_ring < 0)
+ return ksproc->irq_ring;
ksproc->irq_fault = platform_get_irq_byname(pdev, "exception");
- if (ksproc->irq_fault < 0) {
- ret = ksproc->irq_fault;
- goto disable_clk;
- }
+ if (ksproc->irq_fault < 0)
+ return ksproc->irq_fault;
- ksproc->kick_gpio = gpiod_get(dev, "kick", GPIOD_ASIS);
+ ksproc->kick_gpio = devm_gpiod_get(dev, "kick", GPIOD_ASIS);
ret = PTR_ERR_OR_ZERO(ksproc->kick_gpio);
- if (ret) {
- dev_err(dev, "failed to get gpio for virtio kicks, status = %d\n",
- ret);
- goto disable_clk;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get gpio for virtio kicks\n");
- if (of_reserved_mem_device_init(dev))
+ ret = of_reserved_mem_device_init(dev);
+ if (ret) {
dev_warn(dev, "device does not have specific CMA pool\n");
+ } else {
+ ret = devm_add_action_or_reset(dev, keystone_rproc_mem_release, dev);
+ if (ret)
+ return ret;
+ }
/* ensure the DSP is in reset before loading firmware */
ret = reset_control_status(ksproc->reset);
if (ret < 0) {
- dev_err(dev, "failed to get reset status, status = %d\n", ret);
- goto release_mem;
+ return dev_err_probe(dev, ret, "failed to get reset status\n");
} else if (ret == 0) {
WARN(1, "device is not in reset\n");
keystone_rproc_dsp_reset(ksproc);
}
- ret = rproc_add(rproc);
- if (ret) {
- dev_err(dev, "failed to add register device with remoteproc core, status = %d\n",
- ret);
- goto release_mem;
- }
-
- platform_set_drvdata(pdev, ksproc);
+ ret = devm_rproc_add(dev, rproc);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register device with remoteproc core\n");
return 0;
-
-release_mem:
- of_reserved_mem_device_release(dev);
- gpiod_put(ksproc->kick_gpio);
-disable_clk:
- pm_runtime_put_sync(dev);
-disable_rpm:
- pm_runtime_disable(dev);
- return ret;
-}
-
-static void keystone_rproc_remove(struct platform_device *pdev)
-{
- struct keystone_rproc *ksproc = platform_get_drvdata(pdev);
-
- rproc_del(ksproc->rproc);
- gpiod_put(ksproc->kick_gpio);
- pm_runtime_put_sync(&pdev->dev);
- pm_runtime_disable(&pdev->dev);
- of_reserved_mem_device_release(&pdev->dev);
}
static const struct of_device_id keystone_rproc_of_match[] = {
@@ -481,7 +473,6 @@ MODULE_DEVICE_TABLE(of, keystone_rproc_of_match);
static struct platform_driver keystone_rproc_driver = {
.probe = keystone_rproc_probe,
- .remove = keystone_rproc_remove,
.driver = {
.name = "keystone-rproc",
.of_match_table = keystone_rproc_of_match,
diff --git a/drivers/remoteproc/pru_rproc.c b/drivers/remoteproc/pru_rproc.c
index 842e4b6cc5f9..5e3eb7b86a0e 100644
--- a/drivers/remoteproc/pru_rproc.c
+++ b/drivers/remoteproc/pru_rproc.c
@@ -340,7 +340,7 @@ EXPORT_SYMBOL_GPL(pru_rproc_put);
*/
int pru_rproc_set_ctable(struct rproc *rproc, enum pru_ctable_idx c, u32 addr)
{
- struct pru_rproc *pru = rproc->priv;
+ struct pru_rproc *pru;
unsigned int reg;
u32 mask, set;
u16 idx;
@@ -352,6 +352,7 @@ int pru_rproc_set_ctable(struct rproc *rproc, enum pru_ctable_idx c, u32 addr)
if (!rproc->dev.parent || !is_pru_rproc(rproc->dev.parent))
return -ENODEV;
+ pru = rproc->priv;
/* pointer is 16 bit and index is 8-bit so mask out the rest */
idx_mask = (c >= PRU_C28) ? 0xFFFF : 0xFF;
diff --git a/drivers/remoteproc/qcom_q6v5.c b/drivers/remoteproc/qcom_q6v5.c
index 4ee5e67a9f03..58d5b85e58cd 100644
--- a/drivers/remoteproc/qcom_q6v5.c
+++ b/drivers/remoteproc/qcom_q6v5.c
@@ -156,9 +156,6 @@ int qcom_q6v5_wait_for_start(struct qcom_q6v5 *q6v5, int timeout)
int ret;
ret = wait_for_completion_timeout(&q6v5->start_done, timeout);
- if (!ret)
- disable_irq(q6v5->handover_irq);
-
return !ret ? -ETIMEDOUT : 0;
}
EXPORT_SYMBOL_GPL(qcom_q6v5_wait_for_start);
@@ -167,6 +164,11 @@ static irqreturn_t q6v5_handover_interrupt(int irq, void *data)
{
struct qcom_q6v5 *q6v5 = data;
+ if (q6v5->handover_issued) {
+ dev_err(q6v5->dev, "Handover signaled, but it already happened\n");
+ return IRQ_HANDLED;
+ }
+
if (q6v5->handover)
q6v5->handover(q6v5);
diff --git a/drivers/remoteproc/qcom_q6v5_mss.c b/drivers/remoteproc/qcom_q6v5_mss.c
index 0c0199fb0e68..3087d895b87f 100644
--- a/drivers/remoteproc/qcom_q6v5_mss.c
+++ b/drivers/remoteproc/qcom_q6v5_mss.c
@@ -498,6 +498,8 @@ static void q6v5_debug_policy_load(struct q6v5 *qproc, void *mba_region)
release_firmware(dp_fw);
}
+#define MSM8974_B00_OFFSET 0x1000
+
static int q6v5_load(struct rproc *rproc, const struct firmware *fw)
{
struct q6v5 *qproc = rproc->priv;
@@ -516,7 +518,14 @@ static int q6v5_load(struct rproc *rproc, const struct firmware *fw)
return -EBUSY;
}
- memcpy(mba_region, fw->data, fw->size);
+ if ((qproc->version == MSS_MSM8974 ||
+ qproc->version == MSS_MSM8226 ||
+ qproc->version == MSS_MSM8926) &&
+ fw->size > MSM8974_B00_OFFSET &&
+ !memcmp(fw->data, ELFMAG, SELFMAG))
+ memcpy(mba_region, fw->data + MSM8974_B00_OFFSET, fw->size - MSM8974_B00_OFFSET);
+ else
+ memcpy(mba_region, fw->data, fw->size);
q6v5_debug_policy_load(qproc, mba_region);
memunmap(mba_region);
diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c
index 55a7da801183..158bcd6cc85c 100644
--- a/drivers/remoteproc/qcom_q6v5_pas.c
+++ b/drivers/remoteproc/qcom_q6v5_pas.c
@@ -42,6 +42,7 @@ struct qcom_pas_data {
int pas_id;
int dtb_pas_id;
int lite_pas_id;
+ int lite_dtb_pas_id;
unsigned int minidump_id;
bool auto_boot;
bool decrypt_shutdown;
@@ -80,6 +81,7 @@ struct qcom_pas {
int pas_id;
int dtb_pas_id;
int lite_pas_id;
+ int lite_dtb_pas_id;
unsigned int minidump_id;
int crash_reason_smem;
unsigned int smem_host_id;
@@ -225,7 +227,9 @@ static int qcom_pas_load(struct rproc *rproc, const struct firmware *fw)
pas->firmware = fw;
if (pas->lite_pas_id)
- ret = qcom_scm_pas_shutdown(pas->lite_pas_id);
+ qcom_scm_pas_shutdown(pas->lite_pas_id);
+ if (pas->lite_dtb_pas_id)
+ qcom_scm_pas_shutdown(pas->lite_dtb_pas_id);
if (pas->dtb_pas_id) {
ret = request_firmware(&pas->dtb_firmware, pas->dtb_firmware_name, pas->dev);
@@ -721,6 +725,7 @@ static int qcom_pas_probe(struct platform_device *pdev)
pas->minidump_id = desc->minidump_id;
pas->pas_id = desc->pas_id;
pas->lite_pas_id = desc->lite_pas_id;
+ pas->lite_dtb_pas_id = desc->lite_dtb_pas_id;
pas->info_name = desc->sysmon_name;
pas->smem_host_id = desc->smem_host_id;
pas->decrypt_shutdown = desc->decrypt_shutdown;
@@ -1084,6 +1089,7 @@ static const struct qcom_pas_data x1e80100_adsp_resource = {
.pas_id = 1,
.dtb_pas_id = 0x24,
.lite_pas_id = 0x1f,
+ .lite_dtb_pas_id = 0x29,
.minidump_id = 5,
.auto_boot = true,
.proxy_pd_names = (char*[]){
@@ -1255,6 +1261,26 @@ static const struct qcom_pas_data sdx55_mpss_resource = {
.ssctl_id = 0x22,
};
+static const struct qcom_pas_data milos_cdsp_resource = {
+ .crash_reason_smem = 601,
+ .firmware_name = "cdsp.mbn",
+ .dtb_firmware_name = "cdsp_dtb.mbn",
+ .pas_id = 18,
+ .dtb_pas_id = 0x25,
+ .minidump_id = 7,
+ .auto_boot = true,
+ .proxy_pd_names = (char*[]){
+ "cx",
+ "mx",
+ NULL
+ },
+ .load_state = "cdsp",
+ .ssr_name = "cdsp",
+ .sysmon_name = "cdsp",
+ .ssctl_id = 0x17,
+ .smem_host_id = 5,
+};
+
static const struct qcom_pas_data sm8450_mpss_resource = {
.crash_reason_smem = 421,
.firmware_name = "modem.mdt",
@@ -1429,6 +1455,10 @@ static const struct qcom_pas_data sm8750_mpss_resource = {
};
static const struct of_device_id qcom_pas_of_match[] = {
+ { .compatible = "qcom,milos-adsp-pas", .data = &sm8550_adsp_resource},
+ { .compatible = "qcom,milos-cdsp-pas", .data = &milos_cdsp_resource},
+ { .compatible = "qcom,milos-mpss-pas", .data = &sm8450_mpss_resource},
+ { .compatible = "qcom,milos-wpss-pas", .data = &sc7280_wpss_resource},
{ .compatible = "qcom,msm8226-adsp-pil", .data = &msm8996_adsp_resource},
{ .compatible = "qcom,msm8953-adsp-pil", .data = &msm8996_adsp_resource},
{ .compatible = "qcom,msm8974-adsp-pil", .data = &adsp_resource_init},
diff --git a/drivers/remoteproc/ti_k3_common.c b/drivers/remoteproc/ti_k3_common.c
index d4f20900f33b..56b71652e449 100644
--- a/drivers/remoteproc/ti_k3_common.c
+++ b/drivers/remoteproc/ti_k3_common.c
@@ -155,6 +155,13 @@ int k3_rproc_release(struct k3_rproc *kproc)
}
EXPORT_SYMBOL_GPL(k3_rproc_release);
+static void k3_rproc_free_channel(void *data)
+{
+ struct k3_rproc *kproc = data;
+
+ mbox_free_channel(kproc->mbox);
+}
+
int k3_rproc_request_mbox(struct rproc *rproc)
{
struct k3_rproc *kproc = rproc->priv;
@@ -173,19 +180,9 @@ int k3_rproc_request_mbox(struct rproc *rproc)
return dev_err_probe(dev, PTR_ERR(kproc->mbox),
"mbox_request_channel failed\n");
- /*
- * Ping the remote processor, this is only for sanity-sake for now;
- * there is no functional effect whatsoever.
- *
- * Note that the reply will _not_ arrive immediately: this message
- * will wait in the mailbox fifo until the remote processor is booted.
- */
- ret = mbox_send_message(kproc->mbox, (void *)RP_MBOX_ECHO_REQUEST);
- if (ret < 0) {
- dev_err(dev, "mbox_send_message failed (%pe)\n", ERR_PTR(ret));
- mbox_free_channel(kproc->mbox);
+ ret = devm_add_action_or_reset(dev, k3_rproc_free_channel, kproc);
+ if (ret)
return ret;
- }
return 0;
}
diff --git a/drivers/remoteproc/ti_k3_dsp_remoteproc.c b/drivers/remoteproc/ti_k3_dsp_remoteproc.c
index 7a72933bd403..d6ceea6dc920 100644
--- a/drivers/remoteproc/ti_k3_dsp_remoteproc.c
+++ b/drivers/remoteproc/ti_k3_dsp_remoteproc.c
@@ -175,8 +175,6 @@ static void k3_dsp_rproc_remove(struct platform_device *pdev)
if (ret)
dev_err(dev, "failed to detach proc (%pe)\n", ERR_PTR(ret));
}
-
- mbox_free_channel(kproc->mbox);
}
static const struct k3_rproc_mem_data c66_mems[] = {
diff --git a/drivers/remoteproc/ti_k3_r5_remoteproc.c b/drivers/remoteproc/ti_k3_r5_remoteproc.c
index ca5ff280d2dc..04f23295ffc1 100644
--- a/drivers/remoteproc/ti_k3_r5_remoteproc.c
+++ b/drivers/remoteproc/ti_k3_r5_remoteproc.c
@@ -1206,8 +1206,6 @@ static void k3_r5_cluster_rproc_exit(void *data)
return;
}
}
-
- mbox_free_channel(kproc->mbox);
}
}
diff --git a/drivers/remoteproc/wkup_m3_rproc.c b/drivers/remoteproc/wkup_m3_rproc.c
index d8be21e71721..2d5bfbefcacc 100644
--- a/drivers/remoteproc/wkup_m3_rproc.c
+++ b/drivers/remoteproc/wkup_m3_rproc.c
@@ -125,6 +125,13 @@ static const struct of_device_id wkup_m3_rproc_of_match[] = {
};
MODULE_DEVICE_TABLE(of, wkup_m3_rproc_of_match);
+static void wkup_m3_rproc_pm_runtime_put(void *data)
+{
+ struct device *dev = data;
+
+ pm_runtime_put_sync(dev);
+}
+
static int wkup_m3_rproc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -148,19 +155,20 @@ static int wkup_m3_rproc_probe(struct platform_device *pdev)
return -ENODEV;
}
- pm_runtime_enable(&pdev->dev);
+ ret = devm_pm_runtime_enable(dev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to enable runtime PM\n");
ret = pm_runtime_get_sync(&pdev->dev);
- if (ret < 0) {
- dev_err(&pdev->dev, "pm_runtime_get_sync() failed\n");
- goto err;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "pm_runtime_get_sync() failed\n");
+ ret = devm_add_action_or_reset(dev, wkup_m3_rproc_pm_runtime_put, dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add disable pm devm action\n");
- rproc = rproc_alloc(dev, "wkup_m3", &wkup_m3_rproc_ops,
- fw_name, sizeof(*wkupm3));
- if (!rproc) {
- ret = -ENOMEM;
- goto err;
- }
+ rproc = devm_rproc_alloc(dev, "wkup_m3", &wkup_m3_rproc_ops,
+ fw_name, sizeof(*wkupm3));
+ if (!rproc)
+ return -ENOMEM;
rproc->auto_boot = false;
rproc->sysfs_read_only = true;
@@ -175,9 +183,7 @@ static int wkup_m3_rproc_probe(struct platform_device *pdev)
if (!wkupm3->rsts) {
if (!(pdata && pdata->deassert_reset && pdata->assert_reset &&
pdata->reset_name)) {
- dev_err(dev, "Platform data missing!\n");
- ret = -ENODEV;
- goto err_put_rproc;
+ return dev_err_probe(dev, -ENODEV, "Platform data missing!\n");
}
}
@@ -185,12 +191,9 @@ static int wkup_m3_rproc_probe(struct platform_device *pdev)
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
mem_names[i]);
wkupm3->mem[i].cpu_addr = devm_ioremap_resource(dev, res);
- if (IS_ERR(wkupm3->mem[i].cpu_addr)) {
- dev_err(&pdev->dev, "devm_ioremap_resource failed for resource %d\n",
- i);
- ret = PTR_ERR(wkupm3->mem[i].cpu_addr);
- goto err_put_rproc;
- }
+ if (IS_ERR(wkupm3->mem[i].cpu_addr))
+ return dev_err_probe(dev, PTR_ERR(wkupm3->mem[i].cpu_addr),
+ "devm_ioremap_resource failed for resource %d\n", i);
wkupm3->mem[i].bus_addr = res->start;
wkupm3->mem[i].size = resource_size(res);
addrp = of_get_address(dev->of_node, i, &size, NULL);
@@ -207,30 +210,11 @@ static int wkup_m3_rproc_probe(struct platform_device *pdev)
dev_set_drvdata(dev, rproc);
- ret = rproc_add(rproc);
- if (ret) {
- dev_err(dev, "rproc_add failed\n");
- goto err_put_rproc;
- }
+ ret = devm_rproc_add(dev, rproc);
+ if (ret)
+ return dev_err_probe(dev, ret, "rproc_add failed\n");
return 0;
-
-err_put_rproc:
- rproc_free(rproc);
-err:
- pm_runtime_put_noidle(dev);
- pm_runtime_disable(dev);
- return ret;
-}
-
-static void wkup_m3_rproc_remove(struct platform_device *pdev)
-{
- struct rproc *rproc = platform_get_drvdata(pdev);
-
- rproc_del(rproc);
- rproc_free(rproc);
- pm_runtime_put_sync(&pdev->dev);
- pm_runtime_disable(&pdev->dev);
}
#ifdef CONFIG_PM
@@ -251,7 +235,6 @@ static const struct dev_pm_ops wkup_m3_rproc_pm_ops = {
static struct platform_driver wkup_m3_rproc_driver = {
.probe = wkup_m3_rproc_probe,
- .remove = wkup_m3_rproc_remove,
.driver = {
.name = "wkup_m3_rproc",
.of_match_table = wkup_m3_rproc_of_match,
diff --git a/drivers/rpmsg/qcom_glink_native.c b/drivers/rpmsg/qcom_glink_native.c
index a2f9d85c7156..820a6ca5b1d7 100644
--- a/drivers/rpmsg/qcom_glink_native.c
+++ b/drivers/rpmsg/qcom_glink_native.c
@@ -1663,7 +1663,7 @@ static int qcom_glink_rx_open(struct qcom_glink *glink, unsigned int rcid,
}
rpdev->ept = &channel->ept;
- strscpy_pad(rpdev->id.name, name, RPMSG_NAME_SIZE);
+ strscpy(rpdev->id.name, name);
rpdev->src = RPMSG_ADDR_ANY;
rpdev->dst = RPMSG_ADDR_ANY;
rpdev->ops = &glink_device_ops;
diff --git a/drivers/rpmsg/qcom_smd.c b/drivers/rpmsg/qcom_smd.c
index 87c944d4b4f3..42594f5ee438 100644
--- a/drivers/rpmsg/qcom_smd.c
+++ b/drivers/rpmsg/qcom_smd.c
@@ -1089,7 +1089,7 @@ static int qcom_smd_create_device(struct qcom_smd_channel *channel)
/* Assign public information to the rpmsg_device */
rpdev = &qsdev->rpdev;
- strscpy_pad(rpdev->id.name, channel->name, RPMSG_NAME_SIZE);
+ strscpy(rpdev->id.name, channel->name);
rpdev->src = RPMSG_ADDR_ANY;
rpdev->dst = RPMSG_ADDR_ANY;
@@ -1368,7 +1368,7 @@ static int qcom_smd_parse_edge(struct device *dev,
edge->mbox_client.knows_txdone = true;
edge->mbox_chan = mbox_request_channel(&edge->mbox_client, 0);
if (IS_ERR(edge->mbox_chan)) {
- if (PTR_ERR(edge->mbox_chan) != -ENODEV) {
+ if (PTR_ERR(edge->mbox_chan) != -ENOENT) {
ret = dev_err_probe(dev, PTR_ERR(edge->mbox_chan),
"failed to acquire IPC mailbox\n");
goto put_node;
diff --git a/drivers/rpmsg/rpmsg_char.c b/drivers/rpmsg/rpmsg_char.c
index eec7642d2686..96fcdd2d7093 100644
--- a/drivers/rpmsg/rpmsg_char.c
+++ b/drivers/rpmsg/rpmsg_char.c
@@ -522,8 +522,10 @@ static void rpmsg_chrdev_remove(struct rpmsg_device *rpdev)
static struct rpmsg_device_id rpmsg_chrdev_id_table[] = {
{ .name = "rpmsg-raw" },
+ { .name = "rpmsg_chrdev" },
{ },
};
+MODULE_DEVICE_TABLE(rpmsg, rpmsg_chrdev_id_table);
static struct rpmsg_driver rpmsg_chrdev_driver = {
.probe = rpmsg_chrdev_probe,
@@ -565,6 +567,5 @@ static void rpmsg_chrdev_exit(void)
}
module_exit(rpmsg_chrdev_exit);
-MODULE_ALIAS("rpmsg:rpmsg_chrdev");
MODULE_DESCRIPTION("RPMSG device interface");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/rpmsg/rpmsg_core.c b/drivers/rpmsg/rpmsg_core.c
index bece5e635ee9..5d661681a9b6 100644
--- a/drivers/rpmsg/rpmsg_core.c
+++ b/drivers/rpmsg/rpmsg_core.c
@@ -479,7 +479,8 @@ static int rpmsg_dev_probe(struct device *dev)
struct rpmsg_endpoint *ept = NULL;
int err;
- err = dev_pm_domain_attach(dev, PD_FLAG_ATTACH_POWER_ON);
+ err = dev_pm_domain_attach(dev, PD_FLAG_ATTACH_POWER_ON |
+ PD_FLAG_DETACH_POWER_OFF);
if (err)
goto out;
@@ -538,8 +539,6 @@ static void rpmsg_dev_remove(struct device *dev)
if (rpdrv->remove)
rpdrv->remove(rpdev);
- dev_pm_domain_detach(dev, true);
-
if (rpdev->ept)
rpmsg_destroy_ept(rpdev->ept);
}
diff --git a/drivers/s390/char/con3270.c b/drivers/s390/char/con3270.c
index b78b86e8f281..a367f95c7c53 100644
--- a/drivers/s390/char/con3270.c
+++ b/drivers/s390/char/con3270.c
@@ -970,8 +970,6 @@ static void tty3270_resize(struct raw3270_view *view,
char **old_rcl_lines, **new_rcl_lines;
char *old_prompt, *new_prompt;
char *old_input, *new_input;
- struct tty_struct *tty;
- struct winsize ws;
size_t prompt_sz;
int new_allocated, old_allocated = tp->allocated_lines;
@@ -1023,14 +1021,14 @@ static void tty3270_resize(struct raw3270_view *view,
kfree(old_prompt);
tty3270_free_recall(old_rcl_lines);
tty3270_set_timer(tp, 1);
- /* Informat tty layer about new size */
- tty = tty_port_tty_get(&tp->port);
- if (!tty)
- return;
- ws.ws_row = tty3270_tty_rows(tp);
- ws.ws_col = tp->view.cols;
- tty_do_resize(tty, &ws);
- tty_kref_put(tty);
+ /* Inform the tty layer about new size */
+ scoped_guard(tty_port_tty, &tp->port) {
+ struct winsize ws = {
+ .ws_row = tty3270_tty_rows(tp),
+ .ws_col = tp->view.cols,
+ };
+ tty_do_resize(scoped_tty(), &ws);
+ }
return;
out_screen:
tty3270_free_screen(screen, new_rows);
diff --git a/drivers/siox/siox-bus-gpio.c b/drivers/siox/siox-bus-gpio.c
index d6f936464063..413d5f92311c 100644
--- a/drivers/siox/siox-bus-gpio.c
+++ b/drivers/siox/siox-bus-gpio.c
@@ -93,8 +93,7 @@ static int siox_gpio_probe(struct platform_device *pdev)
smaster = devm_siox_master_alloc(dev, sizeof(*ddata));
if (!smaster)
- return dev_err_probe(dev, -ENOMEM,
- "failed to allocate siox master\n");
+ return -ENOMEM;
platform_set_drvdata(pdev, smaster);
ddata = siox_master_get_devdata(smaster);
diff --git a/drivers/slimbus/Kconfig b/drivers/slimbus/Kconfig
index a0fdf9d792cb..60b0dcbc0ebb 100644
--- a/drivers/slimbus/Kconfig
+++ b/drivers/slimbus/Kconfig
@@ -13,13 +13,6 @@ menuconfig SLIMBUS
if SLIMBUS
# SLIMbus controllers
-config SLIM_QCOM_CTRL
- tristate "Qualcomm SLIMbus Manager Component"
- depends on HAS_IOMEM
- help
- Select driver if Qualcomm's SLIMbus Manager Component is
- programmed using Linux kernel.
-
config SLIM_QCOM_NGD_CTRL
tristate "Qualcomm SLIMbus Satellite Non-Generic Device Component"
depends on HAS_IOMEM && DMA_ENGINE && NET
diff --git a/drivers/slimbus/Makefile b/drivers/slimbus/Makefile
index d9aa011b6804..3cfb41c3b592 100644
--- a/drivers/slimbus/Makefile
+++ b/drivers/slimbus/Makefile
@@ -6,8 +6,5 @@ obj-$(CONFIG_SLIMBUS) += slimbus.o
slimbus-y := core.o messaging.o sched.o stream.o
#Controllers
-obj-$(CONFIG_SLIM_QCOM_CTRL) += slim-qcom-ctrl.o
-slim-qcom-ctrl-y := qcom-ctrl.o
-
obj-$(CONFIG_SLIM_QCOM_NGD_CTRL) += slim-qcom-ngd-ctrl.o
slim-qcom-ngd-ctrl-y := qcom-ngd-ctrl.o
diff --git a/drivers/slimbus/messaging.c b/drivers/slimbus/messaging.c
index 6f01d944f9c6..e2dbe4a66b70 100644
--- a/drivers/slimbus/messaging.c
+++ b/drivers/slimbus/messaging.c
@@ -143,8 +143,6 @@ int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn)
if (!txn->msg->comp)
txn->comp = &done;
- else
- txn->comp = txn->comp;
}
ret = ctrl->xfer_msg(ctrl, txn);
@@ -224,7 +222,7 @@ static u16 slim_slicesize(int code)
/**
* slim_xfer_msg() - Transfer a value info message on slim device
*
- * @sbdev: slim device to which this msg has to be transfered
+ * @sbdev: slim device to which this msg has to be transferred
* @msg: value info message pointer
* @mc: message code of the message
*
diff --git a/drivers/slimbus/qcom-ctrl.c b/drivers/slimbus/qcom-ctrl.c
deleted file mode 100644
index ab344f7472f2..000000000000
--- a/drivers/slimbus/qcom-ctrl.c
+++ /dev/null
@@ -1,735 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (c) 2011-2017, The Linux Foundation
- */
-
-#include <linux/irq.h>
-#include <linux/kernel.h>
-#include <linux/init.h>
-#include <linux/slab.h>
-#include <linux/io.h>
-#include <linux/interrupt.h>
-#include <linux/platform_device.h>
-#include <linux/delay.h>
-#include <linux/clk.h>
-#include <linux/of.h>
-#include <linux/pm_runtime.h>
-#include "slimbus.h"
-
-/* Manager registers */
-#define MGR_CFG 0x200
-#define MGR_STATUS 0x204
-#define MGR_INT_EN 0x210
-#define MGR_INT_STAT 0x214
-#define MGR_INT_CLR 0x218
-#define MGR_TX_MSG 0x230
-#define MGR_RX_MSG 0x270
-#define MGR_IE_STAT 0x2F0
-#define MGR_VE_STAT 0x300
-#define MGR_CFG_ENABLE 1
-
-/* Framer registers */
-#define FRM_CFG 0x400
-#define FRM_STAT 0x404
-#define FRM_INT_EN 0x410
-#define FRM_INT_STAT 0x414
-#define FRM_INT_CLR 0x418
-#define FRM_WAKEUP 0x41C
-#define FRM_CLKCTL_DONE 0x420
-#define FRM_IE_STAT 0x430
-#define FRM_VE_STAT 0x440
-
-/* Interface registers */
-#define INTF_CFG 0x600
-#define INTF_STAT 0x604
-#define INTF_INT_EN 0x610
-#define INTF_INT_STAT 0x614
-#define INTF_INT_CLR 0x618
-#define INTF_IE_STAT 0x630
-#define INTF_VE_STAT 0x640
-
-/* Interrupt status bits */
-#define MGR_INT_TX_NACKED_2 BIT(25)
-#define MGR_INT_MSG_BUF_CONTE BIT(26)
-#define MGR_INT_RX_MSG_RCVD BIT(30)
-#define MGR_INT_TX_MSG_SENT BIT(31)
-
-/* Framer config register settings */
-#define FRM_ACTIVE 1
-#define CLK_GEAR 7
-#define ROOT_FREQ 11
-#define REF_CLK_GEAR 15
-#define INTR_WAKE 19
-
-#define SLIM_MSG_ASM_FIRST_WORD(l, mt, mc, dt, ad) \
- ((l) | ((mt) << 5) | ((mc) << 8) | ((dt) << 15) | ((ad) << 16))
-
-#define SLIM_ROOT_FREQ 24576000
-#define QCOM_SLIM_AUTOSUSPEND 1000
-
-/* MAX message size over control channel */
-#define SLIM_MSGQ_BUF_LEN 40
-#define QCOM_TX_MSGS 2
-#define QCOM_RX_MSGS 8
-#define QCOM_BUF_ALLOC_RETRIES 10
-
-#define CFG_PORT(r, v) ((v) ? CFG_PORT_V2(r) : CFG_PORT_V1(r))
-
-/* V2 Component registers */
-#define CFG_PORT_V2(r) ((r ## _V2))
-#define COMP_CFG_V2 4
-#define COMP_TRUST_CFG_V2 0x3000
-
-/* V1 Component registers */
-#define CFG_PORT_V1(r) ((r ## _V1))
-#define COMP_CFG_V1 0
-#define COMP_TRUST_CFG_V1 0x14
-
-/* Resource group info for manager, and non-ported generic device-components */
-#define EE_MGR_RSC_GRP (1 << 10)
-#define EE_NGD_2 (2 << 6)
-#define EE_NGD_1 0
-
-struct slim_ctrl_buf {
- void *base;
- spinlock_t lock;
- int head;
- int tail;
- int sl_sz;
- int n;
-};
-
-struct qcom_slim_ctrl {
- struct slim_controller ctrl;
- struct slim_framer framer;
- struct device *dev;
- void __iomem *base;
- void __iomem *slew_reg;
-
- struct slim_ctrl_buf rx;
- struct slim_ctrl_buf tx;
-
- struct completion **wr_comp;
- int irq;
- struct workqueue_struct *rxwq;
- struct work_struct wd;
- struct clk *rclk;
- struct clk *hclk;
-};
-
-static void qcom_slim_queue_tx(struct qcom_slim_ctrl *ctrl, void *buf,
- u8 len, u32 tx_reg)
-{
- int count = (len + 3) >> 2;
-
- __iowrite32_copy(ctrl->base + tx_reg, buf, count);
-
- /* Ensure Oder of subsequent writes */
- mb();
-}
-
-static void *slim_alloc_rxbuf(struct qcom_slim_ctrl *ctrl)
-{
- unsigned long flags;
- int idx;
-
- spin_lock_irqsave(&ctrl->rx.lock, flags);
- if ((ctrl->rx.tail + 1) % ctrl->rx.n == ctrl->rx.head) {
- spin_unlock_irqrestore(&ctrl->rx.lock, flags);
- dev_err(ctrl->dev, "RX QUEUE full!");
- return NULL;
- }
- idx = ctrl->rx.tail;
- ctrl->rx.tail = (ctrl->rx.tail + 1) % ctrl->rx.n;
- spin_unlock_irqrestore(&ctrl->rx.lock, flags);
-
- return ctrl->rx.base + (idx * ctrl->rx.sl_sz);
-}
-
-static void slim_ack_txn(struct qcom_slim_ctrl *ctrl, int err)
-{
- struct completion *comp;
- unsigned long flags;
- int idx;
-
- spin_lock_irqsave(&ctrl->tx.lock, flags);
- idx = ctrl->tx.head;
- ctrl->tx.head = (ctrl->tx.head + 1) % ctrl->tx.n;
- spin_unlock_irqrestore(&ctrl->tx.lock, flags);
-
- comp = ctrl->wr_comp[idx];
- ctrl->wr_comp[idx] = NULL;
-
- complete(comp);
-}
-
-static irqreturn_t qcom_slim_handle_tx_irq(struct qcom_slim_ctrl *ctrl,
- u32 stat)
-{
- int err = 0;
-
- if (stat & MGR_INT_TX_MSG_SENT)
- writel_relaxed(MGR_INT_TX_MSG_SENT,
- ctrl->base + MGR_INT_CLR);
-
- if (stat & MGR_INT_TX_NACKED_2) {
- u32 mgr_stat = readl_relaxed(ctrl->base + MGR_STATUS);
- u32 mgr_ie_stat = readl_relaxed(ctrl->base + MGR_IE_STAT);
- u32 frm_stat = readl_relaxed(ctrl->base + FRM_STAT);
- u32 frm_cfg = readl_relaxed(ctrl->base + FRM_CFG);
- u32 frm_intr_stat = readl_relaxed(ctrl->base + FRM_INT_STAT);
- u32 frm_ie_stat = readl_relaxed(ctrl->base + FRM_IE_STAT);
- u32 intf_stat = readl_relaxed(ctrl->base + INTF_STAT);
- u32 intf_intr_stat = readl_relaxed(ctrl->base + INTF_INT_STAT);
- u32 intf_ie_stat = readl_relaxed(ctrl->base + INTF_IE_STAT);
-
- writel_relaxed(MGR_INT_TX_NACKED_2, ctrl->base + MGR_INT_CLR);
-
- dev_err(ctrl->dev, "TX Nack MGR:int:0x%x, stat:0x%x\n",
- stat, mgr_stat);
- dev_err(ctrl->dev, "TX Nack MGR:ie:0x%x\n", mgr_ie_stat);
- dev_err(ctrl->dev, "TX Nack FRM:int:0x%x, stat:0x%x\n",
- frm_intr_stat, frm_stat);
- dev_err(ctrl->dev, "TX Nack FRM:cfg:0x%x, ie:0x%x\n",
- frm_cfg, frm_ie_stat);
- dev_err(ctrl->dev, "TX Nack INTF:intr:0x%x, stat:0x%x\n",
- intf_intr_stat, intf_stat);
- dev_err(ctrl->dev, "TX Nack INTF:ie:0x%x\n",
- intf_ie_stat);
- err = -ENOTCONN;
- }
-
- slim_ack_txn(ctrl, err);
-
- return IRQ_HANDLED;
-}
-
-static irqreturn_t qcom_slim_handle_rx_irq(struct qcom_slim_ctrl *ctrl,
- u32 stat)
-{
- u32 *rx_buf, pkt[10];
- bool q_rx = false;
- u8 mc, mt, len;
-
- pkt[0] = readl_relaxed(ctrl->base + MGR_RX_MSG);
- mt = SLIM_HEADER_GET_MT(pkt[0]);
- len = SLIM_HEADER_GET_RL(pkt[0]);
- mc = SLIM_HEADER_GET_MC(pkt[0]>>8);
-
- /*
- * this message cannot be handled by ISR, so
- * let work-queue handle it
- */
- if (mt == SLIM_MSG_MT_CORE && mc == SLIM_MSG_MC_REPORT_PRESENT) {
- rx_buf = (u32 *)slim_alloc_rxbuf(ctrl);
- if (!rx_buf) {
- dev_err(ctrl->dev, "dropping RX:0x%x due to RX full\n",
- pkt[0]);
- goto rx_ret_irq;
- }
- rx_buf[0] = pkt[0];
-
- } else {
- rx_buf = pkt;
- }
-
- __ioread32_copy(rx_buf + 1, ctrl->base + MGR_RX_MSG + 4,
- DIV_ROUND_UP(len, 4));
-
- switch (mc) {
-
- case SLIM_MSG_MC_REPORT_PRESENT:
- q_rx = true;
- break;
- case SLIM_MSG_MC_REPLY_INFORMATION:
- case SLIM_MSG_MC_REPLY_VALUE:
- slim_msg_response(&ctrl->ctrl, (u8 *)(rx_buf + 1),
- (u8)(*rx_buf >> 24), (len - 4));
- break;
- default:
- dev_err(ctrl->dev, "unsupported MC,%x MT:%x\n",
- mc, mt);
- break;
- }
-rx_ret_irq:
- writel(MGR_INT_RX_MSG_RCVD, ctrl->base +
- MGR_INT_CLR);
- if (q_rx)
- queue_work(ctrl->rxwq, &ctrl->wd);
-
- return IRQ_HANDLED;
-}
-
-static irqreturn_t qcom_slim_interrupt(int irq, void *d)
-{
- struct qcom_slim_ctrl *ctrl = d;
- u32 stat = readl_relaxed(ctrl->base + MGR_INT_STAT);
- int ret = IRQ_NONE;
-
- if (stat & MGR_INT_TX_MSG_SENT || stat & MGR_INT_TX_NACKED_2)
- ret = qcom_slim_handle_tx_irq(ctrl, stat);
-
- if (stat & MGR_INT_RX_MSG_RCVD)
- ret = qcom_slim_handle_rx_irq(ctrl, stat);
-
- return ret;
-}
-
-static int qcom_clk_pause_wakeup(struct slim_controller *sctrl)
-{
- struct qcom_slim_ctrl *ctrl = dev_get_drvdata(sctrl->dev);
-
- clk_prepare_enable(ctrl->hclk);
- clk_prepare_enable(ctrl->rclk);
- enable_irq(ctrl->irq);
-
- writel_relaxed(1, ctrl->base + FRM_WAKEUP);
- /* Make sure framer wakeup write goes through before ISR fires */
- mb();
- /*
- * HW Workaround: Currently, slave is reporting lost-sync messages
- * after SLIMbus comes out of clock pause.
- * Transaction with slave fail before slave reports that message
- * Give some time for that report to come
- * SLIMbus wakes up in clock gear 10 at 24.576MHz. With each superframe
- * being 250 usecs, we wait for 5-10 superframes here to ensure
- * we get the message
- */
- usleep_range(1250, 2500);
- return 0;
-}
-
-static void *slim_alloc_txbuf(struct qcom_slim_ctrl *ctrl,
- struct slim_msg_txn *txn,
- struct completion *done)
-{
- unsigned long flags;
- int idx;
-
- spin_lock_irqsave(&ctrl->tx.lock, flags);
- if (((ctrl->tx.head + 1) % ctrl->tx.n) == ctrl->tx.tail) {
- spin_unlock_irqrestore(&ctrl->tx.lock, flags);
- dev_err(ctrl->dev, "controller TX buf unavailable");
- return NULL;
- }
- idx = ctrl->tx.tail;
- ctrl->wr_comp[idx] = done;
- ctrl->tx.tail = (ctrl->tx.tail + 1) % ctrl->tx.n;
-
- spin_unlock_irqrestore(&ctrl->tx.lock, flags);
-
- return ctrl->tx.base + (idx * ctrl->tx.sl_sz);
-}
-
-
-static int qcom_xfer_msg(struct slim_controller *sctrl,
- struct slim_msg_txn *txn)
-{
- struct qcom_slim_ctrl *ctrl = dev_get_drvdata(sctrl->dev);
- DECLARE_COMPLETION_ONSTACK(done);
- void *pbuf = slim_alloc_txbuf(ctrl, txn, &done);
- unsigned long ms = txn->rl + HZ;
- u8 *puc;
- int ret = 0, retries = QCOM_BUF_ALLOC_RETRIES;
- unsigned long time_left;
- u8 la = txn->la;
- u32 *head;
- /* HW expects length field to be excluded */
- txn->rl--;
-
- /* spin till buffer is made available */
- if (!pbuf) {
- while (retries--) {
- usleep_range(10000, 15000);
- pbuf = slim_alloc_txbuf(ctrl, txn, &done);
- if (pbuf)
- break;
- }
- }
-
- if (retries < 0 && !pbuf)
- return -ENOMEM;
-
- puc = (u8 *)pbuf;
- head = (u32 *)pbuf;
-
- if (txn->dt == SLIM_MSG_DEST_LOGICALADDR) {
- *head = SLIM_MSG_ASM_FIRST_WORD(txn->rl, txn->mt,
- txn->mc, 0, la);
- puc += 3;
- } else {
- *head = SLIM_MSG_ASM_FIRST_WORD(txn->rl, txn->mt,
- txn->mc, 1, la);
- puc += 2;
- }
-
- if (slim_tid_txn(txn->mt, txn->mc))
- *(puc++) = txn->tid;
-
- if (slim_ec_txn(txn->mt, txn->mc)) {
- *(puc++) = (txn->ec & 0xFF);
- *(puc++) = (txn->ec >> 8) & 0xFF;
- }
-
- if (txn->msg && txn->msg->wbuf)
- memcpy(puc, txn->msg->wbuf, txn->msg->num_bytes);
-
- qcom_slim_queue_tx(ctrl, head, txn->rl, MGR_TX_MSG);
- time_left = wait_for_completion_timeout(&done, msecs_to_jiffies(ms));
-
- if (!time_left) {
- dev_err(ctrl->dev, "TX timed out:MC:0x%x,mt:0x%x", txn->mc,
- txn->mt);
- ret = -ETIMEDOUT;
- }
-
- return ret;
-
-}
-
-static int qcom_set_laddr(struct slim_controller *sctrl,
- struct slim_eaddr *ead, u8 laddr)
-{
- struct qcom_slim_ctrl *ctrl = dev_get_drvdata(sctrl->dev);
- struct {
- __be16 manf_id;
- __be16 prod_code;
- u8 dev_index;
- u8 instance;
- u8 laddr;
- } __packed p;
- struct slim_val_inf msg = {0};
- DEFINE_SLIM_EDEST_TXN(txn, SLIM_MSG_MC_ASSIGN_LOGICAL_ADDRESS,
- 10, laddr, &msg);
- int ret;
-
- p.manf_id = cpu_to_be16(ead->manf_id);
- p.prod_code = cpu_to_be16(ead->prod_code);
- p.dev_index = ead->dev_index;
- p.instance = ead->instance;
- p.laddr = laddr;
-
- msg.wbuf = (void *)&p;
- msg.num_bytes = 7;
- ret = slim_do_transfer(&ctrl->ctrl, &txn);
-
- if (ret)
- dev_err(ctrl->dev, "set LA:0x%x failed:ret:%d\n",
- laddr, ret);
- return ret;
-}
-
-static int slim_get_current_rxbuf(struct qcom_slim_ctrl *ctrl, void *buf)
-{
- unsigned long flags;
-
- spin_lock_irqsave(&ctrl->rx.lock, flags);
- if (ctrl->rx.tail == ctrl->rx.head) {
- spin_unlock_irqrestore(&ctrl->rx.lock, flags);
- return -ENODATA;
- }
- memcpy(buf, ctrl->rx.base + (ctrl->rx.head * ctrl->rx.sl_sz),
- ctrl->rx.sl_sz);
-
- ctrl->rx.head = (ctrl->rx.head + 1) % ctrl->rx.n;
- spin_unlock_irqrestore(&ctrl->rx.lock, flags);
-
- return 0;
-}
-
-static void qcom_slim_rxwq(struct work_struct *work)
-{
- u8 buf[SLIM_MSGQ_BUF_LEN];
- u8 mc, mt;
- int ret;
- struct qcom_slim_ctrl *ctrl = container_of(work, struct qcom_slim_ctrl,
- wd);
-
- while ((slim_get_current_rxbuf(ctrl, buf)) != -ENODATA) {
- mt = SLIM_HEADER_GET_MT(buf[0]);
- mc = SLIM_HEADER_GET_MC(buf[1]);
- if (mt == SLIM_MSG_MT_CORE &&
- mc == SLIM_MSG_MC_REPORT_PRESENT) {
- struct slim_eaddr ea;
- u8 laddr;
-
- ea.manf_id = be16_to_cpup((__be16 *)&buf[2]);
- ea.prod_code = be16_to_cpup((__be16 *)&buf[4]);
- ea.dev_index = buf[6];
- ea.instance = buf[7];
-
- ret = slim_device_report_present(&ctrl->ctrl, &ea,
- &laddr);
- if (ret < 0)
- dev_err(ctrl->dev, "assign laddr failed:%d\n",
- ret);
- } else {
- dev_err(ctrl->dev, "unexpected message:mc:%x, mt:%x\n",
- mc, mt);
- }
- }
-}
-
-static void qcom_slim_prg_slew(struct platform_device *pdev,
- struct qcom_slim_ctrl *ctrl)
-{
- if (!ctrl->slew_reg) {
- /* SLEW RATE register for this SLIMbus */
- ctrl->slew_reg = devm_platform_ioremap_resource_byname(pdev, "slew");
- if (IS_ERR(ctrl->slew_reg))
- return;
- }
-
- writel_relaxed(1, ctrl->slew_reg);
- /* Make sure SLIMbus-slew rate enabling goes through */
- wmb();
-}
-
-static int qcom_slim_probe(struct platform_device *pdev)
-{
- struct qcom_slim_ctrl *ctrl;
- struct slim_controller *sctrl;
- int ret, ver;
-
- ctrl = devm_kzalloc(&pdev->dev, sizeof(*ctrl), GFP_KERNEL);
- if (!ctrl)
- return -ENOMEM;
-
- ctrl->hclk = devm_clk_get(&pdev->dev, "iface");
- if (IS_ERR(ctrl->hclk))
- return PTR_ERR(ctrl->hclk);
-
- ctrl->rclk = devm_clk_get(&pdev->dev, "core");
- if (IS_ERR(ctrl->rclk))
- return PTR_ERR(ctrl->rclk);
-
- ret = clk_set_rate(ctrl->rclk, SLIM_ROOT_FREQ);
- if (ret) {
- dev_err(&pdev->dev, "ref-clock set-rate failed:%d\n", ret);
- return ret;
- }
-
- ctrl->irq = platform_get_irq(pdev, 0);
- if (ctrl->irq < 0)
- return ctrl->irq;
-
- sctrl = &ctrl->ctrl;
- sctrl->dev = &pdev->dev;
- ctrl->dev = &pdev->dev;
- platform_set_drvdata(pdev, ctrl);
- dev_set_drvdata(ctrl->dev, ctrl);
-
- ctrl->base = devm_platform_ioremap_resource_byname(pdev, "ctrl");
- if (IS_ERR(ctrl->base))
- return PTR_ERR(ctrl->base);
-
- sctrl->set_laddr = qcom_set_laddr;
- sctrl->xfer_msg = qcom_xfer_msg;
- sctrl->wakeup = qcom_clk_pause_wakeup;
- ctrl->tx.n = QCOM_TX_MSGS;
- ctrl->tx.sl_sz = SLIM_MSGQ_BUF_LEN;
- ctrl->rx.n = QCOM_RX_MSGS;
- ctrl->rx.sl_sz = SLIM_MSGQ_BUF_LEN;
- ctrl->wr_comp = kcalloc(QCOM_TX_MSGS, sizeof(struct completion *),
- GFP_KERNEL);
- if (!ctrl->wr_comp)
- return -ENOMEM;
-
- spin_lock_init(&ctrl->rx.lock);
- spin_lock_init(&ctrl->tx.lock);
- INIT_WORK(&ctrl->wd, qcom_slim_rxwq);
- ctrl->rxwq = create_singlethread_workqueue("qcom_slim_rx");
- if (!ctrl->rxwq) {
- dev_err(ctrl->dev, "Failed to start Rx WQ\n");
- return -ENOMEM;
- }
-
- ctrl->framer.rootfreq = SLIM_ROOT_FREQ / 8;
- ctrl->framer.superfreq =
- ctrl->framer.rootfreq / SLIM_CL_PER_SUPERFRAME_DIV8;
- sctrl->a_framer = &ctrl->framer;
- sctrl->clkgear = SLIM_MAX_CLK_GEAR;
-
- qcom_slim_prg_slew(pdev, ctrl);
-
- ret = devm_request_irq(&pdev->dev, ctrl->irq, qcom_slim_interrupt,
- IRQF_TRIGGER_HIGH, "qcom_slim_irq", ctrl);
- if (ret) {
- dev_err(&pdev->dev, "request IRQ failed\n");
- goto err_request_irq_failed;
- }
-
- ret = clk_prepare_enable(ctrl->hclk);
- if (ret)
- goto err_hclk_enable_failed;
-
- ret = clk_prepare_enable(ctrl->rclk);
- if (ret)
- goto err_rclk_enable_failed;
-
- ctrl->tx.base = devm_kcalloc(&pdev->dev, ctrl->tx.n, ctrl->tx.sl_sz,
- GFP_KERNEL);
- if (!ctrl->tx.base) {
- ret = -ENOMEM;
- goto err;
- }
-
- ctrl->rx.base = devm_kcalloc(&pdev->dev,ctrl->rx.n, ctrl->rx.sl_sz,
- GFP_KERNEL);
- if (!ctrl->rx.base) {
- ret = -ENOMEM;
- goto err;
- }
-
- /* Register with framework before enabling frame, clock */
- ret = slim_register_controller(&ctrl->ctrl);
- if (ret) {
- dev_err(ctrl->dev, "error adding controller\n");
- goto err;
- }
-
- ver = readl_relaxed(ctrl->base);
- /* Version info in 16 MSbits */
- ver >>= 16;
- /* Component register initialization */
- writel(1, ctrl->base + CFG_PORT(COMP_CFG, ver));
- writel((EE_MGR_RSC_GRP | EE_NGD_2 | EE_NGD_1),
- ctrl->base + CFG_PORT(COMP_TRUST_CFG, ver));
-
- writel((MGR_INT_TX_NACKED_2 |
- MGR_INT_MSG_BUF_CONTE | MGR_INT_RX_MSG_RCVD |
- MGR_INT_TX_MSG_SENT), ctrl->base + MGR_INT_EN);
- writel(1, ctrl->base + MGR_CFG);
- /* Framer register initialization */
- writel((1 << INTR_WAKE) | (0xA << REF_CLK_GEAR) |
- (0xA << CLK_GEAR) | (1 << ROOT_FREQ) | (1 << FRM_ACTIVE) | 1,
- ctrl->base + FRM_CFG);
- writel(MGR_CFG_ENABLE, ctrl->base + MGR_CFG);
- writel(1, ctrl->base + INTF_CFG);
- writel(1, ctrl->base + CFG_PORT(COMP_CFG, ver));
-
- pm_runtime_use_autosuspend(&pdev->dev);
- pm_runtime_set_autosuspend_delay(&pdev->dev, QCOM_SLIM_AUTOSUSPEND);
- pm_runtime_set_active(&pdev->dev);
- pm_runtime_mark_last_busy(&pdev->dev);
- pm_runtime_enable(&pdev->dev);
-
- dev_dbg(ctrl->dev, "QCOM SB controller is up:ver:0x%x!\n", ver);
- return 0;
-
-err:
- clk_disable_unprepare(ctrl->rclk);
-err_rclk_enable_failed:
- clk_disable_unprepare(ctrl->hclk);
-err_hclk_enable_failed:
-err_request_irq_failed:
- destroy_workqueue(ctrl->rxwq);
- return ret;
-}
-
-static void qcom_slim_remove(struct platform_device *pdev)
-{
- struct qcom_slim_ctrl *ctrl = platform_get_drvdata(pdev);
-
- pm_runtime_disable(&pdev->dev);
- slim_unregister_controller(&ctrl->ctrl);
- clk_disable_unprepare(ctrl->rclk);
- clk_disable_unprepare(ctrl->hclk);
- destroy_workqueue(ctrl->rxwq);
-}
-
-/*
- * If PM_RUNTIME is not defined, these 2 functions become helper
- * functions to be called from system suspend/resume.
- */
-#ifdef CONFIG_PM
-static int qcom_slim_runtime_suspend(struct device *device)
-{
- struct qcom_slim_ctrl *ctrl = dev_get_drvdata(device);
- int ret;
-
- dev_dbg(device, "pm_runtime: suspending...\n");
- ret = slim_ctrl_clk_pause(&ctrl->ctrl, false, SLIM_CLK_UNSPECIFIED);
- if (ret) {
- dev_err(device, "clk pause not entered:%d", ret);
- } else {
- disable_irq(ctrl->irq);
- clk_disable_unprepare(ctrl->hclk);
- clk_disable_unprepare(ctrl->rclk);
- }
- return ret;
-}
-
-static int qcom_slim_runtime_resume(struct device *device)
-{
- struct qcom_slim_ctrl *ctrl = dev_get_drvdata(device);
- int ret = 0;
-
- dev_dbg(device, "pm_runtime: resuming...\n");
- ret = slim_ctrl_clk_pause(&ctrl->ctrl, true, 0);
- if (ret)
- dev_err(device, "clk pause not exited:%d", ret);
- return ret;
-}
-#endif
-
-#ifdef CONFIG_PM_SLEEP
-static int qcom_slim_suspend(struct device *dev)
-{
- int ret = 0;
-
- if (!pm_runtime_enabled(dev) ||
- (!pm_runtime_suspended(dev))) {
- dev_dbg(dev, "system suspend");
- ret = qcom_slim_runtime_suspend(dev);
- }
-
- return ret;
-}
-
-static int qcom_slim_resume(struct device *dev)
-{
- if (!pm_runtime_enabled(dev) || !pm_runtime_suspended(dev)) {
- int ret;
-
- dev_dbg(dev, "system resume");
- ret = qcom_slim_runtime_resume(dev);
- if (!ret) {
- pm_runtime_mark_last_busy(dev);
- pm_request_autosuspend(dev);
- }
- return ret;
-
- }
- return 0;
-}
-#endif /* CONFIG_PM_SLEEP */
-
-static const struct dev_pm_ops qcom_slim_dev_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(qcom_slim_suspend, qcom_slim_resume)
- SET_RUNTIME_PM_OPS(
- qcom_slim_runtime_suspend,
- qcom_slim_runtime_resume,
- NULL
- )
-};
-
-static const struct of_device_id qcom_slim_dt_match[] = {
- { .compatible = "qcom,slim", },
- {}
-};
-MODULE_DEVICE_TABLE(of, qcom_slim_dt_match);
-
-static struct platform_driver qcom_slim_driver = {
- .probe = qcom_slim_probe,
- .remove = qcom_slim_remove,
- .driver = {
- .name = "qcom_slim_ctrl",
- .of_match_table = qcom_slim_dt_match,
- .pm = &qcom_slim_dev_pm_ops,
- },
-};
-module_platform_driver(qcom_slim_driver);
-
-MODULE_LICENSE("GPL v2");
-MODULE_DESCRIPTION("Qualcomm SLIMbus Controller");
diff --git a/drivers/staging/axis-fifo/axis-fifo.c b/drivers/staging/axis-fifo/axis-fifo.c
index 57ed58065eba..e8aa632e0a31 100644
--- a/drivers/staging/axis-fifo/axis-fifo.c
+++ b/drivers/staging/axis-fifo/axis-fifo.c
@@ -107,6 +107,8 @@
static long read_timeout = 1000; /* ms to wait before read() times out */
static long write_timeout = 1000; /* ms to wait before write() times out */
+static DEFINE_IDA(axis_fifo_ida);
+
/* ----------------------------
* module command-line arguments
* ----------------------------
@@ -123,6 +125,7 @@ MODULE_PARM_DESC(write_timeout, "ms to wait before blocking write() timing out;
*/
struct axis_fifo {
+ int id;
int irq; /* interrupt */
void __iomem *base_addr; /* kernel space memory */
@@ -693,17 +696,11 @@ static int axis_fifo_probe(struct platform_device *pdev)
/* get iospace for the device and request physical memory */
fifo->base_addr = devm_platform_get_and_ioremap_resource(pdev, 0, &r_mem);
- if (IS_ERR(fifo->base_addr)) {
- rc = PTR_ERR(fifo->base_addr);
- goto err_initial;
- }
+ if (IS_ERR(fifo->base_addr))
+ return PTR_ERR(fifo->base_addr);
dev_dbg(fifo->dt_device, "remapped memory to 0x%p\n", fifo->base_addr);
- /* create unique device name */
- snprintf(device_name, 32, "%s_%pa", DRIVER_NAME, &r_mem->start);
- dev_dbg(fifo->dt_device, "device name [%s]\n", device_name);
-
/* ----------------------------
* init IP
* ----------------------------
@@ -711,7 +708,7 @@ static int axis_fifo_probe(struct platform_device *pdev)
rc = axis_fifo_parse_dt(fifo);
if (rc)
- goto err_initial;
+ return rc;
reset_ip_core(fifo);
@@ -723,7 +720,7 @@ static int axis_fifo_probe(struct platform_device *pdev)
/* get IRQ resource */
rc = platform_get_irq(pdev, 0);
if (rc < 0)
- goto err_initial;
+ return rc;
/* request IRQ */
fifo->irq = rc;
@@ -732,13 +729,18 @@ static int axis_fifo_probe(struct platform_device *pdev)
if (rc) {
dev_err(fifo->dt_device, "couldn't allocate interrupt %i\n",
fifo->irq);
- goto err_initial;
+ return rc;
}
/* ----------------------------
* init char device
* ----------------------------
*/
+ fifo->id = ida_alloc(&axis_fifo_ida, GFP_KERNEL);
+ if (fifo->id < 0)
+ return fifo->id;
+
+ snprintf(device_name, 32, "%s%d", DRIVER_NAME, fifo->id);
/* create character device */
fifo->miscdev.fops = &fops;
@@ -746,16 +748,14 @@ static int axis_fifo_probe(struct platform_device *pdev)
fifo->miscdev.name = device_name;
fifo->miscdev.parent = dev;
rc = misc_register(&fifo->miscdev);
- if (rc < 0)
- goto err_initial;
+ if (rc < 0) {
+ ida_free(&axis_fifo_ida, fifo->id);
+ return rc;
+ }
axis_fifo_debugfs_init(fifo);
return 0;
-
-err_initial:
- dev_set_drvdata(dev, NULL);
- return rc;
}
static void axis_fifo_remove(struct platform_device *pdev)
@@ -765,7 +765,7 @@ static void axis_fifo_remove(struct platform_device *pdev)
debugfs_remove(fifo->debugfs_dir);
misc_deregister(&fifo->miscdev);
- dev_set_drvdata(dev, NULL);
+ ida_free(&axis_fifo_ida, fifo->id);
}
static const struct of_device_id axis_fifo_of_match[] = {
@@ -805,6 +805,7 @@ module_init(axis_fifo_init);
static void __exit axis_fifo_exit(void)
{
platform_driver_unregister(&axis_fifo_driver);
+ ida_destroy(&axis_fifo_ida);
}
module_exit(axis_fifo_exit);
diff --git a/drivers/staging/gpib/agilent_82357a/agilent_82357a.c b/drivers/staging/gpib/agilent_82357a/agilent_82357a.c
index b923dc606d1d..77c8e549b208 100644
--- a/drivers/staging/gpib/agilent_82357a/agilent_82357a.c
+++ b/drivers/staging/gpib/agilent_82357a/agilent_82357a.c
@@ -449,8 +449,8 @@ static int agilent_82357a_read(struct gpib_board *board, u8 *buffer, size_t leng
if (!out_data)
return -ENOMEM;
out_data[i++] = DATA_PIPE_CMD_READ;
- out_data[i++] = 0; //primary address when ARF_NO_ADDR is not set
- out_data[i++] = 0; //secondary address when ARF_NO_ADDR is not set
+ out_data[i++] = 0; // primary address when ARF_NO_ADDR is not set
+ out_data[i++] = 0; // secondary address when ARF_NO_ADDR is not set
out_data[i] = ARF_NO_ADDRESS | ARF_END_ON_EOI;
if (a_priv->eos_mode & REOS)
out_data[i] |= ARF_END_ON_EOS_CHAR;
@@ -532,7 +532,7 @@ static int agilent_82357a_read(struct gpib_board *board, u8 *buffer, size_t leng
*/
agilent_82357a_take_control_internal(board, 0);
- //FIXME check trailing flags for error
+ // FIXME check trailing flags for error
return retval;
}
@@ -966,7 +966,7 @@ static int agilent_82357a_parallel_poll(struct gpib_board *board, u8 *result)
dev_err(&usb_dev->dev, "write_registers() returned error\n");
return retval;
}
- udelay(2); //silly, since usb write will take way longer
+ udelay(2); // silly, since usb write will take way longer
read.address = CPTR;
retval = agilent_82357a_read_registers(a_priv, &read, 1, 1);
if (retval) {
@@ -989,31 +989,31 @@ static int agilent_82357a_parallel_poll(struct gpib_board *board, u8 *result)
static void agilent_82357a_parallel_poll_configure(struct gpib_board *board, u8 config)
{
- //board can only be system controller
+ // board can only be system controller
return;// 0;
}
static void agilent_82357a_parallel_poll_response(struct gpib_board *board, int ist)
{
- //board can only be system controller
+ // board can only be system controller
return;// 0;
}
static void agilent_82357a_serial_poll_response(struct gpib_board *board, u8 status)
{
- //board can only be system controller
+ // board can only be system controller
return;// 0;
}
static u8 agilent_82357a_serial_poll_status(struct gpib_board *board)
{
- //board can only be system controller
+ // board can only be system controller
return 0;
}
static void agilent_82357a_return_to_local(struct gpib_board *board)
{
- //board can only be system controller
+ // board can only be system controller
return;// 0;
}
diff --git a/drivers/staging/gpib/agilent_82357a/agilent_82357a.h b/drivers/staging/gpib/agilent_82357a/agilent_82357a.h
index 23aa4799eb86..33ac558e5552 100644
--- a/drivers/staging/gpib/agilent_82357a/agilent_82357a.h
+++ b/drivers/staging/gpib/agilent_82357a/agilent_82357a.h
@@ -20,7 +20,7 @@ enum usb_vendor_ids {
enum usb_device_ids {
USB_DEVICE_ID_AGILENT_82357A = 0x0107,
USB_DEVICE_ID_AGILENT_82357A_PREINIT = 0x0007, // device id before firmware is loaded
- USB_DEVICE_ID_AGILENT_82357B = 0x0718, // device id before firmware is loaded
+ USB_DEVICE_ID_AGILENT_82357B = 0x0718, // device id before firmware is loaded
USB_DEVICE_ID_AGILENT_82357B_PREINIT = 0x0518, // device id before firmware is loaded
};
@@ -129,10 +129,10 @@ struct agilent_82357a_priv {
struct urb *bulk_urb;
struct urb *interrupt_urb;
u8 *interrupt_buffer;
- struct mutex bulk_transfer_lock; // bulk transfer lock
- struct mutex bulk_alloc_lock; // bulk transfer allocation lock
- struct mutex interrupt_alloc_lock; // interrupt allocation lock
- struct mutex control_alloc_lock; // control message allocation lock
+ struct mutex bulk_transfer_lock; // bulk transfer lock
+ struct mutex bulk_alloc_lock; // bulk transfer allocation lock
+ struct mutex interrupt_alloc_lock; // interrupt allocation lock
+ struct mutex control_alloc_lock; // control message allocation lock
struct timer_list bulk_timer;
struct agilent_82357a_urb_ctx context;
unsigned int bulk_out_endpoint;
diff --git a/drivers/staging/gpib/cb7210/cb7210.h b/drivers/staging/gpib/cb7210/cb7210.h
index 13f127563ab3..ddc841ff87ae 100644
--- a/drivers/staging/gpib/cb7210/cb7210.h
+++ b/drivers/staging/gpib/cb7210/cb7210.h
@@ -56,10 +56,10 @@ enum cb7210_page_in {
};
enum hs_regs {
- //write registers
+ // write registers
HS_MODE = 0x8, /* HS_MODE register */
HS_INT_LEVEL = 0x9, /* HS_INT_LEVEL register */
- //read registers
+ // read registers
HS_STATUS = 0x8, /* HS_STATUS register */
};
diff --git a/drivers/staging/gpib/cec/cec_gpib.c b/drivers/staging/gpib/cec/cec_gpib.c
index 0c9d10ee7cd2..dbf9b95baabc 100644
--- a/drivers/staging/gpib/cec/cec_gpib.c
+++ b/drivers/staging/gpib/cec/cec_gpib.c
@@ -206,7 +206,7 @@ static struct gpib_interface cec_pci_interface = {
.parallel_poll_configure = cec_parallel_poll_configure,
.parallel_poll_response = cec_parallel_poll_response,
.local_parallel_poll_mode = NULL, // XXX
- .line_status = NULL, //XXX
+ .line_status = NULL, // XXX
.update_status = cec_update_status,
.primary_address = cec_primary_address,
.secondary_address = cec_secondary_address,
diff --git a/drivers/staging/gpib/common/gpib_os.c b/drivers/staging/gpib/common/gpib_os.c
index 2a0465ce16c4..9dbbac8b8436 100644
--- a/drivers/staging/gpib/common/gpib_os.c
+++ b/drivers/staging/gpib/common/gpib_os.c
@@ -326,7 +326,7 @@ static int setup_serial_poll(struct gpib_board *board, unsigned int usec_timeout
cmd_string[i++] = MLA(board->pad); /* controller's listen address */
if (board->sad >= 0)
cmd_string[i++] = MSA(board->sad);
- cmd_string[i++] = SPE; //serial poll enable
+ cmd_string[i++] = SPE; // serial poll enable
ret = board->interface->command(board, cmd_string, i, &bytes_written);
if (ret < 0 || bytes_written < i) {
diff --git a/drivers/staging/gpib/common/iblib.c b/drivers/staging/gpib/common/iblib.c
index 549280d9a6e9..7cbb6a467177 100644
--- a/drivers/staging/gpib/common/iblib.c
+++ b/drivers/staging/gpib/common/iblib.c
@@ -608,7 +608,7 @@ static int wait_satisfied(struct wait_info *winfo, struct gpib_status_queue *sta
*status = temp_status;
return 1;
}
-//XXX does wait for END work?
+// XXX does wait for END work?
return 0;
}
diff --git a/drivers/staging/gpib/eastwood/fluke_gpib.c b/drivers/staging/gpib/eastwood/fluke_gpib.c
index 491356433249..3ae848e3f738 100644
--- a/drivers/staging/gpib/eastwood/fluke_gpib.c
+++ b/drivers/staging/gpib/eastwood/fluke_gpib.c
@@ -507,7 +507,7 @@ static int fluke_accel_write(struct gpib_board *board, u8 *buffer, size_t length
}
if (retval < 0)
return retval;
- //handle sending of last byte with eoi
+ // handle sending of last byte with eoi
if (send_eoi) {
size_t num_bytes;
diff --git a/drivers/staging/gpib/fmh_gpib/fmh_gpib.c b/drivers/staging/gpib/fmh_gpib/fmh_gpib.c
index 4138f3d2bae7..164dcfc3c9ef 100644
--- a/drivers/staging/gpib/fmh_gpib/fmh_gpib.c
+++ b/drivers/staging/gpib/fmh_gpib/fmh_gpib.c
@@ -523,7 +523,7 @@ static int fmh_gpib_accel_write(struct gpib_board *board, u8 *buffer,
}
if (retval < 0)
return retval;
- //handle sending of last byte with eoi
+ // handle sending of last byte with eoi
if (send_eoi) {
size_t num_bytes;
diff --git a/drivers/staging/gpib/gpio/gpib_bitbang.c b/drivers/staging/gpib/gpio/gpib_bitbang.c
index 17884810fd69..374cd61355e9 100644
--- a/drivers/staging/gpib/gpio/gpib_bitbang.c
+++ b/drivers/staging/gpib/gpio/gpib_bitbang.c
@@ -277,8 +277,8 @@ struct bb_priv {
int ndac_mode; /* nrfd interrupt mode 0/1 -> edge/levels */
int dav_tx; /* keep trace of DAV status while sending */
int dav_rx; /* keep trace of DAV status while receiving */
- u8 eos; // eos character
- short eos_flags; // eos mode
+ u8 eos; /* eos character */
+ short eos_flags; /* eos mode */
short eos_check; /* eos check required in current operation ... */
short eos_check_8; /* ... with byte comparison */
short eos_mask_7; /* ... with 7 bit masked character */
@@ -290,14 +290,14 @@ struct bb_priv {
u8 *rbuf;
u8 *wbuf;
int end_flag;
- int r_busy; /* 0==idle 1==busy */
+ int r_busy; /* 0==idle 1==busy */
int w_busy;
int write_done;
- int cmd; /* 1 = cmd write in progress */
+ int cmd; /* 1 = cmd write in progress */
size_t w_cnt;
size_t length;
u8 *w_buf;
- spinlock_t rw_lock; // protect mods to rw_lock
+ spinlock_t rw_lock; /* protect mods to rw_lock */
int phase;
int ndac_idle;
int ndac_seq;
@@ -726,7 +726,7 @@ static irqreturn_t bb_SRQ_interrupt(int irq, void *arg)
static int bb_command(struct gpib_board *board, u8 *buffer,
size_t length, size_t *bytes_written)
{
- size_t ret;
+ int ret;
struct bb_priv *priv = board->private_data;
int i;
@@ -1462,8 +1462,8 @@ static inline void SET_DIR_READ(struct bb_priv *priv)
gpiod_set_value(TE, 0); /* set NDAC and NRFD to transmit and DAV to receive */
}
- gpiod_direction_output(NRFD, 0); // hold off the talker
- gpiod_direction_output(NDAC, 0); // data not accepted
+ gpiod_direction_output(NRFD, 0); /* hold off the talker */
+ gpiod_direction_output(NDAC, 0); /* data not accepted */
priv->direction = DIR_READ;
}
diff --git a/drivers/staging/gpib/hp_82341/hp_82341.c b/drivers/staging/gpib/hp_82341/hp_82341.c
index e5c1997ce7d9..1a2ad0560e14 100644
--- a/drivers/staging/gpib/hp_82341/hp_82341.c
+++ b/drivers/staging/gpib/hp_82341/hp_82341.c
@@ -38,7 +38,7 @@ static int hp_82341_accel_read(struct gpib_board *board, u8 *buffer, size_t leng
unsigned short event_status;
int i;
int num_fifo_bytes;
- //hardware doesn't support checking for end-of-string character when using fifo
+ // hardware doesn't support checking for end-of-string character when using fifo
if (tms_priv->eos_flags & REOS)
return tms9914_read(board, tms_priv, buffer, length, end, bytes_read);
@@ -49,7 +49,7 @@ static int hp_82341_accel_read(struct gpib_board *board, u8 *buffer, size_t leng
*bytes_read = 0;
if (length == 0)
return 0;
- //disable fifo for the moment
+ // disable fifo for the moment
outb(DIRECTION_GPIB_TO_HOST_BIT, hp_priv->iobase[3] + BUFFER_CONTROL_REG);
/*
* Handle corner case of board not in holdoff and one byte has slipped in already.
@@ -154,7 +154,7 @@ static int restart_write_fifo(struct gpib_board *board, struct hp_82341_priv *hp
while (1) {
int status;
- //restart doesn't work if data holdoff is in effect
+ // restart doesn't work if data holdoff is in effect
status = tms9914_line_status(board, tms_priv);
if ((status & BUS_NRFD) == 0) {
outb(RESTART_STREAM_BIT, hp_priv->iobase[0] + STREAM_STATUS_REG);
@@ -764,7 +764,7 @@ static int hp_82341_attach(struct gpib_board *board, const struct gpib_board_con
ENABLE_TI_INTERRUPT_EVENT_BIT, hp_priv->iobase[0] + EVENT_ENABLE_REG);
outb(ENABLE_BUFFER_END_INTERRUPT_BIT | ENABLE_TERMINAL_COUNT_INTERRUPT_BIT |
ENABLE_TI_INTERRUPT_BIT, hp_priv->iobase[0] + INTERRUPT_ENABLE_REG);
- //write clear event register
+ // write clear event register
outb((TI_INTERRUPT_EVENT_BIT | POINTERS_EQUAL_EVENT_BIT |
BUFFER_END_EVENT_BIT | TERMINAL_COUNT_EVENT_BIT),
hp_priv->iobase[0] + EVENT_STATUS_REG);
@@ -867,7 +867,7 @@ static irqreturn_t hp_82341_interrupt(int irq, void *arg)
event_status = inb(hp_priv->iobase[0] + EVENT_STATUS_REG);
if (event_status & INTERRUPT_PENDING_EVENT_BIT)
retval = IRQ_HANDLED;
- //write-clear status bits
+ // write-clear status bits
if (event_status & (TI_INTERRUPT_EVENT_BIT | POINTERS_EQUAL_EVENT_BIT |
BUFFER_END_EVENT_BIT | TERMINAL_COUNT_EVENT_BIT)) {
outb(event_status & (TI_INTERRUPT_EVENT_BIT | POINTERS_EQUAL_EVENT_BIT |
@@ -901,7 +901,7 @@ static void set_transfer_counter(struct hp_82341_priv *hp_priv, int count)
outb(complement & 0xff, hp_priv->iobase[1] + TRANSFER_COUNT_LOW_REG);
outb((complement >> 8) & 0xff, hp_priv->iobase[1] + TRANSFER_COUNT_MID_REG);
- //I don't think the hi count reg is even used, but oh well
+ // I don't think the hi count reg is even used, but oh well
outb((complement >> 16) & 0xf, hp_priv->iobase[1] + TRANSFER_COUNT_HIGH_REG);
}
diff --git a/drivers/staging/gpib/hp_82341/hp_82341.h b/drivers/staging/gpib/hp_82341/hp_82341.h
index 370a3d4576eb..859ef2899acb 100644
--- a/drivers/staging/gpib/hp_82341/hp_82341.h
+++ b/drivers/staging/gpib/hp_82341/hp_82341.h
@@ -65,7 +65,7 @@ enum config_control_status_bits {
IRQ_SELECT_MASK = 0x7,
DMA_CONFIG_MASK = 0x18,
ENABLE_DMA_CONFIG_BIT = 0x20,
- XILINX_READY_BIT = 0x40, //read only
+ XILINX_READY_BIT = 0x40, // read only
DONE_PGL_BIT = 0x80
};
@@ -94,7 +94,7 @@ static inline unsigned int IRQ_SELECT_BITS(int irq)
};
enum mode_control_status_bits {
- SLOT8_BIT = 0x1, // read only
+ SLOT8_BIT = 0x1, // read only
ACTIVE_CONTROLLER_BIT = 0x2, // read only
ENABLE_DMA_BIT = 0x4,
SYSTEM_CONTROLLER_BIT = 0x8,
@@ -106,12 +106,12 @@ enum mode_control_status_bits {
enum monitor_bits {
MONITOR_INTERRUPT_PENDING_BIT = 0x1, // read only
MONITOR_CLEAR_HOLDOFF_BIT = 0x2, // write only
- MONITOR_PPOLL_BIT = 0x4, // write clear
- MONITOR_SRQ_BIT = 0x8, // write clear
- MONITOR_IFC_BIT = 0x10, // write clear
- MONITOR_REN_BIT = 0x20, // write clear
- MONITOR_END_BIT = 0x40, // write clear
- MONITOR_DAV_BIT = 0x80 // write clear
+ MONITOR_PPOLL_BIT = 0x4, // write clear
+ MONITOR_SRQ_BIT = 0x8, // write clear
+ MONITOR_IFC_BIT = 0x10, // write clear
+ MONITOR_REN_BIT = 0x20, // write clear
+ MONITOR_END_BIT = 0x40, // write clear
+ MONITOR_DAV_BIT = 0x80 // write clear
};
enum interrupt_enable_bits {
@@ -123,36 +123,36 @@ enum interrupt_enable_bits {
};
enum event_status_bits {
- TI_INTERRUPT_EVENT_BIT = 0x1, //write clear
+ TI_INTERRUPT_EVENT_BIT = 0x1, // write clear
INTERRUPT_PENDING_EVENT_BIT = 0x2, // read only
- POINTERS_EQUAL_EVENT_BIT = 0x4, //write clear
- BUFFER_END_EVENT_BIT = 0x10, //write clear
+ POINTERS_EQUAL_EVENT_BIT = 0x4, // write clear
+ BUFFER_END_EVENT_BIT = 0x10, // write clear
TERMINAL_COUNT_EVENT_BIT = 0x20, // write clear
DMA_TERMINAL_COUNT_EVENT_BIT = 0x80, // write clear
};
enum event_enable_bits {
- ENABLE_TI_INTERRUPT_EVENT_BIT = 0x1, //write clear
- ENABLE_POINTERS_EQUAL_EVENT_BIT = 0x4, //write clear
- ENABLE_BUFFER_END_EVENT_BIT = 0x10, //write clear
- ENABLE_TERMINAL_COUNT_EVENT_BIT = 0x20, // write clear
+ ENABLE_TI_INTERRUPT_EVENT_BIT = 0x1, // write clear
+ ENABLE_POINTERS_EQUAL_EVENT_BIT = 0x4, // write clear
+ ENABLE_BUFFER_END_EVENT_BIT = 0x10, // write clear
+ ENABLE_TERMINAL_COUNT_EVENT_BIT = 0x20, // write clear
ENABLE_DMA_TERMINAL_COUNT_EVENT_BIT = 0x80, // write clear
};
enum stream_status_bits {
- HALTED_STATUS_BIT = 0x1, //read
- RESTART_STREAM_BIT = 0x1 //write
+ HALTED_STATUS_BIT = 0x1, // read
+ RESTART_STREAM_BIT = 0x1 // write
};
enum buffer_control_bits {
DIRECTION_GPIB_TO_HOST_BIT = 0x20, // transfer direction (set for gpib to host)
- ENABLE_TI_BUFFER_BIT = 0x40, //enable fifo
- FAST_WR_EN_BIT = 0x80, // 350 ns t1 delay?
+ ENABLE_TI_BUFFER_BIT = 0x40, // enable fifo
+ FAST_WR_EN_BIT = 0x80, // 350 ns t1 delay?
};
// registers accessible through isapnp chip on 82341d
enum hp_82341d_pnp_registers {
- PIO_DATA_REG = 0x20, //read/write pio data lines
+ PIO_DATA_REG = 0x20, // read/write pio data lines
PIO_DIRECTION_REG = 0x21, // set pio data line directions (set for input)
};
diff --git a/drivers/staging/gpib/include/amccs5933.h b/drivers/staging/gpib/include/amccs5933.h
index 4de0f6797458..d7f63c795096 100644
--- a/drivers/staging/gpib/include/amccs5933.h
+++ b/drivers/staging/gpib/include/amccs5933.h
@@ -24,7 +24,7 @@ extern inline int INCOMING_MAILBOX_REG(unsigned int mailbox)
enum {
OUTBOX_EMPTY_INTR_BIT = 0x10, // enable outbox empty interrupt
INBOX_FULL_INTR_BIT = 0x1000, // enable inbox full interrupt
- INBOX_INTR_CS_BIT = 0x20000, // read, or write clear inbox full interrupt
+ INBOX_INTR_CS_BIT = 0x20000, // read, or write clear inbox full interrupt
INTR_ASSERTED_BIT = 0x800000, // read only, interrupt asserted
};
@@ -52,7 +52,7 @@ extern inline int OUTBOX_SELECT_BITS(unsigned int mailbox)
return (mailbox & 0x3) << 2;
};
-//BMCSR bits
+// BMCSR bits
enum {
MBOX_FLAGS_RESET_BIT = 0x08000000, // resets mailbox empty/full flags
};
diff --git a/drivers/staging/gpib/include/gpib_types.h b/drivers/staging/gpib/include/gpib_types.h
index db040c80d778..998abb379749 100644
--- a/drivers/staging/gpib/include/gpib_types.h
+++ b/drivers/staging/gpib/include/gpib_types.h
@@ -273,7 +273,8 @@ struct gpib_board {
struct mutex big_gpib_mutex;
/* pid of last process to lock the board mutex */
pid_t locking_pid;
- spinlock_t locking_pid_spinlock; // lock for setting locking pid
+ /* lock for setting locking pid */
+ spinlock_t locking_pid_spinlock;
/* Spin lock for dealing with races with the interrupt handler */
spinlock_t spinlock;
/* Watchdog timer to enable timeouts */
diff --git a/drivers/staging/gpib/include/nec7210.h b/drivers/staging/gpib/include/nec7210.h
index 312217b4580e..9835aa5ef4ff 100644
--- a/drivers/staging/gpib/include/nec7210.h
+++ b/drivers/staging/gpib/include/nec7210.h
@@ -22,18 +22,18 @@ struct nec7210_priv {
u32 iobase;
#endif
void __iomem *mmiobase;
- unsigned int offset; // offset between successive nec7210 io addresses
+ unsigned int offset; // offset between successive nec7210 io addresses
unsigned int dma_channel;
u8 *dma_buffer;
unsigned int dma_buffer_length; // length of dma buffer
dma_addr_t dma_buffer_addr; // bus address of board->buffer for use with dma
// software copy of bits written to registers
u8 reg_bits[8];
- u8 auxa_bits; // bits written to auxiliary register A
- u8 auxb_bits; // bits written to auxiliary register B
+ u8 auxa_bits; // bits written to auxiliary register A
+ u8 auxb_bits; // bits written to auxiliary register B
// used to keep track of board's state, bit definitions given below
unsigned long state;
- /* lock for chips that extend the nec7210 registers by paging in alternate regs */
+ // lock for chips that extend the nec7210 registers by paging in alternate regs
spinlock_t register_page_lock;
// wrappers for outb, inb, readb, or writeb
u8 (*read_byte)(struct nec7210_priv *priv, unsigned int register_number);
@@ -64,17 +64,17 @@ static inline void write_byte(struct nec7210_priv *priv, u8 byte, unsigned int r
// struct nec7210_priv.state bit numbers
enum {
- PIO_IN_PROGRESS_BN, // pio transfer in progress
+ PIO_IN_PROGRESS_BN, // pio transfer in progress
DMA_READ_IN_PROGRESS_BN, // dma read transfer in progress
DMA_WRITE_IN_PROGRESS_BN, // dma write transfer in progress
- READ_READY_BN, // board has data byte available to read
- WRITE_READY_BN, // board is ready to send a data byte
- COMMAND_READY_BN, // board is ready to send a command byte
- RECEIVED_END_BN, // received END
- BUS_ERROR_BN, // output error has occurred
- RFD_HOLDOFF_BN, // rfd holdoff in effect
- DEV_CLEAR_BN, // device clear received
- ADR_CHANGE_BN, // address state change occurred
+ READ_READY_BN, // board has data byte available to read
+ WRITE_READY_BN, // board is ready to send a data byte
+ COMMAND_READY_BN, // board is ready to send a command byte
+ RECEIVED_END_BN, // received END
+ BUS_ERROR_BN, // output error has occurred
+ RFD_HOLDOFF_BN, // rfd holdoff in effect
+ DEV_CLEAR_BN, // device clear received
+ ADR_CHANGE_BN, // address state change occurred
};
// interface functions
diff --git a/drivers/staging/gpib/include/nec7210_registers.h b/drivers/staging/gpib/include/nec7210_registers.h
index 97c53ac8e805..067983d7a07f 100644
--- a/drivers/staging/gpib/include/nec7210_registers.h
+++ b/drivers/staging/gpib/include/nec7210_registers.h
@@ -11,7 +11,7 @@ enum nec7210_chipset {
NEC7210, // The original
TNT4882, // NI
NAT4882, // NI
- CB7210, // measurement computing
+ CB7210, // measurement computing
IOT7210, // iotech
IGPIB7210, // Ines
TNT5004, // NI (minor differences to TNT4882)
@@ -48,7 +48,7 @@ enum nec7210_read_regs {
ADR1, // address 2
};
-//bit definitions common to nec-7210 compatible registers
+// bit definitions common to nec-7210 compatible registers
// ISR1: interrupt status register 1
enum isr1_bits {
diff --git a/drivers/staging/gpib/include/plx9050.h b/drivers/staging/gpib/include/plx9050.h
index 66c56335f5c0..c911b285a0ca 100644
--- a/drivers/staging/gpib/include/plx9050.h
+++ b/drivers/staging/gpib/include/plx9050.h
@@ -23,10 +23,10 @@ enum plx9050_intcsr_bits {
PLX9050_LINTR2_STATUS_BIT = 0x20,
PLX9050_PCI_INTR_EN_BIT = 0x40,
PLX9050_SOFT_INTR_BIT = 0x80,
- PLX9050_LINTR1_SELECT_ENABLE_BIT = 0x100, //9052 extension
- PLX9050_LINTR2_SELECT_ENABLE_BIT = 0x200, //9052 extension
- PLX9050_LINTR1_EDGE_CLEAR_BIT = 0x400, //9052 extension
- PLX9050_LINTR2_EDGE_CLEAR_BIT = 0x800, //9052 extension
+ PLX9050_LINTR1_SELECT_ENABLE_BIT = 0x100, // 9052 extension
+ PLX9050_LINTR2_SELECT_ENABLE_BIT = 0x200, // 9052 extension
+ PLX9050_LINTR1_EDGE_CLEAR_BIT = 0x400, // 9052 extension
+ PLX9050_LINTR2_EDGE_CLEAR_BIT = 0x800, // 9052 extension
};
enum plx9050_cntrl_bits {
diff --git a/drivers/staging/gpib/include/tms9914.h b/drivers/staging/gpib/include/tms9914.h
index 50a9d3b22619..e66b75e0fda8 100644
--- a/drivers/staging/gpib/include/tms9914.h
+++ b/drivers/staging/gpib/include/tms9914.h
@@ -30,10 +30,10 @@ struct tms9914_priv {
u8 imr0_bits, imr1_bits;
// bits written to address mode register
u8 admr_bits;
- u8 auxa_bits; // bits written to auxiliary register A
+ u8 auxa_bits; // bits written to auxiliary register A
// used to keep track of board's state, bit definitions given below
unsigned long state;
- u8 eos; // eos character
+ u8 eos; // eos character
short eos_flags;
u8 spoll_status;
enum tms9914_holdoff_mode holdoff_mode;
@@ -67,15 +67,15 @@ static inline void write_byte(struct tms9914_priv *priv, u8 byte, unsigned int r
// struct tms9914_priv.state bit numbers
enum {
- PIO_IN_PROGRESS_BN, // pio transfer in progress
+ PIO_IN_PROGRESS_BN, // pio transfer in progress
DMA_READ_IN_PROGRESS_BN, // dma read transfer in progress
DMA_WRITE_IN_PROGRESS_BN, // dma write transfer in progress
- READ_READY_BN, // board has data byte available to read
- WRITE_READY_BN, // board is ready to send a data byte
- COMMAND_READY_BN, // board is ready to send a command byte
- RECEIVED_END_BN, // received END
- BUS_ERROR_BN, // bus error
- DEV_CLEAR_BN, // device clear received
+ READ_READY_BN, // board has data byte available to read
+ WRITE_READY_BN, // board is ready to send a data byte
+ COMMAND_READY_BN, // board is ready to send a command byte
+ RECEIVED_END_BN, // received END
+ BUS_ERROR_BN, // bus error
+ DEV_CLEAR_BN, // device clear received
};
// interface functions
@@ -150,23 +150,23 @@ enum {
IMR0 = 0, /* interrupt mask 0 */
IMR1 = 1, /* interrupt mask 1 */
AUXCR = 3, /* auxiliary command */
- ADR = 4, // address register
- SPMR = 5, // serial poll mode register
+ ADR = 4, /* address register */
+ SPMR = 5, /* serial poll mode register */
PPR = 6, /* parallel poll */
CDOR = 7, /* data out register */
};
// read registers
enum {
- ISR0 = 0, /* interrupt status 0 */
- ISR1 = 1, /* interrupt status 1 */
- ADSR = 2, /* address status */
- BSR = 3, /* bus status */
- CPTR = 6, /* command pass thru */
- DIR = 7, /* data in register */
+ ISR0 = 0, /* interrupt status 0 */
+ ISR1 = 1, /* interrupt status 1 */
+ ADSR = 2, /* address status */
+ BSR = 3, /* bus status */
+ CPTR = 6, /* command pass thru */
+ DIR = 7, /* data in register */
};
-//bit definitions common to tms9914 compatible registers
+// bit definitions common to tms9914 compatible registers
/* ISR0 - Register bits */
enum isr0_bits {
@@ -248,33 +248,33 @@ enum bus_status_bits {
/*---------------------------------------------------------*/
enum aux_cmd_bits {
- AUX_CS = 0x80, /* set bit instead of clearing it, used with commands marked 'd' below */
- AUX_CHIP_RESET = 0x0, /* d Chip reset */
- AUX_INVAL = 0x1, // release dac holdoff, invalid command byte
- AUX_VAL = (AUX_INVAL | AUX_CS), // release dac holdoff, valid command byte
- AUX_RHDF = 0x2, /* X Release RFD holdoff */
- AUX_HLDA = 0x3, /* d holdoff on all data */
- AUX_HLDE = 0x4, /* d holdoff on EOI only */
- AUX_NBAF = 0x5, /* X Set new byte available false */
- AUX_FGET = 0x6, /* d force GET */
- AUX_RTL = 0x7, /* d return to local */
- AUX_SEOI = 0x8, /* X send EOI with next byte */
- AUX_LON = 0x9, /* d Listen only */
- AUX_TON = 0xa, /* d Talk only */
- AUX_GTS = 0xb, /* X goto standby */
- AUX_TCA = 0xc, /* X take control asynchronously */
- AUX_TCS = 0xd, /* X take " synchronously */
- AUX_RPP = 0xe, /* d Request parallel poll */
- AUX_SIC = 0xf, /* d send interface clear */
- AUX_SRE = 0x10, /* d send remote enable */
- AUX_RQC = 0x11, /* X request control */
- AUX_RLC = 0x12, /* X release control */
- AUX_DAI = 0x13, /* d disable all interrupts */
- AUX_PTS = 0x14, /* X pass through next secondary */
- AUX_STDL = 0x15, /* d short T1 delay */
- AUX_SHDW = 0x16, /* d shadow handshake */
- AUX_VSTDL = 0x17, /* d very short T1 delay (smj9914 extension) */
- AUX_RSV2 = 0x18, /* d request service bit 2 (smj9914 extension) */
+ AUX_CS = 0x80, /* set bit instead of clearing it, used with commands marked 'd' below */
+ AUX_CHIP_RESET = 0x0, /* d Chip reset */
+ AUX_INVAL = 0x1, /* release dac holdoff, invalid command byte */
+ AUX_VAL = (AUX_INVAL | AUX_CS), /* release dac holdoff, valid command byte */
+ AUX_RHDF = 0x2, /* X Release RFD holdoff */
+ AUX_HLDA = 0x3, /* d holdoff on all data */
+ AUX_HLDE = 0x4, /* d holdoff on EOI only */
+ AUX_NBAF = 0x5, /* X Set new byte available false */
+ AUX_FGET = 0x6, /* d force GET */
+ AUX_RTL = 0x7, /* d return to local */
+ AUX_SEOI = 0x8, /* X send EOI with next byte */
+ AUX_LON = 0x9, /* d Listen only */
+ AUX_TON = 0xa, /* d Talk only */
+ AUX_GTS = 0xb, /* X goto standby */
+ AUX_TCA = 0xc, /* X take control asynchronously */
+ AUX_TCS = 0xd, /* X take " synchronously */
+ AUX_RPP = 0xe, /* d Request parallel poll */
+ AUX_SIC = 0xf, /* d send interface clear */
+ AUX_SRE = 0x10, /* d send remote enable */
+ AUX_RQC = 0x11, /* X request control */
+ AUX_RLC = 0x12, /* X release control */
+ AUX_DAI = 0x13, /* d disable all interrupts */
+ AUX_PTS = 0x14, /* X pass through next secondary */
+ AUX_STDL = 0x15, /* d short T1 delay */
+ AUX_SHDW = 0x16, /* d shadow handshake */
+ AUX_VSTDL = 0x17, /* d very short T1 delay (smj9914 extension) */
+ AUX_RSV2 = 0x18, /* d request service bit 2 (smj9914 extension) */
};
#endif //_TMS9914_H
diff --git a/drivers/staging/gpib/include/tnt4882_registers.h b/drivers/staging/gpib/include/tnt4882_registers.h
index 1b1441cd03d5..d54c4cc61168 100644
--- a/drivers/staging/gpib/include/tnt4882_registers.h
+++ b/drivers/staging/gpib/include/tnt4882_registers.h
@@ -32,11 +32,11 @@ enum {
CMDR = 0x1c, // command register
TIMER = 0x1e, // timer register
- STS1 = 0x10, /* T488 Status Register 1 */
- STS2 = 0x1c, /* T488 Status Register 2 */
+ STS1 = 0x10, // T488 Status Register 1
+ STS2 = 0x1c, // T488 Status Register 2
ISR0 = IMR0,
- ISR3 = 0x1a, /* T488 Interrupt Status Register 3 */
- BCR = 0x1f, /* bus control/status register */
+ ISR3 = 0x1a, // T488 Interrupt Status Register 3
+ BCR = 0x1f, // bus control/status register
BSR = BCR,
};
@@ -107,11 +107,11 @@ enum imr0_bits {
/* ISR0 -- Interrupt Status Register 0 */
enum isr0_bits {
- TNT_SYNC_BIT = 0x1, /* handshake sync */
- TNT_TO_BIT = 0x2, /* timeout */
- TNT_ATNI_BIT = 0x4, /* ATN interrupt */
+ TNT_SYNC_BIT = 0x1, /* handshake sync */
+ TNT_TO_BIT = 0x2, /* timeout */
+ TNT_ATNI_BIT = 0x4, /* ATN interrupt */
TNT_IFCI_BIT = 0x8, /* interface clear interrupt */
- TNT_EOS_BIT = 0x10, /* end of string */
+ TNT_EOS_BIT = 0x10, /* end of string */
TNT_NL_BIT = 0x20, /* new line receive */
TNT_STBO_BIT = 0x40, /* status byte out */
TNT_NBA_BIT = 0x80, /* new byte available */
@@ -129,7 +129,7 @@ enum isr3_bits {
};
enum keyreg_bits {
- MSTD = 0x20, // enable 350ns T1 delay
+ MSTD = 0x20, /* enable 350ns T1 delay */
};
/* STS1 -- Status Register 1 (read only) */
@@ -157,7 +157,7 @@ enum tnt4882_aux_cmds {
AUX_9914 = 0x15, // switch to 9914 mode
AUX_REQT = 0x18,
AUX_REQF = 0x19,
- AUX_PAGEIN = 0x50, /* page in alternate registers */
+ AUX_PAGEIN = 0x50, // page in alternate registers
AUX_HLDI = 0x51, // rfd holdoff immediately
AUX_CLEAR_END = 0x55,
AUX_7210 = 0x99, // switch to 7210 mode
@@ -183,7 +183,7 @@ enum auxi_bits {
enum sasr_bits {
ACRDY_BIT = 0x4, /* acceptor ready state */
- ADHS_BIT = 0x8, /* acceptor data holdoff state */
+ ADHS_BIT = 0x8, /* acceptor data holdoff state */
ANHS2_BIT = 0x10, /* acceptor not ready holdoff immediately state */
ANHS1_BIT = 0x20, /* acceptor not ready holdoff state */
AEHS_BIT = 0x40, /* acceptor end holdoff state */
diff --git a/drivers/staging/gpib/ines/ines.h b/drivers/staging/gpib/ines/ines.h
index f0210ce2470d..6ad57e9a1216 100644
--- a/drivers/staging/gpib/ines/ines.h
+++ b/drivers/staging/gpib/ines/ines.h
@@ -97,9 +97,9 @@ enum extend_mode_bits {
TR3_TRIG_ENABLE_BIT = 0x1, // enable generation of trigger pulse T/R3 pin
// clear message available status bit when chip writes byte with EOI true
MAV_ENABLE_BIT = 0x2,
- EOS1_ENABLE_BIT = 0x4, // enable eos register 1
- EOS2_ENABLE_BIT = 0x8, // enable eos register 2
- EOIDIS_BIT = 0x10, // disable EOI interrupt when doing rfd holdoff on end?
+ EOS1_ENABLE_BIT = 0x4, // enable eos register 1
+ EOS2_ENABLE_BIT = 0x8, // enable eos register 2
+ EOIDIS_BIT = 0x10, // disable EOI interrupt when doing rfd holdoff on end?
XFER_COUNTER_ENABLE_BIT = 0x20,
XFER_COUNTER_OUTPUT_BIT = 0x40, // use counter for output, clear for input
// when xfer counter hits 0, assert EOI on write or RFD holdoff on read
@@ -121,10 +121,10 @@ enum ines_admr_bits {
};
enum xdma_control_bits {
- DMA_OUTPUT_BIT = 0x1, // use dma for output, clear for input
+ DMA_OUTPUT_BIT = 0x1, // use dma for output, clear for input
ENABLE_SYNC_DMA_BIT = 0x2,
- DMA_ACCESS_EVERY_CYCLE = 0x4,// dma accesses fifo every cycle, clear for every other cycle
- DMA_16BIT = 0x8, // clear for 8 bit transfers
+ DMA_ACCESS_EVERY_CYCLE = 0x4, // dma accesses fifo every cycle, clear for every other cycle
+ DMA_16BIT = 0x8, // clear for 8 bit transfers
};
enum bus_control_monitor_bits {
diff --git a/drivers/staging/gpib/ines/ines_gpib.c b/drivers/staging/gpib/ines/ines_gpib.c
index c851fd014f48..a3cf846fd0f9 100644
--- a/drivers/staging/gpib/ines/ines_gpib.c
+++ b/drivers/staging/gpib/ines/ines_gpib.c
@@ -152,7 +152,7 @@ static int ines_accel_read(struct gpib_board *board, u8 *buffer,
write_byte(nec_priv, INES_RFD_HLD_IMMEDIATE, AUXMR);
- //clear in fifo
+ // clear in fifo
nec7210_set_reg_bits(nec_priv, ADMR, IN_FIFO_ENABLE_BIT, 0);
nec7210_set_reg_bits(nec_priv, ADMR, IN_FIFO_ENABLE_BIT, IN_FIFO_ENABLE_BIT);
@@ -225,7 +225,7 @@ static int ines_accel_write(struct gpib_board *board, u8 *buffer, size_t length,
unsigned int num_bytes, i;
*bytes_written = 0;
- //clear out fifo
+ // clear out fifo
nec7210_set_reg_bits(nec_priv, ADMR, OUT_FIFO_ENABLE_BIT, 0);
nec7210_set_reg_bits(nec_priv, ADMR, OUT_FIFO_ENABLE_BIT, OUT_FIFO_ENABLE_BIT);
diff --git a/drivers/staging/gpib/nec7210/nec7210.c b/drivers/staging/gpib/nec7210/nec7210.c
index 34a1cae4f486..bbf39367f5e4 100644
--- a/drivers/staging/gpib/nec7210/nec7210.c
+++ b/drivers/staging/gpib/nec7210/nec7210.c
@@ -779,10 +779,10 @@ int nec7210_write(struct gpib_board *board, struct nec7210_priv *priv,
*bytes_written = 0;
- clear_bit(DEV_CLEAR_BN, &priv->state); //XXX
+ clear_bit(DEV_CLEAR_BN, &priv->state); // XXX
if (send_eoi)
- length-- ; /* save the last byte for sending EOI */
+ length-- ; // save the last byte for sending EOI
if (length > 0) {
// isa dma transfer
@@ -1005,7 +1005,7 @@ void nec7210_board_online(struct nec7210_priv *priv, const struct gpib_board *bo
nec7210_primary_address(board, priv, board->pad);
nec7210_secondary_address(board, priv, board->sad, board->sad >= 0);
- // enable interrupts
+ /* enable interrupts */
priv->reg_bits[IMR1] = HR_ERRIE | HR_DECIE | HR_ENDIE |
HR_DETIE | HR_CPTIE | HR_DOIE | HR_DIIE;
priv->reg_bits[IMR2] = IMR2_ENABLE_INTR_MASK;
diff --git a/drivers/staging/gpib/ni_usb/ni_usb_gpib.c b/drivers/staging/gpib/ni_usb/ni_usb_gpib.c
index 73ea72f34c0a..4dec87d12687 100644
--- a/drivers/staging/gpib/ni_usb/ni_usb_gpib.c
+++ b/drivers/staging/gpib/ni_usb/ni_usb_gpib.c
@@ -29,7 +29,7 @@ static void ni_usb_stop(struct ni_usb_priv *ni_priv);
static DEFINE_MUTEX(ni_usb_hotplug_lock);
-//calculates a reasonable timeout in that can be passed to usb functions
+// calculates a reasonable timeout in that can be passed to usb functions
static inline unsigned long ni_usb_timeout_msecs(unsigned int usec)
{
if (usec == 0)
@@ -327,7 +327,7 @@ static void ni_usb_soft_update_status(struct gpib_board *board, unsigned int ni_
board->status &= ~clear_mask;
board->status &= ~ni_usb_ibsta_mask;
board->status |= ni_usb_ibsta & ni_usb_ibsta_mask;
- //FIXME should generate events on DTAS and DCAS
+ // FIXME should generate events on DTAS and DCAS
spin_lock_irqsave(&board->spinlock, flags);
/* remove set status bits from monitored set why ?***/
@@ -569,7 +569,7 @@ static int ni_usb_write_registers(struct ni_usb_priv *ni_priv,
mutex_unlock(&ni_priv->addressed_transfer_lock);
ni_usb_parse_reg_write_status_block(in_data, &status, &reg_writes_completed);
- //FIXME parse extra 09 status bits and termination
+ // FIXME parse extra 09 status bits and termination
kfree(in_data);
if (status.id != NIUSB_REG_WRITE_ID) {
dev_err(&usb_dev->dev, "parse error, id=0x%x != NIUSB_REG_WRITE_ID\n", status.id);
@@ -1106,7 +1106,7 @@ static int ni_usb_request_system_control(struct gpib_board *board, int request_c
return 0;
}
-//FIXME maybe the interface should have a "pulse interface clear" function that can return an error?
+// FIXME maybe the interface should have a "pulse interface clear" function that can return an error?
static void ni_usb_interface_clear(struct gpib_board *board, int assert)
{
int retval;
@@ -1363,7 +1363,7 @@ static int ni_usb_parallel_poll(struct gpib_board *board, u8 *result)
return -ENOMEM;
out_data[i++] = NIUSB_IBRPP_ID;
- out_data[i++] = 0xf0; //FIXME: this should be the parallel poll timeout code
+ out_data[i++] = 0xf0; // FIXME: this should be the parallel poll timeout code
out_data[i++] = 0x0;
out_data[i++] = 0x0;
i += ni_usb_bulk_termination(&out_data[i]);
diff --git a/drivers/staging/gpib/ni_usb/ni_usb_gpib.h b/drivers/staging/gpib/ni_usb/ni_usb_gpib.h
index b011e131201c..688f5e08792f 100644
--- a/drivers/staging/gpib/ni_usb/ni_usb_gpib.h
+++ b/drivers/staging/gpib/ni_usb/ni_usb_gpib.h
@@ -72,10 +72,10 @@ struct ni_usb_priv {
struct urb *bulk_urb;
struct urb *interrupt_urb;
u8 interrupt_buffer[0x11];
- struct mutex addressed_transfer_lock; // protect transfer lock
- struct mutex bulk_transfer_lock; // protect bulk message sends
- struct mutex control_transfer_lock; // protect control messages
- struct mutex interrupt_transfer_lock; // protect interrupt messages
+ struct mutex addressed_transfer_lock; // protect transfer lock
+ struct mutex bulk_transfer_lock; // protect bulk message sends
+ struct mutex control_transfer_lock; // protect control messages
+ struct mutex interrupt_transfer_lock; // protect interrupt messages
struct timer_list bulk_timer;
struct ni_usb_urb_ctx context;
int product_id;
@@ -145,7 +145,7 @@ enum ni_usb_error_codes {
* CIC with no listener
*/
NIUSB_NO_LISTENER_ERROR = 8,
- // get NIUSB_TIMEOUT_ERROR on board read/write timeout
+ /* get NIUSB_TIMEOUT_ERROR on board read/write timeout */
NIUSB_TIMEOUT_ERROR = 10,
};
diff --git a/drivers/staging/gpib/pc2/pc2_gpib.c b/drivers/staging/gpib/pc2/pc2_gpib.c
index 2282492025b7..9f3943d1df66 100644
--- a/drivers/staging/gpib/pc2/pc2_gpib.c
+++ b/drivers/staging/gpib/pc2/pc2_gpib.c
@@ -36,7 +36,7 @@ static const int pc2_2a_iosize = 16;
static const int pc2a_reg_offset = 0x400;
static const int pc2_reg_offset = 1;
-//interrupt service routine
+// interrupt service routine
static irqreturn_t pc2_interrupt(int irq, void *arg);
static irqreturn_t pc2a_interrupt(int irq, void *arg);
@@ -593,7 +593,7 @@ static struct gpib_interface pc2a_cb7210_interface = {
.parallel_poll_configure = pc2_parallel_poll_configure,
.parallel_poll_response = pc2_parallel_poll_response,
.local_parallel_poll_mode = NULL, // XXX
- .line_status = NULL, //XXX
+ .line_status = NULL, // XXX
.update_status = pc2_update_status,
.primary_address = pc2_primary_address,
.secondary_address = pc2_secondary_address,
diff --git a/drivers/staging/gpib/tms9914/tms9914.c b/drivers/staging/gpib/tms9914/tms9914.c
index 04d57108efc7..0d11b80bb982 100644
--- a/drivers/staging/gpib/tms9914/tms9914.c
+++ b/drivers/staging/gpib/tms9914/tms9914.c
@@ -647,7 +647,7 @@ static void check_my_address_state(struct gpib_board *board,
} else if (cmd_byte == MTA(board->pad)) {
priv->primary_talk_addressed = 1;
if (board->sad < 0)
- //make active talker
+ // make active talker
write_byte(priv, AUX_TON | AUX_CS, AUXCR);
} else if (board->sad >= 0 && priv->primary_talk_addressed &&
cmd_byte == MSA(board->sad)) {
@@ -730,7 +730,7 @@ irqreturn_t tms9914_interrupt_have_status(struct gpib_board *board, struct tms99
if (status0 & HR_SPAS) {
priv->spoll_status &= ~request_service_bit;
write_byte(priv, priv->spoll_status, SPMR);
- //FIXME: set SPOLL status bit
+ // FIXME: set SPOLL status bit
}
// record service request in status
if (status1 & HR_SRQ)
@@ -841,7 +841,7 @@ void tms9914_board_reset(struct tms9914_priv *priv)
/* parallel poll unconfigure */
write_byte(priv, 0, PPR);
- // request for data holdoff
+ /* request for data holdoff */
tms9914_set_holdoff_mode(priv, TMS9914_HOLDOFF_ALL);
}
EXPORT_SYMBOL_GPL(tms9914_board_reset);
@@ -852,7 +852,7 @@ void tms9914_online(struct gpib_board *board, struct tms9914_priv *priv)
tms9914_primary_address(board, priv, board->pad);
tms9914_secondary_address(board, priv, board->sad, board->sad >= 0);
- // enable tms9914 interrupts
+ /* enable tms9914 interrupts */
priv->imr0_bits |= HR_MACIE | HR_RLCIE | HR_ENDIE | HR_BOIE | HR_BIIE |
HR_SPASIE;
priv->imr1_bits |= HR_MAIE | HR_SRQIE | HR_UNCIE | HR_ERRIE | HR_IFCIE |
@@ -861,7 +861,7 @@ void tms9914_online(struct gpib_board *board, struct tms9914_priv *priv)
write_byte(priv, priv->imr1_bits, IMR1);
write_byte(priv, AUX_DAI, AUXCR);
- // turn off reset state
+ /* turn off reset state */
write_byte(priv, AUX_CHIP_RESET, AUXCR);
}
EXPORT_SYMBOL_GPL(tms9914_online);
diff --git a/drivers/staging/gpib/tnt4882/mite.h b/drivers/staging/gpib/tnt4882/mite.h
index 522d6b56cb7d..a1fdba9672a0 100644
--- a/drivers/staging/gpib/tnt4882/mite.h
+++ b/drivers/staging/gpib/tnt4882/mite.h
@@ -219,15 +219,15 @@ void mite_list_devices(void);
#define MITE_AMHOST_A24_BLOCK 0x3b
enum mite_registers {
- MITE_IODWBSR = 0xc0, //IO Device Window Base Size Register
- MITE_CSIGR = 0x460, //chip signature
- MITE_IODWBSR_1 = 0xc4, // IO Device Window Base Size Register 1 (used by 6602 boards)
+ MITE_IODWBSR = 0xc0, // IO Device Window Base Size Register
+ MITE_CSIGR = 0x460, // chip signature
+ MITE_IODWBSR_1 = 0xc4, // IO Device Window Base Size Register 1 (used by 6602 boards)
MITE_IODWCR_1 = 0xf4
};
enum MITE_IODWBSR_bits {
- WENAB = 0x80, // window enable
- WENAB_6602 = 0x8c // window enable for 6602 boards
+ WENAB = 0x80, // window enable
+ WENAB_6602 = 0x8c // window enable for 6602 boards
};
#endif
diff --git a/drivers/staging/gpib/tnt4882/tnt4882_gpib.c b/drivers/staging/gpib/tnt4882/tnt4882_gpib.c
index a17b69e34986..c03a976b7380 100644
--- a/drivers/staging/gpib/tnt4882/tnt4882_gpib.c
+++ b/drivers/staging/gpib/tnt4882/tnt4882_gpib.c
@@ -570,7 +570,7 @@ static irqreturn_t tnt4882_internal_interrupt(struct gpib_board *board)
if (isr0_bits & TNT_IFCI_BIT)
push_gpib_event(board, EVENT_IFC);
- //XXX don't need this wakeup, one below should do?
+ // XXX don't need this wakeup, one below should do?
// wake_up_interruptible(&board->wait);
if (isr3_bits & HR_NFF)
@@ -730,7 +730,7 @@ static int tnt4882_parallel_poll(struct gpib_board *board, u8 *result)
if (tnt_priv->nec7210_priv.type != NEC7210) {
tnt_priv->auxg_bits |= RPP2_BIT;
write_byte(&tnt_priv->nec7210_priv, tnt_priv->auxg_bits, AUXMR);
- udelay(2); //FIXME use parallel poll timeout
+ udelay(2); // FIXME use parallel poll timeout
*result = read_byte(&tnt_priv->nec7210_priv, CPTR);
tnt_priv->auxg_bits &= ~RPP2_BIT;
write_byte(&tnt_priv->nec7210_priv, tnt_priv->auxg_bits, AUXMR);
@@ -1522,7 +1522,6 @@ static void __exit tnt4882_exit_module(void)
#include <linux/moduleparam.h>
#include <linux/ptrace.h>
#include <linux/timer.h>
-#include <linux/ioport.h>
#include <linux/io.h>
#include <pcmcia/cistpl.h>
diff --git a/drivers/staging/iio/adc/ad7816.c b/drivers/staging/iio/adc/ad7816.c
index 4774df778de9..172acf135f3b 100644
--- a/drivers/staging/iio/adc/ad7816.c
+++ b/drivers/staging/iio/adc/ad7816.c
@@ -359,8 +359,6 @@ static int ad7816_probe(struct spi_device *spi_dev)
if (!indio_dev)
return -ENOMEM;
chip = iio_priv(indio_dev);
- /* this is only used for device removal purposes */
- dev_set_drvdata(&spi_dev->dev, indio_dev);
chip->spi_dev = spi_dev;
for (i = 0; i <= AD7816_CS_MAX; i++)
diff --git a/drivers/staging/octeon/ethernet-tx.c b/drivers/staging/octeon/ethernet-tx.c
index 0ba240e634a1..f5bbedac6a65 100644
--- a/drivers/staging/octeon/ethernet-tx.c
+++ b/drivers/staging/octeon/ethernet-tx.c
@@ -573,42 +573,14 @@ netdev_tx_t cvm_oct_xmit_pow(struct sk_buff *skb, struct net_device *dev)
if (skb->protocol == htons(ETH_P_IP)) {
work->word2.s.ip_offset = 14;
-#if 0
- work->word2.s.vlan_valid = 0; /* FIXME */
- work->word2.s.vlan_cfi = 0; /* FIXME */
- work->word2.s.vlan_id = 0; /* FIXME */
- work->word2.s.dec_ipcomp = 0; /* FIXME */
-#endif
work->word2.s.tcp_or_udp =
(ip_hdr(skb)->protocol == IPPROTO_TCP) ||
(ip_hdr(skb)->protocol == IPPROTO_UDP);
-#if 0
- /* FIXME */
- work->word2.s.dec_ipsec = 0;
- /* We only support IPv4 right now */
- work->word2.s.is_v6 = 0;
- /* Hardware would set to zero */
- work->word2.s.software = 0;
- /* No error, packet is internal */
- work->word2.s.L4_error = 0;
-#endif
work->word2.s.is_frag = !((ip_hdr(skb)->frag_off == 0) ||
(ip_hdr(skb)->frag_off ==
cpu_to_be16(1 << 14)));
-#if 0
- /* Assume Linux is sending a good packet */
- work->word2.s.IP_exc = 0;
-#endif
work->word2.s.is_bcast = (skb->pkt_type == PACKET_BROADCAST);
work->word2.s.is_mcast = (skb->pkt_type == PACKET_MULTICAST);
-#if 0
- /* This is an IP packet */
- work->word2.s.not_IP = 0;
- /* No error, packet is internal */
- work->word2.s.rcv_error = 0;
- /* No error, packet is internal */
- work->word2.s.err_code = 0;
-#endif
/*
* When copying the data, include 4 bytes of the
@@ -618,12 +590,6 @@ netdev_tx_t cvm_oct_xmit_pow(struct sk_buff *skb, struct net_device *dev)
memcpy(work->packet_data, skb->data + 10,
sizeof(work->packet_data));
} else {
-#if 0
- work->word2.snoip.vlan_valid = 0; /* FIXME */
- work->word2.snoip.vlan_cfi = 0; /* FIXME */
- work->word2.snoip.vlan_id = 0; /* FIXME */
- work->word2.snoip.software = 0; /* Hardware would set to zero */
-#endif
work->word2.snoip.is_rarp = skb->protocol == htons(ETH_P_RARP);
work->word2.snoip.is_arp = skb->protocol == htons(ETH_P_ARP);
work->word2.snoip.is_bcast =
@@ -631,12 +597,6 @@ netdev_tx_t cvm_oct_xmit_pow(struct sk_buff *skb, struct net_device *dev)
work->word2.snoip.is_mcast =
(skb->pkt_type == PACKET_MULTICAST);
work->word2.snoip.not_IP = 1; /* IP was done up above */
-#if 0
- /* No error, packet is internal */
- work->word2.snoip.rcv_error = 0;
- /* No error, packet is internal */
- work->word2.snoip.err_code = 0;
-#endif
memcpy(work->packet_data, skb->data, sizeof(work->packet_data));
}
diff --git a/drivers/staging/octeon/octeon-stubs.h b/drivers/staging/octeon/octeon-stubs.h
index 44cced319c11..35b5078ba51e 100644
--- a/drivers/staging/octeon/octeon-stubs.h
+++ b/drivers/staging/octeon/octeon-stubs.h
@@ -43,81 +43,83 @@
#define CVMX_POW_WQ_INT_PC 0
union cvmx_pip_wqe_word2 {
- uint64_t u64;
+ u64 u64;
+
struct {
- uint64_t bufs:8;
- uint64_t ip_offset:8;
- uint64_t vlan_valid:1;
- uint64_t vlan_stacked:1;
- uint64_t unassigned:1;
- uint64_t vlan_cfi:1;
- uint64_t vlan_id:12;
- uint64_t pr:4;
- uint64_t unassigned2:8;
- uint64_t dec_ipcomp:1;
- uint64_t tcp_or_udp:1;
- uint64_t dec_ipsec:1;
- uint64_t is_v6:1;
- uint64_t software:1;
- uint64_t L4_error:1;
- uint64_t is_frag:1;
- uint64_t IP_exc:1;
- uint64_t is_bcast:1;
- uint64_t is_mcast:1;
- uint64_t not_IP:1;
- uint64_t rcv_error:1;
- uint64_t err_code:8;
+ u64 bufs : 8;
+ u64 ip_offset : 8;
+ u64 vlan_valid : 1;
+ u64 vlan_stacked : 1;
+ u64 unassigned : 1;
+ u64 vlan_cfi : 1;
+ u64 vlan_id : 12;
+ u64 pr : 4;
+ u64 unassigned2 : 8;
+ u64 dec_ipcomp : 1;
+ u64 tcp_or_udp : 1;
+ u64 dec_ipsec : 1;
+ u64 is_v6 : 1;
+ u64 software : 1;
+ u64 L4_error : 1;
+ u64 is_frag : 1;
+ u64 IP_exc : 1;
+ u64 is_bcast : 1;
+ u64 is_mcast : 1;
+ u64 not_IP : 1;
+ u64 rcv_error : 1;
+ u64 err_code : 8;
} s;
+
struct {
- uint64_t bufs:8;
- uint64_t ip_offset:8;
- uint64_t vlan_valid:1;
- uint64_t vlan_stacked:1;
- uint64_t unassigned:1;
- uint64_t vlan_cfi:1;
- uint64_t vlan_id:12;
- uint64_t port:12;
- uint64_t dec_ipcomp:1;
- uint64_t tcp_or_udp:1;
- uint64_t dec_ipsec:1;
- uint64_t is_v6:1;
- uint64_t software:1;
- uint64_t L4_error:1;
- uint64_t is_frag:1;
- uint64_t IP_exc:1;
- uint64_t is_bcast:1;
- uint64_t is_mcast:1;
- uint64_t not_IP:1;
- uint64_t rcv_error:1;
- uint64_t err_code:8;
+ u64 bufs : 8;
+ u64 ip_offset : 8;
+ u64 vlan_valid : 1;
+ u64 vlan_stacked : 1;
+ u64 unassigned : 1;
+ u64 vlan_cfi : 1;
+ u64 vlan_id : 12;
+ u64 port : 12;
+ u64 dec_ipcomp : 1;
+ u64 tcp_or_udp : 1;
+ u64 dec_ipsec : 1;
+ u64 is_v6 : 1;
+ u64 software : 1;
+ u64 L4_error : 1;
+ u64 is_frag : 1;
+ u64 IP_exc : 1;
+ u64 is_bcast : 1;
+ u64 is_mcast : 1;
+ u64 not_IP : 1;
+ u64 rcv_error : 1;
+ u64 err_code : 8;
} s_cn68xx;
struct {
- uint64_t unused1:16;
- uint64_t vlan:16;
- uint64_t unused2:32;
+ u64 unused1 : 16;
+ u64 vlan : 16;
+ u64 unused2 : 32;
} svlan;
+
struct {
- uint64_t bufs:8;
- uint64_t unused:8;
- uint64_t vlan_valid:1;
- uint64_t vlan_stacked:1;
- uint64_t unassigned:1;
- uint64_t vlan_cfi:1;
- uint64_t vlan_id:12;
- uint64_t pr:4;
- uint64_t unassigned2:12;
- uint64_t software:1;
- uint64_t unassigned3:1;
- uint64_t is_rarp:1;
- uint64_t is_arp:1;
- uint64_t is_bcast:1;
- uint64_t is_mcast:1;
- uint64_t not_IP:1;
- uint64_t rcv_error:1;
- uint64_t err_code:8;
+ u64 bufs : 8;
+ u64 unused : 8;
+ u64 vlan_valid : 1;
+ u64 vlan_stacked : 1;
+ u64 unassigned : 1;
+ u64 vlan_cfi : 1;
+ u64 vlan_id : 12;
+ u64 pr : 4;
+ u64 unassigned2 : 12;
+ u64 software : 1;
+ u64 unassigned3 : 1;
+ u64 is_rarp : 1;
+ u64 is_arp : 1;
+ u64 is_bcast : 1;
+ u64 is_mcast : 1;
+ u64 not_IP : 1;
+ u64 rcv_error : 1;
+ u64 err_code : 8;
} snoip;
-
};
union cvmx_pip_wqe_word0 {
diff --git a/drivers/staging/rtl8723bs/Makefile b/drivers/staging/rtl8723bs/Makefile
index 8560b84a3146..1768b8123f03 100644
--- a/drivers/staging/rtl8723bs/Makefile
+++ b/drivers/staging/rtl8723bs/Makefile
@@ -48,10 +48,8 @@ r8723bs-y = \
hal/HalHWImg8723B_RF.o \
hal/HalPhyRf_8723B.o \
os_dep/ioctl_cfg80211.o \
- os_dep/mlme_linux.o \
os_dep/osdep_service.o \
os_dep/os_intfs.o \
- os_dep/recv_linux.o \
os_dep/sdio_intf.o \
os_dep/sdio_ops_linux.o \
os_dep/wifi_regd.o \
diff --git a/drivers/staging/rtl8723bs/core/rtw_ap.c b/drivers/staging/rtl8723bs/core/rtw_ap.c
index b2e7e7267aa4..0908f2234f67 100644
--- a/drivers/staging/rtl8723bs/core/rtw_ap.c
+++ b/drivers/staging/rtl8723bs/core/rtw_ap.c
@@ -258,11 +258,9 @@ void expire_timeout_chk(struct adapter *padapter)
} else {
/* TODO: Aging mechanism to digest frames in sleep_q to */
/* avoid running out of xmitframe */
- if (psta->sleepq_len > (NR_XMITFRAME / pstapriv->asoc_list_cnt)
- && padapter->xmitpriv.free_xmitframe_cnt < ((
- NR_XMITFRAME / pstapriv->asoc_list_cnt
- ) / 2)
- )
+ if (psta->sleepq_len > (NR_XMITFRAME / pstapriv->asoc_list_cnt) &&
+ padapter->xmitpriv.free_xmitframe_cnt <
+ ((NR_XMITFRAME / pstapriv->asoc_list_cnt) / 2))
wakeup_sta_to_xmit(padapter, psta);
}
}
diff --git a/drivers/staging/rtl8723bs/core/rtw_efuse.c b/drivers/staging/rtl8723bs/core/rtw_efuse.c
index 208373113a62..d5c53b614f61 100644
--- a/drivers/staging/rtl8723bs/core/rtw_efuse.c
+++ b/drivers/staging/rtl8723bs/core/rtw_efuse.c
@@ -29,57 +29,6 @@ u8 fakeBTEfuseModifiedMap[EFUSE_BT_MAX_MAP_LEN] = {0};
#define REG_EFUSE_CTRL 0x0030
#define EFUSE_CTRL REG_EFUSE_CTRL /* E-Fuse Control. */
-static bool
-Efuse_Read1ByteFromFakeContent(u16 Offset, u8 *Value)
-{
- if (Offset >= EFUSE_MAX_HW_SIZE)
- return false;
- if (fakeEfuseBank == 0)
- *Value = fakeEfuseContent[Offset];
- else
- *Value = fakeBTEfuseContent[fakeEfuseBank - 1][Offset];
- return true;
-}
-
-static bool
-Efuse_Write1ByteToFakeContent(u16 Offset, u8 Value)
-{
- if (Offset >= EFUSE_MAX_HW_SIZE)
- return false;
- if (fakeEfuseBank == 0)
- fakeEfuseContent[Offset] = Value;
- else
- fakeBTEfuseContent[fakeEfuseBank - 1][Offset] = Value;
- return true;
-}
-
-/*-----------------------------------------------------------------------------
- * Function: Efuse_PowerSwitch
- *
- * Overview: When we want to enable write operation, we should change to
- * pwr on state. When we stop write, we should switch to 500k mode
- * and disable LDO 2.5V.
- *
- * Input: NONE
- *
- * Output: NONE
- *
- * Return: NONE
- *
- * Revised History:
- * When Who Remark
- * 11/17/2008 MHC Create Version 0.
- *
- */
-void
-Efuse_PowerSwitch(
-struct adapter *padapter,
-u8 bWrite,
-u8 PwrState)
-{
- Hal_EfusePowerSwitch(padapter, bWrite, PwrState);
-}
-
/* 11/16/2008 MH Add description. Get current efuse area enabled word!!. */
u8
Efuse_CalculateWordCnts(u8 word_en)
@@ -97,58 +46,6 @@ Efuse_CalculateWordCnts(u8 word_en)
return word_cnts;
}
-/* */
-/* Description: */
-/* 1. Execute E-Fuse read byte operation according as map offset and */
-/* save to E-Fuse table. */
-/* 2. Referred from SD1 Richard. */
-/* */
-/* Assumption: */
-/* 1. Boot from E-Fuse and successfully auto-load. */
-/* 2. PASSIVE_LEVEL (USB interface) */
-/* */
-/* Created by Roger, 2008.10.21. */
-/* */
-/* 2008/12/12 MH 1. Reorganize code flow and reserve bytes. and add description. */
-/* 2. Add efuse utilization collect. */
-/* 2008/12/22 MH Read Efuse must check if we write section 1 data again!!! Sec1 */
-/* write addr must be after sec5. */
-/* */
-
-void
-efuse_ReadEFuse(
- struct adapter *Adapter,
- u8 efuseType,
- u16 _offset,
- u16 _size_byte,
- u8 *pbuf,
-bool bPseudoTest
- );
-void
-efuse_ReadEFuse(
- struct adapter *Adapter,
- u8 efuseType,
- u16 _offset,
- u16 _size_byte,
- u8 *pbuf,
-bool bPseudoTest
- )
-{
- Hal_ReadEFuse(Adapter, efuseType, _offset, _size_byte, pbuf, bPseudoTest);
-}
-
-void
-EFUSE_GetEfuseDefinition(
- struct adapter *padapter,
- u8 efuseType,
- u8 type,
- void *pOut,
- bool bPseudoTest
- )
-{
- Hal_GetEfuseDefinition(padapter, efuseType, type, pOut, bPseudoTest);
-}
-
/*-----------------------------------------------------------------------------
* Function: EFUSE_Read1Byte
*
@@ -175,7 +72,7 @@ u16 Address)
u32 k = 0;
u16 contentLen = 0;
- EFUSE_GetEfuseDefinition(Adapter, EFUSE_WIFI, TYPE_EFUSE_REAL_CONTENT_LEN, (void *)&contentLen, false);
+ Hal_GetEfuseDefinition(Adapter, EFUSE_WIFI, TYPE_EFUSE_REAL_CONTENT_LEN, (void *)&contentLen);
if (Address < contentLen) {/* E-fuse 512Byte */
/* Write E-fuse Register address bit0~7 */
@@ -210,16 +107,12 @@ u8
efuse_OneByteRead(
struct adapter *padapter,
u16 addr,
-u8 *data,
-bool bPseudoTest)
+u8 *data)
{
u32 tmpidx = 0;
u8 bResult;
u8 readbyte;
- if (bPseudoTest)
- return Efuse_Read1ByteFromFakeContent(addr, data);
-
/* <20130121, Kordan> For SMIC EFUSE specificatoin. */
/* 0x34[11]: SW force PGMEN input of efuse to high. (for the bank selected by 0x34[9:8]) */
/* PHY_SetMacReg(padapter, 0x34, BIT11, 0); */
@@ -251,42 +144,6 @@ bool bPseudoTest)
return bResult;
}
-/* 11/16/2008 MH Write one byte to reald Efuse. */
-u8 efuse_OneByteWrite(struct adapter *padapter, u16 addr, u8 data, bool bPseudoTest)
-{
- u8 tmpidx = 0;
- u8 bResult = false;
-
- if (bPseudoTest)
- return Efuse_Write1ByteToFakeContent(addr, data);
-
- /* -----------------e-fuse reg ctrl --------------------------------- */
- /* address */
-
- /* <20130227, Kordan> 8192E MP chip A-cut had better not set 0x34[11] until B-Cut. */
-
- /* <20130121, Kordan> For SMIC EFUSE specificatoin. */
- /* 0x34[11]: SW force PGMEN input of efuse to high. (for the bank selected by 0x34[9:8]) */
- /* PHY_SetMacReg(padapter, 0x34, BIT11, 1); */
- rtw_write16(padapter, 0x34, rtw_read16(padapter, 0x34) | (BIT11));
- rtw_write32(padapter, EFUSE_CTRL, 0x90600000 | ((addr << 8 | data)));
-
- while ((0x80 & rtw_read8(padapter, EFUSE_CTRL + 3)) && (tmpidx < 100)) {
- mdelay(1);
- tmpidx++;
- }
-
- if (tmpidx < 100)
- bResult = true;
- else
- bResult = false;
-
- /* disable Efuse program enable */
- PHY_SetMacReg(padapter, EFUSE_TEST, BIT(11), 0);
-
- return bResult;
-}
-
/*-----------------------------------------------------------------------------
* Function: Efuse_ReadAllMap
*
@@ -303,23 +160,17 @@ u8 efuse_OneByteWrite(struct adapter *padapter, u16 addr, u8 data, bool bPseudoT
* 11/11/2008 MHC Create Version 0.
*
*/
-void
-Efuse_ReadAllMap(
- struct adapter *padapter,
- u8 efuseType,
- u8 *Efuse,
- bool bPseudoTest);
-void Efuse_ReadAllMap(struct adapter *padapter, u8 efuseType, u8 *Efuse, bool bPseudoTest)
+static void Efuse_ReadAllMap(struct adapter *padapter, u8 efuseType, u8 *Efuse)
{
u16 mapLen = 0;
- Efuse_PowerSwitch(padapter, false, true);
+ Hal_EfusePowerSwitch(padapter, true);
- EFUSE_GetEfuseDefinition(padapter, efuseType, TYPE_EFUSE_MAP_LEN, (void *)&mapLen, bPseudoTest);
+ Hal_GetEfuseDefinition(padapter, efuseType, TYPE_EFUSE_MAP_LEN, (void *)&mapLen);
- efuse_ReadEFuse(padapter, efuseType, 0, mapLen, Efuse, bPseudoTest);
+ Hal_ReadEFuse(padapter, efuseType, 0, mapLen, Efuse);
- Efuse_PowerSwitch(padapter, false, false);
+ Hal_EfusePowerSwitch(padapter, false);
}
/*-----------------------------------------------------------------------------
@@ -386,17 +237,17 @@ static void efuse_ShadowRead4Byte(struct adapter *padapter, u16 Offset, u32 *Val
* 11/13/2008 MHC Create Version 0.
*
*/
-void EFUSE_ShadowMapUpdate(struct adapter *padapter, u8 efuseType, bool bPseudoTest)
+void EFUSE_ShadowMapUpdate(struct adapter *padapter, u8 efuseType)
{
struct eeprom_priv *pEEPROM = GET_EEPROM_EFUSE_PRIV(padapter);
u16 mapLen = 0;
- EFUSE_GetEfuseDefinition(padapter, efuseType, TYPE_EFUSE_MAP_LEN, (void *)&mapLen, bPseudoTest);
+ Hal_GetEfuseDefinition(padapter, efuseType, TYPE_EFUSE_MAP_LEN, (void *)&mapLen);
if (pEEPROM->bautoload_fail_flag)
memset(pEEPROM->efuse_eeprom_data, 0xFF, mapLen);
else
- Efuse_ReadAllMap(padapter, efuseType, pEEPROM->efuse_eeprom_data, bPseudoTest);
+ Efuse_ReadAllMap(padapter, efuseType, pEEPROM->efuse_eeprom_data);
/* PlatformMoveMemory((void *)&pHalData->EfuseMap[EFUSE_MODIFY_MAP][0], */
/* void *)&pHalData->EfuseMap[EFUSE_INIT_MAP][0], mapLen); */
diff --git a/drivers/staging/rtl8723bs/core/rtw_mlme.c b/drivers/staging/rtl8723bs/core/rtw_mlme.c
index 692d0c2b766d..c06d990350e6 100644
--- a/drivers/staging/rtl8723bs/core/rtw_mlme.c
+++ b/drivers/staging/rtl8723bs/core/rtw_mlme.c
@@ -9,6 +9,36 @@
#include <hal_btcoex.h>
#include <linux/jiffies.h>
+static void _dynamic_check_timer_handler(struct timer_list *t)
+{
+ struct adapter *adapter =
+ timer_container_of(adapter, t, mlmepriv.dynamic_chk_timer);
+
+ rtw_dynamic_check_timer_handler(adapter);
+
+ _set_timer(&adapter->mlmepriv.dynamic_chk_timer, 2000);
+}
+
+static void _rtw_set_scan_deny_timer_hdl(struct timer_list *t)
+{
+ struct adapter *adapter =
+ timer_container_of(adapter, t, mlmepriv.set_scan_deny_timer);
+
+ rtw_clear_scan_deny(adapter);
+}
+
+static void rtw_init_mlme_timer(struct adapter *padapter)
+{
+ struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
+
+ timer_setup(&pmlmepriv->assoc_timer, _rtw_join_timeout_handler, 0);
+ timer_setup(&pmlmepriv->scan_to_timer, rtw_scan_timeout_handler, 0);
+ timer_setup(&pmlmepriv->dynamic_chk_timer,
+ _dynamic_check_timer_handler, 0);
+ timer_setup(&pmlmepriv->set_scan_deny_timer,
+ _rtw_set_scan_deny_timer_hdl, 0);
+}
+
int rtw_init_mlme_priv(struct adapter *padapter)
{
int i;
@@ -170,7 +200,6 @@ void _rtw_free_network(struct mlme_priv *pmlmepriv, struct wlan_network *pnetwor
void _rtw_free_network_nolock(struct mlme_priv *pmlmepriv, struct wlan_network *pnetwork)
{
-
struct __queue *free_queue = &pmlmepriv->free_bss_pool;
if (!pnetwork)
@@ -225,11 +254,9 @@ void rtw_free_network_queue(struct adapter *padapter, u8 isfreeall)
phead = get_list_head(scanned_queue);
list_for_each_safe(plist, tmp, phead) {
-
pnetwork = list_entry(plist, struct wlan_network, list);
_rtw_free_network(pmlmepriv, pnetwork, isfreeall);
-
}
spin_unlock_bh(&scanned_queue->lock);
@@ -318,7 +345,6 @@ int rtw_is_same_ibss(struct adapter *adapter, struct wlan_network *pnetwork)
ret = true;
return ret;
-
}
inline int is_same_ess(struct wlan_bssid_ex *a, struct wlan_bssid_ex *b)
@@ -348,7 +374,6 @@ int is_same_network(struct wlan_bssid_ex *src, struct wlan_bssid_ex *dst, u8 fea
(d_cap & WLAN_CAPABILITY_IBSS)) &&
((s_cap & WLAN_CAPABILITY_ESS) ==
(d_cap & WLAN_CAPABILITY_ESS));
-
}
struct wlan_network *_rtw_find_same_network(struct __queue *scanned_queue, struct wlan_network *network)
@@ -380,7 +405,6 @@ struct wlan_network *rtw_get_oldest_wlan_network(struct __queue *scanned_queue)
phead = get_list_head(scanned_queue);
list_for_each(plist, phead) {
-
pwlan = list_entry(plist, struct wlan_network, list);
if (!pwlan->fixed) {
@@ -389,7 +413,6 @@ struct wlan_network *rtw_get_oldest_wlan_network(struct __queue *scanned_queue)
}
}
return oldest;
-
}
void update_network(struct wlan_bssid_ex *dst, struct wlan_bssid_ex *src,
@@ -424,7 +447,6 @@ void update_network(struct wlan_bssid_ex *dst, struct wlan_bssid_ex *src,
sq_final = dst->phy_info.signal_quality;
rssi_final = dst->rssi;
}
-
}
if (update_ie) {
@@ -486,7 +508,6 @@ void rtw_update_scanned_network(struct adapter *adapter, struct wlan_bssid_ex *t
if (!oldest || time_after(oldest->last_scanned, pnetwork->last_scanned))
oldest = pnetwork;
-
}
/* If we didn't find a match, then get a new network slot to initialize
@@ -530,7 +551,6 @@ void rtw_update_scanned_network(struct adapter *adapter, struct wlan_bssid_ex *t
pnetwork->network.phy_info.signal_quality = 0;
list_add_tail(&pnetwork->list, &queue->queue);
-
}
} else {
/* we have an entry and we are going to update it. But this entry may
@@ -567,12 +587,14 @@ void rtw_add_network(struct adapter *adapter, struct wlan_bssid_ex *pnetwork)
rtw_update_scanned_network(adapter, pnetwork);
}
-/* select the desired network based on the capability of the (i)bss. */
-/* check items: (1) security */
-/* (2) network_type */
-/* (3) WMM */
-/* (4) HT */
-/* (5) others */
+/* select the desired network based on the capability of the (i)bss.
+ * check items:
+ * (1) security
+ * (2) network_type
+ * (3) WMM
+ * (4) HT
+ * (5) others
+ */
int rtw_is_desired_network(struct adapter *adapter, struct wlan_network *pnetwork);
int rtw_is_desired_network(struct adapter *adapter, struct wlan_network *pnetwork)
{
@@ -591,7 +613,6 @@ int rtw_is_desired_network(struct adapter *adapter, struct wlan_network *pnetwor
return true;
else
return false;
-
}
if (adapter->registrypriv.wifi_spec == 1) { /* for correct flow of 8021X to do.... */
u8 *p = NULL;
@@ -868,15 +889,23 @@ void rtw_indicate_connect(struct adapter *padapter)
pmlmepriv->to_join = false;
if (!check_fwstate(&padapter->mlmepriv, _FW_LINKED)) {
-
set_fwstate(pmlmepriv, _FW_LINKED);
- rtw_os_indicate_connect(padapter);
+ if (check_fwstate(pmlmepriv, WIFI_ADHOC_MASTER_STATE) ||
+ check_fwstate(pmlmepriv, WIFI_ADHOC_STATE)) {
+ rtw_cfg80211_ibss_indicate_connect(padapter);
+ } else {
+ rtw_cfg80211_indicate_connect(padapter);
+ }
+
+ netif_carrier_on(padapter->pnetdev);
+
+ if (padapter->pid[2] != 0)
+ rtw_signal_process(padapter->pid[2], SIGALRM);
}
rtw_set_to_roam(padapter, 0);
rtw_set_scan_deny(padapter, 3000);
-
}
/*
@@ -891,10 +920,14 @@ void rtw_indicate_disconnect(struct adapter *padapter)
if (rtw_to_roam(padapter) > 0)
_clr_fwstate_(pmlmepriv, _FW_LINKED);
- if (check_fwstate(&padapter->mlmepriv, _FW_LINKED)
- || (rtw_to_roam(padapter) <= 0)
- ) {
- rtw_os_indicate_disconnect(padapter);
+ if (check_fwstate(&padapter->mlmepriv, _FW_LINKED) || rtw_to_roam(padapter) <= 0) {
+ /* Do it first for tx broadcast pkt after disconnection issue! */
+ netif_carrier_off(padapter->pnetdev);
+
+ rtw_cfg80211_indicate_disconnect(padapter);
+
+ /* modify for CONFIG_IEEE80211W, none 11w also can use the same command */
+ rtw_reset_securitypriv_cmd(padapter);
/* set ips_deny_time to avoid enter IPS before LPS leave */
rtw_set_ips_deny(padapter, 3000);
@@ -909,7 +942,7 @@ void rtw_indicate_disconnect(struct adapter *padapter)
inline void rtw_indicate_scan_done(struct adapter *padapter, bool aborted)
{
- rtw_os_indicate_scan_done(padapter, aborted);
+ rtw_cfg80211_indicate_scan_done(padapter, aborted);
if ((!adapter_to_pwrctl(padapter)->bInSuspend) &&
(!check_fwstate(&padapter->mlmepriv,
@@ -929,7 +962,6 @@ void rtw_scan_abort(struct adapter *adapter)
pmlmeext->scan_abort = true;
while (check_fwstate(pmlmepriv, _FW_UNDER_SURVEY)
&& jiffies_to_msecs(start) <= 200) {
-
if (adapter->bDriverStopped || adapter->bSurpriseRemoved)
break;
@@ -1022,7 +1054,6 @@ static struct sta_info *rtw_joinbss_update_stainfo(struct adapter *padapter, str
}
return psta;
-
}
/* pnetwork : returns from rtw_joinbss_event_callback */
@@ -1073,6 +1104,66 @@ static void rtw_joinbss_update_network(struct adapter *padapter, struct wlan_net
rtw_update_ht_cap(padapter, cur_network->network.ies, cur_network->network.ie_length, (u8) cur_network->network.configuration.ds_config);
}
+static struct rt_pmkid_list backupPMKIDList[NUM_PMKID_CACHE];
+void rtw_reset_securitypriv(struct adapter *adapter)
+{
+ u8 backupPMKIDIndex = 0;
+ u8 backupTKIPCountermeasure = 0x00;
+ u32 backupTKIPcountermeasure_time = 0;
+ /* add for CONFIG_IEEE80211W, none 11w also can use */
+ struct mlme_ext_priv *pmlmeext = &adapter->mlmeextpriv;
+
+ spin_lock_bh(&adapter->security_key_mutex);
+
+ if (adapter->securitypriv.dot11AuthAlgrthm == dot11AuthAlgrthm_8021X) {
+ /* 802.1x */
+ /* Added by Albert 2009/02/18 */
+ /* We have to backup the PMK information for WiFi PMK Caching test item. */
+ /* */
+ /* Backup the btkip_countermeasure information. */
+ /* When the countermeasure is trigger, the driver have to disconnect with AP for 60 seconds. */
+
+ memcpy(&backupPMKIDList[0], &adapter->securitypriv.PMKIDList[0], sizeof(struct rt_pmkid_list) * NUM_PMKID_CACHE);
+ backupPMKIDIndex = adapter->securitypriv.PMKIDIndex;
+ backupTKIPCountermeasure = adapter->securitypriv.btkip_countermeasure;
+ backupTKIPcountermeasure_time = adapter->securitypriv.btkip_countermeasure_time;
+
+ /* reset RX BIP packet number */
+ pmlmeext->mgnt_80211w_IPN_rx = 0;
+
+ memset((unsigned char *)&adapter->securitypriv, 0, sizeof(struct security_priv));
+
+ /* Added by Albert 2009/02/18 */
+ /* Restore the PMK information to securitypriv structure for the following connection. */
+ memcpy(&adapter->securitypriv.PMKIDList[0], &backupPMKIDList[0], sizeof(struct rt_pmkid_list) * NUM_PMKID_CACHE);
+ adapter->securitypriv.PMKIDIndex = backupPMKIDIndex;
+ adapter->securitypriv.btkip_countermeasure = backupTKIPCountermeasure;
+ adapter->securitypriv.btkip_countermeasure_time = backupTKIPcountermeasure_time;
+
+ adapter->securitypriv.ndisauthtype = Ndis802_11AuthModeOpen;
+ adapter->securitypriv.ndisencryptstatus = Ndis802_11WEPDisabled;
+
+ } else {
+ /* reset values in securitypriv */
+ /* if (adapter->mlmepriv.fw_state & WIFI_STATION_STATE) */
+ /* */
+ struct security_priv *psec_priv = &adapter->securitypriv;
+
+ psec_priv->dot11AuthAlgrthm = dot11AuthAlgrthm_Open; /* open system */
+ psec_priv->dot11PrivacyAlgrthm = _NO_PRIVACY_;
+ psec_priv->dot11PrivacyKeyIndex = 0;
+
+ psec_priv->dot118021XGrpPrivacy = _NO_PRIVACY_;
+ psec_priv->dot118021XGrpKeyid = 1;
+
+ psec_priv->ndisauthtype = Ndis802_11AuthModeOpen;
+ psec_priv->ndisencryptstatus = Ndis802_11WEPDisabled;
+ /* */
+ }
+ /* add for CONFIG_IEEE80211W, none 11w also can use */
+ spin_unlock_bh(&adapter->security_key_mutex);
+}
+
/* Notes: the function could be > passive_level (the same context as Rx tasklet) */
/* pnetwork : returns from rtw_joinbss_event_callback */
/* ptarget_wlan: found from scanned_queue */
@@ -1397,11 +1488,11 @@ void rtw_stadel_event_callback(struct adapter *adapter, u8 *pbuf)
if (check_fwstate(pmlmepriv, WIFI_ADHOC_MASTER_STATE) ||
check_fwstate(pmlmepriv, WIFI_ADHOC_STATE)) {
-
rtw_free_stainfo(adapter, psta);
if (adapter->stapriv.asoc_sta_count == 1) {/* a sta + bc/mc_stainfo (not Ibss_stainfo) */
u8 ret = _SUCCESS;
+
spin_lock_bh(&pmlmepriv->scanned_queue.lock);
/* free old ibss network */
pwlan = rtw_find_network(&pmlmepriv->scanned_queue, tgt_network->network.mac_address);
@@ -1431,7 +1522,6 @@ void rtw_stadel_event_callback(struct adapter *adapter, u8 *pbuf)
if (ret != _SUCCESS)
goto unlock;
}
-
}
unlock:
@@ -1490,7 +1580,6 @@ void _rtw_join_timeout_handler(struct timer_list *t)
/* indicate disconnect for the case that join_timeout and check_fwstate != FW_LINKED */
rtw_cfg80211_indicate_disconnect(adapter);
-
}
spin_unlock_bh(&pmlmepriv->lock);
@@ -1540,7 +1629,6 @@ static void rtw_auto_scan_handler(struct adapter *padapter)
if (pmlmepriv->auto_scan_int_ms != 0
&& jiffies_to_msecs(jiffies - pmlmepriv->scan_start_time) > pmlmepriv->auto_scan_int_ms) {
-
if (!padapter->registrypriv.wifi_spec) {
if (check_fwstate(pmlmepriv, _FW_UNDER_SURVEY | _FW_UNDER_LINKING) == true)
goto exit;
@@ -1673,12 +1761,10 @@ int rtw_select_roaming_candidate(struct mlme_priv *mlme)
phead = get_list_head(queue);
list_for_each(mlme->pscanned, phead) {
-
pnetwork = list_entry(mlme->pscanned, struct wlan_network,
list);
rtw_check_roaming_candidate(mlme, &candidate, pnetwork);
-
}
if (!candidate) {
@@ -1770,12 +1856,10 @@ int rtw_select_and_join_from_scanned_queue(struct mlme_priv *pmlmepriv)
phead = get_list_head(queue);
list_for_each(pmlmepriv->pscanned, phead) {
-
pnetwork = list_entry(pmlmepriv->pscanned,
struct wlan_network, list);
rtw_check_join_candidate(pmlmepriv, &candidate, pnetwork);
-
}
if (!candidate) {
@@ -1841,9 +1925,9 @@ exit:
signed int rtw_set_key(struct adapter *adapter, struct security_priv *psecuritypriv, signed int keyid, u8 set_tx, bool enqueue)
{
u8 keylen;
- struct cmd_obj *pcmd;
+ struct cmd_obj *pcmd;
struct setkey_parm *psetkeyparm;
- struct cmd_priv *pcmdpriv = &adapter->cmdpriv;
+ struct cmd_priv *pcmdpriv = &adapter->cmdpriv;
signed int res = _SUCCESS;
psetkeyparm = rtw_zmalloc(sizeof(struct setkey_parm));
@@ -1863,7 +1947,6 @@ signed int rtw_set_key(struct adapter *adapter, struct security_priv *psecurityp
adapter->securitypriv.key_mask |= BIT(psetkeyparm->keyid);
switch (psetkeyparm->algorithm) {
-
case _WEP40_:
keylen = 5;
memcpy(&psetkeyparm->key[0], &psecuritypriv->dot11DefKey[keyid].skey[0], keylen);
@@ -1939,20 +2022,18 @@ int rtw_restruct_wmm_ie(struct adapter *adapter, u8 *in_ie, u8 *out_ie, uint in_
}
return ielength;
-
}
-/* */
-/* Ported from 8185: IsInPreAuthKeyList(). (Renamed from SecIsInPreAuthKeyList(), 2006-10-13.) */
-/* Added by Annie, 2006-05-07. */
-/* */
-/* Search by BSSID, */
-/* Return Value: */
-/* -1 :if there is no pre-auth key in the table */
-/* >= 0 :if there is pre-auth key, and return the entry id */
-/* */
-/* */
-
+/* Ported from 8185: IsInPreAuthKeyList().
+ * (Renamed from SecIsInPreAuthKeyList(), 2006-10-13.)
+ * Added by Annie, 2006-05-07.
+ *
+ * Search by BSSID,
+ *
+ * Return Value:
+ * -1: if there is no pre-auth key in the table
+ * >=0: if there is pre-auth key, and return the entry id
+ */
static int SecIsInPMKIDList(struct adapter *Adapter, u8 *bssid)
{
struct security_priv *p = &Adapter->securitypriv;
@@ -1990,6 +2071,40 @@ static int rtw_append_pmkid(struct adapter *Adapter, int iEntry, u8 *ie, uint ie
return ie_len;
}
+static void rtw_report_sec_ie(struct adapter *adapter, u8 authmode, u8 *sec_ie)
+{
+ uint len;
+ u8 *buff, *p, i;
+ union iwreq_data wrqu;
+
+ buff = NULL;
+ if (authmode == WLAN_EID_VENDOR_SPECIFIC) {
+ buff = rtw_zmalloc(IW_CUSTOM_MAX);
+ if (!buff)
+ return;
+
+ p = buff;
+
+ p += scnprintf(p, IW_CUSTOM_MAX - (p - buff), "ASSOCINFO(ReqIEs =");
+
+ len = sec_ie[1] + 2;
+ len = (len < IW_CUSTOM_MAX) ? len : IW_CUSTOM_MAX;
+
+ for (i = 0; i < len; i++)
+ p += scnprintf(p, IW_CUSTOM_MAX - (p - buff), "%02x", sec_ie[i]);
+
+ p += scnprintf(p, IW_CUSTOM_MAX - (p - buff), ")");
+
+ memset(&wrqu, 0, sizeof(wrqu));
+
+ wrqu.data.length = p - buff;
+
+ wrqu.data.length = (wrqu.data.length < IW_CUSTOM_MAX) ? wrqu.data.length : IW_CUSTOM_MAX;
+
+ kfree(buff);
+ }
+}
+
signed int rtw_restruct_sec_ie(struct adapter *adapter, u8 *in_ie, u8 *out_ie, uint in_len)
{
u8 authmode = 0x0;
@@ -2099,7 +2214,7 @@ void rtw_joinbss_reset(struct adapter *padapter)
u8 threshold;
struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
- struct ht_priv *phtpriv = &pmlmepriv->htpriv;
+ struct ht_priv *phtpriv = &pmlmepriv->htpriv;
/* todo: if you want to do something io/reg/hw setting before join_bss, please add code here */
@@ -2125,8 +2240,8 @@ void rtw_joinbss_reset(struct adapter *padapter)
void rtw_ht_use_default_setting(struct adapter *padapter)
{
- struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
- struct ht_priv *phtpriv = &pmlmepriv->htpriv;
+ struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
+ struct ht_priv *phtpriv = &pmlmepriv->htpriv;
struct registry_priv *pregistrypriv = &padapter->registrypriv;
bool bHwLDPCSupport = false, bHwSTBCSupport = false;
bool bHwSupportBeamformer = false, bHwSupportBeamformee = false;
@@ -2200,7 +2315,7 @@ unsigned int rtw_restructure_ht_ie(struct adapter *padapter, u8 *in_ie, u8 *out_
u8 cbw40_enable = 0, stbc_rx_enable = 0, operation_bw = 0;
struct registry_priv *pregistrypriv = &padapter->registrypriv;
struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
- struct ht_priv *phtpriv = &pmlmepriv->htpriv;
+ struct ht_priv *phtpriv = &pmlmepriv->htpriv;
struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
phtpriv->ht_option = false;
@@ -2311,7 +2426,6 @@ unsigned int rtw_restructure_ht_ie(struct adapter *padapter, u8 *in_ie, u8 *out_
}
return phtpriv->ht_option;
-
}
/* the function is > passive_level (in critical_section) */
@@ -2321,7 +2435,7 @@ void rtw_update_ht_cap(struct adapter *padapter, u8 *pie, uint ie_len, u8 channe
int len;
struct ieee80211_ht_cap *pht_capie;
struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
- struct ht_priv *phtpriv = &pmlmepriv->htpriv;
+ struct ht_priv *phtpriv = &pmlmepriv->htpriv;
struct registry_priv *pregistrypriv = &padapter->registrypriv;
struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
struct mlme_ext_info *pmlmeinfo = &pmlmeext->mlmext_info;
@@ -2346,7 +2460,6 @@ void rtw_update_ht_cap(struct adapter *padapter, u8 *pie, uint ie_len, u8 channe
max_ampdu_sz = 1 << (max_ampdu_sz+3); /* max_ampdu_sz (kbytes); */
phtpriv->rx_ampdu_maxlen = max_ampdu_sz;
-
}
len = 0;
@@ -2437,13 +2550,12 @@ void rtw_issue_addbareq_cmd(struct adapter *padapter, struct xmit_frame *pxmitfr
rtw_addbareq_cmd(padapter, (u8) priority, pattrib->ra);
}
}
-
}
void rtw_append_exented_cap(struct adapter *padapter, u8 *out_ie, uint *pout_len)
{
struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
- struct ht_priv *phtpriv = &pmlmepriv->htpriv;
+ struct ht_priv *phtpriv = &pmlmepriv->htpriv;
u8 cap_content[8] = {0};
if (phtpriv->bss_coexist)
@@ -2478,6 +2590,7 @@ void rtw_roaming(struct adapter *padapter, struct wlan_network *tgt_network)
_rtw_roaming(padapter, tgt_network);
spin_unlock_bh(&pmlmepriv->lock);
}
+
void _rtw_roaming(struct adapter *padapter, struct wlan_network *tgt_network)
{
struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
diff --git a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c
index bc980d21d50e..a897c433d2b0 100644
--- a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c
+++ b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c
@@ -374,6 +374,15 @@ static u8 init_channel_set(struct adapter *padapter, u8 ChannelPlan, struct rt_c
return chanset_size;
}
+static void init_mlme_ext_timer(struct adapter *padapter)
+{
+ struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
+
+ timer_setup(&pmlmeext->survey_timer, survey_timer_hdl, 0);
+ timer_setup(&pmlmeext->link_timer, link_timer_hdl, 0);
+ timer_setup(&pmlmeext->sa_query_timer, sa_query_timer_hdl, 0);
+}
+
void init_mlme_ext_priv(struct adapter *padapter)
{
struct registry_priv *pregistrypriv = &padapter->registrypriv;
@@ -937,10 +946,10 @@ unsigned int OnAssocReq(struct adapter *padapter, union recv_frame *precv_frame)
u16 capab_info;
struct rtw_ieee802_11_elems elems;
struct sta_info *pstat;
- unsigned char *p, *pos, *wpa_ie;
+ unsigned char *p, *pos, *wpa_ie;
unsigned char WMM_IE[] = {0x00, 0x50, 0xf2, 0x02, 0x00, 0x01};
int i, ie_len, wpa_ie_len, left;
- unsigned char supportRate[16];
+ unsigned char supportRate[16];
int supportRateNum;
unsigned short status = WLAN_STATUS_SUCCESS;
unsigned short frame_type, ie_offset = 0;
@@ -1122,9 +1131,6 @@ unsigned int OnAssocReq(struct adapter *padapter, union recv_frame *precv_frame)
if (!wpa_ie) {
if (elems.wps_ie) {
pstat->flags |= WLAN_STA_WPS;
- /* wpabuf_free(sta->wps_ie); */
- /* sta->wps_ie = wpabuf_alloc_copy(elems.wps_ie + 4, */
- /* elems.wps_ie_len - 4); */
} else {
pstat->flags |= WLAN_STA_MAYBE_WPS;
}
@@ -1502,11 +1508,12 @@ unsigned int OnDeAuth(struct adapter *padapter, union recv_frame *precv_frame)
return _SUCCESS;
}
- /* Commented by Albert 20130604 */
- /* Before sending the auth frame to start the STA/GC mode connection with AP/GO, */
- /* we will send the deauth first. */
- /* However, the Win8.1 with BRCM Wi-Fi will send the deauth with reason code 6 to us after receieving our deauth. */
- /* Added the following code to avoid this case. */
+ /* Commented by Albert 20130604
+ * Before sending the auth frame to start the STA/GC mode connection with AP/GO,
+ * we will send the deauth first.
+ * However, the Win8.1 with BRCM Wi-Fi will send the deauth with reason code 6 to us after receieving our deauth.
+ * Added the following code to avoid this case.
+ */
if ((pmlmeinfo->state & WIFI_FW_AUTH_STATE) ||
(pmlmeinfo->state & WIFI_FW_ASSOC_STATE)) {
if (reason == WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA) {
@@ -1626,8 +1633,8 @@ unsigned int OnAction_back(struct adapter *padapter, union recv_frame *precv_fra
u8 *addr;
struct sta_info *psta = NULL;
struct recv_reorder_ctrl *preorder_ctrl;
- unsigned char *frame_body;
- unsigned char category, action;
+ unsigned char *frame_body;
+ unsigned char category, action;
unsigned short tid, status;
struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
@@ -2259,10 +2266,10 @@ void issue_probersp(struct adapter *padapter, unsigned char *da, u8 is_valid_p2p
{
struct xmit_frame *pmgntframe;
struct pkt_attrib *pattrib;
- unsigned char *pframe;
+ unsigned char *pframe;
struct ieee80211_hdr *pwlanhdr;
__le16 *fctrl;
- unsigned char *mac, *bssid;
+ unsigned char *mac, *bssid;
struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
u8 *pwps_ie;
@@ -2456,12 +2463,12 @@ static int _issue_probereq(struct adapter *padapter,
int ret = _FAIL;
struct xmit_frame *pmgntframe;
struct pkt_attrib *pattrib;
- unsigned char *pframe;
+ unsigned char *pframe;
struct ieee80211_hdr *pwlanhdr;
__le16 *fctrl;
- unsigned char *mac;
- unsigned char bssrate[NumRates];
- struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
+ unsigned char *mac;
+ unsigned char bssrate[NumRates];
+ struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
struct mlme_priv *pmlmepriv = &(padapter->mlmepriv);
struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv);
int bssrate_len = 0;
@@ -2486,11 +2493,11 @@ static int _issue_probereq(struct adapter *padapter,
*(fctrl) = 0;
if (da) {
- /* unicast probe request frame */
+ /* unicast probe request frame */
memcpy(pwlanhdr->addr1, da, ETH_ALEN);
memcpy(pwlanhdr->addr3, da, ETH_ALEN);
} else {
- /* broadcast probe request frame */
+ /* broadcast probe request frame */
eth_broadcast_addr(pwlanhdr->addr1);
eth_broadcast_addr(pwlanhdr->addr3);
}
@@ -2584,13 +2591,13 @@ void issue_auth(struct adapter *padapter, struct sta_info *psta, unsigned short
{
struct xmit_frame *pmgntframe;
struct pkt_attrib *pattrib;
- unsigned char *pframe;
+ unsigned char *pframe;
struct ieee80211_hdr *pwlanhdr;
__le16 *fctrl;
unsigned int val32;
unsigned short val16;
int use_shared_key = 0;
- struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
+ struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv);
struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
__le16 le_tmp;
@@ -2841,14 +2848,14 @@ void issue_assocreq(struct adapter *padapter)
int ret = _FAIL;
struct xmit_frame *pmgntframe;
struct pkt_attrib *pattrib;
- unsigned char *pframe;
+ unsigned char *pframe;
struct ieee80211_hdr *pwlanhdr;
__le16 *fctrl;
__le16 val16;
unsigned int i, j, index = 0;
unsigned char bssrate[NumRates], sta_bssrate[NumRates];
struct ndis_80211_var_ie *pIE;
- struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
+ struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
struct mlme_priv *pmlmepriv = &(padapter->mlmepriv);
struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv);
struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
@@ -3018,7 +3025,7 @@ static int _issue_nulldata(struct adapter *padapter, unsigned char *da,
int ret = _FAIL;
struct xmit_frame *pmgntframe;
struct pkt_attrib *pattrib;
- unsigned char *pframe;
+ unsigned char *pframe;
struct ieee80211_hdr *pwlanhdr;
__le16 *fctrl;
struct xmit_priv *pxmitpriv;
@@ -3163,11 +3170,11 @@ static int _issue_qos_nulldata(struct adapter *padapter, unsigned char *da,
int ret = _FAIL;
struct xmit_frame *pmgntframe;
struct pkt_attrib *pattrib;
- unsigned char *pframe;
+ unsigned char *pframe;
struct ieee80211_hdr *pwlanhdr;
__le16 *fctrl;
u16 *qc;
- struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
+ struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv);
struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
@@ -3272,10 +3279,10 @@ static int _issue_deauth(struct adapter *padapter, unsigned char *da,
{
struct xmit_frame *pmgntframe;
struct pkt_attrib *pattrib;
- unsigned char *pframe;
+ unsigned char *pframe;
struct ieee80211_hdr *pwlanhdr;
__le16 *fctrl;
- struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
+ struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv);
struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
int ret = _FAIL;
@@ -3366,10 +3373,10 @@ void issue_action_SA_Query(struct adapter *padapter, unsigned char *raddr, unsig
u8 category = RTW_WLAN_CATEGORY_SA_QUERY;
struct xmit_frame *pmgntframe;
struct pkt_attrib *pattrib;
- u8 *pframe;
+ u8 *pframe;
struct ieee80211_hdr *pwlanhdr;
__le16 *fctrl;
- struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
+ struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv);
struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
__le16 le_tmp;
@@ -3439,15 +3446,15 @@ void issue_action_BA(struct adapter *padapter, unsigned char *raddr, unsigned ch
enum ieee80211_max_ampdu_length_exp max_rx_ampdu_factor;
struct xmit_frame *pmgntframe;
struct pkt_attrib *pattrib;
- u8 *pframe;
+ u8 *pframe;
struct ieee80211_hdr *pwlanhdr;
__le16 *fctrl;
- struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
+ struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv);
struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
- struct sta_info *psta;
- struct sta_priv *pstapriv = &padapter->stapriv;
- struct registry_priv *pregpriv = &padapter->registrypriv;
+ struct sta_info *psta;
+ struct sta_priv *pstapriv = &padapter->stapriv;
+ struct registry_priv *pregpriv = &padapter->registrypriv;
__le16 le_tmp;
pmgntframe = alloc_mgtxmitframe(pxmitpriv);
@@ -3585,11 +3592,11 @@ static void issue_action_BSSCoexistPacket(struct adapter *padapter)
unsigned char category, action;
struct xmit_frame *pmgntframe;
struct pkt_attrib *pattrib;
- unsigned char *pframe;
+ unsigned char *pframe;
struct ieee80211_hdr *pwlanhdr;
__le16 *fctrl;
struct wlan_network *pnetwork = NULL;
- struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
+ struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv);
struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
@@ -3798,7 +3805,7 @@ Following are some utility functions for WiFi MLME
void site_survey(struct adapter *padapter)
{
- unsigned char survey_channel = 0, val8;
+ unsigned char survey_channel = 0, val8;
enum rt_scan_type ScanType = SCAN_PASSIVE;
struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
@@ -3867,7 +3874,7 @@ void site_survey(struct adapter *padapter)
set_survey_timer(pmlmeext, channel_scan_time_ms);
} else {
- /* channel number is 0 or this channel is not valid. */
+ /* channel number is 0 or this channel is not valid. */
{
pmlmeext->sitesurvey_res.state = SCAN_COMPLETE;
@@ -4144,12 +4151,13 @@ void start_clnt_join(struct adapter *padapter)
rtw_hal_set_hwreg(padapter, HW_VAR_SEC_CFG, (u8 *)(&val8));
- /* Because of AP's not receiving deauth before */
- /* AP may: 1)not response auth or 2)deauth us after link is complete */
- /* issue deauth before issuing auth to deal with the situation */
-
- /* Commented by Albert 2012/07/21 */
- /* For the Win8 P2P connection, it will be hard to have a successful connection if this Wi-Fi doesn't connect to it. */
+ /* Because of AP's not receiving deauth before
+ * AP may: 1)not response auth or 2)deauth us after link is complete
+ * issue deauth before issuing auth to deal with the situation
+ *
+ * Commented by Albert 2012/07/21
+ * For the Win8 P2P connection, it will be hard to have a successful connection if this Wi-Fi doesn't connect to it.
+ */
{
/* To avoid connecting to AP fail during resume process, change retry count from 5 to 1 */
issue_deauth_ex(padapter, pnetwork->mac_address, WLAN_REASON_DEAUTH_LEAVING, 1, 100);
@@ -4322,7 +4330,6 @@ static void process_80211d(struct adapter *padapter, struct wlan_bssid_ex *bssid
k++;
} else if (chplan_sta[i].ChannelNum < chplan_ap.Channel[j]) {
chplan_new[k].ChannelNum = chplan_sta[i].ChannelNum;
-/* chplan_new[k].ScanType = chplan_sta[i].ScanType; */
chplan_new[k].ScanType = SCAN_PASSIVE;
i++;
k++;
@@ -4340,7 +4347,6 @@ static void process_80211d(struct adapter *padapter, struct wlan_bssid_ex *bssid
(chplan_sta[i].ChannelNum <= 14)) {
chplan_new[k].ChannelNum = chplan_sta[i].ChannelNum;
-/* chplan_new[k].ScanType = chplan_sta[i].ScanType; */
chplan_new[k].ScanType = SCAN_PASSIVE;
i++;
k++;
@@ -4460,7 +4466,7 @@ void report_surveydone_event(struct adapter *padapter)
u32 cmdsz;
struct surveydone_event *psurveydone_evt;
struct C2HEvent_Header *pc2h_evt_hdr;
- struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
+ struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
struct cmd_priv *pcmdpriv = &padapter->cmdpriv;
pcmd_obj = rtw_zmalloc(sizeof(struct cmd_obj));
@@ -4504,7 +4510,7 @@ void report_join_res(struct adapter *padapter, int res)
u32 cmdsz;
struct joinbss_event *pjoinbss_evt;
struct C2HEvent_Header *pc2h_evt_hdr;
- struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
+ struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
struct cmd_priv *pcmdpriv = &padapter->cmdpriv;
@@ -4554,7 +4560,7 @@ void report_wmm_edca_update(struct adapter *padapter)
u32 cmdsz;
struct wmm_event *pwmm_event;
struct C2HEvent_Header *pc2h_evt_hdr;
- struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
+ struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
struct cmd_priv *pcmdpriv = &padapter->cmdpriv;
pcmd_obj = rtw_zmalloc(sizeof(struct cmd_obj));
@@ -4600,7 +4606,7 @@ void report_del_sta_event(struct adapter *padapter, unsigned char *MacAddr, unsi
int mac_id;
struct stadel_event *pdel_sta_evt;
struct C2HEvent_Header *pc2h_evt_hdr;
- struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
+ struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
struct cmd_priv *pcmdpriv = &padapter->cmdpriv;
pcmd_obj = rtw_zmalloc(sizeof(struct cmd_obj));
@@ -4651,7 +4657,7 @@ void report_add_sta_event(struct adapter *padapter, unsigned char *MacAddr, int
u32 cmdsz;
struct stassoc_event *padd_sta_evt;
struct C2HEvent_Header *pc2h_evt_hdr;
- struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
+ struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
struct cmd_priv *pcmdpriv = &padapter->cmdpriv;
pcmd_obj = rtw_zmalloc(sizeof(struct cmd_obj));
@@ -4755,7 +4761,7 @@ void update_sta_info(struct adapter *padapter, struct sta_info *psta)
static void rtw_mlmeext_disconnect(struct adapter *padapter)
{
- struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
+ struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
struct wlan_bssid_ex *pnetwork = (struct wlan_bssid_ex *)(&(pmlmeinfo->network));
@@ -4805,7 +4811,7 @@ void mlmeext_joinbss_event_callback(struct adapter *padapter, int join_res)
struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
struct wlan_bssid_ex *cur_network = &(pmlmeinfo->network);
- struct sta_priv *pstapriv = &padapter->stapriv;
+ struct sta_priv *pstapriv = &padapter->stapriv;
u8 join_type;
struct sta_info *psta;
@@ -4983,11 +4989,11 @@ static u8 chk_ap_is_alive(struct adapter *padapter, struct sta_info *psta)
void linked_status_chk(struct adapter *padapter)
{
u32 i;
- struct sta_info *psta;
- struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
+ struct sta_info *psta;
+ struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);
struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
- struct sta_priv *pstapriv = &padapter->stapriv;
+ struct sta_priv *pstapriv = &padapter->stapriv;
if (is_client_associated_to_ap(padapter)) {
@@ -5091,8 +5097,8 @@ void survey_timer_hdl(struct timer_list *t)
timer_container_of(padapter, t, mlmeextpriv.survey_timer);
struct cmd_obj *ph2c;
struct sitesurvey_parm *psurveyPara;
- struct cmd_priv *pcmdpriv = &padapter->cmdpriv;
- struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
+ struct cmd_priv *pcmdpriv = &padapter->cmdpriv;
+ struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
/* issue rtw_sitesurvey_cmd */
if (pmlmeext->sitesurvey_res.state > SCAN_START) {
@@ -5124,12 +5130,8 @@ void link_timer_hdl(struct timer_list *t)
{
struct adapter *padapter =
timer_container_of(padapter, t, mlmeextpriv.link_timer);
- /* static unsigned int rx_pkt = 0; */
- /* static u64 tx_cnt = 0; */
- /* struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); */
struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
- /* struct sta_priv *pstapriv = &padapter->stapriv; */
if (pmlmeinfo->state & WIFI_FW_AUTH_NULL) {
diff --git a/drivers/staging/rtl8723bs/core/rtw_pwrctrl.c b/drivers/staging/rtl8723bs/core/rtw_pwrctrl.c
index 6a2583d0d3eb..7b643ac320f0 100644
--- a/drivers/staging/rtl8723bs/core/rtw_pwrctrl.c
+++ b/drivers/staging/rtl8723bs/core/rtw_pwrctrl.c
@@ -430,10 +430,7 @@ s32 LPS_RF_ON_check(struct adapter *padapter, u32 delay_ms)
return err;
}
-/* */
-/* Description: */
-/* Enter the leisure power save mode. */
-/* */
+/* Description: Enter the leisure power save mode. */
void LPS_Enter(struct adapter *padapter, const char *msg)
{
struct dvobj_priv *dvobj = adapter_to_dvobj(padapter);
@@ -466,10 +463,7 @@ void LPS_Enter(struct adapter *padapter, const char *msg)
}
}
-/* */
-/* Description: */
-/* Leave the leisure power save mode. */
-/* */
+/* Description: Leave the leisure power save mode. */
void LPS_Leave(struct adapter *padapter, const char *msg)
{
#define LPS_LEAVE_TIMEOUT_MS 100
diff --git a/drivers/staging/rtl8723bs/core/rtw_recv.c b/drivers/staging/rtl8723bs/core/rtw_recv.c
index 8ae527b6e0d6..e893cb6fa273 100644
--- a/drivers/staging/rtl8723bs/core/rtw_recv.c
+++ b/drivers/staging/rtl8723bs/core/rtw_recv.c
@@ -66,7 +66,8 @@ signed int _rtw_init_recv_priv(struct recv_priv *precvpriv, struct adapter *pada
list_add_tail(&(precvframe->u.list), &(precvpriv->free_recv_queue.queue));
- rtw_os_recv_resource_alloc(padapter, precvframe);
+ precvframe->u.hdr.pkt_newalloc = NULL;
+ precvframe->u.hdr.pkt = NULL;
precvframe->u.hdr.len = 0;
@@ -90,11 +91,22 @@ exit:
void _rtw_free_recv_priv(struct recv_priv *precvpriv)
{
+ signed int i;
+ union recv_frame *precvframe;
struct adapter *padapter = precvpriv->adapter;
rtw_free_uc_swdec_pending_queue(padapter);
- rtw_os_recv_resource_free(precvpriv);
+ precvframe = (union recv_frame *)precvpriv->precv_frame_buf;
+
+ for (i = 0; i < NR_RECVFRAME; i++) {
+ if (precvframe->u.hdr.pkt) {
+ /* free skb by driver */
+ dev_kfree_skb_any(precvframe->u.hdr.pkt);
+ precvframe->u.hdr.pkt = NULL;
+ }
+ precvframe++;
+ }
vfree(precvpriv->pallocated_frame_buf);
@@ -147,8 +159,10 @@ int rtw_free_recvframe(union recv_frame *precvframe, struct __queue *pfree_recv_
struct adapter *padapter = precvframe->u.hdr.adapter;
struct recv_priv *precvpriv = &padapter->recvpriv;
- rtw_os_free_recvframe(precvframe);
-
+ if (precvframe->u.hdr.pkt) {
+ dev_kfree_skb_any(precvframe->u.hdr.pkt);/* free skb by driver */
+ precvframe->u.hdr.pkt = NULL;
+ }
spin_lock_bh(&pfree_recv_queue->lock);
@@ -294,6 +308,50 @@ struct recv_buf *rtw_dequeue_recvbuf(struct __queue *queue)
}
+static void rtw_handle_tkip_mic_err(struct adapter *padapter, u8 bgroup)
+{
+ enum nl80211_key_type key_type = 0;
+ union iwreq_data wrqu;
+ struct iw_michaelmicfailure ev;
+ struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
+ struct security_priv *psecuritypriv = &padapter->securitypriv;
+ unsigned long cur_time = 0;
+
+ if (psecuritypriv->last_mic_err_time == 0) {
+ psecuritypriv->last_mic_err_time = jiffies;
+ } else {
+ cur_time = jiffies;
+
+ if (cur_time - psecuritypriv->last_mic_err_time < 60*HZ) {
+ psecuritypriv->btkip_countermeasure = true;
+ psecuritypriv->last_mic_err_time = 0;
+ psecuritypriv->btkip_countermeasure_time = cur_time;
+ } else {
+ psecuritypriv->last_mic_err_time = jiffies;
+ }
+ }
+
+ if (bgroup)
+ key_type |= NL80211_KEYTYPE_GROUP;
+ else
+ key_type |= NL80211_KEYTYPE_PAIRWISE;
+
+ cfg80211_michael_mic_failure(padapter->pnetdev, (u8 *)&pmlmepriv->assoc_bssid[0], key_type, -1,
+ NULL, GFP_ATOMIC);
+
+ memset(&ev, 0x00, sizeof(ev));
+ if (bgroup)
+ ev.flags |= IW_MICFAILURE_GROUP;
+ else
+ ev.flags |= IW_MICFAILURE_PAIRWISE;
+
+ ev.src_addr.sa_family = ARPHRD_ETHER;
+ memcpy(ev.src_addr.sa_data, &pmlmepriv->assoc_bssid[0], ETH_ALEN);
+
+ memset(&wrqu, 0x00, sizeof(wrqu));
+ wrqu.data.length = sizeof(ev);
+}
+
static signed int recvframe_chkmic(struct adapter *adapter, union recv_frame *precvframe)
{
@@ -1564,6 +1622,93 @@ static signed int wlanhdr_to_ethhdr(union recv_frame *precvframe)
return _SUCCESS;
}
+static struct sk_buff *rtw_alloc_msdu_pkt(union recv_frame *prframe, u16 nSubframe_Length, u8 *pdata)
+{
+ u16 eth_type;
+ struct sk_buff *sub_skb;
+ struct rx_pkt_attrib *pattrib;
+
+ pattrib = &prframe->u.hdr.attrib;
+
+ sub_skb = rtw_skb_alloc(nSubframe_Length + 12);
+ if (!sub_skb)
+ return NULL;
+
+ skb_reserve(sub_skb, 12);
+ skb_put_data(sub_skb, (pdata + ETH_HLEN), nSubframe_Length);
+
+ eth_type = get_unaligned_be16(&sub_skb->data[6]);
+
+ if (sub_skb->len >= 8 &&
+ ((!memcmp(sub_skb->data, rfc1042_header, SNAP_SIZE) &&
+ eth_type != ETH_P_AARP && eth_type != ETH_P_IPX) ||
+ !memcmp(sub_skb->data, bridge_tunnel_header, SNAP_SIZE))) {
+ /*
+ * remove RFC1042 or Bridge-Tunnel encapsulation and replace
+ * EtherType
+ */
+ skb_pull(sub_skb, SNAP_SIZE);
+ memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->src, ETH_ALEN);
+ memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->dst, ETH_ALEN);
+ } else {
+ __be16 len;
+ /* Leave Ethernet header part of hdr and full payload */
+ len = htons(sub_skb->len);
+ memcpy(skb_push(sub_skb, 2), &len, 2);
+ memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->src, ETH_ALEN);
+ memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->dst, ETH_ALEN);
+ }
+
+ return sub_skb;
+}
+
+static void rtw_recv_indicate_pkt(struct adapter *padapter, struct sk_buff *pkt, struct rx_pkt_attrib *pattrib)
+{
+ struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
+
+ /* Indicate the packets to upper layer */
+ if (pkt) {
+ if (check_fwstate(pmlmepriv, WIFI_AP_STATE) == true) {
+ struct sk_buff *pskb2 = NULL;
+ struct sta_info *psta = NULL;
+ struct sta_priv *pstapriv = &padapter->stapriv;
+ int bmcast = is_multicast_ether_addr(pattrib->dst);
+
+ if (memcmp(pattrib->dst, myid(&padapter->eeprompriv), ETH_ALEN)) {
+ if (bmcast) {
+ psta = rtw_get_bcmc_stainfo(padapter);
+ pskb2 = skb_clone(pkt, GFP_ATOMIC);
+ } else {
+ psta = rtw_get_stainfo(pstapriv, pattrib->dst);
+ }
+
+ if (psta) {
+ struct net_device *pnetdev = (struct net_device *)padapter->pnetdev;
+ /* skb->ip_summed = CHECKSUM_NONE; */
+ pkt->dev = pnetdev;
+ skb_set_queue_mapping(pkt, rtw_recv_select_queue(pkt));
+
+ _rtw_xmit_entry(pkt, pnetdev);
+
+ if (bmcast && pskb2)
+ pkt = pskb2;
+ else
+ return;
+ }
+ } else {
+ /* to APself */
+ }
+ }
+
+ pkt->protocol = eth_type_trans(pkt, padapter->pnetdev);
+ pkt->dev = padapter->pnetdev;
+
+ pkt->ip_summed = CHECKSUM_NONE;
+
+ rtw_netif_rx(padapter->pnetdev, pkt);
+ }
+}
+
static int amsdu_to_msdu(struct adapter *padapter, union recv_frame *prframe)
{
int a_len, padding_len;
@@ -1593,7 +1738,7 @@ static int amsdu_to_msdu(struct adapter *padapter, union recv_frame *prframe)
if (a_len < ETH_HLEN + nSubframe_Length)
break;
- sub_pkt = rtw_os_alloc_msdu_pkt(prframe, nSubframe_Length, pdata);
+ sub_pkt = rtw_alloc_msdu_pkt(prframe, nSubframe_Length, pdata);
if (!sub_pkt)
break;
@@ -1626,7 +1771,7 @@ static int amsdu_to_msdu(struct adapter *padapter, union recv_frame *prframe)
/* Indicate the packets to upper layer */
if (sub_pkt)
- rtw_os_recv_indicate_pkt(padapter, sub_pkt, &prframe->u.hdr.attrib);
+ rtw_recv_indicate_pkt(padapter, sub_pkt, &prframe->u.hdr.attrib);
}
prframe->u.hdr.len = 0;
@@ -1725,6 +1870,43 @@ static void recv_indicatepkts_pkt_loss_cnt(struct debug_priv *pdbgpriv, u64 prev
}
+static int rtw_recv_indicatepkt(struct adapter *padapter, union recv_frame *precv_frame)
+{
+ struct recv_priv *precvpriv;
+ struct __queue *pfree_recv_queue;
+ struct sk_buff *skb;
+ struct rx_pkt_attrib *pattrib = &precv_frame->u.hdr.attrib;
+
+ precvpriv = &(padapter->recvpriv);
+ pfree_recv_queue = &(precvpriv->free_recv_queue);
+
+ skb = precv_frame->u.hdr.pkt;
+ if (!skb)
+ goto _recv_indicatepkt_drop;
+
+ skb->data = precv_frame->u.hdr.rx_data;
+
+ skb_set_tail_pointer(skb, precv_frame->u.hdr.len);
+
+ skb->len = precv_frame->u.hdr.len;
+
+ rtw_recv_indicate_pkt(padapter, skb, pattrib);
+
+ /* pointers to NULL before rtw_free_recvframe() */
+ precv_frame->u.hdr.pkt = NULL;
+
+ rtw_free_recvframe(precv_frame, pfree_recv_queue);
+
+ return _SUCCESS;
+
+_recv_indicatepkt_drop:
+
+ /* enqueue back to free_recv_queue */
+ rtw_free_recvframe(precv_frame, pfree_recv_queue);
+
+ return _FAIL;
+}
+
static int recv_indicatepkts_in_order(struct adapter *padapter, struct recv_reorder_ctrl *preorder_ctrl, int bforced)
{
struct list_head *phead, *plist;
diff --git a/drivers/staging/rtl8723bs/core/rtw_security.c b/drivers/staging/rtl8723bs/core/rtw_security.c
index e9f382c280d9..3d99d045f4b6 100644
--- a/drivers/staging/rtl8723bs/core/rtw_security.c
+++ b/drivers/staging/rtl8723bs/core/rtw_security.c
@@ -7,6 +7,7 @@
#include <linux/crc32.h>
#include <drv_types.h>
#include <crypto/aes.h>
+#include <crypto/utils.h>
static const char * const _security_type_str[] = {
"N/A",
@@ -637,37 +638,6 @@ exit:
#define MAX_MSG_SIZE 2048
-/*****************************/
-/**** Function Prototypes ****/
-/*****************************/
-
-static void bitwise_xor(u8 *ina, u8 *inb, u8 *out);
-static void construct_mic_iv(u8 *mic_header1,
- signed int qc_exists,
- signed int a4_exists,
- u8 *mpdu,
- uint payload_length,
- u8 *pn_vector,
- uint frtype); /* add for CONFIG_IEEE80211W, none 11w also can use */
-static void construct_mic_header1(u8 *mic_header1,
- signed int header_length,
- u8 *mpdu,
- uint frtype); /* for CONFIG_IEEE80211W, none 11w also can use */
-static void construct_mic_header2(u8 *mic_header2,
- u8 *mpdu,
- signed int a4_exists,
- signed int qc_exists);
-static void construct_ctr_preload(u8 *ctr_preload,
- signed int a4_exists,
- signed int qc_exists,
- u8 *mpdu,
- u8 *pn_vector,
- signed int c,
- uint frtype); /* for CONFIG_IEEE80211W, none 11w also can use */
-
-static void aes128k128d(u8 *key, u8 *data, u8 *ciphertext);
-
-
/****************************************/
/* aes128k128d() */
/* Performs a 128 bit AES encrypt with */
@@ -849,18 +819,6 @@ static void construct_ctr_preload(u8 *ctr_preload,
ctr_preload[15] = (unsigned char) (c % 256);
}
-/************************************/
-/* bitwise_xor() */
-/* A 128 bit, bitwise exclusive or */
-/************************************/
-static void bitwise_xor(u8 *ina, u8 *inb, u8 *out)
-{
- signed int i;
-
- for (i = 0; i < 16; i++)
- out[i] = ina[i] ^ inb[i];
-}
-
static signed int aes_cipher(u8 *key, uint hdrlen,
u8 *pframe, uint plen)
{
@@ -941,13 +899,13 @@ static signed int aes_cipher(u8 *key, uint hdrlen,
/* Calculate MIC */
aes128k128d(key, mic_iv, aes_out);
- bitwise_xor(aes_out, mic_header1, chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, mic_header1, 16);
aes128k128d(key, chain_buffer, aes_out);
- bitwise_xor(aes_out, mic_header2, chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, mic_header2, 16);
aes128k128d(key, chain_buffer, aes_out);
for (i = 0; i < num_blocks; i++) {
- bitwise_xor(aes_out, &pframe[payload_index], chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, &pframe[payload_index], 16);
payload_index += 16;
aes128k128d(key, chain_buffer, aes_out);
@@ -960,7 +918,7 @@ static signed int aes_cipher(u8 *key, uint hdrlen,
for (j = 0; j < payload_remainder; j++)
padded_buffer[j] = pframe[payload_index++];
- bitwise_xor(aes_out, padded_buffer, chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, padded_buffer, 16);
aes128k128d(key, chain_buffer, aes_out);
}
@@ -977,7 +935,7 @@ static signed int aes_cipher(u8 *key, uint hdrlen,
pn_vector, i+1, frtype);
/* add for CONFIG_IEEE80211W, none 11w also can use */
aes128k128d(key, ctr_preload, aes_out);
- bitwise_xor(aes_out, &pframe[payload_index], chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, &pframe[payload_index], 16);
for (j = 0; j < 16; j++)
pframe[payload_index++] = chain_buffer[j];
}
@@ -995,7 +953,7 @@ static signed int aes_cipher(u8 *key, uint hdrlen,
padded_buffer[j] = pframe[payload_index+j];
aes128k128d(key, ctr_preload, aes_out);
- bitwise_xor(aes_out, padded_buffer, chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, padded_buffer, 16);
for (j = 0; j < payload_remainder; j++)
pframe[payload_index++] = chain_buffer[j];
}
@@ -1011,7 +969,7 @@ static signed int aes_cipher(u8 *key, uint hdrlen,
padded_buffer[j] = pframe[j+hdrlen+8+plen];
aes128k128d(key, ctr_preload, aes_out);
- bitwise_xor(aes_out, padded_buffer, chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, padded_buffer, 16);
for (j = 0; j < 8; j++)
pframe[payload_index++] = chain_buffer[j];
@@ -1137,7 +1095,7 @@ static signed int aes_decipher(u8 *key, uint hdrlen,
frtype); /* add for CONFIG_IEEE80211W, none 11w also can use */
aes128k128d(key, ctr_preload, aes_out);
- bitwise_xor(aes_out, &pframe[payload_index], chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, &pframe[payload_index], 16);
for (j = 0; j < 16; j++)
pframe[payload_index++] = chain_buffer[j];
@@ -1156,7 +1114,7 @@ static signed int aes_decipher(u8 *key, uint hdrlen,
padded_buffer[j] = pframe[payload_index+j];
aes128k128d(key, ctr_preload, aes_out);
- bitwise_xor(aes_out, padded_buffer, chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, padded_buffer, 16);
for (j = 0; j < payload_remainder; j++)
pframe[payload_index++] = chain_buffer[j];
}
@@ -1187,13 +1145,13 @@ static signed int aes_decipher(u8 *key, uint hdrlen,
/* Calculate MIC */
aes128k128d(key, mic_iv, aes_out);
- bitwise_xor(aes_out, mic_header1, chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, mic_header1, 16);
aes128k128d(key, chain_buffer, aes_out);
- bitwise_xor(aes_out, mic_header2, chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, mic_header2, 16);
aes128k128d(key, chain_buffer, aes_out);
for (i = 0; i < num_blocks; i++) {
- bitwise_xor(aes_out, &message[payload_index], chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, &message[payload_index], 16);
payload_index += 16;
aes128k128d(key, chain_buffer, aes_out);
@@ -1206,7 +1164,7 @@ static signed int aes_decipher(u8 *key, uint hdrlen,
for (j = 0; j < payload_remainder; j++)
padded_buffer[j] = message[payload_index++];
- bitwise_xor(aes_out, padded_buffer, chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, padded_buffer, 16);
aes128k128d(key, chain_buffer, aes_out);
}
@@ -1223,7 +1181,7 @@ static signed int aes_decipher(u8 *key, uint hdrlen,
frtype);
/* add for CONFIG_IEEE80211W, none 11w also can use */
aes128k128d(key, ctr_preload, aes_out);
- bitwise_xor(aes_out, &message[payload_index], chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, &message[payload_index], 16);
for (j = 0; j < 16; j++)
message[payload_index++] = chain_buffer[j];
}
@@ -1241,7 +1199,7 @@ static signed int aes_decipher(u8 *key, uint hdrlen,
padded_buffer[j] = message[payload_index+j];
aes128k128d(key, ctr_preload, aes_out);
- bitwise_xor(aes_out, padded_buffer, chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, padded_buffer, 16);
for (j = 0; j < payload_remainder; j++)
message[payload_index++] = chain_buffer[j];
}
@@ -1256,7 +1214,7 @@ static signed int aes_decipher(u8 *key, uint hdrlen,
padded_buffer[j] = message[j+hdrlen+8+plen-8];
aes128k128d(key, ctr_preload, aes_out);
- bitwise_xor(aes_out, padded_buffer, chain_buffer);
+ crypto_xor_cpy(chain_buffer, aes_out, padded_buffer, 16);
for (j = 0; j < 8; j++)
message[payload_index++] = chain_buffer[j];
@@ -1405,7 +1363,7 @@ u32 rtw_BIP_verify(struct adapter *padapter, u8 *precvframe)
ClearPwrMgt(BIP_AAD);
ClearMData(BIP_AAD);
/* conscruct AAD, copy address 1 to address 3 */
- memcpy(BIP_AAD+2, pwlanhdr->addr1, 18);
+ memcpy(BIP_AAD + 2, &pwlanhdr->addrs, sizeof(pwlanhdr->addrs));
if (omac1_aes_128(padapter->securitypriv.dot11wBIPKey[padapter->securitypriv.dot11wBIPKeyid].skey
, BIP_AAD, ori_len, mic))
diff --git a/drivers/staging/rtl8723bs/core/rtw_sta_mgt.c b/drivers/staging/rtl8723bs/core/rtw_sta_mgt.c
index 1d2b53c76afc..d1f6030799cb 100644
--- a/drivers/staging/rtl8723bs/core/rtw_sta_mgt.c
+++ b/drivers/staging/rtl8723bs/core/rtw_sta_mgt.c
@@ -229,7 +229,7 @@ struct sta_info *rtw_alloc_stainfo(struct sta_priv *pstapriv, u8 *hwaddr)
for (i = 0; i < 16; i++)
memcpy(&psta->sta_recvpriv.rxcache.tid_rxseq[i], &wRxSeqInitialValue, 2);
- init_addba_retry_timer(pstapriv->padapter, psta);
+ timer_setup(&psta->addba_retry_timer, addba_timer_hdl, 0);
/* for A-MPDU Rx reordering buffer control */
for (i = 0; i < 16 ; i++) {
@@ -247,7 +247,9 @@ struct sta_info *rtw_alloc_stainfo(struct sta_priv *pstapriv, u8 *hwaddr)
INIT_LIST_HEAD(&preorder_ctrl->pending_recvframe_queue.queue);
spin_lock_init(&preorder_ctrl->pending_recvframe_queue.lock);
- rtw_init_recv_timer(preorder_ctrl);
+ /* init recv timer */
+ timer_setup(&preorder_ctrl->reordering_ctrl_timer,
+ rtw_reordering_ctrl_timeout_handler, 0);
}
/* init for DM */
diff --git a/drivers/staging/rtl8723bs/core/rtw_wlan_util.c b/drivers/staging/rtl8723bs/core/rtw_wlan_util.c
index 0c6072d08661..1def9758852c 100644
--- a/drivers/staging/rtl8723bs/core/rtw_wlan_util.c
+++ b/drivers/staging/rtl8723bs/core/rtw_wlan_util.c
@@ -170,10 +170,10 @@ void get_rate_set(struct adapter *padapter, unsigned char *pbssrate, int *bssrat
void set_mcs_rate_by_mask(u8 *mcs_set, u32 mask)
{
- u8 mcs_rate_1r = (u8)(mask&0xff);
- u8 mcs_rate_2r = (u8)((mask>>8)&0xff);
- u8 mcs_rate_3r = (u8)((mask>>16)&0xff);
- u8 mcs_rate_4r = (u8)((mask>>24)&0xff);
+ u8 mcs_rate_1r = (u8)(mask & 0xff);
+ u8 mcs_rate_2r = (u8)((mask >> 8) & 0xff);
+ u8 mcs_rate_3r = (u8)((mask >> 16) & 0xff);
+ u8 mcs_rate_4r = (u8)((mask >> 24) & 0xff);
mcs_set[0] &= mcs_rate_1r;
mcs_set[1] &= mcs_rate_2r;
@@ -267,21 +267,21 @@ inline void rtw_set_oper_ch(struct adapter *adapter, u8 ch)
dvobj->on_oper_ch_time = jiffies;
#ifdef DBG_CH_SWITCH
- cnt += scnprintf(msg+cnt, len-cnt, "switch to ch %3u", ch);
+ cnt += scnprintf(msg + cnt, len - cnt, "switch to ch %3u", ch);
for (i = 0; i < dvobj->iface_nums; i++) {
struct adapter *iface = dvobj->padapters[i];
- cnt += scnprintf(msg+cnt, len-cnt, " [%s:", ADPT_ARG(iface));
+ cnt += scnprintf(msg + cnt, len - cnt, " [%s:", ADPT_ARG(iface));
if (iface->mlmeextpriv.cur_channel == ch)
- cnt += scnprintf(msg+cnt, len-cnt, "C");
+ cnt += scnprintf(msg + cnt, len - cnt, "C");
else
- cnt += scnprintf(msg+cnt, len-cnt, "_");
+ cnt += scnprintf(msg + cnt, len - cnt, "_");
if (iface->wdinfo.listen_channel == ch && !rtw_p2p_chk_state(&iface->wdinfo, P2P_STATE_NONE))
- cnt += scnprintf(msg+cnt, len-cnt, "L");
+ cnt += scnprintf(msg + cnt, len - cnt, "L");
else
- cnt += scnprintf(msg+cnt, len-cnt, "_");
- cnt += scnprintf(msg+cnt, len-cnt, "]");
+ cnt += scnprintf(msg + cnt, len - cnt, "_");
+ cnt += scnprintf(msg + cnt, len - cnt, "]");
}
#endif /* DBG_CH_SWITCH */
@@ -381,7 +381,7 @@ int is_client_associated_to_ap(struct adapter *padapter)
pmlmeext = &padapter->mlmeextpriv;
pmlmeinfo = &(pmlmeext->mlmext_info);
- if ((pmlmeinfo->state & WIFI_FW_ASSOC_SUCCESS) && ((pmlmeinfo->state&0x03) == WIFI_FW_STATION_STATE))
+ if ((pmlmeinfo->state & WIFI_FW_ASSOC_SUCCESS) && ((pmlmeinfo->state & 0x03) == WIFI_FW_STATION_STATE))
return true;
else
return _FAIL;
@@ -392,7 +392,7 @@ int is_client_associated_to_ibss(struct adapter *padapter)
struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
- if ((pmlmeinfo->state & WIFI_FW_ASSOC_SUCCESS) && ((pmlmeinfo->state&0x03) == WIFI_FW_ADHOC_STATE))
+ if ((pmlmeinfo->state & WIFI_FW_ASSOC_SUCCESS) && ((pmlmeinfo->state & 0x03) == WIFI_FW_ADHOC_STATE))
return true;
else
return _FAIL;
@@ -431,7 +431,7 @@ void invalidate_cam_all(struct adapter *padapter)
spin_lock_bh(&cam_ctl->lock);
cam_ctl->bitmap = 0;
- memset(dvobj->cam_cache, 0, sizeof(struct cam_entry_cache)*TOTAL_CAM_ENTRY);
+ memset(dvobj->cam_cache, 0, sizeof(struct cam_entry_cache) * TOTAL_CAM_ENTRY);
spin_unlock_bh(&cam_ctl->lock);
}
@@ -453,7 +453,7 @@ void _write_cam(struct adapter *padapter, u8 entry, u16 ctrl, u8 *mac, u8 *key)
break;
default:
i = (j - 2) << 2;
- val = (key[i] | (key[i+1] << 8) | (key[i+2] << 16) | (key[i+3] << 24));
+ val = (key[i] | (key[i + 1] << 8) | (key[i + 2] << 16) | (key[i + 3] << 24));
break;
}
@@ -522,7 +522,7 @@ static bool _rtw_camid_is_gk(struct adapter *adapter, u8 cam_id)
if (!(cam_ctl->bitmap & BIT(cam_id)))
goto exit;
- ret = (dvobj->cam_cache[cam_id].ctrl&BIT6)?true:false;
+ ret = (dvobj->cam_cache[cam_id].ctrl & BIT6) ? true : false;
exit:
return ret;
@@ -537,7 +537,7 @@ static s16 _rtw_camid_search(struct adapter *adapter, u8 *addr, s16 kid)
for (i = 0; i < TOTAL_CAM_ENTRY; i++) {
if (addr && memcmp(dvobj->cam_cache[i].mac, addr, ETH_ALEN))
continue;
- if (kid >= 0 && kid != (dvobj->cam_cache[i].ctrl&0x03))
+ if (kid >= 0 && kid != (dvobj->cam_cache[i].ctrl & 0x03))
continue;
cam_id = i;
@@ -571,7 +571,7 @@ s16 rtw_camid_alloc(struct adapter *adapter, struct sta_info *sta, u8 kid)
mlmeinfo = &adapter->mlmeextpriv.mlmext_info;
- if ((((mlmeinfo->state&0x03) == WIFI_FW_AP_STATE) || ((mlmeinfo->state&0x03) == WIFI_FW_ADHOC_STATE))
+ if ((((mlmeinfo->state & 0x03) == WIFI_FW_AP_STATE) || ((mlmeinfo->state & 0x03) == WIFI_FW_ADHOC_STATE))
&& !sta) {
/* AP/Ad-hoc mode group key: static alloction to default key by key ID */
if (kid > 3) {
@@ -585,7 +585,7 @@ s16 rtw_camid_alloc(struct adapter *adapter, struct sta_info *sta, u8 kid)
cam_id = kid;
} else {
int i;
- u8 *addr = sta?sta->hwaddr:NULL;
+ u8 *addr = sta ? sta->hwaddr : NULL;
if (!sta) {
if (!(mlmeinfo->state & WIFI_FW_ASSOC_SUCCESS)) {
@@ -792,7 +792,7 @@ void WMMOnAssocRsp(struct adapter *padapter)
switch (ACI) {
case 0x0:
rtw_hal_set_hwreg(padapter, HW_VAR_AC_PARAM_BE, (u8 *)(&acParm));
- acm_mask |= (ACM ? BIT(1):0);
+ acm_mask |= (ACM ? BIT(1) : 0);
edca[XMIT_BE_QUEUE] = acParm;
break;
@@ -804,13 +804,13 @@ void WMMOnAssocRsp(struct adapter *padapter)
case 0x2:
rtw_hal_set_hwreg(padapter, HW_VAR_AC_PARAM_VI, (u8 *)(&acParm));
- acm_mask |= (ACM ? BIT(2):0);
+ acm_mask |= (ACM ? BIT(2) : 0);
edca[XMIT_VI_QUEUE] = acParm;
break;
case 0x3:
rtw_hal_set_hwreg(padapter, HW_VAR_AC_PARAM_VO, (u8 *)(&acParm));
- acm_mask |= (ACM ? BIT(3):0);
+ acm_mask |= (ACM ? BIT(3) : 0);
edca[XMIT_VO_QUEUE] = acParm;
break;
}
@@ -1170,7 +1170,7 @@ int rtw_check_bcn_info(struct adapter *Adapter, u8 *pframe, u32 packet_len)
ht_info_infos_0 = 0;
}
if (ht_cap_info != cur_network->bcn_info.ht_cap_info ||
- ((ht_info_infos_0&0x03) != (cur_network->bcn_info.ht_info_infos_0&0x03))) {
+ ((ht_info_infos_0 & 0x03) != (cur_network->bcn_info.ht_info_infos_0 & 0x03))) {
{
/* bcn_info_update */
cur_network->bcn_info.ht_cap_info = ht_cap_info;
@@ -1238,12 +1238,12 @@ int rtw_check_bcn_info(struct adapter *Adapter, u8 *pframe, u32 packet_len)
goto _mismatch;
if (encryp_protocol == ENCRYP_PROTOCOL_WPA || encryp_protocol == ENCRYP_PROTOCOL_WPA2) {
- pbuf = rtw_get_wpa_ie(&bssid->ies[12], &wpa_ielen, bssid->ie_length-12);
+ pbuf = rtw_get_wpa_ie(&bssid->ies[12], &wpa_ielen, bssid->ie_length - 12);
if (pbuf && (wpa_ielen > 0)) {
rtw_parse_wpa_ie(pbuf, wpa_ielen + 2, &group_cipher,
&pairwise_cipher, &is_8021x);
} else {
- pbuf = rtw_get_wpa2_ie(&bssid->ies[12], &wpa_ielen, bssid->ie_length-12);
+ pbuf = rtw_get_wpa2_ie(&bssid->ies[12], &wpa_ielen, bssid->ie_length - 12);
if (pbuf && (wpa_ielen > 0))
rtw_parse_wpa2_ie(pbuf, wpa_ielen + 2, &group_cipher,
@@ -1630,7 +1630,7 @@ void process_addba_req(struct adapter *padapter, u8 *paddba_req, u8 *addr)
if (psta) {
param = le16_to_cpu(preq->BA_para_set);
- tid = (param>>2)&0x0f;
+ tid = (param >> 2) & 0x0f;
preorder_ctrl = &psta->recvreorder_ctrl[tid];
@@ -1648,7 +1648,7 @@ void update_TSF(struct mlme_ext_priv *pmlmeext, u8 *pframe, uint len)
pIE = pframe + sizeof(struct ieee80211_hdr_3addr);
pbuf = (__le32 *)pIE;
- pmlmeext->TSFValue = le32_to_cpu(*(pbuf+1));
+ pmlmeext->TSFValue = le32_to_cpu(*(pbuf + 1));
pmlmeext->TSFValue = pmlmeext->TSFValue << 32;
@@ -1674,14 +1674,14 @@ void adaptive_early_32k(struct mlme_ext_priv *pmlmeext, u8 *pframe, uint len)
pIE = pframe + sizeof(struct ieee80211_hdr_3addr);
pbuf = (__le32 *)pIE;
- tsf = le32_to_cpu(*(pbuf+1));
+ tsf = le32_to_cpu(*(pbuf + 1));
tsf = tsf << 32;
tsf |= le32_to_cpu(*pbuf);
/* delay = (timestamp mod 1024*100)/1000 (unit: ms) */
/* delay_ms = do_div(tsf, (pmlmeinfo->bcn_interval*1024))/1000; */
- delay_ms = do_div(tsf, (pmlmeinfo->bcn_interval*1024));
- delay_ms = delay_ms/1000;
+ delay_ms = do_div(tsf, (pmlmeinfo->bcn_interval * 1024));
+ delay_ms = delay_ms / 1000;
if (delay_ms >= 8)
pmlmeext->bcn_delay_cnt[8]++;
diff --git a/drivers/staging/rtl8723bs/core/rtw_xmit.c b/drivers/staging/rtl8723bs/core/rtw_xmit.c
index 8c6841f078b4..21690857fd62 100644
--- a/drivers/staging/rtl8723bs/core/rtw_xmit.c
+++ b/drivers/staging/rtl8723bs/core/rtw_xmit.c
@@ -1209,7 +1209,7 @@ s32 rtw_mgmt_xmitframe_coalesce(struct adapter *padapter, struct sk_buff *pkt, s
ClearPwrMgt(BIP_AAD);
ClearMData(BIP_AAD);
/* conscruct AAD, copy address 1 to address 3 */
- memcpy(BIP_AAD+2, pwlanhdr->addr1, 18);
+ memcpy(BIP_AAD + 2, &pwlanhdr->addrs, sizeof(pwlanhdr->addrs));
/* copy management fram body */
memcpy(BIP_AAD+BIP_AAD_SIZE, MGMT_body, frame_body_len);
/* calculate mic */
diff --git a/drivers/staging/rtl8723bs/hal/hal_com_phycfg.c b/drivers/staging/rtl8723bs/hal/hal_com_phycfg.c
index d5649e7d8f99..cd76e26e868f 100644
--- a/drivers/staging/rtl8723bs/hal/hal_com_phycfg.c
+++ b/drivers/staging/rtl8723bs/hal/hal_com_phycfg.c
@@ -59,10 +59,7 @@ phy_SetTxPowerByRateBase(struct adapter *Adapter, u8 RfPath,
}
}
-static void
-phy_StoreTxPowerByRateBase(
-struct adapter *padapter
- )
+static void phy_StoreTxPowerByRateBase(struct adapter *padapter)
{
u8 path, base;
diff --git a/drivers/staging/rtl8723bs/hal/hal_pwr_seq.c b/drivers/staging/rtl8723bs/hal/hal_pwr_seq.c
index fba67a7c069c..2438931ca51b 100644
--- a/drivers/staging/rtl8723bs/hal/hal_pwr_seq.c
+++ b/drivers/staging/rtl8723bs/hal/hal_pwr_seq.c
@@ -8,7 +8,7 @@
/*
*
This file includes all kinds of Power Action event for RTL8723B
-and corresponding hardware configurtions which are released from HW SD.
+and corresponding hardware configurations which are released from HW SD.
Major Change History:
When Who What
diff --git a/drivers/staging/rtl8723bs/hal/odm.c b/drivers/staging/rtl8723bs/hal/odm.c
index ba85efb30db2..4b36af47f680 100644
--- a/drivers/staging/rtl8723bs/hal/odm.c
+++ b/drivers/staging/rtl8723bs/hal/odm.c
@@ -9,118 +9,6 @@
/* Global var */
-u32 OFDMSwingTable[OFDM_TABLE_SIZE] = {
- 0x7f8001fe, /* 0, +6.0dB */
- 0x788001e2, /* 1, +5.5dB */
- 0x71c001c7, /* 2, +5.0dB */
- 0x6b8001ae, /* 3, +4.5dB */
- 0x65400195, /* 4, +4.0dB */
- 0x5fc0017f, /* 5, +3.5dB */
- 0x5a400169, /* 6, +3.0dB */
- 0x55400155, /* 7, +2.5dB */
- 0x50800142, /* 8, +2.0dB */
- 0x4c000130, /* 9, +1.5dB */
- 0x47c0011f, /* 10, +1.0dB */
- 0x43c0010f, /* 11, +0.5dB */
- 0x40000100, /* 12, +0dB */
- 0x3c8000f2, /* 13, -0.5dB */
- 0x390000e4, /* 14, -1.0dB */
- 0x35c000d7, /* 15, -1.5dB */
- 0x32c000cb, /* 16, -2.0dB */
- 0x300000c0, /* 17, -2.5dB */
- 0x2d4000b5, /* 18, -3.0dB */
- 0x2ac000ab, /* 19, -3.5dB */
- 0x288000a2, /* 20, -4.0dB */
- 0x26000098, /* 21, -4.5dB */
- 0x24000090, /* 22, -5.0dB */
- 0x22000088, /* 23, -5.5dB */
- 0x20000080, /* 24, -6.0dB */
- 0x1e400079, /* 25, -6.5dB */
- 0x1c800072, /* 26, -7.0dB */
- 0x1b00006c, /* 27. -7.5dB */
- 0x19800066, /* 28, -8.0dB */
- 0x18000060, /* 29, -8.5dB */
- 0x16c0005b, /* 30, -9.0dB */
- 0x15800056, /* 31, -9.5dB */
- 0x14400051, /* 32, -10.0dB */
- 0x1300004c, /* 33, -10.5dB */
- 0x12000048, /* 34, -11.0dB */
- 0x11000044, /* 35, -11.5dB */
- 0x10000040, /* 36, -12.0dB */
-};
-
-u8 CCKSwingTable_Ch1_Ch13[CCK_TABLE_SIZE][8] = {
- {0x36, 0x35, 0x2e, 0x25, 0x1c, 0x12, 0x09, 0x04}, /* 0, +0dB */
- {0x33, 0x32, 0x2b, 0x23, 0x1a, 0x11, 0x08, 0x04}, /* 1, -0.5dB */
- {0x30, 0x2f, 0x29, 0x21, 0x19, 0x10, 0x08, 0x03}, /* 2, -1.0dB */
- {0x2d, 0x2d, 0x27, 0x1f, 0x18, 0x0f, 0x08, 0x03}, /* 3, -1.5dB */
- {0x2b, 0x2a, 0x25, 0x1e, 0x16, 0x0e, 0x07, 0x03}, /* 4, -2.0dB */
- {0x28, 0x28, 0x22, 0x1c, 0x15, 0x0d, 0x07, 0x03}, /* 5, -2.5dB */
- {0x26, 0x25, 0x21, 0x1b, 0x14, 0x0d, 0x06, 0x03}, /* 6, -3.0dB */
- {0x24, 0x23, 0x1f, 0x19, 0x13, 0x0c, 0x06, 0x03}, /* 7, -3.5dB */
- {0x22, 0x21, 0x1d, 0x18, 0x11, 0x0b, 0x06, 0x02}, /* 8, -4.0dB */
- {0x20, 0x20, 0x1b, 0x16, 0x11, 0x08, 0x05, 0x02}, /* 9, -4.5dB */
- {0x1f, 0x1e, 0x1a, 0x15, 0x10, 0x0a, 0x05, 0x02}, /* 10, -5.0dB */
- {0x1d, 0x1c, 0x18, 0x14, 0x0f, 0x0a, 0x05, 0x02}, /* 11, -5.5dB */
- {0x1b, 0x1a, 0x17, 0x13, 0x0e, 0x09, 0x04, 0x02}, /* 12, -6.0dB <== default */
- {0x1a, 0x19, 0x16, 0x12, 0x0d, 0x09, 0x04, 0x02}, /* 13, -6.5dB */
- {0x18, 0x17, 0x15, 0x11, 0x0c, 0x08, 0x04, 0x02}, /* 14, -7.0dB */
- {0x17, 0x16, 0x13, 0x10, 0x0c, 0x08, 0x04, 0x02}, /* 15, -7.5dB */
- {0x16, 0x15, 0x12, 0x0f, 0x0b, 0x07, 0x04, 0x01}, /* 16, -8.0dB */
- {0x14, 0x14, 0x11, 0x0e, 0x0b, 0x07, 0x03, 0x02}, /* 17, -8.5dB */
- {0x13, 0x13, 0x10, 0x0d, 0x0a, 0x06, 0x03, 0x01}, /* 18, -9.0dB */
- {0x12, 0x12, 0x0f, 0x0c, 0x09, 0x06, 0x03, 0x01}, /* 19, -9.5dB */
- {0x11, 0x11, 0x0f, 0x0c, 0x09, 0x06, 0x03, 0x01}, /* 20, -10.0dB */
- {0x10, 0x10, 0x0e, 0x0b, 0x08, 0x05, 0x03, 0x01}, /* 21, -10.5dB */
- {0x0f, 0x0f, 0x0d, 0x0b, 0x08, 0x05, 0x03, 0x01}, /* 22, -11.0dB */
- {0x0e, 0x0e, 0x0c, 0x0a, 0x08, 0x05, 0x02, 0x01}, /* 23, -11.5dB */
- {0x0d, 0x0d, 0x0c, 0x0a, 0x07, 0x05, 0x02, 0x01}, /* 24, -12.0dB */
- {0x0d, 0x0c, 0x0b, 0x09, 0x07, 0x04, 0x02, 0x01}, /* 25, -12.5dB */
- {0x0c, 0x0c, 0x0a, 0x09, 0x06, 0x04, 0x02, 0x01}, /* 26, -13.0dB */
- {0x0b, 0x0b, 0x0a, 0x08, 0x06, 0x04, 0x02, 0x01}, /* 27, -13.5dB */
- {0x0b, 0x0a, 0x09, 0x08, 0x06, 0x04, 0x02, 0x01}, /* 28, -14.0dB */
- {0x0a, 0x0a, 0x09, 0x07, 0x05, 0x03, 0x02, 0x01}, /* 29, -14.5dB */
- {0x0a, 0x09, 0x08, 0x07, 0x05, 0x03, 0x02, 0x01}, /* 30, -15.0dB */
- {0x09, 0x09, 0x08, 0x06, 0x05, 0x03, 0x01, 0x01}, /* 31, -15.5dB */
- {0x09, 0x08, 0x07, 0x06, 0x04, 0x03, 0x01, 0x01} /* 32, -16.0dB */
-};
-
-u8 CCKSwingTable_Ch14[CCK_TABLE_SIZE][8] = {
- {0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00}, /* 0, +0dB */
- {0x33, 0x32, 0x2b, 0x19, 0x00, 0x00, 0x00, 0x00}, /* 1, -0.5dB */
- {0x30, 0x2f, 0x29, 0x18, 0x00, 0x00, 0x00, 0x00}, /* 2, -1.0dB */
- {0x2d, 0x2d, 0x17, 0x17, 0x00, 0x00, 0x00, 0x00}, /* 3, -1.5dB */
- {0x2b, 0x2a, 0x25, 0x15, 0x00, 0x00, 0x00, 0x00}, /* 4, -2.0dB */
- {0x28, 0x28, 0x24, 0x14, 0x00, 0x00, 0x00, 0x00}, /* 5, -2.5dB */
- {0x26, 0x25, 0x21, 0x13, 0x00, 0x00, 0x00, 0x00}, /* 6, -3.0dB */
- {0x24, 0x23, 0x1f, 0x12, 0x00, 0x00, 0x00, 0x00}, /* 7, -3.5dB */
- {0x22, 0x21, 0x1d, 0x11, 0x00, 0x00, 0x00, 0x00}, /* 8, -4.0dB */
- {0x20, 0x20, 0x1b, 0x10, 0x00, 0x00, 0x00, 0x00}, /* 9, -4.5dB */
- {0x1f, 0x1e, 0x1a, 0x0f, 0x00, 0x00, 0x00, 0x00}, /* 10, -5.0dB */
- {0x1d, 0x1c, 0x18, 0x0e, 0x00, 0x00, 0x00, 0x00}, /* 11, -5.5dB */
- {0x1b, 0x1a, 0x17, 0x0e, 0x00, 0x00, 0x00, 0x00}, /* 12, -6.0dB <== default */
- {0x1a, 0x19, 0x16, 0x0d, 0x00, 0x00, 0x00, 0x00}, /* 13, -6.5dB */
- {0x18, 0x17, 0x15, 0x0c, 0x00, 0x00, 0x00, 0x00}, /* 14, -7.0dB */
- {0x17, 0x16, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00}, /* 15, -7.5dB */
- {0x16, 0x15, 0x12, 0x0b, 0x00, 0x00, 0x00, 0x00}, /* 16, -8.0dB */
- {0x14, 0x14, 0x11, 0x0a, 0x00, 0x00, 0x00, 0x00}, /* 17, -8.5dB */
- {0x13, 0x13, 0x10, 0x0a, 0x00, 0x00, 0x00, 0x00}, /* 18, -9.0dB */
- {0x12, 0x12, 0x0f, 0x09, 0x00, 0x00, 0x00, 0x00}, /* 19, -9.5dB */
- {0x11, 0x11, 0x0f, 0x09, 0x00, 0x00, 0x00, 0x00}, /* 20, -10.0dB */
- {0x10, 0x10, 0x0e, 0x08, 0x00, 0x00, 0x00, 0x00}, /* 21, -10.5dB */
- {0x0f, 0x0f, 0x0d, 0x08, 0x00, 0x00, 0x00, 0x00}, /* 22, -11.0dB */
- {0x0e, 0x0e, 0x0c, 0x07, 0x00, 0x00, 0x00, 0x00}, /* 23, -11.5dB */
- {0x0d, 0x0d, 0x0c, 0x07, 0x00, 0x00, 0x00, 0x00}, /* 24, -12.0dB */
- {0x0d, 0x0c, 0x0b, 0x06, 0x00, 0x00, 0x00, 0x00}, /* 25, -12.5dB */
- {0x0c, 0x0c, 0x0a, 0x06, 0x00, 0x00, 0x00, 0x00}, /* 26, -13.0dB */
- {0x0b, 0x0b, 0x0a, 0x06, 0x00, 0x00, 0x00, 0x00}, /* 27, -13.5dB */
- {0x0b, 0x0a, 0x09, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 28, -14.0dB */
- {0x0a, 0x0a, 0x09, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 29, -14.5dB */
- {0x0a, 0x09, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 30, -15.0dB */
- {0x09, 0x09, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 31, -15.5dB */
- {0x09, 0x08, 0x07, 0x04, 0x00, 0x00, 0x00, 0x00} /* 32, -16.0dB */
-};
-
u32 OFDMSwingTable_New[OFDM_TABLE_SIZE] = {
0x0b40002d, /* 0, -15.0dB */
0x0c000030, /* 1, -14.5dB */
@@ -239,46 +127,6 @@ u8 CCKSwingTable_Ch14_New[CCK_TABLE_SIZE][8] = {
{0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00} /* 32, +0dB */
};
-u32 TxScalingTable_Jaguar[TXSCALE_TABLE_SIZE] = {
- 0x081, /* 0, -12.0dB */
- 0x088, /* 1, -11.5dB */
- 0x090, /* 2, -11.0dB */
- 0x099, /* 3, -10.5dB */
- 0x0A2, /* 4, -10.0dB */
- 0x0AC, /* 5, -9.5dB */
- 0x0B6, /* 6, -9.0dB */
- 0x0C0, /* 7, -8.5dB */
- 0x0CC, /* 8, -8.0dB */
- 0x0D8, /* 9, -7.5dB */
- 0x0E5, /* 10, -7.0dB */
- 0x0F2, /* 11, -6.5dB */
- 0x101, /* 12, -6.0dB */
- 0x110, /* 13, -5.5dB */
- 0x120, /* 14, -5.0dB */
- 0x131, /* 15, -4.5dB */
- 0x143, /* 16, -4.0dB */
- 0x156, /* 17, -3.5dB */
- 0x16A, /* 18, -3.0dB */
- 0x180, /* 19, -2.5dB */
- 0x197, /* 20, -2.0dB */
- 0x1AF, /* 21, -1.5dB */
- 0x1C8, /* 22, -1.0dB */
- 0x1E3, /* 23, -0.5dB */
- 0x200, /* 24, +0 dB */
- 0x21E, /* 25, +0.5dB */
- 0x23E, /* 26, +1.0dB */
- 0x261, /* 27, +1.5dB */
- 0x285, /* 28, +2.0dB */
- 0x2AB, /* 29, +2.5dB */
- 0x2D3, /* 30, +3.0dB */
- 0x2FE, /* 31, +3.5dB */
- 0x32B, /* 32, +4.0dB */
- 0x35C, /* 33, +4.5dB */
- 0x38E, /* 34, +5.0dB */
- 0x3C4, /* 35, +5.5dB */
- 0x3FE /* 36, +6.0dB */
-};
-
/* Remove Edca by Yu Chen */
static void odm_CommonInfoSelfInit(struct dm_odm_t *pDM_Odm)
diff --git a/drivers/staging/rtl8723bs/hal/odm.h b/drivers/staging/rtl8723bs/hal/odm.h
index 010274ba8079..1c929d88e596 100644
--- a/drivers/staging/rtl8723bs/hal/odm.h
+++ b/drivers/staging/rtl8723bs/hal/odm.h
@@ -1080,16 +1080,10 @@ enum { /* tag_RF_Type_Definition */
/* */
/* Extern Global Variables. */
/* */
-extern u32 OFDMSwingTable[OFDM_TABLE_SIZE];
-extern u8 CCKSwingTable_Ch1_Ch13[CCK_TABLE_SIZE][8];
-extern u8 CCKSwingTable_Ch14[CCK_TABLE_SIZE][8];
-
extern u32 OFDMSwingTable_New[OFDM_TABLE_SIZE];
extern u8 CCKSwingTable_Ch1_Ch13_New[CCK_TABLE_SIZE][8];
extern u8 CCKSwingTable_Ch14_New[CCK_TABLE_SIZE][8];
-extern u32 TxScalingTable_Jaguar[TXSCALE_TABLE_SIZE];
-
/* */
/* check Sta pointer valid or not */
/* */
diff --git a/drivers/staging/rtl8723bs/hal/rtl8723b_hal_init.c b/drivers/staging/rtl8723bs/hal/rtl8723b_hal_init.c
index 1608bc71bd71..18244adad9e0 100644
--- a/drivers/staging/rtl8723bs/hal/rtl8723b_hal_init.c
+++ b/drivers/staging/rtl8723bs/hal/rtl8723b_hal_init.c
@@ -445,47 +445,31 @@ void rtl8723b_InitializeFirmwareVars(struct adapter *padapter)
/* Efuse related code */
/* */
static u8 hal_EfuseSwitchToBank(
- struct adapter *padapter, u8 bank, bool bPseudoTest
+ struct adapter *padapter, u8 bank
)
{
- u8 bRet = false;
- u32 value32 = 0;
-#ifdef HAL_EFUSE_MEMORY
- struct hal_com_data *pHalData = GET_HAL_DATA(padapter);
- struct efuse_hal *pEfuseHal = &pHalData->EfuseHal;
-#endif
+ u8 bRet = true;
+ u32 value32 = rtw_read32(padapter, EFUSE_TEST);
-
- if (bPseudoTest) {
-#ifdef HAL_EFUSE_MEMORY
- pEfuseHal->fakeEfuseBank = bank;
-#else
- fakeEfuseBank = bank;
-#endif
- bRet = true;
- } else {
- value32 = rtw_read32(padapter, EFUSE_TEST);
- bRet = true;
- switch (bank) {
- case 0:
- value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_WIFI_SEL_0);
- break;
- case 1:
- value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_BT_SEL_0);
- break;
- case 2:
- value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_BT_SEL_1);
- break;
- case 3:
- value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_BT_SEL_2);
- break;
- default:
- value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_WIFI_SEL_0);
- bRet = false;
- break;
- }
- rtw_write32(padapter, EFUSE_TEST, value32);
+ switch (bank) {
+ case 0:
+ value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_WIFI_SEL_0);
+ break;
+ case 1:
+ value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_BT_SEL_0);
+ break;
+ case 2:
+ value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_BT_SEL_1);
+ break;
+ case 3:
+ value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_BT_SEL_2);
+ break;
+ default:
+ value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_WIFI_SEL_0);
+ bRet = false;
+ break;
}
+ rtw_write32(padapter, EFUSE_TEST, value32);
return bRet;
}
@@ -494,8 +478,7 @@ void Hal_GetEfuseDefinition(
struct adapter *padapter,
u8 efuseType,
u8 type,
- void *pOut,
- bool bPseudoTest
+ void *pOut
)
{
switch (type) {
@@ -585,17 +568,8 @@ void Hal_GetEfuseDefinition(
}
}
-#define VOLTAGE_V25 0x03
-
-/* */
-/* The following is for compile ok */
-/* That should be merged with the original in the future */
-/* */
-#define EFUSE_ACCESS_ON_8723 0x69 /* For RTL8723 only. */
-#define REG_EFUSE_ACCESS_8723 0x00CF /* Efuse access protection for RTL8723 */
-
void Hal_EfusePowerSwitch(
- struct adapter *padapter, u8 bWrite, u8 PwrState
+ struct adapter *padapter, u8 PwrState
)
{
u8 tempval;
@@ -628,7 +602,7 @@ void Hal_EfusePowerSwitch(
} while (1);
}
- rtw_write8(padapter, REG_EFUSE_ACCESS_8723, EFUSE_ACCESS_ON_8723);
+ rtw_write8(padapter, REG_EFUSE_ACCESS, EFUSE_ACCESS_ON);
/* Reset: 0x0000h[28], default valid */
tmpV16 = rtw_read16(padapter, REG_SYS_FUNC_EN);
@@ -643,25 +617,8 @@ void Hal_EfusePowerSwitch(
tmpV16 |= (LOADER_CLK_EN | ANA8M);
rtw_write16(padapter, REG_SYS_CLKR, tmpV16);
}
-
- if (bWrite) {
- /* Enable LDO 2.5V before read/write action */
- tempval = rtw_read8(padapter, EFUSE_TEST+3);
- tempval &= 0x0F;
- tempval |= (VOLTAGE_V25 << 4);
- rtw_write8(padapter, EFUSE_TEST+3, (tempval | 0x80));
-
- /* rtw_write8(padapter, REG_EFUSE_ACCESS, EFUSE_ACCESS_ON); */
- }
} else {
rtw_write8(padapter, REG_EFUSE_ACCESS, EFUSE_ACCESS_OFF);
-
- if (bWrite) {
- /* Disable LDO 2.5V after read/write action */
- tempval = rtw_read8(padapter, EFUSE_TEST+3);
- rtw_write8(padapter, EFUSE_TEST+3, (tempval & 0x7F));
- }
-
}
}
@@ -669,14 +626,9 @@ static void hal_ReadEFuse_WiFi(
struct adapter *padapter,
u16 _offset,
u16 _size_byte,
- u8 *pbuf,
- bool bPseudoTest
+ u8 *pbuf
)
{
-#ifdef HAL_EFUSE_MEMORY
- struct hal_com_data *pHalData = GET_HAL_DATA(padapter);
- struct efuse_hal *pEfuseHal = &pHalData->EfuseHal;
-#endif
u8 *efuseTbl = NULL;
u16 eFuse_Addr = 0;
u8 offset, wden;
@@ -698,10 +650,10 @@ static void hal_ReadEFuse_WiFi(
memset(efuseTbl, 0xFF, EFUSE_MAX_MAP_LEN);
/* switch bank back to bank 0 for later BT and wifi use. */
- hal_EfuseSwitchToBank(padapter, 0, bPseudoTest);
+ hal_EfuseSwitchToBank(padapter, 0);
while (AVAILABLE_EFUSE_ADDR(eFuse_Addr)) {
- efuse_OneByteRead(padapter, eFuse_Addr++, &efuseHeader, bPseudoTest);
+ efuse_OneByteRead(padapter, eFuse_Addr++, &efuseHeader);
if (efuseHeader == 0xFF)
break;
@@ -709,7 +661,7 @@ static void hal_ReadEFuse_WiFi(
if (EXT_HEADER(efuseHeader)) { /* extended header */
offset = GET_HDR_OFFSET_2_0(efuseHeader);
- efuse_OneByteRead(padapter, eFuse_Addr++, &efuseExtHdr, bPseudoTest);
+ efuse_OneByteRead(padapter, eFuse_Addr++, &efuseExtHdr);
if (ALL_WORDS_DISABLED(efuseExtHdr))
continue;
@@ -728,10 +680,10 @@ static void hal_ReadEFuse_WiFi(
for (i = 0; i < EFUSE_MAX_WORD_UNIT; i++) {
/* Check word enable condition in the section */
if (!(wden & (0x01<<i))) {
- efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData, bPseudoTest);
+ efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData);
efuseTbl[addr] = efuseData;
- efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData, bPseudoTest);
+ efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData);
efuseTbl[addr+1] = efuseData;
}
addr += 2;
@@ -746,19 +698,12 @@ static void hal_ReadEFuse_WiFi(
pbuf[i] = efuseTbl[_offset+i];
/* Calculate Efuse utilization */
- EFUSE_GetEfuseDefinition(padapter, EFUSE_WIFI, TYPE_AVAILABLE_EFUSE_BYTES_TOTAL, &total, bPseudoTest);
+ Hal_GetEfuseDefinition(padapter, EFUSE_WIFI, TYPE_AVAILABLE_EFUSE_BYTES_TOTAL, &total);
used = eFuse_Addr - 1;
efuse_usage = (u8)((used*100)/total);
- if (bPseudoTest) {
-#ifdef HAL_EFUSE_MEMORY
- pEfuseHal->fakeEfuseUsedBytes = used;
-#else
- fakeEfuseUsedBytes = used;
-#endif
- } else {
- rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_BYTES, (u8 *)&used);
- rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_USAGE, (u8 *)&efuse_usage);
- }
+
+ rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_BYTES, (u8 *)&used);
+ rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_USAGE, (u8 *)&efuse_usage);
kfree(efuseTbl);
}
@@ -767,14 +712,9 @@ static void hal_ReadEFuse_BT(
struct adapter *padapter,
u16 _offset,
u16 _size_byte,
- u8 *pbuf,
- bool bPseudoTest
+ u8 *pbuf
)
{
-#ifdef HAL_EFUSE_MEMORY
- struct hal_com_data *pHalData = GET_HAL_DATA(padapter);
- struct efuse_hal *pEfuseHal = &pHalData->EfuseHal;
-#endif
u8 *efuseTbl;
u8 bank;
u16 eFuse_Addr;
@@ -797,16 +737,16 @@ static void hal_ReadEFuse_BT(
/* 0xff will be efuse default value instead of 0x00. */
memset(efuseTbl, 0xFF, EFUSE_BT_MAP_LEN);
- EFUSE_GetEfuseDefinition(padapter, EFUSE_BT, TYPE_AVAILABLE_EFUSE_BYTES_BANK, &total, bPseudoTest);
+ Hal_GetEfuseDefinition(padapter, EFUSE_BT, TYPE_AVAILABLE_EFUSE_BYTES_BANK, &total);
for (bank = 1; bank < 3; bank++) { /* 8723b Max bake 0~2 */
- if (hal_EfuseSwitchToBank(padapter, bank, bPseudoTest) == false)
+ if (hal_EfuseSwitchToBank(padapter, bank) == false)
goto exit;
eFuse_Addr = 0;
while (AVAILABLE_EFUSE_ADDR(eFuse_Addr)) {
- efuse_OneByteRead(padapter, eFuse_Addr++, &efuseHeader, bPseudoTest);
+ efuse_OneByteRead(padapter, eFuse_Addr++, &efuseHeader);
if (efuseHeader == 0xFF)
break;
@@ -814,7 +754,7 @@ static void hal_ReadEFuse_BT(
if (EXT_HEADER(efuseHeader)) { /* extended header */
offset = GET_HDR_OFFSET_2_0(efuseHeader);
- efuse_OneByteRead(padapter, eFuse_Addr++, &efuseExtHdr, bPseudoTest);
+ efuse_OneByteRead(padapter, eFuse_Addr++, &efuseExtHdr);
if (ALL_WORDS_DISABLED(efuseExtHdr))
continue;
@@ -832,10 +772,10 @@ static void hal_ReadEFuse_BT(
for (i = 0; i < EFUSE_MAX_WORD_UNIT; i++) {
/* Check word enable condition in the section */
if (!(wden & (0x01<<i))) {
- efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData, bPseudoTest);
+ efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData);
efuseTbl[addr] = efuseData;
- efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData, bPseudoTest);
+ efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData);
efuseTbl[addr+1] = efuseData;
}
addr += 2;
@@ -851,7 +791,7 @@ static void hal_ReadEFuse_BT(
}
/* switch bank back to bank 0 for later BT and wifi use. */
- hal_EfuseSwitchToBank(padapter, 0, bPseudoTest);
+ hal_EfuseSwitchToBank(padapter, 0);
/* Copy from Efuse map to output pointer memory!!! */
for (i = 0; i < _size_byte; i++)
@@ -860,19 +800,12 @@ static void hal_ReadEFuse_BT(
/* */
/* Calculate Efuse utilization. */
/* */
- EFUSE_GetEfuseDefinition(padapter, EFUSE_BT, TYPE_AVAILABLE_EFUSE_BYTES_TOTAL, &total, bPseudoTest);
+ Hal_GetEfuseDefinition(padapter, EFUSE_BT, TYPE_AVAILABLE_EFUSE_BYTES_TOTAL, &total);
used = (EFUSE_BT_REAL_BANK_CONTENT_LEN*(bank-1)) + eFuse_Addr - 1;
efuse_usage = (u8)((used*100)/total);
- if (bPseudoTest) {
-#ifdef HAL_EFUSE_MEMORY
- pEfuseHal->fakeBTEfuseUsedBytes = used;
-#else
- fakeBTEfuseUsedBytes = used;
-#endif
- } else {
- rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_BT_BYTES, (u8 *)&used);
- rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_BT_USAGE, (u8 *)&efuse_usage);
- }
+
+ rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_BT_BYTES, (u8 *)&used);
+ rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_BT_USAGE, (u8 *)&efuse_usage);
exit:
kfree(efuseTbl);
@@ -883,198 +816,13 @@ void Hal_ReadEFuse(
u8 efuseType,
u16 _offset,
u16 _size_byte,
- u8 *pbuf,
- bool bPseudoTest
-)
-{
- if (efuseType == EFUSE_WIFI)
- hal_ReadEFuse_WiFi(padapter, _offset, _size_byte, pbuf, bPseudoTest);
- else
- hal_ReadEFuse_BT(padapter, _offset, _size_byte, pbuf, bPseudoTest);
-}
-
-static u16 hal_EfuseGetCurrentSize_WiFi(
- struct adapter *padapter, bool bPseudoTest
-)
-{
-#ifdef HAL_EFUSE_MEMORY
- struct hal_com_data *pHalData = GET_HAL_DATA(padapter);
- struct efuse_hal *pEfuseHal = &pHalData->EfuseHal;
-#endif
- u16 efuse_addr = 0;
- u16 start_addr = 0; /* for debug */
- u8 hworden = 0;
- u8 efuse_data, word_cnts = 0;
- u32 count = 0; /* for debug */
-
-
- if (bPseudoTest) {
-#ifdef HAL_EFUSE_MEMORY
- efuse_addr = (u16)pEfuseHal->fakeEfuseUsedBytes;
-#else
- efuse_addr = (u16)fakeEfuseUsedBytes;
-#endif
- } else
- rtw_hal_get_hwreg(padapter, HW_VAR_EFUSE_BYTES, (u8 *)&efuse_addr);
-
- start_addr = efuse_addr;
-
- /* switch bank back to bank 0 for later BT and wifi use. */
- hal_EfuseSwitchToBank(padapter, 0, bPseudoTest);
-
- count = 0;
- while (AVAILABLE_EFUSE_ADDR(efuse_addr)) {
- if (efuse_OneByteRead(padapter, efuse_addr, &efuse_data, bPseudoTest) == false)
- goto error;
-
- if (efuse_data == 0xFF)
- break;
-
- if ((start_addr != 0) && (efuse_addr == start_addr)) {
- count++;
-
- efuse_data = 0xFF;
- if (count < 4) {
- /* try again! */
-
- if (count > 2) {
- /* try again form address 0 */
- efuse_addr = 0;
- start_addr = 0;
- }
-
- continue;
- }
-
- goto error;
- }
-
- if (EXT_HEADER(efuse_data)) {
- efuse_addr++;
- efuse_OneByteRead(padapter, efuse_addr, &efuse_data, bPseudoTest);
- if (ALL_WORDS_DISABLED(efuse_data))
- continue;
-
- hworden = efuse_data & 0x0F;
- } else {
- hworden = efuse_data & 0x0F;
- }
-
- word_cnts = Efuse_CalculateWordCnts(hworden);
- efuse_addr += (word_cnts*2)+1;
- }
-
- if (bPseudoTest) {
-#ifdef HAL_EFUSE_MEMORY
- pEfuseHal->fakeEfuseUsedBytes = efuse_addr;
-#else
- fakeEfuseUsedBytes = efuse_addr;
-#endif
- } else
- rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_BYTES, (u8 *)&efuse_addr);
-
- goto exit;
-
-error:
- /* report max size to prevent write efuse */
- EFUSE_GetEfuseDefinition(padapter, EFUSE_WIFI, TYPE_AVAILABLE_EFUSE_BYTES_TOTAL, &efuse_addr, bPseudoTest);
-
-exit:
-
- return efuse_addr;
-}
-
-static u16 hal_EfuseGetCurrentSize_BT(struct adapter *padapter, u8 bPseudoTest)
-{
-#ifdef HAL_EFUSE_MEMORY
- struct hal_com_data *pHalData = GET_HAL_DATA(padapter);
- struct efuse_hal *pEfuseHal = &pHalData->EfuseHal;
-#endif
- u16 btusedbytes;
- u16 efuse_addr;
- u8 bank, startBank;
- u8 hworden = 0;
- u8 efuse_data, word_cnts = 0;
- u16 retU2 = 0;
-
- if (bPseudoTest) {
-#ifdef HAL_EFUSE_MEMORY
- btusedbytes = pEfuseHal->fakeBTEfuseUsedBytes;
-#else
- btusedbytes = fakeBTEfuseUsedBytes;
-#endif
- } else
- rtw_hal_get_hwreg(padapter, HW_VAR_EFUSE_BT_BYTES, (u8 *)&btusedbytes);
-
- efuse_addr = (u16)((btusedbytes%EFUSE_BT_REAL_BANK_CONTENT_LEN));
- startBank = (u8)(1+(btusedbytes/EFUSE_BT_REAL_BANK_CONTENT_LEN));
-
- EFUSE_GetEfuseDefinition(padapter, EFUSE_BT, TYPE_AVAILABLE_EFUSE_BYTES_BANK, &retU2, bPseudoTest);
-
- for (bank = startBank; bank < 3; bank++) {
- if (hal_EfuseSwitchToBank(padapter, bank, bPseudoTest) == false)
- /* bank = EFUSE_MAX_BANK; */
- break;
-
- /* only when bank is switched we have to reset the efuse_addr. */
- if (bank != startBank)
- efuse_addr = 0;
-
- while (AVAILABLE_EFUSE_ADDR(efuse_addr)) {
- if (efuse_OneByteRead(padapter, efuse_addr,
- &efuse_data, bPseudoTest) == false)
- /* bank = EFUSE_MAX_BANK; */
- break;
-
- if (efuse_data == 0xFF)
- break;
-
- if (EXT_HEADER(efuse_data)) {
- efuse_addr++;
- efuse_OneByteRead(padapter, efuse_addr, &efuse_data, bPseudoTest);
-
- if (ALL_WORDS_DISABLED(efuse_data)) {
- efuse_addr++;
- continue;
- }
-
- hworden = efuse_data & 0x0F;
- } else {
- hworden = efuse_data & 0x0F;
- }
-
- word_cnts = Efuse_CalculateWordCnts(hworden);
- /* read next header */
- efuse_addr += (word_cnts*2)+1;
- }
-
- /* Check if we need to check next bank efuse */
- if (efuse_addr < retU2)
- break; /* don't need to check next bank. */
- }
-
- retU2 = ((bank-1)*EFUSE_BT_REAL_BANK_CONTENT_LEN)+efuse_addr;
- if (bPseudoTest) {
- pEfuseHal->fakeBTEfuseUsedBytes = retU2;
- } else {
- pEfuseHal->BTEfuseUsedBytes = retU2;
- }
-
- return retU2;
-}
-
-u16 Hal_EfuseGetCurrentSize(
- struct adapter *padapter, u8 efuseType, bool bPseudoTest
+ u8 *pbuf
)
{
- u16 ret = 0;
-
if (efuseType == EFUSE_WIFI)
- ret = hal_EfuseGetCurrentSize_WiFi(padapter, bPseudoTest);
+ hal_ReadEFuse_WiFi(padapter, _offset, _size_byte, pbuf);
else
- ret = hal_EfuseGetCurrentSize_BT(padapter, bPseudoTest);
-
- return ret;
+ hal_ReadEFuse_BT(padapter, _offset, _size_byte, pbuf);
}
static struct hal_version ReadChipVersion8723B(struct adapter *padapter)
@@ -1438,12 +1186,12 @@ void Hal_InitPGData(struct adapter *padapter, u8 *PROMContent)
if (!pEEPROM->bautoload_fail_flag) { /* autoload OK. */
if (!pEEPROM->EepromOrEfuse) {
/* Read EFUSE real map to shadow. */
- EFUSE_ShadowMapUpdate(padapter, EFUSE_WIFI, false);
+ EFUSE_ShadowMapUpdate(padapter, EFUSE_WIFI);
memcpy((void *)PROMContent, (void *)pEEPROM->efuse_eeprom_data, HWSET_MAX_SIZE_8723B);
}
} else {/* autoload fail */
if (!pEEPROM->EepromOrEfuse)
- EFUSE_ShadowMapUpdate(padapter, EFUSE_WIFI, false);
+ EFUSE_ShadowMapUpdate(padapter, EFUSE_WIFI);
memcpy((void *)PROMContent, (void *)pEEPROM->efuse_eeprom_data, HWSET_MAX_SIZE_8723B);
}
}
@@ -1700,9 +1448,9 @@ void Hal_EfuseParsePackageType_8723B(
u8 package;
u8 efuseContent;
- Efuse_PowerSwitch(padapter, false, true);
- efuse_OneByteRead(padapter, 0x1FB, &efuseContent, false);
- Efuse_PowerSwitch(padapter, false, false);
+ Hal_EfusePowerSwitch(padapter, true);
+ efuse_OneByteRead(padapter, 0x1FB, &efuseContent);
+ Hal_EfusePowerSwitch(padapter, false);
package = efuseContent & 0x7;
switch (package) {
@@ -1763,14 +1511,6 @@ void Hal_EfuseParseCustomerID_8723B(
pHalData->EEPROMCustomerID = 0;
}
-void Hal_EfuseParseAntennaDiversity_8723B(
- struct adapter *padapter,
- u8 *hwinfo,
- bool AutoLoadFail
-)
-{
-}
-
void Hal_EfuseParseXtal_8723B(
struct adapter *padapter, u8 *hwinfo, bool AutoLoadFail
)
diff --git a/drivers/staging/rtl8723bs/hal/rtl8723bs_recv.c b/drivers/staging/rtl8723bs/hal/rtl8723bs_recv.c
index 28c914ec2604..399edfbf8ec6 100644
--- a/drivers/staging/rtl8723bs/hal/rtl8723bs_recv.c
+++ b/drivers/staging/rtl8723bs/hal/rtl8723bs_recv.c
@@ -431,7 +431,8 @@ initbuferror:
precvpriv->free_recv_buf_queue_cnt = 0;
for (i = 0; i < n ; i++) {
list_del_init(&precvbuf->list);
- rtw_os_recvbuf_resource_free(padapter, precvbuf);
+ if (precvbuf->pskb)
+ dev_kfree_skb_any(precvbuf->pskb);
precvbuf++;
}
precvpriv->precv_buf = NULL;
@@ -467,7 +468,8 @@ void rtl8723bs_free_recv_priv(struct adapter *padapter)
precvpriv->free_recv_buf_queue_cnt = 0;
for (i = 0; i < NR_RECVBUFF; i++) {
list_del_init(&precvbuf->list);
- rtw_os_recvbuf_resource_free(padapter, precvbuf);
+ if (precvbuf->pskb)
+ dev_kfree_skb_any(precvbuf->pskb);
precvbuf++;
}
precvpriv->precv_buf = NULL;
diff --git a/drivers/staging/rtl8723bs/hal/rtl8723bs_xmit.c b/drivers/staging/rtl8723bs/hal/rtl8723bs_xmit.c
index 842e19b53421..abb6fdfe7e1f 100644
--- a/drivers/staging/rtl8723bs/hal/rtl8723bs_xmit.c
+++ b/drivers/staging/rtl8723bs/hal/rtl8723bs_xmit.c
@@ -76,7 +76,7 @@ query_free_page:
/* check if hardware tx fifo page is enough */
if (!rtw_hal_sdio_query_tx_freepage(pri_padapter, PageIdx, pxmitbuf->pg_num)) {
if (!bUpdatePageNum) {
- /* Total number of page is NOT available, so update current FIFO status */
+ /* Total page count is not available, so update current FIFO status */
HalQueryTxBufferStatus8723BSdio(padapter);
bUpdatePageNum = true;
goto query_free_page;
diff --git a/drivers/staging/rtl8723bs/hal/sdio_halinit.c b/drivers/staging/rtl8723bs/hal/sdio_halinit.c
index 73561a63401e..7fcb874d0eb3 100644
--- a/drivers/staging/rtl8723bs/hal/sdio_halinit.c
+++ b/drivers/staging/rtl8723bs/hal/sdio_halinit.c
@@ -1071,7 +1071,6 @@ static void _ReadEfuseInfo8723BS(struct adapter *padapter)
Hal_EfuseParseChnlPlan_8723B(padapter, hwinfo, pEEPROM->bautoload_fail_flag);
Hal_EfuseParseXtal_8723B(padapter, hwinfo, pEEPROM->bautoload_fail_flag);
Hal_EfuseParseThermalMeter_8723B(padapter, hwinfo, pEEPROM->bautoload_fail_flag);
- Hal_EfuseParseAntennaDiversity_8723B(padapter, hwinfo, pEEPROM->bautoload_fail_flag);
Hal_EfuseParseCustomerID_8723B(padapter, hwinfo, pEEPROM->bautoload_fail_flag);
Hal_EfuseParseVoltage_8723B(padapter, hwinfo, pEEPROM->bautoload_fail_flag);
diff --git a/drivers/staging/rtl8723bs/include/basic_types.h b/drivers/staging/rtl8723bs/include/basic_types.h
index 24626e65fc7f..1c2da18e6210 100644
--- a/drivers/staging/rtl8723bs/include/basic_types.h
+++ b/drivers/staging/rtl8723bs/include/basic_types.h
@@ -22,11 +22,11 @@
/* TODO: Belows are Sync from SD7-Driver. It is necessary to check correctness */
/*
- *Call endian free function when
+ * Call endian free function when
* 1. Read/write packet content.
* 2. Before write integer to IO.
* 3. After read integer from IO.
-*/
+ */
/* */
/* Byte Swapping routine. */
@@ -68,7 +68,8 @@
(*((u32 *)(_ptr))) = EF2BYTE(_val); \
} while (0)
-/* Create a bit mask
+/*
+ * Create a bit mask
* Examples:
* BIT_LEN_MASK_32(0) => 0x00000000
* BIT_LEN_MASK_32(1) => 0x00000001
@@ -82,7 +83,8 @@
#define BIT_LEN_MASK_8(__bitlen) \
(0xFF >> (8 - (__bitlen)))
-/* Create an offset bit mask
+/*
+ * Create an offset bit mask
* Examples:
* BIT_OFFSET_LEN_MASK_32(0, 2) => 0x00000003
* BIT_OFFSET_LEN_MASK_32(16, 2) => 0x00030000
@@ -94,7 +96,8 @@
#define BIT_OFFSET_LEN_MASK_8(__bitoffset, __bitlen) \
(BIT_LEN_MASK_8(__bitlen) << (__bitoffset))
-/*Description:
+/*
+ * Description:
* Return 4-byte value in host byte ordering from
* 4-byte pointer in little-endian system.
*/
@@ -105,11 +108,11 @@
#define LE_P1BYTE_TO_HOST_1BYTE(__pstart) \
(EF1BYTE(*((u8 *)(__pstart))))
-/* */
-/* Description: */
-/* Translate subfield (continuous bits in little-endian) of 4-byte value in litten byte to */
-/* 4-byte value in host byte ordering. */
-/* */
+/*
+ * Description:
+ * Translate subfield (continuous bits in little-endian) of 4-byte value in
+ * little byte to 4-byte value in host byte ordering.
+ */
#define LE_BITS_TO_4BYTE(__pstart, __bitoffset, __bitlen) \
(\
(LE_P4BYTE_TO_HOST_4BYTE(__pstart) >> (__bitoffset)) & \
@@ -126,11 +129,11 @@
BIT_LEN_MASK_8(__bitlen) \
)
-/* */
-/* Description: */
-/* Mask subfield (continuous bits in little-endian) of 4-byte value in litten byte oredering */
-/* and return the result in 4-byte value in host byte ordering. */
-/* */
+/*
+ * Description:
+ * Mask subfield (continuous bits in little-endian) of 4-byte value in little
+ * byte ordering and return the result in 4-byte value in host byte ordering.
+ */
#define LE_BITS_CLEARED_TO_4BYTE(__pstart, __bitoffset, __bitlen) \
(\
LE_P4BYTE_TO_HOST_4BYTE(__pstart) & \
@@ -147,10 +150,10 @@
(~BIT_OFFSET_LEN_MASK_8(__bitoffset, __bitlen)) \
)
-/* */
-/* Description: */
-/* Set subfield of little-endian 4-byte value to specified value. */
-/* */
+/*
+ * Description:
+ * Set subfield of little-endian 4-byte value to specified value.
+ */
#define SET_BITS_TO_LE_4BYTE(__pstart, __bitoffset, __bitlen, __val) \
*((u32 *)(__pstart)) = \
( \
diff --git a/drivers/staging/rtl8723bs/include/drv_types.h b/drivers/staging/rtl8723bs/include/drv_types.h
index 080c321665c0..dd9018aa4ee5 100644
--- a/drivers/staging/rtl8723bs/include/drv_types.h
+++ b/drivers/staging/rtl8723bs/include/drv_types.h
@@ -33,14 +33,12 @@
#include <xmit_osdep.h>
#include <rtw_recv.h>
-#include <recv_osdep.h>
#include <rtw_efuse.h>
#include <hal_intf.h>
#include <hal_com.h>
#include <rtw_qos.h>
#include <rtw_pwrctrl.h>
#include <rtw_mlme.h>
-#include <mlme_osdep.h>
#include <rtw_io.h>
#include <rtw_ioctl_set.h>
#include <osdep_intf.h>
diff --git a/drivers/staging/rtl8723bs/include/hal_intf.h b/drivers/staging/rtl8723bs/include/hal_intf.h
index 67d51e55bd44..2fa2382ad5f3 100644
--- a/drivers/staging/rtl8723bs/include/hal_intf.h
+++ b/drivers/staging/rtl8723bs/include/hal_intf.h
@@ -265,11 +265,10 @@ u8 GetHalDefVar8723BSDIO(struct adapter *Adapter, enum hal_def_variable eVariabl
u8 SetHalDefVar8723BSDIO(struct adapter *Adapter, enum hal_def_variable eVariable, void *pValue);
void UpdateHalRAMask8723B(struct adapter *padapter, u32 mac_id, u8 rssi_level);
void rtl8723b_SetBeaconRelatedRegisters(struct adapter *padapter);
-void Hal_EfusePowerSwitch(struct adapter *padapter, u8 bWrite, u8 PwrState);
+void Hal_EfusePowerSwitch(struct adapter *padapter, u8 PwrState);
void Hal_ReadEFuse(struct adapter *padapter, u8 efuseType, u16 _offset,
- u16 _size_byte, u8 *pbuf, bool bPseudoTest);
+ u16 _size_byte, u8 *pbuf);
void Hal_GetEfuseDefinition(struct adapter *padapter, u8 efuseType, u8 type,
- void *pOut, bool bPseudoTest);
-u16 Hal_EfuseGetCurrentSize(struct adapter *padapter, u8 efuseType, bool bPseudoTest);
+ void *pOut);
void hal_notch_filter_8723b(struct adapter *adapter, bool enable);
#endif /* __HAL_INTF_H__ */
diff --git a/drivers/staging/rtl8723bs/include/mlme_osdep.h b/drivers/staging/rtl8723bs/include/mlme_osdep.h
deleted file mode 100644
index f0d19637fb0f..000000000000
--- a/drivers/staging/rtl8723bs/include/mlme_osdep.h
+++ /dev/null
@@ -1,19 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/******************************************************************************
- *
- * Copyright(c) 2007 - 2011 Realtek Corporation. All rights reserved.
- *
- ******************************************************************************/
-#ifndef __MLME_OSDEP_H_
-#define __MLME_OSDEP_H_
-
-
-extern void rtw_init_mlme_timer(struct adapter *padapter);
-extern void rtw_os_indicate_disconnect(struct adapter *adapter);
-extern void rtw_os_indicate_connect(struct adapter *adapter);
-void rtw_os_indicate_scan_done(struct adapter *padapter, bool aborted);
-extern void rtw_report_sec_ie(struct adapter *adapter, u8 authmode, u8 *sec_ie);
-
-void rtw_reset_securitypriv(struct adapter *adapter);
-
-#endif /* _MLME_OSDEP_H_ */
diff --git a/drivers/staging/rtl8723bs/include/recv_osdep.h b/drivers/staging/rtl8723bs/include/recv_osdep.h
deleted file mode 100644
index 83330ea98fbf..000000000000
--- a/drivers/staging/rtl8723bs/include/recv_osdep.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/******************************************************************************
- *
- * Copyright(c) 2007 - 2011 Realtek Corporation. All rights reserved.
- *
- ******************************************************************************/
-#ifndef __RECV_OSDEP_H_
-#define __RECV_OSDEP_H_
-
-
-extern signed int _rtw_init_recv_priv(struct recv_priv *precvpriv, struct adapter *padapter);
-extern void _rtw_free_recv_priv(struct recv_priv *precvpriv);
-
-
-extern s32 rtw_recv_entry(union recv_frame *precv_frame);
-extern int rtw_recv_indicatepkt(struct adapter *adapter, union recv_frame *precv_frame);
-extern void rtw_recv_returnpacket(struct net_device *cnxt, struct sk_buff *preturnedpkt);
-
-extern void rtw_handle_tkip_mic_err(struct adapter *padapter, u8 bgroup);
-
-int rtw_init_recv_priv(struct recv_priv *precvpriv, struct adapter *padapter);
-void rtw_free_recv_priv(struct recv_priv *precvpriv);
-
-
-void rtw_os_recv_resource_alloc(struct adapter *padapter, union recv_frame *precvframe);
-void rtw_os_recv_resource_free(struct recv_priv *precvpriv);
-
-
-void rtw_os_free_recvframe(union recv_frame *precvframe);
-
-
-void rtw_os_recvbuf_resource_free(struct adapter *padapter, struct recv_buf *precvbuf);
-
-struct sk_buff *rtw_os_alloc_msdu_pkt(union recv_frame *prframe, u16 nSubframe_Length, u8 *pdata);
-void rtw_os_recv_indicate_pkt(struct adapter *padapter, struct sk_buff *pkt, struct rx_pkt_attrib *pattrib);
-
-void rtw_init_recv_timer(struct recv_reorder_ctrl *preorder_ctrl);
-
-
-#endif /* */
diff --git a/drivers/staging/rtl8723bs/include/rtl8723b_hal.h b/drivers/staging/rtl8723bs/include/rtl8723b_hal.h
index 40ff96d3cf74..2ed1fc8549ec 100644
--- a/drivers/staging/rtl8723bs/include/rtl8723b_hal.h
+++ b/drivers/staging/rtl8723bs/include/rtl8723b_hal.h
@@ -210,8 +210,6 @@ void Hal_EfuseParseChnlPlan_8723B(struct adapter *padapter, u8 *hwinfo,
bool AutoLoadFail);
void Hal_EfuseParseCustomerID_8723B(struct adapter *padapter, u8 *hwinfo,
bool AutoLoadFail);
-void Hal_EfuseParseAntennaDiversity_8723B(struct adapter *padapter, u8 *hwinfo,
- bool AutoLoadFail);
void Hal_EfuseParseXtal_8723B(struct adapter *padapter, u8 *hwinfo,
bool AutoLoadFail);
void Hal_EfuseParseThermalMeter_8723B(struct adapter *padapter, u8 *hwinfo,
diff --git a/drivers/staging/rtl8723bs/include/rtw_efuse.h b/drivers/staging/rtl8723bs/include/rtw_efuse.h
index 669565fa1c69..936b204b8830 100644
--- a/drivers/staging/rtl8723bs/include/rtw_efuse.h
+++ b/drivers/staging/rtl8723bs/include/rtw_efuse.h
@@ -1,9 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 */
-/******************************************************************************
- *
- * Copyright(c) 2007 - 2011 Realtek Corporation. All rights reserved.
- *
- ******************************************************************************/
+/* Copyright(c) 2007 - 2011 Realtek Corporation. All rights reserved. */
+
#ifndef __RTW_EFUSE_H__
#define __RTW_EFUSE_H__
@@ -91,14 +88,10 @@ extern u8 fakeBTEfuseModifiedMap[];
/*------------------------Export global variable----------------------------*/
u8 Efuse_CalculateWordCnts(u8 word_en);
-void EFUSE_GetEfuseDefinition(struct adapter *padapter, u8 efuseType, u8 type, void *pOut, bool bPseudoTest);
-u8 efuse_OneByteRead(struct adapter *padapter, u16 addr, u8 *data, bool bPseudoTest);
-u8 efuse_OneByteWrite(struct adapter *padapter, u16 addr, u8 data, bool bPseudoTest);
-
-void Efuse_PowerSwitch(struct adapter *padapter, u8 bWrite, u8 PwrState);
+u8 efuse_OneByteRead(struct adapter *padapter, u16 addr, u8 *data);
u8 EFUSE_Read1Byte(struct adapter *padapter, u16 Address);
-void EFUSE_ShadowMapUpdate(struct adapter *padapter, u8 efuseType, bool bPseudoTest);
+void EFUSE_ShadowMapUpdate(struct adapter *padapter, u8 efuseType);
void EFUSE_ShadowRead(struct adapter *padapter, u8 Type, u16 Offset, u32 *Value);
void Rtw_Hal_ReadMACAddrFromFile(struct adapter *padapter);
u32 Rtw_Hal_readPGDataFromConfigFile(struct adapter *padapter);
diff --git a/drivers/staging/rtl8723bs/include/rtw_mlme.h b/drivers/staging/rtl8723bs/include/rtw_mlme.h
index 3cf68b85eb32..4c15d0194d4f 100644
--- a/drivers/staging/rtl8723bs/include/rtw_mlme.h
+++ b/drivers/staging/rtl8723bs/include/rtw_mlme.h
@@ -395,5 +395,6 @@ u8 rtw_to_roam(struct adapter *adapter);
int rtw_select_roaming_candidate(struct mlme_priv *pmlmepriv);
void rtw_sta_media_status_rpt(struct adapter *adapter, struct sta_info *psta, u32 mstatus);
+void rtw_reset_securitypriv(struct adapter *adapter);
#endif /* __RTL871X_MLME_H_ */
diff --git a/drivers/staging/rtl8723bs/include/rtw_mlme_ext.h b/drivers/staging/rtl8723bs/include/rtw_mlme_ext.h
index 2080408743ef..53fac838c36a 100644
--- a/drivers/staging/rtl8723bs/include/rtw_mlme_ext.h
+++ b/drivers/staging/rtl8723bs/include/rtw_mlme_ext.h
@@ -426,8 +426,6 @@ void init_mlme_default_rate_set(struct adapter *padapter);
void init_mlme_ext_priv(struct adapter *padapter);
int init_hw_mlme_ext(struct adapter *padapter);
void free_mlme_ext_priv(struct mlme_ext_priv *pmlmeext);
-extern void init_mlme_ext_timer(struct adapter *padapter);
-extern void init_addba_retry_timer(struct adapter *padapter, struct sta_info *psta);
extern struct xmit_frame *alloc_mgtxmitframe(struct xmit_priv *pxmitpriv);
/* void fill_fwpriv(struct adapter *padapter, struct fw_priv *pfwpriv); */
diff --git a/drivers/staging/rtl8723bs/include/rtw_recv.h b/drivers/staging/rtl8723bs/include/rtw_recv.h
index aa9f9d5ecd01..8e45871f07f0 100644
--- a/drivers/staging/rtl8723bs/include/rtw_recv.h
+++ b/drivers/staging/rtl8723bs/include/rtw_recv.h
@@ -342,6 +342,10 @@ struct recv_buf *rtw_dequeue_recvbuf(struct __queue *queue);
void rtw_reordering_ctrl_timeout_handler(struct timer_list *t);
+signed int _rtw_init_recv_priv(struct recv_priv *precvpriv, struct adapter *padapter);
+void _rtw_free_recv_priv(struct recv_priv *precvpriv);
+s32 rtw_recv_entry(union recv_frame *precv_frame);
+
static inline u8 *get_rxmem(union recv_frame *precvframe)
{
/* always return rx_head... */
diff --git a/drivers/staging/rtl8723bs/os_dep/mlme_linux.c b/drivers/staging/rtl8723bs/os_dep/mlme_linux.c
deleted file mode 100644
index fd4ae870a617..000000000000
--- a/drivers/staging/rtl8723bs/os_dep/mlme_linux.c
+++ /dev/null
@@ -1,179 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/******************************************************************************
- *
- * Copyright(c) 2007 - 2011 Realtek Corporation. All rights reserved.
- *
- ******************************************************************************/
-#include <drv_types.h>
-
-static void _dynamic_check_timer_handler(struct timer_list *t)
-{
- struct adapter *adapter =
- timer_container_of(adapter, t, mlmepriv.dynamic_chk_timer);
-
- rtw_dynamic_check_timer_handler(adapter);
-
- _set_timer(&adapter->mlmepriv.dynamic_chk_timer, 2000);
-}
-
-static void _rtw_set_scan_deny_timer_hdl(struct timer_list *t)
-{
- struct adapter *adapter =
- timer_container_of(adapter, t, mlmepriv.set_scan_deny_timer);
-
- rtw_clear_scan_deny(adapter);
-}
-
-void rtw_init_mlme_timer(struct adapter *padapter)
-{
- struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
-
- timer_setup(&pmlmepriv->assoc_timer, _rtw_join_timeout_handler, 0);
- timer_setup(&pmlmepriv->scan_to_timer, rtw_scan_timeout_handler, 0);
- timer_setup(&pmlmepriv->dynamic_chk_timer,
- _dynamic_check_timer_handler, 0);
- timer_setup(&pmlmepriv->set_scan_deny_timer,
- _rtw_set_scan_deny_timer_hdl, 0);
-}
-
-void rtw_os_indicate_connect(struct adapter *adapter)
-{
- struct mlme_priv *pmlmepriv = &(adapter->mlmepriv);
-
- if ((check_fwstate(pmlmepriv, WIFI_ADHOC_MASTER_STATE) == true) ||
- (check_fwstate(pmlmepriv, WIFI_ADHOC_STATE) == true)) {
- rtw_cfg80211_ibss_indicate_connect(adapter);
- } else {
- rtw_cfg80211_indicate_connect(adapter);
- }
-
- netif_carrier_on(adapter->pnetdev);
-
- if (adapter->pid[2] != 0)
- rtw_signal_process(adapter->pid[2], SIGALRM);
-}
-
-void rtw_os_indicate_scan_done(struct adapter *padapter, bool aborted)
-{
- rtw_cfg80211_indicate_scan_done(padapter, aborted);
-}
-
-static struct rt_pmkid_list backupPMKIDList[NUM_PMKID_CACHE];
-void rtw_reset_securitypriv(struct adapter *adapter)
-{
- u8 backupPMKIDIndex = 0;
- u8 backupTKIPCountermeasure = 0x00;
- u32 backupTKIPcountermeasure_time = 0;
- /* add for CONFIG_IEEE80211W, none 11w also can use */
- struct mlme_ext_priv *pmlmeext = &adapter->mlmeextpriv;
-
- spin_lock_bh(&adapter->security_key_mutex);
-
- if (adapter->securitypriv.dot11AuthAlgrthm == dot11AuthAlgrthm_8021X) {
- /* 802.1x */
- /* Added by Albert 2009/02/18 */
- /* We have to backup the PMK information for WiFi PMK Caching test item. */
- /* */
- /* Backup the btkip_countermeasure information. */
- /* When the countermeasure is trigger, the driver have to disconnect with AP for 60 seconds. */
-
- memcpy(&backupPMKIDList[0], &adapter->securitypriv.PMKIDList[0], sizeof(struct rt_pmkid_list) * NUM_PMKID_CACHE);
- backupPMKIDIndex = adapter->securitypriv.PMKIDIndex;
- backupTKIPCountermeasure = adapter->securitypriv.btkip_countermeasure;
- backupTKIPcountermeasure_time = adapter->securitypriv.btkip_countermeasure_time;
-
- /* reset RX BIP packet number */
- pmlmeext->mgnt_80211w_IPN_rx = 0;
-
- memset((unsigned char *)&adapter->securitypriv, 0, sizeof(struct security_priv));
-
- /* Added by Albert 2009/02/18 */
- /* Restore the PMK information to securitypriv structure for the following connection. */
- memcpy(&adapter->securitypriv.PMKIDList[0], &backupPMKIDList[0], sizeof(struct rt_pmkid_list) * NUM_PMKID_CACHE);
- adapter->securitypriv.PMKIDIndex = backupPMKIDIndex;
- adapter->securitypriv.btkip_countermeasure = backupTKIPCountermeasure;
- adapter->securitypriv.btkip_countermeasure_time = backupTKIPcountermeasure_time;
-
- adapter->securitypriv.ndisauthtype = Ndis802_11AuthModeOpen;
- adapter->securitypriv.ndisencryptstatus = Ndis802_11WEPDisabled;
-
- } else {
- /* reset values in securitypriv */
- /* if (adapter->mlmepriv.fw_state & WIFI_STATION_STATE) */
- /* */
- struct security_priv *psec_priv = &adapter->securitypriv;
-
- psec_priv->dot11AuthAlgrthm = dot11AuthAlgrthm_Open; /* open system */
- psec_priv->dot11PrivacyAlgrthm = _NO_PRIVACY_;
- psec_priv->dot11PrivacyKeyIndex = 0;
-
- psec_priv->dot118021XGrpPrivacy = _NO_PRIVACY_;
- psec_priv->dot118021XGrpKeyid = 1;
-
- psec_priv->ndisauthtype = Ndis802_11AuthModeOpen;
- psec_priv->ndisencryptstatus = Ndis802_11WEPDisabled;
- /* */
- }
- /* add for CONFIG_IEEE80211W, none 11w also can use */
- spin_unlock_bh(&adapter->security_key_mutex);
-}
-
-void rtw_os_indicate_disconnect(struct adapter *adapter)
-{
- /* struct rt_pmkid_list backupPMKIDList[ NUM_PMKID_CACHE ]; */
-
- netif_carrier_off(adapter->pnetdev); /* Do it first for tx broadcast pkt after disconnection issue! */
-
- rtw_cfg80211_indicate_disconnect(adapter);
-
- /* modify for CONFIG_IEEE80211W, none 11w also can use the same command */
- rtw_reset_securitypriv_cmd(adapter);
-}
-
-void rtw_report_sec_ie(struct adapter *adapter, u8 authmode, u8 *sec_ie)
-{
- uint len;
- u8 *buff, *p, i;
- union iwreq_data wrqu;
-
- buff = NULL;
- if (authmode == WLAN_EID_VENDOR_SPECIFIC) {
- buff = rtw_zmalloc(IW_CUSTOM_MAX);
- if (!buff)
- return;
-
- p = buff;
-
- p += scnprintf(p, IW_CUSTOM_MAX - (p - buff), "ASSOCINFO(ReqIEs =");
-
- len = sec_ie[1] + 2;
- len = (len < IW_CUSTOM_MAX) ? len : IW_CUSTOM_MAX;
-
- for (i = 0; i < len; i++)
- p += scnprintf(p, IW_CUSTOM_MAX - (p - buff), "%02x", sec_ie[i]);
-
- p += scnprintf(p, IW_CUSTOM_MAX - (p - buff), ")");
-
- memset(&wrqu, 0, sizeof(wrqu));
-
- wrqu.data.length = p - buff;
-
- wrqu.data.length = (wrqu.data.length < IW_CUSTOM_MAX) ? wrqu.data.length : IW_CUSTOM_MAX;
-
- kfree(buff);
- }
-}
-
-void init_addba_retry_timer(struct adapter *padapter, struct sta_info *psta)
-{
- timer_setup(&psta->addba_retry_timer, addba_timer_hdl, 0);
-}
-
-void init_mlme_ext_timer(struct adapter *padapter)
-{
- struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
-
- timer_setup(&pmlmeext->survey_timer, survey_timer_hdl, 0);
- timer_setup(&pmlmeext->link_timer, link_timer_hdl, 0);
- timer_setup(&pmlmeext->sa_query_timer, sa_query_timer_hdl, 0);
-}
diff --git a/drivers/staging/rtl8723bs/os_dep/recv_linux.c b/drivers/staging/rtl8723bs/os_dep/recv_linux.c
deleted file mode 100644
index 98d3e4777210..000000000000
--- a/drivers/staging/rtl8723bs/os_dep/recv_linux.c
+++ /dev/null
@@ -1,225 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/******************************************************************************
- *
- * Copyright(c) 2007 - 2011 Realtek Corporation. All rights reserved.
- *
- ******************************************************************************/
-#include <drv_types.h>
-#include <linux/jiffies.h>
-#include <net/cfg80211.h>
-#include <linux/unaligned.h>
-
-void rtw_os_free_recvframe(union recv_frame *precvframe)
-{
- if (precvframe->u.hdr.pkt) {
- dev_kfree_skb_any(precvframe->u.hdr.pkt);/* free skb by driver */
-
- precvframe->u.hdr.pkt = NULL;
- }
-}
-
-/* alloc os related resource in union recv_frame */
-void rtw_os_recv_resource_alloc(struct adapter *padapter, union recv_frame *precvframe)
-{
- precvframe->u.hdr.pkt_newalloc = precvframe->u.hdr.pkt = NULL;
-}
-
-/* free os related resource in union recv_frame */
-void rtw_os_recv_resource_free(struct recv_priv *precvpriv)
-{
- signed int i;
- union recv_frame *precvframe;
-
- precvframe = (union recv_frame *) precvpriv->precv_frame_buf;
-
- for (i = 0; i < NR_RECVFRAME; i++) {
- if (precvframe->u.hdr.pkt) {
- /* free skb by driver */
- dev_kfree_skb_any(precvframe->u.hdr.pkt);
- precvframe->u.hdr.pkt = NULL;
- }
- precvframe++;
- }
-}
-
-/* free os related resource in struct recv_buf */
-void rtw_os_recvbuf_resource_free(struct adapter *padapter, struct recv_buf *precvbuf)
-{
- if (precvbuf->pskb)
- dev_kfree_skb_any(precvbuf->pskb);
-}
-
-struct sk_buff *rtw_os_alloc_msdu_pkt(union recv_frame *prframe, u16 nSubframe_Length, u8 *pdata)
-{
- u16 eth_type;
- struct sk_buff *sub_skb;
- struct rx_pkt_attrib *pattrib;
-
- pattrib = &prframe->u.hdr.attrib;
-
- sub_skb = rtw_skb_alloc(nSubframe_Length + 12);
- if (!sub_skb)
- return NULL;
-
- skb_reserve(sub_skb, 12);
- skb_put_data(sub_skb, (pdata + ETH_HLEN), nSubframe_Length);
-
- eth_type = get_unaligned_be16(&sub_skb->data[6]);
-
- if (sub_skb->len >= 8 &&
- ((!memcmp(sub_skb->data, rfc1042_header, SNAP_SIZE) &&
- eth_type != ETH_P_AARP && eth_type != ETH_P_IPX) ||
- !memcmp(sub_skb->data, bridge_tunnel_header, SNAP_SIZE))) {
- /*
- * remove RFC1042 or Bridge-Tunnel encapsulation and replace
- * EtherType
- */
- skb_pull(sub_skb, SNAP_SIZE);
- memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->src, ETH_ALEN);
- memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->dst, ETH_ALEN);
- } else {
- __be16 len;
- /* Leave Ethernet header part of hdr and full payload */
- len = htons(sub_skb->len);
- memcpy(skb_push(sub_skb, 2), &len, 2);
- memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->src, ETH_ALEN);
- memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->dst, ETH_ALEN);
- }
-
- return sub_skb;
-}
-
-void rtw_os_recv_indicate_pkt(struct adapter *padapter, struct sk_buff *pkt, struct rx_pkt_attrib *pattrib)
-{
- struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
-
- /* Indicate the packets to upper layer */
- if (pkt) {
- if (check_fwstate(pmlmepriv, WIFI_AP_STATE) == true) {
- struct sk_buff *pskb2 = NULL;
- struct sta_info *psta = NULL;
- struct sta_priv *pstapriv = &padapter->stapriv;
- int bmcast = is_multicast_ether_addr(pattrib->dst);
-
- if (memcmp(pattrib->dst, myid(&padapter->eeprompriv), ETH_ALEN)) {
- if (bmcast) {
- psta = rtw_get_bcmc_stainfo(padapter);
- pskb2 = skb_clone(pkt, GFP_ATOMIC);
- } else {
- psta = rtw_get_stainfo(pstapriv, pattrib->dst);
- }
-
- if (psta) {
- struct net_device *pnetdev = (struct net_device *)padapter->pnetdev;
- /* skb->ip_summed = CHECKSUM_NONE; */
- pkt->dev = pnetdev;
- skb_set_queue_mapping(pkt, rtw_recv_select_queue(pkt));
-
- _rtw_xmit_entry(pkt, pnetdev);
-
- if (bmcast && pskb2)
- pkt = pskb2;
- else
- return;
- }
- } else {
- /* to APself */
- }
- }
-
- pkt->protocol = eth_type_trans(pkt, padapter->pnetdev);
- pkt->dev = padapter->pnetdev;
-
- pkt->ip_summed = CHECKSUM_NONE;
-
- rtw_netif_rx(padapter->pnetdev, pkt);
- }
-}
-
-void rtw_handle_tkip_mic_err(struct adapter *padapter, u8 bgroup)
-{
- enum nl80211_key_type key_type = 0;
- union iwreq_data wrqu;
- struct iw_michaelmicfailure ev;
- struct mlme_priv *pmlmepriv = &padapter->mlmepriv;
- struct security_priv *psecuritypriv = &padapter->securitypriv;
- unsigned long cur_time = 0;
-
- if (psecuritypriv->last_mic_err_time == 0) {
- psecuritypriv->last_mic_err_time = jiffies;
- } else {
- cur_time = jiffies;
-
- if (cur_time - psecuritypriv->last_mic_err_time < 60*HZ) {
- psecuritypriv->btkip_countermeasure = true;
- psecuritypriv->last_mic_err_time = 0;
- psecuritypriv->btkip_countermeasure_time = cur_time;
- } else {
- psecuritypriv->last_mic_err_time = jiffies;
- }
- }
-
- if (bgroup)
- key_type |= NL80211_KEYTYPE_GROUP;
- else
- key_type |= NL80211_KEYTYPE_PAIRWISE;
-
- cfg80211_michael_mic_failure(padapter->pnetdev, (u8 *)&pmlmepriv->assoc_bssid[0], key_type, -1,
- NULL, GFP_ATOMIC);
-
- memset(&ev, 0x00, sizeof(ev));
- if (bgroup)
- ev.flags |= IW_MICFAILURE_GROUP;
- else
- ev.flags |= IW_MICFAILURE_PAIRWISE;
-
- ev.src_addr.sa_family = ARPHRD_ETHER;
- memcpy(ev.src_addr.sa_data, &pmlmepriv->assoc_bssid[0], ETH_ALEN);
-
- memset(&wrqu, 0x00, sizeof(wrqu));
- wrqu.data.length = sizeof(ev);
-}
-
-int rtw_recv_indicatepkt(struct adapter *padapter, union recv_frame *precv_frame)
-{
- struct recv_priv *precvpriv;
- struct __queue *pfree_recv_queue;
- struct sk_buff *skb;
- struct rx_pkt_attrib *pattrib = &precv_frame->u.hdr.attrib;
-
- precvpriv = &(padapter->recvpriv);
- pfree_recv_queue = &(precvpriv->free_recv_queue);
-
- skb = precv_frame->u.hdr.pkt;
- if (!skb)
- goto _recv_indicatepkt_drop;
-
- skb->data = precv_frame->u.hdr.rx_data;
-
- skb_set_tail_pointer(skb, precv_frame->u.hdr.len);
-
- skb->len = precv_frame->u.hdr.len;
-
- rtw_os_recv_indicate_pkt(padapter, skb, pattrib);
-
- /* pointers to NULL before rtw_free_recvframe() */
- precv_frame->u.hdr.pkt = NULL;
-
- rtw_free_recvframe(precv_frame, pfree_recv_queue);
-
- return _SUCCESS;
-
-_recv_indicatepkt_drop:
-
- /* enqueue back to free_recv_queue */
- rtw_free_recvframe(precv_frame, pfree_recv_queue);
-
- return _FAIL;
-}
-
-void rtw_init_recv_timer(struct recv_reorder_ctrl *preorder_ctrl)
-{
- timer_setup(&preorder_ctrl->reordering_ctrl_timer,
- rtw_reordering_ctrl_timeout_handler, 0);
-
-}
diff --git a/drivers/staging/sm750fb/sm750.h b/drivers/staging/sm750fb/sm750.h
index d7f40efe3a2c..fcb7d586ebf0 100644
--- a/drivers/staging/sm750fb/sm750.h
+++ b/drivers/staging/sm750fb/sm750.h
@@ -50,9 +50,9 @@ struct init_status {
struct lynx_accel {
/* base virtual address of DPR registers */
- volatile unsigned char __iomem *dprBase;
+ unsigned char __iomem *dpr_base;
/* base virtual address of de data port */
- volatile unsigned char __iomem *dpPortBase;
+ unsigned char __iomem *dp_port_base;
/* function pointers */
void (*de_init)(struct lynx_accel *accel);
@@ -128,7 +128,7 @@ struct lynx_cursor {
char __iomem *vstart;
int offset;
/* mmio addr of hw cursor */
- volatile char __iomem *mmio;
+ char __iomem *mmio;
};
struct lynxfb_crtc {
diff --git a/drivers/staging/sm750fb/sm750_accel.c b/drivers/staging/sm750fb/sm750_accel.c
index 44b9e3fe3a41..b07c1aa68621 100644
--- a/drivers/staging/sm750fb/sm750_accel.c
+++ b/drivers/staging/sm750fb/sm750_accel.c
@@ -17,19 +17,19 @@
#include "sm750.h"
#include "sm750_accel.h"
-static inline void write_dpr(struct lynx_accel *accel, int offset, u32 regValue)
+static inline void write_dpr(struct lynx_accel *accel, int offset, u32 reg_value)
{
- writel(regValue, accel->dprBase + offset);
+ writel(reg_value, accel->dpr_base + offset);
}
static inline u32 read_dpr(struct lynx_accel *accel, int offset)
{
- return readl(accel->dprBase + offset);
+ return readl(accel->dpr_base + offset);
}
static inline void write_dpPort(struct lynx_accel *accel, u32 data)
{
- writel(data, accel->dpPortBase);
+ writel(data, accel->dp_port_base);
}
void sm750_hw_de_init(struct lynx_accel *accel)
diff --git a/drivers/staging/sm750fb/sm750_hw.c b/drivers/staging/sm750fb/sm750_hw.c
index 7119b67efe11..ce46f240cbaf 100644
--- a/drivers/staging/sm750fb/sm750_hw.c
+++ b/drivers/staging/sm750fb/sm750_hw.c
@@ -58,8 +58,8 @@ int hw_sm750_map(struct sm750_dev *sm750_dev, struct pci_dev *pdev)
}
pr_info("mmio virtual addr = %p\n", sm750_dev->pvReg);
- sm750_dev->accel.dprBase = sm750_dev->pvReg + DE_BASE_ADDR_TYPE1;
- sm750_dev->accel.dpPortBase = sm750_dev->pvReg + DE_PORT_ADDR_TYPE1;
+ sm750_dev->accel.dpr_base = sm750_dev->pvReg + DE_BASE_ADDR_TYPE1;
+ sm750_dev->accel.dp_port_base = sm750_dev->pvReg + DE_PORT_ADDR_TYPE1;
mmio750 = sm750_dev->pvReg;
sm750_set_chip_type(sm750_dev->devid, sm750_dev->revid);
diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h
index 471413248a14..1889494425eb 100644
--- a/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h
+++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h
@@ -13,7 +13,7 @@
/*
* all the data structures which serialise the MMAL protocol. note
- * these are directly mapped onto the recived message data.
+ * these are directly mapped onto the received message data.
*
* BEWARE: They seem to *assume* pointers are u32 and that there is no
* structure padding!
diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c
index 3fe482bd2793..c2b5a37915f2 100644
--- a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c
+++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c
@@ -326,7 +326,7 @@ static int bulk_receive(struct vchiq_mmal_instance *instance,
* committed a buffer_to_host operation to the mmal
* port without the buffer to back it up (underflow
* handling) and there is no obvious way to deal with
- * this - how is the mmal servie going to react when
+ * this - how is the mmal service going to react when
* we fail to do the xfer and reschedule a buffer when
* it arrives? perhaps a starved flag to indicate a
* waiting bulk receive?
diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h
index 97abe4bdcfc5..8c3959f6f97f 100644
--- a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h
+++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h
@@ -115,7 +115,7 @@ int vchiq_mmal_component_disable(struct vchiq_mmal_instance *instance,
/* enable a mmal port
*
- * enables a port and if a buffer callback provided enque buffer
+ * enables a port and, if a buffer callback provided, enqueues buffer
* headers as appropriate for the port.
*/
int vchiq_mmal_port_enable(struct vchiq_mmal_instance *instance,
diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig
index 0abdb69ee9f4..db3b0bef48f4 100644
--- a/drivers/thunderbolt/Kconfig
+++ b/drivers/thunderbolt/Kconfig
@@ -4,8 +4,8 @@ menuconfig USB4
depends on PCI
select APPLE_PROPERTIES if EFI_STUB && X86
select CRC32
- select CRYPTO
- select CRYPTO_HASH
+ select CRYPTO_LIB_SHA256
+ select CRYPTO_LIB_UTILS
select NVMEM
help
USB4 and Thunderbolt driver. USB4 is the public specification
diff --git a/drivers/thunderbolt/acpi.c b/drivers/thunderbolt/acpi.c
index d2a0054217da..45d1415871b4 100644
--- a/drivers/thunderbolt/acpi.c
+++ b/drivers/thunderbolt/acpi.c
@@ -86,7 +86,7 @@ out_put:
* @nhi ACPI node. For each reference a device link is added. The link
* is automatically removed by the driver core.
*
- * Returns %true if at least one link was created.
+ * Returns %true if at least one link was created, %false otherwise.
*/
bool tb_acpi_add_links(struct tb_nhi *nhi)
{
@@ -113,8 +113,10 @@ bool tb_acpi_add_links(struct tb_nhi *nhi)
/**
* tb_acpi_is_native() - Did the platform grant native TBT/USB4 control
*
- * Returns %true if the platform granted OS native control over
- * TBT/USB4. In this case software based connection manager can be used,
+ * Return: %true if the platform granted OS native control over
+ * TBT/USB4, %false otherwise.
+ *
+ * When returned %true, software based connection manager can be used,
* otherwise there is firmware based connection manager running.
*/
bool tb_acpi_is_native(void)
@@ -126,8 +128,8 @@ bool tb_acpi_is_native(void)
/**
* tb_acpi_may_tunnel_usb3() - Is USB3 tunneling allowed by the platform
*
- * When software based connection manager is used, this function
- * returns %true if platform allows native USB3 tunneling.
+ * Return: %true if software based connection manager is used and
+ * platform allows native USB 3.x tunneling, %false otherwise.
*/
bool tb_acpi_may_tunnel_usb3(void)
{
@@ -139,8 +141,8 @@ bool tb_acpi_may_tunnel_usb3(void)
/**
* tb_acpi_may_tunnel_dp() - Is DisplayPort tunneling allowed by the platform
*
- * When software based connection manager is used, this function
- * returns %true if platform allows native DP tunneling.
+ * Return: %true if software based connection manager is used and
+ * platform allows native DP tunneling, %false otherwise.
*/
bool tb_acpi_may_tunnel_dp(void)
{
@@ -152,8 +154,8 @@ bool tb_acpi_may_tunnel_dp(void)
/**
* tb_acpi_may_tunnel_pcie() - Is PCIe tunneling allowed by the platform
*
- * When software based connection manager is used, this function
- * returns %true if platform allows native PCIe tunneling.
+ * Return: %true if software based connection manager is used and
+ * platform allows native PCIe tunneling, %false otherwise.
*/
bool tb_acpi_may_tunnel_pcie(void)
{
@@ -165,8 +167,8 @@ bool tb_acpi_may_tunnel_pcie(void)
/**
* tb_acpi_is_xdomain_allowed() - Are XDomain connections allowed
*
- * When software based connection manager is used, this function
- * returns %true if platform allows XDomain connections.
+ * Return: %true if software based connection manager is used and
+ * platform allows XDomain tunneling, %false otherwise.
*/
bool tb_acpi_is_xdomain_allowed(void)
{
@@ -256,7 +258,7 @@ static int tb_acpi_retimer_set_power(struct tb_port *port, bool power)
*
* This should only be called if the USB4/TBT link is not up.
*
- * Returns %0 on success.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_acpi_power_on_retimers(struct tb_port *port)
{
@@ -270,7 +272,7 @@ int tb_acpi_power_on_retimers(struct tb_port *port)
* This is the opposite of tb_acpi_power_on_retimers(). After returning
* successfully the normal operations with the @port can continue.
*
- * Returns %0 on success.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_acpi_power_off_retimers(struct tb_port *port)
{
diff --git a/drivers/thunderbolt/cap.c b/drivers/thunderbolt/cap.c
index 8ecd610c62d5..4ab22d5291ac 100644
--- a/drivers/thunderbolt/cap.c
+++ b/drivers/thunderbolt/cap.c
@@ -64,10 +64,14 @@ static void tb_port_dummy_read(struct tb_port *port)
* @port: Port to find the capability for
* @offset: Previous capability offset (%0 for start)
*
- * Returns dword offset of the next capability in port config space
- * capability list and returns it. Passing %0 returns the first entry in
- * the capability list. If no next capability is found returns %0. In case
- * of failure returns negative errno.
+ * Finds dword offset of the next capability in port config space
+ * capability list. When passed %0 in @offset parameter, first entry
+ * will be returned, if it exists.
+ *
+ * Return:
+ * * Double word offset of the first or next capability - On success.
+ * * %0 - If no next capability is found.
+ * * Negative errno - Another error occurred.
*/
int tb_port_next_cap(struct tb_port *port, unsigned int offset)
{
@@ -112,9 +116,10 @@ static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
* @port: Port to find the capability for
* @cap: Capability to look
*
- * Returns offset to start of capability or %-ENOENT if no such
- * capability was found. Negative errno is returned if there was an
- * error.
+ * Return:
+ * * Offset to the start of capability - On success.
+ * * %-ENOENT - If no such capability was found.
+ * * Negative errno - Another error occurred.
*/
int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
{
@@ -137,10 +142,14 @@ int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
* @sw: Switch to find the capability for
* @offset: Previous capability offset (%0 for start)
*
- * Finds dword offset of the next capability in router config space
- * capability list and returns it. Passing %0 returns the first entry in
- * the capability list. If no next capability is found returns %0. In case
- * of failure returns negative errno.
+ * Finds dword offset of the next capability in port config space
+ * capability list. When passed %0 in @offset parameter, first entry
+ * will be returned, if it exists.
+ *
+ * Return:
+ * * Double word offset of the first or next capability - On success.
+ * * %0 - If no next capability is found.
+ * * Negative errno - Another error occurred.
*/
int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset)
{
@@ -181,9 +190,10 @@ int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset)
* @sw: Switch to find the capability for
* @cap: Capability to look
*
- * Returns offset to start of capability or %-ENOENT if no such
- * capability was found. Negative errno is returned if there was an
- * error.
+ * Return:
+ * * Offset to the start of capability - On success.
+ * * %-ENOENT - If no such capability was found.
+ * * Negative errno - Another error occurred.
*/
int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
{
@@ -213,10 +223,13 @@ int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
* @sw: Switch to find the capability for
* @vsec: Vendor specific capability to look
*
- * Functions enumerates vendor specific capabilities (VSEC) of a switch
- * and returns offset when capability matching @vsec is found. If no
- * such capability is found returns %-ENOENT. In case of error returns
- * negative errno.
+ * This function enumerates vendor specific capabilities (VSEC) of a
+ * switch and returns offset when capability matching @vsec is found.
+ *
+ * Return:
+ * * Offset of capability - On success.
+ * * %-ENOENT - If capability was not found.
+ * * Negative errno - Another error occurred.
*/
int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec)
{
diff --git a/drivers/thunderbolt/clx.c b/drivers/thunderbolt/clx.c
index 787dfd1550e5..1637e79d988a 100644
--- a/drivers/thunderbolt/clx.c
+++ b/drivers/thunderbolt/clx.c
@@ -167,7 +167,8 @@ static int tb_port_clx(struct tb_port *port)
* @port: USB4 port to check
* @clx: Mask of CL states to check
*
- * Returns true if any of the given CL states is enabled for @port.
+ * Return: %true if any of the given CL states is enabled for @port,
+ * %false otherwise.
*/
bool tb_port_clx_is_enabled(struct tb_port *port, unsigned int clx)
{
@@ -177,6 +178,8 @@ bool tb_port_clx_is_enabled(struct tb_port *port, unsigned int clx)
/**
* tb_switch_clx_is_supported() - Is CLx supported on this type of router
* @sw: The router to check CLx support for
+ *
+ * Return: %true if CLx is supported, %false otherwise.
*/
static bool tb_switch_clx_is_supported(const struct tb_switch *sw)
{
@@ -203,7 +206,7 @@ static bool tb_switch_clx_is_supported(const struct tb_switch *sw)
* Can be called for any router. Initializes the current CL state by
* reading it from the hardware.
*
- * Returns %0 in case of success and negative errno in case of failure.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_clx_init(struct tb_switch *sw)
{
@@ -313,7 +316,7 @@ static bool validate_mask(unsigned int clx)
* is not inter-domain link. The complete set of conditions is described in CM
* Guide 1.0 section 8.1.
*
- * Returns %0 on success or an error code on failure.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_clx_enable(struct tb_switch *sw, unsigned int clx)
{
@@ -390,8 +393,7 @@ int tb_switch_clx_enable(struct tb_switch *sw, unsigned int clx)
* Disables all CL states of the given router. Can be called on any
* router and if the states were not enabled already does nothing.
*
- * Returns the CL states that were disabled or negative errno in case of
- * failure.
+ * Return: CL states that were disabled or negative errno otherwise.
*/
int tb_switch_clx_disable(struct tb_switch *sw)
{
diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c
index 1db2e951b53f..f92175ee3841 100644
--- a/drivers/thunderbolt/ctl.c
+++ b/drivers/thunderbolt/ctl.c
@@ -82,6 +82,8 @@ static DEFINE_MUTEX(tb_cfg_request_lock);
*
* This is refcounted object so when you are done with this, call
* tb_cfg_request_put() to it.
+ *
+ * Return: &struct tb_cfg_request on success, %NULL otherwise.
*/
struct tb_cfg_request *tb_cfg_request_alloc(void)
{
@@ -359,7 +361,7 @@ static void tb_ctl_tx_callback(struct tb_ring *ring, struct ring_frame *frame,
*
* len must be a multiple of four.
*
- * Return: Returns 0 on success or an error code on failure.
+ * Return: %0 on success, negative errno otherwise.
*/
static int tb_ctl_tx(struct tb_ctl *ctl, const void *data, size_t len,
enum tb_cfg_pkg_type type)
@@ -539,6 +541,8 @@ static void tb_cfg_request_work(struct work_struct *work)
*
* This queues @req on the given control channel without waiting for it
* to complete. When the request completes @callback is called.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req,
void (*callback)(void *), void *callback_data)
@@ -605,6 +609,9 @@ static void tb_cfg_request_complete(void *data)
* triggers the request is canceled before function returns. Note the
* caller needs to make sure only one message for given switch is active
* at a time.
+ *
+ * Return: &struct tb_cfg_result with non-zero @err field if error
+ * has occurred.
*/
struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl,
struct tb_cfg_request *req,
@@ -641,7 +648,7 @@ struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl,
*
* cb will be invoked once for every hot plug event.
*
- * Return: Returns a pointer on success or NULL on failure.
+ * Return: Pointer to &struct tb_ctl, %NULL on failure.
*/
struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int index, int timeout_msec,
event_cb cb, void *cb_data)
@@ -764,8 +771,9 @@ void tb_ctl_stop(struct tb_ctl *ctl)
* @route: Router that originated the event
* @error: Pointer to the notification package
*
- * Call this as response for non-plug notification to ack it. Returns
- * %0 on success or an error code on failure.
+ * Call this as a response for non-plug notification to ack it.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_cfg_ack_notification(struct tb_ctl *ctl, u64 route,
const struct cfg_error_pkg *error)
@@ -827,8 +835,9 @@ int tb_cfg_ack_notification(struct tb_ctl *ctl, u64 route,
* @port: Port where the hot plug/unplug happened
* @unplug: Ack hot plug or unplug
*
- * Call this as response for hot plug/unplug event to ack it.
- * Returns %0 on success or an error code on failure.
+ * Call this as a response for hot plug/unplug event to ack it.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug)
{
@@ -895,6 +904,9 @@ static bool tb_cfg_copy(struct tb_cfg_request *req, const struct ctl_pkg *pkg)
* If the switch at route is incorrectly configured then we will not receive a
* reply (even though the switch will reset). The caller should check for
* -ETIMEDOUT and attempt to reconfigure the switch.
+ *
+ * Return: &struct tb_cfg_result with non-zero @err field if error
+ * has occurred.
*/
struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route)
{
@@ -937,6 +949,9 @@ struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route)
* @timeout_msec: Timeout in ms how long to wait for the response
*
* Reads from router config space without translating the possible error.
+ *
+ * Return: &struct tb_cfg_result with non-zero @err field if error
+ * has occurred.
*/
struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
u64 route, u32 port, enum tb_cfg_space space,
@@ -1008,6 +1023,9 @@ struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
* @timeout_msec: Timeout in ms how long to wait for the response
*
* Writes to router config space without translating the possible error.
+ *
+ * Return: &struct tb_cfg_result with non-zero @err field if error
+ * has occurred.
*/
struct tb_cfg_result tb_cfg_write_raw(struct tb_ctl *ctl, const void *buffer,
u64 route, u32 port, enum tb_cfg_space space,
@@ -1150,8 +1168,7 @@ int tb_cfg_write(struct tb_ctl *ctl, const void *buffer, u64 route, u32 port,
* Reads the first dword from the switches TB_CFG_SWITCH config area and
* returns the port number from which the reply originated.
*
- * Return: Returns the upstream port number on success or an error code on
- * failure.
+ * Return: Upstream port number on success or negative error code on failure.
*/
int tb_cfg_get_upstream_port(struct tb_ctl *ctl, u64 route)
{
diff --git a/drivers/thunderbolt/ctl.h b/drivers/thunderbolt/ctl.h
index 7e08ca8f0895..db1646eb4fd0 100644
--- a/drivers/thunderbolt/ctl.h
+++ b/drivers/thunderbolt/ctl.h
@@ -54,6 +54,7 @@ struct ctl_pkg {
* @kref: Reference count
* @ctl: Pointer to the control channel structure. Only set when the
* request is queued.
+ * @request: Request is stored here
* @request_size: Size of the request packet (in bytes)
* @request_type: Type of the request packet
* @response: Response is stored here
diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c
index f8328ca7e22e..46a2a3550be7 100644
--- a/drivers/thunderbolt/debugfs.c
+++ b/drivers/thunderbolt/debugfs.c
@@ -12,6 +12,7 @@
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/pm_runtime.h>
+#include <linux/string_choices.h>
#include <linux/uaccess.h>
#include "tb.h"
@@ -691,7 +692,7 @@ static int margining_caps_show(struct seq_file *s, void *not_used)
seq_printf(s, "0x%08x\n", margining->caps[i]);
seq_printf(s, "# software margining: %s\n",
- supports_software(margining) ? "yes" : "no");
+ str_yes_no(supports_software(margining)));
if (supports_hardware(margining)) {
seq_puts(s, "# hardware margining: yes\n");
seq_puts(s, "# minimum BER level contour: ");
diff --git a/drivers/thunderbolt/dma_port.c b/drivers/thunderbolt/dma_port.c
index 9f20c7bbf0ce..dc8ea188a114 100644
--- a/drivers/thunderbolt/dma_port.c
+++ b/drivers/thunderbolt/dma_port.c
@@ -197,6 +197,8 @@ static int dma_find_port(struct tb_switch *sw)
*
* The DMA control port is functional also when the switch is in safe
* mode.
+ *
+ * Return: &struct tb_dma_port on success, %NULL otherwise.
*/
struct tb_dma_port *dma_port_alloc(struct tb_switch *sw)
{
@@ -354,6 +356,8 @@ static int dma_port_flash_write_block(void *data, unsigned int dwaddress,
* @address: Address relative to the start of active region
* @buf: Buffer where the data is read
* @size: Size of the buffer
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int dma_port_flash_read(struct tb_dma_port *dma, unsigned int address,
void *buf, size_t size)
@@ -372,6 +376,8 @@ int dma_port_flash_read(struct tb_dma_port *dma, unsigned int address,
* Writes block of data to the non-active flash region of the switch. If
* the address is given as %DMA_PORT_CSS_ADDRESS the block is written
* using CSS command.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int dma_port_flash_write(struct tb_dma_port *dma, unsigned int address,
const void *buf, size_t size)
@@ -393,6 +399,8 @@ int dma_port_flash_write(struct tb_dma_port *dma, unsigned int address,
* dma_port_flash_update_auth_status() to get status of this command.
* This is because if the switch in question is root switch the
* thunderbolt host controller gets reset as well.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int dma_port_flash_update_auth(struct tb_dma_port *dma)
{
@@ -410,12 +418,13 @@ int dma_port_flash_update_auth(struct tb_dma_port *dma)
* @status: Status code of the operation
*
* The function checks if there is status available from the last update
- * auth command. Returns %0 if there is no status and no further
- * action is required. If there is status, %1 is returned instead and
- * @status holds the failure code.
+ * auth command.
*
- * Negative return means there was an error reading status from the
- * switch.
+ * Return:
+ * * %0 - If there is no status and no further action is required.
+ * * %1 - If there is some status. @status holds the failure code.
+ * * Negative errno - An error occurred when reading status from the
+ * switch.
*/
int dma_port_flash_update_auth_status(struct tb_dma_port *dma, u32 *status)
{
@@ -446,6 +455,8 @@ int dma_port_flash_update_auth_status(struct tb_dma_port *dma, u32 *status)
* @dma: DMA control port
*
* Triggers power cycle to the switch.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int dma_port_power_cycle(struct tb_dma_port *dma)
{
diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c
index 45239703745e..83defc915d33 100644
--- a/drivers/thunderbolt/domain.c
+++ b/drivers/thunderbolt/domain.c
@@ -12,7 +12,8 @@
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/random.h>
-#include <crypto/hash.h>
+#include <crypto/sha2.h>
+#include <crypto/utils.h>
#include "tb.h"
@@ -368,7 +369,7 @@ static bool tb_domain_event_cb(void *data, enum tb_cfg_pkg_type type,
* Call tb_domain_put() to release the domain before it has been added
* to the system.
*
- * Return: allocated domain structure on %NULL in case of error
+ * Return: Pointer to &struct tb or %NULL in case of error.
*/
struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize)
{
@@ -430,7 +431,7 @@ err_free:
* and release the domain after this function has been called, call
* tb_domain_remove().
*
- * Return: %0 in case of success and negative errno in case of error
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_domain_add(struct tb *tb, bool reset)
{
@@ -518,6 +519,8 @@ void tb_domain_remove(struct tb *tb)
* @tb: Domain to suspend
*
* Suspends all devices in the domain and stops the control channel.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_domain_suspend_noirq(struct tb *tb)
{
@@ -544,6 +547,8 @@ int tb_domain_suspend_noirq(struct tb *tb)
*
* Re-starts the control channel, and resumes all devices connected to
* the domain.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_domain_resume_noirq(struct tb *tb)
{
@@ -643,6 +648,8 @@ int tb_domain_disapprove_switch(struct tb *tb, struct tb_switch *sw)
* This will approve switch by connection manager specific means. In
* case of success the connection manager will create PCIe tunnel from
* parent to @sw.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_domain_approve_switch(struct tb *tb, struct tb_switch *sw)
{
@@ -708,8 +715,6 @@ int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw)
u8 response[TB_SWITCH_KEY_SIZE];
u8 hmac[TB_SWITCH_KEY_SIZE];
struct tb_switch *parent_sw;
- struct crypto_shash *tfm;
- struct shash_desc *shash;
int ret;
if (!tb->cm_ops->approve_switch || !tb->cm_ops->challenge_switch_key)
@@ -725,45 +730,15 @@ int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw)
if (ret)
return ret;
- tfm = crypto_alloc_shash("hmac(sha256)", 0, 0);
- if (IS_ERR(tfm))
- return PTR_ERR(tfm);
-
- ret = crypto_shash_setkey(tfm, sw->key, TB_SWITCH_KEY_SIZE);
- if (ret)
- goto err_free_tfm;
-
- shash = kzalloc(sizeof(*shash) + crypto_shash_descsize(tfm),
- GFP_KERNEL);
- if (!shash) {
- ret = -ENOMEM;
- goto err_free_tfm;
- }
-
- shash->tfm = tfm;
-
- memset(hmac, 0, sizeof(hmac));
- ret = crypto_shash_digest(shash, challenge, sizeof(hmac), hmac);
- if (ret)
- goto err_free_shash;
+ static_assert(sizeof(hmac) == SHA256_DIGEST_SIZE);
+ hmac_sha256_usingrawkey(sw->key, TB_SWITCH_KEY_SIZE,
+ challenge, sizeof(challenge), hmac);
/* The returned HMAC must match the one we calculated */
- if (memcmp(response, hmac, sizeof(hmac))) {
- ret = -EKEYREJECTED;
- goto err_free_shash;
- }
-
- crypto_free_shash(tfm);
- kfree(shash);
+ if (crypto_memneq(response, hmac, sizeof(hmac)))
+ return -EKEYREJECTED;
return tb->cm_ops->approve_switch(tb, sw);
-
-err_free_shash:
- kfree(shash);
-err_free_tfm:
- crypto_free_shash(tfm);
-
- return ret;
}
/**
@@ -773,7 +748,7 @@ err_free_tfm:
* This needs to be called in preparation for NVM upgrade of the host
* controller. Makes sure all PCIe paths are disconnected.
*
- * Return %0 on success and negative errno in case of error.
+ * Return: %0 on success and negative errno in case of error.
*/
int tb_domain_disconnect_pcie_paths(struct tb *tb)
{
@@ -795,9 +770,11 @@ int tb_domain_disconnect_pcie_paths(struct tb *tb)
* Calls connection manager specific method to enable DMA paths to the
* XDomain in question.
*
- * Return: 0% in case of success and negative errno otherwise. In
- * particular returns %-ENOTSUPP if the connection manager
- * implementation does not support XDomains.
+ * Return:
+ * * %0 - On success.
+ * * %-ENOTSUPP - If the connection manager implementation does not support
+ * XDomains.
+ * * Negative errno - An error occurred.
*/
int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
int transmit_path, int transmit_ring,
@@ -822,9 +799,11 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
* Calls connection manager specific method to disconnect DMA paths to
* the XDomain in question.
*
- * Return: 0% in case of success and negative errno otherwise. In
- * particular returns %-ENOTSUPP if the connection manager
- * implementation does not support XDomains.
+ * Return:
+ * * %0 - On success.
+ * * %-ENOTSUPP - If the connection manager implementation does not support
+ * XDomains.
+ * * Negative errno - An error occurred.
*/
int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
int transmit_path, int transmit_ring,
diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
index e66183a72cf9..1af65fece495 100644
--- a/drivers/thunderbolt/eeprom.c
+++ b/drivers/thunderbolt/eeprom.c
@@ -298,6 +298,8 @@ struct tb_drom_entry_desc {
*
* Does not use the cached copy in sw->drom. Used during resume to check switch
* identity.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid)
{
@@ -709,7 +711,7 @@ static int tb_drom_device_read(struct tb_switch *sw)
* populates the fields in @sw accordingly. Can be called for any router
* generation.
*
- * Returns %0 in case of success and negative errno otherwise.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_drom_read(struct tb_switch *sw)
{
diff --git a/drivers/thunderbolt/lc.c b/drivers/thunderbolt/lc.c
index 63cb4b6afb71..0891d51ac2e9 100644
--- a/drivers/thunderbolt/lc.c
+++ b/drivers/thunderbolt/lc.c
@@ -14,6 +14,8 @@
* tb_lc_read_uuid() - Read switch UUID from link controller common register
* @sw: Switch whose UUID is read
* @uuid: UUID is placed here
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_lc_read_uuid(struct tb_switch *sw, u32 *uuid)
{
@@ -52,9 +54,10 @@ static int find_port_lc_cap(struct tb_port *port)
* @port: Port that is reset
*
* Triggers downstream port reset through link controller registers.
- * Returns %0 in case of success negative errno otherwise. Only supports
- * non-USB4 routers with link controller (that's Thunderbolt 2 and
- * Thunderbolt 3).
+ * Only supports non-USB4 routers with link controller (that's
+ * Thunderbolt 2 and Thunderbolt 3).
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_lc_reset_port(struct tb_port *port)
{
@@ -132,6 +135,8 @@ static int tb_lc_set_port_configured(struct tb_port *port, bool configured)
* @port: Port that is set as configured
*
* Sets the port configured for power management purposes.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_lc_configure_port(struct tb_port *port)
{
@@ -143,6 +148,8 @@ int tb_lc_configure_port(struct tb_port *port)
* @port: Port that is set as configured
*
* Sets the port unconfigured for power management purposes.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
void tb_lc_unconfigure_port(struct tb_port *port)
{
@@ -184,8 +191,10 @@ static int tb_lc_set_xdomain_configured(struct tb_port *port, bool configure)
* tb_lc_configure_xdomain() - Inform LC that the link is XDomain
* @port: Switch downstream port connected to another host
*
- * Sets the lane configured for XDomain accordingly so that the LC knows
- * about this. Returns %0 in success and negative errno in failure.
+ * Sets the lane configured for XDomain accordingly so that LC knows
+ * about this.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_lc_configure_xdomain(struct tb_port *port)
{
@@ -211,7 +220,7 @@ void tb_lc_unconfigure_xdomain(struct tb_port *port)
* sleep. Should be called for those downstream lane adapters that were
* not connected (tb_lc_configure_port() was not called) before sleep.
*
- * Returns %0 in success and negative errno in case of failure.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_lc_start_lane_initialization(struct tb_port *port)
{
@@ -244,6 +253,8 @@ int tb_lc_start_lane_initialization(struct tb_port *port)
*
* TB_LC_LINK_ATTR_CPS bit reflects if the link supports CLx including
* active cables (if connected on the link).
+ *
+ * Return: %true if CLx is supported, %false otherwise.
*/
bool tb_lc_is_clx_supported(struct tb_port *port)
{
@@ -266,7 +277,8 @@ bool tb_lc_is_clx_supported(struct tb_port *port)
* tb_lc_is_usb_plugged() - Is there USB device connected to port
* @port: Device router lane 0 adapter
*
- * Returns true if the @port has USB type-C device connected.
+ * Return: %true if the @port has USB Type-C device connected, %false
+ * otherwise.
*/
bool tb_lc_is_usb_plugged(struct tb_port *port)
{
@@ -292,7 +304,8 @@ bool tb_lc_is_usb_plugged(struct tb_port *port)
* tb_lc_is_xhci_connected() - Is the internal xHCI connected
* @port: Device router lane 0 adapter
*
- * Returns true if the internal xHCI has been connected to @port.
+ * Return: %true if the internal xHCI has been connected to
+ * @port, %false otherwise.
*/
bool tb_lc_is_xhci_connected(struct tb_port *port)
{
@@ -343,9 +356,10 @@ static int __tb_lc_xhci_connect(struct tb_port *port, bool connect)
* tb_lc_xhci_connect() - Connect internal xHCI
* @port: Device router lane 0 adapter
*
- * Tells LC to connect the internal xHCI to @port. Returns %0 on success
- * and negative errno in case of failure. Can be called for Thunderbolt 3
- * routers only.
+ * Tells LC to connect the internal xHCI to @port. Can be called for
+ * Thunderbolt 3 routers only.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_lc_xhci_connect(struct tb_port *port)
{
@@ -408,6 +422,8 @@ static int tb_lc_set_wake_one(struct tb_switch *sw, unsigned int offset,
* @flags: Wakeup flags (%0 to disable)
*
* For each LC sets wake bits accordingly.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags)
{
@@ -447,6 +463,8 @@ int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags)
*
* Let the switch link controllers know that the switch is going to
* sleep.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_lc_set_sleep(struct tb_switch *sw)
{
@@ -491,6 +509,8 @@ int tb_lc_set_sleep(struct tb_switch *sw)
*
* Checks whether conditions for lane bonding from parent to @sw are
* possible.
+ *
+ * Return: %true if lane bonding is possible, %false otherwise.
*/
bool tb_lc_lane_bonding_possible(struct tb_switch *sw)
{
@@ -562,6 +582,8 @@ static int tb_lc_dp_sink_available(struct tb_switch *sw, int sink)
*
* Queries through LC SNK_ALLOCATION registers whether DP sink is available
* for the given DP IN port or not.
+ *
+ * Return: %true if DP sink is available, %false otherwise.
*/
bool tb_lc_dp_sink_query(struct tb_switch *sw, struct tb_port *in)
{
@@ -586,10 +608,12 @@ bool tb_lc_dp_sink_query(struct tb_switch *sw, struct tb_port *in)
* @sw: Switch whose DP sink is allocated
* @in: DP IN port the DP sink is allocated for
*
- * Allocate DP sink for @in via LC SNK_ALLOCATION registers. If the
- * resource is available and allocation is successful returns %0. In all
- * other cases returs negative errno. In particular %-EBUSY is returned if
- * the resource was not available.
+ * Allocate DP sink for @in via LC SNK_ALLOCATION registers.
+ *
+ * Return:
+ * * %0 - If the resource is available and allocation is successful.
+ * * %-EBUSY - If resource is not available.
+ * * Negative errno - Another error occurred.
*/
int tb_lc_dp_sink_alloc(struct tb_switch *sw, struct tb_port *in)
{
@@ -637,6 +661,8 @@ int tb_lc_dp_sink_alloc(struct tb_switch *sw, struct tb_port *in)
* @in: DP IN port whose DP sink is de-allocated
*
* De-allocate DP sink from @in using LC SNK_ALLOCATION registers.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_lc_dp_sink_dealloc(struct tb_switch *sw, struct tb_port *in)
{
@@ -680,6 +706,8 @@ int tb_lc_dp_sink_dealloc(struct tb_switch *sw, struct tb_port *in)
*
* This is useful to let authentication cycle pass even without
* a Thunderbolt link present.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_lc_force_power(struct tb_switch *sw)
{
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index f3a2264e012b..5f63f9b9cf40 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -19,6 +19,7 @@
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/property.h>
+#include <linux/string_choices.h>
#include <linux/string_helpers.h>
#include "nhi.h"
@@ -146,7 +147,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active)
dev_WARN(&ring->nhi->pdev->dev,
"interrupt for %s %d is already %s\n",
RING_TYPE(ring), ring->hop,
- active ? "enabled" : "disabled");
+ str_enabled_disabled(active));
if (active)
iowrite32(new, ring->nhi->iobase + reg);
@@ -343,8 +344,10 @@ EXPORT_SYMBOL_GPL(__tb_ring_enqueue);
*
* This function can be called when @start_poll callback of the @ring
* has been called. It will read one completed frame from the ring and
- * return it to the caller. Returns %NULL if there is no more completed
- * frames.
+ * return it to the caller.
+ *
+ * Return: Pointer to &struct ring_frame, %NULL if there is no more
+ * completed frames.
*/
struct ring_frame *tb_ring_poll(struct tb_ring *ring)
{
@@ -639,6 +642,8 @@ err_free_ring:
* @hop: HopID (ring) to allocate
* @size: Number of entries in the ring
* @flags: Flags for the ring
+ *
+ * Return: Pointer to &struct tb_ring, %NULL otherwise.
*/
struct tb_ring *tb_ring_alloc_tx(struct tb_nhi *nhi, int hop, int size,
unsigned int flags)
@@ -660,6 +665,8 @@ EXPORT_SYMBOL_GPL(tb_ring_alloc_tx);
* interrupt is triggered and masked, instead of callback
* in each Rx frame.
* @poll_data: Optional data passed to @start_poll
+ *
+ * Return: Pointer to &struct tb_ring, %NULL otherwise.
*/
struct tb_ring *tb_ring_alloc_rx(struct tb_nhi *nhi, int hop, int size,
unsigned int flags, int e2e_tx_hop,
@@ -853,8 +860,9 @@ EXPORT_SYMBOL_GPL(tb_ring_free);
* @cmd: Command to send
* @data: Data to be send with the command
*
- * Sends mailbox command to the firmware running on NHI. Returns %0 in
- * case of success and negative errno in case of failure.
+ * Sends mailbox command to the firmware running on NHI.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data)
{
@@ -890,6 +898,8 @@ int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data)
*
* The function reads current firmware operation mode using NHI mailbox
* registers and returns it to the caller.
+ *
+ * Return: &enum nhi_fw_mode.
*/
enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi)
{
diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h
index 297a3e440648..cf5222bee971 100644
--- a/drivers/thunderbolt/nhi_regs.h
+++ b/drivers/thunderbolt/nhi_regs.h
@@ -21,6 +21,12 @@ enum ring_flags {
/**
* struct ring_desc - TX/RX ring entry
+ * @phys: DMA mapped address of the frame
+ * @length: Size of the ring
+ * @eof: End of frame protocol defined field
+ * @sof: Start of frame protocol defined field
+ * @flags: Ring descriptor flags
+ * @time: Fill with zero
*
* For TX set length/eof/sof.
* For RX length/eof/sof are set by the NHI.
diff --git a/drivers/thunderbolt/nvm.c b/drivers/thunderbolt/nvm.c
index da11c8112e29..6901058b7ac0 100644
--- a/drivers/thunderbolt/nvm.c
+++ b/drivers/thunderbolt/nvm.c
@@ -278,9 +278,13 @@ static const struct tb_nvm_vendor retimer_nvm_vendors[] = {
* tb_nvm_alloc() - Allocate new NVM structure
* @dev: Device owning the NVM
*
- * Allocates new NVM structure with unique @id and returns it. In case
- * of error returns ERR_PTR(). Specifically returns %-EOPNOTSUPP if the
- * NVM format of the @dev is not known by the kernel.
+ * Allocates new NVM structure with unique @id and returns it.
+ *
+ * Return:
+ * * Pointer to &struct tb_nvm - On success.
+ * * %-EOPNOTSUPP - If the NVM format of the @dev is not known by the
+ * kernel.
+ * * %ERR_PTR - In case of failure.
*/
struct tb_nvm *tb_nvm_alloc(struct device *dev)
{
@@ -347,9 +351,10 @@ struct tb_nvm *tb_nvm_alloc(struct device *dev)
* tb_nvm_read_version() - Read and populate NVM version
* @nvm: NVM structure
*
- * Uses vendor specific means to read out and fill in the existing
- * active NVM version. Returns %0 in case of success and negative errno
- * otherwise.
+ * Uses vendor specific means to read and fill out the existing
+ * active NVM version.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_nvm_read_version(struct tb_nvm *nvm)
{
@@ -365,12 +370,11 @@ int tb_nvm_read_version(struct tb_nvm *nvm)
* tb_nvm_validate() - Validate new NVM image
* @nvm: NVM structure
*
- * Runs vendor specific validation over the new NVM image and if all
- * checks pass returns %0. As side effect updates @nvm->buf_data_start
- * and @nvm->buf_data_size fields to match the actual data to be written
- * to the NVM.
+ * Runs vendor specific validation over the new NVM image. As a
+ * side effect, updates @nvm->buf_data_start and @nvm->buf_data_size
+ * fields to match the actual data to be written to the NVM.
*
- * If the validation does not pass then returns negative errno.
+ * Return: %0 on successful validation, negative errno otherwise.
*/
int tb_nvm_validate(struct tb_nvm *nvm)
{
@@ -405,7 +409,7 @@ int tb_nvm_validate(struct tb_nvm *nvm)
* the image, this function does that. Can be called even if the device
* does not need this.
*
- * Returns %0 in case of success and negative errno otherwise.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_nvm_write_headers(struct tb_nvm *nvm)
{
@@ -423,7 +427,8 @@ int tb_nvm_write_headers(struct tb_nvm *nvm)
* Registers new active NVmem device for @nvm. The @reg_read is called
* directly from NVMem so it must handle possible concurrent access if
* needed. The first parameter passed to @reg_read is @nvm structure.
- * Returns %0 in success and negative errno otherwise.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_nvm_add_active(struct tb_nvm *nvm, nvmem_reg_read_t reg_read)
{
@@ -461,6 +466,11 @@ int tb_nvm_add_active(struct tb_nvm *nvm, nvmem_reg_read_t reg_read)
* Helper function to cache the new NVM image before it is actually
* written to the flash. Copies @bytes from @val to @nvm->buf starting
* from @offset.
+ *
+ * Return:
+ * * %0 - On success.
+ * * %-ENOMEM - If buffer allocation failed.
+ * * Negative errno - Another error occurred.
*/
int tb_nvm_write_buf(struct tb_nvm *nvm, unsigned int offset, void *val,
size_t bytes)
@@ -488,7 +498,7 @@ int tb_nvm_write_buf(struct tb_nvm *nvm, unsigned int offset, void *val,
* needed. The first parameter passed to @reg_write is @nvm structure.
* The size of the NVMem device is set to %NVM_MAX_SIZE.
*
- * Returns %0 in success and negative errno otherwise.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_nvm_add_non_active(struct tb_nvm *nvm, nvmem_reg_write_t reg_write)
{
@@ -545,7 +555,7 @@ void tb_nvm_free(struct tb_nvm *nvm)
* This is a generic function that reads data from NVM or NVM like
* device.
*
- * Returns %0 on success and negative errno otherwise.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_nvm_read_data(unsigned int address, void *buf, size_t size,
unsigned int retries, read_block_fn read_block,
@@ -592,7 +602,7 @@ int tb_nvm_read_data(unsigned int address, void *buf, size_t size,
*
* This is generic function that writes data to NVM or NVM like device.
*
- * Returns %0 on success and negative errno otherwise.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_nvm_write_data(unsigned int address, const void *buf, size_t size,
unsigned int retries, write_block_fn write_block,
diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c
index e1a5f6e3d0b6..f9b11dadfbdd 100644
--- a/drivers/thunderbolt/path.c
+++ b/drivers/thunderbolt/path.c
@@ -96,7 +96,7 @@ static int tb_path_find_src_hopid(struct tb_port *src,
* that the @dst port is the expected one. If it is not, the path can be
* cleaned up by calling tb_path_deactivate() before tb_path_free().
*
- * Return: Discovered path on success, %NULL in case of failure
+ * Return: Pointer to &struct tb_path, %NULL in case of failure.
*/
struct tb_path *tb_path_discover(struct tb_port *src, int src_hopid,
struct tb_port *dst, int dst_hopid,
@@ -233,7 +233,7 @@ err:
* links on the path, prioritizes using @link_nr but takes into account
* that the lanes may be bonded.
*
- * Return: Returns a tb_path on success or NULL on failure.
+ * Return: Pointer to &struct tb_path, %NULL in case of failure.
*/
struct tb_path *tb_path_alloc(struct tb *tb, struct tb_port *src, int src_hopid,
struct tb_port *dst, int dst_hopid, int link_nr,
@@ -452,7 +452,9 @@ static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index,
* @hop_index: HopID of the path to be cleared
*
* This deactivates or clears a single path config space entry at
- * @hop_index. Returns %0 in success and negative errno otherwise.
+ * @hop_index.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_path_deactivate_hop(struct tb_port *port, int hop_index)
{
@@ -498,7 +500,7 @@ void tb_path_deactivate(struct tb_path *path)
* Activate a path starting with the last hop and iterating backwards. The
* caller must fill path->hops before calling tb_path_activate().
*
- * Return: Returns 0 on success or an error code on failure.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_path_activate(struct tb_path *path)
{
@@ -592,7 +594,7 @@ err:
* tb_path_is_invalid() - check whether any ports on the path are invalid
* @path: Path to check
*
- * Return: Returns true if the path is invalid, false otherwise.
+ * Return: %true if the path is invalid, %false otherwise.
*/
bool tb_path_is_invalid(struct tb_path *path)
{
@@ -613,6 +615,8 @@ bool tb_path_is_invalid(struct tb_path *path)
*
* Goes over all hops on path and checks if @port is any of them.
* Direction does not matter.
+ *
+ * Return: %true if port is on the path, %false otherwise.
*/
bool tb_path_port_on_path(const struct tb_path *path, const struct tb_port *port)
{
diff --git a/drivers/thunderbolt/property.c b/drivers/thunderbolt/property.c
index dc555cda98e6..31aa0516932a 100644
--- a/drivers/thunderbolt/property.c
+++ b/drivers/thunderbolt/property.c
@@ -211,11 +211,13 @@ static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,
*
* This function parses the XDomain properties data block into format that
* can be traversed using the helper functions provided by this module.
- * Upon success returns the parsed directory. In case of error returns
- * %NULL. The resulting &struct tb_property_dir needs to be released by
+ *
+ * The resulting &struct tb_property_dir needs to be released by
* calling tb_property_free_dir() when not needed anymore.
*
* The @block is expected to be root directory.
+ *
+ * Return: Pointer to &struct tb_property_dir, %NULL in case of failure.
*/
struct tb_property_dir *tb_property_parse_dir(const u32 *block,
size_t block_len)
@@ -238,6 +240,8 @@ struct tb_property_dir *tb_property_parse_dir(const u32 *block,
*
* Creates new, empty property directory. If @uuid is %NULL then the
* directory is assumed to be root directory.
+ *
+ * Return: Pointer to &struct tb_property_dir, %NULL in case of failure.
*/
struct tb_property_dir *tb_property_create_dir(const uuid_t *uuid)
{
@@ -481,9 +485,11 @@ static ssize_t __tb_property_format_dir(const struct tb_property_dir *dir,
* @block_len: Length of the property block
*
* This function formats the directory to the packed format that can be
- * then send over the thunderbolt fabric to receiving host. Returns %0 in
- * case of success and negative errno on faulure. Passing %NULL in @block
- * returns number of entries the block takes.
+ * then sent over the thunderbolt fabric to receiving host.
+ *
+ * Passing %NULL in @block returns number of entries the block takes.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block,
size_t block_len)
@@ -505,9 +511,9 @@ ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block,
* tb_property_copy_dir() - Take a deep copy of directory
* @dir: Directory to copy
*
- * This function takes a deep copy of @dir and returns back the copy. In
- * case of error returns %NULL. The resulting directory needs to be
- * released by calling tb_property_free_dir().
+ * The resulting directory needs to be released by calling tb_property_free_dir().
+ *
+ * Return: Pointer to &struct tb_property_dir, %NULL in case of failure.
*/
struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir)
{
@@ -577,6 +583,8 @@ err_free:
* @parent: Directory to add the property
* @key: Key for the property
* @value: Immediate value to store with the property
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_property_add_immediate(struct tb_property_dir *parent, const char *key,
u32 value)
@@ -606,6 +614,8 @@ EXPORT_SYMBOL_GPL(tb_property_add_immediate);
* @buflen: Number of bytes in the data buffer
*
* Function takes a copy of @buf and adds it to the directory.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_property_add_data(struct tb_property_dir *parent, const char *key,
const void *buf, size_t buflen)
@@ -642,6 +652,8 @@ EXPORT_SYMBOL_GPL(tb_property_add_data);
* @text: String to add
*
* Function takes a copy of @text and adds it to the directory.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_property_add_text(struct tb_property_dir *parent, const char *key,
const char *text)
@@ -676,6 +688,8 @@ EXPORT_SYMBOL_GPL(tb_property_add_text);
* @parent: Directory to add the property
* @key: Key for the property
* @dir: Directory to add
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_property_add_dir(struct tb_property_dir *parent, const char *key,
struct tb_property_dir *dir)
@@ -716,8 +730,10 @@ EXPORT_SYMBOL_GPL(tb_property_remove);
* @key: Key to look for
* @type: Type of the property
*
- * Finds and returns property from the given directory. Does not recurse
- * into sub-directories. Returns %NULL if the property was not found.
+ * Finds and returns property from the given directory. Does not
+ * recurse into sub-directories.
+ *
+ * Return: Pointer to &struct tb_property, %NULL if the property was not found.
*/
struct tb_property *tb_property_find(struct tb_property_dir *dir,
const char *key, enum tb_property_type type)
@@ -737,6 +753,8 @@ EXPORT_SYMBOL_GPL(tb_property_find);
* tb_property_get_next() - Get next property from directory
* @dir: Directory holding properties
* @prev: Previous property in the directory (%NULL returns the first)
+ *
+ * Return: Pointer to &struct tb_property, %NULL if property was not found.
*/
struct tb_property *tb_property_get_next(struct tb_property_dir *dir,
struct tb_property *prev)
diff --git a/drivers/thunderbolt/retimer.c b/drivers/thunderbolt/retimer.c
index 361fece3d818..3a0f486a24d5 100644
--- a/drivers/thunderbolt/retimer.c
+++ b/drivers/thunderbolt/retimer.c
@@ -27,8 +27,9 @@
* @buf: Data read from NVM is stored here
* @size: Number of bytes to read
*
- * Reads retimer NVM and copies the contents to @buf. Returns %0 if the
- * read was successful and negative errno in case of failure.
+ * Reads retimer NVM and copies the contents to @buf.
+ *
+ * Return: %0 if the read was successful, negative errno in case of failure.
*/
int tb_retimer_nvm_read(struct tb_retimer *rt, unsigned int address, void *buf,
size_t size)
@@ -503,6 +504,8 @@ static struct tb_retimer *tb_port_find_retimer(struct tb_port *port, u8 index)
* Then Tries to enumerate on-board retimers connected to @port. Found
* retimers are registered as children of @port if @add is set. Does
* not scan for cable retimers for now.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_retimer_scan(struct tb_port *port, bool add)
{
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index e9f4186f20f4..0e07904aa73b 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -290,8 +290,9 @@ static int nvm_authenticate(struct tb_switch *sw, bool auth_only)
* @size: Size of the buffer in bytes
*
* Reads from router NVM and returns the requested data in @buf. Locking
- * is up to the caller. Returns %0 in success and negative errno in case
- * of failure.
+ * is up to the caller.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf,
size_t size)
@@ -464,7 +465,7 @@ static void tb_dump_port(struct tb *tb, const struct tb_port *port)
*
* The port must have a TB_CAP_PHY (i.e. it should be a real port).
*
- * Return: Returns an enum tb_port_state on success or an error code on failure.
+ * Return: &enum tb_port_state or negative error code on failure.
*/
int tb_port_state(struct tb_port *port)
{
@@ -491,9 +492,11 @@ int tb_port_state(struct tb_port *port)
* switch resume). Otherwise we only wait if a device is registered but the link
* has not yet been established.
*
- * Return: Returns an error code on failure. Returns 0 if the port is not
- * connected or failed to reach state TB_PORT_UP within one second. Returns 1
- * if the port is connected and in state TB_PORT_UP.
+ * Return:
+ * * %0 - If the port is not connected or failed to reach
+ * state %TB_PORT_UP within one second.
+ * * %1 - If the port is connected and in state %TB_PORT_UP.
+ * * Negative errno - An error occurred.
*/
int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged)
{
@@ -562,7 +565,7 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged)
* Change the number of NFC credits allocated to @port by @credits. To remove
* NFC credits pass a negative amount of credits.
*
- * Return: Returns 0 on success or an error code on failure.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_port_add_nfc_credits(struct tb_port *port, int credits)
{
@@ -599,7 +602,7 @@ int tb_port_add_nfc_credits(struct tb_port *port, int credits)
* @port: Port whose counters to clear
* @counter: Counter index to clear
*
- * Return: Returns 0 on success or an error code on failure.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_port_clear_counter(struct tb_port *port, int counter)
{
@@ -614,6 +617,8 @@ int tb_port_clear_counter(struct tb_port *port, int counter)
*
* Needed for USB4 but can be called for any CIO/USB4 ports. Makes the
* downstream router accessible for CM.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_port_unlock(struct tb_port *port)
{
@@ -659,6 +664,8 @@ static int __tb_port_enable(struct tb_port *port, bool enable)
* @port: Port to enable (can be %NULL)
*
* This is used for lane 0 and 1 adapters to enable it.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_port_enable(struct tb_port *port)
{
@@ -670,6 +677,8 @@ int tb_port_enable(struct tb_port *port)
* @port: Port to disable (can be %NULL)
*
* This is used for lane 0 and 1 adapters to disable it.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_port_disable(struct tb_port *port)
{
@@ -689,7 +698,7 @@ static int tb_port_reset(struct tb_port *port)
* This is a helper method for tb_switch_alloc. Does not check or initialize
* any downstream switches.
*
- * Return: Returns 0 on success or an error code on failure.
+ * Return: %0 on success, negative errno otherwise.
*/
static int tb_init_port(struct tb_port *port)
{
@@ -847,9 +856,9 @@ static inline bool tb_switch_is_reachable(const struct tb_switch *parent,
* link port, the function follows that link and returns another end on
* that same link.
*
- * If the @end port has been reached, return %NULL.
- *
* Domain tb->lock must be held when this function is called.
+ *
+ * Return: Pointer to &struct tb_port, %NULL if the @end port has been reached.
*/
struct tb_port *tb_next_port_on_path(struct tb_port *start, struct tb_port *end,
struct tb_port *prev)
@@ -894,7 +903,7 @@ struct tb_port *tb_next_port_on_path(struct tb_port *start, struct tb_port *end,
* tb_port_get_link_speed() - Get current link speed
* @port: Port to check (USB4 or CIO)
*
- * Returns link speed in Gb/s or negative errno in case of failure.
+ * Return: Link speed in Gb/s or negative errno in case of failure.
*/
int tb_port_get_link_speed(struct tb_port *port)
{
@@ -926,9 +935,11 @@ int tb_port_get_link_speed(struct tb_port *port)
* tb_port_get_link_generation() - Returns link generation
* @port: Lane adapter
*
- * Returns link generation as number or negative errno in case of
- * failure. Does not distinguish between Thunderbolt 1 and Thunderbolt 2
- * links so for those always returns 2.
+ * Return: Link generation as a number or negative errno in case of
+ * failure.
+ *
+ * Does not distinguish between Thunderbolt 1 and Thunderbolt 2
+ * links so for those always returns %2.
*/
int tb_port_get_link_generation(struct tb_port *port)
{
@@ -952,8 +963,8 @@ int tb_port_get_link_generation(struct tb_port *port)
* tb_port_get_link_width() - Get current link width
* @port: Port to check (USB4 or CIO)
*
- * Returns link width. Return the link width as encoded in &enum
- * tb_link_width or negative errno in case of failure.
+ * Return: Link width encoded in &enum tb_link_width or
+ * negative errno in case of failure.
*/
int tb_port_get_link_width(struct tb_port *port)
{
@@ -979,7 +990,9 @@ int tb_port_get_link_width(struct tb_port *port)
* @width: Widths to check (bitmask)
*
* Can be called to any lane adapter. Checks if given @width is
- * supported by the hardware and returns %true if it is.
+ * supported by the hardware.
+ *
+ * Return: %true if link width is supported, %false otherwise.
*/
bool tb_port_width_supported(struct tb_port *port, unsigned int width)
{
@@ -1016,7 +1029,7 @@ bool tb_port_width_supported(struct tb_port *port, unsigned int width)
* Sets the target link width of the lane adapter to @width. Does not
* enable/disable lane bonding. For that call tb_port_set_lane_bonding().
*
- * Return: %0 in case of success and negative errno in case of error
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_port_set_link_width(struct tb_port *port, enum tb_link_width width)
{
@@ -1070,7 +1083,7 @@ int tb_port_set_link_width(struct tb_port *port, enum tb_link_width width)
* cases one should use tb_port_lane_bonding_enable() instead to enable
* lane bonding.
*
- * Return: %0 in case of success and negative errno in case of error
+ * Return: %0 on success, negative errno otherwise.
*/
static int tb_port_set_lane_bonding(struct tb_port *port, bool bonding)
{
@@ -1104,7 +1117,7 @@ static int tb_port_set_lane_bonding(struct tb_port *port, bool bonding)
* tb_port_wait_for_link_width() before enabling any paths through the
* link to make sure the link is in expected state.
*
- * Return: %0 in case of success and negative errno in case of error
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_port_lane_bonding_enable(struct tb_port *port)
{
@@ -1181,9 +1194,14 @@ void tb_port_lane_bonding_disable(struct tb_port *port)
*
* Should be used after both ends of the link have been bonded (or
* bonding has been disabled) to wait until the link actually reaches
- * the expected state. Returns %-ETIMEDOUT if the width was not reached
- * within the given timeout, %0 if it did. Can be passed a mask of
- * expected widths and succeeds if any of the widths is reached.
+ * the expected state.
+ *
+ * Can be passed a mask of expected widths.
+ *
+ * Return:
+ * * %0 - If link reaches any of the specified widths.
+ * * %-ETIMEDOUT - If link does not reach specified width.
+ * * Negative errno - Another error occurred.
*/
int tb_port_wait_for_link_width(struct tb_port *port, unsigned int width,
int timeout_msec)
@@ -1248,6 +1266,8 @@ static int tb_port_do_update_credits(struct tb_port *port)
* After the link is bonded (or bonding was disabled) the port total
* credits may change, so this function needs to be called to re-read
* the credits. Updates also the second lane adapter.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_port_update_credits(struct tb_port *port)
{
@@ -1303,6 +1323,8 @@ static bool tb_port_resume(struct tb_port *port)
/**
* tb_port_is_enabled() - Is the adapter port enabled
* @port: Port to check
+ *
+ * Return: %true if port is enabled, %false otherwise.
*/
bool tb_port_is_enabled(struct tb_port *port)
{
@@ -1327,6 +1349,8 @@ bool tb_port_is_enabled(struct tb_port *port)
/**
* tb_usb3_port_is_enabled() - Is the USB3 adapter port enabled
* @port: USB3 adapter port to check
+ *
+ * Return: %true if port is enabled, %false otherwise.
*/
bool tb_usb3_port_is_enabled(struct tb_port *port)
{
@@ -1343,6 +1367,8 @@ bool tb_usb3_port_is_enabled(struct tb_port *port)
* tb_usb3_port_enable() - Enable USB3 adapter port
* @port: USB3 adapter port to enable
* @enable: Enable/disable the USB3 adapter
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_usb3_port_enable(struct tb_port *port, bool enable)
{
@@ -1358,6 +1384,8 @@ int tb_usb3_port_enable(struct tb_port *port, bool enable)
/**
* tb_pci_port_is_enabled() - Is the PCIe adapter port enabled
* @port: PCIe port to check
+ *
+ * Return: %true if port is enabled, %false otherwise.
*/
bool tb_pci_port_is_enabled(struct tb_port *port)
{
@@ -1374,6 +1402,8 @@ bool tb_pci_port_is_enabled(struct tb_port *port)
* tb_pci_port_enable() - Enable PCIe adapter port
* @port: PCIe port to enable
* @enable: Enable/disable the PCIe adapter
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_pci_port_enable(struct tb_port *port, bool enable)
{
@@ -1389,6 +1419,8 @@ int tb_pci_port_enable(struct tb_port *port, bool enable)
* @port: DP out port to check
*
* Checks if the DP OUT adapter port has HPD bit already set.
+ *
+ * Return: %1 if HPD is active, %0 otherwise.
*/
int tb_dp_port_hpd_is_active(struct tb_port *port)
{
@@ -1408,6 +1440,8 @@ int tb_dp_port_hpd_is_active(struct tb_port *port)
* @port: Port to clear HPD
*
* If the DP IN port has HPD set, this function can be used to clear it.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_dp_port_hpd_clear(struct tb_port *port)
{
@@ -1434,6 +1468,8 @@ int tb_dp_port_hpd_clear(struct tb_port *port)
* Programs specified Hop IDs for DP IN/OUT port. Can be called for USB4
* router DP adapters too but does not program the values as the fields
* are read-only.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_dp_port_set_hops(struct tb_port *port, unsigned int video,
unsigned int aux_tx, unsigned int aux_rx)
@@ -1466,6 +1502,8 @@ int tb_dp_port_set_hops(struct tb_port *port, unsigned int video,
/**
* tb_dp_port_is_enabled() - Is DP adapter port enabled
* @port: DP adapter port to check
+ *
+ * Return: %true if DP port is enabled, %false otherwise.
*/
bool tb_dp_port_is_enabled(struct tb_port *port)
{
@@ -1485,6 +1523,8 @@ bool tb_dp_port_is_enabled(struct tb_port *port)
*
* Once Hop IDs are programmed DP paths can be enabled or disabled by
* calling this function.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_dp_port_enable(struct tb_port *port, bool enable)
{
@@ -1634,7 +1674,7 @@ static bool tb_switch_enumerated(struct tb_switch *sw)
*
* If the router is not enumerated does nothing.
*
- * Returns %0 on success or negative errno in case of failure.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_reset(struct tb_switch *sw)
{
@@ -1670,8 +1710,12 @@ int tb_switch_reset(struct tb_switch *sw)
* @timeout_msec: Timeout in ms how long to wait
*
* Wait till the specified bits in specified offset reach specified value.
- * Returns %0 in case of success, %-ETIMEDOUT if the @value was not reached
- * within the given timeout or a negative errno in case of failure.
+ *
+ * Return:
+ * * %0 - On success.
+ * * %-ETIMEDOUT - If the @value was not reached within
+ * the given timeout.
+ * * Negative errno - In case of failure.
*/
int tb_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit,
u32 value, int timeout_msec)
@@ -1700,7 +1744,7 @@ int tb_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit,
*
* Also configures a sane plug_events_delay of 255ms.
*
- * Return: Returns 0 on success or an error code on failure.
+ * Return: %0 on success, negative errno otherwise.
*/
static int tb_plug_events_active(struct tb_switch *sw, bool active)
{
@@ -2406,8 +2450,7 @@ static bool tb_switch_exceeds_max_depth(const struct tb_switch *sw, int depth)
* separately. The returned switch should be released by calling
* tb_switch_put().
*
- * Return: Pointer to the allocated switch or ERR_PTR() in case of
- * failure.
+ * Return: Pointer to &struct tb_switch or ERR_PTR() in case of failure.
*/
struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent,
u64 route)
@@ -2526,7 +2569,7 @@ err_free_sw_ports:
*
* The returned switch must be released by calling tb_switch_put().
*
- * Return: Pointer to the allocated switch or ERR_PTR() in case of failure
+ * Return: Pointer to &struct tb_switch or ERR_PTR() in case of failure.
*/
struct tb_switch *
tb_switch_alloc_safe_mode(struct tb *tb, struct device *parent, u64 route)
@@ -2562,7 +2605,7 @@ tb_switch_alloc_safe_mode(struct tb *tb, struct device *parent, u64 route)
* connection manager to use. Can be called to the switch again after
* resume from low power states to re-initialize it.
*
- * Return: %0 in case of success and negative errno in case of failure
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_configure(struct tb_switch *sw)
{
@@ -2625,7 +2668,7 @@ int tb_switch_configure(struct tb_switch *sw)
* Needs to be called before any tunnels can be setup through the
* router. Can be called to any router.
*
- * Returns %0 in success and negative errno otherwise.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_configuration_valid(struct tb_switch *sw)
{
@@ -2900,6 +2943,8 @@ static void tb_switch_link_init(struct tb_switch *sw)
* Connection manager can call this function to enable lane bonding of a
* switch. If conditions are correct and both switches support the feature,
* lanes are bonded. It is safe to call this to any switch.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
static int tb_switch_lane_bonding_enable(struct tb_switch *sw)
{
@@ -2950,6 +2995,8 @@ static int tb_switch_lane_bonding_enable(struct tb_switch *sw)
*
* Disables lane bonding between @sw and parent. This can be called even
* if lanes were not bonded originally.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
static int tb_switch_lane_bonding_disable(struct tb_switch *sw)
{
@@ -3074,7 +3121,7 @@ static int tb_switch_asym_disable(struct tb_switch *sw)
*
* Does nothing for host router.
*
- * Returns %0 in case of success, negative errno otherwise.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_set_link_width(struct tb_switch *sw, enum tb_link_width width)
{
@@ -3145,7 +3192,7 @@ int tb_switch_set_link_width(struct tb_switch *sw, enum tb_link_width width)
*
* It is recommended that this is called after lane bonding is enabled.
*
- * Returns %0 on success and negative errno in case of error.
+ * Return: %0 on success and negative errno otherwise.
*/
int tb_switch_configure_link(struct tb_switch *sw)
{
@@ -3245,7 +3292,7 @@ static int tb_switch_port_hotplug_enable(struct tb_switch *sw)
* exposed to the userspace when this function successfully returns. To
* remove and release the switch, call tb_switch_remove().
*
- * Return: %0 in case of success and negative errno in case of failure
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_add(struct tb_switch *sw)
{
@@ -3467,6 +3514,8 @@ static void tb_switch_check_wakes(struct tb_switch *sw)
* suspend. If this is resume from system sleep, notifies PM core about the
* wakes occurred during suspend. Disables all wakes, except USB4 wake of
* upstream port for USB4 routers that shall be always enabled.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_resume(struct tb_switch *sw, bool runtime)
{
@@ -3617,7 +3666,9 @@ void tb_switch_suspend(struct tb_switch *sw, bool runtime)
* @in: DP IN port
*
* Queries availability of DP resource for DP tunneling using switch
- * specific means. Returns %true if resource is available.
+ * specific means.
+ *
+ * Return: %true if resource is available, %false otherwise.
*/
bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in)
{
@@ -3633,7 +3684,8 @@ bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in)
*
* Allocates DP resource for DP tunneling. The resource must be
* available for this to succeed (see tb_switch_query_dp_resource()).
- * Returns %0 in success and negative errno otherwise.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
{
@@ -3718,6 +3770,8 @@ static int tb_switch_match(struct device *dev, const void *data)
*
* Returned switch has reference count increased so the caller needs to
* call tb_switch_put() when done with the switch.
+ *
+ * Return: Pointer to &struct tb_switch, %NULL if not found.
*/
struct tb_switch *tb_switch_find_by_link_depth(struct tb *tb, u8 link, u8 depth)
{
@@ -3743,6 +3797,8 @@ struct tb_switch *tb_switch_find_by_link_depth(struct tb *tb, u8 link, u8 depth)
*
* Returned switch has reference count increased so the caller needs to
* call tb_switch_put() when done with the switch.
+ *
+ * Return: Pointer to &struct tb_switch, %NULL if not found.
*/
struct tb_switch *tb_switch_find_by_uuid(struct tb *tb, const uuid_t *uuid)
{
@@ -3767,6 +3823,8 @@ struct tb_switch *tb_switch_find_by_uuid(struct tb *tb, const uuid_t *uuid)
*
* Returned switch has reference count increased so the caller needs to
* call tb_switch_put() when done with the switch.
+ *
+ * Return: Pointer to &struct tb_switch, %NULL if not found.
*/
struct tb_switch *tb_switch_find_by_route(struct tb *tb, u64 route)
{
@@ -3791,6 +3849,8 @@ struct tb_switch *tb_switch_find_by_route(struct tb *tb, u64 route)
* tb_switch_find_port() - return the first port of @type on @sw or NULL
* @sw: Switch to find the port from
* @type: Port type to look for
+ *
+ * Return: Pointer to &struct tb_port, %NULL if not found.
*/
struct tb_port *tb_switch_find_port(struct tb_switch *sw,
enum tb_port_type type)
@@ -3859,6 +3919,8 @@ static int tb_switch_pcie_bridge_write(struct tb_switch *sw, unsigned int bridge
* entry to PCIe L1 state. Shall be called after the upstream PCIe tunnel
* was configured. Due to Intel platforms limitation, shall be called only
* for first hop switch.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_pcie_l1_enable(struct tb_switch *sw)
{
@@ -3893,6 +3955,8 @@ int tb_switch_pcie_l1_enable(struct tb_switch *sw)
* connected to the type-C port. Call only after PCIe tunnel has been
* established. The function only does the connect if not done already
* so can be called several times for the same router.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_xhci_connect(struct tb_switch *sw)
{
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index c14ab1fbeeaf..4a94cb406bdf 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -225,14 +225,12 @@ static int tb_enable_clx(struct tb_switch *sw)
return ret == -EOPNOTSUPP ? 0 : ret;
}
-/**
- * tb_disable_clx() - Disable CL states up to host router
- * @sw: Router to start
+/*
+ * Disables CL states from @sw up to the host router.
*
- * Disables CL states from @sw up to the host router. Returns true if
- * any CL state were disabled. This can be used to figure out whether
- * the link was setup by us or the boot firmware so we don't
- * accidentally enable them if they were not enabled during discovery.
+ * This can be used to figure out whether the link was setup by us or the
+ * boot firmware so we don't accidentally enable them if they were not
+ * enabled during discovery.
*/
static bool tb_disable_clx(struct tb_switch *sw)
{
@@ -456,10 +454,8 @@ static void tb_scan_xdomain(struct tb_port *port)
}
}
-/**
- * tb_find_unused_port() - return the first inactive port on @sw
- * @sw: Switch to find the port on
- * @type: Port type to look for
+/*
+ * Returns the first inactive port on @sw.
*/
static struct tb_port *tb_find_unused_port(struct tb_switch *sw,
enum tb_port_type type)
@@ -549,6 +545,8 @@ static struct tb_tunnel *tb_find_first_usb3_tunnel(struct tb *tb,
* from @src_port to @dst_port. Does not take USB3 tunnel starting from
* @src_port and ending on @src_port into account because that bandwidth is
* already included in as part of the "first hop" USB3 tunnel.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
static int tb_consumed_usb3_pcie_bandwidth(struct tb *tb,
struct tb_port *src_port,
@@ -601,6 +599,8 @@ static int tb_consumed_usb3_pcie_bandwidth(struct tb *tb,
* If there is bandwidth reserved for any of the groups between
* @src_port and @dst_port (but not yet used) that is also taken into
* account in the returned consumed bandwidth.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
static int tb_consumed_dp_bandwidth(struct tb *tb,
struct tb_port *src_port,
@@ -701,6 +701,8 @@ static bool tb_asym_supported(struct tb_port *src_port, struct tb_port *dst_port
* single link at @port. If @include_asym is set then includes the
* additional banwdith if the links are transitioned into asymmetric to
* direction from @src_port to @dst_port.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
static int tb_maximum_bandwidth(struct tb *tb, struct tb_port *src_port,
struct tb_port *dst_port, struct tb_port *port,
@@ -807,6 +809,8 @@ static int tb_maximum_bandwidth(struct tb *tb, struct tb_port *src_port,
* If @include_asym is true then includes also bandwidth that can be
* added when the links are transitioned into asymmetric (but does not
* transition the links).
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port,
struct tb_port *dst_port, int *available_up,
@@ -1029,6 +1033,8 @@ static int tb_create_usb3_tunnels(struct tb_switch *sw)
* (requested + currently consumed) on that link exceed @asym_threshold.
*
* Must be called with available >= requested over all links.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
static int tb_configure_asym(struct tb *tb, struct tb_port *src_port,
struct tb_port *dst_port, int requested_up,
@@ -1135,6 +1141,8 @@ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port,
* Goes over each link from @src_port to @dst_port and tries to
* transition the link to symmetric if the currently consumed bandwidth
* allows and link asymmetric preference is ignored (if @keep_asym is %false).
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
static int tb_configure_sym(struct tb *tb, struct tb_port *src_port,
struct tb_port *dst_port, bool keep_asym)
@@ -3336,7 +3344,7 @@ static bool tb_apple_add_links(struct tb_nhi *nhi)
if (!pci_is_pcie(pdev))
continue;
if (pci_pcie_type(pdev) != PCI_EXP_TYPE_DOWNSTREAM ||
- !pdev->is_hotplug_bridge)
+ !pdev->is_pciehp)
continue;
link = device_link_add(&pdev->dev, &nhi->pdev->dev,
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index f503bad86413..8e2762ff8d51 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -324,7 +324,7 @@ struct usb4_port {
};
/**
- * tb_retimer: Thunderbolt retimer
+ * struct tb_retimer - Thunderbolt retimer
* @dev: Device for the retimer
* @tb: Pointer to the domain the retimer belongs to
* @index: Retimer index facing the router USB4 port
@@ -552,13 +552,14 @@ static inline void *tb_priv(struct tb *tb)
/**
* tb_upstream_port() - return the upstream port of a switch
+ * @sw: Router
*
* Every switch has an upstream port (for the root switch it is the NHI).
*
* During switch alloc/init tb_upstream_port()->remote may be NULL, even for
* non root switches (on the NHI port remote is always NULL).
*
- * Return: Returns the upstream port of the switch.
+ * Return: Pointer to &struct tb_port.
*/
static inline struct tb_port *tb_upstream_port(struct tb_switch *sw)
{
@@ -569,8 +570,8 @@ static inline struct tb_port *tb_upstream_port(struct tb_switch *sw)
* tb_is_upstream_port() - Is the port upstream facing
* @port: Port to check
*
- * Returns true if @port is upstream facing port. In case of dual link
- * ports both return true.
+ * Return: %true if @port is upstream facing port. In case of dual link
+ * ports, both return %true.
*/
static inline bool tb_is_upstream_port(const struct tb_port *port)
{
@@ -613,7 +614,7 @@ static inline const char *tb_width_name(enum tb_link_width width)
* tb_port_has_remote() - Does the port have switch connected downstream
* @port: Port to check
*
- * Returns true only when the port is primary port and has remote set.
+ * Return: %true only when the port is primary port and has remote set.
*/
static inline bool tb_port_has_remote(const struct tb_port *port)
{
@@ -905,8 +906,9 @@ static inline struct tb_switch *tb_switch_parent(struct tb_switch *sw)
* tb_switch_downstream_port() - Return downstream facing port of parent router
* @sw: Device router pointer
*
- * Only call for device routers. Returns the downstream facing port of
- * the parent router.
+ * Call only for device routers.
+ *
+ * Return: Pointer to &struct tb_port or %NULL in case of failure.
*/
static inline struct tb_port *tb_switch_downstream_port(struct tb_switch *sw)
{
@@ -918,6 +920,8 @@ static inline struct tb_port *tb_switch_downstream_port(struct tb_switch *sw)
/**
* tb_switch_depth() - Returns depth of the connected router
* @sw: Router
+ *
+ * Return: Router depth level as a number.
*/
static inline int tb_switch_depth(const struct tb_switch *sw)
{
@@ -1010,6 +1014,9 @@ static inline bool tb_switch_is_tiger_lake(const struct tb_switch *sw)
* is handling @sw this function can be called. It is valid to call this
* after tb_switch_alloc() and tb_switch_configure() has been called
* (latter only for SW CM case).
+ *
+ * Return: %true if switch is handled by ICM, %false if handled by
+ * software CM.
*/
static inline bool tb_switch_is_icm(const struct tb_switch *sw)
{
@@ -1037,6 +1044,8 @@ int tb_switch_tmu_configure(struct tb_switch *sw, enum tb_switch_tmu_mode mode);
*
* Checks if given router TMU mode is configured to @mode. Note the
* router TMU might not be enabled to this mode.
+ *
+ * Return: %true if TMU mode is equal to @mode, %false otherwise.
*/
static inline bool tb_switch_tmu_is_configured(const struct tb_switch *sw,
enum tb_switch_tmu_mode mode)
@@ -1048,8 +1057,8 @@ static inline bool tb_switch_tmu_is_configured(const struct tb_switch *sw,
* tb_switch_tmu_is_enabled() - Checks if the specified TMU mode is enabled
* @sw: Router whose TMU mode to check
*
- * Return true if hardware TMU configuration matches the requested
- * configuration (and is not %TB_SWITCH_TMU_MODE_OFF).
+ * Return: %true if hardware TMU configuration matches the requested
+ * configuration (and is not %TB_SWITCH_TMU_MODE_OFF), %false otherwise.
*/
static inline bool tb_switch_tmu_is_enabled(const struct tb_switch *sw)
{
@@ -1069,9 +1078,10 @@ int tb_switch_clx_disable(struct tb_switch *sw);
* @clx: The CLx states to check for
*
* Checks if the specified CLx is enabled on the router upstream link.
- * Returns true if any of the given states is enabled.
*
* Not applicable for a host router.
+ *
+ * Return: %true if any of the given states is enabled, %false otherwise.
*/
static inline bool tb_switch_clx_is_enabled(const struct tb_switch *sw,
unsigned int clx)
@@ -1103,7 +1113,7 @@ struct tb_port *tb_next_port_on_path(struct tb_port *start, struct tb_port *end,
* @src: Source adapter
* @dst: Destination adapter
*
- * Returns %true only if the specified path from source adapter (@src)
+ * Return: %true only if the specified path from source adapter (@src)
* to destination adapter (@dst) is directed downstream.
*/
static inline bool
@@ -1232,10 +1242,11 @@ static inline int tb_route_length(u64 route)
/**
* tb_downstream_route() - get route to downstream switch
+ * @port: Port to check
*
* Port must not be the upstream port (otherwise a loop is created).
*
- * Return: Returns a route to the switch behind @port.
+ * Return: Route to the switch behind @port.
*/
static inline u64 tb_downstream_route(struct tb_port *port)
{
@@ -1263,7 +1274,7 @@ static inline struct tb_switch *tb_xdomain_parent(struct tb_xdomain *xd)
* tb_xdomain_downstream_port() - Return downstream facing port of parent router
* @xd: Xdomain pointer
*
- * Returns the downstream port the XDomain is connected to.
+ * Return: Pointer to &struct tb_port or %NULL in case of failure.
*/
static inline struct tb_port *tb_xdomain_downstream_port(struct tb_xdomain *xd)
{
@@ -1291,7 +1302,7 @@ static inline struct tb_retimer *tb_to_retimer(struct device *dev)
* usb4_switch_version() - Returns USB4 version of the router
* @sw: Router to check
*
- * Returns major version of USB4 router (%1 for v1, %2 for v2 and so
+ * Return: Major version of USB4 router (%1 for v1, %2 for v2 and so
* on). Can be called to pre-USB4 router too and in that case returns %0.
*/
static inline unsigned int usb4_switch_version(const struct tb_switch *sw)
@@ -1303,7 +1314,7 @@ static inline unsigned int usb4_switch_version(const struct tb_switch *sw)
* tb_switch_is_usb4() - Is the switch USB4 compliant
* @sw: Switch to check
*
- * Returns true if the @sw is USB4 compliant router, false otherwise.
+ * Return: %true if the @sw is USB4 compliant router, %false otherwise.
*/
static inline bool tb_switch_is_usb4(const struct tb_switch *sw)
{
@@ -1355,7 +1366,7 @@ int usb4_port_asym_set_link_width(struct tb_port *port, enum tb_link_width width
int usb4_port_asym_start(struct tb_port *port);
/**
- * enum tb_sb_target - Sideband transaction target
+ * enum usb4_sb_target - Sideband transaction target
* @USB4_SB_TARGET_ROUTER: Target is the router itself
* @USB4_SB_TARGET_PARTNER: Target is partner
* @USB4_SB_TARGET_RETIMER: Target is retimer
@@ -1400,6 +1411,8 @@ enum usb4_margining_lane {
* @voltage_time_offset: Offset for voltage / time for software margining
* @optional_voltage_offset_range: Enable optional extended voltage range
* @right_high: %false if left/low margin test is performed, %true if right/high
+ * @upper_eye: %true if margin test is done on upper eye, %false if done on
+ * lower eye
* @time: %true if time margining is used instead of voltage
*/
struct usb4_port_margining_params {
diff --git a/drivers/thunderbolt/tmu.c b/drivers/thunderbolt/tmu.c
index 9a259c72e5a7..b22831b41ec0 100644
--- a/drivers/thunderbolt/tmu.c
+++ b/drivers/thunderbolt/tmu.c
@@ -405,6 +405,8 @@ static int tmu_mode_init(struct tb_switch *sw)
* This function must be called before other TMU related functions to
* makes the internal structures are filled in correctly. Does not
* change any hardware configuration.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_tmu_init(struct tb_switch *sw)
{
@@ -439,6 +441,8 @@ int tb_switch_tmu_init(struct tb_switch *sw)
* @sw: Switch whose time to update
*
* Updates switch local time using time posting procedure.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_tmu_post_time(struct tb_switch *sw)
{
@@ -555,6 +559,8 @@ static int disable_enhanced(struct tb_port *up, struct tb_port *down)
* @sw: Switch whose TMU to disable
*
* Turns off TMU of @sw if it is enabled. If not enabled does nothing.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_tmu_disable(struct tb_switch *sw)
{
@@ -938,6 +944,8 @@ out:
* Enables TMU of a router to be in uni-directional Normal/HiFi or
* bi-directional HiFi mode. Calling tb_switch_tmu_configure() is
* required before calling this function.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_switch_tmu_enable(struct tb_switch *sw)
{
@@ -1017,9 +1025,11 @@ int tb_switch_tmu_enable(struct tb_switch *sw)
* Selects the TMU mode that is enabled when tb_switch_tmu_enable() is
* next called.
*
- * Returns %0 in success and negative errno otherwise. Specifically
- * returns %-EOPNOTSUPP if the requested mode is not possible (not
- * supported by the router and/or topology).
+ * Return:
+ * * %0 - On success.
+ * * %-EOPNOTSUPP - If the requested mode is not possible (not supported by
+ * the router and/or topology).
+ * * Negative errno - Another error occurred.
*/
int tb_switch_tmu_configure(struct tb_switch *sw, enum tb_switch_tmu_mode mode)
{
diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c
index d52efe3f658c..bfa0607b5574 100644
--- a/drivers/thunderbolt/tunnel.c
+++ b/drivers/thunderbolt/tunnel.c
@@ -121,6 +121,8 @@ static inline unsigned int tb_usable_credits(const struct tb_port *port)
* @port: Lane adapter to check
* @max_dp_streams: If non-%NULL stores maximum number of simultaneous DP
* streams possible through this lane adapter
+ *
+ * Return: Number of available credits.
*/
static unsigned int tb_available_credits(const struct tb_port *port,
size_t *max_dp_streams)
@@ -415,8 +417,9 @@ static int tb_pci_init_path(struct tb_path *path)
* @alloc_hopid: Allocate HopIDs from visited ports
*
* If @down adapter is active, follows the tunnel to the PCIe upstream
- * adapter and back. Returns the discovered tunnel or %NULL if there was
- * no tunnel.
+ * adapter and back.
+ *
+ * Return: Pointer to &struct tb_tunnel or %NULL if there was no tunnel.
*/
struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down,
bool alloc_hopid)
@@ -496,7 +499,7 @@ err_free:
* Allocate a PCI tunnel. The ports must be of type TB_TYPE_PCIE_UP and
* TB_TYPE_PCIE_DOWN.
*
- * Return: Returns a tb_tunnel on success or NULL on failure.
+ * Return: Pointer to @struct tb_tunnel or %NULL on failure.
*/
struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up,
struct tb_port *down)
@@ -543,9 +546,12 @@ err_free:
*
* Can be called to any connected lane 0 adapter to find out how much
* bandwidth needs to be left in reserve for possible PCIe bulk traffic.
- * Returns true if there is something to be reserved and writes the
- * amount to @reserved_down/@reserved_up. Otherwise returns false and
- * does not touch the parameters.
+ *
+ * Return:
+ * * %true - If there is something to be reserved. Writes the amount to
+ * @reserved_down/@reserved_up.
+ * * %false - Nothing to be reserved. Leaves @reserved_down/@reserved_up
+ * unmodified.
*/
bool tb_tunnel_reserved_pci(struct tb_port *port, int *reserved_up,
int *reserved_down)
@@ -1073,6 +1079,7 @@ static void tb_dp_dprx_work(struct work_struct *work)
if (tunnel->callback)
tunnel->callback(tunnel, tunnel->callback_data);
+ tb_tunnel_put(tunnel);
}
static int tb_dp_dprx_start(struct tb_tunnel *tunnel)
@@ -1100,8 +1107,8 @@ static void tb_dp_dprx_stop(struct tb_tunnel *tunnel)
if (tunnel->dprx_started) {
tunnel->dprx_started = false;
tunnel->dprx_canceled = true;
- cancel_delayed_work(&tunnel->dprx_work);
- tb_tunnel_put(tunnel);
+ if (cancel_delayed_work(&tunnel->dprx_work))
+ tb_tunnel_put(tunnel);
}
}
@@ -1151,7 +1158,8 @@ static int tb_dp_activate(struct tb_tunnel *tunnel, bool active)
* @tunnel: DP tunnel to check
* @max_bw_rounded: Maximum bandwidth in Mb/s rounded up to the next granularity
*
- * Returns maximum possible bandwidth for this tunnel in Mb/s.
+ * Return: Maximum possible bandwidth for this tunnel in Mb/s, negative errno
+ * in case of failure.
*/
static int tb_dp_bandwidth_mode_maximum_bandwidth(struct tb_tunnel *tunnel,
int *max_bw_rounded)
@@ -1547,7 +1555,7 @@ static void tb_dp_dump(struct tb_tunnel *tunnel)
* and back. Returns the discovered tunnel or %NULL if there was no
* tunnel.
*
- * Return: DP tunnel or %NULL if no tunnel found.
+ * Return: Pointer to &struct tb_tunnel or %NULL if no tunnel found.
*/
struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in,
bool alloc_hopid)
@@ -1648,7 +1656,7 @@ err_free:
* successful (or if it returns %false there was some sort of issue).
* The @callback is called without @tb->lock held.
*
- * Return: Returns a tb_tunnel on success or &NULL on failure.
+ * Return: Pointer to @struct tb_tunnel or %NULL in case of failure.
*/
struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in,
struct tb_port *out, int link_nr,
@@ -1861,7 +1869,7 @@ static void tb_dma_destroy(struct tb_tunnel *tunnel)
* @receive_ring: NHI ring number used to receive packets from the
* other domain. Set to %-1 if RX path is not needed.
*
- * Return: Returns a tb_tunnel on success or NULL on failure.
+ * Return: Pointer to @struct tb_tunnel or %NULL in case of failure.
*/
struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi,
struct tb_port *dst, int transmit_path,
@@ -1938,7 +1946,8 @@ err_free:
*
* This function can be used to match specific DMA tunnel, if there are
* multiple DMA tunnels going through the same XDomain connection.
- * Returns true if there is match and false otherwise.
+ *
+ * Return: %true if there is a match, %false otherwise.
*/
bool tb_tunnel_match_dma(const struct tb_tunnel *tunnel, int transmit_path,
int transmit_ring, int receive_path, int receive_ring)
@@ -2160,8 +2169,9 @@ static void tb_usb3_init_path(struct tb_path *path)
* @alloc_hopid: Allocate HopIDs from visited ports
*
* If @down adapter is active, follows the tunnel to the USB3 upstream
- * adapter and back. Returns the discovered tunnel or %NULL if there was
- * no tunnel.
+ * adapter and back.
+ *
+ * Return: Pointer to &struct tb_tunnel or %NULL if there was no tunnel.
*/
struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down,
bool alloc_hopid)
@@ -2266,7 +2276,7 @@ err_free:
* Allocate an USB3 tunnel. The ports must be of type @TB_TYPE_USB3_UP and
* @TB_TYPE_USB3_DOWN.
*
- * Return: Returns a tb_tunnel on success or %NULL on failure.
+ * Return: Pointer to @struct tb_tunnel or %NULL in case of failure.
*/
struct tb_tunnel *tb_tunnel_alloc_usb3(struct tb *tb, struct tb_port *up,
struct tb_port *down, int max_up,
@@ -2337,6 +2347,8 @@ err_free:
/**
* tb_tunnel_is_invalid - check whether an activated path is still valid
* @tunnel: Tunnel to check
+ *
+ * Return: %true if path is valid, %false otherwise.
*/
bool tb_tunnel_is_invalid(struct tb_tunnel *tunnel)
{
@@ -2355,10 +2367,11 @@ bool tb_tunnel_is_invalid(struct tb_tunnel *tunnel)
* tb_tunnel_activate() - activate a tunnel
* @tunnel: Tunnel to activate
*
- * Return: 0 on success and negative errno in case if failure.
- * Specifically returns %-EINPROGRESS if the tunnel activation is still
- * in progress (that's for DP tunnels to complete DPRX capabilities
- * read).
+ * Return:
+ * * %0 - On success.
+ * * %-EINPROGRESS - If the tunnel activation is still in progress (that's
+ * for DP tunnels to complete DPRX capabilities read).
+ * * Negative errno - Another error occurred.
*/
int tb_tunnel_activate(struct tb_tunnel *tunnel)
{
@@ -2438,8 +2451,8 @@ void tb_tunnel_deactivate(struct tb_tunnel *tunnel)
* @tunnel: Tunnel to check
* @port: Port to check
*
- * Returns true if @tunnel goes through @port (direction does not matter),
- * false otherwise.
+ * Return: %true if @tunnel goes through @port (direction does not matter),
+ * %false otherwise.
*/
bool tb_tunnel_port_on_path(const struct tb_tunnel *tunnel,
const struct tb_port *port)
@@ -2469,9 +2482,11 @@ static bool tb_tunnel_is_activated(const struct tb_tunnel *tunnel)
* @max_up: Maximum upstream bandwidth in Mb/s
* @max_down: Maximum downstream bandwidth in Mb/s
*
- * Returns maximum possible bandwidth this tunnel can go if not limited
- * by other bandwidth clients. If the tunnel does not support this
- * returns %-EOPNOTSUPP.
+ * Return:
+ * * Maximum possible bandwidth this tunnel can support if not
+ * limited by other bandwidth clients.
+ * * %-EOPNOTSUPP - If the tunnel does not support this function.
+ * * %-ENOTCONN - If the tunnel is not active.
*/
int tb_tunnel_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up,
int *max_down)
@@ -2491,8 +2506,12 @@ int tb_tunnel_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up,
* @allocated_down: Currently allocated downstream bandwidth in Mb/s is
* stored here
*
- * Returns the bandwidth allocated for the tunnel. This may be higher
- * than what the tunnel actually consumes.
+ * Return:
+ * * Bandwidth allocated for the tunnel. This may be higher than what the
+ * tunnel actually consumes.
+ * * %-EOPNOTSUPP - If the tunnel does not support this function.
+ * * %-ENOTCONN - If the tunnel is not active.
+ * * Negative errno - Another error occurred.
*/
int tb_tunnel_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up,
int *allocated_down)
@@ -2512,10 +2531,12 @@ int tb_tunnel_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up,
* @alloc_up: New upstream bandwidth in Mb/s
* @alloc_down: New downstream bandwidth in Mb/s
*
- * Tries to change tunnel bandwidth allocation. If succeeds returns %0
- * and updates @alloc_up and @alloc_down to that was actually allocated
- * (it may not be the same as passed originally). Returns negative errno
- * in case of failure.
+ * Tries to change tunnel bandwidth allocation.
+ *
+ * Return:
+ * * %0 - On success. Updates @alloc_up and @alloc_down to values that were
+ * actually allocated (it may not be the same as passed originally).
+ * * Negative errno - In case of failure.
*/
int tb_tunnel_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up,
int *alloc_down)
@@ -2546,8 +2567,9 @@ int tb_tunnel_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up,
* Can be %NULL.
*
* Stores the amount of isochronous bandwidth @tunnel consumes in
- * @consumed_up and @consumed_down. In case of success returns %0,
- * negative errno otherwise.
+ * @consumed_up and @consumed_down.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_tunnel_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up,
int *consumed_down)
@@ -2585,7 +2607,7 @@ int tb_tunnel_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up,
* If tunnel supports dynamic bandwidth management (USB3 tunnels at the
* moment) this function makes it to release all the unused bandwidth.
*
- * Returns %0 in case of success and negative errno otherwise.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_tunnel_release_unused_bandwidth(struct tb_tunnel *tunnel)
{
diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h
index 5e9fb73d5220..2c44fc8a10bc 100644
--- a/drivers/thunderbolt/tunnel.h
+++ b/drivers/thunderbolt/tunnel.h
@@ -142,10 +142,11 @@ void tb_tunnel_deactivate(struct tb_tunnel *tunnel);
* tb_tunnel_is_active() - Is tunnel fully activated
* @tunnel: Tunnel to check
*
- * Returns %true if @tunnel is fully activated. For other than DP
- * tunnels this is pretty much once tb_tunnel_activate() returns
- * successfully. However, for DP tunnels this returns %true only once the
- * DPRX capabilities read has been issued successfully.
+ * Return: %true if @tunnel is fully activated.
+ *
+ * Note for DP tunnels this returns %true only once the DPRX capabilities
+ * read has been issued successfully. For other tunnels, this function
+ * returns %true pretty much once tb_tunnel_activate() returns successfully.
*/
static inline bool tb_tunnel_is_active(const struct tb_tunnel *tunnel)
{
diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c
index fdae76c8f728..76f01713a875 100644
--- a/drivers/thunderbolt/usb4.c
+++ b/drivers/thunderbolt/usb4.c
@@ -9,6 +9,7 @@
#include <linux/delay.h>
#include <linux/ktime.h>
+#include <linux/string_choices.h>
#include <linux/units.h>
#include "sb_regs.h"
@@ -172,8 +173,8 @@ void usb4_switch_check_wakes(struct tb_switch *sw)
return;
tb_sw_dbg(sw, "PCIe wake: %s, USB3 wake: %s\n",
- (val & ROUTER_CS_6_WOPS) ? "yes" : "no",
- (val & ROUTER_CS_6_WOUS) ? "yes" : "no");
+ str_yes_no(val & ROUTER_CS_6_WOPS),
+ str_yes_no(val & ROUTER_CS_6_WOUS));
wakeup = val & (ROUTER_CS_6_WOPS | ROUTER_CS_6_WOUS);
}
@@ -191,9 +192,9 @@ void usb4_switch_check_wakes(struct tb_switch *sw)
break;
tb_port_dbg(port, "USB4 wake: %s, connection wake: %s, disconnection wake: %s\n",
- (val & PORT_CS_18_WOU4S) ? "yes" : "no",
- (val & PORT_CS_18_WOCS) ? "yes" : "no",
- (val & PORT_CS_18_WODS) ? "yes" : "no");
+ str_yes_no(val & PORT_CS_18_WOU4S),
+ str_yes_no(val & PORT_CS_18_WOCS),
+ str_yes_no(val & PORT_CS_18_WODS));
wakeup_usb4 = val & (PORT_CS_18_WOU4S | PORT_CS_18_WOCS |
PORT_CS_18_WODS);
@@ -236,6 +237,8 @@ static bool link_is_usb4(struct tb_port *port)
*
* This does not set the configuration valid bit of the router. To do
* that call usb4_switch_configuration_valid().
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_switch_setup(struct tb_switch *sw)
{
@@ -260,7 +263,7 @@ int usb4_switch_setup(struct tb_switch *sw)
tbt3 = !(val & ROUTER_CS_6_TNS);
tb_sw_dbg(sw, "TBT3 support: %s, xHCI: %s\n",
- tbt3 ? "yes" : "no", xhci ? "yes" : "no");
+ str_yes_no(tbt3), str_yes_no(xhci));
ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
if (ret)
@@ -303,7 +306,7 @@ int usb4_switch_setup(struct tb_switch *sw)
* usb4_switch_setup() has been called. Can be called to host and device
* routers (does nothing for the latter).
*
- * Returns %0 in success and negative errno otherwise.
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_switch_configuration_valid(struct tb_switch *sw)
{
@@ -333,6 +336,8 @@ int usb4_switch_configuration_valid(struct tb_switch *sw)
* @uid: UID is stored here
*
* Reads 64-bit UID from USB4 router config space.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_switch_read_uid(struct tb_switch *sw, u64 *uid)
{
@@ -370,6 +375,8 @@ static int usb4_switch_drom_read_block(void *data,
* Uses USB4 router operations to read router DROM. For devices this
* should always work but for hosts it may return %-EOPNOTSUPP in which
* case the host router does not have DROM.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf,
size_t size)
@@ -384,6 +391,8 @@ int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf,
*
* Checks whether conditions are met so that lane bonding can be
* established with the upstream router. Call only for device routers.
+ *
+ * Return: %true if lane bonding is possible, %false otherwise.
*/
bool usb4_switch_lane_bonding_possible(struct tb_switch *sw)
{
@@ -406,6 +415,8 @@ bool usb4_switch_lane_bonding_possible(struct tb_switch *sw)
* @runtime: Wake is being programmed during system runtime
*
* Enables/disables router to wake up from sleep.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags, bool runtime)
{
@@ -483,8 +494,10 @@ int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags, bool runtime)
* usb4_switch_set_sleep() - Prepare the router to enter sleep
* @sw: USB4 router
*
- * Sets sleep bit for the router. Returns when the router sleep ready
+ * Sets sleep bit for the router and waits until router sleep ready
* bit has been asserted.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_switch_set_sleep(struct tb_switch *sw)
{
@@ -510,9 +523,10 @@ int usb4_switch_set_sleep(struct tb_switch *sw)
* usb4_switch_nvm_sector_size() - Return router NVM sector size
* @sw: USB4 router
*
- * If the router supports NVM operations this function returns the NVM
- * sector size in bytes. If NVM operations are not supported returns
- * %-EOPNOTSUPP.
+ * Return:
+ * * NVM sector size in bytes if router supports NVM operations.
+ * * %-EOPNOTSUPP - If router does not support NVM operations.
+ * * Negative errno - Another error occurred.
*/
int usb4_switch_nvm_sector_size(struct tb_switch *sw)
{
@@ -559,8 +573,12 @@ static int usb4_switch_nvm_read_block(void *data,
* @buf: Read data is placed here
* @size: How many bytes to read
*
- * Reads NVM contents of the router. If NVM is not supported returns
- * %-EOPNOTSUPP.
+ * Reads NVM contents of the router.
+ *
+ * Return:
+ * * %0 - Read completed successfully.
+ * * %-EOPNOTSUPP - NVM not supported.
+ * * Negative errno - Another error occurred.
*/
int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf,
size_t size)
@@ -577,7 +595,7 @@ int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf,
* Explicitly sets NVM write offset. Normally when writing to NVM this
* is done automatically by usb4_switch_nvm_write().
*
- * Returns %0 in success and negative errno if there was a failure.
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_switch_nvm_set_offset(struct tb_switch *sw, unsigned int address)
{
@@ -619,8 +637,12 @@ static int usb4_switch_nvm_write_next_block(void *data, unsigned int dwaddress,
* @buf: Pointer to the data to write
* @size: Size of @buf in bytes
*
- * Writes @buf to the router NVM using USB4 router operations. If NVM
- * write is not supported returns %-EOPNOTSUPP.
+ * Writes @buf to the router NVM using USB4 router operations.
+ *
+ * Return:
+ * * %0 - Write completed successfully.
+ * * %-EOPNOTSUPP - NVM write not supported.
+ * * Negative errno - Another error occurred.
*/
int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address,
const void *buf, size_t size)
@@ -642,11 +664,13 @@ int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address,
* After the new NVM has been written via usb4_switch_nvm_write(), this
* function triggers NVM authentication process. The router gets power
* cycled and if the authentication is successful the new NVM starts
- * running. In case of failure returns negative errno.
+ * running.
*
* The caller should call usb4_switch_nvm_authenticate_status() to read
* the status of the authentication after power cycle. It should be the
* first router operation to avoid the status being lost.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_switch_nvm_authenticate(struct tb_switch *sw)
{
@@ -674,11 +698,13 @@ int usb4_switch_nvm_authenticate(struct tb_switch *sw)
* @status: Status code of the operation
*
* The function checks if there is status available from the last NVM
- * authenticate router operation. If there is status then %0 is returned
- * and the status code is placed in @status. Returns negative errno in case
- * of failure.
+ * authenticate router operation.
*
* Must be called before any other router operation.
+ *
+ * Return:
+ * * %0 - If there is status. Status code is placed in @status.
+ * * Negative errno - Failure occurred.
*/
int usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status)
{
@@ -722,7 +748,7 @@ int usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status)
* allocation fields accordingly. Specifically @sw->credits_allocation
* is set to %true if these parameters can be used in tunneling.
*
- * Returns %0 on success and negative errno otherwise.
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_switch_credits_init(struct tb_switch *sw)
{
@@ -861,8 +887,10 @@ err_invalid:
* @in: DP IN adapter
*
* For DP tunneling this function can be used to query availability of
- * DP IN resource. Returns true if the resource is available for DP
- * tunneling, false otherwise.
+ * DP IN resource.
+ *
+ * Return: %true if the resource is available for DP tunneling, %false
+ * otherwise.
*/
bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in)
{
@@ -890,9 +918,12 @@ bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in)
* @in: DP IN adapter
*
* Allocates DP IN resource for DP tunneling using USB4 router
- * operations. If the resource was allocated returns %0. Otherwise
- * returns negative errno, in particular %-EBUSY if the resource is
- * already allocated.
+ * operations.
+ *
+ * Return:
+ * * %0 - Resource allocated successfully.
+ * * %-EBUSY - Resource is already allocated.
+ * * Negative errno - Other failure occurred.
*/
int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
{
@@ -916,6 +947,8 @@ int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
* @in: DP IN adapter
*
* Releases the previously allocated DP IN resource.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
{
@@ -971,6 +1004,8 @@ int usb4_port_index(const struct tb_switch *sw, const struct tb_port *port)
* downstream adapters where the PCIe topology is extended. This
* function returns the corresponding downstream PCIe adapter or %NULL
* if no such mapping was possible.
+ *
+ * Return: Pointer to &struct tb_port or %NULL if not found.
*/
struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw,
const struct tb_port *port)
@@ -1002,6 +1037,8 @@ struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw,
* downstream adapters where the USB 3.x topology is extended. This
* function returns the corresponding downstream USB 3.x adapter or
* %NULL if no such mapping was possible.
+ *
+ * Return: Pointer to &struct tb_port or %NULL if not found.
*/
struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw,
const struct tb_port *port)
@@ -1031,7 +1068,7 @@ struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw,
* For USB4 router finds all USB4 ports and registers devices for each.
* Can be called to any router.
*
- * Return %0 in case of success and negative errno in case of failure.
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_switch_add_ports(struct tb_switch *sw)
{
@@ -1084,6 +1121,8 @@ void usb4_switch_remove_ports(struct tb_switch *sw)
*
* Unlocks USB4 downstream port so that the connection manager can
* access the router below this port.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_unlock(struct tb_port *port)
{
@@ -1104,6 +1143,8 @@ int usb4_port_unlock(struct tb_port *port)
*
* Enables hot plug events on a given port. This is only intended
* to be used on lane, DP-IN, and DP-OUT adapters.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_hotplug_enable(struct tb_port *port)
{
@@ -1123,6 +1164,8 @@ int usb4_port_hotplug_enable(struct tb_port *port)
* @port: USB4 port to reset
*
* Issues downstream port reset to @port.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_reset(struct tb_port *port)
{
@@ -1184,6 +1227,8 @@ static int usb4_port_set_configured(struct tb_port *port, bool configured)
* @port: USB4 router
*
* Sets the USB4 link to be configured for power management purposes.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_configure(struct tb_port *port)
{
@@ -1195,6 +1240,8 @@ int usb4_port_configure(struct tb_port *port)
* @port: USB4 router
*
* Sets the USB4 link to be unconfigured for power management purposes.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
void usb4_port_unconfigure(struct tb_port *port)
{
@@ -1229,7 +1276,9 @@ static int usb4_set_xdomain_configured(struct tb_port *port, bool configured)
* @xd: XDomain that is connected to the port
*
* Marks the USB4 port as being connected to another host and updates
- * the link type. Returns %0 in success and negative errno in failure.
+ * the link type.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_configure_xdomain(struct tb_port *port, struct tb_xdomain *xd)
{
@@ -1299,7 +1348,8 @@ static int usb4_port_write_data(struct tb_port *port, const void *data,
* @size: Size of @buf
*
* Reads data from sideband register @reg and copies it into @buf.
- * Returns %0 in case of success and negative errno in case of failure.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_sb_read(struct tb_port *port, enum usb4_sb_target target, u8 index,
u8 reg, void *buf, u8 size)
@@ -1350,8 +1400,9 @@ int usb4_port_sb_read(struct tb_port *port, enum usb4_sb_target target, u8 index
* @buf: Data to write
* @size: Size of @buf
*
- * Writes @buf to sideband register @reg. Returns %0 in case of success
- * and negative errno in case of failure.
+ * Writes @buf to sideband register @reg.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_sb_write(struct tb_port *port, enum usb4_sb_target target,
u8 index, u8 reg, const void *buf, u8 size)
@@ -1468,8 +1519,7 @@ static int usb4_port_set_router_offline(struct tb_port *port, bool offline)
* port does not react on hotplug events anymore. This needs to be
* called before retimer access is done when the USB4 links is not up.
*
- * Returns %0 in case of success and negative errno if there was an
- * error.
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_router_offline(struct tb_port *port)
{
@@ -1481,6 +1531,8 @@ int usb4_port_router_offline(struct tb_port *port)
* @port: USB4 port
*
* Makes the USB4 port functional again.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_router_online(struct tb_port *port)
{
@@ -1492,8 +1544,9 @@ int usb4_port_router_online(struct tb_port *port)
* @port: USB4 port
*
* This forces the USB4 port to send broadcast RT transaction which
- * makes the retimers on the link to assign index to themselves. Returns
- * %0 in case of success and negative errno if there was an error.
+ * makes the retimers on the link assign index to themselves.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_enumerate_retimers(struct tb_port *port)
{
@@ -1510,6 +1563,8 @@ int usb4_port_enumerate_retimers(struct tb_port *port)
*
* PORT_CS_18_CPS bit reflects if the link supports CLx including
* active cables (if connected on the link).
+ *
+ * Return: %true if Clx is supported, %false otherwise.
*/
bool usb4_port_clx_supported(struct tb_port *port)
{
@@ -1528,8 +1583,9 @@ bool usb4_port_clx_supported(struct tb_port *port)
* usb4_port_asym_supported() - If the port supports asymmetric link
* @port: USB4 port
*
- * Checks if the port and the cable supports asymmetric link and returns
- * %true in that case.
+ * Checks if the port and the cable support asymmetric link.
+ *
+ * Return: %true if asymmetric link is supported, %false otherwise.
*/
bool usb4_port_asym_supported(struct tb_port *port)
{
@@ -1551,6 +1607,8 @@ bool usb4_port_asym_supported(struct tb_port *port)
*
* Sets USB4 port link width to @width. Can be called for widths where
* usb4_port_asym_width_supported() returned @true.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_asym_set_link_width(struct tb_port *port, enum tb_link_width width)
{
@@ -1595,8 +1653,10 @@ int usb4_port_asym_set_link_width(struct tb_port *port, enum tb_link_width width
* (according to what was previously set in tb_port_set_link_width().
* Wait for completion of the change.
*
- * Returns %0 in case of success, %-ETIMEDOUT if case of timeout or
- * a negative errno in case of a failure.
+ * Return:
+ * * %0 - Symmetry change was successful.
+ * * %-ETIMEDOUT - Timeout occurred.
+ * * Negative errno - Other failure occurred.
*/
int usb4_port_asym_start(struct tb_port *port)
{
@@ -1640,6 +1700,8 @@ int usb4_port_asym_start(struct tb_port *port)
* @ncaps: Number of elements in the caps array
*
* Reads the USB4 port lane margining capabilities into @caps.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target,
u8 index, u32 *caps, size_t ncaps)
@@ -1666,6 +1728,8 @@ int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target,
*
* Runs hardware lane margining on USB4 port and returns the result in
* @results.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target,
u8 index, const struct usb4_port_margining_params *params,
@@ -1710,8 +1774,9 @@ int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target,
* @results: Data word for the operation completion data
*
* Runs software lane margining on USB4 port. Read back the error
- * counters by calling usb4_port_sw_margin_errors(). Returns %0 in
- * success and negative errno otherwise.
+ * counters by calling usb4_port_sw_margin_errors().
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target,
u8 index, const struct usb4_port_margining_params *params,
@@ -1758,7 +1823,8 @@ int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target,
* @errors: Error metadata is copied here.
*
* This reads back the software margining error counters from the port.
- * Returns %0 in success and negative errno otherwise.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_sw_margin_errors(struct tb_port *port, enum usb4_sb_target target,
u8 index, u32 *errors)
@@ -1789,6 +1855,8 @@ static inline int usb4_port_retimer_op(struct tb_port *port, u8 index,
*
* Enables sideband channel transations on SBTX. Can be used when USB4
* link does not go up, for example if there is no device connected.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index)
{
@@ -1816,6 +1884,8 @@ int usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index)
*
* Disables sideband channel transations on SBTX. The reverse of
* usb4_port_retimer_set_inbound_sbtx().
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_retimer_unset_inbound_sbtx(struct tb_port *port, u8 index)
{
@@ -1828,10 +1898,12 @@ int usb4_port_retimer_unset_inbound_sbtx(struct tb_port *port, u8 index)
* @port: USB4 port
* @index: Retimer index
*
- * If the retimer at @index is last one (connected directly to the
- * Type-C port) this function returns %1. If it is not returns %0. If
- * the retimer is not present returns %-ENODEV. Otherwise returns
- * negative errno.
+ * Return:
+ * * %1 - Retimer at @index is the last one (connected directly to the
+ * Type-C port).
+ * * %0 - Retimer at @index is not the last one.
+ * * %-ENODEV - Retimer is not present.
+ * * Negative errno - Other failure occurred.
*/
int usb4_port_retimer_is_last(struct tb_port *port, u8 index)
{
@@ -1853,9 +1925,11 @@ int usb4_port_retimer_is_last(struct tb_port *port, u8 index)
* @port: USB4 port
* @index: Retimer index
*
- * If the retimer at @index is last cable retimer this function returns
- * %1 and %0 if it is on-board retimer. In case a retimer is not present
- * at @index returns %-ENODEV. Otherwise returns negative errno.
+ * Return:
+ * * %1 - Retimer at @index is the last cable retimer.
+ * * %0 - Retimer at @index is on-board retimer.
+ * * %-ENODEV - Retimer is not present.
+ * * Negative errno - Other failure occurred.
*/
int usb4_port_retimer_is_cable(struct tb_port *port, u8 index)
{
@@ -1879,9 +1953,12 @@ int usb4_port_retimer_is_cable(struct tb_port *port, u8 index)
*
* Reads NVM sector size (in bytes) of a retimer at @index. This
* operation can be used to determine whether the retimer supports NVM
- * upgrade for example. Returns sector size in bytes or negative errno
- * in case of error. Specifically returns %-ENODEV if there is no
- * retimer at @index.
+ * upgrade for example.
+ *
+ * Return:
+ * * Sector size in bytes.
+ * * %-ENODEV - If there is no retimer at @index.
+ * * Negative errno - In case of an error.
*/
int usb4_port_retimer_nvm_sector_size(struct tb_port *port, u8 index)
{
@@ -1907,7 +1984,7 @@ int usb4_port_retimer_nvm_sector_size(struct tb_port *port, u8 index)
* Exlicitly sets NVM write offset. Normally when writing to NVM this is
* done automatically by usb4_port_retimer_nvm_write().
*
- * Returns %0 in success and negative errno if there was a failure.
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_retimer_nvm_set_offset(struct tb_port *port, u8 index,
unsigned int address)
@@ -1960,9 +2037,12 @@ static int usb4_port_retimer_nvm_write_next_block(void *data,
* @size: Size in bytes how much to write
*
* Writes @size bytes from @buf to the retimer NVM. Used for NVM
- * upgrade. Returns %0 if the data was written successfully and negative
- * errno in case of failure. Specifically returns %-ENODEV if there is
- * no retimer at @index.
+ * upgrade.
+ *
+ * Return:
+ * * %0 - If the data was written successfully.
+ * * %-ENODEV - If there is no retimer at @index.
+ * * Negative errno - In case of an error.
*/
int usb4_port_retimer_nvm_write(struct tb_port *port, u8 index, unsigned int address,
const void *buf, size_t size)
@@ -1988,6 +2068,8 @@ int usb4_port_retimer_nvm_write(struct tb_port *port, u8 index, unsigned int add
* successful the retimer restarts with the new NVM and may not have the
* index set so one needs to call usb4_port_enumerate_retimers() to
* force index to be assigned.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_retimer_nvm_authenticate(struct tb_port *port, u8 index)
{
@@ -2012,9 +2094,9 @@ int usb4_port_retimer_nvm_authenticate(struct tb_port *port, u8 index)
* This can be called after usb4_port_retimer_nvm_authenticate() and
* usb4_port_enumerate_retimers() to fetch status of the NVM upgrade.
*
- * Returns %0 if the authentication status was successfully read. The
+ * Return: %0 if the authentication status was successfully read. The
* completion metadata (the result) is then stored into @status. If
- * reading the status fails, returns negative errno.
+ * status read fails, returns negative errno.
*/
int usb4_port_retimer_nvm_authenticate_status(struct tb_port *port, u8 index,
u32 *status)
@@ -2082,9 +2164,12 @@ static int usb4_port_retimer_nvm_read_block(void *data, unsigned int dwaddress,
* @buf: Data read from NVM is stored here
* @size: Number of bytes to read
*
- * Reads retimer NVM and copies the contents to @buf. Returns %0 if the
- * read was successful and negative errno in case of failure.
- * Specifically returns %-ENODEV if there is no retimer at @index.
+ * Reads retimer NVM and copies the contents to @buf.
+ *
+ * Return:
+ * * %0 - If the read was successful.
+ * * %-ENODEV - If there is no retimer at @index.
+ * * Negative errno - In case of an error.
*/
int usb4_port_retimer_nvm_read(struct tb_port *port, u8 index,
unsigned int address, void *buf, size_t size)
@@ -2108,8 +2193,8 @@ usb4_usb3_port_max_bandwidth(const struct tb_port *port, unsigned int bw)
* usb4_usb3_port_max_link_rate() - Maximum support USB3 link rate
* @port: USB3 adapter port
*
- * Return maximum supported link rate of a USB3 adapter in Mb/s.
- * Negative errno in case of error.
+ * Return: Maximum supported link rate of a USB3 adapter in Mb/s.
+ * Negative errno in case of an error.
*/
int usb4_usb3_port_max_link_rate(struct tb_port *port)
{
@@ -2227,8 +2312,9 @@ static int usb4_usb3_port_read_allocated_bandwidth(struct tb_port *port,
* @downstream_bw: Allocated downstream bandwidth is stored here
*
* Stores currently allocated USB3 bandwidth into @upstream_bw and
- * @downstream_bw in Mb/s. Returns %0 in case of success and negative
- * errno in failure.
+ * @downstream_bw in Mb/s.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_usb3_port_allocated_bandwidth(struct tb_port *port, int *upstream_bw,
int *downstream_bw)
@@ -2330,8 +2416,7 @@ static int usb4_usb3_port_write_allocated_bandwidth(struct tb_port *port,
* cannot be taken away by CM). The actual new values are returned in
* @upstream_bw and @downstream_bw.
*
- * Returns %0 in case of success and negative errno if there was a
- * failure.
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_usb3_port_allocate_bandwidth(struct tb_port *port, int *upstream_bw,
int *downstream_bw)
@@ -2373,7 +2458,7 @@ err_request:
* Releases USB3 allocated bandwidth down to what is actually consumed.
* The new bandwidth is returned in @upstream_bw and @downstream_bw.
*
- * Returns 0% in success and negative errno in case of failure.
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_usb3_port_release_bandwidth(struct tb_port *port, int *upstream_bw,
int *downstream_bw)
@@ -2425,9 +2510,12 @@ static bool is_usb4_dpin(const struct tb_port *port)
* @port: DP IN adapter
* @cm_id: CM ID to assign
*
- * Sets CM ID for the @port. Returns %0 on success and negative errno
- * otherwise. Speficially returns %-EOPNOTSUPP if the @port does not
- * support this.
+ * Sets CM ID for the @port.
+ *
+ * Return:
+ * * %0 - On success.
+ * * %-EOPNOTSUPP - If the @port does not support this.
+ * * Negative errno - Another error occurred.
*/
int usb4_dp_port_set_cm_id(struct tb_port *port, int cm_id)
{
@@ -2454,8 +2542,10 @@ int usb4_dp_port_set_cm_id(struct tb_port *port, int cm_id)
* supported
* @port: DP IN adapter to check
*
- * Can be called to any DP IN adapter. Returns true if the adapter
- * supports USB4 bandwidth allocation mode, false otherwise.
+ * Can be called to any DP IN adapter.
+ *
+ * Return: %true if the adapter supports USB4 bandwidth allocation mode,
+ * %false otherwise.
*/
bool usb4_dp_port_bandwidth_mode_supported(struct tb_port *port)
{
@@ -2478,8 +2568,10 @@ bool usb4_dp_port_bandwidth_mode_supported(struct tb_port *port)
* enabled
* @port: DP IN adapter to check
*
- * Can be called to any DP IN adapter. Returns true if the bandwidth
- * allocation mode has been enabled, false otherwise.
+ * Can be called to any DP IN adapter.
+ *
+ * Return: %true if the bandwidth allocation mode has been enabled,
+ * %false otherwise.
*/
bool usb4_dp_port_bandwidth_mode_enabled(struct tb_port *port)
{
@@ -2504,9 +2596,12 @@ bool usb4_dp_port_bandwidth_mode_enabled(struct tb_port *port)
* @supported: Does the CM support bandwidth allocation mode
*
* Can be called to any DP IN adapter. Sets or clears the CM support bit
- * of the DP IN adapter. Returns %0 in success and negative errno
- * otherwise. Specifically returns %-OPNOTSUPP if the passed in adapter
- * does not support this.
+ * of the DP IN adapter.
+ *
+ * * Return:
+ * * %0 - On success.
+ * * %-EOPNOTSUPP - If the passed IN adapter does not support this.
+ * * Negative errno - Another error occurred.
*/
int usb4_dp_port_set_cm_bandwidth_mode_supported(struct tb_port *port,
bool supported)
@@ -2536,8 +2631,12 @@ int usb4_dp_port_set_cm_bandwidth_mode_supported(struct tb_port *port,
* @port: DP IN adapter
*
* Reads bandwidth allocation Group ID from the DP IN adapter and
- * returns it. If the adapter does not support setting Group_ID
- * %-EOPNOTSUPP is returned.
+ * returns it.
+ *
+ * Return:
+ * * Group ID assigned to adapter @port.
+ * * %-EOPNOTSUPP - If adapter does not support setting GROUP_ID.
+ * * Negative errno - Another error occurred.
*/
int usb4_dp_port_group_id(struct tb_port *port)
{
@@ -2561,9 +2660,11 @@ int usb4_dp_port_group_id(struct tb_port *port)
* @group_id: Group ID for the adapter
*
* Sets bandwidth allocation mode Group ID for the DP IN adapter.
- * Returns %0 in case of success and negative errno otherwise.
- * Specifically returns %-EOPNOTSUPP if the adapter does not support
- * this.
+ *
+ * Return:
+ * * %0 - On success.
+ * * %-EOPNOTSUPP - If the adapter does not support this.
+ * * Negative errno - Another error occurred.
*/
int usb4_dp_port_set_group_id(struct tb_port *port, int group_id)
{
@@ -2591,9 +2692,12 @@ int usb4_dp_port_set_group_id(struct tb_port *port, int group_id)
* @rate: Non-reduced rate in Mb/s is placed here
* @lanes: Non-reduced lanes are placed here
*
- * Reads the non-reduced rate and lanes from the DP IN adapter. Returns
- * %0 in success and negative errno otherwise. Specifically returns
- * %-EOPNOTSUPP if the adapter does not support this.
+ * Reads the non-reduced rate and lanes from the DP IN adapter.
+ *
+ * Return:
+ * * %0 - On success.
+ * * %-EOPNOTSUPP - If the adapter does not support this.
+ * * Negative errno - Another error occurred.
*/
int usb4_dp_port_nrd(struct tb_port *port, int *rate, int *lanes)
{
@@ -2646,10 +2750,13 @@ int usb4_dp_port_nrd(struct tb_port *port, int *rate, int *lanes)
* @rate: Non-reduced rate in Mb/s
* @lanes: Non-reduced lanes
*
- * Before the capabilities reduction this function can be used to set
- * the non-reduced values for the DP IN adapter. Returns %0 in success
- * and negative errno otherwise. If the adapter does not support this
- * %-EOPNOTSUPP is returned.
+ * Before the capabilities reduction, this function can be used to set
+ * the non-reduced values for the DP IN adapter.
+ *
+ * Return:
+ * * %0 - On success.
+ * * %-EOPNOTSUPP - If the adapter does not support this.
+ * * Negative errno - Another error occurred.
*/
int usb4_dp_port_set_nrd(struct tb_port *port, int rate, int lanes)
{
@@ -2708,9 +2815,13 @@ int usb4_dp_port_set_nrd(struct tb_port *port, int rate, int lanes)
* usb4_dp_port_granularity() - Return granularity for the bandwidth values
* @port: DP IN adapter
*
- * Reads the programmed granularity from @port. If the DP IN adapter does
- * not support bandwidth allocation mode returns %-EOPNOTSUPP and negative
- * errno in other error cases.
+ * Reads the programmed granularity from @port.
+ *
+ * Return:
+ * * Granularity value of a @port.
+ * * %-EOPNOTSUPP - If the DP IN adapter does not support bandwidth
+ * allocation mode.
+ * * Negative errno - Another error occurred.
*/
int usb4_dp_port_granularity(struct tb_port *port)
{
@@ -2746,8 +2857,12 @@ int usb4_dp_port_granularity(struct tb_port *port)
* @granularity: Granularity in Mb/s. Supported values: 1000, 500 and 250.
*
* Sets the granularity used with the estimated, allocated and requested
- * bandwidth. Returns %0 in success and negative errno otherwise. If the
- * adapter does not support this %-EOPNOTSUPP is returned.
+ * bandwidth.
+ *
+ * Return:
+ * * %0 - On success.
+ * * %-EOPNOTSUPP - If the adapter does not support this.
+ * * Negative errno - Another error occurred.
*/
int usb4_dp_port_set_granularity(struct tb_port *port, int granularity)
{
@@ -2788,10 +2903,13 @@ int usb4_dp_port_set_granularity(struct tb_port *port, int granularity)
* @bw: Estimated bandwidth in Mb/s.
*
* Sets the estimated bandwidth to @bw. Set the granularity by calling
- * usb4_dp_port_set_granularity() before calling this. The @bw is round
- * down to the closest granularity multiplier. Returns %0 in success
- * and negative errno otherwise. Specifically returns %-EOPNOTSUPP if
- * the adapter does not support this.
+ * usb4_dp_port_set_granularity() before calling this. The @bw is rounded
+ * down to the closest granularity multiplier.
+ *
+ * Return:
+ * * %0 - On success.
+ * * %-EOPNOTSUPP - If the adapter does not support this.
+ * * Negative errno - Another error occurred.
*/
int usb4_dp_port_set_estimated_bandwidth(struct tb_port *port, int bw)
{
@@ -2822,9 +2940,10 @@ int usb4_dp_port_set_estimated_bandwidth(struct tb_port *port, int bw)
* usb4_dp_port_allocated_bandwidth() - Return allocated bandwidth
* @port: DP IN adapter
*
- * Reads and returns allocated bandwidth for @port in Mb/s (taking into
- * account the programmed granularity). Returns negative errno in case
- * of error.
+ * Reads the allocated bandwidth for @port in Mb/s (taking into account
+ * the programmed granularity).
+ *
+ * Return: Allocated bandwidth in Mb/s or negative errno in case of an error.
*/
int usb4_dp_port_allocated_bandwidth(struct tb_port *port)
{
@@ -2919,8 +3038,9 @@ static int usb4_dp_port_wait_and_clear_cm_ack(struct tb_port *port,
* @bw: New allocated bandwidth in Mb/s
*
* Communicates the new allocated bandwidth with the DPCD (graphics
- * driver). Takes into account the programmed granularity. Returns %0 in
- * success and negative errno in case of error.
+ * driver). Takes into account the programmed granularity.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_dp_port_allocate_bandwidth(struct tb_port *port, int bw)
{
@@ -2960,10 +3080,15 @@ int usb4_dp_port_allocate_bandwidth(struct tb_port *port, int bw)
* @port: DP IN adapter
*
* Reads the DPCD (graphics driver) requested bandwidth and returns it
- * in Mb/s. Takes the programmed granularity into account. In case of
- * error returns negative errno. Specifically returns %-EOPNOTSUPP if
- * the adapter does not support bandwidth allocation mode, and %ENODATA
- * if there is no active bandwidth request from the graphics driver.
+ * in Mb/s. Takes the programmed granularity into account.
+ *
+ * Return:
+ * * Requested bandwidth in Mb/s - On success.
+ * * %-EOPNOTSUPP - If the adapter does not support bandwidth allocation
+ * mode.
+ * * %ENODATA - If there is no active bandwidth request from the graphics
+ * driver.
+ * * Negative errno - On failure.
*/
int usb4_dp_port_requested_bandwidth(struct tb_port *port)
{
@@ -2995,8 +3120,9 @@ int usb4_dp_port_requested_bandwidth(struct tb_port *port)
* @enable: Enable/disable extended encapsulation
*
* Enables or disables extended encapsulation used in PCIe tunneling. Caller
- * needs to make sure both adapters support this before enabling. Returns %0 on
- * success and negative errno otherwise.
+ * needs to make sure both adapters support this before enabling.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_pci_port_set_ext_encapsulation(struct tb_port *port, bool enable)
{
diff --git a/drivers/thunderbolt/usb4_port.c b/drivers/thunderbolt/usb4_port.c
index 852a45fcd19d..b5e06237261b 100644
--- a/drivers/thunderbolt/usb4_port.c
+++ b/drivers/thunderbolt/usb4_port.c
@@ -296,8 +296,9 @@ const struct device_type usb4_port_device_type = {
* usb4_port_device_add() - Add USB4 port device
* @port: Lane 0 adapter port to add the USB4 port
*
- * Creates and registers a USB4 port device for @port. Returns the new
- * USB4 port device pointer or ERR_PTR() in case of error.
+ * Creates and registers a USB4 port device for @port.
+ *
+ * Return: Pointer to &struct usb4_port or ERR_PTR() in case of an error.
*/
struct usb4_port *usb4_port_device_add(struct tb_port *port)
{
@@ -356,6 +357,8 @@ void usb4_port_device_remove(struct usb4_port *usb4)
* @usb4: USB4 port device
*
* Used to resume USB4 port device after sleep state.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int usb4_port_device_resume(struct usb4_port *usb4)
{
diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
index b0630e6d9472..9d220ba544ec 100644
--- a/drivers/thunderbolt/xdomain.c
+++ b/drivers/thunderbolt/xdomain.c
@@ -160,7 +160,7 @@ static int __tb_xdomain_response(struct tb_ctl *ctl, const void *response,
* This can be used to send a XDomain response message to the other
* domain. No response for the message is expected.
*
- * Return: %0 in case of success and negative errno in case of failure
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_xdomain_response(struct tb_xdomain *xd, const void *response,
size_t size, enum tb_cfg_pkg_type type)
@@ -212,7 +212,7 @@ static int __tb_xdomain_request(struct tb_ctl *ctl, const void *request,
* the other domain. The function waits until the response is received
* or when timeout triggers. Whichever comes first.
*
- * Return: %0 in case of success and negative errno in case of failure
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_xdomain_request(struct tb_xdomain *xd, const void *request,
size_t request_size, enum tb_cfg_pkg_type request_type,
@@ -613,6 +613,8 @@ static int tb_xdp_link_state_change_response(struct tb_ctl *ctl, u64 route,
* messages. After this function is called the service driver needs to
* be able to handle calls to callback whenever a package with the
* registered protocol is received.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_register_protocol_handler(struct tb_protocol_handler *handler)
{
@@ -877,6 +879,8 @@ tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header *hdr,
* @drv: Driver to register
*
* Registers new service driver from @drv to the bus.
+ *
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_register_service_driver(struct tb_service_driver *drv)
{
@@ -1955,6 +1959,8 @@ static void tb_xdomain_link_exit(struct tb_xdomain *xd)
*
* Allocates new XDomain structure and returns pointer to that. The
* object must be released by calling tb_xdomain_put().
+ *
+ * Return: Pointer to &struct tb_xdomain, %NULL in case of failure.
*/
struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
u64 route, const uuid_t *local_uuid,
@@ -2091,7 +2097,7 @@ void tb_xdomain_remove(struct tb_xdomain *xd)
* to enable bonding by first enabling the port and waiting for the CL0
* state.
*
- * Return: %0 in case of success and negative errno in case of error.
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_xdomain_lane_bonding_enable(struct tb_xdomain *xd)
{
@@ -2171,10 +2177,14 @@ EXPORT_SYMBOL_GPL(tb_xdomain_lane_bonding_disable);
* @xd: XDomain connection
* @hopid: Preferred HopID or %-1 for next available
*
- * Returns allocated HopID or negative errno. Specifically returns
- * %-ENOSPC if there are no more available HopIDs. Returned HopID is
- * guaranteed to be within range supported by the input lane adapter.
+ * Returned HopID is guaranteed to be within range supported by the input
+ * lane adapter.
* Call tb_xdomain_release_in_hopid() to release the allocated HopID.
+ *
+ * Return:
+ * * Allocated HopID - On success.
+ * * %-ENOSPC - If there are no more available HopIDs.
+ * * Negative errno - Another error occurred.
*/
int tb_xdomain_alloc_in_hopid(struct tb_xdomain *xd, int hopid)
{
@@ -2193,10 +2203,14 @@ EXPORT_SYMBOL_GPL(tb_xdomain_alloc_in_hopid);
* @xd: XDomain connection
* @hopid: Preferred HopID or %-1 for next available
*
- * Returns allocated HopID or negative errno. Specifically returns
- * %-ENOSPC if there are no more available HopIDs. Returned HopID is
- * guaranteed to be within range supported by the output lane adapter.
- * Call tb_xdomain_release_in_hopid() to release the allocated HopID.
+ * Returned HopID is guaranteed to be within range supported by the
+ * output lane adapter.
+ * Call tb_xdomain_release_out_hopid() to release the allocated HopID.
+ *
+ * Return:
+ * * Allocated HopID - On success.
+ * * %-ENOSPC - If there are no more available HopIDs.
+ * * Negative errno - Another error occurred.
*/
int tb_xdomain_alloc_out_hopid(struct tb_xdomain *xd, int hopid)
{
@@ -2245,7 +2259,7 @@ EXPORT_SYMBOL_GPL(tb_xdomain_release_out_hopid);
* path. If a transmit or receive path is not needed, pass %-1 for those
* parameters.
*
- * Return: %0 in case of success and negative errno in case of error
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_xdomain_enable_paths(struct tb_xdomain *xd, int transmit_path,
int transmit_ring, int receive_path,
@@ -2270,7 +2284,7 @@ EXPORT_SYMBOL_GPL(tb_xdomain_enable_paths);
* as path/ring parameter means don't care. Normally the callers should
* pass the same values here as they do when paths are enabled.
*
- * Return: %0 in case of success and negative errno in case of error
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_xdomain_disable_paths(struct tb_xdomain *xd, int transmit_path,
int transmit_ring, int receive_path,
@@ -2335,6 +2349,8 @@ static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw,
* to the bus (handshake is still in progress).
*
* The caller needs to hold @tb->lock.
+ *
+ * Return: Pointer to &struct tb_xdomain or %NULL if not found.
*/
struct tb_xdomain *tb_xdomain_find_by_uuid(struct tb *tb, const uuid_t *uuid)
{
@@ -2364,6 +2380,8 @@ EXPORT_SYMBOL_GPL(tb_xdomain_find_by_uuid);
* to the bus (handshake is still in progress).
*
* The caller needs to hold @tb->lock.
+ *
+ * Return: Pointer to &struct tb_xdomain or %NULL if not found.
*/
struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link,
u8 depth)
@@ -2393,6 +2411,8 @@ struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link,
* to the bus (handshake is still in progress).
*
* The caller needs to hold @tb->lock.
+ *
+ * Return: Pointer to &struct tb_xdomain or %NULL if not found.
*/
struct tb_xdomain *tb_xdomain_find_by_route(struct tb *tb, u64 route)
{
@@ -2491,7 +2511,7 @@ static bool remove_directory(const char *key, const struct tb_property_dir *dir)
* notified so they can re-read properties of this host if they are
* interested.
*
- * Return: %0 on success and negative errno on failure
+ * Return: %0 on success, negative errno otherwise.
*/
int tb_register_property_dir(const char *key, struct tb_property_dir *dir)
{
@@ -2562,10 +2582,9 @@ int tb_xdomain_init(void)
* Rest of the properties are filled dynamically based on these
* when the P2P connection is made.
*/
- tb_property_add_immediate(xdomain_property_dir, "vendorid",
- PCI_VENDOR_ID_INTEL);
- tb_property_add_text(xdomain_property_dir, "vendorid", "Intel Corp.");
- tb_property_add_immediate(xdomain_property_dir, "deviceid", 0x1);
+ tb_property_add_immediate(xdomain_property_dir, "vendorid", 0x1d6b);
+ tb_property_add_text(xdomain_property_dir, "vendorid", "Linux");
+ tb_property_add_immediate(xdomain_property_dir, "deviceid", 0x0004);
tb_property_add_immediate(xdomain_property_dir, "devicerv", 0x80000100);
xdomain_property_block_gen = get_random_u32();
diff --git a/drivers/tty/hvc/hvc_console.c b/drivers/tty/hvc/hvc_console.c
index 13c663a154c4..6b58f340f210 100644
--- a/drivers/tty/hvc/hvc_console.c
+++ b/drivers/tty/hvc/hvc_console.c
@@ -184,7 +184,7 @@ static void hvc_console_print(struct console *co, const char *b,
hvc_console_flush(cons_ops[index],
vtermnos[index]);
}
- } else if (r > 0) {
+ } else {
i -= r;
if (i > 0)
memmove(c, c+r, i);
diff --git a/drivers/tty/mxser.c b/drivers/tty/mxser.c
index 2fc13cc02cc5..94677fec685e 100644
--- a/drivers/tty/mxser.c
+++ b/drivers/tty/mxser.c
@@ -442,11 +442,8 @@ static void __mxser_start_tx(struct mxser_port *info)
static void mxser_start_tx(struct mxser_port *info)
{
- unsigned long flags;
-
- spin_lock_irqsave(&info->slock, flags);
+ guard(spinlock_irqsave)(&info->slock);
__mxser_start_tx(info);
- spin_unlock_irqrestore(&info->slock, flags);
}
static void __mxser_stop_tx(struct mxser_port *info)
@@ -465,17 +462,15 @@ static bool mxser_carrier_raised(struct tty_port *port)
static void mxser_dtr_rts(struct tty_port *port, bool active)
{
struct mxser_port *mp = container_of(port, struct mxser_port, port);
- unsigned long flags;
u8 mcr;
- spin_lock_irqsave(&mp->slock, flags);
+ guard(spinlock_irqsave)(&mp->slock);
mcr = inb(mp->ioaddr + UART_MCR);
if (active)
mcr |= UART_MCR_DTR | UART_MCR_RTS;
else
mcr &= ~(UART_MCR_DTR | UART_MCR_RTS);
outb(mcr, mp->ioaddr + UART_MCR);
- spin_unlock_irqrestore(&mp->slock, flags);
}
static int mxser_set_baud(struct tty_struct *tty, speed_t newspd)
@@ -828,32 +823,28 @@ static void mxser_stop_rx(struct mxser_port *info)
static void mxser_shutdown_port(struct tty_port *port)
{
struct mxser_port *info = container_of(port, struct mxser_port, port);
- unsigned long flags;
- spin_lock_irqsave(&info->slock, flags);
+ scoped_guard(spinlock_irqsave, &info->slock) {
+ mxser_stop_rx(info);
- mxser_stop_rx(info);
-
- /*
- * clear delta_msr_wait queue to avoid mem leaks: we may free the irq
- * here so the queue might never be waken up
- */
- wake_up_interruptible(&info->port.delta_msr_wait);
-
- info->IER = 0;
- outb(0x00, info->ioaddr + UART_IER);
-
- /* clear Rx/Tx FIFO's */
- mxser_disable_and_clear_FIFO(info);
+ /*
+ * clear delta_msr_wait queue to avoid mem leaks: we may free the irq
+ * here so the queue might never be waken up
+ */
+ wake_up_interruptible(&info->port.delta_msr_wait);
- /* read data port to reset things */
- (void) inb(info->ioaddr + UART_RX);
+ info->IER = 0;
+ outb(0x00, info->ioaddr + UART_IER);
+ /* clear Rx/Tx FIFO's */
+ mxser_disable_and_clear_FIFO(info);
- if (info->board->must_hwid)
- mxser_must_no_sw_flow_control(info->ioaddr);
+ /* read data port to reset things */
+ (void)inb(info->ioaddr + UART_RX);
- spin_unlock_irqrestore(&info->slock, flags);
+ if (info->board->must_hwid)
+ mxser_must_no_sw_flow_control(info->ioaddr);
+ }
/* make sure ISR is not running while we free the buffer */
synchronize_irq(info->board->irq);
@@ -880,15 +871,13 @@ static int mxser_open(struct tty_struct *tty, struct file *filp)
static void mxser_flush_buffer(struct tty_struct *tty)
{
struct mxser_port *info = tty->driver_data;
- unsigned long flags;
-
- spin_lock_irqsave(&info->slock, flags);
- kfifo_reset(&info->port.xmit_fifo);
- outb(info->FCR | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT,
- info->ioaddr + UART_FCR);
+ scoped_guard(spinlock_irqsave, &info->slock) {
+ kfifo_reset(&info->port.xmit_fifo);
- spin_unlock_irqrestore(&info->slock, flags);
+ outb(info->FCR | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT,
+ info->ioaddr + UART_FCR);
+ }
tty_wakeup(tty);
}
@@ -901,14 +890,13 @@ static void mxser_close(struct tty_struct *tty, struct file *filp)
static ssize_t mxser_write(struct tty_struct *tty, const u8 *buf, size_t count)
{
struct mxser_port *info = tty->driver_data;
- unsigned long flags;
size_t written;
bool is_empty;
- spin_lock_irqsave(&info->slock, flags);
- written = kfifo_in(&info->port.xmit_fifo, buf, count);
- is_empty = kfifo_is_empty(&info->port.xmit_fifo);
- spin_unlock_irqrestore(&info->slock, flags);
+ scoped_guard(spinlock_irqsave, &info->slock) {
+ written = kfifo_in(&info->port.xmit_fifo, buf, count);
+ is_empty = kfifo_is_empty(&info->port.xmit_fifo);
+ }
if (!is_empty && !tty->flow.stopped)
if (!tty->hw_stopped || mxser_16550A_or_MUST(info))
@@ -920,14 +908,9 @@ static ssize_t mxser_write(struct tty_struct *tty, const u8 *buf, size_t count)
static int mxser_put_char(struct tty_struct *tty, u8 ch)
{
struct mxser_port *info = tty->driver_data;
- unsigned long flags;
- int ret;
-
- spin_lock_irqsave(&info->slock, flags);
- ret = kfifo_put(&info->port.xmit_fifo, ch);
- spin_unlock_irqrestore(&info->slock, flags);
- return ret;
+ guard(spinlock_irqsave)(&info->slock);
+ return kfifo_put(&info->port.xmit_fifo, ch);
}
@@ -968,7 +951,7 @@ static int mxser_get_serial_info(struct tty_struct *tty,
struct tty_port *port = &info->port;
unsigned int closing_wait, close_delay;
- mutex_lock(&port->mutex);
+ guard(mutex)(&port->mutex);
close_delay = jiffies_to_msecs(info->port.close_delay) / 10;
closing_wait = info->port.closing_wait;
@@ -984,7 +967,7 @@ static int mxser_get_serial_info(struct tty_struct *tty,
ss->close_delay = close_delay;
ss->closing_wait = closing_wait;
ss->custom_divisor = MXSER_CUSTOM_DIVISOR;
- mutex_unlock(&port->mutex);
+
return 0;
}
@@ -994,20 +977,15 @@ static int mxser_set_serial_info(struct tty_struct *tty,
struct mxser_port *info = tty->driver_data;
struct tty_port *port = &info->port;
speed_t baud;
- unsigned long sl_flags;
unsigned int old_speed, close_delay, closing_wait;
- int retval = 0;
if (tty_io_error(tty))
return -EIO;
- mutex_lock(&port->mutex);
+ guard(mutex)(&port->mutex);
- if (ss->irq != info->board->irq ||
- ss->port != info->ioaddr) {
- mutex_unlock(&port->mutex);
+ if (ss->irq != info->board->irq || ss->port != info->ioaddr)
return -EINVAL;
- }
old_speed = port->flags & ASYNC_SPD_MASK;
@@ -1020,10 +998,9 @@ static int mxser_set_serial_info(struct tty_struct *tty,
if ((ss->baud_base != MXSER_BAUD_BASE) ||
(close_delay != port->close_delay) ||
(closing_wait != port->closing_wait) ||
- ((ss->flags & ~ASYNC_USR_MASK) != (port->flags & ~ASYNC_USR_MASK))) {
- mutex_unlock(&port->mutex);
+ ((ss->flags & ~ASYNC_USR_MASK) != (port->flags & ~ASYNC_USR_MASK)))
return -EPERM;
- }
+
port->flags = (port->flags & ~ASYNC_USR_MASK) |
(ss->flags & ASYNC_USR_MASK);
} else {
@@ -1039,10 +1016,9 @@ static int mxser_set_serial_info(struct tty_struct *tty,
(ss->baud_base != MXSER_BAUD_BASE ||
ss->custom_divisor !=
MXSER_CUSTOM_DIVISOR)) {
- if (ss->custom_divisor == 0) {
- mutex_unlock(&port->mutex);
+ if (ss->custom_divisor == 0)
return -EINVAL;
- }
+
baud = ss->baud_base / ss->custom_divisor;
tty_encode_baud_rate(tty, baud, baud);
}
@@ -1054,16 +1030,17 @@ static int mxser_set_serial_info(struct tty_struct *tty,
if (tty_port_initialized(port)) {
if (old_speed != (port->flags & ASYNC_SPD_MASK)) {
- spin_lock_irqsave(&info->slock, sl_flags);
+ guard(spinlock_irqsave)(&info->slock);
mxser_change_speed(tty, NULL);
- spin_unlock_irqrestore(&info->slock, sl_flags);
}
- } else {
- retval = mxser_activate(port, tty);
- if (retval == 0)
- tty_port_set_initialized(port, true);
+
+ return 0;
}
- mutex_unlock(&port->mutex);
+
+ int retval = mxser_activate(port, tty);
+ if (retval == 0)
+ tty_port_set_initialized(port, true);
+
return retval;
}
@@ -1080,13 +1057,11 @@ static int mxser_set_serial_info(struct tty_struct *tty,
static int mxser_get_lsr_info(struct mxser_port *info,
unsigned int __user *value)
{
- unsigned char status;
unsigned int result;
- unsigned long flags;
+ u8 status;
- spin_lock_irqsave(&info->slock, flags);
- status = inb(info->ioaddr + UART_LSR);
- spin_unlock_irqrestore(&info->slock, flags);
+ scoped_guard(spinlock_irqsave, &info->slock)
+ status = inb(info->ioaddr + UART_LSR);
result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0);
return put_user(result, value);
}
@@ -1095,16 +1070,15 @@ static int mxser_tiocmget(struct tty_struct *tty)
{
struct mxser_port *info = tty->driver_data;
unsigned char control;
- unsigned long flags;
u8 msr;
if (tty_io_error(tty))
return -EIO;
- spin_lock_irqsave(&info->slock, flags);
- control = info->MCR;
- msr = mxser_check_modem_status(tty, info);
- spin_unlock_irqrestore(&info->slock, flags);
+ scoped_guard(spinlock_irqsave, &info->slock) {
+ control = info->MCR;
+ msr = mxser_check_modem_status(tty, info);
+ }
return ((control & UART_MCR_RTS) ? TIOCM_RTS : 0) |
((control & UART_MCR_DTR) ? TIOCM_DTR : 0) |
@@ -1118,12 +1092,11 @@ static int mxser_tiocmset(struct tty_struct *tty,
unsigned int set, unsigned int clear)
{
struct mxser_port *info = tty->driver_data;
- unsigned long flags;
if (tty_io_error(tty))
return -EIO;
- spin_lock_irqsave(&info->slock, flags);
+ guard(spinlock_irqsave)(&info->slock);
if (set & TIOCM_RTS)
info->MCR |= UART_MCR_RTS;
@@ -1136,7 +1109,7 @@ static int mxser_tiocmset(struct tty_struct *tty,
info->MCR &= ~UART_MCR_DTR;
outb(info->MCR, info->ioaddr + UART_MCR);
- spin_unlock_irqrestore(&info->slock, flags);
+
return 0;
}
@@ -1144,12 +1117,11 @@ static int mxser_cflags_changed(struct mxser_port *info, unsigned long arg,
struct async_icount *cprev)
{
struct async_icount cnow;
- unsigned long flags;
int ret;
- spin_lock_irqsave(&info->slock, flags);
- cnow = info->icount; /* atomic copy */
- spin_unlock_irqrestore(&info->slock, flags);
+ /* atomic copy */
+ scoped_guard(spinlock_irqsave, &info->slock)
+ cnow = info->icount;
ret = ((arg & TIOCM_RNG) && (cnow.rng != cprev->rng)) ||
((arg & TIOCM_DSR) && (cnow.dsr != cprev->dsr)) ||
@@ -1179,19 +1151,17 @@ static int mxser_ioctl_op_mode(struct mxser_port *port, int index, bool set,
if (opmode & ~OP_MODE_MASK)
return -EINVAL;
- spin_lock_irq(&port->slock);
+ guard(spinlock_irq)(&port->slock);
val = inb(port->opmode_ioaddr);
val &= ~(OP_MODE_MASK << shiftbit);
val |= (opmode << shiftbit);
outb(val, port->opmode_ioaddr);
- spin_unlock_irq(&port->slock);
return 0;
}
- spin_lock_irq(&port->slock);
- opmode = inb(port->opmode_ioaddr) >> shiftbit;
- spin_unlock_irq(&port->slock);
+ scoped_guard(spinlock_irq, &port->slock)
+ opmode = inb(port->opmode_ioaddr) >> shiftbit;
return put_user(opmode & OP_MODE_MASK, u_opmode);
}
@@ -1201,7 +1171,6 @@ static int mxser_ioctl(struct tty_struct *tty,
{
struct mxser_port *info = tty->driver_data;
struct async_icount cnow;
- unsigned long flags;
void __user *argp = (void __user *)arg;
if (cmd == MOXA_SET_OP_MODE || cmd == MOXA_GET_OP_MODE)
@@ -1221,9 +1190,9 @@ static int mxser_ioctl(struct tty_struct *tty,
* Caller should use TIOCGICOUNT to see which one it was
*/
case TIOCMIWAIT:
- spin_lock_irqsave(&info->slock, flags);
- cnow = info->icount; /* note the counters on entry */
- spin_unlock_irqrestore(&info->slock, flags);
+ /* note the counters on entry */
+ scoped_guard(spinlock_irqsave, &info->slock)
+ cnow = info->icount;
return wait_event_interruptible(info->port.delta_msr_wait,
mxser_cflags_changed(info, arg, &cnow));
@@ -1246,11 +1215,9 @@ static int mxser_get_icount(struct tty_struct *tty,
{
struct mxser_port *info = tty->driver_data;
struct async_icount cnow;
- unsigned long flags;
- spin_lock_irqsave(&info->slock, flags);
- cnow = info->icount;
- spin_unlock_irqrestore(&info->slock, flags);
+ scoped_guard(spinlock_irqsave, &info->slock)
+ cnow = info->icount;
icount->frame = cnow.frame;
icount->brk = cnow.brk;
@@ -1328,34 +1295,28 @@ static void mxser_unthrottle(struct tty_struct *tty)
static void mxser_stop(struct tty_struct *tty)
{
struct mxser_port *info = tty->driver_data;
- unsigned long flags;
- spin_lock_irqsave(&info->slock, flags);
+ guard(spinlock_irqsave)(&info->slock);
if (info->IER & UART_IER_THRI)
__mxser_stop_tx(info);
- spin_unlock_irqrestore(&info->slock, flags);
}
static void mxser_start(struct tty_struct *tty)
{
struct mxser_port *info = tty->driver_data;
- unsigned long flags;
- spin_lock_irqsave(&info->slock, flags);
+ guard(spinlock_irqsave)(&info->slock);
if (!kfifo_is_empty(&info->port.xmit_fifo))
__mxser_start_tx(info);
- spin_unlock_irqrestore(&info->slock, flags);
}
static void mxser_set_termios(struct tty_struct *tty,
const struct ktermios *old_termios)
{
struct mxser_port *info = tty->driver_data;
- unsigned long flags;
- spin_lock_irqsave(&info->slock, flags);
- mxser_change_speed(tty, old_termios);
- spin_unlock_irqrestore(&info->slock, flags);
+ scoped_guard(spinlock_irqsave, &info->slock)
+ mxser_change_speed(tty, old_termios);
if ((old_termios->c_cflag & CRTSCTS) && !C_CRTSCTS(tty)) {
tty->hw_stopped = false;
@@ -1367,9 +1328,8 @@ static void mxser_set_termios(struct tty_struct *tty,
tty->flow.stopped = 0;
if (info->board->must_hwid) {
- spin_lock_irqsave(&info->slock, flags);
+ guard(spinlock_irqsave)(&info->slock);
mxser_must_set_rx_sw_flow_control(info->ioaddr, false);
- spin_unlock_irqrestore(&info->slock, flags);
}
mxser_start(tty);
@@ -1378,14 +1338,8 @@ static void mxser_set_termios(struct tty_struct *tty,
static bool mxser_tx_empty(struct mxser_port *info)
{
- unsigned long flags;
- u8 lsr;
-
- spin_lock_irqsave(&info->slock, flags);
- lsr = inb(info->ioaddr + UART_LSR);
- spin_unlock_irqrestore(&info->slock, flags);
-
- return !(lsr & UART_LSR_TEMT);
+ guard(spinlock_irqsave)(&info->slock);
+ return !(inb(info->ioaddr + UART_LSR) & UART_LSR_TEMT);
}
/*
@@ -1459,17 +1413,15 @@ static void mxser_hangup(struct tty_struct *tty)
static int mxser_rs_break(struct tty_struct *tty, int break_state)
{
struct mxser_port *info = tty->driver_data;
- unsigned long flags;
u8 lcr;
- spin_lock_irqsave(&info->slock, flags);
+ guard(spinlock_irqsave)(&info->slock);
lcr = inb(info->ioaddr + UART_LCR);
if (break_state == -1)
lcr |= UART_LCR_SBC;
else
lcr &= ~UART_LCR_SBC;
outb(lcr, info->ioaddr + UART_LCR);
- spin_unlock_irqrestore(&info->slock, flags);
return 0;
}
@@ -1600,54 +1552,50 @@ static void mxser_transmit_chars(struct tty_struct *tty, struct mxser_port *port
static bool mxser_port_isr(struct mxser_port *port)
{
- struct tty_struct *tty;
u8 iir, status;
- bool error = false;
iir = inb(port->ioaddr + UART_IIR);
if (iir & UART_IIR_NO_INT)
return true;
iir &= MOXA_MUST_IIR_MASK;
- tty = tty_port_tty_get(&port->port);
- if (!tty) {
- status = inb(port->ioaddr + UART_LSR);
- outb(port->FCR | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT,
- port->ioaddr + UART_FCR);
- inb(port->ioaddr + UART_MSR);
- error = true;
- goto put_tty;
- }
+ scoped_guard(tty_port_tty, &port->port) {
+ struct tty_struct *tty = scoped_tty();
- status = inb(port->ioaddr + UART_LSR);
+ status = inb(port->ioaddr + UART_LSR);
- if (port->board->must_hwid) {
- if (iir == MOXA_MUST_IIR_GDA ||
- iir == MOXA_MUST_IIR_RDA ||
- iir == MOXA_MUST_IIR_RTO ||
- iir == MOXA_MUST_IIR_LSR)
- status = mxser_receive_chars(tty, port, status);
- } else {
- status &= port->read_status_mask;
- if (status & UART_LSR_DR)
- status = mxser_receive_chars(tty, port, status);
- }
+ if (port->board->must_hwid) {
+ if (iir == MOXA_MUST_IIR_GDA ||
+ iir == MOXA_MUST_IIR_RDA ||
+ iir == MOXA_MUST_IIR_RTO ||
+ iir == MOXA_MUST_IIR_LSR)
+ status = mxser_receive_chars(tty, port, status);
+ } else {
+ status &= port->read_status_mask;
+ if (status & UART_LSR_DR)
+ status = mxser_receive_chars(tty, port, status);
+ }
- mxser_check_modem_status(tty, port);
+ mxser_check_modem_status(tty, port);
- if (port->board->must_hwid) {
- if (iir == 0x02 && (status & UART_LSR_THRE))
- mxser_transmit_chars(tty, port);
- } else {
- if (status & UART_LSR_THRE)
- mxser_transmit_chars(tty, port);
+ if (port->board->must_hwid) {
+ if (iir == 0x02 && (status & UART_LSR_THRE))
+ mxser_transmit_chars(tty, port);
+ } else {
+ if (status & UART_LSR_THRE)
+ mxser_transmit_chars(tty, port);
+ }
+
+ return false;
}
-put_tty:
- tty_kref_put(tty);
+ status = inb(port->ioaddr + UART_LSR);
+ outb(port->FCR | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT,
+ port->ioaddr + UART_FCR);
+ inb(port->ioaddr + UART_MSR);
- return error;
+ return true;
}
/*
@@ -1676,12 +1624,11 @@ static irqreturn_t mxser_interrupt(int irq, void *dev_id)
port = &brd->ports[i];
int_cnt = 0;
- spin_lock(&port->slock);
+ guard(spinlock)(&port->slock);
do {
if (mxser_port_isr(port))
break;
} while (int_cnt++ < MXSER_ISR_PASS_LIMIT);
- spin_unlock(&port->slock);
}
}
diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
index 7fc535452c0b..553d8c70352b 100644
--- a/drivers/tty/n_gsm.c
+++ b/drivers/tty/n_gsm.c
@@ -461,6 +461,7 @@ static int gsm_send_packet(struct gsm_mux *gsm, struct gsm_msg *msg);
static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr);
static void gsmld_write_trigger(struct gsm_mux *gsm);
static void gsmld_write_task(struct work_struct *work);
+static int gsm_modem_send_initial_msc(struct gsm_dlci *dlci);
/**
* gsm_fcs_add - update FCS
@@ -2174,7 +2175,7 @@ static void gsm_dlci_open(struct gsm_dlci *dlci)
pr_debug("DLCI %d goes open.\n", dlci->addr);
/* Send current modem state */
if (dlci->addr) {
- gsm_modem_update(dlci, 0);
+ gsm_modem_send_initial_msc(dlci);
} else {
/* Start keep-alive control */
gsm->ka_num = 0;
@@ -4162,6 +4163,28 @@ static int gsm_modem_upd_via_msc(struct gsm_dlci *dlci, u8 brk)
}
/**
+ * gsm_modem_send_initial_msc - Send initial modem status message
+ *
+ * @dlci channel
+ *
+ * Send an initial MSC message after DLCI open to set the initial
+ * modem status lines. This is only done for basic mode.
+ * Does not wait for a response as we cannot block the input queue
+ * processing.
+ */
+static int gsm_modem_send_initial_msc(struct gsm_dlci *dlci)
+{
+ u8 modembits[2];
+
+ if (dlci->adaption != 1 || dlci->gsm->encoding != GSM_BASIC_OPT)
+ return 0;
+
+ modembits[0] = (dlci->addr << 2) | 2 | EA; /* DLCI, Valid, EA */
+ modembits[1] = (gsm_encode_modem(dlci) << 1) | EA;
+ return gsm_control_command(dlci->gsm, CMD_MSC, (const u8 *)&modembits, 2);
+}
+
+/**
* gsm_modem_update - send modem status line state
* @dlci: channel
* @brk: break signal
diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c
index d16c207a1a9b..b33e708cb245 100644
--- a/drivers/tty/serdev/core.c
+++ b/drivers/tty/serdev/core.c
@@ -399,15 +399,12 @@ static int serdev_drv_probe(struct device *dev)
const struct serdev_device_driver *sdrv = to_serdev_device_driver(dev->driver);
int ret;
- ret = dev_pm_domain_attach(dev, PD_FLAG_ATTACH_POWER_ON);
+ ret = dev_pm_domain_attach(dev, PD_FLAG_ATTACH_POWER_ON |
+ PD_FLAG_DETACH_POWER_OFF);
if (ret)
return ret;
- ret = sdrv->probe(to_serdev_device(dev));
- if (ret)
- dev_pm_domain_detach(dev, true);
-
- return ret;
+ return sdrv->probe(to_serdev_device(dev));
}
static void serdev_drv_remove(struct device *dev)
@@ -415,8 +412,6 @@ static void serdev_drv_remove(struct device *dev)
const struct serdev_device_driver *sdrv = to_serdev_device_driver(dev->driver);
if (sdrv->remove)
sdrv->remove(to_serdev_device(dev));
-
- dev_pm_domain_detach(dev, true);
}
static const struct bus_type serdev_bus_type = {
diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h
index cfe6ba286b45..58e64c4e1e3a 100644
--- a/drivers/tty/serial/8250/8250.h
+++ b/drivers/tty/serial/8250/8250.h
@@ -186,6 +186,11 @@ static unsigned int __maybe_unused serial_icr_read(struct uart_8250_port *up,
void serial8250_clear_and_reinit_fifos(struct uart_8250_port *p);
+void serial8250_rpm_get(struct uart_8250_port *p);
+void serial8250_rpm_put(struct uart_8250_port *p);
+DEFINE_GUARD(serial8250_rpm, struct uart_8250_port *,
+ serial8250_rpm_get(_T), serial8250_rpm_put(_T));
+
static inline u32 serial_dl_read(struct uart_8250_port *up)
{
return up->dl_read(up);
diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c
index feb920c5b2e8..bfa421ab3253 100644
--- a/drivers/tty/serial/8250/8250_core.c
+++ b/drivers/tty/serial/8250/8250_core.c
@@ -72,7 +72,7 @@ static irqreturn_t serial8250_interrupt(int irq, void *dev_id)
struct list_head *l, *end = NULL;
int pass_counter = 0, handled = 0;
- spin_lock(&i->lock);
+ guard(spinlock)(&i->lock);
l = i->head;
do {
@@ -91,8 +91,6 @@ static irqreturn_t serial8250_interrupt(int irq, void *dev_id)
break;
} while (l != end);
- spin_unlock(&i->lock);
-
return IRQ_RETVAL(handled);
}
@@ -132,22 +130,19 @@ static struct irq_info *serial_get_or_create_irq_info(const struct uart_8250_por
{
struct irq_info *i;
- mutex_lock(&hash_mutex);
+ guard(mutex)(&hash_mutex);
hash_for_each_possible(irq_lists, i, node, up->port.irq)
if (i->irq == up->port.irq)
- goto unlock;
+ return i;
i = kzalloc(sizeof(*i), GFP_KERNEL);
- if (i == NULL) {
- i = ERR_PTR(-ENOMEM);
- goto unlock;
- }
+ if (i == NULL)
+ return ERR_PTR(-ENOMEM);
+
spin_lock_init(&i->lock);
i->irq = up->port.irq;
hash_add(irq_lists, &i->node, i->irq);
-unlock:
- mutex_unlock(&hash_mutex);
return i;
}
@@ -161,23 +156,21 @@ static int serial_link_irq_chain(struct uart_8250_port *up)
if (IS_ERR(i))
return PTR_ERR(i);
- spin_lock_irq(&i->lock);
+ scoped_guard(spinlock_irq, &i->lock) {
+ if (i->head) {
+ list_add(&up->list, i->head);
- if (i->head) {
- list_add(&up->list, i->head);
- spin_unlock_irq(&i->lock);
+ return 0;
+ }
- ret = 0;
- } else {
INIT_LIST_HEAD(&up->list);
i->head = &up->list;
- spin_unlock_irq(&i->lock);
- ret = request_irq(up->port.irq, serial8250_interrupt,
- up->port.irqflags, up->port.name, i);
- if (ret < 0)
- serial_do_unlink(i, up);
}
+ ret = request_irq(up->port.irq, serial8250_interrupt, up->port.irqflags, up->port.name, i);
+ if (ret < 0)
+ serial_do_unlink(i, up);
+
return ret;
}
@@ -185,20 +178,22 @@ static void serial_unlink_irq_chain(struct uart_8250_port *up)
{
struct irq_info *i;
- mutex_lock(&hash_mutex);
+ guard(mutex)(&hash_mutex);
hash_for_each_possible(irq_lists, i, node, up->port.irq)
- if (i->irq == up->port.irq)
- break;
+ if (i->irq == up->port.irq) {
+ if (WARN_ON(i->head == NULL))
+ return;
- BUG_ON(i == NULL);
- BUG_ON(i->head == NULL);
+ if (list_empty(i->head))
+ free_irq(up->port.irq, i);
- if (list_empty(i->head))
- free_irq(up->port.irq, i);
+ serial_do_unlink(i, up);
+
+ return;
+ }
- serial_do_unlink(i, up);
- mutex_unlock(&hash_mutex);
+ WARN_ON(1);
}
/*
@@ -307,7 +302,7 @@ static void univ8250_release_irq(struct uart_8250_port *up)
serial_unlink_irq_chain(up);
}
-const struct uart_ops *univ8250_port_base_ops = NULL;
+const struct uart_ops *univ8250_port_base_ops;
struct uart_ops univ8250_port_ops;
static const struct uart_8250_ops univ8250_driver_ops = {
@@ -670,16 +665,12 @@ static struct uart_8250_port *serial8250_find_match_or_unused(const struct uart_
static void serial_8250_overrun_backoff_work(struct work_struct *work)
{
- struct uart_8250_port *up =
- container_of(to_delayed_work(work), struct uart_8250_port,
- overrun_backoff);
- struct uart_port *port = &up->port;
- unsigned long flags;
+ struct uart_8250_port *up = container_of(to_delayed_work(work), struct uart_8250_port,
+ overrun_backoff);
- uart_port_lock_irqsave(port, &flags);
+ guard(uart_port_lock_irqsave)(&up->port);
up->ier |= UART_IER_RLSI | UART_IER_RDI;
serial_out(up, UART_IER, up->ier);
- uart_port_unlock_irqrestore(port, flags);
}
/**
@@ -698,12 +689,12 @@ static void serial_8250_overrun_backoff_work(struct work_struct *work)
int serial8250_register_8250_port(const struct uart_8250_port *up)
{
struct uart_8250_port *uart;
- int ret = -ENOSPC;
+ int ret;
if (up->port.uartclk == 0)
return -EINVAL;
- mutex_lock(&serial_mutex);
+ guard(mutex)(&serial_mutex);
uart = serial8250_find_match_or_unused(&up->port);
if (!uart) {
@@ -713,15 +704,13 @@ int serial8250_register_8250_port(const struct uart_8250_port *up)
*/
uart = serial8250_setup_port(nr_uarts);
if (!uart)
- goto unlock;
+ return -ENOSPC;
nr_uarts++;
}
/* Check if it is CIR already. We check this below again, see there why. */
- if (uart->port.type == PORT_8250_CIR) {
- ret = -ENODEV;
- goto unlock;
- }
+ if (uart->port.type == PORT_8250_CIR)
+ return -ENODEV;
if (uart->port.dev)
uart_remove_one_port(&serial8250_reg, &uart->port);
@@ -855,14 +844,10 @@ int serial8250_register_8250_port(const struct uart_8250_port *up)
uart->overrun_backoff_time_ms = 0;
}
-unlock:
- mutex_unlock(&serial_mutex);
-
return ret;
err:
uart->port.dev = NULL;
- mutex_unlock(&serial_mutex);
return ret;
}
EXPORT_SYMBOL(serial8250_register_8250_port);
@@ -878,14 +863,11 @@ void serial8250_unregister_port(int line)
{
struct uart_8250_port *uart = &serial8250_ports[line];
- mutex_lock(&serial_mutex);
+ guard(mutex)(&serial_mutex);
if (uart->em485) {
- unsigned long flags;
-
- uart_port_lock_irqsave(&uart->port, &flags);
+ guard(uart_port_lock_irqsave)(&uart->port);
serial8250_em485_destroy(uart);
- uart_port_unlock_irqrestore(&uart->port, flags);
}
uart_remove_one_port(&serial8250_reg, &uart->port);
@@ -901,7 +883,6 @@ void serial8250_unregister_port(int line)
} else {
uart->port.dev = NULL;
}
- mutex_unlock(&serial_mutex);
}
EXPORT_SYMBOL(serial8250_unregister_port);
diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c
index 6707f55bdbe7..9e49ef48b851 100644
--- a/drivers/tty/serial/8250/8250_omap.c
+++ b/drivers/tty/serial/8250/8250_omap.c
@@ -27,6 +27,8 @@
#include <linux/pm_wakeirq.h>
#include <linux/dma-mapping.h>
#include <linux/sys_soc.h>
+#include <linux/reboot.h>
+#include <linux/pinctrl/consumer.h>
#include "8250.h"
@@ -145,6 +147,9 @@ struct omap8250_priv {
spinlock_t rx_dma_lock;
bool rx_dma_broken;
bool throttled;
+
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *pinctrl_wakeup;
};
struct omap8250_dma_params {
@@ -369,18 +374,12 @@ static void omap8250_restore_regs(struct uart_8250_port *up)
serial8250_em485_stop_tx(up, true);
}
-/*
- * OMAP can use "CLK / (16 or 13) / div" for baud rate. And then we have have
- * some differences in how we want to handle flow control.
- */
-static void omap_8250_set_termios(struct uart_port *port,
- struct ktermios *termios,
- const struct ktermios *old)
+static void omap_8250_set_termios_atomic(struct uart_port *port, struct ktermios *termios,
+ const struct ktermios *old, unsigned int baud)
{
struct uart_8250_port *up = up_to_u8250p(port);
struct omap8250_priv *priv = port->private_data;
- unsigned char cval = 0;
- unsigned int baud;
+ u8 cval;
cval = UART_LCR_WLEN(tty_get_char_size(termios->c_cflag));
@@ -393,20 +392,14 @@ static void omap_8250_set_termios(struct uart_port *port,
if (termios->c_cflag & CMSPAR)
cval |= UART_LCR_SPAR;
- /*
- * Ask the core to calculate the divisor for us.
- */
- baud = uart_get_baud_rate(port, termios, old,
- port->uartclk / 16 / UART_DIV_MAX,
- port->uartclk / 13);
omap_8250_get_divisor(port, baud, priv);
/*
* Ok, we're now changing the port state. Do it with
* interrupts disabled.
*/
- pm_runtime_get_sync(port->dev);
- uart_port_lock_irq(port);
+ guard(serial8250_rpm)(up);
+ guard(uart_port_lock_irq)(port);
/*
* Update the per-port timeout.
@@ -514,10 +507,27 @@ static void omap_8250_set_termios(struct uart_port *port,
}
}
omap8250_restore_regs(up);
+}
- uart_port_unlock_irq(&up->port);
- pm_runtime_mark_last_busy(port->dev);
- pm_runtime_put_autosuspend(port->dev);
+/*
+ * OMAP can use "CLK / (16 or 13) / div" for baud rate. And then we have have
+ * some differences in how we want to handle flow control.
+ */
+static void omap_8250_set_termios(struct uart_port *port,
+ struct ktermios *termios,
+ const struct ktermios *old)
+{
+ struct omap8250_priv *priv = port->private_data;
+ unsigned int baud;
+
+ /*
+ * Ask the core to calculate the divisor for us.
+ */
+ baud = uart_get_baud_rate(port, termios, old,
+ port->uartclk / 16 / UART_DIV_MAX,
+ port->uartclk / 13);
+
+ omap_8250_set_termios_atomic(port, termios, old, baud);
/* calculate wakeup latency constraint */
priv->calc_latency = USEC_PER_SEC * 64 * 8 / baud;
@@ -537,10 +547,9 @@ static void omap_8250_pm(struct uart_port *port, unsigned int state,
struct uart_8250_port *up = up_to_u8250p(port);
u8 efr;
- pm_runtime_get_sync(port->dev);
-
+ guard(serial8250_rpm)(up);
/* Synchronize UART_IER access against the console. */
- uart_port_lock_irq(port);
+ guard(uart_port_lock_irq)(port);
serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
efr = serial_in(up, UART_EFR);
@@ -551,11 +560,6 @@ static void omap_8250_pm(struct uart_port *port, unsigned int state,
serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
serial_out(up, UART_EFR, efr);
serial_out(up, UART_LCR, 0);
-
- uart_port_unlock_irq(port);
-
- pm_runtime_mark_last_busy(port->dev);
- pm_runtime_put_autosuspend(port->dev);
}
static void omap_serial_fill_features_erratas(struct uart_8250_port *up,
@@ -727,7 +731,11 @@ static int omap_8250_startup(struct uart_port *port)
return ret;
}
- pm_runtime_get_sync(port->dev);
+#ifdef CONFIG_PM
+ up->capabilities |= UART_CAP_RPM;
+#endif
+
+ guard(serial8250_rpm)(up);
serial_out(up, UART_FCR, UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
@@ -750,14 +758,10 @@ static int omap_8250_startup(struct uart_port *port)
}
/* Synchronize UART_IER access against the console. */
- uart_port_lock_irq(port);
- up->ier = UART_IER_RLSI | UART_IER_RDI;
- serial_out(up, UART_IER, up->ier);
- uart_port_unlock_irq(port);
-
-#ifdef CONFIG_PM
- up->capabilities |= UART_CAP_RPM;
-#endif
+ scoped_guard(uart_port_lock_irq, port) {
+ up->ier = UART_IER_RLSI | UART_IER_RDI;
+ serial_out(up, UART_IER, up->ier);
+ }
/* Enable module level wake up */
priv->wer = OMAP_UART_WER_MOD_WKUP;
@@ -766,15 +770,12 @@ static int omap_8250_startup(struct uart_port *port)
serial_out(up, UART_OMAP_WER, priv->wer);
if (up->dma && !(priv->habit & UART_HAS_EFR2)) {
- uart_port_lock_irq(port);
+ guard(uart_port_lock_irq)(port);
up->dma->rx_dma(up);
- uart_port_unlock_irq(port);
}
enable_irq(port->irq);
- pm_runtime_mark_last_busy(port->dev);
- pm_runtime_put_autosuspend(port->dev);
return 0;
}
@@ -783,7 +784,7 @@ static void omap_8250_shutdown(struct uart_port *port)
struct uart_8250_port *up = up_to_u8250p(port);
struct omap8250_priv *priv = port->private_data;
- pm_runtime_get_sync(port->dev);
+ guard(serial8250_rpm)(up);
flush_work(&priv->qos_work);
if (up->dma)
@@ -794,10 +795,11 @@ static void omap_8250_shutdown(struct uart_port *port)
serial_out(up, UART_OMAP_EFR2, 0x0);
/* Synchronize UART_IER access against the console. */
- uart_port_lock_irq(port);
- up->ier = 0;
- serial_out(up, UART_IER, 0);
- uart_port_unlock_irq(port);
+ scoped_guard(uart_port_lock_irq, port) {
+ up->ier = 0;
+ serial_out(up, UART_IER, 0);
+ }
+
disable_irq_nosync(port->irq);
dev_pm_clear_wake_irq(port->dev);
@@ -810,46 +812,33 @@ static void omap_8250_shutdown(struct uart_port *port)
if (up->lcr & UART_LCR_SBC)
serial_out(up, UART_LCR, up->lcr & ~UART_LCR_SBC);
serial_out(up, UART_FCR, UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
-
- pm_runtime_mark_last_busy(port->dev);
- pm_runtime_put_autosuspend(port->dev);
}
static void omap_8250_throttle(struct uart_port *port)
{
struct omap8250_priv *priv = port->private_data;
- unsigned long flags;
- pm_runtime_get_sync(port->dev);
+ guard(serial8250_rpm)(up_to_u8250p(port));
+ guard(uart_port_lock_irqsave)(port);
- uart_port_lock_irqsave(port, &flags);
port->ops->stop_rx(port);
priv->throttled = true;
- uart_port_unlock_irqrestore(port, flags);
-
- pm_runtime_mark_last_busy(port->dev);
- pm_runtime_put_autosuspend(port->dev);
}
static void omap_8250_unthrottle(struct uart_port *port)
{
struct omap8250_priv *priv = port->private_data;
struct uart_8250_port *up = up_to_u8250p(port);
- unsigned long flags;
-
- pm_runtime_get_sync(port->dev);
+ guard(serial8250_rpm)(up);
/* Synchronize UART_IER access against the console. */
- uart_port_lock_irqsave(port, &flags);
+ guard(uart_port_lock_irqsave)(port);
+
priv->throttled = false;
if (up->dma)
up->dma->rx_dma(up);
up->ier |= UART_IER_RLSI | UART_IER_RDI;
serial_out(up, UART_IER, up->ier);
- uart_port_unlock_irqrestore(port, flags);
-
- pm_runtime_mark_last_busy(port->dev);
- pm_runtime_put_autosuspend(port->dev);
}
static int omap8250_rs485_config(struct uart_port *port,
@@ -987,30 +976,26 @@ static void __dma_rx_complete(void *param)
struct omap8250_priv *priv = p->port.private_data;
struct uart_8250_dma *dma = p->dma;
struct dma_tx_state state;
- unsigned long flags;
/* Synchronize UART_IER access against the console. */
- uart_port_lock_irqsave(&p->port, &flags);
+ guard(uart_port_lock_irqsave)(&p->port);
/*
* If the tx status is not DMA_COMPLETE, then this is a delayed
* completion callback. A previous RX timeout flush would have
* already pushed the data, so exit.
*/
- if (dmaengine_tx_status(dma->rxchan, dma->rx_cookie, &state) !=
- DMA_COMPLETE) {
- uart_port_unlock_irqrestore(&p->port, flags);
+ if (dmaengine_tx_status(dma->rxchan, dma->rx_cookie, &state) != DMA_COMPLETE)
return;
- }
+
__dma_rx_do_complete(p);
- if (!priv->throttled) {
- p->ier |= UART_IER_RLSI | UART_IER_RDI;
- serial_out(p, UART_IER, p->ier);
- if (!(priv->habit & UART_HAS_EFR2))
- omap_8250_rx_dma(p);
- }
+ if (priv->throttled)
+ return;
- uart_port_unlock_irqrestore(&p->port, flags);
+ p->ier |= UART_IER_RLSI | UART_IER_RDI;
+ serial_out(p, UART_IER, p->ier);
+ if (!(priv->habit & UART_HAS_EFR2))
+ omap_8250_rx_dma(p);
}
static void omap_8250_rx_dma_flush(struct uart_8250_port *p)
@@ -1108,14 +1093,13 @@ static void omap_8250_dma_tx_complete(void *param)
struct uart_8250_port *p = param;
struct uart_8250_dma *dma = p->dma;
struct tty_port *tport = &p->port.state->port;
- unsigned long flags;
bool en_thri = false;
struct omap8250_priv *priv = p->port.private_data;
dma_sync_single_for_cpu(dma->txchan->device->dev, dma->tx_addr,
UART_XMIT_SIZE, DMA_TO_DEVICE);
- uart_port_lock_irqsave(&p->port, &flags);
+ guard(uart_port_lock_irqsave)(&p->port);
dma->tx_running = 0;
@@ -1143,8 +1127,6 @@ static void omap_8250_dma_tx_complete(void *param)
dma->tx_err = 1;
serial8250_set_THRI(p);
}
-
- uart_port_unlock_irqrestore(&p->port, flags);
}
static int omap_8250_tx_dma(struct uart_8250_port *p)
@@ -1372,6 +1354,18 @@ static int omap8250_no_handle_irq(struct uart_port *port)
return 0;
}
+static int omap8250_select_wakeup_pinctrl(struct device *dev,
+ struct omap8250_priv *priv)
+{
+ if (IS_ERR_OR_NULL(priv->pinctrl_wakeup))
+ return 0;
+
+ if (!device_may_wakeup(dev))
+ return 0;
+
+ return pinctrl_select_state(priv->pinctrl, priv->pinctrl_wakeup);
+}
+
static struct omap8250_dma_params am654_dma = {
.rx_size = SZ_2K,
.rx_trigger = 1,
@@ -1596,6 +1590,11 @@ static int omap8250_probe(struct platform_device *pdev)
priv->line = ret;
pm_runtime_mark_last_busy(&pdev->dev);
pm_runtime_put_autosuspend(&pdev->dev);
+
+ priv->pinctrl = devm_pinctrl_get(&pdev->dev);
+ if (!IS_ERR_OR_NULL(priv->pinctrl))
+ priv->pinctrl_wakeup = pinctrl_lookup_state(priv->pinctrl, "wakeup");
+
return 0;
err:
pm_runtime_dont_use_autosuspend(&pdev->dev);
@@ -1653,6 +1652,13 @@ static int omap8250_suspend(struct device *dev)
struct uart_8250_port *up = serial8250_get_port(priv->line);
int err = 0;
+ err = omap8250_select_wakeup_pinctrl(dev, priv);
+ if (err) {
+ dev_err(dev, "Failed to select wakeup pinctrl, aborting suspend %pe\n",
+ ERR_PTR(err));
+ return err;
+ }
+
serial8250_suspend_port(priv->line);
err = pm_runtime_resume_and_get(dev);
@@ -1674,6 +1680,13 @@ static int omap8250_resume(struct device *dev)
struct uart_8250_port *up = serial8250_get_port(priv->line);
int err;
+ err = pinctrl_select_default_state(dev);
+ if (err) {
+ dev_err(dev, "Failed to select default pinctrl state on resume: %pe\n",
+ ERR_PTR(err));
+ return err;
+ }
+
if (uart_console(&up->port) && console_suspend_enabled) {
err = pm_runtime_force_resume(dev);
if (err)
@@ -1795,15 +1808,13 @@ static int omap8250_runtime_resume(struct device *dev)
up = serial8250_get_port(priv->line);
if (up && omap8250_lost_context(up)) {
- uart_port_lock_irq(&up->port);
+ guard(uart_port_lock_irq)(&up->port);
omap8250_restore_regs(up);
- uart_port_unlock_irq(&up->port);
}
if (up && up->dma && up->dma->rxchan && !(priv->habit & UART_HAS_EFR2)) {
- uart_port_lock_irq(&up->port);
+ guard(uart_port_lock_irq)(&up->port);
omap_8250_rx_dma(up);
- uart_port_unlock_irq(&up->port);
}
atomic_set(&priv->active, 1);
diff --git a/drivers/tty/serial/8250/8250_platform.c b/drivers/tty/serial/8250/8250_platform.c
index c0343bfb8064..b27981340e76 100644
--- a/drivers/tty/serial/8250/8250_platform.c
+++ b/drivers/tty/serial/8250/8250_platform.c
@@ -10,6 +10,7 @@
*/
#include <linux/acpi.h>
#include <linux/array_size.h>
+#include <linux/cleanup.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
@@ -110,41 +111,44 @@ void __init serial8250_isa_init_ports(void)
static int serial8250_probe_acpi(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct uart_8250_port uart = { };
struct resource *regs;
int ret, line;
+ struct uart_8250_port *uart __free(kfree) = kzalloc(sizeof(*uart), GFP_KERNEL);
+ if (!uart)
+ return -ENOMEM;
+
regs = platform_get_mem_or_io(pdev, 0);
if (!regs)
return dev_err_probe(dev, -EINVAL, "no registers defined\n");
switch (resource_type(regs)) {
case IORESOURCE_IO:
- uart.port.iobase = regs->start;
+ uart->port.iobase = regs->start;
break;
case IORESOURCE_MEM:
- uart.port.mapbase = regs->start;
- uart.port.mapsize = resource_size(regs);
- uart.port.flags = UPF_IOREMAP;
+ uart->port.mapbase = regs->start;
+ uart->port.mapsize = resource_size(regs);
+ uart->port.flags = UPF_IOREMAP;
break;
default:
return -EINVAL;
}
/* default clock frequency */
- uart.port.uartclk = 1843200;
- uart.port.type = PORT_16550A;
- uart.port.dev = &pdev->dev;
- uart.port.flags |= UPF_SKIP_TEST | UPF_BOOT_AUTOCONF;
+ uart->port.uartclk = 1843200;
+ uart->port.type = PORT_16550A;
+ uart->port.dev = &pdev->dev;
+ uart->port.flags |= UPF_SKIP_TEST | UPF_BOOT_AUTOCONF;
- ret = uart_read_and_validate_port_properties(&uart.port);
+ ret = uart_read_and_validate_port_properties(&uart->port);
/* no interrupt -> fall back to polling */
if (ret == -ENXIO)
ret = 0;
if (ret)
return ret;
- line = serial8250_register_8250_port(&uart);
+ line = serial8250_register_8250_port(uart);
if (line < 0)
return line;
@@ -153,43 +157,44 @@ static int serial8250_probe_acpi(struct platform_device *pdev)
static int serial8250_probe_platform(struct platform_device *dev, struct plat_serial8250_port *p)
{
- struct uart_8250_port uart;
int ret, i, irqflag = 0;
- memset(&uart, 0, sizeof(uart));
+ struct uart_8250_port *uart __free(kfree) = kzalloc(sizeof(*uart), GFP_KERNEL);
+ if (!uart)
+ return -ENOMEM;
if (share_irqs)
irqflag = IRQF_SHARED;
for (i = 0; p && p->flags != 0; p++, i++) {
- uart.port.iobase = p->iobase;
- uart.port.membase = p->membase;
- uart.port.irq = p->irq;
- uart.port.irqflags = p->irqflags;
- uart.port.uartclk = p->uartclk;
- uart.port.regshift = p->regshift;
- uart.port.iotype = p->iotype;
- uart.port.flags = p->flags;
- uart.port.mapbase = p->mapbase;
- uart.port.mapsize = p->mapsize;
- uart.port.hub6 = p->hub6;
- uart.port.has_sysrq = p->has_sysrq;
- uart.port.private_data = p->private_data;
- uart.port.type = p->type;
- uart.bugs = p->bugs;
- uart.port.serial_in = p->serial_in;
- uart.port.serial_out = p->serial_out;
- uart.dl_read = p->dl_read;
- uart.dl_write = p->dl_write;
- uart.port.handle_irq = p->handle_irq;
- uart.port.handle_break = p->handle_break;
- uart.port.set_termios = p->set_termios;
- uart.port.set_ldisc = p->set_ldisc;
- uart.port.get_mctrl = p->get_mctrl;
- uart.port.pm = p->pm;
- uart.port.dev = &dev->dev;
- uart.port.irqflags |= irqflag;
- ret = serial8250_register_8250_port(&uart);
+ uart->port.iobase = p->iobase;
+ uart->port.membase = p->membase;
+ uart->port.irq = p->irq;
+ uart->port.irqflags = p->irqflags;
+ uart->port.uartclk = p->uartclk;
+ uart->port.regshift = p->regshift;
+ uart->port.iotype = p->iotype;
+ uart->port.flags = p->flags;
+ uart->port.mapbase = p->mapbase;
+ uart->port.mapsize = p->mapsize;
+ uart->port.hub6 = p->hub6;
+ uart->port.has_sysrq = p->has_sysrq;
+ uart->port.private_data = p->private_data;
+ uart->port.type = p->type;
+ uart->bugs = p->bugs;
+ uart->port.serial_in = p->serial_in;
+ uart->port.serial_out = p->serial_out;
+ uart->dl_read = p->dl_read;
+ uart->dl_write = p->dl_write;
+ uart->port.handle_irq = p->handle_irq;
+ uart->port.handle_break = p->handle_break;
+ uart->port.set_termios = p->set_termios;
+ uart->port.set_ldisc = p->set_ldisc;
+ uart->port.get_mctrl = p->get_mctrl;
+ uart->port.pm = p->pm;
+ uart->port.dev = &dev->dev;
+ uart->port.irqflags |= irqflag;
+ ret = serial8250_register_8250_port(uart);
if (ret < 0) {
dev_err(&dev->dev, "unable to register port at index %d "
"(IO%lx MEM%llx IRQ%d): %d\n", i,
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 2da9db960d09..719faf92aa8a 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -508,20 +508,22 @@ void serial8250_clear_and_reinit_fifos(struct uart_8250_port *p)
}
EXPORT_SYMBOL_GPL(serial8250_clear_and_reinit_fifos);
-static void serial8250_rpm_get(struct uart_8250_port *p)
+void serial8250_rpm_get(struct uart_8250_port *p)
{
if (!(p->capabilities & UART_CAP_RPM))
return;
pm_runtime_get_sync(p->port.dev);
}
+EXPORT_SYMBOL_GPL(serial8250_rpm_get);
-static void serial8250_rpm_put(struct uart_8250_port *p)
+void serial8250_rpm_put(struct uart_8250_port *p)
{
if (!(p->capabilities & UART_CAP_RPM))
return;
pm_runtime_mark_last_busy(p->port.dev);
pm_runtime_put_autosuspend(p->port.dev);
}
+EXPORT_SYMBOL_GPL(serial8250_rpm_put);
/**
* serial8250_em485_init() - put uart_8250_port into rs485 emulating
@@ -672,28 +674,27 @@ static void serial8250_set_sleep(struct uart_8250_port *p, int sleep)
{
unsigned char lcr = 0, efr = 0;
- serial8250_rpm_get(p);
-
- if (p->capabilities & UART_CAP_SLEEP) {
- /* Synchronize UART_IER access against the console. */
- uart_port_lock_irq(&p->port);
- if (p->capabilities & UART_CAP_EFR) {
- lcr = serial_in(p, UART_LCR);
- efr = serial_in(p, UART_EFR);
- serial_out(p, UART_LCR, UART_LCR_CONF_MODE_B);
- serial_out(p, UART_EFR, UART_EFR_ECB);
- serial_out(p, UART_LCR, 0);
- }
- serial_out(p, UART_IER, sleep ? UART_IERX_SLEEP : 0);
- if (p->capabilities & UART_CAP_EFR) {
- serial_out(p, UART_LCR, UART_LCR_CONF_MODE_B);
- serial_out(p, UART_EFR, efr);
- serial_out(p, UART_LCR, lcr);
- }
- uart_port_unlock_irq(&p->port);
- }
+ guard(serial8250_rpm)(p);
+
+ if (!(p->capabilities & UART_CAP_SLEEP))
+ return;
+
+ /* Synchronize UART_IER access against the console. */
+ guard(uart_port_lock_irq)(&p->port);
- serial8250_rpm_put(p);
+ if (p->capabilities & UART_CAP_EFR) {
+ lcr = serial_in(p, UART_LCR);
+ efr = serial_in(p, UART_EFR);
+ serial_out(p, UART_LCR, UART_LCR_CONF_MODE_B);
+ serial_out(p, UART_EFR, UART_EFR_ECB);
+ serial_out(p, UART_LCR, 0);
+ }
+ serial_out(p, UART_IER, sleep ? UART_IERX_SLEEP : 0);
+ if (p->capabilities & UART_CAP_EFR) {
+ serial_out(p, UART_LCR, UART_LCR_CONF_MODE_B);
+ serial_out(p, UART_EFR, efr);
+ serial_out(p, UART_LCR, lcr);
+ }
}
/* Clear the interrupt registers. */
@@ -1229,9 +1230,8 @@ static void autoconfig_irq(struct uart_8250_port *up)
probe_irq_off(probe_irq_on());
save_mcr = serial8250_in_MCR(up);
/* Synchronize UART_IER access against the console. */
- uart_port_lock_irq(port);
- save_ier = serial_in(up, UART_IER);
- uart_port_unlock_irq(port);
+ scoped_guard(uart_port_lock_irq, port)
+ save_ier = serial_in(up, UART_IER);
serial8250_out_MCR(up, UART_MCR_OUT1 | UART_MCR_OUT2);
irqs = probe_irq_on();
@@ -1244,9 +1244,8 @@ static void autoconfig_irq(struct uart_8250_port *up)
UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2);
}
/* Synchronize UART_IER access against the console. */
- uart_port_lock_irq(port);
- serial_out(up, UART_IER, UART_IER_ALL_INTR);
- uart_port_unlock_irq(port);
+ scoped_guard(uart_port_lock_irq, port)
+ serial_out(up, UART_IER, UART_IER_ALL_INTR);
serial8250_clear_interrupts(port);
serial_out(up, UART_TX, 0xFF);
udelay(20);
@@ -1254,9 +1253,8 @@ static void autoconfig_irq(struct uart_8250_port *up)
serial8250_out_MCR(up, save_mcr);
/* Synchronize UART_IER access against the console. */
- uart_port_lock_irq(port);
- serial_out(up, UART_IER, save_ier);
- uart_port_unlock_irq(port);
+ scoped_guard(uart_port_lock_irq, port)
+ serial_out(up, UART_IER, save_ier);
if (port->flags & UPF_FOURPORT)
outb_p(save_ICP, ICP);
@@ -1271,12 +1269,10 @@ static void serial8250_stop_rx(struct uart_port *port)
/* Port locked to synchronize UART_IER access against the console. */
lockdep_assert_held_once(&port->lock);
- serial8250_rpm_get(up);
+ guard(serial8250_rpm)(up);
up->ier &= ~(UART_IER_RLSI | UART_IER_RDI);
serial_port_out(port, UART_IER, up->ier);
-
- serial8250_rpm_put(up);
}
/**
@@ -1320,17 +1316,15 @@ static enum hrtimer_restart serial8250_em485_handle_stop_tx(struct hrtimer *t)
struct uart_8250_em485 *em485 = container_of(t, struct uart_8250_em485,
stop_tx_timer);
struct uart_8250_port *p = em485->port;
- unsigned long flags;
- serial8250_rpm_get(p);
- uart_port_lock_irqsave(&p->port, &flags);
+ guard(serial8250_rpm)(p);
+ guard(uart_port_lock_irqsave)(&p->port);
+
if (em485->active_timer == &em485->stop_tx_timer) {
p->rs485_stop_tx(p, true);
em485->active_timer = NULL;
em485->tx_stopped = true;
}
- uart_port_unlock_irqrestore(&p->port, flags);
- serial8250_rpm_put(p);
return HRTIMER_NORESTART;
}
@@ -1405,7 +1399,7 @@ static void serial8250_stop_tx(struct uart_port *port)
{
struct uart_8250_port *up = up_to_u8250p(port);
- serial8250_rpm_get(up);
+ guard(serial8250_rpm)(up);
__stop_tx(up);
/*
@@ -1415,7 +1409,6 @@ static void serial8250_stop_tx(struct uart_port *port)
up->acr |= UART_ACR_TXDIS;
serial_icr_write(up, UART_ACR, up->acr);
}
- serial8250_rpm_put(up);
}
static inline void __start_tx(struct uart_port *port)
@@ -1510,14 +1503,13 @@ static enum hrtimer_restart serial8250_em485_handle_start_tx(struct hrtimer *t)
struct uart_8250_em485 *em485 = container_of(t, struct uart_8250_em485,
start_tx_timer);
struct uart_8250_port *p = em485->port;
- unsigned long flags;
- uart_port_lock_irqsave(&p->port, &flags);
+ guard(uart_port_lock_irqsave)(&p->port);
+
if (em485->active_timer == &em485->start_tx_timer) {
__start_tx(&p->port);
em485->active_timer = NULL;
}
- uart_port_unlock_irqrestore(&p->port, flags);
return HRTIMER_NORESTART;
}
@@ -1585,9 +1577,8 @@ static void serial8250_enable_ms(struct uart_port *port)
up->ier |= UART_IER_MSI;
- serial8250_rpm_get(up);
+ guard(serial8250_rpm)(up);
serial_port_out(port, UART_IER, up->ier);
- serial8250_rpm_put(up);
}
void serial8250_read_char(struct uart_8250_port *up, u16 lsr)
@@ -1848,15 +1839,11 @@ static int serial8250_default_handle_irq(struct uart_port *port)
{
struct uart_8250_port *up = up_to_u8250p(port);
unsigned int iir;
- int ret;
- serial8250_rpm_get(up);
+ guard(serial8250_rpm)(up);
iir = serial_port_in(port, UART_IIR);
- ret = serial8250_handle_irq(port, iir);
-
- serial8250_rpm_put(up);
- return ret;
+ return serial8250_handle_irq(port, iir);
}
/*
@@ -1867,16 +1854,14 @@ static int serial8250_default_handle_irq(struct uart_port *port)
*/
static int serial8250_tx_threshold_handle_irq(struct uart_port *port)
{
- unsigned long flags;
unsigned int iir = serial_port_in(port, UART_IIR);
/* TX Threshold IRQ triggered so load up FIFO */
if ((iir & UART_IIR_ID) == UART_IIR_THRI) {
struct uart_8250_port *up = up_to_u8250p(port);
- uart_port_lock_irqsave(port, &flags);
+ guard(uart_port_lock_irqsave)(port);
serial8250_tx_chars(up);
- uart_port_unlock_irqrestore(port, flags);
}
iir = serial_port_in(port, UART_IIR);
@@ -1886,19 +1871,14 @@ static int serial8250_tx_threshold_handle_irq(struct uart_port *port)
static unsigned int serial8250_tx_empty(struct uart_port *port)
{
struct uart_8250_port *up = up_to_u8250p(port);
- unsigned int result = 0;
- unsigned long flags;
- serial8250_rpm_get(up);
+ guard(serial8250_rpm)(up);
+ guard(uart_port_lock_irqsave)(port);
- uart_port_lock_irqsave(port, &flags);
if (!serial8250_tx_dma_running(up) && uart_lsr_tx_empty(serial_lsr_in(up)))
- result = TIOCSER_TEMT;
- uart_port_unlock_irqrestore(port, flags);
-
- serial8250_rpm_put(up);
+ return TIOCSER_TEMT;
- return result;
+ return 0;
}
unsigned int serial8250_do_get_mctrl(struct uart_port *port)
@@ -1907,9 +1887,8 @@ unsigned int serial8250_do_get_mctrl(struct uart_port *port)
unsigned int status;
unsigned int val;
- serial8250_rpm_get(up);
- status = serial8250_modem_status(up);
- serial8250_rpm_put(up);
+ scoped_guard(serial8250_rpm, up)
+ status = serial8250_modem_status(up);
val = serial8250_MSR_to_TIOCM(status);
if (up->gpios)
@@ -1953,17 +1932,15 @@ static void serial8250_set_mctrl(struct uart_port *port, unsigned int mctrl)
static void serial8250_break_ctl(struct uart_port *port, int break_state)
{
struct uart_8250_port *up = up_to_u8250p(port);
- unsigned long flags;
- serial8250_rpm_get(up);
- uart_port_lock_irqsave(port, &flags);
+ guard(serial8250_rpm)(up);
+ guard(uart_port_lock_irqsave)(port);
+
if (break_state == -1)
up->lcr |= UART_LCR_SBC;
else
up->lcr &= ~UART_LCR_SBC;
serial_port_out(port, UART_LCR, up->lcr);
- uart_port_unlock_irqrestore(port, flags);
- serial8250_rpm_put(up);
}
/* Returns true if @bits were set, false on timeout */
@@ -2023,22 +2000,15 @@ static void wait_for_xmitr(struct uart_8250_port *up, int bits)
static int serial8250_get_poll_char(struct uart_port *port)
{
struct uart_8250_port *up = up_to_u8250p(port);
- int status;
u16 lsr;
- serial8250_rpm_get(up);
+ guard(serial8250_rpm)(up);
lsr = serial_port_in(port, UART_LSR);
+ if (!(lsr & UART_LSR_DR))
+ return NO_POLL_CHAR;
- if (!(lsr & UART_LSR_DR)) {
- status = NO_POLL_CHAR;
- goto out;
- }
-
- status = serial_port_in(port, UART_RX);
-out:
- serial8250_rpm_put(up);
- return status;
+ return serial_port_in(port, UART_RX);
}
@@ -2056,7 +2026,7 @@ static void serial8250_put_poll_char(struct uart_port *port,
* should allow safe lockless usage here.
*/
- serial8250_rpm_get(up);
+ guard(serial8250_rpm)(up);
/*
* First save the IER then disable the interrupts
*/
@@ -2075,7 +2045,6 @@ static void serial8250_put_poll_char(struct uart_port *port,
*/
wait_for_xmitr(up, UART_LSR_BOTH_EMPTY);
serial_port_out(port, UART_IER, ier);
- serial8250_rpm_put(up);
}
#endif /* CONFIG_CONSOLE_POLL */
@@ -2083,16 +2052,15 @@ static void serial8250_put_poll_char(struct uart_port *port,
static void serial8250_startup_special(struct uart_port *port)
{
struct uart_8250_port *up = up_to_u8250p(port);
- unsigned long flags;
switch (port->type) {
- case PORT_16C950:
+ case PORT_16C950: {
/*
* Wake up and initialize UART
*
* Synchronize UART_IER access against the console.
*/
- uart_port_lock_irqsave(port, &flags);
+ guard(uart_port_lock_irqsave)(port);
up->acr = 0;
serial_port_out(port, UART_LCR, UART_LCR_CONF_MODE_B);
serial_port_out(port, UART_EFR, UART_EFR_ECB);
@@ -2102,18 +2070,18 @@ static void serial8250_startup_special(struct uart_port *port)
serial_port_out(port, UART_LCR, UART_LCR_CONF_MODE_B);
serial_port_out(port, UART_EFR, UART_EFR_ECB);
serial_port_out(port, UART_LCR, 0);
- uart_port_unlock_irqrestore(port, flags);
break;
+ }
case PORT_DA830:
/*
* Reset the port
*
* Synchronize UART_IER access against the console.
*/
- uart_port_lock_irqsave(port, &flags);
- serial_port_out(port, UART_IER, 0);
- serial_port_out(port, UART_DA830_PWREMU_MGMT, 0);
- uart_port_unlock_irqrestore(port, flags);
+ scoped_guard(uart_port_lock_irqsave, port) {
+ serial_port_out(port, UART_IER, 0);
+ serial_port_out(port, UART_DA830_PWREMU_MGMT, 0);
+ }
mdelay(10);
/* Enable Tx, Rx and free run mode */
@@ -2171,7 +2139,6 @@ static void serial8250_set_TRG_levels(struct uart_port *port)
static void serial8250_THRE_test(struct uart_port *port)
{
struct uart_8250_port *up = up_to_u8250p(port);
- unsigned long flags;
bool iir_noint1, iir_noint2;
if (!port->irq)
@@ -2191,19 +2158,17 @@ static void serial8250_THRE_test(struct uart_port *port)
*
* Synchronize UART_IER access against the console.
*/
- uart_port_lock_irqsave(port, &flags);
-
- wait_for_xmitr(up, UART_LSR_THRE);
- serial_port_out_sync(port, UART_IER, UART_IER_THRI);
- udelay(1); /* allow THRE to set */
- iir_noint1 = serial_port_in(port, UART_IIR) & UART_IIR_NO_INT;
- serial_port_out(port, UART_IER, 0);
- serial_port_out_sync(port, UART_IER, UART_IER_THRI);
- udelay(1); /* allow a working UART time to re-assert THRE */
- iir_noint2 = serial_port_in(port, UART_IIR) & UART_IIR_NO_INT;
- serial_port_out(port, UART_IER, 0);
-
- uart_port_unlock_irqrestore(port, flags);
+ scoped_guard(uart_port_lock_irqsave, port) {
+ wait_for_xmitr(up, UART_LSR_THRE);
+ serial_port_out_sync(port, UART_IER, UART_IER_THRI);
+ udelay(1); /* allow THRE to set */
+ iir_noint1 = serial_port_in(port, UART_IIR) & UART_IIR_NO_INT;
+ serial_port_out(port, UART_IER, 0);
+ serial_port_out_sync(port, UART_IER, UART_IER_THRI);
+ udelay(1); /* allow a working UART time to re-assert THRE */
+ iir_noint2 = serial_port_in(port, UART_IIR) & UART_IIR_NO_INT;
+ serial_port_out(port, UART_IER, 0);
+ }
if (port->irqflags & IRQF_SHARED)
enable_irq(port->irq);
@@ -2267,14 +2232,11 @@ static void serial8250_iir_txen_test(struct uart_port *port)
static void serial8250_initialize(struct uart_port *port)
{
- unsigned long flags;
-
- uart_port_lock_irqsave(port, &flags);
+ guard(uart_port_lock_irqsave)(port);
serial_port_out(port, UART_LCR, UART_LCR_WLEN8);
serial8250_init_mctrl(port);
serial8250_iir_txen_test(port);
- uart_port_unlock_irqrestore(port, flags);
}
int serial8250_do_startup(struct uart_port *port)
@@ -2293,7 +2255,7 @@ int serial8250_do_startup(struct uart_port *port)
if (port->iotype != up->cur_iotype)
set_io_from_upio(port);
- serial8250_rpm_get(up);
+ guard(serial8250_rpm)(up);
serial8250_startup_special(port);
@@ -2313,8 +2275,7 @@ int serial8250_do_startup(struct uart_port *port)
if (!(port->flags & UPF_BUGGY_UART) &&
(serial_port_in(port, UART_LSR) == 0xff)) {
dev_info_ratelimited(port->dev, "LSR safety check engaged!\n");
- retval = -ENODEV;
- goto out;
+ return -ENODEV;
}
serial8250_set_TRG_levels(port);
@@ -2325,7 +2286,7 @@ int serial8250_do_startup(struct uart_port *port)
retval = up->ops->setup_irq(up);
if (retval)
- goto out;
+ return retval;
serial8250_THRE_test(port);
@@ -2374,10 +2335,8 @@ int serial8250_do_startup(struct uart_port *port)
outb_p(0x80, icp);
inb_p(icp);
}
- retval = 0;
-out:
- serial8250_rpm_put(up);
- return retval;
+
+ return 0;
}
EXPORT_SYMBOL_GPL(serial8250_do_startup);
@@ -2391,7 +2350,6 @@ static int serial8250_startup(struct uart_port *port)
void serial8250_do_shutdown(struct uart_port *port)
{
struct uart_8250_port *up = up_to_u8250p(port);
- unsigned long flags;
serial8250_rpm_get(up);
/*
@@ -2399,26 +2357,26 @@ void serial8250_do_shutdown(struct uart_port *port)
*
* Synchronize UART_IER access against the console.
*/
- uart_port_lock_irqsave(port, &flags);
- up->ier = 0;
- serial_port_out(port, UART_IER, 0);
- uart_port_unlock_irqrestore(port, flags);
+ scoped_guard(uart_port_lock_irqsave, port) {
+ up->ier = 0;
+ serial_port_out(port, UART_IER, 0);
+ }
synchronize_irq(port->irq);
if (up->dma)
serial8250_release_dma(up);
- uart_port_lock_irqsave(port, &flags);
- if (port->flags & UPF_FOURPORT) {
- /* reset interrupts on the AST Fourport board */
- inb((port->iobase & 0xfe0) | 0x1f);
- port->mctrl |= TIOCM_OUT1;
- } else
- port->mctrl &= ~TIOCM_OUT2;
+ scoped_guard(uart_port_lock_irqsave, port) {
+ if (port->flags & UPF_FOURPORT) {
+ /* reset interrupts on the AST Fourport board */
+ inb((port->iobase & 0xfe0) | 0x1f);
+ port->mctrl |= TIOCM_OUT1;
+ } else
+ port->mctrl &= ~TIOCM_OUT2;
- serial8250_set_mctrl(port, port->mctrl);
- uart_port_unlock_irqrestore(port, flags);
+ serial8250_set_mctrl(port, port->mctrl);
+ }
/*
* Disable break condition and FIFOs
@@ -2610,33 +2568,27 @@ static unsigned int serial8250_get_baud_rate(struct uart_port *port,
void serial8250_update_uartclk(struct uart_port *port, unsigned int uartclk)
{
struct tty_port *tport = &port->state->port;
- struct tty_struct *tty;
- tty = tty_port_tty_get(tport);
- if (!tty) {
- mutex_lock(&tport->mutex);
- port->uartclk = uartclk;
- mutex_unlock(&tport->mutex);
- return;
- }
+ scoped_guard(tty_port_tty, tport) {
+ struct tty_struct *tty = scoped_tty();
- down_write(&tty->termios_rwsem);
- mutex_lock(&tport->mutex);
+ guard(rwsem_write)(&tty->termios_rwsem);
+ guard(mutex)(&tport->mutex);
- if (port->uartclk == uartclk)
- goto out_unlock;
+ if (port->uartclk == uartclk)
+ return;
- port->uartclk = uartclk;
+ port->uartclk = uartclk;
- if (!tty_port_initialized(tport))
- goto out_unlock;
+ if (!tty_port_initialized(tport))
+ return;
- serial8250_do_set_termios(port, &tty->termios, NULL);
+ serial8250_do_set_termios(port, &tty->termios, NULL);
-out_unlock:
- mutex_unlock(&tport->mutex);
- up_write(&tty->termios_rwsem);
- tty_kref_put(tty);
+ return;
+ }
+ guard(mutex)(&tport->mutex);
+ port->uartclk = uartclk;
}
EXPORT_SYMBOL_GPL(serial8250_update_uartclk);
@@ -2791,7 +2743,6 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios,
const struct ktermios *old)
{
struct uart_8250_port *up = up_to_u8250p(port);
- unsigned long flags;
unsigned int baud, quot, frac = 0;
u8 lcr;
@@ -2801,27 +2752,24 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios,
quot = serial8250_get_divisor(port, baud, &frac);
/*
- * Ok, we're now changing the port state. Do it with
- * interrupts disabled.
+ * Ok, we're now changing the port state. Do it with interrupts disabled.
*
* Synchronize UART_IER access against the console.
*/
- serial8250_rpm_get(up);
- uart_port_lock_irqsave(port, &flags);
-
- up->lcr = lcr;
- serial8250_set_trigger_for_slow_speed(port, termios, baud);
- serial8250_set_afe(port, termios);
- uart_update_timeout(port, termios->c_cflag, baud);
- serial8250_set_errors_and_ignores(port, termios);
- serial8250_set_ier(port, termios);
- serial8250_set_efr(port, termios);
- serial8250_set_divisor(port, baud, quot, frac);
- serial8250_set_fcr(port, termios);
- serial8250_set_mctrl(port, port->mctrl);
+ scoped_guard(serial8250_rpm, up) {
+ guard(uart_port_lock_irqsave)(port);
- uart_port_unlock_irqrestore(port, flags);
- serial8250_rpm_put(up);
+ up->lcr = lcr;
+ serial8250_set_trigger_for_slow_speed(port, termios, baud);
+ serial8250_set_afe(port, termios);
+ uart_update_timeout(port, termios->c_cflag, baud);
+ serial8250_set_errors_and_ignores(port, termios);
+ serial8250_set_ier(port, termios);
+ serial8250_set_efr(port, termios);
+ serial8250_set_divisor(port, baud, quot, frac);
+ serial8250_set_fcr(port, termios);
+ serial8250_set_mctrl(port, port->mctrl);
+ }
/* Don't rewrite B0 */
if (tty_termios_baud_rate(termios))
@@ -2843,15 +2791,13 @@ void serial8250_do_set_ldisc(struct uart_port *port, struct ktermios *termios)
{
if (termios->c_line == N_PPS) {
port->flags |= UPF_HARDPPS_CD;
- uart_port_lock_irq(port);
+ guard(uart_port_lock_irq)(port);
serial8250_enable_ms(port);
- uart_port_unlock_irq(port);
} else {
port->flags &= ~UPF_HARDPPS_CD;
if (!UART_ENABLE_MS(port, termios->c_cflag)) {
- uart_port_lock_irq(port);
+ guard(uart_port_lock_irq)(port);
serial8250_disable_ms(port);
- uart_port_unlock_irq(port);
}
}
}
diff --git a/drivers/tty/serial/8250/8250_rsa.c b/drivers/tty/serial/8250/8250_rsa.c
index 12a65b79583c..40a3dbd9e452 100644
--- a/drivers/tty/serial/8250/8250_rsa.c
+++ b/drivers/tty/serial/8250/8250_rsa.c
@@ -140,9 +140,8 @@ void rsa_enable(struct uart_8250_port *up)
return;
if (up->port.uartclk != SERIAL_RSA_BAUD_BASE * 16) {
- uart_port_lock_irq(&up->port);
+ guard(uart_port_lock_irq)(&up->port);
__rsa_enable(up);
- uart_port_unlock_irq(&up->port);
}
if (up->port.uartclk == SERIAL_RSA_BAUD_BASE * 16)
serial_out(up, UART_RSA_FRR, 0);
@@ -165,7 +164,8 @@ void rsa_disable(struct uart_8250_port *up)
if (up->port.uartclk != SERIAL_RSA_BAUD_BASE * 16)
return;
- uart_port_lock_irq(&up->port);
+ guard(uart_port_lock_irq)(&up->port);
+
mode = serial_in(up, UART_RSA_MSR);
result = !(mode & UART_RSA_MSR_FIFO);
@@ -177,7 +177,6 @@ void rsa_disable(struct uart_8250_port *up)
if (result)
up->port.uartclk = SERIAL_RSA_BAUD_BASE_LO * 16;
- uart_port_unlock_irq(&up->port);
}
EXPORT_SYMBOL_FOR_MODULES(rsa_disable, "8250_base");
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 724ad4f3cbee..282116765e64 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -782,7 +782,7 @@ config SERIAL_CPM
depends on CPM2 || CPM1
select SERIAL_CORE
help
- This driver supports the SCC and SMC serial ports on Motorola
+ This driver supports the SCC and SMC serial ports on Motorola
embedded PowerPC that contain a CPM1 (8xx) or CPM2 (8xxx)
config SERIAL_CPM_CONSOLE
@@ -928,6 +928,14 @@ config SERIAL_QCOM_GENI_CONSOLE
Serial console driver for Qualcomm Technologies Inc's GENI based
QUP hardware.
+config SERIAL_QCOM_GENI_UART_PORTS
+ int "Maximum number of GENI UART ports"
+ depends on SERIAL_QCOM_GENI
+ default "8"
+ help
+ Set this to the maximum number of serial ports you want the driver
+ to support.
+
config SERIAL_VT8500
bool "VIA VT8500 on-chip serial port support"
depends on ARCH_VT8500 || COMPILE_TEST
@@ -1412,7 +1420,7 @@ config SERIAL_STM32
config SERIAL_STM32_CONSOLE
bool "Support for console on STM32"
- depends on SERIAL_STM32=y
+ depends on SERIAL_STM32
select SERIAL_CORE_CONSOLE
select SERIAL_EARLYCON
diff --git a/drivers/tty/serial/ip22zilog.c b/drivers/tty/serial/ip22zilog.c
index c2cae50f06f3..6e19c6713849 100644
--- a/drivers/tty/serial/ip22zilog.c
+++ b/drivers/tty/serial/ip22zilog.c
@@ -30,6 +30,7 @@
#include <linux/console.h>
#include <linux/spinlock.h>
#include <linux/init.h>
+#include <linux/platform_device.h>
#include <linux/io.h>
#include <asm/irq.h>
@@ -50,8 +51,9 @@
#define ZSDELAY_LONG() udelay(20)
#define ZS_WSYNC(channel) do { } while (0)
-#define NUM_IP22ZILOG 1
-#define NUM_CHANNELS (NUM_IP22ZILOG * 2)
+#define NUM_CHANNELS 2
+#define CHANNEL_B 0
+#define CHANNEL_A 1
#define ZS_CLOCK 3672000 /* Zilog input clock rate. */
#define ZS_CLOCK_DIVISOR 16 /* Divisor this driver uses. */
@@ -62,9 +64,6 @@
struct uart_ip22zilog_port {
struct uart_port port;
- /* IRQ servicing chain. */
- struct uart_ip22zilog_port *next;
-
/* Current values of Zilog write registers. */
unsigned char curregs[NUM_ZSREGS];
@@ -72,7 +71,6 @@ struct uart_ip22zilog_port {
#define IP22ZILOG_FLAG_IS_CONS 0x00000004
#define IP22ZILOG_FLAG_IS_KGDB 0x00000008
#define IP22ZILOG_FLAG_MODEM_STATUS 0x00000010
-#define IP22ZILOG_FLAG_IS_CHANNEL_A 0x00000020
#define IP22ZILOG_FLAG_REGS_HELD 0x00000040
#define IP22ZILOG_FLAG_TX_STOPPED 0x00000080
#define IP22ZILOG_FLAG_TX_ACTIVE 0x00000100
@@ -84,6 +82,8 @@ struct uart_ip22zilog_port {
unsigned char prev_status;
};
+static struct uart_ip22zilog_port ip22zilog_port_table[NUM_CHANNELS];
+
#define ZILOG_CHANNEL_FROM_PORT(PORT) ((struct zilog_channel *)((PORT)->membase))
#define UART_ZILOG(PORT) ((struct uart_ip22zilog_port *)(PORT))
#define IP22ZILOG_GET_CURR_REG(PORT, REGNUM) \
@@ -93,7 +93,6 @@ struct uart_ip22zilog_port {
#define ZS_IS_CONS(UP) ((UP)->flags & IP22ZILOG_FLAG_IS_CONS)
#define ZS_IS_KGDB(UP) ((UP)->flags & IP22ZILOG_FLAG_IS_KGDB)
#define ZS_WANTS_MODEM_STATUS(UP) ((UP)->flags & IP22ZILOG_FLAG_MODEM_STATUS)
-#define ZS_IS_CHANNEL_A(UP) ((UP)->flags & IP22ZILOG_FLAG_IS_CHANNEL_A)
#define ZS_REGS_HELD(UP) ((UP)->flags & IP22ZILOG_FLAG_REGS_HELD)
#define ZS_TX_STOPPED(UP) ((UP)->flags & IP22ZILOG_FLAG_TX_STOPPED)
#define ZS_TX_ACTIVE(UP) ((UP)->flags & IP22ZILOG_FLAG_TX_ACTIVE)
@@ -423,60 +422,57 @@ ack_tx_int:
static irqreturn_t ip22zilog_interrupt(int irq, void *dev_id)
{
- struct uart_ip22zilog_port *up = dev_id;
-
- while (up) {
- struct zilog_channel *channel
- = ZILOG_CHANNEL_FROM_PORT(&up->port);
- unsigned char r3;
- bool push = false;
-
- uart_port_lock(&up->port);
- r3 = read_zsreg(channel, R3);
+ struct uart_ip22zilog_port *up;
+ struct zilog_channel *channel;
+ unsigned char r3;
+ bool push = false;
- /* Channel A */
- if (r3 & (CHAEXT | CHATxIP | CHARxIP)) {
- writeb(RES_H_IUS, &channel->control);
- ZSDELAY();
- ZS_WSYNC(channel);
+ up = &ip22zilog_port_table[CHANNEL_A];
+ channel = ZILOG_CHANNEL_FROM_PORT(&up->port);
- if (r3 & CHARxIP)
- push = ip22zilog_receive_chars(up, channel);
- if (r3 & CHAEXT)
- ip22zilog_status_handle(up, channel);
- if (r3 & CHATxIP)
- ip22zilog_transmit_chars(up, channel);
- }
- uart_port_unlock(&up->port);
+ uart_port_lock(&up->port);
+ r3 = read_zsreg(channel, R3);
- if (push)
- tty_flip_buffer_push(&up->port.state->port);
+ /* Channel A */
+ if (r3 & (CHAEXT | CHATxIP | CHARxIP)) {
+ writeb(RES_H_IUS, &channel->control);
+ ZSDELAY();
+ ZS_WSYNC(channel);
- /* Channel B */
- up = up->next;
- channel = ZILOG_CHANNEL_FROM_PORT(&up->port);
- push = false;
+ if (r3 & CHARxIP)
+ push = ip22zilog_receive_chars(up, channel);
+ if (r3 & CHAEXT)
+ ip22zilog_status_handle(up, channel);
+ if (r3 & CHATxIP)
+ ip22zilog_transmit_chars(up, channel);
+ }
+ uart_port_unlock(&up->port);
- uart_port_lock(&up->port);
- if (r3 & (CHBEXT | CHBTxIP | CHBRxIP)) {
- writeb(RES_H_IUS, &channel->control);
- ZSDELAY();
- ZS_WSYNC(channel);
+ if (push)
+ tty_flip_buffer_push(&up->port.state->port);
- if (r3 & CHBRxIP)
- push = ip22zilog_receive_chars(up, channel);
- if (r3 & CHBEXT)
- ip22zilog_status_handle(up, channel);
- if (r3 & CHBTxIP)
- ip22zilog_transmit_chars(up, channel);
- }
- uart_port_unlock(&up->port);
+ /* Channel B */
+ up = &ip22zilog_port_table[CHANNEL_B];
+ channel = ZILOG_CHANNEL_FROM_PORT(&up->port);
+ push = false;
- if (push)
- tty_flip_buffer_push(&up->port.state->port);
+ uart_port_lock(&up->port);
+ if (r3 & (CHBEXT | CHBTxIP | CHBRxIP)) {
+ writeb(RES_H_IUS, &channel->control);
+ ZSDELAY();
+ ZS_WSYNC(channel);
- up = up->next;
+ if (r3 & CHBRxIP)
+ push = ip22zilog_receive_chars(up, channel);
+ if (r3 & CHBEXT)
+ ip22zilog_status_handle(up, channel);
+ if (r3 & CHBTxIP)
+ ip22zilog_transmit_chars(up, channel);
}
+ uart_port_unlock(&up->port);
+
+ if (push)
+ tty_flip_buffer_push(&up->port.state->port);
return IRQ_HANDLED;
}
@@ -692,16 +688,16 @@ static void __ip22zilog_reset(struct uart_ip22zilog_port *up)
udelay(100);
}
- if (!ZS_IS_CHANNEL_A(up)) {
- up++;
- channel = ZILOG_CHANNEL_FROM_PORT(&up->port);
- }
+ up = &ip22zilog_port_table[CHANNEL_A];
+ channel = ZILOG_CHANNEL_FROM_PORT(&up->port);
+
write_zsreg(channel, R9, FHWRES);
ZSDELAY_LONG();
(void) read_zsreg(channel, R0);
up->flags |= IP22ZILOG_FLAG_RESET_DONE;
- up->next->flags |= IP22ZILOG_FLAG_RESET_DONE;
+ up = &ip22zilog_port_table[CHANNEL_B];
+ up->flags |= IP22ZILOG_FLAG_RESET_DONE;
}
static void __ip22zilog_startup(struct uart_ip22zilog_port *up)
@@ -942,47 +938,6 @@ static const struct uart_ops ip22zilog_pops = {
.verify_port = ip22zilog_verify_port,
};
-static struct uart_ip22zilog_port *ip22zilog_port_table;
-static struct zilog_layout **ip22zilog_chip_regs;
-
-static struct uart_ip22zilog_port *ip22zilog_irq_chain;
-static int zilog_irq = -1;
-
-static void * __init alloc_one_table(unsigned long size)
-{
- return kzalloc(size, GFP_KERNEL);
-}
-
-static void __init ip22zilog_alloc_tables(void)
-{
- ip22zilog_port_table = (struct uart_ip22zilog_port *)
- alloc_one_table(NUM_CHANNELS * sizeof(struct uart_ip22zilog_port));
- ip22zilog_chip_regs = (struct zilog_layout **)
- alloc_one_table(NUM_IP22ZILOG * sizeof(struct zilog_layout *));
-
- if (ip22zilog_port_table == NULL || ip22zilog_chip_regs == NULL) {
- panic("IP22-Zilog: Cannot allocate IP22-Zilog tables.");
- }
-}
-
-/* Get the address of the registers for IP22-Zilog instance CHIP. */
-static struct zilog_layout * __init get_zs(int chip)
-{
- unsigned long base;
-
- if (chip < 0 || chip >= NUM_IP22ZILOG) {
- panic("IP22-Zilog: Illegal chip number %d in get_zs.", chip);
- }
-
- /* Not probe-able, hard code it. */
- base = (unsigned long) &sgioc->uart;
-
- zilog_irq = SGI_SERIAL_IRQ;
- request_mem_region(base, 8, "IP22-Zilog");
-
- return (struct zilog_layout *) base;
-}
-
#define ZS_PUT_CHAR_MAX_DELAY 2000 /* 10 ms */
#ifdef CONFIG_SERIAL_IP22_ZILOG_CONSOLE
@@ -1070,144 +1025,123 @@ static struct uart_driver ip22zilog_reg = {
#endif
};
-static void __init ip22zilog_prepare(void)
+static void __init ip22zilog_prepare(struct uart_ip22zilog_port *up)
{
unsigned char sysrq_on = IS_ENABLED(CONFIG_SERIAL_IP22_ZILOG_CONSOLE);
+ int brg;
+
+ spin_lock_init(&up->port.lock);
+
+ up->port.iotype = UPIO_MEM;
+ up->port.uartclk = ZS_CLOCK;
+ up->port.fifosize = 1;
+ up->port.has_sysrq = sysrq_on;
+ up->port.ops = &ip22zilog_pops;
+ up->port.type = PORT_IP22ZILOG;
+
+ /* Normal serial TTY. */
+ up->parity_mask = 0xff;
+ up->curregs[R1] = EXT_INT_ENAB | INT_ALL_Rx | TxINT_ENAB;
+ up->curregs[R4] = PAR_EVEN | X16CLK | SB1;
+ up->curregs[R3] = RxENAB | Rx8;
+ up->curregs[R5] = TxENAB | Tx8;
+ up->curregs[R9] = NV | MIE;
+ up->curregs[R10] = NRZ;
+ up->curregs[R11] = TCBR | RCBR;
+ brg = BPS_TO_BRG(9600, ZS_CLOCK / ZS_CLOCK_DIVISOR);
+ up->curregs[R12] = (brg & 0xff);
+ up->curregs[R13] = (brg >> 8) & 0xff;
+ up->curregs[R14] = BRENAB;
+}
+
+static int ip22zilog_probe(struct platform_device *pdev)
+{
struct uart_ip22zilog_port *up;
- struct zilog_layout *rp;
- int channel, chip;
+ char __iomem *membase;
+ struct resource *res;
+ int irq;
+ int i;
- /*
- * Temporary fix.
- */
- for (channel = 0; channel < NUM_CHANNELS; channel++)
- spin_lock_init(&ip22zilog_port_table[channel].port.lock);
-
- ip22zilog_irq_chain = &ip22zilog_port_table[NUM_CHANNELS - 1];
- up = &ip22zilog_port_table[0];
- for (channel = NUM_CHANNELS - 1 ; channel > 0; channel--)
- up[channel].next = &up[channel - 1];
- up[channel].next = NULL;
-
- for (chip = 0; chip < NUM_IP22ZILOG; chip++) {
- if (!ip22zilog_chip_regs[chip]) {
- ip22zilog_chip_regs[chip] = rp = get_zs(chip);
-
- up[(chip * 2) + 0].port.membase = (char *) &rp->channelB;
- up[(chip * 2) + 1].port.membase = (char *) &rp->channelA;
-
- /* In theory mapbase is the physical address ... */
- up[(chip * 2) + 0].port.mapbase =
- (unsigned long) ioremap((unsigned long) &rp->channelB, 8);
- up[(chip * 2) + 1].port.mapbase =
- (unsigned long) ioremap((unsigned long) &rp->channelA, 8);
- }
+ up = &ip22zilog_port_table[CHANNEL_B];
+ if (up->port.dev)
+ return -ENOSPC;
- /* Channel A */
- up[(chip * 2) + 0].port.iotype = UPIO_MEM;
- up[(chip * 2) + 0].port.irq = zilog_irq;
- up[(chip * 2) + 0].port.uartclk = ZS_CLOCK;
- up[(chip * 2) + 0].port.fifosize = 1;
- up[(chip * 2) + 0].port.has_sysrq = sysrq_on;
- up[(chip * 2) + 0].port.ops = &ip22zilog_pops;
- up[(chip * 2) + 0].port.type = PORT_IP22ZILOG;
- up[(chip * 2) + 0].port.flags = 0;
- up[(chip * 2) + 0].port.line = (chip * 2) + 0;
- up[(chip * 2) + 0].flags = 0;
-
- /* Channel B */
- up[(chip * 2) + 1].port.iotype = UPIO_MEM;
- up[(chip * 2) + 1].port.irq = zilog_irq;
- up[(chip * 2) + 1].port.uartclk = ZS_CLOCK;
- up[(chip * 2) + 1].port.fifosize = 1;
- up[(chip * 2) + 1].port.has_sysrq = sysrq_on;
- up[(chip * 2) + 1].port.ops = &ip22zilog_pops;
- up[(chip * 2) + 1].port.type = PORT_IP22ZILOG;
- up[(chip * 2) + 1].port.line = (chip * 2) + 1;
- up[(chip * 2) + 1].flags |= IP22ZILOG_FLAG_IS_CHANNEL_A;
- }
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
- for (channel = 0; channel < NUM_CHANNELS; channel++) {
- struct uart_ip22zilog_port *up = &ip22zilog_port_table[channel];
- int brg;
+ membase = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(membase))
+ return PTR_ERR(membase);
- /* Normal serial TTY. */
- up->parity_mask = 0xff;
- up->curregs[R1] = EXT_INT_ENAB | INT_ALL_Rx | TxINT_ENAB;
- up->curregs[R4] = PAR_EVEN | X16CLK | SB1;
- up->curregs[R3] = RxENAB | Rx8;
- up->curregs[R5] = TxENAB | Tx8;
- up->curregs[R9] = NV | MIE;
- up->curregs[R10] = NRZ;
- up->curregs[R11] = TCBR | RCBR;
- brg = BPS_TO_BRG(9600, ZS_CLOCK / ZS_CLOCK_DIVISOR);
- up->curregs[R12] = (brg & 0xff);
- up->curregs[R13] = (brg >> 8) & 0xff;
- up->curregs[R14] = BRENAB;
- }
-}
+ ip22zilog_prepare(up);
-static int __init ip22zilog_ports_init(void)
-{
- int ret;
+ up->port.mapbase = res->start + offsetof(struct zilog_layout, channelB);
+ up->port.membase = membase + offsetof(struct zilog_layout, channelB);
+ up->port.line = 0;
+ up->port.dev = &pdev->dev;
+ up->port.irq = irq;
- printk(KERN_INFO "Serial: IP22 Zilog driver (%d chips).\n", NUM_IP22ZILOG);
+ up = &ip22zilog_port_table[CHANNEL_A];
+ ip22zilog_prepare(up);
- ip22zilog_prepare();
+ up->port.mapbase = res->start + offsetof(struct zilog_layout, channelA);
+ up->port.membase = membase + offsetof(struct zilog_layout, channelA);
+ up->port.line = 1;
+ up->port.dev = &pdev->dev;
+ up->port.irq = irq;
- if (request_irq(zilog_irq, ip22zilog_interrupt, 0,
- "IP22-Zilog", ip22zilog_irq_chain)) {
+ if (request_irq(irq, ip22zilog_interrupt, 0,
+ "IP22-Zilog", NULL)) {
panic("IP22-Zilog: Unable to register zs interrupt handler.\n");
}
- ret = uart_register_driver(&ip22zilog_reg);
- if (ret == 0) {
- int i;
-
- for (i = 0; i < NUM_CHANNELS; i++) {
- struct uart_ip22zilog_port *up = &ip22zilog_port_table[i];
-
- uart_add_one_port(&ip22zilog_reg, &up->port);
- }
- }
-
- return ret;
-}
-
-static int __init ip22zilog_init(void)
-{
- /* IP22 Zilog setup is hard coded, no probing to do. */
- ip22zilog_alloc_tables();
- ip22zilog_ports_init();
+ for (i = 0; i < NUM_CHANNELS; i++)
+ uart_add_one_port(&ip22zilog_reg,
+ &ip22zilog_port_table[i].port);
return 0;
}
-static void __exit ip22zilog_exit(void)
+static void ip22zilog_remove(struct platform_device *pdev)
{
int i;
- struct uart_ip22zilog_port *up;
for (i = 0; i < NUM_CHANNELS; i++) {
- up = &ip22zilog_port_table[i];
-
- uart_remove_one_port(&ip22zilog_reg, &up->port);
+ uart_remove_one_port(&ip22zilog_reg,
+ &ip22zilog_port_table[i].port);
+ ip22zilog_port_table[i].port.dev = NULL;
}
+}
- /* Free IO mem */
- up = &ip22zilog_port_table[0];
- for (i = 0; i < NUM_IP22ZILOG; i++) {
- if (up[(i * 2) + 0].port.mapbase) {
- iounmap((void*)up[(i * 2) + 0].port.mapbase);
- up[(i * 2) + 0].port.mapbase = 0;
- }
- if (up[(i * 2) + 1].port.mapbase) {
- iounmap((void*)up[(i * 2) + 1].port.mapbase);
- up[(i * 2) + 1].port.mapbase = 0;
- }
+static struct platform_driver ip22zilog_driver = {
+ .probe = ip22zilog_probe,
+ .remove = ip22zilog_remove,
+ .driver = {
+ .name = "ip22zilog"
}
+};
+
+static int __init ip22zilog_init(void)
+{
+ int ret;
+
+ ret = uart_register_driver(&ip22zilog_reg);
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&ip22zilog_driver);
+ if (ret)
+ uart_unregister_driver(&ip22zilog_reg);
+ return ret;
+
+}
+
+static void __exit ip22zilog_exit(void)
+{
uart_unregister_driver(&ip22zilog_reg);
+ platform_driver_unregister(&ip22zilog_driver);
}
module_init(ip22zilog_init);
diff --git a/drivers/tty/serial/max3100.c b/drivers/tty/serial/max3100.c
index 67d80f8f801e..3faa1b6aa3ee 100644
--- a/drivers/tty/serial/max3100.c
+++ b/drivers/tty/serial/max3100.c
@@ -705,7 +705,7 @@ static int max3100_probe(struct spi_device *spi)
break;
if (i == MAX_MAX3100) {
mutex_unlock(&max3100s_lock);
- return dev_err_probe(dev, -ENOMEM, "too many MAX3100 chips\n");
+ return dev_err_probe(dev, -ENOSPC, "too many MAX3100 chips\n");
}
max3100s[i] = kzalloc(sizeof(struct max3100_port), GFP_KERNEL);
diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c
index ce260e9949c3..ac7d3f197c3a 100644
--- a/drivers/tty/serial/max310x.c
+++ b/drivers/tty/serial/max310x.c
@@ -823,17 +823,28 @@ static irqreturn_t max310x_ist(int irq, void *dev_id)
bool handled = false;
if (s->devtype->nr > 1) {
+ bool done;
+
do {
unsigned int val = ~0;
+ unsigned long irq;
+ unsigned int port;
+
+ done = true;
WARN_ON_ONCE(regmap_read(s->regmap,
MAX310X_GLOBALIRQ_REG, &val));
- val = ((1 << s->devtype->nr) - 1) & ~val;
- if (!val)
- break;
- if (max310x_port_irq(s, fls(val) - 1) == IRQ_HANDLED)
- handled = true;
- } while (1);
+
+ irq = val;
+
+ for_each_clear_bit(port, &irq, s->devtype->nr) {
+ done = false;
+
+ if (max310x_port_irq(s, port) == IRQ_HANDLED)
+ handled = true;
+ }
+
+ } while (!done);
} else {
if (max310x_port_irq(s, 0) == IRQ_HANDLED)
handled = true;
@@ -1269,8 +1280,7 @@ static int max310x_probe(struct device *dev, const struct max310x_devtype *devty
/* Alloc port structure */
s = devm_kzalloc(dev, struct_size(s, p, devtype->nr), GFP_KERNEL);
if (!s)
- return dev_err_probe(dev, -ENOMEM,
- "Error allocating port structure\n");
+ return -ENOMEM;
/* Always ask for fixed clock rate from a property. */
device_property_read_u32(dev, "clock-frequency", &uartclk);
@@ -1644,6 +1654,8 @@ static int max310x_i2c_probe(struct i2c_client *client)
port_client = devm_i2c_new_dummy_device(&client->dev,
client->adapter,
port_addr);
+ if (IS_ERR(port_client))
+ return PTR_ERR(port_client);
regcfg_i2c.name = max310x_regmap_name(i);
regmaps[i] = devm_regmap_init_i2c(port_client, &regcfg_i2c);
diff --git a/drivers/tty/serial/msm_serial.c b/drivers/tty/serial/msm_serial.c
index 3449945493ce..2e999cb9c974 100644
--- a/drivers/tty/serial/msm_serial.c
+++ b/drivers/tty/serial/msm_serial.c
@@ -1102,7 +1102,7 @@ msm_find_best_baud(struct uart_port *port, unsigned int baud,
if (result == baud)
break;
- } else if (entry->divisor > divisor) {
+ } else {
old = target;
target = clk_round_rate(msm_port->clk, old + 1);
/*
diff --git a/drivers/tty/serial/mvebu-uart.c b/drivers/tty/serial/mvebu-uart.c
index 5de57b77abdb..8e52be2b34ea 100644
--- a/drivers/tty/serial/mvebu-uart.c
+++ b/drivers/tty/serial/mvebu-uart.c
@@ -1264,14 +1264,16 @@ static unsigned long mvebu_uart_clock_recalc_rate(struct clk_hw *hw,
return parent_rate / uart_clock_base->div;
}
-static long mvebu_uart_clock_round_rate(struct clk_hw *hw, unsigned long rate,
- unsigned long *parent_rate)
+static int mvebu_uart_clock_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
{
struct mvebu_uart_clock *uart_clock = to_uart_clock(hw);
struct mvebu_uart_clock_base *uart_clock_base =
to_uart_clock_base(uart_clock);
- return *parent_rate / uart_clock_base->div;
+ req->rate = req->best_parent_rate / uart_clock_base->div;
+
+ return 0;
}
static int mvebu_uart_clock_set_rate(struct clk_hw *hw, unsigned long rate,
@@ -1293,7 +1295,7 @@ static const struct clk_ops mvebu_uart_clock_ops = {
.is_enabled = mvebu_uart_clock_is_enabled,
.save_context = mvebu_uart_clock_save_context,
.restore_context = mvebu_uart_clock_restore_context,
- .round_rate = mvebu_uart_clock_round_rate,
+ .determine_rate = mvebu_uart_clock_determine_rate,
.set_rate = mvebu_uart_clock_set_rate,
.recalc_rate = mvebu_uart_clock_recalc_rate,
};
diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c
index 3f5b87c4cc54..ce5cb97d60a7 100644
--- a/drivers/tty/serial/qcom_geni_serial.c
+++ b/drivers/tty/serial/qcom_geni_serial.c
@@ -1,5 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
-// Copyright (c) 2017-2018, The Linux foundation. All rights reserved.
+/*
+ * Copyright (c) 2017-2018, The Linux foundation. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
/* Disable MMIO tracing to prevent excessive logging of unwanted MMIO traces */
#define __DISABLE_TRACE_MMIO__
@@ -77,7 +80,6 @@
#define STALE_TIMEOUT 16
#define DEFAULT_BITS_PER_CHAR 10
#define GENI_UART_CONS_PORTS 1
-#define GENI_UART_PORTS 3
#define DEF_FIFO_DEPTH_WORDS 16
#define DEF_TX_WM 2
#define DEF_FIFO_WIDTH_BITS 32
@@ -164,33 +166,6 @@ static inline struct qcom_geni_serial_port *to_dev_port(struct uart_port *uport)
return container_of(uport, struct qcom_geni_serial_port, uport);
}
-static struct qcom_geni_serial_port qcom_geni_uart_ports[GENI_UART_PORTS] = {
- [0] = {
- .uport = {
- .iotype = UPIO_MEM,
- .ops = &qcom_geni_uart_pops,
- .flags = UPF_BOOT_AUTOCONF,
- .line = 0,
- },
- },
- [1] = {
- .uport = {
- .iotype = UPIO_MEM,
- .ops = &qcom_geni_uart_pops,
- .flags = UPF_BOOT_AUTOCONF,
- .line = 1,
- },
- },
- [2] = {
- .uport = {
- .iotype = UPIO_MEM,
- .ops = &qcom_geni_uart_pops,
- .flags = UPF_BOOT_AUTOCONF,
- .line = 2,
- },
- },
-};
-
static struct qcom_geni_serial_port qcom_geni_console_port = {
.uport = {
.iotype = UPIO_MEM,
@@ -285,10 +260,10 @@ static const char *qcom_geni_serial_get_type(struct uart_port *uport)
return "MSM";
}
-static struct qcom_geni_serial_port *get_port_from_line(int line, bool console)
+static struct qcom_geni_serial_port *get_port_from_line(int line, bool console, struct device *dev)
{
struct qcom_geni_serial_port *port;
- int nr_ports = console ? GENI_UART_CONS_PORTS : GENI_UART_PORTS;
+ int nr_ports = console ? GENI_UART_CONS_PORTS : CONFIG_SERIAL_QCOM_GENI_UART_PORTS;
if (console) {
if (line < 0 || line >= nr_ports)
@@ -299,14 +274,23 @@ static struct qcom_geni_serial_port *get_port_from_line(int line, bool console)
int max_alias_num = of_alias_get_highest_id("serial");
if (line < 0 || line >= nr_ports)
- line = ida_alloc_range(&port_ida, max_alias_num + 1, nr_ports, GFP_KERNEL);
+ line = ida_alloc_range(&port_ida, max_alias_num + 1,
+ nr_ports - 1, GFP_KERNEL);
else
- line = ida_alloc_range(&port_ida, line, nr_ports, GFP_KERNEL);
+ line = ida_alloc_range(&port_ida, line,
+ nr_ports - 1, GFP_KERNEL);
if (line < 0)
return ERR_PTR(-ENXIO);
- port = &qcom_geni_uart_ports[line];
+ port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return ERR_PTR(-ENOMEM);
+
+ port->uport.iotype = UPIO_MEM;
+ port->uport.ops = &qcom_geni_uart_pops;
+ port->uport.flags = UPF_BOOT_AUTOCONF;
+ port->uport.line = line;
}
return port;
}
@@ -554,7 +538,7 @@ static void qcom_geni_serial_console_write(struct console *co, const char *s,
WARN_ON(co->index < 0 || co->index >= GENI_UART_CONS_PORTS);
- port = get_port_from_line(co->index, true);
+ port = get_port_from_line(co->index, true, NULL);
if (IS_ERR(port))
return;
@@ -1267,75 +1251,15 @@ static int qcom_geni_serial_startup(struct uart_port *uport)
return 0;
}
-static unsigned long find_clk_rate_in_tol(struct clk *clk, unsigned int desired_clk,
- unsigned int *clk_div, unsigned int percent_tol)
-{
- unsigned long freq;
- unsigned long div, maxdiv;
- u64 mult;
- unsigned long offset, abs_tol, achieved;
-
- abs_tol = div_u64((u64)desired_clk * percent_tol, 100);
- maxdiv = CLK_DIV_MSK >> CLK_DIV_SHFT;
- div = 1;
- while (div <= maxdiv) {
- mult = (u64)div * desired_clk;
- if (mult != (unsigned long)mult)
- break;
-
- offset = div * abs_tol;
- freq = clk_round_rate(clk, mult - offset);
-
- /* Can only get lower if we're done */
- if (freq < mult - offset)
- break;
-
- /*
- * Re-calculate div in case rounding skipped rates but we
- * ended up at a good one, then check for a match.
- */
- div = DIV_ROUND_CLOSEST(freq, desired_clk);
- achieved = DIV_ROUND_CLOSEST(freq, div);
- if (achieved <= desired_clk + abs_tol &&
- achieved >= desired_clk - abs_tol) {
- *clk_div = div;
- return freq;
- }
-
- div = DIV_ROUND_UP(freq, desired_clk);
- }
-
- return 0;
-}
-
-static unsigned long get_clk_div_rate(struct clk *clk, unsigned int baud,
- unsigned int sampling_rate, unsigned int *clk_div)
-{
- unsigned long ser_clk;
- unsigned long desired_clk;
-
- desired_clk = baud * sampling_rate;
- if (!desired_clk)
- return 0;
-
- /*
- * try to find a clock rate within 2% tolerance, then within 5%
- */
- ser_clk = find_clk_rate_in_tol(clk, desired_clk, clk_div, 2);
- if (!ser_clk)
- ser_clk = find_clk_rate_in_tol(clk, desired_clk, clk_div, 5);
-
- return ser_clk;
-}
-
static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud)
{
struct qcom_geni_serial_port *port = to_dev_port(uport);
unsigned long clk_rate;
- unsigned int avg_bw_core;
+ unsigned int avg_bw_core, clk_idx;
unsigned int clk_div;
u32 ver, sampling_rate;
u32 ser_clk_cfg;
+ int ret;
sampling_rate = UART_OVERSAMPLING;
/* Sampling rate is halved for IP versions >= 2.5 */
@@ -1343,17 +1267,22 @@ static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud)
if (ver >= QUP_SE_VERSION_2_5)
sampling_rate /= 2;
- clk_rate = get_clk_div_rate(port->se.clk, baud,
- sampling_rate, &clk_div);
- if (!clk_rate) {
- dev_err(port->se.dev,
- "Couldn't find suitable clock rate for %u\n",
- baud * sampling_rate);
+ ret = geni_se_clk_freq_match(&port->se, baud * sampling_rate, &clk_idx, &clk_rate, false);
+ if (ret) {
+ dev_err(port->se.dev, "Failed to find src clk for baud rate: %d ret: %d\n",
+ baud, ret);
+ return ret;
+ }
+
+ clk_div = DIV_ROUND_UP(clk_rate, baud * sampling_rate);
+ /* Check if calculated divider exceeds maximum allowed value */
+ if (clk_div > (CLK_DIV_MSK >> CLK_DIV_SHFT)) {
+ dev_err(port->se.dev, "Calculated clock divider %u exceeds maximum\n", clk_div);
return -EINVAL;
}
- dev_dbg(port->se.dev, "desired_rate = %u, clk_rate = %lu, clk_div = %u\n",
- baud * sampling_rate, clk_rate, clk_div);
+ dev_dbg(port->se.dev, "desired_rate = %u, clk_rate = %lu, clk_div = %u\n, clk_idx = %u\n",
+ baud * sampling_rate, clk_rate, clk_div, clk_idx);
uport->uartclk = clk_rate;
port->clk_rate = clk_rate;
@@ -1373,6 +1302,8 @@ static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud)
writel(ser_clk_cfg, uport->membase + GENI_SER_M_CLK_CFG);
writel(ser_clk_cfg, uport->membase + GENI_SER_S_CLK_CFG);
+ /* Configure clock selection register with the selected clock index */
+ writel(clk_idx & CLK_SEL_MSK, uport->membase + SE_GENI_CLK_SEL);
return 0;
}
@@ -1517,7 +1448,7 @@ static int qcom_geni_console_setup(struct console *co, char *options)
if (co->index >= GENI_UART_CONS_PORTS || co->index < 0)
return -ENXIO;
- port = get_port_from_line(co->index, true);
+ port = get_port_from_line(co->index, true, NULL);
if (IS_ERR(port)) {
pr_err("Invalid line %d\n", co->index);
return PTR_ERR(port);
@@ -1678,7 +1609,7 @@ static struct uart_driver qcom_geni_uart_driver = {
.owner = THIS_MODULE,
.driver_name = "qcom_geni_uart",
.dev_name = "ttyHS",
- .nr = GENI_UART_PORTS,
+ .nr = CONFIG_SERIAL_QCOM_GENI_UART_PORTS,
};
static int geni_serial_resources_on(struct uart_port *uport)
@@ -1872,7 +1803,7 @@ static int qcom_geni_serial_probe(struct platform_device *pdev)
line = of_alias_get_id(pdev->dev.of_node, "hsuart");
}
- port = get_port_from_line(line, data->console);
+ port = get_port_from_line(line, data->console, &pdev->dev);
if (IS_ERR(port)) {
dev_err(&pdev->dev, "Invalid line %d\n", line);
return PTR_ERR(port);
diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c
index a668e0bb26b3..1a2c4c14f6aa 100644
--- a/drivers/tty/serial/sc16is7xx.c
+++ b/drivers/tty/serial/sc16is7xx.c
@@ -626,7 +626,7 @@ static void sc16is7xx_handle_rx(struct uart_port *port, unsigned int rxlen,
{
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
unsigned int lsr = 0, bytes_read, i;
- bool read_lsr = (iir == SC16IS7XX_IIR_RLSE_SRC) ? true : false;
+ bool read_lsr = (iir == SC16IS7XX_IIR_RLSE_SRC);
u8 ch, flag;
if (unlikely(rxlen >= sizeof(one->buf))) {
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 86d404d649a3..4757293ece8c 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -177,15 +177,13 @@ static void uart_start(struct tty_struct *tty)
static void
uart_update_mctrl(struct uart_port *port, unsigned int set, unsigned int clear)
{
- unsigned long flags;
unsigned int old;
- uart_port_lock_irqsave(port, &flags);
+ guard(uart_port_lock_irqsave)(port);
old = port->mctrl;
port->mctrl = (old & ~clear) | set;
if (old != port->mctrl && !(port->rs485.flags & SER_RS485_ENABLED))
port->ops->set_mctrl(port, port->mctrl);
- uart_port_unlock_irqrestore(port, flags);
}
#define uart_set_mctrl(port, set) uart_update_mctrl(port, set, 0)
@@ -220,7 +218,7 @@ static void uart_change_line_settings(struct tty_struct *tty, struct uart_state
/*
* Set modem status enables based on termios cflag
*/
- uart_port_lock_irq(uport);
+ guard(uart_port_lock_irq)(uport);
if (termios->c_cflag & CRTSCTS)
uport->status |= UPSTAT_CTS_ENABLE;
else
@@ -241,7 +239,6 @@ static void uart_change_line_settings(struct tty_struct *tty, struct uart_state
else
__uart_start(state);
}
- uart_port_unlock_irq(uport);
}
static int uart_alloc_xmit_buf(struct tty_port *port)
@@ -711,7 +708,6 @@ static void uart_send_xchar(struct tty_struct *tty, u8 ch)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port;
- unsigned long flags;
port = uart_port_ref(state);
if (!port)
@@ -720,11 +716,10 @@ static void uart_send_xchar(struct tty_struct *tty, u8 ch)
if (port->ops->send_xchar)
port->ops->send_xchar(port, ch);
else {
- uart_port_lock_irqsave(port, &flags);
+ guard(uart_port_lock_irqsave)(port);
port->x_char = ch;
if (ch)
port->ops->start_tx(port);
- uart_port_unlock_irqrestore(port, flags);
}
uart_port_deref(port);
}
@@ -1089,7 +1084,6 @@ static int uart_tiocmget(struct tty_struct *tty)
struct uart_state *state = tty->driver_data;
struct tty_port *port = &state->port;
struct uart_port *uport;
- int result;
guard(mutex)(&port->mutex);
@@ -1097,12 +1091,9 @@ static int uart_tiocmget(struct tty_struct *tty)
if (!uport || tty_io_error(tty))
return -EIO;
- uart_port_lock_irq(uport);
- result = uport->mctrl;
- result |= uport->ops->get_mctrl(uport);
- uart_port_unlock_irq(uport);
+ guard(uart_port_lock_irq)(uport);
- return result;
+ return uport->mctrl | uport->ops->get_mctrl(uport);
}
static int
@@ -1226,16 +1217,15 @@ static int uart_wait_modem_status(struct uart_state *state, unsigned long arg)
uport = uart_port_ref(state);
if (!uport)
return -EIO;
- uart_port_lock_irq(uport);
- memcpy(&cprev, &uport->icount, sizeof(struct uart_icount));
- uart_enable_ms(uport);
- uart_port_unlock_irq(uport);
+ scoped_guard(uart_port_lock_irq, uport) {
+ memcpy(&cprev, &uport->icount, sizeof(struct uart_icount));
+ uart_enable_ms(uport);
+ }
add_wait_queue(&port->delta_msr_wait, &wait);
for (;;) {
- uart_port_lock_irq(uport);
- memcpy(&cnow, &uport->icount, sizeof(struct uart_icount));
- uart_port_unlock_irq(uport);
+ scoped_guard(uart_port_lock_irq, uport)
+ memcpy(&cnow, &uport->icount, sizeof(struct uart_icount));
set_current_state(TASK_INTERRUPTIBLE);
@@ -1430,7 +1420,6 @@ static void uart_set_rs485_rx_during_tx(struct uart_port *port,
static int uart_rs485_config(struct uart_port *port)
{
struct serial_rs485 *rs485 = &port->rs485;
- unsigned long flags;
int ret;
if (!(rs485->flags & SER_RS485_ENABLED))
@@ -1440,9 +1429,8 @@ static int uart_rs485_config(struct uart_port *port)
uart_set_rs485_termination(port, rs485);
uart_set_rs485_rx_during_tx(port, rs485);
- uart_port_lock_irqsave(port, &flags);
- ret = port->rs485_config(port, NULL, rs485);
- uart_port_unlock_irqrestore(port, flags);
+ scoped_guard(uart_port_lock_irqsave, port)
+ ret = port->rs485_config(port, NULL, rs485);
if (ret) {
memset(rs485, 0, sizeof(*rs485));
/* unset GPIOs */
@@ -1456,12 +1444,10 @@ static int uart_rs485_config(struct uart_port *port)
static int uart_get_rs485_config(struct uart_port *port,
struct serial_rs485 __user *rs485)
{
- unsigned long flags;
struct serial_rs485 aux;
- uart_port_lock_irqsave(port, &flags);
- aux = port->rs485;
- uart_port_unlock_irqrestore(port, flags);
+ scoped_guard(uart_port_lock_irqsave, port)
+ aux = port->rs485;
if (copy_to_user(rs485, &aux, sizeof(aux)))
return -EFAULT;
@@ -1474,7 +1460,6 @@ static int uart_set_rs485_config(struct tty_struct *tty, struct uart_port *port,
{
struct serial_rs485 rs485;
int ret;
- unsigned long flags;
if (!(port->rs485_supported.flags & SER_RS485_ENABLED))
return -ENOTTY;
@@ -1489,16 +1474,16 @@ static int uart_set_rs485_config(struct tty_struct *tty, struct uart_port *port,
uart_set_rs485_termination(port, &rs485);
uart_set_rs485_rx_during_tx(port, &rs485);
- uart_port_lock_irqsave(port, &flags);
- ret = port->rs485_config(port, &tty->termios, &rs485);
- if (!ret) {
- port->rs485 = rs485;
+ scoped_guard(uart_port_lock_irqsave, port) {
+ ret = port->rs485_config(port, &tty->termios, &rs485);
+ if (!ret) {
+ port->rs485 = rs485;
- /* Reset RTS and other mctrl lines when disabling RS485 */
- if (!(rs485.flags & SER_RS485_ENABLED))
- port->ops->set_mctrl(port, port->mctrl);
+ /* Reset RTS and other mctrl lines when disabling RS485 */
+ if (!(rs485.flags & SER_RS485_ENABLED))
+ port->ops->set_mctrl(port, port->mctrl);
+ }
}
- uart_port_unlock_irqrestore(port, flags);
if (ret) {
/* restore old GPIO settings */
gpiod_set_value_cansleep(port->rs485_term_gpio,
@@ -1517,15 +1502,13 @@ static int uart_set_rs485_config(struct tty_struct *tty, struct uart_port *port,
static int uart_get_iso7816_config(struct uart_port *port,
struct serial_iso7816 __user *iso7816)
{
- unsigned long flags;
struct serial_iso7816 aux;
if (!port->iso7816_config)
return -ENOTTY;
- uart_port_lock_irqsave(port, &flags);
- aux = port->iso7816;
- uart_port_unlock_irqrestore(port, flags);
+ scoped_guard(uart_port_lock_irqsave, port)
+ aux = port->iso7816;
if (copy_to_user(iso7816, &aux, sizeof(aux)))
return -EFAULT;
@@ -1537,8 +1520,7 @@ static int uart_set_iso7816_config(struct uart_port *port,
struct serial_iso7816 __user *iso7816_user)
{
struct serial_iso7816 iso7816;
- int i, ret;
- unsigned long flags;
+ int i;
if (!port->iso7816_config)
return -ENOTTY;
@@ -1554,11 +1536,11 @@ static int uart_set_iso7816_config(struct uart_port *port,
if (iso7816.reserved[i])
return -EINVAL;
- uart_port_lock_irqsave(port, &flags);
- ret = port->iso7816_config(port, &iso7816);
- uart_port_unlock_irqrestore(port, flags);
- if (ret)
- return ret;
+ scoped_guard(uart_port_lock_irqsave, port) {
+ int ret = port->iso7816_config(port, &iso7816);
+ if (ret)
+ return ret;
+ }
if (copy_to_user(iso7816_user, &port->iso7816, sizeof(port->iso7816)))
return -EFAULT;
@@ -1770,9 +1752,8 @@ static void uart_tty_port_shutdown(struct tty_port *port)
if (WARN(!uport, "detached port still initialized!\n"))
return;
- uart_port_lock_irq(uport);
- uport->ops->stop_rx(uport);
- uart_port_unlock_irq(uport);
+ scoped_guard(uart_port_lock_irq, uport)
+ uport->ops->stop_rx(uport);
serial_base_port_shutdown(uport);
uart_port_shutdown(port);
@@ -2044,9 +2025,8 @@ static void uart_line_info(struct seq_file *m, struct uart_state *state)
pm_state = state->pm_state;
if (pm_state != UART_PM_STATE_ON)
uart_change_pm(state, UART_PM_STATE_ON);
- uart_port_lock_irq(uport);
- status = uport->ops->get_mctrl(uport);
- uart_port_unlock_irq(uport);
+ scoped_guard(uart_port_lock_irq, uport)
+ status = uport->ops->get_mctrl(uport);
if (pm_state != UART_PM_STATE_ON)
uart_change_pm(state, pm_state);
@@ -2355,9 +2335,8 @@ int uart_suspend_port(struct uart_driver *drv, struct uart_port *uport)
*/
if (!console_suspend_enabled && uart_console(uport)) {
if (uport->ops->start_rx) {
- uart_port_lock_irq(uport);
+ guard(uart_port_lock_irq)(uport);
uport->ops->stop_rx(uport);
- uart_port_unlock_irq(uport);
}
device_set_awake_path(uport->dev);
return 0;
@@ -2373,15 +2352,15 @@ int uart_suspend_port(struct uart_driver *drv, struct uart_port *uport)
tty_port_set_suspended(port, true);
tty_port_set_initialized(port, false);
- uart_port_lock_irq(uport);
- ops->stop_tx(uport);
- if (!(uport->rs485.flags & SER_RS485_ENABLED))
- ops->set_mctrl(uport, 0);
- /* save mctrl so it can be restored on resume */
- mctrl = uport->mctrl;
- uport->mctrl = 0;
- ops->stop_rx(uport);
- uart_port_unlock_irq(uport);
+ scoped_guard(uart_port_lock_irq, uport) {
+ ops->stop_tx(uport);
+ if (!(uport->rs485.flags & SER_RS485_ENABLED))
+ ops->set_mctrl(uport, 0);
+ /* save mctrl so it can be restored on resume */
+ mctrl = uport->mctrl;
+ uport->mctrl = 0;
+ ops->stop_rx(uport);
+ }
/*
* Wait for the transmitter to empty.
@@ -2450,9 +2429,8 @@ int uart_resume_port(struct uart_driver *drv, struct uart_port *uport)
uart_change_pm(state, UART_PM_STATE_ON);
uport->ops->set_termios(uport, &termios, NULL);
if (!console_suspend_enabled && uport->ops->start_rx) {
- uart_port_lock_irq(uport);
+ guard(uart_port_lock_irq)(uport);
uport->ops->start_rx(uport);
- uart_port_unlock_irq(uport);
}
if (console_suspend_enabled)
console_resume(uport->cons);
@@ -2463,10 +2441,9 @@ int uart_resume_port(struct uart_driver *drv, struct uart_port *uport)
int ret;
uart_change_pm(state, UART_PM_STATE_ON);
- uart_port_lock_irq(uport);
- if (!(uport->rs485.flags & SER_RS485_ENABLED))
- ops->set_mctrl(uport, 0);
- uart_port_unlock_irq(uport);
+ scoped_guard(uart_port_lock_irq, uport)
+ if (!(uport->rs485.flags & SER_RS485_ENABLED))
+ ops->set_mctrl(uport, 0);
if (console_suspend_enabled || !uart_console(uport)) {
/* Protected by port mutex for now */
struct tty_struct *tty = port->tty;
@@ -2476,11 +2453,11 @@ int uart_resume_port(struct uart_driver *drv, struct uart_port *uport)
if (tty)
uart_change_line_settings(tty, state, NULL);
uart_rs485_config(uport);
- uart_port_lock_irq(uport);
- if (!(uport->rs485.flags & SER_RS485_ENABLED))
- ops->set_mctrl(uport, uport->mctrl);
- ops->start_tx(uport);
- uart_port_unlock_irq(uport);
+ scoped_guard(uart_port_lock_irq, uport) {
+ if (!(uport->rs485.flags & SER_RS485_ENABLED))
+ ops->set_mctrl(uport, uport->mctrl);
+ ops->start_tx(uport);
+ }
tty_port_set_initialized(port, true);
} else {
/*
@@ -2574,8 +2551,6 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state,
}
if (port->type != PORT_UNKNOWN) {
- unsigned long flags;
-
uart_report_port(drv, port);
/* Synchronize with possible boot console. */
@@ -2590,11 +2565,11 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state,
* keep the DTR setting that is set in uart_set_options()
* We probably don't need a spinlock around this, but
*/
- uart_port_lock_irqsave(port, &flags);
- port->mctrl &= TIOCM_DTR;
- if (!(port->rs485.flags & SER_RS485_ENABLED))
- port->ops->set_mctrl(port, port->mctrl);
- uart_port_unlock_irqrestore(port, flags);
+ scoped_guard(uart_port_lock_irqsave, port) {
+ port->mctrl &= TIOCM_DTR;
+ if (!(port->rs485.flags & SER_RS485_ENABLED))
+ port->ops->set_mctrl(port, port->mctrl);
+ }
uart_rs485_config(port);
diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c
index 97f8a9a52285..1f78b0db3b25 100644
--- a/drivers/tty/sysrq.c
+++ b/drivers/tty/sysrq.c
@@ -1133,8 +1133,7 @@ static int sysrq_sysctl_handler(const struct ctl_table *table, int write,
* Behaves like do_proc_dointvec as t does not have min nor max.
*/
ret = proc_dointvec_minmax(&t, write, buffer, lenp, ppos);
-
- if (ret || !write)
+ if (ret)
return ret;
if (write)
diff --git a/drivers/tty/tty_port.c b/drivers/tty/tty_port.c
index 5b4d5fb99a59..fe67c5cb0a3f 100644
--- a/drivers/tty/tty_port.c
+++ b/drivers/tty/tty_port.c
@@ -63,12 +63,8 @@ static void tty_port_default_lookahead_buf(struct tty_port *port, const u8 *p,
static void tty_port_default_wakeup(struct tty_port *port)
{
- struct tty_struct *tty = tty_port_tty_get(port);
-
- if (tty) {
- tty_wakeup(tty);
- tty_kref_put(tty);
- }
+ scoped_guard(tty_port_tty, port)
+ tty_wakeup(scoped_tty());
}
const struct tty_port_client_operations tty_port_default_client_ops = {
@@ -225,26 +221,27 @@ EXPORT_SYMBOL_GPL(tty_port_unregister_device);
int tty_port_alloc_xmit_buf(struct tty_port *port)
{
/* We may sleep in get_zeroed_page() */
- mutex_lock(&port->buf_mutex);
- if (port->xmit_buf == NULL) {
- port->xmit_buf = (u8 *)get_zeroed_page(GFP_KERNEL);
- if (port->xmit_buf)
- kfifo_init(&port->xmit_fifo, port->xmit_buf, PAGE_SIZE);
- }
- mutex_unlock(&port->buf_mutex);
+ guard(mutex)(&port->buf_mutex);
+
+ if (port->xmit_buf)
+ return 0;
+
+ port->xmit_buf = (u8 *)get_zeroed_page(GFP_KERNEL);
if (port->xmit_buf == NULL)
return -ENOMEM;
+
+ kfifo_init(&port->xmit_fifo, port->xmit_buf, PAGE_SIZE);
+
return 0;
}
EXPORT_SYMBOL(tty_port_alloc_xmit_buf);
void tty_port_free_xmit_buf(struct tty_port *port)
{
- mutex_lock(&port->buf_mutex);
+ guard(mutex)(&port->buf_mutex);
free_page((unsigned long)port->xmit_buf);
port->xmit_buf = NULL;
INIT_KFIFO(port->xmit_fifo);
- mutex_unlock(&port->buf_mutex);
}
EXPORT_SYMBOL(tty_port_free_xmit_buf);
@@ -301,13 +298,8 @@ EXPORT_SYMBOL(tty_port_put);
*/
struct tty_struct *tty_port_tty_get(struct tty_port *port)
{
- unsigned long flags;
- struct tty_struct *tty;
-
- spin_lock_irqsave(&port->lock, flags);
- tty = tty_kref_get(port->tty);
- spin_unlock_irqrestore(&port->lock, flags);
- return tty;
+ guard(spinlock_irqsave)(&port->lock);
+ return tty_kref_get(port->tty);
}
EXPORT_SYMBOL(tty_port_tty_get);
@@ -321,12 +313,9 @@ EXPORT_SYMBOL(tty_port_tty_get);
*/
void tty_port_tty_set(struct tty_port *port, struct tty_struct *tty)
{
- unsigned long flags;
-
- spin_lock_irqsave(&port->lock, flags);
+ guard(spinlock_irqsave)(&port->lock);
tty_kref_put(port->tty);
port->tty = tty_kref_get(tty);
- spin_unlock_irqrestore(&port->lock, flags);
}
EXPORT_SYMBOL(tty_port_tty_set);
@@ -342,24 +331,24 @@ EXPORT_SYMBOL(tty_port_tty_set);
*/
static void tty_port_shutdown(struct tty_port *port, struct tty_struct *tty)
{
- mutex_lock(&port->mutex);
+ guard(mutex)(&port->mutex);
+
if (port->console)
- goto out;
+ return;
- if (tty_port_initialized(port)) {
- tty_port_set_initialized(port, false);
- /*
- * Drop DTR/RTS if HUPCL is set. This causes any attached
- * modem to hang up the line.
- */
- if (tty && C_HUPCL(tty))
- tty_port_lower_dtr_rts(port);
+ if (!tty_port_initialized(port))
+ return;
- if (port->ops->shutdown)
- port->ops->shutdown(port);
- }
-out:
- mutex_unlock(&port->mutex);
+ tty_port_set_initialized(port, false);
+ /*
+ * Drop DTR/RTS if HUPCL is set. This causes any attached
+ * modem to hang up the line.
+ */
+ if (tty && C_HUPCL(tty))
+ tty_port_lower_dtr_rts(port);
+
+ if (port->ops->shutdown)
+ port->ops->shutdown(port);
}
/**
@@ -374,15 +363,15 @@ out:
void tty_port_hangup(struct tty_port *port)
{
struct tty_struct *tty;
- unsigned long flags;
- spin_lock_irqsave(&port->lock, flags);
- port->count = 0;
- tty = port->tty;
- if (tty)
- set_bit(TTY_IO_ERROR, &tty->flags);
- port->tty = NULL;
- spin_unlock_irqrestore(&port->lock, flags);
+ scoped_guard(spinlock_irqsave, &port->lock) {
+ port->count = 0;
+ tty = port->tty;
+ if (tty)
+ set_bit(TTY_IO_ERROR, &tty->flags);
+ port->tty = NULL;
+ }
+
tty_port_set_active(port, false);
tty_port_shutdown(port, tty);
tty_kref_put(tty);
@@ -393,15 +382,16 @@ EXPORT_SYMBOL(tty_port_hangup);
void __tty_port_tty_hangup(struct tty_port *port, bool check_clocal, bool async)
{
- struct tty_struct *tty = tty_port_tty_get(port);
+ scoped_guard(tty_port_tty, port) {
+ struct tty_struct *tty = scoped_tty();
- if (tty && (!check_clocal || !C_CLOCAL(tty))) {
- if (async)
- tty_hangup(tty);
- else
- tty_vhangup(tty);
+ if (!check_clocal || !C_CLOCAL(tty)) {
+ if (async)
+ tty_hangup(tty);
+ else
+ tty_vhangup(tty);
+ }
}
- tty_kref_put(tty);
}
EXPORT_SYMBOL_GPL(__tty_port_tty_hangup);
@@ -490,7 +480,6 @@ int tty_port_block_til_ready(struct tty_port *port,
struct tty_struct *tty, struct file *filp)
{
int do_clocal = 0, retval;
- unsigned long flags;
DEFINE_WAIT(wait);
/* if non-blocking mode is set we can pass directly to open unless
@@ -519,10 +508,10 @@ int tty_port_block_til_ready(struct tty_port *port,
retval = 0;
/* The port lock protects the port counts */
- spin_lock_irqsave(&port->lock, flags);
- port->count--;
- port->blocked_open++;
- spin_unlock_irqrestore(&port->lock, flags);
+ scoped_guard(spinlock_irqsave, &port->lock) {
+ port->count--;
+ port->blocked_open++;
+ }
while (1) {
/* Indicate we are open */
@@ -561,11 +550,11 @@ int tty_port_block_til_ready(struct tty_port *port,
/* Update counts. A parallel hangup will have set count to zero and
* we must not mess that up further.
*/
- spin_lock_irqsave(&port->lock, flags);
- if (!tty_hung_up_p(filp))
- port->count++;
- port->blocked_open--;
- spin_unlock_irqrestore(&port->lock, flags);
+ scoped_guard(spinlock_irqsave, &port->lock) {
+ if (!tty_hung_up_p(filp))
+ port->count++;
+ port->blocked_open--;
+ }
if (retval == 0)
tty_port_set_active(port, true);
return retval;
@@ -604,28 +593,24 @@ static void tty_port_drain_delay(struct tty_port *port, struct tty_struct *tty)
int tty_port_close_start(struct tty_port *port,
struct tty_struct *tty, struct file *filp)
{
- unsigned long flags;
-
if (tty_hung_up_p(filp))
return 0;
- spin_lock_irqsave(&port->lock, flags);
- if (tty->count == 1 && port->count != 1) {
- tty_warn(tty, "%s: tty->count = 1 port count = %d\n", __func__,
- port->count);
- port->count = 1;
- }
- if (--port->count < 0) {
- tty_warn(tty, "%s: bad port count (%d)\n", __func__,
- port->count);
- port->count = 0;
- }
+ scoped_guard(spinlock_irqsave, &port->lock) {
+ if (tty->count == 1 && port->count != 1) {
+ tty_warn(tty, "%s: tty->count = 1 port count = %d\n", __func__,
+ port->count);
+ port->count = 1;
+ }
+ if (--port->count < 0) {
+ tty_warn(tty, "%s: bad port count (%d)\n", __func__,
+ port->count);
+ port->count = 0;
+ }
- if (port->count) {
- spin_unlock_irqrestore(&port->lock, flags);
- return 0;
+ if (port->count)
+ return 0;
}
- spin_unlock_irqrestore(&port->lock, flags);
tty->closing = 1;
@@ -744,9 +729,8 @@ EXPORT_SYMBOL_GPL(tty_port_install);
int tty_port_open(struct tty_port *port, struct tty_struct *tty,
struct file *filp)
{
- spin_lock_irq(&port->lock);
- ++port->count;
- spin_unlock_irq(&port->lock);
+ scoped_guard(spinlock_irq, &port->lock)
+ ++port->count;
tty_port_tty_set(port, tty);
/*
@@ -755,21 +739,17 @@ int tty_port_open(struct tty_port *port, struct tty_struct *tty,
* port mutex.
*/
- mutex_lock(&port->mutex);
-
- if (!tty_port_initialized(port)) {
+ scoped_guard(mutex, &port->mutex) {
+ if (tty_port_initialized(port))
+ break;
clear_bit(TTY_IO_ERROR, &tty->flags);
if (port->ops->activate) {
int retval = port->ops->activate(port, tty);
-
- if (retval) {
- mutex_unlock(&port->mutex);
+ if (retval)
return retval;
- }
}
tty_port_set_initialized(port, true);
}
- mutex_unlock(&port->mutex);
return tty_port_block_til_ready(port, tty, filp);
}
EXPORT_SYMBOL(tty_port_open);
diff --git a/drivers/tty/vt/consolemap.c b/drivers/tty/vt/consolemap.c
index bb4bb272ebec..7a11c3f2e875 100644
--- a/drivers/tty/vt/consolemap.c
+++ b/drivers/tty/vt/consolemap.c
@@ -361,10 +361,10 @@ int con_set_trans_old(unsigned char __user * arg)
inbuf[i] = UNI_DIRECT_BASE | ch;
}
- console_lock();
+ guard(console_lock)();
memcpy(translations[USER_MAP], inbuf, sizeof(inbuf));
update_user_maps();
- console_unlock();
+
return 0;
}
@@ -374,13 +374,11 @@ int con_get_trans_old(unsigned char __user * arg)
unsigned short *p = translations[USER_MAP];
unsigned char outbuf[E_TABSZ];
- console_lock();
- for (i = 0; i < ARRAY_SIZE(outbuf); i++)
- {
- ch = conv_uni_to_pc(vc_cons[fg_console].d, p[i]);
- outbuf[i] = (ch & ~0xff) ? 0 : ch;
- }
- console_unlock();
+ scoped_guard(console_lock)
+ for (i = 0; i < ARRAY_SIZE(outbuf); i++) {
+ ch = conv_uni_to_pc(vc_cons[fg_console].d, p[i]);
+ outbuf[i] = (ch & ~0xff) ? 0 : ch;
+ }
return copy_to_user(arg, outbuf, sizeof(outbuf)) ? -EFAULT : 0;
}
@@ -392,10 +390,10 @@ int con_set_trans_new(ushort __user * arg)
if (copy_from_user(inbuf, arg, sizeof(inbuf)))
return -EFAULT;
- console_lock();
+ guard(console_lock)();
memcpy(translations[USER_MAP], inbuf, sizeof(inbuf));
update_user_maps();
- console_unlock();
+
return 0;
}
@@ -403,9 +401,8 @@ int con_get_trans_new(ushort __user * arg)
{
unsigned short outbuf[E_TABSZ];
- console_lock();
- memcpy(outbuf, translations[USER_MAP], sizeof(outbuf));
- console_unlock();
+ scoped_guard(console_lock)
+ memcpy(outbuf, translations[USER_MAP], sizeof(outbuf));
return copy_to_user(arg, outbuf, sizeof(outbuf)) ? -EFAULT : 0;
}
@@ -571,11 +568,8 @@ static int con_do_clear_unimap(struct vc_data *vc)
int con_clear_unimap(struct vc_data *vc)
{
- int ret;
- console_lock();
- ret = con_do_clear_unimap(vc);
- console_unlock();
- return ret;
+ guard(console_lock)();
+ return con_do_clear_unimap(vc);
}
static struct uni_pagedict *con_unshare_unimap(struct vc_data *vc,
@@ -637,32 +631,28 @@ static struct uni_pagedict *con_unshare_unimap(struct vc_data *vc,
int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list)
{
- int err = 0, err1;
struct uni_pagedict *dict;
- struct unipair *unilist, *plist;
+ struct unipair *plist;
+ int err = 0;
if (!ct)
return 0;
- unilist = vmemdup_array_user(list, ct, sizeof(*unilist));
+ struct unipair *unilist __free(kvfree) = vmemdup_array_user(list, ct, sizeof(*unilist));
if (IS_ERR(unilist))
return PTR_ERR(unilist);
- console_lock();
+ guard(console_lock)();
/* Save original vc_unipagdir_loc in case we allocate a new one */
dict = *vc->uni_pagedict_loc;
- if (!dict) {
- err = -EINVAL;
- goto out_unlock;
- }
+ if (!dict)
+ return -EINVAL;
if (dict->refcount > 1) {
dict = con_unshare_unimap(vc, dict);
- if (IS_ERR(dict)) {
- err = PTR_ERR(dict);
- goto out_unlock;
- }
+ if (IS_ERR(dict))
+ return PTR_ERR(dict);
} else if (dict == dflt) {
dflt = NULL;
}
@@ -671,7 +661,7 @@ int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list)
* Insert user specified unicode pairs into new table.
*/
for (plist = unilist; ct; ct--, plist++) {
- err1 = con_insert_unipair(dict, plist->unicode, plist->fontpos);
+ int err1 = con_insert_unipair(dict, plist->unicode, plist->fontpos);
if (err1)
err = err1;
}
@@ -680,15 +670,12 @@ int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list)
* Merge with fontmaps of any other virtual consoles.
*/
if (con_unify_unimap(vc, dict))
- goto out_unlock;
+ return err;
for (enum translation_map m = FIRST_MAP; m <= LAST_MAP; m++)
set_inverse_transl(vc, dict, m);
set_inverse_trans_unicode(dict);
-out_unlock:
- console_unlock();
- kvfree(unilist);
return err;
}
@@ -787,50 +774,49 @@ int con_get_unimap(struct vc_data *vc, ushort ct, ushort __user *uct,
{
ushort ect;
struct uni_pagedict *dict;
- struct unipair *unilist;
unsigned int d, r, g;
- int ret = 0;
- unilist = kvmalloc_array(ct, sizeof(*unilist), GFP_KERNEL);
+ struct unipair *unilist __free(kvfree) = kvmalloc_array(ct, sizeof(*unilist), GFP_KERNEL);
if (!unilist)
return -ENOMEM;
- console_lock();
-
- ect = 0;
- dict = *vc->uni_pagedict_loc;
- if (!dict)
- goto unlock;
-
- for (d = 0; d < UNI_DIRS; d++) {
- u16 **dir = dict->uni_pgdir[d];
- if (!dir)
- continue;
+ scoped_guard(console_lock) {
+ ect = 0;
+ dict = *vc->uni_pagedict_loc;
+ if (!dict)
+ break;
- for (r = 0; r < UNI_DIR_ROWS; r++) {
- u16 *row = dir[r];
- if (!row)
+ for (d = 0; d < UNI_DIRS; d++) {
+ u16 **dir = dict->uni_pgdir[d];
+ if (!dir)
continue;
- for (g = 0; g < UNI_ROW_GLYPHS; g++, row++) {
- if (*row >= MAX_GLYPH)
+ for (r = 0; r < UNI_DIR_ROWS; r++) {
+ u16 *row = dir[r];
+ if (!row)
continue;
- if (ect < ct) {
- unilist[ect].unicode = UNI(d, r, g);
- unilist[ect].fontpos = *row;
+
+ for (g = 0; g < UNI_ROW_GLYPHS; g++, row++) {
+ if (*row >= MAX_GLYPH)
+ continue;
+ if (ect < ct) {
+ unilist[ect].unicode = UNI(d, r, g);
+ unilist[ect].fontpos = *row;
+ }
+ ect++;
}
- ect++;
}
}
}
-unlock:
- console_unlock();
+
if (copy_to_user(list, unilist, min(ect, ct) * sizeof(*unilist)))
- ret = -EFAULT;
+ return -EFAULT;
if (put_user(ect, uct))
- ret = -EFAULT;
- kvfree(unilist);
- return ret ? ret : (ect <= ct) ? 0 : -ENOMEM;
+ return -EFAULT;
+ if (ect > ct)
+ return -ENOMEM;
+
+ return 0;
}
/*
diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c
index 24b0a53e5a79..07d3b93975d3 100644
--- a/drivers/tty/vt/selection.c
+++ b/drivers/tty/vt/selection.c
@@ -127,9 +127,8 @@ int sel_loadlut(u32 __user *lut)
if (copy_from_user(tmplut, lut, sizeof(inwordLut)))
return -EFAULT;
- console_lock();
+ guard(console_lock)();
memcpy(inwordLut, tmplut, sizeof(inwordLut));
- console_unlock();
return 0;
}
@@ -375,15 +374,9 @@ static int vc_selection(struct vc_data *vc, struct tiocl_selection *v,
int set_selection_kernel(struct tiocl_selection *v, struct tty_struct *tty)
{
- int ret;
-
- mutex_lock(&vc_sel.lock);
- console_lock();
- ret = vc_selection(vc_cons[fg_console].d, v, tty);
- console_unlock();
- mutex_unlock(&vc_sel.lock);
-
- return ret;
+ guard(mutex)(&vc_sel.lock);
+ guard(console_lock)();
+ return vc_selection(vc_cons[fg_console].d, v, tty);
}
EXPORT_SYMBOL_GPL(set_selection_kernel);
@@ -409,9 +402,8 @@ int paste_selection(struct tty_struct *tty)
const char *bps = bp ? bracketed_paste_start : NULL;
const char *bpe = bp ? bracketed_paste_end : NULL;
- console_lock();
- poke_blanked_console();
- console_unlock();
+ scoped_guard(console_lock)
+ poke_blanked_console();
ld = tty_ldisc_ref_wait(tty);
if (!ld)
diff --git a/drivers/tty/vt/vc_screen.c b/drivers/tty/vt/vc_screen.c
index 79b33d998d43..c814644ef4ee 100644
--- a/drivers/tty/vt/vc_screen.c
+++ b/drivers/tty/vt/vc_screen.c
@@ -53,6 +53,8 @@
#define HEADER_SIZE 4u
#define CON_BUF_SIZE (IS_ENABLED(CONFIG_BASE_SMALL) ? 256 : PAGE_SIZE)
+DEFINE_FREE(free_page_ptr, void *, if (_T) free_page((unsigned long)_T));
+
/*
* Our minor space:
*
@@ -72,7 +74,6 @@
#define use_unicode(inode) (iminor(inode) & 64)
#define use_attributes(inode) (iminor(inode) & 128)
-
struct vcs_poll_data {
struct notifier_block notifier;
unsigned int cons_num;
@@ -231,15 +232,13 @@ static loff_t vcs_lseek(struct file *file, loff_t offset, int orig)
struct vc_data *vc;
int size;
- console_lock();
- vc = vcs_vc(inode, NULL);
- if (!vc) {
- console_unlock();
- return -ENXIO;
- }
+ scoped_guard(console_lock) {
+ vc = vcs_vc(inode, NULL);
+ if (!vc)
+ return -ENXIO;
- size = vcs_size(vc, use_attributes(inode), use_unicode(inode));
- console_unlock();
+ size = vcs_size(vc, use_attributes(inode), use_unicode(inode));
+ }
if (size < 0)
return size;
return fixed_size_llseek(file, offset, orig, size);
@@ -369,11 +368,10 @@ vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
struct vcs_poll_data *poll;
unsigned int read;
ssize_t ret;
- char *con_buf;
loff_t pos;
bool viewed, attr, uni_mode;
- con_buf = (char *) __get_free_page(GFP_KERNEL);
+ char *con_buf __free(free_page_ptr) = (char *)__get_free_page(GFP_KERNEL);
if (!con_buf)
return -ENOMEM;
@@ -382,17 +380,16 @@ vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
/* Select the proper current console and verify
* sanity of the situation under the console lock.
*/
- console_lock();
+ guard(console_lock)();
uni_mode = use_unicode(inode);
attr = use_attributes(inode);
- ret = -EINVAL;
if (pos < 0)
- goto unlock_out;
+ return -EINVAL;
/* we enforce 32-bit alignment for pos and count in unicode mode */
if (uni_mode && (pos | count) & 3)
- goto unlock_out;
+ return -EINVAL;
poll = file->private_data;
if (count && poll)
@@ -468,10 +465,8 @@ vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
}
*ppos += read;
if (read)
- ret = read;
-unlock_out:
- console_unlock();
- free_page((unsigned long) con_buf);
+ return read;
+
return ret;
}
@@ -591,7 +586,6 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
struct inode *inode = file_inode(file);
struct vc_data *vc;
- char *con_buf;
u16 *org0, *org;
unsigned int written;
int size;
@@ -602,7 +596,7 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
if (use_unicode(inode))
return -EOPNOTSUPP;
- con_buf = (char *) __get_free_page(GFP_KERNEL);
+ char *con_buf __free(free_page_ptr) = (char *)__get_free_page(GFP_KERNEL);
if (!con_buf)
return -ENOMEM;
@@ -611,22 +605,18 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
/* Select the proper current console and verify
* sanity of the situation under the console lock.
*/
- console_lock();
+ guard(console_lock)();
attr = use_attributes(inode);
- ret = -ENXIO;
vc = vcs_vc(inode, &viewed);
if (!vc)
- goto unlock_out;
+ return -ENXIO;
size = vcs_size(vc, attr, false);
- if (size < 0) {
- ret = size;
- goto unlock_out;
- }
- ret = -EINVAL;
+ if (size < 0)
+ return size;
if (pos < 0 || pos > size)
- goto unlock_out;
+ return -EINVAL;
if (count > size - pos)
count = size - pos;
written = 0;
@@ -651,8 +641,7 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
*/
if (written)
break;
- ret = -EFAULT;
- goto unlock_out;
+ return -EFAULT;
}
}
@@ -664,15 +653,13 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
if (!vc) {
if (written)
break;
- ret = -ENXIO;
- goto unlock_out;
+ return -ENXIO;
}
size = vcs_size(vc, attr, false);
if (size < 0) {
if (written)
break;
- ret = size;
- goto unlock_out;
+ return size;
}
if (pos >= size)
break;
@@ -702,9 +689,6 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
if (written)
vcs_scr_updated(vc);
-unlock_out:
- console_unlock();
- free_page((unsigned long) con_buf);
return ret;
}
@@ -754,17 +738,17 @@ vcs_open(struct inode *inode, struct file *filp)
unsigned int currcons = console(inode);
bool attr = use_attributes(inode);
bool uni_mode = use_unicode(inode);
- int ret = 0;
/* we currently don't support attributes in unicode mode */
if (attr && uni_mode)
return -EOPNOTSUPP;
- console_lock();
- if(currcons && !vc_cons_allocated(currcons-1))
- ret = -ENXIO;
- console_unlock();
- return ret;
+ guard(console_lock)();
+
+ if (currcons && !vc_cons_allocated(currcons - 1))
+ return -ENXIO;
+
+ return 0;
}
static int vcs_release(struct inode *inode, struct file *file)
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 62049ceb34de..6e0089b85c27 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -141,6 +141,7 @@ static const struct consw *con_driver_map[MAX_NR_CONSOLES];
static int con_open(struct tty_struct *, struct file *);
static void vc_init(struct vc_data *vc, int do_clear);
static void gotoxy(struct vc_data *vc, int new_x, int new_y);
+static void restore_cur(struct vc_data *vc);
static void save_cur(struct vc_data *vc);
static void reset_terminal(struct vc_data *vc, int do_clear);
static void con_flush_chars(struct tty_struct *tty);
@@ -1317,12 +1318,9 @@ EXPORT_SYMBOL(__vc_resize);
static int vt_resize(struct tty_struct *tty, struct winsize *ws)
{
struct vc_data *vc = tty->driver_data;
- int ret;
- console_lock();
- ret = vc_do_resize(tty, vc, ws->ws_col, ws->ws_row, false);
- console_unlock();
- return ret;
+ guard(console_lock)();
+ return vc_do_resize(tty, vc, ws->ws_col, ws->ws_row, false);
}
struct vc_data *vc_deallocate(unsigned int currcons)
@@ -1343,6 +1341,10 @@ struct vc_data *vc_deallocate(unsigned int currcons)
vc_uniscr_set(vc, NULL);
kfree(vc->vc_screenbuf);
vc_cons[currcons].d = NULL;
+ if (vc->vc_saved_screen != NULL) {
+ kfree(vc->vc_saved_screen);
+ vc->vc_saved_screen = NULL;
+ }
}
return vc;
}
@@ -1878,6 +1880,45 @@ static int get_bracketed_paste(struct tty_struct *tty)
return vc->vc_bracketed_paste;
}
+/* console_lock is held */
+static void enter_alt_screen(struct vc_data *vc)
+{
+ unsigned int size = vc->vc_rows * vc->vc_cols * 2;
+
+ if (vc->vc_saved_screen != NULL)
+ return; /* Already inside an alt-screen */
+ vc->vc_saved_screen = kmemdup((u16 *)vc->vc_origin, size, GFP_KERNEL);
+ if (vc->vc_saved_screen == NULL)
+ return;
+ vc->vc_saved_rows = vc->vc_rows;
+ vc->vc_saved_cols = vc->vc_cols;
+ save_cur(vc);
+ /* clear entire screen */
+ csi_J(vc, CSI_J_FULL);
+}
+
+/* console_lock is held */
+static void leave_alt_screen(struct vc_data *vc)
+{
+ unsigned int rows = min(vc->vc_saved_rows, vc->vc_rows);
+ unsigned int cols = min(vc->vc_saved_cols, vc->vc_cols);
+ u16 *src, *dest;
+
+ if (vc->vc_saved_screen == NULL)
+ return; /* Not inside an alt-screen */
+ for (unsigned int r = 0; r < rows; r++) {
+ src = vc->vc_saved_screen + r * vc->vc_saved_cols;
+ dest = ((u16 *)vc->vc_origin) + r * vc->vc_cols;
+ memcpy(dest, src, 2 * cols);
+ }
+ restore_cur(vc);
+ /* Update the entire screen */
+ if (con_should_update(vc))
+ do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2);
+ kfree(vc->vc_saved_screen);
+ vc->vc_saved_screen = NULL;
+}
+
enum {
CSI_DEC_hl_CURSOR_KEYS = 1, /* CKM: cursor keys send ^[Ox/^[[x */
CSI_DEC_hl_132_COLUMNS = 3, /* COLM: 80/132 mode switch */
@@ -1888,6 +1929,7 @@ enum {
CSI_DEC_hl_MOUSE_X10 = 9,
CSI_DEC_hl_SHOW_CURSOR = 25, /* TCEM */
CSI_DEC_hl_MOUSE_VT200 = 1000,
+ CSI_DEC_hl_ALT_SCREEN = 1049,
CSI_DEC_hl_BRACKETED_PASTE = 2004,
};
@@ -1944,6 +1986,12 @@ static void csi_DEC_hl(struct vc_data *vc, bool on_off)
case CSI_DEC_hl_BRACKETED_PASTE:
vc->vc_bracketed_paste = on_off;
break;
+ case CSI_DEC_hl_ALT_SCREEN:
+ if (on_off)
+ enter_alt_screen(vc);
+ else
+ leave_alt_screen(vc);
+ break;
}
}
@@ -2182,6 +2230,13 @@ static void reset_terminal(struct vc_data *vc, int do_clear)
vc->vc_deccm = global_cursor_default;
vc->vc_decim = 0;
+ if (vc->vc_saved_screen != NULL) {
+ kfree(vc->vc_saved_screen);
+ vc->vc_saved_screen = NULL;
+ vc->vc_saved_rows = 0;
+ vc->vc_saved_cols = 0;
+ }
+
vt_reset_keyboard(vc->vc_num);
vc->vc_cursor_type = cur_default;
@@ -3135,12 +3190,11 @@ static int do_con_write(struct tty_struct *tty, const u8 *buf, int count)
if (in_interrupt())
return count;
- console_lock();
+ guard(console_lock)();
currcons = vc->vc_num;
if (!vc_cons_allocated(currcons)) {
/* could this happen? */
pr_warn_once("con_write: tty %d not allocated\n", currcons+1);
- console_unlock();
return 0;
}
@@ -3184,7 +3238,7 @@ rescan_last_byte:
con_flush(vc, &draw);
console_conditional_schedule();
notify_update(vc);
- console_unlock();
+
return n;
}
@@ -3199,7 +3253,7 @@ rescan_last_byte:
*/
static void console_callback(struct work_struct *ignored)
{
- console_lock();
+ guard(console_lock)();
if (want_console >= 0) {
if (want_console != fg_console &&
@@ -3228,8 +3282,6 @@ static void console_callback(struct work_struct *ignored)
blank_timer_expired = 0;
}
notify_update(vc_cons[fg_console].d);
-
- console_unlock();
}
int set_console(int nr)
@@ -3433,9 +3485,8 @@ int tioclinux(struct tty_struct *tty, unsigned long arg)
return -EPERM;
return paste_selection(tty);
case TIOCL_UNBLANKSCREEN:
- console_lock();
- unblank_screen();
- console_unlock();
+ scoped_guard(console_lock)
+ unblank_screen();
break;
case TIOCL_SELLOADLUT:
if (!capable(CAP_SYS_ADMIN))
@@ -3451,9 +3502,8 @@ int tioclinux(struct tty_struct *tty, unsigned long arg)
data = vt_get_shift_state();
return put_user(data, p);
case TIOCL_GETMOUSEREPORTING:
- console_lock(); /* May be overkill */
- data = mouse_reporting();
- console_unlock();
+ scoped_guard(console_lock) /* May be overkill */
+ data = mouse_reporting();
return put_user(data, p);
case TIOCL_SETVESABLANK:
return set_vesa_blanking(param);
@@ -3484,15 +3534,14 @@ int tioclinux(struct tty_struct *tty, unsigned long arg)
* Needs the console lock here. Note that lots of other calls
* need fixing before the lock is actually useful!
*/
- console_lock();
- scrollfront(vc_cons[fg_console].d, lines);
- console_unlock();
+ scoped_guard(console_lock)
+ scrollfront(vc_cons[fg_console].d, lines);
break;
case TIOCL_BLANKSCREEN: /* until explicitly unblanked, not only poked */
- console_lock();
- ignore_poke = 1;
- do_blank_screen(0);
- console_unlock();
+ scoped_guard(console_lock) {
+ ignore_poke = 1;
+ do_blank_screen(0);
+ }
break;
case TIOCL_BLANKEDSCREEN:
return console_blanked;
@@ -3582,9 +3631,8 @@ static void con_flush_chars(struct tty_struct *tty)
if (in_interrupt()) /* from flush_to_ldisc */
return;
- console_lock();
+ guard(console_lock)();
set_cursor(vc);
- console_unlock();
}
/*
@@ -3596,22 +3644,20 @@ static int con_install(struct tty_driver *driver, struct tty_struct *tty)
struct vc_data *vc;
int ret;
- console_lock();
+ guard(console_lock)();
ret = vc_allocate(currcons);
if (ret)
- goto unlock;
+ return ret;
vc = vc_cons[currcons].d;
/* Still being freed */
- if (vc->port.tty) {
- ret = -ERESTARTSYS;
- goto unlock;
- }
+ if (vc->port.tty)
+ return -ERESTARTSYS;
ret = tty_port_install(&vc->port, driver, tty);
if (ret)
- goto unlock;
+ return ret;
tty->driver_data = vc;
vc->port.tty = tty;
@@ -3625,9 +3671,8 @@ static int con_install(struct tty_driver *driver, struct tty_struct *tty)
tty->termios.c_iflag |= IUTF8;
else
tty->termios.c_iflag &= ~IUTF8;
-unlock:
- console_unlock();
- return ret;
+
+ return 0;
}
static int con_open(struct tty_struct *tty, struct file *filp)
@@ -3646,9 +3691,9 @@ static void con_shutdown(struct tty_struct *tty)
{
struct vc_data *vc = tty->driver_data;
BUG_ON(vc == NULL);
- console_lock();
+
+ guard(console_lock)();
vc->port.tty = NULL;
- console_unlock();
}
static void con_cleanup(struct tty_struct *tty)
@@ -4137,15 +4182,13 @@ static ssize_t store_bind(struct device *dev, struct device_attribute *attr,
struct con_driver *con = dev_get_drvdata(dev);
int bind = simple_strtoul(buf, NULL, 0);
- console_lock();
+ guard(console_lock)();
if (bind)
vt_bind(con);
else
vt_unbind(con);
- console_unlock();
-
return count;
}
@@ -4155,9 +4198,8 @@ static ssize_t show_bind(struct device *dev, struct device_attribute *attr,
struct con_driver *con = dev_get_drvdata(dev);
int bind;
- console_lock();
- bind = con_is_bound(con->con);
- console_unlock();
+ scoped_guard(console_lock)
+ bind = con_is_bound(con->con);
return sysfs_emit(buf, "%i\n", bind);
}
@@ -4429,7 +4471,7 @@ static void con_driver_unregister_callback(struct work_struct *ignored)
{
int i;
- console_lock();
+ guard(console_lock)();
for (i = 0; i < MAX_NR_CON_DRIVER; i++) {
struct con_driver *con_driver = &registered_con_driver[i];
@@ -4454,8 +4496,6 @@ static void con_driver_unregister_callback(struct work_struct *ignored)
con_driver->first = 0;
con_driver->last = 0;
}
-
- console_unlock();
}
/*
@@ -4491,9 +4531,8 @@ EXPORT_SYMBOL_GPL(do_take_over_console);
*/
void give_up_console(const struct consw *csw)
{
- console_lock();
+ guard(console_lock)();
do_unregister_con_driver(csw);
- console_unlock();
}
EXPORT_SYMBOL(give_up_console);
@@ -4541,9 +4580,8 @@ static int set_vesa_blanking(u8 __user *mode_user)
if (get_user(mode, mode_user))
return -EFAULT;
- console_lock();
+ guard(console_lock)();
vesa_blank_mode = (mode <= VESA_BLANK_MAX) ? mode : VESA_NO_BLANKING;
- console_unlock();
return 0;
}
@@ -4729,7 +4767,7 @@ int con_set_cmap(unsigned char __user *arg)
if (copy_from_user(colormap, arg, sizeof(colormap)))
return -EFAULT;
- console_lock();
+ guard(console_lock)();
for (i = k = 0; i < 16; i++) {
default_red[i] = colormap[k++];
default_grn[i] = colormap[k++];
@@ -4745,7 +4783,6 @@ int con_set_cmap(unsigned char __user *arg)
}
set_palette(vc_cons[i].d);
}
- console_unlock();
return 0;
}
@@ -4755,13 +4792,12 @@ int con_get_cmap(unsigned char __user *arg)
int i, k;
unsigned char colormap[3*16];
- console_lock();
- for (i = k = 0; i < 16; i++) {
- colormap[k++] = default_red[i];
- colormap[k++] = default_grn[i];
- colormap[k++] = default_blu[i];
- }
- console_unlock();
+ scoped_guard(console_lock)
+ for (i = k = 0; i < 16; i++) {
+ colormap[k++] = default_red[i];
+ colormap[k++] = default_grn[i];
+ colormap[k++] = default_blu[i];
+ }
if (copy_to_user(arg, colormap, sizeof(colormap)))
return -EFAULT;
@@ -4801,62 +4837,54 @@ void reset_palette(struct vc_data *vc)
static int con_font_get(struct vc_data *vc, struct console_font_op *op)
{
struct console_font font;
- int rc = -EINVAL;
int c;
unsigned int vpitch = op->op == KD_FONT_OP_GET_TALL ? op->height : 32;
if (vpitch > max_font_height)
return -EINVAL;
+ void *font_data __free(kvfree) = NULL;
if (op->data) {
- font.data = kvzalloc(max_font_size, GFP_KERNEL);
+ font.data = font_data = kvzalloc(max_font_size, GFP_KERNEL);
if (!font.data)
return -ENOMEM;
} else
font.data = NULL;
- console_lock();
- if (vc->vc_mode != KD_TEXT)
- rc = -EINVAL;
- else if (vc->vc_sw->con_font_get)
- rc = vc->vc_sw->con_font_get(vc, &font, vpitch);
- else
- rc = -ENOSYS;
- console_unlock();
+ scoped_guard(console_lock) {
+ if (vc->vc_mode != KD_TEXT)
+ return -EINVAL;
+ if (!vc->vc_sw->con_font_get)
+ return -ENOSYS;
- if (rc)
- goto out;
+ int ret = vc->vc_sw->con_font_get(vc, &font, vpitch);
+ if (ret)
+ return ret;
+ }
c = (font.width+7)/8 * vpitch * font.charcount;
if (op->data && font.charcount > op->charcount)
- rc = -ENOSPC;
+ return -ENOSPC;
if (font.width > op->width || font.height > op->height)
- rc = -ENOSPC;
- if (rc)
- goto out;
+ return -ENOSPC;
op->height = font.height;
op->width = font.width;
op->charcount = font.charcount;
if (op->data && copy_to_user(op->data, font.data, c))
- rc = -EFAULT;
+ return -EFAULT;
-out:
- kvfree(font.data);
- return rc;
+ return 0;
}
static int con_font_set(struct vc_data *vc, const struct console_font_op *op)
{
struct console_font font;
- int rc = -EINVAL;
int size;
unsigned int vpitch = op->op == KD_FONT_OP_SET_TALL ? op->height : 32;
- if (vc->vc_mode != KD_TEXT)
- return -EINVAL;
if (!op->data)
return -EINVAL;
if (op->charcount > max_font_glyphs)
@@ -4870,7 +4898,7 @@ static int con_font_set(struct vc_data *vc, const struct console_font_op *op)
if (size > max_font_size)
return -ENOSPC;
- font.data = memdup_user(op->data, size);
+ void *font_data __free(kfree) = font.data = memdup_user(op->data, size);
if (IS_ERR(font.data))
return PTR_ERR(font.data);
@@ -4878,18 +4906,17 @@ static int con_font_set(struct vc_data *vc, const struct console_font_op *op)
font.width = op->width;
font.height = op->height;
- console_lock();
+ guard(console_lock)();
+
if (vc->vc_mode != KD_TEXT)
- rc = -EINVAL;
- else if (vc->vc_sw->con_font_set) {
- if (vc_is_sel(vc))
- clear_selection();
- rc = vc->vc_sw->con_font_set(vc, &font, vpitch, op->flags);
- } else
- rc = -ENOSYS;
- console_unlock();
- kfree(font.data);
- return rc;
+ return -EINVAL;
+ if (!vc->vc_sw->con_font_set)
+ return -ENOSYS;
+
+ if (vc_is_sel(vc))
+ clear_selection();
+
+ return vc->vc_sw->con_font_set(vc, &font, vpitch, op->flags);
}
static int con_font_default(struct vc_data *vc, struct console_font_op *op)
@@ -4897,8 +4924,6 @@ static int con_font_default(struct vc_data *vc, struct console_font_op *op)
struct console_font font = {.width = op->width, .height = op->height};
char name[MAX_FONT_NAME];
char *s = name;
- int rc;
-
if (!op->data)
s = NULL;
@@ -4907,23 +4932,23 @@ static int con_font_default(struct vc_data *vc, struct console_font_op *op)
else
name[MAX_FONT_NAME - 1] = 0;
- console_lock();
- if (vc->vc_mode != KD_TEXT) {
- console_unlock();
- return -EINVAL;
- }
- if (vc->vc_sw->con_font_default) {
+ scoped_guard(console_lock) {
+ if (vc->vc_mode != KD_TEXT)
+ return -EINVAL;
+ if (!vc->vc_sw->con_font_default)
+ return -ENOSYS;
+
if (vc_is_sel(vc))
clear_selection();
- rc = vc->vc_sw->con_font_default(vc, &font, s);
- } else
- rc = -ENOSYS;
- console_unlock();
- if (!rc) {
- op->width = font.width;
- op->height = font.height;
+ int ret = vc->vc_sw->con_font_default(vc, &font, s);
+ if (ret)
+ return ret;
}
- return rc;
+
+ op->width = font.width;
+ op->height = font.height;
+
+ return 0;
}
int con_font_op(struct vc_data *vc, struct console_font_op *op)
diff --git a/drivers/tty/vt/vt_ioctl.c b/drivers/tty/vt/vt_ioctl.c
index 61342e06970a..28993a3d0acb 100644
--- a/drivers/tty/vt/vt_ioctl.c
+++ b/drivers/tty/vt/vt_ioctl.c
@@ -373,15 +373,13 @@ static int vt_k_ioctl(struct tty_struct *tty, unsigned int cmd,
break;
}
- case KDSETMODE:
+ case KDSETMODE: {
if (!perm)
return -EPERM;
- console_lock();
- ret = vt_kdsetmode(vc, arg);
- console_unlock();
- return ret;
-
+ guard(console_lock)();
+ return vt_kdsetmode(vc, arg);
+ }
case KDGETMODE:
return put_user(vc->vc_mode, (int __user *)arg);
@@ -601,23 +599,21 @@ static int vt_setactivate(struct vt_setactivate __user *sa)
vsa.console--;
vsa.console = array_index_nospec(vsa.console, MAX_NR_CONSOLES);
- console_lock();
- ret = vc_allocate(vsa.console);
- if (ret) {
- console_unlock();
- return ret;
- }
+ scoped_guard(console_lock) {
+ ret = vc_allocate(vsa.console);
+ if (ret)
+ return ret;
- /*
- * This is safe providing we don't drop the console sem between
- * vc_allocate and finishing referencing nvc.
- */
- nvc = vc_cons[vsa.console].d;
- nvc->vt_mode = vsa.mode;
- nvc->vt_mode.frsig = 0;
- put_pid(nvc->vt_pid);
- nvc->vt_pid = get_pid(task_pid(current));
- console_unlock();
+ /*
+ * This is safe providing we don't drop the console sem between
+ * vc_allocate and finishing referencing nvc.
+ */
+ nvc = vc_cons[vsa.console].d;
+ nvc->vt_mode = vsa.mode;
+ nvc->vt_mode.frsig = 0;
+ put_pid(nvc->vt_pid);
+ nvc->vt_pid = get_pid(task_pid(current));
+ }
/* Commence switch and lock */
/* Review set_console locks */
@@ -630,19 +626,18 @@ static int vt_setactivate(struct vt_setactivate __user *sa)
static int vt_disallocate(unsigned int vc_num)
{
struct vc_data *vc = NULL;
- int ret = 0;
- console_lock();
- if (vt_busy(vc_num))
- ret = -EBUSY;
- else if (vc_num)
- vc = vc_deallocate(vc_num);
- console_unlock();
+ scoped_guard(console_lock) {
+ if (vt_busy(vc_num))
+ return -EBUSY;
+ if (vc_num)
+ vc = vc_deallocate(vc_num);
+ }
if (vc && vc_num >= MIN_NR_CONSOLES)
tty_port_put(&vc->port);
- return ret;
+ return 0;
}
/* deallocate all unused consoles, but leave 0 */
@@ -651,13 +646,12 @@ static void vt_disallocate_all(void)
struct vc_data *vc[MAX_NR_CONSOLES];
int i;
- console_lock();
- for (i = 1; i < MAX_NR_CONSOLES; i++)
- if (!vt_busy(i))
- vc[i] = vc_deallocate(i);
- else
- vc[i] = NULL;
- console_unlock();
+ scoped_guard(console_lock)
+ for (i = 1; i < MAX_NR_CONSOLES; i++)
+ if (!vt_busy(i))
+ vc[i] = vc_deallocate(i);
+ else
+ vc[i] = NULL;
for (i = 1; i < MAX_NR_CONSOLES; i++) {
if (vc[i] && i >= MIN_NR_CONSOLES)
@@ -703,7 +697,7 @@ static int vt_resizex(struct vc_data *vc, struct vt_consize __user *cs)
if (!vc_cons[i].d)
continue;
- console_lock();
+ guard(console_lock)();
vcp = vc_cons[i].d;
if (vcp) {
int ret;
@@ -718,11 +712,9 @@ static int vt_resizex(struct vc_data *vc, struct vt_consize __user *cs)
if (ret) {
vcp->vc_scan_lines = save_scan_lines;
vcp->vc_cell_height = save_cell_height;
- console_unlock();
return ret;
}
}
- console_unlock();
}
return 0;
@@ -770,7 +762,7 @@ int vt_ioctl(struct tty_struct *tty,
if (tmp.mode != VT_AUTO && tmp.mode != VT_PROCESS)
return -EINVAL;
- console_lock();
+ guard(console_lock)();
vc->vt_mode = tmp;
/* the frsig is ignored, so we set it to 0 */
vc->vt_mode.frsig = 0;
@@ -778,7 +770,6 @@ int vt_ioctl(struct tty_struct *tty,
vc->vt_pid = get_pid(task_pid(current));
/* no switch is required -- saw@shade.msu.ru */
vc->vt_newvt = -1;
- console_unlock();
break;
}
@@ -787,9 +778,8 @@ int vt_ioctl(struct tty_struct *tty,
struct vt_mode tmp;
int rc;
- console_lock();
- memcpy(&tmp, &vc->vt_mode, sizeof(struct vt_mode));
- console_unlock();
+ scoped_guard(console_lock)
+ memcpy(&tmp, &vc->vt_mode, sizeof(struct vt_mode));
rc = copy_to_user(up, &tmp, sizeof(struct vt_mode));
if (rc)
@@ -811,12 +801,10 @@ int vt_ioctl(struct tty_struct *tty,
return -EFAULT;
state = 1; /* /dev/tty0 is always open */
- console_lock(); /* required by vt_in_use() */
- for (i = 0, mask = 2; i < MAX_NR_CONSOLES && mask;
- ++i, mask <<= 1)
- if (vt_in_use(i))
- state |= mask;
- console_unlock();
+ scoped_guard(console_lock) /* required by vt_in_use() */
+ for (i = 0, mask = 2; i < MAX_NR_CONSOLES && mask; ++i, mask <<= 1)
+ if (vt_in_use(i))
+ state |= mask;
return put_user(state, &vtstat->v_state);
}
@@ -824,11 +812,10 @@ int vt_ioctl(struct tty_struct *tty,
* Returns the first available (non-opened) console.
*/
case VT_OPENQRY:
- console_lock(); /* required by vt_in_use() */
- for (i = 0; i < MAX_NR_CONSOLES; ++i)
- if (!vt_in_use(i))
- break;
- console_unlock();
+ scoped_guard(console_lock) /* required by vt_in_use() */
+ for (i = 0; i < MAX_NR_CONSOLES; ++i)
+ if (!vt_in_use(i))
+ break;
i = i < MAX_NR_CONSOLES ? (i+1) : -1;
return put_user(i, (int __user *)arg);
@@ -845,11 +832,11 @@ int vt_ioctl(struct tty_struct *tty,
arg--;
arg = array_index_nospec(arg, MAX_NR_CONSOLES);
- console_lock();
- ret = vc_allocate(arg);
- console_unlock();
- if (ret)
- return ret;
+ scoped_guard(console_lock) {
+ ret = vc_allocate(arg);
+ if (ret)
+ return ret;
+ }
set_console(arg);
break;
@@ -880,15 +867,13 @@ int vt_ioctl(struct tty_struct *tty,
* 2: completed switch-to OK
*/
case VT_RELDISP:
+ {
if (!perm)
return -EPERM;
- console_lock();
- ret = vt_reldisp(vc, arg);
- console_unlock();
-
- return ret;
-
+ guard(console_lock)();
+ return vt_reldisp(vc, arg);
+ }
/*
* Disallocate memory associated to VT (but leave VT1)
@@ -917,16 +902,17 @@ int vt_ioctl(struct tty_struct *tty,
get_user(cc, &vtsizes->v_cols))
return -EFAULT;
- console_lock();
+ guard(console_lock)();
for (i = 0; i < MAX_NR_CONSOLES; i++) {
vc = vc_cons[i].d;
if (vc) {
/* FIXME: review v tty lock */
- __vc_resize(vc_cons[i].d, cc, ll, true);
+ ret = __vc_resize(vc_cons[i].d, cc, ll, true);
+ if (ret)
+ return ret;
}
}
- console_unlock();
break;
}
@@ -996,20 +982,17 @@ void vc_SAK(struct work_struct *work)
struct vc_data *vc;
struct tty_struct *tty;
- console_lock();
+ guard(console_lock)();
vc = vc_con->d;
- if (vc) {
- /* FIXME: review tty ref counting */
- tty = vc->port.tty;
- /*
- * SAK should also work in all raw modes and reset
- * them properly.
- */
- if (tty)
- __do_SAK(tty);
- reset_vc(vc);
- }
- console_unlock();
+ if (!vc)
+ return;
+
+ /* FIXME: review tty ref counting */
+ tty = vc->port.tty;
+ /* SAK should also work in all raw modes and reset them properly. */
+ if (tty)
+ __do_SAK(tty);
+ reset_vc(vc);
}
#ifdef CONFIG_COMPAT
@@ -1287,31 +1270,29 @@ int vt_move_to_console(unsigned int vt, int alloc)
{
int prev;
- console_lock();
- /* Graphics mode - up to X */
- if (disable_vt_switch) {
- console_unlock();
- return 0;
- }
- prev = fg_console;
+ scoped_guard(console_lock) {
+ /* Graphics mode - up to X */
+ if (disable_vt_switch)
+ return 0;
- if (alloc && vc_allocate(vt)) {
- /* we can't have a free VC for now. Too bad,
- * we don't want to mess the screen for now. */
- console_unlock();
- return -ENOSPC;
- }
+ prev = fg_console;
- if (set_console(vt)) {
- /*
- * We're unable to switch to the SUSPEND_CONSOLE.
- * Let the calling function know so it can decide
- * what to do.
- */
- console_unlock();
- return -EIO;
+ if (alloc && vc_allocate(vt)) {
+ /*
+ * We can't have a free VC for now. Too bad, we don't want to mess the
+ * screen for now.
+ */
+ return -ENOSPC;
+ }
+
+ if (set_console(vt)) {
+ /*
+ * We're unable to switch to the SUSPEND_CONSOLE. Let the calling function
+ * know so it can decide what to do.
+ */
+ return -EIO;
+ }
}
- console_unlock();
if (vt_waitactive(vt + 1)) {
pr_debug("Suspend: Can't switch VCs.");
return -EINTR;
@@ -1328,8 +1309,7 @@ int vt_move_to_console(unsigned int vt, int alloc)
*/
void pm_set_vt_switch(int do_switch)
{
- console_lock();
+ guard(console_lock)();
disable_vt_switch = !do_switch;
- console_unlock();
}
EXPORT_SYMBOL(pm_set_vt_switch);
diff --git a/drivers/uio/uio_aec.c b/drivers/uio/uio_aec.c
index 8c164e51ff9e..dafcc5f44f24 100644
--- a/drivers/uio/uio_aec.c
+++ b/drivers/uio/uio_aec.c
@@ -33,7 +33,7 @@
#define MAILBOX 0x0F
-static struct pci_device_id ids[] = {
+static const struct pci_device_id ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_AEC, PCI_DEVICE_ID_AEC_VITCLTC), },
{ 0, }
};
diff --git a/drivers/uio/uio_cif.c b/drivers/uio/uio_cif.c
index 1cc3b8b5a345..4e4b589ddef1 100644
--- a/drivers/uio/uio_cif.c
+++ b/drivers/uio/uio_cif.c
@@ -105,7 +105,7 @@ static void hilscher_pci_remove(struct pci_dev *dev)
iounmap(info->mem[0].internal_addr);
}
-static struct pci_device_id hilscher_pci_ids[] = {
+static const struct pci_device_id hilscher_pci_ids[] = {
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
diff --git a/drivers/uio/uio_dmem_genirq.c b/drivers/uio/uio_dmem_genirq.c
index 31aa75110ba5..41c18ec62a45 100644
--- a/drivers/uio/uio_dmem_genirq.c
+++ b/drivers/uio/uio_dmem_genirq.c
@@ -297,28 +297,6 @@ static int uio_dmem_genirq_probe(struct platform_device *pdev)
return devm_uio_register_device(&pdev->dev, priv->uioinfo);
}
-static int uio_dmem_genirq_runtime_nop(struct device *dev)
-{
- /* Runtime PM callback shared between ->runtime_suspend()
- * and ->runtime_resume(). Simply returns success.
- *
- * In this driver pm_runtime_get_sync() and pm_runtime_put_sync()
- * are used at open() and release() time. This allows the
- * Runtime PM code to turn off power to the device while the
- * device is unused, ie before open() and after release().
- *
- * This Runtime PM callback does not need to save or restore
- * any registers since user space is responsbile for hardware
- * register reinitialization after open().
- */
- return 0;
-}
-
-static const struct dev_pm_ops uio_dmem_genirq_dev_pm_ops = {
- .runtime_suspend = uio_dmem_genirq_runtime_nop,
- .runtime_resume = uio_dmem_genirq_runtime_nop,
-};
-
#ifdef CONFIG_OF
static const struct of_device_id uio_of_genirq_match[] = {
{ /* empty for now */ },
@@ -330,7 +308,6 @@ static struct platform_driver uio_dmem_genirq = {
.probe = uio_dmem_genirq_probe,
.driver = {
.name = DRIVER_NAME,
- .pm = &uio_dmem_genirq_dev_pm_ops,
.of_match_table = of_match_ptr(uio_of_genirq_match),
},
};
diff --git a/drivers/uio/uio_hv_generic.c b/drivers/uio/uio_hv_generic.c
index f19efad4d6f8..3f8e2e27697f 100644
--- a/drivers/uio/uio_hv_generic.c
+++ b/drivers/uio/uio_hv_generic.c
@@ -111,7 +111,6 @@ static void hv_uio_channel_cb(void *context)
struct hv_device *hv_dev;
struct hv_uio_private_data *pdata;
- chan->inbound.ring_buffer->interrupt_mask = 1;
virt_mb();
/*
@@ -183,8 +182,6 @@ hv_uio_new_channel(struct vmbus_channel *new_sc)
return;
}
- /* Disable interrupts on sub channel */
- new_sc->inbound.ring_buffer->interrupt_mask = 1;
set_channel_read_mode(new_sc, HV_CALL_ISR);
ret = hv_create_ring_sysfs(new_sc, hv_uio_ring_mmap);
if (ret) {
@@ -227,9 +224,7 @@ hv_uio_open(struct uio_info *info, struct inode *inode)
ret = vmbus_connect_ring(dev->channel,
hv_uio_channel_cb, dev->channel);
- if (ret == 0)
- dev->channel->inbound.ring_buffer->interrupt_mask = 1;
- else
+ if (ret)
atomic_dec(&pdata->refcnt);
return ret;
diff --git a/drivers/uio/uio_netx.c b/drivers/uio/uio_netx.c
index a1a58802c793..18917b2ac04c 100644
--- a/drivers/uio/uio_netx.c
+++ b/drivers/uio/uio_netx.c
@@ -127,7 +127,7 @@ static void netx_pci_remove(struct pci_dev *dev)
iounmap(info->mem[0].internal_addr);
}
-static struct pci_device_id netx_pci_ids[] = {
+static const struct pci_device_id netx_pci_ids[] = {
{
.vendor = PCI_VENDOR_ID_HILSCHER,
.device = PCI_DEVICE_ID_HILSCHER_NETX,
diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c
index 2ec7d25e8264..0a1885d1b2e3 100644
--- a/drivers/uio/uio_pdrv_genirq.c
+++ b/drivers/uio/uio_pdrv_genirq.c
@@ -249,34 +249,11 @@ static int uio_pdrv_genirq_probe(struct platform_device *pdev)
return ret;
}
-static int uio_pdrv_genirq_runtime_nop(struct device *dev)
-{
- /* Runtime PM callback shared between ->runtime_suspend()
- * and ->runtime_resume(). Simply returns success.
- *
- * In this driver pm_runtime_get_sync() and pm_runtime_put_sync()
- * are used at open() and release() time. This allows the
- * Runtime PM code to turn off power to the device while the
- * device is unused, ie before open() and after release().
- *
- * This Runtime PM callback does not need to save or restore
- * any registers since user space is responsbile for hardware
- * register reinitialization after open().
- */
- return 0;
-}
-
-static const struct dev_pm_ops uio_pdrv_genirq_dev_pm_ops = {
- .runtime_suspend = uio_pdrv_genirq_runtime_nop,
- .runtime_resume = uio_pdrv_genirq_runtime_nop,
-};
-
#ifdef CONFIG_OF
static struct of_device_id uio_of_genirq_match[] = {
{ /* This is filled with module_parm */ },
{ /* Sentinel */ },
};
-MODULE_DEVICE_TABLE(of, uio_of_genirq_match);
module_param_string(of_id, uio_of_genirq_match[0].compatible, 128, 0);
MODULE_PARM_DESC(of_id, "Openfirmware id of the device to be handled by uio");
#endif
@@ -285,7 +262,6 @@ static struct platform_driver uio_pdrv_genirq = {
.probe = uio_pdrv_genirq_probe,
.driver = {
.name = DRIVER_NAME,
- .pm = &uio_pdrv_genirq_dev_pm_ops,
.of_match_table = of_match_ptr(uio_of_genirq_match),
},
};
diff --git a/drivers/uio/uio_sercos3.c b/drivers/uio/uio_sercos3.c
index b93a5f8f4cba..12afc2fa1a0b 100644
--- a/drivers/uio/uio_sercos3.c
+++ b/drivers/uio/uio_sercos3.c
@@ -191,7 +191,7 @@ static void sercos3_pci_remove(struct pci_dev *dev)
}
}
-static struct pci_device_id sercos3_pci_ids[] = {
+static const struct pci_device_id sercos3_pci_ids[] = {
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
diff --git a/drivers/usb/cdns3/cdns3-trace.h b/drivers/usb/cdns3/cdns3-trace.h
index c4e542f1b9b7..21a6a2ce7a3d 100644
--- a/drivers/usb/cdns3/cdns3-trace.h
+++ b/drivers/usb/cdns3/cdns3-trace.h
@@ -283,39 +283,6 @@ TRACE_EVENT(cdns3_ep0_queue,
__entry->length)
);
-DECLARE_EVENT_CLASS(cdns3_stream_split_transfer_len,
- TP_PROTO(struct cdns3_request *req),
- TP_ARGS(req),
- TP_STRUCT__entry(
- __string(name, req->priv_ep->name)
- __field(struct cdns3_request *, req)
- __field(unsigned int, length)
- __field(unsigned int, actual)
- __field(unsigned int, stream_id)
- ),
- TP_fast_assign(
- __assign_str(name);
- __entry->req = req;
- __entry->actual = req->request.length;
- __entry->length = req->request.actual;
- __entry->stream_id = req->request.stream_id;
- ),
- TP_printk("%s: req: %p,request length: %u actual length: %u SID: %u",
- __get_str(name), __entry->req, __entry->length,
- __entry->actual, __entry->stream_id)
-);
-
-DEFINE_EVENT(cdns3_stream_split_transfer_len, cdns3_stream_transfer_split,
- TP_PROTO(struct cdns3_request *req),
- TP_ARGS(req)
-);
-
-DEFINE_EVENT(cdns3_stream_split_transfer_len,
- cdns3_stream_transfer_split_next_part,
- TP_PROTO(struct cdns3_request *req),
- TP_ARGS(req)
-);
-
DECLARE_EVENT_CLASS(cdns3_log_aligned_request,
TP_PROTO(struct cdns3_request *priv_req),
TP_ARGS(priv_req),
@@ -354,34 +321,6 @@ DEFINE_EVENT(cdns3_log_aligned_request, cdns3_prepare_aligned_request,
TP_ARGS(req)
);
-DECLARE_EVENT_CLASS(cdns3_log_map_request,
- TP_PROTO(struct cdns3_request *priv_req),
- TP_ARGS(priv_req),
- TP_STRUCT__entry(
- __string(name, priv_req->priv_ep->name)
- __field(struct usb_request *, req)
- __field(void *, buf)
- __field(dma_addr_t, dma)
- ),
- TP_fast_assign(
- __assign_str(name);
- __entry->req = &priv_req->request;
- __entry->buf = priv_req->request.buf;
- __entry->dma = priv_req->request.dma;
- ),
- TP_printk("%s: req: %p, req buf %p, dma %p",
- __get_str(name), __entry->req, __entry->buf, &__entry->dma
- )
-);
-DEFINE_EVENT(cdns3_log_map_request, cdns3_map_request,
- TP_PROTO(struct cdns3_request *req),
- TP_ARGS(req)
-);
-DEFINE_EVENT(cdns3_log_map_request, cdns3_mapped_request,
- TP_PROTO(struct cdns3_request *req),
- TP_ARGS(req)
-);
-
DECLARE_EVENT_CLASS(cdns3_log_trb,
TP_PROTO(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb),
TP_ARGS(priv_ep, trb),
diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c
index 55f95f41b3b4..0252560cbc80 100644
--- a/drivers/usb/cdns3/cdnsp-gadget.c
+++ b/drivers/usb/cdns3/cdnsp-gadget.c
@@ -1976,7 +1976,10 @@ static int __cdnsp_gadget_init(struct cdns *cdns)
return 0;
del_gadget:
- usb_del_gadget_udc(&pdev->gadget);
+ usb_del_gadget(&pdev->gadget);
+ cdnsp_gadget_free_endpoints(pdev);
+ usb_put_gadget(&pdev->gadget);
+ goto halt_pdev;
free_endpoints:
cdnsp_gadget_free_endpoints(pdev);
halt_pdev:
@@ -1998,8 +2001,9 @@ static void cdnsp_gadget_exit(struct cdns *cdns)
devm_free_irq(pdev->dev, cdns->dev_irq, pdev);
pm_runtime_mark_last_busy(cdns->dev);
pm_runtime_put_autosuspend(cdns->dev);
- usb_del_gadget_udc(&pdev->gadget);
+ usb_del_gadget(&pdev->gadget);
cdnsp_gadget_free_endpoints(pdev);
+ usb_put_gadget(&pdev->gadget);
cdnsp_mem_cleanup(pdev);
kfree(pdev);
cdns->gadget_dev = NULL;
diff --git a/drivers/usb/cdns3/cdnsp-pci.c b/drivers/usb/cdns3/cdnsp-pci.c
index 8c361b8394e9..5e7b88ca8b96 100644
--- a/drivers/usb/cdns3/cdnsp-pci.c
+++ b/drivers/usb/cdns3/cdnsp-pci.c
@@ -85,7 +85,7 @@ static int cdnsp_pci_probe(struct pci_dev *pdev,
cdnsp = kzalloc(sizeof(*cdnsp), GFP_KERNEL);
if (!cdnsp) {
ret = -ENOMEM;
- goto disable_pci;
+ goto put_pci;
}
}
@@ -168,9 +168,6 @@ free_cdnsp:
if (!pci_is_enabled(func))
kfree(cdnsp);
-disable_pci:
- pci_disable_device(pdev);
-
put_pci:
pci_dev_put(func);
diff --git a/drivers/usb/cdns3/cdnsp-trace.h b/drivers/usb/cdns3/cdnsp-trace.h
index f2bcf77a5d0a..9b33a736c3de 100644
--- a/drivers/usb/cdns3/cdnsp-trace.h
+++ b/drivers/usb/cdns3/cdnsp-trace.h
@@ -178,11 +178,6 @@ DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_set_config,
TP_ARGS(msg)
);
-DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_halted,
- TP_PROTO(char *msg),
- TP_ARGS(msg)
-);
-
DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep_halt,
TP_PROTO(char *msg),
TP_ARGS(msg)
@@ -399,11 +394,6 @@ DEFINE_EVENT(cdnsp_log_trb, cdnsp_cmd_timeout,
TP_ARGS(ring, trb)
);
-DEFINE_EVENT(cdnsp_log_trb, cdnsp_defered_event,
- TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb),
- TP_ARGS(ring, trb)
-);
-
DECLARE_EVENT_CLASS(cdnsp_log_pdev,
TP_PROTO(struct cdnsp_device *pdev),
TP_ARGS(pdev),
@@ -433,16 +423,6 @@ DEFINE_EVENT(cdnsp_log_pdev, cdnsp_alloc_priv_device,
TP_ARGS(vdev)
);
-DEFINE_EVENT(cdnsp_log_pdev, cdnsp_free_priv_device,
- TP_PROTO(struct cdnsp_device *vdev),
- TP_ARGS(vdev)
-);
-
-DEFINE_EVENT(cdnsp_log_pdev, cdnsp_setup_device,
- TP_PROTO(struct cdnsp_device *vdev),
- TP_ARGS(vdev)
-);
-
DEFINE_EVENT(cdnsp_log_pdev, cdnsp_setup_addressable_priv_device,
TP_PROTO(struct cdnsp_device *vdev),
TP_ARGS(vdev)
@@ -575,11 +555,6 @@ DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_stop_ep,
TP_ARGS(ctx)
);
-DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_flush_ep,
- TP_PROTO(struct cdnsp_ep_ctx *ctx),
- TP_ARGS(ctx)
-);
-
DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_set_deq_ep,
TP_PROTO(struct cdnsp_ep_ctx *ctx),
TP_ARGS(ctx)
diff --git a/drivers/usb/class/usblp.c b/drivers/usb/class/usblp.c
index acbefccbdb2a..a7a1d38b6bef 100644
--- a/drivers/usb/class/usblp.c
+++ b/drivers/usb/class/usblp.c
@@ -34,6 +34,7 @@
#include <linux/module.h>
#include <linux/kernel.h>
+#include <linux/minmax.h>
#include <linux/sched/signal.h>
#include <linux/signal.h>
#include <linux/poll.h>
@@ -871,7 +872,7 @@ static ssize_t usblp_read(struct file *file, char __user *buffer, size_t len, lo
goto done;
}
- count = len < avail - usblp->readcount ? len : avail - usblp->readcount;
+ count = min_t(ssize_t, len, avail - usblp->readcount);
if (count != 0 &&
copy_to_user(buffer, usblp->readbuf + usblp->readcount, count)) {
count = -EFAULT;
diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile
index ac006abd13b3..766000b4939e 100644
--- a/drivers/usb/core/Makefile
+++ b/drivers/usb/core/Makefile
@@ -9,6 +9,7 @@ usbcore-y += devio.o notify.o generic.o quirks.o devices.o
usbcore-y += phy.o port.o
usbcore-$(CONFIG_OF) += of.o
+usbcore-$(CONFIG_USB_XHCI_SIDEBAND) += offload.o
usbcore-$(CONFIG_USB_PCI) += hcd-pci.o
usbcore-$(CONFIG_ACPI) += usb-acpi.o
diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c
index 42468bbeffd2..baf5bc844b6f 100644
--- a/drivers/usb/core/config.c
+++ b/drivers/usb/core/config.c
@@ -507,8 +507,8 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno,
}
/* Parse a possible eUSB2 periodic endpoint companion descriptor */
- if (bcdUSB == 0x0220 && d->wMaxPacketSize == 0 &&
- (usb_endpoint_xfer_isoc(d) || usb_endpoint_xfer_int(d)))
+ if (udev->speed == USB_SPEED_HIGH && bcdUSB == 0x0220 &&
+ !le16_to_cpu(d->wMaxPacketSize) && usb_endpoint_is_isoc_in(d))
usb_parse_eusb2_isoc_endpoint_companion(ddev, cfgno, inum, asnum,
endpoint, buffer, size);
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index f441958b0ef4..d29edc7c616a 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -332,10 +332,10 @@ static int usb_probe_interface(struct device *dev)
return error;
if (udev->authorized == 0) {
- dev_err(&intf->dev, "Device is not authorized for usage\n");
+ dev_info(&intf->dev, "Device is not authorized for usage\n");
return error;
} else if (intf->authorized == 0) {
- dev_err(&intf->dev, "Interface %d is not authorized for usage\n",
+ dev_info(&intf->dev, "Interface %d is not authorized for usage\n",
intf->altsetting->desc.bInterfaceNumber);
return error;
}
@@ -1420,11 +1420,28 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
udev->state == USB_STATE_SUSPENDED)
goto done;
+ if (msg.event == PM_EVENT_SUSPEND && usb_offload_check(udev)) {
+ dev_dbg(&udev->dev, "device offloaded, skip suspend.\n");
+ udev->offload_at_suspend = 1;
+ }
+
/* Suspend all the interfaces and then udev itself */
if (udev->actconfig) {
n = udev->actconfig->desc.bNumInterfaces;
for (i = n - 1; i >= 0; --i) {
intf = udev->actconfig->interface[i];
+ /*
+ * Don't suspend interfaces with remote wakeup while
+ * the controller is active. This preserves pending
+ * interrupt urbs, allowing interrupt events to be
+ * handled during system suspend.
+ */
+ if (udev->offload_at_suspend &&
+ intf->needs_remote_wakeup) {
+ dev_dbg(&intf->dev,
+ "device offloaded, skip suspend.\n");
+ continue;
+ }
status = usb_suspend_interface(udev, intf, msg);
/* Ignore errors during system sleep transitions */
@@ -1435,7 +1452,8 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
}
}
if (status == 0) {
- status = usb_suspend_device(udev, msg);
+ if (!udev->offload_at_suspend)
+ status = usb_suspend_device(udev, msg);
/*
* Ignore errors from non-root-hub devices during
@@ -1480,9 +1498,11 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
*/
} else {
udev->can_submit = 0;
- for (i = 0; i < 16; ++i) {
- usb_hcd_flush_endpoint(udev, udev->ep_out[i]);
- usb_hcd_flush_endpoint(udev, udev->ep_in[i]);
+ if (!udev->offload_at_suspend) {
+ for (i = 0; i < 16; ++i) {
+ usb_hcd_flush_endpoint(udev, udev->ep_out[i]);
+ usb_hcd_flush_endpoint(udev, udev->ep_in[i]);
+ }
}
}
@@ -1524,17 +1544,35 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg)
udev->can_submit = 1;
/* Resume the device */
- if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume)
- status = usb_resume_device(udev, msg);
+ if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume) {
+ if (!udev->offload_at_suspend)
+ status = usb_resume_device(udev, msg);
+ else
+ dev_dbg(&udev->dev,
+ "device offloaded, skip resume.\n");
+ }
/* Resume the interfaces */
if (status == 0 && udev->actconfig) {
for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
intf = udev->actconfig->interface[i];
+ /*
+ * Interfaces with remote wakeup aren't suspended
+ * while the controller is active. This preserves
+ * pending interrupt urbs, allowing interrupt events
+ * to be handled during system suspend.
+ */
+ if (udev->offload_at_suspend &&
+ intf->needs_remote_wakeup) {
+ dev_dbg(&intf->dev,
+ "device offloaded, skip resume.\n");
+ continue;
+ }
usb_resume_interface(udev, intf, msg,
udev->reset_resume);
}
}
+ udev->offload_at_suspend = 0;
usb_mark_last_busy(udev);
done:
@@ -1723,8 +1761,6 @@ int usb_autoresume_device(struct usb_device *udev)
dev_vdbg(&udev->dev, "%s: cnt %d -> %d\n",
__func__, atomic_read(&udev->dev.power.usage_count),
status);
- if (status > 0)
- status = 0;
return status;
}
@@ -1829,8 +1865,6 @@ int usb_autopm_get_interface(struct usb_interface *intf)
dev_vdbg(&intf->dev, "%s: cnt %d -> %d\n",
__func__, atomic_read(&intf->dev.power.usage_count),
status);
- if (status > 0)
- status = 0;
return status;
}
EXPORT_SYMBOL_GPL(usb_autopm_get_interface);
diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c
index 9c6ae5e1198b..a48994e11ef3 100644
--- a/drivers/usb/core/generic.c
+++ b/drivers/usb/core/generic.c
@@ -243,7 +243,7 @@ int usb_generic_driver_probe(struct usb_device *udev)
* with the driver core and lets interface drivers bind to them.
*/
if (udev->authorized == 0)
- dev_err(&udev->dev, "Device is not authorized for usage\n");
+ dev_info(&udev->dev, "Device is not authorized for usage\n");
else {
c = usb_choose_configuration(udev);
if (c >= 0) {
diff --git a/drivers/usb/core/offload.c b/drivers/usb/core/offload.c
new file mode 100644
index 000000000000..7c699f1b8d2b
--- /dev/null
+++ b/drivers/usb/core/offload.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * offload.c - USB offload related functions
+ *
+ * Copyright (c) 2025, Google LLC.
+ *
+ * Author: Guan-Yu Lin
+ */
+
+#include <linux/usb.h>
+
+#include "usb.h"
+
+/**
+ * usb_offload_get - increment the offload_usage of a USB device
+ * @udev: the USB device to increment its offload_usage
+ *
+ * Incrementing the offload_usage of a usb_device indicates that offload is
+ * enabled on this usb_device; that is, another entity is actively handling USB
+ * transfers. This information allows the USB driver to adjust its power
+ * management policy based on offload activity.
+ *
+ * Return: 0 on success. A negative error code otherwise.
+ */
+int usb_offload_get(struct usb_device *udev)
+{
+ int ret;
+
+ usb_lock_device(udev);
+ if (udev->state == USB_STATE_NOTATTACHED) {
+ usb_unlock_device(udev);
+ return -ENODEV;
+ }
+
+ if (udev->state == USB_STATE_SUSPENDED ||
+ udev->offload_at_suspend) {
+ usb_unlock_device(udev);
+ return -EBUSY;
+ }
+
+ /*
+ * offload_usage could only be modified when the device is active, since
+ * it will alter the suspend flow of the device.
+ */
+ ret = usb_autoresume_device(udev);
+ if (ret < 0) {
+ usb_unlock_device(udev);
+ return ret;
+ }
+
+ udev->offload_usage++;
+ usb_autosuspend_device(udev);
+ usb_unlock_device(udev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(usb_offload_get);
+
+/**
+ * usb_offload_put - drop the offload_usage of a USB device
+ * @udev: the USB device to drop its offload_usage
+ *
+ * The inverse operation of usb_offload_get, which drops the offload_usage of
+ * a USB device. This information allows the USB driver to adjust its power
+ * management policy based on offload activity.
+ *
+ * Return: 0 on success. A negative error code otherwise.
+ */
+int usb_offload_put(struct usb_device *udev)
+{
+ int ret;
+
+ usb_lock_device(udev);
+ if (udev->state == USB_STATE_NOTATTACHED) {
+ usb_unlock_device(udev);
+ return -ENODEV;
+ }
+
+ if (udev->state == USB_STATE_SUSPENDED ||
+ udev->offload_at_suspend) {
+ usb_unlock_device(udev);
+ return -EBUSY;
+ }
+
+ /*
+ * offload_usage could only be modified when the device is active, since
+ * it will alter the suspend flow of the device.
+ */
+ ret = usb_autoresume_device(udev);
+ if (ret < 0) {
+ usb_unlock_device(udev);
+ return ret;
+ }
+
+ /* Drop the count when it wasn't 0, ignore the operation otherwise. */
+ if (udev->offload_usage)
+ udev->offload_usage--;
+ usb_autosuspend_device(udev);
+ usb_unlock_device(udev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(usb_offload_put);
+
+/**
+ * usb_offload_check - check offload activities on a USB device
+ * @udev: the USB device to check its offload activity.
+ *
+ * Check if there are any offload activity on the USB device right now. This
+ * information could be used for power management or other forms of resource
+ * management.
+ *
+ * The caller must hold @udev's device lock. In addition, the caller should
+ * ensure downstream usb devices are all either suspended or marked as
+ * "offload_at_suspend" to ensure the correctness of the return value.
+ *
+ * Returns true on any offload activity, false otherwise.
+ */
+bool usb_offload_check(struct usb_device *udev) __must_hold(&udev->dev->mutex)
+{
+ struct usb_device *child;
+ bool active;
+ int port1;
+
+ usb_hub_for_each_child(udev, port1, child) {
+ usb_lock_device(child);
+ active = usb_offload_check(child);
+ usb_unlock_device(child);
+ if (active)
+ return true;
+ }
+
+ return !!udev->offload_usage;
+}
+EXPORT_SYMBOL_GPL(usb_offload_check);
diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c
index 7a76d5a62db1..ff8df16cca35 100644
--- a/drivers/usb/core/urb.c
+++ b/drivers/usb/core/urb.c
@@ -372,6 +372,7 @@ int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
struct usb_host_endpoint *ep;
int is_out;
unsigned int allowed;
+ bool is_eusb2_isoch_double;
if (!urb || !urb->complete)
return -EINVAL;
@@ -434,7 +435,8 @@ int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
return -ENODEV;
max = usb_endpoint_maxp(&ep->desc);
- if (max <= 0) {
+ is_eusb2_isoch_double = usb_endpoint_is_hs_isoc_double(dev, ep);
+ if (!max && !is_eusb2_isoch_double) {
dev_dbg(&dev->dev,
"bogus endpoint ep%d%s in %s (bad maxpacket %d)\n",
usb_endpoint_num(&ep->desc), is_out ? "out" : "in",
@@ -467,9 +469,13 @@ int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
max = le32_to_cpu(isoc_ep_comp->dwBytesPerInterval);
}
- /* "high bandwidth" mode, 1-3 packets/uframe? */
- if (dev->speed == USB_SPEED_HIGH)
- max *= usb_endpoint_maxp_mult(&ep->desc);
+ /* High speed, 1-3 packets/uframe, max 6 for eUSB2 double bw */
+ if (dev->speed == USB_SPEED_HIGH) {
+ if (is_eusb2_isoch_double)
+ max = le32_to_cpu(ep->eusb2_isoc_ep_comp.dwBytesPerInterval);
+ else
+ max *= usb_endpoint_maxp_mult(&ep->desc);
+ }
if (urb->number_of_packets <= 0)
return -EINVAL;
diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
index fca7735fc660..b6b0b8489523 100644
--- a/drivers/usb/core/usb.c
+++ b/drivers/usb/core/usb.c
@@ -670,6 +670,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent,
set_dev_node(&dev->dev, dev_to_node(bus->sysdev));
dev->state = USB_STATE_ATTACHED;
dev->lpm_disable_count = 1;
+ dev->offload_usage = 0;
atomic_set(&dev->urbnum, 0);
INIT_LIST_HEAD(&dev->ep0.urb_list);
@@ -1110,6 +1111,56 @@ void usb_free_noncoherent(struct usb_device *dev, size_t size,
}
EXPORT_SYMBOL_GPL(usb_free_noncoherent);
+/**
+ * usb_endpoint_max_periodic_payload - Get maximum payload bytes per service
+ * interval
+ * @udev: The USB device
+ * @ep: The endpoint
+ *
+ * Returns: the maximum number of bytes isochronous or interrupt endpoint @ep
+ * can transfer during a service interval, or 0 for other endpoints.
+ */
+u32 usb_endpoint_max_periodic_payload(struct usb_device *udev,
+ const struct usb_host_endpoint *ep)
+{
+ if (!usb_endpoint_xfer_isoc(&ep->desc) &&
+ !usb_endpoint_xfer_int(&ep->desc))
+ return 0;
+
+ switch (udev->speed) {
+ case USB_SPEED_SUPER_PLUS:
+ if (USB_SS_SSP_ISOC_COMP(ep->ss_ep_comp.bmAttributes))
+ return le32_to_cpu(ep->ssp_isoc_ep_comp.dwBytesPerInterval);
+ fallthrough;
+ case USB_SPEED_SUPER:
+ return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval);
+ default:
+ if (usb_endpoint_is_hs_isoc_double(udev, ep))
+ return le32_to_cpu(ep->eusb2_isoc_ep_comp.dwBytesPerInterval);
+ return usb_endpoint_maxp(&ep->desc) * usb_endpoint_maxp_mult(&ep->desc);
+ }
+}
+EXPORT_SYMBOL_GPL(usb_endpoint_max_periodic_payload);
+
+/**
+ * usb_endpoint_is_hs_isoc_double - Tell whether an endpoint uses USB 2
+ * Isochronous Double IN Bandwidth
+ * @udev: The USB device
+ * @ep: The endpoint
+ *
+ * Returns: true if an endpoint @ep conforms to USB 2 Isochronous Double IN
+ * Bandwidth ECN, false otherwise.
+ */
+bool usb_endpoint_is_hs_isoc_double(struct usb_device *udev,
+ const struct usb_host_endpoint *ep)
+{
+ return ep->eusb2_isoc_ep_comp.bDescriptorType &&
+ le16_to_cpu(udev->descriptor.bcdUSB) == 0x220 &&
+ usb_endpoint_is_isoc_in(&ep->desc) &&
+ !le16_to_cpu(ep->desc.wMaxPacketSize);
+}
+EXPORT_SYMBOL_GPL(usb_endpoint_is_hs_isoc_double);
+
/*
* Notifications of device and interface registration
*/
diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c
index ea6bd537e337..091bfcfef753 100644
--- a/drivers/usb/dwc2/params.c
+++ b/drivers/usb/dwc2/params.c
@@ -1029,11 +1029,33 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg)
return 0;
}
+static int dwc2_limit_speed(struct dwc2_hsotg *hsotg)
+{
+ enum usb_device_speed usb_speed;
+
+ usb_speed = usb_get_maximum_speed(hsotg->dev);
+ switch (usb_speed) {
+ case USB_SPEED_LOW:
+ dev_err(hsotg->dev, "Maximum speed cannot be forced to low-speed\n");
+ return -EINVAL;
+ case USB_SPEED_FULL:
+ if (hsotg->params.speed == DWC2_SPEED_PARAM_LOW)
+ break;
+ hsotg->params.speed = DWC2_SPEED_PARAM_FULL;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
typedef void (*set_params_cb)(struct dwc2_hsotg *data);
int dwc2_init_params(struct dwc2_hsotg *hsotg)
{
set_params_cb set_params;
+ int ret;
dwc2_set_default_params(hsotg);
dwc2_get_device_properties(hsotg);
@@ -1051,6 +1073,10 @@ int dwc2_init_params(struct dwc2_hsotg *hsotg)
}
}
+ ret = dwc2_limit_speed(hsotg);
+ if (ret)
+ return ret;
+
dwc2_check_params(hsotg);
return 0;
diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
index 310d182e10b5..4925d15084f8 100644
--- a/drivers/usb/dwc3/Kconfig
+++ b/drivers/usb/dwc3/Kconfig
@@ -189,4 +189,15 @@ config USB_DWC3_RTK
or dual-role mode.
Say 'Y' or 'M' if you have such device.
+config USB_DWC3_GENERIC_PLAT
+ tristate "DWC3 Generic Platform Driver"
+ depends on OF && COMMON_CLK
+ default USB_DWC3
+ help
+ Support USB3 functionality in simple SoC integrations.
+ Currently supports SpacemiT DWC USB3. Platforms using
+ dwc3-of-simple can easily switch to dwc3-generic by flattening
+ the dwc3 child node in the device tree.
+ Say 'Y' or 'M' here if your platform integrates DWC3 in a similar way.
+
endif
diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
index 830e6c9e5fe0..96469e48ff9d 100644
--- a/drivers/usb/dwc3/Makefile
+++ b/drivers/usb/dwc3/Makefile
@@ -57,3 +57,4 @@ obj-$(CONFIG_USB_DWC3_IMX8MP) += dwc3-imx8mp.o
obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o
obj-$(CONFIG_USB_DWC3_OCTEON) += dwc3-octeon.o
obj-$(CONFIG_USB_DWC3_RTK) += dwc3-rtk.o
+obj-$(CONFIG_USB_DWC3_GENERIC_PLAT) += dwc3-generic-plat.o
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 8002c23a5a02..ae140c356295 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -156,6 +156,7 @@ void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy)
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
dwc->current_dr_role = mode;
+ trace_dwc3_set_prtcap(mode);
}
static void __dwc3_set_mode(struct work_struct *work)
@@ -2351,6 +2352,7 @@ static int dwc3_probe(struct platform_device *pdev)
return -ENOMEM;
dwc->dev = &pdev->dev;
+ dwc->glue_ops = NULL;
probe_data.dwc = dwc;
probe_data.res = res;
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index d5b985fa12f4..a5fc92c4ffa3 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -993,6 +993,17 @@ struct dwc3_scratchpad_array {
};
/**
+ * struct dwc3_glue_ops - The ops indicate the notifications that
+ * need to be passed on to glue layer
+ * @pre_set_role: Notify glue of role switch notifications
+ * @pre_run_stop: Notify run stop enable/disable information to glue
+ */
+struct dwc3_glue_ops {
+ void (*pre_set_role)(struct dwc3 *dwc, enum usb_role role);
+ void (*pre_run_stop)(struct dwc3 *dwc, bool is_on);
+};
+
+/**
* struct dwc3 - representation of our controller
* @drd_work: workqueue used for role swapping
* @ep0_trb: trb which is used for the ctrl_req
@@ -1012,6 +1023,7 @@ struct dwc3_scratchpad_array {
* @eps: endpoint array
* @gadget: device side representation of the peripheral controller
* @gadget_driver: pointer to the gadget driver
+ * @glue_ops: Vendor callbacks for flattened device implementations.
* @bus_clk: clock for accessing the registers
* @ref_clk: reference clock
* @susp_clk: clock used when the SS phy is in low power (S3) state
@@ -1197,6 +1209,8 @@ struct dwc3 {
struct usb_gadget *gadget;
struct usb_gadget_driver *gadget_driver;
+ const struct dwc3_glue_ops *glue_ops;
+
struct clk *bus_clk;
struct clk *ref_clk;
struct clk *susp_clk;
@@ -1614,6 +1628,18 @@ void dwc3_event_buffers_cleanup(struct dwc3 *dwc);
int dwc3_core_soft_reset(struct dwc3 *dwc);
void dwc3_enable_susphy(struct dwc3 *dwc, bool enable);
+static inline void dwc3_pre_set_role(struct dwc3 *dwc, enum usb_role role)
+{
+ if (dwc->glue_ops && dwc->glue_ops->pre_set_role)
+ dwc->glue_ops->pre_set_role(dwc, role);
+}
+
+static inline void dwc3_pre_run_stop(struct dwc3 *dwc, bool is_on)
+{
+ if (dwc->glue_ops && dwc->glue_ops->pre_run_stop)
+ dwc->glue_ops->pre_run_stop(dwc, is_on);
+}
+
#if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
int dwc3_host_init(struct dwc3 *dwc);
void dwc3_host_exit(struct dwc3 *dwc);
diff --git a/drivers/usb/dwc3/debug.h b/drivers/usb/dwc3/debug.h
index 09d703852a92..6e1cdcdce7cc 100644
--- a/drivers/usb/dwc3/debug.h
+++ b/drivers/usb/dwc3/debug.h
@@ -14,6 +14,24 @@
#include "core.h"
/**
+ * dwc3_mode_string - returns mode name
+ * @mode: GCTL.PrtCapDir value
+ */
+static inline const char *dwc3_mode_string(u32 mode)
+{
+ switch (mode) {
+ case DWC3_GCTL_PRTCAP_HOST:
+ return "host";
+ case DWC3_GCTL_PRTCAP_DEVICE:
+ return "device";
+ case DWC3_GCTL_PRTCAP_OTG:
+ return "otg";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+/**
* dwc3_gadget_ep_cmd_string - returns endpoint command string
* @cmd: command code
*/
diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c
index ebf03468fac4..d18bf5e32cc8 100644
--- a/drivers/usb/dwc3/debugfs.c
+++ b/drivers/usb/dwc3/debugfs.c
@@ -402,6 +402,7 @@ static int dwc3_mode_show(struct seq_file *s, void *unused)
struct dwc3 *dwc = s->private;
unsigned long flags;
u32 reg;
+ u32 mode;
int ret;
ret = pm_runtime_resume_and_get(dwc->dev);
@@ -412,18 +413,15 @@ static int dwc3_mode_show(struct seq_file *s, void *unused)
reg = dwc3_readl(dwc->regs, DWC3_GCTL);
spin_unlock_irqrestore(&dwc->lock, flags);
- switch (DWC3_GCTL_PRTCAP(reg)) {
+ mode = DWC3_GCTL_PRTCAP(reg);
+ switch (mode) {
case DWC3_GCTL_PRTCAP_HOST:
- seq_puts(s, "host\n");
- break;
case DWC3_GCTL_PRTCAP_DEVICE:
- seq_puts(s, "device\n");
- break;
case DWC3_GCTL_PRTCAP_OTG:
- seq_puts(s, "otg\n");
+ seq_printf(s, "%s\n", dwc3_mode_string(mode));
break;
default:
- seq_printf(s, "UNKNOWN %08x\n", DWC3_GCTL_PRTCAP(reg));
+ seq_printf(s, "UNKNOWN %08x\n", mode);
}
pm_runtime_put_sync(dwc->dev);
diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c
index 7977860932b1..4c91240eb429 100644
--- a/drivers/usb/dwc3/drd.c
+++ b/drivers/usb/dwc3/drd.c
@@ -464,6 +464,7 @@ static int dwc3_usb_role_switch_set(struct usb_role_switch *sw,
break;
}
+ dwc3_pre_set_role(dwc, role);
dwc3_set_mode(dwc, mode);
return 0;
}
diff --git a/drivers/usb/dwc3/dwc3-generic-plat.c b/drivers/usb/dwc3/dwc3-generic-plat.c
new file mode 100644
index 000000000000..d96b20570002
--- /dev/null
+++ b/drivers/usb/dwc3/dwc3-generic-plat.c
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * dwc3-generic-plat.c - DesignWare USB3 generic platform driver
+ *
+ * Copyright (C) 2025 Ze Huang <huang.ze@linux.dev>
+ *
+ * Inspired by dwc3-qcom.c and dwc3-of-simple.c
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include "glue.h"
+
+struct dwc3_generic {
+ struct device *dev;
+ struct dwc3 dwc;
+ struct clk_bulk_data *clks;
+ int num_clocks;
+ struct reset_control *resets;
+};
+
+#define to_dwc3_generic(d) container_of((d), struct dwc3_generic, dwc)
+
+static void dwc3_generic_reset_control_assert(void *data)
+{
+ reset_control_assert(data);
+}
+
+static int dwc3_generic_probe(struct platform_device *pdev)
+{
+ struct dwc3_probe_data probe_data = {};
+ struct device *dev = &pdev->dev;
+ struct dwc3_generic *dwc3g;
+ struct resource *res;
+ int ret;
+
+ dwc3g = devm_kzalloc(dev, sizeof(*dwc3g), GFP_KERNEL);
+ if (!dwc3g)
+ return -ENOMEM;
+
+ dwc3g->dev = dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "missing memory resource\n");
+ return -ENODEV;
+ }
+
+ dwc3g->resets = devm_reset_control_array_get_optional_exclusive(dev);
+ if (IS_ERR(dwc3g->resets))
+ return dev_err_probe(dev, PTR_ERR(dwc3g->resets), "failed to get resets\n");
+
+ ret = reset_control_assert(dwc3g->resets);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to assert resets\n");
+
+ /* Not strict timing, just for safety */
+ udelay(2);
+
+ ret = reset_control_deassert(dwc3g->resets);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to deassert resets\n");
+
+ ret = devm_add_action_or_reset(dev, dwc3_generic_reset_control_assert, dwc3g->resets);
+ if (ret)
+ return ret;
+
+ ret = devm_clk_bulk_get_all_enabled(dwc3g->dev, &dwc3g->clks);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to get clocks\n");
+
+ dwc3g->num_clocks = ret;
+ dwc3g->dwc.dev = dev;
+ probe_data.dwc = &dwc3g->dwc;
+ probe_data.res = res;
+ probe_data.ignore_clocks_and_resets = true;
+ ret = dwc3_core_probe(&probe_data);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register DWC3 Core\n");
+
+ return 0;
+}
+
+static void dwc3_generic_remove(struct platform_device *pdev)
+{
+ struct dwc3 *dwc = platform_get_drvdata(pdev);
+ struct dwc3_generic *dwc3g = to_dwc3_generic(dwc);
+
+ dwc3_core_remove(dwc);
+
+ clk_bulk_disable_unprepare(dwc3g->num_clocks, dwc3g->clks);
+}
+
+static int dwc3_generic_suspend(struct device *dev)
+{
+ struct dwc3 *dwc = dev_get_drvdata(dev);
+ struct dwc3_generic *dwc3g = to_dwc3_generic(dwc);
+ int ret;
+
+ ret = dwc3_pm_suspend(dwc);
+ if (ret)
+ return ret;
+
+ clk_bulk_disable_unprepare(dwc3g->num_clocks, dwc3g->clks);
+
+ return 0;
+}
+
+static int dwc3_generic_resume(struct device *dev)
+{
+ struct dwc3 *dwc = dev_get_drvdata(dev);
+ struct dwc3_generic *dwc3g = to_dwc3_generic(dwc);
+ int ret;
+
+ ret = clk_bulk_prepare_enable(dwc3g->num_clocks, dwc3g->clks);
+ if (ret)
+ return ret;
+
+ ret = dwc3_pm_resume(dwc);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int dwc3_generic_runtime_suspend(struct device *dev)
+{
+ return dwc3_runtime_suspend(dev_get_drvdata(dev));
+}
+
+static int dwc3_generic_runtime_resume(struct device *dev)
+{
+ return dwc3_runtime_resume(dev_get_drvdata(dev));
+}
+
+static int dwc3_generic_runtime_idle(struct device *dev)
+{
+ return dwc3_runtime_idle(dev_get_drvdata(dev));
+}
+
+static const struct dev_pm_ops dwc3_generic_dev_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(dwc3_generic_suspend, dwc3_generic_resume)
+ RUNTIME_PM_OPS(dwc3_generic_runtime_suspend, dwc3_generic_runtime_resume,
+ dwc3_generic_runtime_idle)
+};
+
+static const struct of_device_id dwc3_generic_of_match[] = {
+ { .compatible = "spacemit,k1-dwc3", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dwc3_generic_of_match);
+
+static struct platform_driver dwc3_generic_driver = {
+ .probe = dwc3_generic_probe,
+ .remove = dwc3_generic_remove,
+ .driver = {
+ .name = "dwc3-generic-plat",
+ .of_match_table = dwc3_generic_of_match,
+ .pm = pm_ptr(&dwc3_generic_dev_pm_ops),
+ },
+};
+module_platform_driver(dwc3_generic_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("DesignWare USB3 generic platform driver");
diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
index ca7e1c02773a..ded2ca86670c 100644
--- a/drivers/usb/dwc3/dwc3-qcom.c
+++ b/drivers/usb/dwc3/dwc3-qcom.c
@@ -11,7 +11,6 @@
#include <linux/of_clk.h>
#include <linux/module.h>
#include <linux/kernel.h>
-#include <linux/extcon.h>
#include <linux/interconnect.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
@@ -79,16 +78,13 @@ struct dwc3_qcom {
struct dwc3_qcom_port ports[DWC3_QCOM_MAX_PORTS];
u8 num_ports;
- struct extcon_dev *edev;
- struct extcon_dev *host_edev;
- struct notifier_block vbus_nb;
- struct notifier_block host_nb;
-
enum usb_dr_mode mode;
bool is_suspended;
bool pm_suspended;
struct icc_path *icc_path_ddr;
struct icc_path *icc_path_apps;
+
+ enum usb_role current_role;
};
#define to_dwc3_qcom(d) container_of((d), struct dwc3_qcom, dwc)
@@ -117,11 +113,6 @@ static inline void dwc3_qcom_clrbits(void __iomem *base, u32 offset, u32 val)
readl(base + offset);
}
-/*
- * TODO: Make the in-core role switching code invoke dwc3_qcom_vbus_override_enable(),
- * validate that the in-core extcon support is functional, and drop extcon
- * handling from the glue
- */
static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable)
{
if (enable) {
@@ -137,80 +128,6 @@ static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable)
}
}
-static int dwc3_qcom_vbus_notifier(struct notifier_block *nb,
- unsigned long event, void *ptr)
-{
- struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, vbus_nb);
-
- /* enable vbus override for device mode */
- dwc3_qcom_vbus_override_enable(qcom, event);
- qcom->mode = event ? USB_DR_MODE_PERIPHERAL : USB_DR_MODE_HOST;
-
- return NOTIFY_DONE;
-}
-
-static int dwc3_qcom_host_notifier(struct notifier_block *nb,
- unsigned long event, void *ptr)
-{
- struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, host_nb);
-
- /* disable vbus override in host mode */
- dwc3_qcom_vbus_override_enable(qcom, !event);
- qcom->mode = event ? USB_DR_MODE_HOST : USB_DR_MODE_PERIPHERAL;
-
- return NOTIFY_DONE;
-}
-
-static int dwc3_qcom_register_extcon(struct dwc3_qcom *qcom)
-{
- struct device *dev = qcom->dev;
- struct extcon_dev *host_edev;
- int ret;
-
- if (!of_property_present(dev->of_node, "extcon"))
- return 0;
-
- qcom->edev = extcon_get_edev_by_phandle(dev, 0);
- if (IS_ERR(qcom->edev))
- return dev_err_probe(dev, PTR_ERR(qcom->edev),
- "Failed to get extcon\n");
-
- qcom->vbus_nb.notifier_call = dwc3_qcom_vbus_notifier;
-
- qcom->host_edev = extcon_get_edev_by_phandle(dev, 1);
- if (IS_ERR(qcom->host_edev))
- qcom->host_edev = NULL;
-
- ret = devm_extcon_register_notifier(dev, qcom->edev, EXTCON_USB,
- &qcom->vbus_nb);
- if (ret < 0) {
- dev_err(dev, "VBUS notifier register failed\n");
- return ret;
- }
-
- if (qcom->host_edev)
- host_edev = qcom->host_edev;
- else
- host_edev = qcom->edev;
-
- qcom->host_nb.notifier_call = dwc3_qcom_host_notifier;
- ret = devm_extcon_register_notifier(dev, host_edev, EXTCON_USB_HOST,
- &qcom->host_nb);
- if (ret < 0) {
- dev_err(dev, "Host notifier register failed\n");
- return ret;
- }
-
- /* Update initial VBUS override based on extcon state */
- if (extcon_get_state(qcom->edev, EXTCON_USB) ||
- !extcon_get_state(host_edev, EXTCON_USB_HOST))
- dwc3_qcom_vbus_notifier(&qcom->vbus_nb, true, qcom->edev);
- else
- dwc3_qcom_vbus_notifier(&qcom->vbus_nb, false, qcom->edev);
-
- return 0;
-}
-
static int dwc3_qcom_interconnect_enable(struct dwc3_qcom *qcom)
{
int ret;
@@ -641,6 +558,55 @@ static int dwc3_qcom_setup_irq(struct dwc3_qcom *qcom, struct platform_device *p
return 0;
}
+static void dwc3_qcom_set_role_notifier(struct dwc3 *dwc, enum usb_role next_role)
+{
+ struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
+
+ if (qcom->current_role == next_role)
+ return;
+
+ if (pm_runtime_resume_and_get(qcom->dev)) {
+ dev_dbg(qcom->dev, "Failed to resume device\n");
+ return;
+ }
+
+ if (qcom->current_role == USB_ROLE_DEVICE)
+ dwc3_qcom_vbus_override_enable(qcom, false);
+ else if (qcom->current_role != USB_ROLE_DEVICE)
+ dwc3_qcom_vbus_override_enable(qcom, true);
+
+ pm_runtime_mark_last_busy(qcom->dev);
+ pm_runtime_put_sync(qcom->dev);
+
+ /*
+ * Current role changes via usb_role_switch_set_role callback protected
+ * internally by mutex lock.
+ */
+ qcom->current_role = next_role;
+}
+
+static void dwc3_qcom_run_stop_notifier(struct dwc3 *dwc, bool is_on)
+{
+ struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
+
+ /*
+ * When autosuspend is enabled and controller goes to suspend
+ * after removing UDC from userspace, the next UDC write needs
+ * setting of QSCRATCH VBUS_VALID to "1" to generate a connect
+ * done event.
+ */
+ if (!is_on)
+ return;
+
+ dwc3_qcom_vbus_override_enable(qcom, true);
+ pm_runtime_mark_last_busy(qcom->dev);
+}
+
+struct dwc3_glue_ops dwc3_qcom_glue_ops = {
+ .pre_set_role = dwc3_qcom_set_role_notifier,
+ .pre_run_stop = dwc3_qcom_run_stop_notifier,
+};
+
static int dwc3_qcom_probe(struct platform_device *pdev)
{
struct dwc3_probe_data probe_data = {};
@@ -717,6 +683,23 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
if (ignore_pipe_clk)
dwc3_qcom_select_utmi_clk(qcom);
+ qcom->mode = usb_get_dr_mode(dev);
+
+ if (qcom->mode == USB_DR_MODE_HOST) {
+ qcom->current_role = USB_ROLE_HOST;
+ } else if (qcom->mode == USB_DR_MODE_PERIPHERAL) {
+ qcom->current_role = USB_ROLE_DEVICE;
+ dwc3_qcom_vbus_override_enable(qcom, true);
+ } else {
+ if ((device_property_read_bool(dev, "usb-role-switch")) &&
+ (usb_get_role_switch_default_mode(dev) == USB_DR_MODE_HOST))
+ qcom->current_role = USB_ROLE_HOST;
+ else
+ qcom->current_role = USB_ROLE_DEVICE;
+ }
+
+ qcom->dwc.glue_ops = &dwc3_qcom_glue_ops;
+
qcom->dwc.dev = dev;
probe_data.dwc = &qcom->dwc;
probe_data.res = &res;
@@ -731,17 +714,6 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
if (ret)
goto remove_core;
- qcom->mode = usb_get_dr_mode(dev);
-
- /* enable vbus override for device mode */
- if (qcom->mode != USB_DR_MODE_HOST)
- dwc3_qcom_vbus_override_enable(qcom, true);
-
- /* register extcon to override sw_vbus on Vbus change later */
- ret = dwc3_qcom_register_extcon(qcom);
- if (ret)
- goto interconnect_exit;
-
wakeup_source = of_property_read_bool(dev->of_node, "wakeup-source");
device_init_wakeup(&pdev->dev, wakeup_source);
@@ -749,8 +721,6 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
return 0;
-interconnect_exit:
- dwc3_qcom_interconnect_exit(qcom);
remove_core:
dwc3_core_remove(&qcom->dwc);
clk_disable:
@@ -764,11 +734,14 @@ static void dwc3_qcom_remove(struct platform_device *pdev)
struct dwc3 *dwc = platform_get_drvdata(pdev);
struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
- dwc3_core_remove(&qcom->dwc);
+ if (pm_runtime_resume_and_get(qcom->dev) < 0)
+ return;
+ dwc3_core_remove(&qcom->dwc);
clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks);
-
dwc3_qcom_interconnect_exit(qcom);
+
+ pm_runtime_put_noidle(qcom->dev);
}
static int dwc3_qcom_pm_suspend(struct device *dev)
@@ -873,6 +846,7 @@ MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match);
static struct platform_driver dwc3_qcom_driver = {
.probe = dwc3_qcom_probe,
.remove = dwc3_qcom_remove,
+ .shutdown = dwc3_qcom_remove,
.driver = {
.name = "dwc3-qcom",
.pm = pm_ptr(&dwc3_qcom_dev_pm_ops),
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 554f997eb8c4..6f18b4840a25 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -2662,6 +2662,7 @@ static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on)
dwc->pullups_connected = false;
}
+ dwc3_pre_run_stop(dwc, is_on);
dwc3_gadget_dctl_write_safe(dwc, reg);
do {
diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h
index bdeb1aaf65d8..b6ba984bafcd 100644
--- a/drivers/usb/dwc3/trace.h
+++ b/drivers/usb/dwc3/trace.h
@@ -19,6 +19,23 @@
#include "core.h"
#include "debug.h"
+DECLARE_EVENT_CLASS(dwc3_log_set_prtcap,
+ TP_PROTO(u32 mode),
+ TP_ARGS(mode),
+ TP_STRUCT__entry(
+ __field(u32, mode)
+ ),
+ TP_fast_assign(
+ __entry->mode = mode;
+ ),
+ TP_printk("mode %s", dwc3_mode_string(__entry->mode))
+);
+
+DEFINE_EVENT(dwc3_log_set_prtcap, dwc3_set_prtcap,
+ TP_PROTO(u32 mode),
+ TP_ARGS(mode)
+);
+
DECLARE_EVENT_CLASS(dwc3_log_io,
TP_PROTO(void *base, u32 offset, u32 value),
TP_ARGS(base, offset, value),
diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
index f94ea196ce54..6bcac85c5550 100644
--- a/drivers/usb/gadget/configfs.c
+++ b/drivers/usb/gadget/configfs.c
@@ -1750,6 +1750,8 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
cdev->use_os_string = true;
cdev->b_vendor_code = gi->b_vendor_code;
memcpy(cdev->qw_sign, gi->qw_sign, OS_STRING_QW_SIGN_LEN);
+ } else {
+ cdev->use_os_string = false;
}
if (gadget_is_otg(gadget) && !otg_desc[0]) {
diff --git a/drivers/usb/gadget/function/f_acm.c b/drivers/usb/gadget/function/f_acm.c
index 7061720b9732..106046e17c4e 100644
--- a/drivers/usb/gadget/function/f_acm.c
+++ b/drivers/usb/gadget/function/f_acm.c
@@ -11,12 +11,15 @@
/* #define VERBOSE_DEBUG */
+#include <linux/cleanup.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/err.h>
+#include <linux/usb/gadget.h>
+
#include "u_serial.h"
@@ -613,6 +616,7 @@ acm_bind(struct usb_configuration *c, struct usb_function *f)
struct usb_string *us;
int status;
struct usb_ep *ep;
+ struct usb_request *request __free(free_usb_request) = NULL;
/* REVISIT might want instance-specific strings to help
* distinguish instances ...
@@ -630,7 +634,7 @@ acm_bind(struct usb_configuration *c, struct usb_function *f)
/* allocate instance-specific interface IDs, and patch descriptors */
status = usb_interface_id(c, f);
if (status < 0)
- goto fail;
+ return status;
acm->ctrl_id = status;
acm_iad_descriptor.bFirstInterface = status;
@@ -639,43 +643,41 @@ acm_bind(struct usb_configuration *c, struct usb_function *f)
status = usb_interface_id(c, f);
if (status < 0)
- goto fail;
+ return status;
acm->data_id = status;
acm_data_interface_desc.bInterfaceNumber = status;
acm_union_desc.bSlaveInterface0 = status;
acm_call_mgmt_descriptor.bDataInterface = status;
- status = -ENODEV;
-
/* allocate instance-specific endpoints */
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc);
if (!ep)
- goto fail;
+ return -ENODEV;
acm->port.in = ep;
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc);
if (!ep)
- goto fail;
+ return -ENODEV;
acm->port.out = ep;
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc);
if (!ep)
- goto fail;
+ return -ENODEV;
acm->notify = ep;
acm_iad_descriptor.bFunctionProtocol = acm->bInterfaceProtocol;
acm_control_interface_desc.bInterfaceProtocol = acm->bInterfaceProtocol;
/* allocate notification */
- acm->notify_req = gs_alloc_req(ep,
- sizeof(struct usb_cdc_notification) + 2,
- GFP_KERNEL);
- if (!acm->notify_req)
- goto fail;
+ request = gs_alloc_req(ep,
+ sizeof(struct usb_cdc_notification) + 2,
+ GFP_KERNEL);
+ if (!request)
+ return -ENODEV;
- acm->notify_req->complete = acm_cdc_notify_complete;
- acm->notify_req->context = acm;
+ request->complete = acm_cdc_notify_complete;
+ request->context = acm;
/* support all relevant hardware speeds... we expect that when
* hardware is dual speed, all bulk-capable endpoints work at
@@ -692,7 +694,9 @@ acm_bind(struct usb_configuration *c, struct usb_function *f)
status = usb_assign_descriptors(f, acm_fs_function, acm_hs_function,
acm_ss_function, acm_ss_function);
if (status)
- goto fail;
+ return status;
+
+ acm->notify_req = no_free_ptr(request);
dev_dbg(&cdev->gadget->dev,
"acm ttyGS%d: IN/%s OUT/%s NOTIFY/%s\n",
@@ -700,14 +704,6 @@ acm_bind(struct usb_configuration *c, struct usb_function *f)
acm->port.in->name, acm->port.out->name,
acm->notify->name);
return 0;
-
-fail:
- if (acm->notify_req)
- gs_free_req(acm->notify, acm->notify_req);
-
- ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status);
-
- return status;
}
static void acm_unbind(struct usb_configuration *c, struct usb_function *f)
diff --git a/drivers/usb/gadget/function/f_ecm.c b/drivers/usb/gadget/function/f_ecm.c
index 027226325039..675d2bc538a4 100644
--- a/drivers/usb/gadget/function/f_ecm.c
+++ b/drivers/usb/gadget/function/f_ecm.c
@@ -8,6 +8,7 @@
/* #define VERBOSE_DEBUG */
+#include <linux/cleanup.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -15,6 +16,8 @@
#include <linux/etherdevice.h>
#include <linux/string_choices.h>
+#include <linux/usb/gadget.h>
+
#include "u_ether.h"
#include "u_ether_configfs.h"
#include "u_ecm.h"
@@ -678,6 +681,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f)
struct usb_ep *ep;
struct f_ecm_opts *ecm_opts;
+ struct usb_request *request __free(free_usb_request) = NULL;
if (!can_support_ecm(cdev->gadget))
return -EINVAL;
@@ -711,7 +715,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f)
/* allocate instance-specific interface IDs */
status = usb_interface_id(c, f);
if (status < 0)
- goto fail;
+ return status;
ecm->ctrl_id = status;
ecm_iad_descriptor.bFirstInterface = status;
@@ -720,24 +724,22 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f)
status = usb_interface_id(c, f);
if (status < 0)
- goto fail;
+ return status;
ecm->data_id = status;
ecm_data_nop_intf.bInterfaceNumber = status;
ecm_data_intf.bInterfaceNumber = status;
ecm_union_desc.bSlaveInterface0 = status;
- status = -ENODEV;
-
/* allocate instance-specific endpoints */
ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_in_desc);
if (!ep)
- goto fail;
+ return -ENODEV;
ecm->port.in_ep = ep;
ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_out_desc);
if (!ep)
- goto fail;
+ return -ENODEV;
ecm->port.out_ep = ep;
/* NOTE: a status/notification endpoint is *OPTIONAL* but we
@@ -746,20 +748,18 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f)
*/
ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_notify_desc);
if (!ep)
- goto fail;
+ return -ENODEV;
ecm->notify = ep;
- status = -ENOMEM;
-
/* allocate notification request and buffer */
- ecm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL);
- if (!ecm->notify_req)
- goto fail;
- ecm->notify_req->buf = kmalloc(ECM_STATUS_BYTECOUNT, GFP_KERNEL);
- if (!ecm->notify_req->buf)
- goto fail;
- ecm->notify_req->context = ecm;
- ecm->notify_req->complete = ecm_notify_complete;
+ request = usb_ep_alloc_request(ep, GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+ request->buf = kmalloc(ECM_STATUS_BYTECOUNT, GFP_KERNEL);
+ if (!request->buf)
+ return -ENOMEM;
+ request->context = ecm;
+ request->complete = ecm_notify_complete;
/* support all relevant hardware speeds... we expect that when
* hardware is dual speed, all bulk-capable endpoints work at
@@ -778,7 +778,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f)
status = usb_assign_descriptors(f, ecm_fs_function, ecm_hs_function,
ecm_ss_function, ecm_ss_function);
if (status)
- goto fail;
+ return status;
/* NOTE: all that is done without knowing or caring about
* the network link ... which is unavailable to this code
@@ -788,20 +788,12 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f)
ecm->port.open = ecm_open;
ecm->port.close = ecm_close;
+ ecm->notify_req = no_free_ptr(request);
+
DBG(cdev, "CDC Ethernet: IN/%s OUT/%s NOTIFY/%s\n",
ecm->port.in_ep->name, ecm->port.out_ep->name,
ecm->notify->name);
return 0;
-
-fail:
- if (ecm->notify_req) {
- kfree(ecm->notify_req->buf);
- usb_ep_free_request(ecm->notify, ecm->notify_req);
- }
-
- ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
-
- return status;
}
static inline struct f_ecm_opts *to_f_ecm_opts(struct config_item *item)
diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
index 5246fa6af3d6..47cfbe41fdff 100644
--- a/drivers/usb/gadget/function/f_fs.c
+++ b/drivers/usb/gadget/function/f_fs.c
@@ -2407,7 +2407,12 @@ static int ffs_func_eps_enable(struct ffs_function *func)
ep = func->eps;
epfile = ffs->epfiles;
count = ffs->eps_count;
- while(count--) {
+ if (!epfile) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ while (count--) {
ep->ep->driver_data = ep;
ret = config_ep_by_speed(func->gadget, &func->function, ep->ep);
@@ -2431,6 +2436,7 @@ static int ffs_func_eps_enable(struct ffs_function *func)
}
wake_up_interruptible(&ffs->wait);
+done:
spin_unlock_irqrestore(&func->ffs->eps_lock, flags);
return ret;
diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c
index 8e1d1e884050..307ea563af95 100644
--- a/drivers/usb/gadget/function/f_hid.c
+++ b/drivers/usb/gadget/function/f_hid.c
@@ -511,7 +511,7 @@ try_again:
}
req->status = 0;
- req->zero = 0;
+ req->zero = 1;
req->length = count;
req->complete = f_hidg_req_complete;
req->context = hidg;
@@ -967,7 +967,7 @@ stall:
return -EOPNOTSUPP;
respond:
- req->zero = 0;
+ req->zero = 1;
req->length = length;
status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
if (status < 0)
diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c
index 58b0dd575af3..0e38330271d5 100644
--- a/drivers/usb/gadget/function/f_ncm.c
+++ b/drivers/usb/gadget/function/f_ncm.c
@@ -11,6 +11,7 @@
* Copyright (C) 2008 Nokia Corporation
*/
+#include <linux/cleanup.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/module.h>
@@ -20,6 +21,7 @@
#include <linux/string_choices.h>
#include <linux/usb/cdc.h>
+#include <linux/usb/gadget.h>
#include "u_ether.h"
#include "u_ether_configfs.h"
@@ -1436,18 +1438,18 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
struct usb_ep *ep;
struct f_ncm_opts *ncm_opts;
+ struct usb_os_desc_table *os_desc_table __free(kfree) = NULL;
+ struct usb_request *request __free(free_usb_request) = NULL;
+
if (!can_support_ecm(cdev->gadget))
return -EINVAL;
ncm_opts = container_of(f->fi, struct f_ncm_opts, func_inst);
if (cdev->use_os_string) {
- f->os_desc_table = kzalloc(sizeof(*f->os_desc_table),
- GFP_KERNEL);
- if (!f->os_desc_table)
+ os_desc_table = kzalloc(sizeof(*os_desc_table), GFP_KERNEL);
+ if (!os_desc_table)
return -ENOMEM;
- f->os_desc_n = 1;
- f->os_desc_table[0].os_desc = &ncm_opts->ncm_os_desc;
}
mutex_lock(&ncm_opts->lock);
@@ -1459,16 +1461,17 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
mutex_unlock(&ncm_opts->lock);
if (status)
- goto fail;
+ return status;
ncm_opts->bound = true;
+ ncm_string_defs[1].s = ncm->ethaddr;
+
us = usb_gstrings_attach(cdev, ncm_strings,
ARRAY_SIZE(ncm_string_defs));
- if (IS_ERR(us)) {
- status = PTR_ERR(us);
- goto fail;
- }
+ if (IS_ERR(us))
+ return PTR_ERR(us);
+
ncm_control_intf.iInterface = us[STRING_CTRL_IDX].id;
ncm_data_nop_intf.iInterface = us[STRING_DATA_IDX].id;
ncm_data_intf.iInterface = us[STRING_DATA_IDX].id;
@@ -1478,20 +1481,16 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
/* allocate instance-specific interface IDs */
status = usb_interface_id(c, f);
if (status < 0)
- goto fail;
+ return status;
ncm->ctrl_id = status;
ncm_iad_desc.bFirstInterface = status;
ncm_control_intf.bInterfaceNumber = status;
ncm_union_desc.bMasterInterface0 = status;
- if (cdev->use_os_string)
- f->os_desc_table[0].if_id =
- ncm_iad_desc.bFirstInterface;
-
status = usb_interface_id(c, f);
if (status < 0)
- goto fail;
+ return status;
ncm->data_id = status;
ncm_data_nop_intf.bInterfaceNumber = status;
@@ -1500,35 +1499,31 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
ecm_desc.wMaxSegmentSize = cpu_to_le16(ncm_opts->max_segment_size);
- status = -ENODEV;
-
/* allocate instance-specific endpoints */
ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_in_desc);
if (!ep)
- goto fail;
+ return -ENODEV;
ncm->port.in_ep = ep;
ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_out_desc);
if (!ep)
- goto fail;
+ return -ENODEV;
ncm->port.out_ep = ep;
ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_notify_desc);
if (!ep)
- goto fail;
+ return -ENODEV;
ncm->notify = ep;
- status = -ENOMEM;
-
/* allocate notification request and buffer */
- ncm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL);
- if (!ncm->notify_req)
- goto fail;
- ncm->notify_req->buf = kmalloc(NCM_STATUS_BYTECOUNT, GFP_KERNEL);
- if (!ncm->notify_req->buf)
- goto fail;
- ncm->notify_req->context = ncm;
- ncm->notify_req->complete = ncm_notify_complete;
+ request = usb_ep_alloc_request(ep, GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+ request->buf = kmalloc(NCM_STATUS_BYTECOUNT, GFP_KERNEL);
+ if (!request->buf)
+ return -ENOMEM;
+ request->context = ncm;
+ request->complete = ncm_notify_complete;
/*
* support all relevant hardware speeds... we expect that when
@@ -1548,7 +1543,7 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
status = usb_assign_descriptors(f, ncm_fs_function, ncm_hs_function,
ncm_ss_function, ncm_ss_function);
if (status)
- goto fail;
+ return status;
/*
* NOTE: all that is done without knowing or caring about
@@ -1561,23 +1556,18 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
hrtimer_setup(&ncm->task_timer, ncm_tx_timeout, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);
+ if (cdev->use_os_string) {
+ os_desc_table[0].os_desc = &ncm_opts->ncm_os_desc;
+ os_desc_table[0].if_id = ncm_iad_desc.bFirstInterface;
+ f->os_desc_table = no_free_ptr(os_desc_table);
+ f->os_desc_n = 1;
+ }
+ ncm->notify_req = no_free_ptr(request);
+
DBG(cdev, "CDC Network: IN/%s OUT/%s NOTIFY/%s\n",
ncm->port.in_ep->name, ncm->port.out_ep->name,
ncm->notify->name);
return 0;
-
-fail:
- kfree(f->os_desc_table);
- f->os_desc_n = 0;
-
- if (ncm->notify_req) {
- kfree(ncm->notify_req->buf);
- usb_ep_free_request(ncm->notify, ncm->notify_req);
- }
-
- ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
-
- return status;
}
static inline struct f_ncm_opts *to_f_ncm_opts(struct config_item *item)
@@ -1771,7 +1761,6 @@ static struct usb_function *ncm_alloc(struct usb_function_instance *fi)
mutex_unlock(&opts->lock);
return ERR_PTR(-EINVAL);
}
- ncm_string_defs[STRING_MAC_IDX].s = ncm->ethaddr;
spin_lock_init(&ncm->lock);
ncm_reset_values(ncm);
diff --git a/drivers/usb/gadget/function/f_rndis.c b/drivers/usb/gadget/function/f_rndis.c
index 7cec19d65fb5..7451e7cb7a85 100644
--- a/drivers/usb/gadget/function/f_rndis.c
+++ b/drivers/usb/gadget/function/f_rndis.c
@@ -19,6 +19,8 @@
#include <linux/atomic.h>
+#include <linux/usb/gadget.h>
+
#include "u_ether.h"
#include "u_ether_configfs.h"
#include "u_rndis.h"
@@ -662,6 +664,8 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
struct usb_ep *ep;
struct f_rndis_opts *rndis_opts;
+ struct usb_os_desc_table *os_desc_table __free(kfree) = NULL;
+ struct usb_request *request __free(free_usb_request) = NULL;
if (!can_support_rndis(c))
return -EINVAL;
@@ -669,12 +673,9 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
rndis_opts = container_of(f->fi, struct f_rndis_opts, func_inst);
if (cdev->use_os_string) {
- f->os_desc_table = kzalloc(sizeof(*f->os_desc_table),
- GFP_KERNEL);
- if (!f->os_desc_table)
+ os_desc_table = kzalloc(sizeof(*os_desc_table), GFP_KERNEL);
+ if (!os_desc_table)
return -ENOMEM;
- f->os_desc_n = 1;
- f->os_desc_table[0].os_desc = &rndis_opts->rndis_os_desc;
}
rndis_iad_descriptor.bFunctionClass = rndis_opts->class;
@@ -692,16 +693,14 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
gether_set_gadget(rndis_opts->net, cdev->gadget);
status = gether_register_netdev(rndis_opts->net);
if (status)
- goto fail;
+ return status;
rndis_opts->bound = true;
}
us = usb_gstrings_attach(cdev, rndis_strings,
ARRAY_SIZE(rndis_string_defs));
- if (IS_ERR(us)) {
- status = PTR_ERR(us);
- goto fail;
- }
+ if (IS_ERR(us))
+ return PTR_ERR(us);
rndis_control_intf.iInterface = us[0].id;
rndis_data_intf.iInterface = us[1].id;
rndis_iad_descriptor.iFunction = us[2].id;
@@ -709,36 +708,30 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
/* allocate instance-specific interface IDs */
status = usb_interface_id(c, f);
if (status < 0)
- goto fail;
+ return status;
rndis->ctrl_id = status;
rndis_iad_descriptor.bFirstInterface = status;
rndis_control_intf.bInterfaceNumber = status;
rndis_union_desc.bMasterInterface0 = status;
- if (cdev->use_os_string)
- f->os_desc_table[0].if_id =
- rndis_iad_descriptor.bFirstInterface;
-
status = usb_interface_id(c, f);
if (status < 0)
- goto fail;
+ return status;
rndis->data_id = status;
rndis_data_intf.bInterfaceNumber = status;
rndis_union_desc.bSlaveInterface0 = status;
- status = -ENODEV;
-
/* allocate instance-specific endpoints */
ep = usb_ep_autoconfig(cdev->gadget, &fs_in_desc);
if (!ep)
- goto fail;
+ return -ENODEV;
rndis->port.in_ep = ep;
ep = usb_ep_autoconfig(cdev->gadget, &fs_out_desc);
if (!ep)
- goto fail;
+ return -ENODEV;
rndis->port.out_ep = ep;
/* NOTE: a status/notification endpoint is, strictly speaking,
@@ -747,21 +740,19 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
*/
ep = usb_ep_autoconfig(cdev->gadget, &fs_notify_desc);
if (!ep)
- goto fail;
+ return -ENODEV;
rndis->notify = ep;
- status = -ENOMEM;
-
/* allocate notification request and buffer */
- rndis->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL);
- if (!rndis->notify_req)
- goto fail;
- rndis->notify_req->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL);
- if (!rndis->notify_req->buf)
- goto fail;
- rndis->notify_req->length = STATUS_BYTECOUNT;
- rndis->notify_req->context = rndis;
- rndis->notify_req->complete = rndis_response_complete;
+ request = usb_ep_alloc_request(ep, GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+ request->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL);
+ if (!request->buf)
+ return -ENOMEM;
+ request->length = STATUS_BYTECOUNT;
+ request->context = rndis;
+ request->complete = rndis_response_complete;
/* support all relevant hardware speeds... we expect that when
* hardware is dual speed, all bulk-capable endpoints work at
@@ -778,7 +769,7 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
status = usb_assign_descriptors(f, eth_fs_function, eth_hs_function,
eth_ss_function, eth_ss_function);
if (status)
- goto fail;
+ return status;
rndis->port.open = rndis_open;
rndis->port.close = rndis_close;
@@ -789,9 +780,18 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
if (rndis->manufacturer && rndis->vendorID &&
rndis_set_param_vendor(rndis->params, rndis->vendorID,
rndis->manufacturer)) {
- status = -EINVAL;
- goto fail_free_descs;
+ usb_free_all_descriptors(f);
+ return -EINVAL;
+ }
+
+ if (cdev->use_os_string) {
+ os_desc_table[0].os_desc = &rndis_opts->rndis_os_desc;
+ os_desc_table[0].if_id = rndis_iad_descriptor.bFirstInterface;
+ f->os_desc_table = no_free_ptr(os_desc_table);
+ f->os_desc_n = 1;
+
}
+ rndis->notify_req = no_free_ptr(request);
/* NOTE: all that is done without knowing or caring about
* the network link ... which is unavailable to this code
@@ -802,21 +802,6 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
rndis->port.in_ep->name, rndis->port.out_ep->name,
rndis->notify->name);
return 0;
-
-fail_free_descs:
- usb_free_all_descriptors(f);
-fail:
- kfree(f->os_desc_table);
- f->os_desc_n = 0;
-
- if (rndis->notify_req) {
- kfree(rndis->notify_req->buf);
- usb_ep_free_request(rndis->notify, rndis->notify_req);
- }
-
- ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
-
- return status;
}
void rndis_borrow_net(struct usb_function_instance *f, struct net_device *net)
diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-trace.h b/drivers/usb/gadget/udc/cdns2/cdns2-trace.h
index ade1752956b1..f4df0e2ff853 100644
--- a/drivers/usb/gadget/udc/cdns2/cdns2-trace.h
+++ b/drivers/usb/gadget/udc/cdns2/cdns2-trace.h
@@ -47,16 +47,6 @@ DEFINE_EVENT(cdns2_log_enable_disable, cdns2_pullup,
TP_ARGS(set)
);
-DEFINE_EVENT(cdns2_log_enable_disable, cdns2_lpm,
- TP_PROTO(int set),
- TP_ARGS(set)
-);
-
-DEFINE_EVENT(cdns2_log_enable_disable, cdns2_may_wakeup,
- TP_PROTO(int set),
- TP_ARGS(set)
-);
-
DECLARE_EVENT_CLASS(cdns2_log_simple,
TP_PROTO(char *msg),
TP_ARGS(msg),
@@ -79,11 +69,6 @@ DEFINE_EVENT(cdns2_log_simple, cdns2_ep0_status_stage,
TP_ARGS(msg)
);
-DEFINE_EVENT(cdns2_log_simple, cdns2_ep0_set_config,
- TP_PROTO(char *msg),
- TP_ARGS(msg)
-);
-
DEFINE_EVENT(cdns2_log_simple, cdns2_ep0_setup,
TP_PROTO(char *msg),
TP_ARGS(msg)
@@ -340,11 +325,6 @@ DEFINE_EVENT(cdns2_log_request, cdns2_free_request,
TP_ARGS(preq)
);
-DEFINE_EVENT(cdns2_log_request, cdns2_ep_queue,
- TP_PROTO(struct cdns2_request *preq),
- TP_ARGS(preq)
-);
-
DEFINE_EVENT(cdns2_log_request, cdns2_request_dequeue,
TP_PROTO(struct cdns2_request *preq),
TP_ARGS(preq)
@@ -355,50 +335,6 @@ DEFINE_EVENT(cdns2_log_request, cdns2_request_giveback,
TP_ARGS(preq)
);
-TRACE_EVENT(cdns2_ep0_enqueue,
- TP_PROTO(struct cdns2_device *dev_priv, struct usb_request *request),
- TP_ARGS(dev_priv, request),
- TP_STRUCT__entry(
- __field(int, dir)
- __field(int, length)
- ),
- TP_fast_assign(
- __entry->dir = dev_priv->eps[0].dir;
- __entry->length = request->length;
- ),
- TP_printk("Queue to ep0%s length: %u", __entry->dir ? "in" : "out",
- __entry->length)
-);
-
-DECLARE_EVENT_CLASS(cdns2_log_map_request,
- TP_PROTO(struct cdns2_request *priv_req),
- TP_ARGS(priv_req),
- TP_STRUCT__entry(
- __string(name, priv_req->pep->name)
- __field(struct usb_request *, req)
- __field(void *, buf)
- __field(dma_addr_t, dma)
- ),
- TP_fast_assign(
- __assign_str(name);
- __entry->req = &priv_req->request;
- __entry->buf = priv_req->request.buf;
- __entry->dma = priv_req->request.dma;
- ),
- TP_printk("%s: req: %p, req buf %p, dma %p",
- __get_str(name), __entry->req, __entry->buf, &__entry->dma
- )
-);
-
-DEFINE_EVENT(cdns2_log_map_request, cdns2_map_request,
- TP_PROTO(struct cdns2_request *req),
- TP_ARGS(req)
-);
-DEFINE_EVENT(cdns2_log_map_request, cdns2_mapped_request,
- TP_PROTO(struct cdns2_request *req),
- TP_ARGS(req)
-);
-
DECLARE_EVENT_CLASS(cdns2_log_trb,
TP_PROTO(struct cdns2_endpoint *pep, struct cdns2_trb *trb),
TP_ARGS(pep, trb),
@@ -507,11 +443,6 @@ DEFINE_EVENT(cdns2_log_ep, cdns2_gadget_ep_disable,
TP_ARGS(pep)
);
-DEFINE_EVENT(cdns2_log_ep, cdns2_iso_out_ep_disable,
- TP_PROTO(struct cdns2_endpoint *pep),
- TP_ARGS(pep)
-);
-
DEFINE_EVENT(cdns2_log_ep, cdns2_ep_busy_try_halt_again,
TP_PROTO(struct cdns2_endpoint *pep),
TP_ARGS(pep)
diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c
index d709e24c1fd4..694653761c44 100644
--- a/drivers/usb/gadget/udc/core.c
+++ b/drivers/usb/gadget/udc/core.c
@@ -194,6 +194,9 @@ struct usb_request *usb_ep_alloc_request(struct usb_ep *ep,
req = ep->ops->alloc_request(ep, gfp_flags);
+ if (req)
+ req->ep = ep;
+
trace_usb_ep_alloc_request(ep, req, req ? 0 : -ENOMEM);
return req;
@@ -1125,6 +1128,7 @@ void usb_gadget_set_state(struct usb_gadget *gadget,
{
gadget->state = state;
schedule_work(&gadget->work);
+ trace_usb_gadget_set_state(gadget, 0);
}
EXPORT_SYMBOL_GPL(usb_gadget_set_state);
diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c
index 1d3085cc9d22..0c38fc37b6e6 100644
--- a/drivers/usb/gadget/udc/tegra-xudc.c
+++ b/drivers/usb/gadget/udc/tegra-xudc.c
@@ -812,8 +812,7 @@ static void tegra_xudc_update_data_role(struct tegra_xudc *xudc,
return;
}
- xudc->device_mode = (usbphy->last_event == USB_EVENT_VBUS) ? true :
- false;
+ xudc->device_mode = usbphy->last_event == USB_EVENT_VBUS;
phy_index = tegra_xudc_get_phy_index(xudc, usbphy);
dev_dbg(xudc->dev, "%s(): current phy index is %d\n", __func__,
diff --git a/drivers/usb/gadget/udc/trace.h b/drivers/usb/gadget/udc/trace.h
index 4e334298b0e8..fa3e6ddf0a12 100644
--- a/drivers/usb/gadget/udc/trace.h
+++ b/drivers/usb/gadget/udc/trace.h
@@ -81,6 +81,11 @@ DECLARE_EVENT_CLASS(udc_log_gadget,
__entry->ret)
);
+DEFINE_EVENT(udc_log_gadget, usb_gadget_set_state,
+ TP_PROTO(struct usb_gadget *g, int ret),
+ TP_ARGS(g, ret)
+);
+
DEFINE_EVENT(udc_log_gadget, usb_gadget_frame_number,
TP_PROTO(struct usb_gadget *g, int ret),
TP_ARGS(g, ret)
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 109100cc77a3..c4f17ce5c77b 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -93,7 +93,7 @@ config USB_XHCI_RCAR
default ARCH_RENESAS
help
Say 'Y' to enable the support for the xHCI host controller
- found in Renesas R-Car ARM SoCs.
+ found in Renesas R-Car and RZ/G3E alike ARM SoCs.
config USB_XHCI_RZV2M
bool "xHCI support for Renesas RZ/V2M SoC"
diff --git a/drivers/usb/host/max3421-hcd.c b/drivers/usb/host/max3421-hcd.c
index dcf31a592f5d..4b5f03f683f7 100644
--- a/drivers/usb/host/max3421-hcd.c
+++ b/drivers/usb/host/max3421-hcd.c
@@ -1916,7 +1916,7 @@ error:
if (hcd) {
kfree(max3421_hcd->tx);
kfree(max3421_hcd->rx);
- if (max3421_hcd->spi_thread)
+ if (!IS_ERR_OR_NULL(max3421_hcd->spi_thread))
kthread_stop(max3421_hcd->spi_thread);
usb_put_hcd(hcd);
}
diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c
index 66d970854357..e623e24d3f8e 100644
--- a/drivers/usb/host/ohci-s3c2410.c
+++ b/drivers/usb/host/ohci-s3c2410.c
@@ -448,13 +448,6 @@ static const struct dev_pm_ops ohci_hcd_s3c2410_pm_ops = {
.resume = ohci_hcd_s3c2410_drv_resume,
};
-static const struct of_device_id ohci_hcd_s3c2410_dt_ids[] = {
- { .compatible = "samsung,s3c2410-ohci" },
- { /* sentinel */ }
-};
-
-MODULE_DEVICE_TABLE(of, ohci_hcd_s3c2410_dt_ids);
-
static struct platform_driver ohci_hcd_s3c2410_driver = {
.probe = ohci_hcd_s3c2410_probe,
.remove = ohci_hcd_s3c2410_remove,
@@ -462,7 +455,6 @@ static struct platform_driver ohci_hcd_s3c2410_driver = {
.driver = {
.name = "s3c2410-ohci",
.pm = &ohci_hcd_s3c2410_pm_ops,
- .of_match_table = ohci_hcd_s3c2410_dt_ids,
},
};
diff --git a/drivers/usb/host/xhci-caps.h b/drivers/usb/host/xhci-caps.h
index 4b8ff4815644..89bc83e4f1eb 100644
--- a/drivers/usb/host/xhci-caps.h
+++ b/drivers/usb/host/xhci-caps.h
@@ -89,3 +89,5 @@
#define HCC2_GSC(p) ((p) & (1 << 8))
/* true: HC support Virtualization Based Trusted I/O Capability */
#define HCC2_VTC(p) ((p) & (1 << 9))
+/* true: HC support Double BW on a eUSB2 HS ISOC EP */
+#define HCC2_EUSB2_DIC(p) ((p) & (1 << 11))
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index c4a6544aa107..6e5b6057de79 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -1330,18 +1330,33 @@ static unsigned int xhci_get_endpoint_interval(struct usb_device *udev,
return interval;
}
-/* The "Mult" field in the endpoint context is only set for SuperSpeed isoc eps.
+/*
+ * xHCs without LEC use the "Mult" field in the endpoint context for SuperSpeed
+ * isoc eps, and High speed isoc eps that support bandwidth doubling. Standard
* High speed endpoint descriptors can define "the number of additional
* transaction opportunities per microframe", but that goes in the Max Burst
* endpoint context field.
*/
-static u32 xhci_get_endpoint_mult(struct usb_device *udev,
- struct usb_host_endpoint *ep)
+static u32 xhci_get_endpoint_mult(struct xhci_hcd *xhci,
+ struct usb_device *udev,
+ struct usb_host_endpoint *ep)
{
- if (udev->speed < USB_SPEED_SUPER ||
- !usb_endpoint_xfer_isoc(&ep->desc))
- return 0;
- return ep->ss_ep_comp.bmAttributes;
+ bool lec;
+
+ /* xHCI 1.1 with LEC set does not use mult field, except intel eUSB2 */
+ lec = xhci->hci_version > 0x100 && HCC2_LEC(xhci->hcc_params2);
+
+ /* eUSB2 double isoc bw devices are the only USB2 devices using mult */
+ if (usb_endpoint_is_hs_isoc_double(udev, ep) &&
+ (!lec || xhci->quirks & XHCI_INTEL_HOST))
+ return 1;
+
+ /* SuperSpeed isoc transfers on hosts without LEC uses mult field */
+ if (udev->speed >= USB_SPEED_SUPER &&
+ usb_endpoint_xfer_isoc(&ep->desc) && !lec)
+ return ep->ss_ep_comp.bmAttributes;
+
+ return 0;
}
static u32 xhci_get_endpoint_max_burst(struct usb_device *udev,
@@ -1353,8 +1368,16 @@ static u32 xhci_get_endpoint_max_burst(struct usb_device *udev,
if (udev->speed == USB_SPEED_HIGH &&
(usb_endpoint_xfer_isoc(&ep->desc) ||
- usb_endpoint_xfer_int(&ep->desc)))
+ usb_endpoint_xfer_int(&ep->desc))) {
+ /*
+ * USB 2 Isochronous Double IN Bandwidth ECN uses fixed burst
+ * size and max packets bits 12:11 are invalid.
+ */
+ if (usb_endpoint_is_hs_isoc_double(udev, ep))
+ return 2;
+
return usb_endpoint_maxp_mult(&ep->desc) - 1;
+ }
return 0;
}
@@ -1378,36 +1401,6 @@ static u32 xhci_get_endpoint_type(struct usb_host_endpoint *ep)
return 0;
}
-/* Return the maximum endpoint service interval time (ESIT) payload.
- * Basically, this is the maxpacket size, multiplied by the burst size
- * and mult size.
- */
-static u32 xhci_get_max_esit_payload(struct usb_device *udev,
- struct usb_host_endpoint *ep)
-{
- int max_burst;
- int max_packet;
-
- /* Only applies for interrupt or isochronous endpoints */
- if (usb_endpoint_xfer_control(&ep->desc) ||
- usb_endpoint_xfer_bulk(&ep->desc))
- return 0;
-
- /* SuperSpeedPlus Isoc ep sending over 48k per esit */
- if ((udev->speed >= USB_SPEED_SUPER_PLUS) &&
- USB_SS_SSP_ISOC_COMP(ep->ss_ep_comp.bmAttributes))
- return le32_to_cpu(ep->ssp_isoc_ep_comp.dwBytesPerInterval);
-
- /* SuperSpeed or SuperSpeedPlus Isoc ep with less than 48k per esit */
- if (udev->speed >= USB_SPEED_SUPER)
- return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval);
-
- max_packet = usb_endpoint_maxp(&ep->desc);
- max_burst = usb_endpoint_maxp_mult(&ep->desc);
- /* A 0 in max burst means 1 transfer per ESIT */
- return max_packet * max_burst;
-}
-
/* Set up an endpoint with one ring segment. Do not allocate stream rings.
* Drivers will have to call usb_alloc_streams() to do that.
*/
@@ -1439,13 +1432,20 @@ int xhci_endpoint_init(struct xhci_hcd *xhci,
ring_type = usb_endpoint_type(&ep->desc);
+ /* Ensure host supports double isoc bandwidth for eUSB2 devices */
+ if (usb_endpoint_is_hs_isoc_double(udev, ep) &&
+ !HCC2_EUSB2_DIC(xhci->hcc_params2)) {
+ dev_dbg(&udev->dev, "Double Isoc Bandwidth not supported by xhci\n");
+ return -EINVAL;
+ }
+
/*
* Get values to fill the endpoint context, mostly from ep descriptor.
* The average TRB buffer lengt for bulk endpoints is unclear as we
* have no clue on scatter gather list entry size. For Isoc and Int,
* set it to max available. See xHCI 1.1 spec 4.14.1.1 for details.
*/
- max_esit_payload = xhci_get_max_esit_payload(udev, ep);
+ max_esit_payload = usb_endpoint_max_periodic_payload(udev, ep);
interval = xhci_get_endpoint_interval(udev, ep);
/* Periodic endpoint bInterval limit quirk */
@@ -1462,8 +1462,8 @@ int xhci_endpoint_init(struct xhci_hcd *xhci,
}
}
- mult = xhci_get_endpoint_mult(udev, ep);
- max_packet = usb_endpoint_maxp(&ep->desc);
+ mult = xhci_get_endpoint_mult(xhci, udev, ep);
+ max_packet = xhci_usb_endpoint_maxp(udev, ep);
max_burst = xhci_get_endpoint_max_burst(udev, ep);
avg_trb_len = max_esit_payload;
@@ -1484,9 +1484,6 @@ int xhci_endpoint_init(struct xhci_hcd *xhci,
/* xHCI 1.0 and 1.1 indicates that ctrl ep avg TRB Length should be 8 */
if (usb_endpoint_xfer_control(&ep->desc) && xhci->hci_version >= 0x100)
avg_trb_len = 8;
- /* xhci 1.1 with LEC support doesn't use mult field, use RsvdZ */
- if ((xhci->hci_version > 0x100) && HCC2_LEC(xhci->hcc_params2))
- mult = 0;
/* Set up the endpoint ring */
virt_dev->eps[ep_index].new_ring =
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index 00fac8b233d2..5c8ab519f497 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -610,7 +610,7 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
int retval;
struct xhci_hcd *xhci;
- struct usb_hcd *hcd;
+ struct usb_hcd *hcd, *usb3_hcd;
struct reset_control *reset;
reset = devm_reset_control_get_optional_exclusive(&dev->dev, NULL);
@@ -636,26 +636,32 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id)
hcd = dev_get_drvdata(&dev->dev);
xhci = hcd_to_xhci(hcd);
xhci->reset = reset;
- xhci->shared_hcd = usb_create_shared_hcd(&xhci_pci_hc_driver, &dev->dev,
- pci_name(dev), hcd);
- if (!xhci->shared_hcd) {
- retval = -ENOMEM;
- goto dealloc_usb2_hcd;
- }
- retval = xhci_ext_cap_init(xhci);
- if (retval)
- goto put_usb3_hcd;
+ xhci->allow_single_roothub = 1;
+ if (!xhci_has_one_roothub(xhci)) {
+ xhci->shared_hcd = usb_create_shared_hcd(&xhci_pci_hc_driver, &dev->dev,
+ pci_name(dev), hcd);
+ if (!xhci->shared_hcd) {
+ retval = -ENOMEM;
+ goto dealloc_usb2_hcd;
+ }
- retval = usb_add_hcd(xhci->shared_hcd, dev->irq,
- IRQF_SHARED);
- if (retval)
- goto put_usb3_hcd;
- /* Roothub already marked as USB 3.0 speed */
+ retval = xhci_ext_cap_init(xhci);
+ if (retval)
+ goto put_usb3_hcd;
+
+ retval = usb_add_hcd(xhci->shared_hcd, dev->irq, IRQF_SHARED);
+ if (retval)
+ goto put_usb3_hcd;
+ } else {
+ retval = xhci_ext_cap_init(xhci);
+ if (retval)
+ goto dealloc_usb2_hcd;
+ }
- if (!(xhci->quirks & XHCI_BROKEN_STREAMS) &&
- HCC_MAX_PSA(xhci->hcc_params) >= 4)
- xhci->shared_hcd->can_do_streams = 1;
+ usb3_hcd = xhci_get_usb3_hcd(xhci);
+ if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && HCC_MAX_PSA(xhci->hcc_params) >= 4)
+ usb3_hcd->can_do_streams = 1;
/* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */
pm_runtime_put_noidle(&dev->dev);
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index 5eb51797de32..074d9c731639 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -20,6 +20,7 @@
#include <linux/acpi.h>
#include <linux/usb/of.h>
#include <linux/reset.h>
+#include <linux/usb/xhci-sideband.h>
#include "xhci.h"
#include "xhci-plat.h"
@@ -74,6 +75,16 @@ static int xhci_priv_resume_quirk(struct usb_hcd *hcd)
return priv->resume_quirk(hcd);
}
+static int xhci_priv_post_resume_quirk(struct usb_hcd *hcd)
+{
+ struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd);
+
+ if (!priv->post_resume_quirk)
+ return 0;
+
+ return priv->post_resume_quirk(hcd);
+}
+
static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci)
{
struct xhci_plat_priv *priv = xhci_to_priv(xhci);
@@ -171,6 +182,7 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s
return ret;
pm_runtime_set_active(&pdev->dev);
+ pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_get_noresume(&pdev->dev);
@@ -454,7 +466,7 @@ void xhci_plat_remove(struct platform_device *dev)
}
EXPORT_SYMBOL_GPL(xhci_plat_remove);
-static int xhci_plat_suspend(struct device *dev)
+static int xhci_plat_suspend_common(struct device *dev)
{
struct usb_hcd *hcd = dev_get_drvdata(dev);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
@@ -482,6 +494,25 @@ static int xhci_plat_suspend(struct device *dev)
return 0;
}
+static int xhci_plat_suspend(struct device *dev)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd);
+
+ if (xhci_sideband_check(hcd)) {
+ priv->sideband_at_suspend = 1;
+ dev_dbg(dev, "sideband instance active, skip suspend.\n");
+ return 0;
+ }
+
+ return xhci_plat_suspend_common(dev);
+}
+
+static int xhci_plat_freeze(struct device *dev)
+{
+ return xhci_plat_suspend_common(dev);
+}
+
static int xhci_plat_resume_common(struct device *dev, bool power_lost)
{
struct usb_hcd *hcd = dev_get_drvdata(dev);
@@ -509,6 +540,10 @@ static int xhci_plat_resume_common(struct device *dev, bool power_lost)
if (ret)
goto disable_clks;
+ ret = xhci_priv_post_resume_quirk(hcd);
+ if (ret)
+ goto disable_clks;
+
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
@@ -526,6 +561,20 @@ disable_clks:
static int xhci_plat_resume(struct device *dev)
{
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd);
+
+ if (priv->sideband_at_suspend) {
+ priv->sideband_at_suspend = 0;
+ dev_dbg(dev, "sideband instance active, skip resume.\n");
+ return 0;
+ }
+
+ return xhci_plat_resume_common(dev, false);
+}
+
+static int xhci_plat_thaw(struct device *dev)
+{
return xhci_plat_resume_common(dev, false);
}
@@ -558,9 +607,9 @@ static int __maybe_unused xhci_plat_runtime_resume(struct device *dev)
const struct dev_pm_ops xhci_plat_pm_ops = {
.suspend = pm_sleep_ptr(xhci_plat_suspend),
.resume = pm_sleep_ptr(xhci_plat_resume),
- .freeze = pm_sleep_ptr(xhci_plat_suspend),
- .thaw = pm_sleep_ptr(xhci_plat_resume),
- .poweroff = pm_sleep_ptr(xhci_plat_suspend),
+ .freeze = pm_sleep_ptr(xhci_plat_freeze),
+ .thaw = pm_sleep_ptr(xhci_plat_thaw),
+ .poweroff = pm_sleep_ptr(xhci_plat_freeze),
.restore = pm_sleep_ptr(xhci_plat_restore),
SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend,
diff --git a/drivers/usb/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h
index fe4f95e690fa..00751d851831 100644
--- a/drivers/usb/host/xhci-plat.h
+++ b/drivers/usb/host/xhci-plat.h
@@ -16,10 +16,12 @@ struct xhci_plat_priv {
const char *firmware_name;
unsigned long long quirks;
bool power_lost;
+ unsigned sideband_at_suspend:1;
void (*plat_start)(struct usb_hcd *);
int (*init_quirk)(struct usb_hcd *);
int (*suspend_quirk)(struct usb_hcd *);
int (*resume_quirk)(struct usb_hcd *);
+ int (*post_resume_quirk)(struct usb_hcd *);
};
#define hcd_to_xhci_priv(h) ((struct xhci_plat_priv *)hcd_to_xhci(h)->priv)
diff --git a/drivers/usb/host/xhci-rcar-regs.h b/drivers/usb/host/xhci-rcar-regs.h
new file mode 100644
index 000000000000..5ecbda858be0
--- /dev/null
+++ b/drivers/usb/host/xhci-rcar-regs.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __XHCI_RCAR_H
+#define __XHCI_RCAR_H
+
+/*** Register Offset ***/
+#define RCAR_USB3_AXH_STA 0x104 /* AXI Host Control Status */
+#define RCAR_USB3_INT_ENA 0x224 /* Interrupt Enable */
+#define RCAR_USB3_DL_CTRL 0x250 /* FW Download Control & Status */
+#define RCAR_USB3_FW_DATA0 0x258 /* FW Data0 */
+
+#define RCAR_USB3_LCLK 0xa44 /* LCLK Select */
+#define RCAR_USB3_CONF1 0xa48 /* USB3.0 Configuration1 */
+#define RCAR_USB3_CONF2 0xa5c /* USB3.0 Configuration2 */
+#define RCAR_USB3_CONF3 0xaa8 /* USB3.0 Configuration3 */
+#define RCAR_USB3_RX_POL 0xab0 /* USB3.0 RX Polarity */
+#define RCAR_USB3_TX_POL 0xab8 /* USB3.0 TX Polarity */
+
+/*** Register Settings ***/
+/* AXI Host Control Status */
+#define RCAR_USB3_AXH_STA_B3_PLL_ACTIVE 0x00010000
+#define RCAR_USB3_AXH_STA_B2_PLL_ACTIVE 0x00000001
+#define RCAR_USB3_AXH_STA_PLL_ACTIVE_MASK (RCAR_USB3_AXH_STA_B3_PLL_ACTIVE | \
+ RCAR_USB3_AXH_STA_B2_PLL_ACTIVE)
+
+/* Interrupt Enable */
+#define RCAR_USB3_INT_XHC_ENA 0x00000001
+#define RCAR_USB3_INT_PME_ENA 0x00000002
+#define RCAR_USB3_INT_HSE_ENA 0x00000004
+#define RCAR_USB3_INT_ENA_VAL (RCAR_USB3_INT_XHC_ENA | \
+ RCAR_USB3_INT_PME_ENA | RCAR_USB3_INT_HSE_ENA)
+
+/* FW Download Control & Status */
+#define RCAR_USB3_DL_CTRL_ENABLE 0x00000001
+#define RCAR_USB3_DL_CTRL_FW_SUCCESS 0x00000010
+#define RCAR_USB3_DL_CTRL_FW_SET_DATA0 0x00000100
+
+/* LCLK Select */
+#define RCAR_USB3_LCLK_ENA_VAL 0x01030001
+
+/* USB3.0 Configuration */
+#define RCAR_USB3_CONF1_VAL 0x00030204
+#define RCAR_USB3_CONF2_VAL 0x00030300
+#define RCAR_USB3_CONF3_VAL 0x13802007
+
+/* USB3.0 Polarity */
+#define RCAR_USB3_RX_POL_VAL BIT(21)
+#define RCAR_USB3_TX_POL_VAL BIT(4)
+
+#endif /* __XHCI_RCAR_H */
diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c
index 1cc082a3b793..8a993ee21c87 100644
--- a/drivers/usb/host/xhci-rcar.c
+++ b/drivers/usb/host/xhci-rcar.c
@@ -11,9 +11,12 @@
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/usb/phy.h>
+#include <linux/reset.h>
#include "xhci.h"
#include "xhci-plat.h"
+#include "xhci-rcar-regs.h"
+#include "xhci-rzg3e-regs.h"
#include "xhci-rzv2m.h"
#define XHCI_RCAR_FIRMWARE_NAME_V1 "r8a779x_usb3_v1.dlmem"
@@ -29,50 +32,6 @@
MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V1);
MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V3);
-/*** Register Offset ***/
-#define RCAR_USB3_AXH_STA 0x104 /* AXI Host Control Status */
-#define RCAR_USB3_INT_ENA 0x224 /* Interrupt Enable */
-#define RCAR_USB3_DL_CTRL 0x250 /* FW Download Control & Status */
-#define RCAR_USB3_FW_DATA0 0x258 /* FW Data0 */
-
-#define RCAR_USB3_LCLK 0xa44 /* LCLK Select */
-#define RCAR_USB3_CONF1 0xa48 /* USB3.0 Configuration1 */
-#define RCAR_USB3_CONF2 0xa5c /* USB3.0 Configuration2 */
-#define RCAR_USB3_CONF3 0xaa8 /* USB3.0 Configuration3 */
-#define RCAR_USB3_RX_POL 0xab0 /* USB3.0 RX Polarity */
-#define RCAR_USB3_TX_POL 0xab8 /* USB3.0 TX Polarity */
-
-/*** Register Settings ***/
-/* AXI Host Control Status */
-#define RCAR_USB3_AXH_STA_B3_PLL_ACTIVE 0x00010000
-#define RCAR_USB3_AXH_STA_B2_PLL_ACTIVE 0x00000001
-#define RCAR_USB3_AXH_STA_PLL_ACTIVE_MASK (RCAR_USB3_AXH_STA_B3_PLL_ACTIVE | \
- RCAR_USB3_AXH_STA_B2_PLL_ACTIVE)
-
-/* Interrupt Enable */
-#define RCAR_USB3_INT_XHC_ENA 0x00000001
-#define RCAR_USB3_INT_PME_ENA 0x00000002
-#define RCAR_USB3_INT_HSE_ENA 0x00000004
-#define RCAR_USB3_INT_ENA_VAL (RCAR_USB3_INT_XHC_ENA | \
- RCAR_USB3_INT_PME_ENA | RCAR_USB3_INT_HSE_ENA)
-
-/* FW Download Control & Status */
-#define RCAR_USB3_DL_CTRL_ENABLE 0x00000001
-#define RCAR_USB3_DL_CTRL_FW_SUCCESS 0x00000010
-#define RCAR_USB3_DL_CTRL_FW_SET_DATA0 0x00000100
-
-/* LCLK Select */
-#define RCAR_USB3_LCLK_ENA_VAL 0x01030001
-
-/* USB3.0 Configuration */
-#define RCAR_USB3_CONF1_VAL 0x00030204
-#define RCAR_USB3_CONF2_VAL 0x00030300
-#define RCAR_USB3_CONF3_VAL 0x13802007
-
-/* USB3.0 Polarity */
-#define RCAR_USB3_RX_POL_VAL BIT(21)
-#define RCAR_USB3_TX_POL_VAL BIT(4)
-
static void xhci_rcar_start_gen2(struct usb_hcd *hcd)
{
/* LCLK Select */
@@ -110,6 +69,48 @@ static void xhci_rcar_start(struct usb_hcd *hcd)
}
}
+static void xhci_rzg3e_start(struct usb_hcd *hcd)
+{
+ u32 int_en;
+
+ if (hcd->regs) {
+ /* Update the controller initial setting */
+ writel(0x03130200, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(0));
+ writel(0x00160200, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(1));
+ writel(0x03150000, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(2));
+ writel(0x03130200, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(3));
+ writel(0x00180000, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(4));
+
+ /* Interrupt Enable */
+ int_en = readl(hcd->regs + RZG3E_USB3_HOST_INTEN);
+ int_en |= RZG3E_USB3_HOST_INTEN_ENA;
+ writel(int_en, hcd->regs + RZG3E_USB3_HOST_INTEN);
+ }
+}
+
+static int xhci_rzg3e_resume(struct usb_hcd *hcd)
+{
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+ return reset_control_deassert(xhci->reset);
+}
+
+static int xhci_rzg3e_post_resume(struct usb_hcd *hcd)
+{
+ xhci_rzg3e_start(hcd);
+
+ return 0;
+}
+
+static int xhci_rzg3e_suspend(struct usb_hcd *hcd)
+{
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+ reset_control_assert(xhci->reset);
+
+ return 0;
+}
+
static int xhci_rcar_download_firmware(struct usb_hcd *hcd)
{
struct device *dev = hcd->self.controller;
@@ -233,6 +234,14 @@ static const struct xhci_plat_priv xhci_plat_renesas_rzv2m = {
.plat_start = xhci_rzv2m_start,
};
+static const struct xhci_plat_priv xhci_plat_renesas_rzg3e = {
+ .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_RESET_ON_RESUME | XHCI_SUSPEND_RESUME_CLKS,
+ .plat_start = xhci_rzg3e_start,
+ .suspend_quirk = xhci_rzg3e_suspend,
+ .resume_quirk = xhci_rzg3e_resume,
+ .post_resume_quirk = xhci_rzg3e_post_resume,
+};
+
static const struct of_device_id usb_xhci_of_match[] = {
{
.compatible = "renesas,xhci-r8a7790",
@@ -250,6 +259,9 @@ static const struct of_device_id usb_xhci_of_match[] = {
.compatible = "renesas,xhci-r8a7796",
.data = &xhci_plat_renesas_rcar_gen3,
}, {
+ .compatible = "renesas,r9a09g047-xhci",
+ .data = &xhci_plat_renesas_rzg3e,
+ }, {
.compatible = "renesas,rcar-gen2-xhci",
.data = &xhci_plat_renesas_rcar_gen2,
}, {
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 4f8f5aab109d..8e209aa33ea7 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -711,7 +711,7 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci,
return -ENODEV;
}
- hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id);
+ hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id) & TR_DEQ_PTR_MASK;
new_seg = ep_ring->deq_seg;
new_deq = ep_ring->dequeue;
new_cycle = le32_to_cpu(td->end_trb->generic.field[3]) & TRB_CYCLE;
@@ -723,7 +723,7 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci,
*/
do {
if (!hw_dequeue_found && xhci_trb_virt_to_dma(new_seg, new_deq)
- == (dma_addr_t)(hw_dequeue & ~0xf)) {
+ == (dma_addr_t)hw_dequeue) {
hw_dequeue_found = true;
if (td_last_trb_found)
break;
@@ -1066,7 +1066,7 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep)
*/
hw_deq = xhci_get_hw_deq(xhci, ep->vdev, ep->ep_index,
td->urb->stream_id);
- hw_deq &= ~0xf;
+ hw_deq &= TR_DEQ_PTR_MASK;
if (td->cancel_status == TD_HALTED || trb_in_td(td, hw_deq)) {
switch (td->cancel_status) {
@@ -1156,7 +1156,7 @@ static struct xhci_td *find_halted_td(struct xhci_virt_ep *ep)
if (!list_empty(&ep->ring->td_list)) { /* Not streams compatible */
hw_deq = xhci_get_hw_deq(ep->xhci, ep->vdev, ep->ep_index, 0);
- hw_deq &= ~0xf;
+ hw_deq &= TR_DEQ_PTR_MASK;
td = list_first_entry(&ep->ring->td_list, struct xhci_td, td_list);
if (trb_in_td(td, hw_deq))
return td;
@@ -1262,19 +1262,17 @@ reset_done:
* Stopped state, but it will soon change to Running.
*
* Assume this bug on unexpected Stop Endpoint failures.
- * Keep retrying until the EP starts and stops again.
+ * Keep retrying until the EP starts and stops again or
+ * up to a timeout (a defective HC may never start, or a
+ * driver bug may cause stopping an already stopped EP).
*/
+ if (time_is_before_jiffies(ep->stop_time + msecs_to_jiffies(100)))
+ break;
fallthrough;
case EP_STATE_RUNNING:
/* Race, HW handled stop ep cmd before ep was running */
xhci_dbg(xhci, "Stop ep completion ctx error, ctx_state %d\n",
GET_EP_CTX_STATE(ep_ctx));
- /*
- * Don't retry forever if we guessed wrong or a defective HC never starts
- * the EP or says 'Running' but fails the command. We must give back TDs.
- */
- if (time_is_before_jiffies(ep->stop_time + msecs_to_jiffies(100)))
- break;
command = xhci_alloc_command(xhci, false, GFP_ATOMIC);
if (!command) {
@@ -1481,7 +1479,7 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id,
u64 deq;
/* 4.6.10 deq ptr is written to the stream ctx for streams */
if (ep->ep_state & EP_HAS_STREAMS) {
- deq = le64_to_cpu(stream_ctx->stream_ring) & SCTX_DEQ_MASK;
+ deq = le64_to_cpu(stream_ctx->stream_ring) & TR_DEQ_PTR_MASK;
/*
* Cadence xHCI controllers store some endpoint state
@@ -1497,7 +1495,7 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id,
stream_ctx->reserved[1] = 0;
}
} else {
- deq = le64_to_cpu(ep_ctx->deq) & ~EP_CTX_CYCLE_MASK;
+ deq = le64_to_cpu(ep_ctx->deq) & TR_DEQ_PTR_MASK;
}
xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb,
"Successful Set TR Deq Ptr cmd, deq = @%08llx", deq);
@@ -3550,7 +3548,7 @@ static u32 xhci_td_remainder(struct xhci_hcd *xhci, int transferred,
if ((xhci->quirks & XHCI_MTK_HOST) && (xhci->hci_version < 0x100))
trb_buff_len = 0;
- maxp = usb_endpoint_maxp(&urb->ep->desc);
+ maxp = xhci_usb_endpoint_maxp(urb->dev, urb->ep);
total_packet_count = DIV_ROUND_UP(td_total_len, maxp);
/* Queueing functions don't count the current TRB into transferred */
@@ -3567,7 +3565,7 @@ static int xhci_align_td(struct xhci_hcd *xhci, struct urb *urb, u32 enqd_len,
u32 new_buff_len;
size_t len;
- max_pkt = usb_endpoint_maxp(&urb->ep->desc);
+ max_pkt = xhci_usb_endpoint_maxp(urb->dev, urb->ep);
unalign = (enqd_len + *trb_buff_len) % max_pkt;
/* we got lucky, last normal TRB data on segment is packet aligned */
@@ -4138,7 +4136,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
addr = start_addr + urb->iso_frame_desc[i].offset;
td_len = urb->iso_frame_desc[i].length;
td_remain_len = td_len;
- max_pkt = usb_endpoint_maxp(&urb->ep->desc);
+ max_pkt = xhci_usb_endpoint_maxp(urb->dev, urb->ep);
total_pkt_count = DIV_ROUND_UP(td_len, max_pkt);
/* A zero-length transfer still involves at least one packet. */
diff --git a/drivers/usb/host/xhci-rzg3e-regs.h b/drivers/usb/host/xhci-rzg3e-regs.h
new file mode 100644
index 000000000000..7a244a47b882
--- /dev/null
+++ b/drivers/usb/host/xhci-rzg3e-regs.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __XHCI_RZG3E_H
+#define __XHCI_RZG3E_H
+
+#define RZG3E_USB3_HOST_INTEN 0x1044 /* Interrupt Enable */
+#define RZG3E_USB3_HOST_U3P0PIPESC(x) (0x10c0 + (x) * 4) /* PIPE Status and Control Register */
+
+#define RZG3E_USB3_HOST_INTEN_XHC BIT(0)
+#define RZG3E_USB3_HOST_INTEN_HSE BIT(2)
+#define RZG3E_USB3_HOST_INTEN_ENA (RZG3E_USB3_HOST_INTEN_XHC | RZG3E_USB3_HOST_INTEN_HSE)
+
+#endif /* __XHCI_RZG3E_H */
diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c
index d49f9886dd84..e771a476fef2 100644
--- a/drivers/usb/host/xhci-sideband.c
+++ b/drivers/usb/host/xhci-sideband.c
@@ -267,6 +267,31 @@ xhci_sideband_get_event_buffer(struct xhci_sideband *sb)
EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer);
/**
+ * xhci_sideband_check - check the existence of active sidebands
+ * @hcd: the host controller driver associated with the target host controller
+ *
+ * Allow other drivers, such as usb controller driver, to check if there are
+ * any sideband activity on the host controller. This information could be used
+ * for power management or other forms of resource management. The caller should
+ * ensure downstream usb devices are all either suspended or marked as
+ * "offload_at_suspend" to ensure the correctness of the return value.
+ *
+ * Returns true on any active sideband existence, false otherwise.
+ */
+bool xhci_sideband_check(struct usb_hcd *hcd)
+{
+ struct usb_device *udev = hcd->self.root_hub;
+ bool active;
+
+ usb_lock_device(udev);
+ active = usb_offload_check(udev);
+ usb_unlock_device(udev);
+
+ return active;
+}
+EXPORT_SYMBOL_GPL(xhci_sideband_check);
+
+/**
* xhci_sideband_create_interrupter - creates a new interrupter for this sideband
* @sb: sideband instance for this usb device
* @num_seg: number of event ring segments to allocate
@@ -286,6 +311,7 @@ xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg,
bool ip_autoclear, u32 imod_interval, int intr_num)
{
int ret = 0;
+ struct usb_device *udev;
if (!sb || !sb->xhci)
return -ENODEV;
@@ -304,6 +330,9 @@ xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg,
goto out;
}
+ udev = sb->vdev->udev;
+ ret = usb_offload_get(udev);
+
sb->ir->ip_autoclear = ip_autoclear;
out:
@@ -323,6 +352,8 @@ EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter);
void
xhci_sideband_remove_interrupter(struct xhci_sideband *sb)
{
+ struct usb_device *udev;
+
if (!sb || !sb->ir)
return;
@@ -330,6 +361,11 @@ xhci_sideband_remove_interrupter(struct xhci_sideband *sb)
xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir);
sb->ir = NULL;
+ udev = sb->vdev->udev;
+
+ if (udev->state != USB_STATE_NOTATTACHED)
+ usb_offload_put(udev);
+
mutex_unlock(&sb->mutex);
}
EXPORT_SYMBOL_GPL(xhci_sideband_remove_interrupter);
diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c
index 0c7af44d4dae..5255b1002893 100644
--- a/drivers/usb/host/xhci-tegra.c
+++ b/drivers/usb/host/xhci-tegra.c
@@ -155,6 +155,8 @@
#define FW_IOCTL_TYPE_SHIFT 24
#define FW_IOCTL_CFGTBL_READ 17
+#define WAKE_IRQ_START_INDEX 2
+
struct tegra_xusb_fw_header {
__le32 boot_loadaddr_in_imem;
__le32 boot_codedfi_offset;
@@ -228,6 +230,7 @@ struct tegra_xusb_soc {
unsigned int num_supplies;
const struct tegra_xusb_phy_type *phy_types;
unsigned int num_types;
+ unsigned int max_num_wakes;
const struct tegra_xusb_context_soc *context;
struct {
@@ -263,6 +266,7 @@ struct tegra_xusb {
int xhci_irq;
int mbox_irq;
int padctl_irq;
+ int *wake_irqs;
void __iomem *ipfs_base;
void __iomem *fpci_base;
@@ -313,6 +317,7 @@ struct tegra_xusb {
bool suspended;
struct tegra_xusb_context context;
u8 lp0_utmi_pad_mask;
+ int num_wakes;
};
static struct hc_driver __read_mostly tegra_xhci_hc_driver;
@@ -1482,7 +1487,7 @@ static int tegra_xhci_id_notify(struct notifier_block *nb,
tegra->otg_usb2_port = tegra_xusb_get_usb2_port(tegra, usbphy);
- tegra->host_mode = (usbphy->last_event == USB_EVENT_ID) ? true : false;
+ tegra->host_mode = usbphy->last_event == USB_EVENT_ID;
schedule_work(&tegra->id_work);
@@ -1537,6 +1542,58 @@ static void tegra_xusb_deinit_usb_phy(struct tegra_xusb *tegra)
otg_set_host(tegra->usbphy[i]->otg, NULL);
}
+static int tegra_xusb_setup_wakeup(struct platform_device *pdev, struct tegra_xusb *tegra)
+{
+ unsigned int i;
+
+ if (tegra->soc->max_num_wakes == 0)
+ return 0;
+
+ tegra->wake_irqs = devm_kcalloc(tegra->dev,
+ tegra->soc->max_num_wakes,
+ sizeof(*tegra->wake_irqs), GFP_KERNEL);
+ if (!tegra->wake_irqs)
+ return -ENOMEM;
+
+ /*
+ * USB wake events are independent of each other, so it is not necessary for a platform
+ * to utilize all wake-up events supported for a given device. The USB host can operate
+ * even if wake-up events are not defined or fail to be configured. Therefore, we only
+ * return critical errors, such as -ENOMEM.
+ */
+ for (i = 0; i < tegra->soc->max_num_wakes; i++) {
+ struct irq_data *data;
+
+ tegra->wake_irqs[i] = platform_get_irq(pdev, i + WAKE_IRQ_START_INDEX);
+ if (tegra->wake_irqs[i] < 0)
+ break;
+
+ data = irq_get_irq_data(tegra->wake_irqs[i]);
+ if (!data) {
+ dev_warn(tegra->dev, "get wake event %d irq data fail\n", i);
+ irq_dispose_mapping(tegra->wake_irqs[i]);
+ break;
+ }
+
+ irq_set_irq_type(tegra->wake_irqs[i], irqd_get_trigger_type(data));
+ }
+
+ tegra->num_wakes = i;
+ dev_dbg(tegra->dev, "setup %d wake events\n", tegra->num_wakes);
+
+ return 0;
+}
+
+static void tegra_xusb_dispose_wake(struct tegra_xusb *tegra)
+{
+ unsigned int i;
+
+ for (i = 0; i < tegra->num_wakes; i++)
+ irq_dispose_mapping(tegra->wake_irqs[i]);
+
+ tegra->num_wakes = 0;
+}
+
static int tegra_xusb_probe(struct platform_device *pdev)
{
struct tegra_xusb *tegra;
@@ -1587,9 +1644,15 @@ static int tegra_xusb_probe(struct platform_device *pdev)
if (tegra->mbox_irq < 0)
return tegra->mbox_irq;
+ err = tegra_xusb_setup_wakeup(pdev, tegra);
+ if (err)
+ return err;
+
tegra->padctl = tegra_xusb_padctl_get(&pdev->dev);
- if (IS_ERR(tegra->padctl))
- return PTR_ERR(tegra->padctl);
+ if (IS_ERR(tegra->padctl)) {
+ err = PTR_ERR(tegra->padctl);
+ goto dispose_wake;
+ }
np = of_parse_phandle(pdev->dev.of_node, "nvidia,xusb-padctl", 0);
if (!np) {
@@ -1913,6 +1976,8 @@ put_powerdomains:
put_padctl:
of_node_put(np);
tegra_xusb_padctl_put(tegra->padctl);
+dispose_wake:
+ tegra_xusb_dispose_wake(tegra);
return err;
}
@@ -1945,6 +2010,8 @@ static void tegra_xusb_remove(struct platform_device *pdev)
if (tegra->padctl_irq)
pm_runtime_disable(&pdev->dev);
+ tegra_xusb_dispose_wake(tegra);
+
pm_runtime_put(&pdev->dev);
tegra_xusb_disable(tegra);
@@ -2355,8 +2422,13 @@ out:
pm_runtime_disable(dev);
if (device_may_wakeup(dev)) {
+ unsigned int i;
+
if (enable_irq_wake(tegra->padctl_irq))
dev_err(dev, "failed to enable padctl wakes\n");
+
+ for (i = 0; i < tegra->num_wakes; i++)
+ enable_irq_wake(tegra->wake_irqs[i]);
}
}
@@ -2384,8 +2456,13 @@ static __maybe_unused int tegra_xusb_resume(struct device *dev)
}
if (device_may_wakeup(dev)) {
+ unsigned int i;
+
if (disable_irq_wake(tegra->padctl_irq))
dev_err(dev, "failed to disable padctl wakes\n");
+
+ for (i = 0; i < tegra->num_wakes; i++)
+ disable_irq_wake(tegra->wake_irqs[i]);
}
tegra->suspended = false;
mutex_unlock(&tegra->lock);
@@ -2636,6 +2713,7 @@ static const struct tegra_xusb_soc tegra234_soc = {
.num_supplies = ARRAY_SIZE(tegra194_supply_names),
.phy_types = tegra194_phy_types,
.num_types = ARRAY_SIZE(tegra194_phy_types),
+ .max_num_wakes = 7,
.context = &tegra186_xusb_context,
.ports = {
.usb3 = { .offset = 0, .count = 4, },
diff --git a/drivers/usb/host/xhci-trace.h b/drivers/usb/host/xhci-trace.h
index bfb5c5c17012..9abc904f1749 100644
--- a/drivers/usb/host/xhci-trace.h
+++ b/drivers/usb/host/xhci-trace.h
@@ -541,23 +541,23 @@ DEFINE_EVENT(xhci_log_ring, xhci_inc_deq,
);
DECLARE_EVENT_CLASS(xhci_log_portsc,
- TP_PROTO(struct xhci_port *port, u32 portsc),
- TP_ARGS(port, portsc),
- TP_STRUCT__entry(
- __field(u32, busnum)
- __field(u32, portnum)
- __field(u32, portsc)
- ),
- TP_fast_assign(
- __entry->busnum = port->rhub->hcd->self.busnum;
- __entry->portnum = port->hcd_portnum;
- __entry->portsc = portsc;
- ),
- TP_printk("port %d-%d: %s",
- __entry->busnum,
- __entry->portnum,
- xhci_decode_portsc(__get_buf(XHCI_MSG_MAX), __entry->portsc)
- )
+ TP_PROTO(struct xhci_port *port, u32 portsc),
+ TP_ARGS(port, portsc),
+ TP_STRUCT__entry(
+ __field(u32, busnum)
+ __field(u32, portnum)
+ __field(u32, portsc)
+ ),
+ TP_fast_assign(
+ __entry->busnum = port->rhub->hcd->self.busnum;
+ __entry->portnum = port->hcd_portnum + 1;
+ __entry->portsc = portsc;
+ ),
+ TP_printk("port %d-%d: %s",
+ __entry->busnum,
+ __entry->portnum,
+ xhci_decode_portsc(__get_buf(XHCI_MSG_MAX), __entry->portsc)
+ )
);
DEFINE_EVENT(xhci_log_portsc, xhci_handle_port_status,
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 742c23826e17..0cb45b95e4f5 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -1336,7 +1336,7 @@ static bool xhci_urb_temp_buffer_required(struct usb_hcd *hcd,
struct scatterlist *tail_sg;
tail_sg = urb->sg;
- max_pkt = usb_endpoint_maxp(&urb->ep->desc);
+ max_pkt = xhci_usb_endpoint_maxp(urb->dev, urb->ep);
if (!urb->num_sgs)
return ret;
@@ -2924,6 +2924,20 @@ out:
}
EXPORT_SYMBOL_GPL(xhci_stop_endpoint_sync);
+/*
+ * xhci_usb_endpoint_maxp - get endpoint max packet size
+ * @host_ep: USB host endpoint to be checked
+ *
+ * Returns max packet from the correct descriptor
+ */
+int xhci_usb_endpoint_maxp(struct usb_device *udev,
+ struct usb_host_endpoint *host_ep)
+{
+ if (usb_endpoint_is_hs_isoc_double(udev, host_ep))
+ return le16_to_cpu(host_ep->eusb2_isoc_ep_comp.wMaxPacketSize);
+ return usb_endpoint_maxp(&host_ep->desc);
+}
+
/* Issue a configure endpoint command or evaluate context command
* and wait for it to finish.
*/
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 85d5b964bf1e..58a51f09cceb 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -500,7 +500,8 @@ struct xhci_ep_ctx {
/* deq bitmasks */
#define EP_CTX_CYCLE_MASK (1 << 0)
-#define SCTX_DEQ_MASK (~0xfL)
+/* bits 63:4 - TR Dequeue Pointer */
+#define TR_DEQ_PTR_MASK GENMASK_ULL(63, 4)
/**
@@ -1958,6 +1959,8 @@ void xhci_update_erst_dequeue(struct xhci_hcd *xhci,
struct xhci_interrupter *ir,
bool clear_ehb);
void xhci_add_interrupter(struct xhci_hcd *xhci, unsigned int intr_num);
+int xhci_usb_endpoint_maxp(struct usb_device *udev,
+ struct usb_host_endpoint *host_ep);
/* xHCI roothub code */
void xhci_set_link_state(struct xhci_hcd *xhci, struct xhci_port *port,
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index 6497c4e81e95..09ac6f1c985f 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -147,6 +147,7 @@ config USB_APPLEDISPLAY
config USB_QCOM_EUD
tristate "QCOM Embedded USB Debugger(EUD) Driver"
depends on ARCH_QCOM || COMPILE_TEST
+ select QCOM_SCM
select USB_ROLE_SWITCH
help
This module enables support for Qualcomm Technologies, Inc.
@@ -178,6 +179,20 @@ config USB_LJCA
This driver can also be built as a module. If so, the module
will be called usb-ljca.
+config USB_USBIO
+ tristate "Intel USBIO Bridge support"
+ depends on USB && ACPI
+ select AUXILIARY_BUS
+ help
+ This adds support for Intel USBIO drivers.
+ This enables the USBIO bridge driver module in charge to talk
+ to the USB device. Additional drivers such as GPIO_USBIO and
+ I2C_USBIO must be enabled in order to use the device's full
+ functionality.
+
+ This driver can also be built as a module. If so, the module
+ will be called usbio.
+
source "drivers/usb/misc/sisusbvga/Kconfig"
config USB_LD
@@ -231,8 +246,8 @@ config USB_EHSET_TEST_FIXTURE
VID/PID pairs. This driver then initiates a corresponding test mode on
the downstream port to which the test fixture is attached.
- See <http://www.usb.org/developers/onthego/EHSET_v1.01.pdf> for more
- information.
+ See <https://www.usb.org/sites/default/files/EHSET_v1.01%281%29.pdf>
+ for more information.
config USB_ISIGHTFW
tristate "iSight firmware loading support"
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index 0cd5bc8f52fe..494ab0377f35 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_USB_EMI62) += emi62.o
obj-$(CONFIG_USB_EZUSB_FX2) += ezusb.o
obj-$(CONFIG_APPLE_MFI_FASTCHARGE) += apple-mfi-fastcharge.o
obj-$(CONFIG_USB_LJCA) += usb-ljca.o
+obj-$(CONFIG_USB_USBIO) += usbio.o
obj-$(CONFIG_USB_IDMOUSE) += idmouse.o
obj-$(CONFIG_USB_IOWARRIOR) += iowarrior.o
obj-$(CONFIG_USB_ISIGHTFW) += isight_firmware.o
diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c
index 83079c414b4f..926419ca560f 100644
--- a/drivers/usb/misc/qcom_eud.c
+++ b/drivers/usb/misc/qcom_eud.c
@@ -15,6 +15,7 @@
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/usb/role.h>
+#include <linux/firmware/qcom/qcom_scm.h>
#define EUD_REG_INT1_EN_MASK 0x0024
#define EUD_REG_INT_STATUS_1 0x0044
@@ -34,7 +35,7 @@ struct eud_chip {
struct device *dev;
struct usb_role_switch *role_sw;
void __iomem *base;
- void __iomem *mode_mgr;
+ phys_addr_t mode_mgr;
unsigned int int_status;
int irq;
bool enabled;
@@ -43,18 +44,29 @@ struct eud_chip {
static int enable_eud(struct eud_chip *priv)
{
+ int ret;
+
+ ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 1);
+ if (ret)
+ return ret;
+
writel(EUD_ENABLE, priv->base + EUD_REG_CSR_EUD_EN);
writel(EUD_INT_VBUS | EUD_INT_SAFE_MODE,
priv->base + EUD_REG_INT1_EN_MASK);
- writel(1, priv->mode_mgr + EUD_REG_EUD_EN2);
return usb_role_switch_set_role(priv->role_sw, USB_ROLE_DEVICE);
}
-static void disable_eud(struct eud_chip *priv)
+static int disable_eud(struct eud_chip *priv)
{
+ int ret;
+
+ ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 0);
+ if (ret)
+ return ret;
+
writel(0, priv->base + EUD_REG_CSR_EUD_EN);
- writel(0, priv->mode_mgr + EUD_REG_EUD_EN2);
+ return 0;
}
static ssize_t enable_show(struct device *dev,
@@ -82,11 +94,12 @@ static ssize_t enable_store(struct device *dev,
chip->enabled = enable;
else
disable_eud(chip);
+
} else {
- disable_eud(chip);
+ ret = disable_eud(chip);
}
- return count;
+ return ret < 0 ? ret : count;
}
static DEVICE_ATTR_RW(enable);
@@ -178,6 +191,7 @@ static void eud_role_switch_release(void *data)
static int eud_probe(struct platform_device *pdev)
{
struct eud_chip *chip;
+ struct resource *res;
int ret;
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
@@ -193,16 +207,16 @@ static int eud_probe(struct platform_device *pdev)
ret = devm_add_action_or_reset(chip->dev, eud_role_switch_release, chip);
if (ret)
- return dev_err_probe(chip->dev, ret,
- "failed to add role switch release action\n");
+ return ret;
chip->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(chip->base))
return PTR_ERR(chip->base);
- chip->mode_mgr = devm_platform_ioremap_resource(pdev, 1);
- if (IS_ERR(chip->mode_mgr))
- return PTR_ERR(chip->mode_mgr);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res)
+ return -ENODEV;
+ chip->mode_mgr = res->start;
chip->irq = platform_get_irq(pdev, 0);
if (chip->irq < 0)
diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c
index 4fb453ca5450..7c0778631bea 100644
--- a/drivers/usb/misc/usb251xb.c
+++ b/drivers/usb/misc/usb251xb.c
@@ -17,6 +17,7 @@
#include <linux/module.h>
#include <linux/nls.h>
#include <linux/of.h>
+#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
@@ -242,15 +243,19 @@ static int usb251xb_check_dev_children(struct device *dev, void *child)
static int usb251x_check_gpio_chip(struct usb251xb *hub)
{
struct gpio_chip *gc = gpiod_to_chip(hub->gpio_reset);
- struct i2c_adapter *adap = hub->i2c->adapter;
+ struct i2c_adapter *adap;
int ret;
+ if (!hub->i2c)
+ return 0;
+
if (!hub->gpio_reset)
return 0;
if (!gc)
return -EINVAL;
+ adap = hub->i2c->adapter;
ret = usb251xb_check_dev_children(&adap->dev, gc->parent);
if (ret) {
dev_err(hub->dev, "Reset GPIO chip is at the same i2c-bus\n");
@@ -271,7 +276,8 @@ static void usb251xb_reset(struct usb251xb *hub)
if (!hub->gpio_reset)
return;
- i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
+ if (hub->i2c)
+ i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
gpiod_set_value_cansleep(hub->gpio_reset, 1);
usleep_range(1, 10); /* >=1us RESET_N asserted */
@@ -280,7 +286,8 @@ static void usb251xb_reset(struct usb251xb *hub)
/* wait for hub recovery/stabilization */
usleep_range(500, 750); /* >=500us after RESET_N deasserted */
- i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
+ if (hub->i2c)
+ i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
}
static int usb251xb_connect(struct usb251xb *hub)
@@ -289,6 +296,12 @@ static int usb251xb_connect(struct usb251xb *hub)
int err, i;
char i2c_wb[USB251XB_I2C_REG_SZ];
+ if (!hub->i2c) {
+ usb251xb_reset(hub);
+ dev_info(dev, "hub is put in default configuration.\n");
+ return 0;
+ }
+
memset(i2c_wb, 0, USB251XB_I2C_REG_SZ);
if (hub->skip_config) {
@@ -698,18 +711,13 @@ static int usb251xb_i2c_probe(struct i2c_client *i2c)
return usb251xb_probe(hub);
}
-static int __maybe_unused usb251xb_suspend(struct device *dev)
+static int usb251xb_suspend(struct usb251xb *hub)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct usb251xb *hub = i2c_get_clientdata(client);
-
return regulator_disable(hub->vdd);
}
-static int __maybe_unused usb251xb_resume(struct device *dev)
+static int usb251xb_resume(struct usb251xb *hub)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct usb251xb *hub = i2c_get_clientdata(client);
int err;
err = regulator_enable(hub->vdd);
@@ -719,7 +727,23 @@ static int __maybe_unused usb251xb_resume(struct device *dev)
return usb251xb_connect(hub);
}
-static SIMPLE_DEV_PM_OPS(usb251xb_pm_ops, usb251xb_suspend, usb251xb_resume);
+static int usb251xb_i2c_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct usb251xb *hub = i2c_get_clientdata(client);
+
+ return usb251xb_suspend(hub);
+}
+
+static int usb251xb_i2c_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct usb251xb *hub = i2c_get_clientdata(client);
+
+ return usb251xb_resume(hub);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_i2c_pm_ops, usb251xb_i2c_suspend, usb251xb_i2c_resume);
static const struct i2c_device_id usb251xb_id[] = {
{ "usb2422" },
@@ -739,13 +763,71 @@ static struct i2c_driver usb251xb_i2c_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = usb251xb_of_match,
- .pm = &usb251xb_pm_ops,
+ .pm = pm_sleep_ptr(&usb251xb_i2c_pm_ops),
},
.probe = usb251xb_i2c_probe,
.id_table = usb251xb_id,
};
-module_i2c_driver(usb251xb_i2c_driver);
+static int usb251xb_plat_probe(struct platform_device *pdev)
+{
+ struct usb251xb *hub;
+
+ hub = devm_kzalloc(&pdev->dev, sizeof(*hub), GFP_KERNEL);
+ if (!hub)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, hub);
+ hub->dev = &pdev->dev;
+
+ return usb251xb_probe(hub);
+}
+
+static int usb251xb_plat_suspend(struct device *dev)
+{
+ return usb251xb_suspend(dev_get_drvdata(dev));
+}
+
+static int usb251xb_plat_resume(struct device *dev)
+{
+ return usb251xb_resume(dev_get_drvdata(dev));
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_plat_pm_ops, usb251xb_plat_suspend, usb251xb_plat_resume);
+
+static struct platform_driver usb251xb_plat_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = usb251xb_of_match,
+ .pm = pm_sleep_ptr(&usb251xb_plat_pm_ops),
+ },
+ .probe = usb251xb_plat_probe,
+};
+
+static int __init usb251xb_init(void)
+{
+ int err;
+
+ err = i2c_add_driver(&usb251xb_i2c_driver);
+ if (err)
+ return err;
+
+ err = platform_driver_register(&usb251xb_plat_driver);
+ if (err) {
+ i2c_del_driver(&usb251xb_i2c_driver);
+ return err;
+ }
+
+ return 0;
+}
+module_init(usb251xb_init);
+
+static void __exit usb251xb_exit(void)
+{
+ platform_driver_unregister(&usb251xb_plat_driver);
+ i2c_del_driver(&usb251xb_i2c_driver);
+}
+module_exit(usb251xb_exit);
MODULE_AUTHOR("Richard Leitner <richard.leitner@skidata.com>");
MODULE_DESCRIPTION("USB251x/xBi USB 2.0 Hub Controller Driver");
diff --git a/drivers/usb/misc/usbio.c b/drivers/usb/misc/usbio.c
new file mode 100644
index 000000000000..37644dddf157
--- /dev/null
+++ b/drivers/usb/misc/usbio.c
@@ -0,0 +1,749 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel USBIO Bridge driver
+ *
+ * Copyright (c) 2025 Intel Corporation.
+ * Copyright (c) 2025 Red Hat, Inc.
+ */
+
+#include <linux/acpi.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/byteorder/generic.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/lockdep.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/usb/usbio.h>
+
+/*************************************
+ * USBIO Bridge Protocol Definitions *
+ *************************************/
+
+/* USBIO Control Commands */
+#define USBIO_CTRLCMD_PROTVER 0
+#define USBIO_CTRLCMD_FWVER 1
+#define USBIO_CTRLCMD_HS 2
+#define USBIO_CTRLCMD_ENUMGPIO 16
+#define USBIO_CTRLCMD_ENUMI2C 17
+
+/* USBIO Packet Flags */
+#define USBIO_PKTFLAG_ACK BIT(0)
+#define USBIO_PKTFLAG_RSP BIT(1)
+#define USBIO_PKTFLAG_CMP BIT(2)
+#define USBIO_PKTFLAG_ERR BIT(3)
+
+#define USBIO_PKTFLAGS_REQRESP (USBIO_PKTFLAG_CMP | USBIO_PKTFLAG_ACK)
+
+#define USBIO_CTRLXFER_TIMEOUT 0
+#define USBIO_BULKXFER_TIMEOUT 100
+
+struct usbio_protver {
+ u8 ver;
+} __packed;
+
+struct usbio_fwver {
+ u8 major;
+ u8 minor;
+ __le16 patch;
+ __le16 build;
+} __packed;
+
+/***********************************
+ * USBIO Bridge Device Definitions *
+ ***********************************/
+
+/**
+ * struct usbio_device - the usb device exposing IOs
+ *
+ * @dev: the device in the usb interface
+ * @udev: the detected usb device
+ * @intf: the usb interface
+ * @quirks: quirks
+ * @ctrl_mutex: protects ctrl_buf
+ * @ctrl_pipe: the control transfer pipe
+ * @ctrlbuf_len: the size of the control transfer pipe
+ * @ctrlbuf: the buffer used for control transfers
+ * @bulk_mutex: protects tx_buf, rx_buf and split bulk-transfers getting interrupted
+ * @tx_pipe: the bulk out pipe
+ * @txbuf_len: the size of the bulk out pipe
+ * @txbuf: the buffer used for bulk out transfers
+ * @rx_pipe: the bulk in pipe
+ * @rxbuf_len: the size of the bulk in pipe
+ * @rxdat_len: the data length at rx buffer
+ * @rxbuf: the buffer used for bulk in transfers
+ * @urb: the urb to read bulk pipe
+ * @done: completion object as request is done
+ * @cli_list: device's client list
+ * @nr_gpio_banks: Number of GPIO banks
+ * @gpios: GPIO bank descriptors
+ * @nr_gpio_banks: Number of I2C busses
+ * @gpios: I2C bank descriptors
+ */
+struct usbio_device {
+ struct device *dev;
+ struct usb_device *udev;
+ struct usb_interface *intf;
+ unsigned long quirks;
+
+ struct mutex ctrl_mutex;
+ unsigned int ctrl_pipe;
+ u16 ctrlbuf_len;
+ void *ctrlbuf;
+
+ struct mutex bulk_mutex;
+ unsigned int tx_pipe;
+ u16 txbuf_len;
+ void *txbuf;
+
+ unsigned int rx_pipe;
+ u16 rxbuf_len;
+ u16 rxdat_len;
+ void *rxbuf;
+ struct urb *urb;
+
+ struct completion done;
+
+ struct list_head cli_list;
+
+ unsigned int nr_gpio_banks;
+ struct usbio_gpio_bank_desc gpios[USBIO_MAX_GPIOBANKS];
+
+ unsigned int nr_i2c_buses;
+ struct usbio_i2c_bus_desc i2cs[USBIO_MAX_I2CBUSES];
+};
+
+/**
+ * struct usbio_client - represents a usbio client
+ *
+ * @auxdev: auxiliary device object
+ * @mutex: protects @bridge
+ * @bridge: usbio bridge who service the client
+ * @link: usbio bridge clients list member
+ */
+struct usbio_client {
+ struct auxiliary_device auxdev;
+ struct mutex mutex;
+ struct usbio_device *bridge;
+ struct list_head link;
+};
+
+#define adev_to_client(adev) container_of_const(adev, struct usbio_client, auxdev)
+
+static int usbio_ctrl_msg(struct usbio_device *usbio, u8 type, u8 cmd,
+ const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len)
+{
+ u8 request = USB_TYPE_VENDOR | USB_RECIP_DEVICE;
+ struct usbio_ctrl_packet *cpkt;
+ unsigned int pipe;
+ u16 cpkt_len;
+ int ret;
+
+ lockdep_assert_held(&usbio->ctrl_mutex);
+
+ if ((obuf_len > (usbio->ctrlbuf_len - sizeof(*cpkt))) ||
+ (ibuf_len > (usbio->ctrlbuf_len - sizeof(*cpkt))))
+ return -EMSGSIZE;
+
+ /* Prepare Control Packet Header */
+ cpkt = usbio->ctrlbuf;
+ cpkt->header.type = type;
+ cpkt->header.cmd = cmd;
+ if (type == USBIO_PKTTYPE_CTRL || ibuf_len)
+ cpkt->header.flags = USBIO_PKTFLAGS_REQRESP;
+ else
+ cpkt->header.flags = USBIO_PKTFLAG_CMP;
+ cpkt->len = obuf_len;
+
+ /* Copy the data */
+ memcpy(cpkt->data, obuf, obuf_len);
+
+ pipe = usb_sndctrlpipe(usbio->udev, usbio->ctrl_pipe);
+ cpkt_len = sizeof(*cpkt) + obuf_len;
+ ret = usb_control_msg(usbio->udev, pipe, 0, request | USB_DIR_OUT, 0, 0,
+ cpkt, cpkt_len, USBIO_CTRLXFER_TIMEOUT);
+ dev_dbg(usbio->dev, "control out %d hdr %*phN data %*phN\n", ret,
+ (int)sizeof(*cpkt), cpkt, (int)cpkt->len, cpkt->data);
+
+ if (ret != cpkt_len) {
+ dev_err(usbio->dev, "USB control out failed: %d\n", ret);
+ return (ret < 0) ? ret : -EPROTO;
+ }
+
+ if (!(cpkt->header.flags & USBIO_PKTFLAG_ACK))
+ return 0;
+
+ pipe = usb_rcvctrlpipe(usbio->udev, usbio->ctrl_pipe);
+ cpkt_len = sizeof(*cpkt) + ibuf_len;
+ ret = usb_control_msg(usbio->udev, pipe, 0, request | USB_DIR_IN, 0, 0,
+ cpkt, cpkt_len, USBIO_CTRLXFER_TIMEOUT);
+ dev_dbg(usbio->dev, "control in %d hdr %*phN data %*phN\n", ret,
+ (int)sizeof(*cpkt), cpkt, (int)cpkt->len, cpkt->data);
+
+ if (ret < sizeof(*cpkt)) {
+ dev_err(usbio->dev, "USB control in failed: %d\n", ret);
+ return (ret < 0) ? ret : -EPROTO;
+ }
+
+ if (cpkt->header.type != type || cpkt->header.cmd != cmd ||
+ !(cpkt->header.flags & USBIO_PKTFLAG_RSP)) {
+ dev_err(usbio->dev, "Unexpected reply type: %u, cmd: %u, flags: %u\n",
+ cpkt->header.type, cpkt->header.cmd, cpkt->header.flags);
+ return -EPROTO;
+ }
+
+ if (cpkt->header.flags & USBIO_PKTFLAG_ERR)
+ return -EREMOTEIO;
+
+ if (ibuf_len < cpkt->len)
+ return -ENOSPC;
+
+ memcpy(ibuf, cpkt->data, cpkt->len);
+
+ return cpkt->len;
+}
+
+int usbio_control_msg(struct auxiliary_device *adev, u8 type, u8 cmd,
+ const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len)
+{
+ struct usbio_client *client = adev_to_client(adev);
+ struct usbio_device *usbio;
+ int ret;
+
+ guard(mutex)(&client->mutex);
+
+ usbio = client->bridge;
+ if (!usbio)
+ return -ENODEV; /* Disconnected */
+
+ ret = usb_autopm_get_interface(usbio->intf);
+ if (ret)
+ return ret;
+
+ mutex_lock(&usbio->ctrl_mutex);
+
+ ret = usbio_ctrl_msg(client->bridge, type, cmd, obuf, obuf_len, ibuf, ibuf_len);
+
+ mutex_unlock(&usbio->ctrl_mutex);
+ usb_autopm_put_interface(usbio->intf);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(usbio_control_msg, "USBIO");
+
+static void usbio_bulk_recv(struct urb *urb)
+{
+ struct usbio_bulk_packet *bpkt = urb->transfer_buffer;
+ struct usbio_device *usbio = urb->context;
+
+ if (!urb->status) {
+ if (bpkt->header.flags & USBIO_PKTFLAG_RSP) {
+ usbio->rxdat_len = urb->actual_length;
+ complete(&usbio->done);
+ }
+ } else if (urb->status != -ENOENT) {
+ dev_err(usbio->dev, "Bulk in error %d\n", urb->status);
+ }
+
+ usb_submit_urb(usbio->urb, GFP_ATOMIC);
+}
+
+int usbio_bulk_msg(struct auxiliary_device *adev, u8 type, u8 cmd, bool last,
+ const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len)
+{
+ struct usbio_client *client = adev_to_client(adev);
+ struct usbio_device *usbio = client->bridge;
+ struct usbio_bulk_packet *bpkt;
+ int ret, act = 0;
+ u16 bpkt_len;
+
+ lockdep_assert_held(&client->mutex);
+ lockdep_assert_held(&usbio->bulk_mutex);
+
+ if ((obuf_len > (usbio->txbuf_len - sizeof(*bpkt))) ||
+ (ibuf_len > (usbio->txbuf_len - sizeof(*bpkt))))
+ return -EMSGSIZE;
+
+ if (ibuf_len)
+ reinit_completion(&usbio->done);
+
+ /* If no data to send, skip to read */
+ if (!obuf_len)
+ goto read;
+
+ /* Prepare Bulk Packet Header */
+ bpkt = usbio->txbuf;
+ bpkt->header.type = type;
+ bpkt->header.cmd = cmd;
+ if (!last)
+ bpkt->header.flags = 0;
+ else if (ibuf_len)
+ bpkt->header.flags = USBIO_PKTFLAGS_REQRESP;
+ else
+ bpkt->header.flags = USBIO_PKTFLAG_CMP;
+ bpkt->len = cpu_to_le16(obuf_len);
+
+ /* Copy the data */
+ memcpy(bpkt->data, obuf, obuf_len);
+
+ bpkt_len = sizeof(*bpkt) + obuf_len;
+ ret = usb_bulk_msg(usbio->udev, usbio->tx_pipe, bpkt, bpkt_len, &act,
+ USBIO_BULKXFER_TIMEOUT);
+ dev_dbg(usbio->dev, "bulk out %d hdr %*phN data %*phN\n", act,
+ (int)sizeof(*bpkt), bpkt, obuf_len, bpkt->data);
+
+ if (ret || act != bpkt_len) {
+ dev_err(usbio->dev, "Bulk out failed: %d\n", ret);
+ return ret ?: -EPROTO;
+ }
+
+ if (!(bpkt->header.flags & USBIO_PKTFLAG_ACK))
+ return obuf_len;
+
+read:
+ ret = wait_for_completion_timeout(&usbio->done, USBIO_BULKXFER_TIMEOUT);
+ if (ret <= 0) {
+ dev_err(usbio->dev, "Bulk in wait failed: %d\n", ret);
+ return ret ?: -ETIMEDOUT;
+ }
+
+ act = usbio->rxdat_len;
+ bpkt = usbio->rxbuf;
+ bpkt_len = le16_to_cpu(bpkt->len);
+ dev_dbg(usbio->dev, "bulk in %d hdr %*phN data %*phN\n", act,
+ (int)sizeof(*bpkt), bpkt, bpkt_len, bpkt->data);
+
+ /*
+ * Unsupported bulk commands get only an usbio_packet_header with
+ * the error flag set as reply. Return -EPIPE for this case.
+ */
+ if (act == sizeof(struct usbio_packet_header) &&
+ (bpkt->header.flags & USBIO_PKTFLAG_ERR))
+ return -EPIPE;
+
+ if (act < sizeof(*bpkt)) {
+ dev_err(usbio->dev, "Bulk in short read: %d\n", act);
+ return -EPROTO;
+ }
+
+ if (bpkt->header.type != type || bpkt->header.cmd != cmd ||
+ !(bpkt->header.flags & USBIO_PKTFLAG_RSP)) {
+ dev_err(usbio->dev,
+ "Unexpected bulk in type 0x%02x cmd 0x%02x flags 0x%02x\n",
+ bpkt->header.type, bpkt->header.cmd, bpkt->header.flags);
+ return -EPROTO;
+ }
+
+ if (bpkt->header.flags & USBIO_PKTFLAG_ERR)
+ return -EREMOTEIO;
+
+ if (ibuf_len < bpkt_len)
+ return -ENOSPC;
+
+ memcpy(ibuf, bpkt->data, bpkt_len);
+
+ return bpkt_len;
+}
+EXPORT_SYMBOL_NS_GPL(usbio_bulk_msg, "USBIO");
+
+int usbio_acquire(struct auxiliary_device *adev)
+{
+ struct usbio_client *client = adev_to_client(adev);
+ struct usbio_device *usbio;
+ int ret;
+
+ mutex_lock(&client->mutex);
+
+ usbio = client->bridge;
+ if (!usbio) {
+ ret = -ENODEV; /* Disconnected */
+ goto err_unlock;
+ }
+
+ ret = usb_autopm_get_interface(usbio->intf);
+ if (ret)
+ goto err_unlock;
+
+ mutex_lock(&usbio->bulk_mutex);
+
+ /* Leave client locked until release to avoid abba deadlock issues */
+ return 0;
+
+err_unlock:
+ mutex_unlock(&client->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(usbio_acquire, "USBIO");
+
+void usbio_release(struct auxiliary_device *adev)
+{
+ struct usbio_client *client = adev_to_client(adev);
+ struct usbio_device *usbio = client->bridge;
+
+ lockdep_assert_held(&client->mutex);
+
+ mutex_unlock(&usbio->bulk_mutex);
+ usb_autopm_put_interface(usbio->intf);
+ mutex_unlock(&client->mutex);
+}
+EXPORT_SYMBOL_NS_GPL(usbio_release, "USBIO");
+
+void usbio_get_txrxbuf_len(struct auxiliary_device *adev, u16 *txbuf_len, u16 *rxbuf_len)
+{
+ struct usbio_client *client = adev_to_client(adev);
+ struct usbio_device *usbio;
+
+ guard(mutex)(&client->mutex);
+
+ usbio = client->bridge;
+ if (!usbio)
+ return; /* Disconnected */
+
+ *txbuf_len = usbio->txbuf_len;
+ *rxbuf_len = usbio->rxbuf_len;
+}
+EXPORT_SYMBOL_NS_GPL(usbio_get_txrxbuf_len, "USBIO");
+
+unsigned long usbio_get_quirks(struct auxiliary_device *adev)
+{
+ struct usbio_client *client = adev_to_client(adev);
+ struct usbio_device *usbio;
+
+ guard(mutex)(&client->mutex);
+
+ usbio = client->bridge;
+ if (!usbio)
+ return 0; /* Disconnected */
+
+ return usbio->quirks;
+}
+EXPORT_SYMBOL_NS_GPL(usbio_get_quirks, "USBIO");
+
+static void usbio_auxdev_release(struct device *dev)
+{
+ struct auxiliary_device *adev = to_auxiliary_dev(dev);
+ struct usbio_client *client = adev_to_client(adev);
+
+ mutex_destroy(&client->mutex);
+ kfree(client);
+}
+
+static int usbio_add_client(struct usbio_device *usbio, char *name, u8 id, void *data)
+{
+ struct usbio_client *client;
+ struct auxiliary_device *adev;
+ int ret;
+
+ client = kzalloc(sizeof(*client), GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+
+ mutex_init(&client->mutex);
+ client->bridge = usbio;
+ adev = &client->auxdev;
+ adev->name = name;
+ adev->id = id;
+
+ adev->dev.parent = usbio->dev;
+ adev->dev.platform_data = data;
+ adev->dev.release = usbio_auxdev_release;
+
+ ret = auxiliary_device_init(adev);
+ if (ret) {
+ usbio_auxdev_release(&adev->dev);
+ return ret;
+ }
+
+ ret = auxiliary_device_add(adev);
+ if (ret) {
+ auxiliary_device_uninit(adev);
+ return ret;
+ }
+
+ list_add_tail(&client->link, &usbio->cli_list);
+
+ return 0;
+}
+
+static int usbio_enum_gpios(struct usbio_device *usbio)
+{
+ struct usbio_gpio_bank_desc *gpio = usbio->gpios;
+
+ dev_dbg(usbio->dev, "GPIO Banks: %d\n", usbio->nr_gpio_banks);
+
+ for (unsigned int i = 0; i < usbio->nr_gpio_banks; i++)
+ dev_dbg(usbio->dev, "\tBank%d[%d] map: %#08x\n",
+ gpio[i].id, gpio[i].pins, gpio[i].bmap);
+
+ usbio_add_client(usbio, USBIO_GPIO_CLIENT, 0, gpio);
+
+ return 0;
+}
+
+static int usbio_enum_i2cs(struct usbio_device *usbio)
+{
+ struct usbio_i2c_bus_desc *i2c = usbio->i2cs;
+
+ dev_dbg(usbio->dev, "I2C Busses: %d\n", usbio->nr_i2c_buses);
+
+ for (unsigned int i = 0; i < usbio->nr_i2c_buses; i++) {
+ dev_dbg(usbio->dev, "\tBus%d caps: %#02x\n", i2c[i].id, i2c[i].caps);
+ usbio_add_client(usbio, USBIO_I2C_CLIENT, i, &i2c[i]);
+ }
+
+ return 0;
+}
+
+static int usbio_suspend(struct usb_interface *intf, pm_message_t msg)
+{
+ struct usbio_device *usbio = usb_get_intfdata(intf);
+
+ usb_kill_urb(usbio->urb);
+
+ return 0;
+}
+
+static int usbio_resume(struct usb_interface *intf)
+{
+ struct usbio_device *usbio = usb_get_intfdata(intf);
+
+ return usb_submit_urb(usbio->urb, GFP_KERNEL);
+}
+
+static void usbio_disconnect(struct usb_interface *intf)
+{
+ struct usbio_device *usbio = usb_get_intfdata(intf);
+ struct usbio_client *client;
+
+ /* Wakeup any clients waiting for a reply */
+ usbio->rxdat_len = 0;
+ complete(&usbio->done);
+
+ /* Let clients know the bridge is gone */
+ list_for_each_entry(client, &usbio->cli_list, link) {
+ mutex_lock(&client->mutex);
+ client->bridge = NULL;
+ mutex_unlock(&client->mutex);
+ }
+
+ /* From here on clients will no longer touch struct usbio_device */
+ usb_kill_urb(usbio->urb);
+ usb_free_urb(usbio->urb);
+
+ list_for_each_entry_reverse(client, &usbio->cli_list, link) {
+ auxiliary_device_delete(&client->auxdev);
+ auxiliary_device_uninit(&client->auxdev);
+ }
+}
+
+static int usbio_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct usb_endpoint_descriptor *ep_in, *ep_out;
+ struct device *dev = &intf->dev;
+ struct usbio_protver protver;
+ struct usbio_device *usbio;
+ struct usbio_fwver fwver;
+ int ret;
+
+ usbio = devm_kzalloc(dev, sizeof(*usbio), GFP_KERNEL);
+ if (!usbio)
+ return -ENOMEM;
+
+ ret = devm_mutex_init(dev, &usbio->ctrl_mutex);
+ if (ret)
+ return ret;
+
+ ret = devm_mutex_init(dev, &usbio->bulk_mutex);
+ if (ret)
+ return ret;
+
+ usbio->dev = dev;
+ usbio->udev = udev;
+ usbio->intf = intf;
+ usbio->quirks = id ? id->driver_info : 0;
+ init_completion(&usbio->done);
+ INIT_LIST_HEAD(&usbio->cli_list);
+ usb_set_intfdata(intf, usbio);
+
+ usbio->ctrl_pipe = usb_endpoint_num(&udev->ep0.desc);
+ usbio->ctrlbuf_len = usb_maxpacket(udev, usbio->ctrl_pipe);
+ usbio->ctrlbuf = devm_kzalloc(dev, usbio->ctrlbuf_len, GFP_KERNEL);
+ if (!usbio->ctrlbuf)
+ return -ENOMEM;
+
+ /* Find the first bulk-in and bulk-out endpoints */
+ ret = usb_find_common_endpoints(intf->cur_altsetting, &ep_in, &ep_out,
+ NULL, NULL);
+ if (ret) {
+ dev_err(dev, "Cannot find bulk endpoints: %d\n", ret);
+ return ret;
+ }
+
+ usbio->tx_pipe = usb_sndbulkpipe(udev, usb_endpoint_num(ep_out));
+
+ if (usbio->quirks & USBIO_QUIRK_BULK_MAXP_63)
+ usbio->txbuf_len = 63;
+ else
+ usbio->txbuf_len = usb_endpoint_maxp(ep_out);
+
+ usbio->txbuf = devm_kzalloc(dev, usbio->txbuf_len, GFP_KERNEL);
+ if (!usbio->txbuf)
+ return -ENOMEM;
+
+ usbio->rx_pipe = usb_rcvbulkpipe(udev, usb_endpoint_num(ep_in));
+
+ if (usbio->quirks & USBIO_QUIRK_BULK_MAXP_63)
+ usbio->rxbuf_len = 63;
+ else
+ usbio->rxbuf_len = usb_endpoint_maxp(ep_in);
+
+ usbio->rxbuf = devm_kzalloc(dev, usbio->rxbuf_len, GFP_KERNEL);
+ if (!usbio->rxbuf)
+ return -ENOMEM;
+
+ usbio->urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!usbio->urb)
+ return -ENOMEM;
+
+ usb_fill_bulk_urb(usbio->urb, udev, usbio->rx_pipe, usbio->rxbuf,
+ usbio->rxbuf_len, usbio_bulk_recv, usbio);
+ ret = usb_submit_urb(usbio->urb, GFP_KERNEL);
+ if (ret)
+ return dev_err_probe(dev, ret, "Submitting usb urb\n");
+
+ mutex_lock(&usbio->ctrl_mutex);
+
+ ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_HS, NULL, 0, NULL, 0);
+ if (ret < 0)
+ goto err_unlock;
+
+ ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_PROTVER, NULL, 0,
+ &protver, sizeof(protver));
+ if (ret < 0)
+ goto err_unlock;
+
+ ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_FWVER, NULL, 0,
+ &fwver, sizeof(fwver));
+ if (ret < 0)
+ goto err_unlock;
+
+ ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_ENUMGPIO, NULL, 0,
+ usbio->gpios, sizeof(usbio->gpios));
+ if (ret < 0 || ret % sizeof(struct usbio_gpio_bank_desc)) {
+ ret = (ret < 0) ? ret : -EPROTO;
+ goto err_unlock;
+ }
+ usbio->nr_gpio_banks = ret / sizeof(struct usbio_gpio_bank_desc);
+
+ ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_ENUMI2C, NULL, 0,
+ usbio->i2cs, sizeof(usbio->i2cs));
+ if (ret < 0 || ret % sizeof(struct usbio_i2c_bus_desc)) {
+ ret = (ret < 0) ? ret : -EPROTO;
+ goto err_unlock;
+ }
+ usbio->nr_i2c_buses = ret / sizeof(struct usbio_i2c_bus_desc);
+
+ mutex_unlock(&usbio->ctrl_mutex);
+
+ dev_dbg(dev, "ProtVer(BCD): %02x FwVer: %d.%d.%d.%d\n",
+ protver.ver, fwver.major, fwver.minor,
+ le16_to_cpu(fwver.patch), le16_to_cpu(fwver.build));
+
+ usbio_enum_gpios(usbio);
+ usbio_enum_i2cs(usbio);
+
+ return 0;
+
+err_unlock:
+ mutex_unlock(&usbio->ctrl_mutex);
+ usb_kill_urb(usbio->urb);
+ usb_free_urb(usbio->urb);
+
+ return ret;
+}
+
+static const struct usb_device_id usbio_table[] = {
+ { USB_DEVICE(0x2ac1, 0x20c1), /* Lattice NX40 */
+ .driver_info = USBIO_QUIRK_I2C_MAX_RW_LEN_52 },
+ { USB_DEVICE(0x2ac1, 0x20c9), /* Lattice NX33 */
+ .driver_info = USBIO_QUIRK_I2C_NO_INIT_ACK | USBIO_QUIRK_I2C_MAX_RW_LEN_52 |
+ USBIO_QUIRK_I2C_ALLOW_400KHZ },
+ { USB_DEVICE(0x2ac1, 0x20cb) }, /* Lattice NX33U */
+ { USB_DEVICE(0x06cb, 0x0701), /* Synaptics Sabre */
+ .driver_info = USBIO_QUIRK_BULK_MAXP_63 | USBIO_QUIRK_I2C_USE_CHUNK_LEN },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, usbio_table);
+
+static struct usb_driver usbio_driver = {
+ .name = "usbio-bridge",
+ .probe = usbio_probe,
+ .disconnect = usbio_disconnect,
+ .suspend = usbio_suspend,
+ .resume = usbio_resume,
+ .id_table = usbio_table,
+ .supports_autosuspend = 1,
+};
+module_usb_driver(usbio_driver);
+
+struct usbio_match_ids_walk_data {
+ struct acpi_device *adev;
+ const struct acpi_device_id *hids;
+ unsigned int id;
+};
+
+static int usbio_match_device_ids(struct acpi_device *adev, void *data)
+{
+ struct usbio_match_ids_walk_data *wd = data;
+ unsigned int id = 0;
+ char *uid;
+
+ if (acpi_match_device_ids(adev, wd->hids))
+ return 0;
+
+ uid = acpi_device_uid(adev);
+ if (uid) {
+ for (int i = 0; i < strlen(uid); i++) {
+ if (!kstrtouint(&uid[i], 10, &id))
+ break;
+ }
+ }
+
+ if (!uid || wd->id == id) {
+ wd->adev = adev;
+ return 1;
+ }
+
+ return 0;
+}
+
+void usbio_acpi_bind(struct auxiliary_device *adev, const struct acpi_device_id *hids)
+{
+ struct device *dev = &adev->dev;
+ struct acpi_device *parent;
+ struct usbio_match_ids_walk_data wd = {
+ .adev = NULL,
+ .hids = hids,
+ .id = adev->id,
+ };
+
+ parent = ACPI_COMPANION(dev->parent);
+ if (!parent)
+ return;
+
+ acpi_dev_for_each_child(parent, usbio_match_device_ids, &wd);
+ if (wd.adev)
+ ACPI_COMPANION_SET(dev, wd.adev);
+}
+EXPORT_SYMBOL_NS_GPL(usbio_acpi_bind, "USBIO");
+
+MODULE_DESCRIPTION("Intel USBIO Bridge driver");
+MODULE_AUTHOR("Israel Cepeda <israel.a.cepeda.lopez@intel.com>");
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/mon/mon_bin.c b/drivers/usb/mon/mon_bin.c
index c93b43f5bc46..e713fc5964b1 100644
--- a/drivers/usb/mon/mon_bin.c
+++ b/drivers/usb/mon/mon_bin.c
@@ -68,18 +68,20 @@
* The magic limit was calculated so that it allows the monitoring
* application to pick data once in two ticks. This way, another application,
* which presumably drives the bus, gets to hog CPU, yet we collect our data.
- * If HZ is 100, a 480 mbit/s bus drives 614 KB every jiffy. USB has an
- * enormous overhead built into the bus protocol, so we need about 1000 KB.
+ *
+ * Originally, for a 480 Mbit/s bus this required a buffer of about 1 MB. For
+ * modern 20 Gbps buses, this value increases to over 50 MB. The maximum
+ * buffer size is set to 64 MiB to accommodate this.
*
* This is still too much for most cases, where we just snoop a few
* descriptor fetches for enumeration. So, the default is a "reasonable"
- * amount for systems with HZ=250 and incomplete bus saturation.
+ * amount for typical, low-throughput use cases.
*
* XXX What about multi-megabyte URBs which take minutes to transfer?
*/
-#define BUFF_MAX CHUNK_ALIGN(1200*1024)
-#define BUFF_DFL CHUNK_ALIGN(300*1024)
-#define BUFF_MIN CHUNK_ALIGN(8*1024)
+#define BUFF_MAX CHUNK_ALIGN(64*1024*1024)
+#define BUFF_DFL CHUNK_ALIGN(300*1024)
+#define BUFF_MIN CHUNK_ALIGN(8*1024)
/*
* The per-event API header (2 per URB).
diff --git a/drivers/usb/musb/musb_dsps.c b/drivers/usb/musb/musb_dsps.c
index 12f587ab8511..a08ce96c08d3 100644
--- a/drivers/usb/musb/musb_dsps.c
+++ b/drivers/usb/musb/musb_dsps.c
@@ -839,7 +839,7 @@ static int dsps_setup_optional_vbus_irq(struct platform_device *pdev,
{
int error;
- glue->vbus_irq = platform_get_irq_byname(pdev, "vbus");
+ glue->vbus_irq = platform_get_irq_byname_optional(pdev, "vbus");
if (glue->vbus_irq == -EPROBE_DEFER)
return -EPROBE_DEFER;
diff --git a/drivers/usb/phy/phy-twl6030-usb.c b/drivers/usb/phy/phy-twl6030-usb.c
index 49d79c1257f3..8c09db750bfd 100644
--- a/drivers/usb/phy/phy-twl6030-usb.c
+++ b/drivers/usb/phy/phy-twl6030-usb.c
@@ -328,9 +328,8 @@ static int twl6030_set_vbus(struct phy_companion *comparator, bool enabled)
static int twl6030_usb_probe(struct platform_device *pdev)
{
- u32 ret;
struct twl6030_usb *twl;
- int status, err;
+ int status, err, ret;
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c
index 18a6ef4dce51..8f536f2c500f 100644
--- a/drivers/usb/renesas_usbhs/common.c
+++ b/drivers/usb/renesas_usbhs/common.c
@@ -580,6 +580,10 @@ static const struct of_device_id usbhs_of_match[] = {
.data = &usbhs_rzg2l_plat_info,
},
{
+ .compatible = "renesas,usbhs-r9a09g077",
+ .data = &usbhs_rzg2l_plat_info,
+ },
+ {
.compatible = "renesas,rcar-gen2-usbhs",
.data = &usbhs_rcar_gen2_plat_info,
},
diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
index fc869b7f803f..62e984d20e59 100644
--- a/drivers/usb/serial/option.c
+++ b/drivers/usb/serial/option.c
@@ -2114,6 +2114,12 @@ static const struct usb_device_id option_ids[] = {
{ USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9003, 0xff) }, /* Simcom SIM7500/SIM7600 MBIM mode */
{ USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9011, 0xff), /* Simcom SIM7500/SIM7600 RNDIS mode */
.driver_info = RSVD(7) },
+ { USB_DEVICE(0x1e0e, 0x9071), /* Simcom SIM8230 RMNET mode */
+ .driver_info = RSVD(3) | RSVD(4) },
+ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9078, 0xff), /* Simcom SIM8230 ECM mode */
+ .driver_info = RSVD(5) },
+ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x907b, 0xff), /* Simcom SIM8230 RNDIS mode */
+ .driver_info = RSVD(5) },
{ USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9205, 0xff) }, /* Simcom SIM7070/SIM7080/SIM7090 AT+ECM mode */
{ USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9206, 0xff) }, /* Simcom SIM7070/SIM7080/SIM7090 AT-only mode */
{ USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_X060S_X200),
diff --git a/drivers/usb/serial/oti6858.c b/drivers/usb/serial/oti6858.c
index 24068368892c..bd206cb9cc08 100644
--- a/drivers/usb/serial/oti6858.c
+++ b/drivers/usb/serial/oti6858.c
@@ -106,7 +106,7 @@ struct oti6858_control_pkt {
#define PIN_DTR 0x04 /* output pin */
#define PIN_RI 0x02 /* input pin, active low */
#define PIN_DCD 0x01 /* input pin, active low */
- u8 rx_bytes_avail; /* number of bytes in rx buffer */;
+ u8 rx_bytes_avail; /* number of bytes in rx buffer */
};
#define OTI6858_CTRL_PKT_SIZE sizeof(struct oti6858_control_pkt)
diff --git a/drivers/usb/storage/realtek_cr.c b/drivers/usb/storage/realtek_cr.c
index cb5bbb19060e..3cc243956fd4 100644
--- a/drivers/usb/storage/realtek_cr.c
+++ b/drivers/usb/storage/realtek_cr.c
@@ -260,8 +260,8 @@ static int rts51x_bulk_transport(struct us_data *us, u8 lun,
* try to compute the actual residue, based on how much data
* was really transferred and what the device tells us
*/
- if (residue)
- residue = residue < buf_len ? residue : buf_len;
+ if (residue > buf_len)
+ residue = buf_len;
if (act_len)
*act_len = buf_len - residue;
diff --git a/drivers/usb/typec/mux/tusb1046.c b/drivers/usb/typec/mux/tusb1046.c
index b4f45c217b59..3c1a4551c2fb 100644
--- a/drivers/usb/typec/mux/tusb1046.c
+++ b/drivers/usb/typec/mux/tusb1046.c
@@ -129,7 +129,7 @@ static int tusb1046_i2c_probe(struct i2c_client *client)
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
- return dev_err_probe(dev, -ENOMEM, "failed to allocate driver data\n");
+ return -ENOMEM;
priv->client = client;
diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c
index 18303b34594b..c8b1463e6e8b 100644
--- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c
+++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c
@@ -567,7 +567,7 @@ int qcom_pmic_typec_pdphy_probe(struct platform_device *pdev,
if (!res->nr_irqs || res->nr_irqs > PMIC_PDPHY_MAX_IRQS)
return -EINVAL;
- irq_data = devm_kzalloc(dev, sizeof(*irq_data) * res->nr_irqs,
+ irq_data = devm_kcalloc(dev, res->nr_irqs, sizeof(*irq_data),
GFP_KERNEL);
if (!irq_data)
return -ENOMEM;
diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c
index 4fc83dcfae64..8051eaa46991 100644
--- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c
+++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c
@@ -713,7 +713,7 @@ int qcom_pmic_typec_port_probe(struct platform_device *pdev,
if (!res->nr_irqs || res->nr_irqs > PMIC_TYPEC_MAX_IRQS)
return -EINVAL;
- irq_data = devm_kzalloc(dev, sizeof(*irq_data) * res->nr_irqs,
+ irq_data = devm_kcalloc(dev, res->nr_irqs, sizeof(*irq_data),
GFP_KERNEL);
if (!irq_data)
return -ENOMEM;
diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
index a56e31b20c21..2a951c585e92 100644
--- a/drivers/usb/typec/tcpm/tcpci.c
+++ b/drivers/usb/typec/tcpm/tcpci.c
@@ -949,6 +949,8 @@ static int tcpci_probe(struct i2c_client *client)
if (err < 0)
goto unregister_port;
+ device_set_wakeup_capable(chip->tcpci->dev, true);
+
return 0;
unregister_port:
@@ -969,6 +971,36 @@ static void tcpci_remove(struct i2c_client *client)
tcpci_unregister_port(chip->tcpci);
}
+static int tcpci_suspend(struct device *dev)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct tcpci_chip *chip = i2c_get_clientdata(i2c);
+ int ret;
+
+ if (device_may_wakeup(dev))
+ ret = enable_irq_wake(i2c->irq);
+ else
+ ret = tcpci_write16(chip->tcpci, TCPC_ALERT_MASK, 0);
+
+ return ret;
+}
+
+static int tcpci_resume(struct device *dev)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct tcpci_chip *chip = i2c_get_clientdata(i2c);
+ int ret;
+
+ if (device_may_wakeup(dev))
+ ret = disable_irq_wake(i2c->irq);
+ else
+ ret = tcpci_write16(chip->tcpci, TCPC_ALERT_MASK, chip->tcpci->alert_mask);
+
+ return ret;
+}
+
+DEFINE_SIMPLE_DEV_PM_OPS(tcpci_pm_ops, tcpci_suspend, tcpci_resume);
+
static const struct i2c_device_id tcpci_id[] = {
{ "tcpci" },
{ }
@@ -987,6 +1019,7 @@ MODULE_DEVICE_TABLE(of, tcpci_of_match);
static struct i2c_driver tcpci_i2c_driver = {
.driver = {
.name = "tcpci",
+ .pm = pm_sleep_ptr(&tcpci_pm_ops),
.of_match_table = of_match_ptr(tcpci_of_match),
},
.probe = tcpci_probe,
diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c
index dcf141ada078..2b1049c9a6f3 100644
--- a/drivers/usb/typec/tipd/core.c
+++ b/drivers/usb/typec/tipd/core.c
@@ -16,6 +16,9 @@
#include <linux/interrupt.h>
#include <linux/usb/typec.h>
#include <linux/usb/typec_altmode.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_mux.h>
+#include <linux/usb/typec_tbt.h>
#include <linux/usb/role.h>
#include <linux/workqueue.h>
#include <linux/firmware.h>
@@ -35,14 +38,18 @@
#define TPS_REG_INT_MASK2 0x17
#define TPS_REG_INT_CLEAR1 0x18
#define TPS_REG_INT_CLEAR2 0x19
-#define TPS_REG_SYSTEM_POWER_STATE 0x20
#define TPS_REG_STATUS 0x1a
+#define TPS_REG_SYSTEM_POWER_STATE 0x20
+#define TPS_REG_USB4_STATUS 0x24
#define TPS_REG_SYSTEM_CONF 0x28
#define TPS_REG_CTRL_CONF 0x29
#define TPS_REG_BOOT_STATUS 0x2D
#define TPS_REG_POWER_STATUS 0x3f
#define TPS_REG_PD_STATUS 0x40
#define TPS_REG_RX_IDENTITY_SOP 0x48
+#define TPS_REG_CF_VID_STATUS 0x5e
+#define TPS_REG_DP_SID_STATUS 0x58
+#define TPS_REG_INTEL_VID_STATUS 0x59
#define TPS_REG_DATA_STATUS 0x5f
#define TPS_REG_SLEEP_CONF 0x70
@@ -85,10 +92,38 @@ struct tps6598x_rx_identity_reg {
struct usb_pd_identity identity;
} __packed;
+/* TPS_REG_USB4_STATUS */
+struct tps6598x_usb4_status_reg {
+ u8 mode_status;
+ __le32 eudo;
+ __le32 unknown;
+} __packed;
+
+/* TPS_REG_DP_SID_STATUS */
+struct tps6598x_dp_sid_status_reg {
+ u8 mode_status;
+ __le32 status_tx;
+ __le32 status_rx;
+ __le32 configure;
+ __le32 mode_data;
+} __packed;
+
+/* TPS_REG_INTEL_VID_STATUS */
+struct tps6598x_intel_vid_status_reg {
+ u8 mode_status;
+ __le32 attention_vdo;
+ __le16 enter_vdo;
+ __le16 device_mode;
+ __le16 cable_mode;
+} __packed;
+
/* Standard Task return codes */
#define TPS_TASK_TIMEOUT 1
#define TPS_TASK_REJECTED 3
+/* Debounce delay for mode changes, in milliseconds */
+#define CD321X_DEBOUNCE_DELAY_MS 500
+
enum {
TPS_MODE_APP,
TPS_MODE_BOOT,
@@ -112,12 +147,20 @@ struct tps6598x;
struct tipd_data {
irq_handler_t irq_handler;
+ u64 irq_mask1;
+ size_t tps_struct_size;
+ void (*remove)(struct tps6598x *tps);
int (*register_port)(struct tps6598x *tps, struct fwnode_handle *node);
+ void (*unregister_port)(struct tps6598x *tps);
+ void (*trace_data_status)(u32 status);
void (*trace_power_status)(u16 status);
void (*trace_status)(u32 status);
int (*apply_patch)(struct tps6598x *tps);
int (*init)(struct tps6598x *tps);
+ int (*switch_power_state)(struct tps6598x *tps, u8 target_state);
+ bool (*read_data_status)(struct tps6598x *tps);
int (*reset)(struct tps6598x *tps);
+ int (*connect)(struct tps6598x *tps, u32 status);
};
struct tps6598x {
@@ -139,12 +182,42 @@ struct tps6598x {
int wakeup;
u32 status; /* status reg */
+ u32 data_status;
u16 pwr_status;
struct delayed_work wq_poll;
const struct tipd_data *data;
};
+struct cd321x_status {
+ u32 status;
+ u32 pwr_status;
+ u32 data_status;
+ u32 status_changed;
+ struct usb_pd_identity partner_identity;
+ struct tps6598x_dp_sid_status_reg dp_sid_status;
+ struct tps6598x_intel_vid_status_reg intel_vid_status;
+ struct tps6598x_usb4_status_reg usb4_status;
+};
+
+struct cd321x {
+ struct tps6598x tps;
+
+ struct tps6598x_dp_sid_status_reg dp_sid_status;
+ struct tps6598x_intel_vid_status_reg intel_vid_status;
+ struct tps6598x_usb4_status_reg usb4_status;
+
+ struct typec_altmode *port_altmode_dp;
+ struct typec_altmode *port_altmode_tbt;
+
+ struct typec_mux *mux;
+ struct typec_mux_state state;
+
+ struct cd321x_status update_status;
+ struct delayed_work update_work;
+ struct usb_pd_identity cur_partner_identity;
+};
+
static enum power_supply_property tps6598x_psy_props[] = {
POWER_SUPPLY_PROP_USB_TYPE,
POWER_SUPPLY_PROP_ONLINE,
@@ -490,7 +563,45 @@ static bool tps6598x_read_data_status(struct tps6598x *tps)
dev_err(tps->dev, "failed to read data status: %d\n", ret);
return false;
}
- trace_tps6598x_data_status(data_status);
+ tps->data_status = data_status;
+
+ if (tps->data->trace_data_status)
+ tps->data->trace_data_status(data_status);
+
+ return true;
+}
+
+static bool cd321x_read_data_status(struct tps6598x *tps)
+{
+ struct cd321x *cd321x = container_of(tps, struct cd321x, tps);
+ int ret;
+
+ ret = tps6598x_read_data_status(tps);
+ if (ret < 0)
+ return false;
+
+ if (tps->data_status & TPS_DATA_STATUS_DP_CONNECTION) {
+ ret = tps6598x_block_read(tps, TPS_REG_DP_SID_STATUS,
+ &cd321x->dp_sid_status, sizeof(cd321x->dp_sid_status));
+ if (ret)
+ dev_err(tps->dev, "Failed to read DP SID Status: %d\n",
+ ret);
+ }
+
+ if (tps->data_status & TPS_DATA_STATUS_TBT_CONNECTION) {
+ ret = tps6598x_block_read(tps, TPS_REG_INTEL_VID_STATUS,
+ &cd321x->intel_vid_status, sizeof(cd321x->intel_vid_status));
+ if (ret)
+ dev_err(tps->dev, "Failed to read Intel VID Status: %d\n", ret);
+ }
+
+ if (tps->data_status & CD321X_DATA_STATUS_USB4_CONNECTION) {
+ ret = tps6598x_block_read(tps, TPS_REG_USB4_STATUS,
+ &cd321x->usb4_status, sizeof(cd321x->usb4_status));
+ if (ret)
+ dev_err(tps->dev,
+ "Failed to read USB4 Status: %d\n", ret);
+ }
return true;
}
@@ -526,6 +637,233 @@ static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status)
}
}
+static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status *st)
+{
+ struct cd321x *cd321x = container_of(tps, struct cd321x, tps);
+
+ if (!(st->data_status & TPS_DATA_STATUS_DATA_CONNECTION)) {
+ if (cd321x->state.mode == TYPEC_STATE_SAFE)
+ return;
+ cd321x->state.alt = NULL;
+ cd321x->state.mode = TYPEC_STATE_SAFE;
+ cd321x->state.data = NULL;
+ typec_mux_set(cd321x->mux, &cd321x->state);
+ } else if (st->data_status & TPS_DATA_STATUS_DP_CONNECTION) {
+ struct typec_displayport_data dp_data;
+ unsigned long mode;
+
+ switch (TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT(st->data_status)) {
+ case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_A:
+ mode = TYPEC_DP_STATE_A;
+ break;
+ case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_B:
+ mode = TYPEC_DP_STATE_B;
+ break;
+ case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_C:
+ mode = TYPEC_DP_STATE_C;
+ break;
+ case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_D:
+ mode = TYPEC_DP_STATE_D;
+ break;
+ case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_E:
+ mode = TYPEC_DP_STATE_E;
+ break;
+ case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_F:
+ mode = TYPEC_DP_STATE_F;
+ break;
+ default:
+ dev_err(tps->dev, "Invalid DP pin assignment\n");
+ return;
+ }
+
+ if (cd321x->state.alt == cd321x->port_altmode_dp &&
+ cd321x->state.mode == mode) {
+ return;
+ }
+
+ dp_data.status = le32_to_cpu(st->dp_sid_status.status_rx);
+ dp_data.conf = le32_to_cpu(st->dp_sid_status.configure);
+ cd321x->state.alt = cd321x->port_altmode_dp;
+ cd321x->state.data = &dp_data;
+ cd321x->state.mode = mode;
+ typec_mux_set(cd321x->mux, &cd321x->state);
+ } else if (st->data_status & TPS_DATA_STATUS_TBT_CONNECTION) {
+ struct typec_thunderbolt_data tbt_data;
+
+ if (cd321x->state.alt == cd321x->port_altmode_tbt &&
+ cd321x->state.mode == TYPEC_TBT_MODE)
+ return;
+
+ tbt_data.cable_mode = le16_to_cpu(st->intel_vid_status.cable_mode);
+ tbt_data.device_mode = le16_to_cpu(st->intel_vid_status.device_mode);
+ tbt_data.enter_vdo = le16_to_cpu(st->intel_vid_status.enter_vdo);
+ cd321x->state.alt = cd321x->port_altmode_tbt;
+ cd321x->state.mode = TYPEC_TBT_MODE;
+ cd321x->state.data = &tbt_data;
+ typec_mux_set(cd321x->mux, &cd321x->state);
+ } else if (st->data_status & CD321X_DATA_STATUS_USB4_CONNECTION) {
+ struct enter_usb_data eusb_data;
+
+ if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_MODE_USB4)
+ return;
+
+ eusb_data.eudo = le32_to_cpu(st->usb4_status.eudo);
+ eusb_data.active_link_training =
+ !!(st->data_status & TPS_DATA_STATUS_ACTIVE_LINK_TRAIN);
+
+ cd321x->state.alt = NULL;
+ cd321x->state.data = &eusb_data;
+ cd321x->state.mode = TYPEC_MODE_USB4;
+ typec_mux_set(cd321x->mux, &cd321x->state);
+ } else {
+ if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_STATE_USB)
+ return;
+ cd321x->state.alt = NULL;
+ cd321x->state.mode = TYPEC_STATE_USB;
+ cd321x->state.data = NULL;
+ typec_mux_set(cd321x->mux, &cd321x->state);
+ }
+
+ /* Clear data since it's no longer used after typec_mux_set and points to the stack */
+ cd321x->state.data = NULL;
+}
+
+static void cd321x_update_work(struct work_struct *work)
+{
+ struct cd321x *cd321x = container_of(to_delayed_work(work),
+ struct cd321x, update_work);
+ struct tps6598x *tps = &cd321x->tps;
+ struct cd321x_status st;
+
+ guard(mutex)(&tps->lock);
+
+ st = cd321x->update_status;
+ cd321x->update_status.status_changed = 0;
+
+ bool old_connected = !!tps->partner;
+ bool new_connected = st.status & TPS_STATUS_PLUG_PRESENT;
+ bool was_disconnected = st.status_changed & TPS_STATUS_PLUG_PRESENT;
+
+ bool usb_connection = st.data_status &
+ (TPS_DATA_STATUS_USB2_CONNECTION | TPS_DATA_STATUS_USB3_CONNECTION);
+
+ enum usb_role old_role = usb_role_switch_get_role(tps->role_sw);
+ enum usb_role new_role = USB_ROLE_NONE;
+ enum typec_pwr_opmode pwr_opmode = TYPEC_PWR_MODE_USB;
+ enum typec_orientation orientation = TYPEC_ORIENTATION_NONE;
+
+ if (usb_connection) {
+ if (tps->data_status & TPS_DATA_STATUS_USB_DATA_ROLE)
+ new_role = USB_ROLE_DEVICE;
+ else
+ new_role = USB_ROLE_HOST;
+ }
+
+ if (new_connected) {
+ pwr_opmode = TPS_POWER_STATUS_PWROPMODE(st.pwr_status);
+ orientation = TPS_STATUS_TO_UPSIDE_DOWN(st.status) ?
+ TYPEC_ORIENTATION_REVERSE : TYPEC_ORIENTATION_NORMAL;
+ }
+
+ bool is_pd = pwr_opmode == TYPEC_PWR_MODE_PD;
+ bool partner_changed = old_connected && new_connected &&
+ (was_disconnected ||
+ (is_pd && memcmp(&st.partner_identity,
+ &cd321x->cur_partner_identity, sizeof(struct usb_pd_identity))));
+
+ /* If we are switching from an active role, transition to USB_ROLE_NONE first */
+ if (old_role != USB_ROLE_NONE && (new_role != old_role || was_disconnected))
+ usb_role_switch_set_role(tps->role_sw, USB_ROLE_NONE);
+
+ /* Process partner disconnection or change */
+ if (!new_connected || partner_changed) {
+ if (!IS_ERR(tps->partner))
+ typec_unregister_partner(tps->partner);
+ tps->partner = NULL;
+ }
+
+ /* If there was a disconnection, set PHY to off */
+ if (!new_connected || was_disconnected) {
+ cd321x->state.alt = NULL;
+ cd321x->state.mode = TYPEC_STATE_SAFE;
+ cd321x->state.data = NULL;
+ typec_set_mode(tps->port, TYPEC_STATE_SAFE);
+ }
+
+ /* Update Type-C properties */
+ typec_set_pwr_opmode(tps->port, pwr_opmode);
+ typec_set_pwr_role(tps->port, TPS_STATUS_TO_TYPEC_PORTROLE(st.status));
+ typec_set_vconn_role(tps->port, TPS_STATUS_TO_TYPEC_VCONN(st.status));
+ typec_set_orientation(tps->port, orientation);
+ typec_set_data_role(tps->port, TPS_STATUS_TO_TYPEC_DATAROLE(st.status));
+ power_supply_changed(tps->psy);
+
+ /* If the plug is disconnected, we are done */
+ if (!new_connected)
+ return;
+
+ /* Set up partner if we were previously disconnected (or changed). */
+ if (!tps->partner) {
+ struct typec_partner_desc desc;
+
+ desc.usb_pd = is_pd;
+ desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */
+ desc.identity = NULL;
+
+ if (desc.usb_pd)
+ desc.identity = &st.partner_identity;
+
+ tps->partner = typec_register_partner(tps->port, &desc);
+ if (IS_ERR(tps->partner))
+ dev_warn(tps->dev, "%s: failed to register partnet\n", __func__);
+
+ if (desc.identity) {
+ typec_partner_set_identity(tps->partner);
+ cd321x->cur_partner_identity = st.partner_identity;
+ }
+ }
+
+ /* Update the TypeC MUX/PHY state */
+ cd321x_typec_update_mode(tps, &st);
+
+ /* Launch the USB role switch */
+ usb_role_switch_set_role(tps->role_sw, new_role);
+
+ power_supply_changed(tps->psy);
+}
+
+static void cd321x_queue_status(struct cd321x *cd321x)
+{
+ cd321x->update_status.status_changed |= cd321x->update_status.status ^ cd321x->tps.status;
+
+ cd321x->update_status.status = cd321x->tps.status;
+ cd321x->update_status.pwr_status = cd321x->tps.pwr_status;
+ cd321x->update_status.data_status = cd321x->tps.data_status;
+
+ cd321x->update_status.partner_identity = cd321x->tps.partner_identity;
+ cd321x->update_status.dp_sid_status = cd321x->dp_sid_status;
+ cd321x->update_status.intel_vid_status = cd321x->intel_vid_status;
+ cd321x->update_status.usb4_status = cd321x->usb4_status;
+}
+
+static int cd321x_connect(struct tps6598x *tps, u32 status)
+{
+ struct cd321x *cd321x = container_of(tps, struct cd321x, tps);
+
+ tps->status = status;
+ cd321x_queue_status(cd321x);
+
+ /*
+ * Cancel pending work if not already running, then requeue after CD321X_DEBOUNCE_DELAY_MS
+ * regardless since the work function will check for any plug or altmodes changes since
+ * its last run anyway.
+ */
+ cancel_delayed_work(&cd321x->update_work);
+ schedule_delayed_work(&cd321x->update_work, msecs_to_jiffies(CD321X_DEBOUNCE_DELAY_MS));
+
+ return 0;
+}
+
static irqreturn_t cd321x_interrupt(int irq, void *data)
{
struct tps6598x *tps = data;
@@ -545,23 +883,28 @@ static irqreturn_t cd321x_interrupt(int irq, void *data)
if (!event)
goto err_unlock;
+ tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event);
+
if (!tps6598x_read_status(tps, &status))
- goto err_clear_ints;
+ goto err_unlock;
- if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE)
+ if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE) {
if (!tps6598x_read_power_status(tps))
- goto err_clear_ints;
+ goto err_unlock;
+ if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD) {
+ if (tps6598x_read_partner_identity(tps)) {
+ dev_err(tps->dev, "failed to read partner identity\n");
+ tps->partner_identity = (struct usb_pd_identity) {0};
+ }
+ }
+ }
if (event & APPLE_CD_REG_INT_DATA_STATUS_UPDATE)
- if (!tps6598x_read_data_status(tps))
- goto err_clear_ints;
+ if (!tps->data->read_data_status(tps))
+ goto err_unlock;
- /* Handle plug insert or removal */
- if (event & APPLE_CD_REG_INT_PLUG_EVENT)
- tps6598x_handle_plug_event(tps, status);
-
-err_clear_ints:
- tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event);
+ /* Can be called uncondtionally since it will check for any changes itself */
+ cd321x_connect(tps, status);
err_unlock:
mutex_unlock(&tps->lock);
@@ -605,7 +948,7 @@ static irqreturn_t tps25750_interrupt(int irq, void *data)
goto err_clear_ints;
if (event[0] & TPS_REG_INT_DATA_STATUS_UPDATE)
- if (!tps6598x_read_data_status(tps))
+ if (!tps->data->read_data_status(tps))
goto err_clear_ints;
/*
@@ -668,25 +1011,24 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data)
if (!(event1[0] | event1[1] | event2[0] | event2[1]))
goto err_unlock;
+ tps6598x_block_write(tps, TPS_REG_INT_CLEAR1, event1, intev_len);
+ tps6598x_block_write(tps, TPS_REG_INT_CLEAR2, event2, intev_len);
+
if (!tps6598x_read_status(tps, &status))
- goto err_clear_ints;
+ goto err_unlock;
if ((event1[0] | event2[0]) & TPS_REG_INT_POWER_STATUS_UPDATE)
if (!tps6598x_read_power_status(tps))
- goto err_clear_ints;
+ goto err_unlock;
if ((event1[0] | event2[0]) & TPS_REG_INT_DATA_STATUS_UPDATE)
- if (!tps6598x_read_data_status(tps))
- goto err_clear_ints;
+ if (!tps->data->read_data_status(tps))
+ goto err_unlock;
/* Handle plug insert or removal */
if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT)
tps6598x_handle_plug_event(tps, status);
-err_clear_ints:
- tps6598x_block_write(tps, TPS_REG_INT_CLEAR1, event1, intev_len);
- tps6598x_block_write(tps, TPS_REG_INT_CLEAR2, event2, intev_len);
-
err_unlock:
mutex_unlock(&tps->lock);
@@ -887,6 +1229,94 @@ tps6598x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode)
return 0;
}
+static int cd321x_register_port_altmodes(struct cd321x *cd321x)
+{
+ struct typec_altmode_desc desc;
+ struct typec_altmode *amode;
+
+ memset(&desc, 0, sizeof(desc));
+ desc.svid = USB_TYPEC_DP_SID;
+ desc.mode = USB_TYPEC_DP_MODE;
+ desc.vdo = DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D));
+ desc.vdo |= DP_CAP_DFP_D;
+ amode = typec_port_register_altmode(cd321x->tps.port, &desc);
+ if (IS_ERR(amode))
+ return PTR_ERR(amode);
+ cd321x->port_altmode_dp = amode;
+
+ memset(&desc, 0, sizeof(desc));
+ desc.svid = USB_TYPEC_TBT_SID;
+ desc.mode = TYPEC_ANY_MODE;
+ amode = typec_port_register_altmode(cd321x->tps.port, &desc);
+ if (IS_ERR(amode)) {
+ typec_unregister_altmode(cd321x->port_altmode_dp);
+ cd321x->port_altmode_dp = NULL;
+ return PTR_ERR(amode);
+ }
+ cd321x->port_altmode_tbt = amode;
+
+ return 0;
+}
+
+static int
+cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode)
+{
+ struct cd321x *cd321x = container_of(tps, struct cd321x, tps);
+ int ret;
+
+ INIT_DELAYED_WORK(&cd321x->update_work, cd321x_update_work);
+
+ ret = tps6598x_register_port(tps, fwnode);
+ if (ret)
+ return ret;
+
+ ret = cd321x_register_port_altmodes(cd321x);
+ if (ret)
+ goto err_unregister_port;
+
+ cd321x->mux = fwnode_typec_mux_get(fwnode);
+ if (IS_ERR(cd321x->mux)) {
+ ret = PTR_ERR(cd321x->mux);
+ goto err_unregister_altmodes;
+ }
+
+ cd321x->state.alt = NULL;
+ cd321x->state.mode = TYPEC_STATE_SAFE;
+ cd321x->state.data = NULL;
+ typec_set_mode(tps->port, TYPEC_STATE_SAFE);
+
+ return 0;
+
+err_unregister_altmodes:
+ typec_unregister_altmode(cd321x->port_altmode_dp);
+ typec_unregister_altmode(cd321x->port_altmode_tbt);
+ cd321x->port_altmode_dp = NULL;
+ cd321x->port_altmode_tbt = NULL;
+err_unregister_port:
+ typec_unregister_port(tps->port);
+ return ret;
+}
+
+static void
+tps6598x_unregister_port(struct tps6598x *tps)
+{
+ typec_unregister_port(tps->port);
+}
+
+static void
+cd321x_unregister_port(struct tps6598x *tps)
+{
+ struct cd321x *cd321x = container_of(tps, struct cd321x, tps);
+
+ typec_mux_put(cd321x->mux);
+ cd321x->mux = NULL;
+ typec_unregister_altmode(cd321x->port_altmode_dp);
+ cd321x->port_altmode_dp = NULL;
+ typec_unregister_altmode(cd321x->port_altmode_tbt);
+ cd321x->port_altmode_tbt = NULL;
+ typec_unregister_port(tps->port);
+}
+
static int tps_request_firmware(struct tps6598x *tps, const struct firmware **fw,
const char **firmware_name)
{
@@ -1292,22 +1722,33 @@ tps25750_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode)
return 0;
}
+static void cd321x_remove(struct tps6598x *tps)
+{
+ struct cd321x *cd321x = container_of(tps, struct cd321x, tps);
+
+ cancel_delayed_work_sync(&cd321x->update_work);
+}
+
static int tps6598x_probe(struct i2c_client *client)
{
- struct device_node *np = client->dev.of_node;
+ const struct tipd_data *data;
struct tps6598x *tps;
struct fwnode_handle *fwnode;
u32 status;
u32 vid;
int ret;
- u64 mask1;
- tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL);
+ data = i2c_get_match_data(client);
+ if (!data)
+ return -EINVAL;
+
+ tps = devm_kzalloc(&client->dev, data->tps_struct_size, GFP_KERNEL);
if (!tps)
return -ENOMEM;
mutex_init(&tps->lock);
tps->dev = &client->dev;
+ tps->data = data;
tps->reset = devm_gpiod_get_optional(tps->dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(tps->reset))
@@ -1333,28 +1774,12 @@ static int tps6598x_probe(struct i2c_client *client)
if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
tps->i2c_protocol = true;
- if (np && of_device_is_compatible(np, "apple,cd321x")) {
- /* Switch CD321X chips to the correct system power state */
- ret = cd321x_switch_power_state(tps, TPS_SYSTEM_POWER_STATE_S0);
+ if (tps->data->switch_power_state) {
+ ret = tps->data->switch_power_state(tps, TPS_SYSTEM_POWER_STATE_S0);
if (ret)
return ret;
-
- /* CD321X chips have all interrupts masked initially */
- mask1 = APPLE_CD_REG_INT_POWER_STATUS_UPDATE |
- APPLE_CD_REG_INT_DATA_STATUS_UPDATE |
- APPLE_CD_REG_INT_PLUG_EVENT;
-
- } else {
- /* Enable power status, data status and plug event interrupts */
- mask1 = TPS_REG_INT_POWER_STATUS_UPDATE |
- TPS_REG_INT_DATA_STATUS_UPDATE |
- TPS_REG_INT_PLUG_EVENT;
}
- tps->data = i2c_get_match_data(client);
- if (!tps->data)
- return -EINVAL;
-
/* Make sure the controller has application firmware running */
ret = tps6598x_check_mode(tps);
if (ret < 0)
@@ -1366,7 +1791,7 @@ static int tps6598x_probe(struct i2c_client *client)
return ret;
}
- ret = tps6598x_write64(tps, TPS_REG_INT_MASK1, mask1);
+ ret = tps6598x_write64(tps, TPS_REG_INT_MASK1, tps->data->irq_mask1);
if (ret)
goto err_reset_controller;
@@ -1401,12 +1826,11 @@ static int tps6598x_probe(struct i2c_client *client)
goto err_role_put;
if (status & TPS_STATUS_PLUG_PRESENT) {
- ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &tps->pwr_status);
- if (ret < 0) {
- dev_err(tps->dev, "failed to read power status: %d\n", ret);
+ if (!tps6598x_read_power_status(tps))
goto err_unregister_port;
- }
- ret = tps6598x_connect(tps, status);
+ if (!tps->data->read_data_status(tps))
+ goto err_unregister_port;
+ ret = tps->data->connect(tps, status);
if (ret)
dev_err(&client->dev, "failed to register partner\n");
}
@@ -1440,7 +1864,7 @@ static int tps6598x_probe(struct i2c_client *client)
err_disconnect:
tps6598x_disconnect(tps, 0);
err_unregister_port:
- typec_unregister_port(tps->port);
+ tps->data->unregister_port(tps);
err_role_put:
usb_role_switch_put(tps->role_sw);
err_fwnode_put:
@@ -1463,8 +1887,11 @@ static void tps6598x_remove(struct i2c_client *client)
else
devm_free_irq(tps->dev, client->irq, tps);
+ if (tps->data->remove)
+ tps->data->remove(tps);
+
tps6598x_disconnect(tps, 0);
- typec_unregister_port(tps->port);
+ tps->data->unregister_port(tps);
usb_role_switch_put(tps->role_sw);
/* Reset PD controller to remove any applied patch */
@@ -1529,31 +1956,57 @@ static const struct dev_pm_ops tps6598x_pm_ops = {
static const struct tipd_data cd321x_data = {
.irq_handler = cd321x_interrupt,
- .register_port = tps6598x_register_port,
+ .irq_mask1 = APPLE_CD_REG_INT_POWER_STATUS_UPDATE |
+ APPLE_CD_REG_INT_DATA_STATUS_UPDATE |
+ APPLE_CD_REG_INT_PLUG_EVENT,
+ .tps_struct_size = sizeof(struct cd321x),
+ .remove = cd321x_remove,
+ .register_port = cd321x_register_port,
+ .unregister_port = cd321x_unregister_port,
+ .trace_data_status = trace_cd321x_data_status,
.trace_power_status = trace_tps6598x_power_status,
.trace_status = trace_tps6598x_status,
.init = cd321x_init,
+ .read_data_status = cd321x_read_data_status,
.reset = cd321x_reset,
+ .switch_power_state = cd321x_switch_power_state,
+ .connect = cd321x_connect,
};
static const struct tipd_data tps6598x_data = {
.irq_handler = tps6598x_interrupt,
+ .irq_mask1 = TPS_REG_INT_POWER_STATUS_UPDATE |
+ TPS_REG_INT_DATA_STATUS_UPDATE |
+ TPS_REG_INT_PLUG_EVENT,
+ .tps_struct_size = sizeof(struct tps6598x),
.register_port = tps6598x_register_port,
+ .unregister_port = tps6598x_unregister_port,
+ .trace_data_status = trace_tps6598x_data_status,
.trace_power_status = trace_tps6598x_power_status,
.trace_status = trace_tps6598x_status,
.apply_patch = tps6598x_apply_patch,
.init = tps6598x_init,
+ .read_data_status = tps6598x_read_data_status,
.reset = tps6598x_reset,
+ .connect = tps6598x_connect,
};
static const struct tipd_data tps25750_data = {
.irq_handler = tps25750_interrupt,
+ .irq_mask1 = TPS_REG_INT_POWER_STATUS_UPDATE |
+ TPS_REG_INT_DATA_STATUS_UPDATE |
+ TPS_REG_INT_PLUG_EVENT,
+ .tps_struct_size = sizeof(struct tps6598x),
.register_port = tps25750_register_port,
+ .unregister_port = tps6598x_unregister_port,
+ .trace_data_status = trace_tps6598x_data_status,
.trace_power_status = trace_tps25750_power_status,
.trace_status = trace_tps25750_status,
.apply_patch = tps25750_apply_patch,
.init = tps25750_init,
+ .read_data_status = tps6598x_read_data_status,
.reset = tps25750_reset,
+ .connect = tps6598x_connect,
};
static const struct of_device_id tps6598x_of_match[] = {
diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h
index cecb8d11d239..03edbb77bbd6 100644
--- a/drivers/usb/typec/tipd/tps6598x.h
+++ b/drivers/usb/typec/tipd/tps6598x.h
@@ -197,6 +197,11 @@
#define TPS_DATA_STATUS_FORCE_LSX BIT(23)
#define TPS_DATA_STATUS_POWER_MISMATCH BIT(24)
+/* modified TPS_REG_DATA_STATUS bits for CD321x (and likely also TPS65987DDK) */
+#define CD321X_DATA_STATUS_HPD_IRQ BIT(14)
+#define CD321X_DATA_STATUS_HPD_LEVEL BIT(15)
+#define CD321X_DATA_STATUS_USB4_CONNECTION BIT(23)
+
#define TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK GENMASK(11, 10)
#define TPS_DATA_STATUS_DP_PIN_ASSIGNMENT(x) \
TPS_FIELD_GET(TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK, (x))
diff --git a/drivers/usb/typec/tipd/trace.h b/drivers/usb/typec/tipd/trace.h
index bea383f2db9d..e9e40425138a 100644
--- a/drivers/usb/typec/tipd/trace.h
+++ b/drivers/usb/typec/tipd/trace.h
@@ -217,6 +217,26 @@
{ TPS_DATA_STATUS_FORCE_LSX, "FORCE_LSX" }, \
{ TPS_DATA_STATUS_POWER_MISMATCH, "POWER_MISMATCH" })
+#define show_cd321x_data_status_flags(data_status) \
+ __print_flags(data_status & TPS_DATA_STATUS_FLAGS_MASK, "|", \
+ { TPS_DATA_STATUS_DATA_CONNECTION, "DATA_CONNECTION" }, \
+ { TPS_DATA_STATUS_UPSIDE_DOWN, "DATA_UPSIDE_DOWN" }, \
+ { TPS_DATA_STATUS_ACTIVE_CABLE, "ACTIVE_CABLE" }, \
+ { TPS_DATA_STATUS_USB2_CONNECTION, "USB2_CONNECTION" }, \
+ { TPS_DATA_STATUS_USB3_CONNECTION, "USB3_CONNECTION" }, \
+ { TPS_DATA_STATUS_USB3_GEN2, "USB3_GEN2" }, \
+ { TPS_DATA_STATUS_USB_DATA_ROLE, "USB_DATA_ROLE" }, \
+ { TPS_DATA_STATUS_DP_CONNECTION, "DP_CONNECTION" }, \
+ { TPS_DATA_STATUS_DP_SINK, "DP_SINK" }, \
+ { CD321X_DATA_STATUS_HPD_IRQ, "HPD_IRQ" }, \
+ { CD321X_DATA_STATUS_HPD_LEVEL, "HPD_LEVEL" }, \
+ { TPS_DATA_STATUS_TBT_CONNECTION, "TBT_CONNECTION" }, \
+ { TPS_DATA_STATUS_TBT_TYPE, "TBT_TYPE" }, \
+ { TPS_DATA_STATUS_OPTICAL_CABLE, "OPTICAL_CABLE" }, \
+ { TPS_DATA_STATUS_ACTIVE_LINK_TRAIN, "ACTIVE_LINK_TRAIN" }, \
+ { CD321X_DATA_STATUS_USB4_CONNECTION, "USB4" }, \
+ { TPS_DATA_STATUS_POWER_MISMATCH, "POWER_MISMATCH" })
+
#define show_data_status_dp_pin_assignment(data_status) \
__print_symbolic(TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT(data_status), \
{ TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_E, "E" }, \
@@ -388,6 +408,25 @@ TRACE_EVENT(tps6598x_data_status,
)
);
+TRACE_EVENT(cd321x_data_status,
+ TP_PROTO(u32 data_status),
+ TP_ARGS(data_status),
+
+ TP_STRUCT__entry(
+ __field(u32, data_status)
+ ),
+
+ TP_fast_assign(
+ __entry->data_status = data_status;
+ ),
+
+ TP_printk("%s%s%s",
+ show_cd321x_data_status_flags(__entry->data_status),
+ __entry->data_status & TPS_DATA_STATUS_DP_CONNECTION ? ", DP pinout " : "",
+ maybe_show_data_status_dp_pin_assignment(__entry->data_status)
+ )
+);
+
#endif /* _TPS6598X_TRACE_H_ */
/* This part must be outside protection */
diff --git a/drivers/usb/typec/ucsi/debugfs.c b/drivers/usb/typec/ucsi/debugfs.c
index 92ebf1a2defd..f73f2b54554e 100644
--- a/drivers/usb/typec/ucsi/debugfs.c
+++ b/drivers/usb/typec/ucsi/debugfs.c
@@ -35,6 +35,7 @@ static int ucsi_cmd(void *data, u64 val)
case UCSI_SET_SINK_PATH:
case UCSI_SET_NEW_CAM:
case UCSI_SET_USB:
+ case UCSI_READ_POWER_LEVEL:
ret = ucsi_send_command(ucsi, val, NULL, 0);
break;
case UCSI_GET_CAPABILITY:
@@ -80,6 +81,33 @@ static int ucsi_resp_show(struct seq_file *s, void *not_used)
}
DEFINE_SHOW_ATTRIBUTE(ucsi_resp);
+static int ucsi_peak_curr_show(struct seq_file *m, void *v)
+{
+ struct ucsi *ucsi = m->private;
+
+ seq_printf(m, "%u mA\n", ucsi->connector->peak_current);
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(ucsi_peak_curr);
+
+static int ucsi_avg_curr_show(struct seq_file *m, void *v)
+{
+ struct ucsi *ucsi = m->private;
+
+ seq_printf(m, "%u mA\n", ucsi->connector->avg_current);
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(ucsi_avg_curr);
+
+static int ucsi_vbus_volt_show(struct seq_file *m, void *v)
+{
+ struct ucsi *ucsi = m->private;
+
+ seq_printf(m, "%u mV\n", ucsi->connector->vbus_voltage);
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(ucsi_vbus_volt);
+
void ucsi_debugfs_register(struct ucsi *ucsi)
{
ucsi->debugfs = kzalloc(sizeof(*ucsi->debugfs), GFP_KERNEL);
@@ -89,6 +117,9 @@ void ucsi_debugfs_register(struct ucsi *ucsi)
ucsi->debugfs->dentry = debugfs_create_dir(dev_name(ucsi->dev), ucsi_debugfs_root);
debugfs_create_file("command", 0200, ucsi->debugfs->dentry, ucsi, &ucsi_cmd_fops);
debugfs_create_file("response", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_resp_fops);
+ debugfs_create_file("peak_current", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_peak_curr_fops);
+ debugfs_create_file("avg_current", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_avg_curr_fops);
+ debugfs_create_file("vbus_voltage", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_vbus_volt_fops);
}
void ucsi_debugfs_unregister(struct ucsi *ucsi)
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index 5739ea2abdd1..3f568f790f39 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -1217,9 +1217,11 @@ static void ucsi_handle_connector_change(struct work_struct *work)
struct ucsi_connector *con = container_of(work, struct ucsi_connector,
work);
struct ucsi *ucsi = con->ucsi;
+ u8 curr_scale, volt_scale;
enum typec_role role;
u16 change;
int ret;
+ u32 val;
mutex_lock(&con->lock);
@@ -1291,6 +1293,21 @@ static void ucsi_handle_connector_change(struct work_struct *work)
if (change & UCSI_CONSTAT_BC_CHANGE)
ucsi_port_psy_changed(con);
+ if (con->ucsi->version >= UCSI_VERSION_2_1 &&
+ UCSI_CONSTAT(con, PWR_READING_READY_V2_1)) {
+ curr_scale = UCSI_CONSTAT(con, CURRENT_SCALE_V2_1);
+ volt_scale = UCSI_CONSTAT(con, VOLTAGE_SCALE_V2_1);
+
+ val = UCSI_CONSTAT(con, PEAK_CURRENT_V2_1);
+ con->peak_current = UCSI_CONSTAT_CURR_SCALE_MULT * curr_scale * val;
+
+ val = UCSI_CONSTAT(con, AVG_CURRENT_V2_1);
+ con->avg_current = UCSI_CONSTAT_CURR_SCALE_MULT * curr_scale * val;
+
+ val = UCSI_CONSTAT(con, VBUS_VOLTAGE_V2_1);
+ con->vbus_voltage = UCSI_CONSTAT_VOLT_SCALE_MULT * volt_scale * val;
+ }
+
out_unlock:
mutex_unlock(&con->lock);
}
diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
index ebd7c27c2cc7..e301d9012936 100644
--- a/drivers/usb/typec/ucsi/ucsi.h
+++ b/drivers/usb/typec/ucsi/ucsi.h
@@ -131,6 +131,7 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num);
#define UCSI_GET_PD_MESSAGE 0x15
#define UCSI_GET_CAM_CS 0x18
#define UCSI_SET_SINK_PATH 0x1c
+#define UCSI_READ_POWER_LEVEL 0x1e
#define UCSI_SET_USB 0x21
#define UCSI_GET_LPM_PPM_INFO 0x22
@@ -359,6 +360,14 @@ struct ucsi_cable_property {
#define UCSI_CONSTAT_BC_SLOW_CHARGING 2
#define UCSI_CONSTAT_BC_TRICKLE_CHARGING 3
#define UCSI_CONSTAT_PD_VERSION_V1_2 UCSI_DECLARE_BITFIELD_V1_2(70, 16)
+#define UCSI_CONSTAT_PWR_READING_READY_V2_1 UCSI_DECLARE_BITFIELD_V2_1(89, 1)
+#define UCSI_CONSTAT_CURRENT_SCALE_V2_1 UCSI_DECLARE_BITFIELD_V2_1(90, 3)
+#define UCSI_CONSTAT_PEAK_CURRENT_V2_1 UCSI_DECLARE_BITFIELD_V2_1(93, 16)
+#define UCSI_CONSTAT_AVG_CURRENT_V2_1 UCSI_DECLARE_BITFIELD_V2_1(109, 16)
+#define UCSI_CONSTAT_VOLTAGE_SCALE_V2_1 UCSI_DECLARE_BITFIELD_V2_1(125, 4)
+#define UCSI_CONSTAT_VBUS_VOLTAGE_V2_1 UCSI_DECLARE_BITFIELD_V2_1(129, 16)
+#define UCSI_CONSTAT_CURR_SCALE_MULT 5
+#define UCSI_CONSTAT_VOLT_SCALE_MULT 5
/* Connector Status Change Bits. */
#define UCSI_CONSTAT_EXT_SUPPLY_CHANGE BIT(1)
@@ -519,6 +528,10 @@ struct ucsi_connector {
u32 src_pdos[PDO_MAX_OBJECTS];
int num_pdos;
+ u32 peak_current;
+ u32 avg_current;
+ u32 vbus_voltage;
+
/* USB PD objects */
struct usb_power_delivery *pd;
struct usb_power_delivery_capabilities *port_source_caps;
diff --git a/drivers/usb/typec/ucsi/ucsi_stm32g0.c b/drivers/usb/typec/ucsi/ucsi_stm32g0.c
index 57ef7d83a412..838ac0185082 100644
--- a/drivers/usb/typec/ucsi/ucsi_stm32g0.c
+++ b/drivers/usb/typec/ucsi/ucsi_stm32g0.c
@@ -10,6 +10,7 @@
#include <linux/firmware.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/unaligned.h>
@@ -523,11 +524,7 @@ static void ucsi_stm32g0_fw_cb(const struct firmware *fw, void *context)
data = fw->data;
end = fw->data + fw->size;
while (data < end) {
- if ((end - data) < STM32G0_I2C_BL_SZ)
- size = end - data;
- else
- size = STM32G0_I2C_BL_SZ;
-
+ size = min(end - data, STM32G0_I2C_BL_SZ);
ret = ucsi_stm32g0_bl_write(g0->ucsi, addr, data, size);
if (ret) {
dev_err(g0->dev, "Write failed %d\n", ret);
diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c
index e70fba9f55d6..0d6c10a8490c 100644
--- a/drivers/usb/usbip/vhci_hcd.c
+++ b/drivers/usb/usbip/vhci_hcd.c
@@ -765,6 +765,17 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
ctrlreq->wValue, vdev->rhport);
vdev->udev = usb_get_dev(urb->dev);
+ /*
+ * NOTE: A similar operation has been done via
+ * USB_REQ_GET_DESCRIPTOR handler below, which is
+ * supposed to always precede USB_REQ_SET_ADDRESS.
+ *
+ * It's not entirely clear if operating on a different
+ * usb_device instance here is a real possibility,
+ * otherwise this call and vdev->udev assignment above
+ * should be dropped.
+ */
+ dev_pm_syscore_device(&vdev->udev->dev, true);
usb_put_dev(old);
spin_lock(&vdev->ud.lock);
@@ -785,6 +796,17 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
"Not yet?:Get_Descriptor to device 0 (get max pipe size)\n");
vdev->udev = usb_get_dev(urb->dev);
+ /*
+ * Set syscore PM flag for the virtually attached
+ * devices to ensure they will not enter suspend on
+ * the client side.
+ *
+ * Note this doesn't have any impact on the physical
+ * devices attached to the host system on the server
+ * side, hence there is no need to undo the operation
+ * on disconnect.
+ */
+ dev_pm_syscore_device(&vdev->udev->dev, true);
usb_put_dev(old);
goto out;
diff --git a/drivers/w1/masters/matrox_w1.c b/drivers/w1/masters/matrox_w1.c
index 2852cd2dc67c..146fa7c6e74e 100644
--- a/drivers/w1/masters/matrox_w1.c
+++ b/drivers/w1/masters/matrox_w1.c
@@ -47,7 +47,6 @@ struct matrox_device {
unsigned long phys_addr;
void __iomem *virt_addr;
- unsigned long found;
struct w1_bus_master *bus_master;
};
@@ -158,8 +157,6 @@ static int matrox_w1_probe(struct pci_dev *pdev, const struct pci_device_id *ent
pci_set_drvdata(pdev, dev);
- dev->found = 1;
-
dev_info(&pdev->dev, "Matrox G400 GPIO transport layer for 1-wire.\n");
return 0;
@@ -176,10 +173,9 @@ static void matrox_w1_remove(struct pci_dev *pdev)
{
struct matrox_device *dev = pci_get_drvdata(pdev);
- if (dev->found) {
- w1_remove_master_device(dev->bus_master);
- iounmap(dev->virt_addr);
- }
+ w1_remove_master_device(dev->bus_master);
+ iounmap(dev->virt_addr);
+
kfree(dev);
}