From 76cb841cb886eef6b3bee341a2266c76578724ad Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 6 May 2024 03:02:30 +0200 Subject: Adding upstream version 4.19.249. Signed-off-by: Daniel Baumann --- drivers/hwmon/Kconfig | 1952 ++++++++++++++++ drivers/hwmon/Makefile | 184 ++ drivers/hwmon/ab8500.c | 207 ++ drivers/hwmon/abituguru.c | 1650 +++++++++++++ drivers/hwmon/abituguru3.c | 1321 +++++++++++ drivers/hwmon/abx500.c | 491 ++++ drivers/hwmon/abx500.h | 69 + drivers/hwmon/acpi_power_meter.c | 1018 ++++++++ drivers/hwmon/ad7314.c | 136 ++ drivers/hwmon/ad7414.c | 241 ++ drivers/hwmon/ad7418.c | 275 +++ drivers/hwmon/adc128d818.c | 572 +++++ drivers/hwmon/adcxx.c | 247 ++ drivers/hwmon/adm1021.c | 506 ++++ drivers/hwmon/adm1025.c | 582 +++++ drivers/hwmon/adm1026.c | 1871 +++++++++++++++ drivers/hwmon/adm1029.c | 417 ++++ drivers/hwmon/adm1031.c | 1101 +++++++++ drivers/hwmon/adm9240.c | 773 +++++++ drivers/hwmon/ads1015.c | 337 +++ drivers/hwmon/ads7828.c | 230 ++ drivers/hwmon/ads7871.c | 215 ++ drivers/hwmon/adt7310.c | 117 + drivers/hwmon/adt7410.c | 80 + drivers/hwmon/adt7411.c | 743 ++++++ drivers/hwmon/adt7462.c | 1946 ++++++++++++++++ drivers/hwmon/adt7470.c | 1390 +++++++++++ drivers/hwmon/adt7475.c | 1945 ++++++++++++++++ drivers/hwmon/adt7x10.c | 510 ++++ drivers/hwmon/adt7x10.h | 38 + drivers/hwmon/amc6821.c | 1021 ++++++++ drivers/hwmon/applesmc.c | 1389 +++++++++++ drivers/hwmon/asb100.c | 1025 ++++++++ drivers/hwmon/asc7621.c | 1246 ++++++++++ drivers/hwmon/aspeed-pwm-tacho.c | 1006 ++++++++ drivers/hwmon/asus_atk0110.c | 1415 +++++++++++ drivers/hwmon/atxp1.c | 304 +++ drivers/hwmon/coretemp.c | 778 +++++++ drivers/hwmon/da9052-hwmon.c | 539 +++++ drivers/hwmon/da9055-hwmon.c | 297 +++ drivers/hwmon/dell-smm-hwmon.c | 1203 ++++++++++ drivers/hwmon/dme1737.c | 2801 ++++++++++++++++++++++ drivers/hwmon/ds1621.c | 409 ++++ drivers/hwmon/ds620.c | 272 +++ drivers/hwmon/emc1403.c | 501 ++++ drivers/hwmon/emc2103.c | 693 ++++++ drivers/hwmon/emc6w201.c | 529 +++++ drivers/hwmon/f71805f.c | 1662 +++++++++++++ drivers/hwmon/f71882fg.c | 2812 ++++++++++++++++++++++ drivers/hwmon/f75375s.c | 925 ++++++++ drivers/hwmon/fam15h_power.c | 516 +++++ drivers/hwmon/fschmd.c | 1389 +++++++++++ drivers/hwmon/ftsteutates.c | 866 +++++++ drivers/hwmon/g760a.c | 222 ++ drivers/hwmon/g762.c | 1103 +++++++++ drivers/hwmon/gl518sm.c | 683 ++++++ drivers/hwmon/gl520sm.c | 939 ++++++++ drivers/hwmon/gpio-fan.c | 619 +++++ drivers/hwmon/hih6130.c | 267 +++ drivers/hwmon/hwmon-vid.c | 317 +++ drivers/hwmon/hwmon.c | 922 ++++++++ drivers/hwmon/i5500_temp.c | 149 ++ drivers/hwmon/i5k_amb.c | 617 +++++ drivers/hwmon/ibmaem.c | 1117 +++++++++ drivers/hwmon/ibmpex.c | 615 +++++ drivers/hwmon/ibmpowernv.c | 737 ++++++ drivers/hwmon/iio_hwmon.c | 172 ++ drivers/hwmon/ina209.c | 633 +++++ drivers/hwmon/ina2xx.c | 556 +++++ drivers/hwmon/ina3221.c | 445 ++++ drivers/hwmon/it87.c | 3378 +++++++++++++++++++++++++++ drivers/hwmon/jc42.c | 616 +++++ drivers/hwmon/jz4740-hwmon.c | 146 ++ drivers/hwmon/k10temp.c | 373 +++ drivers/hwmon/k8temp.c | 334 +++ drivers/hwmon/lineage-pem.c | 547 +++++ drivers/hwmon/lm63.c | 1190 ++++++++++ drivers/hwmon/lm70.c | 220 ++ drivers/hwmon/lm73.c | 287 +++ drivers/hwmon/lm75.c | 671 ++++++ drivers/hwmon/lm75.h | 49 + drivers/hwmon/lm77.c | 373 +++ drivers/hwmon/lm78.c | 1047 +++++++++ drivers/hwmon/lm80.c | 681 ++++++ drivers/hwmon/lm83.c | 389 ++++ drivers/hwmon/lm85.c | 1697 ++++++++++++++ drivers/hwmon/lm87.c | 1011 ++++++++ drivers/hwmon/lm90.c | 1963 ++++++++++++++++ drivers/hwmon/lm92.c | 354 +++ drivers/hwmon/lm93.c | 2781 ++++++++++++++++++++++ drivers/hwmon/lm95234.c | 768 ++++++ drivers/hwmon/lm95241.c | 509 ++++ drivers/hwmon/lm95245.c | 648 ++++++ drivers/hwmon/ltc2945.c | 519 +++++ drivers/hwmon/ltc2990.c | 291 +++ drivers/hwmon/ltc4151.c | 234 ++ drivers/hwmon/ltc4215.c | 280 +++ drivers/hwmon/ltc4222.c | 237 ++ drivers/hwmon/ltc4245.c | 521 +++++ drivers/hwmon/ltc4260.c | 200 ++ drivers/hwmon/ltc4261.c | 266 +++ drivers/hwmon/ltq-cputemp.c | 163 ++ drivers/hwmon/max1111.c | 296 +++ drivers/hwmon/max16065.c | 673 ++++++ drivers/hwmon/max1619.c | 331 +++ drivers/hwmon/max1668.c | 460 ++++ drivers/hwmon/max197.c | 347 +++ drivers/hwmon/max31722.c | 156 ++ drivers/hwmon/max31790.c | 532 +++++ drivers/hwmon/max6621.c | 593 +++++ drivers/hwmon/max6639.c | 613 +++++ drivers/hwmon/max6642.c | 327 +++ drivers/hwmon/max6650.c | 758 ++++++ drivers/hwmon/max6697.c | 729 ++++++ drivers/hwmon/mc13783-adc.c | 340 +++ drivers/hwmon/mcp3021.c | 213 ++ drivers/hwmon/menf21bmc_hwmon.c | 230 ++ drivers/hwmon/mlxreg-fan.c | 495 ++++ drivers/hwmon/nct6683.c | 1502 ++++++++++++ drivers/hwmon/nct6775.c | 4638 +++++++++++++++++++++++++++++++++++++ drivers/hwmon/nct7802.c | 1153 +++++++++ drivers/hwmon/nct7904.c | 580 +++++ drivers/hwmon/npcm750-pwm-fan.c | 1057 +++++++++ drivers/hwmon/nsa320-hwmon.c | 215 ++ drivers/hwmon/ntc_thermistor.c | 668 ++++++ drivers/hwmon/pc87360.c | 1818 +++++++++++++++ drivers/hwmon/pc87427.c | 1364 +++++++++++ drivers/hwmon/pcf8591.c | 335 +++ drivers/hwmon/pmbus/Kconfig | 201 ++ drivers/hwmon/pmbus/Makefile | 23 + drivers/hwmon/pmbus/adm1275.c | 694 ++++++ drivers/hwmon/pmbus/ibm-cffps.c | 439 ++++ drivers/hwmon/pmbus/ir35221.c | 148 ++ drivers/hwmon/pmbus/lm25066.c | 530 +++++ drivers/hwmon/pmbus/ltc2978.c | 792 +++++++ drivers/hwmon/pmbus/ltc3815.c | 215 ++ drivers/hwmon/pmbus/max16064.c | 127 + drivers/hwmon/pmbus/max20751.c | 64 + drivers/hwmon/pmbus/max31785.c | 408 ++++ drivers/hwmon/pmbus/max34440.c | 520 +++++ drivers/hwmon/pmbus/max8688.c | 205 ++ drivers/hwmon/pmbus/pmbus.c | 239 ++ drivers/hwmon/pmbus/pmbus.h | 467 ++++ drivers/hwmon/pmbus/pmbus_core.c | 2450 ++++++++++++++++++++ drivers/hwmon/pmbus/tps40422.c | 64 + drivers/hwmon/pmbus/tps53679.c | 120 + drivers/hwmon/pmbus/ucd9000.c | 632 +++++ drivers/hwmon/pmbus/ucd9200.c | 226 ++ drivers/hwmon/pmbus/zl6100.c | 420 ++++ drivers/hwmon/powr1220.c | 391 ++++ drivers/hwmon/pwm-fan.c | 355 +++ drivers/hwmon/raspberrypi-hwmon.c | 161 ++ drivers/hwmon/s3c-hwmon.c | 391 ++++ drivers/hwmon/sch5627.c | 604 +++++ drivers/hwmon/sch5636.c | 534 +++++ drivers/hwmon/sch56xx-common.c | 589 +++++ drivers/hwmon/sch56xx-common.h | 32 + drivers/hwmon/scmi-hwmon.c | 228 ++ drivers/hwmon/scpi-hwmon.c | 318 +++ drivers/hwmon/sht15.c | 1076 +++++++++ drivers/hwmon/sht21.c | 313 +++ drivers/hwmon/sht3x.c | 782 +++++++ drivers/hwmon/shtc1.c | 251 ++ drivers/hwmon/sis5595.c | 932 ++++++++ drivers/hwmon/smm665.c | 709 ++++++ drivers/hwmon/smsc47b397.c | 383 +++ drivers/hwmon/smsc47m1.c | 956 ++++++++ drivers/hwmon/smsc47m192.c | 645 ++++++ drivers/hwmon/stts751.c | 845 +++++++ drivers/hwmon/tc654.c | 514 ++++ drivers/hwmon/tc74.c | 177 ++ drivers/hwmon/thmc50.c | 445 ++++ drivers/hwmon/tmp102.c | 341 +++ drivers/hwmon/tmp103.c | 197 ++ drivers/hwmon/tmp108.c | 469 ++++ drivers/hwmon/tmp401.c | 785 +++++++ drivers/hwmon/tmp421.c | 335 +++ drivers/hwmon/ultra45_env.c | 323 +++ drivers/hwmon/vexpress-hwmon.c | 259 +++ drivers/hwmon/via-cputemp.c | 343 +++ drivers/hwmon/via686a.c | 964 ++++++++ drivers/hwmon/vt1211.c | 1380 +++++++++++ drivers/hwmon/vt8231.c | 1073 +++++++++ drivers/hwmon/w83627ehf.c | 2893 +++++++++++++++++++++++ drivers/hwmon/w83627hf.c | 2043 ++++++++++++++++ drivers/hwmon/w83773g.c | 329 +++ drivers/hwmon/w83781d.c | 2098 +++++++++++++++++ drivers/hwmon/w83791d.c | 1695 ++++++++++++++ drivers/hwmon/w83792d.c | 1680 ++++++++++++++ drivers/hwmon/w83793.c | 2178 +++++++++++++++++ drivers/hwmon/w83795.c | 2285 ++++++++++++++++++ drivers/hwmon/w83l785ts.c | 300 +++ drivers/hwmon/w83l786ng.c | 778 +++++++ drivers/hwmon/wm831x-hwmon.c | 165 ++ drivers/hwmon/wm8350-hwmon.c | 104 + drivers/hwmon/xgene-hwmon.c | 811 +++++++ 196 files changed, 142484 insertions(+) create mode 100644 drivers/hwmon/Kconfig create mode 100644 drivers/hwmon/Makefile create mode 100644 drivers/hwmon/ab8500.c create mode 100644 drivers/hwmon/abituguru.c create mode 100644 drivers/hwmon/abituguru3.c create mode 100644 drivers/hwmon/abx500.c create mode 100644 drivers/hwmon/abx500.h create mode 100644 drivers/hwmon/acpi_power_meter.c create mode 100644 drivers/hwmon/ad7314.c create mode 100644 drivers/hwmon/ad7414.c create mode 100644 drivers/hwmon/ad7418.c create mode 100644 drivers/hwmon/adc128d818.c create mode 100644 drivers/hwmon/adcxx.c create mode 100644 drivers/hwmon/adm1021.c create mode 100644 drivers/hwmon/adm1025.c create mode 100644 drivers/hwmon/adm1026.c create mode 100644 drivers/hwmon/adm1029.c create mode 100644 drivers/hwmon/adm1031.c create mode 100644 drivers/hwmon/adm9240.c create mode 100644 drivers/hwmon/ads1015.c create mode 100644 drivers/hwmon/ads7828.c create mode 100644 drivers/hwmon/ads7871.c create mode 100644 drivers/hwmon/adt7310.c create mode 100644 drivers/hwmon/adt7410.c create mode 100644 drivers/hwmon/adt7411.c create mode 100644 drivers/hwmon/adt7462.c create mode 100644 drivers/hwmon/adt7470.c create mode 100644 drivers/hwmon/adt7475.c create mode 100644 drivers/hwmon/adt7x10.c create mode 100644 drivers/hwmon/adt7x10.h create mode 100644 drivers/hwmon/amc6821.c create mode 100644 drivers/hwmon/applesmc.c create mode 100644 drivers/hwmon/asb100.c create mode 100644 drivers/hwmon/asc7621.c create mode 100644 drivers/hwmon/aspeed-pwm-tacho.c create mode 100644 drivers/hwmon/asus_atk0110.c create mode 100644 drivers/hwmon/atxp1.c create mode 100644 drivers/hwmon/coretemp.c create mode 100644 drivers/hwmon/da9052-hwmon.c create mode 100644 drivers/hwmon/da9055-hwmon.c create mode 100644 drivers/hwmon/dell-smm-hwmon.c create mode 100644 drivers/hwmon/dme1737.c create mode 100644 drivers/hwmon/ds1621.c create mode 100644 drivers/hwmon/ds620.c create mode 100644 drivers/hwmon/emc1403.c create mode 100644 drivers/hwmon/emc2103.c create mode 100644 drivers/hwmon/emc6w201.c create mode 100644 drivers/hwmon/f71805f.c create mode 100644 drivers/hwmon/f71882fg.c create mode 100644 drivers/hwmon/f75375s.c create mode 100644 drivers/hwmon/fam15h_power.c create mode 100644 drivers/hwmon/fschmd.c create mode 100644 drivers/hwmon/ftsteutates.c create mode 100644 drivers/hwmon/g760a.c create mode 100644 drivers/hwmon/g762.c create mode 100644 drivers/hwmon/gl518sm.c create mode 100644 drivers/hwmon/gl520sm.c create mode 100644 drivers/hwmon/gpio-fan.c create mode 100644 drivers/hwmon/hih6130.c create mode 100644 drivers/hwmon/hwmon-vid.c create mode 100644 drivers/hwmon/hwmon.c create mode 100644 drivers/hwmon/i5500_temp.c create mode 100644 drivers/hwmon/i5k_amb.c create mode 100644 drivers/hwmon/ibmaem.c create mode 100644 drivers/hwmon/ibmpex.c create mode 100644 drivers/hwmon/ibmpowernv.c create mode 100644 drivers/hwmon/iio_hwmon.c create mode 100644 drivers/hwmon/ina209.c create mode 100644 drivers/hwmon/ina2xx.c create mode 100644 drivers/hwmon/ina3221.c create mode 100644 drivers/hwmon/it87.c create mode 100644 drivers/hwmon/jc42.c create mode 100644 drivers/hwmon/jz4740-hwmon.c create mode 100644 drivers/hwmon/k10temp.c create mode 100644 drivers/hwmon/k8temp.c create mode 100644 drivers/hwmon/lineage-pem.c create mode 100644 drivers/hwmon/lm63.c create mode 100644 drivers/hwmon/lm70.c create mode 100644 drivers/hwmon/lm73.c create mode 100644 drivers/hwmon/lm75.c create mode 100644 drivers/hwmon/lm75.h create mode 100644 drivers/hwmon/lm77.c create mode 100644 drivers/hwmon/lm78.c create mode 100644 drivers/hwmon/lm80.c create mode 100644 drivers/hwmon/lm83.c create mode 100644 drivers/hwmon/lm85.c create mode 100644 drivers/hwmon/lm87.c create mode 100644 drivers/hwmon/lm90.c create mode 100644 drivers/hwmon/lm92.c create mode 100644 drivers/hwmon/lm93.c create mode 100644 drivers/hwmon/lm95234.c create mode 100644 drivers/hwmon/lm95241.c create mode 100644 drivers/hwmon/lm95245.c create mode 100644 drivers/hwmon/ltc2945.c create mode 100644 drivers/hwmon/ltc2990.c create mode 100644 drivers/hwmon/ltc4151.c create mode 100644 drivers/hwmon/ltc4215.c create mode 100644 drivers/hwmon/ltc4222.c create mode 100644 drivers/hwmon/ltc4245.c create mode 100644 drivers/hwmon/ltc4260.c create mode 100644 drivers/hwmon/ltc4261.c create mode 100644 drivers/hwmon/ltq-cputemp.c create mode 100644 drivers/hwmon/max1111.c create mode 100644 drivers/hwmon/max16065.c create mode 100644 drivers/hwmon/max1619.c create mode 100644 drivers/hwmon/max1668.c create mode 100644 drivers/hwmon/max197.c create mode 100644 drivers/hwmon/max31722.c create mode 100644 drivers/hwmon/max31790.c create mode 100644 drivers/hwmon/max6621.c create mode 100644 drivers/hwmon/max6639.c create mode 100644 drivers/hwmon/max6642.c create mode 100644 drivers/hwmon/max6650.c create mode 100644 drivers/hwmon/max6697.c create mode 100644 drivers/hwmon/mc13783-adc.c create mode 100644 drivers/hwmon/mcp3021.c create mode 100644 drivers/hwmon/menf21bmc_hwmon.c create mode 100644 drivers/hwmon/mlxreg-fan.c create mode 100644 drivers/hwmon/nct6683.c create mode 100644 drivers/hwmon/nct6775.c create mode 100644 drivers/hwmon/nct7802.c create mode 100644 drivers/hwmon/nct7904.c create mode 100644 drivers/hwmon/npcm750-pwm-fan.c create mode 100644 drivers/hwmon/nsa320-hwmon.c create mode 100644 drivers/hwmon/ntc_thermistor.c create mode 100644 drivers/hwmon/pc87360.c create mode 100644 drivers/hwmon/pc87427.c create mode 100644 drivers/hwmon/pcf8591.c create mode 100644 drivers/hwmon/pmbus/Kconfig create mode 100644 drivers/hwmon/pmbus/Makefile create mode 100644 drivers/hwmon/pmbus/adm1275.c create mode 100644 drivers/hwmon/pmbus/ibm-cffps.c create mode 100644 drivers/hwmon/pmbus/ir35221.c create mode 100644 drivers/hwmon/pmbus/lm25066.c create mode 100644 drivers/hwmon/pmbus/ltc2978.c create mode 100644 drivers/hwmon/pmbus/ltc3815.c create mode 100644 drivers/hwmon/pmbus/max16064.c create mode 100644 drivers/hwmon/pmbus/max20751.c create mode 100644 drivers/hwmon/pmbus/max31785.c create mode 100644 drivers/hwmon/pmbus/max34440.c create mode 100644 drivers/hwmon/pmbus/max8688.c create mode 100644 drivers/hwmon/pmbus/pmbus.c create mode 100644 drivers/hwmon/pmbus/pmbus.h create mode 100644 drivers/hwmon/pmbus/pmbus_core.c create mode 100644 drivers/hwmon/pmbus/tps40422.c create mode 100644 drivers/hwmon/pmbus/tps53679.c create mode 100644 drivers/hwmon/pmbus/ucd9000.c create mode 100644 drivers/hwmon/pmbus/ucd9200.c create mode 100644 drivers/hwmon/pmbus/zl6100.c create mode 100644 drivers/hwmon/powr1220.c create mode 100644 drivers/hwmon/pwm-fan.c create mode 100644 drivers/hwmon/raspberrypi-hwmon.c create mode 100644 drivers/hwmon/s3c-hwmon.c create mode 100644 drivers/hwmon/sch5627.c create mode 100644 drivers/hwmon/sch5636.c create mode 100644 drivers/hwmon/sch56xx-common.c create mode 100644 drivers/hwmon/sch56xx-common.h create mode 100644 drivers/hwmon/scmi-hwmon.c create mode 100644 drivers/hwmon/scpi-hwmon.c create mode 100644 drivers/hwmon/sht15.c create mode 100644 drivers/hwmon/sht21.c create mode 100644 drivers/hwmon/sht3x.c create mode 100644 drivers/hwmon/shtc1.c create mode 100644 drivers/hwmon/sis5595.c create mode 100644 drivers/hwmon/smm665.c create mode 100644 drivers/hwmon/smsc47b397.c create mode 100644 drivers/hwmon/smsc47m1.c create mode 100644 drivers/hwmon/smsc47m192.c create mode 100644 drivers/hwmon/stts751.c create mode 100644 drivers/hwmon/tc654.c create mode 100644 drivers/hwmon/tc74.c create mode 100644 drivers/hwmon/thmc50.c create mode 100644 drivers/hwmon/tmp102.c create mode 100644 drivers/hwmon/tmp103.c create mode 100644 drivers/hwmon/tmp108.c create mode 100644 drivers/hwmon/tmp401.c create mode 100644 drivers/hwmon/tmp421.c create mode 100644 drivers/hwmon/ultra45_env.c create mode 100644 drivers/hwmon/vexpress-hwmon.c create mode 100644 drivers/hwmon/via-cputemp.c create mode 100644 drivers/hwmon/via686a.c create mode 100644 drivers/hwmon/vt1211.c create mode 100644 drivers/hwmon/vt8231.c create mode 100644 drivers/hwmon/w83627ehf.c create mode 100644 drivers/hwmon/w83627hf.c create mode 100644 drivers/hwmon/w83773g.c create mode 100644 drivers/hwmon/w83781d.c create mode 100644 drivers/hwmon/w83791d.c create mode 100644 drivers/hwmon/w83792d.c create mode 100644 drivers/hwmon/w83793.c create mode 100644 drivers/hwmon/w83795.c create mode 100644 drivers/hwmon/w83l785ts.c create mode 100644 drivers/hwmon/w83l786ng.c create mode 100644 drivers/hwmon/wm831x-hwmon.c create mode 100644 drivers/hwmon/wm8350-hwmon.c create mode 100644 drivers/hwmon/xgene-hwmon.c (limited to 'drivers/hwmon') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig new file mode 100644 index 000000000..d150d0cab --- /dev/null +++ b/drivers/hwmon/Kconfig @@ -0,0 +1,1952 @@ +# +# Hardware monitoring chip drivers configuration +# + +menuconfig HWMON + tristate "Hardware Monitoring support" + depends on HAS_IOMEM + default y + help + Hardware monitoring devices let you monitor the hardware health + of a system. Most modern motherboards include such a device. It + can include temperature sensors, voltage sensors, fan speed + sensors and various additional features such as the ability to + control the speed of the fans. If you want this support you + should say Y here and also to the specific driver(s) for your + sensors chip(s) below. + + To find out which specific driver(s) you need, use the + sensors-detect script from the lm_sensors package. Read + for details. + + This support can also be built as a module. If so, the module + will be called hwmon. + +if HWMON + +config HWMON_VID + tristate + +config HWMON_DEBUG_CHIP + bool "Hardware Monitoring Chip debugging messages" + help + Say Y here if you want the I2C chip drivers to produce a bunch of + debug messages to the system log. Select this if you are having + a problem with I2C support and want to see more of what is going + on. + +comment "Native drivers" + +config SENSORS_AB8500 + tristate "AB8500 thermal monitoring" + depends on AB8500_GPADC && AB8500_BM + help + If you say yes here you get support for the thermal sensor part + of the AB8500 chip. The driver includes thermal management for + AB8500 die and two GPADC channels. The GPADC channel are preferably + used to access sensors outside the AB8500 chip. + + This driver can also be built as a module. If so, the module + will be called abx500-temp. + +config SENSORS_ABITUGURU + tristate "Abit uGuru (rev 1 & 2)" + depends on X86 && DMI + help + If you say yes here you get support for the sensor part of the first + and second revision of the Abit uGuru chip. The voltage and frequency + control parts of the Abit uGuru are not supported. The Abit uGuru + chip can be found on Abit uGuru featuring motherboards (most modern + Abit motherboards from before end 2005). For more info and a list + of which motherboards have which revision see + Documentation/hwmon/abituguru + + This driver can also be built as a module. If so, the module + will be called abituguru. + +config SENSORS_ABITUGURU3 + tristate "Abit uGuru (rev 3)" + depends on X86 && DMI + help + If you say yes here you get support for the sensor part of the + third revision of the Abit uGuru chip. Only reading the sensors + and their settings is supported. The third revision of the Abit + uGuru chip can be found on recent Abit motherboards (since end + 2005). For more info and a list of which motherboards have which + revision see Documentation/hwmon/abituguru3 + + This driver can also be built as a module. If so, the module + will be called abituguru3. + +config SENSORS_AD7314 + tristate "Analog Devices AD7314 and compatibles" + depends on SPI + help + If you say yes here you get support for the Analog Devices + AD7314, ADT7301 and ADT7302 temperature sensors. + + This driver can also be built as a module. If so, the module + will be called ad7314. + +config SENSORS_AD7414 + tristate "Analog Devices AD7414" + depends on I2C + help + If you say yes here you get support for the Analog Devices + AD7414 temperature monitoring chip. + + This driver can also be built as a module. If so, the module + will be called ad7414. + +config SENSORS_AD7418 + tristate "Analog Devices AD7416, AD7417 and AD7418" + depends on I2C + help + If you say yes here you get support for the Analog Devices + AD7416, AD7417 and AD7418 temperature monitoring chips. + + This driver can also be built as a module. If so, the module + will be called ad7418. + +config SENSORS_ADM1021 + tristate "Analog Devices ADM1021 and compatibles" + depends on I2C + help + If you say yes here you get support for Analog Devices ADM1021 + and ADM1023 sensor chips and clones: Maxim MAX1617 and MAX1617A, + Genesys Logic GL523SM, National Semiconductor LM84 and TI THMC10. + + This driver can also be built as a module. If so, the module + will be called adm1021. + +config SENSORS_ADM1025 + tristate "Analog Devices ADM1025 and compatibles" + depends on I2C + select HWMON_VID + help + If you say yes here you get support for Analog Devices ADM1025 + and Philips NE1619 sensor chips. + + This driver can also be built as a module. If so, the module + will be called adm1025. + +config SENSORS_ADM1026 + tristate "Analog Devices ADM1026 and compatibles" + depends on I2C + select HWMON_VID + help + If you say yes here you get support for Analog Devices ADM1026 + sensor chip. + + This driver can also be built as a module. If so, the module + will be called adm1026. + +config SENSORS_ADM1029 + tristate "Analog Devices ADM1029" + depends on I2C + help + If you say yes here you get support for Analog Devices ADM1029 + sensor chip. + Very rare chip, please let us know you use it. + + This driver can also be built as a module. If so, the module + will be called adm1029. + +config SENSORS_ADM1031 + tristate "Analog Devices ADM1031 and compatibles" + depends on I2C + help + If you say yes here you get support for Analog Devices ADM1031 + and ADM1030 sensor chips. + + This driver can also be built as a module. If so, the module + will be called adm1031. + +config SENSORS_ADM9240 + tristate "Analog Devices ADM9240 and compatibles" + depends on I2C + select HWMON_VID + help + If you say yes here you get support for Analog Devices ADM9240, + Dallas DS1780, National Semiconductor LM81 sensor chips. + + This driver can also be built as a module. If so, the module + will be called adm9240. + +config SENSORS_ADT7X10 + tristate + help + This module contains common code shared by the ADT7310/ADT7320 and + ADT7410/ADT7420 temperature monitoring chip drivers. + + If build as a module, the module will be called adt7x10. + +config SENSORS_ADT7310 + tristate "Analog Devices ADT7310/ADT7320" + depends on SPI_MASTER + select SENSORS_ADT7X10 + help + If you say yes here you get support for the Analog Devices + ADT7310 and ADT7320 temperature monitoring chips. + + This driver can also be built as a module. If so, the module + will be called adt7310. + +config SENSORS_ADT7410 + tristate "Analog Devices ADT7410/ADT7420" + depends on I2C + select SENSORS_ADT7X10 + help + If you say yes here you get support for the Analog Devices + ADT7410 and ADT7420 temperature monitoring chips. + + This driver can also be built as a module. If so, the module + will be called adt7410. + +config SENSORS_ADT7411 + tristate "Analog Devices ADT7411" + depends on I2C + help + If you say yes here you get support for the Analog Devices + ADT7411 voltage and temperature monitoring chip. + + This driver can also be built as a module. If so, the module + will be called adt7411. + +config SENSORS_ADT7462 + tristate "Analog Devices ADT7462" + depends on I2C + help + If you say yes here you get support for the Analog Devices + ADT7462 temperature monitoring chips. + + This driver can also be built as a module. If so, the module + will be called adt7462. + +config SENSORS_ADT7470 + tristate "Analog Devices ADT7470" + depends on I2C + help + If you say yes here you get support for the Analog Devices + ADT7470 temperature monitoring chips. + + This driver can also be built as a module. If so, the module + will be called adt7470. + +config SENSORS_ADT7475 + tristate "Analog Devices ADT7473, ADT7475, ADT7476 and ADT7490" + depends on I2C + select HWMON_VID + help + If you say yes here you get support for the Analog Devices + ADT7473, ADT7475, ADT7476 and ADT7490 hardware monitoring + chips. + + This driver can also be build as a module. If so, the module + will be called adt7475. + +config SENSORS_ASC7621 + tristate "Andigilog aSC7621" + depends on I2C + help + If you say yes here you get support for the aSC7621 + family of SMBus sensors chip found on most Intel X38, X48, X58, + 945, 965 and 975 desktop boards. Currently supported chips: + aSC7621 + aSC7621a + + This driver can also be built as a module. If so, the module + will be called asc7621. + +config SENSORS_K8TEMP + tristate "AMD Athlon64/FX or Opteron temperature sensor" + depends on X86 && PCI + help + If you say yes here you get support for the temperature + sensor(s) inside your CPU. Supported is whole AMD K8 + microarchitecture. Please note that you will need at least + lm-sensors 2.10.1 for proper userspace support. + + This driver can also be built as a module. If so, the module + will be called k8temp. + +config SENSORS_K10TEMP + tristate "AMD Family 10h+ temperature sensor" + depends on X86 && PCI && AMD_NB + help + If you say yes here you get support for the temperature + sensor(s) inside your CPU. Supported are later revisions of + the AMD Family 10h and all revisions of the AMD Family 11h, + 12h (Llano), 14h (Brazos), 15h (Bulldozer/Trinity/Kaveri/Carrizo) + and 16h (Kabini/Mullins) microarchitectures. + + This driver can also be built as a module. If so, the module + will be called k10temp. + +config SENSORS_FAM15H_POWER + tristate "AMD Family 15h processor power" + depends on X86 && PCI && CPU_SUP_AMD + help + If you say yes here you get support for processor power + information of your AMD family 15h CPU. + + This driver can also be built as a module. If so, the module + will be called fam15h_power. + +config SENSORS_APPLESMC + tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)" + depends on INPUT && X86 + select NEW_LEDS + select LEDS_CLASS + select INPUT_POLLDEV + help + This driver provides support for the Apple System Management + Controller, which provides an accelerometer (Apple Sudden Motion + Sensor), light sensors, temperature sensors, keyboard backlight + control and fan control. + + Only Intel-based Apple's computers are supported (MacBook Pro, + MacBook, MacMini). + + Data from the different sensors, keyboard backlight control and fan + control are accessible via sysfs. + + This driver also provides an absolute input class device, allowing + the laptop to act as a pinball machine-esque joystick. + + Say Y here if you have an applicable laptop and want to experience + the awesome power of applesmc. + +config SENSORS_ARM_SCMI + tristate "ARM SCMI Sensors" + depends on ARM_SCMI_PROTOCOL + depends on THERMAL || !THERMAL_OF + help + This driver provides support for temperature, voltage, current + and power sensors available on SCMI based platforms. The actual + number and type of sensors exported depend on the platform. + + This driver can also be built as a module. If so, the module + will be called scmi-hwmon. + +config SENSORS_ARM_SCPI + tristate "ARM SCPI Sensors" + depends on ARM_SCPI_PROTOCOL + depends on THERMAL || !THERMAL_OF + help + This driver provides support for temperature, voltage, current + and power sensors available on ARM Ltd's SCP based platforms. The + actual number and type of sensors exported depend on the platform. + +config SENSORS_ASB100 + tristate "Asus ASB100 Bach" + depends on X86 && I2C + select HWMON_VID + help + If you say yes here you get support for the ASB100 Bach sensor + chip found on some Asus mainboards. + + This driver can also be built as a module. If so, the module + will be called asb100. + +config SENSORS_ASPEED + tristate "ASPEED AST2400/AST2500 PWM and Fan tach driver" + depends on THERMAL || THERMAL=n + select REGMAP + help + This driver provides support for ASPEED AST2400/AST2500 PWM + and Fan Tacho controllers. + + This driver can also be built as a module. If so, the module + will be called aspeed_pwm_tacho. + +config SENSORS_ATXP1 + tristate "Attansic ATXP1 VID controller" + depends on I2C + select HWMON_VID + help + If you say yes here you get support for the Attansic ATXP1 VID + controller. + + If your board have such a chip, you are able to control your CPU + core and other voltages. + + This driver can also be built as a module. If so, the module + will be called atxp1. + +config SENSORS_DS620 + tristate "Dallas Semiconductor DS620" + depends on I2C + help + If you say yes here you get support for Dallas Semiconductor + DS620 sensor chip. + + This driver can also be built as a module. If so, the module + will be called ds620. + +config SENSORS_DS1621 + tristate "Dallas Semiconductor DS1621 and compatibles" + depends on I2C + help + If you say yes here you get support for Dallas Semiconductor/Maxim + Integrated DS1621 sensor chips and compatible models including: + + - Dallas Semiconductor DS1625 + - Maxim Integrated DS1631 + - Maxim Integrated DS1721 + - Maxim Integrated DS1731 + + This driver can also be built as a module. If so, the module + will be called ds1621. + +config SENSORS_DELL_SMM + tristate "Dell laptop SMM BIOS hwmon driver" + depends on X86 + help + This hwmon driver adds support for reporting temperature of different + sensors and controls the fans on Dell laptops via System Management + Mode provided by Dell BIOS. + + When option I8K is also enabled this driver provides legacy /proc/i8k + userspace interface for i8kutils package. + +config SENSORS_DA9052_ADC + tristate "Dialog DA9052/DA9053 ADC" + depends on PMIC_DA9052 + help + Say y here to support the ADC found on Dialog Semiconductor + DA9052-BC and DA9053-AA/Bx PMICs. + + This driver can also be built as module. If so, the module + will be called da9052-hwmon. + +config SENSORS_DA9055 + tristate "Dialog Semiconductor DA9055 ADC" + depends on MFD_DA9055 + help + If you say yes here you get support for ADC on the Dialog + Semiconductor DA9055 PMIC. + + This driver can also be built as a module. If so, the module + will be called da9055-hwmon. + +config SENSORS_I5K_AMB + tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets" + depends on PCI + help + If you say yes here you get support for FB-DIMM AMB temperature + monitoring chips on systems with the Intel 5000 series chipset. + + This driver can also be built as a module. If so, the module + will be called i5k_amb. + +config SENSORS_F71805F + tristate "Fintek F71805F/FG, F71806F/FG and F71872F/FG" + depends on !PPC + help + If you say yes here you get support for hardware monitoring + features of the Fintek F71805F/FG, F71806F/FG and F71872F/FG + Super-I/O chips. + + This driver can also be built as a module. If so, the module + will be called f71805f. + +config SENSORS_F71882FG + tristate "Fintek F71882FG and compatibles" + depends on !PPC + help + If you say yes here you get support for hardware monitoring + features of many Fintek Super-I/O (LPC) chips. The currently + supported chips are: + F71808E/A + F71858FG + F71862FG + F71863FG + F71869F/E/A + F71882FG + F71883FG + F71889FG/ED/A + F8000 + F81801U + F81865F + + This driver can also be built as a module. If so, the module + will be called f71882fg. + +config SENSORS_F75375S + tristate "Fintek F75375S/SP, F75373 and F75387" + depends on I2C + help + If you say yes here you get support for hardware monitoring + features of the Fintek F75375S/SP, F75373 and F75387 + + This driver can also be built as a module. If so, the module + will be called f75375s. + +config SENSORS_MC13783_ADC + tristate "Freescale MC13783/MC13892 ADC" + depends on MFD_MC13XXX + help + Support for the A/D converter on MC13783 and MC13892 PMIC. + +config SENSORS_FSCHMD + tristate "Fujitsu Siemens Computers sensor chips" + depends on X86 && I2C + help + If you say yes here you get support for the following Fujitsu + Siemens Computers (FSC) sensor chips: Poseidon, Scylla, Hermes, + Heimdall, Heracles, Hades and Syleus including support for the + integrated watchdog. + + This is a merged driver for FSC sensor chips replacing the fscpos, + fscscy and fscher drivers and adding support for several other FSC + sensor chips. + + This driver can also be built as a module. If so, the module + will be called fschmd. + +config SENSORS_FTSTEUTATES + tristate "Fujitsu Technology Solutions sensor chip Teutates" + depends on I2C && WATCHDOG + select WATCHDOG_CORE + help + If you say yes here you get support for the Fujitsu Technology + Solutions (FTS) sensor chip "Teutates" including support for + the integrated watchdog. + + This driver can also be built as a module. If so, the module + will be called ftsteutates. + +config SENSORS_GL518SM + tristate "Genesys Logic GL518SM" + depends on I2C + help + If you say yes here you get support for Genesys Logic GL518SM + sensor chips. + + This driver can also be built as a module. If so, the module + will be called gl518sm. + +config SENSORS_GL520SM + tristate "Genesys Logic GL520SM" + depends on I2C + select HWMON_VID + help + If you say yes here you get support for Genesys Logic GL520SM + sensor chips. + + This driver can also be built as a module. If so, the module + will be called gl520sm. + +config SENSORS_G760A + tristate "GMT G760A" + depends on I2C + help + If you say yes here you get support for Global Mixed-mode + Technology Inc G760A fan speed PWM controller chips. + + This driver can also be built as a module. If so, the module + will be called g760a. + +config SENSORS_G762 + tristate "GMT G762 and G763" + depends on I2C + help + If you say yes here you get support for Global Mixed-mode + Technology Inc G762 and G763 fan speed PWM controller chips. + + This driver can also be built as a module. If so, the module + will be called g762. + +config SENSORS_GPIO_FAN + tristate "GPIO fan" + depends on OF_GPIO + depends on GPIOLIB || COMPILE_TEST + depends on THERMAL || THERMAL=n + help + If you say yes here you get support for fans connected to GPIO lines. + + This driver can also be built as a module. If so, the module + will be called gpio-fan. + +config SENSORS_HIH6130 + tristate "Honeywell Humidicon HIH-6130 humidity/temperature sensor" + depends on I2C + help + If you say yes here you get support for Honeywell Humidicon + HIH-6130 and HIH-6131 Humidicon humidity sensors. + + This driver can also be built as a module. If so, the module + will be called hih6130. + +config SENSORS_IBMAEM + tristate "IBM Active Energy Manager temperature/power sensors and control" + select IPMI_SI + depends on IPMI_HANDLER + help + If you say yes here you get support for the temperature and + power sensors and capping hardware in various IBM System X + servers that support Active Energy Manager. This includes + the x3350, x3550, x3650, x3655, x3755, x3850 M2, x3950 M2, + and certain HC10/HS2x/LS2x/QS2x blades. + + This driver can also be built as a module. If so, the module + will be called ibmaem. + +config SENSORS_IBMPEX + tristate "IBM PowerExecutive temperature/power sensors" + select IPMI_SI + depends on IPMI_HANDLER + help + If you say yes here you get support for the temperature and + power sensors in various IBM System X servers that support + PowerExecutive. So far this includes the x3350, x3550, x3650, + x3655, and x3755; the x3800, x3850, and x3950 models that have + PCI Express; and some of the HS2x, LS2x, and QS2x blades. + + This driver can also be built as a module. If so, the module + will be called ibmpex. + +config SENSORS_IBMPOWERNV + tristate "IBM POWERNV platform sensors" + depends on PPC_POWERNV + default y + help + If you say yes here you get support for the temperature/fan/power + sensors on your PowerNV platform. + + This driver can also be built as a module. If so, the module + will be called ibmpowernv. + +config SENSORS_IIO_HWMON + tristate "Hwmon driver that uses channels specified via iio maps" + depends on IIO + help + This is a platform driver that in combination with a suitable + map allows IIO devices to provide basic hwmon functionality + for those channels specified in the map. This map can be provided + either via platform data or the device tree bindings. + +config SENSORS_I5500 + tristate "Intel 5500/5520/X58 temperature sensor" + depends on X86 && PCI + help + If you say yes here you get support for the temperature + sensor inside the Intel 5500, 5520 and X58 chipsets. + + This driver can also be built as a module. If so, the module + will be called i5500_temp. + +config SENSORS_CORETEMP + tristate "Intel Core/Core2/Atom temperature sensor" + depends on X86 + help + If you say yes here you get support for the temperature + sensor inside your CPU. Most of the family 6 CPUs + are supported. Check Documentation/hwmon/coretemp for details. + +config SENSORS_IT87 + tristate "ITE IT87xx and compatibles" + depends on !PPC + select HWMON_VID + help + If you say yes here you get support for ITE IT8705F, IT8712F, IT8716F, + IT8718F, IT8720F, IT8721F, IT8726F, IT8728F, IT8732F, IT8758E, + IT8771E, IT8772E, IT8781F, IT8782F, IT8783E/F, IT8786E, IT8790E, + IT8603E, IT8620E, IT8623E, and IT8628E sensor chips, and the SiS950 + clone. + + This driver can also be built as a module. If so, the module + will be called it87. + +config SENSORS_JZ4740 + tristate "Ingenic JZ4740 SoC ADC driver" + depends on MACH_JZ4740 && MFD_JZ4740_ADC + help + If you say yes here you get support for reading adc values from the ADCIN + pin on Ingenic JZ4740 SoC based boards. + + This driver can also be build as a module. If so, the module will be + called jz4740-hwmon. + +config SENSORS_JC42 + tristate "JEDEC JC42.4 compliant memory module temperature sensors" + depends on I2C + help + If you say yes here, you get support for JEDEC JC42.4 compliant + temperature sensors, which are used on many DDR3 memory modules for + mobile devices and servers. Support will include, but not be limited + to, ADT7408, AT30TS00, CAT34TS02, CAT6095, MAX6604, MCP9804, MCP9805, + MCP9808, MCP98242, MCP98243, MCP98244, MCP9843, SE97, SE98, + STTS424(E), STTS2002, STTS3000, TSE2002, TSE2004, TS3000, and TS3001. + + This driver can also be built as a module. If so, the module + will be called jc42. + +config SENSORS_POWR1220 + tristate "Lattice POWR1220 Power Monitoring" + depends on I2C + help + If you say yes here you get access to the hardware monitoring + functions of the Lattice POWR1220 isp Power Supply Monitoring, + Sequencing and Margining Controller. + + This driver can also be built as a module. If so, the module + will be called powr1220. + +config SENSORS_LINEAGE + tristate "Lineage Compact Power Line Power Entry Module" + depends on I2C + help + If you say yes here you get support for the Lineage Compact Power Line + series of DC/DC and AC/DC converters such as CP1800, CP2000AC, + CP2000DC, CP2725, and others. + + This driver can also be built as a module. If so, the module + will be called lineage-pem. + +config SENSORS_LTC2945 + tristate "Linear Technology LTC2945" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for Linear Technology LTC2945 + I2C System Monitor. + + This driver can also be built as a module. If so, the module will + be called ltc2945. + +config SENSORS_LTC2990 + tristate "Linear Technology LTC2990" + depends on I2C + help + If you say yes here you get support for Linear Technology LTC2990 + I2C System Monitor. The LTC2990 supports a combination of voltage, + current and temperature monitoring. + + This driver can also be built as a module. If so, the module will + be called ltc2990. + +config SENSORS_LTC4151 + tristate "Linear Technology LTC4151" + depends on I2C + help + If you say yes here you get support for Linear Technology LTC4151 + High Voltage I2C Current and Voltage Monitor interface. + + This driver can also be built as a module. If so, the module will + be called ltc4151. + +config SENSORS_LTC4215 + tristate "Linear Technology LTC4215" + depends on I2C + help + If you say yes here you get support for Linear Technology LTC4215 + Hot Swap Controller I2C interface. + + This driver can also be built as a module. If so, the module will + be called ltc4215. + +config SENSORS_LTC4222 + tristate "Linear Technology LTC4222" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for Linear Technology LTC4222 + Dual Hot Swap Controller I2C interface. + + This driver can also be built as a module. If so, the module will + be called ltc4222. + +config SENSORS_LTC4245 + tristate "Linear Technology LTC4245" + depends on I2C + help + If you say yes here you get support for Linear Technology LTC4245 + Multiple Supply Hot Swap Controller I2C interface. + + This driver can also be built as a module. If so, the module will + be called ltc4245. + +config SENSORS_LTC4260 + tristate "Linear Technology LTC4260" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for Linear Technology LTC4260 + Positive Voltage Hot Swap Controller I2C interface. + + This driver can also be built as a module. If so, the module will + be called ltc4260. + +config SENSORS_LTC4261 + tristate "Linear Technology LTC4261" + depends on I2C + help + If you say yes here you get support for Linear Technology LTC4261 + Negative Voltage Hot Swap Controller I2C interface. + + This driver can also be built as a module. If so, the module will + be called ltc4261. + +config SENSORS_LTQ_CPUTEMP + bool "Lantiq cpu temperature sensor driver" + depends on SOC_XWAY + help + If you say yes here you get support for the temperature + sensor inside your CPU. + +config SENSORS_MAX1111 + tristate "Maxim MAX1111 Serial 8-bit ADC chip and compatibles" + depends on SPI_MASTER + help + Say y here to support Maxim's MAX1110, MAX1111, MAX1112, and MAX1113 + ADC chips. + + This driver can also be built as a module. If so, the module + will be called max1111. + +config SENSORS_MAX16065 + tristate "Maxim MAX16065 System Manager and compatibles" + depends on I2C + help + If you say yes here you get support for hardware monitoring + capabilities of the following Maxim System Manager chips. + MAX16065 + MAX16066 + MAX16067 + MAX16068 + MAX16070 + MAX16071 + + This driver can also be built as a module. If so, the module + will be called max16065. + +config SENSORS_MAX1619 + tristate "Maxim MAX1619 sensor chip" + depends on I2C + help + If you say yes here you get support for MAX1619 sensor chip. + + This driver can also be built as a module. If so, the module + will be called max1619. + +config SENSORS_MAX1668 + tristate "Maxim MAX1668 and compatibles" + depends on I2C + help + If you say yes here you get support for MAX1668, MAX1989 and + MAX1805 chips. + + This driver can also be built as a module. If so, the module + will be called max1668. + +config SENSORS_MAX197 + tristate "Maxim MAX197 and compatibles" + help + Support for the Maxim MAX197 A/D converter. + Support will include, but not be limited to, MAX197, and MAX199. + + This driver can also be built as a module. If so, the module + will be called max197. + +config SENSORS_MAX31722 +tristate "MAX31722 temperature sensor" + depends on SPI + help + Support for the Maxim Integrated MAX31722/MAX31723 digital + thermometers/thermostats operating over an SPI interface. + + This driver can also be built as a module. If so, the module + will be called max31722. + +config SENSORS_MAX6621 + tristate "Maxim MAX6621 sensor chip" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for MAX6621 sensor chip. + MAX6621 is a PECI-to-I2C translator provides an efficient, + low-cost solution for PECI-to-SMBus/I2C protocol conversion. + It allows reading the temperature from the PECI-compliant + host directly from up to four PECI-enabled CPUs. + + This driver can also be built as a module. If so, the module + will be called max6621. + +config SENSORS_MAX6639 + tristate "Maxim MAX6639 sensor chip" + depends on I2C + help + If you say yes here you get support for the MAX6639 + sensor chips. + + This driver can also be built as a module. If so, the module + will be called max6639. + +config SENSORS_MAX6642 + tristate "Maxim MAX6642 sensor chip" + depends on I2C + help + If you say yes here you get support for MAX6642 sensor chip. + MAX6642 is a SMBus-Compatible Remote/Local Temperature Sensor + with Overtemperature Alarm from Maxim. + + This driver can also be built as a module. If so, the module + will be called max6642. + +config SENSORS_MAX6650 + tristate "Maxim MAX6650 sensor chip" + depends on I2C + help + If you say yes here you get support for the MAX6650 / MAX6651 + sensor chips. + + This driver can also be built as a module. If so, the module + will be called max6650. + +config SENSORS_MAX6697 + tristate "Maxim MAX6697 and compatibles" + depends on I2C + help + If you say yes here you get support for MAX6581, MAX6602, MAX6622, + MAX6636, MAX6689, MAX6693, MAX6694, MAX6697, MAX6698, and MAX6699 + temperature sensor chips. + + This driver can also be built as a module. If so, the module + will be called max6697. + +config SENSORS_MAX31790 + tristate "Maxim MAX31790 sensor chip" + depends on I2C + help + If you say yes here you get support for 6-Channel PWM-Output + Fan RPM Controller. + + This driver can also be built as a module. If so, the module + will be called max31790. + +config SENSORS_MCP3021 + tristate "Microchip MCP3021 and compatibles" + depends on I2C + help + If you say yes here you get support for MCP3021 and MCP3221. + The MCP3021 is a A/D converter (ADC) with 10-bit and the MCP3221 + with 12-bit resolution. + + This driver can also be built as a module. If so, the module + will be called mcp3021. + +config SENSORS_MLXREG_FAN + tristate "Mellanox Mellanox FAN driver" + depends on MELLANOX_PLATFORM + imply THERMAL + select REGMAP + help + This option enables support for the FAN control on the Mellanox + Ethernet and InfiniBand switches. The driver can be activated by the + platform device add call. Say Y to enable these. To compile this + driver as a module, choose 'M' here: the module will be called + mlxreg-fan. + +config SENSORS_TC654 + tristate "Microchip TC654/TC655 and compatibles" + depends on I2C + help + If you say yes here you get support for TC654 and TC655. + The TC654 and TC655 are PWM mode fan speed controllers with + FanSense technology for use with brushless DC fans. + + This driver can also be built as a module. If so, the module + will be called tc654. + +config SENSORS_MENF21BMC_HWMON + tristate "MEN 14F021P00 BMC Hardware Monitoring" + depends on MFD_MENF21BMC + help + Say Y here to include support for the MEN 14F021P00 BMC + hardware monitoring. + + This driver can also be built as a module. If so the module + will be called menf21bmc_hwmon. + +config SENSORS_ADCXX + tristate "National Semiconductor ADCxxxSxxx" + depends on SPI_MASTER + help + If you say yes here you get support for the National Semiconductor + ADCS chip family, where + * bb is the resolution in number of bits (8, 10, 12) + * c is the number of channels (1, 2, 4, 8) + * sss is the maximum conversion speed (021 for 200 kSPS, 051 for 500 + kSPS and 101 for 1 MSPS) + + Examples : ADC081S101, ADC124S501, ... + + This driver can also be built as a module. If so, the module + will be called adcxx. + +config SENSORS_LM63 + tristate "National Semiconductor LM63 and compatibles" + depends on I2C + help + If you say yes here you get support for the National + Semiconductor LM63, LM64, and LM96163 remote diode digital temperature + sensors with integrated fan control. Such chips are found + on the Tyan S4882 (Thunder K8QS Pro) motherboard, among + others. + + This driver can also be built as a module. If so, the module + will be called lm63. + +config SENSORS_LM70 + tristate "National Semiconductor LM70 and compatibles" + depends on SPI_MASTER + help + If you say yes here you get support for the National Semiconductor + LM70, LM71, LM74 and Texas Instruments TMP121/TMP123 digital tempera- + ture sensor chips. + + This driver can also be built as a module. If so, the module + will be called lm70. + +config SENSORS_LM73 + tristate "National Semiconductor LM73" + depends on I2C + help + If you say yes here you get support for National Semiconductor LM73 + sensor chips. + This driver can also be built as a module. If so, the module + will be called lm73. + +config SENSORS_LM75 + tristate "National Semiconductor LM75 and compatibles" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for one common type of + temperature sensor chip, with models including: + + - Analog Devices ADT75 + - Dallas Semiconductor DS75, DS1775 and DS7505 + - Global Mixed-mode Technology (GMT) G751 + - Maxim MAX6625 and MAX6626 + - Microchip MCP980x + - National Semiconductor LM75, LM75A + - NXP's LM75A + - ST Microelectronics STDS75 + - TelCom (now Microchip) TCN75 + - Texas Instruments TMP100, TMP101, TMP105, TMP112, TMP75, + TMP175, TMP275 + + This driver supports driver model based binding through board + specific I2C device tables. + + It also supports the "legacy" style of driver binding. To use + that with some chips which don't replicate LM75 quirks exactly, + you may need the "force" module parameter. + + This driver can also be built as a module. If so, the module + will be called lm75. + +config SENSORS_LM77 + tristate "National Semiconductor LM77" + depends on I2C + help + If you say yes here you get support for National Semiconductor LM77 + sensor chips. + + This driver can also be built as a module. If so, the module + will be called lm77. + +config SENSORS_LM78 + tristate "National Semiconductor LM78 and compatibles" + depends on I2C + select HWMON_VID + help + If you say yes here you get support for National Semiconductor LM78, + LM78-J and LM79. + + This driver can also be built as a module. If so, the module + will be called lm78. + +config SENSORS_LM80 + tristate "National Semiconductor LM80 and LM96080" + depends on I2C + help + If you say yes here you get support for National Semiconductor + LM80 and LM96080 sensor chips. + + This driver can also be built as a module. If so, the module + will be called lm80. + +config SENSORS_LM83 + tristate "National Semiconductor LM83 and compatibles" + depends on I2C + help + If you say yes here you get support for National Semiconductor + LM82 and LM83 sensor chips. + + This driver can also be built as a module. If so, the module + will be called lm83. + +config SENSORS_LM85 + tristate "National Semiconductor LM85 and compatibles" + depends on I2C + select HWMON_VID + help + If you say yes here you get support for National Semiconductor LM85 + sensor chips and clones: ADM1027, ADT7463, ADT7468, EMC6D100, + EMC6D101, EMC6D102, and EMC6D103. + + This driver can also be built as a module. If so, the module + will be called lm85. + +config SENSORS_LM87 + tristate "National Semiconductor LM87 and compatibles" + depends on I2C + select HWMON_VID + help + If you say yes here you get support for National Semiconductor LM87 + and Analog Devices ADM1024 sensor chips. + + This driver can also be built as a module. If so, the module + will be called lm87. + +config SENSORS_LM90 + tristate "National Semiconductor LM90 and compatibles" + depends on I2C + help + If you say yes here you get support for National Semiconductor LM90, + LM86, LM89 and LM99, Analog Devices ADM1032, ADT7461, and ADT7461A, + Maxim MAX6646, MAX6647, MAX6648, MAX6649, MAX6657, MAX6658, MAX6659, + MAX6680, MAX6681, MAX6692, MAX6695, MAX6696, ON Semiconductor NCT1008, + Winbond/Nuvoton W83L771W/G/AWG/ASG, Philips SA56004, GMT G781, and + Texas Instruments TMP451 sensor chips. + + This driver can also be built as a module. If so, the module + will be called lm90. + +config SENSORS_LM92 + tristate "National Semiconductor LM92 and compatibles" + depends on I2C + help + If you say yes here you get support for National Semiconductor LM92 + and Maxim MAX6635 sensor chips. + + This driver can also be built as a module. If so, the module + will be called lm92. + +config SENSORS_LM93 + tristate "National Semiconductor LM93 and compatibles" + depends on I2C + select HWMON_VID + help + If you say yes here you get support for National Semiconductor LM93, + LM94, and compatible sensor chips. + + This driver can also be built as a module. If so, the module + will be called lm93. + +config SENSORS_LM95234 + tristate "National Semiconductor LM95234 and compatibles" + depends on I2C + help + If you say yes here you get support for the LM95233 and LM95234 + temperature sensor chips. + + This driver can also be built as a module. If so, the module + will be called lm95234. + +config SENSORS_LM95241 + tristate "National Semiconductor LM95241 and compatibles" + depends on I2C + help + If you say yes here you get support for LM95231 and LM95241 sensor + chips. + + This driver can also be built as a module. If so, the module + will be called lm95241. + +config SENSORS_LM95245 + tristate "National Semiconductor LM95245 and compatibles" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for LM95235 and LM95245 + temperature sensor chips. + + This driver can also be built as a module. If so, the module + will be called lm95245. + +config SENSORS_PC87360 + tristate "National Semiconductor PC87360 family" + depends on !PPC + select HWMON_VID + help + If you say yes here you get access to the hardware monitoring + functions of the National Semiconductor PC8736x Super-I/O chips. + The PC87360, PC87363 and PC87364 only have fan monitoring and + control. The PC87365 and PC87366 additionally have voltage and + temperature monitoring. + + This driver can also be built as a module. If so, the module + will be called pc87360. + +config SENSORS_PC87427 + tristate "National Semiconductor PC87427" + depends on !PPC + help + If you say yes here you get access to the hardware monitoring + functions of the National Semiconductor PC87427 Super-I/O chip. + The chip has two distinct logical devices, one for fan speed + monitoring and control, and one for voltage and temperature + monitoring. Fan speed monitoring and control are supported, as + well as temperature monitoring. Voltages aren't supported yet. + + This driver can also be built as a module. If so, the module + will be called pc87427. + +config SENSORS_NTC_THERMISTOR + tristate "NTC thermistor support from Murata" + depends on !OF || IIO=n || IIO + depends on THERMAL || !THERMAL_OF + help + This driver supports NTC thermistors sensor reading and its + interpretation. The driver can also monitor the temperature and + send notifications about the temperature. + + Currently, this driver supports + NCP15WB473, NCP18WB473, NCP21WB473, NCP03WB473, NCP15WL333, + NCP03WF104 and NCP15XH103 from Murata and B57330V2103 from EPCOS. + + This driver can also be built as a module. If so, the module + will be called ntc-thermistor. + +config SENSORS_NCT6683 + tristate "Nuvoton NCT6683D" + depends on !PPC + help + If you say yes here you get support for the hardware monitoring + functionality of the Nuvoton NCT6683D eSIO chip. + + This driver can also be built as a module. If so, the module + will be called nct6683. + +config SENSORS_NCT6775 + tristate "Nuvoton NCT6775F and compatibles" + depends on !PPC + select HWMON_VID + help + If you say yes here you get support for the hardware monitoring + functionality of the Nuvoton NCT6106D, NCT6775F, NCT6776F, NCT6779D, + NCT6791D, NCT6792D, NCT6793D, NCT6795D, NCT6796D, and compatible + Super-I/O chips. This driver replaces the w83627ehf driver for + NCT6775F and NCT6776F. + + This driver can also be built as a module. If so, the module + will be called nct6775. + +config SENSORS_NCT7802 + tristate "Nuvoton NCT7802Y" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the Nuvoton NCT7802Y + hardware monitoring chip. + + This driver can also be built as a module. If so, the module + will be called nct7802. + +config SENSORS_NCT7904 + tristate "Nuvoton NCT7904" + depends on I2C + help + If you say yes here you get support for the Nuvoton NCT7904 + hardware monitoring chip, including manual fan speed control. + + This driver can also be built as a module. If so, the module + will be called nct7904. + +config SENSORS_NPCM7XX + tristate "Nuvoton NPCM750 and compatible PWM and Fan controllers" + imply THERMAL + help + This driver provides support for Nuvoton NPCM750/730/715/705 PWM + and Fan controllers. + + This driver can also be built as a module. If so, the module + will be called npcm750-pwm-fan. + +config SENSORS_NSA320 + tristate "ZyXEL NSA320 and compatible fan speed and temperature sensors" + depends on GPIOLIB && OF + depends on MACH_KIRKWOOD || COMPILE_TEST + help + If you say yes here you get support for hardware monitoring + for the ZyXEL NSA320 Media Server and other compatible devices + (probably the NSA325 and some NSA310 variants). + + The sensor data is taken from a Holtek HT46R065 microcontroller + connected to GPIO lines. + + This driver can also be built as a module. If so, the module + will be called nsa320-hwmon. + +config SENSORS_PCF8591 + tristate "Philips PCF8591 ADC/DAC" + depends on I2C + help + If you say yes here you get support for Philips PCF8591 4-channel + ADC, 1-channel DAC chips. + + This driver can also be built as a module. If so, the module + will be called pcf8591. + + These devices are hard to detect and rarely found on mainstream + hardware. If unsure, say N. + +source drivers/hwmon/pmbus/Kconfig + +config SENSORS_PWM_FAN + tristate "PWM fan" + depends on (PWM && OF) || COMPILE_TEST + depends on THERMAL || THERMAL=n + help + If you say yes here you get support for fans connected to PWM lines. + The driver uses the generic PWM interface, thus it will work on a + variety of SoCs. + + This driver can also be built as a module. If so, the module + will be called pwm-fan. + +config SENSORS_RASPBERRYPI_HWMON + tristate "Raspberry Pi voltage monitor" + depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE) + help + If you say yes here you get support for voltage sensor on the + Raspberry Pi. + + This driver can also be built as a module. If so, the module + will be called raspberrypi-hwmon. + +config SENSORS_SHT15 + tristate "Sensiron humidity and temperature sensors. SHT15 and compat." + depends on GPIOLIB || COMPILE_TEST + select BITREVERSE + help + If you say yes here you get support for the Sensiron SHT10, SHT11, + SHT15, SHT71, SHT75 humidity and temperature sensors. + + This driver can also be built as a module. If so, the module + will be called sht15. + +config SENSORS_SHT21 + tristate "Sensiron humidity and temperature sensors. SHT21 and compat." + depends on I2C + help + If you say yes here you get support for the Sensiron SHT21, SHT25 + humidity and temperature sensors. + + This driver can also be built as a module. If so, the module + will be called sht21. + +config SENSORS_SHT3x + tristate "Sensiron humidity and temperature sensors. SHT3x and compat." + depends on I2C + select CRC8 + help + If you say yes here you get support for the Sensiron SHT30 and SHT31 + humidity and temperature sensors. + + This driver can also be built as a module. If so, the module + will be called sht3x. + +config SENSORS_SHTC1 + tristate "Sensiron humidity and temperature sensors. SHTC1 and compat." + depends on I2C + help + If you say yes here you get support for the Sensiron SHTC1 and SHTW1 + humidity and temperature sensors. + + This driver can also be built as a module. If so, the module + will be called shtc1. + +config SENSORS_S3C + tristate "Samsung built-in ADC" + depends on S3C_ADC + help + If you say yes here you get support for the on-board ADCs of + the Samsung S3C24XX, S3C64XX and other series of SoC + + This driver can also be built as a module. If so, the module + will be called s3c-hwmon. + +config SENSORS_S3C_RAW + bool "Include raw channel attributes in sysfs" + depends on SENSORS_S3C + help + Say Y here if you want to include raw copies of all the ADC + channels in sysfs. + +config SENSORS_SIS5595 + tristate "Silicon Integrated Systems Corp. SiS5595" + depends on PCI + help + If you say yes here you get support for the integrated sensors in + SiS5595 South Bridges. + + This driver can also be built as a module. If so, the module + will be called sis5595. + +config SENSORS_DME1737 + tristate "SMSC DME1737, SCH311x and compatibles" + depends on I2C && !PPC + select HWMON_VID + help + If you say yes here you get support for the hardware monitoring + and fan control features of the SMSC DME1737, SCH311x, SCH5027, and + Asus A8000 Super-I/O chips. + + This driver can also be built as a module. If so, the module + will be called dme1737. + +config SENSORS_EMC1403 + tristate "SMSC EMC1403/23 thermal sensor" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the SMSC EMC1403/23 + temperature monitoring chip. + + Threshold values can be configured using sysfs. + Data from the different diodes are accessible via sysfs. + +config SENSORS_EMC2103 + tristate "SMSC EMC2103" + depends on I2C + help + If you say yes here you get support for the temperature + and fan sensors of the SMSC EMC2103 chips. + + This driver can also be built as a module. If so, the module + will be called emc2103. + +config SENSORS_EMC6W201 + tristate "SMSC EMC6W201" + depends on I2C + help + If you say yes here you get support for the SMSC EMC6W201 + hardware monitoring chip. + + This driver can also be built as a module. If so, the module + will be called emc6w201. + +config SENSORS_SMSC47M1 + tristate "SMSC LPC47M10x and compatibles" + depends on !PPC + help + If you say yes here you get support for the integrated fan + monitoring and control capabilities of the SMSC LPC47B27x, + LPC47M10x, LPC47M112, LPC47M13x, LPC47M14x, LPC47M15x, + LPC47M192, LPC47M292 and LPC47M997 chips. + + The temperature and voltage sensor features of the LPC47M15x, + LPC47M192, LPC47M292 and LPC47M997 are supported by another + driver, select also "SMSC LPC47M192 and compatibles" below for + those. + + This driver can also be built as a module. If so, the module + will be called smsc47m1. + +config SENSORS_SMSC47M192 + tristate "SMSC LPC47M192 and compatibles" + depends on I2C + select HWMON_VID + help + If you say yes here you get support for the temperature and + voltage sensors of the SMSC LPC47M192, LPC47M15x, LPC47M292 + and LPC47M997 chips. + + The fan monitoring and control capabilities of these chips + are supported by another driver, select + "SMSC LPC47M10x and compatibles" above. You need both drivers + if you want fan control and voltage/temperature sensor support. + + This driver can also be built as a module. If so, the module + will be called smsc47m192. + +config SENSORS_SMSC47B397 + tristate "SMSC LPC47B397-NC" + depends on !PPC + help + If you say yes here you get support for the SMSC LPC47B397-NC + sensor chip. + + This driver can also be built as a module. If so, the module + will be called smsc47b397. + +config SENSORS_SCH56XX_COMMON + tristate + +config SENSORS_SCH5627 + tristate "SMSC SCH5627" + depends on !PPC && WATCHDOG + select SENSORS_SCH56XX_COMMON + select WATCHDOG_CORE + help + If you say yes here you get support for the hardware monitoring + features of the SMSC SCH5627 Super-I/O chip including support for + the integrated watchdog. + + This driver can also be built as a module. If so, the module + will be called sch5627. + +config SENSORS_SCH5636 + tristate "SMSC SCH5636" + depends on !PPC && WATCHDOG + select SENSORS_SCH56XX_COMMON + select WATCHDOG_CORE + help + SMSC SCH5636 Super I/O chips include an embedded microcontroller for + hardware monitoring solutions, allowing motherboard manufacturers to + create their own custom hwmon solution based upon the SCH5636. + + Currently this driver only supports the Fujitsu Theseus SCH5636 based + hwmon solution. Say yes here if you want support for the Fujitsu + Theseus' hardware monitoring features including support for the + integrated watchdog. + + This driver can also be built as a module. If so, the module + will be called sch5636. + +config SENSORS_STTS751 + tristate "ST Microelectronics STTS751" + depends on I2C + help + If you say yes here you get support for STTS751 + temperature sensor chips. + + This driver can also be built as a module. If so, the module + will be called stts751. + +config SENSORS_SMM665 + tristate "Summit Microelectronics SMM665" + depends on I2C + help + If you say yes here you get support for the hardware monitoring + features of the Summit Microelectronics SMM665/SMM665B Six-Channel + Active DC Output Controller / Monitor. + + Other supported chips are SMM465, SMM665C, SMM764, and SMM766. + Support for those chips is untested. + + This driver can also be built as a module. If so, the module will + be called smm665. + +config SENSORS_ADC128D818 + tristate "Texas Instruments ADC128D818" + depends on I2C + help + If you say yes here you get support for the Texas Instruments + ADC128D818 System Monitor with Temperature Sensor chip. + + This driver can also be built as a module. If so, the module + will be called adc128d818. + +config SENSORS_ADS1015 + tristate "Texas Instruments ADS1015" + depends on I2C + help + If you say yes here you get support for Texas Instruments + ADS1015/ADS1115 12/16-bit 4-input ADC device. + + This driver can also be built as a module. If so, the module + will be called ads1015. + +config SENSORS_ADS7828 + tristate "Texas Instruments ADS7828 and compatibles" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for Texas Instruments ADS7828 and + ADS7830 8-channel A/D converters. ADS7828 resolution is 12-bit, while + it is 8-bit on ADS7830. + + This driver can also be built as a module. If so, the module + will be called ads7828. + +config SENSORS_ADS7871 + tristate "Texas Instruments ADS7871 A/D converter" + depends on SPI + help + If you say yes here you get support for TI ADS7871 & ADS7870 + + This driver can also be built as a module. If so, the module + will be called ads7871. + +config SENSORS_AMC6821 + tristate "Texas Instruments AMC6821" + depends on I2C + help + If you say yes here you get support for the Texas Instruments + AMC6821 hardware monitoring chips. + + This driver can also be build as a module. If so, the module + will be called amc6821. + +config SENSORS_INA209 + tristate "TI / Burr Brown INA209" + depends on I2C + help + If you say yes here you get support for the TI / Burr Brown INA209 + voltage / current / power monitor I2C interface. + + This driver can also be built as a module. If so, the module will + be called ina209. + +config SENSORS_INA2XX + tristate "Texas Instruments INA219 and compatibles" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for INA219, INA220, INA226, + INA230, and INA231 power monitor chips. + + The INA2xx driver is configured for the default configuration of + the part as described in the datasheet. + Default value for Rshunt is 10 mOhms. + This driver can also be built as a module. If so, the module + will be called ina2xx. + +config SENSORS_INA3221 + tristate "Texas Instruments INA3221 Triple Power Monitor" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the TI INA3221 Triple Power + Monitor. + + This driver can also be built as a module. If so, the module + will be called ina3221. + +config SENSORS_TC74 + tristate "Microchip TC74" + depends on I2C + help + If you say yes here you get support for Microchip TC74 single + input temperature sensor chips. + + This driver can also be built as a module. If so, the module + will be called tc74. + +config SENSORS_THMC50 + tristate "Texas Instruments THMC50 / Analog Devices ADM1022" + depends on I2C + help + If you say yes here you get support for Texas Instruments THMC50 + sensor chips and clones: the Analog Devices ADM1022. + + This driver can also be built as a module. If so, the module + will be called thmc50. + +config SENSORS_TMP102 + tristate "Texas Instruments TMP102" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for Texas Instruments TMP102 + sensor chips. + + This driver can also be built as a module. If so, the module + will be called tmp102. + +config SENSORS_TMP103 + tristate "Texas Instruments TMP103" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for Texas Instruments TMP103 + sensor chips. + + This driver can also be built as a module. If so, the module + will be called tmp103. + +config SENSORS_TMP108 + tristate "Texas Instruments TMP108" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for Texas Instruments TMP108 + sensor chips. + + This driver can also be built as a module. If so, the module + will be called tmp108. + +config SENSORS_TMP401 + tristate "Texas Instruments TMP401 and compatibles" + depends on I2C + help + If you say yes here you get support for Texas Instruments TMP401, + TMP411, TMP431, TMP432, TMP435, and TMP461 temperature sensor chips. + + This driver can also be built as a module. If so, the module + will be called tmp401. + +config SENSORS_TMP421 + tristate "Texas Instruments TMP421 and compatible" + depends on I2C + help + If you say yes here you get support for Texas Instruments TMP421, + TMP422, TMP423, TMP441, and TMP442 temperature sensor chips. + + This driver can also be built as a module. If so, the module + will be called tmp421. + +config SENSORS_VEXPRESS + tristate "Versatile Express" + depends on VEXPRESS_CONFIG + help + This driver provides support for hardware sensors available on + the ARM Ltd's Versatile Express platform. It can provide wide + range of information like temperature, power, energy. + +config SENSORS_VIA_CPUTEMP + tristate "VIA CPU temperature sensor" + depends on X86 + select HWMON_VID + help + If you say yes here you get support for the temperature + sensor inside your CPU. Supported are all known variants of + the VIA C7 and Nano. + +config SENSORS_VIA686A + tristate "VIA686A" + depends on PCI + help + If you say yes here you get support for the integrated sensors in + Via 686A/B South Bridges. + + This driver can also be built as a module. If so, the module + will be called via686a. + +config SENSORS_VT1211 + tristate "VIA VT1211" + depends on !PPC + select HWMON_VID + help + If you say yes here then you get support for hardware monitoring + features of the VIA VT1211 Super-I/O chip. + + This driver can also be built as a module. If so, the module + will be called vt1211. + +config SENSORS_VT8231 + tristate "VIA VT8231" + depends on PCI + select HWMON_VID + help + If you say yes here then you get support for the integrated sensors + in the VIA VT8231 device. + + This driver can also be built as a module. If so, the module + will be called vt8231. + +config SENSORS_W83773G + tristate "Nuvoton W83773G" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the Nuvoton W83773G hardware + monitoring chip. + + This driver can also be built as a module. If so, the module + will be called w83773g. + +config SENSORS_W83781D + tristate "Winbond W83781D, W83782D, W83783S, Asus AS99127F" + depends on I2C + select HWMON_VID + help + If you say yes here you get support for the Winbond W8378x series + of sensor chips: the W83781D, W83782D and W83783S, and the similar + Asus AS99127F. + + This driver can also be built as a module. If so, the module + will be called w83781d. + +config SENSORS_W83791D + tristate "Winbond W83791D" + depends on I2C + select HWMON_VID + help + If you say yes here you get support for the Winbond W83791D chip. + + This driver can also be built as a module. If so, the module + will be called w83791d. + +config SENSORS_W83792D + tristate "Winbond W83792D" + depends on I2C + help + If you say yes here you get support for the Winbond W83792D chip. + + This driver can also be built as a module. If so, the module + will be called w83792d. + +config SENSORS_W83793 + tristate "Winbond W83793" + depends on I2C + select HWMON_VID + help + If you say yes here you get support for the Winbond W83793 + hardware monitoring chip, including support for the integrated + watchdog. + + This driver can also be built as a module. If so, the module + will be called w83793. + +config SENSORS_W83795 + tristate "Winbond/Nuvoton W83795G/ADG" + depends on I2C + help + If you say yes here you get support for the Winbond W83795G and + W83795ADG hardware monitoring chip, including manual fan speed + control. + + This driver can also be built as a module. If so, the module + will be called w83795. + +config SENSORS_W83795_FANCTRL + bool "Include automatic fan control support (DANGEROUS)" + depends on SENSORS_W83795 + help + If you say yes here, support for automatic fan speed control + will be included in the driver. + + This part of the code wasn't carefully reviewed and tested yet, + so enabling this option is strongly discouraged on production + servers. Only developers and testers should enable it for the + time being. + + Please also note that this option will create sysfs attribute + files which may change in the future, so you shouldn't rely + on them being stable. + +config SENSORS_W83L785TS + tristate "Winbond W83L785TS-S" + depends on I2C + help + If you say yes here you get support for the Winbond W83L785TS-S + sensor chip, which is used on the Asus A7N8X, among other + motherboards. + + This driver can also be built as a module. If so, the module + will be called w83l785ts. + +config SENSORS_W83L786NG + tristate "Winbond W83L786NG, W83L786NR" + depends on I2C + help + If you say yes here you get support for the Winbond W83L786NG + and W83L786NR sensor chips. + + This driver can also be built as a module. If so, the module + will be called w83l786ng. + +config SENSORS_W83627HF + tristate "Winbond W83627HF, W83627THF, W83637HF, W83687THF, W83697HF" + depends on !PPC + select HWMON_VID + help + If you say yes here you get support for the Winbond W836X7 series + of sensor chips: the W83627HF, W83627THF, W83637HF, W83687THF and + W83697HF. + + This driver can also be built as a module. If so, the module + will be called w83627hf. + +config SENSORS_W83627EHF + tristate "Winbond W83627EHF/EHG/DHG/UHG, W83667HG, NCT6775F, NCT6776F" + depends on !PPC + select HWMON_VID + help + If you say yes here you get support for the hardware + monitoring functionality of the Winbond W83627EHF Super-I/O chip. + + This driver also supports the W83627EHG, which is the lead-free + version of the W83627EHF, and the W83627DHG, which is a similar + chip suited for specific Intel processors that use PECI such as + the Core 2 Duo. And also the W83627UHG, which is a stripped down + version of the W83627DHG (as far as hardware monitoring goes.) + + This driver also supports Nuvoton W83667HG, W83667HG-B, NCT6775F + (also known as W83667HG-I), and NCT6776F. + + This driver can also be built as a module. If so, the module + will be called w83627ehf. + +config SENSORS_WM831X + tristate "WM831x PMICs" + depends on MFD_WM831X + help + If you say yes here you get support for the hardware + monitoring functionality of the Wolfson Microelectronics + WM831x series of PMICs. + + This driver can also be built as a module. If so, the module + will be called wm831x-hwmon. + +config SENSORS_WM8350 + tristate "Wolfson Microelectronics WM835x" + depends on MFD_WM8350 + help + If you say yes here you get support for the hardware + monitoring features of the WM835x series of PMICs. + + This driver can also be built as a module. If so, the module + will be called wm8350-hwmon. + +config SENSORS_ULTRA45 + tristate "Sun Ultra45 PIC16F747" + depends on SPARC64 + help + This driver provides support for the Ultra45 workstation environmental + sensors. + +config SENSORS_XGENE + tristate "APM X-Gene SoC hardware monitoring driver" + depends on XGENE_SLIMPRO_MBOX || PCC + help + If you say yes here you get support for the temperature + and power sensors for APM X-Gene SoC. + +if ACPI + +comment "ACPI drivers" + +config SENSORS_ACPI_POWER + tristate "ACPI 4.0 power meter" + help + This driver exposes ACPI 4.0 power meters as hardware monitoring + devices. Say Y (or M) if you have a computer with ACPI 4.0 firmware + and a power meter. + + To compile this driver as a module, choose M here: + the module will be called acpi_power_meter. + +config SENSORS_ATK0110 + tristate "ASUS ATK0110" + depends on X86 + help + If you say yes here you get support for the ACPI hardware + monitoring interface found in many ASUS motherboards. This + driver will provide readings of fans, voltages and temperatures + through the system firmware. + + This driver can also be built as a module. If so, the module + will be called asus_atk0110. + +endif # ACPI + +endif # HWMON diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile new file mode 100644 index 000000000..93f7f41ea --- /dev/null +++ b/drivers/hwmon/Makefile @@ -0,0 +1,184 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for sensor chip drivers. +# + +obj-$(CONFIG_HWMON) += hwmon.o +obj-$(CONFIG_HWMON_VID) += hwmon-vid.o + +# APCI drivers +obj-$(CONFIG_SENSORS_ACPI_POWER) += acpi_power_meter.o +obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o + +# Native drivers +# asb100, then w83781d go first, as they can override other drivers' addresses. +obj-$(CONFIG_SENSORS_ASB100) += asb100.o +obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o +obj-$(CONFIG_SENSORS_W83773G) += w83773g.o +obj-$(CONFIG_SENSORS_W83792D) += w83792d.o +obj-$(CONFIG_SENSORS_W83793) += w83793.o +obj-$(CONFIG_SENSORS_W83795) += w83795.o +obj-$(CONFIG_SENSORS_W83781D) += w83781d.o +obj-$(CONFIG_SENSORS_W83791D) += w83791d.o + +obj-$(CONFIG_SENSORS_AB8500) += abx500.o ab8500.o +obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o +obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o +obj-$(CONFIG_SENSORS_AD7314) += ad7314.o +obj-$(CONFIG_SENSORS_AD7414) += ad7414.o +obj-$(CONFIG_SENSORS_AD7418) += ad7418.o +obj-$(CONFIG_SENSORS_ADC128D818) += adc128d818.o +obj-$(CONFIG_SENSORS_ADCXX) += adcxx.o +obj-$(CONFIG_SENSORS_ADM1021) += adm1021.o +obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o +obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o +obj-$(CONFIG_SENSORS_ADM1029) += adm1029.o +obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o +obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o +obj-$(CONFIG_SENSORS_ADS1015) += ads1015.o +obj-$(CONFIG_SENSORS_ADS7828) += ads7828.o +obj-$(CONFIG_SENSORS_ADS7871) += ads7871.o +obj-$(CONFIG_SENSORS_ADT7X10) += adt7x10.o +obj-$(CONFIG_SENSORS_ADT7310) += adt7310.o +obj-$(CONFIG_SENSORS_ADT7410) += adt7410.o +obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o +obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o +obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o +obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o +obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o +obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o +obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o +obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o +obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o +obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o +obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o +obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o +obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o +obj-$(CONFIG_SENSORS_DELL_SMM) += dell-smm-hwmon.o +obj-$(CONFIG_SENSORS_DME1737) += dme1737.o +obj-$(CONFIG_SENSORS_DS620) += ds620.o +obj-$(CONFIG_SENSORS_DS1621) += ds1621.o +obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o +obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o +obj-$(CONFIG_SENSORS_EMC6W201) += emc6w201.o +obj-$(CONFIG_SENSORS_F71805F) += f71805f.o +obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o +obj-$(CONFIG_SENSORS_F75375S) += f75375s.o +obj-$(CONFIG_SENSORS_FAM15H_POWER) += fam15h_power.o +obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o +obj-$(CONFIG_SENSORS_FTSTEUTATES) += ftsteutates.o +obj-$(CONFIG_SENSORS_G760A) += g760a.o +obj-$(CONFIG_SENSORS_G762) += g762.o +obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o +obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o +obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o +obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o +obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o +obj-$(CONFIG_SENSORS_I5500) += i5500_temp.o +obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o +obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o +obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o +obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o +obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o +obj-$(CONFIG_SENSORS_INA209) += ina209.o +obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o +obj-$(CONFIG_SENSORS_INA3221) += ina3221.o +obj-$(CONFIG_SENSORS_IT87) += it87.o +obj-$(CONFIG_SENSORS_JC42) += jc42.o +obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o +obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o +obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o +obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o +obj-$(CONFIG_SENSORS_LM63) += lm63.o +obj-$(CONFIG_SENSORS_LM70) += lm70.o +obj-$(CONFIG_SENSORS_LM73) += lm73.o +obj-$(CONFIG_SENSORS_LM75) += lm75.o +obj-$(CONFIG_SENSORS_LM77) += lm77.o +obj-$(CONFIG_SENSORS_LM78) += lm78.o +obj-$(CONFIG_SENSORS_LM80) += lm80.o +obj-$(CONFIG_SENSORS_LM83) += lm83.o +obj-$(CONFIG_SENSORS_LM85) += lm85.o +obj-$(CONFIG_SENSORS_LM87) += lm87.o +obj-$(CONFIG_SENSORS_LM90) += lm90.o +obj-$(CONFIG_SENSORS_LM92) += lm92.o +obj-$(CONFIG_SENSORS_LM93) += lm93.o +obj-$(CONFIG_SENSORS_LM95234) += lm95234.o +obj-$(CONFIG_SENSORS_LM95241) += lm95241.o +obj-$(CONFIG_SENSORS_LM95245) += lm95245.o +obj-$(CONFIG_SENSORS_LTC2945) += ltc2945.o +obj-$(CONFIG_SENSORS_LTC2990) += ltc2990.o +obj-$(CONFIG_SENSORS_LTC4151) += ltc4151.o +obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o +obj-$(CONFIG_SENSORS_LTC4222) += ltc4222.o +obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o +obj-$(CONFIG_SENSORS_LTC4260) += ltc4260.o +obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o +obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o +obj-$(CONFIG_SENSORS_MAX1111) += max1111.o +obj-$(CONFIG_SENSORS_MAX16065) += max16065.o +obj-$(CONFIG_SENSORS_MAX1619) += max1619.o +obj-$(CONFIG_SENSORS_MAX1668) += max1668.o +obj-$(CONFIG_SENSORS_MAX197) += max197.o +obj-$(CONFIG_SENSORS_MAX31722) += max31722.o +obj-$(CONFIG_SENSORS_MAX6621) += max6621.o +obj-$(CONFIG_SENSORS_MAX6639) += max6639.o +obj-$(CONFIG_SENSORS_MAX6642) += max6642.o +obj-$(CONFIG_SENSORS_MAX6650) += max6650.o +obj-$(CONFIG_SENSORS_MAX6697) += max6697.o +obj-$(CONFIG_SENSORS_MAX31790) += max31790.o +obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o +obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o +obj-$(CONFIG_SENSORS_TC654) += tc654.o +obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o +obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o +obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o +obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o +obj-$(CONFIG_SENSORS_NCT7802) += nct7802.o +obj-$(CONFIG_SENSORS_NCT7904) += nct7904.o +obj-$(CONFIG_SENSORS_NPCM7XX) += npcm750-pwm-fan.o +obj-$(CONFIG_SENSORS_NSA320) += nsa320-hwmon.o +obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o +obj-$(CONFIG_SENSORS_PC87360) += pc87360.o +obj-$(CONFIG_SENSORS_PC87427) += pc87427.o +obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o +obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o +obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o +obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o +obj-$(CONFIG_SENSORS_S3C) += s3c-hwmon.o +obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o +obj-$(CONFIG_SENSORS_SCH5627) += sch5627.o +obj-$(CONFIG_SENSORS_SCH5636) += sch5636.o +obj-$(CONFIG_SENSORS_SHT15) += sht15.o +obj-$(CONFIG_SENSORS_SHT21) += sht21.o +obj-$(CONFIG_SENSORS_SHT3x) += sht3x.o +obj-$(CONFIG_SENSORS_SHTC1) += shtc1.o +obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o +obj-$(CONFIG_SENSORS_SMM665) += smm665.o +obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o +obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o +obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o +obj-$(CONFIG_SENSORS_STTS751) += stts751.o +obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o +obj-$(CONFIG_SENSORS_TC74) += tc74.o +obj-$(CONFIG_SENSORS_THMC50) += thmc50.o +obj-$(CONFIG_SENSORS_TMP102) += tmp102.o +obj-$(CONFIG_SENSORS_TMP103) += tmp103.o +obj-$(CONFIG_SENSORS_TMP108) += tmp108.o +obj-$(CONFIG_SENSORS_TMP401) += tmp401.o +obj-$(CONFIG_SENSORS_TMP421) += tmp421.o +obj-$(CONFIG_SENSORS_VEXPRESS) += vexpress-hwmon.o +obj-$(CONFIG_SENSORS_VIA_CPUTEMP)+= via-cputemp.o +obj-$(CONFIG_SENSORS_VIA686A) += via686a.o +obj-$(CONFIG_SENSORS_VT1211) += vt1211.o +obj-$(CONFIG_SENSORS_VT8231) += vt8231.o +obj-$(CONFIG_SENSORS_W83627EHF) += w83627ehf.o +obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o +obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o +obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o +obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o +obj-$(CONFIG_SENSORS_XGENE) += xgene-hwmon.o + +obj-$(CONFIG_PMBUS) += pmbus/ + +ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG + diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c new file mode 100644 index 000000000..8b6a4f4c8 --- /dev/null +++ b/drivers/hwmon/ab8500.c @@ -0,0 +1,207 @@ +/* + * Copyright (C) ST-Ericsson 2010 - 2013 + * Author: Martin Persson + * Hongbo Zhang + * License Terms: GNU General Public License v2 + * + * When the AB8500 thermal warning temperature is reached (threshold cannot + * be changed by SW), an interrupt is set, and if no further action is taken + * within a certain time frame, kernel_power_off will be called. + * + * When AB8500 thermal shutdown temperature is reached a hardware shutdown of + * the AB8500 will occur. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "abx500.h" + +#define DEFAULT_POWER_OFF_DELAY (HZ * 10) +#define THERMAL_VCC 1800 +#define PULL_UP_RESISTOR 47000 +/* Number of monitored sensors should not greater than NUM_SENSORS */ +#define NUM_MONITORED_SENSORS 4 + +struct ab8500_gpadc_cfg { + const struct abx500_res_to_temp *temp_tbl; + int tbl_sz; + int vcc; + int r_up; +}; + +struct ab8500_temp { + struct ab8500_gpadc *gpadc; + struct ab8500_btemp *btemp; + struct delayed_work power_off_work; + struct ab8500_gpadc_cfg cfg; + struct abx500_temp *abx500_data; +}; + +/* + * The hardware connection is like this: + * VCC----[ R_up ]-----[ NTC ]----GND + * where R_up is pull-up resistance, and GPADC measures voltage on NTC. + * and res_to_temp table is strictly sorted by falling resistance values. + */ +static int ab8500_voltage_to_temp(struct ab8500_gpadc_cfg *cfg, + int v_ntc, int *temp) +{ + int r_ntc, i = 0, tbl_sz = cfg->tbl_sz; + const struct abx500_res_to_temp *tbl = cfg->temp_tbl; + + if (cfg->vcc < 0 || v_ntc >= cfg->vcc) + return -EINVAL; + + r_ntc = v_ntc * cfg->r_up / (cfg->vcc - v_ntc); + if (r_ntc > tbl[0].resist || r_ntc < tbl[tbl_sz - 1].resist) + return -EINVAL; + + while (!(r_ntc <= tbl[i].resist && r_ntc > tbl[i + 1].resist) && + i < tbl_sz - 2) + i++; + + /* return milli-Celsius */ + *temp = tbl[i].temp * 1000 + ((tbl[i + 1].temp - tbl[i].temp) * 1000 * + (r_ntc - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist); + + return 0; +} + +static int ab8500_read_sensor(struct abx500_temp *data, u8 sensor, int *temp) +{ + int voltage, ret; + struct ab8500_temp *ab8500_data = data->plat_data; + + if (sensor == BAT_CTRL) { + *temp = ab8500_btemp_get_batctrl_temp(ab8500_data->btemp); + } else if (sensor == BTEMP_BALL) { + *temp = ab8500_btemp_get_temp(ab8500_data->btemp); + } else { + voltage = ab8500_gpadc_convert(ab8500_data->gpadc, sensor); + if (voltage < 0) + return voltage; + + ret = ab8500_voltage_to_temp(&ab8500_data->cfg, voltage, temp); + if (ret < 0) + return ret; + } + + return 0; +} + +static void ab8500_thermal_power_off(struct work_struct *work) +{ + struct ab8500_temp *ab8500_data = container_of(work, + struct ab8500_temp, power_off_work.work); + struct abx500_temp *abx500_data = ab8500_data->abx500_data; + + dev_warn(&abx500_data->pdev->dev, "Power off due to critical temp\n"); + + kernel_power_off(); +} + +static ssize_t ab8500_show_name(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + return sprintf(buf, "ab8500\n"); +} + +static ssize_t ab8500_show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + char *label; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int index = attr->index; + + switch (index) { + case 1: + label = "ext_adc1"; + break; + case 2: + label = "ext_adc2"; + break; + case 3: + label = "bat_temp"; + break; + case 4: + label = "bat_ctrl"; + break; + default: + return -EINVAL; + } + + return sprintf(buf, "%s\n", label); +} + +static int ab8500_temp_irq_handler(int irq, struct abx500_temp *data) +{ + struct ab8500_temp *ab8500_data = data->plat_data; + + dev_warn(&data->pdev->dev, "Power off in %d s\n", + DEFAULT_POWER_OFF_DELAY / HZ); + + schedule_delayed_work(&ab8500_data->power_off_work, + DEFAULT_POWER_OFF_DELAY); + return 0; +} + +int abx500_hwmon_init(struct abx500_temp *data) +{ + struct ab8500_temp *ab8500_data; + + ab8500_data = devm_kzalloc(&data->pdev->dev, sizeof(*ab8500_data), + GFP_KERNEL); + if (!ab8500_data) + return -ENOMEM; + + ab8500_data->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + if (IS_ERR(ab8500_data->gpadc)) + return PTR_ERR(ab8500_data->gpadc); + + ab8500_data->btemp = ab8500_btemp_get(); + if (IS_ERR(ab8500_data->btemp)) + return PTR_ERR(ab8500_data->btemp); + + INIT_DELAYED_WORK(&ab8500_data->power_off_work, + ab8500_thermal_power_off); + + ab8500_data->cfg.vcc = THERMAL_VCC; + ab8500_data->cfg.r_up = PULL_UP_RESISTOR; + ab8500_data->cfg.temp_tbl = ab8500_temp_tbl_a_thermistor; + ab8500_data->cfg.tbl_sz = ab8500_temp_tbl_a_size; + + data->plat_data = ab8500_data; + + /* + * ADC_AUX1 and ADC_AUX2, connected to external NTC + * BTEMP_BALL and BAT_CTRL, fixed usage + */ + data->gpadc_addr[0] = ADC_AUX1; + data->gpadc_addr[1] = ADC_AUX2; + data->gpadc_addr[2] = BTEMP_BALL; + data->gpadc_addr[3] = BAT_CTRL; + data->monitored_sensors = NUM_MONITORED_SENSORS; + + data->ops.read_sensor = ab8500_read_sensor; + data->ops.irq_handler = ab8500_temp_irq_handler; + data->ops.show_name = ab8500_show_name; + data->ops.show_label = ab8500_show_label; + data->ops.is_visible = NULL; + + return 0; +} +EXPORT_SYMBOL(abx500_hwmon_init); + +MODULE_AUTHOR("Hongbo Zhang "); +MODULE_DESCRIPTION("AB8500 temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/abituguru.c b/drivers/hwmon/abituguru.c new file mode 100644 index 000000000..7a09c1615 --- /dev/null +++ b/drivers/hwmon/abituguru.c @@ -0,0 +1,1650 @@ +/* + * abituguru.c Copyright (c) 2005-2006 Hans de Goede + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/* + * This driver supports the sensor part of the first and second revision of + * the custom Abit uGuru chip found on Abit uGuru motherboards. Note: because + * of lack of specs the CPU/RAM voltage & frequency control is not supported! + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Banks */ +#define ABIT_UGURU_ALARM_BANK 0x20 /* 1x 3 bytes */ +#define ABIT_UGURU_SENSOR_BANK1 0x21 /* 16x volt and temp */ +#define ABIT_UGURU_FAN_PWM 0x24 /* 3x 5 bytes */ +#define ABIT_UGURU_SENSOR_BANK2 0x26 /* fans */ +/* max nr of sensors in bank1, a bank1 sensor can be in, temp or nc */ +#define ABIT_UGURU_MAX_BANK1_SENSORS 16 +/* + * Warning if you increase one of the 2 MAX defines below to 10 or higher you + * should adjust the belonging _NAMES_LENGTH macro for the 2 digit number! + */ +/* max nr of sensors in bank2, currently mb's with max 6 fans are known */ +#define ABIT_UGURU_MAX_BANK2_SENSORS 6 +/* max nr of pwm outputs, currently mb's with max 5 pwm outputs are known */ +#define ABIT_UGURU_MAX_PWMS 5 +/* uGuru sensor bank 1 flags */ /* Alarm if: */ +#define ABIT_UGURU_TEMP_HIGH_ALARM_ENABLE 0x01 /* temp over warn */ +#define ABIT_UGURU_VOLT_HIGH_ALARM_ENABLE 0x02 /* volt over max */ +#define ABIT_UGURU_VOLT_LOW_ALARM_ENABLE 0x04 /* volt under min */ +#define ABIT_UGURU_TEMP_HIGH_ALARM_FLAG 0x10 /* temp is over warn */ +#define ABIT_UGURU_VOLT_HIGH_ALARM_FLAG 0x20 /* volt is over max */ +#define ABIT_UGURU_VOLT_LOW_ALARM_FLAG 0x40 /* volt is under min */ +/* uGuru sensor bank 2 flags */ /* Alarm if: */ +#define ABIT_UGURU_FAN_LOW_ALARM_ENABLE 0x01 /* fan under min */ +/* uGuru sensor bank common flags */ +#define ABIT_UGURU_BEEP_ENABLE 0x08 /* beep if alarm */ +#define ABIT_UGURU_SHUTDOWN_ENABLE 0x80 /* shutdown if alarm */ +/* uGuru fan PWM (speed control) flags */ +#define ABIT_UGURU_FAN_PWM_ENABLE 0x80 /* enable speed control */ +/* Values used for conversion */ +#define ABIT_UGURU_FAN_MAX 15300 /* RPM */ +/* Bank1 sensor types */ +#define ABIT_UGURU_IN_SENSOR 0 +#define ABIT_UGURU_TEMP_SENSOR 1 +#define ABIT_UGURU_NC 2 +/* + * In many cases we need to wait for the uGuru to reach a certain status, most + * of the time it will reach this status within 30 - 90 ISA reads, and thus we + * can best busy wait. This define gives the total amount of reads to try. + */ +#define ABIT_UGURU_WAIT_TIMEOUT 125 +/* + * However sometimes older versions of the uGuru seem to be distracted and they + * do not respond for a long time. To handle this we sleep before each of the + * last ABIT_UGURU_WAIT_TIMEOUT_SLEEP tries. + */ +#define ABIT_UGURU_WAIT_TIMEOUT_SLEEP 5 +/* + * Normally all expected status in abituguru_ready, are reported after the + * first read, but sometimes not and we need to poll. + */ +#define ABIT_UGURU_READY_TIMEOUT 5 +/* Maximum 3 retries on timedout reads/writes, delay 200 ms before retrying */ +#define ABIT_UGURU_MAX_RETRIES 3 +#define ABIT_UGURU_RETRY_DELAY (HZ/5) +/* Maximum 2 timeouts in abituguru_update_device, iow 3 in a row is an error */ +#define ABIT_UGURU_MAX_TIMEOUTS 2 +/* utility macros */ +#define ABIT_UGURU_NAME "abituguru" +#define ABIT_UGURU_DEBUG(level, format, arg...) \ + do { \ + if (level <= verbose) \ + pr_debug(format , ## arg); \ + } while (0) + +/* Macros to help calculate the sysfs_names array length */ +/* + * sum of strlen of: in??_input\0, in??_{min,max}\0, in??_{min,max}_alarm\0, + * in??_{min,max}_alarm_enable\0, in??_beep\0, in??_shutdown\0 + */ +#define ABITUGURU_IN_NAMES_LENGTH (11 + 2 * 9 + 2 * 15 + 2 * 22 + 10 + 14) +/* + * sum of strlen of: temp??_input\0, temp??_max\0, temp??_crit\0, + * temp??_alarm\0, temp??_alarm_enable\0, temp??_beep\0, temp??_shutdown\0 + */ +#define ABITUGURU_TEMP_NAMES_LENGTH (13 + 11 + 12 + 13 + 20 + 12 + 16) +/* + * sum of strlen of: fan?_input\0, fan?_min\0, fan?_alarm\0, + * fan?_alarm_enable\0, fan?_beep\0, fan?_shutdown\0 + */ +#define ABITUGURU_FAN_NAMES_LENGTH (11 + 9 + 11 + 18 + 10 + 14) +/* + * sum of strlen of: pwm?_enable\0, pwm?_auto_channels_temp\0, + * pwm?_auto_point{1,2}_pwm\0, pwm?_auto_point{1,2}_temp\0 + */ +#define ABITUGURU_PWM_NAMES_LENGTH (12 + 24 + 2 * 21 + 2 * 22) +/* IN_NAMES_LENGTH > TEMP_NAMES_LENGTH so assume all bank1 sensors are in */ +#define ABITUGURU_SYSFS_NAMES_LENGTH ( \ + ABIT_UGURU_MAX_BANK1_SENSORS * ABITUGURU_IN_NAMES_LENGTH + \ + ABIT_UGURU_MAX_BANK2_SENSORS * ABITUGURU_FAN_NAMES_LENGTH + \ + ABIT_UGURU_MAX_PWMS * ABITUGURU_PWM_NAMES_LENGTH) + +/* + * All the macros below are named identical to the oguru and oguru2 programs + * reverse engineered by Olle Sandberg, hence the names might not be 100% + * logical. I could come up with better names, but I prefer keeping the names + * identical so that this driver can be compared with his work more easily. + */ +/* Two i/o-ports are used by uGuru */ +#define ABIT_UGURU_BASE 0x00E0 +/* Used to tell uGuru what to read and to read the actual data */ +#define ABIT_UGURU_CMD 0x00 +/* Mostly used to check if uGuru is busy */ +#define ABIT_UGURU_DATA 0x04 +#define ABIT_UGURU_REGION_LENGTH 5 +/* uGuru status' */ +#define ABIT_UGURU_STATUS_WRITE 0x00 /* Ready to be written */ +#define ABIT_UGURU_STATUS_READ 0x01 /* Ready to be read */ +#define ABIT_UGURU_STATUS_INPUT 0x08 /* More input */ +#define ABIT_UGURU_STATUS_READY 0x09 /* Ready to be written */ + +/* Constants */ +/* in (Volt) sensors go up to 3494 mV, temp to 255000 millidegrees Celsius */ +static const int abituguru_bank1_max_value[2] = { 3494, 255000 }; +/* + * Min / Max allowed values for sensor2 (fan) alarm threshold, these values + * correspond to 300-3000 RPM + */ +static const u8 abituguru_bank2_min_threshold = 5; +static const u8 abituguru_bank2_max_threshold = 50; +/* + * Register 0 is a bitfield, 1 and 2 are pwm settings (255 = 100%), 3 and 4 + * are temperature trip points. + */ +static const int abituguru_pwm_settings_multiplier[5] = { 0, 1, 1, 1000, 1000 }; +/* + * Min / Max allowed values for pwm_settings. Note: pwm1 (CPU fan) is a + * special case the minimum allowed pwm% setting for this is 30% (77) on + * some MB's this special case is handled in the code! + */ +static const u8 abituguru_pwm_min[5] = { 0, 170, 170, 25, 25 }; +static const u8 abituguru_pwm_max[5] = { 0, 255, 255, 75, 75 }; + + +/* Insmod parameters */ +static bool force; +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Set to one to force detection."); +static int bank1_types[ABIT_UGURU_MAX_BANK1_SENSORS] = { -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; +module_param_array(bank1_types, int, NULL, 0); +MODULE_PARM_DESC(bank1_types, "Bank1 sensortype autodetection override:\n" + " -1 autodetect\n" + " 0 volt sensor\n" + " 1 temp sensor\n" + " 2 not connected"); +static int fan_sensors; +module_param(fan_sensors, int, 0); +MODULE_PARM_DESC(fan_sensors, "Number of fan sensors on the uGuru " + "(0 = autodetect)"); +static int pwms; +module_param(pwms, int, 0); +MODULE_PARM_DESC(pwms, "Number of PWMs on the uGuru " + "(0 = autodetect)"); + +/* Default verbose is 2, since this driver is still in the testing phase */ +static int verbose = 2; +module_param(verbose, int, 0644); +MODULE_PARM_DESC(verbose, "How verbose should the driver be? (0-3):\n" + " 0 normal output\n" + " 1 + verbose error reporting\n" + " 2 + sensors type probing info\n" + " 3 + retryable error reporting"); + + +/* + * For the Abit uGuru, we need to keep some data in memory. + * The structure is dynamically allocated, at the same time when a new + * abituguru device is allocated. + */ +struct abituguru_data { + struct device *hwmon_dev; /* hwmon registered device */ + struct mutex update_lock; /* protect access to data and uGuru */ + unsigned long last_updated; /* In jiffies */ + unsigned short addr; /* uguru base address */ + char uguru_ready; /* is the uguru in ready state? */ + unsigned char update_timeouts; /* + * number of update timeouts since last + * successful update + */ + + /* + * The sysfs attr and their names are generated automatically, for bank1 + * we cannot use a predefined array because we don't know beforehand + * of a sensor is a volt or a temp sensor, for bank2 and the pwms its + * easier todo things the same way. For in sensors we have 9 (temp 7) + * sysfs entries per sensor, for bank2 and pwms 6. + */ + struct sensor_device_attribute_2 sysfs_attr[ + ABIT_UGURU_MAX_BANK1_SENSORS * 9 + + ABIT_UGURU_MAX_BANK2_SENSORS * 6 + ABIT_UGURU_MAX_PWMS * 6]; + /* Buffer to store the dynamically generated sysfs names */ + char sysfs_names[ABITUGURU_SYSFS_NAMES_LENGTH]; + + /* Bank 1 data */ + /* number of and addresses of [0] in, [1] temp sensors */ + u8 bank1_sensors[2]; + u8 bank1_address[2][ABIT_UGURU_MAX_BANK1_SENSORS]; + u8 bank1_value[ABIT_UGURU_MAX_BANK1_SENSORS]; + /* + * This array holds 3 entries per sensor for the bank 1 sensor settings + * (flags, min, max for voltage / flags, warn, shutdown for temp). + */ + u8 bank1_settings[ABIT_UGURU_MAX_BANK1_SENSORS][3]; + /* + * Maximum value for each sensor used for scaling in mV/millidegrees + * Celsius. + */ + int bank1_max_value[ABIT_UGURU_MAX_BANK1_SENSORS]; + + /* Bank 2 data, ABIT_UGURU_MAX_BANK2_SENSORS entries for bank2 */ + u8 bank2_sensors; /* actual number of bank2 sensors found */ + u8 bank2_value[ABIT_UGURU_MAX_BANK2_SENSORS]; + u8 bank2_settings[ABIT_UGURU_MAX_BANK2_SENSORS][2]; /* flags, min */ + + /* Alarms 2 bytes for bank1, 1 byte for bank2 */ + u8 alarms[3]; + + /* Fan PWM (speed control) 5 bytes per PWM */ + u8 pwms; /* actual number of pwms found */ + u8 pwm_settings[ABIT_UGURU_MAX_PWMS][5]; +}; + +static const char *never_happen = "This should never happen."; +static const char *report_this = + "Please report this to the abituguru maintainer (see MAINTAINERS)"; + +/* wait till the uguru is in the specified state */ +static int abituguru_wait(struct abituguru_data *data, u8 state) +{ + int timeout = ABIT_UGURU_WAIT_TIMEOUT; + + while (inb_p(data->addr + ABIT_UGURU_DATA) != state) { + timeout--; + if (timeout == 0) + return -EBUSY; + /* + * sleep a bit before our last few tries, see the comment on + * this where ABIT_UGURU_WAIT_TIMEOUT_SLEEP is defined. + */ + if (timeout <= ABIT_UGURU_WAIT_TIMEOUT_SLEEP) + msleep(0); + } + return 0; +} + +/* Put the uguru in ready for input state */ +static int abituguru_ready(struct abituguru_data *data) +{ + int timeout = ABIT_UGURU_READY_TIMEOUT; + + if (data->uguru_ready) + return 0; + + /* Reset? / Prepare for next read/write cycle */ + outb(0x00, data->addr + ABIT_UGURU_DATA); + + /* Wait till the uguru is ready */ + if (abituguru_wait(data, ABIT_UGURU_STATUS_READY)) { + ABIT_UGURU_DEBUG(1, + "timeout exceeded waiting for ready state\n"); + return -EIO; + } + + /* Cmd port MUST be read now and should contain 0xAC */ + while (inb_p(data->addr + ABIT_UGURU_CMD) != 0xAC) { + timeout--; + if (timeout == 0) { + ABIT_UGURU_DEBUG(1, + "CMD reg does not hold 0xAC after ready command\n"); + return -EIO; + } + msleep(0); + } + + /* + * After this the ABIT_UGURU_DATA port should contain + * ABIT_UGURU_STATUS_INPUT + */ + timeout = ABIT_UGURU_READY_TIMEOUT; + while (inb_p(data->addr + ABIT_UGURU_DATA) != ABIT_UGURU_STATUS_INPUT) { + timeout--; + if (timeout == 0) { + ABIT_UGURU_DEBUG(1, + "state != more input after ready command\n"); + return -EIO; + } + msleep(0); + } + + data->uguru_ready = 1; + return 0; +} + +/* + * Send the bank and then sensor address to the uGuru for the next read/write + * cycle. This function gets called as the first part of a read/write by + * abituguru_read and abituguru_write. This function should never be + * called by any other function. + */ +static int abituguru_send_address(struct abituguru_data *data, + u8 bank_addr, u8 sensor_addr, int retries) +{ + /* + * assume the caller does error handling itself if it has not requested + * any retries, and thus be quiet. + */ + int report_errors = retries; + + for (;;) { + /* + * Make sure the uguru is ready and then send the bank address, + * after this the uguru is no longer "ready". + */ + if (abituguru_ready(data) != 0) + return -EIO; + outb(bank_addr, data->addr + ABIT_UGURU_DATA); + data->uguru_ready = 0; + + /* + * Wait till the uguru is ABIT_UGURU_STATUS_INPUT state again + * and send the sensor addr + */ + if (abituguru_wait(data, ABIT_UGURU_STATUS_INPUT)) { + if (retries) { + ABIT_UGURU_DEBUG(3, "timeout exceeded " + "waiting for more input state, %d " + "tries remaining\n", retries); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(ABIT_UGURU_RETRY_DELAY); + retries--; + continue; + } + if (report_errors) + ABIT_UGURU_DEBUG(1, "timeout exceeded " + "waiting for more input state " + "(bank: %d)\n", (int)bank_addr); + return -EBUSY; + } + outb(sensor_addr, data->addr + ABIT_UGURU_CMD); + return 0; + } +} + +/* + * Read count bytes from sensor sensor_addr in bank bank_addr and store the + * result in buf, retry the send address part of the read retries times. + */ +static int abituguru_read(struct abituguru_data *data, + u8 bank_addr, u8 sensor_addr, u8 *buf, int count, int retries) +{ + int i; + + /* Send the address */ + i = abituguru_send_address(data, bank_addr, sensor_addr, retries); + if (i) + return i; + + /* And read the data */ + for (i = 0; i < count; i++) { + if (abituguru_wait(data, ABIT_UGURU_STATUS_READ)) { + ABIT_UGURU_DEBUG(retries ? 1 : 3, + "timeout exceeded waiting for " + "read state (bank: %d, sensor: %d)\n", + (int)bank_addr, (int)sensor_addr); + break; + } + buf[i] = inb(data->addr + ABIT_UGURU_CMD); + } + + /* Last put the chip back in ready state */ + abituguru_ready(data); + + return i; +} + +/* + * Write count bytes from buf to sensor sensor_addr in bank bank_addr, the send + * address part of the write is always retried ABIT_UGURU_MAX_RETRIES times. + */ +static int abituguru_write(struct abituguru_data *data, + u8 bank_addr, u8 sensor_addr, u8 *buf, int count) +{ + /* + * We use the ready timeout as we have to wait for 0xAC just like the + * ready function + */ + int i, timeout = ABIT_UGURU_READY_TIMEOUT; + + /* Send the address */ + i = abituguru_send_address(data, bank_addr, sensor_addr, + ABIT_UGURU_MAX_RETRIES); + if (i) + return i; + + /* And write the data */ + for (i = 0; i < count; i++) { + if (abituguru_wait(data, ABIT_UGURU_STATUS_WRITE)) { + ABIT_UGURU_DEBUG(1, "timeout exceeded waiting for " + "write state (bank: %d, sensor: %d)\n", + (int)bank_addr, (int)sensor_addr); + break; + } + outb(buf[i], data->addr + ABIT_UGURU_CMD); + } + + /* + * Now we need to wait till the chip is ready to be read again, + * so that we can read 0xAC as confirmation that our write has + * succeeded. + */ + if (abituguru_wait(data, ABIT_UGURU_STATUS_READ)) { + ABIT_UGURU_DEBUG(1, "timeout exceeded waiting for read state " + "after write (bank: %d, sensor: %d)\n", (int)bank_addr, + (int)sensor_addr); + return -EIO; + } + + /* Cmd port MUST be read now and should contain 0xAC */ + while (inb_p(data->addr + ABIT_UGURU_CMD) != 0xAC) { + timeout--; + if (timeout == 0) { + ABIT_UGURU_DEBUG(1, "CMD reg does not hold 0xAC after " + "write (bank: %d, sensor: %d)\n", + (int)bank_addr, (int)sensor_addr); + return -EIO; + } + msleep(0); + } + + /* Last put the chip back in ready state */ + abituguru_ready(data); + + return i; +} + +/* + * Detect sensor type. Temp and Volt sensors are enabled with + * different masks and will ignore enable masks not meant for them. + * This enables us to test what kind of sensor we're dealing with. + * By setting the alarm thresholds so that we will always get an + * alarm for sensor type X and then enabling the sensor as sensor type + * X, if we then get an alarm it is a sensor of type X. + */ +static int +abituguru_detect_bank1_sensor_type(struct abituguru_data *data, + u8 sensor_addr) +{ + u8 val, test_flag, buf[3]; + int i, ret = -ENODEV; /* error is the most common used retval :| */ + + /* If overriden by the user return the user selected type */ + if (bank1_types[sensor_addr] >= ABIT_UGURU_IN_SENSOR && + bank1_types[sensor_addr] <= ABIT_UGURU_NC) { + ABIT_UGURU_DEBUG(2, "assuming sensor type %d for bank1 sensor " + "%d because of \"bank1_types\" module param\n", + bank1_types[sensor_addr], (int)sensor_addr); + return bank1_types[sensor_addr]; + } + + /* First read the sensor and the current settings */ + if (abituguru_read(data, ABIT_UGURU_SENSOR_BANK1, sensor_addr, &val, + 1, ABIT_UGURU_MAX_RETRIES) != 1) + return -ENODEV; + + /* Test val is sane / usable for sensor type detection. */ + if ((val < 10u) || (val > 250u)) { + pr_warn("bank1-sensor: %d reading (%d) too close to limits, " + "unable to determine sensor type, skipping sensor\n", + (int)sensor_addr, (int)val); + /* + * assume no sensor is there for sensors for which we can't + * determine the sensor type because their reading is too close + * to their limits, this usually means no sensor is there. + */ + return ABIT_UGURU_NC; + } + + ABIT_UGURU_DEBUG(2, "testing bank1 sensor %d\n", (int)sensor_addr); + /* + * Volt sensor test, enable volt low alarm, set min value ridiculously + * high, or vica versa if the reading is very high. If its a volt + * sensor this should always give us an alarm. + */ + if (val <= 240u) { + buf[0] = ABIT_UGURU_VOLT_LOW_ALARM_ENABLE; + buf[1] = 245; + buf[2] = 250; + test_flag = ABIT_UGURU_VOLT_LOW_ALARM_FLAG; + } else { + buf[0] = ABIT_UGURU_VOLT_HIGH_ALARM_ENABLE; + buf[1] = 5; + buf[2] = 10; + test_flag = ABIT_UGURU_VOLT_HIGH_ALARM_FLAG; + } + + if (abituguru_write(data, ABIT_UGURU_SENSOR_BANK1 + 2, sensor_addr, + buf, 3) != 3) + goto abituguru_detect_bank1_sensor_type_exit; + /* + * Now we need 20 ms to give the uguru time to read the sensors + * and raise a voltage alarm + */ + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/50); + /* Check for alarm and check the alarm is a volt low alarm. */ + if (abituguru_read(data, ABIT_UGURU_ALARM_BANK, 0, buf, 3, + ABIT_UGURU_MAX_RETRIES) != 3) + goto abituguru_detect_bank1_sensor_type_exit; + if (buf[sensor_addr/8] & (0x01 << (sensor_addr % 8))) { + if (abituguru_read(data, ABIT_UGURU_SENSOR_BANK1 + 1, + sensor_addr, buf, 3, + ABIT_UGURU_MAX_RETRIES) != 3) + goto abituguru_detect_bank1_sensor_type_exit; + if (buf[0] & test_flag) { + ABIT_UGURU_DEBUG(2, " found volt sensor\n"); + ret = ABIT_UGURU_IN_SENSOR; + goto abituguru_detect_bank1_sensor_type_exit; + } else + ABIT_UGURU_DEBUG(2, " alarm raised during volt " + "sensor test, but volt range flag not set\n"); + } else + ABIT_UGURU_DEBUG(2, " alarm not raised during volt sensor " + "test\n"); + + /* + * Temp sensor test, enable sensor as a temp sensor, set beep value + * ridiculously low (but not too low, otherwise uguru ignores it). + * If its a temp sensor this should always give us an alarm. + */ + buf[0] = ABIT_UGURU_TEMP_HIGH_ALARM_ENABLE; + buf[1] = 5; + buf[2] = 10; + if (abituguru_write(data, ABIT_UGURU_SENSOR_BANK1 + 2, sensor_addr, + buf, 3) != 3) + goto abituguru_detect_bank1_sensor_type_exit; + /* + * Now we need 50 ms to give the uguru time to read the sensors + * and raise a temp alarm + */ + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/20); + /* Check for alarm and check the alarm is a temp high alarm. */ + if (abituguru_read(data, ABIT_UGURU_ALARM_BANK, 0, buf, 3, + ABIT_UGURU_MAX_RETRIES) != 3) + goto abituguru_detect_bank1_sensor_type_exit; + if (buf[sensor_addr/8] & (0x01 << (sensor_addr % 8))) { + if (abituguru_read(data, ABIT_UGURU_SENSOR_BANK1 + 1, + sensor_addr, buf, 3, + ABIT_UGURU_MAX_RETRIES) != 3) + goto abituguru_detect_bank1_sensor_type_exit; + if (buf[0] & ABIT_UGURU_TEMP_HIGH_ALARM_FLAG) { + ABIT_UGURU_DEBUG(2, " found temp sensor\n"); + ret = ABIT_UGURU_TEMP_SENSOR; + goto abituguru_detect_bank1_sensor_type_exit; + } else + ABIT_UGURU_DEBUG(2, " alarm raised during temp " + "sensor test, but temp high flag not set\n"); + } else + ABIT_UGURU_DEBUG(2, " alarm not raised during temp sensor " + "test\n"); + + ret = ABIT_UGURU_NC; +abituguru_detect_bank1_sensor_type_exit: + /* + * Restore original settings, failing here is really BAD, it has been + * reported that some BIOS-es hang when entering the uGuru menu with + * invalid settings present in the uGuru, so we try this 3 times. + */ + for (i = 0; i < 3; i++) + if (abituguru_write(data, ABIT_UGURU_SENSOR_BANK1 + 2, + sensor_addr, data->bank1_settings[sensor_addr], + 3) == 3) + break; + if (i == 3) { + pr_err("Fatal error could not restore original settings. %s %s\n", + never_happen, report_this); + return -ENODEV; + } + return ret; +} + +/* + * These functions try to find out how many sensors there are in bank2 and how + * many pwms there are. The purpose of this is to make sure that we don't give + * the user the possibility to change settings for non-existent sensors / pwm. + * The uGuru will happily read / write whatever memory happens to be after the + * memory storing the PWM settings when reading/writing to a PWM which is not + * there. Notice even if we detect a PWM which doesn't exist we normally won't + * write to it, unless the user tries to change the settings. + * + * Although the uGuru allows reading (settings) from non existing bank2 + * sensors, my version of the uGuru does seem to stop writing to them, the + * write function above aborts in this case with: + * "CMD reg does not hold 0xAC after write" + * + * Notice these 2 tests are non destructive iow read-only tests, otherwise + * they would defeat their purpose. Although for the bank2_sensors detection a + * read/write test would be feasible because of the reaction above, I've + * however opted to stay on the safe side. + */ +static void +abituguru_detect_no_bank2_sensors(struct abituguru_data *data) +{ + int i; + + if (fan_sensors > 0 && fan_sensors <= ABIT_UGURU_MAX_BANK2_SENSORS) { + data->bank2_sensors = fan_sensors; + ABIT_UGURU_DEBUG(2, "assuming %d fan sensors because of " + "\"fan_sensors\" module param\n", + (int)data->bank2_sensors); + return; + } + + ABIT_UGURU_DEBUG(2, "detecting number of fan sensors\n"); + for (i = 0; i < ABIT_UGURU_MAX_BANK2_SENSORS; i++) { + /* + * 0x89 are the known used bits: + * -0x80 enable shutdown + * -0x08 enable beep + * -0x01 enable alarm + * All other bits should be 0, but on some motherboards + * 0x40 (bit 6) is also high for some of the fans?? + */ + if (data->bank2_settings[i][0] & ~0xC9) { + ABIT_UGURU_DEBUG(2, " bank2 sensor %d does not seem " + "to be a fan sensor: settings[0] = %02X\n", + i, (unsigned int)data->bank2_settings[i][0]); + break; + } + + /* check if the threshold is within the allowed range */ + if (data->bank2_settings[i][1] < + abituguru_bank2_min_threshold) { + ABIT_UGURU_DEBUG(2, " bank2 sensor %d does not seem " + "to be a fan sensor: the threshold (%d) is " + "below the minimum (%d)\n", i, + (int)data->bank2_settings[i][1], + (int)abituguru_bank2_min_threshold); + break; + } + if (data->bank2_settings[i][1] > + abituguru_bank2_max_threshold) { + ABIT_UGURU_DEBUG(2, " bank2 sensor %d does not seem " + "to be a fan sensor: the threshold (%d) is " + "above the maximum (%d)\n", i, + (int)data->bank2_settings[i][1], + (int)abituguru_bank2_max_threshold); + break; + } + } + + data->bank2_sensors = i; + ABIT_UGURU_DEBUG(2, " found: %d fan sensors\n", + (int)data->bank2_sensors); +} + +static void +abituguru_detect_no_pwms(struct abituguru_data *data) +{ + int i, j; + + if (pwms > 0 && pwms <= ABIT_UGURU_MAX_PWMS) { + data->pwms = pwms; + ABIT_UGURU_DEBUG(2, "assuming %d PWM outputs because of " + "\"pwms\" module param\n", (int)data->pwms); + return; + } + + ABIT_UGURU_DEBUG(2, "detecting number of PWM outputs\n"); + for (i = 0; i < ABIT_UGURU_MAX_PWMS; i++) { + /* + * 0x80 is the enable bit and the low + * nibble is which temp sensor to use, + * the other bits should be 0 + */ + if (data->pwm_settings[i][0] & ~0x8F) { + ABIT_UGURU_DEBUG(2, " pwm channel %d does not seem " + "to be a pwm channel: settings[0] = %02X\n", + i, (unsigned int)data->pwm_settings[i][0]); + break; + } + + /* + * the low nibble must correspond to one of the temp sensors + * we've found + */ + for (j = 0; j < data->bank1_sensors[ABIT_UGURU_TEMP_SENSOR]; + j++) { + if (data->bank1_address[ABIT_UGURU_TEMP_SENSOR][j] == + (data->pwm_settings[i][0] & 0x0F)) + break; + } + if (j == data->bank1_sensors[ABIT_UGURU_TEMP_SENSOR]) { + ABIT_UGURU_DEBUG(2, " pwm channel %d does not seem " + "to be a pwm channel: %d is not a valid temp " + "sensor address\n", i, + data->pwm_settings[i][0] & 0x0F); + break; + } + + /* check if all other settings are within the allowed range */ + for (j = 1; j < 5; j++) { + u8 min; + /* special case pwm1 min pwm% */ + if ((i == 0) && ((j == 1) || (j == 2))) + min = 77; + else + min = abituguru_pwm_min[j]; + if (data->pwm_settings[i][j] < min) { + ABIT_UGURU_DEBUG(2, " pwm channel %d does " + "not seem to be a pwm channel: " + "setting %d (%d) is below the minimum " + "value (%d)\n", i, j, + (int)data->pwm_settings[i][j], + (int)min); + goto abituguru_detect_no_pwms_exit; + } + if (data->pwm_settings[i][j] > abituguru_pwm_max[j]) { + ABIT_UGURU_DEBUG(2, " pwm channel %d does " + "not seem to be a pwm channel: " + "setting %d (%d) is above the maximum " + "value (%d)\n", i, j, + (int)data->pwm_settings[i][j], + (int)abituguru_pwm_max[j]); + goto abituguru_detect_no_pwms_exit; + } + } + + /* check that min temp < max temp and min pwm < max pwm */ + if (data->pwm_settings[i][1] >= data->pwm_settings[i][2]) { + ABIT_UGURU_DEBUG(2, " pwm channel %d does not seem " + "to be a pwm channel: min pwm (%d) >= " + "max pwm (%d)\n", i, + (int)data->pwm_settings[i][1], + (int)data->pwm_settings[i][2]); + break; + } + if (data->pwm_settings[i][3] >= data->pwm_settings[i][4]) { + ABIT_UGURU_DEBUG(2, " pwm channel %d does not seem " + "to be a pwm channel: min temp (%d) >= " + "max temp (%d)\n", i, + (int)data->pwm_settings[i][3], + (int)data->pwm_settings[i][4]); + break; + } + } + +abituguru_detect_no_pwms_exit: + data->pwms = i; + ABIT_UGURU_DEBUG(2, " found: %d PWM outputs\n", (int)data->pwms); +} + +/* + * Following are the sysfs callback functions. These functions expect: + * sensor_device_attribute_2->index: sensor address/offset in the bank + * sensor_device_attribute_2->nr: register offset, bitmask or NA. + */ +static struct abituguru_data *abituguru_update_device(struct device *dev); + +static ssize_t show_bank1_value(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = abituguru_update_device(dev); + if (!data) + return -EIO; + return sprintf(buf, "%d\n", (data->bank1_value[attr->index] * + data->bank1_max_value[attr->index] + 128) / 255); +} + +static ssize_t show_bank1_setting(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", + (data->bank1_settings[attr->index][attr->nr] * + data->bank1_max_value[attr->index] + 128) / 255); +} + +static ssize_t show_bank2_value(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = abituguru_update_device(dev); + if (!data) + return -EIO; + return sprintf(buf, "%d\n", (data->bank2_value[attr->index] * + ABIT_UGURU_FAN_MAX + 128) / 255); +} + +static ssize_t show_bank2_setting(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", + (data->bank2_settings[attr->index][attr->nr] * + ABIT_UGURU_FAN_MAX + 128) / 255); +} + +static ssize_t store_bank1_setting(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = dev_get_drvdata(dev); + unsigned long val; + ssize_t ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + ret = count; + val = (val * 255 + data->bank1_max_value[attr->index] / 2) / + data->bank1_max_value[attr->index]; + if (val > 255) + return -EINVAL; + + mutex_lock(&data->update_lock); + if (data->bank1_settings[attr->index][attr->nr] != val) { + u8 orig_val = data->bank1_settings[attr->index][attr->nr]; + data->bank1_settings[attr->index][attr->nr] = val; + if (abituguru_write(data, ABIT_UGURU_SENSOR_BANK1 + 2, + attr->index, data->bank1_settings[attr->index], + 3) <= attr->nr) { + data->bank1_settings[attr->index][attr->nr] = orig_val; + ret = -EIO; + } + } + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t store_bank2_setting(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = dev_get_drvdata(dev); + unsigned long val; + ssize_t ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + ret = count; + val = (val * 255 + ABIT_UGURU_FAN_MAX / 2) / ABIT_UGURU_FAN_MAX; + + /* this check can be done before taking the lock */ + if (val < abituguru_bank2_min_threshold || + val > abituguru_bank2_max_threshold) + return -EINVAL; + + mutex_lock(&data->update_lock); + if (data->bank2_settings[attr->index][attr->nr] != val) { + u8 orig_val = data->bank2_settings[attr->index][attr->nr]; + data->bank2_settings[attr->index][attr->nr] = val; + if (abituguru_write(data, ABIT_UGURU_SENSOR_BANK2 + 2, + attr->index, data->bank2_settings[attr->index], + 2) <= attr->nr) { + data->bank2_settings[attr->index][attr->nr] = orig_val; + ret = -EIO; + } + } + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t show_bank1_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = abituguru_update_device(dev); + if (!data) + return -EIO; + /* + * See if the alarm bit for this sensor is set, and if the + * alarm matches the type of alarm we're looking for (for volt + * it can be either low or high). The type is stored in a few + * readonly bits in the settings part of the relevant sensor. + * The bitmask of the type is passed to us in attr->nr. + */ + if ((data->alarms[attr->index / 8] & (0x01 << (attr->index % 8))) && + (data->bank1_settings[attr->index][0] & attr->nr)) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t show_bank2_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = abituguru_update_device(dev); + if (!data) + return -EIO; + if (data->alarms[2] & (0x01 << attr->index)) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t show_bank1_mask(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = dev_get_drvdata(dev); + if (data->bank1_settings[attr->index][0] & attr->nr) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t show_bank2_mask(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = dev_get_drvdata(dev); + if (data->bank2_settings[attr->index][0] & attr->nr) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t store_bank1_mask(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = dev_get_drvdata(dev); + ssize_t ret; + u8 orig_val; + unsigned long mask; + + ret = kstrtoul(buf, 10, &mask); + if (ret) + return ret; + + ret = count; + mutex_lock(&data->update_lock); + orig_val = data->bank1_settings[attr->index][0]; + + if (mask) + data->bank1_settings[attr->index][0] |= attr->nr; + else + data->bank1_settings[attr->index][0] &= ~attr->nr; + + if ((data->bank1_settings[attr->index][0] != orig_val) && + (abituguru_write(data, + ABIT_UGURU_SENSOR_BANK1 + 2, attr->index, + data->bank1_settings[attr->index], 3) < 1)) { + data->bank1_settings[attr->index][0] = orig_val; + ret = -EIO; + } + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t store_bank2_mask(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = dev_get_drvdata(dev); + ssize_t ret; + u8 orig_val; + unsigned long mask; + + ret = kstrtoul(buf, 10, &mask); + if (ret) + return ret; + + ret = count; + mutex_lock(&data->update_lock); + orig_val = data->bank2_settings[attr->index][0]; + + if (mask) + data->bank2_settings[attr->index][0] |= attr->nr; + else + data->bank2_settings[attr->index][0] &= ~attr->nr; + + if ((data->bank2_settings[attr->index][0] != orig_val) && + (abituguru_write(data, + ABIT_UGURU_SENSOR_BANK2 + 2, attr->index, + data->bank2_settings[attr->index], 2) < 1)) { + data->bank2_settings[attr->index][0] = orig_val; + ret = -EIO; + } + mutex_unlock(&data->update_lock); + return ret; +} + +/* Fan PWM (speed control) */ +static ssize_t show_pwm_setting(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", data->pwm_settings[attr->index][attr->nr] * + abituguru_pwm_settings_multiplier[attr->nr]); +} + +static ssize_t store_pwm_setting(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = dev_get_drvdata(dev); + u8 min; + unsigned long val; + ssize_t ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + ret = count; + val = (val + abituguru_pwm_settings_multiplier[attr->nr] / 2) / + abituguru_pwm_settings_multiplier[attr->nr]; + + /* special case pwm1 min pwm% */ + if ((attr->index == 0) && ((attr->nr == 1) || (attr->nr == 2))) + min = 77; + else + min = abituguru_pwm_min[attr->nr]; + + /* this check can be done before taking the lock */ + if (val < min || val > abituguru_pwm_max[attr->nr]) + return -EINVAL; + + mutex_lock(&data->update_lock); + /* this check needs to be done after taking the lock */ + if ((attr->nr & 1) && + (val >= data->pwm_settings[attr->index][attr->nr + 1])) + ret = -EINVAL; + else if (!(attr->nr & 1) && + (val <= data->pwm_settings[attr->index][attr->nr - 1])) + ret = -EINVAL; + else if (data->pwm_settings[attr->index][attr->nr] != val) { + u8 orig_val = data->pwm_settings[attr->index][attr->nr]; + data->pwm_settings[attr->index][attr->nr] = val; + if (abituguru_write(data, ABIT_UGURU_FAN_PWM + 1, + attr->index, data->pwm_settings[attr->index], + 5) <= attr->nr) { + data->pwm_settings[attr->index][attr->nr] = + orig_val; + ret = -EIO; + } + } + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t show_pwm_sensor(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = dev_get_drvdata(dev); + int i; + /* + * We need to walk to the temp sensor addresses to find what + * the userspace id of the configured temp sensor is. + */ + for (i = 0; i < data->bank1_sensors[ABIT_UGURU_TEMP_SENSOR]; i++) + if (data->bank1_address[ABIT_UGURU_TEMP_SENSOR][i] == + (data->pwm_settings[attr->index][0] & 0x0F)) + return sprintf(buf, "%d\n", i+1); + + return -ENXIO; +} + +static ssize_t store_pwm_sensor(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = dev_get_drvdata(dev); + ssize_t ret; + unsigned long val; + u8 orig_val; + u8 address; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + if (val == 0 || val > data->bank1_sensors[ABIT_UGURU_TEMP_SENSOR]) + return -EINVAL; + + val -= 1; + ret = count; + mutex_lock(&data->update_lock); + orig_val = data->pwm_settings[attr->index][0]; + address = data->bank1_address[ABIT_UGURU_TEMP_SENSOR][val]; + data->pwm_settings[attr->index][0] &= 0xF0; + data->pwm_settings[attr->index][0] |= address; + if (data->pwm_settings[attr->index][0] != orig_val) { + if (abituguru_write(data, ABIT_UGURU_FAN_PWM + 1, attr->index, + data->pwm_settings[attr->index], 5) < 1) { + data->pwm_settings[attr->index][0] = orig_val; + ret = -EIO; + } + } + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = dev_get_drvdata(dev); + int res = 0; + if (data->pwm_settings[attr->index][0] & ABIT_UGURU_FAN_PWM_ENABLE) + res = 2; + return sprintf(buf, "%d\n", res); +} + +static ssize_t store_pwm_enable(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru_data *data = dev_get_drvdata(dev); + u8 orig_val; + ssize_t ret; + unsigned long user_val; + + ret = kstrtoul(buf, 10, &user_val); + if (ret) + return ret; + + ret = count; + mutex_lock(&data->update_lock); + orig_val = data->pwm_settings[attr->index][0]; + switch (user_val) { + case 0: + data->pwm_settings[attr->index][0] &= + ~ABIT_UGURU_FAN_PWM_ENABLE; + break; + case 2: + data->pwm_settings[attr->index][0] |= ABIT_UGURU_FAN_PWM_ENABLE; + break; + default: + ret = -EINVAL; + } + if ((data->pwm_settings[attr->index][0] != orig_val) && + (abituguru_write(data, ABIT_UGURU_FAN_PWM + 1, + attr->index, data->pwm_settings[attr->index], + 5) < 1)) { + data->pwm_settings[attr->index][0] = orig_val; + ret = -EIO; + } + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t show_name(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + return sprintf(buf, "%s\n", ABIT_UGURU_NAME); +} + +/* Sysfs attr templates, the real entries are generated automatically. */ +static const +struct sensor_device_attribute_2 abituguru_sysfs_bank1_templ[2][9] = { + { + SENSOR_ATTR_2(in%d_input, 0444, show_bank1_value, NULL, 0, 0), + SENSOR_ATTR_2(in%d_min, 0644, show_bank1_setting, + store_bank1_setting, 1, 0), + SENSOR_ATTR_2(in%d_min_alarm, 0444, show_bank1_alarm, NULL, + ABIT_UGURU_VOLT_LOW_ALARM_FLAG, 0), + SENSOR_ATTR_2(in%d_max, 0644, show_bank1_setting, + store_bank1_setting, 2, 0), + SENSOR_ATTR_2(in%d_max_alarm, 0444, show_bank1_alarm, NULL, + ABIT_UGURU_VOLT_HIGH_ALARM_FLAG, 0), + SENSOR_ATTR_2(in%d_beep, 0644, show_bank1_mask, + store_bank1_mask, ABIT_UGURU_BEEP_ENABLE, 0), + SENSOR_ATTR_2(in%d_shutdown, 0644, show_bank1_mask, + store_bank1_mask, ABIT_UGURU_SHUTDOWN_ENABLE, 0), + SENSOR_ATTR_2(in%d_min_alarm_enable, 0644, show_bank1_mask, + store_bank1_mask, ABIT_UGURU_VOLT_LOW_ALARM_ENABLE, 0), + SENSOR_ATTR_2(in%d_max_alarm_enable, 0644, show_bank1_mask, + store_bank1_mask, ABIT_UGURU_VOLT_HIGH_ALARM_ENABLE, 0), + }, { + SENSOR_ATTR_2(temp%d_input, 0444, show_bank1_value, NULL, 0, 0), + SENSOR_ATTR_2(temp%d_alarm, 0444, show_bank1_alarm, NULL, + ABIT_UGURU_TEMP_HIGH_ALARM_FLAG, 0), + SENSOR_ATTR_2(temp%d_max, 0644, show_bank1_setting, + store_bank1_setting, 1, 0), + SENSOR_ATTR_2(temp%d_crit, 0644, show_bank1_setting, + store_bank1_setting, 2, 0), + SENSOR_ATTR_2(temp%d_beep, 0644, show_bank1_mask, + store_bank1_mask, ABIT_UGURU_BEEP_ENABLE, 0), + SENSOR_ATTR_2(temp%d_shutdown, 0644, show_bank1_mask, + store_bank1_mask, ABIT_UGURU_SHUTDOWN_ENABLE, 0), + SENSOR_ATTR_2(temp%d_alarm_enable, 0644, show_bank1_mask, + store_bank1_mask, ABIT_UGURU_TEMP_HIGH_ALARM_ENABLE, 0), + } +}; + +static const struct sensor_device_attribute_2 abituguru_sysfs_fan_templ[6] = { + SENSOR_ATTR_2(fan%d_input, 0444, show_bank2_value, NULL, 0, 0), + SENSOR_ATTR_2(fan%d_alarm, 0444, show_bank2_alarm, NULL, 0, 0), + SENSOR_ATTR_2(fan%d_min, 0644, show_bank2_setting, + store_bank2_setting, 1, 0), + SENSOR_ATTR_2(fan%d_beep, 0644, show_bank2_mask, + store_bank2_mask, ABIT_UGURU_BEEP_ENABLE, 0), + SENSOR_ATTR_2(fan%d_shutdown, 0644, show_bank2_mask, + store_bank2_mask, ABIT_UGURU_SHUTDOWN_ENABLE, 0), + SENSOR_ATTR_2(fan%d_alarm_enable, 0644, show_bank2_mask, + store_bank2_mask, ABIT_UGURU_FAN_LOW_ALARM_ENABLE, 0), +}; + +static const struct sensor_device_attribute_2 abituguru_sysfs_pwm_templ[6] = { + SENSOR_ATTR_2(pwm%d_enable, 0644, show_pwm_enable, + store_pwm_enable, 0, 0), + SENSOR_ATTR_2(pwm%d_auto_channels_temp, 0644, show_pwm_sensor, + store_pwm_sensor, 0, 0), + SENSOR_ATTR_2(pwm%d_auto_point1_pwm, 0644, show_pwm_setting, + store_pwm_setting, 1, 0), + SENSOR_ATTR_2(pwm%d_auto_point2_pwm, 0644, show_pwm_setting, + store_pwm_setting, 2, 0), + SENSOR_ATTR_2(pwm%d_auto_point1_temp, 0644, show_pwm_setting, + store_pwm_setting, 3, 0), + SENSOR_ATTR_2(pwm%d_auto_point2_temp, 0644, show_pwm_setting, + store_pwm_setting, 4, 0), +}; + +static struct sensor_device_attribute_2 abituguru_sysfs_attr[] = { + SENSOR_ATTR_2(name, 0444, show_name, NULL, 0, 0), +}; + +static int abituguru_probe(struct platform_device *pdev) +{ + struct abituguru_data *data; + int i, j, used, sysfs_names_free, sysfs_attr_i, res = -ENODEV; + char *sysfs_filename; + + /* + * El weirdo probe order, to keep the sysfs order identical to the + * BIOS and window-appliction listing order. + */ + const u8 probe_order[ABIT_UGURU_MAX_BANK1_SENSORS] = { + 0x00, 0x01, 0x03, 0x04, 0x0A, 0x08, 0x0E, 0x02, + 0x09, 0x06, 0x05, 0x0B, 0x0F, 0x0D, 0x07, 0x0C }; + + data = devm_kzalloc(&pdev->dev, sizeof(struct abituguru_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start; + mutex_init(&data->update_lock); + platform_set_drvdata(pdev, data); + + /* See if the uGuru is ready */ + if (inb_p(data->addr + ABIT_UGURU_DATA) == ABIT_UGURU_STATUS_INPUT) + data->uguru_ready = 1; + + /* + * Completely read the uGuru this has 2 purposes: + * - testread / see if one really is there. + * - make an in memory copy of all the uguru settings for future use. + */ + if (abituguru_read(data, ABIT_UGURU_ALARM_BANK, 0, + data->alarms, 3, ABIT_UGURU_MAX_RETRIES) != 3) + goto abituguru_probe_error; + + for (i = 0; i < ABIT_UGURU_MAX_BANK1_SENSORS; i++) { + if (abituguru_read(data, ABIT_UGURU_SENSOR_BANK1, i, + &data->bank1_value[i], 1, + ABIT_UGURU_MAX_RETRIES) != 1) + goto abituguru_probe_error; + if (abituguru_read(data, ABIT_UGURU_SENSOR_BANK1+1, i, + data->bank1_settings[i], 3, + ABIT_UGURU_MAX_RETRIES) != 3) + goto abituguru_probe_error; + } + /* + * Note: We don't know how many bank2 sensors / pwms there really are, + * but in order to "detect" this we need to read the maximum amount + * anyways. If we read sensors/pwms not there we'll just read crap + * this can't hurt. We need the detection because we don't want + * unwanted writes, which will hurt! + */ + for (i = 0; i < ABIT_UGURU_MAX_BANK2_SENSORS; i++) { + if (abituguru_read(data, ABIT_UGURU_SENSOR_BANK2, i, + &data->bank2_value[i], 1, + ABIT_UGURU_MAX_RETRIES) != 1) + goto abituguru_probe_error; + if (abituguru_read(data, ABIT_UGURU_SENSOR_BANK2+1, i, + data->bank2_settings[i], 2, + ABIT_UGURU_MAX_RETRIES) != 2) + goto abituguru_probe_error; + } + for (i = 0; i < ABIT_UGURU_MAX_PWMS; i++) { + if (abituguru_read(data, ABIT_UGURU_FAN_PWM, i, + data->pwm_settings[i], 5, + ABIT_UGURU_MAX_RETRIES) != 5) + goto abituguru_probe_error; + } + data->last_updated = jiffies; + + /* Detect sensor types and fill the sysfs attr for bank1 */ + sysfs_attr_i = 0; + sysfs_filename = data->sysfs_names; + sysfs_names_free = ABITUGURU_SYSFS_NAMES_LENGTH; + for (i = 0; i < ABIT_UGURU_MAX_BANK1_SENSORS; i++) { + res = abituguru_detect_bank1_sensor_type(data, probe_order[i]); + if (res < 0) + goto abituguru_probe_error; + if (res == ABIT_UGURU_NC) + continue; + + /* res 1 (temp) sensors have 7 sysfs entries, 0 (in) 9 */ + for (j = 0; j < (res ? 7 : 9); j++) { + used = snprintf(sysfs_filename, sysfs_names_free, + abituguru_sysfs_bank1_templ[res][j].dev_attr. + attr.name, data->bank1_sensors[res] + res) + + 1; + data->sysfs_attr[sysfs_attr_i] = + abituguru_sysfs_bank1_templ[res][j]; + data->sysfs_attr[sysfs_attr_i].dev_attr.attr.name = + sysfs_filename; + data->sysfs_attr[sysfs_attr_i].index = probe_order[i]; + sysfs_filename += used; + sysfs_names_free -= used; + sysfs_attr_i++; + } + data->bank1_max_value[probe_order[i]] = + abituguru_bank1_max_value[res]; + data->bank1_address[res][data->bank1_sensors[res]] = + probe_order[i]; + data->bank1_sensors[res]++; + } + /* Detect number of sensors and fill the sysfs attr for bank2 (fans) */ + abituguru_detect_no_bank2_sensors(data); + for (i = 0; i < data->bank2_sensors; i++) { + for (j = 0; j < ARRAY_SIZE(abituguru_sysfs_fan_templ); j++) { + used = snprintf(sysfs_filename, sysfs_names_free, + abituguru_sysfs_fan_templ[j].dev_attr.attr.name, + i + 1) + 1; + data->sysfs_attr[sysfs_attr_i] = + abituguru_sysfs_fan_templ[j]; + data->sysfs_attr[sysfs_attr_i].dev_attr.attr.name = + sysfs_filename; + data->sysfs_attr[sysfs_attr_i].index = i; + sysfs_filename += used; + sysfs_names_free -= used; + sysfs_attr_i++; + } + } + /* Detect number of sensors and fill the sysfs attr for pwms */ + abituguru_detect_no_pwms(data); + for (i = 0; i < data->pwms; i++) { + for (j = 0; j < ARRAY_SIZE(abituguru_sysfs_pwm_templ); j++) { + used = snprintf(sysfs_filename, sysfs_names_free, + abituguru_sysfs_pwm_templ[j].dev_attr.attr.name, + i + 1) + 1; + data->sysfs_attr[sysfs_attr_i] = + abituguru_sysfs_pwm_templ[j]; + data->sysfs_attr[sysfs_attr_i].dev_attr.attr.name = + sysfs_filename; + data->sysfs_attr[sysfs_attr_i].index = i; + sysfs_filename += used; + sysfs_names_free -= used; + sysfs_attr_i++; + } + } + /* Fail safe check, this should never happen! */ + if (sysfs_names_free < 0) { + pr_err("Fatal error ran out of space for sysfs attr names. %s %s", + never_happen, report_this); + res = -ENAMETOOLONG; + goto abituguru_probe_error; + } + pr_info("found Abit uGuru\n"); + + /* Register sysfs hooks */ + for (i = 0; i < sysfs_attr_i; i++) { + res = device_create_file(&pdev->dev, + &data->sysfs_attr[i].dev_attr); + if (res) + goto abituguru_probe_error; + } + for (i = 0; i < ARRAY_SIZE(abituguru_sysfs_attr); i++) { + res = device_create_file(&pdev->dev, + &abituguru_sysfs_attr[i].dev_attr); + if (res) + goto abituguru_probe_error; + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (!IS_ERR(data->hwmon_dev)) + return 0; /* success */ + + res = PTR_ERR(data->hwmon_dev); +abituguru_probe_error: + for (i = 0; data->sysfs_attr[i].dev_attr.attr.name; i++) + device_remove_file(&pdev->dev, &data->sysfs_attr[i].dev_attr); + for (i = 0; i < ARRAY_SIZE(abituguru_sysfs_attr); i++) + device_remove_file(&pdev->dev, + &abituguru_sysfs_attr[i].dev_attr); + return res; +} + +static int abituguru_remove(struct platform_device *pdev) +{ + int i; + struct abituguru_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + for (i = 0; data->sysfs_attr[i].dev_attr.attr.name; i++) + device_remove_file(&pdev->dev, &data->sysfs_attr[i].dev_attr); + for (i = 0; i < ARRAY_SIZE(abituguru_sysfs_attr); i++) + device_remove_file(&pdev->dev, + &abituguru_sysfs_attr[i].dev_attr); + + return 0; +} + +static struct abituguru_data *abituguru_update_device(struct device *dev) +{ + int i, err; + struct abituguru_data *data = dev_get_drvdata(dev); + /* fake a complete successful read if no update necessary. */ + char success = 1; + + mutex_lock(&data->update_lock); + if (time_after(jiffies, data->last_updated + HZ)) { + success = 0; + err = abituguru_read(data, ABIT_UGURU_ALARM_BANK, 0, + data->alarms, 3, 0); + if (err != 3) + goto LEAVE_UPDATE; + for (i = 0; i < ABIT_UGURU_MAX_BANK1_SENSORS; i++) { + err = abituguru_read(data, ABIT_UGURU_SENSOR_BANK1, + i, &data->bank1_value[i], 1, 0); + if (err != 1) + goto LEAVE_UPDATE; + err = abituguru_read(data, ABIT_UGURU_SENSOR_BANK1 + 1, + i, data->bank1_settings[i], 3, 0); + if (err != 3) + goto LEAVE_UPDATE; + } + for (i = 0; i < data->bank2_sensors; i++) { + err = abituguru_read(data, ABIT_UGURU_SENSOR_BANK2, i, + &data->bank2_value[i], 1, 0); + if (err != 1) + goto LEAVE_UPDATE; + } + /* success! */ + success = 1; + data->update_timeouts = 0; +LEAVE_UPDATE: + /* handle timeout condition */ + if (!success && (err == -EBUSY || err >= 0)) { + /* No overflow please */ + if (data->update_timeouts < 255u) + data->update_timeouts++; + if (data->update_timeouts <= ABIT_UGURU_MAX_TIMEOUTS) { + ABIT_UGURU_DEBUG(3, "timeout exceeded, will " + "try again next update\n"); + /* Just a timeout, fake a successful read */ + success = 1; + } else + ABIT_UGURU_DEBUG(1, "timeout exceeded %d " + "times waiting for more input state\n", + (int)data->update_timeouts); + } + /* On success set last_updated */ + if (success) + data->last_updated = jiffies; + } + mutex_unlock(&data->update_lock); + + if (success) + return data; + else + return NULL; +} + +#ifdef CONFIG_PM_SLEEP +static int abituguru_suspend(struct device *dev) +{ + struct abituguru_data *data = dev_get_drvdata(dev); + /* + * make sure all communications with the uguru are done and no new + * ones are started + */ + mutex_lock(&data->update_lock); + return 0; +} + +static int abituguru_resume(struct device *dev) +{ + struct abituguru_data *data = dev_get_drvdata(dev); + /* See if the uGuru is still ready */ + if (inb_p(data->addr + ABIT_UGURU_DATA) != ABIT_UGURU_STATUS_INPUT) + data->uguru_ready = 0; + mutex_unlock(&data->update_lock); + return 0; +} + +static SIMPLE_DEV_PM_OPS(abituguru_pm, abituguru_suspend, abituguru_resume); +#define ABIT_UGURU_PM (&abituguru_pm) +#else +#define ABIT_UGURU_PM NULL +#endif /* CONFIG_PM */ + +static struct platform_driver abituguru_driver = { + .driver = { + .name = ABIT_UGURU_NAME, + .pm = ABIT_UGURU_PM, + }, + .probe = abituguru_probe, + .remove = abituguru_remove, +}; + +static int __init abituguru_detect(void) +{ + /* + * See if there is an uguru there. After a reboot uGuru will hold 0x00 + * at DATA and 0xAC, when this driver has already been loaded once + * DATA will hold 0x08. For most uGuru's CMD will hold 0xAC in either + * scenario but some will hold 0x00. + * Some uGuru's initially hold 0x09 at DATA and will only hold 0x08 + * after reading CMD first, so CMD must be read first! + */ + u8 cmd_val = inb_p(ABIT_UGURU_BASE + ABIT_UGURU_CMD); + u8 data_val = inb_p(ABIT_UGURU_BASE + ABIT_UGURU_DATA); + if (((data_val == 0x00) || (data_val == 0x08)) && + ((cmd_val == 0x00) || (cmd_val == 0xAC))) + return ABIT_UGURU_BASE; + + ABIT_UGURU_DEBUG(2, "no Abit uGuru found, data = 0x%02X, cmd = " + "0x%02X\n", (unsigned int)data_val, (unsigned int)cmd_val); + + if (force) { + pr_info("Assuming Abit uGuru is present because of \"force\" parameter\n"); + return ABIT_UGURU_BASE; + } + + /* No uGuru found */ + return -ENODEV; +} + +static struct platform_device *abituguru_pdev; + +static int __init abituguru_init(void) +{ + int address, err; + struct resource res = { .flags = IORESOURCE_IO }; + const char *board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + + /* safety check, refuse to load on non Abit motherboards */ + if (!force && (!board_vendor || + strcmp(board_vendor, "http://www.abit.com.tw/"))) + return -ENODEV; + + address = abituguru_detect(); + if (address < 0) + return address; + + err = platform_driver_register(&abituguru_driver); + if (err) + goto exit; + + abituguru_pdev = platform_device_alloc(ABIT_UGURU_NAME, address); + if (!abituguru_pdev) { + pr_err("Device allocation failed\n"); + err = -ENOMEM; + goto exit_driver_unregister; + } + + res.start = address; + res.end = address + ABIT_UGURU_REGION_LENGTH - 1; + res.name = ABIT_UGURU_NAME; + + err = platform_device_add_resources(abituguru_pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add(abituguru_pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(abituguru_pdev); +exit_driver_unregister: + platform_driver_unregister(&abituguru_driver); +exit: + return err; +} + +static void __exit abituguru_exit(void) +{ + platform_device_unregister(abituguru_pdev); + platform_driver_unregister(&abituguru_driver); +} + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("Abit uGuru Sensor device"); +MODULE_LICENSE("GPL"); + +module_init(abituguru_init); +module_exit(abituguru_exit); diff --git a/drivers/hwmon/abituguru3.c b/drivers/hwmon/abituguru3.c new file mode 100644 index 000000000..3d2a4ae92 --- /dev/null +++ b/drivers/hwmon/abituguru3.c @@ -0,0 +1,1321 @@ +/* + * abituguru3.c + * + * Copyright (c) 2006-2008 Hans de Goede + * Copyright (c) 2008 Alistair John Strachan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/* + * This driver supports the sensor part of revision 3 of the custom Abit uGuru + * chip found on newer Abit uGuru motherboards. Note: because of lack of specs + * only reading the sensors and their settings is supported. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* uGuru3 bank addresses */ +#define ABIT_UGURU3_SETTINGS_BANK 0x01 +#define ABIT_UGURU3_SENSORS_BANK 0x08 +#define ABIT_UGURU3_MISC_BANK 0x09 +#define ABIT_UGURU3_ALARMS_START 0x1E +#define ABIT_UGURU3_SETTINGS_START 0x24 +#define ABIT_UGURU3_VALUES_START 0x80 +#define ABIT_UGURU3_BOARD_ID 0x0A +/* uGuru3 sensor bank flags */ /* Alarm if: */ +#define ABIT_UGURU3_TEMP_HIGH_ALARM_ENABLE 0x01 /* temp over warn */ +#define ABIT_UGURU3_VOLT_HIGH_ALARM_ENABLE 0x02 /* volt over max */ +#define ABIT_UGURU3_VOLT_LOW_ALARM_ENABLE 0x04 /* volt under min */ +#define ABIT_UGURU3_TEMP_HIGH_ALARM_FLAG 0x10 /* temp is over warn */ +#define ABIT_UGURU3_VOLT_HIGH_ALARM_FLAG 0x20 /* volt is over max */ +#define ABIT_UGURU3_VOLT_LOW_ALARM_FLAG 0x40 /* volt is under min */ +#define ABIT_UGURU3_FAN_LOW_ALARM_ENABLE 0x01 /* fan under min */ +#define ABIT_UGURU3_BEEP_ENABLE 0x08 /* beep if alarm */ +#define ABIT_UGURU3_SHUTDOWN_ENABLE 0x80 /* shutdown if alarm */ +/* sensor types */ +#define ABIT_UGURU3_IN_SENSOR 0 +#define ABIT_UGURU3_TEMP_SENSOR 1 +#define ABIT_UGURU3_FAN_SENSOR 2 + +/* + * Timeouts / Retries, if these turn out to need a lot of fiddling we could + * convert them to params. Determined by trial and error. I assume this is + * cpu-speed independent, since the ISA-bus and not the CPU should be the + * bottleneck. + */ +#define ABIT_UGURU3_WAIT_TIMEOUT 250 +/* + * Normally the 0xAC at the end of synchronize() is reported after the + * first read, but sometimes not and we need to poll + */ +#define ABIT_UGURU3_SYNCHRONIZE_TIMEOUT 5 +/* utility macros */ +#define ABIT_UGURU3_NAME "abituguru3" +#define ABIT_UGURU3_DEBUG(format, arg...) \ + do { \ + if (verbose) \ + pr_debug(format , ## arg); \ + } while (0) + +/* Macros to help calculate the sysfs_names array length */ +#define ABIT_UGURU3_MAX_NO_SENSORS 26 +/* + * sum of strlen +1 of: in??_input\0, in??_{min,max}\0, in??_{min,max}_alarm\0, + * in??_{min,max}_alarm_enable\0, in??_beep\0, in??_shutdown\0, in??_label\0 + */ +#define ABIT_UGURU3_IN_NAMES_LENGTH \ + (11 + 2 * 9 + 2 * 15 + 2 * 22 + 10 + 14 + 11) +/* + * sum of strlen +1 of: temp??_input\0, temp??_max\0, temp??_crit\0, + * temp??_alarm\0, temp??_alarm_enable\0, temp??_beep\0, temp??_shutdown\0, + * temp??_label\0 + */ +#define ABIT_UGURU3_TEMP_NAMES_LENGTH (13 + 11 + 12 + 13 + 20 + 12 + 16 + 13) +/* + * sum of strlen +1 of: fan??_input\0, fan??_min\0, fan??_alarm\0, + * fan??_alarm_enable\0, fan??_beep\0, fan??_shutdown\0, fan??_label\0 + */ +#define ABIT_UGURU3_FAN_NAMES_LENGTH (12 + 10 + 12 + 19 + 11 + 15 + 12) +/* + * Worst case scenario 16 in sensors (longest names_length) and the rest + * temp sensors (second longest names_length). + */ +#define ABIT_UGURU3_SYSFS_NAMES_LENGTH (16 * ABIT_UGURU3_IN_NAMES_LENGTH + \ + (ABIT_UGURU3_MAX_NO_SENSORS - 16) * ABIT_UGURU3_TEMP_NAMES_LENGTH) + +/* + * All the macros below are named identical to the openguru2 program + * reverse engineered by Louis Kruger, hence the names might not be 100% + * logical. I could come up with better names, but I prefer keeping the names + * identical so that this driver can be compared with his work more easily. + */ +/* Two i/o-ports are used by uGuru */ +#define ABIT_UGURU3_BASE 0x00E0 +#define ABIT_UGURU3_CMD 0x00 +#define ABIT_UGURU3_DATA 0x04 +#define ABIT_UGURU3_REGION_LENGTH 5 +/* + * The wait_xxx functions return this on success and the last contents + * of the DATA register (0-255) on failure. + */ +#define ABIT_UGURU3_SUCCESS -1 +/* uGuru status flags */ +#define ABIT_UGURU3_STATUS_READY_FOR_READ 0x01 +#define ABIT_UGURU3_STATUS_BUSY 0x02 + + +/* Structures */ +struct abituguru3_sensor_info { + const char *name; + int port; + int type; + int multiplier; + int divisor; + int offset; +}; + +/* Avoid use of flexible array members */ +#define ABIT_UGURU3_MAX_DMI_NAMES 2 + +struct abituguru3_motherboard_info { + u16 id; + const char *dmi_name[ABIT_UGURU3_MAX_DMI_NAMES + 1]; + /* + 1 -> end of sensors indicated by a sensor with name == NULL */ + struct abituguru3_sensor_info sensors[ABIT_UGURU3_MAX_NO_SENSORS + 1]; +}; + +/* + * For the Abit uGuru, we need to keep some data in memory. + * The structure is dynamically allocated, at the same time when a new + * abituguru3 device is allocated. + */ +struct abituguru3_data { + struct device *hwmon_dev; /* hwmon registered device */ + struct mutex update_lock; /* protect access to data and uGuru */ + unsigned short addr; /* uguru base address */ + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* + * For convenience the sysfs attr and their names are generated + * automatically. We have max 10 entries per sensor (for in sensors) + */ + struct sensor_device_attribute_2 sysfs_attr[ABIT_UGURU3_MAX_NO_SENSORS + * 10]; + + /* Buffer to store the dynamically generated sysfs names */ + char sysfs_names[ABIT_UGURU3_SYSFS_NAMES_LENGTH]; + + /* Pointer to the sensors info for the detected motherboard */ + const struct abituguru3_sensor_info *sensors; + + /* + * The abituguru3 supports up to 48 sensors, and thus has registers + * sets for 48 sensors, for convenience reasons / simplicity of the + * code we always read and store all registers for all 48 sensors + */ + + /* Alarms for all 48 sensors (1 bit per sensor) */ + u8 alarms[48/8]; + + /* Value of all 48 sensors */ + u8 value[48]; + + /* + * Settings of all 48 sensors, note in and temp sensors (the first 32 + * sensors) have 3 bytes of settings, while fans only have 2 bytes, + * for convenience we use 3 bytes for all sensors + */ + u8 settings[48][3]; +}; + + +/* Constants */ +static const struct abituguru3_motherboard_info abituguru3_motherboards[] = { + { 0x000C, { NULL } /* Unknown, need DMI string */, { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 10, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH & PCIE 1.5V", 4, 0, 10, 1, 0 }, + { "MCH 2.5V", 5, 0, 20, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System", 25, 1, 1, 1, 0 }, + { "PWM", 26, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS FAN", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 35, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x000D, { NULL } /* Abit AW8, need DMI string */, { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 10, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH & PCIE 1.5V", 4, 0, 10, 1, 0 }, + { "MCH 2.5V", 5, 0, 20, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System", 25, 1, 1, 1, 0 }, + { "PWM1", 26, 1, 1, 1, 0 }, + { "PWM2", 27, 1, 1, 1, 0 }, + { "PWM3", 28, 1, 1, 1, 0 }, + { "PWM4", 29, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 35, 2, 60, 1, 0 }, + { "AUX2 Fan", 36, 2, 60, 1, 0 }, + { "AUX3 Fan", 37, 2, 60, 1, 0 }, + { "AUX4 Fan", 38, 2, 60, 1, 0 }, + { "AUX5 Fan", 39, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x000E, { NULL } /* AL-8, need DMI string */, { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 10, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH & PCIE 1.5V", 4, 0, 10, 1, 0 }, + { "MCH 2.5V", 5, 0, 20, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System", 25, 1, 1, 1, 0 }, + { "PWM", 26, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x000F, { NULL } /* Unknown, need DMI string */, { + + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 10, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH & PCIE 1.5V", 4, 0, 10, 1, 0 }, + { "MCH 2.5V", 5, 0, 20, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System", 25, 1, 1, 1, 0 }, + { "PWM", 26, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0010, { NULL } /* Abit NI8 SLI GR, need DMI string */, { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 10, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "NB 1.4V", 4, 0, 10, 1, 0 }, + { "SB 1.5V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "SYS", 25, 1, 1, 1, 0 }, + { "PWM", 26, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 35, 2, 60, 1, 0 }, + { "OTES1 Fan", 36, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0011, { "AT8 32X", NULL }, { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 20, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VDDA 2.5V", 6, 0, 20, 1, 0 }, + { "NB 1.8V", 4, 0, 10, 1, 0 }, + { "NB 1.8V Dual", 5, 0, 10, 1, 0 }, + { "HTV 1.2", 3, 0, 10, 1, 0 }, + { "PCIE 1.2V", 12, 0, 10, 1, 0 }, + { "NB 1.2V", 13, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "NB", 25, 1, 1, 1, 0 }, + { "System", 26, 1, 1, 1, 0 }, + { "PWM", 27, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 35, 2, 60, 1, 0 }, + { "AUX2 Fan", 36, 2, 60, 1, 0 }, + { "AUX3 Fan", 37, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0012, { NULL } /* Abit AN8 32X, need DMI string */, { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 20, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "HyperTransport", 3, 0, 10, 1, 0 }, + { "CPU VDDA 2.5V", 5, 0, 20, 1, 0 }, + { "NB", 4, 0, 10, 1, 0 }, + { "SB", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "SYS", 25, 1, 1, 1, 0 }, + { "PWM", 26, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 36, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0013, { NULL } /* Abit AW8D, need DMI string */, { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 10, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH & PCIE 1.5V", 4, 0, 10, 1, 0 }, + { "MCH 2.5V", 5, 0, 20, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System", 25, 1, 1, 1, 0 }, + { "PWM1", 26, 1, 1, 1, 0 }, + { "PWM2", 27, 1, 1, 1, 0 }, + { "PWM3", 28, 1, 1, 1, 0 }, + { "PWM4", 29, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 35, 2, 60, 1, 0 }, + { "AUX2 Fan", 36, 2, 60, 1, 0 }, + { "AUX3 Fan", 37, 2, 60, 1, 0 }, + { "AUX4 Fan", 38, 2, 60, 1, 0 }, + { "AUX5 Fan", 39, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0014, { "AB9", "AB9 Pro", NULL }, { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 10, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH & PCIE 1.5V", 4, 0, 10, 1, 0 }, + { "MCH 2.5V", 5, 0, 20, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System", 25, 1, 1, 1, 0 }, + { "PWM", 26, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0015, { NULL } /* Unknown, need DMI string */, { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR", 1, 0, 20, 1, 0 }, + { "DDR VTT", 2, 0, 10, 1, 0 }, + { "HyperTransport", 3, 0, 10, 1, 0 }, + { "CPU VDDA 2.5V", 5, 0, 20, 1, 0 }, + { "NB", 4, 0, 10, 1, 0 }, + { "SB", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "SYS", 25, 1, 1, 1, 0 }, + { "PWM", 26, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 33, 2, 60, 1, 0 }, + { "AUX2 Fan", 35, 2, 60, 1, 0 }, + { "AUX3 Fan", 36, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0016, { "AW9D-MAX", NULL }, { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR2", 1, 0, 20, 1, 0 }, + { "DDR2 VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH & PCIE 1.5V", 4, 0, 10, 1, 0 }, + { "MCH 2.5V", 5, 0, 20, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System", 25, 1, 1, 1, 0 }, + { "PWM1", 26, 1, 1, 1, 0 }, + { "PWM2", 27, 1, 1, 1, 0 }, + { "PWM3", 28, 1, 1, 1, 0 }, + { "PWM4", 29, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "NB Fan", 33, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 35, 2, 60, 1, 0 }, + { "AUX2 Fan", 36, 2, 60, 1, 0 }, + { "AUX3 Fan", 37, 2, 60, 1, 0 }, + { "OTES1 Fan", 38, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0017, { NULL } /* Unknown, need DMI string */, { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR2", 1, 0, 20, 1, 0 }, + { "DDR2 VTT", 2, 0, 10, 1, 0 }, + { "HyperTransport", 3, 0, 10, 1, 0 }, + { "CPU VDDA 2.5V", 6, 0, 20, 1, 0 }, + { "NB 1.8V", 4, 0, 10, 1, 0 }, + { "NB 1.2V ", 13, 0, 10, 1, 0 }, + { "SB 1.2V", 5, 0, 10, 1, 0 }, + { "PCIE 1.2V", 12, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "ATX +3.3V", 10, 0, 20, 1, 0 }, + { "ATX 5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System", 26, 1, 1, 1, 0 }, + { "PWM", 27, 1, 1, 1, 0 }, + { "CPU FAN", 32, 2, 60, 1, 0 }, + { "SYS FAN", 34, 2, 60, 1, 0 }, + { "AUX1 FAN", 35, 2, 60, 1, 0 }, + { "AUX2 FAN", 36, 2, 60, 1, 0 }, + { "AUX3 FAN", 37, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0018, { "AB9 QuadGT", NULL }, { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR2", 1, 0, 20, 1, 0 }, + { "DDR2 VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT", 3, 0, 10, 1, 0 }, + { "MCH 1.25V", 4, 0, 10, 1, 0 }, + { "ICHIO 1.5V", 5, 0, 10, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System", 25, 1, 1, 1, 0 }, + { "PWM Phase1", 26, 1, 1, 1, 0 }, + { "PWM Phase2", 27, 1, 1, 1, 0 }, + { "PWM Phase3", 28, 1, 1, 1, 0 }, + { "PWM Phase4", 29, 1, 1, 1, 0 }, + { "PWM Phase5", 30, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 33, 2, 60, 1, 0 }, + { "AUX2 Fan", 35, 2, 60, 1, 0 }, + { "AUX3 Fan", 36, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0019, { "IN9 32X MAX", NULL }, { + { "CPU Core", 7, 0, 10, 1, 0 }, + { "DDR2", 13, 0, 20, 1, 0 }, + { "DDR2 VTT", 14, 0, 10, 1, 0 }, + { "CPU VTT", 3, 0, 20, 1, 0 }, + { "NB 1.2V", 4, 0, 10, 1, 0 }, + { "SB 1.5V", 6, 0, 10, 1, 0 }, + { "HyperTransport", 5, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 12, 0, 60, 1, 0 }, + { "ATX +12V (4-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "ATX +3.3V", 10, 0, 20, 1, 0 }, + { "ATX 5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System", 25, 1, 1, 1, 0 }, + { "PWM Phase1", 26, 1, 1, 1, 0 }, + { "PWM Phase2", 27, 1, 1, 1, 0 }, + { "PWM Phase3", 28, 1, 1, 1, 0 }, + { "PWM Phase4", 29, 1, 1, 1, 0 }, + { "PWM Phase5", 30, 1, 1, 1, 0 }, + { "CPU FAN", 32, 2, 60, 1, 0 }, + { "SYS FAN", 34, 2, 60, 1, 0 }, + { "AUX1 FAN", 33, 2, 60, 1, 0 }, + { "AUX2 FAN", 35, 2, 60, 1, 0 }, + { "AUX3 FAN", 36, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x001A, { "IP35 Pro", "IP35 Pro XE", NULL }, { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR2", 1, 0, 20, 1, 0 }, + { "DDR2 VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT 1.2V", 3, 0, 10, 1, 0 }, + { "MCH 1.25V", 4, 0, 10, 1, 0 }, + { "ICHIO 1.5V", 5, 0, 10, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (8-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System", 25, 1, 1, 1, 0 }, + { "PWM", 26, 1, 1, 1, 0 }, + { "PWM Phase2", 27, 1, 1, 1, 0 }, + { "PWM Phase3", 28, 1, 1, 1, 0 }, + { "PWM Phase4", 29, 1, 1, 1, 0 }, + { "PWM Phase5", 30, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 33, 2, 60, 1, 0 }, + { "AUX2 Fan", 35, 2, 60, 1, 0 }, + { "AUX3 Fan", 36, 2, 60, 1, 0 }, + { "AUX4 Fan", 37, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x001B, { NULL } /* Unknown, need DMI string */, { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR3", 1, 0, 20, 1, 0 }, + { "DDR3 VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT", 3, 0, 10, 1, 0 }, + { "MCH 1.25V", 4, 0, 10, 1, 0 }, + { "ICHIO 1.5V", 5, 0, 10, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (8-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System", 25, 1, 1, 1, 0 }, + { "PWM Phase1", 26, 1, 1, 1, 0 }, + { "PWM Phase2", 27, 1, 1, 1, 0 }, + { "PWM Phase3", 28, 1, 1, 1, 0 }, + { "PWM Phase4", 29, 1, 1, 1, 0 }, + { "PWM Phase5", 30, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 33, 2, 60, 1, 0 }, + { "AUX2 Fan", 35, 2, 60, 1, 0 }, + { "AUX3 Fan", 36, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x001C, { "IX38 QuadGT", NULL }, { + { "CPU Core", 0, 0, 10, 1, 0 }, + { "DDR2", 1, 0, 20, 1, 0 }, + { "DDR2 VTT", 2, 0, 10, 1, 0 }, + { "CPU VTT", 3, 0, 10, 1, 0 }, + { "MCH 1.25V", 4, 0, 10, 1, 0 }, + { "ICHIO 1.5V", 5, 0, 10, 1, 0 }, + { "ICH 1.05V", 6, 0, 10, 1, 0 }, + { "ATX +12V (24-Pin)", 7, 0, 60, 1, 0 }, + { "ATX +12V (8-pin)", 8, 0, 60, 1, 0 }, + { "ATX +5V", 9, 0, 30, 1, 0 }, + { "+3.3V", 10, 0, 20, 1, 0 }, + { "5VSB", 11, 0, 30, 1, 0 }, + { "CPU", 24, 1, 1, 1, 0 }, + { "System", 25, 1, 1, 1, 0 }, + { "PWM Phase1", 26, 1, 1, 1, 0 }, + { "PWM Phase2", 27, 1, 1, 1, 0 }, + { "PWM Phase3", 28, 1, 1, 1, 0 }, + { "PWM Phase4", 29, 1, 1, 1, 0 }, + { "PWM Phase5", 30, 1, 1, 1, 0 }, + { "CPU Fan", 32, 2, 60, 1, 0 }, + { "SYS Fan", 34, 2, 60, 1, 0 }, + { "AUX1 Fan", 33, 2, 60, 1, 0 }, + { "AUX2 Fan", 35, 2, 60, 1, 0 }, + { "AUX3 Fan", 36, 2, 60, 1, 0 }, + { NULL, 0, 0, 0, 0, 0 } } + }, + { 0x0000, { NULL }, { { NULL, 0, 0, 0, 0, 0 } } } +}; + + +/* Insmod parameters */ +static bool force; +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Set to one to force detection."); +/* Default verbose is 1, since this driver is still in the testing phase */ +static bool verbose = 1; +module_param(verbose, bool, 0644); +MODULE_PARM_DESC(verbose, "Enable/disable verbose error reporting"); + +static const char *never_happen = "This should never happen."; +static const char *report_this = + "Please report this to the abituguru3 maintainer (see MAINTAINERS)"; + +/* wait while the uguru is busy (usually after a write) */ +static int abituguru3_wait_while_busy(struct abituguru3_data *data) +{ + u8 x; + int timeout = ABIT_UGURU3_WAIT_TIMEOUT; + + while ((x = inb_p(data->addr + ABIT_UGURU3_DATA)) & + ABIT_UGURU3_STATUS_BUSY) { + timeout--; + if (timeout == 0) + return x; + /* + * sleep a bit before our last try, to give the uGuru3 one + * last chance to respond. + */ + if (timeout == 1) + msleep(1); + } + return ABIT_UGURU3_SUCCESS; +} + +/* wait till uguru is ready to be read */ +static int abituguru3_wait_for_read(struct abituguru3_data *data) +{ + u8 x; + int timeout = ABIT_UGURU3_WAIT_TIMEOUT; + + while (!((x = inb_p(data->addr + ABIT_UGURU3_DATA)) & + ABIT_UGURU3_STATUS_READY_FOR_READ)) { + timeout--; + if (timeout == 0) + return x; + /* + * sleep a bit before our last try, to give the uGuru3 one + * last chance to respond. + */ + if (timeout == 1) + msleep(1); + } + return ABIT_UGURU3_SUCCESS; +} + +/* + * This synchronizes us with the uGuru3's protocol state machine, this + * must be done before each command. + */ +static int abituguru3_synchronize(struct abituguru3_data *data) +{ + int x, timeout = ABIT_UGURU3_SYNCHRONIZE_TIMEOUT; + + x = abituguru3_wait_while_busy(data); + if (x != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("synchronize timeout during initial busy " + "wait, status: 0x%02x\n", x); + return -EIO; + } + + outb(0x20, data->addr + ABIT_UGURU3_DATA); + x = abituguru3_wait_while_busy(data); + if (x != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("synchronize timeout after sending 0x20, " + "status: 0x%02x\n", x); + return -EIO; + } + + outb(0x10, data->addr + ABIT_UGURU3_CMD); + x = abituguru3_wait_while_busy(data); + if (x != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("synchronize timeout after sending 0x10, " + "status: 0x%02x\n", x); + return -EIO; + } + + outb(0x00, data->addr + ABIT_UGURU3_CMD); + x = abituguru3_wait_while_busy(data); + if (x != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("synchronize timeout after sending 0x00, " + "status: 0x%02x\n", x); + return -EIO; + } + + x = abituguru3_wait_for_read(data); + if (x != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("synchronize timeout waiting for read, " + "status: 0x%02x\n", x); + return -EIO; + } + + while ((x = inb(data->addr + ABIT_UGURU3_CMD)) != 0xAC) { + timeout--; + if (timeout == 0) { + ABIT_UGURU3_DEBUG("synchronize timeout cmd does not " + "hold 0xAC after synchronize, cmd: 0x%02x\n", + x); + return -EIO; + } + msleep(1); + } + return 0; +} + +/* + * Read count bytes from sensor sensor_addr in bank bank_addr and store the + * result in buf + */ +static int abituguru3_read(struct abituguru3_data *data, u8 bank, u8 offset, + u8 count, u8 *buf) +{ + int i, x; + + x = abituguru3_synchronize(data); + if (x) + return x; + + outb(0x1A, data->addr + ABIT_UGURU3_DATA); + x = abituguru3_wait_while_busy(data); + if (x != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("read from 0x%02x:0x%02x timed out after " + "sending 0x1A, status: 0x%02x\n", (unsigned int)bank, + (unsigned int)offset, x); + return -EIO; + } + + outb(bank, data->addr + ABIT_UGURU3_CMD); + x = abituguru3_wait_while_busy(data); + if (x != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("read from 0x%02x:0x%02x timed out after " + "sending the bank, status: 0x%02x\n", + (unsigned int)bank, (unsigned int)offset, x); + return -EIO; + } + + outb(offset, data->addr + ABIT_UGURU3_CMD); + x = abituguru3_wait_while_busy(data); + if (x != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("read from 0x%02x:0x%02x timed out after " + "sending the offset, status: 0x%02x\n", + (unsigned int)bank, (unsigned int)offset, x); + return -EIO; + } + + outb(count, data->addr + ABIT_UGURU3_CMD); + x = abituguru3_wait_while_busy(data); + if (x != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("read from 0x%02x:0x%02x timed out after " + "sending the count, status: 0x%02x\n", + (unsigned int)bank, (unsigned int)offset, x); + return -EIO; + } + + for (i = 0; i < count; i++) { + x = abituguru3_wait_for_read(data); + if (x != ABIT_UGURU3_SUCCESS) { + ABIT_UGURU3_DEBUG("timeout reading byte %d from " + "0x%02x:0x%02x, status: 0x%02x\n", i, + (unsigned int)bank, (unsigned int)offset, x); + break; + } + buf[i] = inb(data->addr + ABIT_UGURU3_CMD); + } + return i; +} + +/* + * Sensor settings are stored 1 byte per offset with the bytes + * placed add consecutive offsets. + */ +static int abituguru3_read_increment_offset(struct abituguru3_data *data, + u8 bank, u8 offset, u8 count, + u8 *buf, int offset_count) +{ + int i, x; + + for (i = 0; i < offset_count; i++) { + x = abituguru3_read(data, bank, offset + i, count, + buf + i * count); + if (x != count) { + if (x < 0) + return x; + return i * count + x; + } + } + + return i * count; +} + +/* + * Following are the sysfs callback functions. These functions expect: + * sensor_device_attribute_2->index: index into the data->sensors array + * sensor_device_attribute_2->nr: register offset, bitmask or NA. + */ +static struct abituguru3_data *abituguru3_update_device(struct device *dev); + +static ssize_t show_value(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int value; + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru3_data *data = abituguru3_update_device(dev); + const struct abituguru3_sensor_info *sensor; + + if (!data) + return -EIO; + + sensor = &data->sensors[attr->index]; + + /* are we reading a setting, or is this a normal read? */ + if (attr->nr) + value = data->settings[sensor->port][attr->nr]; + else + value = data->value[sensor->port]; + + /* convert the value */ + value = (value * sensor->multiplier) / sensor->divisor + + sensor->offset; + + /* + * alternatively we could update the sensors settings struct for this, + * but then its contents would differ from the windows sw ini files + */ + if (sensor->type == ABIT_UGURU3_TEMP_SENSOR) + value *= 1000; + + return sprintf(buf, "%d\n", value); +} + +static ssize_t show_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int port; + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru3_data *data = abituguru3_update_device(dev); + + if (!data) + return -EIO; + + port = data->sensors[attr->index].port; + + /* + * See if the alarm bit for this sensor is set and if a bitmask is + * given in attr->nr also check if the alarm matches the type of alarm + * we're looking for (for volt it can be either low or high). The type + * is stored in a few readonly bits in the settings of the sensor. + */ + if ((data->alarms[port / 8] & (0x01 << (port % 8))) && + (!attr->nr || (data->settings[port][0] & attr->nr))) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t show_mask(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru3_data *data = dev_get_drvdata(dev); + + if (data->settings[data->sensors[attr->index].port][0] & attr->nr) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct abituguru3_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->sensors[attr->index].name); +} + +static ssize_t show_name(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + return sprintf(buf, "%s\n", ABIT_UGURU3_NAME); +} + +/* Sysfs attr templates, the real entries are generated automatically. */ +static const +struct sensor_device_attribute_2 abituguru3_sysfs_templ[3][10] = { { + SENSOR_ATTR_2(in%d_input, 0444, show_value, NULL, 0, 0), + SENSOR_ATTR_2(in%d_min, 0444, show_value, NULL, 1, 0), + SENSOR_ATTR_2(in%d_max, 0444, show_value, NULL, 2, 0), + SENSOR_ATTR_2(in%d_min_alarm, 0444, show_alarm, NULL, + ABIT_UGURU3_VOLT_LOW_ALARM_FLAG, 0), + SENSOR_ATTR_2(in%d_max_alarm, 0444, show_alarm, NULL, + ABIT_UGURU3_VOLT_HIGH_ALARM_FLAG, 0), + SENSOR_ATTR_2(in%d_beep, 0444, show_mask, NULL, + ABIT_UGURU3_BEEP_ENABLE, 0), + SENSOR_ATTR_2(in%d_shutdown, 0444, show_mask, NULL, + ABIT_UGURU3_SHUTDOWN_ENABLE, 0), + SENSOR_ATTR_2(in%d_min_alarm_enable, 0444, show_mask, NULL, + ABIT_UGURU3_VOLT_LOW_ALARM_ENABLE, 0), + SENSOR_ATTR_2(in%d_max_alarm_enable, 0444, show_mask, NULL, + ABIT_UGURU3_VOLT_HIGH_ALARM_ENABLE, 0), + SENSOR_ATTR_2(in%d_label, 0444, show_label, NULL, 0, 0) + }, { + SENSOR_ATTR_2(temp%d_input, 0444, show_value, NULL, 0, 0), + SENSOR_ATTR_2(temp%d_max, 0444, show_value, NULL, 1, 0), + SENSOR_ATTR_2(temp%d_crit, 0444, show_value, NULL, 2, 0), + SENSOR_ATTR_2(temp%d_alarm, 0444, show_alarm, NULL, 0, 0), + SENSOR_ATTR_2(temp%d_beep, 0444, show_mask, NULL, + ABIT_UGURU3_BEEP_ENABLE, 0), + SENSOR_ATTR_2(temp%d_shutdown, 0444, show_mask, NULL, + ABIT_UGURU3_SHUTDOWN_ENABLE, 0), + SENSOR_ATTR_2(temp%d_alarm_enable, 0444, show_mask, NULL, + ABIT_UGURU3_TEMP_HIGH_ALARM_ENABLE, 0), + SENSOR_ATTR_2(temp%d_label, 0444, show_label, NULL, 0, 0) + }, { + SENSOR_ATTR_2(fan%d_input, 0444, show_value, NULL, 0, 0), + SENSOR_ATTR_2(fan%d_min, 0444, show_value, NULL, 1, 0), + SENSOR_ATTR_2(fan%d_alarm, 0444, show_alarm, NULL, 0, 0), + SENSOR_ATTR_2(fan%d_beep, 0444, show_mask, NULL, + ABIT_UGURU3_BEEP_ENABLE, 0), + SENSOR_ATTR_2(fan%d_shutdown, 0444, show_mask, NULL, + ABIT_UGURU3_SHUTDOWN_ENABLE, 0), + SENSOR_ATTR_2(fan%d_alarm_enable, 0444, show_mask, NULL, + ABIT_UGURU3_FAN_LOW_ALARM_ENABLE, 0), + SENSOR_ATTR_2(fan%d_label, 0444, show_label, NULL, 0, 0) +} }; + +static struct sensor_device_attribute_2 abituguru3_sysfs_attr[] = { + SENSOR_ATTR_2(name, 0444, show_name, NULL, 0, 0), +}; + +static int abituguru3_probe(struct platform_device *pdev) +{ + const int no_sysfs_attr[3] = { 10, 8, 7 }; + int sensor_index[3] = { 0, 1, 1 }; + struct abituguru3_data *data; + int i, j, type, used, sysfs_names_free, sysfs_attr_i, res = -ENODEV; + char *sysfs_filename; + u8 buf[2]; + u16 id; + + data = devm_kzalloc(&pdev->dev, sizeof(struct abituguru3_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start; + mutex_init(&data->update_lock); + platform_set_drvdata(pdev, data); + + /* Read the motherboard ID */ + i = abituguru3_read(data, ABIT_UGURU3_MISC_BANK, ABIT_UGURU3_BOARD_ID, + 2, buf); + if (i != 2) + goto abituguru3_probe_error; + + /* Completely read the uGuru to see if one really is there */ + if (!abituguru3_update_device(&pdev->dev)) + goto abituguru3_probe_error; + + /* lookup the ID in our motherboard table */ + id = ((u16)buf[0] << 8) | (u16)buf[1]; + for (i = 0; abituguru3_motherboards[i].id; i++) + if (abituguru3_motherboards[i].id == id) + break; + if (!abituguru3_motherboards[i].id) { + pr_err("error unknown motherboard ID: %04X. %s\n", + (unsigned int)id, report_this); + goto abituguru3_probe_error; + } + data->sensors = abituguru3_motherboards[i].sensors; + + pr_info("found Abit uGuru3, motherboard ID: %04X\n", (unsigned int)id); + + /* Fill the sysfs attr array */ + sysfs_attr_i = 0; + sysfs_filename = data->sysfs_names; + sysfs_names_free = ABIT_UGURU3_SYSFS_NAMES_LENGTH; + for (i = 0; data->sensors[i].name; i++) { + /* Fail safe check, this should never happen! */ + if (i >= ABIT_UGURU3_MAX_NO_SENSORS) { + pr_err("Fatal error motherboard has more sensors then ABIT_UGURU3_MAX_NO_SENSORS. %s %s\n", + never_happen, report_this); + res = -ENAMETOOLONG; + goto abituguru3_probe_error; + } + type = data->sensors[i].type; + for (j = 0; j < no_sysfs_attr[type]; j++) { + used = snprintf(sysfs_filename, sysfs_names_free, + abituguru3_sysfs_templ[type][j].dev_attr.attr. + name, sensor_index[type]) + 1; + data->sysfs_attr[sysfs_attr_i] = + abituguru3_sysfs_templ[type][j]; + data->sysfs_attr[sysfs_attr_i].dev_attr.attr.name = + sysfs_filename; + data->sysfs_attr[sysfs_attr_i].index = i; + sysfs_filename += used; + sysfs_names_free -= used; + sysfs_attr_i++; + } + sensor_index[type]++; + } + /* Fail safe check, this should never happen! */ + if (sysfs_names_free < 0) { + pr_err("Fatal error ran out of space for sysfs attr names. %s %s\n", + never_happen, report_this); + res = -ENAMETOOLONG; + goto abituguru3_probe_error; + } + + /* Register sysfs hooks */ + for (i = 0; i < sysfs_attr_i; i++) + if (device_create_file(&pdev->dev, + &data->sysfs_attr[i].dev_attr)) + goto abituguru3_probe_error; + for (i = 0; i < ARRAY_SIZE(abituguru3_sysfs_attr); i++) + if (device_create_file(&pdev->dev, + &abituguru3_sysfs_attr[i].dev_attr)) + goto abituguru3_probe_error; + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + res = PTR_ERR(data->hwmon_dev); + goto abituguru3_probe_error; + } + + return 0; /* success */ + +abituguru3_probe_error: + for (i = 0; data->sysfs_attr[i].dev_attr.attr.name; i++) + device_remove_file(&pdev->dev, &data->sysfs_attr[i].dev_attr); + for (i = 0; i < ARRAY_SIZE(abituguru3_sysfs_attr); i++) + device_remove_file(&pdev->dev, + &abituguru3_sysfs_attr[i].dev_attr); + return res; +} + +static int abituguru3_remove(struct platform_device *pdev) +{ + int i; + struct abituguru3_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + for (i = 0; data->sysfs_attr[i].dev_attr.attr.name; i++) + device_remove_file(&pdev->dev, &data->sysfs_attr[i].dev_attr); + for (i = 0; i < ARRAY_SIZE(abituguru3_sysfs_attr); i++) + device_remove_file(&pdev->dev, + &abituguru3_sysfs_attr[i].dev_attr); + return 0; +} + +static struct abituguru3_data *abituguru3_update_device(struct device *dev) +{ + int i; + struct abituguru3_data *data = dev_get_drvdata(dev); + + mutex_lock(&data->update_lock); + if (!data->valid || time_after(jiffies, data->last_updated + HZ)) { + /* Clear data->valid while updating */ + data->valid = 0; + /* Read alarms */ + if (abituguru3_read_increment_offset(data, + ABIT_UGURU3_SETTINGS_BANK, + ABIT_UGURU3_ALARMS_START, + 1, data->alarms, 48/8) != (48/8)) + goto LEAVE_UPDATE; + /* Read in and temp sensors (3 byte settings / sensor) */ + for (i = 0; i < 32; i++) { + if (abituguru3_read(data, ABIT_UGURU3_SENSORS_BANK, + ABIT_UGURU3_VALUES_START + i, + 1, &data->value[i]) != 1) + goto LEAVE_UPDATE; + if (abituguru3_read_increment_offset(data, + ABIT_UGURU3_SETTINGS_BANK, + ABIT_UGURU3_SETTINGS_START + i * 3, + 1, + data->settings[i], 3) != 3) + goto LEAVE_UPDATE; + } + /* Read temp sensors (2 byte settings / sensor) */ + for (i = 0; i < 16; i++) { + if (abituguru3_read(data, ABIT_UGURU3_SENSORS_BANK, + ABIT_UGURU3_VALUES_START + 32 + i, + 1, &data->value[32 + i]) != 1) + goto LEAVE_UPDATE; + if (abituguru3_read_increment_offset(data, + ABIT_UGURU3_SETTINGS_BANK, + ABIT_UGURU3_SETTINGS_START + 32 * 3 + + i * 2, 1, + data->settings[32 + i], 2) != 2) + goto LEAVE_UPDATE; + } + data->last_updated = jiffies; + data->valid = 1; + } +LEAVE_UPDATE: + mutex_unlock(&data->update_lock); + if (data->valid) + return data; + else + return NULL; +} + +#ifdef CONFIG_PM_SLEEP +static int abituguru3_suspend(struct device *dev) +{ + struct abituguru3_data *data = dev_get_drvdata(dev); + /* + * make sure all communications with the uguru3 are done and no new + * ones are started + */ + mutex_lock(&data->update_lock); + return 0; +} + +static int abituguru3_resume(struct device *dev) +{ + struct abituguru3_data *data = dev_get_drvdata(dev); + mutex_unlock(&data->update_lock); + return 0; +} + +static SIMPLE_DEV_PM_OPS(abituguru3_pm, abituguru3_suspend, abituguru3_resume); +#define ABIT_UGURU3_PM (&abituguru3_pm) +#else +#define ABIT_UGURU3_PM NULL +#endif /* CONFIG_PM */ + +static struct platform_driver abituguru3_driver = { + .driver = { + .name = ABIT_UGURU3_NAME, + .pm = ABIT_UGURU3_PM + }, + .probe = abituguru3_probe, + .remove = abituguru3_remove, +}; + +static int __init abituguru3_dmi_detect(void) +{ + const char *board_vendor, *board_name; + int i, err = (force) ? 1 : -ENODEV; + const char *const *dmi_name; + size_t sublen; + + board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + if (!board_vendor || strcmp(board_vendor, "http://www.abit.com.tw/")) + return err; + + board_name = dmi_get_system_info(DMI_BOARD_NAME); + if (!board_name) + return err; + + /* + * At the moment, we don't care about the part of the vendor + * DMI string contained in brackets. Truncate the string at + * the first occurrence of a bracket. Trim any trailing space + * from the substring. + */ + sublen = strcspn(board_name, "("); + while (sublen > 0 && board_name[sublen - 1] == ' ') + sublen--; + + for (i = 0; abituguru3_motherboards[i].id; i++) { + dmi_name = abituguru3_motherboards[i].dmi_name; + for ( ; *dmi_name; dmi_name++) { + if (strlen(*dmi_name) != sublen) + continue; + if (!strncasecmp(board_name, *dmi_name, sublen)) + return 0; + } + } + + /* No match found */ + return 1; +} + +/* + * FIXME: Manual detection should die eventually; we need to collect stable + * DMI model names first before we can rely entirely on CONFIG_DMI. + */ + +static int __init abituguru3_detect(void) +{ + /* + * See if there is an uguru3 there. An idle uGuru3 will hold 0x00 or + * 0x08 at DATA and 0xAC at CMD. Sometimes the uGuru3 will hold 0x05 + * or 0x55 at CMD instead, why is unknown. + */ + u8 data_val = inb_p(ABIT_UGURU3_BASE + ABIT_UGURU3_DATA); + u8 cmd_val = inb_p(ABIT_UGURU3_BASE + ABIT_UGURU3_CMD); + if (((data_val == 0x00) || (data_val == 0x08)) && + ((cmd_val == 0xAC) || (cmd_val == 0x05) || + (cmd_val == 0x55))) + return 0; + + ABIT_UGURU3_DEBUG("no Abit uGuru3 found, data = 0x%02X, cmd = " + "0x%02X\n", (unsigned int)data_val, (unsigned int)cmd_val); + + if (force) { + pr_info("Assuming Abit uGuru3 is present because of \"force\" parameter\n"); + return 0; + } + + /* No uGuru3 found */ + return -ENODEV; +} + +static struct platform_device *abituguru3_pdev; + +static int __init abituguru3_init(void) +{ + struct resource res = { .flags = IORESOURCE_IO }; + int err; + + /* Attempt DMI detection first */ + err = abituguru3_dmi_detect(); + if (err < 0) + return err; + + /* + * Fall back to manual detection if there was no exact + * board name match, or force was specified. + */ + if (err > 0) { + err = abituguru3_detect(); + if (err) + return err; + + pr_warn("this motherboard was not detected using DMI. " + "Please send the output of \"dmidecode\" to the abituguru3 maintainer (see MAINTAINERS)\n"); + } + + err = platform_driver_register(&abituguru3_driver); + if (err) + goto exit; + + abituguru3_pdev = platform_device_alloc(ABIT_UGURU3_NAME, + ABIT_UGURU3_BASE); + if (!abituguru3_pdev) { + pr_err("Device allocation failed\n"); + err = -ENOMEM; + goto exit_driver_unregister; + } + + res.start = ABIT_UGURU3_BASE; + res.end = ABIT_UGURU3_BASE + ABIT_UGURU3_REGION_LENGTH - 1; + res.name = ABIT_UGURU3_NAME; + + err = platform_device_add_resources(abituguru3_pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add(abituguru3_pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(abituguru3_pdev); +exit_driver_unregister: + platform_driver_unregister(&abituguru3_driver); +exit: + return err; +} + +static void __exit abituguru3_exit(void) +{ + platform_device_unregister(abituguru3_pdev); + platform_driver_unregister(&abituguru3_driver); +} + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("Abit uGuru3 Sensor device"); +MODULE_LICENSE("GPL"); + +module_init(abituguru3_init); +module_exit(abituguru3_exit); diff --git a/drivers/hwmon/abx500.c b/drivers/hwmon/abx500.c new file mode 100644 index 000000000..d87cae8c6 --- /dev/null +++ b/drivers/hwmon/abx500.c @@ -0,0 +1,491 @@ +/* + * Copyright (C) ST-Ericsson 2010 - 2013 + * Author: Martin Persson + * Hongbo Zhang + * License Terms: GNU General Public License v2 + * + * ABX500 does not provide auto ADC, so to monitor the required temperatures, + * a periodic work is used. It is more important to not wake up the CPU than + * to perform this job, hence the use of a deferred delay. + * + * A deferred delay for thermal monitor is considered safe because: + * If the chip gets too hot during a sleep state it's most likely due to + * external factors, such as the surrounding temperature. I.e. no SW decisions + * will make any difference. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "abx500.h" + +#define DEFAULT_MONITOR_DELAY HZ +#define DEFAULT_MAX_TEMP 130 + +static inline void schedule_monitor(struct abx500_temp *data) +{ + data->work_active = true; + schedule_delayed_work(&data->work, DEFAULT_MONITOR_DELAY); +} + +static void threshold_updated(struct abx500_temp *data) +{ + int i; + for (i = 0; i < data->monitored_sensors; i++) + if (data->max[i] != 0 || data->min[i] != 0) { + schedule_monitor(data); + return; + } + + dev_dbg(&data->pdev->dev, "No active thresholds.\n"); + cancel_delayed_work_sync(&data->work); + data->work_active = false; +} + +static void gpadc_monitor(struct work_struct *work) +{ + int temp, i, ret; + char alarm_node[30]; + bool updated_min_alarm, updated_max_alarm; + struct abx500_temp *data; + + data = container_of(work, struct abx500_temp, work.work); + mutex_lock(&data->lock); + + for (i = 0; i < data->monitored_sensors; i++) { + /* Thresholds are considered inactive if set to 0 */ + if (data->max[i] == 0 && data->min[i] == 0) + continue; + + if (data->max[i] < data->min[i]) + continue; + + ret = data->ops.read_sensor(data, data->gpadc_addr[i], &temp); + if (ret < 0) { + dev_err(&data->pdev->dev, "GPADC read failed\n"); + continue; + } + + updated_min_alarm = false; + updated_max_alarm = false; + + if (data->min[i] != 0) { + if (temp < data->min[i]) { + if (data->min_alarm[i] == false) { + data->min_alarm[i] = true; + updated_min_alarm = true; + } + } else { + if (data->min_alarm[i] == true) { + data->min_alarm[i] = false; + updated_min_alarm = true; + } + } + } + if (data->max[i] != 0) { + if (temp > data->max[i]) { + if (data->max_alarm[i] == false) { + data->max_alarm[i] = true; + updated_max_alarm = true; + } + } else if (temp < data->max[i] - data->max_hyst[i]) { + if (data->max_alarm[i] == true) { + data->max_alarm[i] = false; + updated_max_alarm = true; + } + } + } + + if (updated_min_alarm) { + ret = sprintf(alarm_node, "temp%d_min_alarm", i + 1); + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + if (updated_max_alarm) { + ret = sprintf(alarm_node, "temp%d_max_alarm", i + 1); + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + } + + schedule_monitor(data); + mutex_unlock(&data->lock); +} + +/* HWMON sysfs interfaces */ +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + /* Show chip name */ + return data->ops.show_name(dev, devattr, buf); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + /* Show each sensor label */ + return data->ops.show_label(dev, devattr, buf); +} + +static ssize_t show_input(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int ret, temp; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + u8 gpadc_addr = data->gpadc_addr[attr->index]; + + ret = data->ops.read_sensor(data, gpadc_addr, &temp); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", temp); +} + +/* Set functions (RW nodes) */ +static ssize_t set_min(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = kstrtol(buf, 10, &val); + if (res < 0) + return res; + + val = clamp_val(val, 0, DEFAULT_MAX_TEMP); + + mutex_lock(&data->lock); + data->min[attr->index] = val; + threshold_updated(data); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_max(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = kstrtol(buf, 10, &val); + if (res < 0) + return res; + + val = clamp_val(val, 0, DEFAULT_MAX_TEMP); + + mutex_lock(&data->lock); + data->max[attr->index] = val; + threshold_updated(data); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_max_hyst(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = kstrtoul(buf, 10, &val); + if (res < 0) + return res; + + val = clamp_val(val, 0, DEFAULT_MAX_TEMP); + + mutex_lock(&data->lock); + data->max_hyst[attr->index] = val; + threshold_updated(data); + mutex_unlock(&data->lock); + + return count; +} + +/* Show functions (RO nodes) */ +static ssize_t show_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%lu\n", data->min[attr->index]); +} + +static ssize_t show_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%lu\n", data->max[attr->index]); +} + +static ssize_t show_max_hyst(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%lu\n", data->max_hyst[attr->index]); +} + +static ssize_t show_min_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%d\n", data->min_alarm[attr->index]); +} + +static ssize_t show_max_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%d\n", data->max_alarm[attr->index]); +} + +static umode_t abx500_attrs_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct abx500_temp *data = dev_get_drvdata(dev); + + if (data->ops.is_visible) + return data->ops.is_visible(attr, n); + + return attr->mode; +} + +/* Chip name, required by hwmon */ +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); + +/* GPADC - SENSOR1 */ +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 0); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 0); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 0); + +/* GPADC - SENSOR2 */ +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 1); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_min_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_max_alarm, NULL, 1); + +/* GPADC - SENSOR3 */ +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_label, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_input, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_min, set_min, 2); +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_max, set_max, 2); +static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 2); +static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_min_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_max_alarm, NULL, 2); + +/* GPADC - SENSOR4 */ +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_input, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_min, S_IWUSR | S_IRUGO, show_min, set_min, 3); +static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_max, set_max, 3); +static SENSOR_DEVICE_ATTR(temp4_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 3); +static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_min_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_max_alarm, NULL, 3); + +static struct attribute *abx500_temp_attributes[] = { + &sensor_dev_attr_name.dev_attr.attr, + + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + + &sensor_dev_attr_temp2_label.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + + &sensor_dev_attr_temp3_label.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + + &sensor_dev_attr_temp4_label.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group abx500_temp_group = { + .attrs = abx500_temp_attributes, + .is_visible = abx500_attrs_visible, +}; + +static irqreturn_t abx500_temp_irq_handler(int irq, void *irq_data) +{ + struct platform_device *pdev = irq_data; + struct abx500_temp *data = platform_get_drvdata(pdev); + + data->ops.irq_handler(irq, data); + return IRQ_HANDLED; +} + +static int setup_irqs(struct platform_device *pdev) +{ + int ret; + int irq = platform_get_irq_byname(pdev, "ABX500_TEMP_WARM"); + + if (irq < 0) { + dev_err(&pdev->dev, "Get irq by name failed\n"); + return irq; + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + abx500_temp_irq_handler, 0, "abx500-temp", pdev); + if (ret < 0) + dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret); + + return ret; +} + +static int abx500_temp_probe(struct platform_device *pdev) +{ + struct abx500_temp *data; + int err; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->pdev = pdev; + mutex_init(&data->lock); + + /* Chip specific initialization */ + err = abx500_hwmon_init(data); + if (err < 0 || !data->ops.read_sensor || !data->ops.show_name || + !data->ops.show_label) + return err; + + INIT_DEFERRABLE_WORK(&data->work, gpadc_monitor); + + platform_set_drvdata(pdev, data); + + err = sysfs_create_group(&pdev->dev.kobj, &abx500_temp_group); + if (err < 0) { + dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err); + return err; + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", err); + goto exit_sysfs_group; + } + + if (data->ops.irq_handler) { + err = setup_irqs(pdev); + if (err < 0) + goto exit_hwmon_reg; + } + return 0; + +exit_hwmon_reg: + hwmon_device_unregister(data->hwmon_dev); +exit_sysfs_group: + sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group); + return err; +} + +static int abx500_temp_remove(struct platform_device *pdev) +{ + struct abx500_temp *data = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&data->work); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group); + + return 0; +} + +static int abx500_temp_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct abx500_temp *data = platform_get_drvdata(pdev); + + if (data->work_active) + cancel_delayed_work_sync(&data->work); + + return 0; +} + +static int abx500_temp_resume(struct platform_device *pdev) +{ + struct abx500_temp *data = platform_get_drvdata(pdev); + + if (data->work_active) + schedule_monitor(data); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id abx500_temp_match[] = { + { .compatible = "stericsson,abx500-temp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, abx500_temp_match); +#endif + +static struct platform_driver abx500_temp_driver = { + .driver = { + .name = "abx500-temp", + .of_match_table = of_match_ptr(abx500_temp_match), + }, + .suspend = abx500_temp_suspend, + .resume = abx500_temp_resume, + .probe = abx500_temp_probe, + .remove = abx500_temp_remove, +}; + +module_platform_driver(abx500_temp_driver); + +MODULE_AUTHOR("Martin Persson "); +MODULE_DESCRIPTION("ABX500 temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/abx500.h b/drivers/hwmon/abx500.h new file mode 100644 index 000000000..9b295e684 --- /dev/null +++ b/drivers/hwmon/abx500.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) ST-Ericsson 2010 - 2013 + * License terms: GNU General Public License v2 + * Author: Martin Persson + * Hongbo Zhang + */ + +#ifndef _ABX500_H +#define _ABX500_H + +#define NUM_SENSORS 5 + +struct abx500_temp; + +/* + * struct abx500_temp_ops - abx500 chip specific ops + * @read_sensor: reads gpadc output + * @irq_handler: irq handler + * @show_name: hwmon device name + * @show_label: hwmon attribute label + * @is_visible: is attribute visible + */ +struct abx500_temp_ops { + int (*read_sensor)(struct abx500_temp *, u8, int *); + int (*irq_handler)(int, struct abx500_temp *); + ssize_t (*show_name)(struct device *, + struct device_attribute *, char *); + ssize_t (*show_label) (struct device *, + struct device_attribute *, char *); + int (*is_visible)(struct attribute *, int); +}; + +/* + * struct abx500_temp - representation of temp mon device + * @pdev: platform device + * @hwmon_dev: hwmon device + * @ops: abx500 chip specific ops + * @gpadc_addr: gpadc channel address + * @min: sensor temperature min value + * @max: sensor temperature max value + * @max_hyst: sensor temperature hysteresis value for max limit + * @min_alarm: sensor temperature min alarm + * @max_alarm: sensor temperature max alarm + * @work: delayed work scheduled to monitor temperature periodically + * @work_active: True if work is active + * @lock: mutex + * @monitored_sensors: number of monitored sensors + * @plat_data: private usage, usually points to platform specific data + */ +struct abx500_temp { + struct platform_device *pdev; + struct device *hwmon_dev; + struct abx500_temp_ops ops; + u8 gpadc_addr[NUM_SENSORS]; + unsigned long min[NUM_SENSORS]; + unsigned long max[NUM_SENSORS]; + unsigned long max_hyst[NUM_SENSORS]; + bool min_alarm[NUM_SENSORS]; + bool max_alarm[NUM_SENSORS]; + struct delayed_work work; + bool work_active; + struct mutex lock; + int monitored_sensors; + void *plat_data; +}; + +int abx500_hwmon_init(struct abx500_temp *data); + +#endif /* _ABX500_H */ diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c new file mode 100644 index 000000000..2f40da04a --- /dev/null +++ b/drivers/hwmon/acpi_power_meter.c @@ -0,0 +1,1018 @@ +/* + * A hwmon driver for ACPI 4.0 power meters + * Copyright (C) 2009 IBM + * + * Author: Darrick J. Wong + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ACPI_POWER_METER_NAME "power_meter" +ACPI_MODULE_NAME(ACPI_POWER_METER_NAME); +#define ACPI_POWER_METER_DEVICE_NAME "Power Meter" +#define ACPI_POWER_METER_CLASS "pwr_meter_resource" + +#define NUM_SENSORS 17 + +#define POWER_METER_CAN_MEASURE (1 << 0) +#define POWER_METER_CAN_TRIP (1 << 1) +#define POWER_METER_CAN_CAP (1 << 2) +#define POWER_METER_CAN_NOTIFY (1 << 3) +#define POWER_METER_IS_BATTERY (1 << 8) +#define UNKNOWN_HYSTERESIS 0xFFFFFFFF + +#define METER_NOTIFY_CONFIG 0x80 +#define METER_NOTIFY_TRIP 0x81 +#define METER_NOTIFY_CAP 0x82 +#define METER_NOTIFY_CAPPING 0x83 +#define METER_NOTIFY_INTERVAL 0x84 + +#define POWER_AVERAGE_NAME "power1_average" +#define POWER_CAP_NAME "power1_cap" +#define POWER_AVG_INTERVAL_NAME "power1_average_interval" +#define POWER_ALARM_NAME "power1_alarm" + +static int cap_in_hardware; +static bool force_cap_on; + +static int can_cap_in_hardware(void) +{ + return force_cap_on || cap_in_hardware; +} + +static const struct acpi_device_id power_meter_ids[] = { + {"ACPI000D", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, power_meter_ids); + +struct acpi_power_meter_capabilities { + u64 flags; + u64 units; + u64 type; + u64 accuracy; + u64 sampling_time; + u64 min_avg_interval; + u64 max_avg_interval; + u64 hysteresis; + u64 configurable_cap; + u64 min_cap; + u64 max_cap; +}; + +struct acpi_power_meter_resource { + struct acpi_device *acpi_dev; + acpi_bus_id name; + struct mutex lock; + struct device *hwmon_dev; + struct acpi_power_meter_capabilities caps; + acpi_string model_number; + acpi_string serial_number; + acpi_string oem_info; + u64 power; + u64 cap; + u64 avg_interval; + int sensors_valid; + unsigned long sensors_last_updated; + struct sensor_device_attribute sensors[NUM_SENSORS]; + int num_sensors; + s64 trip[2]; + int num_domain_devices; + struct acpi_device **domain_devices; + struct kobject *holders_dir; +}; + +struct sensor_template { + char *label; + ssize_t (*show)(struct device *dev, + struct device_attribute *devattr, + char *buf); + ssize_t (*set)(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count); + int index; +}; + +/* Averaging interval */ +static int update_avg_interval(struct acpi_power_meter_resource *resource) +{ + unsigned long long data; + acpi_status status; + + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GAI", + NULL, &data); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GAI")); + return -ENODEV; + } + + resource->avg_interval = data; + return 0; +} + +static ssize_t show_avg_interval(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + + mutex_lock(&resource->lock); + update_avg_interval(resource); + mutex_unlock(&resource->lock); + + return sprintf(buf, "%llu\n", resource->avg_interval); +} + +static ssize_t set_avg_interval(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + union acpi_object arg0 = { ACPI_TYPE_INTEGER }; + struct acpi_object_list args = { 1, &arg0 }; + int res; + unsigned long temp; + unsigned long long data; + acpi_status status; + + res = kstrtoul(buf, 10, &temp); + if (res) + return res; + + if (temp > resource->caps.max_avg_interval || + temp < resource->caps.min_avg_interval) + return -EINVAL; + arg0.integer.value = temp; + + mutex_lock(&resource->lock); + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI", + &args, &data); + if (!ACPI_FAILURE(status)) + resource->avg_interval = temp; + mutex_unlock(&resource->lock); + + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PAI")); + return -EINVAL; + } + + /* _PAI returns 0 on success, nonzero otherwise */ + if (data) + return -EINVAL; + + return count; +} + +/* Cap functions */ +static int update_cap(struct acpi_power_meter_resource *resource) +{ + unsigned long long data; + acpi_status status; + + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GHL", + NULL, &data); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GHL")); + return -ENODEV; + } + + resource->cap = data; + return 0; +} + +static ssize_t show_cap(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + + mutex_lock(&resource->lock); + update_cap(resource); + mutex_unlock(&resource->lock); + + return sprintf(buf, "%llu\n", resource->cap * 1000); +} + +static ssize_t set_cap(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + union acpi_object arg0 = { ACPI_TYPE_INTEGER }; + struct acpi_object_list args = { 1, &arg0 }; + int res; + unsigned long temp; + unsigned long long data; + acpi_status status; + + res = kstrtoul(buf, 10, &temp); + if (res) + return res; + + temp = DIV_ROUND_CLOSEST(temp, 1000); + if (temp > resource->caps.max_cap || temp < resource->caps.min_cap) + return -EINVAL; + arg0.integer.value = temp; + + mutex_lock(&resource->lock); + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL", + &args, &data); + if (!ACPI_FAILURE(status)) + resource->cap = temp; + mutex_unlock(&resource->lock); + + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SHL")); + return -EINVAL; + } + + /* _SHL returns 0 on success, nonzero otherwise */ + if (data) + return -EINVAL; + + return count; +} + +/* Power meter trip points */ +static int set_acpi_trip(struct acpi_power_meter_resource *resource) +{ + union acpi_object arg_objs[] = { + {ACPI_TYPE_INTEGER}, + {ACPI_TYPE_INTEGER} + }; + struct acpi_object_list args = { 2, arg_objs }; + unsigned long long data; + acpi_status status; + + /* Both trip levels must be set */ + if (resource->trip[0] < 0 || resource->trip[1] < 0) + return 0; + + /* This driver stores min, max; ACPI wants max, min. */ + arg_objs[0].integer.value = resource->trip[1]; + arg_objs[1].integer.value = resource->trip[0]; + + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PTP", + &args, &data); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PTP")); + return -EINVAL; + } + + /* _PTP returns 0 on success, nonzero otherwise */ + if (data) + return -EINVAL; + + return 0; +} + +static ssize_t set_trip(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + int res; + unsigned long temp; + + res = kstrtoul(buf, 10, &temp); + if (res) + return res; + + temp = DIV_ROUND_CLOSEST(temp, 1000); + + mutex_lock(&resource->lock); + resource->trip[attr->index - 7] = temp; + res = set_acpi_trip(resource); + mutex_unlock(&resource->lock); + + if (res) + return res; + + return count; +} + +/* Power meter */ +static int update_meter(struct acpi_power_meter_resource *resource) +{ + unsigned long long data; + acpi_status status; + unsigned long local_jiffies = jiffies; + + if (time_before(local_jiffies, resource->sensors_last_updated + + msecs_to_jiffies(resource->caps.sampling_time)) && + resource->sensors_valid) + return 0; + + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PMM", + NULL, &data); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMM")); + return -ENODEV; + } + + resource->power = data; + resource->sensors_valid = 1; + resource->sensors_last_updated = jiffies; + return 0; +} + +static ssize_t show_power(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + + mutex_lock(&resource->lock); + update_meter(resource); + mutex_unlock(&resource->lock); + + return sprintf(buf, "%llu\n", resource->power * 1000); +} + +/* Miscellaneous */ +static ssize_t show_str(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + acpi_string val; + + switch (attr->index) { + case 0: + val = resource->model_number; + break; + case 1: + val = resource->serial_number; + break; + case 2: + val = resource->oem_info; + break; + default: + WARN(1, "Implementation error: unexpected attribute index %d\n", + attr->index); + val = ""; + break; + } + + return sprintf(buf, "%s\n", val); +} + +static ssize_t show_val(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + u64 val = 0; + + switch (attr->index) { + case 0: + val = resource->caps.min_avg_interval; + break; + case 1: + val = resource->caps.max_avg_interval; + break; + case 2: + val = resource->caps.min_cap * 1000; + break; + case 3: + val = resource->caps.max_cap * 1000; + break; + case 4: + if (resource->caps.hysteresis == UNKNOWN_HYSTERESIS) + return sprintf(buf, "unknown\n"); + + val = resource->caps.hysteresis * 1000; + break; + case 5: + if (resource->caps.flags & POWER_METER_IS_BATTERY) + val = 1; + else + val = 0; + break; + case 6: + if (resource->power > resource->cap) + val = 1; + else + val = 0; + break; + case 7: + case 8: + if (resource->trip[attr->index - 7] < 0) + return sprintf(buf, "unknown\n"); + + val = resource->trip[attr->index - 7] * 1000; + break; + default: + WARN(1, "Implementation error: unexpected attribute index %d\n", + attr->index); + break; + } + + return sprintf(buf, "%llu\n", val); +} + +static ssize_t show_accuracy(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + unsigned int acc = resource->caps.accuracy; + + return sprintf(buf, "%u.%u%%\n", acc / 1000, acc % 1000); +} + +static ssize_t show_name(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + return sprintf(buf, "%s\n", ACPI_POWER_METER_NAME); +} + +#define RO_SENSOR_TEMPLATE(_label, _show, _index) \ + { \ + .label = _label, \ + .show = _show, \ + .index = _index, \ + } + +#define RW_SENSOR_TEMPLATE(_label, _show, _set, _index) \ + { \ + .label = _label, \ + .show = _show, \ + .set = _set, \ + .index = _index, \ + } + +/* Sensor descriptions. If you add a sensor, update NUM_SENSORS above! */ +static struct sensor_template meter_attrs[] = { + RO_SENSOR_TEMPLATE(POWER_AVERAGE_NAME, show_power, 0), + RO_SENSOR_TEMPLATE("power1_accuracy", show_accuracy, 0), + RO_SENSOR_TEMPLATE("power1_average_interval_min", show_val, 0), + RO_SENSOR_TEMPLATE("power1_average_interval_max", show_val, 1), + RO_SENSOR_TEMPLATE("power1_is_battery", show_val, 5), + RW_SENSOR_TEMPLATE(POWER_AVG_INTERVAL_NAME, show_avg_interval, + set_avg_interval, 0), + {}, +}; + +static struct sensor_template misc_cap_attrs[] = { + RO_SENSOR_TEMPLATE("power1_cap_min", show_val, 2), + RO_SENSOR_TEMPLATE("power1_cap_max", show_val, 3), + RO_SENSOR_TEMPLATE("power1_cap_hyst", show_val, 4), + RO_SENSOR_TEMPLATE(POWER_ALARM_NAME, show_val, 6), + {}, +}; + +static struct sensor_template ro_cap_attrs[] = { + RO_SENSOR_TEMPLATE(POWER_CAP_NAME, show_cap, 0), + {}, +}; + +static struct sensor_template rw_cap_attrs[] = { + RW_SENSOR_TEMPLATE(POWER_CAP_NAME, show_cap, set_cap, 0), + {}, +}; + +static struct sensor_template trip_attrs[] = { + RW_SENSOR_TEMPLATE("power1_average_min", show_val, set_trip, 7), + RW_SENSOR_TEMPLATE("power1_average_max", show_val, set_trip, 8), + {}, +}; + +static struct sensor_template misc_attrs[] = { + RO_SENSOR_TEMPLATE("name", show_name, 0), + RO_SENSOR_TEMPLATE("power1_model_number", show_str, 0), + RO_SENSOR_TEMPLATE("power1_oem_info", show_str, 2), + RO_SENSOR_TEMPLATE("power1_serial_number", show_str, 1), + {}, +}; + +#undef RO_SENSOR_TEMPLATE +#undef RW_SENSOR_TEMPLATE + +/* Read power domain data */ +static void remove_domain_devices(struct acpi_power_meter_resource *resource) +{ + int i; + + if (!resource->num_domain_devices) + return; + + for (i = 0; i < resource->num_domain_devices; i++) { + struct acpi_device *obj = resource->domain_devices[i]; + if (!obj) + continue; + + sysfs_remove_link(resource->holders_dir, + kobject_name(&obj->dev.kobj)); + put_device(&obj->dev); + } + + kfree(resource->domain_devices); + kobject_put(resource->holders_dir); + resource->num_domain_devices = 0; +} + +static int read_domain_devices(struct acpi_power_meter_resource *resource) +{ + int res = 0; + int i; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *pss; + acpi_status status; + + status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMD", NULL, + &buffer); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMD")); + return -ENODEV; + } + + pss = buffer.pointer; + if (!pss || + pss->type != ACPI_TYPE_PACKAGE) { + dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME + "Invalid _PMD data\n"); + res = -EFAULT; + goto end; + } + + if (!pss->package.count) + goto end; + + resource->domain_devices = kcalloc(pss->package.count, + sizeof(struct acpi_device *), + GFP_KERNEL); + if (!resource->domain_devices) { + res = -ENOMEM; + goto end; + } + + resource->holders_dir = kobject_create_and_add("measures", + &resource->acpi_dev->dev.kobj); + if (!resource->holders_dir) { + res = -ENOMEM; + goto exit_free; + } + + resource->num_domain_devices = pss->package.count; + + for (i = 0; i < pss->package.count; i++) { + struct acpi_device *obj; + union acpi_object *element = &(pss->package.elements[i]); + + /* Refuse non-references */ + if (element->type != ACPI_TYPE_LOCAL_REFERENCE) + continue; + + /* Create a symlink to domain objects */ + resource->domain_devices[i] = NULL; + if (acpi_bus_get_device(element->reference.handle, + &resource->domain_devices[i])) + continue; + + obj = resource->domain_devices[i]; + get_device(&obj->dev); + + res = sysfs_create_link(resource->holders_dir, &obj->dev.kobj, + kobject_name(&obj->dev.kobj)); + if (res) { + put_device(&obj->dev); + resource->domain_devices[i] = NULL; + } + } + + res = 0; + goto end; + +exit_free: + kfree(resource->domain_devices); +end: + kfree(buffer.pointer); + return res; +} + +/* Registration and deregistration */ +static int register_attrs(struct acpi_power_meter_resource *resource, + struct sensor_template *attrs) +{ + struct device *dev = &resource->acpi_dev->dev; + struct sensor_device_attribute *sensors = + &resource->sensors[resource->num_sensors]; + int res = 0; + + while (attrs->label) { + sensors->dev_attr.attr.name = attrs->label; + sensors->dev_attr.attr.mode = S_IRUGO; + sensors->dev_attr.show = attrs->show; + sensors->index = attrs->index; + + if (attrs->set) { + sensors->dev_attr.attr.mode |= S_IWUSR; + sensors->dev_attr.store = attrs->set; + } + + sysfs_attr_init(&sensors->dev_attr.attr); + res = device_create_file(dev, &sensors->dev_attr); + if (res) { + sensors->dev_attr.attr.name = NULL; + goto error; + } + sensors++; + resource->num_sensors++; + attrs++; + } + +error: + return res; +} + +static void remove_attrs(struct acpi_power_meter_resource *resource) +{ + int i; + + for (i = 0; i < resource->num_sensors; i++) { + if (!resource->sensors[i].dev_attr.attr.name) + continue; + device_remove_file(&resource->acpi_dev->dev, + &resource->sensors[i].dev_attr); + } + + remove_domain_devices(resource); + + resource->num_sensors = 0; +} + +static int setup_attrs(struct acpi_power_meter_resource *resource) +{ + int res = 0; + + res = read_domain_devices(resource); + if (res) + return res; + + if (resource->caps.flags & POWER_METER_CAN_MEASURE) { + res = register_attrs(resource, meter_attrs); + if (res) + goto error; + } + + if (resource->caps.flags & POWER_METER_CAN_CAP) { + if (!can_cap_in_hardware()) { + dev_warn(&resource->acpi_dev->dev, + "Ignoring unsafe software power cap!\n"); + goto skip_unsafe_cap; + } + + if (resource->caps.configurable_cap) + res = register_attrs(resource, rw_cap_attrs); + else + res = register_attrs(resource, ro_cap_attrs); + + if (res) + goto error; + + res = register_attrs(resource, misc_cap_attrs); + if (res) + goto error; + } + +skip_unsafe_cap: + if (resource->caps.flags & POWER_METER_CAN_TRIP) { + res = register_attrs(resource, trip_attrs); + if (res) + goto error; + } + + res = register_attrs(resource, misc_attrs); + if (res) + goto error; + + return res; +error: + remove_attrs(resource); + return res; +} + +static void free_capabilities(struct acpi_power_meter_resource *resource) +{ + acpi_string *str; + int i; + + str = &resource->model_number; + for (i = 0; i < 3; i++, str++) + kfree(*str); +} + +static int read_capabilities(struct acpi_power_meter_resource *resource) +{ + int res = 0; + int i; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer state = { 0, NULL }; + struct acpi_buffer format = { sizeof("NNNNNNNNNNN"), "NNNNNNNNNNN" }; + union acpi_object *pss; + acpi_string *str; + acpi_status status; + + status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMC", NULL, + &buffer); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMC")); + return -ENODEV; + } + + pss = buffer.pointer; + if (!pss || + pss->type != ACPI_TYPE_PACKAGE || + pss->package.count != 14) { + dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME + "Invalid _PMC data\n"); + res = -EFAULT; + goto end; + } + + /* Grab all the integer data at once */ + state.length = sizeof(struct acpi_power_meter_capabilities); + state.pointer = &resource->caps; + + status = acpi_extract_package(pss, &format, &state); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Invalid data")); + res = -EFAULT; + goto end; + } + + if (resource->caps.units) { + dev_err(&resource->acpi_dev->dev, ACPI_POWER_METER_NAME + "Unknown units %llu.\n", + resource->caps.units); + res = -EINVAL; + goto end; + } + + /* Grab the string data */ + str = &resource->model_number; + + for (i = 11; i < 14; i++) { + union acpi_object *element = &(pss->package.elements[i]); + + if (element->type != ACPI_TYPE_STRING) { + res = -EINVAL; + goto error; + } + + *str = kcalloc(element->string.length + 1, sizeof(u8), + GFP_KERNEL); + if (!*str) { + res = -ENOMEM; + goto error; + } + + strncpy(*str, element->string.pointer, element->string.length); + str++; + } + + dev_info(&resource->acpi_dev->dev, "Found ACPI power meter.\n"); + goto end; +error: + str = &resource->model_number; + for (i = 0; i < 3; i++, str++) + kfree(*str); +end: + kfree(buffer.pointer); + return res; +} + +/* Handle ACPI event notifications */ +static void acpi_power_meter_notify(struct acpi_device *device, u32 event) +{ + struct acpi_power_meter_resource *resource; + int res; + + if (!device || !acpi_driver_data(device)) + return; + + resource = acpi_driver_data(device); + + mutex_lock(&resource->lock); + switch (event) { + case METER_NOTIFY_CONFIG: + free_capabilities(resource); + res = read_capabilities(resource); + if (res) + break; + + remove_attrs(resource); + setup_attrs(resource); + break; + case METER_NOTIFY_TRIP: + sysfs_notify(&device->dev.kobj, NULL, POWER_AVERAGE_NAME); + update_meter(resource); + break; + case METER_NOTIFY_CAP: + sysfs_notify(&device->dev.kobj, NULL, POWER_CAP_NAME); + update_cap(resource); + break; + case METER_NOTIFY_INTERVAL: + sysfs_notify(&device->dev.kobj, NULL, POWER_AVG_INTERVAL_NAME); + update_avg_interval(resource); + break; + case METER_NOTIFY_CAPPING: + sysfs_notify(&device->dev.kobj, NULL, POWER_ALARM_NAME); + dev_info(&device->dev, "Capping in progress.\n"); + break; + default: + WARN(1, "Unexpected event %d\n", event); + break; + } + mutex_unlock(&resource->lock); + + acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS, + dev_name(&device->dev), event, 0); +} + +static int acpi_power_meter_add(struct acpi_device *device) +{ + int res; + struct acpi_power_meter_resource *resource; + + if (!device) + return -EINVAL; + + resource = kzalloc(sizeof(struct acpi_power_meter_resource), + GFP_KERNEL); + if (!resource) + return -ENOMEM; + + resource->sensors_valid = 0; + resource->acpi_dev = device; + mutex_init(&resource->lock); + strcpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME); + strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS); + device->driver_data = resource; + + free_capabilities(resource); + res = read_capabilities(resource); + if (res) + goto exit_free; + + resource->trip[0] = resource->trip[1] = -1; + + res = setup_attrs(resource); + if (res) + goto exit_free_capability; + + resource->hwmon_dev = hwmon_device_register(&device->dev); + if (IS_ERR(resource->hwmon_dev)) { + res = PTR_ERR(resource->hwmon_dev); + goto exit_remove; + } + + res = 0; + goto exit; + +exit_remove: + remove_attrs(resource); +exit_free_capability: + free_capabilities(resource); +exit_free: + kfree(resource); +exit: + return res; +} + +static int acpi_power_meter_remove(struct acpi_device *device) +{ + struct acpi_power_meter_resource *resource; + + if (!device || !acpi_driver_data(device)) + return -EINVAL; + + resource = acpi_driver_data(device); + hwmon_device_unregister(resource->hwmon_dev); + + free_capabilities(resource); + remove_attrs(resource); + + kfree(resource); + return 0; +} + +#ifdef CONFIG_PM_SLEEP + +static int acpi_power_meter_resume(struct device *dev) +{ + struct acpi_power_meter_resource *resource; + + if (!dev) + return -EINVAL; + + resource = acpi_driver_data(to_acpi_device(dev)); + if (!resource) + return -EINVAL; + + free_capabilities(resource); + read_capabilities(resource); + + return 0; +} + +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(acpi_power_meter_pm, NULL, acpi_power_meter_resume); + +static struct acpi_driver acpi_power_meter_driver = { + .name = "power_meter", + .class = ACPI_POWER_METER_CLASS, + .ids = power_meter_ids, + .ops = { + .add = acpi_power_meter_add, + .remove = acpi_power_meter_remove, + .notify = acpi_power_meter_notify, + }, + .drv.pm = &acpi_power_meter_pm, +}; + +/* Module init/exit routines */ +static int __init enable_cap_knobs(const struct dmi_system_id *d) +{ + cap_in_hardware = 1; + return 0; +} + +static const struct dmi_system_id pm_dmi_table[] __initconst = { + { + enable_cap_knobs, "IBM Active Energy Manager", + { + DMI_MATCH(DMI_SYS_VENDOR, "IBM") + }, + }, + {} +}; + +static int __init acpi_power_meter_init(void) +{ + int result; + + if (acpi_disabled) + return -ENODEV; + + dmi_check_system(pm_dmi_table); + + result = acpi_bus_register_driver(&acpi_power_meter_driver); + if (result < 0) + return result; + + return 0; +} + +static void __exit acpi_power_meter_exit(void) +{ + acpi_bus_unregister_driver(&acpi_power_meter_driver); +} + +MODULE_AUTHOR("Darrick J. Wong "); +MODULE_DESCRIPTION("ACPI 4.0 power meter driver"); +MODULE_LICENSE("GPL"); + +module_param(force_cap_on, bool, 0644); +MODULE_PARM_DESC(force_cap_on, "Enable power cap even it is unsafe to do so."); + +module_init(acpi_power_meter_init); +module_exit(acpi_power_meter_exit); diff --git a/drivers/hwmon/ad7314.c b/drivers/hwmon/ad7314.c new file mode 100644 index 000000000..8ea35932f --- /dev/null +++ b/drivers/hwmon/ad7314.c @@ -0,0 +1,136 @@ +/* + * AD7314 digital temperature sensor driver for AD7314, ADT7301 and ADT7302 + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + * + * Conversion to hwmon from IIO done by Jonathan Cameron + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * AD7314 temperature masks + */ +#define AD7314_TEMP_MASK 0x7FE0 +#define AD7314_TEMP_SHIFT 5 + +/* + * ADT7301 and ADT7302 temperature masks + */ +#define ADT7301_TEMP_MASK 0x3FFF + +enum ad7314_variant { + adt7301, + adt7302, + ad7314, +}; + +struct ad7314_data { + struct spi_device *spi_dev; + u16 rx ____cacheline_aligned; +}; + +static int ad7314_spi_read(struct ad7314_data *chip) +{ + int ret; + + ret = spi_read(chip->spi_dev, (u8 *)&chip->rx, sizeof(chip->rx)); + if (ret < 0) { + dev_err(&chip->spi_dev->dev, "SPI read error\n"); + return ret; + } + + return be16_to_cpu(chip->rx); +} + +static ssize_t ad7314_show_temperature(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ad7314_data *chip = dev_get_drvdata(dev); + s16 data; + int ret; + + ret = ad7314_spi_read(chip); + if (ret < 0) + return ret; + switch (spi_get_device_id(chip->spi_dev)->driver_data) { + case ad7314: + data = (ret & AD7314_TEMP_MASK) >> AD7314_TEMP_SHIFT; + data = sign_extend32(data, 9); + + return sprintf(buf, "%d\n", 250 * data); + case adt7301: + case adt7302: + /* + * Documented as a 13 bit twos complement register + * with a sign bit - which is a 14 bit 2's complement + * register. 1lsb - 31.25 milli degrees centigrade + */ + data = ret & ADT7301_TEMP_MASK; + data = sign_extend32(data, 13); + + return sprintf(buf, "%d\n", + DIV_ROUND_CLOSEST(data * 3125, 100)); + default: + return -EINVAL; + } +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, + ad7314_show_temperature, NULL, 0); + +static struct attribute *ad7314_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(ad7314); + +static int ad7314_probe(struct spi_device *spi_dev) +{ + struct ad7314_data *chip; + struct device *hwmon_dev; + + chip = devm_kzalloc(&spi_dev->dev, sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + chip->spi_dev = spi_dev; + hwmon_dev = devm_hwmon_device_register_with_groups(&spi_dev->dev, + spi_dev->modalias, + chip, ad7314_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct spi_device_id ad7314_id[] = { + { "adt7301", adt7301 }, + { "adt7302", adt7302 }, + { "ad7314", ad7314 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7314_id); + +static struct spi_driver ad7314_driver = { + .driver = { + .name = "ad7314", + }, + .probe = ad7314_probe, + .id_table = ad7314_id, +}; + +module_spi_driver(ad7314_driver); + +MODULE_AUTHOR("Sonic Zhang "); +MODULE_DESCRIPTION("Analog Devices AD7314, ADT7301 and ADT7302 digital temperature sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/ad7414.c b/drivers/hwmon/ad7414.c new file mode 100644 index 000000000..cec227f13 --- /dev/null +++ b/drivers/hwmon/ad7414.c @@ -0,0 +1,241 @@ +/* + * An hwmon driver for the Analog Devices AD7414 + * + * Copyright 2006 Stefan Roese , DENX Software Engineering + * + * Copyright (c) 2008 PIKA Technologies + * Sean MacLennan + * + * Copyright (c) 2008 Spansion Inc. + * Frank Edelhaeuser + * (converted to "new style" I2C driver model, removed checkpatch.pl warnings) + * + * Based on ad7418.c + * Copyright 2006 Tower Technologies, Alessandro Zummo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* AD7414 registers */ +#define AD7414_REG_TEMP 0x00 +#define AD7414_REG_CONF 0x01 +#define AD7414_REG_T_HIGH 0x02 +#define AD7414_REG_T_LOW 0x03 + +static u8 AD7414_REG_LIMIT[] = { AD7414_REG_T_HIGH, AD7414_REG_T_LOW }; + +struct ad7414_data { + struct i2c_client *client; + struct mutex lock; /* atomic read data updates */ + char valid; /* !=0 if following fields are valid */ + unsigned long next_update; /* In jiffies */ + s16 temp_input; /* Register values */ + s8 temps[ARRAY_SIZE(AD7414_REG_LIMIT)]; +}; + +/* REG: (0.25C/bit, two's complement) << 6 */ +static inline int ad7414_temp_from_reg(s16 reg) +{ + /* + * use integer division instead of equivalent right shift to + * guarantee arithmetic shift and preserve the sign + */ + return ((int)reg / 64) * 250; +} + +static inline int ad7414_read(struct i2c_client *client, u8 reg) +{ + if (reg == AD7414_REG_TEMP) + return i2c_smbus_read_word_swapped(client, reg); + else + return i2c_smbus_read_byte_data(client, reg); +} + +static inline int ad7414_write(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static struct ad7414_data *ad7414_update_device(struct device *dev) +{ + struct ad7414_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + mutex_lock(&data->lock); + + if (time_after(jiffies, data->next_update) || !data->valid) { + int value, i; + + dev_dbg(&client->dev, "starting ad7414 update\n"); + + value = ad7414_read(client, AD7414_REG_TEMP); + if (value < 0) + dev_dbg(&client->dev, "AD7414_REG_TEMP err %d\n", + value); + else + data->temp_input = value; + + for (i = 0; i < ARRAY_SIZE(AD7414_REG_LIMIT); ++i) { + value = ad7414_read(client, AD7414_REG_LIMIT[i]); + if (value < 0) + dev_dbg(&client->dev, "AD7414 reg %d err %d\n", + AD7414_REG_LIMIT[i], value); + else + data->temps[i] = value; + } + + data->next_update = jiffies + HZ + HZ / 2; + data->valid = 1; + } + + mutex_unlock(&data->lock); + + return data; +} + +static ssize_t show_temp_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ad7414_data *data = ad7414_update_device(dev); + return sprintf(buf, "%d\n", ad7414_temp_from_reg(data->temp_input)); +} +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL, 0); + +static ssize_t show_max_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + struct ad7414_data *data = ad7414_update_device(dev); + return sprintf(buf, "%d\n", data->temps[index] * 1000); +} + +static ssize_t set_max_min(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ad7414_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int index = to_sensor_dev_attr(attr)->index; + u8 reg = AD7414_REG_LIMIT[index]; + long temp; + int ret = kstrtol(buf, 10, &temp); + + if (ret < 0) + return ret; + + temp = clamp_val(temp, -40000, 85000); + temp = (temp + (temp < 0 ? -500 : 500)) / 1000; + + mutex_lock(&data->lock); + data->temps[index] = temp; + ad7414_write(client, reg, temp); + mutex_unlock(&data->lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, + show_max_min, set_max_min, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, + show_max_min, set_max_min, 1); + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct ad7414_data *data = ad7414_update_device(dev); + int value = (data->temp_input >> bitnr) & 1; + return sprintf(buf, "%d\n", value); +} + +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 4); + +static struct attribute *ad7414_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(ad7414); + +static int ad7414_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + struct device *dev = &client->dev; + struct ad7414_data *data; + struct device *hwmon_dev; + int conf; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -EOPNOTSUPP; + + data = devm_kzalloc(dev, sizeof(struct ad7414_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->lock); + + dev_info(&client->dev, "chip found\n"); + + /* Make sure the chip is powered up. */ + conf = i2c_smbus_read_byte_data(client, AD7414_REG_CONF); + if (conf < 0) + dev_warn(dev, "ad7414_probe unable to read config register.\n"); + else { + conf &= ~(1 << 7); + i2c_smbus_write_byte_data(client, AD7414_REG_CONF, conf); + } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + client->name, + data, ad7414_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id ad7414_id[] = { + { "ad7414", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, ad7414_id); + +static const struct of_device_id ad7414_of_match[] = { + { .compatible = "ad,ad7414" }, + { }, +}; +MODULE_DEVICE_TABLE(of, ad7414_of_match); + +static struct i2c_driver ad7414_driver = { + .driver = { + .name = "ad7414", + .of_match_table = of_match_ptr(ad7414_of_match), + }, + .probe = ad7414_probe, + .id_table = ad7414_id, +}; + +module_i2c_driver(ad7414_driver); + +MODULE_AUTHOR("Stefan Roese , " + "Frank Edelhaeuser "); + +MODULE_DESCRIPTION("AD7414 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ad7418.c b/drivers/hwmon/ad7418.c new file mode 100644 index 000000000..a01b731ba --- /dev/null +++ b/drivers/hwmon/ad7418.c @@ -0,0 +1,275 @@ +/* + * An hwmon driver for the Analog Devices AD7416/17/18 + * Copyright (C) 2006-07 Tower Technologies + * + * Author: Alessandro Zummo + * + * Based on lm75.c + * Copyright (C) 1998-99 Frodo Looijaard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, + * as published by the Free Software Foundation - version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lm75.h" + +#define DRV_VERSION "0.4" + +enum chips { ad7416, ad7417, ad7418 }; + +/* AD7418 registers */ +#define AD7418_REG_TEMP_IN 0x00 +#define AD7418_REG_CONF 0x01 +#define AD7418_REG_TEMP_HYST 0x02 +#define AD7418_REG_TEMP_OS 0x03 +#define AD7418_REG_ADC 0x04 +#define AD7418_REG_CONF2 0x05 + +#define AD7418_REG_ADC_CH(x) ((x) << 5) +#define AD7418_CH_TEMP AD7418_REG_ADC_CH(0) + +static const u8 AD7418_REG_TEMP[] = { AD7418_REG_TEMP_IN, + AD7418_REG_TEMP_HYST, + AD7418_REG_TEMP_OS }; + +struct ad7418_data { + struct i2c_client *client; + enum chips type; + struct mutex lock; + int adc_max; /* number of ADC channels */ + char valid; + unsigned long last_updated; /* In jiffies */ + s16 temp[3]; /* Register values */ + u16 in[4]; +}; + +static struct ad7418_data *ad7418_update_device(struct device *dev) +{ + struct ad7418_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + mutex_lock(&data->lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + u8 cfg; + int i, ch; + + /* read config register and clear channel bits */ + cfg = i2c_smbus_read_byte_data(client, AD7418_REG_CONF); + cfg &= 0x1F; + + i2c_smbus_write_byte_data(client, AD7418_REG_CONF, + cfg | AD7418_CH_TEMP); + udelay(30); + + for (i = 0; i < 3; i++) { + data->temp[i] = + i2c_smbus_read_word_swapped(client, + AD7418_REG_TEMP[i]); + } + + for (i = 0, ch = 4; i < data->adc_max; i++, ch--) { + i2c_smbus_write_byte_data(client, + AD7418_REG_CONF, + cfg | AD7418_REG_ADC_CH(ch)); + + udelay(15); + data->in[data->adc_max - 1 - i] = + i2c_smbus_read_word_swapped(client, + AD7418_REG_ADC); + } + + /* restore old configuration value */ + i2c_smbus_write_word_swapped(client, AD7418_REG_CONF, cfg); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->lock); + + return data; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct ad7418_data *data = ad7418_update_device(dev); + return sprintf(buf, "%d\n", + LM75_TEMP_FROM_REG(data->temp[attr->index])); +} + +static ssize_t show_adc(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct ad7418_data *data = ad7418_update_device(dev); + + return sprintf(buf, "%d\n", + ((data->in[attr->index] >> 6) * 2500 + 512) / 1024); +} + +static ssize_t set_temp(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct ad7418_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + int ret = kstrtol(buf, 10, &temp); + + if (ret < 0) + return ret; + + mutex_lock(&data->lock); + data->temp[attr->index] = LM75_TEMP_TO_REG(temp); + i2c_smbus_write_word_swapped(client, + AD7418_REG_TEMP[attr->index], + data->temp[attr->index]); + mutex_unlock(&data->lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, + show_temp, set_temp, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, + show_temp, set_temp, 2); + +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_adc, NULL, 0); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_adc, NULL, 1); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_adc, NULL, 2); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_adc, NULL, 3); + +static struct attribute *ad7416_attrs[] = { + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(ad7416); + +static struct attribute *ad7417_attrs[] = { + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(ad7417); + +static struct attribute *ad7418_attrs[] = { + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(ad7418); + +static void ad7418_init_client(struct i2c_client *client) +{ + struct ad7418_data *data = i2c_get_clientdata(client); + + int reg = i2c_smbus_read_byte_data(client, AD7418_REG_CONF); + if (reg < 0) { + dev_err(&client->dev, "cannot read configuration register\n"); + } else { + dev_info(&client->dev, "configuring for mode 1\n"); + i2c_smbus_write_byte_data(client, AD7418_REG_CONF, reg & 0xfe); + + if (data->type == ad7417 || data->type == ad7418) + i2c_smbus_write_byte_data(client, + AD7418_REG_CONF2, 0x00); + } +} + +static int ad7418_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct i2c_adapter *adapter = client->adapter; + struct ad7418_data *data; + struct device *hwmon_dev; + const struct attribute_group **attr_groups = NULL; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -EOPNOTSUPP; + + data = devm_kzalloc(dev, sizeof(struct ad7418_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + + mutex_init(&data->lock); + data->client = client; + data->type = id->driver_data; + + switch (data->type) { + case ad7416: + data->adc_max = 0; + attr_groups = ad7416_groups; + break; + + case ad7417: + data->adc_max = 4; + attr_groups = ad7417_groups; + break; + + case ad7418: + data->adc_max = 1; + attr_groups = ad7418_groups; + break; + } + + dev_info(dev, "%s chip found\n", client->name); + + /* Initialize the AD7418 chip */ + ad7418_init_client(client); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + client->name, + data, attr_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id ad7418_id[] = { + { "ad7416", ad7416 }, + { "ad7417", ad7417 }, + { "ad7418", ad7418 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad7418_id); + +static struct i2c_driver ad7418_driver = { + .driver = { + .name = "ad7418", + }, + .probe = ad7418_probe, + .id_table = ad7418_id, +}; + +module_i2c_driver(ad7418_driver); + +MODULE_AUTHOR("Alessandro Zummo "); +MODULE_DESCRIPTION("AD7416/17/18 driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VERSION); diff --git a/drivers/hwmon/adc128d818.c b/drivers/hwmon/adc128d818.c new file mode 100644 index 000000000..bd2ca315c --- /dev/null +++ b/drivers/hwmon/adc128d818.c @@ -0,0 +1,572 @@ +/* + * Driver for TI ADC128D818 System Monitor with Temperature Sensor + * + * Copyright (c) 2014 Guenter Roeck + * + * Derived from lm80.c + * Copyright (C) 1998, 1999 Frodo Looijaard + * and Philip Edelbrock + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan + * The chip also supports addresses 0x35..0x37. Don't scan those addresses + * since they are also used by some EEPROMs, which may result in false + * positives. + */ +static const unsigned short normal_i2c[] = { + 0x1d, 0x1e, 0x1f, 0x2d, 0x2e, 0x2f, I2C_CLIENT_END }; + +/* registers */ +#define ADC128_REG_IN_MAX(nr) (0x2a + (nr) * 2) +#define ADC128_REG_IN_MIN(nr) (0x2b + (nr) * 2) +#define ADC128_REG_IN(nr) (0x20 + (nr)) + +#define ADC128_REG_TEMP 0x27 +#define ADC128_REG_TEMP_MAX 0x38 +#define ADC128_REG_TEMP_HYST 0x39 + +#define ADC128_REG_CONFIG 0x00 +#define ADC128_REG_ALARM 0x01 +#define ADC128_REG_MASK 0x03 +#define ADC128_REG_CONV_RATE 0x07 +#define ADC128_REG_ONESHOT 0x09 +#define ADC128_REG_SHUTDOWN 0x0a +#define ADC128_REG_CONFIG_ADV 0x0b +#define ADC128_REG_BUSY_STATUS 0x0c + +#define ADC128_REG_MAN_ID 0x3e +#define ADC128_REG_DEV_ID 0x3f + +/* No. of voltage entries in adc128_attrs */ +#define ADC128_ATTR_NUM_VOLT (8 * 4) + +/* Voltage inputs visible per operation mode */ +static const u8 num_inputs[] = { 7, 8, 4, 6 }; + +struct adc128_data { + struct i2c_client *client; + struct regulator *regulator; + int vref; /* Reference voltage in mV */ + struct mutex update_lock; + u8 mode; /* Operation mode */ + bool valid; /* true if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u16 in[3][8]; /* Register value, normalized to 12 bit + * 0: input voltage + * 1: min limit + * 2: max limit + */ + s16 temp[3]; /* Register value, normalized to 9 bit + * 0: sensor 1: limit 2: hyst + */ + u8 alarms; /* alarm register value */ +}; + +static struct adc128_data *adc128_update_device(struct device *dev) +{ + struct adc128_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + struct adc128_data *ret = data; + int i, rv; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + for (i = 0; i < num_inputs[data->mode]; i++) { + rv = i2c_smbus_read_word_swapped(client, + ADC128_REG_IN(i)); + if (rv < 0) + goto abort; + data->in[0][i] = rv >> 4; + + rv = i2c_smbus_read_byte_data(client, + ADC128_REG_IN_MIN(i)); + if (rv < 0) + goto abort; + data->in[1][i] = rv << 4; + + rv = i2c_smbus_read_byte_data(client, + ADC128_REG_IN_MAX(i)); + if (rv < 0) + goto abort; + data->in[2][i] = rv << 4; + } + + if (data->mode != 1) { + rv = i2c_smbus_read_word_swapped(client, + ADC128_REG_TEMP); + if (rv < 0) + goto abort; + data->temp[0] = rv >> 7; + + rv = i2c_smbus_read_byte_data(client, + ADC128_REG_TEMP_MAX); + if (rv < 0) + goto abort; + data->temp[1] = rv << 1; + + rv = i2c_smbus_read_byte_data(client, + ADC128_REG_TEMP_HYST); + if (rv < 0) + goto abort; + data->temp[2] = rv << 1; + } + + rv = i2c_smbus_read_byte_data(client, ADC128_REG_ALARM); + if (rv < 0) + goto abort; + data->alarms |= rv; + + data->last_updated = jiffies; + data->valid = true; + } + goto done; + +abort: + ret = ERR_PTR(rv); + data->valid = false; +done: + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t adc128_show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adc128_data *data = adc128_update_device(dev); + int index = to_sensor_dev_attr_2(attr)->index; + int nr = to_sensor_dev_attr_2(attr)->nr; + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = DIV_ROUND_CLOSEST(data->in[index][nr] * data->vref, 4095); + return sprintf(buf, "%d\n", val); +} + +static ssize_t adc128_set_in(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adc128_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr_2(attr)->index; + int nr = to_sensor_dev_attr_2(attr)->nr; + u8 reg, regval; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + mutex_lock(&data->update_lock); + /* 10 mV LSB on limit registers */ + regval = clamp_val(DIV_ROUND_CLOSEST(val, 10), 0, 255); + data->in[index][nr] = regval << 4; + reg = index == 1 ? ADC128_REG_IN_MIN(nr) : ADC128_REG_IN_MAX(nr); + i2c_smbus_write_byte_data(data->client, reg, regval); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t adc128_show_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adc128_data *data = adc128_update_device(dev); + int index = to_sensor_dev_attr(attr)->index; + int temp; + + if (IS_ERR(data)) + return PTR_ERR(data); + + temp = sign_extend32(data->temp[index], 8); + return sprintf(buf, "%d\n", temp * 500);/* 0.5 degrees C resolution */ +} + +static ssize_t adc128_set_temp(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adc128_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(attr)->index; + long val; + int err; + s8 regval; + + err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + mutex_lock(&data->update_lock); + regval = clamp_val(DIV_ROUND_CLOSEST(val, 1000), -128, 127); + data->temp[index] = regval << 1; + i2c_smbus_write_byte_data(data->client, + index == 1 ? ADC128_REG_TEMP_MAX + : ADC128_REG_TEMP_HYST, + regval); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t adc128_show_alarm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adc128_data *data = adc128_update_device(dev); + int mask = 1 << to_sensor_dev_attr(attr)->index; + u8 alarms; + + if (IS_ERR(data)) + return PTR_ERR(data); + + /* + * Clear an alarm after reporting it to user space. If it is still + * active, the next update sequence will set the alarm bit again. + */ + alarms = data->alarms; + data->alarms &= ~mask; + + return sprintf(buf, "%u\n", !!(alarms & mask)); +} + +static umode_t adc128_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct adc128_data *data = dev_get_drvdata(dev); + + if (index < ADC128_ATTR_NUM_VOLT) { + /* Voltage, visible according to num_inputs[] */ + if (index >= num_inputs[data->mode] * 4) + return 0; + } else { + /* Temperature, visible if not in mode 1 */ + if (data->mode == 1) + return 0; + } + + return attr->mode; +} + +static SENSOR_DEVICE_ATTR_2(in0_input, S_IRUGO, + adc128_show_in, NULL, 0, 0); +static SENSOR_DEVICE_ATTR_2(in0_min, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 0, 1); +static SENSOR_DEVICE_ATTR_2(in0_max, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 0, 2); + +static SENSOR_DEVICE_ATTR_2(in1_input, S_IRUGO, + adc128_show_in, NULL, 1, 0); +static SENSOR_DEVICE_ATTR_2(in1_min, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 1, 1); +static SENSOR_DEVICE_ATTR_2(in1_max, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 1, 2); + +static SENSOR_DEVICE_ATTR_2(in2_input, S_IRUGO, + adc128_show_in, NULL, 2, 0); +static SENSOR_DEVICE_ATTR_2(in2_min, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 2, 1); +static SENSOR_DEVICE_ATTR_2(in2_max, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 2, 2); + +static SENSOR_DEVICE_ATTR_2(in3_input, S_IRUGO, + adc128_show_in, NULL, 3, 0); +static SENSOR_DEVICE_ATTR_2(in3_min, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 3, 1); +static SENSOR_DEVICE_ATTR_2(in3_max, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 3, 2); + +static SENSOR_DEVICE_ATTR_2(in4_input, S_IRUGO, + adc128_show_in, NULL, 4, 0); +static SENSOR_DEVICE_ATTR_2(in4_min, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 4, 1); +static SENSOR_DEVICE_ATTR_2(in4_max, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 4, 2); + +static SENSOR_DEVICE_ATTR_2(in5_input, S_IRUGO, + adc128_show_in, NULL, 5, 0); +static SENSOR_DEVICE_ATTR_2(in5_min, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 5, 1); +static SENSOR_DEVICE_ATTR_2(in5_max, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 5, 2); + +static SENSOR_DEVICE_ATTR_2(in6_input, S_IRUGO, + adc128_show_in, NULL, 6, 0); +static SENSOR_DEVICE_ATTR_2(in6_min, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 6, 1); +static SENSOR_DEVICE_ATTR_2(in6_max, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 6, 2); + +static SENSOR_DEVICE_ATTR_2(in7_input, S_IRUGO, + adc128_show_in, NULL, 7, 0); +static SENSOR_DEVICE_ATTR_2(in7_min, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 7, 1); +static SENSOR_DEVICE_ATTR_2(in7_max, S_IWUSR | S_IRUGO, + adc128_show_in, adc128_set_in, 7, 2); + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, adc128_show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, + adc128_show_temp, adc128_set_temp, 1); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, + adc128_show_temp, adc128_set_temp, 2); + +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, adc128_show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, adc128_show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, adc128_show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, adc128_show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, adc128_show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, adc128_show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, adc128_show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, adc128_show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, adc128_show_alarm, NULL, 7); + +static struct attribute *adc128_attrs[] = { + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in6_alarm.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in7_alarm.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + NULL +}; + +static const struct attribute_group adc128_group = { + .attrs = adc128_attrs, + .is_visible = adc128_is_visible, +}; +__ATTRIBUTE_GROUPS(adc128); + +static int adc128_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + int man_id, dev_id; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + man_id = i2c_smbus_read_byte_data(client, ADC128_REG_MAN_ID); + dev_id = i2c_smbus_read_byte_data(client, ADC128_REG_DEV_ID); + if (man_id != 0x01 || dev_id != 0x09) + return -ENODEV; + + /* Check unused bits for confirmation */ + if (i2c_smbus_read_byte_data(client, ADC128_REG_CONFIG) & 0xf4) + return -ENODEV; + if (i2c_smbus_read_byte_data(client, ADC128_REG_CONV_RATE) & 0xfe) + return -ENODEV; + if (i2c_smbus_read_byte_data(client, ADC128_REG_ONESHOT) & 0xfe) + return -ENODEV; + if (i2c_smbus_read_byte_data(client, ADC128_REG_SHUTDOWN) & 0xfe) + return -ENODEV; + if (i2c_smbus_read_byte_data(client, ADC128_REG_CONFIG_ADV) & 0xf8) + return -ENODEV; + if (i2c_smbus_read_byte_data(client, ADC128_REG_BUSY_STATUS) & 0xfc) + return -ENODEV; + + strlcpy(info->type, "adc128d818", I2C_NAME_SIZE); + + return 0; +} + +static int adc128_init_client(struct adc128_data *data) +{ + struct i2c_client *client = data->client; + int err; + + /* + * Reset chip to defaults. + * This makes most other initializations unnecessary. + */ + err = i2c_smbus_write_byte_data(client, ADC128_REG_CONFIG, 0x80); + if (err) + return err; + + /* Set operation mode, if non-default */ + if (data->mode != 0) { + err = i2c_smbus_write_byte_data(client, + ADC128_REG_CONFIG_ADV, + data->mode << 1); + if (err) + return err; + } + + /* Start monitoring */ + err = i2c_smbus_write_byte_data(client, ADC128_REG_CONFIG, 0x01); + if (err) + return err; + + /* If external vref is selected, configure the chip to use it */ + if (data->regulator) { + err = i2c_smbus_write_byte_data(client, + ADC128_REG_CONFIG_ADV, 0x01); + if (err) + return err; + } + + return 0; +} + +static int adc128_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct regulator *regulator; + struct device *hwmon_dev; + struct adc128_data *data; + int err, vref; + + data = devm_kzalloc(dev, sizeof(struct adc128_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* vref is optional. If specified, is used as chip reference voltage */ + regulator = devm_regulator_get_optional(dev, "vref"); + if (!IS_ERR(regulator)) { + data->regulator = regulator; + err = regulator_enable(regulator); + if (err < 0) + return err; + vref = regulator_get_voltage(regulator); + if (vref < 0) { + err = vref; + goto error; + } + data->vref = DIV_ROUND_CLOSEST(vref, 1000); + } else { + data->vref = 2560; /* 2.56V, in mV */ + } + + /* Operation mode is optional. If unspecified, keep current mode */ + if (of_property_read_u8(dev->of_node, "ti,mode", &data->mode) == 0) { + if (data->mode > 3) { + dev_err(dev, "invalid operation mode %d\n", + data->mode); + err = -EINVAL; + goto error; + } + } else { + err = i2c_smbus_read_byte_data(client, ADC128_REG_CONFIG_ADV); + if (err < 0) + goto error; + data->mode = (err >> 1) & ADC128_REG_MASK; + } + + data->client = client; + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Initialize the chip */ + err = adc128_init_client(data); + if (err < 0) + goto error; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, adc128_groups); + if (IS_ERR(hwmon_dev)) { + err = PTR_ERR(hwmon_dev); + goto error; + } + + return 0; + +error: + if (data->regulator) + regulator_disable(data->regulator); + return err; +} + +static int adc128_remove(struct i2c_client *client) +{ + struct adc128_data *data = i2c_get_clientdata(client); + + if (data->regulator) + regulator_disable(data->regulator); + + return 0; +} + +static const struct i2c_device_id adc128_id[] = { + { "adc128d818", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adc128_id); + +static const struct of_device_id adc128_of_match[] = { + { .compatible = "ti,adc128d818" }, + { }, +}; +MODULE_DEVICE_TABLE(of, adc128_of_match); + +static struct i2c_driver adc128_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "adc128d818", + .of_match_table = of_match_ptr(adc128_of_match), + }, + .probe = adc128_probe, + .remove = adc128_remove, + .id_table = adc128_id, + .detect = adc128_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(adc128_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("Driver for ADC128D818"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/adcxx.c b/drivers/hwmon/adcxx.c new file mode 100644 index 000000000..69e0bb97e --- /dev/null +++ b/drivers/hwmon/adcxx.c @@ -0,0 +1,247 @@ +/* + * adcxx.c + * + * The adcxx4s is an AD converter family from National Semiconductor (NS). + * + * Copyright (c) 2008 Marc Pignat + * + * The adcxx4s communicates with a host processor via an SPI/Microwire Bus + * interface. This driver supports the whole family of devices with name + * ADCS, where + * * bb is the resolution in number of bits (8, 10, 12) + * * c is the number of channels (1, 2, 4, 8) + * * sss is the maximum conversion speed (021 for 200 kSPS, 051 for 500 kSPS + * and 101 for 1 MSPS) + * + * Complete datasheets are available at National's website here: + * http://www.national.com/ds/DC/ADCS.pdf + * + * Handling of 8, 10 and 12 bits converters are the same, the + * unavailable bits are 0 :) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "adcxx" + +struct adcxx { + struct device *hwmon_dev; + struct mutex lock; + u32 channels; + u32 reference; /* in millivolts */ +}; + +/* sysfs hook function */ +static ssize_t adcxx_read(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adcxx *adc = spi_get_drvdata(spi); + u8 tx_buf[2]; + u8 rx_buf[2]; + int status; + u32 value; + + if (mutex_lock_interruptible(&adc->lock)) + return -ERESTARTSYS; + + if (adc->channels == 1) { + status = spi_read(spi, rx_buf, sizeof(rx_buf)); + } else { + tx_buf[0] = attr->index << 3; /* other bits are don't care */ + status = spi_write_then_read(spi, tx_buf, sizeof(tx_buf), + rx_buf, sizeof(rx_buf)); + } + if (status < 0) { + dev_warn(dev, "SPI synch. transfer failed with status %d\n", + status); + goto out; + } + + value = (rx_buf[0] << 8) + rx_buf[1]; + dev_dbg(dev, "raw value = 0x%x\n", value); + + value = value * adc->reference >> 12; + status = sprintf(buf, "%d\n", value); +out: + mutex_unlock(&adc->lock); + return status; +} + +static ssize_t adcxx_show_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + /* The minimum reference is 0 for this chip family */ + return sprintf(buf, "0\n"); +} + +static ssize_t adcxx_show_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct adcxx *adc = spi_get_drvdata(spi); + u32 reference; + + if (mutex_lock_interruptible(&adc->lock)) + return -ERESTARTSYS; + + reference = adc->reference; + + mutex_unlock(&adc->lock); + + return sprintf(buf, "%d\n", reference); +} + +static ssize_t adcxx_set_max(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count) +{ + struct spi_device *spi = to_spi_device(dev); + struct adcxx *adc = spi_get_drvdata(spi); + unsigned long value; + + if (kstrtoul(buf, 10, &value)) + return -EINVAL; + + if (mutex_lock_interruptible(&adc->lock)) + return -ERESTARTSYS; + + adc->reference = value; + + mutex_unlock(&adc->lock); + + return count; +} + +static ssize_t adcxx_show_name(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + return sprintf(buf, "%s\n", to_spi_device(dev)->modalias); +} + +static struct sensor_device_attribute ad_input[] = { + SENSOR_ATTR(name, S_IRUGO, adcxx_show_name, NULL, 0), + SENSOR_ATTR(in_min, S_IRUGO, adcxx_show_min, NULL, 0), + SENSOR_ATTR(in_max, S_IWUSR | S_IRUGO, adcxx_show_max, + adcxx_set_max, 0), + SENSOR_ATTR(in0_input, S_IRUGO, adcxx_read, NULL, 0), + SENSOR_ATTR(in1_input, S_IRUGO, adcxx_read, NULL, 1), + SENSOR_ATTR(in2_input, S_IRUGO, adcxx_read, NULL, 2), + SENSOR_ATTR(in3_input, S_IRUGO, adcxx_read, NULL, 3), + SENSOR_ATTR(in4_input, S_IRUGO, adcxx_read, NULL, 4), + SENSOR_ATTR(in5_input, S_IRUGO, adcxx_read, NULL, 5), + SENSOR_ATTR(in6_input, S_IRUGO, adcxx_read, NULL, 6), + SENSOR_ATTR(in7_input, S_IRUGO, adcxx_read, NULL, 7), +}; + +/*----------------------------------------------------------------------*/ + +static int adcxx_probe(struct spi_device *spi) +{ + int channels = spi_get_device_id(spi)->driver_data; + struct adcxx *adc; + int status; + int i; + + adc = devm_kzalloc(&spi->dev, sizeof(*adc), GFP_KERNEL); + if (!adc) + return -ENOMEM; + + /* set a default value for the reference */ + adc->reference = 3300; + adc->channels = channels; + mutex_init(&adc->lock); + + mutex_lock(&adc->lock); + + spi_set_drvdata(spi, adc); + + for (i = 0; i < 3 + adc->channels; i++) { + status = device_create_file(&spi->dev, &ad_input[i].dev_attr); + if (status) { + dev_err(&spi->dev, "device_create_file failed.\n"); + goto out_err; + } + } + + adc->hwmon_dev = hwmon_device_register(&spi->dev); + if (IS_ERR(adc->hwmon_dev)) { + dev_err(&spi->dev, "hwmon_device_register failed.\n"); + status = PTR_ERR(adc->hwmon_dev); + goto out_err; + } + + mutex_unlock(&adc->lock); + return 0; + +out_err: + for (i--; i >= 0; i--) + device_remove_file(&spi->dev, &ad_input[i].dev_attr); + + mutex_unlock(&adc->lock); + return status; +} + +static int adcxx_remove(struct spi_device *spi) +{ + struct adcxx *adc = spi_get_drvdata(spi); + int i; + + mutex_lock(&adc->lock); + hwmon_device_unregister(adc->hwmon_dev); + for (i = 0; i < 3 + adc->channels; i++) + device_remove_file(&spi->dev, &ad_input[i].dev_attr); + + mutex_unlock(&adc->lock); + + return 0; +} + +static const struct spi_device_id adcxx_ids[] = { + { "adcxx1s", 1 }, + { "adcxx2s", 2 }, + { "adcxx4s", 4 }, + { "adcxx8s", 8 }, + { }, +}; +MODULE_DEVICE_TABLE(spi, adcxx_ids); + +static struct spi_driver adcxx_driver = { + .driver = { + .name = "adcxx", + }, + .id_table = adcxx_ids, + .probe = adcxx_probe, + .remove = adcxx_remove, +}; + +module_spi_driver(adcxx_driver); + +MODULE_AUTHOR("Marc Pignat"); +MODULE_DESCRIPTION("National Semiconductor adcxx8sxxx Linux driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/adm1021.c b/drivers/hwmon/adm1021.c new file mode 100644 index 000000000..eacf10fad --- /dev/null +++ b/drivers/hwmon/adm1021.c @@ -0,0 +1,506 @@ +/* + * adm1021.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (c) 1998, 1999 Frodo Looijaard and + * Philip Edelbrock + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { + 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, 0x4c, 0x4d, 0x4e, I2C_CLIENT_END }; + +enum chips { + adm1021, adm1023, max1617, max1617a, thmc10, lm84, gl523sm, mc1066 }; + +/* adm1021 constants specified below */ + +/* The adm1021 registers */ +/* Read-only */ +/* For nr in 0-1 */ +#define ADM1021_REG_TEMP(nr) (nr) +#define ADM1021_REG_STATUS 0x02 +/* 0x41 = AD, 0x49 = TI, 0x4D = Maxim, 0x23 = Genesys , 0x54 = Onsemi */ +#define ADM1021_REG_MAN_ID 0xFE +/* ADM1021 = 0x0X, ADM1023 = 0x3X */ +#define ADM1021_REG_DEV_ID 0xFF +/* These use different addresses for reading/writing */ +#define ADM1021_REG_CONFIG_R 0x03 +#define ADM1021_REG_CONFIG_W 0x09 +#define ADM1021_REG_CONV_RATE_R 0x04 +#define ADM1021_REG_CONV_RATE_W 0x0A +/* These are for the ADM1023's additional precision on the remote temp sensor */ +#define ADM1023_REG_REM_TEMP_PREC 0x10 +#define ADM1023_REG_REM_OFFSET 0x11 +#define ADM1023_REG_REM_OFFSET_PREC 0x12 +#define ADM1023_REG_REM_TOS_PREC 0x13 +#define ADM1023_REG_REM_THYST_PREC 0x14 +/* limits */ +/* For nr in 0-1 */ +#define ADM1021_REG_TOS_R(nr) (0x05 + 2 * (nr)) +#define ADM1021_REG_TOS_W(nr) (0x0B + 2 * (nr)) +#define ADM1021_REG_THYST_R(nr) (0x06 + 2 * (nr)) +#define ADM1021_REG_THYST_W(nr) (0x0C + 2 * (nr)) +/* write-only */ +#define ADM1021_REG_ONESHOT 0x0F + +/* Initial values */ + +/* + * Note: Even though I left the low and high limits named os and hyst, + * they don't quite work like a thermostat the way the LM75 does. I.e., + * a lower temp than THYST actually triggers an alarm instead of + * clearing it. Weird, ey? --Phil + */ + +/* Each client has this additional data */ +struct adm1021_data { + struct i2c_client *client; + enum chips type; + + const struct attribute_group *groups[3]; + + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + char low_power; /* !=0 if device in low power mode */ + unsigned long last_updated; /* In jiffies */ + + int temp_max[2]; /* Register values */ + int temp_min[2]; + int temp[2]; + u8 alarms; + /* Special values for ADM1023 only */ + u8 remote_temp_offset; + u8 remote_temp_offset_prec; +}; + +/* (amalysh) read only mode, otherwise any limit's writing confuse BIOS */ +static bool read_only; + +static struct adm1021_data *adm1021_update_device(struct device *dev) +{ + struct adm1021_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + int i; + + dev_dbg(dev, "Starting adm1021 update\n"); + + for (i = 0; i < 2; i++) { + data->temp[i] = 1000 * + (s8) i2c_smbus_read_byte_data( + client, ADM1021_REG_TEMP(i)); + data->temp_max[i] = 1000 * + (s8) i2c_smbus_read_byte_data( + client, ADM1021_REG_TOS_R(i)); + if (data->type != lm84) { + data->temp_min[i] = 1000 * + (s8) i2c_smbus_read_byte_data(client, + ADM1021_REG_THYST_R(i)); + } + } + data->alarms = i2c_smbus_read_byte_data(client, + ADM1021_REG_STATUS) & 0x7c; + if (data->type == adm1023) { + /* + * The ADM1023 provides 3 extra bits of precision for + * the remote sensor in extra registers. + */ + data->temp[1] += 125 * (i2c_smbus_read_byte_data( + client, ADM1023_REG_REM_TEMP_PREC) >> 5); + data->temp_max[1] += 125 * (i2c_smbus_read_byte_data( + client, ADM1023_REG_REM_TOS_PREC) >> 5); + data->temp_min[1] += 125 * (i2c_smbus_read_byte_data( + client, ADM1023_REG_REM_THYST_PREC) >> 5); + data->remote_temp_offset = + i2c_smbus_read_byte_data(client, + ADM1023_REG_REM_OFFSET); + data->remote_temp_offset_prec = + i2c_smbus_read_byte_data(client, + ADM1023_REG_REM_OFFSET_PREC); + } + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static ssize_t show_temp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct adm1021_data *data = adm1021_update_device(dev); + + return sprintf(buf, "%d\n", data->temp[index]); +} + +static ssize_t show_temp_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct adm1021_data *data = adm1021_update_device(dev); + + return sprintf(buf, "%d\n", data->temp_max[index]); +} + +static ssize_t show_temp_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct adm1021_data *data = adm1021_update_device(dev); + + return sprintf(buf, "%d\n", data->temp_min[index]); +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + struct adm1021_data *data = adm1021_update_device(dev); + return sprintf(buf, "%u\n", (data->alarms >> index) & 1); +} + +static ssize_t alarms_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct adm1021_data *data = adm1021_update_device(dev); + return sprintf(buf, "%u\n", data->alarms); +} + +static ssize_t set_temp_max(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct adm1021_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + int reg_val, err; + + err = kstrtol(buf, 10, &temp); + if (err) + return err; + temp /= 1000; + + mutex_lock(&data->update_lock); + reg_val = clamp_val(temp, -128, 127); + data->temp_max[index] = reg_val * 1000; + if (!read_only) + i2c_smbus_write_byte_data(client, ADM1021_REG_TOS_W(index), + reg_val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t set_temp_min(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct adm1021_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + int reg_val, err; + + err = kstrtol(buf, 10, &temp); + if (err) + return err; + temp /= 1000; + + mutex_lock(&data->update_lock); + reg_val = clamp_val(temp, -128, 127); + data->temp_min[index] = reg_val * 1000; + if (!read_only) + i2c_smbus_write_byte_data(client, ADM1021_REG_THYST_W(index), + reg_val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t low_power_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct adm1021_data *data = adm1021_update_device(dev); + return sprintf(buf, "%d\n", data->low_power); +} + +static ssize_t low_power_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct adm1021_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + char low_power; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + low_power = val != 0; + + mutex_lock(&data->update_lock); + if (low_power != data->low_power) { + int config = i2c_smbus_read_byte_data( + client, ADM1021_REG_CONFIG_R); + data->low_power = low_power; + i2c_smbus_write_byte_data(client, ADM1021_REG_CONFIG_W, + (config & 0xBF) | (low_power << 6)); + } + mutex_unlock(&data->update_lock); + + return count; +} + + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 1); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 1); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 2); + +static DEVICE_ATTR_RO(alarms); +static DEVICE_ATTR_RW(low_power); + +static struct attribute *adm1021_attributes[] = { + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &dev_attr_alarms.attr, + &dev_attr_low_power.attr, + NULL +}; + +static const struct attribute_group adm1021_group = { + .attrs = adm1021_attributes, +}; + +static struct attribute *adm1021_min_attributes[] = { + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group adm1021_min_group = { + .attrs = adm1021_min_attributes, +}; + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int adm1021_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + const char *type_name; + int conv_rate, status, config, man_id, dev_id; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + pr_debug("detect failed, smbus byte data not supported!\n"); + return -ENODEV; + } + + status = i2c_smbus_read_byte_data(client, ADM1021_REG_STATUS); + conv_rate = i2c_smbus_read_byte_data(client, + ADM1021_REG_CONV_RATE_R); + config = i2c_smbus_read_byte_data(client, ADM1021_REG_CONFIG_R); + + /* Check unused bits */ + if ((status & 0x03) || (config & 0x3F) || (conv_rate & 0xF8)) { + pr_debug("detect failed, chip not detected!\n"); + return -ENODEV; + } + + /* Determine the chip type. */ + man_id = i2c_smbus_read_byte_data(client, ADM1021_REG_MAN_ID); + dev_id = i2c_smbus_read_byte_data(client, ADM1021_REG_DEV_ID); + + if (man_id < 0 || dev_id < 0) + return -ENODEV; + + if (man_id == 0x4d && dev_id == 0x01) + type_name = "max1617a"; + else if (man_id == 0x41) { + if ((dev_id & 0xF0) == 0x30) + type_name = "adm1023"; + else if ((dev_id & 0xF0) == 0x00) + type_name = "adm1021"; + else + return -ENODEV; + } else if (man_id == 0x49) + type_name = "thmc10"; + else if (man_id == 0x23) + type_name = "gl523sm"; + else if (man_id == 0x54) + type_name = "mc1066"; + else { + int lte, rte, lhi, rhi, llo, rlo; + + /* extra checks for LM84 and MAX1617 to avoid misdetections */ + + llo = i2c_smbus_read_byte_data(client, ADM1021_REG_THYST_R(0)); + rlo = i2c_smbus_read_byte_data(client, ADM1021_REG_THYST_R(1)); + + /* fail if any of the additional register reads failed */ + if (llo < 0 || rlo < 0) + return -ENODEV; + + lte = i2c_smbus_read_byte_data(client, ADM1021_REG_TEMP(0)); + rte = i2c_smbus_read_byte_data(client, ADM1021_REG_TEMP(1)); + lhi = i2c_smbus_read_byte_data(client, ADM1021_REG_TOS_R(0)); + rhi = i2c_smbus_read_byte_data(client, ADM1021_REG_TOS_R(1)); + + /* + * Fail for negative temperatures and negative high limits. + * This check also catches read errors on the tested registers. + */ + if ((s8)lte < 0 || (s8)rte < 0 || (s8)lhi < 0 || (s8)rhi < 0) + return -ENODEV; + + /* fail if all registers hold the same value */ + if (lte == rte && lte == lhi && lte == rhi && lte == llo + && lte == rlo) + return -ENODEV; + + /* + * LM84 Mfr ID is in a different place, + * and it has more unused bits. + */ + if (conv_rate == 0x00 + && (config & 0x7F) == 0x00 + && (status & 0xAB) == 0x00) { + type_name = "lm84"; + } else { + /* fail if low limits are larger than high limits */ + if ((s8)llo > lhi || (s8)rlo > rhi) + return -ENODEV; + type_name = "max1617"; + } + } + + pr_debug("Detected chip %s at adapter %d, address 0x%02x.\n", + type_name, i2c_adapter_id(adapter), client->addr); + strlcpy(info->type, type_name, I2C_NAME_SIZE); + + return 0; +} + +static void adm1021_init_client(struct i2c_client *client) +{ + /* Enable ADC and disable suspend mode */ + i2c_smbus_write_byte_data(client, ADM1021_REG_CONFIG_W, + i2c_smbus_read_byte_data(client, ADM1021_REG_CONFIG_R) & 0xBF); + /* Set Conversion rate to 1/sec (this can be tinkered with) */ + i2c_smbus_write_byte_data(client, ADM1021_REG_CONV_RATE_W, 0x04); +} + +static int adm1021_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct adm1021_data *data; + struct device *hwmon_dev; + + data = devm_kzalloc(dev, sizeof(struct adm1021_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + data->type = id->driver_data; + mutex_init(&data->update_lock); + + /* Initialize the ADM1021 chip */ + if (data->type != lm84 && !read_only) + adm1021_init_client(client); + + data->groups[0] = &adm1021_group; + if (data->type != lm84) + data->groups[1] = &adm1021_min_group; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id adm1021_id[] = { + { "adm1021", adm1021 }, + { "adm1023", adm1023 }, + { "max1617", max1617 }, + { "max1617a", max1617a }, + { "thmc10", thmc10 }, + { "lm84", lm84 }, + { "gl523sm", gl523sm }, + { "mc1066", mc1066 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adm1021_id); + +static struct i2c_driver adm1021_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "adm1021", + }, + .probe = adm1021_probe, + .id_table = adm1021_id, + .detect = adm1021_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(adm1021_driver); + +MODULE_AUTHOR("Frodo Looijaard and " + "Philip Edelbrock "); +MODULE_DESCRIPTION("adm1021 driver"); +MODULE_LICENSE("GPL"); + +module_param(read_only, bool, 0); +MODULE_PARM_DESC(read_only, "Don't set any values, read only mode"); diff --git a/drivers/hwmon/adm1025.c b/drivers/hwmon/adm1025.c new file mode 100644 index 000000000..1e4dad36f --- /dev/null +++ b/drivers/hwmon/adm1025.c @@ -0,0 +1,582 @@ +/* + * adm1025.c + * + * Copyright (C) 2000 Chen-Yuan Wu + * Copyright (C) 2003-2009 Jean Delvare + * + * The ADM1025 is a sensor chip made by Analog Devices. It reports up to 6 + * voltages (including its own power source) and up to two temperatures + * (its own plus up to one external one). Voltages are scaled internally + * (which is not the common way) with ratios such that the nominal value + * of each voltage correspond to a register value of 192 (which means a + * resolution of about 0.5% of the nominal value). Temperature values are + * reported with a 1 deg resolution and a 3 deg accuracy. Complete + * datasheet can be obtained from Analog's website at: + * http://www.onsemi.com/PowerSolutions/product.do?id=ADM1025 + * + * This driver also supports the ADM1025A, which differs from the ADM1025 + * only in that it has "open-drain VID inputs while the ADM1025 has + * on-chip 100k pull-ups on the VID inputs". It doesn't make any + * difference for us. + * + * This driver also supports the NE1619, a sensor chip made by Philips. + * That chip is similar to the ADM1025A, with a few differences. The only + * difference that matters to us is that the NE1619 has only two possible + * addresses while the ADM1025A has a third one. Complete datasheet can be + * obtained from Philips's website at: + * http://www.semiconductors.philips.com/pip/NE1619DS.html + * + * Since the ADM1025 was the first chipset supported by this driver, most + * comments will refer to this chipset, but are actually general and + * concern all supported chipsets, unless mentioned otherwise. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Addresses to scan + * ADM1025 and ADM1025A have three possible addresses: 0x2c, 0x2d and 0x2e. + * NE1619 has two possible addresses: 0x2c and 0x2d. + */ + +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END }; + +enum chips { adm1025, ne1619 }; + +/* + * The ADM1025 registers + */ + +#define ADM1025_REG_MAN_ID 0x3E +#define ADM1025_REG_CHIP_ID 0x3F +#define ADM1025_REG_CONFIG 0x40 +#define ADM1025_REG_STATUS1 0x41 +#define ADM1025_REG_STATUS2 0x42 +#define ADM1025_REG_IN(nr) (0x20 + (nr)) +#define ADM1025_REG_IN_MAX(nr) (0x2B + (nr) * 2) +#define ADM1025_REG_IN_MIN(nr) (0x2C + (nr) * 2) +#define ADM1025_REG_TEMP(nr) (0x26 + (nr)) +#define ADM1025_REG_TEMP_HIGH(nr) (0x37 + (nr) * 2) +#define ADM1025_REG_TEMP_LOW(nr) (0x38 + (nr) * 2) +#define ADM1025_REG_VID 0x47 +#define ADM1025_REG_VID4 0x49 + +/* + * Conversions and various macros + * The ADM1025 uses signed 8-bit values for temperatures. + */ + +static const int in_scale[6] = { 2500, 2250, 3300, 5000, 12000, 3300 }; + +#define IN_FROM_REG(reg, scale) (((reg) * (scale) + 96) / 192) +#define IN_TO_REG(val, scale) ((val) <= 0 ? 0 : \ + (val) >= (scale) * 255 / 192 ? 255 : \ + ((val) * 192 + (scale) / 2) / (scale)) + +#define TEMP_FROM_REG(reg) ((reg) * 1000) +#define TEMP_TO_REG(val) ((val) <= -127500 ? -128 : \ + (val) >= 126500 ? 127 : \ + (((val) < 0 ? (val) - 500 : \ + (val) + 500) / 1000)) + +/* + * Client data (each client gets its own) + */ + +struct adm1025_data { + struct i2c_client *client; + const struct attribute_group *groups[3]; + struct mutex update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + u8 in[6]; /* register value */ + u8 in_max[6]; /* register value */ + u8 in_min[6]; /* register value */ + s8 temp[2]; /* register value */ + s8 temp_min[2]; /* register value */ + s8 temp_max[2]; /* register value */ + u16 alarms; /* register values, combined */ + u8 vid; /* register values, combined */ + u8 vrm; +}; + +static struct adm1025_data *adm1025_update_device(struct device *dev) +{ + struct adm1025_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ * 2) || !data->valid) { + int i; + + dev_dbg(&client->dev, "Updating data.\n"); + for (i = 0; i < 6; i++) { + data->in[i] = i2c_smbus_read_byte_data(client, + ADM1025_REG_IN(i)); + data->in_min[i] = i2c_smbus_read_byte_data(client, + ADM1025_REG_IN_MIN(i)); + data->in_max[i] = i2c_smbus_read_byte_data(client, + ADM1025_REG_IN_MAX(i)); + } + for (i = 0; i < 2; i++) { + data->temp[i] = i2c_smbus_read_byte_data(client, + ADM1025_REG_TEMP(i)); + data->temp_min[i] = i2c_smbus_read_byte_data(client, + ADM1025_REG_TEMP_LOW(i)); + data->temp_max[i] = i2c_smbus_read_byte_data(client, + ADM1025_REG_TEMP_HIGH(i)); + } + data->alarms = i2c_smbus_read_byte_data(client, + ADM1025_REG_STATUS1) + | (i2c_smbus_read_byte_data(client, + ADM1025_REG_STATUS2) << 8); + data->vid = (i2c_smbus_read_byte_data(client, + ADM1025_REG_VID) & 0x0f) + | ((i2c_smbus_read_byte_data(client, + ADM1025_REG_VID4) & 0x01) << 4); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Sysfs stuff + */ + +static ssize_t +show_in(struct device *dev, struct device_attribute *attr, char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + struct adm1025_data *data = adm1025_update_device(dev); + return sprintf(buf, "%u\n", IN_FROM_REG(data->in[index], + in_scale[index])); +} + +static ssize_t +show_in_min(struct device *dev, struct device_attribute *attr, char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + struct adm1025_data *data = adm1025_update_device(dev); + return sprintf(buf, "%u\n", IN_FROM_REG(data->in_min[index], + in_scale[index])); +} + +static ssize_t +show_in_max(struct device *dev, struct device_attribute *attr, char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + struct adm1025_data *data = adm1025_update_device(dev); + return sprintf(buf, "%u\n", IN_FROM_REG(data->in_max[index], + in_scale[index])); +} + +static ssize_t +show_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + struct adm1025_data *data = adm1025_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[index])); +} + +static ssize_t +show_temp_min(struct device *dev, struct device_attribute *attr, char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + struct adm1025_data *data = adm1025_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_min[index])); +} + +static ssize_t +show_temp_max(struct device *dev, struct device_attribute *attr, char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + struct adm1025_data *data = adm1025_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max[index])); +} + +static ssize_t set_in_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int index = to_sensor_dev_attr(attr)->index; + struct adm1025_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_min[index] = IN_TO_REG(val, in_scale[index]); + i2c_smbus_write_byte_data(client, ADM1025_REG_IN_MIN(index), + data->in_min[index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_in_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int index = to_sensor_dev_attr(attr)->index; + struct adm1025_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_max[index] = IN_TO_REG(val, in_scale[index]); + i2c_smbus_write_byte_data(client, ADM1025_REG_IN_MAX(index), + data->in_max[index]); + mutex_unlock(&data->update_lock); + return count; +} + +#define set_in(offset) \ +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, \ + show_in, NULL, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_min, S_IWUSR | S_IRUGO, \ + show_in_min, set_in_min, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_max, S_IWUSR | S_IRUGO, \ + show_in_max, set_in_max, offset) +set_in(0); +set_in(1); +set_in(2); +set_in(3); +set_in(4); +set_in(5); + +static ssize_t set_temp_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int index = to_sensor_dev_attr(attr)->index; + struct adm1025_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_min[index] = TEMP_TO_REG(val); + i2c_smbus_write_byte_data(client, ADM1025_REG_TEMP_LOW(index), + data->temp_min[index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int index = to_sensor_dev_attr(attr)->index; + struct adm1025_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_max[index] = TEMP_TO_REG(val); + i2c_smbus_write_byte_data(client, ADM1025_REG_TEMP_HIGH(index), + data->temp_max[index]); + mutex_unlock(&data->update_lock); + return count; +} + +#define set_temp(offset) \ +static SENSOR_DEVICE_ATTR(temp##offset##_input, S_IRUGO, \ + show_temp, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_min, S_IWUSR | S_IRUGO, \ + show_temp_min, set_temp_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max, S_IWUSR | S_IRUGO, \ + show_temp_max, set_temp_max, offset - 1) +set_temp(1); +set_temp(2); + +static ssize_t +alarms_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct adm1025_data *data = adm1025_update_device(dev); + return sprintf(buf, "%u\n", data->alarms); +} +static DEVICE_ATTR_RO(alarms); + +static ssize_t +show_alarm(struct device *dev, struct device_attribute *attr, char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct adm1025_data *data = adm1025_update_device(dev); + return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1); +} +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_alarm, NULL, 14); + +static ssize_t +cpu0_vid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct adm1025_data *data = adm1025_update_device(dev); + return sprintf(buf, "%u\n", vid_from_reg(data->vid, data->vrm)); +} +static DEVICE_ATTR_RO(cpu0_vid); + +static ssize_t +vrm_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct adm1025_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%u\n", data->vrm); +} +static ssize_t vrm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1025_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 255) + return -EINVAL; + + data->vrm = val; + return count; +} +static DEVICE_ATTR_RW(vrm); + +/* + * Real code + */ + +static struct attribute *adm1025_attributes[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &dev_attr_alarms.attr, + &dev_attr_cpu0_vid.attr, + &dev_attr_vrm.attr, + NULL +}; + +static const struct attribute_group adm1025_group = { + .attrs = adm1025_attributes, +}; + +static struct attribute *adm1025_attributes_in4[] = { + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group adm1025_group_in4 = { + .attrs = adm1025_attributes_in4, +}; + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int adm1025_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + const char *name; + u8 man_id, chip_id; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* Check for unused bits */ + if ((i2c_smbus_read_byte_data(client, ADM1025_REG_CONFIG) & 0x80) + || (i2c_smbus_read_byte_data(client, ADM1025_REG_STATUS1) & 0xC0) + || (i2c_smbus_read_byte_data(client, ADM1025_REG_STATUS2) & 0xBC)) { + dev_dbg(&adapter->dev, "ADM1025 detection failed at 0x%02x\n", + client->addr); + return -ENODEV; + } + + /* Identification */ + chip_id = i2c_smbus_read_byte_data(client, ADM1025_REG_CHIP_ID); + if ((chip_id & 0xF0) != 0x20) + return -ENODEV; + + man_id = i2c_smbus_read_byte_data(client, ADM1025_REG_MAN_ID); + if (man_id == 0x41) + name = "adm1025"; + else if (man_id == 0xA1 && client->addr != 0x2E) + name = "ne1619"; + else + return -ENODEV; + + strlcpy(info->type, name, I2C_NAME_SIZE); + + return 0; +} + +static void adm1025_init_client(struct i2c_client *client) +{ + u8 reg; + struct adm1025_data *data = i2c_get_clientdata(client); + int i; + + data->vrm = vid_which_vrm(); + + /* + * Set high limits + * Usually we avoid setting limits on driver init, but it happens + * that the ADM1025 comes with stupid default limits (all registers + * set to 0). In case the chip has not gone through any limit + * setting yet, we better set the high limits to the max so that + * no alarm triggers. + */ + for (i = 0; i < 6; i++) { + reg = i2c_smbus_read_byte_data(client, + ADM1025_REG_IN_MAX(i)); + if (reg == 0) + i2c_smbus_write_byte_data(client, + ADM1025_REG_IN_MAX(i), + 0xFF); + } + for (i = 0; i < 2; i++) { + reg = i2c_smbus_read_byte_data(client, + ADM1025_REG_TEMP_HIGH(i)); + if (reg == 0) + i2c_smbus_write_byte_data(client, + ADM1025_REG_TEMP_HIGH(i), + 0x7F); + } + + /* + * Start the conversions + */ + reg = i2c_smbus_read_byte_data(client, ADM1025_REG_CONFIG); + if (!(reg & 0x01)) + i2c_smbus_write_byte_data(client, ADM1025_REG_CONFIG, + (reg&0x7E)|0x01); +} + +static int adm1025_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct adm1025_data *data; + u8 config; + + data = devm_kzalloc(dev, sizeof(struct adm1025_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->client = client; + mutex_init(&data->update_lock); + + /* Initialize the ADM1025 chip */ + adm1025_init_client(client); + + /* sysfs hooks */ + data->groups[0] = &adm1025_group; + /* Pin 11 is either in4 (+12V) or VID4 */ + config = i2c_smbus_read_byte_data(client, ADM1025_REG_CONFIG); + if (!(config & 0x20)) + data->groups[1] = &adm1025_group_in4; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id adm1025_id[] = { + { "adm1025", adm1025 }, + { "ne1619", ne1619 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adm1025_id); + +static struct i2c_driver adm1025_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "adm1025", + }, + .probe = adm1025_probe, + .id_table = adm1025_id, + .detect = adm1025_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(adm1025_driver); + +MODULE_AUTHOR("Jean Delvare "); +MODULE_DESCRIPTION("ADM1025 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/adm1026.c b/drivers/hwmon/adm1026.c new file mode 100644 index 000000000..e43f09a07 --- /dev/null +++ b/drivers/hwmon/adm1026.c @@ -0,0 +1,1871 @@ +/* + * adm1026.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 2002, 2003 Philip Pokorny + * Copyright (C) 2004 Justin Thiessen + * + * Chip details at: + * + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END }; + +static int gpio_input[17] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static int gpio_output[17] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static int gpio_inverted[17] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static int gpio_normal[17] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 }; +static int gpio_fan[8] = { -1, -1, -1, -1, -1, -1, -1, -1 }; +module_param_array(gpio_input, int, NULL, 0); +MODULE_PARM_DESC(gpio_input, "List of GPIO pins (0-16) to program as inputs"); +module_param_array(gpio_output, int, NULL, 0); +MODULE_PARM_DESC(gpio_output, + "List of GPIO pins (0-16) to program as outputs"); +module_param_array(gpio_inverted, int, NULL, 0); +MODULE_PARM_DESC(gpio_inverted, + "List of GPIO pins (0-16) to program as inverted"); +module_param_array(gpio_normal, int, NULL, 0); +MODULE_PARM_DESC(gpio_normal, + "List of GPIO pins (0-16) to program as normal/non-inverted"); +module_param_array(gpio_fan, int, NULL, 0); +MODULE_PARM_DESC(gpio_fan, "List of GPIO pins (0-7) to program as fan tachs"); + +/* Many ADM1026 constants specified below */ + +/* The ADM1026 registers */ +#define ADM1026_REG_CONFIG1 0x00 +#define CFG1_MONITOR 0x01 +#define CFG1_INT_ENABLE 0x02 +#define CFG1_INT_CLEAR 0x04 +#define CFG1_AIN8_9 0x08 +#define CFG1_THERM_HOT 0x10 +#define CFG1_DAC_AFC 0x20 +#define CFG1_PWM_AFC 0x40 +#define CFG1_RESET 0x80 + +#define ADM1026_REG_CONFIG2 0x01 +/* CONFIG2 controls FAN0/GPIO0 through FAN7/GPIO7 */ + +#define ADM1026_REG_CONFIG3 0x07 +#define CFG3_GPIO16_ENABLE 0x01 +#define CFG3_CI_CLEAR 0x02 +#define CFG3_VREF_250 0x04 +#define CFG3_GPIO16_DIR 0x40 +#define CFG3_GPIO16_POL 0x80 + +#define ADM1026_REG_E2CONFIG 0x13 +#define E2CFG_READ 0x01 +#define E2CFG_WRITE 0x02 +#define E2CFG_ERASE 0x04 +#define E2CFG_ROM 0x08 +#define E2CFG_CLK_EXT 0x80 + +/* + * There are 10 general analog inputs and 7 dedicated inputs + * They are: + * 0 - 9 = AIN0 - AIN9 + * 10 = Vbat + * 11 = 3.3V Standby + * 12 = 3.3V Main + * 13 = +5V + * 14 = Vccp (CPU core voltage) + * 15 = +12V + * 16 = -12V + */ +static u16 ADM1026_REG_IN[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x27, 0x29, 0x26, 0x2a, + 0x2b, 0x2c, 0x2d, 0x2e, 0x2f + }; +static u16 ADM1026_REG_IN_MIN[] = { + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, + 0x5e, 0x5f, 0x6d, 0x49, 0x6b, 0x4a, + 0x4b, 0x4c, 0x4d, 0x4e, 0x4f + }; +static u16 ADM1026_REG_IN_MAX[] = { + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x6c, 0x41, 0x6a, 0x42, + 0x43, 0x44, 0x45, 0x46, 0x47 + }; + +/* + * Temperatures are: + * 0 - Internal + * 1 - External 1 + * 2 - External 2 + */ +static u16 ADM1026_REG_TEMP[] = { 0x1f, 0x28, 0x29 }; +static u16 ADM1026_REG_TEMP_MIN[] = { 0x69, 0x48, 0x49 }; +static u16 ADM1026_REG_TEMP_MAX[] = { 0x68, 0x40, 0x41 }; +static u16 ADM1026_REG_TEMP_TMIN[] = { 0x10, 0x11, 0x12 }; +static u16 ADM1026_REG_TEMP_THERM[] = { 0x0d, 0x0e, 0x0f }; +static u16 ADM1026_REG_TEMP_OFFSET[] = { 0x1e, 0x6e, 0x6f }; + +#define ADM1026_REG_FAN(nr) (0x38 + (nr)) +#define ADM1026_REG_FAN_MIN(nr) (0x60 + (nr)) +#define ADM1026_REG_FAN_DIV_0_3 0x02 +#define ADM1026_REG_FAN_DIV_4_7 0x03 + +#define ADM1026_REG_DAC 0x04 +#define ADM1026_REG_PWM 0x05 + +#define ADM1026_REG_GPIO_CFG_0_3 0x08 +#define ADM1026_REG_GPIO_CFG_4_7 0x09 +#define ADM1026_REG_GPIO_CFG_8_11 0x0a +#define ADM1026_REG_GPIO_CFG_12_15 0x0b +/* CFG_16 in REG_CFG3 */ +#define ADM1026_REG_GPIO_STATUS_0_7 0x24 +#define ADM1026_REG_GPIO_STATUS_8_15 0x25 +/* STATUS_16 in REG_STATUS4 */ +#define ADM1026_REG_GPIO_MASK_0_7 0x1c +#define ADM1026_REG_GPIO_MASK_8_15 0x1d +/* MASK_16 in REG_MASK4 */ + +#define ADM1026_REG_COMPANY 0x16 +#define ADM1026_REG_VERSTEP 0x17 +/* These are the recognized values for the above regs */ +#define ADM1026_COMPANY_ANALOG_DEV 0x41 +#define ADM1026_VERSTEP_GENERIC 0x40 +#define ADM1026_VERSTEP_ADM1026 0x44 + +#define ADM1026_REG_MASK1 0x18 +#define ADM1026_REG_MASK2 0x19 +#define ADM1026_REG_MASK3 0x1a +#define ADM1026_REG_MASK4 0x1b + +#define ADM1026_REG_STATUS1 0x20 +#define ADM1026_REG_STATUS2 0x21 +#define ADM1026_REG_STATUS3 0x22 +#define ADM1026_REG_STATUS4 0x23 + +#define ADM1026_FAN_ACTIVATION_TEMP_HYST -6 +#define ADM1026_FAN_CONTROL_TEMP_RANGE 20 +#define ADM1026_PWM_MAX 255 + +/* + * Conversions. Rounding and limit checking is only done on the TO_REG + * variants. Note that you should be a bit careful with which arguments + * these macros are called: arguments may be evaluated more than once. + */ + +/* + * IN are scaled according to built-in resistors. These are the + * voltages corresponding to 3/4 of full scale (192 or 0xc0) + * NOTE: The -12V input needs an additional factor to account + * for the Vref pullup resistor. + * NEG12_OFFSET = SCALE * Vref / V-192 - Vref + * = 13875 * 2.50 / 1.875 - 2500 + * = 16000 + * + * The values in this table are based on Table II, page 15 of the + * datasheet. + */ +static int adm1026_scaling[] = { /* .001 Volts */ + 2250, 2250, 2250, 2250, 2250, 2250, + 1875, 1875, 1875, 1875, 3000, 3330, + 3330, 4995, 2250, 12000, 13875 + }; +#define NEG12_OFFSET 16000 +#define SCALE(val, from, to) (((val)*(to) + ((from)/2))/(from)) +#define INS_TO_REG(n, val) \ + SCALE(clamp_val(val, 0, 255 * adm1026_scaling[n] / 192), \ + adm1026_scaling[n], 192) +#define INS_FROM_REG(n, val) (SCALE(val, 192, adm1026_scaling[n])) + +/* + * FAN speed is measured using 22.5kHz clock and counts for 2 pulses + * and we assume a 2 pulse-per-rev fan tach signal + * 22500 kHz * 60 (sec/min) * 2 (pulse) / 2 (pulse/rev) == 1350000 + */ +#define FAN_TO_REG(val, div) ((val) <= 0 ? 0xff : \ + clamp_val(1350000 / ((val) * (div)), \ + 1, 254)) +#define FAN_FROM_REG(val, div) ((val) == 0 ? -1 : (val) == 0xff ? 0 : \ + 1350000 / ((val) * (div))) +#define DIV_FROM_REG(val) (1 << (val)) +#define DIV_TO_REG(val) ((val) >= 8 ? 3 : (val) >= 4 ? 2 : (val) >= 2 ? 1 : 0) + +/* Temperature is reported in 1 degC increments */ +#define TEMP_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), \ + 1000) +#define TEMP_FROM_REG(val) ((val) * 1000) +#define OFFSET_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), \ + 1000) +#define OFFSET_FROM_REG(val) ((val) * 1000) + +#define PWM_TO_REG(val) (clamp_val(val, 0, 255)) +#define PWM_FROM_REG(val) (val) + +#define PWM_MIN_TO_REG(val) ((val) & 0xf0) +#define PWM_MIN_FROM_REG(val) (((val) & 0xf0) + ((val) >> 4)) + +/* + * Analog output is a voltage, and scaled to millivolts. The datasheet + * indicates that the DAC could be used to drive the fans, but in our + * example board (Arima HDAMA) it isn't connected to the fans at all. + */ +#define DAC_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val(val, 0, 2500) * 255, \ + 2500) +#define DAC_FROM_REG(val) (((val) * 2500) / 255) + +/* + * Chip sampling rates + * + * Some sensors are not updated more frequently than once per second + * so it doesn't make sense to read them more often than that. + * We cache the results and return the saved data if the driver + * is called again before a second has elapsed. + * + * Also, there is significant configuration data for this chip + * So, we keep the config data up to date in the cache + * when it is written and only sample it once every 5 *minutes* + */ +#define ADM1026_DATA_INTERVAL (1 * HZ) +#define ADM1026_CONFIG_INTERVAL (5 * 60 * HZ) + +/* + * We allow for multiple chips in a single system. + * + * For each registered ADM1026, we need to keep state information + * at client->data. The adm1026_data structure is dynamically + * allocated, when a new client structure is allocated. + */ + +struct pwm_data { + u8 pwm; + u8 enable; + u8 auto_pwm_min; +}; + +struct adm1026_data { + struct i2c_client *client; + const struct attribute_group *groups[3]; + + struct mutex update_lock; + int valid; /* !=0 if following fields are valid */ + unsigned long last_reading; /* In jiffies */ + unsigned long last_config; /* In jiffies */ + + u8 in[17]; /* Register value */ + u8 in_max[17]; /* Register value */ + u8 in_min[17]; /* Register value */ + s8 temp[3]; /* Register value */ + s8 temp_min[3]; /* Register value */ + s8 temp_max[3]; /* Register value */ + s8 temp_tmin[3]; /* Register value */ + s8 temp_crit[3]; /* Register value */ + s8 temp_offset[3]; /* Register value */ + u8 fan[8]; /* Register value */ + u8 fan_min[8]; /* Register value */ + u8 fan_div[8]; /* Decoded value */ + struct pwm_data pwm1; /* Pwm control values */ + u8 vrm; /* VRM version */ + u8 analog_out; /* Register value (DAC) */ + long alarms; /* Register encoding, combined */ + long alarm_mask; /* Register encoding, combined */ + long gpio; /* Register encoding, combined */ + long gpio_mask; /* Register encoding, combined */ + u8 gpio_config[17]; /* Decoded value */ + u8 config1; /* Register value */ + u8 config2; /* Register value */ + u8 config3; /* Register value */ +}; + +static int adm1026_read_value(struct i2c_client *client, u8 reg) +{ + int res; + + if (reg < 0x80) { + /* "RAM" locations */ + res = i2c_smbus_read_byte_data(client, reg) & 0xff; + } else { + /* EEPROM, do nothing */ + res = 0; + } + return res; +} + +static int adm1026_write_value(struct i2c_client *client, u8 reg, int value) +{ + int res; + + if (reg < 0x80) { + /* "RAM" locations */ + res = i2c_smbus_write_byte_data(client, reg, value); + } else { + /* EEPROM, do nothing */ + res = 0; + } + return res; +} + +static struct adm1026_data *adm1026_update_device(struct device *dev) +{ + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int i; + long value, alarms, gpio; + + mutex_lock(&data->update_lock); + if (!data->valid + || time_after(jiffies, + data->last_reading + ADM1026_DATA_INTERVAL)) { + /* Things that change quickly */ + dev_dbg(&client->dev, "Reading sensor values\n"); + for (i = 0; i <= 16; ++i) { + data->in[i] = + adm1026_read_value(client, ADM1026_REG_IN[i]); + } + + for (i = 0; i <= 7; ++i) { + data->fan[i] = + adm1026_read_value(client, ADM1026_REG_FAN(i)); + } + + for (i = 0; i <= 2; ++i) { + /* + * NOTE: temp[] is s8 and we assume 2's complement + * "conversion" in the assignment + */ + data->temp[i] = + adm1026_read_value(client, ADM1026_REG_TEMP[i]); + } + + data->pwm1.pwm = adm1026_read_value(client, + ADM1026_REG_PWM); + data->analog_out = adm1026_read_value(client, + ADM1026_REG_DAC); + /* GPIO16 is MSbit of alarms, move it to gpio */ + alarms = adm1026_read_value(client, ADM1026_REG_STATUS4); + gpio = alarms & 0x80 ? 0x0100 : 0; /* GPIO16 */ + alarms &= 0x7f; + alarms <<= 8; + alarms |= adm1026_read_value(client, ADM1026_REG_STATUS3); + alarms <<= 8; + alarms |= adm1026_read_value(client, ADM1026_REG_STATUS2); + alarms <<= 8; + alarms |= adm1026_read_value(client, ADM1026_REG_STATUS1); + data->alarms = alarms; + + /* Read the GPIO values */ + gpio |= adm1026_read_value(client, + ADM1026_REG_GPIO_STATUS_8_15); + gpio <<= 8; + gpio |= adm1026_read_value(client, + ADM1026_REG_GPIO_STATUS_0_7); + data->gpio = gpio; + + data->last_reading = jiffies; + } /* last_reading */ + + if (!data->valid || + time_after(jiffies, data->last_config + ADM1026_CONFIG_INTERVAL)) { + /* Things that don't change often */ + dev_dbg(&client->dev, "Reading config values\n"); + for (i = 0; i <= 16; ++i) { + data->in_min[i] = adm1026_read_value(client, + ADM1026_REG_IN_MIN[i]); + data->in_max[i] = adm1026_read_value(client, + ADM1026_REG_IN_MAX[i]); + } + + value = adm1026_read_value(client, ADM1026_REG_FAN_DIV_0_3) + | (adm1026_read_value(client, ADM1026_REG_FAN_DIV_4_7) + << 8); + for (i = 0; i <= 7; ++i) { + data->fan_min[i] = adm1026_read_value(client, + ADM1026_REG_FAN_MIN(i)); + data->fan_div[i] = DIV_FROM_REG(value & 0x03); + value >>= 2; + } + + for (i = 0; i <= 2; ++i) { + /* + * NOTE: temp_xxx[] are s8 and we assume 2's + * complement "conversion" in the assignment + */ + data->temp_min[i] = adm1026_read_value(client, + ADM1026_REG_TEMP_MIN[i]); + data->temp_max[i] = adm1026_read_value(client, + ADM1026_REG_TEMP_MAX[i]); + data->temp_tmin[i] = adm1026_read_value(client, + ADM1026_REG_TEMP_TMIN[i]); + data->temp_crit[i] = adm1026_read_value(client, + ADM1026_REG_TEMP_THERM[i]); + data->temp_offset[i] = adm1026_read_value(client, + ADM1026_REG_TEMP_OFFSET[i]); + } + + /* Read the STATUS/alarm masks */ + alarms = adm1026_read_value(client, ADM1026_REG_MASK4); + gpio = alarms & 0x80 ? 0x0100 : 0; /* GPIO16 */ + alarms = (alarms & 0x7f) << 8; + alarms |= adm1026_read_value(client, ADM1026_REG_MASK3); + alarms <<= 8; + alarms |= adm1026_read_value(client, ADM1026_REG_MASK2); + alarms <<= 8; + alarms |= adm1026_read_value(client, ADM1026_REG_MASK1); + data->alarm_mask = alarms; + + /* Read the GPIO values */ + gpio |= adm1026_read_value(client, + ADM1026_REG_GPIO_MASK_8_15); + gpio <<= 8; + gpio |= adm1026_read_value(client, ADM1026_REG_GPIO_MASK_0_7); + data->gpio_mask = gpio; + + /* Read various values from CONFIG1 */ + data->config1 = adm1026_read_value(client, + ADM1026_REG_CONFIG1); + if (data->config1 & CFG1_PWM_AFC) { + data->pwm1.enable = 2; + data->pwm1.auto_pwm_min = + PWM_MIN_FROM_REG(data->pwm1.pwm); + } + /* Read the GPIO config */ + data->config2 = adm1026_read_value(client, + ADM1026_REG_CONFIG2); + data->config3 = adm1026_read_value(client, + ADM1026_REG_CONFIG3); + data->gpio_config[16] = (data->config3 >> 6) & 0x03; + + value = 0; + for (i = 0; i <= 15; ++i) { + if ((i & 0x03) == 0) { + value = adm1026_read_value(client, + ADM1026_REG_GPIO_CFG_0_3 + i/4); + } + data->gpio_config[i] = value & 0x03; + value >>= 2; + } + + data->last_config = jiffies; + } /* last_config */ + + data->valid = 1; + mutex_unlock(&data->update_lock); + return data; +} + +static ssize_t show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", INS_FROM_REG(nr, data->in[nr])); +} +static ssize_t show_in_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", INS_FROM_REG(nr, data->in_min[nr])); +} +static ssize_t set_in_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_min[nr] = INS_TO_REG(nr, val); + adm1026_write_value(client, ADM1026_REG_IN_MIN[nr], data->in_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t show_in_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", INS_FROM_REG(nr, data->in_max[nr])); +} +static ssize_t set_in_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_max[nr] = INS_TO_REG(nr, val); + adm1026_write_value(client, ADM1026_REG_IN_MAX[nr], data->in_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define in_reg(offset) \ +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, show_in, \ + NULL, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \ + show_in_min, set_in_min, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \ + show_in_max, set_in_max, offset); + + +in_reg(0); +in_reg(1); +in_reg(2); +in_reg(3); +in_reg(4); +in_reg(5); +in_reg(6); +in_reg(7); +in_reg(8); +in_reg(9); +in_reg(10); +in_reg(11); +in_reg(12); +in_reg(13); +in_reg(14); +in_reg(15); + +static ssize_t show_in16(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", INS_FROM_REG(16, data->in[16]) - + NEG12_OFFSET); +} +static ssize_t show_in16_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", INS_FROM_REG(16, data->in_min[16]) + - NEG12_OFFSET); +} +static ssize_t set_in16_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_min[16] = INS_TO_REG(16, + clamp_val(val, INT_MIN, + INT_MAX - NEG12_OFFSET) + + NEG12_OFFSET); + adm1026_write_value(client, ADM1026_REG_IN_MIN[16], data->in_min[16]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t show_in16_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", INS_FROM_REG(16, data->in_max[16]) + - NEG12_OFFSET); +} +static ssize_t set_in16_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_max[16] = INS_TO_REG(16, + clamp_val(val, INT_MIN, + INT_MAX - NEG12_OFFSET) + + NEG12_OFFSET); + adm1026_write_value(client, ADM1026_REG_IN_MAX[16], data->in_max[16]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(in16_input, S_IRUGO, show_in16, NULL, 16); +static SENSOR_DEVICE_ATTR(in16_min, S_IRUGO | S_IWUSR, show_in16_min, + set_in16_min, 16); +static SENSOR_DEVICE_ATTR(in16_max, S_IRUGO | S_IWUSR, show_in16_max, + set_in16_max, 16); + + +/* Now add fan read/write functions */ + +static ssize_t show_fan(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr], + data->fan_div[nr])); +} +static ssize_t show_fan_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[nr], + data->fan_div[nr])); +} +static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_min[nr] = FAN_TO_REG(val, data->fan_div[nr]); + adm1026_write_value(client, ADM1026_REG_FAN_MIN(nr), + data->fan_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define fan_offset(offset) \ +static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, show_fan, NULL, \ + offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ + show_fan_min, set_fan_min, offset - 1); + +fan_offset(1); +fan_offset(2); +fan_offset(3); +fan_offset(4); +fan_offset(5); +fan_offset(6); +fan_offset(7); +fan_offset(8); + +/* Adjust fan_min to account for new fan divisor */ +static void fixup_fan_min(struct device *dev, int fan, int old_div) +{ + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int new_min; + int new_div = data->fan_div[fan]; + + /* 0 and 0xff are special. Don't adjust them */ + if (data->fan_min[fan] == 0 || data->fan_min[fan] == 0xff) + return; + + new_min = data->fan_min[fan] * old_div / new_div; + new_min = clamp_val(new_min, 1, 254); + data->fan_min[fan] = new_min; + adm1026_write_value(client, ADM1026_REG_FAN_MIN(fan), new_min); +} + +/* Now add fan_div read/write functions */ +static ssize_t show_fan_div(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", data->fan_div[nr]); +} +static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int orig_div, new_div; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + new_div = DIV_TO_REG(val); + + mutex_lock(&data->update_lock); + orig_div = data->fan_div[nr]; + data->fan_div[nr] = DIV_FROM_REG(new_div); + + if (nr < 4) { /* 0 <= nr < 4 */ + adm1026_write_value(client, ADM1026_REG_FAN_DIV_0_3, + (DIV_TO_REG(data->fan_div[0]) << 0) | + (DIV_TO_REG(data->fan_div[1]) << 2) | + (DIV_TO_REG(data->fan_div[2]) << 4) | + (DIV_TO_REG(data->fan_div[3]) << 6)); + } else { /* 3 < nr < 8 */ + adm1026_write_value(client, ADM1026_REG_FAN_DIV_4_7, + (DIV_TO_REG(data->fan_div[4]) << 0) | + (DIV_TO_REG(data->fan_div[5]) << 2) | + (DIV_TO_REG(data->fan_div[6]) << 4) | + (DIV_TO_REG(data->fan_div[7]) << 6)); + } + + if (data->fan_div[nr] != orig_div) + fixup_fan_min(dev, nr, orig_div); + + mutex_unlock(&data->update_lock); + return count; +} + +#define fan_offset_div(offset) \ +static SENSOR_DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR, \ + show_fan_div, set_fan_div, offset - 1); + +fan_offset_div(1); +fan_offset_div(2); +fan_offset_div(3); +fan_offset_div(4); +fan_offset_div(5); +fan_offset_div(6); +fan_offset_div(7); +fan_offset_div(8); + +/* Temps */ +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[nr])); +} +static ssize_t show_temp_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_min[nr])); +} +static ssize_t set_temp_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_min[nr] = TEMP_TO_REG(val); + adm1026_write_value(client, ADM1026_REG_TEMP_MIN[nr], + data->temp_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t show_temp_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max[nr])); +} +static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_max[nr] = TEMP_TO_REG(val); + adm1026_write_value(client, ADM1026_REG_TEMP_MAX[nr], + data->temp_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define temp_reg(offset) \ +static SENSOR_DEVICE_ATTR(temp##offset##_input, S_IRUGO, show_temp, \ + NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_min, S_IRUGO | S_IWUSR, \ + show_temp_min, set_temp_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max, S_IRUGO | S_IWUSR, \ + show_temp_max, set_temp_max, offset - 1); + + +temp_reg(1); +temp_reg(2); +temp_reg(3); + +static ssize_t show_temp_offset(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_offset[nr])); +} +static ssize_t set_temp_offset(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_offset[nr] = TEMP_TO_REG(val); + adm1026_write_value(client, ADM1026_REG_TEMP_OFFSET[nr], + data->temp_offset[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define temp_offset_reg(offset) \ +static SENSOR_DEVICE_ATTR(temp##offset##_offset, S_IRUGO | S_IWUSR, \ + show_temp_offset, set_temp_offset, offset - 1); + +temp_offset_reg(1); +temp_offset_reg(2); +temp_offset_reg(3); + +static ssize_t show_temp_auto_point1_temp_hyst(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG( + ADM1026_FAN_ACTIVATION_TEMP_HYST + data->temp_tmin[nr])); +} +static ssize_t show_temp_auto_point2_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_tmin[nr] + + ADM1026_FAN_CONTROL_TEMP_RANGE)); +} +static ssize_t show_temp_auto_point1_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_tmin[nr])); +} +static ssize_t set_temp_auto_point1_temp(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_tmin[nr] = TEMP_TO_REG(val); + adm1026_write_value(client, ADM1026_REG_TEMP_TMIN[nr], + data->temp_tmin[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define temp_auto_point(offset) \ +static SENSOR_DEVICE_ATTR(temp##offset##_auto_point1_temp, \ + S_IRUGO | S_IWUSR, show_temp_auto_point1_temp, \ + set_temp_auto_point1_temp, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_auto_point1_temp_hyst, S_IRUGO,\ + show_temp_auto_point1_temp_hyst, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_auto_point2_temp, S_IRUGO, \ + show_temp_auto_point2_temp, NULL, offset - 1); + +temp_auto_point(1); +temp_auto_point(2); +temp_auto_point(3); + +static ssize_t show_temp_crit_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", (data->config1 & CFG1_THERM_HOT) >> 4); +} +static ssize_t set_temp_crit_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 1) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->config1 = (data->config1 & ~CFG1_THERM_HOT) | (val << 4); + adm1026_write_value(client, ADM1026_REG_CONFIG1, data->config1); + mutex_unlock(&data->update_lock); + + return count; +} + +#define temp_crit_enable(offset) \ +static DEVICE_ATTR(temp##offset##_crit_enable, S_IRUGO | S_IWUSR, \ + show_temp_crit_enable, set_temp_crit_enable); + +temp_crit_enable(1); +temp_crit_enable(2); +temp_crit_enable(3); + +static ssize_t show_temp_crit(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_crit[nr])); +} +static ssize_t set_temp_crit(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_crit[nr] = TEMP_TO_REG(val); + adm1026_write_value(client, ADM1026_REG_TEMP_THERM[nr], + data->temp_crit[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define temp_crit_reg(offset) \ +static SENSOR_DEVICE_ATTR(temp##offset##_crit, S_IRUGO | S_IWUSR, \ + show_temp_crit, set_temp_crit, offset - 1); + +temp_crit_reg(1); +temp_crit_reg(2); +temp_crit_reg(3); + +static ssize_t analog_out_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", DAC_FROM_REG(data->analog_out)); +} +static ssize_t analog_out_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->analog_out = DAC_TO_REG(val); + adm1026_write_value(client, ADM1026_REG_DAC, data->analog_out); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RW(analog_out); + +static ssize_t cpu0_vid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adm1026_data *data = adm1026_update_device(dev); + int vid = (data->gpio >> 11) & 0x1f; + + dev_dbg(dev, "Setting VID from GPIO11-15.\n"); + return sprintf(buf, "%d\n", vid_from_reg(vid, data->vrm)); +} + +static DEVICE_ATTR_RO(cpu0_vid); + +static ssize_t vrm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adm1026_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", data->vrm); +} + +static ssize_t vrm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1026_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 255) + return -EINVAL; + + data->vrm = val; + return count; +} + +static DEVICE_ATTR_RW(vrm); + +static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%ld\n", data->alarms); +} + +static DEVICE_ATTR_RO(alarms); + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adm1026_data *data = adm1026_update_device(dev); + int bitnr = to_sensor_dev_attr(attr)->index; + return sprintf(buf, "%ld\n", (data->alarms >> bitnr) & 1); +} + +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in9_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in11_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in12_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in13_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(in14_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(in15_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(in16_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 12); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 13); +static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 14); +static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 15); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 16); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 17); +static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, 18); +static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO, show_alarm, NULL, 19); +static SENSOR_DEVICE_ATTR(fan5_alarm, S_IRUGO, show_alarm, NULL, 20); +static SENSOR_DEVICE_ATTR(fan6_alarm, S_IRUGO, show_alarm, NULL, 21); +static SENSOR_DEVICE_ATTR(fan7_alarm, S_IRUGO, show_alarm, NULL, 22); +static SENSOR_DEVICE_ATTR(fan8_alarm, S_IRUGO, show_alarm, NULL, 23); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 24); +static SENSOR_DEVICE_ATTR(in10_alarm, S_IRUGO, show_alarm, NULL, 25); +static SENSOR_DEVICE_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 26); + +static ssize_t alarm_mask_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%ld\n", data->alarm_mask); +} +static ssize_t alarm_mask_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long mask; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->alarm_mask = val & 0x7fffffff; + mask = data->alarm_mask + | (data->gpio_mask & 0x10000 ? 0x80000000 : 0); + adm1026_write_value(client, ADM1026_REG_MASK1, + mask & 0xff); + mask >>= 8; + adm1026_write_value(client, ADM1026_REG_MASK2, + mask & 0xff); + mask >>= 8; + adm1026_write_value(client, ADM1026_REG_MASK3, + mask & 0xff); + mask >>= 8; + adm1026_write_value(client, ADM1026_REG_MASK4, + mask & 0xff); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RW(alarm_mask); + + +static ssize_t gpio_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%ld\n", data->gpio); +} +static ssize_t gpio_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long gpio; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->gpio = val & 0x1ffff; + gpio = data->gpio; + adm1026_write_value(client, ADM1026_REG_GPIO_STATUS_0_7, gpio & 0xff); + gpio >>= 8; + adm1026_write_value(client, ADM1026_REG_GPIO_STATUS_8_15, gpio & 0xff); + gpio = ((gpio >> 1) & 0x80) | (data->alarms >> 24 & 0x7f); + adm1026_write_value(client, ADM1026_REG_STATUS4, gpio & 0xff); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RW(gpio); + +static ssize_t gpio_mask_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%ld\n", data->gpio_mask); +} +static ssize_t gpio_mask_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long mask; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->gpio_mask = val & 0x1ffff; + mask = data->gpio_mask; + adm1026_write_value(client, ADM1026_REG_GPIO_MASK_0_7, mask & 0xff); + mask >>= 8; + adm1026_write_value(client, ADM1026_REG_GPIO_MASK_8_15, mask & 0xff); + mask = ((mask >> 1) & 0x80) | (data->alarm_mask >> 24 & 0x7f); + adm1026_write_value(client, ADM1026_REG_MASK1, mask & 0xff); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RW(gpio_mask); + +static ssize_t pwm1_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", PWM_FROM_REG(data->pwm1.pwm)); +} + +static ssize_t pwm1_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + if (data->pwm1.enable == 1) { + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->pwm1.pwm = PWM_TO_REG(val); + adm1026_write_value(client, ADM1026_REG_PWM, data->pwm1.pwm); + mutex_unlock(&data->update_lock); + } + return count; +} + +static ssize_t temp1_auto_point1_pwm_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", data->pwm1.auto_pwm_min); +} + +static ssize_t temp1_auto_point1_pwm_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->pwm1.auto_pwm_min = clamp_val(val, 0, 255); + if (data->pwm1.enable == 2) { /* apply immediately */ + data->pwm1.pwm = PWM_TO_REG((data->pwm1.pwm & 0x0f) | + PWM_MIN_TO_REG(data->pwm1.auto_pwm_min)); + adm1026_write_value(client, ADM1026_REG_PWM, data->pwm1.pwm); + } + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t temp1_auto_point2_pwm_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", ADM1026_PWM_MAX); +} + +static ssize_t pwm1_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adm1026_data *data = adm1026_update_device(dev); + return sprintf(buf, "%d\n", data->pwm1.enable); +} + +static ssize_t pwm1_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1026_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int old_enable; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val >= 3) + return -EINVAL; + + mutex_lock(&data->update_lock); + old_enable = data->pwm1.enable; + data->pwm1.enable = val; + data->config1 = (data->config1 & ~CFG1_PWM_AFC) + | ((val == 2) ? CFG1_PWM_AFC : 0); + adm1026_write_value(client, ADM1026_REG_CONFIG1, data->config1); + if (val == 2) { /* apply pwm1_auto_pwm_min to pwm1 */ + data->pwm1.pwm = PWM_TO_REG((data->pwm1.pwm & 0x0f) | + PWM_MIN_TO_REG(data->pwm1.auto_pwm_min)); + adm1026_write_value(client, ADM1026_REG_PWM, data->pwm1.pwm); + } else if (!((old_enable == 1) && (val == 1))) { + /* set pwm to safe value */ + data->pwm1.pwm = 255; + adm1026_write_value(client, ADM1026_REG_PWM, data->pwm1.pwm); + } + mutex_unlock(&data->update_lock); + + return count; +} + +/* enable PWM fan control */ +static DEVICE_ATTR_RW(pwm1); +static DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, pwm1_show, pwm1_store); +static DEVICE_ATTR(pwm3, S_IRUGO | S_IWUSR, pwm1_show, pwm1_store); +static DEVICE_ATTR_RW(pwm1_enable); +static DEVICE_ATTR(pwm2_enable, S_IRUGO | S_IWUSR, pwm1_enable_show, + pwm1_enable_store); +static DEVICE_ATTR(pwm3_enable, S_IRUGO | S_IWUSR, pwm1_enable_show, + pwm1_enable_store); +static DEVICE_ATTR_RW(temp1_auto_point1_pwm); +static DEVICE_ATTR(temp2_auto_point1_pwm, S_IRUGO | S_IWUSR, + temp1_auto_point1_pwm_show, temp1_auto_point1_pwm_store); +static DEVICE_ATTR(temp3_auto_point1_pwm, S_IRUGO | S_IWUSR, + temp1_auto_point1_pwm_show, temp1_auto_point1_pwm_store); + +static DEVICE_ATTR_RO(temp1_auto_point2_pwm); +static DEVICE_ATTR(temp2_auto_point2_pwm, S_IRUGO, temp1_auto_point2_pwm_show, + NULL); +static DEVICE_ATTR(temp3_auto_point2_pwm, S_IRUGO, temp1_auto_point2_pwm_show, + NULL); + +static struct attribute *adm1026_attributes[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in6_alarm.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in7_alarm.dev_attr.attr, + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in10_max.dev_attr.attr, + &sensor_dev_attr_in10_min.dev_attr.attr, + &sensor_dev_attr_in10_alarm.dev_attr.attr, + &sensor_dev_attr_in11_input.dev_attr.attr, + &sensor_dev_attr_in11_max.dev_attr.attr, + &sensor_dev_attr_in11_min.dev_attr.attr, + &sensor_dev_attr_in11_alarm.dev_attr.attr, + &sensor_dev_attr_in12_input.dev_attr.attr, + &sensor_dev_attr_in12_max.dev_attr.attr, + &sensor_dev_attr_in12_min.dev_attr.attr, + &sensor_dev_attr_in12_alarm.dev_attr.attr, + &sensor_dev_attr_in13_input.dev_attr.attr, + &sensor_dev_attr_in13_max.dev_attr.attr, + &sensor_dev_attr_in13_min.dev_attr.attr, + &sensor_dev_attr_in13_alarm.dev_attr.attr, + &sensor_dev_attr_in14_input.dev_attr.attr, + &sensor_dev_attr_in14_max.dev_attr.attr, + &sensor_dev_attr_in14_min.dev_attr.attr, + &sensor_dev_attr_in14_alarm.dev_attr.attr, + &sensor_dev_attr_in15_input.dev_attr.attr, + &sensor_dev_attr_in15_max.dev_attr.attr, + &sensor_dev_attr_in15_min.dev_attr.attr, + &sensor_dev_attr_in15_alarm.dev_attr.attr, + &sensor_dev_attr_in16_input.dev_attr.attr, + &sensor_dev_attr_in16_max.dev_attr.attr, + &sensor_dev_attr_in16_min.dev_attr.attr, + &sensor_dev_attr_in16_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan3_div.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan4_div.dev_attr.attr, + &sensor_dev_attr_fan4_min.dev_attr.attr, + &sensor_dev_attr_fan4_alarm.dev_attr.attr, + &sensor_dev_attr_fan5_input.dev_attr.attr, + &sensor_dev_attr_fan5_div.dev_attr.attr, + &sensor_dev_attr_fan5_min.dev_attr.attr, + &sensor_dev_attr_fan5_alarm.dev_attr.attr, + &sensor_dev_attr_fan6_input.dev_attr.attr, + &sensor_dev_attr_fan6_div.dev_attr.attr, + &sensor_dev_attr_fan6_min.dev_attr.attr, + &sensor_dev_attr_fan6_alarm.dev_attr.attr, + &sensor_dev_attr_fan7_input.dev_attr.attr, + &sensor_dev_attr_fan7_div.dev_attr.attr, + &sensor_dev_attr_fan7_min.dev_attr.attr, + &sensor_dev_attr_fan7_alarm.dev_attr.attr, + &sensor_dev_attr_fan8_input.dev_attr.attr, + &sensor_dev_attr_fan8_div.dev_attr.attr, + &sensor_dev_attr_fan8_min.dev_attr.attr, + &sensor_dev_attr_fan8_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_offset.dev_attr.attr, + &sensor_dev_attr_temp2_offset.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &dev_attr_temp1_crit_enable.attr, + &dev_attr_temp2_crit_enable.attr, + &dev_attr_cpu0_vid.attr, + &dev_attr_vrm.attr, + &dev_attr_alarms.attr, + &dev_attr_alarm_mask.attr, + &dev_attr_gpio.attr, + &dev_attr_gpio_mask.attr, + &dev_attr_pwm1.attr, + &dev_attr_pwm2.attr, + &dev_attr_pwm3.attr, + &dev_attr_pwm1_enable.attr, + &dev_attr_pwm2_enable.attr, + &dev_attr_pwm3_enable.attr, + &dev_attr_temp1_auto_point1_pwm.attr, + &dev_attr_temp2_auto_point1_pwm.attr, + &dev_attr_temp1_auto_point2_pwm.attr, + &dev_attr_temp2_auto_point2_pwm.attr, + &dev_attr_analog_out.attr, + NULL +}; + +static const struct attribute_group adm1026_group = { + .attrs = adm1026_attributes, +}; + +static struct attribute *adm1026_attributes_temp3[] = { + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_offset.dev_attr.attr, + &sensor_dev_attr_temp3_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_temp3_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &dev_attr_temp3_crit_enable.attr, + &dev_attr_temp3_auto_point1_pwm.attr, + &dev_attr_temp3_auto_point2_pwm.attr, + NULL +}; + +static const struct attribute_group adm1026_group_temp3 = { + .attrs = adm1026_attributes_temp3, +}; + +static struct attribute *adm1026_attributes_in8_9[] = { + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in8_max.dev_attr.attr, + &sensor_dev_attr_in8_min.dev_attr.attr, + &sensor_dev_attr_in8_alarm.dev_attr.attr, + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in9_max.dev_attr.attr, + &sensor_dev_attr_in9_min.dev_attr.attr, + &sensor_dev_attr_in9_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group adm1026_group_in8_9 = { + .attrs = adm1026_attributes_in8_9, +}; + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int adm1026_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int address = client->addr; + int company, verstep; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + /* We need to be able to do byte I/O */ + return -ENODEV; + } + + /* Now, we do the remaining detection. */ + + company = adm1026_read_value(client, ADM1026_REG_COMPANY); + verstep = adm1026_read_value(client, ADM1026_REG_VERSTEP); + + dev_dbg(&adapter->dev, + "Detecting device at %d,0x%02x with COMPANY: 0x%02x and VERSTEP: 0x%02x\n", + i2c_adapter_id(client->adapter), client->addr, + company, verstep); + + /* Determine the chip type. */ + dev_dbg(&adapter->dev, "Autodetecting device at %d,0x%02x...\n", + i2c_adapter_id(adapter), address); + if (company == ADM1026_COMPANY_ANALOG_DEV + && verstep == ADM1026_VERSTEP_ADM1026) { + /* Analog Devices ADM1026 */ + } else if (company == ADM1026_COMPANY_ANALOG_DEV + && (verstep & 0xf0) == ADM1026_VERSTEP_GENERIC) { + dev_err(&adapter->dev, + "Unrecognized stepping 0x%02x. Defaulting to ADM1026.\n", + verstep); + } else if ((verstep & 0xf0) == ADM1026_VERSTEP_GENERIC) { + dev_err(&adapter->dev, + "Found version/stepping 0x%02x. Assuming generic ADM1026.\n", + verstep); + } else { + dev_dbg(&adapter->dev, "Autodetection failed\n"); + /* Not an ADM1026... */ + return -ENODEV; + } + + strlcpy(info->type, "adm1026", I2C_NAME_SIZE); + + return 0; +} + +static void adm1026_print_gpio(struct i2c_client *client) +{ + struct adm1026_data *data = i2c_get_clientdata(client); + int i; + + dev_dbg(&client->dev, "GPIO config is:\n"); + for (i = 0; i <= 7; ++i) { + if (data->config2 & (1 << i)) { + dev_dbg(&client->dev, "\t%sGP%s%d\n", + data->gpio_config[i] & 0x02 ? "" : "!", + data->gpio_config[i] & 0x01 ? "OUT" : "IN", + i); + } else { + dev_dbg(&client->dev, "\tFAN%d\n", i); + } + } + for (i = 8; i <= 15; ++i) { + dev_dbg(&client->dev, "\t%sGP%s%d\n", + data->gpio_config[i] & 0x02 ? "" : "!", + data->gpio_config[i] & 0x01 ? "OUT" : "IN", + i); + } + if (data->config3 & CFG3_GPIO16_ENABLE) { + dev_dbg(&client->dev, "\t%sGP%s16\n", + data->gpio_config[16] & 0x02 ? "" : "!", + data->gpio_config[16] & 0x01 ? "OUT" : "IN"); + } else { + /* GPIO16 is THERM */ + dev_dbg(&client->dev, "\tTHERM\n"); + } +} + +static void adm1026_fixup_gpio(struct i2c_client *client) +{ + struct adm1026_data *data = i2c_get_clientdata(client); + int i; + int value; + + /* Make the changes requested. */ + /* + * We may need to unlock/stop monitoring or soft-reset the + * chip before we can make changes. This hasn't been + * tested much. FIXME + */ + + /* Make outputs */ + for (i = 0; i <= 16; ++i) { + if (gpio_output[i] >= 0 && gpio_output[i] <= 16) + data->gpio_config[gpio_output[i]] |= 0x01; + /* if GPIO0-7 is output, it isn't a FAN tach */ + if (gpio_output[i] >= 0 && gpio_output[i] <= 7) + data->config2 |= 1 << gpio_output[i]; + } + + /* Input overrides output */ + for (i = 0; i <= 16; ++i) { + if (gpio_input[i] >= 0 && gpio_input[i] <= 16) + data->gpio_config[gpio_input[i]] &= ~0x01; + /* if GPIO0-7 is input, it isn't a FAN tach */ + if (gpio_input[i] >= 0 && gpio_input[i] <= 7) + data->config2 |= 1 << gpio_input[i]; + } + + /* Inverted */ + for (i = 0; i <= 16; ++i) { + if (gpio_inverted[i] >= 0 && gpio_inverted[i] <= 16) + data->gpio_config[gpio_inverted[i]] &= ~0x02; + } + + /* Normal overrides inverted */ + for (i = 0; i <= 16; ++i) { + if (gpio_normal[i] >= 0 && gpio_normal[i] <= 16) + data->gpio_config[gpio_normal[i]] |= 0x02; + } + + /* Fan overrides input and output */ + for (i = 0; i <= 7; ++i) { + if (gpio_fan[i] >= 0 && gpio_fan[i] <= 7) + data->config2 &= ~(1 << gpio_fan[i]); + } + + /* Write new configs to registers */ + adm1026_write_value(client, ADM1026_REG_CONFIG2, data->config2); + data->config3 = (data->config3 & 0x3f) + | ((data->gpio_config[16] & 0x03) << 6); + adm1026_write_value(client, ADM1026_REG_CONFIG3, data->config3); + for (i = 15, value = 0; i >= 0; --i) { + value <<= 2; + value |= data->gpio_config[i] & 0x03; + if ((i & 0x03) == 0) { + adm1026_write_value(client, + ADM1026_REG_GPIO_CFG_0_3 + i/4, + value); + value = 0; + } + } + + /* Print the new config */ + adm1026_print_gpio(client); +} + +static void adm1026_init_client(struct i2c_client *client) +{ + int value, i; + struct adm1026_data *data = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "Initializing device\n"); + /* Read chip config */ + data->config1 = adm1026_read_value(client, ADM1026_REG_CONFIG1); + data->config2 = adm1026_read_value(client, ADM1026_REG_CONFIG2); + data->config3 = adm1026_read_value(client, ADM1026_REG_CONFIG3); + + /* Inform user of chip config */ + dev_dbg(&client->dev, "ADM1026_REG_CONFIG1 is: 0x%02x\n", + data->config1); + if ((data->config1 & CFG1_MONITOR) == 0) { + dev_dbg(&client->dev, + "Monitoring not currently enabled.\n"); + } + if (data->config1 & CFG1_INT_ENABLE) { + dev_dbg(&client->dev, + "SMBALERT interrupts are enabled.\n"); + } + if (data->config1 & CFG1_AIN8_9) { + dev_dbg(&client->dev, + "in8 and in9 enabled. temp3 disabled.\n"); + } else { + dev_dbg(&client->dev, + "temp3 enabled. in8 and in9 disabled.\n"); + } + if (data->config1 & CFG1_THERM_HOT) { + dev_dbg(&client->dev, + "Automatic THERM, PWM, and temp limits enabled.\n"); + } + + if (data->config3 & CFG3_GPIO16_ENABLE) { + dev_dbg(&client->dev, + "GPIO16 enabled. THERM pin disabled.\n"); + } else { + dev_dbg(&client->dev, + "THERM pin enabled. GPIO16 disabled.\n"); + } + if (data->config3 & CFG3_VREF_250) + dev_dbg(&client->dev, "Vref is 2.50 Volts.\n"); + else + dev_dbg(&client->dev, "Vref is 1.82 Volts.\n"); + /* Read and pick apart the existing GPIO configuration */ + value = 0; + for (i = 0; i <= 15; ++i) { + if ((i & 0x03) == 0) { + value = adm1026_read_value(client, + ADM1026_REG_GPIO_CFG_0_3 + i / 4); + } + data->gpio_config[i] = value & 0x03; + value >>= 2; + } + data->gpio_config[16] = (data->config3 >> 6) & 0x03; + + /* ... and then print it */ + adm1026_print_gpio(client); + + /* + * If the user asks us to reprogram the GPIO config, then + * do it now. + */ + if (gpio_input[0] != -1 || gpio_output[0] != -1 + || gpio_inverted[0] != -1 || gpio_normal[0] != -1 + || gpio_fan[0] != -1) { + adm1026_fixup_gpio(client); + } + + /* + * WE INTENTIONALLY make no changes to the limits, + * offsets, pwms, fans and zones. If they were + * configured, we don't want to mess with them. + * If they weren't, the default is 100% PWM, no + * control and will suffice until 'sensors -s' + * can be run by the user. We DO set the default + * value for pwm1.auto_pwm_min to its maximum + * so that enabling automatic pwm fan control + * without first setting a value for pwm1.auto_pwm_min + * will not result in potentially dangerous fan speed decrease. + */ + data->pwm1.auto_pwm_min = 255; + /* Start monitoring */ + value = adm1026_read_value(client, ADM1026_REG_CONFIG1); + /* Set MONITOR, clear interrupt acknowledge and s/w reset */ + value = (value | CFG1_MONITOR) & (~CFG1_INT_CLEAR & ~CFG1_RESET); + dev_dbg(&client->dev, "Setting CONFIG to: 0x%02x\n", value); + data->config1 = value; + adm1026_write_value(client, ADM1026_REG_CONFIG1, value); + + /* initialize fan_div[] to hardware defaults */ + value = adm1026_read_value(client, ADM1026_REG_FAN_DIV_0_3) | + (adm1026_read_value(client, ADM1026_REG_FAN_DIV_4_7) << 8); + for (i = 0; i <= 7; ++i) { + data->fan_div[i] = DIV_FROM_REG(value & 0x03); + value >>= 2; + } +} + +static int adm1026_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct adm1026_data *data; + + data = devm_kzalloc(dev, sizeof(struct adm1026_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->client = client; + mutex_init(&data->update_lock); + + /* Set the VRM version */ + data->vrm = vid_which_vrm(); + + /* Initialize the ADM1026 chip */ + adm1026_init_client(client); + + /* sysfs hooks */ + data->groups[0] = &adm1026_group; + if (data->config1 & CFG1_AIN8_9) + data->groups[1] = &adm1026_group_in8_9; + else + data->groups[1] = &adm1026_group_temp3; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id adm1026_id[] = { + { "adm1026", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adm1026_id); + +static struct i2c_driver adm1026_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "adm1026", + }, + .probe = adm1026_probe, + .id_table = adm1026_id, + .detect = adm1026_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(adm1026_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Philip Pokorny , " + "Justin Thiessen "); +MODULE_DESCRIPTION("ADM1026 driver"); diff --git a/drivers/hwmon/adm1029.c b/drivers/hwmon/adm1029.c new file mode 100644 index 000000000..8c5cdb560 --- /dev/null +++ b/drivers/hwmon/adm1029.c @@ -0,0 +1,417 @@ +/* + * adm1029.c - Part of lm_sensors, Linux kernel modules for hardware monitoring + * + * Copyright (C) 2006 Corentin LABBE + * + * Based on LM83 Driver by Jean Delvare + * + * Give only processor, motherboard temperatures and fan tachs + * Very rare chip please let me know if you use it + * + * http://www.analog.com/UploadedFiles/Data_Sheets/ADM1029.pdf + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Addresses to scan + */ + +static const unsigned short normal_i2c[] = { 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, I2C_CLIENT_END +}; + +/* + * The ADM1029 registers + * Manufacturer ID is 0x41 for Analog Devices + */ + +#define ADM1029_REG_MAN_ID 0x0D +#define ADM1029_REG_CHIP_ID 0x0E +#define ADM1029_REG_CONFIG 0x01 +#define ADM1029_REG_NB_FAN_SUPPORT 0x02 + +#define ADM1029_REG_TEMP_DEVICES_INSTALLED 0x06 + +#define ADM1029_REG_LOCAL_TEMP 0xA0 +#define ADM1029_REG_REMOTE1_TEMP 0xA1 +#define ADM1029_REG_REMOTE2_TEMP 0xA2 + +#define ADM1029_REG_LOCAL_TEMP_HIGH 0x90 +#define ADM1029_REG_REMOTE1_TEMP_HIGH 0x91 +#define ADM1029_REG_REMOTE2_TEMP_HIGH 0x92 + +#define ADM1029_REG_LOCAL_TEMP_LOW 0x98 +#define ADM1029_REG_REMOTE1_TEMP_LOW 0x99 +#define ADM1029_REG_REMOTE2_TEMP_LOW 0x9A + +#define ADM1029_REG_FAN1 0x70 +#define ADM1029_REG_FAN2 0x71 + +#define ADM1029_REG_FAN1_MIN 0x78 +#define ADM1029_REG_FAN2_MIN 0x79 + +#define ADM1029_REG_FAN1_CONFIG 0x68 +#define ADM1029_REG_FAN2_CONFIG 0x69 + +#define TEMP_FROM_REG(val) ((val) * 1000) + +#define DIV_FROM_REG(val) (1 << (((val) >> 6) - 1)) + +/* Registers to be checked by adm1029_update_device() */ +static const u8 ADM1029_REG_TEMP[] = { + ADM1029_REG_LOCAL_TEMP, + ADM1029_REG_REMOTE1_TEMP, + ADM1029_REG_REMOTE2_TEMP, + ADM1029_REG_LOCAL_TEMP_HIGH, + ADM1029_REG_REMOTE1_TEMP_HIGH, + ADM1029_REG_REMOTE2_TEMP_HIGH, + ADM1029_REG_LOCAL_TEMP_LOW, + ADM1029_REG_REMOTE1_TEMP_LOW, + ADM1029_REG_REMOTE2_TEMP_LOW, +}; + +static const u8 ADM1029_REG_FAN[] = { + ADM1029_REG_FAN1, + ADM1029_REG_FAN2, + ADM1029_REG_FAN1_MIN, + ADM1029_REG_FAN2_MIN, +}; + +static const u8 ADM1029_REG_FAN_DIV[] = { + ADM1029_REG_FAN1_CONFIG, + ADM1029_REG_FAN2_CONFIG, +}; + +/* + * Client data (each client gets its own) + */ + +struct adm1029_data { + struct i2c_client *client; + struct mutex update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* registers values, signed for temperature, unsigned for other stuff */ + s8 temp[ARRAY_SIZE(ADM1029_REG_TEMP)]; + u8 fan[ARRAY_SIZE(ADM1029_REG_FAN)]; + u8 fan_div[ARRAY_SIZE(ADM1029_REG_FAN_DIV)]; +}; + +/* + * function that update the status of the chips (temperature for example) + */ +static struct adm1029_data *adm1029_update_device(struct device *dev) +{ + struct adm1029_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + mutex_lock(&data->update_lock); + /* + * Use the "cache" Luke, don't recheck values + * if there are already checked not a long time later + */ + if (time_after(jiffies, data->last_updated + HZ * 2) + || !data->valid) { + int nr; + + dev_dbg(&client->dev, "Updating adm1029 data\n"); + + for (nr = 0; nr < ARRAY_SIZE(ADM1029_REG_TEMP); nr++) { + data->temp[nr] = + i2c_smbus_read_byte_data(client, + ADM1029_REG_TEMP[nr]); + } + for (nr = 0; nr < ARRAY_SIZE(ADM1029_REG_FAN); nr++) { + data->fan[nr] = + i2c_smbus_read_byte_data(client, + ADM1029_REG_FAN[nr]); + } + for (nr = 0; nr < ARRAY_SIZE(ADM1029_REG_FAN_DIV); nr++) { + data->fan_div[nr] = + i2c_smbus_read_byte_data(client, + ADM1029_REG_FAN_DIV[nr]); + } + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Sysfs stuff + */ + +static ssize_t +show_temp(struct device *dev, struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adm1029_data *data = adm1029_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index])); +} + +static ssize_t +show_fan(struct device *dev, struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adm1029_data *data = adm1029_update_device(dev); + u16 val; + if (data->fan[attr->index] == 0 + || (data->fan_div[attr->index] & 0xC0) == 0 + || data->fan[attr->index] == 255) { + return sprintf(buf, "0\n"); + } + + val = 1880 * 120 / DIV_FROM_REG(data->fan_div[attr->index]) + / data->fan[attr->index]; + return sprintf(buf, "%d\n", val); +} + +static ssize_t +show_fan_div(struct device *dev, struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adm1029_data *data = adm1029_update_device(dev); + if ((data->fan_div[attr->index] & 0xC0) == 0) + return sprintf(buf, "0\n"); + return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[attr->index])); +} + +static ssize_t set_fan_div(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count) +{ + struct adm1029_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + u8 reg; + long val; + int ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + mutex_lock(&data->update_lock); + + /*Read actual config */ + reg = i2c_smbus_read_byte_data(client, + ADM1029_REG_FAN_DIV[attr->index]); + + switch (val) { + case 1: + val = 1; + break; + case 2: + val = 2; + break; + case 4: + val = 3; + break; + default: + mutex_unlock(&data->update_lock); + dev_err(&client->dev, + "fan_div value %ld not supported. Choose one of 1, 2 or 4!\n", + val); + return -EINVAL; + } + /* Update the value */ + reg = (reg & 0x3F) | (val << 6); + + /* Update the cache */ + data->fan_div[attr->index] = reg; + + /* Write value */ + i2c_smbus_write_byte_data(client, + ADM1029_REG_FAN_DIV[attr->index], reg); + mutex_unlock(&data->update_lock); + + return count; +} + +/* + * Access rights on sysfs. S_IRUGO: Is Readable by User, Group and Others + * S_IWUSR: Is Writable by User. + */ +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); + +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO, show_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO, show_temp, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO, show_temp, NULL, 6); +static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO, show_temp, NULL, 7); +static SENSOR_DEVICE_ATTR(temp3_min, S_IRUGO, show_temp, NULL, 8); + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); + +static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO, show_fan, NULL, 2); +static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO, show_fan, NULL, 3); + +static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR, + show_fan_div, set_fan_div, 0); +static SENSOR_DEVICE_ATTR(fan2_div, S_IRUGO | S_IWUSR, + show_fan_div, set_fan_div, 1); + +static struct attribute *adm1029_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(adm1029); + +/* + * Real code + */ + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int adm1029_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + u8 man_id, chip_id, temp_devices_installed, nb_fan_support; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* + * ADM1029 doesn't have CHIP ID, check just MAN ID + * For better detection we check also ADM1029_TEMP_DEVICES_INSTALLED, + * ADM1029_REG_NB_FAN_SUPPORT and compare it with possible values + * documented + */ + + man_id = i2c_smbus_read_byte_data(client, ADM1029_REG_MAN_ID); + chip_id = i2c_smbus_read_byte_data(client, ADM1029_REG_CHIP_ID); + temp_devices_installed = i2c_smbus_read_byte_data(client, + ADM1029_REG_TEMP_DEVICES_INSTALLED); + nb_fan_support = i2c_smbus_read_byte_data(client, + ADM1029_REG_NB_FAN_SUPPORT); + /* 0x41 is Analog Devices */ + if (man_id != 0x41 || (temp_devices_installed & 0xf9) != 0x01 + || nb_fan_support != 0x03) + return -ENODEV; + + if ((chip_id & 0xF0) != 0x00) { + /* + * There are no "official" CHIP ID, so actually + * we use Major/Minor revision for that + */ + pr_info("Unknown major revision %x, please let us know\n", + chip_id); + return -ENODEV; + } + + strlcpy(info->type, "adm1029", I2C_NAME_SIZE); + + return 0; +} + +static int adm1029_init_client(struct i2c_client *client) +{ + u8 config; + + config = i2c_smbus_read_byte_data(client, ADM1029_REG_CONFIG); + if ((config & 0x10) == 0) { + i2c_smbus_write_byte_data(client, ADM1029_REG_CONFIG, + config | 0x10); + } + /* recheck config */ + config = i2c_smbus_read_byte_data(client, ADM1029_REG_CONFIG); + if ((config & 0x10) == 0) { + dev_err(&client->dev, "Initialization failed!\n"); + return 0; + } + return 1; +} + +static int adm1029_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct adm1029_data *data; + struct device *hwmon_dev; + + data = devm_kzalloc(dev, sizeof(struct adm1029_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* + * Initialize the ADM1029 chip + * Check config register + */ + if (adm1029_init_client(client) == 0) + return -ENODEV; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + adm1029_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id adm1029_id[] = { + { "adm1029", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adm1029_id); + +static struct i2c_driver adm1029_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "adm1029", + }, + .probe = adm1029_probe, + .id_table = adm1029_id, + .detect = adm1029_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(adm1029_driver); + +MODULE_AUTHOR("Corentin LABBE "); +MODULE_DESCRIPTION("adm1029 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/adm1031.c b/drivers/hwmon/adm1031.c new file mode 100644 index 000000000..bcf508269 --- /dev/null +++ b/drivers/hwmon/adm1031.c @@ -0,0 +1,1101 @@ +/* + * adm1031.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Based on lm75.c and lm85.c + * Supports adm1030 / adm1031 + * Copyright (C) 2004 Alexandre d'Alton + * Reworked by Jean Delvare + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Following macros takes channel parameter starting from 0 to 2 */ +#define ADM1031_REG_FAN_SPEED(nr) (0x08 + (nr)) +#define ADM1031_REG_FAN_DIV(nr) (0x20 + (nr)) +#define ADM1031_REG_PWM (0x22) +#define ADM1031_REG_FAN_MIN(nr) (0x10 + (nr)) +#define ADM1031_REG_FAN_FILTER (0x23) + +#define ADM1031_REG_TEMP_OFFSET(nr) (0x0d + (nr)) +#define ADM1031_REG_TEMP_MAX(nr) (0x14 + 4 * (nr)) +#define ADM1031_REG_TEMP_MIN(nr) (0x15 + 4 * (nr)) +#define ADM1031_REG_TEMP_CRIT(nr) (0x16 + 4 * (nr)) + +#define ADM1031_REG_TEMP(nr) (0x0a + (nr)) +#define ADM1031_REG_AUTO_TEMP(nr) (0x24 + (nr)) + +#define ADM1031_REG_STATUS(nr) (0x2 + (nr)) + +#define ADM1031_REG_CONF1 0x00 +#define ADM1031_REG_CONF2 0x01 +#define ADM1031_REG_EXT_TEMP 0x06 + +#define ADM1031_CONF1_MONITOR_ENABLE 0x01 /* Monitoring enable */ +#define ADM1031_CONF1_PWM_INVERT 0x08 /* PWM Invert */ +#define ADM1031_CONF1_AUTO_MODE 0x80 /* Auto FAN */ + +#define ADM1031_CONF2_PWM1_ENABLE 0x01 +#define ADM1031_CONF2_PWM2_ENABLE 0x02 +#define ADM1031_CONF2_TACH1_ENABLE 0x04 +#define ADM1031_CONF2_TACH2_ENABLE 0x08 +#define ADM1031_CONF2_TEMP_ENABLE(chan) (0x10 << (chan)) + +#define ADM1031_UPDATE_RATE_MASK 0x1c +#define ADM1031_UPDATE_RATE_SHIFT 2 + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END }; + +enum chips { adm1030, adm1031 }; + +typedef u8 auto_chan_table_t[8][2]; + +/* Each client has this additional data */ +struct adm1031_data { + struct i2c_client *client; + const struct attribute_group *groups[3]; + struct mutex update_lock; + int chip_type; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + unsigned int update_interval; /* In milliseconds */ + /* + * The chan_select_table contains the possible configurations for + * auto fan control. + */ + const auto_chan_table_t *chan_select_table; + u16 alarm; + u8 conf1; + u8 conf2; + u8 fan[2]; + u8 fan_div[2]; + u8 fan_min[2]; + u8 pwm[2]; + u8 old_pwm[2]; + s8 temp[3]; + u8 ext_temp[3]; + u8 auto_temp[3]; + u8 auto_temp_min[3]; + u8 auto_temp_off[3]; + u8 auto_temp_max[3]; + s8 temp_offset[3]; + s8 temp_min[3]; + s8 temp_max[3]; + s8 temp_crit[3]; +}; + +static inline u8 adm1031_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static inline int +adm1031_write_value(struct i2c_client *client, u8 reg, unsigned int value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static struct adm1031_data *adm1031_update_device(struct device *dev) +{ + struct adm1031_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long next_update; + int chan; + + mutex_lock(&data->update_lock); + + next_update = data->last_updated + + msecs_to_jiffies(data->update_interval); + if (time_after(jiffies, next_update) || !data->valid) { + + dev_dbg(&client->dev, "Starting adm1031 update\n"); + for (chan = 0; + chan < ((data->chip_type == adm1031) ? 3 : 2); chan++) { + u8 oldh, newh; + + oldh = + adm1031_read_value(client, ADM1031_REG_TEMP(chan)); + data->ext_temp[chan] = + adm1031_read_value(client, ADM1031_REG_EXT_TEMP); + newh = + adm1031_read_value(client, ADM1031_REG_TEMP(chan)); + if (newh != oldh) { + data->ext_temp[chan] = + adm1031_read_value(client, + ADM1031_REG_EXT_TEMP); +#ifdef DEBUG + oldh = + adm1031_read_value(client, + ADM1031_REG_TEMP(chan)); + + /* oldh is actually newer */ + if (newh != oldh) + dev_warn(&client->dev, + "Remote temperature may be wrong.\n"); +#endif + } + data->temp[chan] = newh; + + data->temp_offset[chan] = + adm1031_read_value(client, + ADM1031_REG_TEMP_OFFSET(chan)); + data->temp_min[chan] = + adm1031_read_value(client, + ADM1031_REG_TEMP_MIN(chan)); + data->temp_max[chan] = + adm1031_read_value(client, + ADM1031_REG_TEMP_MAX(chan)); + data->temp_crit[chan] = + adm1031_read_value(client, + ADM1031_REG_TEMP_CRIT(chan)); + data->auto_temp[chan] = + adm1031_read_value(client, + ADM1031_REG_AUTO_TEMP(chan)); + + } + + data->conf1 = adm1031_read_value(client, ADM1031_REG_CONF1); + data->conf2 = adm1031_read_value(client, ADM1031_REG_CONF2); + + data->alarm = adm1031_read_value(client, ADM1031_REG_STATUS(0)) + | (adm1031_read_value(client, ADM1031_REG_STATUS(1)) << 8); + if (data->chip_type == adm1030) + data->alarm &= 0xc0ff; + + for (chan = 0; chan < (data->chip_type == adm1030 ? 1 : 2); + chan++) { + data->fan_div[chan] = + adm1031_read_value(client, + ADM1031_REG_FAN_DIV(chan)); + data->fan_min[chan] = + adm1031_read_value(client, + ADM1031_REG_FAN_MIN(chan)); + data->fan[chan] = + adm1031_read_value(client, + ADM1031_REG_FAN_SPEED(chan)); + data->pwm[chan] = + (adm1031_read_value(client, + ADM1031_REG_PWM) >> (4 * chan)) & 0x0f; + } + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +#define TEMP_TO_REG(val) (((val) < 0 ? ((val - 500) / 1000) : \ + ((val + 500) / 1000))) + +#define TEMP_FROM_REG(val) ((val) * 1000) + +#define TEMP_FROM_REG_EXT(val, ext) (TEMP_FROM_REG(val) + (ext) * 125) + +#define TEMP_OFFSET_TO_REG(val) (TEMP_TO_REG(val) & 0x8f) +#define TEMP_OFFSET_FROM_REG(val) TEMP_FROM_REG((val) < 0 ? \ + (val) | 0x70 : (val)) + +#define FAN_FROM_REG(reg, div) ((reg) ? \ + (11250 * 60) / ((reg) * (div)) : 0) + +static int FAN_TO_REG(int reg, int div) +{ + int tmp; + tmp = FAN_FROM_REG(clamp_val(reg, 0, 65535), div); + return tmp > 255 ? 255 : tmp; +} + +#define FAN_DIV_FROM_REG(reg) (1<<(((reg)&0xc0)>>6)) + +#define PWM_TO_REG(val) (clamp_val((val), 0, 255) >> 4) +#define PWM_FROM_REG(val) ((val) << 4) + +#define FAN_CHAN_FROM_REG(reg) (((reg) >> 5) & 7) +#define FAN_CHAN_TO_REG(val, reg) \ + (((reg) & 0x1F) | (((val) << 5) & 0xe0)) + +#define AUTO_TEMP_MIN_TO_REG(val, reg) \ + ((((val) / 500) & 0xf8) | ((reg) & 0x7)) +#define AUTO_TEMP_RANGE_FROM_REG(reg) (5000 * (1 << ((reg) & 0x7))) +#define AUTO_TEMP_MIN_FROM_REG(reg) (1000 * ((((reg) >> 3) & 0x1f) << 2)) + +#define AUTO_TEMP_MIN_FROM_REG_DEG(reg) ((((reg) >> 3) & 0x1f) << 2) + +#define AUTO_TEMP_OFF_FROM_REG(reg) \ + (AUTO_TEMP_MIN_FROM_REG(reg) - 5000) + +#define AUTO_TEMP_MAX_FROM_REG(reg) \ + (AUTO_TEMP_RANGE_FROM_REG(reg) + \ + AUTO_TEMP_MIN_FROM_REG(reg)) + +static int AUTO_TEMP_MAX_TO_REG(int val, int reg, int pwm) +{ + int ret; + int range = val - AUTO_TEMP_MIN_FROM_REG(reg); + + range = ((val - AUTO_TEMP_MIN_FROM_REG(reg))*10)/(16 - pwm); + ret = ((reg & 0xf8) | + (range < 10000 ? 0 : + range < 20000 ? 1 : + range < 40000 ? 2 : range < 80000 ? 3 : 4)); + return ret; +} + +/* FAN auto control */ +#define GET_FAN_AUTO_BITFIELD(data, idx) \ + (*(data)->chan_select_table)[FAN_CHAN_FROM_REG((data)->conf1)][idx % 2] + +/* + * The tables below contains the possible values for the auto fan + * control bitfields. the index in the table is the register value. + * MSb is the auto fan control enable bit, so the four first entries + * in the table disables auto fan control when both bitfields are zero. + */ +static const auto_chan_table_t auto_channel_select_table_adm1031 = { + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 2 /* 0b010 */ , 4 /* 0b100 */ }, + { 2 /* 0b010 */ , 2 /* 0b010 */ }, + { 4 /* 0b100 */ , 4 /* 0b100 */ }, + { 7 /* 0b111 */ , 7 /* 0b111 */ }, +}; + +static const auto_chan_table_t auto_channel_select_table_adm1030 = { + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 2 /* 0b10 */ , 0 }, + { 0xff /* invalid */ , 0 }, + { 0xff /* invalid */ , 0 }, + { 3 /* 0b11 */ , 0 }, +}; + +/* + * That function checks if a bitfield is valid and returns the other bitfield + * nearest match if no exact match where found. + */ +static int +get_fan_auto_nearest(struct adm1031_data *data, int chan, u8 val, u8 reg) +{ + int i; + int first_match = -1, exact_match = -1; + u8 other_reg_val = + (*data->chan_select_table)[FAN_CHAN_FROM_REG(reg)][chan ? 0 : 1]; + + if (val == 0) + return 0; + + for (i = 0; i < 8; i++) { + if ((val == (*data->chan_select_table)[i][chan]) && + ((*data->chan_select_table)[i][chan ? 0 : 1] == + other_reg_val)) { + /* We found an exact match */ + exact_match = i; + break; + } else if (val == (*data->chan_select_table)[i][chan] && + first_match == -1) { + /* + * Save the first match in case of an exact match has + * not been found + */ + first_match = i; + } + } + + if (exact_match >= 0) + return exact_match; + else if (first_match >= 0) + return first_match; + + return -EINVAL; +} + +static ssize_t show_fan_auto_channel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct adm1031_data *data = adm1031_update_device(dev); + return sprintf(buf, "%d\n", GET_FAN_AUTO_BITFIELD(data, nr)); +} + +static ssize_t +set_fan_auto_channel(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1031_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = to_sensor_dev_attr(attr)->index; + long val; + u8 reg; + int ret; + u8 old_fan_mode; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + old_fan_mode = data->conf1; + + mutex_lock(&data->update_lock); + + ret = get_fan_auto_nearest(data, nr, val, data->conf1); + if (ret < 0) { + mutex_unlock(&data->update_lock); + return ret; + } + reg = ret; + data->conf1 = FAN_CHAN_TO_REG(reg, data->conf1); + if ((data->conf1 & ADM1031_CONF1_AUTO_MODE) ^ + (old_fan_mode & ADM1031_CONF1_AUTO_MODE)) { + if (data->conf1 & ADM1031_CONF1_AUTO_MODE) { + /* + * Switch to Auto Fan Mode + * Save PWM registers + * Set PWM registers to 33% Both + */ + data->old_pwm[0] = data->pwm[0]; + data->old_pwm[1] = data->pwm[1]; + adm1031_write_value(client, ADM1031_REG_PWM, 0x55); + } else { + /* Switch to Manual Mode */ + data->pwm[0] = data->old_pwm[0]; + data->pwm[1] = data->old_pwm[1]; + /* Restore PWM registers */ + adm1031_write_value(client, ADM1031_REG_PWM, + data->pwm[0] | (data->pwm[1] << 4)); + } + } + data->conf1 = FAN_CHAN_TO_REG(reg, data->conf1); + adm1031_write_value(client, ADM1031_REG_CONF1, data->conf1); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(auto_fan1_channel, S_IRUGO | S_IWUSR, + show_fan_auto_channel, set_fan_auto_channel, 0); +static SENSOR_DEVICE_ATTR(auto_fan2_channel, S_IRUGO | S_IWUSR, + show_fan_auto_channel, set_fan_auto_channel, 1); + +/* Auto Temps */ +static ssize_t show_auto_temp_off(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct adm1031_data *data = adm1031_update_device(dev); + return sprintf(buf, "%d\n", + AUTO_TEMP_OFF_FROM_REG(data->auto_temp[nr])); +} +static ssize_t show_auto_temp_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct adm1031_data *data = adm1031_update_device(dev); + return sprintf(buf, "%d\n", + AUTO_TEMP_MIN_FROM_REG(data->auto_temp[nr])); +} +static ssize_t +set_auto_temp_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1031_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = to_sensor_dev_attr(attr)->index; + long val; + int ret; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + val = clamp_val(val, 0, 127000); + mutex_lock(&data->update_lock); + data->auto_temp[nr] = AUTO_TEMP_MIN_TO_REG(val, data->auto_temp[nr]); + adm1031_write_value(client, ADM1031_REG_AUTO_TEMP(nr), + data->auto_temp[nr]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t show_auto_temp_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct adm1031_data *data = adm1031_update_device(dev); + return sprintf(buf, "%d\n", + AUTO_TEMP_MAX_FROM_REG(data->auto_temp[nr])); +} +static ssize_t +set_auto_temp_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1031_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = to_sensor_dev_attr(attr)->index; + long val; + int ret; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + val = clamp_val(val, 0, 127000); + mutex_lock(&data->update_lock); + data->temp_max[nr] = AUTO_TEMP_MAX_TO_REG(val, data->auto_temp[nr], + data->pwm[nr]); + adm1031_write_value(client, ADM1031_REG_AUTO_TEMP(nr), + data->temp_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define auto_temp_reg(offset) \ +static SENSOR_DEVICE_ATTR(auto_temp##offset##_off, S_IRUGO, \ + show_auto_temp_off, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(auto_temp##offset##_min, S_IRUGO | S_IWUSR, \ + show_auto_temp_min, set_auto_temp_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(auto_temp##offset##_max, S_IRUGO | S_IWUSR, \ + show_auto_temp_max, set_auto_temp_max, offset - 1) + +auto_temp_reg(1); +auto_temp_reg(2); +auto_temp_reg(3); + +/* pwm */ +static ssize_t show_pwm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct adm1031_data *data = adm1031_update_device(dev); + return sprintf(buf, "%d\n", PWM_FROM_REG(data->pwm[nr])); +} +static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1031_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = to_sensor_dev_attr(attr)->index; + long val; + int ret, reg; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&data->update_lock); + if ((data->conf1 & ADM1031_CONF1_AUTO_MODE) && + (((val>>4) & 0xf) != 5)) { + /* In automatic mode, the only PWM accepted is 33% */ + mutex_unlock(&data->update_lock); + return -EINVAL; + } + data->pwm[nr] = PWM_TO_REG(val); + reg = adm1031_read_value(client, ADM1031_REG_PWM); + adm1031_write_value(client, ADM1031_REG_PWM, + nr ? ((data->pwm[nr] << 4) & 0xf0) | (reg & 0xf) + : (data->pwm[nr] & 0xf) | (reg & 0xf0)); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, show_pwm, set_pwm, 1); +static SENSOR_DEVICE_ATTR(auto_fan1_min_pwm, S_IRUGO | S_IWUSR, + show_pwm, set_pwm, 0); +static SENSOR_DEVICE_ATTR(auto_fan2_min_pwm, S_IRUGO | S_IWUSR, + show_pwm, set_pwm, 1); + +/* Fans */ + +/* + * That function checks the cases where the fan reading is not + * relevant. It is used to provide 0 as fan reading when the fan is + * not supposed to run + */ +static int trust_fan_readings(struct adm1031_data *data, int chan) +{ + int res = 0; + + if (data->conf1 & ADM1031_CONF1_AUTO_MODE) { + switch (data->conf1 & 0x60) { + case 0x00: + /* + * remote temp1 controls fan1, + * remote temp2 controls fan2 + */ + res = data->temp[chan+1] >= + AUTO_TEMP_MIN_FROM_REG_DEG(data->auto_temp[chan+1]); + break; + case 0x20: /* remote temp1 controls both fans */ + res = + data->temp[1] >= + AUTO_TEMP_MIN_FROM_REG_DEG(data->auto_temp[1]); + break; + case 0x40: /* remote temp2 controls both fans */ + res = + data->temp[2] >= + AUTO_TEMP_MIN_FROM_REG_DEG(data->auto_temp[2]); + break; + case 0x60: /* max controls both fans */ + res = + data->temp[0] >= + AUTO_TEMP_MIN_FROM_REG_DEG(data->auto_temp[0]) + || data->temp[1] >= + AUTO_TEMP_MIN_FROM_REG_DEG(data->auto_temp[1]) + || (data->chip_type == adm1031 + && data->temp[2] >= + AUTO_TEMP_MIN_FROM_REG_DEG(data->auto_temp[2])); + break; + } + } else { + res = data->pwm[chan] > 0; + } + return res; +} + + +static ssize_t show_fan(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct adm1031_data *data = adm1031_update_device(dev); + int value; + + value = trust_fan_readings(data, nr) ? FAN_FROM_REG(data->fan[nr], + FAN_DIV_FROM_REG(data->fan_div[nr])) : 0; + return sprintf(buf, "%d\n", value); +} + +static ssize_t show_fan_div(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct adm1031_data *data = adm1031_update_device(dev); + return sprintf(buf, "%d\n", FAN_DIV_FROM_REG(data->fan_div[nr])); +} +static ssize_t show_fan_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct adm1031_data *data = adm1031_update_device(dev); + return sprintf(buf, "%d\n", + FAN_FROM_REG(data->fan_min[nr], + FAN_DIV_FROM_REG(data->fan_div[nr]))); +} +static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1031_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = to_sensor_dev_attr(attr)->index; + long val; + int ret; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&data->update_lock); + if (val) { + data->fan_min[nr] = + FAN_TO_REG(val, FAN_DIV_FROM_REG(data->fan_div[nr])); + } else { + data->fan_min[nr] = 0xff; + } + adm1031_write_value(client, ADM1031_REG_FAN_MIN(nr), data->fan_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1031_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = to_sensor_dev_attr(attr)->index; + long val; + u8 tmp; + int old_div; + int new_min; + int ret; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + tmp = val == 8 ? 0xc0 : + val == 4 ? 0x80 : + val == 2 ? 0x40 : + val == 1 ? 0x00 : + 0xff; + if (tmp == 0xff) + return -EINVAL; + + mutex_lock(&data->update_lock); + /* Get fresh readings */ + data->fan_div[nr] = adm1031_read_value(client, + ADM1031_REG_FAN_DIV(nr)); + data->fan_min[nr] = adm1031_read_value(client, + ADM1031_REG_FAN_MIN(nr)); + + /* Write the new clock divider and fan min */ + old_div = FAN_DIV_FROM_REG(data->fan_div[nr]); + data->fan_div[nr] = tmp | (0x3f & data->fan_div[nr]); + new_min = data->fan_min[nr] * old_div / val; + data->fan_min[nr] = new_min > 0xff ? 0xff : new_min; + + adm1031_write_value(client, ADM1031_REG_FAN_DIV(nr), + data->fan_div[nr]); + adm1031_write_value(client, ADM1031_REG_FAN_MIN(nr), + data->fan_min[nr]); + + /* Invalidate the cache: fan speed is no longer valid */ + data->valid = 0; + mutex_unlock(&data->update_lock); + return count; +} + +#define fan_offset(offset) \ +static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \ + show_fan, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ + show_fan_min, set_fan_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR, \ + show_fan_div, set_fan_div, offset - 1) + +fan_offset(1); +fan_offset(2); + + +/* Temps */ +static ssize_t show_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct adm1031_data *data = adm1031_update_device(dev); + int ext; + ext = nr == 0 ? + ((data->ext_temp[nr] >> 6) & 0x3) * 2 : + (((data->ext_temp[nr] >> ((nr - 1) * 3)) & 7)); + return sprintf(buf, "%d\n", TEMP_FROM_REG_EXT(data->temp[nr], ext)); +} +static ssize_t show_temp_offset(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct adm1031_data *data = adm1031_update_device(dev); + return sprintf(buf, "%d\n", + TEMP_OFFSET_FROM_REG(data->temp_offset[nr])); +} +static ssize_t show_temp_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct adm1031_data *data = adm1031_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_min[nr])); +} +static ssize_t show_temp_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct adm1031_data *data = adm1031_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max[nr])); +} +static ssize_t show_temp_crit(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct adm1031_data *data = adm1031_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_crit[nr])); +} +static ssize_t set_temp_offset(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct adm1031_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = to_sensor_dev_attr(attr)->index; + long val; + int ret; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + val = clamp_val(val, -15000, 15000); + mutex_lock(&data->update_lock); + data->temp_offset[nr] = TEMP_OFFSET_TO_REG(val); + adm1031_write_value(client, ADM1031_REG_TEMP_OFFSET(nr), + data->temp_offset[nr]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t set_temp_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1031_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = to_sensor_dev_attr(attr)->index; + long val; + int ret; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + val = clamp_val(val, -55000, 127000); + mutex_lock(&data->update_lock); + data->temp_min[nr] = TEMP_TO_REG(val); + adm1031_write_value(client, ADM1031_REG_TEMP_MIN(nr), + data->temp_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1031_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = to_sensor_dev_attr(attr)->index; + long val; + int ret; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + val = clamp_val(val, -55000, 127000); + mutex_lock(&data->update_lock); + data->temp_max[nr] = TEMP_TO_REG(val); + adm1031_write_value(client, ADM1031_REG_TEMP_MAX(nr), + data->temp_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t set_temp_crit(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1031_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = to_sensor_dev_attr(attr)->index; + long val; + int ret; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + val = clamp_val(val, -55000, 127000); + mutex_lock(&data->update_lock); + data->temp_crit[nr] = TEMP_TO_REG(val); + adm1031_write_value(client, ADM1031_REG_TEMP_CRIT(nr), + data->temp_crit[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define temp_reg(offset) \ +static SENSOR_DEVICE_ATTR(temp##offset##_input, S_IRUGO, \ + show_temp, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_offset, S_IRUGO | S_IWUSR, \ + show_temp_offset, set_temp_offset, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_min, S_IRUGO | S_IWUSR, \ + show_temp_min, set_temp_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max, S_IRUGO | S_IWUSR, \ + show_temp_max, set_temp_max, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_crit, S_IRUGO | S_IWUSR, \ + show_temp_crit, set_temp_crit, offset - 1) + +temp_reg(1); +temp_reg(2); +temp_reg(3); + +/* Alarms */ +static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adm1031_data *data = adm1031_update_device(dev); + return sprintf(buf, "%d\n", data->alarm); +} + +static DEVICE_ATTR_RO(alarms); + +static ssize_t show_alarm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct adm1031_data *data = adm1031_update_device(dev); + return sprintf(buf, "%d\n", (data->alarm >> bitnr) & 1); +} + +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(temp3_crit_alarm, S_IRUGO, show_alarm, NULL, 12); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_alarm, NULL, 13); +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 14); + +/* Update Interval */ +static const unsigned int update_intervals[] = { + 16000, 8000, 4000, 2000, 1000, 500, 250, 125, +}; + +static ssize_t update_interval_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adm1031_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", data->update_interval); +} + +static ssize_t update_interval_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm1031_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int i, err; + u8 reg; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + /* + * Find the nearest update interval from the table. + * Use it to determine the matching update rate. + */ + for (i = 0; i < ARRAY_SIZE(update_intervals) - 1; i++) { + if (val >= update_intervals[i]) + break; + } + /* if not found, we point to the last entry (lowest update interval) */ + + /* set the new update rate while preserving other settings */ + reg = adm1031_read_value(client, ADM1031_REG_FAN_FILTER); + reg &= ~ADM1031_UPDATE_RATE_MASK; + reg |= i << ADM1031_UPDATE_RATE_SHIFT; + adm1031_write_value(client, ADM1031_REG_FAN_FILTER, reg); + + mutex_lock(&data->update_lock); + data->update_interval = update_intervals[i]; + mutex_unlock(&data->update_lock); + + return count; +} + +static DEVICE_ATTR_RW(update_interval); + +static struct attribute *adm1031_attributes[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_fault.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_auto_fan1_channel.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_offset.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_offset.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + + &sensor_dev_attr_auto_temp1_off.dev_attr.attr, + &sensor_dev_attr_auto_temp1_min.dev_attr.attr, + &sensor_dev_attr_auto_temp1_max.dev_attr.attr, + + &sensor_dev_attr_auto_temp2_off.dev_attr.attr, + &sensor_dev_attr_auto_temp2_min.dev_attr.attr, + &sensor_dev_attr_auto_temp2_max.dev_attr.attr, + + &sensor_dev_attr_auto_fan1_min_pwm.dev_attr.attr, + + &dev_attr_update_interval.attr, + &dev_attr_alarms.attr, + + NULL +}; + +static const struct attribute_group adm1031_group = { + .attrs = adm1031_attributes, +}; + +static struct attribute *adm1031_attributes_opt[] = { + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_fault.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_auto_fan2_channel.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_offset.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_auto_temp3_off.dev_attr.attr, + &sensor_dev_attr_auto_temp3_min.dev_attr.attr, + &sensor_dev_attr_auto_temp3_max.dev_attr.attr, + &sensor_dev_attr_auto_fan2_min_pwm.dev_attr.attr, + NULL +}; + +static const struct attribute_group adm1031_group_opt = { + .attrs = adm1031_attributes_opt, +}; + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int adm1031_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + const char *name; + int id, co; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + id = i2c_smbus_read_byte_data(client, 0x3d); + co = i2c_smbus_read_byte_data(client, 0x3e); + + if (!((id == 0x31 || id == 0x30) && co == 0x41)) + return -ENODEV; + name = (id == 0x30) ? "adm1030" : "adm1031"; + + strlcpy(info->type, name, I2C_NAME_SIZE); + + return 0; +} + +static void adm1031_init_client(struct i2c_client *client) +{ + unsigned int read_val; + unsigned int mask; + int i; + struct adm1031_data *data = i2c_get_clientdata(client); + + mask = (ADM1031_CONF2_PWM1_ENABLE | ADM1031_CONF2_TACH1_ENABLE); + if (data->chip_type == adm1031) { + mask |= (ADM1031_CONF2_PWM2_ENABLE | + ADM1031_CONF2_TACH2_ENABLE); + } + /* Initialize the ADM1031 chip (enables fan speed reading ) */ + read_val = adm1031_read_value(client, ADM1031_REG_CONF2); + if ((read_val | mask) != read_val) + adm1031_write_value(client, ADM1031_REG_CONF2, read_val | mask); + + read_val = adm1031_read_value(client, ADM1031_REG_CONF1); + if ((read_val | ADM1031_CONF1_MONITOR_ENABLE) != read_val) { + adm1031_write_value(client, ADM1031_REG_CONF1, + read_val | ADM1031_CONF1_MONITOR_ENABLE); + } + + /* Read the chip's update rate */ + mask = ADM1031_UPDATE_RATE_MASK; + read_val = adm1031_read_value(client, ADM1031_REG_FAN_FILTER); + i = (read_val & mask) >> ADM1031_UPDATE_RATE_SHIFT; + /* Save it as update interval */ + data->update_interval = update_intervals[i]; +} + +static int adm1031_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct adm1031_data *data; + + data = devm_kzalloc(dev, sizeof(struct adm1031_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->client = client; + data->chip_type = id->driver_data; + mutex_init(&data->update_lock); + + if (data->chip_type == adm1030) + data->chan_select_table = &auto_channel_select_table_adm1030; + else + data->chan_select_table = &auto_channel_select_table_adm1031; + + /* Initialize the ADM1031 chip */ + adm1031_init_client(client); + + /* sysfs hooks */ + data->groups[0] = &adm1031_group; + if (data->chip_type == adm1031) + data->groups[1] = &adm1031_group_opt; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id adm1031_id[] = { + { "adm1030", adm1030 }, + { "adm1031", adm1031 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adm1031_id); + +static struct i2c_driver adm1031_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "adm1031", + }, + .probe = adm1031_probe, + .id_table = adm1031_id, + .detect = adm1031_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(adm1031_driver); + +MODULE_AUTHOR("Alexandre d'Alton "); +MODULE_DESCRIPTION("ADM1031/ADM1030 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/adm9240.c b/drivers/hwmon/adm9240.c new file mode 100644 index 000000000..255413fdb --- /dev/null +++ b/drivers/hwmon/adm9240.c @@ -0,0 +1,773 @@ +/* + * adm9240.c Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * + * Copyright (C) 1999 Frodo Looijaard + * Philip Edelbrock + * Copyright (C) 2003 Michiel Rook + * Copyright (C) 2005 Grant Coady with valuable + * guidance from Jean Delvare + * + * Driver supports Analog Devices ADM9240 + * Dallas Semiconductor DS1780 + * National Semiconductor LM81 + * + * ADM9240 is the reference, DS1780 and LM81 are register compatibles + * + * Voltage Six inputs are scaled by chip, VID also reported + * Temperature Chip temperature to 0.5'C, maximum and max_hysteris + * Fans 2 fans, low speed alarm, automatic fan clock divider + * Alarms 16-bit map of active alarms + * Analog Out 0..1250 mV output + * + * Chassis Intrusion: clear CI latch with 'echo 0 > intrusion0_alarm' + * + * Test hardware: Intel SE440BX-2 desktop motherboard --Grant + * + * LM81 extended temp reading not implemented + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f, + I2C_CLIENT_END }; + +enum chips { adm9240, ds1780, lm81 }; + +/* ADM9240 registers */ +#define ADM9240_REG_MAN_ID 0x3e +#define ADM9240_REG_DIE_REV 0x3f +#define ADM9240_REG_CONFIG 0x40 + +#define ADM9240_REG_IN(nr) (0x20 + (nr)) /* 0..5 */ +#define ADM9240_REG_IN_MAX(nr) (0x2b + (nr) * 2) +#define ADM9240_REG_IN_MIN(nr) (0x2c + (nr) * 2) +#define ADM9240_REG_FAN(nr) (0x28 + (nr)) /* 0..1 */ +#define ADM9240_REG_FAN_MIN(nr) (0x3b + (nr)) +#define ADM9240_REG_INT(nr) (0x41 + (nr)) +#define ADM9240_REG_INT_MASK(nr) (0x43 + (nr)) +#define ADM9240_REG_TEMP 0x27 +#define ADM9240_REG_TEMP_MAX(nr) (0x39 + (nr)) /* 0, 1 = high, hyst */ +#define ADM9240_REG_ANALOG_OUT 0x19 +#define ADM9240_REG_CHASSIS_CLEAR 0x46 +#define ADM9240_REG_VID_FAN_DIV 0x47 +#define ADM9240_REG_I2C_ADDR 0x48 +#define ADM9240_REG_VID4 0x49 +#define ADM9240_REG_TEMP_CONF 0x4b + +/* generalised scaling with integer rounding */ +static inline int SCALE(long val, int mul, int div) +{ + if (val < 0) + return (val * mul - div / 2) / div; + else + return (val * mul + div / 2) / div; +} + +/* adm9240 internally scales voltage measurements */ +static const u16 nom_mv[] = { 2500, 2700, 3300, 5000, 12000, 2700 }; + +static inline unsigned int IN_FROM_REG(u8 reg, int n) +{ + return SCALE(reg, nom_mv[n], 192); +} + +static inline u8 IN_TO_REG(unsigned long val, int n) +{ + val = clamp_val(val, 0, nom_mv[n] * 255 / 192); + return SCALE(val, 192, nom_mv[n]); +} + +/* temperature range: -40..125, 127 disables temperature alarm */ +static inline s8 TEMP_TO_REG(long val) +{ + val = clamp_val(val, -40000, 127000); + return SCALE(val, 1, 1000); +} + +/* two fans, each with low fan speed limit */ +static inline unsigned int FAN_FROM_REG(u8 reg, u8 div) +{ + if (!reg) /* error */ + return -1; + + if (reg == 255) + return 0; + + return SCALE(1350000, 1, reg * div); +} + +/* analog out 0..1250mV */ +static inline u8 AOUT_TO_REG(unsigned long val) +{ + val = clamp_val(val, 0, 1250); + return SCALE(val, 255, 1250); +} + +static inline unsigned int AOUT_FROM_REG(u8 reg) +{ + return SCALE(reg, 1250, 255); +} + +/* per client data */ +struct adm9240_data { + struct i2c_client *client; + struct mutex update_lock; + char valid; + unsigned long last_updated_measure; + unsigned long last_updated_config; + + u8 in[6]; /* ro in0_input */ + u8 in_max[6]; /* rw in0_max */ + u8 in_min[6]; /* rw in0_min */ + u8 fan[2]; /* ro fan1_input */ + u8 fan_min[2]; /* rw fan1_min */ + u8 fan_div[2]; /* rw fan1_div, read-only accessor */ + s16 temp; /* ro temp1_input, 9-bit sign-extended */ + s8 temp_max[2]; /* rw 0 -> temp_max, 1 -> temp_max_hyst */ + u16 alarms; /* ro alarms */ + u8 aout; /* rw aout_output */ + u8 vid; /* ro vid */ + u8 vrm; /* -- vrm set on startup, no accessor */ +}; + +/* write new fan div, callers must hold data->update_lock */ +static void adm9240_write_fan_div(struct i2c_client *client, int nr, + u8 fan_div) +{ + u8 reg, old, shift = (nr + 2) * 2; + + reg = i2c_smbus_read_byte_data(client, ADM9240_REG_VID_FAN_DIV); + old = (reg >> shift) & 3; + reg &= ~(3 << shift); + reg |= (fan_div << shift); + i2c_smbus_write_byte_data(client, ADM9240_REG_VID_FAN_DIV, reg); + dev_dbg(&client->dev, + "fan%d clock divider changed from %u to %u\n", + nr + 1, 1 << old, 1 << fan_div); +} + +static struct adm9240_data *adm9240_update_device(struct device *dev) +{ + struct adm9240_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int i; + + mutex_lock(&data->update_lock); + + /* minimum measurement cycle: 1.75 seconds */ + if (time_after(jiffies, data->last_updated_measure + (HZ * 7 / 4)) + || !data->valid) { + + for (i = 0; i < 6; i++) { /* read voltages */ + data->in[i] = i2c_smbus_read_byte_data(client, + ADM9240_REG_IN(i)); + } + data->alarms = i2c_smbus_read_byte_data(client, + ADM9240_REG_INT(0)) | + i2c_smbus_read_byte_data(client, + ADM9240_REG_INT(1)) << 8; + + /* + * read temperature: assume temperature changes less than + * 0.5'C per two measurement cycles thus ignore possible + * but unlikely aliasing error on lsb reading. --Grant + */ + data->temp = (i2c_smbus_read_byte_data(client, + ADM9240_REG_TEMP) << 8) | + i2c_smbus_read_byte_data(client, + ADM9240_REG_TEMP_CONF); + + for (i = 0; i < 2; i++) { /* read fans */ + data->fan[i] = i2c_smbus_read_byte_data(client, + ADM9240_REG_FAN(i)); + + /* adjust fan clock divider on overflow */ + if (data->valid && data->fan[i] == 255 && + data->fan_div[i] < 3) { + + adm9240_write_fan_div(client, i, + ++data->fan_div[i]); + + /* adjust fan_min if active, but not to 0 */ + if (data->fan_min[i] < 255 && + data->fan_min[i] >= 2) + data->fan_min[i] /= 2; + } + } + data->last_updated_measure = jiffies; + } + + /* minimum config reading cycle: 300 seconds */ + if (time_after(jiffies, data->last_updated_config + (HZ * 300)) + || !data->valid) { + + for (i = 0; i < 6; i++) { + data->in_min[i] = i2c_smbus_read_byte_data(client, + ADM9240_REG_IN_MIN(i)); + data->in_max[i] = i2c_smbus_read_byte_data(client, + ADM9240_REG_IN_MAX(i)); + } + for (i = 0; i < 2; i++) { + data->fan_min[i] = i2c_smbus_read_byte_data(client, + ADM9240_REG_FAN_MIN(i)); + } + data->temp_max[0] = i2c_smbus_read_byte_data(client, + ADM9240_REG_TEMP_MAX(0)); + data->temp_max[1] = i2c_smbus_read_byte_data(client, + ADM9240_REG_TEMP_MAX(1)); + + /* read fan divs and 5-bit VID */ + i = i2c_smbus_read_byte_data(client, ADM9240_REG_VID_FAN_DIV); + data->fan_div[0] = (i >> 4) & 3; + data->fan_div[1] = (i >> 6) & 3; + data->vid = i & 0x0f; + data->vid |= (i2c_smbus_read_byte_data(client, + ADM9240_REG_VID4) & 1) << 4; + /* read analog out */ + data->aout = i2c_smbus_read_byte_data(client, + ADM9240_REG_ANALOG_OUT); + + data->last_updated_config = jiffies; + data->valid = 1; + } + mutex_unlock(&data->update_lock); + return data; +} + +/*** sysfs accessors ***/ + +/* temperature */ +static ssize_t temp1_input_show(struct device *dev, + struct device_attribute *dummy, char *buf) +{ + struct adm9240_data *data = adm9240_update_device(dev); + return sprintf(buf, "%d\n", data->temp / 128 * 500); /* 9-bit value */ +} + +static ssize_t show_max(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adm9240_data *data = adm9240_update_device(dev); + return sprintf(buf, "%d\n", data->temp_max[attr->index] * 1000); +} + +static ssize_t set_max(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adm9240_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_max[attr->index] = TEMP_TO_REG(val); + i2c_smbus_write_byte_data(client, ADM9240_REG_TEMP_MAX(attr->index), + data->temp_max[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RO(temp1_input); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, + show_max, set_max, 0); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, + show_max, set_max, 1); + +/* voltage */ +static ssize_t show_in(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adm9240_data *data = adm9240_update_device(dev); + return sprintf(buf, "%d\n", IN_FROM_REG(data->in[attr->index], + attr->index)); +} + +static ssize_t show_in_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adm9240_data *data = adm9240_update_device(dev); + return sprintf(buf, "%d\n", IN_FROM_REG(data->in_min[attr->index], + attr->index)); +} + +static ssize_t show_in_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adm9240_data *data = adm9240_update_device(dev); + return sprintf(buf, "%d\n", IN_FROM_REG(data->in_max[attr->index], + attr->index)); +} + +static ssize_t set_in_min(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adm9240_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_min[attr->index] = IN_TO_REG(val, attr->index); + i2c_smbus_write_byte_data(client, ADM9240_REG_IN_MIN(attr->index), + data->in_min[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_in_max(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adm9240_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_max[attr->index] = IN_TO_REG(val, attr->index); + i2c_smbus_write_byte_data(client, ADM9240_REG_IN_MAX(attr->index), + data->in_max[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +#define vin(nr) \ +static SENSOR_DEVICE_ATTR(in##nr##_input, S_IRUGO, \ + show_in, NULL, nr); \ +static SENSOR_DEVICE_ATTR(in##nr##_min, S_IRUGO | S_IWUSR, \ + show_in_min, set_in_min, nr); \ +static SENSOR_DEVICE_ATTR(in##nr##_max, S_IRUGO | S_IWUSR, \ + show_in_max, set_in_max, nr); + +vin(0); +vin(1); +vin(2); +vin(3); +vin(4); +vin(5); + +/* fans */ +static ssize_t show_fan(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adm9240_data *data = adm9240_update_device(dev); + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[attr->index], + 1 << data->fan_div[attr->index])); +} + +static ssize_t show_fan_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adm9240_data *data = adm9240_update_device(dev); + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[attr->index], + 1 << data->fan_div[attr->index])); +} + +static ssize_t show_fan_div(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adm9240_data *data = adm9240_update_device(dev); + return sprintf(buf, "%d\n", 1 << data->fan_div[attr->index]); +} + +/* + * set fan speed low limit: + * + * - value is zero: disable fan speed low limit alarm + * + * - value is below fan speed measurement range: enable fan speed low + * limit alarm to be asserted while fan speed too slow to measure + * + * - otherwise: select fan clock divider to suit fan speed low limit, + * measurement code may adjust registers to ensure fan speed reading + */ +static ssize_t set_fan_min(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adm9240_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = attr->index; + u8 new_div; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + if (!val) { + data->fan_min[nr] = 255; + new_div = data->fan_div[nr]; + + dev_dbg(&client->dev, "fan%u low limit set disabled\n", + nr + 1); + + } else if (val < 1350000 / (8 * 254)) { + new_div = 3; + data->fan_min[nr] = 254; + + dev_dbg(&client->dev, "fan%u low limit set minimum %u\n", + nr + 1, FAN_FROM_REG(254, 1 << new_div)); + + } else { + unsigned int new_min = 1350000 / val; + + new_div = 0; + while (new_min > 192 && new_div < 3) { + new_div++; + new_min /= 2; + } + if (!new_min) /* keep > 0 */ + new_min++; + + data->fan_min[nr] = new_min; + + dev_dbg(&client->dev, "fan%u low limit set fan speed %u\n", + nr + 1, FAN_FROM_REG(new_min, 1 << new_div)); + } + + if (new_div != data->fan_div[nr]) { + data->fan_div[nr] = new_div; + adm9240_write_fan_div(client, nr, new_div); + } + i2c_smbus_write_byte_data(client, ADM9240_REG_FAN_MIN(nr), + data->fan_min[nr]); + + mutex_unlock(&data->update_lock); + return count; +} + +#define fan(nr) \ +static SENSOR_DEVICE_ATTR(fan##nr##_input, S_IRUGO, \ + show_fan, NULL, nr - 1); \ +static SENSOR_DEVICE_ATTR(fan##nr##_div, S_IRUGO, \ + show_fan_div, NULL, nr - 1); \ +static SENSOR_DEVICE_ATTR(fan##nr##_min, S_IRUGO | S_IWUSR, \ + show_fan_min, set_fan_min, nr - 1); + +fan(1); +fan(2); + +/* alarms */ +static ssize_t alarms_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adm9240_data *data = adm9240_update_device(dev); + return sprintf(buf, "%u\n", data->alarms); +} +static DEVICE_ATTR_RO(alarms); + +static ssize_t show_alarm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct adm9240_data *data = adm9240_update_device(dev); + return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1); +} +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 7); + +/* vid */ +static ssize_t cpu0_vid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adm9240_data *data = adm9240_update_device(dev); + return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); +} +static DEVICE_ATTR_RO(cpu0_vid); + +/* analog output */ +static ssize_t aout_output_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adm9240_data *data = adm9240_update_device(dev); + return sprintf(buf, "%d\n", AOUT_FROM_REG(data->aout)); +} + +static ssize_t aout_output_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm9240_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->aout = AOUT_TO_REG(val); + i2c_smbus_write_byte_data(client, ADM9240_REG_ANALOG_OUT, data->aout); + mutex_unlock(&data->update_lock); + return count; +} +static DEVICE_ATTR_RW(aout_output); + +static ssize_t chassis_clear(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adm9240_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + + if (kstrtoul(buf, 10, &val) || val != 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + i2c_smbus_write_byte_data(client, ADM9240_REG_CHASSIS_CLEAR, 0x80); + data->valid = 0; /* Force cache refresh */ + mutex_unlock(&data->update_lock); + dev_dbg(&client->dev, "chassis intrusion latch cleared\n"); + + return count; +} +static SENSOR_DEVICE_ATTR(intrusion0_alarm, S_IRUGO | S_IWUSR, show_alarm, + chassis_clear, 12); + +static struct attribute *adm9240_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + &dev_attr_temp1_input.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &dev_attr_alarms.attr, + &dev_attr_aout_output.attr, + &sensor_dev_attr_intrusion0_alarm.dev_attr.attr, + &dev_attr_cpu0_vid.attr, + NULL +}; + +ATTRIBUTE_GROUPS(adm9240); + + +/*** sensor chip detect and driver install ***/ + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int adm9240_detect(struct i2c_client *new_client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = new_client->adapter; + const char *name = ""; + int address = new_client->addr; + u8 man_id, die_rev; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* verify chip: reg address should match i2c address */ + if (i2c_smbus_read_byte_data(new_client, ADM9240_REG_I2C_ADDR) + != address) { + dev_err(&adapter->dev, "detect fail: address match, 0x%02x\n", + address); + return -ENODEV; + } + + /* check known chip manufacturer */ + man_id = i2c_smbus_read_byte_data(new_client, ADM9240_REG_MAN_ID); + if (man_id == 0x23) { + name = "adm9240"; + } else if (man_id == 0xda) { + name = "ds1780"; + } else if (man_id == 0x01) { + name = "lm81"; + } else { + dev_err(&adapter->dev, "detect fail: unknown manuf, 0x%02x\n", + man_id); + return -ENODEV; + } + + /* successful detect, print chip info */ + die_rev = i2c_smbus_read_byte_data(new_client, ADM9240_REG_DIE_REV); + dev_info(&adapter->dev, "found %s revision %u\n", + man_id == 0x23 ? "ADM9240" : + man_id == 0xda ? "DS1780" : "LM81", die_rev); + + strlcpy(info->type, name, I2C_NAME_SIZE); + + return 0; +} + +static void adm9240_init_client(struct i2c_client *client) +{ + struct adm9240_data *data = i2c_get_clientdata(client); + u8 conf = i2c_smbus_read_byte_data(client, ADM9240_REG_CONFIG); + u8 mode = i2c_smbus_read_byte_data(client, ADM9240_REG_TEMP_CONF) & 3; + + data->vrm = vid_which_vrm(); /* need this to report vid as mV */ + + dev_info(&client->dev, "Using VRM: %d.%d\n", data->vrm / 10, + data->vrm % 10); + + if (conf & 1) { /* measurement cycle running: report state */ + + dev_info(&client->dev, "status: config 0x%02x mode %u\n", + conf, mode); + + } else { /* cold start: open limits before starting chip */ + int i; + + for (i = 0; i < 6; i++) { + i2c_smbus_write_byte_data(client, + ADM9240_REG_IN_MIN(i), 0); + i2c_smbus_write_byte_data(client, + ADM9240_REG_IN_MAX(i), 255); + } + i2c_smbus_write_byte_data(client, + ADM9240_REG_FAN_MIN(0), 255); + i2c_smbus_write_byte_data(client, + ADM9240_REG_FAN_MIN(1), 255); + i2c_smbus_write_byte_data(client, + ADM9240_REG_TEMP_MAX(0), 127); + i2c_smbus_write_byte_data(client, + ADM9240_REG_TEMP_MAX(1), 127); + + /* start measurement cycle */ + i2c_smbus_write_byte_data(client, ADM9240_REG_CONFIG, 1); + + dev_info(&client->dev, + "cold start: config was 0x%02x mode %u\n", conf, mode); + } +} + +static int adm9240_probe(struct i2c_client *new_client, + const struct i2c_device_id *id) +{ + struct device *dev = &new_client->dev; + struct device *hwmon_dev; + struct adm9240_data *data; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(new_client, data); + data->client = new_client; + mutex_init(&data->update_lock); + + adm9240_init_client(new_client); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + new_client->name, + data, + adm9240_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id adm9240_id[] = { + { "adm9240", adm9240 }, + { "ds1780", ds1780 }, + { "lm81", lm81 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adm9240_id); + +static struct i2c_driver adm9240_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "adm9240", + }, + .probe = adm9240_probe, + .id_table = adm9240_id, + .detect = adm9240_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(adm9240_driver); + +MODULE_AUTHOR("Michiel Rook , " + "Grant Coady and others"); +MODULE_DESCRIPTION("ADM9240/DS1780/LM81 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ads1015.c b/drivers/hwmon/ads1015.c new file mode 100644 index 000000000..98c704d33 --- /dev/null +++ b/drivers/hwmon/ads1015.c @@ -0,0 +1,337 @@ +/* + * ads1015.c - lm_sensors driver for ads1015 12-bit 4-input ADC + * (C) Copyright 2010 + * Dirk Eibach, Guntermann & Drunck GmbH + * + * Based on the ads7828 driver by Steve Hardy. + * + * Datasheet available at: http://focus.ti.com/lit/ds/symlink/ads1015.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* ADS1015 registers */ +enum { + ADS1015_CONVERSION = 0, + ADS1015_CONFIG = 1, +}; + +/* PGA fullscale voltages in mV */ +static const unsigned int fullscale_table[8] = { + 6144, 4096, 2048, 1024, 512, 256, 256, 256 }; + +/* Data rates in samples per second */ +static const unsigned int data_rate_table_1015[8] = { + 128, 250, 490, 920, 1600, 2400, 3300, 3300 +}; + +static const unsigned int data_rate_table_1115[8] = { + 8, 16, 32, 64, 128, 250, 475, 860 +}; + +#define ADS1015_DEFAULT_CHANNELS 0xff +#define ADS1015_DEFAULT_PGA 2 +#define ADS1015_DEFAULT_DATA_RATE 4 + +enum ads1015_chips { + ads1015, + ads1115, +}; + +struct ads1015_data { + struct device *hwmon_dev; + struct mutex update_lock; /* mutex protect updates */ + struct ads1015_channel_data channel_data[ADS1015_CHANNELS]; + enum ads1015_chips id; +}; + +static int ads1015_read_adc(struct i2c_client *client, unsigned int channel) +{ + u16 config; + struct ads1015_data *data = i2c_get_clientdata(client); + unsigned int pga = data->channel_data[channel].pga; + unsigned int data_rate = data->channel_data[channel].data_rate; + unsigned int conversion_time_ms; + const unsigned int * const rate_table = data->id == ads1115 ? + data_rate_table_1115 : data_rate_table_1015; + int res; + + mutex_lock(&data->update_lock); + + /* get channel parameters */ + res = i2c_smbus_read_word_swapped(client, ADS1015_CONFIG); + if (res < 0) + goto err_unlock; + config = res; + conversion_time_ms = DIV_ROUND_UP(1000, rate_table[data_rate]); + + /* setup and start single conversion */ + config &= 0x001f; + config |= (1 << 15) | (1 << 8); + config |= (channel & 0x0007) << 12; + config |= (pga & 0x0007) << 9; + config |= (data_rate & 0x0007) << 5; + + res = i2c_smbus_write_word_swapped(client, ADS1015_CONFIG, config); + if (res < 0) + goto err_unlock; + + /* wait until conversion finished */ + msleep(conversion_time_ms); + res = i2c_smbus_read_word_swapped(client, ADS1015_CONFIG); + if (res < 0) + goto err_unlock; + config = res; + if (!(config & (1 << 15))) { + /* conversion not finished in time */ + res = -EIO; + goto err_unlock; + } + + res = i2c_smbus_read_word_swapped(client, ADS1015_CONVERSION); + +err_unlock: + mutex_unlock(&data->update_lock); + return res; +} + +static int ads1015_reg_to_mv(struct i2c_client *client, unsigned int channel, + s16 reg) +{ + struct ads1015_data *data = i2c_get_clientdata(client); + unsigned int pga = data->channel_data[channel].pga; + int fullscale = fullscale_table[pga]; + const int mask = data->id == ads1115 ? 0x7fff : 0x7ff0; + + return DIV_ROUND_CLOSEST(reg * fullscale, mask); +} + +/* sysfs callback function */ +static ssize_t show_in(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct i2c_client *client = to_i2c_client(dev); + int res; + int index = attr->index; + + res = ads1015_read_adc(client, index); + if (res < 0) + return res; + + return sprintf(buf, "%d\n", ads1015_reg_to_mv(client, index, res)); +} + +static const struct sensor_device_attribute ads1015_in[] = { + SENSOR_ATTR(in0_input, S_IRUGO, show_in, NULL, 0), + SENSOR_ATTR(in1_input, S_IRUGO, show_in, NULL, 1), + SENSOR_ATTR(in2_input, S_IRUGO, show_in, NULL, 2), + SENSOR_ATTR(in3_input, S_IRUGO, show_in, NULL, 3), + SENSOR_ATTR(in4_input, S_IRUGO, show_in, NULL, 4), + SENSOR_ATTR(in5_input, S_IRUGO, show_in, NULL, 5), + SENSOR_ATTR(in6_input, S_IRUGO, show_in, NULL, 6), + SENSOR_ATTR(in7_input, S_IRUGO, show_in, NULL, 7), +}; + +/* + * Driver interface + */ + +static int ads1015_remove(struct i2c_client *client) +{ + struct ads1015_data *data = i2c_get_clientdata(client); + int k; + + hwmon_device_unregister(data->hwmon_dev); + for (k = 0; k < ADS1015_CHANNELS; ++k) + device_remove_file(&client->dev, &ads1015_in[k].dev_attr); + return 0; +} + +#ifdef CONFIG_OF +static int ads1015_get_channels_config_of(struct i2c_client *client) +{ + struct ads1015_data *data = i2c_get_clientdata(client); + struct device_node *node; + + if (!client->dev.of_node + || !of_get_next_child(client->dev.of_node, NULL)) + return -EINVAL; + + for_each_child_of_node(client->dev.of_node, node) { + u32 pval; + unsigned int channel; + unsigned int pga = ADS1015_DEFAULT_PGA; + unsigned int data_rate = ADS1015_DEFAULT_DATA_RATE; + + if (of_property_read_u32(node, "reg", &pval)) { + dev_err(&client->dev, "invalid reg on %pOF\n", node); + continue; + } + + channel = pval; + if (channel >= ADS1015_CHANNELS) { + dev_err(&client->dev, + "invalid channel index %d on %pOF\n", + channel, node); + continue; + } + + if (!of_property_read_u32(node, "ti,gain", &pval)) { + pga = pval; + if (pga > 6) { + dev_err(&client->dev, "invalid gain on %pOF\n", + node); + return -EINVAL; + } + } + + if (!of_property_read_u32(node, "ti,datarate", &pval)) { + data_rate = pval; + if (data_rate > 7) { + dev_err(&client->dev, + "invalid data_rate on %pOF\n", node); + return -EINVAL; + } + } + + data->channel_data[channel].enabled = true; + data->channel_data[channel].pga = pga; + data->channel_data[channel].data_rate = data_rate; + } + + return 0; +} +#endif + +static void ads1015_get_channels_config(struct i2c_client *client) +{ + unsigned int k; + struct ads1015_data *data = i2c_get_clientdata(client); + struct ads1015_platform_data *pdata = dev_get_platdata(&client->dev); + + /* prefer platform data */ + if (pdata) { + memcpy(data->channel_data, pdata->channel_data, + sizeof(data->channel_data)); + return; + } + +#ifdef CONFIG_OF + if (!ads1015_get_channels_config_of(client)) + return; +#endif + + /* fallback on default configuration */ + for (k = 0; k < ADS1015_CHANNELS; ++k) { + data->channel_data[k].enabled = true; + data->channel_data[k].pga = ADS1015_DEFAULT_PGA; + data->channel_data[k].data_rate = ADS1015_DEFAULT_DATA_RATE; + } +} + +static int ads1015_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ads1015_data *data; + int err; + unsigned int k; + + data = devm_kzalloc(&client->dev, sizeof(struct ads1015_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (client->dev.of_node) + data->id = (enum ads1015_chips) + of_device_get_match_data(&client->dev); + else + data->id = id->driver_data; + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* build sysfs attribute group */ + ads1015_get_channels_config(client); + for (k = 0; k < ADS1015_CHANNELS; ++k) { + if (!data->channel_data[k].enabled) + continue; + err = device_create_file(&client->dev, &ads1015_in[k].dev_attr); + if (err) + goto exit_remove; + } + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + for (k = 0; k < ADS1015_CHANNELS; ++k) + device_remove_file(&client->dev, &ads1015_in[k].dev_attr); + return err; +} + +static const struct i2c_device_id ads1015_id[] = { + { "ads1015", ads1015}, + { "ads1115", ads1115}, + { } +}; +MODULE_DEVICE_TABLE(i2c, ads1015_id); + +static const struct of_device_id ads1015_of_match[] = { + { + .compatible = "ti,ads1015", + .data = (void *)ads1015 + }, + { + .compatible = "ti,ads1115", + .data = (void *)ads1115 + }, + { }, +}; +MODULE_DEVICE_TABLE(of, ads1015_of_match); + +static struct i2c_driver ads1015_driver = { + .driver = { + .name = "ads1015", + .of_match_table = of_match_ptr(ads1015_of_match), + }, + .probe = ads1015_probe, + .remove = ads1015_remove, + .id_table = ads1015_id, +}; + +module_i2c_driver(ads1015_driver); + +MODULE_AUTHOR("Dirk Eibach "); +MODULE_DESCRIPTION("ADS1015 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ads7828.c b/drivers/hwmon/ads7828.c new file mode 100644 index 000000000..898607bf6 --- /dev/null +++ b/drivers/hwmon/ads7828.c @@ -0,0 +1,230 @@ +/* + * ads7828.c - driver for TI ADS7828 8-channel A/D converter and compatibles + * (C) 2007 EADS Astrium + * + * This driver is based on the lm75 and other lm_sensors/hwmon drivers + * + * Written by Steve Hardy + * + * ADS7830 support, by Guillaume Roguez + * + * For further information, see the Documentation/hwmon/ads7828 file. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* The ADS7828 registers */ +#define ADS7828_CMD_SD_SE 0x80 /* Single ended inputs */ +#define ADS7828_CMD_PD1 0x04 /* Internal vref OFF && A/D ON */ +#define ADS7828_CMD_PD3 0x0C /* Internal vref ON && A/D ON */ +#define ADS7828_INT_VREF_MV 2500 /* Internal vref is 2.5V, 2500mV */ +#define ADS7828_EXT_VREF_MV_MIN 50 /* External vref min value 0.05V */ +#define ADS7828_EXT_VREF_MV_MAX 5250 /* External vref max value 5.25V */ + +/* List of supported devices */ +enum ads7828_chips { ads7828, ads7830 }; + +/* Client specific data */ +struct ads7828_data { + struct regmap *regmap; + u8 cmd_byte; /* Command byte without channel bits */ + unsigned int lsb_resol; /* Resolution of the ADC sample LSB */ +}; + +/* Command byte C2,C1,C0 - see datasheet */ +static inline u8 ads7828_cmd_byte(u8 cmd, int ch) +{ + return cmd | (((ch >> 1) | (ch & 0x01) << 2) << 4); +} + +/* sysfs callback function */ +static ssize_t ads7828_show_in(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ads7828_data *data = dev_get_drvdata(dev); + u8 cmd = ads7828_cmd_byte(data->cmd_byte, attr->index); + unsigned int regval; + int err; + + err = regmap_read(data->regmap, cmd, ®val); + if (err < 0) + return err; + + return sprintf(buf, "%d\n", + DIV_ROUND_CLOSEST(regval * data->lsb_resol, 1000)); +} + +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ads7828_show_in, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ads7828_show_in, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, ads7828_show_in, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, ads7828_show_in, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, ads7828_show_in, NULL, 4); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, ads7828_show_in, NULL, 5); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, ads7828_show_in, NULL, 6); +static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, ads7828_show_in, NULL, 7); + +static struct attribute *ads7828_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(ads7828); + +static const struct regmap_config ads2828_regmap_config = { + .reg_bits = 8, + .val_bits = 16, +}; + +static const struct regmap_config ads2830_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int ads7828_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct ads7828_platform_data *pdata = dev_get_platdata(dev); + struct ads7828_data *data; + struct device *hwmon_dev; + unsigned int vref_mv = ADS7828_INT_VREF_MV; + unsigned int vref_uv; + bool diff_input = false; + bool ext_vref = false; + unsigned int regval; + enum ads7828_chips chip; + struct regulator *reg; + + data = devm_kzalloc(dev, sizeof(struct ads7828_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (pdata) { + diff_input = pdata->diff_input; + ext_vref = pdata->ext_vref; + if (ext_vref && pdata->vref_mv) + vref_mv = pdata->vref_mv; + } else if (dev->of_node) { + diff_input = of_property_read_bool(dev->of_node, + "ti,differential-input"); + reg = devm_regulator_get_optional(dev, "vref"); + if (!IS_ERR(reg)) { + vref_uv = regulator_get_voltage(reg); + vref_mv = DIV_ROUND_CLOSEST(vref_uv, 1000); + if (vref_mv < ADS7828_EXT_VREF_MV_MIN || + vref_mv > ADS7828_EXT_VREF_MV_MAX) + return -EINVAL; + ext_vref = true; + } + } + + if (client->dev.of_node) + chip = (enum ads7828_chips) + of_device_get_match_data(&client->dev); + else + chip = id->driver_data; + + /* Bound Vref with min/max values */ + vref_mv = clamp_val(vref_mv, ADS7828_EXT_VREF_MV_MIN, + ADS7828_EXT_VREF_MV_MAX); + + /* ADS7828 uses 12-bit samples, while ADS7830 is 8-bit */ + if (chip == ads7828) { + data->lsb_resol = DIV_ROUND_CLOSEST(vref_mv * 1000, 4096); + data->regmap = devm_regmap_init_i2c(client, + &ads2828_regmap_config); + } else { + data->lsb_resol = DIV_ROUND_CLOSEST(vref_mv * 1000, 256); + data->regmap = devm_regmap_init_i2c(client, + &ads2830_regmap_config); + } + + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + data->cmd_byte = ext_vref ? ADS7828_CMD_PD1 : ADS7828_CMD_PD3; + if (!diff_input) + data->cmd_byte |= ADS7828_CMD_SD_SE; + + /* + * Datasheet specifies internal reference voltage is disabled by + * default. The internal reference voltage needs to be enabled and + * voltage needs to settle before getting valid ADC data. So perform a + * dummy read to enable the internal reference voltage. + */ + if (!ext_vref) + regmap_read(data->regmap, data->cmd_byte, ®val); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + ads7828_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id ads7828_device_ids[] = { + { "ads7828", ads7828 }, + { "ads7830", ads7830 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ads7828_device_ids); + +static const struct of_device_id ads7828_of_match[] = { + { + .compatible = "ti,ads7828", + .data = (void *)ads7828 + }, + { + .compatible = "ti,ads7830", + .data = (void *)ads7830 + }, + { }, +}; +MODULE_DEVICE_TABLE(of, ads7828_of_match); + +static struct i2c_driver ads7828_driver = { + .driver = { + .name = "ads7828", + .of_match_table = of_match_ptr(ads7828_of_match), + }, + + .id_table = ads7828_device_ids, + .probe = ads7828_probe, +}; + +module_i2c_driver(ads7828_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Steve Hardy "); +MODULE_DESCRIPTION("Driver for TI ADS7828 A/D converter and compatibles"); diff --git a/drivers/hwmon/ads7871.c b/drivers/hwmon/ads7871.c new file mode 100644 index 000000000..59bd7b9e1 --- /dev/null +++ b/drivers/hwmon/ads7871.c @@ -0,0 +1,215 @@ +/* + * ads7871 - driver for TI ADS7871 A/D converter + * + * Copyright (c) 2010 Paul Thomas + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or + * later as publishhed by the Free Software Foundation. + * + * You need to have something like this in struct spi_board_info + * { + * .modalias = "ads7871", + * .max_speed_hz = 2*1000*1000, + * .chip_select = 0, + * .bus_num = 1, + * }, + */ + +/*From figure 18 in the datasheet*/ +/*Register addresses*/ +#define REG_LS_BYTE 0 /*A/D Output Data, LS Byte*/ +#define REG_MS_BYTE 1 /*A/D Output Data, MS Byte*/ +#define REG_PGA_VALID 2 /*PGA Valid Register*/ +#define REG_AD_CONTROL 3 /*A/D Control Register*/ +#define REG_GAIN_MUX 4 /*Gain/Mux Register*/ +#define REG_IO_STATE 5 /*Digital I/O State Register*/ +#define REG_IO_CONTROL 6 /*Digital I/O Control Register*/ +#define REG_OSC_CONTROL 7 /*Rev/Oscillator Control Register*/ +#define REG_SER_CONTROL 24 /*Serial Interface Control Register*/ +#define REG_ID 31 /*ID Register*/ + +/* + * From figure 17 in the datasheet + * These bits get ORed with the address to form + * the instruction byte + */ +/*Instruction Bit masks*/ +#define INST_MODE_BM (1 << 7) +#define INST_READ_BM (1 << 6) +#define INST_16BIT_BM (1 << 5) + +/*From figure 18 in the datasheet*/ +/*bit masks for Rev/Oscillator Control Register*/ +#define MUX_CNV_BV 7 +#define MUX_CNV_BM (1 << MUX_CNV_BV) +#define MUX_M3_BM (1 << 3) /*M3 selects single ended*/ +#define MUX_G_BV 4 /*allows for reg = (gain << MUX_G_BV) | ...*/ + +/*From figure 18 in the datasheet*/ +/*bit masks for Rev/Oscillator Control Register*/ +#define OSC_OSCR_BM (1 << 5) +#define OSC_OSCE_BM (1 << 4) +#define OSC_REFE_BM (1 << 3) +#define OSC_BUFE_BM (1 << 2) +#define OSC_R2V_BM (1 << 1) +#define OSC_RBG_BM (1 << 0) + +#include +#include +#include +#include +#include +#include +#include + +#define DEVICE_NAME "ads7871" + +struct ads7871_data { + struct spi_device *spi; +}; + +static int ads7871_read_reg8(struct spi_device *spi, int reg) +{ + int ret; + reg = reg | INST_READ_BM; + ret = spi_w8r8(spi, reg); + return ret; +} + +static int ads7871_read_reg16(struct spi_device *spi, int reg) +{ + int ret; + reg = reg | INST_READ_BM | INST_16BIT_BM; + ret = spi_w8r16(spi, reg); + return ret; +} + +static int ads7871_write_reg8(struct spi_device *spi, int reg, u8 val) +{ + u8 tmp[2] = {reg, val}; + return spi_write(spi, tmp, sizeof(tmp)); +} + +static ssize_t show_voltage(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct ads7871_data *pdata = dev_get_drvdata(dev); + struct spi_device *spi = pdata->spi; + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int ret, val, i = 0; + uint8_t channel, mux_cnv; + + channel = attr->index; + /* + * TODO: add support for conversions + * other than single ended with a gain of 1 + */ + /*MUX_M3_BM forces single ended*/ + /*This is also where the gain of the PGA would be set*/ + ads7871_write_reg8(spi, REG_GAIN_MUX, + (MUX_CNV_BM | MUX_M3_BM | channel)); + + ret = ads7871_read_reg8(spi, REG_GAIN_MUX); + mux_cnv = ((ret & MUX_CNV_BM) >> MUX_CNV_BV); + /* + * on 400MHz arm9 platform the conversion + * is already done when we do this test + */ + while ((i < 2) && mux_cnv) { + i++; + ret = ads7871_read_reg8(spi, REG_GAIN_MUX); + mux_cnv = ((ret & MUX_CNV_BM) >> MUX_CNV_BV); + msleep_interruptible(1); + } + + if (mux_cnv == 0) { + val = ads7871_read_reg16(spi, REG_LS_BYTE); + /*result in volts*10000 = (val/8192)*2.5*10000*/ + val = ((val >> 2) * 25000) / 8192; + return sprintf(buf, "%d\n", val); + } else { + return -1; + } +} + +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_voltage, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_voltage, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_voltage, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_voltage, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_voltage, NULL, 4); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, show_voltage, NULL, 5); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, show_voltage, NULL, 6); +static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, show_voltage, NULL, 7); + +static struct attribute *ads7871_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(ads7871); + +static int ads7871_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + int ret; + uint8_t val; + struct ads7871_data *pdata; + struct device *hwmon_dev; + + /* Configure the SPI bus */ + spi->mode = (SPI_MODE_0); + spi->bits_per_word = 8; + spi_setup(spi); + + ads7871_write_reg8(spi, REG_SER_CONTROL, 0); + ads7871_write_reg8(spi, REG_AD_CONTROL, 0); + + val = (OSC_OSCR_BM | OSC_OSCE_BM | OSC_REFE_BM | OSC_BUFE_BM); + ads7871_write_reg8(spi, REG_OSC_CONTROL, val); + ret = ads7871_read_reg8(spi, REG_OSC_CONTROL); + + dev_dbg(dev, "REG_OSC_CONTROL write:%x, read:%x\n", val, ret); + /* + * because there is no other error checking on an SPI bus + * we need to make sure we really have a chip + */ + if (val != ret) + return -ENODEV; + + pdata = devm_kzalloc(dev, sizeof(struct ads7871_data), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->spi = spi; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, spi->modalias, + pdata, + ads7871_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct spi_driver ads7871_driver = { + .driver = { + .name = DEVICE_NAME, + }, + .probe = ads7871_probe, +}; + +module_spi_driver(ads7871_driver); + +MODULE_AUTHOR("Paul Thomas "); +MODULE_DESCRIPTION("TI ADS7871 A/D driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/adt7310.c b/drivers/hwmon/adt7310.c new file mode 100644 index 000000000..ec02f4f0d --- /dev/null +++ b/drivers/hwmon/adt7310.c @@ -0,0 +1,117 @@ +/* + * ADT7310/ADT7310 digital temperature sensor driver + * + * Copyright 2012-2013 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include + +#include "adt7x10.h" + +#define ADT7310_STATUS 0 +#define ADT7310_CONFIG 1 +#define ADT7310_TEMPERATURE 2 +#define ADT7310_ID 3 +#define ADT7310_T_CRIT 4 +#define ADT7310_T_HYST 5 +#define ADT7310_T_ALARM_HIGH 6 +#define ADT7310_T_ALARM_LOW 7 + +static const u8 adt7310_reg_table[] = { + [ADT7X10_TEMPERATURE] = ADT7310_TEMPERATURE, + [ADT7X10_STATUS] = ADT7310_STATUS, + [ADT7X10_CONFIG] = ADT7310_CONFIG, + [ADT7X10_T_ALARM_HIGH] = ADT7310_T_ALARM_HIGH, + [ADT7X10_T_ALARM_LOW] = ADT7310_T_ALARM_LOW, + [ADT7X10_T_CRIT] = ADT7310_T_CRIT, + [ADT7X10_T_HYST] = ADT7310_T_HYST, + [ADT7X10_ID] = ADT7310_ID, +}; + +#define ADT7310_CMD_REG_OFFSET 3 +#define ADT7310_CMD_READ 0x40 + +#define AD7310_COMMAND(reg) (adt7310_reg_table[(reg)] << ADT7310_CMD_REG_OFFSET) + +static int adt7310_spi_read_word(struct device *dev, u8 reg) +{ + struct spi_device *spi = to_spi_device(dev); + + return spi_w8r16be(spi, AD7310_COMMAND(reg) | ADT7310_CMD_READ); +} + +static int adt7310_spi_write_word(struct device *dev, u8 reg, u16 data) +{ + struct spi_device *spi = to_spi_device(dev); + u8 buf[3]; + + buf[0] = AD7310_COMMAND(reg); + put_unaligned_be16(data, &buf[1]); + + return spi_write(spi, buf, sizeof(buf)); +} + +static int adt7310_spi_read_byte(struct device *dev, u8 reg) +{ + struct spi_device *spi = to_spi_device(dev); + + return spi_w8r8(spi, AD7310_COMMAND(reg) | ADT7310_CMD_READ); +} + +static int adt7310_spi_write_byte(struct device *dev, u8 reg, + u8 data) +{ + struct spi_device *spi = to_spi_device(dev); + u8 buf[2]; + + buf[0] = AD7310_COMMAND(reg); + buf[1] = data; + + return spi_write(spi, buf, sizeof(buf)); +} + +static const struct adt7x10_ops adt7310_spi_ops = { + .read_word = adt7310_spi_read_word, + .write_word = adt7310_spi_write_word, + .read_byte = adt7310_spi_read_byte, + .write_byte = adt7310_spi_write_byte, +}; + +static int adt7310_spi_probe(struct spi_device *spi) +{ + return adt7x10_probe(&spi->dev, spi_get_device_id(spi)->name, spi->irq, + &adt7310_spi_ops); +} + +static int adt7310_spi_remove(struct spi_device *spi) +{ + return adt7x10_remove(&spi->dev, spi->irq); +} + +static const struct spi_device_id adt7310_id[] = { + { "adt7310", 0 }, + { "adt7320", 0 }, + {} +}; +MODULE_DEVICE_TABLE(spi, adt7310_id); + +static struct spi_driver adt7310_driver = { + .driver = { + .name = "adt7310", + .pm = ADT7X10_DEV_PM_OPS, + }, + .probe = adt7310_spi_probe, + .remove = adt7310_spi_remove, + .id_table = adt7310_id, +}; +module_spi_driver(adt7310_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("ADT7310/ADT7320 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/adt7410.c b/drivers/hwmon/adt7410.c new file mode 100644 index 000000000..0dc066a93 --- /dev/null +++ b/drivers/hwmon/adt7410.c @@ -0,0 +1,80 @@ +/* + * ADT7410/ADT7420 digital temperature sensor driver + * + * Copyright 2012-2013 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include + +#include "adt7x10.h" + +static int adt7410_i2c_read_word(struct device *dev, u8 reg) +{ + return i2c_smbus_read_word_swapped(to_i2c_client(dev), reg); +} + +static int adt7410_i2c_write_word(struct device *dev, u8 reg, u16 data) +{ + return i2c_smbus_write_word_swapped(to_i2c_client(dev), reg, data); +} + +static int adt7410_i2c_read_byte(struct device *dev, u8 reg) +{ + return i2c_smbus_read_byte_data(to_i2c_client(dev), reg); +} + +static int adt7410_i2c_write_byte(struct device *dev, u8 reg, u8 data) +{ + return i2c_smbus_write_byte_data(to_i2c_client(dev), reg, data); +} + +static const struct adt7x10_ops adt7410_i2c_ops = { + .read_word = adt7410_i2c_read_word, + .write_word = adt7410_i2c_write_word, + .read_byte = adt7410_i2c_read_byte, + .write_byte = adt7410_i2c_write_byte, +}; + +static int adt7410_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + return adt7x10_probe(&client->dev, NULL, client->irq, &adt7410_i2c_ops); +} + +static int adt7410_i2c_remove(struct i2c_client *client) +{ + return adt7x10_remove(&client->dev, client->irq); +} + +static const struct i2c_device_id adt7410_ids[] = { + { "adt7410", 0 }, + { "adt7420", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, adt7410_ids); + +static struct i2c_driver adt7410_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "adt7410", + .pm = ADT7X10_DEV_PM_OPS, + }, + .probe = adt7410_i2c_probe, + .remove = adt7410_i2c_remove, + .id_table = adt7410_ids, + .address_list = I2C_ADDRS(0x48, 0x49, 0x4a, 0x4b), +}; +module_i2c_driver(adt7410_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("ADT7410/AD7420 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/adt7411.c b/drivers/hwmon/adt7411.c new file mode 100644 index 000000000..b939f8a11 --- /dev/null +++ b/drivers/hwmon/adt7411.c @@ -0,0 +1,743 @@ +/* + * Driver for the ADT7411 (I2C/SPI 8 channel 10 bit ADC & temperature-sensor) + * + * Copyright (C) 2008, 2010 Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: SPI, use power-down mode for suspend?, interrupt handling? + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADT7411_REG_STAT_1 0x00 +#define ADT7411_STAT_1_INT_TEMP_HIGH BIT(0) +#define ADT7411_STAT_1_INT_TEMP_LOW BIT(1) +#define ADT7411_STAT_1_EXT_TEMP_HIGH_AIN1 BIT(2) +#define ADT7411_STAT_1_EXT_TEMP_LOW BIT(3) +#define ADT7411_STAT_1_EXT_TEMP_FAULT BIT(4) +#define ADT7411_STAT_1_AIN2 BIT(5) +#define ADT7411_STAT_1_AIN3 BIT(6) +#define ADT7411_STAT_1_AIN4 BIT(7) +#define ADT7411_REG_STAT_2 0x01 +#define ADT7411_STAT_2_AIN5 BIT(0) +#define ADT7411_STAT_2_AIN6 BIT(1) +#define ADT7411_STAT_2_AIN7 BIT(2) +#define ADT7411_STAT_2_AIN8 BIT(3) +#define ADT7411_STAT_2_VDD BIT(4) +#define ADT7411_REG_INT_TEMP_VDD_LSB 0x03 +#define ADT7411_REG_EXT_TEMP_AIN14_LSB 0x04 +#define ADT7411_REG_VDD_MSB 0x06 +#define ADT7411_REG_INT_TEMP_MSB 0x07 +#define ADT7411_REG_EXT_TEMP_AIN1_MSB 0x08 + +#define ADT7411_REG_CFG1 0x18 +#define ADT7411_CFG1_START_MONITOR BIT(0) +#define ADT7411_CFG1_RESERVED_BIT1 BIT(1) +#define ADT7411_CFG1_EXT_TDM BIT(2) +#define ADT7411_CFG1_RESERVED_BIT3 BIT(3) + +#define ADT7411_REG_CFG2 0x19 +#define ADT7411_CFG2_DISABLE_AVG BIT(5) + +#define ADT7411_REG_CFG3 0x1a +#define ADT7411_CFG3_ADC_CLK_225 BIT(0) +#define ADT7411_CFG3_RESERVED_BIT1 BIT(1) +#define ADT7411_CFG3_RESERVED_BIT2 BIT(2) +#define ADT7411_CFG3_RESERVED_BIT3 BIT(3) +#define ADT7411_CFG3_REF_VDD BIT(4) + +#define ADT7411_REG_VDD_HIGH 0x23 +#define ADT7411_REG_VDD_LOW 0x24 +#define ADT7411_REG_TEMP_HIGH(nr) (0x25 + 2 * (nr)) +#define ADT7411_REG_TEMP_LOW(nr) (0x26 + 2 * (nr)) +#define ADT7411_REG_IN_HIGH(nr) ((nr) > 1 \ + ? 0x2b + 2 * ((nr)-2) \ + : 0x27) +#define ADT7411_REG_IN_LOW(nr) ((nr) > 1 \ + ? 0x2c + 2 * ((nr)-2) \ + : 0x28) + +#define ADT7411_REG_DEVICE_ID 0x4d +#define ADT7411_REG_MANUFACTURER_ID 0x4e + +#define ADT7411_DEVICE_ID 0x2 +#define ADT7411_MANUFACTURER_ID 0x41 + +static const unsigned short normal_i2c[] = { 0x48, 0x4a, 0x4b, I2C_CLIENT_END }; + +static const u8 adt7411_in_alarm_reg[] = { + ADT7411_REG_STAT_2, + ADT7411_REG_STAT_1, + ADT7411_REG_STAT_1, + ADT7411_REG_STAT_1, + ADT7411_REG_STAT_1, + ADT7411_REG_STAT_2, + ADT7411_REG_STAT_2, + ADT7411_REG_STAT_2, + ADT7411_REG_STAT_2, +}; + +static const u8 adt7411_in_alarm_bits[] = { + ADT7411_STAT_2_VDD, + ADT7411_STAT_1_EXT_TEMP_HIGH_AIN1, + ADT7411_STAT_1_AIN2, + ADT7411_STAT_1_AIN3, + ADT7411_STAT_1_AIN4, + ADT7411_STAT_2_AIN5, + ADT7411_STAT_2_AIN6, + ADT7411_STAT_2_AIN7, + ADT7411_STAT_2_AIN8, +}; + +struct adt7411_data { + struct mutex device_lock; /* for "atomic" device accesses */ + struct mutex update_lock; + unsigned long next_update; + long vref_cached; + struct i2c_client *client; + bool use_ext_temp; +}; + +/* + * When reading a register containing (up to 4) lsb, all associated + * msb-registers get locked by the hardware. After _one_ of those msb is read, + * _all_ are unlocked. In order to use this locking correctly, reading lsb/msb + * is protected here with a mutex, too. + */ +static int adt7411_read_10_bit(struct i2c_client *client, u8 lsb_reg, + u8 msb_reg, u8 lsb_shift) +{ + struct adt7411_data *data = i2c_get_clientdata(client); + int val, tmp; + + mutex_lock(&data->device_lock); + + val = i2c_smbus_read_byte_data(client, lsb_reg); + if (val < 0) + goto exit_unlock; + + tmp = (val >> lsb_shift) & 3; + val = i2c_smbus_read_byte_data(client, msb_reg); + + if (val >= 0) + val = (val << 2) | tmp; + + exit_unlock: + mutex_unlock(&data->device_lock); + + return val; +} + +static int adt7411_modify_bit(struct i2c_client *client, u8 reg, u8 bit, + bool flag) +{ + struct adt7411_data *data = i2c_get_clientdata(client); + int ret, val; + + mutex_lock(&data->device_lock); + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + goto exit_unlock; + + if (flag) + val = ret | bit; + else + val = ret & ~bit; + + ret = i2c_smbus_write_byte_data(client, reg, val); + + exit_unlock: + mutex_unlock(&data->device_lock); + return ret; +} + +static ssize_t adt7411_show_bit(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(attr); + struct adt7411_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret = i2c_smbus_read_byte_data(client, attr2->index); + + return ret < 0 ? ret : sprintf(buf, "%u\n", !!(ret & attr2->nr)); +} + +static ssize_t adt7411_set_bit(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct sensor_device_attribute_2 *s_attr2 = to_sensor_dev_attr_2(attr); + struct adt7411_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret; + unsigned long flag; + + ret = kstrtoul(buf, 0, &flag); + if (ret || flag > 1) + return -EINVAL; + + ret = adt7411_modify_bit(client, s_attr2->index, s_attr2->nr, flag); + + /* force update */ + mutex_lock(&data->update_lock); + data->next_update = jiffies; + mutex_unlock(&data->update_lock); + + return ret < 0 ? ret : count; +} + +#define ADT7411_BIT_ATTR(__name, __reg, __bit) \ + SENSOR_DEVICE_ATTR_2(__name, S_IRUGO | S_IWUSR, adt7411_show_bit, \ + adt7411_set_bit, __bit, __reg) + +static ADT7411_BIT_ATTR(no_average, ADT7411_REG_CFG2, ADT7411_CFG2_DISABLE_AVG); +static ADT7411_BIT_ATTR(fast_sampling, ADT7411_REG_CFG3, ADT7411_CFG3_ADC_CLK_225); +static ADT7411_BIT_ATTR(adc_ref_vdd, ADT7411_REG_CFG3, ADT7411_CFG3_REF_VDD); + +static struct attribute *adt7411_attrs[] = { + &sensor_dev_attr_no_average.dev_attr.attr, + &sensor_dev_attr_fast_sampling.dev_attr.attr, + &sensor_dev_attr_adc_ref_vdd.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(adt7411); + +static int adt7411_read_in_alarm(struct device *dev, int channel, long *val) +{ + struct adt7411_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret; + + ret = i2c_smbus_read_byte_data(client, adt7411_in_alarm_reg[channel]); + if (ret < 0) + return ret; + *val = !!(ret & adt7411_in_alarm_bits[channel]); + return 0; +} + +static int adt7411_read_in_vdd(struct device *dev, u32 attr, long *val) +{ + struct adt7411_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret; + + switch (attr) { + case hwmon_in_input: + ret = adt7411_read_10_bit(client, ADT7411_REG_INT_TEMP_VDD_LSB, + ADT7411_REG_VDD_MSB, 2); + if (ret < 0) + return ret; + *val = ret * 7000 / 1024; + return 0; + case hwmon_in_min: + ret = i2c_smbus_read_byte_data(client, ADT7411_REG_VDD_LOW); + if (ret < 0) + return ret; + *val = ret * 7000 / 256; + return 0; + case hwmon_in_max: + ret = i2c_smbus_read_byte_data(client, ADT7411_REG_VDD_HIGH); + if (ret < 0) + return ret; + *val = ret * 7000 / 256; + return 0; + case hwmon_in_alarm: + return adt7411_read_in_alarm(dev, 0, val); + default: + return -EOPNOTSUPP; + } +} + +static int adt7411_update_vref(struct device *dev) +{ + struct adt7411_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int val; + + if (time_after_eq(jiffies, data->next_update)) { + val = i2c_smbus_read_byte_data(client, ADT7411_REG_CFG3); + if (val < 0) + return val; + + if (val & ADT7411_CFG3_REF_VDD) { + val = adt7411_read_in_vdd(dev, hwmon_in_input, + &data->vref_cached); + if (val < 0) + return val; + } else { + data->vref_cached = 2250; + } + + data->next_update = jiffies + HZ; + } + + return 0; +} + +static int adt7411_read_in_chan(struct device *dev, u32 attr, int channel, + long *val) +{ + struct adt7411_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + int ret; + int reg, lsb_reg, lsb_shift; + int nr = channel - 1; + + mutex_lock(&data->update_lock); + ret = adt7411_update_vref(dev); + if (ret < 0) + goto exit_unlock; + + switch (attr) { + case hwmon_in_input: + lsb_reg = ADT7411_REG_EXT_TEMP_AIN14_LSB + (nr >> 2); + lsb_shift = 2 * (nr & 0x03); + ret = adt7411_read_10_bit(client, lsb_reg, + ADT7411_REG_EXT_TEMP_AIN1_MSB + nr, + lsb_shift); + if (ret < 0) + goto exit_unlock; + *val = ret * data->vref_cached / 1024; + ret = 0; + break; + case hwmon_in_min: + case hwmon_in_max: + reg = (attr == hwmon_in_min) + ? ADT7411_REG_IN_LOW(channel) + : ADT7411_REG_IN_HIGH(channel); + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + goto exit_unlock; + *val = ret * data->vref_cached / 256; + ret = 0; + break; + case hwmon_in_alarm: + ret = adt7411_read_in_alarm(dev, channel, val); + break; + default: + ret = -EOPNOTSUPP; + break; + } + exit_unlock: + mutex_unlock(&data->update_lock); + return ret; +} + +static int adt7411_read_in(struct device *dev, u32 attr, int channel, + long *val) +{ + if (channel == 0) + return adt7411_read_in_vdd(dev, attr, val); + else + return adt7411_read_in_chan(dev, attr, channel, val); +} + + +static int adt7411_read_temp_alarm(struct device *dev, u32 attr, int channel, + long *val) +{ + struct adt7411_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret, bit; + + ret = i2c_smbus_read_byte_data(client, ADT7411_REG_STAT_1); + if (ret < 0) + return ret; + + switch (attr) { + case hwmon_temp_min_alarm: + bit = channel ? ADT7411_STAT_1_EXT_TEMP_LOW + : ADT7411_STAT_1_INT_TEMP_LOW; + break; + case hwmon_temp_max_alarm: + bit = channel ? ADT7411_STAT_1_EXT_TEMP_HIGH_AIN1 + : ADT7411_STAT_1_INT_TEMP_HIGH; + break; + case hwmon_temp_fault: + bit = ADT7411_STAT_1_EXT_TEMP_FAULT; + break; + default: + return -EOPNOTSUPP; + } + + *val = !!(ret & bit); + return 0; +} + +static int adt7411_read_temp(struct device *dev, u32 attr, int channel, + long *val) +{ + struct adt7411_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret, reg, regl, regh; + + switch (attr) { + case hwmon_temp_input: + regl = channel ? ADT7411_REG_EXT_TEMP_AIN14_LSB : + ADT7411_REG_INT_TEMP_VDD_LSB; + regh = channel ? ADT7411_REG_EXT_TEMP_AIN1_MSB : + ADT7411_REG_INT_TEMP_MSB; + ret = adt7411_read_10_bit(client, regl, regh, 0); + if (ret < 0) + return ret; + ret = ret & 0x200 ? ret - 0x400 : ret; /* 10 bit signed */ + *val = ret * 250; + return 0; + case hwmon_temp_min: + case hwmon_temp_max: + reg = (attr == hwmon_temp_min) + ? ADT7411_REG_TEMP_LOW(channel) + : ADT7411_REG_TEMP_HIGH(channel); + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + return ret; + ret = ret & 0x80 ? ret - 0x100 : ret; /* 8 bit signed */ + *val = ret * 1000; + return 0; + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_fault: + return adt7411_read_temp_alarm(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int adt7411_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_in: + return adt7411_read_in(dev, attr, channel, val); + case hwmon_temp: + return adt7411_read_temp(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int adt7411_write_in_vdd(struct device *dev, u32 attr, long val) +{ + struct adt7411_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int reg; + + val = clamp_val(val, 0, 255 * 7000 / 256); + val = DIV_ROUND_CLOSEST(val * 256, 7000); + + switch (attr) { + case hwmon_in_min: + reg = ADT7411_REG_VDD_LOW; + break; + case hwmon_in_max: + reg = ADT7411_REG_VDD_HIGH; + break; + default: + return -EOPNOTSUPP; + } + + return i2c_smbus_write_byte_data(client, reg, val); +} + +static int adt7411_write_in_chan(struct device *dev, u32 attr, int channel, + long val) +{ + struct adt7411_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret, reg; + + mutex_lock(&data->update_lock); + ret = adt7411_update_vref(dev); + if (ret < 0) + goto exit_unlock; + val = clamp_val(val, 0, 255 * data->vref_cached / 256); + val = DIV_ROUND_CLOSEST(val * 256, data->vref_cached); + + switch (attr) { + case hwmon_in_min: + reg = ADT7411_REG_IN_LOW(channel); + break; + case hwmon_in_max: + reg = ADT7411_REG_IN_HIGH(channel); + break; + default: + ret = -EOPNOTSUPP; + goto exit_unlock; + } + + ret = i2c_smbus_write_byte_data(client, reg, val); + exit_unlock: + mutex_unlock(&data->update_lock); + return ret; +} + +static int adt7411_write_in(struct device *dev, u32 attr, int channel, + long val) +{ + if (channel == 0) + return adt7411_write_in_vdd(dev, attr, val); + else + return adt7411_write_in_chan(dev, attr, channel, val); +} + +static int adt7411_write_temp(struct device *dev, u32 attr, int channel, + long val) +{ + struct adt7411_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int reg; + + val = clamp_val(val, -128000, 127000); + val = DIV_ROUND_CLOSEST(val, 1000); + + switch (attr) { + case hwmon_temp_min: + reg = ADT7411_REG_TEMP_LOW(channel); + break; + case hwmon_temp_max: + reg = ADT7411_REG_TEMP_HIGH(channel); + break; + default: + return -EOPNOTSUPP; + } + + return i2c_smbus_write_byte_data(client, reg, val); +} + +static int adt7411_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_in: + return adt7411_write_in(dev, attr, channel, val); + case hwmon_temp: + return adt7411_write_temp(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t adt7411_is_visible(const void *_data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct adt7411_data *data = _data; + bool visible; + + switch (type) { + case hwmon_in: + visible = channel == 0 || channel >= 3 || !data->use_ext_temp; + switch (attr) { + case hwmon_in_input: + case hwmon_in_alarm: + return visible ? S_IRUGO : 0; + case hwmon_in_min: + case hwmon_in_max: + return visible ? S_IRUGO | S_IWUSR : 0; + } + break; + case hwmon_temp: + visible = channel == 0 || data->use_ext_temp; + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_fault: + return visible ? S_IRUGO : 0; + case hwmon_temp_min: + case hwmon_temp_max: + return visible ? S_IRUGO | S_IWUSR : 0; + } + break; + default: + break; + } + return 0; +} + +static int adt7411_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + int val; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + val = i2c_smbus_read_byte_data(client, ADT7411_REG_MANUFACTURER_ID); + if (val < 0 || val != ADT7411_MANUFACTURER_ID) { + dev_dbg(&client->dev, + "Wrong manufacturer ID. Got %d, expected %d\n", + val, ADT7411_MANUFACTURER_ID); + return -ENODEV; + } + + val = i2c_smbus_read_byte_data(client, ADT7411_REG_DEVICE_ID); + if (val < 0 || val != ADT7411_DEVICE_ID) { + dev_dbg(&client->dev, + "Wrong device ID. Got %d, expected %d\n", + val, ADT7411_DEVICE_ID); + return -ENODEV; + } + + strlcpy(info->type, "adt7411", I2C_NAME_SIZE); + + return 0; +} + +static int adt7411_init_device(struct adt7411_data *data) +{ + int ret; + u8 val; + + ret = i2c_smbus_read_byte_data(data->client, ADT7411_REG_CFG3); + if (ret < 0) + return ret; + + /* + * We must only write zero to bit 1 and bit 2 and only one to bit 3 + * according to the datasheet. + */ + val = ret; + val &= ~(ADT7411_CFG3_RESERVED_BIT1 | ADT7411_CFG3_RESERVED_BIT2); + val |= ADT7411_CFG3_RESERVED_BIT3; + + ret = i2c_smbus_write_byte_data(data->client, ADT7411_REG_CFG3, val); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, ADT7411_REG_CFG1); + if (ret < 0) + return ret; + + data->use_ext_temp = ret & ADT7411_CFG1_EXT_TDM; + + /* + * We must only write zero to bit 1 and only one to bit 3 according to + * the datasheet. + */ + val = ret; + val &= ~ADT7411_CFG1_RESERVED_BIT1; + val |= ADT7411_CFG1_RESERVED_BIT3; + + /* enable monitoring */ + val |= ADT7411_CFG1_START_MONITOR; + + return i2c_smbus_write_byte_data(data->client, ADT7411_REG_CFG1, val); +} + +static const u32 adt7411_in_config[] = { + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_ALARM, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_ALARM, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_ALARM, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_ALARM, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_ALARM, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_ALARM, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_ALARM, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_ALARM, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_ALARM, + 0 +}; + +static const struct hwmon_channel_info adt7411_in = { + .type = hwmon_in, + .config = adt7411_in_config, +}; + +static const u32 adt7411_temp_config[] = { + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MIN_ALARM | + HWMON_T_MAX | HWMON_T_MAX_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MIN_ALARM | + HWMON_T_MAX | HWMON_T_MAX_ALARM | HWMON_T_FAULT, + 0 +}; + +static const struct hwmon_channel_info adt7411_temp = { + .type = hwmon_temp, + .config = adt7411_temp_config, +}; + +static const struct hwmon_channel_info *adt7411_info[] = { + &adt7411_in, + &adt7411_temp, + NULL +}; + +static const struct hwmon_ops adt7411_hwmon_ops = { + .is_visible = adt7411_is_visible, + .read = adt7411_read, + .write = adt7411_write, +}; + +static const struct hwmon_chip_info adt7411_chip_info = { + .ops = &adt7411_hwmon_ops, + .info = adt7411_info, +}; + +static int adt7411_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct adt7411_data *data; + struct device *hwmon_dev; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->client = client; + mutex_init(&data->device_lock); + mutex_init(&data->update_lock); + + ret = adt7411_init_device(data); + if (ret < 0) + return ret; + + /* force update on first occasion */ + data->next_update = jiffies; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, + &adt7411_chip_info, + adt7411_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id adt7411_id[] = { + { "adt7411", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adt7411_id); + +static struct i2c_driver adt7411_driver = { + .driver = { + .name = "adt7411", + }, + .probe = adt7411_probe, + .id_table = adt7411_id, + .detect = adt7411_detect, + .address_list = normal_i2c, + .class = I2C_CLASS_HWMON, +}; + +module_i2c_driver(adt7411_driver); + +MODULE_AUTHOR("Sascha Hauer and " + "Wolfram Sang "); +MODULE_DESCRIPTION("ADT7411 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/adt7462.c b/drivers/hwmon/adt7462.c new file mode 100644 index 000000000..bdd7679fd --- /dev/null +++ b/drivers/hwmon/adt7462.c @@ -0,0 +1,1946 @@ +/* + * A hwmon driver for the Analog Devices ADT7462 + * Copyright (C) 2008 IBM + * + * Author: Darrick J. Wong + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x58, 0x5C, I2C_CLIENT_END }; + +/* ADT7462 registers */ +#define ADT7462_REG_DEVICE 0x3D +#define ADT7462_REG_VENDOR 0x3E +#define ADT7462_REG_REVISION 0x3F + +#define ADT7462_REG_MIN_TEMP_BASE_ADDR 0x44 +#define ADT7462_REG_MIN_TEMP_MAX_ADDR 0x47 +#define ADT7462_REG_MAX_TEMP_BASE_ADDR 0x48 +#define ADT7462_REG_MAX_TEMP_MAX_ADDR 0x4B +#define ADT7462_REG_TEMP_BASE_ADDR 0x88 +#define ADT7462_REG_TEMP_MAX_ADDR 0x8F + +#define ADT7462_REG_FAN_BASE_ADDR 0x98 +#define ADT7462_REG_FAN_MAX_ADDR 0x9F +#define ADT7462_REG_FAN2_BASE_ADDR 0xA2 +#define ADT7462_REG_FAN2_MAX_ADDR 0xA9 +#define ADT7462_REG_FAN_ENABLE 0x07 +#define ADT7462_REG_FAN_MIN_BASE_ADDR 0x78 +#define ADT7462_REG_FAN_MIN_MAX_ADDR 0x7F + +#define ADT7462_REG_CFG2 0x02 +#define ADT7462_FSPD_MASK 0x20 + +#define ADT7462_REG_PWM_BASE_ADDR 0xAA +#define ADT7462_REG_PWM_MAX_ADDR 0xAD +#define ADT7462_REG_PWM_MIN_BASE_ADDR 0x28 +#define ADT7462_REG_PWM_MIN_MAX_ADDR 0x2B +#define ADT7462_REG_PWM_MAX 0x2C +#define ADT7462_REG_PWM_TEMP_MIN_BASE_ADDR 0x5C +#define ADT7462_REG_PWM_TEMP_MIN_MAX_ADDR 0x5F +#define ADT7462_REG_PWM_TEMP_RANGE_BASE_ADDR 0x60 +#define ADT7462_REG_PWM_TEMP_RANGE_MAX_ADDR 0x63 +#define ADT7462_PWM_HYST_MASK 0x0F +#define ADT7462_PWM_RANGE_MASK 0xF0 +#define ADT7462_PWM_RANGE_SHIFT 4 +#define ADT7462_REG_PWM_CFG_BASE_ADDR 0x21 +#define ADT7462_REG_PWM_CFG_MAX_ADDR 0x24 +#define ADT7462_PWM_CHANNEL_MASK 0xE0 +#define ADT7462_PWM_CHANNEL_SHIFT 5 + +#define ADT7462_REG_PIN_CFG_BASE_ADDR 0x10 +#define ADT7462_REG_PIN_CFG_MAX_ADDR 0x13 +#define ADT7462_PIN7_INPUT 0x01 /* cfg0 */ +#define ADT7462_DIODE3_INPUT 0x20 +#define ADT7462_DIODE1_INPUT 0x40 +#define ADT7462_VID_INPUT 0x80 +#define ADT7462_PIN22_INPUT 0x04 /* cfg1 */ +#define ADT7462_PIN21_INPUT 0x08 +#define ADT7462_PIN19_INPUT 0x10 +#define ADT7462_PIN15_INPUT 0x20 +#define ADT7462_PIN13_INPUT 0x40 +#define ADT7462_PIN8_INPUT 0x80 +#define ADT7462_PIN23_MASK 0x03 +#define ADT7462_PIN23_SHIFT 0 +#define ADT7462_PIN26_MASK 0x0C /* cfg2 */ +#define ADT7462_PIN26_SHIFT 2 +#define ADT7462_PIN25_MASK 0x30 +#define ADT7462_PIN25_SHIFT 4 +#define ADT7462_PIN24_MASK 0xC0 +#define ADT7462_PIN24_SHIFT 6 +#define ADT7462_PIN26_VOLT_INPUT 0x08 +#define ADT7462_PIN25_VOLT_INPUT 0x20 +#define ADT7462_PIN28_SHIFT 4 /* cfg3 */ +#define ADT7462_PIN28_VOLT 0x5 + +#define ADT7462_REG_ALARM1 0xB8 +#define ADT7462_LT_ALARM 0x02 +#define ADT7462_R1T_ALARM 0x04 +#define ADT7462_R2T_ALARM 0x08 +#define ADT7462_R3T_ALARM 0x10 +#define ADT7462_REG_ALARM2 0xBB +#define ADT7462_V0_ALARM 0x01 +#define ADT7462_V1_ALARM 0x02 +#define ADT7462_V2_ALARM 0x04 +#define ADT7462_V3_ALARM 0x08 +#define ADT7462_V4_ALARM 0x10 +#define ADT7462_V5_ALARM 0x20 +#define ADT7462_V6_ALARM 0x40 +#define ADT7462_V7_ALARM 0x80 +#define ADT7462_REG_ALARM3 0xBC +#define ADT7462_V8_ALARM 0x08 +#define ADT7462_V9_ALARM 0x10 +#define ADT7462_V10_ALARM 0x20 +#define ADT7462_V11_ALARM 0x40 +#define ADT7462_V12_ALARM 0x80 +#define ADT7462_REG_ALARM4 0xBD +#define ADT7462_F0_ALARM 0x01 +#define ADT7462_F1_ALARM 0x02 +#define ADT7462_F2_ALARM 0x04 +#define ADT7462_F3_ALARM 0x08 +#define ADT7462_F4_ALARM 0x10 +#define ADT7462_F5_ALARM 0x20 +#define ADT7462_F6_ALARM 0x40 +#define ADT7462_F7_ALARM 0x80 +#define ADT7462_ALARM1 0x0000 +#define ADT7462_ALARM2 0x0100 +#define ADT7462_ALARM3 0x0200 +#define ADT7462_ALARM4 0x0300 +#define ADT7462_ALARM_REG_SHIFT 8 +#define ADT7462_ALARM_FLAG_MASK 0x0F + +#define ADT7462_TEMP_COUNT 4 +#define ADT7462_TEMP_REG(x) (ADT7462_REG_TEMP_BASE_ADDR + ((x) * 2)) +#define ADT7462_TEMP_MIN_REG(x) (ADT7462_REG_MIN_TEMP_BASE_ADDR + (x)) +#define ADT7462_TEMP_MAX_REG(x) (ADT7462_REG_MAX_TEMP_BASE_ADDR + (x)) +#define TEMP_FRAC_OFFSET 6 + +#define ADT7462_FAN_COUNT 8 +#define ADT7462_REG_FAN_MIN(x) (ADT7462_REG_FAN_MIN_BASE_ADDR + (x)) + +#define ADT7462_PWM_COUNT 4 +#define ADT7462_REG_PWM(x) (ADT7462_REG_PWM_BASE_ADDR + (x)) +#define ADT7462_REG_PWM_MIN(x) (ADT7462_REG_PWM_MIN_BASE_ADDR + (x)) +#define ADT7462_REG_PWM_TMIN(x) \ + (ADT7462_REG_PWM_TEMP_MIN_BASE_ADDR + (x)) +#define ADT7462_REG_PWM_TRANGE(x) \ + (ADT7462_REG_PWM_TEMP_RANGE_BASE_ADDR + (x)) + +#define ADT7462_PIN_CFG_REG_COUNT 4 +#define ADT7462_REG_PIN_CFG(x) (ADT7462_REG_PIN_CFG_BASE_ADDR + (x)) +#define ADT7462_REG_PWM_CFG(x) (ADT7462_REG_PWM_CFG_BASE_ADDR + (x)) + +#define ADT7462_ALARM_REG_COUNT 4 + +/* + * The chip can measure 13 different voltage sources: + * + * 1. +12V1 (pin 7) + * 2. Vccp1/+2.5V/+1.8V/+1.5V (pin 23) + * 3. +12V3 (pin 22) + * 4. +5V (pin 21) + * 5. +1.25V/+0.9V (pin 19) + * 6. +2.5V/+1.8V (pin 15) + * 7. +3.3v (pin 13) + * 8. +12V2 (pin 8) + * 9. Vbatt/FSB_Vtt (pin 26) + * A. +3.3V/+1.2V1 (pin 25) + * B. Vccp2/+2.5V/+1.8V/+1.5V (pin 24) + * C. +1.5V ICH (only if BOTH pin 28/29 are set to +1.5V) + * D. +1.5V 3GPIO (only if BOTH pin 28/29 are set to +1.5V) + * + * Each of these 13 has a factor to convert raw to voltage. Even better, + * the pins can be connected to other sensors (tach/gpio/hot/etc), which + * makes the bookkeeping tricky. + * + * Some, but not all, of these voltages have low/high limits. + */ +#define ADT7462_VOLT_COUNT 13 + +#define ADT7462_VENDOR 0x41 +#define ADT7462_DEVICE 0x62 +/* datasheet only mentions a revision 4 */ +#define ADT7462_REVISION 0x04 + +/* How often do we reread sensors values? (In jiffies) */ +#define SENSOR_REFRESH_INTERVAL (2 * HZ) + +/* How often do we reread sensor limit values? (In jiffies) */ +#define LIMIT_REFRESH_INTERVAL (60 * HZ) + +/* datasheet says to divide this number by the fan reading to get fan rpm */ +#define FAN_PERIOD_TO_RPM(x) ((90000 * 60) / (x)) +#define FAN_RPM_TO_PERIOD FAN_PERIOD_TO_RPM +#define FAN_PERIOD_INVALID 65535 +#define FAN_DATA_VALID(x) ((x) && (x) != FAN_PERIOD_INVALID) + +#define MASK_AND_SHIFT(value, prefix) \ + (((value) & prefix##_MASK) >> prefix##_SHIFT) + +struct adt7462_data { + struct i2c_client *client; + struct mutex lock; + char sensors_valid; + char limits_valid; + unsigned long sensors_last_updated; /* In jiffies */ + unsigned long limits_last_updated; /* In jiffies */ + + u8 temp[ADT7462_TEMP_COUNT]; + /* bits 6-7 are quarter pieces of temp */ + u8 temp_frac[ADT7462_TEMP_COUNT]; + u8 temp_min[ADT7462_TEMP_COUNT]; + u8 temp_max[ADT7462_TEMP_COUNT]; + u16 fan[ADT7462_FAN_COUNT]; + u8 fan_enabled; + u8 fan_min[ADT7462_FAN_COUNT]; + u8 cfg2; + u8 pwm[ADT7462_PWM_COUNT]; + u8 pin_cfg[ADT7462_PIN_CFG_REG_COUNT]; + u8 voltages[ADT7462_VOLT_COUNT]; + u8 volt_max[ADT7462_VOLT_COUNT]; + u8 volt_min[ADT7462_VOLT_COUNT]; + u8 pwm_min[ADT7462_PWM_COUNT]; + u8 pwm_tmin[ADT7462_PWM_COUNT]; + u8 pwm_trange[ADT7462_PWM_COUNT]; + u8 pwm_max; /* only one per chip */ + u8 pwm_cfg[ADT7462_PWM_COUNT]; + u8 alarms[ADT7462_ALARM_REG_COUNT]; +}; + +/* + * 16-bit registers on the ADT7462 are low-byte first. The data sheet says + * that the low byte must be read before the high byte. + */ +static inline int adt7462_read_word_data(struct i2c_client *client, u8 reg) +{ + u16 foo; + foo = i2c_smbus_read_byte_data(client, reg); + foo |= ((u16)i2c_smbus_read_byte_data(client, reg + 1) << 8); + return foo; +} + +/* For some reason these registers are not contiguous. */ +static int ADT7462_REG_FAN(int fan) +{ + if (fan < 4) + return ADT7462_REG_FAN_BASE_ADDR + (2 * fan); + return ADT7462_REG_FAN2_BASE_ADDR + (2 * (fan - 4)); +} + +/* Voltage registers are scattered everywhere */ +static int ADT7462_REG_VOLT_MAX(struct adt7462_data *data, int which) +{ + switch (which) { + case 0: + if (!(data->pin_cfg[0] & ADT7462_PIN7_INPUT)) + return 0x7C; + break; + case 1: + return 0x69; + case 2: + if (!(data->pin_cfg[1] & ADT7462_PIN22_INPUT)) + return 0x7F; + break; + case 3: + if (!(data->pin_cfg[1] & ADT7462_PIN21_INPUT)) + return 0x7E; + break; + case 4: + if (!(data->pin_cfg[0] & ADT7462_DIODE3_INPUT)) + return 0x4B; + break; + case 5: + if (!(data->pin_cfg[0] & ADT7462_DIODE1_INPUT)) + return 0x49; + break; + case 6: + if (!(data->pin_cfg[1] & ADT7462_PIN13_INPUT)) + return 0x68; + break; + case 7: + if (!(data->pin_cfg[1] & ADT7462_PIN8_INPUT)) + return 0x7D; + break; + case 8: + if (!(data->pin_cfg[2] & ADT7462_PIN26_VOLT_INPUT)) + return 0x6C; + break; + case 9: + if (!(data->pin_cfg[2] & ADT7462_PIN25_VOLT_INPUT)) + return 0x6B; + break; + case 10: + return 0x6A; + case 11: + if (data->pin_cfg[3] >> ADT7462_PIN28_SHIFT == + ADT7462_PIN28_VOLT && + !(data->pin_cfg[0] & ADT7462_VID_INPUT)) + return 0x50; + break; + case 12: + if (data->pin_cfg[3] >> ADT7462_PIN28_SHIFT == + ADT7462_PIN28_VOLT && + !(data->pin_cfg[0] & ADT7462_VID_INPUT)) + return 0x4C; + break; + } + return 0; +} + +static int ADT7462_REG_VOLT_MIN(struct adt7462_data *data, int which) +{ + switch (which) { + case 0: + if (!(data->pin_cfg[0] & ADT7462_PIN7_INPUT)) + return 0x6D; + break; + case 1: + return 0x72; + case 2: + if (!(data->pin_cfg[1] & ADT7462_PIN22_INPUT)) + return 0x6F; + break; + case 3: + if (!(data->pin_cfg[1] & ADT7462_PIN21_INPUT)) + return 0x71; + break; + case 4: + if (!(data->pin_cfg[0] & ADT7462_DIODE3_INPUT)) + return 0x47; + break; + case 5: + if (!(data->pin_cfg[0] & ADT7462_DIODE1_INPUT)) + return 0x45; + break; + case 6: + if (!(data->pin_cfg[1] & ADT7462_PIN13_INPUT)) + return 0x70; + break; + case 7: + if (!(data->pin_cfg[1] & ADT7462_PIN8_INPUT)) + return 0x6E; + break; + case 8: + if (!(data->pin_cfg[2] & ADT7462_PIN26_VOLT_INPUT)) + return 0x75; + break; + case 9: + if (!(data->pin_cfg[2] & ADT7462_PIN25_VOLT_INPUT)) + return 0x74; + break; + case 10: + return 0x73; + case 11: + if (data->pin_cfg[3] >> ADT7462_PIN28_SHIFT == + ADT7462_PIN28_VOLT && + !(data->pin_cfg[0] & ADT7462_VID_INPUT)) + return 0x76; + break; + case 12: + if (data->pin_cfg[3] >> ADT7462_PIN28_SHIFT == + ADT7462_PIN28_VOLT && + !(data->pin_cfg[0] & ADT7462_VID_INPUT)) + return 0x77; + break; + } + return 0; +} + +static int ADT7462_REG_VOLT(struct adt7462_data *data, int which) +{ + switch (which) { + case 0: + if (!(data->pin_cfg[0] & ADT7462_PIN7_INPUT)) + return 0xA3; + break; + case 1: + return 0x90; + case 2: + if (!(data->pin_cfg[1] & ADT7462_PIN22_INPUT)) + return 0xA9; + break; + case 3: + if (!(data->pin_cfg[1] & ADT7462_PIN21_INPUT)) + return 0xA7; + break; + case 4: + if (!(data->pin_cfg[0] & ADT7462_DIODE3_INPUT)) + return 0x8F; + break; + case 5: + if (!(data->pin_cfg[0] & ADT7462_DIODE1_INPUT)) + return 0x8B; + break; + case 6: + if (!(data->pin_cfg[1] & ADT7462_PIN13_INPUT)) + return 0x96; + break; + case 7: + if (!(data->pin_cfg[1] & ADT7462_PIN8_INPUT)) + return 0xA5; + break; + case 8: + if (!(data->pin_cfg[2] & ADT7462_PIN26_VOLT_INPUT)) + return 0x93; + break; + case 9: + if (!(data->pin_cfg[2] & ADT7462_PIN25_VOLT_INPUT)) + return 0x92; + break; + case 10: + return 0x91; + case 11: + if (data->pin_cfg[3] >> ADT7462_PIN28_SHIFT == + ADT7462_PIN28_VOLT && + !(data->pin_cfg[0] & ADT7462_VID_INPUT)) + return 0x94; + break; + case 12: + if (data->pin_cfg[3] >> ADT7462_PIN28_SHIFT == + ADT7462_PIN28_VOLT && + !(data->pin_cfg[0] & ADT7462_VID_INPUT)) + return 0x95; + break; + } + return 0; +} + +/* Provide labels for sysfs */ +static const char *voltage_label(struct adt7462_data *data, int which) +{ + switch (which) { + case 0: + if (!(data->pin_cfg[0] & ADT7462_PIN7_INPUT)) + return "+12V1"; + break; + case 1: + switch (MASK_AND_SHIFT(data->pin_cfg[1], ADT7462_PIN23)) { + case 0: + return "Vccp1"; + case 1: + return "+2.5V"; + case 2: + return "+1.8V"; + case 3: + return "+1.5V"; + } + case 2: + if (!(data->pin_cfg[1] & ADT7462_PIN22_INPUT)) + return "+12V3"; + break; + case 3: + if (!(data->pin_cfg[1] & ADT7462_PIN21_INPUT)) + return "+5V"; + break; + case 4: + if (!(data->pin_cfg[0] & ADT7462_DIODE3_INPUT)) { + if (data->pin_cfg[1] & ADT7462_PIN19_INPUT) + return "+0.9V"; + return "+1.25V"; + } + break; + case 5: + if (!(data->pin_cfg[0] & ADT7462_DIODE1_INPUT)) { + if (data->pin_cfg[1] & ADT7462_PIN19_INPUT) + return "+1.8V"; + return "+2.5V"; + } + break; + case 6: + if (!(data->pin_cfg[1] & ADT7462_PIN13_INPUT)) + return "+3.3V"; + break; + case 7: + if (!(data->pin_cfg[1] & ADT7462_PIN8_INPUT)) + return "+12V2"; + break; + case 8: + switch (MASK_AND_SHIFT(data->pin_cfg[2], ADT7462_PIN26)) { + case 0: + return "Vbatt"; + case 1: + return "FSB_Vtt"; + } + break; + case 9: + switch (MASK_AND_SHIFT(data->pin_cfg[2], ADT7462_PIN25)) { + case 0: + return "+3.3V"; + case 1: + return "+1.2V1"; + } + break; + case 10: + switch (MASK_AND_SHIFT(data->pin_cfg[2], ADT7462_PIN24)) { + case 0: + return "Vccp2"; + case 1: + return "+2.5V"; + case 2: + return "+1.8V"; + case 3: + return "+1.5"; + } + case 11: + if (data->pin_cfg[3] >> ADT7462_PIN28_SHIFT == + ADT7462_PIN28_VOLT && + !(data->pin_cfg[0] & ADT7462_VID_INPUT)) + return "+1.5V ICH"; + break; + case 12: + if (data->pin_cfg[3] >> ADT7462_PIN28_SHIFT == + ADT7462_PIN28_VOLT && + !(data->pin_cfg[0] & ADT7462_VID_INPUT)) + return "+1.5V 3GPIO"; + break; + } + return "N/A"; +} + +/* Multipliers are actually in uV, not mV. */ +static int voltage_multiplier(struct adt7462_data *data, int which) +{ + switch (which) { + case 0: + if (!(data->pin_cfg[0] & ADT7462_PIN7_INPUT)) + return 62500; + break; + case 1: + switch (MASK_AND_SHIFT(data->pin_cfg[1], ADT7462_PIN23)) { + case 0: + if (data->pin_cfg[0] & ADT7462_VID_INPUT) + return 12500; + return 6250; + case 1: + return 13000; + case 2: + return 9400; + case 3: + return 7800; + } + case 2: + if (!(data->pin_cfg[1] & ADT7462_PIN22_INPUT)) + return 62500; + break; + case 3: + if (!(data->pin_cfg[1] & ADT7462_PIN21_INPUT)) + return 26000; + break; + case 4: + if (!(data->pin_cfg[0] & ADT7462_DIODE3_INPUT)) { + if (data->pin_cfg[1] & ADT7462_PIN19_INPUT) + return 4690; + return 6500; + } + break; + case 5: + if (!(data->pin_cfg[0] & ADT7462_DIODE1_INPUT)) { + if (data->pin_cfg[1] & ADT7462_PIN15_INPUT) + return 9400; + return 13000; + } + break; + case 6: + if (!(data->pin_cfg[1] & ADT7462_PIN13_INPUT)) + return 17200; + break; + case 7: + if (!(data->pin_cfg[1] & ADT7462_PIN8_INPUT)) + return 62500; + break; + case 8: + switch (MASK_AND_SHIFT(data->pin_cfg[2], ADT7462_PIN26)) { + case 0: + return 15600; + case 1: + return 6250; + } + break; + case 9: + switch (MASK_AND_SHIFT(data->pin_cfg[2], ADT7462_PIN25)) { + case 0: + return 17200; + case 1: + return 6250; + } + break; + case 10: + switch (MASK_AND_SHIFT(data->pin_cfg[2], ADT7462_PIN24)) { + case 0: + return 6250; + case 1: + return 13000; + case 2: + return 9400; + case 3: + return 7800; + } + case 11: + case 12: + if (data->pin_cfg[3] >> ADT7462_PIN28_SHIFT == + ADT7462_PIN28_VOLT && + !(data->pin_cfg[0] & ADT7462_VID_INPUT)) + return 7800; + } + return 0; +} + +static int temp_enabled(struct adt7462_data *data, int which) +{ + switch (which) { + case 0: + case 2: + return 1; + case 1: + if (data->pin_cfg[0] & ADT7462_DIODE1_INPUT) + return 1; + break; + case 3: + if (data->pin_cfg[0] & ADT7462_DIODE3_INPUT) + return 1; + break; + } + return 0; +} + +static const char *temp_label(struct adt7462_data *data, int which) +{ + switch (which) { + case 0: + return "local"; + case 1: + if (data->pin_cfg[0] & ADT7462_DIODE1_INPUT) + return "remote1"; + break; + case 2: + return "remote2"; + case 3: + if (data->pin_cfg[0] & ADT7462_DIODE3_INPUT) + return "remote3"; + break; + } + return "N/A"; +} + +/* Map Trange register values to mC */ +#define NUM_TRANGE_VALUES 16 +static const int trange_values[NUM_TRANGE_VALUES] = { + 2000, + 2500, + 3300, + 4000, + 5000, + 6700, + 8000, + 10000, + 13300, + 16000, + 20000, + 26700, + 32000, + 40000, + 53300, + 80000 +}; + +static int find_trange_value(int trange) +{ + int i; + + for (i = 0; i < NUM_TRANGE_VALUES; i++) + if (trange_values[i] == trange) + return i; + + return -EINVAL; +} + +static struct adt7462_data *adt7462_update_device(struct device *dev) +{ + struct adt7462_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long local_jiffies = jiffies; + int i; + + mutex_lock(&data->lock); + if (time_before(local_jiffies, data->sensors_last_updated + + SENSOR_REFRESH_INTERVAL) + && data->sensors_valid) + goto no_sensor_update; + + for (i = 0; i < ADT7462_TEMP_COUNT; i++) { + /* + * Reading the fractional register locks the integral + * register until both have been read. + */ + data->temp_frac[i] = i2c_smbus_read_byte_data(client, + ADT7462_TEMP_REG(i)); + data->temp[i] = i2c_smbus_read_byte_data(client, + ADT7462_TEMP_REG(i) + 1); + } + + for (i = 0; i < ADT7462_FAN_COUNT; i++) + data->fan[i] = adt7462_read_word_data(client, + ADT7462_REG_FAN(i)); + + data->fan_enabled = i2c_smbus_read_byte_data(client, + ADT7462_REG_FAN_ENABLE); + + for (i = 0; i < ADT7462_PWM_COUNT; i++) + data->pwm[i] = i2c_smbus_read_byte_data(client, + ADT7462_REG_PWM(i)); + + for (i = 0; i < ADT7462_PIN_CFG_REG_COUNT; i++) + data->pin_cfg[i] = i2c_smbus_read_byte_data(client, + ADT7462_REG_PIN_CFG(i)); + + for (i = 0; i < ADT7462_VOLT_COUNT; i++) { + int reg = ADT7462_REG_VOLT(data, i); + if (!reg) + data->voltages[i] = 0; + else + data->voltages[i] = i2c_smbus_read_byte_data(client, + reg); + } + + data->alarms[0] = i2c_smbus_read_byte_data(client, ADT7462_REG_ALARM1); + data->alarms[1] = i2c_smbus_read_byte_data(client, ADT7462_REG_ALARM2); + data->alarms[2] = i2c_smbus_read_byte_data(client, ADT7462_REG_ALARM3); + data->alarms[3] = i2c_smbus_read_byte_data(client, ADT7462_REG_ALARM4); + + data->sensors_last_updated = local_jiffies; + data->sensors_valid = 1; + +no_sensor_update: + if (time_before(local_jiffies, data->limits_last_updated + + LIMIT_REFRESH_INTERVAL) + && data->limits_valid) + goto out; + + for (i = 0; i < ADT7462_TEMP_COUNT; i++) { + data->temp_min[i] = i2c_smbus_read_byte_data(client, + ADT7462_TEMP_MIN_REG(i)); + data->temp_max[i] = i2c_smbus_read_byte_data(client, + ADT7462_TEMP_MAX_REG(i)); + } + + for (i = 0; i < ADT7462_FAN_COUNT; i++) + data->fan_min[i] = i2c_smbus_read_byte_data(client, + ADT7462_REG_FAN_MIN(i)); + + for (i = 0; i < ADT7462_VOLT_COUNT; i++) { + int reg = ADT7462_REG_VOLT_MAX(data, i); + data->volt_max[i] = + (reg ? i2c_smbus_read_byte_data(client, reg) : 0); + + reg = ADT7462_REG_VOLT_MIN(data, i); + data->volt_min[i] = + (reg ? i2c_smbus_read_byte_data(client, reg) : 0); + } + + for (i = 0; i < ADT7462_PWM_COUNT; i++) { + data->pwm_min[i] = i2c_smbus_read_byte_data(client, + ADT7462_REG_PWM_MIN(i)); + data->pwm_tmin[i] = i2c_smbus_read_byte_data(client, + ADT7462_REG_PWM_TMIN(i)); + data->pwm_trange[i] = i2c_smbus_read_byte_data(client, + ADT7462_REG_PWM_TRANGE(i)); + data->pwm_cfg[i] = i2c_smbus_read_byte_data(client, + ADT7462_REG_PWM_CFG(i)); + } + + data->pwm_max = i2c_smbus_read_byte_data(client, ADT7462_REG_PWM_MAX); + + data->cfg2 = i2c_smbus_read_byte_data(client, ADT7462_REG_CFG2); + + data->limits_last_updated = local_jiffies; + data->limits_valid = 1; + +out: + mutex_unlock(&data->lock); + return data; +} + +static ssize_t show_temp_min(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + + if (!temp_enabled(data, attr->index)) + return sprintf(buf, "0\n"); + + return sprintf(buf, "%d\n", 1000 * (data->temp_min[attr->index] - 64)); +} + +static ssize_t set_temp_min(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp) || !temp_enabled(data, attr->index)) + return -EINVAL; + + temp = clamp_val(temp, -64000, 191000); + temp = DIV_ROUND_CLOSEST(temp, 1000) + 64; + + mutex_lock(&data->lock); + data->temp_min[attr->index] = temp; + i2c_smbus_write_byte_data(client, ADT7462_TEMP_MIN_REG(attr->index), + temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_temp_max(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + + if (!temp_enabled(data, attr->index)) + return sprintf(buf, "0\n"); + + return sprintf(buf, "%d\n", 1000 * (data->temp_max[attr->index] - 64)); +} + +static ssize_t set_temp_max(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp) || !temp_enabled(data, attr->index)) + return -EINVAL; + + temp = clamp_val(temp, -64000, 191000); + temp = DIV_ROUND_CLOSEST(temp, 1000) + 64; + + mutex_lock(&data->lock); + data->temp_max[attr->index] = temp; + i2c_smbus_write_byte_data(client, ADT7462_TEMP_MAX_REG(attr->index), + temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + u8 frac = data->temp_frac[attr->index] >> TEMP_FRAC_OFFSET; + + if (!temp_enabled(data, attr->index)) + return sprintf(buf, "0\n"); + + return sprintf(buf, "%d\n", 1000 * (data->temp[attr->index] - 64) + + 250 * frac); +} + +static ssize_t show_temp_label(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + + return sprintf(buf, "%s\n", temp_label(data, attr->index)); +} + +static ssize_t show_volt_max(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + int x = voltage_multiplier(data, attr->index); + + x *= data->volt_max[attr->index]; + x /= 1000; /* convert from uV to mV */ + + return sprintf(buf, "%d\n", x); +} + +static ssize_t set_volt_max(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int x = voltage_multiplier(data, attr->index); + long temp; + + if (kstrtol(buf, 10, &temp) || !x) + return -EINVAL; + + temp = clamp_val(temp, 0, 255 * x / 1000); + temp *= 1000; /* convert mV to uV */ + temp = DIV_ROUND_CLOSEST(temp, x); + + mutex_lock(&data->lock); + data->volt_max[attr->index] = temp; + i2c_smbus_write_byte_data(client, + ADT7462_REG_VOLT_MAX(data, attr->index), + temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_volt_min(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + int x = voltage_multiplier(data, attr->index); + + x *= data->volt_min[attr->index]; + x /= 1000; /* convert from uV to mV */ + + return sprintf(buf, "%d\n", x); +} + +static ssize_t set_volt_min(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int x = voltage_multiplier(data, attr->index); + long temp; + + if (kstrtol(buf, 10, &temp) || !x) + return -EINVAL; + + temp = clamp_val(temp, 0, 255 * x / 1000); + temp *= 1000; /* convert mV to uV */ + temp = DIV_ROUND_CLOSEST(temp, x); + + mutex_lock(&data->lock); + data->volt_min[attr->index] = temp; + i2c_smbus_write_byte_data(client, + ADT7462_REG_VOLT_MIN(data, attr->index), + temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_voltage(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + int x = voltage_multiplier(data, attr->index); + + x *= data->voltages[attr->index]; + x /= 1000; /* convert from uV to mV */ + + return sprintf(buf, "%d\n", x); +} + +static ssize_t show_voltage_label(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + + return sprintf(buf, "%s\n", voltage_label(data, attr->index)); +} + +static ssize_t show_alarm(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + int reg = attr->index >> ADT7462_ALARM_REG_SHIFT; + int mask = attr->index & ADT7462_ALARM_FLAG_MASK; + + if (data->alarms[reg] & mask) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static int fan_enabled(struct adt7462_data *data, int fan) +{ + return data->fan_enabled & (1 << fan); +} + +static ssize_t show_fan_min(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + u16 temp; + + /* Only the MSB of the min fan period is stored... */ + temp = data->fan_min[attr->index]; + temp <<= 8; + + if (!fan_enabled(data, attr->index) || + !FAN_DATA_VALID(temp)) + return sprintf(buf, "0\n"); + + return sprintf(buf, "%d\n", FAN_PERIOD_TO_RPM(temp)); +} + +static ssize_t set_fan_min(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp) || !temp || + !fan_enabled(data, attr->index)) + return -EINVAL; + + temp = FAN_RPM_TO_PERIOD(temp); + temp >>= 8; + temp = clamp_val(temp, 1, 255); + + mutex_lock(&data->lock); + data->fan_min[attr->index] = temp; + i2c_smbus_write_byte_data(client, ADT7462_REG_FAN_MIN(attr->index), + temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + + if (!fan_enabled(data, attr->index) || + !FAN_DATA_VALID(data->fan[attr->index])) + return sprintf(buf, "0\n"); + + return sprintf(buf, "%d\n", + FAN_PERIOD_TO_RPM(data->fan[attr->index])); +} + +static ssize_t show_force_pwm_max(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct adt7462_data *data = adt7462_update_device(dev); + return sprintf(buf, "%d\n", (data->cfg2 & ADT7462_FSPD_MASK ? 1 : 0)); +} + +static ssize_t set_force_pwm_max(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct adt7462_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + u8 reg; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + mutex_lock(&data->lock); + reg = i2c_smbus_read_byte_data(client, ADT7462_REG_CFG2); + if (temp) + reg |= ADT7462_FSPD_MASK; + else + reg &= ~ADT7462_FSPD_MASK; + data->cfg2 = reg; + i2c_smbus_write_byte_data(client, ADT7462_REG_CFG2, reg); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + return sprintf(buf, "%d\n", data->pwm[attr->index]); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = clamp_val(temp, 0, 255); + + mutex_lock(&data->lock); + data->pwm[attr->index] = temp; + i2c_smbus_write_byte_data(client, ADT7462_REG_PWM(attr->index), temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_pwm_max(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct adt7462_data *data = adt7462_update_device(dev); + return sprintf(buf, "%d\n", data->pwm_max); +} + +static ssize_t set_pwm_max(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct adt7462_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = clamp_val(temp, 0, 255); + + mutex_lock(&data->lock); + data->pwm_max = temp; + i2c_smbus_write_byte_data(client, ADT7462_REG_PWM_MAX, temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_pwm_min(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + return sprintf(buf, "%d\n", data->pwm_min[attr->index]); +} + +static ssize_t set_pwm_min(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = clamp_val(temp, 0, 255); + + mutex_lock(&data->lock); + data->pwm_min[attr->index] = temp; + i2c_smbus_write_byte_data(client, ADT7462_REG_PWM_MIN(attr->index), + temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_pwm_hyst(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + return sprintf(buf, "%d\n", 1000 * + (data->pwm_trange[attr->index] & ADT7462_PWM_HYST_MASK)); +} + +static ssize_t set_pwm_hyst(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = clamp_val(temp, 0, 15000); + temp = DIV_ROUND_CLOSEST(temp, 1000); + + /* package things up */ + temp &= ADT7462_PWM_HYST_MASK; + temp |= data->pwm_trange[attr->index] & ADT7462_PWM_RANGE_MASK; + + mutex_lock(&data->lock); + data->pwm_trange[attr->index] = temp; + i2c_smbus_write_byte_data(client, ADT7462_REG_PWM_TRANGE(attr->index), + temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_pwm_tmax(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + + /* tmax = tmin + trange */ + int trange = trange_values[data->pwm_trange[attr->index] >> + ADT7462_PWM_RANGE_SHIFT]; + int tmin = (data->pwm_tmin[attr->index] - 64) * 1000; + + return sprintf(buf, "%d\n", tmin + trange); +} + +static ssize_t set_pwm_tmax(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + int temp; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int tmin, trange_value; + long trange; + + if (kstrtol(buf, 10, &trange)) + return -EINVAL; + + /* trange = tmax - tmin */ + tmin = (data->pwm_tmin[attr->index] - 64) * 1000; + trange_value = find_trange_value(trange - tmin); + if (trange_value < 0) + return trange_value; + + temp = trange_value << ADT7462_PWM_RANGE_SHIFT; + temp |= data->pwm_trange[attr->index] & ADT7462_PWM_HYST_MASK; + + mutex_lock(&data->lock); + data->pwm_trange[attr->index] = temp; + i2c_smbus_write_byte_data(client, ADT7462_REG_PWM_TRANGE(attr->index), + temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_pwm_tmin(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + return sprintf(buf, "%d\n", 1000 * (data->pwm_tmin[attr->index] - 64)); +} + +static ssize_t set_pwm_tmin(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = clamp_val(temp, -64000, 191000); + temp = DIV_ROUND_CLOSEST(temp, 1000) + 64; + + mutex_lock(&data->lock); + data->pwm_tmin[attr->index] = temp; + i2c_smbus_write_byte_data(client, ADT7462_REG_PWM_TMIN(attr->index), + temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_pwm_auto(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + int cfg = data->pwm_cfg[attr->index] >> ADT7462_PWM_CHANNEL_SHIFT; + + switch (cfg) { + case 4: /* off */ + return sprintf(buf, "0\n"); + case 7: /* manual */ + return sprintf(buf, "1\n"); + default: /* automatic */ + return sprintf(buf, "2\n"); + } +} + +static void set_pwm_channel(struct i2c_client *client, + struct adt7462_data *data, + int which, + int value) +{ + int temp = data->pwm_cfg[which] & ~ADT7462_PWM_CHANNEL_MASK; + temp |= value << ADT7462_PWM_CHANNEL_SHIFT; + + mutex_lock(&data->lock); + data->pwm_cfg[which] = temp; + i2c_smbus_write_byte_data(client, ADT7462_REG_PWM_CFG(which), temp); + mutex_unlock(&data->lock); +} + +static ssize_t set_pwm_auto(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + switch (temp) { + case 0: /* off */ + set_pwm_channel(client, data, attr->index, 4); + return count; + case 1: /* manual */ + set_pwm_channel(client, data, attr->index, 7); + return count; + default: + return -EINVAL; + } +} + +static ssize_t show_pwm_auto_temp(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = adt7462_update_device(dev); + int channel = data->pwm_cfg[attr->index] >> ADT7462_PWM_CHANNEL_SHIFT; + + switch (channel) { + case 0: /* temp[1234] only */ + case 1: + case 2: + case 3: + return sprintf(buf, "%d\n", (1 << channel)); + case 5: /* temp1 & temp4 */ + return sprintf(buf, "9\n"); + case 6: + return sprintf(buf, "15\n"); + default: + return sprintf(buf, "0\n"); + } +} + +static int cvt_auto_temp(int input) +{ + if (input == 0xF) + return 6; + if (input == 0x9) + return 5; + if (input < 1 || !is_power_of_2(input)) + return -EINVAL; + return ilog2(input); +} + +static ssize_t set_pwm_auto_temp(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7462_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = cvt_auto_temp(temp); + if (temp < 0) + return temp; + + set_pwm_channel(client, data, attr->index, temp); + + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 1); +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 2); +static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 3); + +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 0); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 1); +static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 2); +static SENSOR_DEVICE_ATTR(temp4_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 3); + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3); + +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_temp_label, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_temp_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_temp_label, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_temp_label, NULL, 3); + +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM1 | ADT7462_LT_ALARM); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM1 | ADT7462_R1T_ALARM); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM1 | ADT7462_R2T_ALARM); +static SENSOR_DEVICE_ATTR(temp4_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM1 | ADT7462_R3T_ALARM); + +static SENSOR_DEVICE_ATTR(in1_max, S_IWUSR | S_IRUGO, show_volt_max, + set_volt_max, 0); +static SENSOR_DEVICE_ATTR(in2_max, S_IWUSR | S_IRUGO, show_volt_max, + set_volt_max, 1); +static SENSOR_DEVICE_ATTR(in3_max, S_IWUSR | S_IRUGO, show_volt_max, + set_volt_max, 2); +static SENSOR_DEVICE_ATTR(in4_max, S_IWUSR | S_IRUGO, show_volt_max, + set_volt_max, 3); +static SENSOR_DEVICE_ATTR(in5_max, S_IWUSR | S_IRUGO, show_volt_max, + set_volt_max, 4); +static SENSOR_DEVICE_ATTR(in6_max, S_IWUSR | S_IRUGO, show_volt_max, + set_volt_max, 5); +static SENSOR_DEVICE_ATTR(in7_max, S_IWUSR | S_IRUGO, show_volt_max, + set_volt_max, 6); +static SENSOR_DEVICE_ATTR(in8_max, S_IWUSR | S_IRUGO, show_volt_max, + set_volt_max, 7); +static SENSOR_DEVICE_ATTR(in9_max, S_IWUSR | S_IRUGO, show_volt_max, + set_volt_max, 8); +static SENSOR_DEVICE_ATTR(in10_max, S_IWUSR | S_IRUGO, show_volt_max, + set_volt_max, 9); +static SENSOR_DEVICE_ATTR(in11_max, S_IWUSR | S_IRUGO, show_volt_max, + set_volt_max, 10); +static SENSOR_DEVICE_ATTR(in12_max, S_IWUSR | S_IRUGO, show_volt_max, + set_volt_max, 11); +static SENSOR_DEVICE_ATTR(in13_max, S_IWUSR | S_IRUGO, show_volt_max, + set_volt_max, 12); + +static SENSOR_DEVICE_ATTR(in1_min, S_IWUSR | S_IRUGO, show_volt_min, + set_volt_min, 0); +static SENSOR_DEVICE_ATTR(in2_min, S_IWUSR | S_IRUGO, show_volt_min, + set_volt_min, 1); +static SENSOR_DEVICE_ATTR(in3_min, S_IWUSR | S_IRUGO, show_volt_min, + set_volt_min, 2); +static SENSOR_DEVICE_ATTR(in4_min, S_IWUSR | S_IRUGO, show_volt_min, + set_volt_min, 3); +static SENSOR_DEVICE_ATTR(in5_min, S_IWUSR | S_IRUGO, show_volt_min, + set_volt_min, 4); +static SENSOR_DEVICE_ATTR(in6_min, S_IWUSR | S_IRUGO, show_volt_min, + set_volt_min, 5); +static SENSOR_DEVICE_ATTR(in7_min, S_IWUSR | S_IRUGO, show_volt_min, + set_volt_min, 6); +static SENSOR_DEVICE_ATTR(in8_min, S_IWUSR | S_IRUGO, show_volt_min, + set_volt_min, 7); +static SENSOR_DEVICE_ATTR(in9_min, S_IWUSR | S_IRUGO, show_volt_min, + set_volt_min, 8); +static SENSOR_DEVICE_ATTR(in10_min, S_IWUSR | S_IRUGO, show_volt_min, + set_volt_min, 9); +static SENSOR_DEVICE_ATTR(in11_min, S_IWUSR | S_IRUGO, show_volt_min, + set_volt_min, 10); +static SENSOR_DEVICE_ATTR(in12_min, S_IWUSR | S_IRUGO, show_volt_min, + set_volt_min, 11); +static SENSOR_DEVICE_ATTR(in13_min, S_IWUSR | S_IRUGO, show_volt_min, + set_volt_min, 12); + +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_voltage, NULL, 0); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_voltage, NULL, 1); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_voltage, NULL, 2); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_voltage, NULL, 3); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, show_voltage, NULL, 4); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, show_voltage, NULL, 5); +static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, show_voltage, NULL, 6); +static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, show_voltage, NULL, 7); +static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, show_voltage, NULL, 8); +static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, show_voltage, NULL, 9); +static SENSOR_DEVICE_ATTR(in11_input, S_IRUGO, show_voltage, NULL, 10); +static SENSOR_DEVICE_ATTR(in12_input, S_IRUGO, show_voltage, NULL, 11); +static SENSOR_DEVICE_ATTR(in13_input, S_IRUGO, show_voltage, NULL, 12); + +static SENSOR_DEVICE_ATTR(in1_label, S_IRUGO, show_voltage_label, NULL, 0); +static SENSOR_DEVICE_ATTR(in2_label, S_IRUGO, show_voltage_label, NULL, 1); +static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, show_voltage_label, NULL, 2); +static SENSOR_DEVICE_ATTR(in4_label, S_IRUGO, show_voltage_label, NULL, 3); +static SENSOR_DEVICE_ATTR(in5_label, S_IRUGO, show_voltage_label, NULL, 4); +static SENSOR_DEVICE_ATTR(in6_label, S_IRUGO, show_voltage_label, NULL, 5); +static SENSOR_DEVICE_ATTR(in7_label, S_IRUGO, show_voltage_label, NULL, 6); +static SENSOR_DEVICE_ATTR(in8_label, S_IRUGO, show_voltage_label, NULL, 7); +static SENSOR_DEVICE_ATTR(in9_label, S_IRUGO, show_voltage_label, NULL, 8); +static SENSOR_DEVICE_ATTR(in10_label, S_IRUGO, show_voltage_label, NULL, 9); +static SENSOR_DEVICE_ATTR(in11_label, S_IRUGO, show_voltage_label, NULL, 10); +static SENSOR_DEVICE_ATTR(in12_label, S_IRUGO, show_voltage_label, NULL, 11); +static SENSOR_DEVICE_ATTR(in13_label, S_IRUGO, show_voltage_label, NULL, 12); + +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM2 | ADT7462_V0_ALARM); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM2 | ADT7462_V7_ALARM); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM2 | ADT7462_V2_ALARM); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM2 | ADT7462_V6_ALARM); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM2 | ADT7462_V5_ALARM); +static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM2 | ADT7462_V4_ALARM); +static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM2 | ADT7462_V3_ALARM); +static SENSOR_DEVICE_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM2 | ADT7462_V1_ALARM); +static SENSOR_DEVICE_ATTR(in9_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM3 | ADT7462_V10_ALARM); +static SENSOR_DEVICE_ATTR(in10_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM3 | ADT7462_V9_ALARM); +static SENSOR_DEVICE_ATTR(in11_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM3 | ADT7462_V8_ALARM); +static SENSOR_DEVICE_ATTR(in12_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM3 | ADT7462_V11_ALARM); +static SENSOR_DEVICE_ATTR(in13_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM3 | ADT7462_V12_ALARM); + +static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min, + set_fan_min, 0); +static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min, + set_fan_min, 1); +static SENSOR_DEVICE_ATTR(fan3_min, S_IWUSR | S_IRUGO, show_fan_min, + set_fan_min, 2); +static SENSOR_DEVICE_ATTR(fan4_min, S_IWUSR | S_IRUGO, show_fan_min, + set_fan_min, 3); +static SENSOR_DEVICE_ATTR(fan5_min, S_IWUSR | S_IRUGO, show_fan_min, + set_fan_min, 4); +static SENSOR_DEVICE_ATTR(fan6_min, S_IWUSR | S_IRUGO, show_fan_min, + set_fan_min, 5); +static SENSOR_DEVICE_ATTR(fan7_min, S_IWUSR | S_IRUGO, show_fan_min, + set_fan_min, 6); +static SENSOR_DEVICE_ATTR(fan8_min, S_IWUSR | S_IRUGO, show_fan_min, + set_fan_min, 7); + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3); +static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, show_fan, NULL, 4); +static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, show_fan, NULL, 5); +static SENSOR_DEVICE_ATTR(fan7_input, S_IRUGO, show_fan, NULL, 6); +static SENSOR_DEVICE_ATTR(fan8_input, S_IRUGO, show_fan, NULL, 7); + +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM4 | ADT7462_F0_ALARM); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM4 | ADT7462_F1_ALARM); +static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM4 | ADT7462_F2_ALARM); +static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM4 | ADT7462_F3_ALARM); +static SENSOR_DEVICE_ATTR(fan5_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM4 | ADT7462_F4_ALARM); +static SENSOR_DEVICE_ATTR(fan6_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM4 | ADT7462_F5_ALARM); +static SENSOR_DEVICE_ATTR(fan7_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM4 | ADT7462_F6_ALARM); +static SENSOR_DEVICE_ATTR(fan8_alarm, S_IRUGO, show_alarm, NULL, + ADT7462_ALARM4 | ADT7462_F7_ALARM); + +static SENSOR_DEVICE_ATTR(force_pwm_max, S_IWUSR | S_IRUGO, + show_force_pwm_max, set_force_pwm_max, 0); + +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 2); +static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 3); + +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IWUSR | S_IRUGO, + show_pwm_min, set_pwm_min, 0); +static SENSOR_DEVICE_ATTR(pwm2_auto_point1_pwm, S_IWUSR | S_IRUGO, + show_pwm_min, set_pwm_min, 1); +static SENSOR_DEVICE_ATTR(pwm3_auto_point1_pwm, S_IWUSR | S_IRUGO, + show_pwm_min, set_pwm_min, 2); +static SENSOR_DEVICE_ATTR(pwm4_auto_point1_pwm, S_IWUSR | S_IRUGO, + show_pwm_min, set_pwm_min, 3); + +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO, + show_pwm_max, set_pwm_max, 0); +static SENSOR_DEVICE_ATTR(pwm2_auto_point2_pwm, S_IWUSR | S_IRUGO, + show_pwm_max, set_pwm_max, 1); +static SENSOR_DEVICE_ATTR(pwm3_auto_point2_pwm, S_IWUSR | S_IRUGO, + show_pwm_max, set_pwm_max, 2); +static SENSOR_DEVICE_ATTR(pwm4_auto_point2_pwm, S_IWUSR | S_IRUGO, + show_pwm_max, set_pwm_max, 3); + +static SENSOR_DEVICE_ATTR(temp1_auto_point1_hyst, S_IWUSR | S_IRUGO, + show_pwm_hyst, set_pwm_hyst, 0); +static SENSOR_DEVICE_ATTR(temp2_auto_point1_hyst, S_IWUSR | S_IRUGO, + show_pwm_hyst, set_pwm_hyst, 1); +static SENSOR_DEVICE_ATTR(temp3_auto_point1_hyst, S_IWUSR | S_IRUGO, + show_pwm_hyst, set_pwm_hyst, 2); +static SENSOR_DEVICE_ATTR(temp4_auto_point1_hyst, S_IWUSR | S_IRUGO, + show_pwm_hyst, set_pwm_hyst, 3); + +static SENSOR_DEVICE_ATTR(temp1_auto_point2_hyst, S_IWUSR | S_IRUGO, + show_pwm_hyst, set_pwm_hyst, 0); +static SENSOR_DEVICE_ATTR(temp2_auto_point2_hyst, S_IWUSR | S_IRUGO, + show_pwm_hyst, set_pwm_hyst, 1); +static SENSOR_DEVICE_ATTR(temp3_auto_point2_hyst, S_IWUSR | S_IRUGO, + show_pwm_hyst, set_pwm_hyst, 2); +static SENSOR_DEVICE_ATTR(temp4_auto_point2_hyst, S_IWUSR | S_IRUGO, + show_pwm_hyst, set_pwm_hyst, 3); + +static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, S_IWUSR | S_IRUGO, + show_pwm_tmin, set_pwm_tmin, 0); +static SENSOR_DEVICE_ATTR(temp2_auto_point1_temp, S_IWUSR | S_IRUGO, + show_pwm_tmin, set_pwm_tmin, 1); +static SENSOR_DEVICE_ATTR(temp3_auto_point1_temp, S_IWUSR | S_IRUGO, + show_pwm_tmin, set_pwm_tmin, 2); +static SENSOR_DEVICE_ATTR(temp4_auto_point1_temp, S_IWUSR | S_IRUGO, + show_pwm_tmin, set_pwm_tmin, 3); + +static SENSOR_DEVICE_ATTR(temp1_auto_point2_temp, S_IWUSR | S_IRUGO, + show_pwm_tmax, set_pwm_tmax, 0); +static SENSOR_DEVICE_ATTR(temp2_auto_point2_temp, S_IWUSR | S_IRUGO, + show_pwm_tmax, set_pwm_tmax, 1); +static SENSOR_DEVICE_ATTR(temp3_auto_point2_temp, S_IWUSR | S_IRUGO, + show_pwm_tmax, set_pwm_tmax, 2); +static SENSOR_DEVICE_ATTR(temp4_auto_point2_temp, S_IWUSR | S_IRUGO, + show_pwm_tmax, set_pwm_tmax, 3); + +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, show_pwm_auto, + set_pwm_auto, 0); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, show_pwm_auto, + set_pwm_auto, 1); +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, show_pwm_auto, + set_pwm_auto, 2); +static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO, show_pwm_auto, + set_pwm_auto, 3); + +static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IWUSR | S_IRUGO, + show_pwm_auto_temp, set_pwm_auto_temp, 0); +static SENSOR_DEVICE_ATTR(pwm2_auto_channels_temp, S_IWUSR | S_IRUGO, + show_pwm_auto_temp, set_pwm_auto_temp, 1); +static SENSOR_DEVICE_ATTR(pwm3_auto_channels_temp, S_IWUSR | S_IRUGO, + show_pwm_auto_temp, set_pwm_auto_temp, 2); +static SENSOR_DEVICE_ATTR(pwm4_auto_channels_temp, S_IWUSR | S_IRUGO, + show_pwm_auto_temp, set_pwm_auto_temp, 3); + +static struct attribute *adt7462_attrs[] = { + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp4_min.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp2_label.dev_attr.attr, + &sensor_dev_attr_temp3_label.dev_attr.attr, + &sensor_dev_attr_temp4_label.dev_attr.attr, + + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_alarm.dev_attr.attr, + + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in8_max.dev_attr.attr, + &sensor_dev_attr_in9_max.dev_attr.attr, + &sensor_dev_attr_in10_max.dev_attr.attr, + &sensor_dev_attr_in11_max.dev_attr.attr, + &sensor_dev_attr_in12_max.dev_attr.attr, + &sensor_dev_attr_in13_max.dev_attr.attr, + + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in8_min.dev_attr.attr, + &sensor_dev_attr_in9_min.dev_attr.attr, + &sensor_dev_attr_in10_min.dev_attr.attr, + &sensor_dev_attr_in11_min.dev_attr.attr, + &sensor_dev_attr_in12_min.dev_attr.attr, + &sensor_dev_attr_in13_min.dev_attr.attr, + + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in11_input.dev_attr.attr, + &sensor_dev_attr_in12_input.dev_attr.attr, + &sensor_dev_attr_in13_input.dev_attr.attr, + + &sensor_dev_attr_in1_label.dev_attr.attr, + &sensor_dev_attr_in2_label.dev_attr.attr, + &sensor_dev_attr_in3_label.dev_attr.attr, + &sensor_dev_attr_in4_label.dev_attr.attr, + &sensor_dev_attr_in5_label.dev_attr.attr, + &sensor_dev_attr_in6_label.dev_attr.attr, + &sensor_dev_attr_in7_label.dev_attr.attr, + &sensor_dev_attr_in8_label.dev_attr.attr, + &sensor_dev_attr_in9_label.dev_attr.attr, + &sensor_dev_attr_in10_label.dev_attr.attr, + &sensor_dev_attr_in11_label.dev_attr.attr, + &sensor_dev_attr_in12_label.dev_attr.attr, + &sensor_dev_attr_in13_label.dev_attr.attr, + + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + &sensor_dev_attr_in6_alarm.dev_attr.attr, + &sensor_dev_attr_in7_alarm.dev_attr.attr, + &sensor_dev_attr_in8_alarm.dev_attr.attr, + &sensor_dev_attr_in9_alarm.dev_attr.attr, + &sensor_dev_attr_in10_alarm.dev_attr.attr, + &sensor_dev_attr_in11_alarm.dev_attr.attr, + &sensor_dev_attr_in12_alarm.dev_attr.attr, + &sensor_dev_attr_in13_alarm.dev_attr.attr, + + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan4_min.dev_attr.attr, + &sensor_dev_attr_fan5_min.dev_attr.attr, + &sensor_dev_attr_fan6_min.dev_attr.attr, + &sensor_dev_attr_fan7_min.dev_attr.attr, + &sensor_dev_attr_fan8_min.dev_attr.attr, + + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan5_input.dev_attr.attr, + &sensor_dev_attr_fan6_input.dev_attr.attr, + &sensor_dev_attr_fan7_input.dev_attr.attr, + &sensor_dev_attr_fan8_input.dev_attr.attr, + + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_fan4_alarm.dev_attr.attr, + &sensor_dev_attr_fan5_alarm.dev_attr.attr, + &sensor_dev_attr_fan6_alarm.dev_attr.attr, + &sensor_dev_attr_fan7_alarm.dev_attr.attr, + &sensor_dev_attr_fan8_alarm.dev_attr.attr, + + &sensor_dev_attr_force_pwm_max.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm4.dev_attr.attr, + + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm4_auto_point1_pwm.dev_attr.attr, + + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm4_auto_point2_pwm.dev_attr.attr, + + &sensor_dev_attr_temp1_auto_point1_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point1_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_auto_point1_hyst.dev_attr.attr, + &sensor_dev_attr_temp4_auto_point1_hyst.dev_attr.attr, + + &sensor_dev_attr_temp1_auto_point2_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point2_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_auto_point2_hyst.dev_attr.attr, + &sensor_dev_attr_temp4_auto_point2_hyst.dev_attr.attr, + + &sensor_dev_attr_temp1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_temp3_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_temp4_auto_point1_temp.dev_attr.attr, + + &sensor_dev_attr_temp1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_temp3_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_temp4_auto_point2_temp.dev_attr.attr, + + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_pwm4_enable.dev_attr.attr, + + &sensor_dev_attr_pwm1_auto_channels_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_channels_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_channels_temp.dev_attr.attr, + &sensor_dev_attr_pwm4_auto_channels_temp.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(adt7462); + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int adt7462_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int vendor, device, revision; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + vendor = i2c_smbus_read_byte_data(client, ADT7462_REG_VENDOR); + if (vendor != ADT7462_VENDOR) + return -ENODEV; + + device = i2c_smbus_read_byte_data(client, ADT7462_REG_DEVICE); + if (device != ADT7462_DEVICE) + return -ENODEV; + + revision = i2c_smbus_read_byte_data(client, ADT7462_REG_REVISION); + if (revision != ADT7462_REVISION) + return -ENODEV; + + strlcpy(info->type, "adt7462", I2C_NAME_SIZE); + + return 0; +} + +static int adt7462_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct adt7462_data *data; + struct device *hwmon_dev; + + data = devm_kzalloc(dev, sizeof(struct adt7462_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->lock); + + dev_info(&client->dev, "%s chip found\n", client->name); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + adt7462_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id adt7462_id[] = { + { "adt7462", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adt7462_id); + +static struct i2c_driver adt7462_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "adt7462", + }, + .probe = adt7462_probe, + .id_table = adt7462_id, + .detect = adt7462_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(adt7462_driver); + +MODULE_AUTHOR("Darrick J. Wong "); +MODULE_DESCRIPTION("ADT7462 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c new file mode 100644 index 000000000..6876e3817 --- /dev/null +++ b/drivers/hwmon/adt7470.c @@ -0,0 +1,1390 @@ +/* + * A hwmon driver for the Analog Devices ADT7470 + * Copyright (C) 2007 IBM + * + * Author: Darrick J. Wong + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; + +/* ADT7470 registers */ +#define ADT7470_REG_BASE_ADDR 0x20 +#define ADT7470_REG_TEMP_BASE_ADDR 0x20 +#define ADT7470_REG_TEMP_MAX_ADDR 0x29 +#define ADT7470_REG_FAN_BASE_ADDR 0x2A +#define ADT7470_REG_FAN_MAX_ADDR 0x31 +#define ADT7470_REG_PWM_BASE_ADDR 0x32 +#define ADT7470_REG_PWM_MAX_ADDR 0x35 +#define ADT7470_REG_PWM_MAX_BASE_ADDR 0x38 +#define ADT7470_REG_PWM_MAX_MAX_ADDR 0x3B +#define ADT7470_REG_CFG 0x40 +#define ADT7470_FSPD_MASK 0x04 +#define ADT7470_REG_ALARM1 0x41 +#define ADT7470_R1T_ALARM 0x01 +#define ADT7470_R2T_ALARM 0x02 +#define ADT7470_R3T_ALARM 0x04 +#define ADT7470_R4T_ALARM 0x08 +#define ADT7470_R5T_ALARM 0x10 +#define ADT7470_R6T_ALARM 0x20 +#define ADT7470_R7T_ALARM 0x40 +#define ADT7470_OOL_ALARM 0x80 +#define ADT7470_REG_ALARM2 0x42 +#define ADT7470_R8T_ALARM 0x01 +#define ADT7470_R9T_ALARM 0x02 +#define ADT7470_R10T_ALARM 0x04 +#define ADT7470_FAN1_ALARM 0x10 +#define ADT7470_FAN2_ALARM 0x20 +#define ADT7470_FAN3_ALARM 0x40 +#define ADT7470_FAN4_ALARM 0x80 +#define ADT7470_REG_TEMP_LIMITS_BASE_ADDR 0x44 +#define ADT7470_REG_TEMP_LIMITS_MAX_ADDR 0x57 +#define ADT7470_REG_FAN_MIN_BASE_ADDR 0x58 +#define ADT7470_REG_FAN_MIN_MAX_ADDR 0x5F +#define ADT7470_REG_FAN_MAX_BASE_ADDR 0x60 +#define ADT7470_REG_FAN_MAX_MAX_ADDR 0x67 +#define ADT7470_REG_PWM_CFG_BASE_ADDR 0x68 +#define ADT7470_REG_PWM12_CFG 0x68 +#define ADT7470_PWM2_AUTO_MASK 0x40 +#define ADT7470_PWM1_AUTO_MASK 0x80 +#define ADT7470_PWM_AUTO_MASK 0xC0 +#define ADT7470_REG_PWM34_CFG 0x69 +#define ADT7470_PWM3_AUTO_MASK 0x40 +#define ADT7470_PWM4_AUTO_MASK 0x80 +#define ADT7470_REG_PWM_MIN_BASE_ADDR 0x6A +#define ADT7470_REG_PWM_MIN_MAX_ADDR 0x6D +#define ADT7470_REG_PWM_TEMP_MIN_BASE_ADDR 0x6E +#define ADT7470_REG_PWM_TEMP_MIN_MAX_ADDR 0x71 +#define ADT7470_REG_CFG_2 0x74 +#define ADT7470_REG_ACOUSTICS12 0x75 +#define ADT7470_REG_ACOUSTICS34 0x76 +#define ADT7470_REG_DEVICE 0x3D +#define ADT7470_REG_VENDOR 0x3E +#define ADT7470_REG_REVISION 0x3F +#define ADT7470_REG_ALARM1_MASK 0x72 +#define ADT7470_REG_ALARM2_MASK 0x73 +#define ADT7470_REG_PWM_AUTO_TEMP_BASE_ADDR 0x7C +#define ADT7470_REG_PWM_AUTO_TEMP_MAX_ADDR 0x7D +#define ADT7470_REG_MAX_ADDR 0x81 + +#define ADT7470_TEMP_COUNT 10 +#define ADT7470_TEMP_REG(x) (ADT7470_REG_TEMP_BASE_ADDR + (x)) +#define ADT7470_TEMP_MIN_REG(x) (ADT7470_REG_TEMP_LIMITS_BASE_ADDR + ((x) * 2)) +#define ADT7470_TEMP_MAX_REG(x) (ADT7470_REG_TEMP_LIMITS_BASE_ADDR + \ + ((x) * 2) + 1) + +#define ADT7470_FAN_COUNT 4 +#define ADT7470_REG_FAN(x) (ADT7470_REG_FAN_BASE_ADDR + ((x) * 2)) +#define ADT7470_REG_FAN_MIN(x) (ADT7470_REG_FAN_MIN_BASE_ADDR + ((x) * 2)) +#define ADT7470_REG_FAN_MAX(x) (ADT7470_REG_FAN_MAX_BASE_ADDR + ((x) * 2)) + +#define ADT7470_PWM_COUNT 4 +#define ADT7470_REG_PWM(x) (ADT7470_REG_PWM_BASE_ADDR + (x)) +#define ADT7470_REG_PWM_MAX(x) (ADT7470_REG_PWM_MAX_BASE_ADDR + (x)) +#define ADT7470_REG_PWM_MIN(x) (ADT7470_REG_PWM_MIN_BASE_ADDR + (x)) +#define ADT7470_REG_PWM_TMIN(x) (ADT7470_REG_PWM_TEMP_MIN_BASE_ADDR + (x)) +#define ADT7470_REG_PWM_CFG(x) (ADT7470_REG_PWM_CFG_BASE_ADDR + ((x) / 2)) +#define ADT7470_REG_PWM_AUTO_TEMP(x) (ADT7470_REG_PWM_AUTO_TEMP_BASE_ADDR + \ + ((x) / 2)) + +#define ALARM2(x) ((x) << 8) + +#define ADT7470_VENDOR 0x41 +#define ADT7470_DEVICE 0x70 +/* datasheet only mentions a revision 2 */ +#define ADT7470_REVISION 0x02 + +/* "all temps" according to hwmon sysfs interface spec */ +#define ADT7470_PWM_ALL_TEMPS 0x3FF + +/* How often do we reread sensors values? (In jiffies) */ +#define SENSOR_REFRESH_INTERVAL (5 * HZ) + +/* How often do we reread sensor limit values? (In jiffies) */ +#define LIMIT_REFRESH_INTERVAL (60 * HZ) + +/* Wait at least 200ms per sensor for 10 sensors */ +#define TEMP_COLLECTION_TIME 2000 + +/* auto update thing won't fire more than every 2s */ +#define AUTO_UPDATE_INTERVAL 2000 + +/* datasheet says to divide this number by the fan reading to get fan rpm */ +#define FAN_PERIOD_TO_RPM(x) ((90000 * 60) / (x)) +#define FAN_RPM_TO_PERIOD FAN_PERIOD_TO_RPM +#define FAN_PERIOD_INVALID 65535 +#define FAN_DATA_VALID(x) ((x) && (x) != FAN_PERIOD_INVALID) + +/* Config registers 1 and 2 include fields for selecting the PWM frequency */ +#define ADT7470_CFG_LF 0x40 +#define ADT7470_FREQ_MASK 0x70 +#define ADT7470_FREQ_SHIFT 4 + +struct adt7470_data { + struct i2c_client *client; + struct mutex lock; + char sensors_valid; + char limits_valid; + unsigned long sensors_last_updated; /* In jiffies */ + unsigned long limits_last_updated; /* In jiffies */ + + int num_temp_sensors; /* -1 = probe */ + int temperatures_probed; + + s8 temp[ADT7470_TEMP_COUNT]; + s8 temp_min[ADT7470_TEMP_COUNT]; + s8 temp_max[ADT7470_TEMP_COUNT]; + u16 fan[ADT7470_FAN_COUNT]; + u16 fan_min[ADT7470_FAN_COUNT]; + u16 fan_max[ADT7470_FAN_COUNT]; + u16 alarm; + u16 alarms_mask; + u8 force_pwm_max; + u8 pwm[ADT7470_PWM_COUNT]; + u8 pwm_max[ADT7470_PWM_COUNT]; + u8 pwm_automatic[ADT7470_PWM_COUNT]; + u8 pwm_min[ADT7470_PWM_COUNT]; + s8 pwm_tmin[ADT7470_PWM_COUNT]; + u8 pwm_auto_temp[ADT7470_PWM_COUNT]; + + struct task_struct *auto_update; + unsigned int auto_update_interval; +}; + +/* + * 16-bit registers on the ADT7470 are low-byte first. The data sheet says + * that the low byte must be read before the high byte. + */ +static inline int adt7470_read_word_data(struct i2c_client *client, u8 reg) +{ + u16 foo; + foo = i2c_smbus_read_byte_data(client, reg); + foo |= ((u16)i2c_smbus_read_byte_data(client, reg + 1) << 8); + return foo; +} + +static inline int adt7470_write_word_data(struct i2c_client *client, u8 reg, + u16 value) +{ + return i2c_smbus_write_byte_data(client, reg, value & 0xFF) + || i2c_smbus_write_byte_data(client, reg + 1, value >> 8); +} + +/* Probe for temperature sensors. Assumes lock is held */ +static int adt7470_read_temperatures(struct i2c_client *client, + struct adt7470_data *data) +{ + unsigned long res; + int i; + u8 cfg, pwm[4], pwm_cfg[2]; + + /* save pwm[1-4] config register */ + pwm_cfg[0] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM_CFG(0)); + pwm_cfg[1] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM_CFG(2)); + + /* set manual pwm to whatever it is set to now */ + for (i = 0; i < ADT7470_FAN_COUNT; i++) + pwm[i] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM(i)); + + /* put pwm in manual mode */ + i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(0), + pwm_cfg[0] & ~(ADT7470_PWM_AUTO_MASK)); + i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(2), + pwm_cfg[1] & ~(ADT7470_PWM_AUTO_MASK)); + + /* write pwm control to whatever it was */ + for (i = 0; i < ADT7470_FAN_COUNT; i++) + i2c_smbus_write_byte_data(client, ADT7470_REG_PWM(i), pwm[i]); + + /* start reading temperature sensors */ + cfg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG); + cfg |= 0x80; + i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, cfg); + + /* Delay is 200ms * number of temp sensors. */ + res = msleep_interruptible((data->num_temp_sensors >= 0 ? + data->num_temp_sensors * 200 : + TEMP_COLLECTION_TIME)); + + /* done reading temperature sensors */ + cfg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG); + cfg &= ~0x80; + i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, cfg); + + /* restore pwm[1-4] config registers */ + i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(0), pwm_cfg[0]); + i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(2), pwm_cfg[1]); + + if (res) { + pr_err("ha ha, interrupted\n"); + return -EAGAIN; + } + + /* Only count fans if we have to */ + if (data->num_temp_sensors >= 0) + return 0; + + for (i = 0; i < ADT7470_TEMP_COUNT; i++) { + data->temp[i] = i2c_smbus_read_byte_data(client, + ADT7470_TEMP_REG(i)); + if (data->temp[i]) + data->num_temp_sensors = i + 1; + } + data->temperatures_probed = 1; + return 0; +} + +static int adt7470_update_thread(void *p) +{ + struct i2c_client *client = p; + struct adt7470_data *data = i2c_get_clientdata(client); + + while (!kthread_should_stop()) { + mutex_lock(&data->lock); + adt7470_read_temperatures(client, data); + mutex_unlock(&data->lock); + + if (kthread_should_stop()) + break; + + schedule_timeout_interruptible(msecs_to_jiffies(data->auto_update_interval)); + } + + return 0; +} + +static struct adt7470_data *adt7470_update_device(struct device *dev) +{ + struct adt7470_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long local_jiffies = jiffies; + u8 cfg; + int i; + int need_sensors = 1; + int need_limits = 1; + + /* + * Figure out if we need to update the shadow registers. + * Lockless means that we may occasionally report out of + * date data. + */ + if (time_before(local_jiffies, data->sensors_last_updated + + SENSOR_REFRESH_INTERVAL) && + data->sensors_valid) + need_sensors = 0; + + if (time_before(local_jiffies, data->limits_last_updated + + LIMIT_REFRESH_INTERVAL) && + data->limits_valid) + need_limits = 0; + + if (!need_sensors && !need_limits) + return data; + + mutex_lock(&data->lock); + if (!need_sensors) + goto no_sensor_update; + + if (!data->temperatures_probed) + adt7470_read_temperatures(client, data); + else + for (i = 0; i < ADT7470_TEMP_COUNT; i++) + data->temp[i] = i2c_smbus_read_byte_data(client, + ADT7470_TEMP_REG(i)); + + for (i = 0; i < ADT7470_FAN_COUNT; i++) + data->fan[i] = adt7470_read_word_data(client, + ADT7470_REG_FAN(i)); + + for (i = 0; i < ADT7470_PWM_COUNT; i++) { + int reg; + int reg_mask; + + data->pwm[i] = i2c_smbus_read_byte_data(client, + ADT7470_REG_PWM(i)); + + if (i % 2) + reg_mask = ADT7470_PWM2_AUTO_MASK; + else + reg_mask = ADT7470_PWM1_AUTO_MASK; + + reg = ADT7470_REG_PWM_CFG(i); + if (i2c_smbus_read_byte_data(client, reg) & reg_mask) + data->pwm_automatic[i] = 1; + else + data->pwm_automatic[i] = 0; + + reg = ADT7470_REG_PWM_AUTO_TEMP(i); + cfg = i2c_smbus_read_byte_data(client, reg); + if (!(i % 2)) + data->pwm_auto_temp[i] = cfg >> 4; + else + data->pwm_auto_temp[i] = cfg & 0xF; + } + + if (i2c_smbus_read_byte_data(client, ADT7470_REG_CFG) & + ADT7470_FSPD_MASK) + data->force_pwm_max = 1; + else + data->force_pwm_max = 0; + + data->alarm = i2c_smbus_read_byte_data(client, ADT7470_REG_ALARM1); + if (data->alarm & ADT7470_OOL_ALARM) + data->alarm |= ALARM2(i2c_smbus_read_byte_data(client, + ADT7470_REG_ALARM2)); + data->alarms_mask = adt7470_read_word_data(client, + ADT7470_REG_ALARM1_MASK); + + data->sensors_last_updated = local_jiffies; + data->sensors_valid = 1; + +no_sensor_update: + if (!need_limits) + goto out; + + for (i = 0; i < ADT7470_TEMP_COUNT; i++) { + data->temp_min[i] = i2c_smbus_read_byte_data(client, + ADT7470_TEMP_MIN_REG(i)); + data->temp_max[i] = i2c_smbus_read_byte_data(client, + ADT7470_TEMP_MAX_REG(i)); + } + + for (i = 0; i < ADT7470_FAN_COUNT; i++) { + data->fan_min[i] = adt7470_read_word_data(client, + ADT7470_REG_FAN_MIN(i)); + data->fan_max[i] = adt7470_read_word_data(client, + ADT7470_REG_FAN_MAX(i)); + } + + for (i = 0; i < ADT7470_PWM_COUNT; i++) { + data->pwm_max[i] = i2c_smbus_read_byte_data(client, + ADT7470_REG_PWM_MAX(i)); + data->pwm_min[i] = i2c_smbus_read_byte_data(client, + ADT7470_REG_PWM_MIN(i)); + data->pwm_tmin[i] = i2c_smbus_read_byte_data(client, + ADT7470_REG_PWM_TMIN(i)); + } + + data->limits_last_updated = local_jiffies; + data->limits_valid = 1; + +out: + mutex_unlock(&data->lock); + return data; +} + +static ssize_t auto_update_interval_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct adt7470_data *data = adt7470_update_device(dev); + return sprintf(buf, "%d\n", data->auto_update_interval); +} + +static ssize_t auto_update_interval_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct adt7470_data *data = dev_get_drvdata(dev); + long temp; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = clamp_val(temp, 0, 60000); + + mutex_lock(&data->lock); + data->auto_update_interval = temp; + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t num_temp_sensors_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct adt7470_data *data = adt7470_update_device(dev); + return sprintf(buf, "%d\n", data->num_temp_sensors); +} + +static ssize_t num_temp_sensors_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct adt7470_data *data = dev_get_drvdata(dev); + long temp; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = clamp_val(temp, -1, 10); + + mutex_lock(&data->lock); + data->num_temp_sensors = temp; + if (temp < 0) + data->temperatures_probed = 0; + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_temp_min(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = adt7470_update_device(dev); + return sprintf(buf, "%d\n", 1000 * data->temp_min[attr->index]); +} + +static ssize_t set_temp_min(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = clamp_val(temp, -128000, 127000); + temp = DIV_ROUND_CLOSEST(temp, 1000); + + mutex_lock(&data->lock); + data->temp_min[attr->index] = temp; + i2c_smbus_write_byte_data(client, ADT7470_TEMP_MIN_REG(attr->index), + temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_temp_max(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = adt7470_update_device(dev); + return sprintf(buf, "%d\n", 1000 * data->temp_max[attr->index]); +} + +static ssize_t set_temp_max(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = clamp_val(temp, -128000, 127000); + temp = DIV_ROUND_CLOSEST(temp, 1000); + + mutex_lock(&data->lock); + data->temp_max[attr->index] = temp; + i2c_smbus_write_byte_data(client, ADT7470_TEMP_MAX_REG(attr->index), + temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = adt7470_update_device(dev); + return sprintf(buf, "%d\n", 1000 * data->temp[attr->index]); +} + +static ssize_t alarm_mask_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct adt7470_data *data = adt7470_update_device(dev); + + return sprintf(buf, "%x\n", data->alarms_mask); +} + +static ssize_t alarm_mask_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct adt7470_data *data = dev_get_drvdata(dev); + long mask; + + if (kstrtoul(buf, 0, &mask)) + return -EINVAL; + + if (mask & ~0xffff) + return -EINVAL; + + mutex_lock(&data->lock); + data->alarms_mask = mask; + adt7470_write_word_data(data->client, ADT7470_REG_ALARM1_MASK, mask); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_fan_max(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = adt7470_update_device(dev); + + if (FAN_DATA_VALID(data->fan_max[attr->index])) + return sprintf(buf, "%d\n", + FAN_PERIOD_TO_RPM(data->fan_max[attr->index])); + else + return sprintf(buf, "0\n"); +} + +static ssize_t set_fan_max(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp) || !temp) + return -EINVAL; + + temp = FAN_RPM_TO_PERIOD(temp); + temp = clamp_val(temp, 1, 65534); + + mutex_lock(&data->lock); + data->fan_max[attr->index] = temp; + adt7470_write_word_data(client, ADT7470_REG_FAN_MAX(attr->index), temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_fan_min(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = adt7470_update_device(dev); + + if (FAN_DATA_VALID(data->fan_min[attr->index])) + return sprintf(buf, "%d\n", + FAN_PERIOD_TO_RPM(data->fan_min[attr->index])); + else + return sprintf(buf, "0\n"); +} + +static ssize_t set_fan_min(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp) || !temp) + return -EINVAL; + + temp = FAN_RPM_TO_PERIOD(temp); + temp = clamp_val(temp, 1, 65534); + + mutex_lock(&data->lock); + data->fan_min[attr->index] = temp; + adt7470_write_word_data(client, ADT7470_REG_FAN_MIN(attr->index), temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = adt7470_update_device(dev); + + if (FAN_DATA_VALID(data->fan[attr->index])) + return sprintf(buf, "%d\n", + FAN_PERIOD_TO_RPM(data->fan[attr->index])); + else + return sprintf(buf, "0\n"); +} + +static ssize_t show_force_pwm_max(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct adt7470_data *data = adt7470_update_device(dev); + return sprintf(buf, "%d\n", data->force_pwm_max); +} + +static ssize_t set_force_pwm_max(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct adt7470_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + u8 reg; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + mutex_lock(&data->lock); + data->force_pwm_max = temp; + reg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG); + if (temp) + reg |= ADT7470_FSPD_MASK; + else + reg &= ~ADT7470_FSPD_MASK; + i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, reg); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = adt7470_update_device(dev); + return sprintf(buf, "%d\n", data->pwm[attr->index]); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = clamp_val(temp, 0, 255); + + mutex_lock(&data->lock); + data->pwm[attr->index] = temp; + i2c_smbus_write_byte_data(client, ADT7470_REG_PWM(attr->index), temp); + mutex_unlock(&data->lock); + + return count; +} + +/* These are the valid PWM frequencies to the nearest Hz */ +static const int adt7470_freq_map[] = { + 11, 15, 22, 29, 35, 44, 59, 88, 1400, 22500 +}; + +static ssize_t pwm1_freq_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct adt7470_data *data = adt7470_update_device(dev); + unsigned char cfg_reg_1; + unsigned char cfg_reg_2; + int index; + + mutex_lock(&data->lock); + cfg_reg_1 = i2c_smbus_read_byte_data(data->client, ADT7470_REG_CFG); + cfg_reg_2 = i2c_smbus_read_byte_data(data->client, ADT7470_REG_CFG_2); + mutex_unlock(&data->lock); + + index = (cfg_reg_2 & ADT7470_FREQ_MASK) >> ADT7470_FREQ_SHIFT; + if (!(cfg_reg_1 & ADT7470_CFG_LF)) + index += 8; + if (index >= ARRAY_SIZE(adt7470_freq_map)) + index = ARRAY_SIZE(adt7470_freq_map) - 1; + + return scnprintf(buf, PAGE_SIZE, "%d\n", adt7470_freq_map[index]); +} + +static ssize_t pwm1_freq_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct adt7470_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long freq; + int index; + int low_freq = ADT7470_CFG_LF; + unsigned char val; + + if (kstrtol(buf, 10, &freq)) + return -EINVAL; + + /* Round the user value given to the closest available frequency */ + index = find_closest(freq, adt7470_freq_map, + ARRAY_SIZE(adt7470_freq_map)); + + if (index >= 8) { + index -= 8; + low_freq = 0; + } + + mutex_lock(&data->lock); + /* Configuration Register 1 */ + val = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG); + i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, + (val & ~ADT7470_CFG_LF) | low_freq); + /* Configuration Register 2 */ + val = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG_2); + i2c_smbus_write_byte_data(client, ADT7470_REG_CFG_2, + (val & ~ADT7470_FREQ_MASK) | (index << ADT7470_FREQ_SHIFT)); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_pwm_max(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = adt7470_update_device(dev); + return sprintf(buf, "%d\n", data->pwm_max[attr->index]); +} + +static ssize_t set_pwm_max(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = clamp_val(temp, 0, 255); + + mutex_lock(&data->lock); + data->pwm_max[attr->index] = temp; + i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_MAX(attr->index), + temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_pwm_min(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = adt7470_update_device(dev); + return sprintf(buf, "%d\n", data->pwm_min[attr->index]); +} + +static ssize_t set_pwm_min(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = clamp_val(temp, 0, 255); + + mutex_lock(&data->lock); + data->pwm_min[attr->index] = temp; + i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_MIN(attr->index), + temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_pwm_tmax(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = adt7470_update_device(dev); + /* the datasheet says that tmax = tmin + 20C */ + return sprintf(buf, "%d\n", 1000 * (20 + data->pwm_tmin[attr->index])); +} + +static ssize_t show_pwm_tmin(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = adt7470_update_device(dev); + return sprintf(buf, "%d\n", 1000 * data->pwm_tmin[attr->index]); +} + +static ssize_t set_pwm_tmin(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = clamp_val(temp, -128000, 127000); + temp = DIV_ROUND_CLOSEST(temp, 1000); + + mutex_lock(&data->lock); + data->pwm_tmin[attr->index] = temp; + i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_TMIN(attr->index), + temp); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_pwm_auto(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = adt7470_update_device(dev); + return sprintf(buf, "%d\n", 1 + data->pwm_automatic[attr->index]); +} + +static ssize_t set_pwm_auto(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int pwm_auto_reg = ADT7470_REG_PWM_CFG(attr->index); + int pwm_auto_reg_mask; + long temp; + u8 reg; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + if (attr->index % 2) + pwm_auto_reg_mask = ADT7470_PWM2_AUTO_MASK; + else + pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK; + + if (temp != 2 && temp != 1) + return -EINVAL; + temp--; + + mutex_lock(&data->lock); + data->pwm_automatic[attr->index] = temp; + reg = i2c_smbus_read_byte_data(client, pwm_auto_reg); + if (temp) + reg |= pwm_auto_reg_mask; + else + reg &= ~pwm_auto_reg_mask; + i2c_smbus_write_byte_data(client, pwm_auto_reg, reg); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_pwm_auto_temp(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = adt7470_update_device(dev); + u8 ctrl = data->pwm_auto_temp[attr->index]; + + if (ctrl) + return sprintf(buf, "%d\n", 1 << (ctrl - 1)); + else + return sprintf(buf, "%d\n", ADT7470_PWM_ALL_TEMPS); +} + +static int cvt_auto_temp(int input) +{ + if (input == ADT7470_PWM_ALL_TEMPS) + return 0; + if (input < 1 || !is_power_of_2(input)) + return -EINVAL; + return ilog2(input) + 1; +} + +static ssize_t set_pwm_auto_temp(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int pwm_auto_reg = ADT7470_REG_PWM_AUTO_TEMP(attr->index); + long temp; + u8 reg; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = cvt_auto_temp(temp); + if (temp < 0) + return temp; + + mutex_lock(&data->lock); + data->pwm_automatic[attr->index] = temp; + reg = i2c_smbus_read_byte_data(client, pwm_auto_reg); + + if (!(attr->index % 2)) { + reg &= 0xF; + reg |= (temp << 4) & 0xF0; + } else { + reg &= 0xF0; + reg |= temp & 0xF; + } + + i2c_smbus_write_byte_data(client, pwm_auto_reg, reg); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_alarm(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct adt7470_data *data = adt7470_update_device(dev); + + if (data->alarm & attr->index) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static DEVICE_ATTR_RW(alarm_mask); +static DEVICE_ATTR_RW(num_temp_sensors); +static DEVICE_ATTR_RW(auto_update_interval); + +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 1); +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 2); +static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 3); +static SENSOR_DEVICE_ATTR(temp5_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 4); +static SENSOR_DEVICE_ATTR(temp6_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 5); +static SENSOR_DEVICE_ATTR(temp7_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 6); +static SENSOR_DEVICE_ATTR(temp8_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 7); +static SENSOR_DEVICE_ATTR(temp9_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 8); +static SENSOR_DEVICE_ATTR(temp10_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 9); + +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 0); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 1); +static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 2); +static SENSOR_DEVICE_ATTR(temp4_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 3); +static SENSOR_DEVICE_ATTR(temp5_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 4); +static SENSOR_DEVICE_ATTR(temp6_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 5); +static SENSOR_DEVICE_ATTR(temp7_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 6); +static SENSOR_DEVICE_ATTR(temp8_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 7); +static SENSOR_DEVICE_ATTR(temp9_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 8); +static SENSOR_DEVICE_ATTR(temp10_min, S_IWUSR | S_IRUGO, show_temp_min, + set_temp_min, 9); + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_temp, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_temp, NULL, 7); +static SENSOR_DEVICE_ATTR(temp9_input, S_IRUGO, show_temp, NULL, 8); +static SENSOR_DEVICE_ATTR(temp10_input, S_IRUGO, show_temp, NULL, 9); + +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, + ADT7470_R1T_ALARM); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, + ADT7470_R2T_ALARM); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, + ADT7470_R3T_ALARM); +static SENSOR_DEVICE_ATTR(temp4_alarm, S_IRUGO, show_alarm, NULL, + ADT7470_R4T_ALARM); +static SENSOR_DEVICE_ATTR(temp5_alarm, S_IRUGO, show_alarm, NULL, + ADT7470_R5T_ALARM); +static SENSOR_DEVICE_ATTR(temp6_alarm, S_IRUGO, show_alarm, NULL, + ADT7470_R6T_ALARM); +static SENSOR_DEVICE_ATTR(temp7_alarm, S_IRUGO, show_alarm, NULL, + ADT7470_R7T_ALARM); +static SENSOR_DEVICE_ATTR(temp8_alarm, S_IRUGO, show_alarm, NULL, + ALARM2(ADT7470_R8T_ALARM)); +static SENSOR_DEVICE_ATTR(temp9_alarm, S_IRUGO, show_alarm, NULL, + ALARM2(ADT7470_R9T_ALARM)); +static SENSOR_DEVICE_ATTR(temp10_alarm, S_IRUGO, show_alarm, NULL, + ALARM2(ADT7470_R10T_ALARM)); + +static SENSOR_DEVICE_ATTR(fan1_max, S_IWUSR | S_IRUGO, show_fan_max, + set_fan_max, 0); +static SENSOR_DEVICE_ATTR(fan2_max, S_IWUSR | S_IRUGO, show_fan_max, + set_fan_max, 1); +static SENSOR_DEVICE_ATTR(fan3_max, S_IWUSR | S_IRUGO, show_fan_max, + set_fan_max, 2); +static SENSOR_DEVICE_ATTR(fan4_max, S_IWUSR | S_IRUGO, show_fan_max, + set_fan_max, 3); + +static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min, + set_fan_min, 0); +static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min, + set_fan_min, 1); +static SENSOR_DEVICE_ATTR(fan3_min, S_IWUSR | S_IRUGO, show_fan_min, + set_fan_min, 2); +static SENSOR_DEVICE_ATTR(fan4_min, S_IWUSR | S_IRUGO, show_fan_min, + set_fan_min, 3); + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3); + +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, + ALARM2(ADT7470_FAN1_ALARM)); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, + ALARM2(ADT7470_FAN2_ALARM)); +static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, + ALARM2(ADT7470_FAN3_ALARM)); +static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO, show_alarm, NULL, + ALARM2(ADT7470_FAN4_ALARM)); + +static SENSOR_DEVICE_ATTR(force_pwm_max, S_IWUSR | S_IRUGO, + show_force_pwm_max, set_force_pwm_max, 0); + +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 2); +static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 3); + +static DEVICE_ATTR_RW(pwm1_freq); + +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IWUSR | S_IRUGO, + show_pwm_min, set_pwm_min, 0); +static SENSOR_DEVICE_ATTR(pwm2_auto_point1_pwm, S_IWUSR | S_IRUGO, + show_pwm_min, set_pwm_min, 1); +static SENSOR_DEVICE_ATTR(pwm3_auto_point1_pwm, S_IWUSR | S_IRUGO, + show_pwm_min, set_pwm_min, 2); +static SENSOR_DEVICE_ATTR(pwm4_auto_point1_pwm, S_IWUSR | S_IRUGO, + show_pwm_min, set_pwm_min, 3); + +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO, + show_pwm_max, set_pwm_max, 0); +static SENSOR_DEVICE_ATTR(pwm2_auto_point2_pwm, S_IWUSR | S_IRUGO, + show_pwm_max, set_pwm_max, 1); +static SENSOR_DEVICE_ATTR(pwm3_auto_point2_pwm, S_IWUSR | S_IRUGO, + show_pwm_max, set_pwm_max, 2); +static SENSOR_DEVICE_ATTR(pwm4_auto_point2_pwm, S_IWUSR | S_IRUGO, + show_pwm_max, set_pwm_max, 3); + +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_temp, S_IWUSR | S_IRUGO, + show_pwm_tmin, set_pwm_tmin, 0); +static SENSOR_DEVICE_ATTR(pwm2_auto_point1_temp, S_IWUSR | S_IRUGO, + show_pwm_tmin, set_pwm_tmin, 1); +static SENSOR_DEVICE_ATTR(pwm3_auto_point1_temp, S_IWUSR | S_IRUGO, + show_pwm_tmin, set_pwm_tmin, 2); +static SENSOR_DEVICE_ATTR(pwm4_auto_point1_temp, S_IWUSR | S_IRUGO, + show_pwm_tmin, set_pwm_tmin, 3); + +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_temp, S_IRUGO, show_pwm_tmax, + NULL, 0); +static SENSOR_DEVICE_ATTR(pwm2_auto_point2_temp, S_IRUGO, show_pwm_tmax, + NULL, 1); +static SENSOR_DEVICE_ATTR(pwm3_auto_point2_temp, S_IRUGO, show_pwm_tmax, + NULL, 2); +static SENSOR_DEVICE_ATTR(pwm4_auto_point2_temp, S_IRUGO, show_pwm_tmax, + NULL, 3); + +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, show_pwm_auto, + set_pwm_auto, 0); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, show_pwm_auto, + set_pwm_auto, 1); +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, show_pwm_auto, + set_pwm_auto, 2); +static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO, show_pwm_auto, + set_pwm_auto, 3); + +static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IWUSR | S_IRUGO, + show_pwm_auto_temp, set_pwm_auto_temp, 0); +static SENSOR_DEVICE_ATTR(pwm2_auto_channels_temp, S_IWUSR | S_IRUGO, + show_pwm_auto_temp, set_pwm_auto_temp, 1); +static SENSOR_DEVICE_ATTR(pwm3_auto_channels_temp, S_IWUSR | S_IRUGO, + show_pwm_auto_temp, set_pwm_auto_temp, 2); +static SENSOR_DEVICE_ATTR(pwm4_auto_channels_temp, S_IWUSR | S_IRUGO, + show_pwm_auto_temp, set_pwm_auto_temp, 3); + +static struct attribute *adt7470_attrs[] = { + &dev_attr_alarm_mask.attr, + &dev_attr_num_temp_sensors.attr, + &dev_attr_auto_update_interval.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp6_max.dev_attr.attr, + &sensor_dev_attr_temp7_max.dev_attr.attr, + &sensor_dev_attr_temp8_max.dev_attr.attr, + &sensor_dev_attr_temp9_max.dev_attr.attr, + &sensor_dev_attr_temp10_max.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp5_min.dev_attr.attr, + &sensor_dev_attr_temp6_min.dev_attr.attr, + &sensor_dev_attr_temp7_min.dev_attr.attr, + &sensor_dev_attr_temp8_min.dev_attr.attr, + &sensor_dev_attr_temp9_min.dev_attr.attr, + &sensor_dev_attr_temp10_min.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp6_input.dev_attr.attr, + &sensor_dev_attr_temp7_input.dev_attr.attr, + &sensor_dev_attr_temp8_input.dev_attr.attr, + &sensor_dev_attr_temp9_input.dev_attr.attr, + &sensor_dev_attr_temp10_input.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_alarm.dev_attr.attr, + &sensor_dev_attr_temp6_alarm.dev_attr.attr, + &sensor_dev_attr_temp7_alarm.dev_attr.attr, + &sensor_dev_attr_temp8_alarm.dev_attr.attr, + &sensor_dev_attr_temp9_alarm.dev_attr.attr, + &sensor_dev_attr_temp10_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_max.dev_attr.attr, + &sensor_dev_attr_fan2_max.dev_attr.attr, + &sensor_dev_attr_fan3_max.dev_attr.attr, + &sensor_dev_attr_fan4_max.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan4_min.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_fan4_alarm.dev_attr.attr, + &sensor_dev_attr_force_pwm_max.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &dev_attr_pwm1_freq.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm4.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm4_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm4_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm4_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm4_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_pwm4_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_channels_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_channels_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_channels_temp.dev_attr.attr, + &sensor_dev_attr_pwm4_auto_channels_temp.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(adt7470); + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int adt7470_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int vendor, device, revision; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + vendor = i2c_smbus_read_byte_data(client, ADT7470_REG_VENDOR); + if (vendor != ADT7470_VENDOR) + return -ENODEV; + + device = i2c_smbus_read_byte_data(client, ADT7470_REG_DEVICE); + if (device != ADT7470_DEVICE) + return -ENODEV; + + revision = i2c_smbus_read_byte_data(client, ADT7470_REG_REVISION); + if (revision != ADT7470_REVISION) + return -ENODEV; + + strlcpy(info->type, "adt7470", I2C_NAME_SIZE); + + return 0; +} + +static void adt7470_init_client(struct i2c_client *client) +{ + int reg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG); + + if (reg < 0) { + dev_err(&client->dev, "cannot read configuration register\n"); + } else { + /* start monitoring (and do a self-test) */ + i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, reg | 3); + } +} + +static int adt7470_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct adt7470_data *data; + struct device *hwmon_dev; + + data = devm_kzalloc(dev, sizeof(struct adt7470_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->num_temp_sensors = -1; + data->auto_update_interval = AUTO_UPDATE_INTERVAL; + + i2c_set_clientdata(client, data); + data->client = client; + mutex_init(&data->lock); + + dev_info(&client->dev, "%s chip found\n", client->name); + + /* Initialize the ADT7470 chip */ + adt7470_init_client(client); + + /* Register sysfs hooks */ + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + adt7470_groups); + + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + data->auto_update = kthread_run(adt7470_update_thread, client, "%s", + dev_name(hwmon_dev)); + if (IS_ERR(data->auto_update)) { + return PTR_ERR(data->auto_update); + } + + return 0; +} + +static int adt7470_remove(struct i2c_client *client) +{ + struct adt7470_data *data = i2c_get_clientdata(client); + + kthread_stop(data->auto_update); + return 0; +} + +static const struct i2c_device_id adt7470_id[] = { + { "adt7470", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adt7470_id); + +static struct i2c_driver adt7470_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "adt7470", + }, + .probe = adt7470_probe, + .remove = adt7470_remove, + .id_table = adt7470_id, + .detect = adt7470_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(adt7470_driver); + +MODULE_AUTHOR("Darrick J. Wong "); +MODULE_DESCRIPTION("ADT7470 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/adt7475.c b/drivers/hwmon/adt7475.c new file mode 100644 index 000000000..0a87c5b51 --- /dev/null +++ b/drivers/hwmon/adt7475.c @@ -0,0 +1,1945 @@ +/* + * adt7475 - Thermal sensor driver for the ADT7475 chip and derivatives + * Copyright (C) 2007-2008, Advanced Micro Devices, Inc. + * Copyright (C) 2008 Jordan Crouse + * Copyright (C) 2008 Hans de Goede + * Copyright (C) 2009 Jean Delvare + * + * Derived from the lm83 driver by Jean Delvare + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Indexes for the sysfs hooks */ + +#define INPUT 0 +#define MIN 1 +#define MAX 2 +#define CONTROL 3 +#define OFFSET 3 +#define AUTOMIN 4 +#define THERM 5 +#define HYSTERSIS 6 + +/* + * These are unique identifiers for the sysfs functions - unlike the + * numbers above, these are not also indexes into an array + */ + +#define ALARM 9 +#define FAULT 10 + +/* 7475 Common Registers */ + +#define REG_DEVREV2 0x12 /* ADT7490 only */ + +#define REG_VTT 0x1E /* ADT7490 only */ +#define REG_EXTEND3 0x1F /* ADT7490 only */ + +#define REG_VOLTAGE_BASE 0x20 +#define REG_TEMP_BASE 0x25 +#define REG_TACH_BASE 0x28 +#define REG_PWM_BASE 0x30 +#define REG_PWM_MAX_BASE 0x38 + +#define REG_DEVID 0x3D +#define REG_VENDID 0x3E +#define REG_DEVID2 0x3F + +#define REG_CONFIG1 0x40 + +#define REG_STATUS1 0x41 +#define REG_STATUS2 0x42 + +#define REG_VID 0x43 /* ADT7476 only */ + +#define REG_VOLTAGE_MIN_BASE 0x44 +#define REG_VOLTAGE_MAX_BASE 0x45 + +#define REG_TEMP_MIN_BASE 0x4E +#define REG_TEMP_MAX_BASE 0x4F + +#define REG_TACH_MIN_BASE 0x54 + +#define REG_PWM_CONFIG_BASE 0x5C + +#define REG_TEMP_TRANGE_BASE 0x5F + +#define REG_ENHANCE_ACOUSTICS1 0x62 +#define REG_ENHANCE_ACOUSTICS2 0x63 + +#define REG_PWM_MIN_BASE 0x64 + +#define REG_TEMP_TMIN_BASE 0x67 +#define REG_TEMP_THERM_BASE 0x6A + +#define REG_REMOTE1_HYSTERSIS 0x6D +#define REG_REMOTE2_HYSTERSIS 0x6E + +#define REG_TEMP_OFFSET_BASE 0x70 + +#define REG_CONFIG2 0x73 + +#define REG_EXTEND1 0x76 +#define REG_EXTEND2 0x77 + +#define REG_CONFIG3 0x78 +#define REG_CONFIG5 0x7C +#define REG_CONFIG4 0x7D + +#define REG_STATUS4 0x81 /* ADT7490 only */ + +#define REG_VTT_MIN 0x84 /* ADT7490 only */ +#define REG_VTT_MAX 0x86 /* ADT7490 only */ + +#define VID_VIDSEL 0x80 /* ADT7476 only */ + +#define CONFIG2_ATTN 0x20 + +#define CONFIG3_SMBALERT 0x01 +#define CONFIG3_THERM 0x02 + +#define CONFIG4_PINFUNC 0x03 +#define CONFIG4_MAXDUTY 0x08 +#define CONFIG4_ATTN_IN10 0x30 +#define CONFIG4_ATTN_IN43 0xC0 + +#define CONFIG5_TWOSCOMP 0x01 +#define CONFIG5_TEMPOFFSET 0x02 +#define CONFIG5_VIDGPIO 0x10 /* ADT7476 only */ + +/* ADT7475 Settings */ + +#define ADT7475_VOLTAGE_COUNT 5 /* Not counting Vtt */ +#define ADT7475_TEMP_COUNT 3 +#define ADT7475_TACH_COUNT 4 +#define ADT7475_PWM_COUNT 3 + +/* Macro to read the registers */ + +#define adt7475_read(reg) i2c_smbus_read_byte_data(client, (reg)) + +/* Macros to easily index the registers */ + +#define TACH_REG(idx) (REG_TACH_BASE + ((idx) * 2)) +#define TACH_MIN_REG(idx) (REG_TACH_MIN_BASE + ((idx) * 2)) + +#define PWM_REG(idx) (REG_PWM_BASE + (idx)) +#define PWM_MAX_REG(idx) (REG_PWM_MAX_BASE + (idx)) +#define PWM_MIN_REG(idx) (REG_PWM_MIN_BASE + (idx)) +#define PWM_CONFIG_REG(idx) (REG_PWM_CONFIG_BASE + (idx)) + +#define VOLTAGE_REG(idx) (REG_VOLTAGE_BASE + (idx)) +#define VOLTAGE_MIN_REG(idx) (REG_VOLTAGE_MIN_BASE + ((idx) * 2)) +#define VOLTAGE_MAX_REG(idx) (REG_VOLTAGE_MAX_BASE + ((idx) * 2)) + +#define TEMP_REG(idx) (REG_TEMP_BASE + (idx)) +#define TEMP_MIN_REG(idx) (REG_TEMP_MIN_BASE + ((idx) * 2)) +#define TEMP_MAX_REG(idx) (REG_TEMP_MAX_BASE + ((idx) * 2)) +#define TEMP_TMIN_REG(idx) (REG_TEMP_TMIN_BASE + (idx)) +#define TEMP_THERM_REG(idx) (REG_TEMP_THERM_BASE + (idx)) +#define TEMP_OFFSET_REG(idx) (REG_TEMP_OFFSET_BASE + (idx)) +#define TEMP_TRANGE_REG(idx) (REG_TEMP_TRANGE_BASE + (idx)) + +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END }; + +enum chips { adt7473, adt7475, adt7476, adt7490 }; + +static const struct i2c_device_id adt7475_id[] = { + { "adt7473", adt7473 }, + { "adt7475", adt7475 }, + { "adt7476", adt7476 }, + { "adt7490", adt7490 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adt7475_id); + +static const struct of_device_id adt7475_of_match[] = { + { + .compatible = "adi,adt7473", + .data = (void *)adt7473 + }, + { + .compatible = "adi,adt7475", + .data = (void *)adt7475 + }, + { + .compatible = "adi,adt7476", + .data = (void *)adt7476 + }, + { + .compatible = "adi,adt7490", + .data = (void *)adt7490 + }, + { }, +}; +MODULE_DEVICE_TABLE(of, adt7475_of_match); + +struct adt7475_data { + struct device *hwmon_dev; + struct mutex lock; + + unsigned long measure_updated; + bool valid; + + u8 config4; + u8 config5; + u8 has_voltage; + u8 bypass_attn; /* Bypass voltage attenuator */ + u8 has_pwm2:1; + u8 has_fan4:1; + u8 has_vid:1; + u32 alarms; + u16 voltage[3][6]; + u16 temp[7][3]; + u16 tach[2][4]; + u8 pwm[4][3]; + u8 range[3]; + u8 pwmctl[3]; + u8 pwmchan[3]; + u8 enh_acoustics[2]; + + u8 vid; + u8 vrm; +}; + +static struct i2c_driver adt7475_driver; +static struct adt7475_data *adt7475_update_device(struct device *dev); +static void adt7475_read_hystersis(struct i2c_client *client); +static void adt7475_read_pwm(struct i2c_client *client, int index); + +/* Given a temp value, convert it to register value */ + +static inline u16 temp2reg(struct adt7475_data *data, long val) +{ + u16 ret; + + if (!(data->config5 & CONFIG5_TWOSCOMP)) { + val = clamp_val(val, -64000, 191000); + ret = (val + 64500) / 1000; + } else { + val = clamp_val(val, -128000, 127000); + if (val < -500) + ret = (256500 + val) / 1000; + else + ret = (val + 500) / 1000; + } + + return ret << 2; +} + +/* Given a register value, convert it to a real temp value */ + +static inline int reg2temp(struct adt7475_data *data, u16 reg) +{ + if (data->config5 & CONFIG5_TWOSCOMP) { + if (reg >= 512) + return (reg - 1024) * 250; + else + return reg * 250; + } else + return (reg - 256) * 250; +} + +static inline int tach2rpm(u16 tach) +{ + if (tach == 0 || tach == 0xFFFF) + return 0; + + return (90000 * 60) / tach; +} + +static inline u16 rpm2tach(unsigned long rpm) +{ + if (rpm == 0) + return 0; + + return clamp_val((90000 * 60) / rpm, 1, 0xFFFF); +} + +/* Scaling factors for voltage inputs, taken from the ADT7490 datasheet */ +static const int adt7473_in_scaling[ADT7475_VOLTAGE_COUNT + 1][2] = { + { 45, 94 }, /* +2.5V */ + { 175, 525 }, /* Vccp */ + { 68, 71 }, /* Vcc */ + { 93, 47 }, /* +5V */ + { 120, 20 }, /* +12V */ + { 45, 45 }, /* Vtt */ +}; + +static inline int reg2volt(int channel, u16 reg, u8 bypass_attn) +{ + const int *r = adt7473_in_scaling[channel]; + + if (bypass_attn & (1 << channel)) + return DIV_ROUND_CLOSEST(reg * 2250, 1024); + return DIV_ROUND_CLOSEST(reg * (r[0] + r[1]) * 2250, r[1] * 1024); +} + +static inline u16 volt2reg(int channel, long volt, u8 bypass_attn) +{ + const int *r = adt7473_in_scaling[channel]; + long reg; + + if (bypass_attn & (1 << channel)) + reg = DIV_ROUND_CLOSEST(volt * 1024, 2250); + else + reg = DIV_ROUND_CLOSEST(volt * r[1] * 1024, + (r[0] + r[1]) * 2250); + return clamp_val(reg, 0, 1023) & (0xff << 2); +} + +static int adt7475_read_word(struct i2c_client *client, int reg) +{ + int val1, val2; + + val1 = i2c_smbus_read_byte_data(client, reg); + if (val1 < 0) + return val1; + val2 = i2c_smbus_read_byte_data(client, reg + 1); + if (val2 < 0) + return val2; + + return val1 | (val2 << 8); +} + +static void adt7475_write_word(struct i2c_client *client, int reg, u16 val) +{ + i2c_smbus_write_byte_data(client, reg + 1, val >> 8); + i2c_smbus_write_byte_data(client, reg, val & 0xFF); +} + +static ssize_t show_voltage(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adt7475_data *data = adt7475_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + unsigned short val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + switch (sattr->nr) { + case ALARM: + return sprintf(buf, "%d\n", + (data->alarms >> sattr->index) & 1); + default: + val = data->voltage[sattr->nr][sattr->index]; + return sprintf(buf, "%d\n", + reg2volt(sattr->index, val, data->bypass_attn)); + } +} + +static ssize_t set_voltage(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7475_data *data = i2c_get_clientdata(client); + unsigned char reg; + long val; + + if (kstrtol(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&data->lock); + + data->voltage[sattr->nr][sattr->index] = + volt2reg(sattr->index, val, data->bypass_attn); + + if (sattr->index < ADT7475_VOLTAGE_COUNT) { + if (sattr->nr == MIN) + reg = VOLTAGE_MIN_REG(sattr->index); + else + reg = VOLTAGE_MAX_REG(sattr->index); + } else { + if (sattr->nr == MIN) + reg = REG_VTT_MIN; + else + reg = REG_VTT_MAX; + } + + i2c_smbus_write_byte_data(client, reg, + data->voltage[sattr->nr][sattr->index] >> 2); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adt7475_data *data = adt7475_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int out; + + if (IS_ERR(data)) + return PTR_ERR(data); + + switch (sattr->nr) { + case HYSTERSIS: + mutex_lock(&data->lock); + out = data->temp[sattr->nr][sattr->index]; + if (sattr->index != 1) + out = (out >> 4) & 0xF; + else + out = (out & 0xF); + /* + * Show the value as an absolute number tied to + * THERM + */ + out = reg2temp(data, data->temp[THERM][sattr->index]) - + out * 1000; + mutex_unlock(&data->lock); + break; + + case OFFSET: + /* + * Offset is always 2's complement, regardless of the + * setting in CONFIG5 + */ + mutex_lock(&data->lock); + out = (s8)data->temp[sattr->nr][sattr->index]; + if (data->config5 & CONFIG5_TEMPOFFSET) + out *= 1000; + else + out *= 500; + mutex_unlock(&data->lock); + break; + + case ALARM: + out = (data->alarms >> (sattr->index + 4)) & 1; + break; + + case FAULT: + /* Note - only for remote1 and remote2 */ + out = !!(data->alarms & (sattr->index ? 0x8000 : 0x4000)); + break; + + default: + /* All other temp values are in the configured format */ + out = reg2temp(data, data->temp[sattr->nr][sattr->index]); + } + + return sprintf(buf, "%d\n", out); +} + +static ssize_t set_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7475_data *data = i2c_get_clientdata(client); + unsigned char reg = 0; + u8 out; + int temp; + long val; + + if (kstrtol(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&data->lock); + + /* We need the config register in all cases for temp <-> reg conv. */ + data->config5 = adt7475_read(REG_CONFIG5); + + switch (sattr->nr) { + case OFFSET: + if (data->config5 & CONFIG5_TEMPOFFSET) { + val = clamp_val(val, -63000, 127000); + out = data->temp[OFFSET][sattr->index] = val / 1000; + } else { + val = clamp_val(val, -63000, 64000); + out = data->temp[OFFSET][sattr->index] = val / 500; + } + break; + + case HYSTERSIS: + /* + * The value will be given as an absolute value, turn it + * into an offset based on THERM + */ + + /* Read fresh THERM and HYSTERSIS values from the chip */ + data->temp[THERM][sattr->index] = + adt7475_read(TEMP_THERM_REG(sattr->index)) << 2; + adt7475_read_hystersis(client); + + temp = reg2temp(data, data->temp[THERM][sattr->index]); + val = clamp_val(val, temp - 15000, temp); + val = (temp - val) / 1000; + + if (sattr->index != 1) { + data->temp[HYSTERSIS][sattr->index] &= 0xF0; + data->temp[HYSTERSIS][sattr->index] |= (val & 0xF) << 4; + } else { + data->temp[HYSTERSIS][sattr->index] &= 0x0F; + data->temp[HYSTERSIS][sattr->index] |= (val & 0xF); + } + + out = data->temp[HYSTERSIS][sattr->index]; + break; + + default: + data->temp[sattr->nr][sattr->index] = temp2reg(data, val); + + /* + * We maintain an extra 2 digits of precision for simplicity + * - shift those back off before writing the value + */ + out = (u8) (data->temp[sattr->nr][sattr->index] >> 2); + } + + switch (sattr->nr) { + case MIN: + reg = TEMP_MIN_REG(sattr->index); + break; + case MAX: + reg = TEMP_MAX_REG(sattr->index); + break; + case OFFSET: + reg = TEMP_OFFSET_REG(sattr->index); + break; + case AUTOMIN: + reg = TEMP_TMIN_REG(sattr->index); + break; + case THERM: + reg = TEMP_THERM_REG(sattr->index); + break; + case HYSTERSIS: + if (sattr->index != 2) + reg = REG_REMOTE1_HYSTERSIS; + else + reg = REG_REMOTE2_HYSTERSIS; + + break; + } + + i2c_smbus_write_byte_data(client, reg, out); + + mutex_unlock(&data->lock); + return count; +} + +/* Assuming CONFIG6[SLOW] is 0 */ +static const int ad7475_st_map[] = { + 37500, 18800, 12500, 7500, 4700, 3100, 1600, 800, +}; + +static ssize_t show_temp_st(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7475_data *data = i2c_get_clientdata(client); + long val; + + switch (sattr->index) { + case 0: + val = data->enh_acoustics[0] & 0xf; + break; + case 1: + val = (data->enh_acoustics[1] >> 4) & 0xf; + break; + case 2: + default: + val = data->enh_acoustics[1] & 0xf; + break; + } + + if (val & 0x8) + return sprintf(buf, "%d\n", ad7475_st_map[val & 0x7]); + else + return sprintf(buf, "0\n"); +} + +static ssize_t set_temp_st(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7475_data *data = i2c_get_clientdata(client); + unsigned char reg; + int shift, idx; + ulong val; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + switch (sattr->index) { + case 0: + reg = REG_ENHANCE_ACOUSTICS1; + shift = 0; + idx = 0; + break; + case 1: + reg = REG_ENHANCE_ACOUSTICS2; + shift = 0; + idx = 1; + break; + case 2: + default: + reg = REG_ENHANCE_ACOUSTICS2; + shift = 4; + idx = 1; + break; + } + + if (val > 0) { + val = find_closest_descending(val, ad7475_st_map, + ARRAY_SIZE(ad7475_st_map)); + val |= 0x8; + } + + mutex_lock(&data->lock); + + data->enh_acoustics[idx] &= ~(0xf << shift); + data->enh_acoustics[idx] |= (val << shift); + + i2c_smbus_write_byte_data(client, reg, data->enh_acoustics[idx]); + + mutex_unlock(&data->lock); + + return count; +} + +/* + * Table of autorange values - the user will write the value in millidegrees, + * and we'll convert it + */ +static const int autorange_table[] = { + 2000, 2500, 3330, 4000, 5000, 6670, 8000, + 10000, 13330, 16000, 20000, 26670, 32000, 40000, + 53330, 80000 +}; + +static ssize_t show_point2(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adt7475_data *data = adt7475_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int out, val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->lock); + out = (data->range[sattr->index] >> 4) & 0x0F; + val = reg2temp(data, data->temp[AUTOMIN][sattr->index]); + mutex_unlock(&data->lock); + + return sprintf(buf, "%d\n", val + autorange_table[out]); +} + +static ssize_t set_point2(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct adt7475_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int temp; + long val; + + if (kstrtol(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&data->lock); + + /* Get a fresh copy of the needed registers */ + data->config5 = adt7475_read(REG_CONFIG5); + data->temp[AUTOMIN][sattr->index] = + adt7475_read(TEMP_TMIN_REG(sattr->index)) << 2; + data->range[sattr->index] = + adt7475_read(TEMP_TRANGE_REG(sattr->index)); + + /* + * The user will write an absolute value, so subtract the start point + * to figure the range + */ + temp = reg2temp(data, data->temp[AUTOMIN][sattr->index]); + val = clamp_val(val, temp + autorange_table[0], + temp + autorange_table[ARRAY_SIZE(autorange_table) - 1]); + val -= temp; + + /* Find the nearest table entry to what the user wrote */ + val = find_closest(val, autorange_table, ARRAY_SIZE(autorange_table)); + + data->range[sattr->index] &= ~0xF0; + data->range[sattr->index] |= val << 4; + + i2c_smbus_write_byte_data(client, TEMP_TRANGE_REG(sattr->index), + data->range[sattr->index]); + + mutex_unlock(&data->lock); + return count; +} + +static ssize_t show_tach(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adt7475_data *data = adt7475_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int out; + + if (IS_ERR(data)) + return PTR_ERR(data); + + if (sattr->nr == ALARM) + out = (data->alarms >> (sattr->index + 10)) & 1; + else + out = tach2rpm(data->tach[sattr->nr][sattr->index]); + + return sprintf(buf, "%d\n", out); +} + +static ssize_t set_tach(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7475_data *data = i2c_get_clientdata(client); + unsigned long val; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&data->lock); + + data->tach[MIN][sattr->index] = rpm2tach(val); + + adt7475_write_word(client, TACH_MIN_REG(sattr->index), + data->tach[MIN][sattr->index]); + + mutex_unlock(&data->lock); + return count; +} + +static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adt7475_data *data = adt7475_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->pwm[sattr->nr][sattr->index]); +} + +static ssize_t show_pwmchan(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adt7475_data *data = adt7475_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->pwmchan[sattr->index]); +} + +static ssize_t show_pwmctrl(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adt7475_data *data = adt7475_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->pwmctl[sattr->index]); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7475_data *data = i2c_get_clientdata(client); + unsigned char reg = 0; + long val; + + if (kstrtol(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&data->lock); + + switch (sattr->nr) { + case INPUT: + /* Get a fresh value for CONTROL */ + data->pwm[CONTROL][sattr->index] = + adt7475_read(PWM_CONFIG_REG(sattr->index)); + + /* + * If we are not in manual mode, then we shouldn't allow + * the user to set the pwm speed + */ + if (((data->pwm[CONTROL][sattr->index] >> 5) & 7) != 7) { + mutex_unlock(&data->lock); + return count; + } + + reg = PWM_REG(sattr->index); + break; + + case MIN: + reg = PWM_MIN_REG(sattr->index); + break; + + case MAX: + reg = PWM_MAX_REG(sattr->index); + break; + } + + data->pwm[sattr->nr][sattr->index] = clamp_val(val, 0, 0xFF); + i2c_smbus_write_byte_data(client, reg, + data->pwm[sattr->nr][sattr->index]); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_stall_disable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7475_data *data = i2c_get_clientdata(client); + u8 mask = BIT(5 + sattr->index); + + return sprintf(buf, "%d\n", !!(data->enh_acoustics[0] & mask)); +} + +static ssize_t set_stall_disable(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7475_data *data = i2c_get_clientdata(client); + long val; + u8 mask = BIT(5 + sattr->index); + + if (kstrtol(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&data->lock); + + data->enh_acoustics[0] &= ~mask; + if (val) + data->enh_acoustics[0] |= mask; + + i2c_smbus_write_byte_data(client, REG_ENHANCE_ACOUSTICS1, + data->enh_acoustics[0]); + + mutex_unlock(&data->lock); + + return count; +} + +/* Called by set_pwmctrl and set_pwmchan */ + +static int hw_set_pwm(struct i2c_client *client, int index, + unsigned int pwmctl, unsigned int pwmchan) +{ + struct adt7475_data *data = i2c_get_clientdata(client); + long val = 0; + + switch (pwmctl) { + case 0: + val = 0x03; /* Run at full speed */ + break; + case 1: + val = 0x07; /* Manual mode */ + break; + case 2: + switch (pwmchan) { + case 1: + /* Remote1 controls PWM */ + val = 0x00; + break; + case 2: + /* local controls PWM */ + val = 0x01; + break; + case 4: + /* remote2 controls PWM */ + val = 0x02; + break; + case 6: + /* local/remote2 control PWM */ + val = 0x05; + break; + case 7: + /* All three control PWM */ + val = 0x06; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + data->pwmctl[index] = pwmctl; + data->pwmchan[index] = pwmchan; + + data->pwm[CONTROL][index] &= ~0xE0; + data->pwm[CONTROL][index] |= (val & 7) << 5; + + i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(index), + data->pwm[CONTROL][index]); + + return 0; +} + +static ssize_t set_pwmchan(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7475_data *data = i2c_get_clientdata(client); + int r; + long val; + + if (kstrtol(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&data->lock); + /* Read Modify Write PWM values */ + adt7475_read_pwm(client, sattr->index); + r = hw_set_pwm(client, sattr->index, data->pwmctl[sattr->index], val); + if (r) + count = r; + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_pwmctrl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7475_data *data = i2c_get_clientdata(client); + int r; + long val; + + if (kstrtol(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&data->lock); + /* Read Modify Write PWM values */ + adt7475_read_pwm(client, sattr->index); + r = hw_set_pwm(client, sattr->index, val, data->pwmchan[sattr->index]); + if (r) + count = r; + mutex_unlock(&data->lock); + + return count; +} + +/* List of frequencies for the PWM */ +static const int pwmfreq_table[] = { + 11, 14, 22, 29, 35, 44, 58, 88, 22500 +}; + +static ssize_t show_pwmfreq(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adt7475_data *data = adt7475_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int idx; + + if (IS_ERR(data)) + return PTR_ERR(data); + idx = clamp_val(data->range[sattr->index] & 0xf, 0, + ARRAY_SIZE(pwmfreq_table) - 1); + + return sprintf(buf, "%d\n", pwmfreq_table[idx]); +} + +static ssize_t set_pwmfreq(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7475_data *data = i2c_get_clientdata(client); + int out; + long val; + + if (kstrtol(buf, 10, &val)) + return -EINVAL; + + out = find_closest(val, pwmfreq_table, ARRAY_SIZE(pwmfreq_table)); + + mutex_lock(&data->lock); + + data->range[sattr->index] = + adt7475_read(TEMP_TRANGE_REG(sattr->index)); + data->range[sattr->index] &= ~0xf; + data->range[sattr->index] |= out; + + i2c_smbus_write_byte_data(client, TEMP_TRANGE_REG(sattr->index), + data->range[sattr->index]); + + mutex_unlock(&data->lock); + return count; +} + +static ssize_t pwm_use_point2_pwm_at_crit_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct adt7475_data *data = adt7475_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", !!(data->config4 & CONFIG4_MAXDUTY)); +} + +static ssize_t pwm_use_point2_pwm_at_crit_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct adt7475_data *data = i2c_get_clientdata(client); + long val; + + if (kstrtol(buf, 10, &val)) + return -EINVAL; + if (val != 0 && val != 1) + return -EINVAL; + + mutex_lock(&data->lock); + data->config4 = i2c_smbus_read_byte_data(client, REG_CONFIG4); + if (val) + data->config4 |= CONFIG4_MAXDUTY; + else + data->config4 &= ~CONFIG4_MAXDUTY; + i2c_smbus_write_byte_data(client, REG_CONFIG4, data->config4); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t vrm_show(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct adt7475_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", (int)data->vrm); +} + +static ssize_t vrm_store(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct adt7475_data *data = dev_get_drvdata(dev); + long val; + + if (kstrtol(buf, 10, &val)) + return -EINVAL; + if (val < 0 || val > 255) + return -EINVAL; + data->vrm = val; + + return count; +} + +static ssize_t cpu0_vid_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct adt7475_data *data = adt7475_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); +} + +static SENSOR_DEVICE_ATTR_2(in0_input, S_IRUGO, show_voltage, NULL, INPUT, 0); +static SENSOR_DEVICE_ATTR_2(in0_max, S_IRUGO | S_IWUSR, show_voltage, + set_voltage, MAX, 0); +static SENSOR_DEVICE_ATTR_2(in0_min, S_IRUGO | S_IWUSR, show_voltage, + set_voltage, MIN, 0); +static SENSOR_DEVICE_ATTR_2(in0_alarm, S_IRUGO, show_voltage, NULL, ALARM, 0); +static SENSOR_DEVICE_ATTR_2(in1_input, S_IRUGO, show_voltage, NULL, INPUT, 1); +static SENSOR_DEVICE_ATTR_2(in1_max, S_IRUGO | S_IWUSR, show_voltage, + set_voltage, MAX, 1); +static SENSOR_DEVICE_ATTR_2(in1_min, S_IRUGO | S_IWUSR, show_voltage, + set_voltage, MIN, 1); +static SENSOR_DEVICE_ATTR_2(in1_alarm, S_IRUGO, show_voltage, NULL, ALARM, 1); +static SENSOR_DEVICE_ATTR_2(in2_input, S_IRUGO, show_voltage, NULL, INPUT, 2); +static SENSOR_DEVICE_ATTR_2(in2_max, S_IRUGO | S_IWUSR, show_voltage, + set_voltage, MAX, 2); +static SENSOR_DEVICE_ATTR_2(in2_min, S_IRUGO | S_IWUSR, show_voltage, + set_voltage, MIN, 2); +static SENSOR_DEVICE_ATTR_2(in2_alarm, S_IRUGO, show_voltage, NULL, ALARM, 2); +static SENSOR_DEVICE_ATTR_2(in3_input, S_IRUGO, show_voltage, NULL, INPUT, 3); +static SENSOR_DEVICE_ATTR_2(in3_max, S_IRUGO | S_IWUSR, show_voltage, + set_voltage, MAX, 3); +static SENSOR_DEVICE_ATTR_2(in3_min, S_IRUGO | S_IWUSR, show_voltage, + set_voltage, MIN, 3); +static SENSOR_DEVICE_ATTR_2(in3_alarm, S_IRUGO, show_voltage, NULL, ALARM, 3); +static SENSOR_DEVICE_ATTR_2(in4_input, S_IRUGO, show_voltage, NULL, INPUT, 4); +static SENSOR_DEVICE_ATTR_2(in4_max, S_IRUGO | S_IWUSR, show_voltage, + set_voltage, MAX, 4); +static SENSOR_DEVICE_ATTR_2(in4_min, S_IRUGO | S_IWUSR, show_voltage, + set_voltage, MIN, 4); +static SENSOR_DEVICE_ATTR_2(in4_alarm, S_IRUGO, show_voltage, NULL, ALARM, 8); +static SENSOR_DEVICE_ATTR_2(in5_input, S_IRUGO, show_voltage, NULL, INPUT, 5); +static SENSOR_DEVICE_ATTR_2(in5_max, S_IRUGO | S_IWUSR, show_voltage, + set_voltage, MAX, 5); +static SENSOR_DEVICE_ATTR_2(in5_min, S_IRUGO | S_IWUSR, show_voltage, + set_voltage, MIN, 5); +static SENSOR_DEVICE_ATTR_2(in5_alarm, S_IRUGO, show_voltage, NULL, ALARM, 31); +static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, INPUT, 0); +static SENSOR_DEVICE_ATTR_2(temp1_alarm, S_IRUGO, show_temp, NULL, ALARM, 0); +static SENSOR_DEVICE_ATTR_2(temp1_fault, S_IRUGO, show_temp, NULL, FAULT, 0); +static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + MAX, 0); +static SENSOR_DEVICE_ATTR_2(temp1_min, S_IRUGO | S_IWUSR, show_temp, set_temp, + MIN, 0); +static SENSOR_DEVICE_ATTR_2(temp1_offset, S_IRUGO | S_IWUSR, show_temp, + set_temp, OFFSET, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_point1_temp, S_IRUGO | S_IWUSR, + show_temp, set_temp, AUTOMIN, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_point2_temp, S_IRUGO | S_IWUSR, + show_point2, set_point2, 0, 0); +static SENSOR_DEVICE_ATTR_2(temp1_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + THERM, 0); +static SENSOR_DEVICE_ATTR_2(temp1_crit_hyst, S_IRUGO | S_IWUSR, show_temp, + set_temp, HYSTERSIS, 0); +static SENSOR_DEVICE_ATTR_2(temp1_smoothing, S_IRUGO | S_IWUSR, show_temp_st, + set_temp_st, 0, 0); +static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, INPUT, 1); +static SENSOR_DEVICE_ATTR_2(temp2_alarm, S_IRUGO, show_temp, NULL, ALARM, 1); +static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + MAX, 1); +static SENSOR_DEVICE_ATTR_2(temp2_min, S_IRUGO | S_IWUSR, show_temp, set_temp, + MIN, 1); +static SENSOR_DEVICE_ATTR_2(temp2_offset, S_IRUGO | S_IWUSR, show_temp, + set_temp, OFFSET, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_point1_temp, S_IRUGO | S_IWUSR, + show_temp, set_temp, AUTOMIN, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_point2_temp, S_IRUGO | S_IWUSR, + show_point2, set_point2, 0, 1); +static SENSOR_DEVICE_ATTR_2(temp2_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + THERM, 1); +static SENSOR_DEVICE_ATTR_2(temp2_crit_hyst, S_IRUGO | S_IWUSR, show_temp, + set_temp, HYSTERSIS, 1); +static SENSOR_DEVICE_ATTR_2(temp2_smoothing, S_IRUGO | S_IWUSR, show_temp_st, + set_temp_st, 0, 1); +static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, INPUT, 2); +static SENSOR_DEVICE_ATTR_2(temp3_alarm, S_IRUGO, show_temp, NULL, ALARM, 2); +static SENSOR_DEVICE_ATTR_2(temp3_fault, S_IRUGO, show_temp, NULL, FAULT, 2); +static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + MAX, 2); +static SENSOR_DEVICE_ATTR_2(temp3_min, S_IRUGO | S_IWUSR, show_temp, set_temp, + MIN, 2); +static SENSOR_DEVICE_ATTR_2(temp3_offset, S_IRUGO | S_IWUSR, show_temp, + set_temp, OFFSET, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_point1_temp, S_IRUGO | S_IWUSR, + show_temp, set_temp, AUTOMIN, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_point2_temp, S_IRUGO | S_IWUSR, + show_point2, set_point2, 0, 2); +static SENSOR_DEVICE_ATTR_2(temp3_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + THERM, 2); +static SENSOR_DEVICE_ATTR_2(temp3_crit_hyst, S_IRUGO | S_IWUSR, show_temp, + set_temp, HYSTERSIS, 2); +static SENSOR_DEVICE_ATTR_2(temp3_smoothing, S_IRUGO | S_IWUSR, show_temp_st, + set_temp_st, 0, 2); +static SENSOR_DEVICE_ATTR_2(fan1_input, S_IRUGO, show_tach, NULL, INPUT, 0); +static SENSOR_DEVICE_ATTR_2(fan1_min, S_IRUGO | S_IWUSR, show_tach, set_tach, + MIN, 0); +static SENSOR_DEVICE_ATTR_2(fan1_alarm, S_IRUGO, show_tach, NULL, ALARM, 0); +static SENSOR_DEVICE_ATTR_2(fan2_input, S_IRUGO, show_tach, NULL, INPUT, 1); +static SENSOR_DEVICE_ATTR_2(fan2_min, S_IRUGO | S_IWUSR, show_tach, set_tach, + MIN, 1); +static SENSOR_DEVICE_ATTR_2(fan2_alarm, S_IRUGO, show_tach, NULL, ALARM, 1); +static SENSOR_DEVICE_ATTR_2(fan3_input, S_IRUGO, show_tach, NULL, INPUT, 2); +static SENSOR_DEVICE_ATTR_2(fan3_min, S_IRUGO | S_IWUSR, show_tach, set_tach, + MIN, 2); +static SENSOR_DEVICE_ATTR_2(fan3_alarm, S_IRUGO, show_tach, NULL, ALARM, 2); +static SENSOR_DEVICE_ATTR_2(fan4_input, S_IRUGO, show_tach, NULL, INPUT, 3); +static SENSOR_DEVICE_ATTR_2(fan4_min, S_IRUGO | S_IWUSR, show_tach, set_tach, + MIN, 3); +static SENSOR_DEVICE_ATTR_2(fan4_alarm, S_IRUGO, show_tach, NULL, ALARM, 3); +static SENSOR_DEVICE_ATTR_2(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm, INPUT, + 0); +static SENSOR_DEVICE_ATTR_2(pwm1_freq, S_IRUGO | S_IWUSR, show_pwmfreq, + set_pwmfreq, INPUT, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_enable, S_IRUGO | S_IWUSR, show_pwmctrl, + set_pwmctrl, INPUT, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_channels_temp, S_IRUGO | S_IWUSR, + show_pwmchan, set_pwmchan, INPUT, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point1_pwm, S_IRUGO | S_IWUSR, show_pwm, + set_pwm, MIN, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point2_pwm, S_IRUGO | S_IWUSR, show_pwm, + set_pwm, MAX, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_stall_disable, S_IRUGO | S_IWUSR, + show_stall_disable, set_stall_disable, 0, 0); +static SENSOR_DEVICE_ATTR_2(pwm2, S_IRUGO | S_IWUSR, show_pwm, set_pwm, INPUT, + 1); +static SENSOR_DEVICE_ATTR_2(pwm2_freq, S_IRUGO | S_IWUSR, show_pwmfreq, + set_pwmfreq, INPUT, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_enable, S_IRUGO | S_IWUSR, show_pwmctrl, + set_pwmctrl, INPUT, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_channels_temp, S_IRUGO | S_IWUSR, + show_pwmchan, set_pwmchan, INPUT, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point1_pwm, S_IRUGO | S_IWUSR, show_pwm, + set_pwm, MIN, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point2_pwm, S_IRUGO | S_IWUSR, show_pwm, + set_pwm, MAX, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_stall_disable, S_IRUGO | S_IWUSR, + show_stall_disable, set_stall_disable, 0, 1); +static SENSOR_DEVICE_ATTR_2(pwm3, S_IRUGO | S_IWUSR, show_pwm, set_pwm, INPUT, + 2); +static SENSOR_DEVICE_ATTR_2(pwm3_freq, S_IRUGO | S_IWUSR, show_pwmfreq, + set_pwmfreq, INPUT, 2); +static SENSOR_DEVICE_ATTR_2(pwm3_enable, S_IRUGO | S_IWUSR, show_pwmctrl, + set_pwmctrl, INPUT, 2); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_channels_temp, S_IRUGO | S_IWUSR, + show_pwmchan, set_pwmchan, INPUT, 2); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point1_pwm, S_IRUGO | S_IWUSR, show_pwm, + set_pwm, MIN, 2); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point2_pwm, S_IRUGO | S_IWUSR, show_pwm, + set_pwm, MAX, 2); +static SENSOR_DEVICE_ATTR_2(pwm3_stall_disable, S_IRUGO | S_IWUSR, + show_stall_disable, set_stall_disable, 0, 2); + +/* Non-standard name, might need revisiting */ +static DEVICE_ATTR_RW(pwm_use_point2_pwm_at_crit); + +static DEVICE_ATTR_RW(vrm); +static DEVICE_ATTR_RO(cpu0_vid); + +static struct attribute *adt7475_attrs[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_offset.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_smoothing.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_offset.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_smoothing.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_offset.dev_attr.attr, + &sensor_dev_attr_temp3_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_temp3_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp3_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_smoothing.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_freq.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_channels_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_stall_disable.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm3_freq.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_channels_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_stall_disable.dev_attr.attr, + &dev_attr_pwm_use_point2_pwm_at_crit.attr, + NULL, +}; + +static struct attribute *fan4_attrs[] = { + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan4_min.dev_attr.attr, + &sensor_dev_attr_fan4_alarm.dev_attr.attr, + NULL +}; + +static struct attribute *pwm2_attrs[] = { + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm2_freq.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_channels_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_stall_disable.dev_attr.attr, + NULL +}; + +static struct attribute *in0_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + NULL +}; + +static struct attribute *in3_attrs[] = { + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + NULL +}; + +static struct attribute *in4_attrs[] = { + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + NULL +}; + +static struct attribute *in5_attrs[] = { + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + NULL +}; + +static struct attribute *vid_attrs[] = { + &dev_attr_cpu0_vid.attr, + &dev_attr_vrm.attr, + NULL +}; + +static const struct attribute_group adt7475_attr_group = { .attrs = adt7475_attrs }; +static const struct attribute_group fan4_attr_group = { .attrs = fan4_attrs }; +static const struct attribute_group pwm2_attr_group = { .attrs = pwm2_attrs }; +static const struct attribute_group in0_attr_group = { .attrs = in0_attrs }; +static const struct attribute_group in3_attr_group = { .attrs = in3_attrs }; +static const struct attribute_group in4_attr_group = { .attrs = in4_attrs }; +static const struct attribute_group in5_attr_group = { .attrs = in5_attrs }; +static const struct attribute_group vid_attr_group = { .attrs = vid_attrs }; + +static int adt7475_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int vendid, devid, devid2; + const char *name; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + vendid = adt7475_read(REG_VENDID); + devid2 = adt7475_read(REG_DEVID2); + if (vendid != 0x41 || /* Analog Devices */ + (devid2 & 0xf8) != 0x68) + return -ENODEV; + + devid = adt7475_read(REG_DEVID); + if (devid == 0x73) + name = "adt7473"; + else if (devid == 0x75 && client->addr == 0x2e) + name = "adt7475"; + else if (devid == 0x76) + name = "adt7476"; + else if ((devid2 & 0xfc) == 0x6c) + name = "adt7490"; + else { + dev_dbg(&adapter->dev, + "Couldn't detect an ADT7473/75/76/90 part at " + "0x%02x\n", (unsigned int)client->addr); + return -ENODEV; + } + + strlcpy(info->type, name, I2C_NAME_SIZE); + + return 0; +} + +static void adt7475_remove_files(struct i2c_client *client, + struct adt7475_data *data) +{ + sysfs_remove_group(&client->dev.kobj, &adt7475_attr_group); + if (data->has_fan4) + sysfs_remove_group(&client->dev.kobj, &fan4_attr_group); + if (data->has_pwm2) + sysfs_remove_group(&client->dev.kobj, &pwm2_attr_group); + if (data->has_voltage & (1 << 0)) + sysfs_remove_group(&client->dev.kobj, &in0_attr_group); + if (data->has_voltage & (1 << 3)) + sysfs_remove_group(&client->dev.kobj, &in3_attr_group); + if (data->has_voltage & (1 << 4)) + sysfs_remove_group(&client->dev.kobj, &in4_attr_group); + if (data->has_voltage & (1 << 5)) + sysfs_remove_group(&client->dev.kobj, &in5_attr_group); + if (data->has_vid) + sysfs_remove_group(&client->dev.kobj, &vid_attr_group); +} + +static int adt7475_update_limits(struct i2c_client *client) +{ + struct adt7475_data *data = i2c_get_clientdata(client); + int i; + int ret; + + ret = adt7475_read(REG_CONFIG4); + if (ret < 0) + return ret; + data->config4 = ret; + + ret = adt7475_read(REG_CONFIG5); + if (ret < 0) + return ret; + data->config5 = ret; + + for (i = 0; i < ADT7475_VOLTAGE_COUNT; i++) { + if (!(data->has_voltage & (1 << i))) + continue; + /* Adjust values so they match the input precision */ + ret = adt7475_read(VOLTAGE_MIN_REG(i)); + if (ret < 0) + return ret; + data->voltage[MIN][i] = ret << 2; + + ret = adt7475_read(VOLTAGE_MAX_REG(i)); + if (ret < 0) + return ret; + data->voltage[MAX][i] = ret << 2; + } + + if (data->has_voltage & (1 << 5)) { + ret = adt7475_read(REG_VTT_MIN); + if (ret < 0) + return ret; + data->voltage[MIN][5] = ret << 2; + + ret = adt7475_read(REG_VTT_MAX); + if (ret < 0) + return ret; + data->voltage[MAX][5] = ret << 2; + } + + for (i = 0; i < ADT7475_TEMP_COUNT; i++) { + /* Adjust values so they match the input precision */ + ret = adt7475_read(TEMP_MIN_REG(i)); + if (ret < 0) + return ret; + data->temp[MIN][i] = ret << 2; + + ret = adt7475_read(TEMP_MAX_REG(i)); + if (ret < 0) + return ret; + data->temp[MAX][i] = ret << 2; + + ret = adt7475_read(TEMP_TMIN_REG(i)); + if (ret < 0) + return ret; + data->temp[AUTOMIN][i] = ret << 2; + + ret = adt7475_read(TEMP_THERM_REG(i)); + if (ret < 0) + return ret; + data->temp[THERM][i] = ret << 2; + + ret = adt7475_read(TEMP_OFFSET_REG(i)); + if (ret < 0) + return ret; + data->temp[OFFSET][i] = ret; + } + adt7475_read_hystersis(client); + + for (i = 0; i < ADT7475_TACH_COUNT; i++) { + if (i == 3 && !data->has_fan4) + continue; + ret = adt7475_read_word(client, TACH_MIN_REG(i)); + if (ret < 0) + return ret; + data->tach[MIN][i] = ret; + } + + for (i = 0; i < ADT7475_PWM_COUNT; i++) { + if (i == 1 && !data->has_pwm2) + continue; + ret = adt7475_read(PWM_MAX_REG(i)); + if (ret < 0) + return ret; + data->pwm[MAX][i] = ret; + + ret = adt7475_read(PWM_MIN_REG(i)); + if (ret < 0) + return ret; + data->pwm[MIN][i] = ret; + /* Set the channel and control information */ + adt7475_read_pwm(client, i); + } + + ret = adt7475_read(TEMP_TRANGE_REG(0)); + if (ret < 0) + return ret; + data->range[0] = ret; + + ret = adt7475_read(TEMP_TRANGE_REG(1)); + if (ret < 0) + return ret; + data->range[1] = ret; + + ret = adt7475_read(TEMP_TRANGE_REG(2)); + if (ret < 0) + return ret; + data->range[2] = ret; + + return 0; +} + +static int adt7475_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + enum chips chip; + static const char * const names[] = { + [adt7473] = "ADT7473", + [adt7475] = "ADT7475", + [adt7476] = "ADT7476", + [adt7490] = "ADT7490", + }; + + struct adt7475_data *data; + int i, ret = 0, revision; + u8 config2, config3; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + mutex_init(&data->lock); + i2c_set_clientdata(client, data); + + if (client->dev.of_node) + chip = (enum chips)of_device_get_match_data(&client->dev); + else + chip = id->driver_data; + + /* Initialize device-specific values */ + switch (chip) { + case adt7476: + data->has_voltage = 0x0e; /* in1 to in3 */ + revision = adt7475_read(REG_DEVID2) & 0x07; + break; + case adt7490: + data->has_voltage = 0x3e; /* in1 to in5 */ + revision = adt7475_read(REG_DEVID2) & 0x03; + if (revision == 0x03) + revision += adt7475_read(REG_DEVREV2); + break; + default: + data->has_voltage = 0x06; /* in1, in2 */ + revision = adt7475_read(REG_DEVID2) & 0x07; + } + + config3 = adt7475_read(REG_CONFIG3); + /* Pin PWM2 may alternatively be used for ALERT output */ + if (!(config3 & CONFIG3_SMBALERT)) + data->has_pwm2 = 1; + /* Meaning of this bit is inverted for the ADT7473-1 */ + if (id->driver_data == adt7473 && revision >= 1) + data->has_pwm2 = !data->has_pwm2; + + data->config4 = adt7475_read(REG_CONFIG4); + /* Pin TACH4 may alternatively be used for THERM */ + if ((data->config4 & CONFIG4_PINFUNC) == 0x0) + data->has_fan4 = 1; + + /* + * THERM configuration is more complex on the ADT7476 and ADT7490, + * because 2 different pins (TACH4 and +2.5 Vin) can be used for + * this function + */ + if (id->driver_data == adt7490) { + if ((data->config4 & CONFIG4_PINFUNC) == 0x1 && + !(config3 & CONFIG3_THERM)) + data->has_fan4 = 1; + } + if (id->driver_data == adt7476 || id->driver_data == adt7490) { + if (!(config3 & CONFIG3_THERM) || + (data->config4 & CONFIG4_PINFUNC) == 0x1) + data->has_voltage |= (1 << 0); /* in0 */ + } + + /* + * On the ADT7476, the +12V input pin may instead be used as VID5, + * and VID pins may alternatively be used as GPIO + */ + if (id->driver_data == adt7476) { + u8 vid = adt7475_read(REG_VID); + if (!(vid & VID_VIDSEL)) + data->has_voltage |= (1 << 4); /* in4 */ + + data->has_vid = !(adt7475_read(REG_CONFIG5) & CONFIG5_VIDGPIO); + } + + /* Voltage attenuators can be bypassed, globally or individually */ + config2 = adt7475_read(REG_CONFIG2); + if (config2 & CONFIG2_ATTN) { + data->bypass_attn = (0x3 << 3) | 0x3; + } else { + data->bypass_attn = ((data->config4 & CONFIG4_ATTN_IN10) >> 4) | + ((data->config4 & CONFIG4_ATTN_IN43) >> 3); + } + data->bypass_attn &= data->has_voltage; + + /* + * Call adt7475_read_pwm for all pwm's as this will reprogram any + * pwm's which are disabled to manual mode with 0% duty cycle + */ + for (i = 0; i < ADT7475_PWM_COUNT; i++) + adt7475_read_pwm(client, i); + + /* Start monitoring */ + switch (chip) { + case adt7475: + case adt7476: + i2c_smbus_write_byte_data(client, REG_CONFIG1, + adt7475_read(REG_CONFIG1) | 0x01); + break; + default: + break; + } + + ret = sysfs_create_group(&client->dev.kobj, &adt7475_attr_group); + if (ret) + return ret; + + /* Features that can be disabled individually */ + if (data->has_fan4) { + ret = sysfs_create_group(&client->dev.kobj, &fan4_attr_group); + if (ret) + goto eremove; + } + if (data->has_pwm2) { + ret = sysfs_create_group(&client->dev.kobj, &pwm2_attr_group); + if (ret) + goto eremove; + } + if (data->has_voltage & (1 << 0)) { + ret = sysfs_create_group(&client->dev.kobj, &in0_attr_group); + if (ret) + goto eremove; + } + if (data->has_voltage & (1 << 3)) { + ret = sysfs_create_group(&client->dev.kobj, &in3_attr_group); + if (ret) + goto eremove; + } + if (data->has_voltage & (1 << 4)) { + ret = sysfs_create_group(&client->dev.kobj, &in4_attr_group); + if (ret) + goto eremove; + } + if (data->has_voltage & (1 << 5)) { + ret = sysfs_create_group(&client->dev.kobj, &in5_attr_group); + if (ret) + goto eremove; + } + if (data->has_vid) { + data->vrm = vid_which_vrm(); + ret = sysfs_create_group(&client->dev.kobj, &vid_attr_group); + if (ret) + goto eremove; + } + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto eremove; + } + + dev_info(&client->dev, "%s device, revision %d\n", + names[id->driver_data], revision); + if ((data->has_voltage & 0x11) || data->has_fan4 || data->has_pwm2) + dev_info(&client->dev, "Optional features:%s%s%s%s%s\n", + (data->has_voltage & (1 << 0)) ? " in0" : "", + (data->has_voltage & (1 << 4)) ? " in4" : "", + data->has_fan4 ? " fan4" : "", + data->has_pwm2 ? " pwm2" : "", + data->has_vid ? " vid" : ""); + if (data->bypass_attn) + dev_info(&client->dev, "Bypassing attenuators on:%s%s%s%s\n", + (data->bypass_attn & (1 << 0)) ? " in0" : "", + (data->bypass_attn & (1 << 1)) ? " in1" : "", + (data->bypass_attn & (1 << 3)) ? " in3" : "", + (data->bypass_attn & (1 << 4)) ? " in4" : ""); + + /* Limits and settings, should never change update more than once */ + ret = adt7475_update_limits(client); + if (ret) + goto eremove; + + return 0; + +eremove: + adt7475_remove_files(client, data); + return ret; +} + +static int adt7475_remove(struct i2c_client *client) +{ + struct adt7475_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + adt7475_remove_files(client, data); + + return 0; +} + +static struct i2c_driver adt7475_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "adt7475", + .of_match_table = of_match_ptr(adt7475_of_match), + }, + .probe = adt7475_probe, + .remove = adt7475_remove, + .id_table = adt7475_id, + .detect = adt7475_detect, + .address_list = normal_i2c, +}; + +static void adt7475_read_hystersis(struct i2c_client *client) +{ + struct adt7475_data *data = i2c_get_clientdata(client); + + data->temp[HYSTERSIS][0] = (u16) adt7475_read(REG_REMOTE1_HYSTERSIS); + data->temp[HYSTERSIS][1] = data->temp[HYSTERSIS][0]; + data->temp[HYSTERSIS][2] = (u16) adt7475_read(REG_REMOTE2_HYSTERSIS); +} + +static void adt7475_read_pwm(struct i2c_client *client, int index) +{ + struct adt7475_data *data = i2c_get_clientdata(client); + unsigned int v; + + data->pwm[CONTROL][index] = adt7475_read(PWM_CONFIG_REG(index)); + + /* + * Figure out the internal value for pwmctrl and pwmchan + * based on the current settings + */ + v = (data->pwm[CONTROL][index] >> 5) & 7; + + if (v == 3) + data->pwmctl[index] = 0; + else if (v == 7) + data->pwmctl[index] = 1; + else if (v == 4) { + /* + * The fan is disabled - we don't want to + * support that, so change to manual mode and + * set the duty cycle to 0 instead + */ + data->pwm[INPUT][index] = 0; + data->pwm[CONTROL][index] &= ~0xE0; + data->pwm[CONTROL][index] |= (7 << 5); + + i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(index), + data->pwm[INPUT][index]); + + i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(index), + data->pwm[CONTROL][index]); + + data->pwmctl[index] = 1; + } else { + data->pwmctl[index] = 2; + + switch (v) { + case 0: + data->pwmchan[index] = 1; + break; + case 1: + data->pwmchan[index] = 2; + break; + case 2: + data->pwmchan[index] = 4; + break; + case 5: + data->pwmchan[index] = 6; + break; + case 6: + data->pwmchan[index] = 7; + break; + } + } +} + +static int adt7475_update_measure(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct adt7475_data *data = i2c_get_clientdata(client); + u16 ext; + int i; + int ret; + + ret = adt7475_read(REG_STATUS2); + if (ret < 0) + return ret; + data->alarms = ret << 8; + + ret = adt7475_read(REG_STATUS1); + if (ret < 0) + return ret; + data->alarms |= ret; + + ret = adt7475_read(REG_EXTEND2); + if (ret < 0) + return ret; + + ext = (ret << 8); + + ret = adt7475_read(REG_EXTEND1); + if (ret < 0) + return ret; + + ext |= ret; + + for (i = 0; i < ADT7475_VOLTAGE_COUNT; i++) { + if (!(data->has_voltage & (1 << i))) + continue; + ret = adt7475_read(VOLTAGE_REG(i)); + if (ret < 0) + return ret; + data->voltage[INPUT][i] = + (ret << 2) | + ((ext >> (i * 2)) & 3); + } + + for (i = 0; i < ADT7475_TEMP_COUNT; i++) { + ret = adt7475_read(TEMP_REG(i)); + if (ret < 0) + return ret; + data->temp[INPUT][i] = + (ret << 2) | + ((ext >> ((i + 5) * 2)) & 3); + } + + if (data->has_voltage & (1 << 5)) { + ret = adt7475_read(REG_STATUS4); + if (ret < 0) + return ret; + data->alarms |= ret << 24; + + ret = adt7475_read(REG_EXTEND3); + if (ret < 0) + return ret; + ext = ret; + + ret = adt7475_read(REG_VTT); + if (ret < 0) + return ret; + data->voltage[INPUT][5] = ret << 2 | + ((ext >> 4) & 3); + } + + for (i = 0; i < ADT7475_TACH_COUNT; i++) { + if (i == 3 && !data->has_fan4) + continue; + ret = adt7475_read_word(client, TACH_REG(i)); + if (ret < 0) + return ret; + data->tach[INPUT][i] = ret; + } + + /* Updated by hw when in auto mode */ + for (i = 0; i < ADT7475_PWM_COUNT; i++) { + if (i == 1 && !data->has_pwm2) + continue; + ret = adt7475_read(PWM_REG(i)); + if (ret < 0) + return ret; + data->pwm[INPUT][i] = ret; + } + + if (data->has_vid) { + ret = adt7475_read(REG_VID); + if (ret < 0) + return ret; + data->vid = ret & 0x3f; + } + + return 0; +} + +static struct adt7475_data *adt7475_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct adt7475_data *data = i2c_get_clientdata(client); + int ret; + + mutex_lock(&data->lock); + + /* Measurement values update every 2 seconds */ + if (time_after(jiffies, data->measure_updated + HZ * 2) || + !data->valid) { + ret = adt7475_update_measure(dev); + if (ret) { + data->valid = false; + mutex_unlock(&data->lock); + return ERR_PTR(ret); + } + data->measure_updated = jiffies; + data->valid = true; + } + + mutex_unlock(&data->lock); + + return data; +} + +module_i2c_driver(adt7475_driver); + +MODULE_AUTHOR("Advanced Micro Devices, Inc"); +MODULE_DESCRIPTION("adt7475 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/adt7x10.c b/drivers/hwmon/adt7x10.c new file mode 100644 index 000000000..0f538f8be --- /dev/null +++ b/drivers/hwmon/adt7x10.c @@ -0,0 +1,510 @@ +/* + * adt7x10.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * This driver handles the ADT7410 and compatible digital temperature sensors. + * Hartmut Knaack 2012-07-22 + * based on lm75.c by Frodo Looijaard + * and adt7410.c from iio-staging by Sonic Zhang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "adt7x10.h" + +/* + * ADT7X10 status + */ +#define ADT7X10_STAT_T_LOW (1 << 4) +#define ADT7X10_STAT_T_HIGH (1 << 5) +#define ADT7X10_STAT_T_CRIT (1 << 6) +#define ADT7X10_STAT_NOT_RDY (1 << 7) + +/* + * ADT7X10 config + */ +#define ADT7X10_FAULT_QUEUE_MASK (1 << 0 | 1 << 1) +#define ADT7X10_CT_POLARITY (1 << 2) +#define ADT7X10_INT_POLARITY (1 << 3) +#define ADT7X10_EVENT_MODE (1 << 4) +#define ADT7X10_MODE_MASK (1 << 5 | 1 << 6) +#define ADT7X10_FULL (0 << 5 | 0 << 6) +#define ADT7X10_PD (1 << 5 | 1 << 6) +#define ADT7X10_RESOLUTION (1 << 7) + +/* + * ADT7X10 masks + */ +#define ADT7X10_T13_VALUE_MASK 0xFFF8 +#define ADT7X10_T_HYST_MASK 0xF + +/* straight from the datasheet */ +#define ADT7X10_TEMP_MIN (-55000) +#define ADT7X10_TEMP_MAX 150000 + +/* Each client has this additional data */ +struct adt7x10_data { + const struct adt7x10_ops *ops; + const char *name; + struct device *hwmon_dev; + struct mutex update_lock; + u8 config; + u8 oldconfig; + bool valid; /* true if registers valid */ + unsigned long last_updated; /* In jiffies */ + s16 temp[4]; /* Register values, + 0 = input + 1 = high + 2 = low + 3 = critical */ + u8 hyst; /* hysteresis offset */ +}; + +static int adt7x10_read_byte(struct device *dev, u8 reg) +{ + struct adt7x10_data *d = dev_get_drvdata(dev); + return d->ops->read_byte(dev, reg); +} + +static int adt7x10_write_byte(struct device *dev, u8 reg, u8 data) +{ + struct adt7x10_data *d = dev_get_drvdata(dev); + return d->ops->write_byte(dev, reg, data); +} + +static int adt7x10_read_word(struct device *dev, u8 reg) +{ + struct adt7x10_data *d = dev_get_drvdata(dev); + return d->ops->read_word(dev, reg); +} + +static int adt7x10_write_word(struct device *dev, u8 reg, u16 data) +{ + struct adt7x10_data *d = dev_get_drvdata(dev); + return d->ops->write_word(dev, reg, data); +} + +static const u8 ADT7X10_REG_TEMP[4] = { + ADT7X10_TEMPERATURE, /* input */ + ADT7X10_T_ALARM_HIGH, /* high */ + ADT7X10_T_ALARM_LOW, /* low */ + ADT7X10_T_CRIT, /* critical */ +}; + +static irqreturn_t adt7x10_irq_handler(int irq, void *private) +{ + struct device *dev = private; + int status; + + status = adt7x10_read_byte(dev, ADT7X10_STATUS); + if (status < 0) + return IRQ_HANDLED; + + if (status & ADT7X10_STAT_T_HIGH) + sysfs_notify(&dev->kobj, NULL, "temp1_max_alarm"); + if (status & ADT7X10_STAT_T_LOW) + sysfs_notify(&dev->kobj, NULL, "temp1_min_alarm"); + if (status & ADT7X10_STAT_T_CRIT) + sysfs_notify(&dev->kobj, NULL, "temp1_crit_alarm"); + + return IRQ_HANDLED; +} + +static int adt7x10_temp_ready(struct device *dev) +{ + int i, status; + + for (i = 0; i < 6; i++) { + status = adt7x10_read_byte(dev, ADT7X10_STATUS); + if (status < 0) + return status; + if (!(status & ADT7X10_STAT_NOT_RDY)) + return 0; + msleep(60); + } + return -ETIMEDOUT; +} + +static int adt7x10_update_temp(struct device *dev) +{ + struct adt7x10_data *data = dev_get_drvdata(dev); + int ret = 0; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + int temp; + + dev_dbg(dev, "Starting update\n"); + + ret = adt7x10_temp_ready(dev); /* check for new value */ + if (ret) + goto abort; + + temp = adt7x10_read_word(dev, ADT7X10_REG_TEMP[0]); + if (temp < 0) { + ret = temp; + dev_dbg(dev, "Failed to read value: reg %d, error %d\n", + ADT7X10_REG_TEMP[0], ret); + goto abort; + } + data->temp[0] = temp; + data->last_updated = jiffies; + data->valid = true; + } + +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +static int adt7x10_fill_cache(struct device *dev) +{ + struct adt7x10_data *data = dev_get_drvdata(dev); + int ret; + int i; + + for (i = 1; i < ARRAY_SIZE(data->temp); i++) { + ret = adt7x10_read_word(dev, ADT7X10_REG_TEMP[i]); + if (ret < 0) { + dev_dbg(dev, "Failed to read value: reg %d, error %d\n", + ADT7X10_REG_TEMP[i], ret); + return ret; + } + data->temp[i] = ret; + } + + ret = adt7x10_read_byte(dev, ADT7X10_T_HYST); + if (ret < 0) { + dev_dbg(dev, "Failed to read value: reg %d, error %d\n", + ADT7X10_T_HYST, ret); + return ret; + } + data->hyst = ret; + + return 0; +} + +static s16 ADT7X10_TEMP_TO_REG(long temp) +{ + return DIV_ROUND_CLOSEST(clamp_val(temp, ADT7X10_TEMP_MIN, + ADT7X10_TEMP_MAX) * 128, 1000); +} + +static int ADT7X10_REG_TO_TEMP(struct adt7x10_data *data, s16 reg) +{ + /* in 13 bit mode, bits 0-2 are status flags - mask them out */ + if (!(data->config & ADT7X10_RESOLUTION)) + reg &= ADT7X10_T13_VALUE_MASK; + /* + * temperature is stored in twos complement format, in steps of + * 1/128°C + */ + return DIV_ROUND_CLOSEST(reg * 1000, 128); +} + +/*-----------------------------------------------------------------------*/ + +/* sysfs attributes for hwmon */ + +static ssize_t adt7x10_show_temp(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct adt7x10_data *data = dev_get_drvdata(dev); + + + if (attr->index == 0) { + int ret; + + ret = adt7x10_update_temp(dev); + if (ret) + return ret; + } + + return sprintf(buf, "%d\n", ADT7X10_REG_TO_TEMP(data, + data->temp[attr->index])); +} + +static ssize_t adt7x10_set_temp(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct adt7x10_data *data = dev_get_drvdata(dev); + int nr = attr->index; + long temp; + int ret; + + ret = kstrtol(buf, 10, &temp); + if (ret) + return ret; + + mutex_lock(&data->update_lock); + data->temp[nr] = ADT7X10_TEMP_TO_REG(temp); + ret = adt7x10_write_word(dev, ADT7X10_REG_TEMP[nr], data->temp[nr]); + if (ret) + count = ret; + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t adt7x10_show_t_hyst(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct adt7x10_data *data = dev_get_drvdata(dev); + int nr = attr->index; + int hyst; + + hyst = (data->hyst & ADT7X10_T_HYST_MASK) * 1000; + + /* + * hysteresis is stored as a 4 bit offset in the device, convert it + * to an absolute value + */ + if (nr == 2) /* min has positive offset, others have negative */ + hyst = -hyst; + return sprintf(buf, "%d\n", + ADT7X10_REG_TO_TEMP(data, data->temp[nr]) - hyst); +} + +static ssize_t adt7x10_set_t_hyst(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct adt7x10_data *data = dev_get_drvdata(dev); + int limit, ret; + long hyst; + + ret = kstrtol(buf, 10, &hyst); + if (ret) + return ret; + /* convert absolute hysteresis value to a 4 bit delta value */ + limit = ADT7X10_REG_TO_TEMP(data, data->temp[1]); + hyst = clamp_val(hyst, ADT7X10_TEMP_MIN, ADT7X10_TEMP_MAX); + data->hyst = clamp_val(DIV_ROUND_CLOSEST(limit - hyst, 1000), + 0, ADT7X10_T_HYST_MASK); + ret = adt7x10_write_byte(dev, ADT7X10_T_HYST, data->hyst); + if (ret) + return ret; + + return count; +} + +static ssize_t adt7x10_show_alarm(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int ret; + + ret = adt7x10_read_byte(dev, ADT7X10_STATUS); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", !!(ret & attr->index)); +} + +static ssize_t name_show(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct adt7x10_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, adt7x10_show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, + adt7x10_show_temp, adt7x10_set_temp, 1); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, + adt7x10_show_temp, adt7x10_set_temp, 2); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, + adt7x10_show_temp, adt7x10_set_temp, 3); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, + adt7x10_show_t_hyst, adt7x10_set_t_hyst, 1); +static SENSOR_DEVICE_ATTR(temp1_min_hyst, S_IRUGO, + adt7x10_show_t_hyst, NULL, 2); +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IRUGO, + adt7x10_show_t_hyst, NULL, 3); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, adt7x10_show_alarm, + NULL, ADT7X10_STAT_T_LOW); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, adt7x10_show_alarm, + NULL, ADT7X10_STAT_T_HIGH); +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, adt7x10_show_alarm, + NULL, ADT7X10_STAT_T_CRIT); +static DEVICE_ATTR_RO(name); + +static struct attribute *adt7x10_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_min_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group adt7x10_group = { + .attrs = adt7x10_attributes, +}; + +int adt7x10_probe(struct device *dev, const char *name, int irq, + const struct adt7x10_ops *ops) +{ + struct adt7x10_data *data; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->ops = ops; + data->name = name; + + dev_set_drvdata(dev, data); + mutex_init(&data->update_lock); + + /* configure as specified */ + ret = adt7x10_read_byte(dev, ADT7X10_CONFIG); + if (ret < 0) { + dev_dbg(dev, "Can't read config? %d\n", ret); + return ret; + } + data->oldconfig = ret; + + /* + * Set to 16 bit resolution, continous conversion and comparator mode. + */ + data->config = data->oldconfig; + data->config &= ~(ADT7X10_MODE_MASK | ADT7X10_CT_POLARITY | + ADT7X10_INT_POLARITY); + data->config |= ADT7X10_FULL | ADT7X10_RESOLUTION | ADT7X10_EVENT_MODE; + + if (data->config != data->oldconfig) { + ret = adt7x10_write_byte(dev, ADT7X10_CONFIG, data->config); + if (ret) + return ret; + } + dev_dbg(dev, "Config %02x\n", data->config); + + ret = adt7x10_fill_cache(dev); + if (ret) + goto exit_restore; + + /* Register sysfs hooks */ + ret = sysfs_create_group(&dev->kobj, &adt7x10_group); + if (ret) + goto exit_restore; + + /* + * The I2C device will already have it's own 'name' attribute, but for + * the SPI device we need to register it. name will only be non NULL if + * the device doesn't register the 'name' attribute on its own. + */ + if (name) { + ret = device_create_file(dev, &dev_attr_name); + if (ret) + goto exit_remove; + } + + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto exit_remove_name; + } + + if (irq > 0) { + ret = request_threaded_irq(irq, NULL, adt7x10_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(dev), dev); + if (ret) + goto exit_hwmon_device_unregister; + } + + return 0; + +exit_hwmon_device_unregister: + hwmon_device_unregister(data->hwmon_dev); +exit_remove_name: + if (name) + device_remove_file(dev, &dev_attr_name); +exit_remove: + sysfs_remove_group(&dev->kobj, &adt7x10_group); +exit_restore: + adt7x10_write_byte(dev, ADT7X10_CONFIG, data->oldconfig); + return ret; +} +EXPORT_SYMBOL_GPL(adt7x10_probe); + +int adt7x10_remove(struct device *dev, int irq) +{ + struct adt7x10_data *data = dev_get_drvdata(dev); + + if (irq > 0) + free_irq(irq, dev); + + hwmon_device_unregister(data->hwmon_dev); + if (data->name) + device_remove_file(dev, &dev_attr_name); + sysfs_remove_group(&dev->kobj, &adt7x10_group); + if (data->oldconfig != data->config) + adt7x10_write_byte(dev, ADT7X10_CONFIG, data->oldconfig); + return 0; +} +EXPORT_SYMBOL_GPL(adt7x10_remove); + +#ifdef CONFIG_PM_SLEEP + +static int adt7x10_suspend(struct device *dev) +{ + struct adt7x10_data *data = dev_get_drvdata(dev); + + return adt7x10_write_byte(dev, ADT7X10_CONFIG, + data->config | ADT7X10_PD); +} + +static int adt7x10_resume(struct device *dev) +{ + struct adt7x10_data *data = dev_get_drvdata(dev); + + return adt7x10_write_byte(dev, ADT7X10_CONFIG, data->config); +} + +SIMPLE_DEV_PM_OPS(adt7x10_dev_pm_ops, adt7x10_suspend, adt7x10_resume); +EXPORT_SYMBOL_GPL(adt7x10_dev_pm_ops); + +#endif /* CONFIG_PM_SLEEP */ + +MODULE_AUTHOR("Hartmut Knaack"); +MODULE_DESCRIPTION("ADT7410/ADT7420, ADT7310/ADT7320 common code"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/adt7x10.h b/drivers/hwmon/adt7x10.h new file mode 100644 index 000000000..21ad15ce3 --- /dev/null +++ b/drivers/hwmon/adt7x10.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __HWMON_ADT7X10_H__ +#define __HWMON_ADT7X10_H__ + +#include +#include + +/* ADT7410 registers definition */ +#define ADT7X10_TEMPERATURE 0 +#define ADT7X10_STATUS 2 +#define ADT7X10_CONFIG 3 +#define ADT7X10_T_ALARM_HIGH 4 +#define ADT7X10_T_ALARM_LOW 6 +#define ADT7X10_T_CRIT 8 +#define ADT7X10_T_HYST 0xA +#define ADT7X10_ID 0xB + +struct device; + +struct adt7x10_ops { + int (*read_byte)(struct device *, u8 reg); + int (*write_byte)(struct device *, u8 reg, u8 data); + int (*read_word)(struct device *, u8 reg); + int (*write_word)(struct device *, u8 reg, u16 data); +}; + +int adt7x10_probe(struct device *dev, const char *name, int irq, + const struct adt7x10_ops *ops); +int adt7x10_remove(struct device *dev, int irq); + +#ifdef CONFIG_PM_SLEEP +extern const struct dev_pm_ops adt7x10_dev_pm_ops; +#define ADT7X10_DEV_PM_OPS (&adt7x10_dev_pm_ops) +#else +#define ADT7X10_DEV_PM_OPS NULL +#endif + +#endif diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c new file mode 100644 index 000000000..46b4e35fd --- /dev/null +++ b/drivers/hwmon/amc6821.c @@ -0,0 +1,1021 @@ +/* + * amc6821.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 2009 T. Mertelj + * + * Based on max6650.c: + * Copyright (C) 2007 Hans J. Koch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include /* Needed for KERN_INFO */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Addresses to scan. + */ + +static const unsigned short normal_i2c[] = {0x18, 0x19, 0x1a, 0x2c, 0x2d, 0x2e, + 0x4c, 0x4d, 0x4e, I2C_CLIENT_END}; + +/* + * Insmod parameters + */ + +static int pwminv; /*Inverted PWM output. */ +module_param(pwminv, int, S_IRUGO); + +static int init = 1; /*Power-on initialization.*/ +module_param(init, int, S_IRUGO); + +enum chips { amc6821 }; + +#define AMC6821_REG_DEV_ID 0x3D +#define AMC6821_REG_COMP_ID 0x3E +#define AMC6821_REG_CONF1 0x00 +#define AMC6821_REG_CONF2 0x01 +#define AMC6821_REG_CONF3 0x3F +#define AMC6821_REG_CONF4 0x04 +#define AMC6821_REG_STAT1 0x02 +#define AMC6821_REG_STAT2 0x03 +#define AMC6821_REG_TDATA_LOW 0x08 +#define AMC6821_REG_TDATA_HI 0x09 +#define AMC6821_REG_LTEMP_HI 0x0A +#define AMC6821_REG_RTEMP_HI 0x0B +#define AMC6821_REG_LTEMP_LIMIT_MIN 0x15 +#define AMC6821_REG_LTEMP_LIMIT_MAX 0x14 +#define AMC6821_REG_RTEMP_LIMIT_MIN 0x19 +#define AMC6821_REG_RTEMP_LIMIT_MAX 0x18 +#define AMC6821_REG_LTEMP_CRIT 0x1B +#define AMC6821_REG_RTEMP_CRIT 0x1D +#define AMC6821_REG_PSV_TEMP 0x1C +#define AMC6821_REG_DCY 0x22 +#define AMC6821_REG_LTEMP_FAN_CTRL 0x24 +#define AMC6821_REG_RTEMP_FAN_CTRL 0x25 +#define AMC6821_REG_DCY_LOW_TEMP 0x21 + +#define AMC6821_REG_TACH_LLIMITL 0x10 +#define AMC6821_REG_TACH_LLIMITH 0x11 +#define AMC6821_REG_TACH_HLIMITL 0x12 +#define AMC6821_REG_TACH_HLIMITH 0x13 + +#define AMC6821_CONF1_START 0x01 +#define AMC6821_CONF1_FAN_INT_EN 0x02 +#define AMC6821_CONF1_FANIE 0x04 +#define AMC6821_CONF1_PWMINV 0x08 +#define AMC6821_CONF1_FAN_FAULT_EN 0x10 +#define AMC6821_CONF1_FDRC0 0x20 +#define AMC6821_CONF1_FDRC1 0x40 +#define AMC6821_CONF1_THERMOVIE 0x80 + +#define AMC6821_CONF2_PWM_EN 0x01 +#define AMC6821_CONF2_TACH_MODE 0x02 +#define AMC6821_CONF2_TACH_EN 0x04 +#define AMC6821_CONF2_RTFIE 0x08 +#define AMC6821_CONF2_LTOIE 0x10 +#define AMC6821_CONF2_RTOIE 0x20 +#define AMC6821_CONF2_PSVIE 0x40 +#define AMC6821_CONF2_RST 0x80 + +#define AMC6821_CONF3_THERM_FAN_EN 0x80 +#define AMC6821_CONF3_REV_MASK 0x0F + +#define AMC6821_CONF4_OVREN 0x10 +#define AMC6821_CONF4_TACH_FAST 0x20 +#define AMC6821_CONF4_PSPR 0x40 +#define AMC6821_CONF4_MODE 0x80 + +#define AMC6821_STAT1_RPM_ALARM 0x01 +#define AMC6821_STAT1_FANS 0x02 +#define AMC6821_STAT1_RTH 0x04 +#define AMC6821_STAT1_RTL 0x08 +#define AMC6821_STAT1_R_THERM 0x10 +#define AMC6821_STAT1_RTF 0x20 +#define AMC6821_STAT1_LTH 0x40 +#define AMC6821_STAT1_LTL 0x80 + +#define AMC6821_STAT2_RTC 0x08 +#define AMC6821_STAT2_LTC 0x10 +#define AMC6821_STAT2_LPSV 0x20 +#define AMC6821_STAT2_L_THERM 0x40 +#define AMC6821_STAT2_THERM_IN 0x80 + +enum {IDX_TEMP1_INPUT = 0, IDX_TEMP1_MIN, IDX_TEMP1_MAX, + IDX_TEMP1_CRIT, IDX_TEMP2_INPUT, IDX_TEMP2_MIN, + IDX_TEMP2_MAX, IDX_TEMP2_CRIT, + TEMP_IDX_LEN, }; + +static const u8 temp_reg[] = {AMC6821_REG_LTEMP_HI, + AMC6821_REG_LTEMP_LIMIT_MIN, + AMC6821_REG_LTEMP_LIMIT_MAX, + AMC6821_REG_LTEMP_CRIT, + AMC6821_REG_RTEMP_HI, + AMC6821_REG_RTEMP_LIMIT_MIN, + AMC6821_REG_RTEMP_LIMIT_MAX, + AMC6821_REG_RTEMP_CRIT, }; + +enum {IDX_FAN1_INPUT = 0, IDX_FAN1_MIN, IDX_FAN1_MAX, + FAN1_IDX_LEN, }; + +static const u8 fan_reg_low[] = {AMC6821_REG_TDATA_LOW, + AMC6821_REG_TACH_LLIMITL, + AMC6821_REG_TACH_HLIMITL, }; + + +static const u8 fan_reg_hi[] = {AMC6821_REG_TDATA_HI, + AMC6821_REG_TACH_LLIMITH, + AMC6821_REG_TACH_HLIMITH, }; + +/* + * Client data (each client gets its own) + */ + +struct amc6821_data { + struct i2c_client *client; + struct mutex update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* register values */ + int temp[TEMP_IDX_LEN]; + + u16 fan[FAN1_IDX_LEN]; + u8 fan1_div; + + u8 pwm1; + u8 temp1_auto_point_temp[3]; + u8 temp2_auto_point_temp[3]; + u8 pwm1_auto_point_pwm[3]; + u8 pwm1_enable; + u8 pwm1_auto_channels_temp; + + u8 stat1; + u8 stat2; +}; + +static struct amc6821_data *amc6821_update_device(struct device *dev) +{ + struct amc6821_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int timeout = HZ; + u8 reg; + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + timeout) || + !data->valid) { + + for (i = 0; i < TEMP_IDX_LEN; i++) + data->temp[i] = (int8_t)i2c_smbus_read_byte_data( + client, temp_reg[i]); + + data->stat1 = i2c_smbus_read_byte_data(client, + AMC6821_REG_STAT1); + data->stat2 = i2c_smbus_read_byte_data(client, + AMC6821_REG_STAT2); + + data->pwm1 = i2c_smbus_read_byte_data(client, + AMC6821_REG_DCY); + for (i = 0; i < FAN1_IDX_LEN; i++) { + data->fan[i] = i2c_smbus_read_byte_data( + client, + fan_reg_low[i]); + data->fan[i] += i2c_smbus_read_byte_data( + client, + fan_reg_hi[i]) << 8; + } + data->fan1_div = i2c_smbus_read_byte_data(client, + AMC6821_REG_CONF4); + data->fan1_div = data->fan1_div & AMC6821_CONF4_PSPR ? 4 : 2; + + data->pwm1_auto_point_pwm[0] = 0; + data->pwm1_auto_point_pwm[2] = 255; + data->pwm1_auto_point_pwm[1] = i2c_smbus_read_byte_data(client, + AMC6821_REG_DCY_LOW_TEMP); + + data->temp1_auto_point_temp[0] = + i2c_smbus_read_byte_data(client, + AMC6821_REG_PSV_TEMP); + data->temp2_auto_point_temp[0] = + data->temp1_auto_point_temp[0]; + reg = i2c_smbus_read_byte_data(client, + AMC6821_REG_LTEMP_FAN_CTRL); + data->temp1_auto_point_temp[1] = (reg & 0xF8) >> 1; + reg &= 0x07; + reg = 0x20 >> reg; + if (reg > 0) + data->temp1_auto_point_temp[2] = + data->temp1_auto_point_temp[1] + + (data->pwm1_auto_point_pwm[2] - + data->pwm1_auto_point_pwm[1]) / reg; + else + data->temp1_auto_point_temp[2] = 255; + + reg = i2c_smbus_read_byte_data(client, + AMC6821_REG_RTEMP_FAN_CTRL); + data->temp2_auto_point_temp[1] = (reg & 0xF8) >> 1; + reg &= 0x07; + reg = 0x20 >> reg; + if (reg > 0) + data->temp2_auto_point_temp[2] = + data->temp2_auto_point_temp[1] + + (data->pwm1_auto_point_pwm[2] - + data->pwm1_auto_point_pwm[1]) / reg; + else + data->temp2_auto_point_temp[2] = 255; + + reg = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1); + reg = (reg >> 5) & 0x3; + switch (reg) { + case 0: /*open loop: software sets pwm1*/ + data->pwm1_auto_channels_temp = 0; + data->pwm1_enable = 1; + break; + case 2: /*closed loop: remote T (temp2)*/ + data->pwm1_auto_channels_temp = 2; + data->pwm1_enable = 2; + break; + case 3: /*closed loop: local and remote T (temp2)*/ + data->pwm1_auto_channels_temp = 3; + data->pwm1_enable = 3; + break; + case 1: /* + * semi-open loop: software sets rpm, chip controls + * pwm1, currently not implemented + */ + data->pwm1_auto_channels_temp = 0; + data->pwm1_enable = 0; + break; + } + + data->last_updated = jiffies; + data->valid = 1; + } + mutex_unlock(&data->update_lock); + return data; +} + +static ssize_t get_temp( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct amc6821_data *data = amc6821_update_device(dev); + int ix = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%d\n", data->temp[ix] * 1000); +} + +static ssize_t set_temp( + struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct amc6821_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ix = to_sensor_dev_attr(attr)->index; + long val; + + int ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + val = clamp_val(val / 1000, -128, 127); + + mutex_lock(&data->update_lock); + data->temp[ix] = val; + if (i2c_smbus_write_byte_data(client, temp_reg[ix], data->temp[ix])) { + dev_err(&client->dev, "Register write error, aborting.\n"); + count = -EIO; + } + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t get_temp_alarm( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct amc6821_data *data = amc6821_update_device(dev); + int ix = to_sensor_dev_attr(devattr)->index; + u8 flag; + + switch (ix) { + case IDX_TEMP1_MIN: + flag = data->stat1 & AMC6821_STAT1_LTL; + break; + case IDX_TEMP1_MAX: + flag = data->stat1 & AMC6821_STAT1_LTH; + break; + case IDX_TEMP1_CRIT: + flag = data->stat2 & AMC6821_STAT2_LTC; + break; + case IDX_TEMP2_MIN: + flag = data->stat1 & AMC6821_STAT1_RTL; + break; + case IDX_TEMP2_MAX: + flag = data->stat1 & AMC6821_STAT1_RTH; + break; + case IDX_TEMP2_CRIT: + flag = data->stat2 & AMC6821_STAT2_RTC; + break; + default: + dev_dbg(dev, "Unknown attr->index (%d).\n", ix); + return -EINVAL; + } + if (flag) + return sprintf(buf, "1"); + else + return sprintf(buf, "0"); +} + +static ssize_t get_temp2_fault( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct amc6821_data *data = amc6821_update_device(dev); + if (data->stat1 & AMC6821_STAT1_RTF) + return sprintf(buf, "1"); + else + return sprintf(buf, "0"); +} + +static ssize_t get_pwm1( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct amc6821_data *data = amc6821_update_device(dev); + return sprintf(buf, "%d\n", data->pwm1); +} + +static ssize_t set_pwm1( + struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct amc6821_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&data->update_lock); + data->pwm1 = clamp_val(val , 0, 255); + i2c_smbus_write_byte_data(client, AMC6821_REG_DCY, data->pwm1); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t get_pwm1_enable( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct amc6821_data *data = amc6821_update_device(dev); + return sprintf(buf, "%d\n", data->pwm1_enable); +} + +static ssize_t set_pwm1_enable( + struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct amc6821_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int config = kstrtol(buf, 10, &val); + if (config) + return config; + + mutex_lock(&data->update_lock); + config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1); + if (config < 0) { + dev_err(&client->dev, + "Error reading configuration register, aborting.\n"); + count = config; + goto unlock; + } + + switch (val) { + case 1: + config &= ~AMC6821_CONF1_FDRC0; + config &= ~AMC6821_CONF1_FDRC1; + break; + case 2: + config &= ~AMC6821_CONF1_FDRC0; + config |= AMC6821_CONF1_FDRC1; + break; + case 3: + config |= AMC6821_CONF1_FDRC0; + config |= AMC6821_CONF1_FDRC1; + break; + default: + count = -EINVAL; + goto unlock; + } + if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF1, config)) { + dev_err(&client->dev, + "Configuration register write error, aborting.\n"); + count = -EIO; + } +unlock: + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t get_pwm1_auto_channels_temp( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct amc6821_data *data = amc6821_update_device(dev); + return sprintf(buf, "%d\n", data->pwm1_auto_channels_temp); +} + +static ssize_t get_temp_auto_point_temp( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + int ix = to_sensor_dev_attr_2(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->nr; + struct amc6821_data *data = amc6821_update_device(dev); + switch (nr) { + case 1: + return sprintf(buf, "%d\n", + data->temp1_auto_point_temp[ix] * 1000); + case 2: + return sprintf(buf, "%d\n", + data->temp2_auto_point_temp[ix] * 1000); + default: + dev_dbg(dev, "Unknown attr->nr (%d).\n", nr); + return -EINVAL; + } +} + +static ssize_t get_pwm1_auto_point_pwm( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + int ix = to_sensor_dev_attr(devattr)->index; + struct amc6821_data *data = amc6821_update_device(dev); + return sprintf(buf, "%d\n", data->pwm1_auto_point_pwm[ix]); +} + +static inline ssize_t set_slope_register(struct i2c_client *client, + u8 reg, + u8 dpwm, + u8 *ptemp) +{ + int dt; + u8 tmp; + + dt = ptemp[2]-ptemp[1]; + for (tmp = 4; tmp > 0; tmp--) { + if (dt * (0x20 >> tmp) >= dpwm) + break; + } + tmp |= (ptemp[1] & 0x7C) << 1; + if (i2c_smbus_write_byte_data(client, + reg, tmp)) { + dev_err(&client->dev, "Register write error, aborting.\n"); + return -EIO; + } + return 0; +} + +static ssize_t set_temp_auto_point_temp( + struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct amc6821_data *data = amc6821_update_device(dev); + struct i2c_client *client = data->client; + int ix = to_sensor_dev_attr_2(attr)->index; + int nr = to_sensor_dev_attr_2(attr)->nr; + u8 *ptemp; + u8 reg; + int dpwm; + long val; + int ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + switch (nr) { + case 1: + ptemp = data->temp1_auto_point_temp; + reg = AMC6821_REG_LTEMP_FAN_CTRL; + break; + case 2: + ptemp = data->temp2_auto_point_temp; + reg = AMC6821_REG_RTEMP_FAN_CTRL; + break; + default: + dev_dbg(dev, "Unknown attr->nr (%d).\n", nr); + return -EINVAL; + } + + mutex_lock(&data->update_lock); + data->valid = 0; + + switch (ix) { + case 0: + ptemp[0] = clamp_val(val / 1000, 0, + data->temp1_auto_point_temp[1]); + ptemp[0] = clamp_val(ptemp[0], 0, + data->temp2_auto_point_temp[1]); + ptemp[0] = clamp_val(ptemp[0], 0, 63); + if (i2c_smbus_write_byte_data( + client, + AMC6821_REG_PSV_TEMP, + ptemp[0])) { + dev_err(&client->dev, + "Register write error, aborting.\n"); + count = -EIO; + } + goto EXIT; + case 1: + ptemp[1] = clamp_val(val / 1000, (ptemp[0] & 0x7C) + 4, 124); + ptemp[1] &= 0x7C; + ptemp[2] = clamp_val(ptemp[2], ptemp[1] + 1, 255); + break; + case 2: + ptemp[2] = clamp_val(val / 1000, ptemp[1]+1, 255); + break; + default: + dev_dbg(dev, "Unknown attr->index (%d).\n", ix); + count = -EINVAL; + goto EXIT; + } + dpwm = data->pwm1_auto_point_pwm[2] - data->pwm1_auto_point_pwm[1]; + if (set_slope_register(client, reg, dpwm, ptemp)) + count = -EIO; + +EXIT: + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_pwm1_auto_point_pwm( + struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct amc6821_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int dpwm; + long val; + int ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&data->update_lock); + data->pwm1_auto_point_pwm[1] = clamp_val(val, 0, 254); + if (i2c_smbus_write_byte_data(client, AMC6821_REG_DCY_LOW_TEMP, + data->pwm1_auto_point_pwm[1])) { + dev_err(&client->dev, "Register write error, aborting.\n"); + count = -EIO; + goto EXIT; + } + dpwm = data->pwm1_auto_point_pwm[2] - data->pwm1_auto_point_pwm[1]; + if (set_slope_register(client, AMC6821_REG_LTEMP_FAN_CTRL, dpwm, + data->temp1_auto_point_temp)) { + count = -EIO; + goto EXIT; + } + if (set_slope_register(client, AMC6821_REG_RTEMP_FAN_CTRL, dpwm, + data->temp2_auto_point_temp)) { + count = -EIO; + goto EXIT; + } + +EXIT: + data->valid = 0; + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t get_fan( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct amc6821_data *data = amc6821_update_device(dev); + int ix = to_sensor_dev_attr(devattr)->index; + if (0 == data->fan[ix]) + return sprintf(buf, "0"); + return sprintf(buf, "%d\n", (int)(6000000 / data->fan[ix])); +} + +static ssize_t get_fan1_fault( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct amc6821_data *data = amc6821_update_device(dev); + if (data->stat1 & AMC6821_STAT1_FANS) + return sprintf(buf, "1"); + else + return sprintf(buf, "0"); +} + +static ssize_t set_fan( + struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct amc6821_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int ix = to_sensor_dev_attr(attr)->index; + int ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + val = 1 > val ? 0xFFFF : 6000000/val; + + mutex_lock(&data->update_lock); + data->fan[ix] = (u16) clamp_val(val, 1, 0xFFFF); + if (i2c_smbus_write_byte_data(client, fan_reg_low[ix], + data->fan[ix] & 0xFF)) { + dev_err(&client->dev, "Register write error, aborting.\n"); + count = -EIO; + goto EXIT; + } + if (i2c_smbus_write_byte_data(client, + fan_reg_hi[ix], data->fan[ix] >> 8)) { + dev_err(&client->dev, "Register write error, aborting.\n"); + count = -EIO; + } +EXIT: + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t get_fan1_div( + struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct amc6821_data *data = amc6821_update_device(dev); + return sprintf(buf, "%d\n", data->fan1_div); +} + +static ssize_t set_fan1_div( + struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct amc6821_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int config = kstrtol(buf, 10, &val); + if (config) + return config; + + mutex_lock(&data->update_lock); + config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4); + if (config < 0) { + dev_err(&client->dev, + "Error reading configuration register, aborting.\n"); + count = config; + goto EXIT; + } + switch (val) { + case 2: + config &= ~AMC6821_CONF4_PSPR; + data->fan1_div = 2; + break; + case 4: + config |= AMC6821_CONF4_PSPR; + data->fan1_div = 4; + break; + default: + count = -EINVAL; + goto EXIT; + } + if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, config)) { + dev_err(&client->dev, + "Configuration register write error, aborting.\n"); + count = -EIO; + } +EXIT: + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, + get_temp, NULL, IDX_TEMP1_INPUT); +static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, get_temp, + set_temp, IDX_TEMP1_MIN); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, get_temp, + set_temp, IDX_TEMP1_MAX); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR, get_temp, + set_temp, IDX_TEMP1_CRIT); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, + get_temp_alarm, NULL, IDX_TEMP1_MIN); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, + get_temp_alarm, NULL, IDX_TEMP1_MAX); +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, + get_temp_alarm, NULL, IDX_TEMP1_CRIT); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, + get_temp, NULL, IDX_TEMP2_INPUT); +static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, get_temp, + set_temp, IDX_TEMP2_MIN); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, get_temp, + set_temp, IDX_TEMP2_MAX); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO | S_IWUSR, get_temp, + set_temp, IDX_TEMP2_CRIT); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, + get_temp2_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, + get_temp_alarm, NULL, IDX_TEMP2_MIN); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, + get_temp_alarm, NULL, IDX_TEMP2_MAX); +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, + get_temp_alarm, NULL, IDX_TEMP2_CRIT); +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan, NULL, IDX_FAN1_INPUT); +static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR, + get_fan, set_fan, IDX_FAN1_MIN); +static SENSOR_DEVICE_ATTR(fan1_max, S_IRUGO | S_IWUSR, + get_fan, set_fan, IDX_FAN1_MAX); +static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan1_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR, + get_fan1_div, set_fan1_div, 0); + +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm1, set_pwm1, 0); +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + get_pwm1_enable, set_pwm1_enable, 0); +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO, + get_pwm1_auto_point_pwm, NULL, 0); +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO, + get_pwm1_auto_point_pwm, set_pwm1_auto_point_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IRUGO, + get_pwm1_auto_point_pwm, NULL, 2); +static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IRUGO, + get_pwm1_auto_channels_temp, NULL, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_point1_temp, S_IRUGO, + get_temp_auto_point_temp, NULL, 1, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_point2_temp, S_IWUSR | S_IRUGO, + get_temp_auto_point_temp, set_temp_auto_point_temp, 1, 1); +static SENSOR_DEVICE_ATTR_2(temp1_auto_point3_temp, S_IWUSR | S_IRUGO, + get_temp_auto_point_temp, set_temp_auto_point_temp, 1, 2); + +static SENSOR_DEVICE_ATTR_2(temp2_auto_point1_temp, S_IWUSR | S_IRUGO, + get_temp_auto_point_temp, set_temp_auto_point_temp, 2, 0); +static SENSOR_DEVICE_ATTR_2(temp2_auto_point2_temp, S_IWUSR | S_IRUGO, + get_temp_auto_point_temp, set_temp_auto_point_temp, 2, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_point3_temp, S_IWUSR | S_IRUGO, + get_temp_auto_point_temp, set_temp_auto_point_temp, 2, 2); + +static struct attribute *amc6821_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_max.dev_attr.attr, + &sensor_dev_attr_fan1_fault.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_channels_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point3_temp.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(amc6821); + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int amc6821_detect( + struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int address = client->addr; + int dev_id, comp_id; + + dev_dbg(&adapter->dev, "amc6821_detect called.\n"); + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_dbg(&adapter->dev, + "amc6821: I2C bus doesn't support byte mode, " + "skipping.\n"); + return -ENODEV; + } + + dev_id = i2c_smbus_read_byte_data(client, AMC6821_REG_DEV_ID); + comp_id = i2c_smbus_read_byte_data(client, AMC6821_REG_COMP_ID); + if (dev_id != 0x21 || comp_id != 0x49) { + dev_dbg(&adapter->dev, + "amc6821: detection failed at 0x%02x.\n", + address); + return -ENODEV; + } + + /* + * Bit 7 of the address register is ignored, so we can check the + * ID registers again + */ + dev_id = i2c_smbus_read_byte_data(client, 0x80 | AMC6821_REG_DEV_ID); + comp_id = i2c_smbus_read_byte_data(client, 0x80 | AMC6821_REG_COMP_ID); + if (dev_id != 0x21 || comp_id != 0x49) { + dev_dbg(&adapter->dev, + "amc6821: detection failed at 0x%02x.\n", + address); + return -ENODEV; + } + + dev_info(&adapter->dev, "amc6821: chip found at 0x%02x.\n", address); + strlcpy(info->type, "amc6821", I2C_NAME_SIZE); + + return 0; +} + +static int amc6821_init_client(struct i2c_client *client) +{ + int config; + int err = -EIO; + + if (init) { + config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF4); + + if (config < 0) { + dev_err(&client->dev, + "Error reading configuration register, aborting.\n"); + return err; + } + + config |= AMC6821_CONF4_MODE; + + if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF4, + config)) { + dev_err(&client->dev, + "Configuration register write error, aborting.\n"); + return err; + } + + config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF3); + + if (config < 0) { + dev_err(&client->dev, + "Error reading configuration register, aborting.\n"); + return err; + } + + dev_info(&client->dev, "Revision %d\n", config & 0x0f); + + config &= ~AMC6821_CONF3_THERM_FAN_EN; + + if (i2c_smbus_write_byte_data(client, AMC6821_REG_CONF3, + config)) { + dev_err(&client->dev, + "Configuration register write error, aborting.\n"); + return err; + } + + config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF2); + + if (config < 0) { + dev_err(&client->dev, + "Error reading configuration register, aborting.\n"); + return err; + } + + config &= ~AMC6821_CONF2_RTFIE; + config &= ~AMC6821_CONF2_LTOIE; + config &= ~AMC6821_CONF2_RTOIE; + if (i2c_smbus_write_byte_data(client, + AMC6821_REG_CONF2, config)) { + dev_err(&client->dev, + "Configuration register write error, aborting.\n"); + return err; + } + + config = i2c_smbus_read_byte_data(client, AMC6821_REG_CONF1); + + if (config < 0) { + dev_err(&client->dev, + "Error reading configuration register, aborting.\n"); + return err; + } + + config &= ~AMC6821_CONF1_THERMOVIE; + config &= ~AMC6821_CONF1_FANIE; + config |= AMC6821_CONF1_START; + if (pwminv) + config |= AMC6821_CONF1_PWMINV; + else + config &= ~AMC6821_CONF1_PWMINV; + + if (i2c_smbus_write_byte_data( + client, AMC6821_REG_CONF1, config)) { + dev_err(&client->dev, + "Configuration register write error, aborting.\n"); + return err; + } + } + return 0; +} + +static int amc6821_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct amc6821_data *data; + struct device *hwmon_dev; + int err; + + data = devm_kzalloc(dev, sizeof(struct amc6821_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* + * Initialize the amc6821 chip + */ + err = amc6821_init_client(client); + if (err) + return err; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + amc6821_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id amc6821_id[] = { + { "amc6821", amc6821 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, amc6821_id); + +static struct i2c_driver amc6821_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "amc6821", + }, + .probe = amc6821_probe, + .id_table = amc6821_id, + .detect = amc6821_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(amc6821_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("T. Mertelj "); +MODULE_DESCRIPTION("Texas Instruments amc6821 hwmon driver"); diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c new file mode 100644 index 000000000..b201129a9 --- /dev/null +++ b/drivers/hwmon/applesmc.c @@ -0,0 +1,1389 @@ +/* + * drivers/hwmon/applesmc.c - driver for Apple's SMC (accelerometer, temperature + * sensors, fan control, keyboard backlight control) used in Intel-based Apple + * computers. + * + * Copyright (C) 2007 Nicolas Boichat + * Copyright (C) 2010 Henrik Rydberg + * + * Based on hdaps.c driver: + * Copyright (C) 2005 Robert Love + * Copyright (C) 2005 Jesper Juhl + * + * Fan control based on smcFanControl: + * Copyright (C) 2006 Hendrik Holtmann + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* data port used by Apple SMC */ +#define APPLESMC_DATA_PORT 0x300 +/* command/status port used by Apple SMC */ +#define APPLESMC_CMD_PORT 0x304 + +#define APPLESMC_NR_PORTS 32 /* 0x300-0x31f */ + +#define APPLESMC_MAX_DATA_LENGTH 32 + +/* wait up to 128 ms for a status change. */ +#define APPLESMC_MIN_WAIT 0x0010 +#define APPLESMC_RETRY_WAIT 0x0100 +#define APPLESMC_MAX_WAIT 0x20000 + +#define APPLESMC_READ_CMD 0x10 +#define APPLESMC_WRITE_CMD 0x11 +#define APPLESMC_GET_KEY_BY_INDEX_CMD 0x12 +#define APPLESMC_GET_KEY_TYPE_CMD 0x13 + +#define KEY_COUNT_KEY "#KEY" /* r-o ui32 */ + +#define LIGHT_SENSOR_LEFT_KEY "ALV0" /* r-o {alv (6-10 bytes) */ +#define LIGHT_SENSOR_RIGHT_KEY "ALV1" /* r-o {alv (6-10 bytes) */ +#define BACKLIGHT_KEY "LKSB" /* w-o {lkb (2 bytes) */ + +#define CLAMSHELL_KEY "MSLD" /* r-o ui8 (unused) */ + +#define MOTION_SENSOR_X_KEY "MO_X" /* r-o sp78 (2 bytes) */ +#define MOTION_SENSOR_Y_KEY "MO_Y" /* r-o sp78 (2 bytes) */ +#define MOTION_SENSOR_Z_KEY "MO_Z" /* r-o sp78 (2 bytes) */ +#define MOTION_SENSOR_KEY "MOCN" /* r/w ui16 */ + +#define FANS_COUNT "FNum" /* r-o ui8 */ +#define FANS_MANUAL "FS! " /* r-w ui16 */ +#define FAN_ID_FMT "F%dID" /* r-o char[16] */ + +#define TEMP_SENSOR_TYPE "sp78" + +/* List of keys used to read/write fan speeds */ +static const char *const fan_speed_fmt[] = { + "F%dAc", /* actual speed */ + "F%dMn", /* minimum speed (rw) */ + "F%dMx", /* maximum speed */ + "F%dSf", /* safe speed - not all models */ + "F%dTg", /* target speed (manual: rw) */ +}; + +#define INIT_TIMEOUT_MSECS 5000 /* wait up to 5s for device init ... */ +#define INIT_WAIT_MSECS 50 /* ... in 50ms increments */ + +#define APPLESMC_POLL_INTERVAL 50 /* msecs */ +#define APPLESMC_INPUT_FUZZ 4 /* input event threshold */ +#define APPLESMC_INPUT_FLAT 4 + +#define to_index(attr) (to_sensor_dev_attr(attr)->index & 0xffff) +#define to_option(attr) (to_sensor_dev_attr(attr)->index >> 16) + +/* Dynamic device node attributes */ +struct applesmc_dev_attr { + struct sensor_device_attribute sda; /* hwmon attributes */ + char name[32]; /* room for node file name */ +}; + +/* Dynamic device node group */ +struct applesmc_node_group { + char *format; /* format string */ + void *show; /* show function */ + void *store; /* store function */ + int option; /* function argument */ + struct applesmc_dev_attr *nodes; /* dynamic node array */ +}; + +/* AppleSMC entry - cached register information */ +struct applesmc_entry { + char key[5]; /* four-letter key code */ + u8 valid; /* set when entry is successfully read once */ + u8 len; /* bounded by APPLESMC_MAX_DATA_LENGTH */ + char type[5]; /* four-letter type code */ + u8 flags; /* 0x10: func; 0x40: write; 0x80: read */ +}; + +/* Register lookup and registers common to all SMCs */ +static struct applesmc_registers { + struct mutex mutex; /* register read/write mutex */ + unsigned int key_count; /* number of SMC registers */ + unsigned int fan_count; /* number of fans */ + unsigned int temp_count; /* number of temperature registers */ + unsigned int temp_begin; /* temperature lower index bound */ + unsigned int temp_end; /* temperature upper index bound */ + unsigned int index_count; /* size of temperature index array */ + int num_light_sensors; /* number of light sensors */ + bool has_accelerometer; /* has motion sensor */ + bool has_key_backlight; /* has keyboard backlight */ + bool init_complete; /* true when fully initialized */ + struct applesmc_entry *cache; /* cached key entries */ + const char **index; /* temperature key index */ +} smcreg = { + .mutex = __MUTEX_INITIALIZER(smcreg.mutex), +}; + +static const int debug; +static struct platform_device *pdev; +static s16 rest_x; +static s16 rest_y; +static u8 backlight_state[2]; + +static struct device *hwmon_dev; +static struct input_polled_dev *applesmc_idev; + +/* + * Last index written to key_at_index sysfs file, and value to use for all other + * key_at_index_* sysfs files. + */ +static unsigned int key_at_index; + +static struct workqueue_struct *applesmc_led_wq; + +/* + * wait_read - Wait for a byte to appear on SMC port. Callers must + * hold applesmc_lock. + */ +static int wait_read(void) +{ + u8 status; + int us; + for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) { + udelay(us); + status = inb(APPLESMC_CMD_PORT); + /* read: wait for smc to settle */ + if (status & 0x01) + return 0; + } + + pr_warn("wait_read() fail: 0x%02x\n", status); + return -EIO; +} + +/* + * send_byte - Write to SMC port, retrying when necessary. Callers + * must hold applesmc_lock. + */ +static int send_byte(u8 cmd, u16 port) +{ + u8 status; + int us; + + outb(cmd, port); + for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) { + udelay(us); + status = inb(APPLESMC_CMD_PORT); + /* write: wait for smc to settle */ + if (status & 0x02) + continue; + /* ready: cmd accepted, return */ + if (status & 0x04) + return 0; + /* timeout: give up */ + if (us << 1 == APPLESMC_MAX_WAIT) + break; + /* busy: long wait and resend */ + udelay(APPLESMC_RETRY_WAIT); + outb(cmd, port); + } + + pr_warn("send_byte(0x%02x, 0x%04x) fail: 0x%02x\n", cmd, port, status); + return -EIO; +} + +static int send_command(u8 cmd) +{ + return send_byte(cmd, APPLESMC_CMD_PORT); +} + +static int send_argument(const char *key) +{ + int i; + + for (i = 0; i < 4; i++) + if (send_byte(key[i], APPLESMC_DATA_PORT)) + return -EIO; + return 0; +} + +static int read_smc(u8 cmd, const char *key, u8 *buffer, u8 len) +{ + u8 status, data = 0; + int i; + + if (send_command(cmd) || send_argument(key)) { + pr_warn("%.4s: read arg fail\n", key); + return -EIO; + } + + /* This has no effect on newer (2012) SMCs */ + if (send_byte(len, APPLESMC_DATA_PORT)) { + pr_warn("%.4s: read len fail\n", key); + return -EIO; + } + + for (i = 0; i < len; i++) { + if (wait_read()) { + pr_warn("%.4s: read data[%d] fail\n", key, i); + return -EIO; + } + buffer[i] = inb(APPLESMC_DATA_PORT); + } + + /* Read the data port until bit0 is cleared */ + for (i = 0; i < 16; i++) { + udelay(APPLESMC_MIN_WAIT); + status = inb(APPLESMC_CMD_PORT); + if (!(status & 0x01)) + break; + data = inb(APPLESMC_DATA_PORT); + } + if (i) + pr_warn("flushed %d bytes, last value is: %d\n", i, data); + + return 0; +} + +static int write_smc(u8 cmd, const char *key, const u8 *buffer, u8 len) +{ + int i; + + if (send_command(cmd) || send_argument(key)) { + pr_warn("%s: write arg fail\n", key); + return -EIO; + } + + if (send_byte(len, APPLESMC_DATA_PORT)) { + pr_warn("%.4s: write len fail\n", key); + return -EIO; + } + + for (i = 0; i < len; i++) { + if (send_byte(buffer[i], APPLESMC_DATA_PORT)) { + pr_warn("%s: write data fail\n", key); + return -EIO; + } + } + + return 0; +} + +static int read_register_count(unsigned int *count) +{ + __be32 be; + int ret; + + ret = read_smc(APPLESMC_READ_CMD, KEY_COUNT_KEY, (u8 *)&be, 4); + if (ret) + return ret; + + *count = be32_to_cpu(be); + return 0; +} + +/* + * Serialized I/O + * + * Returns zero on success or a negative error on failure. + * All functions below are concurrency safe - callers should NOT hold lock. + */ + +static int applesmc_read_entry(const struct applesmc_entry *entry, + u8 *buf, u8 len) +{ + int ret; + + if (entry->len != len) + return -EINVAL; + mutex_lock(&smcreg.mutex); + ret = read_smc(APPLESMC_READ_CMD, entry->key, buf, len); + mutex_unlock(&smcreg.mutex); + + return ret; +} + +static int applesmc_write_entry(const struct applesmc_entry *entry, + const u8 *buf, u8 len) +{ + int ret; + + if (entry->len != len) + return -EINVAL; + mutex_lock(&smcreg.mutex); + ret = write_smc(APPLESMC_WRITE_CMD, entry->key, buf, len); + mutex_unlock(&smcreg.mutex); + return ret; +} + +static const struct applesmc_entry *applesmc_get_entry_by_index(int index) +{ + struct applesmc_entry *cache = &smcreg.cache[index]; + u8 key[4], info[6]; + __be32 be; + int ret = 0; + + if (cache->valid) + return cache; + + mutex_lock(&smcreg.mutex); + + if (cache->valid) + goto out; + be = cpu_to_be32(index); + ret = read_smc(APPLESMC_GET_KEY_BY_INDEX_CMD, (u8 *)&be, key, 4); + if (ret) + goto out; + ret = read_smc(APPLESMC_GET_KEY_TYPE_CMD, key, info, 6); + if (ret) + goto out; + + memcpy(cache->key, key, 4); + cache->len = info[0]; + memcpy(cache->type, &info[1], 4); + cache->flags = info[5]; + cache->valid = 1; + +out: + mutex_unlock(&smcreg.mutex); + if (ret) + return ERR_PTR(ret); + return cache; +} + +static int applesmc_get_lower_bound(unsigned int *lo, const char *key) +{ + int begin = 0, end = smcreg.key_count; + const struct applesmc_entry *entry; + + while (begin != end) { + int middle = begin + (end - begin) / 2; + entry = applesmc_get_entry_by_index(middle); + if (IS_ERR(entry)) { + *lo = 0; + return PTR_ERR(entry); + } + if (strcmp(entry->key, key) < 0) + begin = middle + 1; + else + end = middle; + } + + *lo = begin; + return 0; +} + +static int applesmc_get_upper_bound(unsigned int *hi, const char *key) +{ + int begin = 0, end = smcreg.key_count; + const struct applesmc_entry *entry; + + while (begin != end) { + int middle = begin + (end - begin) / 2; + entry = applesmc_get_entry_by_index(middle); + if (IS_ERR(entry)) { + *hi = smcreg.key_count; + return PTR_ERR(entry); + } + if (strcmp(key, entry->key) < 0) + end = middle; + else + begin = middle + 1; + } + + *hi = begin; + return 0; +} + +static const struct applesmc_entry *applesmc_get_entry_by_key(const char *key) +{ + int begin, end; + int ret; + + ret = applesmc_get_lower_bound(&begin, key); + if (ret) + return ERR_PTR(ret); + ret = applesmc_get_upper_bound(&end, key); + if (ret) + return ERR_PTR(ret); + if (end - begin != 1) + return ERR_PTR(-EINVAL); + + return applesmc_get_entry_by_index(begin); +} + +static int applesmc_read_key(const char *key, u8 *buffer, u8 len) +{ + const struct applesmc_entry *entry; + + entry = applesmc_get_entry_by_key(key); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + return applesmc_read_entry(entry, buffer, len); +} + +static int applesmc_write_key(const char *key, const u8 *buffer, u8 len) +{ + const struct applesmc_entry *entry; + + entry = applesmc_get_entry_by_key(key); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + return applesmc_write_entry(entry, buffer, len); +} + +static int applesmc_has_key(const char *key, bool *value) +{ + const struct applesmc_entry *entry; + + entry = applesmc_get_entry_by_key(key); + if (IS_ERR(entry) && PTR_ERR(entry) != -EINVAL) + return PTR_ERR(entry); + + *value = !IS_ERR(entry); + return 0; +} + +/* + * applesmc_read_s16 - Read 16-bit signed big endian register + */ +static int applesmc_read_s16(const char *key, s16 *value) +{ + u8 buffer[2]; + int ret; + + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + + *value = ((s16)buffer[0] << 8) | buffer[1]; + return 0; +} + +/* + * applesmc_device_init - initialize the accelerometer. Can sleep. + */ +static void applesmc_device_init(void) +{ + int total; + u8 buffer[2]; + + if (!smcreg.has_accelerometer) + return; + + for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) { + if (!applesmc_read_key(MOTION_SENSOR_KEY, buffer, 2) && + (buffer[0] != 0x00 || buffer[1] != 0x00)) + return; + buffer[0] = 0xe0; + buffer[1] = 0x00; + applesmc_write_key(MOTION_SENSOR_KEY, buffer, 2); + msleep(INIT_WAIT_MSECS); + } + + pr_warn("failed to init the device\n"); +} + +static int applesmc_init_index(struct applesmc_registers *s) +{ + const struct applesmc_entry *entry; + unsigned int i; + + if (s->index) + return 0; + + s->index = kcalloc(s->temp_count, sizeof(s->index[0]), GFP_KERNEL); + if (!s->index) + return -ENOMEM; + + for (i = s->temp_begin; i < s->temp_end; i++) { + entry = applesmc_get_entry_by_index(i); + if (IS_ERR(entry)) + continue; + if (strcmp(entry->type, TEMP_SENSOR_TYPE)) + continue; + s->index[s->index_count++] = entry->key; + } + + return 0; +} + +/* + * applesmc_init_smcreg_try - Try to initialize register cache. Idempotent. + */ +static int applesmc_init_smcreg_try(void) +{ + struct applesmc_registers *s = &smcreg; + bool left_light_sensor = 0, right_light_sensor = 0; + unsigned int count; + u8 tmp[1]; + int ret; + + if (s->init_complete) + return 0; + + ret = read_register_count(&count); + if (ret) + return ret; + + if (s->cache && s->key_count != count) { + pr_warn("key count changed from %d to %d\n", + s->key_count, count); + kfree(s->cache); + s->cache = NULL; + } + s->key_count = count; + + if (!s->cache) + s->cache = kcalloc(s->key_count, sizeof(*s->cache), GFP_KERNEL); + if (!s->cache) + return -ENOMEM; + + ret = applesmc_read_key(FANS_COUNT, tmp, 1); + if (ret) + return ret; + s->fan_count = tmp[0]; + if (s->fan_count > 10) + s->fan_count = 10; + + ret = applesmc_get_lower_bound(&s->temp_begin, "T"); + if (ret) + return ret; + ret = applesmc_get_lower_bound(&s->temp_end, "U"); + if (ret) + return ret; + s->temp_count = s->temp_end - s->temp_begin; + + ret = applesmc_init_index(s); + if (ret) + return ret; + + ret = applesmc_has_key(LIGHT_SENSOR_LEFT_KEY, &left_light_sensor); + if (ret) + return ret; + ret = applesmc_has_key(LIGHT_SENSOR_RIGHT_KEY, &right_light_sensor); + if (ret) + return ret; + ret = applesmc_has_key(MOTION_SENSOR_KEY, &s->has_accelerometer); + if (ret) + return ret; + ret = applesmc_has_key(BACKLIGHT_KEY, &s->has_key_backlight); + if (ret) + return ret; + + s->num_light_sensors = left_light_sensor + right_light_sensor; + s->init_complete = true; + + pr_info("key=%d fan=%d temp=%d index=%d acc=%d lux=%d kbd=%d\n", + s->key_count, s->fan_count, s->temp_count, s->index_count, + s->has_accelerometer, + s->num_light_sensors, + s->has_key_backlight); + + return 0; +} + +static void applesmc_destroy_smcreg(void) +{ + kfree(smcreg.index); + smcreg.index = NULL; + kfree(smcreg.cache); + smcreg.cache = NULL; + smcreg.init_complete = false; +} + +/* + * applesmc_init_smcreg - Initialize register cache. + * + * Retries until initialization is successful, or the operation times out. + * + */ +static int applesmc_init_smcreg(void) +{ + int ms, ret; + + for (ms = 0; ms < INIT_TIMEOUT_MSECS; ms += INIT_WAIT_MSECS) { + ret = applesmc_init_smcreg_try(); + if (!ret) { + if (ms) + pr_info("init_smcreg() took %d ms\n", ms); + return 0; + } + msleep(INIT_WAIT_MSECS); + } + + applesmc_destroy_smcreg(); + + return ret; +} + +/* Device model stuff */ +static int applesmc_probe(struct platform_device *dev) +{ + int ret; + + ret = applesmc_init_smcreg(); + if (ret) + return ret; + + applesmc_device_init(); + + return 0; +} + +/* Synchronize device with memorized backlight state */ +static int applesmc_pm_resume(struct device *dev) +{ + if (smcreg.has_key_backlight) + applesmc_write_key(BACKLIGHT_KEY, backlight_state, 2); + return 0; +} + +/* Reinitialize device on resume from hibernation */ +static int applesmc_pm_restore(struct device *dev) +{ + applesmc_device_init(); + return applesmc_pm_resume(dev); +} + +static const struct dev_pm_ops applesmc_pm_ops = { + .resume = applesmc_pm_resume, + .restore = applesmc_pm_restore, +}; + +static struct platform_driver applesmc_driver = { + .probe = applesmc_probe, + .driver = { + .name = "applesmc", + .pm = &applesmc_pm_ops, + }, +}; + +/* + * applesmc_calibrate - Set our "resting" values. Callers must + * hold applesmc_lock. + */ +static void applesmc_calibrate(void) +{ + applesmc_read_s16(MOTION_SENSOR_X_KEY, &rest_x); + applesmc_read_s16(MOTION_SENSOR_Y_KEY, &rest_y); + rest_x = -rest_x; +} + +static void applesmc_idev_poll(struct input_polled_dev *dev) +{ + struct input_dev *idev = dev->input; + s16 x, y; + + if (applesmc_read_s16(MOTION_SENSOR_X_KEY, &x)) + return; + if (applesmc_read_s16(MOTION_SENSOR_Y_KEY, &y)) + return; + + x = -x; + input_report_abs(idev, ABS_X, x - rest_x); + input_report_abs(idev, ABS_Y, y - rest_y); + input_sync(idev); +} + +/* Sysfs Files */ + +static ssize_t applesmc_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "applesmc\n"); +} + +static ssize_t applesmc_position_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + s16 x, y, z; + + ret = applesmc_read_s16(MOTION_SENSOR_X_KEY, &x); + if (ret) + goto out; + ret = applesmc_read_s16(MOTION_SENSOR_Y_KEY, &y); + if (ret) + goto out; + ret = applesmc_read_s16(MOTION_SENSOR_Z_KEY, &z); + if (ret) + goto out; + +out: + if (ret) + return ret; + else + return snprintf(buf, PAGE_SIZE, "(%d,%d,%d)\n", x, y, z); +} + +static ssize_t applesmc_light_show(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) +{ + const struct applesmc_entry *entry; + static int data_length; + int ret; + u8 left = 0, right = 0; + u8 buffer[10]; + + if (!data_length) { + entry = applesmc_get_entry_by_key(LIGHT_SENSOR_LEFT_KEY); + if (IS_ERR(entry)) + return PTR_ERR(entry); + if (entry->len > 10) + return -ENXIO; + data_length = entry->len; + pr_info("light sensor data length set to %d\n", data_length); + } + + ret = applesmc_read_key(LIGHT_SENSOR_LEFT_KEY, buffer, data_length); + if (ret) + goto out; + /* newer macbooks report a single 10-bit bigendian value */ + if (data_length == 10) { + left = be16_to_cpu(*(__be16 *)(buffer + 6)) >> 2; + goto out; + } + left = buffer[2]; + + ret = applesmc_read_key(LIGHT_SENSOR_RIGHT_KEY, buffer, data_length); + if (ret) + goto out; + right = buffer[2]; + +out: + if (ret) + return ret; + else + return snprintf(sysfsbuf, PAGE_SIZE, "(%d,%d)\n", left, right); +} + +/* Displays sensor key as label */ +static ssize_t applesmc_show_sensor_label(struct device *dev, + struct device_attribute *devattr, char *sysfsbuf) +{ + const char *key = smcreg.index[to_index(devattr)]; + + return snprintf(sysfsbuf, PAGE_SIZE, "%s\n", key); +} + +/* Displays degree Celsius * 1000 */ +static ssize_t applesmc_show_temperature(struct device *dev, + struct device_attribute *devattr, char *sysfsbuf) +{ + const char *key = smcreg.index[to_index(devattr)]; + int ret; + s16 value; + int temp; + + ret = applesmc_read_s16(key, &value); + if (ret) + return ret; + + temp = 250 * (value >> 6); + + return snprintf(sysfsbuf, PAGE_SIZE, "%d\n", temp); +} + +static ssize_t applesmc_show_fan_speed(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) +{ + int ret; + unsigned int speed = 0; + char newkey[5]; + u8 buffer[2]; + + scnprintf(newkey, sizeof(newkey), fan_speed_fmt[to_option(attr)], + to_index(attr)); + + ret = applesmc_read_key(newkey, buffer, 2); + if (ret) + return ret; + + speed = ((buffer[0] << 8 | buffer[1]) >> 2); + return snprintf(sysfsbuf, PAGE_SIZE, "%u\n", speed); +} + +static ssize_t applesmc_store_fan_speed(struct device *dev, + struct device_attribute *attr, + const char *sysfsbuf, size_t count) +{ + int ret; + unsigned long speed; + char newkey[5]; + u8 buffer[2]; + + if (kstrtoul(sysfsbuf, 10, &speed) < 0 || speed >= 0x4000) + return -EINVAL; /* Bigger than a 14-bit value */ + + scnprintf(newkey, sizeof(newkey), fan_speed_fmt[to_option(attr)], + to_index(attr)); + + buffer[0] = (speed >> 6) & 0xff; + buffer[1] = (speed << 2) & 0xff; + ret = applesmc_write_key(newkey, buffer, 2); + + if (ret) + return ret; + else + return count; +} + +static ssize_t applesmc_show_fan_manual(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) +{ + int ret; + u16 manual = 0; + u8 buffer[2]; + + ret = applesmc_read_key(FANS_MANUAL, buffer, 2); + if (ret) + return ret; + + manual = ((buffer[0] << 8 | buffer[1]) >> to_index(attr)) & 0x01; + return snprintf(sysfsbuf, PAGE_SIZE, "%d\n", manual); +} + +static ssize_t applesmc_store_fan_manual(struct device *dev, + struct device_attribute *attr, + const char *sysfsbuf, size_t count) +{ + int ret; + u8 buffer[2]; + unsigned long input; + u16 val; + + if (kstrtoul(sysfsbuf, 10, &input) < 0) + return -EINVAL; + + ret = applesmc_read_key(FANS_MANUAL, buffer, 2); + if (ret) + goto out; + + val = (buffer[0] << 8 | buffer[1]); + + if (input) + val = val | (0x01 << to_index(attr)); + else + val = val & ~(0x01 << to_index(attr)); + + buffer[0] = (val >> 8) & 0xFF; + buffer[1] = val & 0xFF; + + ret = applesmc_write_key(FANS_MANUAL, buffer, 2); + +out: + if (ret) + return ret; + else + return count; +} + +static ssize_t applesmc_show_fan_position(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) +{ + int ret; + char newkey[5]; + u8 buffer[17]; + + scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, to_index(attr)); + + ret = applesmc_read_key(newkey, buffer, 16); + buffer[16] = 0; + + if (ret) + return ret; + else + return snprintf(sysfsbuf, PAGE_SIZE, "%s\n", buffer+4); +} + +static ssize_t applesmc_calibrate_show(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) +{ + return snprintf(sysfsbuf, PAGE_SIZE, "(%d,%d)\n", rest_x, rest_y); +} + +static ssize_t applesmc_calibrate_store(struct device *dev, + struct device_attribute *attr, const char *sysfsbuf, size_t count) +{ + applesmc_calibrate(); + + return count; +} + +static void applesmc_backlight_set(struct work_struct *work) +{ + applesmc_write_key(BACKLIGHT_KEY, backlight_state, 2); +} +static DECLARE_WORK(backlight_work, &applesmc_backlight_set); + +static void applesmc_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + int ret; + + backlight_state[0] = value; + ret = queue_work(applesmc_led_wq, &backlight_work); + + if (debug && (!ret)) + dev_dbg(led_cdev->dev, "work was already on the queue.\n"); +} + +static ssize_t applesmc_key_count_show(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) +{ + int ret; + u8 buffer[4]; + u32 count; + + ret = applesmc_read_key(KEY_COUNT_KEY, buffer, 4); + if (ret) + return ret; + + count = ((u32)buffer[0]<<24) + ((u32)buffer[1]<<16) + + ((u32)buffer[2]<<8) + buffer[3]; + return snprintf(sysfsbuf, PAGE_SIZE, "%d\n", count); +} + +static ssize_t applesmc_key_at_index_read_show(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) +{ + const struct applesmc_entry *entry; + int ret; + + entry = applesmc_get_entry_by_index(key_at_index); + if (IS_ERR(entry)) + return PTR_ERR(entry); + ret = applesmc_read_entry(entry, sysfsbuf, entry->len); + if (ret) + return ret; + + return entry->len; +} + +static ssize_t applesmc_key_at_index_data_length_show(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) +{ + const struct applesmc_entry *entry; + + entry = applesmc_get_entry_by_index(key_at_index); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + return snprintf(sysfsbuf, PAGE_SIZE, "%d\n", entry->len); +} + +static ssize_t applesmc_key_at_index_type_show(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) +{ + const struct applesmc_entry *entry; + + entry = applesmc_get_entry_by_index(key_at_index); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + return snprintf(sysfsbuf, PAGE_SIZE, "%s\n", entry->type); +} + +static ssize_t applesmc_key_at_index_name_show(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) +{ + const struct applesmc_entry *entry; + + entry = applesmc_get_entry_by_index(key_at_index); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + return snprintf(sysfsbuf, PAGE_SIZE, "%s\n", entry->key); +} + +static ssize_t applesmc_key_at_index_show(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) +{ + return snprintf(sysfsbuf, PAGE_SIZE, "%d\n", key_at_index); +} + +static ssize_t applesmc_key_at_index_store(struct device *dev, + struct device_attribute *attr, const char *sysfsbuf, size_t count) +{ + unsigned long newkey; + + if (kstrtoul(sysfsbuf, 10, &newkey) < 0 + || newkey >= smcreg.key_count) + return -EINVAL; + + key_at_index = newkey; + return count; +} + +static struct led_classdev applesmc_backlight = { + .name = "smc::kbd_backlight", + .default_trigger = "nand-disk", + .brightness_set = applesmc_brightness_set, +}; + +static struct applesmc_node_group info_group[] = { + { "name", applesmc_name_show }, + { "key_count", applesmc_key_count_show }, + { "key_at_index", applesmc_key_at_index_show, applesmc_key_at_index_store }, + { "key_at_index_name", applesmc_key_at_index_name_show }, + { "key_at_index_type", applesmc_key_at_index_type_show }, + { "key_at_index_data_length", applesmc_key_at_index_data_length_show }, + { "key_at_index_data", applesmc_key_at_index_read_show }, + { } +}; + +static struct applesmc_node_group accelerometer_group[] = { + { "position", applesmc_position_show }, + { "calibrate", applesmc_calibrate_show, applesmc_calibrate_store }, + { } +}; + +static struct applesmc_node_group light_sensor_group[] = { + { "light", applesmc_light_show }, + { } +}; + +static struct applesmc_node_group fan_group[] = { + { "fan%d_label", applesmc_show_fan_position }, + { "fan%d_input", applesmc_show_fan_speed, NULL, 0 }, + { "fan%d_min", applesmc_show_fan_speed, applesmc_store_fan_speed, 1 }, + { "fan%d_max", applesmc_show_fan_speed, NULL, 2 }, + { "fan%d_safe", applesmc_show_fan_speed, NULL, 3 }, + { "fan%d_output", applesmc_show_fan_speed, applesmc_store_fan_speed, 4 }, + { "fan%d_manual", applesmc_show_fan_manual, applesmc_store_fan_manual }, + { } +}; + +static struct applesmc_node_group temp_group[] = { + { "temp%d_label", applesmc_show_sensor_label }, + { "temp%d_input", applesmc_show_temperature }, + { } +}; + +/* Module stuff */ + +/* + * applesmc_destroy_nodes - remove files and free associated memory + */ +static void applesmc_destroy_nodes(struct applesmc_node_group *groups) +{ + struct applesmc_node_group *grp; + struct applesmc_dev_attr *node; + + for (grp = groups; grp->nodes; grp++) { + for (node = grp->nodes; node->sda.dev_attr.attr.name; node++) + sysfs_remove_file(&pdev->dev.kobj, + &node->sda.dev_attr.attr); + kfree(grp->nodes); + grp->nodes = NULL; + } +} + +/* + * applesmc_create_nodes - create a two-dimensional group of sysfs files + */ +static int applesmc_create_nodes(struct applesmc_node_group *groups, int num) +{ + struct applesmc_node_group *grp; + struct applesmc_dev_attr *node; + struct attribute *attr; + int ret, i; + + for (grp = groups; grp->format; grp++) { + grp->nodes = kcalloc(num + 1, sizeof(*node), GFP_KERNEL); + if (!grp->nodes) { + ret = -ENOMEM; + goto out; + } + for (i = 0; i < num; i++) { + node = &grp->nodes[i]; + scnprintf(node->name, sizeof(node->name), grp->format, + i + 1); + node->sda.index = (grp->option << 16) | (i & 0xffff); + node->sda.dev_attr.show = grp->show; + node->sda.dev_attr.store = grp->store; + attr = &node->sda.dev_attr.attr; + sysfs_attr_init(attr); + attr->name = node->name; + attr->mode = S_IRUGO | (grp->store ? S_IWUSR : 0); + ret = sysfs_create_file(&pdev->dev.kobj, attr); + if (ret) { + attr->name = NULL; + goto out; + } + } + } + + return 0; +out: + applesmc_destroy_nodes(groups); + return ret; +} + +/* Create accelerometer resources */ +static int applesmc_create_accelerometer(void) +{ + struct input_dev *idev; + int ret; + + if (!smcreg.has_accelerometer) + return 0; + + ret = applesmc_create_nodes(accelerometer_group, 1); + if (ret) + goto out; + + applesmc_idev = input_allocate_polled_device(); + if (!applesmc_idev) { + ret = -ENOMEM; + goto out_sysfs; + } + + applesmc_idev->poll = applesmc_idev_poll; + applesmc_idev->poll_interval = APPLESMC_POLL_INTERVAL; + + /* initial calibrate for the input device */ + applesmc_calibrate(); + + /* initialize the input device */ + idev = applesmc_idev->input; + idev->name = "applesmc"; + idev->id.bustype = BUS_HOST; + idev->dev.parent = &pdev->dev; + idev->evbit[0] = BIT_MASK(EV_ABS); + input_set_abs_params(idev, ABS_X, + -256, 256, APPLESMC_INPUT_FUZZ, APPLESMC_INPUT_FLAT); + input_set_abs_params(idev, ABS_Y, + -256, 256, APPLESMC_INPUT_FUZZ, APPLESMC_INPUT_FLAT); + + ret = input_register_polled_device(applesmc_idev); + if (ret) + goto out_idev; + + return 0; + +out_idev: + input_free_polled_device(applesmc_idev); + +out_sysfs: + applesmc_destroy_nodes(accelerometer_group); + +out: + pr_warn("driver init failed (ret=%d)!\n", ret); + return ret; +} + +/* Release all resources used by the accelerometer */ +static void applesmc_release_accelerometer(void) +{ + if (!smcreg.has_accelerometer) + return; + input_unregister_polled_device(applesmc_idev); + input_free_polled_device(applesmc_idev); + applesmc_destroy_nodes(accelerometer_group); +} + +static int applesmc_create_light_sensor(void) +{ + if (!smcreg.num_light_sensors) + return 0; + return applesmc_create_nodes(light_sensor_group, 1); +} + +static void applesmc_release_light_sensor(void) +{ + if (!smcreg.num_light_sensors) + return; + applesmc_destroy_nodes(light_sensor_group); +} + +static int applesmc_create_key_backlight(void) +{ + if (!smcreg.has_key_backlight) + return 0; + applesmc_led_wq = create_singlethread_workqueue("applesmc-led"); + if (!applesmc_led_wq) + return -ENOMEM; + return led_classdev_register(&pdev->dev, &applesmc_backlight); +} + +static void applesmc_release_key_backlight(void) +{ + if (!smcreg.has_key_backlight) + return; + led_classdev_unregister(&applesmc_backlight); + destroy_workqueue(applesmc_led_wq); +} + +static int applesmc_dmi_match(const struct dmi_system_id *id) +{ + return 1; +} + +/* + * Note that DMI_MATCH(...,"MacBook") will match "MacBookPro1,1". + * So we need to put "Apple MacBook Pro" before "Apple MacBook". + */ +static const struct dmi_system_id applesmc_whitelist[] __initconst = { + { applesmc_dmi_match, "Apple MacBook Air", { + DMI_MATCH(DMI_BOARD_VENDOR, "Apple"), + DMI_MATCH(DMI_PRODUCT_NAME, "MacBookAir") }, + }, + { applesmc_dmi_match, "Apple MacBook Pro", { + DMI_MATCH(DMI_BOARD_VENDOR, "Apple"), + DMI_MATCH(DMI_PRODUCT_NAME, "MacBookPro") }, + }, + { applesmc_dmi_match, "Apple MacBook", { + DMI_MATCH(DMI_BOARD_VENDOR, "Apple"), + DMI_MATCH(DMI_PRODUCT_NAME, "MacBook") }, + }, + { applesmc_dmi_match, "Apple Macmini", { + DMI_MATCH(DMI_BOARD_VENDOR, "Apple"), + DMI_MATCH(DMI_PRODUCT_NAME, "Macmini") }, + }, + { applesmc_dmi_match, "Apple MacPro", { + DMI_MATCH(DMI_BOARD_VENDOR, "Apple"), + DMI_MATCH(DMI_PRODUCT_NAME, "MacPro") }, + }, + { applesmc_dmi_match, "Apple iMac", { + DMI_MATCH(DMI_BOARD_VENDOR, "Apple"), + DMI_MATCH(DMI_PRODUCT_NAME, "iMac") }, + }, + { .ident = NULL } +}; + +static int __init applesmc_init(void) +{ + int ret; + + if (!dmi_check_system(applesmc_whitelist)) { + pr_warn("supported laptop not found!\n"); + ret = -ENODEV; + goto out; + } + + if (!request_region(APPLESMC_DATA_PORT, APPLESMC_NR_PORTS, + "applesmc")) { + ret = -ENXIO; + goto out; + } + + ret = platform_driver_register(&applesmc_driver); + if (ret) + goto out_region; + + pdev = platform_device_register_simple("applesmc", APPLESMC_DATA_PORT, + NULL, 0); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + goto out_driver; + } + + /* create register cache */ + ret = applesmc_init_smcreg(); + if (ret) + goto out_device; + + ret = applesmc_create_nodes(info_group, 1); + if (ret) + goto out_smcreg; + + ret = applesmc_create_nodes(fan_group, smcreg.fan_count); + if (ret) + goto out_info; + + ret = applesmc_create_nodes(temp_group, smcreg.index_count); + if (ret) + goto out_fans; + + ret = applesmc_create_accelerometer(); + if (ret) + goto out_temperature; + + ret = applesmc_create_light_sensor(); + if (ret) + goto out_accelerometer; + + ret = applesmc_create_key_backlight(); + if (ret) + goto out_light_sysfs; + + hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(hwmon_dev)) { + ret = PTR_ERR(hwmon_dev); + goto out_light_ledclass; + } + + return 0; + +out_light_ledclass: + applesmc_release_key_backlight(); +out_light_sysfs: + applesmc_release_light_sensor(); +out_accelerometer: + applesmc_release_accelerometer(); +out_temperature: + applesmc_destroy_nodes(temp_group); +out_fans: + applesmc_destroy_nodes(fan_group); +out_info: + applesmc_destroy_nodes(info_group); +out_smcreg: + applesmc_destroy_smcreg(); +out_device: + platform_device_unregister(pdev); +out_driver: + platform_driver_unregister(&applesmc_driver); +out_region: + release_region(APPLESMC_DATA_PORT, APPLESMC_NR_PORTS); +out: + pr_warn("driver init failed (ret=%d)!\n", ret); + return ret; +} + +static void __exit applesmc_exit(void) +{ + hwmon_device_unregister(hwmon_dev); + applesmc_release_key_backlight(); + applesmc_release_light_sensor(); + applesmc_release_accelerometer(); + applesmc_destroy_nodes(temp_group); + applesmc_destroy_nodes(fan_group); + applesmc_destroy_nodes(info_group); + applesmc_destroy_smcreg(); + platform_device_unregister(pdev); + platform_driver_unregister(&applesmc_driver); + release_region(APPLESMC_DATA_PORT, APPLESMC_NR_PORTS); +} + +module_init(applesmc_init); +module_exit(applesmc_exit); + +MODULE_AUTHOR("Nicolas Boichat"); +MODULE_DESCRIPTION("Apple SMC"); +MODULE_LICENSE("GPL v2"); +MODULE_DEVICE_TABLE(dmi, applesmc_whitelist); diff --git a/drivers/hwmon/asb100.c b/drivers/hwmon/asb100.c new file mode 100644 index 000000000..62e191311 --- /dev/null +++ b/drivers/hwmon/asb100.c @@ -0,0 +1,1025 @@ +/* + * asb100.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * + * Copyright (C) 2004 Mark M. Hoffman + * + * (derived from w83781d.c) + * + * Copyright (C) 1998 - 2003 Frodo Looijaard , + * Philip Edelbrock , and + * Mark Studebaker + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * This driver supports the hardware sensor chips: Asus ASB100 and + * ASB100-A "BACH". + * + * ASB100-A supports pwm1, while plain ASB100 does not. There is no known + * way for the driver to tell which one is there. + * + * Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA + * asb100 7 3 1 4 0x31 0x0694 yes no + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lm75.h" + +/* I2C addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2d, I2C_CLIENT_END }; + +static unsigned short force_subclients[4]; +module_param_array(force_subclients, short, NULL, 0); +MODULE_PARM_DESC(force_subclients, + "List of subclient addresses: {bus, clientaddr, subclientaddr1, subclientaddr2}"); + +/* Voltage IN registers 0-6 */ +#define ASB100_REG_IN(nr) (0x20 + (nr)) +#define ASB100_REG_IN_MAX(nr) (0x2b + (nr * 2)) +#define ASB100_REG_IN_MIN(nr) (0x2c + (nr * 2)) + +/* FAN IN registers 1-3 */ +#define ASB100_REG_FAN(nr) (0x28 + (nr)) +#define ASB100_REG_FAN_MIN(nr) (0x3b + (nr)) + +/* TEMPERATURE registers 1-4 */ +static const u16 asb100_reg_temp[] = {0, 0x27, 0x150, 0x250, 0x17}; +static const u16 asb100_reg_temp_max[] = {0, 0x39, 0x155, 0x255, 0x18}; +static const u16 asb100_reg_temp_hyst[] = {0, 0x3a, 0x153, 0x253, 0x19}; + +#define ASB100_REG_TEMP(nr) (asb100_reg_temp[nr]) +#define ASB100_REG_TEMP_MAX(nr) (asb100_reg_temp_max[nr]) +#define ASB100_REG_TEMP_HYST(nr) (asb100_reg_temp_hyst[nr]) + +#define ASB100_REG_TEMP2_CONFIG 0x0152 +#define ASB100_REG_TEMP3_CONFIG 0x0252 + + +#define ASB100_REG_CONFIG 0x40 +#define ASB100_REG_ALARM1 0x41 +#define ASB100_REG_ALARM2 0x42 +#define ASB100_REG_SMIM1 0x43 +#define ASB100_REG_SMIM2 0x44 +#define ASB100_REG_VID_FANDIV 0x47 +#define ASB100_REG_I2C_ADDR 0x48 +#define ASB100_REG_CHIPID 0x49 +#define ASB100_REG_I2C_SUBADDR 0x4a +#define ASB100_REG_PIN 0x4b +#define ASB100_REG_IRQ 0x4c +#define ASB100_REG_BANK 0x4e +#define ASB100_REG_CHIPMAN 0x4f + +#define ASB100_REG_WCHIPID 0x58 + +/* bit 7 -> enable, bits 0-3 -> duty cycle */ +#define ASB100_REG_PWM1 0x59 + +/* + * CONVERSIONS + * Rounding and limit checking is only done on the TO_REG variants. + */ + +/* These constants are a guess, consistent w/ w83781d */ +#define ASB100_IN_MIN 0 +#define ASB100_IN_MAX 4080 + +/* + * IN: 1/1000 V (0V to 4.08V) + * REG: 16mV/bit + */ +static u8 IN_TO_REG(unsigned val) +{ + unsigned nval = clamp_val(val, ASB100_IN_MIN, ASB100_IN_MAX); + return (nval + 8) / 16; +} + +static unsigned IN_FROM_REG(u8 reg) +{ + return reg * 16; +} + +static u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == -1) + return 0; + if (rpm == 0) + return 255; + rpm = clamp_val(rpm, 1, 1000000); + return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 254); +} + +static int FAN_FROM_REG(u8 val, int div) +{ + return val == 0 ? -1 : val == 255 ? 0 : 1350000 / (val * div); +} + +/* These constants are a guess, consistent w/ w83781d */ +#define ASB100_TEMP_MIN -128000 +#define ASB100_TEMP_MAX 127000 + +/* + * TEMP: 0.001C/bit (-128C to +127C) + * REG: 1C/bit, two's complement + */ +static u8 TEMP_TO_REG(long temp) +{ + int ntemp = clamp_val(temp, ASB100_TEMP_MIN, ASB100_TEMP_MAX); + ntemp += (ntemp < 0 ? -500 : 500); + return (u8)(ntemp / 1000); +} + +static int TEMP_FROM_REG(u8 reg) +{ + return (s8)reg * 1000; +} + +/* + * PWM: 0 - 255 per sensors documentation + * REG: (6.25% duty cycle per bit) + */ +static u8 ASB100_PWM_TO_REG(int pwm) +{ + pwm = clamp_val(pwm, 0, 255); + return (u8)(pwm / 16); +} + +static int ASB100_PWM_FROM_REG(u8 reg) +{ + return reg * 16; +} + +#define DIV_FROM_REG(val) (1 << (val)) + +/* + * FAN DIV: 1, 2, 4, or 8 (defaults to 2) + * REG: 0, 1, 2, or 3 (respectively) (defaults to 1) + */ +static u8 DIV_TO_REG(long val) +{ + return val == 8 ? 3 : val == 4 ? 2 : val == 1 ? 0 : 1; +} + +/* + * For each registered client, we need to keep some data in memory. That + * data is pointed to by client->data. The structure itself is + * dynamically allocated, at the same time the client itself is allocated. + */ +struct asb100_data { + struct device *hwmon_dev; + struct mutex lock; + + struct mutex update_lock; + unsigned long last_updated; /* In jiffies */ + + /* array of 2 pointers to subclients */ + struct i2c_client *lm75[2]; + + char valid; /* !=0 if following fields are valid */ + u8 in[7]; /* Register value */ + u8 in_max[7]; /* Register value */ + u8 in_min[7]; /* Register value */ + u8 fan[3]; /* Register value */ + u8 fan_min[3]; /* Register value */ + u16 temp[4]; /* Register value (0 and 3 are u8 only) */ + u16 temp_max[4]; /* Register value (0 and 3 are u8 only) */ + u16 temp_hyst[4]; /* Register value (0 and 3 are u8 only) */ + u8 fan_div[3]; /* Register encoding, right justified */ + u8 pwm; /* Register encoding */ + u8 vid; /* Register encoding, combined */ + u32 alarms; /* Register encoding, combined */ + u8 vrm; +}; + +static int asb100_read_value(struct i2c_client *client, u16 reg); +static void asb100_write_value(struct i2c_client *client, u16 reg, u16 val); + +static int asb100_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int asb100_detect(struct i2c_client *client, + struct i2c_board_info *info); +static int asb100_remove(struct i2c_client *client); +static struct asb100_data *asb100_update_device(struct device *dev); +static void asb100_init_client(struct i2c_client *client); + +static const struct i2c_device_id asb100_id[] = { + { "asb100", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, asb100_id); + +static struct i2c_driver asb100_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "asb100", + }, + .probe = asb100_probe, + .remove = asb100_remove, + .id_table = asb100_id, + .detect = asb100_detect, + .address_list = normal_i2c, +}; + +/* 7 Voltages */ +#define show_in_reg(reg) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + int nr = to_sensor_dev_attr(attr)->index; \ + struct asb100_data *data = asb100_update_device(dev); \ + return sprintf(buf, "%d\n", IN_FROM_REG(data->reg[nr])); \ +} + +show_in_reg(in) +show_in_reg(in_min) +show_in_reg(in_max) + +#define set_in_reg(REG, reg) \ +static ssize_t set_in_##reg(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + int nr = to_sensor_dev_attr(attr)->index; \ + struct i2c_client *client = to_i2c_client(dev); \ + struct asb100_data *data = i2c_get_clientdata(client); \ + unsigned long val; \ + int err = kstrtoul(buf, 10, &val); \ + if (err) \ + return err; \ + mutex_lock(&data->update_lock); \ + data->in_##reg[nr] = IN_TO_REG(val); \ + asb100_write_value(client, ASB100_REG_IN_##REG(nr), \ + data->in_##reg[nr]); \ + mutex_unlock(&data->update_lock); \ + return count; \ +} + +set_in_reg(MIN, min) +set_in_reg(MAX, max) + +#define sysfs_in(offset) \ +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, \ + show_in, NULL, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \ + show_in_min, set_in_min, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \ + show_in_max, set_in_max, offset) + +sysfs_in(0); +sysfs_in(1); +sysfs_in(2); +sysfs_in(3); +sysfs_in(4); +sysfs_in(5); +sysfs_in(6); + +/* 3 Fans */ +static ssize_t show_fan(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct asb100_data *data = asb100_update_device(dev); + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr], + DIV_FROM_REG(data->fan_div[nr]))); +} + +static ssize_t show_fan_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct asb100_data *data = asb100_update_device(dev); + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr]))); +} + +static ssize_t show_fan_div(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct asb100_data *data = asb100_update_device(dev); + return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[nr])); +} + +static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct asb100_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_min[nr] = FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); + asb100_write_value(client, ASB100_REG_FAN_MIN(nr), data->fan_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Note: we save and restore the fan minimum here, because its value is + * determined in part by the fan divisor. This follows the principle of + * least surprise; the user doesn't expect the fan minimum to change just + * because the divisor changed. + */ +static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct asb100_data *data = i2c_get_clientdata(client); + unsigned long min; + int reg; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + min = FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr])); + data->fan_div[nr] = DIV_TO_REG(val); + + switch (nr) { + case 0: /* fan 1 */ + reg = asb100_read_value(client, ASB100_REG_VID_FANDIV); + reg = (reg & 0xcf) | (data->fan_div[0] << 4); + asb100_write_value(client, ASB100_REG_VID_FANDIV, reg); + break; + + case 1: /* fan 2 */ + reg = asb100_read_value(client, ASB100_REG_VID_FANDIV); + reg = (reg & 0x3f) | (data->fan_div[1] << 6); + asb100_write_value(client, ASB100_REG_VID_FANDIV, reg); + break; + + case 2: /* fan 3 */ + reg = asb100_read_value(client, ASB100_REG_PIN); + reg = (reg & 0x3f) | (data->fan_div[2] << 6); + asb100_write_value(client, ASB100_REG_PIN, reg); + break; + } + + data->fan_min[nr] = + FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr])); + asb100_write_value(client, ASB100_REG_FAN_MIN(nr), data->fan_min[nr]); + + mutex_unlock(&data->update_lock); + + return count; +} + +#define sysfs_fan(offset) \ +static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \ + show_fan, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ + show_fan_min, set_fan_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR, \ + show_fan_div, set_fan_div, offset - 1) + +sysfs_fan(1); +sysfs_fan(2); +sysfs_fan(3); + +/* 4 Temp. Sensors */ +static int sprintf_temp_from_reg(u16 reg, char *buf, int nr) +{ + int ret = 0; + + switch (nr) { + case 1: case 2: + ret = sprintf(buf, "%d\n", LM75_TEMP_FROM_REG(reg)); + break; + case 0: case 3: default: + ret = sprintf(buf, "%d\n", TEMP_FROM_REG(reg)); + break; + } + return ret; +} + +#define show_temp_reg(reg) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + int nr = to_sensor_dev_attr(attr)->index; \ + struct asb100_data *data = asb100_update_device(dev); \ + return sprintf_temp_from_reg(data->reg[nr], buf, nr); \ +} + +show_temp_reg(temp); +show_temp_reg(temp_max); +show_temp_reg(temp_hyst); + +#define set_temp_reg(REG, reg) \ +static ssize_t set_##reg(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + int nr = to_sensor_dev_attr(attr)->index; \ + struct i2c_client *client = to_i2c_client(dev); \ + struct asb100_data *data = i2c_get_clientdata(client); \ + long val; \ + int err = kstrtol(buf, 10, &val); \ + if (err) \ + return err; \ + mutex_lock(&data->update_lock); \ + switch (nr) { \ + case 1: case 2: \ + data->reg[nr] = LM75_TEMP_TO_REG(val); \ + break; \ + case 0: case 3: default: \ + data->reg[nr] = TEMP_TO_REG(val); \ + break; \ + } \ + asb100_write_value(client, ASB100_REG_TEMP_##REG(nr+1), \ + data->reg[nr]); \ + mutex_unlock(&data->update_lock); \ + return count; \ +} + +set_temp_reg(MAX, temp_max); +set_temp_reg(HYST, temp_hyst); + +#define sysfs_temp(num) \ +static SENSOR_DEVICE_ATTR(temp##num##_input, S_IRUGO, \ + show_temp, NULL, num - 1); \ +static SENSOR_DEVICE_ATTR(temp##num##_max, S_IRUGO | S_IWUSR, \ + show_temp_max, set_temp_max, num - 1); \ +static SENSOR_DEVICE_ATTR(temp##num##_max_hyst, S_IRUGO | S_IWUSR, \ + show_temp_hyst, set_temp_hyst, num - 1) + +sysfs_temp(1); +sysfs_temp(2); +sysfs_temp(3); +sysfs_temp(4); + +/* VID */ +static ssize_t cpu0_vid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct asb100_data *data = asb100_update_device(dev); + return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); +} + +static DEVICE_ATTR_RO(cpu0_vid); + +/* VRM */ +static ssize_t vrm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct asb100_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", data->vrm); +} + +static ssize_t vrm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct asb100_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 255) + return -EINVAL; + + data->vrm = val; + return count; +} + +/* Alarms */ +static DEVICE_ATTR_RW(vrm); + +static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct asb100_data *data = asb100_update_device(dev); + return sprintf(buf, "%u\n", data->alarms); +} + +static DEVICE_ATTR_RO(alarms); + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct asb100_data *data = asb100_update_device(dev); + return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1); +} +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 13); + +/* 1 PWM */ +static ssize_t pwm1_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct asb100_data *data = asb100_update_device(dev); + return sprintf(buf, "%d\n", ASB100_PWM_FROM_REG(data->pwm & 0x0f)); +} + +static ssize_t pwm1_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct asb100_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->pwm &= 0x80; /* keep the enable bit */ + data->pwm |= (0x0f & ASB100_PWM_TO_REG(val)); + asb100_write_value(client, ASB100_REG_PWM1, data->pwm); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t pwm1_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct asb100_data *data = asb100_update_device(dev); + return sprintf(buf, "%d\n", (data->pwm & 0x80) ? 1 : 0); +} + +static ssize_t pwm1_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct asb100_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->pwm &= 0x0f; /* keep the duty cycle bits */ + data->pwm |= (val ? 0x80 : 0x00); + asb100_write_value(client, ASB100_REG_PWM1, data->pwm); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RW(pwm1); +static DEVICE_ATTR_RW(pwm1_enable); + +static struct attribute *asb100_attributes[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan3_div.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_max_hyst.dev_attr.attr, + + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + + &dev_attr_cpu0_vid.attr, + &dev_attr_vrm.attr, + &dev_attr_alarms.attr, + &dev_attr_pwm1.attr, + &dev_attr_pwm1_enable.attr, + + NULL +}; + +static const struct attribute_group asb100_group = { + .attrs = asb100_attributes, +}; + +static int asb100_detect_subclients(struct i2c_client *client) +{ + int i, id, err; + int address = client->addr; + unsigned short sc_addr[2]; + struct asb100_data *data = i2c_get_clientdata(client); + struct i2c_adapter *adapter = client->adapter; + + id = i2c_adapter_id(adapter); + + if (force_subclients[0] == id && force_subclients[1] == address) { + for (i = 2; i <= 3; i++) { + if (force_subclients[i] < 0x48 || + force_subclients[i] > 0x4f) { + dev_err(&client->dev, + "invalid subclient address %d; must be 0x48-0x4f\n", + force_subclients[i]); + err = -ENODEV; + goto ERROR_SC_2; + } + } + asb100_write_value(client, ASB100_REG_I2C_SUBADDR, + (force_subclients[2] & 0x07) | + ((force_subclients[3] & 0x07) << 4)); + sc_addr[0] = force_subclients[2]; + sc_addr[1] = force_subclients[3]; + } else { + int val = asb100_read_value(client, ASB100_REG_I2C_SUBADDR); + sc_addr[0] = 0x48 + (val & 0x07); + sc_addr[1] = 0x48 + ((val >> 4) & 0x07); + } + + if (sc_addr[0] == sc_addr[1]) { + dev_err(&client->dev, + "duplicate addresses 0x%x for subclients\n", + sc_addr[0]); + err = -ENODEV; + goto ERROR_SC_2; + } + + data->lm75[0] = i2c_new_dummy(adapter, sc_addr[0]); + if (!data->lm75[0]) { + dev_err(&client->dev, + "subclient %d registration at address 0x%x failed.\n", + 1, sc_addr[0]); + err = -ENOMEM; + goto ERROR_SC_2; + } + + data->lm75[1] = i2c_new_dummy(adapter, sc_addr[1]); + if (!data->lm75[1]) { + dev_err(&client->dev, + "subclient %d registration at address 0x%x failed.\n", + 2, sc_addr[1]); + err = -ENOMEM; + goto ERROR_SC_3; + } + + return 0; + +/* Undo inits in case of errors */ +ERROR_SC_3: + i2c_unregister_device(data->lm75[0]); +ERROR_SC_2: + return err; +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int asb100_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int val1, val2; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + pr_debug("detect failed, smbus byte data not supported!\n"); + return -ENODEV; + } + + val1 = i2c_smbus_read_byte_data(client, ASB100_REG_BANK); + val2 = i2c_smbus_read_byte_data(client, ASB100_REG_CHIPMAN); + + /* If we're in bank 0 */ + if ((!(val1 & 0x07)) && + /* Check for ASB100 ID (low byte) */ + (((!(val1 & 0x80)) && (val2 != 0x94)) || + /* Check for ASB100 ID (high byte ) */ + ((val1 & 0x80) && (val2 != 0x06)))) { + pr_debug("detect failed, bad chip id 0x%02x!\n", val2); + return -ENODEV; + } + + /* Put it now into bank 0 and Vendor ID High Byte */ + i2c_smbus_write_byte_data(client, ASB100_REG_BANK, + (i2c_smbus_read_byte_data(client, ASB100_REG_BANK) & 0x78) + | 0x80); + + /* Determine the chip type. */ + val1 = i2c_smbus_read_byte_data(client, ASB100_REG_WCHIPID); + val2 = i2c_smbus_read_byte_data(client, ASB100_REG_CHIPMAN); + + if (val1 != 0x31 || val2 != 0x06) + return -ENODEV; + + strlcpy(info->type, "asb100", I2C_NAME_SIZE); + + return 0; +} + +static int asb100_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err; + struct asb100_data *data; + + data = devm_kzalloc(&client->dev, sizeof(struct asb100_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->lock); + mutex_init(&data->update_lock); + + /* Attach secondary lm75 clients */ + err = asb100_detect_subclients(client); + if (err) + return err; + + /* Initialize the chip */ + asb100_init_client(client); + + /* A few vars need to be filled upon startup */ + data->fan_min[0] = asb100_read_value(client, ASB100_REG_FAN_MIN(0)); + data->fan_min[1] = asb100_read_value(client, ASB100_REG_FAN_MIN(1)); + data->fan_min[2] = asb100_read_value(client, ASB100_REG_FAN_MIN(2)); + + /* Register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &asb100_group); + if (err) + goto ERROR3; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto ERROR4; + } + + return 0; + +ERROR4: + sysfs_remove_group(&client->dev.kobj, &asb100_group); +ERROR3: + i2c_unregister_device(data->lm75[1]); + i2c_unregister_device(data->lm75[0]); + return err; +} + +static int asb100_remove(struct i2c_client *client) +{ + struct asb100_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &asb100_group); + + i2c_unregister_device(data->lm75[1]); + i2c_unregister_device(data->lm75[0]); + + return 0; +} + +/* + * The SMBus locks itself, usually, but nothing may access the chip between + * bank switches. + */ +static int asb100_read_value(struct i2c_client *client, u16 reg) +{ + struct asb100_data *data = i2c_get_clientdata(client); + struct i2c_client *cl; + int res, bank; + + mutex_lock(&data->lock); + + bank = (reg >> 8) & 0x0f; + if (bank > 2) + /* switch banks */ + i2c_smbus_write_byte_data(client, ASB100_REG_BANK, bank); + + if (bank == 0 || bank > 2) { + res = i2c_smbus_read_byte_data(client, reg & 0xff); + } else { + /* switch to subclient */ + cl = data->lm75[bank - 1]; + + /* convert from ISA to LM75 I2C addresses */ + switch (reg & 0xff) { + case 0x50: /* TEMP */ + res = i2c_smbus_read_word_swapped(cl, 0); + break; + case 0x52: /* CONFIG */ + res = i2c_smbus_read_byte_data(cl, 1); + break; + case 0x53: /* HYST */ + res = i2c_smbus_read_word_swapped(cl, 2); + break; + case 0x55: /* MAX */ + default: + res = i2c_smbus_read_word_swapped(cl, 3); + break; + } + } + + if (bank > 2) + i2c_smbus_write_byte_data(client, ASB100_REG_BANK, 0); + + mutex_unlock(&data->lock); + + return res; +} + +static void asb100_write_value(struct i2c_client *client, u16 reg, u16 value) +{ + struct asb100_data *data = i2c_get_clientdata(client); + struct i2c_client *cl; + int bank; + + mutex_lock(&data->lock); + + bank = (reg >> 8) & 0x0f; + if (bank > 2) + /* switch banks */ + i2c_smbus_write_byte_data(client, ASB100_REG_BANK, bank); + + if (bank == 0 || bank > 2) { + i2c_smbus_write_byte_data(client, reg & 0xff, value & 0xff); + } else { + /* switch to subclient */ + cl = data->lm75[bank - 1]; + + /* convert from ISA to LM75 I2C addresses */ + switch (reg & 0xff) { + case 0x52: /* CONFIG */ + i2c_smbus_write_byte_data(cl, 1, value & 0xff); + break; + case 0x53: /* HYST */ + i2c_smbus_write_word_swapped(cl, 2, value); + break; + case 0x55: /* MAX */ + i2c_smbus_write_word_swapped(cl, 3, value); + break; + } + } + + if (bank > 2) + i2c_smbus_write_byte_data(client, ASB100_REG_BANK, 0); + + mutex_unlock(&data->lock); +} + +static void asb100_init_client(struct i2c_client *client) +{ + struct asb100_data *data = i2c_get_clientdata(client); + + data->vrm = vid_which_vrm(); + + /* Start monitoring */ + asb100_write_value(client, ASB100_REG_CONFIG, + (asb100_read_value(client, ASB100_REG_CONFIG) & 0xf7) | 0x01); +} + +static struct asb100_data *asb100_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct asb100_data *data = i2c_get_clientdata(client); + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + + dev_dbg(&client->dev, "starting device update...\n"); + + /* 7 voltage inputs */ + for (i = 0; i < 7; i++) { + data->in[i] = asb100_read_value(client, + ASB100_REG_IN(i)); + data->in_min[i] = asb100_read_value(client, + ASB100_REG_IN_MIN(i)); + data->in_max[i] = asb100_read_value(client, + ASB100_REG_IN_MAX(i)); + } + + /* 3 fan inputs */ + for (i = 0; i < 3; i++) { + data->fan[i] = asb100_read_value(client, + ASB100_REG_FAN(i)); + data->fan_min[i] = asb100_read_value(client, + ASB100_REG_FAN_MIN(i)); + } + + /* 4 temperature inputs */ + for (i = 1; i <= 4; i++) { + data->temp[i-1] = asb100_read_value(client, + ASB100_REG_TEMP(i)); + data->temp_max[i-1] = asb100_read_value(client, + ASB100_REG_TEMP_MAX(i)); + data->temp_hyst[i-1] = asb100_read_value(client, + ASB100_REG_TEMP_HYST(i)); + } + + /* VID and fan divisors */ + i = asb100_read_value(client, ASB100_REG_VID_FANDIV); + data->vid = i & 0x0f; + data->vid |= (asb100_read_value(client, + ASB100_REG_CHIPID) & 0x01) << 4; + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = (i >> 6) & 0x03; + data->fan_div[2] = (asb100_read_value(client, + ASB100_REG_PIN) >> 6) & 0x03; + + /* PWM */ + data->pwm = asb100_read_value(client, ASB100_REG_PWM1); + + /* alarms */ + data->alarms = asb100_read_value(client, ASB100_REG_ALARM1) + + (asb100_read_value(client, ASB100_REG_ALARM2) << 8); + + data->last_updated = jiffies; + data->valid = 1; + + dev_dbg(&client->dev, "... device update complete\n"); + } + + mutex_unlock(&data->update_lock); + + return data; +} + +module_i2c_driver(asb100_driver); + +MODULE_AUTHOR("Mark M. Hoffman "); +MODULE_DESCRIPTION("ASB100 Bach driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/asc7621.c b/drivers/hwmon/asc7621.c new file mode 100644 index 000000000..6d34c05a4 --- /dev/null +++ b/drivers/hwmon/asc7621.c @@ -0,0 +1,1246 @@ +/* + * asc7621.c - Part of lm_sensors, Linux kernel modules for hardware monitoring + * Copyright (c) 2007, 2010 George Joseph + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { + 0x2c, 0x2d, 0x2e, I2C_CLIENT_END +}; + +enum asc7621_type { + asc7621, + asc7621a +}; + +#define INTERVAL_HIGH (HZ + HZ / 2) +#define INTERVAL_LOW (1 * 60 * HZ) +#define PRI_NONE 0 +#define PRI_LOW 1 +#define PRI_HIGH 2 +#define FIRST_CHIP asc7621 +#define LAST_CHIP asc7621a + +struct asc7621_chip { + char *name; + enum asc7621_type chip_type; + u8 company_reg; + u8 company_id; + u8 verstep_reg; + u8 verstep_id; + const unsigned short *addresses; +}; + +static struct asc7621_chip asc7621_chips[] = { + { + .name = "asc7621", + .chip_type = asc7621, + .company_reg = 0x3e, + .company_id = 0x61, + .verstep_reg = 0x3f, + .verstep_id = 0x6c, + .addresses = normal_i2c, + }, + { + .name = "asc7621a", + .chip_type = asc7621a, + .company_reg = 0x3e, + .company_id = 0x61, + .verstep_reg = 0x3f, + .verstep_id = 0x6d, + .addresses = normal_i2c, + }, +}; + +/* + * Defines the highest register to be used, not the count. + * The actual count will probably be smaller because of gaps + * in the implementation (unused register locations). + * This define will safely set the array size of both the parameter + * and data arrays. + * This comes from the data sheet register description table. + */ +#define LAST_REGISTER 0xff + +struct asc7621_data { + struct i2c_client client; + struct device *class_dev; + struct mutex update_lock; + int valid; /* !=0 if following fields are valid */ + unsigned long last_high_reading; /* In jiffies */ + unsigned long last_low_reading; /* In jiffies */ + /* + * Registers we care about occupy the corresponding index + * in the array. Registers we don't care about are left + * at 0. + */ + u8 reg[LAST_REGISTER + 1]; +}; + +/* + * Macro to get the parent asc7621_param structure + * from a sensor_device_attribute passed into the + * show/store functions. + */ +#define to_asc7621_param(_sda) \ + container_of(_sda, struct asc7621_param, sda) + +/* + * Each parameter to be retrieved needs an asc7621_param structure + * allocated. It contains the sensor_device_attribute structure + * and the control info needed to retrieve the value from the register map. + */ +struct asc7621_param { + struct sensor_device_attribute sda; + u8 priority; + u8 msb[3]; + u8 lsb[3]; + u8 mask[3]; + u8 shift[3]; +}; + +/* + * This is the map that ultimately indicates whether we'll be + * retrieving a register value or not, and at what frequency. + */ +static u8 asc7621_register_priorities[255]; + +static struct asc7621_data *asc7621_update_device(struct device *dev); + +static inline u8 read_byte(struct i2c_client *client, u8 reg) +{ + int res = i2c_smbus_read_byte_data(client, reg); + if (res < 0) { + dev_err(&client->dev, + "Unable to read from register 0x%02x.\n", reg); + return 0; + } + return res & 0xff; +} + +static inline int write_byte(struct i2c_client *client, u8 reg, u8 data) +{ + int res = i2c_smbus_write_byte_data(client, reg, data); + if (res < 0) { + dev_err(&client->dev, + "Unable to write value 0x%02x to register 0x%02x.\n", + data, reg); + } + return res; +} + +/* + * Data Handlers + * Each function handles the formatting, storage + * and retrieval of like parameters. + */ + +#define SETUP_SHOW_DATA_PARAM(d, a) \ + struct sensor_device_attribute *sda = to_sensor_dev_attr(a); \ + struct asc7621_data *data = asc7621_update_device(d); \ + struct asc7621_param *param = to_asc7621_param(sda) + +#define SETUP_STORE_DATA_PARAM(d, a) \ + struct sensor_device_attribute *sda = to_sensor_dev_attr(a); \ + struct i2c_client *client = to_i2c_client(d); \ + struct asc7621_data *data = i2c_get_clientdata(client); \ + struct asc7621_param *param = to_asc7621_param(sda) + +/* + * u8 is just what it sounds like...an unsigned byte with no + * special formatting. + */ +static ssize_t show_u8(struct device *dev, struct device_attribute *attr, + char *buf) +{ + SETUP_SHOW_DATA_PARAM(dev, attr); + + return sprintf(buf, "%u\n", data->reg[param->msb[0]]); +} + +static ssize_t store_u8(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_DATA_PARAM(dev, attr); + long reqval; + + if (kstrtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = clamp_val(reqval, 0, 255); + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = reqval; + write_byte(client, param->msb[0], reqval); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Many of the config values occupy only a few bits of a register. + */ +static ssize_t show_bitmask(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_DATA_PARAM(dev, attr); + + return sprintf(buf, "%u\n", + (data->reg[param->msb[0]] >> param-> + shift[0]) & param->mask[0]); +} + +static ssize_t store_bitmask(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_DATA_PARAM(dev, attr); + long reqval; + u8 currval; + + if (kstrtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = clamp_val(reqval, 0, param->mask[0]); + + reqval = (reqval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + reqval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = reqval; + write_byte(client, param->msb[0], reqval); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * 16 bit fan rpm values + * reported by the device as the number of 11.111us periods (90khz) + * between full fan rotations. Therefore... + * RPM = (90000 * 60) / register value + */ +static ssize_t show_fan16(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_DATA_PARAM(dev, attr); + u16 regval; + + mutex_lock(&data->update_lock); + regval = (data->reg[param->msb[0]] << 8) | data->reg[param->lsb[0]]; + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", + (regval == 0 ? -1 : (regval) == + 0xffff ? 0 : 5400000 / regval)); +} + +static ssize_t store_fan16(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + SETUP_STORE_DATA_PARAM(dev, attr); + long reqval; + + if (kstrtol(buf, 10, &reqval)) + return -EINVAL; + + /* + * If a minimum RPM of zero is requested, then we set the register to + * 0xffff. This value allows the fan to be stopped completely without + * generating an alarm. + */ + reqval = + (reqval <= 0 ? 0xffff : clamp_val(5400000 / reqval, 0, 0xfffe)); + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = (reqval >> 8) & 0xff; + data->reg[param->lsb[0]] = reqval & 0xff; + write_byte(client, param->msb[0], data->reg[param->msb[0]]); + write_byte(client, param->lsb[0], data->reg[param->lsb[0]]); + mutex_unlock(&data->update_lock); + + return count; +} + +/* + * Voltages are scaled in the device so that the nominal voltage + * is 3/4ths of the 0-255 range (i.e. 192). + * If all voltages are 'normal' then all voltage registers will + * read 0xC0. + * + * The data sheet provides us with the 3/4 scale value for each voltage + * which is stored in in_scaling. The sda->index parameter value provides + * the index into in_scaling. + * + * NOTE: The chip expects the first 2 inputs be 2.5 and 2.25 volts + * respectively. That doesn't mean that's what the motherboard provides. :) + */ + +static const int asc7621_in_scaling[] = { + 2500, 2250, 3300, 5000, 12000 +}; + +static ssize_t show_in10(struct device *dev, struct device_attribute *attr, + char *buf) +{ + SETUP_SHOW_DATA_PARAM(dev, attr); + u16 regval; + u8 nr = sda->index; + + mutex_lock(&data->update_lock); + regval = (data->reg[param->msb[0]] << 8) | (data->reg[param->lsb[0]]); + mutex_unlock(&data->update_lock); + + /* The LSB value is a 2-bit scaling of the MSB's LSbit value. */ + regval = (regval >> 6) * asc7621_in_scaling[nr] / (0xc0 << 2); + + return sprintf(buf, "%u\n", regval); +} + +/* 8 bit voltage values (the mins and maxs) */ +static ssize_t show_in8(struct device *dev, struct device_attribute *attr, + char *buf) +{ + SETUP_SHOW_DATA_PARAM(dev, attr); + u8 nr = sda->index; + + return sprintf(buf, "%u\n", + ((data->reg[param->msb[0]] * + asc7621_in_scaling[nr]) / 0xc0)); +} + +static ssize_t store_in8(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_DATA_PARAM(dev, attr); + long reqval; + u8 nr = sda->index; + + if (kstrtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = clamp_val(reqval, 0, 0xffff); + + reqval = reqval * 0xc0 / asc7621_in_scaling[nr]; + + reqval = clamp_val(reqval, 0, 0xff); + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = reqval; + write_byte(client, param->msb[0], reqval); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_temp8(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_DATA_PARAM(dev, attr); + + return sprintf(buf, "%d\n", ((s8) data->reg[param->msb[0]]) * 1000); +} + +static ssize_t store_temp8(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + SETUP_STORE_DATA_PARAM(dev, attr); + long reqval; + s8 temp; + + if (kstrtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = clamp_val(reqval, -127000, 127000); + + temp = reqval / 1000; + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = temp; + write_byte(client, param->msb[0], temp); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Temperatures that occupy 2 bytes always have the whole + * number of degrees in the MSB with some part of the LSB + * indicating fractional degrees. + */ + +/* mmmmmmmm.llxxxxxx */ +static ssize_t show_temp10(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_DATA_PARAM(dev, attr); + u8 msb, lsb; + int temp; + + mutex_lock(&data->update_lock); + msb = data->reg[param->msb[0]]; + lsb = (data->reg[param->lsb[0]] >> 6) & 0x03; + temp = (((s8) msb) * 1000) + (lsb * 250); + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", temp); +} + +/* mmmmmm.ll */ +static ssize_t show_temp62(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_DATA_PARAM(dev, attr); + u8 regval = data->reg[param->msb[0]]; + int temp = ((s8) (regval & 0xfc) * 1000) + ((regval & 0x03) * 250); + + return sprintf(buf, "%d\n", temp); +} + +static ssize_t store_temp62(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + SETUP_STORE_DATA_PARAM(dev, attr); + long reqval, i, f; + s8 temp; + + if (kstrtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = clamp_val(reqval, -32000, 31750); + i = reqval / 1000; + f = reqval - (i * 1000); + temp = i << 2; + temp |= f / 250; + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = temp; + write_byte(client, param->msb[0], temp); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * The aSC7621 doesn't provide an "auto_point2". Instead, you + * specify the auto_point1 and a range. To keep with the sysfs + * hwmon specs, we synthesize the auto_point_2 from them. + */ + +static const u32 asc7621_range_map[] = { + 2000, 2500, 3330, 4000, 5000, 6670, 8000, 10000, + 13330, 16000, 20000, 26670, 32000, 40000, 53330, 80000, +}; + +static ssize_t show_ap2_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_DATA_PARAM(dev, attr); + long auto_point1; + u8 regval; + int temp; + + mutex_lock(&data->update_lock); + auto_point1 = ((s8) data->reg[param->msb[1]]) * 1000; + regval = + ((data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]); + temp = auto_point1 + asc7621_range_map[clamp_val(regval, 0, 15)]; + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", temp); + +} + +static ssize_t store_ap2_temp(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_DATA_PARAM(dev, attr); + long reqval, auto_point1; + int i; + u8 currval, newval = 0; + + if (kstrtol(buf, 10, &reqval)) + return -EINVAL; + + mutex_lock(&data->update_lock); + auto_point1 = data->reg[param->msb[1]] * 1000; + reqval = clamp_val(reqval, auto_point1 + 2000, auto_point1 + 80000); + + for (i = ARRAY_SIZE(asc7621_range_map) - 1; i >= 0; i--) { + if (reqval >= auto_point1 + asc7621_range_map[i]) { + newval = i; + break; + } + } + + newval = (newval & param->mask[0]) << param->shift[0]; + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm_ac(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_DATA_PARAM(dev, attr); + u8 config, altbit, regval; + static const u8 map[] = { + 0x01, 0x02, 0x04, 0x1f, 0x00, 0x06, 0x07, 0x10, + 0x08, 0x0f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f + }; + + mutex_lock(&data->update_lock); + config = (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + altbit = (data->reg[param->msb[1]] >> param->shift[1]) & param->mask[1]; + regval = config | (altbit << 3); + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", map[clamp_val(regval, 0, 15)]); +} + +static ssize_t store_pwm_ac(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_DATA_PARAM(dev, attr); + unsigned long reqval; + u8 currval, config, altbit, newval; + static const u16 map[] = { + 0x04, 0x00, 0x01, 0xff, 0x02, 0xff, 0x05, 0x06, + 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, + 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, + }; + + if (kstrtoul(buf, 10, &reqval)) + return -EINVAL; + + if (reqval > 31) + return -EINVAL; + + reqval = map[reqval]; + if (reqval == 0xff) + return -EINVAL; + + config = reqval & 0x07; + altbit = (reqval >> 3) & 0x01; + + config = (config & param->mask[0]) << param->shift[0]; + altbit = (altbit & param->mask[1]) << param->shift[1]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval = config | (currval & ~(param->mask[0] << param->shift[0])); + newval = altbit | (newval & ~(param->mask[1] << param->shift[1])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_DATA_PARAM(dev, attr); + u8 config, altbit, minoff, val, newval; + + mutex_lock(&data->update_lock); + config = (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + altbit = (data->reg[param->msb[1]] >> param->shift[1]) & param->mask[1]; + minoff = (data->reg[param->msb[2]] >> param->shift[2]) & param->mask[2]; + mutex_unlock(&data->update_lock); + + val = config | (altbit << 3); + + if (val == 3 || val >= 10) + newval = 255; + else if (val == 4) + newval = 0; + else if (val == 7) + newval = 1; + else if (minoff == 1) + newval = 2; + else + newval = 3; + + return sprintf(buf, "%u\n", newval); +} + +static ssize_t store_pwm_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_DATA_PARAM(dev, attr); + long reqval; + u8 currval, config, altbit, newval, minoff = 255; + + if (kstrtol(buf, 10, &reqval)) + return -EINVAL; + + switch (reqval) { + case 0: + newval = 0x04; + break; + case 1: + newval = 0x07; + break; + case 2: + newval = 0x00; + minoff = 1; + break; + case 3: + newval = 0x00; + minoff = 0; + break; + case 255: + newval = 0x03; + break; + default: + return -EINVAL; + } + + config = newval & 0x07; + altbit = (newval >> 3) & 0x01; + + mutex_lock(&data->update_lock); + config = (config & param->mask[0]) << param->shift[0]; + altbit = (altbit & param->mask[1]) << param->shift[1]; + currval = read_byte(client, param->msb[0]); + newval = config | (currval & ~(param->mask[0] << param->shift[0])); + newval = altbit | (newval & ~(param->mask[1] << param->shift[1])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + if (minoff < 255) { + minoff = (minoff & param->mask[2]) << param->shift[2]; + currval = read_byte(client, param->msb[2]); + newval = + minoff | (currval & ~(param->mask[2] << param->shift[2])); + data->reg[param->msb[2]] = newval; + write_byte(client, param->msb[2], newval); + } + mutex_unlock(&data->update_lock); + return count; +} + +static const u32 asc7621_pwm_freq_map[] = { + 10, 15, 23, 30, 38, 47, 62, 94, + 23000, 24000, 25000, 26000, 27000, 28000, 29000, 30000 +}; + +static ssize_t show_pwm_freq(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_DATA_PARAM(dev, attr); + u8 regval = + (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + + regval = clamp_val(regval, 0, 15); + + return sprintf(buf, "%u\n", asc7621_pwm_freq_map[regval]); +} + +static ssize_t store_pwm_freq(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_DATA_PARAM(dev, attr); + unsigned long reqval; + u8 currval, newval = 255; + int i; + + if (kstrtoul(buf, 10, &reqval)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(asc7621_pwm_freq_map); i++) { + if (reqval == asc7621_pwm_freq_map[i]) { + newval = i; + break; + } + } + if (newval == 255) + return -EINVAL; + + newval = (newval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static const u32 asc7621_pwm_auto_spinup_map[] = { + 0, 100, 250, 400, 700, 1000, 2000, 4000 +}; + +static ssize_t show_pwm_ast(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_DATA_PARAM(dev, attr); + u8 regval = + (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + + regval = clamp_val(regval, 0, 7); + + return sprintf(buf, "%u\n", asc7621_pwm_auto_spinup_map[regval]); + +} + +static ssize_t store_pwm_ast(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_DATA_PARAM(dev, attr); + long reqval; + u8 currval, newval = 255; + u32 i; + + if (kstrtol(buf, 10, &reqval)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(asc7621_pwm_auto_spinup_map); i++) { + if (reqval == asc7621_pwm_auto_spinup_map[i]) { + newval = i; + break; + } + } + if (newval == 255) + return -EINVAL; + + newval = (newval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static const u32 asc7621_temp_smoothing_time_map[] = { + 35000, 17600, 11800, 7000, 4400, 3000, 1600, 800 +}; + +static ssize_t show_temp_st(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_DATA_PARAM(dev, attr); + u8 regval = + (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + regval = clamp_val(regval, 0, 7); + + return sprintf(buf, "%u\n", asc7621_temp_smoothing_time_map[regval]); +} + +static ssize_t store_temp_st(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_DATA_PARAM(dev, attr); + long reqval; + u8 currval, newval = 255; + u32 i; + + if (kstrtol(buf, 10, &reqval)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(asc7621_temp_smoothing_time_map); i++) { + if (reqval == asc7621_temp_smoothing_time_map[i]) { + newval = i; + break; + } + } + + if (newval == 255) + return -EINVAL; + + newval = (newval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * End of data handlers + * + * These defines do nothing more than make the table easier + * to read when wrapped at column 80. + */ + +/* + * Creates a variable length array inititalizer. + * VAA(1,3,5,7) would produce {1,3,5,7} + */ +#define VAA(args...) {args} + +#define PREAD(name, n, pri, rm, rl, m, s, r) \ + {.sda = SENSOR_ATTR(name, S_IRUGO, show_##r, NULL, n), \ + .priority = pri, .msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \ + .shift[0] = s,} + +#define PWRITE(name, n, pri, rm, rl, m, s, r) \ + {.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \ + .priority = pri, .msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \ + .shift[0] = s,} + +/* + * PWRITEM assumes that the initializers for the .msb, .lsb, .mask and .shift + * were created using the VAA macro. + */ +#define PWRITEM(name, n, pri, rm, rl, m, s, r) \ + {.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \ + .priority = pri, .msb = rm, .lsb = rl, .mask = m, .shift = s,} + +static struct asc7621_param asc7621_params[] = { + PREAD(in0_input, 0, PRI_HIGH, 0x20, 0x13, 0, 0, in10), + PREAD(in1_input, 1, PRI_HIGH, 0x21, 0x18, 0, 0, in10), + PREAD(in2_input, 2, PRI_HIGH, 0x22, 0x11, 0, 0, in10), + PREAD(in3_input, 3, PRI_HIGH, 0x23, 0x12, 0, 0, in10), + PREAD(in4_input, 4, PRI_HIGH, 0x24, 0x14, 0, 0, in10), + + PWRITE(in0_min, 0, PRI_LOW, 0x44, 0, 0, 0, in8), + PWRITE(in1_min, 1, PRI_LOW, 0x46, 0, 0, 0, in8), + PWRITE(in2_min, 2, PRI_LOW, 0x48, 0, 0, 0, in8), + PWRITE(in3_min, 3, PRI_LOW, 0x4a, 0, 0, 0, in8), + PWRITE(in4_min, 4, PRI_LOW, 0x4c, 0, 0, 0, in8), + + PWRITE(in0_max, 0, PRI_LOW, 0x45, 0, 0, 0, in8), + PWRITE(in1_max, 1, PRI_LOW, 0x47, 0, 0, 0, in8), + PWRITE(in2_max, 2, PRI_LOW, 0x49, 0, 0, 0, in8), + PWRITE(in3_max, 3, PRI_LOW, 0x4b, 0, 0, 0, in8), + PWRITE(in4_max, 4, PRI_LOW, 0x4d, 0, 0, 0, in8), + + PREAD(in0_alarm, 0, PRI_HIGH, 0x41, 0, 0x01, 0, bitmask), + PREAD(in1_alarm, 1, PRI_HIGH, 0x41, 0, 0x01, 1, bitmask), + PREAD(in2_alarm, 2, PRI_HIGH, 0x41, 0, 0x01, 2, bitmask), + PREAD(in3_alarm, 3, PRI_HIGH, 0x41, 0, 0x01, 3, bitmask), + PREAD(in4_alarm, 4, PRI_HIGH, 0x42, 0, 0x01, 0, bitmask), + + PREAD(fan1_input, 0, PRI_HIGH, 0x29, 0x28, 0, 0, fan16), + PREAD(fan2_input, 1, PRI_HIGH, 0x2b, 0x2a, 0, 0, fan16), + PREAD(fan3_input, 2, PRI_HIGH, 0x2d, 0x2c, 0, 0, fan16), + PREAD(fan4_input, 3, PRI_HIGH, 0x2f, 0x2e, 0, 0, fan16), + + PWRITE(fan1_min, 0, PRI_LOW, 0x55, 0x54, 0, 0, fan16), + PWRITE(fan2_min, 1, PRI_LOW, 0x57, 0x56, 0, 0, fan16), + PWRITE(fan3_min, 2, PRI_LOW, 0x59, 0x58, 0, 0, fan16), + PWRITE(fan4_min, 3, PRI_LOW, 0x5b, 0x5a, 0, 0, fan16), + + PREAD(fan1_alarm, 0, PRI_HIGH, 0x42, 0, 0x01, 2, bitmask), + PREAD(fan2_alarm, 1, PRI_HIGH, 0x42, 0, 0x01, 3, bitmask), + PREAD(fan3_alarm, 2, PRI_HIGH, 0x42, 0, 0x01, 4, bitmask), + PREAD(fan4_alarm, 3, PRI_HIGH, 0x42, 0, 0x01, 5, bitmask), + + PREAD(temp1_input, 0, PRI_HIGH, 0x25, 0x10, 0, 0, temp10), + PREAD(temp2_input, 1, PRI_HIGH, 0x26, 0x15, 0, 0, temp10), + PREAD(temp3_input, 2, PRI_HIGH, 0x27, 0x16, 0, 0, temp10), + PREAD(temp4_input, 3, PRI_HIGH, 0x33, 0x17, 0, 0, temp10), + PREAD(temp5_input, 4, PRI_HIGH, 0xf7, 0xf6, 0, 0, temp10), + PREAD(temp6_input, 5, PRI_HIGH, 0xf9, 0xf8, 0, 0, temp10), + PREAD(temp7_input, 6, PRI_HIGH, 0xfb, 0xfa, 0, 0, temp10), + PREAD(temp8_input, 7, PRI_HIGH, 0xfd, 0xfc, 0, 0, temp10), + + PWRITE(temp1_min, 0, PRI_LOW, 0x4e, 0, 0, 0, temp8), + PWRITE(temp2_min, 1, PRI_LOW, 0x50, 0, 0, 0, temp8), + PWRITE(temp3_min, 2, PRI_LOW, 0x52, 0, 0, 0, temp8), + PWRITE(temp4_min, 3, PRI_LOW, 0x34, 0, 0, 0, temp8), + + PWRITE(temp1_max, 0, PRI_LOW, 0x4f, 0, 0, 0, temp8), + PWRITE(temp2_max, 1, PRI_LOW, 0x51, 0, 0, 0, temp8), + PWRITE(temp3_max, 2, PRI_LOW, 0x53, 0, 0, 0, temp8), + PWRITE(temp4_max, 3, PRI_LOW, 0x35, 0, 0, 0, temp8), + + PREAD(temp1_alarm, 0, PRI_HIGH, 0x41, 0, 0x01, 4, bitmask), + PREAD(temp2_alarm, 1, PRI_HIGH, 0x41, 0, 0x01, 5, bitmask), + PREAD(temp3_alarm, 2, PRI_HIGH, 0x41, 0, 0x01, 6, bitmask), + PREAD(temp4_alarm, 3, PRI_HIGH, 0x43, 0, 0x01, 0, bitmask), + + PWRITE(temp1_source, 0, PRI_LOW, 0x02, 0, 0x07, 4, bitmask), + PWRITE(temp2_source, 1, PRI_LOW, 0x02, 0, 0x07, 0, bitmask), + PWRITE(temp3_source, 2, PRI_LOW, 0x03, 0, 0x07, 4, bitmask), + PWRITE(temp4_source, 3, PRI_LOW, 0x03, 0, 0x07, 0, bitmask), + + PWRITE(temp1_smoothing_enable, 0, PRI_LOW, 0x62, 0, 0x01, 3, bitmask), + PWRITE(temp2_smoothing_enable, 1, PRI_LOW, 0x63, 0, 0x01, 7, bitmask), + PWRITE(temp3_smoothing_enable, 2, PRI_LOW, 0x63, 0, 0x01, 3, bitmask), + PWRITE(temp4_smoothing_enable, 3, PRI_LOW, 0x3c, 0, 0x01, 3, bitmask), + + PWRITE(temp1_smoothing_time, 0, PRI_LOW, 0x62, 0, 0x07, 0, temp_st), + PWRITE(temp2_smoothing_time, 1, PRI_LOW, 0x63, 0, 0x07, 4, temp_st), + PWRITE(temp3_smoothing_time, 2, PRI_LOW, 0x63, 0, 0x07, 0, temp_st), + PWRITE(temp4_smoothing_time, 3, PRI_LOW, 0x3c, 0, 0x07, 0, temp_st), + + PWRITE(temp1_auto_point1_temp_hyst, 0, PRI_LOW, 0x6d, 0, 0x0f, 4, + bitmask), + PWRITE(temp2_auto_point1_temp_hyst, 1, PRI_LOW, 0x6d, 0, 0x0f, 0, + bitmask), + PWRITE(temp3_auto_point1_temp_hyst, 2, PRI_LOW, 0x6e, 0, 0x0f, 4, + bitmask), + PWRITE(temp4_auto_point1_temp_hyst, 3, PRI_LOW, 0x6e, 0, 0x0f, 0, + bitmask), + + PREAD(temp1_auto_point2_temp_hyst, 0, PRI_LOW, 0x6d, 0, 0x0f, 4, + bitmask), + PREAD(temp2_auto_point2_temp_hyst, 1, PRI_LOW, 0x6d, 0, 0x0f, 0, + bitmask), + PREAD(temp3_auto_point2_temp_hyst, 2, PRI_LOW, 0x6e, 0, 0x0f, 4, + bitmask), + PREAD(temp4_auto_point2_temp_hyst, 3, PRI_LOW, 0x6e, 0, 0x0f, 0, + bitmask), + + PWRITE(temp1_auto_point1_temp, 0, PRI_LOW, 0x67, 0, 0, 0, temp8), + PWRITE(temp2_auto_point1_temp, 1, PRI_LOW, 0x68, 0, 0, 0, temp8), + PWRITE(temp3_auto_point1_temp, 2, PRI_LOW, 0x69, 0, 0, 0, temp8), + PWRITE(temp4_auto_point1_temp, 3, PRI_LOW, 0x3b, 0, 0, 0, temp8), + + PWRITEM(temp1_auto_point2_temp, 0, PRI_LOW, VAA(0x5f, 0x67), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + PWRITEM(temp2_auto_point2_temp, 1, PRI_LOW, VAA(0x60, 0x68), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + PWRITEM(temp3_auto_point2_temp, 2, PRI_LOW, VAA(0x61, 0x69), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + PWRITEM(temp4_auto_point2_temp, 3, PRI_LOW, VAA(0x3c, 0x3b), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + + PWRITE(temp1_crit, 0, PRI_LOW, 0x6a, 0, 0, 0, temp8), + PWRITE(temp2_crit, 1, PRI_LOW, 0x6b, 0, 0, 0, temp8), + PWRITE(temp3_crit, 2, PRI_LOW, 0x6c, 0, 0, 0, temp8), + PWRITE(temp4_crit, 3, PRI_LOW, 0x3d, 0, 0, 0, temp8), + + PWRITE(temp5_enable, 4, PRI_LOW, 0x0e, 0, 0x01, 0, bitmask), + PWRITE(temp6_enable, 5, PRI_LOW, 0x0e, 0, 0x01, 1, bitmask), + PWRITE(temp7_enable, 6, PRI_LOW, 0x0e, 0, 0x01, 2, bitmask), + PWRITE(temp8_enable, 7, PRI_LOW, 0x0e, 0, 0x01, 3, bitmask), + + PWRITE(remote1_offset, 0, PRI_LOW, 0x1c, 0, 0, 0, temp62), + PWRITE(remote2_offset, 1, PRI_LOW, 0x1d, 0, 0, 0, temp62), + + PWRITE(pwm1, 0, PRI_HIGH, 0x30, 0, 0, 0, u8), + PWRITE(pwm2, 1, PRI_HIGH, 0x31, 0, 0, 0, u8), + PWRITE(pwm3, 2, PRI_HIGH, 0x32, 0, 0, 0, u8), + + PWRITE(pwm1_invert, 0, PRI_LOW, 0x5c, 0, 0x01, 4, bitmask), + PWRITE(pwm2_invert, 1, PRI_LOW, 0x5d, 0, 0x01, 4, bitmask), + PWRITE(pwm3_invert, 2, PRI_LOW, 0x5e, 0, 0x01, 4, bitmask), + + PWRITEM(pwm1_enable, 0, PRI_LOW, VAA(0x5c, 0x5c, 0x62), VAA(0, 0, 0), + VAA(0x07, 0x01, 0x01), VAA(5, 3, 5), pwm_enable), + PWRITEM(pwm2_enable, 1, PRI_LOW, VAA(0x5d, 0x5d, 0x62), VAA(0, 0, 0), + VAA(0x07, 0x01, 0x01), VAA(5, 3, 6), pwm_enable), + PWRITEM(pwm3_enable, 2, PRI_LOW, VAA(0x5e, 0x5e, 0x62), VAA(0, 0, 0), + VAA(0x07, 0x01, 0x01), VAA(5, 3, 7), pwm_enable), + + PWRITEM(pwm1_auto_channels, 0, PRI_LOW, VAA(0x5c, 0x5c), VAA(0, 0), + VAA(0x07, 0x01), VAA(5, 3), pwm_ac), + PWRITEM(pwm2_auto_channels, 1, PRI_LOW, VAA(0x5d, 0x5d), VAA(0, 0), + VAA(0x07, 0x01), VAA(5, 3), pwm_ac), + PWRITEM(pwm3_auto_channels, 2, PRI_LOW, VAA(0x5e, 0x5e), VAA(0, 0), + VAA(0x07, 0x01), VAA(5, 3), pwm_ac), + + PWRITE(pwm1_auto_point1_pwm, 0, PRI_LOW, 0x64, 0, 0, 0, u8), + PWRITE(pwm2_auto_point1_pwm, 1, PRI_LOW, 0x65, 0, 0, 0, u8), + PWRITE(pwm3_auto_point1_pwm, 2, PRI_LOW, 0x66, 0, 0, 0, u8), + + PWRITE(pwm1_auto_point2_pwm, 0, PRI_LOW, 0x38, 0, 0, 0, u8), + PWRITE(pwm2_auto_point2_pwm, 1, PRI_LOW, 0x39, 0, 0, 0, u8), + PWRITE(pwm3_auto_point2_pwm, 2, PRI_LOW, 0x3a, 0, 0, 0, u8), + + PWRITE(pwm1_freq, 0, PRI_LOW, 0x5f, 0, 0x0f, 0, pwm_freq), + PWRITE(pwm2_freq, 1, PRI_LOW, 0x60, 0, 0x0f, 0, pwm_freq), + PWRITE(pwm3_freq, 2, PRI_LOW, 0x61, 0, 0x0f, 0, pwm_freq), + + PREAD(pwm1_auto_zone_assigned, 0, PRI_LOW, 0, 0, 0x03, 2, bitmask), + PREAD(pwm2_auto_zone_assigned, 1, PRI_LOW, 0, 0, 0x03, 4, bitmask), + PREAD(pwm3_auto_zone_assigned, 2, PRI_LOW, 0, 0, 0x03, 6, bitmask), + + PWRITE(pwm1_auto_spinup_time, 0, PRI_LOW, 0x5c, 0, 0x07, 0, pwm_ast), + PWRITE(pwm2_auto_spinup_time, 1, PRI_LOW, 0x5d, 0, 0x07, 0, pwm_ast), + PWRITE(pwm3_auto_spinup_time, 2, PRI_LOW, 0x5e, 0, 0x07, 0, pwm_ast), + + PWRITE(peci_enable, 0, PRI_LOW, 0x40, 0, 0x01, 4, bitmask), + PWRITE(peci_avg, 0, PRI_LOW, 0x36, 0, 0x07, 0, bitmask), + PWRITE(peci_domain, 0, PRI_LOW, 0x36, 0, 0x01, 3, bitmask), + PWRITE(peci_legacy, 0, PRI_LOW, 0x36, 0, 0x01, 4, bitmask), + PWRITE(peci_diode, 0, PRI_LOW, 0x0e, 0, 0x07, 4, bitmask), + PWRITE(peci_4domain, 0, PRI_LOW, 0x0e, 0, 0x01, 4, bitmask), + +}; + +static struct asc7621_data *asc7621_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct asc7621_data *data = i2c_get_clientdata(client); + int i; + +/* + * The asc7621 chips guarantee consistent reads of multi-byte values + * regardless of the order of the reads. No special logic is needed + * so we can just read the registers in whatever order they appear + * in the asc7621_params array. + */ + + mutex_lock(&data->update_lock); + + /* Read all the high priority registers */ + + if (!data->valid || + time_after(jiffies, data->last_high_reading + INTERVAL_HIGH)) { + + for (i = 0; i < ARRAY_SIZE(asc7621_register_priorities); i++) { + if (asc7621_register_priorities[i] == PRI_HIGH) { + data->reg[i] = + i2c_smbus_read_byte_data(client, i) & 0xff; + } + } + data->last_high_reading = jiffies; + } /* last_reading */ + + /* Read all the low priority registers. */ + + if (!data->valid || + time_after(jiffies, data->last_low_reading + INTERVAL_LOW)) { + + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + if (asc7621_register_priorities[i] == PRI_LOW) { + data->reg[i] = + i2c_smbus_read_byte_data(client, i) & 0xff; + } + } + data->last_low_reading = jiffies; + } /* last_reading */ + + data->valid = 1; + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Standard detection and initialization below + * + * Helper function that checks if an address is valid + * for a particular chip. + */ + +static inline int valid_address_for_chip(int chip_type, int address) +{ + int i; + + for (i = 0; asc7621_chips[chip_type].addresses[i] != I2C_CLIENT_END; + i++) { + if (asc7621_chips[chip_type].addresses[i] == address) + return 1; + } + return 0; +} + +static void asc7621_init_client(struct i2c_client *client) +{ + int value; + + /* Warn if part was not "READY" */ + + value = read_byte(client, 0x40); + + if (value & 0x02) { + dev_err(&client->dev, + "Client (%d,0x%02x) config is locked.\n", + i2c_adapter_id(client->adapter), client->addr); + } + if (!(value & 0x04)) { + dev_err(&client->dev, "Client (%d,0x%02x) is not ready.\n", + i2c_adapter_id(client->adapter), client->addr); + } + +/* + * Start monitoring + * + * Try to clear LOCK, Set START, save everything else + */ + value = (value & ~0x02) | 0x01; + write_byte(client, 0x40, value & 0xff); + +} + +static int +asc7621_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct asc7621_data *data; + int i, err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + data = devm_kzalloc(&client->dev, sizeof(struct asc7621_data), + GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Initialize the asc7621 chip */ + asc7621_init_client(client); + + /* Create the sysfs entries */ + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + err = + device_create_file(&client->dev, + &(asc7621_params[i].sda.dev_attr)); + if (err) + goto exit_remove; + } + + data->class_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->class_dev)) { + err = PTR_ERR(data->class_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + device_remove_file(&client->dev, + &(asc7621_params[i].sda.dev_attr)); + } + + return err; +} + +static int asc7621_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int company, verstep, chip_index; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + for (chip_index = FIRST_CHIP; chip_index <= LAST_CHIP; chip_index++) { + + if (!valid_address_for_chip(chip_index, client->addr)) + continue; + + company = read_byte(client, + asc7621_chips[chip_index].company_reg); + verstep = read_byte(client, + asc7621_chips[chip_index].verstep_reg); + + if (company == asc7621_chips[chip_index].company_id && + verstep == asc7621_chips[chip_index].verstep_id) { + strlcpy(info->type, asc7621_chips[chip_index].name, + I2C_NAME_SIZE); + + dev_info(&adapter->dev, "Matched %s at 0x%02x\n", + asc7621_chips[chip_index].name, client->addr); + return 0; + } + } + + return -ENODEV; +} + +static int asc7621_remove(struct i2c_client *client) +{ + struct asc7621_data *data = i2c_get_clientdata(client); + int i; + + hwmon_device_unregister(data->class_dev); + + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + device_remove_file(&client->dev, + &(asc7621_params[i].sda.dev_attr)); + } + + return 0; +} + +static const struct i2c_device_id asc7621_id[] = { + {"asc7621", asc7621}, + {"asc7621a", asc7621a}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, asc7621_id); + +static struct i2c_driver asc7621_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "asc7621", + }, + .probe = asc7621_probe, + .remove = asc7621_remove, + .id_table = asc7621_id, + .detect = asc7621_detect, + .address_list = normal_i2c, +}; + +static int __init sm_asc7621_init(void) +{ + int i, j; +/* + * Collect all the registers needed into a single array. + * This way, if a register isn't actually used for anything, + * we don't retrieve it. + */ + + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + for (j = 0; j < ARRAY_SIZE(asc7621_params[i].msb); j++) + asc7621_register_priorities[asc7621_params[i].msb[j]] = + asc7621_params[i].priority; + for (j = 0; j < ARRAY_SIZE(asc7621_params[i].lsb); j++) + asc7621_register_priorities[asc7621_params[i].lsb[j]] = + asc7621_params[i].priority; + } + return i2c_add_driver(&asc7621_driver); +} + +static void __exit sm_asc7621_exit(void) +{ + i2c_del_driver(&asc7621_driver); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("George Joseph"); +MODULE_DESCRIPTION("Andigilog aSC7621 and aSC7621a driver"); + +module_init(sm_asc7621_init); +module_exit(sm_asc7621_exit); diff --git a/drivers/hwmon/aspeed-pwm-tacho.c b/drivers/hwmon/aspeed-pwm-tacho.c new file mode 100644 index 000000000..a43fa730a --- /dev/null +++ b/drivers/hwmon/aspeed-pwm-tacho.c @@ -0,0 +1,1006 @@ +/* + * Copyright (c) 2016 Google, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or later as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ASPEED PWM & FAN Tach Register Definition */ +#define ASPEED_PTCR_CTRL 0x00 +#define ASPEED_PTCR_CLK_CTRL 0x04 +#define ASPEED_PTCR_DUTY0_CTRL 0x08 +#define ASPEED_PTCR_DUTY1_CTRL 0x0c +#define ASPEED_PTCR_TYPEM_CTRL 0x10 +#define ASPEED_PTCR_TYPEM_CTRL1 0x14 +#define ASPEED_PTCR_TYPEN_CTRL 0x18 +#define ASPEED_PTCR_TYPEN_CTRL1 0x1c +#define ASPEED_PTCR_TACH_SOURCE 0x20 +#define ASPEED_PTCR_TRIGGER 0x28 +#define ASPEED_PTCR_RESULT 0x2c +#define ASPEED_PTCR_INTR_CTRL 0x30 +#define ASPEED_PTCR_INTR_STS 0x34 +#define ASPEED_PTCR_TYPEM_LIMIT 0x38 +#define ASPEED_PTCR_TYPEN_LIMIT 0x3C +#define ASPEED_PTCR_CTRL_EXT 0x40 +#define ASPEED_PTCR_CLK_CTRL_EXT 0x44 +#define ASPEED_PTCR_DUTY2_CTRL 0x48 +#define ASPEED_PTCR_DUTY3_CTRL 0x4c +#define ASPEED_PTCR_TYPEO_CTRL 0x50 +#define ASPEED_PTCR_TYPEO_CTRL1 0x54 +#define ASPEED_PTCR_TACH_SOURCE_EXT 0x60 +#define ASPEED_PTCR_TYPEO_LIMIT 0x78 + +/* ASPEED_PTCR_CTRL : 0x00 - General Control Register */ +#define ASPEED_PTCR_CTRL_SET_PWMD_TYPE_PART1 15 +#define ASPEED_PTCR_CTRL_SET_PWMD_TYPE_PART2 6 +#define ASPEED_PTCR_CTRL_SET_PWMD_TYPE_MASK (BIT(7) | BIT(15)) + +#define ASPEED_PTCR_CTRL_SET_PWMC_TYPE_PART1 14 +#define ASPEED_PTCR_CTRL_SET_PWMC_TYPE_PART2 5 +#define ASPEED_PTCR_CTRL_SET_PWMC_TYPE_MASK (BIT(6) | BIT(14)) + +#define ASPEED_PTCR_CTRL_SET_PWMB_TYPE_PART1 13 +#define ASPEED_PTCR_CTRL_SET_PWMB_TYPE_PART2 4 +#define ASPEED_PTCR_CTRL_SET_PWMB_TYPE_MASK (BIT(5) | BIT(13)) + +#define ASPEED_PTCR_CTRL_SET_PWMA_TYPE_PART1 12 +#define ASPEED_PTCR_CTRL_SET_PWMA_TYPE_PART2 3 +#define ASPEED_PTCR_CTRL_SET_PWMA_TYPE_MASK (BIT(4) | BIT(12)) + +#define ASPEED_PTCR_CTRL_FAN_NUM_EN(x) BIT(16 + (x)) + +#define ASPEED_PTCR_CTRL_PWMD_EN BIT(11) +#define ASPEED_PTCR_CTRL_PWMC_EN BIT(10) +#define ASPEED_PTCR_CTRL_PWMB_EN BIT(9) +#define ASPEED_PTCR_CTRL_PWMA_EN BIT(8) + +#define ASPEED_PTCR_CTRL_CLK_SRC BIT(1) +#define ASPEED_PTCR_CTRL_CLK_EN BIT(0) + +/* ASPEED_PTCR_CLK_CTRL : 0x04 - Clock Control Register */ +/* TYPE N */ +#define ASPEED_PTCR_CLK_CTRL_TYPEN_MASK GENMASK(31, 16) +#define ASPEED_PTCR_CLK_CTRL_TYPEN_UNIT 24 +#define ASPEED_PTCR_CLK_CTRL_TYPEN_H 20 +#define ASPEED_PTCR_CLK_CTRL_TYPEN_L 16 +/* TYPE M */ +#define ASPEED_PTCR_CLK_CTRL_TYPEM_MASK GENMASK(15, 0) +#define ASPEED_PTCR_CLK_CTRL_TYPEM_UNIT 8 +#define ASPEED_PTCR_CLK_CTRL_TYPEM_H 4 +#define ASPEED_PTCR_CLK_CTRL_TYPEM_L 0 + +/* + * ASPEED_PTCR_DUTY_CTRL/1/2/3 : 0x08/0x0C/0x48/0x4C - PWM-FAN duty control + * 0/1/2/3 register + */ +#define DUTY_CTRL_PWM2_FALL_POINT 24 +#define DUTY_CTRL_PWM2_RISE_POINT 16 +#define DUTY_CTRL_PWM2_RISE_FALL_MASK GENMASK(31, 16) +#define DUTY_CTRL_PWM1_FALL_POINT 8 +#define DUTY_CTRL_PWM1_RISE_POINT 0 +#define DUTY_CTRL_PWM1_RISE_FALL_MASK GENMASK(15, 0) + +/* ASPEED_PTCR_TYPEM_CTRL : 0x10/0x18/0x50 - Type M/N/O Ctrl 0 Register */ +#define TYPE_CTRL_FAN_MASK (GENMASK(5, 1) | GENMASK(31, 16)) +#define TYPE_CTRL_FAN1_MASK GENMASK(31, 0) +#define TYPE_CTRL_FAN_PERIOD 16 +#define TYPE_CTRL_FAN_MODE 4 +#define TYPE_CTRL_FAN_DIVISION 1 +#define TYPE_CTRL_FAN_TYPE_EN 1 + +/* ASPEED_PTCR_TACH_SOURCE : 0x20/0x60 - Tach Source Register */ +/* bit [0,1] at 0x20, bit [2] at 0x60 */ +#define TACH_PWM_SOURCE_BIT01(x) ((x) * 2) +#define TACH_PWM_SOURCE_BIT2(x) ((x) * 2) +#define TACH_PWM_SOURCE_MASK_BIT01(x) (0x3 << ((x) * 2)) +#define TACH_PWM_SOURCE_MASK_BIT2(x) BIT((x) * 2) + +/* ASPEED_PTCR_RESULT : 0x2c - Result Register */ +#define RESULT_STATUS_MASK BIT(31) +#define RESULT_VALUE_MASK 0xfffff + +/* ASPEED_PTCR_CTRL_EXT : 0x40 - General Control Extension #1 Register */ +#define ASPEED_PTCR_CTRL_SET_PWMH_TYPE_PART1 15 +#define ASPEED_PTCR_CTRL_SET_PWMH_TYPE_PART2 6 +#define ASPEED_PTCR_CTRL_SET_PWMH_TYPE_MASK (BIT(7) | BIT(15)) + +#define ASPEED_PTCR_CTRL_SET_PWMG_TYPE_PART1 14 +#define ASPEED_PTCR_CTRL_SET_PWMG_TYPE_PART2 5 +#define ASPEED_PTCR_CTRL_SET_PWMG_TYPE_MASK (BIT(6) | BIT(14)) + +#define ASPEED_PTCR_CTRL_SET_PWMF_TYPE_PART1 13 +#define ASPEED_PTCR_CTRL_SET_PWMF_TYPE_PART2 4 +#define ASPEED_PTCR_CTRL_SET_PWMF_TYPE_MASK (BIT(5) | BIT(13)) + +#define ASPEED_PTCR_CTRL_SET_PWME_TYPE_PART1 12 +#define ASPEED_PTCR_CTRL_SET_PWME_TYPE_PART2 3 +#define ASPEED_PTCR_CTRL_SET_PWME_TYPE_MASK (BIT(4) | BIT(12)) + +#define ASPEED_PTCR_CTRL_PWMH_EN BIT(11) +#define ASPEED_PTCR_CTRL_PWMG_EN BIT(10) +#define ASPEED_PTCR_CTRL_PWMF_EN BIT(9) +#define ASPEED_PTCR_CTRL_PWME_EN BIT(8) + +/* ASPEED_PTCR_CLK_EXT_CTRL : 0x44 - Clock Control Extension #1 Register */ +/* TYPE O */ +#define ASPEED_PTCR_CLK_CTRL_TYPEO_MASK GENMASK(15, 0) +#define ASPEED_PTCR_CLK_CTRL_TYPEO_UNIT 8 +#define ASPEED_PTCR_CLK_CTRL_TYPEO_H 4 +#define ASPEED_PTCR_CLK_CTRL_TYPEO_L 0 + +#define PWM_MAX 255 + +#define BOTH_EDGES 0x02 /* 10b */ + +#define M_PWM_DIV_H 0x00 +#define M_PWM_DIV_L 0x05 +#define M_PWM_PERIOD 0x5F +#define M_TACH_CLK_DIV 0x00 +/* + * 5:4 Type N fan tach mode selection bit: + * 00: falling + * 01: rising + * 10: both + * 11: reserved. + */ +#define M_TACH_MODE 0x02 /* 10b */ +#define M_TACH_UNIT 0x0210 +#define INIT_FAN_CTRL 0xFF + +/* How long we sleep in us while waiting for an RPM result. */ +#define ASPEED_RPM_STATUS_SLEEP_USEC 500 + +#define MAX_CDEV_NAME_LEN 16 + +struct aspeed_cooling_device { + char name[16]; + struct aspeed_pwm_tacho_data *priv; + struct thermal_cooling_device *tcdev; + int pwm_port; + u8 *cooling_levels; + u8 max_state; + u8 cur_state; +}; + +struct aspeed_pwm_tacho_data { + struct regmap *regmap; + struct reset_control *rst; + unsigned long clk_freq; + bool pwm_present[8]; + bool fan_tach_present[16]; + u8 type_pwm_clock_unit[3]; + u8 type_pwm_clock_division_h[3]; + u8 type_pwm_clock_division_l[3]; + u8 type_fan_tach_clock_division[3]; + u8 type_fan_tach_mode[3]; + u16 type_fan_tach_unit[3]; + u8 pwm_port_type[8]; + u8 pwm_port_fan_ctrl[8]; + u8 fan_tach_ch_source[16]; + struct aspeed_cooling_device *cdev[8]; + const struct attribute_group *groups[3]; +}; + +enum type { TYPEM, TYPEN, TYPEO }; + +struct type_params { + u32 l_value; + u32 h_value; + u32 unit_value; + u32 clk_ctrl_mask; + u32 clk_ctrl_reg; + u32 ctrl_reg; + u32 ctrl_reg1; +}; + +static const struct type_params type_params[] = { + [TYPEM] = { + .l_value = ASPEED_PTCR_CLK_CTRL_TYPEM_L, + .h_value = ASPEED_PTCR_CLK_CTRL_TYPEM_H, + .unit_value = ASPEED_PTCR_CLK_CTRL_TYPEM_UNIT, + .clk_ctrl_mask = ASPEED_PTCR_CLK_CTRL_TYPEM_MASK, + .clk_ctrl_reg = ASPEED_PTCR_CLK_CTRL, + .ctrl_reg = ASPEED_PTCR_TYPEM_CTRL, + .ctrl_reg1 = ASPEED_PTCR_TYPEM_CTRL1, + }, + [TYPEN] = { + .l_value = ASPEED_PTCR_CLK_CTRL_TYPEN_L, + .h_value = ASPEED_PTCR_CLK_CTRL_TYPEN_H, + .unit_value = ASPEED_PTCR_CLK_CTRL_TYPEN_UNIT, + .clk_ctrl_mask = ASPEED_PTCR_CLK_CTRL_TYPEN_MASK, + .clk_ctrl_reg = ASPEED_PTCR_CLK_CTRL, + .ctrl_reg = ASPEED_PTCR_TYPEN_CTRL, + .ctrl_reg1 = ASPEED_PTCR_TYPEN_CTRL1, + }, + [TYPEO] = { + .l_value = ASPEED_PTCR_CLK_CTRL_TYPEO_L, + .h_value = ASPEED_PTCR_CLK_CTRL_TYPEO_H, + .unit_value = ASPEED_PTCR_CLK_CTRL_TYPEO_UNIT, + .clk_ctrl_mask = ASPEED_PTCR_CLK_CTRL_TYPEO_MASK, + .clk_ctrl_reg = ASPEED_PTCR_CLK_CTRL_EXT, + .ctrl_reg = ASPEED_PTCR_TYPEO_CTRL, + .ctrl_reg1 = ASPEED_PTCR_TYPEO_CTRL1, + } +}; + +enum pwm_port { PWMA, PWMB, PWMC, PWMD, PWME, PWMF, PWMG, PWMH }; + +struct pwm_port_params { + u32 pwm_en; + u32 ctrl_reg; + u32 type_part1; + u32 type_part2; + u32 type_mask; + u32 duty_ctrl_rise_point; + u32 duty_ctrl_fall_point; + u32 duty_ctrl_reg; + u32 duty_ctrl_rise_fall_mask; +}; + +static const struct pwm_port_params pwm_port_params[] = { + [PWMA] = { + .pwm_en = ASPEED_PTCR_CTRL_PWMA_EN, + .ctrl_reg = ASPEED_PTCR_CTRL, + .type_part1 = ASPEED_PTCR_CTRL_SET_PWMA_TYPE_PART1, + .type_part2 = ASPEED_PTCR_CTRL_SET_PWMA_TYPE_PART2, + .type_mask = ASPEED_PTCR_CTRL_SET_PWMA_TYPE_MASK, + .duty_ctrl_rise_point = DUTY_CTRL_PWM1_RISE_POINT, + .duty_ctrl_fall_point = DUTY_CTRL_PWM1_FALL_POINT, + .duty_ctrl_reg = ASPEED_PTCR_DUTY0_CTRL, + .duty_ctrl_rise_fall_mask = DUTY_CTRL_PWM1_RISE_FALL_MASK, + }, + [PWMB] = { + .pwm_en = ASPEED_PTCR_CTRL_PWMB_EN, + .ctrl_reg = ASPEED_PTCR_CTRL, + .type_part1 = ASPEED_PTCR_CTRL_SET_PWMB_TYPE_PART1, + .type_part2 = ASPEED_PTCR_CTRL_SET_PWMB_TYPE_PART2, + .type_mask = ASPEED_PTCR_CTRL_SET_PWMB_TYPE_MASK, + .duty_ctrl_rise_point = DUTY_CTRL_PWM2_RISE_POINT, + .duty_ctrl_fall_point = DUTY_CTRL_PWM2_FALL_POINT, + .duty_ctrl_reg = ASPEED_PTCR_DUTY0_CTRL, + .duty_ctrl_rise_fall_mask = DUTY_CTRL_PWM2_RISE_FALL_MASK, + }, + [PWMC] = { + .pwm_en = ASPEED_PTCR_CTRL_PWMC_EN, + .ctrl_reg = ASPEED_PTCR_CTRL, + .type_part1 = ASPEED_PTCR_CTRL_SET_PWMC_TYPE_PART1, + .type_part2 = ASPEED_PTCR_CTRL_SET_PWMC_TYPE_PART2, + .type_mask = ASPEED_PTCR_CTRL_SET_PWMC_TYPE_MASK, + .duty_ctrl_rise_point = DUTY_CTRL_PWM1_RISE_POINT, + .duty_ctrl_fall_point = DUTY_CTRL_PWM1_FALL_POINT, + .duty_ctrl_reg = ASPEED_PTCR_DUTY1_CTRL, + .duty_ctrl_rise_fall_mask = DUTY_CTRL_PWM1_RISE_FALL_MASK, + }, + [PWMD] = { + .pwm_en = ASPEED_PTCR_CTRL_PWMD_EN, + .ctrl_reg = ASPEED_PTCR_CTRL, + .type_part1 = ASPEED_PTCR_CTRL_SET_PWMD_TYPE_PART1, + .type_part2 = ASPEED_PTCR_CTRL_SET_PWMD_TYPE_PART2, + .type_mask = ASPEED_PTCR_CTRL_SET_PWMD_TYPE_MASK, + .duty_ctrl_rise_point = DUTY_CTRL_PWM2_RISE_POINT, + .duty_ctrl_fall_point = DUTY_CTRL_PWM2_FALL_POINT, + .duty_ctrl_reg = ASPEED_PTCR_DUTY1_CTRL, + .duty_ctrl_rise_fall_mask = DUTY_CTRL_PWM2_RISE_FALL_MASK, + }, + [PWME] = { + .pwm_en = ASPEED_PTCR_CTRL_PWME_EN, + .ctrl_reg = ASPEED_PTCR_CTRL_EXT, + .type_part1 = ASPEED_PTCR_CTRL_SET_PWME_TYPE_PART1, + .type_part2 = ASPEED_PTCR_CTRL_SET_PWME_TYPE_PART2, + .type_mask = ASPEED_PTCR_CTRL_SET_PWME_TYPE_MASK, + .duty_ctrl_rise_point = DUTY_CTRL_PWM1_RISE_POINT, + .duty_ctrl_fall_point = DUTY_CTRL_PWM1_FALL_POINT, + .duty_ctrl_reg = ASPEED_PTCR_DUTY2_CTRL, + .duty_ctrl_rise_fall_mask = DUTY_CTRL_PWM1_RISE_FALL_MASK, + }, + [PWMF] = { + .pwm_en = ASPEED_PTCR_CTRL_PWMF_EN, + .ctrl_reg = ASPEED_PTCR_CTRL_EXT, + .type_part1 = ASPEED_PTCR_CTRL_SET_PWMF_TYPE_PART1, + .type_part2 = ASPEED_PTCR_CTRL_SET_PWMF_TYPE_PART2, + .type_mask = ASPEED_PTCR_CTRL_SET_PWMF_TYPE_MASK, + .duty_ctrl_rise_point = DUTY_CTRL_PWM2_RISE_POINT, + .duty_ctrl_fall_point = DUTY_CTRL_PWM2_FALL_POINT, + .duty_ctrl_reg = ASPEED_PTCR_DUTY2_CTRL, + .duty_ctrl_rise_fall_mask = DUTY_CTRL_PWM2_RISE_FALL_MASK, + }, + [PWMG] = { + .pwm_en = ASPEED_PTCR_CTRL_PWMG_EN, + .ctrl_reg = ASPEED_PTCR_CTRL_EXT, + .type_part1 = ASPEED_PTCR_CTRL_SET_PWMG_TYPE_PART1, + .type_part2 = ASPEED_PTCR_CTRL_SET_PWMG_TYPE_PART2, + .type_mask = ASPEED_PTCR_CTRL_SET_PWMG_TYPE_MASK, + .duty_ctrl_rise_point = DUTY_CTRL_PWM1_RISE_POINT, + .duty_ctrl_fall_point = DUTY_CTRL_PWM1_FALL_POINT, + .duty_ctrl_reg = ASPEED_PTCR_DUTY3_CTRL, + .duty_ctrl_rise_fall_mask = DUTY_CTRL_PWM1_RISE_FALL_MASK, + }, + [PWMH] = { + .pwm_en = ASPEED_PTCR_CTRL_PWMH_EN, + .ctrl_reg = ASPEED_PTCR_CTRL_EXT, + .type_part1 = ASPEED_PTCR_CTRL_SET_PWMH_TYPE_PART1, + .type_part2 = ASPEED_PTCR_CTRL_SET_PWMH_TYPE_PART2, + .type_mask = ASPEED_PTCR_CTRL_SET_PWMH_TYPE_MASK, + .duty_ctrl_rise_point = DUTY_CTRL_PWM2_RISE_POINT, + .duty_ctrl_fall_point = DUTY_CTRL_PWM2_FALL_POINT, + .duty_ctrl_reg = ASPEED_PTCR_DUTY3_CTRL, + .duty_ctrl_rise_fall_mask = DUTY_CTRL_PWM2_RISE_FALL_MASK, + } +}; + +static int regmap_aspeed_pwm_tacho_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + void __iomem *regs = (void __iomem *)context; + + writel(val, regs + reg); + return 0; +} + +static int regmap_aspeed_pwm_tacho_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + void __iomem *regs = (void __iomem *)context; + + *val = readl(regs + reg); + return 0; +} + +static const struct regmap_config aspeed_pwm_tacho_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = ASPEED_PTCR_TYPEO_LIMIT, + .reg_write = regmap_aspeed_pwm_tacho_reg_write, + .reg_read = regmap_aspeed_pwm_tacho_reg_read, + .fast_io = true, +}; + +static void aspeed_set_clock_enable(struct regmap *regmap, bool val) +{ + regmap_update_bits(regmap, ASPEED_PTCR_CTRL, + ASPEED_PTCR_CTRL_CLK_EN, + val ? ASPEED_PTCR_CTRL_CLK_EN : 0); +} + +static void aspeed_set_clock_source(struct regmap *regmap, int val) +{ + regmap_update_bits(regmap, ASPEED_PTCR_CTRL, + ASPEED_PTCR_CTRL_CLK_SRC, + val ? ASPEED_PTCR_CTRL_CLK_SRC : 0); +} + +static void aspeed_set_pwm_clock_values(struct regmap *regmap, u8 type, + u8 div_high, u8 div_low, u8 unit) +{ + u32 reg_value = ((div_high << type_params[type].h_value) | + (div_low << type_params[type].l_value) | + (unit << type_params[type].unit_value)); + + regmap_update_bits(regmap, type_params[type].clk_ctrl_reg, + type_params[type].clk_ctrl_mask, reg_value); +} + +static void aspeed_set_pwm_port_enable(struct regmap *regmap, u8 pwm_port, + bool enable) +{ + regmap_update_bits(regmap, pwm_port_params[pwm_port].ctrl_reg, + pwm_port_params[pwm_port].pwm_en, + enable ? pwm_port_params[pwm_port].pwm_en : 0); +} + +static void aspeed_set_pwm_port_type(struct regmap *regmap, + u8 pwm_port, u8 type) +{ + u32 reg_value = (type & 0x1) << pwm_port_params[pwm_port].type_part1; + + reg_value |= (type & 0x2) << pwm_port_params[pwm_port].type_part2; + + regmap_update_bits(regmap, pwm_port_params[pwm_port].ctrl_reg, + pwm_port_params[pwm_port].type_mask, reg_value); +} + +static void aspeed_set_pwm_port_duty_rising_falling(struct regmap *regmap, + u8 pwm_port, u8 rising, + u8 falling) +{ + u32 reg_value = (rising << + pwm_port_params[pwm_port].duty_ctrl_rise_point); + reg_value |= (falling << + pwm_port_params[pwm_port].duty_ctrl_fall_point); + + regmap_update_bits(regmap, pwm_port_params[pwm_port].duty_ctrl_reg, + pwm_port_params[pwm_port].duty_ctrl_rise_fall_mask, + reg_value); +} + +static void aspeed_set_tacho_type_enable(struct regmap *regmap, u8 type, + bool enable) +{ + regmap_update_bits(regmap, type_params[type].ctrl_reg, + TYPE_CTRL_FAN_TYPE_EN, + enable ? TYPE_CTRL_FAN_TYPE_EN : 0); +} + +static void aspeed_set_tacho_type_values(struct regmap *regmap, u8 type, + u8 mode, u16 unit, u8 division) +{ + u32 reg_value = ((mode << TYPE_CTRL_FAN_MODE) | + (unit << TYPE_CTRL_FAN_PERIOD) | + (division << TYPE_CTRL_FAN_DIVISION)); + + regmap_update_bits(regmap, type_params[type].ctrl_reg, + TYPE_CTRL_FAN_MASK, reg_value); + regmap_update_bits(regmap, type_params[type].ctrl_reg1, + TYPE_CTRL_FAN1_MASK, unit << 16); +} + +static void aspeed_set_fan_tach_ch_enable(struct regmap *regmap, u8 fan_tach_ch, + bool enable) +{ + regmap_update_bits(regmap, ASPEED_PTCR_CTRL, + ASPEED_PTCR_CTRL_FAN_NUM_EN(fan_tach_ch), + enable ? + ASPEED_PTCR_CTRL_FAN_NUM_EN(fan_tach_ch) : 0); +} + +static void aspeed_set_fan_tach_ch_source(struct regmap *regmap, u8 fan_tach_ch, + u8 fan_tach_ch_source) +{ + u32 reg_value1 = ((fan_tach_ch_source & 0x3) << + TACH_PWM_SOURCE_BIT01(fan_tach_ch)); + u32 reg_value2 = (((fan_tach_ch_source & 0x4) >> 2) << + TACH_PWM_SOURCE_BIT2(fan_tach_ch)); + + regmap_update_bits(regmap, ASPEED_PTCR_TACH_SOURCE, + TACH_PWM_SOURCE_MASK_BIT01(fan_tach_ch), + reg_value1); + + regmap_update_bits(regmap, ASPEED_PTCR_TACH_SOURCE_EXT, + TACH_PWM_SOURCE_MASK_BIT2(fan_tach_ch), + reg_value2); +} + +static void aspeed_set_pwm_port_fan_ctrl(struct aspeed_pwm_tacho_data *priv, + u8 index, u8 fan_ctrl) +{ + u16 period, dc_time_on; + + period = priv->type_pwm_clock_unit[priv->pwm_port_type[index]]; + period += 1; + dc_time_on = (fan_ctrl * period) / PWM_MAX; + + if (dc_time_on == 0) { + aspeed_set_pwm_port_enable(priv->regmap, index, false); + } else { + if (dc_time_on == period) + dc_time_on = 0; + + aspeed_set_pwm_port_duty_rising_falling(priv->regmap, index, 0, + dc_time_on); + aspeed_set_pwm_port_enable(priv->regmap, index, true); + } +} + +static u32 aspeed_get_fan_tach_ch_measure_period(struct aspeed_pwm_tacho_data + *priv, u8 type) +{ + u32 clk; + u16 tacho_unit; + u8 clk_unit, div_h, div_l, tacho_div; + + clk = priv->clk_freq; + clk_unit = priv->type_pwm_clock_unit[type]; + div_h = priv->type_pwm_clock_division_h[type]; + div_h = 0x1 << div_h; + div_l = priv->type_pwm_clock_division_l[type]; + if (div_l == 0) + div_l = 1; + else + div_l = div_l * 2; + + tacho_unit = priv->type_fan_tach_unit[type]; + tacho_div = priv->type_fan_tach_clock_division[type]; + + tacho_div = 0x4 << (tacho_div * 2); + return clk / (clk_unit * div_h * div_l * tacho_div * tacho_unit); +} + +static int aspeed_get_fan_tach_ch_rpm(struct aspeed_pwm_tacho_data *priv, + u8 fan_tach_ch) +{ + u32 raw_data, tach_div, clk_source, msec, usec, val; + u8 fan_tach_ch_source, type, mode, both; + int ret; + + regmap_write(priv->regmap, ASPEED_PTCR_TRIGGER, 0); + regmap_write(priv->regmap, ASPEED_PTCR_TRIGGER, 0x1 << fan_tach_ch); + + fan_tach_ch_source = priv->fan_tach_ch_source[fan_tach_ch]; + type = priv->pwm_port_type[fan_tach_ch_source]; + + msec = (1000 / aspeed_get_fan_tach_ch_measure_period(priv, type)); + usec = msec * 1000; + + ret = regmap_read_poll_timeout( + priv->regmap, + ASPEED_PTCR_RESULT, + val, + (val & RESULT_STATUS_MASK), + ASPEED_RPM_STATUS_SLEEP_USEC, + usec); + + /* return -ETIMEDOUT if we didn't get an answer. */ + if (ret) + return ret; + + raw_data = val & RESULT_VALUE_MASK; + tach_div = priv->type_fan_tach_clock_division[type]; + /* + * We need the mode to determine if the raw_data is double (from + * counting both edges). + */ + mode = priv->type_fan_tach_mode[type]; + both = (mode & BOTH_EDGES) ? 1 : 0; + + tach_div = (0x4 << both) << (tach_div * 2); + clk_source = priv->clk_freq; + + if (raw_data == 0) + return 0; + + return (clk_source * 60) / (2 * raw_data * tach_div); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int index = sensor_attr->index; + int ret; + struct aspeed_pwm_tacho_data *priv = dev_get_drvdata(dev); + long fan_ctrl; + + ret = kstrtol(buf, 10, &fan_ctrl); + if (ret != 0) + return ret; + + if (fan_ctrl < 0 || fan_ctrl > PWM_MAX) + return -EINVAL; + + if (priv->pwm_port_fan_ctrl[index] == fan_ctrl) + return count; + + priv->pwm_port_fan_ctrl[index] = fan_ctrl; + aspeed_set_pwm_port_fan_ctrl(priv, index, fan_ctrl); + + return count; +} + +static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int index = sensor_attr->index; + struct aspeed_pwm_tacho_data *priv = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", priv->pwm_port_fan_ctrl[index]); +} + +static ssize_t show_rpm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int index = sensor_attr->index; + int rpm; + struct aspeed_pwm_tacho_data *priv = dev_get_drvdata(dev); + + rpm = aspeed_get_fan_tach_ch_rpm(priv, index); + if (rpm < 0) + return rpm; + + return sprintf(buf, "%d\n", rpm); +} + +static umode_t pwm_is_visible(struct kobject *kobj, + struct attribute *a, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct aspeed_pwm_tacho_data *priv = dev_get_drvdata(dev); + + if (!priv->pwm_present[index]) + return 0; + return a->mode; +} + +static umode_t fan_dev_is_visible(struct kobject *kobj, + struct attribute *a, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct aspeed_pwm_tacho_data *priv = dev_get_drvdata(dev); + + if (!priv->fan_tach_present[index]) + return 0; + return a->mode; +} + +static SENSOR_DEVICE_ATTR(pwm1, 0644, + show_pwm, set_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm2, 0644, + show_pwm, set_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm3, 0644, + show_pwm, set_pwm, 2); +static SENSOR_DEVICE_ATTR(pwm4, 0644, + show_pwm, set_pwm, 3); +static SENSOR_DEVICE_ATTR(pwm5, 0644, + show_pwm, set_pwm, 4); +static SENSOR_DEVICE_ATTR(pwm6, 0644, + show_pwm, set_pwm, 5); +static SENSOR_DEVICE_ATTR(pwm7, 0644, + show_pwm, set_pwm, 6); +static SENSOR_DEVICE_ATTR(pwm8, 0644, + show_pwm, set_pwm, 7); +static struct attribute *pwm_dev_attrs[] = { + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm4.dev_attr.attr, + &sensor_dev_attr_pwm5.dev_attr.attr, + &sensor_dev_attr_pwm6.dev_attr.attr, + &sensor_dev_attr_pwm7.dev_attr.attr, + &sensor_dev_attr_pwm8.dev_attr.attr, + NULL, +}; + +static const struct attribute_group pwm_dev_group = { + .attrs = pwm_dev_attrs, + .is_visible = pwm_is_visible, +}; + +static SENSOR_DEVICE_ATTR(fan1_input, 0444, + show_rpm, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, 0444, + show_rpm, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_input, 0444, + show_rpm, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_input, 0444, + show_rpm, NULL, 3); +static SENSOR_DEVICE_ATTR(fan5_input, 0444, + show_rpm, NULL, 4); +static SENSOR_DEVICE_ATTR(fan6_input, 0444, + show_rpm, NULL, 5); +static SENSOR_DEVICE_ATTR(fan7_input, 0444, + show_rpm, NULL, 6); +static SENSOR_DEVICE_ATTR(fan8_input, 0444, + show_rpm, NULL, 7); +static SENSOR_DEVICE_ATTR(fan9_input, 0444, + show_rpm, NULL, 8); +static SENSOR_DEVICE_ATTR(fan10_input, 0444, + show_rpm, NULL, 9); +static SENSOR_DEVICE_ATTR(fan11_input, 0444, + show_rpm, NULL, 10); +static SENSOR_DEVICE_ATTR(fan12_input, 0444, + show_rpm, NULL, 11); +static SENSOR_DEVICE_ATTR(fan13_input, 0444, + show_rpm, NULL, 12); +static SENSOR_DEVICE_ATTR(fan14_input, 0444, + show_rpm, NULL, 13); +static SENSOR_DEVICE_ATTR(fan15_input, 0444, + show_rpm, NULL, 14); +static SENSOR_DEVICE_ATTR(fan16_input, 0444, + show_rpm, NULL, 15); +static struct attribute *fan_dev_attrs[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan5_input.dev_attr.attr, + &sensor_dev_attr_fan6_input.dev_attr.attr, + &sensor_dev_attr_fan7_input.dev_attr.attr, + &sensor_dev_attr_fan8_input.dev_attr.attr, + &sensor_dev_attr_fan9_input.dev_attr.attr, + &sensor_dev_attr_fan10_input.dev_attr.attr, + &sensor_dev_attr_fan11_input.dev_attr.attr, + &sensor_dev_attr_fan12_input.dev_attr.attr, + &sensor_dev_attr_fan13_input.dev_attr.attr, + &sensor_dev_attr_fan14_input.dev_attr.attr, + &sensor_dev_attr_fan15_input.dev_attr.attr, + &sensor_dev_attr_fan16_input.dev_attr.attr, + NULL +}; + +static const struct attribute_group fan_dev_group = { + .attrs = fan_dev_attrs, + .is_visible = fan_dev_is_visible, +}; + +/* + * The clock type is type M : + * The PWM frequency = 24MHz / (type M clock division L bit * + * type M clock division H bit * (type M PWM period bit + 1)) + */ +static void aspeed_create_type(struct aspeed_pwm_tacho_data *priv) +{ + priv->type_pwm_clock_division_h[TYPEM] = M_PWM_DIV_H; + priv->type_pwm_clock_division_l[TYPEM] = M_PWM_DIV_L; + priv->type_pwm_clock_unit[TYPEM] = M_PWM_PERIOD; + aspeed_set_pwm_clock_values(priv->regmap, TYPEM, M_PWM_DIV_H, + M_PWM_DIV_L, M_PWM_PERIOD); + aspeed_set_tacho_type_enable(priv->regmap, TYPEM, true); + priv->type_fan_tach_clock_division[TYPEM] = M_TACH_CLK_DIV; + priv->type_fan_tach_unit[TYPEM] = M_TACH_UNIT; + priv->type_fan_tach_mode[TYPEM] = M_TACH_MODE; + aspeed_set_tacho_type_values(priv->regmap, TYPEM, M_TACH_MODE, + M_TACH_UNIT, M_TACH_CLK_DIV); +} + +static void aspeed_create_pwm_port(struct aspeed_pwm_tacho_data *priv, + u8 pwm_port) +{ + aspeed_set_pwm_port_enable(priv->regmap, pwm_port, true); + priv->pwm_present[pwm_port] = true; + + priv->pwm_port_type[pwm_port] = TYPEM; + aspeed_set_pwm_port_type(priv->regmap, pwm_port, TYPEM); + + priv->pwm_port_fan_ctrl[pwm_port] = INIT_FAN_CTRL; + aspeed_set_pwm_port_fan_ctrl(priv, pwm_port, INIT_FAN_CTRL); +} + +static void aspeed_create_fan_tach_channel(struct aspeed_pwm_tacho_data *priv, + u8 *fan_tach_ch, + int count, + u8 pwm_source) +{ + u8 val, index; + + for (val = 0; val < count; val++) { + index = fan_tach_ch[val]; + aspeed_set_fan_tach_ch_enable(priv->regmap, index, true); + priv->fan_tach_present[index] = true; + priv->fan_tach_ch_source[index] = pwm_source; + aspeed_set_fan_tach_ch_source(priv->regmap, index, pwm_source); + } +} + +static int +aspeed_pwm_cz_get_max_state(struct thermal_cooling_device *tcdev, + unsigned long *state) +{ + struct aspeed_cooling_device *cdev = tcdev->devdata; + + *state = cdev->max_state; + + return 0; +} + +static int +aspeed_pwm_cz_get_cur_state(struct thermal_cooling_device *tcdev, + unsigned long *state) +{ + struct aspeed_cooling_device *cdev = tcdev->devdata; + + *state = cdev->cur_state; + + return 0; +} + +static int +aspeed_pwm_cz_set_cur_state(struct thermal_cooling_device *tcdev, + unsigned long state) +{ + struct aspeed_cooling_device *cdev = tcdev->devdata; + + if (state > cdev->max_state) + return -EINVAL; + + cdev->cur_state = state; + cdev->priv->pwm_port_fan_ctrl[cdev->pwm_port] = + cdev->cooling_levels[cdev->cur_state]; + aspeed_set_pwm_port_fan_ctrl(cdev->priv, cdev->pwm_port, + cdev->cooling_levels[cdev->cur_state]); + + return 0; +} + +static const struct thermal_cooling_device_ops aspeed_pwm_cool_ops = { + .get_max_state = aspeed_pwm_cz_get_max_state, + .get_cur_state = aspeed_pwm_cz_get_cur_state, + .set_cur_state = aspeed_pwm_cz_set_cur_state, +}; + +static int aspeed_create_pwm_cooling(struct device *dev, + struct device_node *child, + struct aspeed_pwm_tacho_data *priv, + u32 pwm_port, u8 num_levels) +{ + int ret; + struct aspeed_cooling_device *cdev; + + cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL); + + if (!cdev) + return -ENOMEM; + + cdev->cooling_levels = devm_kzalloc(dev, num_levels, GFP_KERNEL); + if (!cdev->cooling_levels) + return -ENOMEM; + + cdev->max_state = num_levels - 1; + ret = of_property_read_u8_array(child, "cooling-levels", + cdev->cooling_levels, + num_levels); + if (ret) { + dev_err(dev, "Property 'cooling-levels' cannot be read.\n"); + return ret; + } + snprintf(cdev->name, MAX_CDEV_NAME_LEN, "%s%d", child->name, pwm_port); + + cdev->tcdev = thermal_of_cooling_device_register(child, + cdev->name, + cdev, + &aspeed_pwm_cool_ops); + if (IS_ERR(cdev->tcdev)) + return PTR_ERR(cdev->tcdev); + + cdev->priv = priv; + cdev->pwm_port = pwm_port; + + priv->cdev[pwm_port] = cdev; + + return 0; +} + +static int aspeed_create_fan(struct device *dev, + struct device_node *child, + struct aspeed_pwm_tacho_data *priv) +{ + u8 *fan_tach_ch; + u32 pwm_port; + int ret, count; + + ret = of_property_read_u32(child, "reg", &pwm_port); + if (ret) + return ret; + if (pwm_port >= ARRAY_SIZE(pwm_port_params)) + return -EINVAL; + aspeed_create_pwm_port(priv, (u8)pwm_port); + + ret = of_property_count_u8_elems(child, "cooling-levels"); + + if (ret > 0) { + ret = aspeed_create_pwm_cooling(dev, child, priv, pwm_port, + ret); + if (ret) + return ret; + } + + count = of_property_count_u8_elems(child, "aspeed,fan-tach-ch"); + if (count < 1) + return -EINVAL; + fan_tach_ch = devm_kcalloc(dev, count, sizeof(*fan_tach_ch), + GFP_KERNEL); + if (!fan_tach_ch) + return -ENOMEM; + ret = of_property_read_u8_array(child, "aspeed,fan-tach-ch", + fan_tach_ch, count); + if (ret) + return ret; + aspeed_create_fan_tach_channel(priv, fan_tach_ch, count, pwm_port); + + return 0; +} + +static void aspeed_pwm_tacho_remove(void *data) +{ + struct aspeed_pwm_tacho_data *priv = data; + + reset_control_assert(priv->rst); +} + +static int aspeed_pwm_tacho_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np, *child; + struct aspeed_pwm_tacho_data *priv; + void __iomem *regs; + struct resource *res; + struct device *hwmon; + struct clk *clk; + int ret; + + np = dev->of_node; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->regmap = devm_regmap_init(dev, NULL, (__force void *)regs, + &aspeed_pwm_tacho_regmap_config); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->rst = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(priv->rst)) { + dev_err(dev, + "missing or invalid reset controller device tree entry"); + return PTR_ERR(priv->rst); + } + reset_control_deassert(priv->rst); + + ret = devm_add_action_or_reset(dev, aspeed_pwm_tacho_remove, priv); + if (ret) + return ret; + + regmap_write(priv->regmap, ASPEED_PTCR_TACH_SOURCE, 0); + regmap_write(priv->regmap, ASPEED_PTCR_TACH_SOURCE_EXT, 0); + + clk = devm_clk_get(dev, NULL); + if (IS_ERR(clk)) + return -ENODEV; + priv->clk_freq = clk_get_rate(clk); + aspeed_set_clock_enable(priv->regmap, true); + aspeed_set_clock_source(priv->regmap, 0); + + aspeed_create_type(priv); + + for_each_child_of_node(np, child) { + ret = aspeed_create_fan(dev, child, priv); + if (ret) { + of_node_put(child); + return ret; + } + } + + priv->groups[0] = &pwm_dev_group; + priv->groups[1] = &fan_dev_group; + priv->groups[2] = NULL; + hwmon = devm_hwmon_device_register_with_groups(dev, + "aspeed_pwm_tacho", + priv, priv->groups); + return PTR_ERR_OR_ZERO(hwmon); +} + +static const struct of_device_id of_pwm_tacho_match_table[] = { + { .compatible = "aspeed,ast2400-pwm-tacho", }, + { .compatible = "aspeed,ast2500-pwm-tacho", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_pwm_tacho_match_table); + +static struct platform_driver aspeed_pwm_tacho_driver = { + .probe = aspeed_pwm_tacho_probe, + .driver = { + .name = "aspeed_pwm_tacho", + .of_match_table = of_pwm_tacho_match_table, + }, +}; + +module_platform_driver(aspeed_pwm_tacho_driver); + +MODULE_AUTHOR("Jaghathiswari Rankappagounder Natarajan "); +MODULE_DESCRIPTION("ASPEED PWM and Fan Tacho device driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/asus_atk0110.c b/drivers/hwmon/asus_atk0110.c new file mode 100644 index 000000000..a6636fe42 --- /dev/null +++ b/drivers/hwmon/asus_atk0110.c @@ -0,0 +1,1415 @@ +/* + * Copyright (C) 2007-2009 Luca Tettamanti + * + * This file is released under the GPLv2 + * See COPYING in the top level directory of the kernel tree. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ATK_HID "ATK0110" + +static bool new_if; +module_param(new_if, bool, 0); +MODULE_PARM_DESC(new_if, "Override detection heuristic and force the use of the new ATK0110 interface"); + +static const struct dmi_system_id __initconst atk_force_new_if[] = { + { + /* Old interface has broken MCH temp monitoring */ + .ident = "Asus Sabertooth X58", + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "SABERTOOTH X58") + } + }, { + /* Old interface reads the same sensor for fan0 and fan1 */ + .ident = "Asus M5A78L", + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "M5A78L") + } + }, + { } +}; + +/* + * Minimum time between readings, enforced in order to avoid + * hogging the CPU. + */ +#define CACHE_TIME HZ + +#define BOARD_ID "MBIF" +#define METHOD_ENUMERATE "GGRP" +#define METHOD_READ "GITM" +#define METHOD_WRITE "SITM" +#define METHOD_OLD_READ_TMP "RTMP" +#define METHOD_OLD_READ_VLT "RVLT" +#define METHOD_OLD_READ_FAN "RFAN" +#define METHOD_OLD_ENUM_TMP "TSIF" +#define METHOD_OLD_ENUM_VLT "VSIF" +#define METHOD_OLD_ENUM_FAN "FSIF" + +#define ATK_MUX_HWMON 0x00000006ULL +#define ATK_MUX_MGMT 0x00000011ULL + +#define ATK_CLASS_MASK 0xff000000ULL +#define ATK_CLASS_FREQ_CTL 0x03000000ULL +#define ATK_CLASS_FAN_CTL 0x04000000ULL +#define ATK_CLASS_HWMON 0x06000000ULL +#define ATK_CLASS_MGMT 0x11000000ULL + +#define ATK_TYPE_MASK 0x00ff0000ULL +#define HWMON_TYPE_VOLT 0x00020000ULL +#define HWMON_TYPE_TEMP 0x00030000ULL +#define HWMON_TYPE_FAN 0x00040000ULL + +#define ATK_ELEMENT_ID_MASK 0x0000ffffULL + +#define ATK_EC_ID 0x11060004ULL + +enum atk_pack_member { + HWMON_PACK_FLAGS, + HWMON_PACK_NAME, + HWMON_PACK_LIMIT1, + HWMON_PACK_LIMIT2, + HWMON_PACK_ENABLE +}; + +/* New package format */ +#define _HWMON_NEW_PACK_SIZE 7 +#define _HWMON_NEW_PACK_FLAGS 0 +#define _HWMON_NEW_PACK_NAME 1 +#define _HWMON_NEW_PACK_UNK1 2 +#define _HWMON_NEW_PACK_UNK2 3 +#define _HWMON_NEW_PACK_LIMIT1 4 +#define _HWMON_NEW_PACK_LIMIT2 5 +#define _HWMON_NEW_PACK_ENABLE 6 + +/* Old package format */ +#define _HWMON_OLD_PACK_SIZE 5 +#define _HWMON_OLD_PACK_FLAGS 0 +#define _HWMON_OLD_PACK_NAME 1 +#define _HWMON_OLD_PACK_LIMIT1 2 +#define _HWMON_OLD_PACK_LIMIT2 3 +#define _HWMON_OLD_PACK_ENABLE 4 + + +struct atk_data { + struct device *hwmon_dev; + acpi_handle atk_handle; + struct acpi_device *acpi_dev; + + bool old_interface; + + /* old interface */ + acpi_handle rtmp_handle; + acpi_handle rvlt_handle; + acpi_handle rfan_handle; + /* new interface */ + acpi_handle enumerate_handle; + acpi_handle read_handle; + acpi_handle write_handle; + + bool disable_ec; + + int voltage_count; + int temperature_count; + int fan_count; + struct list_head sensor_list; + struct attribute_group attr_group; + const struct attribute_group *attr_groups[2]; + + struct { + struct dentry *root; + u32 id; + } debugfs; +}; + + +typedef ssize_t (*sysfs_show_func)(struct device *dev, + struct device_attribute *attr, char *buf); + +static const struct acpi_device_id atk_ids[] = { + {ATK_HID, 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, atk_ids); + +#define ATTR_NAME_SIZE 16 /* Worst case is "tempN_input" */ + +struct atk_sensor_data { + struct list_head list; + struct atk_data *data; + struct device_attribute label_attr; + struct device_attribute input_attr; + struct device_attribute limit1_attr; + struct device_attribute limit2_attr; + char label_attr_name[ATTR_NAME_SIZE]; + char input_attr_name[ATTR_NAME_SIZE]; + char limit1_attr_name[ATTR_NAME_SIZE]; + char limit2_attr_name[ATTR_NAME_SIZE]; + u64 id; + u64 type; + u64 limit1; + u64 limit2; + u64 cached_value; + unsigned long last_updated; /* in jiffies */ + bool is_valid; + char const *acpi_name; +}; + +/* + * Return buffer format: + * [0-3] "value" is valid flag + * [4-7] value + * [8- ] unknown stuff on newer mobos + */ +struct atk_acpi_ret_buffer { + u32 flags; + u32 value; + u8 data[]; +}; + +/* Input buffer used for GITM and SITM methods */ +struct atk_acpi_input_buf { + u32 id; + u32 param1; + u32 param2; +}; + +static int atk_add(struct acpi_device *device); +static int atk_remove(struct acpi_device *device); +static void atk_print_sensor(struct atk_data *data, union acpi_object *obj); +static int atk_read_value(struct atk_sensor_data *sensor, u64 *value); + +static struct acpi_driver atk_driver = { + .name = ATK_HID, + .class = "hwmon", + .ids = atk_ids, + .ops = { + .add = atk_add, + .remove = atk_remove, + }, +}; + +#define input_to_atk_sensor(attr) \ + container_of(attr, struct atk_sensor_data, input_attr) + +#define label_to_atk_sensor(attr) \ + container_of(attr, struct atk_sensor_data, label_attr) + +#define limit1_to_atk_sensor(attr) \ + container_of(attr, struct atk_sensor_data, limit1_attr) + +#define limit2_to_atk_sensor(attr) \ + container_of(attr, struct atk_sensor_data, limit2_attr) + +static ssize_t atk_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atk_sensor_data *s = input_to_atk_sensor(attr); + u64 value; + int err; + + err = atk_read_value(s, &value); + if (err) + return err; + + if (s->type == HWMON_TYPE_TEMP) + /* ACPI returns decidegree */ + value *= 100; + + return sprintf(buf, "%llu\n", value); +} + +static ssize_t atk_label_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atk_sensor_data *s = label_to_atk_sensor(attr); + + return sprintf(buf, "%s\n", s->acpi_name); +} + +static ssize_t atk_limit1_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atk_sensor_data *s = limit1_to_atk_sensor(attr); + u64 value = s->limit1; + + if (s->type == HWMON_TYPE_TEMP) + value *= 100; + + return sprintf(buf, "%lld\n", value); +} + +static ssize_t atk_limit2_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atk_sensor_data *s = limit2_to_atk_sensor(attr); + u64 value = s->limit2; + + if (s->type == HWMON_TYPE_TEMP) + value *= 100; + + return sprintf(buf, "%lld\n", value); +} + +static void atk_init_attribute(struct device_attribute *attr, char *name, + sysfs_show_func show) +{ + sysfs_attr_init(&attr->attr); + attr->attr.name = name; + attr->attr.mode = 0444; + attr->show = show; + attr->store = NULL; +} + + +static union acpi_object *atk_get_pack_member(struct atk_data *data, + union acpi_object *pack, + enum atk_pack_member m) +{ + bool old_if = data->old_interface; + int offset; + + switch (m) { + case HWMON_PACK_FLAGS: + offset = old_if ? _HWMON_OLD_PACK_FLAGS : _HWMON_NEW_PACK_FLAGS; + break; + case HWMON_PACK_NAME: + offset = old_if ? _HWMON_OLD_PACK_NAME : _HWMON_NEW_PACK_NAME; + break; + case HWMON_PACK_LIMIT1: + offset = old_if ? _HWMON_OLD_PACK_LIMIT1 : + _HWMON_NEW_PACK_LIMIT1; + break; + case HWMON_PACK_LIMIT2: + offset = old_if ? _HWMON_OLD_PACK_LIMIT2 : + _HWMON_NEW_PACK_LIMIT2; + break; + case HWMON_PACK_ENABLE: + offset = old_if ? _HWMON_OLD_PACK_ENABLE : + _HWMON_NEW_PACK_ENABLE; + break; + default: + return NULL; + } + + return &pack->package.elements[offset]; +} + + +/* + * New package format is: + * - flag (int) + * class - used for de-muxing the request to the correct GITn + * type (volt, temp, fan) + * sensor id | + * sensor id - used for de-muxing the request _inside_ the GITn + * - name (str) + * - unknown (int) + * - unknown (int) + * - limit1 (int) + * - limit2 (int) + * - enable (int) + * + * The old package has the same format but it's missing the two unknown fields. + */ +static int validate_hwmon_pack(struct atk_data *data, union acpi_object *obj) +{ + struct device *dev = &data->acpi_dev->dev; + union acpi_object *tmp; + bool old_if = data->old_interface; + int const expected_size = old_if ? _HWMON_OLD_PACK_SIZE : + _HWMON_NEW_PACK_SIZE; + + if (obj->type != ACPI_TYPE_PACKAGE) { + dev_warn(dev, "Invalid type: %d\n", obj->type); + return -EINVAL; + } + + if (obj->package.count != expected_size) { + dev_warn(dev, "Invalid package size: %d, expected: %d\n", + obj->package.count, expected_size); + return -EINVAL; + } + + tmp = atk_get_pack_member(data, obj, HWMON_PACK_FLAGS); + if (tmp->type != ACPI_TYPE_INTEGER) { + dev_warn(dev, "Invalid type (flag): %d\n", tmp->type); + return -EINVAL; + } + + tmp = atk_get_pack_member(data, obj, HWMON_PACK_NAME); + if (tmp->type != ACPI_TYPE_STRING) { + dev_warn(dev, "Invalid type (name): %d\n", tmp->type); + return -EINVAL; + } + + /* Don't check... we don't know what they're useful for anyway */ +#if 0 + tmp = &obj->package.elements[HWMON_PACK_UNK1]; + if (tmp->type != ACPI_TYPE_INTEGER) { + dev_warn(dev, "Invalid type (unk1): %d\n", tmp->type); + return -EINVAL; + } + + tmp = &obj->package.elements[HWMON_PACK_UNK2]; + if (tmp->type != ACPI_TYPE_INTEGER) { + dev_warn(dev, "Invalid type (unk2): %d\n", tmp->type); + return -EINVAL; + } +#endif + + tmp = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT1); + if (tmp->type != ACPI_TYPE_INTEGER) { + dev_warn(dev, "Invalid type (limit1): %d\n", tmp->type); + return -EINVAL; + } + + tmp = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT2); + if (tmp->type != ACPI_TYPE_INTEGER) { + dev_warn(dev, "Invalid type (limit2): %d\n", tmp->type); + return -EINVAL; + } + + tmp = atk_get_pack_member(data, obj, HWMON_PACK_ENABLE); + if (tmp->type != ACPI_TYPE_INTEGER) { + dev_warn(dev, "Invalid type (enable): %d\n", tmp->type); + return -EINVAL; + } + + atk_print_sensor(data, obj); + + return 0; +} + +#ifdef DEBUG +static char const *atk_sensor_type(union acpi_object *flags) +{ + u64 type = flags->integer.value & ATK_TYPE_MASK; + char const *what; + + switch (type) { + case HWMON_TYPE_VOLT: + what = "voltage"; + break; + case HWMON_TYPE_TEMP: + what = "temperature"; + break; + case HWMON_TYPE_FAN: + what = "fan"; + break; + default: + what = "unknown"; + break; + } + + return what; +} +#endif + +static void atk_print_sensor(struct atk_data *data, union acpi_object *obj) +{ +#ifdef DEBUG + struct device *dev = &data->acpi_dev->dev; + union acpi_object *flags; + union acpi_object *name; + union acpi_object *limit1; + union acpi_object *limit2; + union acpi_object *enable; + char const *what; + + flags = atk_get_pack_member(data, obj, HWMON_PACK_FLAGS); + name = atk_get_pack_member(data, obj, HWMON_PACK_NAME); + limit1 = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT1); + limit2 = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT2); + enable = atk_get_pack_member(data, obj, HWMON_PACK_ENABLE); + + what = atk_sensor_type(flags); + + dev_dbg(dev, "%s: %#llx %s [%llu-%llu] %s\n", what, + flags->integer.value, + name->string.pointer, + limit1->integer.value, limit2->integer.value, + enable->integer.value ? "enabled" : "disabled"); +#endif +} + +static int atk_read_value_old(struct atk_sensor_data *sensor, u64 *value) +{ + struct atk_data *data = sensor->data; + struct device *dev = &data->acpi_dev->dev; + struct acpi_object_list params; + union acpi_object id; + acpi_status status; + acpi_handle method; + + switch (sensor->type) { + case HWMON_TYPE_VOLT: + method = data->rvlt_handle; + break; + case HWMON_TYPE_TEMP: + method = data->rtmp_handle; + break; + case HWMON_TYPE_FAN: + method = data->rfan_handle; + break; + default: + return -EINVAL; + } + + id.type = ACPI_TYPE_INTEGER; + id.integer.value = sensor->id; + + params.count = 1; + params.pointer = &id; + + status = acpi_evaluate_integer(method, NULL, ¶ms, value); + if (status != AE_OK) { + dev_warn(dev, "%s: ACPI exception: %s\n", __func__, + acpi_format_exception(status)); + return -EIO; + } + + return 0; +} + +static union acpi_object *atk_ggrp(struct atk_data *data, u16 mux) +{ + struct device *dev = &data->acpi_dev->dev; + struct acpi_buffer buf; + acpi_status ret; + struct acpi_object_list params; + union acpi_object id; + union acpi_object *pack; + + id.type = ACPI_TYPE_INTEGER; + id.integer.value = mux; + params.count = 1; + params.pointer = &id; + + buf.length = ACPI_ALLOCATE_BUFFER; + ret = acpi_evaluate_object(data->enumerate_handle, NULL, ¶ms, &buf); + if (ret != AE_OK) { + dev_err(dev, "GGRP[%#x] ACPI exception: %s\n", mux, + acpi_format_exception(ret)); + return ERR_PTR(-EIO); + } + pack = buf.pointer; + if (pack->type != ACPI_TYPE_PACKAGE) { + /* Execution was successful, but the id was not found */ + ACPI_FREE(pack); + return ERR_PTR(-ENOENT); + } + + if (pack->package.count < 1) { + dev_err(dev, "GGRP[%#x] package is too small\n", mux); + ACPI_FREE(pack); + return ERR_PTR(-EIO); + } + return pack; +} + +static union acpi_object *atk_gitm(struct atk_data *data, u64 id) +{ + struct device *dev = &data->acpi_dev->dev; + struct atk_acpi_input_buf buf; + union acpi_object tmp; + struct acpi_object_list params; + struct acpi_buffer ret; + union acpi_object *obj; + acpi_status status; + + buf.id = id; + buf.param1 = 0; + buf.param2 = 0; + + tmp.type = ACPI_TYPE_BUFFER; + tmp.buffer.pointer = (u8 *)&buf; + tmp.buffer.length = sizeof(buf); + + params.count = 1; + params.pointer = (void *)&tmp; + + ret.length = ACPI_ALLOCATE_BUFFER; + status = acpi_evaluate_object_typed(data->read_handle, NULL, ¶ms, + &ret, ACPI_TYPE_BUFFER); + if (status != AE_OK) { + dev_warn(dev, "GITM[%#llx] ACPI exception: %s\n", id, + acpi_format_exception(status)); + return ERR_PTR(-EIO); + } + obj = ret.pointer; + + /* Sanity check */ + if (obj->buffer.length < 8) { + dev_warn(dev, "Unexpected ASBF length: %u\n", + obj->buffer.length); + ACPI_FREE(obj); + return ERR_PTR(-EIO); + } + return obj; +} + +static union acpi_object *atk_sitm(struct atk_data *data, + struct atk_acpi_input_buf *buf) +{ + struct device *dev = &data->acpi_dev->dev; + struct acpi_object_list params; + union acpi_object tmp; + struct acpi_buffer ret; + union acpi_object *obj; + acpi_status status; + + tmp.type = ACPI_TYPE_BUFFER; + tmp.buffer.pointer = (u8 *)buf; + tmp.buffer.length = sizeof(*buf); + + params.count = 1; + params.pointer = &tmp; + + ret.length = ACPI_ALLOCATE_BUFFER; + status = acpi_evaluate_object_typed(data->write_handle, NULL, ¶ms, + &ret, ACPI_TYPE_BUFFER); + if (status != AE_OK) { + dev_warn(dev, "SITM[%#x] ACPI exception: %s\n", buf->id, + acpi_format_exception(status)); + return ERR_PTR(-EIO); + } + obj = ret.pointer; + + /* Sanity check */ + if (obj->buffer.length < 8) { + dev_warn(dev, "Unexpected ASBF length: %u\n", + obj->buffer.length); + ACPI_FREE(obj); + return ERR_PTR(-EIO); + } + return obj; +} + +static int atk_read_value_new(struct atk_sensor_data *sensor, u64 *value) +{ + struct atk_data *data = sensor->data; + struct device *dev = &data->acpi_dev->dev; + union acpi_object *obj; + struct atk_acpi_ret_buffer *buf; + int err = 0; + + obj = atk_gitm(data, sensor->id); + if (IS_ERR(obj)) + return PTR_ERR(obj); + + buf = (struct atk_acpi_ret_buffer *)obj->buffer.pointer; + if (buf->flags == 0) { + /* + * The reading is not valid, possible causes: + * - sensor failure + * - enumeration was FUBAR (and we didn't notice) + */ + dev_warn(dev, "Read failed, sensor = %#llx\n", sensor->id); + err = -EIO; + goto out; + } + + *value = buf->value; +out: + ACPI_FREE(obj); + return err; +} + +static int atk_read_value(struct atk_sensor_data *sensor, u64 *value) +{ + int err; + + if (!sensor->is_valid || + time_after(jiffies, sensor->last_updated + CACHE_TIME)) { + if (sensor->data->old_interface) + err = atk_read_value_old(sensor, value); + else + err = atk_read_value_new(sensor, value); + + if (err) + return err; + + sensor->is_valid = true; + sensor->last_updated = jiffies; + sensor->cached_value = *value; + } else { + *value = sensor->cached_value; + err = 0; + } + + return err; +} + +#ifdef CONFIG_DEBUG_FS +static int atk_debugfs_gitm_get(void *p, u64 *val) +{ + struct atk_data *data = p; + union acpi_object *ret; + struct atk_acpi_ret_buffer *buf; + int err = 0; + + if (!data->read_handle) + return -ENODEV; + + if (!data->debugfs.id) + return -EINVAL; + + ret = atk_gitm(data, data->debugfs.id); + if (IS_ERR(ret)) + return PTR_ERR(ret); + + buf = (struct atk_acpi_ret_buffer *)ret->buffer.pointer; + if (buf->flags) + *val = buf->value; + else + err = -EIO; + + ACPI_FREE(ret); + return err; +} + +DEFINE_SIMPLE_ATTRIBUTE(atk_debugfs_gitm, + atk_debugfs_gitm_get, + NULL, + "0x%08llx\n"); + +static int atk_acpi_print(char *buf, size_t sz, union acpi_object *obj) +{ + int ret = 0; + + switch (obj->type) { + case ACPI_TYPE_INTEGER: + ret = snprintf(buf, sz, "0x%08llx\n", obj->integer.value); + break; + case ACPI_TYPE_STRING: + ret = snprintf(buf, sz, "%s\n", obj->string.pointer); + break; + } + + return ret; +} + +static void atk_pack_print(char *buf, size_t sz, union acpi_object *pack) +{ + int ret; + int i; + + for (i = 0; i < pack->package.count; i++) { + union acpi_object *obj = &pack->package.elements[i]; + + ret = atk_acpi_print(buf, sz, obj); + if (ret >= sz) + break; + buf += ret; + sz -= ret; + } +} + +static int atk_debugfs_ggrp_open(struct inode *inode, struct file *file) +{ + struct atk_data *data = inode->i_private; + char *buf = NULL; + union acpi_object *ret; + u8 cls; + int i; + + if (!data->enumerate_handle) + return -ENODEV; + if (!data->debugfs.id) + return -EINVAL; + + cls = (data->debugfs.id & 0xff000000) >> 24; + ret = atk_ggrp(data, cls); + if (IS_ERR(ret)) + return PTR_ERR(ret); + + for (i = 0; i < ret->package.count; i++) { + union acpi_object *pack = &ret->package.elements[i]; + union acpi_object *id; + + if (pack->type != ACPI_TYPE_PACKAGE) + continue; + if (!pack->package.count) + continue; + id = &pack->package.elements[0]; + if (id->integer.value == data->debugfs.id) { + /* Print the package */ + buf = kzalloc(512, GFP_KERNEL); + if (!buf) { + ACPI_FREE(ret); + return -ENOMEM; + } + atk_pack_print(buf, 512, pack); + break; + } + } + ACPI_FREE(ret); + + if (!buf) + return -EINVAL; + + file->private_data = buf; + + return nonseekable_open(inode, file); +} + +static ssize_t atk_debugfs_ggrp_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + char *str = file->private_data; + size_t len = strlen(str); + + return simple_read_from_buffer(buf, count, pos, str, len); +} + +static int atk_debugfs_ggrp_release(struct inode *inode, struct file *file) +{ + kfree(file->private_data); + return 0; +} + +static const struct file_operations atk_debugfs_ggrp_fops = { + .read = atk_debugfs_ggrp_read, + .open = atk_debugfs_ggrp_open, + .release = atk_debugfs_ggrp_release, + .llseek = no_llseek, +}; + +static void atk_debugfs_init(struct atk_data *data) +{ + struct dentry *d; + struct dentry *f; + + data->debugfs.id = 0; + + d = debugfs_create_dir("asus_atk0110", NULL); + if (!d || IS_ERR(d)) + return; + + f = debugfs_create_x32("id", S_IRUSR | S_IWUSR, d, &data->debugfs.id); + if (!f || IS_ERR(f)) + goto cleanup; + + f = debugfs_create_file("gitm", S_IRUSR, d, data, + &atk_debugfs_gitm); + if (!f || IS_ERR(f)) + goto cleanup; + + f = debugfs_create_file("ggrp", S_IRUSR, d, data, + &atk_debugfs_ggrp_fops); + if (!f || IS_ERR(f)) + goto cleanup; + + data->debugfs.root = d; + + return; +cleanup: + debugfs_remove_recursive(d); +} + +static void atk_debugfs_cleanup(struct atk_data *data) +{ + debugfs_remove_recursive(data->debugfs.root); +} + +#else /* CONFIG_DEBUG_FS */ + +static void atk_debugfs_init(struct atk_data *data) +{ +} + +static void atk_debugfs_cleanup(struct atk_data *data) +{ +} +#endif + +static int atk_add_sensor(struct atk_data *data, union acpi_object *obj) +{ + struct device *dev = &data->acpi_dev->dev; + union acpi_object *flags; + union acpi_object *name; + union acpi_object *limit1; + union acpi_object *limit2; + union acpi_object *enable; + struct atk_sensor_data *sensor; + char const *base_name; + char const *limit1_name; + char const *limit2_name; + u64 type; + int err; + int *num; + int start; + + if (obj->type != ACPI_TYPE_PACKAGE) { + /* wft is this? */ + dev_warn(dev, "Unknown type for ACPI object: (%d)\n", + obj->type); + return -EINVAL; + } + + err = validate_hwmon_pack(data, obj); + if (err) + return err; + + /* Ok, we have a valid hwmon package */ + type = atk_get_pack_member(data, obj, HWMON_PACK_FLAGS)->integer.value + & ATK_TYPE_MASK; + + switch (type) { + case HWMON_TYPE_VOLT: + base_name = "in"; + limit1_name = "min"; + limit2_name = "max"; + num = &data->voltage_count; + start = 0; + break; + case HWMON_TYPE_TEMP: + base_name = "temp"; + limit1_name = "max"; + limit2_name = "crit"; + num = &data->temperature_count; + start = 1; + break; + case HWMON_TYPE_FAN: + base_name = "fan"; + limit1_name = "min"; + limit2_name = "max"; + num = &data->fan_count; + start = 1; + break; + default: + dev_warn(dev, "Unknown sensor type: %#llx\n", type); + return -EINVAL; + } + + enable = atk_get_pack_member(data, obj, HWMON_PACK_ENABLE); + if (!enable->integer.value) + /* sensor is disabled */ + return 0; + + flags = atk_get_pack_member(data, obj, HWMON_PACK_FLAGS); + name = atk_get_pack_member(data, obj, HWMON_PACK_NAME); + limit1 = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT1); + limit2 = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT2); + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->acpi_name = devm_kstrdup(dev, name->string.pointer, GFP_KERNEL); + if (!sensor->acpi_name) + return -ENOMEM; + + INIT_LIST_HEAD(&sensor->list); + sensor->type = type; + sensor->data = data; + sensor->id = flags->integer.value; + sensor->limit1 = limit1->integer.value; + if (data->old_interface) + sensor->limit2 = limit2->integer.value; + else + /* The upper limit is expressed as delta from lower limit */ + sensor->limit2 = sensor->limit1 + limit2->integer.value; + + snprintf(sensor->input_attr_name, ATTR_NAME_SIZE, + "%s%d_input", base_name, start + *num); + atk_init_attribute(&sensor->input_attr, + sensor->input_attr_name, + atk_input_show); + + snprintf(sensor->label_attr_name, ATTR_NAME_SIZE, + "%s%d_label", base_name, start + *num); + atk_init_attribute(&sensor->label_attr, + sensor->label_attr_name, + atk_label_show); + + snprintf(sensor->limit1_attr_name, ATTR_NAME_SIZE, + "%s%d_%s", base_name, start + *num, limit1_name); + atk_init_attribute(&sensor->limit1_attr, + sensor->limit1_attr_name, + atk_limit1_show); + + snprintf(sensor->limit2_attr_name, ATTR_NAME_SIZE, + "%s%d_%s", base_name, start + *num, limit2_name); + atk_init_attribute(&sensor->limit2_attr, + sensor->limit2_attr_name, + atk_limit2_show); + + list_add(&sensor->list, &data->sensor_list); + (*num)++; + + return 1; +} + +static int atk_enumerate_old_hwmon(struct atk_data *data) +{ + struct device *dev = &data->acpi_dev->dev; + struct acpi_buffer buf; + union acpi_object *pack; + acpi_status status; + int i, ret; + int count = 0; + + /* Voltages */ + buf.length = ACPI_ALLOCATE_BUFFER; + status = acpi_evaluate_object_typed(data->atk_handle, + METHOD_OLD_ENUM_VLT, NULL, &buf, ACPI_TYPE_PACKAGE); + if (status != AE_OK) { + dev_warn(dev, METHOD_OLD_ENUM_VLT ": ACPI exception: %s\n", + acpi_format_exception(status)); + + return -ENODEV; + } + + pack = buf.pointer; + for (i = 1; i < pack->package.count; i++) { + union acpi_object *obj = &pack->package.elements[i]; + + ret = atk_add_sensor(data, obj); + if (ret > 0) + count++; + } + ACPI_FREE(buf.pointer); + + /* Temperatures */ + buf.length = ACPI_ALLOCATE_BUFFER; + status = acpi_evaluate_object_typed(data->atk_handle, + METHOD_OLD_ENUM_TMP, NULL, &buf, ACPI_TYPE_PACKAGE); + if (status != AE_OK) { + dev_warn(dev, METHOD_OLD_ENUM_TMP ": ACPI exception: %s\n", + acpi_format_exception(status)); + + return -ENODEV; + } + + pack = buf.pointer; + for (i = 1; i < pack->package.count; i++) { + union acpi_object *obj = &pack->package.elements[i]; + + ret = atk_add_sensor(data, obj); + if (ret > 0) + count++; + } + ACPI_FREE(buf.pointer); + + /* Fans */ + buf.length = ACPI_ALLOCATE_BUFFER; + status = acpi_evaluate_object_typed(data->atk_handle, + METHOD_OLD_ENUM_FAN, NULL, &buf, ACPI_TYPE_PACKAGE); + if (status != AE_OK) { + dev_warn(dev, METHOD_OLD_ENUM_FAN ": ACPI exception: %s\n", + acpi_format_exception(status)); + + return -ENODEV; + } + + pack = buf.pointer; + for (i = 1; i < pack->package.count; i++) { + union acpi_object *obj = &pack->package.elements[i]; + + ret = atk_add_sensor(data, obj); + if (ret > 0) + count++; + } + ACPI_FREE(buf.pointer); + + return count; +} + +static int atk_ec_present(struct atk_data *data) +{ + struct device *dev = &data->acpi_dev->dev; + union acpi_object *pack; + union acpi_object *ec; + int ret; + int i; + + pack = atk_ggrp(data, ATK_MUX_MGMT); + if (IS_ERR(pack)) { + if (PTR_ERR(pack) == -ENOENT) { + /* The MGMT class does not exists - that's ok */ + dev_dbg(dev, "Class %#llx not found\n", ATK_MUX_MGMT); + return 0; + } + return PTR_ERR(pack); + } + + /* Search the EC */ + ec = NULL; + for (i = 0; i < pack->package.count; i++) { + union acpi_object *obj = &pack->package.elements[i]; + union acpi_object *id; + + if (obj->type != ACPI_TYPE_PACKAGE) + continue; + + id = &obj->package.elements[0]; + if (id->type != ACPI_TYPE_INTEGER) + continue; + + if (id->integer.value == ATK_EC_ID) { + ec = obj; + break; + } + } + + ret = (ec != NULL); + if (!ret) + /* The system has no EC */ + dev_dbg(dev, "EC not found\n"); + + ACPI_FREE(pack); + return ret; +} + +static int atk_ec_enabled(struct atk_data *data) +{ + struct device *dev = &data->acpi_dev->dev; + union acpi_object *obj; + struct atk_acpi_ret_buffer *buf; + int err; + + obj = atk_gitm(data, ATK_EC_ID); + if (IS_ERR(obj)) { + dev_err(dev, "Unable to query EC status\n"); + return PTR_ERR(obj); + } + buf = (struct atk_acpi_ret_buffer *)obj->buffer.pointer; + + if (buf->flags == 0) { + dev_err(dev, "Unable to query EC status\n"); + err = -EIO; + } else { + err = (buf->value != 0); + dev_dbg(dev, "EC is %sabled\n", + err ? "en" : "dis"); + } + + ACPI_FREE(obj); + return err; +} + +static int atk_ec_ctl(struct atk_data *data, int enable) +{ + struct device *dev = &data->acpi_dev->dev; + union acpi_object *obj; + struct atk_acpi_input_buf sitm; + struct atk_acpi_ret_buffer *ec_ret; + int err = 0; + + sitm.id = ATK_EC_ID; + sitm.param1 = enable; + sitm.param2 = 0; + + obj = atk_sitm(data, &sitm); + if (IS_ERR(obj)) { + dev_err(dev, "Failed to %sable the EC\n", + enable ? "en" : "dis"); + return PTR_ERR(obj); + } + ec_ret = (struct atk_acpi_ret_buffer *)obj->buffer.pointer; + if (ec_ret->flags == 0) { + dev_err(dev, "Failed to %sable the EC\n", + enable ? "en" : "dis"); + err = -EIO; + } else { + dev_info(dev, "EC %sabled\n", + enable ? "en" : "dis"); + } + + ACPI_FREE(obj); + return err; +} + +static int atk_enumerate_new_hwmon(struct atk_data *data) +{ + struct device *dev = &data->acpi_dev->dev; + union acpi_object *pack; + int err; + int i; + + err = atk_ec_present(data); + if (err < 0) + return err; + if (err) { + err = atk_ec_enabled(data); + if (err < 0) + return err; + /* If the EC was disabled we will disable it again on unload */ + data->disable_ec = err; + + err = atk_ec_ctl(data, 1); + if (err) { + data->disable_ec = false; + return err; + } + } + + dev_dbg(dev, "Enumerating hwmon sensors\n"); + + pack = atk_ggrp(data, ATK_MUX_HWMON); + if (IS_ERR(pack)) + return PTR_ERR(pack); + + for (i = 0; i < pack->package.count; i++) { + union acpi_object *obj = &pack->package.elements[i]; + + atk_add_sensor(data, obj); + } + + err = data->voltage_count + data->temperature_count + data->fan_count; + + ACPI_FREE(pack); + return err; +} + +static int atk_init_attribute_groups(struct atk_data *data) +{ + struct device *dev = &data->acpi_dev->dev; + struct atk_sensor_data *s; + struct attribute **attrs; + int i = 0; + int len = (data->voltage_count + data->temperature_count + + data->fan_count) * 4 + 1; + + attrs = devm_kcalloc(dev, len, sizeof(struct attribute *), GFP_KERNEL); + if (!attrs) + return -ENOMEM; + + list_for_each_entry(s, &data->sensor_list, list) { + attrs[i++] = &s->input_attr.attr; + attrs[i++] = &s->label_attr.attr; + attrs[i++] = &s->limit1_attr.attr; + attrs[i++] = &s->limit2_attr.attr; + } + + data->attr_group.attrs = attrs; + data->attr_groups[0] = &data->attr_group; + + return 0; +} + +static int atk_register_hwmon(struct atk_data *data) +{ + struct device *dev = &data->acpi_dev->dev; + + dev_dbg(dev, "registering hwmon device\n"); + data->hwmon_dev = hwmon_device_register_with_groups(dev, "atk0110", + data, + data->attr_groups); + if (IS_ERR(data->hwmon_dev)) + return PTR_ERR(data->hwmon_dev); + + return 0; +} + +static int atk_probe_if(struct atk_data *data) +{ + struct device *dev = &data->acpi_dev->dev; + acpi_handle ret; + acpi_status status; + int err = 0; + + /* RTMP: read temperature */ + status = acpi_get_handle(data->atk_handle, METHOD_OLD_READ_TMP, &ret); + if (ACPI_SUCCESS(status)) + data->rtmp_handle = ret; + else + dev_dbg(dev, "method " METHOD_OLD_READ_TMP " not found: %s\n", + acpi_format_exception(status)); + + /* RVLT: read voltage */ + status = acpi_get_handle(data->atk_handle, METHOD_OLD_READ_VLT, &ret); + if (ACPI_SUCCESS(status)) + data->rvlt_handle = ret; + else + dev_dbg(dev, "method " METHOD_OLD_READ_VLT " not found: %s\n", + acpi_format_exception(status)); + + /* RFAN: read fan status */ + status = acpi_get_handle(data->atk_handle, METHOD_OLD_READ_FAN, &ret); + if (ACPI_SUCCESS(status)) + data->rfan_handle = ret; + else + dev_dbg(dev, "method " METHOD_OLD_READ_FAN " not found: %s\n", + acpi_format_exception(status)); + + /* Enumeration */ + status = acpi_get_handle(data->atk_handle, METHOD_ENUMERATE, &ret); + if (ACPI_SUCCESS(status)) + data->enumerate_handle = ret; + else + dev_dbg(dev, "method " METHOD_ENUMERATE " not found: %s\n", + acpi_format_exception(status)); + + /* De-multiplexer (read) */ + status = acpi_get_handle(data->atk_handle, METHOD_READ, &ret); + if (ACPI_SUCCESS(status)) + data->read_handle = ret; + else + dev_dbg(dev, "method " METHOD_READ " not found: %s\n", + acpi_format_exception(status)); + + /* De-multiplexer (write) */ + status = acpi_get_handle(data->atk_handle, METHOD_WRITE, &ret); + if (ACPI_SUCCESS(status)) + data->write_handle = ret; + else + dev_dbg(dev, "method " METHOD_WRITE " not found: %s\n", + acpi_format_exception(status)); + + /* + * Check for hwmon methods: first check "old" style methods; note that + * both may be present: in this case we stick to the old interface; + * analysis of multiple DSDTs indicates that when both interfaces + * are present the new one (GGRP/GITM) is not functional. + */ + if (new_if) + dev_info(dev, "Overriding interface detection\n"); + if (data->rtmp_handle && + data->rvlt_handle && data->rfan_handle && !new_if) + data->old_interface = true; + else if (data->enumerate_handle && data->read_handle && + data->write_handle) + data->old_interface = false; + else + err = -ENODEV; + + return err; +} + +static int atk_add(struct acpi_device *device) +{ + acpi_status ret; + int err; + struct acpi_buffer buf; + union acpi_object *obj; + struct atk_data *data; + + dev_dbg(&device->dev, "adding...\n"); + + data = devm_kzalloc(&device->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->acpi_dev = device; + data->atk_handle = device->handle; + INIT_LIST_HEAD(&data->sensor_list); + data->disable_ec = false; + + buf.length = ACPI_ALLOCATE_BUFFER; + ret = acpi_evaluate_object_typed(data->atk_handle, BOARD_ID, NULL, + &buf, ACPI_TYPE_PACKAGE); + if (ret != AE_OK) { + dev_dbg(&device->dev, "atk: method MBIF not found\n"); + } else { + obj = buf.pointer; + if (obj->package.count >= 2) { + union acpi_object *id = &obj->package.elements[1]; + if (id->type == ACPI_TYPE_STRING) + dev_dbg(&device->dev, "board ID = %s\n", + id->string.pointer); + } + ACPI_FREE(buf.pointer); + } + + err = atk_probe_if(data); + if (err) { + dev_err(&device->dev, "No usable hwmon interface detected\n"); + goto out; + } + + if (data->old_interface) { + dev_dbg(&device->dev, "Using old hwmon interface\n"); + err = atk_enumerate_old_hwmon(data); + } else { + dev_dbg(&device->dev, "Using new hwmon interface\n"); + err = atk_enumerate_new_hwmon(data); + } + if (err < 0) + goto out; + if (err == 0) { + dev_info(&device->dev, + "No usable sensor detected, bailing out\n"); + err = -ENODEV; + goto out; + } + + err = atk_init_attribute_groups(data); + if (err) + goto out; + err = atk_register_hwmon(data); + if (err) + goto out; + + atk_debugfs_init(data); + + device->driver_data = data; + return 0; +out: + if (data->disable_ec) + atk_ec_ctl(data, 0); + return err; +} + +static int atk_remove(struct acpi_device *device) +{ + struct atk_data *data = device->driver_data; + dev_dbg(&device->dev, "removing...\n"); + + device->driver_data = NULL; + + atk_debugfs_cleanup(data); + + hwmon_device_unregister(data->hwmon_dev); + + if (data->disable_ec) { + if (atk_ec_ctl(data, 0)) + dev_err(&device->dev, "Failed to disable EC\n"); + } + + return 0; +} + +static int __init atk0110_init(void) +{ + int ret; + + /* Make sure it's safe to access the device through ACPI */ + if (!acpi_resources_are_enforced()) { + pr_err("Resources not safely usable due to acpi_enforce_resources kernel parameter\n"); + return -EBUSY; + } + + if (dmi_check_system(atk_force_new_if)) + new_if = true; + + ret = acpi_bus_register_driver(&atk_driver); + if (ret) + pr_info("acpi_bus_register_driver failed: %d\n", ret); + + return ret; +} + +static void __exit atk0110_exit(void) +{ + acpi_bus_unregister_driver(&atk_driver); +} + +module_init(atk0110_init); +module_exit(atk0110_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/atxp1.c b/drivers/hwmon/atxp1.c new file mode 100644 index 000000000..b7eadb54c --- /dev/null +++ b/drivers/hwmon/atxp1.c @@ -0,0 +1,304 @@ +/* + * atxp1.c - kernel module for setting CPU VID and general purpose + * I/Os using the Attansic ATXP1 chip. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * The ATXP1 can reside on I2C addresses 0x37 or 0x4e. The chip is + * not auto-detected by the driver and must be instantiated explicitly. + * See Documentation/i2c/instantiating-devices for more information. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("System voltages control via Attansic ATXP1"); +MODULE_VERSION("0.6.3"); +MODULE_AUTHOR("Sebastian Witt "); + +#define ATXP1_VID 0x00 +#define ATXP1_CVID 0x01 +#define ATXP1_GPIO1 0x06 +#define ATXP1_GPIO2 0x0a +#define ATXP1_VIDENA 0x20 +#define ATXP1_VIDMASK 0x1f +#define ATXP1_GPIO1MASK 0x0f + +struct atxp1_data { + struct i2c_client *client; + struct mutex update_lock; + unsigned long last_updated; + u8 valid; + struct { + u8 vid; /* VID output register */ + u8 cpu_vid; /* VID input from CPU */ + u8 gpio1; /* General purpose I/O register 1 */ + u8 gpio2; /* General purpose I/O register 2 */ + } reg; + u8 vrm; /* Detected CPU VRM */ +}; + +static struct atxp1_data *atxp1_update_device(struct device *dev) +{ + struct atxp1_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + + /* Update local register data */ + data->reg.vid = i2c_smbus_read_byte_data(client, ATXP1_VID); + data->reg.cpu_vid = i2c_smbus_read_byte_data(client, + ATXP1_CVID); + data->reg.gpio1 = i2c_smbus_read_byte_data(client, ATXP1_GPIO1); + data->reg.gpio2 = i2c_smbus_read_byte_data(client, ATXP1_GPIO2); + + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* sys file functions for cpu0_vid */ +static ssize_t cpu0_vid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int size; + struct atxp1_data *data; + + data = atxp1_update_device(dev); + + size = sprintf(buf, "%d\n", vid_from_reg(data->reg.vid & ATXP1_VIDMASK, + data->vrm)); + + return size; +} + +static ssize_t cpu0_vid_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct atxp1_data *data = atxp1_update_device(dev); + struct i2c_client *client = data->client; + int vid, cvid; + unsigned long vcore; + int err; + + err = kstrtoul(buf, 10, &vcore); + if (err) + return err; + + vcore /= 25; + vcore *= 25; + + /* Calculate VID */ + vid = vid_to_reg(vcore, data->vrm); + if (vid < 0) { + dev_err(dev, "VID calculation failed.\n"); + return vid; + } + + /* + * If output enabled, use control register value. + * Otherwise original CPU VID + */ + if (data->reg.vid & ATXP1_VIDENA) + cvid = data->reg.vid & ATXP1_VIDMASK; + else + cvid = data->reg.cpu_vid; + + /* Nothing changed, aborting */ + if (vid == cvid) + return count; + + dev_dbg(dev, "Setting VCore to %d mV (0x%02x)\n", (int)vcore, vid); + + /* Write every 25 mV step to increase stability */ + if (cvid > vid) { + for (; cvid >= vid; cvid--) + i2c_smbus_write_byte_data(client, + ATXP1_VID, cvid | ATXP1_VIDENA); + } else { + for (; cvid <= vid; cvid++) + i2c_smbus_write_byte_data(client, + ATXP1_VID, cvid | ATXP1_VIDENA); + } + + data->valid = 0; + + return count; +} + +/* + * CPU core reference voltage + * unit: millivolt + */ +static DEVICE_ATTR_RW(cpu0_vid); + +/* sys file functions for GPIO1 */ +static ssize_t gpio1_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int size; + struct atxp1_data *data; + + data = atxp1_update_device(dev); + + size = sprintf(buf, "0x%02x\n", data->reg.gpio1 & ATXP1_GPIO1MASK); + + return size; +} + +static ssize_t gpio1_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct atxp1_data *data = atxp1_update_device(dev); + struct i2c_client *client = data->client; + unsigned long value; + int err; + + err = kstrtoul(buf, 16, &value); + if (err) + return err; + + value &= ATXP1_GPIO1MASK; + + if (value != (data->reg.gpio1 & ATXP1_GPIO1MASK)) { + dev_info(dev, "Writing 0x%x to GPIO1.\n", (unsigned int)value); + + i2c_smbus_write_byte_data(client, ATXP1_GPIO1, value); + + data->valid = 0; + } + + return count; +} + +/* + * GPIO1 data register + * unit: Four bit as hex (e.g. 0x0f) + */ +static DEVICE_ATTR_RW(gpio1); + +/* sys file functions for GPIO2 */ +static ssize_t gpio2_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int size; + struct atxp1_data *data; + + data = atxp1_update_device(dev); + + size = sprintf(buf, "0x%02x\n", data->reg.gpio2); + + return size; +} + +static ssize_t gpio2_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct atxp1_data *data = atxp1_update_device(dev); + struct i2c_client *client = data->client; + unsigned long value; + int err; + + err = kstrtoul(buf, 16, &value); + if (err) + return err; + value &= 0xff; + + if (value != data->reg.gpio2) { + dev_info(dev, "Writing 0x%x to GPIO1.\n", (unsigned int)value); + + i2c_smbus_write_byte_data(client, ATXP1_GPIO2, value); + + data->valid = 0; + } + + return count; +} + +/* + * GPIO2 data register + * unit: Eight bit as hex (e.g. 0xff) + */ +static DEVICE_ATTR_RW(gpio2); + +static struct attribute *atxp1_attrs[] = { + &dev_attr_gpio1.attr, + &dev_attr_gpio2.attr, + &dev_attr_cpu0_vid.attr, + NULL +}; +ATTRIBUTE_GROUPS(atxp1); + +static int atxp1_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct atxp1_data *data; + struct device *hwmon_dev; + + data = devm_kzalloc(dev, sizeof(struct atxp1_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* Get VRM */ + data->vrm = vid_which_vrm(); + if (data->vrm != 90 && data->vrm != 91) { + dev_err(dev, "atxp1: Not supporting VRM %d.%d\n", + data->vrm / 10, data->vrm % 10); + return -ENODEV; + } + + data->client = client; + mutex_init(&data->update_lock); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + atxp1_groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + dev_info(dev, "Using VRM: %d.%d\n", data->vrm / 10, data->vrm % 10); + + return 0; +}; + +static const struct i2c_device_id atxp1_id[] = { + { "atxp1", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, atxp1_id); + +static struct i2c_driver atxp1_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "atxp1", + }, + .probe = atxp1_probe, + .id_table = atxp1_id, +}; + +module_i2c_driver(atxp1_driver); diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c new file mode 100644 index 000000000..10645c9bb --- /dev/null +++ b/drivers/hwmon/coretemp.c @@ -0,0 +1,778 @@ +/* + * coretemp.c - Linux kernel module for hardware monitoring + * + * Copyright (C) 2007 Rudolf Marek + * + * Inspired from many hwmon drivers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "coretemp" + +/* + * force_tjmax only matters when TjMax can't be read from the CPU itself. + * When set, it replaces the driver's suboptimal heuristic. + */ +static int force_tjmax; +module_param_named(tjmax, force_tjmax, int, 0444); +MODULE_PARM_DESC(tjmax, "TjMax value in degrees Celsius"); + +#define PKG_SYSFS_ATTR_NO 1 /* Sysfs attribute for package temp */ +#define BASE_SYSFS_ATTR_NO 2 /* Sysfs Base attr no for coretemp */ +#define NUM_REAL_CORES 128 /* Number of Real cores per cpu */ +#define CORETEMP_NAME_LENGTH 19 /* String Length of attrs */ +#define MAX_CORE_ATTRS 4 /* Maximum no of basic attrs */ +#define TOTAL_ATTRS (MAX_CORE_ATTRS + 1) +#define MAX_CORE_DATA (NUM_REAL_CORES + BASE_SYSFS_ATTR_NO) + +#define TO_CORE_ID(cpu) (cpu_data(cpu).cpu_core_id) +#define TO_ATTR_NO(cpu) (TO_CORE_ID(cpu) + BASE_SYSFS_ATTR_NO) + +#ifdef CONFIG_SMP +#define for_each_sibling(i, cpu) \ + for_each_cpu(i, topology_sibling_cpumask(cpu)) +#else +#define for_each_sibling(i, cpu) for (i = 0; false; ) +#endif + +/* + * Per-Core Temperature Data + * @last_updated: The time when the current temperature value was updated + * earlier (in jiffies). + * @cpu_core_id: The CPU Core from which temperature values should be read + * This value is passed as "id" field to rdmsr/wrmsr functions. + * @status_reg: One of IA32_THERM_STATUS or IA32_PACKAGE_THERM_STATUS, + * from where the temperature values should be read. + * @attr_size: Total number of pre-core attrs displayed in the sysfs. + * @is_pkg_data: If this is 1, the temp_data holds pkgtemp data. + * Otherwise, temp_data holds coretemp data. + * @valid: If this is 1, the current temperature is valid. + */ +struct temp_data { + int temp; + int ttarget; + int tjmax; + unsigned long last_updated; + unsigned int cpu; + u32 cpu_core_id; + u32 status_reg; + int attr_size; + bool is_pkg_data; + bool valid; + struct sensor_device_attribute sd_attrs[TOTAL_ATTRS]; + char attr_name[TOTAL_ATTRS][CORETEMP_NAME_LENGTH]; + struct attribute *attrs[TOTAL_ATTRS + 1]; + struct attribute_group attr_group; + struct mutex update_lock; +}; + +/* Platform Data per Physical CPU */ +struct platform_data { + struct device *hwmon_dev; + u16 pkg_id; + struct cpumask cpumask; + struct temp_data *core_data[MAX_CORE_DATA]; + struct device_attribute name_attr; +}; + +/* Keep track of how many package pointers we allocated in init() */ +static int max_packages __read_mostly; +/* Array of package pointers. Serialized by cpu hotplug lock */ +static struct platform_device **pkg_devices; + +static ssize_t show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct platform_data *pdata = dev_get_drvdata(dev); + struct temp_data *tdata = pdata->core_data[attr->index]; + + if (tdata->is_pkg_data) + return sprintf(buf, "Package id %u\n", pdata->pkg_id); + + return sprintf(buf, "Core %u\n", tdata->cpu_core_id); +} + +static ssize_t show_crit_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + u32 eax, edx; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct platform_data *pdata = dev_get_drvdata(dev); + struct temp_data *tdata = pdata->core_data[attr->index]; + + mutex_lock(&tdata->update_lock); + rdmsr_on_cpu(tdata->cpu, tdata->status_reg, &eax, &edx); + mutex_unlock(&tdata->update_lock); + + return sprintf(buf, "%d\n", (eax >> 5) & 1); +} + +static ssize_t show_tjmax(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct platform_data *pdata = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", pdata->core_data[attr->index]->tjmax); +} + +static ssize_t show_ttarget(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct platform_data *pdata = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", pdata->core_data[attr->index]->ttarget); +} + +static ssize_t show_temp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + u32 eax, edx; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct platform_data *pdata = dev_get_drvdata(dev); + struct temp_data *tdata = pdata->core_data[attr->index]; + + mutex_lock(&tdata->update_lock); + + /* Check whether the time interval has elapsed */ + if (!tdata->valid || time_after(jiffies, tdata->last_updated + HZ)) { + rdmsr_on_cpu(tdata->cpu, tdata->status_reg, &eax, &edx); + /* + * Ignore the valid bit. In all observed cases the register + * value is either low or zero if the valid bit is 0. + * Return it instead of reporting an error which doesn't + * really help at all. + */ + tdata->temp = tdata->tjmax - ((eax >> 16) & 0x7f) * 1000; + tdata->valid = 1; + tdata->last_updated = jiffies; + } + + mutex_unlock(&tdata->update_lock); + return sprintf(buf, "%d\n", tdata->temp); +} + +struct tjmax_pci { + unsigned int device; + int tjmax; +}; + +static const struct tjmax_pci tjmax_pci_table[] = { + { 0x0708, 110000 }, /* CE41x0 (Sodaville ) */ + { 0x0c72, 102000 }, /* Atom S1240 (Centerton) */ + { 0x0c73, 95000 }, /* Atom S1220 (Centerton) */ + { 0x0c75, 95000 }, /* Atom S1260 (Centerton) */ +}; + +struct tjmax { + char const *id; + int tjmax; +}; + +static const struct tjmax tjmax_table[] = { + { "CPU 230", 100000 }, /* Model 0x1c, stepping 2 */ + { "CPU 330", 125000 }, /* Model 0x1c, stepping 2 */ +}; + +struct tjmax_model { + u8 model; + u8 mask; + int tjmax; +}; + +#define ANY 0xff + +static const struct tjmax_model tjmax_model_table[] = { + { 0x1c, 10, 100000 }, /* D4xx, K4xx, N4xx, D5xx, K5xx, N5xx */ + { 0x1c, ANY, 90000 }, /* Z5xx, N2xx, possibly others + * Note: Also matches 230 and 330, + * which are covered by tjmax_table + */ + { 0x26, ANY, 90000 }, /* Atom Tunnel Creek (Exx), Lincroft (Z6xx) + * Note: TjMax for E6xxT is 110C, but CPU type + * is undetectable by software + */ + { 0x27, ANY, 90000 }, /* Atom Medfield (Z2460) */ + { 0x35, ANY, 90000 }, /* Atom Clover Trail/Cloverview (Z27x0) */ + { 0x36, ANY, 100000 }, /* Atom Cedar Trail/Cedarview (N2xxx, D2xxx) + * Also matches S12x0 (stepping 9), covered by + * PCI table + */ +}; + +static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) +{ + /* The 100C is default for both mobile and non mobile CPUs */ + + int tjmax = 100000; + int tjmax_ee = 85000; + int usemsr_ee = 1; + int err; + u32 eax, edx; + int i; + u16 devfn = PCI_DEVFN(0, 0); + struct pci_dev *host_bridge = pci_get_domain_bus_and_slot(0, 0, devfn); + + /* + * Explicit tjmax table entries override heuristics. + * First try PCI host bridge IDs, followed by model ID strings + * and model/stepping information. + */ + if (host_bridge && host_bridge->vendor == PCI_VENDOR_ID_INTEL) { + for (i = 0; i < ARRAY_SIZE(tjmax_pci_table); i++) { + if (host_bridge->device == tjmax_pci_table[i].device) + return tjmax_pci_table[i].tjmax; + } + } + + for (i = 0; i < ARRAY_SIZE(tjmax_table); i++) { + if (strstr(c->x86_model_id, tjmax_table[i].id)) + return tjmax_table[i].tjmax; + } + + for (i = 0; i < ARRAY_SIZE(tjmax_model_table); i++) { + const struct tjmax_model *tm = &tjmax_model_table[i]; + if (c->x86_model == tm->model && + (tm->mask == ANY || c->x86_stepping == tm->mask)) + return tm->tjmax; + } + + /* Early chips have no MSR for TjMax */ + + if (c->x86_model == 0xf && c->x86_stepping < 4) + usemsr_ee = 0; + + if (c->x86_model > 0xe && usemsr_ee) { + u8 platform_id; + + /* + * Now we can detect the mobile CPU using Intel provided table + * http://softwarecommunity.intel.com/Wiki/Mobility/720.htm + * For Core2 cores, check MSR 0x17, bit 28 1 = Mobile CPU + */ + err = rdmsr_safe_on_cpu(id, 0x17, &eax, &edx); + if (err) { + dev_warn(dev, + "Unable to access MSR 0x17, assuming desktop" + " CPU\n"); + usemsr_ee = 0; + } else if (c->x86_model < 0x17 && !(eax & 0x10000000)) { + /* + * Trust bit 28 up to Penryn, I could not find any + * documentation on that; if you happen to know + * someone at Intel please ask + */ + usemsr_ee = 0; + } else { + /* Platform ID bits 52:50 (EDX starts at bit 32) */ + platform_id = (edx >> 18) & 0x7; + + /* + * Mobile Penryn CPU seems to be platform ID 7 or 5 + * (guesswork) + */ + if (c->x86_model == 0x17 && + (platform_id == 5 || platform_id == 7)) { + /* + * If MSR EE bit is set, set it to 90 degrees C, + * otherwise 105 degrees C + */ + tjmax_ee = 90000; + tjmax = 105000; + } + } + } + + if (usemsr_ee) { + err = rdmsr_safe_on_cpu(id, 0xee, &eax, &edx); + if (err) { + dev_warn(dev, + "Unable to access MSR 0xEE, for Tjmax, left" + " at default\n"); + } else if (eax & 0x40000000) { + tjmax = tjmax_ee; + } + } else if (tjmax == 100000) { + /* + * If we don't use msr EE it means we are desktop CPU + * (with exeception of Atom) + */ + dev_warn(dev, "Using relative temperature scale!\n"); + } + + return tjmax; +} + +static bool cpu_has_tjmax(struct cpuinfo_x86 *c) +{ + u8 model = c->x86_model; + + return model > 0xe && + model != 0x1c && + model != 0x26 && + model != 0x27 && + model != 0x35 && + model != 0x36; +} + +static int get_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) +{ + int err; + u32 eax, edx; + u32 val; + + /* + * A new feature of current Intel(R) processors, the + * IA32_TEMPERATURE_TARGET contains the TjMax value + */ + err = rdmsr_safe_on_cpu(id, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); + if (err) { + if (cpu_has_tjmax(c)) + dev_warn(dev, "Unable to read TjMax from CPU %u\n", id); + } else { + val = (eax >> 16) & 0xff; + /* + * If the TjMax is not plausible, an assumption + * will be used + */ + if (val) { + dev_dbg(dev, "TjMax is %d degrees C\n", val); + return val * 1000; + } + } + + if (force_tjmax) { + dev_notice(dev, "TjMax forced to %d degrees C by user\n", + force_tjmax); + return force_tjmax * 1000; + } + + /* + * An assumption is made for early CPUs and unreadable MSR. + * NOTE: the calculated value may not be correct. + */ + return adjust_tjmax(c, id, dev); +} + +static int create_core_attrs(struct temp_data *tdata, struct device *dev, + int attr_no) +{ + int i; + static ssize_t (*const rd_ptr[TOTAL_ATTRS]) (struct device *dev, + struct device_attribute *devattr, char *buf) = { + show_label, show_crit_alarm, show_temp, show_tjmax, + show_ttarget }; + static const char *const suffixes[TOTAL_ATTRS] = { + "label", "crit_alarm", "input", "crit", "max" + }; + + for (i = 0; i < tdata->attr_size; i++) { + snprintf(tdata->attr_name[i], CORETEMP_NAME_LENGTH, + "temp%d_%s", attr_no, suffixes[i]); + sysfs_attr_init(&tdata->sd_attrs[i].dev_attr.attr); + tdata->sd_attrs[i].dev_attr.attr.name = tdata->attr_name[i]; + tdata->sd_attrs[i].dev_attr.attr.mode = S_IRUGO; + tdata->sd_attrs[i].dev_attr.show = rd_ptr[i]; + tdata->sd_attrs[i].index = attr_no; + tdata->attrs[i] = &tdata->sd_attrs[i].dev_attr.attr; + } + tdata->attr_group.attrs = tdata->attrs; + return sysfs_create_group(&dev->kobj, &tdata->attr_group); +} + + +static int chk_ucode_version(unsigned int cpu) +{ + struct cpuinfo_x86 *c = &cpu_data(cpu); + + /* + * Check if we have problem with errata AE18 of Core processors: + * Readings might stop update when processor visited too deep sleep, + * fixed for stepping D0 (6EC). + */ + if (c->x86_model == 0xe && c->x86_stepping < 0xc && c->microcode < 0x39) { + pr_err("Errata AE18 not fixed, update BIOS or microcode of the CPU!\n"); + return -ENODEV; + } + return 0; +} + +static struct platform_device *coretemp_get_pdev(unsigned int cpu) +{ + int pkgid = topology_logical_package_id(cpu); + + if (pkgid >= 0 && pkgid < max_packages) + return pkg_devices[pkgid]; + return NULL; +} + +static struct temp_data *init_temp_data(unsigned int cpu, int pkg_flag) +{ + struct temp_data *tdata; + + tdata = kzalloc(sizeof(struct temp_data), GFP_KERNEL); + if (!tdata) + return NULL; + + tdata->status_reg = pkg_flag ? MSR_IA32_PACKAGE_THERM_STATUS : + MSR_IA32_THERM_STATUS; + tdata->is_pkg_data = pkg_flag; + tdata->cpu = cpu; + tdata->cpu_core_id = TO_CORE_ID(cpu); + tdata->attr_size = MAX_CORE_ATTRS; + mutex_init(&tdata->update_lock); + return tdata; +} + +static int create_core_data(struct platform_device *pdev, unsigned int cpu, + int pkg_flag) +{ + struct temp_data *tdata; + struct platform_data *pdata = platform_get_drvdata(pdev); + struct cpuinfo_x86 *c = &cpu_data(cpu); + u32 eax, edx; + int err, attr_no; + + /* + * Find attr number for sysfs: + * We map the attr number to core id of the CPU + * The attr number is always core id + 2 + * The Pkgtemp will always show up as temp1_*, if available + */ + attr_no = pkg_flag ? PKG_SYSFS_ATTR_NO : TO_ATTR_NO(cpu); + + if (attr_no > MAX_CORE_DATA - 1) + return -ERANGE; + + tdata = init_temp_data(cpu, pkg_flag); + if (!tdata) + return -ENOMEM; + + /* Test if we can access the status register */ + err = rdmsr_safe_on_cpu(cpu, tdata->status_reg, &eax, &edx); + if (err) + goto exit_free; + + /* We can access status register. Get Critical Temperature */ + tdata->tjmax = get_tjmax(c, cpu, &pdev->dev); + + /* + * Read the still undocumented bits 8:15 of IA32_TEMPERATURE_TARGET. + * The target temperature is available on older CPUs but not in this + * register. Atoms don't have the register at all. + */ + if (c->x86_model > 0xe && c->x86_model != 0x1c) { + err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, + &eax, &edx); + if (!err) { + tdata->ttarget + = tdata->tjmax - ((eax >> 8) & 0xff) * 1000; + tdata->attr_size++; + } + } + + pdata->core_data[attr_no] = tdata; + + /* Create sysfs interfaces */ + err = create_core_attrs(tdata, pdata->hwmon_dev, attr_no); + if (err) + goto exit_free; + + return 0; +exit_free: + pdata->core_data[attr_no] = NULL; + kfree(tdata); + return err; +} + +static void +coretemp_add_core(struct platform_device *pdev, unsigned int cpu, int pkg_flag) +{ + if (create_core_data(pdev, cpu, pkg_flag)) + dev_err(&pdev->dev, "Adding Core %u failed\n", cpu); +} + +static void coretemp_remove_core(struct platform_data *pdata, int indx) +{ + struct temp_data *tdata = pdata->core_data[indx]; + + /* Remove the sysfs attributes */ + sysfs_remove_group(&pdata->hwmon_dev->kobj, &tdata->attr_group); + + kfree(pdata->core_data[indx]); + pdata->core_data[indx] = NULL; +} + +static int coretemp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct platform_data *pdata; + + /* Initialize the per-package data structures */ + pdata = devm_kzalloc(dev, sizeof(struct platform_data), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->pkg_id = pdev->id; + platform_set_drvdata(pdev, pdata); + + pdata->hwmon_dev = devm_hwmon_device_register_with_groups(dev, DRVNAME, + pdata, NULL); + return PTR_ERR_OR_ZERO(pdata->hwmon_dev); +} + +static int coretemp_remove(struct platform_device *pdev) +{ + struct platform_data *pdata = platform_get_drvdata(pdev); + int i; + + for (i = MAX_CORE_DATA - 1; i >= 0; --i) + if (pdata->core_data[i]) + coretemp_remove_core(pdata, i); + + return 0; +} + +static struct platform_driver coretemp_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = coretemp_probe, + .remove = coretemp_remove, +}; + +static struct platform_device *coretemp_device_add(unsigned int cpu) +{ + int err, pkgid = topology_logical_package_id(cpu); + struct platform_device *pdev; + + if (pkgid < 0) + return ERR_PTR(-ENOMEM); + + pdev = platform_device_alloc(DRVNAME, pkgid); + if (!pdev) + return ERR_PTR(-ENOMEM); + + err = platform_device_add(pdev); + if (err) { + platform_device_put(pdev); + return ERR_PTR(err); + } + + pkg_devices[pkgid] = pdev; + return pdev; +} + +static int coretemp_cpu_online(unsigned int cpu) +{ + struct platform_device *pdev = coretemp_get_pdev(cpu); + struct cpuinfo_x86 *c = &cpu_data(cpu); + struct platform_data *pdata; + + /* + * Don't execute this on resume as the offline callback did + * not get executed on suspend. + */ + if (cpuhp_tasks_frozen) + return 0; + + /* + * CPUID.06H.EAX[0] indicates whether the CPU has thermal + * sensors. We check this bit only, all the early CPUs + * without thermal sensors will be filtered out. + */ + if (!cpu_has(c, X86_FEATURE_DTHERM)) + return -ENODEV; + + if (!pdev) { + /* Check the microcode version of the CPU */ + if (chk_ucode_version(cpu)) + return -EINVAL; + + /* + * Alright, we have DTS support. + * We are bringing the _first_ core in this pkg + * online. So, initialize per-pkg data structures and + * then bring this core online. + */ + pdev = coretemp_device_add(cpu); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + /* + * Check whether pkgtemp support is available. + * If so, add interfaces for pkgtemp. + */ + if (cpu_has(c, X86_FEATURE_PTS)) + coretemp_add_core(pdev, cpu, 1); + } + + pdata = platform_get_drvdata(pdev); + /* + * Check whether a thread sibling is already online. If not add the + * interface for this CPU core. + */ + if (!cpumask_intersects(&pdata->cpumask, topology_sibling_cpumask(cpu))) + coretemp_add_core(pdev, cpu, 0); + + cpumask_set_cpu(cpu, &pdata->cpumask); + return 0; +} + +static int coretemp_cpu_offline(unsigned int cpu) +{ + struct platform_device *pdev = coretemp_get_pdev(cpu); + struct platform_data *pd; + struct temp_data *tdata; + int indx, target; + + /* + * Don't execute this on suspend as the device remove locks + * up the machine. + */ + if (cpuhp_tasks_frozen) + return 0; + + /* If the physical CPU device does not exist, just return */ + if (!pdev) + return 0; + + /* The core id is too big, just return */ + indx = TO_ATTR_NO(cpu); + if (indx > MAX_CORE_DATA - 1) + return 0; + + pd = platform_get_drvdata(pdev); + tdata = pd->core_data[indx]; + + cpumask_clear_cpu(cpu, &pd->cpumask); + + /* + * If this is the last thread sibling, remove the CPU core + * interface, If there is still a sibling online, transfer the + * target cpu of that core interface to it. + */ + target = cpumask_any_and(&pd->cpumask, topology_sibling_cpumask(cpu)); + if (target >= nr_cpu_ids) { + coretemp_remove_core(pd, indx); + } else if (tdata && tdata->cpu == cpu) { + mutex_lock(&tdata->update_lock); + tdata->cpu = target; + mutex_unlock(&tdata->update_lock); + } + + /* + * If all cores in this pkg are offline, remove the device. This + * will invoke the platform driver remove function, which cleans up + * the rest. + */ + if (cpumask_empty(&pd->cpumask)) { + pkg_devices[topology_logical_package_id(cpu)] = NULL; + platform_device_unregister(pdev); + return 0; + } + + /* + * Check whether this core is the target for the package + * interface. We need to assign it to some other cpu. + */ + tdata = pd->core_data[PKG_SYSFS_ATTR_NO]; + if (tdata && tdata->cpu == cpu) { + target = cpumask_first(&pd->cpumask); + mutex_lock(&tdata->update_lock); + tdata->cpu = target; + mutex_unlock(&tdata->update_lock); + } + return 0; +} +static const struct x86_cpu_id __initconst coretemp_ids[] = { + { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_DTHERM }, + {} +}; +MODULE_DEVICE_TABLE(x86cpu, coretemp_ids); + +static enum cpuhp_state coretemp_hp_online; + +static int __init coretemp_init(void) +{ + int err; + + /* + * CPUID.06H.EAX[0] indicates whether the CPU has thermal + * sensors. We check this bit only, all the early CPUs + * without thermal sensors will be filtered out. + */ + if (!x86_match_cpu(coretemp_ids)) + return -ENODEV; + + max_packages = topology_max_packages(); + pkg_devices = kcalloc(max_packages, sizeof(struct platform_device *), + GFP_KERNEL); + if (!pkg_devices) + return -ENOMEM; + + err = platform_driver_register(&coretemp_driver); + if (err) + return err; + + err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "hwmon/coretemp:online", + coretemp_cpu_online, coretemp_cpu_offline); + if (err < 0) + goto outdrv; + coretemp_hp_online = err; + return 0; + +outdrv: + platform_driver_unregister(&coretemp_driver); + kfree(pkg_devices); + return err; +} +module_init(coretemp_init) + +static void __exit coretemp_exit(void) +{ + cpuhp_remove_state(coretemp_hp_online); + platform_driver_unregister(&coretemp_driver); + kfree(pkg_devices); +} +module_exit(coretemp_exit) + +MODULE_AUTHOR("Rudolf Marek "); +MODULE_DESCRIPTION("Intel Core temperature monitor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/da9052-hwmon.c b/drivers/hwmon/da9052-hwmon.c new file mode 100644 index 000000000..9e44d2385 --- /dev/null +++ b/drivers/hwmon/da9052-hwmon.c @@ -0,0 +1,539 @@ +/* + * HWMON Driver for Dialog DA9052 + * + * Copyright(c) 2012 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +struct da9052_hwmon { + struct da9052 *da9052; + struct mutex hwmon_lock; + bool tsi_as_adc; + int tsiref_mv; + struct regulator *tsiref; + struct completion tsidone; +}; + +static const char * const input_names[] = { + [DA9052_ADC_VDDOUT] = "VDDOUT", + [DA9052_ADC_ICH] = "CHARGING CURRENT", + [DA9052_ADC_TBAT] = "BATTERY TEMP", + [DA9052_ADC_VBAT] = "BATTERY VOLTAGE", + [DA9052_ADC_IN4] = "ADC IN4", + [DA9052_ADC_IN5] = "ADC IN5", + [DA9052_ADC_IN6] = "ADC IN6", + [DA9052_ADC_TSI_XP] = "ADC TS X+", + [DA9052_ADC_TSI_YP] = "ADC TS Y+", + [DA9052_ADC_TSI_XN] = "ADC TS X-", + [DA9052_ADC_TSI_YN] = "ADC TS Y-", + [DA9052_ADC_TJUNC] = "BATTERY JUNCTION TEMP", + [DA9052_ADC_VBBAT] = "BACK-UP BATTERY VOLTAGE", +}; + +/* Conversion function for VDDOUT and VBAT */ +static inline int volt_reg_to_mv(int value) +{ + return DIV_ROUND_CLOSEST(value * 2000, 1023) + 2500; +} + +/* Conversion function for ADC channels 4, 5 and 6 */ +static inline int input_reg_to_mv(int value) +{ + return DIV_ROUND_CLOSEST(value * 2500, 1023); +} + +/* Conversion function for VBBAT */ +static inline int vbbat_reg_to_mv(int value) +{ + return DIV_ROUND_CLOSEST(value * 5000, 1023); +} + +static inline int input_tsireg_to_mv(struct da9052_hwmon *hwmon, int value) +{ + return DIV_ROUND_CLOSEST(value * hwmon->tsiref_mv, 1023); +} + +static inline int da9052_enable_vddout_channel(struct da9052 *da9052) +{ + return da9052_reg_update(da9052, DA9052_ADC_CONT_REG, + DA9052_ADCCONT_AUTOVDDEN, + DA9052_ADCCONT_AUTOVDDEN); +} + +static inline int da9052_disable_vddout_channel(struct da9052 *da9052) +{ + return da9052_reg_update(da9052, DA9052_ADC_CONT_REG, + DA9052_ADCCONT_AUTOVDDEN, 0); +} + +static ssize_t da9052_read_vddout(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct da9052_hwmon *hwmon = dev_get_drvdata(dev); + int ret, vdd; + + mutex_lock(&hwmon->hwmon_lock); + + ret = da9052_enable_vddout_channel(hwmon->da9052); + if (ret < 0) + goto hwmon_err; + + vdd = da9052_reg_read(hwmon->da9052, DA9052_VDD_RES_REG); + if (vdd < 0) { + ret = vdd; + goto hwmon_err_release; + } + + ret = da9052_disable_vddout_channel(hwmon->da9052); + if (ret < 0) + goto hwmon_err; + + mutex_unlock(&hwmon->hwmon_lock); + return sprintf(buf, "%d\n", volt_reg_to_mv(vdd)); + +hwmon_err_release: + da9052_disable_vddout_channel(hwmon->da9052); +hwmon_err: + mutex_unlock(&hwmon->hwmon_lock); + return ret; +} + +static ssize_t da9052_read_ich(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct da9052_hwmon *hwmon = dev_get_drvdata(dev); + int ret; + + ret = da9052_reg_read(hwmon->da9052, DA9052_ICHG_AV_REG); + if (ret < 0) + return ret; + + /* Equivalent to 3.9mA/bit in register ICHG_AV */ + return sprintf(buf, "%d\n", DIV_ROUND_CLOSEST(ret * 39, 10)); +} + +static ssize_t da9052_read_tbat(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct da9052_hwmon *hwmon = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", da9052_adc_read_temp(hwmon->da9052)); +} + +static ssize_t da9052_read_vbat(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct da9052_hwmon *hwmon = dev_get_drvdata(dev); + int ret; + + ret = da9052_adc_manual_read(hwmon->da9052, DA9052_ADC_VBAT); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", volt_reg_to_mv(ret)); +} + +static ssize_t da9052_read_misc_channel(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct da9052_hwmon *hwmon = dev_get_drvdata(dev); + int channel = to_sensor_dev_attr(devattr)->index; + int ret; + + ret = da9052_adc_manual_read(hwmon->da9052, channel); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", input_reg_to_mv(ret)); +} + +static int da9052_request_tsi_read(struct da9052_hwmon *hwmon, int channel) +{ + u8 val = DA9052_TSICONTB_TSIMAN; + + switch (channel) { + case DA9052_ADC_TSI_XP: + val |= DA9052_TSICONTB_TSIMUX_XP; + break; + case DA9052_ADC_TSI_YP: + val |= DA9052_TSICONTB_TSIMUX_YP; + break; + case DA9052_ADC_TSI_XN: + val |= DA9052_TSICONTB_TSIMUX_XN; + break; + case DA9052_ADC_TSI_YN: + val |= DA9052_TSICONTB_TSIMUX_YN; + break; + } + + return da9052_reg_write(hwmon->da9052, DA9052_TSI_CONT_B_REG, val); +} + +static int da9052_get_tsi_result(struct da9052_hwmon *hwmon, int channel) +{ + u8 regs[3]; + int msb, lsb, err; + + /* block read to avoid separation of MSB and LSB */ + err = da9052_group_read(hwmon->da9052, DA9052_TSI_X_MSB_REG, + ARRAY_SIZE(regs), regs); + if (err) + return err; + + switch (channel) { + case DA9052_ADC_TSI_XP: + case DA9052_ADC_TSI_XN: + msb = regs[0] << DA9052_TSILSB_TSIXL_BITS; + lsb = regs[2] & DA9052_TSILSB_TSIXL; + lsb >>= DA9052_TSILSB_TSIXL_SHIFT; + break; + case DA9052_ADC_TSI_YP: + case DA9052_ADC_TSI_YN: + msb = regs[1] << DA9052_TSILSB_TSIYL_BITS; + lsb = regs[2] & DA9052_TSILSB_TSIYL; + lsb >>= DA9052_TSILSB_TSIYL_SHIFT; + break; + default: + return -EINVAL; + } + + return msb | lsb; +} + + +static ssize_t __da9052_read_tsi(struct device *dev, int channel) +{ + struct da9052_hwmon *hwmon = dev_get_drvdata(dev); + int ret; + + reinit_completion(&hwmon->tsidone); + + ret = da9052_request_tsi_read(hwmon, channel); + if (ret < 0) + return ret; + + /* Wait for an conversion done interrupt */ + if (!wait_for_completion_timeout(&hwmon->tsidone, + msecs_to_jiffies(500))) + return -ETIMEDOUT; + + return da9052_get_tsi_result(hwmon, channel); +} + +static ssize_t da9052_read_tsi(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct da9052_hwmon *hwmon = dev_get_drvdata(dev); + int channel = to_sensor_dev_attr(devattr)->index; + int ret; + + mutex_lock(&hwmon->da9052->auxadc_lock); + ret = __da9052_read_tsi(dev, channel); + mutex_unlock(&hwmon->da9052->auxadc_lock); + + if (ret < 0) + return ret; + else + return sprintf(buf, "%d\n", input_tsireg_to_mv(hwmon, ret)); +} + +static ssize_t da9052_read_tjunc(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct da9052_hwmon *hwmon = dev_get_drvdata(dev); + int tjunc; + int toffset; + + tjunc = da9052_reg_read(hwmon->da9052, DA9052_TJUNC_RES_REG); + if (tjunc < 0) + return tjunc; + + toffset = da9052_reg_read(hwmon->da9052, DA9052_T_OFFSET_REG); + if (toffset < 0) + return toffset; + + /* + * Degrees celsius = 1.708 * (TJUNC_RES - T_OFFSET) - 108.8 + * T_OFFSET is a trim value used to improve accuracy of the result + */ + return sprintf(buf, "%d\n", 1708 * (tjunc - toffset) - 108800); +} + +static ssize_t da9052_read_vbbat(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct da9052_hwmon *hwmon = dev_get_drvdata(dev); + int ret; + + ret = da9052_adc_manual_read(hwmon->da9052, DA9052_ADC_VBBAT); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", vbbat_reg_to_mv(ret)); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + return sprintf(buf, "%s\n", + input_names[to_sensor_dev_attr(devattr)->index]); +} + +static umode_t da9052_channel_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct da9052_hwmon *hwmon = dev_get_drvdata(dev); + struct device_attribute *dattr = container_of(attr, + struct device_attribute, attr); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(dattr); + + if (!hwmon->tsi_as_adc) { + switch (sattr->index) { + case DA9052_ADC_TSI_XP: + case DA9052_ADC_TSI_YP: + case DA9052_ADC_TSI_XN: + case DA9052_ADC_TSI_YN: + return 0; + } + } + + return attr->mode; +} + +static SENSOR_DEVICE_ATTR(in0_input, 0444, da9052_read_vddout, NULL, + DA9052_ADC_VDDOUT); +static SENSOR_DEVICE_ATTR(in0_label, 0444, show_label, NULL, + DA9052_ADC_VDDOUT); +static SENSOR_DEVICE_ATTR(in3_input, 0444, da9052_read_vbat, NULL, + DA9052_ADC_VBAT); +static SENSOR_DEVICE_ATTR(in3_label, 0444, show_label, NULL, + DA9052_ADC_VBAT); +static SENSOR_DEVICE_ATTR(in4_input, 0444, da9052_read_misc_channel, NULL, + DA9052_ADC_IN4); +static SENSOR_DEVICE_ATTR(in4_label, 0444, show_label, NULL, + DA9052_ADC_IN4); +static SENSOR_DEVICE_ATTR(in5_input, 0444, da9052_read_misc_channel, NULL, + DA9052_ADC_IN5); +static SENSOR_DEVICE_ATTR(in5_label, 0444, show_label, NULL, + DA9052_ADC_IN5); +static SENSOR_DEVICE_ATTR(in6_input, 0444, da9052_read_misc_channel, NULL, + DA9052_ADC_IN6); +static SENSOR_DEVICE_ATTR(in6_label, 0444, show_label, NULL, + DA9052_ADC_IN6); +static SENSOR_DEVICE_ATTR(in9_input, 0444, da9052_read_vbbat, NULL, + DA9052_ADC_VBBAT); +static SENSOR_DEVICE_ATTR(in9_label, 0444, show_label, NULL, + DA9052_ADC_VBBAT); + +static SENSOR_DEVICE_ATTR(in70_input, 0444, da9052_read_tsi, NULL, + DA9052_ADC_TSI_XP); +static SENSOR_DEVICE_ATTR(in70_label, 0444, show_label, NULL, + DA9052_ADC_TSI_XP); +static SENSOR_DEVICE_ATTR(in71_input, 0444, da9052_read_tsi, NULL, + DA9052_ADC_TSI_XN); +static SENSOR_DEVICE_ATTR(in71_label, 0444, show_label, NULL, + DA9052_ADC_TSI_XN); +static SENSOR_DEVICE_ATTR(in72_input, 0444, da9052_read_tsi, NULL, + DA9052_ADC_TSI_YP); +static SENSOR_DEVICE_ATTR(in72_label, 0444, show_label, NULL, + DA9052_ADC_TSI_YP); +static SENSOR_DEVICE_ATTR(in73_input, 0444, da9052_read_tsi, NULL, + DA9052_ADC_TSI_YN); +static SENSOR_DEVICE_ATTR(in73_label, 0444, show_label, NULL, + DA9052_ADC_TSI_YN); + +static SENSOR_DEVICE_ATTR(curr1_input, 0444, da9052_read_ich, NULL, + DA9052_ADC_ICH); +static SENSOR_DEVICE_ATTR(curr1_label, 0444, show_label, NULL, + DA9052_ADC_ICH); + +static SENSOR_DEVICE_ATTR(temp2_input, 0444, da9052_read_tbat, NULL, + DA9052_ADC_TBAT); +static SENSOR_DEVICE_ATTR(temp2_label, 0444, show_label, NULL, + DA9052_ADC_TBAT); +static SENSOR_DEVICE_ATTR(temp8_input, 0444, da9052_read_tjunc, NULL, + DA9052_ADC_TJUNC); +static SENSOR_DEVICE_ATTR(temp8_label, 0444, show_label, NULL, + DA9052_ADC_TJUNC); + +static struct attribute *da9052_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_label.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_label.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_label.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_label.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_label.dev_attr.attr, + &sensor_dev_attr_in70_input.dev_attr.attr, + &sensor_dev_attr_in70_label.dev_attr.attr, + &sensor_dev_attr_in71_input.dev_attr.attr, + &sensor_dev_attr_in71_label.dev_attr.attr, + &sensor_dev_attr_in72_input.dev_attr.attr, + &sensor_dev_attr_in72_label.dev_attr.attr, + &sensor_dev_attr_in73_input.dev_attr.attr, + &sensor_dev_attr_in73_label.dev_attr.attr, + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in9_label.dev_attr.attr, + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_curr1_label.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_label.dev_attr.attr, + &sensor_dev_attr_temp8_input.dev_attr.attr, + &sensor_dev_attr_temp8_label.dev_attr.attr, + NULL +}; + +static const struct attribute_group da9052_group = { + .attrs = da9052_attrs, + .is_visible = da9052_channel_is_visible, +}; +__ATTRIBUTE_GROUPS(da9052); + +static irqreturn_t da9052_tsi_datardy_irq(int irq, void *data) +{ + struct da9052_hwmon *hwmon = data; + + complete(&hwmon->tsidone); + return IRQ_HANDLED; +} + +static int da9052_hwmon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct da9052_hwmon *hwmon; + struct device *hwmon_dev; + int err; + + hwmon = devm_kzalloc(dev, sizeof(struct da9052_hwmon), GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + + platform_set_drvdata(pdev, hwmon); + + mutex_init(&hwmon->hwmon_lock); + hwmon->da9052 = dev_get_drvdata(pdev->dev.parent); + + init_completion(&hwmon->tsidone); + + hwmon->tsi_as_adc = + device_property_read_bool(pdev->dev.parent, "dlg,tsi-as-adc"); + + if (hwmon->tsi_as_adc) { + hwmon->tsiref = devm_regulator_get(pdev->dev.parent, "tsiref"); + if (IS_ERR(hwmon->tsiref)) { + err = PTR_ERR(hwmon->tsiref); + dev_err(&pdev->dev, "failed to get tsiref: %d", err); + return err; + } + + err = regulator_enable(hwmon->tsiref); + if (err) + return err; + + hwmon->tsiref_mv = regulator_get_voltage(hwmon->tsiref); + if (hwmon->tsiref_mv < 0) { + err = hwmon->tsiref_mv; + goto exit_regulator; + } + + /* convert from microvolt (DT) to millivolt (hwmon) */ + hwmon->tsiref_mv /= 1000; + + /* TSIREF limits from datasheet */ + if (hwmon->tsiref_mv < 1800 || hwmon->tsiref_mv > 2600) { + dev_err(hwmon->da9052->dev, "invalid TSIREF voltage: %d", + hwmon->tsiref_mv); + err = -ENXIO; + goto exit_regulator; + } + + /* disable touchscreen features */ + da9052_reg_write(hwmon->da9052, DA9052_TSI_CONT_A_REG, 0x00); + + /* Sample every 1ms */ + da9052_reg_update(hwmon->da9052, DA9052_ADC_CONT_REG, + DA9052_ADCCONT_ADCMODE, + DA9052_ADCCONT_ADCMODE); + + err = da9052_request_irq(hwmon->da9052, DA9052_IRQ_TSIREADY, + "tsiready-irq", da9052_tsi_datardy_irq, + hwmon); + if (err) { + dev_err(&pdev->dev, "Failed to register TSIRDY IRQ: %d", + err); + goto exit_regulator; + } + } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "da9052", + hwmon, + da9052_groups); + err = PTR_ERR_OR_ZERO(hwmon_dev); + if (err) + goto exit_irq; + + return 0; + +exit_irq: + if (hwmon->tsi_as_adc) + da9052_free_irq(hwmon->da9052, DA9052_IRQ_TSIREADY, hwmon); +exit_regulator: + if (hwmon->tsiref) + regulator_disable(hwmon->tsiref); + + return err; +} + +static int da9052_hwmon_remove(struct platform_device *pdev) +{ + struct da9052_hwmon *hwmon = platform_get_drvdata(pdev); + + if (hwmon->tsi_as_adc) { + da9052_free_irq(hwmon->da9052, DA9052_IRQ_TSIREADY, hwmon); + regulator_disable(hwmon->tsiref); + } + + return 0; +} + +static struct platform_driver da9052_hwmon_driver = { + .probe = da9052_hwmon_probe, + .remove = da9052_hwmon_remove, + .driver = { + .name = "da9052-hwmon", + }, +}; + +module_platform_driver(da9052_hwmon_driver); + +MODULE_AUTHOR("David Dajun Chen "); +MODULE_DESCRIPTION("DA9052 HWMON driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da9052-hwmon"); diff --git a/drivers/hwmon/da9055-hwmon.c b/drivers/hwmon/da9055-hwmon.c new file mode 100644 index 000000000..f6e159cab --- /dev/null +++ b/drivers/hwmon/da9055-hwmon.c @@ -0,0 +1,297 @@ +/* + * HWMON Driver for Dialog DA9055 + * + * Copyright(c) 2012 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DA9055_ADCIN_DIV 102 +#define DA9055_VSYS_DIV 85 + +#define DA9055_ADC_VSYS 0 +#define DA9055_ADC_ADCIN1 1 +#define DA9055_ADC_ADCIN2 2 +#define DA9055_ADC_ADCIN3 3 +#define DA9055_ADC_TJUNC 4 + +struct da9055_hwmon { + struct da9055 *da9055; + struct mutex hwmon_lock; + struct mutex irq_lock; + struct completion done; +}; + +static const char * const input_names[] = { + [DA9055_ADC_VSYS] = "VSYS", + [DA9055_ADC_ADCIN1] = "ADC IN1", + [DA9055_ADC_ADCIN2] = "ADC IN2", + [DA9055_ADC_ADCIN3] = "ADC IN3", + [DA9055_ADC_TJUNC] = "CHIP TEMP", +}; + +static const u8 chan_mux[DA9055_ADC_TJUNC + 1] = { + [DA9055_ADC_VSYS] = DA9055_ADC_MUX_VSYS, + [DA9055_ADC_ADCIN1] = DA9055_ADC_MUX_ADCIN1, + [DA9055_ADC_ADCIN2] = DA9055_ADC_MUX_ADCIN2, + [DA9055_ADC_ADCIN3] = DA9055_ADC_MUX_ADCIN3, + [DA9055_ADC_TJUNC] = DA9055_ADC_MUX_T_SENSE, +}; + +static int da9055_adc_manual_read(struct da9055_hwmon *hwmon, + unsigned char channel) +{ + int ret; + unsigned short calc_data; + unsigned short data; + unsigned char mux_sel; + struct da9055 *da9055 = hwmon->da9055; + + if (channel > DA9055_ADC_TJUNC) + return -EINVAL; + + mutex_lock(&hwmon->irq_lock); + + /* Selects desired MUX for manual conversion */ + mux_sel = chan_mux[channel] | DA9055_ADC_MAN_CONV; + + ret = da9055_reg_write(da9055, DA9055_REG_ADC_MAN, mux_sel); + if (ret < 0) + goto err; + + /* Wait for an interrupt */ + if (!wait_for_completion_timeout(&hwmon->done, + msecs_to_jiffies(500))) { + dev_err(da9055->dev, + "timeout waiting for ADC conversion interrupt\n"); + ret = -ETIMEDOUT; + goto err; + } + + ret = da9055_reg_read(da9055, DA9055_REG_ADC_RES_H); + if (ret < 0) + goto err; + + calc_data = (unsigned short)ret; + data = calc_data << 2; + + ret = da9055_reg_read(da9055, DA9055_REG_ADC_RES_L); + if (ret < 0) + goto err; + + calc_data = (unsigned short)(ret & DA9055_ADC_LSB_MASK); + data |= calc_data; + + ret = data; + +err: + mutex_unlock(&hwmon->irq_lock); + return ret; +} + +static irqreturn_t da9055_auxadc_irq(int irq, void *irq_data) +{ + struct da9055_hwmon *hwmon = irq_data; + + complete(&hwmon->done); + + return IRQ_HANDLED; +} + +/* Conversion function for VSYS and ADCINx */ +static inline int volt_reg_to_mv(int value, int channel) +{ + if (channel == DA9055_ADC_VSYS) + return DIV_ROUND_CLOSEST(value * 1000, DA9055_VSYS_DIV) + 2500; + else + return DIV_ROUND_CLOSEST(value * 1000, DA9055_ADCIN_DIV); +} + +static int da9055_enable_auto_mode(struct da9055 *da9055, int channel) +{ + + return da9055_reg_update(da9055, DA9055_REG_ADC_CONT, 1 << channel, + 1 << channel); + +} + +static int da9055_disable_auto_mode(struct da9055 *da9055, int channel) +{ + + return da9055_reg_update(da9055, DA9055_REG_ADC_CONT, 1 << channel, 0); +} + +static ssize_t da9055_read_auto_ch(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct da9055_hwmon *hwmon = dev_get_drvdata(dev); + int ret, adc; + int channel = to_sensor_dev_attr(devattr)->index; + + mutex_lock(&hwmon->hwmon_lock); + + ret = da9055_enable_auto_mode(hwmon->da9055, channel); + if (ret < 0) + goto hwmon_err; + + usleep_range(10000, 10500); + + adc = da9055_reg_read(hwmon->da9055, DA9055_REG_VSYS_RES + channel); + if (adc < 0) { + ret = adc; + goto hwmon_err_release; + } + + ret = da9055_disable_auto_mode(hwmon->da9055, channel); + if (ret < 0) + goto hwmon_err; + + mutex_unlock(&hwmon->hwmon_lock); + + return sprintf(buf, "%d\n", volt_reg_to_mv(adc, channel)); + +hwmon_err_release: + da9055_disable_auto_mode(hwmon->da9055, channel); +hwmon_err: + mutex_unlock(&hwmon->hwmon_lock); + return ret; +} + +static ssize_t da9055_read_tjunc(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct da9055_hwmon *hwmon = dev_get_drvdata(dev); + int tjunc; + int toffset; + + tjunc = da9055_adc_manual_read(hwmon, DA9055_ADC_TJUNC); + if (tjunc < 0) + return tjunc; + + toffset = da9055_reg_read(hwmon->da9055, DA9055_REG_T_OFFSET); + if (toffset < 0) + return toffset; + + /* + * Degrees celsius = -0.4084 * (ADC_RES - T_OFFSET) + 307.6332 + * T_OFFSET is a trim value used to improve accuracy of the result + */ + return sprintf(buf, "%d\n", DIV_ROUND_CLOSEST(-4084 * (tjunc - toffset) + + 3076332, 10000)); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + return sprintf(buf, "%s\n", + input_names[to_sensor_dev_attr(devattr)->index]); +} + +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, da9055_read_auto_ch, NULL, + DA9055_ADC_VSYS); +static SENSOR_DEVICE_ATTR(in0_label, S_IRUGO, show_label, NULL, + DA9055_ADC_VSYS); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, da9055_read_auto_ch, NULL, + DA9055_ADC_ADCIN1); +static SENSOR_DEVICE_ATTR(in1_label, S_IRUGO, show_label, NULL, + DA9055_ADC_ADCIN1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, da9055_read_auto_ch, NULL, + DA9055_ADC_ADCIN2); +static SENSOR_DEVICE_ATTR(in2_label, S_IRUGO, show_label, NULL, + DA9055_ADC_ADCIN2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, da9055_read_auto_ch, NULL, + DA9055_ADC_ADCIN3); +static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, show_label, NULL, + DA9055_ADC_ADCIN3); + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, da9055_read_tjunc, NULL, + DA9055_ADC_TJUNC); +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, + DA9055_ADC_TJUNC); + +static struct attribute *da9055_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_label.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_label.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_label.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_label.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(da9055); + +static int da9055_hwmon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct da9055_hwmon *hwmon; + struct device *hwmon_dev; + int hwmon_irq, ret; + + hwmon = devm_kzalloc(dev, sizeof(struct da9055_hwmon), GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + + mutex_init(&hwmon->hwmon_lock); + mutex_init(&hwmon->irq_lock); + + init_completion(&hwmon->done); + hwmon->da9055 = dev_get_drvdata(pdev->dev.parent); + + hwmon_irq = platform_get_irq_byname(pdev, "HWMON"); + if (hwmon_irq < 0) + return hwmon_irq; + + ret = devm_request_threaded_irq(&pdev->dev, hwmon_irq, + NULL, da9055_auxadc_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "adc-irq", hwmon); + if (ret != 0) { + dev_err(hwmon->da9055->dev, "DA9055 ADC IRQ failed ret=%d\n", + ret); + return ret; + } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "da9055", + hwmon, + da9055_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct platform_driver da9055_hwmon_driver = { + .probe = da9055_hwmon_probe, + .driver = { + .name = "da9055-hwmon", + }, +}; + +module_platform_driver(da9055_hwmon_driver); + +MODULE_AUTHOR("David Dajun Chen "); +MODULE_DESCRIPTION("DA9055 HWMON driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da9055-hwmon"); diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c new file mode 100644 index 000000000..a5bca3bbe --- /dev/null +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -0,0 +1,1203 @@ +/* + * dell-smm-hwmon.c -- Linux driver for accessing the SMM BIOS on Dell laptops. + * + * Copyright (C) 2001 Massimo Dal Zotto + * + * Hwmon integration: + * Copyright (C) 2011 Jean Delvare + * Copyright (C) 2013, 2014 Guenter Roeck + * Copyright (C) 2014, 2015 Pali Rohár + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define I8K_SMM_FN_STATUS 0x0025 +#define I8K_SMM_POWER_STATUS 0x0069 +#define I8K_SMM_SET_FAN 0x01a3 +#define I8K_SMM_GET_FAN 0x00a3 +#define I8K_SMM_GET_SPEED 0x02a3 +#define I8K_SMM_GET_FAN_TYPE 0x03a3 +#define I8K_SMM_GET_NOM_SPEED 0x04a3 +#define I8K_SMM_GET_TEMP 0x10a3 +#define I8K_SMM_GET_TEMP_TYPE 0x11a3 +#define I8K_SMM_GET_DELL_SIG1 0xfea3 +#define I8K_SMM_GET_DELL_SIG2 0xffa3 + +#define I8K_FAN_MULT 30 +#define I8K_FAN_MAX_RPM 30000 +#define I8K_MAX_TEMP 127 + +#define I8K_FN_NONE 0x00 +#define I8K_FN_UP 0x01 +#define I8K_FN_DOWN 0x02 +#define I8K_FN_MUTE 0x04 +#define I8K_FN_MASK 0x07 +#define I8K_FN_SHIFT 8 + +#define I8K_POWER_AC 0x05 +#define I8K_POWER_BATTERY 0x01 + +static DEFINE_MUTEX(i8k_mutex); +static char bios_version[4]; +static char bios_machineid[16]; +static struct device *i8k_hwmon_dev; +static u32 i8k_hwmon_flags; +static uint i8k_fan_mult = I8K_FAN_MULT; +static uint i8k_pwm_mult; +static uint i8k_fan_max = I8K_FAN_HIGH; +static bool disallow_fan_type_call; +static bool disallow_fan_support; + +#define I8K_HWMON_HAVE_TEMP1 (1 << 0) +#define I8K_HWMON_HAVE_TEMP2 (1 << 1) +#define I8K_HWMON_HAVE_TEMP3 (1 << 2) +#define I8K_HWMON_HAVE_TEMP4 (1 << 3) +#define I8K_HWMON_HAVE_FAN1 (1 << 4) +#define I8K_HWMON_HAVE_FAN2 (1 << 5) +#define I8K_HWMON_HAVE_FAN3 (1 << 6) + +MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)"); +MODULE_AUTHOR("Pali Rohár "); +MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("i8k"); + +static bool force; +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Force loading without checking for supported models"); + +static bool ignore_dmi; +module_param(ignore_dmi, bool, 0); +MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match"); + +#if IS_ENABLED(CONFIG_I8K) +static bool restricted = true; +module_param(restricted, bool, 0); +MODULE_PARM_DESC(restricted, "Restrict fan control and serial number to CAP_SYS_ADMIN (default: 1)"); + +static bool power_status; +module_param(power_status, bool, 0600); +MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k (default: 0)"); +#endif + +static uint fan_mult; +module_param(fan_mult, uint, 0); +MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)"); + +static uint fan_max; +module_param(fan_max, uint, 0); +MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)"); + +struct smm_regs { + unsigned int eax; + unsigned int ebx __packed; + unsigned int ecx __packed; + unsigned int edx __packed; + unsigned int esi __packed; + unsigned int edi __packed; +}; + +static inline const char *i8k_get_dmi_data(int field) +{ + const char *dmi_data = dmi_get_system_info(field); + + return dmi_data && *dmi_data ? dmi_data : "?"; +} + +/* + * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard. + */ +static int i8k_smm_func(void *par) +{ + int rc; + struct smm_regs *regs = par; + int eax = regs->eax; + +#ifdef DEBUG + int ebx = regs->ebx; + unsigned long duration; + ktime_t calltime, delta, rettime; + + calltime = ktime_get(); +#endif + + /* SMM requires CPU 0 */ + if (smp_processor_id() != 0) + return -EBUSY; + +#if defined(CONFIG_X86_64) + asm volatile("pushq %%rax\n\t" + "movl 0(%%rax),%%edx\n\t" + "pushq %%rdx\n\t" + "movl 4(%%rax),%%ebx\n\t" + "movl 8(%%rax),%%ecx\n\t" + "movl 12(%%rax),%%edx\n\t" + "movl 16(%%rax),%%esi\n\t" + "movl 20(%%rax),%%edi\n\t" + "popq %%rax\n\t" + "out %%al,$0xb2\n\t" + "out %%al,$0x84\n\t" + "xchgq %%rax,(%%rsp)\n\t" + "movl %%ebx,4(%%rax)\n\t" + "movl %%ecx,8(%%rax)\n\t" + "movl %%edx,12(%%rax)\n\t" + "movl %%esi,16(%%rax)\n\t" + "movl %%edi,20(%%rax)\n\t" + "popq %%rdx\n\t" + "movl %%edx,0(%%rax)\n\t" + "pushfq\n\t" + "popq %%rax\n\t" + "andl $1,%%eax\n" + : "=a"(rc) + : "a"(regs) + : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory"); +#else + asm volatile("pushl %%eax\n\t" + "movl 0(%%eax),%%edx\n\t" + "push %%edx\n\t" + "movl 4(%%eax),%%ebx\n\t" + "movl 8(%%eax),%%ecx\n\t" + "movl 12(%%eax),%%edx\n\t" + "movl 16(%%eax),%%esi\n\t" + "movl 20(%%eax),%%edi\n\t" + "popl %%eax\n\t" + "out %%al,$0xb2\n\t" + "out %%al,$0x84\n\t" + "xchgl %%eax,(%%esp)\n\t" + "movl %%ebx,4(%%eax)\n\t" + "movl %%ecx,8(%%eax)\n\t" + "movl %%edx,12(%%eax)\n\t" + "movl %%esi,16(%%eax)\n\t" + "movl %%edi,20(%%eax)\n\t" + "popl %%edx\n\t" + "movl %%edx,0(%%eax)\n\t" + "lahf\n\t" + "shrl $8,%%eax\n\t" + "andl $1,%%eax\n" + : "=a"(rc) + : "a"(regs) + : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory"); +#endif + if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax) + rc = -EINVAL; + +#ifdef DEBUG + rettime = ktime_get(); + delta = ktime_sub(rettime, calltime); + duration = ktime_to_ns(delta) >> 10; + pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x (took %7lu usecs)\n", eax, ebx, + (rc ? 0xffff : regs->eax & 0xffff), duration); +#endif + + return rc; +} + +/* + * Call the System Management Mode BIOS. + */ +static int i8k_smm(struct smm_regs *regs) +{ + int ret; + + get_online_cpus(); + ret = smp_call_on_cpu(0, i8k_smm_func, regs, true); + put_online_cpus(); + + return ret; +} + +/* + * Read the fan status. + */ +static int i8k_get_fan_status(int fan) +{ + struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, }; + + if (disallow_fan_support) + return -EINVAL; + + regs.ebx = fan & 0xff; + return i8k_smm(®s) ? : regs.eax & 0xff; +} + +/* + * Read the fan speed in RPM. + */ +static int i8k_get_fan_speed(int fan) +{ + struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, }; + + if (disallow_fan_support) + return -EINVAL; + + regs.ebx = fan & 0xff; + return i8k_smm(®s) ? : (regs.eax & 0xffff) * i8k_fan_mult; +} + +/* + * Read the fan type. + */ +static int _i8k_get_fan_type(int fan) +{ + struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, }; + + if (disallow_fan_support || disallow_fan_type_call) + return -EINVAL; + + regs.ebx = fan & 0xff; + return i8k_smm(®s) ? : regs.eax & 0xff; +} + +static int i8k_get_fan_type(int fan) +{ + /* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */ + static int types[3] = { INT_MIN, INT_MIN, INT_MIN }; + + if (types[fan] == INT_MIN) + types[fan] = _i8k_get_fan_type(fan); + + return types[fan]; +} + +/* + * Read the fan nominal rpm for specific fan speed. + */ +static int i8k_get_fan_nominal_speed(int fan, int speed) +{ + struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, }; + + if (disallow_fan_support) + return -EINVAL; + + regs.ebx = (fan & 0xff) | (speed << 8); + return i8k_smm(®s) ? : (regs.eax & 0xffff) * i8k_fan_mult; +} + +/* + * Set the fan speed (off, low, high, ...). + */ +static int i8k_set_fan(int fan, int speed) +{ + struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, }; + + if (disallow_fan_support) + return -EINVAL; + + speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed); + regs.ebx = (fan & 0xff) | (speed << 8); + + return i8k_smm(®s); +} + +static int i8k_get_temp_type(int sensor) +{ + struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, }; + + regs.ebx = sensor & 0xff; + return i8k_smm(®s) ? : regs.eax & 0xff; +} + +/* + * Read the cpu temperature. + */ +static int _i8k_get_temp(int sensor) +{ + struct smm_regs regs = { + .eax = I8K_SMM_GET_TEMP, + .ebx = sensor & 0xff, + }; + + return i8k_smm(®s) ? : regs.eax & 0xff; +} + +static int i8k_get_temp(int sensor) +{ + int temp = _i8k_get_temp(sensor); + + /* + * Sometimes the temperature sensor returns 0x99, which is out of range. + * In this case we retry (once) before returning an error. + # 1003655137 00000058 00005a4b + # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees + # 1003655139 00000054 00005c52 + */ + if (temp == 0x99) { + msleep(100); + temp = _i8k_get_temp(sensor); + } + /* + * Return -ENODATA for all invalid temperatures. + * + * Known instances are the 0x99 value as seen above as well as + * 0xc1 (193), which may be returned when trying to read the GPU + * temperature if the system supports a GPU and it is currently + * turned off. + */ + if (temp > I8K_MAX_TEMP) + return -ENODATA; + + return temp; +} + +static int i8k_get_dell_signature(int req_fn) +{ + struct smm_regs regs = { .eax = req_fn, }; + int rc; + + rc = i8k_smm(®s); + if (rc < 0) + return rc; + + return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1; +} + +#if IS_ENABLED(CONFIG_I8K) + +/* + * Read the Fn key status. + */ +static int i8k_get_fn_status(void) +{ + struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, }; + int rc; + + rc = i8k_smm(®s); + if (rc < 0) + return rc; + + switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) { + case I8K_FN_UP: + return I8K_VOL_UP; + case I8K_FN_DOWN: + return I8K_VOL_DOWN; + case I8K_FN_MUTE: + return I8K_VOL_MUTE; + default: + return 0; + } +} + +/* + * Read the power status. + */ +static int i8k_get_power_status(void) +{ + struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, }; + int rc; + + rc = i8k_smm(®s); + if (rc < 0) + return rc; + + return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY; +} + +/* + * Procfs interface + */ + +static int +i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg) +{ + int val = 0; + int speed, err; + unsigned char buff[16]; + int __user *argp = (int __user *)arg; + + if (!argp) + return -EINVAL; + + switch (cmd) { + case I8K_BIOS_VERSION: + if (!isdigit(bios_version[0]) || !isdigit(bios_version[1]) || + !isdigit(bios_version[2])) + return -EINVAL; + + val = (bios_version[0] << 16) | + (bios_version[1] << 8) | bios_version[2]; + break; + + case I8K_MACHINE_ID: + if (restricted && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + memset(buff, 0, sizeof(buff)); + strlcpy(buff, bios_machineid, sizeof(buff)); + break; + + case I8K_FN_STATUS: + val = i8k_get_fn_status(); + break; + + case I8K_POWER_STATUS: + val = i8k_get_power_status(); + break; + + case I8K_GET_TEMP: + val = i8k_get_temp(0); + break; + + case I8K_GET_SPEED: + if (copy_from_user(&val, argp, sizeof(int))) + return -EFAULT; + + val = i8k_get_fan_speed(val); + break; + + case I8K_GET_FAN: + if (copy_from_user(&val, argp, sizeof(int))) + return -EFAULT; + + val = i8k_get_fan_status(val); + break; + + case I8K_SET_FAN: + if (restricted && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (copy_from_user(&val, argp, sizeof(int))) + return -EFAULT; + + if (copy_from_user(&speed, argp + 1, sizeof(int))) + return -EFAULT; + + err = i8k_set_fan(val, speed); + if (err < 0) + return err; + + val = i8k_get_fan_status(val); + break; + + default: + return -EINVAL; + } + + if (val < 0) + return val; + + switch (cmd) { + case I8K_BIOS_VERSION: + if (copy_to_user(argp, &val, 4)) + return -EFAULT; + + break; + case I8K_MACHINE_ID: + if (copy_to_user(argp, buff, 16)) + return -EFAULT; + + break; + default: + if (copy_to_user(argp, &val, sizeof(int))) + return -EFAULT; + + break; + } + + return 0; +} + +static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) +{ + long ret; + + mutex_lock(&i8k_mutex); + ret = i8k_ioctl_unlocked(fp, cmd, arg); + mutex_unlock(&i8k_mutex); + + return ret; +} + +/* + * Print the information for /proc/i8k. + */ +static int i8k_proc_show(struct seq_file *seq, void *offset) +{ + int fn_key, cpu_temp, ac_power; + int left_fan, right_fan, left_speed, right_speed; + + cpu_temp = i8k_get_temp(0); /* 11100 µs */ + left_fan = i8k_get_fan_status(I8K_FAN_LEFT); /* 580 µs */ + right_fan = i8k_get_fan_status(I8K_FAN_RIGHT); /* 580 µs */ + left_speed = i8k_get_fan_speed(I8K_FAN_LEFT); /* 580 µs */ + right_speed = i8k_get_fan_speed(I8K_FAN_RIGHT); /* 580 µs */ + fn_key = i8k_get_fn_status(); /* 750 µs */ + if (power_status) + ac_power = i8k_get_power_status(); /* 14700 µs */ + else + ac_power = -1; + + /* + * Info: + * + * 1) Format version (this will change if format changes) + * 2) BIOS version + * 3) BIOS machine ID + * 4) Cpu temperature + * 5) Left fan status + * 6) Right fan status + * 7) Left fan speed + * 8) Right fan speed + * 9) AC power + * 10) Fn Key status + */ + seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n", + I8K_PROC_FMT, + bios_version, + (restricted && !capable(CAP_SYS_ADMIN)) ? "-1" : bios_machineid, + cpu_temp, + left_fan, right_fan, left_speed, right_speed, + ac_power, fn_key); + + return 0; +} + +static int i8k_open_fs(struct inode *inode, struct file *file) +{ + return single_open(file, i8k_proc_show, NULL); +} + +static const struct file_operations i8k_fops = { + .owner = THIS_MODULE, + .open = i8k_open_fs, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .unlocked_ioctl = i8k_ioctl, +}; + +static struct proc_dir_entry *entry; + +static void __init i8k_init_procfs(void) +{ + /* Register the proc entry */ + entry = proc_create("i8k", 0, NULL, &i8k_fops); +} + +static void __exit i8k_exit_procfs(void) +{ + if (entry) + remove_proc_entry("i8k", NULL); +} + +#else + +static inline void __init i8k_init_procfs(void) +{ +} + +static inline void __exit i8k_exit_procfs(void) +{ +} + +#endif + +/* + * Hwmon interface + */ + +static ssize_t i8k_hwmon_show_temp_label(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + static const char * const labels[] = { + "CPU", + "GPU", + "SODIMM", + "Other", + "Ambient", + "Other", + }; + int index = to_sensor_dev_attr(devattr)->index; + int type; + + type = i8k_get_temp_type(index); + if (type < 0) + return type; + if (type >= ARRAY_SIZE(labels)) + type = ARRAY_SIZE(labels) - 1; + return sprintf(buf, "%s\n", labels[type]); +} + +static ssize_t i8k_hwmon_show_temp(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + int temp; + + temp = i8k_get_temp(index); + if (temp < 0) + return temp; + return sprintf(buf, "%d\n", temp * 1000); +} + +static ssize_t i8k_hwmon_show_fan_label(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + static const char * const labels[] = { + "Processor Fan", + "Motherboard Fan", + "Video Fan", + "Power Supply Fan", + "Chipset Fan", + "Other Fan", + }; + int index = to_sensor_dev_attr(devattr)->index; + bool dock = false; + int type; + + type = i8k_get_fan_type(index); + if (type < 0) + return type; + + if (type & 0x10) { + dock = true; + type &= 0x0F; + } + + if (type >= ARRAY_SIZE(labels)) + type = (ARRAY_SIZE(labels) - 1); + + return sprintf(buf, "%s%s\n", (dock ? "Docking " : ""), labels[type]); +} + +static ssize_t i8k_hwmon_show_fan(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + int fan_speed; + + fan_speed = i8k_get_fan_speed(index); + if (fan_speed < 0) + return fan_speed; + return sprintf(buf, "%d\n", fan_speed); +} + +static ssize_t i8k_hwmon_show_pwm(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + int status; + + status = i8k_get_fan_status(index); + if (status < 0) + return -EIO; + return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255)); +} + +static ssize_t i8k_hwmon_set_pwm(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int index = to_sensor_dev_attr(attr)->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max); + + mutex_lock(&i8k_mutex); + err = i8k_set_fan(index, val); + mutex_unlock(&i8k_mutex); + + return err < 0 ? -EIO : count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL, + 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL, + 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL, + 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL, + 3); +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, i8k_hwmon_show_fan, NULL, 0); +static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL, + 0); +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm, + i8k_hwmon_set_pwm, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, i8k_hwmon_show_fan, NULL, + 1); +static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL, + 1); +static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm, + i8k_hwmon_set_pwm, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, i8k_hwmon_show_fan, NULL, + 2); +static SENSOR_DEVICE_ATTR(fan3_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL, + 2); +static SENSOR_DEVICE_ATTR(pwm3, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm, + i8k_hwmon_set_pwm, 2); + +static struct attribute *i8k_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, /* 0 */ + &sensor_dev_attr_temp1_label.dev_attr.attr, /* 1 */ + &sensor_dev_attr_temp2_input.dev_attr.attr, /* 2 */ + &sensor_dev_attr_temp2_label.dev_attr.attr, /* 3 */ + &sensor_dev_attr_temp3_input.dev_attr.attr, /* 4 */ + &sensor_dev_attr_temp3_label.dev_attr.attr, /* 5 */ + &sensor_dev_attr_temp4_input.dev_attr.attr, /* 6 */ + &sensor_dev_attr_temp4_label.dev_attr.attr, /* 7 */ + &sensor_dev_attr_fan1_input.dev_attr.attr, /* 8 */ + &sensor_dev_attr_fan1_label.dev_attr.attr, /* 9 */ + &sensor_dev_attr_pwm1.dev_attr.attr, /* 10 */ + &sensor_dev_attr_fan2_input.dev_attr.attr, /* 11 */ + &sensor_dev_attr_fan2_label.dev_attr.attr, /* 12 */ + &sensor_dev_attr_pwm2.dev_attr.attr, /* 13 */ + &sensor_dev_attr_fan3_input.dev_attr.attr, /* 14 */ + &sensor_dev_attr_fan3_label.dev_attr.attr, /* 15 */ + &sensor_dev_attr_pwm3.dev_attr.attr, /* 16 */ + NULL +}; + +static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr, + int index) +{ + if (disallow_fan_support && index >= 8) + return 0; + if (disallow_fan_type_call && + (index == 9 || index == 12 || index == 15)) + return 0; + if (index >= 0 && index <= 1 && + !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1)) + return 0; + if (index >= 2 && index <= 3 && + !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2)) + return 0; + if (index >= 4 && index <= 5 && + !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3)) + return 0; + if (index >= 6 && index <= 7 && + !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4)) + return 0; + if (index >= 8 && index <= 10 && + !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1)) + return 0; + if (index >= 11 && index <= 13 && + !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2)) + return 0; + if (index >= 14 && index <= 16 && + !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN3)) + return 0; + + return attr->mode; +} + +static const struct attribute_group i8k_group = { + .attrs = i8k_attrs, + .is_visible = i8k_is_visible, +}; +__ATTRIBUTE_GROUPS(i8k); + +static int __init i8k_init_hwmon(void) +{ + int err; + + i8k_hwmon_flags = 0; + + /* CPU temperature attributes, if temperature type is OK */ + err = i8k_get_temp_type(0); + if (err >= 0) + i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1; + /* check for additional temperature sensors */ + err = i8k_get_temp_type(1); + if (err >= 0) + i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2; + err = i8k_get_temp_type(2); + if (err >= 0) + i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3; + err = i8k_get_temp_type(3); + if (err >= 0) + i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4; + + /* First fan attributes, if fan status or type is OK */ + err = i8k_get_fan_status(0); + if (err < 0) + err = i8k_get_fan_type(0); + if (err >= 0) + i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1; + + /* Second fan attributes, if fan status or type is OK */ + err = i8k_get_fan_status(1); + if (err < 0) + err = i8k_get_fan_type(1); + if (err >= 0) + i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2; + + /* Third fan attributes, if fan status or type is OK */ + err = i8k_get_fan_status(2); + if (err < 0) + err = i8k_get_fan_type(2); + if (err >= 0) + i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN3; + + i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "dell_smm", + NULL, i8k_groups); + if (IS_ERR(i8k_hwmon_dev)) { + err = PTR_ERR(i8k_hwmon_dev); + i8k_hwmon_dev = NULL; + pr_err("hwmon registration failed (%d)\n", err); + return err; + } + return 0; +} + +struct i8k_config_data { + uint fan_mult; + uint fan_max; +}; + +enum i8k_configs { + DELL_LATITUDE_D520, + DELL_PRECISION_490, + DELL_STUDIO, + DELL_XPS, +}; + +static const struct i8k_config_data i8k_config_data[] = { + [DELL_LATITUDE_D520] = { + .fan_mult = 1, + .fan_max = I8K_FAN_TURBO, + }, + [DELL_PRECISION_490] = { + .fan_mult = 1, + .fan_max = I8K_FAN_TURBO, + }, + [DELL_STUDIO] = { + .fan_mult = 1, + .fan_max = I8K_FAN_HIGH, + }, + [DELL_XPS] = { + .fan_mult = 1, + .fan_max = I8K_FAN_HIGH, + }, +}; + +static const struct dmi_system_id i8k_dmi_table[] __initconst = { + { + .ident = "Dell Inspiron", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"), + }, + }, + { + .ident = "Dell Latitude", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"), + }, + }, + { + .ident = "Dell Inspiron 2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"), + }, + }, + { + .ident = "Dell Latitude D520", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"), + }, + .driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520], + }, + { + .ident = "Dell Latitude 2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"), + }, + }, + { /* UK Inspiron 6400 */ + .ident = "Dell Inspiron 3", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "MM061"), + }, + }, + { + .ident = "Dell Inspiron 3", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "MP061"), + }, + }, + { + .ident = "Dell Precision 490", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, + "Precision WorkStation 490"), + }, + .driver_data = (void *)&i8k_config_data[DELL_PRECISION_490], + }, + { + .ident = "Dell Precision", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Precision"), + }, + }, + { + .ident = "Dell Vostro", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"), + }, + }, + { + .ident = "Dell XPS421", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"), + }, + }, + { + .ident = "Dell Studio", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Studio"), + }, + .driver_data = (void *)&i8k_config_data[DELL_STUDIO], + }, + { + .ident = "Dell XPS 13", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "XPS13"), + }, + .driver_data = (void *)&i8k_config_data[DELL_XPS], + }, + { + .ident = "Dell XPS M140", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"), + }, + .driver_data = (void *)&i8k_config_data[DELL_XPS], + }, + { + .ident = "Dell XPS 15 9560", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "XPS 15 9560"), + }, + }, + { } +}; + +MODULE_DEVICE_TABLE(dmi, i8k_dmi_table); + +/* + * On some machines once I8K_SMM_GET_FAN_TYPE is issued then CPU fan speed + * randomly going up and down due to bug in Dell SMM or BIOS. Here is blacklist + * of affected Dell machines for which we disallow I8K_SMM_GET_FAN_TYPE call. + * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=100121 + */ +static const struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initconst = { + { + .ident = "Dell Studio XPS 8000", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8000"), + }, + }, + { + .ident = "Dell Studio XPS 8100", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8100"), + }, + }, + { + .ident = "Dell Inspiron 580", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 580 "), + }, + }, + { } +}; + +/* + * On some machines all fan related SMM functions implemented by Dell BIOS + * firmware freeze kernel for about 500ms. Until Dell fixes these problems fan + * support for affected blacklisted Dell machines stay disabled. + * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=195751 + */ +static struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initdata = { + { + .ident = "Dell Inspiron 7720", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"), + }, + }, + { + .ident = "Dell Vostro 3360", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"), + }, + }, + { + .ident = "Dell XPS13 9333", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"), + }, + }, + { } +}; + +/* + * Probe for the presence of a supported laptop. + */ +static int __init i8k_probe(void) +{ + const struct dmi_system_id *id; + int fan, ret; + + /* + * Get DMI information + */ + if (!dmi_check_system(i8k_dmi_table)) { + if (!ignore_dmi && !force) + return -ENODEV; + + pr_info("not running on a supported Dell system.\n"); + pr_info("vendor=%s, model=%s, version=%s\n", + i8k_get_dmi_data(DMI_SYS_VENDOR), + i8k_get_dmi_data(DMI_PRODUCT_NAME), + i8k_get_dmi_data(DMI_BIOS_VERSION)); + } + + if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) { + pr_warn("broken Dell BIOS detected, disallow fan support\n"); + if (!force) + disallow_fan_support = true; + } + + if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) { + pr_warn("broken Dell BIOS detected, disallow fan type call\n"); + if (!force) + disallow_fan_type_call = true; + } + + strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION), + sizeof(bios_version)); + strlcpy(bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL), + sizeof(bios_machineid)); + + /* + * Get SMM Dell signature + */ + if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) && + i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) { + pr_err("unable to get SMM Dell signature\n"); + if (!force) + return -ENODEV; + } + + /* + * Set fan multiplier and maximal fan speed from dmi config + * Values specified in module parameters override values from dmi + */ + id = dmi_first_match(i8k_dmi_table); + if (id && id->driver_data) { + const struct i8k_config_data *conf = id->driver_data; + if (!fan_mult && conf->fan_mult) + fan_mult = conf->fan_mult; + if (!fan_max && conf->fan_max) + fan_max = conf->fan_max; + } + + i8k_fan_max = fan_max ? : I8K_FAN_HIGH; /* Must not be 0 */ + i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max); + + if (!fan_mult) { + /* + * Autodetect fan multiplier based on nominal rpm + * If fan reports rpm value too high then set multiplier to 1 + */ + for (fan = 0; fan < 2; ++fan) { + ret = i8k_get_fan_nominal_speed(fan, i8k_fan_max); + if (ret < 0) + continue; + if (ret > I8K_FAN_MAX_RPM) + i8k_fan_mult = 1; + break; + } + } else { + /* Fan multiplier was specified in module param or in dmi */ + i8k_fan_mult = fan_mult; + } + + return 0; +} + +static int __init i8k_init(void) +{ + int err; + + /* Are we running on an supported laptop? */ + if (i8k_probe()) + return -ENODEV; + + err = i8k_init_hwmon(); + if (err) + return err; + + i8k_init_procfs(); + return 0; +} + +static void __exit i8k_exit(void) +{ + hwmon_device_unregister(i8k_hwmon_dev); + i8k_exit_procfs(); +} + +module_init(i8k_init); +module_exit(i8k_exit); diff --git a/drivers/hwmon/dme1737.c b/drivers/hwmon/dme1737.c new file mode 100644 index 000000000..aa40a00ad --- /dev/null +++ b/drivers/hwmon/dme1737.c @@ -0,0 +1,2801 @@ +/* + * dme1737.c - Driver for the SMSC DME1737, Asus A8000, SMSC SCH311x, SCH5027, + * and SCH5127 Super-I/O chips integrated hardware monitoring + * features. + * Copyright (c) 2007, 2008, 2009, 2010 Juerg Haefliger + * + * This driver is an I2C/ISA hybrid, meaning that it uses the I2C bus to access + * the chip registers if a DME1737, A8000, or SCH5027 is found and the ISA bus + * if a SCH311x or SCH5127 chip is found. Both types of chips have very + * similar hardware monitoring capabilities but differ in the way they can be + * accessed. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ISA device, if found */ +static struct platform_device *pdev; + +/* Module load parameters */ +static bool force_start; +module_param(force_start, bool, 0); +MODULE_PARM_DESC(force_start, "Force the chip to start monitoring inputs"); + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +static bool probe_all_addr; +module_param(probe_all_addr, bool, 0); +MODULE_PARM_DESC(probe_all_addr, + "Include probing of non-standard LPC addresses"); + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = {0x2c, 0x2d, 0x2e, I2C_CLIENT_END}; + +enum chips { dme1737, sch5027, sch311x, sch5127 }; + +#define DO_REPORT "Please report to the driver maintainer." + +/* --------------------------------------------------------------------- + * Registers + * + * The sensors are defined as follows: + * + * Voltages Temperatures + * -------- ------------ + * in0 +5VTR (+5V stdby) temp1 Remote diode 1 + * in1 Vccp (proc core) temp2 Internal temp + * in2 VCC (internal +3.3V) temp3 Remote diode 2 + * in3 +5V + * in4 +12V + * in5 VTR (+3.3V stby) + * in6 Vbat + * in7 Vtrip (sch5127 only) + * + * --------------------------------------------------------------------- */ + +/* Voltages (in) numbered 0-7 (ix) */ +#define DME1737_REG_IN(ix) ((ix) < 5 ? 0x20 + (ix) : \ + (ix) < 7 ? 0x94 + (ix) : \ + 0x1f) +#define DME1737_REG_IN_MIN(ix) ((ix) < 5 ? 0x44 + (ix) * 2 \ + : 0x91 + (ix) * 2) +#define DME1737_REG_IN_MAX(ix) ((ix) < 5 ? 0x45 + (ix) * 2 \ + : 0x92 + (ix) * 2) + +/* Temperatures (temp) numbered 0-2 (ix) */ +#define DME1737_REG_TEMP(ix) (0x25 + (ix)) +#define DME1737_REG_TEMP_MIN(ix) (0x4e + (ix) * 2) +#define DME1737_REG_TEMP_MAX(ix) (0x4f + (ix) * 2) +#define DME1737_REG_TEMP_OFFSET(ix) ((ix) == 0 ? 0x1f \ + : 0x1c + (ix)) + +/* + * Voltage and temperature LSBs + * The LSBs (4 bits each) are stored in 5 registers with the following layouts: + * IN_TEMP_LSB(0) = [in5, in6] + * IN_TEMP_LSB(1) = [temp3, temp1] + * IN_TEMP_LSB(2) = [in4, temp2] + * IN_TEMP_LSB(3) = [in3, in0] + * IN_TEMP_LSB(4) = [in2, in1] + * IN_TEMP_LSB(5) = [res, in7] + */ +#define DME1737_REG_IN_TEMP_LSB(ix) (0x84 + (ix)) +static const u8 DME1737_REG_IN_LSB[] = {3, 4, 4, 3, 2, 0, 0, 5}; +static const u8 DME1737_REG_IN_LSB_SHL[] = {4, 4, 0, 0, 0, 0, 4, 4}; +static const u8 DME1737_REG_TEMP_LSB[] = {1, 2, 1}; +static const u8 DME1737_REG_TEMP_LSB_SHL[] = {4, 4, 0}; + +/* Fans numbered 0-5 (ix) */ +#define DME1737_REG_FAN(ix) ((ix) < 4 ? 0x28 + (ix) * 2 \ + : 0xa1 + (ix) * 2) +#define DME1737_REG_FAN_MIN(ix) ((ix) < 4 ? 0x54 + (ix) * 2 \ + : 0xa5 + (ix) * 2) +#define DME1737_REG_FAN_OPT(ix) ((ix) < 4 ? 0x90 + (ix) \ + : 0xb2 + (ix)) +#define DME1737_REG_FAN_MAX(ix) (0xb4 + (ix)) /* only for fan[4-5] */ + +/* PWMs numbered 0-2, 4-5 (ix) */ +#define DME1737_REG_PWM(ix) ((ix) < 3 ? 0x30 + (ix) \ + : 0xa1 + (ix)) +#define DME1737_REG_PWM_CONFIG(ix) (0x5c + (ix)) /* only for pwm[0-2] */ +#define DME1737_REG_PWM_MIN(ix) (0x64 + (ix)) /* only for pwm[0-2] */ +#define DME1737_REG_PWM_FREQ(ix) ((ix) < 3 ? 0x5f + (ix) \ + : 0xa3 + (ix)) +/* + * The layout of the ramp rate registers is different from the other pwm + * registers. The bits for the 3 PWMs are stored in 2 registers: + * PWM_RR(0) = [OFF3, OFF2, OFF1, RES, RR1E, RR1-2, RR1-1, RR1-0] + * PWM_RR(1) = [RR2E, RR2-2, RR2-1, RR2-0, RR3E, RR3-2, RR3-1, RR3-0] + */ +#define DME1737_REG_PWM_RR(ix) (0x62 + (ix)) /* only for pwm[0-2] */ + +/* Thermal zones 0-2 */ +#define DME1737_REG_ZONE_LOW(ix) (0x67 + (ix)) +#define DME1737_REG_ZONE_ABS(ix) (0x6a + (ix)) +/* + * The layout of the hysteresis registers is different from the other zone + * registers. The bits for the 3 zones are stored in 2 registers: + * ZONE_HYST(0) = [H1-3, H1-2, H1-1, H1-0, H2-3, H2-2, H2-1, H2-0] + * ZONE_HYST(1) = [H3-3, H3-2, H3-1, H3-0, RES, RES, RES, RES] + */ +#define DME1737_REG_ZONE_HYST(ix) (0x6d + (ix)) + +/* + * Alarm registers and bit mapping + * The 3 8-bit alarm registers will be concatenated to a single 32-bit + * alarm value [0, ALARM3, ALARM2, ALARM1]. + */ +#define DME1737_REG_ALARM1 0x41 +#define DME1737_REG_ALARM2 0x42 +#define DME1737_REG_ALARM3 0x83 +static const u8 DME1737_BIT_ALARM_IN[] = {0, 1, 2, 3, 8, 16, 17, 18}; +static const u8 DME1737_BIT_ALARM_TEMP[] = {4, 5, 6}; +static const u8 DME1737_BIT_ALARM_FAN[] = {10, 11, 12, 13, 22, 23}; + +/* Miscellaneous registers */ +#define DME1737_REG_DEVICE 0x3d +#define DME1737_REG_COMPANY 0x3e +#define DME1737_REG_VERSTEP 0x3f +#define DME1737_REG_CONFIG 0x40 +#define DME1737_REG_CONFIG2 0x7f +#define DME1737_REG_VID 0x43 +#define DME1737_REG_TACH_PWM 0x81 + +/* --------------------------------------------------------------------- + * Misc defines + * --------------------------------------------------------------------- */ + +/* Chip identification */ +#define DME1737_COMPANY_SMSC 0x5c +#define DME1737_VERSTEP 0x88 +#define DME1737_VERSTEP_MASK 0xf8 +#define SCH311X_DEVICE 0x8c +#define SCH5027_VERSTEP 0x69 +#define SCH5127_DEVICE 0x8e + +/* Device ID values (global configuration register index 0x20) */ +#define DME1737_ID_1 0x77 +#define DME1737_ID_2 0x78 +#define SCH3112_ID 0x7c +#define SCH3114_ID 0x7d +#define SCH3116_ID 0x7f +#define SCH5027_ID 0x89 +#define SCH5127_ID 0x86 + +/* Length of ISA address segment */ +#define DME1737_EXTENT 2 + +/* chip-dependent features */ +#define HAS_TEMP_OFFSET (1 << 0) /* bit 0 */ +#define HAS_VID (1 << 1) /* bit 1 */ +#define HAS_ZONE3 (1 << 2) /* bit 2 */ +#define HAS_ZONE_HYST (1 << 3) /* bit 3 */ +#define HAS_PWM_MIN (1 << 4) /* bit 4 */ +#define HAS_FAN(ix) (1 << ((ix) + 5)) /* bits 5-10 */ +#define HAS_PWM(ix) (1 << ((ix) + 11)) /* bits 11-16 */ +#define HAS_IN7 (1 << 17) /* bit 17 */ + +/* --------------------------------------------------------------------- + * Data structures and manipulation thereof + * --------------------------------------------------------------------- */ + +struct dme1737_data { + struct i2c_client *client; /* for I2C devices only */ + struct device *hwmon_dev; + const char *name; + unsigned int addr; /* for ISA devices only */ + + struct mutex update_lock; + int valid; /* !=0 if following fields are valid */ + unsigned long last_update; /* in jiffies */ + unsigned long last_vbat; /* in jiffies */ + enum chips type; + const int *in_nominal; /* pointer to IN_NOMINAL array */ + + u8 vid; + u8 pwm_rr_en; + u32 has_features; + + /* Register values */ + u16 in[8]; + u8 in_min[8]; + u8 in_max[8]; + s16 temp[3]; + s8 temp_min[3]; + s8 temp_max[3]; + s8 temp_offset[3]; + u8 config; + u8 config2; + u8 vrm; + u16 fan[6]; + u16 fan_min[6]; + u8 fan_max[2]; + u8 fan_opt[6]; + u8 pwm[6]; + u8 pwm_min[3]; + u8 pwm_config[3]; + u8 pwm_acz[3]; + u8 pwm_freq[6]; + u8 pwm_rr[2]; + s8 zone_low[3]; + s8 zone_abs[3]; + u8 zone_hyst[2]; + u32 alarms; +}; + +/* Nominal voltage values */ +static const int IN_NOMINAL_DME1737[] = {5000, 2250, 3300, 5000, 12000, 3300, + 3300}; +static const int IN_NOMINAL_SCH311x[] = {2500, 1500, 3300, 5000, 12000, 3300, + 3300}; +static const int IN_NOMINAL_SCH5027[] = {5000, 2250, 3300, 1125, 1125, 3300, + 3300}; +static const int IN_NOMINAL_SCH5127[] = {2500, 2250, 3300, 1125, 1125, 3300, + 3300, 1500}; +#define IN_NOMINAL(type) ((type) == sch311x ? IN_NOMINAL_SCH311x : \ + (type) == sch5027 ? IN_NOMINAL_SCH5027 : \ + (type) == sch5127 ? IN_NOMINAL_SCH5127 : \ + IN_NOMINAL_DME1737) + +/* + * Voltage input + * Voltage inputs have 16 bits resolution, limit values have 8 bits + * resolution. + */ +static inline int IN_FROM_REG(int reg, int nominal, int res) +{ + return (reg * nominal + (3 << (res - 3))) / (3 << (res - 2)); +} + +static inline int IN_TO_REG(long val, int nominal) +{ + val = clamp_val(val, 0, 255 * nominal / 192); + return DIV_ROUND_CLOSEST(val * 192, nominal); +} + +/* + * Temperature input + * The register values represent temperatures in 2's complement notation from + * -127 degrees C to +127 degrees C. Temp inputs have 16 bits resolution, limit + * values have 8 bits resolution. + */ +static inline int TEMP_FROM_REG(int reg, int res) +{ + return (reg * 1000) >> (res - 8); +} + +static inline int TEMP_TO_REG(long val) +{ + val = clamp_val(val, -128000, 127000); + return DIV_ROUND_CLOSEST(val, 1000); +} + +/* Temperature range */ +static const int TEMP_RANGE[] = {2000, 2500, 3333, 4000, 5000, 6666, 8000, + 10000, 13333, 16000, 20000, 26666, 32000, + 40000, 53333, 80000}; + +static inline int TEMP_RANGE_FROM_REG(int reg) +{ + return TEMP_RANGE[(reg >> 4) & 0x0f]; +} + +static int TEMP_RANGE_TO_REG(long val, int reg) +{ + int i; + + for (i = 15; i > 0; i--) { + if (val > (TEMP_RANGE[i] + TEMP_RANGE[i - 1] + 1) / 2) + break; + } + + return (reg & 0x0f) | (i << 4); +} + +/* + * Temperature hysteresis + * Register layout: + * reg[0] = [H1-3, H1-2, H1-1, H1-0, H2-3, H2-2, H2-1, H2-0] + * reg[1] = [H3-3, H3-2, H3-1, H3-0, xxxx, xxxx, xxxx, xxxx] + */ +static inline int TEMP_HYST_FROM_REG(int reg, int ix) +{ + return (((ix == 1) ? reg : reg >> 4) & 0x0f) * 1000; +} + +static inline int TEMP_HYST_TO_REG(int temp, long hyst, int ix, int reg) +{ + hyst = clamp_val(hyst, temp - 15000, temp); + hyst = DIV_ROUND_CLOSEST(temp - hyst, 1000); + + return (ix == 1) ? (reg & 0xf0) | hyst : (reg & 0x0f) | (hyst << 4); +} + +/* Fan input RPM */ +static inline int FAN_FROM_REG(int reg, int tpc) +{ + if (tpc) + return tpc * reg; + else + return (reg == 0 || reg == 0xffff) ? 0 : 90000 * 60 / reg; +} + +static inline int FAN_TO_REG(long val, int tpc) +{ + if (tpc) { + return clamp_val(val / tpc, 0, 0xffff); + } else { + return (val <= 0) ? 0xffff : + clamp_val(90000 * 60 / val, 0, 0xfffe); + } +} + +/* + * Fan TPC (tach pulse count) + * Converts a register value to a TPC multiplier or returns 0 if the tachometer + * is configured in legacy (non-tpc) mode + */ +static inline int FAN_TPC_FROM_REG(int reg) +{ + return (reg & 0x20) ? 0 : 60 >> (reg & 0x03); +} + +/* + * Fan type + * The type of a fan is expressed in number of pulses-per-revolution that it + * emits + */ +static inline int FAN_TYPE_FROM_REG(int reg) +{ + int edge = (reg >> 1) & 0x03; + + return (edge > 0) ? 1 << (edge - 1) : 0; +} + +static inline int FAN_TYPE_TO_REG(long val, int reg) +{ + int edge = (val == 4) ? 3 : val; + + return (reg & 0xf9) | (edge << 1); +} + +/* Fan max RPM */ +static const int FAN_MAX[] = {0x54, 0x38, 0x2a, 0x21, 0x1c, 0x18, 0x15, 0x12, + 0x11, 0x0f, 0x0e}; + +static int FAN_MAX_FROM_REG(int reg) +{ + int i; + + for (i = 10; i > 0; i--) { + if (reg == FAN_MAX[i]) + break; + } + + return 1000 + i * 500; +} + +static int FAN_MAX_TO_REG(long val) +{ + int i; + + for (i = 10; i > 0; i--) { + if (val > (1000 + (i - 1) * 500)) + break; + } + + return FAN_MAX[i]; +} + +/* + * PWM enable + * Register to enable mapping: + * 000: 2 fan on zone 1 auto + * 001: 2 fan on zone 2 auto + * 010: 2 fan on zone 3 auto + * 011: 0 fan full on + * 100: -1 fan disabled + * 101: 2 fan on hottest of zones 2,3 auto + * 110: 2 fan on hottest of zones 1,2,3 auto + * 111: 1 fan in manual mode + */ +static inline int PWM_EN_FROM_REG(int reg) +{ + static const int en[] = {2, 2, 2, 0, -1, 2, 2, 1}; + + return en[(reg >> 5) & 0x07]; +} + +static inline int PWM_EN_TO_REG(int val, int reg) +{ + int en = (val == 1) ? 7 : 3; + + return (reg & 0x1f) | ((en & 0x07) << 5); +} + +/* + * PWM auto channels zone + * Register to auto channels zone mapping (ACZ is a bitfield with bit x + * corresponding to zone x+1): + * 000: 001 fan on zone 1 auto + * 001: 010 fan on zone 2 auto + * 010: 100 fan on zone 3 auto + * 011: 000 fan full on + * 100: 000 fan disabled + * 101: 110 fan on hottest of zones 2,3 auto + * 110: 111 fan on hottest of zones 1,2,3 auto + * 111: 000 fan in manual mode + */ +static inline int PWM_ACZ_FROM_REG(int reg) +{ + static const int acz[] = {1, 2, 4, 0, 0, 6, 7, 0}; + + return acz[(reg >> 5) & 0x07]; +} + +static inline int PWM_ACZ_TO_REG(long val, int reg) +{ + int acz = (val == 4) ? 2 : val - 1; + + return (reg & 0x1f) | ((acz & 0x07) << 5); +} + +/* PWM frequency */ +static const int PWM_FREQ[] = {11, 15, 22, 29, 35, 44, 59, 88, + 15000, 20000, 30000, 25000, 0, 0, 0, 0}; + +static inline int PWM_FREQ_FROM_REG(int reg) +{ + return PWM_FREQ[reg & 0x0f]; +} + +static int PWM_FREQ_TO_REG(long val, int reg) +{ + int i; + + /* the first two cases are special - stupid chip design! */ + if (val > 27500) { + i = 10; + } else if (val > 22500) { + i = 11; + } else { + for (i = 9; i > 0; i--) { + if (val > (PWM_FREQ[i] + PWM_FREQ[i - 1] + 1) / 2) + break; + } + } + + return (reg & 0xf0) | i; +} + +/* + * PWM ramp rate + * Register layout: + * reg[0] = [OFF3, OFF2, OFF1, RES, RR1-E, RR1-2, RR1-1, RR1-0] + * reg[1] = [RR2-E, RR2-2, RR2-1, RR2-0, RR3-E, RR3-2, RR3-1, RR3-0] + */ +static const u8 PWM_RR[] = {206, 104, 69, 41, 26, 18, 10, 5}; + +static inline int PWM_RR_FROM_REG(int reg, int ix) +{ + int rr = (ix == 1) ? reg >> 4 : reg; + + return (rr & 0x08) ? PWM_RR[rr & 0x07] : 0; +} + +static int PWM_RR_TO_REG(long val, int ix, int reg) +{ + int i; + + for (i = 0; i < 7; i++) { + if (val > (PWM_RR[i] + PWM_RR[i + 1] + 1) / 2) + break; + } + + return (ix == 1) ? (reg & 0x8f) | (i << 4) : (reg & 0xf8) | i; +} + +/* PWM ramp rate enable */ +static inline int PWM_RR_EN_FROM_REG(int reg, int ix) +{ + return PWM_RR_FROM_REG(reg, ix) ? 1 : 0; +} + +static inline int PWM_RR_EN_TO_REG(long val, int ix, int reg) +{ + int en = (ix == 1) ? 0x80 : 0x08; + + return val ? reg | en : reg & ~en; +} + +/* + * PWM min/off + * The PWM min/off bits are part of the PMW ramp rate register 0 (see above for + * the register layout). + */ +static inline int PWM_OFF_FROM_REG(int reg, int ix) +{ + return (reg >> (ix + 5)) & 0x01; +} + +static inline int PWM_OFF_TO_REG(int val, int ix, int reg) +{ + return (reg & ~(1 << (ix + 5))) | ((val & 0x01) << (ix + 5)); +} + +/* --------------------------------------------------------------------- + * Device I/O access + * + * ISA access is performed through an index/data register pair and needs to + * be protected by a mutex during runtime (not required for initialization). + * We use data->update_lock for this and need to ensure that we acquire it + * before calling dme1737_read or dme1737_write. + * --------------------------------------------------------------------- */ + +static u8 dme1737_read(const struct dme1737_data *data, u8 reg) +{ + struct i2c_client *client = data->client; + s32 val; + + if (client) { /* I2C device */ + val = i2c_smbus_read_byte_data(client, reg); + + if (val < 0) { + dev_warn(&client->dev, + "Read from register 0x%02x failed! %s\n", + reg, DO_REPORT); + } + } else { /* ISA device */ + outb(reg, data->addr); + val = inb(data->addr + 1); + } + + return val; +} + +static s32 dme1737_write(const struct dme1737_data *data, u8 reg, u8 val) +{ + struct i2c_client *client = data->client; + s32 res = 0; + + if (client) { /* I2C device */ + res = i2c_smbus_write_byte_data(client, reg, val); + + if (res < 0) { + dev_warn(&client->dev, + "Write to register 0x%02x failed! %s\n", + reg, DO_REPORT); + } + } else { /* ISA device */ + outb(reg, data->addr); + outb(val, data->addr + 1); + } + + return res; +} + +static struct dme1737_data *dme1737_update_device(struct device *dev) +{ + struct dme1737_data *data = dev_get_drvdata(dev); + int ix; + u8 lsb[6]; + + mutex_lock(&data->update_lock); + + /* Enable a Vbat monitoring cycle every 10 mins */ + if (time_after(jiffies, data->last_vbat + 600 * HZ) || !data->valid) { + dme1737_write(data, DME1737_REG_CONFIG, dme1737_read(data, + DME1737_REG_CONFIG) | 0x10); + data->last_vbat = jiffies; + } + + /* Sample register contents every 1 sec */ + if (time_after(jiffies, data->last_update + HZ) || !data->valid) { + if (data->has_features & HAS_VID) { + data->vid = dme1737_read(data, DME1737_REG_VID) & + 0x3f; + } + + /* In (voltage) registers */ + for (ix = 0; ix < ARRAY_SIZE(data->in); ix++) { + /* + * Voltage inputs are stored as 16 bit values even + * though they have only 12 bits resolution. This is + * to make it consistent with the temp inputs. + */ + if (ix == 7 && !(data->has_features & HAS_IN7)) + continue; + data->in[ix] = dme1737_read(data, + DME1737_REG_IN(ix)) << 8; + data->in_min[ix] = dme1737_read(data, + DME1737_REG_IN_MIN(ix)); + data->in_max[ix] = dme1737_read(data, + DME1737_REG_IN_MAX(ix)); + } + + /* Temp registers */ + for (ix = 0; ix < ARRAY_SIZE(data->temp); ix++) { + /* + * Temp inputs are stored as 16 bit values even + * though they have only 12 bits resolution. This is + * to take advantage of implicit conversions between + * register values (2's complement) and temp values + * (signed decimal). + */ + data->temp[ix] = dme1737_read(data, + DME1737_REG_TEMP(ix)) << 8; + data->temp_min[ix] = dme1737_read(data, + DME1737_REG_TEMP_MIN(ix)); + data->temp_max[ix] = dme1737_read(data, + DME1737_REG_TEMP_MAX(ix)); + if (data->has_features & HAS_TEMP_OFFSET) { + data->temp_offset[ix] = dme1737_read(data, + DME1737_REG_TEMP_OFFSET(ix)); + } + } + + /* + * In and temp LSB registers + * The LSBs are latched when the MSBs are read, so the order in + * which the registers are read (MSB first, then LSB) is + * important! + */ + for (ix = 0; ix < ARRAY_SIZE(lsb); ix++) { + if (ix == 5 && !(data->has_features & HAS_IN7)) + continue; + lsb[ix] = dme1737_read(data, + DME1737_REG_IN_TEMP_LSB(ix)); + } + for (ix = 0; ix < ARRAY_SIZE(data->in); ix++) { + if (ix == 7 && !(data->has_features & HAS_IN7)) + continue; + data->in[ix] |= (lsb[DME1737_REG_IN_LSB[ix]] << + DME1737_REG_IN_LSB_SHL[ix]) & 0xf0; + } + for (ix = 0; ix < ARRAY_SIZE(data->temp); ix++) { + data->temp[ix] |= (lsb[DME1737_REG_TEMP_LSB[ix]] << + DME1737_REG_TEMP_LSB_SHL[ix]) & 0xf0; + } + + /* Fan registers */ + for (ix = 0; ix < ARRAY_SIZE(data->fan); ix++) { + /* + * Skip reading registers if optional fans are not + * present + */ + if (!(data->has_features & HAS_FAN(ix))) + continue; + data->fan[ix] = dme1737_read(data, + DME1737_REG_FAN(ix)); + data->fan[ix] |= dme1737_read(data, + DME1737_REG_FAN(ix) + 1) << 8; + data->fan_min[ix] = dme1737_read(data, + DME1737_REG_FAN_MIN(ix)); + data->fan_min[ix] |= dme1737_read(data, + DME1737_REG_FAN_MIN(ix) + 1) << 8; + data->fan_opt[ix] = dme1737_read(data, + DME1737_REG_FAN_OPT(ix)); + /* fan_max exists only for fan[5-6] */ + if (ix > 3) { + data->fan_max[ix - 4] = dme1737_read(data, + DME1737_REG_FAN_MAX(ix)); + } + } + + /* PWM registers */ + for (ix = 0; ix < ARRAY_SIZE(data->pwm); ix++) { + /* + * Skip reading registers if optional PWMs are not + * present + */ + if (!(data->has_features & HAS_PWM(ix))) + continue; + data->pwm[ix] = dme1737_read(data, + DME1737_REG_PWM(ix)); + data->pwm_freq[ix] = dme1737_read(data, + DME1737_REG_PWM_FREQ(ix)); + /* pwm_config and pwm_min exist only for pwm[1-3] */ + if (ix < 3) { + data->pwm_config[ix] = dme1737_read(data, + DME1737_REG_PWM_CONFIG(ix)); + data->pwm_min[ix] = dme1737_read(data, + DME1737_REG_PWM_MIN(ix)); + } + } + for (ix = 0; ix < ARRAY_SIZE(data->pwm_rr); ix++) { + data->pwm_rr[ix] = dme1737_read(data, + DME1737_REG_PWM_RR(ix)); + } + + /* Thermal zone registers */ + for (ix = 0; ix < ARRAY_SIZE(data->zone_low); ix++) { + /* Skip reading registers if zone3 is not present */ + if ((ix == 2) && !(data->has_features & HAS_ZONE3)) + continue; + /* sch5127 zone2 registers are special */ + if ((ix == 1) && (data->type == sch5127)) { + data->zone_low[1] = dme1737_read(data, + DME1737_REG_ZONE_LOW(2)); + data->zone_abs[1] = dme1737_read(data, + DME1737_REG_ZONE_ABS(2)); + } else { + data->zone_low[ix] = dme1737_read(data, + DME1737_REG_ZONE_LOW(ix)); + data->zone_abs[ix] = dme1737_read(data, + DME1737_REG_ZONE_ABS(ix)); + } + } + if (data->has_features & HAS_ZONE_HYST) { + for (ix = 0; ix < ARRAY_SIZE(data->zone_hyst); ix++) { + data->zone_hyst[ix] = dme1737_read(data, + DME1737_REG_ZONE_HYST(ix)); + } + } + + /* Alarm registers */ + data->alarms = dme1737_read(data, + DME1737_REG_ALARM1); + /* + * Bit 7 tells us if the other alarm registers are non-zero and + * therefore also need to be read + */ + if (data->alarms & 0x80) { + data->alarms |= dme1737_read(data, + DME1737_REG_ALARM2) << 8; + data->alarms |= dme1737_read(data, + DME1737_REG_ALARM3) << 16; + } + + /* + * The ISA chips require explicit clearing of alarm bits. + * Don't worry, an alarm will come back if the condition + * that causes it still exists + */ + if (!data->client) { + if (data->alarms & 0xff0000) + dme1737_write(data, DME1737_REG_ALARM3, 0xff); + if (data->alarms & 0xff00) + dme1737_write(data, DME1737_REG_ALARM2, 0xff); + if (data->alarms & 0xff) + dme1737_write(data, DME1737_REG_ALARM1, 0xff); + } + + data->last_update = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* --------------------------------------------------------------------- + * Voltage sysfs attributes + * ix = [0-7] + * --------------------------------------------------------------------- */ + +#define SYS_IN_INPUT 0 +#define SYS_IN_MIN 1 +#define SYS_IN_MAX 2 +#define SYS_IN_ALARM 3 + +static ssize_t show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dme1737_data *data = dme1737_update_device(dev); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int res; + + switch (fn) { + case SYS_IN_INPUT: + res = IN_FROM_REG(data->in[ix], data->in_nominal[ix], 16); + break; + case SYS_IN_MIN: + res = IN_FROM_REG(data->in_min[ix], data->in_nominal[ix], 8); + break; + case SYS_IN_MAX: + res = IN_FROM_REG(data->in_max[ix], data->in_nominal[ix], 8); + break; + case SYS_IN_ALARM: + res = (data->alarms >> DME1737_BIT_ALARM_IN[ix]) & 0x01; + break; + default: + res = 0; + dev_dbg(dev, "Unknown function %d.\n", fn); + } + + return sprintf(buf, "%d\n", res); +} + +static ssize_t set_in(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dme1737_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + switch (fn) { + case SYS_IN_MIN: + data->in_min[ix] = IN_TO_REG(val, data->in_nominal[ix]); + dme1737_write(data, DME1737_REG_IN_MIN(ix), + data->in_min[ix]); + break; + case SYS_IN_MAX: + data->in_max[ix] = IN_TO_REG(val, data->in_nominal[ix]); + dme1737_write(data, DME1737_REG_IN_MAX(ix), + data->in_max[ix]); + break; + default: + dev_dbg(dev, "Unknown function %d.\n", fn); + } + mutex_unlock(&data->update_lock); + + return count; +} + +/* --------------------------------------------------------------------- + * Temperature sysfs attributes + * ix = [0-2] + * --------------------------------------------------------------------- */ + +#define SYS_TEMP_INPUT 0 +#define SYS_TEMP_MIN 1 +#define SYS_TEMP_MAX 2 +#define SYS_TEMP_OFFSET 3 +#define SYS_TEMP_ALARM 4 +#define SYS_TEMP_FAULT 5 + +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dme1737_data *data = dme1737_update_device(dev); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int res; + + switch (fn) { + case SYS_TEMP_INPUT: + res = TEMP_FROM_REG(data->temp[ix], 16); + break; + case SYS_TEMP_MIN: + res = TEMP_FROM_REG(data->temp_min[ix], 8); + break; + case SYS_TEMP_MAX: + res = TEMP_FROM_REG(data->temp_max[ix], 8); + break; + case SYS_TEMP_OFFSET: + res = TEMP_FROM_REG(data->temp_offset[ix], 8); + break; + case SYS_TEMP_ALARM: + res = (data->alarms >> DME1737_BIT_ALARM_TEMP[ix]) & 0x01; + break; + case SYS_TEMP_FAULT: + res = (((u16)data->temp[ix] & 0xff00) == 0x8000); + break; + default: + res = 0; + dev_dbg(dev, "Unknown function %d.\n", fn); + } + + return sprintf(buf, "%d\n", res); +} + +static ssize_t set_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dme1737_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + switch (fn) { + case SYS_TEMP_MIN: + data->temp_min[ix] = TEMP_TO_REG(val); + dme1737_write(data, DME1737_REG_TEMP_MIN(ix), + data->temp_min[ix]); + break; + case SYS_TEMP_MAX: + data->temp_max[ix] = TEMP_TO_REG(val); + dme1737_write(data, DME1737_REG_TEMP_MAX(ix), + data->temp_max[ix]); + break; + case SYS_TEMP_OFFSET: + data->temp_offset[ix] = TEMP_TO_REG(val); + dme1737_write(data, DME1737_REG_TEMP_OFFSET(ix), + data->temp_offset[ix]); + break; + default: + dev_dbg(dev, "Unknown function %d.\n", fn); + } + mutex_unlock(&data->update_lock); + + return count; +} + +/* --------------------------------------------------------------------- + * Zone sysfs attributes + * ix = [0-2] + * --------------------------------------------------------------------- */ + +#define SYS_ZONE_AUTO_CHANNELS_TEMP 0 +#define SYS_ZONE_AUTO_POINT1_TEMP_HYST 1 +#define SYS_ZONE_AUTO_POINT1_TEMP 2 +#define SYS_ZONE_AUTO_POINT2_TEMP 3 +#define SYS_ZONE_AUTO_POINT3_TEMP 4 + +static ssize_t show_zone(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dme1737_data *data = dme1737_update_device(dev); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int res; + + switch (fn) { + case SYS_ZONE_AUTO_CHANNELS_TEMP: + /* check config2 for non-standard temp-to-zone mapping */ + if ((ix == 1) && (data->config2 & 0x02)) + res = 4; + else + res = 1 << ix; + break; + case SYS_ZONE_AUTO_POINT1_TEMP_HYST: + res = TEMP_FROM_REG(data->zone_low[ix], 8) - + TEMP_HYST_FROM_REG(data->zone_hyst[ix == 2], ix); + break; + case SYS_ZONE_AUTO_POINT1_TEMP: + res = TEMP_FROM_REG(data->zone_low[ix], 8); + break; + case SYS_ZONE_AUTO_POINT2_TEMP: + /* pwm_freq holds the temp range bits in the upper nibble */ + res = TEMP_FROM_REG(data->zone_low[ix], 8) + + TEMP_RANGE_FROM_REG(data->pwm_freq[ix]); + break; + case SYS_ZONE_AUTO_POINT3_TEMP: + res = TEMP_FROM_REG(data->zone_abs[ix], 8); + break; + default: + res = 0; + dev_dbg(dev, "Unknown function %d.\n", fn); + } + + return sprintf(buf, "%d\n", res); +} + +static ssize_t set_zone(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dme1737_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + long val; + int temp; + int err; + u8 reg; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + switch (fn) { + case SYS_ZONE_AUTO_POINT1_TEMP_HYST: + /* Refresh the cache */ + data->zone_low[ix] = dme1737_read(data, + DME1737_REG_ZONE_LOW(ix)); + /* Modify the temp hyst value */ + temp = TEMP_FROM_REG(data->zone_low[ix], 8); + reg = dme1737_read(data, DME1737_REG_ZONE_HYST(ix == 2)); + data->zone_hyst[ix == 2] = TEMP_HYST_TO_REG(temp, val, ix, reg); + dme1737_write(data, DME1737_REG_ZONE_HYST(ix == 2), + data->zone_hyst[ix == 2]); + break; + case SYS_ZONE_AUTO_POINT1_TEMP: + data->zone_low[ix] = TEMP_TO_REG(val); + dme1737_write(data, DME1737_REG_ZONE_LOW(ix), + data->zone_low[ix]); + break; + case SYS_ZONE_AUTO_POINT2_TEMP: + /* Refresh the cache */ + data->zone_low[ix] = dme1737_read(data, + DME1737_REG_ZONE_LOW(ix)); + /* + * Modify the temp range value (which is stored in the upper + * nibble of the pwm_freq register) + */ + temp = TEMP_FROM_REG(data->zone_low[ix], 8); + val = clamp_val(val, temp, temp + 80000); + reg = dme1737_read(data, DME1737_REG_PWM_FREQ(ix)); + data->pwm_freq[ix] = TEMP_RANGE_TO_REG(val - temp, reg); + dme1737_write(data, DME1737_REG_PWM_FREQ(ix), + data->pwm_freq[ix]); + break; + case SYS_ZONE_AUTO_POINT3_TEMP: + data->zone_abs[ix] = TEMP_TO_REG(val); + dme1737_write(data, DME1737_REG_ZONE_ABS(ix), + data->zone_abs[ix]); + break; + default: + dev_dbg(dev, "Unknown function %d.\n", fn); + } + mutex_unlock(&data->update_lock); + + return count; +} + +/* --------------------------------------------------------------------- + * Fan sysfs attributes + * ix = [0-5] + * --------------------------------------------------------------------- */ + +#define SYS_FAN_INPUT 0 +#define SYS_FAN_MIN 1 +#define SYS_FAN_MAX 2 +#define SYS_FAN_ALARM 3 +#define SYS_FAN_TYPE 4 + +static ssize_t show_fan(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dme1737_data *data = dme1737_update_device(dev); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int res; + + switch (fn) { + case SYS_FAN_INPUT: + res = FAN_FROM_REG(data->fan[ix], + ix < 4 ? 0 : + FAN_TPC_FROM_REG(data->fan_opt[ix])); + break; + case SYS_FAN_MIN: + res = FAN_FROM_REG(data->fan_min[ix], + ix < 4 ? 0 : + FAN_TPC_FROM_REG(data->fan_opt[ix])); + break; + case SYS_FAN_MAX: + /* only valid for fan[5-6] */ + res = FAN_MAX_FROM_REG(data->fan_max[ix - 4]); + break; + case SYS_FAN_ALARM: + res = (data->alarms >> DME1737_BIT_ALARM_FAN[ix]) & 0x01; + break; + case SYS_FAN_TYPE: + /* only valid for fan[1-4] */ + res = FAN_TYPE_FROM_REG(data->fan_opt[ix]); + break; + default: + res = 0; + dev_dbg(dev, "Unknown function %d.\n", fn); + } + + return sprintf(buf, "%d\n", res); +} + +static ssize_t set_fan(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dme1737_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + switch (fn) { + case SYS_FAN_MIN: + if (ix < 4) { + data->fan_min[ix] = FAN_TO_REG(val, 0); + } else { + /* Refresh the cache */ + data->fan_opt[ix] = dme1737_read(data, + DME1737_REG_FAN_OPT(ix)); + /* Modify the fan min value */ + data->fan_min[ix] = FAN_TO_REG(val, + FAN_TPC_FROM_REG(data->fan_opt[ix])); + } + dme1737_write(data, DME1737_REG_FAN_MIN(ix), + data->fan_min[ix] & 0xff); + dme1737_write(data, DME1737_REG_FAN_MIN(ix) + 1, + data->fan_min[ix] >> 8); + break; + case SYS_FAN_MAX: + /* Only valid for fan[5-6] */ + data->fan_max[ix - 4] = FAN_MAX_TO_REG(val); + dme1737_write(data, DME1737_REG_FAN_MAX(ix), + data->fan_max[ix - 4]); + break; + case SYS_FAN_TYPE: + /* Only valid for fan[1-4] */ + if (!(val == 1 || val == 2 || val == 4)) { + count = -EINVAL; + dev_warn(dev, + "Fan type value %ld not supported. Choose one of 1, 2, or 4.\n", + val); + goto exit; + } + data->fan_opt[ix] = FAN_TYPE_TO_REG(val, dme1737_read(data, + DME1737_REG_FAN_OPT(ix))); + dme1737_write(data, DME1737_REG_FAN_OPT(ix), + data->fan_opt[ix]); + break; + default: + dev_dbg(dev, "Unknown function %d.\n", fn); + } +exit: + mutex_unlock(&data->update_lock); + + return count; +} + +/* --------------------------------------------------------------------- + * PWM sysfs attributes + * ix = [0-4] + * --------------------------------------------------------------------- */ + +#define SYS_PWM 0 +#define SYS_PWM_FREQ 1 +#define SYS_PWM_ENABLE 2 +#define SYS_PWM_RAMP_RATE 3 +#define SYS_PWM_AUTO_CHANNELS_ZONE 4 +#define SYS_PWM_AUTO_PWM_MIN 5 +#define SYS_PWM_AUTO_POINT1_PWM 6 +#define SYS_PWM_AUTO_POINT2_PWM 7 + +static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dme1737_data *data = dme1737_update_device(dev); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int res; + + switch (fn) { + case SYS_PWM: + if (PWM_EN_FROM_REG(data->pwm_config[ix]) == 0) + res = 255; + else + res = data->pwm[ix]; + break; + case SYS_PWM_FREQ: + res = PWM_FREQ_FROM_REG(data->pwm_freq[ix]); + break; + case SYS_PWM_ENABLE: + if (ix >= 3) + res = 1; /* pwm[5-6] hard-wired to manual mode */ + else + res = PWM_EN_FROM_REG(data->pwm_config[ix]); + break; + case SYS_PWM_RAMP_RATE: + /* Only valid for pwm[1-3] */ + res = PWM_RR_FROM_REG(data->pwm_rr[ix > 0], ix); + break; + case SYS_PWM_AUTO_CHANNELS_ZONE: + /* Only valid for pwm[1-3] */ + if (PWM_EN_FROM_REG(data->pwm_config[ix]) == 2) + res = PWM_ACZ_FROM_REG(data->pwm_config[ix]); + else + res = data->pwm_acz[ix]; + break; + case SYS_PWM_AUTO_PWM_MIN: + /* Only valid for pwm[1-3] */ + if (PWM_OFF_FROM_REG(data->pwm_rr[0], ix)) + res = data->pwm_min[ix]; + else + res = 0; + break; + case SYS_PWM_AUTO_POINT1_PWM: + /* Only valid for pwm[1-3] */ + res = data->pwm_min[ix]; + break; + case SYS_PWM_AUTO_POINT2_PWM: + /* Only valid for pwm[1-3] */ + res = 255; /* hard-wired */ + break; + default: + res = 0; + dev_dbg(dev, "Unknown function %d.\n", fn); + } + + return sprintf(buf, "%d\n", res); +} + +static struct attribute *dme1737_pwm_chmod_attr[]; +static void dme1737_chmod_file(struct device*, struct attribute*, umode_t); + +static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dme1737_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 + *sensor_attr_2 = to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + switch (fn) { + case SYS_PWM: + data->pwm[ix] = clamp_val(val, 0, 255); + dme1737_write(data, DME1737_REG_PWM(ix), data->pwm[ix]); + break; + case SYS_PWM_FREQ: + data->pwm_freq[ix] = PWM_FREQ_TO_REG(val, dme1737_read(data, + DME1737_REG_PWM_FREQ(ix))); + dme1737_write(data, DME1737_REG_PWM_FREQ(ix), + data->pwm_freq[ix]); + break; + case SYS_PWM_ENABLE: + /* Only valid for pwm[1-3] */ + if (val < 0 || val > 2) { + count = -EINVAL; + dev_warn(dev, + "PWM enable %ld not supported. Choose one of 0, 1, or 2.\n", + val); + goto exit; + } + /* Refresh the cache */ + data->pwm_config[ix] = dme1737_read(data, + DME1737_REG_PWM_CONFIG(ix)); + if (val == PWM_EN_FROM_REG(data->pwm_config[ix])) { + /* Bail out if no change */ + goto exit; + } + /* Do some housekeeping if we are currently in auto mode */ + if (PWM_EN_FROM_REG(data->pwm_config[ix]) == 2) { + /* Save the current zone channel assignment */ + data->pwm_acz[ix] = PWM_ACZ_FROM_REG( + data->pwm_config[ix]); + /* Save the current ramp rate state and disable it */ + data->pwm_rr[ix > 0] = dme1737_read(data, + DME1737_REG_PWM_RR(ix > 0)); + data->pwm_rr_en &= ~(1 << ix); + if (PWM_RR_EN_FROM_REG(data->pwm_rr[ix > 0], ix)) { + data->pwm_rr_en |= (1 << ix); + data->pwm_rr[ix > 0] = PWM_RR_EN_TO_REG(0, ix, + data->pwm_rr[ix > 0]); + dme1737_write(data, + DME1737_REG_PWM_RR(ix > 0), + data->pwm_rr[ix > 0]); + } + } + /* Set the new PWM mode */ + switch (val) { + case 0: + /* Change permissions of pwm[ix] to read-only */ + dme1737_chmod_file(dev, dme1737_pwm_chmod_attr[ix], + S_IRUGO); + /* Turn fan fully on */ + data->pwm_config[ix] = PWM_EN_TO_REG(0, + data->pwm_config[ix]); + dme1737_write(data, DME1737_REG_PWM_CONFIG(ix), + data->pwm_config[ix]); + break; + case 1: + /* Turn on manual mode */ + data->pwm_config[ix] = PWM_EN_TO_REG(1, + data->pwm_config[ix]); + dme1737_write(data, DME1737_REG_PWM_CONFIG(ix), + data->pwm_config[ix]); + /* Change permissions of pwm[ix] to read-writeable */ + dme1737_chmod_file(dev, dme1737_pwm_chmod_attr[ix], + S_IRUGO | S_IWUSR); + break; + case 2: + /* Change permissions of pwm[ix] to read-only */ + dme1737_chmod_file(dev, dme1737_pwm_chmod_attr[ix], + S_IRUGO); + /* + * Turn on auto mode using the saved zone channel + * assignment + */ + data->pwm_config[ix] = PWM_ACZ_TO_REG( + data->pwm_acz[ix], + data->pwm_config[ix]); + dme1737_write(data, DME1737_REG_PWM_CONFIG(ix), + data->pwm_config[ix]); + /* Enable PWM ramp rate if previously enabled */ + if (data->pwm_rr_en & (1 << ix)) { + data->pwm_rr[ix > 0] = PWM_RR_EN_TO_REG(1, ix, + dme1737_read(data, + DME1737_REG_PWM_RR(ix > 0))); + dme1737_write(data, + DME1737_REG_PWM_RR(ix > 0), + data->pwm_rr[ix > 0]); + } + break; + } + break; + case SYS_PWM_RAMP_RATE: + /* Only valid for pwm[1-3] */ + /* Refresh the cache */ + data->pwm_config[ix] = dme1737_read(data, + DME1737_REG_PWM_CONFIG(ix)); + data->pwm_rr[ix > 0] = dme1737_read(data, + DME1737_REG_PWM_RR(ix > 0)); + /* Set the ramp rate value */ + if (val > 0) { + data->pwm_rr[ix > 0] = PWM_RR_TO_REG(val, ix, + data->pwm_rr[ix > 0]); + } + /* + * Enable/disable the feature only if the associated PWM + * output is in automatic mode. + */ + if (PWM_EN_FROM_REG(data->pwm_config[ix]) == 2) { + data->pwm_rr[ix > 0] = PWM_RR_EN_TO_REG(val > 0, ix, + data->pwm_rr[ix > 0]); + } + dme1737_write(data, DME1737_REG_PWM_RR(ix > 0), + data->pwm_rr[ix > 0]); + break; + case SYS_PWM_AUTO_CHANNELS_ZONE: + /* Only valid for pwm[1-3] */ + if (!(val == 1 || val == 2 || val == 4 || + val == 6 || val == 7)) { + count = -EINVAL; + dev_warn(dev, + "PWM auto channels zone %ld not supported. Choose one of 1, 2, 4, 6, " + "or 7.\n", val); + goto exit; + } + /* Refresh the cache */ + data->pwm_config[ix] = dme1737_read(data, + DME1737_REG_PWM_CONFIG(ix)); + if (PWM_EN_FROM_REG(data->pwm_config[ix]) == 2) { + /* + * PWM is already in auto mode so update the temp + * channel assignment + */ + data->pwm_config[ix] = PWM_ACZ_TO_REG(val, + data->pwm_config[ix]); + dme1737_write(data, DME1737_REG_PWM_CONFIG(ix), + data->pwm_config[ix]); + } else { + /* + * PWM is not in auto mode so we save the temp + * channel assignment for later use + */ + data->pwm_acz[ix] = val; + } + break; + case SYS_PWM_AUTO_PWM_MIN: + /* Only valid for pwm[1-3] */ + /* Refresh the cache */ + data->pwm_min[ix] = dme1737_read(data, + DME1737_REG_PWM_MIN(ix)); + /* + * There are only 2 values supported for the auto_pwm_min + * value: 0 or auto_point1_pwm. So if the temperature drops + * below the auto_point1_temp_hyst value, the fan either turns + * off or runs at auto_point1_pwm duty-cycle. + */ + if (val > ((data->pwm_min[ix] + 1) / 2)) { + data->pwm_rr[0] = PWM_OFF_TO_REG(1, ix, + dme1737_read(data, + DME1737_REG_PWM_RR(0))); + } else { + data->pwm_rr[0] = PWM_OFF_TO_REG(0, ix, + dme1737_read(data, + DME1737_REG_PWM_RR(0))); + } + dme1737_write(data, DME1737_REG_PWM_RR(0), + data->pwm_rr[0]); + break; + case SYS_PWM_AUTO_POINT1_PWM: + /* Only valid for pwm[1-3] */ + data->pwm_min[ix] = clamp_val(val, 0, 255); + dme1737_write(data, DME1737_REG_PWM_MIN(ix), + data->pwm_min[ix]); + break; + default: + dev_dbg(dev, "Unknown function %d.\n", fn); + } +exit: + mutex_unlock(&data->update_lock); + + return count; +} + +/* --------------------------------------------------------------------- + * Miscellaneous sysfs attributes + * --------------------------------------------------------------------- */ + +static ssize_t vrm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct dme1737_data *data = i2c_get_clientdata(client); + + return sprintf(buf, "%d\n", data->vrm); +} + +static ssize_t vrm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dme1737_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 255) + return -EINVAL; + + data->vrm = val; + return count; +} + +static ssize_t cpu0_vid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dme1737_data *data = dme1737_update_device(dev); + + return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); +} + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dme1737_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} + +/* --------------------------------------------------------------------- + * Sysfs device attribute defines and structs + * --------------------------------------------------------------------- */ + +/* Voltages 0-7 */ + +#define SENSOR_DEVICE_ATTR_IN(ix) \ +static SENSOR_DEVICE_ATTR_2(in##ix##_input, S_IRUGO, \ + show_in, NULL, SYS_IN_INPUT, ix); \ +static SENSOR_DEVICE_ATTR_2(in##ix##_min, S_IRUGO | S_IWUSR, \ + show_in, set_in, SYS_IN_MIN, ix); \ +static SENSOR_DEVICE_ATTR_2(in##ix##_max, S_IRUGO | S_IWUSR, \ + show_in, set_in, SYS_IN_MAX, ix); \ +static SENSOR_DEVICE_ATTR_2(in##ix##_alarm, S_IRUGO, \ + show_in, NULL, SYS_IN_ALARM, ix) + +SENSOR_DEVICE_ATTR_IN(0); +SENSOR_DEVICE_ATTR_IN(1); +SENSOR_DEVICE_ATTR_IN(2); +SENSOR_DEVICE_ATTR_IN(3); +SENSOR_DEVICE_ATTR_IN(4); +SENSOR_DEVICE_ATTR_IN(5); +SENSOR_DEVICE_ATTR_IN(6); +SENSOR_DEVICE_ATTR_IN(7); + +/* Temperatures 1-3 */ + +#define SENSOR_DEVICE_ATTR_TEMP(ix) \ +static SENSOR_DEVICE_ATTR_2(temp##ix##_input, S_IRUGO, \ + show_temp, NULL, SYS_TEMP_INPUT, ix-1); \ +static SENSOR_DEVICE_ATTR_2(temp##ix##_min, S_IRUGO | S_IWUSR, \ + show_temp, set_temp, SYS_TEMP_MIN, ix-1); \ +static SENSOR_DEVICE_ATTR_2(temp##ix##_max, S_IRUGO | S_IWUSR, \ + show_temp, set_temp, SYS_TEMP_MAX, ix-1); \ +static SENSOR_DEVICE_ATTR_2(temp##ix##_offset, S_IRUGO, \ + show_temp, set_temp, SYS_TEMP_OFFSET, ix-1); \ +static SENSOR_DEVICE_ATTR_2(temp##ix##_alarm, S_IRUGO, \ + show_temp, NULL, SYS_TEMP_ALARM, ix-1); \ +static SENSOR_DEVICE_ATTR_2(temp##ix##_fault, S_IRUGO, \ + show_temp, NULL, SYS_TEMP_FAULT, ix-1) + +SENSOR_DEVICE_ATTR_TEMP(1); +SENSOR_DEVICE_ATTR_TEMP(2); +SENSOR_DEVICE_ATTR_TEMP(3); + +/* Zones 1-3 */ + +#define SENSOR_DEVICE_ATTR_ZONE(ix) \ +static SENSOR_DEVICE_ATTR_2(zone##ix##_auto_channels_temp, S_IRUGO, \ + show_zone, NULL, SYS_ZONE_AUTO_CHANNELS_TEMP, ix-1); \ +static SENSOR_DEVICE_ATTR_2(zone##ix##_auto_point1_temp_hyst, S_IRUGO, \ + show_zone, set_zone, SYS_ZONE_AUTO_POINT1_TEMP_HYST, ix-1); \ +static SENSOR_DEVICE_ATTR_2(zone##ix##_auto_point1_temp, S_IRUGO, \ + show_zone, set_zone, SYS_ZONE_AUTO_POINT1_TEMP, ix-1); \ +static SENSOR_DEVICE_ATTR_2(zone##ix##_auto_point2_temp, S_IRUGO, \ + show_zone, set_zone, SYS_ZONE_AUTO_POINT2_TEMP, ix-1); \ +static SENSOR_DEVICE_ATTR_2(zone##ix##_auto_point3_temp, S_IRUGO, \ + show_zone, set_zone, SYS_ZONE_AUTO_POINT3_TEMP, ix-1) + +SENSOR_DEVICE_ATTR_ZONE(1); +SENSOR_DEVICE_ATTR_ZONE(2); +SENSOR_DEVICE_ATTR_ZONE(3); + +/* Fans 1-4 */ + +#define SENSOR_DEVICE_ATTR_FAN_1TO4(ix) \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_input, S_IRUGO, \ + show_fan, NULL, SYS_FAN_INPUT, ix-1); \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_min, S_IRUGO | S_IWUSR, \ + show_fan, set_fan, SYS_FAN_MIN, ix-1); \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_alarm, S_IRUGO, \ + show_fan, NULL, SYS_FAN_ALARM, ix-1); \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_type, S_IRUGO | S_IWUSR, \ + show_fan, set_fan, SYS_FAN_TYPE, ix-1) + +SENSOR_DEVICE_ATTR_FAN_1TO4(1); +SENSOR_DEVICE_ATTR_FAN_1TO4(2); +SENSOR_DEVICE_ATTR_FAN_1TO4(3); +SENSOR_DEVICE_ATTR_FAN_1TO4(4); + +/* Fans 5-6 */ + +#define SENSOR_DEVICE_ATTR_FAN_5TO6(ix) \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_input, S_IRUGO, \ + show_fan, NULL, SYS_FAN_INPUT, ix-1); \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_min, S_IRUGO | S_IWUSR, \ + show_fan, set_fan, SYS_FAN_MIN, ix-1); \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_alarm, S_IRUGO, \ + show_fan, NULL, SYS_FAN_ALARM, ix-1); \ +static SENSOR_DEVICE_ATTR_2(fan##ix##_max, S_IRUGO | S_IWUSR, \ + show_fan, set_fan, SYS_FAN_MAX, ix-1) + +SENSOR_DEVICE_ATTR_FAN_5TO6(5); +SENSOR_DEVICE_ATTR_FAN_5TO6(6); + +/* PWMs 1-3 */ + +#define SENSOR_DEVICE_ATTR_PWM_1TO3(ix) \ +static SENSOR_DEVICE_ATTR_2(pwm##ix, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_freq, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM_FREQ, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_enable, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM_ENABLE, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_ramp_rate, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM_RAMP_RATE, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_auto_channels_zone, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM_AUTO_CHANNELS_ZONE, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_auto_pwm_min, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM_AUTO_PWM_MIN, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_auto_point1_pwm, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM_AUTO_POINT1_PWM, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_auto_point2_pwm, S_IRUGO, \ + show_pwm, NULL, SYS_PWM_AUTO_POINT2_PWM, ix-1) + +SENSOR_DEVICE_ATTR_PWM_1TO3(1); +SENSOR_DEVICE_ATTR_PWM_1TO3(2); +SENSOR_DEVICE_ATTR_PWM_1TO3(3); + +/* PWMs 5-6 */ + +#define SENSOR_DEVICE_ATTR_PWM_5TO6(ix) \ +static SENSOR_DEVICE_ATTR_2(pwm##ix, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_freq, S_IRUGO, \ + show_pwm, set_pwm, SYS_PWM_FREQ, ix-1); \ +static SENSOR_DEVICE_ATTR_2(pwm##ix##_enable, S_IRUGO, \ + show_pwm, NULL, SYS_PWM_ENABLE, ix-1) + +SENSOR_DEVICE_ATTR_PWM_5TO6(5); +SENSOR_DEVICE_ATTR_PWM_5TO6(6); + +/* Misc */ + +static DEVICE_ATTR_RW(vrm); +static DEVICE_ATTR_RO(cpu0_vid); +static DEVICE_ATTR_RO(name); /* for ISA devices */ + +/* + * This struct holds all the attributes that are always present and need to be + * created unconditionally. The attributes that need modification of their + * permissions are created read-only and write permissions are added or removed + * on the fly when required + */ +static struct attribute *dme1737_attr[] = { + /* Voltages */ + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in6_alarm.dev_attr.attr, + /* Temperatures */ + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + /* Zones */ + &sensor_dev_attr_zone1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_zone1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_zone1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_zone1_auto_channels_temp.dev_attr.attr, + &sensor_dev_attr_zone2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_zone2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_zone2_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_zone2_auto_channels_temp.dev_attr.attr, + NULL +}; + +static const struct attribute_group dme1737_group = { + .attrs = dme1737_attr, +}; + +/* + * The following struct holds temp offset attributes, which are not available + * in all chips. The following chips support them: + * DME1737, SCH311x + */ +static struct attribute *dme1737_temp_offset_attr[] = { + &sensor_dev_attr_temp1_offset.dev_attr.attr, + &sensor_dev_attr_temp2_offset.dev_attr.attr, + &sensor_dev_attr_temp3_offset.dev_attr.attr, + NULL +}; + +static const struct attribute_group dme1737_temp_offset_group = { + .attrs = dme1737_temp_offset_attr, +}; + +/* + * The following struct holds VID related attributes, which are not available + * in all chips. The following chips support them: + * DME1737 + */ +static struct attribute *dme1737_vid_attr[] = { + &dev_attr_vrm.attr, + &dev_attr_cpu0_vid.attr, + NULL +}; + +static const struct attribute_group dme1737_vid_group = { + .attrs = dme1737_vid_attr, +}; + +/* + * The following struct holds temp zone 3 related attributes, which are not + * available in all chips. The following chips support them: + * DME1737, SCH311x, SCH5027 + */ +static struct attribute *dme1737_zone3_attr[] = { + &sensor_dev_attr_zone3_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_zone3_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_zone3_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_zone3_auto_channels_temp.dev_attr.attr, + NULL +}; + +static const struct attribute_group dme1737_zone3_group = { + .attrs = dme1737_zone3_attr, +}; + + +/* + * The following struct holds temp zone hysteresis related attributes, which + * are not available in all chips. The following chips support them: + * DME1737, SCH311x + */ +static struct attribute *dme1737_zone_hyst_attr[] = { + &sensor_dev_attr_zone1_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_zone2_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_zone3_auto_point1_temp_hyst.dev_attr.attr, + NULL +}; + +static const struct attribute_group dme1737_zone_hyst_group = { + .attrs = dme1737_zone_hyst_attr, +}; + +/* + * The following struct holds voltage in7 related attributes, which + * are not available in all chips. The following chips support them: + * SCH5127 + */ +static struct attribute *dme1737_in7_attr[] = { + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in7_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group dme1737_in7_group = { + .attrs = dme1737_in7_attr, +}; + +/* + * The following structs hold the PWM attributes, some of which are optional. + * Their creation depends on the chip configuration which is determined during + * module load. + */ +static struct attribute *dme1737_pwm1_attr[] = { + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_freq.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_ramp_rate.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_channels_zone.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + NULL +}; +static struct attribute *dme1737_pwm2_attr[] = { + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm2_freq.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm2_ramp_rate.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_channels_zone.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr, + NULL +}; +static struct attribute *dme1737_pwm3_attr[] = { + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm3_freq.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_pwm3_ramp_rate.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_channels_zone.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr, + NULL +}; +static struct attribute *dme1737_pwm5_attr[] = { + &sensor_dev_attr_pwm5.dev_attr.attr, + &sensor_dev_attr_pwm5_freq.dev_attr.attr, + &sensor_dev_attr_pwm5_enable.dev_attr.attr, + NULL +}; +static struct attribute *dme1737_pwm6_attr[] = { + &sensor_dev_attr_pwm6.dev_attr.attr, + &sensor_dev_attr_pwm6_freq.dev_attr.attr, + &sensor_dev_attr_pwm6_enable.dev_attr.attr, + NULL +}; + +static const struct attribute_group dme1737_pwm_group[] = { + { .attrs = dme1737_pwm1_attr }, + { .attrs = dme1737_pwm2_attr }, + { .attrs = dme1737_pwm3_attr }, + { .attrs = NULL }, + { .attrs = dme1737_pwm5_attr }, + { .attrs = dme1737_pwm6_attr }, +}; + +/* + * The following struct holds auto PWM min attributes, which are not available + * in all chips. Their creation depends on the chip type which is determined + * during module load. + */ +static struct attribute *dme1737_auto_pwm_min_attr[] = { + &sensor_dev_attr_pwm1_auto_pwm_min.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_pwm_min.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_pwm_min.dev_attr.attr, +}; + +/* + * The following structs hold the fan attributes, some of which are optional. + * Their creation depends on the chip configuration which is determined during + * module load. + */ +static struct attribute *dme1737_fan1_attr[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_type.dev_attr.attr, + NULL +}; +static struct attribute *dme1737_fan2_attr[] = { + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_type.dev_attr.attr, + NULL +}; +static struct attribute *dme1737_fan3_attr[] = { + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_type.dev_attr.attr, + NULL +}; +static struct attribute *dme1737_fan4_attr[] = { + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan4_min.dev_attr.attr, + &sensor_dev_attr_fan4_alarm.dev_attr.attr, + &sensor_dev_attr_fan4_type.dev_attr.attr, + NULL +}; +static struct attribute *dme1737_fan5_attr[] = { + &sensor_dev_attr_fan5_input.dev_attr.attr, + &sensor_dev_attr_fan5_min.dev_attr.attr, + &sensor_dev_attr_fan5_alarm.dev_attr.attr, + &sensor_dev_attr_fan5_max.dev_attr.attr, + NULL +}; +static struct attribute *dme1737_fan6_attr[] = { + &sensor_dev_attr_fan6_input.dev_attr.attr, + &sensor_dev_attr_fan6_min.dev_attr.attr, + &sensor_dev_attr_fan6_alarm.dev_attr.attr, + &sensor_dev_attr_fan6_max.dev_attr.attr, + NULL +}; + +static const struct attribute_group dme1737_fan_group[] = { + { .attrs = dme1737_fan1_attr }, + { .attrs = dme1737_fan2_attr }, + { .attrs = dme1737_fan3_attr }, + { .attrs = dme1737_fan4_attr }, + { .attrs = dme1737_fan5_attr }, + { .attrs = dme1737_fan6_attr }, +}; + +/* + * The permissions of the following zone attributes are changed to read- + * writeable if the chip is *not* locked. Otherwise they stay read-only. + */ +static struct attribute *dme1737_zone_chmod_attr[] = { + &sensor_dev_attr_zone1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_zone1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_zone1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_zone2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_zone2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_zone2_auto_point3_temp.dev_attr.attr, + NULL +}; + +static const struct attribute_group dme1737_zone_chmod_group = { + .attrs = dme1737_zone_chmod_attr, +}; + + +/* + * The permissions of the following zone 3 attributes are changed to read- + * writeable if the chip is *not* locked. Otherwise they stay read-only. + */ +static struct attribute *dme1737_zone3_chmod_attr[] = { + &sensor_dev_attr_zone3_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_zone3_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_zone3_auto_point3_temp.dev_attr.attr, + NULL +}; + +static const struct attribute_group dme1737_zone3_chmod_group = { + .attrs = dme1737_zone3_chmod_attr, +}; + +/* + * The permissions of the following PWM attributes are changed to read- + * writeable if the chip is *not* locked and the respective PWM is available. + * Otherwise they stay read-only. + */ +static struct attribute *dme1737_pwm1_chmod_attr[] = { + &sensor_dev_attr_pwm1_freq.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_ramp_rate.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_channels_zone.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + NULL +}; +static struct attribute *dme1737_pwm2_chmod_attr[] = { + &sensor_dev_attr_pwm2_freq.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm2_ramp_rate.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_channels_zone.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, + NULL +}; +static struct attribute *dme1737_pwm3_chmod_attr[] = { + &sensor_dev_attr_pwm3_freq.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_pwm3_ramp_rate.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_channels_zone.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr, + NULL +}; +static struct attribute *dme1737_pwm5_chmod_attr[] = { + &sensor_dev_attr_pwm5.dev_attr.attr, + &sensor_dev_attr_pwm5_freq.dev_attr.attr, + NULL +}; +static struct attribute *dme1737_pwm6_chmod_attr[] = { + &sensor_dev_attr_pwm6.dev_attr.attr, + &sensor_dev_attr_pwm6_freq.dev_attr.attr, + NULL +}; + +static const struct attribute_group dme1737_pwm_chmod_group[] = { + { .attrs = dme1737_pwm1_chmod_attr }, + { .attrs = dme1737_pwm2_chmod_attr }, + { .attrs = dme1737_pwm3_chmod_attr }, + { .attrs = NULL }, + { .attrs = dme1737_pwm5_chmod_attr }, + { .attrs = dme1737_pwm6_chmod_attr }, +}; + +/* + * Pwm[1-3] are read-writeable if the associated pwm is in manual mode and the + * chip is not locked. Otherwise they are read-only. + */ +static struct attribute *dme1737_pwm_chmod_attr[] = { + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, +}; + +/* --------------------------------------------------------------------- + * Super-IO functions + * --------------------------------------------------------------------- */ + +static inline void dme1737_sio_enter(int sio_cip) +{ + outb(0x55, sio_cip); +} + +static inline void dme1737_sio_exit(int sio_cip) +{ + outb(0xaa, sio_cip); +} + +static inline int dme1737_sio_inb(int sio_cip, int reg) +{ + outb(reg, sio_cip); + return inb(sio_cip + 1); +} + +static inline void dme1737_sio_outb(int sio_cip, int reg, int val) +{ + outb(reg, sio_cip); + outb(val, sio_cip + 1); +} + +/* --------------------------------------------------------------------- + * Device initialization + * --------------------------------------------------------------------- */ + +static int dme1737_i2c_get_features(int, struct dme1737_data*); + +static void dme1737_chmod_file(struct device *dev, + struct attribute *attr, umode_t mode) +{ + if (sysfs_chmod_file(&dev->kobj, attr, mode)) { + dev_warn(dev, "Failed to change permissions of %s.\n", + attr->name); + } +} + +static void dme1737_chmod_group(struct device *dev, + const struct attribute_group *group, + umode_t mode) +{ + struct attribute **attr; + + for (attr = group->attrs; *attr; attr++) + dme1737_chmod_file(dev, *attr, mode); +} + +static void dme1737_remove_files(struct device *dev) +{ + struct dme1737_data *data = dev_get_drvdata(dev); + int ix; + + for (ix = 0; ix < ARRAY_SIZE(dme1737_fan_group); ix++) { + if (data->has_features & HAS_FAN(ix)) { + sysfs_remove_group(&dev->kobj, + &dme1737_fan_group[ix]); + } + } + + for (ix = 0; ix < ARRAY_SIZE(dme1737_pwm_group); ix++) { + if (data->has_features & HAS_PWM(ix)) { + sysfs_remove_group(&dev->kobj, + &dme1737_pwm_group[ix]); + if ((data->has_features & HAS_PWM_MIN) && ix < 3) { + sysfs_remove_file(&dev->kobj, + dme1737_auto_pwm_min_attr[ix]); + } + } + } + + if (data->has_features & HAS_TEMP_OFFSET) + sysfs_remove_group(&dev->kobj, &dme1737_temp_offset_group); + if (data->has_features & HAS_VID) + sysfs_remove_group(&dev->kobj, &dme1737_vid_group); + if (data->has_features & HAS_ZONE3) + sysfs_remove_group(&dev->kobj, &dme1737_zone3_group); + if (data->has_features & HAS_ZONE_HYST) + sysfs_remove_group(&dev->kobj, &dme1737_zone_hyst_group); + if (data->has_features & HAS_IN7) + sysfs_remove_group(&dev->kobj, &dme1737_in7_group); + sysfs_remove_group(&dev->kobj, &dme1737_group); + + if (!data->client) + sysfs_remove_file(&dev->kobj, &dev_attr_name.attr); +} + +static int dme1737_create_files(struct device *dev) +{ + struct dme1737_data *data = dev_get_drvdata(dev); + int err, ix; + + /* Create a name attribute for ISA devices */ + if (!data->client) { + err = sysfs_create_file(&dev->kobj, &dev_attr_name.attr); + if (err) + goto exit; + } + + /* Create standard sysfs attributes */ + err = sysfs_create_group(&dev->kobj, &dme1737_group); + if (err) + goto exit_remove; + + /* Create chip-dependent sysfs attributes */ + if (data->has_features & HAS_TEMP_OFFSET) { + err = sysfs_create_group(&dev->kobj, + &dme1737_temp_offset_group); + if (err) + goto exit_remove; + } + if (data->has_features & HAS_VID) { + err = sysfs_create_group(&dev->kobj, &dme1737_vid_group); + if (err) + goto exit_remove; + } + if (data->has_features & HAS_ZONE3) { + err = sysfs_create_group(&dev->kobj, &dme1737_zone3_group); + if (err) + goto exit_remove; + } + if (data->has_features & HAS_ZONE_HYST) { + err = sysfs_create_group(&dev->kobj, &dme1737_zone_hyst_group); + if (err) + goto exit_remove; + } + if (data->has_features & HAS_IN7) { + err = sysfs_create_group(&dev->kobj, &dme1737_in7_group); + if (err) + goto exit_remove; + } + + /* Create fan sysfs attributes */ + for (ix = 0; ix < ARRAY_SIZE(dme1737_fan_group); ix++) { + if (data->has_features & HAS_FAN(ix)) { + err = sysfs_create_group(&dev->kobj, + &dme1737_fan_group[ix]); + if (err) + goto exit_remove; + } + } + + /* Create PWM sysfs attributes */ + for (ix = 0; ix < ARRAY_SIZE(dme1737_pwm_group); ix++) { + if (data->has_features & HAS_PWM(ix)) { + err = sysfs_create_group(&dev->kobj, + &dme1737_pwm_group[ix]); + if (err) + goto exit_remove; + if ((data->has_features & HAS_PWM_MIN) && (ix < 3)) { + err = sysfs_create_file(&dev->kobj, + dme1737_auto_pwm_min_attr[ix]); + if (err) + goto exit_remove; + } + } + } + + /* + * Inform if the device is locked. Otherwise change the permissions of + * selected attributes from read-only to read-writeable. + */ + if (data->config & 0x02) { + dev_info(dev, + "Device is locked. Some attributes will be read-only.\n"); + } else { + /* Change permissions of zone sysfs attributes */ + dme1737_chmod_group(dev, &dme1737_zone_chmod_group, + S_IRUGO | S_IWUSR); + + /* Change permissions of chip-dependent sysfs attributes */ + if (data->has_features & HAS_TEMP_OFFSET) { + dme1737_chmod_group(dev, &dme1737_temp_offset_group, + S_IRUGO | S_IWUSR); + } + if (data->has_features & HAS_ZONE3) { + dme1737_chmod_group(dev, &dme1737_zone3_chmod_group, + S_IRUGO | S_IWUSR); + } + if (data->has_features & HAS_ZONE_HYST) { + dme1737_chmod_group(dev, &dme1737_zone_hyst_group, + S_IRUGO | S_IWUSR); + } + + /* Change permissions of PWM sysfs attributes */ + for (ix = 0; ix < ARRAY_SIZE(dme1737_pwm_chmod_group); ix++) { + if (data->has_features & HAS_PWM(ix)) { + dme1737_chmod_group(dev, + &dme1737_pwm_chmod_group[ix], + S_IRUGO | S_IWUSR); + if ((data->has_features & HAS_PWM_MIN) && + ix < 3) { + dme1737_chmod_file(dev, + dme1737_auto_pwm_min_attr[ix], + S_IRUGO | S_IWUSR); + } + } + } + + /* Change permissions of pwm[1-3] if in manual mode */ + for (ix = 0; ix < 3; ix++) { + if ((data->has_features & HAS_PWM(ix)) && + (PWM_EN_FROM_REG(data->pwm_config[ix]) == 1)) { + dme1737_chmod_file(dev, + dme1737_pwm_chmod_attr[ix], + S_IRUGO | S_IWUSR); + } + } + } + + return 0; + +exit_remove: + dme1737_remove_files(dev); +exit: + return err; +} + +static int dme1737_init_device(struct device *dev) +{ + struct dme1737_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ix; + u8 reg; + + /* Point to the right nominal voltages array */ + data->in_nominal = IN_NOMINAL(data->type); + + data->config = dme1737_read(data, DME1737_REG_CONFIG); + /* Inform if part is not monitoring/started */ + if (!(data->config & 0x01)) { + if (!force_start) { + dev_err(dev, + "Device is not monitoring. Use the force_start load parameter to override.\n"); + return -EFAULT; + } + + /* Force monitoring */ + data->config |= 0x01; + dme1737_write(data, DME1737_REG_CONFIG, data->config); + } + /* Inform if part is not ready */ + if (!(data->config & 0x04)) { + dev_err(dev, "Device is not ready.\n"); + return -EFAULT; + } + + /* + * Determine which optional fan and pwm features are enabled (only + * valid for I2C devices) + */ + if (client) { /* I2C chip */ + data->config2 = dme1737_read(data, DME1737_REG_CONFIG2); + /* Check if optional fan3 input is enabled */ + if (data->config2 & 0x04) + data->has_features |= HAS_FAN(2); + + /* + * Fan4 and pwm3 are only available if the client's I2C address + * is the default 0x2e. Otherwise the I/Os associated with + * these functions are used for addr enable/select. + */ + if (client->addr == 0x2e) + data->has_features |= HAS_FAN(3) | HAS_PWM(2); + + /* + * Determine which of the optional fan[5-6] and pwm[5-6] + * features are enabled. For this, we need to query the runtime + * registers through the Super-IO LPC interface. Try both + * config ports 0x2e and 0x4e. + */ + if (dme1737_i2c_get_features(0x2e, data) && + dme1737_i2c_get_features(0x4e, data)) { + dev_warn(dev, + "Failed to query Super-IO for optional features.\n"); + } + } + + /* Fan[1-2] and pwm[1-2] are present in all chips */ + data->has_features |= HAS_FAN(0) | HAS_FAN(1) | HAS_PWM(0) | HAS_PWM(1); + + /* Chip-dependent features */ + switch (data->type) { + case dme1737: + data->has_features |= HAS_TEMP_OFFSET | HAS_VID | HAS_ZONE3 | + HAS_ZONE_HYST | HAS_PWM_MIN; + break; + case sch311x: + data->has_features |= HAS_TEMP_OFFSET | HAS_ZONE3 | + HAS_ZONE_HYST | HAS_PWM_MIN | HAS_FAN(2) | HAS_PWM(2); + break; + case sch5027: + data->has_features |= HAS_ZONE3; + break; + case sch5127: + data->has_features |= HAS_FAN(2) | HAS_PWM(2) | HAS_IN7; + break; + default: + break; + } + + dev_info(dev, + "Optional features: pwm3=%s, pwm5=%s, pwm6=%s, fan3=%s, fan4=%s, fan5=%s, fan6=%s.\n", + (data->has_features & HAS_PWM(2)) ? "yes" : "no", + (data->has_features & HAS_PWM(4)) ? "yes" : "no", + (data->has_features & HAS_PWM(5)) ? "yes" : "no", + (data->has_features & HAS_FAN(2)) ? "yes" : "no", + (data->has_features & HAS_FAN(3)) ? "yes" : "no", + (data->has_features & HAS_FAN(4)) ? "yes" : "no", + (data->has_features & HAS_FAN(5)) ? "yes" : "no"); + + reg = dme1737_read(data, DME1737_REG_TACH_PWM); + /* Inform if fan-to-pwm mapping differs from the default */ + if (client && reg != 0xa4) { /* I2C chip */ + dev_warn(dev, + "Non-standard fan to pwm mapping: fan1->pwm%d, fan2->pwm%d, fan3->pwm%d, fan4->pwm%d. %s\n", + (reg & 0x03) + 1, ((reg >> 2) & 0x03) + 1, + ((reg >> 4) & 0x03) + 1, ((reg >> 6) & 0x03) + 1, + DO_REPORT); + } else if (!client && reg != 0x24) { /* ISA chip */ + dev_warn(dev, + "Non-standard fan to pwm mapping: fan1->pwm%d, fan2->pwm%d, fan3->pwm%d. %s\n", + (reg & 0x03) + 1, ((reg >> 2) & 0x03) + 1, + ((reg >> 4) & 0x03) + 1, DO_REPORT); + } + + /* + * Switch pwm[1-3] to manual mode if they are currently disabled and + * set the duty-cycles to 0% (which is identical to the PWMs being + * disabled). + */ + if (!(data->config & 0x02)) { + for (ix = 0; ix < 3; ix++) { + data->pwm_config[ix] = dme1737_read(data, + DME1737_REG_PWM_CONFIG(ix)); + if ((data->has_features & HAS_PWM(ix)) && + (PWM_EN_FROM_REG(data->pwm_config[ix]) == -1)) { + dev_info(dev, + "Switching pwm%d to manual mode.\n", + ix + 1); + data->pwm_config[ix] = PWM_EN_TO_REG(1, + data->pwm_config[ix]); + dme1737_write(data, DME1737_REG_PWM(ix), 0); + dme1737_write(data, + DME1737_REG_PWM_CONFIG(ix), + data->pwm_config[ix]); + } + } + } + + /* Initialize the default PWM auto channels zone (acz) assignments */ + data->pwm_acz[0] = 1; /* pwm1 -> zone1 */ + data->pwm_acz[1] = 2; /* pwm2 -> zone2 */ + data->pwm_acz[2] = 4; /* pwm3 -> zone3 */ + + /* Set VRM */ + if (data->has_features & HAS_VID) + data->vrm = vid_which_vrm(); + + return 0; +} + +/* --------------------------------------------------------------------- + * I2C device detection and registration + * --------------------------------------------------------------------- */ + +static struct i2c_driver dme1737_i2c_driver; + +static int dme1737_i2c_get_features(int sio_cip, struct dme1737_data *data) +{ + int err = 0, reg; + u16 addr; + + dme1737_sio_enter(sio_cip); + + /* + * Check device ID + * We currently know about two kinds of DME1737 and SCH5027. + */ + reg = force_id ? force_id : dme1737_sio_inb(sio_cip, 0x20); + if (!(reg == DME1737_ID_1 || reg == DME1737_ID_2 || + reg == SCH5027_ID)) { + err = -ENODEV; + goto exit; + } + + /* Select logical device A (runtime registers) */ + dme1737_sio_outb(sio_cip, 0x07, 0x0a); + + /* Get the base address of the runtime registers */ + addr = (dme1737_sio_inb(sio_cip, 0x60) << 8) | + dme1737_sio_inb(sio_cip, 0x61); + if (!addr) { + err = -ENODEV; + goto exit; + } + + /* + * Read the runtime registers to determine which optional features + * are enabled and available. Bits [3:2] of registers 0x43-0x46 are set + * to '10' if the respective feature is enabled. + */ + if ((inb(addr + 0x43) & 0x0c) == 0x08) /* fan6 */ + data->has_features |= HAS_FAN(5); + if ((inb(addr + 0x44) & 0x0c) == 0x08) /* pwm6 */ + data->has_features |= HAS_PWM(5); + if ((inb(addr + 0x45) & 0x0c) == 0x08) /* fan5 */ + data->has_features |= HAS_FAN(4); + if ((inb(addr + 0x46) & 0x0c) == 0x08) /* pwm5 */ + data->has_features |= HAS_PWM(4); + +exit: + dme1737_sio_exit(sio_cip); + + return err; +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int dme1737_i2c_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + struct device *dev = &adapter->dev; + u8 company, verstep = 0; + const char *name; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + company = i2c_smbus_read_byte_data(client, DME1737_REG_COMPANY); + verstep = i2c_smbus_read_byte_data(client, DME1737_REG_VERSTEP); + + if (company == DME1737_COMPANY_SMSC && + verstep == SCH5027_VERSTEP) { + name = "sch5027"; + } else if (company == DME1737_COMPANY_SMSC && + (verstep & DME1737_VERSTEP_MASK) == DME1737_VERSTEP) { + name = "dme1737"; + } else { + return -ENODEV; + } + + dev_info(dev, "Found a %s chip at 0x%02x (rev 0x%02x).\n", + verstep == SCH5027_VERSTEP ? "SCH5027" : "DME1737", + client->addr, verstep); + strlcpy(info->type, name, I2C_NAME_SIZE); + + return 0; +} + +static int dme1737_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct dme1737_data *data; + struct device *dev = &client->dev; + int err; + + data = devm_kzalloc(dev, sizeof(struct dme1737_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->type = id->driver_data; + data->client = client; + data->name = client->name; + mutex_init(&data->update_lock); + + /* Initialize the DME1737 chip */ + err = dme1737_init_device(dev); + if (err) { + dev_err(dev, "Failed to initialize device.\n"); + return err; + } + + /* Create sysfs files */ + err = dme1737_create_files(dev); + if (err) { + dev_err(dev, "Failed to create sysfs files.\n"); + return err; + } + + /* Register device */ + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + dev_err(dev, "Failed to register device.\n"); + err = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + dme1737_remove_files(dev); + return err; +} + +static int dme1737_i2c_remove(struct i2c_client *client) +{ + struct dme1737_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + dme1737_remove_files(&client->dev); + + return 0; +} + +static const struct i2c_device_id dme1737_id[] = { + { "dme1737", dme1737 }, + { "sch5027", sch5027 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, dme1737_id); + +static struct i2c_driver dme1737_i2c_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "dme1737", + }, + .probe = dme1737_i2c_probe, + .remove = dme1737_i2c_remove, + .id_table = dme1737_id, + .detect = dme1737_i2c_detect, + .address_list = normal_i2c, +}; + +/* --------------------------------------------------------------------- + * ISA device detection and registration + * --------------------------------------------------------------------- */ + +static int __init dme1737_isa_detect(int sio_cip, unsigned short *addr) +{ + int err = 0, reg; + unsigned short base_addr; + + dme1737_sio_enter(sio_cip); + + /* + * Check device ID + * We currently know about SCH3112, SCH3114, SCH3116, and SCH5127 + */ + reg = force_id ? force_id : dme1737_sio_inb(sio_cip, 0x20); + if (!(reg == SCH3112_ID || reg == SCH3114_ID || reg == SCH3116_ID || + reg == SCH5127_ID)) { + err = -ENODEV; + goto exit; + } + + /* Select logical device A (runtime registers) */ + dme1737_sio_outb(sio_cip, 0x07, 0x0a); + + /* Get the base address of the runtime registers */ + base_addr = (dme1737_sio_inb(sio_cip, 0x60) << 8) | + dme1737_sio_inb(sio_cip, 0x61); + if (!base_addr) { + pr_err("Base address not set\n"); + err = -ENODEV; + goto exit; + } + + /* + * Access to the hwmon registers is through an index/data register + * pair located at offset 0x70/0x71. + */ + *addr = base_addr + 0x70; + +exit: + dme1737_sio_exit(sio_cip); + return err; +} + +static int __init dme1737_isa_device_add(unsigned short addr) +{ + struct resource res = { + .start = addr, + .end = addr + DME1737_EXTENT - 1, + .name = "dme1737", + .flags = IORESOURCE_IO, + }; + int err; + + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + + pdev = platform_device_alloc("dme1737", addr); + if (!pdev) { + pr_err("Failed to allocate device\n"); + err = -ENOMEM; + goto exit; + } + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + pr_err("Failed to add device resource (err = %d)\n", err); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + pr_err("Failed to add device (err = %d)\n", err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); + pdev = NULL; +exit: + return err; +} + +static int dme1737_isa_probe(struct platform_device *pdev) +{ + u8 company, device; + struct resource *res; + struct dme1737_data *data; + struct device *dev = &pdev->dev; + int err; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(dev, res->start, DME1737_EXTENT, "dme1737")) { + dev_err(dev, "Failed to request region 0x%04x-0x%04x.\n", + (unsigned short)res->start, + (unsigned short)res->start + DME1737_EXTENT - 1); + return -EBUSY; + } + + data = devm_kzalloc(dev, sizeof(struct dme1737_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = res->start; + platform_set_drvdata(pdev, data); + + /* Skip chip detection if module is loaded with force_id parameter */ + switch (force_id) { + case SCH3112_ID: + case SCH3114_ID: + case SCH3116_ID: + data->type = sch311x; + break; + case SCH5127_ID: + data->type = sch5127; + break; + default: + company = dme1737_read(data, DME1737_REG_COMPANY); + device = dme1737_read(data, DME1737_REG_DEVICE); + + if ((company == DME1737_COMPANY_SMSC) && + (device == SCH311X_DEVICE)) { + data->type = sch311x; + } else if ((company == DME1737_COMPANY_SMSC) && + (device == SCH5127_DEVICE)) { + data->type = sch5127; + } else { + return -ENODEV; + } + } + + if (data->type == sch5127) + data->name = "sch5127"; + else + data->name = "sch311x"; + + /* Initialize the mutex */ + mutex_init(&data->update_lock); + + dev_info(dev, "Found a %s chip at 0x%04x\n", + data->type == sch5127 ? "SCH5127" : "SCH311x", data->addr); + + /* Initialize the chip */ + err = dme1737_init_device(dev); + if (err) { + dev_err(dev, "Failed to initialize device.\n"); + return err; + } + + /* Create sysfs files */ + err = dme1737_create_files(dev); + if (err) { + dev_err(dev, "Failed to create sysfs files.\n"); + return err; + } + + /* Register device */ + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + dev_err(dev, "Failed to register device.\n"); + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_files; + } + + return 0; + +exit_remove_files: + dme1737_remove_files(dev); + return err; +} + +static int dme1737_isa_remove(struct platform_device *pdev) +{ + struct dme1737_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + dme1737_remove_files(&pdev->dev); + + return 0; +} + +static struct platform_driver dme1737_isa_driver = { + .driver = { + .name = "dme1737", + }, + .probe = dme1737_isa_probe, + .remove = dme1737_isa_remove, +}; + +/* --------------------------------------------------------------------- + * Module initialization and cleanup + * --------------------------------------------------------------------- */ + +static int __init dme1737_init(void) +{ + int err; + unsigned short addr; + + err = i2c_add_driver(&dme1737_i2c_driver); + if (err) + goto exit; + + if (dme1737_isa_detect(0x2e, &addr) && + dme1737_isa_detect(0x4e, &addr) && + (!probe_all_addr || + (dme1737_isa_detect(0x162e, &addr) && + dme1737_isa_detect(0x164e, &addr)))) { + /* Return 0 if we didn't find an ISA device */ + return 0; + } + + err = platform_driver_register(&dme1737_isa_driver); + if (err) + goto exit_del_i2c_driver; + + /* Sets global pdev as a side effect */ + err = dme1737_isa_device_add(addr); + if (err) + goto exit_del_isa_driver; + + return 0; + +exit_del_isa_driver: + platform_driver_unregister(&dme1737_isa_driver); +exit_del_i2c_driver: + i2c_del_driver(&dme1737_i2c_driver); +exit: + return err; +} + +static void __exit dme1737_exit(void) +{ + if (pdev) { + platform_device_unregister(pdev); + platform_driver_unregister(&dme1737_isa_driver); + } + + i2c_del_driver(&dme1737_i2c_driver); +} + +MODULE_AUTHOR("Juerg Haefliger "); +MODULE_DESCRIPTION("DME1737 sensors"); +MODULE_LICENSE("GPL"); + +module_init(dme1737_init); +module_exit(dme1737_exit); diff --git a/drivers/hwmon/ds1621.c b/drivers/hwmon/ds1621.c new file mode 100644 index 000000000..5c317fc32 --- /dev/null +++ b/drivers/hwmon/ds1621.c @@ -0,0 +1,409 @@ +/* + * ds1621.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Christian W. Zuckschwerdt 2000-11-23 + * based on lm75.c by Frodo Looijaard + * Ported to Linux 2.6 by Aurelien Jarno with + * the help of Jean Delvare + * + * The DS1621 device is a digital temperature/thermometer with 9-bit + * resolution, a thermal alarm output (Tout), and user-defined minimum + * and maximum temperature thresholds (TH and TL). + * + * The DS1625, DS1631, DS1721, and DS1731 are pin compatible with the DS1621 + * and similar in operation, with slight variations as noted in the device + * datasheets (please refer to www.maximintegrated.com for specific + * device information). + * + * Since the DS1621 was the first chipset supported by this driver, + * most comments will refer to this chipset, but are actually general + * and concern all supported chipsets, unless mentioned otherwise. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Supported devices */ +enum chips { ds1621, ds1625, ds1631, ds1721, ds1731 }; + +/* Insmod parameters */ +static int polarity = -1; +module_param(polarity, int, 0); +MODULE_PARM_DESC(polarity, "Output's polarity: 0 = active high, 1 = active low"); + +/* + * The Configuration/Status register + * + * - DS1621: + * 7 6 5 4 3 2 1 0 + * |Done|THF |TLF |NVB | X | X |POL |1SHOT| + * + * - DS1625: + * 7 6 5 4 3 2 1 0 + * |Done|THF |TLF |NVB | 1 | 0 |POL |1SHOT| + * + * - DS1631, DS1731: + * 7 6 5 4 3 2 1 0 + * |Done|THF |TLF |NVB | R1 | R0 |POL |1SHOT| + * + * - DS1721: + * 7 6 5 4 3 2 1 0 + * |Done| X | X | U | R1 | R0 |POL |1SHOT| + * + * Where: + * - 'X' is Reserved + * - 'U' is Undefined + */ +#define DS1621_REG_CONFIG_NVB 0x10 +#define DS1621_REG_CONFIG_RESOL 0x0C +#define DS1621_REG_CONFIG_POLARITY 0x02 +#define DS1621_REG_CONFIG_1SHOT 0x01 +#define DS1621_REG_CONFIG_DONE 0x80 + +#define DS1621_REG_CONFIG_RESOL_SHIFT 2 + +/* ds1721 conversion rates: {C/LSB, time(ms), resolution bit setting} */ +static const unsigned short ds1721_convrates[] = { + 94, /* 9-bits (0.5, 93.75, RES[0..1] = 0 */ + 188, /* 10-bits (0.25, 187.5, RES[0..1] = 1 */ + 375, /* 11-bits (0.125, 375, RES[0..1] = 2 */ + 750, /* 12-bits (0.0625, 750, RES[0..1] = 3 */ +}; + +#define DS1621_CONVERSION_MAX 750 +#define DS1625_CONVERSION_MAX 500 + +#define DS1621_TEMP_MAX 125000 +#define DS1621_TEMP_MIN (-55000) + +/* The DS1621 temperature registers */ +static const u8 DS1621_REG_TEMP[3] = { + 0xAA, /* input, word, RO */ + 0xA2, /* min, word, RW */ + 0xA1, /* max, word, RW */ +}; +#define DS1621_REG_CONF 0xAC /* byte, RW */ +#define DS1621_COM_START 0xEE /* no data */ +#define DS1721_COM_START 0x51 /* no data */ +#define DS1621_COM_STOP 0x22 /* no data */ + +/* The DS1621 configuration register */ +#define DS1621_ALARM_TEMP_HIGH 0x40 +#define DS1621_ALARM_TEMP_LOW 0x20 + +/* Conversions */ +#define ALARMS_FROM_REG(val) ((val) & \ + (DS1621_ALARM_TEMP_HIGH | DS1621_ALARM_TEMP_LOW)) + +/* Each client has this additional data */ +struct ds1621_data { + struct i2c_client *client; + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + enum chips kind; /* device type */ + + u16 temp[3]; /* Register values, word */ + u8 conf; /* Register encoding, combined */ + u8 zbits; /* Resolution encoded as number of + * zero bits */ + u16 update_interval; /* Conversion rate in milliseconds */ +}; + +static inline int DS1621_TEMP_FROM_REG(u16 reg) +{ + return DIV_ROUND_CLOSEST(((s16)reg / 16) * 625, 10); +} + +/* + * TEMP: 0.001C/bit (-55C to +125C) + * REG: + * - 1621, 1625: 0.5C/bit, 7 zero-bits + * - 1631, 1721, 1731: 0.0625C/bit, 4 zero-bits + */ +static inline u16 DS1621_TEMP_TO_REG(long temp, u8 zbits) +{ + temp = clamp_val(temp, DS1621_TEMP_MIN, DS1621_TEMP_MAX); + temp = DIV_ROUND_CLOSEST(temp * (1 << (8 - zbits)), 1000) << zbits; + return temp; +} + +static void ds1621_init_client(struct ds1621_data *data, + struct i2c_client *client) +{ + u8 conf, new_conf, sreg, resol; + + new_conf = conf = i2c_smbus_read_byte_data(client, DS1621_REG_CONF); + /* switch to continuous conversion mode */ + new_conf &= ~DS1621_REG_CONFIG_1SHOT; + + /* setup output polarity */ + if (polarity == 0) + new_conf &= ~DS1621_REG_CONFIG_POLARITY; + else if (polarity == 1) + new_conf |= DS1621_REG_CONFIG_POLARITY; + + if (conf != new_conf) + i2c_smbus_write_byte_data(client, DS1621_REG_CONF, new_conf); + + switch (data->kind) { + case ds1625: + data->update_interval = DS1625_CONVERSION_MAX; + data->zbits = 7; + sreg = DS1621_COM_START; + break; + case ds1631: + case ds1721: + case ds1731: + resol = (new_conf & DS1621_REG_CONFIG_RESOL) >> + DS1621_REG_CONFIG_RESOL_SHIFT; + data->update_interval = ds1721_convrates[resol]; + data->zbits = 7 - resol; + sreg = DS1721_COM_START; + break; + default: + data->update_interval = DS1621_CONVERSION_MAX; + data->zbits = 7; + sreg = DS1621_COM_START; + break; + } + + /* start conversion */ + i2c_smbus_write_byte(client, sreg); +} + +static struct ds1621_data *ds1621_update_client(struct device *dev) +{ + struct ds1621_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 new_conf; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + data->update_interval) || + !data->valid) { + int i; + + dev_dbg(&client->dev, "Starting ds1621 update\n"); + + data->conf = i2c_smbus_read_byte_data(client, DS1621_REG_CONF); + + for (i = 0; i < ARRAY_SIZE(data->temp); i++) + data->temp[i] = i2c_smbus_read_word_swapped(client, + DS1621_REG_TEMP[i]); + + /* reset alarms if necessary */ + new_conf = data->conf; + if (data->temp[0] > data->temp[1]) /* input > min */ + new_conf &= ~DS1621_ALARM_TEMP_LOW; + if (data->temp[0] < data->temp[2]) /* input < max */ + new_conf &= ~DS1621_ALARM_TEMP_HIGH; + if (data->conf != new_conf) + i2c_smbus_write_byte_data(client, DS1621_REG_CONF, + new_conf); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ds1621_data *data = ds1621_update_client(dev); + return sprintf(buf, "%d\n", + DS1621_TEMP_FROM_REG(data->temp[attr->index])); +} + +static ssize_t set_temp(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ds1621_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp[attr->index] = DS1621_TEMP_TO_REG(val, data->zbits); + i2c_smbus_write_word_swapped(data->client, DS1621_REG_TEMP[attr->index], + data->temp[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t alarms_show(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct ds1621_data *data = ds1621_update_client(dev); + return sprintf(buf, "%d\n", ALARMS_FROM_REG(data->conf)); +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ds1621_data *data = ds1621_update_client(dev); + return sprintf(buf, "%d\n", !!(data->conf & attr->index)); +} + +static ssize_t update_interval_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct ds1621_data *data = dev_get_drvdata(dev); + return scnprintf(buf, PAGE_SIZE, "%hu\n", data->update_interval); +} + +static ssize_t update_interval_store(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct ds1621_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long convrate; + s32 err; + int resol = 0; + + err = kstrtoul(buf, 10, &convrate); + if (err) + return err; + + /* Convert rate into resolution bits */ + while (resol < (ARRAY_SIZE(ds1721_convrates) - 1) && + convrate > ds1721_convrates[resol]) + resol++; + + mutex_lock(&data->update_lock); + data->conf = i2c_smbus_read_byte_data(client, DS1621_REG_CONF); + data->conf &= ~DS1621_REG_CONFIG_RESOL; + data->conf |= (resol << DS1621_REG_CONFIG_RESOL_SHIFT); + i2c_smbus_write_byte_data(client, DS1621_REG_CONF, data->conf); + data->update_interval = ds1721_convrates[resol]; + data->zbits = 7 - resol; + mutex_unlock(&data->update_lock); + + return count; +} + +static DEVICE_ATTR_RO(alarms); +static DEVICE_ATTR_RW(update_interval); + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp, set_temp, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp, set_temp, 2); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, + DS1621_ALARM_TEMP_LOW); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, + DS1621_ALARM_TEMP_HIGH); + +static struct attribute *ds1621_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &dev_attr_alarms.attr, + &dev_attr_update_interval.attr, + NULL +}; + +static umode_t ds1621_attribute_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct ds1621_data *data = dev_get_drvdata(dev); + + if (attr == &dev_attr_update_interval.attr) + if (data->kind == ds1621 || data->kind == ds1625) + /* shhh, we're hiding update_interval */ + return 0; + return attr->mode; +} + +static const struct attribute_group ds1621_group = { + .attrs = ds1621_attributes, + .is_visible = ds1621_attribute_visible +}; +__ATTRIBUTE_GROUPS(ds1621); + +static int ds1621_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ds1621_data *data; + struct device *hwmon_dev; + + data = devm_kzalloc(&client->dev, sizeof(struct ds1621_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->update_lock); + + data->kind = id->driver_data; + data->client = client; + + /* Initialize the DS1621 chip */ + ds1621_init_client(data, client); + + hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, + client->name, data, + ds1621_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id ds1621_id[] = { + { "ds1621", ds1621 }, + { "ds1625", ds1625 }, + { "ds1631", ds1631 }, + { "ds1721", ds1721 }, + { "ds1731", ds1731 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ds1621_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver ds1621_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "ds1621", + }, + .probe = ds1621_probe, + .id_table = ds1621_id, +}; + +module_i2c_driver(ds1621_driver); + +MODULE_AUTHOR("Christian W. Zuckschwerdt "); +MODULE_DESCRIPTION("DS1621 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ds620.c b/drivers/hwmon/ds620.c new file mode 100644 index 000000000..57d6958c7 --- /dev/null +++ b/drivers/hwmon/ds620.c @@ -0,0 +1,272 @@ +/* + * ds620.c - Support for temperature sensor and thermostat DS620 + * + * Copyright (C) 2010, 2011 Roland Stigge + * + * based on ds1621.c by Christian W. Zuckschwerdt + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Many DS620 constants specified below + * 15 14 13 12 11 10 09 08 + * |Done|NVB |THF |TLF |R1 |R0 |AUTOC|1SHOT| + * + * 07 06 05 04 03 02 01 00 + * |PO2 |PO1 |A2 |A1 |A0 | | | | + */ +#define DS620_REG_CONFIG_DONE 0x8000 +#define DS620_REG_CONFIG_NVB 0x4000 +#define DS620_REG_CONFIG_THF 0x2000 +#define DS620_REG_CONFIG_TLF 0x1000 +#define DS620_REG_CONFIG_R1 0x0800 +#define DS620_REG_CONFIG_R0 0x0400 +#define DS620_REG_CONFIG_AUTOC 0x0200 +#define DS620_REG_CONFIG_1SHOT 0x0100 +#define DS620_REG_CONFIG_PO2 0x0080 +#define DS620_REG_CONFIG_PO1 0x0040 +#define DS620_REG_CONFIG_A2 0x0020 +#define DS620_REG_CONFIG_A1 0x0010 +#define DS620_REG_CONFIG_A0 0x0008 + +/* The DS620 registers */ +static const u8 DS620_REG_TEMP[3] = { + 0xAA, /* input, word, RO */ + 0xA2, /* min, word, RW */ + 0xA0, /* max, word, RW */ +}; + +#define DS620_REG_CONF 0xAC /* word, RW */ +#define DS620_COM_START 0x51 /* no data */ +#define DS620_COM_STOP 0x22 /* no data */ + +/* Each client has this additional data */ +struct ds620_data { + struct i2c_client *client; + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + s16 temp[3]; /* Register values, word */ +}; + +static void ds620_init_client(struct i2c_client *client) +{ + struct ds620_platform_data *ds620_info = dev_get_platdata(&client->dev); + u16 conf, new_conf; + + new_conf = conf = + i2c_smbus_read_word_swapped(client, DS620_REG_CONF); + + /* switch to continuous conversion mode */ + new_conf &= ~DS620_REG_CONFIG_1SHOT; + /* already high at power-on, but don't trust the BIOS! */ + new_conf |= DS620_REG_CONFIG_PO2; + /* thermostat mode according to platform data */ + if (ds620_info && ds620_info->pomode == 1) + new_conf &= ~DS620_REG_CONFIG_PO1; /* PO_LOW */ + else if (ds620_info && ds620_info->pomode == 2) + new_conf |= DS620_REG_CONFIG_PO1; /* PO_HIGH */ + else + new_conf &= ~DS620_REG_CONFIG_PO2; /* always low */ + /* with highest precision */ + new_conf |= DS620_REG_CONFIG_R1 | DS620_REG_CONFIG_R0; + + if (conf != new_conf) + i2c_smbus_write_word_swapped(client, DS620_REG_CONF, new_conf); + + /* start conversion */ + i2c_smbus_write_byte(client, DS620_COM_START); +} + +static struct ds620_data *ds620_update_client(struct device *dev) +{ + struct ds620_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + struct ds620_data *ret = data; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + int i; + int res; + + dev_dbg(&client->dev, "Starting ds620 update\n"); + + for (i = 0; i < ARRAY_SIZE(data->temp); i++) { + res = i2c_smbus_read_word_swapped(client, + DS620_REG_TEMP[i]); + if (res < 0) { + ret = ERR_PTR(res); + goto abort; + } + + data->temp[i] = res; + } + + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + + return ret; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ds620_data *data = ds620_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", ((data->temp[attr->index] / 8) * 625) / 10); +} + +static ssize_t set_temp(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + int res; + long val; + + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ds620_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + res = kstrtol(buf, 10, &val); + + if (res) + return res; + + val = (clamp_val(val, -128000, 128000) * 10 / 625) * 8; + + mutex_lock(&data->update_lock); + data->temp[attr->index] = val; + i2c_smbus_write_word_swapped(client, DS620_REG_TEMP[attr->index], + data->temp[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ds620_data *data = ds620_update_client(dev); + struct i2c_client *client; + u16 conf, new_conf; + int res; + + if (IS_ERR(data)) + return PTR_ERR(data); + + client = data->client; + + /* reset alarms if necessary */ + res = i2c_smbus_read_word_swapped(client, DS620_REG_CONF); + if (res < 0) + return res; + + new_conf = conf = res; + new_conf &= ~attr->index; + if (conf != new_conf) { + res = i2c_smbus_write_word_swapped(client, DS620_REG_CONF, + new_conf); + if (res < 0) + return res; + } + + return sprintf(buf, "%d\n", !!(conf & attr->index)); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp, set_temp, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp, set_temp, 2); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, + DS620_REG_CONFIG_TLF); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, + DS620_REG_CONFIG_THF); + +static struct attribute *ds620_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(ds620); + +static int ds620_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct ds620_data *data; + + data = devm_kzalloc(dev, sizeof(struct ds620_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* Initialize the DS620 chip */ + ds620_init_client(client); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, ds620_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id ds620_id[] = { + {"ds620", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ds620_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver ds620_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "ds620", + }, + .probe = ds620_probe, + .id_table = ds620_id, +}; + +module_i2c_driver(ds620_driver); + +MODULE_AUTHOR("Roland Stigge "); +MODULE_DESCRIPTION("DS620 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c new file mode 100644 index 000000000..aaebeb726 --- /dev/null +++ b/drivers/hwmon/emc1403.c @@ -0,0 +1,501 @@ +/* + * emc1403.c - SMSC Thermal Driver + * + * Copyright (C) 2008 Intel Corp + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THERMAL_PID_REG 0xfd +#define THERMAL_SMSC_ID_REG 0xfe +#define THERMAL_REVISION_REG 0xff + +enum emc1403_chip { emc1402, emc1403, emc1404 }; + +struct thermal_data { + struct regmap *regmap; + struct mutex mutex; + const struct attribute_group *groups[4]; +}; + +static ssize_t show_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sda = to_sensor_dev_attr(attr); + struct thermal_data *data = dev_get_drvdata(dev); + unsigned int val; + int retval; + + retval = regmap_read(data->regmap, sda->index, &val); + if (retval < 0) + return retval; + return sprintf(buf, "%d000\n", val); +} + +static ssize_t show_bit(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sda = to_sensor_dev_attr_2(attr); + struct thermal_data *data = dev_get_drvdata(dev); + unsigned int val; + int retval; + + retval = regmap_read(data->regmap, sda->nr, &val); + if (retval < 0) + return retval; + return sprintf(buf, "%d\n", !!(val & sda->index)); +} + +static ssize_t store_temp(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sensor_device_attribute *sda = to_sensor_dev_attr(attr); + struct thermal_data *data = dev_get_drvdata(dev); + unsigned long val; + int retval; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + retval = regmap_write(data->regmap, sda->index, + DIV_ROUND_CLOSEST(val, 1000)); + if (retval < 0) + return retval; + return count; +} + +static ssize_t store_bit(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sda = to_sensor_dev_attr_2(attr); + struct thermal_data *data = dev_get_drvdata(dev); + unsigned long val; + int retval; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + retval = regmap_update_bits(data->regmap, sda->nr, sda->index, + val ? sda->index : 0); + if (retval < 0) + return retval; + return count; +} + +static ssize_t show_hyst_common(struct device *dev, + struct device_attribute *attr, char *buf, + bool is_min) +{ + struct sensor_device_attribute *sda = to_sensor_dev_attr(attr); + struct thermal_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + unsigned int limit; + unsigned int hyst; + int retval; + + retval = regmap_read(regmap, sda->index, &limit); + if (retval < 0) + return retval; + + retval = regmap_read(regmap, 0x21, &hyst); + if (retval < 0) + return retval; + + return sprintf(buf, "%d000\n", is_min ? limit + hyst : limit - hyst); +} + +static ssize_t show_hyst(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return show_hyst_common(dev, attr, buf, false); +} + +static ssize_t show_min_hyst(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return show_hyst_common(dev, attr, buf, true); +} + +static ssize_t store_hyst(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sensor_device_attribute *sda = to_sensor_dev_attr(attr); + struct thermal_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + unsigned int limit; + int retval; + int hyst; + unsigned long val; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&data->mutex); + retval = regmap_read(regmap, sda->index, &limit); + if (retval < 0) + goto fail; + + hyst = limit * 1000 - val; + hyst = clamp_val(DIV_ROUND_CLOSEST(hyst, 1000), 0, 255); + retval = regmap_write(regmap, 0x21, hyst); + if (retval == 0) + retval = count; +fail: + mutex_unlock(&data->mutex); + return retval; +} + +/* + * Sensors. We pass the actual i2c register to the methods. + */ + +static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x06); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x05); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x20); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0x00); +static SENSOR_DEVICE_ATTR_2(temp1_min_alarm, S_IRUGO, + show_bit, NULL, 0x36, 0x01); +static SENSOR_DEVICE_ATTR_2(temp1_max_alarm, S_IRUGO, + show_bit, NULL, 0x35, 0x01); +static SENSOR_DEVICE_ATTR_2(temp1_crit_alarm, S_IRUGO, + show_bit, NULL, 0x37, 0x01); +static SENSOR_DEVICE_ATTR(temp1_min_hyst, S_IRUGO, show_min_hyst, NULL, 0x06); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IRUGO, show_hyst, NULL, 0x05); +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IRUGO | S_IWUSR, + show_hyst, store_hyst, 0x20); + +static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x08); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x07); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x19); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 0x01); +static SENSOR_DEVICE_ATTR_2(temp2_fault, S_IRUGO, show_bit, NULL, 0x1b, 0x02); +static SENSOR_DEVICE_ATTR_2(temp2_min_alarm, S_IRUGO, + show_bit, NULL, 0x36, 0x02); +static SENSOR_DEVICE_ATTR_2(temp2_max_alarm, S_IRUGO, + show_bit, NULL, 0x35, 0x02); +static SENSOR_DEVICE_ATTR_2(temp2_crit_alarm, S_IRUGO, + show_bit, NULL, 0x37, 0x02); +static SENSOR_DEVICE_ATTR(temp2_min_hyst, S_IRUGO, show_min_hyst, NULL, 0x08); +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IRUGO, show_hyst, NULL, 0x07); +static SENSOR_DEVICE_ATTR(temp2_crit_hyst, S_IRUGO, show_hyst, NULL, 0x19); + +static SENSOR_DEVICE_ATTR(temp3_min, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x16); +static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x15); +static SENSOR_DEVICE_ATTR(temp3_crit, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x1A); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 0x23); +static SENSOR_DEVICE_ATTR_2(temp3_fault, S_IRUGO, show_bit, NULL, 0x1b, 0x04); +static SENSOR_DEVICE_ATTR_2(temp3_min_alarm, S_IRUGO, + show_bit, NULL, 0x36, 0x04); +static SENSOR_DEVICE_ATTR_2(temp3_max_alarm, S_IRUGO, + show_bit, NULL, 0x35, 0x04); +static SENSOR_DEVICE_ATTR_2(temp3_crit_alarm, S_IRUGO, + show_bit, NULL, 0x37, 0x04); +static SENSOR_DEVICE_ATTR(temp3_min_hyst, S_IRUGO, show_min_hyst, NULL, 0x16); +static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IRUGO, show_hyst, NULL, 0x15); +static SENSOR_DEVICE_ATTR(temp3_crit_hyst, S_IRUGO, show_hyst, NULL, 0x1A); + +static SENSOR_DEVICE_ATTR(temp4_min, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x2D); +static SENSOR_DEVICE_ATTR(temp4_max, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x2C); +static SENSOR_DEVICE_ATTR(temp4_crit, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x30); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 0x2A); +static SENSOR_DEVICE_ATTR_2(temp4_fault, S_IRUGO, show_bit, NULL, 0x1b, 0x08); +static SENSOR_DEVICE_ATTR_2(temp4_min_alarm, S_IRUGO, + show_bit, NULL, 0x36, 0x08); +static SENSOR_DEVICE_ATTR_2(temp4_max_alarm, S_IRUGO, + show_bit, NULL, 0x35, 0x08); +static SENSOR_DEVICE_ATTR_2(temp4_crit_alarm, S_IRUGO, + show_bit, NULL, 0x37, 0x08); +static SENSOR_DEVICE_ATTR(temp4_min_hyst, S_IRUGO, show_min_hyst, NULL, 0x2D); +static SENSOR_DEVICE_ATTR(temp4_max_hyst, S_IRUGO, show_hyst, NULL, 0x2C); +static SENSOR_DEVICE_ATTR(temp4_crit_hyst, S_IRUGO, show_hyst, NULL, 0x30); + +static SENSOR_DEVICE_ATTR_2(power_state, S_IRUGO | S_IWUSR, + show_bit, store_bit, 0x03, 0x40); + +static struct attribute *emc1402_attrs[] = { + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, + + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr, + + &sensor_dev_attr_power_state.dev_attr.attr, + NULL +}; + +static const struct attribute_group emc1402_group = { + .attrs = emc1402_attrs, +}; + +static struct attribute *emc1403_attrs[] = { + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_min_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_crit_hyst.dev_attr.attr, + NULL +}; + +static const struct attribute_group emc1403_group = { + .attrs = emc1403_attrs, +}; + +static struct attribute *emc1404_attrs[] = { + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_crit.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_min_hyst.dev_attr.attr, + &sensor_dev_attr_temp4_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp4_crit_hyst.dev_attr.attr, + NULL +}; + +static const struct attribute_group emc1404_group = { + .attrs = emc1404_attrs, +}; + +/* + * EMC14x2 uses a different register and different bits to report alarm and + * fault status. For simplicity, provide a separate attribute group for this + * chip series. + * Since we can not re-use the same attribute names, create a separate attribute + * array. + */ +static struct sensor_device_attribute_2 emc1402_alarms[] = { + SENSOR_ATTR_2(temp1_min_alarm, S_IRUGO, show_bit, NULL, 0x02, 0x20), + SENSOR_ATTR_2(temp1_max_alarm, S_IRUGO, show_bit, NULL, 0x02, 0x40), + SENSOR_ATTR_2(temp1_crit_alarm, S_IRUGO, show_bit, NULL, 0x02, 0x01), + + SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_bit, NULL, 0x02, 0x04), + SENSOR_ATTR_2(temp2_min_alarm, S_IRUGO, show_bit, NULL, 0x02, 0x08), + SENSOR_ATTR_2(temp2_max_alarm, S_IRUGO, show_bit, NULL, 0x02, 0x10), + SENSOR_ATTR_2(temp2_crit_alarm, S_IRUGO, show_bit, NULL, 0x02, 0x02), +}; + +static struct attribute *emc1402_alarm_attrs[] = { + &emc1402_alarms[0].dev_attr.attr, + &emc1402_alarms[1].dev_attr.attr, + &emc1402_alarms[2].dev_attr.attr, + &emc1402_alarms[3].dev_attr.attr, + &emc1402_alarms[4].dev_attr.attr, + &emc1402_alarms[5].dev_attr.attr, + &emc1402_alarms[6].dev_attr.attr, + NULL, +}; + +static const struct attribute_group emc1402_alarm_group = { + .attrs = emc1402_alarm_attrs, +}; + +static int emc1403_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + int id; + /* Check if thermal chip is SMSC and EMC1403 or EMC1423 */ + + id = i2c_smbus_read_byte_data(client, THERMAL_SMSC_ID_REG); + if (id != 0x5d) + return -ENODEV; + + id = i2c_smbus_read_byte_data(client, THERMAL_PID_REG); + switch (id) { + case 0x20: + strlcpy(info->type, "emc1402", I2C_NAME_SIZE); + break; + case 0x21: + strlcpy(info->type, "emc1403", I2C_NAME_SIZE); + break; + case 0x22: + strlcpy(info->type, "emc1422", I2C_NAME_SIZE); + break; + case 0x23: + strlcpy(info->type, "emc1423", I2C_NAME_SIZE); + break; + case 0x25: + strlcpy(info->type, "emc1404", I2C_NAME_SIZE); + break; + case 0x27: + strlcpy(info->type, "emc1424", I2C_NAME_SIZE); + break; + default: + return -ENODEV; + } + + id = i2c_smbus_read_byte_data(client, THERMAL_REVISION_REG); + if (id < 0x01 || id > 0x04) + return -ENODEV; + + return 0; +} + +static bool emc1403_regmap_is_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x00: /* internal diode high byte */ + case 0x01: /* external diode 1 high byte */ + case 0x02: /* status */ + case 0x10: /* external diode 1 low byte */ + case 0x1b: /* external diode fault */ + case 0x23: /* external diode 2 high byte */ + case 0x24: /* external diode 2 low byte */ + case 0x29: /* internal diode low byte */ + case 0x2a: /* externl diode 3 high byte */ + case 0x2b: /* external diode 3 low byte */ + case 0x35: /* high limit status */ + case 0x36: /* low limit status */ + case 0x37: /* therm limit status */ + return true; + default: + return false; + } +} + +static const struct regmap_config emc1403_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = emc1403_regmap_is_volatile, +}; + +static int emc1403_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct thermal_data *data; + struct device *hwmon_dev; + + data = devm_kzalloc(&client->dev, sizeof(struct thermal_data), + GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + data->regmap = devm_regmap_init_i2c(client, &emc1403_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + mutex_init(&data->mutex); + + switch (id->driver_data) { + case emc1404: + data->groups[2] = &emc1404_group; + /* fall through */ + case emc1403: + data->groups[1] = &emc1403_group; + /* fall through */ + case emc1402: + data->groups[0] = &emc1402_group; + } + + if (id->driver_data == emc1402) + data->groups[1] = &emc1402_alarm_group; + + hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, + client->name, data, + data->groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + dev_info(&client->dev, "%s Thermal chip found\n", id->name); + return 0; +} + +static const unsigned short emc1403_address_list[] = { + 0x18, 0x1c, 0x29, 0x4c, 0x4d, 0x5c, I2C_CLIENT_END +}; + +/* Last digit of chip name indicates number of channels */ +static const struct i2c_device_id emc1403_idtable[] = { + { "emc1402", emc1402 }, + { "emc1403", emc1403 }, + { "emc1404", emc1404 }, + { "emc1412", emc1402 }, + { "emc1413", emc1403 }, + { "emc1414", emc1404 }, + { "emc1422", emc1402 }, + { "emc1423", emc1403 }, + { "emc1424", emc1404 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, emc1403_idtable); + +static struct i2c_driver sensor_emc1403 = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "emc1403", + }, + .detect = emc1403_detect, + .probe = emc1403_probe, + .id_table = emc1403_idtable, + .address_list = emc1403_address_list, +}; + +module_i2c_driver(sensor_emc1403); + +MODULE_AUTHOR("Kalhan Trisal +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses scanned */ +static const unsigned short normal_i2c[] = { 0x2E, I2C_CLIENT_END }; + +static const u8 REG_TEMP[4] = { 0x00, 0x02, 0x04, 0x06 }; +static const u8 REG_TEMP_MIN[4] = { 0x3c, 0x38, 0x39, 0x3a }; +static const u8 REG_TEMP_MAX[4] = { 0x34, 0x30, 0x31, 0x32 }; + +#define REG_CONF1 0x20 +#define REG_TEMP_MAX_ALARM 0x24 +#define REG_TEMP_MIN_ALARM 0x25 +#define REG_FAN_CONF1 0x42 +#define REG_FAN_TARGET_LO 0x4c +#define REG_FAN_TARGET_HI 0x4d +#define REG_FAN_TACH_HI 0x4e +#define REG_FAN_TACH_LO 0x4f +#define REG_PRODUCT_ID 0xfd +#define REG_MFG_ID 0xfe + +/* equation 4 from datasheet: rpm = (3932160 * multipler) / count */ +#define FAN_RPM_FACTOR 3932160 + +/* + * 2103-2 and 2103-4's 3rd temperature sensor can be connected to two diodes + * in anti-parallel mode, and in this configuration both can be read + * independently (so we have 4 temperature inputs). The device can't + * detect if it's connected in this mode, so we have to manually enable + * it. Default is to leave the device in the state it's already in (-1). + * This parameter allows APD mode to be optionally forced on or off + */ +static int apd = -1; +module_param(apd, bint, 0); +MODULE_PARM_DESC(apd, "Set to zero to disable anti-parallel diode mode"); + +struct temperature { + s8 degrees; + u8 fraction; /* 0-7 multiples of 0.125 */ +}; + +struct emc2103_data { + struct i2c_client *client; + const struct attribute_group *groups[4]; + struct mutex update_lock; + bool valid; /* registers are valid */ + bool fan_rpm_control; + int temp_count; /* num of temp sensors */ + unsigned long last_updated; /* in jiffies */ + struct temperature temp[4]; /* internal + 3 external */ + s8 temp_min[4]; /* no fractional part */ + s8 temp_max[4]; /* no fractional part */ + u8 temp_min_alarm; + u8 temp_max_alarm; + u8 fan_multiplier; + u16 fan_tach; + u16 fan_target; +}; + +static int read_u8_from_i2c(struct i2c_client *client, u8 i2c_reg, u8 *output) +{ + int status = i2c_smbus_read_byte_data(client, i2c_reg); + if (status < 0) { + dev_warn(&client->dev, "reg 0x%02x, err %d\n", + i2c_reg, status); + } else { + *output = status; + } + return status; +} + +static void read_temp_from_i2c(struct i2c_client *client, u8 i2c_reg, + struct temperature *temp) +{ + u8 degrees, fractional; + + if (read_u8_from_i2c(client, i2c_reg, °rees) < 0) + return; + + if (read_u8_from_i2c(client, i2c_reg + 1, &fractional) < 0) + return; + + temp->degrees = degrees; + temp->fraction = (fractional & 0xe0) >> 5; +} + +static void read_fan_from_i2c(struct i2c_client *client, u16 *output, + u8 hi_addr, u8 lo_addr) +{ + u8 high_byte, lo_byte; + + if (read_u8_from_i2c(client, hi_addr, &high_byte) < 0) + return; + + if (read_u8_from_i2c(client, lo_addr, &lo_byte) < 0) + return; + + *output = ((u16)high_byte << 5) | (lo_byte >> 3); +} + +static void write_fan_target_to_i2c(struct i2c_client *client, u16 new_target) +{ + u8 high_byte = (new_target & 0x1fe0) >> 5; + u8 low_byte = (new_target & 0x001f) << 3; + i2c_smbus_write_byte_data(client, REG_FAN_TARGET_LO, low_byte); + i2c_smbus_write_byte_data(client, REG_FAN_TARGET_HI, high_byte); +} + +static void read_fan_config_from_i2c(struct i2c_client *client) + +{ + struct emc2103_data *data = i2c_get_clientdata(client); + u8 conf1; + + if (read_u8_from_i2c(client, REG_FAN_CONF1, &conf1) < 0) + return; + + data->fan_multiplier = 1 << ((conf1 & 0x60) >> 5); + data->fan_rpm_control = (conf1 & 0x80) != 0; +} + +static struct emc2103_data *emc2103_update_device(struct device *dev) +{ + struct emc2103_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + int i; + + for (i = 0; i < data->temp_count; i++) { + read_temp_from_i2c(client, REG_TEMP[i], &data->temp[i]); + read_u8_from_i2c(client, REG_TEMP_MIN[i], + &data->temp_min[i]); + read_u8_from_i2c(client, REG_TEMP_MAX[i], + &data->temp_max[i]); + } + + read_u8_from_i2c(client, REG_TEMP_MIN_ALARM, + &data->temp_min_alarm); + read_u8_from_i2c(client, REG_TEMP_MAX_ALARM, + &data->temp_max_alarm); + + read_fan_from_i2c(client, &data->fan_tach, + REG_FAN_TACH_HI, REG_FAN_TACH_LO); + read_fan_from_i2c(client, &data->fan_target, + REG_FAN_TARGET_HI, REG_FAN_TARGET_LO); + read_fan_config_from_i2c(client); + + data->last_updated = jiffies; + data->valid = true; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static ssize_t +show_temp(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + int millidegrees = data->temp[nr].degrees * 1000 + + data->temp[nr].fraction * 125; + return sprintf(buf, "%d\n", millidegrees); +} + +static ssize_t +show_temp_min(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + int millidegrees = data->temp_min[nr] * 1000; + return sprintf(buf, "%d\n", millidegrees); +} + +static ssize_t +show_temp_max(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + int millidegrees = data->temp_max[nr] * 1000; + return sprintf(buf, "%d\n", millidegrees); +} + +static ssize_t +show_temp_fault(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + bool fault = (data->temp[nr].degrees == -128); + return sprintf(buf, "%d\n", fault ? 1 : 0); +} + +static ssize_t +show_temp_min_alarm(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + bool alarm = data->temp_min_alarm & (1 << nr); + return sprintf(buf, "%d\n", alarm ? 1 : 0); +} + +static ssize_t +show_temp_max_alarm(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + bool alarm = data->temp_max_alarm & (1 << nr); + return sprintf(buf, "%d\n", alarm ? 1 : 0); +} + +static ssize_t set_temp_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + + int result = kstrtol(buf, 10, &val); + if (result < 0) + return result; + + val = DIV_ROUND_CLOSEST(clamp_val(val, -63000, 127000), 1000); + + mutex_lock(&data->update_lock); + data->temp_min[nr] = val; + i2c_smbus_write_byte_data(client, REG_TEMP_MIN[nr], val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t set_temp_max(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + + int result = kstrtol(buf, 10, &val); + if (result < 0) + return result; + + val = DIV_ROUND_CLOSEST(clamp_val(val, -63000, 127000), 1000); + + mutex_lock(&data->update_lock); + data->temp_max[nr] = val; + i2c_smbus_write_byte_data(client, REG_TEMP_MAX[nr], val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +fan1_input_show(struct device *dev, struct device_attribute *da, char *buf) +{ + struct emc2103_data *data = emc2103_update_device(dev); + int rpm = 0; + if (data->fan_tach != 0) + rpm = (FAN_RPM_FACTOR * data->fan_multiplier) / data->fan_tach; + return sprintf(buf, "%d\n", rpm); +} + +static ssize_t +fan1_div_show(struct device *dev, struct device_attribute *da, char *buf) +{ + struct emc2103_data *data = emc2103_update_device(dev); + int fan_div = 8 / data->fan_multiplier; + return sprintf(buf, "%d\n", fan_div); +} + +/* + * Note: we also update the fan target here, because its value is + * determined in part by the fan clock divider. This follows the principle + * of least surprise; the user doesn't expect the fan target to change just + * because the divider changed. + */ +static ssize_t fan1_div_store(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct emc2103_data *data = emc2103_update_device(dev); + struct i2c_client *client = data->client; + int new_range_bits, old_div = 8 / data->fan_multiplier; + long new_div; + + int status = kstrtol(buf, 10, &new_div); + if (status < 0) + return status; + + if (new_div == old_div) /* No change */ + return count; + + switch (new_div) { + case 1: + new_range_bits = 3; + break; + case 2: + new_range_bits = 2; + break; + case 4: + new_range_bits = 1; + break; + case 8: + new_range_bits = 0; + break; + default: + return -EINVAL; + } + + mutex_lock(&data->update_lock); + + status = i2c_smbus_read_byte_data(client, REG_FAN_CONF1); + if (status < 0) { + dev_dbg(&client->dev, "reg 0x%02x, err %d\n", + REG_FAN_CONF1, status); + mutex_unlock(&data->update_lock); + return status; + } + status &= 0x9F; + status |= (new_range_bits << 5); + i2c_smbus_write_byte_data(client, REG_FAN_CONF1, status); + + data->fan_multiplier = 8 / new_div; + + /* update fan target if high byte is not disabled */ + if ((data->fan_target & 0x1fe0) != 0x1fe0) { + u16 new_target = (data->fan_target * old_div) / new_div; + data->fan_target = min(new_target, (u16)0x1fff); + write_fan_target_to_i2c(client, data->fan_target); + } + + /* invalidate data to force re-read from hardware */ + data->valid = false; + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +fan1_target_show(struct device *dev, struct device_attribute *da, char *buf) +{ + struct emc2103_data *data = emc2103_update_device(dev); + int rpm = 0; + + /* high byte of 0xff indicates disabled so return 0 */ + if ((data->fan_target != 0) && ((data->fan_target & 0x1fe0) != 0x1fe0)) + rpm = (FAN_RPM_FACTOR * data->fan_multiplier) + / data->fan_target; + + return sprintf(buf, "%d\n", rpm); +} + +static ssize_t fan1_target_store(struct device *dev, + struct device_attribute *da, const char *buf, + size_t count) +{ + struct emc2103_data *data = emc2103_update_device(dev); + struct i2c_client *client = data->client; + unsigned long rpm_target; + + int result = kstrtoul(buf, 10, &rpm_target); + if (result < 0) + return result; + + /* Datasheet states 16384 as maximum RPM target (table 3.2) */ + rpm_target = clamp_val(rpm_target, 0, 16384); + + mutex_lock(&data->update_lock); + + if (rpm_target == 0) + data->fan_target = 0x1fff; + else + data->fan_target = clamp_val( + (FAN_RPM_FACTOR * data->fan_multiplier) / rpm_target, + 0, 0x1fff); + + write_fan_target_to_i2c(client, data->fan_target); + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +fan1_fault_show(struct device *dev, struct device_attribute *da, char *buf) +{ + struct emc2103_data *data = emc2103_update_device(dev); + bool fault = ((data->fan_tach & 0x1fe0) == 0x1fe0); + return sprintf(buf, "%d\n", fault ? 1 : 0); +} + +static ssize_t +pwm1_enable_show(struct device *dev, struct device_attribute *da, char *buf) +{ + struct emc2103_data *data = emc2103_update_device(dev); + return sprintf(buf, "%d\n", data->fan_rpm_control ? 3 : 0); +} + +static ssize_t pwm1_enable_store(struct device *dev, + struct device_attribute *da, const char *buf, + size_t count) +{ + struct emc2103_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long new_value; + u8 conf_reg; + + int result = kstrtol(buf, 10, &new_value); + if (result < 0) + return result; + + mutex_lock(&data->update_lock); + switch (new_value) { + case 0: + data->fan_rpm_control = false; + break; + case 3: + data->fan_rpm_control = true; + break; + default: + count = -EINVAL; + goto err; + } + + result = read_u8_from_i2c(client, REG_FAN_CONF1, &conf_reg); + if (result < 0) { + count = result; + goto err; + } + + if (data->fan_rpm_control) + conf_reg |= 0x80; + else + conf_reg &= ~0x80; + + i2c_smbus_write_byte_data(client, REG_FAN_CONF1, conf_reg); +err: + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, show_temp_min, + set_temp_min, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, show_temp_max, + set_temp_max, 0); +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_temp_min_alarm, + NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_temp_max_alarm, + NULL, 0); + +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, show_temp_min, + set_temp_min, 1); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, show_temp_max, + set_temp_max, 1); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_temp_min_alarm, + NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_temp_max_alarm, + NULL, 1); + +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_min, S_IRUGO | S_IWUSR, show_temp_min, + set_temp_min, 2); +static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO | S_IWUSR, show_temp_max, + set_temp_max, 2); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_temp_min_alarm, + NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_temp_max_alarm, + NULL, 2); + +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_min, S_IRUGO | S_IWUSR, show_temp_min, + set_temp_min, 3); +static SENSOR_DEVICE_ATTR(temp4_max, S_IRUGO | S_IWUSR, show_temp_max, + set_temp_max, 3); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_temp_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_temp_min_alarm, + NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_temp_max_alarm, + NULL, 3); + +static DEVICE_ATTR_RO(fan1_input); +static DEVICE_ATTR_RW(fan1_div); +static DEVICE_ATTR_RW(fan1_target); +static DEVICE_ATTR_RO(fan1_fault); + +static DEVICE_ATTR_RW(pwm1_enable); + +/* sensors present on all models */ +static struct attribute *emc2103_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &dev_attr_fan1_input.attr, + &dev_attr_fan1_div.attr, + &dev_attr_fan1_target.attr, + &dev_attr_fan1_fault.attr, + &dev_attr_pwm1_enable.attr, + NULL +}; + +/* extra temperature sensors only present on 2103-2 and 2103-4 */ +static struct attribute *emc2103_attributes_temp3[] = { + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + NULL +}; + +/* extra temperature sensors only present on 2103-2 and 2103-4 in APD mode */ +static struct attribute *emc2103_attributes_temp4[] = { + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group emc2103_group = { + .attrs = emc2103_attributes, +}; + +static const struct attribute_group emc2103_temp3_group = { + .attrs = emc2103_attributes_temp3, +}; + +static const struct attribute_group emc2103_temp4_group = { + .attrs = emc2103_attributes_temp4, +}; + +static int +emc2103_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct emc2103_data *data; + struct device *hwmon_dev; + int status, idx = 0; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + data = devm_kzalloc(&client->dev, sizeof(struct emc2103_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->client = client; + mutex_init(&data->update_lock); + + /* 2103-2 and 2103-4 have 3 external diodes, 2103-1 has 1 */ + status = i2c_smbus_read_byte_data(client, REG_PRODUCT_ID); + if (status == 0x24) { + /* 2103-1 only has 1 external diode */ + data->temp_count = 2; + } else { + /* 2103-2 and 2103-4 have 3 or 4 external diodes */ + status = i2c_smbus_read_byte_data(client, REG_CONF1); + if (status < 0) { + dev_dbg(&client->dev, "reg 0x%02x, err %d\n", REG_CONF1, + status); + return status; + } + + /* detect current state of hardware */ + data->temp_count = (status & 0x01) ? 4 : 3; + + /* force APD state if module parameter is set */ + if (apd == 0) { + /* force APD mode off */ + data->temp_count = 3; + status &= ~(0x01); + i2c_smbus_write_byte_data(client, REG_CONF1, status); + } else if (apd == 1) { + /* force APD mode on */ + data->temp_count = 4; + status |= 0x01; + i2c_smbus_write_byte_data(client, REG_CONF1, status); + } + } + + /* sysfs hooks */ + data->groups[idx++] = &emc2103_group; + if (data->temp_count >= 3) + data->groups[idx++] = &emc2103_temp3_group; + if (data->temp_count == 4) + data->groups[idx++] = &emc2103_temp4_group; + + hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, + client->name, data, + data->groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + dev_info(&client->dev, "%s: sensor '%s'\n", + dev_name(hwmon_dev), client->name); + + return 0; +} + +static const struct i2c_device_id emc2103_ids[] = { + { "emc2103", 0, }, + { /* LIST END */ } +}; +MODULE_DEVICE_TABLE(i2c, emc2103_ids); + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int +emc2103_detect(struct i2c_client *new_client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = new_client->adapter; + int manufacturer, product; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + manufacturer = i2c_smbus_read_byte_data(new_client, REG_MFG_ID); + if (manufacturer != 0x5D) + return -ENODEV; + + product = i2c_smbus_read_byte_data(new_client, REG_PRODUCT_ID); + if ((product != 0x24) && (product != 0x26)) + return -ENODEV; + + strlcpy(info->type, "emc2103", I2C_NAME_SIZE); + + return 0; +} + +static struct i2c_driver emc2103_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "emc2103", + }, + .probe = emc2103_probe, + .id_table = emc2103_ids, + .detect = emc2103_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(emc2103_driver); + +MODULE_AUTHOR("Steve Glendinning "); +MODULE_DESCRIPTION("SMSC EMC2103 hwmon driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/emc6w201.c b/drivers/hwmon/emc6w201.c new file mode 100644 index 000000000..4aee5adf9 --- /dev/null +++ b/drivers/hwmon/emc6w201.c @@ -0,0 +1,529 @@ +/* + * emc6w201.c - Hardware monitoring driver for the SMSC EMC6W201 + * Copyright (C) 2011 Jean Delvare + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Addresses to scan + */ + +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END }; + +/* + * The EMC6W201 registers + */ + +#define EMC6W201_REG_IN(nr) (0x20 + (nr)) +#define EMC6W201_REG_TEMP(nr) (0x26 + (nr)) +#define EMC6W201_REG_FAN(nr) (0x2C + (nr) * 2) +#define EMC6W201_REG_COMPANY 0x3E +#define EMC6W201_REG_VERSTEP 0x3F +#define EMC6W201_REG_CONFIG 0x40 +#define EMC6W201_REG_IN_LOW(nr) (0x4A + (nr) * 2) +#define EMC6W201_REG_IN_HIGH(nr) (0x4B + (nr) * 2) +#define EMC6W201_REG_TEMP_LOW(nr) (0x56 + (nr) * 2) +#define EMC6W201_REG_TEMP_HIGH(nr) (0x57 + (nr) * 2) +#define EMC6W201_REG_FAN_MIN(nr) (0x62 + (nr) * 2) + +enum subfeature { input, min, max }; + +/* + * Per-device data + */ + +struct emc6w201_data { + struct i2c_client *client; + struct mutex update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* registers values */ + u8 in[3][6]; + s8 temp[3][6]; + u16 fan[2][5]; +}; + +/* + * Combine LSB and MSB registers in a single value + * Locking: must be called with data->update_lock held + */ +static u16 emc6w201_read16(struct i2c_client *client, u8 reg) +{ + int lsb, msb; + + lsb = i2c_smbus_read_byte_data(client, reg); + msb = i2c_smbus_read_byte_data(client, reg + 1); + if (unlikely(lsb < 0 || msb < 0)) { + dev_err(&client->dev, "%d-bit %s failed at 0x%02x\n", + 16, "read", reg); + return 0xFFFF; /* Arbitrary value */ + } + + return (msb << 8) | lsb; +} + +/* + * Write 16-bit value to LSB and MSB registers + * Locking: must be called with data->update_lock held + */ +static int emc6w201_write16(struct i2c_client *client, u8 reg, u16 val) +{ + int err; + + err = i2c_smbus_write_byte_data(client, reg, val & 0xff); + if (likely(!err)) + err = i2c_smbus_write_byte_data(client, reg + 1, val >> 8); + if (unlikely(err < 0)) + dev_err(&client->dev, "%d-bit %s failed at 0x%02x\n", + 16, "write", reg); + + return err; +} + +/* Read 8-bit value from register */ +static u8 emc6w201_read8(struct i2c_client *client, u8 reg) +{ + int val; + + val = i2c_smbus_read_byte_data(client, reg); + if (unlikely(val < 0)) { + dev_err(&client->dev, "%d-bit %s failed at 0x%02x\n", + 8, "read", reg); + return 0x00; /* Arbitrary value */ + } + + return val; +} + +/* Write 8-bit value to register */ +static int emc6w201_write8(struct i2c_client *client, u8 reg, u8 val) +{ + int err; + + err = i2c_smbus_write_byte_data(client, reg, val); + if (unlikely(err < 0)) + dev_err(&client->dev, "%d-bit %s failed at 0x%02x\n", + 8, "write", reg); + + return err; +} + +static struct emc6w201_data *emc6w201_update_device(struct device *dev) +{ + struct emc6w201_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + for (nr = 0; nr < 6; nr++) { + data->in[input][nr] = + emc6w201_read8(client, + EMC6W201_REG_IN(nr)); + data->in[min][nr] = + emc6w201_read8(client, + EMC6W201_REG_IN_LOW(nr)); + data->in[max][nr] = + emc6w201_read8(client, + EMC6W201_REG_IN_HIGH(nr)); + } + + for (nr = 0; nr < 6; nr++) { + data->temp[input][nr] = + emc6w201_read8(client, + EMC6W201_REG_TEMP(nr)); + data->temp[min][nr] = + emc6w201_read8(client, + EMC6W201_REG_TEMP_LOW(nr)); + data->temp[max][nr] = + emc6w201_read8(client, + EMC6W201_REG_TEMP_HIGH(nr)); + } + + for (nr = 0; nr < 5; nr++) { + data->fan[input][nr] = + emc6w201_read16(client, + EMC6W201_REG_FAN(nr)); + data->fan[min][nr] = + emc6w201_read16(client, + EMC6W201_REG_FAN_MIN(nr)); + } + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Sysfs callback functions + */ + +static const s16 nominal_mv[6] = { 2500, 1500, 3300, 5000, 1500, 1500 }; + +static ssize_t show_in(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct emc6w201_data *data = emc6w201_update_device(dev); + int sf = to_sensor_dev_attr_2(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->nr; + + return sprintf(buf, "%u\n", + (unsigned)data->in[sf][nr] * nominal_mv[nr] / 0xC0); +} + +static ssize_t set_in(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct emc6w201_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int sf = to_sensor_dev_attr_2(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->nr; + int err; + long val; + u8 reg; + + err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(val, 0, 255 * nominal_mv[nr] / 192); + val = DIV_ROUND_CLOSEST(val * 192, nominal_mv[nr]); + reg = (sf == min) ? EMC6W201_REG_IN_LOW(nr) + : EMC6W201_REG_IN_HIGH(nr); + + mutex_lock(&data->update_lock); + data->in[sf][nr] = val; + err = emc6w201_write8(client, reg, data->in[sf][nr]); + mutex_unlock(&data->update_lock); + + return err < 0 ? err : count; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct emc6w201_data *data = emc6w201_update_device(dev); + int sf = to_sensor_dev_attr_2(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->nr; + + return sprintf(buf, "%d\n", (int)data->temp[sf][nr] * 1000); +} + +static ssize_t set_temp(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct emc6w201_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int sf = to_sensor_dev_attr_2(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->nr; + int err; + long val; + u8 reg; + + err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(val, -127000, 127000); + val = DIV_ROUND_CLOSEST(val, 1000); + reg = (sf == min) ? EMC6W201_REG_TEMP_LOW(nr) + : EMC6W201_REG_TEMP_HIGH(nr); + + mutex_lock(&data->update_lock); + data->temp[sf][nr] = val; + err = emc6w201_write8(client, reg, data->temp[sf][nr]); + mutex_unlock(&data->update_lock); + + return err < 0 ? err : count; +} + +static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct emc6w201_data *data = emc6w201_update_device(dev); + int sf = to_sensor_dev_attr_2(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->nr; + unsigned rpm; + + if (data->fan[sf][nr] == 0 || data->fan[sf][nr] == 0xFFFF) + rpm = 0; + else + rpm = 5400000U / data->fan[sf][nr]; + + return sprintf(buf, "%u\n", rpm); +} + +static ssize_t set_fan(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct emc6w201_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int sf = to_sensor_dev_attr_2(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->nr; + int err; + unsigned long val; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (val == 0) { + val = 0xFFFF; + } else { + val = DIV_ROUND_CLOSEST(5400000U, val); + val = clamp_val(val, 0, 0xFFFE); + } + + mutex_lock(&data->update_lock); + data->fan[sf][nr] = val; + err = emc6w201_write16(client, EMC6W201_REG_FAN_MIN(nr), + data->fan[sf][nr]); + mutex_unlock(&data->update_lock); + + return err < 0 ? err : count; +} + +static SENSOR_DEVICE_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, input); +static SENSOR_DEVICE_ATTR_2(in0_min, S_IRUGO | S_IWUSR, show_in, set_in, + 0, min); +static SENSOR_DEVICE_ATTR_2(in0_max, S_IRUGO | S_IWUSR, show_in, set_in, + 0, max); +static SENSOR_DEVICE_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 1, input); +static SENSOR_DEVICE_ATTR_2(in1_min, S_IRUGO | S_IWUSR, show_in, set_in, + 1, min); +static SENSOR_DEVICE_ATTR_2(in1_max, S_IRUGO | S_IWUSR, show_in, set_in, + 1, max); +static SENSOR_DEVICE_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 2, input); +static SENSOR_DEVICE_ATTR_2(in2_min, S_IRUGO | S_IWUSR, show_in, set_in, + 2, min); +static SENSOR_DEVICE_ATTR_2(in2_max, S_IRUGO | S_IWUSR, show_in, set_in, + 2, max); +static SENSOR_DEVICE_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 3, input); +static SENSOR_DEVICE_ATTR_2(in3_min, S_IRUGO | S_IWUSR, show_in, set_in, + 3, min); +static SENSOR_DEVICE_ATTR_2(in3_max, S_IRUGO | S_IWUSR, show_in, set_in, + 3, max); +static SENSOR_DEVICE_ATTR_2(in4_input, S_IRUGO, show_in, NULL, 4, input); +static SENSOR_DEVICE_ATTR_2(in4_min, S_IRUGO | S_IWUSR, show_in, set_in, + 4, min); +static SENSOR_DEVICE_ATTR_2(in4_max, S_IRUGO | S_IWUSR, show_in, set_in, + 4, max); +static SENSOR_DEVICE_ATTR_2(in5_input, S_IRUGO, show_in, NULL, 5, input); +static SENSOR_DEVICE_ATTR_2(in5_min, S_IRUGO | S_IWUSR, show_in, set_in, + 5, min); +static SENSOR_DEVICE_ATTR_2(in5_max, S_IRUGO | S_IWUSR, show_in, set_in, + 5, max); + +static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, input); +static SENSOR_DEVICE_ATTR_2(temp1_min, S_IRUGO | S_IWUSR, show_temp, set_temp, + 0, min); +static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 0, max); +static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 1, input); +static SENSOR_DEVICE_ATTR_2(temp2_min, S_IRUGO | S_IWUSR, show_temp, set_temp, + 1, min); +static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 1, max); +static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 2, input); +static SENSOR_DEVICE_ATTR_2(temp3_min, S_IRUGO | S_IWUSR, show_temp, set_temp, + 2, min); +static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 2, max); +static SENSOR_DEVICE_ATTR_2(temp4_input, S_IRUGO, show_temp, NULL, 3, input); +static SENSOR_DEVICE_ATTR_2(temp4_min, S_IRUGO | S_IWUSR, show_temp, set_temp, + 3, min); +static SENSOR_DEVICE_ATTR_2(temp4_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 3, max); +static SENSOR_DEVICE_ATTR_2(temp5_input, S_IRUGO, show_temp, NULL, 4, input); +static SENSOR_DEVICE_ATTR_2(temp5_min, S_IRUGO | S_IWUSR, show_temp, set_temp, + 4, min); +static SENSOR_DEVICE_ATTR_2(temp5_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 4, max); +static SENSOR_DEVICE_ATTR_2(temp6_input, S_IRUGO, show_temp, NULL, 5, input); +static SENSOR_DEVICE_ATTR_2(temp6_min, S_IRUGO | S_IWUSR, show_temp, set_temp, + 5, min); +static SENSOR_DEVICE_ATTR_2(temp6_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 5, max); + +static SENSOR_DEVICE_ATTR_2(fan1_input, S_IRUGO, show_fan, NULL, 0, input); +static SENSOR_DEVICE_ATTR_2(fan1_min, S_IRUGO | S_IWUSR, show_fan, set_fan, + 0, min); +static SENSOR_DEVICE_ATTR_2(fan2_input, S_IRUGO, show_fan, NULL, 1, input); +static SENSOR_DEVICE_ATTR_2(fan2_min, S_IRUGO | S_IWUSR, show_fan, set_fan, + 1, min); +static SENSOR_DEVICE_ATTR_2(fan3_input, S_IRUGO, show_fan, NULL, 2, input); +static SENSOR_DEVICE_ATTR_2(fan3_min, S_IRUGO | S_IWUSR, show_fan, set_fan, + 2, min); +static SENSOR_DEVICE_ATTR_2(fan4_input, S_IRUGO, show_fan, NULL, 3, input); +static SENSOR_DEVICE_ATTR_2(fan4_min, S_IRUGO | S_IWUSR, show_fan, set_fan, + 3, min); +static SENSOR_DEVICE_ATTR_2(fan5_input, S_IRUGO, show_fan, NULL, 4, input); +static SENSOR_DEVICE_ATTR_2(fan5_min, S_IRUGO | S_IWUSR, show_fan, set_fan, + 4, min); + +static struct attribute *emc6w201_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp5_min.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp6_input.dev_attr.attr, + &sensor_dev_attr_temp6_min.dev_attr.attr, + &sensor_dev_attr_temp6_max.dev_attr.attr, + + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan4_min.dev_attr.attr, + &sensor_dev_attr_fan5_input.dev_attr.attr, + &sensor_dev_attr_fan5_min.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(emc6w201); + +/* + * Driver interface + */ + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int emc6w201_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int company, verstep, config; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* Identification */ + company = i2c_smbus_read_byte_data(client, EMC6W201_REG_COMPANY); + if (company != 0x5C) + return -ENODEV; + verstep = i2c_smbus_read_byte_data(client, EMC6W201_REG_VERSTEP); + if (verstep < 0 || (verstep & 0xF0) != 0xB0) + return -ENODEV; + if ((verstep & 0x0F) > 2) { + dev_dbg(&client->dev, "Unknown EMC6W201 stepping %d\n", + verstep & 0x0F); + return -ENODEV; + } + + /* Check configuration */ + config = i2c_smbus_read_byte_data(client, EMC6W201_REG_CONFIG); + if (config < 0 || (config & 0xF4) != 0x04) + return -ENODEV; + if (!(config & 0x01)) { + dev_err(&client->dev, "Monitoring not enabled\n"); + return -ENODEV; + } + + strlcpy(info->type, "emc6w201", I2C_NAME_SIZE); + + return 0; +} + +static int emc6w201_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct emc6w201_data *data; + struct device *hwmon_dev; + + data = devm_kzalloc(dev, sizeof(struct emc6w201_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + emc6w201_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id emc6w201_id[] = { + { "emc6w201", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, emc6w201_id); + +static struct i2c_driver emc6w201_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "emc6w201", + }, + .probe = emc6w201_probe, + .id_table = emc6w201_id, + .detect = emc6w201_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(emc6w201_driver); + +MODULE_AUTHOR("Jean Delvare "); +MODULE_DESCRIPTION("SMSC EMC6W201 hardware monitoring driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/f71805f.c b/drivers/hwmon/f71805f.c new file mode 100644 index 000000000..623736d2a --- /dev/null +++ b/drivers/hwmon/f71805f.c @@ -0,0 +1,1662 @@ +/* + * f71805f.c - driver for the Fintek F71805F/FG and F71872F/FG Super-I/O + * chips integrated hardware monitoring features + * Copyright (C) 2005-2006 Jean Delvare + * + * The F71805F/FG is a LPC Super-I/O chip made by Fintek. It integrates + * complete hardware monitoring features: voltage, fan and temperature + * sensors, and manual and automatic fan speed control. + * + * The F71872F/FG is almost the same, with two more voltages monitored, + * and 6 VID inputs. + * + * The F71806F/FG is essentially the same as the F71872F/FG. It even has + * the same chip ID, so the driver can't differentiate between. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +static struct platform_device *pdev; + +#define DRVNAME "f71805f" +enum kinds { f71805f, f71872f }; + +/* + * Super-I/O constants and functions + */ + +#define F71805F_LD_HWM 0x04 + +#define SIO_REG_LDSEL 0x07 /* Logical device select */ +#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ +#define SIO_REG_DEVREV 0x22 /* Device revision */ +#define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */ +#define SIO_REG_FNSEL1 0x29 /* Multi Function Select 1 (F71872F) */ +#define SIO_REG_ENABLE 0x30 /* Logical device enable */ +#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ + +#define SIO_FINTEK_ID 0x1934 +#define SIO_F71805F_ID 0x0406 +#define SIO_F71872F_ID 0x0341 + +static inline int +superio_inb(int base, int reg) +{ + outb(reg, base); + return inb(base + 1); +} + +static int +superio_inw(int base, int reg) +{ + int val; + outb(reg++, base); + val = inb(base + 1) << 8; + outb(reg, base); + val |= inb(base + 1); + return val; +} + +static inline void +superio_select(int base, int ld) +{ + outb(SIO_REG_LDSEL, base); + outb(ld, base + 1); +} + +static inline int +superio_enter(int base) +{ + if (!request_muxed_region(base, 2, DRVNAME)) + return -EBUSY; + + outb(0x87, base); + outb(0x87, base); + + return 0; +} + +static inline void +superio_exit(int base) +{ + outb(0xaa, base); + release_region(base, 2); +} + +/* + * ISA constants + */ + +#define REGION_LENGTH 8 +#define ADDR_REG_OFFSET 5 +#define DATA_REG_OFFSET 6 + +/* + * Registers + */ + +/* in nr from 0 to 10 (8-bit values) */ +#define F71805F_REG_IN(nr) (0x10 + (nr)) +#define F71805F_REG_IN_HIGH(nr) ((nr) < 10 ? 0x40 + 2 * (nr) : 0x2E) +#define F71805F_REG_IN_LOW(nr) ((nr) < 10 ? 0x41 + 2 * (nr) : 0x2F) +/* fan nr from 0 to 2 (12-bit values, two registers) */ +#define F71805F_REG_FAN(nr) (0x20 + 2 * (nr)) +#define F71805F_REG_FAN_LOW(nr) (0x28 + 2 * (nr)) +#define F71805F_REG_FAN_TARGET(nr) (0x69 + 16 * (nr)) +#define F71805F_REG_FAN_CTRL(nr) (0x60 + 16 * (nr)) +#define F71805F_REG_PWM_FREQ(nr) (0x63 + 16 * (nr)) +#define F71805F_REG_PWM_DUTY(nr) (0x6B + 16 * (nr)) +/* temp nr from 0 to 2 (8-bit values) */ +#define F71805F_REG_TEMP(nr) (0x1B + (nr)) +#define F71805F_REG_TEMP_HIGH(nr) (0x54 + 2 * (nr)) +#define F71805F_REG_TEMP_HYST(nr) (0x55 + 2 * (nr)) +#define F71805F_REG_TEMP_MODE 0x01 +/* pwm/fan pwmnr from 0 to 2, auto point apnr from 0 to 2 */ +/* map Fintek numbers to our numbers as follows: 9->0, 5->1, 1->2 */ +#define F71805F_REG_PWM_AUTO_POINT_TEMP(pwmnr, apnr) \ + (0xA0 + 0x10 * (pwmnr) + (2 - (apnr))) +#define F71805F_REG_PWM_AUTO_POINT_FAN(pwmnr, apnr) \ + (0xA4 + 0x10 * (pwmnr) + \ + 2 * (2 - (apnr))) + +#define F71805F_REG_START 0x00 +/* status nr from 0 to 2 */ +#define F71805F_REG_STATUS(nr) (0x36 + (nr)) + +/* individual register bits */ +#define FAN_CTRL_DC_MODE 0x10 +#define FAN_CTRL_LATCH_FULL 0x08 +#define FAN_CTRL_MODE_MASK 0x03 +#define FAN_CTRL_MODE_SPEED 0x00 +#define FAN_CTRL_MODE_TEMPERATURE 0x01 +#define FAN_CTRL_MODE_MANUAL 0x02 + +/* + * Data structures and manipulation thereof + */ + +struct f71805f_auto_point { + u8 temp[3]; + u16 fan[3]; +}; + +struct f71805f_data { + unsigned short addr; + const char *name; + struct device *hwmon_dev; + + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + unsigned long last_limits; /* In jiffies */ + + /* Register values */ + u8 in[11]; + u8 in_high[11]; + u8 in_low[11]; + u16 has_in; + u16 fan[3]; + u16 fan_low[3]; + u16 fan_target[3]; + u8 fan_ctrl[3]; + u8 pwm[3]; + u8 pwm_freq[3]; + u8 temp[3]; + u8 temp_high[3]; + u8 temp_hyst[3]; + u8 temp_mode; + unsigned long alarms; + struct f71805f_auto_point auto_points[3]; +}; + +struct f71805f_sio_data { + enum kinds kind; + u8 fnsel1; +}; + +static inline long in_from_reg(u8 reg) +{ + return reg * 8; +} + +/* The 2 least significant bits are not used */ +static inline u8 in_to_reg(long val) +{ + if (val <= 0) + return 0; + if (val >= 2016) + return 0xfc; + return ((val + 16) / 32) << 2; +} + +/* in0 is downscaled by a factor 2 internally */ +static inline long in0_from_reg(u8 reg) +{ + return reg * 16; +} + +static inline u8 in0_to_reg(long val) +{ + if (val <= 0) + return 0; + if (val >= 4032) + return 0xfc; + return ((val + 32) / 64) << 2; +} + +/* The 4 most significant bits are not used */ +static inline long fan_from_reg(u16 reg) +{ + reg &= 0xfff; + if (!reg || reg == 0xfff) + return 0; + return 1500000 / reg; +} + +static inline u16 fan_to_reg(long rpm) +{ + /* + * If the low limit is set below what the chip can measure, + * store the largest possible 12-bit value in the registers, + * so that no alarm will ever trigger. + */ + if (rpm < 367) + return 0xfff; + return 1500000 / rpm; +} + +static inline unsigned long pwm_freq_from_reg(u8 reg) +{ + unsigned long clock = (reg & 0x80) ? 48000000UL : 1000000UL; + + reg &= 0x7f; + if (reg == 0) + reg++; + return clock / (reg << 8); +} + +static inline u8 pwm_freq_to_reg(unsigned long val) +{ + if (val >= 187500) /* The highest we can do */ + return 0x80; + if (val >= 1475) /* Use 48 MHz clock */ + return 0x80 | (48000000UL / (val << 8)); + if (val < 31) /* The lowest we can do */ + return 0x7f; + else /* Use 1 MHz clock */ + return 1000000UL / (val << 8); +} + +static inline int pwm_mode_from_reg(u8 reg) +{ + return !(reg & FAN_CTRL_DC_MODE); +} + +static inline long temp_from_reg(u8 reg) +{ + return reg * 1000; +} + +static inline u8 temp_to_reg(long val) +{ + if (val <= 0) + return 0; + if (val >= 1000 * 0xff) + return 0xff; + return (val + 500) / 1000; +} + +/* + * Device I/O access + */ + +/* Must be called with data->update_lock held, except during initialization */ +static u8 f71805f_read8(struct f71805f_data *data, u8 reg) +{ + outb(reg, data->addr + ADDR_REG_OFFSET); + return inb(data->addr + DATA_REG_OFFSET); +} + +/* Must be called with data->update_lock held, except during initialization */ +static void f71805f_write8(struct f71805f_data *data, u8 reg, u8 val) +{ + outb(reg, data->addr + ADDR_REG_OFFSET); + outb(val, data->addr + DATA_REG_OFFSET); +} + +/* + * It is important to read the MSB first, because doing so latches the + * value of the LSB, so we are sure both bytes belong to the same value. + * Must be called with data->update_lock held, except during initialization + */ +static u16 f71805f_read16(struct f71805f_data *data, u8 reg) +{ + u16 val; + + outb(reg, data->addr + ADDR_REG_OFFSET); + val = inb(data->addr + DATA_REG_OFFSET) << 8; + outb(++reg, data->addr + ADDR_REG_OFFSET); + val |= inb(data->addr + DATA_REG_OFFSET); + + return val; +} + +/* Must be called with data->update_lock held, except during initialization */ +static void f71805f_write16(struct f71805f_data *data, u8 reg, u16 val) +{ + outb(reg, data->addr + ADDR_REG_OFFSET); + outb(val >> 8, data->addr + DATA_REG_OFFSET); + outb(++reg, data->addr + ADDR_REG_OFFSET); + outb(val & 0xff, data->addr + DATA_REG_OFFSET); +} + +static struct f71805f_data *f71805f_update_device(struct device *dev) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + int nr, apnr; + + mutex_lock(&data->update_lock); + + /* Limit registers cache is refreshed after 60 seconds */ + if (time_after(jiffies, data->last_updated + 60 * HZ) + || !data->valid) { + for (nr = 0; nr < 11; nr++) { + if (!(data->has_in & (1 << nr))) + continue; + data->in_high[nr] = f71805f_read8(data, + F71805F_REG_IN_HIGH(nr)); + data->in_low[nr] = f71805f_read8(data, + F71805F_REG_IN_LOW(nr)); + } + for (nr = 0; nr < 3; nr++) { + data->fan_low[nr] = f71805f_read16(data, + F71805F_REG_FAN_LOW(nr)); + data->fan_target[nr] = f71805f_read16(data, + F71805F_REG_FAN_TARGET(nr)); + data->pwm_freq[nr] = f71805f_read8(data, + F71805F_REG_PWM_FREQ(nr)); + } + for (nr = 0; nr < 3; nr++) { + data->temp_high[nr] = f71805f_read8(data, + F71805F_REG_TEMP_HIGH(nr)); + data->temp_hyst[nr] = f71805f_read8(data, + F71805F_REG_TEMP_HYST(nr)); + } + data->temp_mode = f71805f_read8(data, F71805F_REG_TEMP_MODE); + for (nr = 0; nr < 3; nr++) { + for (apnr = 0; apnr < 3; apnr++) { + data->auto_points[nr].temp[apnr] = + f71805f_read8(data, + F71805F_REG_PWM_AUTO_POINT_TEMP(nr, + apnr)); + data->auto_points[nr].fan[apnr] = + f71805f_read16(data, + F71805F_REG_PWM_AUTO_POINT_FAN(nr, + apnr)); + } + } + + data->last_limits = jiffies; + } + + /* Measurement registers cache is refreshed after 1 second */ + if (time_after(jiffies, data->last_updated + HZ) + || !data->valid) { + for (nr = 0; nr < 11; nr++) { + if (!(data->has_in & (1 << nr))) + continue; + data->in[nr] = f71805f_read8(data, + F71805F_REG_IN(nr)); + } + for (nr = 0; nr < 3; nr++) { + data->fan[nr] = f71805f_read16(data, + F71805F_REG_FAN(nr)); + data->fan_ctrl[nr] = f71805f_read8(data, + F71805F_REG_FAN_CTRL(nr)); + data->pwm[nr] = f71805f_read8(data, + F71805F_REG_PWM_DUTY(nr)); + } + for (nr = 0; nr < 3; nr++) { + data->temp[nr] = f71805f_read8(data, + F71805F_REG_TEMP(nr)); + } + data->alarms = f71805f_read8(data, F71805F_REG_STATUS(0)) + + (f71805f_read8(data, F71805F_REG_STATUS(1)) << 8) + + (f71805f_read8(data, F71805F_REG_STATUS(2)) << 16); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Sysfs interface + */ + +static ssize_t show_in0(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%ld\n", in0_from_reg(data->in[nr])); +} + +static ssize_t show_in0_max(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%ld\n", in0_from_reg(data->in_high[nr])); +} + +static ssize_t show_in0_min(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%ld\n", in0_from_reg(data->in_low[nr])); +} + +static ssize_t set_in0_max(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_high[nr] = in0_to_reg(val); + f71805f_write8(data, F71805F_REG_IN_HIGH(nr), data->in_high[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t set_in0_min(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_low[nr] = in0_to_reg(val); + f71805f_write8(data, F71805F_REG_IN_LOW(nr), data->in_low[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_in(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%ld\n", in_from_reg(data->in[nr])); +} + +static ssize_t show_in_max(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%ld\n", in_from_reg(data->in_high[nr])); +} + +static ssize_t show_in_min(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%ld\n", in_from_reg(data->in_low[nr])); +} + +static ssize_t set_in_max(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_high[nr] = in_to_reg(val); + f71805f_write8(data, F71805F_REG_IN_HIGH(nr), data->in_high[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t set_in_min(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_low[nr] = in_to_reg(val); + f71805f_write8(data, F71805F_REG_IN_LOW(nr), data->in_low[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%ld\n", fan_from_reg(data->fan[nr])); +} + +static ssize_t show_fan_min(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%ld\n", fan_from_reg(data->fan_low[nr])); +} + +static ssize_t show_fan_target(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%ld\n", fan_from_reg(data->fan_target[nr])); +} + +static ssize_t set_fan_min(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_low[nr] = fan_to_reg(val); + f71805f_write16(data, F71805F_REG_FAN_LOW(nr), data->fan_low[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t set_fan_target(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_target[nr] = fan_to_reg(val); + f71805f_write16(data, F71805F_REG_FAN_TARGET(nr), + data->fan_target[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%d\n", (int)data->pwm[nr]); +} + +static ssize_t show_pwm_enable(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + int mode; + + switch (data->fan_ctrl[nr] & FAN_CTRL_MODE_MASK) { + case FAN_CTRL_MODE_SPEED: + mode = 3; + break; + case FAN_CTRL_MODE_TEMPERATURE: + mode = 2; + break; + default: /* MANUAL */ + mode = 1; + } + + return sprintf(buf, "%d\n", mode); +} + +static ssize_t show_pwm_freq(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%lu\n", pwm_freq_from_reg(data->pwm_freq[nr])); +} + +static ssize_t show_pwm_mode(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%d\n", pwm_mode_from_reg(data->fan_ctrl[nr])); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 255) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->pwm[nr] = val; + f71805f_write8(data, F71805F_REG_PWM_DUTY(nr), data->pwm[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static struct attribute *f71805f_attr_pwm[]; + +static ssize_t set_pwm_enable(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + u8 reg; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val < 1 || val > 3) + return -EINVAL; + + if (val > 1) { /* Automatic mode, user can't set PWM value */ + if (sysfs_chmod_file(&dev->kobj, f71805f_attr_pwm[nr], + S_IRUGO)) + dev_dbg(dev, "chmod -w pwm%d failed\n", nr + 1); + } + + mutex_lock(&data->update_lock); + reg = f71805f_read8(data, F71805F_REG_FAN_CTRL(nr)) + & ~FAN_CTRL_MODE_MASK; + switch (val) { + case 1: + reg |= FAN_CTRL_MODE_MANUAL; + break; + case 2: + reg |= FAN_CTRL_MODE_TEMPERATURE; + break; + case 3: + reg |= FAN_CTRL_MODE_SPEED; + break; + } + data->fan_ctrl[nr] = reg; + f71805f_write8(data, F71805F_REG_FAN_CTRL(nr), reg); + mutex_unlock(&data->update_lock); + + if (val == 1) { /* Manual mode, user can set PWM value */ + if (sysfs_chmod_file(&dev->kobj, f71805f_attr_pwm[nr], + S_IRUGO | S_IWUSR)) + dev_dbg(dev, "chmod +w pwm%d failed\n", nr + 1); + } + + return count; +} + +static ssize_t set_pwm_freq(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->pwm_freq[nr] = pwm_freq_to_reg(val); + f71805f_write8(data, F71805F_REG_PWM_FREQ(nr), data->pwm_freq[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_auto_point_temp(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + int pwmnr = attr->nr; + int apnr = attr->index; + + return sprintf(buf, "%ld\n", + temp_from_reg(data->auto_points[pwmnr].temp[apnr])); +} + +static ssize_t set_pwm_auto_point_temp(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + int pwmnr = attr->nr; + int apnr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->auto_points[pwmnr].temp[apnr] = temp_to_reg(val); + f71805f_write8(data, F71805F_REG_PWM_AUTO_POINT_TEMP(pwmnr, apnr), + data->auto_points[pwmnr].temp[apnr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_auto_point_fan(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + int pwmnr = attr->nr; + int apnr = attr->index; + + return sprintf(buf, "%ld\n", + fan_from_reg(data->auto_points[pwmnr].fan[apnr])); +} + +static ssize_t set_pwm_auto_point_fan(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + int pwmnr = attr->nr; + int apnr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->auto_points[pwmnr].fan[apnr] = fan_to_reg(val); + f71805f_write16(data, F71805F_REG_PWM_AUTO_POINT_FAN(pwmnr, apnr), + data->auto_points[pwmnr].fan[apnr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%ld\n", temp_from_reg(data->temp[nr])); +} + +static ssize_t show_temp_max(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%ld\n", temp_from_reg(data->temp_high[nr])); +} + +static ssize_t show_temp_hyst(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + return sprintf(buf, "%ld\n", temp_from_reg(data->temp_hyst[nr])); +} + +static ssize_t show_temp_type(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + + /* 3 is diode, 4 is thermistor */ + return sprintf(buf, "%u\n", (data->temp_mode & (1 << nr)) ? 3 : 4); +} + +static ssize_t set_temp_max(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_high[nr] = temp_to_reg(val); + f71805f_write8(data, F71805F_REG_TEMP_HIGH(nr), data->temp_high[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t set_temp_hyst(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int nr = attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_hyst[nr] = temp_to_reg(val); + f71805f_write8(data, F71805F_REG_TEMP_HYST(nr), data->temp_hyst[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t alarms_in_show(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + + return sprintf(buf, "%lu\n", data->alarms & 0x7ff); +} + +static ssize_t alarms_fan_show(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + + return sprintf(buf, "%lu\n", (data->alarms >> 16) & 0x07); +} + +static ssize_t alarms_temp_show(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + + return sprintf(buf, "%lu\n", (data->alarms >> 11) & 0x07); +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = f71805f_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int bitnr = attr->index; + + return sprintf(buf, "%lu\n", (data->alarms >> bitnr) & 1); +} + +static ssize_t name_show(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71805f_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} + +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL, 0); +static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO | S_IWUSR, + show_in0_max, set_in0_max, 0); +static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO | S_IWUSR, + show_in0_min, set_in0_min, 0); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 1); +static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, + show_in_max, set_in_max, 1); +static SENSOR_DEVICE_ATTR(in1_min, S_IRUGO | S_IWUSR, + show_in_min, set_in_min, 1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_in, NULL, 2); +static SENSOR_DEVICE_ATTR(in2_max, S_IRUGO | S_IWUSR, + show_in_max, set_in_max, 2); +static SENSOR_DEVICE_ATTR(in2_min, S_IRUGO | S_IWUSR, + show_in_min, set_in_min, 2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_in, NULL, 3); +static SENSOR_DEVICE_ATTR(in3_max, S_IRUGO | S_IWUSR, + show_in_max, set_in_max, 3); +static SENSOR_DEVICE_ATTR(in3_min, S_IRUGO | S_IWUSR, + show_in_min, set_in_min, 3); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_in, NULL, 4); +static SENSOR_DEVICE_ATTR(in4_max, S_IRUGO | S_IWUSR, + show_in_max, set_in_max, 4); +static SENSOR_DEVICE_ATTR(in4_min, S_IRUGO | S_IWUSR, + show_in_min, set_in_min, 4); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, show_in, NULL, 5); +static SENSOR_DEVICE_ATTR(in5_max, S_IRUGO | S_IWUSR, + show_in_max, set_in_max, 5); +static SENSOR_DEVICE_ATTR(in5_min, S_IRUGO | S_IWUSR, + show_in_min, set_in_min, 5); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, show_in, NULL, 6); +static SENSOR_DEVICE_ATTR(in6_max, S_IRUGO | S_IWUSR, + show_in_max, set_in_max, 6); +static SENSOR_DEVICE_ATTR(in6_min, S_IRUGO | S_IWUSR, + show_in_min, set_in_min, 6); +static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, show_in, NULL, 7); +static SENSOR_DEVICE_ATTR(in7_max, S_IRUGO | S_IWUSR, + show_in_max, set_in_max, 7); +static SENSOR_DEVICE_ATTR(in7_min, S_IRUGO | S_IWUSR, + show_in_min, set_in_min, 7); +static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, show_in, NULL, 8); +static SENSOR_DEVICE_ATTR(in8_max, S_IRUGO | S_IWUSR, + show_in_max, set_in_max, 8); +static SENSOR_DEVICE_ATTR(in8_min, S_IRUGO | S_IWUSR, + show_in_min, set_in_min, 8); +static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, show_in0, NULL, 9); +static SENSOR_DEVICE_ATTR(in9_max, S_IRUGO | S_IWUSR, + show_in0_max, set_in0_max, 9); +static SENSOR_DEVICE_ATTR(in9_min, S_IRUGO | S_IWUSR, + show_in0_min, set_in0_min, 9); +static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, show_in0, NULL, 10); +static SENSOR_DEVICE_ATTR(in10_max, S_IRUGO | S_IWUSR, + show_in0_max, set_in0_max, 10); +static SENSOR_DEVICE_ATTR(in10_min, S_IRUGO | S_IWUSR, + show_in0_min, set_in0_min, 10); + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); +static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR, + show_fan_min, set_fan_min, 0); +static SENSOR_DEVICE_ATTR(fan1_target, S_IRUGO | S_IWUSR, + show_fan_target, set_fan_target, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); +static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO | S_IWUSR, + show_fan_min, set_fan_min, 1); +static SENSOR_DEVICE_ATTR(fan2_target, S_IRUGO | S_IWUSR, + show_fan_target, set_fan_target, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2); +static SENSOR_DEVICE_ATTR(fan3_min, S_IRUGO | S_IWUSR, + show_fan_min, set_fan_min, 2); +static SENSOR_DEVICE_ATTR(fan3_target, S_IRUGO | S_IWUSR, + show_fan_target, set_fan_target, 2); + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, + show_temp_max, set_temp_max, 0); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IRUGO | S_IWUSR, + show_temp_hyst, set_temp_hyst, 0); +static SENSOR_DEVICE_ATTR(temp1_type, S_IRUGO, show_temp_type, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, + show_temp_max, set_temp_max, 1); +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IRUGO | S_IWUSR, + show_temp_hyst, set_temp_hyst, 1); +static SENSOR_DEVICE_ATTR(temp2_type, S_IRUGO, show_temp_type, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO | S_IWUSR, + show_temp_max, set_temp_max, 2); +static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IRUGO | S_IWUSR, + show_temp_hyst, set_temp_hyst, 2); +static SENSOR_DEVICE_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2); + +/* + * pwm (value) files are created read-only, write permission is + * then added or removed dynamically as needed + */ +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO, show_pwm, set_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, + show_pwm_enable, set_pwm_enable, 0); +static SENSOR_DEVICE_ATTR(pwm1_freq, S_IRUGO | S_IWUSR, + show_pwm_freq, set_pwm_freq, 0); +static SENSOR_DEVICE_ATTR(pwm1_mode, S_IRUGO, show_pwm_mode, NULL, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO, show_pwm, set_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IRUGO | S_IWUSR, + show_pwm_enable, set_pwm_enable, 1); +static SENSOR_DEVICE_ATTR(pwm2_freq, S_IRUGO | S_IWUSR, + show_pwm_freq, set_pwm_freq, 1); +static SENSOR_DEVICE_ATTR(pwm2_mode, S_IRUGO, show_pwm_mode, NULL, 1); +static SENSOR_DEVICE_ATTR(pwm3, S_IRUGO, show_pwm, set_pwm, 2); +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IRUGO | S_IWUSR, + show_pwm_enable, set_pwm_enable, 2); +static SENSOR_DEVICE_ATTR(pwm3_freq, S_IRUGO | S_IWUSR, + show_pwm_freq, set_pwm_freq, 2); +static SENSOR_DEVICE_ATTR(pwm3_mode, S_IRUGO, show_pwm_mode, NULL, 2); + +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point1_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 0, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point1_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 0, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point2_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 0, 1); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point2_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 0, 1); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point3_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 0, 2); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point3_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 0, 2); + +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point1_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 1, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point1_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 1, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point2_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 1, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point2_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 1, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point3_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 1, 2); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point3_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 1, 2); + +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point1_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 2, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point1_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 2, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point2_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 2, 1); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point2_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 2, 1); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point3_temp, S_IRUGO | S_IWUSR, + show_pwm_auto_point_temp, set_pwm_auto_point_temp, + 2, 2); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point3_fan, S_IRUGO | S_IWUSR, + show_pwm_auto_point_fan, set_pwm_auto_point_fan, + 2, 2); + +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(in9_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(in10_alarm, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 12); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 13); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 16); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 17); +static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, 18); +static DEVICE_ATTR_RO(alarms_in); +static DEVICE_ATTR_RO(alarms_fan); +static DEVICE_ATTR_RO(alarms_temp); + +static DEVICE_ATTR_RO(name); + +static struct attribute *f71805f_attributes[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_target.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_target.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_target.dev_attr.attr, + + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_mode.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm2_mode.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_pwm3_mode.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_type.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_type.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_type.dev_attr.attr, + + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_fan.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_fan.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_fan.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_fan.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_fan.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_fan.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_fan.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_fan.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point3_fan.dev_attr.attr, + + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + &sensor_dev_attr_in6_alarm.dev_attr.attr, + &sensor_dev_attr_in7_alarm.dev_attr.attr, + &dev_attr_alarms_in.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + &dev_attr_alarms_temp.attr, + &dev_attr_alarms_fan.attr, + + &dev_attr_name.attr, + NULL +}; + +static const struct attribute_group f71805f_group = { + .attrs = f71805f_attributes, +}; + +static struct attribute *f71805f_attributes_optin[4][5] = { + { + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in8_max.dev_attr.attr, + &sensor_dev_attr_in8_min.dev_attr.attr, + &sensor_dev_attr_in8_alarm.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in9_max.dev_attr.attr, + &sensor_dev_attr_in9_min.dev_attr.attr, + &sensor_dev_attr_in9_alarm.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in10_max.dev_attr.attr, + &sensor_dev_attr_in10_min.dev_attr.attr, + &sensor_dev_attr_in10_alarm.dev_attr.attr, + NULL + } +}; + +static const struct attribute_group f71805f_group_optin[4] = { + { .attrs = f71805f_attributes_optin[0] }, + { .attrs = f71805f_attributes_optin[1] }, + { .attrs = f71805f_attributes_optin[2] }, + { .attrs = f71805f_attributes_optin[3] }, +}; + +/* + * We don't include pwm_freq files in the arrays above, because they must be + * created conditionally (only if pwm_mode is 1 == PWM) + */ +static struct attribute *f71805f_attributes_pwm_freq[] = { + &sensor_dev_attr_pwm1_freq.dev_attr.attr, + &sensor_dev_attr_pwm2_freq.dev_attr.attr, + &sensor_dev_attr_pwm3_freq.dev_attr.attr, + NULL +}; + +static const struct attribute_group f71805f_group_pwm_freq = { + .attrs = f71805f_attributes_pwm_freq, +}; + +/* We also need an indexed access to pwmN files to toggle writability */ +static struct attribute *f71805f_attr_pwm[] = { + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, +}; + +/* + * Device registration and initialization + */ + +static void f71805f_init_device(struct f71805f_data *data) +{ + u8 reg; + int i; + + reg = f71805f_read8(data, F71805F_REG_START); + if ((reg & 0x41) != 0x01) { + pr_debug("Starting monitoring operations\n"); + f71805f_write8(data, F71805F_REG_START, (reg | 0x01) & ~0x40); + } + + /* + * Fan monitoring can be disabled. If it is, we won't be polling + * the register values, and won't create the related sysfs files. + */ + for (i = 0; i < 3; i++) { + data->fan_ctrl[i] = f71805f_read8(data, + F71805F_REG_FAN_CTRL(i)); + /* + * Clear latch full bit, else "speed mode" fan speed control + * doesn't work + */ + if (data->fan_ctrl[i] & FAN_CTRL_LATCH_FULL) { + data->fan_ctrl[i] &= ~FAN_CTRL_LATCH_FULL; + f71805f_write8(data, F71805F_REG_FAN_CTRL(i), + data->fan_ctrl[i]); + } + } +} + +static int f71805f_probe(struct platform_device *pdev) +{ + struct f71805f_sio_data *sio_data = dev_get_platdata(&pdev->dev); + struct f71805f_data *data; + struct resource *res; + int i, err; + + static const char * const names[] = { + "f71805f", + "f71872f", + }; + + data = devm_kzalloc(&pdev->dev, sizeof(struct f71805f_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(&pdev->dev, res->start + ADDR_REG_OFFSET, 2, + DRVNAME)) { + dev_err(&pdev->dev, "Failed to request region 0x%lx-0x%lx\n", + (unsigned long)(res->start + ADDR_REG_OFFSET), + (unsigned long)(res->start + ADDR_REG_OFFSET + 1)); + return -EBUSY; + } + data->addr = res->start; + data->name = names[sio_data->kind]; + mutex_init(&data->update_lock); + + platform_set_drvdata(pdev, data); + + /* Some voltage inputs depend on chip model and configuration */ + switch (sio_data->kind) { + case f71805f: + data->has_in = 0x1ff; + break; + case f71872f: + data->has_in = 0x6ef; + if (sio_data->fnsel1 & 0x01) + data->has_in |= (1 << 4); /* in4 */ + if (sio_data->fnsel1 & 0x02) + data->has_in |= (1 << 8); /* in8 */ + break; + } + + /* Initialize the F71805F chip */ + f71805f_init_device(data); + + /* Register sysfs interface files */ + err = sysfs_create_group(&pdev->dev.kobj, &f71805f_group); + if (err) + return err; + if (data->has_in & (1 << 4)) { /* in4 */ + err = sysfs_create_group(&pdev->dev.kobj, + &f71805f_group_optin[0]); + if (err) + goto exit_remove_files; + } + if (data->has_in & (1 << 8)) { /* in8 */ + err = sysfs_create_group(&pdev->dev.kobj, + &f71805f_group_optin[1]); + if (err) + goto exit_remove_files; + } + if (data->has_in & (1 << 9)) { /* in9 (F71872F/FG only) */ + err = sysfs_create_group(&pdev->dev.kobj, + &f71805f_group_optin[2]); + if (err) + goto exit_remove_files; + } + if (data->has_in & (1 << 10)) { /* in9 (F71872F/FG only) */ + err = sysfs_create_group(&pdev->dev.kobj, + &f71805f_group_optin[3]); + if (err) + goto exit_remove_files; + } + for (i = 0; i < 3; i++) { + /* If control mode is PWM, create pwm_freq file */ + if (!(data->fan_ctrl[i] & FAN_CTRL_DC_MODE)) { + err = sysfs_create_file(&pdev->dev.kobj, + f71805f_attributes_pwm_freq[i]); + if (err) + goto exit_remove_files; + } + /* If PWM is in manual mode, add write permission */ + if (data->fan_ctrl[i] & FAN_CTRL_MODE_MANUAL) { + err = sysfs_chmod_file(&pdev->dev.kobj, + f71805f_attr_pwm[i], + S_IRUGO | S_IWUSR); + if (err) { + dev_err(&pdev->dev, "chmod +w pwm%d failed\n", + i + 1); + goto exit_remove_files; + } + } + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", err); + goto exit_remove_files; + } + + return 0; + +exit_remove_files: + sysfs_remove_group(&pdev->dev.kobj, &f71805f_group); + for (i = 0; i < 4; i++) + sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]); + sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq); + return err; +} + +static int f71805f_remove(struct platform_device *pdev) +{ + struct f71805f_data *data = platform_get_drvdata(pdev); + int i; + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &f71805f_group); + for (i = 0; i < 4; i++) + sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]); + sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq); + + return 0; +} + +static struct platform_driver f71805f_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = f71805f_probe, + .remove = f71805f_remove, +}; + +static int __init f71805f_device_add(unsigned short address, + const struct f71805f_sio_data *sio_data) +{ + struct resource res = { + .start = address, + .end = address + REGION_LENGTH - 1, + .flags = IORESOURCE_IO, + }; + int err; + + pdev = platform_device_alloc(DRVNAME, address); + if (!pdev) { + err = -ENOMEM; + pr_err("Device allocation failed\n"); + goto exit; + } + + res.name = pdev->name; + err = acpi_check_resource_conflict(&res); + if (err) + goto exit_device_put; + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add_data(pdev, sio_data, + sizeof(struct f71805f_sio_data)); + if (err) { + pr_err("Platform data allocation failed\n"); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + +static int __init f71805f_find(int sioaddr, unsigned short *address, + struct f71805f_sio_data *sio_data) +{ + int err; + u16 devid; + + static const char * const names[] = { + "F71805F/FG", + "F71872F/FG or F71806F/FG", + }; + + err = superio_enter(sioaddr); + if (err) + return err; + + err = -ENODEV; + devid = superio_inw(sioaddr, SIO_REG_MANID); + if (devid != SIO_FINTEK_ID) + goto exit; + + devid = force_id ? force_id : superio_inw(sioaddr, SIO_REG_DEVID); + switch (devid) { + case SIO_F71805F_ID: + sio_data->kind = f71805f; + break; + case SIO_F71872F_ID: + sio_data->kind = f71872f; + sio_data->fnsel1 = superio_inb(sioaddr, SIO_REG_FNSEL1); + break; + default: + pr_info("Unsupported Fintek device, skipping\n"); + goto exit; + } + + superio_select(sioaddr, F71805F_LD_HWM); + if (!(superio_inb(sioaddr, SIO_REG_ENABLE) & 0x01)) { + pr_warn("Device not activated, skipping\n"); + goto exit; + } + + *address = superio_inw(sioaddr, SIO_REG_ADDR); + if (*address == 0) { + pr_warn("Base address not set, skipping\n"); + goto exit; + } + *address &= ~(REGION_LENGTH - 1); /* Ignore 3 LSB */ + + err = 0; + pr_info("Found %s chip at %#x, revision %u\n", + names[sio_data->kind], *address, + superio_inb(sioaddr, SIO_REG_DEVREV)); + +exit: + superio_exit(sioaddr); + return err; +} + +static int __init f71805f_init(void) +{ + int err; + unsigned short address; + struct f71805f_sio_data sio_data; + + if (f71805f_find(0x2e, &address, &sio_data) + && f71805f_find(0x4e, &address, &sio_data)) + return -ENODEV; + + err = platform_driver_register(&f71805f_driver); + if (err) + goto exit; + + /* Sets global pdev as a side effect */ + err = f71805f_device_add(address, &sio_data); + if (err) + goto exit_driver; + + return 0; + +exit_driver: + platform_driver_unregister(&f71805f_driver); +exit: + return err; +} + +static void __exit f71805f_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&f71805f_driver); +} + +MODULE_AUTHOR("Jean Delvare "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("F71805F/F71872F hardware monitoring driver"); + +module_init(f71805f_init); +module_exit(f71805f_exit); diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c new file mode 100644 index 000000000..4010b6174 --- /dev/null +++ b/drivers/hwmon/f71882fg.c @@ -0,0 +1,2812 @@ +/*************************************************************************** + * Copyright (C) 2006 by Hans Edgington * + * Copyright (C) 2007-2011 Hans de Goede * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "f71882fg" + +#define SIO_F71858FG_LD_HWM 0x02 /* Hardware monitor logical device */ +#define SIO_F71882FG_LD_HWM 0x04 /* Hardware monitor logical device */ +#define SIO_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */ +#define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */ + +#define SIO_REG_LDSEL 0x07 /* Logical device select */ +#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ +#define SIO_REG_DEVREV 0x22 /* Device revision */ +#define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */ +#define SIO_REG_ENABLE 0x30 /* Logical device enable */ +#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ + +#define SIO_FINTEK_ID 0x1934 /* Manufacturers ID */ +#define SIO_F71808E_ID 0x0901 /* Chipset ID */ +#define SIO_F71808A_ID 0x1001 /* Chipset ID */ +#define SIO_F71858_ID 0x0507 /* Chipset ID */ +#define SIO_F71862_ID 0x0601 /* Chipset ID */ +#define SIO_F71868_ID 0x1106 /* Chipset ID */ +#define SIO_F71869_ID 0x0814 /* Chipset ID */ +#define SIO_F71869A_ID 0x1007 /* Chipset ID */ +#define SIO_F71882_ID 0x0541 /* Chipset ID */ +#define SIO_F71889_ID 0x0723 /* Chipset ID */ +#define SIO_F71889E_ID 0x0909 /* Chipset ID */ +#define SIO_F71889A_ID 0x1005 /* Chipset ID */ +#define SIO_F8000_ID 0x0581 /* Chipset ID */ +#define SIO_F81768D_ID 0x1210 /* Chipset ID */ +#define SIO_F81865_ID 0x0704 /* Chipset ID */ +#define SIO_F81866_ID 0x1010 /* Chipset ID */ + +#define REGION_LENGTH 8 +#define ADDR_REG_OFFSET 5 +#define DATA_REG_OFFSET 6 + +#define F71882FG_REG_IN_STATUS 0x12 /* f7188x only */ +#define F71882FG_REG_IN_BEEP 0x13 /* f7188x only */ +#define F71882FG_REG_IN(nr) (0x20 + (nr)) +#define F71882FG_REG_IN1_HIGH 0x32 /* f7188x only */ + +#define F81866_REG_IN_STATUS 0x16 /* F81866 only */ +#define F81866_REG_IN_BEEP 0x17 /* F81866 only */ +#define F81866_REG_IN1_HIGH 0x3a /* F81866 only */ + +#define F71882FG_REG_FAN(nr) (0xA0 + (16 * (nr))) +#define F71882FG_REG_FAN_TARGET(nr) (0xA2 + (16 * (nr))) +#define F71882FG_REG_FAN_FULL_SPEED(nr) (0xA4 + (16 * (nr))) +#define F71882FG_REG_FAN_STATUS 0x92 +#define F71882FG_REG_FAN_BEEP 0x93 + +#define F71882FG_REG_TEMP(nr) (0x70 + 2 * (nr)) +#define F71882FG_REG_TEMP_OVT(nr) (0x80 + 2 * (nr)) +#define F71882FG_REG_TEMP_HIGH(nr) (0x81 + 2 * (nr)) +#define F71882FG_REG_TEMP_STATUS 0x62 +#define F71882FG_REG_TEMP_BEEP 0x63 +#define F71882FG_REG_TEMP_CONFIG 0x69 +#define F71882FG_REG_TEMP_HYST(nr) (0x6C + (nr)) +#define F71882FG_REG_TEMP_TYPE 0x6B +#define F71882FG_REG_TEMP_DIODE_OPEN 0x6F + +#define F71882FG_REG_PWM(nr) (0xA3 + (16 * (nr))) +#define F71882FG_REG_PWM_TYPE 0x94 +#define F71882FG_REG_PWM_ENABLE 0x96 + +#define F71882FG_REG_FAN_HYST(nr) (0x98 + (nr)) + +#define F71882FG_REG_FAN_FAULT_T 0x9F +#define F71882FG_FAN_NEG_TEMP_EN 0x20 +#define F71882FG_FAN_PROG_SEL 0x80 + +#define F71882FG_REG_POINT_PWM(pwm, point) (0xAA + (point) + (16 * (pwm))) +#define F71882FG_REG_POINT_TEMP(pwm, point) (0xA6 + (point) + (16 * (pwm))) +#define F71882FG_REG_POINT_MAPPING(nr) (0xAF + 16 * (nr)) + +#define F71882FG_REG_START 0x01 + +#define F71882FG_MAX_INS 11 + +#define FAN_MIN_DETECT 366 /* Lowest detectable fanspeed */ + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +enum chips { f71808e, f71808a, f71858fg, f71862fg, f71868a, f71869, f71869a, + f71882fg, f71889fg, f71889ed, f71889a, f8000, f81768d, f81865f, + f81866a}; + +static const char *const f71882fg_names[] = { + "f71808e", + "f71808a", + "f71858fg", + "f71862fg", + "f71868a", + "f71869", /* Both f71869f and f71869e, reg. compatible and same id */ + "f71869a", + "f71882fg", + "f71889fg", /* f81801u too, same id */ + "f71889ed", + "f71889a", + "f8000", + "f81768d", + "f81865f", + "f81866a", +}; + +static const char f71882fg_has_in[][F71882FG_MAX_INS] = { + [f71808e] = { 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0 }, + [f71808a] = { 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0 }, + [f71858fg] = { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + [f71862fg] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, + [f71868a] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + [f71869] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, + [f71869a] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, + [f71882fg] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, + [f71889fg] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, + [f71889ed] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, + [f71889a] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, + [f8000] = { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + [f81768d] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + [f81865f] = { 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + [f81866a] = { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, +}; + +static const char f71882fg_has_in1_alarm[] = { + [f71808e] = 0, + [f71808a] = 0, + [f71858fg] = 0, + [f71862fg] = 0, + [f71868a] = 0, + [f71869] = 0, + [f71869a] = 0, + [f71882fg] = 1, + [f71889fg] = 1, + [f71889ed] = 1, + [f71889a] = 1, + [f8000] = 0, + [f81768d] = 1, + [f81865f] = 1, + [f81866a] = 1, +}; + +static const char f71882fg_fan_has_beep[] = { + [f71808e] = 0, + [f71808a] = 0, + [f71858fg] = 0, + [f71862fg] = 1, + [f71868a] = 1, + [f71869] = 1, + [f71869a] = 1, + [f71882fg] = 1, + [f71889fg] = 1, + [f71889ed] = 1, + [f71889a] = 1, + [f8000] = 0, + [f81768d] = 1, + [f81865f] = 1, + [f81866a] = 1, +}; + +static const char f71882fg_nr_fans[] = { + [f71808e] = 3, + [f71808a] = 2, /* +1 fan which is monitor + simple pwm only */ + [f71858fg] = 3, + [f71862fg] = 3, + [f71868a] = 3, + [f71869] = 3, + [f71869a] = 3, + [f71882fg] = 4, + [f71889fg] = 3, + [f71889ed] = 3, + [f71889a] = 3, + [f8000] = 3, /* +1 fan which is monitor only */ + [f81768d] = 3, + [f81865f] = 2, + [f81866a] = 3, +}; + +static const char f71882fg_temp_has_beep[] = { + [f71808e] = 0, + [f71808a] = 1, + [f71858fg] = 0, + [f71862fg] = 1, + [f71868a] = 1, + [f71869] = 1, + [f71869a] = 1, + [f71882fg] = 1, + [f71889fg] = 1, + [f71889ed] = 1, + [f71889a] = 1, + [f8000] = 0, + [f81768d] = 1, + [f81865f] = 1, + [f81866a] = 1, +}; + +static const char f71882fg_nr_temps[] = { + [f71808e] = 2, + [f71808a] = 2, + [f71858fg] = 3, + [f71862fg] = 3, + [f71868a] = 3, + [f71869] = 3, + [f71869a] = 3, + [f71882fg] = 3, + [f71889fg] = 3, + [f71889ed] = 3, + [f71889a] = 3, + [f8000] = 3, + [f81768d] = 3, + [f81865f] = 2, + [f81866a] = 3, +}; + +static struct platform_device *f71882fg_pdev; + +/* Super-I/O Function prototypes */ +static inline int superio_inb(int base, int reg); +static inline int superio_inw(int base, int reg); +static inline int superio_enter(int base); +static inline void superio_select(int base, int ld); +static inline void superio_exit(int base); + +struct f71882fg_sio_data { + enum chips type; +}; + +struct f71882fg_data { + unsigned short addr; + enum chips type; + struct device *hwmon_dev; + + struct mutex update_lock; + int temp_start; /* temp numbering start (0 or 1) */ + char valid; /* !=0 if following fields are valid */ + char auto_point_temp_signed; + unsigned long last_updated; /* In jiffies */ + unsigned long last_limits; /* In jiffies */ + + /* Register Values */ + u8 in[F71882FG_MAX_INS]; + u8 in1_max; + u8 in_status; + u8 in_beep; + u16 fan[4]; + u16 fan_target[4]; + u16 fan_full_speed[4]; + u8 fan_status; + u8 fan_beep; + /* + * Note: all models have max 3 temperature channels, but on some + * they are addressed as 0-2 and on others as 1-3, so for coding + * convenience we reserve space for 4 channels + */ + u16 temp[4]; + u8 temp_ovt[4]; + u8 temp_high[4]; + u8 temp_hyst[2]; /* 2 hysts stored per reg */ + u8 temp_type[4]; + u8 temp_status; + u8 temp_beep; + u8 temp_diode_open; + u8 temp_config; + u8 pwm[4]; + u8 pwm_enable; + u8 pwm_auto_point_hyst[2]; + u8 pwm_auto_point_mapping[4]; + u8 pwm_auto_point_pwm[4][5]; + s8 pwm_auto_point_temp[4][4]; +}; + +/* Sysfs in */ +static ssize_t show_in(struct device *dev, struct device_attribute *devattr, + char *buf); +static ssize_t show_in_max(struct device *dev, struct device_attribute + *devattr, char *buf); +static ssize_t store_in_max(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count); +static ssize_t show_in_beep(struct device *dev, struct device_attribute + *devattr, char *buf); +static ssize_t store_in_beep(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count); +static ssize_t show_in_alarm(struct device *dev, struct device_attribute + *devattr, char *buf); +/* Sysfs Fan */ +static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, + char *buf); +static ssize_t show_fan_full_speed(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t store_fan_full_speed(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count); +static ssize_t show_fan_beep(struct device *dev, struct device_attribute + *devattr, char *buf); +static ssize_t store_fan_beep(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count); +static ssize_t show_fan_alarm(struct device *dev, struct device_attribute + *devattr, char *buf); +/* Sysfs Temp */ +static ssize_t show_temp(struct device *dev, struct device_attribute + *devattr, char *buf); +static ssize_t show_temp_max(struct device *dev, struct device_attribute + *devattr, char *buf); +static ssize_t store_temp_max(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count); +static ssize_t show_temp_max_hyst(struct device *dev, struct device_attribute + *devattr, char *buf); +static ssize_t store_temp_max_hyst(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count); +static ssize_t show_temp_crit(struct device *dev, struct device_attribute + *devattr, char *buf); +static ssize_t store_temp_crit(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count); +static ssize_t show_temp_crit_hyst(struct device *dev, struct device_attribute + *devattr, char *buf); +static ssize_t show_temp_type(struct device *dev, struct device_attribute + *devattr, char *buf); +static ssize_t show_temp_beep(struct device *dev, struct device_attribute + *devattr, char *buf); +static ssize_t store_temp_beep(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count); +static ssize_t show_temp_alarm(struct device *dev, struct device_attribute + *devattr, char *buf); +static ssize_t show_temp_fault(struct device *dev, struct device_attribute + *devattr, char *buf); +/* PWM and Auto point control */ +static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr, + char *buf); +static ssize_t store_pwm(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count); +static ssize_t show_simple_pwm(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t store_simple_pwm(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count); +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t store_pwm_enable(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count); +static ssize_t show_pwm_interpolate(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t store_pwm_interpolate(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count); +static ssize_t show_pwm_auto_point_channel(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t store_pwm_auto_point_channel(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count); +static ssize_t show_pwm_auto_point_temp_hyst(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t store_pwm_auto_point_temp_hyst(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count); +static ssize_t show_pwm_auto_point_pwm(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t store_pwm_auto_point_pwm(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count); +static ssize_t show_pwm_auto_point_temp(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t store_pwm_auto_point_temp(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count); +/* Sysfs misc */ +static ssize_t name_show(struct device *dev, struct device_attribute *devattr, + char *buf); + +static int f71882fg_probe(struct platform_device *pdev); +static int f71882fg_remove(struct platform_device *pdev); + +static struct platform_driver f71882fg_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = f71882fg_probe, + .remove = f71882fg_remove, +}; + +static DEVICE_ATTR_RO(name); + +/* + * Temp attr for the f71858fg, the f71858fg is special as it has its + * temperature indexes start at 0 (the others start at 1) + */ +static struct sensor_device_attribute_2 f71858fg_temp_attr[] = { + SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0), + SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_max, + store_temp_max, 0, 0), + SENSOR_ATTR_2(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 0, 0), + SENSOR_ATTR_2(temp1_max_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 0), + SENSOR_ATTR_2(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit, + store_temp_crit, 0, 0), + SENSOR_ATTR_2(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, + 0, 0), + SENSOR_ATTR_2(temp1_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 4), + SENSOR_ATTR_2(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0, 0), + SENSOR_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 1), + SENSOR_ATTR_2(temp2_max, S_IRUGO|S_IWUSR, show_temp_max, + store_temp_max, 0, 1), + SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 0, 1), + SENSOR_ATTR_2(temp2_max_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 1), + SENSOR_ATTR_2(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit, + store_temp_crit, 0, 1), + SENSOR_ATTR_2(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, + 0, 1), + SENSOR_ATTR_2(temp2_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 5), + SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_temp_fault, NULL, 0, 1), + SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 2), + SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_max, + store_temp_max, 0, 2), + SENSOR_ATTR_2(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 0, 2), + SENSOR_ATTR_2(temp3_max_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 2), + SENSOR_ATTR_2(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit, + store_temp_crit, 0, 2), + SENSOR_ATTR_2(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, + 0, 2), + SENSOR_ATTR_2(temp3_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 6), + SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 0, 2), +}; + +/* Temp attr for the standard models */ +static struct sensor_device_attribute_2 fxxxx_temp_attr[3][9] = { { + SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 1), + SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_max, + store_temp_max, 0, 1), + SENSOR_ATTR_2(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 0, 1), + /* + * Should really be temp1_max_alarm, but older versions did not handle + * the max and crit alarms separately and lm_sensors v2 depends on the + * presence of temp#_alarm files. The same goes for temp2/3 _alarm. + */ + SENSOR_ATTR_2(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 1), + SENSOR_ATTR_2(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit, + store_temp_crit, 0, 1), + SENSOR_ATTR_2(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, + 0, 1), + SENSOR_ATTR_2(temp1_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 5), + SENSOR_ATTR_2(temp1_type, S_IRUGO, show_temp_type, NULL, 0, 1), + SENSOR_ATTR_2(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0, 1), +}, { + SENSOR_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 2), + SENSOR_ATTR_2(temp2_max, S_IRUGO|S_IWUSR, show_temp_max, + store_temp_max, 0, 2), + SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 0, 2), + /* Should be temp2_max_alarm, see temp1_alarm note */ + SENSOR_ATTR_2(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 2), + SENSOR_ATTR_2(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit, + store_temp_crit, 0, 2), + SENSOR_ATTR_2(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, + 0, 2), + SENSOR_ATTR_2(temp2_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 6), + SENSOR_ATTR_2(temp2_type, S_IRUGO, show_temp_type, NULL, 0, 2), + SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_temp_fault, NULL, 0, 2), +}, { + SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 3), + SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_max, + store_temp_max, 0, 3), + SENSOR_ATTR_2(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 0, 3), + /* Should be temp3_max_alarm, see temp1_alarm note */ + SENSOR_ATTR_2(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 3), + SENSOR_ATTR_2(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit, + store_temp_crit, 0, 3), + SENSOR_ATTR_2(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, + 0, 3), + SENSOR_ATTR_2(temp3_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 7), + SENSOR_ATTR_2(temp3_type, S_IRUGO, show_temp_type, NULL, 0, 3), + SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 0, 3), +} }; + +/* Temp attr for models which can beep on temp alarm */ +static struct sensor_device_attribute_2 fxxxx_temp_beep_attr[3][2] = { { + SENSOR_ATTR_2(temp1_max_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 1), + SENSOR_ATTR_2(temp1_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 5), +}, { + SENSOR_ATTR_2(temp2_max_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 2), + SENSOR_ATTR_2(temp2_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 6), +}, { + SENSOR_ATTR_2(temp3_max_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 3), + SENSOR_ATTR_2(temp3_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 7), +} }; + +static struct sensor_device_attribute_2 f81866_temp_beep_attr[3][2] = { { + SENSOR_ATTR_2(temp1_max_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 0), + SENSOR_ATTR_2(temp1_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 4), +}, { + SENSOR_ATTR_2(temp2_max_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 1), + SENSOR_ATTR_2(temp2_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 5), +}, { + SENSOR_ATTR_2(temp3_max_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 2), + SENSOR_ATTR_2(temp3_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 6), +} }; + +/* + * Temp attr for the f8000 + * Note on the f8000 temp_ovt (crit) is used as max, and temp_high (max) + * is used as hysteresis value to clear alarms + * Also like the f71858fg its temperature indexes start at 0 + */ +static struct sensor_device_attribute_2 f8000_temp_attr[] = { + SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0), + SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_crit, + store_temp_crit, 0, 0), + SENSOR_ATTR_2(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max, + store_temp_max, 0, 0), + SENSOR_ATTR_2(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 4), + SENSOR_ATTR_2(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0, 0), + SENSOR_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 1), + SENSOR_ATTR_2(temp2_max, S_IRUGO|S_IWUSR, show_temp_crit, + store_temp_crit, 0, 1), + SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max, + store_temp_max, 0, 1), + SENSOR_ATTR_2(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 5), + SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_temp_fault, NULL, 0, 1), + SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 2), + SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_crit, + store_temp_crit, 0, 2), + SENSOR_ATTR_2(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max, + store_temp_max, 0, 2), + SENSOR_ATTR_2(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 6), + SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 0, 2), +}; + +/* in attr for all models */ +static struct sensor_device_attribute_2 fxxxx_in_attr[] = { + SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0), + SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 0, 1), + SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 0, 2), + SENSOR_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 0, 3), + SENSOR_ATTR_2(in4_input, S_IRUGO, show_in, NULL, 0, 4), + SENSOR_ATTR_2(in5_input, S_IRUGO, show_in, NULL, 0, 5), + SENSOR_ATTR_2(in6_input, S_IRUGO, show_in, NULL, 0, 6), + SENSOR_ATTR_2(in7_input, S_IRUGO, show_in, NULL, 0, 7), + SENSOR_ATTR_2(in8_input, S_IRUGO, show_in, NULL, 0, 8), + SENSOR_ATTR_2(in9_input, S_IRUGO, show_in, NULL, 0, 9), + SENSOR_ATTR_2(in10_input, S_IRUGO, show_in, NULL, 0, 10), +}; + +/* For models with in1 alarm capability */ +static struct sensor_device_attribute_2 fxxxx_in1_alarm_attr[] = { + SENSOR_ATTR_2(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, + 0, 1), + SENSOR_ATTR_2(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, + 0, 1), + SENSOR_ATTR_2(in1_alarm, S_IRUGO, show_in_alarm, NULL, 0, 1), +}; + +/* Fan / PWM attr common to all models */ +static struct sensor_device_attribute_2 fxxxx_fan_attr[4][6] = { { + SENSOR_ATTR_2(fan1_input, S_IRUGO, show_fan, NULL, 0, 0), + SENSOR_ATTR_2(fan1_full_speed, S_IRUGO|S_IWUSR, + show_fan_full_speed, + store_fan_full_speed, 0, 0), + SENSOR_ATTR_2(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 0), + SENSOR_ATTR_2(pwm1, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 0), + SENSOR_ATTR_2(pwm1_enable, S_IRUGO|S_IWUSR, show_pwm_enable, + store_pwm_enable, 0, 0), + SENSOR_ATTR_2(pwm1_interpolate, S_IRUGO|S_IWUSR, + show_pwm_interpolate, store_pwm_interpolate, 0, 0), +}, { + SENSOR_ATTR_2(fan2_input, S_IRUGO, show_fan, NULL, 0, 1), + SENSOR_ATTR_2(fan2_full_speed, S_IRUGO|S_IWUSR, + show_fan_full_speed, + store_fan_full_speed, 0, 1), + SENSOR_ATTR_2(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 1), + SENSOR_ATTR_2(pwm2, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 1), + SENSOR_ATTR_2(pwm2_enable, S_IRUGO|S_IWUSR, show_pwm_enable, + store_pwm_enable, 0, 1), + SENSOR_ATTR_2(pwm2_interpolate, S_IRUGO|S_IWUSR, + show_pwm_interpolate, store_pwm_interpolate, 0, 1), +}, { + SENSOR_ATTR_2(fan3_input, S_IRUGO, show_fan, NULL, 0, 2), + SENSOR_ATTR_2(fan3_full_speed, S_IRUGO|S_IWUSR, + show_fan_full_speed, + store_fan_full_speed, 0, 2), + SENSOR_ATTR_2(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 2), + SENSOR_ATTR_2(pwm3, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 2), + SENSOR_ATTR_2(pwm3_enable, S_IRUGO|S_IWUSR, show_pwm_enable, + store_pwm_enable, 0, 2), + SENSOR_ATTR_2(pwm3_interpolate, S_IRUGO|S_IWUSR, + show_pwm_interpolate, store_pwm_interpolate, 0, 2), +}, { + SENSOR_ATTR_2(fan4_input, S_IRUGO, show_fan, NULL, 0, 3), + SENSOR_ATTR_2(fan4_full_speed, S_IRUGO|S_IWUSR, + show_fan_full_speed, + store_fan_full_speed, 0, 3), + SENSOR_ATTR_2(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 3), + SENSOR_ATTR_2(pwm4, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 3), + SENSOR_ATTR_2(pwm4_enable, S_IRUGO|S_IWUSR, show_pwm_enable, + store_pwm_enable, 0, 3), + SENSOR_ATTR_2(pwm4_interpolate, S_IRUGO|S_IWUSR, + show_pwm_interpolate, store_pwm_interpolate, 0, 3), +} }; + +/* Attr for the third fan of the f71808a, which only has manual pwm */ +static struct sensor_device_attribute_2 f71808a_fan3_attr[] = { + SENSOR_ATTR_2(fan3_input, S_IRUGO, show_fan, NULL, 0, 2), + SENSOR_ATTR_2(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 2), + SENSOR_ATTR_2(pwm3, S_IRUGO|S_IWUSR, + show_simple_pwm, store_simple_pwm, 0, 2), +}; + +/* Attr for models which can beep on Fan alarm */ +static struct sensor_device_attribute_2 fxxxx_fan_beep_attr[] = { + SENSOR_ATTR_2(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep, + store_fan_beep, 0, 0), + SENSOR_ATTR_2(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep, + store_fan_beep, 0, 1), + SENSOR_ATTR_2(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep, + store_fan_beep, 0, 2), + SENSOR_ATTR_2(fan4_beep, S_IRUGO|S_IWUSR, show_fan_beep, + store_fan_beep, 0, 3), +}; + +/* + * PWM attr for the f71862fg, fewer pwms and fewer zones per pwm than the + * standard models + */ +static struct sensor_device_attribute_2 f71862fg_auto_pwm_attr[3][7] = { { + SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 0), + SENSOR_ATTR_2(pwm1_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 0), + SENSOR_ATTR_2(pwm1_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 0), + SENSOR_ATTR_2(pwm1_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 0), + SENSOR_ATTR_2(pwm1_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 0), +}, { + SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 1), + SENSOR_ATTR_2(pwm2_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 1), + SENSOR_ATTR_2(pwm2_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 1), + SENSOR_ATTR_2(pwm2_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 1), + SENSOR_ATTR_2(pwm2_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 1), +}, { + SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 2), + SENSOR_ATTR_2(pwm3_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 2), + SENSOR_ATTR_2(pwm3_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 2), + SENSOR_ATTR_2(pwm3_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 2), + SENSOR_ATTR_2(pwm3_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 2), +} }; + +/* + * PWM attr for the f71808e/f71869, almost identical to the f71862fg, but the + * pwm setting when the temperature is above the pwmX_auto_point1_temp can be + * programmed instead of being hardcoded to 0xff + */ +static struct sensor_device_attribute_2 f71869_auto_pwm_attr[3][8] = { { + SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 0), + SENSOR_ATTR_2(pwm1_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 0), + SENSOR_ATTR_2(pwm1_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 0), + SENSOR_ATTR_2(pwm1_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 0), + SENSOR_ATTR_2(pwm1_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 0), +}, { + SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 1), + SENSOR_ATTR_2(pwm2_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 1), + SENSOR_ATTR_2(pwm2_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 1), + SENSOR_ATTR_2(pwm2_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 1), + SENSOR_ATTR_2(pwm2_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 1), +}, { + SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 2), + SENSOR_ATTR_2(pwm3_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 2), + SENSOR_ATTR_2(pwm3_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 2), + SENSOR_ATTR_2(pwm3_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 2), + SENSOR_ATTR_2(pwm3_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 2), +} }; + +/* PWM attr for the standard models */ +static struct sensor_device_attribute_2 fxxxx_auto_pwm_attr[4][14] = { { + SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 0), + SENSOR_ATTR_2(pwm1_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 0), + SENSOR_ATTR_2(pwm1_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 2, 0), + SENSOR_ATTR_2(pwm1_auto_point4_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 3, 0), + SENSOR_ATTR_2(pwm1_auto_point5_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 0), + SENSOR_ATTR_2(pwm1_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 1, 0), + SENSOR_ATTR_2(pwm1_auto_point3_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 2, 0), + SENSOR_ATTR_2(pwm1_auto_point4_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 0), + SENSOR_ATTR_2(pwm1_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 1, 0), + SENSOR_ATTR_2(pwm1_auto_point3_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 2, 0), + SENSOR_ATTR_2(pwm1_auto_point4_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 0), +}, { + SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 1), + SENSOR_ATTR_2(pwm2_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 1), + SENSOR_ATTR_2(pwm2_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 2, 1), + SENSOR_ATTR_2(pwm2_auto_point4_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 3, 1), + SENSOR_ATTR_2(pwm2_auto_point5_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 1), + SENSOR_ATTR_2(pwm2_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 1, 1), + SENSOR_ATTR_2(pwm2_auto_point3_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 2, 1), + SENSOR_ATTR_2(pwm2_auto_point4_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 1), + SENSOR_ATTR_2(pwm2_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 1, 1), + SENSOR_ATTR_2(pwm2_auto_point3_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 2, 1), + SENSOR_ATTR_2(pwm2_auto_point4_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 1), +}, { + SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 2), + SENSOR_ATTR_2(pwm3_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 2), + SENSOR_ATTR_2(pwm3_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 2, 2), + SENSOR_ATTR_2(pwm3_auto_point4_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 3, 2), + SENSOR_ATTR_2(pwm3_auto_point5_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 2), + SENSOR_ATTR_2(pwm3_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 1, 2), + SENSOR_ATTR_2(pwm3_auto_point3_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 2, 2), + SENSOR_ATTR_2(pwm3_auto_point4_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 2), + SENSOR_ATTR_2(pwm3_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 1, 2), + SENSOR_ATTR_2(pwm3_auto_point3_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 2, 2), + SENSOR_ATTR_2(pwm3_auto_point4_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 2), +}, { + SENSOR_ATTR_2(pwm4_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 3), + SENSOR_ATTR_2(pwm4_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 3), + SENSOR_ATTR_2(pwm4_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 3), + SENSOR_ATTR_2(pwm4_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 2, 3), + SENSOR_ATTR_2(pwm4_auto_point4_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 3, 3), + SENSOR_ATTR_2(pwm4_auto_point5_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 3), + SENSOR_ATTR_2(pwm4_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 3), + SENSOR_ATTR_2(pwm4_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 1, 3), + SENSOR_ATTR_2(pwm4_auto_point3_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 2, 3), + SENSOR_ATTR_2(pwm4_auto_point4_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 3), + SENSOR_ATTR_2(pwm4_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 3), + SENSOR_ATTR_2(pwm4_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 1, 3), + SENSOR_ATTR_2(pwm4_auto_point3_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 2, 3), + SENSOR_ATTR_2(pwm4_auto_point4_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 3), +} }; + +/* Fan attr specific to the f8000 (4th fan input can only measure speed) */ +static struct sensor_device_attribute_2 f8000_fan_attr[] = { + SENSOR_ATTR_2(fan4_input, S_IRUGO, show_fan, NULL, 0, 3), +}; + +/* + * PWM attr for the f8000, zones mapped to temp instead of to pwm! + * Also the register block at offset A0 maps to TEMP1 (so our temp2, as the + * F8000 starts counting temps at 0), B0 maps the TEMP2 and C0 maps to TEMP0 + */ +static struct sensor_device_attribute_2 f8000_auto_pwm_attr[3][14] = { { + SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 0), + SENSOR_ATTR_2(temp1_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 2), + SENSOR_ATTR_2(temp1_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 2), + SENSOR_ATTR_2(temp1_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 2, 2), + SENSOR_ATTR_2(temp1_auto_point4_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 3, 2), + SENSOR_ATTR_2(temp1_auto_point5_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 2), + SENSOR_ATTR_2(temp1_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 2), + SENSOR_ATTR_2(temp1_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 1, 2), + SENSOR_ATTR_2(temp1_auto_point3_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 2, 2), + SENSOR_ATTR_2(temp1_auto_point4_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 2), + SENSOR_ATTR_2(temp1_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 2), + SENSOR_ATTR_2(temp1_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 1, 2), + SENSOR_ATTR_2(temp1_auto_point3_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 2, 2), + SENSOR_ATTR_2(temp1_auto_point4_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 2), +}, { + SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 1), + SENSOR_ATTR_2(temp2_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 0), + SENSOR_ATTR_2(temp2_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 0), + SENSOR_ATTR_2(temp2_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 2, 0), + SENSOR_ATTR_2(temp2_auto_point4_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 3, 0), + SENSOR_ATTR_2(temp2_auto_point5_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 0), + SENSOR_ATTR_2(temp2_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 0), + SENSOR_ATTR_2(temp2_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 1, 0), + SENSOR_ATTR_2(temp2_auto_point3_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 2, 0), + SENSOR_ATTR_2(temp2_auto_point4_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 0), + SENSOR_ATTR_2(temp2_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 0), + SENSOR_ATTR_2(temp2_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 1, 0), + SENSOR_ATTR_2(temp2_auto_point3_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 2, 0), + SENSOR_ATTR_2(temp2_auto_point4_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 0), +}, { + SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 2), + SENSOR_ATTR_2(temp3_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 1), + SENSOR_ATTR_2(temp3_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 1), + SENSOR_ATTR_2(temp3_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 2, 1), + SENSOR_ATTR_2(temp3_auto_point4_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 3, 1), + SENSOR_ATTR_2(temp3_auto_point5_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 1), + SENSOR_ATTR_2(temp3_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 1), + SENSOR_ATTR_2(temp3_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 1, 1), + SENSOR_ATTR_2(temp3_auto_point3_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 2, 1), + SENSOR_ATTR_2(temp3_auto_point4_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 1), + SENSOR_ATTR_2(temp3_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 1), + SENSOR_ATTR_2(temp3_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 1, 1), + SENSOR_ATTR_2(temp3_auto_point3_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 2, 1), + SENSOR_ATTR_2(temp3_auto_point4_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 1), +} }; + +/* Super I/O functions */ +static inline int superio_inb(int base, int reg) +{ + outb(reg, base); + return inb(base + 1); +} + +static int superio_inw(int base, int reg) +{ + int val; + val = superio_inb(base, reg) << 8; + val |= superio_inb(base, reg + 1); + return val; +} + +static inline int superio_enter(int base) +{ + /* Don't step on other drivers' I/O space by accident */ + if (!request_muxed_region(base, 2, DRVNAME)) { + pr_err("I/O address 0x%04x already in use\n", base); + return -EBUSY; + } + + /* according to the datasheet the key must be send twice! */ + outb(SIO_UNLOCK_KEY, base); + outb(SIO_UNLOCK_KEY, base); + + return 0; +} + +static inline void superio_select(int base, int ld) +{ + outb(SIO_REG_LDSEL, base); + outb(ld, base + 1); +} + +static inline void superio_exit(int base) +{ + outb(SIO_LOCK_KEY, base); + release_region(base, 2); +} + +static inline int fan_from_reg(u16 reg) +{ + return reg ? (1500000 / reg) : 0; +} + +static inline u16 fan_to_reg(int fan) +{ + return fan ? (1500000 / fan) : 0; +} + +static u8 f71882fg_read8(struct f71882fg_data *data, u8 reg) +{ + u8 val; + + outb(reg, data->addr + ADDR_REG_OFFSET); + val = inb(data->addr + DATA_REG_OFFSET); + + return val; +} + +static u16 f71882fg_read16(struct f71882fg_data *data, u8 reg) +{ + u16 val; + + val = f71882fg_read8(data, reg) << 8; + val |= f71882fg_read8(data, reg + 1); + + return val; +} + +static void f71882fg_write8(struct f71882fg_data *data, u8 reg, u8 val) +{ + outb(reg, data->addr + ADDR_REG_OFFSET); + outb(val, data->addr + DATA_REG_OFFSET); +} + +static void f71882fg_write16(struct f71882fg_data *data, u8 reg, u16 val) +{ + f71882fg_write8(data, reg, val >> 8); + f71882fg_write8(data, reg + 1, val & 0xff); +} + +static u16 f71882fg_read_temp(struct f71882fg_data *data, int nr) +{ + if (data->type == f71858fg) + return f71882fg_read16(data, F71882FG_REG_TEMP(nr)); + else + return f71882fg_read8(data, F71882FG_REG_TEMP(nr)); +} + +static struct f71882fg_data *f71882fg_update_device(struct device *dev) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int nr_fans = f71882fg_nr_fans[data->type]; + int nr_temps = f71882fg_nr_temps[data->type]; + int nr, reg, point; + + mutex_lock(&data->update_lock); + + /* Update once every 60 seconds */ + if (time_after(jiffies, data->last_limits + 60 * HZ) || + !data->valid) { + if (f71882fg_has_in1_alarm[data->type]) { + if (data->type == f81866a) { + data->in1_max = + f71882fg_read8(data, + F81866_REG_IN1_HIGH); + data->in_beep = + f71882fg_read8(data, + F81866_REG_IN_BEEP); + } else { + data->in1_max = + f71882fg_read8(data, + F71882FG_REG_IN1_HIGH); + data->in_beep = + f71882fg_read8(data, + F71882FG_REG_IN_BEEP); + } + } + + /* Get High & boundary temps*/ + for (nr = data->temp_start; nr < nr_temps + data->temp_start; + nr++) { + data->temp_ovt[nr] = f71882fg_read8(data, + F71882FG_REG_TEMP_OVT(nr)); + data->temp_high[nr] = f71882fg_read8(data, + F71882FG_REG_TEMP_HIGH(nr)); + } + + if (data->type != f8000) { + data->temp_hyst[0] = f71882fg_read8(data, + F71882FG_REG_TEMP_HYST(0)); + data->temp_hyst[1] = f71882fg_read8(data, + F71882FG_REG_TEMP_HYST(1)); + } + /* All but the f71858fg / f8000 have this register */ + if ((data->type != f71858fg) && (data->type != f8000)) { + reg = f71882fg_read8(data, F71882FG_REG_TEMP_TYPE); + data->temp_type[1] = (reg & 0x02) ? 2 : 4; + data->temp_type[2] = (reg & 0x04) ? 2 : 4; + data->temp_type[3] = (reg & 0x08) ? 2 : 4; + } + + if (f71882fg_fan_has_beep[data->type]) + data->fan_beep = f71882fg_read8(data, + F71882FG_REG_FAN_BEEP); + + if (f71882fg_temp_has_beep[data->type]) + data->temp_beep = f71882fg_read8(data, + F71882FG_REG_TEMP_BEEP); + + data->pwm_enable = f71882fg_read8(data, + F71882FG_REG_PWM_ENABLE); + data->pwm_auto_point_hyst[0] = + f71882fg_read8(data, F71882FG_REG_FAN_HYST(0)); + data->pwm_auto_point_hyst[1] = + f71882fg_read8(data, F71882FG_REG_FAN_HYST(1)); + + for (nr = 0; nr < nr_fans; nr++) { + data->pwm_auto_point_mapping[nr] = + f71882fg_read8(data, + F71882FG_REG_POINT_MAPPING(nr)); + + switch (data->type) { + default: + for (point = 0; point < 5; point++) { + data->pwm_auto_point_pwm[nr][point] = + f71882fg_read8(data, + F71882FG_REG_POINT_PWM + (nr, point)); + } + for (point = 0; point < 4; point++) { + data->pwm_auto_point_temp[nr][point] = + f71882fg_read8(data, + F71882FG_REG_POINT_TEMP + (nr, point)); + } + break; + case f71808e: + case f71869: + data->pwm_auto_point_pwm[nr][0] = + f71882fg_read8(data, + F71882FG_REG_POINT_PWM(nr, 0)); + /* Fall through */ + case f71862fg: + data->pwm_auto_point_pwm[nr][1] = + f71882fg_read8(data, + F71882FG_REG_POINT_PWM + (nr, 1)); + data->pwm_auto_point_pwm[nr][4] = + f71882fg_read8(data, + F71882FG_REG_POINT_PWM + (nr, 4)); + data->pwm_auto_point_temp[nr][0] = + f71882fg_read8(data, + F71882FG_REG_POINT_TEMP + (nr, 0)); + data->pwm_auto_point_temp[nr][3] = + f71882fg_read8(data, + F71882FG_REG_POINT_TEMP + (nr, 3)); + break; + } + } + data->last_limits = jiffies; + } + + /* Update every second */ + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + data->temp_status = f71882fg_read8(data, + F71882FG_REG_TEMP_STATUS); + data->temp_diode_open = f71882fg_read8(data, + F71882FG_REG_TEMP_DIODE_OPEN); + for (nr = data->temp_start; nr < nr_temps + data->temp_start; + nr++) + data->temp[nr] = f71882fg_read_temp(data, nr); + + data->fan_status = f71882fg_read8(data, + F71882FG_REG_FAN_STATUS); + for (nr = 0; nr < nr_fans; nr++) { + data->fan[nr] = f71882fg_read16(data, + F71882FG_REG_FAN(nr)); + data->fan_target[nr] = + f71882fg_read16(data, F71882FG_REG_FAN_TARGET(nr)); + data->fan_full_speed[nr] = + f71882fg_read16(data, + F71882FG_REG_FAN_FULL_SPEED(nr)); + data->pwm[nr] = + f71882fg_read8(data, F71882FG_REG_PWM(nr)); + } + /* Some models have 1 more fan with limited capabilities */ + if (data->type == f71808a) { + data->fan[2] = f71882fg_read16(data, + F71882FG_REG_FAN(2)); + data->pwm[2] = f71882fg_read8(data, + F71882FG_REG_PWM(2)); + } + if (data->type == f8000) + data->fan[3] = f71882fg_read16(data, + F71882FG_REG_FAN(3)); + + if (f71882fg_has_in1_alarm[data->type]) { + if (data->type == f81866a) + data->in_status = f71882fg_read8(data, + F81866_REG_IN_STATUS); + + else + data->in_status = f71882fg_read8(data, + F71882FG_REG_IN_STATUS); + } + + for (nr = 0; nr < F71882FG_MAX_INS; nr++) + if (f71882fg_has_in[data->type][nr]) + data->in[nr] = f71882fg_read8(data, + F71882FG_REG_IN(nr)); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* Sysfs Interface */ +static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + int speed = fan_from_reg(data->fan[nr]); + + if (speed == FAN_MIN_DETECT) + speed = 0; + + return sprintf(buf, "%d\n", speed); +} + +static ssize_t show_fan_full_speed(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + int speed = fan_from_reg(data->fan_full_speed[nr]); + return sprintf(buf, "%d\n", speed); +} + +static ssize_t store_fan_full_speed(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err, nr = to_sensor_dev_attr_2(devattr)->index; + long val; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + val = clamp_val(val, 23, 1500000); + val = fan_to_reg(val); + + mutex_lock(&data->update_lock); + f71882fg_write16(data, F71882FG_REG_FAN_FULL_SPEED(nr), val); + data->fan_full_speed[nr] = val; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_fan_beep(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + if (data->fan_beep & (1 << nr)) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t store_fan_beep(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err, nr = to_sensor_dev_attr_2(devattr)->index; + unsigned long val; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_beep = f71882fg_read8(data, F71882FG_REG_FAN_BEEP); + if (val) + data->fan_beep |= 1 << nr; + else + data->fan_beep &= ~(1 << nr); + + f71882fg_write8(data, F71882FG_REG_FAN_BEEP, data->fan_beep); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_fan_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + if (data->fan_status & (1 << nr)) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t show_in(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + return sprintf(buf, "%d\n", data->in[nr] * 8); +} + +static ssize_t show_in_max(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + + return sprintf(buf, "%d\n", data->in1_max * 8); +} + +static ssize_t store_in_max(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err; + long val; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + val /= 8; + val = clamp_val(val, 0, 255); + + mutex_lock(&data->update_lock); + if (data->type == f81866a) + f71882fg_write8(data, F81866_REG_IN1_HIGH, val); + else + f71882fg_write8(data, F71882FG_REG_IN1_HIGH, val); + data->in1_max = val; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_in_beep(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + if (data->in_beep & (1 << nr)) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t store_in_beep(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err, nr = to_sensor_dev_attr_2(devattr)->index; + unsigned long val; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + if (data->type == f81866a) + data->in_beep = f71882fg_read8(data, F81866_REG_IN_BEEP); + else + data->in_beep = f71882fg_read8(data, F71882FG_REG_IN_BEEP); + + if (val) + data->in_beep |= 1 << nr; + else + data->in_beep &= ~(1 << nr); + + if (data->type == f81866a) + f71882fg_write8(data, F81866_REG_IN_BEEP, data->in_beep); + else + f71882fg_write8(data, F71882FG_REG_IN_BEEP, data->in_beep); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_in_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + if (data->in_status & (1 << nr)) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + int sign, temp; + + if (data->type == f71858fg) { + /* TEMP_TABLE_SEL 1 or 3 ? */ + if (data->temp_config & 1) { + sign = data->temp[nr] & 0x0001; + temp = (data->temp[nr] >> 5) & 0x7ff; + } else { + sign = data->temp[nr] & 0x8000; + temp = (data->temp[nr] >> 5) & 0x3ff; + } + temp *= 125; + if (sign) + temp -= 128000; + } else { + temp = ((s8)data->temp[nr]) * 1000; + } + + return sprintf(buf, "%d\n", temp); +} + +static ssize_t show_temp_max(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + return sprintf(buf, "%d\n", data->temp_high[nr] * 1000); +} + +static ssize_t store_temp_max(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err, nr = to_sensor_dev_attr_2(devattr)->index; + long val; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + val /= 1000; + val = clamp_val(val, 0, 255); + + mutex_lock(&data->update_lock); + f71882fg_write8(data, F71882FG_REG_TEMP_HIGH(nr), val); + data->temp_high[nr] = val; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_temp_max_hyst(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + int temp_max_hyst; + + mutex_lock(&data->update_lock); + if (nr & 1) + temp_max_hyst = data->temp_hyst[nr / 2] >> 4; + else + temp_max_hyst = data->temp_hyst[nr / 2] & 0x0f; + temp_max_hyst = (data->temp_high[nr] - temp_max_hyst) * 1000; + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", temp_max_hyst); +} + +static ssize_t store_temp_max_hyst(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err, nr = to_sensor_dev_attr_2(devattr)->index; + ssize_t ret = count; + u8 reg; + long val; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + val /= 1000; + + mutex_lock(&data->update_lock); + + /* convert abs to relative and check */ + data->temp_high[nr] = f71882fg_read8(data, F71882FG_REG_TEMP_HIGH(nr)); + val = clamp_val(val, data->temp_high[nr] - 15, data->temp_high[nr]); + val = data->temp_high[nr] - val; + + /* convert value to register contents */ + reg = f71882fg_read8(data, F71882FG_REG_TEMP_HYST(nr / 2)); + if (nr & 1) + reg = (reg & 0x0f) | (val << 4); + else + reg = (reg & 0xf0) | val; + f71882fg_write8(data, F71882FG_REG_TEMP_HYST(nr / 2), reg); + data->temp_hyst[nr / 2] = reg; + + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t show_temp_crit(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + return sprintf(buf, "%d\n", data->temp_ovt[nr] * 1000); +} + +static ssize_t store_temp_crit(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err, nr = to_sensor_dev_attr_2(devattr)->index; + long val; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + val /= 1000; + val = clamp_val(val, 0, 255); + + mutex_lock(&data->update_lock); + f71882fg_write8(data, F71882FG_REG_TEMP_OVT(nr), val); + data->temp_ovt[nr] = val; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_temp_crit_hyst(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + int temp_crit_hyst; + + mutex_lock(&data->update_lock); + if (nr & 1) + temp_crit_hyst = data->temp_hyst[nr / 2] >> 4; + else + temp_crit_hyst = data->temp_hyst[nr / 2] & 0x0f; + temp_crit_hyst = (data->temp_ovt[nr] - temp_crit_hyst) * 1000; + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", temp_crit_hyst); +} + +static ssize_t show_temp_type(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + return sprintf(buf, "%d\n", data->temp_type[nr]); +} + +static ssize_t show_temp_beep(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + if (data->temp_beep & (1 << nr)) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t store_temp_beep(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err, nr = to_sensor_dev_attr_2(devattr)->index; + unsigned long val; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_beep = f71882fg_read8(data, F71882FG_REG_TEMP_BEEP); + if (val) + data->temp_beep |= 1 << nr; + else + data->temp_beep &= ~(1 << nr); + + f71882fg_write8(data, F71882FG_REG_TEMP_BEEP, data->temp_beep); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_temp_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + if (data->temp_status & (1 << nr)) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t show_temp_fault(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + if (data->temp_diode_open & (1 << nr)) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t show_pwm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int val, nr = to_sensor_dev_attr_2(devattr)->index; + mutex_lock(&data->update_lock); + if (data->pwm_enable & (1 << (2 * nr))) + /* PWM mode */ + val = data->pwm[nr]; + else { + /* RPM mode */ + val = 255 * fan_from_reg(data->fan_target[nr]) + / fan_from_reg(data->fan_full_speed[nr]); + } + mutex_unlock(&data->update_lock); + return sprintf(buf, "%d\n", val); +} + +static ssize_t store_pwm(struct device *dev, + struct device_attribute *devattr, const char *buf, + size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err, nr = to_sensor_dev_attr_2(devattr)->index; + long val; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + val = clamp_val(val, 0, 255); + + mutex_lock(&data->update_lock); + data->pwm_enable = f71882fg_read8(data, F71882FG_REG_PWM_ENABLE); + if ((data->type == f8000 && ((data->pwm_enable >> 2 * nr) & 3) != 2) || + (data->type != f8000 && !((data->pwm_enable >> 2 * nr) & 2))) { + count = -EROFS; + goto leave; + } + if (data->pwm_enable & (1 << (2 * nr))) { + /* PWM mode */ + f71882fg_write8(data, F71882FG_REG_PWM(nr), val); + data->pwm[nr] = val; + } else { + /* RPM mode */ + int target, full_speed; + full_speed = f71882fg_read16(data, + F71882FG_REG_FAN_FULL_SPEED(nr)); + target = fan_to_reg(val * fan_from_reg(full_speed) / 255); + f71882fg_write16(data, F71882FG_REG_FAN_TARGET(nr), target); + data->fan_target[nr] = target; + data->fan_full_speed[nr] = full_speed; + } +leave: + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_simple_pwm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int val, nr = to_sensor_dev_attr_2(devattr)->index; + + val = data->pwm[nr]; + return sprintf(buf, "%d\n", val); +} + +static ssize_t store_simple_pwm(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err, nr = to_sensor_dev_attr_2(devattr)->index; + long val; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + val = clamp_val(val, 0, 255); + + mutex_lock(&data->update_lock); + f71882fg_write8(data, F71882FG_REG_PWM(nr), val); + data->pwm[nr] = val; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int result = 0; + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + switch ((data->pwm_enable >> 2 * nr) & 3) { + case 0: + case 1: + result = 2; /* Normal auto mode */ + break; + case 2: + result = 1; /* Manual mode */ + break; + case 3: + if (data->type == f8000) + result = 3; /* Thermostat mode */ + else + result = 1; /* Manual mode */ + break; + } + + return sprintf(buf, "%d\n", result); +} + +static ssize_t store_pwm_enable(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err, nr = to_sensor_dev_attr_2(devattr)->index; + long val; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + /* Special case for F8000 pwm channel 3 which only does auto mode */ + if (data->type == f8000 && nr == 2 && val != 2) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->pwm_enable = f71882fg_read8(data, F71882FG_REG_PWM_ENABLE); + /* Special case for F8000 auto PWM mode / Thermostat mode */ + if (data->type == f8000 && ((data->pwm_enable >> 2 * nr) & 1)) { + switch (val) { + case 2: + data->pwm_enable &= ~(2 << (2 * nr)); + break; /* Normal auto mode */ + case 3: + data->pwm_enable |= 2 << (2 * nr); + break; /* Thermostat mode */ + default: + count = -EINVAL; + goto leave; + } + } else { + switch (val) { + case 1: + /* The f71858fg does not support manual RPM mode */ + if (data->type == f71858fg && + ((data->pwm_enable >> (2 * nr)) & 1)) { + count = -EINVAL; + goto leave; + } + data->pwm_enable |= 2 << (2 * nr); + break; /* Manual */ + case 2: + data->pwm_enable &= ~(2 << (2 * nr)); + break; /* Normal auto mode */ + default: + count = -EINVAL; + goto leave; + } + } + f71882fg_write8(data, F71882FG_REG_PWM_ENABLE, data->pwm_enable); +leave: + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_auto_point_pwm(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + int result; + struct f71882fg_data *data = f71882fg_update_device(dev); + int pwm = to_sensor_dev_attr_2(devattr)->index; + int point = to_sensor_dev_attr_2(devattr)->nr; + + mutex_lock(&data->update_lock); + if (data->pwm_enable & (1 << (2 * pwm))) { + /* PWM mode */ + result = data->pwm_auto_point_pwm[pwm][point]; + } else { + /* RPM mode */ + result = 32 * 255 / (32 + data->pwm_auto_point_pwm[pwm][point]); + } + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", result); +} + +static ssize_t store_pwm_auto_point_pwm(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err, pwm = to_sensor_dev_attr_2(devattr)->index; + int point = to_sensor_dev_attr_2(devattr)->nr; + long val; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + val = clamp_val(val, 0, 255); + + mutex_lock(&data->update_lock); + data->pwm_enable = f71882fg_read8(data, F71882FG_REG_PWM_ENABLE); + if (data->pwm_enable & (1 << (2 * pwm))) { + /* PWM mode */ + } else { + /* RPM mode */ + if (val < 29) /* Prevent negative numbers */ + val = 255; + else + val = (255 - val) * 32 / val; + } + f71882fg_write8(data, F71882FG_REG_POINT_PWM(pwm, point), val); + data->pwm_auto_point_pwm[pwm][point] = val; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_auto_point_temp_hyst(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + int result = 0; + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + int point = to_sensor_dev_attr_2(devattr)->nr; + + mutex_lock(&data->update_lock); + if (nr & 1) + result = data->pwm_auto_point_hyst[nr / 2] >> 4; + else + result = data->pwm_auto_point_hyst[nr / 2] & 0x0f; + result = 1000 * (data->pwm_auto_point_temp[nr][point] - result); + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", result); +} + +static ssize_t store_pwm_auto_point_temp_hyst(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err, nr = to_sensor_dev_attr_2(devattr)->index; + int point = to_sensor_dev_attr_2(devattr)->nr; + u8 reg; + long val; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + val /= 1000; + + mutex_lock(&data->update_lock); + data->pwm_auto_point_temp[nr][point] = + f71882fg_read8(data, F71882FG_REG_POINT_TEMP(nr, point)); + val = clamp_val(val, data->pwm_auto_point_temp[nr][point] - 15, + data->pwm_auto_point_temp[nr][point]); + val = data->pwm_auto_point_temp[nr][point] - val; + + reg = f71882fg_read8(data, F71882FG_REG_FAN_HYST(nr / 2)); + if (nr & 1) + reg = (reg & 0x0f) | (val << 4); + else + reg = (reg & 0xf0) | val; + + f71882fg_write8(data, F71882FG_REG_FAN_HYST(nr / 2), reg); + data->pwm_auto_point_hyst[nr / 2] = reg; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_interpolate(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int result; + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + result = (data->pwm_auto_point_mapping[nr] >> 4) & 1; + + return sprintf(buf, "%d\n", result); +} + +static ssize_t store_pwm_interpolate(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err, nr = to_sensor_dev_attr_2(devattr)->index; + unsigned long val; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->pwm_auto_point_mapping[nr] = + f71882fg_read8(data, F71882FG_REG_POINT_MAPPING(nr)); + if (val) + val = data->pwm_auto_point_mapping[nr] | (1 << 4); + else + val = data->pwm_auto_point_mapping[nr] & (~(1 << 4)); + f71882fg_write8(data, F71882FG_REG_POINT_MAPPING(nr), val); + data->pwm_auto_point_mapping[nr] = val; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_auto_point_channel(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + int result; + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + result = 1 << ((data->pwm_auto_point_mapping[nr] & 3) - + data->temp_start); + + return sprintf(buf, "%d\n", result); +} + +static ssize_t store_pwm_auto_point_channel(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err, nr = to_sensor_dev_attr_2(devattr)->index; + long val; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + switch (val) { + case 1: + val = 0; + break; + case 2: + val = 1; + break; + case 4: + val = 2; + break; + default: + return -EINVAL; + } + val += data->temp_start; + mutex_lock(&data->update_lock); + data->pwm_auto_point_mapping[nr] = + f71882fg_read8(data, F71882FG_REG_POINT_MAPPING(nr)); + val = (data->pwm_auto_point_mapping[nr] & 0xfc) | val; + f71882fg_write8(data, F71882FG_REG_POINT_MAPPING(nr), val); + data->pwm_auto_point_mapping[nr] = val; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_auto_point_temp(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + int result; + struct f71882fg_data *data = f71882fg_update_device(dev); + int pwm = to_sensor_dev_attr_2(devattr)->index; + int point = to_sensor_dev_attr_2(devattr)->nr; + + result = data->pwm_auto_point_temp[pwm][point]; + return sprintf(buf, "%d\n", 1000 * result); +} + +static ssize_t store_pwm_auto_point_temp(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int err, pwm = to_sensor_dev_attr_2(devattr)->index; + int point = to_sensor_dev_attr_2(devattr)->nr; + long val; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + val /= 1000; + + if (data->auto_point_temp_signed) + val = clamp_val(val, -128, 127); + else + val = clamp_val(val, 0, 127); + + mutex_lock(&data->update_lock); + f71882fg_write8(data, F71882FG_REG_POINT_TEMP(pwm, point), val); + data->pwm_auto_point_temp[pwm][point] = val; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t name_show(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", f71882fg_names[data->type]); +} + +static int f71882fg_create_sysfs_files(struct platform_device *pdev, + struct sensor_device_attribute_2 *attr, int count) +{ + int err, i; + + for (i = 0; i < count; i++) { + err = device_create_file(&pdev->dev, &attr[i].dev_attr); + if (err) + return err; + } + return 0; +} + +static void f71882fg_remove_sysfs_files(struct platform_device *pdev, + struct sensor_device_attribute_2 *attr, int count) +{ + int i; + + for (i = 0; i < count; i++) + device_remove_file(&pdev->dev, &attr[i].dev_attr); +} + +static int f71882fg_create_fan_sysfs_files( + struct platform_device *pdev, int idx) +{ + struct f71882fg_data *data = platform_get_drvdata(pdev); + int err; + + /* Sanity check the pwm setting */ + err = 0; + switch (data->type) { + case f71858fg: + if (((data->pwm_enable >> (idx * 2)) & 3) == 3) + err = 1; + break; + case f71862fg: + if (((data->pwm_enable >> (idx * 2)) & 1) != 1) + err = 1; + break; + case f8000: + if (idx == 2) + err = data->pwm_enable & 0x20; + break; + default: + break; + } + if (err) { + dev_err(&pdev->dev, + "Invalid (reserved) pwm settings: 0x%02x, " + "skipping fan %d\n", + (data->pwm_enable >> (idx * 2)) & 3, idx + 1); + return 0; /* This is a non fatal condition */ + } + + err = f71882fg_create_sysfs_files(pdev, &fxxxx_fan_attr[idx][0], + ARRAY_SIZE(fxxxx_fan_attr[0])); + if (err) + return err; + + if (f71882fg_fan_has_beep[data->type]) { + err = f71882fg_create_sysfs_files(pdev, + &fxxxx_fan_beep_attr[idx], + 1); + if (err) + return err; + } + + dev_info(&pdev->dev, "Fan: %d is in %s mode\n", idx + 1, + (data->pwm_enable & (1 << (2 * idx))) ? "duty-cycle" : "RPM"); + + /* Check for unsupported auto pwm settings */ + switch (data->type) { + case f71808e: + case f71808a: + case f71869: + case f71869a: + case f71889fg: + case f71889ed: + case f71889a: + data->pwm_auto_point_mapping[idx] = + f71882fg_read8(data, F71882FG_REG_POINT_MAPPING(idx)); + if ((data->pwm_auto_point_mapping[idx] & 0x80) || + (data->pwm_auto_point_mapping[idx] & 3) == 0) { + dev_warn(&pdev->dev, + "Auto pwm controlled by raw digital " + "data, disabling pwm auto_point " + "sysfs attributes for fan %d\n", idx + 1); + return 0; /* This is a non fatal condition */ + } + break; + default: + break; + } + + switch (data->type) { + case f71862fg: + err = f71882fg_create_sysfs_files(pdev, + &f71862fg_auto_pwm_attr[idx][0], + ARRAY_SIZE(f71862fg_auto_pwm_attr[0])); + break; + case f71808e: + case f71869: + err = f71882fg_create_sysfs_files(pdev, + &f71869_auto_pwm_attr[idx][0], + ARRAY_SIZE(f71869_auto_pwm_attr[0])); + break; + case f8000: + err = f71882fg_create_sysfs_files(pdev, + &f8000_auto_pwm_attr[idx][0], + ARRAY_SIZE(f8000_auto_pwm_attr[0])); + break; + default: + err = f71882fg_create_sysfs_files(pdev, + &fxxxx_auto_pwm_attr[idx][0], + ARRAY_SIZE(fxxxx_auto_pwm_attr[0])); + } + + return err; +} + +static int f71882fg_probe(struct platform_device *pdev) +{ + struct f71882fg_data *data; + struct f71882fg_sio_data *sio_data = dev_get_platdata(&pdev->dev); + int nr_fans = f71882fg_nr_fans[sio_data->type]; + int nr_temps = f71882fg_nr_temps[sio_data->type]; + int err, i; + int size; + u8 start_reg, reg; + + data = devm_kzalloc(&pdev->dev, sizeof(struct f71882fg_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start; + data->type = sio_data->type; + data->temp_start = + (data->type == f71858fg || data->type == f8000 || + data->type == f81866a) ? 0 : 1; + mutex_init(&data->update_lock); + platform_set_drvdata(pdev, data); + + start_reg = f71882fg_read8(data, F71882FG_REG_START); + if (start_reg & 0x04) { + dev_warn(&pdev->dev, "Hardware monitor is powered down\n"); + return -ENODEV; + } + if (!(start_reg & 0x03)) { + dev_warn(&pdev->dev, "Hardware monitoring not activated\n"); + return -ENODEV; + } + + /* Register sysfs interface files */ + err = device_create_file(&pdev->dev, &dev_attr_name); + if (err) + goto exit_unregister_sysfs; + + if (start_reg & 0x01) { + switch (data->type) { + case f71858fg: + data->temp_config = + f71882fg_read8(data, F71882FG_REG_TEMP_CONFIG); + if (data->temp_config & 0x10) + /* + * The f71858fg temperature alarms behave as + * the f8000 alarms in this mode + */ + err = f71882fg_create_sysfs_files(pdev, + f8000_temp_attr, + ARRAY_SIZE(f8000_temp_attr)); + else + err = f71882fg_create_sysfs_files(pdev, + f71858fg_temp_attr, + ARRAY_SIZE(f71858fg_temp_attr)); + break; + case f8000: + err = f71882fg_create_sysfs_files(pdev, + f8000_temp_attr, + ARRAY_SIZE(f8000_temp_attr)); + break; + case f81866a: + err = f71882fg_create_sysfs_files(pdev, + f71858fg_temp_attr, + ARRAY_SIZE(f71858fg_temp_attr)); + break; + default: + err = f71882fg_create_sysfs_files(pdev, + &fxxxx_temp_attr[0][0], + ARRAY_SIZE(fxxxx_temp_attr[0]) * nr_temps); + } + if (err) + goto exit_unregister_sysfs; + + if (f71882fg_temp_has_beep[data->type]) { + if (data->type == f81866a) { + size = ARRAY_SIZE(f81866_temp_beep_attr[0]); + err = f71882fg_create_sysfs_files(pdev, + &f81866_temp_beep_attr[0][0], + size * nr_temps); + + } else { + size = ARRAY_SIZE(fxxxx_temp_beep_attr[0]); + err = f71882fg_create_sysfs_files(pdev, + &fxxxx_temp_beep_attr[0][0], + size * nr_temps); + } + if (err) + goto exit_unregister_sysfs; + } + + for (i = 0; i < F71882FG_MAX_INS; i++) { + if (f71882fg_has_in[data->type][i]) { + err = device_create_file(&pdev->dev, + &fxxxx_in_attr[i].dev_attr); + if (err) + goto exit_unregister_sysfs; + } + } + if (f71882fg_has_in1_alarm[data->type]) { + err = f71882fg_create_sysfs_files(pdev, + fxxxx_in1_alarm_attr, + ARRAY_SIZE(fxxxx_in1_alarm_attr)); + if (err) + goto exit_unregister_sysfs; + } + } + + if (start_reg & 0x02) { + switch (data->type) { + case f71808e: + case f71808a: + case f71869: + case f71869a: + /* These always have signed auto point temps */ + data->auto_point_temp_signed = 1; + /* Fall through to select correct fan/pwm reg bank! */ + case f71889fg: + case f71889ed: + case f71889a: + reg = f71882fg_read8(data, F71882FG_REG_FAN_FAULT_T); + if (reg & F71882FG_FAN_NEG_TEMP_EN) + data->auto_point_temp_signed = 1; + /* Ensure banked pwm registers point to right bank */ + reg &= ~F71882FG_FAN_PROG_SEL; + f71882fg_write8(data, F71882FG_REG_FAN_FAULT_T, reg); + break; + default: + break; + } + + data->pwm_enable = + f71882fg_read8(data, F71882FG_REG_PWM_ENABLE); + + for (i = 0; i < nr_fans; i++) { + err = f71882fg_create_fan_sysfs_files(pdev, i); + if (err) + goto exit_unregister_sysfs; + } + + /* Some types have 1 extra fan with limited functionality */ + switch (data->type) { + case f71808a: + err = f71882fg_create_sysfs_files(pdev, + f71808a_fan3_attr, + ARRAY_SIZE(f71808a_fan3_attr)); + break; + case f8000: + err = f71882fg_create_sysfs_files(pdev, + f8000_fan_attr, + ARRAY_SIZE(f8000_fan_attr)); + break; + default: + break; + } + if (err) + goto exit_unregister_sysfs; + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + data->hwmon_dev = NULL; + goto exit_unregister_sysfs; + } + + return 0; + +exit_unregister_sysfs: + f71882fg_remove(pdev); /* Will unregister the sysfs files for us */ + return err; /* f71882fg_remove() also frees our data */ +} + +static int f71882fg_remove(struct platform_device *pdev) +{ + struct f71882fg_data *data = platform_get_drvdata(pdev); + int nr_fans = f71882fg_nr_fans[data->type]; + int nr_temps = f71882fg_nr_temps[data->type]; + int i; + u8 start_reg = f71882fg_read8(data, F71882FG_REG_START); + + if (data->hwmon_dev) + hwmon_device_unregister(data->hwmon_dev); + + device_remove_file(&pdev->dev, &dev_attr_name); + + if (start_reg & 0x01) { + switch (data->type) { + case f71858fg: + if (data->temp_config & 0x10) + f71882fg_remove_sysfs_files(pdev, + f8000_temp_attr, + ARRAY_SIZE(f8000_temp_attr)); + else + f71882fg_remove_sysfs_files(pdev, + f71858fg_temp_attr, + ARRAY_SIZE(f71858fg_temp_attr)); + break; + case f8000: + f71882fg_remove_sysfs_files(pdev, + f8000_temp_attr, + ARRAY_SIZE(f8000_temp_attr)); + break; + case f81866a: + f71882fg_remove_sysfs_files(pdev, + f71858fg_temp_attr, + ARRAY_SIZE(f71858fg_temp_attr)); + break; + default: + f71882fg_remove_sysfs_files(pdev, + &fxxxx_temp_attr[0][0], + ARRAY_SIZE(fxxxx_temp_attr[0]) * nr_temps); + } + if (f71882fg_temp_has_beep[data->type]) { + if (data->type == f81866a) + f71882fg_remove_sysfs_files(pdev, + &f81866_temp_beep_attr[0][0], + ARRAY_SIZE(f81866_temp_beep_attr[0]) + * nr_temps); + else + f71882fg_remove_sysfs_files(pdev, + &fxxxx_temp_beep_attr[0][0], + ARRAY_SIZE(fxxxx_temp_beep_attr[0]) + * nr_temps); + } + + for (i = 0; i < F71882FG_MAX_INS; i++) { + if (f71882fg_has_in[data->type][i]) { + device_remove_file(&pdev->dev, + &fxxxx_in_attr[i].dev_attr); + } + } + if (f71882fg_has_in1_alarm[data->type]) { + f71882fg_remove_sysfs_files(pdev, + fxxxx_in1_alarm_attr, + ARRAY_SIZE(fxxxx_in1_alarm_attr)); + } + } + + if (start_reg & 0x02) { + f71882fg_remove_sysfs_files(pdev, &fxxxx_fan_attr[0][0], + ARRAY_SIZE(fxxxx_fan_attr[0]) * nr_fans); + + if (f71882fg_fan_has_beep[data->type]) { + f71882fg_remove_sysfs_files(pdev, + fxxxx_fan_beep_attr, nr_fans); + } + + switch (data->type) { + case f71808a: + f71882fg_remove_sysfs_files(pdev, + &fxxxx_auto_pwm_attr[0][0], + ARRAY_SIZE(fxxxx_auto_pwm_attr[0]) * nr_fans); + f71882fg_remove_sysfs_files(pdev, + f71808a_fan3_attr, + ARRAY_SIZE(f71808a_fan3_attr)); + break; + case f71862fg: + f71882fg_remove_sysfs_files(pdev, + &f71862fg_auto_pwm_attr[0][0], + ARRAY_SIZE(f71862fg_auto_pwm_attr[0]) * + nr_fans); + break; + case f71808e: + case f71869: + f71882fg_remove_sysfs_files(pdev, + &f71869_auto_pwm_attr[0][0], + ARRAY_SIZE(f71869_auto_pwm_attr[0]) * nr_fans); + break; + case f8000: + f71882fg_remove_sysfs_files(pdev, + f8000_fan_attr, + ARRAY_SIZE(f8000_fan_attr)); + f71882fg_remove_sysfs_files(pdev, + &f8000_auto_pwm_attr[0][0], + ARRAY_SIZE(f8000_auto_pwm_attr[0]) * nr_fans); + break; + default: + f71882fg_remove_sysfs_files(pdev, + &fxxxx_auto_pwm_attr[0][0], + ARRAY_SIZE(fxxxx_auto_pwm_attr[0]) * nr_fans); + } + } + return 0; +} + +static int __init f71882fg_find(int sioaddr, struct f71882fg_sio_data *sio_data) +{ + u16 devid; + unsigned short address; + int err = superio_enter(sioaddr); + if (err) + return err; + + devid = superio_inw(sioaddr, SIO_REG_MANID); + if (devid != SIO_FINTEK_ID) { + pr_debug("Not a Fintek device\n"); + err = -ENODEV; + goto exit; + } + + devid = force_id ? force_id : superio_inw(sioaddr, SIO_REG_DEVID); + switch (devid) { + case SIO_F71808E_ID: + sio_data->type = f71808e; + break; + case SIO_F71808A_ID: + sio_data->type = f71808a; + break; + case SIO_F71858_ID: + sio_data->type = f71858fg; + break; + case SIO_F71862_ID: + sio_data->type = f71862fg; + break; + case SIO_F71868_ID: + sio_data->type = f71868a; + break; + case SIO_F71869_ID: + sio_data->type = f71869; + break; + case SIO_F71869A_ID: + sio_data->type = f71869a; + break; + case SIO_F71882_ID: + sio_data->type = f71882fg; + break; + case SIO_F71889_ID: + sio_data->type = f71889fg; + break; + case SIO_F71889E_ID: + sio_data->type = f71889ed; + break; + case SIO_F71889A_ID: + sio_data->type = f71889a; + break; + case SIO_F8000_ID: + sio_data->type = f8000; + break; + case SIO_F81768D_ID: + sio_data->type = f81768d; + break; + case SIO_F81865_ID: + sio_data->type = f81865f; + break; + case SIO_F81866_ID: + sio_data->type = f81866a; + break; + default: + pr_info("Unsupported Fintek device: %04x\n", + (unsigned int)devid); + err = -ENODEV; + goto exit; + } + + if (sio_data->type == f71858fg) + superio_select(sioaddr, SIO_F71858FG_LD_HWM); + else + superio_select(sioaddr, SIO_F71882FG_LD_HWM); + + if (!(superio_inb(sioaddr, SIO_REG_ENABLE) & 0x01)) { + pr_warn("Device not activated\n"); + err = -ENODEV; + goto exit; + } + + address = superio_inw(sioaddr, SIO_REG_ADDR); + if (address == 0) { + pr_warn("Base address not set\n"); + err = -ENODEV; + goto exit; + } + address &= ~(REGION_LENGTH - 1); /* Ignore 3 LSB */ + + err = address; + pr_info("Found %s chip at %#x, revision %d\n", + f71882fg_names[sio_data->type], (unsigned int)address, + (int)superio_inb(sioaddr, SIO_REG_DEVREV)); +exit: + superio_exit(sioaddr); + return err; +} + +static int __init f71882fg_device_add(int address, + const struct f71882fg_sio_data *sio_data) +{ + struct resource res = { + .start = address, + .end = address + REGION_LENGTH - 1, + .flags = IORESOURCE_IO, + }; + int err; + + f71882fg_pdev = platform_device_alloc(DRVNAME, address); + if (!f71882fg_pdev) + return -ENOMEM; + + res.name = f71882fg_pdev->name; + err = acpi_check_resource_conflict(&res); + if (err) + goto exit_device_put; + + err = platform_device_add_resources(f71882fg_pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed\n"); + goto exit_device_put; + } + + err = platform_device_add_data(f71882fg_pdev, sio_data, + sizeof(struct f71882fg_sio_data)); + if (err) { + pr_err("Platform data allocation failed\n"); + goto exit_device_put; + } + + err = platform_device_add(f71882fg_pdev); + if (err) { + pr_err("Device addition failed\n"); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(f71882fg_pdev); + + return err; +} + +static int __init f71882fg_init(void) +{ + int err; + int address; + struct f71882fg_sio_data sio_data; + + memset(&sio_data, 0, sizeof(sio_data)); + + address = f71882fg_find(0x2e, &sio_data); + if (address < 0) + address = f71882fg_find(0x4e, &sio_data); + if (address < 0) + return address; + + err = platform_driver_register(&f71882fg_driver); + if (err) + return err; + + err = f71882fg_device_add(address, &sio_data); + if (err) + goto exit_driver; + + return 0; + +exit_driver: + platform_driver_unregister(&f71882fg_driver); + return err; +} + +static void __exit f71882fg_exit(void) +{ + platform_device_unregister(f71882fg_pdev); + platform_driver_unregister(&f71882fg_driver); +} + +MODULE_DESCRIPTION("F71882FG Hardware Monitoring Driver"); +MODULE_AUTHOR("Hans Edgington, Hans de Goede "); +MODULE_LICENSE("GPL"); + +module_init(f71882fg_init); +module_exit(f71882fg_exit); diff --git a/drivers/hwmon/f75375s.c b/drivers/hwmon/f75375s.c new file mode 100644 index 000000000..80c42bea9 --- /dev/null +++ b/drivers/hwmon/f75375s.c @@ -0,0 +1,925 @@ +/* + * f75375s.c - driver for the Fintek F75375/SP, F75373 and + * F75387SG/RG hardware monitoring features + * Copyright (C) 2006-2007 Riku Voipio + * + * Datasheets available at: + * + * f75375: + * http://www.fintek.com.tw/files/productfiles/F75375_V026P.pdf + * + * f75373: + * http://www.fintek.com.tw/files/productfiles/F75373_V025P.pdf + * + * f75387: + * http://www.fintek.com.tw/files/productfiles/F75387_V027P.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2d, 0x2e, I2C_CLIENT_END }; + +enum chips { f75373, f75375, f75387 }; + +/* Fintek F75375 registers */ +#define F75375_REG_CONFIG0 0x0 +#define F75375_REG_CONFIG1 0x1 +#define F75375_REG_CONFIG2 0x2 +#define F75375_REG_CONFIG3 0x3 +#define F75375_REG_ADDR 0x4 +#define F75375_REG_INTR 0x31 +#define F75375_CHIP_ID 0x5A +#define F75375_REG_VERSION 0x5C +#define F75375_REG_VENDOR 0x5D +#define F75375_REG_FAN_TIMER 0x60 + +#define F75375_REG_VOLT(nr) (0x10 + (nr)) +#define F75375_REG_VOLT_HIGH(nr) (0x20 + (nr) * 2) +#define F75375_REG_VOLT_LOW(nr) (0x21 + (nr) * 2) + +#define F75375_REG_TEMP(nr) (0x14 + (nr)) +#define F75387_REG_TEMP11_LSB(nr) (0x1a + (nr)) +#define F75375_REG_TEMP_HIGH(nr) (0x28 + (nr) * 2) +#define F75375_REG_TEMP_HYST(nr) (0x29 + (nr) * 2) + +#define F75375_REG_FAN(nr) (0x16 + (nr) * 2) +#define F75375_REG_FAN_MIN(nr) (0x2C + (nr) * 2) +#define F75375_REG_FAN_FULL(nr) (0x70 + (nr) * 0x10) +#define F75375_REG_FAN_PWM_DUTY(nr) (0x76 + (nr) * 0x10) +#define F75375_REG_FAN_PWM_CLOCK(nr) (0x7D + (nr) * 0x10) + +#define F75375_REG_FAN_EXP(nr) (0x74 + (nr) * 0x10) +#define F75375_REG_FAN_B_TEMP(nr, step) ((0xA0 + (nr) * 0x10) + (step)) +#define F75375_REG_FAN_B_SPEED(nr, step) \ + ((0xA5 + (nr) * 0x10) + (step) * 2) + +#define F75375_REG_PWM1_RAISE_DUTY 0x69 +#define F75375_REG_PWM2_RAISE_DUTY 0x6A +#define F75375_REG_PWM1_DROP_DUTY 0x6B +#define F75375_REG_PWM2_DROP_DUTY 0x6C + +#define F75375_FAN_CTRL_LINEAR(nr) (4 + nr) +#define F75387_FAN_CTRL_LINEAR(nr) (1 + ((nr) * 4)) +#define FAN_CTRL_MODE(nr) (4 + ((nr) * 2)) +#define F75387_FAN_DUTY_MODE(nr) (2 + ((nr) * 4)) +#define F75387_FAN_MANU_MODE(nr) ((nr) * 4) + +/* + * Data structures and manipulation thereof + */ + +struct f75375_data { + unsigned short addr; + struct device *hwmon_dev; + + const char *name; + int kind; + struct mutex update_lock; /* protect register access */ + char valid; + unsigned long last_updated; /* In jiffies */ + unsigned long last_limits; /* In jiffies */ + + /* Register values */ + u8 in[4]; + u8 in_max[4]; + u8 in_min[4]; + u16 fan[2]; + u16 fan_min[2]; + u16 fan_max[2]; + u16 fan_target[2]; + u8 fan_timer; + u8 pwm[2]; + u8 pwm_mode[2]; + u8 pwm_enable[2]; + /* + * f75387: For remote temperature reading, it uses signed 11-bit + * values with LSB = 0.125 degree Celsius, left-justified in 16-bit + * registers. For original 8-bit temp readings, the LSB just is 0. + */ + s16 temp11[2]; + s8 temp_high[2]; + s8 temp_max_hyst[2]; +}; + +static int f75375_detect(struct i2c_client *client, + struct i2c_board_info *info); +static int f75375_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int f75375_remove(struct i2c_client *client); + +static const struct i2c_device_id f75375_id[] = { + { "f75373", f75373 }, + { "f75375", f75375 }, + { "f75387", f75387 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, f75375_id); + +static struct i2c_driver f75375_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "f75375", + }, + .probe = f75375_probe, + .remove = f75375_remove, + .id_table = f75375_id, + .detect = f75375_detect, + .address_list = normal_i2c, +}; + +static inline int f75375_read8(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +/* in most cases, should be called while holding update_lock */ +static inline u16 f75375_read16(struct i2c_client *client, u8 reg) +{ + return (i2c_smbus_read_byte_data(client, reg) << 8) + | i2c_smbus_read_byte_data(client, reg + 1); +} + +static inline void f75375_write8(struct i2c_client *client, u8 reg, + u8 value) +{ + i2c_smbus_write_byte_data(client, reg, value); +} + +static inline void f75375_write16(struct i2c_client *client, u8 reg, + u16 value) +{ + int err = i2c_smbus_write_byte_data(client, reg, (value >> 8)); + if (err) + return; + i2c_smbus_write_byte_data(client, reg + 1, (value & 0xFF)); +} + +static void f75375_write_pwm(struct i2c_client *client, int nr) +{ + struct f75375_data *data = i2c_get_clientdata(client); + if (data->kind == f75387) + f75375_write16(client, F75375_REG_FAN_EXP(nr), data->pwm[nr]); + else + f75375_write8(client, F75375_REG_FAN_PWM_DUTY(nr), + data->pwm[nr]); +} + +static struct f75375_data *f75375_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct f75375_data *data = i2c_get_clientdata(client); + int nr; + + mutex_lock(&data->update_lock); + + /* Limit registers cache is refreshed after 60 seconds */ + if (time_after(jiffies, data->last_limits + 60 * HZ) + || !data->valid) { + for (nr = 0; nr < 2; nr++) { + data->temp_high[nr] = + f75375_read8(client, F75375_REG_TEMP_HIGH(nr)); + data->temp_max_hyst[nr] = + f75375_read8(client, F75375_REG_TEMP_HYST(nr)); + data->fan_max[nr] = + f75375_read16(client, F75375_REG_FAN_FULL(nr)); + data->fan_min[nr] = + f75375_read16(client, F75375_REG_FAN_MIN(nr)); + data->fan_target[nr] = + f75375_read16(client, F75375_REG_FAN_EXP(nr)); + } + for (nr = 0; nr < 4; nr++) { + data->in_max[nr] = + f75375_read8(client, F75375_REG_VOLT_HIGH(nr)); + data->in_min[nr] = + f75375_read8(client, F75375_REG_VOLT_LOW(nr)); + } + data->fan_timer = f75375_read8(client, F75375_REG_FAN_TIMER); + data->last_limits = jiffies; + } + + /* Measurement registers cache is refreshed after 2 second */ + if (time_after(jiffies, data->last_updated + 2 * HZ) + || !data->valid) { + for (nr = 0; nr < 2; nr++) { + data->pwm[nr] = f75375_read8(client, + F75375_REG_FAN_PWM_DUTY(nr)); + /* assign MSB, therefore shift it by 8 bits */ + data->temp11[nr] = + f75375_read8(client, F75375_REG_TEMP(nr)) << 8; + if (data->kind == f75387) + /* merge F75387's temperature LSB (11-bit) */ + data->temp11[nr] |= + f75375_read8(client, + F75387_REG_TEMP11_LSB(nr)); + data->fan[nr] = + f75375_read16(client, F75375_REG_FAN(nr)); + } + for (nr = 0; nr < 4; nr++) + data->in[nr] = + f75375_read8(client, F75375_REG_VOLT(nr)); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + return data; +} + +static inline u16 rpm_from_reg(u16 reg) +{ + if (reg == 0 || reg == 0xffff) + return 0; + return 1500000 / reg; +} + +static inline u16 rpm_to_reg(int rpm) +{ + if (rpm < 367 || rpm > 0xffff) + return 0xffff; + return 1500000 / rpm; +} + +static bool duty_mode_enabled(u8 pwm_enable) +{ + switch (pwm_enable) { + case 0: /* Manual, duty mode (full speed) */ + case 1: /* Manual, duty mode */ + case 4: /* Auto, duty mode */ + return true; + case 2: /* Auto, speed mode */ + case 3: /* Manual, speed mode */ + return false; + default: + WARN(1, "Unexpected pwm_enable value %d\n", pwm_enable); + return true; + } +} + +static bool auto_mode_enabled(u8 pwm_enable) +{ + switch (pwm_enable) { + case 0: /* Manual, duty mode (full speed) */ + case 1: /* Manual, duty mode */ + case 3: /* Manual, speed mode */ + return false; + case 2: /* Auto, speed mode */ + case 4: /* Auto, duty mode */ + return true; + default: + WARN(1, "Unexpected pwm_enable value %d\n", pwm_enable); + return false; + } +} + +static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct f75375_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + mutex_lock(&data->update_lock); + data->fan_min[nr] = rpm_to_reg(val); + f75375_write16(client, F75375_REG_FAN_MIN(nr), data->fan_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_fan_target(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct f75375_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (auto_mode_enabled(data->pwm_enable[nr])) + return -EINVAL; + if (data->kind == f75387 && duty_mode_enabled(data->pwm_enable[nr])) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->fan_target[nr] = rpm_to_reg(val); + f75375_write16(client, F75375_REG_FAN_EXP(nr), data->fan_target[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct f75375_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (auto_mode_enabled(data->pwm_enable[nr]) || + !duty_mode_enabled(data->pwm_enable[nr])) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->pwm[nr] = clamp_val(val, 0, 255); + f75375_write_pwm(client, nr); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm_enable(struct device *dev, struct device_attribute + *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct f75375_data *data = f75375_update_device(dev); + return sprintf(buf, "%d\n", data->pwm_enable[nr]); +} + +static int set_pwm_enable_direct(struct i2c_client *client, int nr, int val) +{ + struct f75375_data *data = i2c_get_clientdata(client); + u8 fanmode; + + if (val < 0 || val > 4) + return -EINVAL; + + fanmode = f75375_read8(client, F75375_REG_FAN_TIMER); + if (data->kind == f75387) { + /* For now, deny dangerous toggling of duty mode */ + if (duty_mode_enabled(data->pwm_enable[nr]) != + duty_mode_enabled(val)) + return -EOPNOTSUPP; + /* clear each fanX_mode bit before setting them properly */ + fanmode &= ~(1 << F75387_FAN_DUTY_MODE(nr)); + fanmode &= ~(1 << F75387_FAN_MANU_MODE(nr)); + switch (val) { + case 0: /* full speed */ + fanmode |= (1 << F75387_FAN_MANU_MODE(nr)); + fanmode |= (1 << F75387_FAN_DUTY_MODE(nr)); + data->pwm[nr] = 255; + break; + case 1: /* PWM */ + fanmode |= (1 << F75387_FAN_MANU_MODE(nr)); + fanmode |= (1 << F75387_FAN_DUTY_MODE(nr)); + break; + case 2: /* Automatic, speed mode */ + break; + case 3: /* fan speed */ + fanmode |= (1 << F75387_FAN_MANU_MODE(nr)); + break; + case 4: /* Automatic, pwm */ + fanmode |= (1 << F75387_FAN_DUTY_MODE(nr)); + break; + } + } else { + /* clear each fanX_mode bit before setting them properly */ + fanmode &= ~(3 << FAN_CTRL_MODE(nr)); + switch (val) { + case 0: /* full speed */ + fanmode |= (3 << FAN_CTRL_MODE(nr)); + data->pwm[nr] = 255; + break; + case 1: /* PWM */ + fanmode |= (3 << FAN_CTRL_MODE(nr)); + break; + case 2: /* AUTOMATIC*/ + fanmode |= (1 << FAN_CTRL_MODE(nr)); + break; + case 3: /* fan speed */ + break; + case 4: /* Automatic pwm */ + return -EINVAL; + } + } + + f75375_write8(client, F75375_REG_FAN_TIMER, fanmode); + data->pwm_enable[nr] = val; + if (val == 0) + f75375_write_pwm(client, nr); + return 0; +} + +static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct f75375_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + mutex_lock(&data->update_lock); + err = set_pwm_enable_direct(client, nr, val); + mutex_unlock(&data->update_lock); + return err ? err : count; +} + +static ssize_t set_pwm_mode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct f75375_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + u8 conf; + char reg, ctrl; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (!(val == 0 || val == 1)) + return -EINVAL; + + /* F75373 does not support DC (linear voltage) fan control mode */ + if (data->kind == f75373 && val == 0) + return -EINVAL; + + /* take care for different registers */ + if (data->kind == f75387) { + reg = F75375_REG_FAN_TIMER; + ctrl = F75387_FAN_CTRL_LINEAR(nr); + } else { + reg = F75375_REG_CONFIG1; + ctrl = F75375_FAN_CTRL_LINEAR(nr); + } + + mutex_lock(&data->update_lock); + conf = f75375_read8(client, reg); + conf &= ~(1 << ctrl); + + if (val == 0) + conf |= (1 << ctrl); + + f75375_write8(client, reg, conf); + data->pwm_mode[nr] = val; + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm(struct device *dev, struct device_attribute + *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct f75375_data *data = f75375_update_device(dev); + return sprintf(buf, "%d\n", data->pwm[nr]); +} + +static ssize_t show_pwm_mode(struct device *dev, struct device_attribute + *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct f75375_data *data = f75375_update_device(dev); + return sprintf(buf, "%d\n", data->pwm_mode[nr]); +} + +#define VOLT_FROM_REG(val) ((val) * 8) +#define VOLT_TO_REG(val) ((val) / 8) + +static ssize_t show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct f75375_data *data = f75375_update_device(dev); + return sprintf(buf, "%d\n", VOLT_FROM_REG(data->in[nr])); +} + +static ssize_t show_in_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct f75375_data *data = f75375_update_device(dev); + return sprintf(buf, "%d\n", VOLT_FROM_REG(data->in_max[nr])); +} + +static ssize_t show_in_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct f75375_data *data = f75375_update_device(dev); + return sprintf(buf, "%d\n", VOLT_FROM_REG(data->in_min[nr])); +} + +static ssize_t set_in_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct f75375_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(VOLT_TO_REG(val), 0, 0xff); + mutex_lock(&data->update_lock); + data->in_max[nr] = val; + f75375_write8(client, F75375_REG_VOLT_HIGH(nr), data->in_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_in_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct f75375_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(VOLT_TO_REG(val), 0, 0xff); + mutex_lock(&data->update_lock); + data->in_min[nr] = val; + f75375_write8(client, F75375_REG_VOLT_LOW(nr), data->in_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} +#define TEMP_FROM_REG(val) ((val) * 1000) +#define TEMP_TO_REG(val) ((val) / 1000) +#define TEMP11_FROM_REG(reg) ((reg) / 32 * 125) + +static ssize_t show_temp11(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct f75375_data *data = f75375_update_device(dev); + return sprintf(buf, "%d\n", TEMP11_FROM_REG(data->temp11[nr])); +} + +static ssize_t show_temp_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct f75375_data *data = f75375_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_high[nr])); +} + +static ssize_t show_temp_max_hyst(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct f75375_data *data = f75375_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max_hyst[nr])); +} + +static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct f75375_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(TEMP_TO_REG(val), 0, 127); + mutex_lock(&data->update_lock); + data->temp_high[nr] = val; + f75375_write8(client, F75375_REG_TEMP_HIGH(nr), data->temp_high[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_temp_max_hyst(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct f75375_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(TEMP_TO_REG(val), 0, 127); + mutex_lock(&data->update_lock); + data->temp_max_hyst[nr] = val; + f75375_write8(client, F75375_REG_TEMP_HYST(nr), + data->temp_max_hyst[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define show_fan(thing) \ +static ssize_t show_##thing(struct device *dev, struct device_attribute *attr, \ + char *buf)\ +{\ + int nr = to_sensor_dev_attr(attr)->index;\ + struct f75375_data *data = f75375_update_device(dev); \ + return sprintf(buf, "%d\n", rpm_from_reg(data->thing[nr])); \ +} + +show_fan(fan); +show_fan(fan_min); +show_fan(fan_max); +show_fan(fan_target); + +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in, NULL, 0); +static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO|S_IWUSR, + show_in_max, set_in_max, 0); +static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO|S_IWUSR, + show_in_min, set_in_min, 0); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 1); +static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO|S_IWUSR, + show_in_max, set_in_max, 1); +static SENSOR_DEVICE_ATTR(in1_min, S_IRUGO|S_IWUSR, + show_in_min, set_in_min, 1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_in, NULL, 2); +static SENSOR_DEVICE_ATTR(in2_max, S_IRUGO|S_IWUSR, + show_in_max, set_in_max, 2); +static SENSOR_DEVICE_ATTR(in2_min, S_IRUGO|S_IWUSR, + show_in_min, set_in_min, 2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_in, NULL, 3); +static SENSOR_DEVICE_ATTR(in3_max, S_IRUGO|S_IWUSR, + show_in_max, set_in_max, 3); +static SENSOR_DEVICE_ATTR(in3_min, S_IRUGO|S_IWUSR, + show_in_min, set_in_min, 3); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp11, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IRUGO|S_IWUSR, + show_temp_max_hyst, set_temp_max_hyst, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO|S_IWUSR, + show_temp_max, set_temp_max, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp11, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IRUGO|S_IWUSR, + show_temp_max_hyst, set_temp_max_hyst, 1); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO|S_IWUSR, + show_temp_max, set_temp_max, 1); +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); +static SENSOR_DEVICE_ATTR(fan1_max, S_IRUGO, show_fan_max, NULL, 0); +static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO|S_IWUSR, + show_fan_min, set_fan_min, 0); +static SENSOR_DEVICE_ATTR(fan1_target, S_IRUGO|S_IWUSR, + show_fan_target, set_fan_target, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); +static SENSOR_DEVICE_ATTR(fan2_max, S_IRUGO, show_fan_max, NULL, 1); +static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO|S_IWUSR, + show_fan_min, set_fan_min, 1); +static SENSOR_DEVICE_ATTR(fan2_target, S_IRUGO|S_IWUSR, + show_fan_target, set_fan_target, 1); +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO|S_IWUSR, + show_pwm, set_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IRUGO|S_IWUSR, + show_pwm_enable, set_pwm_enable, 0); +static SENSOR_DEVICE_ATTR(pwm1_mode, S_IRUGO, + show_pwm_mode, set_pwm_mode, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, + show_pwm, set_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IRUGO|S_IWUSR, + show_pwm_enable, set_pwm_enable, 1); +static SENSOR_DEVICE_ATTR(pwm2_mode, S_IRUGO, + show_pwm_mode, set_pwm_mode, 1); + +static struct attribute *f75375_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_max.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_target.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_max.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_target.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_mode.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm2_mode.dev_attr.attr, + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + NULL +}; + +static const struct attribute_group f75375_group = { + .attrs = f75375_attributes, +}; + +static void f75375_init(struct i2c_client *client, struct f75375_data *data, + struct f75375s_platform_data *f75375s_pdata) +{ + int nr; + + if (!f75375s_pdata) { + u8 conf, mode; + int nr; + + conf = f75375_read8(client, F75375_REG_CONFIG1); + mode = f75375_read8(client, F75375_REG_FAN_TIMER); + for (nr = 0; nr < 2; nr++) { + if (data->kind == f75387) { + bool manu, duty; + + if (!(mode & (1 << F75387_FAN_CTRL_LINEAR(nr)))) + data->pwm_mode[nr] = 1; + + manu = ((mode >> F75387_FAN_MANU_MODE(nr)) & 1); + duty = ((mode >> F75387_FAN_DUTY_MODE(nr)) & 1); + if (!manu && duty) + /* auto, pwm */ + data->pwm_enable[nr] = 4; + else if (manu && !duty) + /* manual, speed */ + data->pwm_enable[nr] = 3; + else if (!manu && !duty) + /* automatic, speed */ + data->pwm_enable[nr] = 2; + else + /* manual, pwm */ + data->pwm_enable[nr] = 1; + } else { + if (!(conf & (1 << F75375_FAN_CTRL_LINEAR(nr)))) + data->pwm_mode[nr] = 1; + + switch ((mode >> FAN_CTRL_MODE(nr)) & 3) { + case 0: /* speed */ + data->pwm_enable[nr] = 3; + break; + case 1: /* automatic */ + data->pwm_enable[nr] = 2; + break; + default: /* manual */ + data->pwm_enable[nr] = 1; + break; + } + } + } + return; + } + + set_pwm_enable_direct(client, 0, f75375s_pdata->pwm_enable[0]); + set_pwm_enable_direct(client, 1, f75375s_pdata->pwm_enable[1]); + for (nr = 0; nr < 2; nr++) { + if (auto_mode_enabled(f75375s_pdata->pwm_enable[nr]) || + !duty_mode_enabled(f75375s_pdata->pwm_enable[nr])) + continue; + data->pwm[nr] = clamp_val(f75375s_pdata->pwm[nr], 0, 255); + f75375_write_pwm(client, nr); + } + +} + +static int f75375_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct f75375_data *data; + struct f75375s_platform_data *f75375s_pdata = + dev_get_platdata(&client->dev); + int err; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + data = devm_kzalloc(&client->dev, sizeof(struct f75375_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + data->kind = id->driver_data; + + err = sysfs_create_group(&client->dev.kobj, &f75375_group); + if (err) + return err; + + if (data->kind != f75373) { + err = sysfs_chmod_file(&client->dev.kobj, + &sensor_dev_attr_pwm1_mode.dev_attr.attr, + S_IRUGO | S_IWUSR); + if (err) + goto exit_remove; + err = sysfs_chmod_file(&client->dev.kobj, + &sensor_dev_attr_pwm2_mode.dev_attr.attr, + S_IRUGO | S_IWUSR); + if (err) + goto exit_remove; + } + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + f75375_init(client, data, f75375s_pdata); + + return 0; + +exit_remove: + sysfs_remove_group(&client->dev.kobj, &f75375_group); + return err; +} + +static int f75375_remove(struct i2c_client *client) +{ + struct f75375_data *data = i2c_get_clientdata(client); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &f75375_group); + return 0; +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int f75375_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + u16 vendid, chipid; + u8 version; + const char *name; + + vendid = f75375_read16(client, F75375_REG_VENDOR); + chipid = f75375_read16(client, F75375_CHIP_ID); + if (vendid != 0x1934) + return -ENODEV; + + if (chipid == 0x0306) + name = "f75375"; + else if (chipid == 0x0204) + name = "f75373"; + else if (chipid == 0x0410) + name = "f75387"; + else + return -ENODEV; + + version = f75375_read8(client, F75375_REG_VERSION); + dev_info(&adapter->dev, "found %s version: %02X\n", name, version); + strlcpy(info->type, name, I2C_NAME_SIZE); + + return 0; +} + +module_i2c_driver(f75375_driver); + +MODULE_AUTHOR("Riku Voipio"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("F75373/F75375/F75387 hardware monitoring driver"); diff --git a/drivers/hwmon/fam15h_power.c b/drivers/hwmon/fam15h_power.c new file mode 100644 index 000000000..9545a3460 --- /dev/null +++ b/drivers/hwmon/fam15h_power.c @@ -0,0 +1,516 @@ +/* + * fam15h_power.c - AMD Family 15h processor power monitoring + * + * Copyright (c) 2011-2016 Advanced Micro Devices, Inc. + * Author: Andreas Herrmann + * + * + * This driver is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License; either + * version 2 of the License, or (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("AMD Family 15h CPU processor power monitor"); +MODULE_AUTHOR("Andreas Herrmann "); +MODULE_LICENSE("GPL"); + +/* D18F3 */ +#define REG_NORTHBRIDGE_CAP 0xe8 + +/* D18F4 */ +#define REG_PROCESSOR_TDP 0x1b8 + +/* D18F5 */ +#define REG_TDP_RUNNING_AVERAGE 0xe0 +#define REG_TDP_LIMIT3 0xe8 + +#define FAM15H_MIN_NUM_ATTRS 2 +#define FAM15H_NUM_GROUPS 2 +#define MAX_CUS 8 + +/* set maximum interval as 1 second */ +#define MAX_INTERVAL 1000 + +#define MSR_F15H_CU_PWR_ACCUMULATOR 0xc001007a +#define MSR_F15H_CU_MAX_PWR_ACCUMULATOR 0xc001007b +#define MSR_F15H_PTSC 0xc0010280 + +#define PCI_DEVICE_ID_AMD_15H_M70H_NB_F4 0x15b4 + +struct fam15h_power_data { + struct pci_dev *pdev; + unsigned int tdp_to_watts; + unsigned int base_tdp; + unsigned int processor_pwr_watts; + unsigned int cpu_pwr_sample_ratio; + const struct attribute_group *groups[FAM15H_NUM_GROUPS]; + struct attribute_group group; + /* maximum accumulated power of a compute unit */ + u64 max_cu_acc_power; + /* accumulated power of the compute units */ + u64 cu_acc_power[MAX_CUS]; + /* performance timestamp counter */ + u64 cpu_sw_pwr_ptsc[MAX_CUS]; + /* online/offline status of current compute unit */ + int cu_on[MAX_CUS]; + unsigned long power_period; +}; + +static bool is_carrizo_or_later(void) +{ + return boot_cpu_data.x86 == 0x15 && boot_cpu_data.x86_model >= 0x60; +} + +static ssize_t power1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u32 val, tdp_limit, running_avg_range; + s32 running_avg_capture; + u64 curr_pwr_watts; + struct fam15h_power_data *data = dev_get_drvdata(dev); + struct pci_dev *f4 = data->pdev; + + pci_bus_read_config_dword(f4->bus, PCI_DEVFN(PCI_SLOT(f4->devfn), 5), + REG_TDP_RUNNING_AVERAGE, &val); + + /* + * On Carrizo and later platforms, TdpRunAvgAccCap bit field + * is extended to 4:31 from 4:25. + */ + if (is_carrizo_or_later()) { + running_avg_capture = val >> 4; + running_avg_capture = sign_extend32(running_avg_capture, 27); + } else { + running_avg_capture = (val >> 4) & 0x3fffff; + running_avg_capture = sign_extend32(running_avg_capture, 21); + } + + running_avg_range = (val & 0xf) + 1; + + pci_bus_read_config_dword(f4->bus, PCI_DEVFN(PCI_SLOT(f4->devfn), 5), + REG_TDP_LIMIT3, &val); + + /* + * On Carrizo and later platforms, ApmTdpLimit bit field + * is extended to 16:31 from 16:28. + */ + if (is_carrizo_or_later()) + tdp_limit = val >> 16; + else + tdp_limit = (val >> 16) & 0x1fff; + + curr_pwr_watts = ((u64)(tdp_limit + + data->base_tdp)) << running_avg_range; + curr_pwr_watts -= running_avg_capture; + curr_pwr_watts *= data->tdp_to_watts; + + /* + * Convert to microWatt + * + * power is in Watt provided as fixed point integer with + * scaling factor 1/(2^16). For conversion we use + * (10^6)/(2^16) = 15625/(2^10) + */ + curr_pwr_watts = (curr_pwr_watts * 15625) >> (10 + running_avg_range); + return sprintf(buf, "%u\n", (unsigned int) curr_pwr_watts); +} +static DEVICE_ATTR_RO(power1_input); + +static ssize_t power1_crit_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fam15h_power_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", data->processor_pwr_watts); +} +static DEVICE_ATTR_RO(power1_crit); + +static void do_read_registers_on_cu(void *_data) +{ + struct fam15h_power_data *data = _data; + int cpu, cu; + + cpu = smp_processor_id(); + + /* + * With the new x86 topology modelling, cpu core id actually + * is compute unit id. + */ + cu = cpu_data(cpu).cpu_core_id; + + rdmsrl_safe(MSR_F15H_CU_PWR_ACCUMULATOR, &data->cu_acc_power[cu]); + rdmsrl_safe(MSR_F15H_PTSC, &data->cpu_sw_pwr_ptsc[cu]); + + data->cu_on[cu] = 1; +} + +/* + * This function is only able to be called when CPUID + * Fn8000_0007:EDX[12] is set. + */ +static int read_registers(struct fam15h_power_data *data) +{ + int core, this_core; + cpumask_var_t mask; + int ret, cpu; + + ret = zalloc_cpumask_var(&mask, GFP_KERNEL); + if (!ret) + return -ENOMEM; + + memset(data->cu_on, 0, sizeof(int) * MAX_CUS); + + get_online_cpus(); + + /* + * Choose the first online core of each compute unit, and then + * read their MSR value of power and ptsc in a single IPI, + * because the MSR value of CPU core represent the compute + * unit's. + */ + core = -1; + + for_each_online_cpu(cpu) { + this_core = topology_core_id(cpu); + + if (this_core == core) + continue; + + core = this_core; + + /* get any CPU on this compute unit */ + cpumask_set_cpu(cpumask_any(topology_sibling_cpumask(cpu)), mask); + } + + on_each_cpu_mask(mask, do_read_registers_on_cu, data, true); + + put_online_cpus(); + free_cpumask_var(mask); + + return 0; +} + +static ssize_t power1_average_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fam15h_power_data *data = dev_get_drvdata(dev); + u64 prev_cu_acc_power[MAX_CUS], prev_ptsc[MAX_CUS], + jdelta[MAX_CUS]; + u64 tdelta, avg_acc; + int cu, cu_num, ret; + signed long leftover; + + /* + * With the new x86 topology modelling, x86_max_cores is the + * compute unit number. + */ + cu_num = boot_cpu_data.x86_max_cores; + + ret = read_registers(data); + if (ret) + return 0; + + for (cu = 0; cu < cu_num; cu++) { + prev_cu_acc_power[cu] = data->cu_acc_power[cu]; + prev_ptsc[cu] = data->cpu_sw_pwr_ptsc[cu]; + } + + leftover = schedule_timeout_interruptible(msecs_to_jiffies(data->power_period)); + if (leftover) + return 0; + + ret = read_registers(data); + if (ret) + return 0; + + for (cu = 0, avg_acc = 0; cu < cu_num; cu++) { + /* check if current compute unit is online */ + if (data->cu_on[cu] == 0) + continue; + + if (data->cu_acc_power[cu] < prev_cu_acc_power[cu]) { + jdelta[cu] = data->max_cu_acc_power + data->cu_acc_power[cu]; + jdelta[cu] -= prev_cu_acc_power[cu]; + } else { + jdelta[cu] = data->cu_acc_power[cu] - prev_cu_acc_power[cu]; + } + tdelta = data->cpu_sw_pwr_ptsc[cu] - prev_ptsc[cu]; + jdelta[cu] *= data->cpu_pwr_sample_ratio * 1000; + do_div(jdelta[cu], tdelta); + + /* the unit is microWatt */ + avg_acc += jdelta[cu]; + } + + return sprintf(buf, "%llu\n", (unsigned long long)avg_acc); +} +static DEVICE_ATTR_RO(power1_average); + +static ssize_t power1_average_interval_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fam15h_power_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%lu\n", data->power_period); +} + +static ssize_t power1_average_interval_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fam15h_power_data *data = dev_get_drvdata(dev); + unsigned long temp; + int ret; + + ret = kstrtoul(buf, 10, &temp); + if (ret) + return ret; + + if (temp > MAX_INTERVAL) + return -EINVAL; + + /* the interval value should be greater than 0 */ + if (temp <= 0) + return -EINVAL; + + data->power_period = temp; + + return count; +} +static DEVICE_ATTR_RW(power1_average_interval); + +static int fam15h_power_init_attrs(struct pci_dev *pdev, + struct fam15h_power_data *data) +{ + int n = FAM15H_MIN_NUM_ATTRS; + struct attribute **fam15h_power_attrs; + struct cpuinfo_x86 *c = &boot_cpu_data; + + if (c->x86 == 0x15 && + (c->x86_model <= 0xf || + (c->x86_model >= 0x60 && c->x86_model <= 0x7f))) + n += 1; + + /* check if processor supports accumulated power */ + if (boot_cpu_has(X86_FEATURE_ACC_POWER)) + n += 2; + + fam15h_power_attrs = devm_kcalloc(&pdev->dev, n, + sizeof(*fam15h_power_attrs), + GFP_KERNEL); + + if (!fam15h_power_attrs) + return -ENOMEM; + + n = 0; + fam15h_power_attrs[n++] = &dev_attr_power1_crit.attr; + if (c->x86 == 0x15 && + (c->x86_model <= 0xf || + (c->x86_model >= 0x60 && c->x86_model <= 0x7f))) + fam15h_power_attrs[n++] = &dev_attr_power1_input.attr; + + if (boot_cpu_has(X86_FEATURE_ACC_POWER)) { + fam15h_power_attrs[n++] = &dev_attr_power1_average.attr; + fam15h_power_attrs[n++] = &dev_attr_power1_average_interval.attr; + } + + data->group.attrs = fam15h_power_attrs; + + return 0; +} + +static bool should_load_on_this_node(struct pci_dev *f4) +{ + u32 val; + + pci_bus_read_config_dword(f4->bus, PCI_DEVFN(PCI_SLOT(f4->devfn), 3), + REG_NORTHBRIDGE_CAP, &val); + if ((val & BIT(29)) && ((val >> 30) & 3)) + return false; + + return true; +} + +/* + * Newer BKDG versions have an updated recommendation on how to properly + * initialize the running average range (was: 0xE, now: 0x9). This avoids + * counter saturations resulting in bogus power readings. + * We correct this value ourselves to cope with older BIOSes. + */ +static const struct pci_device_id affected_device[] = { + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_NB_F4) }, + { 0 } +}; + +static void tweak_runavg_range(struct pci_dev *pdev) +{ + u32 val; + + /* + * let this quirk apply only to the current version of the + * northbridge, since future versions may change the behavior + */ + if (!pci_match_id(affected_device, pdev)) + return; + + pci_bus_read_config_dword(pdev->bus, + PCI_DEVFN(PCI_SLOT(pdev->devfn), 5), + REG_TDP_RUNNING_AVERAGE, &val); + if ((val & 0xf) != 0xe) + return; + + val &= ~0xf; + val |= 0x9; + pci_bus_write_config_dword(pdev->bus, + PCI_DEVFN(PCI_SLOT(pdev->devfn), 5), + REG_TDP_RUNNING_AVERAGE, val); +} + +#ifdef CONFIG_PM +static int fam15h_power_resume(struct pci_dev *pdev) +{ + tweak_runavg_range(pdev); + return 0; +} +#else +#define fam15h_power_resume NULL +#endif + +static int fam15h_power_init_data(struct pci_dev *f4, + struct fam15h_power_data *data) +{ + u32 val; + u64 tmp; + int ret; + + pci_read_config_dword(f4, REG_PROCESSOR_TDP, &val); + data->base_tdp = val >> 16; + tmp = val & 0xffff; + + pci_bus_read_config_dword(f4->bus, PCI_DEVFN(PCI_SLOT(f4->devfn), 5), + REG_TDP_LIMIT3, &val); + + data->tdp_to_watts = ((val & 0x3ff) << 6) | ((val >> 10) & 0x3f); + tmp *= data->tdp_to_watts; + + /* result not allowed to be >= 256W */ + if ((tmp >> 16) >= 256) + dev_warn(&f4->dev, + "Bogus value for ProcessorPwrWatts (processor_pwr_watts>=%u)\n", + (unsigned int) (tmp >> 16)); + + /* convert to microWatt */ + data->processor_pwr_watts = (tmp * 15625) >> 10; + + ret = fam15h_power_init_attrs(f4, data); + if (ret) + return ret; + + + /* CPUID Fn8000_0007:EDX[12] indicates to support accumulated power */ + if (!boot_cpu_has(X86_FEATURE_ACC_POWER)) + return 0; + + /* + * determine the ratio of the compute unit power accumulator + * sample period to the PTSC counter period by executing CPUID + * Fn8000_0007:ECX + */ + data->cpu_pwr_sample_ratio = cpuid_ecx(0x80000007); + + if (rdmsrl_safe(MSR_F15H_CU_MAX_PWR_ACCUMULATOR, &tmp)) { + pr_err("Failed to read max compute unit power accumulator MSR\n"); + return -ENODEV; + } + + data->max_cu_acc_power = tmp; + + /* + * Milliseconds are a reasonable interval for the measurement. + * But it shouldn't set too long here, because several seconds + * would cause the read function to hang. So set default + * interval as 10 ms. + */ + data->power_period = 10; + + return read_registers(data); +} + +static int fam15h_power_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct fam15h_power_data *data; + struct device *dev = &pdev->dev; + struct device *hwmon_dev; + int ret; + + /* + * though we ignore every other northbridge, we still have to + * do the tweaking on _each_ node in MCM processors as the counters + * are working hand-in-hand + */ + tweak_runavg_range(pdev); + + if (!should_load_on_this_node(pdev)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(struct fam15h_power_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = fam15h_power_init_data(pdev, data); + if (ret) + return ret; + + data->pdev = pdev; + + data->groups[0] = &data->group; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "fam15h_power", + data, + &data->groups[0]); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct pci_device_id fam15h_power_id_table[] = { + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_NB_F4) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_M30H_NB_F4) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_M60H_NB_F4) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_M70H_NB_F4) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_16H_NB_F4) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_16H_M30H_NB_F4) }, + {} +}; +MODULE_DEVICE_TABLE(pci, fam15h_power_id_table); + +static struct pci_driver fam15h_power_driver = { + .name = "fam15h_power", + .id_table = fam15h_power_id_table, + .probe = fam15h_power_probe, + .resume = fam15h_power_resume, +}; + +module_pci_driver(fam15h_power_driver); diff --git a/drivers/hwmon/fschmd.c b/drivers/hwmon/fschmd.c new file mode 100644 index 000000000..22d3a84f1 --- /dev/null +++ b/drivers/hwmon/fschmd.c @@ -0,0 +1,1389 @@ +/* + * fschmd.c + * + * Copyright (C) 2007 - 2009 Hans de Goede + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Merged Fujitsu Siemens hwmon driver, supporting the Poseidon, Hermes, + * Scylla, Heracles, Heimdall, Hades and Syleus chips + * + * Based on the original 2.4 fscscy, 2.6 fscpos, 2.6 fscher and 2.6 + * (candidate) fschmd drivers: + * Copyright (C) 2006 Thilo Cestonaro + * + * Copyright (C) 2004, 2005 Stefan Ott + * Copyright (C) 2003, 2004 Reinhard Nissl + * Copyright (c) 2001 Martin Knoblauch + * Copyright (C) 2000 Hermann Jung + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END }; + +/* Insmod parameters */ +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +enum chips { fscpos, fscher, fscscy, fschrc, fschmd, fschds, fscsyl }; + +/* + * The FSCHMD registers and other defines + */ + +/* chip identification */ +#define FSCHMD_REG_IDENT_0 0x00 +#define FSCHMD_REG_IDENT_1 0x01 +#define FSCHMD_REG_IDENT_2 0x02 +#define FSCHMD_REG_REVISION 0x03 + +/* global control and status */ +#define FSCHMD_REG_EVENT_STATE 0x04 +#define FSCHMD_REG_CONTROL 0x05 + +#define FSCHMD_CONTROL_ALERT_LED 0x01 + +/* watchdog */ +static const u8 FSCHMD_REG_WDOG_CONTROL[7] = { + 0x21, 0x21, 0x21, 0x21, 0x21, 0x28, 0x28 }; +static const u8 FSCHMD_REG_WDOG_STATE[7] = { + 0x23, 0x23, 0x23, 0x23, 0x23, 0x29, 0x29 }; +static const u8 FSCHMD_REG_WDOG_PRESET[7] = { + 0x28, 0x28, 0x28, 0x28, 0x28, 0x2a, 0x2a }; + +#define FSCHMD_WDOG_CONTROL_TRIGGER 0x10 +#define FSCHMD_WDOG_CONTROL_STARTED 0x10 /* the same as trigger */ +#define FSCHMD_WDOG_CONTROL_STOP 0x20 +#define FSCHMD_WDOG_CONTROL_RESOLUTION 0x40 + +#define FSCHMD_WDOG_STATE_CARDRESET 0x02 + +/* voltages, weird order is to keep the same order as the old drivers */ +static const u8 FSCHMD_REG_VOLT[7][6] = { + { 0x45, 0x42, 0x48 }, /* pos */ + { 0x45, 0x42, 0x48 }, /* her */ + { 0x45, 0x42, 0x48 }, /* scy */ + { 0x45, 0x42, 0x48 }, /* hrc */ + { 0x45, 0x42, 0x48 }, /* hmd */ + { 0x21, 0x20, 0x22 }, /* hds */ + { 0x21, 0x20, 0x22, 0x23, 0x24, 0x25 }, /* syl */ +}; + +static const int FSCHMD_NO_VOLT_SENSORS[7] = { 3, 3, 3, 3, 3, 3, 6 }; + +/* + * minimum pwm at which the fan is driven (pwm can be increased depending on + * the temp. Notice that for the scy some fans share there minimum speed. + * Also notice that with the scy the sensor order is different than with the + * other chips, this order was in the 2.4 driver and kept for consistency. + */ +static const u8 FSCHMD_REG_FAN_MIN[7][7] = { + { 0x55, 0x65 }, /* pos */ + { 0x55, 0x65, 0xb5 }, /* her */ + { 0x65, 0x65, 0x55, 0xa5, 0x55, 0xa5 }, /* scy */ + { 0x55, 0x65, 0xa5, 0xb5 }, /* hrc */ + { 0x55, 0x65, 0xa5, 0xb5, 0xc5 }, /* hmd */ + { 0x55, 0x65, 0xa5, 0xb5, 0xc5 }, /* hds */ + { 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb4 }, /* syl */ +}; + +/* actual fan speed */ +static const u8 FSCHMD_REG_FAN_ACT[7][7] = { + { 0x0e, 0x6b, 0xab }, /* pos */ + { 0x0e, 0x6b, 0xbb }, /* her */ + { 0x6b, 0x6c, 0x0e, 0xab, 0x5c, 0xbb }, /* scy */ + { 0x0e, 0x6b, 0xab, 0xbb }, /* hrc */ + { 0x5b, 0x6b, 0xab, 0xbb, 0xcb }, /* hmd */ + { 0x5b, 0x6b, 0xab, 0xbb, 0xcb }, /* hds */ + { 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7 }, /* syl */ +}; + +/* fan status registers */ +static const u8 FSCHMD_REG_FAN_STATE[7][7] = { + { 0x0d, 0x62, 0xa2 }, /* pos */ + { 0x0d, 0x62, 0xb2 }, /* her */ + { 0x62, 0x61, 0x0d, 0xa2, 0x52, 0xb2 }, /* scy */ + { 0x0d, 0x62, 0xa2, 0xb2 }, /* hrc */ + { 0x52, 0x62, 0xa2, 0xb2, 0xc2 }, /* hmd */ + { 0x52, 0x62, 0xa2, 0xb2, 0xc2 }, /* hds */ + { 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0 }, /* syl */ +}; + +/* fan ripple / divider registers */ +static const u8 FSCHMD_REG_FAN_RIPPLE[7][7] = { + { 0x0f, 0x6f, 0xaf }, /* pos */ + { 0x0f, 0x6f, 0xbf }, /* her */ + { 0x6f, 0x6f, 0x0f, 0xaf, 0x0f, 0xbf }, /* scy */ + { 0x0f, 0x6f, 0xaf, 0xbf }, /* hrc */ + { 0x5f, 0x6f, 0xaf, 0xbf, 0xcf }, /* hmd */ + { 0x5f, 0x6f, 0xaf, 0xbf, 0xcf }, /* hds */ + { 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6 }, /* syl */ +}; + +static const int FSCHMD_NO_FAN_SENSORS[7] = { 3, 3, 6, 4, 5, 5, 7 }; + +/* Fan status register bitmasks */ +#define FSCHMD_FAN_ALARM 0x04 /* called fault by FSC! */ +#define FSCHMD_FAN_NOT_PRESENT 0x08 +#define FSCHMD_FAN_DISABLED 0x80 + + +/* actual temperature registers */ +static const u8 FSCHMD_REG_TEMP_ACT[7][11] = { + { 0x64, 0x32, 0x35 }, /* pos */ + { 0x64, 0x32, 0x35 }, /* her */ + { 0x64, 0xD0, 0x32, 0x35 }, /* scy */ + { 0x64, 0x32, 0x35 }, /* hrc */ + { 0x70, 0x80, 0x90, 0xd0, 0xe0 }, /* hmd */ + { 0x70, 0x80, 0x90, 0xd0, 0xe0 }, /* hds */ + { 0x58, 0x68, 0x78, 0x88, 0x98, 0xa8, /* syl */ + 0xb8, 0xc8, 0xd8, 0xe8, 0xf8 }, +}; + +/* temperature state registers */ +static const u8 FSCHMD_REG_TEMP_STATE[7][11] = { + { 0x71, 0x81, 0x91 }, /* pos */ + { 0x71, 0x81, 0x91 }, /* her */ + { 0x71, 0xd1, 0x81, 0x91 }, /* scy */ + { 0x71, 0x81, 0x91 }, /* hrc */ + { 0x71, 0x81, 0x91, 0xd1, 0xe1 }, /* hmd */ + { 0x71, 0x81, 0x91, 0xd1, 0xe1 }, /* hds */ + { 0x59, 0x69, 0x79, 0x89, 0x99, 0xa9, /* syl */ + 0xb9, 0xc9, 0xd9, 0xe9, 0xf9 }, +}; + +/* + * temperature high limit registers, FSC does not document these. Proven to be + * there with field testing on the fscher and fschrc, already supported / used + * in the fscscy 2.4 driver. FSC has confirmed that the fschmd has registers + * at these addresses, but doesn't want to confirm they are the same as with + * the fscher?? + */ +static const u8 FSCHMD_REG_TEMP_LIMIT[7][11] = { + { 0, 0, 0 }, /* pos */ + { 0x76, 0x86, 0x96 }, /* her */ + { 0x76, 0xd6, 0x86, 0x96 }, /* scy */ + { 0x76, 0x86, 0x96 }, /* hrc */ + { 0x76, 0x86, 0x96, 0xd6, 0xe6 }, /* hmd */ + { 0x76, 0x86, 0x96, 0xd6, 0xe6 }, /* hds */ + { 0x5a, 0x6a, 0x7a, 0x8a, 0x9a, 0xaa, /* syl */ + 0xba, 0xca, 0xda, 0xea, 0xfa }, +}; + +/* + * These were found through experimenting with an fscher, currently they are + * not used, but we keep them around for future reference. + * On the fscsyl AUTOP1 lives at 0x#c (so 0x5c for fan1, 0x6c for fan2, etc), + * AUTOP2 lives at 0x#e, and 0x#1 is a bitmask defining which temps influence + * the fan speed. + * static const u8 FSCHER_REG_TEMP_AUTOP1[] = { 0x73, 0x83, 0x93 }; + * static const u8 FSCHER_REG_TEMP_AUTOP2[] = { 0x75, 0x85, 0x95 }; + */ + +static const int FSCHMD_NO_TEMP_SENSORS[7] = { 3, 3, 4, 3, 5, 5, 11 }; + +/* temp status register bitmasks */ +#define FSCHMD_TEMP_WORKING 0x01 +#define FSCHMD_TEMP_ALERT 0x02 +#define FSCHMD_TEMP_DISABLED 0x80 +/* there only really is an alarm if the sensor is working and alert == 1 */ +#define FSCHMD_TEMP_ALARM_MASK \ + (FSCHMD_TEMP_WORKING | FSCHMD_TEMP_ALERT) + +/* + * Functions declarations + */ + +static int fschmd_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int fschmd_detect(struct i2c_client *client, + struct i2c_board_info *info); +static int fschmd_remove(struct i2c_client *client); +static struct fschmd_data *fschmd_update_device(struct device *dev); + +/* + * Driver data (common to all clients) + */ + +static const struct i2c_device_id fschmd_id[] = { + { "fscpos", fscpos }, + { "fscher", fscher }, + { "fscscy", fscscy }, + { "fschrc", fschrc }, + { "fschmd", fschmd }, + { "fschds", fschds }, + { "fscsyl", fscsyl }, + { } +}; +MODULE_DEVICE_TABLE(i2c, fschmd_id); + +static struct i2c_driver fschmd_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "fschmd", + }, + .probe = fschmd_probe, + .remove = fschmd_remove, + .id_table = fschmd_id, + .detect = fschmd_detect, + .address_list = normal_i2c, +}; + +/* + * Client data (each client gets its own) + */ + +struct fschmd_data { + struct i2c_client *client; + struct device *hwmon_dev; + struct mutex update_lock; + struct mutex watchdog_lock; + struct list_head list; /* member of the watchdog_data_list */ + struct kref kref; + struct miscdevice watchdog_miscdev; + enum chips kind; + unsigned long watchdog_is_open; + char watchdog_expect_close; + char watchdog_name[10]; /* must be unique to avoid sysfs conflict */ + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* register values */ + u8 revision; /* chip revision */ + u8 global_control; /* global control register */ + u8 watchdog_control; /* watchdog control register */ + u8 watchdog_state; /* watchdog status register */ + u8 watchdog_preset; /* watchdog counter preset on trigger val */ + u8 volt[6]; /* voltage */ + u8 temp_act[11]; /* temperature */ + u8 temp_status[11]; /* status of sensor */ + u8 temp_max[11]; /* high temp limit, notice: undocumented! */ + u8 fan_act[7]; /* fans revolutions per second */ + u8 fan_status[7]; /* fan status */ + u8 fan_min[7]; /* fan min value for rps */ + u8 fan_ripple[7]; /* divider for rps */ +}; + +/* + * Global variables to hold information read from special DMI tables, which are + * available on FSC machines with an fscher or later chip. There is no need to + * protect these with a lock as they are only modified from our attach function + * which always gets called with the i2c-core lock held and never accessed + * before the attach function is done with them. + */ +static int dmi_mult[6] = { 490, 200, 100, 100, 200, 100 }; +static int dmi_offset[6] = { 0, 0, 0, 0, 0, 0 }; +static int dmi_vref = -1; + +/* + * Somewhat ugly :( global data pointer list with all fschmd devices, so that + * we can find our device data as when using misc_register there is no other + * method to get to ones device data from the open fop. + */ +static LIST_HEAD(watchdog_data_list); +/* Note this lock not only protect list access, but also data.kref access */ +static DEFINE_MUTEX(watchdog_data_mutex); + +/* + * Release our data struct when we're detached from the i2c client *and* all + * references to our watchdog device are released + */ +static void fschmd_release_resources(struct kref *ref) +{ + struct fschmd_data *data = container_of(ref, struct fschmd_data, kref); + kfree(data); +} + +/* + * Sysfs attr show / store functions + */ + +static ssize_t show_in_value(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + const int max_reading[3] = { 14200, 6600, 3300 }; + int index = to_sensor_dev_attr(devattr)->index; + struct fschmd_data *data = fschmd_update_device(dev); + + if (data->kind == fscher || data->kind >= fschrc) + return sprintf(buf, "%d\n", (data->volt[index] * dmi_vref * + dmi_mult[index]) / 255 + dmi_offset[index]); + else + return sprintf(buf, "%d\n", (data->volt[index] * + max_reading[index] + 128) / 255); +} + + +#define TEMP_FROM_REG(val) (((val) - 128) * 1000) + +static ssize_t show_temp_value(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct fschmd_data *data = fschmd_update_device(dev); + + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_act[index])); +} + +static ssize_t show_temp_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct fschmd_data *data = fschmd_update_device(dev); + + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max[index])); +} + +static ssize_t store_temp_max(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct fschmd_data *data = dev_get_drvdata(dev); + long v; + int err; + + err = kstrtol(buf, 10, &v); + if (err) + return err; + + v = clamp_val(v / 1000, -128, 127) + 128; + + mutex_lock(&data->update_lock); + i2c_smbus_write_byte_data(to_i2c_client(dev), + FSCHMD_REG_TEMP_LIMIT[data->kind][index], v); + data->temp_max[index] = v; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_temp_fault(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct fschmd_data *data = fschmd_update_device(dev); + + /* bit 0 set means sensor working ok, so no fault! */ + if (data->temp_status[index] & FSCHMD_TEMP_WORKING) + return sprintf(buf, "0\n"); + else + return sprintf(buf, "1\n"); +} + +static ssize_t show_temp_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct fschmd_data *data = fschmd_update_device(dev); + + if ((data->temp_status[index] & FSCHMD_TEMP_ALARM_MASK) == + FSCHMD_TEMP_ALARM_MASK) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + + +#define RPM_FROM_REG(val) ((val) * 60) + +static ssize_t show_fan_value(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct fschmd_data *data = fschmd_update_device(dev); + + return sprintf(buf, "%u\n", RPM_FROM_REG(data->fan_act[index])); +} + +static ssize_t show_fan_div(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct fschmd_data *data = fschmd_update_device(dev); + + /* bits 2..7 reserved => mask with 3 */ + return sprintf(buf, "%d\n", 1 << (data->fan_ripple[index] & 3)); +} + +static ssize_t store_fan_div(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + u8 reg; + int index = to_sensor_dev_attr(devattr)->index; + struct fschmd_data *data = dev_get_drvdata(dev); + /* supported values: 2, 4, 8 */ + unsigned long v; + int err; + + err = kstrtoul(buf, 10, &v); + if (err) + return err; + + switch (v) { + case 2: + v = 1; + break; + case 4: + v = 2; + break; + case 8: + v = 3; + break; + default: + dev_err(dev, + "fan_div value %lu not supported. Choose one of 2, 4 or 8!\n", + v); + return -EINVAL; + } + + mutex_lock(&data->update_lock); + + reg = i2c_smbus_read_byte_data(to_i2c_client(dev), + FSCHMD_REG_FAN_RIPPLE[data->kind][index]); + + /* bits 2..7 reserved => mask with 0x03 */ + reg &= ~0x03; + reg |= v; + + i2c_smbus_write_byte_data(to_i2c_client(dev), + FSCHMD_REG_FAN_RIPPLE[data->kind][index], reg); + + data->fan_ripple[index] = reg; + + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_fan_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct fschmd_data *data = fschmd_update_device(dev); + + if (data->fan_status[index] & FSCHMD_FAN_ALARM) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t show_fan_fault(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct fschmd_data *data = fschmd_update_device(dev); + + if (data->fan_status[index] & FSCHMD_FAN_NOT_PRESENT) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + + +static ssize_t show_pwm_auto_point1_pwm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct fschmd_data *data = fschmd_update_device(dev); + int val = data->fan_min[index]; + + /* 0 = allow turning off (except on the syl), 1-255 = 50-100% */ + if (val || data->kind == fscsyl) + val = val / 2 + 128; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t store_pwm_auto_point1_pwm(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct fschmd_data *data = dev_get_drvdata(dev); + unsigned long v; + int err; + + err = kstrtoul(buf, 10, &v); + if (err) + return err; + + /* reg: 0 = allow turning off (except on the syl), 1-255 = 50-100% */ + if (v || data->kind == fscsyl) { + v = clamp_val(v, 128, 255); + v = (v - 128) * 2 + 1; + } + + mutex_lock(&data->update_lock); + + i2c_smbus_write_byte_data(to_i2c_client(dev), + FSCHMD_REG_FAN_MIN[data->kind][index], v); + data->fan_min[index] = v; + + mutex_unlock(&data->update_lock); + + return count; +} + + +/* + * The FSC hwmon family has the ability to force an attached alert led to flash + * from software, we export this as an alert_led sysfs attr + */ +static ssize_t alert_led_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct fschmd_data *data = fschmd_update_device(dev); + + if (data->global_control & FSCHMD_CONTROL_ALERT_LED) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t alert_led_store(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count) +{ + u8 reg; + struct fschmd_data *data = dev_get_drvdata(dev); + unsigned long v; + int err; + + err = kstrtoul(buf, 10, &v); + if (err) + return err; + + mutex_lock(&data->update_lock); + + reg = i2c_smbus_read_byte_data(to_i2c_client(dev), FSCHMD_REG_CONTROL); + + if (v) + reg |= FSCHMD_CONTROL_ALERT_LED; + else + reg &= ~FSCHMD_CONTROL_ALERT_LED; + + i2c_smbus_write_byte_data(to_i2c_client(dev), FSCHMD_REG_CONTROL, reg); + + data->global_control = reg; + + mutex_unlock(&data->update_lock); + + return count; +} + +static DEVICE_ATTR_RW(alert_led); + +static struct sensor_device_attribute fschmd_attr[] = { + SENSOR_ATTR(in0_input, 0444, show_in_value, NULL, 0), + SENSOR_ATTR(in1_input, 0444, show_in_value, NULL, 1), + SENSOR_ATTR(in2_input, 0444, show_in_value, NULL, 2), + SENSOR_ATTR(in3_input, 0444, show_in_value, NULL, 3), + SENSOR_ATTR(in4_input, 0444, show_in_value, NULL, 4), + SENSOR_ATTR(in5_input, 0444, show_in_value, NULL, 5), +}; + +static struct sensor_device_attribute fschmd_temp_attr[] = { + SENSOR_ATTR(temp1_input, 0444, show_temp_value, NULL, 0), + SENSOR_ATTR(temp1_max, 0644, show_temp_max, store_temp_max, 0), + SENSOR_ATTR(temp1_fault, 0444, show_temp_fault, NULL, 0), + SENSOR_ATTR(temp1_alarm, 0444, show_temp_alarm, NULL, 0), + SENSOR_ATTR(temp2_input, 0444, show_temp_value, NULL, 1), + SENSOR_ATTR(temp2_max, 0644, show_temp_max, store_temp_max, 1), + SENSOR_ATTR(temp2_fault, 0444, show_temp_fault, NULL, 1), + SENSOR_ATTR(temp2_alarm, 0444, show_temp_alarm, NULL, 1), + SENSOR_ATTR(temp3_input, 0444, show_temp_value, NULL, 2), + SENSOR_ATTR(temp3_max, 0644, show_temp_max, store_temp_max, 2), + SENSOR_ATTR(temp3_fault, 0444, show_temp_fault, NULL, 2), + SENSOR_ATTR(temp3_alarm, 0444, show_temp_alarm, NULL, 2), + SENSOR_ATTR(temp4_input, 0444, show_temp_value, NULL, 3), + SENSOR_ATTR(temp4_max, 0644, show_temp_max, store_temp_max, 3), + SENSOR_ATTR(temp4_fault, 0444, show_temp_fault, NULL, 3), + SENSOR_ATTR(temp4_alarm, 0444, show_temp_alarm, NULL, 3), + SENSOR_ATTR(temp5_input, 0444, show_temp_value, NULL, 4), + SENSOR_ATTR(temp5_max, 0644, show_temp_max, store_temp_max, 4), + SENSOR_ATTR(temp5_fault, 0444, show_temp_fault, NULL, 4), + SENSOR_ATTR(temp5_alarm, 0444, show_temp_alarm, NULL, 4), + SENSOR_ATTR(temp6_input, 0444, show_temp_value, NULL, 5), + SENSOR_ATTR(temp6_max, 0644, show_temp_max, store_temp_max, 5), + SENSOR_ATTR(temp6_fault, 0444, show_temp_fault, NULL, 5), + SENSOR_ATTR(temp6_alarm, 0444, show_temp_alarm, NULL, 5), + SENSOR_ATTR(temp7_input, 0444, show_temp_value, NULL, 6), + SENSOR_ATTR(temp7_max, 0644, show_temp_max, store_temp_max, 6), + SENSOR_ATTR(temp7_fault, 0444, show_temp_fault, NULL, 6), + SENSOR_ATTR(temp7_alarm, 0444, show_temp_alarm, NULL, 6), + SENSOR_ATTR(temp8_input, 0444, show_temp_value, NULL, 7), + SENSOR_ATTR(temp8_max, 0644, show_temp_max, store_temp_max, 7), + SENSOR_ATTR(temp8_fault, 0444, show_temp_fault, NULL, 7), + SENSOR_ATTR(temp8_alarm, 0444, show_temp_alarm, NULL, 7), + SENSOR_ATTR(temp9_input, 0444, show_temp_value, NULL, 8), + SENSOR_ATTR(temp9_max, 0644, show_temp_max, store_temp_max, 8), + SENSOR_ATTR(temp9_fault, 0444, show_temp_fault, NULL, 8), + SENSOR_ATTR(temp9_alarm, 0444, show_temp_alarm, NULL, 8), + SENSOR_ATTR(temp10_input, 0444, show_temp_value, NULL, 9), + SENSOR_ATTR(temp10_max, 0644, show_temp_max, store_temp_max, 9), + SENSOR_ATTR(temp10_fault, 0444, show_temp_fault, NULL, 9), + SENSOR_ATTR(temp10_alarm, 0444, show_temp_alarm, NULL, 9), + SENSOR_ATTR(temp11_input, 0444, show_temp_value, NULL, 10), + SENSOR_ATTR(temp11_max, 0644, show_temp_max, store_temp_max, 10), + SENSOR_ATTR(temp11_fault, 0444, show_temp_fault, NULL, 10), + SENSOR_ATTR(temp11_alarm, 0444, show_temp_alarm, NULL, 10), +}; + +static struct sensor_device_attribute fschmd_fan_attr[] = { + SENSOR_ATTR(fan1_input, 0444, show_fan_value, NULL, 0), + SENSOR_ATTR(fan1_div, 0644, show_fan_div, store_fan_div, 0), + SENSOR_ATTR(fan1_alarm, 0444, show_fan_alarm, NULL, 0), + SENSOR_ATTR(fan1_fault, 0444, show_fan_fault, NULL, 0), + SENSOR_ATTR(pwm1_auto_point1_pwm, 0644, show_pwm_auto_point1_pwm, + store_pwm_auto_point1_pwm, 0), + SENSOR_ATTR(fan2_input, 0444, show_fan_value, NULL, 1), + SENSOR_ATTR(fan2_div, 0644, show_fan_div, store_fan_div, 1), + SENSOR_ATTR(fan2_alarm, 0444, show_fan_alarm, NULL, 1), + SENSOR_ATTR(fan2_fault, 0444, show_fan_fault, NULL, 1), + SENSOR_ATTR(pwm2_auto_point1_pwm, 0644, show_pwm_auto_point1_pwm, + store_pwm_auto_point1_pwm, 1), + SENSOR_ATTR(fan3_input, 0444, show_fan_value, NULL, 2), + SENSOR_ATTR(fan3_div, 0644, show_fan_div, store_fan_div, 2), + SENSOR_ATTR(fan3_alarm, 0444, show_fan_alarm, NULL, 2), + SENSOR_ATTR(fan3_fault, 0444, show_fan_fault, NULL, 2), + SENSOR_ATTR(pwm3_auto_point1_pwm, 0644, show_pwm_auto_point1_pwm, + store_pwm_auto_point1_pwm, 2), + SENSOR_ATTR(fan4_input, 0444, show_fan_value, NULL, 3), + SENSOR_ATTR(fan4_div, 0644, show_fan_div, store_fan_div, 3), + SENSOR_ATTR(fan4_alarm, 0444, show_fan_alarm, NULL, 3), + SENSOR_ATTR(fan4_fault, 0444, show_fan_fault, NULL, 3), + SENSOR_ATTR(pwm4_auto_point1_pwm, 0644, show_pwm_auto_point1_pwm, + store_pwm_auto_point1_pwm, 3), + SENSOR_ATTR(fan5_input, 0444, show_fan_value, NULL, 4), + SENSOR_ATTR(fan5_div, 0644, show_fan_div, store_fan_div, 4), + SENSOR_ATTR(fan5_alarm, 0444, show_fan_alarm, NULL, 4), + SENSOR_ATTR(fan5_fault, 0444, show_fan_fault, NULL, 4), + SENSOR_ATTR(pwm5_auto_point1_pwm, 0644, show_pwm_auto_point1_pwm, + store_pwm_auto_point1_pwm, 4), + SENSOR_ATTR(fan6_input, 0444, show_fan_value, NULL, 5), + SENSOR_ATTR(fan6_div, 0644, show_fan_div, store_fan_div, 5), + SENSOR_ATTR(fan6_alarm, 0444, show_fan_alarm, NULL, 5), + SENSOR_ATTR(fan6_fault, 0444, show_fan_fault, NULL, 5), + SENSOR_ATTR(pwm6_auto_point1_pwm, 0644, show_pwm_auto_point1_pwm, + store_pwm_auto_point1_pwm, 5), + SENSOR_ATTR(fan7_input, 0444, show_fan_value, NULL, 6), + SENSOR_ATTR(fan7_div, 0644, show_fan_div, store_fan_div, 6), + SENSOR_ATTR(fan7_alarm, 0444, show_fan_alarm, NULL, 6), + SENSOR_ATTR(fan7_fault, 0444, show_fan_fault, NULL, 6), + SENSOR_ATTR(pwm7_auto_point1_pwm, 0644, show_pwm_auto_point1_pwm, + store_pwm_auto_point1_pwm, 6), +}; + + +/* + * Watchdog routines + */ + +static int watchdog_set_timeout(struct fschmd_data *data, int timeout) +{ + int ret, resolution; + int kind = data->kind + 1; /* 0-x array index -> 1-x module param */ + + /* 2 second or 60 second resolution? */ + if (timeout <= 510 || kind == fscpos || kind == fscscy) + resolution = 2; + else + resolution = 60; + + if (timeout < resolution || timeout > (resolution * 255)) + return -EINVAL; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + if (resolution == 2) + data->watchdog_control &= ~FSCHMD_WDOG_CONTROL_RESOLUTION; + else + data->watchdog_control |= FSCHMD_WDOG_CONTROL_RESOLUTION; + + data->watchdog_preset = DIV_ROUND_UP(timeout, resolution); + + /* Write new timeout value */ + i2c_smbus_write_byte_data(data->client, + FSCHMD_REG_WDOG_PRESET[data->kind], data->watchdog_preset); + /* Write new control register, do not trigger! */ + i2c_smbus_write_byte_data(data->client, + FSCHMD_REG_WDOG_CONTROL[data->kind], + data->watchdog_control & ~FSCHMD_WDOG_CONTROL_TRIGGER); + + ret = data->watchdog_preset * resolution; + +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_get_timeout(struct fschmd_data *data) +{ + int timeout; + + mutex_lock(&data->watchdog_lock); + if (data->watchdog_control & FSCHMD_WDOG_CONTROL_RESOLUTION) + timeout = data->watchdog_preset * 60; + else + timeout = data->watchdog_preset * 2; + mutex_unlock(&data->watchdog_lock); + + return timeout; +} + +static int watchdog_trigger(struct fschmd_data *data) +{ + int ret = 0; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + data->watchdog_control |= FSCHMD_WDOG_CONTROL_TRIGGER; + i2c_smbus_write_byte_data(data->client, + FSCHMD_REG_WDOG_CONTROL[data->kind], + data->watchdog_control); +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_stop(struct fschmd_data *data) +{ + int ret = 0; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + data->watchdog_control &= ~FSCHMD_WDOG_CONTROL_STARTED; + /* + * Don't store the stop flag in our watchdog control register copy, as + * its a write only bit (read always returns 0) + */ + i2c_smbus_write_byte_data(data->client, + FSCHMD_REG_WDOG_CONTROL[data->kind], + data->watchdog_control | FSCHMD_WDOG_CONTROL_STOP); +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_open(struct inode *inode, struct file *filp) +{ + struct fschmd_data *pos, *data = NULL; + int watchdog_is_open; + + /* + * We get called from drivers/char/misc.c with misc_mtx hold, and we + * call misc_register() from fschmd_probe() with watchdog_data_mutex + * hold, as misc_register() takes the misc_mtx lock, this is a possible + * deadlock, so we use mutex_trylock here. + */ + if (!mutex_trylock(&watchdog_data_mutex)) + return -ERESTARTSYS; + list_for_each_entry(pos, &watchdog_data_list, list) { + if (pos->watchdog_miscdev.minor == iminor(inode)) { + data = pos; + break; + } + } + /* Note we can never not have found data, so we don't check for this */ + watchdog_is_open = test_and_set_bit(0, &data->watchdog_is_open); + if (!watchdog_is_open) + kref_get(&data->kref); + mutex_unlock(&watchdog_data_mutex); + + if (watchdog_is_open) + return -EBUSY; + + /* Start the watchdog */ + watchdog_trigger(data); + filp->private_data = data; + + return nonseekable_open(inode, filp); +} + +static int watchdog_release(struct inode *inode, struct file *filp) +{ + struct fschmd_data *data = filp->private_data; + + if (data->watchdog_expect_close) { + watchdog_stop(data); + data->watchdog_expect_close = 0; + } else { + watchdog_trigger(data); + dev_crit(&data->client->dev, + "unexpected close, not stopping watchdog!\n"); + } + + clear_bit(0, &data->watchdog_is_open); + + mutex_lock(&watchdog_data_mutex); + kref_put(&data->kref, fschmd_release_resources); + mutex_unlock(&watchdog_data_mutex); + + return 0; +} + +static ssize_t watchdog_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offset) +{ + int ret; + struct fschmd_data *data = filp->private_data; + + if (count) { + if (!nowayout) { + size_t i; + + /* Clear it in case it was set with a previous write */ + data->watchdog_expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + data->watchdog_expect_close = 1; + } + } + ret = watchdog_trigger(data); + if (ret < 0) + return ret; + } + return count; +} + +static long watchdog_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | + WDIOF_CARDRESET, + .identity = "FSC watchdog" + }; + int i, ret = 0; + struct fschmd_data *data = filp->private_data; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ident.firmware_version = data->revision; + if (!nowayout) + ident.options |= WDIOF_MAGICCLOSE; + if (copy_to_user((void __user *)arg, &ident, sizeof(ident))) + ret = -EFAULT; + break; + + case WDIOC_GETSTATUS: + ret = put_user(0, (int __user *)arg); + break; + + case WDIOC_GETBOOTSTATUS: + if (data->watchdog_state & FSCHMD_WDOG_STATE_CARDRESET) + ret = put_user(WDIOF_CARDRESET, (int __user *)arg); + else + ret = put_user(0, (int __user *)arg); + break; + + case WDIOC_KEEPALIVE: + ret = watchdog_trigger(data); + break; + + case WDIOC_GETTIMEOUT: + i = watchdog_get_timeout(data); + ret = put_user(i, (int __user *)arg); + break; + + case WDIOC_SETTIMEOUT: + if (get_user(i, (int __user *)arg)) { + ret = -EFAULT; + break; + } + ret = watchdog_set_timeout(data, i); + if (ret > 0) + ret = put_user(ret, (int __user *)arg); + break; + + case WDIOC_SETOPTIONS: + if (get_user(i, (int __user *)arg)) { + ret = -EFAULT; + break; + } + + if (i & WDIOS_DISABLECARD) + ret = watchdog_stop(data); + else if (i & WDIOS_ENABLECARD) + ret = watchdog_trigger(data); + else + ret = -EINVAL; + + break; + default: + ret = -ENOTTY; + } + return ret; +} + +static const struct file_operations watchdog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = watchdog_open, + .release = watchdog_release, + .write = watchdog_write, + .unlocked_ioctl = watchdog_ioctl, +}; + + +/* + * Detect, register, unregister and update device functions + */ + +/* + * DMI decode routine to read voltage scaling factors from special DMI tables, + * which are available on FSC machines with an fscher or later chip. + */ +static void fschmd_dmi_decode(const struct dmi_header *header, void *dummy) +{ + int i, mult[3] = { 0 }, offset[3] = { 0 }, vref = 0, found = 0; + + /* + * dmi code ugliness, we get passed the address of the contents of + * a complete DMI record, but in the form of a dmi_header pointer, in + * reality this address holds header->length bytes of which the header + * are the first 4 bytes + */ + u8 *dmi_data = (u8 *)header; + + /* We are looking for OEM-specific type 185 */ + if (header->type != 185) + return; + + /* + * we are looking for what Siemens calls "subtype" 19, the subtype + * is stored in byte 5 of the dmi block + */ + if (header->length < 5 || dmi_data[4] != 19) + return; + + /* + * After the subtype comes 1 unknown byte and then blocks of 5 bytes, + * consisting of what Siemens calls an "Entity" number, followed by + * 2 16-bit words in LSB first order + */ + for (i = 6; (i + 4) < header->length; i += 5) { + /* entity 1 - 3: voltage multiplier and offset */ + if (dmi_data[i] >= 1 && dmi_data[i] <= 3) { + /* Our in sensors order and the DMI order differ */ + const int shuffle[3] = { 1, 0, 2 }; + int in = shuffle[dmi_data[i] - 1]; + + /* Check for twice the same entity */ + if (found & (1 << in)) + return; + + mult[in] = dmi_data[i + 1] | (dmi_data[i + 2] << 8); + offset[in] = dmi_data[i + 3] | (dmi_data[i + 4] << 8); + + found |= 1 << in; + } + + /* entity 7: reference voltage */ + if (dmi_data[i] == 7) { + /* Check for twice the same entity */ + if (found & 0x08) + return; + + vref = dmi_data[i + 1] | (dmi_data[i + 2] << 8); + + found |= 0x08; + } + } + + if (found == 0x0F) { + for (i = 0; i < 3; i++) { + dmi_mult[i] = mult[i] * 10; + dmi_offset[i] = offset[i] * 10; + } + /* + * According to the docs there should be separate dmi entries + * for the mult's and offsets of in3-5 of the syl, but on + * my test machine these are not present + */ + dmi_mult[3] = dmi_mult[2]; + dmi_mult[4] = dmi_mult[1]; + dmi_mult[5] = dmi_mult[2]; + dmi_offset[3] = dmi_offset[2]; + dmi_offset[4] = dmi_offset[1]; + dmi_offset[5] = dmi_offset[2]; + dmi_vref = vref; + } +} + +static int fschmd_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + enum chips kind; + struct i2c_adapter *adapter = client->adapter; + char id[4]; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* Detect & Identify the chip */ + id[0] = i2c_smbus_read_byte_data(client, FSCHMD_REG_IDENT_0); + id[1] = i2c_smbus_read_byte_data(client, FSCHMD_REG_IDENT_1); + id[2] = i2c_smbus_read_byte_data(client, FSCHMD_REG_IDENT_2); + id[3] = '\0'; + + if (!strcmp(id, "PEG")) + kind = fscpos; + else if (!strcmp(id, "HER")) + kind = fscher; + else if (!strcmp(id, "SCY")) + kind = fscscy; + else if (!strcmp(id, "HRC")) + kind = fschrc; + else if (!strcmp(id, "HMD")) + kind = fschmd; + else if (!strcmp(id, "HDS")) + kind = fschds; + else if (!strcmp(id, "SYL")) + kind = fscsyl; + else + return -ENODEV; + + strlcpy(info->type, fschmd_id[kind].name, I2C_NAME_SIZE); + + return 0; +} + +static int fschmd_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct fschmd_data *data; + const char * const names[7] = { "Poseidon", "Hermes", "Scylla", + "Heracles", "Heimdall", "Hades", "Syleus" }; + const int watchdog_minors[] = { WATCHDOG_MINOR, 212, 213, 214, 215 }; + int i, err; + enum chips kind = id->driver_data; + + data = kzalloc(sizeof(struct fschmd_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + mutex_init(&data->watchdog_lock); + INIT_LIST_HEAD(&data->list); + kref_init(&data->kref); + /* + * Store client pointer in our data struct for watchdog usage + * (where the client is found through a data ptr instead of the + * otherway around) + */ + data->client = client; + data->kind = kind; + + if (kind == fscpos) { + /* + * The Poseidon has hardwired temp limits, fill these + * in for the alarm resetting code + */ + data->temp_max[0] = 70 + 128; + data->temp_max[1] = 50 + 128; + data->temp_max[2] = 50 + 128; + } + + /* Read the special DMI table for fscher and newer chips */ + if ((kind == fscher || kind >= fschrc) && dmi_vref == -1) { + dmi_walk(fschmd_dmi_decode, NULL); + if (dmi_vref == -1) { + dev_warn(&client->dev, + "Couldn't get voltage scaling factors from " + "BIOS DMI table, using builtin defaults\n"); + dmi_vref = 33; + } + } + + /* Read in some never changing registers */ + data->revision = i2c_smbus_read_byte_data(client, FSCHMD_REG_REVISION); + data->global_control = i2c_smbus_read_byte_data(client, + FSCHMD_REG_CONTROL); + data->watchdog_control = i2c_smbus_read_byte_data(client, + FSCHMD_REG_WDOG_CONTROL[data->kind]); + data->watchdog_state = i2c_smbus_read_byte_data(client, + FSCHMD_REG_WDOG_STATE[data->kind]); + data->watchdog_preset = i2c_smbus_read_byte_data(client, + FSCHMD_REG_WDOG_PRESET[data->kind]); + + err = device_create_file(&client->dev, &dev_attr_alert_led); + if (err) + goto exit_detach; + + for (i = 0; i < FSCHMD_NO_VOLT_SENSORS[data->kind]; i++) { + err = device_create_file(&client->dev, + &fschmd_attr[i].dev_attr); + if (err) + goto exit_detach; + } + + for (i = 0; i < (FSCHMD_NO_TEMP_SENSORS[data->kind] * 4); i++) { + /* Poseidon doesn't have TEMP_LIMIT registers */ + if (kind == fscpos && fschmd_temp_attr[i].dev_attr.show == + show_temp_max) + continue; + + if (kind == fscsyl) { + if (i % 4 == 0) + data->temp_status[i / 4] = + i2c_smbus_read_byte_data(client, + FSCHMD_REG_TEMP_STATE + [data->kind][i / 4]); + if (data->temp_status[i / 4] & FSCHMD_TEMP_DISABLED) + continue; + } + + err = device_create_file(&client->dev, + &fschmd_temp_attr[i].dev_attr); + if (err) + goto exit_detach; + } + + for (i = 0; i < (FSCHMD_NO_FAN_SENSORS[data->kind] * 5); i++) { + /* Poseidon doesn't have a FAN_MIN register for its 3rd fan */ + if (kind == fscpos && + !strcmp(fschmd_fan_attr[i].dev_attr.attr.name, + "pwm3_auto_point1_pwm")) + continue; + + if (kind == fscsyl) { + if (i % 5 == 0) + data->fan_status[i / 5] = + i2c_smbus_read_byte_data(client, + FSCHMD_REG_FAN_STATE + [data->kind][i / 5]); + if (data->fan_status[i / 5] & FSCHMD_FAN_DISABLED) + continue; + } + + err = device_create_file(&client->dev, + &fschmd_fan_attr[i].dev_attr); + if (err) + goto exit_detach; + } + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + data->hwmon_dev = NULL; + goto exit_detach; + } + + /* + * We take the data_mutex lock early so that watchdog_open() cannot + * run when misc_register() has completed, but we've not yet added + * our data to the watchdog_data_list (and set the default timeout) + */ + mutex_lock(&watchdog_data_mutex); + for (i = 0; i < ARRAY_SIZE(watchdog_minors); i++) { + /* Register our watchdog part */ + snprintf(data->watchdog_name, sizeof(data->watchdog_name), + "watchdog%c", (i == 0) ? '\0' : ('0' + i)); + data->watchdog_miscdev.name = data->watchdog_name; + data->watchdog_miscdev.fops = &watchdog_fops; + data->watchdog_miscdev.minor = watchdog_minors[i]; + err = misc_register(&data->watchdog_miscdev); + if (err == -EBUSY) + continue; + if (err) { + data->watchdog_miscdev.minor = 0; + dev_err(&client->dev, + "Registering watchdog chardev: %d\n", err); + break; + } + + list_add(&data->list, &watchdog_data_list); + watchdog_set_timeout(data, 60); + dev_info(&client->dev, + "Registered watchdog chardev major 10, minor: %d\n", + watchdog_minors[i]); + break; + } + if (i == ARRAY_SIZE(watchdog_minors)) { + data->watchdog_miscdev.minor = 0; + dev_warn(&client->dev, + "Couldn't register watchdog chardev (due to no free minor)\n"); + } + mutex_unlock(&watchdog_data_mutex); + + dev_info(&client->dev, "Detected FSC %s chip, revision: %d\n", + names[data->kind], (int) data->revision); + + return 0; + +exit_detach: + fschmd_remove(client); /* will also free data for us */ + return err; +} + +static int fschmd_remove(struct i2c_client *client) +{ + struct fschmd_data *data = i2c_get_clientdata(client); + int i; + + /* Unregister the watchdog (if registered) */ + if (data->watchdog_miscdev.minor) { + misc_deregister(&data->watchdog_miscdev); + if (data->watchdog_is_open) { + dev_warn(&client->dev, + "i2c client detached with watchdog open! " + "Stopping watchdog.\n"); + watchdog_stop(data); + } + mutex_lock(&watchdog_data_mutex); + list_del(&data->list); + mutex_unlock(&watchdog_data_mutex); + /* Tell the watchdog code the client is gone */ + mutex_lock(&data->watchdog_lock); + data->client = NULL; + mutex_unlock(&data->watchdog_lock); + } + + /* + * Check if registered in case we're called from fschmd_detect + * to cleanup after an error + */ + if (data->hwmon_dev) + hwmon_device_unregister(data->hwmon_dev); + + device_remove_file(&client->dev, &dev_attr_alert_led); + for (i = 0; i < (FSCHMD_NO_VOLT_SENSORS[data->kind]); i++) + device_remove_file(&client->dev, &fschmd_attr[i].dev_attr); + for (i = 0; i < (FSCHMD_NO_TEMP_SENSORS[data->kind] * 4); i++) + device_remove_file(&client->dev, + &fschmd_temp_attr[i].dev_attr); + for (i = 0; i < (FSCHMD_NO_FAN_SENSORS[data->kind] * 5); i++) + device_remove_file(&client->dev, + &fschmd_fan_attr[i].dev_attr); + + mutex_lock(&watchdog_data_mutex); + kref_put(&data->kref, fschmd_release_resources); + mutex_unlock(&watchdog_data_mutex); + + return 0; +} + +static struct fschmd_data *fschmd_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fschmd_data *data = i2c_get_clientdata(client); + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + 2 * HZ) || !data->valid) { + + for (i = 0; i < FSCHMD_NO_TEMP_SENSORS[data->kind]; i++) { + data->temp_act[i] = i2c_smbus_read_byte_data(client, + FSCHMD_REG_TEMP_ACT[data->kind][i]); + data->temp_status[i] = i2c_smbus_read_byte_data(client, + FSCHMD_REG_TEMP_STATE[data->kind][i]); + + /* The fscpos doesn't have TEMP_LIMIT registers */ + if (FSCHMD_REG_TEMP_LIMIT[data->kind][i]) + data->temp_max[i] = i2c_smbus_read_byte_data( + client, + FSCHMD_REG_TEMP_LIMIT[data->kind][i]); + + /* + * reset alarm if the alarm condition is gone, + * the chip doesn't do this itself + */ + if ((data->temp_status[i] & FSCHMD_TEMP_ALARM_MASK) == + FSCHMD_TEMP_ALARM_MASK && + data->temp_act[i] < data->temp_max[i]) + i2c_smbus_write_byte_data(client, + FSCHMD_REG_TEMP_STATE[data->kind][i], + data->temp_status[i]); + } + + for (i = 0; i < FSCHMD_NO_FAN_SENSORS[data->kind]; i++) { + data->fan_act[i] = i2c_smbus_read_byte_data(client, + FSCHMD_REG_FAN_ACT[data->kind][i]); + data->fan_status[i] = i2c_smbus_read_byte_data(client, + FSCHMD_REG_FAN_STATE[data->kind][i]); + data->fan_ripple[i] = i2c_smbus_read_byte_data(client, + FSCHMD_REG_FAN_RIPPLE[data->kind][i]); + + /* The fscpos third fan doesn't have a fan_min */ + if (FSCHMD_REG_FAN_MIN[data->kind][i]) + data->fan_min[i] = i2c_smbus_read_byte_data( + client, + FSCHMD_REG_FAN_MIN[data->kind][i]); + + /* reset fan status if speed is back to > 0 */ + if ((data->fan_status[i] & FSCHMD_FAN_ALARM) && + data->fan_act[i]) + i2c_smbus_write_byte_data(client, + FSCHMD_REG_FAN_STATE[data->kind][i], + data->fan_status[i]); + } + + for (i = 0; i < FSCHMD_NO_VOLT_SENSORS[data->kind]; i++) + data->volt[i] = i2c_smbus_read_byte_data(client, + FSCHMD_REG_VOLT[data->kind][i]); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +module_i2c_driver(fschmd_driver); + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("FSC Poseidon, Hermes, Scylla, Heracles, Heimdall, Hades " + "and Syleus driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ftsteutates.c b/drivers/hwmon/ftsteutates.c new file mode 100644 index 000000000..0801f48a4 --- /dev/null +++ b/drivers/hwmon/ftsteutates.c @@ -0,0 +1,866 @@ +/* + * Support for the FTS Systemmonitoring Chip "Teutates" + * + * Copyright (C) 2016 Fujitsu Technology Solutions GmbH, + * Thilo Cestonaro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FTS_DEVICE_ID_REG 0x0000 +#define FTS_DEVICE_REVISION_REG 0x0001 +#define FTS_DEVICE_STATUS_REG 0x0004 +#define FTS_SATELLITE_STATUS_REG 0x0005 +#define FTS_EVENT_STATUS_REG 0x0006 +#define FTS_GLOBAL_CONTROL_REG 0x0007 + +#define FTS_DEVICE_DETECT_REG_1 0x0C +#define FTS_DEVICE_DETECT_REG_2 0x0D +#define FTS_DEVICE_DETECT_REG_3 0x0E + +#define FTS_SENSOR_EVENT_REG 0x0010 + +#define FTS_FAN_EVENT_REG 0x0014 +#define FTS_FAN_PRESENT_REG 0x0015 + +#define FTS_POWER_ON_TIME_COUNTER_A 0x007A +#define FTS_POWER_ON_TIME_COUNTER_B 0x007B +#define FTS_POWER_ON_TIME_COUNTER_C 0x007C + +#define FTS_PAGE_SELECT_REG 0x007F + +#define FTS_WATCHDOG_TIME_PRESET 0x000B +#define FTS_WATCHDOG_CONTROL 0x5081 + +#define FTS_NO_FAN_SENSORS 0x08 +#define FTS_NO_TEMP_SENSORS 0x10 +#define FTS_NO_VOLT_SENSORS 0x04 + +static const unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END }; + +static const struct i2c_device_id fts_id[] = { + { "ftsteutates", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, fts_id); + +enum WATCHDOG_RESOLUTION { + seconds = 1, + minutes = 60 +}; + +struct fts_data { + struct i2c_client *client; + /* update sensor data lock */ + struct mutex update_lock; + /* read/write register lock */ + struct mutex access_lock; + unsigned long last_updated; /* in jiffies */ + struct watchdog_device wdd; + enum WATCHDOG_RESOLUTION resolution; + bool valid; /* false until following fields are valid */ + + u8 volt[FTS_NO_VOLT_SENSORS]; + + u8 temp_input[FTS_NO_TEMP_SENSORS]; + u8 temp_alarm; + + u8 fan_present; + u8 fan_input[FTS_NO_FAN_SENSORS]; /* in rps */ + u8 fan_source[FTS_NO_FAN_SENSORS]; + u8 fan_alarm; +}; + +#define FTS_REG_FAN_INPUT(idx) ((idx) + 0x20) +#define FTS_REG_FAN_SOURCE(idx) ((idx) + 0x30) +#define FTS_REG_FAN_CONTROL(idx) (((idx) << 16) + 0x4881) + +#define FTS_REG_TEMP_INPUT(idx) ((idx) + 0x40) +#define FTS_REG_TEMP_CONTROL(idx) (((idx) << 16) + 0x0681) + +#define FTS_REG_VOLT(idx) ((idx) + 0x18) + +/*****************************************************************************/ +/* I2C Helper functions */ +/*****************************************************************************/ +static int fts_read_byte(struct i2c_client *client, unsigned short reg) +{ + int ret; + unsigned char page = reg >> 8; + struct fts_data *data = dev_get_drvdata(&client->dev); + + mutex_lock(&data->access_lock); + + dev_dbg(&client->dev, "page select - page: 0x%.02x\n", page); + ret = i2c_smbus_write_byte_data(client, FTS_PAGE_SELECT_REG, page); + if (ret < 0) + goto error; + + reg &= 0xFF; + ret = i2c_smbus_read_byte_data(client, reg); + dev_dbg(&client->dev, "read - reg: 0x%.02x: val: 0x%.02x\n", reg, ret); + +error: + mutex_unlock(&data->access_lock); + return ret; +} + +static int fts_write_byte(struct i2c_client *client, unsigned short reg, + unsigned char value) +{ + int ret; + unsigned char page = reg >> 8; + struct fts_data *data = dev_get_drvdata(&client->dev); + + mutex_lock(&data->access_lock); + + dev_dbg(&client->dev, "page select - page: 0x%.02x\n", page); + ret = i2c_smbus_write_byte_data(client, FTS_PAGE_SELECT_REG, page); + if (ret < 0) + goto error; + + reg &= 0xFF; + dev_dbg(&client->dev, + "write - reg: 0x%.02x: val: 0x%.02x\n", reg, value); + ret = i2c_smbus_write_byte_data(client, reg, value); + +error: + mutex_unlock(&data->access_lock); + return ret; +} + +/*****************************************************************************/ +/* Data Updater Helper function */ +/*****************************************************************************/ +static int fts_update_device(struct fts_data *data) +{ + int i; + int err = 0; + + mutex_lock(&data->update_lock); + if (!time_after(jiffies, data->last_updated + 2 * HZ) && data->valid) + goto exit; + + err = fts_read_byte(data->client, FTS_DEVICE_STATUS_REG); + if (err < 0) + goto exit; + + data->valid = !!(err & 0x02); /* Data not ready yet */ + if (unlikely(!data->valid)) { + err = -EAGAIN; + goto exit; + } + + err = fts_read_byte(data->client, FTS_FAN_PRESENT_REG); + if (err < 0) + goto exit; + data->fan_present = err; + + err = fts_read_byte(data->client, FTS_FAN_EVENT_REG); + if (err < 0) + goto exit; + data->fan_alarm = err; + + for (i = 0; i < FTS_NO_FAN_SENSORS; i++) { + if (data->fan_present & BIT(i)) { + err = fts_read_byte(data->client, FTS_REG_FAN_INPUT(i)); + if (err < 0) + goto exit; + data->fan_input[i] = err; + + err = fts_read_byte(data->client, + FTS_REG_FAN_SOURCE(i)); + if (err < 0) + goto exit; + data->fan_source[i] = err; + } else { + data->fan_input[i] = 0; + data->fan_source[i] = 0; + } + } + + err = fts_read_byte(data->client, FTS_SENSOR_EVENT_REG); + if (err < 0) + goto exit; + data->temp_alarm = err; + + for (i = 0; i < FTS_NO_TEMP_SENSORS; i++) { + err = fts_read_byte(data->client, FTS_REG_TEMP_INPUT(i)); + if (err < 0) + goto exit; + data->temp_input[i] = err; + } + + for (i = 0; i < FTS_NO_VOLT_SENSORS; i++) { + err = fts_read_byte(data->client, FTS_REG_VOLT(i)); + if (err < 0) + goto exit; + data->volt[i] = err; + } + data->last_updated = jiffies; + err = 0; +exit: + mutex_unlock(&data->update_lock); + return err; +} + +/*****************************************************************************/ +/* Watchdog functions */ +/*****************************************************************************/ +static int fts_wd_set_resolution(struct fts_data *data, + enum WATCHDOG_RESOLUTION resolution) +{ + int ret; + + if (data->resolution == resolution) + return 0; + + ret = fts_read_byte(data->client, FTS_WATCHDOG_CONTROL); + if (ret < 0) + return ret; + + if ((resolution == seconds && ret & BIT(1)) || + (resolution == minutes && (ret & BIT(1)) == 0)) { + data->resolution = resolution; + return 0; + } + + if (resolution == seconds) + ret |= BIT(1); + else + ret &= ~BIT(1); + + ret = fts_write_byte(data->client, FTS_WATCHDOG_CONTROL, ret); + if (ret < 0) + return ret; + + data->resolution = resolution; + return ret; +} + +static int fts_wd_set_timeout(struct watchdog_device *wdd, unsigned int timeout) +{ + struct fts_data *data; + enum WATCHDOG_RESOLUTION resolution = seconds; + int ret; + + data = watchdog_get_drvdata(wdd); + /* switch watchdog resolution to minutes if timeout does not fit + * into a byte + */ + if (timeout > 0xFF) { + timeout = DIV_ROUND_UP(timeout, 60) * 60; + resolution = minutes; + } + + ret = fts_wd_set_resolution(data, resolution); + if (ret < 0) + return ret; + + wdd->timeout = timeout; + return 0; +} + +static int fts_wd_start(struct watchdog_device *wdd) +{ + struct fts_data *data = watchdog_get_drvdata(wdd); + + return fts_write_byte(data->client, FTS_WATCHDOG_TIME_PRESET, + wdd->timeout / (u8)data->resolution); +} + +static int fts_wd_stop(struct watchdog_device *wdd) +{ + struct fts_data *data; + + data = watchdog_get_drvdata(wdd); + return fts_write_byte(data->client, FTS_WATCHDOG_TIME_PRESET, 0); +} + +static const struct watchdog_info fts_wd_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .identity = "FTS Teutates Hardware Watchdog", +}; + +static const struct watchdog_ops fts_wd_ops = { + .owner = THIS_MODULE, + .start = fts_wd_start, + .stop = fts_wd_stop, + .set_timeout = fts_wd_set_timeout, +}; + +static int fts_watchdog_init(struct fts_data *data) +{ + int timeout, ret; + + watchdog_set_drvdata(&data->wdd, data); + + timeout = fts_read_byte(data->client, FTS_WATCHDOG_TIME_PRESET); + if (timeout < 0) + return timeout; + + /* watchdog not running, set timeout to a default of 60 sec. */ + if (timeout == 0) { + ret = fts_wd_set_resolution(data, seconds); + if (ret < 0) + return ret; + data->wdd.timeout = 60; + } else { + ret = fts_read_byte(data->client, FTS_WATCHDOG_CONTROL); + if (ret < 0) + return ret; + + data->resolution = ret & BIT(1) ? seconds : minutes; + data->wdd.timeout = timeout * (u8)data->resolution; + set_bit(WDOG_HW_RUNNING, &data->wdd.status); + } + + /* Register our watchdog part */ + data->wdd.info = &fts_wd_info; + data->wdd.ops = &fts_wd_ops; + data->wdd.parent = &data->client->dev; + data->wdd.min_timeout = 1; + + /* max timeout 255 minutes. */ + data->wdd.max_hw_heartbeat_ms = 0xFF * 60 * MSEC_PER_SEC; + + return watchdog_register_device(&data->wdd); +} + +/*****************************************************************************/ +/* SysFS handler functions */ +/*****************************************************************************/ +static ssize_t show_in_value(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + int err; + + err = fts_update_device(data); + if (err < 0) + return err; + + return sprintf(buf, "%u\n", data->volt[index]); +} + +static ssize_t show_temp_value(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + int err; + + err = fts_update_device(data); + if (err < 0) + return err; + + return sprintf(buf, "%u\n", data->temp_input[index]); +} + +static ssize_t show_temp_fault(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + int err; + + err = fts_update_device(data); + if (err < 0) + return err; + + /* 00h Temperature = Sensor Error */ + return sprintf(buf, "%d\n", data->temp_input[index] == 0); +} + +static ssize_t show_temp_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + int err; + + err = fts_update_device(data); + if (err < 0) + return err; + + return sprintf(buf, "%u\n", !!(data->temp_alarm & BIT(index))); +} + +static ssize_t +clear_temp_alarm(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + long ret; + + ret = fts_update_device(data); + if (ret < 0) + return ret; + + if (kstrtoul(buf, 10, &ret) || ret != 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + ret = fts_read_byte(data->client, FTS_REG_TEMP_CONTROL(index)); + if (ret < 0) + goto error; + + ret = fts_write_byte(data->client, FTS_REG_TEMP_CONTROL(index), + ret | 0x1); + if (ret < 0) + goto error; + + data->valid = false; + ret = count; +error: + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t show_fan_value(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + int err; + + err = fts_update_device(data); + if (err < 0) + return err; + + return sprintf(buf, "%u\n", data->fan_input[index]); +} + +static ssize_t show_fan_source(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + int err; + + err = fts_update_device(data); + if (err < 0) + return err; + + return sprintf(buf, "%u\n", data->fan_source[index]); +} + +static ssize_t show_fan_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + int err; + + err = fts_update_device(data); + if (err < 0) + return err; + + return sprintf(buf, "%d\n", !!(data->fan_alarm & BIT(index))); +} + +static ssize_t +clear_fan_alarm(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct fts_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + long ret; + + ret = fts_update_device(data); + if (ret < 0) + return ret; + + if (kstrtoul(buf, 10, &ret) || ret != 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + ret = fts_read_byte(data->client, FTS_REG_FAN_CONTROL(index)); + if (ret < 0) + goto error; + + ret = fts_write_byte(data->client, FTS_REG_FAN_CONTROL(index), + ret | 0x1); + if (ret < 0) + goto error; + + data->valid = false; + ret = count; +error: + mutex_unlock(&data->update_lock); + return ret; +} + +/*****************************************************************************/ +/* SysFS structs */ +/*****************************************************************************/ + +/* Temprature sensors */ +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_value, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_value, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp_value, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp_value, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp_value, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp_value, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_temp_value, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_temp_value, NULL, 7); +static SENSOR_DEVICE_ATTR(temp9_input, S_IRUGO, show_temp_value, NULL, 8); +static SENSOR_DEVICE_ATTR(temp10_input, S_IRUGO, show_temp_value, NULL, 9); +static SENSOR_DEVICE_ATTR(temp11_input, S_IRUGO, show_temp_value, NULL, 10); +static SENSOR_DEVICE_ATTR(temp12_input, S_IRUGO, show_temp_value, NULL, 11); +static SENSOR_DEVICE_ATTR(temp13_input, S_IRUGO, show_temp_value, NULL, 12); +static SENSOR_DEVICE_ATTR(temp14_input, S_IRUGO, show_temp_value, NULL, 13); +static SENSOR_DEVICE_ATTR(temp15_input, S_IRUGO, show_temp_value, NULL, 14); +static SENSOR_DEVICE_ATTR(temp16_input, S_IRUGO, show_temp_value, NULL, 15); + +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_temp_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_temp_fault, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_fault, S_IRUGO, show_temp_fault, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_fault, S_IRUGO, show_temp_fault, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_fault, S_IRUGO, show_temp_fault, NULL, 7); +static SENSOR_DEVICE_ATTR(temp9_fault, S_IRUGO, show_temp_fault, NULL, 8); +static SENSOR_DEVICE_ATTR(temp10_fault, S_IRUGO, show_temp_fault, NULL, 9); +static SENSOR_DEVICE_ATTR(temp11_fault, S_IRUGO, show_temp_fault, NULL, 10); +static SENSOR_DEVICE_ATTR(temp12_fault, S_IRUGO, show_temp_fault, NULL, 11); +static SENSOR_DEVICE_ATTR(temp13_fault, S_IRUGO, show_temp_fault, NULL, 12); +static SENSOR_DEVICE_ATTR(temp14_fault, S_IRUGO, show_temp_fault, NULL, 13); +static SENSOR_DEVICE_ATTR(temp15_fault, S_IRUGO, show_temp_fault, NULL, 14); +static SENSOR_DEVICE_ATTR(temp16_fault, S_IRUGO, show_temp_fault, NULL, 15); + +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 0); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 1); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 2); +static SENSOR_DEVICE_ATTR(temp4_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 3); +static SENSOR_DEVICE_ATTR(temp5_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 4); +static SENSOR_DEVICE_ATTR(temp6_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 5); +static SENSOR_DEVICE_ATTR(temp7_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 6); +static SENSOR_DEVICE_ATTR(temp8_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 7); +static SENSOR_DEVICE_ATTR(temp9_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 8); +static SENSOR_DEVICE_ATTR(temp10_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 9); +static SENSOR_DEVICE_ATTR(temp11_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 10); +static SENSOR_DEVICE_ATTR(temp12_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 11); +static SENSOR_DEVICE_ATTR(temp13_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 12); +static SENSOR_DEVICE_ATTR(temp14_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 13); +static SENSOR_DEVICE_ATTR(temp15_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 14); +static SENSOR_DEVICE_ATTR(temp16_alarm, S_IRUGO | S_IWUSR, show_temp_alarm, + clear_temp_alarm, 15); + +static struct attribute *fts_temp_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp6_input.dev_attr.attr, + &sensor_dev_attr_temp7_input.dev_attr.attr, + &sensor_dev_attr_temp8_input.dev_attr.attr, + &sensor_dev_attr_temp9_input.dev_attr.attr, + &sensor_dev_attr_temp10_input.dev_attr.attr, + &sensor_dev_attr_temp11_input.dev_attr.attr, + &sensor_dev_attr_temp12_input.dev_attr.attr, + &sensor_dev_attr_temp13_input.dev_attr.attr, + &sensor_dev_attr_temp14_input.dev_attr.attr, + &sensor_dev_attr_temp15_input.dev_attr.attr, + &sensor_dev_attr_temp16_input.dev_attr.attr, + + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + &sensor_dev_attr_temp5_fault.dev_attr.attr, + &sensor_dev_attr_temp6_fault.dev_attr.attr, + &sensor_dev_attr_temp7_fault.dev_attr.attr, + &sensor_dev_attr_temp8_fault.dev_attr.attr, + &sensor_dev_attr_temp9_fault.dev_attr.attr, + &sensor_dev_attr_temp10_fault.dev_attr.attr, + &sensor_dev_attr_temp11_fault.dev_attr.attr, + &sensor_dev_attr_temp12_fault.dev_attr.attr, + &sensor_dev_attr_temp13_fault.dev_attr.attr, + &sensor_dev_attr_temp14_fault.dev_attr.attr, + &sensor_dev_attr_temp15_fault.dev_attr.attr, + &sensor_dev_attr_temp16_fault.dev_attr.attr, + + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_alarm.dev_attr.attr, + &sensor_dev_attr_temp6_alarm.dev_attr.attr, + &sensor_dev_attr_temp7_alarm.dev_attr.attr, + &sensor_dev_attr_temp8_alarm.dev_attr.attr, + &sensor_dev_attr_temp9_alarm.dev_attr.attr, + &sensor_dev_attr_temp10_alarm.dev_attr.attr, + &sensor_dev_attr_temp11_alarm.dev_attr.attr, + &sensor_dev_attr_temp12_alarm.dev_attr.attr, + &sensor_dev_attr_temp13_alarm.dev_attr.attr, + &sensor_dev_attr_temp14_alarm.dev_attr.attr, + &sensor_dev_attr_temp15_alarm.dev_attr.attr, + &sensor_dev_attr_temp16_alarm.dev_attr.attr, + NULL +}; + +/* Fans */ +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_value, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_value, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan_value, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan_value, NULL, 3); +static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, show_fan_value, NULL, 4); +static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, show_fan_value, NULL, 5); +static SENSOR_DEVICE_ATTR(fan7_input, S_IRUGO, show_fan_value, NULL, 6); +static SENSOR_DEVICE_ATTR(fan8_input, S_IRUGO, show_fan_value, NULL, 7); + +static SENSOR_DEVICE_ATTR(fan1_source, S_IRUGO, show_fan_source, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_source, S_IRUGO, show_fan_source, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_source, S_IRUGO, show_fan_source, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_source, S_IRUGO, show_fan_source, NULL, 3); +static SENSOR_DEVICE_ATTR(fan5_source, S_IRUGO, show_fan_source, NULL, 4); +static SENSOR_DEVICE_ATTR(fan6_source, S_IRUGO, show_fan_source, NULL, 5); +static SENSOR_DEVICE_ATTR(fan7_source, S_IRUGO, show_fan_source, NULL, 6); +static SENSOR_DEVICE_ATTR(fan8_source, S_IRUGO, show_fan_source, NULL, 7); + +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 0); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 1); +static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 2); +static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 3); +static SENSOR_DEVICE_ATTR(fan5_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 4); +static SENSOR_DEVICE_ATTR(fan6_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 5); +static SENSOR_DEVICE_ATTR(fan7_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 6); +static SENSOR_DEVICE_ATTR(fan8_alarm, S_IRUGO | S_IWUSR, + show_fan_alarm, clear_fan_alarm, 7); + +static struct attribute *fts_fan_attrs[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan5_input.dev_attr.attr, + &sensor_dev_attr_fan6_input.dev_attr.attr, + &sensor_dev_attr_fan7_input.dev_attr.attr, + &sensor_dev_attr_fan8_input.dev_attr.attr, + + &sensor_dev_attr_fan1_source.dev_attr.attr, + &sensor_dev_attr_fan2_source.dev_attr.attr, + &sensor_dev_attr_fan3_source.dev_attr.attr, + &sensor_dev_attr_fan4_source.dev_attr.attr, + &sensor_dev_attr_fan5_source.dev_attr.attr, + &sensor_dev_attr_fan6_source.dev_attr.attr, + &sensor_dev_attr_fan7_source.dev_attr.attr, + &sensor_dev_attr_fan8_source.dev_attr.attr, + + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_fan4_alarm.dev_attr.attr, + &sensor_dev_attr_fan5_alarm.dev_attr.attr, + &sensor_dev_attr_fan6_alarm.dev_attr.attr, + &sensor_dev_attr_fan7_alarm.dev_attr.attr, + &sensor_dev_attr_fan8_alarm.dev_attr.attr, + NULL +}; + +/* Voltages */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in_value, NULL, 0); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_in_value, NULL, 1); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_in_value, NULL, 2); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_in_value, NULL, 3); +static struct attribute *fts_voltage_attrs[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + NULL +}; + +static const struct attribute_group fts_voltage_attr_group = { + .attrs = fts_voltage_attrs +}; + +static const struct attribute_group fts_temp_attr_group = { + .attrs = fts_temp_attrs +}; + +static const struct attribute_group fts_fan_attr_group = { + .attrs = fts_fan_attrs +}; + +static const struct attribute_group *fts_attr_groups[] = { + &fts_voltage_attr_group, + &fts_temp_attr_group, + &fts_fan_attr_group, + NULL +}; + +/*****************************************************************************/ +/* Module initialization / remove functions */ +/*****************************************************************************/ +static int fts_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + int val; + + /* detection works with revsion greater or equal to 0x2b */ + val = i2c_smbus_read_byte_data(client, FTS_DEVICE_REVISION_REG); + if (val < 0x2b) + return -ENODEV; + + /* Device Detect Regs must have 0x17 0x34 and 0x54 */ + val = i2c_smbus_read_byte_data(client, FTS_DEVICE_DETECT_REG_1); + if (val != 0x17) + return -ENODEV; + + val = i2c_smbus_read_byte_data(client, FTS_DEVICE_DETECT_REG_2); + if (val != 0x34) + return -ENODEV; + + val = i2c_smbus_read_byte_data(client, FTS_DEVICE_DETECT_REG_3); + if (val != 0x54) + return -ENODEV; + + /* + * 0x10 == Baseboard Management Controller, 0x01 == Teutates + * Device ID Reg needs to be 0x11 + */ + val = i2c_smbus_read_byte_data(client, FTS_DEVICE_ID_REG); + if (val != 0x11) + return -ENODEV; + + strlcpy(info->type, fts_id[0].name, I2C_NAME_SIZE); + info->flags = 0; + return 0; +} + +static int fts_remove(struct i2c_client *client) +{ + struct fts_data *data = dev_get_drvdata(&client->dev); + + watchdog_unregister_device(&data->wdd); + return 0; +} + +static int fts_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + u8 revision; + struct fts_data *data; + int err; + s8 deviceid; + struct device *hwmon_dev; + + if (client->addr != 0x73) + return -ENODEV; + + /* Baseboard Management Controller check */ + deviceid = i2c_smbus_read_byte_data(client, FTS_DEVICE_ID_REG); + if (deviceid > 0 && (deviceid & 0xF0) == 0x10) { + switch (deviceid & 0x0F) { + case 0x01: + break; + default: + dev_dbg(&client->dev, + "No Baseboard Management Controller\n"); + return -ENODEV; + } + } else { + dev_dbg(&client->dev, "No fujitsu board\n"); + return -ENODEV; + } + + data = devm_kzalloc(&client->dev, sizeof(struct fts_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->update_lock); + mutex_init(&data->access_lock); + data->client = client; + dev_set_drvdata(&client->dev, data); + + err = i2c_smbus_read_byte_data(client, FTS_DEVICE_REVISION_REG); + if (err < 0) + return err; + revision = err; + + hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, + "ftsteutates", + data, + fts_attr_groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + err = fts_watchdog_init(data); + if (err) + return err; + + dev_info(&client->dev, "Detected FTS Teutates chip, revision: %d.%d\n", + (revision & 0xF0) >> 4, revision & 0x0F); + return 0; +} + +/*****************************************************************************/ +/* Module Details */ +/*****************************************************************************/ +static struct i2c_driver fts_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "ftsteutates", + }, + .id_table = fts_id, + .probe = fts_probe, + .remove = fts_remove, + .detect = fts_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(fts_driver); + +MODULE_AUTHOR("Thilo Cestonaro "); +MODULE_DESCRIPTION("FTS Teutates driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/g760a.c b/drivers/hwmon/g760a.c new file mode 100644 index 000000000..7be1371b2 --- /dev/null +++ b/drivers/hwmon/g760a.c @@ -0,0 +1,222 @@ +/* + * g760a - Driver for the Global Mixed-mode Technology Inc. G760A + * fan speed PWM controller chip + * + * Copyright (C) 2007 Herbert Valerio Riedel + * + * Complete datasheet is available at GMT's website: + * http://www.gmt.com.tw/product/datasheet/EDS-760A.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum g760a_regs { + G760A_REG_SET_CNT = 0x00, + G760A_REG_ACT_CNT = 0x01, + G760A_REG_FAN_STA = 0x02 +}; + +#define G760A_REG_FAN_STA_RPM_OFF 0x1 /* +/-20% off */ +#define G760A_REG_FAN_STA_RPM_LOW 0x2 /* below 1920rpm */ + +/* register data is read (and cached) at most once per second */ +#define G760A_UPDATE_INTERVAL (HZ) + +struct g760a_data { + struct i2c_client *client; + struct mutex update_lock; + + /* board specific parameters */ + u32 clk; /* default 32kHz */ + u16 fan_div; /* default P=2 */ + + /* g760a register cache */ + unsigned int valid:1; + unsigned long last_updated; /* In jiffies */ + + u8 set_cnt; /* PWM (period) count number; 0xff stops fan */ + u8 act_cnt; /* formula: cnt = (CLK * 30)/(rpm * P) */ + u8 fan_sta; /* bit 0: set when actual fan speed more than 20% + * outside requested fan speed + * bit 1: set when fan speed below 1920 rpm + */ +}; + +#define G760A_DEFAULT_CLK 32768 +#define G760A_DEFAULT_FAN_DIV 2 + +#define PWM_FROM_CNT(cnt) (0xff-(cnt)) +#define PWM_TO_CNT(pwm) (0xff-(pwm)) + +static inline unsigned int rpm_from_cnt(u8 val, u32 clk, u16 div) +{ + return ((val == 0x00) ? 0 : ((clk*30)/(val*div))); +} + +/* read/write wrappers */ +static int g760a_read_value(struct i2c_client *client, enum g760a_regs reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int g760a_write_value(struct i2c_client *client, enum g760a_regs reg, + u16 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +/* + * sysfs attributes + */ + +static struct g760a_data *g760a_update_client(struct device *dev) +{ + struct g760a_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + G760A_UPDATE_INTERVAL) + || !data->valid) { + dev_dbg(&client->dev, "Starting g760a update\n"); + + data->set_cnt = g760a_read_value(client, G760A_REG_SET_CNT); + data->act_cnt = g760a_read_value(client, G760A_REG_ACT_CNT); + data->fan_sta = g760a_read_value(client, G760A_REG_FAN_STA); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static ssize_t fan1_input_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct g760a_data *data = g760a_update_client(dev); + unsigned int rpm = 0; + + mutex_lock(&data->update_lock); + if (!(data->fan_sta & G760A_REG_FAN_STA_RPM_LOW)) + rpm = rpm_from_cnt(data->act_cnt, data->clk, data->fan_div); + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", rpm); +} + +static ssize_t fan1_alarm_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct g760a_data *data = g760a_update_client(dev); + + int fan_alarm = (data->fan_sta & G760A_REG_FAN_STA_RPM_OFF) ? 1 : 0; + + return sprintf(buf, "%d\n", fan_alarm); +} + +static ssize_t pwm1_show(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct g760a_data *data = g760a_update_client(dev); + + return sprintf(buf, "%d\n", PWM_FROM_CNT(data->set_cnt)); +} + +static ssize_t pwm1_store(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct g760a_data *data = g760a_update_client(dev); + struct i2c_client *client = data->client; + unsigned long val; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->set_cnt = PWM_TO_CNT(clamp_val(val, 0, 255)); + g760a_write_value(client, G760A_REG_SET_CNT, data->set_cnt); + mutex_unlock(&data->update_lock); + + return count; +} + +static DEVICE_ATTR_RW(pwm1); +static DEVICE_ATTR_RO(fan1_input); +static DEVICE_ATTR_RO(fan1_alarm); + +static struct attribute *g760a_attrs[] = { + &dev_attr_pwm1.attr, + &dev_attr_fan1_input.attr, + &dev_attr_fan1_alarm.attr, + NULL +}; + +ATTRIBUTE_GROUPS(g760a); + +/* + * new-style driver model code + */ + +static int g760a_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct g760a_data *data; + struct device *hwmon_dev; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + data = devm_kzalloc(dev, sizeof(struct g760a_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* setup default configuration for now */ + data->fan_div = G760A_DEFAULT_FAN_DIV; + data->clk = G760A_DEFAULT_CLK; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + g760a_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id g760a_id[] = { + { "g760a", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, g760a_id); + +static struct i2c_driver g760a_driver = { + .driver = { + .name = "g760a", + }, + .probe = g760a_probe, + .id_table = g760a_id, +}; + +module_i2c_driver(g760a_driver); + +MODULE_AUTHOR("Herbert Valerio Riedel "); +MODULE_DESCRIPTION("GMT G760A driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/g762.c b/drivers/hwmon/g762.c new file mode 100644 index 000000000..6c83c385a --- /dev/null +++ b/drivers/hwmon/g762.c @@ -0,0 +1,1103 @@ +/* + * g762 - Driver for the Global Mixed-mode Technology Inc. fan speed + * PWM controller chips from G762 family, i.e. G762 and G763 + * + * Copyright (C) 2013, Arnaud EBALARD + * + * This work is based on a basic version for 2.6.31 kernel developed + * by Olivier Mouchet for LaCie. Updates and correction have been + * performed to run on recent kernels. Additional features, like the + * ability to configure various characteristics via .dts file or + * board init file have been added. Detailed datasheet on which this + * development is based is available here: + * + * http://natisbad.org/NAS/refs/GMT_EDS-762_763-080710-0.2.pdf + * + * Headers from previous developments have been kept below: + * + * Copyright (c) 2009 LaCie + * + * Author: Olivier Mouchet + * + * based on g760a code written by Herbert Valerio Riedel + * Copyright (C) 2007 Herbert Valerio Riedel + * + * g762: minimal datasheet available at: + * http://www.gmt.com.tw/product/datasheet/EDS-762_3.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "g762" + +static const struct i2c_device_id g762_id[] = { + { "g762", 0 }, + { "g763", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, g762_id); + +enum g762_regs { + G762_REG_SET_CNT = 0x00, + G762_REG_ACT_CNT = 0x01, + G762_REG_FAN_STA = 0x02, + G762_REG_SET_OUT = 0x03, + G762_REG_FAN_CMD1 = 0x04, + G762_REG_FAN_CMD2 = 0x05, +}; + +/* Config register bits */ +#define G762_REG_FAN_CMD1_DET_FAN_FAIL 0x80 /* enable fan_fail signal */ +#define G762_REG_FAN_CMD1_DET_FAN_OOC 0x40 /* enable fan_out_of_control */ +#define G762_REG_FAN_CMD1_OUT_MODE 0x20 /* out mode: PWM or DC */ +#define G762_REG_FAN_CMD1_FAN_MODE 0x10 /* fan mode: closed/open-loop */ +#define G762_REG_FAN_CMD1_CLK_DIV_ID1 0x08 /* clock divisor value */ +#define G762_REG_FAN_CMD1_CLK_DIV_ID0 0x04 +#define G762_REG_FAN_CMD1_PWM_POLARITY 0x02 /* PWM polarity */ +#define G762_REG_FAN_CMD1_PULSE_PER_REV 0x01 /* pulse per fan revolution */ + +#define G762_REG_FAN_CMD2_GEAR_MODE_1 0x08 /* fan gear mode */ +#define G762_REG_FAN_CMD2_GEAR_MODE_0 0x04 +#define G762_REG_FAN_CMD2_FAN_STARTV_1 0x02 /* fan startup voltage */ +#define G762_REG_FAN_CMD2_FAN_STARTV_0 0x01 + +#define G762_REG_FAN_STA_FAIL 0x02 /* fan fail */ +#define G762_REG_FAN_STA_OOC 0x01 /* fan out of control */ + +/* Config register values */ +#define G762_OUT_MODE_PWM 1 +#define G762_OUT_MODE_DC 0 + +#define G762_FAN_MODE_CLOSED_LOOP 2 +#define G762_FAN_MODE_OPEN_LOOP 1 + +#define G762_PWM_POLARITY_NEGATIVE 1 +#define G762_PWM_POLARITY_POSITIVE 0 + +/* Register data is read (and cached) at most once per second. */ +#define G762_UPDATE_INTERVAL HZ + +/* + * Extract pulse count per fan revolution value (2 or 4) from given + * FAN_CMD1 register value. + */ +#define G762_PULSE_FROM_REG(reg) \ + ((((reg) & G762_REG_FAN_CMD1_PULSE_PER_REV) + 1) << 1) + +/* + * Extract fan clock divisor (1, 2, 4 or 8) from given FAN_CMD1 + * register value. + */ +#define G762_CLKDIV_FROM_REG(reg) \ + (1 << (((reg) & (G762_REG_FAN_CMD1_CLK_DIV_ID0 | \ + G762_REG_FAN_CMD1_CLK_DIV_ID1)) >> 2)) + +/* + * Extract fan gear mode multiplier value (0, 2 or 4) from given + * FAN_CMD2 register value. + */ +#define G762_GEARMULT_FROM_REG(reg) \ + (1 << (((reg) & (G762_REG_FAN_CMD2_GEAR_MODE_0 | \ + G762_REG_FAN_CMD2_GEAR_MODE_1)) >> 2)) + +struct g762_data { + struct i2c_client *client; + struct clk *clk; + + /* update mutex */ + struct mutex update_lock; + + /* board specific parameters. */ + u32 clk_freq; + + /* g762 register cache */ + bool valid; + unsigned long last_updated; /* in jiffies */ + + u8 set_cnt; /* controls fan rotation speed in closed-loop mode */ + u8 act_cnt; /* provides access to current fan RPM value */ + u8 fan_sta; /* bit 0: set when actual fan speed is more than + * 25% outside requested fan speed + * bit 1: set when no transition occurs on fan + * pin for 0.7s + */ + u8 set_out; /* controls fan rotation speed in open-loop mode */ + u8 fan_cmd1; /* 0: FG_PLS_ID0 FG pulses count per revolution + * 0: 2 counts per revolution + * 1: 4 counts per revolution + * 1: PWM_POLARITY 1: negative_duty + * 0: positive_duty + * 2,3: [FG_CLOCK_ID0, FG_CLK_ID1] + * 00: Divide fan clock by 1 + * 01: Divide fan clock by 2 + * 10: Divide fan clock by 4 + * 11: Divide fan clock by 8 + * 4: FAN_MODE 1:closed-loop, 0:open-loop + * 5: OUT_MODE 1:PWM, 0:DC + * 6: DET_FAN_OOC enable "fan ooc" status + * 7: DET_FAN_FAIL enable "fan fail" status + */ + u8 fan_cmd2; /* 0,1: FAN_STARTV 0,1,2,3 -> 0,32,64,96 dac_code + * 2,3: FG_GEAR_MODE + * 00: multiplier = 1 + * 01: multiplier = 2 + * 10: multiplier = 4 + * 4: Mask ALERT# (g763 only) + */ +}; + +/* + * Convert count value from fan controller register (FAN_SET_CNT) into fan + * speed RPM value. Note that the datasheet documents a basic formula; + * influence of additional parameters (fan clock divisor, fan gear mode) + * have been infered from examples in the datasheet and tests. + */ +static inline unsigned int rpm_from_cnt(u8 cnt, u32 clk_freq, u16 p, + u8 clk_div, u8 gear_mult) +{ + if (cnt == 0xff) /* setting cnt to 255 stops the fan */ + return 0; + + return (clk_freq * 30 * gear_mult) / ((cnt ? cnt : 1) * p * clk_div); +} + +/* + * Convert fan RPM value from sysfs into count value for fan controller + * register (FAN_SET_CNT). + */ +static inline unsigned char cnt_from_rpm(unsigned long rpm, u32 clk_freq, u16 p, + u8 clk_div, u8 gear_mult) +{ + unsigned long f1 = clk_freq * 30 * gear_mult; + unsigned long f2 = p * clk_div; + + if (!rpm) /* to stop the fan, set cnt to 255 */ + return 0xff; + + rpm = clamp_val(rpm, f1 / (255 * f2), ULONG_MAX / f2); + return DIV_ROUND_CLOSEST(f1, rpm * f2); +} + +/* helper to grab and cache data, at most one time per second */ +static struct g762_data *g762_update_client(struct device *dev) +{ + struct g762_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret = 0; + + mutex_lock(&data->update_lock); + if (time_before(jiffies, data->last_updated + G762_UPDATE_INTERVAL) && + likely(data->valid)) + goto out; + + ret = i2c_smbus_read_byte_data(client, G762_REG_SET_CNT); + if (ret < 0) + goto out; + data->set_cnt = ret; + + ret = i2c_smbus_read_byte_data(client, G762_REG_ACT_CNT); + if (ret < 0) + goto out; + data->act_cnt = ret; + + ret = i2c_smbus_read_byte_data(client, G762_REG_FAN_STA); + if (ret < 0) + goto out; + data->fan_sta = ret; + + ret = i2c_smbus_read_byte_data(client, G762_REG_SET_OUT); + if (ret < 0) + goto out; + data->set_out = ret; + + ret = i2c_smbus_read_byte_data(client, G762_REG_FAN_CMD1); + if (ret < 0) + goto out; + data->fan_cmd1 = ret; + + ret = i2c_smbus_read_byte_data(client, G762_REG_FAN_CMD2); + if (ret < 0) + goto out; + data->fan_cmd2 = ret; + + data->last_updated = jiffies; + data->valid = true; + out: + mutex_unlock(&data->update_lock); + + if (ret < 0) /* upon error, encode it in return value */ + data = ERR_PTR(ret); + + return data; +} + +/* helpers for writing hardware parameters */ + +/* + * Set input clock frequency received on CLK pin of the chip. Accepted values + * are between 0 and 0xffffff. If zero is given, then default frequency + * (32,768Hz) is used. Note that clock frequency is a characteristic of the + * system but an internal parameter, i.e. value is not passed to the device. + */ +static int do_set_clk_freq(struct device *dev, unsigned long val) +{ + struct g762_data *data = dev_get_drvdata(dev); + + if (val > 0xffffff) + return -EINVAL; + if (!val) + val = 32768; + + data->clk_freq = val; + + return 0; +} + +/* Set pwm mode. Accepts either 0 (PWM mode) or 1 (DC mode) */ +static int do_set_pwm_mode(struct device *dev, unsigned long val) +{ + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + switch (val) { + case G762_OUT_MODE_PWM: + data->fan_cmd1 |= G762_REG_FAN_CMD1_OUT_MODE; + break; + case G762_OUT_MODE_DC: + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_OUT_MODE; + break; + default: + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_byte_data(data->client, G762_REG_FAN_CMD1, + data->fan_cmd1); + data->valid = false; + out: + mutex_unlock(&data->update_lock); + + return ret; +} + +/* Set fan clock divisor. Accepts either 1, 2, 4 or 8. */ +static int do_set_fan_div(struct device *dev, unsigned long val) +{ + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + switch (val) { + case 1: + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_CLK_DIV_ID0; + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_CLK_DIV_ID1; + break; + case 2: + data->fan_cmd1 |= G762_REG_FAN_CMD1_CLK_DIV_ID0; + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_CLK_DIV_ID1; + break; + case 4: + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_CLK_DIV_ID0; + data->fan_cmd1 |= G762_REG_FAN_CMD1_CLK_DIV_ID1; + break; + case 8: + data->fan_cmd1 |= G762_REG_FAN_CMD1_CLK_DIV_ID0; + data->fan_cmd1 |= G762_REG_FAN_CMD1_CLK_DIV_ID1; + break; + default: + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_byte_data(data->client, G762_REG_FAN_CMD1, + data->fan_cmd1); + data->valid = false; + out: + mutex_unlock(&data->update_lock); + + return ret; +} + +/* Set fan gear mode. Accepts either 0, 1 or 2. */ +static int do_set_fan_gear_mode(struct device *dev, unsigned long val) +{ + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + switch (val) { + case 0: + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_GEAR_MODE_0; + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_GEAR_MODE_1; + break; + case 1: + data->fan_cmd2 |= G762_REG_FAN_CMD2_GEAR_MODE_0; + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_GEAR_MODE_1; + break; + case 2: + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_GEAR_MODE_0; + data->fan_cmd2 |= G762_REG_FAN_CMD2_GEAR_MODE_1; + break; + default: + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_byte_data(data->client, G762_REG_FAN_CMD2, + data->fan_cmd2); + data->valid = false; + out: + mutex_unlock(&data->update_lock); + + return ret; +} + +/* Set number of fan pulses per revolution. Accepts either 2 or 4. */ +static int do_set_fan_pulses(struct device *dev, unsigned long val) +{ + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + switch (val) { + case 2: + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_PULSE_PER_REV; + break; + case 4: + data->fan_cmd1 |= G762_REG_FAN_CMD1_PULSE_PER_REV; + break; + default: + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_byte_data(data->client, G762_REG_FAN_CMD1, + data->fan_cmd1); + data->valid = false; + out: + mutex_unlock(&data->update_lock); + + return ret; +} + +/* Set fan mode. Accepts either 1 (open-loop) or 2 (closed-loop). */ +static int do_set_pwm_enable(struct device *dev, unsigned long val) +{ + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + switch (val) { + case G762_FAN_MODE_CLOSED_LOOP: + data->fan_cmd1 |= G762_REG_FAN_CMD1_FAN_MODE; + break; + case G762_FAN_MODE_OPEN_LOOP: + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_FAN_MODE; + /* + * BUG FIX: if SET_CNT register value is 255 then, for some + * unknown reason, fan will not rotate as expected, no matter + * the value of SET_OUT (to be specific, this seems to happen + * only in PWM mode). To workaround this bug, we give SET_CNT + * value of 254 if it is 255 when switching to open-loop. + */ + if (data->set_cnt == 0xff) + i2c_smbus_write_byte_data(data->client, + G762_REG_SET_CNT, 254); + break; + default: + ret = -EINVAL; + goto out; + } + + ret = i2c_smbus_write_byte_data(data->client, G762_REG_FAN_CMD1, + data->fan_cmd1); + data->valid = false; + out: + mutex_unlock(&data->update_lock); + + return ret; +} + +/* Set PWM polarity. Accepts either 0 (positive duty) or 1 (negative duty) */ +static int do_set_pwm_polarity(struct device *dev, unsigned long val) +{ + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + switch (val) { + case G762_PWM_POLARITY_POSITIVE: + data->fan_cmd1 &= ~G762_REG_FAN_CMD1_PWM_POLARITY; + break; + case G762_PWM_POLARITY_NEGATIVE: + data->fan_cmd1 |= G762_REG_FAN_CMD1_PWM_POLARITY; + break; + default: + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_byte_data(data->client, G762_REG_FAN_CMD1, + data->fan_cmd1); + data->valid = false; + out: + mutex_unlock(&data->update_lock); + + return ret; +} + +/* + * Set pwm value. Accepts values between 0 (stops the fan) and + * 255 (full speed). This only makes sense in open-loop mode. + */ +static int do_set_pwm(struct device *dev, unsigned long val) +{ + struct g762_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret; + + if (val > 255) + return -EINVAL; + + mutex_lock(&data->update_lock); + ret = i2c_smbus_write_byte_data(client, G762_REG_SET_OUT, val); + data->valid = false; + mutex_unlock(&data->update_lock); + + return ret; +} + +/* + * Set fan RPM value. Can be called both in closed and open-loop mode + * but effect will only be seen after closed-loop mode is configured. + */ +static int do_set_fan_target(struct device *dev, unsigned long val) +{ + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + data->set_cnt = cnt_from_rpm(val, data->clk_freq, + G762_PULSE_FROM_REG(data->fan_cmd1), + G762_CLKDIV_FROM_REG(data->fan_cmd1), + G762_GEARMULT_FROM_REG(data->fan_cmd2)); + ret = i2c_smbus_write_byte_data(data->client, G762_REG_SET_CNT, + data->set_cnt); + data->valid = false; + mutex_unlock(&data->update_lock); + + return ret; +} + +/* Set fan startup voltage. Accepted values are either 0, 1, 2 or 3. */ +static int do_set_fan_startv(struct device *dev, unsigned long val) +{ + struct g762_data *data = g762_update_client(dev); + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + switch (val) { + case 0: + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_FAN_STARTV_0; + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_FAN_STARTV_1; + break; + case 1: + data->fan_cmd2 |= G762_REG_FAN_CMD2_FAN_STARTV_0; + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_FAN_STARTV_1; + break; + case 2: + data->fan_cmd2 &= ~G762_REG_FAN_CMD2_FAN_STARTV_0; + data->fan_cmd2 |= G762_REG_FAN_CMD2_FAN_STARTV_1; + break; + case 3: + data->fan_cmd2 |= G762_REG_FAN_CMD2_FAN_STARTV_0; + data->fan_cmd2 |= G762_REG_FAN_CMD2_FAN_STARTV_1; + break; + default: + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_byte_data(data->client, G762_REG_FAN_CMD2, + data->fan_cmd2); + data->valid = false; + out: + mutex_unlock(&data->update_lock); + + return ret; +} + +/* + * Helper to import hardware characteristics from .dts file and push + * those to the chip. + */ + +#ifdef CONFIG_OF +static const struct of_device_id g762_dt_match[] = { + { .compatible = "gmt,g762" }, + { .compatible = "gmt,g763" }, + { }, +}; +MODULE_DEVICE_TABLE(of, g762_dt_match); + +/* + * Grab clock (a required property), enable it, get (fixed) clock frequency + * and store it. Note: upon success, clock has been prepared and enabled; it + * must later be unprepared and disabled (e.g. during module unloading) by a + * call to g762_of_clock_disable(). Note that a reference to clock is kept + * in our private data structure to be used in this function. + */ +static void g762_of_clock_disable(void *data) +{ + struct g762_data *g762 = data; + + clk_disable_unprepare(g762->clk); + clk_put(g762->clk); +} + +static int g762_of_clock_enable(struct i2c_client *client) +{ + struct g762_data *data; + unsigned long clk_freq; + struct clk *clk; + int ret; + + if (!client->dev.of_node) + return 0; + + clk = of_clk_get(client->dev.of_node, 0); + if (IS_ERR(clk)) { + dev_err(&client->dev, "failed to get clock\n"); + return PTR_ERR(clk); + } + + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(&client->dev, "failed to enable clock\n"); + goto clk_put; + } + + clk_freq = clk_get_rate(clk); + ret = do_set_clk_freq(&client->dev, clk_freq); + if (ret) { + dev_err(&client->dev, "invalid clock freq %lu\n", clk_freq); + goto clk_unprep; + } + + data = i2c_get_clientdata(client); + data->clk = clk; + + devm_add_action(&client->dev, g762_of_clock_disable, data); + return 0; + + clk_unprep: + clk_disable_unprepare(clk); + + clk_put: + clk_put(clk); + + return ret; +} + +static int g762_of_prop_import_one(struct i2c_client *client, + const char *pname, + int (*psetter)(struct device *dev, + unsigned long val)) +{ + int ret; + u32 pval; + + if (of_property_read_u32(client->dev.of_node, pname, &pval)) + return 0; + + dev_dbg(&client->dev, "found %s (%d)\n", pname, pval); + ret = (*psetter)(&client->dev, pval); + if (ret) + dev_err(&client->dev, "unable to set %s (%d)\n", pname, pval); + + return ret; +} + +static int g762_of_prop_import(struct i2c_client *client) +{ + int ret; + + if (!client->dev.of_node) + return 0; + + ret = g762_of_prop_import_one(client, "fan_gear_mode", + do_set_fan_gear_mode); + if (ret) + return ret; + + ret = g762_of_prop_import_one(client, "pwm_polarity", + do_set_pwm_polarity); + if (ret) + return ret; + + return g762_of_prop_import_one(client, "fan_startv", + do_set_fan_startv); +} + +#else +static int g762_of_prop_import(struct i2c_client *client) +{ + return 0; +} + +static int g762_of_clock_enable(struct i2c_client *client) +{ + return 0; +} +#endif + +/* + * Helper to import hardware characteristics from .dts file and push + * those to the chip. + */ + +static int g762_pdata_prop_import(struct i2c_client *client) +{ + struct g762_platform_data *pdata = dev_get_platdata(&client->dev); + int ret; + + if (!pdata) + return 0; + + ret = do_set_fan_gear_mode(&client->dev, pdata->fan_gear_mode); + if (ret) + return ret; + + ret = do_set_pwm_polarity(&client->dev, pdata->pwm_polarity); + if (ret) + return ret; + + ret = do_set_fan_startv(&client->dev, pdata->fan_startv); + if (ret) + return ret; + + return do_set_clk_freq(&client->dev, pdata->clk_freq); +} + +/* + * sysfs attributes + */ + +/* + * Read function for fan1_input sysfs file. Return current fan RPM value, or + * 0 if fan is out of control. + */ +static ssize_t fan1_input_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct g762_data *data = g762_update_client(dev); + unsigned int rpm = 0; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + /* reverse logic: fan out of control reporting is enabled low */ + if (data->fan_sta & G762_REG_FAN_STA_OOC) { + rpm = rpm_from_cnt(data->act_cnt, data->clk_freq, + G762_PULSE_FROM_REG(data->fan_cmd1), + G762_CLKDIV_FROM_REG(data->fan_cmd1), + G762_GEARMULT_FROM_REG(data->fan_cmd2)); + } + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", rpm); +} + +/* + * Read and write functions for pwm1_mode sysfs file. Get and set fan speed + * control mode i.e. PWM (1) or DC (0). + */ +static ssize_t pwm1_mode_show(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", + !!(data->fan_cmd1 & G762_REG_FAN_CMD1_OUT_MODE)); +} + +static ssize_t pwm1_mode_store(struct device *dev, + struct device_attribute *da, const char *buf, + size_t count) +{ + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + ret = do_set_pwm_mode(dev, val); + if (ret < 0) + return ret; + + return count; +} + +/* + * Read and write functions for fan1_div sysfs file. Get and set fan + * controller prescaler value + */ +static ssize_t fan1_div_show(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", G762_CLKDIV_FROM_REG(data->fan_cmd1)); +} + +static ssize_t fan1_div_store(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + ret = do_set_fan_div(dev, val); + if (ret < 0) + return ret; + + return count; +} + +/* + * Read and write functions for fan1_pulses sysfs file. Get and set number + * of tachometer pulses per fan revolution. + */ +static ssize_t fan1_pulses_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", G762_PULSE_FROM_REG(data->fan_cmd1)); +} + +static ssize_t fan1_pulses_store(struct device *dev, + struct device_attribute *da, const char *buf, + size_t count) +{ + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + ret = do_set_fan_pulses(dev, val); + if (ret < 0) + return ret; + + return count; +} + +/* + * Read and write functions for pwm1_enable. Get and set fan speed control mode + * (i.e. closed or open-loop). + * + * Following documentation about hwmon's sysfs interface, a pwm1_enable node + * should accept the following: + * + * 0 : no fan speed control (i.e. fan at full speed) + * 1 : manual fan speed control enabled (use pwm[1-*]) (open-loop) + * 2+: automatic fan speed control enabled (use fan[1-*]_target) (closed-loop) + * + * but we do not accept 0 as this mode is not natively supported by the chip + * and it is not emulated by g762 driver. -EINVAL is returned in this case. + */ +static ssize_t pwm1_enable_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", + (!!(data->fan_cmd1 & G762_REG_FAN_CMD1_FAN_MODE)) + 1); +} + +static ssize_t pwm1_enable_store(struct device *dev, + struct device_attribute *da, const char *buf, + size_t count) +{ + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + ret = do_set_pwm_enable(dev, val); + if (ret < 0) + return ret; + + return count; +} + +/* + * Read and write functions for pwm1 sysfs file. Get and set pwm value + * (which affects fan speed) in open-loop mode. 0 stops the fan and 255 + * makes it run at full speed. + */ +static ssize_t pwm1_show(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->set_out); +} + +static ssize_t pwm1_store(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + ret = do_set_pwm(dev, val); + if (ret < 0) + return ret; + + return count; +} + +/* + * Read and write function for fan1_target sysfs file. Get/set the fan speed in + * closed-loop mode. Speed is given as a RPM value; then the chip will regulate + * the fan speed using pulses from fan tachometer. + * + * Refer to rpm_from_cnt() implementation above to get info about count number + * calculation. + * + * Also note that due to rounding errors it is possible that you don't read + * back exactly the value you have set. + */ +static ssize_t fan1_target_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct g762_data *data = g762_update_client(dev); + unsigned int rpm; + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + rpm = rpm_from_cnt(data->set_cnt, data->clk_freq, + G762_PULSE_FROM_REG(data->fan_cmd1), + G762_CLKDIV_FROM_REG(data->fan_cmd1), + G762_GEARMULT_FROM_REG(data->fan_cmd2)); + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", rpm); +} + +static ssize_t fan1_target_store(struct device *dev, + struct device_attribute *da, const char *buf, + size_t count) +{ + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + ret = do_set_fan_target(dev, val); + if (ret < 0) + return ret; + + return count; +} + +/* read function for fan1_fault sysfs file. */ +static ssize_t fan1_fault_show(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%u\n", !!(data->fan_sta & G762_REG_FAN_STA_FAIL)); +} + +/* + * read function for fan1_alarm sysfs file. Note that OOC condition is + * enabled low + */ +static ssize_t fan1_alarm_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%u\n", !(data->fan_sta & G762_REG_FAN_STA_OOC)); +} + +static DEVICE_ATTR_RW(pwm1); +static DEVICE_ATTR_RW(pwm1_mode); +static DEVICE_ATTR_RW(pwm1_enable); +static DEVICE_ATTR_RO(fan1_input); +static DEVICE_ATTR_RO(fan1_alarm); +static DEVICE_ATTR_RO(fan1_fault); +static DEVICE_ATTR_RW(fan1_target); +static DEVICE_ATTR_RW(fan1_div); +static DEVICE_ATTR_RW(fan1_pulses); + +/* Driver data */ +static struct attribute *g762_attrs[] = { + &dev_attr_fan1_input.attr, + &dev_attr_fan1_alarm.attr, + &dev_attr_fan1_fault.attr, + &dev_attr_fan1_target.attr, + &dev_attr_fan1_div.attr, + &dev_attr_fan1_pulses.attr, + &dev_attr_pwm1.attr, + &dev_attr_pwm1_mode.attr, + &dev_attr_pwm1_enable.attr, + NULL +}; + +ATTRIBUTE_GROUPS(g762); + +/* + * Enable both fan failure detection and fan out of control protection. The + * function does not protect change/access to data structure; it must thus + * only be called during initialization. + */ +static inline int g762_fan_init(struct device *dev) +{ + struct g762_data *data = g762_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + data->fan_cmd1 |= G762_REG_FAN_CMD1_DET_FAN_FAIL; + data->fan_cmd1 |= G762_REG_FAN_CMD1_DET_FAN_OOC; + data->valid = false; + + return i2c_smbus_write_byte_data(data->client, G762_REG_FAN_CMD1, + data->fan_cmd1); +} + +static int g762_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct g762_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(struct g762_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->client = client; + mutex_init(&data->update_lock); + + /* Enable fan failure detection and fan out of control protection */ + ret = g762_fan_init(dev); + if (ret) + return ret; + + /* Get configuration via DT ... */ + ret = g762_of_clock_enable(client); + if (ret) + return ret; + ret = g762_of_prop_import(client); + if (ret) + return ret; + /* ... or platform_data */ + ret = g762_pdata_prop_import(client); + if (ret) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, g762_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct i2c_driver g762_driver = { + .driver = { + .name = DRVNAME, + .of_match_table = of_match_ptr(g762_dt_match), + }, + .probe = g762_probe, + .id_table = g762_id, +}; + +module_i2c_driver(g762_driver); + +MODULE_AUTHOR("Arnaud EBALARD "); +MODULE_DESCRIPTION("GMT G762/G763 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/gl518sm.c b/drivers/hwmon/gl518sm.c new file mode 100644 index 000000000..b267510da --- /dev/null +++ b/drivers/hwmon/gl518sm.c @@ -0,0 +1,683 @@ +/* + * gl518sm.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 1998, 1999 Frodo Looijaard and + * Kyosti Malkki + * Copyright (C) 2004 Hong-Gunn Chew and + * Jean Delvare + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Ported to Linux 2.6 by Hong-Gunn Chew with the help of Jean Delvare + * and advice of Greg Kroah-Hartman. + * + * Notes about the port: + * Release 0x00 of the GL518SM chipset doesn't support reading of in0, + * in1 nor in2. The original driver had an ugly workaround to get them + * anyway (changing limits and watching alarms trigger and wear off). + * We did not keep that part of the original driver in the Linux 2.6 + * version, since it was making the driver significantly more complex + * with no real benefit. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END }; + +enum chips { gl518sm_r00, gl518sm_r80 }; + +/* Many GL518 constants specified below */ + +/* The GL518 registers */ +#define GL518_REG_CHIP_ID 0x00 +#define GL518_REG_REVISION 0x01 +#define GL518_REG_VENDOR_ID 0x02 +#define GL518_REG_CONF 0x03 +#define GL518_REG_TEMP_IN 0x04 +#define GL518_REG_TEMP_MAX 0x05 +#define GL518_REG_TEMP_HYST 0x06 +#define GL518_REG_FAN_COUNT 0x07 +#define GL518_REG_FAN_LIMIT 0x08 +#define GL518_REG_VIN1_LIMIT 0x09 +#define GL518_REG_VIN2_LIMIT 0x0a +#define GL518_REG_VIN3_LIMIT 0x0b +#define GL518_REG_VDD_LIMIT 0x0c +#define GL518_REG_VIN3 0x0d +#define GL518_REG_MISC 0x0f +#define GL518_REG_ALARM 0x10 +#define GL518_REG_MASK 0x11 +#define GL518_REG_INT 0x12 +#define GL518_REG_VIN2 0x13 +#define GL518_REG_VIN1 0x14 +#define GL518_REG_VDD 0x15 + + +/* + * Conversions. Rounding and limit checking is only done on the TO_REG + * variants. Note that you should be a bit careful with which arguments + * these macros are called: arguments may be evaluated more than once. + * Fixing this is just not worth it. + */ + +#define RAW_FROM_REG(val) val + +#define BOOL_FROM_REG(val) ((val) ? 0 : 1) +#define BOOL_TO_REG(val) ((val) ? 0 : 1) + +#define TEMP_CLAMP(val) clamp_val(val, -119000, 136000) +#define TEMP_TO_REG(val) (DIV_ROUND_CLOSEST(TEMP_CLAMP(val), 1000) + 119) +#define TEMP_FROM_REG(val) (((val) - 119) * 1000) + +static inline u8 FAN_TO_REG(long rpm, int div) +{ + long rpmdiv; + if (rpm == 0) + return 0; + rpmdiv = clamp_val(rpm, 1, 960000) * div; + return clamp_val((480000 + rpmdiv / 2) / rpmdiv, 1, 255); +} +#define FAN_FROM_REG(val, div) ((val) == 0 ? 0 : (480000 / ((val) * (div)))) + +#define IN_CLAMP(val) clamp_val(val, 0, 255 * 19) +#define IN_TO_REG(val) DIV_ROUND_CLOSEST(IN_CLAMP(val), 19) +#define IN_FROM_REG(val) ((val) * 19) + +#define VDD_CLAMP(val) clamp_val(val, 0, 255 * 95 / 4) +#define VDD_TO_REG(val) DIV_ROUND_CLOSEST(VDD_CLAMP(val) * 4, 95) +#define VDD_FROM_REG(val) DIV_ROUND_CLOSEST((val) * 95, 4) + +#define DIV_FROM_REG(val) (1 << (val)) + +#define BEEP_MASK_TO_REG(val) ((val) & 0x7f & data->alarm_mask) +#define BEEP_MASK_FROM_REG(val) ((val) & 0x7f) + +/* Each client has this additional data */ +struct gl518_data { + struct i2c_client *client; + const struct attribute_group *groups[3]; + enum chips type; + + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 voltage_in[4]; /* Register values; [0] = VDD */ + u8 voltage_min[4]; /* Register values; [0] = VDD */ + u8 voltage_max[4]; /* Register values; [0] = VDD */ + u8 fan_in[2]; + u8 fan_min[2]; + u8 fan_div[2]; /* Register encoding, shifted right */ + u8 fan_auto1; /* Boolean */ + u8 temp_in; /* Register values */ + u8 temp_max; /* Register values */ + u8 temp_hyst; /* Register values */ + u8 alarms; /* Register value */ + u8 alarm_mask; + u8 beep_mask; /* Register value */ + u8 beep_enable; /* Boolean */ +}; + +/* + * Registers 0x07 to 0x0c are word-sized, others are byte-sized + * GL518 uses a high-byte first convention, which is exactly opposite to + * the SMBus standard. + */ +static int gl518_read_value(struct i2c_client *client, u8 reg) +{ + if ((reg >= 0x07) && (reg <= 0x0c)) + return i2c_smbus_read_word_swapped(client, reg); + else + return i2c_smbus_read_byte_data(client, reg); +} + +static int gl518_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + if ((reg >= 0x07) && (reg <= 0x0c)) + return i2c_smbus_write_word_swapped(client, reg, value); + else + return i2c_smbus_write_byte_data(client, reg, value); +} + +static struct gl518_data *gl518_update_device(struct device *dev) +{ + struct gl518_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int val; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + dev_dbg(&client->dev, "Starting gl518 update\n"); + + data->alarms = gl518_read_value(client, GL518_REG_INT); + data->beep_mask = gl518_read_value(client, GL518_REG_ALARM); + + val = gl518_read_value(client, GL518_REG_VDD_LIMIT); + data->voltage_min[0] = val & 0xff; + data->voltage_max[0] = (val >> 8) & 0xff; + val = gl518_read_value(client, GL518_REG_VIN1_LIMIT); + data->voltage_min[1] = val & 0xff; + data->voltage_max[1] = (val >> 8) & 0xff; + val = gl518_read_value(client, GL518_REG_VIN2_LIMIT); + data->voltage_min[2] = val & 0xff; + data->voltage_max[2] = (val >> 8) & 0xff; + val = gl518_read_value(client, GL518_REG_VIN3_LIMIT); + data->voltage_min[3] = val & 0xff; + data->voltage_max[3] = (val >> 8) & 0xff; + + val = gl518_read_value(client, GL518_REG_FAN_COUNT); + data->fan_in[0] = (val >> 8) & 0xff; + data->fan_in[1] = val & 0xff; + + val = gl518_read_value(client, GL518_REG_FAN_LIMIT); + data->fan_min[0] = (val >> 8) & 0xff; + data->fan_min[1] = val & 0xff; + + data->temp_in = gl518_read_value(client, GL518_REG_TEMP_IN); + data->temp_max = + gl518_read_value(client, GL518_REG_TEMP_MAX); + data->temp_hyst = + gl518_read_value(client, GL518_REG_TEMP_HYST); + + val = gl518_read_value(client, GL518_REG_MISC); + data->fan_div[0] = (val >> 6) & 0x03; + data->fan_div[1] = (val >> 4) & 0x03; + data->fan_auto1 = (val >> 3) & 0x01; + + data->alarms &= data->alarm_mask; + + val = gl518_read_value(client, GL518_REG_CONF); + data->beep_enable = (val >> 2) & 1; + + if (data->type != gl518sm_r00) { + data->voltage_in[0] = + gl518_read_value(client, GL518_REG_VDD); + data->voltage_in[1] = + gl518_read_value(client, GL518_REG_VIN1); + data->voltage_in[2] = + gl518_read_value(client, GL518_REG_VIN2); + } + data->voltage_in[3] = + gl518_read_value(client, GL518_REG_VIN3); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Sysfs stuff + */ + +#define show(type, suffix, value) \ +static ssize_t show_##suffix(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct gl518_data *data = gl518_update_device(dev); \ + return sprintf(buf, "%d\n", type##_FROM_REG(data->value)); \ +} + +show(TEMP, temp_input1, temp_in); +show(TEMP, temp_max1, temp_max); +show(TEMP, temp_hyst1, temp_hyst); +show(BOOL, fan_auto1, fan_auto1); +show(VDD, in_input0, voltage_in[0]); +show(IN, in_input1, voltage_in[1]); +show(IN, in_input2, voltage_in[2]); +show(IN, in_input3, voltage_in[3]); +show(VDD, in_min0, voltage_min[0]); +show(IN, in_min1, voltage_min[1]); +show(IN, in_min2, voltage_min[2]); +show(IN, in_min3, voltage_min[3]); +show(VDD, in_max0, voltage_max[0]); +show(IN, in_max1, voltage_max[1]); +show(IN, in_max2, voltage_max[2]); +show(IN, in_max3, voltage_max[3]); +show(RAW, alarms, alarms); +show(BOOL, beep_enable, beep_enable); +show(BEEP_MASK, beep_mask, beep_mask); + +static ssize_t show_fan_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct gl518_data *data = gl518_update_device(dev); + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_in[nr], + DIV_FROM_REG(data->fan_div[nr]))); +} + +static ssize_t show_fan_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct gl518_data *data = gl518_update_device(dev); + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr]))); +} + +static ssize_t show_fan_div(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct gl518_data *data = gl518_update_device(dev); + return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[nr])); +} + +#define set(type, suffix, value, reg) \ +static ssize_t set_##suffix(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct gl518_data *data = dev_get_drvdata(dev); \ + struct i2c_client *client = data->client; \ + long val; \ + int err = kstrtol(buf, 10, &val); \ + if (err) \ + return err; \ + \ + mutex_lock(&data->update_lock); \ + data->value = type##_TO_REG(val); \ + gl518_write_value(client, reg, data->value); \ + mutex_unlock(&data->update_lock); \ + return count; \ +} + +#define set_bits(type, suffix, value, reg, mask, shift) \ +static ssize_t set_##suffix(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct gl518_data *data = dev_get_drvdata(dev); \ + struct i2c_client *client = data->client; \ + int regvalue; \ + unsigned long val; \ + int err = kstrtoul(buf, 10, &val); \ + if (err) \ + return err; \ + \ + mutex_lock(&data->update_lock); \ + regvalue = gl518_read_value(client, reg); \ + data->value = type##_TO_REG(val); \ + regvalue = (regvalue & ~mask) | (data->value << shift); \ + gl518_write_value(client, reg, regvalue); \ + mutex_unlock(&data->update_lock); \ + return count; \ +} + +#define set_low(type, suffix, value, reg) \ + set_bits(type, suffix, value, reg, 0x00ff, 0) +#define set_high(type, suffix, value, reg) \ + set_bits(type, suffix, value, reg, 0xff00, 8) + +set(TEMP, temp_max1, temp_max, GL518_REG_TEMP_MAX); +set(TEMP, temp_hyst1, temp_hyst, GL518_REG_TEMP_HYST); +set_bits(BOOL, fan_auto1, fan_auto1, GL518_REG_MISC, 0x08, 3); +set_low(VDD, in_min0, voltage_min[0], GL518_REG_VDD_LIMIT); +set_low(IN, in_min1, voltage_min[1], GL518_REG_VIN1_LIMIT); +set_low(IN, in_min2, voltage_min[2], GL518_REG_VIN2_LIMIT); +set_low(IN, in_min3, voltage_min[3], GL518_REG_VIN3_LIMIT); +set_high(VDD, in_max0, voltage_max[0], GL518_REG_VDD_LIMIT); +set_high(IN, in_max1, voltage_max[1], GL518_REG_VIN1_LIMIT); +set_high(IN, in_max2, voltage_max[2], GL518_REG_VIN2_LIMIT); +set_high(IN, in_max3, voltage_max[3], GL518_REG_VIN3_LIMIT); +set_bits(BOOL, beep_enable, beep_enable, GL518_REG_CONF, 0x04, 2); +set(BEEP_MASK, beep_mask, beep_mask, GL518_REG_ALARM); + +static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gl518_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = to_sensor_dev_attr(attr)->index; + int regvalue; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + regvalue = gl518_read_value(client, GL518_REG_FAN_LIMIT); + data->fan_min[nr] = FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); + regvalue = (regvalue & (0xff << (8 * nr))) + | (data->fan_min[nr] << (8 * (1 - nr))); + gl518_write_value(client, GL518_REG_FAN_LIMIT, regvalue); + + data->beep_mask = gl518_read_value(client, GL518_REG_ALARM); + if (data->fan_min[nr] == 0) + data->alarm_mask &= ~(0x20 << nr); + else + data->alarm_mask |= (0x20 << nr); + data->beep_mask &= data->alarm_mask; + gl518_write_value(client, GL518_REG_ALARM, data->beep_mask); + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gl518_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = to_sensor_dev_attr(attr)->index; + int regvalue; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + switch (val) { + case 1: + val = 0; + break; + case 2: + val = 1; + break; + case 4: + val = 2; + break; + case 8: + val = 3; + break; + default: + dev_err(dev, + "Invalid fan clock divider %lu, choose one of 1, 2, 4 or 8\n", + val); + return -EINVAL; + } + + mutex_lock(&data->update_lock); + regvalue = gl518_read_value(client, GL518_REG_MISC); + data->fan_div[nr] = val; + regvalue = (regvalue & ~(0xc0 >> (2 * nr))) + | (data->fan_div[nr] << (6 - 2 * nr)); + gl518_write_value(client, GL518_REG_MISC, regvalue); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input1, NULL); +static DEVICE_ATTR(temp1_max, S_IWUSR|S_IRUGO, show_temp_max1, set_temp_max1); +static DEVICE_ATTR(temp1_max_hyst, S_IWUSR|S_IRUGO, + show_temp_hyst1, set_temp_hyst1); +static DEVICE_ATTR(fan1_auto, S_IWUSR|S_IRUGO, show_fan_auto1, set_fan_auto1); +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_input, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_input, NULL, 1); +static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR|S_IRUGO, + show_fan_min, set_fan_min, 0); +static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR|S_IRUGO, + show_fan_min, set_fan_min, 1); +static SENSOR_DEVICE_ATTR(fan1_div, S_IWUSR|S_IRUGO, + show_fan_div, set_fan_div, 0); +static SENSOR_DEVICE_ATTR(fan2_div, S_IWUSR|S_IRUGO, + show_fan_div, set_fan_div, 1); +static DEVICE_ATTR(in0_input, S_IRUGO, show_in_input0, NULL); +static DEVICE_ATTR(in1_input, S_IRUGO, show_in_input1, NULL); +static DEVICE_ATTR(in2_input, S_IRUGO, show_in_input2, NULL); +static DEVICE_ATTR(in3_input, S_IRUGO, show_in_input3, NULL); +static DEVICE_ATTR(in0_min, S_IWUSR|S_IRUGO, show_in_min0, set_in_min0); +static DEVICE_ATTR(in1_min, S_IWUSR|S_IRUGO, show_in_min1, set_in_min1); +static DEVICE_ATTR(in2_min, S_IWUSR|S_IRUGO, show_in_min2, set_in_min2); +static DEVICE_ATTR(in3_min, S_IWUSR|S_IRUGO, show_in_min3, set_in_min3); +static DEVICE_ATTR(in0_max, S_IWUSR|S_IRUGO, show_in_max0, set_in_max0); +static DEVICE_ATTR(in1_max, S_IWUSR|S_IRUGO, show_in_max1, set_in_max1); +static DEVICE_ATTR(in2_max, S_IWUSR|S_IRUGO, show_in_max2, set_in_max2); +static DEVICE_ATTR(in3_max, S_IWUSR|S_IRUGO, show_in_max3, set_in_max3); +static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL); +static DEVICE_ATTR(beep_enable, S_IWUSR|S_IRUGO, + show_beep_enable, set_beep_enable); +static DEVICE_ATTR(beep_mask, S_IWUSR|S_IRUGO, + show_beep_mask, set_beep_mask); + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct gl518_data *data = gl518_update_device(dev); + return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1); +} + +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 6); + +static ssize_t show_beep(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct gl518_data *data = gl518_update_device(dev); + return sprintf(buf, "%u\n", (data->beep_mask >> bitnr) & 1); +} + +static ssize_t set_beep(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gl518_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int bitnr = to_sensor_dev_attr(attr)->index; + unsigned long bit; + int err; + + err = kstrtoul(buf, 10, &bit); + if (err) + return err; + + if (bit & ~1) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->beep_mask = gl518_read_value(client, GL518_REG_ALARM); + if (bit) + data->beep_mask |= (1 << bitnr); + else + data->beep_mask &= ~(1 << bitnr); + gl518_write_value(client, GL518_REG_ALARM, data->beep_mask); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(in0_beep, S_IRUGO|S_IWUSR, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(in1_beep, S_IRUGO|S_IWUSR, show_beep, set_beep, 1); +static SENSOR_DEVICE_ATTR(in2_beep, S_IRUGO|S_IWUSR, show_beep, set_beep, 2); +static SENSOR_DEVICE_ATTR(in3_beep, S_IRUGO|S_IWUSR, show_beep, set_beep, 3); +static SENSOR_DEVICE_ATTR(temp1_beep, S_IRUGO|S_IWUSR, show_beep, set_beep, 4); +static SENSOR_DEVICE_ATTR(fan1_beep, S_IRUGO|S_IWUSR, show_beep, set_beep, 5); +static SENSOR_DEVICE_ATTR(fan2_beep, S_IRUGO|S_IWUSR, show_beep, set_beep, 6); + +static struct attribute *gl518_attributes[] = { + &dev_attr_in3_input.attr, + &dev_attr_in0_min.attr, + &dev_attr_in1_min.attr, + &dev_attr_in2_min.attr, + &dev_attr_in3_min.attr, + &dev_attr_in0_max.attr, + &dev_attr_in1_max.attr, + &dev_attr_in2_max.attr, + &dev_attr_in3_max.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in0_beep.dev_attr.attr, + &sensor_dev_attr_in1_beep.dev_attr.attr, + &sensor_dev_attr_in2_beep.dev_attr.attr, + &sensor_dev_attr_in3_beep.dev_attr.attr, + + &dev_attr_fan1_auto.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_beep.dev_attr.attr, + &sensor_dev_attr_fan2_beep.dev_attr.attr, + + &dev_attr_temp1_input.attr, + &dev_attr_temp1_max.attr, + &dev_attr_temp1_max_hyst.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_beep.dev_attr.attr, + + &dev_attr_alarms.attr, + &dev_attr_beep_enable.attr, + &dev_attr_beep_mask.attr, + NULL +}; + +static const struct attribute_group gl518_group = { + .attrs = gl518_attributes, +}; + +static struct attribute *gl518_attributes_r80[] = { + &dev_attr_in0_input.attr, + &dev_attr_in1_input.attr, + &dev_attr_in2_input.attr, + NULL +}; + +static const struct attribute_group gl518_group_r80 = { + .attrs = gl518_attributes_r80, +}; + +/* + * Real code + */ + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int gl518_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int rev; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + /* Now, we do the remaining detection. */ + if ((gl518_read_value(client, GL518_REG_CHIP_ID) != 0x80) + || (gl518_read_value(client, GL518_REG_CONF) & 0x80)) + return -ENODEV; + + /* Determine the chip type. */ + rev = gl518_read_value(client, GL518_REG_REVISION); + if (rev != 0x00 && rev != 0x80) + return -ENODEV; + + strlcpy(info->type, "gl518sm", I2C_NAME_SIZE); + + return 0; +} + +/* + * Called when we have found a new GL518SM. + * Note that we preserve D4:NoFan2 and D2:beep_enable. + */ +static void gl518_init_client(struct i2c_client *client) +{ + /* Make sure we leave D7:Reset untouched */ + u8 regvalue = gl518_read_value(client, GL518_REG_CONF) & 0x7f; + + /* Comparator mode (D3=0), standby mode (D6=0) */ + gl518_write_value(client, GL518_REG_CONF, (regvalue &= 0x37)); + + /* Never interrupts */ + gl518_write_value(client, GL518_REG_MASK, 0x00); + + /* Clear status register (D5=1), start (D6=1) */ + gl518_write_value(client, GL518_REG_CONF, 0x20 | regvalue); + gl518_write_value(client, GL518_REG_CONF, 0x40 | regvalue); +} + +static int gl518_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct gl518_data *data; + int revision; + + data = devm_kzalloc(dev, sizeof(struct gl518_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + revision = gl518_read_value(client, GL518_REG_REVISION); + data->type = revision == 0x80 ? gl518sm_r80 : gl518sm_r00; + mutex_init(&data->update_lock); + + /* Initialize the GL518SM chip */ + data->alarm_mask = 0xff; + gl518_init_client(client); + + /* sysfs hooks */ + data->groups[0] = &gl518_group; + if (data->type == gl518sm_r80) + data->groups[1] = &gl518_group_r80; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id gl518_id[] = { + { "gl518sm", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, gl518_id); + +static struct i2c_driver gl518_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "gl518sm", + }, + .probe = gl518_probe, + .id_table = gl518_id, + .detect = gl518_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(gl518_driver); + +MODULE_AUTHOR("Frodo Looijaard , " + "Kyosti Malkki and " + "Hong-Gunn Chew "); +MODULE_DESCRIPTION("GL518SM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/gl520sm.c b/drivers/hwmon/gl520sm.c new file mode 100644 index 000000000..4ff32ee67 --- /dev/null +++ b/drivers/hwmon/gl520sm.c @@ -0,0 +1,939 @@ +/* + * gl520sm.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (c) 1998, 1999 Frodo Looijaard , + * Kyösti Mälkki + * Copyright (c) 2005 Maarten Deprez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Type of the extra sensor */ +static unsigned short extra_sensor_type; +module_param(extra_sensor_type, ushort, 0); +MODULE_PARM_DESC(extra_sensor_type, "Type of extra sensor (0=autodetect, 1=temperature, 2=voltage)"); + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END }; + +/* + * Many GL520 constants specified below + * One of the inputs can be configured as either temp or voltage. + * That's why _TEMP2 and _IN4 access the same register + */ + +/* The GL520 registers */ +#define GL520_REG_CHIP_ID 0x00 +#define GL520_REG_REVISION 0x01 +#define GL520_REG_CONF 0x03 +#define GL520_REG_MASK 0x11 + +#define GL520_REG_VID_INPUT 0x02 + +static const u8 GL520_REG_IN_INPUT[] = { 0x15, 0x14, 0x13, 0x0d, 0x0e }; +static const u8 GL520_REG_IN_LIMIT[] = { 0x0c, 0x09, 0x0a, 0x0b }; +static const u8 GL520_REG_IN_MIN[] = { 0x0c, 0x09, 0x0a, 0x0b, 0x18 }; +static const u8 GL520_REG_IN_MAX[] = { 0x0c, 0x09, 0x0a, 0x0b, 0x17 }; + +static const u8 GL520_REG_TEMP_INPUT[] = { 0x04, 0x0e }; +static const u8 GL520_REG_TEMP_MAX[] = { 0x05, 0x17 }; +static const u8 GL520_REG_TEMP_MAX_HYST[] = { 0x06, 0x18 }; + +#define GL520_REG_FAN_INPUT 0x07 +#define GL520_REG_FAN_MIN 0x08 +#define GL520_REG_FAN_DIV 0x0f +#define GL520_REG_FAN_OFF GL520_REG_FAN_DIV + +#define GL520_REG_ALARMS 0x12 +#define GL520_REG_BEEP_MASK 0x10 +#define GL520_REG_BEEP_ENABLE GL520_REG_CONF + +/* Client data */ +struct gl520_data { + struct i2c_client *client; + const struct attribute_group *groups[3]; + struct mutex update_lock; + char valid; /* zero until the following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + u8 vid; + u8 vrm; + u8 in_input[5]; /* [0] = VVD */ + u8 in_min[5]; /* [0] = VDD */ + u8 in_max[5]; /* [0] = VDD */ + u8 fan_input[2]; + u8 fan_min[2]; + u8 fan_div[2]; + u8 fan_off; + u8 temp_input[2]; + u8 temp_max[2]; + u8 temp_max_hyst[2]; + u8 alarms; + u8 beep_enable; + u8 beep_mask; + u8 alarm_mask; + u8 two_temps; +}; + +/* + * Registers 0x07 to 0x0c are word-sized, others are byte-sized + * GL520 uses a high-byte first convention + */ +static int gl520_read_value(struct i2c_client *client, u8 reg) +{ + if ((reg >= 0x07) && (reg <= 0x0c)) + return i2c_smbus_read_word_swapped(client, reg); + else + return i2c_smbus_read_byte_data(client, reg); +} + +static int gl520_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + if ((reg >= 0x07) && (reg <= 0x0c)) + return i2c_smbus_write_word_swapped(client, reg, value); + else + return i2c_smbus_write_byte_data(client, reg, value); +} + +static struct gl520_data *gl520_update_device(struct device *dev) +{ + struct gl520_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int val, i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + 2 * HZ) || !data->valid) { + + dev_dbg(&client->dev, "Starting gl520sm update\n"); + + data->alarms = gl520_read_value(client, GL520_REG_ALARMS); + data->beep_mask = gl520_read_value(client, GL520_REG_BEEP_MASK); + data->vid = gl520_read_value(client, + GL520_REG_VID_INPUT) & 0x1f; + + for (i = 0; i < 4; i++) { + data->in_input[i] = gl520_read_value(client, + GL520_REG_IN_INPUT[i]); + val = gl520_read_value(client, GL520_REG_IN_LIMIT[i]); + data->in_min[i] = val & 0xff; + data->in_max[i] = (val >> 8) & 0xff; + } + + val = gl520_read_value(client, GL520_REG_FAN_INPUT); + data->fan_input[0] = (val >> 8) & 0xff; + data->fan_input[1] = val & 0xff; + + val = gl520_read_value(client, GL520_REG_FAN_MIN); + data->fan_min[0] = (val >> 8) & 0xff; + data->fan_min[1] = val & 0xff; + + data->temp_input[0] = gl520_read_value(client, + GL520_REG_TEMP_INPUT[0]); + data->temp_max[0] = gl520_read_value(client, + GL520_REG_TEMP_MAX[0]); + data->temp_max_hyst[0] = gl520_read_value(client, + GL520_REG_TEMP_MAX_HYST[0]); + + val = gl520_read_value(client, GL520_REG_FAN_DIV); + data->fan_div[0] = (val >> 6) & 0x03; + data->fan_div[1] = (val >> 4) & 0x03; + data->fan_off = (val >> 2) & 0x01; + + data->alarms &= data->alarm_mask; + + val = gl520_read_value(client, GL520_REG_CONF); + data->beep_enable = !((val >> 2) & 1); + + /* Temp1 and Vin4 are the same input */ + if (data->two_temps) { + data->temp_input[1] = gl520_read_value(client, + GL520_REG_TEMP_INPUT[1]); + data->temp_max[1] = gl520_read_value(client, + GL520_REG_TEMP_MAX[1]); + data->temp_max_hyst[1] = gl520_read_value(client, + GL520_REG_TEMP_MAX_HYST[1]); + } else { + data->in_input[4] = gl520_read_value(client, + GL520_REG_IN_INPUT[4]); + data->in_min[4] = gl520_read_value(client, + GL520_REG_IN_MIN[4]); + data->in_max[4] = gl520_read_value(client, + GL520_REG_IN_MAX[4]); + } + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Sysfs stuff + */ + +static ssize_t cpu0_vid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gl520_data *data = gl520_update_device(dev); + return sprintf(buf, "%u\n", vid_from_reg(data->vid, data->vrm)); +} +static DEVICE_ATTR_RO(cpu0_vid); + +#define VDD_FROM_REG(val) DIV_ROUND_CLOSEST((val) * 95, 4) +#define VDD_CLAMP(val) clamp_val(val, 0, 255 * 95 / 4) +#define VDD_TO_REG(val) DIV_ROUND_CLOSEST(VDD_CLAMP(val) * 4, 95) + +#define IN_FROM_REG(val) ((val) * 19) +#define IN_CLAMP(val) clamp_val(val, 0, 255 * 19) +#define IN_TO_REG(val) DIV_ROUND_CLOSEST(IN_CLAMP(val), 19) + +static ssize_t get_in_input(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int n = to_sensor_dev_attr(attr)->index; + struct gl520_data *data = gl520_update_device(dev); + u8 r = data->in_input[n]; + + if (n == 0) + return sprintf(buf, "%d\n", VDD_FROM_REG(r)); + else + return sprintf(buf, "%d\n", IN_FROM_REG(r)); +} + +static ssize_t get_in_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int n = to_sensor_dev_attr(attr)->index; + struct gl520_data *data = gl520_update_device(dev); + u8 r = data->in_min[n]; + + if (n == 0) + return sprintf(buf, "%d\n", VDD_FROM_REG(r)); + else + return sprintf(buf, "%d\n", IN_FROM_REG(r)); +} + +static ssize_t get_in_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int n = to_sensor_dev_attr(attr)->index; + struct gl520_data *data = gl520_update_device(dev); + u8 r = data->in_max[n]; + + if (n == 0) + return sprintf(buf, "%d\n", VDD_FROM_REG(r)); + else + return sprintf(buf, "%d\n", IN_FROM_REG(r)); +} + +static ssize_t set_in_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gl520_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int n = to_sensor_dev_attr(attr)->index; + u8 r; + long v; + int err; + + err = kstrtol(buf, 10, &v); + if (err) + return err; + + mutex_lock(&data->update_lock); + + if (n == 0) + r = VDD_TO_REG(v); + else + r = IN_TO_REG(v); + + data->in_min[n] = r; + + if (n < 4) + gl520_write_value(client, GL520_REG_IN_MIN[n], + (gl520_read_value(client, GL520_REG_IN_MIN[n]) + & ~0xff) | r); + else + gl520_write_value(client, GL520_REG_IN_MIN[n], r); + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_in_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gl520_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int n = to_sensor_dev_attr(attr)->index; + u8 r; + long v; + int err; + + err = kstrtol(buf, 10, &v); + if (err) + return err; + + if (n == 0) + r = VDD_TO_REG(v); + else + r = IN_TO_REG(v); + + mutex_lock(&data->update_lock); + + data->in_max[n] = r; + + if (n < 4) + gl520_write_value(client, GL520_REG_IN_MAX[n], + (gl520_read_value(client, GL520_REG_IN_MAX[n]) + & ~0xff00) | (r << 8)); + else + gl520_write_value(client, GL520_REG_IN_MAX[n], r); + + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, get_in_input, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, get_in_input, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, get_in_input, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, get_in_input, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, get_in_input, NULL, 4); +static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO | S_IWUSR, + get_in_min, set_in_min, 0); +static SENSOR_DEVICE_ATTR(in1_min, S_IRUGO | S_IWUSR, + get_in_min, set_in_min, 1); +static SENSOR_DEVICE_ATTR(in2_min, S_IRUGO | S_IWUSR, + get_in_min, set_in_min, 2); +static SENSOR_DEVICE_ATTR(in3_min, S_IRUGO | S_IWUSR, + get_in_min, set_in_min, 3); +static SENSOR_DEVICE_ATTR(in4_min, S_IRUGO | S_IWUSR, + get_in_min, set_in_min, 4); +static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO | S_IWUSR, + get_in_max, set_in_max, 0); +static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, + get_in_max, set_in_max, 1); +static SENSOR_DEVICE_ATTR(in2_max, S_IRUGO | S_IWUSR, + get_in_max, set_in_max, 2); +static SENSOR_DEVICE_ATTR(in3_max, S_IRUGO | S_IWUSR, + get_in_max, set_in_max, 3); +static SENSOR_DEVICE_ATTR(in4_max, S_IRUGO | S_IWUSR, + get_in_max, set_in_max, 4); + +#define DIV_FROM_REG(val) (1 << (val)) +#define FAN_FROM_REG(val, div) ((val) == 0 ? 0 : (480000 / ((val) << (div)))) + +#define FAN_BASE(div) (480000 >> (div)) +#define FAN_CLAMP(val, div) clamp_val(val, FAN_BASE(div) / 255, \ + FAN_BASE(div)) +#define FAN_TO_REG(val, div) ((val) == 0 ? 0 : \ + DIV_ROUND_CLOSEST(480000, \ + FAN_CLAMP(val, div) << (div))) + +static ssize_t get_fan_input(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int n = to_sensor_dev_attr(attr)->index; + struct gl520_data *data = gl520_update_device(dev); + + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_input[n], + data->fan_div[n])); +} + +static ssize_t get_fan_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int n = to_sensor_dev_attr(attr)->index; + struct gl520_data *data = gl520_update_device(dev); + + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[n], + data->fan_div[n])); +} + +static ssize_t get_fan_div(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int n = to_sensor_dev_attr(attr)->index; + struct gl520_data *data = gl520_update_device(dev); + + return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[n])); +} + +static ssize_t fan1_off_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gl520_data *data = gl520_update_device(dev); + return sprintf(buf, "%d\n", data->fan_off); +} + +static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gl520_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int n = to_sensor_dev_attr(attr)->index; + u8 r; + unsigned long v; + int err; + + err = kstrtoul(buf, 10, &v); + if (err) + return err; + + mutex_lock(&data->update_lock); + r = FAN_TO_REG(v, data->fan_div[n]); + data->fan_min[n] = r; + + if (n == 0) + gl520_write_value(client, GL520_REG_FAN_MIN, + (gl520_read_value(client, GL520_REG_FAN_MIN) + & ~0xff00) | (r << 8)); + else + gl520_write_value(client, GL520_REG_FAN_MIN, + (gl520_read_value(client, GL520_REG_FAN_MIN) + & ~0xff) | r); + + data->beep_mask = gl520_read_value(client, GL520_REG_BEEP_MASK); + if (data->fan_min[n] == 0) + data->alarm_mask &= (n == 0) ? ~0x20 : ~0x40; + else + data->alarm_mask |= (n == 0) ? 0x20 : 0x40; + data->beep_mask &= data->alarm_mask; + gl520_write_value(client, GL520_REG_BEEP_MASK, data->beep_mask); + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gl520_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int n = to_sensor_dev_attr(attr)->index; + u8 r; + unsigned long v; + int err; + + err = kstrtoul(buf, 10, &v); + if (err) + return err; + + switch (v) { + case 1: + r = 0; + break; + case 2: + r = 1; + break; + case 4: + r = 2; + break; + case 8: + r = 3; + break; + default: + dev_err(&client->dev, + "fan_div value %ld not supported. Choose one of 1, 2, 4 or 8!\n", v); + return -EINVAL; + } + + mutex_lock(&data->update_lock); + data->fan_div[n] = r; + + if (n == 0) + gl520_write_value(client, GL520_REG_FAN_DIV, + (gl520_read_value(client, GL520_REG_FAN_DIV) + & ~0xc0) | (r << 6)); + else + gl520_write_value(client, GL520_REG_FAN_DIV, + (gl520_read_value(client, GL520_REG_FAN_DIV) + & ~0x30) | (r << 4)); + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t fan1_off_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct gl520_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 r; + unsigned long v; + int err; + + err = kstrtoul(buf, 10, &v); + if (err) + return err; + + r = (v ? 1 : 0); + + mutex_lock(&data->update_lock); + data->fan_off = r; + gl520_write_value(client, GL520_REG_FAN_OFF, + (gl520_read_value(client, GL520_REG_FAN_OFF) + & ~0x0c) | (r << 2)); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan_input, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, get_fan_input, NULL, 1); +static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR, + get_fan_min, set_fan_min, 0); +static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO | S_IWUSR, + get_fan_min, set_fan_min, 1); +static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR, + get_fan_div, set_fan_div, 0); +static SENSOR_DEVICE_ATTR(fan2_div, S_IRUGO | S_IWUSR, + get_fan_div, set_fan_div, 1); +static DEVICE_ATTR_RW(fan1_off); + +#define TEMP_FROM_REG(val) (((val) - 130) * 1000) +#define TEMP_CLAMP(val) clamp_val(val, -130000, 125000) +#define TEMP_TO_REG(val) (DIV_ROUND_CLOSEST(TEMP_CLAMP(val), 1000) + 130) + +static ssize_t get_temp_input(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int n = to_sensor_dev_attr(attr)->index; + struct gl520_data *data = gl520_update_device(dev); + + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_input[n])); +} + +static ssize_t get_temp_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int n = to_sensor_dev_attr(attr)->index; + struct gl520_data *data = gl520_update_device(dev); + + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max[n])); +} + +static ssize_t get_temp_max_hyst(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int n = to_sensor_dev_attr(attr)->index; + struct gl520_data *data = gl520_update_device(dev); + + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max_hyst[n])); +} + +static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gl520_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int n = to_sensor_dev_attr(attr)->index; + long v; + int err; + + err = kstrtol(buf, 10, &v); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_max[n] = TEMP_TO_REG(v); + gl520_write_value(client, GL520_REG_TEMP_MAX[n], data->temp_max[n]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_temp_max_hyst(struct device *dev, struct device_attribute + *attr, const char *buf, size_t count) +{ + struct gl520_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int n = to_sensor_dev_attr(attr)->index; + long v; + int err; + + err = kstrtol(buf, 10, &v); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_max_hyst[n] = TEMP_TO_REG(v); + gl520_write_value(client, GL520_REG_TEMP_MAX_HYST[n], + data->temp_max_hyst[n]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, get_temp_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, get_temp_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, + get_temp_max, set_temp_max, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, + get_temp_max, set_temp_max, 1); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IRUGO | S_IWUSR, + get_temp_max_hyst, set_temp_max_hyst, 0); +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IRUGO | S_IWUSR, + get_temp_max_hyst, set_temp_max_hyst, 1); + +static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct gl520_data *data = gl520_update_device(dev); + return sprintf(buf, "%d\n", data->alarms); +} + +static ssize_t beep_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gl520_data *data = gl520_update_device(dev); + return sprintf(buf, "%d\n", data->beep_enable); +} + +static ssize_t beep_mask_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gl520_data *data = gl520_update_device(dev); + return sprintf(buf, "%d\n", data->beep_mask); +} + +static ssize_t beep_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gl520_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 r; + unsigned long v; + int err; + + err = kstrtoul(buf, 10, &v); + if (err) + return err; + + r = (v ? 0 : 1); + + mutex_lock(&data->update_lock); + data->beep_enable = !r; + gl520_write_value(client, GL520_REG_BEEP_ENABLE, + (gl520_read_value(client, GL520_REG_BEEP_ENABLE) + & ~0x04) | (r << 2)); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t beep_mask_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct gl520_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long r; + int err; + + err = kstrtoul(buf, 10, &r); + if (err) + return err; + + mutex_lock(&data->update_lock); + r &= data->alarm_mask; + data->beep_mask = r; + gl520_write_value(client, GL520_REG_BEEP_MASK, r); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RO(alarms); +static DEVICE_ATTR_RW(beep_enable); +static DEVICE_ATTR_RW(beep_mask); + +static ssize_t get_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bit_nr = to_sensor_dev_attr(attr)->index; + struct gl520_data *data = gl520_update_device(dev); + + return sprintf(buf, "%d\n", (data->alarms >> bit_nr) & 1); +} + +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, get_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, get_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, get_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, get_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, get_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, get_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, get_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, get_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, get_alarm, NULL, 7); + +static ssize_t get_beep(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct gl520_data *data = gl520_update_device(dev); + + return sprintf(buf, "%d\n", (data->beep_mask >> bitnr) & 1); +} + +static ssize_t set_beep(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gl520_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int bitnr = to_sensor_dev_attr(attr)->index; + unsigned long bit; + + int err; + + err = kstrtoul(buf, 10, &bit); + if (err) + return err; + if (bit & ~1) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->beep_mask = gl520_read_value(client, GL520_REG_BEEP_MASK); + if (bit) + data->beep_mask |= (1 << bitnr); + else + data->beep_mask &= ~(1 << bitnr); + gl520_write_value(client, GL520_REG_BEEP_MASK, data->beep_mask); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(in0_beep, S_IRUGO | S_IWUSR, get_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(in1_beep, S_IRUGO | S_IWUSR, get_beep, set_beep, 1); +static SENSOR_DEVICE_ATTR(in2_beep, S_IRUGO | S_IWUSR, get_beep, set_beep, 2); +static SENSOR_DEVICE_ATTR(in3_beep, S_IRUGO | S_IWUSR, get_beep, set_beep, 3); +static SENSOR_DEVICE_ATTR(temp1_beep, S_IRUGO | S_IWUSR, get_beep, set_beep, 4); +static SENSOR_DEVICE_ATTR(fan1_beep, S_IRUGO | S_IWUSR, get_beep, set_beep, 5); +static SENSOR_DEVICE_ATTR(fan2_beep, S_IRUGO | S_IWUSR, get_beep, set_beep, 6); +static SENSOR_DEVICE_ATTR(temp2_beep, S_IRUGO | S_IWUSR, get_beep, set_beep, 7); +static SENSOR_DEVICE_ATTR(in4_beep, S_IRUGO | S_IWUSR, get_beep, set_beep, 7); + +static struct attribute *gl520_attributes[] = { + &dev_attr_cpu0_vid.attr, + + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in0_beep.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in1_beep.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in2_beep.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in3_beep.dev_attr.attr, + + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_beep.dev_attr.attr, + &dev_attr_fan1_off.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_beep.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_beep.dev_attr.attr, + + &dev_attr_alarms.attr, + &dev_attr_beep_enable.attr, + &dev_attr_beep_mask.attr, + NULL +}; + +static const struct attribute_group gl520_group = { + .attrs = gl520_attributes, +}; + +static struct attribute *gl520_attributes_in4[] = { + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + &sensor_dev_attr_in4_beep.dev_attr.attr, + NULL +}; + +static struct attribute *gl520_attributes_temp2[] = { + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_beep.dev_attr.attr, + NULL +}; + +static const struct attribute_group gl520_group_in4 = { + .attrs = gl520_attributes_in4, +}; + +static const struct attribute_group gl520_group_temp2 = { + .attrs = gl520_attributes_temp2, +}; + + +/* + * Real code + */ + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int gl520_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + /* Determine the chip type. */ + if ((gl520_read_value(client, GL520_REG_CHIP_ID) != 0x20) || + ((gl520_read_value(client, GL520_REG_REVISION) & 0x7f) != 0x00) || + ((gl520_read_value(client, GL520_REG_CONF) & 0x80) != 0x00)) { + dev_dbg(&client->dev, "Unknown chip type, skipping\n"); + return -ENODEV; + } + + strlcpy(info->type, "gl520sm", I2C_NAME_SIZE); + + return 0; +} + +/* Called when we have found a new GL520SM. */ +static void gl520_init_client(struct i2c_client *client) +{ + struct gl520_data *data = i2c_get_clientdata(client); + u8 oldconf, conf; + + conf = oldconf = gl520_read_value(client, GL520_REG_CONF); + + data->alarm_mask = 0xff; + data->vrm = vid_which_vrm(); + + if (extra_sensor_type == 1) + conf &= ~0x10; + else if (extra_sensor_type == 2) + conf |= 0x10; + data->two_temps = !(conf & 0x10); + + /* If IRQ# is disabled, we can safely force comparator mode */ + if (!(conf & 0x20)) + conf &= 0xf7; + + /* Enable monitoring if needed */ + conf |= 0x40; + + if (conf != oldconf) + gl520_write_value(client, GL520_REG_CONF, conf); + + gl520_update_device(&(client->dev)); + + if (data->fan_min[0] == 0) + data->alarm_mask &= ~0x20; + if (data->fan_min[1] == 0) + data->alarm_mask &= ~0x40; + + data->beep_mask &= data->alarm_mask; + gl520_write_value(client, GL520_REG_BEEP_MASK, data->beep_mask); +} + +static int gl520_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct gl520_data *data; + + data = devm_kzalloc(dev, sizeof(struct gl520_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + data->client = client; + + /* Initialize the GL520SM chip */ + gl520_init_client(client); + + /* sysfs hooks */ + data->groups[0] = &gl520_group; + + if (data->two_temps) + data->groups[1] = &gl520_group_temp2; + else + data->groups[1] = &gl520_group_in4; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id gl520_id[] = { + { "gl520sm", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, gl520_id); + +static struct i2c_driver gl520_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "gl520sm", + }, + .probe = gl520_probe, + .id_table = gl520_id, + .detect = gl520_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(gl520_driver); + +MODULE_AUTHOR("Frodo Looijaard , " + "Kyösti Mälkki , " + "Maarten Deprez "); +MODULE_DESCRIPTION("GL520SM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c new file mode 100644 index 000000000..a3974cdde --- /dev/null +++ b/drivers/hwmon/gpio-fan.c @@ -0,0 +1,619 @@ +/* + * gpio-fan.c - Hwmon driver for fans connected to GPIO lines. + * + * Copyright (C) 2010 LaCie + * + * Author: Simon Guinot + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct gpio_fan_speed { + int rpm; + int ctrl_val; +}; + +struct gpio_fan_data { + struct device *dev; + struct device *hwmon_dev; + /* Cooling device if any */ + struct thermal_cooling_device *cdev; + struct mutex lock; /* lock GPIOs operations. */ + int num_gpios; + struct gpio_desc **gpios; + int num_speed; + struct gpio_fan_speed *speed; + int speed_index; +#ifdef CONFIG_PM_SLEEP + int resume_speed; +#endif + bool pwm_enable; + struct gpio_desc *alarm_gpio; + struct work_struct alarm_work; +}; + +/* + * Alarm GPIO. + */ + +static void fan_alarm_notify(struct work_struct *ws) +{ + struct gpio_fan_data *fan_data = + container_of(ws, struct gpio_fan_data, alarm_work); + + sysfs_notify(&fan_data->dev->kobj, NULL, "fan1_alarm"); + kobject_uevent(&fan_data->dev->kobj, KOBJ_CHANGE); +} + +static irqreturn_t fan_alarm_irq_handler(int irq, void *dev_id) +{ + struct gpio_fan_data *fan_data = dev_id; + + schedule_work(&fan_data->alarm_work); + + return IRQ_NONE; +} + +static ssize_t fan1_alarm_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", + gpiod_get_value_cansleep(fan_data->alarm_gpio)); +} + +static DEVICE_ATTR_RO(fan1_alarm); + +static int fan_alarm_init(struct gpio_fan_data *fan_data) +{ + int alarm_irq; + struct device *dev = fan_data->dev; + + /* + * If the alarm GPIO don't support interrupts, just leave + * without initializing the fail notification support. + */ + alarm_irq = gpiod_to_irq(fan_data->alarm_gpio); + if (alarm_irq <= 0) + return 0; + + INIT_WORK(&fan_data->alarm_work, fan_alarm_notify); + irq_set_irq_type(alarm_irq, IRQ_TYPE_EDGE_BOTH); + return devm_request_irq(dev, alarm_irq, fan_alarm_irq_handler, + IRQF_SHARED, "GPIO fan alarm", fan_data); +} + +/* + * Control GPIOs. + */ + +/* Must be called with fan_data->lock held, except during initialization. */ +static void __set_fan_ctrl(struct gpio_fan_data *fan_data, int ctrl_val) +{ + int i; + + for (i = 0; i < fan_data->num_gpios; i++) + gpiod_set_value_cansleep(fan_data->gpios[i], + (ctrl_val >> i) & 1); +} + +static int __get_fan_ctrl(struct gpio_fan_data *fan_data) +{ + int i; + int ctrl_val = 0; + + for (i = 0; i < fan_data->num_gpios; i++) { + int value; + + value = gpiod_get_value_cansleep(fan_data->gpios[i]); + ctrl_val |= (value << i); + } + return ctrl_val; +} + +/* Must be called with fan_data->lock held, except during initialization. */ +static void set_fan_speed(struct gpio_fan_data *fan_data, int speed_index) +{ + if (fan_data->speed_index == speed_index) + return; + + __set_fan_ctrl(fan_data, fan_data->speed[speed_index].ctrl_val); + fan_data->speed_index = speed_index; +} + +static int get_fan_speed_index(struct gpio_fan_data *fan_data) +{ + int ctrl_val = __get_fan_ctrl(fan_data); + int i; + + for (i = 0; i < fan_data->num_speed; i++) + if (fan_data->speed[i].ctrl_val == ctrl_val) + return i; + + dev_warn(fan_data->dev, + "missing speed array entry for GPIO value 0x%x\n", ctrl_val); + + return -ENODEV; +} + +static int rpm_to_speed_index(struct gpio_fan_data *fan_data, unsigned long rpm) +{ + struct gpio_fan_speed *speed = fan_data->speed; + int i; + + for (i = 0; i < fan_data->num_speed; i++) + if (speed[i].rpm >= rpm) + return i; + + return fan_data->num_speed - 1; +} + +static ssize_t pwm1_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + u8 pwm = fan_data->speed_index * 255 / (fan_data->num_speed - 1); + + return sprintf(buf, "%d\n", pwm); +} + +static ssize_t pwm1_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + unsigned long pwm; + int speed_index; + int ret = count; + + if (kstrtoul(buf, 10, &pwm) || pwm > 255) + return -EINVAL; + + mutex_lock(&fan_data->lock); + + if (!fan_data->pwm_enable) { + ret = -EPERM; + goto exit_unlock; + } + + speed_index = DIV_ROUND_UP(pwm * (fan_data->num_speed - 1), 255); + set_fan_speed(fan_data, speed_index); + +exit_unlock: + mutex_unlock(&fan_data->lock); + + return ret; +} + +static ssize_t pwm1_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", fan_data->pwm_enable); +} + +static ssize_t pwm1_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + unsigned long val; + + if (kstrtoul(buf, 10, &val) || val > 1) + return -EINVAL; + + if (fan_data->pwm_enable == val) + return count; + + mutex_lock(&fan_data->lock); + + fan_data->pwm_enable = val; + + /* Disable manual control mode: set fan at full speed. */ + if (val == 0) + set_fan_speed(fan_data, fan_data->num_speed - 1); + + mutex_unlock(&fan_data->lock); + + return count; +} + +static ssize_t pwm1_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "0\n"); +} + +static ssize_t fan1_min_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", fan_data->speed[0].rpm); +} + +static ssize_t fan1_max_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", + fan_data->speed[fan_data->num_speed - 1].rpm); +} + +static ssize_t fan1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", fan_data->speed[fan_data->speed_index].rpm); +} + +static ssize_t set_rpm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + unsigned long rpm; + int ret = count; + + if (kstrtoul(buf, 10, &rpm)) + return -EINVAL; + + mutex_lock(&fan_data->lock); + + if (!fan_data->pwm_enable) { + ret = -EPERM; + goto exit_unlock; + } + + set_fan_speed(fan_data, rpm_to_speed_index(fan_data, rpm)); + +exit_unlock: + mutex_unlock(&fan_data->lock); + + return ret; +} + +static DEVICE_ATTR_RW(pwm1); +static DEVICE_ATTR_RW(pwm1_enable); +static DEVICE_ATTR_RO(pwm1_mode); +static DEVICE_ATTR_RO(fan1_min); +static DEVICE_ATTR_RO(fan1_max); +static DEVICE_ATTR_RO(fan1_input); +static DEVICE_ATTR(fan1_target, S_IRUGO | S_IWUSR, fan1_input_show, set_rpm); + +static umode_t gpio_fan_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct gpio_fan_data *data = dev_get_drvdata(dev); + + if (index == 0 && !data->alarm_gpio) + return 0; + if (index > 0 && !data->gpios) + return 0; + + return attr->mode; +} + +static struct attribute *gpio_fan_attributes[] = { + &dev_attr_fan1_alarm.attr, /* 0 */ + &dev_attr_pwm1.attr, /* 1 */ + &dev_attr_pwm1_enable.attr, + &dev_attr_pwm1_mode.attr, + &dev_attr_fan1_input.attr, + &dev_attr_fan1_target.attr, + &dev_attr_fan1_min.attr, + &dev_attr_fan1_max.attr, + NULL +}; + +static const struct attribute_group gpio_fan_group = { + .attrs = gpio_fan_attributes, + .is_visible = gpio_fan_is_visible, +}; + +static const struct attribute_group *gpio_fan_groups[] = { + &gpio_fan_group, + NULL +}; + +static int fan_ctrl_init(struct gpio_fan_data *fan_data) +{ + int num_gpios = fan_data->num_gpios; + struct gpio_desc **gpios = fan_data->gpios; + int i, err; + + for (i = 0; i < num_gpios; i++) { + /* + * The GPIO descriptors were retrieved with GPIOD_ASIS so here + * we set the GPIO into output mode, carefully preserving the + * current value by setting it to whatever it is already set + * (no surprise changes in default fan speed). + */ + err = gpiod_direction_output(gpios[i], + gpiod_get_value_cansleep(gpios[i])); + if (err) + return err; + } + + fan_data->pwm_enable = true; /* Enable manual fan speed control. */ + fan_data->speed_index = get_fan_speed_index(fan_data); + if (fan_data->speed_index < 0) + return fan_data->speed_index; + + return 0; +} + +static int gpio_fan_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct gpio_fan_data *fan_data = cdev->devdata; + + if (!fan_data) + return -EINVAL; + + *state = fan_data->num_speed - 1; + return 0; +} + +static int gpio_fan_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct gpio_fan_data *fan_data = cdev->devdata; + + if (!fan_data) + return -EINVAL; + + *state = fan_data->speed_index; + return 0; +} + +static int gpio_fan_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct gpio_fan_data *fan_data = cdev->devdata; + + if (!fan_data) + return -EINVAL; + + set_fan_speed(fan_data, state); + return 0; +} + +static const struct thermal_cooling_device_ops gpio_fan_cool_ops = { + .get_max_state = gpio_fan_get_max_state, + .get_cur_state = gpio_fan_get_cur_state, + .set_cur_state = gpio_fan_set_cur_state, +}; + +/* + * Translate OpenFirmware node properties into platform_data + */ +static int gpio_fan_get_of_data(struct gpio_fan_data *fan_data) +{ + struct gpio_fan_speed *speed; + struct device *dev = fan_data->dev; + struct device_node *np = dev->of_node; + struct gpio_desc **gpios; + unsigned i; + u32 u; + struct property *prop; + const __be32 *p; + + /* Alarm GPIO if one exists */ + fan_data->alarm_gpio = devm_gpiod_get_optional(dev, "alarm", GPIOD_IN); + if (IS_ERR(fan_data->alarm_gpio)) + return PTR_ERR(fan_data->alarm_gpio); + + /* Fill GPIO pin array */ + fan_data->num_gpios = gpiod_count(dev, NULL); + if (fan_data->num_gpios <= 0) { + if (fan_data->alarm_gpio) + return 0; + dev_err(dev, "DT properties empty / missing"); + return -ENODEV; + } + gpios = devm_kcalloc(dev, + fan_data->num_gpios, sizeof(struct gpio_desc *), + GFP_KERNEL); + if (!gpios) + return -ENOMEM; + for (i = 0; i < fan_data->num_gpios; i++) { + gpios[i] = devm_gpiod_get_index(dev, NULL, i, GPIOD_ASIS); + if (IS_ERR(gpios[i])) + return PTR_ERR(gpios[i]); + } + fan_data->gpios = gpios; + + /* Get number of RPM/ctrl_val pairs in speed map */ + prop = of_find_property(np, "gpio-fan,speed-map", &i); + if (!prop) { + dev_err(dev, "gpio-fan,speed-map DT property missing"); + return -ENODEV; + } + i = i / sizeof(u32); + if (i == 0 || i & 1) { + dev_err(dev, "gpio-fan,speed-map contains zero/odd number of entries"); + return -ENODEV; + } + fan_data->num_speed = i / 2; + + /* + * Populate speed map + * Speed map is in the form + * this needs splitting into pairs to create gpio_fan_speed structs + */ + speed = devm_kcalloc(dev, + fan_data->num_speed, sizeof(struct gpio_fan_speed), + GFP_KERNEL); + if (!speed) + return -ENOMEM; + p = NULL; + for (i = 0; i < fan_data->num_speed; i++) { + p = of_prop_next_u32(prop, p, &u); + if (!p) + return -ENODEV; + speed[i].rpm = u; + p = of_prop_next_u32(prop, p, &u); + if (!p) + return -ENODEV; + speed[i].ctrl_val = u; + } + fan_data->speed = speed; + + return 0; +} + +static const struct of_device_id of_gpio_fan_match[] = { + { .compatible = "gpio-fan", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_gpio_fan_match); + +static int gpio_fan_probe(struct platform_device *pdev) +{ + int err; + struct gpio_fan_data *fan_data; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + + fan_data = devm_kzalloc(dev, sizeof(struct gpio_fan_data), + GFP_KERNEL); + if (!fan_data) + return -ENOMEM; + + fan_data->dev = dev; + err = gpio_fan_get_of_data(fan_data); + if (err) + return err; + + platform_set_drvdata(pdev, fan_data); + mutex_init(&fan_data->lock); + + /* Configure alarm GPIO if available. */ + if (fan_data->alarm_gpio) { + err = fan_alarm_init(fan_data); + if (err) + return err; + } + + /* Configure control GPIOs if available. */ + if (fan_data->gpios && fan_data->num_gpios > 0) { + if (!fan_data->speed || fan_data->num_speed <= 1) + return -EINVAL; + err = fan_ctrl_init(fan_data); + if (err) + return err; + } + + /* Make this driver part of hwmon class. */ + fan_data->hwmon_dev = + devm_hwmon_device_register_with_groups(dev, + "gpio_fan", fan_data, + gpio_fan_groups); + if (IS_ERR(fan_data->hwmon_dev)) + return PTR_ERR(fan_data->hwmon_dev); + + /* Optional cooling device register for Device tree platforms */ + fan_data->cdev = thermal_of_cooling_device_register(np, + "gpio-fan", + fan_data, + &gpio_fan_cool_ops); + + dev_info(dev, "GPIO fan initialized\n"); + + return 0; +} + +static int gpio_fan_remove(struct platform_device *pdev) +{ + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); + + if (!IS_ERR(fan_data->cdev)) + thermal_cooling_device_unregister(fan_data->cdev); + + if (fan_data->gpios) + set_fan_speed(fan_data, 0); + + return 0; +} + +static void gpio_fan_shutdown(struct platform_device *pdev) +{ + gpio_fan_remove(pdev); +} + +#ifdef CONFIG_PM_SLEEP +static int gpio_fan_suspend(struct device *dev) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + if (fan_data->gpios) { + fan_data->resume_speed = fan_data->speed_index; + set_fan_speed(fan_data, 0); + } + + return 0; +} + +static int gpio_fan_resume(struct device *dev) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + if (fan_data->gpios) + set_fan_speed(fan_data, fan_data->resume_speed); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(gpio_fan_pm, gpio_fan_suspend, gpio_fan_resume); +#define GPIO_FAN_PM (&gpio_fan_pm) +#else +#define GPIO_FAN_PM NULL +#endif + +static struct platform_driver gpio_fan_driver = { + .probe = gpio_fan_probe, + .remove = gpio_fan_remove, + .shutdown = gpio_fan_shutdown, + .driver = { + .name = "gpio-fan", + .pm = GPIO_FAN_PM, + .of_match_table = of_match_ptr(of_gpio_fan_match), + }, +}; + +module_platform_driver(gpio_fan_driver); + +MODULE_AUTHOR("Simon Guinot "); +MODULE_DESCRIPTION("GPIO FAN driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gpio-fan"); diff --git a/drivers/hwmon/hih6130.c b/drivers/hwmon/hih6130.c new file mode 100644 index 000000000..0ae1ee1db --- /dev/null +++ b/drivers/hwmon/hih6130.c @@ -0,0 +1,267 @@ +/* Honeywell HIH-6130/HIH-6131 humidity and temperature sensor driver + * + * Copyright (C) 2012 Iain Paton + * + * heavily based on the sht21 driver + * Copyright (C) 2010 Urs Fleisch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA + * + * Data sheets available (2012-06-22) at + * http://sensing.honeywell.com/index.php?ci_id=3106&la_id=1&defId=44872 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * struct hih6130 - HIH-6130 device specific data + * @client: pointer to I2C client device + * @lock: mutex to protect measurement values + * @valid: only false before first measurement is taken + * @last_update: time of last update (jiffies) + * @temperature: cached temperature measurement value + * @humidity: cached humidity measurement value + * @write_length: length for I2C measurement request + */ +struct hih6130 { + struct i2c_client *client; + struct mutex lock; + bool valid; + unsigned long last_update; + int temperature; + int humidity; + size_t write_length; +}; + +/** + * hih6130_temp_ticks_to_millicelsius() - convert raw temperature ticks to + * milli celsius + * @ticks: temperature ticks value received from sensor + */ +static inline int hih6130_temp_ticks_to_millicelsius(int ticks) +{ + ticks = ticks >> 2; + /* + * from data sheet section 5.0 + * Formula T = ( ticks / ( 2^14 - 2 ) ) * 165 -40 + */ + return (DIV_ROUND_CLOSEST(ticks * 1650, 16382) - 400) * 100; +} + +/** + * hih6130_rh_ticks_to_per_cent_mille() - convert raw humidity ticks to + * one-thousandths of a percent relative humidity + * @ticks: humidity ticks value received from sensor + */ +static inline int hih6130_rh_ticks_to_per_cent_mille(int ticks) +{ + ticks &= ~0xC000; /* clear status bits */ + /* + * from data sheet section 4.0 + * Formula RH = ( ticks / ( 2^14 -2 ) ) * 100 + */ + return DIV_ROUND_CLOSEST(ticks * 1000, 16382) * 100; +} + +/** + * hih6130_update_measurements() - get updated measurements from device + * @dev: device + * + * Returns 0 on success, else negative errno. + */ +static int hih6130_update_measurements(struct device *dev) +{ + struct hih6130 *hih6130 = dev_get_drvdata(dev); + struct i2c_client *client = hih6130->client; + int ret = 0; + int t; + unsigned char tmp[4]; + struct i2c_msg msgs[1] = { + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = 4, + .buf = tmp, + } + }; + + mutex_lock(&hih6130->lock); + + /* + * While the measurement can be completed in ~40ms the sensor takes + * much longer to react to a change in external conditions. How quickly + * it reacts depends on airflow and other factors outwith our control. + * The datasheet specifies maximum 'Response time' for humidity at 8s + * and temperature at 30s under specified conditions. + * We therefore choose to only read the sensor at most once per second. + * This trades off pointless activity polling the sensor much faster + * than it can react against better response times in conditions more + * favourable than specified in the datasheet. + */ + if (time_after(jiffies, hih6130->last_update + HZ) || !hih6130->valid) { + + /* + * Write to slave address to request a measurement. + * According with the datasheet it should be with no data, but + * for systems with I2C bus drivers that do not allow zero + * length packets we write one dummy byte to allow sensor + * measurements on them. + */ + tmp[0] = 0; + ret = i2c_master_send(client, tmp, hih6130->write_length); + if (ret < 0) + goto out; + + /* measurement cycle time is ~36.65msec */ + msleep(40); + + ret = i2c_transfer(client->adapter, msgs, 1); + if (ret < 0) + goto out; + + if ((tmp[0] & 0xC0) != 0) { + dev_err(&client->dev, "Error while reading measurement result\n"); + ret = -EIO; + goto out; + } + + t = (tmp[0] << 8) + tmp[1]; + hih6130->humidity = hih6130_rh_ticks_to_per_cent_mille(t); + + t = (tmp[2] << 8) + tmp[3]; + hih6130->temperature = hih6130_temp_ticks_to_millicelsius(t); + + hih6130->last_update = jiffies; + hih6130->valid = true; + } +out: + mutex_unlock(&hih6130->lock); + + return ret >= 0 ? 0 : ret; +} + +/** + * hih6130_show_temperature() - show temperature measurement value in sysfs + * @dev: device + * @attr: device attribute + * @buf: sysfs buffer (PAGE_SIZE) where measurement values are written to + * + * Will be called on read access to temp1_input sysfs attribute. + * Returns number of bytes written into buffer, negative errno on error. + */ +static ssize_t hih6130_show_temperature(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hih6130 *hih6130 = dev_get_drvdata(dev); + int ret; + + ret = hih6130_update_measurements(dev); + if (ret < 0) + return ret; + return sprintf(buf, "%d\n", hih6130->temperature); +} + +/** + * hih6130_show_humidity() - show humidity measurement value in sysfs + * @dev: device + * @attr: device attribute + * @buf: sysfs buffer (PAGE_SIZE) where measurement values are written to + * + * Will be called on read access to humidity1_input sysfs attribute. + * Returns number of bytes written into buffer, negative errno on error. + */ +static ssize_t hih6130_show_humidity(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hih6130 *hih6130 = dev_get_drvdata(dev); + int ret; + + ret = hih6130_update_measurements(dev); + if (ret < 0) + return ret; + return sprintf(buf, "%d\n", hih6130->humidity); +} + +/* sysfs attributes */ +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, hih6130_show_temperature, + NULL, 0); +static SENSOR_DEVICE_ATTR(humidity1_input, S_IRUGO, hih6130_show_humidity, + NULL, 0); + +static struct attribute *hih6130_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_humidity1_input.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(hih6130); + +static int hih6130_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct hih6130 *hih6130; + struct device *hwmon_dev; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "adapter does not support true I2C\n"); + return -ENODEV; + } + + hih6130 = devm_kzalloc(dev, sizeof(*hih6130), GFP_KERNEL); + if (!hih6130) + return -ENOMEM; + + hih6130->client = client; + mutex_init(&hih6130->lock); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_QUICK)) + hih6130->write_length = 1; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + hih6130, + hih6130_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +/* Device ID table */ +static const struct i2c_device_id hih6130_id[] = { + { "hih6130", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hih6130_id); + +static struct i2c_driver hih6130_driver = { + .driver.name = "hih6130", + .probe = hih6130_probe, + .id_table = hih6130_id, +}; + +module_i2c_driver(hih6130_driver); + +MODULE_AUTHOR("Iain Paton "); +MODULE_DESCRIPTION("Honeywell HIH-6130 humidity and temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/hwmon-vid.c b/drivers/hwmon/hwmon-vid.c new file mode 100644 index 000000000..84e91286f --- /dev/null +++ b/drivers/hwmon/hwmon-vid.c @@ -0,0 +1,317 @@ +/* + * hwmon-vid.c - VID/VRM/VRD voltage conversions + * + * Copyright (c) 2004 Rudolf Marek + * + * Partly imported from i2c-vid.h of the lm_sensors project + * Copyright (c) 2002 Mark D. Studebaker + * With assistance from Trent Piepho + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include + +/* + * Common code for decoding VID pins. + * + * References: + * + * For VRM 8.4 to 9.1, "VRM x.y DC-DC Converter Design Guidelines", + * available at http://developer.intel.com/. + * + * For VRD 10.0 and up, "VRD x.y Design Guide", + * available at http://developer.intel.com/. + * + * AMD Athlon 64 and AMD Opteron Processors, AMD Publication 26094, + * http://support.amd.com/us/Processor_TechDocs/26094.PDF + * Table 74. VID Code Voltages + * This corresponds to an arbitrary VRM code of 24 in the functions below. + * These CPU models (K8 revision <= E) have 5 VID pins. See also: + * Revision Guide for AMD Athlon 64 and AMD Opteron Processors, AMD Publication 25759, + * http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/25759.pdf + * + * AMD NPT Family 0Fh Processors, AMD Publication 32559, + * http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/32559.pdf + * Table 71. VID Code Voltages + * This corresponds to an arbitrary VRM code of 25 in the functions below. + * These CPU models (K8 revision >= F) have 6 VID pins. See also: + * Revision Guide for AMD NPT Family 0Fh Processors, AMD Publication 33610, + * http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/33610.pdf + * + * The 17 specification is in fact Intel Mobile Voltage Positioning - + * (IMVP-II). You can find more information in the datasheet of Max1718 + * http://www.maxim-ic.com/quick_view2.cfm/qv_pk/2452 + * + * The 13 specification corresponds to the Intel Pentium M series. There + * doesn't seem to be any named specification for these. The conversion + * tables are detailed directly in the various Pentium M datasheets: + * http://www.intel.com/design/intarch/pentiumm/docs_pentiumm.htm + * + * The 14 specification corresponds to Intel Core series. There + * doesn't seem to be any named specification for these. The conversion + * tables are detailed directly in the various Pentium Core datasheets: + * http://www.intel.com/design/mobile/datashts/309221.htm + * + * The 110 (VRM 11) specification corresponds to Intel Conroe based series. + * http://www.intel.com/design/processor/applnots/313214.htm + */ + +/* + * vrm is the VRM/VRD document version multiplied by 10. + * val is the 4-bit or more VID code. + * Returned value is in mV to avoid floating point in the kernel. + * Some VID have some bits in uV scale, this is rounded to mV. + */ +int vid_from_reg(int val, u8 vrm) +{ + int vid; + + switch (vrm) { + + case 100: /* VRD 10.0 */ + /* compute in uV, round to mV */ + val &= 0x3f; + if ((val & 0x1f) == 0x1f) + return 0; + if ((val & 0x1f) <= 0x09 || val == 0x0a) + vid = 1087500 - (val & 0x1f) * 25000; + else + vid = 1862500 - (val & 0x1f) * 25000; + if (val & 0x20) + vid -= 12500; + return (vid + 500) / 1000; + + case 110: /* Intel Conroe */ + /* compute in uV, round to mV */ + val &= 0xff; + if (val < 0x02 || val > 0xb2) + return 0; + return (1600000 - (val - 2) * 6250 + 500) / 1000; + + case 24: /* Athlon64 & Opteron */ + val &= 0x1f; + if (val == 0x1f) + return 0; + /* fall through */ + case 25: /* AMD NPT 0Fh */ + val &= 0x3f; + return (val < 32) ? 1550 - 25 * val + : 775 - (25 * (val - 31)) / 2; + + case 26: /* AMD family 10h to 15h, serial VID */ + val &= 0x7f; + if (val >= 0x7c) + return 0; + return DIV_ROUND_CLOSEST(15500 - 125 * val, 10); + + case 91: /* VRM 9.1 */ + case 90: /* VRM 9.0 */ + val &= 0x1f; + return val == 0x1f ? 0 : + 1850 - val * 25; + + case 85: /* VRM 8.5 */ + val &= 0x1f; + return (val & 0x10 ? 25 : 0) + + ((val & 0x0f) > 0x04 ? 2050 : 1250) - + ((val & 0x0f) * 50); + + case 84: /* VRM 8.4 */ + val &= 0x0f; + /* fall through */ + case 82: /* VRM 8.2 */ + val &= 0x1f; + return val == 0x1f ? 0 : + val & 0x10 ? 5100 - (val) * 100 : + 2050 - (val) * 50; + case 17: /* Intel IMVP-II */ + val &= 0x1f; + return val & 0x10 ? 975 - (val & 0xF) * 25 : + 1750 - val * 50; + case 13: + case 131: + val &= 0x3f; + /* Exception for Eden ULV 500 MHz */ + if (vrm == 131 && val == 0x3f) + val++; + return 1708 - val * 16; + case 14: /* Intel Core */ + /* compute in uV, round to mV */ + val &= 0x7f; + return val > 0x77 ? 0 : (1500000 - (val * 12500) + 500) / 1000; + default: /* report 0 for unknown */ + if (vrm) + pr_warn("Requested unsupported VRM version (%u)\n", + (unsigned int)vrm); + return 0; + } +} +EXPORT_SYMBOL(vid_from_reg); + +/* + * After this point is the code to automatically determine which + * VRM/VRD specification should be used depending on the CPU. + */ + +struct vrm_model { + u8 vendor; + u8 family; + u8 model_from; + u8 model_to; + u8 stepping_to; + u8 vrm_type; +}; + +#define ANY 0xFF + +#ifdef CONFIG_X86 + +/* + * The stepping_to parameter is highest acceptable stepping for current line. + * The model match must be exact for 4-bit values. For model values 0x10 + * and above (extended model), all models below the parameter will match. + */ + +static struct vrm_model vrm_models[] = { + {X86_VENDOR_AMD, 0x6, 0x0, ANY, ANY, 90}, /* Athlon Duron etc */ + {X86_VENDOR_AMD, 0xF, 0x0, 0x3F, ANY, 24}, /* Athlon 64, Opteron */ + /* + * In theory, all NPT family 0Fh processors have 6 VID pins and should + * thus use vrm 25, however in practice not all mainboards route the + * 6th VID pin because it is never needed. So we use the 5 VID pin + * variant (vrm 24) for the models which exist today. + */ + {X86_VENDOR_AMD, 0xF, 0x40, 0x7F, ANY, 24}, /* NPT family 0Fh */ + {X86_VENDOR_AMD, 0xF, 0x80, ANY, ANY, 25}, /* future fam. 0Fh */ + {X86_VENDOR_AMD, 0x10, 0x0, ANY, ANY, 25}, /* NPT family 10h */ + {X86_VENDOR_AMD, 0x11, 0x0, ANY, ANY, 26}, /* family 11h */ + {X86_VENDOR_AMD, 0x12, 0x0, ANY, ANY, 26}, /* family 12h */ + {X86_VENDOR_AMD, 0x14, 0x0, ANY, ANY, 26}, /* family 14h */ + {X86_VENDOR_AMD, 0x15, 0x0, ANY, ANY, 26}, /* family 15h */ + + {X86_VENDOR_INTEL, 0x6, 0x0, 0x6, ANY, 82}, /* Pentium Pro, + * Pentium II, Xeon, + * Mobile Pentium, + * Celeron */ + {X86_VENDOR_INTEL, 0x6, 0x7, 0x7, ANY, 84}, /* Pentium III, Xeon */ + {X86_VENDOR_INTEL, 0x6, 0x8, 0x8, ANY, 82}, /* Pentium III, Xeon */ + {X86_VENDOR_INTEL, 0x6, 0x9, 0x9, ANY, 13}, /* Pentium M (130 nm) */ + {X86_VENDOR_INTEL, 0x6, 0xA, 0xA, ANY, 82}, /* Pentium III Xeon */ + {X86_VENDOR_INTEL, 0x6, 0xB, 0xB, ANY, 85}, /* Tualatin */ + {X86_VENDOR_INTEL, 0x6, 0xD, 0xD, ANY, 13}, /* Pentium M (90 nm) */ + {X86_VENDOR_INTEL, 0x6, 0xE, 0xE, ANY, 14}, /* Intel Core (65 nm) */ + {X86_VENDOR_INTEL, 0x6, 0xF, ANY, ANY, 110}, /* Intel Conroe and + * later */ + {X86_VENDOR_INTEL, 0xF, 0x0, 0x0, ANY, 90}, /* P4 */ + {X86_VENDOR_INTEL, 0xF, 0x1, 0x1, ANY, 90}, /* P4 Willamette */ + {X86_VENDOR_INTEL, 0xF, 0x2, 0x2, ANY, 90}, /* P4 Northwood */ + {X86_VENDOR_INTEL, 0xF, 0x3, ANY, ANY, 100}, /* Prescott and above + * assume VRD 10 */ + + {X86_VENDOR_CENTAUR, 0x6, 0x7, 0x7, ANY, 85}, /* Eden ESP/Ezra */ + {X86_VENDOR_CENTAUR, 0x6, 0x8, 0x8, 0x7, 85}, /* Ezra T */ + {X86_VENDOR_CENTAUR, 0x6, 0x9, 0x9, 0x7, 85}, /* Nehemiah */ + {X86_VENDOR_CENTAUR, 0x6, 0x9, 0x9, ANY, 17}, /* C3-M, Eden-N */ + {X86_VENDOR_CENTAUR, 0x6, 0xA, 0xA, 0x7, 0}, /* No information */ + {X86_VENDOR_CENTAUR, 0x6, 0xA, 0xA, ANY, 13}, /* C7-M, C7, + * Eden (Esther) */ + {X86_VENDOR_CENTAUR, 0x6, 0xD, 0xD, ANY, 134}, /* C7-D, C7-M, C7, + * Eden (Esther) */ +}; + +/* + * Special case for VIA model D: there are two different possible + * VID tables, so we have to figure out first, which one must be + * used. This resolves temporary drm value 134 to 14 (Intel Core + * 7-bit VID), 13 (Pentium M 6-bit VID) or 131 (Pentium M 6-bit VID + * + quirk for Eden ULV 500 MHz). + * Note: something similar might be needed for model A, I'm not sure. + */ +static u8 get_via_model_d_vrm(void) +{ + unsigned int vid, brand, __maybe_unused dummy; + static const char *brands[4] = { + "C7-M", "C7", "Eden", "C7-D" + }; + + rdmsr(0x198, dummy, vid); + vid &= 0xff; + + rdmsr(0x1154, brand, dummy); + brand = ((brand >> 4) ^ (brand >> 2)) & 0x03; + + if (vid > 0x3f) { + pr_info("Using %d-bit VID table for VIA %s CPU\n", + 7, brands[brand]); + return 14; + } else { + pr_info("Using %d-bit VID table for VIA %s CPU\n", + 6, brands[brand]); + /* Enable quirk for Eden */ + return brand == 2 ? 131 : 13; + } +} + +static u8 find_vrm(u8 family, u8 model, u8 stepping, u8 vendor) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(vrm_models); i++) { + if (vendor == vrm_models[i].vendor && + family == vrm_models[i].family && + model >= vrm_models[i].model_from && + model <= vrm_models[i].model_to && + stepping <= vrm_models[i].stepping_to) + return vrm_models[i].vrm_type; + } + + return 0; +} + +u8 vid_which_vrm(void) +{ + struct cpuinfo_x86 *c = &cpu_data(0); + u8 vrm_ret; + + if (c->x86 < 6) /* Any CPU with family lower than 6 */ + return 0; /* doesn't have VID */ + + vrm_ret = find_vrm(c->x86, c->x86_model, c->x86_stepping, c->x86_vendor); + if (vrm_ret == 134) + vrm_ret = get_via_model_d_vrm(); + if (vrm_ret == 0) + pr_info("Unknown VRM version of your x86 CPU\n"); + return vrm_ret; +} + +/* and now for something completely different for the non-x86 world */ +#else +u8 vid_which_vrm(void) +{ + pr_info("Unknown VRM version of your CPU\n"); + return 0; +} +#endif +EXPORT_SYMBOL(vid_which_vrm); + +MODULE_AUTHOR("Rudolf Marek "); + +MODULE_DESCRIPTION("hwmon-vid driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c new file mode 100644 index 000000000..c4051a3e6 --- /dev/null +++ b/drivers/hwmon/hwmon.c @@ -0,0 +1,922 @@ +/* + * hwmon.c - part of lm_sensors, Linux kernel modules for hardware monitoring + * + * This file defines the sysfs class "hwmon", for use by sensors drivers. + * + * Copyright (C) 2005 Mark M. Hoffman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HWMON_ID_PREFIX "hwmon" +#define HWMON_ID_FORMAT HWMON_ID_PREFIX "%d" + +struct hwmon_device { + const char *name; + struct device dev; + const struct hwmon_chip_info *chip; + + struct attribute_group group; + const struct attribute_group **groups; +}; + +#define to_hwmon_device(d) container_of(d, struct hwmon_device, dev) + +#define MAX_SYSFS_ATTR_NAME_LENGTH 32 + +struct hwmon_device_attribute { + struct device_attribute dev_attr; + const struct hwmon_ops *ops; + enum hwmon_sensor_types type; + u32 attr; + int index; + char name[MAX_SYSFS_ATTR_NAME_LENGTH]; +}; + +#define to_hwmon_attr(d) \ + container_of(d, struct hwmon_device_attribute, dev_attr) +#define to_dev_attr(a) container_of(a, struct device_attribute, attr) + +/* + * Thermal zone information + * In addition to the reference to the hwmon device, + * also provides the sensor index. + */ +struct hwmon_thermal_data { + struct device *dev; /* Reference to hwmon device */ + int index; /* sensor index */ +}; + +static ssize_t +name_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", to_hwmon_device(dev)->name); +} +static DEVICE_ATTR_RO(name); + +static struct attribute *hwmon_dev_attrs[] = { + &dev_attr_name.attr, + NULL +}; + +static umode_t hwmon_dev_name_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + + if (to_hwmon_device(dev)->name == NULL) + return 0; + + return attr->mode; +} + +static const struct attribute_group hwmon_dev_attr_group = { + .attrs = hwmon_dev_attrs, + .is_visible = hwmon_dev_name_is_visible, +}; + +static const struct attribute_group *hwmon_dev_attr_groups[] = { + &hwmon_dev_attr_group, + NULL +}; + +static void hwmon_free_attrs(struct attribute **attrs) +{ + int i; + + for (i = 0; attrs[i]; i++) { + struct device_attribute *dattr = to_dev_attr(attrs[i]); + struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr); + + kfree(hattr); + } + kfree(attrs); +} + +static void hwmon_dev_release(struct device *dev) +{ + struct hwmon_device *hwdev = to_hwmon_device(dev); + + if (hwdev->group.attrs) + hwmon_free_attrs(hwdev->group.attrs); + kfree(hwdev->groups); + kfree(hwdev); +} + +static struct class hwmon_class = { + .name = "hwmon", + .owner = THIS_MODULE, + .dev_groups = hwmon_dev_attr_groups, + .dev_release = hwmon_dev_release, +}; + +static DEFINE_IDA(hwmon_ida); + +/* Thermal zone handling */ + +/* + * The complex conditional is necessary to avoid a cyclic dependency + * between hwmon and thermal_sys modules. + */ +#if IS_REACHABLE(CONFIG_THERMAL) && defined(CONFIG_THERMAL_OF) && \ + (!defined(CONFIG_THERMAL_HWMON) || \ + !(defined(MODULE) && IS_MODULE(CONFIG_THERMAL))) +static int hwmon_thermal_get_temp(void *data, int *temp) +{ + struct hwmon_thermal_data *tdata = data; + struct hwmon_device *hwdev = to_hwmon_device(tdata->dev); + int ret; + long t; + + ret = hwdev->chip->ops->read(tdata->dev, hwmon_temp, hwmon_temp_input, + tdata->index, &t); + if (ret < 0) + return ret; + + *temp = t; + + return 0; +} + +static const struct thermal_zone_of_device_ops hwmon_thermal_ops = { + .get_temp = hwmon_thermal_get_temp, +}; + +static int hwmon_thermal_add_sensor(struct device *dev, int index) +{ + struct hwmon_thermal_data *tdata; + struct thermal_zone_device *tzd; + + tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL); + if (!tdata) + return -ENOMEM; + + tdata->dev = dev; + tdata->index = index; + + tzd = devm_thermal_zone_of_sensor_register(dev, index, tdata, + &hwmon_thermal_ops); + /* + * If CONFIG_THERMAL_OF is disabled, this returns -ENODEV, + * so ignore that error but forward any other error. + */ + if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV)) + return PTR_ERR(tzd); + + return 0; +} +#else +static int hwmon_thermal_add_sensor(struct device *dev, int index) +{ + return 0; +} +#endif /* IS_REACHABLE(CONFIG_THERMAL) && ... */ + +/* sysfs attribute management */ + +static ssize_t hwmon_attr_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + long val; + int ret; + + ret = hattr->ops->read(dev, hattr->type, hattr->attr, hattr->index, + &val); + if (ret < 0) + return ret; + + return sprintf(buf, "%ld\n", val); +} + +static ssize_t hwmon_attr_show_string(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + const char *s; + int ret; + + ret = hattr->ops->read_string(dev, hattr->type, hattr->attr, + hattr->index, &s); + if (ret < 0) + return ret; + + return sprintf(buf, "%s\n", s); +} + +static ssize_t hwmon_attr_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + long val; + int ret; + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + ret = hattr->ops->write(dev, hattr->type, hattr->attr, hattr->index, + val); + if (ret < 0) + return ret; + + return count; +} + +static int hwmon_attr_base(enum hwmon_sensor_types type) +{ + if (type == hwmon_in) + return 0; + return 1; +} + +static bool is_string_attr(enum hwmon_sensor_types type, u32 attr) +{ + return (type == hwmon_temp && attr == hwmon_temp_label) || + (type == hwmon_in && attr == hwmon_in_label) || + (type == hwmon_curr && attr == hwmon_curr_label) || + (type == hwmon_power && attr == hwmon_power_label) || + (type == hwmon_energy && attr == hwmon_energy_label) || + (type == hwmon_humidity && attr == hwmon_humidity_label) || + (type == hwmon_fan && attr == hwmon_fan_label); +} + +static struct attribute *hwmon_genattr(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, + int index, + const char *template, + const struct hwmon_ops *ops) +{ + struct hwmon_device_attribute *hattr; + struct device_attribute *dattr; + struct attribute *a; + umode_t mode; + char *name; + bool is_string = is_string_attr(type, attr); + + /* The attribute is invisible if there is no template string */ + if (!template) + return ERR_PTR(-ENOENT); + + mode = ops->is_visible(drvdata, type, attr, index); + if (!mode) + return ERR_PTR(-ENOENT); + + if ((mode & S_IRUGO) && ((is_string && !ops->read_string) || + (!is_string && !ops->read))) + return ERR_PTR(-EINVAL); + if ((mode & S_IWUGO) && !ops->write) + return ERR_PTR(-EINVAL); + + hattr = kzalloc(sizeof(*hattr), GFP_KERNEL); + if (!hattr) + return ERR_PTR(-ENOMEM); + + if (type == hwmon_chip) { + name = (char *)template; + } else { + scnprintf(hattr->name, sizeof(hattr->name), template, + index + hwmon_attr_base(type)); + name = hattr->name; + } + + hattr->type = type; + hattr->attr = attr; + hattr->index = index; + hattr->ops = ops; + + dattr = &hattr->dev_attr; + dattr->show = is_string ? hwmon_attr_show_string : hwmon_attr_show; + dattr->store = hwmon_attr_store; + + a = &dattr->attr; + sysfs_attr_init(a); + a->name = name; + a->mode = mode; + + return a; +} + +/* + * Chip attributes are not attribute templates but actual sysfs attributes. + * See hwmon_genattr() for special handling. + */ +static const char * const hwmon_chip_attrs[] = { + [hwmon_chip_temp_reset_history] = "temp_reset_history", + [hwmon_chip_in_reset_history] = "in_reset_history", + [hwmon_chip_curr_reset_history] = "curr_reset_history", + [hwmon_chip_power_reset_history] = "power_reset_history", + [hwmon_chip_update_interval] = "update_interval", + [hwmon_chip_alarms] = "alarms", +}; + +static const char * const hwmon_temp_attr_templates[] = { + [hwmon_temp_input] = "temp%d_input", + [hwmon_temp_type] = "temp%d_type", + [hwmon_temp_lcrit] = "temp%d_lcrit", + [hwmon_temp_lcrit_hyst] = "temp%d_lcrit_hyst", + [hwmon_temp_min] = "temp%d_min", + [hwmon_temp_min_hyst] = "temp%d_min_hyst", + [hwmon_temp_max] = "temp%d_max", + [hwmon_temp_max_hyst] = "temp%d_max_hyst", + [hwmon_temp_crit] = "temp%d_crit", + [hwmon_temp_crit_hyst] = "temp%d_crit_hyst", + [hwmon_temp_emergency] = "temp%d_emergency", + [hwmon_temp_emergency_hyst] = "temp%d_emergency_hyst", + [hwmon_temp_alarm] = "temp%d_alarm", + [hwmon_temp_lcrit_alarm] = "temp%d_lcrit_alarm", + [hwmon_temp_min_alarm] = "temp%d_min_alarm", + [hwmon_temp_max_alarm] = "temp%d_max_alarm", + [hwmon_temp_crit_alarm] = "temp%d_crit_alarm", + [hwmon_temp_emergency_alarm] = "temp%d_emergency_alarm", + [hwmon_temp_fault] = "temp%d_fault", + [hwmon_temp_offset] = "temp%d_offset", + [hwmon_temp_label] = "temp%d_label", + [hwmon_temp_lowest] = "temp%d_lowest", + [hwmon_temp_highest] = "temp%d_highest", + [hwmon_temp_reset_history] = "temp%d_reset_history", +}; + +static const char * const hwmon_in_attr_templates[] = { + [hwmon_in_input] = "in%d_input", + [hwmon_in_min] = "in%d_min", + [hwmon_in_max] = "in%d_max", + [hwmon_in_lcrit] = "in%d_lcrit", + [hwmon_in_crit] = "in%d_crit", + [hwmon_in_average] = "in%d_average", + [hwmon_in_lowest] = "in%d_lowest", + [hwmon_in_highest] = "in%d_highest", + [hwmon_in_reset_history] = "in%d_reset_history", + [hwmon_in_label] = "in%d_label", + [hwmon_in_alarm] = "in%d_alarm", + [hwmon_in_min_alarm] = "in%d_min_alarm", + [hwmon_in_max_alarm] = "in%d_max_alarm", + [hwmon_in_lcrit_alarm] = "in%d_lcrit_alarm", + [hwmon_in_crit_alarm] = "in%d_crit_alarm", +}; + +static const char * const hwmon_curr_attr_templates[] = { + [hwmon_curr_input] = "curr%d_input", + [hwmon_curr_min] = "curr%d_min", + [hwmon_curr_max] = "curr%d_max", + [hwmon_curr_lcrit] = "curr%d_lcrit", + [hwmon_curr_crit] = "curr%d_crit", + [hwmon_curr_average] = "curr%d_average", + [hwmon_curr_lowest] = "curr%d_lowest", + [hwmon_curr_highest] = "curr%d_highest", + [hwmon_curr_reset_history] = "curr%d_reset_history", + [hwmon_curr_label] = "curr%d_label", + [hwmon_curr_alarm] = "curr%d_alarm", + [hwmon_curr_min_alarm] = "curr%d_min_alarm", + [hwmon_curr_max_alarm] = "curr%d_max_alarm", + [hwmon_curr_lcrit_alarm] = "curr%d_lcrit_alarm", + [hwmon_curr_crit_alarm] = "curr%d_crit_alarm", +}; + +static const char * const hwmon_power_attr_templates[] = { + [hwmon_power_average] = "power%d_average", + [hwmon_power_average_interval] = "power%d_average_interval", + [hwmon_power_average_interval_max] = "power%d_interval_max", + [hwmon_power_average_interval_min] = "power%d_interval_min", + [hwmon_power_average_highest] = "power%d_average_highest", + [hwmon_power_average_lowest] = "power%d_average_lowest", + [hwmon_power_average_max] = "power%d_average_max", + [hwmon_power_average_min] = "power%d_average_min", + [hwmon_power_input] = "power%d_input", + [hwmon_power_input_highest] = "power%d_input_highest", + [hwmon_power_input_lowest] = "power%d_input_lowest", + [hwmon_power_reset_history] = "power%d_reset_history", + [hwmon_power_accuracy] = "power%d_accuracy", + [hwmon_power_cap] = "power%d_cap", + [hwmon_power_cap_hyst] = "power%d_cap_hyst", + [hwmon_power_cap_max] = "power%d_cap_max", + [hwmon_power_cap_min] = "power%d_cap_min", + [hwmon_power_min] = "power%d_min", + [hwmon_power_max] = "power%d_max", + [hwmon_power_lcrit] = "power%d_lcrit", + [hwmon_power_crit] = "power%d_crit", + [hwmon_power_label] = "power%d_label", + [hwmon_power_alarm] = "power%d_alarm", + [hwmon_power_cap_alarm] = "power%d_cap_alarm", + [hwmon_power_min_alarm] = "power%d_min_alarm", + [hwmon_power_max_alarm] = "power%d_max_alarm", + [hwmon_power_lcrit_alarm] = "power%d_lcrit_alarm", + [hwmon_power_crit_alarm] = "power%d_crit_alarm", +}; + +static const char * const hwmon_energy_attr_templates[] = { + [hwmon_energy_input] = "energy%d_input", + [hwmon_energy_label] = "energy%d_label", +}; + +static const char * const hwmon_humidity_attr_templates[] = { + [hwmon_humidity_input] = "humidity%d_input", + [hwmon_humidity_label] = "humidity%d_label", + [hwmon_humidity_min] = "humidity%d_min", + [hwmon_humidity_min_hyst] = "humidity%d_min_hyst", + [hwmon_humidity_max] = "humidity%d_max", + [hwmon_humidity_max_hyst] = "humidity%d_max_hyst", + [hwmon_humidity_alarm] = "humidity%d_alarm", + [hwmon_humidity_fault] = "humidity%d_fault", +}; + +static const char * const hwmon_fan_attr_templates[] = { + [hwmon_fan_input] = "fan%d_input", + [hwmon_fan_label] = "fan%d_label", + [hwmon_fan_min] = "fan%d_min", + [hwmon_fan_max] = "fan%d_max", + [hwmon_fan_div] = "fan%d_div", + [hwmon_fan_pulses] = "fan%d_pulses", + [hwmon_fan_target] = "fan%d_target", + [hwmon_fan_alarm] = "fan%d_alarm", + [hwmon_fan_min_alarm] = "fan%d_min_alarm", + [hwmon_fan_max_alarm] = "fan%d_max_alarm", + [hwmon_fan_fault] = "fan%d_fault", +}; + +static const char * const hwmon_pwm_attr_templates[] = { + [hwmon_pwm_input] = "pwm%d", + [hwmon_pwm_enable] = "pwm%d_enable", + [hwmon_pwm_mode] = "pwm%d_mode", + [hwmon_pwm_freq] = "pwm%d_freq", +}; + +static const char * const *__templates[] = { + [hwmon_chip] = hwmon_chip_attrs, + [hwmon_temp] = hwmon_temp_attr_templates, + [hwmon_in] = hwmon_in_attr_templates, + [hwmon_curr] = hwmon_curr_attr_templates, + [hwmon_power] = hwmon_power_attr_templates, + [hwmon_energy] = hwmon_energy_attr_templates, + [hwmon_humidity] = hwmon_humidity_attr_templates, + [hwmon_fan] = hwmon_fan_attr_templates, + [hwmon_pwm] = hwmon_pwm_attr_templates, +}; + +static const int __templates_size[] = { + [hwmon_chip] = ARRAY_SIZE(hwmon_chip_attrs), + [hwmon_temp] = ARRAY_SIZE(hwmon_temp_attr_templates), + [hwmon_in] = ARRAY_SIZE(hwmon_in_attr_templates), + [hwmon_curr] = ARRAY_SIZE(hwmon_curr_attr_templates), + [hwmon_power] = ARRAY_SIZE(hwmon_power_attr_templates), + [hwmon_energy] = ARRAY_SIZE(hwmon_energy_attr_templates), + [hwmon_humidity] = ARRAY_SIZE(hwmon_humidity_attr_templates), + [hwmon_fan] = ARRAY_SIZE(hwmon_fan_attr_templates), + [hwmon_pwm] = ARRAY_SIZE(hwmon_pwm_attr_templates), +}; + +static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info) +{ + int i, n; + + for (i = n = 0; info->config[i]; i++) + n += hweight32(info->config[i]); + + return n; +} + +static int hwmon_genattrs(const void *drvdata, + struct attribute **attrs, + const struct hwmon_ops *ops, + const struct hwmon_channel_info *info) +{ + const char * const *templates; + int template_size; + int i, aindex = 0; + + if (info->type >= ARRAY_SIZE(__templates)) + return -EINVAL; + + templates = __templates[info->type]; + template_size = __templates_size[info->type]; + + for (i = 0; info->config[i]; i++) { + u32 attr_mask = info->config[i]; + u32 attr; + + while (attr_mask) { + struct attribute *a; + + attr = __ffs(attr_mask); + attr_mask &= ~BIT(attr); + if (attr >= template_size) + return -EINVAL; + a = hwmon_genattr(drvdata, info->type, attr, i, + templates[attr], ops); + if (IS_ERR(a)) { + if (PTR_ERR(a) != -ENOENT) + return PTR_ERR(a); + continue; + } + attrs[aindex++] = a; + } + } + return aindex; +} + +static struct attribute ** +__hwmon_create_attrs(const void *drvdata, const struct hwmon_chip_info *chip) +{ + int ret, i, aindex = 0, nattrs = 0; + struct attribute **attrs; + + for (i = 0; chip->info[i]; i++) + nattrs += hwmon_num_channel_attrs(chip->info[i]); + + if (nattrs == 0) + return ERR_PTR(-EINVAL); + + attrs = kcalloc(nattrs + 1, sizeof(*attrs), GFP_KERNEL); + if (!attrs) + return ERR_PTR(-ENOMEM); + + for (i = 0; chip->info[i]; i++) { + ret = hwmon_genattrs(drvdata, &attrs[aindex], chip->ops, + chip->info[i]); + if (ret < 0) { + hwmon_free_attrs(attrs); + return ERR_PTR(ret); + } + aindex += ret; + } + + return attrs; +} + +static struct device * +__hwmon_device_register(struct device *dev, const char *name, void *drvdata, + const struct hwmon_chip_info *chip, + const struct attribute_group **groups) +{ + struct hwmon_device *hwdev; + struct device *hdev; + int i, j, err, id; + + /* Complain about invalid characters in hwmon name attribute */ + if (name && (!strlen(name) || strpbrk(name, "-* \t\n"))) + dev_warn(dev, + "hwmon: '%s' is not a valid name attribute, please fix\n", + name); + + id = ida_simple_get(&hwmon_ida, 0, 0, GFP_KERNEL); + if (id < 0) + return ERR_PTR(id); + + hwdev = kzalloc(sizeof(*hwdev), GFP_KERNEL); + if (hwdev == NULL) { + err = -ENOMEM; + goto ida_remove; + } + + hdev = &hwdev->dev; + + if (chip) { + struct attribute **attrs; + int ngroups = 2; /* terminating NULL plus &hwdev->groups */ + + if (groups) + for (i = 0; groups[i]; i++) + ngroups++; + + hwdev->groups = kcalloc(ngroups, sizeof(*groups), GFP_KERNEL); + if (!hwdev->groups) { + err = -ENOMEM; + goto free_hwmon; + } + + attrs = __hwmon_create_attrs(drvdata, chip); + if (IS_ERR(attrs)) { + err = PTR_ERR(attrs); + goto free_hwmon; + } + + hwdev->group.attrs = attrs; + ngroups = 0; + hwdev->groups[ngroups++] = &hwdev->group; + + if (groups) { + for (i = 0; groups[i]; i++) + hwdev->groups[ngroups++] = groups[i]; + } + + hdev->groups = hwdev->groups; + } else { + hdev->groups = groups; + } + + hwdev->name = name; + hdev->class = &hwmon_class; + hdev->parent = dev; + hdev->of_node = dev ? dev->of_node : NULL; + hwdev->chip = chip; + dev_set_drvdata(hdev, drvdata); + dev_set_name(hdev, HWMON_ID_FORMAT, id); + err = device_register(hdev); + if (err) { + put_device(hdev); + goto ida_remove; + } + + if (dev && dev->of_node && chip && chip->ops->read && + chip->info[0]->type == hwmon_chip && + (chip->info[0]->config[0] & HWMON_C_REGISTER_TZ)) { + const struct hwmon_channel_info **info = chip->info; + + for (i = 1; info[i]; i++) { + if (info[i]->type != hwmon_temp) + continue; + + for (j = 0; info[i]->config[j]; j++) { + if (!chip->ops->is_visible(drvdata, hwmon_temp, + hwmon_temp_input, j)) + continue; + if (info[i]->config[j] & HWMON_T_INPUT) { + err = hwmon_thermal_add_sensor(hdev, j); + if (err) { + device_unregister(hdev); + goto ida_remove; + } + } + } + } + } + + return hdev; + +free_hwmon: + hwmon_dev_release(hdev); +ida_remove: + ida_simple_remove(&hwmon_ida, id); + return ERR_PTR(err); +} + +/** + * hwmon_device_register_with_groups - register w/ hwmon + * @dev: the parent device + * @name: hwmon name attribute + * @drvdata: driver data to attach to created device + * @groups: List of attribute groups to create + * + * hwmon_device_unregister() must be called when the device is no + * longer needed. + * + * Returns the pointer to the new device. + */ +struct device * +hwmon_device_register_with_groups(struct device *dev, const char *name, + void *drvdata, + const struct attribute_group **groups) +{ + if (!name) + return ERR_PTR(-EINVAL); + + return __hwmon_device_register(dev, name, drvdata, NULL, groups); +} +EXPORT_SYMBOL_GPL(hwmon_device_register_with_groups); + +/** + * hwmon_device_register_with_info - register w/ hwmon + * @dev: the parent device + * @name: hwmon name attribute + * @drvdata: driver data to attach to created device + * @chip: pointer to hwmon chip information + * @extra_groups: pointer to list of additional non-standard attribute groups + * + * hwmon_device_unregister() must be called when the device is no + * longer needed. + * + * Returns the pointer to the new device. + */ +struct device * +hwmon_device_register_with_info(struct device *dev, const char *name, + void *drvdata, + const struct hwmon_chip_info *chip, + const struct attribute_group **extra_groups) +{ + if (!name) + return ERR_PTR(-EINVAL); + + if (chip && (!chip->ops || !chip->ops->is_visible || !chip->info)) + return ERR_PTR(-EINVAL); + + if (chip && !dev) + return ERR_PTR(-EINVAL); + + return __hwmon_device_register(dev, name, drvdata, chip, extra_groups); +} +EXPORT_SYMBOL_GPL(hwmon_device_register_with_info); + +/** + * hwmon_device_register - register w/ hwmon + * @dev: the device to register + * + * hwmon_device_unregister() must be called when the device is no + * longer needed. + * + * Returns the pointer to the new device. + */ +struct device *hwmon_device_register(struct device *dev) +{ + dev_warn(dev, + "hwmon_device_register() is deprecated. Please convert the driver to use hwmon_device_register_with_info().\n"); + + return __hwmon_device_register(dev, NULL, NULL, NULL, NULL); +} +EXPORT_SYMBOL_GPL(hwmon_device_register); + +/** + * hwmon_device_unregister - removes the previously registered class device + * + * @dev: the class device to destroy + */ +void hwmon_device_unregister(struct device *dev) +{ + int id; + + if (likely(sscanf(dev_name(dev), HWMON_ID_FORMAT, &id) == 1)) { + device_unregister(dev); + ida_simple_remove(&hwmon_ida, id); + } else + dev_dbg(dev->parent, + "hwmon_device_unregister() failed: bad class ID!\n"); +} +EXPORT_SYMBOL_GPL(hwmon_device_unregister); + +static void devm_hwmon_release(struct device *dev, void *res) +{ + struct device *hwdev = *(struct device **)res; + + hwmon_device_unregister(hwdev); +} + +/** + * devm_hwmon_device_register_with_groups - register w/ hwmon + * @dev: the parent device + * @name: hwmon name attribute + * @drvdata: driver data to attach to created device + * @groups: List of attribute groups to create + * + * Returns the pointer to the new device. The new device is automatically + * unregistered with the parent device. + */ +struct device * +devm_hwmon_device_register_with_groups(struct device *dev, const char *name, + void *drvdata, + const struct attribute_group **groups) +{ + struct device **ptr, *hwdev; + + if (!dev) + return ERR_PTR(-EINVAL); + + ptr = devres_alloc(devm_hwmon_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + hwdev = hwmon_device_register_with_groups(dev, name, drvdata, groups); + if (IS_ERR(hwdev)) + goto error; + + *ptr = hwdev; + devres_add(dev, ptr); + return hwdev; + +error: + devres_free(ptr); + return hwdev; +} +EXPORT_SYMBOL_GPL(devm_hwmon_device_register_with_groups); + +/** + * devm_hwmon_device_register_with_info - register w/ hwmon + * @dev: the parent device + * @name: hwmon name attribute + * @drvdata: driver data to attach to created device + * @chip: pointer to hwmon chip information + * @groups: pointer to list of driver specific attribute groups + * + * Returns the pointer to the new device. The new device is automatically + * unregistered with the parent device. + */ +struct device * +devm_hwmon_device_register_with_info(struct device *dev, const char *name, + void *drvdata, + const struct hwmon_chip_info *chip, + const struct attribute_group **groups) +{ + struct device **ptr, *hwdev; + + if (!dev) + return ERR_PTR(-EINVAL); + + ptr = devres_alloc(devm_hwmon_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + hwdev = hwmon_device_register_with_info(dev, name, drvdata, chip, + groups); + if (IS_ERR(hwdev)) + goto error; + + *ptr = hwdev; + devres_add(dev, ptr); + + return hwdev; + +error: + devres_free(ptr); + return hwdev; +} +EXPORT_SYMBOL_GPL(devm_hwmon_device_register_with_info); + +static int devm_hwmon_match(struct device *dev, void *res, void *data) +{ + struct device **hwdev = res; + + return *hwdev == data; +} + +/** + * devm_hwmon_device_unregister - removes a previously registered hwmon device + * + * @dev: the parent device of the device to unregister + */ +void devm_hwmon_device_unregister(struct device *dev) +{ + WARN_ON(devres_release(dev, devm_hwmon_release, devm_hwmon_match, dev)); +} +EXPORT_SYMBOL_GPL(devm_hwmon_device_unregister); + +static void __init hwmon_pci_quirks(void) +{ +#if defined CONFIG_X86 && defined CONFIG_PCI + struct pci_dev *sb; + u16 base; + u8 enable; + + /* Open access to 0x295-0x296 on MSI MS-7031 */ + sb = pci_get_device(PCI_VENDOR_ID_ATI, 0x436c, NULL); + if (sb) { + if (sb->subsystem_vendor == 0x1462 && /* MSI */ + sb->subsystem_device == 0x0031) { /* MS-7031 */ + pci_read_config_byte(sb, 0x48, &enable); + pci_read_config_word(sb, 0x64, &base); + + if (base == 0 && !(enable & BIT(2))) { + dev_info(&sb->dev, + "Opening wide generic port at 0x295\n"); + pci_write_config_word(sb, 0x64, 0x295); + pci_write_config_byte(sb, 0x48, + enable | BIT(2)); + } + } + pci_dev_put(sb); + } +#endif +} + +static int __init hwmon_init(void) +{ + int err; + + hwmon_pci_quirks(); + + err = class_register(&hwmon_class); + if (err) { + pr_err("couldn't register hwmon sysfs class\n"); + return err; + } + return 0; +} + +static void __exit hwmon_exit(void) +{ + class_unregister(&hwmon_class); +} + +subsys_initcall(hwmon_init); +module_exit(hwmon_exit); + +MODULE_AUTHOR("Mark M. Hoffman "); +MODULE_DESCRIPTION("hardware monitoring sysfs/class support"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/hwmon/i5500_temp.c b/drivers/hwmon/i5500_temp.c new file mode 100644 index 000000000..400e0675a --- /dev/null +++ b/drivers/hwmon/i5500_temp.c @@ -0,0 +1,149 @@ +/* + * i5500_temp - Driver for Intel 5500/5520/X58 chipset thermal sensor + * + * Copyright (C) 2012, 2014 Jean Delvare + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register definitions from datasheet */ +#define REG_TSTHRCATA 0xE2 +#define REG_TSCTRL 0xE8 +#define REG_TSTHRRPEX 0xEB +#define REG_TSTHRLO 0xEC +#define REG_TSTHRHI 0xEE +#define REG_CTHINT 0xF0 +#define REG_TSFSC 0xF3 +#define REG_CTSTS 0xF4 +#define REG_TSTHRRQPI 0xF5 +#define REG_CTCTRL 0xF7 +#define REG_TSTIMER 0xF8 + +/* + * Sysfs stuff + */ + +/* Sensor resolution : 0.5 degree C */ +static ssize_t temp1_input_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct pci_dev *pdev = to_pci_dev(dev->parent); + long temp; + u16 tsthrhi; + s8 tsfsc; + + pci_read_config_word(pdev, REG_TSTHRHI, &tsthrhi); + pci_read_config_byte(pdev, REG_TSFSC, &tsfsc); + temp = ((long)tsthrhi - tsfsc) * 500; + + return sprintf(buf, "%ld\n", temp); +} + +static ssize_t show_thresh(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct pci_dev *pdev = to_pci_dev(dev->parent); + int reg = to_sensor_dev_attr(devattr)->index; + long temp; + u16 tsthr; + + pci_read_config_word(pdev, reg, &tsthr); + temp = tsthr * 500; + + return sprintf(buf, "%ld\n", temp); +} + +static ssize_t show_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct pci_dev *pdev = to_pci_dev(dev->parent); + int nr = to_sensor_dev_attr(devattr)->index; + u8 ctsts; + + pci_read_config_byte(pdev, REG_CTSTS, &ctsts); + return sprintf(buf, "%u\n", (unsigned int)ctsts & (1 << nr)); +} + +static DEVICE_ATTR_RO(temp1_input); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_thresh, NULL, 0xE2); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IRUGO, show_thresh, NULL, 0xEC); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_thresh, NULL, 0xEE); +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 1); + +static struct attribute *i5500_temp_attrs[] = { + &dev_attr_temp1_input.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(i5500_temp); + +static const struct pci_device_id i5500_temp_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x3438) }, + { 0 }, +}; + +MODULE_DEVICE_TABLE(pci, i5500_temp_ids); + +static int i5500_temp_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int err; + struct device *hwmon_dev; + u32 tstimer; + s8 tsfsc; + + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "Failed to enable device\n"); + return err; + } + + pci_read_config_byte(pdev, REG_TSFSC, &tsfsc); + pci_read_config_dword(pdev, REG_TSTIMER, &tstimer); + if (tsfsc == 0x7F && tstimer == 0x07D30D40) { + dev_notice(&pdev->dev, "Sensor seems to be disabled\n"); + return -ENODEV; + } + + hwmon_dev = devm_hwmon_device_register_with_groups(&pdev->dev, + "intel5500", NULL, + i5500_temp_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct pci_driver i5500_temp_driver = { + .name = "i5500_temp", + .id_table = i5500_temp_ids, + .probe = i5500_temp_probe, +}; + +module_pci_driver(i5500_temp_driver); + +MODULE_AUTHOR("Jean Delvare "); +MODULE_DESCRIPTION("Intel 5500/5520/X58 chipset thermal sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/i5k_amb.c b/drivers/hwmon/i5k_amb.c new file mode 100644 index 000000000..a4edc43dd --- /dev/null +++ b/drivers/hwmon/i5k_amb.c @@ -0,0 +1,617 @@ +/* + * A hwmon driver for the Intel 5000 series chipset FB-DIMM AMB + * temperature sensors + * Copyright (C) 2007 IBM + * + * Author: Darrick J. Wong + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "i5k_amb" + +#define I5K_REG_AMB_BASE_ADDR 0x48 +#define I5K_REG_AMB_LEN_ADDR 0x50 +#define I5K_REG_CHAN0_PRESENCE_ADDR 0x64 +#define I5K_REG_CHAN1_PRESENCE_ADDR 0x66 + +#define AMB_REG_TEMP_MIN_ADDR 0x80 +#define AMB_REG_TEMP_MID_ADDR 0x81 +#define AMB_REG_TEMP_MAX_ADDR 0x82 +#define AMB_REG_TEMP_STATUS_ADDR 0x84 +#define AMB_REG_TEMP_ADDR 0x85 + +#define AMB_CONFIG_SIZE 2048 +#define AMB_FUNC_3_OFFSET 768 + +static unsigned long amb_reg_temp_status(unsigned int amb) +{ + return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_STATUS_ADDR + + AMB_CONFIG_SIZE * amb; +} + +static unsigned long amb_reg_temp_min(unsigned int amb) +{ + return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_MIN_ADDR + + AMB_CONFIG_SIZE * amb; +} + +static unsigned long amb_reg_temp_mid(unsigned int amb) +{ + return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_MID_ADDR + + AMB_CONFIG_SIZE * amb; +} + +static unsigned long amb_reg_temp_max(unsigned int amb) +{ + return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_MAX_ADDR + + AMB_CONFIG_SIZE * amb; +} + +static unsigned long amb_reg_temp(unsigned int amb) +{ + return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_ADDR + + AMB_CONFIG_SIZE * amb; +} + +#define MAX_MEM_CHANNELS 4 +#define MAX_AMBS_PER_CHANNEL 16 +#define MAX_AMBS (MAX_MEM_CHANNELS * \ + MAX_AMBS_PER_CHANNEL) +#define CHANNEL_SHIFT 4 +#define DIMM_MASK 0xF +/* + * Ugly hack: For some reason the highest bit is set if there + * are _any_ DIMMs in the channel. Attempting to read from + * this "high-order" AMB results in a memory bus error, so + * for now we'll just ignore that top bit, even though that + * might prevent us from seeing the 16th DIMM in the channel. + */ +#define REAL_MAX_AMBS_PER_CHANNEL 15 +#define KNOBS_PER_AMB 6 + +static unsigned long amb_num_from_reg(unsigned int byte_num, unsigned int bit) +{ + return byte_num * MAX_AMBS_PER_CHANNEL + bit; +} + +#define AMB_SYSFS_NAME_LEN 16 +struct i5k_device_attribute { + struct sensor_device_attribute s_attr; + char name[AMB_SYSFS_NAME_LEN]; +}; + +struct i5k_amb_data { + struct device *hwmon_dev; + + unsigned long amb_base; + unsigned long amb_len; + u16 amb_present[MAX_MEM_CHANNELS]; + void __iomem *amb_mmio; + struct i5k_device_attribute *attrs; + unsigned int num_attrs; +}; + +static ssize_t name_show(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return sprintf(buf, "%s\n", DRVNAME); +} + + +static DEVICE_ATTR_RO(name); + +static struct platform_device *amb_pdev; + +static u8 amb_read_byte(struct i5k_amb_data *data, unsigned long offset) +{ + return ioread8(data->amb_mmio + offset); +} + +static void amb_write_byte(struct i5k_amb_data *data, unsigned long offset, + u8 val) +{ + iowrite8(val, data->amb_mmio + offset); +} + +static ssize_t show_amb_alarm(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct i5k_amb_data *data = dev_get_drvdata(dev); + + if (!(amb_read_byte(data, amb_reg_temp_status(attr->index)) & 0x20) && + (amb_read_byte(data, amb_reg_temp_status(attr->index)) & 0x8)) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t store_amb_min(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct i5k_amb_data *data = dev_get_drvdata(dev); + unsigned long temp; + int ret = kstrtoul(buf, 10, &temp); + if (ret < 0) + return ret; + + temp = temp / 500; + if (temp > 255) + temp = 255; + + amb_write_byte(data, amb_reg_temp_min(attr->index), temp); + return count; +} + +static ssize_t store_amb_mid(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct i5k_amb_data *data = dev_get_drvdata(dev); + unsigned long temp; + int ret = kstrtoul(buf, 10, &temp); + if (ret < 0) + return ret; + + temp = temp / 500; + if (temp > 255) + temp = 255; + + amb_write_byte(data, amb_reg_temp_mid(attr->index), temp); + return count; +} + +static ssize_t store_amb_max(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct i5k_amb_data *data = dev_get_drvdata(dev); + unsigned long temp; + int ret = kstrtoul(buf, 10, &temp); + if (ret < 0) + return ret; + + temp = temp / 500; + if (temp > 255) + temp = 255; + + amb_write_byte(data, amb_reg_temp_max(attr->index), temp); + return count; +} + +static ssize_t show_amb_min(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct i5k_amb_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", + 500 * amb_read_byte(data, amb_reg_temp_min(attr->index))); +} + +static ssize_t show_amb_mid(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct i5k_amb_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", + 500 * amb_read_byte(data, amb_reg_temp_mid(attr->index))); +} + +static ssize_t show_amb_max(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct i5k_amb_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", + 500 * amb_read_byte(data, amb_reg_temp_max(attr->index))); +} + +static ssize_t show_amb_temp(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct i5k_amb_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", + 500 * amb_read_byte(data, amb_reg_temp(attr->index))); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "Ch. %d DIMM %d\n", attr->index >> CHANNEL_SHIFT, + attr->index & DIMM_MASK); +} + +static int i5k_amb_hwmon_init(struct platform_device *pdev) +{ + int i, j, k, d = 0; + u16 c; + int res = 0; + int num_ambs = 0; + struct i5k_amb_data *data = platform_get_drvdata(pdev); + + /* Count the number of AMBs found */ + /* ignore the high-order bit, see "Ugly hack" comment above */ + for (i = 0; i < MAX_MEM_CHANNELS; i++) + num_ambs += hweight16(data->amb_present[i] & 0x7fff); + + /* Set up sysfs stuff */ + data->attrs = kzalloc(array3_size(num_ambs, KNOBS_PER_AMB, + sizeof(*data->attrs)), + GFP_KERNEL); + if (!data->attrs) + return -ENOMEM; + data->num_attrs = 0; + + for (i = 0; i < MAX_MEM_CHANNELS; i++) { + c = data->amb_present[i]; + for (j = 0; j < REAL_MAX_AMBS_PER_CHANNEL; j++, c >>= 1) { + struct i5k_device_attribute *iattr; + + k = amb_num_from_reg(i, j); + if (!(c & 0x1)) + continue; + d++; + + /* sysfs label */ + iattr = data->attrs + data->num_attrs; + snprintf(iattr->name, AMB_SYSFS_NAME_LEN, + "temp%d_label", d); + iattr->s_attr.dev_attr.attr.name = iattr->name; + iattr->s_attr.dev_attr.attr.mode = S_IRUGO; + iattr->s_attr.dev_attr.show = show_label; + iattr->s_attr.index = k; + sysfs_attr_init(&iattr->s_attr.dev_attr.attr); + res = device_create_file(&pdev->dev, + &iattr->s_attr.dev_attr); + if (res) + goto exit_remove; + data->num_attrs++; + + /* Temperature sysfs knob */ + iattr = data->attrs + data->num_attrs; + snprintf(iattr->name, AMB_SYSFS_NAME_LEN, + "temp%d_input", d); + iattr->s_attr.dev_attr.attr.name = iattr->name; + iattr->s_attr.dev_attr.attr.mode = S_IRUGO; + iattr->s_attr.dev_attr.show = show_amb_temp; + iattr->s_attr.index = k; + sysfs_attr_init(&iattr->s_attr.dev_attr.attr); + res = device_create_file(&pdev->dev, + &iattr->s_attr.dev_attr); + if (res) + goto exit_remove; + data->num_attrs++; + + /* Temperature min sysfs knob */ + iattr = data->attrs + data->num_attrs; + snprintf(iattr->name, AMB_SYSFS_NAME_LEN, + "temp%d_min", d); + iattr->s_attr.dev_attr.attr.name = iattr->name; + iattr->s_attr.dev_attr.attr.mode = S_IWUSR | S_IRUGO; + iattr->s_attr.dev_attr.show = show_amb_min; + iattr->s_attr.dev_attr.store = store_amb_min; + iattr->s_attr.index = k; + sysfs_attr_init(&iattr->s_attr.dev_attr.attr); + res = device_create_file(&pdev->dev, + &iattr->s_attr.dev_attr); + if (res) + goto exit_remove; + data->num_attrs++; + + /* Temperature mid sysfs knob */ + iattr = data->attrs + data->num_attrs; + snprintf(iattr->name, AMB_SYSFS_NAME_LEN, + "temp%d_mid", d); + iattr->s_attr.dev_attr.attr.name = iattr->name; + iattr->s_attr.dev_attr.attr.mode = S_IWUSR | S_IRUGO; + iattr->s_attr.dev_attr.show = show_amb_mid; + iattr->s_attr.dev_attr.store = store_amb_mid; + iattr->s_attr.index = k; + sysfs_attr_init(&iattr->s_attr.dev_attr.attr); + res = device_create_file(&pdev->dev, + &iattr->s_attr.dev_attr); + if (res) + goto exit_remove; + data->num_attrs++; + + /* Temperature max sysfs knob */ + iattr = data->attrs + data->num_attrs; + snprintf(iattr->name, AMB_SYSFS_NAME_LEN, + "temp%d_max", d); + iattr->s_attr.dev_attr.attr.name = iattr->name; + iattr->s_attr.dev_attr.attr.mode = S_IWUSR | S_IRUGO; + iattr->s_attr.dev_attr.show = show_amb_max; + iattr->s_attr.dev_attr.store = store_amb_max; + iattr->s_attr.index = k; + sysfs_attr_init(&iattr->s_attr.dev_attr.attr); + res = device_create_file(&pdev->dev, + &iattr->s_attr.dev_attr); + if (res) + goto exit_remove; + data->num_attrs++; + + /* Temperature alarm sysfs knob */ + iattr = data->attrs + data->num_attrs; + snprintf(iattr->name, AMB_SYSFS_NAME_LEN, + "temp%d_alarm", d); + iattr->s_attr.dev_attr.attr.name = iattr->name; + iattr->s_attr.dev_attr.attr.mode = S_IRUGO; + iattr->s_attr.dev_attr.show = show_amb_alarm; + iattr->s_attr.index = k; + sysfs_attr_init(&iattr->s_attr.dev_attr.attr); + res = device_create_file(&pdev->dev, + &iattr->s_attr.dev_attr); + if (res) + goto exit_remove; + data->num_attrs++; + } + } + + res = device_create_file(&pdev->dev, &dev_attr_name); + if (res) + goto exit_remove; + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + res = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + return res; + +exit_remove: + device_remove_file(&pdev->dev, &dev_attr_name); + for (i = 0; i < data->num_attrs; i++) + device_remove_file(&pdev->dev, &data->attrs[i].s_attr.dev_attr); + kfree(data->attrs); + + return res; +} + +static int i5k_amb_add(void) +{ + int res = -ENODEV; + + /* only ever going to be one of these */ + amb_pdev = platform_device_alloc(DRVNAME, 0); + if (!amb_pdev) + return -ENOMEM; + + res = platform_device_add(amb_pdev); + if (res) + goto err; + return 0; + +err: + platform_device_put(amb_pdev); + return res; +} + +static int i5k_find_amb_registers(struct i5k_amb_data *data, + unsigned long devid) +{ + struct pci_dev *pcidev; + u32 val32; + int res = -ENODEV; + + /* Find AMB register memory space */ + pcidev = pci_get_device(PCI_VENDOR_ID_INTEL, + devid, + NULL); + if (!pcidev) + return -ENODEV; + + if (pci_read_config_dword(pcidev, I5K_REG_AMB_BASE_ADDR, &val32)) + goto out; + data->amb_base = val32; + + if (pci_read_config_dword(pcidev, I5K_REG_AMB_LEN_ADDR, &val32)) + goto out; + data->amb_len = val32; + + /* Is it big enough? */ + if (data->amb_len < AMB_CONFIG_SIZE * MAX_AMBS) { + dev_err(&pcidev->dev, "AMB region too small!\n"); + goto out; + } + + res = 0; +out: + pci_dev_put(pcidev); + return res; +} + +static int i5k_channel_probe(u16 *amb_present, unsigned long dev_id) +{ + struct pci_dev *pcidev; + u16 val16; + int res = -ENODEV; + + /* Copy the DIMM presence map for these two channels */ + pcidev = pci_get_device(PCI_VENDOR_ID_INTEL, dev_id, NULL); + if (!pcidev) + return -ENODEV; + + if (pci_read_config_word(pcidev, I5K_REG_CHAN0_PRESENCE_ADDR, &val16)) + goto out; + amb_present[0] = val16; + + if (pci_read_config_word(pcidev, I5K_REG_CHAN1_PRESENCE_ADDR, &val16)) + goto out; + amb_present[1] = val16; + + res = 0; + +out: + pci_dev_put(pcidev); + return res; +} + +static struct { + unsigned long err; + unsigned long fbd0; +} chipset_ids[] = { + { PCI_DEVICE_ID_INTEL_5000_ERR, PCI_DEVICE_ID_INTEL_5000_FBD0 }, + { PCI_DEVICE_ID_INTEL_5400_ERR, PCI_DEVICE_ID_INTEL_5400_FBD0 }, + { 0, 0 } +}; + +#ifdef MODULE +static const struct pci_device_id i5k_amb_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5000_ERR) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5400_ERR) }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, i5k_amb_ids); +#endif + +static int i5k_amb_probe(struct platform_device *pdev) +{ + struct i5k_amb_data *data; + struct resource *reso; + int i, res; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* Figure out where the AMB registers live */ + i = 0; + do { + res = i5k_find_amb_registers(data, chipset_ids[i].err); + if (res == 0) + break; + i++; + } while (chipset_ids[i].err); + + if (res) + goto err; + + /* Copy the DIMM presence map for the first two channels */ + res = i5k_channel_probe(&data->amb_present[0], chipset_ids[i].fbd0); + if (res) + goto err; + + /* Copy the DIMM presence map for the optional second two channels */ + i5k_channel_probe(&data->amb_present[2], chipset_ids[i].fbd0 + 1); + + /* Set up resource regions */ + reso = request_mem_region(data->amb_base, data->amb_len, DRVNAME); + if (!reso) { + res = -EBUSY; + goto err; + } + + data->amb_mmio = ioremap_nocache(data->amb_base, data->amb_len); + if (!data->amb_mmio) { + res = -EBUSY; + goto err_map_failed; + } + + platform_set_drvdata(pdev, data); + + res = i5k_amb_hwmon_init(pdev); + if (res) + goto err_init_failed; + + return res; + +err_init_failed: + iounmap(data->amb_mmio); +err_map_failed: + release_mem_region(data->amb_base, data->amb_len); +err: + kfree(data); + return res; +} + +static int i5k_amb_remove(struct platform_device *pdev) +{ + int i; + struct i5k_amb_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + device_remove_file(&pdev->dev, &dev_attr_name); + for (i = 0; i < data->num_attrs; i++) + device_remove_file(&pdev->dev, &data->attrs[i].s_attr.dev_attr); + kfree(data->attrs); + iounmap(data->amb_mmio); + release_mem_region(data->amb_base, data->amb_len); + kfree(data); + return 0; +} + +static struct platform_driver i5k_amb_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = i5k_amb_probe, + .remove = i5k_amb_remove, +}; + +static int __init i5k_amb_init(void) +{ + int res; + + res = platform_driver_register(&i5k_amb_driver); + if (res) + return res; + + res = i5k_amb_add(); + if (res) + platform_driver_unregister(&i5k_amb_driver); + + return res; +} + +static void __exit i5k_amb_exit(void) +{ + platform_device_unregister(amb_pdev); + platform_driver_unregister(&i5k_amb_driver); +} + +MODULE_AUTHOR("Darrick J. Wong "); +MODULE_DESCRIPTION("Intel 5000 chipset FB-DIMM AMB temperature sensor"); +MODULE_LICENSE("GPL"); + +module_init(i5k_amb_init); +module_exit(i5k_amb_exit); diff --git a/drivers/hwmon/ibmaem.c b/drivers/hwmon/ibmaem.c new file mode 100644 index 000000000..1f643782c --- /dev/null +++ b/drivers/hwmon/ibmaem.c @@ -0,0 +1,1117 @@ +/* + * A hwmon driver for the IBM System Director Active Energy Manager (AEM) + * temperature/power/energy sensors and capping functionality. + * Copyright (C) 2008 IBM + * + * Author: Darrick J. Wong + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REFRESH_INTERVAL (HZ) +#define IPMI_TIMEOUT (30 * HZ) +#define DRVNAME "aem" + +#define AEM_NETFN 0x2E + +#define AEM_FIND_FW_CMD 0x80 +#define AEM_ELEMENT_CMD 0x81 +#define AEM_FW_INSTANCE_CMD 0x82 + +#define AEM_READ_ELEMENT_CFG 0x80 +#define AEM_READ_BUFFER 0x81 +#define AEM_READ_REGISTER 0x82 +#define AEM_WRITE_REGISTER 0x83 +#define AEM_SET_REG_MASK 0x84 +#define AEM_CLEAR_REG_MASK 0x85 +#define AEM_READ_ELEMENT_CFG2 0x86 + +#define AEM_CONTROL_ELEMENT 0 +#define AEM_ENERGY_ELEMENT 1 +#define AEM_CLOCK_ELEMENT 4 +#define AEM_POWER_CAP_ELEMENT 7 +#define AEM_EXHAUST_ELEMENT 9 +#define AEM_POWER_ELEMENT 10 + +#define AEM_MODULE_TYPE_ID 0x0001 + +#define AEM2_NUM_ENERGY_REGS 2 +#define AEM2_NUM_PCAP_REGS 6 +#define AEM2_NUM_TEMP_REGS 2 +#define AEM2_NUM_SENSORS 14 + +#define AEM1_NUM_ENERGY_REGS 1 +#define AEM1_NUM_SENSORS 3 + +/* AEM 2.x has more energy registers */ +#define AEM_NUM_ENERGY_REGS AEM2_NUM_ENERGY_REGS +/* AEM 2.x needs more sensor files */ +#define AEM_NUM_SENSORS AEM2_NUM_SENSORS + +#define POWER_CAP 0 +#define POWER_CAP_MAX_HOTPLUG 1 +#define POWER_CAP_MAX 2 +#define POWER_CAP_MIN_WARNING 3 +#define POWER_CAP_MIN 4 +#define POWER_AUX 5 + +#define AEM_DEFAULT_POWER_INTERVAL 1000 +#define AEM_MIN_POWER_INTERVAL 200 +#define UJ_PER_MJ 1000L + +static DEFINE_IDA(aem_ida); + +static struct platform_driver aem_driver = { + .driver = { + .name = DRVNAME, + .bus = &platform_bus_type, + } +}; + +struct aem_ipmi_data { + struct completion read_complete; + struct ipmi_addr address; + ipmi_user_t user; + int interface; + + struct kernel_ipmi_msg tx_message; + long tx_msgid; + + void *rx_msg_data; + unsigned short rx_msg_len; + unsigned char rx_result; + int rx_recv_type; + + struct device *bmc_device; +}; + +struct aem_ro_sensor_template { + char *label; + ssize_t (*show)(struct device *dev, + struct device_attribute *devattr, + char *buf); + int index; +}; + +struct aem_rw_sensor_template { + char *label; + ssize_t (*show)(struct device *dev, + struct device_attribute *devattr, + char *buf); + ssize_t (*set)(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count); + int index; +}; + +struct aem_data { + struct list_head list; + + struct device *hwmon_dev; + struct platform_device *pdev; + struct mutex lock; + char valid; + unsigned long last_updated; /* In jiffies */ + u8 ver_major; + u8 ver_minor; + u8 module_handle; + int id; + struct aem_ipmi_data ipmi; + + /* Function and buffer to update sensors */ + void (*update)(struct aem_data *data); + struct aem_read_sensor_resp *rs_resp; + + /* + * AEM 1.x sensors: + * Available sensors: + * Energy meter + * Power meter + * + * AEM 2.x sensors: + * Two energy meters + * Two power meters + * Two temperature sensors + * Six power cap registers + */ + + /* sysfs attrs */ + struct sensor_device_attribute sensors[AEM_NUM_SENSORS]; + + /* energy use in mJ */ + u64 energy[AEM_NUM_ENERGY_REGS]; + + /* power sampling interval in ms */ + unsigned long power_period[AEM_NUM_ENERGY_REGS]; + + /* Everything past here is for AEM2 only */ + + /* power caps in dW */ + u16 pcap[AEM2_NUM_PCAP_REGS]; + + /* exhaust temperature in C */ + u8 temp[AEM2_NUM_TEMP_REGS]; +}; + +/* Data structures returned by the AEM firmware */ +struct aem_iana_id { + u8 bytes[3]; +}; +static struct aem_iana_id system_x_id = { + .bytes = {0x4D, 0x4F, 0x00} +}; + +/* These are used to find AEM1 instances */ +struct aem_find_firmware_req { + struct aem_iana_id id; + u8 rsvd; + __be16 index; + __be16 module_type_id; +} __packed; + +struct aem_find_firmware_resp { + struct aem_iana_id id; + u8 num_instances; +} __packed; + +/* These are used to find AEM2 instances */ +struct aem_find_instance_req { + struct aem_iana_id id; + u8 instance_number; + __be16 module_type_id; +} __packed; + +struct aem_find_instance_resp { + struct aem_iana_id id; + u8 num_instances; + u8 major; + u8 minor; + u8 module_handle; + u16 record_id; +} __packed; + +/* These are used to query sensors */ +struct aem_read_sensor_req { + struct aem_iana_id id; + u8 module_handle; + u8 element; + u8 subcommand; + u8 reg; + u8 rx_buf_size; +} __packed; + +struct aem_read_sensor_resp { + struct aem_iana_id id; + u8 bytes[0]; +} __packed; + +/* Data structures to talk to the IPMI layer */ +struct aem_driver_data { + struct list_head aem_devices; + struct ipmi_smi_watcher bmc_events; + struct ipmi_user_hndl ipmi_hndlrs; +}; + +static void aem_register_bmc(int iface, struct device *dev); +static void aem_bmc_gone(int iface); +static void aem_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data); + +static void aem_remove_sensors(struct aem_data *data); +static int aem1_find_sensors(struct aem_data *data); +static int aem2_find_sensors(struct aem_data *data); +static void update_aem1_sensors(struct aem_data *data); +static void update_aem2_sensors(struct aem_data *data); + +static struct aem_driver_data driver_data = { + .aem_devices = LIST_HEAD_INIT(driver_data.aem_devices), + .bmc_events = { + .owner = THIS_MODULE, + .new_smi = aem_register_bmc, + .smi_gone = aem_bmc_gone, + }, + .ipmi_hndlrs = { + .ipmi_recv_hndl = aem_msg_handler, + }, +}; + +/* Functions to talk to the IPMI layer */ + +/* Initialize IPMI address, message buffers and user data */ +static int aem_init_ipmi_data(struct aem_ipmi_data *data, int iface, + struct device *bmc) +{ + int err; + + init_completion(&data->read_complete); + data->bmc_device = bmc; + + /* Initialize IPMI address */ + data->address.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; + data->address.channel = IPMI_BMC_CHANNEL; + data->address.data[0] = 0; + data->interface = iface; + + /* Initialize message buffers */ + data->tx_msgid = 0; + data->tx_message.netfn = AEM_NETFN; + + /* Create IPMI messaging interface user */ + err = ipmi_create_user(data->interface, &driver_data.ipmi_hndlrs, + data, &data->user); + if (err < 0) { + dev_err(bmc, + "Unable to register user with IPMI interface %d\n", + data->interface); + return err; + } + + return 0; +} + +/* Send an IPMI command */ +static int aem_send_message(struct aem_ipmi_data *data) +{ + int err; + + err = ipmi_validate_addr(&data->address, sizeof(data->address)); + if (err) + goto out; + + data->tx_msgid++; + err = ipmi_request_settime(data->user, &data->address, data->tx_msgid, + &data->tx_message, data, 0, 0, 0); + if (err) + goto out1; + + return 0; +out1: + dev_err(data->bmc_device, "request_settime=%x\n", err); + return err; +out: + dev_err(data->bmc_device, "validate_addr=%x\n", err); + return err; +} + +/* Dispatch IPMI messages to callers */ +static void aem_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data) +{ + unsigned short rx_len; + struct aem_ipmi_data *data = user_msg_data; + + if (msg->msgid != data->tx_msgid) { + dev_err(data->bmc_device, + "Mismatch between received msgid (%02x) and transmitted msgid (%02x)!\n", + (int)msg->msgid, + (int)data->tx_msgid); + ipmi_free_recv_msg(msg); + return; + } + + data->rx_recv_type = msg->recv_type; + if (msg->msg.data_len > 0) + data->rx_result = msg->msg.data[0]; + else + data->rx_result = IPMI_UNKNOWN_ERR_COMPLETION_CODE; + + if (msg->msg.data_len > 1) { + rx_len = msg->msg.data_len - 1; + if (data->rx_msg_len < rx_len) + rx_len = data->rx_msg_len; + data->rx_msg_len = rx_len; + memcpy(data->rx_msg_data, msg->msg.data + 1, data->rx_msg_len); + } else + data->rx_msg_len = 0; + + ipmi_free_recv_msg(msg); + complete(&data->read_complete); +} + +/* Sensor support functions */ + +/* Read a sensor value; must be called with data->lock held */ +static int aem_read_sensor(struct aem_data *data, u8 elt, u8 reg, + void *buf, size_t size) +{ + int rs_size, res; + struct aem_read_sensor_req rs_req; + /* Use preallocated rx buffer */ + struct aem_read_sensor_resp *rs_resp = data->rs_resp; + struct aem_ipmi_data *ipmi = &data->ipmi; + + /* AEM registers are 1, 2, 4 or 8 bytes */ + switch (size) { + case 1: + case 2: + case 4: + case 8: + break; + default: + return -EINVAL; + } + + rs_req.id = system_x_id; + rs_req.module_handle = data->module_handle; + rs_req.element = elt; + rs_req.subcommand = AEM_READ_REGISTER; + rs_req.reg = reg; + rs_req.rx_buf_size = size; + + ipmi->tx_message.cmd = AEM_ELEMENT_CMD; + ipmi->tx_message.data = (char *)&rs_req; + ipmi->tx_message.data_len = sizeof(rs_req); + + rs_size = sizeof(*rs_resp) + size; + ipmi->rx_msg_data = rs_resp; + ipmi->rx_msg_len = rs_size; + + aem_send_message(ipmi); + + res = wait_for_completion_timeout(&ipmi->read_complete, IPMI_TIMEOUT); + if (!res) { + res = -ETIMEDOUT; + goto out; + } + + if (ipmi->rx_result || ipmi->rx_msg_len != rs_size || + memcmp(&rs_resp->id, &system_x_id, sizeof(system_x_id))) { + res = -ENOENT; + goto out; + } + + switch (size) { + case 1: { + u8 *x = buf; + *x = rs_resp->bytes[0]; + break; + } + case 2: { + u16 *x = buf; + *x = be16_to_cpup((__be16 *)rs_resp->bytes); + break; + } + case 4: { + u32 *x = buf; + *x = be32_to_cpup((__be32 *)rs_resp->bytes); + break; + } + case 8: { + u64 *x = buf; + *x = be64_to_cpup((__be64 *)rs_resp->bytes); + break; + } + } + res = 0; + +out: + return res; +} + +/* Update AEM energy registers */ +static void update_aem_energy_one(struct aem_data *data, int which) +{ + aem_read_sensor(data, AEM_ENERGY_ELEMENT, which, + &data->energy[which], 8); +} + +static void update_aem_energy(struct aem_data *data) +{ + update_aem_energy_one(data, 0); + if (data->ver_major < 2) + return; + update_aem_energy_one(data, 1); +} + +/* Update all AEM1 sensors */ +static void update_aem1_sensors(struct aem_data *data) +{ + mutex_lock(&data->lock); + if (time_before(jiffies, data->last_updated + REFRESH_INTERVAL) && + data->valid) + goto out; + + update_aem_energy(data); +out: + mutex_unlock(&data->lock); +} + +/* Update all AEM2 sensors */ +static void update_aem2_sensors(struct aem_data *data) +{ + int i; + + mutex_lock(&data->lock); + if (time_before(jiffies, data->last_updated + REFRESH_INTERVAL) && + data->valid) + goto out; + + update_aem_energy(data); + aem_read_sensor(data, AEM_EXHAUST_ELEMENT, 0, &data->temp[0], 1); + aem_read_sensor(data, AEM_EXHAUST_ELEMENT, 1, &data->temp[1], 1); + + for (i = POWER_CAP; i <= POWER_AUX; i++) + aem_read_sensor(data, AEM_POWER_CAP_ELEMENT, i, + &data->pcap[i], 2); +out: + mutex_unlock(&data->lock); +} + +/* Delete an AEM instance */ +static void aem_delete(struct aem_data *data) +{ + list_del(&data->list); + aem_remove_sensors(data); + kfree(data->rs_resp); + hwmon_device_unregister(data->hwmon_dev); + ipmi_destroy_user(data->ipmi.user); + platform_set_drvdata(data->pdev, NULL); + platform_device_unregister(data->pdev); + ida_simple_remove(&aem_ida, data->id); + kfree(data); +} + +/* Probe functions for AEM1 devices */ + +/* Retrieve version and module handle for an AEM1 instance */ +static int aem_find_aem1_count(struct aem_ipmi_data *data) +{ + int res; + struct aem_find_firmware_req ff_req; + struct aem_find_firmware_resp ff_resp; + + ff_req.id = system_x_id; + ff_req.index = 0; + ff_req.module_type_id = cpu_to_be16(AEM_MODULE_TYPE_ID); + + data->tx_message.cmd = AEM_FIND_FW_CMD; + data->tx_message.data = (char *)&ff_req; + data->tx_message.data_len = sizeof(ff_req); + + data->rx_msg_data = &ff_resp; + data->rx_msg_len = sizeof(ff_resp); + + aem_send_message(data); + + res = wait_for_completion_timeout(&data->read_complete, IPMI_TIMEOUT); + if (!res) + return -ETIMEDOUT; + + if (data->rx_result || data->rx_msg_len != sizeof(ff_resp) || + memcmp(&ff_resp.id, &system_x_id, sizeof(system_x_id))) + return -ENOENT; + + return ff_resp.num_instances; +} + +/* Find and initialize one AEM1 instance */ +static int aem_init_aem1_inst(struct aem_ipmi_data *probe, u8 module_handle) +{ + struct aem_data *data; + int i; + int res = -ENOMEM; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return res; + mutex_init(&data->lock); + + /* Copy instance data */ + data->ver_major = 1; + data->ver_minor = 0; + data->module_handle = module_handle; + for (i = 0; i < AEM1_NUM_ENERGY_REGS; i++) + data->power_period[i] = AEM_DEFAULT_POWER_INTERVAL; + + /* Create sub-device for this fw instance */ + data->id = ida_simple_get(&aem_ida, 0, 0, GFP_KERNEL); + if (data->id < 0) + goto id_err; + + data->pdev = platform_device_alloc(DRVNAME, data->id); + if (!data->pdev) + goto dev_err; + data->pdev->dev.driver = &aem_driver.driver; + + res = platform_device_add(data->pdev); + if (res) + goto ipmi_err; + + platform_set_drvdata(data->pdev, data); + + /* Set up IPMI interface */ + res = aem_init_ipmi_data(&data->ipmi, probe->interface, + probe->bmc_device); + if (res) + goto ipmi_err; + + /* Register with hwmon */ + data->hwmon_dev = hwmon_device_register(&data->pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + dev_err(&data->pdev->dev, + "Unable to register hwmon device for IPMI interface %d\n", + probe->interface); + res = PTR_ERR(data->hwmon_dev); + goto hwmon_reg_err; + } + + data->update = update_aem1_sensors; + data->rs_resp = kzalloc(sizeof(*(data->rs_resp)) + 8, GFP_KERNEL); + if (!data->rs_resp) { + res = -ENOMEM; + goto alloc_resp_err; + } + + /* Find sensors */ + res = aem1_find_sensors(data); + if (res) + goto sensor_err; + + /* Add to our list of AEM devices */ + list_add_tail(&data->list, &driver_data.aem_devices); + + dev_info(data->ipmi.bmc_device, "Found AEM v%d.%d at 0x%X\n", + data->ver_major, data->ver_minor, + data->module_handle); + return 0; + +sensor_err: + kfree(data->rs_resp); +alloc_resp_err: + hwmon_device_unregister(data->hwmon_dev); +hwmon_reg_err: + ipmi_destroy_user(data->ipmi.user); +ipmi_err: + platform_set_drvdata(data->pdev, NULL); + platform_device_unregister(data->pdev); +dev_err: + ida_simple_remove(&aem_ida, data->id); +id_err: + kfree(data); + + return res; +} + +/* Find and initialize all AEM1 instances */ +static void aem_init_aem1(struct aem_ipmi_data *probe) +{ + int num, i, err; + + num = aem_find_aem1_count(probe); + for (i = 0; i < num; i++) { + err = aem_init_aem1_inst(probe, i); + if (err) { + dev_err(probe->bmc_device, + "Error %d initializing AEM1 0x%X\n", + err, i); + } + } +} + +/* Probe functions for AEM2 devices */ + +/* Retrieve version and module handle for an AEM2 instance */ +static int aem_find_aem2(struct aem_ipmi_data *data, + struct aem_find_instance_resp *fi_resp, + int instance_num) +{ + int res; + struct aem_find_instance_req fi_req; + + fi_req.id = system_x_id; + fi_req.instance_number = instance_num; + fi_req.module_type_id = cpu_to_be16(AEM_MODULE_TYPE_ID); + + data->tx_message.cmd = AEM_FW_INSTANCE_CMD; + data->tx_message.data = (char *)&fi_req; + data->tx_message.data_len = sizeof(fi_req); + + data->rx_msg_data = fi_resp; + data->rx_msg_len = sizeof(*fi_resp); + + aem_send_message(data); + + res = wait_for_completion_timeout(&data->read_complete, IPMI_TIMEOUT); + if (!res) + return -ETIMEDOUT; + + if (data->rx_result || data->rx_msg_len != sizeof(*fi_resp) || + memcmp(&fi_resp->id, &system_x_id, sizeof(system_x_id)) || + fi_resp->num_instances <= instance_num) + return -ENOENT; + + return 0; +} + +/* Find and initialize one AEM2 instance */ +static int aem_init_aem2_inst(struct aem_ipmi_data *probe, + struct aem_find_instance_resp *fi_resp) +{ + struct aem_data *data; + int i; + int res = -ENOMEM; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return res; + mutex_init(&data->lock); + + /* Copy instance data */ + data->ver_major = fi_resp->major; + data->ver_minor = fi_resp->minor; + data->module_handle = fi_resp->module_handle; + for (i = 0; i < AEM2_NUM_ENERGY_REGS; i++) + data->power_period[i] = AEM_DEFAULT_POWER_INTERVAL; + + /* Create sub-device for this fw instance */ + data->id = ida_simple_get(&aem_ida, 0, 0, GFP_KERNEL); + if (data->id < 0) + goto id_err; + + data->pdev = platform_device_alloc(DRVNAME, data->id); + if (!data->pdev) + goto dev_err; + data->pdev->dev.driver = &aem_driver.driver; + + res = platform_device_add(data->pdev); + if (res) + goto ipmi_err; + + platform_set_drvdata(data->pdev, data); + + /* Set up IPMI interface */ + res = aem_init_ipmi_data(&data->ipmi, probe->interface, + probe->bmc_device); + if (res) + goto ipmi_err; + + /* Register with hwmon */ + data->hwmon_dev = hwmon_device_register(&data->pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + dev_err(&data->pdev->dev, + "Unable to register hwmon device for IPMI interface %d\n", + probe->interface); + res = PTR_ERR(data->hwmon_dev); + goto hwmon_reg_err; + } + + data->update = update_aem2_sensors; + data->rs_resp = kzalloc(sizeof(*(data->rs_resp)) + 8, GFP_KERNEL); + if (!data->rs_resp) { + res = -ENOMEM; + goto alloc_resp_err; + } + + /* Find sensors */ + res = aem2_find_sensors(data); + if (res) + goto sensor_err; + + /* Add to our list of AEM devices */ + list_add_tail(&data->list, &driver_data.aem_devices); + + dev_info(data->ipmi.bmc_device, "Found AEM v%d.%d at 0x%X\n", + data->ver_major, data->ver_minor, + data->module_handle); + return 0; + +sensor_err: + kfree(data->rs_resp); +alloc_resp_err: + hwmon_device_unregister(data->hwmon_dev); +hwmon_reg_err: + ipmi_destroy_user(data->ipmi.user); +ipmi_err: + platform_set_drvdata(data->pdev, NULL); + platform_device_unregister(data->pdev); +dev_err: + ida_simple_remove(&aem_ida, data->id); +id_err: + kfree(data); + + return res; +} + +/* Find and initialize all AEM2 instances */ +static void aem_init_aem2(struct aem_ipmi_data *probe) +{ + struct aem_find_instance_resp fi_resp; + int err; + int i = 0; + + while (!aem_find_aem2(probe, &fi_resp, i)) { + if (fi_resp.major != 2) { + dev_err(probe->bmc_device, + "Unknown AEM v%d; please report this to the maintainer.\n", + fi_resp.major); + i++; + continue; + } + err = aem_init_aem2_inst(probe, &fi_resp); + if (err) { + dev_err(probe->bmc_device, + "Error %d initializing AEM2 0x%X\n", + err, fi_resp.module_handle); + } + i++; + } +} + +/* Probe a BMC for AEM firmware instances */ +static void aem_register_bmc(int iface, struct device *dev) +{ + struct aem_ipmi_data probe; + + if (aem_init_ipmi_data(&probe, iface, dev)) + return; + + /* Ignore probe errors; they won't cause problems */ + aem_init_aem1(&probe); + aem_init_aem2(&probe); + + ipmi_destroy_user(probe.user); +} + +/* Handle BMC deletion */ +static void aem_bmc_gone(int iface) +{ + struct aem_data *p1, *next1; + + list_for_each_entry_safe(p1, next1, &driver_data.aem_devices, list) + if (p1->ipmi.interface == iface) + aem_delete(p1); +} + +/* sysfs support functions */ + +/* AEM device name */ +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct aem_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s%d\n", DRVNAME, data->ver_major); +} +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); + +/* AEM device version */ +static ssize_t show_version(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct aem_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%d.%d\n", data->ver_major, data->ver_minor); +} +static SENSOR_DEVICE_ATTR(version, S_IRUGO, show_version, NULL, 0); + +/* Display power use */ +static ssize_t aem_show_power(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct aem_data *data = dev_get_drvdata(dev); + u64 before, after, delta, time; + signed long leftover; + + mutex_lock(&data->lock); + update_aem_energy_one(data, attr->index); + time = ktime_get_ns(); + before = data->energy[attr->index]; + + leftover = schedule_timeout_interruptible( + msecs_to_jiffies(data->power_period[attr->index]) + ); + if (leftover) { + mutex_unlock(&data->lock); + return 0; + } + + update_aem_energy_one(data, attr->index); + time = ktime_get_ns() - time; + after = data->energy[attr->index]; + mutex_unlock(&data->lock); + + delta = (after - before) * UJ_PER_MJ; + + return sprintf(buf, "%llu\n", + (unsigned long long)div64_u64(delta * NSEC_PER_SEC, time)); +} + +/* Display energy use */ +static ssize_t aem_show_energy(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct aem_data *a = dev_get_drvdata(dev); + mutex_lock(&a->lock); + update_aem_energy_one(a, attr->index); + mutex_unlock(&a->lock); + + return sprintf(buf, "%llu\n", + (unsigned long long)a->energy[attr->index] * 1000); +} + +/* Display power interval registers */ +static ssize_t aem_show_power_period(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct aem_data *a = dev_get_drvdata(dev); + a->update(a); + + return sprintf(buf, "%lu\n", a->power_period[attr->index]); +} + +/* Set power interval registers */ +static ssize_t aem_set_power_period(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct aem_data *a = dev_get_drvdata(dev); + unsigned long temp; + int res; + + res = kstrtoul(buf, 10, &temp); + if (res) + return res; + + if (temp < AEM_MIN_POWER_INTERVAL) + return -EINVAL; + + mutex_lock(&a->lock); + a->power_period[attr->index] = temp; + mutex_unlock(&a->lock); + + return count; +} + +/* Discover sensors on an AEM device */ +static int aem_register_sensors(struct aem_data *data, + const struct aem_ro_sensor_template *ro, + const struct aem_rw_sensor_template *rw) +{ + struct device *dev = &data->pdev->dev; + struct sensor_device_attribute *sensors = data->sensors; + int err; + + /* Set up read-only sensors */ + while (ro->label) { + sysfs_attr_init(&sensors->dev_attr.attr); + sensors->dev_attr.attr.name = ro->label; + sensors->dev_attr.attr.mode = S_IRUGO; + sensors->dev_attr.show = ro->show; + sensors->index = ro->index; + + err = device_create_file(dev, &sensors->dev_attr); + if (err) { + sensors->dev_attr.attr.name = NULL; + goto error; + } + sensors++; + ro++; + } + + /* Set up read-write sensors */ + while (rw->label) { + sysfs_attr_init(&sensors->dev_attr.attr); + sensors->dev_attr.attr.name = rw->label; + sensors->dev_attr.attr.mode = S_IRUGO | S_IWUSR; + sensors->dev_attr.show = rw->show; + sensors->dev_attr.store = rw->set; + sensors->index = rw->index; + + err = device_create_file(dev, &sensors->dev_attr); + if (err) { + sensors->dev_attr.attr.name = NULL; + goto error; + } + sensors++; + rw++; + } + + err = device_create_file(dev, &sensor_dev_attr_name.dev_attr); + if (err) + goto error; + err = device_create_file(dev, &sensor_dev_attr_version.dev_attr); + return err; + +error: + aem_remove_sensors(data); + return err; +} + +/* sysfs support functions for AEM2 sensors */ + +/* Display temperature use */ +static ssize_t aem2_show_temp(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct aem_data *a = dev_get_drvdata(dev); + a->update(a); + + return sprintf(buf, "%u\n", a->temp[attr->index] * 1000); +} + +/* Display power-capping registers */ +static ssize_t aem2_show_pcap_value(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct aem_data *a = dev_get_drvdata(dev); + a->update(a); + + return sprintf(buf, "%u\n", a->pcap[attr->index] * 100000); +} + +/* Remove sensors attached to an AEM device */ +static void aem_remove_sensors(struct aem_data *data) +{ + int i; + + for (i = 0; i < AEM_NUM_SENSORS; i++) { + if (!data->sensors[i].dev_attr.attr.name) + continue; + device_remove_file(&data->pdev->dev, + &data->sensors[i].dev_attr); + } + + device_remove_file(&data->pdev->dev, + &sensor_dev_attr_name.dev_attr); + device_remove_file(&data->pdev->dev, + &sensor_dev_attr_version.dev_attr); +} + +/* Sensor probe functions */ + +/* Description of AEM1 sensors */ +static const struct aem_ro_sensor_template aem1_ro_sensors[] = { +{"energy1_input", aem_show_energy, 0}, +{"power1_average", aem_show_power, 0}, +{NULL, NULL, 0}, +}; + +static const struct aem_rw_sensor_template aem1_rw_sensors[] = { +{"power1_average_interval", aem_show_power_period, aem_set_power_period, 0}, +{NULL, NULL, NULL, 0}, +}; + +/* Description of AEM2 sensors */ +static const struct aem_ro_sensor_template aem2_ro_sensors[] = { +{"energy1_input", aem_show_energy, 0}, +{"energy2_input", aem_show_energy, 1}, +{"power1_average", aem_show_power, 0}, +{"power2_average", aem_show_power, 1}, +{"temp1_input", aem2_show_temp, 0}, +{"temp2_input", aem2_show_temp, 1}, + +{"power4_average", aem2_show_pcap_value, POWER_CAP_MAX_HOTPLUG}, +{"power5_average", aem2_show_pcap_value, POWER_CAP_MAX}, +{"power6_average", aem2_show_pcap_value, POWER_CAP_MIN_WARNING}, +{"power7_average", aem2_show_pcap_value, POWER_CAP_MIN}, + +{"power3_average", aem2_show_pcap_value, POWER_AUX}, +{"power_cap", aem2_show_pcap_value, POWER_CAP}, +{NULL, NULL, 0}, +}; + +static const struct aem_rw_sensor_template aem2_rw_sensors[] = { +{"power1_average_interval", aem_show_power_period, aem_set_power_period, 0}, +{"power2_average_interval", aem_show_power_period, aem_set_power_period, 1}, +{NULL, NULL, NULL, 0}, +}; + +/* Set up AEM1 sensor attrs */ +static int aem1_find_sensors(struct aem_data *data) +{ + return aem_register_sensors(data, aem1_ro_sensors, aem1_rw_sensors); +} + +/* Set up AEM2 sensor attrs */ +static int aem2_find_sensors(struct aem_data *data) +{ + return aem_register_sensors(data, aem2_ro_sensors, aem2_rw_sensors); +} + +/* Module init/exit routines */ + +static int __init aem_init(void) +{ + int res; + + res = driver_register(&aem_driver.driver); + if (res) { + pr_err("Can't register aem driver\n"); + return res; + } + + res = ipmi_smi_watcher_register(&driver_data.bmc_events); + if (res) + goto ipmi_reg_err; + return 0; + +ipmi_reg_err: + driver_unregister(&aem_driver.driver); + return res; + +} + +static void __exit aem_exit(void) +{ + struct aem_data *p1, *next1; + + ipmi_smi_watcher_unregister(&driver_data.bmc_events); + driver_unregister(&aem_driver.driver); + list_for_each_entry_safe(p1, next1, &driver_data.aem_devices, list) + aem_delete(p1); +} + +MODULE_AUTHOR("Darrick J. Wong "); +MODULE_DESCRIPTION("IBM AEM power/temp/energy sensor driver"); +MODULE_LICENSE("GPL"); + +module_init(aem_init); +module_exit(aem_exit); + +MODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3350-*"); +MODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3550-*"); +MODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3650-*"); +MODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3655-*"); +MODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3755-*"); +MODULE_ALIAS("dmi:bvnIBM:*:pnIBM3850M2/x3950M2-*"); +MODULE_ALIAS("dmi:bvnIBM:*:pnIBMBladeHC10-*"); diff --git a/drivers/hwmon/ibmpex.c b/drivers/hwmon/ibmpex.c new file mode 100644 index 000000000..ab72cabf5 --- /dev/null +++ b/drivers/hwmon/ibmpex.c @@ -0,0 +1,615 @@ +/* + * A hwmon driver for the IBM PowerExecutive temperature/power sensors + * Copyright (C) 2007 IBM + * + * Author: Darrick J. Wong + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define REFRESH_INTERVAL (2 * HZ) +#define DRVNAME "ibmpex" + +#define PEX_GET_VERSION 1 +#define PEX_GET_SENSOR_COUNT 2 +#define PEX_GET_SENSOR_NAME 3 +#define PEX_RESET_HIGH_LOW 4 +#define PEX_GET_SENSOR_DATA 6 + +#define PEX_NET_FUNCTION 0x3A +#define PEX_COMMAND 0x3C + +static inline u16 extract_value(const char *data, int offset) +{ + return be16_to_cpup((__be16 *)&data[offset]); +} + +#define TEMP_SENSOR 1 +#define POWER_SENSOR 2 + +#define PEX_SENSOR_TYPE_LEN 3 +static u8 const power_sensor_sig[] = {0x70, 0x77, 0x72}; +static u8 const temp_sensor_sig[] = {0x74, 0x65, 0x6D}; + +#define PEX_MULT_LEN 2 +static u8 const watt_sensor_sig[] = {0x41, 0x43}; + +#define PEX_NUM_SENSOR_FUNCS 3 +static const char * const sensor_name_suffixes[] = { + "", + "_lowest", + "_highest" +}; + +static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data); +static void ibmpex_register_bmc(int iface, struct device *dev); +static void ibmpex_bmc_gone(int iface); + +struct ibmpex_sensor_data { + int in_use; + s16 values[PEX_NUM_SENSOR_FUNCS]; + int multiplier; + + struct sensor_device_attribute_2 attr[PEX_NUM_SENSOR_FUNCS]; +}; + +struct ibmpex_bmc_data { + struct list_head list; + struct device *hwmon_dev; + struct device *bmc_device; + struct mutex lock; + char valid; + unsigned long last_updated; /* In jiffies */ + + struct ipmi_addr address; + struct completion read_complete; + ipmi_user_t user; + int interface; + + struct kernel_ipmi_msg tx_message; + unsigned char tx_msg_data[IPMI_MAX_MSG_LENGTH]; + long tx_msgid; + + unsigned char rx_msg_data[IPMI_MAX_MSG_LENGTH]; + unsigned long rx_msg_len; + unsigned char rx_result; + int rx_recv_type; + + unsigned char sensor_major; + unsigned char sensor_minor; + + unsigned char num_sensors; + struct ibmpex_sensor_data *sensors; +}; + +struct ibmpex_driver_data { + struct list_head bmc_data; + struct ipmi_smi_watcher bmc_events; + struct ipmi_user_hndl ipmi_hndlrs; +}; + +static struct ibmpex_driver_data driver_data = { + .bmc_data = LIST_HEAD_INIT(driver_data.bmc_data), + .bmc_events = { + .owner = THIS_MODULE, + .new_smi = ibmpex_register_bmc, + .smi_gone = ibmpex_bmc_gone, + }, + .ipmi_hndlrs = { + .ipmi_recv_hndl = ibmpex_msg_handler, + }, +}; + +static int ibmpex_send_message(struct ibmpex_bmc_data *data) +{ + int err; + + err = ipmi_validate_addr(&data->address, sizeof(data->address)); + if (err) + goto out; + + data->tx_msgid++; + err = ipmi_request_settime(data->user, &data->address, data->tx_msgid, + &data->tx_message, data, 0, 0, 0); + if (err) + goto out1; + + return 0; +out1: + dev_err(data->bmc_device, "request_settime=%x\n", err); + return err; +out: + dev_err(data->bmc_device, "validate_addr=%x\n", err); + return err; +} + +static int ibmpex_ver_check(struct ibmpex_bmc_data *data) +{ + data->tx_msg_data[0] = PEX_GET_VERSION; + data->tx_message.data_len = 1; + ibmpex_send_message(data); + + wait_for_completion(&data->read_complete); + + if (data->rx_result || data->rx_msg_len != 6) + return -ENOENT; + + data->sensor_major = data->rx_msg_data[0]; + data->sensor_minor = data->rx_msg_data[1]; + + dev_info(data->bmc_device, + "Found BMC with sensor interface v%d.%d %d-%02d-%02d on interface %d\n", + data->sensor_major, + data->sensor_minor, + extract_value(data->rx_msg_data, 2), + data->rx_msg_data[4], + data->rx_msg_data[5], + data->interface); + + return 0; +} + +static int ibmpex_query_sensor_count(struct ibmpex_bmc_data *data) +{ + data->tx_msg_data[0] = PEX_GET_SENSOR_COUNT; + data->tx_message.data_len = 1; + ibmpex_send_message(data); + + wait_for_completion(&data->read_complete); + + if (data->rx_result || data->rx_msg_len != 1) + return -ENOENT; + + return data->rx_msg_data[0]; +} + +static int ibmpex_query_sensor_name(struct ibmpex_bmc_data *data, int sensor) +{ + data->tx_msg_data[0] = PEX_GET_SENSOR_NAME; + data->tx_msg_data[1] = sensor; + data->tx_message.data_len = 2; + ibmpex_send_message(data); + + wait_for_completion(&data->read_complete); + + if (data->rx_result || data->rx_msg_len < 1) + return -ENOENT; + + return 0; +} + +static int ibmpex_query_sensor_data(struct ibmpex_bmc_data *data, int sensor) +{ + data->tx_msg_data[0] = PEX_GET_SENSOR_DATA; + data->tx_msg_data[1] = sensor; + data->tx_message.data_len = 2; + ibmpex_send_message(data); + + wait_for_completion(&data->read_complete); + + if (data->rx_result || data->rx_msg_len < 26) { + dev_err(data->bmc_device, "Error reading sensor %d.\n", + sensor); + return -ENOENT; + } + + return 0; +} + +static int ibmpex_reset_high_low_data(struct ibmpex_bmc_data *data) +{ + data->tx_msg_data[0] = PEX_RESET_HIGH_LOW; + data->tx_message.data_len = 1; + ibmpex_send_message(data); + + wait_for_completion(&data->read_complete); + + return 0; +} + +static void ibmpex_update_device(struct ibmpex_bmc_data *data) +{ + int i, err; + + mutex_lock(&data->lock); + if (time_before(jiffies, data->last_updated + REFRESH_INTERVAL) && + data->valid) + goto out; + + for (i = 0; i < data->num_sensors; i++) { + if (!data->sensors[i].in_use) + continue; + err = ibmpex_query_sensor_data(data, i); + if (err) + continue; + data->sensors[i].values[0] = + extract_value(data->rx_msg_data, 16); + data->sensors[i].values[1] = + extract_value(data->rx_msg_data, 18); + data->sensors[i].values[2] = + extract_value(data->rx_msg_data, 20); + } + + data->last_updated = jiffies; + data->valid = 1; + +out: + mutex_unlock(&data->lock); +} + +static struct ibmpex_bmc_data *get_bmc_data(int iface) +{ + struct ibmpex_bmc_data *p, *next; + + list_for_each_entry_safe(p, next, &driver_data.bmc_data, list) + if (p->interface == iface) + return p; + + return NULL; +} + +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return sprintf(buf, "%s\n", DRVNAME); +} +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); + +static ssize_t ibmpex_show_sensor(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct ibmpex_bmc_data *data = dev_get_drvdata(dev); + int mult = data->sensors[attr->index].multiplier; + ibmpex_update_device(data); + + return sprintf(buf, "%d\n", + data->sensors[attr->index].values[attr->nr] * mult); +} + +static ssize_t ibmpex_reset_high_low(struct device *dev, + struct device_attribute *devattr, + const char *buf, + size_t count) +{ + struct ibmpex_bmc_data *data = dev_get_drvdata(dev); + + ibmpex_reset_high_low_data(data); + + return count; +} + +static SENSOR_DEVICE_ATTR(reset_high_low, S_IWUSR, NULL, + ibmpex_reset_high_low, 0); + +static int is_power_sensor(const char *sensor_id, int len) +{ + if (len < PEX_SENSOR_TYPE_LEN) + return 0; + + if (!memcmp(sensor_id, power_sensor_sig, PEX_SENSOR_TYPE_LEN)) + return 1; + return 0; +} + +static int is_temp_sensor(const char *sensor_id, int len) +{ + if (len < PEX_SENSOR_TYPE_LEN) + return 0; + + if (!memcmp(sensor_id, temp_sensor_sig, PEX_SENSOR_TYPE_LEN)) + return 1; + return 0; +} + +static int power_sensor_multiplier(struct ibmpex_bmc_data *data, + const char *sensor_id, int len) +{ + int i; + + if (data->sensor_major == 2) + return 1000000; + + for (i = PEX_SENSOR_TYPE_LEN; i < len - 1; i++) + if (!memcmp(&sensor_id[i], watt_sensor_sig, PEX_MULT_LEN)) + return 1000000; + + return 100000; +} + +static int create_sensor(struct ibmpex_bmc_data *data, int type, + int counter, int sensor, int func) +{ + int err; + char *n; + + n = kmalloc(32, GFP_KERNEL); + if (!n) + return -ENOMEM; + + if (type == TEMP_SENSOR) + sprintf(n, "temp%d_input%s", + counter, sensor_name_suffixes[func]); + else if (type == POWER_SENSOR) + sprintf(n, "power%d_average%s", + counter, sensor_name_suffixes[func]); + + sysfs_attr_init(&data->sensors[sensor].attr[func].dev_attr.attr); + data->sensors[sensor].attr[func].dev_attr.attr.name = n; + data->sensors[sensor].attr[func].dev_attr.attr.mode = S_IRUGO; + data->sensors[sensor].attr[func].dev_attr.show = ibmpex_show_sensor; + data->sensors[sensor].attr[func].index = sensor; + data->sensors[sensor].attr[func].nr = func; + + err = device_create_file(data->bmc_device, + &data->sensors[sensor].attr[func].dev_attr); + if (err) { + data->sensors[sensor].attr[func].dev_attr.attr.name = NULL; + kfree(n); + return err; + } + + return 0; +} + +static int ibmpex_find_sensors(struct ibmpex_bmc_data *data) +{ + int i, j, err; + int sensor_type; + int sensor_counter; + int num_power = 0; + int num_temp = 0; + + err = ibmpex_query_sensor_count(data); + if (err <= 0) + return -ENOENT; + data->num_sensors = err; + + data->sensors = kcalloc(data->num_sensors, sizeof(*data->sensors), + GFP_KERNEL); + if (!data->sensors) + return -ENOMEM; + + for (i = 0; i < data->num_sensors; i++) { + err = ibmpex_query_sensor_name(data, i); + if (err) + continue; + + if (is_power_sensor(data->rx_msg_data, data->rx_msg_len)) { + sensor_type = POWER_SENSOR; + num_power++; + sensor_counter = num_power; + data->sensors[i].multiplier = + power_sensor_multiplier(data, + data->rx_msg_data, + data->rx_msg_len); + } else if (is_temp_sensor(data->rx_msg_data, + data->rx_msg_len)) { + sensor_type = TEMP_SENSOR; + num_temp++; + sensor_counter = num_temp; + data->sensors[i].multiplier = 1000; + } else + continue; + + data->sensors[i].in_use = 1; + + /* Create attributes */ + for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) { + err = create_sensor(data, sensor_type, sensor_counter, + i, j); + if (err) + goto exit_remove; + } + } + + err = device_create_file(data->bmc_device, + &sensor_dev_attr_reset_high_low.dev_attr); + if (err) + goto exit_remove; + + err = device_create_file(data->bmc_device, + &sensor_dev_attr_name.dev_attr); + if (err) + goto exit_remove; + + return 0; + +exit_remove: + device_remove_file(data->bmc_device, + &sensor_dev_attr_reset_high_low.dev_attr); + device_remove_file(data->bmc_device, &sensor_dev_attr_name.dev_attr); + for (i = 0; i < data->num_sensors; i++) + for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) { + if (!data->sensors[i].attr[j].dev_attr.attr.name) + continue; + device_remove_file(data->bmc_device, + &data->sensors[i].attr[j].dev_attr); + kfree(data->sensors[i].attr[j].dev_attr.attr.name); + } + + kfree(data->sensors); + return err; +} + +static void ibmpex_register_bmc(int iface, struct device *dev) +{ + struct ibmpex_bmc_data *data; + int err; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return; + + data->address.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; + data->address.channel = IPMI_BMC_CHANNEL; + data->address.data[0] = 0; + data->interface = iface; + data->bmc_device = dev; + + /* Create IPMI messaging interface user */ + err = ipmi_create_user(data->interface, &driver_data.ipmi_hndlrs, + data, &data->user); + if (err < 0) { + dev_err(dev, + "Unable to register user with IPMI interface %d\n", + data->interface); + goto out; + } + + mutex_init(&data->lock); + + /* Initialize message */ + data->tx_msgid = 0; + init_completion(&data->read_complete); + data->tx_message.netfn = PEX_NET_FUNCTION; + data->tx_message.cmd = PEX_COMMAND; + data->tx_message.data = data->tx_msg_data; + + /* Does this BMC support PowerExecutive? */ + err = ibmpex_ver_check(data); + if (err) + goto out_user; + + /* Register the BMC as a HWMON class device */ + data->hwmon_dev = hwmon_device_register(data->bmc_device); + + if (IS_ERR(data->hwmon_dev)) { + dev_err(data->bmc_device, + "Unable to register hwmon device for IPMI interface %d\n", + data->interface); + goto out_user; + } + + /* finally add the new bmc data to the bmc data list */ + dev_set_drvdata(dev, data); + list_add_tail(&data->list, &driver_data.bmc_data); + + /* Now go find all the sensors */ + err = ibmpex_find_sensors(data); + if (err) { + dev_err(data->bmc_device, "Error %d finding sensors\n", err); + goto out_register; + } + + return; + +out_register: + hwmon_device_unregister(data->hwmon_dev); +out_user: + ipmi_destroy_user(data->user); +out: + kfree(data); +} + +static void ibmpex_bmc_delete(struct ibmpex_bmc_data *data) +{ + int i, j; + + device_remove_file(data->bmc_device, + &sensor_dev_attr_reset_high_low.dev_attr); + device_remove_file(data->bmc_device, &sensor_dev_attr_name.dev_attr); + for (i = 0; i < data->num_sensors; i++) + for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) { + if (!data->sensors[i].attr[j].dev_attr.attr.name) + continue; + device_remove_file(data->bmc_device, + &data->sensors[i].attr[j].dev_attr); + kfree(data->sensors[i].attr[j].dev_attr.attr.name); + } + + list_del(&data->list); + dev_set_drvdata(data->bmc_device, NULL); + hwmon_device_unregister(data->hwmon_dev); + ipmi_destroy_user(data->user); + kfree(data->sensors); + kfree(data); +} + +static void ibmpex_bmc_gone(int iface) +{ + struct ibmpex_bmc_data *data = get_bmc_data(iface); + + if (!data) + return; + + ibmpex_bmc_delete(data); +} + +static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data) +{ + struct ibmpex_bmc_data *data = (struct ibmpex_bmc_data *)user_msg_data; + + if (msg->msgid != data->tx_msgid) { + dev_err(data->bmc_device, + "Mismatch between received msgid (%02x) and transmitted msgid (%02x)!\n", + (int)msg->msgid, + (int)data->tx_msgid); + ipmi_free_recv_msg(msg); + return; + } + + data->rx_recv_type = msg->recv_type; + if (msg->msg.data_len > 0) + data->rx_result = msg->msg.data[0]; + else + data->rx_result = IPMI_UNKNOWN_ERR_COMPLETION_CODE; + + if (msg->msg.data_len > 1) { + data->rx_msg_len = msg->msg.data_len - 1; + memcpy(data->rx_msg_data, msg->msg.data + 1, data->rx_msg_len); + } else + data->rx_msg_len = 0; + + ipmi_free_recv_msg(msg); + complete(&data->read_complete); +} + +static int __init ibmpex_init(void) +{ + return ipmi_smi_watcher_register(&driver_data.bmc_events); +} + +static void __exit ibmpex_exit(void) +{ + struct ibmpex_bmc_data *p, *next; + + ipmi_smi_watcher_unregister(&driver_data.bmc_events); + list_for_each_entry_safe(p, next, &driver_data.bmc_data, list) + ibmpex_bmc_delete(p); +} + +MODULE_AUTHOR("Darrick J. Wong "); +MODULE_DESCRIPTION("IBM PowerExecutive power/temperature sensor driver"); +MODULE_LICENSE("GPL"); + +module_init(ibmpex_init); +module_exit(ibmpex_exit); + +MODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3350-*"); +MODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3550-*"); +MODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3650-*"); +MODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3655-*"); +MODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3755-*"); diff --git a/drivers/hwmon/ibmpowernv.c b/drivers/hwmon/ibmpowernv.c new file mode 100644 index 000000000..64d05edff --- /dev/null +++ b/drivers/hwmon/ibmpowernv.c @@ -0,0 +1,737 @@ +/* + * IBM PowerNV platform sensors for temperature/fan/voltage/power + * Copyright (C) 2014 IBM + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. + */ + +#define DRVNAME "ibmpowernv" +#define pr_fmt(fmt) DRVNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define MAX_ATTR_LEN 32 +#define MAX_LABEL_LEN 64 + +/* Sensor suffix name from DT */ +#define DT_FAULT_ATTR_SUFFIX "faulted" +#define DT_DATA_ATTR_SUFFIX "data" +#define DT_THRESHOLD_ATTR_SUFFIX "thrs" + +/* + * Enumerates all the types of sensors in the POWERNV platform and does index + * into 'struct sensor_group' + */ +enum sensors { + FAN, + TEMP, + POWER_SUPPLY, + POWER_INPUT, + CURRENT, + ENERGY, + MAX_SENSOR_TYPE, +}; + +#define INVALID_INDEX (-1U) + +/* + * 'compatible' string properties for sensor types as defined in old + * PowerNV firmware (skiboot). These are ordered as 'enum sensors'. + */ +static const char * const legacy_compatibles[] = { + "ibm,opal-sensor-cooling-fan", + "ibm,opal-sensor-amb-temp", + "ibm,opal-sensor-power-supply", + "ibm,opal-sensor-power" +}; + +static struct sensor_group { + const char *name; /* matches property 'sensor-type' */ + struct attribute_group group; + u32 attr_count; + u32 hwmon_index; +} sensor_groups[] = { + { "fan" }, + { "temp" }, + { "in" }, + { "power" }, + { "curr" }, + { "energy" }, +}; + +struct sensor_data { + u32 id; /* An opaque id of the firmware for each sensor */ + u32 hwmon_index; + u32 opal_index; + enum sensors type; + char label[MAX_LABEL_LEN]; + char name[MAX_ATTR_LEN]; + struct device_attribute dev_attr; + struct sensor_group_data *sgrp_data; +}; + +struct sensor_group_data { + struct mutex mutex; + u32 gid; + bool enable; +}; + +struct platform_data { + const struct attribute_group *attr_groups[MAX_SENSOR_TYPE + 1]; + struct sensor_group_data *sgrp_data; + u32 sensors_count; /* Total count of sensors from each group */ + u32 nr_sensor_groups; /* Total number of sensor groups */ +}; + +static ssize_t show_sensor(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_data *sdata = container_of(devattr, struct sensor_data, + dev_attr); + ssize_t ret; + u64 x; + + if (sdata->sgrp_data && !sdata->sgrp_data->enable) + return -ENODATA; + + ret = opal_get_sensor_data_u64(sdata->id, &x); + + if (ret) + return ret; + + /* Convert temperature to milli-degrees */ + if (sdata->type == TEMP) + x *= 1000; + /* Convert power to micro-watts */ + else if (sdata->type == POWER_INPUT) + x *= 1000000; + + return sprintf(buf, "%llu\n", x); +} + +static ssize_t show_enable(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_data *sdata = container_of(devattr, struct sensor_data, + dev_attr); + + return sprintf(buf, "%u\n", sdata->sgrp_data->enable); +} + +static ssize_t store_enable(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_data *sdata = container_of(devattr, struct sensor_data, + dev_attr); + struct sensor_group_data *sgrp_data = sdata->sgrp_data; + int ret; + bool data; + + ret = kstrtobool(buf, &data); + if (ret) + return ret; + + ret = mutex_lock_interruptible(&sgrp_data->mutex); + if (ret) + return ret; + + if (data != sgrp_data->enable) { + ret = sensor_group_enable(sgrp_data->gid, data); + if (!ret) + sgrp_data->enable = data; + } + + if (!ret) + ret = count; + + mutex_unlock(&sgrp_data->mutex); + return ret; +} + +static ssize_t show_label(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_data *sdata = container_of(devattr, struct sensor_data, + dev_attr); + + return sprintf(buf, "%s\n", sdata->label); +} + +static int get_logical_cpu(int hwcpu) +{ + int cpu; + + for_each_possible_cpu(cpu) + if (get_hard_smp_processor_id(cpu) == hwcpu) + return cpu; + + return -ENOENT; +} + +static void make_sensor_label(struct device_node *np, + struct sensor_data *sdata, const char *label) +{ + u32 id; + size_t n; + + n = snprintf(sdata->label, sizeof(sdata->label), "%s", label); + + /* + * Core temp pretty print + */ + if (!of_property_read_u32(np, "ibm,pir", &id)) { + int cpuid = get_logical_cpu(id); + + if (cpuid >= 0) + /* + * The digital thermal sensors are associated + * with a core. + */ + n += snprintf(sdata->label + n, + sizeof(sdata->label) - n, " %d", + cpuid); + else + n += snprintf(sdata->label + n, + sizeof(sdata->label) - n, " phy%d", id); + } + + /* + * Membuffer pretty print + */ + if (!of_property_read_u32(np, "ibm,chip-id", &id)) + n += snprintf(sdata->label + n, sizeof(sdata->label) - n, + " %d", id & 0xffff); +} + +static int get_sensor_index_attr(const char *name, u32 *index, char *attr) +{ + char *hash_pos = strchr(name, '#'); + char buf[8] = { 0 }; + char *dash_pos; + u32 copy_len; + int err; + + if (!hash_pos) + return -EINVAL; + + dash_pos = strchr(hash_pos, '-'); + if (!dash_pos) + return -EINVAL; + + copy_len = dash_pos - hash_pos - 1; + if (copy_len >= sizeof(buf)) + return -EINVAL; + + strncpy(buf, hash_pos + 1, copy_len); + + err = kstrtou32(buf, 10, index); + if (err) + return err; + + strncpy(attr, dash_pos + 1, MAX_ATTR_LEN); + + return 0; +} + +static const char *convert_opal_attr_name(enum sensors type, + const char *opal_attr) +{ + const char *attr_name = NULL; + + if (!strcmp(opal_attr, DT_FAULT_ATTR_SUFFIX)) { + attr_name = "fault"; + } else if (!strcmp(opal_attr, DT_DATA_ATTR_SUFFIX)) { + attr_name = "input"; + } else if (!strcmp(opal_attr, DT_THRESHOLD_ATTR_SUFFIX)) { + if (type == TEMP) + attr_name = "max"; + else if (type == FAN) + attr_name = "min"; + } + + return attr_name; +} + +/* + * This function translates the DT node name into the 'hwmon' attribute name. + * IBMPOWERNV device node appear like cooling-fan#2-data, amb-temp#1-thrs etc. + * which need to be mapped as fan2_input, temp1_max respectively before + * populating them inside hwmon device class. + */ +static const char *parse_opal_node_name(const char *node_name, + enum sensors type, u32 *index) +{ + char attr_suffix[MAX_ATTR_LEN]; + const char *attr_name; + int err; + + err = get_sensor_index_attr(node_name, index, attr_suffix); + if (err) + return ERR_PTR(err); + + attr_name = convert_opal_attr_name(type, attr_suffix); + if (!attr_name) + return ERR_PTR(-ENOENT); + + return attr_name; +} + +static int get_sensor_type(struct device_node *np) +{ + enum sensors type; + const char *str; + + for (type = 0; type < ARRAY_SIZE(legacy_compatibles); type++) { + if (of_device_is_compatible(np, legacy_compatibles[type])) + return type; + } + + /* + * Let's check if we have a newer device tree + */ + if (!of_device_is_compatible(np, "ibm,opal-sensor")) + return MAX_SENSOR_TYPE; + + if (of_property_read_string(np, "sensor-type", &str)) + return MAX_SENSOR_TYPE; + + for (type = 0; type < MAX_SENSOR_TYPE; type++) + if (!strcmp(str, sensor_groups[type].name)) + return type; + + return MAX_SENSOR_TYPE; +} + +static u32 get_sensor_hwmon_index(struct sensor_data *sdata, + struct sensor_data *sdata_table, int count) +{ + int i; + + /* + * We don't use the OPAL index on newer device trees + */ + if (sdata->opal_index != INVALID_INDEX) { + for (i = 0; i < count; i++) + if (sdata_table[i].opal_index == sdata->opal_index && + sdata_table[i].type == sdata->type) + return sdata_table[i].hwmon_index; + } + return ++sensor_groups[sdata->type].hwmon_index; +} + +static int init_sensor_group_data(struct platform_device *pdev, + struct platform_data *pdata) +{ + struct sensor_group_data *sgrp_data; + struct device_node *groups, *sgrp; + int count = 0, ret = 0; + enum sensors type; + + groups = of_find_compatible_node(NULL, NULL, "ibm,opal-sensor-group"); + if (!groups) + return ret; + + for_each_child_of_node(groups, sgrp) { + type = get_sensor_type(sgrp); + if (type != MAX_SENSOR_TYPE) + pdata->nr_sensor_groups++; + } + + if (!pdata->nr_sensor_groups) + goto out; + + sgrp_data = devm_kcalloc(&pdev->dev, pdata->nr_sensor_groups, + sizeof(*sgrp_data), GFP_KERNEL); + if (!sgrp_data) { + ret = -ENOMEM; + goto out; + } + + for_each_child_of_node(groups, sgrp) { + u32 gid; + + type = get_sensor_type(sgrp); + if (type == MAX_SENSOR_TYPE) + continue; + + if (of_property_read_u32(sgrp, "sensor-group-id", &gid)) + continue; + + if (of_count_phandle_with_args(sgrp, "sensors", NULL) <= 0) + continue; + + sensor_groups[type].attr_count++; + sgrp_data[count].gid = gid; + mutex_init(&sgrp_data[count].mutex); + sgrp_data[count++].enable = false; + } + + pdata->sgrp_data = sgrp_data; +out: + of_node_put(groups); + return ret; +} + +static struct sensor_group_data *get_sensor_group(struct platform_data *pdata, + struct device_node *node, + enum sensors gtype) +{ + struct sensor_group_data *sgrp_data = pdata->sgrp_data; + struct device_node *groups, *sgrp; + + groups = of_find_compatible_node(NULL, NULL, "ibm,opal-sensor-group"); + if (!groups) + return NULL; + + for_each_child_of_node(groups, sgrp) { + struct of_phandle_iterator it; + u32 gid; + int rc, i; + enum sensors type; + + type = get_sensor_type(sgrp); + if (type != gtype) + continue; + + if (of_property_read_u32(sgrp, "sensor-group-id", &gid)) + continue; + + of_for_each_phandle(&it, rc, sgrp, "sensors", NULL, 0) + if (it.phandle == node->phandle) { + of_node_put(it.node); + break; + } + + if (rc) + continue; + + for (i = 0; i < pdata->nr_sensor_groups; i++) + if (gid == sgrp_data[i].gid) { + of_node_put(sgrp); + of_node_put(groups); + return &sgrp_data[i]; + } + } + + of_node_put(groups); + return NULL; +} + +static int populate_attr_groups(struct platform_device *pdev) +{ + struct platform_data *pdata = platform_get_drvdata(pdev); + const struct attribute_group **pgroups = pdata->attr_groups; + struct device_node *opal, *np; + enum sensors type; + int ret; + + ret = init_sensor_group_data(pdev, pdata); + if (ret) + return ret; + + opal = of_find_node_by_path("/ibm,opal/sensors"); + for_each_child_of_node(opal, np) { + const char *label; + + if (np->name == NULL) + continue; + + type = get_sensor_type(np); + if (type == MAX_SENSOR_TYPE) + continue; + + sensor_groups[type].attr_count++; + + /* + * add attributes for labels, min and max + */ + if (!of_property_read_string(np, "label", &label)) + sensor_groups[type].attr_count++; + if (of_find_property(np, "sensor-data-min", NULL)) + sensor_groups[type].attr_count++; + if (of_find_property(np, "sensor-data-max", NULL)) + sensor_groups[type].attr_count++; + } + + of_node_put(opal); + + for (type = 0; type < MAX_SENSOR_TYPE; type++) { + sensor_groups[type].group.attrs = devm_kcalloc(&pdev->dev, + sensor_groups[type].attr_count + 1, + sizeof(struct attribute *), + GFP_KERNEL); + if (!sensor_groups[type].group.attrs) + return -ENOMEM; + + pgroups[type] = &sensor_groups[type].group; + pdata->sensors_count += sensor_groups[type].attr_count; + sensor_groups[type].attr_count = 0; + } + + return 0; +} + +static void create_hwmon_attr(struct sensor_data *sdata, const char *attr_name, + ssize_t (*show)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*store)(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count)) +{ + snprintf(sdata->name, MAX_ATTR_LEN, "%s%d_%s", + sensor_groups[sdata->type].name, sdata->hwmon_index, + attr_name); + + sysfs_attr_init(&sdata->dev_attr.attr); + sdata->dev_attr.attr.name = sdata->name; + sdata->dev_attr.show = show; + if (store) { + sdata->dev_attr.store = store; + sdata->dev_attr.attr.mode = 0664; + } else { + sdata->dev_attr.attr.mode = 0444; + } +} + +static void populate_sensor(struct sensor_data *sdata, int od, int hd, int sid, + const char *attr_name, enum sensors type, + const struct attribute_group *pgroup, + struct sensor_group_data *sgrp_data, + ssize_t (*show)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*store)(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count)) +{ + sdata->id = sid; + sdata->type = type; + sdata->opal_index = od; + sdata->hwmon_index = hd; + create_hwmon_attr(sdata, attr_name, show, store); + pgroup->attrs[sensor_groups[type].attr_count++] = &sdata->dev_attr.attr; + sdata->sgrp_data = sgrp_data; +} + +static char *get_max_attr(enum sensors type) +{ + switch (type) { + case POWER_INPUT: + return "input_highest"; + default: + return "highest"; + } +} + +static char *get_min_attr(enum sensors type) +{ + switch (type) { + case POWER_INPUT: + return "input_lowest"; + default: + return "lowest"; + } +} + +/* + * Iterate through the device tree for each child of 'sensors' node, create + * a sysfs attribute file, the file is named by translating the DT node name + * to the name required by the higher 'hwmon' driver like fan1_input, temp1_max + * etc.. + */ +static int create_device_attrs(struct platform_device *pdev) +{ + struct platform_data *pdata = platform_get_drvdata(pdev); + const struct attribute_group **pgroups = pdata->attr_groups; + struct device_node *opal, *np; + struct sensor_data *sdata; + u32 count = 0; + u32 group_attr_id[MAX_SENSOR_TYPE] = {0}; + + sdata = devm_kcalloc(&pdev->dev, + pdata->sensors_count, sizeof(*sdata), + GFP_KERNEL); + if (!sdata) + return -ENOMEM; + + opal = of_find_node_by_path("/ibm,opal/sensors"); + for_each_child_of_node(opal, np) { + struct sensor_group_data *sgrp_data; + const char *attr_name; + u32 opal_index, hw_id; + u32 sensor_id; + const char *label; + enum sensors type; + + if (np->name == NULL) + continue; + + type = get_sensor_type(np); + if (type == MAX_SENSOR_TYPE) + continue; + + /* + * Newer device trees use a "sensor-data" property + * name for input. + */ + if (of_property_read_u32(np, "sensor-id", &sensor_id) && + of_property_read_u32(np, "sensor-data", &sensor_id)) { + dev_info(&pdev->dev, + "'sensor-id' missing in the node '%s'\n", + np->name); + continue; + } + + sdata[count].id = sensor_id; + sdata[count].type = type; + + /* + * If we can not parse the node name, it means we are + * running on a newer device tree. We can just forget + * about the OPAL index and use a defaut value for the + * hwmon attribute name + */ + attr_name = parse_opal_node_name(np->name, type, &opal_index); + if (IS_ERR(attr_name)) { + attr_name = "input"; + opal_index = INVALID_INDEX; + } + + hw_id = get_sensor_hwmon_index(&sdata[count], sdata, count); + sgrp_data = get_sensor_group(pdata, np, type); + populate_sensor(&sdata[count], opal_index, hw_id, sensor_id, + attr_name, type, pgroups[type], sgrp_data, + show_sensor, NULL); + count++; + + if (!of_property_read_string(np, "label", &label)) { + /* + * For the label attribute, we can reuse the + * "properties" of the previous "input" + * attribute. They are related to the same + * sensor. + */ + + make_sensor_label(np, &sdata[count], label); + populate_sensor(&sdata[count], opal_index, hw_id, + sensor_id, "label", type, pgroups[type], + NULL, show_label, NULL); + count++; + } + + if (!of_property_read_u32(np, "sensor-data-max", &sensor_id)) { + attr_name = get_max_attr(type); + populate_sensor(&sdata[count], opal_index, hw_id, + sensor_id, attr_name, type, + pgroups[type], sgrp_data, show_sensor, + NULL); + count++; + } + + if (!of_property_read_u32(np, "sensor-data-min", &sensor_id)) { + attr_name = get_min_attr(type); + populate_sensor(&sdata[count], opal_index, hw_id, + sensor_id, attr_name, type, + pgroups[type], sgrp_data, show_sensor, + NULL); + count++; + } + + if (sgrp_data && !sgrp_data->enable) { + sgrp_data->enable = true; + hw_id = ++group_attr_id[type]; + populate_sensor(&sdata[count], opal_index, hw_id, + sgrp_data->gid, "enable", type, + pgroups[type], sgrp_data, show_enable, + store_enable); + count++; + } + } + + of_node_put(opal); + return 0; +} + +static int ibmpowernv_probe(struct platform_device *pdev) +{ + struct platform_data *pdata; + struct device *hwmon_dev; + int err; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + platform_set_drvdata(pdev, pdata); + pdata->sensors_count = 0; + pdata->nr_sensor_groups = 0; + err = populate_attr_groups(pdev); + if (err) + return err; + + /* Create sysfs attribute data for each sensor found in the DT */ + err = create_device_attrs(pdev); + if (err) + return err; + + /* Finally, register with hwmon */ + hwmon_dev = devm_hwmon_device_register_with_groups(&pdev->dev, DRVNAME, + pdata, + pdata->attr_groups); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct platform_device_id opal_sensor_driver_ids[] = { + { + .name = "opal-sensor", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, opal_sensor_driver_ids); + +static const struct of_device_id opal_sensor_match[] = { + { .compatible = "ibm,opal-sensor" }, + { }, +}; +MODULE_DEVICE_TABLE(of, opal_sensor_match); + +static struct platform_driver ibmpowernv_driver = { + .probe = ibmpowernv_probe, + .id_table = opal_sensor_driver_ids, + .driver = { + .name = DRVNAME, + .of_match_table = opal_sensor_match, + }, +}; + +module_platform_driver(ibmpowernv_driver); + +MODULE_AUTHOR("Neelesh Gupta "); +MODULE_DESCRIPTION("IBM POWERNV platform sensors"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c new file mode 100644 index 000000000..2f3f875c0 --- /dev/null +++ b/drivers/hwmon/iio_hwmon.c @@ -0,0 +1,172 @@ +/* Hwmon client for industrial I/O devices + * + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * struct iio_hwmon_state - device instance state + * @channels: filled with array of channels from iio + * @num_channels: number of channels in channels (saves counting twice) + * @attr_group: the group of attributes + * @groups: null terminated array of attribute groups + * @attrs: null terminated array of attribute pointers. + */ +struct iio_hwmon_state { + struct iio_channel *channels; + int num_channels; + struct attribute_group attr_group; + const struct attribute_group *groups[2]; + struct attribute **attrs; +}; + +/* + * Assumes that IIO and hwmon operate in the same base units. + * This is supposed to be true, but needs verification for + * new channel types. + */ +static ssize_t iio_hwmon_read_val(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int result; + int ret; + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct iio_hwmon_state *state = dev_get_drvdata(dev); + + ret = iio_read_channel_processed(&state->channels[sattr->index], + &result); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", result); +} + +static int iio_hwmon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_hwmon_state *st; + struct sensor_device_attribute *a; + int ret, i; + int in_i = 1, temp_i = 1, curr_i = 1, humidity_i = 1; + enum iio_chan_type type; + struct iio_channel *channels; + const char *name = "iio_hwmon"; + struct device *hwmon_dev; + char *sname; + + if (dev->of_node && dev->of_node->name) + name = dev->of_node->name; + + channels = devm_iio_channel_get_all(dev); + if (IS_ERR(channels)) { + if (PTR_ERR(channels) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(channels); + } + + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); + if (st == NULL) + return -ENOMEM; + + st->channels = channels; + + /* count how many attributes we have */ + while (st->channels[st->num_channels].indio_dev) + st->num_channels++; + + st->attrs = devm_kcalloc(dev, + st->num_channels + 1, sizeof(*st->attrs), + GFP_KERNEL); + if (st->attrs == NULL) + return -ENOMEM; + + for (i = 0; i < st->num_channels; i++) { + a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL); + if (a == NULL) + return -ENOMEM; + + sysfs_attr_init(&a->dev_attr.attr); + ret = iio_get_channel_type(&st->channels[i], &type); + if (ret < 0) + return ret; + + switch (type) { + case IIO_VOLTAGE: + a->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL, + "in%d_input", + in_i++); + break; + case IIO_TEMP: + a->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL, + "temp%d_input", + temp_i++); + break; + case IIO_CURRENT: + a->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL, + "curr%d_input", + curr_i++); + break; + case IIO_HUMIDITYRELATIVE: + a->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL, + "humidity%d_input", + humidity_i++); + break; + default: + return -EINVAL; + } + if (a->dev_attr.attr.name == NULL) + return -ENOMEM; + + a->dev_attr.show = iio_hwmon_read_val; + a->dev_attr.attr.mode = S_IRUGO; + a->index = i; + st->attrs[i] = &a->dev_attr.attr; + } + + st->attr_group.attrs = st->attrs; + st->groups[0] = &st->attr_group; + + sname = devm_kstrdup(dev, name, GFP_KERNEL); + if (!sname) + return -ENOMEM; + + strreplace(sname, '-', '_'); + hwmon_dev = devm_hwmon_device_register_with_groups(dev, sname, st, + st->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct of_device_id iio_hwmon_of_match[] = { + { .compatible = "iio-hwmon", }, + { } +}; +MODULE_DEVICE_TABLE(of, iio_hwmon_of_match); + +static struct platform_driver __refdata iio_hwmon_driver = { + .driver = { + .name = "iio_hwmon", + .of_match_table = iio_hwmon_of_match, + }, + .probe = iio_hwmon_probe, +}; + +module_platform_driver(iio_hwmon_driver); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("IIO to hwmon driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/ina209.c b/drivers/hwmon/ina209.c new file mode 100644 index 000000000..aa0768ce8 --- /dev/null +++ b/drivers/hwmon/ina209.c @@ -0,0 +1,633 @@ +/* + * Driver for the Texas Instruments / Burr Brown INA209 + * Bidirectional Current/Power Monitor + * + * Copyright (C) 2012 Guenter Roeck + * + * Derived from Ira W. Snyder's original driver submission + * Copyright (C) 2008 Paul Hays + * Copyright (C) 2008-2009 Ira W. Snyder + * + * Aligned with ina2xx driver + * Copyright (C) 2012 Lothar Felten + * Thanks to Jan Volkering + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * Datasheet: + * http://www.ti.com/lit/gpn/ina209 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* register definitions */ +#define INA209_CONFIGURATION 0x00 +#define INA209_STATUS 0x01 +#define INA209_STATUS_MASK 0x02 +#define INA209_SHUNT_VOLTAGE 0x03 +#define INA209_BUS_VOLTAGE 0x04 +#define INA209_POWER 0x05 +#define INA209_CURRENT 0x06 +#define INA209_SHUNT_VOLTAGE_POS_PEAK 0x07 +#define INA209_SHUNT_VOLTAGE_NEG_PEAK 0x08 +#define INA209_BUS_VOLTAGE_MAX_PEAK 0x09 +#define INA209_BUS_VOLTAGE_MIN_PEAK 0x0a +#define INA209_POWER_PEAK 0x0b +#define INA209_SHUNT_VOLTAGE_POS_WARN 0x0c +#define INA209_SHUNT_VOLTAGE_NEG_WARN 0x0d +#define INA209_POWER_WARN 0x0e +#define INA209_BUS_VOLTAGE_OVER_WARN 0x0f +#define INA209_BUS_VOLTAGE_UNDER_WARN 0x10 +#define INA209_POWER_OVER_LIMIT 0x11 +#define INA209_BUS_VOLTAGE_OVER_LIMIT 0x12 +#define INA209_BUS_VOLTAGE_UNDER_LIMIT 0x13 +#define INA209_CRITICAL_DAC_POS 0x14 +#define INA209_CRITICAL_DAC_NEG 0x15 +#define INA209_CALIBRATION 0x16 + +#define INA209_REGISTERS 0x17 + +#define INA209_CONFIG_DEFAULT 0x3c47 /* PGA=8, full range */ +#define INA209_SHUNT_DEFAULT 10000 /* uOhm */ + +struct ina209_data { + struct i2c_client *client; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + + u16 regs[INA209_REGISTERS]; /* All chip registers */ + + u16 config_orig; /* Original configuration */ + u16 calibration_orig; /* Original calibration */ + u16 update_interval; +}; + +static struct ina209_data *ina209_update_device(struct device *dev) +{ + struct ina209_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + struct ina209_data *ret = data; + s32 val; + int i; + + mutex_lock(&data->update_lock); + + if (!data->valid || + time_after(jiffies, data->last_updated + data->update_interval)) { + for (i = 0; i < ARRAY_SIZE(data->regs); i++) { + val = i2c_smbus_read_word_swapped(client, i); + if (val < 0) { + ret = ERR_PTR(val); + goto abort; + } + data->regs[i] = val; + } + data->last_updated = jiffies; + data->valid = true; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +/* + * Read a value from a device register and convert it to the + * appropriate sysfs units + */ +static long ina209_from_reg(const u8 reg, const u16 val) +{ + switch (reg) { + case INA209_SHUNT_VOLTAGE: + case INA209_SHUNT_VOLTAGE_POS_PEAK: + case INA209_SHUNT_VOLTAGE_NEG_PEAK: + case INA209_SHUNT_VOLTAGE_POS_WARN: + case INA209_SHUNT_VOLTAGE_NEG_WARN: + /* LSB=10 uV. Convert to mV. */ + return DIV_ROUND_CLOSEST((s16)val, 100); + + case INA209_BUS_VOLTAGE: + case INA209_BUS_VOLTAGE_MAX_PEAK: + case INA209_BUS_VOLTAGE_MIN_PEAK: + case INA209_BUS_VOLTAGE_OVER_WARN: + case INA209_BUS_VOLTAGE_UNDER_WARN: + case INA209_BUS_VOLTAGE_OVER_LIMIT: + case INA209_BUS_VOLTAGE_UNDER_LIMIT: + /* LSB=4 mV, last 3 bits unused */ + return (val >> 3) * 4; + + case INA209_CRITICAL_DAC_POS: + /* LSB=1 mV, in the upper 8 bits */ + return val >> 8; + + case INA209_CRITICAL_DAC_NEG: + /* LSB=1 mV, in the upper 8 bits */ + return -1 * (val >> 8); + + case INA209_POWER: + case INA209_POWER_PEAK: + case INA209_POWER_WARN: + case INA209_POWER_OVER_LIMIT: + /* LSB=20 mW. Convert to uW */ + return val * 20 * 1000L; + + case INA209_CURRENT: + /* LSB=1 mA (selected). Is in mA */ + return (s16)val; + } + + /* programmer goofed */ + WARN_ON_ONCE(1); + return 0; +} + +/* + * Take a value and convert it to register format, clamping the value + * to the appropriate range. + */ +static int ina209_to_reg(u8 reg, u16 old, long val) +{ + switch (reg) { + case INA209_SHUNT_VOLTAGE_POS_WARN: + case INA209_SHUNT_VOLTAGE_NEG_WARN: + /* Limit to +- 320 mV, 10 uV LSB */ + return clamp_val(val, -320, 320) * 100; + + case INA209_BUS_VOLTAGE_OVER_WARN: + case INA209_BUS_VOLTAGE_UNDER_WARN: + case INA209_BUS_VOLTAGE_OVER_LIMIT: + case INA209_BUS_VOLTAGE_UNDER_LIMIT: + /* + * Limit to 0-32000 mV, 4 mV LSB + * + * The last three bits aren't part of the value, but we'll + * preserve them in their original state. + */ + return (DIV_ROUND_CLOSEST(clamp_val(val, 0, 32000), 4) << 3) + | (old & 0x7); + + case INA209_CRITICAL_DAC_NEG: + /* + * Limit to -255-0 mV, 1 mV LSB + * Convert the value to a positive value for the register + * + * The value lives in the top 8 bits only, be careful + * and keep original value of other bits. + */ + return (clamp_val(-val, 0, 255) << 8) | (old & 0xff); + + case INA209_CRITICAL_DAC_POS: + /* + * Limit to 0-255 mV, 1 mV LSB + * + * The value lives in the top 8 bits only, be careful + * and keep original value of other bits. + */ + return (clamp_val(val, 0, 255) << 8) | (old & 0xff); + + case INA209_POWER_WARN: + case INA209_POWER_OVER_LIMIT: + /* 20 mW LSB */ + return DIV_ROUND_CLOSEST(val, 20 * 1000); + } + + /* Other registers are read-only, return access error */ + return -EACCES; +} + +static int ina209_interval_from_reg(u16 reg) +{ + return 68 >> (15 - ((reg >> 3) & 0x0f)); +} + +static u16 ina209_reg_from_interval(u16 config, long interval) +{ + int i, adc; + + if (interval <= 0) { + adc = 8; + } else { + adc = 15; + for (i = 34 + 34 / 2; i; i >>= 1) { + if (i < interval) + break; + adc--; + } + } + return (config & 0xf807) | (adc << 3) | (adc << 7); +} + +static ssize_t ina209_set_interval(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct ina209_data *data = ina209_update_device(dev); + long val; + u16 regval; + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + mutex_lock(&data->update_lock); + regval = ina209_reg_from_interval(data->regs[INA209_CONFIGURATION], + val); + i2c_smbus_write_word_swapped(data->client, INA209_CONFIGURATION, + regval); + data->regs[INA209_CONFIGURATION] = regval; + data->update_interval = ina209_interval_from_reg(regval); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t ina209_show_interval(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct ina209_data *data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", data->update_interval); +} + +/* + * History is reset by writing 1 into bit 0 of the respective peak register. + * Since more than one peak register may be affected by the scope of a + * reset_history attribute write, use a bit mask in attr->index to identify + * which registers are affected. + */ +static u16 ina209_reset_history_regs[] = { + INA209_SHUNT_VOLTAGE_POS_PEAK, + INA209_SHUNT_VOLTAGE_NEG_PEAK, + INA209_BUS_VOLTAGE_MAX_PEAK, + INA209_BUS_VOLTAGE_MIN_PEAK, + INA209_POWER_PEAK +}; + +static ssize_t ina209_reset_history(struct device *dev, + struct device_attribute *da, + const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ina209_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u32 mask = attr->index; + long val; + int i, ret; + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + mutex_lock(&data->update_lock); + for (i = 0; i < ARRAY_SIZE(ina209_reset_history_regs); i++) { + if (mask & (1 << i)) + i2c_smbus_write_word_swapped(client, + ina209_reset_history_regs[i], 1); + } + data->valid = false; + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t ina209_set_value(struct device *dev, + struct device_attribute *da, + const char *buf, + size_t count) +{ + struct ina209_data *data = ina209_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int reg = attr->index; + long val; + int ret; + + if (IS_ERR(data)) + return PTR_ERR(data); + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + mutex_lock(&data->update_lock); + ret = ina209_to_reg(reg, data->regs[reg], val); + if (ret < 0) { + count = ret; + goto abort; + } + i2c_smbus_write_word_swapped(data->client, reg, ret); + data->regs[reg] = ret; +abort: + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t ina209_show_value(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ina209_data *data = ina209_update_device(dev); + long val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = ina209_from_reg(attr->index, data->regs[attr->index]); + return snprintf(buf, PAGE_SIZE, "%ld\n", val); +} + +static ssize_t ina209_show_alarm(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ina209_data *data = ina209_update_device(dev); + const unsigned int mask = attr->index; + u16 status; + + if (IS_ERR(data)) + return PTR_ERR(data); + + status = data->regs[INA209_STATUS]; + + /* + * All alarms are in the INA209_STATUS register. To avoid a long + * switch statement, the mask is passed in attr->index + */ + return snprintf(buf, PAGE_SIZE, "%u\n", !!(status & mask)); +} + +/* Shunt voltage, history, limits, alarms */ +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ina209_show_value, NULL, + INA209_SHUNT_VOLTAGE); +static SENSOR_DEVICE_ATTR(in0_input_highest, S_IRUGO, ina209_show_value, NULL, + INA209_SHUNT_VOLTAGE_POS_PEAK); +static SENSOR_DEVICE_ATTR(in0_input_lowest, S_IRUGO, ina209_show_value, NULL, + INA209_SHUNT_VOLTAGE_NEG_PEAK); +static SENSOR_DEVICE_ATTR(in0_reset_history, S_IWUSR, NULL, + ina209_reset_history, (1 << 0) | (1 << 1)); +static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_SHUNT_VOLTAGE_POS_WARN); +static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_SHUNT_VOLTAGE_NEG_WARN); +static SENSOR_DEVICE_ATTR(in0_crit_max, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_CRITICAL_DAC_POS); +static SENSOR_DEVICE_ATTR(in0_crit_min, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_CRITICAL_DAC_NEG); + +static SENSOR_DEVICE_ATTR(in0_min_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 11); +static SENSOR_DEVICE_ATTR(in0_max_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 12); +static SENSOR_DEVICE_ATTR(in0_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 6); +static SENSOR_DEVICE_ATTR(in0_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 7); + +/* Bus voltage, history, limits, alarms */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ina209_show_value, NULL, + INA209_BUS_VOLTAGE); +static SENSOR_DEVICE_ATTR(in1_input_highest, S_IRUGO, ina209_show_value, NULL, + INA209_BUS_VOLTAGE_MAX_PEAK); +static SENSOR_DEVICE_ATTR(in1_input_lowest, S_IRUGO, ina209_show_value, NULL, + INA209_BUS_VOLTAGE_MIN_PEAK); +static SENSOR_DEVICE_ATTR(in1_reset_history, S_IWUSR, NULL, + ina209_reset_history, (1 << 2) | (1 << 3)); +static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_BUS_VOLTAGE_OVER_WARN); +static SENSOR_DEVICE_ATTR(in1_min, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_BUS_VOLTAGE_UNDER_WARN); +static SENSOR_DEVICE_ATTR(in1_crit_max, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_BUS_VOLTAGE_OVER_LIMIT); +static SENSOR_DEVICE_ATTR(in1_crit_min, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_BUS_VOLTAGE_UNDER_LIMIT); + +static SENSOR_DEVICE_ATTR(in1_min_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 14); +static SENSOR_DEVICE_ATTR(in1_max_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 15); +static SENSOR_DEVICE_ATTR(in1_crit_min_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 9); +static SENSOR_DEVICE_ATTR(in1_crit_max_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 10); + +/* Power */ +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ina209_show_value, NULL, + INA209_POWER); +static SENSOR_DEVICE_ATTR(power1_input_highest, S_IRUGO, ina209_show_value, + NULL, INA209_POWER_PEAK); +static SENSOR_DEVICE_ATTR(power1_reset_history, S_IWUSR, NULL, + ina209_reset_history, 1 << 4); +static SENSOR_DEVICE_ATTR(power1_max, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_POWER_WARN); +static SENSOR_DEVICE_ATTR(power1_crit, S_IRUGO | S_IWUSR, ina209_show_value, + ina209_set_value, INA209_POWER_OVER_LIMIT); + +static SENSOR_DEVICE_ATTR(power1_max_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 13); +static SENSOR_DEVICE_ATTR(power1_crit_alarm, S_IRUGO, ina209_show_alarm, NULL, + 1 << 8); + +/* Current */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ina209_show_value, NULL, + INA209_CURRENT); + +static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR, + ina209_show_interval, ina209_set_interval, 0); + +/* + * Finally, construct an array of pointers to members of the above objects, + * as required for sysfs_create_group() + */ +static struct attribute *ina209_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_input_highest.dev_attr.attr, + &sensor_dev_attr_in0_input_lowest.dev_attr.attr, + &sensor_dev_attr_in0_reset_history.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_crit_max.dev_attr.attr, + &sensor_dev_attr_in0_crit_min.dev_attr.attr, + &sensor_dev_attr_in0_max_alarm.dev_attr.attr, + &sensor_dev_attr_in0_min_alarm.dev_attr.attr, + &sensor_dev_attr_in0_crit_max_alarm.dev_attr.attr, + &sensor_dev_attr_in0_crit_min_alarm.dev_attr.attr, + + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_input_highest.dev_attr.attr, + &sensor_dev_attr_in1_input_lowest.dev_attr.attr, + &sensor_dev_attr_in1_reset_history.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_crit_max.dev_attr.attr, + &sensor_dev_attr_in1_crit_min.dev_attr.attr, + &sensor_dev_attr_in1_max_alarm.dev_attr.attr, + &sensor_dev_attr_in1_min_alarm.dev_attr.attr, + &sensor_dev_attr_in1_crit_max_alarm.dev_attr.attr, + &sensor_dev_attr_in1_crit_min_alarm.dev_attr.attr, + + &sensor_dev_attr_power1_input.dev_attr.attr, + &sensor_dev_attr_power1_input_highest.dev_attr.attr, + &sensor_dev_attr_power1_reset_history.dev_attr.attr, + &sensor_dev_attr_power1_max.dev_attr.attr, + &sensor_dev_attr_power1_crit.dev_attr.attr, + &sensor_dev_attr_power1_max_alarm.dev_attr.attr, + &sensor_dev_attr_power1_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_curr1_input.dev_attr.attr, + + &sensor_dev_attr_update_interval.dev_attr.attr, + + NULL, +}; +ATTRIBUTE_GROUPS(ina209); + +static void ina209_restore_conf(struct i2c_client *client, + struct ina209_data *data) +{ + /* Restore initial configuration */ + i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION, + data->config_orig); + i2c_smbus_write_word_swapped(client, INA209_CALIBRATION, + data->calibration_orig); +} + +static int ina209_init_client(struct i2c_client *client, + struct ina209_data *data) +{ + struct ina2xx_platform_data *pdata = dev_get_platdata(&client->dev); + u32 shunt; + int reg; + + reg = i2c_smbus_read_word_swapped(client, INA209_CALIBRATION); + if (reg < 0) + return reg; + data->calibration_orig = reg; + + reg = i2c_smbus_read_word_swapped(client, INA209_CONFIGURATION); + if (reg < 0) + return reg; + data->config_orig = reg; + + if (pdata) { + if (pdata->shunt_uohms <= 0) + return -EINVAL; + shunt = pdata->shunt_uohms; + } else if (!of_property_read_u32(client->dev.of_node, "shunt-resistor", + &shunt)) { + if (shunt == 0) + return -EINVAL; + } else { + shunt = data->calibration_orig ? + 40960000 / data->calibration_orig : INA209_SHUNT_DEFAULT; + } + + i2c_smbus_write_word_swapped(client, INA209_CONFIGURATION, + INA209_CONFIG_DEFAULT); + data->update_interval = ina209_interval_from_reg(INA209_CONFIG_DEFAULT); + + /* + * Calibrate current LSB to 1mA. Shunt is in uOhms. + * See equation 13 in datasheet. + */ + i2c_smbus_write_word_swapped(client, INA209_CALIBRATION, + clamp_val(40960000 / shunt, 1, 65535)); + + /* Clear status register */ + i2c_smbus_read_word_swapped(client, INA209_STATUS); + + return 0; +} + +static int ina209_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct ina209_data *data; + struct device *hwmon_dev; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->client = client; + mutex_init(&data->update_lock); + + ret = ina209_init_client(client, data); + if (ret) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, + client->name, + data, ina209_groups); + if (IS_ERR(hwmon_dev)) { + ret = PTR_ERR(hwmon_dev); + goto out_restore_conf; + } + + return 0; + +out_restore_conf: + ina209_restore_conf(client, data); + return ret; +} + +static int ina209_remove(struct i2c_client *client) +{ + struct ina209_data *data = i2c_get_clientdata(client); + + ina209_restore_conf(client, data); + + return 0; +} + +static const struct i2c_device_id ina209_id[] = { + { "ina209", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ina209_id); + +static const struct of_device_id ina209_of_match[] = { + { .compatible = "ti,ina209" }, + { }, +}; +MODULE_DEVICE_TABLE(of, ina209_of_match); + +/* This is the driver that will be inserted */ +static struct i2c_driver ina209_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "ina209", + .of_match_table = of_match_ptr(ina209_of_match), + }, + .probe = ina209_probe, + .remove = ina209_remove, + .id_table = ina209_id, +}; + +module_i2c_driver(ina209_driver); + +MODULE_AUTHOR("Ira W. Snyder , Paul Hays , Guenter Roeck "); +MODULE_DESCRIPTION("INA209 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c new file mode 100644 index 000000000..07ee19573 --- /dev/null +++ b/drivers/hwmon/ina2xx.c @@ -0,0 +1,556 @@ +/* + * Driver for Texas Instruments INA219, INA226 power monitor chips + * + * INA219: + * Zero Drift Bi-Directional Current/Power Monitor with I2C Interface + * Datasheet: http://www.ti.com/product/ina219 + * + * INA220: + * Bi-Directional Current/Power Monitor with I2C Interface + * Datasheet: http://www.ti.com/product/ina220 + * + * INA226: + * Bi-Directional Current/Power Monitor with I2C Interface + * Datasheet: http://www.ti.com/product/ina226 + * + * INA230: + * Bi-directional Current/Power Monitor with I2C Interface + * Datasheet: http://www.ti.com/product/ina230 + * + * Copyright (C) 2012 Lothar Felten + * Thanks to Jan Volkering + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* common register definitions */ +#define INA2XX_CONFIG 0x00 +#define INA2XX_SHUNT_VOLTAGE 0x01 /* readonly */ +#define INA2XX_BUS_VOLTAGE 0x02 /* readonly */ +#define INA2XX_POWER 0x03 /* readonly */ +#define INA2XX_CURRENT 0x04 /* readonly */ +#define INA2XX_CALIBRATION 0x05 + +/* INA226 register definitions */ +#define INA226_MASK_ENABLE 0x06 +#define INA226_ALERT_LIMIT 0x07 +#define INA226_DIE_ID 0xFF + +/* register count */ +#define INA219_REGISTERS 6 +#define INA226_REGISTERS 8 + +#define INA2XX_MAX_REGISTERS 8 + +/* settings - depend on use case */ +#define INA219_CONFIG_DEFAULT 0x399F /* PGA=8 */ +#define INA226_CONFIG_DEFAULT 0x4527 /* averages=16 */ + +/* worst case is 68.10 ms (~14.6Hz, ina219) */ +#define INA2XX_CONVERSION_RATE 15 +#define INA2XX_MAX_DELAY 69 /* worst case delay in ms */ + +#define INA2XX_RSHUNT_DEFAULT 10000 + +/* bit mask for reading the averaging setting in the configuration register */ +#define INA226_AVG_RD_MASK 0x0E00 + +#define INA226_READ_AVG(reg) (((reg) & INA226_AVG_RD_MASK) >> 9) +#define INA226_SHIFT_AVG(val) ((val) << 9) + +/* common attrs, ina226 attrs and NULL */ +#define INA2XX_MAX_ATTRIBUTE_GROUPS 3 + +/* + * Both bus voltage and shunt voltage conversion times for ina226 are set + * to 0b0100 on POR, which translates to 2200 microseconds in total. + */ +#define INA226_TOTAL_CONV_TIME_DEFAULT 2200 + +static struct regmap_config ina2xx_regmap_config = { + .reg_bits = 8, + .val_bits = 16, +}; + +enum ina2xx_ids { ina219, ina226 }; + +struct ina2xx_config { + u16 config_default; + int calibration_value; + int registers; + int shunt_div; + int bus_voltage_shift; + int bus_voltage_lsb; /* uV */ + int power_lsb_factor; +}; + +struct ina2xx_data { + const struct ina2xx_config *config; + + long rshunt; + long current_lsb_uA; + long power_lsb_uW; + struct mutex config_lock; + struct regmap *regmap; + + const struct attribute_group *groups[INA2XX_MAX_ATTRIBUTE_GROUPS]; +}; + +static const struct ina2xx_config ina2xx_config[] = { + [ina219] = { + .config_default = INA219_CONFIG_DEFAULT, + .calibration_value = 4096, + .registers = INA219_REGISTERS, + .shunt_div = 100, + .bus_voltage_shift = 3, + .bus_voltage_lsb = 4000, + .power_lsb_factor = 20, + }, + [ina226] = { + .config_default = INA226_CONFIG_DEFAULT, + .calibration_value = 2048, + .registers = INA226_REGISTERS, + .shunt_div = 400, + .bus_voltage_shift = 0, + .bus_voltage_lsb = 1250, + .power_lsb_factor = 25, + }, +}; + +/* + * Available averaging rates for ina226. The indices correspond with + * the bit values expected by the chip (according to the ina226 datasheet, + * table 3 AVG bit settings, found at + * http://www.ti.com/lit/ds/symlink/ina226.pdf. + */ +static const int ina226_avg_tab[] = { 1, 4, 16, 64, 128, 256, 512, 1024 }; + +static int ina226_reg_to_interval(u16 config) +{ + int avg = ina226_avg_tab[INA226_READ_AVG(config)]; + + /* + * Multiply the total conversion time by the number of averages. + * Return the result in milliseconds. + */ + return DIV_ROUND_CLOSEST(avg * INA226_TOTAL_CONV_TIME_DEFAULT, 1000); +} + +/* + * Return the new, shifted AVG field value of CONFIG register, + * to use with regmap_update_bits + */ +static u16 ina226_interval_to_reg(int interval) +{ + int avg, avg_bits; + + avg = DIV_ROUND_CLOSEST(interval * 1000, + INA226_TOTAL_CONV_TIME_DEFAULT); + avg_bits = find_closest(avg, ina226_avg_tab, + ARRAY_SIZE(ina226_avg_tab)); + + return INA226_SHIFT_AVG(avg_bits); +} + +/* + * Calibration register is set to the best value, which eliminates + * truncation errors on calculating current register in hardware. + * According to datasheet (eq. 3) the best values are 2048 for + * ina226 and 4096 for ina219. They are hardcoded as calibration_value. + */ +static int ina2xx_calibrate(struct ina2xx_data *data) +{ + return regmap_write(data->regmap, INA2XX_CALIBRATION, + data->config->calibration_value); +} + +/* + * Initialize the configuration and calibration registers. + */ +static int ina2xx_init(struct ina2xx_data *data) +{ + int ret = regmap_write(data->regmap, INA2XX_CONFIG, + data->config->config_default); + if (ret < 0) + return ret; + + return ina2xx_calibrate(data); +} + +static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval) +{ + struct ina2xx_data *data = dev_get_drvdata(dev); + int ret, retry; + + dev_dbg(dev, "Starting register %d read\n", reg); + + for (retry = 5; retry; retry--) { + + ret = regmap_read(data->regmap, reg, regval); + if (ret < 0) + return ret; + + dev_dbg(dev, "read %d, val = 0x%04x\n", reg, *regval); + + /* + * If the current value in the calibration register is 0, the + * power and current registers will also remain at 0. In case + * the chip has been reset let's check the calibration + * register and reinitialize if needed. + * We do that extra read of the calibration register if there + * is some hint of a chip reset. + */ + if (*regval == 0) { + unsigned int cal; + + ret = regmap_read(data->regmap, INA2XX_CALIBRATION, + &cal); + if (ret < 0) + return ret; + + if (cal == 0) { + dev_warn(dev, "chip not calibrated, reinitializing\n"); + + ret = ina2xx_init(data); + if (ret < 0) + return ret; + /* + * Let's make sure the power and current + * registers have been updated before trying + * again. + */ + msleep(INA2XX_MAX_DELAY); + continue; + } + } + return 0; + } + + /* + * If we're here then although all write operations succeeded, the + * chip still returns 0 in the calibration register. Nothing more we + * can do here. + */ + dev_err(dev, "unable to reinitialize the chip\n"); + return -ENODEV; +} + +static int ina2xx_get_value(struct ina2xx_data *data, u8 reg, + unsigned int regval) +{ + int val; + + switch (reg) { + case INA2XX_SHUNT_VOLTAGE: + /* signed register */ + val = DIV_ROUND_CLOSEST((s16)regval, data->config->shunt_div); + break; + case INA2XX_BUS_VOLTAGE: + val = (regval >> data->config->bus_voltage_shift) + * data->config->bus_voltage_lsb; + val = DIV_ROUND_CLOSEST(val, 1000); + break; + case INA2XX_POWER: + val = regval * data->power_lsb_uW; + break; + case INA2XX_CURRENT: + /* signed register, result in mA */ + val = (s16)regval * data->current_lsb_uA; + val = DIV_ROUND_CLOSEST(val, 1000); + break; + case INA2XX_CALIBRATION: + val = regval; + break; + default: + /* programmer goofed */ + WARN_ON_ONCE(1); + val = 0; + break; + } + + return val; +} + +static ssize_t ina2xx_show_value(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ina2xx_data *data = dev_get_drvdata(dev); + unsigned int regval; + + int err = ina2xx_read_reg(dev, attr->index, ®val); + + if (err < 0) + return err; + + return snprintf(buf, PAGE_SIZE, "%d\n", + ina2xx_get_value(data, attr->index, regval)); +} + +/* + * In order to keep calibration register value fixed, the product + * of current_lsb and shunt_resistor should also be fixed and equal + * to shunt_voltage_lsb = 1 / shunt_div multiplied by 10^9 in order + * to keep the scale. + */ +static int ina2xx_set_shunt(struct ina2xx_data *data, long val) +{ + unsigned int dividend = DIV_ROUND_CLOSEST(1000000000, + data->config->shunt_div); + if (val <= 0 || val > dividend) + return -EINVAL; + + mutex_lock(&data->config_lock); + data->rshunt = val; + data->current_lsb_uA = DIV_ROUND_CLOSEST(dividend, val); + data->power_lsb_uW = data->config->power_lsb_factor * + data->current_lsb_uA; + mutex_unlock(&data->config_lock); + + return 0; +} + +static ssize_t ina2xx_show_shunt(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct ina2xx_data *data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%li\n", data->rshunt); +} + +static ssize_t ina2xx_store_shunt(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + unsigned long val; + int status; + struct ina2xx_data *data = dev_get_drvdata(dev); + + status = kstrtoul(buf, 10, &val); + if (status < 0) + return status; + + status = ina2xx_set_shunt(data, val); + if (status < 0) + return status; + return count; +} + +static ssize_t ina226_set_interval(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct ina2xx_data *data = dev_get_drvdata(dev); + unsigned long val; + int status; + + status = kstrtoul(buf, 10, &val); + if (status < 0) + return status; + + if (val > INT_MAX || val == 0) + return -EINVAL; + + status = regmap_update_bits(data->regmap, INA2XX_CONFIG, + INA226_AVG_RD_MASK, + ina226_interval_to_reg(val)); + if (status < 0) + return status; + + return count; +} + +static ssize_t ina226_show_interval(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct ina2xx_data *data = dev_get_drvdata(dev); + int status; + unsigned int regval; + + status = regmap_read(data->regmap, INA2XX_CONFIG, ®val); + if (status) + return status; + + return snprintf(buf, PAGE_SIZE, "%d\n", ina226_reg_to_interval(regval)); +} + +/* shunt voltage */ +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ina2xx_show_value, NULL, + INA2XX_SHUNT_VOLTAGE); + +/* bus voltage */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ina2xx_show_value, NULL, + INA2XX_BUS_VOLTAGE); + +/* calculated current */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ina2xx_show_value, NULL, + INA2XX_CURRENT); + +/* calculated power */ +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ina2xx_show_value, NULL, + INA2XX_POWER); + +/* shunt resistance */ +static SENSOR_DEVICE_ATTR(shunt_resistor, S_IRUGO | S_IWUSR, + ina2xx_show_shunt, ina2xx_store_shunt, + INA2XX_CALIBRATION); + +/* update interval (ina226 only) */ +static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR, + ina226_show_interval, ina226_set_interval, 0); + +/* pointers to created device attributes */ +static struct attribute *ina2xx_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_power1_input.dev_attr.attr, + &sensor_dev_attr_shunt_resistor.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ina2xx_group = { + .attrs = ina2xx_attrs, +}; + +static struct attribute *ina226_attrs[] = { + &sensor_dev_attr_update_interval.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ina226_group = { + .attrs = ina226_attrs, +}; + +static int ina2xx_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct ina2xx_data *data; + struct device *hwmon_dev; + u32 val; + int ret, group = 0; + enum ina2xx_ids chip; + + if (client->dev.of_node) + chip = (enum ina2xx_ids)of_device_get_match_data(&client->dev); + else + chip = id->driver_data; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* set the device type */ + data->config = &ina2xx_config[chip]; + mutex_init(&data->config_lock); + + if (of_property_read_u32(dev->of_node, "shunt-resistor", &val) < 0) { + struct ina2xx_platform_data *pdata = dev_get_platdata(dev); + + if (pdata) + val = pdata->shunt_uohms; + else + val = INA2XX_RSHUNT_DEFAULT; + } + + ina2xx_set_shunt(data, val); + + ina2xx_regmap_config.max_register = data->config->registers; + + data->regmap = devm_regmap_init_i2c(client, &ina2xx_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(data->regmap); + } + + ret = ina2xx_init(data); + if (ret < 0) { + dev_err(dev, "error configuring the device: %d\n", ret); + return -ENODEV; + } + + data->groups[group++] = &ina2xx_group; + if (chip == ina226) + data->groups[group++] = &ina226_group; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + dev_info(dev, "power monitor %s (Rshunt = %li uOhm)\n", + client->name, data->rshunt); + + return 0; +} + +static const struct i2c_device_id ina2xx_id[] = { + { "ina219", ina219 }, + { "ina220", ina219 }, + { "ina226", ina226 }, + { "ina230", ina226 }, + { "ina231", ina226 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ina2xx_id); + +static const struct of_device_id ina2xx_of_match[] = { + { + .compatible = "ti,ina219", + .data = (void *)ina219 + }, + { + .compatible = "ti,ina220", + .data = (void *)ina219 + }, + { + .compatible = "ti,ina226", + .data = (void *)ina226 + }, + { + .compatible = "ti,ina230", + .data = (void *)ina226 + }, + { + .compatible = "ti,ina231", + .data = (void *)ina226 + }, + { }, +}; +MODULE_DEVICE_TABLE(of, ina2xx_of_match); + +static struct i2c_driver ina2xx_driver = { + .driver = { + .name = "ina2xx", + .of_match_table = of_match_ptr(ina2xx_of_match), + }, + .probe = ina2xx_probe, + .id_table = ina2xx_id, +}; + +module_i2c_driver(ina2xx_driver); + +MODULE_AUTHOR("Lothar Felten "); +MODULE_DESCRIPTION("ina2xx driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ina3221.c b/drivers/hwmon/ina3221.c new file mode 100644 index 000000000..8c9555313 --- /dev/null +++ b/drivers/hwmon/ina3221.c @@ -0,0 +1,445 @@ +/* + * INA3221 Triple Current/Voltage Monitor + * + * Copyright (C) 2016 Texas Instruments Incorporated - http://www.ti.com/ + * Andrew F. Davis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#define INA3221_DRIVER_NAME "ina3221" + +#define INA3221_CONFIG 0x00 +#define INA3221_SHUNT1 0x01 +#define INA3221_BUS1 0x02 +#define INA3221_SHUNT2 0x03 +#define INA3221_BUS2 0x04 +#define INA3221_SHUNT3 0x05 +#define INA3221_BUS3 0x06 +#define INA3221_CRIT1 0x07 +#define INA3221_WARN1 0x08 +#define INA3221_CRIT2 0x09 +#define INA3221_WARN2 0x0a +#define INA3221_CRIT3 0x0b +#define INA3221_WARN3 0x0c +#define INA3221_MASK_ENABLE 0x0f + +#define INA3221_CONFIG_MODE_SHUNT BIT(0) +#define INA3221_CONFIG_MODE_BUS BIT(1) +#define INA3221_CONFIG_MODE_CONTINUOUS BIT(2) + +#define INA3221_RSHUNT_DEFAULT 10000 + +enum ina3221_fields { + /* Configuration */ + F_RST, + + /* Alert Flags */ + F_WF3, F_WF2, F_WF1, + F_CF3, F_CF2, F_CF1, + + /* sentinel */ + F_MAX_FIELDS +}; + +static const struct reg_field ina3221_reg_fields[] = { + [F_RST] = REG_FIELD(INA3221_CONFIG, 15, 15), + + [F_WF3] = REG_FIELD(INA3221_MASK_ENABLE, 3, 3), + [F_WF2] = REG_FIELD(INA3221_MASK_ENABLE, 4, 4), + [F_WF1] = REG_FIELD(INA3221_MASK_ENABLE, 5, 5), + [F_CF3] = REG_FIELD(INA3221_MASK_ENABLE, 7, 7), + [F_CF2] = REG_FIELD(INA3221_MASK_ENABLE, 8, 8), + [F_CF1] = REG_FIELD(INA3221_MASK_ENABLE, 9, 9), +}; + +enum ina3221_channels { + INA3221_CHANNEL1, + INA3221_CHANNEL2, + INA3221_CHANNEL3, + INA3221_NUM_CHANNELS +}; + +static const unsigned int register_channel[] = { + [INA3221_SHUNT1] = INA3221_CHANNEL1, + [INA3221_SHUNT2] = INA3221_CHANNEL2, + [INA3221_SHUNT3] = INA3221_CHANNEL3, + [INA3221_CRIT1] = INA3221_CHANNEL1, + [INA3221_CRIT2] = INA3221_CHANNEL2, + [INA3221_CRIT3] = INA3221_CHANNEL3, + [INA3221_WARN1] = INA3221_CHANNEL1, + [INA3221_WARN2] = INA3221_CHANNEL2, + [INA3221_WARN3] = INA3221_CHANNEL3, +}; + +/** + * struct ina3221_data - device specific information + * @regmap: Register map of the device + * @fields: Register fields of the device + * @shunt_resistors: Array of resistor values per channel + */ +struct ina3221_data { + struct regmap *regmap; + struct regmap_field *fields[F_MAX_FIELDS]; + int shunt_resistors[INA3221_NUM_CHANNELS]; +}; + +static int ina3221_read_value(struct ina3221_data *ina, unsigned int reg, + int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(ina->regmap, reg, ®val); + if (ret) + return ret; + + *val = sign_extend32(regval >> 3, 12); + + return 0; +} + +static ssize_t ina3221_show_bus_voltage(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina3221_data *ina = dev_get_drvdata(dev); + unsigned int reg = sd_attr->index; + int val, voltage_mv, ret; + + ret = ina3221_read_value(ina, reg, &val); + if (ret) + return ret; + + voltage_mv = val * 8; + + return snprintf(buf, PAGE_SIZE, "%d\n", voltage_mv); +} + +static ssize_t ina3221_show_shunt_voltage(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina3221_data *ina = dev_get_drvdata(dev); + unsigned int reg = sd_attr->index; + int val, voltage_uv, ret; + + ret = ina3221_read_value(ina, reg, &val); + if (ret) + return ret; + voltage_uv = val * 40; + + return snprintf(buf, PAGE_SIZE, "%d\n", voltage_uv); +} + +static ssize_t ina3221_show_current(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina3221_data *ina = dev_get_drvdata(dev); + unsigned int reg = sd_attr->index; + unsigned int channel = register_channel[reg]; + int resistance_uo = ina->shunt_resistors[channel]; + int val, current_ma, voltage_nv, ret; + + ret = ina3221_read_value(ina, reg, &val); + if (ret) + return ret; + voltage_nv = val * 40000; + + current_ma = DIV_ROUND_CLOSEST(voltage_nv, resistance_uo); + + return snprintf(buf, PAGE_SIZE, "%d\n", current_ma); +} + +static ssize_t ina3221_set_current(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina3221_data *ina = dev_get_drvdata(dev); + unsigned int reg = sd_attr->index; + unsigned int channel = register_channel[reg]; + int resistance_uo = ina->shunt_resistors[channel]; + int val, current_ma, voltage_uv, ret; + + ret = kstrtoint(buf, 0, ¤t_ma); + if (ret) + return ret; + + /* clamp current */ + current_ma = clamp_val(current_ma, + INT_MIN / resistance_uo, + INT_MAX / resistance_uo); + + voltage_uv = DIV_ROUND_CLOSEST(current_ma * resistance_uo, 1000); + + /* clamp voltage */ + voltage_uv = clamp_val(voltage_uv, -163800, 163800); + + /* 1 / 40uV(scale) << 3(register shift) = 5 */ + val = DIV_ROUND_CLOSEST(voltage_uv, 5) & 0xfff8; + + ret = regmap_write(ina->regmap, reg, val); + if (ret) + return ret; + + return count; +} + +static ssize_t ina3221_show_shunt(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina3221_data *ina = dev_get_drvdata(dev); + unsigned int channel = sd_attr->index; + unsigned int resistance_uo; + + resistance_uo = ina->shunt_resistors[channel]; + + return snprintf(buf, PAGE_SIZE, "%d\n", resistance_uo); +} + +static ssize_t ina3221_set_shunt(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina3221_data *ina = dev_get_drvdata(dev); + unsigned int channel = sd_attr->index; + int val; + int ret; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + + val = clamp_val(val, 1, INT_MAX); + + ina->shunt_resistors[channel] = val; + + return count; +} + +static ssize_t ina3221_show_alert(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr); + struct ina3221_data *ina = dev_get_drvdata(dev); + unsigned int field = sd_attr->index; + unsigned int regval; + int ret; + + ret = regmap_field_read(ina->fields[field], ®val); + if (ret) + return ret; + + return snprintf(buf, PAGE_SIZE, "%d\n", regval); +} + +/* bus voltage */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, + ina3221_show_bus_voltage, NULL, INA3221_BUS1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, + ina3221_show_bus_voltage, NULL, INA3221_BUS2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, + ina3221_show_bus_voltage, NULL, INA3221_BUS3); + +/* calculated current */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, + ina3221_show_current, NULL, INA3221_SHUNT1); +static SENSOR_DEVICE_ATTR(curr2_input, S_IRUGO, + ina3221_show_current, NULL, INA3221_SHUNT2); +static SENSOR_DEVICE_ATTR(curr3_input, S_IRUGO, + ina3221_show_current, NULL, INA3221_SHUNT3); + +/* shunt resistance */ +static SENSOR_DEVICE_ATTR(shunt1_resistor, S_IRUGO | S_IWUSR, + ina3221_show_shunt, ina3221_set_shunt, INA3221_CHANNEL1); +static SENSOR_DEVICE_ATTR(shunt2_resistor, S_IRUGO | S_IWUSR, + ina3221_show_shunt, ina3221_set_shunt, INA3221_CHANNEL2); +static SENSOR_DEVICE_ATTR(shunt3_resistor, S_IRUGO | S_IWUSR, + ina3221_show_shunt, ina3221_set_shunt, INA3221_CHANNEL3); + +/* critical current */ +static SENSOR_DEVICE_ATTR(curr1_crit, S_IRUGO | S_IWUSR, + ina3221_show_current, ina3221_set_current, INA3221_CRIT1); +static SENSOR_DEVICE_ATTR(curr2_crit, S_IRUGO | S_IWUSR, + ina3221_show_current, ina3221_set_current, INA3221_CRIT2); +static SENSOR_DEVICE_ATTR(curr3_crit, S_IRUGO | S_IWUSR, + ina3221_show_current, ina3221_set_current, INA3221_CRIT3); + +/* critical current alert */ +static SENSOR_DEVICE_ATTR(curr1_crit_alarm, S_IRUGO, + ina3221_show_alert, NULL, F_CF1); +static SENSOR_DEVICE_ATTR(curr2_crit_alarm, S_IRUGO, + ina3221_show_alert, NULL, F_CF2); +static SENSOR_DEVICE_ATTR(curr3_crit_alarm, S_IRUGO, + ina3221_show_alert, NULL, F_CF3); + +/* warning current */ +static SENSOR_DEVICE_ATTR(curr1_max, S_IRUGO | S_IWUSR, + ina3221_show_current, ina3221_set_current, INA3221_WARN1); +static SENSOR_DEVICE_ATTR(curr2_max, S_IRUGO | S_IWUSR, + ina3221_show_current, ina3221_set_current, INA3221_WARN2); +static SENSOR_DEVICE_ATTR(curr3_max, S_IRUGO | S_IWUSR, + ina3221_show_current, ina3221_set_current, INA3221_WARN3); + +/* warning current alert */ +static SENSOR_DEVICE_ATTR(curr1_max_alarm, S_IRUGO, + ina3221_show_alert, NULL, F_WF1); +static SENSOR_DEVICE_ATTR(curr2_max_alarm, S_IRUGO, + ina3221_show_alert, NULL, F_WF2); +static SENSOR_DEVICE_ATTR(curr3_max_alarm, S_IRUGO, + ina3221_show_alert, NULL, F_WF3); + +/* shunt voltage */ +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, + ina3221_show_shunt_voltage, NULL, INA3221_SHUNT1); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, + ina3221_show_shunt_voltage, NULL, INA3221_SHUNT2); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, + ina3221_show_shunt_voltage, NULL, INA3221_SHUNT3); + +static struct attribute *ina3221_attrs[] = { + /* channel 1 */ + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_shunt1_resistor.dev_attr.attr, + &sensor_dev_attr_curr1_crit.dev_attr.attr, + &sensor_dev_attr_curr1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_curr1_max.dev_attr.attr, + &sensor_dev_attr_curr1_max_alarm.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + + /* channel 2 */ + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_curr2_input.dev_attr.attr, + &sensor_dev_attr_shunt2_resistor.dev_attr.attr, + &sensor_dev_attr_curr2_crit.dev_attr.attr, + &sensor_dev_attr_curr2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_curr2_max.dev_attr.attr, + &sensor_dev_attr_curr2_max_alarm.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + + /* channel 3 */ + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_curr3_input.dev_attr.attr, + &sensor_dev_attr_shunt3_resistor.dev_attr.attr, + &sensor_dev_attr_curr3_crit.dev_attr.attr, + &sensor_dev_attr_curr3_crit_alarm.dev_attr.attr, + &sensor_dev_attr_curr3_max.dev_attr.attr, + &sensor_dev_attr_curr3_max_alarm.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + + NULL, +}; +ATTRIBUTE_GROUPS(ina3221); + +static const struct regmap_range ina3221_yes_ranges[] = { + regmap_reg_range(INA3221_SHUNT1, INA3221_BUS3), + regmap_reg_range(INA3221_MASK_ENABLE, INA3221_MASK_ENABLE), +}; + +static const struct regmap_access_table ina3221_volatile_table = { + .yes_ranges = ina3221_yes_ranges, + .n_yes_ranges = ARRAY_SIZE(ina3221_yes_ranges), +}; + +static const struct regmap_config ina3221_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + + .cache_type = REGCACHE_RBTREE, + .volatile_table = &ina3221_volatile_table, +}; + +static int ina3221_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct ina3221_data *ina; + struct device *hwmon_dev; + int i, ret; + + ina = devm_kzalloc(dev, sizeof(*ina), GFP_KERNEL); + if (!ina) + return -ENOMEM; + + ina->regmap = devm_regmap_init_i2c(client, &ina3221_regmap_config); + if (IS_ERR(ina->regmap)) { + dev_err(dev, "Unable to allocate register map\n"); + return PTR_ERR(ina->regmap); + } + + for (i = 0; i < F_MAX_FIELDS; i++) { + ina->fields[i] = devm_regmap_field_alloc(dev, + ina->regmap, + ina3221_reg_fields[i]); + if (IS_ERR(ina->fields[i])) { + dev_err(dev, "Unable to allocate regmap fields\n"); + return PTR_ERR(ina->fields[i]); + } + } + + for (i = 0; i < INA3221_NUM_CHANNELS; i++) + ina->shunt_resistors[i] = INA3221_RSHUNT_DEFAULT; + + ret = regmap_field_write(ina->fields[F_RST], true); + if (ret) { + dev_err(dev, "Unable to reset device\n"); + return ret; + } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + client->name, + ina, ina3221_groups); + if (IS_ERR(hwmon_dev)) { + dev_err(dev, "Unable to register hwmon device\n"); + return PTR_ERR(hwmon_dev); + } + + return 0; +} + +static const struct of_device_id ina3221_of_match_table[] = { + { .compatible = "ti,ina3221", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ina3221_of_match_table); + +static const struct i2c_device_id ina3221_ids[] = { + { "ina3221", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, ina3221_ids); + +static struct i2c_driver ina3221_i2c_driver = { + .probe = ina3221_probe, + .driver = { + .name = INA3221_DRIVER_NAME, + .of_match_table = ina3221_of_match_table, + }, + .id_table = ina3221_ids, +}; +module_i2c_driver(ina3221_i2c_driver); + +MODULE_AUTHOR("Andrew F. Davis "); +MODULE_DESCRIPTION("Texas Instruments INA3221 HWMon Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c new file mode 100644 index 000000000..f8499cb95 --- /dev/null +++ b/drivers/hwmon/it87.c @@ -0,0 +1,3378 @@ +/* + * it87.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring. + * + * The IT8705F is an LPC-based Super I/O part that contains UARTs, a + * parallel port, an IR port, a MIDI port, a floppy controller, etc., in + * addition to an Environment Controller (Enhanced Hardware Monitor and + * Fan Controller) + * + * This driver supports only the Environment Controller in the IT8705F and + * similar parts. The other devices are supported by different drivers. + * + * Supports: IT8603E Super I/O chip w/LPC interface + * IT8620E Super I/O chip w/LPC interface + * IT8622E Super I/O chip w/LPC interface + * IT8623E Super I/O chip w/LPC interface + * IT8628E Super I/O chip w/LPC interface + * IT8705F Super I/O chip w/LPC interface + * IT8712F Super I/O chip w/LPC interface + * IT8716F Super I/O chip w/LPC interface + * IT8718F Super I/O chip w/LPC interface + * IT8720F Super I/O chip w/LPC interface + * IT8721F Super I/O chip w/LPC interface + * IT8726F Super I/O chip w/LPC interface + * IT8728F Super I/O chip w/LPC interface + * IT8732F Super I/O chip w/LPC interface + * IT8758E Super I/O chip w/LPC interface + * IT8771E Super I/O chip w/LPC interface + * IT8772E Super I/O chip w/LPC interface + * IT8781F Super I/O chip w/LPC interface + * IT8782F Super I/O chip w/LPC interface + * IT8783E/F Super I/O chip w/LPC interface + * IT8786E Super I/O chip w/LPC interface + * IT8790E Super I/O chip w/LPC interface + * IT8792E Super I/O chip w/LPC interface + * Sis950 A clone of the IT8705F + * + * Copyright (C) 2001 Chris Gauthron + * Copyright (C) 2005-2010 Jean Delvare + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "it87" + +enum chips { it87, it8712, it8716, it8718, it8720, it8721, it8728, it8732, + it8771, it8772, it8781, it8782, it8783, it8786, it8790, + it8792, it8603, it8620, it8622, it8628 }; + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +static struct platform_device *it87_pdev[2]; + +#define REG_2E 0x2e /* The register to read/write */ +#define REG_4E 0x4e /* Secondary register to read/write */ + +#define DEV 0x07 /* Register: Logical device select */ +#define PME 0x04 /* The device with the fan registers in it */ + +/* The device with the IT8718F/IT8720F VID value in it */ +#define GPIO 0x07 + +#define DEVID 0x20 /* Register: Device ID */ +#define DEVREV 0x22 /* Register: Device Revision */ + +static inline int superio_inb(int ioreg, int reg) +{ + outb(reg, ioreg); + return inb(ioreg + 1); +} + +static inline void superio_outb(int ioreg, int reg, int val) +{ + outb(reg, ioreg); + outb(val, ioreg + 1); +} + +static int superio_inw(int ioreg, int reg) +{ + int val; + + outb(reg++, ioreg); + val = inb(ioreg + 1) << 8; + outb(reg, ioreg); + val |= inb(ioreg + 1); + return val; +} + +static inline void superio_select(int ioreg, int ldn) +{ + outb(DEV, ioreg); + outb(ldn, ioreg + 1); +} + +static inline int superio_enter(int ioreg) +{ + /* + * Try to reserve ioreg and ioreg + 1 for exclusive access. + */ + if (!request_muxed_region(ioreg, 2, DRVNAME)) + return -EBUSY; + + outb(0x87, ioreg); + outb(0x01, ioreg); + outb(0x55, ioreg); + outb(ioreg == REG_4E ? 0xaa : 0x55, ioreg); + return 0; +} + +static inline void superio_exit(int ioreg) +{ + outb(0x02, ioreg); + outb(0x02, ioreg + 1); + release_region(ioreg, 2); +} + +/* Logical device 4 registers */ +#define IT8712F_DEVID 0x8712 +#define IT8705F_DEVID 0x8705 +#define IT8716F_DEVID 0x8716 +#define IT8718F_DEVID 0x8718 +#define IT8720F_DEVID 0x8720 +#define IT8721F_DEVID 0x8721 +#define IT8726F_DEVID 0x8726 +#define IT8728F_DEVID 0x8728 +#define IT8732F_DEVID 0x8732 +#define IT8792E_DEVID 0x8733 +#define IT8771E_DEVID 0x8771 +#define IT8772E_DEVID 0x8772 +#define IT8781F_DEVID 0x8781 +#define IT8782F_DEVID 0x8782 +#define IT8783E_DEVID 0x8783 +#define IT8786E_DEVID 0x8786 +#define IT8790E_DEVID 0x8790 +#define IT8603E_DEVID 0x8603 +#define IT8620E_DEVID 0x8620 +#define IT8622E_DEVID 0x8622 +#define IT8623E_DEVID 0x8623 +#define IT8628E_DEVID 0x8628 +#define IT87_ACT_REG 0x30 +#define IT87_BASE_REG 0x60 + +/* Logical device 7 registers (IT8712F and later) */ +#define IT87_SIO_GPIO1_REG 0x25 +#define IT87_SIO_GPIO2_REG 0x26 +#define IT87_SIO_GPIO3_REG 0x27 +#define IT87_SIO_GPIO4_REG 0x28 +#define IT87_SIO_GPIO5_REG 0x29 +#define IT87_SIO_PINX1_REG 0x2a /* Pin selection */ +#define IT87_SIO_PINX2_REG 0x2c /* Pin selection */ +#define IT87_SIO_SPI_REG 0xef /* SPI function pin select */ +#define IT87_SIO_VID_REG 0xfc /* VID value */ +#define IT87_SIO_BEEP_PIN_REG 0xf6 /* Beep pin mapping */ + +/* Update battery voltage after every reading if true */ +static bool update_vbat; + +/* Not all BIOSes properly configure the PWM registers */ +static bool fix_pwm_polarity; + +/* Many IT87 constants specified below */ + +/* Length of ISA address segment */ +#define IT87_EXTENT 8 + +/* Length of ISA address segment for Environmental Controller */ +#define IT87_EC_EXTENT 2 + +/* Offset of EC registers from ISA base address */ +#define IT87_EC_OFFSET 5 + +/* Where are the ISA address/data registers relative to the EC base address */ +#define IT87_ADDR_REG_OFFSET 0 +#define IT87_DATA_REG_OFFSET 1 + +/*----- The IT87 registers -----*/ + +#define IT87_REG_CONFIG 0x00 + +#define IT87_REG_ALARM1 0x01 +#define IT87_REG_ALARM2 0x02 +#define IT87_REG_ALARM3 0x03 + +/* + * The IT8718F and IT8720F have the VID value in a different register, in + * Super-I/O configuration space. + */ +#define IT87_REG_VID 0x0a +/* + * The IT8705F and IT8712F earlier than revision 0x08 use register 0x0b + * for fan divisors. Later IT8712F revisions must use 16-bit tachometer + * mode. + */ +#define IT87_REG_FAN_DIV 0x0b +#define IT87_REG_FAN_16BIT 0x0c + +/* + * Monitors: + * - up to 13 voltage (0 to 7, battery, avcc, 10 to 12) + * - up to 6 temp (1 to 6) + * - up to 6 fan (1 to 6) + */ + +static const u8 IT87_REG_FAN[] = { 0x0d, 0x0e, 0x0f, 0x80, 0x82, 0x4c }; +static const u8 IT87_REG_FAN_MIN[] = { 0x10, 0x11, 0x12, 0x84, 0x86, 0x4e }; +static const u8 IT87_REG_FANX[] = { 0x18, 0x19, 0x1a, 0x81, 0x83, 0x4d }; +static const u8 IT87_REG_FANX_MIN[] = { 0x1b, 0x1c, 0x1d, 0x85, 0x87, 0x4f }; +static const u8 IT87_REG_TEMP_OFFSET[] = { 0x56, 0x57, 0x59 }; + +#define IT87_REG_FAN_MAIN_CTRL 0x13 +#define IT87_REG_FAN_CTL 0x14 +static const u8 IT87_REG_PWM[] = { 0x15, 0x16, 0x17, 0x7f, 0xa7, 0xaf }; +static const u8 IT87_REG_PWM_DUTY[] = { 0x63, 0x6b, 0x73, 0x7b, 0xa3, 0xab }; + +static const u8 IT87_REG_VIN[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, + 0x27, 0x28, 0x2f, 0x2c, 0x2d, 0x2e }; + +#define IT87_REG_TEMP(nr) (0x29 + (nr)) + +#define IT87_REG_VIN_MAX(nr) (0x30 + (nr) * 2) +#define IT87_REG_VIN_MIN(nr) (0x31 + (nr) * 2) +#define IT87_REG_TEMP_HIGH(nr) (0x40 + (nr) * 2) +#define IT87_REG_TEMP_LOW(nr) (0x41 + (nr) * 2) + +#define IT87_REG_VIN_ENABLE 0x50 +#define IT87_REG_TEMP_ENABLE 0x51 +#define IT87_REG_TEMP_EXTRA 0x55 +#define IT87_REG_BEEP_ENABLE 0x5c + +#define IT87_REG_CHIPID 0x58 + +static const u8 IT87_REG_AUTO_BASE[] = { 0x60, 0x68, 0x70, 0x78, 0xa0, 0xa8 }; + +#define IT87_REG_AUTO_TEMP(nr, i) (IT87_REG_AUTO_BASE[nr] + (i)) +#define IT87_REG_AUTO_PWM(nr, i) (IT87_REG_AUTO_BASE[nr] + 5 + (i)) + +#define IT87_REG_TEMP456_ENABLE 0x77 + +#define NUM_VIN ARRAY_SIZE(IT87_REG_VIN) +#define NUM_VIN_LIMIT 8 +#define NUM_TEMP 6 +#define NUM_TEMP_OFFSET ARRAY_SIZE(IT87_REG_TEMP_OFFSET) +#define NUM_TEMP_LIMIT 3 +#define NUM_FAN ARRAY_SIZE(IT87_REG_FAN) +#define NUM_FAN_DIV 3 +#define NUM_PWM ARRAY_SIZE(IT87_REG_PWM) +#define NUM_AUTO_PWM ARRAY_SIZE(IT87_REG_PWM) + +struct it87_devices { + const char *name; + const char * const suffix; + u32 features; + u8 peci_mask; + u8 old_peci_mask; +}; + +#define FEAT_12MV_ADC BIT(0) +#define FEAT_NEWER_AUTOPWM BIT(1) +#define FEAT_OLD_AUTOPWM BIT(2) +#define FEAT_16BIT_FANS BIT(3) +#define FEAT_TEMP_OFFSET BIT(4) +#define FEAT_TEMP_PECI BIT(5) +#define FEAT_TEMP_OLD_PECI BIT(6) +#define FEAT_FAN16_CONFIG BIT(7) /* Need to enable 16-bit fans */ +#define FEAT_FIVE_FANS BIT(8) /* Supports five fans */ +#define FEAT_VID BIT(9) /* Set if chip supports VID */ +#define FEAT_IN7_INTERNAL BIT(10) /* Set if in7 is internal */ +#define FEAT_SIX_FANS BIT(11) /* Supports six fans */ +#define FEAT_10_9MV_ADC BIT(12) +#define FEAT_AVCC3 BIT(13) /* Chip supports in9/AVCC3 */ +#define FEAT_FIVE_PWM BIT(14) /* Chip supports 5 pwm chn */ +#define FEAT_SIX_PWM BIT(15) /* Chip supports 6 pwm chn */ +#define FEAT_PWM_FREQ2 BIT(16) /* Separate pwm freq 2 */ +#define FEAT_SIX_TEMP BIT(17) /* Up to 6 temp sensors */ +#define FEAT_VIN3_5V BIT(18) /* VIN3 connected to +5V */ + +static const struct it87_devices it87_devices[] = { + [it87] = { + .name = "it87", + .suffix = "F", + .features = FEAT_OLD_AUTOPWM, /* may need to overwrite */ + }, + [it8712] = { + .name = "it8712", + .suffix = "F", + .features = FEAT_OLD_AUTOPWM | FEAT_VID, + /* may need to overwrite */ + }, + [it8716] = { + .name = "it8716", + .suffix = "F", + .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID + | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_PWM_FREQ2, + }, + [it8718] = { + .name = "it8718", + .suffix = "F", + .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID + | FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS + | FEAT_PWM_FREQ2, + .old_peci_mask = 0x4, + }, + [it8720] = { + .name = "it8720", + .suffix = "F", + .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID + | FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS + | FEAT_PWM_FREQ2, + .old_peci_mask = 0x4, + }, + [it8721] = { + .name = "it8721", + .suffix = "F", + .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI + | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_IN7_INTERNAL + | FEAT_PWM_FREQ2, + .peci_mask = 0x05, + .old_peci_mask = 0x02, /* Actually reports PCH */ + }, + [it8728] = { + .name = "it8728", + .suffix = "F", + .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS + | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2, + .peci_mask = 0x07, + }, + [it8732] = { + .name = "it8732", + .suffix = "F", + .features = FEAT_NEWER_AUTOPWM | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI + | FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL, + .peci_mask = 0x07, + .old_peci_mask = 0x02, /* Actually reports PCH */ + }, + [it8771] = { + .name = "it8771", + .suffix = "E", + .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL + | FEAT_PWM_FREQ2, + /* PECI: guesswork */ + /* 12mV ADC (OHM) */ + /* 16 bit fans (OHM) */ + /* three fans, always 16 bit (guesswork) */ + .peci_mask = 0x07, + }, + [it8772] = { + .name = "it8772", + .suffix = "E", + .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL + | FEAT_PWM_FREQ2, + /* PECI (coreboot) */ + /* 12mV ADC (HWSensors4, OHM) */ + /* 16 bit fans (HWSensors4, OHM) */ + /* three fans, always 16 bit (datasheet) */ + .peci_mask = 0x07, + }, + [it8781] = { + .name = "it8781", + .suffix = "F", + .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET + | FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2, + .old_peci_mask = 0x4, + }, + [it8782] = { + .name = "it8782", + .suffix = "F", + .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET + | FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2, + .old_peci_mask = 0x4, + }, + [it8783] = { + .name = "it8783", + .suffix = "E/F", + .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET + | FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2, + .old_peci_mask = 0x4, + }, + [it8786] = { + .name = "it8786", + .suffix = "E", + .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL + | FEAT_PWM_FREQ2, + .peci_mask = 0x07, + }, + [it8790] = { + .name = "it8790", + .suffix = "E", + .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL + | FEAT_PWM_FREQ2, + .peci_mask = 0x07, + }, + [it8792] = { + .name = "it8792", + .suffix = "E", + .features = FEAT_NEWER_AUTOPWM | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI + | FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL, + .peci_mask = 0x07, + .old_peci_mask = 0x02, /* Actually reports PCH */ + }, + [it8603] = { + .name = "it8603", + .suffix = "E", + .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL + | FEAT_AVCC3 | FEAT_PWM_FREQ2, + .peci_mask = 0x07, + }, + [it8620] = { + .name = "it8620", + .suffix = "E", + .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS + | FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2 + | FEAT_SIX_TEMP | FEAT_VIN3_5V, + .peci_mask = 0x07, + }, + [it8622] = { + .name = "it8622", + .suffix = "E", + .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS + | FEAT_FIVE_PWM | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2 + | FEAT_AVCC3 | FEAT_VIN3_5V, + .peci_mask = 0x07, + }, + [it8628] = { + .name = "it8628", + .suffix = "E", + .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS + | FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2 + | FEAT_SIX_TEMP | FEAT_VIN3_5V, + .peci_mask = 0x07, + }, +}; + +#define has_16bit_fans(data) ((data)->features & FEAT_16BIT_FANS) +#define has_12mv_adc(data) ((data)->features & FEAT_12MV_ADC) +#define has_10_9mv_adc(data) ((data)->features & FEAT_10_9MV_ADC) +#define has_newer_autopwm(data) ((data)->features & FEAT_NEWER_AUTOPWM) +#define has_old_autopwm(data) ((data)->features & FEAT_OLD_AUTOPWM) +#define has_temp_offset(data) ((data)->features & FEAT_TEMP_OFFSET) +#define has_temp_peci(data, nr) (((data)->features & FEAT_TEMP_PECI) && \ + ((data)->peci_mask & BIT(nr))) +#define has_temp_old_peci(data, nr) \ + (((data)->features & FEAT_TEMP_OLD_PECI) && \ + ((data)->old_peci_mask & BIT(nr))) +#define has_fan16_config(data) ((data)->features & FEAT_FAN16_CONFIG) +#define has_five_fans(data) ((data)->features & (FEAT_FIVE_FANS | \ + FEAT_SIX_FANS)) +#define has_vid(data) ((data)->features & FEAT_VID) +#define has_in7_internal(data) ((data)->features & FEAT_IN7_INTERNAL) +#define has_six_fans(data) ((data)->features & FEAT_SIX_FANS) +#define has_avcc3(data) ((data)->features & FEAT_AVCC3) +#define has_five_pwm(data) ((data)->features & (FEAT_FIVE_PWM \ + | FEAT_SIX_PWM)) +#define has_six_pwm(data) ((data)->features & FEAT_SIX_PWM) +#define has_pwm_freq2(data) ((data)->features & FEAT_PWM_FREQ2) +#define has_six_temp(data) ((data)->features & FEAT_SIX_TEMP) +#define has_vin3_5v(data) ((data)->features & FEAT_VIN3_5V) + +struct it87_sio_data { + int sioaddr; + enum chips type; + /* Values read from Super-I/O config space */ + u8 revision; + u8 vid_value; + u8 beep_pin; + u8 internal; /* Internal sensors can be labeled */ + bool need_in7_reroute; + /* Features skipped based on config or DMI */ + u16 skip_in; + u8 skip_vid; + u8 skip_fan; + u8 skip_pwm; + u8 skip_temp; +}; + +/* + * For each registered chip, we need to keep some data in memory. + * The structure is dynamically allocated. + */ +struct it87_data { + const struct attribute_group *groups[7]; + int sioaddr; + enum chips type; + u32 features; + u8 peci_mask; + u8 old_peci_mask; + + unsigned short addr; + const char *name; + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u16 in_scaled; /* Internal voltage sensors are scaled */ + u16 in_internal; /* Bitfield, internal sensors (for labels) */ + u16 has_in; /* Bitfield, voltage sensors enabled */ + u8 in[NUM_VIN][3]; /* [nr][0]=in, [1]=min, [2]=max */ + bool need_in7_reroute; + u8 has_fan; /* Bitfield, fans enabled */ + u16 fan[NUM_FAN][2]; /* Register values, [nr][0]=fan, [1]=min */ + u8 has_temp; /* Bitfield, temp sensors enabled */ + s8 temp[NUM_TEMP][4]; /* [nr][0]=temp, [1]=min, [2]=max, [3]=offset */ + u8 sensor; /* Register value (IT87_REG_TEMP_ENABLE) */ + u8 extra; /* Register value (IT87_REG_TEMP_EXTRA) */ + u8 fan_div[NUM_FAN_DIV];/* Register encoding, shifted right */ + bool has_vid; /* True if VID supported */ + u8 vid; /* Register encoding, combined */ + u8 vrm; + u32 alarms; /* Register encoding, combined */ + bool has_beep; /* true if beep supported */ + u8 beeps; /* Register encoding */ + u8 fan_main_ctrl; /* Register value */ + u8 fan_ctl; /* Register value */ + + /* + * The following 3 arrays correspond to the same registers up to + * the IT8720F. The meaning of bits 6-0 depends on the value of bit + * 7, and we want to preserve settings on mode changes, so we have + * to track all values separately. + * Starting with the IT8721F, the manual PWM duty cycles are stored + * in separate registers (8-bit values), so the separate tracking + * is no longer needed, but it is still done to keep the driver + * simple. + */ + u8 has_pwm; /* Bitfield, pwm control enabled */ + u8 pwm_ctrl[NUM_PWM]; /* Register value */ + u8 pwm_duty[NUM_PWM]; /* Manual PWM value set by user */ + u8 pwm_temp_map[NUM_PWM];/* PWM to temp. chan. mapping (bits 1-0) */ + + /* Automatic fan speed control registers */ + u8 auto_pwm[NUM_AUTO_PWM][4]; /* [nr][3] is hard-coded */ + s8 auto_temp[NUM_AUTO_PWM][5]; /* [nr][0] is point1_temp_hyst */ +}; + +static int adc_lsb(const struct it87_data *data, int nr) +{ + int lsb; + + if (has_12mv_adc(data)) + lsb = 120; + else if (has_10_9mv_adc(data)) + lsb = 109; + else + lsb = 160; + if (data->in_scaled & BIT(nr)) + lsb <<= 1; + return lsb; +} + +static u8 in_to_reg(const struct it87_data *data, int nr, long val) +{ + val = DIV_ROUND_CLOSEST(val * 10, adc_lsb(data, nr)); + return clamp_val(val, 0, 255); +} + +static int in_from_reg(const struct it87_data *data, int nr, int val) +{ + return DIV_ROUND_CLOSEST(val * adc_lsb(data, nr), 10); +} + +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 255; + rpm = clamp_val(rpm, 1, 1000000); + return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 254); +} + +static inline u16 FAN16_TO_REG(long rpm) +{ + if (rpm == 0) + return 0xffff; + return clamp_val((1350000 + rpm) / (rpm * 2), 1, 0xfffe); +} + +#define FAN_FROM_REG(val, div) ((val) == 0 ? -1 : (val) == 255 ? 0 : \ + 1350000 / ((val) * (div))) +/* The divider is fixed to 2 in 16-bit mode */ +#define FAN16_FROM_REG(val) ((val) == 0 ? -1 : (val) == 0xffff ? 0 : \ + 1350000 / ((val) * 2)) + +#define TEMP_TO_REG(val) (clamp_val(((val) < 0 ? (((val) - 500) / 1000) : \ + ((val) + 500) / 1000), -128, 127)) +#define TEMP_FROM_REG(val) ((val) * 1000) + +static u8 pwm_to_reg(const struct it87_data *data, long val) +{ + if (has_newer_autopwm(data)) + return val; + else + return val >> 1; +} + +static int pwm_from_reg(const struct it87_data *data, u8 reg) +{ + if (has_newer_autopwm(data)) + return reg; + else + return (reg & 0x7f) << 1; +} + +static int DIV_TO_REG(int val) +{ + int answer = 0; + + while (answer < 7 && (val >>= 1)) + answer++; + return answer; +} + +#define DIV_FROM_REG(val) BIT(val) + +/* + * PWM base frequencies. The frequency has to be divided by either 128 or 256, + * depending on the chip type, to calculate the actual PWM frequency. + * + * Some of the chip datasheets suggest a base frequency of 51 kHz instead + * of 750 kHz for the slowest base frequency, resulting in a PWM frequency + * of 200 Hz. Sometimes both PWM frequency select registers are affected, + * sometimes just one. It is unknown if this is a datasheet error or real, + * so this is ignored for now. + */ +static const unsigned int pwm_freq[8] = { + 48000000, + 24000000, + 12000000, + 8000000, + 6000000, + 3000000, + 1500000, + 750000, +}; + +/* + * Must be called with data->update_lock held, except during initialization. + * We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks, + * would slow down the IT87 access and should not be necessary. + */ +static int it87_read_value(struct it87_data *data, u8 reg) +{ + outb_p(reg, data->addr + IT87_ADDR_REG_OFFSET); + return inb_p(data->addr + IT87_DATA_REG_OFFSET); +} + +/* + * Must be called with data->update_lock held, except during initialization. + * We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks, + * would slow down the IT87 access and should not be necessary. + */ +static void it87_write_value(struct it87_data *data, u8 reg, u8 value) +{ + outb_p(reg, data->addr + IT87_ADDR_REG_OFFSET); + outb_p(value, data->addr + IT87_DATA_REG_OFFSET); +} + +static void it87_update_pwm_ctrl(struct it87_data *data, int nr) +{ + data->pwm_ctrl[nr] = it87_read_value(data, IT87_REG_PWM[nr]); + if (has_newer_autopwm(data)) { + data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03; + data->pwm_duty[nr] = it87_read_value(data, + IT87_REG_PWM_DUTY[nr]); + } else { + if (data->pwm_ctrl[nr] & 0x80) /* Automatic mode */ + data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03; + else /* Manual mode */ + data->pwm_duty[nr] = data->pwm_ctrl[nr] & 0x7f; + } + + if (has_old_autopwm(data)) { + int i; + + for (i = 0; i < 5 ; i++) + data->auto_temp[nr][i] = it87_read_value(data, + IT87_REG_AUTO_TEMP(nr, i)); + for (i = 0; i < 3 ; i++) + data->auto_pwm[nr][i] = it87_read_value(data, + IT87_REG_AUTO_PWM(nr, i)); + } else if (has_newer_autopwm(data)) { + int i; + + /* + * 0: temperature hysteresis (base + 5) + * 1: fan off temperature (base + 0) + * 2: fan start temperature (base + 1) + * 3: fan max temperature (base + 2) + */ + data->auto_temp[nr][0] = + it87_read_value(data, IT87_REG_AUTO_TEMP(nr, 5)); + + for (i = 0; i < 3 ; i++) + data->auto_temp[nr][i + 1] = + it87_read_value(data, + IT87_REG_AUTO_TEMP(nr, i)); + /* + * 0: start pwm value (base + 3) + * 1: pwm slope (base + 4, 1/8th pwm) + */ + data->auto_pwm[nr][0] = + it87_read_value(data, IT87_REG_AUTO_TEMP(nr, 3)); + data->auto_pwm[nr][1] = + it87_read_value(data, IT87_REG_AUTO_TEMP(nr, 4)); + } +} + +static struct it87_data *it87_update_device(struct device *dev) +{ + struct it87_data *data = dev_get_drvdata(dev); + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) || + !data->valid) { + if (update_vbat) { + /* + * Cleared after each update, so reenable. Value + * returned by this read will be previous value + */ + it87_write_value(data, IT87_REG_CONFIG, + it87_read_value(data, IT87_REG_CONFIG) | 0x40); + } + for (i = 0; i < NUM_VIN; i++) { + if (!(data->has_in & BIT(i))) + continue; + + data->in[i][0] = + it87_read_value(data, IT87_REG_VIN[i]); + + /* VBAT and AVCC don't have limit registers */ + if (i >= NUM_VIN_LIMIT) + continue; + + data->in[i][1] = + it87_read_value(data, IT87_REG_VIN_MIN(i)); + data->in[i][2] = + it87_read_value(data, IT87_REG_VIN_MAX(i)); + } + + for (i = 0; i < NUM_FAN; i++) { + /* Skip disabled fans */ + if (!(data->has_fan & BIT(i))) + continue; + + data->fan[i][1] = + it87_read_value(data, IT87_REG_FAN_MIN[i]); + data->fan[i][0] = it87_read_value(data, + IT87_REG_FAN[i]); + /* Add high byte if in 16-bit mode */ + if (has_16bit_fans(data)) { + data->fan[i][0] |= it87_read_value(data, + IT87_REG_FANX[i]) << 8; + data->fan[i][1] |= it87_read_value(data, + IT87_REG_FANX_MIN[i]) << 8; + } + } + for (i = 0; i < NUM_TEMP; i++) { + if (!(data->has_temp & BIT(i))) + continue; + data->temp[i][0] = + it87_read_value(data, IT87_REG_TEMP(i)); + + if (has_temp_offset(data) && i < NUM_TEMP_OFFSET) + data->temp[i][3] = + it87_read_value(data, + IT87_REG_TEMP_OFFSET[i]); + + if (i >= NUM_TEMP_LIMIT) + continue; + + data->temp[i][1] = + it87_read_value(data, IT87_REG_TEMP_LOW(i)); + data->temp[i][2] = + it87_read_value(data, IT87_REG_TEMP_HIGH(i)); + } + + /* Newer chips don't have clock dividers */ + if ((data->has_fan & 0x07) && !has_16bit_fans(data)) { + i = it87_read_value(data, IT87_REG_FAN_DIV); + data->fan_div[0] = i & 0x07; + data->fan_div[1] = (i >> 3) & 0x07; + data->fan_div[2] = (i & 0x40) ? 3 : 1; + } + + data->alarms = + it87_read_value(data, IT87_REG_ALARM1) | + (it87_read_value(data, IT87_REG_ALARM2) << 8) | + (it87_read_value(data, IT87_REG_ALARM3) << 16); + data->beeps = it87_read_value(data, IT87_REG_BEEP_ENABLE); + + data->fan_main_ctrl = it87_read_value(data, + IT87_REG_FAN_MAIN_CTRL); + data->fan_ctl = it87_read_value(data, IT87_REG_FAN_CTL); + for (i = 0; i < NUM_PWM; i++) { + if (!(data->has_pwm & BIT(i))) + continue; + it87_update_pwm_ctrl(data, i); + } + + data->sensor = it87_read_value(data, IT87_REG_TEMP_ENABLE); + data->extra = it87_read_value(data, IT87_REG_TEMP_EXTRA); + /* + * The IT8705F does not have VID capability. + * The IT8718F and later don't use IT87_REG_VID for the + * same purpose. + */ + if (data->type == it8712 || data->type == it8716) { + data->vid = it87_read_value(data, IT87_REG_VID); + /* + * The older IT8712F revisions had only 5 VID pins, + * but we assume it is always safe to read 6 bits. + */ + data->vid &= 0x3f; + } + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static ssize_t show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct it87_data *data = it87_update_device(dev); + int index = sattr->index; + int nr = sattr->nr; + + return sprintf(buf, "%d\n", in_from_reg(data, nr, data->in[nr][index])); +} + +static ssize_t set_in(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct it87_data *data = dev_get_drvdata(dev); + int index = sattr->index; + int nr = sattr->nr; + unsigned long val; + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->in[nr][index] = in_to_reg(data, nr, val); + it87_write_value(data, + index == 1 ? IT87_REG_VIN_MIN(nr) + : IT87_REG_VIN_MAX(nr), + data->in[nr][index]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0); +static SENSOR_DEVICE_ATTR_2(in0_min, S_IRUGO | S_IWUSR, show_in, set_in, + 0, 1); +static SENSOR_DEVICE_ATTR_2(in0_max, S_IRUGO | S_IWUSR, show_in, set_in, + 0, 2); + +static SENSOR_DEVICE_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 1, 0); +static SENSOR_DEVICE_ATTR_2(in1_min, S_IRUGO | S_IWUSR, show_in, set_in, + 1, 1); +static SENSOR_DEVICE_ATTR_2(in1_max, S_IRUGO | S_IWUSR, show_in, set_in, + 1, 2); + +static SENSOR_DEVICE_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 2, 0); +static SENSOR_DEVICE_ATTR_2(in2_min, S_IRUGO | S_IWUSR, show_in, set_in, + 2, 1); +static SENSOR_DEVICE_ATTR_2(in2_max, S_IRUGO | S_IWUSR, show_in, set_in, + 2, 2); + +static SENSOR_DEVICE_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 3, 0); +static SENSOR_DEVICE_ATTR_2(in3_min, S_IRUGO | S_IWUSR, show_in, set_in, + 3, 1); +static SENSOR_DEVICE_ATTR_2(in3_max, S_IRUGO | S_IWUSR, show_in, set_in, + 3, 2); + +static SENSOR_DEVICE_ATTR_2(in4_input, S_IRUGO, show_in, NULL, 4, 0); +static SENSOR_DEVICE_ATTR_2(in4_min, S_IRUGO | S_IWUSR, show_in, set_in, + 4, 1); +static SENSOR_DEVICE_ATTR_2(in4_max, S_IRUGO | S_IWUSR, show_in, set_in, + 4, 2); + +static SENSOR_DEVICE_ATTR_2(in5_input, S_IRUGO, show_in, NULL, 5, 0); +static SENSOR_DEVICE_ATTR_2(in5_min, S_IRUGO | S_IWUSR, show_in, set_in, + 5, 1); +static SENSOR_DEVICE_ATTR_2(in5_max, S_IRUGO | S_IWUSR, show_in, set_in, + 5, 2); + +static SENSOR_DEVICE_ATTR_2(in6_input, S_IRUGO, show_in, NULL, 6, 0); +static SENSOR_DEVICE_ATTR_2(in6_min, S_IRUGO | S_IWUSR, show_in, set_in, + 6, 1); +static SENSOR_DEVICE_ATTR_2(in6_max, S_IRUGO | S_IWUSR, show_in, set_in, + 6, 2); + +static SENSOR_DEVICE_ATTR_2(in7_input, S_IRUGO, show_in, NULL, 7, 0); +static SENSOR_DEVICE_ATTR_2(in7_min, S_IRUGO | S_IWUSR, show_in, set_in, + 7, 1); +static SENSOR_DEVICE_ATTR_2(in7_max, S_IRUGO | S_IWUSR, show_in, set_in, + 7, 2); + +static SENSOR_DEVICE_ATTR_2(in8_input, S_IRUGO, show_in, NULL, 8, 0); +static SENSOR_DEVICE_ATTR_2(in9_input, S_IRUGO, show_in, NULL, 9, 0); +static SENSOR_DEVICE_ATTR_2(in10_input, S_IRUGO, show_in, NULL, 10, 0); +static SENSOR_DEVICE_ATTR_2(in11_input, S_IRUGO, show_in, NULL, 11, 0); +static SENSOR_DEVICE_ATTR_2(in12_input, S_IRUGO, show_in, NULL, 12, 0); + +/* Up to 6 temperatures */ +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + struct it87_data *data = it87_update_device(dev); + + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[nr][index])); +} + +static ssize_t set_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + struct it87_data *data = dev_get_drvdata(dev); + long val; + u8 reg, regval; + + if (kstrtol(buf, 10, &val) < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + + switch (index) { + default: + case 1: + reg = IT87_REG_TEMP_LOW(nr); + break; + case 2: + reg = IT87_REG_TEMP_HIGH(nr); + break; + case 3: + regval = it87_read_value(data, IT87_REG_BEEP_ENABLE); + if (!(regval & 0x80)) { + regval |= 0x80; + it87_write_value(data, IT87_REG_BEEP_ENABLE, regval); + } + data->valid = 0; + reg = IT87_REG_TEMP_OFFSET[nr]; + break; + } + + data->temp[nr][index] = TEMP_TO_REG(val); + it87_write_value(data, reg, data->temp[nr][index]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0); +static SENSOR_DEVICE_ATTR_2(temp1_min, S_IRUGO | S_IWUSR, show_temp, set_temp, + 0, 1); +static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 0, 2); +static SENSOR_DEVICE_ATTR_2(temp1_offset, S_IRUGO | S_IWUSR, show_temp, + set_temp, 0, 3); +static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 1, 0); +static SENSOR_DEVICE_ATTR_2(temp2_min, S_IRUGO | S_IWUSR, show_temp, set_temp, + 1, 1); +static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 1, 2); +static SENSOR_DEVICE_ATTR_2(temp2_offset, S_IRUGO | S_IWUSR, show_temp, + set_temp, 1, 3); +static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 2, 0); +static SENSOR_DEVICE_ATTR_2(temp3_min, S_IRUGO | S_IWUSR, show_temp, set_temp, + 2, 1); +static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 2, 2); +static SENSOR_DEVICE_ATTR_2(temp3_offset, S_IRUGO | S_IWUSR, show_temp, + set_temp, 2, 3); +static SENSOR_DEVICE_ATTR_2(temp4_input, S_IRUGO, show_temp, NULL, 3, 0); +static SENSOR_DEVICE_ATTR_2(temp5_input, S_IRUGO, show_temp, NULL, 4, 0); +static SENSOR_DEVICE_ATTR_2(temp6_input, S_IRUGO, show_temp, NULL, 5, 0); + +static ssize_t show_temp_type(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct it87_data *data = it87_update_device(dev); + u8 reg = data->sensor; /* In case value is updated while used */ + u8 extra = data->extra; + + if ((has_temp_peci(data, nr) && (reg >> 6 == nr + 1)) || + (has_temp_old_peci(data, nr) && (extra & 0x80))) + return sprintf(buf, "6\n"); /* Intel PECI */ + if (reg & (1 << nr)) + return sprintf(buf, "3\n"); /* thermal diode */ + if (reg & (8 << nr)) + return sprintf(buf, "4\n"); /* thermistor */ + return sprintf(buf, "0\n"); /* disabled */ +} + +static ssize_t set_temp_type(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + + struct it87_data *data = dev_get_drvdata(dev); + long val; + u8 reg, extra; + + if (kstrtol(buf, 10, &val) < 0) + return -EINVAL; + + reg = it87_read_value(data, IT87_REG_TEMP_ENABLE); + reg &= ~(1 << nr); + reg &= ~(8 << nr); + if (has_temp_peci(data, nr) && (reg >> 6 == nr + 1 || val == 6)) + reg &= 0x3f; + extra = it87_read_value(data, IT87_REG_TEMP_EXTRA); + if (has_temp_old_peci(data, nr) && ((extra & 0x80) || val == 6)) + extra &= 0x7f; + if (val == 2) { /* backwards compatibility */ + dev_warn(dev, + "Sensor type 2 is deprecated, please use 4 instead\n"); + val = 4; + } + /* 3 = thermal diode; 4 = thermistor; 6 = Intel PECI; 0 = disabled */ + if (val == 3) + reg |= 1 << nr; + else if (val == 4) + reg |= 8 << nr; + else if (has_temp_peci(data, nr) && val == 6) + reg |= (nr + 1) << 6; + else if (has_temp_old_peci(data, nr) && val == 6) + extra |= 0x80; + else if (val != 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->sensor = reg; + data->extra = extra; + it87_write_value(data, IT87_REG_TEMP_ENABLE, data->sensor); + if (has_temp_old_peci(data, nr)) + it87_write_value(data, IT87_REG_TEMP_EXTRA, data->extra); + data->valid = 0; /* Force cache refresh */ + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_type, S_IRUGO | S_IWUSR, show_temp_type, + set_temp_type, 0); +static SENSOR_DEVICE_ATTR(temp2_type, S_IRUGO | S_IWUSR, show_temp_type, + set_temp_type, 1); +static SENSOR_DEVICE_ATTR(temp3_type, S_IRUGO | S_IWUSR, show_temp_type, + set_temp_type, 2); + +/* 6 Fans */ + +static int pwm_mode(const struct it87_data *data, int nr) +{ + if (data->type != it8603 && nr < 3 && !(data->fan_main_ctrl & BIT(nr))) + return 0; /* Full speed */ + if (data->pwm_ctrl[nr] & 0x80) + return 2; /* Automatic mode */ + if ((data->type == it8603 || nr >= 3) && + data->pwm_duty[nr] == pwm_to_reg(data, 0xff)) + return 0; /* Full speed */ + + return 1; /* Manual mode */ +} + +static ssize_t show_fan(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + int speed; + struct it87_data *data = it87_update_device(dev); + + speed = has_16bit_fans(data) ? + FAN16_FROM_REG(data->fan[nr][index]) : + FAN_FROM_REG(data->fan[nr][index], + DIV_FROM_REG(data->fan_div[nr])); + return sprintf(buf, "%d\n", speed); +} + +static ssize_t show_fan_div(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct it87_data *data = it87_update_device(dev); + int nr = sensor_attr->index; + + return sprintf(buf, "%lu\n", DIV_FROM_REG(data->fan_div[nr])); +} + +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct it87_data *data = it87_update_device(dev); + int nr = sensor_attr->index; + + return sprintf(buf, "%d\n", pwm_mode(data, nr)); +} + +static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct it87_data *data = it87_update_device(dev); + int nr = sensor_attr->index; + + return sprintf(buf, "%d\n", + pwm_from_reg(data, data->pwm_duty[nr])); +} + +static ssize_t show_pwm_freq(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct it87_data *data = it87_update_device(dev); + int nr = sensor_attr->index; + unsigned int freq; + int index; + + if (has_pwm_freq2(data) && nr == 1) + index = (data->extra >> 4) & 0x07; + else + index = (data->fan_ctl >> 4) & 0x07; + + freq = pwm_freq[index] / (has_newer_autopwm(data) ? 256 : 128); + + return sprintf(buf, "%u\n", freq); +} + +static ssize_t set_fan(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + + struct it87_data *data = dev_get_drvdata(dev); + long val; + u8 reg; + + if (kstrtol(buf, 10, &val) < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + + if (has_16bit_fans(data)) { + data->fan[nr][index] = FAN16_TO_REG(val); + it87_write_value(data, IT87_REG_FAN_MIN[nr], + data->fan[nr][index] & 0xff); + it87_write_value(data, IT87_REG_FANX_MIN[nr], + data->fan[nr][index] >> 8); + } else { + reg = it87_read_value(data, IT87_REG_FAN_DIV); + switch (nr) { + case 0: + data->fan_div[nr] = reg & 0x07; + break; + case 1: + data->fan_div[nr] = (reg >> 3) & 0x07; + break; + case 2: + data->fan_div[nr] = (reg & 0x40) ? 3 : 1; + break; + } + data->fan[nr][index] = + FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); + it87_write_value(data, IT87_REG_FAN_MIN[nr], + data->fan[nr][index]); + } + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct it87_data *data = dev_get_drvdata(dev); + int nr = sensor_attr->index; + unsigned long val; + int min; + u8 old; + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + old = it87_read_value(data, IT87_REG_FAN_DIV); + + /* Save fan min limit */ + min = FAN_FROM_REG(data->fan[nr][1], DIV_FROM_REG(data->fan_div[nr])); + + switch (nr) { + case 0: + case 1: + data->fan_div[nr] = DIV_TO_REG(val); + break; + case 2: + if (val < 8) + data->fan_div[nr] = 1; + else + data->fan_div[nr] = 3; + } + val = old & 0x80; + val |= (data->fan_div[0] & 0x07); + val |= (data->fan_div[1] & 0x07) << 3; + if (data->fan_div[2] == 3) + val |= 0x1 << 6; + it87_write_value(data, IT87_REG_FAN_DIV, val); + + /* Restore fan min limit */ + data->fan[nr][1] = FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr])); + it87_write_value(data, IT87_REG_FAN_MIN[nr], data->fan[nr][1]); + + mutex_unlock(&data->update_lock); + return count; +} + +/* Returns 0 if OK, -EINVAL otherwise */ +static int check_trip_points(struct device *dev, int nr) +{ + const struct it87_data *data = dev_get_drvdata(dev); + int i, err = 0; + + if (has_old_autopwm(data)) { + for (i = 0; i < 3; i++) { + if (data->auto_temp[nr][i] > data->auto_temp[nr][i + 1]) + err = -EINVAL; + } + for (i = 0; i < 2; i++) { + if (data->auto_pwm[nr][i] > data->auto_pwm[nr][i + 1]) + err = -EINVAL; + } + } else if (has_newer_autopwm(data)) { + for (i = 1; i < 3; i++) { + if (data->auto_temp[nr][i] > data->auto_temp[nr][i + 1]) + err = -EINVAL; + } + } + + if (err) { + dev_err(dev, + "Inconsistent trip points, not switching to automatic mode\n"); + dev_err(dev, "Adjust the trip points and try again\n"); + } + return err; +} + +static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct it87_data *data = dev_get_drvdata(dev); + int nr = sensor_attr->index; + long val; + + if (kstrtol(buf, 10, &val) < 0 || val < 0 || val > 2) + return -EINVAL; + + /* Check trip points before switching to automatic mode */ + if (val == 2) { + if (check_trip_points(dev, nr) < 0) + return -EINVAL; + } + + mutex_lock(&data->update_lock); + + if (val == 0) { + if (nr < 3 && data->type != it8603) { + int tmp; + /* make sure the fan is on when in on/off mode */ + tmp = it87_read_value(data, IT87_REG_FAN_CTL); + it87_write_value(data, IT87_REG_FAN_CTL, tmp | BIT(nr)); + /* set on/off mode */ + data->fan_main_ctrl &= ~BIT(nr); + it87_write_value(data, IT87_REG_FAN_MAIN_CTRL, + data->fan_main_ctrl); + } else { + u8 ctrl; + + /* No on/off mode, set maximum pwm value */ + data->pwm_duty[nr] = pwm_to_reg(data, 0xff); + it87_write_value(data, IT87_REG_PWM_DUTY[nr], + data->pwm_duty[nr]); + /* and set manual mode */ + if (has_newer_autopwm(data)) { + ctrl = (data->pwm_ctrl[nr] & 0x7c) | + data->pwm_temp_map[nr]; + } else { + ctrl = data->pwm_duty[nr]; + } + data->pwm_ctrl[nr] = ctrl; + it87_write_value(data, IT87_REG_PWM[nr], ctrl); + } + } else { + u8 ctrl; + + if (has_newer_autopwm(data)) { + ctrl = (data->pwm_ctrl[nr] & 0x7c) | + data->pwm_temp_map[nr]; + if (val != 1) + ctrl |= 0x80; + } else { + ctrl = (val == 1 ? data->pwm_duty[nr] : 0x80); + } + data->pwm_ctrl[nr] = ctrl; + it87_write_value(data, IT87_REG_PWM[nr], ctrl); + + if (data->type != it8603 && nr < 3) { + /* set SmartGuardian mode */ + data->fan_main_ctrl |= BIT(nr); + it87_write_value(data, IT87_REG_FAN_MAIN_CTRL, + data->fan_main_ctrl); + } + } + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct it87_data *data = dev_get_drvdata(dev); + int nr = sensor_attr->index; + long val; + + if (kstrtol(buf, 10, &val) < 0 || val < 0 || val > 255) + return -EINVAL; + + mutex_lock(&data->update_lock); + it87_update_pwm_ctrl(data, nr); + if (has_newer_autopwm(data)) { + /* + * If we are in automatic mode, the PWM duty cycle register + * is read-only so we can't write the value. + */ + if (data->pwm_ctrl[nr] & 0x80) { + mutex_unlock(&data->update_lock); + return -EBUSY; + } + data->pwm_duty[nr] = pwm_to_reg(data, val); + it87_write_value(data, IT87_REG_PWM_DUTY[nr], + data->pwm_duty[nr]); + } else { + data->pwm_duty[nr] = pwm_to_reg(data, val); + /* + * If we are in manual mode, write the duty cycle immediately; + * otherwise, just store it for later use. + */ + if (!(data->pwm_ctrl[nr] & 0x80)) { + data->pwm_ctrl[nr] = data->pwm_duty[nr]; + it87_write_value(data, IT87_REG_PWM[nr], + data->pwm_ctrl[nr]); + } + } + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_pwm_freq(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct it87_data *data = dev_get_drvdata(dev); + int nr = sensor_attr->index; + unsigned long val; + int i; + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + + val = clamp_val(val, 0, 1000000); + val *= has_newer_autopwm(data) ? 256 : 128; + + /* Search for the nearest available frequency */ + for (i = 0; i < 7; i++) { + if (val > (pwm_freq[i] + pwm_freq[i + 1]) / 2) + break; + } + + mutex_lock(&data->update_lock); + if (nr == 0) { + data->fan_ctl = it87_read_value(data, IT87_REG_FAN_CTL) & 0x8f; + data->fan_ctl |= i << 4; + it87_write_value(data, IT87_REG_FAN_CTL, data->fan_ctl); + } else { + data->extra = it87_read_value(data, IT87_REG_TEMP_EXTRA) & 0x8f; + data->extra |= i << 4; + it87_write_value(data, IT87_REG_TEMP_EXTRA, data->extra); + } + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_temp_map(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct it87_data *data = it87_update_device(dev); + int nr = sensor_attr->index; + int map; + + map = data->pwm_temp_map[nr]; + if (map >= 3) + map = 0; /* Should never happen */ + if (nr >= 3) /* pwm channels 3..6 map to temp4..6 */ + map += 3; + + return sprintf(buf, "%d\n", (int)BIT(map)); +} + +static ssize_t set_pwm_temp_map(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct it87_data *data = dev_get_drvdata(dev); + int nr = sensor_attr->index; + long val; + u8 reg; + + if (kstrtol(buf, 10, &val) < 0) + return -EINVAL; + + if (nr >= 3) + val -= 3; + + switch (val) { + case BIT(0): + reg = 0x00; + break; + case BIT(1): + reg = 0x01; + break; + case BIT(2): + reg = 0x02; + break; + default: + return -EINVAL; + } + + mutex_lock(&data->update_lock); + it87_update_pwm_ctrl(data, nr); + data->pwm_temp_map[nr] = reg; + /* + * If we are in automatic mode, write the temp mapping immediately; + * otherwise, just store it for later use. + */ + if (data->pwm_ctrl[nr] & 0x80) { + data->pwm_ctrl[nr] = (data->pwm_ctrl[nr] & 0xfc) | + data->pwm_temp_map[nr]; + it87_write_value(data, IT87_REG_PWM[nr], data->pwm_ctrl[nr]); + } + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_auto_pwm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct it87_data *data = it87_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int point = sensor_attr->index; + + return sprintf(buf, "%d\n", + pwm_from_reg(data, data->auto_pwm[nr][point])); +} + +static ssize_t set_auto_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct it87_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int point = sensor_attr->index; + int regaddr; + long val; + + if (kstrtol(buf, 10, &val) < 0 || val < 0 || val > 255) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->auto_pwm[nr][point] = pwm_to_reg(data, val); + if (has_newer_autopwm(data)) + regaddr = IT87_REG_AUTO_TEMP(nr, 3); + else + regaddr = IT87_REG_AUTO_PWM(nr, point); + it87_write_value(data, regaddr, data->auto_pwm[nr][point]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_auto_pwm_slope(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it87_data *data = it87_update_device(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + + return sprintf(buf, "%d\n", data->auto_pwm[nr][1] & 0x7f); +} + +static ssize_t set_auto_pwm_slope(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct it87_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + unsigned long val; + + if (kstrtoul(buf, 10, &val) < 0 || val > 127) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->auto_pwm[nr][1] = (data->auto_pwm[nr][1] & 0x80) | val; + it87_write_value(data, IT87_REG_AUTO_TEMP(nr, 4), + data->auto_pwm[nr][1]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_auto_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct it87_data *data = it87_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int point = sensor_attr->index; + int reg; + + if (has_old_autopwm(data) || point) + reg = data->auto_temp[nr][point]; + else + reg = data->auto_temp[nr][1] - (data->auto_temp[nr][0] & 0x1f); + + return sprintf(buf, "%d\n", TEMP_FROM_REG(reg)); +} + +static ssize_t set_auto_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct it87_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int point = sensor_attr->index; + long val; + int reg; + + if (kstrtol(buf, 10, &val) < 0 || val < -128000 || val > 127000) + return -EINVAL; + + mutex_lock(&data->update_lock); + if (has_newer_autopwm(data) && !point) { + reg = data->auto_temp[nr][1] - TEMP_TO_REG(val); + reg = clamp_val(reg, 0, 0x1f) | (data->auto_temp[nr][0] & 0xe0); + data->auto_temp[nr][0] = reg; + it87_write_value(data, IT87_REG_AUTO_TEMP(nr, 5), reg); + } else { + reg = TEMP_TO_REG(val); + data->auto_temp[nr][point] = reg; + if (has_newer_autopwm(data)) + point--; + it87_write_value(data, IT87_REG_AUTO_TEMP(nr, point), reg); + } + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR_2(fan1_input, S_IRUGO, show_fan, NULL, 0, 0); +static SENSOR_DEVICE_ATTR_2(fan1_min, S_IRUGO | S_IWUSR, show_fan, set_fan, + 0, 1); +static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR, show_fan_div, + set_fan_div, 0); + +static SENSOR_DEVICE_ATTR_2(fan2_input, S_IRUGO, show_fan, NULL, 1, 0); +static SENSOR_DEVICE_ATTR_2(fan2_min, S_IRUGO | S_IWUSR, show_fan, set_fan, + 1, 1); +static SENSOR_DEVICE_ATTR(fan2_div, S_IRUGO | S_IWUSR, show_fan_div, + set_fan_div, 1); + +static SENSOR_DEVICE_ATTR_2(fan3_input, S_IRUGO, show_fan, NULL, 2, 0); +static SENSOR_DEVICE_ATTR_2(fan3_min, S_IRUGO | S_IWUSR, show_fan, set_fan, + 2, 1); +static SENSOR_DEVICE_ATTR(fan3_div, S_IRUGO | S_IWUSR, show_fan_div, + set_fan_div, 2); + +static SENSOR_DEVICE_ATTR_2(fan4_input, S_IRUGO, show_fan, NULL, 3, 0); +static SENSOR_DEVICE_ATTR_2(fan4_min, S_IRUGO | S_IWUSR, show_fan, set_fan, + 3, 1); + +static SENSOR_DEVICE_ATTR_2(fan5_input, S_IRUGO, show_fan, NULL, 4, 0); +static SENSOR_DEVICE_ATTR_2(fan5_min, S_IRUGO | S_IWUSR, show_fan, set_fan, + 4, 1); + +static SENSOR_DEVICE_ATTR_2(fan6_input, S_IRUGO, show_fan, NULL, 5, 0); +static SENSOR_DEVICE_ATTR_2(fan6_min, S_IRUGO | S_IWUSR, show_fan, set_fan, + 5, 1); + +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, + show_pwm_enable, set_pwm_enable, 0); +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm1_freq, S_IRUGO | S_IWUSR, show_pwm_freq, + set_pwm_freq, 0); +static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IRUGO, + show_pwm_temp_map, set_pwm_temp_map, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point1_pwm, S_IRUGO | S_IWUSR, + show_auto_pwm, set_auto_pwm, 0, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point2_pwm, S_IRUGO | S_IWUSR, + show_auto_pwm, set_auto_pwm, 0, 1); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point3_pwm, S_IRUGO | S_IWUSR, + show_auto_pwm, set_auto_pwm, 0, 2); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point4_pwm, S_IRUGO, + show_auto_pwm, NULL, 0, 3); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point1_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 0, 1); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point1_temp_hyst, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 0, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point2_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 0, 2); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point3_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 0, 3); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point4_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 0, 4); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_start, S_IRUGO | S_IWUSR, + show_auto_pwm, set_auto_pwm, 0, 0); +static SENSOR_DEVICE_ATTR(pwm1_auto_slope, S_IRUGO | S_IWUSR, + show_auto_pwm_slope, set_auto_pwm_slope, 0); + +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IRUGO | S_IWUSR, + show_pwm_enable, set_pwm_enable, 1); +static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, show_pwm, set_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm2_freq, S_IRUGO, show_pwm_freq, set_pwm_freq, 1); +static SENSOR_DEVICE_ATTR(pwm2_auto_channels_temp, S_IRUGO, + show_pwm_temp_map, set_pwm_temp_map, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point1_pwm, S_IRUGO | S_IWUSR, + show_auto_pwm, set_auto_pwm, 1, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point2_pwm, S_IRUGO | S_IWUSR, + show_auto_pwm, set_auto_pwm, 1, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point3_pwm, S_IRUGO | S_IWUSR, + show_auto_pwm, set_auto_pwm, 1, 2); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point4_pwm, S_IRUGO, + show_auto_pwm, NULL, 1, 3); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point1_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 1, 1); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point1_temp_hyst, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 1, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point2_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 1, 2); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point3_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 1, 3); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point4_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 1, 4); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_start, S_IRUGO | S_IWUSR, + show_auto_pwm, set_auto_pwm, 1, 0); +static SENSOR_DEVICE_ATTR(pwm2_auto_slope, S_IRUGO | S_IWUSR, + show_auto_pwm_slope, set_auto_pwm_slope, 1); + +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IRUGO | S_IWUSR, + show_pwm_enable, set_pwm_enable, 2); +static SENSOR_DEVICE_ATTR(pwm3, S_IRUGO | S_IWUSR, show_pwm, set_pwm, 2); +static SENSOR_DEVICE_ATTR(pwm3_freq, S_IRUGO, show_pwm_freq, NULL, 2); +static SENSOR_DEVICE_ATTR(pwm3_auto_channels_temp, S_IRUGO, + show_pwm_temp_map, set_pwm_temp_map, 2); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point1_pwm, S_IRUGO | S_IWUSR, + show_auto_pwm, set_auto_pwm, 2, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point2_pwm, S_IRUGO | S_IWUSR, + show_auto_pwm, set_auto_pwm, 2, 1); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point3_pwm, S_IRUGO | S_IWUSR, + show_auto_pwm, set_auto_pwm, 2, 2); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point4_pwm, S_IRUGO, + show_auto_pwm, NULL, 2, 3); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point1_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 1); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point1_temp_hyst, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point2_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 2); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point3_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 3); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point4_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 4); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_start, S_IRUGO | S_IWUSR, + show_auto_pwm, set_auto_pwm, 2, 0); +static SENSOR_DEVICE_ATTR(pwm3_auto_slope, S_IRUGO | S_IWUSR, + show_auto_pwm_slope, set_auto_pwm_slope, 2); + +static SENSOR_DEVICE_ATTR(pwm4_enable, S_IRUGO | S_IWUSR, + show_pwm_enable, set_pwm_enable, 3); +static SENSOR_DEVICE_ATTR(pwm4, S_IRUGO | S_IWUSR, show_pwm, set_pwm, 3); +static SENSOR_DEVICE_ATTR(pwm4_freq, S_IRUGO, show_pwm_freq, NULL, 3); +static SENSOR_DEVICE_ATTR(pwm4_auto_channels_temp, S_IRUGO, + show_pwm_temp_map, set_pwm_temp_map, 3); +static SENSOR_DEVICE_ATTR_2(pwm4_auto_point1_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 1); +static SENSOR_DEVICE_ATTR_2(pwm4_auto_point1_temp_hyst, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 0); +static SENSOR_DEVICE_ATTR_2(pwm4_auto_point2_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 2); +static SENSOR_DEVICE_ATTR_2(pwm4_auto_point3_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 3); +static SENSOR_DEVICE_ATTR_2(pwm4_auto_start, S_IRUGO | S_IWUSR, + show_auto_pwm, set_auto_pwm, 3, 0); +static SENSOR_DEVICE_ATTR(pwm4_auto_slope, S_IRUGO | S_IWUSR, + show_auto_pwm_slope, set_auto_pwm_slope, 3); + +static SENSOR_DEVICE_ATTR(pwm5_enable, S_IRUGO | S_IWUSR, + show_pwm_enable, set_pwm_enable, 4); +static SENSOR_DEVICE_ATTR(pwm5, S_IRUGO | S_IWUSR, show_pwm, set_pwm, 4); +static SENSOR_DEVICE_ATTR(pwm5_freq, S_IRUGO, show_pwm_freq, NULL, 4); +static SENSOR_DEVICE_ATTR(pwm5_auto_channels_temp, S_IRUGO, + show_pwm_temp_map, set_pwm_temp_map, 4); +static SENSOR_DEVICE_ATTR_2(pwm5_auto_point1_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 1); +static SENSOR_DEVICE_ATTR_2(pwm5_auto_point1_temp_hyst, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 0); +static SENSOR_DEVICE_ATTR_2(pwm5_auto_point2_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 2); +static SENSOR_DEVICE_ATTR_2(pwm5_auto_point3_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 3); +static SENSOR_DEVICE_ATTR_2(pwm5_auto_start, S_IRUGO | S_IWUSR, + show_auto_pwm, set_auto_pwm, 4, 0); +static SENSOR_DEVICE_ATTR(pwm5_auto_slope, S_IRUGO | S_IWUSR, + show_auto_pwm_slope, set_auto_pwm_slope, 4); + +static SENSOR_DEVICE_ATTR(pwm6_enable, S_IRUGO | S_IWUSR, + show_pwm_enable, set_pwm_enable, 5); +static SENSOR_DEVICE_ATTR(pwm6, S_IRUGO | S_IWUSR, show_pwm, set_pwm, 5); +static SENSOR_DEVICE_ATTR(pwm6_freq, S_IRUGO, show_pwm_freq, NULL, 5); +static SENSOR_DEVICE_ATTR(pwm6_auto_channels_temp, S_IRUGO, + show_pwm_temp_map, set_pwm_temp_map, 5); +static SENSOR_DEVICE_ATTR_2(pwm6_auto_point1_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 1); +static SENSOR_DEVICE_ATTR_2(pwm6_auto_point1_temp_hyst, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 0); +static SENSOR_DEVICE_ATTR_2(pwm6_auto_point2_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 2); +static SENSOR_DEVICE_ATTR_2(pwm6_auto_point3_temp, S_IRUGO | S_IWUSR, + show_auto_temp, set_auto_temp, 2, 3); +static SENSOR_DEVICE_ATTR_2(pwm6_auto_start, S_IRUGO | S_IWUSR, + show_auto_pwm, set_auto_pwm, 5, 0); +static SENSOR_DEVICE_ATTR(pwm6_auto_slope, S_IRUGO | S_IWUSR, + show_auto_pwm_slope, set_auto_pwm_slope, 5); + +/* Alarms */ +static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct it87_data *data = it87_update_device(dev); + + return sprintf(buf, "%u\n", data->alarms); +} +static DEVICE_ATTR_RO(alarms); + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct it87_data *data = it87_update_device(dev); + int bitnr = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1); +} + +static ssize_t clear_intrusion(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct it87_data *data = dev_get_drvdata(dev); + int config; + long val; + + if (kstrtol(buf, 10, &val) < 0 || val != 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + config = it87_read_value(data, IT87_REG_CONFIG); + if (config < 0) { + count = config; + } else { + config |= BIT(5); + it87_write_value(data, IT87_REG_CONFIG, config); + /* Invalidate cache to force re-read */ + data->valid = 0; + } + mutex_unlock(&data->update_lock); + + return count; +} + +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 12); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 13); +static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 14); +static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 15); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(fan5_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(fan6_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 16); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 17); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 18); +static SENSOR_DEVICE_ATTR(intrusion0_alarm, S_IRUGO | S_IWUSR, + show_alarm, clear_intrusion, 4); + +static ssize_t show_beep(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct it87_data *data = it87_update_device(dev); + int bitnr = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%u\n", (data->beeps >> bitnr) & 1); +} + +static ssize_t set_beep(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct it87_data *data = dev_get_drvdata(dev); + long val; + + if (kstrtol(buf, 10, &val) < 0 || (val != 0 && val != 1)) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->beeps = it87_read_value(data, IT87_REG_BEEP_ENABLE); + if (val) + data->beeps |= BIT(bitnr); + else + data->beeps &= ~BIT(bitnr); + it87_write_value(data, IT87_REG_BEEP_ENABLE, data->beeps); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(in0_beep, S_IRUGO | S_IWUSR, + show_beep, set_beep, 1); +static SENSOR_DEVICE_ATTR(in1_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in3_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in4_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in5_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in6_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in7_beep, S_IRUGO, show_beep, NULL, 1); +/* fanX_beep writability is set later */ +static SENSOR_DEVICE_ATTR(fan1_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(fan2_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(fan3_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(fan4_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(fan5_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(fan6_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(temp1_beep, S_IRUGO | S_IWUSR, + show_beep, set_beep, 2); +static SENSOR_DEVICE_ATTR(temp2_beep, S_IRUGO, show_beep, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_beep, S_IRUGO, show_beep, NULL, 2); + +static ssize_t vrm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct it87_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", data->vrm); +} + +static ssize_t vrm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct it87_data *data = dev_get_drvdata(dev); + unsigned long val; + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + + data->vrm = val; + + return count; +} +static DEVICE_ATTR_RW(vrm); + +static ssize_t cpu0_vid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it87_data *data = it87_update_device(dev); + + return sprintf(buf, "%ld\n", (long)vid_from_reg(data->vid, data->vrm)); +} +static DEVICE_ATTR_RO(cpu0_vid); + +static ssize_t show_label(struct device *dev, struct device_attribute *attr, + char *buf) +{ + static const char * const labels[] = { + "+5V", + "5VSB", + "Vbat", + "AVCC", + }; + static const char * const labels_it8721[] = { + "+3.3V", + "3VSB", + "Vbat", + "+3.3V", + }; + struct it87_data *data = dev_get_drvdata(dev); + int nr = to_sensor_dev_attr(attr)->index; + const char *label; + + if (has_vin3_5v(data) && nr == 0) + label = labels[0]; + else if (has_12mv_adc(data) || has_10_9mv_adc(data)) + label = labels_it8721[nr]; + else + label = labels[nr]; + + return sprintf(buf, "%s\n", label); +} +static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, show_label, NULL, 0); +static SENSOR_DEVICE_ATTR(in7_label, S_IRUGO, show_label, NULL, 1); +static SENSOR_DEVICE_ATTR(in8_label, S_IRUGO, show_label, NULL, 2); +/* AVCC3 */ +static SENSOR_DEVICE_ATTR(in9_label, S_IRUGO, show_label, NULL, 3); + +static umode_t it87_in_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct it87_data *data = dev_get_drvdata(dev); + int i = index / 5; /* voltage index */ + int a = index % 5; /* attribute index */ + + if (index >= 40) { /* in8 and higher only have input attributes */ + i = index - 40 + 8; + a = 0; + } + + if (!(data->has_in & BIT(i))) + return 0; + + if (a == 4 && !data->has_beep) + return 0; + + return attr->mode; +} + +static struct attribute *it87_attributes_in[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in0_beep.dev_attr.attr, /* 4 */ + + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in1_beep.dev_attr.attr, /* 9 */ + + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in2_beep.dev_attr.attr, /* 14 */ + + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in3_beep.dev_attr.attr, /* 19 */ + + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + &sensor_dev_attr_in4_beep.dev_attr.attr, /* 24 */ + + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + &sensor_dev_attr_in5_beep.dev_attr.attr, /* 29 */ + + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in6_alarm.dev_attr.attr, + &sensor_dev_attr_in6_beep.dev_attr.attr, /* 34 */ + + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in7_alarm.dev_attr.attr, + &sensor_dev_attr_in7_beep.dev_attr.attr, /* 39 */ + + &sensor_dev_attr_in8_input.dev_attr.attr, /* 40 */ + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in11_input.dev_attr.attr, + &sensor_dev_attr_in12_input.dev_attr.attr, + NULL +}; + +static const struct attribute_group it87_group_in = { + .attrs = it87_attributes_in, + .is_visible = it87_in_is_visible, +}; + +static umode_t it87_temp_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct it87_data *data = dev_get_drvdata(dev); + int i = index / 7; /* temperature index */ + int a = index % 7; /* attribute index */ + + if (index >= 21) { + i = index - 21 + 3; + a = 0; + } + + if (!(data->has_temp & BIT(i))) + return 0; + + if (a == 5 && !has_temp_offset(data)) + return 0; + + if (a == 6 && !data->has_beep) + return 0; + + return attr->mode; +} + +static struct attribute *it87_attributes_temp[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_type.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_offset.dev_attr.attr, /* 5 */ + &sensor_dev_attr_temp1_beep.dev_attr.attr, /* 6 */ + + &sensor_dev_attr_temp2_input.dev_attr.attr, /* 7 */ + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_type.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_offset.dev_attr.attr, + &sensor_dev_attr_temp2_beep.dev_attr.attr, + + &sensor_dev_attr_temp3_input.dev_attr.attr, /* 14 */ + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_type.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_offset.dev_attr.attr, + &sensor_dev_attr_temp3_beep.dev_attr.attr, + + &sensor_dev_attr_temp4_input.dev_attr.attr, /* 21 */ + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp6_input.dev_attr.attr, + NULL +}; + +static const struct attribute_group it87_group_temp = { + .attrs = it87_attributes_temp, + .is_visible = it87_temp_is_visible, +}; + +static umode_t it87_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct it87_data *data = dev_get_drvdata(dev); + + if ((index == 2 || index == 3) && !data->has_vid) + return 0; + + if (index > 3 && !(data->in_internal & BIT(index - 4))) + return 0; + + return attr->mode; +} + +static struct attribute *it87_attributes[] = { + &dev_attr_alarms.attr, + &sensor_dev_attr_intrusion0_alarm.dev_attr.attr, + &dev_attr_vrm.attr, /* 2 */ + &dev_attr_cpu0_vid.attr, /* 3 */ + &sensor_dev_attr_in3_label.dev_attr.attr, /* 4 .. 7 */ + &sensor_dev_attr_in7_label.dev_attr.attr, + &sensor_dev_attr_in8_label.dev_attr.attr, + &sensor_dev_attr_in9_label.dev_attr.attr, + NULL +}; + +static const struct attribute_group it87_group = { + .attrs = it87_attributes, + .is_visible = it87_is_visible, +}; + +static umode_t it87_fan_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct it87_data *data = dev_get_drvdata(dev); + int i = index / 5; /* fan index */ + int a = index % 5; /* attribute index */ + + if (index >= 15) { /* fan 4..6 don't have divisor attributes */ + i = (index - 15) / 4 + 3; + a = (index - 15) % 4; + } + + if (!(data->has_fan & BIT(i))) + return 0; + + if (a == 3) { /* beep */ + if (!data->has_beep) + return 0; + /* first fan beep attribute is writable */ + if (i == __ffs(data->has_fan)) + return attr->mode | S_IWUSR; + } + + if (a == 4 && has_16bit_fans(data)) /* divisor */ + return 0; + + return attr->mode; +} + +static struct attribute *it87_attributes_fan[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_beep.dev_attr.attr, /* 3 */ + &sensor_dev_attr_fan1_div.dev_attr.attr, /* 4 */ + + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_beep.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, /* 9 */ + + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_beep.dev_attr.attr, + &sensor_dev_attr_fan3_div.dev_attr.attr, /* 14 */ + + &sensor_dev_attr_fan4_input.dev_attr.attr, /* 15 */ + &sensor_dev_attr_fan4_min.dev_attr.attr, + &sensor_dev_attr_fan4_alarm.dev_attr.attr, + &sensor_dev_attr_fan4_beep.dev_attr.attr, + + &sensor_dev_attr_fan5_input.dev_attr.attr, /* 19 */ + &sensor_dev_attr_fan5_min.dev_attr.attr, + &sensor_dev_attr_fan5_alarm.dev_attr.attr, + &sensor_dev_attr_fan5_beep.dev_attr.attr, + + &sensor_dev_attr_fan6_input.dev_attr.attr, /* 23 */ + &sensor_dev_attr_fan6_min.dev_attr.attr, + &sensor_dev_attr_fan6_alarm.dev_attr.attr, + &sensor_dev_attr_fan6_beep.dev_attr.attr, + NULL +}; + +static const struct attribute_group it87_group_fan = { + .attrs = it87_attributes_fan, + .is_visible = it87_fan_is_visible, +}; + +static umode_t it87_pwm_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct it87_data *data = dev_get_drvdata(dev); + int i = index / 4; /* pwm index */ + int a = index % 4; /* attribute index */ + + if (!(data->has_pwm & BIT(i))) + return 0; + + /* pwmX_auto_channels_temp is only writable if auto pwm is supported */ + if (a == 3 && (has_old_autopwm(data) || has_newer_autopwm(data))) + return attr->mode | S_IWUSR; + + /* pwm2_freq is writable if there are two pwm frequency selects */ + if (has_pwm_freq2(data) && i == 1 && a == 2) + return attr->mode | S_IWUSR; + + return attr->mode; +} + +static struct attribute *it87_attributes_pwm[] = { + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_freq.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_channels_temp.dev_attr.attr, + + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm2_freq.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_channels_temp.dev_attr.attr, + + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm3_freq.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_channels_temp.dev_attr.attr, + + &sensor_dev_attr_pwm4_enable.dev_attr.attr, + &sensor_dev_attr_pwm4.dev_attr.attr, + &sensor_dev_attr_pwm4_freq.dev_attr.attr, + &sensor_dev_attr_pwm4_auto_channels_temp.dev_attr.attr, + + &sensor_dev_attr_pwm5_enable.dev_attr.attr, + &sensor_dev_attr_pwm5.dev_attr.attr, + &sensor_dev_attr_pwm5_freq.dev_attr.attr, + &sensor_dev_attr_pwm5_auto_channels_temp.dev_attr.attr, + + &sensor_dev_attr_pwm6_enable.dev_attr.attr, + &sensor_dev_attr_pwm6.dev_attr.attr, + &sensor_dev_attr_pwm6_freq.dev_attr.attr, + &sensor_dev_attr_pwm6_auto_channels_temp.dev_attr.attr, + + NULL +}; + +static const struct attribute_group it87_group_pwm = { + .attrs = it87_attributes_pwm, + .is_visible = it87_pwm_is_visible, +}; + +static umode_t it87_auto_pwm_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct it87_data *data = dev_get_drvdata(dev); + int i = index / 11; /* pwm index */ + int a = index % 11; /* attribute index */ + + if (index >= 33) { /* pwm 4..6 */ + i = (index - 33) / 6 + 3; + a = (index - 33) % 6 + 4; + } + + if (!(data->has_pwm & BIT(i))) + return 0; + + if (has_newer_autopwm(data)) { + if (a < 4) /* no auto point pwm */ + return 0; + if (a == 8) /* no auto_point4 */ + return 0; + } + if (has_old_autopwm(data)) { + if (a >= 9) /* no pwm_auto_start, pwm_auto_slope */ + return 0; + } + + return attr->mode; +} + +static struct attribute *it87_attributes_auto_pwm[] = { + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_start.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_slope.dev_attr.attr, + + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, /* 11 */ + &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_start.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_slope.dev_attr.attr, + + &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr, /* 22 */ + &sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_start.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_slope.dev_attr.attr, + + &sensor_dev_attr_pwm4_auto_point1_temp.dev_attr.attr, /* 33 */ + &sensor_dev_attr_pwm4_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm4_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm4_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm4_auto_start.dev_attr.attr, + &sensor_dev_attr_pwm4_auto_slope.dev_attr.attr, + + &sensor_dev_attr_pwm5_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm5_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm5_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm5_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm5_auto_start.dev_attr.attr, + &sensor_dev_attr_pwm5_auto_slope.dev_attr.attr, + + &sensor_dev_attr_pwm6_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm6_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm6_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm6_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm6_auto_start.dev_attr.attr, + &sensor_dev_attr_pwm6_auto_slope.dev_attr.attr, + + NULL, +}; + +static const struct attribute_group it87_group_auto_pwm = { + .attrs = it87_attributes_auto_pwm, + .is_visible = it87_auto_pwm_is_visible, +}; + +/* SuperIO detection - will change isa_address if a chip is found */ +static int __init it87_find(int sioaddr, unsigned short *address, + struct it87_sio_data *sio_data) +{ + int err; + u16 chip_type; + const char *board_vendor, *board_name; + const struct it87_devices *config; + + err = superio_enter(sioaddr); + if (err) + return err; + + err = -ENODEV; + chip_type = force_id ? force_id : superio_inw(sioaddr, DEVID); + + switch (chip_type) { + case IT8705F_DEVID: + sio_data->type = it87; + break; + case IT8712F_DEVID: + sio_data->type = it8712; + break; + case IT8716F_DEVID: + case IT8726F_DEVID: + sio_data->type = it8716; + break; + case IT8718F_DEVID: + sio_data->type = it8718; + break; + case IT8720F_DEVID: + sio_data->type = it8720; + break; + case IT8721F_DEVID: + sio_data->type = it8721; + break; + case IT8728F_DEVID: + sio_data->type = it8728; + break; + case IT8732F_DEVID: + sio_data->type = it8732; + break; + case IT8792E_DEVID: + sio_data->type = it8792; + break; + case IT8771E_DEVID: + sio_data->type = it8771; + break; + case IT8772E_DEVID: + sio_data->type = it8772; + break; + case IT8781F_DEVID: + sio_data->type = it8781; + break; + case IT8782F_DEVID: + sio_data->type = it8782; + break; + case IT8783E_DEVID: + sio_data->type = it8783; + break; + case IT8786E_DEVID: + sio_data->type = it8786; + break; + case IT8790E_DEVID: + sio_data->type = it8790; + break; + case IT8603E_DEVID: + case IT8623E_DEVID: + sio_data->type = it8603; + break; + case IT8620E_DEVID: + sio_data->type = it8620; + break; + case IT8622E_DEVID: + sio_data->type = it8622; + break; + case IT8628E_DEVID: + sio_data->type = it8628; + break; + case 0xffff: /* No device at all */ + goto exit; + default: + pr_debug("Unsupported chip (DEVID=0x%x)\n", chip_type); + goto exit; + } + + superio_select(sioaddr, PME); + if (!(superio_inb(sioaddr, IT87_ACT_REG) & 0x01)) { + pr_info("Device not activated, skipping\n"); + goto exit; + } + + *address = superio_inw(sioaddr, IT87_BASE_REG) & ~(IT87_EXTENT - 1); + if (*address == 0) { + pr_info("Base address not set, skipping\n"); + goto exit; + } + + err = 0; + sio_data->sioaddr = sioaddr; + sio_data->revision = superio_inb(sioaddr, DEVREV) & 0x0f; + pr_info("Found IT%04x%s chip at 0x%x, revision %d\n", chip_type, + it87_devices[sio_data->type].suffix, + *address, sio_data->revision); + + config = &it87_devices[sio_data->type]; + + /* in7 (VSB or VCCH5V) is always internal on some chips */ + if (has_in7_internal(config)) + sio_data->internal |= BIT(1); + + /* in8 (Vbat) is always internal */ + sio_data->internal |= BIT(2); + + /* in9 (AVCC3), always internal if supported */ + if (has_avcc3(config)) + sio_data->internal |= BIT(3); /* in9 is AVCC */ + else + sio_data->skip_in |= BIT(9); + + if (!has_five_pwm(config)) + sio_data->skip_pwm |= BIT(3) | BIT(4) | BIT(5); + else if (!has_six_pwm(config)) + sio_data->skip_pwm |= BIT(5); + + if (!has_vid(config)) + sio_data->skip_vid = 1; + + /* Read GPIO config and VID value from LDN 7 (GPIO) */ + if (sio_data->type == it87) { + /* The IT8705F has a different LD number for GPIO */ + superio_select(sioaddr, 5); + sio_data->beep_pin = superio_inb(sioaddr, + IT87_SIO_BEEP_PIN_REG) & 0x3f; + } else if (sio_data->type == it8783) { + int reg25, reg27, reg2a, reg2c, regef; + + superio_select(sioaddr, GPIO); + + reg25 = superio_inb(sioaddr, IT87_SIO_GPIO1_REG); + reg27 = superio_inb(sioaddr, IT87_SIO_GPIO3_REG); + reg2a = superio_inb(sioaddr, IT87_SIO_PINX1_REG); + reg2c = superio_inb(sioaddr, IT87_SIO_PINX2_REG); + regef = superio_inb(sioaddr, IT87_SIO_SPI_REG); + + /* Check if fan3 is there or not */ + if ((reg27 & BIT(0)) || !(reg2c & BIT(2))) + sio_data->skip_fan |= BIT(2); + if ((reg25 & BIT(4)) || + (!(reg2a & BIT(1)) && (regef & BIT(0)))) + sio_data->skip_pwm |= BIT(2); + + /* Check if fan2 is there or not */ + if (reg27 & BIT(7)) + sio_data->skip_fan |= BIT(1); + if (reg27 & BIT(3)) + sio_data->skip_pwm |= BIT(1); + + /* VIN5 */ + if ((reg27 & BIT(0)) || (reg2c & BIT(2))) + sio_data->skip_in |= BIT(5); /* No VIN5 */ + + /* VIN6 */ + if (reg27 & BIT(1)) + sio_data->skip_in |= BIT(6); /* No VIN6 */ + + /* + * VIN7 + * Does not depend on bit 2 of Reg2C, contrary to datasheet. + */ + if (reg27 & BIT(2)) { + /* + * The data sheet is a bit unclear regarding the + * internal voltage divider for VCCH5V. It says + * "This bit enables and switches VIN7 (pin 91) to the + * internal voltage divider for VCCH5V". + * This is different to other chips, where the internal + * voltage divider would connect VIN7 to an internal + * voltage source. Maybe that is the case here as well. + * + * Since we don't know for sure, re-route it if that is + * not the case, and ask the user to report if the + * resulting voltage is sane. + */ + if (!(reg2c & BIT(1))) { + reg2c |= BIT(1); + superio_outb(sioaddr, IT87_SIO_PINX2_REG, + reg2c); + sio_data->need_in7_reroute = true; + pr_notice("Routing internal VCCH5V to in7.\n"); + } + pr_notice("in7 routed to internal voltage divider, with external pin disabled.\n"); + pr_notice("Please report if it displays a reasonable voltage.\n"); + } + + if (reg2c & BIT(0)) + sio_data->internal |= BIT(0); + if (reg2c & BIT(1)) + sio_data->internal |= BIT(1); + + sio_data->beep_pin = superio_inb(sioaddr, + IT87_SIO_BEEP_PIN_REG) & 0x3f; + } else if (sio_data->type == it8603) { + int reg27, reg29; + + superio_select(sioaddr, GPIO); + + reg27 = superio_inb(sioaddr, IT87_SIO_GPIO3_REG); + + /* Check if fan3 is there or not */ + if (reg27 & BIT(6)) + sio_data->skip_pwm |= BIT(2); + if (reg27 & BIT(7)) + sio_data->skip_fan |= BIT(2); + + /* Check if fan2 is there or not */ + reg29 = superio_inb(sioaddr, IT87_SIO_GPIO5_REG); + if (reg29 & BIT(1)) + sio_data->skip_pwm |= BIT(1); + if (reg29 & BIT(2)) + sio_data->skip_fan |= BIT(1); + + sio_data->skip_in |= BIT(5); /* No VIN5 */ + sio_data->skip_in |= BIT(6); /* No VIN6 */ + + sio_data->beep_pin = superio_inb(sioaddr, + IT87_SIO_BEEP_PIN_REG) & 0x3f; + } else if (sio_data->type == it8620 || sio_data->type == it8628) { + int reg; + + superio_select(sioaddr, GPIO); + + /* Check for pwm5 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO1_REG); + if (reg & BIT(6)) + sio_data->skip_pwm |= BIT(4); + + /* Check for fan4, fan5 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO2_REG); + if (!(reg & BIT(5))) + sio_data->skip_fan |= BIT(3); + if (!(reg & BIT(4))) + sio_data->skip_fan |= BIT(4); + + /* Check for pwm3, fan3 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO3_REG); + if (reg & BIT(6)) + sio_data->skip_pwm |= BIT(2); + if (reg & BIT(7)) + sio_data->skip_fan |= BIT(2); + + /* Check for pwm4 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO4_REG); + if (reg & BIT(2)) + sio_data->skip_pwm |= BIT(3); + + /* Check for pwm2, fan2 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO5_REG); + if (reg & BIT(1)) + sio_data->skip_pwm |= BIT(1); + if (reg & BIT(2)) + sio_data->skip_fan |= BIT(1); + /* Check for pwm6, fan6 */ + if (!(reg & BIT(7))) { + sio_data->skip_pwm |= BIT(5); + sio_data->skip_fan |= BIT(5); + } + + /* Check if AVCC is on VIN3 */ + reg = superio_inb(sioaddr, IT87_SIO_PINX2_REG); + if (reg & BIT(0)) + sio_data->internal |= BIT(0); + else + sio_data->skip_in |= BIT(9); + + sio_data->beep_pin = superio_inb(sioaddr, + IT87_SIO_BEEP_PIN_REG) & 0x3f; + } else if (sio_data->type == it8622) { + int reg; + + superio_select(sioaddr, GPIO); + + /* Check for pwm4, fan4 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO1_REG); + if (reg & BIT(6)) + sio_data->skip_fan |= BIT(3); + if (reg & BIT(5)) + sio_data->skip_pwm |= BIT(3); + + /* Check for pwm3, fan3, pwm5, fan5 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO3_REG); + if (reg & BIT(6)) + sio_data->skip_pwm |= BIT(2); + if (reg & BIT(7)) + sio_data->skip_fan |= BIT(2); + if (reg & BIT(3)) + sio_data->skip_pwm |= BIT(4); + if (reg & BIT(1)) + sio_data->skip_fan |= BIT(4); + + /* Check for pwm2, fan2 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO5_REG); + if (reg & BIT(1)) + sio_data->skip_pwm |= BIT(1); + if (reg & BIT(2)) + sio_data->skip_fan |= BIT(1); + + /* Check for AVCC */ + reg = superio_inb(sioaddr, IT87_SIO_PINX2_REG); + if (!(reg & BIT(0))) + sio_data->skip_in |= BIT(9); + + sio_data->beep_pin = superio_inb(sioaddr, + IT87_SIO_BEEP_PIN_REG) & 0x3f; + } else { + int reg; + bool uart6; + + superio_select(sioaddr, GPIO); + + /* Check for fan4, fan5 */ + if (has_five_fans(config)) { + reg = superio_inb(sioaddr, IT87_SIO_GPIO2_REG); + switch (sio_data->type) { + case it8718: + if (reg & BIT(5)) + sio_data->skip_fan |= BIT(3); + if (reg & BIT(4)) + sio_data->skip_fan |= BIT(4); + break; + case it8720: + case it8721: + case it8728: + if (!(reg & BIT(5))) + sio_data->skip_fan |= BIT(3); + if (!(reg & BIT(4))) + sio_data->skip_fan |= BIT(4); + break; + default: + break; + } + } + + reg = superio_inb(sioaddr, IT87_SIO_GPIO3_REG); + if (!sio_data->skip_vid) { + /* We need at least 4 VID pins */ + if (reg & 0x0f) { + pr_info("VID is disabled (pins used for GPIO)\n"); + sio_data->skip_vid = 1; + } + } + + /* Check if fan3 is there or not */ + if (reg & BIT(6)) + sio_data->skip_pwm |= BIT(2); + if (reg & BIT(7)) + sio_data->skip_fan |= BIT(2); + + /* Check if fan2 is there or not */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO5_REG); + if (reg & BIT(1)) + sio_data->skip_pwm |= BIT(1); + if (reg & BIT(2)) + sio_data->skip_fan |= BIT(1); + + if ((sio_data->type == it8718 || sio_data->type == it8720) && + !(sio_data->skip_vid)) + sio_data->vid_value = superio_inb(sioaddr, + IT87_SIO_VID_REG); + + reg = superio_inb(sioaddr, IT87_SIO_PINX2_REG); + + uart6 = sio_data->type == it8782 && (reg & BIT(2)); + + /* + * The IT8720F has no VIN7 pin, so VCCH5V should always be + * routed internally to VIN7 with an internal divider. + * Curiously, there still is a configuration bit to control + * this, which means it can be set incorrectly. And even + * more curiously, many boards out there are improperly + * configured, even though the IT8720F datasheet claims + * that the internal routing of VCCH5V to VIN7 is the default + * setting. So we force the internal routing in this case. + * + * On IT8782F, VIN7 is multiplexed with one of the UART6 pins. + * If UART6 is enabled, re-route VIN7 to the internal divider + * if that is not already the case. + */ + if ((sio_data->type == it8720 || uart6) && !(reg & BIT(1))) { + reg |= BIT(1); + superio_outb(sioaddr, IT87_SIO_PINX2_REG, reg); + sio_data->need_in7_reroute = true; + pr_notice("Routing internal VCCH5V to in7\n"); + } + if (reg & BIT(0)) + sio_data->internal |= BIT(0); + if (reg & BIT(1)) + sio_data->internal |= BIT(1); + + /* + * On IT8782F, UART6 pins overlap with VIN5, VIN6, and VIN7. + * While VIN7 can be routed to the internal voltage divider, + * VIN5 and VIN6 are not available if UART6 is enabled. + * + * Also, temp3 is not available if UART6 is enabled and TEMPIN3 + * is the temperature source. Since we can not read the + * temperature source here, skip_temp is preliminary. + */ + if (uart6) { + sio_data->skip_in |= BIT(5) | BIT(6); + sio_data->skip_temp |= BIT(2); + } + + sio_data->beep_pin = superio_inb(sioaddr, + IT87_SIO_BEEP_PIN_REG) & 0x3f; + } + if (sio_data->beep_pin) + pr_info("Beeping is supported\n"); + + /* Disable specific features based on DMI strings */ + board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + board_name = dmi_get_system_info(DMI_BOARD_NAME); + if (board_vendor && board_name) { + if (strcmp(board_vendor, "nVIDIA") == 0 && + strcmp(board_name, "FN68PT") == 0) { + /* + * On the Shuttle SN68PT, FAN_CTL2 is apparently not + * connected to a fan, but to something else. One user + * has reported instant system power-off when changing + * the PWM2 duty cycle, so we disable it. + * I use the board name string as the trigger in case + * the same board is ever used in other systems. + */ + pr_info("Disabling pwm2 due to hardware constraints\n"); + sio_data->skip_pwm = BIT(1); + } + } + +exit: + superio_exit(sioaddr); + return err; +} + +/* + * Some chips seem to have default value 0xff for all limit + * registers. For low voltage limits it makes no sense and triggers + * alarms, so change to 0 instead. For high temperature limits, it + * means -1 degree C, which surprisingly doesn't trigger an alarm, + * but is still confusing, so change to 127 degrees C. + */ +static void it87_check_limit_regs(struct it87_data *data) +{ + int i, reg; + + for (i = 0; i < NUM_VIN_LIMIT; i++) { + reg = it87_read_value(data, IT87_REG_VIN_MIN(i)); + if (reg == 0xff) + it87_write_value(data, IT87_REG_VIN_MIN(i), 0); + } + for (i = 0; i < NUM_TEMP_LIMIT; i++) { + reg = it87_read_value(data, IT87_REG_TEMP_HIGH(i)); + if (reg == 0xff) + it87_write_value(data, IT87_REG_TEMP_HIGH(i), 127); + } +} + +/* Check if voltage monitors are reset manually or by some reason */ +static void it87_check_voltage_monitors_reset(struct it87_data *data) +{ + int reg; + + reg = it87_read_value(data, IT87_REG_VIN_ENABLE); + if ((reg & 0xff) == 0) { + /* Enable all voltage monitors */ + it87_write_value(data, IT87_REG_VIN_ENABLE, 0xff); + } +} + +/* Check if tachometers are reset manually or by some reason */ +static void it87_check_tachometers_reset(struct platform_device *pdev) +{ + struct it87_sio_data *sio_data = dev_get_platdata(&pdev->dev); + struct it87_data *data = platform_get_drvdata(pdev); + u8 mask, fan_main_ctrl; + + mask = 0x70 & ~(sio_data->skip_fan << 4); + fan_main_ctrl = it87_read_value(data, IT87_REG_FAN_MAIN_CTRL); + if ((fan_main_ctrl & mask) == 0) { + /* Enable all fan tachometers */ + fan_main_ctrl |= mask; + it87_write_value(data, IT87_REG_FAN_MAIN_CTRL, + fan_main_ctrl); + } +} + +/* Set tachometers to 16-bit mode if needed */ +static void it87_check_tachometers_16bit_mode(struct platform_device *pdev) +{ + struct it87_data *data = platform_get_drvdata(pdev); + int reg; + + if (!has_fan16_config(data)) + return; + + reg = it87_read_value(data, IT87_REG_FAN_16BIT); + if (~reg & 0x07 & data->has_fan) { + dev_dbg(&pdev->dev, + "Setting fan1-3 to 16-bit mode\n"); + it87_write_value(data, IT87_REG_FAN_16BIT, + reg | 0x07); + } +} + +static void it87_start_monitoring(struct it87_data *data) +{ + it87_write_value(data, IT87_REG_CONFIG, + (it87_read_value(data, IT87_REG_CONFIG) & 0x3e) + | (update_vbat ? 0x41 : 0x01)); +} + +/* Called when we have found a new IT87. */ +static void it87_init_device(struct platform_device *pdev) +{ + struct it87_sio_data *sio_data = dev_get_platdata(&pdev->dev); + struct it87_data *data = platform_get_drvdata(pdev); + int tmp, i; + + /* + * For each PWM channel: + * - If it is in automatic mode, setting to manual mode should set + * the fan to full speed by default. + * - If it is in manual mode, we need a mapping to temperature + * channels to use when later setting to automatic mode later. + * Use a 1:1 mapping by default (we are clueless.) + * In both cases, the value can (and should) be changed by the user + * prior to switching to a different mode. + * Note that this is no longer needed for the IT8721F and later, as + * these have separate registers for the temperature mapping and the + * manual duty cycle. + */ + for (i = 0; i < NUM_AUTO_PWM; i++) { + data->pwm_temp_map[i] = i; + data->pwm_duty[i] = 0x7f; /* Full speed */ + data->auto_pwm[i][3] = 0x7f; /* Full speed, hard-coded */ + } + + it87_check_limit_regs(data); + + /* + * Temperature channels are not forcibly enabled, as they can be + * set to two different sensor types and we can't guess which one + * is correct for a given system. These channels can be enabled at + * run-time through the temp{1-3}_type sysfs accessors if needed. + */ + + it87_check_voltage_monitors_reset(data); + + it87_check_tachometers_reset(pdev); + + data->fan_main_ctrl = it87_read_value(data, IT87_REG_FAN_MAIN_CTRL); + data->has_fan = (data->fan_main_ctrl >> 4) & 0x07; + + it87_check_tachometers_16bit_mode(pdev); + + /* Check for additional fans */ + if (has_five_fans(data)) { + tmp = it87_read_value(data, IT87_REG_FAN_16BIT); + + if (tmp & BIT(4)) + data->has_fan |= BIT(3); /* fan4 enabled */ + if (tmp & BIT(5)) + data->has_fan |= BIT(4); /* fan5 enabled */ + if (has_six_fans(data) && (tmp & BIT(2))) + data->has_fan |= BIT(5); /* fan6 enabled */ + } + + /* Fan input pins may be used for alternative functions */ + data->has_fan &= ~sio_data->skip_fan; + + /* Check if pwm5, pwm6 are enabled */ + if (has_six_pwm(data)) { + /* The following code may be IT8620E specific */ + tmp = it87_read_value(data, IT87_REG_FAN_DIV); + if ((tmp & 0xc0) == 0xc0) + sio_data->skip_pwm |= BIT(4); + if (!(tmp & BIT(3))) + sio_data->skip_pwm |= BIT(5); + } + + it87_start_monitoring(data); +} + +/* Return 1 if and only if the PWM interface is safe to use */ +static int it87_check_pwm(struct device *dev) +{ + struct it87_data *data = dev_get_drvdata(dev); + /* + * Some BIOSes fail to correctly configure the IT87 fans. All fans off + * and polarity set to active low is sign that this is the case so we + * disable pwm control to protect the user. + */ + int tmp = it87_read_value(data, IT87_REG_FAN_CTL); + + if ((tmp & 0x87) == 0) { + if (fix_pwm_polarity) { + /* + * The user asks us to attempt a chip reconfiguration. + * This means switching to active high polarity and + * inverting all fan speed values. + */ + int i; + u8 pwm[3]; + + for (i = 0; i < ARRAY_SIZE(pwm); i++) + pwm[i] = it87_read_value(data, + IT87_REG_PWM[i]); + + /* + * If any fan is in automatic pwm mode, the polarity + * might be correct, as suspicious as it seems, so we + * better don't change anything (but still disable the + * PWM interface). + */ + if (!((pwm[0] | pwm[1] | pwm[2]) & 0x80)) { + dev_info(dev, + "Reconfiguring PWM to active high polarity\n"); + it87_write_value(data, IT87_REG_FAN_CTL, + tmp | 0x87); + for (i = 0; i < 3; i++) + it87_write_value(data, + IT87_REG_PWM[i], + 0x7f & ~pwm[i]); + return 1; + } + + dev_info(dev, + "PWM configuration is too broken to be fixed\n"); + } + + return 0; + } else if (fix_pwm_polarity) { + dev_info(dev, + "PWM configuration looks sane, won't touch\n"); + } + + return 1; +} + +static int it87_probe(struct platform_device *pdev) +{ + struct it87_data *data; + struct resource *res; + struct device *dev = &pdev->dev; + struct it87_sio_data *sio_data = dev_get_platdata(dev); + int enable_pwm_interface; + struct device *hwmon_dev; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(&pdev->dev, res->start, IT87_EC_EXTENT, + DRVNAME)) { + dev_err(dev, "Failed to request region 0x%lx-0x%lx\n", + (unsigned long)res->start, + (unsigned long)(res->start + IT87_EC_EXTENT - 1)); + return -EBUSY; + } + + data = devm_kzalloc(&pdev->dev, sizeof(struct it87_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = res->start; + data->sioaddr = sio_data->sioaddr; + data->type = sio_data->type; + data->features = it87_devices[sio_data->type].features; + data->peci_mask = it87_devices[sio_data->type].peci_mask; + data->old_peci_mask = it87_devices[sio_data->type].old_peci_mask; + /* + * IT8705F Datasheet 0.4.1, 3h == Version G. + * IT8712F Datasheet 0.9.1, section 8.3.5 indicates 8h == Version J. + * These are the first revisions with 16-bit tachometer support. + */ + switch (data->type) { + case it87: + if (sio_data->revision >= 0x03) { + data->features &= ~FEAT_OLD_AUTOPWM; + data->features |= FEAT_FAN16_CONFIG | FEAT_16BIT_FANS; + } + break; + case it8712: + if (sio_data->revision >= 0x08) { + data->features &= ~FEAT_OLD_AUTOPWM; + data->features |= FEAT_FAN16_CONFIG | FEAT_16BIT_FANS | + FEAT_FIVE_FANS; + } + break; + default: + break; + } + + /* Now, we do the remaining detection. */ + if ((it87_read_value(data, IT87_REG_CONFIG) & 0x80) || + it87_read_value(data, IT87_REG_CHIPID) != 0x90) + return -ENODEV; + + platform_set_drvdata(pdev, data); + + mutex_init(&data->update_lock); + + /* Check PWM configuration */ + enable_pwm_interface = it87_check_pwm(dev); + if (!enable_pwm_interface) + dev_info(dev, + "Detected broken BIOS defaults, disabling PWM interface\n"); + + /* Starting with IT8721F, we handle scaling of internal voltages */ + if (has_12mv_adc(data)) { + if (sio_data->internal & BIT(0)) + data->in_scaled |= BIT(3); /* in3 is AVCC */ + if (sio_data->internal & BIT(1)) + data->in_scaled |= BIT(7); /* in7 is VSB */ + if (sio_data->internal & BIT(2)) + data->in_scaled |= BIT(8); /* in8 is Vbat */ + if (sio_data->internal & BIT(3)) + data->in_scaled |= BIT(9); /* in9 is AVCC */ + } else if (sio_data->type == it8781 || sio_data->type == it8782 || + sio_data->type == it8783) { + if (sio_data->internal & BIT(0)) + data->in_scaled |= BIT(3); /* in3 is VCC5V */ + if (sio_data->internal & BIT(1)) + data->in_scaled |= BIT(7); /* in7 is VCCH5V */ + } + + data->has_temp = 0x07; + if (sio_data->skip_temp & BIT(2)) { + if (sio_data->type == it8782 && + !(it87_read_value(data, IT87_REG_TEMP_EXTRA) & 0x80)) + data->has_temp &= ~BIT(2); + } + + data->in_internal = sio_data->internal; + data->need_in7_reroute = sio_data->need_in7_reroute; + data->has_in = 0x3ff & ~sio_data->skip_in; + + if (has_six_temp(data)) { + u8 reg = it87_read_value(data, IT87_REG_TEMP456_ENABLE); + + /* Check for additional temperature sensors */ + if ((reg & 0x03) >= 0x02) + data->has_temp |= BIT(3); + if (((reg >> 2) & 0x03) >= 0x02) + data->has_temp |= BIT(4); + if (((reg >> 4) & 0x03) >= 0x02) + data->has_temp |= BIT(5); + + /* Check for additional voltage sensors */ + if ((reg & 0x03) == 0x01) + data->has_in |= BIT(10); + if (((reg >> 2) & 0x03) == 0x01) + data->has_in |= BIT(11); + if (((reg >> 4) & 0x03) == 0x01) + data->has_in |= BIT(12); + } + + data->has_beep = !!sio_data->beep_pin; + + /* Initialize the IT87 chip */ + it87_init_device(pdev); + + if (!sio_data->skip_vid) { + data->has_vid = true; + data->vrm = vid_which_vrm(); + /* VID reading from Super-I/O config space if available */ + data->vid = sio_data->vid_value; + } + + /* Prepare for sysfs hooks */ + data->groups[0] = &it87_group; + data->groups[1] = &it87_group_in; + data->groups[2] = &it87_group_temp; + data->groups[3] = &it87_group_fan; + + if (enable_pwm_interface) { + data->has_pwm = BIT(ARRAY_SIZE(IT87_REG_PWM)) - 1; + data->has_pwm &= ~sio_data->skip_pwm; + + data->groups[4] = &it87_group_pwm; + if (has_old_autopwm(data) || has_newer_autopwm(data)) + data->groups[5] = &it87_group_auto_pwm; + } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + it87_devices[sio_data->type].name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static void __maybe_unused it87_resume_sio(struct platform_device *pdev) +{ + struct it87_data *data = dev_get_drvdata(&pdev->dev); + int err; + int reg2c; + + if (!data->need_in7_reroute) + return; + + err = superio_enter(data->sioaddr); + if (err) { + dev_warn(&pdev->dev, + "Unable to enter Super I/O to reroute in7 (%d)", + err); + return; + } + + superio_select(data->sioaddr, GPIO); + + reg2c = superio_inb(data->sioaddr, IT87_SIO_PINX2_REG); + if (!(reg2c & BIT(1))) { + dev_dbg(&pdev->dev, + "Routing internal VCCH5V to in7 again"); + + reg2c |= BIT(1); + superio_outb(data->sioaddr, IT87_SIO_PINX2_REG, + reg2c); + } + + superio_exit(data->sioaddr); +} + +static int __maybe_unused it87_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct it87_data *data = dev_get_drvdata(dev); + + it87_resume_sio(pdev); + + mutex_lock(&data->update_lock); + + it87_check_pwm(dev); + it87_check_limit_regs(data); + it87_check_voltage_monitors_reset(data); + it87_check_tachometers_reset(pdev); + it87_check_tachometers_16bit_mode(pdev); + + it87_start_monitoring(data); + + /* force update */ + data->valid = 0; + + mutex_unlock(&data->update_lock); + + it87_update_device(dev); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(it87_dev_pm_ops, NULL, it87_resume); + +static struct platform_driver it87_driver = { + .driver = { + .name = DRVNAME, + .pm = &it87_dev_pm_ops, + }, + .probe = it87_probe, +}; + +static int __init it87_device_add(int index, unsigned short address, + const struct it87_sio_data *sio_data) +{ + struct platform_device *pdev; + struct resource res = { + .start = address + IT87_EC_OFFSET, + .end = address + IT87_EC_OFFSET + IT87_EC_EXTENT - 1, + .name = DRVNAME, + .flags = IORESOURCE_IO, + }; + int err; + + err = acpi_check_resource_conflict(&res); + if (err) + return err; + + pdev = platform_device_alloc(DRVNAME, address); + if (!pdev) + return -ENOMEM; + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add_data(pdev, sio_data, + sizeof(struct it87_sio_data)); + if (err) { + pr_err("Platform data allocation failed\n"); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + it87_pdev[index] = pdev; + return 0; + +exit_device_put: + platform_device_put(pdev); + return err; +} + +static int __init sm_it87_init(void) +{ + int sioaddr[2] = { REG_2E, REG_4E }; + struct it87_sio_data sio_data; + unsigned short isa_address[2]; + bool found = false; + int i, err; + + err = platform_driver_register(&it87_driver); + if (err) + return err; + + for (i = 0; i < ARRAY_SIZE(sioaddr); i++) { + memset(&sio_data, 0, sizeof(struct it87_sio_data)); + isa_address[i] = 0; + err = it87_find(sioaddr[i], &isa_address[i], &sio_data); + if (err || isa_address[i] == 0) + continue; + /* + * Don't register second chip if its ISA address matches + * the first chip's ISA address. + */ + if (i && isa_address[i] == isa_address[0]) + break; + + err = it87_device_add(i, isa_address[i], &sio_data); + if (err) + goto exit_dev_unregister; + + found = true; + + /* + * IT8705F may respond on both SIO addresses. + * Stop probing after finding one. + */ + if (sio_data.type == it87) + break; + } + + if (!found) { + err = -ENODEV; + goto exit_unregister; + } + return 0; + +exit_dev_unregister: + /* NULL check handled by platform_device_unregister */ + platform_device_unregister(it87_pdev[0]); +exit_unregister: + platform_driver_unregister(&it87_driver); + return err; +} + +static void __exit sm_it87_exit(void) +{ + /* NULL check handled by platform_device_unregister */ + platform_device_unregister(it87_pdev[1]); + platform_device_unregister(it87_pdev[0]); + platform_driver_unregister(&it87_driver); +} + +MODULE_AUTHOR("Chris Gauthron, Jean Delvare "); +MODULE_DESCRIPTION("IT8705F/IT871xF/IT872xF hardware monitoring driver"); +module_param(update_vbat, bool, 0); +MODULE_PARM_DESC(update_vbat, "Update vbat if set else return powerup value"); +module_param(fix_pwm_polarity, bool, 0); +MODULE_PARM_DESC(fix_pwm_polarity, + "Force PWM polarity to active high (DANGEROUS)"); +MODULE_LICENSE("GPL"); + +module_init(sm_it87_init); +module_exit(sm_it87_exit); diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c new file mode 100644 index 000000000..b6e5aaa54 --- /dev/null +++ b/drivers/hwmon/jc42.c @@ -0,0 +1,616 @@ +/* + * jc42.c - driver for Jedec JC42.4 compliant temperature sensors + * + * Copyright (c) 2010 Ericsson AB. + * + * Derived from lm77.c by Andras BALI . + * + * JC42.4 compliant temperature sensors are typically used on memory modules. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, I2C_CLIENT_END }; + +/* JC42 registers. All registers are 16 bit. */ +#define JC42_REG_CAP 0x00 +#define JC42_REG_CONFIG 0x01 +#define JC42_REG_TEMP_UPPER 0x02 +#define JC42_REG_TEMP_LOWER 0x03 +#define JC42_REG_TEMP_CRITICAL 0x04 +#define JC42_REG_TEMP 0x05 +#define JC42_REG_MANID 0x06 +#define JC42_REG_DEVICEID 0x07 +#define JC42_REG_SMBUS 0x22 /* NXP and Atmel, possibly others? */ + +/* Status bits in temperature register */ +#define JC42_ALARM_CRIT_BIT 15 +#define JC42_ALARM_MAX_BIT 14 +#define JC42_ALARM_MIN_BIT 13 + +/* Configuration register defines */ +#define JC42_CFG_CRIT_ONLY (1 << 2) +#define JC42_CFG_TCRIT_LOCK (1 << 6) +#define JC42_CFG_EVENT_LOCK (1 << 7) +#define JC42_CFG_SHUTDOWN (1 << 8) +#define JC42_CFG_HYST_SHIFT 9 +#define JC42_CFG_HYST_MASK (0x03 << 9) + +/* Capabilities */ +#define JC42_CAP_RANGE (1 << 2) + +/* Manufacturer IDs */ +#define ADT_MANID 0x11d4 /* Analog Devices */ +#define ATMEL_MANID 0x001f /* Atmel */ +#define ATMEL_MANID2 0x1114 /* Atmel */ +#define MAX_MANID 0x004d /* Maxim */ +#define IDT_MANID 0x00b3 /* IDT */ +#define MCP_MANID 0x0054 /* Microchip */ +#define NXP_MANID 0x1131 /* NXP Semiconductors */ +#define ONS_MANID 0x1b09 /* ON Semiconductor */ +#define STM_MANID 0x104a /* ST Microelectronics */ +#define GT_MANID 0x1c68 /* Giantec */ +#define GT_MANID2 0x132d /* Giantec, 2nd mfg ID */ + +/* SMBUS register */ +#define SMBUS_STMOUT BIT(7) /* SMBus time-out, active low */ + +/* Supported chips */ + +/* Analog Devices */ +#define ADT7408_DEVID 0x0801 +#define ADT7408_DEVID_MASK 0xffff + +/* Atmel */ +#define AT30TS00_DEVID 0x8201 +#define AT30TS00_DEVID_MASK 0xffff + +#define AT30TSE004_DEVID 0x2200 +#define AT30TSE004_DEVID_MASK 0xffff + +/* Giantec */ +#define GT30TS00_DEVID 0x2200 +#define GT30TS00_DEVID_MASK 0xff00 + +#define GT34TS02_DEVID 0x3300 +#define GT34TS02_DEVID_MASK 0xff00 + +/* IDT */ +#define TSE2004_DEVID 0x2200 +#define TSE2004_DEVID_MASK 0xff00 + +#define TS3000_DEVID 0x2900 /* Also matches TSE2002 */ +#define TS3000_DEVID_MASK 0xff00 + +#define TS3001_DEVID 0x3000 +#define TS3001_DEVID_MASK 0xff00 + +/* Maxim */ +#define MAX6604_DEVID 0x3e00 +#define MAX6604_DEVID_MASK 0xffff + +/* Microchip */ +#define MCP9804_DEVID 0x0200 +#define MCP9804_DEVID_MASK 0xfffc + +#define MCP9808_DEVID 0x0400 +#define MCP9808_DEVID_MASK 0xfffc + +#define MCP98242_DEVID 0x2000 +#define MCP98242_DEVID_MASK 0xfffc + +#define MCP98243_DEVID 0x2100 +#define MCP98243_DEVID_MASK 0xfffc + +#define MCP98244_DEVID 0x2200 +#define MCP98244_DEVID_MASK 0xfffc + +#define MCP9843_DEVID 0x0000 /* Also matches mcp9805 */ +#define MCP9843_DEVID_MASK 0xfffe + +/* NXP */ +#define SE97_DEVID 0xa200 +#define SE97_DEVID_MASK 0xfffc + +#define SE98_DEVID 0xa100 +#define SE98_DEVID_MASK 0xfffc + +/* ON Semiconductor */ +#define CAT6095_DEVID 0x0800 /* Also matches CAT34TS02 */ +#define CAT6095_DEVID_MASK 0xffe0 + +#define CAT34TS02C_DEVID 0x0a00 +#define CAT34TS02C_DEVID_MASK 0xfff0 + +#define CAT34TS04_DEVID 0x2200 +#define CAT34TS04_DEVID_MASK 0xfff0 + +/* ST Microelectronics */ +#define STTS424_DEVID 0x0101 +#define STTS424_DEVID_MASK 0xffff + +#define STTS424E_DEVID 0x0000 +#define STTS424E_DEVID_MASK 0xfffe + +#define STTS2002_DEVID 0x0300 +#define STTS2002_DEVID_MASK 0xffff + +#define STTS2004_DEVID 0x2201 +#define STTS2004_DEVID_MASK 0xffff + +#define STTS3000_DEVID 0x0200 +#define STTS3000_DEVID_MASK 0xffff + +static u16 jc42_hysteresis[] = { 0, 1500, 3000, 6000 }; + +struct jc42_chips { + u16 manid; + u16 devid; + u16 devid_mask; +}; + +static struct jc42_chips jc42_chips[] = { + { ADT_MANID, ADT7408_DEVID, ADT7408_DEVID_MASK }, + { ATMEL_MANID, AT30TS00_DEVID, AT30TS00_DEVID_MASK }, + { ATMEL_MANID2, AT30TSE004_DEVID, AT30TSE004_DEVID_MASK }, + { GT_MANID, GT30TS00_DEVID, GT30TS00_DEVID_MASK }, + { GT_MANID2, GT34TS02_DEVID, GT34TS02_DEVID_MASK }, + { IDT_MANID, TSE2004_DEVID, TSE2004_DEVID_MASK }, + { IDT_MANID, TS3000_DEVID, TS3000_DEVID_MASK }, + { IDT_MANID, TS3001_DEVID, TS3001_DEVID_MASK }, + { MAX_MANID, MAX6604_DEVID, MAX6604_DEVID_MASK }, + { MCP_MANID, MCP9804_DEVID, MCP9804_DEVID_MASK }, + { MCP_MANID, MCP9808_DEVID, MCP9808_DEVID_MASK }, + { MCP_MANID, MCP98242_DEVID, MCP98242_DEVID_MASK }, + { MCP_MANID, MCP98243_DEVID, MCP98243_DEVID_MASK }, + { MCP_MANID, MCP98244_DEVID, MCP98244_DEVID_MASK }, + { MCP_MANID, MCP9843_DEVID, MCP9843_DEVID_MASK }, + { NXP_MANID, SE97_DEVID, SE97_DEVID_MASK }, + { ONS_MANID, CAT6095_DEVID, CAT6095_DEVID_MASK }, + { ONS_MANID, CAT34TS02C_DEVID, CAT34TS02C_DEVID_MASK }, + { ONS_MANID, CAT34TS04_DEVID, CAT34TS04_DEVID_MASK }, + { NXP_MANID, SE98_DEVID, SE98_DEVID_MASK }, + { STM_MANID, STTS424_DEVID, STTS424_DEVID_MASK }, + { STM_MANID, STTS424E_DEVID, STTS424E_DEVID_MASK }, + { STM_MANID, STTS2002_DEVID, STTS2002_DEVID_MASK }, + { STM_MANID, STTS2004_DEVID, STTS2004_DEVID_MASK }, + { STM_MANID, STTS3000_DEVID, STTS3000_DEVID_MASK }, +}; + +enum temp_index { + t_input = 0, + t_crit, + t_min, + t_max, + t_num_temp +}; + +static const u8 temp_regs[t_num_temp] = { + [t_input] = JC42_REG_TEMP, + [t_crit] = JC42_REG_TEMP_CRITICAL, + [t_min] = JC42_REG_TEMP_LOWER, + [t_max] = JC42_REG_TEMP_UPPER, +}; + +/* Each client has this additional data */ +struct jc42_data { + struct i2c_client *client; + struct mutex update_lock; /* protect register access */ + bool extended; /* true if extended range supported */ + bool valid; + unsigned long last_updated; /* In jiffies */ + u16 orig_config; /* original configuration */ + u16 config; /* current configuration */ + u16 temp[t_num_temp];/* Temperatures */ +}; + +#define JC42_TEMP_MIN_EXTENDED (-40000) +#define JC42_TEMP_MIN 0 +#define JC42_TEMP_MAX 125000 + +static u16 jc42_temp_to_reg(long temp, bool extended) +{ + int ntemp = clamp_val(temp, + extended ? JC42_TEMP_MIN_EXTENDED : + JC42_TEMP_MIN, JC42_TEMP_MAX); + + /* convert from 0.001 to 0.0625 resolution */ + return (ntemp * 2 / 125) & 0x1fff; +} + +static int jc42_temp_from_reg(s16 reg) +{ + reg = sign_extend32(reg, 12); + + /* convert from 0.0625 to 0.001 resolution */ + return reg * 125 / 2; +} + +static struct jc42_data *jc42_update_device(struct device *dev) +{ + struct jc42_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + struct jc42_data *ret = data; + int i, val; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + for (i = 0; i < t_num_temp; i++) { + val = i2c_smbus_read_word_swapped(client, temp_regs[i]); + if (val < 0) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i] = val; + } + data->last_updated = jiffies; + data->valid = true; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +static int jc42_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct jc42_data *data = jc42_update_device(dev); + int temp, hyst; + + if (IS_ERR(data)) + return PTR_ERR(data); + + switch (attr) { + case hwmon_temp_input: + *val = jc42_temp_from_reg(data->temp[t_input]); + return 0; + case hwmon_temp_min: + *val = jc42_temp_from_reg(data->temp[t_min]); + return 0; + case hwmon_temp_max: + *val = jc42_temp_from_reg(data->temp[t_max]); + return 0; + case hwmon_temp_crit: + *val = jc42_temp_from_reg(data->temp[t_crit]); + return 0; + case hwmon_temp_max_hyst: + temp = jc42_temp_from_reg(data->temp[t_max]); + hyst = jc42_hysteresis[(data->config & JC42_CFG_HYST_MASK) + >> JC42_CFG_HYST_SHIFT]; + *val = temp - hyst; + return 0; + case hwmon_temp_crit_hyst: + temp = jc42_temp_from_reg(data->temp[t_crit]); + hyst = jc42_hysteresis[(data->config & JC42_CFG_HYST_MASK) + >> JC42_CFG_HYST_SHIFT]; + *val = temp - hyst; + return 0; + case hwmon_temp_min_alarm: + *val = (data->temp[t_input] >> JC42_ALARM_MIN_BIT) & 1; + return 0; + case hwmon_temp_max_alarm: + *val = (data->temp[t_input] >> JC42_ALARM_MAX_BIT) & 1; + return 0; + case hwmon_temp_crit_alarm: + *val = (data->temp[t_input] >> JC42_ALARM_CRIT_BIT) & 1; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int jc42_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct jc42_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int diff, hyst; + int ret; + + mutex_lock(&data->update_lock); + + switch (attr) { + case hwmon_temp_min: + data->temp[t_min] = jc42_temp_to_reg(val, data->extended); + ret = i2c_smbus_write_word_swapped(client, temp_regs[t_min], + data->temp[t_min]); + break; + case hwmon_temp_max: + data->temp[t_max] = jc42_temp_to_reg(val, data->extended); + ret = i2c_smbus_write_word_swapped(client, temp_regs[t_max], + data->temp[t_max]); + break; + case hwmon_temp_crit: + data->temp[t_crit] = jc42_temp_to_reg(val, data->extended); + ret = i2c_smbus_write_word_swapped(client, temp_regs[t_crit], + data->temp[t_crit]); + break; + case hwmon_temp_crit_hyst: + /* + * JC42.4 compliant chips only support four hysteresis values. + * Pick best choice and go from there. + */ + val = clamp_val(val, (data->extended ? JC42_TEMP_MIN_EXTENDED + : JC42_TEMP_MIN) - 6000, + JC42_TEMP_MAX); + diff = jc42_temp_from_reg(data->temp[t_crit]) - val; + hyst = 0; + if (diff > 0) { + if (diff < 2250) + hyst = 1; /* 1.5 degrees C */ + else if (diff < 4500) + hyst = 2; /* 3.0 degrees C */ + else + hyst = 3; /* 6.0 degrees C */ + } + data->config = (data->config & ~JC42_CFG_HYST_MASK) | + (hyst << JC42_CFG_HYST_SHIFT); + ret = i2c_smbus_write_word_swapped(data->client, + JC42_REG_CONFIG, + data->config); + break; + default: + ret = -EOPNOTSUPP; + break; + } + + mutex_unlock(&data->update_lock); + + return ret; +} + +static umode_t jc42_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct jc42_data *data = _data; + unsigned int config = data->config; + umode_t mode = S_IRUGO; + + switch (attr) { + case hwmon_temp_min: + case hwmon_temp_max: + if (!(config & JC42_CFG_EVENT_LOCK)) + mode |= S_IWUSR; + break; + case hwmon_temp_crit: + if (!(config & JC42_CFG_TCRIT_LOCK)) + mode |= S_IWUSR; + break; + case hwmon_temp_crit_hyst: + if (!(config & (JC42_CFG_EVENT_LOCK | JC42_CFG_TCRIT_LOCK))) + mode |= S_IWUSR; + break; + case hwmon_temp_input: + case hwmon_temp_max_hyst: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + break; + default: + mode = 0; + break; + } + return mode; +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int jc42_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int i, config, cap, manid, devid; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + cap = i2c_smbus_read_word_swapped(client, JC42_REG_CAP); + config = i2c_smbus_read_word_swapped(client, JC42_REG_CONFIG); + manid = i2c_smbus_read_word_swapped(client, JC42_REG_MANID); + devid = i2c_smbus_read_word_swapped(client, JC42_REG_DEVICEID); + + if (cap < 0 || config < 0 || manid < 0 || devid < 0) + return -ENODEV; + + if ((cap & 0xff00) || (config & 0xf800)) + return -ENODEV; + + for (i = 0; i < ARRAY_SIZE(jc42_chips); i++) { + struct jc42_chips *chip = &jc42_chips[i]; + if (manid == chip->manid && + (devid & chip->devid_mask) == chip->devid) { + strlcpy(info->type, "jc42", I2C_NAME_SIZE); + return 0; + } + } + return -ENODEV; +} + +static const u32 jc42_temp_config[] = { + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_HYST | HWMON_T_CRIT_HYST | + HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM, + 0 +}; + +static const struct hwmon_channel_info jc42_temp = { + .type = hwmon_temp, + .config = jc42_temp_config, +}; + +static const struct hwmon_channel_info *jc42_info[] = { + &jc42_temp, + NULL +}; + +static const struct hwmon_ops jc42_hwmon_ops = { + .is_visible = jc42_is_visible, + .read = jc42_read, + .write = jc42_write, +}; + +static const struct hwmon_chip_info jc42_chip_info = { + .ops = &jc42_hwmon_ops, + .info = jc42_info, +}; + +static int jc42_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct jc42_data *data; + int config, cap; + + data = devm_kzalloc(dev, sizeof(struct jc42_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + cap = i2c_smbus_read_word_swapped(client, JC42_REG_CAP); + if (cap < 0) + return cap; + + data->extended = !!(cap & JC42_CAP_RANGE); + + if (device_property_read_bool(dev, "smbus-timeout-disable")) { + int smbus; + + /* + * Not all chips support this register, but from a + * quick read of various datasheets no chip appears + * incompatible with the below attempt to disable + * the timeout. And the whole thing is opt-in... + */ + smbus = i2c_smbus_read_word_swapped(client, JC42_REG_SMBUS); + if (smbus < 0) + return smbus; + i2c_smbus_write_word_swapped(client, JC42_REG_SMBUS, + smbus | SMBUS_STMOUT); + } + + config = i2c_smbus_read_word_swapped(client, JC42_REG_CONFIG); + if (config < 0) + return config; + + data->orig_config = config; + if (config & JC42_CFG_SHUTDOWN) { + config &= ~JC42_CFG_SHUTDOWN; + i2c_smbus_write_word_swapped(client, JC42_REG_CONFIG, config); + } + data->config = config; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, "jc42", + data, &jc42_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static int jc42_remove(struct i2c_client *client) +{ + struct jc42_data *data = i2c_get_clientdata(client); + + /* Restore original configuration except hysteresis */ + if ((data->config & ~JC42_CFG_HYST_MASK) != + (data->orig_config & ~JC42_CFG_HYST_MASK)) { + int config; + + config = (data->orig_config & ~JC42_CFG_HYST_MASK) + | (data->config & JC42_CFG_HYST_MASK); + i2c_smbus_write_word_swapped(client, JC42_REG_CONFIG, config); + } + return 0; +} + +#ifdef CONFIG_PM + +static int jc42_suspend(struct device *dev) +{ + struct jc42_data *data = dev_get_drvdata(dev); + + data->config |= JC42_CFG_SHUTDOWN; + i2c_smbus_write_word_swapped(data->client, JC42_REG_CONFIG, + data->config); + return 0; +} + +static int jc42_resume(struct device *dev) +{ + struct jc42_data *data = dev_get_drvdata(dev); + + data->config &= ~JC42_CFG_SHUTDOWN; + i2c_smbus_write_word_swapped(data->client, JC42_REG_CONFIG, + data->config); + return 0; +} + +static const struct dev_pm_ops jc42_dev_pm_ops = { + .suspend = jc42_suspend, + .resume = jc42_resume, +}; + +#define JC42_DEV_PM_OPS (&jc42_dev_pm_ops) +#else +#define JC42_DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + +static const struct i2c_device_id jc42_id[] = { + { "jc42", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, jc42_id); + +#ifdef CONFIG_OF +static const struct of_device_id jc42_of_ids[] = { + { .compatible = "jedec,jc-42.4-temp", }, + { } +}; +MODULE_DEVICE_TABLE(of, jc42_of_ids); +#endif + +static struct i2c_driver jc42_driver = { + .class = I2C_CLASS_SPD | I2C_CLASS_HWMON, + .driver = { + .name = "jc42", + .pm = JC42_DEV_PM_OPS, + .of_match_table = of_match_ptr(jc42_of_ids), + }, + .probe = jc42_probe, + .remove = jc42_remove, + .id_table = jc42_id, + .detect = jc42_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(jc42_driver); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("JC42 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/jz4740-hwmon.c b/drivers/hwmon/jz4740-hwmon.c new file mode 100644 index 000000000..2d40a2e77 --- /dev/null +++ b/drivers/hwmon/jz4740-hwmon.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2009-2010, Lars-Peter Clausen + * JZ4740 SoC HWMON driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +struct jz4740_hwmon { + void __iomem *base; + int irq; + const struct mfd_cell *cell; + struct platform_device *pdev; + struct completion read_completion; + struct mutex lock; +}; + +static irqreturn_t jz4740_hwmon_irq(int irq, void *data) +{ + struct jz4740_hwmon *hwmon = data; + + complete(&hwmon->read_completion); + return IRQ_HANDLED; +} + +static ssize_t in0_input_show(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct jz4740_hwmon *hwmon = dev_get_drvdata(dev); + struct platform_device *pdev = hwmon->pdev; + struct completion *completion = &hwmon->read_completion; + long t; + unsigned long val; + int ret; + + mutex_lock(&hwmon->lock); + + reinit_completion(completion); + + enable_irq(hwmon->irq); + hwmon->cell->enable(pdev); + + t = wait_for_completion_interruptible_timeout(completion, HZ); + + if (t > 0) { + val = readw(hwmon->base) & 0xfff; + val = (val * 3300) >> 12; + ret = sprintf(buf, "%lu\n", val); + } else { + ret = t ? t : -ETIMEDOUT; + } + + hwmon->cell->disable(pdev); + disable_irq(hwmon->irq); + + mutex_unlock(&hwmon->lock); + + return ret; +} + +static DEVICE_ATTR_RO(in0_input); + +static struct attribute *jz4740_attrs[] = { + &dev_attr_in0_input.attr, + NULL +}; + +ATTRIBUTE_GROUPS(jz4740); + +static int jz4740_hwmon_probe(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + struct jz4740_hwmon *hwmon; + struct device *hwmon_dev; + struct resource *mem; + + hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + + hwmon->cell = mfd_get_cell(pdev); + + hwmon->irq = platform_get_irq(pdev, 0); + if (hwmon->irq < 0) { + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", + hwmon->irq); + return hwmon->irq; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hwmon->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(hwmon->base)) + return PTR_ERR(hwmon->base); + + hwmon->pdev = pdev; + init_completion(&hwmon->read_completion); + mutex_init(&hwmon->lock); + + ret = devm_request_irq(dev, hwmon->irq, jz4740_hwmon_irq, 0, + pdev->name, hwmon); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); + return ret; + } + disable_irq(hwmon->irq); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "jz4740", hwmon, + jz4740_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct platform_driver jz4740_hwmon_driver = { + .probe = jz4740_hwmon_probe, + .driver = { + .name = "jz4740-hwmon", + }, +}; + +module_platform_driver(jz4740_hwmon_driver); + +MODULE_DESCRIPTION("JZ4740 SoC HWMON driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:jz4740-hwmon"); diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c new file mode 100644 index 000000000..bc6871c8d --- /dev/null +++ b/drivers/hwmon/k10temp.c @@ -0,0 +1,373 @@ +/* + * k10temp.c - AMD Family 10h/11h/12h/14h/15h/16h processor hardware monitoring + * + * Copyright (c) 2009 Clemens Ladisch + * + * + * This driver is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License; either + * version 2 of the License, or (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("AMD Family 10h+ CPU core temperature monitor"); +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_LICENSE("GPL"); + +static bool force; +module_param(force, bool, 0444); +MODULE_PARM_DESC(force, "force loading on processors with erratum 319"); + +/* Provide lock for writing to NB_SMU_IND_ADDR */ +static DEFINE_MUTEX(nb_smu_ind_mutex); + +#ifndef PCI_DEVICE_ID_AMD_15H_M70H_NB_F3 +#define PCI_DEVICE_ID_AMD_15H_M70H_NB_F3 0x15b3 +#endif + +/* CPUID function 0x80000001, ebx */ +#define CPUID_PKGTYPE_MASK 0xf0000000 +#define CPUID_PKGTYPE_F 0x00000000 +#define CPUID_PKGTYPE_AM2R2_AM3 0x10000000 + +/* DRAM controller (PCI function 2) */ +#define REG_DCT0_CONFIG_HIGH 0x094 +#define DDR3_MODE 0x00000100 + +/* miscellaneous (PCI function 3) */ +#define REG_HARDWARE_THERMAL_CONTROL 0x64 +#define HTC_ENABLE 0x00000001 + +#define REG_REPORTED_TEMPERATURE 0xa4 + +#define REG_NORTHBRIDGE_CAPABILITIES 0xe8 +#define NB_CAP_HTC 0x00000400 + +/* + * For F15h M60h and M70h, REG_HARDWARE_THERMAL_CONTROL + * and REG_REPORTED_TEMPERATURE have been moved to + * D0F0xBC_xD820_0C64 [Hardware Temperature Control] + * D0F0xBC_xD820_0CA4 [Reported Temperature Control] + */ +#define F15H_M60H_HARDWARE_TEMP_CTRL_OFFSET 0xd8200c64 +#define F15H_M60H_REPORTED_TEMP_CTRL_OFFSET 0xd8200ca4 + +/* F17h M01h Access througn SMN */ +#define F17H_M01H_REPORTED_TEMP_CTRL_OFFSET 0x00059800 + +struct k10temp_data { + struct pci_dev *pdev; + void (*read_htcreg)(struct pci_dev *pdev, u32 *regval); + void (*read_tempreg)(struct pci_dev *pdev, u32 *regval); + int temp_offset; + u32 temp_adjust_mask; + bool show_tdie; +}; + +struct tctl_offset { + u8 model; + char const *id; + int offset; +}; + +static const struct tctl_offset tctl_offset_table[] = { + { 0x17, "AMD Ryzen 5 1600X", 20000 }, + { 0x17, "AMD Ryzen 7 1700X", 20000 }, + { 0x17, "AMD Ryzen 7 1800X", 20000 }, + { 0x17, "AMD Ryzen 7 2700X", 10000 }, + { 0x17, "AMD Ryzen Threadripper 19", 27000 }, /* 19{00,20,50}X */ + { 0x17, "AMD Ryzen Threadripper 29", 27000 }, /* 29{20,50,70,90}[W]X */ +}; + +static void read_htcreg_pci(struct pci_dev *pdev, u32 *regval) +{ + pci_read_config_dword(pdev, REG_HARDWARE_THERMAL_CONTROL, regval); +} + +static void read_tempreg_pci(struct pci_dev *pdev, u32 *regval) +{ + pci_read_config_dword(pdev, REG_REPORTED_TEMPERATURE, regval); +} + +static void amd_nb_index_read(struct pci_dev *pdev, unsigned int devfn, + unsigned int base, int offset, u32 *val) +{ + mutex_lock(&nb_smu_ind_mutex); + pci_bus_write_config_dword(pdev->bus, devfn, + base, offset); + pci_bus_read_config_dword(pdev->bus, devfn, + base + 4, val); + mutex_unlock(&nb_smu_ind_mutex); +} + +static void read_htcreg_nb_f15(struct pci_dev *pdev, u32 *regval) +{ + amd_nb_index_read(pdev, PCI_DEVFN(0, 0), 0xb8, + F15H_M60H_HARDWARE_TEMP_CTRL_OFFSET, regval); +} + +static void read_tempreg_nb_f15(struct pci_dev *pdev, u32 *regval) +{ + amd_nb_index_read(pdev, PCI_DEVFN(0, 0), 0xb8, + F15H_M60H_REPORTED_TEMP_CTRL_OFFSET, regval); +} + +static void read_tempreg_nb_f17(struct pci_dev *pdev, u32 *regval) +{ + amd_smn_read(amd_pci_dev_to_node_id(pdev), + F17H_M01H_REPORTED_TEMP_CTRL_OFFSET, regval); +} + +static unsigned int get_raw_temp(struct k10temp_data *data) +{ + unsigned int temp; + u32 regval; + + data->read_tempreg(data->pdev, ®val); + temp = (regval >> 21) * 125; + if (regval & data->temp_adjust_mask) + temp -= 49000; + return temp; +} + +static ssize_t temp1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct k10temp_data *data = dev_get_drvdata(dev); + unsigned int temp = get_raw_temp(data); + + if (temp > data->temp_offset) + temp -= data->temp_offset; + else + temp = 0; + + return sprintf(buf, "%u\n", temp); +} + +static ssize_t temp2_input_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct k10temp_data *data = dev_get_drvdata(dev); + unsigned int temp = get_raw_temp(data); + + return sprintf(buf, "%u\n", temp); +} + +static ssize_t temp_label_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%s\n", attr->index ? "Tctl" : "Tdie"); +} + +static ssize_t temp1_max_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", 70 * 1000); +} + +static ssize_t show_temp_crit(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct k10temp_data *data = dev_get_drvdata(dev); + int show_hyst = attr->index; + u32 regval; + int value; + + data->read_htcreg(data->pdev, ®val); + value = ((regval >> 16) & 0x7f) * 500 + 52000; + if (show_hyst) + value -= ((regval >> 24) & 0xf) * 500; + return sprintf(buf, "%d\n", value); +} + +static DEVICE_ATTR_RO(temp1_input); +static DEVICE_ATTR_RO(temp1_max); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp_crit, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IRUGO, show_temp_crit, NULL, 1); + +static SENSOR_DEVICE_ATTR(temp1_label, 0444, temp_label_show, NULL, 0); +static DEVICE_ATTR_RO(temp2_input); +static SENSOR_DEVICE_ATTR(temp2_label, 0444, temp_label_show, NULL, 1); + +static umode_t k10temp_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct k10temp_data *data = dev_get_drvdata(dev); + struct pci_dev *pdev = data->pdev; + u32 reg; + + switch (index) { + case 0 ... 1: /* temp1_input, temp1_max */ + default: + break; + case 2 ... 3: /* temp1_crit, temp1_crit_hyst */ + if (!data->read_htcreg) + return 0; + + pci_read_config_dword(pdev, REG_NORTHBRIDGE_CAPABILITIES, + ®); + if (!(reg & NB_CAP_HTC)) + return 0; + + data->read_htcreg(data->pdev, ®); + if (!(reg & HTC_ENABLE)) + return 0; + break; + case 4 ... 6: /* temp1_label, temp2_input, temp2_label */ + if (!data->show_tdie) + return 0; + break; + } + return attr->mode; +} + +static struct attribute *k10temp_attrs[] = { + &dev_attr_temp1_input.attr, + &dev_attr_temp1_max.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + &dev_attr_temp2_input.attr, + &sensor_dev_attr_temp2_label.dev_attr.attr, + NULL +}; + +static const struct attribute_group k10temp_group = { + .attrs = k10temp_attrs, + .is_visible = k10temp_is_visible, +}; +__ATTRIBUTE_GROUPS(k10temp); + +static bool has_erratum_319(struct pci_dev *pdev) +{ + u32 pkg_type, reg_dram_cfg; + + if (boot_cpu_data.x86 != 0x10) + return false; + + /* + * Erratum 319: The thermal sensor of Socket F/AM2+ processors + * may be unreliable. + */ + pkg_type = cpuid_ebx(0x80000001) & CPUID_PKGTYPE_MASK; + if (pkg_type == CPUID_PKGTYPE_F) + return true; + if (pkg_type != CPUID_PKGTYPE_AM2R2_AM3) + return false; + + /* DDR3 memory implies socket AM3, which is good */ + pci_bus_read_config_dword(pdev->bus, + PCI_DEVFN(PCI_SLOT(pdev->devfn), 2), + REG_DCT0_CONFIG_HIGH, ®_dram_cfg); + if (reg_dram_cfg & DDR3_MODE) + return false; + + /* + * Unfortunately it is possible to run a socket AM3 CPU with DDR2 + * memory. We blacklist all the cores which do exist in socket AM2+ + * format. It still isn't perfect, as RB-C2 cores exist in both AM2+ + * and AM3 formats, but that's the best we can do. + */ + return boot_cpu_data.x86_model < 4 || + (boot_cpu_data.x86_model == 4 && boot_cpu_data.x86_stepping <= 2); +} + +static int k10temp_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int unreliable = has_erratum_319(pdev); + struct device *dev = &pdev->dev; + struct k10temp_data *data; + struct device *hwmon_dev; + int i; + + if (unreliable) { + if (!force) { + dev_err(dev, + "unreliable CPU thermal sensor; monitoring disabled\n"); + return -ENODEV; + } + dev_warn(dev, + "unreliable CPU thermal sensor; check erratum 319\n"); + } + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->pdev = pdev; + + if (boot_cpu_data.x86 == 0x15 && + ((boot_cpu_data.x86_model & 0xf0) == 0x60 || + (boot_cpu_data.x86_model & 0xf0) == 0x70)) { + data->read_htcreg = read_htcreg_nb_f15; + data->read_tempreg = read_tempreg_nb_f15; + } else if (boot_cpu_data.x86 == 0x17) { + data->temp_adjust_mask = 0x80000; + data->read_tempreg = read_tempreg_nb_f17; + data->show_tdie = true; + } else { + data->read_htcreg = read_htcreg_pci; + data->read_tempreg = read_tempreg_pci; + } + + for (i = 0; i < ARRAY_SIZE(tctl_offset_table); i++) { + const struct tctl_offset *entry = &tctl_offset_table[i]; + + if (boot_cpu_data.x86 == entry->model && + strstr(boot_cpu_data.x86_model_id, entry->id)) { + data->temp_offset = entry->offset; + break; + } + } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "k10temp", data, + k10temp_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct pci_device_id k10temp_id_table[] = { + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_10H_NB_MISC) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_11H_NB_MISC) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_CNB17H_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_NB_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_M10H_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_M30H_NB_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_M60H_NB_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_M70H_NB_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_16H_NB_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_16H_M30H_NB_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_DF_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M10H_DF_F3) }, + {} +}; +MODULE_DEVICE_TABLE(pci, k10temp_id_table); + +static struct pci_driver k10temp_driver = { + .name = "k10temp", + .id_table = k10temp_id_table, + .probe = k10temp_probe, +}; + +module_pci_driver(k10temp_driver); diff --git a/drivers/hwmon/k8temp.c b/drivers/hwmon/k8temp.c new file mode 100644 index 000000000..e59f9113f --- /dev/null +++ b/drivers/hwmon/k8temp.c @@ -0,0 +1,334 @@ +/* + * k8temp.c - Linux kernel module for hardware monitoring + * + * Copyright (C) 2006 Rudolf Marek + * + * Inspired from the w83785 and amd756 drivers. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEMP_FROM_REG(val) (((((val) >> 16) & 0xff) - 49) * 1000) +#define REG_TEMP 0xe4 +#define SEL_PLACE 0x40 +#define SEL_CORE 0x04 + +struct k8temp_data { + struct device *hwmon_dev; + struct mutex update_lock; + const char *name; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* registers values */ + u8 sensorsp; /* sensor presence bits - SEL_CORE, SEL_PLACE */ + u32 temp[2][2]; /* core, place */ + u8 swap_core_select; /* meaning of SEL_CORE is inverted */ + u32 temp_offset; +}; + +static struct k8temp_data *k8temp_update_device(struct device *dev) +{ + struct k8temp_data *data = dev_get_drvdata(dev); + struct pci_dev *pdev = to_pci_dev(dev); + u8 tmp; + + mutex_lock(&data->update_lock); + + if (!data->valid + || time_after(jiffies, data->last_updated + HZ)) { + pci_read_config_byte(pdev, REG_TEMP, &tmp); + tmp &= ~(SEL_PLACE | SEL_CORE); /* Select sensor 0, core0 */ + pci_write_config_byte(pdev, REG_TEMP, tmp); + pci_read_config_dword(pdev, REG_TEMP, &data->temp[0][0]); + + if (data->sensorsp & SEL_PLACE) { + tmp |= SEL_PLACE; /* Select sensor 1, core0 */ + pci_write_config_byte(pdev, REG_TEMP, tmp); + pci_read_config_dword(pdev, REG_TEMP, + &data->temp[0][1]); + } + + if (data->sensorsp & SEL_CORE) { + tmp &= ~SEL_PLACE; /* Select sensor 0, core1 */ + tmp |= SEL_CORE; + pci_write_config_byte(pdev, REG_TEMP, tmp); + pci_read_config_dword(pdev, REG_TEMP, + &data->temp[1][0]); + + if (data->sensorsp & SEL_PLACE) { + tmp |= SEL_PLACE; /* Select sensor 1, core1 */ + pci_write_config_byte(pdev, REG_TEMP, tmp); + pci_read_config_dword(pdev, REG_TEMP, + &data->temp[1][1]); + } + } + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + return data; +} + +/* + * Sysfs stuff + */ + +static ssize_t name_show(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct k8temp_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} + + +static ssize_t show_temp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute_2 *attr = + to_sensor_dev_attr_2(devattr); + int core = attr->nr; + int place = attr->index; + int temp; + struct k8temp_data *data = k8temp_update_device(dev); + + if (data->swap_core_select && (data->sensorsp & SEL_CORE)) + core = core ? 0 : 1; + + temp = TEMP_FROM_REG(data->temp[core][place]) + data->temp_offset; + + return sprintf(buf, "%d\n", temp); +} + +/* core, place */ + +static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0); +static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 1); +static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 1, 0); +static SENSOR_DEVICE_ATTR_2(temp4_input, S_IRUGO, show_temp, NULL, 1, 1); +static DEVICE_ATTR_RO(name); + +static const struct pci_device_id k8temp_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_K8_NB_MISC) }, + { 0 }, +}; + +MODULE_DEVICE_TABLE(pci, k8temp_ids); + +static int is_rev_g_desktop(u8 model) +{ + u32 brandidx; + + if (model < 0x69) + return 0; + + if (model == 0xc1 || model == 0x6c || model == 0x7c) + return 0; + + /* + * Differentiate between AM2 and ASB1. + * See "Constructing the processor Name String" in "Revision + * Guide for AMD NPT Family 0Fh Processors" (33610). + */ + brandidx = cpuid_ebx(0x80000001); + brandidx = (brandidx >> 9) & 0x1f; + + /* Single core */ + if ((model == 0x6f || model == 0x7f) && + (brandidx == 0x7 || brandidx == 0x9 || brandidx == 0xc)) + return 0; + + /* Dual core */ + if (model == 0x6b && + (brandidx == 0xb || brandidx == 0xc)) + return 0; + + return 1; +} + +static int k8temp_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int err; + u8 scfg; + u32 temp; + u8 model, stepping; + struct k8temp_data *data; + + data = devm_kzalloc(&pdev->dev, sizeof(struct k8temp_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + model = boot_cpu_data.x86_model; + stepping = boot_cpu_data.x86_stepping; + + /* feature available since SH-C0, exclude older revisions */ + if ((model == 4 && stepping == 0) || + (model == 5 && stepping <= 1)) + return -ENODEV; + + /* + * AMD NPT family 0fh, i.e. RevF and RevG: + * meaning of SEL_CORE bit is inverted + */ + if (model >= 0x40) { + data->swap_core_select = 1; + dev_warn(&pdev->dev, + "Temperature readouts might be wrong - check erratum #141\n"); + } + + /* + * RevG desktop CPUs (i.e. no socket S1G1 or ASB1 parts) need + * additional offset, otherwise reported temperature is below + * ambient temperature + */ + if (is_rev_g_desktop(model)) + data->temp_offset = 21000; + + pci_read_config_byte(pdev, REG_TEMP, &scfg); + scfg &= ~(SEL_PLACE | SEL_CORE); /* Select sensor 0, core0 */ + pci_write_config_byte(pdev, REG_TEMP, scfg); + pci_read_config_byte(pdev, REG_TEMP, &scfg); + + if (scfg & (SEL_PLACE | SEL_CORE)) { + dev_err(&pdev->dev, "Configuration bit(s) stuck at 1!\n"); + return -ENODEV; + } + + scfg |= (SEL_PLACE | SEL_CORE); + pci_write_config_byte(pdev, REG_TEMP, scfg); + + /* now we know if we can change core and/or sensor */ + pci_read_config_byte(pdev, REG_TEMP, &data->sensorsp); + + if (data->sensorsp & SEL_PLACE) { + scfg &= ~SEL_CORE; /* Select sensor 1, core0 */ + pci_write_config_byte(pdev, REG_TEMP, scfg); + pci_read_config_dword(pdev, REG_TEMP, &temp); + scfg |= SEL_CORE; /* prepare for next selection */ + if (!((temp >> 16) & 0xff)) /* if temp is 0 -49C is unlikely */ + data->sensorsp &= ~SEL_PLACE; + } + + if (data->sensorsp & SEL_CORE) { + scfg &= ~SEL_PLACE; /* Select sensor 0, core1 */ + pci_write_config_byte(pdev, REG_TEMP, scfg); + pci_read_config_dword(pdev, REG_TEMP, &temp); + if (!((temp >> 16) & 0xff)) /* if temp is 0 -49C is unlikely */ + data->sensorsp &= ~SEL_CORE; + } + + data->name = "k8temp"; + mutex_init(&data->update_lock); + pci_set_drvdata(pdev, data); + + /* Register sysfs hooks */ + err = device_create_file(&pdev->dev, + &sensor_dev_attr_temp1_input.dev_attr); + if (err) + goto exit_remove; + + /* sensor can be changed and reports something */ + if (data->sensorsp & SEL_PLACE) { + err = device_create_file(&pdev->dev, + &sensor_dev_attr_temp2_input.dev_attr); + if (err) + goto exit_remove; + } + + /* core can be changed and reports something */ + if (data->sensorsp & SEL_CORE) { + err = device_create_file(&pdev->dev, + &sensor_dev_attr_temp3_input.dev_attr); + if (err) + goto exit_remove; + if (data->sensorsp & SEL_PLACE) { + err = device_create_file(&pdev->dev, + &sensor_dev_attr_temp4_input. + dev_attr); + if (err) + goto exit_remove; + } + } + + err = device_create_file(&pdev->dev, &dev_attr_name); + if (err) + goto exit_remove; + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + device_remove_file(&pdev->dev, + &sensor_dev_attr_temp1_input.dev_attr); + device_remove_file(&pdev->dev, + &sensor_dev_attr_temp2_input.dev_attr); + device_remove_file(&pdev->dev, + &sensor_dev_attr_temp3_input.dev_attr); + device_remove_file(&pdev->dev, + &sensor_dev_attr_temp4_input.dev_attr); + device_remove_file(&pdev->dev, &dev_attr_name); + return err; +} + +static void k8temp_remove(struct pci_dev *pdev) +{ + struct k8temp_data *data = pci_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + device_remove_file(&pdev->dev, + &sensor_dev_attr_temp1_input.dev_attr); + device_remove_file(&pdev->dev, + &sensor_dev_attr_temp2_input.dev_attr); + device_remove_file(&pdev->dev, + &sensor_dev_attr_temp3_input.dev_attr); + device_remove_file(&pdev->dev, + &sensor_dev_attr_temp4_input.dev_attr); + device_remove_file(&pdev->dev, &dev_attr_name); +} + +static struct pci_driver k8temp_driver = { + .name = "k8temp", + .id_table = k8temp_ids, + .probe = k8temp_probe, + .remove = k8temp_remove, +}; + +module_pci_driver(k8temp_driver); + +MODULE_AUTHOR("Rudolf Marek "); +MODULE_DESCRIPTION("AMD K8 core temperature monitor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lineage-pem.c b/drivers/hwmon/lineage-pem.c new file mode 100644 index 000000000..84d791bdb --- /dev/null +++ b/drivers/hwmon/lineage-pem.c @@ -0,0 +1,547 @@ +/* + * Driver for Lineage Compact Power Line series of power entry modules. + * + * Copyright (C) 2010, 2011 Ericsson AB. + * + * Documentation: + * http://www.lineagepower.com/oem/pdf/CPLI2C.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * This driver supports various Lineage Compact Power Line DC/DC and AC/DC + * converters such as CP1800, CP2000AC, CP2000DC, CP2100DC, and others. + * + * The devices are nominally PMBus compliant. However, most standard PMBus + * commands are not supported. Specifically, all hardware monitoring and + * status reporting commands are non-standard. For this reason, a standard + * PMBus driver can not be used. + * + * All Lineage CPL devices have a built-in I2C bus master selector (PCA9541). + * To ensure device access, this driver should only be used as client driver + * to the pca9541 I2C master selector driver. + */ + +/* Command codes */ +#define PEM_OPERATION 0x01 +#define PEM_CLEAR_INFO_FLAGS 0x03 +#define PEM_VOUT_COMMAND 0x21 +#define PEM_VOUT_OV_FAULT_LIMIT 0x40 +#define PEM_READ_DATA_STRING 0xd0 +#define PEM_READ_INPUT_STRING 0xdc +#define PEM_READ_FIRMWARE_REV 0xdd +#define PEM_READ_RUN_TIMER 0xde +#define PEM_FAN_HI_SPEED 0xdf +#define PEM_FAN_NORMAL_SPEED 0xe0 +#define PEM_READ_FAN_SPEED 0xe1 + +/* offsets in data string */ +#define PEM_DATA_STATUS_2 0 +#define PEM_DATA_STATUS_1 1 +#define PEM_DATA_ALARM_2 2 +#define PEM_DATA_ALARM_1 3 +#define PEM_DATA_VOUT_LSB 4 +#define PEM_DATA_VOUT_MSB 5 +#define PEM_DATA_CURRENT 6 +#define PEM_DATA_TEMP 7 + +/* Virtual entries, to report constants */ +#define PEM_DATA_TEMP_MAX 10 +#define PEM_DATA_TEMP_CRIT 11 + +/* offsets in input string */ +#define PEM_INPUT_VOLTAGE 0 +#define PEM_INPUT_POWER_LSB 1 +#define PEM_INPUT_POWER_MSB 2 + +/* offsets in fan data */ +#define PEM_FAN_ADJUSTMENT 0 +#define PEM_FAN_FAN1 1 +#define PEM_FAN_FAN2 2 +#define PEM_FAN_FAN3 3 + +/* Status register bits */ +#define STS1_OUTPUT_ON (1 << 0) +#define STS1_LEDS_FLASHING (1 << 1) +#define STS1_EXT_FAULT (1 << 2) +#define STS1_SERVICE_LED_ON (1 << 3) +#define STS1_SHUTDOWN_OCCURRED (1 << 4) +#define STS1_INT_FAULT (1 << 5) +#define STS1_ISOLATION_TEST_OK (1 << 6) + +#define STS2_ENABLE_PIN_HI (1 << 0) +#define STS2_DATA_OUT_RANGE (1 << 1) +#define STS2_RESTARTED_OK (1 << 1) +#define STS2_ISOLATION_TEST_FAIL (1 << 3) +#define STS2_HIGH_POWER_CAP (1 << 4) +#define STS2_INVALID_INSTR (1 << 5) +#define STS2_WILL_RESTART (1 << 6) +#define STS2_PEC_ERR (1 << 7) + +/* Alarm register bits */ +#define ALRM1_VIN_OUT_LIMIT (1 << 0) +#define ALRM1_VOUT_OUT_LIMIT (1 << 1) +#define ALRM1_OV_VOLT_SHUTDOWN (1 << 2) +#define ALRM1_VIN_OVERCURRENT (1 << 3) +#define ALRM1_TEMP_WARNING (1 << 4) +#define ALRM1_TEMP_SHUTDOWN (1 << 5) +#define ALRM1_PRIMARY_FAULT (1 << 6) +#define ALRM1_POWER_LIMIT (1 << 7) + +#define ALRM2_5V_OUT_LIMIT (1 << 1) +#define ALRM2_TEMP_FAULT (1 << 2) +#define ALRM2_OV_LOW (1 << 3) +#define ALRM2_DCDC_TEMP_HIGH (1 << 4) +#define ALRM2_PRI_TEMP_HIGH (1 << 5) +#define ALRM2_NO_PRIMARY (1 << 6) +#define ALRM2_FAN_FAULT (1 << 7) + +#define FIRMWARE_REV_LEN 4 +#define DATA_STRING_LEN 9 +#define INPUT_STRING_LEN 5 /* 4 for most devices */ +#define FAN_SPEED_LEN 5 + +struct pem_data { + struct i2c_client *client; + const struct attribute_group *groups[4]; + + struct mutex update_lock; + bool valid; + bool fans_supported; + int input_length; + unsigned long last_updated; /* in jiffies */ + + u8 firmware_rev[FIRMWARE_REV_LEN]; + u8 data_string[DATA_STRING_LEN]; + u8 input_string[INPUT_STRING_LEN]; + u8 fan_speed[FAN_SPEED_LEN]; +}; + +static int pem_read_block(struct i2c_client *client, u8 command, u8 *data, + int data_len) +{ + u8 block_buffer[I2C_SMBUS_BLOCK_MAX]; + int result; + + result = i2c_smbus_read_block_data(client, command, block_buffer); + if (unlikely(result < 0)) + goto abort; + if (unlikely(result == 0xff || result != data_len)) { + result = -EIO; + goto abort; + } + memcpy(data, block_buffer, data_len); + result = 0; +abort: + return result; +} + +static struct pem_data *pem_update_device(struct device *dev) +{ + struct pem_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + struct pem_data *ret = data; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + int result; + + /* Read data string */ + result = pem_read_block(client, PEM_READ_DATA_STRING, + data->data_string, + sizeof(data->data_string)); + if (unlikely(result < 0)) { + ret = ERR_PTR(result); + goto abort; + } + + /* Read input string */ + if (data->input_length) { + result = pem_read_block(client, PEM_READ_INPUT_STRING, + data->input_string, + data->input_length); + if (unlikely(result < 0)) { + ret = ERR_PTR(result); + goto abort; + } + } + + /* Read fan speeds */ + if (data->fans_supported) { + result = pem_read_block(client, PEM_READ_FAN_SPEED, + data->fan_speed, + sizeof(data->fan_speed)); + if (unlikely(result < 0)) { + ret = ERR_PTR(result); + goto abort; + } + } + + i2c_smbus_write_byte(client, PEM_CLEAR_INFO_FLAGS); + + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +static long pem_get_data(u8 *data, int len, int index) +{ + long val; + + switch (index) { + case PEM_DATA_VOUT_LSB: + val = (data[index] + (data[index+1] << 8)) * 5 / 2; + break; + case PEM_DATA_CURRENT: + val = data[index] * 200; + break; + case PEM_DATA_TEMP: + val = data[index] * 1000; + break; + case PEM_DATA_TEMP_MAX: + val = 97 * 1000; /* 97 degrees C per datasheet */ + break; + case PEM_DATA_TEMP_CRIT: + val = 107 * 1000; /* 107 degrees C per datasheet */ + break; + default: + WARN_ON_ONCE(1); + val = 0; + } + return val; +} + +static long pem_get_input(u8 *data, int len, int index) +{ + long val; + + switch (index) { + case PEM_INPUT_VOLTAGE: + if (len == INPUT_STRING_LEN) + val = (data[index] + (data[index+1] << 8) - 75) * 1000; + else + val = (data[index] - 75) * 1000; + break; + case PEM_INPUT_POWER_LSB: + if (len == INPUT_STRING_LEN) + index++; + val = (data[index] + (data[index+1] << 8)) * 1000000L; + break; + default: + WARN_ON_ONCE(1); + val = 0; + } + return val; +} + +static long pem_get_fan(u8 *data, int len, int index) +{ + long val; + + switch (index) { + case PEM_FAN_FAN1: + case PEM_FAN_FAN2: + case PEM_FAN_FAN3: + val = data[index] * 100; + break; + default: + WARN_ON_ONCE(1); + val = 0; + } + return val; +} + +/* + * Show boolean, either a fault or an alarm. + * .nr points to the register, .index is the bit mask to check + */ +static ssize_t pem_show_bool(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(da); + struct pem_data *data = pem_update_device(dev); + u8 status; + + if (IS_ERR(data)) + return PTR_ERR(data); + + status = data->data_string[attr->nr] & attr->index; + return snprintf(buf, PAGE_SIZE, "%d\n", !!status); +} + +static ssize_t pem_show_data(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pem_data *data = pem_update_device(dev); + long value; + + if (IS_ERR(data)) + return PTR_ERR(data); + + value = pem_get_data(data->data_string, sizeof(data->data_string), + attr->index); + + return snprintf(buf, PAGE_SIZE, "%ld\n", value); +} + +static ssize_t pem_show_input(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pem_data *data = pem_update_device(dev); + long value; + + if (IS_ERR(data)) + return PTR_ERR(data); + + value = pem_get_input(data->input_string, sizeof(data->input_string), + attr->index); + + return snprintf(buf, PAGE_SIZE, "%ld\n", value); +} + +static ssize_t pem_show_fan(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pem_data *data = pem_update_device(dev); + long value; + + if (IS_ERR(data)) + return PTR_ERR(data); + + value = pem_get_fan(data->fan_speed, sizeof(data->fan_speed), + attr->index); + + return snprintf(buf, PAGE_SIZE, "%ld\n", value); +} + +/* Voltages */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, pem_show_data, NULL, + PEM_DATA_VOUT_LSB); +static SENSOR_DEVICE_ATTR_2(in1_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_VOUT_OUT_LIMIT); +static SENSOR_DEVICE_ATTR_2(in1_crit_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_OV_VOLT_SHUTDOWN); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, pem_show_input, NULL, + PEM_INPUT_VOLTAGE); +static SENSOR_DEVICE_ATTR_2(in2_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, + ALRM1_VIN_OUT_LIMIT | ALRM1_PRIMARY_FAULT); + +/* Currents */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, pem_show_data, NULL, + PEM_DATA_CURRENT); +static SENSOR_DEVICE_ATTR_2(curr1_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_VIN_OVERCURRENT); + +/* Power */ +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, pem_show_input, NULL, + PEM_INPUT_POWER_LSB); +static SENSOR_DEVICE_ATTR_2(power1_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_POWER_LIMIT); + +/* Fans */ +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, pem_show_fan, NULL, + PEM_FAN_FAN1); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, pem_show_fan, NULL, + PEM_FAN_FAN2); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, pem_show_fan, NULL, + PEM_FAN_FAN3); +static SENSOR_DEVICE_ATTR_2(fan1_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_2, ALRM2_FAN_FAULT); + +/* Temperatures */ +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, pem_show_data, NULL, + PEM_DATA_TEMP); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, pem_show_data, NULL, + PEM_DATA_TEMP_MAX); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, pem_show_data, NULL, + PEM_DATA_TEMP_CRIT); +static SENSOR_DEVICE_ATTR_2(temp1_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_TEMP_WARNING); +static SENSOR_DEVICE_ATTR_2(temp1_crit_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_TEMP_SHUTDOWN); +static SENSOR_DEVICE_ATTR_2(temp1_fault, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_2, ALRM2_TEMP_FAULT); + +static struct attribute *pem_attributes[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + + &sensor_dev_attr_curr1_alarm.dev_attr.attr, + + &sensor_dev_attr_power1_alarm.dev_attr.attr, + + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + + NULL, +}; + +static const struct attribute_group pem_group = { + .attrs = pem_attributes, +}; + +static struct attribute *pem_input_attributes[] = { + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_power1_input.dev_attr.attr, + NULL +}; + +static const struct attribute_group pem_input_group = { + .attrs = pem_input_attributes, +}; + +static struct attribute *pem_fan_attributes[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + NULL +}; + +static const struct attribute_group pem_fan_group = { + .attrs = pem_fan_attributes, +}; + +static int pem_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct pem_data *data; + int ret, idx = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BLOCK_DATA + | I2C_FUNC_SMBUS_WRITE_BYTE)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* + * We use the next two commands to determine if the device is really + * there. + */ + ret = pem_read_block(client, PEM_READ_FIRMWARE_REV, + data->firmware_rev, sizeof(data->firmware_rev)); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte(client, PEM_CLEAR_INFO_FLAGS); + if (ret < 0) + return ret; + + dev_info(dev, "Firmware revision %d.%d.%d\n", + data->firmware_rev[0], data->firmware_rev[1], + data->firmware_rev[2]); + + /* sysfs hooks */ + data->groups[idx++] = &pem_group; + + /* + * Check if input readings are supported. + * This is the case if we can read input data, + * and if the returned data is not all zeros. + * Note that input alarms are always supported. + */ + ret = pem_read_block(client, PEM_READ_INPUT_STRING, + data->input_string, + sizeof(data->input_string) - 1); + if (!ret && (data->input_string[0] || data->input_string[1] || + data->input_string[2])) + data->input_length = sizeof(data->input_string) - 1; + else if (ret < 0) { + /* Input string is one byte longer for some devices */ + ret = pem_read_block(client, PEM_READ_INPUT_STRING, + data->input_string, + sizeof(data->input_string)); + if (!ret && (data->input_string[0] || data->input_string[1] || + data->input_string[2] || data->input_string[3])) + data->input_length = sizeof(data->input_string); + } + + if (data->input_length) + data->groups[idx++] = &pem_input_group; + + /* + * Check if fan speed readings are supported. + * This is the case if we can read fan speed data, + * and if the returned data is not all zeros. + * Note that the fan alarm is always supported. + */ + ret = pem_read_block(client, PEM_READ_FAN_SPEED, + data->fan_speed, + sizeof(data->fan_speed)); + if (!ret && (data->fan_speed[0] || data->fan_speed[1] || + data->fan_speed[2] || data->fan_speed[3])) { + data->fans_supported = true; + data->groups[idx++] = &pem_fan_group; + } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id pem_id[] = { + {"lineage_pem", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, pem_id); + +static struct i2c_driver pem_driver = { + .driver = { + .name = "lineage_pem", + }, + .probe = pem_probe, + .id_table = pem_id, +}; + +module_i2c_driver(pem_driver); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("Lineage CPL PEM hardware monitoring driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm63.c b/drivers/hwmon/lm63.c new file mode 100644 index 000000000..4c1770920 --- /dev/null +++ b/drivers/hwmon/lm63.c @@ -0,0 +1,1190 @@ +/* + * lm63.c - driver for the National Semiconductor LM63 temperature sensor + * with integrated fan control + * Copyright (C) 2004-2008 Jean Delvare + * Based on the lm90 driver. + * + * The LM63 is a sensor chip made by National Semiconductor. It measures + * two temperatures (its own and one external one) and the speed of one + * fan, those speed it can additionally control. Complete datasheet can be + * obtained from National's website at: + * http://www.national.com/pf/LM/LM63.html + * + * The LM63 is basically an LM86 with fan speed monitoring and control + * capabilities added. It misses some of the LM86 features though: + * - No low limit for local temperature. + * - No critical limit for local temperature. + * - Critical limit for remote temperature can be changed only once. We + * will consider that the critical limit is read-only. + * + * The datasheet isn't very clear about what the tachometer reading is. + * I had a explanation from National Semiconductor though. The two lower + * bits of the read value have to be masked out. The value is still 16 bit + * in width. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Addresses to scan + * Address is fully defined internally and cannot be changed except for + * LM64 which has one pin dedicated to address selection. + * LM63 and LM96163 have address 0x4c. + * LM64 can have address 0x18 or 0x4e. + */ + +static const unsigned short normal_i2c[] = { 0x18, 0x4c, 0x4e, I2C_CLIENT_END }; + +/* + * The LM63 registers + */ + +#define LM63_REG_CONFIG1 0x03 +#define LM63_REG_CONVRATE 0x04 +#define LM63_REG_CONFIG2 0xBF +#define LM63_REG_CONFIG_FAN 0x4A + +#define LM63_REG_TACH_COUNT_MSB 0x47 +#define LM63_REG_TACH_COUNT_LSB 0x46 +#define LM63_REG_TACH_LIMIT_MSB 0x49 +#define LM63_REG_TACH_LIMIT_LSB 0x48 + +#define LM63_REG_PWM_VALUE 0x4C +#define LM63_REG_PWM_FREQ 0x4D +#define LM63_REG_LUT_TEMP_HYST 0x4F +#define LM63_REG_LUT_TEMP(nr) (0x50 + 2 * (nr)) +#define LM63_REG_LUT_PWM(nr) (0x51 + 2 * (nr)) + +#define LM63_REG_LOCAL_TEMP 0x00 +#define LM63_REG_LOCAL_HIGH 0x05 + +#define LM63_REG_REMOTE_TEMP_MSB 0x01 +#define LM63_REG_REMOTE_TEMP_LSB 0x10 +#define LM63_REG_REMOTE_OFFSET_MSB 0x11 +#define LM63_REG_REMOTE_OFFSET_LSB 0x12 +#define LM63_REG_REMOTE_HIGH_MSB 0x07 +#define LM63_REG_REMOTE_HIGH_LSB 0x13 +#define LM63_REG_REMOTE_LOW_MSB 0x08 +#define LM63_REG_REMOTE_LOW_LSB 0x14 +#define LM63_REG_REMOTE_TCRIT 0x19 +#define LM63_REG_REMOTE_TCRIT_HYST 0x21 + +#define LM63_REG_ALERT_STATUS 0x02 +#define LM63_REG_ALERT_MASK 0x16 + +#define LM63_REG_MAN_ID 0xFE +#define LM63_REG_CHIP_ID 0xFF + +#define LM96163_REG_TRUTHERM 0x30 +#define LM96163_REG_REMOTE_TEMP_U_MSB 0x31 +#define LM96163_REG_REMOTE_TEMP_U_LSB 0x32 +#define LM96163_REG_CONFIG_ENHANCED 0x45 + +#define LM63_MAX_CONVRATE 9 + +#define LM63_MAX_CONVRATE_HZ 32 +#define LM96163_MAX_CONVRATE_HZ 26 + +/* + * Conversions and various macros + * For tachometer counts, the LM63 uses 16-bit values. + * For local temperature and high limit, remote critical limit and hysteresis + * value, it uses signed 8-bit values with LSB = 1 degree Celsius. + * For remote temperature, low and high limits, it uses signed 11-bit values + * with LSB = 0.125 degree Celsius, left-justified in 16-bit registers. + * For LM64 the actual remote diode temperature is 16 degree Celsius higher + * than the register reading. Remote temperature setpoints have to be + * adapted accordingly. + */ + +#define FAN_FROM_REG(reg) ((reg) == 0xFFFC || (reg) == 0 ? 0 : \ + 5400000 / (reg)) +#define FAN_TO_REG(val) ((val) <= 82 ? 0xFFFC : \ + (5400000 / (val)) & 0xFFFC) +#define TEMP8_FROM_REG(reg) ((reg) * 1000) +#define TEMP8_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val((val), -128000, \ + 127000), 1000) +#define TEMP8U_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val((val), 0, \ + 255000), 1000) +#define TEMP11_FROM_REG(reg) ((reg) / 32 * 125) +#define TEMP11_TO_REG(val) (DIV_ROUND_CLOSEST(clamp_val((val), -128000, \ + 127875), 125) * 32) +#define TEMP11U_TO_REG(val) (DIV_ROUND_CLOSEST(clamp_val((val), 0, \ + 255875), 125) * 32) +#define HYST_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val((val), 0, 127000), \ + 1000) + +#define UPDATE_INTERVAL(max, rate) \ + ((1000 << (LM63_MAX_CONVRATE - (rate))) / (max)) + +enum chips { lm63, lm64, lm96163 }; + +/* + * Client data (each client gets its own) + */ + +struct lm63_data { + struct i2c_client *client; + struct mutex update_lock; + const struct attribute_group *groups[5]; + char valid; /* zero until following fields are valid */ + char lut_valid; /* zero until lut fields are valid */ + unsigned long last_updated; /* in jiffies */ + unsigned long lut_last_updated; /* in jiffies */ + enum chips kind; + int temp2_offset; + + int update_interval; /* in milliseconds */ + int max_convrate_hz; + int lut_size; /* 8 or 12 */ + + /* registers values */ + u8 config, config_fan; + u16 fan[2]; /* 0: input + 1: low limit */ + u8 pwm1_freq; + u8 pwm1[13]; /* 0: current output + 1-12: lookup table */ + s8 temp8[15]; /* 0: local input + 1: local high limit + 2: remote critical limit + 3-14: lookup table */ + s16 temp11[4]; /* 0: remote input + 1: remote low limit + 2: remote high limit + 3: remote offset */ + u16 temp11u; /* remote input (unsigned) */ + u8 temp2_crit_hyst; + u8 lut_temp_hyst; + u8 alarms; + bool pwm_highres; + bool lut_temp_highres; + bool remote_unsigned; /* true if unsigned remote upper limits */ + bool trutherm; +}; + +static inline int temp8_from_reg(struct lm63_data *data, int nr) +{ + if (data->remote_unsigned) + return TEMP8_FROM_REG((u8)data->temp8[nr]); + return TEMP8_FROM_REG(data->temp8[nr]); +} + +static inline int lut_temp_from_reg(struct lm63_data *data, int nr) +{ + return data->temp8[nr] * (data->lut_temp_highres ? 500 : 1000); +} + +static inline int lut_temp_to_reg(struct lm63_data *data, long val) +{ + val -= data->temp2_offset; + if (data->lut_temp_highres) + return DIV_ROUND_CLOSEST(clamp_val(val, 0, 127500), 500); + else + return DIV_ROUND_CLOSEST(clamp_val(val, 0, 127000), 1000); +} + +/* + * Update the lookup table register cache. + * client->update_lock must be held when calling this function. + */ +static void lm63_update_lut(struct lm63_data *data) +{ + struct i2c_client *client = data->client; + int i; + + if (time_after(jiffies, data->lut_last_updated + 5 * HZ) || + !data->lut_valid) { + for (i = 0; i < data->lut_size; i++) { + data->pwm1[1 + i] = i2c_smbus_read_byte_data(client, + LM63_REG_LUT_PWM(i)); + data->temp8[3 + i] = i2c_smbus_read_byte_data(client, + LM63_REG_LUT_TEMP(i)); + } + data->lut_temp_hyst = i2c_smbus_read_byte_data(client, + LM63_REG_LUT_TEMP_HYST); + + data->lut_last_updated = jiffies; + data->lut_valid = 1; + } +} + +static struct lm63_data *lm63_update_device(struct device *dev) +{ + struct lm63_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long next_update; + + mutex_lock(&data->update_lock); + + next_update = data->last_updated + + msecs_to_jiffies(data->update_interval); + if (time_after(jiffies, next_update) || !data->valid) { + if (data->config & 0x04) { /* tachometer enabled */ + /* order matters for fan1_input */ + data->fan[0] = i2c_smbus_read_byte_data(client, + LM63_REG_TACH_COUNT_LSB) & 0xFC; + data->fan[0] |= i2c_smbus_read_byte_data(client, + LM63_REG_TACH_COUNT_MSB) << 8; + data->fan[1] = (i2c_smbus_read_byte_data(client, + LM63_REG_TACH_LIMIT_LSB) & 0xFC) + | (i2c_smbus_read_byte_data(client, + LM63_REG_TACH_LIMIT_MSB) << 8); + } + + data->pwm1_freq = i2c_smbus_read_byte_data(client, + LM63_REG_PWM_FREQ); + if (data->pwm1_freq == 0) + data->pwm1_freq = 1; + data->pwm1[0] = i2c_smbus_read_byte_data(client, + LM63_REG_PWM_VALUE); + + data->temp8[0] = i2c_smbus_read_byte_data(client, + LM63_REG_LOCAL_TEMP); + data->temp8[1] = i2c_smbus_read_byte_data(client, + LM63_REG_LOCAL_HIGH); + + /* order matters for temp2_input */ + data->temp11[0] = i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_TEMP_MSB) << 8; + data->temp11[0] |= i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_TEMP_LSB); + data->temp11[1] = (i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_LOW_MSB) << 8) + | i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_LOW_LSB); + data->temp11[2] = (i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_HIGH_MSB) << 8) + | i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_HIGH_LSB); + data->temp11[3] = (i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_OFFSET_MSB) << 8) + | i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_OFFSET_LSB); + + if (data->kind == lm96163) + data->temp11u = (i2c_smbus_read_byte_data(client, + LM96163_REG_REMOTE_TEMP_U_MSB) << 8) + | i2c_smbus_read_byte_data(client, + LM96163_REG_REMOTE_TEMP_U_LSB); + + data->temp8[2] = i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_TCRIT); + data->temp2_crit_hyst = i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_TCRIT_HYST); + + data->alarms = i2c_smbus_read_byte_data(client, + LM63_REG_ALERT_STATUS) & 0x7F; + + data->last_updated = jiffies; + data->valid = 1; + } + + lm63_update_lut(data); + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Trip points in the lookup table should be in ascending order for both + * temperatures and PWM output values. + */ +static int lm63_lut_looks_bad(struct device *dev, struct lm63_data *data) +{ + int i; + + mutex_lock(&data->update_lock); + lm63_update_lut(data); + + for (i = 1; i < data->lut_size; i++) { + if (data->pwm1[1 + i - 1] > data->pwm1[1 + i] + || data->temp8[3 + i - 1] > data->temp8[3 + i]) { + dev_warn(dev, + "Lookup table doesn't look sane (check entries %d and %d)\n", + i, i + 1); + break; + } + } + mutex_unlock(&data->update_lock); + + return i == data->lut_size ? 0 : 1; +} + +/* + * Sysfs callback functions and files + */ + +static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm63_data *data = lm63_update_device(dev); + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[attr->index])); +} + +static ssize_t set_fan(struct device *dev, struct device_attribute *dummy, + const char *buf, size_t count) +{ + struct lm63_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan[1] = FAN_TO_REG(val); + i2c_smbus_write_byte_data(client, LM63_REG_TACH_LIMIT_LSB, + data->fan[1] & 0xFF); + i2c_smbus_write_byte_data(client, LM63_REG_TACH_LIMIT_MSB, + data->fan[1] >> 8); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm1(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm63_data *data = lm63_update_device(dev); + int nr = attr->index; + int pwm; + + if (data->pwm_highres) + pwm = data->pwm1[nr]; + else + pwm = data->pwm1[nr] >= 2 * data->pwm1_freq ? + 255 : (data->pwm1[nr] * 255 + data->pwm1_freq) / + (2 * data->pwm1_freq); + + return sprintf(buf, "%d\n", pwm); +} + +static ssize_t set_pwm1(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm63_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = attr->index; + unsigned long val; + int err; + u8 reg; + + if (!(data->config_fan & 0x20)) /* register is read-only */ + return -EPERM; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + reg = nr ? LM63_REG_LUT_PWM(nr - 1) : LM63_REG_PWM_VALUE; + val = clamp_val(val, 0, 255); + + mutex_lock(&data->update_lock); + data->pwm1[nr] = data->pwm_highres ? val : + (val * data->pwm1_freq * 2 + 127) / 255; + i2c_smbus_write_byte_data(client, reg, data->pwm1[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t pwm1_enable_show(struct device *dev, + struct device_attribute *dummy, char *buf) +{ + struct lm63_data *data = lm63_update_device(dev); + return sprintf(buf, "%d\n", data->config_fan & 0x20 ? 1 : 2); +} + +static ssize_t pwm1_enable_store(struct device *dev, + struct device_attribute *dummy, + const char *buf, size_t count) +{ + struct lm63_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + if (val < 1 || val > 2) + return -EINVAL; + + /* + * Only let the user switch to automatic mode if the lookup table + * looks sane. + */ + if (val == 2 && lm63_lut_looks_bad(dev, data)) + return -EPERM; + + mutex_lock(&data->update_lock); + data->config_fan = i2c_smbus_read_byte_data(client, + LM63_REG_CONFIG_FAN); + if (val == 1) + data->config_fan |= 0x20; + else + data->config_fan &= ~0x20; + i2c_smbus_write_byte_data(client, LM63_REG_CONFIG_FAN, + data->config_fan); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * There are 8bit registers for both local(temp1) and remote(temp2) sensor. + * For remote sensor registers temp2_offset has to be considered, + * for local sensor it must not. + * So we need separate 8bit accessors for local and remote sensor. + */ +static ssize_t show_local_temp8(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm63_data *data = lm63_update_device(dev); + return sprintf(buf, "%d\n", TEMP8_FROM_REG(data->temp8[attr->index])); +} + +static ssize_t show_remote_temp8(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm63_data *data = lm63_update_device(dev); + return sprintf(buf, "%d\n", temp8_from_reg(data, attr->index) + + data->temp2_offset); +} + +static ssize_t show_lut_temp(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm63_data *data = lm63_update_device(dev); + return sprintf(buf, "%d\n", lut_temp_from_reg(data, attr->index) + + data->temp2_offset); +} + +static ssize_t set_temp8(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm63_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = attr->index; + long val; + int err; + int temp; + u8 reg; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + switch (nr) { + case 2: + reg = LM63_REG_REMOTE_TCRIT; + if (data->remote_unsigned) + temp = TEMP8U_TO_REG(val - data->temp2_offset); + else + temp = TEMP8_TO_REG(val - data->temp2_offset); + break; + case 1: + reg = LM63_REG_LOCAL_HIGH; + temp = TEMP8_TO_REG(val); + break; + default: /* lookup table */ + reg = LM63_REG_LUT_TEMP(nr - 3); + temp = lut_temp_to_reg(data, val); + } + data->temp8[nr] = temp; + i2c_smbus_write_byte_data(client, reg, temp); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp11(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm63_data *data = lm63_update_device(dev); + int nr = attr->index; + int temp; + + if (!nr) { + /* + * Use unsigned temperature unless its value is zero. + * If it is zero, use signed temperature. + */ + if (data->temp11u) + temp = TEMP11_FROM_REG(data->temp11u); + else + temp = TEMP11_FROM_REG(data->temp11[nr]); + } else { + if (data->remote_unsigned && nr == 2) + temp = TEMP11_FROM_REG((u16)data->temp11[nr]); + else + temp = TEMP11_FROM_REG(data->temp11[nr]); + } + return sprintf(buf, "%d\n", temp + data->temp2_offset); +} + +static ssize_t set_temp11(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + static const u8 reg[6] = { + LM63_REG_REMOTE_LOW_MSB, + LM63_REG_REMOTE_LOW_LSB, + LM63_REG_REMOTE_HIGH_MSB, + LM63_REG_REMOTE_HIGH_LSB, + LM63_REG_REMOTE_OFFSET_MSB, + LM63_REG_REMOTE_OFFSET_LSB, + }; + + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm63_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + int nr = attr->index; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + if (data->remote_unsigned && nr == 2) + data->temp11[nr] = TEMP11U_TO_REG(val - data->temp2_offset); + else + data->temp11[nr] = TEMP11_TO_REG(val - data->temp2_offset); + + i2c_smbus_write_byte_data(client, reg[(nr - 1) * 2], + data->temp11[nr] >> 8); + i2c_smbus_write_byte_data(client, reg[(nr - 1) * 2 + 1], + data->temp11[nr] & 0xff); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Hysteresis register holds a relative value, while we want to present + * an absolute to user-space + */ +static ssize_t temp2_crit_hyst_show(struct device *dev, + struct device_attribute *dummy, char *buf) +{ + struct lm63_data *data = lm63_update_device(dev); + return sprintf(buf, "%d\n", temp8_from_reg(data, 2) + + data->temp2_offset + - TEMP8_FROM_REG(data->temp2_crit_hyst)); +} + +static ssize_t show_lut_temp_hyst(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm63_data *data = lm63_update_device(dev); + + return sprintf(buf, "%d\n", lut_temp_from_reg(data, attr->index) + + data->temp2_offset + - TEMP8_FROM_REG(data->lut_temp_hyst)); +} + +/* + * And now the other way around, user-space provides an absolute + * hysteresis value and we have to store a relative one + */ +static ssize_t temp2_crit_hyst_store(struct device *dev, + struct device_attribute *dummy, + const char *buf, size_t count) +{ + struct lm63_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + long hyst; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + hyst = temp8_from_reg(data, 2) + data->temp2_offset - val; + i2c_smbus_write_byte_data(client, LM63_REG_REMOTE_TCRIT_HYST, + HYST_TO_REG(hyst)); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Set conversion rate. + * client->update_lock must be held when calling this function. + */ +static void lm63_set_convrate(struct lm63_data *data, unsigned int interval) +{ + struct i2c_client *client = data->client; + unsigned int update_interval; + int i; + + /* Shift calculations to avoid rounding errors */ + interval <<= 6; + + /* find the nearest update rate */ + update_interval = (1 << (LM63_MAX_CONVRATE + 6)) * 1000 + / data->max_convrate_hz; + for (i = 0; i < LM63_MAX_CONVRATE; i++, update_interval >>= 1) + if (interval >= update_interval * 3 / 4) + break; + + i2c_smbus_write_byte_data(client, LM63_REG_CONVRATE, i); + data->update_interval = UPDATE_INTERVAL(data->max_convrate_hz, i); +} + +static ssize_t update_interval_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm63_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", data->update_interval); +} + +static ssize_t update_interval_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm63_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + lm63_set_convrate(data, clamp_val(val, 0, 100000)); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t temp2_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm63_data *data = dev_get_drvdata(dev); + + return sprintf(buf, data->trutherm ? "1\n" : "2\n"); +} + +static ssize_t temp2_type_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm63_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int ret; + u8 reg; + + ret = kstrtoul(buf, 10, &val); + if (ret < 0) + return ret; + if (val != 1 && val != 2) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->trutherm = val == 1; + reg = i2c_smbus_read_byte_data(client, LM96163_REG_TRUTHERM) & ~0x02; + i2c_smbus_write_byte_data(client, LM96163_REG_TRUTHERM, + reg | (data->trutherm ? 0x02 : 0x00)); + data->valid = 0; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t alarms_show(struct device *dev, struct device_attribute *dummy, + char *buf) +{ + struct lm63_data *data = lm63_update_device(dev); + return sprintf(buf, "%u\n", data->alarms); +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm63_data *data = lm63_update_device(dev); + int bitnr = attr->index; + + return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1); +} + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); +static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan, + set_fan, 1); + +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm1, set_pwm1, 0); +static DEVICE_ATTR_RW(pwm1_enable); +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 1); +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 3); +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_temp_hyst, S_IRUGO, + show_lut_temp_hyst, NULL, 3); +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 2); +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 4); +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_temp_hyst, S_IRUGO, + show_lut_temp_hyst, NULL, 4); +static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 3); +static SENSOR_DEVICE_ATTR(pwm1_auto_point3_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 5); +static SENSOR_DEVICE_ATTR(pwm1_auto_point3_temp_hyst, S_IRUGO, + show_lut_temp_hyst, NULL, 5); +static SENSOR_DEVICE_ATTR(pwm1_auto_point4_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 4); +static SENSOR_DEVICE_ATTR(pwm1_auto_point4_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 6); +static SENSOR_DEVICE_ATTR(pwm1_auto_point4_temp_hyst, S_IRUGO, + show_lut_temp_hyst, NULL, 6); +static SENSOR_DEVICE_ATTR(pwm1_auto_point5_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 5); +static SENSOR_DEVICE_ATTR(pwm1_auto_point5_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 7); +static SENSOR_DEVICE_ATTR(pwm1_auto_point5_temp_hyst, S_IRUGO, + show_lut_temp_hyst, NULL, 7); +static SENSOR_DEVICE_ATTR(pwm1_auto_point6_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 6); +static SENSOR_DEVICE_ATTR(pwm1_auto_point6_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 8); +static SENSOR_DEVICE_ATTR(pwm1_auto_point6_temp_hyst, S_IRUGO, + show_lut_temp_hyst, NULL, 8); +static SENSOR_DEVICE_ATTR(pwm1_auto_point7_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 7); +static SENSOR_DEVICE_ATTR(pwm1_auto_point7_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 9); +static SENSOR_DEVICE_ATTR(pwm1_auto_point7_temp_hyst, S_IRUGO, + show_lut_temp_hyst, NULL, 9); +static SENSOR_DEVICE_ATTR(pwm1_auto_point8_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 8); +static SENSOR_DEVICE_ATTR(pwm1_auto_point8_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 10); +static SENSOR_DEVICE_ATTR(pwm1_auto_point8_temp_hyst, S_IRUGO, + show_lut_temp_hyst, NULL, 10); +static SENSOR_DEVICE_ATTR(pwm1_auto_point9_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 9); +static SENSOR_DEVICE_ATTR(pwm1_auto_point9_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 11); +static SENSOR_DEVICE_ATTR(pwm1_auto_point9_temp_hyst, S_IRUGO, + show_lut_temp_hyst, NULL, 11); +static SENSOR_DEVICE_ATTR(pwm1_auto_point10_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 10); +static SENSOR_DEVICE_ATTR(pwm1_auto_point10_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 12); +static SENSOR_DEVICE_ATTR(pwm1_auto_point10_temp_hyst, S_IRUGO, + show_lut_temp_hyst, NULL, 12); +static SENSOR_DEVICE_ATTR(pwm1_auto_point11_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 11); +static SENSOR_DEVICE_ATTR(pwm1_auto_point11_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 13); +static SENSOR_DEVICE_ATTR(pwm1_auto_point11_temp_hyst, S_IRUGO, + show_lut_temp_hyst, NULL, 13); +static SENSOR_DEVICE_ATTR(pwm1_auto_point12_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 12); +static SENSOR_DEVICE_ATTR(pwm1_auto_point12_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 14); +static SENSOR_DEVICE_ATTR(pwm1_auto_point12_temp_hyst, S_IRUGO, + show_lut_temp_hyst, NULL, 14); + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_local_temp8, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_local_temp8, + set_temp8, 1); + +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp11, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_temp11, + set_temp11, 1); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp11, + set_temp11, 2); +static SENSOR_DEVICE_ATTR(temp2_offset, S_IWUSR | S_IRUGO, show_temp11, + set_temp11, 3); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO, show_remote_temp8, + set_temp8, 2); +static DEVICE_ATTR_RW(temp2_crit_hyst); + +static DEVICE_ATTR_RW(temp2_type); + +/* Individual alarm files */ +static SENSOR_DEVICE_ATTR(fan1_min_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 6); +/* Raw alarm file for compatibility */ +static DEVICE_ATTR_RO(alarms); + +static DEVICE_ATTR_RW(update_interval); + +static struct attribute *lm63_attributes[] = { + &sensor_dev_attr_pwm1.dev_attr.attr, + &dev_attr_pwm1_enable.attr, + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point6_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point7_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point7_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point7_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point8_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point8_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point8_temp_hyst.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_offset.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &dev_attr_temp2_crit_hyst.attr, + + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &dev_attr_alarms.attr, + &dev_attr_update_interval.attr, + NULL +}; + +static struct attribute *lm63_attributes_temp2_type[] = { + &dev_attr_temp2_type.attr, + NULL +}; + +static const struct attribute_group lm63_group_temp2_type = { + .attrs = lm63_attributes_temp2_type, +}; + +static struct attribute *lm63_attributes_extra_lut[] = { + &sensor_dev_attr_pwm1_auto_point9_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point9_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point9_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point10_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point10_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point10_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point11_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point11_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point11_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point12_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point12_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point12_temp_hyst.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm63_group_extra_lut = { + .attrs = lm63_attributes_extra_lut, +}; + +/* + * On LM63, temp2_crit can be set only once, which should be job + * of the bootloader. + * On LM64, temp2_crit can always be set. + * On LM96163, temp2_crit can be set if bit 1 of the configuration + * register is true. + */ +static umode_t lm63_attribute_mode(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct lm63_data *data = dev_get_drvdata(dev); + + if (attr == &sensor_dev_attr_temp2_crit.dev_attr.attr + && (data->kind == lm64 || + (data->kind == lm96163 && (data->config & 0x02)))) + return attr->mode | S_IWUSR; + + return attr->mode; +} + +static const struct attribute_group lm63_group = { + .is_visible = lm63_attribute_mode, + .attrs = lm63_attributes, +}; + +static struct attribute *lm63_attributes_fan1[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + + &sensor_dev_attr_fan1_min_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm63_group_fan1 = { + .attrs = lm63_attributes_fan1, +}; + +/* + * Real code + */ + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int lm63_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + u8 man_id, chip_id, reg_config1, reg_config2; + u8 reg_alert_status, reg_alert_mask; + int address = client->addr; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + man_id = i2c_smbus_read_byte_data(client, LM63_REG_MAN_ID); + chip_id = i2c_smbus_read_byte_data(client, LM63_REG_CHIP_ID); + + reg_config1 = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG1); + reg_config2 = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG2); + reg_alert_status = i2c_smbus_read_byte_data(client, + LM63_REG_ALERT_STATUS); + reg_alert_mask = i2c_smbus_read_byte_data(client, LM63_REG_ALERT_MASK); + + if (man_id != 0x01 /* National Semiconductor */ + || (reg_config1 & 0x18) != 0x00 + || (reg_config2 & 0xF8) != 0x00 + || (reg_alert_status & 0x20) != 0x00 + || (reg_alert_mask & 0xA4) != 0xA4) { + dev_dbg(&adapter->dev, + "Unsupported chip (man_id=0x%02X, chip_id=0x%02X)\n", + man_id, chip_id); + return -ENODEV; + } + + if (chip_id == 0x41 && address == 0x4c) + strlcpy(info->type, "lm63", I2C_NAME_SIZE); + else if (chip_id == 0x51 && (address == 0x18 || address == 0x4e)) + strlcpy(info->type, "lm64", I2C_NAME_SIZE); + else if (chip_id == 0x49 && address == 0x4c) + strlcpy(info->type, "lm96163", I2C_NAME_SIZE); + else + return -ENODEV; + + return 0; +} + +/* + * Ideally we shouldn't have to initialize anything, since the BIOS + * should have taken care of everything + */ +static void lm63_init_client(struct lm63_data *data) +{ + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + u8 convrate; + + data->config = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG1); + data->config_fan = i2c_smbus_read_byte_data(client, + LM63_REG_CONFIG_FAN); + + /* Start converting if needed */ + if (data->config & 0x40) { /* standby */ + dev_dbg(dev, "Switching to operational mode\n"); + data->config &= 0xA7; + i2c_smbus_write_byte_data(client, LM63_REG_CONFIG1, + data->config); + } + /* Tachometer is always enabled on LM64 */ + if (data->kind == lm64) + data->config |= 0x04; + + /* We may need pwm1_freq before ever updating the client data */ + data->pwm1_freq = i2c_smbus_read_byte_data(client, LM63_REG_PWM_FREQ); + if (data->pwm1_freq == 0) + data->pwm1_freq = 1; + + switch (data->kind) { + case lm63: + case lm64: + data->max_convrate_hz = LM63_MAX_CONVRATE_HZ; + data->lut_size = 8; + break; + case lm96163: + data->max_convrate_hz = LM96163_MAX_CONVRATE_HZ; + data->lut_size = 12; + data->trutherm + = i2c_smbus_read_byte_data(client, + LM96163_REG_TRUTHERM) & 0x02; + break; + } + convrate = i2c_smbus_read_byte_data(client, LM63_REG_CONVRATE); + if (unlikely(convrate > LM63_MAX_CONVRATE)) + convrate = LM63_MAX_CONVRATE; + data->update_interval = UPDATE_INTERVAL(data->max_convrate_hz, + convrate); + + /* + * For LM96163, check if high resolution PWM + * and unsigned temperature format is enabled. + */ + if (data->kind == lm96163) { + u8 config_enhanced + = i2c_smbus_read_byte_data(client, + LM96163_REG_CONFIG_ENHANCED); + if (config_enhanced & 0x20) + data->lut_temp_highres = true; + if ((config_enhanced & 0x10) + && !(data->config_fan & 0x08) && data->pwm1_freq == 8) + data->pwm_highres = true; + if (config_enhanced & 0x08) + data->remote_unsigned = true; + } + + /* Show some debug info about the LM63 configuration */ + if (data->kind == lm63) + dev_dbg(dev, "Alert/tach pin configured for %s\n", + (data->config & 0x04) ? "tachometer input" : + "alert output"); + dev_dbg(dev, "PWM clock %s kHz, output frequency %u Hz\n", + (data->config_fan & 0x08) ? "1.4" : "360", + ((data->config_fan & 0x08) ? 700 : 180000) / data->pwm1_freq); + dev_dbg(dev, "PWM output active %s, %s mode\n", + (data->config_fan & 0x10) ? "low" : "high", + (data->config_fan & 0x20) ? "manual" : "auto"); +} + +static int lm63_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct lm63_data *data; + int groups = 0; + + data = devm_kzalloc(dev, sizeof(struct lm63_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* Set the device type */ + if (client->dev.of_node) + data->kind = (enum chips)of_device_get_match_data(&client->dev); + else + data->kind = id->driver_data; + data->kind = id->driver_data; + if (data->kind == lm64) + data->temp2_offset = 16000; + + /* Initialize chip */ + lm63_init_client(data); + + /* Register sysfs hooks */ + data->groups[groups++] = &lm63_group; + if (data->config & 0x04) /* tachometer enabled */ + data->groups[groups++] = &lm63_group_fan1; + + if (data->kind == lm96163) { + data->groups[groups++] = &lm63_group_temp2_type; + data->groups[groups++] = &lm63_group_extra_lut; + } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +/* + * Driver data (common to all clients) + */ + +static const struct i2c_device_id lm63_id[] = { + { "lm63", lm63 }, + { "lm64", lm64 }, + { "lm96163", lm96163 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm63_id); + +static const struct of_device_id lm63_of_match[] = { + { + .compatible = "national,lm63", + .data = (void *)lm63 + }, + { + .compatible = "national,lm64", + .data = (void *)lm64 + }, + { + .compatible = "national,lm96163", + .data = (void *)lm96163 + }, + { }, +}; +MODULE_DEVICE_TABLE(of, lm63_of_match); + +static struct i2c_driver lm63_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm63", + .of_match_table = of_match_ptr(lm63_of_match), + }, + .probe = lm63_probe, + .id_table = lm63_id, + .detect = lm63_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(lm63_driver); + +MODULE_AUTHOR("Jean Delvare "); +MODULE_DESCRIPTION("LM63 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm70.c b/drivers/hwmon/lm70.c new file mode 100644 index 000000000..543556dc5 --- /dev/null +++ b/drivers/hwmon/lm70.c @@ -0,0 +1,220 @@ +/* + * lm70.c + * + * The LM70 is a temperature sensor chip from National Semiconductor (NS). + * Copyright (C) 2006 Kaiwan N Billimoria + * + * The LM70 communicates with a host processor via an SPI/Microwire Bus + * interface. The complete datasheet is available at National's website + * here: + * http://www.national.com/pf/LM/LM70.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define DRVNAME "lm70" + +#define LM70_CHIP_LM70 0 /* original NS LM70 */ +#define LM70_CHIP_TMP121 1 /* TI TMP121/TMP123 */ +#define LM70_CHIP_LM71 2 /* NS LM71 */ +#define LM70_CHIP_LM74 3 /* NS LM74 */ +#define LM70_CHIP_TMP122 4 /* TI TMP122/TMP124 */ + +struct lm70 { + struct spi_device *spi; + struct mutex lock; + unsigned int chip; +}; + +/* sysfs hook function */ +static ssize_t temp1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm70 *p_lm70 = dev_get_drvdata(dev); + struct spi_device *spi = p_lm70->spi; + int status, val = 0; + u8 rxbuf[2]; + s16 raw = 0; + + if (mutex_lock_interruptible(&p_lm70->lock)) + return -ERESTARTSYS; + + /* + * spi_read() requires a DMA-safe buffer; so we use + * spi_write_then_read(), transmitting 0 bytes. + */ + status = spi_write_then_read(spi, NULL, 0, &rxbuf[0], 2); + if (status < 0) { + dev_warn(dev, "spi_write_then_read failed with status %d\n", + status); + goto out; + } + raw = (rxbuf[0] << 8) + rxbuf[1]; + dev_dbg(dev, "rxbuf[0] : 0x%02x rxbuf[1] : 0x%02x raw=0x%04x\n", + rxbuf[0], rxbuf[1], raw); + + /* + * LM70: + * The "raw" temperature read into rxbuf[] is a 16-bit signed 2's + * complement value. Only the MSB 11 bits (1 sign + 10 temperature + * bits) are meaningful; the LSB 5 bits are to be discarded. + * See the datasheet. + * + * Further, each bit represents 0.25 degrees Celsius; so, multiply + * by 0.25. Also multiply by 1000 to represent in millidegrees + * Celsius. + * So it's equivalent to multiplying by 0.25 * 1000 = 250. + * + * LM74 and TMP121/TMP122/TMP123/TMP124: + * 13 bits of 2's complement data, discard LSB 3 bits, + * resolution 0.0625 degrees celsius. + * + * LM71: + * 14 bits of 2's complement data, discard LSB 2 bits, + * resolution 0.0312 degrees celsius. + */ + switch (p_lm70->chip) { + case LM70_CHIP_LM70: + val = ((int)raw / 32) * 250; + break; + + case LM70_CHIP_TMP121: + case LM70_CHIP_TMP122: + case LM70_CHIP_LM74: + val = ((int)raw / 8) * 625 / 10; + break; + + case LM70_CHIP_LM71: + val = ((int)raw / 4) * 3125 / 100; + break; + } + + status = sprintf(buf, "%d\n", val); /* millidegrees Celsius */ +out: + mutex_unlock(&p_lm70->lock); + return status; +} + +static DEVICE_ATTR_RO(temp1_input); + +static struct attribute *lm70_attrs[] = { + &dev_attr_temp1_input.attr, + NULL +}; + +ATTRIBUTE_GROUPS(lm70); + +/*----------------------------------------------------------------------*/ + +#ifdef CONFIG_OF +static const struct of_device_id lm70_of_ids[] = { + { + .compatible = "ti,lm70", + .data = (void *) LM70_CHIP_LM70, + }, + { + .compatible = "ti,tmp121", + .data = (void *) LM70_CHIP_TMP121, + }, + { + .compatible = "ti,tmp122", + .data = (void *) LM70_CHIP_TMP122, + }, + { + .compatible = "ti,lm71", + .data = (void *) LM70_CHIP_LM71, + }, + { + .compatible = "ti,lm74", + .data = (void *) LM70_CHIP_LM74, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, lm70_of_ids); +#endif + +static int lm70_probe(struct spi_device *spi) +{ + const struct of_device_id *match; + struct device *hwmon_dev; + struct lm70 *p_lm70; + int chip; + + match = of_match_device(lm70_of_ids, &spi->dev); + if (match) + chip = (int)(uintptr_t)match->data; + else + chip = spi_get_device_id(spi)->driver_data; + + /* signaling is SPI_MODE_0 */ + if (spi->mode & (SPI_CPOL | SPI_CPHA)) + return -EINVAL; + + /* NOTE: we assume 8-bit words, and convert to 16 bits manually */ + + p_lm70 = devm_kzalloc(&spi->dev, sizeof(*p_lm70), GFP_KERNEL); + if (!p_lm70) + return -ENOMEM; + + mutex_init(&p_lm70->lock); + p_lm70->chip = chip; + p_lm70->spi = spi; + + hwmon_dev = devm_hwmon_device_register_with_groups(&spi->dev, + spi->modalias, + p_lm70, lm70_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct spi_device_id lm70_ids[] = { + { "lm70", LM70_CHIP_LM70 }, + { "tmp121", LM70_CHIP_TMP121 }, + { "tmp122", LM70_CHIP_TMP122 }, + { "lm71", LM70_CHIP_LM71 }, + { "lm74", LM70_CHIP_LM74 }, + { }, +}; +MODULE_DEVICE_TABLE(spi, lm70_ids); + +static struct spi_driver lm70_driver = { + .driver = { + .name = "lm70", + .of_match_table = of_match_ptr(lm70_of_ids), + }, + .id_table = lm70_ids, + .probe = lm70_probe, +}; + +module_spi_driver(lm70_driver); + +MODULE_AUTHOR("Kaiwan N Billimoria"); +MODULE_DESCRIPTION("NS LM70 and compatibles Linux driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm73.c b/drivers/hwmon/lm73.c new file mode 100644 index 000000000..9653bb870 --- /dev/null +++ b/drivers/hwmon/lm73.c @@ -0,0 +1,287 @@ +/* + * LM73 Sensor driver + * Based on LM75 + * + * Copyright (C) 2007, CenoSYS (www.cenosys.com). + * Copyright (C) 2009, Bollore telecom (www.bolloretelecom.eu). + * + * Guillaume Ligneul + * Adrien Demarez + * Jeremy Laine + * Chris Verges + * + * This software program is licensed subject to the GNU General Public License + * (GPL).Version 2,June 1991, available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + */ + +#include +#include +#include +#include +#include +#include + + +/* Addresses scanned */ +static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4c, + 0x4d, 0x4e, I2C_CLIENT_END }; + +/* LM73 registers */ +#define LM73_REG_INPUT 0x00 +#define LM73_REG_CONF 0x01 +#define LM73_REG_MAX 0x02 +#define LM73_REG_MIN 0x03 +#define LM73_REG_CTRL 0x04 +#define LM73_REG_ID 0x07 + +#define LM73_ID 0x9001 /* 0x0190, byte-swapped */ +#define DRVNAME "lm73" +#define LM73_TEMP_MIN (-256000 / 250) +#define LM73_TEMP_MAX (255750 / 250) + +#define LM73_CTRL_RES_SHIFT 5 +#define LM73_CTRL_RES_MASK (BIT(5) | BIT(6)) +#define LM73_CTRL_TO_MASK BIT(7) + +#define LM73_CTRL_HI_SHIFT 2 +#define LM73_CTRL_LO_SHIFT 1 + +static const unsigned short lm73_convrates[] = { + 14, /* 11-bits (0.25000 C/LSB): RES1 Bit = 0, RES0 Bit = 0 */ + 28, /* 12-bits (0.12500 C/LSB): RES1 Bit = 0, RES0 Bit = 1 */ + 56, /* 13-bits (0.06250 C/LSB): RES1 Bit = 1, RES0 Bit = 0 */ + 112, /* 14-bits (0.03125 C/LSB): RES1 Bit = 1, RES0 Bit = 1 */ +}; + +struct lm73_data { + struct i2c_client *client; + struct mutex lock; + u8 ctrl; /* control register value */ +}; + +/*-----------------------------------------------------------------------*/ + +static ssize_t set_temp(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct lm73_data *data = dev_get_drvdata(dev); + long temp; + short value; + s32 err; + + int status = kstrtol(buf, 10, &temp); + if (status < 0) + return status; + + /* Write value */ + value = clamp_val(temp / 250, LM73_TEMP_MIN, LM73_TEMP_MAX) << 5; + err = i2c_smbus_write_word_swapped(data->client, attr->index, value); + return (err < 0) ? err : count; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct lm73_data *data = dev_get_drvdata(dev); + int temp; + + s32 err = i2c_smbus_read_word_swapped(data->client, attr->index); + if (err < 0) + return err; + + /* use integer division instead of equivalent right shift to + guarantee arithmetic shift and preserve the sign */ + temp = (((s16) err) * 250) / 32; + return scnprintf(buf, PAGE_SIZE, "%d\n", temp); +} + +static ssize_t set_convrate(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct lm73_data *data = dev_get_drvdata(dev); + unsigned long convrate; + s32 err; + int res = 0; + + err = kstrtoul(buf, 10, &convrate); + if (err < 0) + return err; + + /* + * Convert the desired conversion rate into register bits. + * res is already initialized, and everything past the second-to-last + * value in the array is treated as belonging to the last value + * in the array. + */ + while (res < (ARRAY_SIZE(lm73_convrates) - 1) && + convrate > lm73_convrates[res]) + res++; + + mutex_lock(&data->lock); + data->ctrl &= LM73_CTRL_TO_MASK; + data->ctrl |= res << LM73_CTRL_RES_SHIFT; + err = i2c_smbus_write_byte_data(data->client, LM73_REG_CTRL, + data->ctrl); + mutex_unlock(&data->lock); + + if (err < 0) + return err; + + return count; +} + +static ssize_t show_convrate(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct lm73_data *data = dev_get_drvdata(dev); + int res; + + res = (data->ctrl & LM73_CTRL_RES_MASK) >> LM73_CTRL_RES_SHIFT; + return scnprintf(buf, PAGE_SIZE, "%hu\n", lm73_convrates[res]); +} + +static ssize_t show_maxmin_alarm(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct lm73_data *data = dev_get_drvdata(dev); + s32 ctrl; + + mutex_lock(&data->lock); + ctrl = i2c_smbus_read_byte_data(data->client, LM73_REG_CTRL); + if (ctrl < 0) + goto abort; + data->ctrl = ctrl; + mutex_unlock(&data->lock); + + return scnprintf(buf, PAGE_SIZE, "%d\n", (ctrl >> attr->index) & 1); + +abort: + mutex_unlock(&data->lock); + return ctrl; +} + +/*-----------------------------------------------------------------------*/ + +/* sysfs attributes for hwmon */ + +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, + show_temp, set_temp, LM73_REG_MAX); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, + show_temp, set_temp, LM73_REG_MIN); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, + show_temp, NULL, LM73_REG_INPUT); +static SENSOR_DEVICE_ATTR(update_interval, S_IWUSR | S_IRUGO, + show_convrate, set_convrate, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, + show_maxmin_alarm, NULL, LM73_CTRL_HI_SHIFT); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, + show_maxmin_alarm, NULL, LM73_CTRL_LO_SHIFT); + +static struct attribute *lm73_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_update_interval.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(lm73); + +/*-----------------------------------------------------------------------*/ + +/* device probe and removal */ + +static int +lm73_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct lm73_data *data; + int ctrl; + + data = devm_kzalloc(dev, sizeof(struct lm73_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->lock); + + ctrl = i2c_smbus_read_byte_data(client, LM73_REG_CTRL); + if (ctrl < 0) + return ctrl; + data->ctrl = ctrl; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, lm73_groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + dev_info(dev, "sensor '%s'\n", client->name); + + return 0; +} + +static const struct i2c_device_id lm73_ids[] = { + { "lm73", 0 }, + { /* LIST END */ } +}; +MODULE_DEVICE_TABLE(i2c, lm73_ids); + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int lm73_detect(struct i2c_client *new_client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = new_client->adapter; + int id, ctrl, conf; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + /* + * Do as much detection as possible with byte reads first, as word + * reads can confuse other devices. + */ + ctrl = i2c_smbus_read_byte_data(new_client, LM73_REG_CTRL); + if (ctrl < 0 || (ctrl & 0x10)) + return -ENODEV; + + conf = i2c_smbus_read_byte_data(new_client, LM73_REG_CONF); + if (conf < 0 || (conf & 0x0c)) + return -ENODEV; + + id = i2c_smbus_read_byte_data(new_client, LM73_REG_ID); + if (id < 0 || id != (LM73_ID & 0xff)) + return -ENODEV; + + /* Check device ID */ + id = i2c_smbus_read_word_data(new_client, LM73_REG_ID); + if (id < 0 || id != LM73_ID) + return -ENODEV; + + strlcpy(info->type, "lm73", I2C_NAME_SIZE); + + return 0; +} + +static struct i2c_driver lm73_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm73", + }, + .probe = lm73_probe, + .id_table = lm73_ids, + .detect = lm73_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(lm73_driver); + +MODULE_AUTHOR("Guillaume Ligneul "); +MODULE_DESCRIPTION("LM73 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c new file mode 100644 index 000000000..7f28912c9 --- /dev/null +++ b/drivers/hwmon/lm75.c @@ -0,0 +1,671 @@ +/* + * lm75.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (c) 1998, 1999 Frodo Looijaard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lm75.h" + + +/* + * This driver handles the LM75 and compatible digital temperature sensors. + */ + +enum lm75_type { /* keep sorted in alphabetical order */ + adt75, + ds1775, + ds75, + ds7505, + g751, + lm75, + lm75a, + lm75b, + max6625, + max6626, + mcp980x, + stds75, + tcn75, + tmp100, + tmp101, + tmp105, + tmp112, + tmp175, + tmp275, + tmp75, + tmp75c, +}; + +/* Addresses scanned */ +static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c, + 0x4d, 0x4e, 0x4f, I2C_CLIENT_END }; + + +/* The LM75 registers */ +#define LM75_REG_TEMP 0x00 +#define LM75_REG_CONF 0x01 +#define LM75_REG_HYST 0x02 +#define LM75_REG_MAX 0x03 + +/* Each client has this additional data */ +struct lm75_data { + struct i2c_client *client; + struct regmap *regmap; + u8 orig_conf; + u8 resolution; /* In bits, between 9 and 12 */ + u8 resolution_limits; + unsigned int sample_time; /* In ms */ +}; + +/*-----------------------------------------------------------------------*/ + +static inline long lm75_reg_to_mc(s16 temp, u8 resolution) +{ + return ((temp >> (16 - resolution)) * 1000) >> (resolution - 8); +} + +static int lm75_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct lm75_data *data = dev_get_drvdata(dev); + unsigned int regval; + int err, reg; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + *val = data->sample_time; + break; + default: + return -EINVAL; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + reg = LM75_REG_TEMP; + break; + case hwmon_temp_max: + reg = LM75_REG_MAX; + break; + case hwmon_temp_max_hyst: + reg = LM75_REG_HYST; + break; + default: + return -EINVAL; + } + err = regmap_read(data->regmap, reg, ®val); + if (err < 0) + return err; + + *val = lm75_reg_to_mc(regval, data->resolution); + break; + default: + return -EINVAL; + } + return 0; +} + +static int lm75_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long temp) +{ + struct lm75_data *data = dev_get_drvdata(dev); + u8 resolution; + int reg; + + if (type != hwmon_temp) + return -EINVAL; + + switch (attr) { + case hwmon_temp_max: + reg = LM75_REG_MAX; + break; + case hwmon_temp_max_hyst: + reg = LM75_REG_HYST; + break; + default: + return -EINVAL; + } + + /* + * Resolution of limit registers is assumed to be the same as the + * temperature input register resolution unless given explicitly. + */ + if (data->resolution_limits) + resolution = data->resolution_limits; + else + resolution = data->resolution; + + temp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX); + temp = DIV_ROUND_CLOSEST(temp << (resolution - 8), + 1000) << (16 - resolution); + + return regmap_write(data->regmap, reg, (u16)temp); +} + +static umode_t lm75_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return S_IRUGO; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + return S_IRUGO; + case hwmon_temp_max: + case hwmon_temp_max_hyst: + return S_IRUGO | S_IWUSR; + } + break; + default: + break; + } + return 0; +} + +/*-----------------------------------------------------------------------*/ + +/* device probe and removal */ + +/* chip configuration */ + +static const u32 lm75_chip_config[] = { + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL, + 0 +}; + +static const struct hwmon_channel_info lm75_chip = { + .type = hwmon_chip, + .config = lm75_chip_config, +}; + +static const u32 lm75_temp_config[] = { + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST, + 0 +}; + +static const struct hwmon_channel_info lm75_temp = { + .type = hwmon_temp, + .config = lm75_temp_config, +}; + +static const struct hwmon_channel_info *lm75_info[] = { + &lm75_chip, + &lm75_temp, + NULL +}; + +static const struct hwmon_ops lm75_hwmon_ops = { + .is_visible = lm75_is_visible, + .read = lm75_read, + .write = lm75_write, +}; + +static const struct hwmon_chip_info lm75_chip_info = { + .ops = &lm75_hwmon_ops, + .info = lm75_info, +}; + +static bool lm75_is_writeable_reg(struct device *dev, unsigned int reg) +{ + return reg != LM75_REG_TEMP; +} + +static bool lm75_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg == LM75_REG_TEMP; +} + +static const struct regmap_config lm75_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = LM75_REG_MAX, + .writeable_reg = lm75_is_writeable_reg, + .volatile_reg = lm75_is_volatile_reg, + .val_format_endian = REGMAP_ENDIAN_BIG, + .cache_type = REGCACHE_RBTREE, + .use_single_rw = true, +}; + +static void lm75_remove(void *data) +{ + struct lm75_data *lm75 = data; + struct i2c_client *client = lm75->client; + + i2c_smbus_write_byte_data(client, LM75_REG_CONF, lm75->orig_conf); +} + +static int +lm75_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct lm75_data *data; + int status, err; + u8 set_mask, clr_mask; + int new; + enum lm75_type kind; + + if (client->dev.of_node) + kind = (enum lm75_type)of_device_get_match_data(&client->dev); + else + kind = id->driver_data; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) + return -EIO; + + data = devm_kzalloc(dev, sizeof(struct lm75_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + + data->regmap = devm_regmap_init_i2c(client, &lm75_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + /* Set to LM75 resolution (9 bits, 1/2 degree C) and range. + * Then tweak to be more precise when appropriate. + */ + set_mask = 0; + clr_mask = LM75_SHUTDOWN; /* continuous conversions */ + + switch (kind) { + case adt75: + clr_mask |= 1 << 5; /* not one-shot mode */ + data->resolution = 12; + data->sample_time = MSEC_PER_SEC / 8; + break; + case ds1775: + case ds75: + case stds75: + clr_mask |= 3 << 5; + set_mask |= 2 << 5; /* 11-bit mode */ + data->resolution = 11; + data->sample_time = MSEC_PER_SEC; + break; + case ds7505: + set_mask |= 3 << 5; /* 12-bit mode */ + data->resolution = 12; + data->sample_time = MSEC_PER_SEC / 4; + break; + case g751: + case lm75: + case lm75a: + data->resolution = 9; + data->sample_time = MSEC_PER_SEC / 2; + break; + case lm75b: + data->resolution = 11; + data->sample_time = MSEC_PER_SEC / 4; + break; + case max6625: + data->resolution = 9; + data->sample_time = MSEC_PER_SEC / 4; + break; + case max6626: + data->resolution = 12; + data->resolution_limits = 9; + data->sample_time = MSEC_PER_SEC / 4; + break; + case tcn75: + data->resolution = 9; + data->sample_time = MSEC_PER_SEC / 8; + break; + case mcp980x: + data->resolution_limits = 9; + /* fall through */ + case tmp100: + case tmp101: + set_mask |= 3 << 5; /* 12-bit mode */ + data->resolution = 12; + data->sample_time = MSEC_PER_SEC; + clr_mask |= 1 << 7; /* not one-shot mode */ + break; + case tmp112: + set_mask |= 3 << 5; /* 12-bit mode */ + clr_mask |= 1 << 7; /* not one-shot mode */ + data->resolution = 12; + data->sample_time = MSEC_PER_SEC / 4; + break; + case tmp105: + case tmp175: + case tmp275: + case tmp75: + set_mask |= 3 << 5; /* 12-bit mode */ + clr_mask |= 1 << 7; /* not one-shot mode */ + data->resolution = 12; + data->sample_time = MSEC_PER_SEC / 2; + break; + case tmp75c: + clr_mask |= 1 << 5; /* not one-shot mode */ + data->resolution = 12; + data->sample_time = MSEC_PER_SEC / 4; + break; + } + + /* configure as specified */ + status = i2c_smbus_read_byte_data(client, LM75_REG_CONF); + if (status < 0) { + dev_dbg(dev, "Can't read config? %d\n", status); + return status; + } + data->orig_conf = status; + new = status & ~clr_mask; + new |= set_mask; + if (status != new) + i2c_smbus_write_byte_data(client, LM75_REG_CONF, new); + + err = devm_add_action_or_reset(dev, lm75_remove, data); + if (err) + return err; + + dev_dbg(dev, "Config %02x\n", new); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, &lm75_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + dev_info(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), client->name); + + return 0; +} + +static const struct i2c_device_id lm75_ids[] = { + { "adt75", adt75, }, + { "ds1775", ds1775, }, + { "ds75", ds75, }, + { "ds7505", ds7505, }, + { "g751", g751, }, + { "lm75", lm75, }, + { "lm75a", lm75a, }, + { "lm75b", lm75b, }, + { "max6625", max6625, }, + { "max6626", max6626, }, + { "mcp980x", mcp980x, }, + { "stds75", stds75, }, + { "tcn75", tcn75, }, + { "tmp100", tmp100, }, + { "tmp101", tmp101, }, + { "tmp105", tmp105, }, + { "tmp112", tmp112, }, + { "tmp175", tmp175, }, + { "tmp275", tmp275, }, + { "tmp75", tmp75, }, + { "tmp75c", tmp75c, }, + { /* LIST END */ } +}; +MODULE_DEVICE_TABLE(i2c, lm75_ids); + +static const struct of_device_id lm75_of_match[] = { + { + .compatible = "adi,adt75", + .data = (void *)adt75 + }, + { + .compatible = "dallas,ds1775", + .data = (void *)ds1775 + }, + { + .compatible = "dallas,ds75", + .data = (void *)ds75 + }, + { + .compatible = "dallas,ds7505", + .data = (void *)ds7505 + }, + { + .compatible = "gmt,g751", + .data = (void *)g751 + }, + { + .compatible = "national,lm75", + .data = (void *)lm75 + }, + { + .compatible = "national,lm75a", + .data = (void *)lm75a + }, + { + .compatible = "national,lm75b", + .data = (void *)lm75b + }, + { + .compatible = "maxim,max6625", + .data = (void *)max6625 + }, + { + .compatible = "maxim,max6626", + .data = (void *)max6626 + }, + { + .compatible = "maxim,mcp980x", + .data = (void *)mcp980x + }, + { + .compatible = "st,stds75", + .data = (void *)stds75 + }, + { + .compatible = "microchip,tcn75", + .data = (void *)tcn75 + }, + { + .compatible = "ti,tmp100", + .data = (void *)tmp100 + }, + { + .compatible = "ti,tmp101", + .data = (void *)tmp101 + }, + { + .compatible = "ti,tmp105", + .data = (void *)tmp105 + }, + { + .compatible = "ti,tmp112", + .data = (void *)tmp112 + }, + { + .compatible = "ti,tmp175", + .data = (void *)tmp175 + }, + { + .compatible = "ti,tmp275", + .data = (void *)tmp275 + }, + { + .compatible = "ti,tmp75", + .data = (void *)tmp75 + }, + { + .compatible = "ti,tmp75c", + .data = (void *)tmp75c + }, + { }, +}; +MODULE_DEVICE_TABLE(of, lm75_of_match); + +#define LM75A_ID 0xA1 + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int lm75_detect(struct i2c_client *new_client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = new_client->adapter; + int i; + int conf, hyst, os; + bool is_lm75a = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + /* + * Now, we do the remaining detection. There is no identification- + * dedicated register so we have to rely on several tricks: + * unused bits, registers cycling over 8-address boundaries, + * addresses 0x04-0x07 returning the last read value. + * The cycling+unused addresses combination is not tested, + * since it would significantly slow the detection down and would + * hardly add any value. + * + * The National Semiconductor LM75A is different than earlier + * LM75s. It has an ID byte of 0xaX (where X is the chip + * revision, with 1 being the only revision in existence) in + * register 7, and unused registers return 0xff rather than the + * last read value. + * + * Note that this function only detects the original National + * Semiconductor LM75 and the LM75A. Clones from other vendors + * aren't detected, on purpose, because they are typically never + * found on PC hardware. They are found on embedded designs where + * they can be instantiated explicitly so detection is not needed. + * The absence of identification registers on all these clones + * would make their exhaustive detection very difficult and weak, + * and odds are that the driver would bind to unsupported devices. + */ + + /* Unused bits */ + conf = i2c_smbus_read_byte_data(new_client, 1); + if (conf & 0xe0) + return -ENODEV; + + /* First check for LM75A */ + if (i2c_smbus_read_byte_data(new_client, 7) == LM75A_ID) { + /* LM75A returns 0xff on unused registers so + just to be sure we check for that too. */ + if (i2c_smbus_read_byte_data(new_client, 4) != 0xff + || i2c_smbus_read_byte_data(new_client, 5) != 0xff + || i2c_smbus_read_byte_data(new_client, 6) != 0xff) + return -ENODEV; + is_lm75a = 1; + hyst = i2c_smbus_read_byte_data(new_client, 2); + os = i2c_smbus_read_byte_data(new_client, 3); + } else { /* Traditional style LM75 detection */ + /* Unused addresses */ + hyst = i2c_smbus_read_byte_data(new_client, 2); + if (i2c_smbus_read_byte_data(new_client, 4) != hyst + || i2c_smbus_read_byte_data(new_client, 5) != hyst + || i2c_smbus_read_byte_data(new_client, 6) != hyst + || i2c_smbus_read_byte_data(new_client, 7) != hyst) + return -ENODEV; + os = i2c_smbus_read_byte_data(new_client, 3); + if (i2c_smbus_read_byte_data(new_client, 4) != os + || i2c_smbus_read_byte_data(new_client, 5) != os + || i2c_smbus_read_byte_data(new_client, 6) != os + || i2c_smbus_read_byte_data(new_client, 7) != os) + return -ENODEV; + } + /* + * It is very unlikely that this is a LM75 if both + * hysteresis and temperature limit registers are 0. + */ + if (hyst == 0 && os == 0) + return -ENODEV; + + /* Addresses cycling */ + for (i = 8; i <= 248; i += 40) { + if (i2c_smbus_read_byte_data(new_client, i + 1) != conf + || i2c_smbus_read_byte_data(new_client, i + 2) != hyst + || i2c_smbus_read_byte_data(new_client, i + 3) != os) + return -ENODEV; + if (is_lm75a && i2c_smbus_read_byte_data(new_client, i + 7) + != LM75A_ID) + return -ENODEV; + } + + strlcpy(info->type, is_lm75a ? "lm75a" : "lm75", I2C_NAME_SIZE); + + return 0; +} + +#ifdef CONFIG_PM +static int lm75_suspend(struct device *dev) +{ + int status; + struct i2c_client *client = to_i2c_client(dev); + status = i2c_smbus_read_byte_data(client, LM75_REG_CONF); + if (status < 0) { + dev_dbg(&client->dev, "Can't read config? %d\n", status); + return status; + } + status = status | LM75_SHUTDOWN; + i2c_smbus_write_byte_data(client, LM75_REG_CONF, status); + return 0; +} + +static int lm75_resume(struct device *dev) +{ + int status; + struct i2c_client *client = to_i2c_client(dev); + status = i2c_smbus_read_byte_data(client, LM75_REG_CONF); + if (status < 0) { + dev_dbg(&client->dev, "Can't read config? %d\n", status); + return status; + } + status = status & ~LM75_SHUTDOWN; + i2c_smbus_write_byte_data(client, LM75_REG_CONF, status); + return 0; +} + +static const struct dev_pm_ops lm75_dev_pm_ops = { + .suspend = lm75_suspend, + .resume = lm75_resume, +}; +#define LM75_DEV_PM_OPS (&lm75_dev_pm_ops) +#else +#define LM75_DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + +static struct i2c_driver lm75_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm75", + .of_match_table = of_match_ptr(lm75_of_match), + .pm = LM75_DEV_PM_OPS, + }, + .probe = lm75_probe, + .id_table = lm75_ids, + .detect = lm75_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(lm75_driver); + +MODULE_AUTHOR("Frodo Looijaard "); +MODULE_DESCRIPTION("LM75 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm75.h b/drivers/hwmon/lm75.h new file mode 100644 index 000000000..5cde94e56 --- /dev/null +++ b/drivers/hwmon/lm75.h @@ -0,0 +1,49 @@ +/* + lm75.h - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 2003 Mark M. Hoffman + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + This file contains common code for encoding/decoding LM75 type + temperature readings, which are emulated by many of the chips + we support. As the user is unlikely to load more than one driver + which contains this code, we don't worry about the wasted space. +*/ + +#include + +/* straight from the datasheet */ +#define LM75_TEMP_MIN (-55000) +#define LM75_TEMP_MAX 125000 +#define LM75_SHUTDOWN 0x01 + +/* TEMP: 0.001C/bit (-55C to +125C) + REG: (0.5C/bit, two's complement) << 7 */ +static inline u16 LM75_TEMP_TO_REG(long temp) +{ + int ntemp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX); + ntemp += (ntemp < 0 ? -250 : 250); + return (u16)((ntemp / 500) << 7); +} + +static inline int LM75_TEMP_FROM_REG(u16 reg) +{ + /* use integer division instead of equivalent right shift to + guarantee arithmetic shift and preserve the sign */ + return ((s16)reg / 128) * 500; +} diff --git a/drivers/hwmon/lm77.c b/drivers/hwmon/lm77.c new file mode 100644 index 000000000..69b05cc2f --- /dev/null +++ b/drivers/hwmon/lm77.c @@ -0,0 +1,373 @@ +/* + * lm77.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * + * Copyright (c) 2004 Andras BALI + * + * Heavily based on lm75.c by Frodo Looijaard . The LM77 + * is a temperature sensor and thermal window comparator with 0.5 deg + * resolution made by National Semiconductor. Complete datasheet can be + * obtained at their site: + * http://www.national.com/pf/LM/LM77.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, + I2C_CLIENT_END }; + +/* The LM77 registers */ +#define LM77_REG_TEMP 0x00 +#define LM77_REG_CONF 0x01 +#define LM77_REG_TEMP_HYST 0x02 +#define LM77_REG_TEMP_CRIT 0x03 +#define LM77_REG_TEMP_MIN 0x04 +#define LM77_REG_TEMP_MAX 0x05 + +enum temp_index { + t_input = 0, + t_crit, + t_min, + t_max, + t_hyst, + t_num_temp +}; + +static const u8 temp_regs[t_num_temp] = { + [t_input] = LM77_REG_TEMP, + [t_min] = LM77_REG_TEMP_MIN, + [t_max] = LM77_REG_TEMP_MAX, + [t_crit] = LM77_REG_TEMP_CRIT, + [t_hyst] = LM77_REG_TEMP_HYST, +}; + +/* Each client has this additional data */ +struct lm77_data { + struct i2c_client *client; + struct mutex update_lock; + char valid; + unsigned long last_updated; /* In jiffies */ + int temp[t_num_temp]; /* index using temp_index */ + u8 alarms; +}; + +/* straight from the datasheet */ +#define LM77_TEMP_MIN (-55000) +#define LM77_TEMP_MAX 125000 + +/* + * In the temperature registers, the low 3 bits are not part of the + * temperature values; they are the status bits. + */ +static inline s16 LM77_TEMP_TO_REG(int temp) +{ + return (temp / 500) * 8; +} + +static inline int LM77_TEMP_FROM_REG(s16 reg) +{ + return (reg / 8) * 500; +} + +/* + * All registers are word-sized, except for the configuration register. + * The LM77 uses the high-byte first convention. + */ +static u16 lm77_read_value(struct i2c_client *client, u8 reg) +{ + if (reg == LM77_REG_CONF) + return i2c_smbus_read_byte_data(client, reg); + else + return i2c_smbus_read_word_swapped(client, reg); +} + +static int lm77_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + if (reg == LM77_REG_CONF) + return i2c_smbus_write_byte_data(client, reg, value); + else + return i2c_smbus_write_word_swapped(client, reg, value); +} + +static struct lm77_data *lm77_update_device(struct device *dev) +{ + struct lm77_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + dev_dbg(&client->dev, "Starting lm77 update\n"); + for (i = 0; i < t_num_temp; i++) { + data->temp[i] = + LM77_TEMP_FROM_REG(lm77_read_value(client, + temp_regs[i])); + } + data->alarms = + lm77_read_value(client, LM77_REG_TEMP) & 0x0007; + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* sysfs stuff */ + +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm77_data *data = lm77_update_device(dev); + + return sprintf(buf, "%d\n", data->temp[attr->index]); +} + +static ssize_t show_temp_hyst(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm77_data *data = lm77_update_device(dev); + int nr = attr->index; + int temp; + + temp = nr == t_min ? data->temp[nr] + data->temp[t_hyst] : + data->temp[nr] - data->temp[t_hyst]; + + return sprintf(buf, "%d\n", temp); +} + +static ssize_t set_temp(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm77_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + val = clamp_val(val, LM77_TEMP_MIN, LM77_TEMP_MAX); + mutex_lock(&data->update_lock); + data->temp[nr] = val; + lm77_write_value(client, temp_regs[nr], LM77_TEMP_TO_REG(val)); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * hysteresis is stored as a relative value on the chip, so it has to be + * converted first. + */ +static ssize_t set_temp_hyst(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct lm77_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + val = clamp_val(data->temp[t_crit] - val, LM77_TEMP_MIN, LM77_TEMP_MAX); + data->temp[t_hyst] = val; + lm77_write_value(client, LM77_REG_TEMP_HYST, + LM77_TEMP_TO_REG(data->temp[t_hyst])); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct lm77_data *data = lm77_update_device(dev); + return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, t_input); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, show_temp, set_temp, + t_crit); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp, set_temp, + t_min); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp, set_temp, + t_max); + +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO, show_temp_hyst, + set_temp_hyst, t_crit); +static SENSOR_DEVICE_ATTR(temp1_min_hyst, S_IRUGO, show_temp_hyst, NULL, t_min); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IRUGO, show_temp_hyst, NULL, t_max); + +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 1); + +static struct attribute *lm77_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_min_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(lm77); + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int lm77_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int i, cur, conf, hyst, crit, min, max; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + /* + * Here comes the remaining detection. Since the LM77 has no + * register dedicated to identification, we have to rely on the + * following tricks: + * + * 1. the high 4 bits represent the sign and thus they should + * always be the same + * 2. the high 3 bits are unused in the configuration register + * 3. addresses 0x06 and 0x07 return the last read value + * 4. registers cycling over 8-address boundaries + * + * Word-sized registers are high-byte first. + */ + + /* addresses cycling */ + cur = i2c_smbus_read_word_data(client, 0); + conf = i2c_smbus_read_byte_data(client, 1); + hyst = i2c_smbus_read_word_data(client, 2); + crit = i2c_smbus_read_word_data(client, 3); + min = i2c_smbus_read_word_data(client, 4); + max = i2c_smbus_read_word_data(client, 5); + for (i = 8; i <= 0xff; i += 8) { + if (i2c_smbus_read_byte_data(client, i + 1) != conf + || i2c_smbus_read_word_data(client, i + 2) != hyst + || i2c_smbus_read_word_data(client, i + 3) != crit + || i2c_smbus_read_word_data(client, i + 4) != min + || i2c_smbus_read_word_data(client, i + 5) != max) + return -ENODEV; + } + + /* sign bits */ + if (((cur & 0x00f0) != 0xf0 && (cur & 0x00f0) != 0x0) + || ((hyst & 0x00f0) != 0xf0 && (hyst & 0x00f0) != 0x0) + || ((crit & 0x00f0) != 0xf0 && (crit & 0x00f0) != 0x0) + || ((min & 0x00f0) != 0xf0 && (min & 0x00f0) != 0x0) + || ((max & 0x00f0) != 0xf0 && (max & 0x00f0) != 0x0)) + return -ENODEV; + + /* unused bits */ + if (conf & 0xe0) + return -ENODEV; + + /* 0x06 and 0x07 return the last read value */ + cur = i2c_smbus_read_word_data(client, 0); + if (i2c_smbus_read_word_data(client, 6) != cur + || i2c_smbus_read_word_data(client, 7) != cur) + return -ENODEV; + hyst = i2c_smbus_read_word_data(client, 2); + if (i2c_smbus_read_word_data(client, 6) != hyst + || i2c_smbus_read_word_data(client, 7) != hyst) + return -ENODEV; + min = i2c_smbus_read_word_data(client, 4); + if (i2c_smbus_read_word_data(client, 6) != min + || i2c_smbus_read_word_data(client, 7) != min) + return -ENODEV; + + strlcpy(info->type, "lm77", I2C_NAME_SIZE); + + return 0; +} + +static void lm77_init_client(struct i2c_client *client) +{ + /* Initialize the LM77 chip - turn off shutdown mode */ + int conf = lm77_read_value(client, LM77_REG_CONF); + if (conf & 1) + lm77_write_value(client, LM77_REG_CONF, conf & 0xfe); +} + +static int lm77_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct lm77_data *data; + + data = devm_kzalloc(dev, sizeof(struct lm77_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* Initialize the LM77 chip */ + lm77_init_client(client); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, lm77_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id lm77_id[] = { + { "lm77", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm77_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver lm77_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm77", + }, + .probe = lm77_probe, + .id_table = lm77_id, + .detect = lm77_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(lm77_driver); + +MODULE_AUTHOR("Andras BALI "); +MODULE_DESCRIPTION("LM77 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm78.c b/drivers/hwmon/lm78.c new file mode 100644 index 000000000..0cb7ff613 --- /dev/null +++ b/drivers/hwmon/lm78.c @@ -0,0 +1,1047 @@ +/* + * lm78.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (c) 1998, 1999 Frodo Looijaard + * Copyright (c) 2007, 2011 Jean Delvare + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_ISA +#include +#include +#include +#endif + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, I2C_CLIENT_END }; +enum chips { lm78, lm79 }; + +/* Many LM78 constants specified below */ + +/* Length of ISA address segment */ +#define LM78_EXTENT 8 + +/* Where are the ISA address/data registers relative to the base address */ +#define LM78_ADDR_REG_OFFSET 5 +#define LM78_DATA_REG_OFFSET 6 + +/* The LM78 registers */ +#define LM78_REG_IN_MAX(nr) (0x2b + (nr) * 2) +#define LM78_REG_IN_MIN(nr) (0x2c + (nr) * 2) +#define LM78_REG_IN(nr) (0x20 + (nr)) + +#define LM78_REG_FAN_MIN(nr) (0x3b + (nr)) +#define LM78_REG_FAN(nr) (0x28 + (nr)) + +#define LM78_REG_TEMP 0x27 +#define LM78_REG_TEMP_OVER 0x39 +#define LM78_REG_TEMP_HYST 0x3a + +#define LM78_REG_ALARM1 0x41 +#define LM78_REG_ALARM2 0x42 + +#define LM78_REG_VID_FANDIV 0x47 + +#define LM78_REG_CONFIG 0x40 +#define LM78_REG_CHIPID 0x49 +#define LM78_REG_I2C_ADDR 0x48 + + +/* + * Conversions. Rounding and limit checking is only done on the TO_REG + * variants. + */ + +/* + * IN: mV (0V to 4.08V) + * REG: 16mV/bit + */ +static inline u8 IN_TO_REG(unsigned long val) +{ + unsigned long nval = clamp_val(val, 0, 4080); + return (nval + 8) / 16; +} +#define IN_FROM_REG(val) ((val) * 16) + +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm <= 0) + return 255; + if (rpm > 1350000) + return 1; + return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 254); +} + +static inline int FAN_FROM_REG(u8 val, int div) +{ + return val == 0 ? -1 : val == 255 ? 0 : 1350000 / (val * div); +} + +/* + * TEMP: mC (-128C to +127C) + * REG: 1C/bit, two's complement + */ +static inline s8 TEMP_TO_REG(long val) +{ + int nval = clamp_val(val, -128000, 127000) ; + return nval < 0 ? (nval - 500) / 1000 : (nval + 500) / 1000; +} + +static inline int TEMP_FROM_REG(s8 val) +{ + return val * 1000; +} + +#define DIV_FROM_REG(val) (1 << (val)) + +struct lm78_data { + struct i2c_client *client; + struct mutex lock; + enum chips type; + + /* For ISA device only */ + const char *name; + int isa_addr; + + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[7]; /* Register value */ + u8 in_max[7]; /* Register value */ + u8 in_min[7]; /* Register value */ + u8 fan[3]; /* Register value */ + u8 fan_min[3]; /* Register value */ + s8 temp; /* Register value */ + s8 temp_over; /* Register value */ + s8 temp_hyst; /* Register value */ + u8 fan_div[3]; /* Register encoding, shifted right */ + u8 vid; /* Register encoding, combined */ + u16 alarms; /* Register encoding, combined */ +}; + + +static int lm78_read_value(struct lm78_data *data, u8 reg); +static int lm78_write_value(struct lm78_data *data, u8 reg, u8 value); +static struct lm78_data *lm78_update_device(struct device *dev); +static void lm78_init_device(struct lm78_data *data); + + +/* 7 Voltages */ +static ssize_t show_in(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct lm78_data *data = lm78_update_device(dev); + return sprintf(buf, "%d\n", IN_FROM_REG(data->in[attr->index])); +} + +static ssize_t show_in_min(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct lm78_data *data = lm78_update_device(dev); + return sprintf(buf, "%d\n", IN_FROM_REG(data->in_min[attr->index])); +} + +static ssize_t show_in_max(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct lm78_data *data = lm78_update_device(dev); + return sprintf(buf, "%d\n", IN_FROM_REG(data->in_max[attr->index])); +} + +static ssize_t set_in_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct lm78_data *data = dev_get_drvdata(dev); + int nr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_min[nr] = IN_TO_REG(val); + lm78_write_value(data, LM78_REG_IN_MIN(nr), data->in_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_in_max(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct lm78_data *data = dev_get_drvdata(dev); + int nr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_max[nr] = IN_TO_REG(val); + lm78_write_value(data, LM78_REG_IN_MAX(nr), data->in_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define show_in_offset(offset) \ +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, \ + show_in, NULL, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \ + show_in_min, set_in_min, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \ + show_in_max, set_in_max, offset); + +show_in_offset(0); +show_in_offset(1); +show_in_offset(2); +show_in_offset(3); +show_in_offset(4); +show_in_offset(5); +show_in_offset(6); + +/* Temperature */ +static ssize_t temp1_input_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct lm78_data *data = lm78_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp)); +} + +static ssize_t temp1_max_show(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct lm78_data *data = lm78_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_over)); +} + +static ssize_t temp1_max_store(struct device *dev, + struct device_attribute *da, const char *buf, + size_t count) +{ + struct lm78_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_over = TEMP_TO_REG(val); + lm78_write_value(data, LM78_REG_TEMP_OVER, data->temp_over); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t temp1_max_hyst_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct lm78_data *data = lm78_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_hyst)); +} + +static ssize_t temp1_max_hyst_store(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct lm78_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_hyst = TEMP_TO_REG(val); + lm78_write_value(data, LM78_REG_TEMP_HYST, data->temp_hyst); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RO(temp1_input); +static DEVICE_ATTR_RW(temp1_max); +static DEVICE_ATTR_RW(temp1_max_hyst); + +/* 3 Fans */ +static ssize_t show_fan(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct lm78_data *data = lm78_update_device(dev); + int nr = attr->index; + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr], + DIV_FROM_REG(data->fan_div[nr]))); +} + +static ssize_t show_fan_min(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct lm78_data *data = lm78_update_device(dev); + int nr = attr->index; + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr]))); +} + +static ssize_t set_fan_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct lm78_data *data = dev_get_drvdata(dev); + int nr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_min[nr] = FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); + lm78_write_value(data, LM78_REG_FAN_MIN(nr), data->fan_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_fan_div(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct lm78_data *data = lm78_update_device(dev); + return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[attr->index])); +} + +/* + * Note: we save and restore the fan minimum here, because its value is + * determined in part by the fan divisor. This follows the principle of + * least surprise; the user doesn't expect the fan minimum to change just + * because the divisor changed. + */ +static ssize_t set_fan_div(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct lm78_data *data = dev_get_drvdata(dev); + int nr = attr->index; + unsigned long min; + u8 reg; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + min = FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr])); + + switch (val) { + case 1: + data->fan_div[nr] = 0; + break; + case 2: + data->fan_div[nr] = 1; + break; + case 4: + data->fan_div[nr] = 2; + break; + case 8: + data->fan_div[nr] = 3; + break; + default: + dev_err(dev, + "fan_div value %ld not supported. Choose one of 1, 2, 4 or 8!\n", + val); + mutex_unlock(&data->update_lock); + return -EINVAL; + } + + reg = lm78_read_value(data, LM78_REG_VID_FANDIV); + switch (nr) { + case 0: + reg = (reg & 0xcf) | (data->fan_div[nr] << 4); + break; + case 1: + reg = (reg & 0x3f) | (data->fan_div[nr] << 6); + break; + } + lm78_write_value(data, LM78_REG_VID_FANDIV, reg); + + data->fan_min[nr] = + FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr])); + lm78_write_value(data, LM78_REG_FAN_MIN(nr), data->fan_min[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +#define show_fan_offset(offset) \ +static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \ + show_fan, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ + show_fan_min, set_fan_min, offset - 1); + +show_fan_offset(1); +show_fan_offset(2); +show_fan_offset(3); + +/* Fan 3 divisor is locked in H/W */ +static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR, + show_fan_div, set_fan_div, 0); +static SENSOR_DEVICE_ATTR(fan2_div, S_IRUGO | S_IWUSR, + show_fan_div, set_fan_div, 1); +static SENSOR_DEVICE_ATTR(fan3_div, S_IRUGO, show_fan_div, NULL, 2); + +/* VID */ +static ssize_t cpu0_vid_show(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct lm78_data *data = lm78_update_device(dev); + return sprintf(buf, "%d\n", vid_from_reg(data->vid, 82)); +} +static DEVICE_ATTR_RO(cpu0_vid); + +/* Alarms */ +static ssize_t alarms_show(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct lm78_data *data = lm78_update_device(dev); + return sprintf(buf, "%u\n", data->alarms); +} +static DEVICE_ATTR_RO(alarms); + +static ssize_t show_alarm(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct lm78_data *data = lm78_update_device(dev); + int nr = to_sensor_dev_attr(da)->index; + return sprintf(buf, "%u\n", (data->alarms >> nr) & 1); +} +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 4); + +static struct attribute *lm78_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in6_alarm.dev_attr.attr, + &dev_attr_temp1_input.attr, + &dev_attr_temp1_max.attr, + &dev_attr_temp1_max_hyst.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan3_div.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &dev_attr_alarms.attr, + &dev_attr_cpu0_vid.attr, + + NULL +}; + +ATTRIBUTE_GROUPS(lm78); + +/* + * ISA related code + */ +#ifdef CONFIG_ISA + +/* ISA device, if found */ +static struct platform_device *pdev; + +static unsigned short isa_address = 0x290; + +static struct lm78_data *lm78_data_if_isa(void) +{ + return pdev ? platform_get_drvdata(pdev) : NULL; +} + +/* Returns 1 if the I2C chip appears to be an alias of the ISA chip */ +static int lm78_alias_detect(struct i2c_client *client, u8 chipid) +{ + struct lm78_data *isa; + int i; + + if (!pdev) /* No ISA chip */ + return 0; + isa = platform_get_drvdata(pdev); + + if (lm78_read_value(isa, LM78_REG_I2C_ADDR) != client->addr) + return 0; /* Address doesn't match */ + if ((lm78_read_value(isa, LM78_REG_CHIPID) & 0xfe) != (chipid & 0xfe)) + return 0; /* Chip type doesn't match */ + + /* + * We compare all the limit registers, the config register and the + * interrupt mask registers + */ + for (i = 0x2b; i <= 0x3d; i++) { + if (lm78_read_value(isa, i) != + i2c_smbus_read_byte_data(client, i)) + return 0; + } + if (lm78_read_value(isa, LM78_REG_CONFIG) != + i2c_smbus_read_byte_data(client, LM78_REG_CONFIG)) + return 0; + for (i = 0x43; i <= 0x46; i++) { + if (lm78_read_value(isa, i) != + i2c_smbus_read_byte_data(client, i)) + return 0; + } + + return 1; +} +#else /* !CONFIG_ISA */ + +static int lm78_alias_detect(struct i2c_client *client, u8 chipid) +{ + return 0; +} + +static struct lm78_data *lm78_data_if_isa(void) +{ + return NULL; +} +#endif /* CONFIG_ISA */ + +static int lm78_i2c_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + int i; + struct lm78_data *isa = lm78_data_if_isa(); + const char *client_name; + struct i2c_adapter *adapter = client->adapter; + int address = client->addr; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* + * We block updates of the ISA device to minimize the risk of + * concurrent access to the same LM78 chip through different + * interfaces. + */ + if (isa) + mutex_lock(&isa->update_lock); + + if ((i2c_smbus_read_byte_data(client, LM78_REG_CONFIG) & 0x80) + || i2c_smbus_read_byte_data(client, LM78_REG_I2C_ADDR) != address) + goto err_nodev; + + /* Explicitly prevent the misdetection of Winbond chips */ + i = i2c_smbus_read_byte_data(client, 0x4f); + if (i == 0xa3 || i == 0x5c) + goto err_nodev; + + /* Determine the chip type. */ + i = i2c_smbus_read_byte_data(client, LM78_REG_CHIPID); + if (i == 0x00 || i == 0x20 /* LM78 */ + || i == 0x40) /* LM78-J */ + client_name = "lm78"; + else if ((i & 0xfe) == 0xc0) + client_name = "lm79"; + else + goto err_nodev; + + if (lm78_alias_detect(client, i)) { + dev_dbg(&adapter->dev, + "Device at 0x%02x appears to be the same as ISA device\n", + address); + goto err_nodev; + } + + if (isa) + mutex_unlock(&isa->update_lock); + + strlcpy(info->type, client_name, I2C_NAME_SIZE); + + return 0; + + err_nodev: + if (isa) + mutex_unlock(&isa->update_lock); + return -ENODEV; +} + +static int lm78_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct lm78_data *data; + + data = devm_kzalloc(dev, sizeof(struct lm78_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + data->type = id->driver_data; + + /* Initialize the LM78 chip */ + lm78_init_device(data); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, lm78_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id lm78_i2c_id[] = { + { "lm78", lm78 }, + { "lm79", lm79 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm78_i2c_id); + +static struct i2c_driver lm78_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm78", + }, + .probe = lm78_i2c_probe, + .id_table = lm78_i2c_id, + .detect = lm78_i2c_detect, + .address_list = normal_i2c, +}; + +/* + * The SMBus locks itself, but ISA access must be locked explicitly! + * We don't want to lock the whole ISA bus, so we lock each client + * separately. + * We ignore the LM78 BUSY flag at this moment - it could lead to deadlocks, + * would slow down the LM78 access and should not be necessary. + */ +static int lm78_read_value(struct lm78_data *data, u8 reg) +{ + struct i2c_client *client = data->client; + +#ifdef CONFIG_ISA + if (!client) { /* ISA device */ + int res; + mutex_lock(&data->lock); + outb_p(reg, data->isa_addr + LM78_ADDR_REG_OFFSET); + res = inb_p(data->isa_addr + LM78_DATA_REG_OFFSET); + mutex_unlock(&data->lock); + return res; + } else +#endif + return i2c_smbus_read_byte_data(client, reg); +} + +static int lm78_write_value(struct lm78_data *data, u8 reg, u8 value) +{ + struct i2c_client *client = data->client; + +#ifdef CONFIG_ISA + if (!client) { /* ISA device */ + mutex_lock(&data->lock); + outb_p(reg, data->isa_addr + LM78_ADDR_REG_OFFSET); + outb_p(value, data->isa_addr + LM78_DATA_REG_OFFSET); + mutex_unlock(&data->lock); + return 0; + } else +#endif + return i2c_smbus_write_byte_data(client, reg, value); +} + +static void lm78_init_device(struct lm78_data *data) +{ + u8 config; + int i; + + /* Start monitoring */ + config = lm78_read_value(data, LM78_REG_CONFIG); + if ((config & 0x09) != 0x01) + lm78_write_value(data, LM78_REG_CONFIG, + (config & 0xf7) | 0x01); + + /* A few vars need to be filled upon startup */ + for (i = 0; i < 3; i++) { + data->fan_min[i] = lm78_read_value(data, + LM78_REG_FAN_MIN(i)); + } + + mutex_init(&data->update_lock); +} + +static struct lm78_data *lm78_update_device(struct device *dev) +{ + struct lm78_data *data = dev_get_drvdata(dev); + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + + dev_dbg(dev, "Starting lm78 update\n"); + + for (i = 0; i <= 6; i++) { + data->in[i] = + lm78_read_value(data, LM78_REG_IN(i)); + data->in_min[i] = + lm78_read_value(data, LM78_REG_IN_MIN(i)); + data->in_max[i] = + lm78_read_value(data, LM78_REG_IN_MAX(i)); + } + for (i = 0; i < 3; i++) { + data->fan[i] = + lm78_read_value(data, LM78_REG_FAN(i)); + data->fan_min[i] = + lm78_read_value(data, LM78_REG_FAN_MIN(i)); + } + data->temp = lm78_read_value(data, LM78_REG_TEMP); + data->temp_over = + lm78_read_value(data, LM78_REG_TEMP_OVER); + data->temp_hyst = + lm78_read_value(data, LM78_REG_TEMP_HYST); + i = lm78_read_value(data, LM78_REG_VID_FANDIV); + data->vid = i & 0x0f; + if (data->type == lm79) + data->vid |= + (lm78_read_value(data, LM78_REG_CHIPID) & + 0x01) << 4; + else + data->vid |= 0x10; + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = i >> 6; + data->alarms = lm78_read_value(data, LM78_REG_ALARM1) + + (lm78_read_value(data, LM78_REG_ALARM2) << 8); + data->last_updated = jiffies; + data->valid = 1; + + data->fan_div[2] = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +#ifdef CONFIG_ISA +static int lm78_isa_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device *hwmon_dev; + struct lm78_data *data; + struct resource *res; + + /* Reserve the ISA region */ + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(dev, res->start + LM78_ADDR_REG_OFFSET, + 2, "lm78")) + return -EBUSY; + + data = devm_kzalloc(dev, sizeof(struct lm78_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->lock); + data->isa_addr = res->start; + platform_set_drvdata(pdev, data); + + if (lm78_read_value(data, LM78_REG_CHIPID) & 0x80) { + data->type = lm79; + data->name = "lm79"; + } else { + data->type = lm78; + data->name = "lm78"; + } + + /* Initialize the LM78 chip */ + lm78_init_device(data); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, data->name, + data, lm78_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct platform_driver lm78_isa_driver = { + .driver = { + .name = "lm78", + }, + .probe = lm78_isa_probe, +}; + +/* return 1 if a supported chip is found, 0 otherwise */ +static int __init lm78_isa_found(unsigned short address) +{ + int val, save, found = 0; + int port; + + /* + * Some boards declare base+0 to base+7 as a PNP device, some base+4 + * to base+7 and some base+5 to base+6. So we better request each port + * individually for the probing phase. + */ + for (port = address; port < address + LM78_EXTENT; port++) { + if (!request_region(port, 1, "lm78")) { + pr_debug("Failed to request port 0x%x\n", port); + goto release; + } + } + +#define REALLY_SLOW_IO + /* + * We need the timeouts for at least some LM78-like + * chips. But only if we read 'undefined' registers. + */ + val = inb_p(address + 1); + if (inb_p(address + 2) != val + || inb_p(address + 3) != val + || inb_p(address + 7) != val) + goto release; +#undef REALLY_SLOW_IO + + /* + * We should be able to change the 7 LSB of the address port. The + * MSB (busy flag) should be clear initially, set after the write. + */ + save = inb_p(address + LM78_ADDR_REG_OFFSET); + if (save & 0x80) + goto release; + val = ~save & 0x7f; + outb_p(val, address + LM78_ADDR_REG_OFFSET); + if (inb_p(address + LM78_ADDR_REG_OFFSET) != (val | 0x80)) { + outb_p(save, address + LM78_ADDR_REG_OFFSET); + goto release; + } + + /* We found a device, now see if it could be an LM78 */ + outb_p(LM78_REG_CONFIG, address + LM78_ADDR_REG_OFFSET); + val = inb_p(address + LM78_DATA_REG_OFFSET); + if (val & 0x80) + goto release; + outb_p(LM78_REG_I2C_ADDR, address + LM78_ADDR_REG_OFFSET); + val = inb_p(address + LM78_DATA_REG_OFFSET); + if (val < 0x03 || val > 0x77) /* Not a valid I2C address */ + goto release; + + /* The busy flag should be clear again */ + if (inb_p(address + LM78_ADDR_REG_OFFSET) & 0x80) + goto release; + + /* Explicitly prevent the misdetection of Winbond chips */ + outb_p(0x4f, address + LM78_ADDR_REG_OFFSET); + val = inb_p(address + LM78_DATA_REG_OFFSET); + if (val == 0xa3 || val == 0x5c) + goto release; + + /* Explicitly prevent the misdetection of ITE chips */ + outb_p(0x58, address + LM78_ADDR_REG_OFFSET); + val = inb_p(address + LM78_DATA_REG_OFFSET); + if (val == 0x90) + goto release; + + /* Determine the chip type */ + outb_p(LM78_REG_CHIPID, address + LM78_ADDR_REG_OFFSET); + val = inb_p(address + LM78_DATA_REG_OFFSET); + if (val == 0x00 || val == 0x20 /* LM78 */ + || val == 0x40 /* LM78-J */ + || (val & 0xfe) == 0xc0) /* LM79 */ + found = 1; + + if (found) + pr_info("Found an %s chip at %#x\n", + val & 0x80 ? "LM79" : "LM78", (int)address); + + release: + for (port--; port >= address; port--) + release_region(port, 1); + return found; +} + +static int __init lm78_isa_device_add(unsigned short address) +{ + struct resource res = { + .start = address, + .end = address + LM78_EXTENT - 1, + .name = "lm78", + .flags = IORESOURCE_IO, + }; + int err; + + pdev = platform_device_alloc("lm78", address); + if (!pdev) { + err = -ENOMEM; + pr_err("Device allocation failed\n"); + goto exit; + } + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + + exit_device_put: + platform_device_put(pdev); + exit: + pdev = NULL; + return err; +} + +static int __init lm78_isa_register(void) +{ + int res; + + if (lm78_isa_found(isa_address)) { + res = platform_driver_register(&lm78_isa_driver); + if (res) + goto exit; + + /* Sets global pdev as a side effect */ + res = lm78_isa_device_add(isa_address); + if (res) + goto exit_unreg_isa_driver; + } + + return 0; + + exit_unreg_isa_driver: + platform_driver_unregister(&lm78_isa_driver); + exit: + return res; +} + +static void lm78_isa_unregister(void) +{ + if (pdev) { + platform_device_unregister(pdev); + platform_driver_unregister(&lm78_isa_driver); + } +} +#else /* !CONFIG_ISA */ + +static int __init lm78_isa_register(void) +{ + return 0; +} + +static void lm78_isa_unregister(void) +{ +} +#endif /* CONFIG_ISA */ + +static int __init sm_lm78_init(void) +{ + int res; + + /* + * We register the ISA device first, so that we can skip the + * registration of an I2C interface to the same device. + */ + res = lm78_isa_register(); + if (res) + goto exit; + + res = i2c_add_driver(&lm78_driver); + if (res) + goto exit_unreg_isa_device; + + return 0; + + exit_unreg_isa_device: + lm78_isa_unregister(); + exit: + return res; +} + +static void __exit sm_lm78_exit(void) +{ + lm78_isa_unregister(); + i2c_del_driver(&lm78_driver); +} + +MODULE_AUTHOR("Frodo Looijaard, Jean Delvare "); +MODULE_DESCRIPTION("LM78/LM79 driver"); +MODULE_LICENSE("GPL"); + +module_init(sm_lm78_init); +module_exit(sm_lm78_exit); diff --git a/drivers/hwmon/lm80.c b/drivers/hwmon/lm80.c new file mode 100644 index 000000000..dc2bd82b3 --- /dev/null +++ b/drivers/hwmon/lm80.c @@ -0,0 +1,681 @@ +/* + * lm80.c - From lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 1998, 1999 Frodo Looijaard + * and Philip Edelbrock + * + * Ported to Linux 2.6 by Tiago Sousa + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, I2C_CLIENT_END }; + +/* Many LM80 constants specified below */ + +/* The LM80 registers */ +#define LM80_REG_IN_MAX(nr) (0x2a + (nr) * 2) +#define LM80_REG_IN_MIN(nr) (0x2b + (nr) * 2) +#define LM80_REG_IN(nr) (0x20 + (nr)) + +#define LM80_REG_FAN1 0x28 +#define LM80_REG_FAN2 0x29 +#define LM80_REG_FAN_MIN(nr) (0x3b + (nr)) + +#define LM80_REG_TEMP 0x27 +#define LM80_REG_TEMP_HOT_MAX 0x38 +#define LM80_REG_TEMP_HOT_HYST 0x39 +#define LM80_REG_TEMP_OS_MAX 0x3a +#define LM80_REG_TEMP_OS_HYST 0x3b + +#define LM80_REG_CONFIG 0x00 +#define LM80_REG_ALARM1 0x01 +#define LM80_REG_ALARM2 0x02 +#define LM80_REG_MASK1 0x03 +#define LM80_REG_MASK2 0x04 +#define LM80_REG_FANDIV 0x05 +#define LM80_REG_RES 0x06 + +#define LM96080_REG_CONV_RATE 0x07 +#define LM96080_REG_MAN_ID 0x3e +#define LM96080_REG_DEV_ID 0x3f + + +/* + * Conversions. Rounding and limit checking is only done on the TO_REG + * variants. Note that you should be a bit careful with which arguments + * these macros are called: arguments may be evaluated more than once. + * Fixing this is just not worth it. + */ + +#define IN_TO_REG(val) (clamp_val(((val) + 5) / 10, 0, 255)) +#define IN_FROM_REG(val) ((val) * 10) + +static inline unsigned char FAN_TO_REG(unsigned rpm, unsigned div) +{ + if (rpm == 0) + return 255; + rpm = clamp_val(rpm, 1, 1000000); + return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 254); +} + +#define FAN_FROM_REG(val, div) ((val) == 0 ? -1 : \ + (val) == 255 ? 0 : 1350000/((div) * (val))) + +#define TEMP_FROM_REG(reg) ((reg) * 125 / 32) +#define TEMP_TO_REG(temp) (DIV_ROUND_CLOSEST(clamp_val((temp), \ + -128000, 127000), 1000) << 8) + +#define DIV_FROM_REG(val) (1 << (val)) + +enum temp_index { + t_input = 0, + t_hot_max, + t_hot_hyst, + t_os_max, + t_os_hyst, + t_num_temp +}; + +static const u8 temp_regs[t_num_temp] = { + [t_input] = LM80_REG_TEMP, + [t_hot_max] = LM80_REG_TEMP_HOT_MAX, + [t_hot_hyst] = LM80_REG_TEMP_HOT_HYST, + [t_os_max] = LM80_REG_TEMP_OS_MAX, + [t_os_hyst] = LM80_REG_TEMP_OS_HYST, +}; + +enum in_index { + i_input = 0, + i_max, + i_min, + i_num_in +}; + +enum fan_index { + f_input, + f_min, + f_num_fan +}; + +/* + * Client data (each client gets its own) + */ + +struct lm80_data { + struct i2c_client *client; + struct mutex update_lock; + char error; /* !=0 if error occurred during last update */ + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[i_num_in][7]; /* Register value, 1st index is enum in_index */ + u8 fan[f_num_fan][2]; /* Register value, 1st index enum fan_index */ + u8 fan_div[2]; /* Register encoding, shifted right */ + s16 temp[t_num_temp]; /* Register values, normalized to 16 bit */ + u16 alarms; /* Register encoding, combined */ +}; + +static int lm80_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int lm80_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +/* Called when we have found a new LM80 and after read errors */ +static void lm80_init_client(struct i2c_client *client) +{ + /* + * Reset all except Watchdog values and last conversion values + * This sets fan-divs to 2, among others. This makes most other + * initializations unnecessary + */ + lm80_write_value(client, LM80_REG_CONFIG, 0x80); + /* Set 11-bit temperature resolution */ + lm80_write_value(client, LM80_REG_RES, 0x08); + + /* Start monitoring */ + lm80_write_value(client, LM80_REG_CONFIG, 0x01); +} + +static struct lm80_data *lm80_update_device(struct device *dev) +{ + struct lm80_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int i; + int rv; + int prev_rv; + struct lm80_data *ret = data; + + mutex_lock(&data->update_lock); + + if (data->error) + lm80_init_client(client); + + if (time_after(jiffies, data->last_updated + 2 * HZ) || !data->valid) { + dev_dbg(dev, "Starting lm80 update\n"); + for (i = 0; i <= 6; i++) { + rv = lm80_read_value(client, LM80_REG_IN(i)); + if (rv < 0) + goto abort; + data->in[i_input][i] = rv; + + rv = lm80_read_value(client, LM80_REG_IN_MIN(i)); + if (rv < 0) + goto abort; + data->in[i_min][i] = rv; + + rv = lm80_read_value(client, LM80_REG_IN_MAX(i)); + if (rv < 0) + goto abort; + data->in[i_max][i] = rv; + } + + rv = lm80_read_value(client, LM80_REG_FAN1); + if (rv < 0) + goto abort; + data->fan[f_input][0] = rv; + + rv = lm80_read_value(client, LM80_REG_FAN_MIN(1)); + if (rv < 0) + goto abort; + data->fan[f_min][0] = rv; + + rv = lm80_read_value(client, LM80_REG_FAN2); + if (rv < 0) + goto abort; + data->fan[f_input][1] = rv; + + rv = lm80_read_value(client, LM80_REG_FAN_MIN(2)); + if (rv < 0) + goto abort; + data->fan[f_min][1] = rv; + + prev_rv = rv = lm80_read_value(client, LM80_REG_TEMP); + if (rv < 0) + goto abort; + rv = lm80_read_value(client, LM80_REG_RES); + if (rv < 0) + goto abort; + data->temp[t_input] = (prev_rv << 8) | (rv & 0xf0); + + for (i = t_input + 1; i < t_num_temp; i++) { + rv = lm80_read_value(client, temp_regs[i]); + if (rv < 0) + goto abort; + data->temp[i] = rv << 8; + } + + rv = lm80_read_value(client, LM80_REG_FANDIV); + if (rv < 0) + goto abort; + data->fan_div[0] = (rv >> 2) & 0x03; + data->fan_div[1] = (rv >> 4) & 0x03; + + prev_rv = rv = lm80_read_value(client, LM80_REG_ALARM1); + if (rv < 0) + goto abort; + rv = lm80_read_value(client, LM80_REG_ALARM2); + if (rv < 0) + goto abort; + data->alarms = prev_rv + (rv << 8); + + data->last_updated = jiffies; + data->valid = 1; + data->error = 0; + } + goto done; + +abort: + ret = ERR_PTR(rv); + data->valid = 0; + data->error = 1; + +done: + mutex_unlock(&data->update_lock); + + return ret; +} + +/* + * Sysfs stuff + */ + +static ssize_t show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm80_data *data = lm80_update_device(dev); + int index = to_sensor_dev_attr_2(attr)->index; + int nr = to_sensor_dev_attr_2(attr)->nr; + + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", IN_FROM_REG(data->in[nr][index])); +} + +static ssize_t set_in(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm80_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int index = to_sensor_dev_attr_2(attr)->index; + int nr = to_sensor_dev_attr_2(attr)->nr; + long val; + u8 reg; + int err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + reg = nr == i_min ? LM80_REG_IN_MIN(index) : LM80_REG_IN_MAX(index); + + mutex_lock(&data->update_lock); + data->in[nr][index] = IN_TO_REG(val); + lm80_write_value(client, reg, data->in[nr][index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_fan(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int index = to_sensor_dev_attr_2(attr)->index; + int nr = to_sensor_dev_attr_2(attr)->nr; + struct lm80_data *data = lm80_update_device(dev); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr][index], + DIV_FROM_REG(data->fan_div[index]))); +} + +static ssize_t show_fan_div(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm80_data *data = lm80_update_device(dev); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[nr])); +} + +static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int index = to_sensor_dev_attr_2(attr)->index; + int nr = to_sensor_dev_attr_2(attr)->nr; + struct lm80_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + mutex_lock(&data->update_lock); + data->fan[nr][index] = FAN_TO_REG(val, + DIV_FROM_REG(data->fan_div[index])); + lm80_write_value(client, LM80_REG_FAN_MIN(index + 1), + data->fan[nr][index]); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Note: we save and restore the fan minimum here, because its value is + * determined in part by the fan divisor. This follows the principle of + * least surprise; the user doesn't expect the fan minimum to change just + * because the divisor changed. + */ +static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm80_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long min, val; + u8 reg; + int rv; + + rv = kstrtoul(buf, 10, &val); + if (rv < 0) + return rv; + + /* Save fan_min */ + mutex_lock(&data->update_lock); + min = FAN_FROM_REG(data->fan[f_min][nr], + DIV_FROM_REG(data->fan_div[nr])); + + switch (val) { + case 1: + data->fan_div[nr] = 0; + break; + case 2: + data->fan_div[nr] = 1; + break; + case 4: + data->fan_div[nr] = 2; + break; + case 8: + data->fan_div[nr] = 3; + break; + default: + dev_err(dev, + "fan_div value %ld not supported. Choose one of 1, 2, 4 or 8!\n", + val); + mutex_unlock(&data->update_lock); + return -EINVAL; + } + + rv = lm80_read_value(client, LM80_REG_FANDIV); + if (rv < 0) { + mutex_unlock(&data->update_lock); + return rv; + } + reg = (rv & ~(3 << (2 * (nr + 1)))) + | (data->fan_div[nr] << (2 * (nr + 1))); + lm80_write_value(client, LM80_REG_FANDIV, reg); + + /* Restore fan_min */ + data->fan[f_min][nr] = FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr])); + lm80_write_value(client, LM80_REG_FAN_MIN(nr + 1), + data->fan[f_min][nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm80_data *data = lm80_update_device(dev); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index])); +} + +static ssize_t set_temp(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm80_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = attr->index; + long val; + int err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + mutex_lock(&data->update_lock); + data->temp[nr] = TEMP_TO_REG(val); + lm80_write_value(client, temp_regs[nr], data->temp[nr] >> 8); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm80_data *data = lm80_update_device(dev); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%u\n", data->alarms); +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct lm80_data *data = lm80_update_device(dev); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1); +} + +static SENSOR_DEVICE_ATTR_2(in0_min, S_IWUSR | S_IRUGO, + show_in, set_in, i_min, 0); +static SENSOR_DEVICE_ATTR_2(in1_min, S_IWUSR | S_IRUGO, + show_in, set_in, i_min, 1); +static SENSOR_DEVICE_ATTR_2(in2_min, S_IWUSR | S_IRUGO, + show_in, set_in, i_min, 2); +static SENSOR_DEVICE_ATTR_2(in3_min, S_IWUSR | S_IRUGO, + show_in, set_in, i_min, 3); +static SENSOR_DEVICE_ATTR_2(in4_min, S_IWUSR | S_IRUGO, + show_in, set_in, i_min, 4); +static SENSOR_DEVICE_ATTR_2(in5_min, S_IWUSR | S_IRUGO, + show_in, set_in, i_min, 5); +static SENSOR_DEVICE_ATTR_2(in6_min, S_IWUSR | S_IRUGO, + show_in, set_in, i_min, 6); +static SENSOR_DEVICE_ATTR_2(in0_max, S_IWUSR | S_IRUGO, + show_in, set_in, i_max, 0); +static SENSOR_DEVICE_ATTR_2(in1_max, S_IWUSR | S_IRUGO, + show_in, set_in, i_max, 1); +static SENSOR_DEVICE_ATTR_2(in2_max, S_IWUSR | S_IRUGO, + show_in, set_in, i_max, 2); +static SENSOR_DEVICE_ATTR_2(in3_max, S_IWUSR | S_IRUGO, + show_in, set_in, i_max, 3); +static SENSOR_DEVICE_ATTR_2(in4_max, S_IWUSR | S_IRUGO, + show_in, set_in, i_max, 4); +static SENSOR_DEVICE_ATTR_2(in5_max, S_IWUSR | S_IRUGO, + show_in, set_in, i_max, 5); +static SENSOR_DEVICE_ATTR_2(in6_max, S_IWUSR | S_IRUGO, + show_in, set_in, i_max, 6); +static SENSOR_DEVICE_ATTR_2(in0_input, S_IRUGO, show_in, NULL, i_input, 0); +static SENSOR_DEVICE_ATTR_2(in1_input, S_IRUGO, show_in, NULL, i_input, 1); +static SENSOR_DEVICE_ATTR_2(in2_input, S_IRUGO, show_in, NULL, i_input, 2); +static SENSOR_DEVICE_ATTR_2(in3_input, S_IRUGO, show_in, NULL, i_input, 3); +static SENSOR_DEVICE_ATTR_2(in4_input, S_IRUGO, show_in, NULL, i_input, 4); +static SENSOR_DEVICE_ATTR_2(in5_input, S_IRUGO, show_in, NULL, i_input, 5); +static SENSOR_DEVICE_ATTR_2(in6_input, S_IRUGO, show_in, NULL, i_input, 6); +static SENSOR_DEVICE_ATTR_2(fan1_min, S_IWUSR | S_IRUGO, + show_fan, set_fan_min, f_min, 0); +static SENSOR_DEVICE_ATTR_2(fan2_min, S_IWUSR | S_IRUGO, + show_fan, set_fan_min, f_min, 1); +static SENSOR_DEVICE_ATTR_2(fan1_input, S_IRUGO, show_fan, NULL, f_input, 0); +static SENSOR_DEVICE_ATTR_2(fan2_input, S_IRUGO, show_fan, NULL, f_input, 1); +static SENSOR_DEVICE_ATTR(fan1_div, S_IWUSR | S_IRUGO, + show_fan_div, set_fan_div, 0); +static SENSOR_DEVICE_ATTR(fan2_div, S_IWUSR | S_IRUGO, + show_fan_div, set_fan_div, 1); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, t_input); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp, + set_temp, t_hot_max); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, show_temp, + set_temp, t_hot_hyst); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, show_temp, + set_temp, t_os_max); +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO, show_temp, + set_temp, t_os_hyst); +static DEVICE_ATTR_RO(alarms); +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 13); + +/* + * Real code + */ + +static struct attribute *lm80_attrs[] = { + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, + &dev_attr_alarms.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + &sensor_dev_attr_in6_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(lm80); + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int lm80_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int i, cur, man_id, dev_id; + const char *name = NULL; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* First check for unused bits, common to both chip types */ + if ((lm80_read_value(client, LM80_REG_ALARM2) & 0xc0) + || (lm80_read_value(client, LM80_REG_CONFIG) & 0x80)) + return -ENODEV; + + /* + * The LM96080 has manufacturer and stepping/die rev registers so we + * can just check that. The LM80 does not have such registers so we + * have to use a more expensive trick. + */ + man_id = lm80_read_value(client, LM96080_REG_MAN_ID); + dev_id = lm80_read_value(client, LM96080_REG_DEV_ID); + if (man_id == 0x01 && dev_id == 0x08) { + /* Check more unused bits for confirmation */ + if (lm80_read_value(client, LM96080_REG_CONV_RATE) & 0xfe) + return -ENODEV; + + name = "lm96080"; + } else { + /* Check 6-bit addressing */ + for (i = 0x2a; i <= 0x3d; i++) { + cur = i2c_smbus_read_byte_data(client, i); + if ((i2c_smbus_read_byte_data(client, i + 0x40) != cur) + || (i2c_smbus_read_byte_data(client, i + 0x80) != cur) + || (i2c_smbus_read_byte_data(client, i + 0xc0) != cur)) + return -ENODEV; + } + + name = "lm80"; + } + + strlcpy(info->type, name, I2C_NAME_SIZE); + + return 0; +} + +static int lm80_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct lm80_data *data; + + data = devm_kzalloc(dev, sizeof(struct lm80_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* Initialize the LM80 chip */ + lm80_init_client(client); + + /* A few vars need to be filled upon startup */ + data->fan[f_min][0] = lm80_read_value(client, LM80_REG_FAN_MIN(1)); + data->fan[f_min][1] = lm80_read_value(client, LM80_REG_FAN_MIN(2)); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, lm80_groups); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +/* + * Driver data (common to all clients) + */ + +static const struct i2c_device_id lm80_id[] = { + { "lm80", 0 }, + { "lm96080", 1 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm80_id); + +static struct i2c_driver lm80_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm80", + }, + .probe = lm80_probe, + .id_table = lm80_id, + .detect = lm80_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(lm80_driver); + +MODULE_AUTHOR("Frodo Looijaard and " + "Philip Edelbrock "); +MODULE_DESCRIPTION("LM80 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c new file mode 100644 index 000000000..cbfd0bb7f --- /dev/null +++ b/drivers/hwmon/lm83.c @@ -0,0 +1,389 @@ +/* + * lm83.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 2003-2009 Jean Delvare + * + * Heavily inspired from the lm78, lm75 and adm1021 drivers. The LM83 is + * a sensor chip made by National Semiconductor. It reports up to four + * temperatures (its own plus up to three external ones) with a 1 deg + * resolution and a 3-4 deg accuracy. Complete datasheet can be obtained + * from National's website at: + * http://www.national.com/pf/LM/LM83.html + * Since the datasheet omits to give the chip stepping code, I give it + * here: 0x03 (at register 0xff). + * + * Also supports the LM82 temp sensor, which is basically a stripped down + * model of the LM83. Datasheet is here: + * http://www.national.com/pf/LM/LM82.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Addresses to scan + * Address is selected using 2 three-level pins, resulting in 9 possible + * addresses. + */ + +static const unsigned short normal_i2c[] = { + 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, 0x4c, 0x4d, 0x4e, I2C_CLIENT_END }; + +enum chips { lm83, lm82 }; + +/* + * The LM83 registers + * Manufacturer ID is 0x01 for National Semiconductor. + */ + +#define LM83_REG_R_MAN_ID 0xFE +#define LM83_REG_R_CHIP_ID 0xFF +#define LM83_REG_R_CONFIG 0x03 +#define LM83_REG_W_CONFIG 0x09 +#define LM83_REG_R_STATUS1 0x02 +#define LM83_REG_R_STATUS2 0x35 +#define LM83_REG_R_LOCAL_TEMP 0x00 +#define LM83_REG_R_LOCAL_HIGH 0x05 +#define LM83_REG_W_LOCAL_HIGH 0x0B +#define LM83_REG_R_REMOTE1_TEMP 0x30 +#define LM83_REG_R_REMOTE1_HIGH 0x38 +#define LM83_REG_W_REMOTE1_HIGH 0x50 +#define LM83_REG_R_REMOTE2_TEMP 0x01 +#define LM83_REG_R_REMOTE2_HIGH 0x07 +#define LM83_REG_W_REMOTE2_HIGH 0x0D +#define LM83_REG_R_REMOTE3_TEMP 0x31 +#define LM83_REG_R_REMOTE3_HIGH 0x3A +#define LM83_REG_W_REMOTE3_HIGH 0x52 +#define LM83_REG_R_TCRIT 0x42 +#define LM83_REG_W_TCRIT 0x5A + +/* + * Conversions and various macros + * The LM83 uses signed 8-bit values with LSB = 1 degree Celsius. + */ + +#define TEMP_FROM_REG(val) ((val) * 1000) +#define TEMP_TO_REG(val) ((val) <= -128000 ? -128 : \ + (val) >= 127000 ? 127 : \ + (val) < 0 ? ((val) - 500) / 1000 : \ + ((val) + 500) / 1000) + +static const u8 LM83_REG_R_TEMP[] = { + LM83_REG_R_LOCAL_TEMP, + LM83_REG_R_REMOTE1_TEMP, + LM83_REG_R_REMOTE2_TEMP, + LM83_REG_R_REMOTE3_TEMP, + LM83_REG_R_LOCAL_HIGH, + LM83_REG_R_REMOTE1_HIGH, + LM83_REG_R_REMOTE2_HIGH, + LM83_REG_R_REMOTE3_HIGH, + LM83_REG_R_TCRIT, +}; + +static const u8 LM83_REG_W_HIGH[] = { + LM83_REG_W_LOCAL_HIGH, + LM83_REG_W_REMOTE1_HIGH, + LM83_REG_W_REMOTE2_HIGH, + LM83_REG_W_REMOTE3_HIGH, + LM83_REG_W_TCRIT, +}; + +/* + * Client data (each client gets its own) + */ + +struct lm83_data { + struct i2c_client *client; + const struct attribute_group *groups[3]; + struct mutex update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* registers values */ + s8 temp[9]; /* 0..3: input 1-4, + 4..7: high limit 1-4, + 8 : critical limit */ + u16 alarms; /* bitvector, combined */ +}; + +static struct lm83_data *lm83_update_device(struct device *dev) +{ + struct lm83_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ * 2) || !data->valid) { + int nr; + + dev_dbg(&client->dev, "Updating lm83 data.\n"); + for (nr = 0; nr < 9; nr++) { + data->temp[nr] = + i2c_smbus_read_byte_data(client, + LM83_REG_R_TEMP[nr]); + } + data->alarms = + i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS1) + + (i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS2) + << 8); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Sysfs stuff + */ + +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm83_data *data = lm83_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index])); +} + +static ssize_t set_temp(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm83_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int nr = attr->index; + int err; + + err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + mutex_lock(&data->update_lock); + data->temp[nr] = TEMP_TO_REG(val); + i2c_smbus_write_byte_data(client, LM83_REG_W_HIGH[nr - 4], + data->temp[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t alarms_show(struct device *dev, struct device_attribute *dummy, + char *buf) +{ + struct lm83_data *data = lm83_update_device(dev); + return sprintf(buf, "%d\n", data->alarms); +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm83_data *data = lm83_update_device(dev); + int bitnr = attr->index; + + return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp, + set_temp, 4); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp, + set_temp, 5); +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_temp, + set_temp, 6); +static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_temp, + set_temp, 7); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp, NULL, 8); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO, show_temp, NULL, 8); +static SENSOR_DEVICE_ATTR(temp3_crit, S_IWUSR | S_IRUGO, show_temp, + set_temp, 8); +static SENSOR_DEVICE_ATTR(temp4_crit, S_IRUGO, show_temp, NULL, 8); + +/* Individual alarm files */ +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp3_crit_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(temp4_crit_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_alarm, NULL, 12); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 13); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 15); +/* Raw alarm file for compatibility */ +static DEVICE_ATTR_RO(alarms); + +static struct attribute *lm83_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &dev_attr_alarms.attr, + NULL +}; + +static const struct attribute_group lm83_group = { + .attrs = lm83_attributes, +}; + +static struct attribute *lm83_attributes_opt[] = { + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp4_crit.dev_attr.attr, + + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm83_group_opt = { + .attrs = lm83_attributes_opt, +}; + +/* + * Real code + */ + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int lm83_detect(struct i2c_client *new_client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = new_client->adapter; + const char *name; + u8 man_id, chip_id; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* Detection */ + if ((i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS1) & 0xA8) || + (i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS2) & 0x48) || + (i2c_smbus_read_byte_data(new_client, LM83_REG_R_CONFIG) & 0x41)) { + dev_dbg(&adapter->dev, "LM83 detection failed at 0x%02x\n", + new_client->addr); + return -ENODEV; + } + + /* Identification */ + man_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_MAN_ID); + if (man_id != 0x01) /* National Semiconductor */ + return -ENODEV; + + chip_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_CHIP_ID); + switch (chip_id) { + case 0x03: + name = "lm83"; + break; + case 0x01: + name = "lm82"; + break; + default: + /* identification failed */ + dev_info(&adapter->dev, + "Unsupported chip (man_id=0x%02X, chip_id=0x%02X)\n", + man_id, chip_id); + return -ENODEV; + } + + strlcpy(info->type, name, I2C_NAME_SIZE); + + return 0; +} + +static int lm83_probe(struct i2c_client *new_client, + const struct i2c_device_id *id) +{ + struct device *hwmon_dev; + struct lm83_data *data; + + data = devm_kzalloc(&new_client->dev, sizeof(struct lm83_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = new_client; + mutex_init(&data->update_lock); + + /* + * Register sysfs hooks + * The LM82 can only monitor one external diode which is + * at the same register as the LM83 temp3 entry - so we + * declare 1 and 3 common, and then 2 and 4 only for the LM83. + */ + data->groups[0] = &lm83_group; + if (id->driver_data == lm83) + data->groups[1] = &lm83_group_opt; + + hwmon_dev = devm_hwmon_device_register_with_groups(&new_client->dev, + new_client->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +/* + * Driver data (common to all clients) + */ + +static const struct i2c_device_id lm83_id[] = { + { "lm83", lm83 }, + { "lm82", lm82 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm83_id); + +static struct i2c_driver lm83_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm83", + }, + .probe = lm83_probe, + .id_table = lm83_id, + .detect = lm83_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(lm83_driver); + +MODULE_AUTHOR("Jean Delvare "); +MODULE_DESCRIPTION("LM83 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm85.c b/drivers/hwmon/lm85.c new file mode 100644 index 000000000..0a325878e --- /dev/null +++ b/drivers/hwmon/lm85.c @@ -0,0 +1,1697 @@ +/* + * lm85.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (c) 1998, 1999 Frodo Looijaard + * Copyright (c) 2002, 2003 Philip Pokorny + * Copyright (c) 2003 Margit Schubert-While + * Copyright (c) 2004 Justin Thiessen + * Copyright (C) 2007--2014 Jean Delvare + * + * Chip details at + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END }; + +enum chips { + lm85, + adm1027, adt7463, adt7468, + emc6d100, emc6d102, emc6d103, emc6d103s +}; + +/* The LM85 registers */ + +#define LM85_REG_IN(nr) (0x20 + (nr)) +#define LM85_REG_IN_MIN(nr) (0x44 + (nr) * 2) +#define LM85_REG_IN_MAX(nr) (0x45 + (nr) * 2) + +#define LM85_REG_TEMP(nr) (0x25 + (nr)) +#define LM85_REG_TEMP_MIN(nr) (0x4e + (nr) * 2) +#define LM85_REG_TEMP_MAX(nr) (0x4f + (nr) * 2) + +/* Fan speeds are LSB, MSB (2 bytes) */ +#define LM85_REG_FAN(nr) (0x28 + (nr) * 2) +#define LM85_REG_FAN_MIN(nr) (0x54 + (nr) * 2) + +#define LM85_REG_PWM(nr) (0x30 + (nr)) + +#define LM85_REG_COMPANY 0x3e +#define LM85_REG_VERSTEP 0x3f + +#define ADT7468_REG_CFG5 0x7c +#define ADT7468_OFF64 (1 << 0) +#define ADT7468_HFPWM (1 << 1) +#define IS_ADT7468_OFF64(data) \ + ((data)->type == adt7468 && !((data)->cfg5 & ADT7468_OFF64)) +#define IS_ADT7468_HFPWM(data) \ + ((data)->type == adt7468 && !((data)->cfg5 & ADT7468_HFPWM)) + +/* These are the recognized values for the above regs */ +#define LM85_COMPANY_NATIONAL 0x01 +#define LM85_COMPANY_ANALOG_DEV 0x41 +#define LM85_COMPANY_SMSC 0x5c +#define LM85_VERSTEP_LM85C 0x60 +#define LM85_VERSTEP_LM85B 0x62 +#define LM85_VERSTEP_LM96000_1 0x68 +#define LM85_VERSTEP_LM96000_2 0x69 +#define LM85_VERSTEP_ADM1027 0x60 +#define LM85_VERSTEP_ADT7463 0x62 +#define LM85_VERSTEP_ADT7463C 0x6A +#define LM85_VERSTEP_ADT7468_1 0x71 +#define LM85_VERSTEP_ADT7468_2 0x72 +#define LM85_VERSTEP_EMC6D100_A0 0x60 +#define LM85_VERSTEP_EMC6D100_A1 0x61 +#define LM85_VERSTEP_EMC6D102 0x65 +#define LM85_VERSTEP_EMC6D103_A0 0x68 +#define LM85_VERSTEP_EMC6D103_A1 0x69 +#define LM85_VERSTEP_EMC6D103S 0x6A /* Also known as EMC6D103:A2 */ + +#define LM85_REG_CONFIG 0x40 + +#define LM85_REG_ALARM1 0x41 +#define LM85_REG_ALARM2 0x42 + +#define LM85_REG_VID 0x43 + +/* Automated FAN control */ +#define LM85_REG_AFAN_CONFIG(nr) (0x5c + (nr)) +#define LM85_REG_AFAN_RANGE(nr) (0x5f + (nr)) +#define LM85_REG_AFAN_SPIKE1 0x62 +#define LM85_REG_AFAN_MINPWM(nr) (0x64 + (nr)) +#define LM85_REG_AFAN_LIMIT(nr) (0x67 + (nr)) +#define LM85_REG_AFAN_CRITICAL(nr) (0x6a + (nr)) +#define LM85_REG_AFAN_HYST1 0x6d +#define LM85_REG_AFAN_HYST2 0x6e + +#define ADM1027_REG_EXTEND_ADC1 0x76 +#define ADM1027_REG_EXTEND_ADC2 0x77 + +#define EMC6D100_REG_ALARM3 0x7d +/* IN5, IN6 and IN7 */ +#define EMC6D100_REG_IN(nr) (0x70 + ((nr) - 5)) +#define EMC6D100_REG_IN_MIN(nr) (0x73 + ((nr) - 5) * 2) +#define EMC6D100_REG_IN_MAX(nr) (0x74 + ((nr) - 5) * 2) +#define EMC6D102_REG_EXTEND_ADC1 0x85 +#define EMC6D102_REG_EXTEND_ADC2 0x86 +#define EMC6D102_REG_EXTEND_ADC3 0x87 +#define EMC6D102_REG_EXTEND_ADC4 0x88 + +/* + * Conversions. Rounding and limit checking is only done on the TO_REG + * variants. Note that you should be a bit careful with which arguments + * these macros are called: arguments may be evaluated more than once. + */ + +/* IN are scaled according to built-in resistors */ +static const int lm85_scaling[] = { /* .001 Volts */ + 2500, 2250, 3300, 5000, 12000, + 3300, 1500, 1800 /*EMC6D100*/ +}; +#define SCALE(val, from, to) (((val) * (to) + ((from) / 2)) / (from)) + +#define INS_TO_REG(n, val) \ + SCALE(clamp_val(val, 0, 255 * lm85_scaling[n] / 192), \ + lm85_scaling[n], 192) + +#define INSEXT_FROM_REG(n, val, ext) \ + SCALE(((val) << 4) + (ext), 192 << 4, lm85_scaling[n]) + +#define INS_FROM_REG(n, val) SCALE((val), 192, lm85_scaling[n]) + +/* FAN speed is measured using 90kHz clock */ +static inline u16 FAN_TO_REG(unsigned long val) +{ + if (!val) + return 0xffff; + return clamp_val(5400000 / val, 1, 0xfffe); +} +#define FAN_FROM_REG(val) ((val) == 0 ? -1 : (val) == 0xffff ? 0 : \ + 5400000 / (val)) + +/* Temperature is reported in .001 degC increments */ +#define TEMP_TO_REG(val) \ + DIV_ROUND_CLOSEST(clamp_val((val), -127000, 127000), 1000) +#define TEMPEXT_FROM_REG(val, ext) \ + SCALE(((val) << 4) + (ext), 16, 1000) +#define TEMP_FROM_REG(val) ((val) * 1000) + +#define PWM_TO_REG(val) clamp_val(val, 0, 255) +#define PWM_FROM_REG(val) (val) + + +/* + * ZONEs have the following parameters: + * Limit (low) temp, 1. degC + * Hysteresis (below limit), 1. degC (0-15) + * Range of speed control, .1 degC (2-80) + * Critical (high) temp, 1. degC + * + * FAN PWMs have the following parameters: + * Reference Zone, 1, 2, 3, etc. + * Spinup time, .05 sec + * PWM value at limit/low temp, 1 count + * PWM Frequency, 1. Hz + * PWM is Min or OFF below limit, flag + * Invert PWM output, flag + * + * Some chips filter the temp, others the fan. + * Filter constant (or disabled) .1 seconds + */ + +/* These are the zone temperature range encodings in .001 degree C */ +static const int lm85_range_map[] = { + 2000, 2500, 3300, 4000, 5000, 6600, 8000, 10000, + 13300, 16000, 20000, 26600, 32000, 40000, 53300, 80000 +}; + +static int RANGE_TO_REG(long range) +{ + return find_closest(range, lm85_range_map, ARRAY_SIZE(lm85_range_map)); +} +#define RANGE_FROM_REG(val) lm85_range_map[(val) & 0x0f] + +/* These are the PWM frequency encodings */ +static const int lm85_freq_map[8] = { /* 1 Hz */ + 10, 15, 23, 30, 38, 47, 61, 94 +}; +static const int adm1027_freq_map[8] = { /* 1 Hz */ + 11, 15, 22, 29, 35, 44, 59, 88 +}; +#define FREQ_MAP_LEN 8 + +static int FREQ_TO_REG(const int *map, + unsigned int map_size, unsigned long freq) +{ + return find_closest(freq, map, map_size); +} + +static int FREQ_FROM_REG(const int *map, u8 reg) +{ + return map[reg & 0x07]; +} + +/* + * Since we can't use strings, I'm abusing these numbers + * to stand in for the following meanings: + * 1 -- PWM responds to Zone 1 + * 2 -- PWM responds to Zone 2 + * 3 -- PWM responds to Zone 3 + * 23 -- PWM responds to the higher temp of Zone 2 or 3 + * 123 -- PWM responds to highest of Zone 1, 2, or 3 + * 0 -- PWM is always at 0% (ie, off) + * -1 -- PWM is always at 100% + * -2 -- PWM responds to manual control + */ + +static const int lm85_zone_map[] = { 1, 2, 3, -1, 0, 23, 123, -2 }; +#define ZONE_FROM_REG(val) lm85_zone_map[(val) >> 5] + +static int ZONE_TO_REG(int zone) +{ + int i; + + for (i = 0; i <= 7; ++i) + if (zone == lm85_zone_map[i]) + break; + if (i > 7) /* Not found. */ + i = 3; /* Always 100% */ + return i << 5; +} + +#define HYST_TO_REG(val) clamp_val(((val) + 500) / 1000, 0, 15) +#define HYST_FROM_REG(val) ((val) * 1000) + +/* + * Chip sampling rates + * + * Some sensors are not updated more frequently than once per second + * so it doesn't make sense to read them more often than that. + * We cache the results and return the saved data if the driver + * is called again before a second has elapsed. + * + * Also, there is significant configuration data for this chip + * given the automatic PWM fan control that is possible. There + * are about 47 bytes of config data to only 22 bytes of actual + * readings. So, we keep the config data up to date in the cache + * when it is written and only sample it once every 1 *minute* + */ +#define LM85_DATA_INTERVAL (HZ + HZ / 2) +#define LM85_CONFIG_INTERVAL (1 * 60 * HZ) + +/* + * LM85 can automatically adjust fan speeds based on temperature + * This structure encapsulates an entire Zone config. There are + * three zones (one for each temperature input) on the lm85 + */ +struct lm85_zone { + s8 limit; /* Low temp limit */ + u8 hyst; /* Low limit hysteresis. (0-15) */ + u8 range; /* Temp range, encoded */ + s8 critical; /* "All fans ON" temp limit */ + u8 max_desired; /* + * Actual "max" temperature specified. Preserved + * to prevent "drift" as other autofan control + * values change. + */ +}; + +struct lm85_autofan { + u8 config; /* Register value */ + u8 min_pwm; /* Minimum PWM value, encoded */ + u8 min_off; /* Min PWM or OFF below "limit", flag */ +}; + +/* + * For each registered chip, we need to keep some data in memory. + * The structure is dynamically allocated. + */ +struct lm85_data { + struct i2c_client *client; + const struct attribute_group *groups[6]; + const int *freq_map; + enum chips type; + + bool has_vid5; /* true if VID5 is configured for ADT7463 or ADT7468 */ + + struct mutex update_lock; + int valid; /* !=0 if following fields are valid */ + unsigned long last_reading; /* In jiffies */ + unsigned long last_config; /* In jiffies */ + + u8 in[8]; /* Register value */ + u8 in_max[8]; /* Register value */ + u8 in_min[8]; /* Register value */ + s8 temp[3]; /* Register value */ + s8 temp_min[3]; /* Register value */ + s8 temp_max[3]; /* Register value */ + u16 fan[4]; /* Register value */ + u16 fan_min[4]; /* Register value */ + u8 pwm[3]; /* Register value */ + u8 pwm_freq[3]; /* Register encoding */ + u8 temp_ext[3]; /* Decoded values */ + u8 in_ext[8]; /* Decoded values */ + u8 vid; /* Register value */ + u8 vrm; /* VRM version */ + u32 alarms; /* Register encoding, combined */ + u8 cfg5; /* Config Register 5 on ADT7468 */ + struct lm85_autofan autofan[3]; + struct lm85_zone zone[3]; +}; + +static int lm85_read_value(struct i2c_client *client, u8 reg) +{ + int res; + + /* What size location is it? */ + switch (reg) { + case LM85_REG_FAN(0): /* Read WORD data */ + case LM85_REG_FAN(1): + case LM85_REG_FAN(2): + case LM85_REG_FAN(3): + case LM85_REG_FAN_MIN(0): + case LM85_REG_FAN_MIN(1): + case LM85_REG_FAN_MIN(2): + case LM85_REG_FAN_MIN(3): + case LM85_REG_ALARM1: /* Read both bytes at once */ + res = i2c_smbus_read_byte_data(client, reg) & 0xff; + res |= i2c_smbus_read_byte_data(client, reg + 1) << 8; + break; + default: /* Read BYTE data */ + res = i2c_smbus_read_byte_data(client, reg); + break; + } + + return res; +} + +static void lm85_write_value(struct i2c_client *client, u8 reg, int value) +{ + switch (reg) { + case LM85_REG_FAN(0): /* Write WORD data */ + case LM85_REG_FAN(1): + case LM85_REG_FAN(2): + case LM85_REG_FAN(3): + case LM85_REG_FAN_MIN(0): + case LM85_REG_FAN_MIN(1): + case LM85_REG_FAN_MIN(2): + case LM85_REG_FAN_MIN(3): + /* NOTE: ALARM is read only, so not included here */ + i2c_smbus_write_byte_data(client, reg, value & 0xff); + i2c_smbus_write_byte_data(client, reg + 1, value >> 8); + break; + default: /* Write BYTE data */ + i2c_smbus_write_byte_data(client, reg, value); + break; + } +} + +static struct lm85_data *lm85_update_device(struct device *dev) +{ + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int i; + + mutex_lock(&data->update_lock); + + if (!data->valid || + time_after(jiffies, data->last_reading + LM85_DATA_INTERVAL)) { + /* Things that change quickly */ + dev_dbg(&client->dev, "Reading sensor values\n"); + + /* + * Have to read extended bits first to "freeze" the + * more significant bits that are read later. + * There are 2 additional resolution bits per channel and we + * have room for 4, so we shift them to the left. + */ + if (data->type == adm1027 || data->type == adt7463 || + data->type == adt7468) { + int ext1 = lm85_read_value(client, + ADM1027_REG_EXTEND_ADC1); + int ext2 = lm85_read_value(client, + ADM1027_REG_EXTEND_ADC2); + int val = (ext1 << 8) + ext2; + + for (i = 0; i <= 4; i++) + data->in_ext[i] = + ((val >> (i * 2)) & 0x03) << 2; + + for (i = 0; i <= 2; i++) + data->temp_ext[i] = + (val >> ((i + 4) * 2)) & 0x0c; + } + + data->vid = lm85_read_value(client, LM85_REG_VID); + + for (i = 0; i <= 3; ++i) { + data->in[i] = + lm85_read_value(client, LM85_REG_IN(i)); + data->fan[i] = + lm85_read_value(client, LM85_REG_FAN(i)); + } + + if (!data->has_vid5) + data->in[4] = lm85_read_value(client, LM85_REG_IN(4)); + + if (data->type == adt7468) + data->cfg5 = lm85_read_value(client, ADT7468_REG_CFG5); + + for (i = 0; i <= 2; ++i) { + data->temp[i] = + lm85_read_value(client, LM85_REG_TEMP(i)); + data->pwm[i] = + lm85_read_value(client, LM85_REG_PWM(i)); + + if (IS_ADT7468_OFF64(data)) + data->temp[i] -= 64; + } + + data->alarms = lm85_read_value(client, LM85_REG_ALARM1); + + if (data->type == emc6d100) { + /* Three more voltage sensors */ + for (i = 5; i <= 7; ++i) { + data->in[i] = lm85_read_value(client, + EMC6D100_REG_IN(i)); + } + /* More alarm bits */ + data->alarms |= lm85_read_value(client, + EMC6D100_REG_ALARM3) << 16; + } else if (data->type == emc6d102 || data->type == emc6d103 || + data->type == emc6d103s) { + /* + * Have to read LSB bits after the MSB ones because + * the reading of the MSB bits has frozen the + * LSBs (backward from the ADM1027). + */ + int ext1 = lm85_read_value(client, + EMC6D102_REG_EXTEND_ADC1); + int ext2 = lm85_read_value(client, + EMC6D102_REG_EXTEND_ADC2); + int ext3 = lm85_read_value(client, + EMC6D102_REG_EXTEND_ADC3); + int ext4 = lm85_read_value(client, + EMC6D102_REG_EXTEND_ADC4); + data->in_ext[0] = ext3 & 0x0f; + data->in_ext[1] = ext4 & 0x0f; + data->in_ext[2] = ext4 >> 4; + data->in_ext[3] = ext3 >> 4; + data->in_ext[4] = ext2 >> 4; + + data->temp_ext[0] = ext1 & 0x0f; + data->temp_ext[1] = ext2 & 0x0f; + data->temp_ext[2] = ext1 >> 4; + } + + data->last_reading = jiffies; + } /* last_reading */ + + if (!data->valid || + time_after(jiffies, data->last_config + LM85_CONFIG_INTERVAL)) { + /* Things that don't change often */ + dev_dbg(&client->dev, "Reading config values\n"); + + for (i = 0; i <= 3; ++i) { + data->in_min[i] = + lm85_read_value(client, LM85_REG_IN_MIN(i)); + data->in_max[i] = + lm85_read_value(client, LM85_REG_IN_MAX(i)); + data->fan_min[i] = + lm85_read_value(client, LM85_REG_FAN_MIN(i)); + } + + if (!data->has_vid5) { + data->in_min[4] = lm85_read_value(client, + LM85_REG_IN_MIN(4)); + data->in_max[4] = lm85_read_value(client, + LM85_REG_IN_MAX(4)); + } + + if (data->type == emc6d100) { + for (i = 5; i <= 7; ++i) { + data->in_min[i] = lm85_read_value(client, + EMC6D100_REG_IN_MIN(i)); + data->in_max[i] = lm85_read_value(client, + EMC6D100_REG_IN_MAX(i)); + } + } + + for (i = 0; i <= 2; ++i) { + int val; + + data->temp_min[i] = + lm85_read_value(client, LM85_REG_TEMP_MIN(i)); + data->temp_max[i] = + lm85_read_value(client, LM85_REG_TEMP_MAX(i)); + + data->autofan[i].config = + lm85_read_value(client, LM85_REG_AFAN_CONFIG(i)); + val = lm85_read_value(client, LM85_REG_AFAN_RANGE(i)); + data->pwm_freq[i] = val & 0x07; + data->zone[i].range = val >> 4; + data->autofan[i].min_pwm = + lm85_read_value(client, LM85_REG_AFAN_MINPWM(i)); + data->zone[i].limit = + lm85_read_value(client, LM85_REG_AFAN_LIMIT(i)); + data->zone[i].critical = + lm85_read_value(client, LM85_REG_AFAN_CRITICAL(i)); + + if (IS_ADT7468_OFF64(data)) { + data->temp_min[i] -= 64; + data->temp_max[i] -= 64; + data->zone[i].limit -= 64; + data->zone[i].critical -= 64; + } + } + + if (data->type != emc6d103s) { + i = lm85_read_value(client, LM85_REG_AFAN_SPIKE1); + data->autofan[0].min_off = (i & 0x20) != 0; + data->autofan[1].min_off = (i & 0x40) != 0; + data->autofan[2].min_off = (i & 0x80) != 0; + + i = lm85_read_value(client, LM85_REG_AFAN_HYST1); + data->zone[0].hyst = i >> 4; + data->zone[1].hyst = i & 0x0f; + + i = lm85_read_value(client, LM85_REG_AFAN_HYST2); + data->zone[2].hyst = i >> 4; + } + + data->last_config = jiffies; + } /* last_config */ + + data->valid = 1; + + mutex_unlock(&data->update_lock); + + return data; +} + +/* 4 Fans */ +static ssize_t show_fan(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr])); +} + +static ssize_t show_fan_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[nr])); +} + +static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_min[nr] = FAN_TO_REG(val); + lm85_write_value(client, LM85_REG_FAN_MIN(nr), data->fan_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define show_fan_offset(offset) \ +static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \ + show_fan, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ + show_fan_min, set_fan_min, offset - 1) + +show_fan_offset(1); +show_fan_offset(2); +show_fan_offset(3); +show_fan_offset(4); + +/* vid, vrm, alarms */ + +static ssize_t cpu0_vid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm85_data *data = lm85_update_device(dev); + int vid; + + if (data->has_vid5) { + /* 6-pin VID (VRM 10) */ + vid = vid_from_reg(data->vid & 0x3f, data->vrm); + } else { + /* 5-pin VID (VRM 9) */ + vid = vid_from_reg(data->vid & 0x1f, data->vrm); + } + + return sprintf(buf, "%d\n", vid); +} + +static DEVICE_ATTR_RO(cpu0_vid); + +static ssize_t vrm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm85_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%ld\n", (long) data->vrm); +} + +static ssize_t vrm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm85_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 255) + return -EINVAL; + + data->vrm = val; + return count; +} + +static DEVICE_ATTR_RW(vrm); + +static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%u\n", data->alarms); +} + +static DEVICE_ATTR_RO(alarms); + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%u\n", (data->alarms >> nr) & 1); +} + +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 18); +static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 16); +static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 17); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_alarm, NULL, 14); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_alarm, NULL, 15); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, 12); +static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO, show_alarm, NULL, 13); + +/* pwm */ + +static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", PWM_FROM_REG(data->pwm[nr])); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->pwm[nr] = PWM_TO_REG(val); + lm85_write_value(client, LM85_REG_PWM(nr), data->pwm[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm_enable(struct device *dev, struct device_attribute + *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + int pwm_zone, enable; + + pwm_zone = ZONE_FROM_REG(data->autofan[nr].config); + switch (pwm_zone) { + case -1: /* PWM is always at 100% */ + enable = 0; + break; + case 0: /* PWM is always at 0% */ + case -2: /* PWM responds to manual control */ + enable = 1; + break; + default: /* PWM in automatic mode */ + enable = 2; + } + return sprintf(buf, "%d\n", enable); +} + +static ssize_t set_pwm_enable(struct device *dev, struct device_attribute + *attr, const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 config; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + switch (val) { + case 0: + config = 3; + break; + case 1: + config = 7; + break; + case 2: + /* + * Here we have to choose arbitrarily one of the 5 possible + * configurations; I go for the safest + */ + config = 6; + break; + default: + return -EINVAL; + } + + mutex_lock(&data->update_lock); + data->autofan[nr].config = lm85_read_value(client, + LM85_REG_AFAN_CONFIG(nr)); + data->autofan[nr].config = (data->autofan[nr].config & ~0xe0) + | (config << 5); + lm85_write_value(client, LM85_REG_AFAN_CONFIG(nr), + data->autofan[nr].config); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm_freq(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + int freq; + + if (IS_ADT7468_HFPWM(data)) + freq = 22500; + else + freq = FREQ_FROM_REG(data->freq_map, data->pwm_freq[nr]); + + return sprintf(buf, "%d\n", freq); +} + +static ssize_t set_pwm_freq(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + /* + * The ADT7468 has a special high-frequency PWM output mode, + * where all PWM outputs are driven by a 22.5 kHz clock. + * This might confuse the user, but there's not much we can do. + */ + if (data->type == adt7468 && val >= 11300) { /* High freq. mode */ + data->cfg5 &= ~ADT7468_HFPWM; + lm85_write_value(client, ADT7468_REG_CFG5, data->cfg5); + } else { /* Low freq. mode */ + data->pwm_freq[nr] = FREQ_TO_REG(data->freq_map, + FREQ_MAP_LEN, val); + lm85_write_value(client, LM85_REG_AFAN_RANGE(nr), + (data->zone[nr].range << 4) + | data->pwm_freq[nr]); + if (data->type == adt7468) { + data->cfg5 |= ADT7468_HFPWM; + lm85_write_value(client, ADT7468_REG_CFG5, data->cfg5); + } + } + mutex_unlock(&data->update_lock); + return count; +} + +#define show_pwm_reg(offset) \ +static SENSOR_DEVICE_ATTR(pwm##offset, S_IRUGO | S_IWUSR, \ + show_pwm, set_pwm, offset - 1); \ +static SENSOR_DEVICE_ATTR(pwm##offset##_enable, S_IRUGO | S_IWUSR, \ + show_pwm_enable, set_pwm_enable, offset - 1); \ +static SENSOR_DEVICE_ATTR(pwm##offset##_freq, S_IRUGO | S_IWUSR, \ + show_pwm_freq, set_pwm_freq, offset - 1) + +show_pwm_reg(1); +show_pwm_reg(2); +show_pwm_reg(3); + +/* Voltages */ + +static ssize_t show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", INSEXT_FROM_REG(nr, data->in[nr], + data->in_ext[nr])); +} + +static ssize_t show_in_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", INS_FROM_REG(nr, data->in_min[nr])); +} + +static ssize_t set_in_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_min[nr] = INS_TO_REG(nr, val); + lm85_write_value(client, LM85_REG_IN_MIN(nr), data->in_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_in_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", INS_FROM_REG(nr, data->in_max[nr])); +} + +static ssize_t set_in_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_max[nr] = INS_TO_REG(nr, val); + lm85_write_value(client, LM85_REG_IN_MAX(nr), data->in_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define show_in_reg(offset) \ +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, \ + show_in, NULL, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \ + show_in_min, set_in_min, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \ + show_in_max, set_in_max, offset) + +show_in_reg(0); +show_in_reg(1); +show_in_reg(2); +show_in_reg(3); +show_in_reg(4); +show_in_reg(5); +show_in_reg(6); +show_in_reg(7); + +/* Temps */ + +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", TEMPEXT_FROM_REG(data->temp[nr], + data->temp_ext[nr])); +} + +static ssize_t show_temp_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_min[nr])); +} + +static ssize_t set_temp_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + if (IS_ADT7468_OFF64(data)) + val += 64; + + mutex_lock(&data->update_lock); + data->temp_min[nr] = TEMP_TO_REG(val); + lm85_write_value(client, LM85_REG_TEMP_MIN(nr), data->temp_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max[nr])); +} + +static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + if (IS_ADT7468_OFF64(data)) + val += 64; + + mutex_lock(&data->update_lock); + data->temp_max[nr] = TEMP_TO_REG(val); + lm85_write_value(client, LM85_REG_TEMP_MAX(nr), data->temp_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define show_temp_reg(offset) \ +static SENSOR_DEVICE_ATTR(temp##offset##_input, S_IRUGO, \ + show_temp, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_min, S_IRUGO | S_IWUSR, \ + show_temp_min, set_temp_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max, S_IRUGO | S_IWUSR, \ + show_temp_max, set_temp_max, offset - 1); + +show_temp_reg(1); +show_temp_reg(2); +show_temp_reg(3); + + +/* Automatic PWM control */ + +static ssize_t show_pwm_auto_channels(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", ZONE_FROM_REG(data->autofan[nr].config)); +} + +static ssize_t set_pwm_auto_channels(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->autofan[nr].config = (data->autofan[nr].config & (~0xe0)) + | ZONE_TO_REG(val); + lm85_write_value(client, LM85_REG_AFAN_CONFIG(nr), + data->autofan[nr].config); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm_auto_pwm_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", PWM_FROM_REG(data->autofan[nr].min_pwm)); +} + +static ssize_t set_pwm_auto_pwm_min(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->autofan[nr].min_pwm = PWM_TO_REG(val); + lm85_write_value(client, LM85_REG_AFAN_MINPWM(nr), + data->autofan[nr].min_pwm); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm_auto_pwm_minctl(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", data->autofan[nr].min_off); +} + +static ssize_t set_pwm_auto_pwm_minctl(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 tmp; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->autofan[nr].min_off = val; + tmp = lm85_read_value(client, LM85_REG_AFAN_SPIKE1); + tmp &= ~(0x20 << nr); + if (data->autofan[nr].min_off) + tmp |= 0x20 << nr; + lm85_write_value(client, LM85_REG_AFAN_SPIKE1, tmp); + mutex_unlock(&data->update_lock); + return count; +} + +#define pwm_auto(offset) \ +static SENSOR_DEVICE_ATTR(pwm##offset##_auto_channels, \ + S_IRUGO | S_IWUSR, show_pwm_auto_channels, \ + set_pwm_auto_channels, offset - 1); \ +static SENSOR_DEVICE_ATTR(pwm##offset##_auto_pwm_min, \ + S_IRUGO | S_IWUSR, show_pwm_auto_pwm_min, \ + set_pwm_auto_pwm_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(pwm##offset##_auto_pwm_minctl, \ + S_IRUGO | S_IWUSR, show_pwm_auto_pwm_minctl, \ + set_pwm_auto_pwm_minctl, offset - 1) + +pwm_auto(1); +pwm_auto(2); +pwm_auto(3); + +/* Temperature settings for automatic PWM control */ + +static ssize_t show_temp_auto_temp_off(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->zone[nr].limit) - + HYST_FROM_REG(data->zone[nr].hyst)); +} + +static ssize_t set_temp_auto_temp_off(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int min; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + min = TEMP_FROM_REG(data->zone[nr].limit); + data->zone[nr].hyst = HYST_TO_REG(min - val); + if (nr == 0 || nr == 1) { + lm85_write_value(client, LM85_REG_AFAN_HYST1, + (data->zone[0].hyst << 4) + | data->zone[1].hyst); + } else { + lm85_write_value(client, LM85_REG_AFAN_HYST2, + (data->zone[2].hyst << 4)); + } + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp_auto_temp_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->zone[nr].limit)); +} + +static ssize_t set_temp_auto_temp_min(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->zone[nr].limit = TEMP_TO_REG(val); + lm85_write_value(client, LM85_REG_AFAN_LIMIT(nr), + data->zone[nr].limit); + +/* Update temp_auto_max and temp_auto_range */ + data->zone[nr].range = RANGE_TO_REG( + TEMP_FROM_REG(data->zone[nr].max_desired) - + TEMP_FROM_REG(data->zone[nr].limit)); + lm85_write_value(client, LM85_REG_AFAN_RANGE(nr), + ((data->zone[nr].range & 0x0f) << 4) + | (data->pwm_freq[nr] & 0x07)); + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp_auto_temp_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->zone[nr].limit) + + RANGE_FROM_REG(data->zone[nr].range)); +} + +static ssize_t set_temp_auto_temp_max(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int min; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + min = TEMP_FROM_REG(data->zone[nr].limit); + data->zone[nr].max_desired = TEMP_TO_REG(val); + data->zone[nr].range = RANGE_TO_REG( + val - min); + lm85_write_value(client, LM85_REG_AFAN_RANGE(nr), + ((data->zone[nr].range & 0x0f) << 4) + | (data->pwm_freq[nr] & 0x07)); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp_auto_temp_crit(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = lm85_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->zone[nr].critical)); +} + +static ssize_t set_temp_auto_temp_crit(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct lm85_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->zone[nr].critical = TEMP_TO_REG(val); + lm85_write_value(client, LM85_REG_AFAN_CRITICAL(nr), + data->zone[nr].critical); + mutex_unlock(&data->update_lock); + return count; +} + +#define temp_auto(offset) \ +static SENSOR_DEVICE_ATTR(temp##offset##_auto_temp_off, \ + S_IRUGO | S_IWUSR, show_temp_auto_temp_off, \ + set_temp_auto_temp_off, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_auto_temp_min, \ + S_IRUGO | S_IWUSR, show_temp_auto_temp_min, \ + set_temp_auto_temp_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_auto_temp_max, \ + S_IRUGO | S_IWUSR, show_temp_auto_temp_max, \ + set_temp_auto_temp_max, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_auto_temp_crit, \ + S_IRUGO | S_IWUSR, show_temp_auto_temp_crit, \ + set_temp_auto_temp_crit, offset - 1); + +temp_auto(1); +temp_auto(2); +temp_auto(3); + +static struct attribute *lm85_attributes[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan4_min.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_fan4_alarm.dev_attr.attr, + + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_freq.dev_attr.attr, + &sensor_dev_attr_pwm2_freq.dev_attr.attr, + &sensor_dev_attr_pwm3_freq.dev_attr.attr, + + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + + &sensor_dev_attr_pwm1_auto_channels.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_channels.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_channels.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_pwm_min.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_pwm_min.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_pwm_min.dev_attr.attr, + + &sensor_dev_attr_temp1_auto_temp_min.dev_attr.attr, + &sensor_dev_attr_temp2_auto_temp_min.dev_attr.attr, + &sensor_dev_attr_temp3_auto_temp_min.dev_attr.attr, + &sensor_dev_attr_temp1_auto_temp_max.dev_attr.attr, + &sensor_dev_attr_temp2_auto_temp_max.dev_attr.attr, + &sensor_dev_attr_temp3_auto_temp_max.dev_attr.attr, + &sensor_dev_attr_temp1_auto_temp_crit.dev_attr.attr, + &sensor_dev_attr_temp2_auto_temp_crit.dev_attr.attr, + &sensor_dev_attr_temp3_auto_temp_crit.dev_attr.attr, + + &dev_attr_vrm.attr, + &dev_attr_cpu0_vid.attr, + &dev_attr_alarms.attr, + NULL +}; + +static const struct attribute_group lm85_group = { + .attrs = lm85_attributes, +}; + +static struct attribute *lm85_attributes_minctl[] = { + &sensor_dev_attr_pwm1_auto_pwm_minctl.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_pwm_minctl.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_pwm_minctl.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm85_group_minctl = { + .attrs = lm85_attributes_minctl, +}; + +static struct attribute *lm85_attributes_temp_off[] = { + &sensor_dev_attr_temp1_auto_temp_off.dev_attr.attr, + &sensor_dev_attr_temp2_auto_temp_off.dev_attr.attr, + &sensor_dev_attr_temp3_auto_temp_off.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm85_group_temp_off = { + .attrs = lm85_attributes_temp_off, +}; + +static struct attribute *lm85_attributes_in4[] = { + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm85_group_in4 = { + .attrs = lm85_attributes_in4, +}; + +static struct attribute *lm85_attributes_in567[] = { + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + &sensor_dev_attr_in6_alarm.dev_attr.attr, + &sensor_dev_attr_in7_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm85_group_in567 = { + .attrs = lm85_attributes_in567, +}; + +static void lm85_init_client(struct i2c_client *client) +{ + int value; + + /* Start monitoring if needed */ + value = lm85_read_value(client, LM85_REG_CONFIG); + if (!(value & 0x01)) { + dev_info(&client->dev, "Starting monitoring\n"); + lm85_write_value(client, LM85_REG_CONFIG, value | 0x01); + } + + /* Warn about unusual configuration bits */ + if (value & 0x02) + dev_warn(&client->dev, "Device configuration is locked\n"); + if (!(value & 0x04)) + dev_warn(&client->dev, "Device is not ready\n"); +} + +static int lm85_is_fake(struct i2c_client *client) +{ + /* + * Differenciate between real LM96000 and Winbond WPCD377I. The latter + * emulate the former except that it has no hardware monitoring function + * so the readings are always 0. + */ + int i; + u8 in_temp, fan; + + for (i = 0; i < 8; i++) { + in_temp = i2c_smbus_read_byte_data(client, 0x20 + i); + fan = i2c_smbus_read_byte_data(client, 0x28 + i); + if (in_temp != 0x00 || fan != 0xff) + return 0; + } + + return 1; +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int lm85_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int address = client->addr; + const char *type_name = NULL; + int company, verstep; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + /* We need to be able to do byte I/O */ + return -ENODEV; + } + + /* Determine the chip type */ + company = lm85_read_value(client, LM85_REG_COMPANY); + verstep = lm85_read_value(client, LM85_REG_VERSTEP); + + dev_dbg(&adapter->dev, + "Detecting device at 0x%02x with COMPANY: 0x%02x and VERSTEP: 0x%02x\n", + address, company, verstep); + + if (company == LM85_COMPANY_NATIONAL) { + switch (verstep) { + case LM85_VERSTEP_LM85C: + type_name = "lm85c"; + break; + case LM85_VERSTEP_LM85B: + type_name = "lm85b"; + break; + case LM85_VERSTEP_LM96000_1: + case LM85_VERSTEP_LM96000_2: + /* Check for Winbond WPCD377I */ + if (lm85_is_fake(client)) { + dev_dbg(&adapter->dev, + "Found Winbond WPCD377I, ignoring\n"); + return -ENODEV; + } + type_name = "lm85"; + break; + } + } else if (company == LM85_COMPANY_ANALOG_DEV) { + switch (verstep) { + case LM85_VERSTEP_ADM1027: + type_name = "adm1027"; + break; + case LM85_VERSTEP_ADT7463: + case LM85_VERSTEP_ADT7463C: + type_name = "adt7463"; + break; + case LM85_VERSTEP_ADT7468_1: + case LM85_VERSTEP_ADT7468_2: + type_name = "adt7468"; + break; + } + } else if (company == LM85_COMPANY_SMSC) { + switch (verstep) { + case LM85_VERSTEP_EMC6D100_A0: + case LM85_VERSTEP_EMC6D100_A1: + /* Note: we can't tell a '100 from a '101 */ + type_name = "emc6d100"; + break; + case LM85_VERSTEP_EMC6D102: + type_name = "emc6d102"; + break; + case LM85_VERSTEP_EMC6D103_A0: + case LM85_VERSTEP_EMC6D103_A1: + type_name = "emc6d103"; + break; + case LM85_VERSTEP_EMC6D103S: + type_name = "emc6d103s"; + break; + } + } + + if (!type_name) + return -ENODEV; + + strlcpy(info->type, type_name, I2C_NAME_SIZE); + + return 0; +} + +static int lm85_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct lm85_data *data; + int idx = 0; + + data = devm_kzalloc(dev, sizeof(struct lm85_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + if (client->dev.of_node) + data->type = (enum chips)of_device_get_match_data(&client->dev); + else + data->type = id->driver_data; + mutex_init(&data->update_lock); + + /* Fill in the chip specific driver values */ + switch (data->type) { + case adm1027: + case adt7463: + case adt7468: + case emc6d100: + case emc6d102: + case emc6d103: + case emc6d103s: + data->freq_map = adm1027_freq_map; + break; + default: + data->freq_map = lm85_freq_map; + } + + /* Set the VRM version */ + data->vrm = vid_which_vrm(); + + /* Initialize the LM85 chip */ + lm85_init_client(client); + + /* sysfs hooks */ + data->groups[idx++] = &lm85_group; + + /* minctl and temp_off exist on all chips except emc6d103s */ + if (data->type != emc6d103s) { + data->groups[idx++] = &lm85_group_minctl; + data->groups[idx++] = &lm85_group_temp_off; + } + + /* + * The ADT7463/68 have an optional VRM 10 mode where pin 21 is used + * as a sixth digital VID input rather than an analog input. + */ + if (data->type == adt7463 || data->type == adt7468) { + u8 vid = lm85_read_value(client, LM85_REG_VID); + if (vid & 0x80) + data->has_vid5 = true; + } + + if (!data->has_vid5) + data->groups[idx++] = &lm85_group_in4; + + /* The EMC6D100 has 3 additional voltage inputs */ + if (data->type == emc6d100) + data->groups[idx++] = &lm85_group_in567; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id lm85_id[] = { + { "adm1027", adm1027 }, + { "adt7463", adt7463 }, + { "adt7468", adt7468 }, + { "lm85", lm85 }, + { "lm85b", lm85 }, + { "lm85c", lm85 }, + { "emc6d100", emc6d100 }, + { "emc6d101", emc6d100 }, + { "emc6d102", emc6d102 }, + { "emc6d103", emc6d103 }, + { "emc6d103s", emc6d103s }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm85_id); + +static const struct of_device_id lm85_of_match[] = { + { + .compatible = "adi,adm1027", + .data = (void *)adm1027 + }, + { + .compatible = "adi,adt7463", + .data = (void *)adt7463 + }, + { + .compatible = "adi,adt7468", + .data = (void *)adt7468 + }, + { + .compatible = "national,lm85", + .data = (void *)lm85 + }, + { + .compatible = "national,lm85b", + .data = (void *)lm85 + }, + { + .compatible = "national,lm85c", + .data = (void *)lm85 + }, + { + .compatible = "smsc,emc6d100", + .data = (void *)emc6d100 + }, + { + .compatible = "smsc,emc6d101", + .data = (void *)emc6d100 + }, + { + .compatible = "smsc,emc6d102", + .data = (void *)emc6d102 + }, + { + .compatible = "smsc,emc6d103", + .data = (void *)emc6d103 + }, + { + .compatible = "smsc,emc6d103s", + .data = (void *)emc6d103s + }, + { }, +}; +MODULE_DEVICE_TABLE(of, lm85_of_match); + +static struct i2c_driver lm85_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm85", + .of_match_table = of_match_ptr(lm85_of_match), + }, + .probe = lm85_probe, + .id_table = lm85_id, + .detect = lm85_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(lm85_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Philip Pokorny , " + "Margit Schubert-While , " + "Justin Thiessen "); +MODULE_DESCRIPTION("LM85-B, LM85-C driver"); diff --git a/drivers/hwmon/lm87.c b/drivers/hwmon/lm87.c new file mode 100644 index 000000000..b48d30760 --- /dev/null +++ b/drivers/hwmon/lm87.c @@ -0,0 +1,1011 @@ +/* + * lm87.c + * + * Copyright (C) 2000 Frodo Looijaard + * Philip Edelbrock + * Stephen Rousset + * Dan Eaton + * Copyright (C) 2004-2008 Jean Delvare + * + * Original port to Linux 2.6 by Jeff Oliver. + * + * The LM87 is a sensor chip made by National Semiconductor. It monitors up + * to 8 voltages (including its own power source), up to three temperatures + * (its own plus up to two external ones) and up to two fans. The default + * configuration is 6 voltages, two temperatures and two fans (see below). + * Voltages are scaled internally with ratios such that the nominal value of + * each voltage correspond to a register value of 192 (which means a + * resolution of about 0.5% of the nominal value). Temperature values are + * reported with a 1 deg resolution and a 3-4 deg accuracy. Complete + * datasheet can be obtained from National's website at: + * http://www.national.com/pf/LM/LM87.html + * + * Some functions share pins, so not all functions are available at the same + * time. Which are depends on the hardware setup. This driver normally + * assumes that firmware configured the chip correctly. Where this is not + * the case, platform code must set the I2C client's platform_data to point + * to a u8 value to be written to the channel register. + * For reference, here is the list of exclusive functions: + * - in0+in5 (default) or temp3 + * - fan1 (default) or in6 + * - fan2 (default) or in7 + * - VID lines (default) or IRQ lines (not handled by this driver) + * + * The LM87 additionally features an analog output, supposedly usable to + * control the speed of a fan. All new chips use pulse width modulation + * instead. The LM87 is the only hardware monitoring chipset I know of + * which uses amplitude modulation. Be careful when using this feature. + * + * This driver also supports the ADM1024, a sensor chip made by Analog + * Devices. That chip is fully compatible with the LM87. Complete + * datasheet can be obtained from Analog's website at: + * http://www.analog.com/en/prod/0,2877,ADM1024,00.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Addresses to scan + * LM87 has three possible addresses: 0x2c, 0x2d and 0x2e. + */ + +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END }; + +/* + * The LM87 registers + */ + +/* nr in 0..5 */ +#define LM87_REG_IN(nr) (0x20 + (nr)) +#define LM87_REG_IN_MAX(nr) (0x2B + (nr) * 2) +#define LM87_REG_IN_MIN(nr) (0x2C + (nr) * 2) +/* nr in 0..1 */ +#define LM87_REG_AIN(nr) (0x28 + (nr)) +#define LM87_REG_AIN_MIN(nr) (0x1A + (nr)) +#define LM87_REG_AIN_MAX(nr) (0x3B + (nr)) + +static u8 LM87_REG_TEMP[3] = { 0x27, 0x26, 0x20 }; +static u8 LM87_REG_TEMP_HIGH[3] = { 0x39, 0x37, 0x2B }; +static u8 LM87_REG_TEMP_LOW[3] = { 0x3A, 0x38, 0x2C }; + +#define LM87_REG_TEMP_HW_INT_LOCK 0x13 +#define LM87_REG_TEMP_HW_EXT_LOCK 0x14 +#define LM87_REG_TEMP_HW_INT 0x17 +#define LM87_REG_TEMP_HW_EXT 0x18 + +/* nr in 0..1 */ +#define LM87_REG_FAN(nr) (0x28 + (nr)) +#define LM87_REG_FAN_MIN(nr) (0x3B + (nr)) +#define LM87_REG_AOUT 0x19 + +#define LM87_REG_CONFIG 0x40 +#define LM87_REG_CHANNEL_MODE 0x16 +#define LM87_REG_VID_FAN_DIV 0x47 +#define LM87_REG_VID4 0x49 + +#define LM87_REG_ALARMS1 0x41 +#define LM87_REG_ALARMS2 0x42 + +#define LM87_REG_COMPANY_ID 0x3E +#define LM87_REG_REVISION 0x3F + +/* + * Conversions and various macros + * The LM87 uses signed 8-bit values for temperatures. + */ + +#define IN_FROM_REG(reg, scale) (((reg) * (scale) + 96) / 192) +#define IN_TO_REG(val, scale) ((val) <= 0 ? 0 : \ + (val) >= (scale) * 255 / 192 ? 255 : \ + ((val) * 192 + (scale) / 2) / (scale)) + +#define TEMP_FROM_REG(reg) ((reg) * 1000) +#define TEMP_TO_REG(val) ((val) <= -127500 ? -128 : \ + (val) >= 126500 ? 127 : \ + (((val) < 0 ? (val) - 500 : \ + (val) + 500) / 1000)) + +#define FAN_FROM_REG(reg, div) ((reg) == 255 || (reg) == 0 ? 0 : \ + (1350000 + (reg)*(div) / 2) / ((reg) * (div))) +#define FAN_TO_REG(val, div) ((val) * (div) * 255 <= 1350000 ? 255 : \ + (1350000 + (val)*(div) / 2) / ((val) * (div))) + +#define FAN_DIV_FROM_REG(reg) (1 << (reg)) + +/* analog out is 9.80mV/LSB */ +#define AOUT_FROM_REG(reg) (((reg) * 98 + 5) / 10) +#define AOUT_TO_REG(val) ((val) <= 0 ? 0 : \ + (val) >= 2500 ? 255 : \ + ((val) * 10 + 49) / 98) + +/* nr in 0..1 */ +#define CHAN_NO_FAN(nr) (1 << (nr)) +#define CHAN_TEMP3 (1 << 2) +#define CHAN_VCC_5V (1 << 3) +#define CHAN_NO_VID (1 << 7) + +/* + * Client data (each client gets its own) + */ + +struct lm87_data { + struct mutex update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 channel; /* register value */ + u8 config; /* original register value */ + + u8 in[8]; /* register value */ + u8 in_max[8]; /* register value */ + u8 in_min[8]; /* register value */ + u16 in_scale[8]; + + s8 temp[3]; /* register value */ + s8 temp_high[3]; /* register value */ + s8 temp_low[3]; /* register value */ + s8 temp_crit_int; /* min of two register values */ + s8 temp_crit_ext; /* min of two register values */ + + u8 fan[2]; /* register value */ + u8 fan_min[2]; /* register value */ + u8 fan_div[2]; /* register value, shifted right */ + u8 aout; /* register value */ + + u16 alarms; /* register values, combined */ + u8 vid; /* register values, combined */ + u8 vrm; + + const struct attribute_group *attr_groups[6]; +}; + +static inline int lm87_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static inline int lm87_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static struct lm87_data *lm87_update_device(struct device *dev) +{ + struct i2c_client *client = dev_get_drvdata(dev); + struct lm87_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + int i, j; + + dev_dbg(&client->dev, "Updating data.\n"); + + i = (data->channel & CHAN_TEMP3) ? 1 : 0; + j = (data->channel & CHAN_TEMP3) ? 5 : 6; + for (; i < j; i++) { + data->in[i] = lm87_read_value(client, + LM87_REG_IN(i)); + data->in_min[i] = lm87_read_value(client, + LM87_REG_IN_MIN(i)); + data->in_max[i] = lm87_read_value(client, + LM87_REG_IN_MAX(i)); + } + + for (i = 0; i < 2; i++) { + if (data->channel & CHAN_NO_FAN(i)) { + data->in[6+i] = lm87_read_value(client, + LM87_REG_AIN(i)); + data->in_max[6+i] = lm87_read_value(client, + LM87_REG_AIN_MAX(i)); + data->in_min[6+i] = lm87_read_value(client, + LM87_REG_AIN_MIN(i)); + + } else { + data->fan[i] = lm87_read_value(client, + LM87_REG_FAN(i)); + data->fan_min[i] = lm87_read_value(client, + LM87_REG_FAN_MIN(i)); + } + } + + j = (data->channel & CHAN_TEMP3) ? 3 : 2; + for (i = 0 ; i < j; i++) { + data->temp[i] = lm87_read_value(client, + LM87_REG_TEMP[i]); + data->temp_high[i] = lm87_read_value(client, + LM87_REG_TEMP_HIGH[i]); + data->temp_low[i] = lm87_read_value(client, + LM87_REG_TEMP_LOW[i]); + } + + i = lm87_read_value(client, LM87_REG_TEMP_HW_INT_LOCK); + j = lm87_read_value(client, LM87_REG_TEMP_HW_INT); + data->temp_crit_int = min(i, j); + + i = lm87_read_value(client, LM87_REG_TEMP_HW_EXT_LOCK); + j = lm87_read_value(client, LM87_REG_TEMP_HW_EXT); + data->temp_crit_ext = min(i, j); + + i = lm87_read_value(client, LM87_REG_VID_FAN_DIV); + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = (i >> 6) & 0x03; + data->vid = (i & 0x0F) + | (lm87_read_value(client, LM87_REG_VID4) & 0x01) + << 4; + + data->alarms = lm87_read_value(client, LM87_REG_ALARMS1) + | (lm87_read_value(client, LM87_REG_ALARMS2) + << 8); + data->aout = lm87_read_value(client, LM87_REG_AOUT); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Sysfs stuff + */ + +static ssize_t show_in_input(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm87_data *data = lm87_update_device(dev); + int nr = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%u\n", IN_FROM_REG(data->in[nr], + data->in_scale[nr])); +} + +static ssize_t show_in_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm87_data *data = lm87_update_device(dev); + int nr = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%u\n", IN_FROM_REG(data->in_min[nr], + data->in_scale[nr])); +} + +static ssize_t show_in_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm87_data *data = lm87_update_device(dev); + int nr = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%u\n", IN_FROM_REG(data->in_max[nr], + data->in_scale[nr])); +} + +static ssize_t set_in_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = dev_get_drvdata(dev); + struct lm87_data *data = i2c_get_clientdata(client); + int nr = to_sensor_dev_attr(attr)->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_min[nr] = IN_TO_REG(val, data->in_scale[nr]); + lm87_write_value(client, nr < 6 ? LM87_REG_IN_MIN(nr) : + LM87_REG_AIN_MIN(nr - 6), data->in_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_in_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = dev_get_drvdata(dev); + struct lm87_data *data = i2c_get_clientdata(client); + int nr = to_sensor_dev_attr(attr)->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_max[nr] = IN_TO_REG(val, data->in_scale[nr]); + lm87_write_value(client, nr < 6 ? LM87_REG_IN_MAX(nr) : + LM87_REG_AIN_MAX(nr - 6), data->in_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define set_in(offset) \ +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, \ + show_in_input, NULL, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \ + show_in_min, set_in_min, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \ + show_in_max, set_in_max, offset) +set_in(0); +set_in(1); +set_in(2); +set_in(3); +set_in(4); +set_in(5); +set_in(6); +set_in(7); + +static ssize_t show_temp_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm87_data *data = lm87_update_device(dev); + int nr = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[nr])); +} + +static ssize_t show_temp_low(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm87_data *data = lm87_update_device(dev); + int nr = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%d\n", + TEMP_FROM_REG(data->temp_low[nr])); +} + +static ssize_t show_temp_high(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm87_data *data = lm87_update_device(dev); + int nr = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%d\n", + TEMP_FROM_REG(data->temp_high[nr])); +} + +static ssize_t set_temp_low(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = dev_get_drvdata(dev); + struct lm87_data *data = i2c_get_clientdata(client); + int nr = to_sensor_dev_attr(attr)->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_low[nr] = TEMP_TO_REG(val); + lm87_write_value(client, LM87_REG_TEMP_LOW[nr], data->temp_low[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_temp_high(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = dev_get_drvdata(dev); + struct lm87_data *data = i2c_get_clientdata(client); + int nr = to_sensor_dev_attr(attr)->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_high[nr] = TEMP_TO_REG(val); + lm87_write_value(client, LM87_REG_TEMP_HIGH[nr], data->temp_high[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define set_temp(offset) \ +static SENSOR_DEVICE_ATTR(temp##offset##_input, S_IRUGO, \ + show_temp_input, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max, S_IRUGO | S_IWUSR, \ + show_temp_high, set_temp_high, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_min, S_IRUGO | S_IWUSR, \ + show_temp_low, set_temp_low, offset - 1) +set_temp(1); +set_temp(2); +set_temp(3); + +static ssize_t temp1_crit_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm87_data *data = lm87_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_crit_int)); +} + +static ssize_t temp2_crit_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm87_data *data = lm87_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_crit_ext)); +} + +static DEVICE_ATTR_RO(temp1_crit); +static DEVICE_ATTR_RO(temp2_crit); +static DEVICE_ATTR(temp3_crit, S_IRUGO, temp2_crit_show, NULL); + +static ssize_t show_fan_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm87_data *data = lm87_update_device(dev); + int nr = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr], + FAN_DIV_FROM_REG(data->fan_div[nr]))); +} + +static ssize_t show_fan_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm87_data *data = lm87_update_device(dev); + int nr = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[nr], + FAN_DIV_FROM_REG(data->fan_div[nr]))); +} + +static ssize_t show_fan_div(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm87_data *data = lm87_update_device(dev); + int nr = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%d\n", + FAN_DIV_FROM_REG(data->fan_div[nr])); +} + +static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = dev_get_drvdata(dev); + struct lm87_data *data = i2c_get_clientdata(client); + int nr = to_sensor_dev_attr(attr)->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_min[nr] = FAN_TO_REG(val, + FAN_DIV_FROM_REG(data->fan_div[nr])); + lm87_write_value(client, LM87_REG_FAN_MIN(nr), data->fan_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Note: we save and restore the fan minimum here, because its value is + * determined in part by the fan clock divider. This follows the principle + * of least surprise; the user doesn't expect the fan minimum to change just + * because the divider changed. + */ +static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = dev_get_drvdata(dev); + struct lm87_data *data = i2c_get_clientdata(client); + int nr = to_sensor_dev_attr(attr)->index; + long val; + int err; + unsigned long min; + u8 reg; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + min = FAN_FROM_REG(data->fan_min[nr], + FAN_DIV_FROM_REG(data->fan_div[nr])); + + switch (val) { + case 1: + data->fan_div[nr] = 0; + break; + case 2: + data->fan_div[nr] = 1; + break; + case 4: + data->fan_div[nr] = 2; + break; + case 8: + data->fan_div[nr] = 3; + break; + default: + mutex_unlock(&data->update_lock); + return -EINVAL; + } + + reg = lm87_read_value(client, LM87_REG_VID_FAN_DIV); + switch (nr) { + case 0: + reg = (reg & 0xCF) | (data->fan_div[0] << 4); + break; + case 1: + reg = (reg & 0x3F) | (data->fan_div[1] << 6); + break; + } + lm87_write_value(client, LM87_REG_VID_FAN_DIV, reg); + + data->fan_min[nr] = FAN_TO_REG(min, val); + lm87_write_value(client, LM87_REG_FAN_MIN(nr), + data->fan_min[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +#define set_fan(offset) \ +static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \ + show_fan_input, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ + show_fan_min, set_fan_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR, \ + show_fan_div, set_fan_div, offset - 1) +set_fan(1); +set_fan(2); + +static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm87_data *data = lm87_update_device(dev); + return sprintf(buf, "%d\n", data->alarms); +} +static DEVICE_ATTR_RO(alarms); + +static ssize_t cpu0_vid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm87_data *data = lm87_update_device(dev); + return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); +} +static DEVICE_ATTR_RO(cpu0_vid); + +static ssize_t vrm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm87_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", data->vrm); +} +static ssize_t vrm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm87_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 255) + return -EINVAL; + + data->vrm = val; + return count; +} +static DEVICE_ATTR_RW(vrm); + +static ssize_t aout_output_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm87_data *data = lm87_update_device(dev); + return sprintf(buf, "%d\n", AOUT_FROM_REG(data->aout)); +} +static ssize_t aout_output_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = dev_get_drvdata(dev); + struct lm87_data *data = i2c_get_clientdata(client); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->aout = AOUT_TO_REG(val); + lm87_write_value(client, LM87_REG_AOUT, data->aout); + mutex_unlock(&data->update_lock); + return count; +} +static DEVICE_ATTR_RW(aout_output); + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm87_data *data = lm87_update_device(dev); + int bitnr = to_sensor_dev_attr(attr)->index; + return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1); +} +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 14); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_alarm, NULL, 15); + +/* + * Real code + */ + +static struct attribute *lm87_attributes[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &dev_attr_temp1_crit.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &dev_attr_temp2_crit.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + + &dev_attr_alarms.attr, + &dev_attr_aout_output.attr, + + NULL +}; + +static const struct attribute_group lm87_group = { + .attrs = lm87_attributes, +}; + +static struct attribute *lm87_attributes_in6[] = { + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in6_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm87_group_in6 = { + .attrs = lm87_attributes_in6, +}; + +static struct attribute *lm87_attributes_fan1[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm87_group_fan1 = { + .attrs = lm87_attributes_fan1, +}; + +static struct attribute *lm87_attributes_in7[] = { + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in7_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm87_group_in7 = { + .attrs = lm87_attributes_in7, +}; + +static struct attribute *lm87_attributes_fan2[] = { + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm87_group_fan2 = { + .attrs = lm87_attributes_fan2, +}; + +static struct attribute *lm87_attributes_temp3[] = { + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &dev_attr_temp3_crit.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm87_group_temp3 = { + .attrs = lm87_attributes_temp3, +}; + +static struct attribute *lm87_attributes_in0_5[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm87_group_in0_5 = { + .attrs = lm87_attributes_in0_5, +}; + +static struct attribute *lm87_attributes_vid[] = { + &dev_attr_cpu0_vid.attr, + &dev_attr_vrm.attr, + NULL +}; + +static const struct attribute_group lm87_group_vid = { + .attrs = lm87_attributes_vid, +}; + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int lm87_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + const char *name; + u8 cid, rev; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + if (lm87_read_value(client, LM87_REG_CONFIG) & 0x80) + return -ENODEV; + + /* Now, we do the remaining detection. */ + cid = lm87_read_value(client, LM87_REG_COMPANY_ID); + rev = lm87_read_value(client, LM87_REG_REVISION); + + if (cid == 0x02 /* National Semiconductor */ + && (rev >= 0x01 && rev <= 0x08)) + name = "lm87"; + else if (cid == 0x41 /* Analog Devices */ + && (rev & 0xf0) == 0x10) + name = "adm1024"; + else { + dev_dbg(&adapter->dev, "LM87 detection failed at 0x%02x\n", + client->addr); + return -ENODEV; + } + + strlcpy(info->type, name, I2C_NAME_SIZE); + + return 0; +} + +static void lm87_restore_config(void *arg) +{ + struct i2c_client *client = arg; + struct lm87_data *data = i2c_get_clientdata(client); + + lm87_write_value(client, LM87_REG_CONFIG, data->config); +} + +static int lm87_init_client(struct i2c_client *client) +{ + struct lm87_data *data = i2c_get_clientdata(client); + int rc; + struct device_node *of_node = client->dev.of_node; + u8 val = 0; + struct regulator *vcc = NULL; + + if (of_node) { + if (of_property_read_bool(of_node, "has-temp3")) + val |= CHAN_TEMP3; + if (of_property_read_bool(of_node, "has-in6")) + val |= CHAN_NO_FAN(0); + if (of_property_read_bool(of_node, "has-in7")) + val |= CHAN_NO_FAN(1); + vcc = devm_regulator_get_optional(&client->dev, "vcc"); + if (!IS_ERR(vcc)) { + if (regulator_get_voltage(vcc) == 5000000) + val |= CHAN_VCC_5V; + } + data->channel = val; + lm87_write_value(client, + LM87_REG_CHANNEL_MODE, data->channel); + } else if (dev_get_platdata(&client->dev)) { + data->channel = *(u8 *)dev_get_platdata(&client->dev); + lm87_write_value(client, + LM87_REG_CHANNEL_MODE, data->channel); + } else { + data->channel = lm87_read_value(client, LM87_REG_CHANNEL_MODE); + } + data->config = lm87_read_value(client, LM87_REG_CONFIG) & 0x6F; + + rc = devm_add_action(&client->dev, lm87_restore_config, client); + if (rc) + return rc; + + if (!(data->config & 0x01)) { + int i; + + /* Limits are left uninitialized after power-up */ + for (i = 1; i < 6; i++) { + lm87_write_value(client, LM87_REG_IN_MIN(i), 0x00); + lm87_write_value(client, LM87_REG_IN_MAX(i), 0xFF); + } + for (i = 0; i < 2; i++) { + lm87_write_value(client, LM87_REG_TEMP_HIGH[i], 0x7F); + lm87_write_value(client, LM87_REG_TEMP_LOW[i], 0x00); + lm87_write_value(client, LM87_REG_AIN_MIN(i), 0x00); + lm87_write_value(client, LM87_REG_AIN_MAX(i), 0xFF); + } + if (data->channel & CHAN_TEMP3) { + lm87_write_value(client, LM87_REG_TEMP_HIGH[2], 0x7F); + lm87_write_value(client, LM87_REG_TEMP_LOW[2], 0x00); + } else { + lm87_write_value(client, LM87_REG_IN_MIN(0), 0x00); + lm87_write_value(client, LM87_REG_IN_MAX(0), 0xFF); + } + } + + /* Make sure Start is set and INT#_Clear is clear */ + if ((data->config & 0x09) != 0x01) + lm87_write_value(client, LM87_REG_CONFIG, + (data->config & 0x77) | 0x01); + return 0; +} + +static int lm87_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct lm87_data *data; + struct device *hwmon_dev; + int err; + unsigned int group_tail = 0; + + data = devm_kzalloc(&client->dev, sizeof(struct lm87_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Initialize the LM87 chip */ + err = lm87_init_client(client); + if (err) + return err; + + data->in_scale[0] = 2500; + data->in_scale[1] = 2700; + data->in_scale[2] = (data->channel & CHAN_VCC_5V) ? 5000 : 3300; + data->in_scale[3] = 5000; + data->in_scale[4] = 12000; + data->in_scale[5] = 2700; + data->in_scale[6] = 1875; + data->in_scale[7] = 1875; + + /* + * Construct the list of attributes, the list depends on the + * configuration of the chip + */ + data->attr_groups[group_tail++] = &lm87_group; + if (data->channel & CHAN_NO_FAN(0)) + data->attr_groups[group_tail++] = &lm87_group_in6; + else + data->attr_groups[group_tail++] = &lm87_group_fan1; + + if (data->channel & CHAN_NO_FAN(1)) + data->attr_groups[group_tail++] = &lm87_group_in7; + else + data->attr_groups[group_tail++] = &lm87_group_fan2; + + if (data->channel & CHAN_TEMP3) + data->attr_groups[group_tail++] = &lm87_group_temp3; + else + data->attr_groups[group_tail++] = &lm87_group_in0_5; + + if (!(data->channel & CHAN_NO_VID)) { + data->vrm = vid_which_vrm(); + data->attr_groups[group_tail++] = &lm87_group_vid; + } + + hwmon_dev = devm_hwmon_device_register_with_groups( + &client->dev, client->name, client, data->attr_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +/* + * Driver data (common to all clients) + */ + +static const struct i2c_device_id lm87_id[] = { + { "lm87", 0 }, + { "adm1024", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm87_id); + +static const struct of_device_id lm87_of_match[] = { + { .compatible = "ti,lm87" }, + { .compatible = "adi,adm1024" }, + { }, +}; +MODULE_DEVICE_TABLE(of, lm87_of_match); + +static struct i2c_driver lm87_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm87", + .of_match_table = lm87_of_match, + }, + .probe = lm87_probe, + .id_table = lm87_id, + .detect = lm87_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(lm87_driver); + +MODULE_AUTHOR("Jean Delvare and others"); +MODULE_DESCRIPTION("LM87 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c new file mode 100644 index 000000000..033c89f83 --- /dev/null +++ b/drivers/hwmon/lm90.c @@ -0,0 +1,1963 @@ +/* + * lm90.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 2003-2010 Jean Delvare + * + * Based on the lm83 driver. The LM90 is a sensor chip made by National + * Semiconductor. It reports up to two temperatures (its own plus up to + * one external one) with a 0.125 deg resolution (1 deg for local + * temperature) and a 3-4 deg accuracy. + * + * This driver also supports the LM89 and LM99, two other sensor chips + * made by National Semiconductor. Both have an increased remote + * temperature measurement accuracy (1 degree), and the LM99 + * additionally shifts remote temperatures (measured and limits) by 16 + * degrees, which allows for higher temperatures measurement. + * Note that there is no way to differentiate between both chips. + * When device is auto-detected, the driver will assume an LM99. + * + * This driver also supports the LM86, another sensor chip made by + * National Semiconductor. It is exactly similar to the LM90 except it + * has a higher accuracy. + * + * This driver also supports the ADM1032, a sensor chip made by Analog + * Devices. That chip is similar to the LM90, with a few differences + * that are not handled by this driver. Among others, it has a higher + * accuracy than the LM90, much like the LM86 does. + * + * This driver also supports the MAX6657, MAX6658 and MAX6659 sensor + * chips made by Maxim. These chips are similar to the LM86. + * Note that there is no easy way to differentiate between the three + * variants. We use the device address to detect MAX6659, which will result + * in a detection as max6657 if it is on address 0x4c. The extra address + * and features of the MAX6659 are only supported if the chip is configured + * explicitly as max6659, or if its address is not 0x4c. + * These chips lack the remote temperature offset feature. + * + * This driver also supports the MAX6646, MAX6647, MAX6648, MAX6649 and + * MAX6692 chips made by Maxim. These are again similar to the LM86, + * but they use unsigned temperature values and can report temperatures + * from 0 to 145 degrees. + * + * This driver also supports the MAX6680 and MAX6681, two other sensor + * chips made by Maxim. These are quite similar to the other Maxim + * chips. The MAX6680 and MAX6681 only differ in the pinout so they can + * be treated identically. + * + * This driver also supports the MAX6695 and MAX6696, two other sensor + * chips made by Maxim. These are also quite similar to other Maxim + * chips, but support three temperature sensors instead of two. MAX6695 + * and MAX6696 only differ in the pinout so they can be treated identically. + * + * This driver also supports ADT7461 and ADT7461A from Analog Devices as well as + * NCT1008 from ON Semiconductor. The chips are supported in both compatibility + * and extended mode. They are mostly compatible with LM90 except for a data + * format difference for the temperature value registers. + * + * This driver also supports the SA56004 from Philips. This device is + * pin-compatible with the LM86, the ED/EDP parts are also address-compatible. + * + * This driver also supports the G781 from GMT. This device is compatible + * with the ADM1032. + * + * This driver also supports TMP451 from Texas Instruments. This device is + * supported in both compatibility and extended mode. It's mostly compatible + * with ADT7461 except for local temperature low byte register and max + * conversion rate. + * + * Since the LM90 was the first chipset supported by this driver, most + * comments will refer to this chipset, but are actually general and + * concern all supported chipsets, unless mentioned otherwise. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Addresses to scan + * Address is fully defined internally and cannot be changed except for + * MAX6659, MAX6680 and MAX6681. + * LM86, LM89, LM90, LM99, ADM1032, ADM1032-1, ADT7461, ADT7461A, MAX6649, + * MAX6657, MAX6658, NCT1008 and W83L771 have address 0x4c. + * ADM1032-2, ADT7461-2, ADT7461A-2, LM89-1, LM99-1, MAX6646, and NCT1008D + * have address 0x4d. + * MAX6647 has address 0x4e. + * MAX6659 can have address 0x4c, 0x4d or 0x4e. + * MAX6680 and MAX6681 can have address 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, + * 0x4c, 0x4d or 0x4e. + * SA56004 can have address 0x48 through 0x4F. + */ + +static const unsigned short normal_i2c[] = { + 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, 0x48, 0x49, 0x4a, 0x4b, 0x4c, + 0x4d, 0x4e, 0x4f, I2C_CLIENT_END }; + +enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680, + max6646, w83l771, max6696, sa56004, g781, tmp451 }; + +/* + * The LM90 registers + */ + +#define LM90_REG_R_MAN_ID 0xFE +#define LM90_REG_R_CHIP_ID 0xFF +#define LM90_REG_R_CONFIG1 0x03 +#define LM90_REG_W_CONFIG1 0x09 +#define LM90_REG_R_CONFIG2 0xBF +#define LM90_REG_W_CONFIG2 0xBF +#define LM90_REG_R_CONVRATE 0x04 +#define LM90_REG_W_CONVRATE 0x0A +#define LM90_REG_R_STATUS 0x02 +#define LM90_REG_R_LOCAL_TEMP 0x00 +#define LM90_REG_R_LOCAL_HIGH 0x05 +#define LM90_REG_W_LOCAL_HIGH 0x0B +#define LM90_REG_R_LOCAL_LOW 0x06 +#define LM90_REG_W_LOCAL_LOW 0x0C +#define LM90_REG_R_LOCAL_CRIT 0x20 +#define LM90_REG_W_LOCAL_CRIT 0x20 +#define LM90_REG_R_REMOTE_TEMPH 0x01 +#define LM90_REG_R_REMOTE_TEMPL 0x10 +#define LM90_REG_R_REMOTE_OFFSH 0x11 +#define LM90_REG_W_REMOTE_OFFSH 0x11 +#define LM90_REG_R_REMOTE_OFFSL 0x12 +#define LM90_REG_W_REMOTE_OFFSL 0x12 +#define LM90_REG_R_REMOTE_HIGHH 0x07 +#define LM90_REG_W_REMOTE_HIGHH 0x0D +#define LM90_REG_R_REMOTE_HIGHL 0x13 +#define LM90_REG_W_REMOTE_HIGHL 0x13 +#define LM90_REG_R_REMOTE_LOWH 0x08 +#define LM90_REG_W_REMOTE_LOWH 0x0E +#define LM90_REG_R_REMOTE_LOWL 0x14 +#define LM90_REG_W_REMOTE_LOWL 0x14 +#define LM90_REG_R_REMOTE_CRIT 0x19 +#define LM90_REG_W_REMOTE_CRIT 0x19 +#define LM90_REG_R_TCRIT_HYST 0x21 +#define LM90_REG_W_TCRIT_HYST 0x21 + +/* MAX6646/6647/6649/6657/6658/6659/6695/6696 registers */ + +#define MAX6657_REG_R_LOCAL_TEMPL 0x11 +#define MAX6696_REG_R_STATUS2 0x12 +#define MAX6659_REG_R_REMOTE_EMERG 0x16 +#define MAX6659_REG_W_REMOTE_EMERG 0x16 +#define MAX6659_REG_R_LOCAL_EMERG 0x17 +#define MAX6659_REG_W_LOCAL_EMERG 0x17 + +/* SA56004 registers */ + +#define SA56004_REG_R_LOCAL_TEMPL 0x22 + +#define LM90_MAX_CONVRATE_MS 16000 /* Maximum conversion rate in ms */ + +/* TMP451 registers */ +#define TMP451_REG_R_LOCAL_TEMPL 0x15 + +/* + * Device flags + */ +#define LM90_FLAG_ADT7461_EXT (1 << 0) /* ADT7461 extended mode */ +/* Device features */ +#define LM90_HAVE_OFFSET (1 << 1) /* temperature offset register */ +#define LM90_HAVE_REM_LIMIT_EXT (1 << 3) /* extended remote limit */ +#define LM90_HAVE_EMERGENCY (1 << 4) /* 3rd upper (emergency) limit */ +#define LM90_HAVE_EMERGENCY_ALARM (1 << 5)/* emergency alarm */ +#define LM90_HAVE_TEMP3 (1 << 6) /* 3rd temperature sensor */ +#define LM90_HAVE_BROKEN_ALERT (1 << 7) /* Broken alert */ +#define LM90_PAUSE_FOR_CONFIG (1 << 8) /* Pause conversion for config */ + +/* LM90 status */ +#define LM90_STATUS_LTHRM (1 << 0) /* local THERM limit tripped */ +#define LM90_STATUS_RTHRM (1 << 1) /* remote THERM limit tripped */ +#define LM90_STATUS_ROPEN (1 << 2) /* remote is an open circuit */ +#define LM90_STATUS_RLOW (1 << 3) /* remote low temp limit tripped */ +#define LM90_STATUS_RHIGH (1 << 4) /* remote high temp limit tripped */ +#define LM90_STATUS_LLOW (1 << 5) /* local low temp limit tripped */ +#define LM90_STATUS_LHIGH (1 << 6) /* local high temp limit tripped */ +#define LM90_STATUS_BUSY (1 << 7) /* conversion is ongoing */ + +#define MAX6696_STATUS2_R2THRM (1 << 1) /* remote2 THERM limit tripped */ +#define MAX6696_STATUS2_R2OPEN (1 << 2) /* remote2 is an open circuit */ +#define MAX6696_STATUS2_R2LOW (1 << 3) /* remote2 low temp limit tripped */ +#define MAX6696_STATUS2_R2HIGH (1 << 4) /* remote2 high temp limit tripped */ +#define MAX6696_STATUS2_ROT2 (1 << 5) /* remote emergency limit tripped */ +#define MAX6696_STATUS2_R2OT2 (1 << 6) /* remote2 emergency limit tripped */ +#define MAX6696_STATUS2_LOT2 (1 << 7) /* local emergency limit tripped */ + +/* + * Driver data (common to all clients) + */ + +static const struct i2c_device_id lm90_id[] = { + { "adm1032", adm1032 }, + { "adt7461", adt7461 }, + { "adt7461a", adt7461 }, + { "g781", g781 }, + { "lm90", lm90 }, + { "lm86", lm86 }, + { "lm89", lm86 }, + { "lm99", lm99 }, + { "max6646", max6646 }, + { "max6647", max6646 }, + { "max6649", max6646 }, + { "max6657", max6657 }, + { "max6658", max6657 }, + { "max6659", max6659 }, + { "max6680", max6680 }, + { "max6681", max6680 }, + { "max6695", max6696 }, + { "max6696", max6696 }, + { "nct1008", adt7461 }, + { "w83l771", w83l771 }, + { "sa56004", sa56004 }, + { "tmp451", tmp451 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm90_id); + +static const struct of_device_id lm90_of_match[] = { + { + .compatible = "adi,adm1032", + .data = (void *)adm1032 + }, + { + .compatible = "adi,adt7461", + .data = (void *)adt7461 + }, + { + .compatible = "adi,adt7461a", + .data = (void *)adt7461 + }, + { + .compatible = "gmt,g781", + .data = (void *)g781 + }, + { + .compatible = "national,lm90", + .data = (void *)lm90 + }, + { + .compatible = "national,lm86", + .data = (void *)lm86 + }, + { + .compatible = "national,lm89", + .data = (void *)lm86 + }, + { + .compatible = "national,lm99", + .data = (void *)lm99 + }, + { + .compatible = "dallas,max6646", + .data = (void *)max6646 + }, + { + .compatible = "dallas,max6647", + .data = (void *)max6646 + }, + { + .compatible = "dallas,max6649", + .data = (void *)max6646 + }, + { + .compatible = "dallas,max6657", + .data = (void *)max6657 + }, + { + .compatible = "dallas,max6658", + .data = (void *)max6657 + }, + { + .compatible = "dallas,max6659", + .data = (void *)max6659 + }, + { + .compatible = "dallas,max6680", + .data = (void *)max6680 + }, + { + .compatible = "dallas,max6681", + .data = (void *)max6680 + }, + { + .compatible = "dallas,max6695", + .data = (void *)max6696 + }, + { + .compatible = "dallas,max6696", + .data = (void *)max6696 + }, + { + .compatible = "onnn,nct1008", + .data = (void *)adt7461 + }, + { + .compatible = "winbond,w83l771", + .data = (void *)w83l771 + }, + { + .compatible = "nxp,sa56004", + .data = (void *)sa56004 + }, + { + .compatible = "ti,tmp451", + .data = (void *)tmp451 + }, + { }, +}; +MODULE_DEVICE_TABLE(of, lm90_of_match); + +/* + * chip type specific parameters + */ +struct lm90_params { + u32 flags; /* Capabilities */ + u16 alert_alarms; /* Which alarm bits trigger ALERT# */ + /* Upper 8 bits for max6695/96 */ + u8 max_convrate; /* Maximum conversion rate register value */ + u8 reg_local_ext; /* Extended local temp register (optional) */ +}; + +static const struct lm90_params lm90_params[] = { + [adm1032] = { + .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT + | LM90_HAVE_BROKEN_ALERT, + .alert_alarms = 0x7c, + .max_convrate = 10, + }, + [adt7461] = { + .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT + | LM90_HAVE_BROKEN_ALERT, + .alert_alarms = 0x7c, + .max_convrate = 10, + }, + [g781] = { + .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT + | LM90_HAVE_BROKEN_ALERT, + .alert_alarms = 0x7c, + .max_convrate = 7, + }, + [lm86] = { + .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT, + .alert_alarms = 0x7b, + .max_convrate = 9, + }, + [lm90] = { + .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT, + .alert_alarms = 0x7b, + .max_convrate = 9, + }, + [lm99] = { + .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT, + .alert_alarms = 0x7b, + .max_convrate = 9, + }, + [max6646] = { + .alert_alarms = 0x7c, + .max_convrate = 6, + .reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL, + }, + [max6657] = { + .flags = LM90_PAUSE_FOR_CONFIG, + .alert_alarms = 0x7c, + .max_convrate = 8, + .reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL, + }, + [max6659] = { + .flags = LM90_HAVE_EMERGENCY, + .alert_alarms = 0x7c, + .max_convrate = 8, + .reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL, + }, + [max6680] = { + .flags = LM90_HAVE_OFFSET, + .alert_alarms = 0x7c, + .max_convrate = 7, + }, + [max6696] = { + .flags = LM90_HAVE_EMERGENCY + | LM90_HAVE_EMERGENCY_ALARM | LM90_HAVE_TEMP3, + .alert_alarms = 0x1c7c, + .max_convrate = 6, + .reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL, + }, + [w83l771] = { + .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT, + .alert_alarms = 0x7c, + .max_convrate = 8, + }, + [sa56004] = { + .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT, + .alert_alarms = 0x7b, + .max_convrate = 9, + .reg_local_ext = SA56004_REG_R_LOCAL_TEMPL, + }, + [tmp451] = { + .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT + | LM90_HAVE_BROKEN_ALERT, + .alert_alarms = 0x7c, + .max_convrate = 9, + .reg_local_ext = TMP451_REG_R_LOCAL_TEMPL, + }, +}; + +/* + * TEMP8 register index + */ +enum lm90_temp8_reg_index { + LOCAL_LOW = 0, + LOCAL_HIGH, + LOCAL_CRIT, + REMOTE_CRIT, + LOCAL_EMERG, /* max6659 and max6695/96 */ + REMOTE_EMERG, /* max6659 and max6695/96 */ + REMOTE2_CRIT, /* max6695/96 only */ + REMOTE2_EMERG, /* max6695/96 only */ + TEMP8_REG_NUM +}; + +/* + * TEMP11 register index + */ +enum lm90_temp11_reg_index { + REMOTE_TEMP = 0, + REMOTE_LOW, + REMOTE_HIGH, + REMOTE_OFFSET, /* except max6646, max6657/58/59, and max6695/96 */ + LOCAL_TEMP, + REMOTE2_TEMP, /* max6695/96 only */ + REMOTE2_LOW, /* max6695/96 only */ + REMOTE2_HIGH, /* max6695/96 only */ + TEMP11_REG_NUM +}; + +/* + * Client data (each client gets its own) + */ + +struct lm90_data { + struct i2c_client *client; + u32 channel_config[4]; + struct hwmon_channel_info temp_info; + const struct hwmon_channel_info *info[3]; + struct hwmon_chip_info chip; + struct mutex update_lock; + bool valid; /* true if register values are valid */ + unsigned long last_updated; /* in jiffies */ + int kind; + u32 flags; + + unsigned int update_interval; /* in milliseconds */ + + u8 config_orig; /* Original configuration register value */ + u8 convrate_orig; /* Original conversion rate register value */ + u16 alert_alarms; /* Which alarm bits trigger ALERT# */ + /* Upper 8 bits for max6695/96 */ + u8 max_convrate; /* Maximum conversion rate */ + u8 reg_local_ext; /* local extension register offset */ + + /* registers values */ + s8 temp8[TEMP8_REG_NUM]; + s16 temp11[TEMP11_REG_NUM]; + u8 temp_hyst; + u16 alarms; /* bitvector (upper 8 bits for max6695/96) */ +}; + +/* + * Support functions + */ + +/* + * The ADM1032 supports PEC but not on write byte transactions, so we need + * to explicitly ask for a transaction without PEC. + */ +static inline s32 adm1032_write_byte(struct i2c_client *client, u8 value) +{ + return i2c_smbus_xfer(client->adapter, client->addr, + client->flags & ~I2C_CLIENT_PEC, + I2C_SMBUS_WRITE, value, I2C_SMBUS_BYTE, NULL); +} + +/* + * It is assumed that client->update_lock is held (unless we are in + * detection or initialization steps). This matters when PEC is enabled, + * because we don't want the address pointer to change between the write + * byte and the read byte transactions. + */ +static int lm90_read_reg(struct i2c_client *client, u8 reg) +{ + int err; + + if (client->flags & I2C_CLIENT_PEC) { + err = adm1032_write_byte(client, reg); + if (err >= 0) + err = i2c_smbus_read_byte(client); + } else + err = i2c_smbus_read_byte_data(client, reg); + + return err; +} + +static int lm90_read16(struct i2c_client *client, u8 regh, u8 regl) +{ + int oldh, newh, l; + + /* + * There is a trick here. We have to read two registers to have the + * sensor temperature, but we have to beware a conversion could occur + * between the readings. The datasheet says we should either use + * the one-shot conversion register, which we don't want to do + * (disables hardware monitoring) or monitor the busy bit, which is + * impossible (we can't read the values and monitor that bit at the + * exact same time). So the solution used here is to read the high + * byte once, then the low byte, then the high byte again. If the new + * high byte matches the old one, then we have a valid reading. Else + * we have to read the low byte again, and now we believe we have a + * correct reading. + */ + oldh = lm90_read_reg(client, regh); + if (oldh < 0) + return oldh; + l = lm90_read_reg(client, regl); + if (l < 0) + return l; + newh = lm90_read_reg(client, regh); + if (newh < 0) + return newh; + if (oldh != newh) { + l = lm90_read_reg(client, regl); + if (l < 0) + return l; + } + return (newh << 8) | l; +} + +/* + * client->update_lock must be held when calling this function (unless we are + * in detection or initialization steps), and while a remote channel other + * than channel 0 is selected. Also, calling code must make sure to re-select + * external channel 0 before releasing the lock. This is necessary because + * various registers have different meanings as a result of selecting a + * non-default remote channel. + */ +static inline int lm90_select_remote_channel(struct i2c_client *client, + struct lm90_data *data, + int channel) +{ + int config; + + if (data->kind == max6696) { + config = lm90_read_reg(client, LM90_REG_R_CONFIG1); + if (config < 0) + return config; + config &= ~0x08; + if (channel) + config |= 0x08; + i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, + config); + } + return 0; +} + +static int lm90_write_convrate(struct i2c_client *client, + struct lm90_data *data, int val) +{ + int err; + int config_orig, config_stop; + + /* Save config and pause conversion */ + if (data->flags & LM90_PAUSE_FOR_CONFIG) { + config_orig = lm90_read_reg(client, LM90_REG_R_CONFIG1); + if (config_orig < 0) + return config_orig; + config_stop = config_orig | 0x40; + if (config_orig != config_stop) { + err = i2c_smbus_write_byte_data(client, + LM90_REG_W_CONFIG1, + config_stop); + if (err < 0) + return err; + } + } + + /* Set conv rate */ + err = i2c_smbus_write_byte_data(client, LM90_REG_W_CONVRATE, val); + + /* Revert change to config */ + if (data->flags & LM90_PAUSE_FOR_CONFIG && config_orig != config_stop) + i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, + config_orig); + + return err; +} + +/* + * Set conversion rate. + * client->update_lock must be held when calling this function (unless we are + * in detection or initialization steps). + */ +static int lm90_set_convrate(struct i2c_client *client, struct lm90_data *data, + unsigned int interval) +{ + unsigned int update_interval; + int i, err; + + /* Shift calculations to avoid rounding errors */ + interval <<= 6; + + /* find the nearest update rate */ + for (i = 0, update_interval = LM90_MAX_CONVRATE_MS << 6; + i < data->max_convrate; i++, update_interval >>= 1) + if (interval >= update_interval * 3 / 4) + break; + + err = lm90_write_convrate(client, data, i); + data->update_interval = DIV_ROUND_CLOSEST(update_interval, 64); + return err; +} + +static int lm90_update_limits(struct device *dev) +{ + struct lm90_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int val; + + val = lm90_read_reg(client, LM90_REG_R_LOCAL_CRIT); + if (val < 0) + return val; + data->temp8[LOCAL_CRIT] = val; + + val = lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT); + if (val < 0) + return val; + data->temp8[REMOTE_CRIT] = val; + + val = lm90_read_reg(client, LM90_REG_R_TCRIT_HYST); + if (val < 0) + return val; + data->temp_hyst = val; + + val = lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH); + if (val < 0) + return val; + data->temp11[REMOTE_LOW] = val << 8; + + if (data->flags & LM90_HAVE_REM_LIMIT_EXT) { + val = lm90_read_reg(client, LM90_REG_R_REMOTE_LOWL); + if (val < 0) + return val; + data->temp11[REMOTE_LOW] |= val; + } + + val = lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH); + if (val < 0) + return val; + data->temp11[REMOTE_HIGH] = val << 8; + + if (data->flags & LM90_HAVE_REM_LIMIT_EXT) { + val = lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHL); + if (val < 0) + return val; + data->temp11[REMOTE_HIGH] |= val; + } + + if (data->flags & LM90_HAVE_OFFSET) { + val = lm90_read16(client, LM90_REG_R_REMOTE_OFFSH, + LM90_REG_R_REMOTE_OFFSL); + if (val < 0) + return val; + data->temp11[REMOTE_OFFSET] = val; + } + + if (data->flags & LM90_HAVE_EMERGENCY) { + val = lm90_read_reg(client, MAX6659_REG_R_LOCAL_EMERG); + if (val < 0) + return val; + data->temp8[LOCAL_EMERG] = val; + + val = lm90_read_reg(client, MAX6659_REG_R_REMOTE_EMERG); + if (val < 0) + return val; + data->temp8[REMOTE_EMERG] = val; + } + + if (data->kind == max6696) { + val = lm90_select_remote_channel(client, data, 1); + if (val < 0) + return val; + + val = lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT); + if (val < 0) + return val; + data->temp8[REMOTE2_CRIT] = val; + + val = lm90_read_reg(client, MAX6659_REG_R_REMOTE_EMERG); + if (val < 0) + return val; + data->temp8[REMOTE2_EMERG] = val; + + val = lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH); + if (val < 0) + return val; + data->temp11[REMOTE2_LOW] = val << 8; + + val = lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH); + if (val < 0) + return val; + data->temp11[REMOTE2_HIGH] = val << 8; + + lm90_select_remote_channel(client, data, 0); + } + + return 0; +} + +static int lm90_update_device(struct device *dev) +{ + struct lm90_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long next_update; + int val; + + if (!data->valid) { + val = lm90_update_limits(dev); + if (val < 0) + return val; + } + + next_update = data->last_updated + + msecs_to_jiffies(data->update_interval); + if (time_after(jiffies, next_update) || !data->valid) { + dev_dbg(&client->dev, "Updating lm90 data.\n"); + + data->valid = false; + + val = lm90_read_reg(client, LM90_REG_R_LOCAL_LOW); + if (val < 0) + return val; + data->temp8[LOCAL_LOW] = val; + + val = lm90_read_reg(client, LM90_REG_R_LOCAL_HIGH); + if (val < 0) + return val; + data->temp8[LOCAL_HIGH] = val; + + if (data->reg_local_ext) { + val = lm90_read16(client, LM90_REG_R_LOCAL_TEMP, + data->reg_local_ext); + if (val < 0) + return val; + data->temp11[LOCAL_TEMP] = val; + } else { + val = lm90_read_reg(client, LM90_REG_R_LOCAL_TEMP); + if (val < 0) + return val; + data->temp11[LOCAL_TEMP] = val << 8; + } + val = lm90_read16(client, LM90_REG_R_REMOTE_TEMPH, + LM90_REG_R_REMOTE_TEMPL); + if (val < 0) + return val; + data->temp11[REMOTE_TEMP] = val; + + val = lm90_read_reg(client, LM90_REG_R_STATUS); + if (val < 0) + return val; + data->alarms = val & ~LM90_STATUS_BUSY; + + if (data->kind == max6696) { + val = lm90_select_remote_channel(client, data, 1); + if (val < 0) + return val; + + val = lm90_read16(client, LM90_REG_R_REMOTE_TEMPH, + LM90_REG_R_REMOTE_TEMPL); + if (val < 0) { + lm90_select_remote_channel(client, data, 0); + return val; + } + data->temp11[REMOTE2_TEMP] = val; + + lm90_select_remote_channel(client, data, 0); + + val = lm90_read_reg(client, MAX6696_REG_R_STATUS2); + if (val < 0) + return val; + data->alarms |= val << 8; + } + + /* + * Re-enable ALERT# output if it was originally enabled and + * relevant alarms are all clear + */ + if (!(data->config_orig & 0x80) && + !(data->alarms & data->alert_alarms)) { + val = lm90_read_reg(client, LM90_REG_R_CONFIG1); + if (val < 0) + return val; + + if (val & 0x80) { + dev_dbg(&client->dev, "Re-enabling ALERT#\n"); + i2c_smbus_write_byte_data(client, + LM90_REG_W_CONFIG1, + val & ~0x80); + } + } + + data->last_updated = jiffies; + data->valid = true; + } + + return 0; +} + +/* + * Conversions + * For local temperatures and limits, critical limits and the hysteresis + * value, the LM90 uses signed 8-bit values with LSB = 1 degree Celsius. + * For remote temperatures and limits, it uses signed 11-bit values with + * LSB = 0.125 degree Celsius, left-justified in 16-bit registers. Some + * Maxim chips use unsigned values. + */ + +static inline int temp_from_s8(s8 val) +{ + return val * 1000; +} + +static inline int temp_from_u8(u8 val) +{ + return val * 1000; +} + +static inline int temp_from_s16(s16 val) +{ + return val / 32 * 125; +} + +static inline int temp_from_u16(u16 val) +{ + return val / 32 * 125; +} + +static s8 temp_to_s8(long val) +{ + if (val <= -128000) + return -128; + if (val >= 127000) + return 127; + if (val < 0) + return (val - 500) / 1000; + return (val + 500) / 1000; +} + +static u8 temp_to_u8(long val) +{ + if (val <= 0) + return 0; + if (val >= 255000) + return 255; + return (val + 500) / 1000; +} + +static s16 temp_to_s16(long val) +{ + if (val <= -128000) + return 0x8000; + if (val >= 127875) + return 0x7FE0; + if (val < 0) + return (val - 62) / 125 * 32; + return (val + 62) / 125 * 32; +} + +static u8 hyst_to_reg(long val) +{ + if (val <= 0) + return 0; + if (val >= 30500) + return 31; + return (val + 500) / 1000; +} + +/* + * ADT7461 in compatibility mode is almost identical to LM90 except that + * attempts to write values that are outside the range 0 < temp < 127 are + * treated as the boundary value. + * + * ADT7461 in "extended mode" operation uses unsigned integers offset by + * 64 (e.g., 0 -> -64 degC). The range is restricted to -64..191 degC. + */ +static inline int temp_from_u8_adt7461(struct lm90_data *data, u8 val) +{ + if (data->flags & LM90_FLAG_ADT7461_EXT) + return (val - 64) * 1000; + return temp_from_s8(val); +} + +static inline int temp_from_u16_adt7461(struct lm90_data *data, u16 val) +{ + if (data->flags & LM90_FLAG_ADT7461_EXT) + return (val - 0x4000) / 64 * 250; + return temp_from_s16(val); +} + +static u8 temp_to_u8_adt7461(struct lm90_data *data, long val) +{ + if (data->flags & LM90_FLAG_ADT7461_EXT) { + if (val <= -64000) + return 0; + if (val >= 191000) + return 0xFF; + return (val + 500 + 64000) / 1000; + } + if (val <= 0) + return 0; + if (val >= 127000) + return 127; + return (val + 500) / 1000; +} + +static u16 temp_to_u16_adt7461(struct lm90_data *data, long val) +{ + if (data->flags & LM90_FLAG_ADT7461_EXT) { + if (val <= -64000) + return 0; + if (val >= 191750) + return 0xFFC0; + return (val + 64000 + 125) / 250 * 64; + } + if (val <= 0) + return 0; + if (val >= 127750) + return 0x7FC0; + return (val + 125) / 250 * 64; +} + +/* pec used for ADM1032 only */ +static ssize_t pec_show(struct device *dev, struct device_attribute *dummy, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + + return sprintf(buf, "%d\n", !!(client->flags & I2C_CLIENT_PEC)); +} + +static ssize_t pec_store(struct device *dev, struct device_attribute *dummy, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + switch (val) { + case 0: + client->flags &= ~I2C_CLIENT_PEC; + break; + case 1: + client->flags |= I2C_CLIENT_PEC; + break; + default: + return -EINVAL; + } + + return count; +} + +static DEVICE_ATTR_RW(pec); + +static int lm90_get_temp11(struct lm90_data *data, int index) +{ + s16 temp11 = data->temp11[index]; + int temp; + + if (data->kind == adt7461 || data->kind == tmp451) + temp = temp_from_u16_adt7461(data, temp11); + else if (data->kind == max6646) + temp = temp_from_u16(temp11); + else + temp = temp_from_s16(temp11); + + /* +16 degrees offset for temp2 for the LM99 */ + if (data->kind == lm99 && index <= 2) + temp += 16000; + + return temp; +} + +static int lm90_set_temp11(struct lm90_data *data, int index, long val) +{ + static struct reg { + u8 high; + u8 low; + } reg[] = { + [REMOTE_LOW] = { LM90_REG_W_REMOTE_LOWH, LM90_REG_W_REMOTE_LOWL }, + [REMOTE_HIGH] = { LM90_REG_W_REMOTE_HIGHH, LM90_REG_W_REMOTE_HIGHL }, + [REMOTE_OFFSET] = { LM90_REG_W_REMOTE_OFFSH, LM90_REG_W_REMOTE_OFFSL }, + [REMOTE2_LOW] = { LM90_REG_W_REMOTE_LOWH, LM90_REG_W_REMOTE_LOWL }, + [REMOTE2_HIGH] = { LM90_REG_W_REMOTE_HIGHH, LM90_REG_W_REMOTE_HIGHL } + }; + struct i2c_client *client = data->client; + struct reg *regp = ®[index]; + int err; + + /* +16 degrees offset for temp2 for the LM99 */ + if (data->kind == lm99 && index <= 2) + val -= 16000; + + if (data->kind == adt7461 || data->kind == tmp451) + data->temp11[index] = temp_to_u16_adt7461(data, val); + else if (data->kind == max6646) + data->temp11[index] = temp_to_u8(val) << 8; + else if (data->flags & LM90_HAVE_REM_LIMIT_EXT) + data->temp11[index] = temp_to_s16(val); + else + data->temp11[index] = temp_to_s8(val) << 8; + + lm90_select_remote_channel(client, data, index >= 3); + err = i2c_smbus_write_byte_data(client, regp->high, + data->temp11[index] >> 8); + if (err < 0) + return err; + if (data->flags & LM90_HAVE_REM_LIMIT_EXT) + err = i2c_smbus_write_byte_data(client, regp->low, + data->temp11[index] & 0xff); + + lm90_select_remote_channel(client, data, 0); + return err; +} + +static int lm90_get_temp8(struct lm90_data *data, int index) +{ + s8 temp8 = data->temp8[index]; + int temp; + + if (data->kind == adt7461 || data->kind == tmp451) + temp = temp_from_u8_adt7461(data, temp8); + else if (data->kind == max6646) + temp = temp_from_u8(temp8); + else + temp = temp_from_s8(temp8); + + /* +16 degrees offset for temp2 for the LM99 */ + if (data->kind == lm99 && index == 3) + temp += 16000; + + return temp; +} + +static int lm90_set_temp8(struct lm90_data *data, int index, long val) +{ + static const u8 reg[TEMP8_REG_NUM] = { + LM90_REG_W_LOCAL_LOW, + LM90_REG_W_LOCAL_HIGH, + LM90_REG_W_LOCAL_CRIT, + LM90_REG_W_REMOTE_CRIT, + MAX6659_REG_W_LOCAL_EMERG, + MAX6659_REG_W_REMOTE_EMERG, + LM90_REG_W_REMOTE_CRIT, + MAX6659_REG_W_REMOTE_EMERG, + }; + struct i2c_client *client = data->client; + int err; + + /* +16 degrees offset for temp2 for the LM99 */ + if (data->kind == lm99 && index == 3) + val -= 16000; + + if (data->kind == adt7461 || data->kind == tmp451) + data->temp8[index] = temp_to_u8_adt7461(data, val); + else if (data->kind == max6646) + data->temp8[index] = temp_to_u8(val); + else + data->temp8[index] = temp_to_s8(val); + + lm90_select_remote_channel(client, data, index >= 6); + err = i2c_smbus_write_byte_data(client, reg[index], data->temp8[index]); + lm90_select_remote_channel(client, data, 0); + + return err; +} + +static int lm90_get_temphyst(struct lm90_data *data, int index) +{ + int temp; + + if (data->kind == adt7461 || data->kind == tmp451) + temp = temp_from_u8_adt7461(data, data->temp8[index]); + else if (data->kind == max6646) + temp = temp_from_u8(data->temp8[index]); + else + temp = temp_from_s8(data->temp8[index]); + + /* +16 degrees offset for temp2 for the LM99 */ + if (data->kind == lm99 && index == 3) + temp += 16000; + + return temp - temp_from_s8(data->temp_hyst); +} + +static int lm90_set_temphyst(struct lm90_data *data, long val) +{ + struct i2c_client *client = data->client; + int temp; + int err; + + if (data->kind == adt7461 || data->kind == tmp451) + temp = temp_from_u8_adt7461(data, data->temp8[LOCAL_CRIT]); + else if (data->kind == max6646) + temp = temp_from_u8(data->temp8[LOCAL_CRIT]); + else + temp = temp_from_s8(data->temp8[LOCAL_CRIT]); + + data->temp_hyst = hyst_to_reg(temp - val); + err = i2c_smbus_write_byte_data(client, LM90_REG_W_TCRIT_HYST, + data->temp_hyst); + return err; +} + +static const u8 lm90_temp_index[3] = { + LOCAL_TEMP, REMOTE_TEMP, REMOTE2_TEMP +}; + +static const u8 lm90_temp_min_index[3] = { + LOCAL_LOW, REMOTE_LOW, REMOTE2_LOW +}; + +static const u8 lm90_temp_max_index[3] = { + LOCAL_HIGH, REMOTE_HIGH, REMOTE2_HIGH +}; + +static const u8 lm90_temp_crit_index[3] = { + LOCAL_CRIT, REMOTE_CRIT, REMOTE2_CRIT +}; + +static const u8 lm90_temp_emerg_index[3] = { + LOCAL_EMERG, REMOTE_EMERG, REMOTE2_EMERG +}; + +static const u8 lm90_min_alarm_bits[3] = { 5, 3, 11 }; +static const u8 lm90_max_alarm_bits[3] = { 6, 4, 12 }; +static const u8 lm90_crit_alarm_bits[3] = { 0, 1, 9 }; +static const u8 lm90_emergency_alarm_bits[3] = { 15, 13, 14 }; +static const u8 lm90_fault_bits[3] = { 0, 2, 10 }; + +static int lm90_temp_read(struct device *dev, u32 attr, int channel, long *val) +{ + struct lm90_data *data = dev_get_drvdata(dev); + int err; + + mutex_lock(&data->update_lock); + err = lm90_update_device(dev); + mutex_unlock(&data->update_lock); + if (err) + return err; + + switch (attr) { + case hwmon_temp_input: + *val = lm90_get_temp11(data, lm90_temp_index[channel]); + break; + case hwmon_temp_min_alarm: + *val = (data->alarms >> lm90_min_alarm_bits[channel]) & 1; + break; + case hwmon_temp_max_alarm: + *val = (data->alarms >> lm90_max_alarm_bits[channel]) & 1; + break; + case hwmon_temp_crit_alarm: + *val = (data->alarms >> lm90_crit_alarm_bits[channel]) & 1; + break; + case hwmon_temp_emergency_alarm: + *val = (data->alarms >> lm90_emergency_alarm_bits[channel]) & 1; + break; + case hwmon_temp_fault: + *val = (data->alarms >> lm90_fault_bits[channel]) & 1; + break; + case hwmon_temp_min: + if (channel == 0) + *val = lm90_get_temp8(data, + lm90_temp_min_index[channel]); + else + *val = lm90_get_temp11(data, + lm90_temp_min_index[channel]); + break; + case hwmon_temp_max: + if (channel == 0) + *val = lm90_get_temp8(data, + lm90_temp_max_index[channel]); + else + *val = lm90_get_temp11(data, + lm90_temp_max_index[channel]); + break; + case hwmon_temp_crit: + *val = lm90_get_temp8(data, lm90_temp_crit_index[channel]); + break; + case hwmon_temp_crit_hyst: + *val = lm90_get_temphyst(data, lm90_temp_crit_index[channel]); + break; + case hwmon_temp_emergency: + *val = lm90_get_temp8(data, lm90_temp_emerg_index[channel]); + break; + case hwmon_temp_emergency_hyst: + *val = lm90_get_temphyst(data, lm90_temp_emerg_index[channel]); + break; + case hwmon_temp_offset: + *val = lm90_get_temp11(data, REMOTE_OFFSET); + break; + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int lm90_temp_write(struct device *dev, u32 attr, int channel, long val) +{ + struct lm90_data *data = dev_get_drvdata(dev); + int err; + + mutex_lock(&data->update_lock); + + err = lm90_update_device(dev); + if (err) + goto error; + + switch (attr) { + case hwmon_temp_min: + if (channel == 0) + err = lm90_set_temp8(data, + lm90_temp_min_index[channel], + val); + else + err = lm90_set_temp11(data, + lm90_temp_min_index[channel], + val); + break; + case hwmon_temp_max: + if (channel == 0) + err = lm90_set_temp8(data, + lm90_temp_max_index[channel], + val); + else + err = lm90_set_temp11(data, + lm90_temp_max_index[channel], + val); + break; + case hwmon_temp_crit: + err = lm90_set_temp8(data, lm90_temp_crit_index[channel], val); + break; + case hwmon_temp_crit_hyst: + err = lm90_set_temphyst(data, val); + break; + case hwmon_temp_emergency: + err = lm90_set_temp8(data, lm90_temp_emerg_index[channel], val); + break; + case hwmon_temp_offset: + err = lm90_set_temp11(data, REMOTE_OFFSET, val); + break; + default: + err = -EOPNOTSUPP; + break; + } +error: + mutex_unlock(&data->update_lock); + + return err; +} + +static umode_t lm90_temp_is_visible(const void *data, u32 attr, int channel) +{ + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + case hwmon_temp_emergency_alarm: + case hwmon_temp_emergency_hyst: + case hwmon_temp_fault: + return S_IRUGO; + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_crit: + case hwmon_temp_emergency: + case hwmon_temp_offset: + return S_IRUGO | S_IWUSR; + case hwmon_temp_crit_hyst: + if (channel == 0) + return S_IRUGO | S_IWUSR; + return S_IRUGO; + default: + return 0; + } +} + +static int lm90_chip_read(struct device *dev, u32 attr, int channel, long *val) +{ + struct lm90_data *data = dev_get_drvdata(dev); + int err; + + mutex_lock(&data->update_lock); + err = lm90_update_device(dev); + mutex_unlock(&data->update_lock); + if (err) + return err; + + switch (attr) { + case hwmon_chip_update_interval: + *val = data->update_interval; + break; + case hwmon_chip_alarms: + *val = data->alarms; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int lm90_chip_write(struct device *dev, u32 attr, int channel, long val) +{ + struct lm90_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int err; + + mutex_lock(&data->update_lock); + + err = lm90_update_device(dev); + if (err) + goto error; + + switch (attr) { + case hwmon_chip_update_interval: + err = lm90_set_convrate(client, data, + clamp_val(val, 0, 100000)); + break; + default: + err = -EOPNOTSUPP; + break; + } +error: + mutex_unlock(&data->update_lock); + + return err; +} + +static umode_t lm90_chip_is_visible(const void *data, u32 attr, int channel) +{ + switch (attr) { + case hwmon_chip_update_interval: + return S_IRUGO | S_IWUSR; + case hwmon_chip_alarms: + return S_IRUGO; + default: + return 0; + } +} + +static int lm90_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_chip: + return lm90_chip_read(dev, attr, channel, val); + case hwmon_temp: + return lm90_temp_read(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int lm90_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_chip: + return lm90_chip_write(dev, attr, channel, val); + case hwmon_temp: + return lm90_temp_write(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t lm90_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_chip: + return lm90_chip_is_visible(data, attr, channel); + case hwmon_temp: + return lm90_temp_is_visible(data, attr, channel); + default: + return 0; + } +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int lm90_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int address = client->addr; + const char *name = NULL; + int man_id, chip_id, config1, config2, convrate; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* detection and identification */ + man_id = i2c_smbus_read_byte_data(client, LM90_REG_R_MAN_ID); + chip_id = i2c_smbus_read_byte_data(client, LM90_REG_R_CHIP_ID); + config1 = i2c_smbus_read_byte_data(client, LM90_REG_R_CONFIG1); + convrate = i2c_smbus_read_byte_data(client, LM90_REG_R_CONVRATE); + if (man_id < 0 || chip_id < 0 || config1 < 0 || convrate < 0) + return -ENODEV; + + if (man_id == 0x01 || man_id == 0x5C || man_id == 0xA1) { + config2 = i2c_smbus_read_byte_data(client, LM90_REG_R_CONFIG2); + if (config2 < 0) + return -ENODEV; + } + + if ((address == 0x4C || address == 0x4D) + && man_id == 0x01) { /* National Semiconductor */ + if ((config1 & 0x2A) == 0x00 + && (config2 & 0xF8) == 0x00 + && convrate <= 0x09) { + if (address == 0x4C + && (chip_id & 0xF0) == 0x20) { /* LM90 */ + name = "lm90"; + } else + if ((chip_id & 0xF0) == 0x30) { /* LM89/LM99 */ + name = "lm99"; + dev_info(&adapter->dev, + "Assuming LM99 chip at 0x%02x\n", + address); + dev_info(&adapter->dev, + "If it is an LM89, instantiate it " + "with the new_device sysfs " + "interface\n"); + } else + if (address == 0x4C + && (chip_id & 0xF0) == 0x10) { /* LM86 */ + name = "lm86"; + } + } + } else + if ((address == 0x4C || address == 0x4D) + && man_id == 0x41) { /* Analog Devices */ + if ((chip_id & 0xF0) == 0x40 /* ADM1032 */ + && (config1 & 0x3F) == 0x00 + && convrate <= 0x0A) { + name = "adm1032"; + /* + * The ADM1032 supports PEC, but only if combined + * transactions are not used. + */ + if (i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_BYTE)) + info->flags |= I2C_CLIENT_PEC; + } else + if (chip_id == 0x51 /* ADT7461 */ + && (config1 & 0x1B) == 0x00 + && convrate <= 0x0A) { + name = "adt7461"; + } else + if (chip_id == 0x57 /* ADT7461A, NCT1008 */ + && (config1 & 0x1B) == 0x00 + && convrate <= 0x0A) { + name = "adt7461a"; + } + } else + if (man_id == 0x4D) { /* Maxim */ + int emerg, emerg2, status2; + + /* + * We read MAX6659_REG_R_REMOTE_EMERG twice, and re-read + * LM90_REG_R_MAN_ID in between. If MAX6659_REG_R_REMOTE_EMERG + * exists, both readings will reflect the same value. Otherwise, + * the readings will be different. + */ + emerg = i2c_smbus_read_byte_data(client, + MAX6659_REG_R_REMOTE_EMERG); + man_id = i2c_smbus_read_byte_data(client, + LM90_REG_R_MAN_ID); + emerg2 = i2c_smbus_read_byte_data(client, + MAX6659_REG_R_REMOTE_EMERG); + status2 = i2c_smbus_read_byte_data(client, + MAX6696_REG_R_STATUS2); + if (emerg < 0 || man_id < 0 || emerg2 < 0 || status2 < 0) + return -ENODEV; + + /* + * The MAX6657, MAX6658 and MAX6659 do NOT have a chip_id + * register. Reading from that address will return the last + * read value, which in our case is those of the man_id + * register. Likewise, the config1 register seems to lack a + * low nibble, so the value will be those of the previous + * read, so in our case those of the man_id register. + * MAX6659 has a third set of upper temperature limit registers. + * Those registers also return values on MAX6657 and MAX6658, + * thus the only way to detect MAX6659 is by its address. + * For this reason it will be mis-detected as MAX6657 if its + * address is 0x4C. + */ + if (chip_id == man_id + && (address == 0x4C || address == 0x4D || address == 0x4E) + && (config1 & 0x1F) == (man_id & 0x0F) + && convrate <= 0x09) { + if (address == 0x4C) + name = "max6657"; + else + name = "max6659"; + } else + /* + * Even though MAX6695 and MAX6696 do not have a chip ID + * register, reading it returns 0x01. Bit 4 of the config1 + * register is unused and should return zero when read. Bit 0 of + * the status2 register is unused and should return zero when + * read. + * + * MAX6695 and MAX6696 have an additional set of temperature + * limit registers. We can detect those chips by checking if + * one of those registers exists. + */ + if (chip_id == 0x01 + && (config1 & 0x10) == 0x00 + && (status2 & 0x01) == 0x00 + && emerg == emerg2 + && convrate <= 0x07) { + name = "max6696"; + } else + /* + * The chip_id register of the MAX6680 and MAX6681 holds the + * revision of the chip. The lowest bit of the config1 register + * is unused and should return zero when read, so should the + * second to last bit of config1 (software reset). + */ + if (chip_id == 0x01 + && (config1 & 0x03) == 0x00 + && convrate <= 0x07) { + name = "max6680"; + } else + /* + * The chip_id register of the MAX6646/6647/6649 holds the + * revision of the chip. The lowest 6 bits of the config1 + * register are unused and should return zero when read. + */ + if (chip_id == 0x59 + && (config1 & 0x3f) == 0x00 + && convrate <= 0x07) { + name = "max6646"; + } + } else + if (address == 0x4C + && man_id == 0x5C) { /* Winbond/Nuvoton */ + if ((config1 & 0x2A) == 0x00 + && (config2 & 0xF8) == 0x00) { + if (chip_id == 0x01 /* W83L771W/G */ + && convrate <= 0x09) { + name = "w83l771"; + } else + if ((chip_id & 0xFE) == 0x10 /* W83L771AWG/ASG */ + && convrate <= 0x08) { + name = "w83l771"; + } + } + } else + if (address >= 0x48 && address <= 0x4F + && man_id == 0xA1) { /* NXP Semiconductor/Philips */ + if (chip_id == 0x00 + && (config1 & 0x2A) == 0x00 + && (config2 & 0xFE) == 0x00 + && convrate <= 0x09) { + name = "sa56004"; + } + } else + if ((address == 0x4C || address == 0x4D) + && man_id == 0x47) { /* GMT */ + if (chip_id == 0x01 /* G781 */ + && (config1 & 0x3F) == 0x00 + && convrate <= 0x08) + name = "g781"; + } else + if (address == 0x4C + && man_id == 0x55) { /* Texas Instruments */ + int local_ext; + + local_ext = i2c_smbus_read_byte_data(client, + TMP451_REG_R_LOCAL_TEMPL); + + if (chip_id == 0x00 /* TMP451 */ + && (config1 & 0x1B) == 0x00 + && convrate <= 0x09 + && (local_ext & 0x0F) == 0x00) + name = "tmp451"; + } + + if (!name) { /* identification failed */ + dev_dbg(&adapter->dev, + "Unsupported chip at 0x%02x (man_id=0x%02X, " + "chip_id=0x%02X)\n", address, man_id, chip_id); + return -ENODEV; + } + + strlcpy(info->type, name, I2C_NAME_SIZE); + + return 0; +} + +static void lm90_restore_conf(void *_data) +{ + struct lm90_data *data = _data; + struct i2c_client *client = data->client; + + /* Restore initial configuration */ + lm90_write_convrate(client, data, data->convrate_orig); + i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, + data->config_orig); +} + +static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) +{ + int config, convrate; + + convrate = lm90_read_reg(client, LM90_REG_R_CONVRATE); + if (convrate < 0) + return convrate; + data->convrate_orig = convrate; + + /* + * Start the conversions. + */ + config = lm90_read_reg(client, LM90_REG_R_CONFIG1); + if (config < 0) + return config; + data->config_orig = config; + + lm90_set_convrate(client, data, 500); /* 500ms; 2Hz conversion rate */ + + /* Check Temperature Range Select */ + if (data->kind == adt7461 || data->kind == tmp451) { + if (config & 0x04) + data->flags |= LM90_FLAG_ADT7461_EXT; + } + + /* + * Put MAX6680/MAX8881 into extended resolution (bit 0x10, + * 0.125 degree resolution) and range (0x08, extend range + * to -64 degree) mode for the remote temperature sensor. + */ + if (data->kind == max6680) + config |= 0x18; + + /* + * Select external channel 0 for max6695/96 + */ + if (data->kind == max6696) + config &= ~0x08; + + config &= 0xBF; /* run */ + if (config != data->config_orig) /* Only write if changed */ + i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, config); + + return devm_add_action_or_reset(&client->dev, lm90_restore_conf, data); +} + +static bool lm90_is_tripped(struct i2c_client *client, u16 *status) +{ + struct lm90_data *data = i2c_get_clientdata(client); + int st, st2 = 0; + + st = lm90_read_reg(client, LM90_REG_R_STATUS); + if (st < 0) + return false; + + if (data->kind == max6696) { + st2 = lm90_read_reg(client, MAX6696_REG_R_STATUS2); + if (st2 < 0) + return false; + } + + *status = st | (st2 << 8); + + if ((st & 0x7f) == 0 && (st2 & 0xfe) == 0) + return false; + + if ((st & (LM90_STATUS_LLOW | LM90_STATUS_LHIGH | LM90_STATUS_LTHRM)) || + (st2 & MAX6696_STATUS2_LOT2)) + dev_warn(&client->dev, + "temp%d out of range, please check!\n", 1); + if ((st & (LM90_STATUS_RLOW | LM90_STATUS_RHIGH | LM90_STATUS_RTHRM)) || + (st2 & MAX6696_STATUS2_ROT2)) + dev_warn(&client->dev, + "temp%d out of range, please check!\n", 2); + if (st & LM90_STATUS_ROPEN) + dev_warn(&client->dev, + "temp%d diode open, please check!\n", 2); + if (st2 & (MAX6696_STATUS2_R2LOW | MAX6696_STATUS2_R2HIGH | + MAX6696_STATUS2_R2THRM | MAX6696_STATUS2_R2OT2)) + dev_warn(&client->dev, + "temp%d out of range, please check!\n", 3); + if (st2 & MAX6696_STATUS2_R2OPEN) + dev_warn(&client->dev, + "temp%d diode open, please check!\n", 3); + + return true; +} + +static irqreturn_t lm90_irq_thread(int irq, void *dev_id) +{ + struct i2c_client *client = dev_id; + u16 status; + + if (lm90_is_tripped(client, &status)) + return IRQ_HANDLED; + else + return IRQ_NONE; +} + +static void lm90_remove_pec(void *dev) +{ + device_remove_file(dev, &dev_attr_pec); +} + +static void lm90_regulator_disable(void *regulator) +{ + regulator_disable(regulator); +} + +static const u32 lm90_chip_config[] = { + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL | HWMON_C_ALARMS, + 0 +}; + +static const struct hwmon_channel_info lm90_chip_info = { + .type = hwmon_chip, + .config = lm90_chip_config, +}; + + +static const struct hwmon_ops lm90_ops = { + .is_visible = lm90_is_visible, + .read = lm90_read, + .write = lm90_write, +}; + +static int lm90_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct i2c_adapter *adapter = to_i2c_adapter(dev->parent); + struct hwmon_channel_info *info; + struct regulator *regulator; + struct device *hwmon_dev; + struct lm90_data *data; + int err; + + regulator = devm_regulator_get(dev, "vcc"); + if (IS_ERR(regulator)) + return PTR_ERR(regulator); + + err = regulator_enable(regulator); + if (err < 0) { + dev_err(dev, "Failed to enable regulator: %d\n", err); + return err; + } + + err = devm_add_action_or_reset(dev, lm90_regulator_disable, regulator); + if (err) + return err; + + data = devm_kzalloc(dev, sizeof(struct lm90_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Set the device type */ + if (client->dev.of_node) + data->kind = (enum chips)of_device_get_match_data(&client->dev); + else + data->kind = id->driver_data; + if (data->kind == adm1032) { + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + client->flags &= ~I2C_CLIENT_PEC; + } + + /* + * Different devices have different alarm bits triggering the + * ALERT# output + */ + data->alert_alarms = lm90_params[data->kind].alert_alarms; + + /* Set chip capabilities */ + data->flags = lm90_params[data->kind].flags; + + data->chip.ops = &lm90_ops; + data->chip.info = data->info; + + data->info[0] = &lm90_chip_info; + data->info[1] = &data->temp_info; + + info = &data->temp_info; + info->type = hwmon_temp; + info->config = data->channel_config; + + data->channel_config[0] = HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM; + data->channel_config[1] = HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT; + + if (data->flags & LM90_HAVE_OFFSET) + data->channel_config[1] |= HWMON_T_OFFSET; + + if (data->flags & LM90_HAVE_EMERGENCY) { + data->channel_config[0] |= HWMON_T_EMERGENCY | + HWMON_T_EMERGENCY_HYST; + data->channel_config[1] |= HWMON_T_EMERGENCY | + HWMON_T_EMERGENCY_HYST; + } + + if (data->flags & LM90_HAVE_EMERGENCY_ALARM) { + data->channel_config[0] |= HWMON_T_EMERGENCY_ALARM; + data->channel_config[1] |= HWMON_T_EMERGENCY_ALARM; + } + + if (data->flags & LM90_HAVE_TEMP3) { + data->channel_config[2] = HWMON_T_INPUT | + HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | + HWMON_T_EMERGENCY | HWMON_T_EMERGENCY_HYST | + HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_EMERGENCY_ALARM | + HWMON_T_FAULT; + } + + data->reg_local_ext = lm90_params[data->kind].reg_local_ext; + + /* Set maximum conversion rate */ + data->max_convrate = lm90_params[data->kind].max_convrate; + + /* Initialize the LM90 chip */ + err = lm90_init_client(client, data); + if (err < 0) { + dev_err(dev, "Failed to initialize device\n"); + return err; + } + + /* + * The 'pec' attribute is attached to the i2c device and thus created + * separately. + */ + if (client->flags & I2C_CLIENT_PEC) { + err = device_create_file(dev, &dev_attr_pec); + if (err) + return err; + err = devm_add_action_or_reset(dev, lm90_remove_pec, dev); + if (err) + return err; + } + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, &data->chip, + NULL); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + if (client->irq) { + dev_dbg(dev, "IRQ: %d\n", client->irq); + err = devm_request_threaded_irq(dev, client->irq, + NULL, lm90_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "lm90", client); + if (err < 0) { + dev_err(dev, "cannot request IRQ %d\n", client->irq); + return err; + } + } + + return 0; +} + +static void lm90_alert(struct i2c_client *client, enum i2c_alert_protocol type, + unsigned int flag) +{ + u16 alarms; + + if (type != I2C_PROTOCOL_SMBUS_ALERT) + return; + + if (lm90_is_tripped(client, &alarms)) { + /* + * Disable ALERT# output, because these chips don't implement + * SMBus alert correctly; they should only hold the alert line + * low briefly. + */ + struct lm90_data *data = i2c_get_clientdata(client); + + if ((data->flags & LM90_HAVE_BROKEN_ALERT) && + (alarms & data->alert_alarms)) { + int config; + + dev_dbg(&client->dev, "Disabling ALERT#\n"); + config = lm90_read_reg(client, LM90_REG_R_CONFIG1); + if (config >= 0) + i2c_smbus_write_byte_data(client, + LM90_REG_W_CONFIG1, + config | 0x80); + } + } else { + dev_info(&client->dev, "Everything OK\n"); + } +} + +static struct i2c_driver lm90_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm90", + .of_match_table = of_match_ptr(lm90_of_match), + }, + .probe = lm90_probe, + .alert = lm90_alert, + .id_table = lm90_id, + .detect = lm90_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(lm90_driver); + +MODULE_AUTHOR("Jean Delvare "); +MODULE_DESCRIPTION("LM90/ADM1032 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm92.c b/drivers/hwmon/lm92.c new file mode 100644 index 000000000..d40fe5122 --- /dev/null +++ b/drivers/hwmon/lm92.c @@ -0,0 +1,354 @@ +/* + * lm92 - Hardware monitoring driver + * Copyright (C) 2005-2008 Jean Delvare + * + * Based on the lm90 driver, with some ideas taken from the lm_sensors + * lm92 driver as well. + * + * The LM92 is a sensor chip made by National Semiconductor. It reports + * its own temperature with a 0.0625 deg resolution and a 0.33 deg + * accuracy. Complete datasheet can be obtained from National's website + * at: + * http://www.national.com/pf/LM/LM92.html + * + * This driver also supports the MAX6635 sensor chip made by Maxim. + * This chip is compatible with the LM92, but has a lesser accuracy + * (1.0 deg). Complete datasheet can be obtained from Maxim's website + * at: + * http://www.maxim-ic.com/quick_view2.cfm/qv_pk/3074 + * + * Since the LM92 was the first chipset supported by this driver, most + * comments will refer to this chipset, but are actually general and + * concern all supported chipsets, unless mentioned otherwise. + * + * Support could easily be added for the National Semiconductor LM76 + * and Maxim MAX6633 and MAX6634 chips, which are mostly compatible + * with the LM92. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The LM92 and MAX6635 have 2 two-state pins for address selection, + * resulting in 4 possible addresses. + */ +static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, + I2C_CLIENT_END }; +enum chips { lm92, max6635 }; + +/* The LM92 registers */ +#define LM92_REG_CONFIG 0x01 /* 8-bit, RW */ +#define LM92_REG_TEMP 0x00 /* 16-bit, RO */ +#define LM92_REG_TEMP_HYST 0x02 /* 16-bit, RW */ +#define LM92_REG_TEMP_CRIT 0x03 /* 16-bit, RW */ +#define LM92_REG_TEMP_LOW 0x04 /* 16-bit, RW */ +#define LM92_REG_TEMP_HIGH 0x05 /* 16-bit, RW */ +#define LM92_REG_MAN_ID 0x07 /* 16-bit, RO, LM92 only */ + +/* + * The LM92 uses signed 13-bit values with LSB = 0.0625 degree Celsius, + * left-justified in 16-bit registers. No rounding is done, with such + * a resolution it's just not worth it. Note that the MAX6635 doesn't + * make use of the 4 lower bits for limits (i.e. effective resolution + * for limits is 1 degree Celsius). + */ +static inline int TEMP_FROM_REG(s16 reg) +{ + return reg / 8 * 625 / 10; +} + +static inline s16 TEMP_TO_REG(long val) +{ + val = clamp_val(val, -60000, 160000); + return val * 10 / 625 * 8; +} + +/* Alarm flags are stored in the 3 LSB of the temperature register */ +static inline u8 ALARMS_FROM_REG(s16 reg) +{ + return reg & 0x0007; +} + +enum temp_index { + t_input, + t_crit, + t_min, + t_max, + t_hyst, + t_num_regs +}; + +static const u8 regs[t_num_regs] = { + [t_input] = LM92_REG_TEMP, + [t_crit] = LM92_REG_TEMP_CRIT, + [t_min] = LM92_REG_TEMP_LOW, + [t_max] = LM92_REG_TEMP_HIGH, + [t_hyst] = LM92_REG_TEMP_HYST, +}; + +/* Client data (each client gets its own) */ +struct lm92_data { + struct i2c_client *client; + struct mutex update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* registers values */ + s16 temp[t_num_regs]; /* index with enum temp_index */ +}; + +/* + * Sysfs attributes and callback functions + */ + +static struct lm92_data *lm92_update_device(struct device *dev) +{ + struct lm92_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) + || !data->valid) { + dev_dbg(&client->dev, "Updating lm92 data\n"); + for (i = 0; i < t_num_regs; i++) { + data->temp[i] = + i2c_smbus_read_word_swapped(client, regs[i]); + } + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm92_data *data = lm92_update_device(dev); + + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index])); +} + +static ssize_t set_temp(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm92_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int nr = attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp[nr] = TEMP_TO_REG(val); + i2c_smbus_write_word_swapped(client, regs[nr], data->temp[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp_hyst(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm92_data *data = lm92_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index]) + - TEMP_FROM_REG(data->temp[t_hyst])); +} + +static ssize_t temp1_min_hyst_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm92_data *data = lm92_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[t_min]) + + TEMP_FROM_REG(data->temp[t_hyst])); +} + +static ssize_t set_temp_hyst(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct lm92_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + val = clamp_val(val, -120000, 220000); + mutex_lock(&data->update_lock); + data->temp[t_hyst] = + TEMP_TO_REG(TEMP_FROM_REG(data->temp[attr->index]) - val); + i2c_smbus_write_word_swapped(client, LM92_REG_TEMP_HYST, + data->temp[t_hyst]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm92_data *data = lm92_update_device(dev); + return sprintf(buf, "%d\n", ALARMS_FROM_REG(data->temp[t_input])); +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct lm92_data *data = lm92_update_device(dev); + return sprintf(buf, "%d\n", (data->temp[t_input] >> bitnr) & 1); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, t_input); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, show_temp, set_temp, + t_crit); +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO, show_temp_hyst, + set_temp_hyst, t_crit); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp, set_temp, + t_min); +static DEVICE_ATTR_RO(temp1_min_hyst); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp, set_temp, + t_max); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IRUGO, show_temp_hyst, NULL, t_max); +static DEVICE_ATTR_RO(alarms); +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 1); + +/* + * Detection and registration + */ + +static void lm92_init_client(struct i2c_client *client) +{ + u8 config; + + /* Start the conversions if needed */ + config = i2c_smbus_read_byte_data(client, LM92_REG_CONFIG); + if (config & 0x01) + i2c_smbus_write_byte_data(client, LM92_REG_CONFIG, + config & 0xFE); +} + +static struct attribute *lm92_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &dev_attr_temp1_min_hyst.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &dev_attr_alarms.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(lm92); + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int lm92_detect(struct i2c_client *new_client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = new_client->adapter; + u8 config; + u16 man_id; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + config = i2c_smbus_read_byte_data(new_client, LM92_REG_CONFIG); + man_id = i2c_smbus_read_word_data(new_client, LM92_REG_MAN_ID); + + if ((config & 0xe0) == 0x00 && man_id == 0x0180) + pr_info("lm92: Found National Semiconductor LM92 chip\n"); + else + return -ENODEV; + + strlcpy(info->type, "lm92", I2C_NAME_SIZE); + + return 0; +} + +static int lm92_probe(struct i2c_client *new_client, + const struct i2c_device_id *id) +{ + struct device *hwmon_dev; + struct lm92_data *data; + + data = devm_kzalloc(&new_client->dev, sizeof(struct lm92_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = new_client; + mutex_init(&data->update_lock); + + /* Initialize the chipset */ + lm92_init_client(new_client); + + hwmon_dev = devm_hwmon_device_register_with_groups(&new_client->dev, + new_client->name, + data, lm92_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + + +/* + * Module and driver stuff + */ + +static const struct i2c_device_id lm92_id[] = { + { "lm92", lm92 }, + { "max6635", max6635 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm92_id); + +static struct i2c_driver lm92_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm92", + }, + .probe = lm92_probe, + .id_table = lm92_id, + .detect = lm92_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(lm92_driver); + +MODULE_AUTHOR("Jean Delvare "); +MODULE_DESCRIPTION("LM92/MAX6635 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm93.c b/drivers/hwmon/lm93.c new file mode 100644 index 000000000..77a0a8339 --- /dev/null +++ b/drivers/hwmon/lm93.c @@ -0,0 +1,2781 @@ +/* + * lm93.c - Part of lm_sensors, Linux kernel modules for hardware monitoring + * + * Author/Maintainer: Mark M. Hoffman + * Copyright (c) 2004 Utilitek Systems, Inc. + * + * derived in part from lm78.c: + * Copyright (c) 1998, 1999 Frodo Looijaard + * + * derived in part from lm85.c: + * Copyright (c) 2002, 2003 Philip Pokorny + * Copyright (c) 2003 Margit Schubert-While + * + * derived in part from w83l785ts.c: + * Copyright (c) 2003-2004 Jean Delvare + * + * Ported to Linux 2.6 by Eric J. Bowersox + * Copyright (c) 2005 Aspen Systems, Inc. + * + * Adapted to 2.6.20 by Carsten Emde + * Copyright (c) 2006 Carsten Emde, Open Source Automation Development Lab + * + * Modified for mainline integration by Hans J. Koch + * Copyright (c) 2007 Hans J. Koch, Linutronix GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* LM93 REGISTER ADDRESSES */ + +/* miscellaneous */ +#define LM93_REG_MFR_ID 0x3e +#define LM93_REG_VER 0x3f +#define LM93_REG_STATUS_CONTROL 0xe2 +#define LM93_REG_CONFIG 0xe3 +#define LM93_REG_SLEEP_CONTROL 0xe4 + +/* alarm values start here */ +#define LM93_REG_HOST_ERROR_1 0x48 + +/* voltage inputs: in1-in16 (nr => 0-15) */ +#define LM93_REG_IN(nr) (0x56 + (nr)) +#define LM93_REG_IN_MIN(nr) (0x90 + (nr) * 2) +#define LM93_REG_IN_MAX(nr) (0x91 + (nr) * 2) + +/* temperature inputs: temp1-temp4 (nr => 0-3) */ +#define LM93_REG_TEMP(nr) (0x50 + (nr)) +#define LM93_REG_TEMP_MIN(nr) (0x78 + (nr) * 2) +#define LM93_REG_TEMP_MAX(nr) (0x79 + (nr) * 2) + +/* temp[1-4]_auto_boost (nr => 0-3) */ +#define LM93_REG_BOOST(nr) (0x80 + (nr)) + +/* #PROCHOT inputs: prochot1-prochot2 (nr => 0-1) */ +#define LM93_REG_PROCHOT_CUR(nr) (0x67 + (nr) * 2) +#define LM93_REG_PROCHOT_AVG(nr) (0x68 + (nr) * 2) +#define LM93_REG_PROCHOT_MAX(nr) (0xb0 + (nr)) + +/* fan tach inputs: fan1-fan4 (nr => 0-3) */ +#define LM93_REG_FAN(nr) (0x6e + (nr) * 2) +#define LM93_REG_FAN_MIN(nr) (0xb4 + (nr) * 2) + +/* pwm outputs: pwm1-pwm2 (nr => 0-1, reg => 0-3) */ +#define LM93_REG_PWM_CTL(nr, reg) (0xc8 + (reg) + (nr) * 4) +#define LM93_PWM_CTL1 0x0 +#define LM93_PWM_CTL2 0x1 +#define LM93_PWM_CTL3 0x2 +#define LM93_PWM_CTL4 0x3 + +/* GPIO input state */ +#define LM93_REG_GPI 0x6b + +/* vid inputs: vid1-vid2 (nr => 0-1) */ +#define LM93_REG_VID(nr) (0x6c + (nr)) + +/* vccp1 & vccp2: VID relative inputs (nr => 0-1) */ +#define LM93_REG_VCCP_LIMIT_OFF(nr) (0xb2 + (nr)) + +/* temp[1-4]_auto_boost_hyst */ +#define LM93_REG_BOOST_HYST_12 0xc0 +#define LM93_REG_BOOST_HYST_34 0xc1 +#define LM93_REG_BOOST_HYST(nr) (0xc0 + (nr)/2) + +/* temp[1-4]_auto_pwm_[min|hyst] */ +#define LM93_REG_PWM_MIN_HYST_12 0xc3 +#define LM93_REG_PWM_MIN_HYST_34 0xc4 +#define LM93_REG_PWM_MIN_HYST(nr) (0xc3 + (nr)/2) + +/* prochot_override & prochot_interval */ +#define LM93_REG_PROCHOT_OVERRIDE 0xc6 +#define LM93_REG_PROCHOT_INTERVAL 0xc7 + +/* temp[1-4]_auto_base (nr => 0-3) */ +#define LM93_REG_TEMP_BASE(nr) (0xd0 + (nr)) + +/* temp[1-4]_auto_offsets (step => 0-11) */ +#define LM93_REG_TEMP_OFFSET(step) (0xd4 + (step)) + +/* #PROCHOT & #VRDHOT PWM ramp control */ +#define LM93_REG_PWM_RAMP_CTL 0xbf + +/* miscellaneous */ +#define LM93_REG_SFC1 0xbc +#define LM93_REG_SFC2 0xbd +#define LM93_REG_GPI_VID_CTL 0xbe +#define LM93_REG_SF_TACH_TO_PWM 0xe0 + +/* error masks */ +#define LM93_REG_GPI_ERR_MASK 0xec +#define LM93_REG_MISC_ERR_MASK 0xed + +/* LM93 REGISTER VALUES */ +#define LM93_MFR_ID 0x73 +#define LM93_MFR_ID_PROTOTYPE 0x72 + +/* LM94 REGISTER VALUES */ +#define LM94_MFR_ID_2 0x7a +#define LM94_MFR_ID 0x79 +#define LM94_MFR_ID_PROTOTYPE 0x78 + +/* SMBus capabilities */ +#define LM93_SMBUS_FUNC_FULL (I2C_FUNC_SMBUS_BYTE_DATA | \ + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA) +#define LM93_SMBUS_FUNC_MIN (I2C_FUNC_SMBUS_BYTE_DATA | \ + I2C_FUNC_SMBUS_WORD_DATA) + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END }; + +/* Insmod parameters */ + +static bool disable_block; +module_param(disable_block, bool, 0); +MODULE_PARM_DESC(disable_block, + "Set to non-zero to disable SMBus block data transactions."); + +static bool init; +module_param(init, bool, 0); +MODULE_PARM_DESC(init, "Set to non-zero to force chip initialization."); + +static int vccp_limit_type[2] = {0, 0}; +module_param_array(vccp_limit_type, int, NULL, 0); +MODULE_PARM_DESC(vccp_limit_type, "Configures in7 and in8 limit modes."); + +static int vid_agtl; +module_param(vid_agtl, int, 0); +MODULE_PARM_DESC(vid_agtl, "Configures VID pin input thresholds."); + +/* Driver data */ +static struct i2c_driver lm93_driver; + +/* LM93 BLOCK READ COMMANDS */ +static const struct { u8 cmd; u8 len; } lm93_block_read_cmds[12] = { + { 0xf2, 8 }, + { 0xf3, 8 }, + { 0xf4, 6 }, + { 0xf5, 16 }, + { 0xf6, 4 }, + { 0xf7, 8 }, + { 0xf8, 12 }, + { 0xf9, 32 }, + { 0xfa, 8 }, + { 0xfb, 8 }, + { 0xfc, 16 }, + { 0xfd, 9 }, +}; + +/* + * ALARMS: SYSCTL format described further below + * REG: 64 bits in 8 registers, as immediately below + */ +struct block1_t { + u8 host_status_1; + u8 host_status_2; + u8 host_status_3; + u8 host_status_4; + u8 p1_prochot_status; + u8 p2_prochot_status; + u8 gpi_status; + u8 fan_status; +}; + +/* + * Client-specific data + */ +struct lm93_data { + struct i2c_client *client; + + struct mutex update_lock; + unsigned long last_updated; /* In jiffies */ + + /* client update function */ + void (*update)(struct lm93_data *, struct i2c_client *); + + char valid; /* !=0 if following fields are valid */ + + /* register values, arranged by block read groups */ + struct block1_t block1; + + /* + * temp1 - temp4: unfiltered readings + * temp1 - temp2: filtered readings + */ + u8 block2[6]; + + /* vin1 - vin16: readings */ + u8 block3[16]; + + /* prochot1 - prochot2: readings */ + struct { + u8 cur; + u8 avg; + } block4[2]; + + /* fan counts 1-4 => 14-bits, LE, *left* justified */ + u16 block5[4]; + + /* block6 has a lot of data we don't need */ + struct { + u8 min; + u8 max; + } temp_lim[4]; + + /* vin1 - vin16: low and high limits */ + struct { + u8 min; + u8 max; + } block7[16]; + + /* fan count limits 1-4 => same format as block5 */ + u16 block8[4]; + + /* pwm control registers (2 pwms, 4 regs) */ + u8 block9[2][4]; + + /* auto/pwm base temp and offset temp registers */ + struct { + u8 base[4]; + u8 offset[12]; + } block10; + + /* master config register */ + u8 config; + + /* VID1 & VID2 => register format, 6-bits, right justified */ + u8 vid[2]; + + /* prochot1 - prochot2: limits */ + u8 prochot_max[2]; + + /* vccp1 & vccp2 (in7 & in8): VID relative limits (register format) */ + u8 vccp_limits[2]; + + /* GPIO input state (register format, i.e. inverted) */ + u8 gpi; + + /* #PROCHOT override (register format) */ + u8 prochot_override; + + /* #PROCHOT intervals (register format) */ + u8 prochot_interval; + + /* Fan Boost Temperatures (register format) */ + u8 boost[4]; + + /* Fan Boost Hysteresis (register format) */ + u8 boost_hyst[2]; + + /* Temperature Zone Min. PWM & Hysteresis (register format) */ + u8 auto_pwm_min_hyst[2]; + + /* #PROCHOT & #VRDHOT PWM Ramp Control */ + u8 pwm_ramp_ctl; + + /* miscellaneous setup regs */ + u8 sfc1; + u8 sfc2; + u8 sf_tach_to_pwm; + + /* + * The two PWM CTL2 registers can read something other than what was + * last written for the OVR_DC field (duty cycle override). So, we + * save the user-commanded value here. + */ + u8 pwm_override[2]; +}; + +/* + * VID: mV + * REG: 6-bits, right justified, *always* using Intel VRM/VRD 10 + */ +static int LM93_VID_FROM_REG(u8 reg) +{ + return vid_from_reg((reg & 0x3f), 100); +} + +/* min, max, and nominal register values, per channel (u8) */ +static const u8 lm93_vin_reg_min[16] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xae, +}; +static const u8 lm93_vin_reg_max[16] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, +}; +/* + * Values from the datasheet. They're here for documentation only. + * static const u8 lm93_vin_reg_nom[16] = { + * 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + * 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x40, 0xc0, + * }; + */ + +/* min, max, and nominal voltage readings, per channel (mV)*/ +static const unsigned long lm93_vin_val_min[16] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3000, +}; + +static const unsigned long lm93_vin_val_max[16] = { + 1236, 1236, 1236, 1600, 2000, 2000, 1600, 1600, + 4400, 6500, 3333, 2625, 1312, 1312, 1236, 3600, +}; +/* + * Values from the datasheet. They're here for documentation only. + * static const unsigned long lm93_vin_val_nom[16] = { + * 927, 927, 927, 1200, 1500, 1500, 1200, 1200, + * 3300, 5000, 2500, 1969, 984, 984, 309, 3300, + * }; + */ + +static unsigned LM93_IN_FROM_REG(int nr, u8 reg) +{ + const long uv_max = lm93_vin_val_max[nr] * 1000; + const long uv_min = lm93_vin_val_min[nr] * 1000; + + const long slope = (uv_max - uv_min) / + (lm93_vin_reg_max[nr] - lm93_vin_reg_min[nr]); + const long intercept = uv_min - slope * lm93_vin_reg_min[nr]; + + return (slope * reg + intercept + 500) / 1000; +} + +/* + * IN: mV, limits determined by channel nr + * REG: scaling determined by channel nr + */ +static u8 LM93_IN_TO_REG(int nr, unsigned val) +{ + /* range limit */ + const long mv = clamp_val(val, + lm93_vin_val_min[nr], lm93_vin_val_max[nr]); + + /* try not to lose too much precision here */ + const long uv = mv * 1000; + const long uv_max = lm93_vin_val_max[nr] * 1000; + const long uv_min = lm93_vin_val_min[nr] * 1000; + + /* convert */ + const long slope = (uv_max - uv_min) / + (lm93_vin_reg_max[nr] - lm93_vin_reg_min[nr]); + const long intercept = uv_min - slope * lm93_vin_reg_min[nr]; + + u8 result = ((uv - intercept + (slope/2)) / slope); + result = clamp_val(result, + lm93_vin_reg_min[nr], lm93_vin_reg_max[nr]); + return result; +} + +/* vid in mV, upper == 0 indicates low limit, otherwise upper limit */ +static unsigned LM93_IN_REL_FROM_REG(u8 reg, int upper, int vid) +{ + const long uv_offset = upper ? (((reg >> 4 & 0x0f) + 1) * 12500) : + (((reg >> 0 & 0x0f) + 1) * -25000); + const long uv_vid = vid * 1000; + return (uv_vid + uv_offset + 5000) / 10000; +} + +#define LM93_IN_MIN_FROM_REG(reg, vid) LM93_IN_REL_FROM_REG((reg), 0, (vid)) +#define LM93_IN_MAX_FROM_REG(reg, vid) LM93_IN_REL_FROM_REG((reg), 1, (vid)) + +/* + * vid in mV , upper == 0 indicates low limit, otherwise upper limit + * upper also determines which nibble of the register is returned + * (the other nibble will be 0x0) + */ +static u8 LM93_IN_REL_TO_REG(unsigned val, int upper, int vid) +{ + long uv_offset = vid * 1000 - val * 10000; + if (upper) { + uv_offset = clamp_val(uv_offset, 12500, 200000); + return (u8)((uv_offset / 12500 - 1) << 4); + } else { + uv_offset = clamp_val(uv_offset, -400000, -25000); + return (u8)((uv_offset / -25000 - 1) << 0); + } +} + +/* + * TEMP: 1/1000 degrees C (-128C to +127C) + * REG: 1C/bit, two's complement + */ +static int LM93_TEMP_FROM_REG(u8 reg) +{ + return (s8)reg * 1000; +} + +#define LM93_TEMP_MIN (-128000) +#define LM93_TEMP_MAX (127000) + +/* + * TEMP: 1/1000 degrees C (-128C to +127C) + * REG: 1C/bit, two's complement + */ +static u8 LM93_TEMP_TO_REG(long temp) +{ + int ntemp = clamp_val(temp, LM93_TEMP_MIN, LM93_TEMP_MAX); + ntemp += (ntemp < 0 ? -500 : 500); + return (u8)(ntemp / 1000); +} + +/* Determine 4-bit temperature offset resolution */ +static int LM93_TEMP_OFFSET_MODE_FROM_REG(u8 sfc2, int nr) +{ + /* mode: 0 => 1C/bit, nonzero => 0.5C/bit */ + return sfc2 & (nr < 2 ? 0x10 : 0x20); +} + +/* + * This function is common to all 4-bit temperature offsets + * reg is 4 bits right justified + * mode 0 => 1C/bit, mode !0 => 0.5C/bit + */ +static int LM93_TEMP_OFFSET_FROM_REG(u8 reg, int mode) +{ + return (reg & 0x0f) * (mode ? 5 : 10); +} + +#define LM93_TEMP_OFFSET_MIN (0) +#define LM93_TEMP_OFFSET_MAX0 (150) +#define LM93_TEMP_OFFSET_MAX1 (75) + +/* + * This function is common to all 4-bit temperature offsets + * returns 4 bits right justified + * mode 0 => 1C/bit, mode !0 => 0.5C/bit + */ +static u8 LM93_TEMP_OFFSET_TO_REG(int off, int mode) +{ + int factor = mode ? 5 : 10; + + off = clamp_val(off, LM93_TEMP_OFFSET_MIN, + mode ? LM93_TEMP_OFFSET_MAX1 : LM93_TEMP_OFFSET_MAX0); + return (u8)((off + factor/2) / factor); +} + +/* 0 <= nr <= 3 */ +static int LM93_TEMP_AUTO_OFFSET_FROM_REG(u8 reg, int nr, int mode) +{ + /* temp1-temp2 (nr=0,1) use lower nibble */ + if (nr < 2) + return LM93_TEMP_OFFSET_FROM_REG(reg & 0x0f, mode); + + /* temp3-temp4 (nr=2,3) use upper nibble */ + else + return LM93_TEMP_OFFSET_FROM_REG(reg >> 4 & 0x0f, mode); +} + +/* + * TEMP: 1/10 degrees C (0C to +15C (mode 0) or +7.5C (mode non-zero)) + * REG: 1.0C/bit (mode 0) or 0.5C/bit (mode non-zero) + * 0 <= nr <= 3 + */ +static u8 LM93_TEMP_AUTO_OFFSET_TO_REG(u8 old, int off, int nr, int mode) +{ + u8 new = LM93_TEMP_OFFSET_TO_REG(off, mode); + + /* temp1-temp2 (nr=0,1) use lower nibble */ + if (nr < 2) + return (old & 0xf0) | (new & 0x0f); + + /* temp3-temp4 (nr=2,3) use upper nibble */ + else + return (new << 4 & 0xf0) | (old & 0x0f); +} + +static int LM93_AUTO_BOOST_HYST_FROM_REGS(struct lm93_data *data, int nr, + int mode) +{ + u8 reg; + + switch (nr) { + case 0: + reg = data->boost_hyst[0] & 0x0f; + break; + case 1: + reg = data->boost_hyst[0] >> 4 & 0x0f; + break; + case 2: + reg = data->boost_hyst[1] & 0x0f; + break; + case 3: + default: + reg = data->boost_hyst[1] >> 4 & 0x0f; + break; + } + + return LM93_TEMP_FROM_REG(data->boost[nr]) - + LM93_TEMP_OFFSET_FROM_REG(reg, mode); +} + +static u8 LM93_AUTO_BOOST_HYST_TO_REG(struct lm93_data *data, long hyst, + int nr, int mode) +{ + u8 reg = LM93_TEMP_OFFSET_TO_REG( + (LM93_TEMP_FROM_REG(data->boost[nr]) - hyst), mode); + + switch (nr) { + case 0: + reg = (data->boost_hyst[0] & 0xf0) | (reg & 0x0f); + break; + case 1: + reg = (reg << 4 & 0xf0) | (data->boost_hyst[0] & 0x0f); + break; + case 2: + reg = (data->boost_hyst[1] & 0xf0) | (reg & 0x0f); + break; + case 3: + default: + reg = (reg << 4 & 0xf0) | (data->boost_hyst[1] & 0x0f); + break; + } + + return reg; +} + +/* + * PWM: 0-255 per sensors documentation + * REG: 0-13 as mapped below... right justified + */ +enum pwm_freq { LM93_PWM_MAP_HI_FREQ, LM93_PWM_MAP_LO_FREQ }; + +static int lm93_pwm_map[2][16] = { + { + 0x00, /* 0.00% */ 0x40, /* 25.00% */ + 0x50, /* 31.25% */ 0x60, /* 37.50% */ + 0x70, /* 43.75% */ 0x80, /* 50.00% */ + 0x90, /* 56.25% */ 0xa0, /* 62.50% */ + 0xb0, /* 68.75% */ 0xc0, /* 75.00% */ + 0xd0, /* 81.25% */ 0xe0, /* 87.50% */ + 0xf0, /* 93.75% */ 0xff, /* 100.00% */ + 0xff, 0xff, /* 14, 15 are reserved and should never occur */ + }, + { + 0x00, /* 0.00% */ 0x40, /* 25.00% */ + 0x49, /* 28.57% */ 0x52, /* 32.14% */ + 0x5b, /* 35.71% */ 0x64, /* 39.29% */ + 0x6d, /* 42.86% */ 0x76, /* 46.43% */ + 0x80, /* 50.00% */ 0x89, /* 53.57% */ + 0x92, /* 57.14% */ 0xb6, /* 71.43% */ + 0xdb, /* 85.71% */ 0xff, /* 100.00% */ + 0xff, 0xff, /* 14, 15 are reserved and should never occur */ + }, +}; + +static int LM93_PWM_FROM_REG(u8 reg, enum pwm_freq freq) +{ + return lm93_pwm_map[freq][reg & 0x0f]; +} + +/* round up to nearest match */ +static u8 LM93_PWM_TO_REG(int pwm, enum pwm_freq freq) +{ + int i; + for (i = 0; i < 13; i++) + if (pwm <= lm93_pwm_map[freq][i]) + break; + + /* can fall through with i==13 */ + return (u8)i; +} + +static int LM93_FAN_FROM_REG(u16 regs) +{ + const u16 count = le16_to_cpu(regs) >> 2; + return count == 0 ? -1 : count == 0x3fff ? 0 : 1350000 / count; +} + +/* + * RPM: (82.5 to 1350000) + * REG: 14-bits, LE, *left* justified + */ +static u16 LM93_FAN_TO_REG(long rpm) +{ + u16 count, regs; + + if (rpm == 0) { + count = 0x3fff; + } else { + rpm = clamp_val(rpm, 1, 1000000); + count = clamp_val((1350000 + rpm) / rpm, 1, 0x3ffe); + } + + regs = count << 2; + return cpu_to_le16(regs); +} + +/* + * PWM FREQ: HZ + * REG: 0-7 as mapped below + */ +static int lm93_pwm_freq_map[8] = { + 22500, 96, 84, 72, 60, 48, 36, 12 +}; + +static int LM93_PWM_FREQ_FROM_REG(u8 reg) +{ + return lm93_pwm_freq_map[reg & 0x07]; +} + +/* round up to nearest match */ +static u8 LM93_PWM_FREQ_TO_REG(int freq) +{ + int i; + for (i = 7; i > 0; i--) + if (freq <= lm93_pwm_freq_map[i]) + break; + + /* can fall through with i==0 */ + return (u8)i; +} + +/* + * TIME: 1/100 seconds + * REG: 0-7 as mapped below + */ +static int lm93_spinup_time_map[8] = { + 0, 10, 25, 40, 70, 100, 200, 400, +}; + +static int LM93_SPINUP_TIME_FROM_REG(u8 reg) +{ + return lm93_spinup_time_map[reg >> 5 & 0x07]; +} + +/* round up to nearest match */ +static u8 LM93_SPINUP_TIME_TO_REG(int time) +{ + int i; + for (i = 0; i < 7; i++) + if (time <= lm93_spinup_time_map[i]) + break; + + /* can fall through with i==8 */ + return (u8)i; +} + +#define LM93_RAMP_MIN 0 +#define LM93_RAMP_MAX 75 + +static int LM93_RAMP_FROM_REG(u8 reg) +{ + return (reg & 0x0f) * 5; +} + +/* + * RAMP: 1/100 seconds + * REG: 50mS/bit 4-bits right justified + */ +static u8 LM93_RAMP_TO_REG(int ramp) +{ + ramp = clamp_val(ramp, LM93_RAMP_MIN, LM93_RAMP_MAX); + return (u8)((ramp + 2) / 5); +} + +/* + * PROCHOT: 0-255, 0 => 0%, 255 => > 96.6% + * REG: (same) + */ +static u8 LM93_PROCHOT_TO_REG(long prochot) +{ + prochot = clamp_val(prochot, 0, 255); + return (u8)prochot; +} + +/* + * PROCHOT-INTERVAL: 73 - 37200 (1/100 seconds) + * REG: 0-9 as mapped below + */ +static int lm93_interval_map[10] = { + 73, 146, 290, 580, 1170, 2330, 4660, 9320, 18600, 37200, +}; + +static int LM93_INTERVAL_FROM_REG(u8 reg) +{ + return lm93_interval_map[reg & 0x0f]; +} + +/* round up to nearest match */ +static u8 LM93_INTERVAL_TO_REG(long interval) +{ + int i; + for (i = 0; i < 9; i++) + if (interval <= lm93_interval_map[i]) + break; + + /* can fall through with i==9 */ + return (u8)i; +} + +/* + * GPIO: 0-255, GPIO0 is LSB + * REG: inverted + */ +static unsigned LM93_GPI_FROM_REG(u8 reg) +{ + return ~reg & 0xff; +} + +/* + * alarm bitmask definitions + * The LM93 has nearly 64 bits of error status... I've pared that down to + * what I think is a useful subset in order to fit it into 32 bits. + * + * Especially note that the #VRD_HOT alarms are missing because we provide + * that information as values in another sysfs file. + * + * If libsensors is extended to support 64 bit values, this could be revisited. + */ +#define LM93_ALARM_IN1 0x00000001 +#define LM93_ALARM_IN2 0x00000002 +#define LM93_ALARM_IN3 0x00000004 +#define LM93_ALARM_IN4 0x00000008 +#define LM93_ALARM_IN5 0x00000010 +#define LM93_ALARM_IN6 0x00000020 +#define LM93_ALARM_IN7 0x00000040 +#define LM93_ALARM_IN8 0x00000080 +#define LM93_ALARM_IN9 0x00000100 +#define LM93_ALARM_IN10 0x00000200 +#define LM93_ALARM_IN11 0x00000400 +#define LM93_ALARM_IN12 0x00000800 +#define LM93_ALARM_IN13 0x00001000 +#define LM93_ALARM_IN14 0x00002000 +#define LM93_ALARM_IN15 0x00004000 +#define LM93_ALARM_IN16 0x00008000 +#define LM93_ALARM_FAN1 0x00010000 +#define LM93_ALARM_FAN2 0x00020000 +#define LM93_ALARM_FAN3 0x00040000 +#define LM93_ALARM_FAN4 0x00080000 +#define LM93_ALARM_PH1_ERR 0x00100000 +#define LM93_ALARM_PH2_ERR 0x00200000 +#define LM93_ALARM_SCSI1_ERR 0x00400000 +#define LM93_ALARM_SCSI2_ERR 0x00800000 +#define LM93_ALARM_DVDDP1_ERR 0x01000000 +#define LM93_ALARM_DVDDP2_ERR 0x02000000 +#define LM93_ALARM_D1_ERR 0x04000000 +#define LM93_ALARM_D2_ERR 0x08000000 +#define LM93_ALARM_TEMP1 0x10000000 +#define LM93_ALARM_TEMP2 0x20000000 +#define LM93_ALARM_TEMP3 0x40000000 + +static unsigned LM93_ALARMS_FROM_REG(struct block1_t b1) +{ + unsigned result; + result = b1.host_status_2 & 0x3f; + + if (vccp_limit_type[0]) + result |= (b1.host_status_4 & 0x10) << 2; + else + result |= b1.host_status_2 & 0x40; + + if (vccp_limit_type[1]) + result |= (b1.host_status_4 & 0x20) << 2; + else + result |= b1.host_status_2 & 0x80; + + result |= b1.host_status_3 << 8; + result |= (b1.fan_status & 0x0f) << 16; + result |= (b1.p1_prochot_status & 0x80) << 13; + result |= (b1.p2_prochot_status & 0x80) << 14; + result |= (b1.host_status_4 & 0xfc) << 20; + result |= (b1.host_status_1 & 0x07) << 28; + return result; +} + +#define MAX_RETRIES 5 + +static u8 lm93_read_byte(struct i2c_client *client, u8 reg) +{ + int value, i; + + /* retry in case of read errors */ + for (i = 1; i <= MAX_RETRIES; i++) { + value = i2c_smbus_read_byte_data(client, reg); + if (value >= 0) { + return value; + } else { + dev_warn(&client->dev, + "lm93: read byte data failed, address 0x%02x.\n", + reg); + mdelay(i + 3); + } + + } + + /* what to return in case of error? */ + dev_err(&client->dev, "lm93: All read byte retries failed!!\n"); + return 0; +} + +static int lm93_write_byte(struct i2c_client *client, u8 reg, u8 value) +{ + int result; + + /* how to handle write errors? */ + result = i2c_smbus_write_byte_data(client, reg, value); + + if (result < 0) + dev_warn(&client->dev, + "lm93: write byte data failed, 0x%02x at address 0x%02x.\n", + value, reg); + + return result; +} + +static u16 lm93_read_word(struct i2c_client *client, u8 reg) +{ + int value, i; + + /* retry in case of read errors */ + for (i = 1; i <= MAX_RETRIES; i++) { + value = i2c_smbus_read_word_data(client, reg); + if (value >= 0) { + return value; + } else { + dev_warn(&client->dev, + "lm93: read word data failed, address 0x%02x.\n", + reg); + mdelay(i + 3); + } + + } + + /* what to return in case of error? */ + dev_err(&client->dev, "lm93: All read word retries failed!!\n"); + return 0; +} + +static int lm93_write_word(struct i2c_client *client, u8 reg, u16 value) +{ + int result; + + /* how to handle write errors? */ + result = i2c_smbus_write_word_data(client, reg, value); + + if (result < 0) + dev_warn(&client->dev, + "lm93: write word data failed, 0x%04x at address 0x%02x.\n", + value, reg); + + return result; +} + +static u8 lm93_block_buffer[I2C_SMBUS_BLOCK_MAX]; + +/* + * read block data into values, retry if not expected length + * fbn => index to lm93_block_read_cmds table + * (Fixed Block Number - section 14.5.2 of LM93 datasheet) + */ +static void lm93_read_block(struct i2c_client *client, u8 fbn, u8 *values) +{ + int i, result = 0; + + for (i = 1; i <= MAX_RETRIES; i++) { + result = i2c_smbus_read_block_data(client, + lm93_block_read_cmds[fbn].cmd, lm93_block_buffer); + + if (result == lm93_block_read_cmds[fbn].len) { + break; + } else { + dev_warn(&client->dev, + "lm93: block read data failed, command 0x%02x.\n", + lm93_block_read_cmds[fbn].cmd); + mdelay(i + 3); + } + } + + if (result == lm93_block_read_cmds[fbn].len) { + memcpy(values, lm93_block_buffer, + lm93_block_read_cmds[fbn].len); + } else { + /* what to do in case of error? */ + } +} + +static struct lm93_data *lm93_update_device(struct device *dev) +{ + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + const unsigned long interval = HZ + (HZ / 2); + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + interval) || + !data->valid) { + + data->update(data, client); + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + return data; +} + +/* update routine for data that has no corresponding SMBus block command */ +static void lm93_update_client_common(struct lm93_data *data, + struct i2c_client *client) +{ + int i; + u8 *ptr; + + /* temp1 - temp4: limits */ + for (i = 0; i < 4; i++) { + data->temp_lim[i].min = + lm93_read_byte(client, LM93_REG_TEMP_MIN(i)); + data->temp_lim[i].max = + lm93_read_byte(client, LM93_REG_TEMP_MAX(i)); + } + + /* config register */ + data->config = lm93_read_byte(client, LM93_REG_CONFIG); + + /* vid1 - vid2: values */ + for (i = 0; i < 2; i++) + data->vid[i] = lm93_read_byte(client, LM93_REG_VID(i)); + + /* prochot1 - prochot2: limits */ + for (i = 0; i < 2; i++) + data->prochot_max[i] = lm93_read_byte(client, + LM93_REG_PROCHOT_MAX(i)); + + /* vccp1 - vccp2: VID relative limits */ + for (i = 0; i < 2; i++) + data->vccp_limits[i] = lm93_read_byte(client, + LM93_REG_VCCP_LIMIT_OFF(i)); + + /* GPIO input state */ + data->gpi = lm93_read_byte(client, LM93_REG_GPI); + + /* #PROCHOT override state */ + data->prochot_override = lm93_read_byte(client, + LM93_REG_PROCHOT_OVERRIDE); + + /* #PROCHOT intervals */ + data->prochot_interval = lm93_read_byte(client, + LM93_REG_PROCHOT_INTERVAL); + + /* Fan Boost Temperature registers */ + for (i = 0; i < 4; i++) + data->boost[i] = lm93_read_byte(client, LM93_REG_BOOST(i)); + + /* Fan Boost Temperature Hyst. registers */ + data->boost_hyst[0] = lm93_read_byte(client, LM93_REG_BOOST_HYST_12); + data->boost_hyst[1] = lm93_read_byte(client, LM93_REG_BOOST_HYST_34); + + /* Temperature Zone Min. PWM & Hysteresis registers */ + data->auto_pwm_min_hyst[0] = + lm93_read_byte(client, LM93_REG_PWM_MIN_HYST_12); + data->auto_pwm_min_hyst[1] = + lm93_read_byte(client, LM93_REG_PWM_MIN_HYST_34); + + /* #PROCHOT & #VRDHOT PWM Ramp Control register */ + data->pwm_ramp_ctl = lm93_read_byte(client, LM93_REG_PWM_RAMP_CTL); + + /* misc setup registers */ + data->sfc1 = lm93_read_byte(client, LM93_REG_SFC1); + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + data->sf_tach_to_pwm = lm93_read_byte(client, + LM93_REG_SF_TACH_TO_PWM); + + /* write back alarm values to clear */ + for (i = 0, ptr = (u8 *)(&data->block1); i < 8; i++) + lm93_write_byte(client, LM93_REG_HOST_ERROR_1 + i, *(ptr + i)); +} + +/* update routine which uses SMBus block data commands */ +static void lm93_update_client_full(struct lm93_data *data, + struct i2c_client *client) +{ + dev_dbg(&client->dev, "starting device update (block data enabled)\n"); + + /* in1 - in16: values & limits */ + lm93_read_block(client, 3, (u8 *)(data->block3)); + lm93_read_block(client, 7, (u8 *)(data->block7)); + + /* temp1 - temp4: values */ + lm93_read_block(client, 2, (u8 *)(data->block2)); + + /* prochot1 - prochot2: values */ + lm93_read_block(client, 4, (u8 *)(data->block4)); + + /* fan1 - fan4: values & limits */ + lm93_read_block(client, 5, (u8 *)(data->block5)); + lm93_read_block(client, 8, (u8 *)(data->block8)); + + /* pmw control registers */ + lm93_read_block(client, 9, (u8 *)(data->block9)); + + /* alarm values */ + lm93_read_block(client, 1, (u8 *)(&data->block1)); + + /* auto/pwm registers */ + lm93_read_block(client, 10, (u8 *)(&data->block10)); + + lm93_update_client_common(data, client); +} + +/* update routine which uses SMBus byte/word data commands only */ +static void lm93_update_client_min(struct lm93_data *data, + struct i2c_client *client) +{ + int i, j; + u8 *ptr; + + dev_dbg(&client->dev, "starting device update (block data disabled)\n"); + + /* in1 - in16: values & limits */ + for (i = 0; i < 16; i++) { + data->block3[i] = + lm93_read_byte(client, LM93_REG_IN(i)); + data->block7[i].min = + lm93_read_byte(client, LM93_REG_IN_MIN(i)); + data->block7[i].max = + lm93_read_byte(client, LM93_REG_IN_MAX(i)); + } + + /* temp1 - temp4: values */ + for (i = 0; i < 4; i++) { + data->block2[i] = + lm93_read_byte(client, LM93_REG_TEMP(i)); + } + + /* prochot1 - prochot2: values */ + for (i = 0; i < 2; i++) { + data->block4[i].cur = + lm93_read_byte(client, LM93_REG_PROCHOT_CUR(i)); + data->block4[i].avg = + lm93_read_byte(client, LM93_REG_PROCHOT_AVG(i)); + } + + /* fan1 - fan4: values & limits */ + for (i = 0; i < 4; i++) { + data->block5[i] = + lm93_read_word(client, LM93_REG_FAN(i)); + data->block8[i] = + lm93_read_word(client, LM93_REG_FAN_MIN(i)); + } + + /* pwm control registers */ + for (i = 0; i < 2; i++) { + for (j = 0; j < 4; j++) { + data->block9[i][j] = + lm93_read_byte(client, LM93_REG_PWM_CTL(i, j)); + } + } + + /* alarm values */ + for (i = 0, ptr = (u8 *)(&data->block1); i < 8; i++) { + *(ptr + i) = + lm93_read_byte(client, LM93_REG_HOST_ERROR_1 + i); + } + + /* auto/pwm (base temp) registers */ + for (i = 0; i < 4; i++) { + data->block10.base[i] = + lm93_read_byte(client, LM93_REG_TEMP_BASE(i)); + } + + /* auto/pwm (offset temp) registers */ + for (i = 0; i < 12; i++) { + data->block10.offset[i] = + lm93_read_byte(client, LM93_REG_TEMP_OFFSET(i)); + } + + lm93_update_client_common(data, client); +} + +/* following are the sysfs callback functions */ +static ssize_t show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", LM93_IN_FROM_REG(nr, data->block3[nr])); +} + +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 0); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_in, NULL, 1); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_in, NULL, 2); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_in, NULL, 3); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, show_in, NULL, 4); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, show_in, NULL, 5); +static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, show_in, NULL, 6); +static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, show_in, NULL, 7); +static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, show_in, NULL, 8); +static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, show_in, NULL, 9); +static SENSOR_DEVICE_ATTR(in11_input, S_IRUGO, show_in, NULL, 10); +static SENSOR_DEVICE_ATTR(in12_input, S_IRUGO, show_in, NULL, 11); +static SENSOR_DEVICE_ATTR(in13_input, S_IRUGO, show_in, NULL, 12); +static SENSOR_DEVICE_ATTR(in14_input, S_IRUGO, show_in, NULL, 13); +static SENSOR_DEVICE_ATTR(in15_input, S_IRUGO, show_in, NULL, 14); +static SENSOR_DEVICE_ATTR(in16_input, S_IRUGO, show_in, NULL, 15); + +static ssize_t show_in_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + int vccp = nr - 6; + long rc, vid; + + if ((nr == 6 || nr == 7) && vccp_limit_type[vccp]) { + vid = LM93_VID_FROM_REG(data->vid[vccp]); + rc = LM93_IN_MIN_FROM_REG(data->vccp_limits[vccp], vid); + } else { + rc = LM93_IN_FROM_REG(nr, data->block7[nr].min); + } + return sprintf(buf, "%ld\n", rc); +} + +static ssize_t store_in_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int vccp = nr - 6; + long vid; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + if ((nr == 6 || nr == 7) && vccp_limit_type[vccp]) { + vid = LM93_VID_FROM_REG(data->vid[vccp]); + data->vccp_limits[vccp] = (data->vccp_limits[vccp] & 0xf0) | + LM93_IN_REL_TO_REG(val, 0, vid); + lm93_write_byte(client, LM93_REG_VCCP_LIMIT_OFF(vccp), + data->vccp_limits[vccp]); + } else { + data->block7[nr].min = LM93_IN_TO_REG(nr, val); + lm93_write_byte(client, LM93_REG_IN_MIN(nr), + data->block7[nr].min); + } + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(in1_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 0); +static SENSOR_DEVICE_ATTR(in2_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 1); +static SENSOR_DEVICE_ATTR(in3_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 2); +static SENSOR_DEVICE_ATTR(in4_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 3); +static SENSOR_DEVICE_ATTR(in5_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 4); +static SENSOR_DEVICE_ATTR(in6_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 5); +static SENSOR_DEVICE_ATTR(in7_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 6); +static SENSOR_DEVICE_ATTR(in8_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 7); +static SENSOR_DEVICE_ATTR(in9_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 8); +static SENSOR_DEVICE_ATTR(in10_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 9); +static SENSOR_DEVICE_ATTR(in11_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 10); +static SENSOR_DEVICE_ATTR(in12_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 11); +static SENSOR_DEVICE_ATTR(in13_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 12); +static SENSOR_DEVICE_ATTR(in14_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 13); +static SENSOR_DEVICE_ATTR(in15_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 14); +static SENSOR_DEVICE_ATTR(in16_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 15); + +static ssize_t show_in_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + int vccp = nr - 6; + long rc, vid; + + if ((nr == 6 || nr == 7) && vccp_limit_type[vccp]) { + vid = LM93_VID_FROM_REG(data->vid[vccp]); + rc = LM93_IN_MAX_FROM_REG(data->vccp_limits[vccp], vid); + } else { + rc = LM93_IN_FROM_REG(nr, data->block7[nr].max); + } + return sprintf(buf, "%ld\n", rc); +} + +static ssize_t store_in_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int vccp = nr - 6; + long vid; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + if ((nr == 6 || nr == 7) && vccp_limit_type[vccp]) { + vid = LM93_VID_FROM_REG(data->vid[vccp]); + data->vccp_limits[vccp] = (data->vccp_limits[vccp] & 0x0f) | + LM93_IN_REL_TO_REG(val, 1, vid); + lm93_write_byte(client, LM93_REG_VCCP_LIMIT_OFF(vccp), + data->vccp_limits[vccp]); + } else { + data->block7[nr].max = LM93_IN_TO_REG(nr, val); + lm93_write_byte(client, LM93_REG_IN_MAX(nr), + data->block7[nr].max); + } + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(in1_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 0); +static SENSOR_DEVICE_ATTR(in2_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 1); +static SENSOR_DEVICE_ATTR(in3_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 2); +static SENSOR_DEVICE_ATTR(in4_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 3); +static SENSOR_DEVICE_ATTR(in5_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 4); +static SENSOR_DEVICE_ATTR(in6_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 5); +static SENSOR_DEVICE_ATTR(in7_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 6); +static SENSOR_DEVICE_ATTR(in8_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 7); +static SENSOR_DEVICE_ATTR(in9_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 8); +static SENSOR_DEVICE_ATTR(in10_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 9); +static SENSOR_DEVICE_ATTR(in11_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 10); +static SENSOR_DEVICE_ATTR(in12_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 11); +static SENSOR_DEVICE_ATTR(in13_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 12); +static SENSOR_DEVICE_ATTR(in14_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 13); +static SENSOR_DEVICE_ATTR(in15_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 14); +static SENSOR_DEVICE_ATTR(in16_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 15); + +static ssize_t show_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", LM93_TEMP_FROM_REG(data->block2[nr])); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); + +static ssize_t show_temp_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", LM93_TEMP_FROM_REG(data->temp_lim[nr].min)); +} + +static ssize_t store_temp_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_lim[nr].min = LM93_TEMP_TO_REG(val); + lm93_write_byte(client, LM93_REG_TEMP_MIN(nr), data->temp_lim[nr].min); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, + show_temp_min, store_temp_min, 0); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, + show_temp_min, store_temp_min, 1); +static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, + show_temp_min, store_temp_min, 2); + +static ssize_t show_temp_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", LM93_TEMP_FROM_REG(data->temp_lim[nr].max)); +} + +static ssize_t store_temp_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_lim[nr].max = LM93_TEMP_TO_REG(val); + lm93_write_byte(client, LM93_REG_TEMP_MAX(nr), data->temp_lim[nr].max); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, + show_temp_max, store_temp_max, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, + show_temp_max, store_temp_max, 1); +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, + show_temp_max, store_temp_max, 2); + +static ssize_t show_temp_auto_base(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", LM93_TEMP_FROM_REG(data->block10.base[nr])); +} + +static ssize_t store_temp_auto_base(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->block10.base[nr] = LM93_TEMP_TO_REG(val); + lm93_write_byte(client, LM93_REG_TEMP_BASE(nr), data->block10.base[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_auto_base, S_IWUSR | S_IRUGO, + show_temp_auto_base, store_temp_auto_base, 0); +static SENSOR_DEVICE_ATTR(temp2_auto_base, S_IWUSR | S_IRUGO, + show_temp_auto_base, store_temp_auto_base, 1); +static SENSOR_DEVICE_ATTR(temp3_auto_base, S_IWUSR | S_IRUGO, + show_temp_auto_base, store_temp_auto_base, 2); + +static ssize_t show_temp_auto_boost(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", LM93_TEMP_FROM_REG(data->boost[nr])); +} + +static ssize_t store_temp_auto_boost(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->boost[nr] = LM93_TEMP_TO_REG(val); + lm93_write_byte(client, LM93_REG_BOOST(nr), data->boost[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_auto_boost, S_IWUSR | S_IRUGO, + show_temp_auto_boost, store_temp_auto_boost, 0); +static SENSOR_DEVICE_ATTR(temp2_auto_boost, S_IWUSR | S_IRUGO, + show_temp_auto_boost, store_temp_auto_boost, 1); +static SENSOR_DEVICE_ATTR(temp3_auto_boost, S_IWUSR | S_IRUGO, + show_temp_auto_boost, store_temp_auto_boost, 2); + +static ssize_t show_temp_auto_boost_hyst(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + int mode = LM93_TEMP_OFFSET_MODE_FROM_REG(data->sfc2, nr); + return sprintf(buf, "%d\n", + LM93_AUTO_BOOST_HYST_FROM_REGS(data, nr, mode)); +} + +static ssize_t store_temp_auto_boost_hyst(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + /* force 0.5C/bit mode */ + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + data->sfc2 |= ((nr < 2) ? 0x10 : 0x20); + lm93_write_byte(client, LM93_REG_SFC2, data->sfc2); + data->boost_hyst[nr/2] = LM93_AUTO_BOOST_HYST_TO_REG(data, val, nr, 1); + lm93_write_byte(client, LM93_REG_BOOST_HYST(nr), + data->boost_hyst[nr/2]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_auto_boost_hyst, S_IWUSR | S_IRUGO, + show_temp_auto_boost_hyst, + store_temp_auto_boost_hyst, 0); +static SENSOR_DEVICE_ATTR(temp2_auto_boost_hyst, S_IWUSR | S_IRUGO, + show_temp_auto_boost_hyst, + store_temp_auto_boost_hyst, 1); +static SENSOR_DEVICE_ATTR(temp3_auto_boost_hyst, S_IWUSR | S_IRUGO, + show_temp_auto_boost_hyst, + store_temp_auto_boost_hyst, 2); + +static ssize_t show_temp_auto_offset(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *s_attr = to_sensor_dev_attr_2(attr); + int nr = s_attr->index; + int ofs = s_attr->nr; + struct lm93_data *data = lm93_update_device(dev); + int mode = LM93_TEMP_OFFSET_MODE_FROM_REG(data->sfc2, nr); + return sprintf(buf, "%d\n", + LM93_TEMP_AUTO_OFFSET_FROM_REG(data->block10.offset[ofs], + nr, mode)); +} + +static ssize_t store_temp_auto_offset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *s_attr = to_sensor_dev_attr_2(attr); + int nr = s_attr->index; + int ofs = s_attr->nr; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + /* force 0.5C/bit mode */ + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + data->sfc2 |= ((nr < 2) ? 0x10 : 0x20); + lm93_write_byte(client, LM93_REG_SFC2, data->sfc2); + data->block10.offset[ofs] = LM93_TEMP_AUTO_OFFSET_TO_REG( + data->block10.offset[ofs], val, nr, 1); + lm93_write_byte(client, LM93_REG_TEMP_OFFSET(ofs), + data->block10.offset[ofs]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset1, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 0, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset2, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 1, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset3, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 2, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset4, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 3, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset5, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 4, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset6, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 5, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset7, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 6, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset8, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 7, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset9, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 8, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset10, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 9, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset11, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 10, 0); +static SENSOR_DEVICE_ATTR_2(temp1_auto_offset12, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 11, 0); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset1, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 0, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset2, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 1, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset3, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 2, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset4, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 3, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset5, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 4, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset6, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 5, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset7, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 6, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset8, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 7, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset9, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 8, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset10, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 9, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset11, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 10, 1); +static SENSOR_DEVICE_ATTR_2(temp2_auto_offset12, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 11, 1); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset1, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 0, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset2, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 1, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset3, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 2, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset4, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 3, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset5, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 4, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset6, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 5, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset7, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 6, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset8, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 7, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset9, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 8, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset10, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 9, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset11, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 10, 2); +static SENSOR_DEVICE_ATTR_2(temp3_auto_offset12, S_IWUSR | S_IRUGO, + show_temp_auto_offset, store_temp_auto_offset, 11, 2); + +static ssize_t show_temp_auto_pwm_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + u8 reg, ctl4; + struct lm93_data *data = lm93_update_device(dev); + reg = data->auto_pwm_min_hyst[nr/2] >> 4 & 0x0f; + ctl4 = data->block9[nr][LM93_PWM_CTL4]; + return sprintf(buf, "%d\n", LM93_PWM_FROM_REG(reg, (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : LM93_PWM_MAP_HI_FREQ)); +} + +static ssize_t store_temp_auto_pwm_min(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 reg, ctl4; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + reg = lm93_read_byte(client, LM93_REG_PWM_MIN_HYST(nr)); + ctl4 = lm93_read_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL4)); + reg = (reg & 0x0f) | + LM93_PWM_TO_REG(val, (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : + LM93_PWM_MAP_HI_FREQ) << 4; + data->auto_pwm_min_hyst[nr/2] = reg; + lm93_write_byte(client, LM93_REG_PWM_MIN_HYST(nr), reg); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_auto_pwm_min, S_IWUSR | S_IRUGO, + show_temp_auto_pwm_min, + store_temp_auto_pwm_min, 0); +static SENSOR_DEVICE_ATTR(temp2_auto_pwm_min, S_IWUSR | S_IRUGO, + show_temp_auto_pwm_min, + store_temp_auto_pwm_min, 1); +static SENSOR_DEVICE_ATTR(temp3_auto_pwm_min, S_IWUSR | S_IRUGO, + show_temp_auto_pwm_min, + store_temp_auto_pwm_min, 2); + +static ssize_t show_temp_auto_offset_hyst(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + int mode = LM93_TEMP_OFFSET_MODE_FROM_REG(data->sfc2, nr); + return sprintf(buf, "%d\n", LM93_TEMP_OFFSET_FROM_REG( + data->auto_pwm_min_hyst[nr / 2], mode)); +} + +static ssize_t store_temp_auto_offset_hyst(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 reg; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + /* force 0.5C/bit mode */ + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + data->sfc2 |= ((nr < 2) ? 0x10 : 0x20); + lm93_write_byte(client, LM93_REG_SFC2, data->sfc2); + reg = data->auto_pwm_min_hyst[nr/2]; + reg = (reg & 0xf0) | (LM93_TEMP_OFFSET_TO_REG(val, 1) & 0x0f); + data->auto_pwm_min_hyst[nr/2] = reg; + lm93_write_byte(client, LM93_REG_PWM_MIN_HYST(nr), reg); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_auto_offset_hyst, S_IWUSR | S_IRUGO, + show_temp_auto_offset_hyst, + store_temp_auto_offset_hyst, 0); +static SENSOR_DEVICE_ATTR(temp2_auto_offset_hyst, S_IWUSR | S_IRUGO, + show_temp_auto_offset_hyst, + store_temp_auto_offset_hyst, 1); +static SENSOR_DEVICE_ATTR(temp3_auto_offset_hyst, S_IWUSR | S_IRUGO, + show_temp_auto_offset_hyst, + store_temp_auto_offset_hyst, 2); + +static ssize_t show_fan_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *s_attr = to_sensor_dev_attr(attr); + int nr = s_attr->index; + struct lm93_data *data = lm93_update_device(dev); + + return sprintf(buf, "%d\n", LM93_FAN_FROM_REG(data->block5[nr])); +} + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_input, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_input, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan_input, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan_input, NULL, 3); + +static ssize_t show_fan_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + + return sprintf(buf, "%d\n", LM93_FAN_FROM_REG(data->block8[nr])); +} + +static ssize_t store_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->block8[nr] = LM93_FAN_TO_REG(val); + lm93_write_word(client, LM93_REG_FAN_MIN(nr), data->block8[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 0); +static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 1); +static SENSOR_DEVICE_ATTR(fan3_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 2); +static SENSOR_DEVICE_ATTR(fan4_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 3); + +/* + * some tedious bit-twiddling here to deal with the register format: + * + * data->sf_tach_to_pwm: (tach to pwm mapping bits) + * + * bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 + * T4:P2 T4:P1 T3:P2 T3:P1 T2:P2 T2:P1 T1:P2 T1:P1 + * + * data->sfc2: (enable bits) + * + * bit | 3 | 2 | 1 | 0 + * T4 T3 T2 T1 + */ + +static ssize_t show_fan_smart_tach(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + long rc = 0; + int mapping; + + /* extract the relevant mapping */ + mapping = (data->sf_tach_to_pwm >> (nr * 2)) & 0x03; + + /* if there's a mapping and it's enabled */ + if (mapping && ((data->sfc2 >> nr) & 0x01)) + rc = mapping; + return sprintf(buf, "%ld\n", rc); +} + +/* + * helper function - must grab data->update_lock before calling + * fan is 0-3, indicating fan1-fan4 + */ +static void lm93_write_fan_smart_tach(struct i2c_client *client, + struct lm93_data *data, int fan, long value) +{ + /* insert the new mapping and write it out */ + data->sf_tach_to_pwm = lm93_read_byte(client, LM93_REG_SF_TACH_TO_PWM); + data->sf_tach_to_pwm &= ~(0x3 << fan * 2); + data->sf_tach_to_pwm |= value << fan * 2; + lm93_write_byte(client, LM93_REG_SF_TACH_TO_PWM, data->sf_tach_to_pwm); + + /* insert the enable bit and write it out */ + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + if (value) + data->sfc2 |= 1 << fan; + else + data->sfc2 &= ~(1 << fan); + lm93_write_byte(client, LM93_REG_SFC2, data->sfc2); +} + +static ssize_t store_fan_smart_tach(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + /* sanity test, ignore the write otherwise */ + if (val <= 2) { + /* can't enable if pwm freq is 22.5KHz */ + if (val) { + u8 ctl4 = lm93_read_byte(client, + LM93_REG_PWM_CTL(val - 1, LM93_PWM_CTL4)); + if ((ctl4 & 0x07) == 0) + val = 0; + } + lm93_write_fan_smart_tach(client, data, nr, val); + } + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(fan1_smart_tach, S_IWUSR | S_IRUGO, + show_fan_smart_tach, store_fan_smart_tach, 0); +static SENSOR_DEVICE_ATTR(fan2_smart_tach, S_IWUSR | S_IRUGO, + show_fan_smart_tach, store_fan_smart_tach, 1); +static SENSOR_DEVICE_ATTR(fan3_smart_tach, S_IWUSR | S_IRUGO, + show_fan_smart_tach, store_fan_smart_tach, 2); +static SENSOR_DEVICE_ATTR(fan4_smart_tach, S_IWUSR | S_IRUGO, + show_fan_smart_tach, store_fan_smart_tach, 3); + +static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + u8 ctl2, ctl4; + long rc; + + ctl2 = data->block9[nr][LM93_PWM_CTL2]; + ctl4 = data->block9[nr][LM93_PWM_CTL4]; + if (ctl2 & 0x01) /* show user commanded value if enabled */ + rc = data->pwm_override[nr]; + else /* show present h/w value if manual pwm disabled */ + rc = LM93_PWM_FROM_REG(ctl2 >> 4, (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : LM93_PWM_MAP_HI_FREQ); + return sprintf(buf, "%ld\n", rc); +} + +static ssize_t store_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 ctl2, ctl4; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + ctl2 = lm93_read_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL2)); + ctl4 = lm93_read_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL4)); + ctl2 = (ctl2 & 0x0f) | LM93_PWM_TO_REG(val, (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : LM93_PWM_MAP_HI_FREQ) << 4; + /* save user commanded value */ + data->pwm_override[nr] = LM93_PWM_FROM_REG(ctl2 >> 4, + (ctl4 & 0x07) ? LM93_PWM_MAP_LO_FREQ : + LM93_PWM_MAP_HI_FREQ); + lm93_write_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL2), ctl2); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 1); + +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + u8 ctl2; + long rc; + + ctl2 = data->block9[nr][LM93_PWM_CTL2]; + if (ctl2 & 0x01) /* manual override enabled ? */ + rc = ((ctl2 & 0xF0) == 0xF0) ? 0 : 1; + else + rc = 2; + return sprintf(buf, "%ld\n", rc); +} + +static ssize_t store_pwm_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 ctl2; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + ctl2 = lm93_read_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL2)); + + switch (val) { + case 0: + ctl2 |= 0xF1; /* enable manual override, set PWM to max */ + break; + case 1: + ctl2 |= 0x01; /* enable manual override */ + break; + case 2: + ctl2 &= ~0x01; /* disable manual override */ + break; + default: + mutex_unlock(&data->update_lock); + return -EINVAL; + } + + lm93_write_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL2), ctl2); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + show_pwm_enable, store_pwm_enable, 0); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, + show_pwm_enable, store_pwm_enable, 1); + +static ssize_t show_pwm_freq(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + u8 ctl4; + + ctl4 = data->block9[nr][LM93_PWM_CTL4]; + return sprintf(buf, "%d\n", LM93_PWM_FREQ_FROM_REG(ctl4)); +} + +/* + * helper function - must grab data->update_lock before calling + * pwm is 0-1, indicating pwm1-pwm2 + * this disables smart tach for all tach channels bound to the given pwm + */ +static void lm93_disable_fan_smart_tach(struct i2c_client *client, + struct lm93_data *data, int pwm) +{ + int mapping = lm93_read_byte(client, LM93_REG_SF_TACH_TO_PWM); + int mask; + + /* collapse the mapping into a mask of enable bits */ + mapping = (mapping >> pwm) & 0x55; + mask = mapping & 0x01; + mask |= (mapping & 0x04) >> 1; + mask |= (mapping & 0x10) >> 2; + mask |= (mapping & 0x40) >> 3; + + /* disable smart tach according to the mask */ + data->sfc2 = lm93_read_byte(client, LM93_REG_SFC2); + data->sfc2 &= ~mask; + lm93_write_byte(client, LM93_REG_SFC2, data->sfc2); +} + +static ssize_t store_pwm_freq(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 ctl4; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + ctl4 = lm93_read_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL4)); + ctl4 = (ctl4 & 0xf8) | LM93_PWM_FREQ_TO_REG(val); + data->block9[nr][LM93_PWM_CTL4] = ctl4; + /* ctl4 == 0 -> 22.5KHz -> disable smart tach */ + if (!ctl4) + lm93_disable_fan_smart_tach(client, data, nr); + lm93_write_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL4), ctl4); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1_freq, S_IWUSR | S_IRUGO, + show_pwm_freq, store_pwm_freq, 0); +static SENSOR_DEVICE_ATTR(pwm2_freq, S_IWUSR | S_IRUGO, + show_pwm_freq, store_pwm_freq, 1); + +static ssize_t show_pwm_auto_channels(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", data->block9[nr][LM93_PWM_CTL1]); +} + +static ssize_t store_pwm_auto_channels(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->block9[nr][LM93_PWM_CTL1] = clamp_val(val, 0, 255); + lm93_write_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL1), + data->block9[nr][LM93_PWM_CTL1]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1_auto_channels, S_IWUSR | S_IRUGO, + show_pwm_auto_channels, store_pwm_auto_channels, 0); +static SENSOR_DEVICE_ATTR(pwm2_auto_channels, S_IWUSR | S_IRUGO, + show_pwm_auto_channels, store_pwm_auto_channels, 1); + +static ssize_t show_pwm_auto_spinup_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + u8 ctl3, ctl4; + + ctl3 = data->block9[nr][LM93_PWM_CTL3]; + ctl4 = data->block9[nr][LM93_PWM_CTL4]; + return sprintf(buf, "%d\n", + LM93_PWM_FROM_REG(ctl3 & 0x0f, (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : LM93_PWM_MAP_HI_FREQ)); +} + +static ssize_t store_pwm_auto_spinup_min(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 ctl3, ctl4; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + ctl3 = lm93_read_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL3)); + ctl4 = lm93_read_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL4)); + ctl3 = (ctl3 & 0xf0) | LM93_PWM_TO_REG(val, (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : + LM93_PWM_MAP_HI_FREQ); + data->block9[nr][LM93_PWM_CTL3] = ctl3; + lm93_write_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL3), ctl3); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1_auto_spinup_min, S_IWUSR | S_IRUGO, + show_pwm_auto_spinup_min, + store_pwm_auto_spinup_min, 0); +static SENSOR_DEVICE_ATTR(pwm2_auto_spinup_min, S_IWUSR | S_IRUGO, + show_pwm_auto_spinup_min, + store_pwm_auto_spinup_min, 1); + +static ssize_t show_pwm_auto_spinup_time(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", LM93_SPINUP_TIME_FROM_REG( + data->block9[nr][LM93_PWM_CTL3])); +} + +static ssize_t store_pwm_auto_spinup_time(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 ctl3; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + ctl3 = lm93_read_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL3)); + ctl3 = (ctl3 & 0x1f) | (LM93_SPINUP_TIME_TO_REG(val) << 5 & 0xe0); + data->block9[nr][LM93_PWM_CTL3] = ctl3; + lm93_write_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL3), ctl3); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1_auto_spinup_time, S_IWUSR | S_IRUGO, + show_pwm_auto_spinup_time, + store_pwm_auto_spinup_time, 0); +static SENSOR_DEVICE_ATTR(pwm2_auto_spinup_time, S_IWUSR | S_IRUGO, + show_pwm_auto_spinup_time, + store_pwm_auto_spinup_time, 1); + +static ssize_t pwm_auto_prochot_ramp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", + LM93_RAMP_FROM_REG(data->pwm_ramp_ctl >> 4 & 0x0f)); +} + +static ssize_t pwm_auto_prochot_ramp_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 ramp; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + ramp = lm93_read_byte(client, LM93_REG_PWM_RAMP_CTL); + ramp = (ramp & 0x0f) | (LM93_RAMP_TO_REG(val) << 4 & 0xf0); + lm93_write_byte(client, LM93_REG_PWM_RAMP_CTL, ramp); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RW(pwm_auto_prochot_ramp); + +static ssize_t pwm_auto_vrdhot_ramp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", + LM93_RAMP_FROM_REG(data->pwm_ramp_ctl & 0x0f)); +} + +static ssize_t pwm_auto_vrdhot_ramp_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 ramp; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + ramp = lm93_read_byte(client, LM93_REG_PWM_RAMP_CTL); + ramp = (ramp & 0xf0) | (LM93_RAMP_TO_REG(val) & 0x0f); + lm93_write_byte(client, LM93_REG_PWM_RAMP_CTL, ramp); + mutex_unlock(&data->update_lock); + return 0; +} + +static DEVICE_ATTR_RW(pwm_auto_vrdhot_ramp); + +static ssize_t show_vid(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", LM93_VID_FROM_REG(data->vid[nr])); +} + +static SENSOR_DEVICE_ATTR(cpu0_vid, S_IRUGO, show_vid, NULL, 0); +static SENSOR_DEVICE_ATTR(cpu1_vid, S_IRUGO, show_vid, NULL, 1); + +static ssize_t show_prochot(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", data->block4[nr].cur); +} + +static SENSOR_DEVICE_ATTR(prochot1, S_IRUGO, show_prochot, NULL, 0); +static SENSOR_DEVICE_ATTR(prochot2, S_IRUGO, show_prochot, NULL, 1); + +static ssize_t show_prochot_avg(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", data->block4[nr].avg); +} + +static SENSOR_DEVICE_ATTR(prochot1_avg, S_IRUGO, show_prochot_avg, NULL, 0); +static SENSOR_DEVICE_ATTR(prochot2_avg, S_IRUGO, show_prochot_avg, NULL, 1); + +static ssize_t show_prochot_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", data->prochot_max[nr]); +} + +static ssize_t store_prochot_max(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->prochot_max[nr] = LM93_PROCHOT_TO_REG(val); + lm93_write_byte(client, LM93_REG_PROCHOT_MAX(nr), + data->prochot_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(prochot1_max, S_IWUSR | S_IRUGO, + show_prochot_max, store_prochot_max, 0); +static SENSOR_DEVICE_ATTR(prochot2_max, S_IWUSR | S_IRUGO, + show_prochot_max, store_prochot_max, 1); + +static const u8 prochot_override_mask[] = { 0x80, 0x40 }; + +static ssize_t show_prochot_override(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", + (data->prochot_override & prochot_override_mask[nr]) ? 1 : 0); +} + +static ssize_t store_prochot_override(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + if (val) + data->prochot_override |= prochot_override_mask[nr]; + else + data->prochot_override &= (~prochot_override_mask[nr]); + lm93_write_byte(client, LM93_REG_PROCHOT_OVERRIDE, + data->prochot_override); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(prochot1_override, S_IWUSR | S_IRUGO, + show_prochot_override, store_prochot_override, 0); +static SENSOR_DEVICE_ATTR(prochot2_override, S_IWUSR | S_IRUGO, + show_prochot_override, store_prochot_override, 1); + +static ssize_t show_prochot_interval(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + u8 tmp; + if (nr == 1) + tmp = (data->prochot_interval & 0xf0) >> 4; + else + tmp = data->prochot_interval & 0x0f; + return sprintf(buf, "%d\n", LM93_INTERVAL_FROM_REG(tmp)); +} + +static ssize_t store_prochot_interval(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 tmp; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + tmp = lm93_read_byte(client, LM93_REG_PROCHOT_INTERVAL); + if (nr == 1) + tmp = (tmp & 0x0f) | (LM93_INTERVAL_TO_REG(val) << 4); + else + tmp = (tmp & 0xf0) | LM93_INTERVAL_TO_REG(val); + data->prochot_interval = tmp; + lm93_write_byte(client, LM93_REG_PROCHOT_INTERVAL, tmp); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(prochot1_interval, S_IWUSR | S_IRUGO, + show_prochot_interval, store_prochot_interval, 0); +static SENSOR_DEVICE_ATTR(prochot2_interval, S_IWUSR | S_IRUGO, + show_prochot_interval, store_prochot_interval, 1); + +static ssize_t prochot_override_duty_cycle_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", data->prochot_override & 0x0f); +} + +static ssize_t prochot_override_duty_cycle_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->prochot_override = (data->prochot_override & 0xf0) | + clamp_val(val, 0, 15); + lm93_write_byte(client, LM93_REG_PROCHOT_OVERRIDE, + data->prochot_override); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RW(prochot_override_duty_cycle); + +static ssize_t prochot_short_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", (data->config & 0x10) ? 1 : 0); +} + +static ssize_t prochot_short_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm93_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + if (val) + data->config |= 0x10; + else + data->config &= ~0x10; + lm93_write_byte(client, LM93_REG_CONFIG, data->config); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RW(prochot_short); + +static ssize_t show_vrdhot(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = (to_sensor_dev_attr(attr))->index; + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", + data->block1.host_status_1 & (1 << (nr + 4)) ? 1 : 0); +} + +static SENSOR_DEVICE_ATTR(vrdhot1, S_IRUGO, show_vrdhot, NULL, 0); +static SENSOR_DEVICE_ATTR(vrdhot2, S_IRUGO, show_vrdhot, NULL, 1); + +static ssize_t gpio_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", LM93_GPI_FROM_REG(data->gpi)); +} + +static DEVICE_ATTR_RO(gpio); + +static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm93_data *data = lm93_update_device(dev); + return sprintf(buf, "%d\n", LM93_ALARMS_FROM_REG(data->block1)); +} + +static DEVICE_ATTR_RO(alarms); + +static struct attribute *lm93_attrs[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in11_input.dev_attr.attr, + &sensor_dev_attr_in12_input.dev_attr.attr, + &sensor_dev_attr_in13_input.dev_attr.attr, + &sensor_dev_attr_in14_input.dev_attr.attr, + &sensor_dev_attr_in15_input.dev_attr.attr, + &sensor_dev_attr_in16_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in8_min.dev_attr.attr, + &sensor_dev_attr_in9_min.dev_attr.attr, + &sensor_dev_attr_in10_min.dev_attr.attr, + &sensor_dev_attr_in11_min.dev_attr.attr, + &sensor_dev_attr_in12_min.dev_attr.attr, + &sensor_dev_attr_in13_min.dev_attr.attr, + &sensor_dev_attr_in14_min.dev_attr.attr, + &sensor_dev_attr_in15_min.dev_attr.attr, + &sensor_dev_attr_in16_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in8_max.dev_attr.attr, + &sensor_dev_attr_in9_max.dev_attr.attr, + &sensor_dev_attr_in10_max.dev_attr.attr, + &sensor_dev_attr_in11_max.dev_attr.attr, + &sensor_dev_attr_in12_max.dev_attr.attr, + &sensor_dev_attr_in13_max.dev_attr.attr, + &sensor_dev_attr_in14_max.dev_attr.attr, + &sensor_dev_attr_in15_max.dev_attr.attr, + &sensor_dev_attr_in16_max.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp1_auto_base.dev_attr.attr, + &sensor_dev_attr_temp2_auto_base.dev_attr.attr, + &sensor_dev_attr_temp3_auto_base.dev_attr.attr, + &sensor_dev_attr_temp1_auto_boost.dev_attr.attr, + &sensor_dev_attr_temp2_auto_boost.dev_attr.attr, + &sensor_dev_attr_temp3_auto_boost.dev_attr.attr, + &sensor_dev_attr_temp1_auto_boost_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_auto_boost_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_auto_boost_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset1.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset2.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset3.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset4.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset5.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset6.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset7.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset8.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset9.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset10.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset11.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset12.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset1.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset2.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset3.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset4.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset5.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset6.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset7.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset8.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset9.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset10.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset11.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset12.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset1.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset2.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset3.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset4.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset5.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset6.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset7.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset8.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset9.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset10.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset11.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset12.dev_attr.attr, + &sensor_dev_attr_temp1_auto_pwm_min.dev_attr.attr, + &sensor_dev_attr_temp2_auto_pwm_min.dev_attr.attr, + &sensor_dev_attr_temp3_auto_pwm_min.dev_attr.attr, + &sensor_dev_attr_temp1_auto_offset_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_auto_offset_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_auto_offset_hyst.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan4_min.dev_attr.attr, + &sensor_dev_attr_fan1_smart_tach.dev_attr.attr, + &sensor_dev_attr_fan2_smart_tach.dev_attr.attr, + &sensor_dev_attr_fan3_smart_tach.dev_attr.attr, + &sensor_dev_attr_fan4_smart_tach.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_freq.dev_attr.attr, + &sensor_dev_attr_pwm2_freq.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_channels.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_channels.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_spinup_min.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_spinup_min.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_spinup_time.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_spinup_time.dev_attr.attr, + &dev_attr_pwm_auto_prochot_ramp.attr, + &dev_attr_pwm_auto_vrdhot_ramp.attr, + &sensor_dev_attr_cpu0_vid.dev_attr.attr, + &sensor_dev_attr_cpu1_vid.dev_attr.attr, + &sensor_dev_attr_prochot1.dev_attr.attr, + &sensor_dev_attr_prochot2.dev_attr.attr, + &sensor_dev_attr_prochot1_avg.dev_attr.attr, + &sensor_dev_attr_prochot2_avg.dev_attr.attr, + &sensor_dev_attr_prochot1_max.dev_attr.attr, + &sensor_dev_attr_prochot2_max.dev_attr.attr, + &sensor_dev_attr_prochot1_override.dev_attr.attr, + &sensor_dev_attr_prochot2_override.dev_attr.attr, + &sensor_dev_attr_prochot1_interval.dev_attr.attr, + &sensor_dev_attr_prochot2_interval.dev_attr.attr, + &dev_attr_prochot_override_duty_cycle.attr, + &dev_attr_prochot_short.attr, + &sensor_dev_attr_vrdhot1.dev_attr.attr, + &sensor_dev_attr_vrdhot2.dev_attr.attr, + &dev_attr_gpio.attr, + &dev_attr_alarms.attr, + NULL +}; + +ATTRIBUTE_GROUPS(lm93); + +static void lm93_init_client(struct i2c_client *client) +{ + int i; + u8 reg; + + /* configure VID pin input thresholds */ + reg = lm93_read_byte(client, LM93_REG_GPI_VID_CTL); + lm93_write_byte(client, LM93_REG_GPI_VID_CTL, + reg | (vid_agtl ? 0x03 : 0x00)); + + if (init) { + /* enable #ALERT pin */ + reg = lm93_read_byte(client, LM93_REG_CONFIG); + lm93_write_byte(client, LM93_REG_CONFIG, reg | 0x08); + + /* enable ASF mode for BMC status registers */ + reg = lm93_read_byte(client, LM93_REG_STATUS_CONTROL); + lm93_write_byte(client, LM93_REG_STATUS_CONTROL, reg | 0x02); + + /* set sleep state to S0 */ + lm93_write_byte(client, LM93_REG_SLEEP_CONTROL, 0); + + /* unmask #VRDHOT and dynamic VCCP (if nec) error events */ + reg = lm93_read_byte(client, LM93_REG_MISC_ERR_MASK); + reg &= ~0x03; + reg &= ~(vccp_limit_type[0] ? 0x10 : 0); + reg &= ~(vccp_limit_type[1] ? 0x20 : 0); + lm93_write_byte(client, LM93_REG_MISC_ERR_MASK, reg); + } + + /* start monitoring */ + reg = lm93_read_byte(client, LM93_REG_CONFIG); + lm93_write_byte(client, LM93_REG_CONFIG, reg | 0x01); + + /* spin until ready */ + for (i = 0; i < 20; i++) { + msleep(10); + if ((lm93_read_byte(client, LM93_REG_CONFIG) & 0x80) == 0x80) + return; + } + + dev_warn(&client->dev, + "timed out waiting for sensor chip to signal ready!\n"); +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int lm93_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int mfr, ver; + const char *name; + + if (!i2c_check_functionality(adapter, LM93_SMBUS_FUNC_MIN)) + return -ENODEV; + + /* detection */ + mfr = lm93_read_byte(client, LM93_REG_MFR_ID); + if (mfr != 0x01) { + dev_dbg(&adapter->dev, + "detect failed, bad manufacturer id 0x%02x!\n", mfr); + return -ENODEV; + } + + ver = lm93_read_byte(client, LM93_REG_VER); + switch (ver) { + case LM93_MFR_ID: + case LM93_MFR_ID_PROTOTYPE: + name = "lm93"; + break; + case LM94_MFR_ID_2: + case LM94_MFR_ID: + case LM94_MFR_ID_PROTOTYPE: + name = "lm94"; + break; + default: + dev_dbg(&adapter->dev, + "detect failed, bad version id 0x%02x!\n", ver); + return -ENODEV; + } + + strlcpy(info->type, name, I2C_NAME_SIZE); + dev_dbg(&adapter->dev, "loading %s at %d, 0x%02x\n", + client->name, i2c_adapter_id(client->adapter), + client->addr); + + return 0; +} + +static int lm93_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct lm93_data *data; + struct device *hwmon_dev; + int func; + void (*update)(struct lm93_data *, struct i2c_client *); + + /* choose update routine based on bus capabilities */ + func = i2c_get_functionality(client->adapter); + if (((LM93_SMBUS_FUNC_FULL & func) == LM93_SMBUS_FUNC_FULL) && + (!disable_block)) { + dev_dbg(dev, "using SMBus block data transactions\n"); + update = lm93_update_client_full; + } else if ((LM93_SMBUS_FUNC_MIN & func) == LM93_SMBUS_FUNC_MIN) { + dev_dbg(dev, "disabled SMBus block data transactions\n"); + update = lm93_update_client_min; + } else { + dev_dbg(dev, "detect failed, smbus byte and/or word data not supported!\n"); + return -ENODEV; + } + + data = devm_kzalloc(dev, sizeof(struct lm93_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* housekeeping */ + data->client = client; + data->update = update; + mutex_init(&data->update_lock); + + /* initialize the chip */ + lm93_init_client(client); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + lm93_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id lm93_id[] = { + { "lm93", 0 }, + { "lm94", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm93_id); + +static struct i2c_driver lm93_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm93", + }, + .probe = lm93_probe, + .id_table = lm93_id, + .detect = lm93_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(lm93_driver); + +MODULE_AUTHOR("Mark M. Hoffman , " + "Hans J. Koch "); +MODULE_DESCRIPTION("LM93 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm95234.c b/drivers/hwmon/lm95234.c new file mode 100644 index 000000000..c7fcc9e7f --- /dev/null +++ b/drivers/hwmon/lm95234.c @@ -0,0 +1,768 @@ +/* + * Driver for Texas Instruments / National Semiconductor LM95234 + * + * Copyright (c) 2013, 2014 Guenter Roeck + * + * Derived from lm95241.c + * Copyright (C) 2008, 2010 Davide Rizzo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "lm95234" + +enum chips { lm95233, lm95234 }; + +static const unsigned short normal_i2c[] = { + 0x18, 0x2a, 0x2b, 0x4d, 0x4e, I2C_CLIENT_END }; + +/* LM95234 registers */ +#define LM95234_REG_MAN_ID 0xFE +#define LM95234_REG_CHIP_ID 0xFF +#define LM95234_REG_STATUS 0x02 +#define LM95234_REG_CONFIG 0x03 +#define LM95234_REG_CONVRATE 0x04 +#define LM95234_REG_STS_FAULT 0x07 +#define LM95234_REG_STS_TCRIT1 0x08 +#define LM95234_REG_STS_TCRIT2 0x09 +#define LM95234_REG_TEMPH(x) ((x) + 0x10) +#define LM95234_REG_TEMPL(x) ((x) + 0x20) +#define LM95234_REG_UTEMPH(x) ((x) + 0x19) /* Remote only */ +#define LM95234_REG_UTEMPL(x) ((x) + 0x29) +#define LM95234_REG_REM_MODEL 0x30 +#define LM95234_REG_REM_MODEL_STS 0x38 +#define LM95234_REG_OFFSET(x) ((x) + 0x31) /* Remote only */ +#define LM95234_REG_TCRIT1(x) ((x) + 0x40) +#define LM95234_REG_TCRIT2(x) ((x) + 0x49) /* Remote channel 1,2 */ +#define LM95234_REG_TCRIT_HYST 0x5a + +#define NATSEMI_MAN_ID 0x01 +#define LM95233_CHIP_ID 0x89 +#define LM95234_CHIP_ID 0x79 + +/* Client data (each client gets its own) */ +struct lm95234_data { + struct i2c_client *client; + const struct attribute_group *groups[3]; + struct mutex update_lock; + unsigned long last_updated, interval; /* in jiffies */ + bool valid; /* false until following fields are valid */ + /* registers values */ + int temp[5]; /* temperature (signed) */ + u32 status; /* fault/alarm status */ + u8 tcrit1[5]; /* critical temperature limit */ + u8 tcrit2[2]; /* high temperature limit */ + s8 toffset[4]; /* remote temperature offset */ + u8 thyst; /* common hysteresis */ + + u8 sensor_type; /* temperature sensor type */ +}; + +static int lm95234_read_temp(struct i2c_client *client, int index, int *t) +{ + int val; + u16 temp = 0; + + if (index) { + val = i2c_smbus_read_byte_data(client, + LM95234_REG_UTEMPH(index - 1)); + if (val < 0) + return val; + temp = val << 8; + val = i2c_smbus_read_byte_data(client, + LM95234_REG_UTEMPL(index - 1)); + if (val < 0) + return val; + temp |= val; + *t = temp; + } + /* + * Read signed temperature if unsigned temperature is 0, + * or if this is the local sensor. + */ + if (!temp) { + val = i2c_smbus_read_byte_data(client, + LM95234_REG_TEMPH(index)); + if (val < 0) + return val; + temp = val << 8; + val = i2c_smbus_read_byte_data(client, + LM95234_REG_TEMPL(index)); + if (val < 0) + return val; + temp |= val; + *t = (s16)temp; + } + return 0; +} + +static u16 update_intervals[] = { 143, 364, 1000, 2500 }; + +/* Fill value cache. Must be called with update lock held. */ + +static int lm95234_fill_cache(struct lm95234_data *data, + struct i2c_client *client) +{ + int i, ret; + + ret = i2c_smbus_read_byte_data(client, LM95234_REG_CONVRATE); + if (ret < 0) + return ret; + + data->interval = msecs_to_jiffies(update_intervals[ret & 0x03]); + + for (i = 0; i < ARRAY_SIZE(data->tcrit1); i++) { + ret = i2c_smbus_read_byte_data(client, LM95234_REG_TCRIT1(i)); + if (ret < 0) + return ret; + data->tcrit1[i] = ret; + } + for (i = 0; i < ARRAY_SIZE(data->tcrit2); i++) { + ret = i2c_smbus_read_byte_data(client, LM95234_REG_TCRIT2(i)); + if (ret < 0) + return ret; + data->tcrit2[i] = ret; + } + for (i = 0; i < ARRAY_SIZE(data->toffset); i++) { + ret = i2c_smbus_read_byte_data(client, LM95234_REG_OFFSET(i)); + if (ret < 0) + return ret; + data->toffset[i] = ret; + } + + ret = i2c_smbus_read_byte_data(client, LM95234_REG_TCRIT_HYST); + if (ret < 0) + return ret; + data->thyst = ret; + + ret = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL); + if (ret < 0) + return ret; + data->sensor_type = ret; + + return 0; +} + +static int lm95234_update_device(struct lm95234_data *data) +{ + struct i2c_client *client = data->client; + int ret; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + data->interval) || + !data->valid) { + int i; + + if (!data->valid) { + ret = lm95234_fill_cache(data, client); + if (ret < 0) + goto abort; + } + + data->valid = false; + for (i = 0; i < ARRAY_SIZE(data->temp); i++) { + ret = lm95234_read_temp(client, i, &data->temp[i]); + if (ret < 0) + goto abort; + } + + ret = i2c_smbus_read_byte_data(client, LM95234_REG_STS_FAULT); + if (ret < 0) + goto abort; + data->status = ret; + + ret = i2c_smbus_read_byte_data(client, LM95234_REG_STS_TCRIT1); + if (ret < 0) + goto abort; + data->status |= ret << 8; + + ret = i2c_smbus_read_byte_data(client, LM95234_REG_STS_TCRIT2); + if (ret < 0) + goto abort; + data->status |= ret << 16; + + data->last_updated = jiffies; + data->valid = true; + } + ret = 0; +abort: + mutex_unlock(&data->update_lock); + + return ret; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(data); + + if (ret) + return ret; + + return sprintf(buf, "%d\n", + DIV_ROUND_CLOSEST(data->temp[index] * 125, 32)); +} + +static ssize_t show_alarm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + u32 mask = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(data); + + if (ret) + return ret; + + return sprintf(buf, "%u", !!(data->status & mask)); +} + +static ssize_t show_type(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + u8 mask = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(data); + + if (ret) + return ret; + + return sprintf(buf, data->sensor_type & mask ? "1\n" : "2\n"); +} + +static ssize_t set_type(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + unsigned long val; + u8 mask = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(data); + + if (ret) + return ret; + + ret = kstrtoul(buf, 10, &val); + if (ret < 0) + return ret; + + if (val != 1 && val != 2) + return -EINVAL; + + mutex_lock(&data->update_lock); + if (val == 1) + data->sensor_type |= mask; + else + data->sensor_type &= ~mask; + data->valid = false; + i2c_smbus_write_byte_data(data->client, LM95234_REG_REM_MODEL, + data->sensor_type); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_tcrit2(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(data); + + if (ret) + return ret; + + return sprintf(buf, "%u", data->tcrit2[index] * 1000); +} + +static ssize_t set_tcrit2(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(attr)->index; + long val; + int ret = lm95234_update_device(data); + + if (ret) + return ret; + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, index ? 255 : 127); + + mutex_lock(&data->update_lock); + data->tcrit2[index] = val; + i2c_smbus_write_byte_data(data->client, LM95234_REG_TCRIT2(index), val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_tcrit2_hyst(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(data); + + if (ret) + return ret; + + /* Result can be negative, so be careful with unsigned operands */ + return sprintf(buf, "%d", + ((int)data->tcrit2[index] - (int)data->thyst) * 1000); +} + +static ssize_t show_tcrit1(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%u", data->tcrit1[index] * 1000); +} + +static ssize_t set_tcrit1(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(data); + long val; + + if (ret) + return ret; + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, 255); + + mutex_lock(&data->update_lock); + data->tcrit1[index] = val; + i2c_smbus_write_byte_data(data->client, LM95234_REG_TCRIT1(index), val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_tcrit1_hyst(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(data); + + if (ret) + return ret; + + /* Result can be negative, so be careful with unsigned operands */ + return sprintf(buf, "%d", + ((int)data->tcrit1[index] - (int)data->thyst) * 1000); +} + +static ssize_t set_tcrit1_hyst(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(data); + long val; + + if (ret) + return ret; + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + val = DIV_ROUND_CLOSEST(val, 1000); + val = clamp_val((int)data->tcrit1[index] - val, 0, 31); + + mutex_lock(&data->update_lock); + data->thyst = val; + i2c_smbus_write_byte_data(data->client, LM95234_REG_TCRIT_HYST, val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_offset(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(data); + + if (ret) + return ret; + + return sprintf(buf, "%d", data->toffset[index] * 500); +} + +static ssize_t set_offset(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(attr)->index; + int ret = lm95234_update_device(data); + long val; + + if (ret) + return ret; + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + /* Accuracy is 1/2 degrees C */ + val = clamp_val(DIV_ROUND_CLOSEST(val, 500), -128, 127); + + mutex_lock(&data->update_lock); + data->toffset[index] = val; + i2c_smbus_write_byte_data(data->client, LM95234_REG_OFFSET(index), val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t update_interval_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + int ret = lm95234_update_device(data); + + if (ret) + return ret; + + return sprintf(buf, "%lu\n", + DIV_ROUND_CLOSEST(data->interval * 1000, HZ)); +} + +static ssize_t update_interval_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + int ret = lm95234_update_device(data); + unsigned long val; + u8 regval; + + if (ret) + return ret; + + ret = kstrtoul(buf, 10, &val); + if (ret < 0) + return ret; + + for (regval = 0; regval < 3; regval++) { + if (val <= update_intervals[regval]) + break; + } + + mutex_lock(&data->update_lock); + data->interval = msecs_to_jiffies(update_intervals[regval]); + i2c_smbus_write_byte_data(data->client, LM95234_REG_CONVRATE, regval); + mutex_unlock(&data->update_lock); + + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp, NULL, 4); + +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, + BIT(0) | BIT(1)); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_alarm, NULL, + BIT(2) | BIT(3)); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_alarm, NULL, + BIT(4) | BIT(5)); +static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_alarm, NULL, + BIT(6) | BIT(7)); + +static SENSOR_DEVICE_ATTR(temp2_type, S_IWUSR | S_IRUGO, show_type, set_type, + BIT(1)); +static SENSOR_DEVICE_ATTR(temp3_type, S_IWUSR | S_IRUGO, show_type, set_type, + BIT(2)); +static SENSOR_DEVICE_ATTR(temp4_type, S_IWUSR | S_IRUGO, show_type, set_type, + BIT(3)); +static SENSOR_DEVICE_ATTR(temp5_type, S_IWUSR | S_IRUGO, show_type, set_type, + BIT(4)); + +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_tcrit1, + set_tcrit1, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_tcrit2, + set_tcrit2, 0); +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_tcrit2, + set_tcrit2, 1); +static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_tcrit1, + set_tcrit1, 3); +static SENSOR_DEVICE_ATTR(temp5_max, S_IWUSR | S_IRUGO, show_tcrit1, + set_tcrit1, 4); + +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, show_tcrit1_hyst, + set_tcrit1_hyst, 0); +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IRUGO, show_tcrit2_hyst, NULL, 0); +static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IRUGO, show_tcrit2_hyst, NULL, 1); +static SENSOR_DEVICE_ATTR(temp4_max_hyst, S_IRUGO, show_tcrit1_hyst, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_max_hyst, S_IRUGO, show_tcrit1_hyst, NULL, 4); + +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, + BIT(0 + 8)); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, + BIT(1 + 16)); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_alarm, NULL, + BIT(2 + 16)); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_alarm, NULL, + BIT(3 + 8)); +static SENSOR_DEVICE_ATTR(temp5_max_alarm, S_IRUGO, show_alarm, NULL, + BIT(4 + 8)); + +static SENSOR_DEVICE_ATTR(temp2_crit, S_IWUSR | S_IRUGO, show_tcrit1, + set_tcrit1, 1); +static SENSOR_DEVICE_ATTR(temp3_crit, S_IWUSR | S_IRUGO, show_tcrit1, + set_tcrit1, 2); + +static SENSOR_DEVICE_ATTR(temp2_crit_hyst, S_IRUGO, show_tcrit1_hyst, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_crit_hyst, S_IRUGO, show_tcrit1_hyst, NULL, 2); + +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, + BIT(1 + 8)); +static SENSOR_DEVICE_ATTR(temp3_crit_alarm, S_IRUGO, show_alarm, NULL, + BIT(2 + 8)); + +static SENSOR_DEVICE_ATTR(temp2_offset, S_IWUSR | S_IRUGO, show_offset, + set_offset, 0); +static SENSOR_DEVICE_ATTR(temp3_offset, S_IWUSR | S_IRUGO, show_offset, + set_offset, 1); +static SENSOR_DEVICE_ATTR(temp4_offset, S_IWUSR | S_IRUGO, show_offset, + set_offset, 2); +static SENSOR_DEVICE_ATTR(temp5_offset, S_IWUSR | S_IRUGO, show_offset, + set_offset, 3); + +static DEVICE_ATTR_RW(update_interval); + +static struct attribute *lm95234_common_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp2_type.dev_attr.attr, + &sensor_dev_attr_temp3_type.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_offset.dev_attr.attr, + &sensor_dev_attr_temp3_offset.dev_attr.attr, + &dev_attr_update_interval.attr, + NULL +}; + +static const struct attribute_group lm95234_common_group = { + .attrs = lm95234_common_attrs, +}; + +static struct attribute *lm95234_attrs[] = { + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + &sensor_dev_attr_temp5_fault.dev_attr.attr, + &sensor_dev_attr_temp4_type.dev_attr.attr, + &sensor_dev_attr_temp5_type.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp4_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp5_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_offset.dev_attr.attr, + &sensor_dev_attr_temp5_offset.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm95234_group = { + .attrs = lm95234_attrs, +}; + +static int lm95234_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int address = client->addr; + u8 config_mask, model_mask; + int mfg_id, chip_id, val; + const char *name; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + mfg_id = i2c_smbus_read_byte_data(client, LM95234_REG_MAN_ID); + if (mfg_id != NATSEMI_MAN_ID) + return -ENODEV; + + chip_id = i2c_smbus_read_byte_data(client, LM95234_REG_CHIP_ID); + switch (chip_id) { + case LM95233_CHIP_ID: + if (address != 0x18 && address != 0x2a && address != 0x2b) + return -ENODEV; + config_mask = 0xbf; + model_mask = 0xf9; + name = "lm95233"; + break; + case LM95234_CHIP_ID: + if (address != 0x18 && address != 0x4d && address != 0x4e) + return -ENODEV; + config_mask = 0xbc; + model_mask = 0xe1; + name = "lm95234"; + break; + default: + return -ENODEV; + } + + val = i2c_smbus_read_byte_data(client, LM95234_REG_STATUS); + if (val & 0x30) + return -ENODEV; + + val = i2c_smbus_read_byte_data(client, LM95234_REG_CONFIG); + if (val & config_mask) + return -ENODEV; + + val = i2c_smbus_read_byte_data(client, LM95234_REG_CONVRATE); + if (val & 0xfc) + return -ENODEV; + + val = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL); + if (val & model_mask) + return -ENODEV; + + val = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL_STS); + if (val & model_mask) + return -ENODEV; + + strlcpy(info->type, name, I2C_NAME_SIZE); + return 0; +} + +static int lm95234_init_client(struct i2c_client *client) +{ + int val, model; + + /* start conversion if necessary */ + val = i2c_smbus_read_byte_data(client, LM95234_REG_CONFIG); + if (val < 0) + return val; + if (val & 0x40) + i2c_smbus_write_byte_data(client, LM95234_REG_CONFIG, + val & ~0x40); + + /* If diode type status reports an error, try to fix it */ + val = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL_STS); + if (val < 0) + return val; + model = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL); + if (model < 0) + return model; + if (model & val) { + dev_notice(&client->dev, + "Fixing remote diode type misconfiguration (0x%x)\n", + val); + i2c_smbus_write_byte_data(client, LM95234_REG_REM_MODEL, + model & ~val); + } + return 0; +} + +static int lm95234_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct lm95234_data *data; + struct device *hwmon_dev; + int err; + + data = devm_kzalloc(dev, sizeof(struct lm95234_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* Initialize the LM95234 chip */ + err = lm95234_init_client(client); + if (err < 0) + return err; + + data->groups[0] = &lm95234_common_group; + if (id->driver_data == lm95234) + data->groups[1] = &lm95234_group; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +/* Driver data (common to all clients) */ +static const struct i2c_device_id lm95234_id[] = { + { "lm95233", lm95233 }, + { "lm95234", lm95234 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm95234_id); + +static struct i2c_driver lm95234_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = DRVNAME, + }, + .probe = lm95234_probe, + .id_table = lm95234_id, + .detect = lm95234_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(lm95234_driver); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("LM95233/LM95234 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm95241.c b/drivers/hwmon/lm95241.c new file mode 100644 index 000000000..8c573e6e9 --- /dev/null +++ b/drivers/hwmon/lm95241.c @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2008, 2010 Davide Rizzo + * + * The LM95241 is a sensor chip made by National Semiconductors. + * It reports up to three temperatures (its own plus up to two external ones). + * Complete datasheet can be obtained from National's website at: + * http://www.national.com/ds.cgi/LM/LM95241.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEVNAME "lm95241" + +static const unsigned short normal_i2c[] = { + 0x19, 0x2a, 0x2b, I2C_CLIENT_END }; + +/* LM95241 registers */ +#define LM95241_REG_R_MAN_ID 0xFE +#define LM95241_REG_R_CHIP_ID 0xFF +#define LM95241_REG_R_STATUS 0x02 +#define LM95241_REG_RW_CONFIG 0x03 +#define LM95241_REG_RW_REM_FILTER 0x06 +#define LM95241_REG_RW_TRUTHERM 0x07 +#define LM95241_REG_W_ONE_SHOT 0x0F +#define LM95241_REG_R_LOCAL_TEMPH 0x10 +#define LM95241_REG_R_REMOTE1_TEMPH 0x11 +#define LM95241_REG_R_REMOTE2_TEMPH 0x12 +#define LM95241_REG_R_LOCAL_TEMPL 0x20 +#define LM95241_REG_R_REMOTE1_TEMPL 0x21 +#define LM95241_REG_R_REMOTE2_TEMPL 0x22 +#define LM95241_REG_RW_REMOTE_MODEL 0x30 + +/* LM95241 specific bitfields */ +#define CFG_STOP BIT(6) +#define CFG_CR0076 0x00 +#define CFG_CR0182 BIT(4) +#define CFG_CR1000 BIT(5) +#define CFG_CR2700 (BIT(4) | BIT(5)) +#define CFG_CRMASK (BIT(4) | BIT(5)) +#define R1MS_MASK BIT(0) +#define R2MS_MASK BIT(2) +#define R1DF_MASK BIT(1) +#define R2DF_MASK BIT(2) +#define R1FE_MASK BIT(0) +#define R2FE_MASK BIT(2) +#define R1DM BIT(0) +#define R2DM BIT(1) +#define TT1_SHIFT 0 +#define TT2_SHIFT 4 +#define TT_OFF 0 +#define TT_ON 1 +#define TT_MASK 7 +#define NATSEMI_MAN_ID 0x01 +#define LM95231_CHIP_ID 0xA1 +#define LM95241_CHIP_ID 0xA4 + +static const u8 lm95241_reg_address[] = { + LM95241_REG_R_LOCAL_TEMPH, + LM95241_REG_R_LOCAL_TEMPL, + LM95241_REG_R_REMOTE1_TEMPH, + LM95241_REG_R_REMOTE1_TEMPL, + LM95241_REG_R_REMOTE2_TEMPH, + LM95241_REG_R_REMOTE2_TEMPL +}; + +/* Client data (each client gets its own) */ +struct lm95241_data { + struct i2c_client *client; + struct mutex update_lock; + unsigned long last_updated; /* in jiffies */ + unsigned long interval; /* in milli-seconds */ + char valid; /* zero until following fields are valid */ + /* registers values */ + u8 temp[ARRAY_SIZE(lm95241_reg_address)]; + u8 status, config, model, trutherm; +}; + +/* Conversions */ +static int temp_from_reg_signed(u8 val_h, u8 val_l) +{ + s16 val_hl = (val_h << 8) | val_l; + return val_hl * 1000 / 256; +} + +static int temp_from_reg_unsigned(u8 val_h, u8 val_l) +{ + u16 val_hl = (val_h << 8) | val_l; + return val_hl * 1000 / 256; +} + +static struct lm95241_data *lm95241_update_device(struct device *dev) +{ + struct lm95241_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + + msecs_to_jiffies(data->interval)) || + !data->valid) { + int i; + + dev_dbg(dev, "Updating lm95241 data.\n"); + for (i = 0; i < ARRAY_SIZE(lm95241_reg_address); i++) + data->temp[i] + = i2c_smbus_read_byte_data(client, + lm95241_reg_address[i]); + + data->status = i2c_smbus_read_byte_data(client, + LM95241_REG_R_STATUS); + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static int lm95241_read_chip(struct device *dev, u32 attr, int channel, + long *val) +{ + struct lm95241_data *data = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_chip_update_interval: + *val = data->interval; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int lm95241_read_temp(struct device *dev, u32 attr, int channel, + long *val) +{ + struct lm95241_data *data = lm95241_update_device(dev); + + switch (attr) { + case hwmon_temp_input: + if (!channel || (data->config & BIT(channel - 1))) + *val = temp_from_reg_signed(data->temp[channel * 2], + data->temp[channel * 2 + 1]); + else + *val = temp_from_reg_unsigned(data->temp[channel * 2], + data->temp[channel * 2 + 1]); + return 0; + case hwmon_temp_min: + if (channel == 1) + *val = (data->config & R1DF_MASK) ? -128000 : 0; + else + *val = (data->config & R2DF_MASK) ? -128000 : 0; + return 0; + case hwmon_temp_max: + if (channel == 1) + *val = (data->config & R1DF_MASK) ? 127875 : 255875; + else + *val = (data->config & R2DF_MASK) ? 127875 : 255875; + return 0; + case hwmon_temp_type: + if (channel == 1) + *val = (data->model & R1MS_MASK) ? 1 : 2; + else + *val = (data->model & R2MS_MASK) ? 1 : 2; + return 0; + case hwmon_temp_fault: + if (channel == 1) + *val = !!(data->status & R1DM); + else + *val = !!(data->status & R2DM); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int lm95241_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_chip: + return lm95241_read_chip(dev, attr, channel, val); + case hwmon_temp: + return lm95241_read_temp(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int lm95241_write_chip(struct device *dev, u32 attr, int channel, + long val) +{ + struct lm95241_data *data = dev_get_drvdata(dev); + int convrate; + u8 config; + int ret; + + mutex_lock(&data->update_lock); + + switch (attr) { + case hwmon_chip_update_interval: + config = data->config & ~CFG_CRMASK; + if (val < 130) { + convrate = 76; + config |= CFG_CR0076; + } else if (val < 590) { + convrate = 182; + config |= CFG_CR0182; + } else if (val < 1850) { + convrate = 1000; + config |= CFG_CR1000; + } else { + convrate = 2700; + config |= CFG_CR2700; + } + data->interval = convrate; + data->config = config; + ret = i2c_smbus_write_byte_data(data->client, + LM95241_REG_RW_CONFIG, config); + break; + default: + ret = -EOPNOTSUPP; + break; + } + mutex_unlock(&data->update_lock); + return ret; +} + +static int lm95241_write_temp(struct device *dev, u32 attr, int channel, + long val) +{ + struct lm95241_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret; + + mutex_lock(&data->update_lock); + + switch (attr) { + case hwmon_temp_min: + if (channel == 1) { + if (val < 0) + data->config |= R1DF_MASK; + else + data->config &= ~R1DF_MASK; + } else { + if (val < 0) + data->config |= R2DF_MASK; + else + data->config &= ~R2DF_MASK; + } + data->valid = 0; + ret = i2c_smbus_write_byte_data(client, LM95241_REG_RW_CONFIG, + data->config); + break; + case hwmon_temp_max: + if (channel == 1) { + if (val <= 127875) + data->config |= R1DF_MASK; + else + data->config &= ~R1DF_MASK; + } else { + if (val <= 127875) + data->config |= R2DF_MASK; + else + data->config &= ~R2DF_MASK; + } + data->valid = 0; + ret = i2c_smbus_write_byte_data(client, LM95241_REG_RW_CONFIG, + data->config); + break; + case hwmon_temp_type: + if (val != 1 && val != 2) { + ret = -EINVAL; + break; + } + if (channel == 1) { + data->trutherm &= ~(TT_MASK << TT1_SHIFT); + if (val == 1) { + data->model |= R1MS_MASK; + data->trutherm |= (TT_ON << TT1_SHIFT); + } else { + data->model &= ~R1MS_MASK; + data->trutherm |= (TT_OFF << TT1_SHIFT); + } + } else { + data->trutherm &= ~(TT_MASK << TT2_SHIFT); + if (val == 1) { + data->model |= R2MS_MASK; + data->trutherm |= (TT_ON << TT2_SHIFT); + } else { + data->model &= ~R2MS_MASK; + data->trutherm |= (TT_OFF << TT2_SHIFT); + } + } + ret = i2c_smbus_write_byte_data(client, + LM95241_REG_RW_REMOTE_MODEL, + data->model); + if (ret < 0) + break; + ret = i2c_smbus_write_byte_data(client, LM95241_REG_RW_TRUTHERM, + data->trutherm); + break; + default: + ret = -EOPNOTSUPP; + break; + } + + mutex_unlock(&data->update_lock); + + return ret; +} + +static int lm95241_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_chip: + return lm95241_write_chip(dev, attr, channel, val); + case hwmon_temp: + return lm95241_write_temp(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t lm95241_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return S_IRUGO | S_IWUSR; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + return S_IRUGO; + case hwmon_temp_fault: + return S_IRUGO; + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_type: + return S_IRUGO | S_IWUSR; + } + break; + default: + break; + } + return 0; +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int lm95241_detect(struct i2c_client *new_client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = new_client->adapter; + const char *name; + int mfg_id, chip_id; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + mfg_id = i2c_smbus_read_byte_data(new_client, LM95241_REG_R_MAN_ID); + if (mfg_id != NATSEMI_MAN_ID) + return -ENODEV; + + chip_id = i2c_smbus_read_byte_data(new_client, LM95241_REG_R_CHIP_ID); + switch (chip_id) { + case LM95231_CHIP_ID: + name = "lm95231"; + break; + case LM95241_CHIP_ID: + name = "lm95241"; + break; + default: + return -ENODEV; + } + + /* Fill the i2c board info */ + strlcpy(info->type, name, I2C_NAME_SIZE); + return 0; +} + +static void lm95241_init_client(struct i2c_client *client, + struct lm95241_data *data) +{ + data->interval = 1000; + data->config = CFG_CR1000; + data->trutherm = (TT_OFF << TT1_SHIFT) | (TT_OFF << TT2_SHIFT); + + i2c_smbus_write_byte_data(client, LM95241_REG_RW_CONFIG, data->config); + i2c_smbus_write_byte_data(client, LM95241_REG_RW_REM_FILTER, + R1FE_MASK | R2FE_MASK); + i2c_smbus_write_byte_data(client, LM95241_REG_RW_TRUTHERM, + data->trutherm); + i2c_smbus_write_byte_data(client, LM95241_REG_RW_REMOTE_MODEL, + data->model); +} + +static const u32 lm95241_chip_config[] = { + HWMON_C_UPDATE_INTERVAL, + 0 +}; + +static const struct hwmon_channel_info lm95241_chip = { + .type = hwmon_chip, + .config = lm95241_chip_config, +}; + +static const u32 lm95241_temp_config[] = { + HWMON_T_INPUT, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_TYPE | + HWMON_T_FAULT, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_TYPE | + HWMON_T_FAULT, + 0 +}; + +static const struct hwmon_channel_info lm95241_temp = { + .type = hwmon_temp, + .config = lm95241_temp_config, +}; + +static const struct hwmon_channel_info *lm95241_info[] = { + &lm95241_chip, + &lm95241_temp, + NULL +}; + +static const struct hwmon_ops lm95241_hwmon_ops = { + .is_visible = lm95241_is_visible, + .read = lm95241_read, + .write = lm95241_write, +}; + +static const struct hwmon_chip_info lm95241_chip_info = { + .ops = &lm95241_hwmon_ops, + .info = lm95241_info, +}; + +static int lm95241_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct lm95241_data *data; + struct device *hwmon_dev; + + data = devm_kzalloc(dev, sizeof(struct lm95241_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* Initialize the LM95241 chip */ + lm95241_init_client(client, data); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, + &lm95241_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +/* Driver data (common to all clients) */ +static const struct i2c_device_id lm95241_id[] = { + { "lm95231", 0 }, + { "lm95241", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm95241_id); + +static struct i2c_driver lm95241_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = DEVNAME, + }, + .probe = lm95241_probe, + .id_table = lm95241_id, + .detect = lm95241_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(lm95241_driver); + +MODULE_AUTHOR("Davide Rizzo "); +MODULE_DESCRIPTION("LM95231/LM95241 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm95245.c b/drivers/hwmon/lm95245.c new file mode 100644 index 000000000..27cb06d65 --- /dev/null +++ b/drivers/hwmon/lm95245.c @@ -0,0 +1,648 @@ +/* + * Copyright (C) 2011 Alexander Stein + * + * The LM95245 is a sensor chip made by TI / National Semiconductor. + * It reports up to two temperatures (its own plus an external one). + * + * This driver is based on lm95241.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static const unsigned short normal_i2c[] = { + 0x18, 0x19, 0x29, 0x4c, 0x4d, I2C_CLIENT_END }; + +/* LM95245 registers */ +/* general registers */ +#define LM95245_REG_RW_CONFIG1 0x03 +#define LM95245_REG_RW_CONVERS_RATE 0x04 +#define LM95245_REG_W_ONE_SHOT 0x0F + +/* diode configuration */ +#define LM95245_REG_RW_CONFIG2 0xBF +#define LM95245_REG_RW_REMOTE_OFFH 0x11 +#define LM95245_REG_RW_REMOTE_OFFL 0x12 + +/* status registers */ +#define LM95245_REG_R_STATUS1 0x02 +#define LM95245_REG_R_STATUS2 0x33 + +/* limit registers */ +#define LM95245_REG_RW_REMOTE_OS_LIMIT 0x07 +#define LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT 0x20 +#define LM95245_REG_RW_REMOTE_TCRIT_LIMIT 0x19 +#define LM95245_REG_RW_COMMON_HYSTERESIS 0x21 + +/* temperature signed */ +#define LM95245_REG_R_LOCAL_TEMPH_S 0x00 +#define LM95245_REG_R_LOCAL_TEMPL_S 0x30 +#define LM95245_REG_R_REMOTE_TEMPH_S 0x01 +#define LM95245_REG_R_REMOTE_TEMPL_S 0x10 +/* temperature unsigned */ +#define LM95245_REG_R_REMOTE_TEMPH_U 0x31 +#define LM95245_REG_R_REMOTE_TEMPL_U 0x32 + +/* id registers */ +#define LM95245_REG_R_MAN_ID 0xFE +#define LM95245_REG_R_CHIP_ID 0xFF + +/* LM95245 specific bitfields */ +#define CFG_STOP 0x40 +#define CFG_REMOTE_TCRIT_MASK 0x10 +#define CFG_REMOTE_OS_MASK 0x08 +#define CFG_LOCAL_TCRIT_MASK 0x04 +#define CFG_LOCAL_OS_MASK 0x02 + +#define CFG2_OS_A0 0x40 +#define CFG2_DIODE_FAULT_OS 0x20 +#define CFG2_DIODE_FAULT_TCRIT 0x10 +#define CFG2_REMOTE_TT 0x08 +#define CFG2_REMOTE_FILTER_DIS 0x00 +#define CFG2_REMOTE_FILTER_EN 0x06 + +/* conversation rate in ms */ +#define RATE_CR0063 0x00 +#define RATE_CR0364 0x01 +#define RATE_CR1000 0x02 +#define RATE_CR2500 0x03 + +#define STATUS1_ROS 0x10 +#define STATUS1_DIODE_FAULT 0x04 +#define STATUS1_RTCRIT 0x02 +#define STATUS1_LOC 0x01 + +#define MANUFACTURER_ID 0x01 +#define LM95235_REVISION 0xB1 +#define LM95245_REVISION 0xB3 + +static const u8 lm95245_reg_address[] = { + LM95245_REG_R_LOCAL_TEMPH_S, + LM95245_REG_R_LOCAL_TEMPL_S, + LM95245_REG_R_REMOTE_TEMPH_S, + LM95245_REG_R_REMOTE_TEMPL_S, + LM95245_REG_R_REMOTE_TEMPH_U, + LM95245_REG_R_REMOTE_TEMPL_U, + LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT, + LM95245_REG_RW_REMOTE_TCRIT_LIMIT, + LM95245_REG_RW_COMMON_HYSTERESIS, + LM95245_REG_R_STATUS1, +}; + +/* Client data (each client gets its own) */ +struct lm95245_data { + struct regmap *regmap; + struct mutex update_lock; + int interval; /* in msecs */ +}; + +/* Conversions */ +static int temp_from_reg_unsigned(u8 val_h, u8 val_l) +{ + return val_h * 1000 + val_l * 1000 / 256; +} + +static int temp_from_reg_signed(u8 val_h, u8 val_l) +{ + if (val_h & 0x80) + return (val_h - 0x100) * 1000; + return temp_from_reg_unsigned(val_h, val_l); +} + +static int lm95245_read_conversion_rate(struct lm95245_data *data) +{ + unsigned int rate; + int ret; + + ret = regmap_read(data->regmap, LM95245_REG_RW_CONVERS_RATE, &rate); + if (ret < 0) + return ret; + + switch (rate) { + case RATE_CR0063: + data->interval = 63; + break; + case RATE_CR0364: + data->interval = 364; + break; + case RATE_CR1000: + data->interval = 1000; + break; + case RATE_CR2500: + default: + data->interval = 2500; + break; + } + return 0; +} + +static int lm95245_set_conversion_rate(struct lm95245_data *data, long interval) +{ + int ret, rate; + + if (interval <= 63) { + interval = 63; + rate = RATE_CR0063; + } else if (interval <= 364) { + interval = 364; + rate = RATE_CR0364; + } else if (interval <= 1000) { + interval = 1000; + rate = RATE_CR1000; + } else { + interval = 2500; + rate = RATE_CR2500; + } + + ret = regmap_write(data->regmap, LM95245_REG_RW_CONVERS_RATE, rate); + if (ret < 0) + return ret; + + data->interval = interval; + return 0; +} + +static int lm95245_read_temp(struct device *dev, u32 attr, int channel, + long *val) +{ + struct lm95245_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + int ret, regl, regh, regvall, regvalh; + + switch (attr) { + case hwmon_temp_input: + regl = channel ? LM95245_REG_R_REMOTE_TEMPL_S : + LM95245_REG_R_LOCAL_TEMPL_S; + regh = channel ? LM95245_REG_R_REMOTE_TEMPH_S : + LM95245_REG_R_LOCAL_TEMPH_S; + ret = regmap_read(regmap, regl, ®vall); + if (ret < 0) + return ret; + ret = regmap_read(regmap, regh, ®valh); + if (ret < 0) + return ret; + /* + * Local temp is always signed. + * Remote temp has both signed and unsigned data. + * Use signed calculation for remote if signed bit is set + * or if reported temperature is below signed limit. + */ + if (!channel || (regvalh & 0x80) || regvalh < 0x7f) { + *val = temp_from_reg_signed(regvalh, regvall); + return 0; + } + ret = regmap_read(regmap, LM95245_REG_R_REMOTE_TEMPL_U, + ®vall); + if (ret < 0) + return ret; + ret = regmap_read(regmap, LM95245_REG_R_REMOTE_TEMPH_U, + ®valh); + if (ret < 0) + return ret; + *val = temp_from_reg_unsigned(regvalh, regvall); + return 0; + case hwmon_temp_max: + ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OS_LIMIT, + ®valh); + if (ret < 0) + return ret; + *val = regvalh * 1000; + return 0; + case hwmon_temp_crit: + regh = channel ? LM95245_REG_RW_REMOTE_TCRIT_LIMIT : + LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT; + ret = regmap_read(regmap, regh, ®valh); + if (ret < 0) + return ret; + *val = regvalh * 1000; + return 0; + case hwmon_temp_max_hyst: + ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OS_LIMIT, + ®valh); + if (ret < 0) + return ret; + ret = regmap_read(regmap, LM95245_REG_RW_COMMON_HYSTERESIS, + ®vall); + if (ret < 0) + return ret; + *val = (regvalh - regvall) * 1000; + return 0; + case hwmon_temp_crit_hyst: + regh = channel ? LM95245_REG_RW_REMOTE_TCRIT_LIMIT : + LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT; + ret = regmap_read(regmap, regh, ®valh); + if (ret < 0) + return ret; + ret = regmap_read(regmap, LM95245_REG_RW_COMMON_HYSTERESIS, + ®vall); + if (ret < 0) + return ret; + *val = (regvalh - regvall) * 1000; + return 0; + case hwmon_temp_type: + ret = regmap_read(regmap, LM95245_REG_RW_CONFIG2, ®valh); + if (ret < 0) + return ret; + *val = (regvalh & CFG2_REMOTE_TT) ? 1 : 2; + return 0; + case hwmon_temp_offset: + ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OFFL, + ®vall); + if (ret < 0) + return ret; + ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OFFH, + ®valh); + if (ret < 0) + return ret; + *val = temp_from_reg_signed(regvalh, regvall); + return 0; + case hwmon_temp_max_alarm: + ret = regmap_read(regmap, LM95245_REG_R_STATUS1, ®valh); + if (ret < 0) + return ret; + *val = !!(regvalh & STATUS1_ROS); + return 0; + case hwmon_temp_crit_alarm: + ret = regmap_read(regmap, LM95245_REG_R_STATUS1, ®valh); + if (ret < 0) + return ret; + *val = !!(regvalh & (channel ? STATUS1_RTCRIT : STATUS1_LOC)); + return 0; + case hwmon_temp_fault: + ret = regmap_read(regmap, LM95245_REG_R_STATUS1, ®valh); + if (ret < 0) + return ret; + *val = !!(regvalh & STATUS1_DIODE_FAULT); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int lm95245_write_temp(struct device *dev, u32 attr, int channel, + long val) +{ + struct lm95245_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + unsigned int regval; + int ret, reg; + + switch (attr) { + case hwmon_temp_max: + val = clamp_val(val / 1000, 0, 255); + ret = regmap_write(regmap, LM95245_REG_RW_REMOTE_OS_LIMIT, val); + return ret; + case hwmon_temp_crit: + reg = channel ? LM95245_REG_RW_REMOTE_TCRIT_LIMIT : + LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT; + val = clamp_val(val / 1000, 0, channel ? 255 : 127); + ret = regmap_write(regmap, reg, val); + return ret; + case hwmon_temp_crit_hyst: + mutex_lock(&data->update_lock); + ret = regmap_read(regmap, LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT, + ®val); + if (ret < 0) { + mutex_unlock(&data->update_lock); + return ret; + } + /* Clamp to reasonable range to prevent overflow */ + val = clamp_val(val, -1000000, 1000000); + val = regval - val / 1000; + val = clamp_val(val, 0, 31); + ret = regmap_write(regmap, LM95245_REG_RW_COMMON_HYSTERESIS, + val); + mutex_unlock(&data->update_lock); + return ret; + case hwmon_temp_offset: + val = clamp_val(val, -128000, 127875); + val = val * 256 / 1000; + mutex_lock(&data->update_lock); + ret = regmap_write(regmap, LM95245_REG_RW_REMOTE_OFFL, + val & 0xe0); + if (ret < 0) { + mutex_unlock(&data->update_lock); + return ret; + } + ret = regmap_write(regmap, LM95245_REG_RW_REMOTE_OFFH, + (val >> 8) & 0xff); + mutex_unlock(&data->update_lock); + return ret; + case hwmon_temp_type: + if (val != 1 && val != 2) + return -EINVAL; + ret = regmap_update_bits(regmap, LM95245_REG_RW_CONFIG2, + CFG2_REMOTE_TT, + val == 1 ? CFG2_REMOTE_TT : 0); + return ret; + default: + return -EOPNOTSUPP; + } +} + +static int lm95245_read_chip(struct device *dev, u32 attr, int channel, + long *val) +{ + struct lm95245_data *data = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_chip_update_interval: + *val = data->interval; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int lm95245_write_chip(struct device *dev, u32 attr, int channel, + long val) +{ + struct lm95245_data *data = dev_get_drvdata(dev); + int ret; + + switch (attr) { + case hwmon_chip_update_interval: + mutex_lock(&data->update_lock); + ret = lm95245_set_conversion_rate(data, val); + mutex_unlock(&data->update_lock); + return ret; + default: + return -EOPNOTSUPP; + } +} + +static int lm95245_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_chip: + return lm95245_read_chip(dev, attr, channel, val); + case hwmon_temp: + return lm95245_read_temp(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int lm95245_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_chip: + return lm95245_write_chip(dev, attr, channel, val); + case hwmon_temp: + return lm95245_write_temp(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t lm95245_temp_is_visible(const void *data, u32 attr, int channel) +{ + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_max_alarm: + case hwmon_temp_max_hyst: + case hwmon_temp_crit_alarm: + case hwmon_temp_fault: + return S_IRUGO; + case hwmon_temp_type: + case hwmon_temp_max: + case hwmon_temp_crit: + case hwmon_temp_offset: + return S_IRUGO | S_IWUSR; + case hwmon_temp_crit_hyst: + return (channel == 0) ? S_IRUGO | S_IWUSR : S_IRUGO; + default: + return 0; + } +} + +static umode_t lm95245_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return S_IRUGO | S_IWUSR; + default: + return 0; + } + case hwmon_temp: + return lm95245_temp_is_visible(data, attr, channel); + default: + return 0; + } +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int lm95245_detect(struct i2c_client *new_client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = new_client->adapter; + int address = new_client->addr; + const char *name; + int rev, id; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + id = i2c_smbus_read_byte_data(new_client, LM95245_REG_R_MAN_ID); + if (id != MANUFACTURER_ID) + return -ENODEV; + + rev = i2c_smbus_read_byte_data(new_client, LM95245_REG_R_CHIP_ID); + switch (rev) { + case LM95235_REVISION: + if (address != 0x18 && address != 0x29 && address != 0x4c) + return -ENODEV; + name = "lm95235"; + break; + case LM95245_REVISION: + name = "lm95245"; + break; + default: + return -ENODEV; + } + + strlcpy(info->type, name, I2C_NAME_SIZE); + return 0; +} + +static int lm95245_init_client(struct lm95245_data *data) +{ + int ret; + + ret = lm95245_read_conversion_rate(data); + if (ret < 0) + return ret; + + return regmap_update_bits(data->regmap, LM95245_REG_RW_CONFIG1, + CFG_STOP, 0); +} + +static bool lm95245_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LM95245_REG_RW_CONFIG1: + case LM95245_REG_RW_CONVERS_RATE: + case LM95245_REG_W_ONE_SHOT: + case LM95245_REG_RW_CONFIG2: + case LM95245_REG_RW_REMOTE_OFFH: + case LM95245_REG_RW_REMOTE_OFFL: + case LM95245_REG_RW_REMOTE_OS_LIMIT: + case LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT: + case LM95245_REG_RW_REMOTE_TCRIT_LIMIT: + case LM95245_REG_RW_COMMON_HYSTERESIS: + return true; + default: + return false; + } +} + +static bool lm95245_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LM95245_REG_R_STATUS1: + case LM95245_REG_R_STATUS2: + case LM95245_REG_R_LOCAL_TEMPH_S: + case LM95245_REG_R_LOCAL_TEMPL_S: + case LM95245_REG_R_REMOTE_TEMPH_S: + case LM95245_REG_R_REMOTE_TEMPL_S: + case LM95245_REG_R_REMOTE_TEMPH_U: + case LM95245_REG_R_REMOTE_TEMPL_U: + return true; + default: + return false; + } +} + +static const struct regmap_config lm95245_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = lm95245_is_writeable_reg, + .volatile_reg = lm95245_is_volatile_reg, + .cache_type = REGCACHE_RBTREE, + .use_single_rw = true, +}; + +static const u32 lm95245_chip_config[] = { + HWMON_C_UPDATE_INTERVAL, + 0 +}; + +static const struct hwmon_channel_info lm95245_chip = { + .type = hwmon_chip, + .config = lm95245_chip_config, +}; + +static const u32 lm95245_temp_config[] = { + HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_CRIT_ALARM, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | HWMON_T_CRIT | + HWMON_T_CRIT_HYST | HWMON_T_FAULT | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_TYPE | HWMON_T_OFFSET, + 0 +}; + +static const struct hwmon_channel_info lm95245_temp = { + .type = hwmon_temp, + .config = lm95245_temp_config, +}; + +static const struct hwmon_channel_info *lm95245_info[] = { + &lm95245_chip, + &lm95245_temp, + NULL +}; + +static const struct hwmon_ops lm95245_hwmon_ops = { + .is_visible = lm95245_is_visible, + .read = lm95245_read, + .write = lm95245_write, +}; + +static const struct hwmon_chip_info lm95245_chip_info = { + .ops = &lm95245_hwmon_ops, + .info = lm95245_info, +}; + +static int lm95245_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct lm95245_data *data; + struct device *hwmon_dev; + int ret; + + data = devm_kzalloc(dev, sizeof(struct lm95245_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->regmap = devm_regmap_init_i2c(client, &lm95245_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + mutex_init(&data->update_lock); + + /* Initialize the LM95245 chip */ + ret = lm95245_init_client(data); + if (ret < 0) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, + &lm95245_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +/* Driver data (common to all clients) */ +static const struct i2c_device_id lm95245_id[] = { + { "lm95235", 0 }, + { "lm95245", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm95245_id); + +static const struct of_device_id lm95245_of_match[] = { + { .compatible = "national,lm95235" }, + { .compatible = "national,lm95245" }, + { }, +}; +MODULE_DEVICE_TABLE(of, lm95245_of_match); + +static struct i2c_driver lm95245_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm95245", + .of_match_table = of_match_ptr(lm95245_of_match), + }, + .probe = lm95245_probe, + .id_table = lm95245_id, + .detect = lm95245_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(lm95245_driver); + +MODULE_AUTHOR("Alexander Stein "); +MODULE_DESCRIPTION("LM95235/LM95245 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ltc2945.c b/drivers/hwmon/ltc2945.c new file mode 100644 index 000000000..1b92e4f6e --- /dev/null +++ b/drivers/hwmon/ltc2945.c @@ -0,0 +1,519 @@ +/* + * Driver for Linear Technology LTC2945 I2C Power Monitor + * + * Copyright (c) 2014 Guenter Roeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* chip registers */ +#define LTC2945_CONTROL 0x00 +#define LTC2945_ALERT 0x01 +#define LTC2945_STATUS 0x02 +#define LTC2945_FAULT 0x03 +#define LTC2945_POWER_H 0x05 +#define LTC2945_MAX_POWER_H 0x08 +#define LTC2945_MIN_POWER_H 0x0b +#define LTC2945_MAX_POWER_THRES_H 0x0e +#define LTC2945_MIN_POWER_THRES_H 0x11 +#define LTC2945_SENSE_H 0x14 +#define LTC2945_MAX_SENSE_H 0x16 +#define LTC2945_MIN_SENSE_H 0x18 +#define LTC2945_MAX_SENSE_THRES_H 0x1a +#define LTC2945_MIN_SENSE_THRES_H 0x1c +#define LTC2945_VIN_H 0x1e +#define LTC2945_MAX_VIN_H 0x20 +#define LTC2945_MIN_VIN_H 0x22 +#define LTC2945_MAX_VIN_THRES_H 0x24 +#define LTC2945_MIN_VIN_THRES_H 0x26 +#define LTC2945_ADIN_H 0x28 +#define LTC2945_MAX_ADIN_H 0x2a +#define LTC2945_MIN_ADIN_H 0x2c +#define LTC2945_MAX_ADIN_THRES_H 0x2e +#define LTC2945_MIN_ADIN_THRES_H 0x30 +#define LTC2945_MIN_ADIN_THRES_L 0x31 + +/* Fault register bits */ + +#define FAULT_ADIN_UV (1 << 0) +#define FAULT_ADIN_OV (1 << 1) +#define FAULT_VIN_UV (1 << 2) +#define FAULT_VIN_OV (1 << 3) +#define FAULT_SENSE_UV (1 << 4) +#define FAULT_SENSE_OV (1 << 5) +#define FAULT_POWER_UV (1 << 6) +#define FAULT_POWER_OV (1 << 7) + +/* Control register bits */ + +#define CONTROL_MULT_SELECT (1 << 0) +#define CONTROL_TEST_MODE (1 << 4) + +static inline bool is_power_reg(u8 reg) +{ + return reg < LTC2945_SENSE_H; +} + +/* Return the value from the given register in uW, mV, or mA */ +static long long ltc2945_reg_to_val(struct device *dev, u8 reg) +{ + struct regmap *regmap = dev_get_drvdata(dev); + unsigned int control; + u8 buf[3]; + long long val; + int ret; + + ret = regmap_bulk_read(regmap, reg, buf, + is_power_reg(reg) ? 3 : 2); + if (ret < 0) + return ret; + + if (is_power_reg(reg)) { + /* power */ + val = (buf[0] << 16) + (buf[1] << 8) + buf[2]; + } else { + /* current, voltage */ + val = (buf[0] << 4) + (buf[1] >> 4); + } + + switch (reg) { + case LTC2945_POWER_H: + case LTC2945_MAX_POWER_H: + case LTC2945_MIN_POWER_H: + case LTC2945_MAX_POWER_THRES_H: + case LTC2945_MIN_POWER_THRES_H: + /* + * Convert to uW by assuming current is measured with + * an 1mOhm sense resistor, similar to current + * measurements. + * Control register bit 0 selects if voltage at SENSE+/VDD + * or voltage at ADIN is used to measure power. + */ + ret = regmap_read(regmap, LTC2945_CONTROL, &control); + if (ret < 0) + return ret; + if (control & CONTROL_MULT_SELECT) { + /* 25 mV * 25 uV = 0.625 uV resolution. */ + val *= 625LL; + } else { + /* 0.5 mV * 25 uV = 0.0125 uV resolution. */ + val = (val * 25LL) >> 1; + } + break; + case LTC2945_VIN_H: + case LTC2945_MAX_VIN_H: + case LTC2945_MIN_VIN_H: + case LTC2945_MAX_VIN_THRES_H: + case LTC2945_MIN_VIN_THRES_H: + /* 25 mV resolution. Convert to mV. */ + val *= 25; + break; + case LTC2945_ADIN_H: + case LTC2945_MAX_ADIN_H: + case LTC2945_MIN_ADIN_THRES_H: + case LTC2945_MAX_ADIN_THRES_H: + case LTC2945_MIN_ADIN_H: + /* 0.5mV resolution. Convert to mV. */ + val = val >> 1; + break; + case LTC2945_SENSE_H: + case LTC2945_MAX_SENSE_H: + case LTC2945_MIN_SENSE_H: + case LTC2945_MAX_SENSE_THRES_H: + case LTC2945_MIN_SENSE_THRES_H: + /* + * 25 uV resolution. Convert to current as measured with + * an 1 mOhm sense resistor, in mA. If a different sense + * resistor is installed, calculate the actual current by + * dividing the reported current by the sense resistor value + * in mOhm. + */ + val *= 25; + break; + default: + return -EINVAL; + } + return val; +} + +static int ltc2945_val_to_reg(struct device *dev, u8 reg, + unsigned long val) +{ + struct regmap *regmap = dev_get_drvdata(dev); + unsigned int control; + int ret; + + switch (reg) { + case LTC2945_POWER_H: + case LTC2945_MAX_POWER_H: + case LTC2945_MIN_POWER_H: + case LTC2945_MAX_POWER_THRES_H: + case LTC2945_MIN_POWER_THRES_H: + /* + * Convert to register value by assuming current is measured + * with an 1mOhm sense resistor, similar to current + * measurements. + * Control register bit 0 selects if voltage at SENSE+/VDD + * or voltage at ADIN is used to measure power, which in turn + * determines register calculations. + */ + ret = regmap_read(regmap, LTC2945_CONTROL, &control); + if (ret < 0) + return ret; + if (control & CONTROL_MULT_SELECT) { + /* 25 mV * 25 uV = 0.625 uV resolution. */ + val = DIV_ROUND_CLOSEST(val, 625); + } else { + /* + * 0.5 mV * 25 uV = 0.0125 uV resolution. + * Divide first to avoid overflow; + * accept loss of accuracy. + */ + val = DIV_ROUND_CLOSEST(val, 25) * 2; + } + break; + case LTC2945_VIN_H: + case LTC2945_MAX_VIN_H: + case LTC2945_MIN_VIN_H: + case LTC2945_MAX_VIN_THRES_H: + case LTC2945_MIN_VIN_THRES_H: + /* 25 mV resolution. */ + val /= 25; + break; + case LTC2945_ADIN_H: + case LTC2945_MAX_ADIN_H: + case LTC2945_MIN_ADIN_THRES_H: + case LTC2945_MAX_ADIN_THRES_H: + case LTC2945_MIN_ADIN_H: + /* 0.5mV resolution. */ + val *= 2; + break; + case LTC2945_SENSE_H: + case LTC2945_MAX_SENSE_H: + case LTC2945_MIN_SENSE_H: + case LTC2945_MAX_SENSE_THRES_H: + case LTC2945_MIN_SENSE_THRES_H: + /* + * 25 uV resolution. Convert to current as measured with + * an 1 mOhm sense resistor, in mA. If a different sense + * resistor is installed, calculate the actual current by + * dividing the reported current by the sense resistor value + * in mOhm. + */ + val = DIV_ROUND_CLOSEST(val, 25); + break; + default: + return -EINVAL; + } + return val; +} + +static ssize_t ltc2945_show_value(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + long long value; + + value = ltc2945_reg_to_val(dev, attr->index); + if (value < 0) + return value; + return snprintf(buf, PAGE_SIZE, "%lld\n", value); +} + +static ssize_t ltc2945_set_value(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct regmap *regmap = dev_get_drvdata(dev); + u8 reg = attr->index; + unsigned long val; + u8 regbuf[3]; + int num_regs; + int regval; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + /* convert to register value, then clamp and write result */ + regval = ltc2945_val_to_reg(dev, reg, val); + if (is_power_reg(reg)) { + regval = clamp_val(regval, 0, 0xffffff); + regbuf[0] = regval >> 16; + regbuf[1] = (regval >> 8) & 0xff; + regbuf[2] = regval; + num_regs = 3; + } else { + regval = clamp_val(regval, 0, 0xfff) << 4; + regbuf[0] = regval >> 8; + regbuf[1] = regval & 0xff; + num_regs = 2; + } + ret = regmap_bulk_write(regmap, reg, regbuf, num_regs); + return ret < 0 ? ret : count; +} + +static ssize_t ltc2945_reset_history(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct regmap *regmap = dev_get_drvdata(dev); + u8 reg = attr->index; + int num_regs = is_power_reg(reg) ? 3 : 2; + u8 buf_min[3] = { 0xff, 0xff, 0xff }; + u8 buf_max[3] = { 0, 0, 0 }; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + if (val != 1) + return -EINVAL; + + ret = regmap_update_bits(regmap, LTC2945_CONTROL, CONTROL_TEST_MODE, + CONTROL_TEST_MODE); + + /* Reset minimum */ + ret = regmap_bulk_write(regmap, reg, buf_min, num_regs); + if (ret) + return ret; + + switch (reg) { + case LTC2945_MIN_POWER_H: + reg = LTC2945_MAX_POWER_H; + break; + case LTC2945_MIN_SENSE_H: + reg = LTC2945_MAX_SENSE_H; + break; + case LTC2945_MIN_VIN_H: + reg = LTC2945_MAX_VIN_H; + break; + case LTC2945_MIN_ADIN_H: + reg = LTC2945_MAX_ADIN_H; + break; + default: + WARN_ONCE(1, "Bad register: 0x%x\n", reg); + return -EINVAL; + } + /* Reset maximum */ + ret = regmap_bulk_write(regmap, reg, buf_max, num_regs); + + /* Try resetting test mode even if there was an error */ + regmap_update_bits(regmap, LTC2945_CONTROL, CONTROL_TEST_MODE, 0); + + return ret ? : count; +} + +static ssize_t ltc2945_show_bool(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct regmap *regmap = dev_get_drvdata(dev); + unsigned int fault; + int ret; + + ret = regmap_read(regmap, LTC2945_FAULT, &fault); + if (ret < 0) + return ret; + + fault &= attr->index; + if (fault) /* Clear reported faults in chip register */ + regmap_update_bits(regmap, LTC2945_FAULT, attr->index, 0); + + return snprintf(buf, PAGE_SIZE, "%d\n", !!fault); +} + +/* Input voltages */ + +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ltc2945_show_value, NULL, + LTC2945_VIN_H); +static SENSOR_DEVICE_ATTR(in1_min, S_IRUGO | S_IWUSR, ltc2945_show_value, + ltc2945_set_value, LTC2945_MIN_VIN_THRES_H); +static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, ltc2945_show_value, + ltc2945_set_value, LTC2945_MAX_VIN_THRES_H); +static SENSOR_DEVICE_ATTR(in1_lowest, S_IRUGO, ltc2945_show_value, NULL, + LTC2945_MIN_VIN_H); +static SENSOR_DEVICE_ATTR(in1_highest, S_IRUGO, ltc2945_show_value, NULL, + LTC2945_MAX_VIN_H); +static SENSOR_DEVICE_ATTR(in1_reset_history, S_IWUSR, NULL, + ltc2945_reset_history, LTC2945_MIN_VIN_H); + +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, ltc2945_show_value, NULL, + LTC2945_ADIN_H); +static SENSOR_DEVICE_ATTR(in2_min, S_IRUGO | S_IWUSR, ltc2945_show_value, + ltc2945_set_value, LTC2945_MIN_ADIN_THRES_H); +static SENSOR_DEVICE_ATTR(in2_max, S_IRUGO | S_IWUSR, ltc2945_show_value, + ltc2945_set_value, LTC2945_MAX_ADIN_THRES_H); +static SENSOR_DEVICE_ATTR(in2_lowest, S_IRUGO, ltc2945_show_value, NULL, + LTC2945_MIN_ADIN_H); +static SENSOR_DEVICE_ATTR(in2_highest, S_IRUGO, ltc2945_show_value, NULL, + LTC2945_MAX_ADIN_H); +static SENSOR_DEVICE_ATTR(in2_reset_history, S_IWUSR, NULL, + ltc2945_reset_history, LTC2945_MIN_ADIN_H); + +/* Voltage alarms */ + +static SENSOR_DEVICE_ATTR(in1_min_alarm, S_IRUGO, ltc2945_show_bool, NULL, + FAULT_VIN_UV); +static SENSOR_DEVICE_ATTR(in1_max_alarm, S_IRUGO, ltc2945_show_bool, NULL, + FAULT_VIN_OV); +static SENSOR_DEVICE_ATTR(in2_min_alarm, S_IRUGO, ltc2945_show_bool, NULL, + FAULT_ADIN_UV); +static SENSOR_DEVICE_ATTR(in2_max_alarm, S_IRUGO, ltc2945_show_bool, NULL, + FAULT_ADIN_OV); + +/* Currents (via sense resistor) */ + +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ltc2945_show_value, NULL, + LTC2945_SENSE_H); +static SENSOR_DEVICE_ATTR(curr1_min, S_IRUGO | S_IWUSR, ltc2945_show_value, + ltc2945_set_value, LTC2945_MIN_SENSE_THRES_H); +static SENSOR_DEVICE_ATTR(curr1_max, S_IRUGO | S_IWUSR, ltc2945_show_value, + ltc2945_set_value, LTC2945_MAX_SENSE_THRES_H); +static SENSOR_DEVICE_ATTR(curr1_lowest, S_IRUGO, ltc2945_show_value, NULL, + LTC2945_MIN_SENSE_H); +static SENSOR_DEVICE_ATTR(curr1_highest, S_IRUGO, ltc2945_show_value, NULL, + LTC2945_MAX_SENSE_H); +static SENSOR_DEVICE_ATTR(curr1_reset_history, S_IWUSR, NULL, + ltc2945_reset_history, LTC2945_MIN_SENSE_H); + +/* Current alarms */ + +static SENSOR_DEVICE_ATTR(curr1_min_alarm, S_IRUGO, ltc2945_show_bool, NULL, + FAULT_SENSE_UV); +static SENSOR_DEVICE_ATTR(curr1_max_alarm, S_IRUGO, ltc2945_show_bool, NULL, + FAULT_SENSE_OV); + +/* Power */ + +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ltc2945_show_value, NULL, + LTC2945_POWER_H); +static SENSOR_DEVICE_ATTR(power1_min, S_IRUGO | S_IWUSR, ltc2945_show_value, + ltc2945_set_value, LTC2945_MIN_POWER_THRES_H); +static SENSOR_DEVICE_ATTR(power1_max, S_IRUGO | S_IWUSR, ltc2945_show_value, + ltc2945_set_value, LTC2945_MAX_POWER_THRES_H); +static SENSOR_DEVICE_ATTR(power1_input_lowest, S_IRUGO, ltc2945_show_value, + NULL, LTC2945_MIN_POWER_H); +static SENSOR_DEVICE_ATTR(power1_input_highest, S_IRUGO, ltc2945_show_value, + NULL, LTC2945_MAX_POWER_H); +static SENSOR_DEVICE_ATTR(power1_reset_history, S_IWUSR, NULL, + ltc2945_reset_history, LTC2945_MIN_POWER_H); + +/* Power alarms */ + +static SENSOR_DEVICE_ATTR(power1_min_alarm, S_IRUGO, ltc2945_show_bool, NULL, + FAULT_POWER_UV); +static SENSOR_DEVICE_ATTR(power1_max_alarm, S_IRUGO, ltc2945_show_bool, NULL, + FAULT_POWER_OV); + +static struct attribute *ltc2945_attrs[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_lowest.dev_attr.attr, + &sensor_dev_attr_in1_highest.dev_attr.attr, + &sensor_dev_attr_in1_reset_history.dev_attr.attr, + &sensor_dev_attr_in1_min_alarm.dev_attr.attr, + &sensor_dev_attr_in1_max_alarm.dev_attr.attr, + + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_lowest.dev_attr.attr, + &sensor_dev_attr_in2_highest.dev_attr.attr, + &sensor_dev_attr_in2_reset_history.dev_attr.attr, + &sensor_dev_attr_in2_min_alarm.dev_attr.attr, + &sensor_dev_attr_in2_max_alarm.dev_attr.attr, + + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_curr1_min.dev_attr.attr, + &sensor_dev_attr_curr1_max.dev_attr.attr, + &sensor_dev_attr_curr1_lowest.dev_attr.attr, + &sensor_dev_attr_curr1_highest.dev_attr.attr, + &sensor_dev_attr_curr1_reset_history.dev_attr.attr, + &sensor_dev_attr_curr1_min_alarm.dev_attr.attr, + &sensor_dev_attr_curr1_max_alarm.dev_attr.attr, + + &sensor_dev_attr_power1_input.dev_attr.attr, + &sensor_dev_attr_power1_min.dev_attr.attr, + &sensor_dev_attr_power1_max.dev_attr.attr, + &sensor_dev_attr_power1_input_lowest.dev_attr.attr, + &sensor_dev_attr_power1_input_highest.dev_attr.attr, + &sensor_dev_attr_power1_reset_history.dev_attr.attr, + &sensor_dev_attr_power1_min_alarm.dev_attr.attr, + &sensor_dev_attr_power1_max_alarm.dev_attr.attr, + + NULL, +}; +ATTRIBUTE_GROUPS(ltc2945); + +static const struct regmap_config ltc2945_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = LTC2945_MIN_ADIN_THRES_L, +}; + +static int ltc2945_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, <c2945_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(regmap); + } + + /* Clear faults */ + regmap_write(regmap, LTC2945_FAULT, 0x00); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + regmap, + ltc2945_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id ltc2945_id[] = { + {"ltc2945", 0}, + { } +}; + +MODULE_DEVICE_TABLE(i2c, ltc2945_id); + +static struct i2c_driver ltc2945_driver = { + .driver = { + .name = "ltc2945", + }, + .probe = ltc2945_probe, + .id_table = ltc2945_id, +}; + +module_i2c_driver(ltc2945_driver); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("LTC2945 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ltc2990.c b/drivers/hwmon/ltc2990.c new file mode 100644 index 000000000..2aefdc58b --- /dev/null +++ b/drivers/hwmon/ltc2990.c @@ -0,0 +1,291 @@ +/* + * Driver for Linear Technology LTC2990 power monitor + * + * Copyright (C) 2014 Topic Embedded Products + * Author: Mike Looijmans + * + * License: GPLv2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define LTC2990_STATUS 0x00 +#define LTC2990_CONTROL 0x01 +#define LTC2990_TRIGGER 0x02 +#define LTC2990_TINT_MSB 0x04 +#define LTC2990_V1_MSB 0x06 +#define LTC2990_V2_MSB 0x08 +#define LTC2990_V3_MSB 0x0A +#define LTC2990_V4_MSB 0x0C +#define LTC2990_VCC_MSB 0x0E + +#define LTC2990_IN0 BIT(0) +#define LTC2990_IN1 BIT(1) +#define LTC2990_IN2 BIT(2) +#define LTC2990_IN3 BIT(3) +#define LTC2990_IN4 BIT(4) +#define LTC2990_CURR1 BIT(5) +#define LTC2990_CURR2 BIT(6) +#define LTC2990_TEMP1 BIT(7) +#define LTC2990_TEMP2 BIT(8) +#define LTC2990_TEMP3 BIT(9) +#define LTC2990_NONE 0 +#define LTC2990_ALL GENMASK(9, 0) + +#define LTC2990_MODE0_SHIFT 0 +#define LTC2990_MODE0_MASK GENMASK(2, 0) +#define LTC2990_MODE1_SHIFT 3 +#define LTC2990_MODE1_MASK GENMASK(1, 0) + +/* Enabled measurements for mode bits 2..0 */ +static const int ltc2990_attrs_ena_0[] = { + LTC2990_IN1 | LTC2990_IN2 | LTC2990_TEMP3, + LTC2990_CURR1 | LTC2990_TEMP3, + LTC2990_CURR1 | LTC2990_IN3 | LTC2990_IN4, + LTC2990_TEMP2 | LTC2990_IN3 | LTC2990_IN4, + LTC2990_TEMP2 | LTC2990_CURR2, + LTC2990_TEMP2 | LTC2990_TEMP3, + LTC2990_CURR1 | LTC2990_CURR2, + LTC2990_IN1 | LTC2990_IN2 | LTC2990_IN3 | LTC2990_IN4 +}; + +/* Enabled measurements for mode bits 4..3 */ +static const int ltc2990_attrs_ena_1[] = { + LTC2990_NONE, + LTC2990_TEMP2 | LTC2990_IN1 | LTC2990_CURR1, + LTC2990_TEMP3 | LTC2990_IN3 | LTC2990_CURR2, + LTC2990_ALL +}; + +struct ltc2990_data { + struct i2c_client *i2c; + u32 mode[2]; +}; + +/* Return the converted value from the given register in uV or mC */ +static int ltc2990_get_value(struct i2c_client *i2c, int index, int *result) +{ + int val; + u8 reg; + + switch (index) { + case LTC2990_IN0: + reg = LTC2990_VCC_MSB; + break; + case LTC2990_IN1: + case LTC2990_CURR1: + case LTC2990_TEMP2: + reg = LTC2990_V1_MSB; + break; + case LTC2990_IN2: + reg = LTC2990_V2_MSB; + break; + case LTC2990_IN3: + case LTC2990_CURR2: + case LTC2990_TEMP3: + reg = LTC2990_V3_MSB; + break; + case LTC2990_IN4: + reg = LTC2990_V4_MSB; + break; + case LTC2990_TEMP1: + reg = LTC2990_TINT_MSB; + break; + default: + return -EINVAL; + } + + val = i2c_smbus_read_word_swapped(i2c, reg); + if (unlikely(val < 0)) + return val; + + switch (index) { + case LTC2990_TEMP1: + case LTC2990_TEMP2: + case LTC2990_TEMP3: + /* temp, 0.0625 degrees/LSB */ + *result = sign_extend32(val, 12) * 1000 / 16; + break; + case LTC2990_CURR1: + case LTC2990_CURR2: + /* Vx-Vy, 19.42uV/LSB */ + *result = sign_extend32(val, 14) * 1942 / 100; + break; + case LTC2990_IN0: + /* Vcc, 305.18uV/LSB, 2.5V offset */ + *result = sign_extend32(val, 14) * 30518 / (100 * 1000) + 2500; + break; + case LTC2990_IN1: + case LTC2990_IN2: + case LTC2990_IN3: + case LTC2990_IN4: + /* Vx, 305.18uV/LSB */ + *result = sign_extend32(val, 14) * 30518 / (100 * 1000); + break; + default: + return -EINVAL; /* won't happen, keep compiler happy */ + } + + return 0; +} + +static ssize_t ltc2990_show_value(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ltc2990_data *data = dev_get_drvdata(dev); + int value; + int ret; + + ret = ltc2990_get_value(data->i2c, attr->index, &value); + if (unlikely(ret < 0)) + return ret; + + return snprintf(buf, PAGE_SIZE, "%d\n", value); +} + +static umode_t ltc2990_attrs_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct ltc2990_data *data = dev_get_drvdata(dev); + struct device_attribute *da = + container_of(a, struct device_attribute, attr); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + + int attrs_mask = LTC2990_IN0 | LTC2990_TEMP1 | + (ltc2990_attrs_ena_0[data->mode[0]] & + ltc2990_attrs_ena_1[data->mode[1]]); + + if (attr->index & attrs_mask) + return a->mode; + + return 0; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, ltc2990_show_value, NULL, + LTC2990_TEMP1); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, ltc2990_show_value, NULL, + LTC2990_TEMP2); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, ltc2990_show_value, NULL, + LTC2990_TEMP3); +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ltc2990_show_value, NULL, + LTC2990_CURR1); +static SENSOR_DEVICE_ATTR(curr2_input, S_IRUGO, ltc2990_show_value, NULL, + LTC2990_CURR2); +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ltc2990_show_value, NULL, + LTC2990_IN0); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ltc2990_show_value, NULL, + LTC2990_IN1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, ltc2990_show_value, NULL, + LTC2990_IN2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, ltc2990_show_value, NULL, + LTC2990_IN3); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, ltc2990_show_value, NULL, + LTC2990_IN4); + +static struct attribute *ltc2990_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_curr2_input.dev_attr.attr, + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ltc2990_group = { + .attrs = ltc2990_attrs, + .is_visible = ltc2990_attrs_visible, +}; +__ATTRIBUTE_GROUPS(ltc2990); + +static int ltc2990_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + int ret; + struct device *hwmon_dev; + struct ltc2990_data *data; + struct device_node *of_node = i2c->dev.of_node; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + data = devm_kzalloc(&i2c->dev, sizeof(struct ltc2990_data), GFP_KERNEL); + if (unlikely(!data)) + return -ENOMEM; + + data->i2c = i2c; + + if (of_node) { + ret = of_property_read_u32_array(of_node, "lltc,meas-mode", + data->mode, 2); + if (ret < 0) + return ret; + + if (data->mode[0] & ~LTC2990_MODE0_MASK || + data->mode[1] & ~LTC2990_MODE1_MASK) + return -EINVAL; + } else { + ret = i2c_smbus_read_byte_data(i2c, LTC2990_CONTROL); + if (ret < 0) + return ret; + + data->mode[0] = ret >> LTC2990_MODE0_SHIFT & LTC2990_MODE0_MASK; + data->mode[1] = ret >> LTC2990_MODE1_SHIFT & LTC2990_MODE1_MASK; + } + + /* Setup continuous mode */ + ret = i2c_smbus_write_byte_data(i2c, LTC2990_CONTROL, + data->mode[0] << LTC2990_MODE0_SHIFT | + data->mode[1] << LTC2990_MODE1_SHIFT); + if (ret < 0) { + dev_err(&i2c->dev, "Error: Failed to set control mode.\n"); + return ret; + } + /* Trigger once to start continuous conversion */ + ret = i2c_smbus_write_byte_data(i2c, LTC2990_TRIGGER, 1); + if (ret < 0) { + dev_err(&i2c->dev, "Error: Failed to start acquisition.\n"); + return ret; + } + + hwmon_dev = devm_hwmon_device_register_with_groups(&i2c->dev, + i2c->name, + data, + ltc2990_groups); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id ltc2990_i2c_id[] = { + { "ltc2990", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, ltc2990_i2c_id); + +static struct i2c_driver ltc2990_i2c_driver = { + .driver = { + .name = "ltc2990", + }, + .probe = ltc2990_i2c_probe, + .id_table = ltc2990_i2c_id, +}; + +module_i2c_driver(ltc2990_i2c_driver); + +MODULE_DESCRIPTION("LTC2990 Sensor Driver"); +MODULE_AUTHOR("Topic Embedded Products"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/ltc4151.c b/drivers/hwmon/ltc4151.c new file mode 100644 index 000000000..b904cb547 --- /dev/null +++ b/drivers/hwmon/ltc4151.c @@ -0,0 +1,234 @@ +/* + * Driver for Linear Technology LTC4151 High Voltage I2C Current + * and Voltage Monitor + * + * Copyright (C) 2011 AppearTV AS + * + * Derived from: + * + * Driver for Linear Technology LTC4261 I2C Negative Voltage Hot + * Swap Controller + * Copyright (C) 2010 Ericsson AB. + * + * Datasheet: http://www.linear.com/docs/Datasheet/4151fc.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* chip registers */ +#define LTC4151_SENSE_H 0x00 +#define LTC4151_SENSE_L 0x01 +#define LTC4151_VIN_H 0x02 +#define LTC4151_VIN_L 0x03 +#define LTC4151_ADIN_H 0x04 +#define LTC4151_ADIN_L 0x05 + +struct ltc4151_data { + struct i2c_client *client; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + unsigned int shunt; /* in micro ohms */ + + /* Registers */ + u8 regs[6]; +}; + +static struct ltc4151_data *ltc4151_update_device(struct device *dev) +{ + struct ltc4151_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + struct ltc4151_data *ret = data; + + mutex_lock(&data->update_lock); + + /* + * The chip's A/D updates 6 times per second + * (Conversion Rate 6 - 9 Hz) + */ + if (time_after(jiffies, data->last_updated + HZ / 6) || !data->valid) { + int i; + + dev_dbg(&client->dev, "Starting ltc4151 update\n"); + + /* Read all registers */ + for (i = 0; i < ARRAY_SIZE(data->regs); i++) { + int val; + + val = i2c_smbus_read_byte_data(client, i); + if (unlikely(val < 0)) { + dev_dbg(dev, + "Failed to read ADC value: error %d\n", + val); + ret = ERR_PTR(val); + goto abort; + } + data->regs[i] = val; + } + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +/* Return the voltage from the given register in mV */ +static int ltc4151_get_value(struct ltc4151_data *data, u8 reg) +{ + u32 val; + + val = (data->regs[reg] << 4) + (data->regs[reg + 1] >> 4); + + switch (reg) { + case LTC4151_ADIN_H: + /* 500uV resolution. Convert to mV. */ + val = val * 500 / 1000; + break; + case LTC4151_SENSE_H: + /* + * 20uV resolution. Convert to current as measured with + * a given sense resistor, in mA. + */ + val = val * 20 * 1000 / data->shunt; + break; + case LTC4151_VIN_H: + /* 25 mV per increment */ + val = val * 25; + break; + default: + /* If we get here, the developer messed up */ + WARN_ON_ONCE(1); + val = 0; + break; + } + + return val; +} + +static ssize_t ltc4151_show_value(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ltc4151_data *data = ltc4151_update_device(dev); + int value; + + if (IS_ERR(data)) + return PTR_ERR(data); + + value = ltc4151_get_value(data, attr->index); + return snprintf(buf, PAGE_SIZE, "%d\n", value); +} + +/* + * Input voltages. + */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ltc4151_show_value, NULL, + LTC4151_VIN_H); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, ltc4151_show_value, NULL, + LTC4151_ADIN_H); + +/* Currents (via sense resistor) */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ltc4151_show_value, NULL, + LTC4151_SENSE_H); + +/* + * Finally, construct an array of pointers to members of the above objects, + * as required for sysfs_create_group() + */ +static struct attribute *ltc4151_attrs[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + + &sensor_dev_attr_curr1_input.dev_attr.attr, + + NULL, +}; +ATTRIBUTE_GROUPS(ltc4151); + +static int ltc4151_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct device *dev = &client->dev; + struct ltc4151_data *data; + struct device *hwmon_dev; + u32 shunt; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (of_property_read_u32(client->dev.of_node, + "shunt-resistor-micro-ohms", &shunt)) + shunt = 1000; /* 1 mOhm if not set via DT */ + + if (shunt == 0) + return -EINVAL; + + data->shunt = shunt; + + data->client = client; + mutex_init(&data->update_lock); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + ltc4151_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id ltc4151_id[] = { + { "ltc4151", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc4151_id); + +static const struct of_device_id ltc4151_match[] = { + { .compatible = "lltc,ltc4151" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ltc4151_match); + +/* This is the driver that will be inserted */ +static struct i2c_driver ltc4151_driver = { + .driver = { + .name = "ltc4151", + .of_match_table = of_match_ptr(ltc4151_match), + }, + .probe = ltc4151_probe, + .id_table = ltc4151_id, +}; + +module_i2c_driver(ltc4151_driver); + +MODULE_AUTHOR("Per Dalen "); +MODULE_DESCRIPTION("LTC4151 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ltc4215.c b/drivers/hwmon/ltc4215.c new file mode 100644 index 000000000..c8a9bd9b0 --- /dev/null +++ b/drivers/hwmon/ltc4215.c @@ -0,0 +1,280 @@ +/* + * Driver for Linear Technology LTC4215 I2C Hot Swap Controller + * + * Copyright (C) 2009 Ira W. Snyder + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * Datasheet: + * http://www.linear.com/pc/downloadDocument.do?navId=H0,C1,C1003,C1006,C1163,P17572,D12697 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Here are names of the chip's registers (a.k.a. commands) */ +enum ltc4215_cmd { + LTC4215_CONTROL = 0x00, /* rw */ + LTC4215_ALERT = 0x01, /* rw */ + LTC4215_STATUS = 0x02, /* ro */ + LTC4215_FAULT = 0x03, /* rw */ + LTC4215_SENSE = 0x04, /* rw */ + LTC4215_SOURCE = 0x05, /* rw */ + LTC4215_ADIN = 0x06, /* rw */ +}; + +struct ltc4215_data { + struct i2c_client *client; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + + /* Registers */ + u8 regs[7]; +}; + +static struct ltc4215_data *ltc4215_update_device(struct device *dev) +{ + struct ltc4215_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + s32 val; + int i; + + mutex_lock(&data->update_lock); + + /* The chip's A/D updates 10 times per second */ + if (time_after(jiffies, data->last_updated + HZ / 10) || !data->valid) { + + dev_dbg(&client->dev, "Starting ltc4215 update\n"); + + /* Read all registers */ + for (i = 0; i < ARRAY_SIZE(data->regs); i++) { + val = i2c_smbus_read_byte_data(client, i); + if (unlikely(val < 0)) + data->regs[i] = 0; + else + data->regs[i] = val; + } + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* Return the voltage from the given register in millivolts */ +static int ltc4215_get_voltage(struct device *dev, u8 reg) +{ + struct ltc4215_data *data = ltc4215_update_device(dev); + const u8 regval = data->regs[reg]; + u32 voltage = 0; + + switch (reg) { + case LTC4215_SENSE: + /* 151 uV per increment */ + voltage = regval * 151 / 1000; + break; + case LTC4215_SOURCE: + /* 60.5 mV per increment */ + voltage = regval * 605 / 10; + break; + case LTC4215_ADIN: + /* + * The ADIN input is divided by 12.5, and has 4.82 mV + * per increment, so we have the additional multiply + */ + voltage = regval * 482 * 125 / 1000; + break; + default: + /* If we get here, the developer messed up */ + WARN_ON_ONCE(1); + break; + } + + return voltage; +} + +/* Return the current from the sense resistor in mA */ +static unsigned int ltc4215_get_current(struct device *dev) +{ + struct ltc4215_data *data = ltc4215_update_device(dev); + + /* + * The strange looking conversions that follow are fixed-point + * math, since we cannot do floating point in the kernel. + * + * Step 1: convert sense register to microVolts + * Step 2: convert voltage to milliAmperes + * + * If you play around with the V=IR equation, you come up with + * the following: X uV / Y mOhm == Z mA + * + * With the resistors that are fractions of a milliOhm, we multiply + * the voltage and resistance by 10, to shift the decimal point. + * Now we can use the normal division operator again. + */ + + /* Calculate voltage in microVolts (151 uV per increment) */ + const unsigned int voltage = data->regs[LTC4215_SENSE] * 151; + + /* Calculate current in milliAmperes (4 milliOhm sense resistor) */ + const unsigned int curr = voltage / 4; + + return curr; +} + +static ssize_t ltc4215_show_voltage(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + const int voltage = ltc4215_get_voltage(dev, attr->index); + + return snprintf(buf, PAGE_SIZE, "%d\n", voltage); +} + +static ssize_t ltc4215_show_current(struct device *dev, + struct device_attribute *da, + char *buf) +{ + const unsigned int curr = ltc4215_get_current(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", curr); +} + +static ssize_t ltc4215_show_power(struct device *dev, + struct device_attribute *da, + char *buf) +{ + const unsigned int curr = ltc4215_get_current(dev); + const int output_voltage = ltc4215_get_voltage(dev, LTC4215_ADIN); + + /* current in mA * voltage in mV == power in uW */ + const unsigned int power = abs(output_voltage * curr); + + return snprintf(buf, PAGE_SIZE, "%u\n", power); +} + +static ssize_t ltc4215_show_alarm(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ltc4215_data *data = ltc4215_update_device(dev); + const u8 reg = data->regs[LTC4215_STATUS]; + const u32 mask = attr->index; + + return snprintf(buf, PAGE_SIZE, "%u\n", !!(reg & mask)); +} + +/* + * These macros are used below in constructing device attribute objects + * for use with sysfs_create_group() to make a sysfs device file + * for each register. + */ + +/* Construct a sensor_device_attribute structure for each register */ + +/* Current */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ltc4215_show_current, NULL, 0); +static SENSOR_DEVICE_ATTR(curr1_max_alarm, S_IRUGO, ltc4215_show_alarm, NULL, + 1 << 2); + +/* Power (virtual) */ +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ltc4215_show_power, NULL, 0); + +/* Input Voltage */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ltc4215_show_voltage, NULL, + LTC4215_ADIN); +static SENSOR_DEVICE_ATTR(in1_max_alarm, S_IRUGO, ltc4215_show_alarm, NULL, + 1 << 0); +static SENSOR_DEVICE_ATTR(in1_min_alarm, S_IRUGO, ltc4215_show_alarm, NULL, + 1 << 1); + +/* Output Voltage */ +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, ltc4215_show_voltage, NULL, + LTC4215_SOURCE); +static SENSOR_DEVICE_ATTR(in2_min_alarm, S_IRUGO, ltc4215_show_alarm, NULL, + 1 << 3); + +/* + * Finally, construct an array of pointers to members of the above objects, + * as required for sysfs_create_group() + */ +static struct attribute *ltc4215_attrs[] = { + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_curr1_max_alarm.dev_attr.attr, + + &sensor_dev_attr_power1_input.dev_attr.attr, + + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_max_alarm.dev_attr.attr, + &sensor_dev_attr_in1_min_alarm.dev_attr.attr, + + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min_alarm.dev_attr.attr, + + NULL, +}; +ATTRIBUTE_GROUPS(ltc4215); + +static int ltc4215_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct device *dev = &client->dev; + struct ltc4215_data *data; + struct device *hwmon_dev; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* Initialize the LTC4215 chip */ + i2c_smbus_write_byte_data(client, LTC4215_FAULT, 0x00); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + ltc4215_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id ltc4215_id[] = { + { "ltc4215", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc4215_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver ltc4215_driver = { + .driver = { + .name = "ltc4215", + }, + .probe = ltc4215_probe, + .id_table = ltc4215_id, +}; + +module_i2c_driver(ltc4215_driver); + +MODULE_AUTHOR("Ira W. Snyder "); +MODULE_DESCRIPTION("LTC4215 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ltc4222.c b/drivers/hwmon/ltc4222.c new file mode 100644 index 000000000..88f747292 --- /dev/null +++ b/drivers/hwmon/ltc4222.c @@ -0,0 +1,237 @@ +/* + * Driver for Linear Technology LTC4222 Dual Hot Swap controller + * + * Copyright (c) 2014 Guenter Roeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* chip registers */ + +#define LTC4222_CONTROL1 0xd0 +#define LTC4222_ALERT1 0xd1 +#define LTC4222_STATUS1 0xd2 +#define LTC4222_FAULT1 0xd3 +#define LTC4222_CONTROL2 0xd4 +#define LTC4222_ALERT2 0xd5 +#define LTC4222_STATUS2 0xd6 +#define LTC4222_FAULT2 0xd7 +#define LTC4222_SOURCE1 0xd8 +#define LTC4222_SOURCE2 0xda +#define LTC4222_ADIN1 0xdc +#define LTC4222_ADIN2 0xde +#define LTC4222_SENSE1 0xe0 +#define LTC4222_SENSE2 0xe2 +#define LTC4222_ADC_CONTROL 0xe4 + +/* + * Fault register bits + */ +#define FAULT_OV BIT(0) +#define FAULT_UV BIT(1) +#define FAULT_OC BIT(2) +#define FAULT_POWER_BAD BIT(3) +#define FAULT_FET_BAD BIT(5) + +/* Return the voltage from the given register in mV or mA */ +static int ltc4222_get_value(struct device *dev, u8 reg) +{ + struct regmap *regmap = dev_get_drvdata(dev); + unsigned int val; + u8 buf[2]; + int ret; + + ret = regmap_bulk_read(regmap, reg, buf, 2); + if (ret < 0) + return ret; + + val = ((buf[0] << 8) + buf[1]) >> 6; + + switch (reg) { + case LTC4222_ADIN1: + case LTC4222_ADIN2: + /* 1.25 mV resolution. Convert to mV. */ + val = DIV_ROUND_CLOSEST(val * 5, 4); + break; + case LTC4222_SOURCE1: + case LTC4222_SOURCE2: + /* 31.25 mV resolution. Convert to mV. */ + val = DIV_ROUND_CLOSEST(val * 125, 4); + break; + case LTC4222_SENSE1: + case LTC4222_SENSE2: + /* + * 62.5 uV resolution. Convert to current as measured with + * an 1 mOhm sense resistor, in mA. If a different sense + * resistor is installed, calculate the actual current by + * dividing the reported current by the sense resistor value + * in mOhm. + */ + val = DIV_ROUND_CLOSEST(val * 125, 2); + break; + default: + return -EINVAL; + } + return val; +} + +static ssize_t ltc4222_show_value(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int value; + + value = ltc4222_get_value(dev, attr->index); + if (value < 0) + return value; + return snprintf(buf, PAGE_SIZE, "%d\n", value); +} + +static ssize_t ltc4222_show_bool(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(da); + struct regmap *regmap = dev_get_drvdata(dev); + unsigned int fault; + int ret; + + ret = regmap_read(regmap, attr->nr, &fault); + if (ret < 0) + return ret; + fault &= attr->index; + if (fault) /* Clear reported faults in chip register */ + regmap_update_bits(regmap, attr->nr, attr->index, 0); + + return snprintf(buf, PAGE_SIZE, "%d\n", !!fault); +} + +/* Voltages */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ltc4222_show_value, NULL, + LTC4222_SOURCE1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, ltc4222_show_value, NULL, + LTC4222_ADIN1); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, ltc4222_show_value, NULL, + LTC4222_SOURCE2); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, ltc4222_show_value, NULL, + LTC4222_ADIN2); + +/* + * Voltage alarms + * UV/OV faults are associated with the input voltage, and power bad and fet + * faults are associated with the output voltage. + */ +static SENSOR_DEVICE_ATTR_2(in1_min_alarm, S_IRUGO, ltc4222_show_bool, NULL, + LTC4222_FAULT1, FAULT_UV); +static SENSOR_DEVICE_ATTR_2(in1_max_alarm, S_IRUGO, ltc4222_show_bool, NULL, + LTC4222_FAULT1, FAULT_OV); +static SENSOR_DEVICE_ATTR_2(in2_alarm, S_IRUGO, ltc4222_show_bool, NULL, + LTC4222_FAULT1, FAULT_POWER_BAD | FAULT_FET_BAD); + +static SENSOR_DEVICE_ATTR_2(in3_min_alarm, S_IRUGO, ltc4222_show_bool, NULL, + LTC4222_FAULT2, FAULT_UV); +static SENSOR_DEVICE_ATTR_2(in3_max_alarm, S_IRUGO, ltc4222_show_bool, NULL, + LTC4222_FAULT2, FAULT_OV); +static SENSOR_DEVICE_ATTR_2(in4_alarm, S_IRUGO, ltc4222_show_bool, NULL, + LTC4222_FAULT2, FAULT_POWER_BAD | FAULT_FET_BAD); + +/* Current (via sense resistor) */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ltc4222_show_value, NULL, + LTC4222_SENSE1); +static SENSOR_DEVICE_ATTR(curr2_input, S_IRUGO, ltc4222_show_value, NULL, + LTC4222_SENSE2); + +/* Overcurrent alarm */ +static SENSOR_DEVICE_ATTR_2(curr1_max_alarm, S_IRUGO, ltc4222_show_bool, NULL, + LTC4222_FAULT1, FAULT_OC); +static SENSOR_DEVICE_ATTR_2(curr2_max_alarm, S_IRUGO, ltc4222_show_bool, NULL, + LTC4222_FAULT2, FAULT_OC); + +static struct attribute *ltc4222_attrs[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min_alarm.dev_attr.attr, + &sensor_dev_attr_in1_max_alarm.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min_alarm.dev_attr.attr, + &sensor_dev_attr_in3_max_alarm.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_curr1_max_alarm.dev_attr.attr, + &sensor_dev_attr_curr2_input.dev_attr.attr, + &sensor_dev_attr_curr2_max_alarm.dev_attr.attr, + + NULL, +}; +ATTRIBUTE_GROUPS(ltc4222); + +static const struct regmap_config ltc4222_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = LTC4222_ADC_CONTROL, +}; + +static int ltc4222_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, <c4222_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(regmap); + } + + /* Clear faults */ + regmap_write(regmap, LTC4222_FAULT1, 0x00); + regmap_write(regmap, LTC4222_FAULT2, 0x00); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + regmap, + ltc4222_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id ltc4222_id[] = { + {"ltc4222", 0}, + { } +}; + +MODULE_DEVICE_TABLE(i2c, ltc4222_id); + +static struct i2c_driver ltc4222_driver = { + .driver = { + .name = "ltc4222", + }, + .probe = ltc4222_probe, + .id_table = ltc4222_id, +}; + +module_i2c_driver(ltc4222_driver); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("LTC4222 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ltc4245.c b/drivers/hwmon/ltc4245.c new file mode 100644 index 000000000..082f0a0bd --- /dev/null +++ b/drivers/hwmon/ltc4245.c @@ -0,0 +1,521 @@ +/* + * Driver for Linear Technology LTC4245 I2C Multiple Supply Hot Swap Controller + * + * Copyright (C) 2008 Ira W. Snyder + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This driver is based on the ds1621 and ina209 drivers. + * + * Datasheet: + * http://www.linear.com/pc/downloadDocument.do?navId=H0,C1,C1003,C1006,C1140,P19392,D13517 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Here are names of the chip's registers (a.k.a. commands) */ +enum ltc4245_cmd { + LTC4245_STATUS = 0x00, /* readonly */ + LTC4245_ALERT = 0x01, + LTC4245_CONTROL = 0x02, + LTC4245_ON = 0x03, + LTC4245_FAULT1 = 0x04, + LTC4245_FAULT2 = 0x05, + LTC4245_GPIO = 0x06, + LTC4245_ADCADR = 0x07, + + LTC4245_12VIN = 0x10, + LTC4245_12VSENSE = 0x11, + LTC4245_12VOUT = 0x12, + LTC4245_5VIN = 0x13, + LTC4245_5VSENSE = 0x14, + LTC4245_5VOUT = 0x15, + LTC4245_3VIN = 0x16, + LTC4245_3VSENSE = 0x17, + LTC4245_3VOUT = 0x18, + LTC4245_VEEIN = 0x19, + LTC4245_VEESENSE = 0x1a, + LTC4245_VEEOUT = 0x1b, + LTC4245_GPIOADC = 0x1c, +}; + +struct ltc4245_data { + struct i2c_client *client; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + + /* Control registers */ + u8 cregs[0x08]; + + /* Voltage registers */ + u8 vregs[0x0d]; + + /* GPIO ADC registers */ + bool use_extra_gpios; + int gpios[3]; +}; + +/* + * Update the readings from the GPIO pins. If the driver has been configured to + * sample all GPIO's as analog voltages, a round-robin sampling method is used. + * Otherwise, only the configured GPIO pin is sampled. + * + * LOCKING: must hold data->update_lock + */ +static void ltc4245_update_gpios(struct device *dev) +{ + struct ltc4245_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 gpio_curr, gpio_next, gpio_reg; + int i; + + /* no extra gpio support, we're basically done */ + if (!data->use_extra_gpios) { + data->gpios[0] = data->vregs[LTC4245_GPIOADC - 0x10]; + return; + } + + /* + * If the last reading was too long ago, then we mark all old GPIO + * readings as stale by setting them to -EAGAIN + */ + if (time_after(jiffies, data->last_updated + 5 * HZ)) { + for (i = 0; i < ARRAY_SIZE(data->gpios); i++) + data->gpios[i] = -EAGAIN; + } + + /* + * Get the current GPIO pin + * + * The datasheet calls these GPIO[1-3], but we'll calculate the zero + * based array index instead, and call them GPIO[0-2]. This is much + * easier to think about. + */ + gpio_curr = (data->cregs[LTC4245_GPIO] & 0xc0) >> 6; + if (gpio_curr > 0) + gpio_curr -= 1; + + /* Read the GPIO voltage from the GPIOADC register */ + data->gpios[gpio_curr] = data->vregs[LTC4245_GPIOADC - 0x10]; + + /* Find the next GPIO pin to read */ + gpio_next = (gpio_curr + 1) % ARRAY_SIZE(data->gpios); + + /* + * Calculate the correct setting for the GPIO register so it will + * sample the next GPIO pin + */ + gpio_reg = (data->cregs[LTC4245_GPIO] & 0x3f) | ((gpio_next + 1) << 6); + + /* Update the GPIO register */ + i2c_smbus_write_byte_data(client, LTC4245_GPIO, gpio_reg); + + /* Update saved data */ + data->cregs[LTC4245_GPIO] = gpio_reg; +} + +static struct ltc4245_data *ltc4245_update_device(struct device *dev) +{ + struct ltc4245_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + s32 val; + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + + /* Read control registers -- 0x00 to 0x07 */ + for (i = 0; i < ARRAY_SIZE(data->cregs); i++) { + val = i2c_smbus_read_byte_data(client, i); + if (unlikely(val < 0)) + data->cregs[i] = 0; + else + data->cregs[i] = val; + } + + /* Read voltage registers -- 0x10 to 0x1c */ + for (i = 0; i < ARRAY_SIZE(data->vregs); i++) { + val = i2c_smbus_read_byte_data(client, i+0x10); + if (unlikely(val < 0)) + data->vregs[i] = 0; + else + data->vregs[i] = val; + } + + /* Update GPIO readings */ + ltc4245_update_gpios(dev); + + data->last_updated = jiffies; + data->valid = true; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* Return the voltage from the given register in millivolts */ +static int ltc4245_get_voltage(struct device *dev, u8 reg) +{ + struct ltc4245_data *data = ltc4245_update_device(dev); + const u8 regval = data->vregs[reg - 0x10]; + u32 voltage = 0; + + switch (reg) { + case LTC4245_12VIN: + case LTC4245_12VOUT: + voltage = regval * 55; + break; + case LTC4245_5VIN: + case LTC4245_5VOUT: + voltage = regval * 22; + break; + case LTC4245_3VIN: + case LTC4245_3VOUT: + voltage = regval * 15; + break; + case LTC4245_VEEIN: + case LTC4245_VEEOUT: + voltage = regval * -55; + break; + case LTC4245_GPIOADC: + voltage = regval * 10; + break; + default: + /* If we get here, the developer messed up */ + WARN_ON_ONCE(1); + break; + } + + return voltage; +} + +/* Return the current in the given sense register in milliAmperes */ +static unsigned int ltc4245_get_current(struct device *dev, u8 reg) +{ + struct ltc4245_data *data = ltc4245_update_device(dev); + const u8 regval = data->vregs[reg - 0x10]; + unsigned int voltage; + unsigned int curr; + + /* + * The strange looking conversions that follow are fixed-point + * math, since we cannot do floating point in the kernel. + * + * Step 1: convert sense register to microVolts + * Step 2: convert voltage to milliAmperes + * + * If you play around with the V=IR equation, you come up with + * the following: X uV / Y mOhm == Z mA + * + * With the resistors that are fractions of a milliOhm, we multiply + * the voltage and resistance by 10, to shift the decimal point. + * Now we can use the normal division operator again. + */ + + switch (reg) { + case LTC4245_12VSENSE: + voltage = regval * 250; /* voltage in uV */ + curr = voltage / 50; /* sense resistor 50 mOhm */ + break; + case LTC4245_5VSENSE: + voltage = regval * 125; /* voltage in uV */ + curr = (voltage * 10) / 35; /* sense resistor 3.5 mOhm */ + break; + case LTC4245_3VSENSE: + voltage = regval * 125; /* voltage in uV */ + curr = (voltage * 10) / 25; /* sense resistor 2.5 mOhm */ + break; + case LTC4245_VEESENSE: + voltage = regval * 250; /* voltage in uV */ + curr = voltage / 100; /* sense resistor 100 mOhm */ + break; + default: + /* If we get here, the developer messed up */ + WARN_ON_ONCE(1); + curr = 0; + break; + } + + return curr; +} + +/* Map from voltage channel index to voltage register */ + +static const s8 ltc4245_in_regs[] = { + LTC4245_12VIN, LTC4245_5VIN, LTC4245_3VIN, LTC4245_VEEIN, + LTC4245_12VOUT, LTC4245_5VOUT, LTC4245_3VOUT, LTC4245_VEEOUT, +}; + +/* Map from current channel index to current register */ + +static const s8 ltc4245_curr_regs[] = { + LTC4245_12VSENSE, LTC4245_5VSENSE, LTC4245_3VSENSE, LTC4245_VEESENSE, +}; + +static int ltc4245_read_curr(struct device *dev, u32 attr, int channel, + long *val) +{ + struct ltc4245_data *data = ltc4245_update_device(dev); + + switch (attr) { + case hwmon_curr_input: + *val = ltc4245_get_current(dev, ltc4245_curr_regs[channel]); + return 0; + case hwmon_curr_max_alarm: + *val = !!(data->cregs[LTC4245_FAULT1] & BIT(channel + 4)); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ltc4245_read_in(struct device *dev, u32 attr, int channel, long *val) +{ + struct ltc4245_data *data = ltc4245_update_device(dev); + + switch (attr) { + case hwmon_in_input: + if (channel < 8) { + *val = ltc4245_get_voltage(dev, + ltc4245_in_regs[channel]); + } else { + int regval = data->gpios[channel - 8]; + + if (regval < 0) + return regval; + *val = regval * 10; + } + return 0; + case hwmon_in_min_alarm: + if (channel < 4) + *val = !!(data->cregs[LTC4245_FAULT1] & BIT(channel)); + else + *val = !!(data->cregs[LTC4245_FAULT2] & + BIT(channel - 4)); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ltc4245_read_power(struct device *dev, u32 attr, int channel, + long *val) +{ + unsigned long curr; + long voltage; + + switch (attr) { + case hwmon_power_input: + (void)ltc4245_update_device(dev); + curr = ltc4245_get_current(dev, ltc4245_curr_regs[channel]); + voltage = ltc4245_get_voltage(dev, ltc4245_in_regs[channel]); + *val = abs(curr * voltage); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ltc4245_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + + switch (type) { + case hwmon_curr: + return ltc4245_read_curr(dev, attr, channel, val); + case hwmon_power: + return ltc4245_read_power(dev, attr, channel, val); + case hwmon_in: + return ltc4245_read_in(dev, attr, channel - 1, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t ltc4245_is_visible(const void *_data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct ltc4245_data *data = _data; + + switch (type) { + case hwmon_in: + if (channel == 0) + return 0; + switch (attr) { + case hwmon_in_input: + if (channel > 9 && !data->use_extra_gpios) + return 0; + return S_IRUGO; + case hwmon_in_min_alarm: + if (channel > 8) + return 0; + return S_IRUGO; + default: + return 0; + } + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + case hwmon_curr_max_alarm: + return S_IRUGO; + default: + return 0; + } + case hwmon_power: + switch (attr) { + case hwmon_power_input: + return S_IRUGO; + default: + return 0; + } + default: + return 0; + } +} + +static const u32 ltc4245_in_config[] = { + HWMON_I_INPUT, /* dummy, skipped in is_visible */ + HWMON_I_INPUT | HWMON_I_MIN_ALARM, + HWMON_I_INPUT | HWMON_I_MIN_ALARM, + HWMON_I_INPUT | HWMON_I_MIN_ALARM, + HWMON_I_INPUT | HWMON_I_MIN_ALARM, + HWMON_I_INPUT | HWMON_I_MIN_ALARM, + HWMON_I_INPUT | HWMON_I_MIN_ALARM, + HWMON_I_INPUT | HWMON_I_MIN_ALARM, + HWMON_I_INPUT | HWMON_I_MIN_ALARM, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + 0 +}; + +static const struct hwmon_channel_info ltc4245_in = { + .type = hwmon_in, + .config = ltc4245_in_config, +}; + +static const u32 ltc4245_curr_config[] = { + HWMON_C_INPUT | HWMON_C_MAX_ALARM, + HWMON_C_INPUT | HWMON_C_MAX_ALARM, + HWMON_C_INPUT | HWMON_C_MAX_ALARM, + HWMON_C_INPUT | HWMON_C_MAX_ALARM, + 0 +}; + +static const struct hwmon_channel_info ltc4245_curr = { + .type = hwmon_curr, + .config = ltc4245_curr_config, +}; + +static const u32 ltc4245_power_config[] = { + HWMON_P_INPUT, + HWMON_P_INPUT, + HWMON_P_INPUT, + HWMON_P_INPUT, + 0 +}; + +static const struct hwmon_channel_info ltc4245_power = { + .type = hwmon_power, + .config = ltc4245_power_config, +}; + +static const struct hwmon_channel_info *ltc4245_info[] = { + <c4245_in, + <c4245_curr, + <c4245_power, + NULL +}; + +static const struct hwmon_ops ltc4245_hwmon_ops = { + .is_visible = ltc4245_is_visible, + .read = ltc4245_read, +}; + +static const struct hwmon_chip_info ltc4245_chip_info = { + .ops = <c4245_hwmon_ops, + .info = ltc4245_info, +}; + +static bool ltc4245_use_extra_gpios(struct i2c_client *client) +{ + struct ltc4245_platform_data *pdata = dev_get_platdata(&client->dev); + struct device_node *np = client->dev.of_node; + + /* prefer platform data */ + if (pdata) + return pdata->use_extra_gpios; + + /* fallback on OF */ + if (of_find_property(np, "ltc4245,use-extra-gpios", NULL)) + return true; + + return false; +} + +static int ltc4245_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct ltc4245_data *data; + struct device *hwmon_dev; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + data->use_extra_gpios = ltc4245_use_extra_gpios(client); + + /* Initialize the LTC4245 chip */ + i2c_smbus_write_byte_data(client, LTC4245_FAULT1, 0x00); + i2c_smbus_write_byte_data(client, LTC4245_FAULT2, 0x00); + + hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, + client->name, data, + <c4245_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id ltc4245_id[] = { + { "ltc4245", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc4245_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver ltc4245_driver = { + .driver = { + .name = "ltc4245", + }, + .probe = ltc4245_probe, + .id_table = ltc4245_id, +}; + +module_i2c_driver(ltc4245_driver); + +MODULE_AUTHOR("Ira W. Snyder "); +MODULE_DESCRIPTION("LTC4245 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ltc4260.c b/drivers/hwmon/ltc4260.c new file mode 100644 index 000000000..afb09574b --- /dev/null +++ b/drivers/hwmon/ltc4260.c @@ -0,0 +1,200 @@ +/* + * Driver for Linear Technology LTC4260 I2C Positive Voltage Hot Swap Controller + * + * Copyright (c) 2014 Guenter Roeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* chip registers */ +#define LTC4260_CONTROL 0x00 +#define LTC4260_ALERT 0x01 +#define LTC4260_STATUS 0x02 +#define LTC4260_FAULT 0x03 +#define LTC4260_SENSE 0x04 +#define LTC4260_SOURCE 0x05 +#define LTC4260_ADIN 0x06 + +/* + * Fault register bits + */ +#define FAULT_OV (1 << 0) +#define FAULT_UV (1 << 1) +#define FAULT_OC (1 << 2) +#define FAULT_POWER_BAD (1 << 3) +#define FAULT_FET_SHORT (1 << 5) + +/* Return the voltage from the given register in mV or mA */ +static int ltc4260_get_value(struct device *dev, u8 reg) +{ + struct regmap *regmap = dev_get_drvdata(dev); + unsigned int val; + int ret; + + ret = regmap_read(regmap, reg, &val); + if (ret < 0) + return ret; + + switch (reg) { + case LTC4260_ADIN: + /* 10 mV resolution. Convert to mV. */ + val = val * 10; + break; + case LTC4260_SOURCE: + /* 400 mV resolution. Convert to mV. */ + val = val * 400; + break; + case LTC4260_SENSE: + /* + * 300 uV resolution. Convert to current as measured with + * an 1 mOhm sense resistor, in mA. If a different sense + * resistor is installed, calculate the actual current by + * dividing the reported current by the sense resistor value + * in mOhm. + */ + val = val * 300; + break; + default: + return -EINVAL; + } + + return val; +} + +static ssize_t ltc4260_show_value(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int value; + + value = ltc4260_get_value(dev, attr->index); + if (value < 0) + return value; + return snprintf(buf, PAGE_SIZE, "%d\n", value); +} + +static ssize_t ltc4260_show_bool(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct regmap *regmap = dev_get_drvdata(dev); + unsigned int fault; + int ret; + + ret = regmap_read(regmap, LTC4260_FAULT, &fault); + if (ret < 0) + return ret; + + fault &= attr->index; + if (fault) /* Clear reported faults in chip register */ + regmap_update_bits(regmap, LTC4260_FAULT, attr->index, 0); + + return snprintf(buf, PAGE_SIZE, "%d\n", !!fault); +} + +/* Voltages */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ltc4260_show_value, NULL, + LTC4260_SOURCE); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, ltc4260_show_value, NULL, + LTC4260_ADIN); + +/* + * Voltage alarms + * UV/OV faults are associated with the input voltage, and the POWER BAD and + * FET SHORT faults are associated with the output voltage. + */ +static SENSOR_DEVICE_ATTR(in1_min_alarm, S_IRUGO, ltc4260_show_bool, NULL, + FAULT_UV); +static SENSOR_DEVICE_ATTR(in1_max_alarm, S_IRUGO, ltc4260_show_bool, NULL, + FAULT_OV); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, ltc4260_show_bool, NULL, + FAULT_POWER_BAD | FAULT_FET_SHORT); + +/* Current (via sense resistor) */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ltc4260_show_value, NULL, + LTC4260_SENSE); + +/* Overcurrent alarm */ +static SENSOR_DEVICE_ATTR(curr1_max_alarm, S_IRUGO, ltc4260_show_bool, NULL, + FAULT_OC); + +static struct attribute *ltc4260_attrs[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min_alarm.dev_attr.attr, + &sensor_dev_attr_in1_max_alarm.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_curr1_max_alarm.dev_attr.attr, + + NULL, +}; +ATTRIBUTE_GROUPS(ltc4260); + +static const struct regmap_config ltc4260_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = LTC4260_ADIN, +}; + +static int ltc4260_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, <c4260_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(regmap); + } + + /* Clear faults */ + regmap_write(regmap, LTC4260_FAULT, 0x00); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + regmap, + ltc4260_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id ltc4260_id[] = { + {"ltc4260", 0}, + { } +}; + +MODULE_DEVICE_TABLE(i2c, ltc4260_id); + +static struct i2c_driver ltc4260_driver = { + .driver = { + .name = "ltc4260", + }, + .probe = ltc4260_probe, + .id_table = ltc4260_id, +}; + +module_i2c_driver(ltc4260_driver); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("LTC4260 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ltc4261.c b/drivers/hwmon/ltc4261.c new file mode 100644 index 000000000..0becd6984 --- /dev/null +++ b/drivers/hwmon/ltc4261.c @@ -0,0 +1,266 @@ +/* + * Driver for Linear Technology LTC4261 I2C Negative Voltage Hot Swap Controller + * + * Copyright (C) 2010 Ericsson AB. + * + * Derived from: + * + * Driver for Linear Technology LTC4245 I2C Multiple Supply Hot Swap Controller + * Copyright (C) 2008 Ira W. Snyder + * + * Datasheet: http://cds.linear.com/docs/Datasheet/42612fb.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* chip registers */ +#define LTC4261_STATUS 0x00 /* readonly */ +#define LTC4261_FAULT 0x01 +#define LTC4261_ALERT 0x02 +#define LTC4261_CONTROL 0x03 +#define LTC4261_SENSE_H 0x04 +#define LTC4261_SENSE_L 0x05 +#define LTC4261_ADIN2_H 0x06 +#define LTC4261_ADIN2_L 0x07 +#define LTC4261_ADIN_H 0x08 +#define LTC4261_ADIN_L 0x09 + +/* + * Fault register bits + */ +#define FAULT_OV (1<<0) +#define FAULT_UV (1<<1) +#define FAULT_OC (1<<2) + +struct ltc4261_data { + struct i2c_client *client; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + + /* Registers */ + u8 regs[10]; +}; + +static struct ltc4261_data *ltc4261_update_device(struct device *dev) +{ + struct ltc4261_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + struct ltc4261_data *ret = data; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ / 4) || !data->valid) { + int i; + + /* Read registers -- 0x00 to 0x09 */ + for (i = 0; i < ARRAY_SIZE(data->regs); i++) { + int val; + + val = i2c_smbus_read_byte_data(client, i); + if (unlikely(val < 0)) { + dev_dbg(dev, + "Failed to read ADC value: error %d\n", + val); + ret = ERR_PTR(val); + data->valid = 0; + goto abort; + } + data->regs[i] = val; + } + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +/* Return the voltage from the given register in mV or mA */ +static int ltc4261_get_value(struct ltc4261_data *data, u8 reg) +{ + u32 val; + + val = (data->regs[reg] << 2) + (data->regs[reg + 1] >> 6); + + switch (reg) { + case LTC4261_ADIN_H: + case LTC4261_ADIN2_H: + /* 2.5mV resolution. Convert to mV. */ + val = val * 25 / 10; + break; + case LTC4261_SENSE_H: + /* + * 62.5uV resolution. Convert to current as measured with + * an 1 mOhm sense resistor, in mA. If a different sense + * resistor is installed, calculate the actual current by + * dividing the reported current by the sense resistor value + * in mOhm. + */ + val = val * 625 / 10; + break; + default: + /* If we get here, the developer messed up */ + WARN_ON_ONCE(1); + val = 0; + break; + } + + return val; +} + +static ssize_t ltc4261_show_value(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ltc4261_data *data = ltc4261_update_device(dev); + int value; + + if (IS_ERR(data)) + return PTR_ERR(data); + + value = ltc4261_get_value(data, attr->index); + return snprintf(buf, PAGE_SIZE, "%d\n", value); +} + +static ssize_t ltc4261_show_bool(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ltc4261_data *data = ltc4261_update_device(dev); + u8 fault; + + if (IS_ERR(data)) + return PTR_ERR(data); + + fault = data->regs[LTC4261_FAULT] & attr->index; + if (fault) /* Clear reported faults in chip register */ + i2c_smbus_write_byte_data(data->client, LTC4261_FAULT, ~fault); + + return snprintf(buf, PAGE_SIZE, "%d\n", fault ? 1 : 0); +} + +/* + * Input voltages. + */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ltc4261_show_value, NULL, + LTC4261_ADIN_H); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, ltc4261_show_value, NULL, + LTC4261_ADIN2_H); + +/* + * Voltage alarms. The chip has only one set of voltage alarm status bits, + * triggered by input voltage alarms. In many designs, those alarms are + * associated with the ADIN2 sensor, due to the proximity of the ADIN2 pin + * to the OV pin. ADIN2 is, however, not available on all chip variants. + * To ensure that the alarm condition is reported to the user, report it + * with both voltage sensors. + */ +static SENSOR_DEVICE_ATTR(in1_min_alarm, S_IRUGO, ltc4261_show_bool, NULL, + FAULT_UV); +static SENSOR_DEVICE_ATTR(in1_max_alarm, S_IRUGO, ltc4261_show_bool, NULL, + FAULT_OV); +static SENSOR_DEVICE_ATTR(in2_min_alarm, S_IRUGO, ltc4261_show_bool, NULL, + FAULT_UV); +static SENSOR_DEVICE_ATTR(in2_max_alarm, S_IRUGO, ltc4261_show_bool, NULL, + FAULT_OV); + +/* Currents (via sense resistor) */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ltc4261_show_value, NULL, + LTC4261_SENSE_H); + +/* Overcurrent alarm */ +static SENSOR_DEVICE_ATTR(curr1_max_alarm, S_IRUGO, ltc4261_show_bool, NULL, + FAULT_OC); + +static struct attribute *ltc4261_attrs[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min_alarm.dev_attr.attr, + &sensor_dev_attr_in1_max_alarm.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min_alarm.dev_attr.attr, + &sensor_dev_attr_in2_max_alarm.dev_attr.attr, + + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_curr1_max_alarm.dev_attr.attr, + + NULL, +}; +ATTRIBUTE_GROUPS(ltc4261); + +static int ltc4261_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct device *dev = &client->dev; + struct ltc4261_data *data; + struct device *hwmon_dev; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + if (i2c_smbus_read_byte_data(client, LTC4261_STATUS) < 0) { + dev_err(dev, "Failed to read status register\n"); + return -ENODEV; + } + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* Clear faults */ + i2c_smbus_write_byte_data(client, LTC4261_FAULT, 0x00); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + ltc4261_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id ltc4261_id[] = { + {"ltc4261", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ltc4261_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver ltc4261_driver = { + .driver = { + .name = "ltc4261", + }, + .probe = ltc4261_probe, + .id_table = ltc4261_id, +}; + +module_i2c_driver(ltc4261_driver); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("LTC4261 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ltq-cputemp.c b/drivers/hwmon/ltq-cputemp.c new file mode 100644 index 000000000..1d33f9459 --- /dev/null +++ b/drivers/hwmon/ltq-cputemp.c @@ -0,0 +1,163 @@ +/* Lantiq cpu temperature sensor driver + * + * Copyright (C) 2017 Florian Eckert + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * This program is distributed in the hope that it will be useful + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +/* gphy1 configuration register contains cpu temperature */ +#define CGU_GPHY1_CR 0x0040 +#define CGU_TEMP_PD BIT(19) + +static void ltq_cputemp_enable(void) +{ + ltq_cgu_w32(ltq_cgu_r32(CGU_GPHY1_CR) | CGU_TEMP_PD, CGU_GPHY1_CR); +} + +static void ltq_cputemp_disable(void *data) +{ + ltq_cgu_w32(ltq_cgu_r32(CGU_GPHY1_CR) & ~CGU_TEMP_PD, CGU_GPHY1_CR); +} + +static int ltq_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *temp) +{ + int value; + + switch (attr) { + case hwmon_temp_input: + /* get the temperature including one decimal place */ + value = (ltq_cgu_r32(CGU_GPHY1_CR) >> 9) & 0x01FF; + value = value * 5; + /* range -38 to +154 °C, register value zero is -38.0 °C */ + value -= 380; + /* scale temp to millidegree */ + value = value * 100; + break; + default: + return -EOPNOTSUPP; + } + + *temp = value; + return 0; +} + +static umode_t ltq_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type != hwmon_temp) + return 0; + + switch (attr) { + case hwmon_temp_input: + return 0444; + default: + return 0; + } +} + +static const u32 ltq_chip_config[] = { + HWMON_C_REGISTER_TZ, + 0 +}; + +static const struct hwmon_channel_info ltq_chip = { + .type = hwmon_chip, + .config = ltq_chip_config, +}; + +static const u32 ltq_temp_config[] = { + HWMON_T_INPUT, + 0 +}; + +static const struct hwmon_channel_info ltq_temp = { + .type = hwmon_temp, + .config = ltq_temp_config, +}; + +static const struct hwmon_channel_info *ltq_info[] = { + <q_chip, + <q_temp, + NULL +}; + +static const struct hwmon_ops ltq_hwmon_ops = { + .is_visible = ltq_is_visible, + .read = ltq_read, +}; + +static const struct hwmon_chip_info ltq_chip_info = { + .ops = <q_hwmon_ops, + .info = ltq_info, +}; + +static int ltq_cputemp_probe(struct platform_device *pdev) +{ + struct device *hwmon_dev; + int err = 0; + + /* available on vr9 v1.2 SoCs only */ + if (ltq_soc_type() != SOC_TYPE_VR9_2) + return -ENODEV; + + err = devm_add_action(&pdev->dev, ltq_cputemp_disable, NULL); + if (err) + return err; + + ltq_cputemp_enable(); + + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, + "ltq_cputemp", + NULL, + <q_chip_info, + NULL); + + if (IS_ERR(hwmon_dev)) { + dev_err(&pdev->dev, "Failed to register as hwmon device"); + return PTR_ERR(hwmon_dev); + } + + return 0; +} + +const struct of_device_id ltq_cputemp_match[] = { + { .compatible = "lantiq,cputemp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ltq_cputemp_match); + +static struct platform_driver ltq_cputemp_driver = { + .probe = ltq_cputemp_probe, + .driver = { + .name = "ltq-cputemp", + .of_match_table = ltq_cputemp_match, + }, +}; + +module_platform_driver(ltq_cputemp_driver); + +MODULE_AUTHOR("Florian Eckert "); +MODULE_DESCRIPTION("Lantiq cpu temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/max1111.c b/drivers/hwmon/max1111.c new file mode 100644 index 000000000..8ddd4d690 --- /dev/null +++ b/drivers/hwmon/max1111.c @@ -0,0 +1,296 @@ +/* + * max1111.c - +2.7V, Low-Power, Multichannel, Serial 8-bit ADCs + * + * Based on arch/arm/mach-pxa/corgi_ssp.c + * + * Copyright (C) 2004-2005 Richard Purdie + * + * Copyright (C) 2008 Marvell International Ltd. + * Eric Miao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * publishhed by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +enum chips { max1110, max1111, max1112, max1113 }; + +#define MAX1111_TX_BUF_SIZE 1 +#define MAX1111_RX_BUF_SIZE 2 + +/* MAX1111 Commands */ +#define MAX1111_CTRL_PD0 (1u << 0) +#define MAX1111_CTRL_PD1 (1u << 1) +#define MAX1111_CTRL_SGL (1u << 2) +#define MAX1111_CTRL_UNI (1u << 3) +#define MAX1110_CTRL_SEL_SH (4) +#define MAX1111_CTRL_SEL_SH (5) /* NOTE: bit 4 is ignored */ +#define MAX1111_CTRL_STR (1u << 7) + +struct max1111_data { + struct spi_device *spi; + struct device *hwmon_dev; + struct spi_message msg; + struct spi_transfer xfer[2]; + uint8_t tx_buf[MAX1111_TX_BUF_SIZE]; + uint8_t rx_buf[MAX1111_RX_BUF_SIZE]; + struct mutex drvdata_lock; + /* protect msg, xfer and buffers from multiple access */ + int sel_sh; + int lsb; +}; + +static int max1111_read(struct device *dev, int channel) +{ + struct max1111_data *data = dev_get_drvdata(dev); + uint8_t v1, v2; + int err; + + /* writing to drvdata struct is not thread safe, wait on mutex */ + mutex_lock(&data->drvdata_lock); + + data->tx_buf[0] = (channel << data->sel_sh) | + MAX1111_CTRL_PD0 | MAX1111_CTRL_PD1 | + MAX1111_CTRL_SGL | MAX1111_CTRL_UNI | MAX1111_CTRL_STR; + + err = spi_sync(data->spi, &data->msg); + if (err < 0) { + dev_err(dev, "spi_sync failed with %d\n", err); + mutex_unlock(&data->drvdata_lock); + return err; + } + + v1 = data->rx_buf[0]; + v2 = data->rx_buf[1]; + + mutex_unlock(&data->drvdata_lock); + + if ((v1 & 0xc0) || (v2 & 0x3f)) + return -EINVAL; + + return (v1 << 2) | (v2 >> 6); +} + +#ifdef CONFIG_SHARPSL_PM +static struct max1111_data *the_max1111; + +int max1111_read_channel(int channel) +{ + if (!the_max1111 || !the_max1111->spi) + return -ENODEV; + + return max1111_read(&the_max1111->spi->dev, channel); +} +EXPORT_SYMBOL(max1111_read_channel); +#endif + +/* + * NOTE: SPI devices do not have a default 'name' attribute, which is + * likely to be used by hwmon applications to distinguish between + * different devices, explicitly add a name attribute here. + */ +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", to_spi_device(dev)->modalias); +} + +static ssize_t show_adc(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct max1111_data *data = dev_get_drvdata(dev); + int channel = to_sensor_dev_attr(attr)->index; + int ret; + + ret = max1111_read(dev, channel); + if (ret < 0) + return ret; + + /* + * Assume the reference voltage to be 2.048V or 4.096V, with an 8-bit + * sample. The LSB weight is 8mV or 16mV depending on the chip type. + */ + return sprintf(buf, "%d\n", ret * data->lsb); +} + +#define MAX1111_ADC_ATTR(_id) \ + SENSOR_DEVICE_ATTR(in##_id##_input, S_IRUGO, show_adc, NULL, _id) + +static DEVICE_ATTR_RO(name); +static MAX1111_ADC_ATTR(0); +static MAX1111_ADC_ATTR(1); +static MAX1111_ADC_ATTR(2); +static MAX1111_ADC_ATTR(3); +static MAX1111_ADC_ATTR(4); +static MAX1111_ADC_ATTR(5); +static MAX1111_ADC_ATTR(6); +static MAX1111_ADC_ATTR(7); + +static struct attribute *max1111_attributes[] = { + &dev_attr_name.attr, + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + NULL, +}; + +static const struct attribute_group max1111_attr_group = { + .attrs = max1111_attributes, +}; + +static struct attribute *max1110_attributes[] = { + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + NULL, +}; + +static const struct attribute_group max1110_attr_group = { + .attrs = max1110_attributes, +}; + +static int setup_transfer(struct max1111_data *data) +{ + struct spi_message *m; + struct spi_transfer *x; + + m = &data->msg; + x = &data->xfer[0]; + + spi_message_init(m); + + x->tx_buf = &data->tx_buf[0]; + x->len = MAX1111_TX_BUF_SIZE; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &data->rx_buf[0]; + x->len = MAX1111_RX_BUF_SIZE; + spi_message_add_tail(x, m); + + return 0; +} + +static int max1111_probe(struct spi_device *spi) +{ + enum chips chip = spi_get_device_id(spi)->driver_data; + struct max1111_data *data; + int err; + + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + err = spi_setup(spi); + if (err < 0) + return err; + + data = devm_kzalloc(&spi->dev, sizeof(struct max1111_data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + switch (chip) { + case max1110: + data->lsb = 8; + data->sel_sh = MAX1110_CTRL_SEL_SH; + break; + case max1111: + data->lsb = 8; + data->sel_sh = MAX1111_CTRL_SEL_SH; + break; + case max1112: + data->lsb = 16; + data->sel_sh = MAX1110_CTRL_SEL_SH; + break; + case max1113: + data->lsb = 16; + data->sel_sh = MAX1111_CTRL_SEL_SH; + break; + } + err = setup_transfer(data); + if (err) + return err; + + mutex_init(&data->drvdata_lock); + + data->spi = spi; + spi_set_drvdata(spi, data); + + err = sysfs_create_group(&spi->dev.kobj, &max1111_attr_group); + if (err) { + dev_err(&spi->dev, "failed to create attribute group\n"); + return err; + } + if (chip == max1110 || chip == max1112) { + err = sysfs_create_group(&spi->dev.kobj, &max1110_attr_group); + if (err) { + dev_err(&spi->dev, + "failed to create extended attribute group\n"); + goto err_remove; + } + } + + data->hwmon_dev = hwmon_device_register(&spi->dev); + if (IS_ERR(data->hwmon_dev)) { + dev_err(&spi->dev, "failed to create hwmon device\n"); + err = PTR_ERR(data->hwmon_dev); + goto err_remove; + } + +#ifdef CONFIG_SHARPSL_PM + the_max1111 = data; +#endif + return 0; + +err_remove: + sysfs_remove_group(&spi->dev.kobj, &max1110_attr_group); + sysfs_remove_group(&spi->dev.kobj, &max1111_attr_group); + return err; +} + +static int max1111_remove(struct spi_device *spi) +{ + struct max1111_data *data = spi_get_drvdata(spi); + +#ifdef CONFIG_SHARPSL_PM + the_max1111 = NULL; +#endif + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&spi->dev.kobj, &max1110_attr_group); + sysfs_remove_group(&spi->dev.kobj, &max1111_attr_group); + mutex_destroy(&data->drvdata_lock); + return 0; +} + +static const struct spi_device_id max1111_ids[] = { + { "max1110", max1110 }, + { "max1111", max1111 }, + { "max1112", max1112 }, + { "max1113", max1113 }, + { }, +}; +MODULE_DEVICE_TABLE(spi, max1111_ids); + +static struct spi_driver max1111_driver = { + .driver = { + .name = "max1111", + }, + .id_table = max1111_ids, + .probe = max1111_probe, + .remove = max1111_remove, +}; + +module_spi_driver(max1111_driver); + +MODULE_AUTHOR("Eric Miao "); +MODULE_DESCRIPTION("MAX1110/MAX1111/MAX1112/MAX1113 ADC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/max16065.c b/drivers/hwmon/max16065.c new file mode 100644 index 000000000..162401aae --- /dev/null +++ b/drivers/hwmon/max16065.c @@ -0,0 +1,673 @@ +/* + * Driver for + * Maxim MAX16065/MAX16066 12-Channel/8-Channel, Flash-Configurable + * System Managers with Nonvolatile Fault Registers + * Maxim MAX16067/MAX16068 6-Channel, Flash-Configurable System Managers + * with Nonvolatile Fault Registers + * Maxim MAX16070/MAX16071 12-Channel/8-Channel, Flash-Configurable System + * Monitors with Nonvolatile Fault Registers + * + * Copyright (C) 2011 Ericsson AB. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum chips { max16065, max16066, max16067, max16068, max16070, max16071 }; + +/* + * Registers + */ +#define MAX16065_ADC(x) ((x) * 2) + +#define MAX16065_CURR_SENSE 0x18 +#define MAX16065_CSP_ADC 0x19 +#define MAX16065_FAULT(x) (0x1b + (x)) +#define MAX16065_SCALE(x) (0x43 + (x)) +#define MAX16065_CURR_CONTROL 0x47 +#define MAX16065_LIMIT(l, x) (0x48 + (l) + (x) * 3) /* + * l: limit + * 0: min/max + * 1: crit + * 2: lcrit + * x: ADC index + */ + +#define MAX16065_SW_ENABLE 0x73 + +#define MAX16065_WARNING_OV (1 << 3) /* Set if secondary threshold is OV + warning */ + +#define MAX16065_CURR_ENABLE (1 << 0) + +#define MAX16065_NUM_LIMIT 3 +#define MAX16065_NUM_ADC 12 /* maximum number of ADC channels */ + +static const int max16065_num_adc[] = { + [max16065] = 12, + [max16066] = 8, + [max16067] = 6, + [max16068] = 6, + [max16070] = 12, + [max16071] = 8, +}; + +static const bool max16065_have_secondary[] = { + [max16065] = true, + [max16066] = true, + [max16067] = false, + [max16068] = false, + [max16070] = true, + [max16071] = true, +}; + +static const bool max16065_have_current[] = { + [max16065] = true, + [max16066] = true, + [max16067] = false, + [max16068] = false, + [max16070] = true, + [max16071] = true, +}; + +struct max16065_data { + enum chips type; + struct i2c_client *client; + const struct attribute_group *groups[4]; + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + int num_adc; + bool have_current; + int curr_gain; + /* limits are in mV */ + int limit[MAX16065_NUM_LIMIT][MAX16065_NUM_ADC]; + int range[MAX16065_NUM_ADC + 1];/* voltage range */ + int adc[MAX16065_NUM_ADC + 1]; /* adc values (raw) including csp_adc */ + int curr_sense; + int fault[2]; +}; + +static const int max16065_adc_range[] = { 5560, 2780, 1390, 0 }; +static const int max16065_csp_adc_range[] = { 7000, 14000 }; + +/* ADC registers have 10 bit resolution. */ +static inline int ADC_TO_MV(int adc, int range) +{ + return (adc * range) / 1024; +} + +/* + * Limit registers have 8 bit resolution and match upper 8 bits of ADC + * registers. + */ +static inline int LIMIT_TO_MV(int limit, int range) +{ + return limit * range / 256; +} + +static inline int MV_TO_LIMIT(int mv, int range) +{ + return clamp_val(DIV_ROUND_CLOSEST(mv * 256, range), 0, 255); +} + +static inline int ADC_TO_CURR(int adc, int gain) +{ + return adc * 1400000 / (gain * 255); +} + +/* + * max16065_read_adc() + * + * Read 16 bit value from , . + * Upper 8 bits are in , lower 2 bits are in bits 7:6 of . + */ +static int max16065_read_adc(struct i2c_client *client, int reg) +{ + int rv; + + rv = i2c_smbus_read_word_swapped(client, reg); + if (unlikely(rv < 0)) + return rv; + return rv >> 6; +} + +static struct max16065_data *max16065_update_device(struct device *dev) +{ + struct max16065_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + mutex_lock(&data->update_lock); + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + int i; + + for (i = 0; i < data->num_adc; i++) + data->adc[i] + = max16065_read_adc(client, MAX16065_ADC(i)); + + if (data->have_current) { + data->adc[MAX16065_NUM_ADC] + = max16065_read_adc(client, MAX16065_CSP_ADC); + data->curr_sense + = i2c_smbus_read_byte_data(client, + MAX16065_CURR_SENSE); + } + + for (i = 0; i < DIV_ROUND_UP(data->num_adc, 8); i++) + data->fault[i] + = i2c_smbus_read_byte_data(client, MAX16065_FAULT(i)); + + data->last_updated = jiffies; + data->valid = 1; + } + mutex_unlock(&data->update_lock); + return data; +} + +static ssize_t max16065_show_alarm(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(da); + struct max16065_data *data = max16065_update_device(dev); + int val = data->fault[attr2->nr]; + + if (val < 0) + return val; + + val &= (1 << attr2->index); + if (val) + i2c_smbus_write_byte_data(data->client, + MAX16065_FAULT(attr2->nr), val); + + return snprintf(buf, PAGE_SIZE, "%d\n", !!val); +} + +static ssize_t max16065_show_input(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct max16065_data *data = max16065_update_device(dev); + int adc = data->adc[attr->index]; + + if (unlikely(adc < 0)) + return adc; + + return snprintf(buf, PAGE_SIZE, "%d\n", + ADC_TO_MV(adc, data->range[attr->index])); +} + +static ssize_t max16065_show_current(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct max16065_data *data = max16065_update_device(dev); + + if (unlikely(data->curr_sense < 0)) + return data->curr_sense; + + return snprintf(buf, PAGE_SIZE, "%d\n", + ADC_TO_CURR(data->curr_sense, data->curr_gain)); +} + +static ssize_t max16065_set_limit(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(da); + struct max16065_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + int limit; + + err = kstrtoul(buf, 10, &val); + if (unlikely(err < 0)) + return err; + + limit = MV_TO_LIMIT(val, data->range[attr2->index]); + + mutex_lock(&data->update_lock); + data->limit[attr2->nr][attr2->index] + = LIMIT_TO_MV(limit, data->range[attr2->index]); + i2c_smbus_write_byte_data(data->client, + MAX16065_LIMIT(attr2->nr, attr2->index), + limit); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t max16065_show_limit(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(da); + struct max16065_data *data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", + data->limit[attr2->nr][attr2->index]); +} + +/* Construct a sensor_device_attribute structure for each register */ + +/* Input voltages */ +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, max16065_show_input, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, max16065_show_input, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, max16065_show_input, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, max16065_show_input, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, max16065_show_input, NULL, 4); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, max16065_show_input, NULL, 5); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, max16065_show_input, NULL, 6); +static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, max16065_show_input, NULL, 7); +static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, max16065_show_input, NULL, 8); +static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, max16065_show_input, NULL, 9); +static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, max16065_show_input, NULL, 10); +static SENSOR_DEVICE_ATTR(in11_input, S_IRUGO, max16065_show_input, NULL, 11); +static SENSOR_DEVICE_ATTR(in12_input, S_IRUGO, max16065_show_input, NULL, 12); + +/* Input voltages lcrit */ +static SENSOR_DEVICE_ATTR_2(in0_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 0); +static SENSOR_DEVICE_ATTR_2(in1_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 1); +static SENSOR_DEVICE_ATTR_2(in2_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 2); +static SENSOR_DEVICE_ATTR_2(in3_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 3); +static SENSOR_DEVICE_ATTR_2(in4_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 4); +static SENSOR_DEVICE_ATTR_2(in5_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 5); +static SENSOR_DEVICE_ATTR_2(in6_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 6); +static SENSOR_DEVICE_ATTR_2(in7_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 7); +static SENSOR_DEVICE_ATTR_2(in8_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 8); +static SENSOR_DEVICE_ATTR_2(in9_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 9); +static SENSOR_DEVICE_ATTR_2(in10_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 10); +static SENSOR_DEVICE_ATTR_2(in11_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 2, 11); + +/* Input voltages crit */ +static SENSOR_DEVICE_ATTR_2(in0_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 0); +static SENSOR_DEVICE_ATTR_2(in1_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 1); +static SENSOR_DEVICE_ATTR_2(in2_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 2); +static SENSOR_DEVICE_ATTR_2(in3_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 3); +static SENSOR_DEVICE_ATTR_2(in4_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 4); +static SENSOR_DEVICE_ATTR_2(in5_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 5); +static SENSOR_DEVICE_ATTR_2(in6_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 6); +static SENSOR_DEVICE_ATTR_2(in7_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 7); +static SENSOR_DEVICE_ATTR_2(in8_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 8); +static SENSOR_DEVICE_ATTR_2(in9_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 9); +static SENSOR_DEVICE_ATTR_2(in10_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 10); +static SENSOR_DEVICE_ATTR_2(in11_crit, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 1, 11); + +/* Input voltages min */ +static SENSOR_DEVICE_ATTR_2(in0_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 0); +static SENSOR_DEVICE_ATTR_2(in1_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 1); +static SENSOR_DEVICE_ATTR_2(in2_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 2); +static SENSOR_DEVICE_ATTR_2(in3_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 3); +static SENSOR_DEVICE_ATTR_2(in4_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 4); +static SENSOR_DEVICE_ATTR_2(in5_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 5); +static SENSOR_DEVICE_ATTR_2(in6_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 6); +static SENSOR_DEVICE_ATTR_2(in7_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 7); +static SENSOR_DEVICE_ATTR_2(in8_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 8); +static SENSOR_DEVICE_ATTR_2(in9_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 9); +static SENSOR_DEVICE_ATTR_2(in10_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 10); +static SENSOR_DEVICE_ATTR_2(in11_min, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 11); + +/* Input voltages max */ +static SENSOR_DEVICE_ATTR_2(in0_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 0); +static SENSOR_DEVICE_ATTR_2(in1_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 1); +static SENSOR_DEVICE_ATTR_2(in2_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 2); +static SENSOR_DEVICE_ATTR_2(in3_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 3); +static SENSOR_DEVICE_ATTR_2(in4_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 4); +static SENSOR_DEVICE_ATTR_2(in5_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 5); +static SENSOR_DEVICE_ATTR_2(in6_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 6); +static SENSOR_DEVICE_ATTR_2(in7_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 7); +static SENSOR_DEVICE_ATTR_2(in8_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 8); +static SENSOR_DEVICE_ATTR_2(in9_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 9); +static SENSOR_DEVICE_ATTR_2(in10_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 10); +static SENSOR_DEVICE_ATTR_2(in11_max, S_IWUSR | S_IRUGO, max16065_show_limit, + max16065_set_limit, 0, 11); + +/* alarms */ +static SENSOR_DEVICE_ATTR_2(in0_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 0); +static SENSOR_DEVICE_ATTR_2(in1_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 1); +static SENSOR_DEVICE_ATTR_2(in2_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 2); +static SENSOR_DEVICE_ATTR_2(in3_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 3); +static SENSOR_DEVICE_ATTR_2(in4_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 4); +static SENSOR_DEVICE_ATTR_2(in5_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 5); +static SENSOR_DEVICE_ATTR_2(in6_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 6); +static SENSOR_DEVICE_ATTR_2(in7_alarm, S_IRUGO, max16065_show_alarm, NULL, + 0, 7); +static SENSOR_DEVICE_ATTR_2(in8_alarm, S_IRUGO, max16065_show_alarm, NULL, + 1, 0); +static SENSOR_DEVICE_ATTR_2(in9_alarm, S_IRUGO, max16065_show_alarm, NULL, + 1, 1); +static SENSOR_DEVICE_ATTR_2(in10_alarm, S_IRUGO, max16065_show_alarm, NULL, + 1, 2); +static SENSOR_DEVICE_ATTR_2(in11_alarm, S_IRUGO, max16065_show_alarm, NULL, + 1, 3); + +/* Current and alarm */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, max16065_show_current, NULL, 0); +static SENSOR_DEVICE_ATTR_2(curr1_alarm, S_IRUGO, max16065_show_alarm, NULL, + 1, 4); + +/* + * Finally, construct an array of pointers to members of the above objects, + * as required for sysfs_create_group() + */ +static struct attribute *max16065_basic_attributes[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_lcrit.dev_attr.attr, + &sensor_dev_attr_in0_crit.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_lcrit.dev_attr.attr, + &sensor_dev_attr_in1_crit.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_lcrit.dev_attr.attr, + &sensor_dev_attr_in2_crit.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_lcrit.dev_attr.attr, + &sensor_dev_attr_in3_crit.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_lcrit.dev_attr.attr, + &sensor_dev_attr_in4_crit.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_lcrit.dev_attr.attr, + &sensor_dev_attr_in5_crit.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_lcrit.dev_attr.attr, + &sensor_dev_attr_in6_crit.dev_attr.attr, + &sensor_dev_attr_in6_alarm.dev_attr.attr, + + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_lcrit.dev_attr.attr, + &sensor_dev_attr_in7_crit.dev_attr.attr, + &sensor_dev_attr_in7_alarm.dev_attr.attr, + + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in8_lcrit.dev_attr.attr, + &sensor_dev_attr_in8_crit.dev_attr.attr, + &sensor_dev_attr_in8_alarm.dev_attr.attr, + + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in9_lcrit.dev_attr.attr, + &sensor_dev_attr_in9_crit.dev_attr.attr, + &sensor_dev_attr_in9_alarm.dev_attr.attr, + + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in10_lcrit.dev_attr.attr, + &sensor_dev_attr_in10_crit.dev_attr.attr, + &sensor_dev_attr_in10_alarm.dev_attr.attr, + + &sensor_dev_attr_in11_input.dev_attr.attr, + &sensor_dev_attr_in11_lcrit.dev_attr.attr, + &sensor_dev_attr_in11_crit.dev_attr.attr, + &sensor_dev_attr_in11_alarm.dev_attr.attr, + + NULL +}; + +static struct attribute *max16065_current_attributes[] = { + &sensor_dev_attr_in12_input.dev_attr.attr, + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_curr1_alarm.dev_attr.attr, + NULL +}; + +static struct attribute *max16065_min_attributes[] = { + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in8_min.dev_attr.attr, + &sensor_dev_attr_in9_min.dev_attr.attr, + &sensor_dev_attr_in10_min.dev_attr.attr, + &sensor_dev_attr_in11_min.dev_attr.attr, + NULL +}; + +static struct attribute *max16065_max_attributes[] = { + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in8_max.dev_attr.attr, + &sensor_dev_attr_in9_max.dev_attr.attr, + &sensor_dev_attr_in10_max.dev_attr.attr, + &sensor_dev_attr_in11_max.dev_attr.attr, + NULL +}; + +static umode_t max16065_basic_is_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct max16065_data *data = dev_get_drvdata(dev); + int index = n / 4; + + if (index >= data->num_adc || !data->range[index]) + return 0; + return a->mode; +} + +static umode_t max16065_secondary_is_visible(struct kobject *kobj, + struct attribute *a, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct max16065_data *data = dev_get_drvdata(dev); + + if (index >= data->num_adc) + return 0; + return a->mode; +} + +static const struct attribute_group max16065_basic_group = { + .attrs = max16065_basic_attributes, + .is_visible = max16065_basic_is_visible, +}; + +static const struct attribute_group max16065_current_group = { + .attrs = max16065_current_attributes, +}; + +static const struct attribute_group max16065_min_group = { + .attrs = max16065_min_attributes, + .is_visible = max16065_secondary_is_visible, +}; + +static const struct attribute_group max16065_max_group = { + .attrs = max16065_max_attributes, + .is_visible = max16065_secondary_is_visible, +}; + +static int max16065_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct max16065_data *data; + struct device *dev = &client->dev; + struct device *hwmon_dev; + int i, j, val; + bool have_secondary; /* true if chip has secondary limits */ + bool secondary_is_max = false; /* secondary limits reflect max */ + int groups = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (unlikely(!data)) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + data->num_adc = max16065_num_adc[id->driver_data]; + data->have_current = max16065_have_current[id->driver_data]; + have_secondary = max16065_have_secondary[id->driver_data]; + + if (have_secondary) { + val = i2c_smbus_read_byte_data(client, MAX16065_SW_ENABLE); + if (unlikely(val < 0)) + return val; + secondary_is_max = val & MAX16065_WARNING_OV; + } + + /* Read scale registers, convert to range */ + for (i = 0; i < DIV_ROUND_UP(data->num_adc, 4); i++) { + val = i2c_smbus_read_byte_data(client, MAX16065_SCALE(i)); + if (unlikely(val < 0)) + return val; + for (j = 0; j < 4 && i * 4 + j < data->num_adc; j++) { + data->range[i * 4 + j] = + max16065_adc_range[(val >> (j * 2)) & 0x3]; + } + } + + /* Read limits */ + for (i = 0; i < MAX16065_NUM_LIMIT; i++) { + if (i == 0 && !have_secondary) + continue; + + for (j = 0; j < data->num_adc; j++) { + val = i2c_smbus_read_byte_data(client, + MAX16065_LIMIT(i, j)); + if (unlikely(val < 0)) + return val; + data->limit[i][j] = LIMIT_TO_MV(val, data->range[j]); + } + } + + /* sysfs hooks */ + data->groups[groups++] = &max16065_basic_group; + if (have_secondary) + data->groups[groups++] = secondary_is_max ? + &max16065_max_group : &max16065_min_group; + + if (data->have_current) { + val = i2c_smbus_read_byte_data(client, MAX16065_CURR_CONTROL); + if (unlikely(val < 0)) + return val; + if (val & MAX16065_CURR_ENABLE) { + /* + * Current gain is 6, 12, 24, 48 based on values in + * bit 2,3. + */ + data->curr_gain = 6 << ((val >> 2) & 0x03); + data->range[MAX16065_NUM_ADC] + = max16065_csp_adc_range[(val >> 1) & 0x01]; + data->groups[groups++] = &max16065_current_group; + } else { + data->have_current = false; + } + } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id max16065_id[] = { + { "max16065", max16065 }, + { "max16066", max16066 }, + { "max16067", max16067 }, + { "max16068", max16068 }, + { "max16070", max16070 }, + { "max16071", max16071 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, max16065_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max16065_driver = { + .driver = { + .name = "max16065", + }, + .probe = max16065_probe, + .id_table = max16065_id, +}; + +module_i2c_driver(max16065_driver); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("MAX16065 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/max1619.c b/drivers/hwmon/max1619.c new file mode 100644 index 000000000..76d966932 --- /dev/null +++ b/drivers/hwmon/max1619.c @@ -0,0 +1,331 @@ +/* + * max1619.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 2003-2004 Oleksij Rempel + * Jean Delvare + * + * Based on the lm90 driver. The MAX1619 is a sensor chip made by Maxim. + * It reports up to two temperatures (its own plus up to + * one external one). Complete datasheet can be + * obtained from Maxim's website at: + * http://pdfserv.maxim-ic.com/en/ds/MAX1619.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const unsigned short normal_i2c[] = { + 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, 0x4c, 0x4d, 0x4e, I2C_CLIENT_END }; + +/* + * The MAX1619 registers + */ + +#define MAX1619_REG_R_MAN_ID 0xFE +#define MAX1619_REG_R_CHIP_ID 0xFF +#define MAX1619_REG_R_CONFIG 0x03 +#define MAX1619_REG_W_CONFIG 0x09 +#define MAX1619_REG_R_CONVRATE 0x04 +#define MAX1619_REG_W_CONVRATE 0x0A +#define MAX1619_REG_R_STATUS 0x02 +#define MAX1619_REG_R_LOCAL_TEMP 0x00 +#define MAX1619_REG_R_REMOTE_TEMP 0x01 +#define MAX1619_REG_R_REMOTE_HIGH 0x07 +#define MAX1619_REG_W_REMOTE_HIGH 0x0D +#define MAX1619_REG_R_REMOTE_LOW 0x08 +#define MAX1619_REG_W_REMOTE_LOW 0x0E +#define MAX1619_REG_R_REMOTE_CRIT 0x10 +#define MAX1619_REG_W_REMOTE_CRIT 0x12 +#define MAX1619_REG_R_TCRIT_HYST 0x11 +#define MAX1619_REG_W_TCRIT_HYST 0x13 + +/* + * Conversions + */ + +static int temp_from_reg(int val) +{ + return (val & 0x80 ? val-0x100 : val) * 1000; +} + +static int temp_to_reg(int val) +{ + return (val < 0 ? val+0x100*1000 : val) / 1000; +} + +enum temp_index { + t_input1 = 0, + t_input2, + t_low2, + t_high2, + t_crit2, + t_hyst2, + t_num_regs +}; + +/* + * Client data (each client gets its own) + */ + +struct max1619_data { + struct i2c_client *client; + struct mutex update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* registers values */ + u8 temp[t_num_regs]; /* index with enum temp_index */ + u8 alarms; +}; + +static const u8 regs_read[t_num_regs] = { + [t_input1] = MAX1619_REG_R_LOCAL_TEMP, + [t_input2] = MAX1619_REG_R_REMOTE_TEMP, + [t_low2] = MAX1619_REG_R_REMOTE_LOW, + [t_high2] = MAX1619_REG_R_REMOTE_HIGH, + [t_crit2] = MAX1619_REG_R_REMOTE_CRIT, + [t_hyst2] = MAX1619_REG_R_TCRIT_HYST, +}; + +static const u8 regs_write[t_num_regs] = { + [t_low2] = MAX1619_REG_W_REMOTE_LOW, + [t_high2] = MAX1619_REG_W_REMOTE_HIGH, + [t_crit2] = MAX1619_REG_W_REMOTE_CRIT, + [t_hyst2] = MAX1619_REG_W_TCRIT_HYST, +}; + +static struct max1619_data *max1619_update_device(struct device *dev) +{ + struct max1619_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int config, i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ * 2) || !data->valid) { + dev_dbg(&client->dev, "Updating max1619 data.\n"); + for (i = 0; i < t_num_regs; i++) + data->temp[i] = i2c_smbus_read_byte_data(client, + regs_read[i]); + data->alarms = i2c_smbus_read_byte_data(client, + MAX1619_REG_R_STATUS); + /* If OVERT polarity is low, reverse alarm bit */ + config = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CONFIG); + if (!(config & 0x20)) + data->alarms ^= 0x02; + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Sysfs stuff + */ + +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct max1619_data *data = max1619_update_device(dev); + + return sprintf(buf, "%d\n", temp_from_reg(data->temp[attr->index])); +} + +static ssize_t set_temp(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct max1619_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp[attr->index] = temp_to_reg(val); + i2c_smbus_write_byte_data(client, regs_write[attr->index], + data->temp[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct max1619_data *data = max1619_update_device(dev); + return sprintf(buf, "%d\n", data->alarms); +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct max1619_data *data = max1619_update_device(dev); + return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, t_input1); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, t_input2); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_temp, set_temp, + t_low2); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp, set_temp, + t_high2); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IWUSR | S_IRUGO, show_temp, set_temp, + t_crit2); +static SENSOR_DEVICE_ATTR(temp2_crit_hyst, S_IWUSR | S_IRUGO, show_temp, + set_temp, t_hyst2); + +static DEVICE_ATTR_RO(alarms); +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 4); + +static struct attribute *max1619_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr, + + &dev_attr_alarms.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(max1619); + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int max1619_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + u8 reg_config, reg_convrate, reg_status, man_id, chip_id; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* detection */ + reg_config = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CONFIG); + reg_convrate = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CONVRATE); + reg_status = i2c_smbus_read_byte_data(client, MAX1619_REG_R_STATUS); + if ((reg_config & 0x03) != 0x00 + || reg_convrate > 0x07 || (reg_status & 0x61) != 0x00) { + dev_dbg(&adapter->dev, "MAX1619 detection failed at 0x%02x\n", + client->addr); + return -ENODEV; + } + + /* identification */ + man_id = i2c_smbus_read_byte_data(client, MAX1619_REG_R_MAN_ID); + chip_id = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CHIP_ID); + if (man_id != 0x4D || chip_id != 0x04) { + dev_info(&adapter->dev, + "Unsupported chip (man_id=0x%02X, chip_id=0x%02X).\n", + man_id, chip_id); + return -ENODEV; + } + + strlcpy(info->type, "max1619", I2C_NAME_SIZE); + + return 0; +} + +static void max1619_init_client(struct i2c_client *client) +{ + u8 config; + + /* + * Start the conversions. + */ + i2c_smbus_write_byte_data(client, MAX1619_REG_W_CONVRATE, + 5); /* 2 Hz */ + config = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CONFIG); + if (config & 0x40) + i2c_smbus_write_byte_data(client, MAX1619_REG_W_CONFIG, + config & 0xBF); /* run */ +} + +static int max1619_probe(struct i2c_client *new_client, + const struct i2c_device_id *id) +{ + struct max1619_data *data; + struct device *hwmon_dev; + + data = devm_kzalloc(&new_client->dev, sizeof(struct max1619_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = new_client; + mutex_init(&data->update_lock); + + /* Initialize the MAX1619 chip */ + max1619_init_client(new_client); + + hwmon_dev = devm_hwmon_device_register_with_groups(&new_client->dev, + new_client->name, + data, + max1619_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id max1619_id[] = { + { "max1619", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max1619_id); + +#ifdef CONFIG_OF +static const struct of_device_id max1619_of_match[] = { + { .compatible = "maxim,max1619", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, max1619_of_match); +#endif + +static struct i2c_driver max1619_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "max1619", + .of_match_table = of_match_ptr(max1619_of_match), + }, + .probe = max1619_probe, + .id_table = max1619_id, + .detect = max1619_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(max1619_driver); + +MODULE_AUTHOR("Oleksij Rempel , Jean Delvare "); +MODULE_DESCRIPTION("MAX1619 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/max1668.c b/drivers/hwmon/max1668.c new file mode 100644 index 000000000..7ca889910 --- /dev/null +++ b/drivers/hwmon/max1668.c @@ -0,0 +1,460 @@ +/* + * Copyright (c) 2011 David George + * + * based on adm1021.c + * some credit to Christoph Scheurer, but largely a rewrite + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short max1668_addr_list[] = { + 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, 0x4c, 0x4d, 0x4e, I2C_CLIENT_END }; + +/* max1668 registers */ + +#define MAX1668_REG_TEMP(nr) (nr) +#define MAX1668_REG_STAT1 0x05 +#define MAX1668_REG_STAT2 0x06 +#define MAX1668_REG_MAN_ID 0xfe +#define MAX1668_REG_DEV_ID 0xff + +/* limits */ + +/* write high limits */ +#define MAX1668_REG_LIMH_WR(nr) (0x13 + 2 * (nr)) +/* write low limits */ +#define MAX1668_REG_LIML_WR(nr) (0x14 + 2 * (nr)) +/* read high limits */ +#define MAX1668_REG_LIMH_RD(nr) (0x08 + 2 * (nr)) +/* read low limits */ +#define MAX1668_REG_LIML_RD(nr) (0x09 + 2 * (nr)) + +/* manufacturer and device ID Constants */ +#define MAN_ID_MAXIM 0x4d +#define DEV_ID_MAX1668 0x3 +#define DEV_ID_MAX1805 0x5 +#define DEV_ID_MAX1989 0xb + +/* read only mode module parameter */ +static bool read_only; +module_param(read_only, bool, 0); +MODULE_PARM_DESC(read_only, "Don't set any values, read only mode"); + +enum chips { max1668, max1805, max1989 }; + +struct max1668_data { + struct i2c_client *client; + const struct attribute_group *groups[3]; + enum chips type; + + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* 1x local and 4x remote */ + s8 temp_max[5]; + s8 temp_min[5]; + s8 temp[5]; + u16 alarms; +}; + +static struct max1668_data *max1668_update_device(struct device *dev) +{ + struct max1668_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + struct max1668_data *ret = data; + s32 val; + int i; + + mutex_lock(&data->update_lock); + + if (data->valid && !time_after(jiffies, + data->last_updated + HZ + HZ / 2)) + goto abort; + + for (i = 0; i < 5; i++) { + val = i2c_smbus_read_byte_data(client, MAX1668_REG_TEMP(i)); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i] = (s8) val; + + val = i2c_smbus_read_byte_data(client, MAX1668_REG_LIMH_RD(i)); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp_max[i] = (s8) val; + + val = i2c_smbus_read_byte_data(client, MAX1668_REG_LIML_RD(i)); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp_min[i] = (s8) val; + } + + val = i2c_smbus_read_byte_data(client, MAX1668_REG_STAT1); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->alarms = val << 8; + + val = i2c_smbus_read_byte_data(client, MAX1668_REG_STAT2); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->alarms |= val; + + data->last_updated = jiffies; + data->valid = 1; +abort: + mutex_unlock(&data->update_lock); + + return ret; +} + +static ssize_t show_temp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct max1668_data *data = max1668_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->temp[index] * 1000); +} + +static ssize_t show_temp_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct max1668_data *data = max1668_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->temp_max[index] * 1000); +} + +static ssize_t show_temp_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct max1668_data *data = max1668_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->temp_min[index] * 1000); +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + struct max1668_data *data = max1668_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%u\n", (data->alarms >> index) & 0x1); +} + +static ssize_t show_fault(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct max1668_data *data = max1668_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%u\n", + (data->alarms & (1 << 12)) && data->temp[index] == 127); +} + +static ssize_t set_temp_max(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct max1668_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + int ret; + + ret = kstrtol(buf, 10, &temp); + if (ret < 0) + return ret; + + mutex_lock(&data->update_lock); + data->temp_max[index] = clamp_val(temp/1000, -128, 127); + ret = i2c_smbus_write_byte_data(client, + MAX1668_REG_LIMH_WR(index), + data->temp_max[index]); + if (ret < 0) + count = ret; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t set_temp_min(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct max1668_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long temp; + int ret; + + ret = kstrtol(buf, 10, &temp); + if (ret < 0) + return ret; + + mutex_lock(&data->update_lock); + data->temp_min[index] = clamp_val(temp/1000, -128, 127); + ret = i2c_smbus_write_byte_data(client, + MAX1668_REG_LIML_WR(index), + data->temp_min[index]); + if (ret < 0) + count = ret; + mutex_unlock(&data->update_lock); + + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp_max, + set_temp_max, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO, show_temp_min, + set_temp_min, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO, show_temp_max, + set_temp_max, 1); +static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO, show_temp_min, + set_temp_min, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO, show_temp_max, + set_temp_max, 2); +static SENSOR_DEVICE_ATTR(temp3_min, S_IRUGO, show_temp_min, + set_temp_min, 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_max, S_IRUGO, show_temp_max, + set_temp_max, 3); +static SENSOR_DEVICE_ATTR(temp4_min, S_IRUGO, show_temp_min, + set_temp_min, 3); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp5_max, S_IRUGO, show_temp_max, + set_temp_max, 4); +static SENSOR_DEVICE_ATTR(temp5_min, S_IRUGO, show_temp_min, + set_temp_min, 4); + +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 14); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, 13); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp5_min_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp5_max_alarm, S_IRUGO, show_alarm, NULL, 0); + +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_fault, NULL, 4); + +/* Attributes common to MAX1668, MAX1989 and MAX1805 */ +static struct attribute *max1668_attribute_common[] = { + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + NULL +}; + +/* Attributes not present on MAX1805 */ +static struct attribute *max1668_attribute_unique[] = { + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp5_min.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_min_alarm.dev_attr.attr, + + &sensor_dev_attr_temp4_fault.dev_attr.attr, + &sensor_dev_attr_temp5_fault.dev_attr.attr, + NULL +}; + +static umode_t max1668_attribute_mode(struct kobject *kobj, + struct attribute *attr, int index) +{ + umode_t ret = S_IRUGO; + if (read_only) + return ret; + if (attr == &sensor_dev_attr_temp1_max.dev_attr.attr || + attr == &sensor_dev_attr_temp2_max.dev_attr.attr || + attr == &sensor_dev_attr_temp3_max.dev_attr.attr || + attr == &sensor_dev_attr_temp4_max.dev_attr.attr || + attr == &sensor_dev_attr_temp5_max.dev_attr.attr || + attr == &sensor_dev_attr_temp1_min.dev_attr.attr || + attr == &sensor_dev_attr_temp2_min.dev_attr.attr || + attr == &sensor_dev_attr_temp3_min.dev_attr.attr || + attr == &sensor_dev_attr_temp4_min.dev_attr.attr || + attr == &sensor_dev_attr_temp5_min.dev_attr.attr) + ret |= S_IWUSR; + return ret; +} + +static const struct attribute_group max1668_group_common = { + .attrs = max1668_attribute_common, + .is_visible = max1668_attribute_mode +}; + +static const struct attribute_group max1668_group_unique = { + .attrs = max1668_attribute_unique, + .is_visible = max1668_attribute_mode +}; + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int max1668_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + const char *type_name; + int man_id, dev_id; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* Check for unsupported part */ + man_id = i2c_smbus_read_byte_data(client, MAX1668_REG_MAN_ID); + if (man_id != MAN_ID_MAXIM) + return -ENODEV; + + dev_id = i2c_smbus_read_byte_data(client, MAX1668_REG_DEV_ID); + if (dev_id < 0) + return -ENODEV; + + type_name = NULL; + if (dev_id == DEV_ID_MAX1668) + type_name = "max1668"; + else if (dev_id == DEV_ID_MAX1805) + type_name = "max1805"; + else if (dev_id == DEV_ID_MAX1989) + type_name = "max1989"; + + if (!type_name) + return -ENODEV; + + strlcpy(info->type, type_name, I2C_NAME_SIZE); + + return 0; +} + +static int max1668_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct max1668_data *data; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(struct max1668_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + data->type = id->driver_data; + mutex_init(&data->update_lock); + + /* sysfs hooks */ + data->groups[0] = &max1668_group_common; + if (data->type == max1668 || data->type == max1989) + data->groups[1] = &max1668_group_unique; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id max1668_id[] = { + { "max1668", max1668 }, + { "max1805", max1805 }, + { "max1989", max1989 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max1668_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max1668_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "max1668", + }, + .probe = max1668_probe, + .id_table = max1668_id, + .detect = max1668_detect, + .address_list = max1668_addr_list, +}; + +module_i2c_driver(max1668_driver); + +MODULE_AUTHOR("David George "); +MODULE_DESCRIPTION("MAX1668 remote temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/max197.c b/drivers/hwmon/max197.c new file mode 100644 index 000000000..3d9e210be --- /dev/null +++ b/drivers/hwmon/max197.c @@ -0,0 +1,347 @@ +/* + * Maxim MAX197 A/D Converter driver + * + * Copyright (c) 2012 Savoir-faire Linux Inc. + * Vivien Didelot + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * For further information, see the Documentation/hwmon/max197 file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX199_LIMIT 4000 /* 4V */ +#define MAX197_LIMIT 10000 /* 10V */ + +#define MAX197_NUM_CH 8 /* 8 Analog Input Channels */ + +/* Control byte format */ +#define MAX197_BIP (1 << 3) /* Bipolarity */ +#define MAX197_RNG (1 << 4) /* Full range */ + +#define MAX197_SCALE 12207 /* Scale coefficient for raw data */ + +/* List of supported chips */ +enum max197_chips { max197, max199 }; + +/** + * struct max197_data - device instance specific data + * @pdata: Platform data. + * @hwmon_dev: The hwmon device. + * @lock: Read/Write mutex. + * @limit: Max range value (10V for MAX197, 4V for MAX199). + * @scale: Need to scale. + * @ctrl_bytes: Channels control byte. + */ +struct max197_data { + struct max197_platform_data *pdata; + struct device *hwmon_dev; + struct mutex lock; + int limit; + bool scale; + u8 ctrl_bytes[MAX197_NUM_CH]; +}; + +static inline void max197_set_unipolarity(struct max197_data *data, int channel) +{ + data->ctrl_bytes[channel] &= ~MAX197_BIP; +} + +static inline void max197_set_bipolarity(struct max197_data *data, int channel) +{ + data->ctrl_bytes[channel] |= MAX197_BIP; +} + +static inline void max197_set_half_range(struct max197_data *data, int channel) +{ + data->ctrl_bytes[channel] &= ~MAX197_RNG; +} + +static inline void max197_set_full_range(struct max197_data *data, int channel) +{ + data->ctrl_bytes[channel] |= MAX197_RNG; +} + +static inline bool max197_is_bipolar(struct max197_data *data, int channel) +{ + return data->ctrl_bytes[channel] & MAX197_BIP; +} + +static inline bool max197_is_full_range(struct max197_data *data, int channel) +{ + return data->ctrl_bytes[channel] & MAX197_RNG; +} + +/* Function called on read access on in{0,1,2,3,4,5,6,7}_{min,max} */ +static ssize_t max197_show_range(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct max197_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + int channel = attr->index; + bool is_min = attr->nr; + int range; + + if (mutex_lock_interruptible(&data->lock)) + return -ERESTARTSYS; + + range = max197_is_full_range(data, channel) ? + data->limit : data->limit / 2; + if (is_min) { + if (max197_is_bipolar(data, channel)) + range = -range; + else + range = 0; + } + + mutex_unlock(&data->lock); + + return sprintf(buf, "%d\n", range); +} + +/* Function called on write access on in{0,1,2,3,4,5,6,7}_{min,max} */ +static ssize_t max197_store_range(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct max197_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + int channel = attr->index; + bool is_min = attr->nr; + long value; + int half = data->limit / 2; + int full = data->limit; + + if (kstrtol(buf, 10, &value)) + return -EINVAL; + + if (is_min) { + if (value <= -full) + value = -full; + else if (value < 0) + value = -half; + else + value = 0; + } else { + if (value >= full) + value = full; + else + value = half; + } + + if (mutex_lock_interruptible(&data->lock)) + return -ERESTARTSYS; + + if (value == 0) { + /* We can deduce only the polarity */ + max197_set_unipolarity(data, channel); + } else if (value == -half) { + max197_set_bipolarity(data, channel); + max197_set_half_range(data, channel); + } else if (value == -full) { + max197_set_bipolarity(data, channel); + max197_set_full_range(data, channel); + } else if (value == half) { + /* We can deduce only the range */ + max197_set_half_range(data, channel); + } else if (value == full) { + /* We can deduce only the range */ + max197_set_full_range(data, channel); + } + + mutex_unlock(&data->lock); + + return count; +} + +/* Function called on read access on in{0,1,2,3,4,5,6,7}_input */ +static ssize_t max197_show_input(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct max197_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int channel = attr->index; + s32 value; + int ret; + + if (mutex_lock_interruptible(&data->lock)) + return -ERESTARTSYS; + + ret = data->pdata->convert(data->ctrl_bytes[channel]); + if (ret < 0) { + dev_err(dev, "conversion failed\n"); + goto unlock; + } + value = ret; + + /* + * Coefficient to apply on raw value. + * See Table 1. Full Scale and Zero Scale in the MAX197 datasheet. + */ + if (data->scale) { + value *= MAX197_SCALE; + if (max197_is_full_range(data, channel)) + value *= 2; + value /= 10000; + } + + ret = sprintf(buf, "%d\n", value); + +unlock: + mutex_unlock(&data->lock); + return ret; +} + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + return sprintf(buf, "%s\n", pdev->name); +} + +#define MAX197_SENSOR_DEVICE_ATTR_CH(chan) \ + static SENSOR_DEVICE_ATTR(in##chan##_input, S_IRUGO, \ + max197_show_input, NULL, chan); \ + static SENSOR_DEVICE_ATTR_2(in##chan##_min, S_IRUGO | S_IWUSR, \ + max197_show_range, \ + max197_store_range, \ + true, chan); \ + static SENSOR_DEVICE_ATTR_2(in##chan##_max, S_IRUGO | S_IWUSR, \ + max197_show_range, \ + max197_store_range, \ + false, chan) + +#define MAX197_SENSOR_DEV_ATTR_IN(chan) \ + &sensor_dev_attr_in##chan##_input.dev_attr.attr, \ + &sensor_dev_attr_in##chan##_max.dev_attr.attr, \ + &sensor_dev_attr_in##chan##_min.dev_attr.attr + +static DEVICE_ATTR_RO(name); + +MAX197_SENSOR_DEVICE_ATTR_CH(0); +MAX197_SENSOR_DEVICE_ATTR_CH(1); +MAX197_SENSOR_DEVICE_ATTR_CH(2); +MAX197_SENSOR_DEVICE_ATTR_CH(3); +MAX197_SENSOR_DEVICE_ATTR_CH(4); +MAX197_SENSOR_DEVICE_ATTR_CH(5); +MAX197_SENSOR_DEVICE_ATTR_CH(6); +MAX197_SENSOR_DEVICE_ATTR_CH(7); + +static const struct attribute_group max197_sysfs_group = { + .attrs = (struct attribute *[]) { + &dev_attr_name.attr, + MAX197_SENSOR_DEV_ATTR_IN(0), + MAX197_SENSOR_DEV_ATTR_IN(1), + MAX197_SENSOR_DEV_ATTR_IN(2), + MAX197_SENSOR_DEV_ATTR_IN(3), + MAX197_SENSOR_DEV_ATTR_IN(4), + MAX197_SENSOR_DEV_ATTR_IN(5), + MAX197_SENSOR_DEV_ATTR_IN(6), + MAX197_SENSOR_DEV_ATTR_IN(7), + NULL + }, +}; + +static int max197_probe(struct platform_device *pdev) +{ + int ch, ret; + struct max197_data *data; + struct max197_platform_data *pdata = dev_get_platdata(&pdev->dev); + enum max197_chips chip = platform_get_device_id(pdev)->driver_data; + + if (pdata == NULL) { + dev_err(&pdev->dev, "no platform data supplied\n"); + return -EINVAL; + } + + if (pdata->convert == NULL) { + dev_err(&pdev->dev, "no convert function supplied\n"); + return -EINVAL; + } + + data = devm_kzalloc(&pdev->dev, sizeof(struct max197_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->pdata = pdata; + mutex_init(&data->lock); + + if (chip == max197) { + data->limit = MAX197_LIMIT; + data->scale = true; + } else { + data->limit = MAX199_LIMIT; + data->scale = false; + } + + for (ch = 0; ch < MAX197_NUM_CH; ch++) + data->ctrl_bytes[ch] = (u8) ch; + + platform_set_drvdata(pdev, data); + + ret = sysfs_create_group(&pdev->dev.kobj, &max197_sysfs_group); + if (ret) { + dev_err(&pdev->dev, "sysfs create group failed\n"); + return ret; + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "hwmon device register failed\n"); + goto error; + } + + return 0; + +error: + sysfs_remove_group(&pdev->dev.kobj, &max197_sysfs_group); + return ret; +} + +static int max197_remove(struct platform_device *pdev) +{ + struct max197_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &max197_sysfs_group); + + return 0; +} + +static const struct platform_device_id max197_device_ids[] = { + { "max197", max197 }, + { "max199", max199 }, + { } +}; +MODULE_DEVICE_TABLE(platform, max197_device_ids); + +static struct platform_driver max197_driver = { + .driver = { + .name = "max197", + }, + .probe = max197_probe, + .remove = max197_remove, + .id_table = max197_device_ids, +}; +module_platform_driver(max197_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Savoir-faire Linux Inc. "); +MODULE_DESCRIPTION("Maxim MAX197 A/D Converter driver"); diff --git a/drivers/hwmon/max31722.c b/drivers/hwmon/max31722.c new file mode 100644 index 000000000..877c3d7dc --- /dev/null +++ b/drivers/hwmon/max31722.c @@ -0,0 +1,156 @@ +/* + * max31722 - hwmon driver for Maxim Integrated MAX31722/MAX31723 SPI + * digital thermometer and thermostats. + * + * Copyright (c) 2016, Intel Corporation. + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + */ + +#include +#include +#include +#include +#include + +#define MAX31722_REG_CFG 0x00 +#define MAX31722_REG_TEMP_LSB 0x01 + +#define MAX31722_MODE_CONTINUOUS 0x00 +#define MAX31722_MODE_STANDBY 0x01 +#define MAX31722_MODE_MASK 0xFE +#define MAX31722_RESOLUTION_12BIT 0x06 +#define MAX31722_WRITE_MASK 0x80 + +struct max31722_data { + struct device *hwmon_dev; + struct spi_device *spi_device; + u8 mode; +}; + +static int max31722_set_mode(struct max31722_data *data, u8 mode) +{ + int ret; + struct spi_device *spi = data->spi_device; + u8 buf[2] = { + MAX31722_REG_CFG | MAX31722_WRITE_MASK, + (data->mode & MAX31722_MODE_MASK) | mode + }; + + ret = spi_write(spi, &buf, sizeof(buf)); + if (ret < 0) { + dev_err(&spi->dev, "failed to set sensor mode.\n"); + return ret; + } + data->mode = (data->mode & MAX31722_MODE_MASK) | mode; + + return 0; +} + +static ssize_t max31722_show_temp(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t ret; + struct max31722_data *data = dev_get_drvdata(dev); + + ret = spi_w8r16(data->spi_device, MAX31722_REG_TEMP_LSB); + if (ret < 0) + return ret; + /* Keep 12 bits and multiply by the scale of 62.5 millidegrees/bit. */ + return sprintf(buf, "%d\n", (s16)le16_to_cpu(ret) * 125 / 32); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, + max31722_show_temp, NULL, 0); + +static struct attribute *max31722_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(max31722); + +static int max31722_probe(struct spi_device *spi) +{ + int ret; + struct max31722_data *data; + + data = devm_kzalloc(&spi->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + spi_set_drvdata(spi, data); + data->spi_device = spi; + /* + * Set SD bit to 0 so we can have continuous measurements. + * Set resolution to 12 bits for maximum precision. + */ + data->mode = MAX31722_MODE_CONTINUOUS | MAX31722_RESOLUTION_12BIT; + ret = max31722_set_mode(data, MAX31722_MODE_CONTINUOUS); + if (ret < 0) + return ret; + + data->hwmon_dev = hwmon_device_register_with_groups(&spi->dev, + spi->modalias, + data, + max31722_groups); + if (IS_ERR(data->hwmon_dev)) { + max31722_set_mode(data, MAX31722_MODE_STANDBY); + return PTR_ERR(data->hwmon_dev); + } + + return 0; +} + +static int max31722_remove(struct spi_device *spi) +{ + struct max31722_data *data = spi_get_drvdata(spi); + + hwmon_device_unregister(data->hwmon_dev); + + return max31722_set_mode(data, MAX31722_MODE_STANDBY); +} + +static int __maybe_unused max31722_suspend(struct device *dev) +{ + struct spi_device *spi_device = to_spi_device(dev); + struct max31722_data *data = spi_get_drvdata(spi_device); + + return max31722_set_mode(data, MAX31722_MODE_STANDBY); +} + +static int __maybe_unused max31722_resume(struct device *dev) +{ + struct spi_device *spi_device = to_spi_device(dev); + struct max31722_data *data = spi_get_drvdata(spi_device); + + return max31722_set_mode(data, MAX31722_MODE_CONTINUOUS); +} + +static SIMPLE_DEV_PM_OPS(max31722_pm_ops, max31722_suspend, max31722_resume); + +static const struct spi_device_id max31722_spi_id[] = { + {"max31722", 0}, + {"max31723", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, max31722_spi_id); + +static struct spi_driver max31722_driver = { + .driver = { + .name = "max31722", + .pm = &max31722_pm_ops, + }, + .probe = max31722_probe, + .remove = max31722_remove, + .id_table = max31722_spi_id, +}; + +module_spi_driver(max31722_driver); + +MODULE_AUTHOR("Tiberiu Breana "); +MODULE_DESCRIPTION("max31722 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/max31790.c b/drivers/hwmon/max31790.c new file mode 100644 index 000000000..66cf772de --- /dev/null +++ b/drivers/hwmon/max31790.c @@ -0,0 +1,532 @@ +/* + * max31790.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring. + * + * (C) 2015 by Il Han + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* MAX31790 registers */ +#define MAX31790_REG_GLOBAL_CONFIG 0x00 +#define MAX31790_REG_FAN_CONFIG(ch) (0x02 + (ch)) +#define MAX31790_REG_FAN_DYNAMICS(ch) (0x08 + (ch)) +#define MAX31790_REG_FAN_FAULT_STATUS2 0x10 +#define MAX31790_REG_FAN_FAULT_STATUS1 0x11 +#define MAX31790_REG_TACH_COUNT(ch) (0x18 + (ch) * 2) +#define MAX31790_REG_PWM_DUTY_CYCLE(ch) (0x30 + (ch) * 2) +#define MAX31790_REG_PWMOUT(ch) (0x40 + (ch) * 2) +#define MAX31790_REG_TARGET_COUNT(ch) (0x50 + (ch) * 2) + +/* Fan Config register bits */ +#define MAX31790_FAN_CFG_RPM_MODE 0x80 +#define MAX31790_FAN_CFG_TACH_INPUT_EN 0x08 +#define MAX31790_FAN_CFG_TACH_INPUT 0x01 + +/* Fan Dynamics register bits */ +#define MAX31790_FAN_DYN_SR_SHIFT 5 +#define MAX31790_FAN_DYN_SR_MASK 0xE0 +#define SR_FROM_REG(reg) (((reg) & MAX31790_FAN_DYN_SR_MASK) \ + >> MAX31790_FAN_DYN_SR_SHIFT) + +#define FAN_RPM_MIN 120 +#define FAN_RPM_MAX 7864320 + +#define RPM_FROM_REG(reg, sr) (((reg) >> 4) ? \ + ((60 * (sr) * 8192) / ((reg) >> 4)) : \ + FAN_RPM_MAX) +#define RPM_TO_REG(rpm, sr) ((60 * (sr) * 8192) / ((rpm) * 2)) + +#define NR_CHANNEL 6 + +/* + * Client data (each client gets its own) + */ +struct max31790_data { + struct i2c_client *client; + struct mutex update_lock; + bool valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* register values */ + u8 fan_config[NR_CHANNEL]; + u8 fan_dynamics[NR_CHANNEL]; + u16 fault_status; + u16 tach[NR_CHANNEL * 2]; + u16 pwm[NR_CHANNEL]; + u16 target_count[NR_CHANNEL]; +}; + +static struct max31790_data *max31790_update_device(struct device *dev) +{ + struct max31790_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + struct max31790_data *ret = data; + int i; + int rv; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + rv = i2c_smbus_read_byte_data(client, + MAX31790_REG_FAN_FAULT_STATUS1); + if (rv < 0) + goto abort; + data->fault_status = rv & 0x3F; + + rv = i2c_smbus_read_byte_data(client, + MAX31790_REG_FAN_FAULT_STATUS2); + if (rv < 0) + goto abort; + data->fault_status |= (rv & 0x3F) << 6; + + for (i = 0; i < NR_CHANNEL; i++) { + rv = i2c_smbus_read_word_swapped(client, + MAX31790_REG_TACH_COUNT(i)); + if (rv < 0) + goto abort; + data->tach[i] = rv; + + if (data->fan_config[i] + & MAX31790_FAN_CFG_TACH_INPUT) { + rv = i2c_smbus_read_word_swapped(client, + MAX31790_REG_TACH_COUNT(NR_CHANNEL + + i)); + if (rv < 0) + goto abort; + data->tach[NR_CHANNEL + i] = rv; + } else { + rv = i2c_smbus_read_word_swapped(client, + MAX31790_REG_PWMOUT(i)); + if (rv < 0) + goto abort; + data->pwm[i] = rv; + + rv = i2c_smbus_read_word_swapped(client, + MAX31790_REG_TARGET_COUNT(i)); + if (rv < 0) + goto abort; + data->target_count[i] = rv; + } + } + + data->last_updated = jiffies; + data->valid = true; + } + goto done; + +abort: + data->valid = false; + ret = ERR_PTR(rv); + +done: + mutex_unlock(&data->update_lock); + + return ret; +} + +static const u8 tach_period[8] = { 1, 2, 4, 8, 16, 32, 32, 32 }; + +static u8 get_tach_period(u8 fan_dynamics) +{ + return tach_period[SR_FROM_REG(fan_dynamics)]; +} + +static u8 bits_for_tach_period(int rpm) +{ + u8 bits; + + if (rpm < 500) + bits = 0x0; + else if (rpm < 1000) + bits = 0x1; + else if (rpm < 2000) + bits = 0x2; + else if (rpm < 4000) + bits = 0x3; + else if (rpm < 8000) + bits = 0x4; + else + bits = 0x5; + + return bits; +} + +static int max31790_read_fan(struct device *dev, u32 attr, int channel, + long *val) +{ + struct max31790_data *data = max31790_update_device(dev); + int sr, rpm; + + if (IS_ERR(data)) + return PTR_ERR(data); + + switch (attr) { + case hwmon_fan_input: + sr = get_tach_period(data->fan_dynamics[channel % NR_CHANNEL]); + rpm = RPM_FROM_REG(data->tach[channel], sr); + *val = rpm; + return 0; + case hwmon_fan_target: + sr = get_tach_period(data->fan_dynamics[channel]); + rpm = RPM_FROM_REG(data->target_count[channel], sr); + *val = rpm; + return 0; + case hwmon_fan_fault: + *val = !!(data->fault_status & (1 << channel)); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int max31790_write_fan(struct device *dev, u32 attr, int channel, + long val) +{ + struct max31790_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int target_count; + int err = 0; + u8 bits; + int sr; + + mutex_lock(&data->update_lock); + + switch (attr) { + case hwmon_fan_target: + val = clamp_val(val, FAN_RPM_MIN, FAN_RPM_MAX); + bits = bits_for_tach_period(val); + data->fan_dynamics[channel] = + ((data->fan_dynamics[channel] & + ~MAX31790_FAN_DYN_SR_MASK) | + (bits << MAX31790_FAN_DYN_SR_SHIFT)); + err = i2c_smbus_write_byte_data(client, + MAX31790_REG_FAN_DYNAMICS(channel), + data->fan_dynamics[channel]); + if (err < 0) + break; + + sr = get_tach_period(data->fan_dynamics[channel]); + target_count = RPM_TO_REG(val, sr); + target_count = clamp_val(target_count, 0x1, 0x7FF); + + data->target_count[channel] = target_count << 5; + + err = i2c_smbus_write_word_swapped(client, + MAX31790_REG_TARGET_COUNT(channel), + data->target_count[channel]); + break; + default: + err = -EOPNOTSUPP; + break; + } + + mutex_unlock(&data->update_lock); + + return err; +} + +static umode_t max31790_fan_is_visible(const void *_data, u32 attr, int channel) +{ + const struct max31790_data *data = _data; + u8 fan_config = data->fan_config[channel % NR_CHANNEL]; + + switch (attr) { + case hwmon_fan_input: + case hwmon_fan_fault: + if (channel < NR_CHANNEL || + (fan_config & MAX31790_FAN_CFG_TACH_INPUT)) + return S_IRUGO; + return 0; + case hwmon_fan_target: + if (channel < NR_CHANNEL && + !(fan_config & MAX31790_FAN_CFG_TACH_INPUT)) + return S_IRUGO | S_IWUSR; + return 0; + default: + return 0; + } +} + +static int max31790_read_pwm(struct device *dev, u32 attr, int channel, + long *val) +{ + struct max31790_data *data = max31790_update_device(dev); + u8 fan_config; + + if (IS_ERR(data)) + return PTR_ERR(data); + + fan_config = data->fan_config[channel]; + + switch (attr) { + case hwmon_pwm_input: + *val = data->pwm[channel] >> 8; + return 0; + case hwmon_pwm_enable: + if (fan_config & MAX31790_FAN_CFG_RPM_MODE) + *val = 2; + else if (fan_config & MAX31790_FAN_CFG_TACH_INPUT_EN) + *val = 1; + else + *val = 0; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int max31790_write_pwm(struct device *dev, u32 attr, int channel, + long val) +{ + struct max31790_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 fan_config; + int err = 0; + + mutex_lock(&data->update_lock); + + switch (attr) { + case hwmon_pwm_input: + if (val < 0 || val > 255) { + err = -EINVAL; + break; + } + data->pwm[channel] = val << 8; + err = i2c_smbus_write_word_swapped(client, + MAX31790_REG_PWMOUT(channel), + data->pwm[channel]); + break; + case hwmon_pwm_enable: + fan_config = data->fan_config[channel]; + if (val == 0) { + fan_config &= ~(MAX31790_FAN_CFG_TACH_INPUT_EN | + MAX31790_FAN_CFG_RPM_MODE); + } else if (val == 1) { + fan_config = (fan_config | + MAX31790_FAN_CFG_TACH_INPUT_EN) & + ~MAX31790_FAN_CFG_RPM_MODE; + } else if (val == 2) { + fan_config |= MAX31790_FAN_CFG_TACH_INPUT_EN | + MAX31790_FAN_CFG_RPM_MODE; + } else { + err = -EINVAL; + break; + } + data->fan_config[channel] = fan_config; + err = i2c_smbus_write_byte_data(client, + MAX31790_REG_FAN_CONFIG(channel), + fan_config); + break; + default: + err = -EOPNOTSUPP; + break; + } + + mutex_unlock(&data->update_lock); + + return err; +} + +static umode_t max31790_pwm_is_visible(const void *_data, u32 attr, int channel) +{ + const struct max31790_data *data = _data; + u8 fan_config = data->fan_config[channel]; + + switch (attr) { + case hwmon_pwm_input: + case hwmon_pwm_enable: + if (!(fan_config & MAX31790_FAN_CFG_TACH_INPUT)) + return S_IRUGO | S_IWUSR; + return 0; + default: + return 0; + } +} + +static int max31790_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_fan: + return max31790_read_fan(dev, attr, channel, val); + case hwmon_pwm: + return max31790_read_pwm(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int max31790_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_fan: + return max31790_write_fan(dev, attr, channel, val); + case hwmon_pwm: + return max31790_write_pwm(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t max31790_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_fan: + return max31790_fan_is_visible(data, attr, channel); + case hwmon_pwm: + return max31790_pwm_is_visible(data, attr, channel); + default: + return 0; + } +} + +static const u32 max31790_fan_config[] = { + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + 0 +}; + +static const struct hwmon_channel_info max31790_fan = { + .type = hwmon_fan, + .config = max31790_fan_config, +}; + +static const u32 max31790_pwm_config[] = { + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + 0 +}; + +static const struct hwmon_channel_info max31790_pwm = { + .type = hwmon_pwm, + .config = max31790_pwm_config, +}; + +static const struct hwmon_channel_info *max31790_info[] = { + &max31790_fan, + &max31790_pwm, + NULL +}; + +static const struct hwmon_ops max31790_hwmon_ops = { + .is_visible = max31790_is_visible, + .read = max31790_read, + .write = max31790_write, +}; + +static const struct hwmon_chip_info max31790_chip_info = { + .ops = &max31790_hwmon_ops, + .info = max31790_info, +}; + +static int max31790_init_client(struct i2c_client *client, + struct max31790_data *data) +{ + int i, rv; + + for (i = 0; i < NR_CHANNEL; i++) { + rv = i2c_smbus_read_byte_data(client, + MAX31790_REG_FAN_CONFIG(i)); + if (rv < 0) + return rv; + data->fan_config[i] = rv; + + rv = i2c_smbus_read_byte_data(client, + MAX31790_REG_FAN_DYNAMICS(i)); + if (rv < 0) + return rv; + data->fan_dynamics[i] = rv; + } + + return 0; +} + +static int max31790_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct device *dev = &client->dev; + struct max31790_data *data; + struct device *hwmon_dev; + int err; + + if (!i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(struct max31790_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* + * Initialize the max31790 chip + */ + err = max31790_init_client(client, data); + if (err) + return err; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, + &max31790_chip_info, + NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id max31790_id[] = { + { "max31790", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max31790_id); + +static struct i2c_driver max31790_driver = { + .class = I2C_CLASS_HWMON, + .probe = max31790_probe, + .driver = { + .name = "max31790", + }, + .id_table = max31790_id, +}; + +module_i2c_driver(max31790_driver); + +MODULE_AUTHOR("Il Han "); +MODULE_DESCRIPTION("MAX31790 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/max6621.c b/drivers/hwmon/max6621.c new file mode 100644 index 000000000..35555f0ee --- /dev/null +++ b/drivers/hwmon/max6621.c @@ -0,0 +1,593 @@ +/* + * Hardware monitoring driver for Maxim MAX6621 + * + * Copyright (c) 2017 Mellanox Technologies. All rights reserved. + * Copyright (c) 2017 Vadim Pasternak + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX6621_DRV_NAME "max6621" +#define MAX6621_TEMP_INPUT_REG_NUM 9 +#define MAX6621_TEMP_INPUT_MIN -127000 +#define MAX6621_TEMP_INPUT_MAX 128000 +#define MAX6621_TEMP_ALERT_CHAN_SHIFT 1 + +#define MAX6621_TEMP_S0D0_REG 0x00 +#define MAX6621_TEMP_S0D1_REG 0x01 +#define MAX6621_TEMP_S1D0_REG 0x02 +#define MAX6621_TEMP_S1D1_REG 0x03 +#define MAX6621_TEMP_S2D0_REG 0x04 +#define MAX6621_TEMP_S2D1_REG 0x05 +#define MAX6621_TEMP_S3D0_REG 0x06 +#define MAX6621_TEMP_S3D1_REG 0x07 +#define MAX6621_TEMP_MAX_REG 0x08 +#define MAX6621_TEMP_MAX_ADDR_REG 0x0a +#define MAX6621_TEMP_ALERT_CAUSE_REG 0x0b +#define MAX6621_CONFIG0_REG 0x0c +#define MAX6621_CONFIG1_REG 0x0d +#define MAX6621_CONFIG2_REG 0x0e +#define MAX6621_CONFIG3_REG 0x0f +#define MAX6621_TEMP_S0_ALERT_REG 0x10 +#define MAX6621_TEMP_S1_ALERT_REG 0x11 +#define MAX6621_TEMP_S2_ALERT_REG 0x12 +#define MAX6621_TEMP_S3_ALERT_REG 0x13 +#define MAX6621_CLEAR_ALERT_REG 0x15 +#define MAX6621_REG_MAX (MAX6621_CLEAR_ALERT_REG + 1) +#define MAX6621_REG_TEMP_SHIFT 0x06 + +#define MAX6621_ENABLE_TEMP_ALERTS_BIT 4 +#define MAX6621_ENABLE_I2C_CRC_BIT 5 +#define MAX6621_ENABLE_ALTERNATE_DATA 6 +#define MAX6621_ENABLE_LOCKUP_TO 7 +#define MAX6621_ENABLE_S0D0_BIT 8 +#define MAX6621_ENABLE_S3D1_BIT 15 +#define MAX6621_ENABLE_TEMP_ALL GENMASK(MAX6621_ENABLE_S3D1_BIT, \ + MAX6621_ENABLE_S0D0_BIT) +#define MAX6621_POLL_DELAY_MASK 0x5 +#define MAX6621_CONFIG0_INIT (MAX6621_ENABLE_TEMP_ALL | \ + BIT(MAX6621_ENABLE_LOCKUP_TO) | \ + BIT(MAX6621_ENABLE_I2C_CRC_BIT) | \ + MAX6621_POLL_DELAY_MASK) +#define MAX6621_PECI_BIT_TIME 0x2 +#define MAX6621_PECI_RETRY_NUM 0x3 +#define MAX6621_CONFIG1_INIT ((MAX6621_PECI_BIT_TIME << 8) | \ + MAX6621_PECI_RETRY_NUM) + +/* Error codes */ +#define MAX6621_TRAN_FAILED 0x8100 /* + * PECI transaction failed for more + * than the configured number of + * consecutive retries. + */ +#define MAX6621_POOL_DIS 0x8101 /* + * Polling disabled for requested + * socket/domain. + */ +#define MAX6621_POOL_UNCOMPLETE 0x8102 /* + * First poll not yet completed for + * requested socket/domain (on + * startup). + */ +#define MAX6621_SD_DIS 0x8103 /* + * Read maximum temperature requested, + * but no sockets/domains enabled or + * all enabled sockets/domains have + * errors; or read maximum temperature + * address requested, but read maximum + * temperature was not called. + */ +#define MAX6621_ALERT_DIS 0x8104 /* + * Get alert socket/domain requested, + * but no alert active. + */ +#define MAX6621_PECI_ERR_MIN 0x8000 /* Intel spec PECI error min value. */ +#define MAX6621_PECI_ERR_MAX 0x80ff /* Intel spec PECI error max value. */ + +static const u32 max6621_temp_regs[] = { + MAX6621_TEMP_MAX_REG, MAX6621_TEMP_S0D0_REG, MAX6621_TEMP_S1D0_REG, + MAX6621_TEMP_S2D0_REG, MAX6621_TEMP_S3D0_REG, MAX6621_TEMP_S0D1_REG, + MAX6621_TEMP_S1D1_REG, MAX6621_TEMP_S2D1_REG, MAX6621_TEMP_S3D1_REG, +}; + +static const char *const max6621_temp_labels[] = { + "maximum", + "socket0_0", + "socket1_0", + "socket2_0", + "socket3_0", + "socket0_1", + "socket1_1", + "socket2_1", + "socket3_1", +}; + +static const int max6621_temp_alert_chan2reg[] = { + MAX6621_TEMP_S0_ALERT_REG, + MAX6621_TEMP_S1_ALERT_REG, + MAX6621_TEMP_S2_ALERT_REG, + MAX6621_TEMP_S3_ALERT_REG, +}; + +/** + * struct max6621_data - private data: + * + * @client: I2C client; + * @regmap: register map handle; + * @input_chan2reg: mapping from channel to register; + */ +struct max6621_data { + struct i2c_client *client; + struct regmap *regmap; + int input_chan2reg[MAX6621_TEMP_INPUT_REG_NUM + 1]; +}; + +static long max6621_temp_mc2reg(long val) +{ + return (val / 1000L) << MAX6621_REG_TEMP_SHIFT; +} + +static umode_t +max6621_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, + int channel) +{ + /* Skip channels which are not physically conncted. */ + if (((struct max6621_data *)data)->input_chan2reg[channel] < 0) + return 0; + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_label: + case hwmon_temp_crit_alarm: + return 0444; + case hwmon_temp_offset: + case hwmon_temp_crit: + return 0644; + default: + break; + } + + default: + break; + } + + return 0; +} + +static int max6621_verify_reg_data(struct device *dev, int regval) +{ + if (regval >= MAX6621_PECI_ERR_MIN && + regval <= MAX6621_PECI_ERR_MAX) { + dev_dbg(dev, "PECI error code - err 0x%04x.\n", + regval); + + return -EIO; + } + + switch (regval) { + case MAX6621_TRAN_FAILED: + dev_dbg(dev, "PECI transaction failed - err 0x%04x.\n", + regval); + return -EIO; + case MAX6621_POOL_DIS: + dev_dbg(dev, "Polling disabled - err 0x%04x.\n", regval); + return -EOPNOTSUPP; + case MAX6621_POOL_UNCOMPLETE: + dev_dbg(dev, "First poll not completed on startup - err 0x%04x.\n", + regval); + return -EIO; + case MAX6621_SD_DIS: + dev_dbg(dev, "Resource is disabled - err 0x%04x.\n", regval); + return -EOPNOTSUPP; + case MAX6621_ALERT_DIS: + dev_dbg(dev, "No alert active - err 0x%04x.\n", regval); + return -EOPNOTSUPP; + default: + return 0; + } +} + +static int +max6621_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct max6621_data *data = dev_get_drvdata(dev); + u32 regval; + int reg; + s8 temp; + int ret; + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + reg = data->input_chan2reg[channel]; + ret = regmap_read(data->regmap, reg, ®val); + if (ret) + return ret; + + ret = max6621_verify_reg_data(dev, regval); + if (ret) + return ret; + + /* + * Bit MAX6621_REG_TEMP_SHIFT represents 1 degree step. + * The temperature is given in two's complement and 8 + * bits is used for the register conversion. + */ + temp = (regval >> MAX6621_REG_TEMP_SHIFT); + *val = temp * 1000L; + + break; + case hwmon_temp_offset: + ret = regmap_read(data->regmap, MAX6621_CONFIG2_REG, + ®val); + if (ret) + return ret; + + ret = max6621_verify_reg_data(dev, regval); + if (ret) + return ret; + + *val = (regval >> MAX6621_REG_TEMP_SHIFT) * + 1000L; + + break; + case hwmon_temp_crit: + channel -= MAX6621_TEMP_ALERT_CHAN_SHIFT; + reg = max6621_temp_alert_chan2reg[channel]; + ret = regmap_read(data->regmap, reg, ®val); + if (ret) + return ret; + + ret = max6621_verify_reg_data(dev, regval); + if (ret) + return ret; + + *val = regval * 1000L; + + break; + case hwmon_temp_crit_alarm: + /* + * Set val to zero to recover the case, when reading + * MAX6621_TEMP_ALERT_CAUSE_REG results in for example + * MAX6621_ALERT_DIS. Reading will return with error, + * but in such case alarm should be returned as 0. + */ + *val = 0; + ret = regmap_read(data->regmap, + MAX6621_TEMP_ALERT_CAUSE_REG, + ®val); + if (ret) + return ret; + + ret = max6621_verify_reg_data(dev, regval); + if (ret) { + /* Do not report error if alert is disabled. */ + if (regval == MAX6621_ALERT_DIS) + return 0; + else + return ret; + } + + /* + * Clear the alert automatically, using send-byte + * smbus protocol for clearing alert. + */ + if (regval) { + ret = i2c_smbus_write_byte(data->client, + MAX6621_CLEAR_ALERT_REG); + if (ret) + return ret; + } + + *val = !!regval; + + break; + default: + return -EOPNOTSUPP; + } + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int +max6621_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long val) +{ + struct max6621_data *data = dev_get_drvdata(dev); + u32 reg; + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_offset: + /* Clamp to allowed range to prevent overflow. */ + val = clamp_val(val, MAX6621_TEMP_INPUT_MIN, + MAX6621_TEMP_INPUT_MAX); + val = max6621_temp_mc2reg(val); + + return regmap_write(data->regmap, + MAX6621_CONFIG2_REG, val); + case hwmon_temp_crit: + channel -= MAX6621_TEMP_ALERT_CHAN_SHIFT; + reg = max6621_temp_alert_chan2reg[channel]; + /* Clamp to allowed range to prevent overflow. */ + val = clamp_val(val, MAX6621_TEMP_INPUT_MIN, + MAX6621_TEMP_INPUT_MAX); + val = val / 1000L; + + return regmap_write(data->regmap, reg, val); + default: + return -EOPNOTSUPP; + } + break; + + default: + return -EOPNOTSUPP; + } + + return -EOPNOTSUPP; +} + +static int +max6621_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_label: + *str = max6621_temp_labels[channel]; + return 0; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return -EOPNOTSUPP; +} + +static bool max6621_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX6621_CONFIG0_REG: + case MAX6621_CONFIG1_REG: + case MAX6621_CONFIG2_REG: + case MAX6621_CONFIG3_REG: + case MAX6621_TEMP_S0_ALERT_REG: + case MAX6621_TEMP_S1_ALERT_REG: + case MAX6621_TEMP_S2_ALERT_REG: + case MAX6621_TEMP_S3_ALERT_REG: + case MAX6621_TEMP_ALERT_CAUSE_REG: + return true; + } + return false; +} + +static bool max6621_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX6621_TEMP_S0D0_REG: + case MAX6621_TEMP_S0D1_REG: + case MAX6621_TEMP_S1D0_REG: + case MAX6621_TEMP_S1D1_REG: + case MAX6621_TEMP_S2D0_REG: + case MAX6621_TEMP_S2D1_REG: + case MAX6621_TEMP_S3D0_REG: + case MAX6621_TEMP_S3D1_REG: + case MAX6621_TEMP_MAX_REG: + case MAX6621_TEMP_MAX_ADDR_REG: + case MAX6621_CONFIG0_REG: + case MAX6621_CONFIG1_REG: + case MAX6621_CONFIG2_REG: + case MAX6621_CONFIG3_REG: + case MAX6621_TEMP_S0_ALERT_REG: + case MAX6621_TEMP_S1_ALERT_REG: + case MAX6621_TEMP_S2_ALERT_REG: + case MAX6621_TEMP_S3_ALERT_REG: + return true; + } + return false; +} + +static bool max6621_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX6621_TEMP_S0D0_REG: + case MAX6621_TEMP_S0D1_REG: + case MAX6621_TEMP_S1D0_REG: + case MAX6621_TEMP_S1D1_REG: + case MAX6621_TEMP_S2D0_REG: + case MAX6621_TEMP_S2D1_REG: + case MAX6621_TEMP_S3D0_REG: + case MAX6621_TEMP_S3D1_REG: + case MAX6621_TEMP_MAX_REG: + case MAX6621_TEMP_S0_ALERT_REG: + case MAX6621_TEMP_S1_ALERT_REG: + case MAX6621_TEMP_S2_ALERT_REG: + case MAX6621_TEMP_S3_ALERT_REG: + case MAX6621_TEMP_ALERT_CAUSE_REG: + return true; + } + return false; +} + +static const struct reg_default max6621_regmap_default[] = { + { MAX6621_CONFIG0_REG, MAX6621_CONFIG0_INIT }, + { MAX6621_CONFIG1_REG, MAX6621_CONFIG1_INIT }, +}; + +static const struct regmap_config max6621_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = MAX6621_REG_MAX, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .cache_type = REGCACHE_FLAT, + .writeable_reg = max6621_writeable_reg, + .readable_reg = max6621_readable_reg, + .volatile_reg = max6621_volatile_reg, + .reg_defaults = max6621_regmap_default, + .num_reg_defaults = ARRAY_SIZE(max6621_regmap_default), +}; + +static u32 max6621_chip_config[] = { + HWMON_C_REGISTER_TZ, + 0 +}; + +static const struct hwmon_channel_info max6621_chip = { + .type = hwmon_chip, + .config = max6621_chip_config, +}; + +static const u32 max6621_temp_config[] = { + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_OFFSET, + HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + 0 +}; + +static const struct hwmon_channel_info max6621_temp = { + .type = hwmon_temp, + .config = max6621_temp_config, +}; + +static const struct hwmon_channel_info *max6621_info[] = { + &max6621_chip, + &max6621_temp, + NULL +}; + +static const struct hwmon_ops max6621_hwmon_ops = { + .read = max6621_read, + .write = max6621_write, + .read_string = max6621_read_string, + .is_visible = max6621_is_visible, +}; + +static const struct hwmon_chip_info max6621_chip_info = { + .ops = &max6621_hwmon_ops, + .info = max6621_info, +}; + +static int max6621_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct max6621_data *data; + struct device *hwmon_dev; + int i; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->regmap = devm_regmap_init_i2c(client, &max6621_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + i2c_set_clientdata(client, data); + data->client = client; + + /* Set CONFIG0 register masking temperature alerts and PEC. */ + ret = regmap_write(data->regmap, MAX6621_CONFIG0_REG, + MAX6621_CONFIG0_INIT); + if (ret) + return ret; + + /* Set CONFIG1 register for PEC access retry number. */ + ret = regmap_write(data->regmap, MAX6621_CONFIG1_REG, + MAX6621_CONFIG1_INIT); + if (ret) + return ret; + + /* Sync registers with hardware. */ + regcache_mark_dirty(data->regmap); + ret = regcache_sync(data->regmap); + if (ret) + return ret; + + /* Verify which temperature input registers are enabled. */ + for (i = 0; i < MAX6621_TEMP_INPUT_REG_NUM; i++) { + ret = i2c_smbus_read_word_data(client, max6621_temp_regs[i]); + if (ret < 0) + return ret; + ret = max6621_verify_reg_data(dev, ret); + if (ret) { + data->input_chan2reg[i] = -1; + continue; + } + + data->input_chan2reg[i] = max6621_temp_regs[i]; + } + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, + &max6621_chip_info, + NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id max6621_id[] = { + { MAX6621_DRV_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max6621_id); + +static const struct of_device_id max6621_of_match[] = { + { .compatible = "maxim,max6621" }, + { } +}; +MODULE_DEVICE_TABLE(of, max6621_of_match); + +static struct i2c_driver max6621_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = MAX6621_DRV_NAME, + .of_match_table = of_match_ptr(max6621_of_match), + }, + .probe = max6621_probe, + .id_table = max6621_id, +}; + +module_i2c_driver(max6621_driver); + +MODULE_AUTHOR("Vadim Pasternak "); +MODULE_DESCRIPTION("Driver for Maxim MAX6621"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/max6639.c b/drivers/hwmon/max6639.c new file mode 100644 index 000000000..f98a83c79 --- /dev/null +++ b/drivers/hwmon/max6639.c @@ -0,0 +1,613 @@ +/* + * max6639.c - Support for Maxim MAX6639 + * + * 2-Channel Temperature Monitor with Dual PWM Fan-Speed Controller + * + * Copyright (C) 2010, 2011 Roland Stigge + * + * based on the initial MAX6639 support from semptian.net + * by He Changqing + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2c, 0x2e, 0x2f, I2C_CLIENT_END }; + +/* The MAX6639 registers, valid channel numbers: 0, 1 */ +#define MAX6639_REG_TEMP(ch) (0x00 + (ch)) +#define MAX6639_REG_STATUS 0x02 +#define MAX6639_REG_OUTPUT_MASK 0x03 +#define MAX6639_REG_GCONFIG 0x04 +#define MAX6639_REG_TEMP_EXT(ch) (0x05 + (ch)) +#define MAX6639_REG_ALERT_LIMIT(ch) (0x08 + (ch)) +#define MAX6639_REG_OT_LIMIT(ch) (0x0A + (ch)) +#define MAX6639_REG_THERM_LIMIT(ch) (0x0C + (ch)) +#define MAX6639_REG_FAN_CONFIG1(ch) (0x10 + (ch) * 4) +#define MAX6639_REG_FAN_CONFIG2a(ch) (0x11 + (ch) * 4) +#define MAX6639_REG_FAN_CONFIG2b(ch) (0x12 + (ch) * 4) +#define MAX6639_REG_FAN_CONFIG3(ch) (0x13 + (ch) * 4) +#define MAX6639_REG_FAN_CNT(ch) (0x20 + (ch)) +#define MAX6639_REG_TARGET_CNT(ch) (0x22 + (ch)) +#define MAX6639_REG_FAN_PPR(ch) (0x24 + (ch)) +#define MAX6639_REG_TARGTDUTY(ch) (0x26 + (ch)) +#define MAX6639_REG_FAN_START_TEMP(ch) (0x28 + (ch)) +#define MAX6639_REG_DEVID 0x3D +#define MAX6639_REG_MANUID 0x3E +#define MAX6639_REG_DEVREV 0x3F + +/* Register bits */ +#define MAX6639_GCONFIG_STANDBY 0x80 +#define MAX6639_GCONFIG_POR 0x40 +#define MAX6639_GCONFIG_DISABLE_TIMEOUT 0x20 +#define MAX6639_GCONFIG_CH2_LOCAL 0x10 +#define MAX6639_GCONFIG_PWM_FREQ_HI 0x08 + +#define MAX6639_FAN_CONFIG1_PWM 0x80 + +#define MAX6639_FAN_CONFIG3_THERM_FULL_SPEED 0x40 + +static const int rpm_ranges[] = { 2000, 4000, 8000, 16000 }; + +#define FAN_FROM_REG(val, rpm_range) ((val) == 0 || (val) == 255 ? \ + 0 : (rpm_ranges[rpm_range] * 30) / (val)) +#define TEMP_LIMIT_TO_REG(val) clamp_val((val) / 1000, 0, 255) + +/* + * Client data (each client gets its own) + */ +struct max6639_data { + struct i2c_client *client; + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* Register values sampled regularly */ + u16 temp[2]; /* Temperature, in 1/8 C, 0..255 C */ + bool temp_fault[2]; /* Detected temperature diode failure */ + u8 fan[2]; /* Register value: TACH count for fans >=30 */ + u8 status; /* Detected channel alarms and fan failures */ + + /* Register values only written to */ + u8 pwm[2]; /* Register value: Duty cycle 0..120 */ + u8 temp_therm[2]; /* THERM Temperature, 0..255 C (->_max) */ + u8 temp_alert[2]; /* ALERT Temperature, 0..255 C (->_crit) */ + u8 temp_ot[2]; /* OT Temperature, 0..255 C (->_emergency) */ + + /* Register values initialized only once */ + u8 ppr; /* Pulses per rotation 0..3 for 1..4 ppr */ + u8 rpm_range; /* Index in above rpm_ranges table */ +}; + +static struct max6639_data *max6639_update_device(struct device *dev) +{ + struct max6639_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + struct max6639_data *ret = data; + int i; + int status_reg; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + 2 * HZ) || !data->valid) { + int res; + + dev_dbg(&client->dev, "Starting max6639 update\n"); + + status_reg = i2c_smbus_read_byte_data(client, + MAX6639_REG_STATUS); + if (status_reg < 0) { + ret = ERR_PTR(status_reg); + goto abort; + } + + data->status = status_reg; + + for (i = 0; i < 2; i++) { + res = i2c_smbus_read_byte_data(client, + MAX6639_REG_FAN_CNT(i)); + if (res < 0) { + ret = ERR_PTR(res); + goto abort; + } + data->fan[i] = res; + + res = i2c_smbus_read_byte_data(client, + MAX6639_REG_TEMP_EXT(i)); + if (res < 0) { + ret = ERR_PTR(res); + goto abort; + } + data->temp[i] = res >> 5; + data->temp_fault[i] = res & 0x01; + + res = i2c_smbus_read_byte_data(client, + MAX6639_REG_TEMP(i)); + if (res < 0) { + ret = ERR_PTR(res); + goto abort; + } + data->temp[i] |= res << 3; + } + + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + + return ret; +} + +static ssize_t show_temp_input(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + long temp; + struct max6639_data *data = max6639_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + if (IS_ERR(data)) + return PTR_ERR(data); + + temp = data->temp[attr->index] * 125; + return sprintf(buf, "%ld\n", temp); +} + +static ssize_t show_temp_fault(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct max6639_data *data = max6639_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->temp_fault[attr->index]); +} + +static ssize_t show_temp_max(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + struct max6639_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", (data->temp_therm[attr->index] * 1000)); +} + +static ssize_t set_temp_max(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + struct max6639_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int res; + + res = kstrtoul(buf, 10, &val); + if (res) + return res; + + mutex_lock(&data->update_lock); + data->temp_therm[attr->index] = TEMP_LIMIT_TO_REG(val); + i2c_smbus_write_byte_data(client, + MAX6639_REG_THERM_LIMIT(attr->index), + data->temp_therm[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp_crit(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + struct max6639_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", (data->temp_alert[attr->index] * 1000)); +} + +static ssize_t set_temp_crit(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + struct max6639_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int res; + + res = kstrtoul(buf, 10, &val); + if (res) + return res; + + mutex_lock(&data->update_lock); + data->temp_alert[attr->index] = TEMP_LIMIT_TO_REG(val); + i2c_smbus_write_byte_data(client, + MAX6639_REG_ALERT_LIMIT(attr->index), + data->temp_alert[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp_emergency(struct device *dev, + struct device_attribute *dev_attr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + struct max6639_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", (data->temp_ot[attr->index] * 1000)); +} + +static ssize_t set_temp_emergency(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + struct max6639_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int res; + + res = kstrtoul(buf, 10, &val); + if (res) + return res; + + mutex_lock(&data->update_lock); + data->temp_ot[attr->index] = TEMP_LIMIT_TO_REG(val); + i2c_smbus_write_byte_data(client, + MAX6639_REG_OT_LIMIT(attr->index), + data->temp_ot[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + struct max6639_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", data->pwm[attr->index] * 255 / 120); +} + +static ssize_t set_pwm(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + struct max6639_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int res; + + res = kstrtoul(buf, 10, &val); + if (res) + return res; + + val = clamp_val(val, 0, 255); + + mutex_lock(&data->update_lock); + data->pwm[attr->index] = (u8)(val * 120 / 255); + i2c_smbus_write_byte_data(client, + MAX6639_REG_TARGTDUTY(attr->index), + data->pwm[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_fan_input(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct max6639_data *data = max6639_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[attr->index], + data->rpm_range)); +} + +static ssize_t show_alarm(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct max6639_data *data = max6639_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", !!(data->status & (1 << attr->index))); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 1); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, show_temp_crit, + set_temp_crit, 0); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IWUSR | S_IRUGO, show_temp_crit, + set_temp_crit, 1); +static SENSOR_DEVICE_ATTR(temp1_emergency, S_IWUSR | S_IRUGO, + show_temp_emergency, set_temp_emergency, 0); +static SENSOR_DEVICE_ATTR(temp2_emergency, S_IWUSR | S_IRUGO, + show_temp_emergency, set_temp_emergency, 1); +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1); +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_input, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_input, NULL, 1); +static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(temp1_emergency_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp2_emergency_alarm, S_IRUGO, show_alarm, NULL, 4); + + +static struct attribute *max6639_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp1_emergency.dev_attr.attr, + &sensor_dev_attr_temp2_emergency.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan1_fault.dev_attr.attr, + &sensor_dev_attr_fan2_fault.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_emergency_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_emergency_alarm.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(max6639); + +/* + * returns respective index in rpm_ranges table + * 1 by default on invalid range + */ +static int rpm_range_to_reg(int range) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rpm_ranges); i++) { + if (rpm_ranges[i] == range) + return i; + } + + return 1; /* default: 4000 RPM */ +} + +static int max6639_init_client(struct i2c_client *client, + struct max6639_data *data) +{ + struct max6639_platform_data *max6639_info = + dev_get_platdata(&client->dev); + int i; + int rpm_range = 1; /* default: 4000 RPM */ + int err; + + /* Reset chip to default values, see below for GCONFIG setup */ + err = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG, + MAX6639_GCONFIG_POR); + if (err) + goto exit; + + /* Fans pulse per revolution is 2 by default */ + if (max6639_info && max6639_info->ppr > 0 && + max6639_info->ppr < 5) + data->ppr = max6639_info->ppr; + else + data->ppr = 2; + data->ppr -= 1; + + if (max6639_info) + rpm_range = rpm_range_to_reg(max6639_info->rpm_range); + data->rpm_range = rpm_range; + + for (i = 0; i < 2; i++) { + + /* Set Fan pulse per revolution */ + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_FAN_PPR(i), + data->ppr << 6); + if (err) + goto exit; + + /* Fans config PWM, RPM */ + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_FAN_CONFIG1(i), + MAX6639_FAN_CONFIG1_PWM | rpm_range); + if (err) + goto exit; + + /* Fans PWM polarity high by default */ + if (max6639_info && max6639_info->pwm_polarity == 0) + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_FAN_CONFIG2a(i), 0x00); + else + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_FAN_CONFIG2a(i), 0x02); + if (err) + goto exit; + + /* + * /THERM full speed enable, + * PWM frequency 25kHz, see also GCONFIG below + */ + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_FAN_CONFIG3(i), + MAX6639_FAN_CONFIG3_THERM_FULL_SPEED | 0x03); + if (err) + goto exit; + + /* Max. temp. 80C/90C/100C */ + data->temp_therm[i] = 80; + data->temp_alert[i] = 90; + data->temp_ot[i] = 100; + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_THERM_LIMIT(i), + data->temp_therm[i]); + if (err) + goto exit; + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_ALERT_LIMIT(i), + data->temp_alert[i]); + if (err) + goto exit; + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_OT_LIMIT(i), data->temp_ot[i]); + if (err) + goto exit; + + /* PWM 120/120 (i.e. 100%) */ + data->pwm[i] = 120; + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_TARGTDUTY(i), data->pwm[i]); + if (err) + goto exit; + } + /* Start monitoring */ + err = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG, + MAX6639_GCONFIG_DISABLE_TIMEOUT | MAX6639_GCONFIG_CH2_LOCAL | + MAX6639_GCONFIG_PWM_FREQ_HI); +exit: + return err; +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int max6639_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int dev_id, manu_id; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* Actual detection via device and manufacturer ID */ + dev_id = i2c_smbus_read_byte_data(client, MAX6639_REG_DEVID); + manu_id = i2c_smbus_read_byte_data(client, MAX6639_REG_MANUID); + if (dev_id != 0x58 || manu_id != 0x4D) + return -ENODEV; + + strlcpy(info->type, "max6639", I2C_NAME_SIZE); + + return 0; +} + +static int max6639_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct max6639_data *data; + struct device *hwmon_dev; + int err; + + data = devm_kzalloc(dev, sizeof(struct max6639_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* Initialize the max6639 chip */ + err = max6639_init_client(client, data); + if (err < 0) + return err; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + max6639_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +#ifdef CONFIG_PM_SLEEP +static int max6639_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + int data = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG); + if (data < 0) + return data; + + return i2c_smbus_write_byte_data(client, + MAX6639_REG_GCONFIG, data | MAX6639_GCONFIG_STANDBY); +} + +static int max6639_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + int data = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG); + if (data < 0) + return data; + + return i2c_smbus_write_byte_data(client, + MAX6639_REG_GCONFIG, data & ~MAX6639_GCONFIG_STANDBY); +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct i2c_device_id max6639_id[] = { + {"max6639", 0}, + { } +}; + +MODULE_DEVICE_TABLE(i2c, max6639_id); + +static SIMPLE_DEV_PM_OPS(max6639_pm_ops, max6639_suspend, max6639_resume); + +static struct i2c_driver max6639_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "max6639", + .pm = &max6639_pm_ops, + }, + .probe = max6639_probe, + .id_table = max6639_id, + .detect = max6639_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(max6639_driver); + +MODULE_AUTHOR("Roland Stigge "); +MODULE_DESCRIPTION("max6639 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/max6642.c b/drivers/hwmon/max6642.c new file mode 100644 index 000000000..6520bc51d --- /dev/null +++ b/drivers/hwmon/max6642.c @@ -0,0 +1,327 @@ +/* + * Driver for +/-1 degree C, SMBus-Compatible Remote/Local Temperature Sensor + * with Overtemperature Alarm + * + * Copyright (C) 2011 AppearTV AS + * + * Derived from: + * + * Based on the max1619 driver. + * Copyright (C) 2003-2004 Oleksij Rempel + * Jean Delvare + * + * The MAX6642 is a sensor chip made by Maxim. + * It reports up to two temperatures (its own plus up to + * one external one). Complete datasheet can be + * obtained from Maxim's website at: + * http://datasheets.maxim-ic.com/en/ds/MAX6642.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const unsigned short normal_i2c[] = { + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, I2C_CLIENT_END }; + +/* + * The MAX6642 registers + */ + +#define MAX6642_REG_R_MAN_ID 0xFE +#define MAX6642_REG_R_CONFIG 0x03 +#define MAX6642_REG_W_CONFIG 0x09 +#define MAX6642_REG_R_STATUS 0x02 +#define MAX6642_REG_R_LOCAL_TEMP 0x00 +#define MAX6642_REG_R_LOCAL_TEMPL 0x11 +#define MAX6642_REG_R_LOCAL_HIGH 0x05 +#define MAX6642_REG_W_LOCAL_HIGH 0x0B +#define MAX6642_REG_R_REMOTE_TEMP 0x01 +#define MAX6642_REG_R_REMOTE_TEMPL 0x10 +#define MAX6642_REG_R_REMOTE_HIGH 0x07 +#define MAX6642_REG_W_REMOTE_HIGH 0x0D + +/* + * Conversions + */ + +static int temp_from_reg10(int val) +{ + return val * 250; +} + +static int temp_from_reg(int val) +{ + return val * 1000; +} + +static int temp_to_reg(int val) +{ + return val / 1000; +} + +/* + * Client data (each client gets its own) + */ + +struct max6642_data { + struct i2c_client *client; + struct mutex update_lock; + bool valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* registers values */ + u16 temp_input[2]; /* local/remote */ + u16 temp_high[2]; /* local/remote */ + u8 alarms; +}; + +/* + * Real code + */ + +static void max6642_init_client(struct max6642_data *data, + struct i2c_client *client) +{ + u8 config; + + /* + * Start the conversions. + */ + config = i2c_smbus_read_byte_data(client, MAX6642_REG_R_CONFIG); + if (config & 0x40) + i2c_smbus_write_byte_data(client, MAX6642_REG_W_CONFIG, + config & 0xBF); /* run */ + + data->temp_high[0] = i2c_smbus_read_byte_data(client, + MAX6642_REG_R_LOCAL_HIGH); + data->temp_high[1] = i2c_smbus_read_byte_data(client, + MAX6642_REG_R_REMOTE_HIGH); +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int max6642_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + u8 reg_config, reg_status, man_id; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* identification */ + man_id = i2c_smbus_read_byte_data(client, MAX6642_REG_R_MAN_ID); + if (man_id != 0x4D) + return -ENODEV; + + /* sanity check */ + if (i2c_smbus_read_byte_data(client, 0x04) != 0x4D + || i2c_smbus_read_byte_data(client, 0x06) != 0x4D + || i2c_smbus_read_byte_data(client, 0xff) != 0x4D) + return -ENODEV; + + /* + * We read the config and status register, the 4 lower bits in the + * config register should be zero and bit 5, 3, 1 and 0 should be + * zero in the status register. + */ + reg_config = i2c_smbus_read_byte_data(client, MAX6642_REG_R_CONFIG); + if ((reg_config & 0x0f) != 0x00) + return -ENODEV; + + /* in between, another round of sanity checks */ + if (i2c_smbus_read_byte_data(client, 0x04) != reg_config + || i2c_smbus_read_byte_data(client, 0x06) != reg_config + || i2c_smbus_read_byte_data(client, 0xff) != reg_config) + return -ENODEV; + + reg_status = i2c_smbus_read_byte_data(client, MAX6642_REG_R_STATUS); + if ((reg_status & 0x2b) != 0x00) + return -ENODEV; + + strlcpy(info->type, "max6642", I2C_NAME_SIZE); + + return 0; +} + +static struct max6642_data *max6642_update_device(struct device *dev) +{ + struct max6642_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u16 val, tmp; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + dev_dbg(dev, "Updating max6642 data.\n"); + val = i2c_smbus_read_byte_data(client, + MAX6642_REG_R_LOCAL_TEMPL); + tmp = (val >> 6) & 3; + val = i2c_smbus_read_byte_data(client, + MAX6642_REG_R_LOCAL_TEMP); + val = (val << 2) | tmp; + data->temp_input[0] = val; + val = i2c_smbus_read_byte_data(client, + MAX6642_REG_R_REMOTE_TEMPL); + tmp = (val >> 6) & 3; + val = i2c_smbus_read_byte_data(client, + MAX6642_REG_R_REMOTE_TEMP); + val = (val << 2) | tmp; + data->temp_input[1] = val; + data->alarms = i2c_smbus_read_byte_data(client, + MAX6642_REG_R_STATUS); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Sysfs stuff + */ + +static ssize_t show_temp_max10(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + struct max6642_data *data = max6642_update_device(dev); + + return sprintf(buf, "%d\n", + temp_from_reg10(data->temp_input[attr->index])); +} + +static ssize_t show_temp_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(attr); + struct max6642_data *data = max6642_update_device(dev); + + return sprintf(buf, "%d\n", temp_from_reg(data->temp_high[attr2->nr])); +} + +static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(attr); + struct max6642_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + mutex_lock(&data->update_lock); + data->temp_high[attr2->nr] = clamp_val(temp_to_reg(val), 0, 255); + i2c_smbus_write_byte_data(data->client, attr2->index, + data->temp_high[attr2->nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct max6642_data *data = max6642_update_device(dev); + return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_max10, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_max10, NULL, 1); +static SENSOR_DEVICE_ATTR_2(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 0, MAX6642_REG_W_LOCAL_HIGH); +static SENSOR_DEVICE_ATTR_2(temp2_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 1, MAX6642_REG_W_REMOTE_HIGH); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 4); + +static struct attribute *max6642_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(max6642); + +static int max6642_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct max6642_data *data; + struct device *hwmon_dev; + + data = devm_kzalloc(dev, sizeof(struct max6642_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* Initialize the MAX6642 chip */ + max6642_init_client(data, client); + + hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, + client->name, data, + max6642_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +/* + * Driver data (common to all clients) + */ + +static const struct i2c_device_id max6642_id[] = { + { "max6642", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max6642_id); + +static struct i2c_driver max6642_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "max6642", + }, + .probe = max6642_probe, + .id_table = max6642_id, + .detect = max6642_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(max6642_driver); + +MODULE_AUTHOR("Per Dalen "); +MODULE_DESCRIPTION("MAX6642 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/max6650.c b/drivers/hwmon/max6650.c new file mode 100644 index 000000000..65be4b19f --- /dev/null +++ b/drivers/hwmon/max6650.c @@ -0,0 +1,758 @@ +/* + * max6650.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring. + * + * (C) 2007 by Hans J. Koch + * + * based on code written by John Morris + * Copyright (c) 2003 Spirent Communications + * and Claus Gindhart + * + * This module has only been tested with the MAX6650 chip. It should + * also work with the MAX6651. It does not distinguish max6650 and max6651 + * chips. + * + * The datasheet was last seen at: + * + * http://pdfserv.maxim-ic.com/en/ds/MAX6650-MAX6651.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Insmod parameters + */ + +/* fan_voltage: 5=5V fan, 12=12V fan, 0=don't change */ +static int fan_voltage; +/* prescaler: Possible values are 1, 2, 4, 8, 16 or 0 for don't change */ +static int prescaler; +/* clock: The clock frequency of the chip (max6651 can be clocked externally) */ +static int clock = 254000; + +module_param(fan_voltage, int, S_IRUGO); +module_param(prescaler, int, S_IRUGO); +module_param(clock, int, S_IRUGO); + +/* + * MAX 6650/6651 registers + */ + +#define MAX6650_REG_SPEED 0x00 +#define MAX6650_REG_CONFIG 0x02 +#define MAX6650_REG_GPIO_DEF 0x04 +#define MAX6650_REG_DAC 0x06 +#define MAX6650_REG_ALARM_EN 0x08 +#define MAX6650_REG_ALARM 0x0A +#define MAX6650_REG_TACH0 0x0C +#define MAX6650_REG_TACH1 0x0E +#define MAX6650_REG_TACH2 0x10 +#define MAX6650_REG_TACH3 0x12 +#define MAX6650_REG_GPIO_STAT 0x14 +#define MAX6650_REG_COUNT 0x16 + +/* + * Config register bits + */ + +#define MAX6650_CFG_V12 0x08 +#define MAX6650_CFG_PRESCALER_MASK 0x07 +#define MAX6650_CFG_PRESCALER_2 0x01 +#define MAX6650_CFG_PRESCALER_4 0x02 +#define MAX6650_CFG_PRESCALER_8 0x03 +#define MAX6650_CFG_PRESCALER_16 0x04 +#define MAX6650_CFG_MODE_MASK 0x30 +#define MAX6650_CFG_MODE_ON 0x00 +#define MAX6650_CFG_MODE_OFF 0x10 +#define MAX6650_CFG_MODE_CLOSED_LOOP 0x20 +#define MAX6650_CFG_MODE_OPEN_LOOP 0x30 +#define MAX6650_COUNT_MASK 0x03 + +/* + * Alarm status register bits + */ + +#define MAX6650_ALRM_MAX 0x01 +#define MAX6650_ALRM_MIN 0x02 +#define MAX6650_ALRM_TACH 0x04 +#define MAX6650_ALRM_GPIO1 0x08 +#define MAX6650_ALRM_GPIO2 0x10 + +/* Minimum and maximum values of the FAN-RPM */ +#define FAN_RPM_MIN 240 +#define FAN_RPM_MAX 30000 + +#define DIV_FROM_REG(reg) (1 << (reg & 7)) + +/* + * Client data (each client gets its own) + */ + +struct max6650_data { + struct i2c_client *client; + const struct attribute_group *groups[3]; + struct mutex update_lock; + int nr_fans; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* register values */ + u8 speed; + u8 config; + u8 tach[4]; + u8 count; + u8 dac; + u8 alarm; +}; + +static const u8 tach_reg[] = { + MAX6650_REG_TACH0, + MAX6650_REG_TACH1, + MAX6650_REG_TACH2, + MAX6650_REG_TACH3, +}; + +static const struct of_device_id max6650_dt_match[] = { + { + .compatible = "maxim,max6650", + .data = (void *)1 + }, + { + .compatible = "maxim,max6651", + .data = (void *)4 + }, + { }, +}; +MODULE_DEVICE_TABLE(of, max6650_dt_match); + +static struct max6650_data *max6650_update_device(struct device *dev) +{ + struct max6650_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + data->speed = i2c_smbus_read_byte_data(client, + MAX6650_REG_SPEED); + data->config = i2c_smbus_read_byte_data(client, + MAX6650_REG_CONFIG); + for (i = 0; i < data->nr_fans; i++) { + data->tach[i] = i2c_smbus_read_byte_data(client, + tach_reg[i]); + } + data->count = i2c_smbus_read_byte_data(client, + MAX6650_REG_COUNT); + data->dac = i2c_smbus_read_byte_data(client, MAX6650_REG_DAC); + + /* + * Alarms are cleared on read in case the condition that + * caused the alarm is removed. Keep the value latched here + * for providing the register through different alarm files. + */ + data->alarm |= i2c_smbus_read_byte_data(client, + MAX6650_REG_ALARM); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Change the operating mode of the chip (if needed). + * mode is one of the MAX6650_CFG_MODE_* values. + */ +static int max6650_set_operating_mode(struct max6650_data *data, u8 mode) +{ + int result; + u8 config = data->config; + + if (mode == (config & MAX6650_CFG_MODE_MASK)) + return 0; + + config = (config & ~MAX6650_CFG_MODE_MASK) | mode; + + result = i2c_smbus_write_byte_data(data->client, MAX6650_REG_CONFIG, + config); + if (result < 0) + return result; + + data->config = config; + + return 0; +} + +static ssize_t get_fan(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct max6650_data *data = max6650_update_device(dev); + int rpm; + + /* + * Calculation details: + * + * Each tachometer counts over an interval given by the "count" + * register (0.25, 0.5, 1 or 2 seconds). This module assumes + * that the fans produce two pulses per revolution (this seems + * to be the most common). + */ + + rpm = ((data->tach[attr->index] * 120) / DIV_FROM_REG(data->count)); + return sprintf(buf, "%d\n", rpm); +} + +/* + * Set the fan speed to the specified RPM (or read back the RPM setting). + * This works in closed loop mode only. Use pwm1 for open loop speed setting. + * + * The MAX6650/1 will automatically control fan speed when in closed loop + * mode. + * + * Assumptions: + * + * 1) The MAX6650/1 internal 254kHz clock frequency is set correctly. Use + * the clock module parameter if you need to fine tune this. + * + * 2) The prescaler (low three bits of the config register) has already + * been set to an appropriate value. Use the prescaler module parameter + * if your BIOS doesn't initialize the chip properly. + * + * The relevant equations are given on pages 21 and 22 of the datasheet. + * + * From the datasheet, the relevant equation when in regulation is: + * + * [fCLK / (128 x (KTACH + 1))] = 2 x FanSpeed / KSCALE + * + * where: + * + * fCLK is the oscillator frequency (either the 254kHz internal + * oscillator or the externally applied clock) + * + * KTACH is the value in the speed register + * + * FanSpeed is the speed of the fan in rps + * + * KSCALE is the prescaler value (1, 2, 4, 8, or 16) + * + * When reading, we need to solve for FanSpeed. When writing, we need to + * solve for KTACH. + * + * Note: this tachometer is completely separate from the tachometers + * used to measure the fan speeds. Only one fan's speed (fan1) is + * controlled. + */ + +static ssize_t fan1_target_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct max6650_data *data = max6650_update_device(dev); + int kscale, ktach, rpm; + + /* + * Use the datasheet equation: + * + * FanSpeed = KSCALE x fCLK / [256 x (KTACH + 1)] + * + * then multiply by 60 to give rpm. + */ + + kscale = DIV_FROM_REG(data->config); + ktach = data->speed; + rpm = 60 * kscale * clock / (256 * (ktach + 1)); + return sprintf(buf, "%d\n", rpm); +} + +static int max6650_set_target(struct max6650_data *data, unsigned long rpm) +{ + int kscale, ktach; + + if (rpm == 0) + return max6650_set_operating_mode(data, MAX6650_CFG_MODE_OFF); + + rpm = clamp_val(rpm, FAN_RPM_MIN, FAN_RPM_MAX); + + /* + * Divide the required speed by 60 to get from rpm to rps, then + * use the datasheet equation: + * + * KTACH = [(fCLK x KSCALE) / (256 x FanSpeed)] - 1 + */ + + kscale = DIV_FROM_REG(data->config); + ktach = ((clock * kscale) / (256 * rpm / 60)) - 1; + if (ktach < 0) + ktach = 0; + if (ktach > 255) + ktach = 255; + data->speed = ktach; + + return i2c_smbus_write_byte_data(data->client, MAX6650_REG_SPEED, + data->speed); +} + +static ssize_t fan1_target_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct max6650_data *data = dev_get_drvdata(dev); + unsigned long rpm; + int err; + + err = kstrtoul(buf, 10, &rpm); + if (err) + return err; + + mutex_lock(&data->update_lock); + + err = max6650_set_target(data, rpm); + + mutex_unlock(&data->update_lock); + + if (err < 0) + return err; + + return count; +} + +/* + * Get/set the fan speed in open loop mode using pwm1 sysfs file. + * Speed is given as a relative value from 0 to 255, where 255 is maximum + * speed. Note that this is done by writing directly to the chip's DAC, + * it won't change the closed loop speed set by fan1_target. + * Also note that due to rounding errors it is possible that you don't read + * back exactly the value you have set. + */ + +static ssize_t pwm1_show(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + int pwm; + struct max6650_data *data = max6650_update_device(dev); + + /* + * Useful range for dac is 0-180 for 12V fans and 0-76 for 5V fans. + * Lower DAC values mean higher speeds. + */ + if (data->config & MAX6650_CFG_V12) + pwm = 255 - (255 * (int)data->dac)/180; + else + pwm = 255 - (255 * (int)data->dac)/76; + + if (pwm < 0) + pwm = 0; + + return sprintf(buf, "%d\n", pwm); +} + +static ssize_t pwm1_store(struct device *dev, + struct device_attribute *devattr, const char *buf, + size_t count) +{ + struct max6650_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long pwm; + int err; + + err = kstrtoul(buf, 10, &pwm); + if (err) + return err; + + pwm = clamp_val(pwm, 0, 255); + + mutex_lock(&data->update_lock); + + if (data->config & MAX6650_CFG_V12) + data->dac = 180 - (180 * pwm)/255; + else + data->dac = 76 - (76 * pwm)/255; + err = i2c_smbus_write_byte_data(client, MAX6650_REG_DAC, data->dac); + + mutex_unlock(&data->update_lock); + + return err < 0 ? err : count; +} + +/* + * Get/Set controller mode: + * Possible values: + * 0 = Fan always on + * 1 = Open loop, Voltage is set according to speed, not regulated. + * 2 = Closed loop, RPM for all fans regulated by fan1 tachometer + * 3 = Fan off + */ +static ssize_t pwm1_enable_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct max6650_data *data = max6650_update_device(dev); + int mode = (data->config & MAX6650_CFG_MODE_MASK) >> 4; + int sysfs_modes[4] = {0, 3, 2, 1}; + + return sprintf(buf, "%d\n", sysfs_modes[mode]); +} + +static ssize_t pwm1_enable_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct max6650_data *data = dev_get_drvdata(dev); + unsigned long mode; + int err; + const u8 max6650_modes[] = { + MAX6650_CFG_MODE_ON, + MAX6650_CFG_MODE_OPEN_LOOP, + MAX6650_CFG_MODE_CLOSED_LOOP, + MAX6650_CFG_MODE_OFF, + }; + + err = kstrtoul(buf, 10, &mode); + if (err) + return err; + + if (mode >= ARRAY_SIZE(max6650_modes)) + return -EINVAL; + + mutex_lock(&data->update_lock); + + max6650_set_operating_mode(data, max6650_modes[mode]); + + mutex_unlock(&data->update_lock); + + return count; +} + +/* + * Read/write functions for fan1_div sysfs file. The MAX6650 has no such + * divider. We handle this by converting between divider and counttime: + * + * (counttime == k) <==> (divider == 2^k), k = 0, 1, 2, or 3 + * + * Lower values of k allow to connect a faster fan without the risk of + * counter overflow. The price is lower resolution. You can also set counttime + * using the module parameter. Note that the module parameter "prescaler" also + * influences the behaviour. Unfortunately, there's no sysfs attribute + * defined for that. See the data sheet for details. + */ + +static ssize_t fan1_div_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct max6650_data *data = max6650_update_device(dev); + + return sprintf(buf, "%d\n", DIV_FROM_REG(data->count)); +} + +static ssize_t fan1_div_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct max6650_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long div; + int err; + + err = kstrtoul(buf, 10, &div); + if (err) + return err; + + mutex_lock(&data->update_lock); + switch (div) { + case 1: + data->count = 0; + break; + case 2: + data->count = 1; + break; + case 4: + data->count = 2; + break; + case 8: + data->count = 3; + break; + default: + mutex_unlock(&data->update_lock); + return -EINVAL; + } + + i2c_smbus_write_byte_data(client, MAX6650_REG_COUNT, data->count); + mutex_unlock(&data->update_lock); + + return count; +} + +/* + * Get alarm stati: + * Possible values: + * 0 = no alarm + * 1 = alarm + */ + +static ssize_t get_alarm(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct max6650_data *data = max6650_update_device(dev); + struct i2c_client *client = data->client; + int alarm = 0; + + if (data->alarm & attr->index) { + mutex_lock(&data->update_lock); + alarm = 1; + data->alarm &= ~attr->index; + data->alarm |= i2c_smbus_read_byte_data(client, + MAX6650_REG_ALARM); + mutex_unlock(&data->update_lock); + } + + return sprintf(buf, "%d\n", alarm); +} + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, get_fan, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, get_fan, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, get_fan, NULL, 3); +static DEVICE_ATTR_RW(fan1_target); +static DEVICE_ATTR_RW(fan1_div); +static DEVICE_ATTR_RW(pwm1_enable); +static DEVICE_ATTR_RW(pwm1); +static SENSOR_DEVICE_ATTR(fan1_max_alarm, S_IRUGO, get_alarm, NULL, + MAX6650_ALRM_MAX); +static SENSOR_DEVICE_ATTR(fan1_min_alarm, S_IRUGO, get_alarm, NULL, + MAX6650_ALRM_MIN); +static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, get_alarm, NULL, + MAX6650_ALRM_TACH); +static SENSOR_DEVICE_ATTR(gpio1_alarm, S_IRUGO, get_alarm, NULL, + MAX6650_ALRM_GPIO1); +static SENSOR_DEVICE_ATTR(gpio2_alarm, S_IRUGO, get_alarm, NULL, + MAX6650_ALRM_GPIO2); + +static umode_t max6650_attrs_visible(struct kobject *kobj, struct attribute *a, + int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct max6650_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 alarm_en = i2c_smbus_read_byte_data(client, MAX6650_REG_ALARM_EN); + struct device_attribute *devattr; + + /* + * Hide the alarms that have not been enabled by the firmware + */ + + devattr = container_of(a, struct device_attribute, attr); + if (devattr == &sensor_dev_attr_fan1_max_alarm.dev_attr + || devattr == &sensor_dev_attr_fan1_min_alarm.dev_attr + || devattr == &sensor_dev_attr_fan1_fault.dev_attr + || devattr == &sensor_dev_attr_gpio1_alarm.dev_attr + || devattr == &sensor_dev_attr_gpio2_alarm.dev_attr) { + if (!(alarm_en & to_sensor_dev_attr(devattr)->index)) + return 0; + } + + return a->mode; +} + +static struct attribute *max6650_attrs[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &dev_attr_fan1_target.attr, + &dev_attr_fan1_div.attr, + &dev_attr_pwm1_enable.attr, + &dev_attr_pwm1.attr, + &sensor_dev_attr_fan1_max_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_min_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_fault.dev_attr.attr, + &sensor_dev_attr_gpio1_alarm.dev_attr.attr, + &sensor_dev_attr_gpio2_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group max6650_group = { + .attrs = max6650_attrs, + .is_visible = max6650_attrs_visible, +}; + +static struct attribute *max6651_attrs[] = { + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + NULL +}; + +static const struct attribute_group max6651_group = { + .attrs = max6651_attrs, +}; + +/* + * Real code + */ + +static int max6650_init_client(struct max6650_data *data, + struct i2c_client *client) +{ + struct device *dev = &client->dev; + int config; + int err = -EIO; + u32 voltage; + u32 prescale; + u32 target_rpm; + + if (of_property_read_u32(dev->of_node, "maxim,fan-microvolt", + &voltage)) + voltage = fan_voltage; + else + voltage /= 1000000; /* Microvolts to volts */ + if (of_property_read_u32(dev->of_node, "maxim,fan-prescale", + &prescale)) + prescale = prescaler; + + config = i2c_smbus_read_byte_data(client, MAX6650_REG_CONFIG); + + if (config < 0) { + dev_err(dev, "Error reading config, aborting.\n"); + return err; + } + + switch (voltage) { + case 0: + break; + case 5: + config &= ~MAX6650_CFG_V12; + break; + case 12: + config |= MAX6650_CFG_V12; + break; + default: + dev_err(dev, "illegal value for fan_voltage (%d)\n", voltage); + } + + switch (prescale) { + case 0: + break; + case 1: + config &= ~MAX6650_CFG_PRESCALER_MASK; + break; + case 2: + config = (config & ~MAX6650_CFG_PRESCALER_MASK) + | MAX6650_CFG_PRESCALER_2; + break; + case 4: + config = (config & ~MAX6650_CFG_PRESCALER_MASK) + | MAX6650_CFG_PRESCALER_4; + break; + case 8: + config = (config & ~MAX6650_CFG_PRESCALER_MASK) + | MAX6650_CFG_PRESCALER_8; + break; + case 16: + config = (config & ~MAX6650_CFG_PRESCALER_MASK) + | MAX6650_CFG_PRESCALER_16; + break; + default: + dev_err(dev, "illegal value for prescaler (%d)\n", prescale); + } + + dev_info(dev, "Fan voltage: %dV, prescaler: %d.\n", + (config & MAX6650_CFG_V12) ? 12 : 5, + 1 << (config & MAX6650_CFG_PRESCALER_MASK)); + + if (i2c_smbus_write_byte_data(client, MAX6650_REG_CONFIG, config)) { + dev_err(dev, "Config write error, aborting.\n"); + return err; + } + + data->config = config; + data->count = i2c_smbus_read_byte_data(client, MAX6650_REG_COUNT); + + if (!of_property_read_u32(client->dev.of_node, "maxim,fan-target-rpm", + &target_rpm)) { + max6650_set_target(data, target_rpm); + max6650_set_operating_mode(data, MAX6650_CFG_MODE_CLOSED_LOOP); + } + + return 0; +} + +static int max6650_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + const struct of_device_id *of_id = + of_match_device(of_match_ptr(max6650_dt_match), dev); + struct max6650_data *data; + struct device *hwmon_dev; + int err; + + data = devm_kzalloc(dev, sizeof(struct max6650_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + data->nr_fans = of_id ? (int)(uintptr_t)of_id->data : id->driver_data; + + /* + * Initialize the max6650 chip + */ + err = max6650_init_client(data, client); + if (err) + return err; + + data->groups[0] = &max6650_group; + /* 3 additional fan inputs for the MAX6651 */ + if (data->nr_fans == 4) + data->groups[1] = &max6651_group; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + client->name, data, + data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id max6650_id[] = { + { "max6650", 1 }, + { "max6651", 4 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max6650_id); + +static struct i2c_driver max6650_driver = { + .driver = { + .name = "max6650", + .of_match_table = of_match_ptr(max6650_dt_match), + }, + .probe = max6650_probe, + .id_table = max6650_id, +}; + +module_i2c_driver(max6650_driver); + +MODULE_AUTHOR("Hans J. Koch"); +MODULE_DESCRIPTION("MAX6650 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/max6697.c b/drivers/hwmon/max6697.c new file mode 100644 index 000000000..6df28fe05 --- /dev/null +++ b/drivers/hwmon/max6697.c @@ -0,0 +1,729 @@ +/* + * Copyright (c) 2012 Guenter Roeck + * + * based on max1668.c + * Copyright (c) 2011 David George + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +enum chips { max6581, max6602, max6622, max6636, max6689, max6693, max6694, + max6697, max6698, max6699 }; + +/* Report local sensor as temp1 */ + +static const u8 MAX6697_REG_TEMP[] = { + 0x07, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08 }; +static const u8 MAX6697_REG_TEMP_EXT[] = { + 0x57, 0x09, 0x52, 0x53, 0x54, 0x55, 0x56, 0 }; +static const u8 MAX6697_REG_MAX[] = { + 0x17, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x18 }; +static const u8 MAX6697_REG_CRIT[] = { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27 }; + +/* + * Map device tree / platform data register bit map to chip bit map. + * Applies to alert register and over-temperature register. + */ +#define MAX6697_ALERT_MAP_BITS(reg) ((((reg) & 0x7e) >> 1) | \ + (((reg) & 0x01) << 6) | ((reg) & 0x80)) +#define MAX6697_OVERT_MAP_BITS(reg) (((reg) >> 1) | (((reg) & 0x01) << 7)) + +#define MAX6697_REG_STAT(n) (0x44 + (n)) + +#define MAX6697_REG_CONFIG 0x41 +#define MAX6581_CONF_EXTENDED (1 << 1) +#define MAX6693_CONF_BETA (1 << 2) +#define MAX6697_CONF_RESISTANCE (1 << 3) +#define MAX6697_CONF_TIMEOUT (1 << 5) +#define MAX6697_REG_ALERT_MASK 0x42 +#define MAX6697_REG_OVERT_MASK 0x43 + +#define MAX6581_REG_RESISTANCE 0x4a +#define MAX6581_REG_IDEALITY 0x4b +#define MAX6581_REG_IDEALITY_SELECT 0x4c +#define MAX6581_REG_OFFSET 0x4d +#define MAX6581_REG_OFFSET_SELECT 0x4e + +#define MAX6697_CONV_TIME 156 /* ms per channel, worst case */ + +struct max6697_chip_data { + int channels; + u32 have_ext; + u32 have_crit; + u32 have_fault; + u8 valid_conf; + const u8 *alarm_map; +}; + +struct max6697_data { + struct i2c_client *client; + + enum chips type; + const struct max6697_chip_data *chip; + + int update_interval; /* in milli-seconds */ + int temp_offset; /* in degrees C */ + + struct mutex update_lock; + unsigned long last_updated; /* In jiffies */ + bool valid; /* true if following fields are valid */ + + /* 1x local and up to 7x remote */ + u8 temp[8][4]; /* [nr][0]=temp [1]=ext [2]=max [3]=crit */ +#define MAX6697_TEMP_INPUT 0 +#define MAX6697_TEMP_EXT 1 +#define MAX6697_TEMP_MAX 2 +#define MAX6697_TEMP_CRIT 3 + u32 alarms; +}; + +/* Diode fault status bits on MAX6581 are right shifted by one bit */ +static const u8 max6581_alarm_map[] = { + 0, 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23 }; + +static const struct max6697_chip_data max6697_chip_data[] = { + [max6581] = { + .channels = 8, + .have_crit = 0xff, + .have_ext = 0x7f, + .have_fault = 0xfe, + .valid_conf = MAX6581_CONF_EXTENDED | MAX6697_CONF_TIMEOUT, + .alarm_map = max6581_alarm_map, + }, + [max6602] = { + .channels = 5, + .have_crit = 0x12, + .have_ext = 0x02, + .have_fault = 0x1e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6622] = { + .channels = 5, + .have_crit = 0x12, + .have_ext = 0x02, + .have_fault = 0x1e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6636] = { + .channels = 7, + .have_crit = 0x72, + .have_ext = 0x02, + .have_fault = 0x7e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6689] = { + .channels = 7, + .have_crit = 0x72, + .have_ext = 0x02, + .have_fault = 0x7e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6693] = { + .channels = 7, + .have_crit = 0x72, + .have_ext = 0x02, + .have_fault = 0x7e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6693_CONF_BETA | + MAX6697_CONF_TIMEOUT, + }, + [max6694] = { + .channels = 5, + .have_crit = 0x12, + .have_ext = 0x02, + .have_fault = 0x1e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6693_CONF_BETA | + MAX6697_CONF_TIMEOUT, + }, + [max6697] = { + .channels = 7, + .have_crit = 0x72, + .have_ext = 0x02, + .have_fault = 0x7e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6698] = { + .channels = 7, + .have_crit = 0x72, + .have_ext = 0x02, + .have_fault = 0x0e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, + [max6699] = { + .channels = 5, + .have_crit = 0x12, + .have_ext = 0x02, + .have_fault = 0x1e, + .valid_conf = MAX6697_CONF_RESISTANCE | MAX6697_CONF_TIMEOUT, + }, +}; + +static struct max6697_data *max6697_update_device(struct device *dev) +{ + struct max6697_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + struct max6697_data *ret = data; + int val; + int i; + u32 alarms; + + mutex_lock(&data->update_lock); + + if (data->valid && + !time_after(jiffies, data->last_updated + + msecs_to_jiffies(data->update_interval))) + goto abort; + + for (i = 0; i < data->chip->channels; i++) { + if (data->chip->have_ext & (1 << i)) { + val = i2c_smbus_read_byte_data(client, + MAX6697_REG_TEMP_EXT[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i][MAX6697_TEMP_EXT] = val; + } + + val = i2c_smbus_read_byte_data(client, MAX6697_REG_TEMP[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i][MAX6697_TEMP_INPUT] = val; + + val = i2c_smbus_read_byte_data(client, MAX6697_REG_MAX[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i][MAX6697_TEMP_MAX] = val; + + if (data->chip->have_crit & (1 << i)) { + val = i2c_smbus_read_byte_data(client, + MAX6697_REG_CRIT[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i][MAX6697_TEMP_CRIT] = val; + } + } + + alarms = 0; + for (i = 0; i < 3; i++) { + val = i2c_smbus_read_byte_data(client, MAX6697_REG_STAT(i)); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + alarms = (alarms << 8) | val; + } + data->alarms = alarms; + data->last_updated = jiffies; + data->valid = true; +abort: + mutex_unlock(&data->update_lock); + + return ret; +} + +static ssize_t show_temp_input(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int index = to_sensor_dev_attr(devattr)->index; + struct max6697_data *data = max6697_update_device(dev); + int temp; + + if (IS_ERR(data)) + return PTR_ERR(data); + + temp = (data->temp[index][MAX6697_TEMP_INPUT] - data->temp_offset) << 3; + temp |= data->temp[index][MAX6697_TEMP_EXT] >> 5; + + return sprintf(buf, "%d\n", temp * 125); +} + +static ssize_t show_temp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int nr = to_sensor_dev_attr_2(devattr)->nr; + int index = to_sensor_dev_attr_2(devattr)->index; + struct max6697_data *data = max6697_update_device(dev); + int temp; + + if (IS_ERR(data)) + return PTR_ERR(data); + + temp = data->temp[nr][index]; + temp -= data->temp_offset; + + return sprintf(buf, "%d\n", temp * 1000); +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + struct max6697_data *data = max6697_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + if (data->chip->alarm_map) + index = data->chip->alarm_map[index]; + + return sprintf(buf, "%u\n", (data->alarms >> index) & 0x1); +} + +static ssize_t set_temp(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr_2(devattr)->nr; + int index = to_sensor_dev_attr_2(devattr)->index; + struct max6697_data *data = dev_get_drvdata(dev); + long temp; + int ret; + + ret = kstrtol(buf, 10, &temp); + if (ret < 0) + return ret; + + mutex_lock(&data->update_lock); + temp = DIV_ROUND_CLOSEST(temp, 1000) + data->temp_offset; + temp = clamp_val(temp, 0, data->type == max6581 ? 255 : 127); + data->temp[nr][index] = temp; + ret = i2c_smbus_write_byte_data(data->client, + index == 2 ? MAX6697_REG_MAX[nr] + : MAX6697_REG_CRIT[nr], + temp); + mutex_unlock(&data->update_lock); + + return ret < 0 ? ret : count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL, 0); +static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 0, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp1_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 0, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_input, NULL, 1); +static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 1, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp2_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 1, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp_input, NULL, 2); +static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 2, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp3_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 2, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp_input, NULL, 3); +static SENSOR_DEVICE_ATTR_2(temp4_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 3, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp4_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 3, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp_input, NULL, 4); +static SENSOR_DEVICE_ATTR_2(temp5_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 4, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp5_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 4, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp_input, NULL, 5); +static SENSOR_DEVICE_ATTR_2(temp6_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 5, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp6_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 5, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_temp_input, NULL, 6); +static SENSOR_DEVICE_ATTR_2(temp7_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 6, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp7_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 6, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_temp_input, NULL, 7); +static SENSOR_DEVICE_ATTR_2(temp8_max, S_IRUGO | S_IWUSR, show_temp, set_temp, + 7, MAX6697_TEMP_MAX); +static SENSOR_DEVICE_ATTR_2(temp8_crit, S_IRUGO | S_IWUSR, show_temp, set_temp, + 7, MAX6697_TEMP_CRIT); + +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 22); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 16); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_alarm, NULL, 17); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_alarm, NULL, 18); +static SENSOR_DEVICE_ATTR(temp5_max_alarm, S_IRUGO, show_alarm, NULL, 19); +static SENSOR_DEVICE_ATTR(temp6_max_alarm, S_IRUGO, show_alarm, NULL, 20); +static SENSOR_DEVICE_ATTR(temp7_max_alarm, S_IRUGO, show_alarm, NULL, 21); +static SENSOR_DEVICE_ATTR(temp8_max_alarm, S_IRUGO, show_alarm, NULL, 23); + +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 14); +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(temp3_crit_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(temp4_crit_alarm, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(temp5_crit_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(temp6_crit_alarm, S_IRUGO, show_alarm, NULL, 12); +static SENSOR_DEVICE_ATTR(temp7_crit_alarm, S_IRUGO, show_alarm, NULL, 13); +static SENSOR_DEVICE_ATTR(temp8_crit_alarm, S_IRUGO, show_alarm, NULL, 15); + +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_fault, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_fault, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_fault, S_IRUGO, show_alarm, NULL, 7); + +static DEVICE_ATTR(dummy, 0, NULL, NULL); + +static umode_t max6697_is_visible(struct kobject *kobj, struct attribute *attr, + int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct max6697_data *data = dev_get_drvdata(dev); + const struct max6697_chip_data *chip = data->chip; + int channel = index / 6; /* channel number */ + int nr = index % 6; /* attribute index within channel */ + + if (channel >= chip->channels) + return 0; + + if ((nr == 3 || nr == 4) && !(chip->have_crit & (1 << channel))) + return 0; + if (nr == 5 && !(chip->have_fault & (1 << channel))) + return 0; + + return attr->mode; +} + +/* + * max6697_is_visible uses the index into the following array to determine + * if attributes should be created or not. Any change in order or content + * must be matched in max6697_is_visible. + */ +static struct attribute *max6697_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &dev_attr_dummy.attr, + + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_crit.dev_attr.attr, + &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp5_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_crit.dev_attr.attr, + &sensor_dev_attr_temp5_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_fault.dev_attr.attr, + + &sensor_dev_attr_temp6_input.dev_attr.attr, + &sensor_dev_attr_temp6_max.dev_attr.attr, + &sensor_dev_attr_temp6_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp6_crit.dev_attr.attr, + &sensor_dev_attr_temp6_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp6_fault.dev_attr.attr, + + &sensor_dev_attr_temp7_input.dev_attr.attr, + &sensor_dev_attr_temp7_max.dev_attr.attr, + &sensor_dev_attr_temp7_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp7_crit.dev_attr.attr, + &sensor_dev_attr_temp7_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp7_fault.dev_attr.attr, + + &sensor_dev_attr_temp8_input.dev_attr.attr, + &sensor_dev_attr_temp8_max.dev_attr.attr, + &sensor_dev_attr_temp8_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp8_crit.dev_attr.attr, + &sensor_dev_attr_temp8_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp8_fault.dev_attr.attr, + NULL +}; + +static const struct attribute_group max6697_group = { + .attrs = max6697_attributes, .is_visible = max6697_is_visible, +}; +__ATTRIBUTE_GROUPS(max6697); + +static void max6697_get_config_of(struct device_node *node, + struct max6697_platform_data *pdata) +{ + int len; + const __be32 *prop; + + pdata->smbus_timeout_disable = + of_property_read_bool(node, "smbus-timeout-disable"); + pdata->extended_range_enable = + of_property_read_bool(node, "extended-range-enable"); + pdata->beta_compensation = + of_property_read_bool(node, "beta-compensation-enable"); + + prop = of_get_property(node, "alert-mask", &len); + if (prop && len == sizeof(u32)) + pdata->alert_mask = be32_to_cpu(prop[0]); + prop = of_get_property(node, "over-temperature-mask", &len); + if (prop && len == sizeof(u32)) + pdata->over_temperature_mask = be32_to_cpu(prop[0]); + prop = of_get_property(node, "resistance-cancellation", &len); + if (prop) { + if (len == sizeof(u32)) + pdata->resistance_cancellation = be32_to_cpu(prop[0]); + else + pdata->resistance_cancellation = 0xfe; + } + prop = of_get_property(node, "transistor-ideality", &len); + if (prop && len == 2 * sizeof(u32)) { + pdata->ideality_mask = be32_to_cpu(prop[0]); + pdata->ideality_value = be32_to_cpu(prop[1]); + } +} + +static int max6697_init_chip(struct max6697_data *data, + struct i2c_client *client) +{ + struct max6697_platform_data *pdata = dev_get_platdata(&client->dev); + struct max6697_platform_data p; + const struct max6697_chip_data *chip = data->chip; + int factor = chip->channels; + int ret, reg; + + /* + * Don't touch configuration if neither platform data nor OF + * configuration was specified. If that is the case, use the + * current chip configuration. + */ + if (!pdata && !client->dev.of_node) { + reg = i2c_smbus_read_byte_data(client, MAX6697_REG_CONFIG); + if (reg < 0) + return reg; + if (data->type == max6581) { + if (reg & MAX6581_CONF_EXTENDED) + data->temp_offset = 64; + reg = i2c_smbus_read_byte_data(client, + MAX6581_REG_RESISTANCE); + if (reg < 0) + return reg; + factor += hweight8(reg); + } else { + if (reg & MAX6697_CONF_RESISTANCE) + factor++; + } + goto done; + } + + if (client->dev.of_node) { + memset(&p, 0, sizeof(p)); + max6697_get_config_of(client->dev.of_node, &p); + pdata = &p; + } + + reg = 0; + if (pdata->smbus_timeout_disable && + (chip->valid_conf & MAX6697_CONF_TIMEOUT)) { + reg |= MAX6697_CONF_TIMEOUT; + } + if (pdata->extended_range_enable && + (chip->valid_conf & MAX6581_CONF_EXTENDED)) { + reg |= MAX6581_CONF_EXTENDED; + data->temp_offset = 64; + } + if (pdata->resistance_cancellation && + (chip->valid_conf & MAX6697_CONF_RESISTANCE)) { + reg |= MAX6697_CONF_RESISTANCE; + factor++; + } + if (pdata->beta_compensation && + (chip->valid_conf & MAX6693_CONF_BETA)) { + reg |= MAX6693_CONF_BETA; + } + + ret = i2c_smbus_write_byte_data(client, MAX6697_REG_CONFIG, reg); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, MAX6697_REG_ALERT_MASK, + MAX6697_ALERT_MAP_BITS(pdata->alert_mask)); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, MAX6697_REG_OVERT_MASK, + MAX6697_OVERT_MAP_BITS(pdata->over_temperature_mask)); + if (ret < 0) + return ret; + + if (data->type == max6581) { + factor += hweight8(pdata->resistance_cancellation >> 1); + ret = i2c_smbus_write_byte_data(client, MAX6581_REG_RESISTANCE, + pdata->resistance_cancellation >> 1); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(client, MAX6581_REG_IDEALITY, + pdata->ideality_value); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(client, + MAX6581_REG_IDEALITY_SELECT, + pdata->ideality_mask >> 1); + if (ret < 0) + return ret; + } +done: + data->update_interval = factor * MAX6697_CONV_TIME; + return 0; +} + +static int max6697_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct device *dev = &client->dev; + struct max6697_data *data; + struct device *hwmon_dev; + int err; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(struct max6697_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (client->dev.of_node) + data->type = (enum chips)of_device_get_match_data(&client->dev); + else + data->type = id->driver_data; + data->chip = &max6697_chip_data[data->type]; + data->client = client; + mutex_init(&data->update_lock); + + err = max6697_init_chip(data, client); + if (err) + return err; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + max6697_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id max6697_id[] = { + { "max6581", max6581 }, + { "max6602", max6602 }, + { "max6622", max6622 }, + { "max6636", max6636 }, + { "max6689", max6689 }, + { "max6693", max6693 }, + { "max6694", max6694 }, + { "max6697", max6697 }, + { "max6698", max6698 }, + { "max6699", max6699 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max6697_id); + +static const struct of_device_id max6697_of_match[] = { + { + .compatible = "maxim,max6581", + .data = (void *)max6581 + }, + { + .compatible = "maxim,max6602", + .data = (void *)max6602 + }, + { + .compatible = "maxim,max6622", + .data = (void *)max6622 + }, + { + .compatible = "maxim,max6636", + .data = (void *)max6636 + }, + { + .compatible = "maxim,max6689", + .data = (void *)max6689 + }, + { + .compatible = "maxim,max6693", + .data = (void *)max6693 + }, + { + .compatible = "maxim,max6694", + .data = (void *)max6694 + }, + { + .compatible = "maxim,max6697", + .data = (void *)max6697 + }, + { + .compatible = "maxim,max6698", + .data = (void *)max6698 + }, + { + .compatible = "maxim,max6699", + .data = (void *)max6699 + }, + { }, +}; +MODULE_DEVICE_TABLE(of, max6697_of_match); + +static struct i2c_driver max6697_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "max6697", + .of_match_table = of_match_ptr(max6697_of_match), + }, + .probe = max6697_probe, + .id_table = max6697_id, +}; + +module_i2c_driver(max6697_driver); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("MAX6697 temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/mc13783-adc.c b/drivers/hwmon/mc13783-adc.c new file mode 100644 index 000000000..78fe8759d --- /dev/null +++ b/drivers/hwmon/mc13783-adc.c @@ -0,0 +1,340 @@ +/* + * Driver for the ADC on Freescale Semiconductor MC13783 and MC13892 PMICs. + * + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright (C) 2009 Sascha Hauer, Pengutronix + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "mc13783-adc" + +/* platform device id driver data */ +#define MC13783_ADC_16CHANS 1 +#define MC13783_ADC_BPDIV2 2 + +struct mc13783_adc_priv { + struct mc13xxx *mc13xxx; + struct device *hwmon_dev; + char name[PLATFORM_NAME_SIZE]; +}; + +static ssize_t name_show(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct mc13783_adc_priv *priv = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", priv->name); +} + +static int mc13783_adc_read(struct device *dev, + struct device_attribute *devattr, unsigned int *val) +{ + struct mc13783_adc_priv *priv = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + unsigned int channel = attr->index; + unsigned int sample[4]; + int ret; + + ret = mc13xxx_adc_do_conversion(priv->mc13xxx, + MC13XXX_ADC_MODE_MULT_CHAN, + channel, 0, 0, sample); + if (ret) + return ret; + + /* ADIN7 subchannels */ + if (channel >= 16) + channel = 7; + + channel &= 0x7; + + *val = (sample[channel % 4] >> (channel > 3 ? 14 : 2)) & 0x3ff; + + return 0; +} + +static ssize_t mc13783_adc_read_bp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + unsigned val; + struct platform_device *pdev = to_platform_device(dev); + kernel_ulong_t driver_data = platform_get_device_id(pdev)->driver_data; + int ret = mc13783_adc_read(dev, devattr, &val); + + if (ret) + return ret; + + if (driver_data & MC13783_ADC_BPDIV2) + val = DIV_ROUND_CLOSEST(val * 9, 2); + else + /* + * BP (channel 2) reports with offset 2.4V to the actual value + * to fit the input range of the ADC. unit = 2.25mV = 9/4 mV. + */ + val = DIV_ROUND_CLOSEST(val * 9, 4) + 2400; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t mc13783_adc_read_gp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + unsigned val; + int ret = mc13783_adc_read(dev, devattr, &val); + + if (ret) + return ret; + + /* + * input range is [0, 2.3V], val has 10 bits, so each bit + * is worth 9/4 mV. + */ + val = DIV_ROUND_CLOSEST(val * 9, 4); + + return sprintf(buf, "%u\n", val); +} + +static ssize_t mc13783_adc_read_uid(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + unsigned int val; + struct platform_device *pdev = to_platform_device(dev); + kernel_ulong_t driver_data = platform_get_device_id(pdev)->driver_data; + int ret = mc13783_adc_read(dev, devattr, &val); + + if (ret) + return ret; + + if (driver_data & MC13783_ADC_BPDIV2) + /* MC13892 have 1/2 divider, input range is [0, 4.800V] */ + val = DIV_ROUND_CLOSEST(val * 4800, 1024); + else + /* MC13783 have 0.9 divider, input range is [0, 2.555V] */ + val = DIV_ROUND_CLOSEST(val * 2555, 1024); + + return sprintf(buf, "%u\n", val); +} + +static ssize_t mc13783_adc_read_temp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + unsigned int val; + struct platform_device *pdev = to_platform_device(dev); + kernel_ulong_t driver_data = platform_get_device_id(pdev)->driver_data; + int ret = mc13783_adc_read(dev, devattr, &val); + + if (ret) + return ret; + + if (driver_data & MC13783_ADC_BPDIV2) { + /* + * MC13892: + * Die Temperature Read Out Code at 25C 680 + * Temperature change per LSB +0.4244C + */ + ret = DIV_ROUND_CLOSEST(-2635920 + val * 4244, 10); + } else { + /* + * MC13783: + * Die Temperature Read Out Code at 25C 282 + * Temperature change per LSB -1.14C + */ + ret = 346480 - 1140 * val; + } + + return sprintf(buf, "%d\n", ret); +} + +static DEVICE_ATTR_RO(name); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, mc13783_adc_read_bp, NULL, 2); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, mc13783_adc_read_gp, NULL, 5); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, mc13783_adc_read_gp, NULL, 6); +static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, mc13783_adc_read_gp, NULL, 7); +static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, mc13783_adc_read_gp, NULL, 8); +static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, mc13783_adc_read_gp, NULL, 9); +static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, mc13783_adc_read_gp, NULL, 10); +static SENSOR_DEVICE_ATTR(in11_input, S_IRUGO, mc13783_adc_read_gp, NULL, 11); +static SENSOR_DEVICE_ATTR(in12_input, S_IRUGO, mc13783_adc_read_gp, NULL, 12); +static SENSOR_DEVICE_ATTR(in13_input, S_IRUGO, mc13783_adc_read_gp, NULL, 13); +static SENSOR_DEVICE_ATTR(in14_input, S_IRUGO, mc13783_adc_read_gp, NULL, 14); +static SENSOR_DEVICE_ATTR(in15_input, S_IRUGO, mc13783_adc_read_gp, NULL, 15); +static SENSOR_DEVICE_ATTR(in16_input, S_IRUGO, mc13783_adc_read_uid, NULL, 16); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, + mc13783_adc_read_temp, NULL, 17); + +static struct attribute *mc13783_attr_base[] = { + &dev_attr_name.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in16_input.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + NULL +}; + +static const struct attribute_group mc13783_group_base = { + .attrs = mc13783_attr_base, +}; + +/* these are only used if MC13783_ADC_16CHANS is provided in driver data */ +static struct attribute *mc13783_attr_16chans[] = { + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in11_input.dev_attr.attr, + NULL +}; + +static const struct attribute_group mc13783_group_16chans = { + .attrs = mc13783_attr_16chans, +}; + +/* last four channels may be occupied by the touchscreen */ +static struct attribute *mc13783_attr_ts[] = { + &sensor_dev_attr_in12_input.dev_attr.attr, + &sensor_dev_attr_in13_input.dev_attr.attr, + &sensor_dev_attr_in14_input.dev_attr.attr, + &sensor_dev_attr_in15_input.dev_attr.attr, + NULL +}; + +static const struct attribute_group mc13783_group_ts = { + .attrs = mc13783_attr_ts, +}; + +static int mc13783_adc_use_touchscreen(struct platform_device *pdev) +{ + struct mc13783_adc_priv *priv = platform_get_drvdata(pdev); + unsigned flags = mc13xxx_get_flags(priv->mc13xxx); + + return flags & MC13XXX_USE_TOUCHSCREEN; +} + +static int __init mc13783_adc_probe(struct platform_device *pdev) +{ + struct mc13783_adc_priv *priv; + int ret; + const struct platform_device_id *id = platform_get_device_id(pdev); + char *dash; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->mc13xxx = dev_get_drvdata(pdev->dev.parent); + snprintf(priv->name, ARRAY_SIZE(priv->name), "%s", id->name); + dash = strchr(priv->name, '-'); + if (dash) + *dash = '\0'; + + platform_set_drvdata(pdev, priv); + + /* Register sysfs hooks */ + ret = sysfs_create_group(&pdev->dev.kobj, &mc13783_group_base); + if (ret) + return ret; + + if (id->driver_data & MC13783_ADC_16CHANS) { + ret = sysfs_create_group(&pdev->dev.kobj, + &mc13783_group_16chans); + if (ret) + goto out_err_create_16chans; + } + + if (!mc13783_adc_use_touchscreen(pdev)) { + ret = sysfs_create_group(&pdev->dev.kobj, &mc13783_group_ts); + if (ret) + goto out_err_create_ts; + } + + priv->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(priv->hwmon_dev)) { + ret = PTR_ERR(priv->hwmon_dev); + dev_err(&pdev->dev, + "hwmon_device_register failed with %d.\n", ret); + goto out_err_register; + } + + return 0; + +out_err_register: + + if (!mc13783_adc_use_touchscreen(pdev)) + sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_ts); +out_err_create_ts: + + if (id->driver_data & MC13783_ADC_16CHANS) + sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_16chans); +out_err_create_16chans: + + sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_base); + return ret; +} + +static int mc13783_adc_remove(struct platform_device *pdev) +{ + struct mc13783_adc_priv *priv = platform_get_drvdata(pdev); + kernel_ulong_t driver_data = platform_get_device_id(pdev)->driver_data; + + hwmon_device_unregister(priv->hwmon_dev); + + if (!mc13783_adc_use_touchscreen(pdev)) + sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_ts); + + if (driver_data & MC13783_ADC_16CHANS) + sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_16chans); + + sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_base); + + return 0; +} + +static const struct platform_device_id mc13783_adc_idtable[] = { + { + .name = "mc13783-adc", + .driver_data = MC13783_ADC_16CHANS, + }, { + .name = "mc13892-adc", + .driver_data = MC13783_ADC_BPDIV2, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, mc13783_adc_idtable); + +static struct platform_driver mc13783_adc_driver = { + .remove = mc13783_adc_remove, + .driver = { + .name = DRIVER_NAME, + }, + .id_table = mc13783_adc_idtable, +}; + +module_platform_driver_probe(mc13783_adc_driver, mc13783_adc_probe); + +MODULE_DESCRIPTION("MC13783 ADC driver"); +MODULE_AUTHOR("Luotao Fu "); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/mcp3021.c b/drivers/hwmon/mcp3021.c new file mode 100644 index 000000000..de886f821 --- /dev/null +++ b/drivers/hwmon/mcp3021.c @@ -0,0 +1,213 @@ +/* + * mcp3021.c - driver for Microchip MCP3021 and MCP3221 + * + * Copyright (C) 2008-2009, 2012 Freescale Semiconductor, Inc. + * Author: Mingkai Hu + * Reworked by Sven Schuchmann + * DT support added by Clemens Gruber + * + * This driver export the value of analog input voltage to sysfs, the + * voltage unit is mV. Through the sysfs interface, lm-sensors tool + * can also display the input voltage. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Vdd / reference voltage in millivolt */ +#define MCP3021_VDD_REF_MAX 5500 +#define MCP3021_VDD_REF_MIN 2700 +#define MCP3021_VDD_REF_DEFAULT 3300 + +/* output format */ +#define MCP3021_SAR_SHIFT 2 +#define MCP3021_SAR_MASK 0x3ff +#define MCP3021_OUTPUT_RES 10 /* 10-bit resolution */ + +#define MCP3221_SAR_SHIFT 0 +#define MCP3221_SAR_MASK 0xfff +#define MCP3221_OUTPUT_RES 12 /* 12-bit resolution */ + +enum chips { + mcp3021, + mcp3221 +}; + +/* + * Client data (each client gets its own) + */ +struct mcp3021_data { + struct device *hwmon_dev; + u32 vdd; /* supply and reference voltage in millivolt */ + u16 sar_shift; + u16 sar_mask; + u8 output_res; +}; + +static int mcp3021_read16(struct i2c_client *client) +{ + struct mcp3021_data *data = i2c_get_clientdata(client); + int ret; + u16 reg; + __be16 buf; + + ret = i2c_master_recv(client, (char *)&buf, 2); + if (ret < 0) + return ret; + if (ret != 2) + return -EIO; + + /* The output code of the MCP3021 is transmitted with MSB first. */ + reg = be16_to_cpu(buf); + + /* + * The ten-bit output code is composed of the lower 4-bit of the + * first byte and the upper 6-bit of the second byte. + */ + reg = (reg >> data->sar_shift) & data->sar_mask; + + return reg; +} + +static inline u16 volts_from_reg(struct mcp3021_data *data, u16 val) +{ + return DIV_ROUND_CLOSEST(data->vdd * val, 1 << data->output_res); +} + +static ssize_t in0_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mcp3021_data *data = i2c_get_clientdata(client); + int reg, in_input; + + reg = mcp3021_read16(client); + if (reg < 0) + return reg; + + in_input = volts_from_reg(data, reg); + + return sprintf(buf, "%d\n", in_input); +} + +static DEVICE_ATTR_RO(in0_input); + +static int mcp3021_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err; + struct mcp3021_data *data = NULL; + struct device_node *np = client->dev.of_node; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(struct mcp3021_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + + if (np) { + if (!of_property_read_u32(np, "reference-voltage-microvolt", + &data->vdd)) + data->vdd /= 1000; + else + data->vdd = MCP3021_VDD_REF_DEFAULT; + } else { + u32 *pdata = dev_get_platdata(&client->dev); + + if (pdata) + data->vdd = *pdata; + else + data->vdd = MCP3021_VDD_REF_DEFAULT; + } + + switch (id->driver_data) { + case mcp3021: + data->sar_shift = MCP3021_SAR_SHIFT; + data->sar_mask = MCP3021_SAR_MASK; + data->output_res = MCP3021_OUTPUT_RES; + break; + + case mcp3221: + data->sar_shift = MCP3221_SAR_SHIFT; + data->sar_mask = MCP3221_SAR_MASK; + data->output_res = MCP3221_OUTPUT_RES; + break; + } + + if (data->vdd > MCP3021_VDD_REF_MAX || data->vdd < MCP3021_VDD_REF_MIN) + return -EINVAL; + + err = sysfs_create_file(&client->dev.kobj, &dev_attr_in0_input.attr); + if (err) + return err; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + sysfs_remove_file(&client->dev.kobj, &dev_attr_in0_input.attr); + return err; +} + +static int mcp3021_remove(struct i2c_client *client) +{ + struct mcp3021_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_file(&client->dev.kobj, &dev_attr_in0_input.attr); + + return 0; +} + +static const struct i2c_device_id mcp3021_id[] = { + { "mcp3021", mcp3021 }, + { "mcp3221", mcp3221 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp3021_id); + +#ifdef CONFIG_OF +static const struct of_device_id of_mcp3021_match[] = { + { .compatible = "microchip,mcp3021", .data = (void *)mcp3021 }, + { .compatible = "microchip,mcp3221", .data = (void *)mcp3221 }, + { } +}; +MODULE_DEVICE_TABLE(of, of_mcp3021_match); +#endif + +static struct i2c_driver mcp3021_driver = { + .driver = { + .name = "mcp3021", + .of_match_table = of_match_ptr(of_mcp3021_match), + }, + .probe = mcp3021_probe, + .remove = mcp3021_remove, + .id_table = mcp3021_id, +}; + +module_i2c_driver(mcp3021_driver); + +MODULE_AUTHOR("Mingkai Hu "); +MODULE_DESCRIPTION("Microchip MCP3021/MCP3221 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/menf21bmc_hwmon.c b/drivers/hwmon/menf21bmc_hwmon.c new file mode 100644 index 000000000..c29a4c3c6 --- /dev/null +++ b/drivers/hwmon/menf21bmc_hwmon.c @@ -0,0 +1,230 @@ +/* + * MEN 14F021P00 Board Management Controller (BMC) hwmon driver. + * + * This is the core hwmon driver of the MEN 14F021P00 BMC. + * The BMC monitors the board voltages which can be access with this + * driver through sysfs. + * + * Copyright (C) 2014 MEN Mikro Elektronik Nuernberg GmbH + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "menf21bmc_hwmon" + +#define BMC_VOLT_COUNT 5 +#define MENF21BMC_V33 0 +#define MENF21BMC_V5 1 +#define MENF21BMC_V12 2 +#define MENF21BMC_V5_SB 3 +#define MENF21BMC_VBAT 4 + +#define IDX_TO_VOLT_MIN_CMD(idx) (0x40 + idx) +#define IDX_TO_VOLT_MAX_CMD(idx) (0x50 + idx) +#define IDX_TO_VOLT_INP_CMD(idx) (0x60 + idx) + +struct menf21bmc_hwmon { + bool valid; + struct i2c_client *i2c_client; + unsigned long last_update; + int in_val[BMC_VOLT_COUNT]; + int in_min[BMC_VOLT_COUNT]; + int in_max[BMC_VOLT_COUNT]; +}; + +static const char *const input_names[] = { + [MENF21BMC_V33] = "MON_3_3V", + [MENF21BMC_V5] = "MON_5V", + [MENF21BMC_V12] = "MON_12V", + [MENF21BMC_V5_SB] = "5V_STANDBY", + [MENF21BMC_VBAT] = "VBAT" +}; + +static struct menf21bmc_hwmon *menf21bmc_hwmon_update(struct device *dev) +{ + int i; + int val; + struct menf21bmc_hwmon *drv_data = dev_get_drvdata(dev); + struct menf21bmc_hwmon *data_ret = drv_data; + + if (time_after(jiffies, drv_data->last_update + HZ) + || !drv_data->valid) { + for (i = 0; i < BMC_VOLT_COUNT; i++) { + val = i2c_smbus_read_word_data(drv_data->i2c_client, + IDX_TO_VOLT_INP_CMD(i)); + if (val < 0) { + data_ret = ERR_PTR(val); + goto abort; + } + drv_data->in_val[i] = val; + } + drv_data->last_update = jiffies; + drv_data->valid = true; + } +abort: + return data_ret; +} + +static int menf21bmc_hwmon_get_volt_limits(struct menf21bmc_hwmon *drv_data) +{ + int i, val; + + for (i = 0; i < BMC_VOLT_COUNT; i++) { + val = i2c_smbus_read_word_data(drv_data->i2c_client, + IDX_TO_VOLT_MIN_CMD(i)); + if (val < 0) + return val; + + drv_data->in_min[i] = val; + + val = i2c_smbus_read_word_data(drv_data->i2c_client, + IDX_TO_VOLT_MAX_CMD(i)); + if (val < 0) + return val; + + drv_data->in_max[i] = val; + } + return 0; +} + +static ssize_t +show_label(struct device *dev, struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%s\n", input_names[attr->index]); +} + +static ssize_t +show_in(struct device *dev, struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct menf21bmc_hwmon *drv_data = menf21bmc_hwmon_update(dev); + + if (IS_ERR(drv_data)) + return PTR_ERR(drv_data); + + return sprintf(buf, "%d\n", drv_data->in_val[attr->index]); +} + +static ssize_t +show_min(struct device *dev, struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct menf21bmc_hwmon *drv_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", drv_data->in_min[attr->index]); +} + +static ssize_t +show_max(struct device *dev, struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct menf21bmc_hwmon *drv_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", drv_data->in_max[attr->index]); +} + +#define create_voltage_sysfs(idx) \ +static SENSOR_DEVICE_ATTR(in##idx##_input, S_IRUGO, \ + show_in, NULL, idx); \ +static SENSOR_DEVICE_ATTR(in##idx##_min, S_IRUGO, \ + show_min, NULL, idx); \ +static SENSOR_DEVICE_ATTR(in##idx##_max, S_IRUGO, \ + show_max, NULL, idx); \ +static SENSOR_DEVICE_ATTR(in##idx##_label, S_IRUGO, \ + show_label, NULL, idx); + +create_voltage_sysfs(0); +create_voltage_sysfs(1); +create_voltage_sysfs(2); +create_voltage_sysfs(3); +create_voltage_sysfs(4); + +static struct attribute *menf21bmc_hwmon_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_label.dev_attr.attr, + + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_label.dev_attr.attr, + + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_label.dev_attr.attr, + + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_label.dev_attr.attr, + + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_label.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(menf21bmc_hwmon); + +static int menf21bmc_hwmon_probe(struct platform_device *pdev) +{ + int ret; + struct menf21bmc_hwmon *drv_data; + struct i2c_client *i2c_client = to_i2c_client(pdev->dev.parent); + struct device *hwmon_dev; + + drv_data = devm_kzalloc(&pdev->dev, sizeof(struct menf21bmc_hwmon), + GFP_KERNEL); + if (!drv_data) + return -ENOMEM; + + drv_data->i2c_client = i2c_client; + + ret = menf21bmc_hwmon_get_volt_limits(drv_data); + if (ret) { + dev_err(&pdev->dev, "failed to read sensor limits"); + return ret; + } + + hwmon_dev = devm_hwmon_device_register_with_groups(&pdev->dev, + "menf21bmc", drv_data, + menf21bmc_hwmon_groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + dev_info(&pdev->dev, "MEN 14F021P00 BMC hwmon device enabled"); + + return 0; +} + +static struct platform_driver menf21bmc_hwmon = { + .probe = menf21bmc_hwmon_probe, + .driver = { + .name = DRV_NAME, + }, +}; + +module_platform_driver(menf21bmc_hwmon); + +MODULE_AUTHOR("Andreas Werner "); +MODULE_DESCRIPTION("MEN 14F021P00 BMC hwmon"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:menf21bmc_hwmon"); diff --git a/drivers/hwmon/mlxreg-fan.c b/drivers/hwmon/mlxreg-fan.c new file mode 100644 index 000000000..e57b0c511 --- /dev/null +++ b/drivers/hwmon/mlxreg-fan.c @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// Copyright (c) 2018 Mellanox Technologies. All rights reserved. +// Copyright (c) 2018 Vadim Pasternak + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MLXREG_FAN_MAX_TACHO 12 +#define MLXREG_FAN_MAX_STATE 10 +#define MLXREG_FAN_MIN_DUTY 51 /* 20% */ +#define MLXREG_FAN_MAX_DUTY 255 /* 100% */ +/* + * Minimum and maximum FAN allowed speed in percent: from 20% to 100%. Values + * MLXREG_FAN_MAX_STATE + x, where x is between 2 and 10 are used for + * setting FAN speed dynamic minimum. For example, if value is set to 14 (40%) + * cooling levels vector will be set to 4, 4, 4, 4, 4, 5, 6, 7, 8, 9, 10 to + * introduce PWM speed in percent: 40, 40, 40, 40, 40, 50, 60. 70, 80, 90, 100. + */ +#define MLXREG_FAN_SPEED_MIN (MLXREG_FAN_MAX_STATE + 2) +#define MLXREG_FAN_SPEED_MAX (MLXREG_FAN_MAX_STATE * 2) +#define MLXREG_FAN_SPEED_MIN_LEVEL 2 /* 20 percent */ +#define MLXREG_FAN_TACHO_SAMPLES_PER_PULSE_DEF 44 +#define MLXREG_FAN_TACHO_DIVIDER_DEF 1132 +/* + * FAN datasheet defines the formula for RPM calculations as RPM = 15/t-high. + * The logic in a programmable device measures the time t-high by sampling the + * tachometer every t-sample (with the default value 11.32 uS) and increment + * a counter (N) as long as the pulse has not change: + * RPM = 15 / (t-sample * (K + Regval)), where: + * Regval: is the value read from the programmable device register; + * - 0xff - represents tachometer fault; + * - 0xfe - represents tachometer minimum value , which is 4444 RPM; + * - 0x00 - represents tachometer maximum value , which is 300000 RPM; + * K: is 44 and it represents the minimum allowed samples per pulse; + * N: is equal K + Regval; + * In order to calculate RPM from the register value the following formula is + * used: RPM = 15 / ((Regval + K) * 11.32) * 10^(-6)), which in the + * default case is modified to: + * RPM = 15000000 * 100 / ((Regval + 44) * 1132); + * - for Regval 0x00, RPM will be 15000000 * 100 / (44 * 1132) = 30115; + * - for Regval 0xfe, RPM will be 15000000 * 100 / ((254 + 44) * 1132) = 4446; + * In common case the formula is modified to: + * RPM = 15000000 * 100 / ((Regval + samples) * divider). + */ +#define MLXREG_FAN_GET_RPM(rval, d, s) (DIV_ROUND_CLOSEST(15000000 * 100, \ + ((rval) + (s)) * (d))) +#define MLXREG_FAN_GET_FAULT(val, mask) (!((val) ^ (mask))) +#define MLXREG_FAN_PWM_DUTY2STATE(duty) (DIV_ROUND_CLOSEST((duty) * \ + MLXREG_FAN_MAX_STATE, \ + MLXREG_FAN_MAX_DUTY)) +#define MLXREG_FAN_PWM_STATE2DUTY(stat) (DIV_ROUND_CLOSEST((stat) * \ + MLXREG_FAN_MAX_DUTY, \ + MLXREG_FAN_MAX_STATE)) + +/* + * struct mlxreg_fan_tacho - tachometer data (internal use): + * + * @connected: indicates if tachometer is connected; + * @reg: register offset; + * @mask: fault mask; + */ +struct mlxreg_fan_tacho { + bool connected; + u32 reg; + u32 mask; +}; + +/* + * struct mlxreg_fan_pwm - PWM data (internal use): + * + * @connected: indicates if PWM is connected; + * @reg: register offset; + */ +struct mlxreg_fan_pwm { + bool connected; + u32 reg; +}; + +/* + * struct mlxreg_fan - private data (internal use): + * + * @dev: basic device; + * @regmap: register map of parent device; + * @tacho: tachometer data; + * @pwm: PWM data; + * @samples: minimum allowed samples per pulse; + * @divider: divider value for tachometer RPM calculation; + * @cooling: cooling device levels; + * @cdev: cooling device; + */ +struct mlxreg_fan { + struct device *dev; + void *regmap; + struct mlxreg_core_platform_data *pdata; + struct mlxreg_fan_tacho tacho[MLXREG_FAN_MAX_TACHO]; + struct mlxreg_fan_pwm pwm; + int samples; + int divider; + u8 cooling_levels[MLXREG_FAN_MAX_STATE + 1]; + struct thermal_cooling_device *cdev; +}; + +static int +mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct mlxreg_fan *fan = dev_get_drvdata(dev); + struct mlxreg_fan_tacho *tacho; + u32 regval; + int err; + + switch (type) { + case hwmon_fan: + tacho = &fan->tacho[channel]; + switch (attr) { + case hwmon_fan_input: + err = regmap_read(fan->regmap, tacho->reg, ®val); + if (err) + return err; + + *val = MLXREG_FAN_GET_RPM(regval, fan->divider, + fan->samples); + break; + + case hwmon_fan_fault: + err = regmap_read(fan->regmap, tacho->reg, ®val); + if (err) + return err; + + *val = MLXREG_FAN_GET_FAULT(regval, tacho->mask); + break; + + default: + return -EOPNOTSUPP; + } + break; + + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + err = regmap_read(fan->regmap, fan->pwm.reg, ®val); + if (err) + return err; + + *val = regval; + break; + + default: + return -EOPNOTSUPP; + } + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int +mlxreg_fan_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long val) +{ + struct mlxreg_fan *fan = dev_get_drvdata(dev); + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + if (val < MLXREG_FAN_MIN_DUTY || + val > MLXREG_FAN_MAX_DUTY) + return -EINVAL; + return regmap_write(fan->regmap, fan->pwm.reg, val); + default: + return -EOPNOTSUPP; + } + break; + + default: + return -EOPNOTSUPP; + } + + return -EOPNOTSUPP; +} + +static umode_t +mlxreg_fan_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, + int channel) +{ + switch (type) { + case hwmon_fan: + if (!(((struct mlxreg_fan *)data)->tacho[channel].connected)) + return 0; + + switch (attr) { + case hwmon_fan_input: + case hwmon_fan_fault: + return 0444; + default: + break; + } + break; + + case hwmon_pwm: + if (!(((struct mlxreg_fan *)data)->pwm.connected)) + return 0; + + switch (attr) { + case hwmon_pwm_input: + return 0644; + default: + break; + } + break; + + default: + break; + } + + return 0; +} + +static const u32 mlxreg_fan_hwmon_fan_config[] = { + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + HWMON_F_INPUT | HWMON_F_FAULT, + 0 +}; + +static const struct hwmon_channel_info mlxreg_fan_hwmon_fan = { + .type = hwmon_fan, + .config = mlxreg_fan_hwmon_fan_config, +}; + +static const u32 mlxreg_fan_hwmon_pwm_config[] = { + HWMON_PWM_INPUT, + 0 +}; + +static const struct hwmon_channel_info mlxreg_fan_hwmon_pwm = { + .type = hwmon_pwm, + .config = mlxreg_fan_hwmon_pwm_config, +}; + +static const struct hwmon_channel_info *mlxreg_fan_hwmon_info[] = { + &mlxreg_fan_hwmon_fan, + &mlxreg_fan_hwmon_pwm, + NULL +}; + +static const struct hwmon_ops mlxreg_fan_hwmon_hwmon_ops = { + .is_visible = mlxreg_fan_is_visible, + .read = mlxreg_fan_read, + .write = mlxreg_fan_write, +}; + +static const struct hwmon_chip_info mlxreg_fan_hwmon_chip_info = { + .ops = &mlxreg_fan_hwmon_hwmon_ops, + .info = mlxreg_fan_hwmon_info, +}; + +static int mlxreg_fan_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = MLXREG_FAN_MAX_STATE; + return 0; +} + +static int mlxreg_fan_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) + +{ + struct mlxreg_fan *fan = cdev->devdata; + u32 regval; + int err; + + err = regmap_read(fan->regmap, fan->pwm.reg, ®val); + if (err) { + dev_err(fan->dev, "Failed to query PWM duty\n"); + return err; + } + + *state = MLXREG_FAN_PWM_DUTY2STATE(regval); + + return 0; +} + +static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) + +{ + struct mlxreg_fan *fan = cdev->devdata; + unsigned long cur_state; + int i, config = 0; + u32 regval; + int err; + + /* + * Verify if this request is for changing allowed FAN dynamical + * minimum. If it is - update cooling levels accordingly and update + * state, if current state is below the newly requested minimum state. + * For example, if current state is 5, and minimal state is to be + * changed from 4 to 6, fan->cooling_levels[0 to 5] will be changed all + * from 4 to 6. And state 5 (fan->cooling_levels[4]) should be + * overwritten. + */ + if (state >= MLXREG_FAN_SPEED_MIN && state <= MLXREG_FAN_SPEED_MAX) { + /* + * This is configuration change, which is only supported through sysfs. + * For configuration non-zero value is to be returned to avoid thermal + * statistics update. + */ + config = 1; + state -= MLXREG_FAN_MAX_STATE; + for (i = 0; i < state; i++) + fan->cooling_levels[i] = state; + for (i = state; i <= MLXREG_FAN_MAX_STATE; i++) + fan->cooling_levels[i] = i; + + err = regmap_read(fan->regmap, fan->pwm.reg, ®val); + if (err) { + dev_err(fan->dev, "Failed to query PWM duty\n"); + return err; + } + + cur_state = MLXREG_FAN_PWM_DUTY2STATE(regval); + if (state < cur_state) + return config; + + state = cur_state; + } + + if (state > MLXREG_FAN_MAX_STATE) + return -EINVAL; + + /* Normalize the state to the valid speed range. */ + state = fan->cooling_levels[state]; + err = regmap_write(fan->regmap, fan->pwm.reg, + MLXREG_FAN_PWM_STATE2DUTY(state)); + if (err) { + dev_err(fan->dev, "Failed to write PWM duty\n"); + return err; + } + return config; +} + +static const struct thermal_cooling_device_ops mlxreg_fan_cooling_ops = { + .get_max_state = mlxreg_fan_get_max_state, + .get_cur_state = mlxreg_fan_get_cur_state, + .set_cur_state = mlxreg_fan_set_cur_state, +}; + +static int mlxreg_fan_config(struct mlxreg_fan *fan, + struct mlxreg_core_platform_data *pdata) +{ + struct mlxreg_core_data *data = pdata->data; + bool configured = false; + int tacho_num = 0, i; + + fan->samples = MLXREG_FAN_TACHO_SAMPLES_PER_PULSE_DEF; + fan->divider = MLXREG_FAN_TACHO_DIVIDER_DEF; + for (i = 0; i < pdata->counter; i++, data++) { + if (strnstr(data->label, "tacho", sizeof(data->label))) { + if (tacho_num == MLXREG_FAN_MAX_TACHO) { + dev_err(fan->dev, "too many tacho entries: %s\n", + data->label); + return -EINVAL; + } + fan->tacho[tacho_num].reg = data->reg; + fan->tacho[tacho_num].mask = data->mask; + fan->tacho[tacho_num++].connected = true; + } else if (strnstr(data->label, "pwm", sizeof(data->label))) { + if (fan->pwm.connected) { + dev_err(fan->dev, "duplicate pwm entry: %s\n", + data->label); + return -EINVAL; + } + fan->pwm.reg = data->reg; + fan->pwm.connected = true; + } else if (strnstr(data->label, "conf", sizeof(data->label))) { + if (configured) { + dev_err(fan->dev, "duplicate conf entry: %s\n", + data->label); + return -EINVAL; + } + /* Validate that conf parameters are not zeros. */ + if (!data->mask || !data->bit) { + dev_err(fan->dev, "invalid conf entry params: %s\n", + data->label); + return -EINVAL; + } + fan->samples = data->mask; + fan->divider = data->bit; + configured = true; + } else { + dev_err(fan->dev, "invalid label: %s\n", data->label); + return -EINVAL; + } + } + + /* Init cooling levels per PWM state. */ + for (i = 0; i < MLXREG_FAN_SPEED_MIN_LEVEL; i++) + fan->cooling_levels[i] = MLXREG_FAN_SPEED_MIN_LEVEL; + for (i = MLXREG_FAN_SPEED_MIN_LEVEL; i <= MLXREG_FAN_MAX_STATE; i++) + fan->cooling_levels[i] = i; + + return 0; +} + +static int mlxreg_fan_probe(struct platform_device *pdev) +{ + struct mlxreg_core_platform_data *pdata; + struct mlxreg_fan *fan; + struct device *hwm; + int err; + + pdata = dev_get_platdata(&pdev->dev); + if (!pdata) { + dev_err(&pdev->dev, "Failed to get platform data.\n"); + return -EINVAL; + } + + fan = devm_kzalloc(&pdev->dev, sizeof(*fan), GFP_KERNEL); + if (!fan) + return -ENOMEM; + + fan->dev = &pdev->dev; + fan->regmap = pdata->regmap; + platform_set_drvdata(pdev, fan); + + err = mlxreg_fan_config(fan, pdata); + if (err) + return err; + + hwm = devm_hwmon_device_register_with_info(&pdev->dev, "mlxreg_fan", + fan, + &mlxreg_fan_hwmon_chip_info, + NULL); + if (IS_ERR(hwm)) { + dev_err(&pdev->dev, "Failed to register hwmon device\n"); + return PTR_ERR(hwm); + } + + if (IS_REACHABLE(CONFIG_THERMAL)) { + fan->cdev = thermal_cooling_device_register("mlxreg_fan", fan, + &mlxreg_fan_cooling_ops); + if (IS_ERR(fan->cdev)) { + dev_err(&pdev->dev, "Failed to register cooling device\n"); + return PTR_ERR(fan->cdev); + } + } + + return 0; +} + +static int mlxreg_fan_remove(struct platform_device *pdev) +{ + struct mlxreg_fan *fan = platform_get_drvdata(pdev); + + if (IS_REACHABLE(CONFIG_THERMAL)) + thermal_cooling_device_unregister(fan->cdev); + + return 0; +} + +static struct platform_driver mlxreg_fan_driver = { + .driver = { + .name = "mlxreg-fan", + }, + .probe = mlxreg_fan_probe, + .remove = mlxreg_fan_remove, +}; + +module_platform_driver(mlxreg_fan_driver); + +MODULE_AUTHOR("Vadim Pasternak "); +MODULE_DESCRIPTION("Mellanox FAN driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mlxreg-fan"); diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c new file mode 100644 index 000000000..a753464a1 --- /dev/null +++ b/drivers/hwmon/nct6683.c @@ -0,0 +1,1502 @@ +/* + * nct6683 - Driver for the hardware monitoring functionality of + * Nuvoton NCT6683D eSIO + * + * Copyright (C) 2013 Guenter Roeck + * + * Derived from nct6775 driver + * Copyright (C) 2012, 2013 Guenter Roeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Supports the following chips: + * + * Chip #vin #fan #pwm #temp chip ID + * nct6683d 21(1) 16 8 32(1) 0xc730 + * + * Notes: + * (1) Total number of vin and temp inputs is 32. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum kinds { nct6683 }; + +static bool force; +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Set to one to enable support for unknown vendors"); + +static const char * const nct6683_device_names[] = { + "nct6683", +}; + +static const char * const nct6683_chip_names[] = { + "NCT6683D", +}; + +#define DRVNAME "nct6683" + +/* + * Super-I/O constants and functions + */ + +#define NCT6683_LD_ACPI 0x0a +#define NCT6683_LD_HWM 0x0b +#define NCT6683_LD_VID 0x0d + +#define SIO_REG_LDSEL 0x07 /* Logical device select */ +#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ +#define SIO_REG_ENABLE 0x30 /* Logical device enable */ +#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ + +#define SIO_NCT6681_ID 0xb270 /* for later */ +#define SIO_NCT6683_ID 0xc730 +#define SIO_ID_MASK 0xFFF0 + +static inline void +superio_outb(int ioreg, int reg, int val) +{ + outb(reg, ioreg); + outb(val, ioreg + 1); +} + +static inline int +superio_inb(int ioreg, int reg) +{ + outb(reg, ioreg); + return inb(ioreg + 1); +} + +static inline void +superio_select(int ioreg, int ld) +{ + outb(SIO_REG_LDSEL, ioreg); + outb(ld, ioreg + 1); +} + +static inline int +superio_enter(int ioreg) +{ + /* + * Try to reserve and for exclusive access. + */ + if (!request_muxed_region(ioreg, 2, DRVNAME)) + return -EBUSY; + + outb(0x87, ioreg); + outb(0x87, ioreg); + + return 0; +} + +static inline void +superio_exit(int ioreg) +{ + outb(0xaa, ioreg); + outb(0x02, ioreg); + outb(0x02, ioreg + 1); + release_region(ioreg, 2); +} + +/* + * ISA constants + */ + +#define IOREGION_ALIGNMENT (~7) +#define IOREGION_OFFSET 4 /* Use EC port 1 */ +#define IOREGION_LENGTH 4 + +#define EC_PAGE_REG 0 +#define EC_INDEX_REG 1 +#define EC_DATA_REG 2 +#define EC_EVENT_REG 3 + +/* Common and NCT6683 specific data */ + +#define NCT6683_NUM_REG_MON 32 +#define NCT6683_NUM_REG_FAN 16 +#define NCT6683_NUM_REG_PWM 8 + +#define NCT6683_REG_MON(x) (0x100 + (x) * 2) +#define NCT6683_REG_FAN_RPM(x) (0x140 + (x) * 2) +#define NCT6683_REG_PWM(x) (0x160 + (x)) +#define NCT6683_REG_PWM_WRITE(x) (0xa28 + (x)) + +#define NCT6683_REG_MON_STS(x) (0x174 + (x)) +#define NCT6683_REG_IDLE(x) (0x178 + (x)) + +#define NCT6683_REG_FAN_STS(x) (0x17c + (x)) +#define NCT6683_REG_FAN_ERRSTS 0x17e +#define NCT6683_REG_FAN_INITSTS 0x17f + +#define NCT6683_HWM_CFG 0x180 + +#define NCT6683_REG_MON_CFG(x) (0x1a0 + (x)) +#define NCT6683_REG_FANIN_CFG(x) (0x1c0 + (x)) +#define NCT6683_REG_FANOUT_CFG(x) (0x1d0 + (x)) + +#define NCT6683_REG_INTEL_TEMP_MAX(x) (0x901 + (x) * 16) +#define NCT6683_REG_INTEL_TEMP_CRIT(x) (0x90d + (x) * 16) + +#define NCT6683_REG_TEMP_HYST(x) (0x330 + (x)) /* 8 bit */ +#define NCT6683_REG_TEMP_MAX(x) (0x350 + (x)) /* 8 bit */ +#define NCT6683_REG_MON_HIGH(x) (0x370 + (x) * 2) /* 8 bit */ +#define NCT6683_REG_MON_LOW(x) (0x371 + (x) * 2) /* 8 bit */ + +#define NCT6683_REG_FAN_MIN(x) (0x3b8 + (x) * 2) /* 16 bit */ + +#define NCT6683_REG_FAN_CFG_CTRL 0xa01 +#define NCT6683_FAN_CFG_REQ 0x80 +#define NCT6683_FAN_CFG_DONE 0x40 + +#define NCT6683_REG_CUSTOMER_ID 0x602 +#define NCT6683_CUSTOMER_ID_INTEL 0x805 +#define NCT6683_CUSTOMER_ID_MITAC 0xa0e + +#define NCT6683_REG_BUILD_YEAR 0x604 +#define NCT6683_REG_BUILD_MONTH 0x605 +#define NCT6683_REG_BUILD_DAY 0x606 +#define NCT6683_REG_SERIAL 0x607 +#define NCT6683_REG_VERSION_HI 0x608 +#define NCT6683_REG_VERSION_LO 0x609 + +#define NCT6683_REG_CR_CASEOPEN 0xe8 +#define NCT6683_CR_CASEOPEN_MASK (1 << 7) + +#define NCT6683_REG_CR_BEEP 0xe0 +#define NCT6683_CR_BEEP_MASK (1 << 6) + +static const char *const nct6683_mon_label[] = { + NULL, /* disabled */ + "Local", + "Diode 0 (curr)", + "Diode 1 (curr)", + "Diode 2 (curr)", + "Diode 0 (volt)", + "Diode 1 (volt)", + "Diode 2 (volt)", + "Thermistor 14", + "Thermistor 15", + "Thermistor 16", + "Thermistor 0", + "Thermistor 1", + "Thermistor 2", + "Thermistor 3", + "Thermistor 4", + "Thermistor 5", /* 0x10 */ + "Thermistor 6", + "Thermistor 7", + "Thermistor 8", + "Thermistor 9", + "Thermistor 10", + "Thermistor 11", + "Thermistor 12", + "Thermistor 13", + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + "PECI 0.0", /* 0x20 */ + "PECI 1.0", + "PECI 2.0", + "PECI 3.0", + "PECI 0.1", + "PECI 1.1", + "PECI 2.1", + "PECI 3.1", + "PECI DIMM 0", + "PECI DIMM 1", + "PECI DIMM 2", + "PECI DIMM 3", + NULL, NULL, NULL, NULL, + "PCH CPU", /* 0x30 */ + "PCH CHIP", + "PCH CHIP CPU MAX", + "PCH MCH", + "PCH DIMM 0", + "PCH DIMM 1", + "PCH DIMM 2", + "PCH DIMM 3", + "SMBus 0", + "SMBus 1", + "SMBus 2", + "SMBus 3", + "SMBus 4", + "SMBus 5", + "DIMM 0", + "DIMM 1", + "DIMM 2", /* 0x40 */ + "DIMM 3", + "AMD TSI Addr 90h", + "AMD TSI Addr 92h", + "AMD TSI Addr 94h", + "AMD TSI Addr 96h", + "AMD TSI Addr 98h", + "AMD TSI Addr 9ah", + "AMD TSI Addr 9ch", + "AMD TSI Addr 9dh", + NULL, NULL, NULL, NULL, NULL, NULL, + "Virtual 0", /* 0x50 */ + "Virtual 1", + "Virtual 2", + "Virtual 3", + "Virtual 4", + "Virtual 5", + "Virtual 6", + "Virtual 7", + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + "VCC", /* 0x60 voltage sensors */ + "VSB", + "AVSB", + "VTT", + "VBAT", + "VREF", + "VIN0", + "VIN1", + "VIN2", + "VIN3", + "VIN4", + "VIN5", + "VIN6", + "VIN7", + "VIN8", + "VIN9", + "VIN10", + "VIN11", + "VIN12", + "VIN13", + "VIN14", + "VIN15", + "VIN16", +}; + +#define NUM_MON_LABELS ARRAY_SIZE(nct6683_mon_label) +#define MON_VOLTAGE_START 0x60 + +/* ------------------------------------------------------- */ + +struct nct6683_data { + int addr; /* IO base of EC space */ + int sioreg; /* SIO register */ + enum kinds kind; + u16 customer_id; + + struct device *hwmon_dev; + const struct attribute_group *groups[6]; + + int temp_num; /* number of temperature attributes */ + u8 temp_index[NCT6683_NUM_REG_MON]; + u8 temp_src[NCT6683_NUM_REG_MON]; + + u8 in_num; /* number of voltage attributes */ + u8 in_index[NCT6683_NUM_REG_MON]; + u8 in_src[NCT6683_NUM_REG_MON]; + + struct mutex update_lock; /* used to protect sensor updates */ + bool valid; /* true if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* Voltage attribute values */ + u8 in[3][NCT6683_NUM_REG_MON]; /* [0]=in, [1]=in_max, [2]=in_min */ + + /* Temperature attribute values */ + s16 temp_in[NCT6683_NUM_REG_MON]; + s8 temp[4][NCT6683_NUM_REG_MON];/* [0]=min, [1]=max, [2]=hyst, + * [3]=crit + */ + + /* Fan attribute values */ + unsigned int rpm[NCT6683_NUM_REG_FAN]; + u16 fan_min[NCT6683_NUM_REG_FAN]; + u8 fanin_cfg[NCT6683_NUM_REG_FAN]; + u8 fanout_cfg[NCT6683_NUM_REG_FAN]; + u16 have_fan; /* some fan inputs can be disabled */ + + u8 have_pwm; + u8 pwm[NCT6683_NUM_REG_PWM]; + +#ifdef CONFIG_PM + /* Remember extra register values over suspend/resume */ + u8 hwm_cfg; +#endif +}; + +struct nct6683_sio_data { + int sioreg; + enum kinds kind; +}; + +struct sensor_device_template { + struct device_attribute dev_attr; + union { + struct { + u8 nr; + u8 index; + } s; + int index; + } u; + bool s2; /* true if both index and nr are used */ +}; + +struct sensor_device_attr_u { + union { + struct sensor_device_attribute a1; + struct sensor_device_attribute_2 a2; + } u; + char name[32]; +}; + +#define __TEMPLATE_ATTR(_template, _mode, _show, _store) { \ + .attr = {.name = _template, .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +} + +#define SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, _index) \ + { .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \ + .u.index = _index, \ + .s2 = false } + +#define SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store, \ + _nr, _index) \ + { .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \ + .u.s.index = _index, \ + .u.s.nr = _nr, \ + .s2 = true } + +#define SENSOR_TEMPLATE(_name, _template, _mode, _show, _store, _index) \ +static struct sensor_device_template sensor_dev_template_##_name \ + = SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, \ + _index) + +#define SENSOR_TEMPLATE_2(_name, _template, _mode, _show, _store, \ + _nr, _index) \ +static struct sensor_device_template sensor_dev_template_##_name \ + = SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store, \ + _nr, _index) + +struct sensor_template_group { + struct sensor_device_template **templates; + umode_t (*is_visible)(struct kobject *, struct attribute *, int); + int base; +}; + +static struct attribute_group * +nct6683_create_attr_group(struct device *dev, + const struct sensor_template_group *tg, + int repeat) +{ + struct sensor_device_attribute_2 *a2; + struct sensor_device_attribute *a; + struct sensor_device_template **t; + struct sensor_device_attr_u *su; + struct attribute_group *group; + struct attribute **attrs; + int i, j, count; + + if (repeat <= 0) + return ERR_PTR(-EINVAL); + + t = tg->templates; + for (count = 0; *t; t++, count++) + ; + + if (count == 0) + return ERR_PTR(-EINVAL); + + group = devm_kzalloc(dev, sizeof(*group), GFP_KERNEL); + if (group == NULL) + return ERR_PTR(-ENOMEM); + + attrs = devm_kcalloc(dev, repeat * count + 1, sizeof(*attrs), + GFP_KERNEL); + if (attrs == NULL) + return ERR_PTR(-ENOMEM); + + su = devm_kzalloc(dev, array3_size(repeat, count, sizeof(*su)), + GFP_KERNEL); + if (su == NULL) + return ERR_PTR(-ENOMEM); + + group->attrs = attrs; + group->is_visible = tg->is_visible; + + for (i = 0; i < repeat; i++) { + t = tg->templates; + for (j = 0; *t != NULL; j++) { + snprintf(su->name, sizeof(su->name), + (*t)->dev_attr.attr.name, tg->base + i); + if ((*t)->s2) { + a2 = &su->u.a2; + sysfs_attr_init(&a2->dev_attr.attr); + a2->dev_attr.attr.name = su->name; + a2->nr = (*t)->u.s.nr + i; + a2->index = (*t)->u.s.index; + a2->dev_attr.attr.mode = + (*t)->dev_attr.attr.mode; + a2->dev_attr.show = (*t)->dev_attr.show; + a2->dev_attr.store = (*t)->dev_attr.store; + *attrs = &a2->dev_attr.attr; + } else { + a = &su->u.a1; + sysfs_attr_init(&a->dev_attr.attr); + a->dev_attr.attr.name = su->name; + a->index = (*t)->u.index + i; + a->dev_attr.attr.mode = + (*t)->dev_attr.attr.mode; + a->dev_attr.show = (*t)->dev_attr.show; + a->dev_attr.store = (*t)->dev_attr.store; + *attrs = &a->dev_attr.attr; + } + attrs++; + su++; + t++; + } + } + + return group; +} + +/* LSB is 16 mV, except for the following sources, where it is 32 mV */ +#define MON_SRC_VCC 0x60 +#define MON_SRC_VSB 0x61 +#define MON_SRC_AVSB 0x62 +#define MON_SRC_VBAT 0x64 + +static inline long in_from_reg(u16 reg, u8 src) +{ + int scale = 16; + + if (src == MON_SRC_VCC || src == MON_SRC_VSB || src == MON_SRC_AVSB || + src == MON_SRC_VBAT) + scale <<= 1; + return reg * scale; +} + +static inline u16 in_to_reg(u32 val, u8 src) +{ + int scale = 16; + + if (src == MON_SRC_VCC || src == MON_SRC_VSB || src == MON_SRC_AVSB || + src == MON_SRC_VBAT) + scale <<= 1; + + return clamp_val(DIV_ROUND_CLOSEST(val, scale), 0, 127); +} + +static u16 nct6683_read(struct nct6683_data *data, u16 reg) +{ + int res; + + outb_p(0xff, data->addr + EC_PAGE_REG); /* unlock */ + outb_p(reg >> 8, data->addr + EC_PAGE_REG); + outb_p(reg & 0xff, data->addr + EC_INDEX_REG); + res = inb_p(data->addr + EC_DATA_REG); + return res; +} + +static u16 nct6683_read16(struct nct6683_data *data, u16 reg) +{ + return (nct6683_read(data, reg) << 8) | nct6683_read(data, reg + 1); +} + +static void nct6683_write(struct nct6683_data *data, u16 reg, u16 value) +{ + outb_p(0xff, data->addr + EC_PAGE_REG); /* unlock */ + outb_p(reg >> 8, data->addr + EC_PAGE_REG); + outb_p(reg & 0xff, data->addr + EC_INDEX_REG); + outb_p(value & 0xff, data->addr + EC_DATA_REG); +} + +static int get_in_reg(struct nct6683_data *data, int nr, int index) +{ + int ch = data->in_index[index]; + int reg = -EINVAL; + + switch (nr) { + case 0: + reg = NCT6683_REG_MON(ch); + break; + case 1: + if (data->customer_id != NCT6683_CUSTOMER_ID_INTEL) + reg = NCT6683_REG_MON_LOW(ch); + break; + case 2: + if (data->customer_id != NCT6683_CUSTOMER_ID_INTEL) + reg = NCT6683_REG_MON_HIGH(ch); + break; + default: + break; + } + return reg; +} + +static int get_temp_reg(struct nct6683_data *data, int nr, int index) +{ + int ch = data->temp_index[index]; + int reg = -EINVAL; + + switch (data->customer_id) { + case NCT6683_CUSTOMER_ID_INTEL: + switch (nr) { + default: + case 1: /* max */ + reg = NCT6683_REG_INTEL_TEMP_MAX(ch); + break; + case 3: /* crit */ + reg = NCT6683_REG_INTEL_TEMP_CRIT(ch); + break; + } + break; + case NCT6683_CUSTOMER_ID_MITAC: + default: + switch (nr) { + default: + case 0: /* min */ + reg = NCT6683_REG_MON_LOW(ch); + break; + case 1: /* max */ + reg = NCT6683_REG_TEMP_MAX(ch); + break; + case 2: /* hyst */ + reg = NCT6683_REG_TEMP_HYST(ch); + break; + case 3: /* crit */ + reg = NCT6683_REG_MON_HIGH(ch); + break; + } + break; + } + return reg; +} + +static void nct6683_update_pwm(struct device *dev) +{ + struct nct6683_data *data = dev_get_drvdata(dev); + int i; + + for (i = 0; i < NCT6683_NUM_REG_PWM; i++) { + if (!(data->have_pwm & (1 << i))) + continue; + data->pwm[i] = nct6683_read(data, NCT6683_REG_PWM(i)); + } +} + +static struct nct6683_data *nct6683_update_device(struct device *dev) +{ + struct nct6683_data *data = dev_get_drvdata(dev); + int i, j; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + /* Measured voltages and limits */ + for (i = 0; i < data->in_num; i++) { + for (j = 0; j < 3; j++) { + int reg = get_in_reg(data, j, i); + + if (reg >= 0) + data->in[j][i] = + nct6683_read(data, reg); + } + } + + /* Measured temperatures and limits */ + for (i = 0; i < data->temp_num; i++) { + u8 ch = data->temp_index[i]; + + data->temp_in[i] = nct6683_read16(data, + NCT6683_REG_MON(ch)); + for (j = 0; j < 4; j++) { + int reg = get_temp_reg(data, j, i); + + if (reg >= 0) + data->temp[j][i] = + nct6683_read(data, reg); + } + } + + /* Measured fan speeds and limits */ + for (i = 0; i < ARRAY_SIZE(data->rpm); i++) { + if (!(data->have_fan & (1 << i))) + continue; + + data->rpm[i] = nct6683_read16(data, + NCT6683_REG_FAN_RPM(i)); + data->fan_min[i] = nct6683_read16(data, + NCT6683_REG_FAN_MIN(i)); + } + + nct6683_update_pwm(dev); + + data->last_updated = jiffies; + data->valid = true; + } + + mutex_unlock(&data->update_lock); + return data; +} + +/* + * Sysfs callback functions + */ +static ssize_t +show_in_label(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6683_data *data = nct6683_update_device(dev); + int nr = sattr->index; + + return sprintf(buf, "%s\n", nct6683_mon_label[data->in_src[nr]]); +} + +static ssize_t +show_in_reg(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct6683_data *data = nct6683_update_device(dev); + int index = sattr->index; + int nr = sattr->nr; + + return sprintf(buf, "%ld\n", + in_from_reg(data->in[index][nr], data->in_index[index])); +} + +static umode_t nct6683_in_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct6683_data *data = dev_get_drvdata(dev); + int nr = index % 4; /* attribute */ + + /* + * Voltage limits exist for Intel boards, + * but register location and encoding is unknown + */ + if ((nr == 2 || nr == 3) && + data->customer_id == NCT6683_CUSTOMER_ID_INTEL) + return 0; + + return attr->mode; +} + +SENSOR_TEMPLATE(in_label, "in%d_label", S_IRUGO, show_in_label, NULL, 0); +SENSOR_TEMPLATE_2(in_input, "in%d_input", S_IRUGO, show_in_reg, NULL, 0, 0); +SENSOR_TEMPLATE_2(in_min, "in%d_min", S_IRUGO, show_in_reg, NULL, 0, 1); +SENSOR_TEMPLATE_2(in_max, "in%d_max", S_IRUGO, show_in_reg, NULL, 0, 2); + +static struct sensor_device_template *nct6683_attributes_in_template[] = { + &sensor_dev_template_in_label, + &sensor_dev_template_in_input, + &sensor_dev_template_in_min, + &sensor_dev_template_in_max, + NULL +}; + +static const struct sensor_template_group nct6683_in_template_group = { + .templates = nct6683_attributes_in_template, + .is_visible = nct6683_in_is_visible, +}; + +static ssize_t +show_fan(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6683_data *data = nct6683_update_device(dev); + + return sprintf(buf, "%d\n", data->rpm[sattr->index]); +} + +static ssize_t +show_fan_min(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6683_data *data = nct6683_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + + return sprintf(buf, "%d\n", data->fan_min[nr]); +} + +static ssize_t +show_fan_pulses(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6683_data *data = nct6683_update_device(dev); + + return sprintf(buf, "%d\n", + ((data->fanin_cfg[sattr->index] >> 5) & 0x03) + 1); +} + +static umode_t nct6683_fan_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct6683_data *data = dev_get_drvdata(dev); + int fan = index / 3; /* fan index */ + int nr = index % 3; /* attribute index */ + + if (!(data->have_fan & (1 << fan))) + return 0; + + /* + * Intel may have minimum fan speed limits, + * but register location and encoding are unknown. + */ + if (nr == 2 && data->customer_id == NCT6683_CUSTOMER_ID_INTEL) + return 0; + + return attr->mode; +} + +SENSOR_TEMPLATE(fan_input, "fan%d_input", S_IRUGO, show_fan, NULL, 0); +SENSOR_TEMPLATE(fan_pulses, "fan%d_pulses", S_IRUGO, show_fan_pulses, NULL, 0); +SENSOR_TEMPLATE(fan_min, "fan%d_min", S_IRUGO, show_fan_min, NULL, 0); + +/* + * nct6683_fan_is_visible uses the index into the following array + * to determine if attributes should be created or not. + * Any change in order or content must be matched. + */ +static struct sensor_device_template *nct6683_attributes_fan_template[] = { + &sensor_dev_template_fan_input, + &sensor_dev_template_fan_pulses, + &sensor_dev_template_fan_min, + NULL +}; + +static const struct sensor_template_group nct6683_fan_template_group = { + .templates = nct6683_attributes_fan_template, + .is_visible = nct6683_fan_is_visible, + .base = 1, +}; + +static ssize_t +show_temp_label(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6683_data *data = nct6683_update_device(dev); + int nr = sattr->index; + + return sprintf(buf, "%s\n", nct6683_mon_label[data->temp_src[nr]]); +} + +static ssize_t +show_temp8(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct6683_data *data = nct6683_update_device(dev); + int index = sattr->index; + int nr = sattr->nr; + + return sprintf(buf, "%d\n", data->temp[index][nr] * 1000); +} + +static ssize_t +show_temp_hyst(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6683_data *data = nct6683_update_device(dev); + int nr = sattr->index; + int temp = data->temp[1][nr] - data->temp[2][nr]; + + return sprintf(buf, "%d\n", temp * 1000); +} + +static ssize_t +show_temp16(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6683_data *data = nct6683_update_device(dev); + int index = sattr->index; + + return sprintf(buf, "%d\n", (data->temp_in[index] / 128) * 500); +} + +/* + * Temperature sensor type is determined by temperature source + * and can not be modified. + * 0x02..0x07: Thermal diode + * 0x08..0x18: Thermistor + * 0x20..0x2b: Intel PECI + * 0x42..0x49: AMD TSI + * Others are unspecified (not visible) + */ + +static int get_temp_type(u8 src) +{ + if (src >= 0x02 && src <= 0x07) + return 3; /* thermal diode */ + else if (src >= 0x08 && src <= 0x18) + return 4; /* thermistor */ + else if (src >= 0x20 && src <= 0x2b) + return 6; /* PECI */ + else if (src >= 0x42 && src <= 0x49) + return 5; + + return 0; +} + +static ssize_t +show_temp_type(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6683_data *data = nct6683_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + return sprintf(buf, "%d\n", get_temp_type(data->temp_src[nr])); +} + +static umode_t nct6683_temp_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct6683_data *data = dev_get_drvdata(dev); + int temp = index / 7; /* temp index */ + int nr = index % 7; /* attribute index */ + + /* + * Intel does not have low temperature limits or temperature hysteresis + * registers, or at least register location and encoding is unknown. + */ + if ((nr == 2 || nr == 4) && + data->customer_id == NCT6683_CUSTOMER_ID_INTEL) + return 0; + + if (nr == 6 && get_temp_type(data->temp_src[temp]) == 0) + return 0; /* type */ + + return attr->mode; +} + +SENSOR_TEMPLATE(temp_input, "temp%d_input", S_IRUGO, show_temp16, NULL, 0); +SENSOR_TEMPLATE(temp_label, "temp%d_label", S_IRUGO, show_temp_label, NULL, 0); +SENSOR_TEMPLATE_2(temp_min, "temp%d_min", S_IRUGO, show_temp8, NULL, 0, 0); +SENSOR_TEMPLATE_2(temp_max, "temp%d_max", S_IRUGO, show_temp8, NULL, 0, 1); +SENSOR_TEMPLATE(temp_max_hyst, "temp%d_max_hyst", S_IRUGO, show_temp_hyst, NULL, + 0); +SENSOR_TEMPLATE_2(temp_crit, "temp%d_crit", S_IRUGO, show_temp8, NULL, 0, 3); +SENSOR_TEMPLATE(temp_type, "temp%d_type", S_IRUGO, show_temp_type, NULL, 0); + +/* + * nct6683_temp_is_visible uses the index into the following array + * to determine if attributes should be created or not. + * Any change in order or content must be matched. + */ +static struct sensor_device_template *nct6683_attributes_temp_template[] = { + &sensor_dev_template_temp_input, + &sensor_dev_template_temp_label, + &sensor_dev_template_temp_min, /* 2 */ + &sensor_dev_template_temp_max, /* 3 */ + &sensor_dev_template_temp_max_hyst, /* 4 */ + &sensor_dev_template_temp_crit, /* 5 */ + &sensor_dev_template_temp_type, /* 6 */ + NULL +}; + +static const struct sensor_template_group nct6683_temp_template_group = { + .templates = nct6683_attributes_temp_template, + .is_visible = nct6683_temp_is_visible, + .base = 1, +}; + +static ssize_t +show_pwm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6683_data *data = nct6683_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int index = sattr->index; + + return sprintf(buf, "%d\n", data->pwm[index]); +} + +static ssize_t +store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct6683_data *data = dev_get_drvdata(dev); + int index = sattr->index; + unsigned long val; + + if (kstrtoul(buf, 10, &val) || val > 255) + return -EINVAL; + + mutex_lock(&data->update_lock); + nct6683_write(data, NCT6683_REG_FAN_CFG_CTRL, NCT6683_FAN_CFG_REQ); + usleep_range(1000, 2000); + nct6683_write(data, NCT6683_REG_PWM_WRITE(index), val); + nct6683_write(data, NCT6683_REG_FAN_CFG_CTRL, NCT6683_FAN_CFG_DONE); + mutex_unlock(&data->update_lock); + + return count; +} + +SENSOR_TEMPLATE(pwm, "pwm%d", S_IRUGO, show_pwm, store_pwm, 0); + +static umode_t nct6683_pwm_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct6683_data *data = dev_get_drvdata(dev); + int pwm = index; /* pwm index */ + + if (!(data->have_pwm & (1 << pwm))) + return 0; + + /* Only update pwm values for Mitac boards */ + if (data->customer_id == NCT6683_CUSTOMER_ID_MITAC) + return attr->mode | S_IWUSR; + + return attr->mode; +} + +static struct sensor_device_template *nct6683_attributes_pwm_template[] = { + &sensor_dev_template_pwm, + NULL +}; + +static const struct sensor_template_group nct6683_pwm_template_group = { + .templates = nct6683_attributes_pwm_template, + .is_visible = nct6683_pwm_is_visible, + .base = 1, +}; + +static ssize_t +beep_enable_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6683_data *data = dev_get_drvdata(dev); + int ret; + u8 reg; + + mutex_lock(&data->update_lock); + + ret = superio_enter(data->sioreg); + if (ret) + goto error; + superio_select(data->sioreg, NCT6683_LD_HWM); + reg = superio_inb(data->sioreg, NCT6683_REG_CR_BEEP); + superio_exit(data->sioreg); + + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", !!(reg & NCT6683_CR_BEEP_MASK)); + +error: + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t +beep_enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6683_data *data = dev_get_drvdata(dev); + unsigned long val; + u8 reg; + int ret; + + if (kstrtoul(buf, 10, &val) || (val != 0 && val != 1)) + return -EINVAL; + + mutex_lock(&data->update_lock); + + ret = superio_enter(data->sioreg); + if (ret) { + count = ret; + goto error; + } + + superio_select(data->sioreg, NCT6683_LD_HWM); + reg = superio_inb(data->sioreg, NCT6683_REG_CR_BEEP); + if (val) + reg |= NCT6683_CR_BEEP_MASK; + else + reg &= ~NCT6683_CR_BEEP_MASK; + superio_outb(data->sioreg, NCT6683_REG_CR_BEEP, reg); + superio_exit(data->sioreg); +error: + mutex_unlock(&data->update_lock); + return count; +} + +/* Case open detection */ + +static ssize_t +intrusion0_alarm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nct6683_data *data = dev_get_drvdata(dev); + int ret; + u8 reg; + + mutex_lock(&data->update_lock); + + ret = superio_enter(data->sioreg); + if (ret) + goto error; + superio_select(data->sioreg, NCT6683_LD_ACPI); + reg = superio_inb(data->sioreg, NCT6683_REG_CR_CASEOPEN); + superio_exit(data->sioreg); + + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", !(reg & NCT6683_CR_CASEOPEN_MASK)); + +error: + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t +intrusion0_alarm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6683_data *data = dev_get_drvdata(dev); + unsigned long val; + u8 reg; + int ret; + + if (kstrtoul(buf, 10, &val) || val != 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + + /* + * Use CR registers to clear caseopen status. + * Caseopen is activ low, clear by writing 1 into the register. + */ + + ret = superio_enter(data->sioreg); + if (ret) { + count = ret; + goto error; + } + + superio_select(data->sioreg, NCT6683_LD_ACPI); + reg = superio_inb(data->sioreg, NCT6683_REG_CR_CASEOPEN); + reg |= NCT6683_CR_CASEOPEN_MASK; + superio_outb(data->sioreg, NCT6683_REG_CR_CASEOPEN, reg); + reg &= ~NCT6683_CR_CASEOPEN_MASK; + superio_outb(data->sioreg, NCT6683_REG_CR_CASEOPEN, reg); + superio_exit(data->sioreg); + + data->valid = false; /* Force cache refresh */ +error: + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RW(intrusion0_alarm); +static DEVICE_ATTR_RW(beep_enable); + +static struct attribute *nct6683_attributes_other[] = { + &dev_attr_intrusion0_alarm.attr, + &dev_attr_beep_enable.attr, + NULL +}; + +static const struct attribute_group nct6683_group_other = { + .attrs = nct6683_attributes_other, +}; + +/* Get the monitoring functions started */ +static inline void nct6683_init_device(struct nct6683_data *data) +{ + u8 tmp; + + /* Start hardware monitoring if needed */ + tmp = nct6683_read(data, NCT6683_HWM_CFG); + if (!(tmp & 0x80)) + nct6683_write(data, NCT6683_HWM_CFG, tmp | 0x80); +} + +/* + * There are a total of 24 fan inputs. Each can be configured as input + * or as output. A maximum of 16 inputs and 8 outputs is configurable. + */ +static void +nct6683_setup_fans(struct nct6683_data *data) +{ + int i; + u8 reg; + + for (i = 0; i < NCT6683_NUM_REG_FAN; i++) { + reg = nct6683_read(data, NCT6683_REG_FANIN_CFG(i)); + if (reg & 0x80) + data->have_fan |= 1 << i; + data->fanin_cfg[i] = reg; + } + for (i = 0; i < NCT6683_NUM_REG_PWM; i++) { + reg = nct6683_read(data, NCT6683_REG_FANOUT_CFG(i)); + if (reg & 0x80) + data->have_pwm |= 1 << i; + data->fanout_cfg[i] = reg; + } +} + +/* + * Translation from monitoring register to temperature and voltage attributes + * ========================================================================== + * + * There are a total of 32 monitoring registers. Each can be assigned to either + * a temperature or voltage monitoring source. + * NCT6683_REG_MON_CFG(x) defines assignment for each monitoring source. + * + * Temperature and voltage attribute mapping is determined by walking through + * the NCT6683_REG_MON_CFG registers. If the assigned source is + * a temperature, temp_index[n] is set to the monitor register index, and + * temp_src[n] is set to the temperature source. If the assigned source is + * a voltage, the respective values are stored in in_index[] and in_src[], + * respectively. + */ + +static void nct6683_setup_sensors(struct nct6683_data *data) +{ + u8 reg; + int i; + + data->temp_num = 0; + data->in_num = 0; + for (i = 0; i < NCT6683_NUM_REG_MON; i++) { + reg = nct6683_read(data, NCT6683_REG_MON_CFG(i)) & 0x7f; + /* Ignore invalid assignments */ + if (reg >= NUM_MON_LABELS) + continue; + /* Skip if disabled or reserved */ + if (nct6683_mon_label[reg] == NULL) + continue; + if (reg < MON_VOLTAGE_START) { + data->temp_index[data->temp_num] = i; + data->temp_src[data->temp_num] = reg; + data->temp_num++; + } else { + data->in_index[data->in_num] = i; + data->in_src[data->in_num] = reg; + data->in_num++; + } + } +} + +static int nct6683_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nct6683_sio_data *sio_data = dev->platform_data; + struct attribute_group *group; + struct nct6683_data *data; + struct device *hwmon_dev; + struct resource *res; + int groups = 0; + char build[16]; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(dev, res->start, IOREGION_LENGTH, DRVNAME)) + return -EBUSY; + + data = devm_kzalloc(dev, sizeof(struct nct6683_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->kind = sio_data->kind; + data->sioreg = sio_data->sioreg; + data->addr = res->start; + mutex_init(&data->update_lock); + platform_set_drvdata(pdev, data); + + data->customer_id = nct6683_read16(data, NCT6683_REG_CUSTOMER_ID); + + /* By default only instantiate driver if the customer ID is known */ + switch (data->customer_id) { + case NCT6683_CUSTOMER_ID_INTEL: + break; + case NCT6683_CUSTOMER_ID_MITAC: + break; + default: + if (!force) + return -ENODEV; + } + + nct6683_init_device(data); + nct6683_setup_fans(data); + nct6683_setup_sensors(data); + + /* Register sysfs hooks */ + + if (data->have_pwm) { + group = nct6683_create_attr_group(dev, + &nct6683_pwm_template_group, + fls(data->have_pwm)); + if (IS_ERR(group)) + return PTR_ERR(group); + data->groups[groups++] = group; + } + + if (data->in_num) { + group = nct6683_create_attr_group(dev, + &nct6683_in_template_group, + data->in_num); + if (IS_ERR(group)) + return PTR_ERR(group); + data->groups[groups++] = group; + } + + if (data->have_fan) { + group = nct6683_create_attr_group(dev, + &nct6683_fan_template_group, + fls(data->have_fan)); + if (IS_ERR(group)) + return PTR_ERR(group); + data->groups[groups++] = group; + } + + if (data->temp_num) { + group = nct6683_create_attr_group(dev, + &nct6683_temp_template_group, + data->temp_num); + if (IS_ERR(group)) + return PTR_ERR(group); + data->groups[groups++] = group; + } + data->groups[groups++] = &nct6683_group_other; + + if (data->customer_id == NCT6683_CUSTOMER_ID_INTEL) + scnprintf(build, sizeof(build), "%02x/%02x/%02x", + nct6683_read(data, NCT6683_REG_BUILD_MONTH), + nct6683_read(data, NCT6683_REG_BUILD_DAY), + nct6683_read(data, NCT6683_REG_BUILD_YEAR)); + else + scnprintf(build, sizeof(build), "%02d/%02d/%02d", + nct6683_read(data, NCT6683_REG_BUILD_MONTH), + nct6683_read(data, NCT6683_REG_BUILD_DAY), + nct6683_read(data, NCT6683_REG_BUILD_YEAR)); + + dev_info(dev, "%s EC firmware version %d.%d build %s\n", + nct6683_chip_names[data->kind], + nct6683_read(data, NCT6683_REG_VERSION_HI), + nct6683_read(data, NCT6683_REG_VERSION_LO), + build); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + nct6683_device_names[data->kind], data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +#ifdef CONFIG_PM +static int nct6683_suspend(struct device *dev) +{ + struct nct6683_data *data = nct6683_update_device(dev); + + mutex_lock(&data->update_lock); + data->hwm_cfg = nct6683_read(data, NCT6683_HWM_CFG); + mutex_unlock(&data->update_lock); + + return 0; +} + +static int nct6683_resume(struct device *dev) +{ + struct nct6683_data *data = dev_get_drvdata(dev); + + mutex_lock(&data->update_lock); + + nct6683_write(data, NCT6683_HWM_CFG, data->hwm_cfg); + + /* Force re-reading all values */ + data->valid = false; + mutex_unlock(&data->update_lock); + + return 0; +} + +static const struct dev_pm_ops nct6683_dev_pm_ops = { + .suspend = nct6683_suspend, + .resume = nct6683_resume, + .freeze = nct6683_suspend, + .restore = nct6683_resume, +}; + +#define NCT6683_DEV_PM_OPS (&nct6683_dev_pm_ops) +#else +#define NCT6683_DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + +static struct platform_driver nct6683_driver = { + .driver = { + .name = DRVNAME, + .pm = NCT6683_DEV_PM_OPS, + }, + .probe = nct6683_probe, +}; + +static int __init nct6683_find(int sioaddr, struct nct6683_sio_data *sio_data) +{ + int addr; + u16 val; + int err; + + err = superio_enter(sioaddr); + if (err) + return err; + + val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) + | superio_inb(sioaddr, SIO_REG_DEVID + 1); + + switch (val & SIO_ID_MASK) { + case SIO_NCT6683_ID: + sio_data->kind = nct6683; + break; + default: + if (val != 0xffff) + pr_debug("unsupported chip ID: 0x%04x\n", val); + goto fail; + } + + /* We have a known chip, find the HWM I/O address */ + superio_select(sioaddr, NCT6683_LD_HWM); + val = (superio_inb(sioaddr, SIO_REG_ADDR) << 8) + | superio_inb(sioaddr, SIO_REG_ADDR + 1); + addr = val & IOREGION_ALIGNMENT; + if (addr == 0) { + pr_err("EC base I/O port unconfigured\n"); + goto fail; + } + + /* Activate logical device if needed */ + val = superio_inb(sioaddr, SIO_REG_ENABLE); + if (!(val & 0x01)) { + pr_warn("Forcibly enabling EC access. Data may be unusable.\n"); + superio_outb(sioaddr, SIO_REG_ENABLE, val | 0x01); + } + + superio_exit(sioaddr); + pr_info("Found %s or compatible chip at %#x:%#x\n", + nct6683_chip_names[sio_data->kind], sioaddr, addr); + sio_data->sioreg = sioaddr; + + return addr; + +fail: + superio_exit(sioaddr); + return -ENODEV; +} + +/* + * when Super-I/O functions move to a separate file, the Super-I/O + * bus will manage the lifetime of the device and this module will only keep + * track of the nct6683 driver. But since we use platform_device_alloc(), we + * must keep track of the device + */ +static struct platform_device *pdev[2]; + +static int __init sensors_nct6683_init(void) +{ + struct nct6683_sio_data sio_data; + int sioaddr[2] = { 0x2e, 0x4e }; + struct resource res; + bool found = false; + int address; + int i, err; + + err = platform_driver_register(&nct6683_driver); + if (err) + return err; + + /* + * initialize sio_data->kind and sio_data->sioreg. + * + * when Super-I/O functions move to a separate file, the Super-I/O + * driver will probe 0x2e and 0x4e and auto-detect the presence of a + * nct6683 hardware monitor, and call probe() + */ + for (i = 0; i < ARRAY_SIZE(pdev); i++) { + address = nct6683_find(sioaddr[i], &sio_data); + if (address <= 0) + continue; + + found = true; + + pdev[i] = platform_device_alloc(DRVNAME, address); + if (!pdev[i]) { + err = -ENOMEM; + goto exit_device_unregister; + } + + err = platform_device_add_data(pdev[i], &sio_data, + sizeof(struct nct6683_sio_data)); + if (err) + goto exit_device_put; + + memset(&res, 0, sizeof(res)); + res.name = DRVNAME; + res.start = address + IOREGION_OFFSET; + res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1; + res.flags = IORESOURCE_IO; + + err = acpi_check_resource_conflict(&res); + if (err) { + platform_device_put(pdev[i]); + pdev[i] = NULL; + continue; + } + + err = platform_device_add_resources(pdev[i], &res, 1); + if (err) + goto exit_device_put; + + /* platform_device_add calls probe() */ + err = platform_device_add(pdev[i]); + if (err) + goto exit_device_put; + } + if (!found) { + err = -ENODEV; + goto exit_unregister; + } + + return 0; + +exit_device_put: + platform_device_put(pdev[i]); +exit_device_unregister: + while (--i >= 0) { + if (pdev[i]) + platform_device_unregister(pdev[i]); + } +exit_unregister: + platform_driver_unregister(&nct6683_driver); + return err; +} + +static void __exit sensors_nct6683_exit(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pdev); i++) { + if (pdev[i]) + platform_device_unregister(pdev[i]); + } + platform_driver_unregister(&nct6683_driver); +} + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("NCT6683D driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_nct6683_init); +module_exit(sensors_nct6683_exit); diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c new file mode 100644 index 000000000..559101a1c --- /dev/null +++ b/drivers/hwmon/nct6775.c @@ -0,0 +1,4638 @@ +/* + * nct6775 - Driver for the hardware monitoring functionality of + * Nuvoton NCT677x Super-I/O chips + * + * Copyright (C) 2012 Guenter Roeck + * + * Derived from w83627ehf driver + * Copyright (C) 2005-2012 Jean Delvare + * Copyright (C) 2006 Yuan Mu (Winbond), + * Rudolf Marek + * David Hubbard + * Daniel J Blueman + * Copyright (C) 2010 Sheng-Yuan Huang (Nuvoton) (PS00) + * + * Shamelessly ripped from the w83627hf driver + * Copyright (C) 2003 Mark Studebaker + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + * Supports the following chips: + * + * Chip #vin #fan #pwm #temp chip IDs man ID + * nct6106d 9 3 3 6+3 0xc450 0xc1 0x5ca3 + * nct6775f 9 4 3 6+3 0xb470 0xc1 0x5ca3 + * nct6776f 9 5 3 6+3 0xc330 0xc1 0x5ca3 + * nct6779d 15 5 5 2+6 0xc560 0xc1 0x5ca3 + * nct6791d 15 6 6 2+6 0xc800 0xc1 0x5ca3 + * nct6792d 15 6 6 2+6 0xc910 0xc1 0x5ca3 + * nct6793d 15 6 6 2+6 0xd120 0xc1 0x5ca3 + * nct6795d 14 6 6 2+6 0xd350 0xc1 0x5ca3 + * nct6796d 14 7 7 2+6 0xd420 0xc1 0x5ca3 + * + * #temp lists the number of monitored temperature sources (first value) plus + * the number of directly connectable temperature sensors (second value). + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lm75.h" + +#define USE_ALTERNATE + +enum kinds { nct6106, nct6775, nct6776, nct6779, nct6791, nct6792, nct6793, + nct6795, nct6796 }; + +/* used to set data->name = nct6775_device_names[data->sio_kind] */ +static const char * const nct6775_device_names[] = { + "nct6106", + "nct6775", + "nct6776", + "nct6779", + "nct6791", + "nct6792", + "nct6793", + "nct6795", + "nct6796", +}; + +static const char * const nct6775_sio_names[] __initconst = { + "NCT6106D", + "NCT6775F", + "NCT6776D/F", + "NCT6779D", + "NCT6791D", + "NCT6792D", + "NCT6793D", + "NCT6795D", + "NCT6796D", +}; + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +static unsigned short fan_debounce; +module_param(fan_debounce, ushort, 0); +MODULE_PARM_DESC(fan_debounce, "Enable debouncing for fan RPM signal"); + +#define DRVNAME "nct6775" + +/* + * Super-I/O constants and functions + */ + +#define NCT6775_LD_ACPI 0x0a +#define NCT6775_LD_HWM 0x0b +#define NCT6775_LD_VID 0x0d +#define NCT6775_LD_12 0x12 + +#define SIO_REG_LDSEL 0x07 /* Logical device select */ +#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ +#define SIO_REG_ENABLE 0x30 /* Logical device enable */ +#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ + +#define SIO_NCT6106_ID 0xc450 +#define SIO_NCT6775_ID 0xb470 +#define SIO_NCT6776_ID 0xc330 +#define SIO_NCT6779_ID 0xc560 +#define SIO_NCT6791_ID 0xc800 +#define SIO_NCT6792_ID 0xc910 +#define SIO_NCT6793_ID 0xd120 +#define SIO_NCT6795_ID 0xd350 +#define SIO_NCT6796_ID 0xd420 +#define SIO_ID_MASK 0xFFF0 + +enum pwm_enable { off, manual, thermal_cruise, speed_cruise, sf3, sf4 }; + +static inline void +superio_outb(int ioreg, int reg, int val) +{ + outb(reg, ioreg); + outb(val, ioreg + 1); +} + +static inline int +superio_inb(int ioreg, int reg) +{ + outb(reg, ioreg); + return inb(ioreg + 1); +} + +static inline void +superio_select(int ioreg, int ld) +{ + outb(SIO_REG_LDSEL, ioreg); + outb(ld, ioreg + 1); +} + +static inline int +superio_enter(int ioreg) +{ + /* + * Try to reserve and for exclusive access. + */ + if (!request_muxed_region(ioreg, 2, DRVNAME)) + return -EBUSY; + + outb(0x87, ioreg); + outb(0x87, ioreg); + + return 0; +} + +static inline void +superio_exit(int ioreg) +{ + outb(0xaa, ioreg); + outb(0x02, ioreg); + outb(0x02, ioreg + 1); + release_region(ioreg, 2); +} + +/* + * ISA constants + */ + +#define IOREGION_ALIGNMENT (~7) +#define IOREGION_OFFSET 5 +#define IOREGION_LENGTH 2 +#define ADDR_REG_OFFSET 0 +#define DATA_REG_OFFSET 1 + +#define NCT6775_REG_BANK 0x4E +#define NCT6775_REG_CONFIG 0x40 + +/* + * Not currently used: + * REG_MAN_ID has the value 0x5ca3 for all supported chips. + * REG_CHIP_ID == 0x88/0xa1/0xc1 depending on chip model. + * REG_MAN_ID is at port 0x4f + * REG_CHIP_ID is at port 0x58 + */ + +#define NUM_TEMP 10 /* Max number of temp attribute sets w/ limits*/ +#define NUM_TEMP_FIXED 6 /* Max number of fixed temp attribute sets */ + +#define NUM_REG_ALARM 7 /* Max number of alarm registers */ +#define NUM_REG_BEEP 5 /* Max number of beep registers */ + +#define NUM_FAN 7 + +/* Common and NCT6775 specific data */ + +/* Voltage min/max registers for nr=7..14 are in bank 5 */ + +static const u16 NCT6775_REG_IN_MAX[] = { + 0x2b, 0x2d, 0x2f, 0x31, 0x33, 0x35, 0x37, 0x554, 0x556, 0x558, 0x55a, + 0x55c, 0x55e, 0x560, 0x562 }; +static const u16 NCT6775_REG_IN_MIN[] = { + 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x555, 0x557, 0x559, 0x55b, + 0x55d, 0x55f, 0x561, 0x563 }; +static const u16 NCT6775_REG_IN[] = { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x550, 0x551, 0x552 +}; + +#define NCT6775_REG_VBAT 0x5D +#define NCT6775_REG_DIODE 0x5E +#define NCT6775_DIODE_MASK 0x02 + +#define NCT6775_REG_FANDIV1 0x506 +#define NCT6775_REG_FANDIV2 0x507 + +#define NCT6775_REG_CR_FAN_DEBOUNCE 0xf0 + +static const u16 NCT6775_REG_ALARM[NUM_REG_ALARM] = { 0x459, 0x45A, 0x45B }; + +/* 0..15 voltages, 16..23 fans, 24..29 temperatures, 30..31 intrusion */ + +static const s8 NCT6775_ALARM_BITS[] = { + 0, 1, 2, 3, 8, 21, 20, 16, /* in0.. in7 */ + 17, -1, -1, -1, -1, -1, -1, /* in8..in14 */ + -1, /* unused */ + 6, 7, 11, -1, -1, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ + 12, -1 }; /* intrusion0, intrusion1 */ + +#define FAN_ALARM_BASE 16 +#define TEMP_ALARM_BASE 24 +#define INTRUSION_ALARM_BASE 30 + +static const u16 NCT6775_REG_BEEP[NUM_REG_BEEP] = { 0x56, 0x57, 0x453, 0x4e }; + +/* + * 0..14 voltages, 15 global beep enable, 16..23 fans, 24..29 temperatures, + * 30..31 intrusion + */ +static const s8 NCT6775_BEEP_BITS[] = { + 0, 1, 2, 3, 8, 9, 10, 16, /* in0.. in7 */ + 17, -1, -1, -1, -1, -1, -1, /* in8..in14 */ + 21, /* global beep enable */ + 6, 7, 11, 28, -1, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ + 12, -1 }; /* intrusion0, intrusion1 */ + +#define BEEP_ENABLE_BASE 15 + +static const u8 NCT6775_REG_CR_CASEOPEN_CLR[] = { 0xe6, 0xee }; +static const u8 NCT6775_CR_CASEOPEN_CLR_MASK[] = { 0x20, 0x01 }; + +/* DC or PWM output fan configuration */ +static const u8 NCT6775_REG_PWM_MODE[] = { 0x04, 0x04, 0x12 }; +static const u8 NCT6775_PWM_MODE_MASK[] = { 0x01, 0x02, 0x01 }; + +/* Advanced Fan control, some values are common for all fans */ + +static const u16 NCT6775_REG_TARGET[] = { + 0x101, 0x201, 0x301, 0x801, 0x901, 0xa01, 0xb01 }; +static const u16 NCT6775_REG_FAN_MODE[] = { + 0x102, 0x202, 0x302, 0x802, 0x902, 0xa02, 0xb02 }; +static const u16 NCT6775_REG_FAN_STEP_DOWN_TIME[] = { + 0x103, 0x203, 0x303, 0x803, 0x903, 0xa03, 0xb03 }; +static const u16 NCT6775_REG_FAN_STEP_UP_TIME[] = { + 0x104, 0x204, 0x304, 0x804, 0x904, 0xa04, 0xb04 }; +static const u16 NCT6775_REG_FAN_STOP_OUTPUT[] = { + 0x105, 0x205, 0x305, 0x805, 0x905, 0xa05, 0xb05 }; +static const u16 NCT6775_REG_FAN_START_OUTPUT[] = { + 0x106, 0x206, 0x306, 0x806, 0x906, 0xa06, 0xb06 }; +static const u16 NCT6775_REG_FAN_MAX_OUTPUT[] = { 0x10a, 0x20a, 0x30a }; +static const u16 NCT6775_REG_FAN_STEP_OUTPUT[] = { 0x10b, 0x20b, 0x30b }; + +static const u16 NCT6775_REG_FAN_STOP_TIME[] = { + 0x107, 0x207, 0x307, 0x807, 0x907, 0xa07, 0xb07 }; +static const u16 NCT6775_REG_PWM[] = { + 0x109, 0x209, 0x309, 0x809, 0x909, 0xa09, 0xb09 }; +static const u16 NCT6775_REG_PWM_READ[] = { + 0x01, 0x03, 0x11, 0x13, 0x15, 0xa09, 0xb09 }; + +static const u16 NCT6775_REG_FAN[] = { 0x630, 0x632, 0x634, 0x636, 0x638 }; +static const u16 NCT6775_REG_FAN_MIN[] = { 0x3b, 0x3c, 0x3d }; +static const u16 NCT6775_REG_FAN_PULSES[NUM_FAN] = { + 0x641, 0x642, 0x643, 0x644 }; +static const u16 NCT6775_FAN_PULSE_SHIFT[NUM_FAN] = { }; + +static const u16 NCT6775_REG_TEMP[] = { + 0x27, 0x150, 0x250, 0x62b, 0x62c, 0x62d }; + +static const u16 NCT6775_REG_TEMP_MON[] = { 0x73, 0x75, 0x77 }; + +static const u16 NCT6775_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6775_REG_TEMP)] = { + 0, 0x152, 0x252, 0x628, 0x629, 0x62A }; +static const u16 NCT6775_REG_TEMP_HYST[ARRAY_SIZE(NCT6775_REG_TEMP)] = { + 0x3a, 0x153, 0x253, 0x673, 0x678, 0x67D }; +static const u16 NCT6775_REG_TEMP_OVER[ARRAY_SIZE(NCT6775_REG_TEMP)] = { + 0x39, 0x155, 0x255, 0x672, 0x677, 0x67C }; + +static const u16 NCT6775_REG_TEMP_SOURCE[ARRAY_SIZE(NCT6775_REG_TEMP)] = { + 0x621, 0x622, 0x623, 0x624, 0x625, 0x626 }; + +static const u16 NCT6775_REG_TEMP_SEL[] = { + 0x100, 0x200, 0x300, 0x800, 0x900, 0xa00, 0xb00 }; + +static const u16 NCT6775_REG_WEIGHT_TEMP_SEL[] = { + 0x139, 0x239, 0x339, 0x839, 0x939, 0xa39 }; +static const u16 NCT6775_REG_WEIGHT_TEMP_STEP[] = { + 0x13a, 0x23a, 0x33a, 0x83a, 0x93a, 0xa3a }; +static const u16 NCT6775_REG_WEIGHT_TEMP_STEP_TOL[] = { + 0x13b, 0x23b, 0x33b, 0x83b, 0x93b, 0xa3b }; +static const u16 NCT6775_REG_WEIGHT_DUTY_STEP[] = { + 0x13c, 0x23c, 0x33c, 0x83c, 0x93c, 0xa3c }; +static const u16 NCT6775_REG_WEIGHT_TEMP_BASE[] = { + 0x13d, 0x23d, 0x33d, 0x83d, 0x93d, 0xa3d }; + +static const u16 NCT6775_REG_TEMP_OFFSET[] = { 0x454, 0x455, 0x456 }; + +static const u16 NCT6775_REG_AUTO_TEMP[] = { + 0x121, 0x221, 0x321, 0x821, 0x921, 0xa21, 0xb21 }; +static const u16 NCT6775_REG_AUTO_PWM[] = { + 0x127, 0x227, 0x327, 0x827, 0x927, 0xa27, 0xb27 }; + +#define NCT6775_AUTO_TEMP(data, nr, p) ((data)->REG_AUTO_TEMP[nr] + (p)) +#define NCT6775_AUTO_PWM(data, nr, p) ((data)->REG_AUTO_PWM[nr] + (p)) + +static const u16 NCT6775_REG_CRITICAL_ENAB[] = { 0x134, 0x234, 0x334 }; + +static const u16 NCT6775_REG_CRITICAL_TEMP[] = { + 0x135, 0x235, 0x335, 0x835, 0x935, 0xa35, 0xb35 }; +static const u16 NCT6775_REG_CRITICAL_TEMP_TOLERANCE[] = { + 0x138, 0x238, 0x338, 0x838, 0x938, 0xa38, 0xb38 }; + +static const char *const nct6775_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN", + "AMD SB-TSI", + "PECI Agent 0", + "PECI Agent 1", + "PECI Agent 2", + "PECI Agent 3", + "PECI Agent 4", + "PECI Agent 5", + "PECI Agent 6", + "PECI Agent 7", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "PCH_DIM0_TEMP", + "PCH_DIM1_TEMP", + "PCH_DIM2_TEMP", + "PCH_DIM3_TEMP" +}; + +#define NCT6775_TEMP_MASK 0x001ffffe +#define NCT6775_VIRT_TEMP_MASK 0x00000000 + +static const u16 NCT6775_REG_TEMP_ALTERNATE[32] = { + [13] = 0x661, + [14] = 0x662, + [15] = 0x664, +}; + +static const u16 NCT6775_REG_TEMP_CRIT[32] = { + [4] = 0xa00, + [5] = 0xa01, + [6] = 0xa02, + [7] = 0xa03, + [8] = 0xa04, + [9] = 0xa05, + [10] = 0xa06, + [11] = 0xa07 +}; + +/* NCT6776 specific data */ + +/* STEP_UP_TIME and STEP_DOWN_TIME regs are swapped for all chips but NCT6775 */ +#define NCT6776_REG_FAN_STEP_UP_TIME NCT6775_REG_FAN_STEP_DOWN_TIME +#define NCT6776_REG_FAN_STEP_DOWN_TIME NCT6775_REG_FAN_STEP_UP_TIME + +static const s8 NCT6776_ALARM_BITS[] = { + 0, 1, 2, 3, 8, 21, 20, 16, /* in0.. in7 */ + 17, -1, -1, -1, -1, -1, -1, /* in8..in14 */ + -1, /* unused */ + 6, 7, 11, 10, 23, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ + 12, 9 }; /* intrusion0, intrusion1 */ + +static const u16 NCT6776_REG_BEEP[NUM_REG_BEEP] = { 0xb2, 0xb3, 0xb4, 0xb5 }; + +static const s8 NCT6776_BEEP_BITS[] = { + 0, 1, 2, 3, 4, 5, 6, 7, /* in0.. in7 */ + 8, -1, -1, -1, -1, -1, -1, /* in8..in14 */ + 24, /* global beep enable */ + 25, 26, 27, 28, 29, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 16, 17, 18, 19, 20, 21, /* temp1..temp6 */ + 30, 31 }; /* intrusion0, intrusion1 */ + +static const u16 NCT6776_REG_TOLERANCE_H[] = { + 0x10c, 0x20c, 0x30c, 0x80c, 0x90c, 0xa0c, 0xb0c }; + +static const u8 NCT6776_REG_PWM_MODE[] = { 0x04, 0, 0, 0, 0, 0 }; +static const u8 NCT6776_PWM_MODE_MASK[] = { 0x01, 0, 0, 0, 0, 0 }; + +static const u16 NCT6776_REG_FAN_MIN[] = { + 0x63a, 0x63c, 0x63e, 0x640, 0x642, 0x64a, 0x64c }; +static const u16 NCT6776_REG_FAN_PULSES[NUM_FAN] = { + 0x644, 0x645, 0x646, 0x647, 0x648, 0x649 }; + +static const u16 NCT6776_REG_WEIGHT_DUTY_BASE[] = { + 0x13e, 0x23e, 0x33e, 0x83e, 0x93e, 0xa3e }; + +static const u16 NCT6776_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6775_REG_TEMP)] = { + 0x18, 0x152, 0x252, 0x628, 0x629, 0x62A }; + +static const char *const nct6776_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN", + "SMBUSMASTER 0", + "SMBUSMASTER 1", + "SMBUSMASTER 2", + "SMBUSMASTER 3", + "SMBUSMASTER 4", + "SMBUSMASTER 5", + "SMBUSMASTER 6", + "SMBUSMASTER 7", + "PECI Agent 0", + "PECI Agent 1", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "PCH_DIM0_TEMP", + "PCH_DIM1_TEMP", + "PCH_DIM2_TEMP", + "PCH_DIM3_TEMP", + "BYTE_TEMP" +}; + +#define NCT6776_TEMP_MASK 0x007ffffe +#define NCT6776_VIRT_TEMP_MASK 0x00000000 + +static const u16 NCT6776_REG_TEMP_ALTERNATE[32] = { + [14] = 0x401, + [15] = 0x402, + [16] = 0x404, +}; + +static const u16 NCT6776_REG_TEMP_CRIT[32] = { + [11] = 0x709, + [12] = 0x70a, +}; + +/* NCT6779 specific data */ + +static const u16 NCT6779_REG_IN[] = { + 0x480, 0x481, 0x482, 0x483, 0x484, 0x485, 0x486, 0x487, + 0x488, 0x489, 0x48a, 0x48b, 0x48c, 0x48d, 0x48e }; + +static const u16 NCT6779_REG_ALARM[NUM_REG_ALARM] = { + 0x459, 0x45A, 0x45B, 0x568 }; + +static const s8 NCT6779_ALARM_BITS[] = { + 0, 1, 2, 3, 8, 21, 20, 16, /* in0.. in7 */ + 17, 24, 25, 26, 27, 28, 29, /* in8..in14 */ + -1, /* unused */ + 6, 7, 11, 10, 23, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ + 12, 9 }; /* intrusion0, intrusion1 */ + +static const s8 NCT6779_BEEP_BITS[] = { + 0, 1, 2, 3, 4, 5, 6, 7, /* in0.. in7 */ + 8, 9, 10, 11, 12, 13, 14, /* in8..in14 */ + 24, /* global beep enable */ + 25, 26, 27, 28, 29, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 16, 17, -1, -1, -1, -1, /* temp1..temp6 */ + 30, 31 }; /* intrusion0, intrusion1 */ + +static const u16 NCT6779_REG_FAN[] = { + 0x4c0, 0x4c2, 0x4c4, 0x4c6, 0x4c8, 0x4ca, 0x4ce }; +static const u16 NCT6779_REG_FAN_PULSES[NUM_FAN] = { + 0x644, 0x645, 0x646, 0x647, 0x648, 0x649 }; + +static const u16 NCT6779_REG_CRITICAL_PWM_ENABLE[] = { + 0x136, 0x236, 0x336, 0x836, 0x936, 0xa36, 0xb36 }; +#define NCT6779_CRITICAL_PWM_ENABLE_MASK 0x01 +static const u16 NCT6779_REG_CRITICAL_PWM[] = { + 0x137, 0x237, 0x337, 0x837, 0x937, 0xa37, 0xb37 }; + +static const u16 NCT6779_REG_TEMP[] = { 0x27, 0x150 }; +static const u16 NCT6779_REG_TEMP_MON[] = { 0x73, 0x75, 0x77, 0x79, 0x7b }; +static const u16 NCT6779_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6779_REG_TEMP)] = { + 0x18, 0x152 }; +static const u16 NCT6779_REG_TEMP_HYST[ARRAY_SIZE(NCT6779_REG_TEMP)] = { + 0x3a, 0x153 }; +static const u16 NCT6779_REG_TEMP_OVER[ARRAY_SIZE(NCT6779_REG_TEMP)] = { + 0x39, 0x155 }; + +static const u16 NCT6779_REG_TEMP_OFFSET[] = { + 0x454, 0x455, 0x456, 0x44a, 0x44b, 0x44c }; + +static const char *const nct6779_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN0", + "AUXTIN1", + "AUXTIN2", + "AUXTIN3", + "", + "SMBUSMASTER 0", + "SMBUSMASTER 1", + "SMBUSMASTER 2", + "SMBUSMASTER 3", + "SMBUSMASTER 4", + "SMBUSMASTER 5", + "SMBUSMASTER 6", + "SMBUSMASTER 7", + "PECI Agent 0", + "PECI Agent 1", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "PCH_DIM0_TEMP", + "PCH_DIM1_TEMP", + "PCH_DIM2_TEMP", + "PCH_DIM3_TEMP", + "BYTE_TEMP", + "", + "", + "", + "", + "Virtual_TEMP" +}; + +#define NCT6779_TEMP_MASK 0x07ffff7e +#define NCT6779_VIRT_TEMP_MASK 0x00000000 +#define NCT6791_TEMP_MASK 0x87ffff7e +#define NCT6791_VIRT_TEMP_MASK 0x80000000 + +static const u16 NCT6779_REG_TEMP_ALTERNATE[32] + = { 0x490, 0x491, 0x492, 0x493, 0x494, 0x495, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0x400, 0x401, 0x402, 0x404, 0x405, 0x406, 0x407, + 0x408, 0 }; + +static const u16 NCT6779_REG_TEMP_CRIT[32] = { + [15] = 0x709, + [16] = 0x70a, +}; + +/* NCT6791 specific data */ + +#define NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE 0x28 + +static const u16 NCT6791_REG_WEIGHT_TEMP_SEL[NUM_FAN] = { 0, 0x239 }; +static const u16 NCT6791_REG_WEIGHT_TEMP_STEP[NUM_FAN] = { 0, 0x23a }; +static const u16 NCT6791_REG_WEIGHT_TEMP_STEP_TOL[NUM_FAN] = { 0, 0x23b }; +static const u16 NCT6791_REG_WEIGHT_DUTY_STEP[NUM_FAN] = { 0, 0x23c }; +static const u16 NCT6791_REG_WEIGHT_TEMP_BASE[NUM_FAN] = { 0, 0x23d }; +static const u16 NCT6791_REG_WEIGHT_DUTY_BASE[NUM_FAN] = { 0, 0x23e }; + +static const u16 NCT6791_REG_ALARM[NUM_REG_ALARM] = { + 0x459, 0x45A, 0x45B, 0x568, 0x45D }; + +static const s8 NCT6791_ALARM_BITS[] = { + 0, 1, 2, 3, 8, 21, 20, 16, /* in0.. in7 */ + 17, 24, 25, 26, 27, 28, 29, /* in8..in14 */ + -1, /* unused */ + 6, 7, 11, 10, 23, 33, /* fan1..fan6 */ + -1, -1, /* unused */ + 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ + 12, 9 }; /* intrusion0, intrusion1 */ + +/* NCT6792/NCT6793 specific data */ + +static const u16 NCT6792_REG_TEMP_MON[] = { + 0x73, 0x75, 0x77, 0x79, 0x7b, 0x7d }; +static const u16 NCT6792_REG_BEEP[NUM_REG_BEEP] = { + 0xb2, 0xb3, 0xb4, 0xb5, 0xbf }; + +static const char *const nct6792_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN0", + "AUXTIN1", + "AUXTIN2", + "AUXTIN3", + "", + "SMBUSMASTER 0", + "SMBUSMASTER 1", + "SMBUSMASTER 2", + "SMBUSMASTER 3", + "SMBUSMASTER 4", + "SMBUSMASTER 5", + "SMBUSMASTER 6", + "SMBUSMASTER 7", + "PECI Agent 0", + "PECI Agent 1", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "PCH_DIM0_TEMP", + "PCH_DIM1_TEMP", + "PCH_DIM2_TEMP", + "PCH_DIM3_TEMP", + "BYTE_TEMP", + "PECI Agent 0 Calibration", + "PECI Agent 1 Calibration", + "", + "", + "Virtual_TEMP" +}; + +#define NCT6792_TEMP_MASK 0x9fffff7e +#define NCT6792_VIRT_TEMP_MASK 0x80000000 + +static const char *const nct6793_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN0", + "AUXTIN1", + "AUXTIN2", + "AUXTIN3", + "", + "SMBUSMASTER 0", + "SMBUSMASTER 1", + "", + "", + "", + "", + "", + "", + "PECI Agent 0", + "PECI Agent 1", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "Agent0 Dimm0 ", + "Agent0 Dimm1", + "Agent1 Dimm0", + "Agent1 Dimm1", + "BYTE_TEMP0", + "BYTE_TEMP1", + "PECI Agent 0 Calibration", + "PECI Agent 1 Calibration", + "", + "Virtual_TEMP" +}; + +#define NCT6793_TEMP_MASK 0xbfff037e +#define NCT6793_VIRT_TEMP_MASK 0x80000000 + +static const char *const nct6795_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN0", + "AUXTIN1", + "AUXTIN2", + "AUXTIN3", + "", + "SMBUSMASTER 0", + "SMBUSMASTER 1", + "SMBUSMASTER 2", + "SMBUSMASTER 3", + "SMBUSMASTER 4", + "SMBUSMASTER 5", + "SMBUSMASTER 6", + "SMBUSMASTER 7", + "PECI Agent 0", + "PECI Agent 1", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "Agent0 Dimm0", + "Agent0 Dimm1", + "Agent1 Dimm0", + "Agent1 Dimm1", + "BYTE_TEMP0", + "BYTE_TEMP1", + "PECI Agent 0 Calibration", + "PECI Agent 1 Calibration", + "", + "Virtual_TEMP" +}; + +#define NCT6795_TEMP_MASK 0xbfffff7e +#define NCT6795_VIRT_TEMP_MASK 0x80000000 + +static const char *const nct6796_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN0", + "AUXTIN1", + "AUXTIN2", + "AUXTIN3", + "AUXTIN4", + "SMBUSMASTER 0", + "SMBUSMASTER 1", + "Virtual_TEMP", + "Virtual_TEMP", + "", + "", + "", + "", + "PECI Agent 0", + "PECI Agent 1", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "Agent0 Dimm0", + "Agent0 Dimm1", + "Agent1 Dimm0", + "Agent1 Dimm1", + "BYTE_TEMP0", + "BYTE_TEMP1", + "PECI Agent 0 Calibration", + "PECI Agent 1 Calibration", + "", + "Virtual_TEMP" +}; + +#define NCT6796_TEMP_MASK 0xbfff0ffe +#define NCT6796_VIRT_TEMP_MASK 0x80000c00 + +/* NCT6102D/NCT6106D specific data */ + +#define NCT6106_REG_VBAT 0x318 +#define NCT6106_REG_DIODE 0x319 +#define NCT6106_DIODE_MASK 0x01 + +static const u16 NCT6106_REG_IN_MAX[] = { + 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9e, 0xa0, 0xa2 }; +static const u16 NCT6106_REG_IN_MIN[] = { + 0x91, 0x93, 0x95, 0x97, 0x99, 0x9b, 0x9f, 0xa1, 0xa3 }; +static const u16 NCT6106_REG_IN[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x07, 0x08, 0x09 }; + +static const u16 NCT6106_REG_TEMP[] = { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }; +static const u16 NCT6106_REG_TEMP_MON[] = { 0x18, 0x19, 0x1a }; +static const u16 NCT6106_REG_TEMP_HYST[] = { + 0xc3, 0xc7, 0xcb, 0xcf, 0xd3, 0xd7 }; +static const u16 NCT6106_REG_TEMP_OVER[] = { + 0xc2, 0xc6, 0xca, 0xce, 0xd2, 0xd6 }; +static const u16 NCT6106_REG_TEMP_CRIT_L[] = { + 0xc0, 0xc4, 0xc8, 0xcc, 0xd0, 0xd4 }; +static const u16 NCT6106_REG_TEMP_CRIT_H[] = { + 0xc1, 0xc5, 0xc9, 0xcf, 0xd1, 0xd5 }; +static const u16 NCT6106_REG_TEMP_OFFSET[] = { 0x311, 0x312, 0x313 }; +static const u16 NCT6106_REG_TEMP_CONFIG[] = { + 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc }; + +static const u16 NCT6106_REG_FAN[] = { 0x20, 0x22, 0x24 }; +static const u16 NCT6106_REG_FAN_MIN[] = { 0xe0, 0xe2, 0xe4 }; +static const u16 NCT6106_REG_FAN_PULSES[] = { 0xf6, 0xf6, 0xf6 }; +static const u16 NCT6106_FAN_PULSE_SHIFT[] = { 0, 2, 4 }; + +static const u8 NCT6106_REG_PWM_MODE[] = { 0xf3, 0xf3, 0xf3 }; +static const u8 NCT6106_PWM_MODE_MASK[] = { 0x01, 0x02, 0x04 }; +static const u16 NCT6106_REG_PWM[] = { 0x119, 0x129, 0x139 }; +static const u16 NCT6106_REG_PWM_READ[] = { 0x4a, 0x4b, 0x4c }; +static const u16 NCT6106_REG_FAN_MODE[] = { 0x113, 0x123, 0x133 }; +static const u16 NCT6106_REG_TEMP_SEL[] = { 0x110, 0x120, 0x130 }; +static const u16 NCT6106_REG_TEMP_SOURCE[] = { + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5 }; + +static const u16 NCT6106_REG_CRITICAL_TEMP[] = { 0x11a, 0x12a, 0x13a }; +static const u16 NCT6106_REG_CRITICAL_TEMP_TOLERANCE[] = { + 0x11b, 0x12b, 0x13b }; + +static const u16 NCT6106_REG_CRITICAL_PWM_ENABLE[] = { 0x11c, 0x12c, 0x13c }; +#define NCT6106_CRITICAL_PWM_ENABLE_MASK 0x10 +static const u16 NCT6106_REG_CRITICAL_PWM[] = { 0x11d, 0x12d, 0x13d }; + +static const u16 NCT6106_REG_FAN_STEP_UP_TIME[] = { 0x114, 0x124, 0x134 }; +static const u16 NCT6106_REG_FAN_STEP_DOWN_TIME[] = { 0x115, 0x125, 0x135 }; +static const u16 NCT6106_REG_FAN_STOP_OUTPUT[] = { 0x116, 0x126, 0x136 }; +static const u16 NCT6106_REG_FAN_START_OUTPUT[] = { 0x117, 0x127, 0x137 }; +static const u16 NCT6106_REG_FAN_STOP_TIME[] = { 0x118, 0x128, 0x138 }; +static const u16 NCT6106_REG_TOLERANCE_H[] = { 0x112, 0x122, 0x132 }; + +static const u16 NCT6106_REG_TARGET[] = { 0x111, 0x121, 0x131 }; + +static const u16 NCT6106_REG_WEIGHT_TEMP_SEL[] = { 0x168, 0x178, 0x188 }; +static const u16 NCT6106_REG_WEIGHT_TEMP_STEP[] = { 0x169, 0x179, 0x189 }; +static const u16 NCT6106_REG_WEIGHT_TEMP_STEP_TOL[] = { 0x16a, 0x17a, 0x18a }; +static const u16 NCT6106_REG_WEIGHT_DUTY_STEP[] = { 0x16b, 0x17b, 0x18b }; +static const u16 NCT6106_REG_WEIGHT_TEMP_BASE[] = { 0x16c, 0x17c, 0x18c }; +static const u16 NCT6106_REG_WEIGHT_DUTY_BASE[] = { 0x16d, 0x17d, 0x18d }; + +static const u16 NCT6106_REG_AUTO_TEMP[] = { 0x160, 0x170, 0x180 }; +static const u16 NCT6106_REG_AUTO_PWM[] = { 0x164, 0x174, 0x184 }; + +static const u16 NCT6106_REG_ALARM[NUM_REG_ALARM] = { + 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d }; + +static const s8 NCT6106_ALARM_BITS[] = { + 0, 1, 2, 3, 4, 5, 7, 8, /* in0.. in7 */ + 9, -1, -1, -1, -1, -1, -1, /* in8..in14 */ + -1, /* unused */ + 32, 33, 34, -1, -1, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 16, 17, 18, 19, 20, 21, /* temp1..temp6 */ + 48, -1 /* intrusion0, intrusion1 */ +}; + +static const u16 NCT6106_REG_BEEP[NUM_REG_BEEP] = { + 0x3c0, 0x3c1, 0x3c2, 0x3c3, 0x3c4 }; + +static const s8 NCT6106_BEEP_BITS[] = { + 0, 1, 2, 3, 4, 5, 7, 8, /* in0.. in7 */ + 9, 10, 11, 12, -1, -1, -1, /* in8..in14 */ + 32, /* global beep enable */ + 24, 25, 26, 27, 28, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 16, 17, 18, 19, 20, 21, /* temp1..temp6 */ + 34, -1 /* intrusion0, intrusion1 */ +}; + +static const u16 NCT6106_REG_TEMP_ALTERNATE[32] = { + [14] = 0x51, + [15] = 0x52, + [16] = 0x54, +}; + +static const u16 NCT6106_REG_TEMP_CRIT[32] = { + [11] = 0x204, + [12] = 0x205, +}; + +static enum pwm_enable reg_to_pwm_enable(int pwm, int mode) +{ + if (mode == 0 && pwm == 255) + return off; + return mode + 1; +} + +static int pwm_enable_to_reg(enum pwm_enable mode) +{ + if (mode == off) + return 0; + return mode - 1; +} + +/* + * Conversions + */ + +/* 1 is DC mode, output in ms */ +static unsigned int step_time_from_reg(u8 reg, u8 mode) +{ + return mode ? 400 * reg : 100 * reg; +} + +static u8 step_time_to_reg(unsigned int msec, u8 mode) +{ + return clamp_val((mode ? (msec + 200) / 400 : + (msec + 50) / 100), 1, 255); +} + +static unsigned int fan_from_reg8(u16 reg, unsigned int divreg) +{ + if (reg == 0 || reg == 255) + return 0; + return 1350000U / (reg << divreg); +} + +static unsigned int fan_from_reg13(u16 reg, unsigned int divreg) +{ + if ((reg & 0xff1f) == 0xff1f) + return 0; + + reg = (reg & 0x1f) | ((reg & 0xff00) >> 3); + + if (reg == 0) + return 0; + + return 1350000U / reg; +} + +static unsigned int fan_from_reg16(u16 reg, unsigned int divreg) +{ + if (reg == 0 || reg == 0xffff) + return 0; + + /* + * Even though the registers are 16 bit wide, the fan divisor + * still applies. + */ + return 1350000U / (reg << divreg); +} + +static unsigned int fan_from_reg_rpm(u16 reg, unsigned int divreg) +{ + return reg; +} + +static u16 fan_to_reg(u32 fan, unsigned int divreg) +{ + if (!fan) + return 0; + + return (1350000U / fan) >> divreg; +} + +static inline unsigned int +div_from_reg(u8 reg) +{ + return BIT(reg); +} + +/* + * Some of the voltage inputs have internal scaling, the tables below + * contain 8 (the ADC LSB in mV) * scaling factor * 100 + */ +static const u16 scale_in[15] = { + 800, 800, 1600, 1600, 800, 800, 800, 1600, 1600, 800, 800, 800, 800, + 800, 800 +}; + +static inline long in_from_reg(u8 reg, u8 nr) +{ + return DIV_ROUND_CLOSEST(reg * scale_in[nr], 100); +} + +static inline u8 in_to_reg(u32 val, u8 nr) +{ + return clamp_val(DIV_ROUND_CLOSEST(val * 100, scale_in[nr]), 0, 255); +} + +/* + * Data structures and manipulation thereof + */ + +struct nct6775_data { + int addr; /* IO base of hw monitor block */ + int sioreg; /* SIO register address */ + enum kinds kind; + const char *name; + + const struct attribute_group *groups[6]; + + u16 reg_temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst, + * 3=temp_crit, 4=temp_lcrit + */ + u8 temp_src[NUM_TEMP]; + u16 reg_temp_config[NUM_TEMP]; + const char * const *temp_label; + u32 temp_mask; + u32 virt_temp_mask; + + u16 REG_CONFIG; + u16 REG_VBAT; + u16 REG_DIODE; + u8 DIODE_MASK; + + const s8 *ALARM_BITS; + const s8 *BEEP_BITS; + + const u16 *REG_VIN; + const u16 *REG_IN_MINMAX[2]; + + const u16 *REG_TARGET; + const u16 *REG_FAN; + const u16 *REG_FAN_MODE; + const u16 *REG_FAN_MIN; + const u16 *REG_FAN_PULSES; + const u16 *FAN_PULSE_SHIFT; + const u16 *REG_FAN_TIME[3]; + + const u16 *REG_TOLERANCE_H; + + const u8 *REG_PWM_MODE; + const u8 *PWM_MODE_MASK; + + const u16 *REG_PWM[7]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, + * [3]=pwm_max, [4]=pwm_step, + * [5]=weight_duty_step, [6]=weight_duty_base + */ + const u16 *REG_PWM_READ; + + const u16 *REG_CRITICAL_PWM_ENABLE; + u8 CRITICAL_PWM_ENABLE_MASK; + const u16 *REG_CRITICAL_PWM; + + const u16 *REG_AUTO_TEMP; + const u16 *REG_AUTO_PWM; + + const u16 *REG_CRITICAL_TEMP; + const u16 *REG_CRITICAL_TEMP_TOLERANCE; + + const u16 *REG_TEMP_SOURCE; /* temp register sources */ + const u16 *REG_TEMP_SEL; + const u16 *REG_WEIGHT_TEMP_SEL; + const u16 *REG_WEIGHT_TEMP[3]; /* 0=base, 1=tolerance, 2=step */ + + const u16 *REG_TEMP_OFFSET; + + const u16 *REG_ALARM; + const u16 *REG_BEEP; + + unsigned int (*fan_from_reg)(u16 reg, unsigned int divreg); + unsigned int (*fan_from_reg_min)(u16 reg, unsigned int divreg); + + struct mutex update_lock; + bool valid; /* true if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* Register values */ + u8 bank; /* current register bank */ + u8 in_num; /* number of in inputs we have */ + u8 in[15][3]; /* [0]=in, [1]=in_max, [2]=in_min */ + unsigned int rpm[NUM_FAN]; + u16 fan_min[NUM_FAN]; + u8 fan_pulses[NUM_FAN]; + u8 fan_div[NUM_FAN]; + u8 has_pwm; + u8 has_fan; /* some fan inputs can be disabled */ + u8 has_fan_min; /* some fans don't have min register */ + bool has_fan_div; + + u8 num_temp_alarms; /* 2, 3, or 6 */ + u8 num_temp_beeps; /* 2, 3, or 6 */ + u8 temp_fixed_num; /* 3 or 6 */ + u8 temp_type[NUM_TEMP_FIXED]; + s8 temp_offset[NUM_TEMP_FIXED]; + s16 temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst, + * 3=temp_crit, 4=temp_lcrit */ + u64 alarms; + u64 beeps; + + u8 pwm_num; /* number of pwm */ + u8 pwm_mode[NUM_FAN]; /* 0->DC variable voltage, + * 1->PWM variable duty cycle + */ + enum pwm_enable pwm_enable[NUM_FAN]; + /* 0->off + * 1->manual + * 2->thermal cruise mode (also called SmartFan I) + * 3->fan speed cruise mode + * 4->SmartFan III + * 5->enhanced variable thermal cruise (SmartFan IV) + */ + u8 pwm[7][NUM_FAN]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, + * [3]=pwm_max, [4]=pwm_step, + * [5]=weight_duty_step, [6]=weight_duty_base + */ + + u8 target_temp[NUM_FAN]; + u8 target_temp_mask; + u32 target_speed[NUM_FAN]; + u32 target_speed_tolerance[NUM_FAN]; + u8 speed_tolerance_limit; + + u8 temp_tolerance[2][NUM_FAN]; + u8 tolerance_mask; + + u8 fan_time[3][NUM_FAN]; /* 0 = stop_time, 1 = step_up, 2 = step_down */ + + /* Automatic fan speed control registers */ + int auto_pwm_num; + u8 auto_pwm[NUM_FAN][7]; + u8 auto_temp[NUM_FAN][7]; + u8 pwm_temp_sel[NUM_FAN]; + u8 pwm_weight_temp_sel[NUM_FAN]; + u8 weight_temp[3][NUM_FAN]; /* 0->temp_step, 1->temp_step_tol, + * 2->temp_base + */ + + u8 vid; + u8 vrm; + + bool have_vid; + + u16 have_temp; + u16 have_temp_fixed; + u16 have_in; + + /* Remember extra register values over suspend/resume */ + u8 vbat; + u8 fandiv1; + u8 fandiv2; + u8 sio_reg_enable; +}; + +struct nct6775_sio_data { + int sioreg; + enum kinds kind; +}; + +struct sensor_device_template { + struct device_attribute dev_attr; + union { + struct { + u8 nr; + u8 index; + } s; + int index; + } u; + bool s2; /* true if both index and nr are used */ +}; + +struct sensor_device_attr_u { + union { + struct sensor_device_attribute a1; + struct sensor_device_attribute_2 a2; + } u; + char name[32]; +}; + +#define __TEMPLATE_ATTR(_template, _mode, _show, _store) { \ + .attr = {.name = _template, .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +} + +#define SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, _index) \ + { .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \ + .u.index = _index, \ + .s2 = false } + +#define SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store, \ + _nr, _index) \ + { .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \ + .u.s.index = _index, \ + .u.s.nr = _nr, \ + .s2 = true } + +#define SENSOR_TEMPLATE(_name, _template, _mode, _show, _store, _index) \ +static struct sensor_device_template sensor_dev_template_##_name \ + = SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, \ + _index) + +#define SENSOR_TEMPLATE_2(_name, _template, _mode, _show, _store, \ + _nr, _index) \ +static struct sensor_device_template sensor_dev_template_##_name \ + = SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store, \ + _nr, _index) + +struct sensor_template_group { + struct sensor_device_template **templates; + umode_t (*is_visible)(struct kobject *, struct attribute *, int); + int base; +}; + +static struct attribute_group * +nct6775_create_attr_group(struct device *dev, + const struct sensor_template_group *tg, + int repeat) +{ + struct attribute_group *group; + struct sensor_device_attr_u *su; + struct sensor_device_attribute *a; + struct sensor_device_attribute_2 *a2; + struct attribute **attrs; + struct sensor_device_template **t; + int i, count; + + if (repeat <= 0) + return ERR_PTR(-EINVAL); + + t = tg->templates; + for (count = 0; *t; t++, count++) + ; + + if (count == 0) + return ERR_PTR(-EINVAL); + + group = devm_kzalloc(dev, sizeof(*group), GFP_KERNEL); + if (group == NULL) + return ERR_PTR(-ENOMEM); + + attrs = devm_kcalloc(dev, repeat * count + 1, sizeof(*attrs), + GFP_KERNEL); + if (attrs == NULL) + return ERR_PTR(-ENOMEM); + + su = devm_kzalloc(dev, array3_size(repeat, count, sizeof(*su)), + GFP_KERNEL); + if (su == NULL) + return ERR_PTR(-ENOMEM); + + group->attrs = attrs; + group->is_visible = tg->is_visible; + + for (i = 0; i < repeat; i++) { + t = tg->templates; + while (*t != NULL) { + snprintf(su->name, sizeof(su->name), + (*t)->dev_attr.attr.name, tg->base + i); + if ((*t)->s2) { + a2 = &su->u.a2; + sysfs_attr_init(&a2->dev_attr.attr); + a2->dev_attr.attr.name = su->name; + a2->nr = (*t)->u.s.nr + i; + a2->index = (*t)->u.s.index; + a2->dev_attr.attr.mode = + (*t)->dev_attr.attr.mode; + a2->dev_attr.show = (*t)->dev_attr.show; + a2->dev_attr.store = (*t)->dev_attr.store; + *attrs = &a2->dev_attr.attr; + } else { + a = &su->u.a1; + sysfs_attr_init(&a->dev_attr.attr); + a->dev_attr.attr.name = su->name; + a->index = (*t)->u.index + i; + a->dev_attr.attr.mode = + (*t)->dev_attr.attr.mode; + a->dev_attr.show = (*t)->dev_attr.show; + a->dev_attr.store = (*t)->dev_attr.store; + *attrs = &a->dev_attr.attr; + } + attrs++; + su++; + t++; + } + } + + return group; +} + +static bool is_word_sized(struct nct6775_data *data, u16 reg) +{ + switch (data->kind) { + case nct6106: + return reg == 0x20 || reg == 0x22 || reg == 0x24 || + reg == 0xe0 || reg == 0xe2 || reg == 0xe4 || + reg == 0x111 || reg == 0x121 || reg == 0x131; + case nct6775: + return (((reg & 0xff00) == 0x100 || + (reg & 0xff00) == 0x200) && + ((reg & 0x00ff) == 0x50 || + (reg & 0x00ff) == 0x53 || + (reg & 0x00ff) == 0x55)) || + (reg & 0xfff0) == 0x630 || + reg == 0x640 || reg == 0x642 || + reg == 0x662 || + ((reg & 0xfff0) == 0x650 && (reg & 0x000f) >= 0x06) || + reg == 0x73 || reg == 0x75 || reg == 0x77; + case nct6776: + return (((reg & 0xff00) == 0x100 || + (reg & 0xff00) == 0x200) && + ((reg & 0x00ff) == 0x50 || + (reg & 0x00ff) == 0x53 || + (reg & 0x00ff) == 0x55)) || + (reg & 0xfff0) == 0x630 || + reg == 0x402 || + reg == 0x640 || reg == 0x642 || + ((reg & 0xfff0) == 0x650 && (reg & 0x000f) >= 0x06) || + reg == 0x73 || reg == 0x75 || reg == 0x77; + case nct6779: + case nct6791: + case nct6792: + case nct6793: + case nct6795: + case nct6796: + return reg == 0x150 || reg == 0x153 || reg == 0x155 || + (reg & 0xfff0) == 0x4c0 || + reg == 0x402 || + reg == 0x63a || reg == 0x63c || reg == 0x63e || + reg == 0x640 || reg == 0x642 || reg == 0x64a || + reg == 0x64c || + reg == 0x73 || reg == 0x75 || reg == 0x77 || reg == 0x79 || + reg == 0x7b || reg == 0x7d; + } + return false; +} + +/* + * On older chips, only registers 0x50-0x5f are banked. + * On more recent chips, all registers are banked. + * Assume that is the case and set the bank number for each access. + * Cache the bank number so it only needs to be set if it changes. + */ +static inline void nct6775_set_bank(struct nct6775_data *data, u16 reg) +{ + u8 bank = reg >> 8; + + if (data->bank != bank) { + outb_p(NCT6775_REG_BANK, data->addr + ADDR_REG_OFFSET); + outb_p(bank, data->addr + DATA_REG_OFFSET); + data->bank = bank; + } +} + +static u16 nct6775_read_value(struct nct6775_data *data, u16 reg) +{ + int res, word_sized = is_word_sized(data, reg); + + nct6775_set_bank(data, reg); + outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); + res = inb_p(data->addr + DATA_REG_OFFSET); + if (word_sized) { + outb_p((reg & 0xff) + 1, + data->addr + ADDR_REG_OFFSET); + res = (res << 8) + inb_p(data->addr + DATA_REG_OFFSET); + } + return res; +} + +static int nct6775_write_value(struct nct6775_data *data, u16 reg, u16 value) +{ + int word_sized = is_word_sized(data, reg); + + nct6775_set_bank(data, reg); + outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); + if (word_sized) { + outb_p(value >> 8, data->addr + DATA_REG_OFFSET); + outb_p((reg & 0xff) + 1, + data->addr + ADDR_REG_OFFSET); + } + outb_p(value & 0xff, data->addr + DATA_REG_OFFSET); + return 0; +} + +/* We left-align 8-bit temperature values to make the code simpler */ +static u16 nct6775_read_temp(struct nct6775_data *data, u16 reg) +{ + u16 res; + + res = nct6775_read_value(data, reg); + if (!is_word_sized(data, reg)) + res <<= 8; + + return res; +} + +static int nct6775_write_temp(struct nct6775_data *data, u16 reg, u16 value) +{ + if (!is_word_sized(data, reg)) + value >>= 8; + return nct6775_write_value(data, reg, value); +} + +/* This function assumes that the caller holds data->update_lock */ +static void nct6775_write_fan_div(struct nct6775_data *data, int nr) +{ + u8 reg; + + switch (nr) { + case 0: + reg = (nct6775_read_value(data, NCT6775_REG_FANDIV1) & 0x70) + | (data->fan_div[0] & 0x7); + nct6775_write_value(data, NCT6775_REG_FANDIV1, reg); + break; + case 1: + reg = (nct6775_read_value(data, NCT6775_REG_FANDIV1) & 0x7) + | ((data->fan_div[1] << 4) & 0x70); + nct6775_write_value(data, NCT6775_REG_FANDIV1, reg); + break; + case 2: + reg = (nct6775_read_value(data, NCT6775_REG_FANDIV2) & 0x70) + | (data->fan_div[2] & 0x7); + nct6775_write_value(data, NCT6775_REG_FANDIV2, reg); + break; + case 3: + reg = (nct6775_read_value(data, NCT6775_REG_FANDIV2) & 0x7) + | ((data->fan_div[3] << 4) & 0x70); + nct6775_write_value(data, NCT6775_REG_FANDIV2, reg); + break; + } +} + +static void nct6775_write_fan_div_common(struct nct6775_data *data, int nr) +{ + if (data->kind == nct6775) + nct6775_write_fan_div(data, nr); +} + +static void nct6775_update_fan_div(struct nct6775_data *data) +{ + u8 i; + + i = nct6775_read_value(data, NCT6775_REG_FANDIV1); + data->fan_div[0] = i & 0x7; + data->fan_div[1] = (i & 0x70) >> 4; + i = nct6775_read_value(data, NCT6775_REG_FANDIV2); + data->fan_div[2] = i & 0x7; + if (data->has_fan & BIT(3)) + data->fan_div[3] = (i & 0x70) >> 4; +} + +static void nct6775_update_fan_div_common(struct nct6775_data *data) +{ + if (data->kind == nct6775) + nct6775_update_fan_div(data); +} + +static void nct6775_init_fan_div(struct nct6775_data *data) +{ + int i; + + nct6775_update_fan_div_common(data); + /* + * For all fans, start with highest divider value if the divider + * register is not initialized. This ensures that we get a + * reading from the fan count register, even if it is not optimal. + * We'll compute a better divider later on. + */ + for (i = 0; i < ARRAY_SIZE(data->fan_div); i++) { + if (!(data->has_fan & BIT(i))) + continue; + if (data->fan_div[i] == 0) { + data->fan_div[i] = 7; + nct6775_write_fan_div_common(data, i); + } + } +} + +static void nct6775_init_fan_common(struct device *dev, + struct nct6775_data *data) +{ + int i; + u8 reg; + + if (data->has_fan_div) + nct6775_init_fan_div(data); + + /* + * If fan_min is not set (0), set it to 0xff to disable it. This + * prevents the unnecessary warning when fanX_min is reported as 0. + */ + for (i = 0; i < ARRAY_SIZE(data->fan_min); i++) { + if (data->has_fan_min & BIT(i)) { + reg = nct6775_read_value(data, data->REG_FAN_MIN[i]); + if (!reg) + nct6775_write_value(data, data->REG_FAN_MIN[i], + data->has_fan_div ? 0xff + : 0xff1f); + } + } +} + +static void nct6775_select_fan_div(struct device *dev, + struct nct6775_data *data, int nr, u16 reg) +{ + u8 fan_div = data->fan_div[nr]; + u16 fan_min; + + if (!data->has_fan_div) + return; + + /* + * If we failed to measure the fan speed, or the reported value is not + * in the optimal range, and the clock divider can be modified, + * let's try that for next time. + */ + if (reg == 0x00 && fan_div < 0x07) + fan_div++; + else if (reg != 0x00 && reg < 0x30 && fan_div > 0) + fan_div--; + + if (fan_div != data->fan_div[nr]) { + dev_dbg(dev, "Modifying fan%d clock divider from %u to %u\n", + nr + 1, div_from_reg(data->fan_div[nr]), + div_from_reg(fan_div)); + + /* Preserve min limit if possible */ + if (data->has_fan_min & BIT(nr)) { + fan_min = data->fan_min[nr]; + if (fan_div > data->fan_div[nr]) { + if (fan_min != 255 && fan_min > 1) + fan_min >>= 1; + } else { + if (fan_min != 255) { + fan_min <<= 1; + if (fan_min > 254) + fan_min = 254; + } + } + if (fan_min != data->fan_min[nr]) { + data->fan_min[nr] = fan_min; + nct6775_write_value(data, data->REG_FAN_MIN[nr], + fan_min); + } + } + data->fan_div[nr] = fan_div; + nct6775_write_fan_div_common(data, nr); + } +} + +static void nct6775_update_pwm(struct device *dev) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + int i, j; + int fanmodecfg, reg; + bool duty_is_dc; + + for (i = 0; i < data->pwm_num; i++) { + if (!(data->has_pwm & BIT(i))) + continue; + + duty_is_dc = data->REG_PWM_MODE[i] && + (nct6775_read_value(data, data->REG_PWM_MODE[i]) + & data->PWM_MODE_MASK[i]); + data->pwm_mode[i] = !duty_is_dc; + + fanmodecfg = nct6775_read_value(data, data->REG_FAN_MODE[i]); + for (j = 0; j < ARRAY_SIZE(data->REG_PWM); j++) { + if (data->REG_PWM[j] && data->REG_PWM[j][i]) { + data->pwm[j][i] + = nct6775_read_value(data, + data->REG_PWM[j][i]); + } + } + + data->pwm_enable[i] = reg_to_pwm_enable(data->pwm[0][i], + (fanmodecfg >> 4) & 7); + + if (!data->temp_tolerance[0][i] || + data->pwm_enable[i] != speed_cruise) + data->temp_tolerance[0][i] = fanmodecfg & 0x0f; + if (!data->target_speed_tolerance[i] || + data->pwm_enable[i] == speed_cruise) { + u8 t = fanmodecfg & 0x0f; + + if (data->REG_TOLERANCE_H) { + t |= (nct6775_read_value(data, + data->REG_TOLERANCE_H[i]) & 0x70) >> 1; + } + data->target_speed_tolerance[i] = t; + } + + data->temp_tolerance[1][i] = + nct6775_read_value(data, + data->REG_CRITICAL_TEMP_TOLERANCE[i]); + + reg = nct6775_read_value(data, data->REG_TEMP_SEL[i]); + data->pwm_temp_sel[i] = reg & 0x1f; + /* If fan can stop, report floor as 0 */ + if (reg & 0x80) + data->pwm[2][i] = 0; + + if (!data->REG_WEIGHT_TEMP_SEL[i]) + continue; + + reg = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[i]); + data->pwm_weight_temp_sel[i] = reg & 0x1f; + /* If weight is disabled, report weight source as 0 */ + if (!(reg & 0x80)) + data->pwm_weight_temp_sel[i] = 0; + + /* Weight temp data */ + for (j = 0; j < ARRAY_SIZE(data->weight_temp); j++) { + data->weight_temp[j][i] + = nct6775_read_value(data, + data->REG_WEIGHT_TEMP[j][i]); + } + } +} + +static void nct6775_update_pwm_limits(struct device *dev) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + int i, j; + u8 reg; + u16 reg_t; + + for (i = 0; i < data->pwm_num; i++) { + if (!(data->has_pwm & BIT(i))) + continue; + + for (j = 0; j < ARRAY_SIZE(data->fan_time); j++) { + data->fan_time[j][i] = + nct6775_read_value(data, data->REG_FAN_TIME[j][i]); + } + + reg_t = nct6775_read_value(data, data->REG_TARGET[i]); + /* Update only in matching mode or if never updated */ + if (!data->target_temp[i] || + data->pwm_enable[i] == thermal_cruise) + data->target_temp[i] = reg_t & data->target_temp_mask; + if (!data->target_speed[i] || + data->pwm_enable[i] == speed_cruise) { + if (data->REG_TOLERANCE_H) { + reg_t |= (nct6775_read_value(data, + data->REG_TOLERANCE_H[i]) & 0x0f) << 8; + } + data->target_speed[i] = reg_t; + } + + for (j = 0; j < data->auto_pwm_num; j++) { + data->auto_pwm[i][j] = + nct6775_read_value(data, + NCT6775_AUTO_PWM(data, i, j)); + data->auto_temp[i][j] = + nct6775_read_value(data, + NCT6775_AUTO_TEMP(data, i, j)); + } + + /* critical auto_pwm temperature data */ + data->auto_temp[i][data->auto_pwm_num] = + nct6775_read_value(data, data->REG_CRITICAL_TEMP[i]); + + switch (data->kind) { + case nct6775: + reg = nct6775_read_value(data, + NCT6775_REG_CRITICAL_ENAB[i]); + data->auto_pwm[i][data->auto_pwm_num] = + (reg & 0x02) ? 0xff : 0x00; + break; + case nct6776: + data->auto_pwm[i][data->auto_pwm_num] = 0xff; + break; + case nct6106: + case nct6779: + case nct6791: + case nct6792: + case nct6793: + case nct6795: + case nct6796: + reg = nct6775_read_value(data, + data->REG_CRITICAL_PWM_ENABLE[i]); + if (reg & data->CRITICAL_PWM_ENABLE_MASK) + reg = nct6775_read_value(data, + data->REG_CRITICAL_PWM[i]); + else + reg = 0xff; + data->auto_pwm[i][data->auto_pwm_num] = reg; + break; + } + } +} + +static struct nct6775_data *nct6775_update_device(struct device *dev) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + int i, j; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + /* Fan clock dividers */ + nct6775_update_fan_div_common(data); + + /* Measured voltages and limits */ + for (i = 0; i < data->in_num; i++) { + if (!(data->have_in & BIT(i))) + continue; + + data->in[i][0] = nct6775_read_value(data, + data->REG_VIN[i]); + data->in[i][1] = nct6775_read_value(data, + data->REG_IN_MINMAX[0][i]); + data->in[i][2] = nct6775_read_value(data, + data->REG_IN_MINMAX[1][i]); + } + + /* Measured fan speeds and limits */ + for (i = 0; i < ARRAY_SIZE(data->rpm); i++) { + u16 reg; + + if (!(data->has_fan & BIT(i))) + continue; + + reg = nct6775_read_value(data, data->REG_FAN[i]); + data->rpm[i] = data->fan_from_reg(reg, + data->fan_div[i]); + + if (data->has_fan_min & BIT(i)) + data->fan_min[i] = nct6775_read_value(data, + data->REG_FAN_MIN[i]); + + if (data->REG_FAN_PULSES[i]) { + data->fan_pulses[i] = + (nct6775_read_value(data, + data->REG_FAN_PULSES[i]) + >> data->FAN_PULSE_SHIFT[i]) & 0x03; + } + + nct6775_select_fan_div(dev, data, i, reg); + } + + nct6775_update_pwm(dev); + nct6775_update_pwm_limits(dev); + + /* Measured temperatures and limits */ + for (i = 0; i < NUM_TEMP; i++) { + if (!(data->have_temp & BIT(i))) + continue; + for (j = 0; j < ARRAY_SIZE(data->reg_temp); j++) { + if (data->reg_temp[j][i]) + data->temp[j][i] + = nct6775_read_temp(data, + data->reg_temp[j][i]); + } + if (i >= NUM_TEMP_FIXED || + !(data->have_temp_fixed & BIT(i))) + continue; + data->temp_offset[i] + = nct6775_read_value(data, data->REG_TEMP_OFFSET[i]); + } + + data->alarms = 0; + for (i = 0; i < NUM_REG_ALARM; i++) { + u8 alarm; + + if (!data->REG_ALARM[i]) + continue; + alarm = nct6775_read_value(data, data->REG_ALARM[i]); + data->alarms |= ((u64)alarm) << (i << 3); + } + + data->beeps = 0; + for (i = 0; i < NUM_REG_BEEP; i++) { + u8 beep; + + if (!data->REG_BEEP[i]) + continue; + beep = nct6775_read_value(data, data->REG_BEEP[i]); + data->beeps |= ((u64)beep) << (i << 3); + } + + data->last_updated = jiffies; + data->valid = true; + } + + mutex_unlock(&data->update_lock); + return data; +} + +/* + * Sysfs callback functions + */ +static ssize_t +show_in_reg(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int index = sattr->index; + int nr = sattr->nr; + + return sprintf(buf, "%ld\n", in_from_reg(data->in[nr][index], nr)); +} + +static ssize_t +store_in_reg(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int index = sattr->index; + int nr = sattr->nr; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + mutex_lock(&data->update_lock); + data->in[nr][index] = in_to_reg(val, nr); + nct6775_write_value(data, data->REG_IN_MINMAX[index - 1][nr], + data->in[nr][index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_alarm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = data->ALARM_BITS[sattr->index]; + + return sprintf(buf, "%u\n", + (unsigned int)((data->alarms >> nr) & 0x01)); +} + +static int find_temp_source(struct nct6775_data *data, int index, int count) +{ + int source = data->temp_src[index]; + int nr; + + for (nr = 0; nr < count; nr++) { + int src; + + src = nct6775_read_value(data, + data->REG_TEMP_SOURCE[nr]) & 0x1f; + if (src == source) + return nr; + } + return -ENODEV; +} + +static ssize_t +show_temp_alarm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6775_data *data = nct6775_update_device(dev); + unsigned int alarm = 0; + int nr; + + /* + * For temperatures, there is no fixed mapping from registers to alarm + * bits. Alarm bits are determined by the temperature source mapping. + */ + nr = find_temp_source(data, sattr->index, data->num_temp_alarms); + if (nr >= 0) { + int bit = data->ALARM_BITS[nr + TEMP_ALARM_BASE]; + + alarm = (data->alarms >> bit) & 0x01; + } + return sprintf(buf, "%u\n", alarm); +} + +static ssize_t +show_beep(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6775_data *data = nct6775_update_device(dev); + int nr = data->BEEP_BITS[sattr->index]; + + return sprintf(buf, "%u\n", + (unsigned int)((data->beeps >> nr) & 0x01)); +} + +static ssize_t +store_beep(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct6775_data *data = dev_get_drvdata(dev); + int nr = data->BEEP_BITS[sattr->index]; + int regindex = nr >> 3; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + if (val > 1) + return -EINVAL; + + mutex_lock(&data->update_lock); + if (val) + data->beeps |= (1ULL << nr); + else + data->beeps &= ~(1ULL << nr); + nct6775_write_value(data, data->REG_BEEP[regindex], + (data->beeps >> (regindex << 3)) & 0xff); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_temp_beep(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6775_data *data = nct6775_update_device(dev); + unsigned int beep = 0; + int nr; + + /* + * For temperatures, there is no fixed mapping from registers to beep + * enable bits. Beep enable bits are determined by the temperature + * source mapping. + */ + nr = find_temp_source(data, sattr->index, data->num_temp_beeps); + if (nr >= 0) { + int bit = data->BEEP_BITS[nr + TEMP_ALARM_BASE]; + + beep = (data->beeps >> bit) & 0x01; + } + return sprintf(buf, "%u\n", beep); +} + +static ssize_t +store_temp_beep(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct6775_data *data = dev_get_drvdata(dev); + int nr, bit, regindex; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + if (val > 1) + return -EINVAL; + + nr = find_temp_source(data, sattr->index, data->num_temp_beeps); + if (nr < 0) + return nr; + + bit = data->BEEP_BITS[nr + TEMP_ALARM_BASE]; + regindex = bit >> 3; + + mutex_lock(&data->update_lock); + if (val) + data->beeps |= (1ULL << bit); + else + data->beeps &= ~(1ULL << bit); + nct6775_write_value(data, data->REG_BEEP[regindex], + (data->beeps >> (regindex << 3)) & 0xff); + mutex_unlock(&data->update_lock); + + return count; +} + +static umode_t nct6775_in_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct6775_data *data = dev_get_drvdata(dev); + int in = index / 5; /* voltage index */ + + if (!(data->have_in & BIT(in))) + return 0; + + return attr->mode; +} + +SENSOR_TEMPLATE_2(in_input, "in%d_input", S_IRUGO, show_in_reg, NULL, 0, 0); +SENSOR_TEMPLATE(in_alarm, "in%d_alarm", S_IRUGO, show_alarm, NULL, 0); +SENSOR_TEMPLATE(in_beep, "in%d_beep", S_IWUSR | S_IRUGO, show_beep, store_beep, + 0); +SENSOR_TEMPLATE_2(in_min, "in%d_min", S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 0, 1); +SENSOR_TEMPLATE_2(in_max, "in%d_max", S_IWUSR | S_IRUGO, show_in_reg, + store_in_reg, 0, 2); + +/* + * nct6775_in_is_visible uses the index into the following array + * to determine if attributes should be created or not. + * Any change in order or content must be matched. + */ +static struct sensor_device_template *nct6775_attributes_in_template[] = { + &sensor_dev_template_in_input, + &sensor_dev_template_in_alarm, + &sensor_dev_template_in_beep, + &sensor_dev_template_in_min, + &sensor_dev_template_in_max, + NULL +}; + +static const struct sensor_template_group nct6775_in_template_group = { + .templates = nct6775_attributes_in_template, + .is_visible = nct6775_in_is_visible, +}; + +static ssize_t +show_fan(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + + return sprintf(buf, "%d\n", data->rpm[nr]); +} + +static ssize_t +show_fan_min(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + + return sprintf(buf, "%d\n", + data->fan_from_reg_min(data->fan_min[nr], + data->fan_div[nr])); +} + +static ssize_t +show_fan_div(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + + return sprintf(buf, "%u\n", div_from_reg(data->fan_div[nr])); +} + +static ssize_t +store_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + unsigned int reg; + u8 new_div; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + mutex_lock(&data->update_lock); + if (!data->has_fan_div) { + /* NCT6776F or NCT6779D; we know this is a 13 bit register */ + if (!val) { + val = 0xff1f; + } else { + if (val > 1350000U) + val = 135000U; + val = 1350000U / val; + val = (val & 0x1f) | ((val << 3) & 0xff00); + } + data->fan_min[nr] = val; + goto write_min; /* Leave fan divider alone */ + } + if (!val) { + /* No min limit, alarm disabled */ + data->fan_min[nr] = 255; + new_div = data->fan_div[nr]; /* No change */ + dev_info(dev, "fan%u low limit and alarm disabled\n", nr + 1); + goto write_div; + } + reg = 1350000U / val; + if (reg >= 128 * 255) { + /* + * Speed below this value cannot possibly be represented, + * even with the highest divider (128) + */ + data->fan_min[nr] = 254; + new_div = 7; /* 128 == BIT(7) */ + dev_warn(dev, + "fan%u low limit %lu below minimum %u, set to minimum\n", + nr + 1, val, data->fan_from_reg_min(254, 7)); + } else if (!reg) { + /* + * Speed above this value cannot possibly be represented, + * even with the lowest divider (1) + */ + data->fan_min[nr] = 1; + new_div = 0; /* 1 == BIT(0) */ + dev_warn(dev, + "fan%u low limit %lu above maximum %u, set to maximum\n", + nr + 1, val, data->fan_from_reg_min(1, 0)); + } else { + /* + * Automatically pick the best divider, i.e. the one such + * that the min limit will correspond to a register value + * in the 96..192 range + */ + new_div = 0; + while (reg > 192 && new_div < 7) { + reg >>= 1; + new_div++; + } + data->fan_min[nr] = reg; + } + +write_div: + /* + * Write both the fan clock divider (if it changed) and the new + * fan min (unconditionally) + */ + if (new_div != data->fan_div[nr]) { + dev_dbg(dev, "fan%u clock divider changed from %u to %u\n", + nr + 1, div_from_reg(data->fan_div[nr]), + div_from_reg(new_div)); + data->fan_div[nr] = new_div; + nct6775_write_fan_div_common(data, nr); + /* Give the chip time to sample a new speed value */ + data->last_updated = jiffies; + } + +write_min: + nct6775_write_value(data, data->REG_FAN_MIN[nr], data->fan_min[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_fan_pulses(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int p = data->fan_pulses[sattr->index]; + + return sprintf(buf, "%d\n", p ? : 4); +} + +static ssize_t +store_fan_pulses(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + u8 reg; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (val > 4) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->fan_pulses[nr] = val & 3; + reg = nct6775_read_value(data, data->REG_FAN_PULSES[nr]); + reg &= ~(0x03 << data->FAN_PULSE_SHIFT[nr]); + reg |= (val & 3) << data->FAN_PULSE_SHIFT[nr]; + nct6775_write_value(data, data->REG_FAN_PULSES[nr], reg); + mutex_unlock(&data->update_lock); + + return count; +} + +static umode_t nct6775_fan_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct6775_data *data = dev_get_drvdata(dev); + int fan = index / 6; /* fan index */ + int nr = index % 6; /* attribute index */ + + if (!(data->has_fan & BIT(fan))) + return 0; + + if (nr == 1 && data->ALARM_BITS[FAN_ALARM_BASE + fan] == -1) + return 0; + if (nr == 2 && data->BEEP_BITS[FAN_ALARM_BASE + fan] == -1) + return 0; + if (nr == 3 && !data->REG_FAN_PULSES[fan]) + return 0; + if (nr == 4 && !(data->has_fan_min & BIT(fan))) + return 0; + if (nr == 5 && data->kind != nct6775) + return 0; + + return attr->mode; +} + +SENSOR_TEMPLATE(fan_input, "fan%d_input", S_IRUGO, show_fan, NULL, 0); +SENSOR_TEMPLATE(fan_alarm, "fan%d_alarm", S_IRUGO, show_alarm, NULL, + FAN_ALARM_BASE); +SENSOR_TEMPLATE(fan_beep, "fan%d_beep", S_IWUSR | S_IRUGO, show_beep, + store_beep, FAN_ALARM_BASE); +SENSOR_TEMPLATE(fan_pulses, "fan%d_pulses", S_IWUSR | S_IRUGO, show_fan_pulses, + store_fan_pulses, 0); +SENSOR_TEMPLATE(fan_min, "fan%d_min", S_IWUSR | S_IRUGO, show_fan_min, + store_fan_min, 0); +SENSOR_TEMPLATE(fan_div, "fan%d_div", S_IRUGO, show_fan_div, NULL, 0); + +/* + * nct6775_fan_is_visible uses the index into the following array + * to determine if attributes should be created or not. + * Any change in order or content must be matched. + */ +static struct sensor_device_template *nct6775_attributes_fan_template[] = { + &sensor_dev_template_fan_input, + &sensor_dev_template_fan_alarm, /* 1 */ + &sensor_dev_template_fan_beep, /* 2 */ + &sensor_dev_template_fan_pulses, + &sensor_dev_template_fan_min, /* 4 */ + &sensor_dev_template_fan_div, /* 5 */ + NULL +}; + +static const struct sensor_template_group nct6775_fan_template_group = { + .templates = nct6775_attributes_fan_template, + .is_visible = nct6775_fan_is_visible, + .base = 1, +}; + +static ssize_t +show_temp_label(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + + return sprintf(buf, "%s\n", data->temp_label[data->temp_src[nr]]); +} + +static ssize_t +show_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + + return sprintf(buf, "%d\n", LM75_TEMP_FROM_REG(data->temp[index][nr])); +} + +static ssize_t +store_temp(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + int err; + long val; + + err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + mutex_lock(&data->update_lock); + data->temp[index][nr] = LM75_TEMP_TO_REG(val); + nct6775_write_temp(data, data->reg_temp[index][nr], + data->temp[index][nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_temp_offset(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + return sprintf(buf, "%d\n", data->temp_offset[sattr->index] * 1000); +} + +static ssize_t +store_temp_offset(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), -128, 127); + + mutex_lock(&data->update_lock); + data->temp_offset[nr] = val; + nct6775_write_value(data, data->REG_TEMP_OFFSET[nr], val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_temp_type(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + + return sprintf(buf, "%d\n", (int)data->temp_type[nr]); +} + +static ssize_t +store_temp_type(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + u8 vbat, diode, vbit, dbit; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (val != 1 && val != 3 && val != 4) + return -EINVAL; + + mutex_lock(&data->update_lock); + + data->temp_type[nr] = val; + vbit = 0x02 << nr; + dbit = data->DIODE_MASK << nr; + vbat = nct6775_read_value(data, data->REG_VBAT) & ~vbit; + diode = nct6775_read_value(data, data->REG_DIODE) & ~dbit; + switch (val) { + case 1: /* CPU diode (diode, current mode) */ + vbat |= vbit; + diode |= dbit; + break; + case 3: /* diode, voltage mode */ + vbat |= dbit; + break; + case 4: /* thermistor */ + break; + } + nct6775_write_value(data, data->REG_VBAT, vbat); + nct6775_write_value(data, data->REG_DIODE, diode); + + mutex_unlock(&data->update_lock); + return count; +} + +static umode_t nct6775_temp_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct6775_data *data = dev_get_drvdata(dev); + int temp = index / 10; /* temp index */ + int nr = index % 10; /* attribute index */ + + if (!(data->have_temp & BIT(temp))) + return 0; + + if (nr == 1 && !data->temp_label) + return 0; + + if (nr == 2 && find_temp_source(data, temp, data->num_temp_alarms) < 0) + return 0; /* alarm */ + + if (nr == 3 && find_temp_source(data, temp, data->num_temp_beeps) < 0) + return 0; /* beep */ + + if (nr == 4 && !data->reg_temp[1][temp]) /* max */ + return 0; + + if (nr == 5 && !data->reg_temp[2][temp]) /* max_hyst */ + return 0; + + if (nr == 6 && !data->reg_temp[3][temp]) /* crit */ + return 0; + + if (nr == 7 && !data->reg_temp[4][temp]) /* lcrit */ + return 0; + + /* offset and type only apply to fixed sensors */ + if (nr > 7 && !(data->have_temp_fixed & BIT(temp))) + return 0; + + return attr->mode; +} + +SENSOR_TEMPLATE_2(temp_input, "temp%d_input", S_IRUGO, show_temp, NULL, 0, 0); +SENSOR_TEMPLATE(temp_label, "temp%d_label", S_IRUGO, show_temp_label, NULL, 0); +SENSOR_TEMPLATE_2(temp_max, "temp%d_max", S_IRUGO | S_IWUSR, show_temp, + store_temp, 0, 1); +SENSOR_TEMPLATE_2(temp_max_hyst, "temp%d_max_hyst", S_IRUGO | S_IWUSR, + show_temp, store_temp, 0, 2); +SENSOR_TEMPLATE_2(temp_crit, "temp%d_crit", S_IRUGO | S_IWUSR, show_temp, + store_temp, 0, 3); +SENSOR_TEMPLATE_2(temp_lcrit, "temp%d_lcrit", S_IRUGO | S_IWUSR, show_temp, + store_temp, 0, 4); +SENSOR_TEMPLATE(temp_offset, "temp%d_offset", S_IRUGO | S_IWUSR, + show_temp_offset, store_temp_offset, 0); +SENSOR_TEMPLATE(temp_type, "temp%d_type", S_IRUGO | S_IWUSR, show_temp_type, + store_temp_type, 0); +SENSOR_TEMPLATE(temp_alarm, "temp%d_alarm", S_IRUGO, show_temp_alarm, NULL, 0); +SENSOR_TEMPLATE(temp_beep, "temp%d_beep", S_IRUGO | S_IWUSR, show_temp_beep, + store_temp_beep, 0); + +/* + * nct6775_temp_is_visible uses the index into the following array + * to determine if attributes should be created or not. + * Any change in order or content must be matched. + */ +static struct sensor_device_template *nct6775_attributes_temp_template[] = { + &sensor_dev_template_temp_input, + &sensor_dev_template_temp_label, + &sensor_dev_template_temp_alarm, /* 2 */ + &sensor_dev_template_temp_beep, /* 3 */ + &sensor_dev_template_temp_max, /* 4 */ + &sensor_dev_template_temp_max_hyst, /* 5 */ + &sensor_dev_template_temp_crit, /* 6 */ + &sensor_dev_template_temp_lcrit, /* 7 */ + &sensor_dev_template_temp_offset, /* 8 */ + &sensor_dev_template_temp_type, /* 9 */ + NULL +}; + +static const struct sensor_template_group nct6775_temp_template_group = { + .templates = nct6775_attributes_temp_template, + .is_visible = nct6775_temp_is_visible, + .base = 1, +}; + +static ssize_t +show_pwm_mode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + return sprintf(buf, "%d\n", data->pwm_mode[sattr->index]); +} + +static ssize_t +store_pwm_mode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + u8 reg; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (val > 1) + return -EINVAL; + + /* Setting DC mode (0) is not supported for all chips/channels */ + if (data->REG_PWM_MODE[nr] == 0) { + if (!val) + return -EINVAL; + return count; + } + + mutex_lock(&data->update_lock); + data->pwm_mode[nr] = val; + reg = nct6775_read_value(data, data->REG_PWM_MODE[nr]); + reg &= ~data->PWM_MODE_MASK[nr]; + if (!val) + reg |= data->PWM_MODE_MASK[nr]; + nct6775_write_value(data, data->REG_PWM_MODE[nr], reg); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_pwm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + int pwm; + + /* + * For automatic fan control modes, show current pwm readings. + * Otherwise, show the configured value. + */ + if (index == 0 && data->pwm_enable[nr] > manual) + pwm = nct6775_read_value(data, data->REG_PWM_READ[nr]); + else + pwm = data->pwm[index][nr]; + + return sprintf(buf, "%d\n", pwm); +} + +static ssize_t +store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + unsigned long val; + int minval[7] = { 0, 1, 1, data->pwm[2][nr], 0, 0, 0 }; + int maxval[7] + = { 255, 255, data->pwm[3][nr] ? : 255, 255, 255, 255, 255 }; + int err; + u8 reg; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + val = clamp_val(val, minval[index], maxval[index]); + + mutex_lock(&data->update_lock); + data->pwm[index][nr] = val; + nct6775_write_value(data, data->REG_PWM[index][nr], val); + if (index == 2) { /* floor: disable if val == 0 */ + reg = nct6775_read_value(data, data->REG_TEMP_SEL[nr]); + reg &= 0x7f; + if (val) + reg |= 0x80; + nct6775_write_value(data, data->REG_TEMP_SEL[nr], reg); + } + mutex_unlock(&data->update_lock); + return count; +} + +/* Returns 0 if OK, -EINVAL otherwise */ +static int check_trip_points(struct nct6775_data *data, int nr) +{ + int i; + + for (i = 0; i < data->auto_pwm_num - 1; i++) { + if (data->auto_temp[nr][i] > data->auto_temp[nr][i + 1]) + return -EINVAL; + } + for (i = 0; i < data->auto_pwm_num - 1; i++) { + if (data->auto_pwm[nr][i] > data->auto_pwm[nr][i + 1]) + return -EINVAL; + } + /* validate critical temperature and pwm if enabled (pwm > 0) */ + if (data->auto_pwm[nr][data->auto_pwm_num]) { + if (data->auto_temp[nr][data->auto_pwm_num - 1] > + data->auto_temp[nr][data->auto_pwm_num] || + data->auto_pwm[nr][data->auto_pwm_num - 1] > + data->auto_pwm[nr][data->auto_pwm_num]) + return -EINVAL; + } + return 0; +} + +static void pwm_update_registers(struct nct6775_data *data, int nr) +{ + u8 reg; + + switch (data->pwm_enable[nr]) { + case off: + case manual: + break; + case speed_cruise: + reg = nct6775_read_value(data, data->REG_FAN_MODE[nr]); + reg = (reg & ~data->tolerance_mask) | + (data->target_speed_tolerance[nr] & data->tolerance_mask); + nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); + nct6775_write_value(data, data->REG_TARGET[nr], + data->target_speed[nr] & 0xff); + if (data->REG_TOLERANCE_H) { + reg = (data->target_speed[nr] >> 8) & 0x0f; + reg |= (data->target_speed_tolerance[nr] & 0x38) << 1; + nct6775_write_value(data, + data->REG_TOLERANCE_H[nr], + reg); + } + break; + case thermal_cruise: + nct6775_write_value(data, data->REG_TARGET[nr], + data->target_temp[nr]); + /* fall through */ + default: + reg = nct6775_read_value(data, data->REG_FAN_MODE[nr]); + reg = (reg & ~data->tolerance_mask) | + data->temp_tolerance[0][nr]; + nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); + break; + } +} + +static ssize_t +show_pwm_enable(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + return sprintf(buf, "%d\n", data->pwm_enable[sattr->index]); +} + +static ssize_t +store_pwm_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + u16 reg; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (val > sf4) + return -EINVAL; + + if (val == sf3 && data->kind != nct6775) + return -EINVAL; + + if (val == sf4 && check_trip_points(data, nr)) { + dev_err(dev, "Inconsistent trip points, not switching to SmartFan IV mode\n"); + dev_err(dev, "Adjust trip points and try again\n"); + return -EINVAL; + } + + mutex_lock(&data->update_lock); + data->pwm_enable[nr] = val; + if (val == off) { + /* + * turn off pwm control: select manual mode, set pwm to maximum + */ + data->pwm[0][nr] = 255; + nct6775_write_value(data, data->REG_PWM[0][nr], 255); + } + pwm_update_registers(data, nr); + reg = nct6775_read_value(data, data->REG_FAN_MODE[nr]); + reg &= 0x0f; + reg |= pwm_enable_to_reg(val) << 4; + nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_pwm_temp_sel_common(struct nct6775_data *data, char *buf, int src) +{ + int i, sel = 0; + + for (i = 0; i < NUM_TEMP; i++) { + if (!(data->have_temp & BIT(i))) + continue; + if (src == data->temp_src[i]) { + sel = i + 1; + break; + } + } + + return sprintf(buf, "%d\n", sel); +} + +static ssize_t +show_pwm_temp_sel(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int index = sattr->index; + + return show_pwm_temp_sel_common(data, buf, data->pwm_temp_sel[index]); +} + +static ssize_t +store_pwm_temp_sel(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err, reg, src; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + if (val == 0 || val > NUM_TEMP) + return -EINVAL; + if (!(data->have_temp & BIT(val - 1)) || !data->temp_src[val - 1]) + return -EINVAL; + + mutex_lock(&data->update_lock); + src = data->temp_src[val - 1]; + data->pwm_temp_sel[nr] = src; + reg = nct6775_read_value(data, data->REG_TEMP_SEL[nr]); + reg &= 0xe0; + reg |= src; + nct6775_write_value(data, data->REG_TEMP_SEL[nr], reg); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_pwm_weight_temp_sel(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int index = sattr->index; + + return show_pwm_temp_sel_common(data, buf, + data->pwm_weight_temp_sel[index]); +} + +static ssize_t +store_pwm_weight_temp_sel(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err, reg, src; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + if (val > NUM_TEMP) + return -EINVAL; + val = array_index_nospec(val, NUM_TEMP + 1); + if (val && (!(data->have_temp & BIT(val - 1)) || + !data->temp_src[val - 1])) + return -EINVAL; + + mutex_lock(&data->update_lock); + if (val) { + src = data->temp_src[val - 1]; + data->pwm_weight_temp_sel[nr] = src; + reg = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[nr]); + reg &= 0xe0; + reg |= (src | 0x80); + nct6775_write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); + } else { + data->pwm_weight_temp_sel[nr] = 0; + reg = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[nr]); + reg &= 0x7f; + nct6775_write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); + } + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_target_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + return sprintf(buf, "%d\n", data->target_temp[sattr->index] * 1000); +} + +static ssize_t +store_target_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, + data->target_temp_mask); + + mutex_lock(&data->update_lock); + data->target_temp[nr] = val; + pwm_update_registers(data, nr); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_target_speed(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + + return sprintf(buf, "%d\n", + fan_from_reg16(data->target_speed[nr], + data->fan_div[nr])); +} + +static ssize_t +store_target_speed(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + u16 speed; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(val, 0, 1350000U); + speed = fan_to_reg(val, data->fan_div[nr]); + + mutex_lock(&data->update_lock); + data->target_speed[nr] = speed; + pwm_update_registers(data, nr); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_temp_tolerance(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + + return sprintf(buf, "%d\n", data->temp_tolerance[index][nr] * 1000); +} + +static ssize_t +store_temp_tolerance(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + /* Limit tolerance as needed */ + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, data->tolerance_mask); + + mutex_lock(&data->update_lock); + data->temp_tolerance[index][nr] = val; + if (index) + pwm_update_registers(data, nr); + else + nct6775_write_value(data, + data->REG_CRITICAL_TEMP_TOLERANCE[nr], + val); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Fan speed tolerance is a tricky beast, since the associated register is + * a tick counter, but the value is reported and configured as rpm. + * Compute resulting low and high rpm values and report the difference. + */ +static ssize_t +show_speed_tolerance(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + int low = data->target_speed[nr] - data->target_speed_tolerance[nr]; + int high = data->target_speed[nr] + data->target_speed_tolerance[nr]; + int tolerance; + + if (low <= 0) + low = 1; + if (high > 0xffff) + high = 0xffff; + if (high < low) + high = low; + + tolerance = (fan_from_reg16(low, data->fan_div[nr]) + - fan_from_reg16(high, data->fan_div[nr])) / 2; + + return sprintf(buf, "%d\n", tolerance); +} + +static ssize_t +store_speed_tolerance(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + int low, high; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + high = fan_from_reg16(data->target_speed[nr], + data->fan_div[nr]) + val; + low = fan_from_reg16(data->target_speed[nr], + data->fan_div[nr]) - val; + if (low <= 0) + low = 1; + if (high < low) + high = low; + + val = (fan_to_reg(low, data->fan_div[nr]) - + fan_to_reg(high, data->fan_div[nr])) / 2; + + /* Limit tolerance as needed */ + val = clamp_val(val, 0, data->speed_tolerance_limit); + + mutex_lock(&data->update_lock); + data->target_speed_tolerance[nr] = val; + pwm_update_registers(data, nr); + mutex_unlock(&data->update_lock); + return count; +} + +SENSOR_TEMPLATE_2(pwm, "pwm%d", S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0, 0); +SENSOR_TEMPLATE(pwm_mode, "pwm%d_mode", S_IWUSR | S_IRUGO, show_pwm_mode, + store_pwm_mode, 0); +SENSOR_TEMPLATE(pwm_enable, "pwm%d_enable", S_IWUSR | S_IRUGO, show_pwm_enable, + store_pwm_enable, 0); +SENSOR_TEMPLATE(pwm_temp_sel, "pwm%d_temp_sel", S_IWUSR | S_IRUGO, + show_pwm_temp_sel, store_pwm_temp_sel, 0); +SENSOR_TEMPLATE(pwm_target_temp, "pwm%d_target_temp", S_IWUSR | S_IRUGO, + show_target_temp, store_target_temp, 0); +SENSOR_TEMPLATE(fan_target, "fan%d_target", S_IWUSR | S_IRUGO, + show_target_speed, store_target_speed, 0); +SENSOR_TEMPLATE(fan_tolerance, "fan%d_tolerance", S_IWUSR | S_IRUGO, + show_speed_tolerance, store_speed_tolerance, 0); + +/* Smart Fan registers */ + +static ssize_t +show_weight_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + + return sprintf(buf, "%d\n", data->weight_temp[index][nr] * 1000); +} + +static ssize_t +store_weight_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, 255); + + mutex_lock(&data->update_lock); + data->weight_temp[index][nr] = val; + nct6775_write_value(data, data->REG_WEIGHT_TEMP[index][nr], val); + mutex_unlock(&data->update_lock); + return count; +} + +SENSOR_TEMPLATE(pwm_weight_temp_sel, "pwm%d_weight_temp_sel", S_IWUSR | S_IRUGO, + show_pwm_weight_temp_sel, store_pwm_weight_temp_sel, 0); +SENSOR_TEMPLATE_2(pwm_weight_temp_step, "pwm%d_weight_temp_step", + S_IWUSR | S_IRUGO, show_weight_temp, store_weight_temp, 0, 0); +SENSOR_TEMPLATE_2(pwm_weight_temp_step_tol, "pwm%d_weight_temp_step_tol", + S_IWUSR | S_IRUGO, show_weight_temp, store_weight_temp, 0, 1); +SENSOR_TEMPLATE_2(pwm_weight_temp_step_base, "pwm%d_weight_temp_step_base", + S_IWUSR | S_IRUGO, show_weight_temp, store_weight_temp, 0, 2); +SENSOR_TEMPLATE_2(pwm_weight_duty_step, "pwm%d_weight_duty_step", + S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0, 5); +SENSOR_TEMPLATE_2(pwm_weight_duty_base, "pwm%d_weight_duty_base", + S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0, 6); + +static ssize_t +show_fan_time(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + + return sprintf(buf, "%d\n", + step_time_from_reg(data->fan_time[index][nr], + data->pwm_mode[nr])); +} + +static ssize_t +store_fan_time(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int index = sattr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = step_time_to_reg(val, data->pwm_mode[nr]); + mutex_lock(&data->update_lock); + data->fan_time[index][nr] = val; + nct6775_write_value(data, data->REG_FAN_TIME[index][nr], val); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_auto_pwm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + return sprintf(buf, "%d\n", data->auto_pwm[sattr->nr][sattr->index]); +} + +static ssize_t +store_auto_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int point = sattr->index; + unsigned long val; + int err; + u8 reg; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + if (val > 255) + return -EINVAL; + + if (point == data->auto_pwm_num) { + if (data->kind != nct6775 && !val) + return -EINVAL; + if (data->kind != nct6779 && val) + val = 0xff; + } + + mutex_lock(&data->update_lock); + data->auto_pwm[nr][point] = val; + if (point < data->auto_pwm_num) { + nct6775_write_value(data, + NCT6775_AUTO_PWM(data, nr, point), + data->auto_pwm[nr][point]); + } else { + switch (data->kind) { + case nct6775: + /* disable if needed (pwm == 0) */ + reg = nct6775_read_value(data, + NCT6775_REG_CRITICAL_ENAB[nr]); + if (val) + reg |= 0x02; + else + reg &= ~0x02; + nct6775_write_value(data, NCT6775_REG_CRITICAL_ENAB[nr], + reg); + break; + case nct6776: + break; /* always enabled, nothing to do */ + case nct6106: + case nct6779: + case nct6791: + case nct6792: + case nct6793: + case nct6795: + case nct6796: + nct6775_write_value(data, data->REG_CRITICAL_PWM[nr], + val); + reg = nct6775_read_value(data, + data->REG_CRITICAL_PWM_ENABLE[nr]); + if (val == 255) + reg &= ~data->CRITICAL_PWM_ENABLE_MASK; + else + reg |= data->CRITICAL_PWM_ENABLE_MASK; + nct6775_write_value(data, + data->REG_CRITICAL_PWM_ENABLE[nr], + reg); + break; + } + } + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_auto_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int point = sattr->index; + + /* + * We don't know for sure if the temperature is signed or unsigned. + * Assume it is unsigned. + */ + return sprintf(buf, "%d\n", data->auto_temp[nr][point] * 1000); +} + +static ssize_t +store_auto_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int nr = sattr->nr; + int point = sattr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + if (val > 255000) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->auto_temp[nr][point] = DIV_ROUND_CLOSEST(val, 1000); + if (point < data->auto_pwm_num) { + nct6775_write_value(data, + NCT6775_AUTO_TEMP(data, nr, point), + data->auto_temp[nr][point]); + } else { + nct6775_write_value(data, data->REG_CRITICAL_TEMP[nr], + data->auto_temp[nr][point]); + } + mutex_unlock(&data->update_lock); + return count; +} + +static umode_t nct6775_pwm_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct6775_data *data = dev_get_drvdata(dev); + int pwm = index / 36; /* pwm index */ + int nr = index % 36; /* attribute index */ + + if (!(data->has_pwm & BIT(pwm))) + return 0; + + if ((nr >= 14 && nr <= 18) || nr == 21) /* weight */ + if (!data->REG_WEIGHT_TEMP_SEL[pwm]) + return 0; + if (nr == 19 && data->REG_PWM[3] == NULL) /* pwm_max */ + return 0; + if (nr == 20 && data->REG_PWM[4] == NULL) /* pwm_step */ + return 0; + if (nr == 21 && data->REG_PWM[6] == NULL) /* weight_duty_base */ + return 0; + + if (nr >= 22 && nr <= 35) { /* auto point */ + int api = (nr - 22) / 2; /* auto point index */ + + if (api > data->auto_pwm_num) + return 0; + } + return attr->mode; +} + +SENSOR_TEMPLATE_2(pwm_stop_time, "pwm%d_stop_time", S_IWUSR | S_IRUGO, + show_fan_time, store_fan_time, 0, 0); +SENSOR_TEMPLATE_2(pwm_step_up_time, "pwm%d_step_up_time", S_IWUSR | S_IRUGO, + show_fan_time, store_fan_time, 0, 1); +SENSOR_TEMPLATE_2(pwm_step_down_time, "pwm%d_step_down_time", S_IWUSR | S_IRUGO, + show_fan_time, store_fan_time, 0, 2); +SENSOR_TEMPLATE_2(pwm_start, "pwm%d_start", S_IWUSR | S_IRUGO, show_pwm, + store_pwm, 0, 1); +SENSOR_TEMPLATE_2(pwm_floor, "pwm%d_floor", S_IWUSR | S_IRUGO, show_pwm, + store_pwm, 0, 2); +SENSOR_TEMPLATE_2(pwm_temp_tolerance, "pwm%d_temp_tolerance", S_IWUSR | S_IRUGO, + show_temp_tolerance, store_temp_tolerance, 0, 0); +SENSOR_TEMPLATE_2(pwm_crit_temp_tolerance, "pwm%d_crit_temp_tolerance", + S_IWUSR | S_IRUGO, show_temp_tolerance, store_temp_tolerance, + 0, 1); + +SENSOR_TEMPLATE_2(pwm_max, "pwm%d_max", S_IWUSR | S_IRUGO, show_pwm, store_pwm, + 0, 3); + +SENSOR_TEMPLATE_2(pwm_step, "pwm%d_step", S_IWUSR | S_IRUGO, show_pwm, + store_pwm, 0, 4); + +SENSOR_TEMPLATE_2(pwm_auto_point1_pwm, "pwm%d_auto_point1_pwm", + S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 0); +SENSOR_TEMPLATE_2(pwm_auto_point1_temp, "pwm%d_auto_point1_temp", + S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 0); + +SENSOR_TEMPLATE_2(pwm_auto_point2_pwm, "pwm%d_auto_point2_pwm", + S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 1); +SENSOR_TEMPLATE_2(pwm_auto_point2_temp, "pwm%d_auto_point2_temp", + S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 1); + +SENSOR_TEMPLATE_2(pwm_auto_point3_pwm, "pwm%d_auto_point3_pwm", + S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 2); +SENSOR_TEMPLATE_2(pwm_auto_point3_temp, "pwm%d_auto_point3_temp", + S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 2); + +SENSOR_TEMPLATE_2(pwm_auto_point4_pwm, "pwm%d_auto_point4_pwm", + S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 3); +SENSOR_TEMPLATE_2(pwm_auto_point4_temp, "pwm%d_auto_point4_temp", + S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 3); + +SENSOR_TEMPLATE_2(pwm_auto_point5_pwm, "pwm%d_auto_point5_pwm", + S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 4); +SENSOR_TEMPLATE_2(pwm_auto_point5_temp, "pwm%d_auto_point5_temp", + S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 4); + +SENSOR_TEMPLATE_2(pwm_auto_point6_pwm, "pwm%d_auto_point6_pwm", + S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 5); +SENSOR_TEMPLATE_2(pwm_auto_point6_temp, "pwm%d_auto_point6_temp", + S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 5); + +SENSOR_TEMPLATE_2(pwm_auto_point7_pwm, "pwm%d_auto_point7_pwm", + S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 6); +SENSOR_TEMPLATE_2(pwm_auto_point7_temp, "pwm%d_auto_point7_temp", + S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 6); + +/* + * nct6775_pwm_is_visible uses the index into the following array + * to determine if attributes should be created or not. + * Any change in order or content must be matched. + */ +static struct sensor_device_template *nct6775_attributes_pwm_template[] = { + &sensor_dev_template_pwm, + &sensor_dev_template_pwm_mode, + &sensor_dev_template_pwm_enable, + &sensor_dev_template_pwm_temp_sel, + &sensor_dev_template_pwm_temp_tolerance, + &sensor_dev_template_pwm_crit_temp_tolerance, + &sensor_dev_template_pwm_target_temp, + &sensor_dev_template_fan_target, + &sensor_dev_template_fan_tolerance, + &sensor_dev_template_pwm_stop_time, + &sensor_dev_template_pwm_step_up_time, + &sensor_dev_template_pwm_step_down_time, + &sensor_dev_template_pwm_start, + &sensor_dev_template_pwm_floor, + &sensor_dev_template_pwm_weight_temp_sel, /* 14 */ + &sensor_dev_template_pwm_weight_temp_step, + &sensor_dev_template_pwm_weight_temp_step_tol, + &sensor_dev_template_pwm_weight_temp_step_base, + &sensor_dev_template_pwm_weight_duty_step, /* 18 */ + &sensor_dev_template_pwm_max, /* 19 */ + &sensor_dev_template_pwm_step, /* 20 */ + &sensor_dev_template_pwm_weight_duty_base, /* 21 */ + &sensor_dev_template_pwm_auto_point1_pwm, /* 22 */ + &sensor_dev_template_pwm_auto_point1_temp, + &sensor_dev_template_pwm_auto_point2_pwm, + &sensor_dev_template_pwm_auto_point2_temp, + &sensor_dev_template_pwm_auto_point3_pwm, + &sensor_dev_template_pwm_auto_point3_temp, + &sensor_dev_template_pwm_auto_point4_pwm, + &sensor_dev_template_pwm_auto_point4_temp, + &sensor_dev_template_pwm_auto_point5_pwm, + &sensor_dev_template_pwm_auto_point5_temp, + &sensor_dev_template_pwm_auto_point6_pwm, + &sensor_dev_template_pwm_auto_point6_temp, + &sensor_dev_template_pwm_auto_point7_pwm, + &sensor_dev_template_pwm_auto_point7_temp, /* 35 */ + + NULL +}; + +static const struct sensor_template_group nct6775_pwm_template_group = { + .templates = nct6775_attributes_pwm_template, + .is_visible = nct6775_pwm_is_visible, + .base = 1, +}; + +static ssize_t +cpu0_vid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); +} + +static DEVICE_ATTR_RO(cpu0_vid); + +/* Case open detection */ + +static ssize_t +clear_caseopen(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + int nr = to_sensor_dev_attr(attr)->index - INTRUSION_ALARM_BASE; + unsigned long val; + u8 reg; + int ret; + + if (kstrtoul(buf, 10, &val) || val != 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + + /* + * Use CR registers to clear caseopen status. + * The CR registers are the same for all chips, and not all chips + * support clearing the caseopen status through "regular" registers. + */ + ret = superio_enter(data->sioreg); + if (ret) { + count = ret; + goto error; + } + + superio_select(data->sioreg, NCT6775_LD_ACPI); + reg = superio_inb(data->sioreg, NCT6775_REG_CR_CASEOPEN_CLR[nr]); + reg |= NCT6775_CR_CASEOPEN_CLR_MASK[nr]; + superio_outb(data->sioreg, NCT6775_REG_CR_CASEOPEN_CLR[nr], reg); + reg &= ~NCT6775_CR_CASEOPEN_CLR_MASK[nr]; + superio_outb(data->sioreg, NCT6775_REG_CR_CASEOPEN_CLR[nr], reg); + superio_exit(data->sioreg); + + data->valid = false; /* Force cache refresh */ +error: + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(intrusion0_alarm, S_IWUSR | S_IRUGO, show_alarm, + clear_caseopen, INTRUSION_ALARM_BASE); +static SENSOR_DEVICE_ATTR(intrusion1_alarm, S_IWUSR | S_IRUGO, show_alarm, + clear_caseopen, INTRUSION_ALARM_BASE + 1); +static SENSOR_DEVICE_ATTR(intrusion0_beep, S_IWUSR | S_IRUGO, show_beep, + store_beep, INTRUSION_ALARM_BASE); +static SENSOR_DEVICE_ATTR(intrusion1_beep, S_IWUSR | S_IRUGO, show_beep, + store_beep, INTRUSION_ALARM_BASE + 1); +static SENSOR_DEVICE_ATTR(beep_enable, S_IWUSR | S_IRUGO, show_beep, + store_beep, BEEP_ENABLE_BASE); + +static umode_t nct6775_other_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct6775_data *data = dev_get_drvdata(dev); + + if (index == 0 && !data->have_vid) + return 0; + + if (index == 1 || index == 2) { + if (data->ALARM_BITS[INTRUSION_ALARM_BASE + index - 1] < 0) + return 0; + } + + if (index == 3 || index == 4) { + if (data->BEEP_BITS[INTRUSION_ALARM_BASE + index - 3] < 0) + return 0; + } + + return attr->mode; +} + +/* + * nct6775_other_is_visible uses the index into the following array + * to determine if attributes should be created or not. + * Any change in order or content must be matched. + */ +static struct attribute *nct6775_attributes_other[] = { + &dev_attr_cpu0_vid.attr, /* 0 */ + &sensor_dev_attr_intrusion0_alarm.dev_attr.attr, /* 1 */ + &sensor_dev_attr_intrusion1_alarm.dev_attr.attr, /* 2 */ + &sensor_dev_attr_intrusion0_beep.dev_attr.attr, /* 3 */ + &sensor_dev_attr_intrusion1_beep.dev_attr.attr, /* 4 */ + &sensor_dev_attr_beep_enable.dev_attr.attr, /* 5 */ + + NULL +}; + +static const struct attribute_group nct6775_group_other = { + .attrs = nct6775_attributes_other, + .is_visible = nct6775_other_is_visible, +}; + +static inline void nct6775_init_device(struct nct6775_data *data) +{ + int i; + u8 tmp, diode; + + /* Start monitoring if needed */ + if (data->REG_CONFIG) { + tmp = nct6775_read_value(data, data->REG_CONFIG); + if (!(tmp & 0x01)) + nct6775_write_value(data, data->REG_CONFIG, tmp | 0x01); + } + + /* Enable temperature sensors if needed */ + for (i = 0; i < NUM_TEMP; i++) { + if (!(data->have_temp & BIT(i))) + continue; + if (!data->reg_temp_config[i]) + continue; + tmp = nct6775_read_value(data, data->reg_temp_config[i]); + if (tmp & 0x01) + nct6775_write_value(data, data->reg_temp_config[i], + tmp & 0xfe); + } + + /* Enable VBAT monitoring if needed */ + tmp = nct6775_read_value(data, data->REG_VBAT); + if (!(tmp & 0x01)) + nct6775_write_value(data, data->REG_VBAT, tmp | 0x01); + + diode = nct6775_read_value(data, data->REG_DIODE); + + for (i = 0; i < data->temp_fixed_num; i++) { + if (!(data->have_temp_fixed & BIT(i))) + continue; + if ((tmp & (data->DIODE_MASK << i))) /* diode */ + data->temp_type[i] + = 3 - ((diode >> i) & data->DIODE_MASK); + else /* thermistor */ + data->temp_type[i] = 4; + } +} + +static void +nct6775_check_fan_inputs(struct nct6775_data *data) +{ + bool fan3pin = false, fan4pin = false, fan4min = false; + bool fan5pin = false, fan6pin = false, fan7pin = false; + bool pwm3pin = false, pwm4pin = false, pwm5pin = false; + bool pwm6pin = false, pwm7pin = false; + int sioreg = data->sioreg; + int regval; + + /* Store SIO_REG_ENABLE for use during resume */ + superio_select(sioreg, NCT6775_LD_HWM); + data->sio_reg_enable = superio_inb(sioreg, SIO_REG_ENABLE); + + /* fan4 and fan5 share some pins with the GPIO and serial flash */ + if (data->kind == nct6775) { + regval = superio_inb(sioreg, 0x2c); + + fan3pin = regval & BIT(6); + pwm3pin = regval & BIT(7); + + /* On NCT6775, fan4 shares pins with the fdc interface */ + fan4pin = !(superio_inb(sioreg, 0x2A) & 0x80); + } else if (data->kind == nct6776) { + bool gpok = superio_inb(sioreg, 0x27) & 0x80; + const char *board_vendor, *board_name; + + board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + board_name = dmi_get_system_info(DMI_BOARD_NAME); + + if (board_name && board_vendor && + !strcmp(board_vendor, "ASRock")) { + /* + * Auxiliary fan monitoring is not enabled on ASRock + * Z77 Pro4-M if booted in UEFI Ultra-FastBoot mode. + * Observed with BIOS version 2.00. + */ + if (!strcmp(board_name, "Z77 Pro4-M")) { + if ((data->sio_reg_enable & 0xe0) != 0xe0) { + data->sio_reg_enable |= 0xe0; + superio_outb(sioreg, SIO_REG_ENABLE, + data->sio_reg_enable); + } + } + } + + if (data->sio_reg_enable & 0x80) + fan3pin = gpok; + else + fan3pin = !(superio_inb(sioreg, 0x24) & 0x40); + + if (data->sio_reg_enable & 0x40) + fan4pin = gpok; + else + fan4pin = superio_inb(sioreg, 0x1C) & 0x01; + + if (data->sio_reg_enable & 0x20) + fan5pin = gpok; + else + fan5pin = superio_inb(sioreg, 0x1C) & 0x02; + + fan4min = fan4pin; + pwm3pin = fan3pin; + } else if (data->kind == nct6106) { + regval = superio_inb(sioreg, 0x24); + fan3pin = !(regval & 0x80); + pwm3pin = regval & 0x08; + } else { + /* NCT6779D, NCT6791D, NCT6792D, NCT6793D, NCT6795D, NCT6796D */ + int regval_1b, regval_2a, regval_2f; + bool dsw_en; + + regval = superio_inb(sioreg, 0x1c); + + fan3pin = !(regval & BIT(5)); + fan4pin = !(regval & BIT(6)); + fan5pin = !(regval & BIT(7)); + + pwm3pin = !(regval & BIT(0)); + pwm4pin = !(regval & BIT(1)); + pwm5pin = !(regval & BIT(2)); + + regval = superio_inb(sioreg, 0x2d); + switch (data->kind) { + case nct6791: + case nct6792: + fan6pin = regval & BIT(1); + pwm6pin = regval & BIT(0); + break; + case nct6793: + case nct6795: + case nct6796: + regval_1b = superio_inb(sioreg, 0x1b); + regval_2a = superio_inb(sioreg, 0x2a); + regval_2f = superio_inb(sioreg, 0x2f); + dsw_en = regval_2f & BIT(3); + + if (!pwm5pin) + pwm5pin = regval & BIT(7); + + if (!fan5pin) + fan5pin = regval_1b & BIT(5); + + superio_select(sioreg, NCT6775_LD_12); + if (data->kind != nct6796) { + int regval_eb = superio_inb(sioreg, 0xeb); + + if (!dsw_en) { + fan6pin = regval & BIT(1); + pwm6pin = regval & BIT(0); + } + + if (!fan5pin) + fan5pin = regval_eb & BIT(5); + if (!pwm5pin) + pwm5pin = (regval_eb & BIT(4)) && + !(regval_2a & BIT(0)); + if (!fan6pin) + fan6pin = regval_eb & BIT(3); + if (!pwm6pin) + pwm6pin = regval_eb & BIT(2); + } + + if (data->kind == nct6795 || data->kind == nct6796) { + int regval_ed = superio_inb(sioreg, 0xed); + + if (!fan6pin) + fan6pin = (regval_2a & BIT(4)) && + (!dsw_en || + (dsw_en && (regval_ed & BIT(4)))); + if (!pwm6pin) + pwm6pin = (regval_2a & BIT(3)) && + (regval_ed & BIT(2)); + } + + if (data->kind == nct6796) { + int regval_1d = superio_inb(sioreg, 0x1d); + int regval_2b = superio_inb(sioreg, 0x2b); + + fan7pin = !(regval_2b & BIT(2)); + pwm7pin = !(regval_1d & (BIT(2) | BIT(3))); + } + + break; + default: /* NCT6779D */ + break; + } + + fan4min = fan4pin; + } + + /* fan 1 and 2 (0x03) are always present */ + data->has_fan = 0x03 | (fan3pin << 2) | (fan4pin << 3) | + (fan5pin << 4) | (fan6pin << 5) | (fan7pin << 6); + data->has_fan_min = 0x03 | (fan3pin << 2) | (fan4min << 3) | + (fan5pin << 4) | (fan6pin << 5) | (fan7pin << 6); + data->has_pwm = 0x03 | (pwm3pin << 2) | (pwm4pin << 3) | + (pwm5pin << 4) | (pwm6pin << 5) | (pwm7pin << 6); +} + +static void add_temp_sensors(struct nct6775_data *data, const u16 *regp, + int *available, int *mask) +{ + int i; + u8 src; + + for (i = 0; i < data->pwm_num && *available; i++) { + int index; + + if (!regp[i]) + continue; + src = nct6775_read_value(data, regp[i]); + src &= 0x1f; + if (!src || (*mask & BIT(src))) + continue; + if (!(data->temp_mask & BIT(src))) + continue; + + index = __ffs(*available); + nct6775_write_value(data, data->REG_TEMP_SOURCE[index], src); + *available &= ~BIT(index); + *mask |= BIT(src); + } +} + +static int nct6775_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nct6775_sio_data *sio_data = dev_get_platdata(dev); + struct nct6775_data *data; + struct resource *res; + int i, s, err = 0; + int src, mask, available; + const u16 *reg_temp, *reg_temp_over, *reg_temp_hyst, *reg_temp_config; + const u16 *reg_temp_mon, *reg_temp_alternate, *reg_temp_crit; + const u16 *reg_temp_crit_l = NULL, *reg_temp_crit_h = NULL; + int num_reg_temp, num_reg_temp_mon; + u8 cr2a; + struct attribute_group *group; + struct device *hwmon_dev; + int num_attr_groups = 0; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(&pdev->dev, res->start, IOREGION_LENGTH, + DRVNAME)) + return -EBUSY; + + data = devm_kzalloc(&pdev->dev, sizeof(struct nct6775_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->kind = sio_data->kind; + data->sioreg = sio_data->sioreg; + data->addr = res->start; + mutex_init(&data->update_lock); + data->name = nct6775_device_names[data->kind]; + data->bank = 0xff; /* Force initial bank selection */ + platform_set_drvdata(pdev, data); + + switch (data->kind) { + case nct6106: + data->in_num = 9; + data->pwm_num = 3; + data->auto_pwm_num = 4; + data->temp_fixed_num = 3; + data->num_temp_alarms = 6; + data->num_temp_beeps = 6; + + data->fan_from_reg = fan_from_reg13; + data->fan_from_reg_min = fan_from_reg13; + + data->temp_label = nct6776_temp_label; + data->temp_mask = NCT6776_TEMP_MASK; + data->virt_temp_mask = NCT6776_VIRT_TEMP_MASK; + + data->REG_VBAT = NCT6106_REG_VBAT; + data->REG_DIODE = NCT6106_REG_DIODE; + data->DIODE_MASK = NCT6106_DIODE_MASK; + data->REG_VIN = NCT6106_REG_IN; + data->REG_IN_MINMAX[0] = NCT6106_REG_IN_MIN; + data->REG_IN_MINMAX[1] = NCT6106_REG_IN_MAX; + data->REG_TARGET = NCT6106_REG_TARGET; + data->REG_FAN = NCT6106_REG_FAN; + data->REG_FAN_MODE = NCT6106_REG_FAN_MODE; + data->REG_FAN_MIN = NCT6106_REG_FAN_MIN; + data->REG_FAN_PULSES = NCT6106_REG_FAN_PULSES; + data->FAN_PULSE_SHIFT = NCT6106_FAN_PULSE_SHIFT; + data->REG_FAN_TIME[0] = NCT6106_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6106_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6106_REG_FAN_STEP_DOWN_TIME; + data->REG_TOLERANCE_H = NCT6106_REG_TOLERANCE_H; + data->REG_PWM[0] = NCT6106_REG_PWM; + data->REG_PWM[1] = NCT6106_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6106_REG_FAN_STOP_OUTPUT; + data->REG_PWM[5] = NCT6106_REG_WEIGHT_DUTY_STEP; + data->REG_PWM[6] = NCT6106_REG_WEIGHT_DUTY_BASE; + data->REG_PWM_READ = NCT6106_REG_PWM_READ; + data->REG_PWM_MODE = NCT6106_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6106_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6106_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6106_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6106_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE + = NCT6106_REG_CRITICAL_TEMP_TOLERANCE; + data->REG_CRITICAL_PWM_ENABLE = NCT6106_REG_CRITICAL_PWM_ENABLE; + data->CRITICAL_PWM_ENABLE_MASK + = NCT6106_CRITICAL_PWM_ENABLE_MASK; + data->REG_CRITICAL_PWM = NCT6106_REG_CRITICAL_PWM; + data->REG_TEMP_OFFSET = NCT6106_REG_TEMP_OFFSET; + data->REG_TEMP_SOURCE = NCT6106_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6106_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6106_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6106_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6106_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6106_REG_WEIGHT_TEMP_BASE; + data->REG_ALARM = NCT6106_REG_ALARM; + data->ALARM_BITS = NCT6106_ALARM_BITS; + data->REG_BEEP = NCT6106_REG_BEEP; + data->BEEP_BITS = NCT6106_BEEP_BITS; + + reg_temp = NCT6106_REG_TEMP; + reg_temp_mon = NCT6106_REG_TEMP_MON; + num_reg_temp = ARRAY_SIZE(NCT6106_REG_TEMP); + num_reg_temp_mon = ARRAY_SIZE(NCT6106_REG_TEMP_MON); + reg_temp_over = NCT6106_REG_TEMP_OVER; + reg_temp_hyst = NCT6106_REG_TEMP_HYST; + reg_temp_config = NCT6106_REG_TEMP_CONFIG; + reg_temp_alternate = NCT6106_REG_TEMP_ALTERNATE; + reg_temp_crit = NCT6106_REG_TEMP_CRIT; + reg_temp_crit_l = NCT6106_REG_TEMP_CRIT_L; + reg_temp_crit_h = NCT6106_REG_TEMP_CRIT_H; + + break; + case nct6775: + data->in_num = 9; + data->pwm_num = 3; + data->auto_pwm_num = 6; + data->has_fan_div = true; + data->temp_fixed_num = 3; + data->num_temp_alarms = 3; + data->num_temp_beeps = 3; + + data->ALARM_BITS = NCT6775_ALARM_BITS; + data->BEEP_BITS = NCT6775_BEEP_BITS; + + data->fan_from_reg = fan_from_reg16; + data->fan_from_reg_min = fan_from_reg8; + data->target_temp_mask = 0x7f; + data->tolerance_mask = 0x0f; + data->speed_tolerance_limit = 15; + + data->temp_label = nct6775_temp_label; + data->temp_mask = NCT6775_TEMP_MASK; + data->virt_temp_mask = NCT6775_VIRT_TEMP_MASK; + + data->REG_CONFIG = NCT6775_REG_CONFIG; + data->REG_VBAT = NCT6775_REG_VBAT; + data->REG_DIODE = NCT6775_REG_DIODE; + data->DIODE_MASK = NCT6775_DIODE_MASK; + data->REG_VIN = NCT6775_REG_IN; + data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; + data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; + data->REG_TARGET = NCT6775_REG_TARGET; + data->REG_FAN = NCT6775_REG_FAN; + data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; + data->REG_FAN_MIN = NCT6775_REG_FAN_MIN; + data->REG_FAN_PULSES = NCT6775_REG_FAN_PULSES; + data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT; + data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6775_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6775_REG_FAN_STEP_DOWN_TIME; + data->REG_PWM[0] = NCT6775_REG_PWM; + data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_PWM[3] = NCT6775_REG_FAN_MAX_OUTPUT; + data->REG_PWM[4] = NCT6775_REG_FAN_STEP_OUTPUT; + data->REG_PWM[5] = NCT6775_REG_WEIGHT_DUTY_STEP; + data->REG_PWM_READ = NCT6775_REG_PWM_READ; + data->REG_PWM_MODE = NCT6775_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6775_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE + = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; + data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET; + data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6775_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6775_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6775_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; + data->REG_ALARM = NCT6775_REG_ALARM; + data->REG_BEEP = NCT6775_REG_BEEP; + + reg_temp = NCT6775_REG_TEMP; + reg_temp_mon = NCT6775_REG_TEMP_MON; + num_reg_temp = ARRAY_SIZE(NCT6775_REG_TEMP); + num_reg_temp_mon = ARRAY_SIZE(NCT6775_REG_TEMP_MON); + reg_temp_over = NCT6775_REG_TEMP_OVER; + reg_temp_hyst = NCT6775_REG_TEMP_HYST; + reg_temp_config = NCT6775_REG_TEMP_CONFIG; + reg_temp_alternate = NCT6775_REG_TEMP_ALTERNATE; + reg_temp_crit = NCT6775_REG_TEMP_CRIT; + + break; + case nct6776: + data->in_num = 9; + data->pwm_num = 3; + data->auto_pwm_num = 4; + data->has_fan_div = false; + data->temp_fixed_num = 3; + data->num_temp_alarms = 3; + data->num_temp_beeps = 6; + + data->ALARM_BITS = NCT6776_ALARM_BITS; + data->BEEP_BITS = NCT6776_BEEP_BITS; + + data->fan_from_reg = fan_from_reg13; + data->fan_from_reg_min = fan_from_reg13; + data->target_temp_mask = 0xff; + data->tolerance_mask = 0x07; + data->speed_tolerance_limit = 63; + + data->temp_label = nct6776_temp_label; + data->temp_mask = NCT6776_TEMP_MASK; + data->virt_temp_mask = NCT6776_VIRT_TEMP_MASK; + + data->REG_CONFIG = NCT6775_REG_CONFIG; + data->REG_VBAT = NCT6775_REG_VBAT; + data->REG_DIODE = NCT6775_REG_DIODE; + data->DIODE_MASK = NCT6775_DIODE_MASK; + data->REG_VIN = NCT6775_REG_IN; + data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; + data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; + data->REG_TARGET = NCT6775_REG_TARGET; + data->REG_FAN = NCT6775_REG_FAN; + data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; + data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; + data->REG_FAN_PULSES = NCT6776_REG_FAN_PULSES; + data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT; + data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6776_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6776_REG_FAN_STEP_DOWN_TIME; + data->REG_TOLERANCE_H = NCT6776_REG_TOLERANCE_H; + data->REG_PWM[0] = NCT6775_REG_PWM; + data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_PWM[5] = NCT6775_REG_WEIGHT_DUTY_STEP; + data->REG_PWM[6] = NCT6776_REG_WEIGHT_DUTY_BASE; + data->REG_PWM_READ = NCT6775_REG_PWM_READ; + data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE + = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; + data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET; + data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6775_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6775_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6775_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; + data->REG_ALARM = NCT6775_REG_ALARM; + data->REG_BEEP = NCT6776_REG_BEEP; + + reg_temp = NCT6775_REG_TEMP; + reg_temp_mon = NCT6775_REG_TEMP_MON; + num_reg_temp = ARRAY_SIZE(NCT6775_REG_TEMP); + num_reg_temp_mon = ARRAY_SIZE(NCT6775_REG_TEMP_MON); + reg_temp_over = NCT6775_REG_TEMP_OVER; + reg_temp_hyst = NCT6775_REG_TEMP_HYST; + reg_temp_config = NCT6776_REG_TEMP_CONFIG; + reg_temp_alternate = NCT6776_REG_TEMP_ALTERNATE; + reg_temp_crit = NCT6776_REG_TEMP_CRIT; + + break; + case nct6779: + data->in_num = 15; + data->pwm_num = 5; + data->auto_pwm_num = 4; + data->has_fan_div = false; + data->temp_fixed_num = 6; + data->num_temp_alarms = 2; + data->num_temp_beeps = 2; + + data->ALARM_BITS = NCT6779_ALARM_BITS; + data->BEEP_BITS = NCT6779_BEEP_BITS; + + data->fan_from_reg = fan_from_reg_rpm; + data->fan_from_reg_min = fan_from_reg13; + data->target_temp_mask = 0xff; + data->tolerance_mask = 0x07; + data->speed_tolerance_limit = 63; + + data->temp_label = nct6779_temp_label; + data->temp_mask = NCT6779_TEMP_MASK; + data->virt_temp_mask = NCT6779_VIRT_TEMP_MASK; + + data->REG_CONFIG = NCT6775_REG_CONFIG; + data->REG_VBAT = NCT6775_REG_VBAT; + data->REG_DIODE = NCT6775_REG_DIODE; + data->DIODE_MASK = NCT6775_DIODE_MASK; + data->REG_VIN = NCT6779_REG_IN; + data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; + data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; + data->REG_TARGET = NCT6775_REG_TARGET; + data->REG_FAN = NCT6779_REG_FAN; + data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; + data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; + data->REG_FAN_PULSES = NCT6779_REG_FAN_PULSES; + data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT; + data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6776_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6776_REG_FAN_STEP_DOWN_TIME; + data->REG_TOLERANCE_H = NCT6776_REG_TOLERANCE_H; + data->REG_PWM[0] = NCT6775_REG_PWM; + data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_PWM[5] = NCT6775_REG_WEIGHT_DUTY_STEP; + data->REG_PWM[6] = NCT6776_REG_WEIGHT_DUTY_BASE; + data->REG_PWM_READ = NCT6775_REG_PWM_READ; + data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE + = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; + data->REG_CRITICAL_PWM_ENABLE = NCT6779_REG_CRITICAL_PWM_ENABLE; + data->CRITICAL_PWM_ENABLE_MASK + = NCT6779_CRITICAL_PWM_ENABLE_MASK; + data->REG_CRITICAL_PWM = NCT6779_REG_CRITICAL_PWM; + data->REG_TEMP_OFFSET = NCT6779_REG_TEMP_OFFSET; + data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6775_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6775_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6775_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; + data->REG_ALARM = NCT6779_REG_ALARM; + data->REG_BEEP = NCT6776_REG_BEEP; + + reg_temp = NCT6779_REG_TEMP; + reg_temp_mon = NCT6779_REG_TEMP_MON; + num_reg_temp = ARRAY_SIZE(NCT6779_REG_TEMP); + num_reg_temp_mon = ARRAY_SIZE(NCT6779_REG_TEMP_MON); + reg_temp_over = NCT6779_REG_TEMP_OVER; + reg_temp_hyst = NCT6779_REG_TEMP_HYST; + reg_temp_config = NCT6779_REG_TEMP_CONFIG; + reg_temp_alternate = NCT6779_REG_TEMP_ALTERNATE; + reg_temp_crit = NCT6779_REG_TEMP_CRIT; + + break; + case nct6791: + case nct6792: + case nct6793: + case nct6795: + case nct6796: + data->in_num = 15; + data->pwm_num = (data->kind == nct6796) ? 7 : 6; + data->auto_pwm_num = 4; + data->has_fan_div = false; + data->temp_fixed_num = 6; + data->num_temp_alarms = 2; + data->num_temp_beeps = 2; + + data->ALARM_BITS = NCT6791_ALARM_BITS; + data->BEEP_BITS = NCT6779_BEEP_BITS; + + data->fan_from_reg = fan_from_reg_rpm; + data->fan_from_reg_min = fan_from_reg13; + data->target_temp_mask = 0xff; + data->tolerance_mask = 0x07; + data->speed_tolerance_limit = 63; + + switch (data->kind) { + default: + case nct6791: + data->temp_label = nct6779_temp_label; + data->temp_mask = NCT6791_TEMP_MASK; + data->virt_temp_mask = NCT6791_VIRT_TEMP_MASK; + break; + case nct6792: + data->temp_label = nct6792_temp_label; + data->temp_mask = NCT6792_TEMP_MASK; + data->virt_temp_mask = NCT6792_VIRT_TEMP_MASK; + break; + case nct6793: + data->temp_label = nct6793_temp_label; + data->temp_mask = NCT6793_TEMP_MASK; + data->virt_temp_mask = NCT6793_VIRT_TEMP_MASK; + break; + case nct6795: + data->temp_label = nct6795_temp_label; + data->temp_mask = NCT6795_TEMP_MASK; + data->virt_temp_mask = NCT6795_VIRT_TEMP_MASK; + break; + case nct6796: + data->temp_label = nct6796_temp_label; + data->temp_mask = NCT6796_TEMP_MASK; + data->virt_temp_mask = NCT6796_VIRT_TEMP_MASK; + break; + } + + data->REG_CONFIG = NCT6775_REG_CONFIG; + data->REG_VBAT = NCT6775_REG_VBAT; + data->REG_DIODE = NCT6775_REG_DIODE; + data->DIODE_MASK = NCT6775_DIODE_MASK; + data->REG_VIN = NCT6779_REG_IN; + data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; + data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; + data->REG_TARGET = NCT6775_REG_TARGET; + data->REG_FAN = NCT6779_REG_FAN; + data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; + data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; + data->REG_FAN_PULSES = NCT6779_REG_FAN_PULSES; + data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT; + data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6776_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6776_REG_FAN_STEP_DOWN_TIME; + data->REG_TOLERANCE_H = NCT6776_REG_TOLERANCE_H; + data->REG_PWM[0] = NCT6775_REG_PWM; + data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_PWM[5] = NCT6791_REG_WEIGHT_DUTY_STEP; + data->REG_PWM[6] = NCT6791_REG_WEIGHT_DUTY_BASE; + data->REG_PWM_READ = NCT6775_REG_PWM_READ; + data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE + = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; + data->REG_CRITICAL_PWM_ENABLE = NCT6779_REG_CRITICAL_PWM_ENABLE; + data->CRITICAL_PWM_ENABLE_MASK + = NCT6779_CRITICAL_PWM_ENABLE_MASK; + data->REG_CRITICAL_PWM = NCT6779_REG_CRITICAL_PWM; + data->REG_TEMP_OFFSET = NCT6779_REG_TEMP_OFFSET; + data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6791_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6791_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6791_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6791_REG_WEIGHT_TEMP_BASE; + data->REG_ALARM = NCT6791_REG_ALARM; + if (data->kind == nct6791) + data->REG_BEEP = NCT6776_REG_BEEP; + else + data->REG_BEEP = NCT6792_REG_BEEP; + + reg_temp = NCT6779_REG_TEMP; + num_reg_temp = ARRAY_SIZE(NCT6779_REG_TEMP); + if (data->kind == nct6791) { + reg_temp_mon = NCT6779_REG_TEMP_MON; + num_reg_temp_mon = ARRAY_SIZE(NCT6779_REG_TEMP_MON); + } else { + reg_temp_mon = NCT6792_REG_TEMP_MON; + num_reg_temp_mon = ARRAY_SIZE(NCT6792_REG_TEMP_MON); + } + reg_temp_over = NCT6779_REG_TEMP_OVER; + reg_temp_hyst = NCT6779_REG_TEMP_HYST; + reg_temp_config = NCT6779_REG_TEMP_CONFIG; + reg_temp_alternate = NCT6779_REG_TEMP_ALTERNATE; + reg_temp_crit = NCT6779_REG_TEMP_CRIT; + + break; + default: + return -ENODEV; + } + data->have_in = BIT(data->in_num) - 1; + data->have_temp = 0; + + /* + * On some boards, not all available temperature sources are monitored, + * even though some of the monitoring registers are unused. + * Get list of unused monitoring registers, then detect if any fan + * controls are configured to use unmonitored temperature sources. + * If so, assign the unmonitored temperature sources to available + * monitoring registers. + */ + mask = 0; + available = 0; + for (i = 0; i < num_reg_temp; i++) { + if (reg_temp[i] == 0) + continue; + + src = nct6775_read_value(data, data->REG_TEMP_SOURCE[i]) & 0x1f; + if (!src || (mask & BIT(src))) + available |= BIT(i); + + mask |= BIT(src); + } + + /* + * Now find unmonitored temperature registers and enable monitoring + * if additional monitoring registers are available. + */ + add_temp_sensors(data, data->REG_TEMP_SEL, &available, &mask); + add_temp_sensors(data, data->REG_WEIGHT_TEMP_SEL, &available, &mask); + + mask = 0; + s = NUM_TEMP_FIXED; /* First dynamic temperature attribute */ + for (i = 0; i < num_reg_temp; i++) { + if (reg_temp[i] == 0) + continue; + + src = nct6775_read_value(data, data->REG_TEMP_SOURCE[i]) & 0x1f; + if (!src || (mask & BIT(src))) + continue; + + if (!(data->temp_mask & BIT(src))) { + dev_info(dev, + "Invalid temperature source %d at index %d, source register 0x%x, temp register 0x%x\n", + src, i, data->REG_TEMP_SOURCE[i], reg_temp[i]); + continue; + } + + mask |= BIT(src); + + /* Use fixed index for SYSTIN(1), CPUTIN(2), AUXTIN(3) */ + if (src <= data->temp_fixed_num) { + data->have_temp |= BIT(src - 1); + data->have_temp_fixed |= BIT(src - 1); + data->reg_temp[0][src - 1] = reg_temp[i]; + data->reg_temp[1][src - 1] = reg_temp_over[i]; + data->reg_temp[2][src - 1] = reg_temp_hyst[i]; + if (reg_temp_crit_h && reg_temp_crit_h[i]) + data->reg_temp[3][src - 1] = reg_temp_crit_h[i]; + else if (reg_temp_crit[src - 1]) + data->reg_temp[3][src - 1] + = reg_temp_crit[src - 1]; + if (reg_temp_crit_l && reg_temp_crit_l[i]) + data->reg_temp[4][src - 1] = reg_temp_crit_l[i]; + data->reg_temp_config[src - 1] = reg_temp_config[i]; + data->temp_src[src - 1] = src; + continue; + } + + if (s >= NUM_TEMP) + continue; + + /* Use dynamic index for other sources */ + data->have_temp |= BIT(s); + data->reg_temp[0][s] = reg_temp[i]; + data->reg_temp[1][s] = reg_temp_over[i]; + data->reg_temp[2][s] = reg_temp_hyst[i]; + data->reg_temp_config[s] = reg_temp_config[i]; + if (reg_temp_crit_h && reg_temp_crit_h[i]) + data->reg_temp[3][s] = reg_temp_crit_h[i]; + else if (reg_temp_crit[src - 1]) + data->reg_temp[3][s] = reg_temp_crit[src - 1]; + if (reg_temp_crit_l && reg_temp_crit_l[i]) + data->reg_temp[4][s] = reg_temp_crit_l[i]; + + data->temp_src[s] = src; + s++; + } + + /* + * Repeat with temperatures used for fan control. + * This set of registers does not support limits. + */ + for (i = 0; i < num_reg_temp_mon; i++) { + if (reg_temp_mon[i] == 0) + continue; + + src = nct6775_read_value(data, data->REG_TEMP_SEL[i]) & 0x1f; + if (!src) + continue; + + if (!(data->temp_mask & BIT(src))) { + dev_info(dev, + "Invalid temperature source %d at index %d, source register 0x%x, temp register 0x%x\n", + src, i, data->REG_TEMP_SEL[i], + reg_temp_mon[i]); + continue; + } + + /* + * For virtual temperature sources, the 'virtual' temperature + * for each fan reflects a different temperature, and there + * are no duplicates. + */ + if (!(data->virt_temp_mask & BIT(src))) { + if (mask & BIT(src)) + continue; + mask |= BIT(src); + } + + /* Use fixed index for SYSTIN(1), CPUTIN(2), AUXTIN(3) */ + if (src <= data->temp_fixed_num) { + if (data->have_temp & BIT(src - 1)) + continue; + data->have_temp |= BIT(src - 1); + data->have_temp_fixed |= BIT(src - 1); + data->reg_temp[0][src - 1] = reg_temp_mon[i]; + data->temp_src[src - 1] = src; + continue; + } + + if (s >= NUM_TEMP) + continue; + + /* Use dynamic index for other sources */ + data->have_temp |= BIT(s); + data->reg_temp[0][s] = reg_temp_mon[i]; + data->temp_src[s] = src; + s++; + } + +#ifdef USE_ALTERNATE + /* + * Go through the list of alternate temp registers and enable + * if possible. + * The temperature is already monitored if the respective bit in + * is set. + */ + for (i = 0; i < 31; i++) { + if (!(data->temp_mask & BIT(i + 1))) + continue; + if (!reg_temp_alternate[i]) + continue; + if (mask & BIT(i + 1)) + continue; + if (i < data->temp_fixed_num) { + if (data->have_temp & BIT(i)) + continue; + data->have_temp |= BIT(i); + data->have_temp_fixed |= BIT(i); + data->reg_temp[0][i] = reg_temp_alternate[i]; + if (i < num_reg_temp) { + data->reg_temp[1][i] = reg_temp_over[i]; + data->reg_temp[2][i] = reg_temp_hyst[i]; + } + data->temp_src[i] = i + 1; + continue; + } + + if (s >= NUM_TEMP) /* Abort if no more space */ + break; + + data->have_temp |= BIT(s); + data->reg_temp[0][s] = reg_temp_alternate[i]; + data->temp_src[s] = i + 1; + s++; + } +#endif /* USE_ALTERNATE */ + + /* Initialize the chip */ + nct6775_init_device(data); + + err = superio_enter(sio_data->sioreg); + if (err) + return err; + + cr2a = superio_inb(sio_data->sioreg, 0x2a); + switch (data->kind) { + case nct6775: + data->have_vid = (cr2a & 0x40); + break; + case nct6776: + data->have_vid = (cr2a & 0x60) == 0x40; + break; + case nct6106: + case nct6779: + case nct6791: + case nct6792: + case nct6793: + case nct6795: + case nct6796: + break; + } + + /* + * Read VID value + * We can get the VID input values directly at logical device D 0xe3. + */ + if (data->have_vid) { + superio_select(sio_data->sioreg, NCT6775_LD_VID); + data->vid = superio_inb(sio_data->sioreg, 0xe3); + data->vrm = vid_which_vrm(); + } + + if (fan_debounce) { + u8 tmp; + + superio_select(sio_data->sioreg, NCT6775_LD_HWM); + tmp = superio_inb(sio_data->sioreg, + NCT6775_REG_CR_FAN_DEBOUNCE); + switch (data->kind) { + case nct6106: + tmp |= 0xe0; + break; + case nct6775: + tmp |= 0x1e; + break; + case nct6776: + case nct6779: + tmp |= 0x3e; + break; + case nct6791: + case nct6792: + case nct6793: + case nct6795: + case nct6796: + tmp |= 0x7e; + break; + } + superio_outb(sio_data->sioreg, NCT6775_REG_CR_FAN_DEBOUNCE, + tmp); + dev_info(&pdev->dev, "Enabled fan debounce for chip %s\n", + data->name); + } + + nct6775_check_fan_inputs(data); + + superio_exit(sio_data->sioreg); + + /* Read fan clock dividers immediately */ + nct6775_init_fan_common(dev, data); + + /* Register sysfs hooks */ + group = nct6775_create_attr_group(dev, &nct6775_pwm_template_group, + data->pwm_num); + if (IS_ERR(group)) + return PTR_ERR(group); + + data->groups[num_attr_groups++] = group; + + group = nct6775_create_attr_group(dev, &nct6775_in_template_group, + fls(data->have_in)); + if (IS_ERR(group)) + return PTR_ERR(group); + + data->groups[num_attr_groups++] = group; + + group = nct6775_create_attr_group(dev, &nct6775_fan_template_group, + fls(data->has_fan)); + if (IS_ERR(group)) + return PTR_ERR(group); + + data->groups[num_attr_groups++] = group; + + group = nct6775_create_attr_group(dev, &nct6775_temp_template_group, + fls(data->have_temp)); + if (IS_ERR(group)) + return PTR_ERR(group); + + data->groups[num_attr_groups++] = group; + data->groups[num_attr_groups++] = &nct6775_group_other; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, data->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static void nct6791_enable_io_mapping(int sioaddr) +{ + int val; + + val = superio_inb(sioaddr, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE); + if (val & 0x10) { + pr_info("Enabling hardware monitor logical device mappings.\n"); + superio_outb(sioaddr, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE, + val & ~0x10); + } +} + +static int __maybe_unused nct6775_suspend(struct device *dev) +{ + struct nct6775_data *data = nct6775_update_device(dev); + + mutex_lock(&data->update_lock); + data->vbat = nct6775_read_value(data, data->REG_VBAT); + if (data->kind == nct6775) { + data->fandiv1 = nct6775_read_value(data, NCT6775_REG_FANDIV1); + data->fandiv2 = nct6775_read_value(data, NCT6775_REG_FANDIV2); + } + mutex_unlock(&data->update_lock); + + return 0; +} + +static int __maybe_unused nct6775_resume(struct device *dev) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + int sioreg = data->sioreg; + int i, j, err = 0; + u8 reg; + + mutex_lock(&data->update_lock); + data->bank = 0xff; /* Force initial bank selection */ + + err = superio_enter(sioreg); + if (err) + goto abort; + + superio_select(sioreg, NCT6775_LD_HWM); + reg = superio_inb(sioreg, SIO_REG_ENABLE); + if (reg != data->sio_reg_enable) + superio_outb(sioreg, SIO_REG_ENABLE, data->sio_reg_enable); + + if (data->kind == nct6791 || data->kind == nct6792 || + data->kind == nct6793 || data->kind == nct6795 || + data->kind == nct6796) + nct6791_enable_io_mapping(sioreg); + + superio_exit(sioreg); + + /* Restore limits */ + for (i = 0; i < data->in_num; i++) { + if (!(data->have_in & BIT(i))) + continue; + + nct6775_write_value(data, data->REG_IN_MINMAX[0][i], + data->in[i][1]); + nct6775_write_value(data, data->REG_IN_MINMAX[1][i], + data->in[i][2]); + } + + for (i = 0; i < ARRAY_SIZE(data->fan_min); i++) { + if (!(data->has_fan_min & BIT(i))) + continue; + + nct6775_write_value(data, data->REG_FAN_MIN[i], + data->fan_min[i]); + } + + for (i = 0; i < NUM_TEMP; i++) { + if (!(data->have_temp & BIT(i))) + continue; + + for (j = 1; j < ARRAY_SIZE(data->reg_temp); j++) + if (data->reg_temp[j][i]) + nct6775_write_temp(data, data->reg_temp[j][i], + data->temp[j][i]); + } + + /* Restore other settings */ + nct6775_write_value(data, data->REG_VBAT, data->vbat); + if (data->kind == nct6775) { + nct6775_write_value(data, NCT6775_REG_FANDIV1, data->fandiv1); + nct6775_write_value(data, NCT6775_REG_FANDIV2, data->fandiv2); + } + +abort: + /* Force re-reading all values */ + data->valid = false; + mutex_unlock(&data->update_lock); + + return err; +} + +static SIMPLE_DEV_PM_OPS(nct6775_dev_pm_ops, nct6775_suspend, nct6775_resume); + +static struct platform_driver nct6775_driver = { + .driver = { + .name = DRVNAME, + .pm = &nct6775_dev_pm_ops, + }, + .probe = nct6775_probe, +}; + +/* nct6775_find() looks for a '627 in the Super-I/O config space */ +static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data) +{ + u16 val; + int err; + int addr; + + err = superio_enter(sioaddr); + if (err) + return err; + + val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) | + superio_inb(sioaddr, SIO_REG_DEVID + 1); + if (force_id && val != 0xffff) + val = force_id; + + switch (val & SIO_ID_MASK) { + case SIO_NCT6106_ID: + sio_data->kind = nct6106; + break; + case SIO_NCT6775_ID: + sio_data->kind = nct6775; + break; + case SIO_NCT6776_ID: + sio_data->kind = nct6776; + break; + case SIO_NCT6779_ID: + sio_data->kind = nct6779; + break; + case SIO_NCT6791_ID: + sio_data->kind = nct6791; + break; + case SIO_NCT6792_ID: + sio_data->kind = nct6792; + break; + case SIO_NCT6793_ID: + sio_data->kind = nct6793; + break; + case SIO_NCT6795_ID: + sio_data->kind = nct6795; + break; + case SIO_NCT6796_ID: + sio_data->kind = nct6796; + break; + default: + if (val != 0xffff) + pr_debug("unsupported chip ID: 0x%04x\n", val); + superio_exit(sioaddr); + return -ENODEV; + } + + /* We have a known chip, find the HWM I/O address */ + superio_select(sioaddr, NCT6775_LD_HWM); + val = (superio_inb(sioaddr, SIO_REG_ADDR) << 8) + | superio_inb(sioaddr, SIO_REG_ADDR + 1); + addr = val & IOREGION_ALIGNMENT; + if (addr == 0) { + pr_err("Refusing to enable a Super-I/O device with a base I/O port 0\n"); + superio_exit(sioaddr); + return -ENODEV; + } + + /* Activate logical device if needed */ + val = superio_inb(sioaddr, SIO_REG_ENABLE); + if (!(val & 0x01)) { + pr_warn("Forcibly enabling Super-I/O. Sensor is probably unusable.\n"); + superio_outb(sioaddr, SIO_REG_ENABLE, val | 0x01); + } + + if (sio_data->kind == nct6791 || sio_data->kind == nct6792 || + sio_data->kind == nct6793 || sio_data->kind == nct6795 || + sio_data->kind == nct6796) + nct6791_enable_io_mapping(sioaddr); + + superio_exit(sioaddr); + pr_info("Found %s or compatible chip at %#x:%#x\n", + nct6775_sio_names[sio_data->kind], sioaddr, addr); + sio_data->sioreg = sioaddr; + + return addr; +} + +/* + * when Super-I/O functions move to a separate file, the Super-I/O + * bus will manage the lifetime of the device and this module will only keep + * track of the nct6775 driver. But since we use platform_device_alloc(), we + * must keep track of the device + */ +static struct platform_device *pdev[2]; + +static int __init sensors_nct6775_init(void) +{ + int i, err; + bool found = false; + int address; + struct resource res; + struct nct6775_sio_data sio_data; + int sioaddr[2] = { 0x2e, 0x4e }; + + err = platform_driver_register(&nct6775_driver); + if (err) + return err; + + /* + * initialize sio_data->kind and sio_data->sioreg. + * + * when Super-I/O functions move to a separate file, the Super-I/O + * driver will probe 0x2e and 0x4e and auto-detect the presence of a + * nct6775 hardware monitor, and call probe() + */ + for (i = 0; i < ARRAY_SIZE(pdev); i++) { + address = nct6775_find(sioaddr[i], &sio_data); + if (address <= 0) + continue; + + found = true; + + pdev[i] = platform_device_alloc(DRVNAME, address); + if (!pdev[i]) { + err = -ENOMEM; + goto exit_device_unregister; + } + + err = platform_device_add_data(pdev[i], &sio_data, + sizeof(struct nct6775_sio_data)); + if (err) + goto exit_device_put; + + memset(&res, 0, sizeof(res)); + res.name = DRVNAME; + res.start = address + IOREGION_OFFSET; + res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1; + res.flags = IORESOURCE_IO; + + err = acpi_check_resource_conflict(&res); + if (err) { + platform_device_put(pdev[i]); + pdev[i] = NULL; + continue; + } + + err = platform_device_add_resources(pdev[i], &res, 1); + if (err) + goto exit_device_put; + + /* platform_device_add calls probe() */ + err = platform_device_add(pdev[i]); + if (err) + goto exit_device_put; + } + if (!found) { + err = -ENODEV; + goto exit_unregister; + } + + return 0; + +exit_device_put: + platform_device_put(pdev[i]); +exit_device_unregister: + while (--i >= 0) { + if (pdev[i]) + platform_device_unregister(pdev[i]); + } +exit_unregister: + platform_driver_unregister(&nct6775_driver); + return err; +} + +static void __exit sensors_nct6775_exit(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pdev); i++) { + if (pdev[i]) + platform_device_unregister(pdev[i]); + } + platform_driver_unregister(&nct6775_driver); +} + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("Driver for NCT6775F and compatible chips"); +MODULE_LICENSE("GPL"); + +module_init(sensors_nct6775_init); +module_exit(sensors_nct6775_exit); diff --git a/drivers/hwmon/nct7802.c b/drivers/hwmon/nct7802.c new file mode 100644 index 000000000..779ec8fdf --- /dev/null +++ b/drivers/hwmon/nct7802.c @@ -0,0 +1,1153 @@ +/* + * nct7802 - Driver for Nuvoton NCT7802Y + * + * Copyright (C) 2014 Guenter Roeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "nct7802" + +static const u8 REG_VOLTAGE[5] = { 0x09, 0x0a, 0x0c, 0x0d, 0x0e }; + +static const u8 REG_VOLTAGE_LIMIT_LSB[2][5] = { + { 0x46, 0x00, 0x40, 0x42, 0x44 }, + { 0x45, 0x00, 0x3f, 0x41, 0x43 }, +}; + +static const u8 REG_VOLTAGE_LIMIT_MSB[5] = { 0x48, 0x00, 0x47, 0x47, 0x48 }; + +static const u8 REG_VOLTAGE_LIMIT_MSB_SHIFT[2][5] = { + { 0, 0, 4, 0, 4 }, + { 2, 0, 6, 2, 6 }, +}; + +#define REG_BANK 0x00 +#define REG_TEMP_LSB 0x05 +#define REG_TEMP_PECI_LSB 0x08 +#define REG_VOLTAGE_LOW 0x0f +#define REG_FANCOUNT_LOW 0x13 +#define REG_START 0x21 +#define REG_MODE 0x22 /* 7.2.32 Mode Selection Register */ +#define REG_PECI_ENABLE 0x23 +#define REG_FAN_ENABLE 0x24 +#define REG_VMON_ENABLE 0x25 +#define REG_PWM(x) (0x60 + (x)) +#define REG_SMARTFAN_EN(x) (0x64 + (x) / 2) +#define SMARTFAN_EN_SHIFT(x) ((x) % 2 * 4) +#define REG_VENDOR_ID 0xfd +#define REG_CHIP_ID 0xfe +#define REG_VERSION_ID 0xff + +/* + * Data structures and manipulation thereof + */ + +struct nct7802_data { + struct regmap *regmap; + struct mutex access_lock; /* for multi-byte read and write operations */ +}; + +static ssize_t show_temp_type(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nct7802_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + unsigned int mode; + int ret; + + ret = regmap_read(data->regmap, REG_MODE, &mode); + if (ret < 0) + return ret; + + return sprintf(buf, "%u\n", (mode >> (2 * sattr->index) & 3) + 2); +} + +static ssize_t store_temp_type(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct7802_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + unsigned int type; + int err; + + err = kstrtouint(buf, 0, &type); + if (err < 0) + return err; + if (sattr->index == 2 && type != 4) /* RD3 */ + return -EINVAL; + if (type < 3 || type > 4) + return -EINVAL; + err = regmap_update_bits(data->regmap, REG_MODE, + 3 << 2 * sattr->index, (type - 2) << 2 * sattr->index); + return err ? : count; +} + +static ssize_t show_pwm_mode(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct7802_data *data = dev_get_drvdata(dev); + unsigned int regval; + int ret; + + if (sattr->index > 1) + return sprintf(buf, "1\n"); + + ret = regmap_read(data->regmap, 0x5E, ®val); + if (ret < 0) + return ret; + + return sprintf(buf, "%u\n", !(regval & (1 << sattr->index))); +} + +static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct nct7802_data *data = dev_get_drvdata(dev); + unsigned int val; + int ret; + + if (!attr->index) + return sprintf(buf, "255\n"); + + ret = regmap_read(data->regmap, attr->index, &val); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t store_pwm(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct nct7802_data *data = dev_get_drvdata(dev); + int err; + u8 val; + + err = kstrtou8(buf, 0, &val); + if (err < 0) + return err; + + err = regmap_write(data->regmap, attr->index, val); + return err ? : count; +} + +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nct7802_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + unsigned int reg, enabled; + int ret; + + ret = regmap_read(data->regmap, REG_SMARTFAN_EN(sattr->index), ®); + if (ret < 0) + return ret; + enabled = reg >> SMARTFAN_EN_SHIFT(sattr->index) & 1; + return sprintf(buf, "%u\n", enabled + 1); +} + +static ssize_t store_pwm_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct7802_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + u8 val; + int ret; + + ret = kstrtou8(buf, 0, &val); + if (ret < 0) + return ret; + if (val < 1 || val > 2) + return -EINVAL; + ret = regmap_update_bits(data->regmap, REG_SMARTFAN_EN(sattr->index), + 1 << SMARTFAN_EN_SHIFT(sattr->index), + (val - 1) << SMARTFAN_EN_SHIFT(sattr->index)); + return ret ? : count; +} + +static int nct7802_read_temp(struct nct7802_data *data, + u8 reg_temp, u8 reg_temp_low, int *temp) +{ + unsigned int t1, t2 = 0; + int err; + + *temp = 0; + + mutex_lock(&data->access_lock); + err = regmap_read(data->regmap, reg_temp, &t1); + if (err < 0) + goto abort; + t1 <<= 8; + if (reg_temp_low) { /* 11 bit data */ + err = regmap_read(data->regmap, reg_temp_low, &t2); + if (err < 0) + goto abort; + } + t1 |= t2 & 0xe0; + *temp = (s16)t1 / 32 * 125; +abort: + mutex_unlock(&data->access_lock); + return err; +} + +static int nct7802_read_fan(struct nct7802_data *data, u8 reg_fan) +{ + unsigned int f1, f2; + int ret; + + mutex_lock(&data->access_lock); + ret = regmap_read(data->regmap, reg_fan, &f1); + if (ret < 0) + goto abort; + ret = regmap_read(data->regmap, REG_FANCOUNT_LOW, &f2); + if (ret < 0) + goto abort; + ret = (f1 << 5) | (f2 >> 3); + /* convert fan count to rpm */ + if (ret == 0x1fff) /* maximum value, assume fan is stopped */ + ret = 0; + else if (ret) + ret = DIV_ROUND_CLOSEST(1350000U, ret); +abort: + mutex_unlock(&data->access_lock); + return ret; +} + +static int nct7802_read_fan_min(struct nct7802_data *data, u8 reg_fan_low, + u8 reg_fan_high) +{ + unsigned int f1, f2; + int ret; + + mutex_lock(&data->access_lock); + ret = regmap_read(data->regmap, reg_fan_low, &f1); + if (ret < 0) + goto abort; + ret = regmap_read(data->regmap, reg_fan_high, &f2); + if (ret < 0) + goto abort; + ret = f1 | ((f2 & 0xf8) << 5); + /* convert fan count to rpm */ + if (ret == 0x1fff) /* maximum value, assume no limit */ + ret = 0; + else if (ret) + ret = DIV_ROUND_CLOSEST(1350000U, ret); + else + ret = 1350000U; +abort: + mutex_unlock(&data->access_lock); + return ret; +} + +static int nct7802_write_fan_min(struct nct7802_data *data, u8 reg_fan_low, + u8 reg_fan_high, unsigned long limit) +{ + int err; + + if (limit) + limit = DIV_ROUND_CLOSEST(1350000U, limit); + else + limit = 0x1fff; + limit = clamp_val(limit, 0, 0x1fff); + + mutex_lock(&data->access_lock); + err = regmap_write(data->regmap, reg_fan_low, limit & 0xff); + if (err < 0) + goto abort; + + err = regmap_write(data->regmap, reg_fan_high, (limit & 0x1f00) >> 5); +abort: + mutex_unlock(&data->access_lock); + return err; +} + +static u8 nct7802_vmul[] = { 4, 2, 2, 2, 2 }; + +static int nct7802_read_voltage(struct nct7802_data *data, int nr, int index) +{ + unsigned int v1, v2; + int ret; + + mutex_lock(&data->access_lock); + if (index == 0) { /* voltage */ + ret = regmap_read(data->regmap, REG_VOLTAGE[nr], &v1); + if (ret < 0) + goto abort; + ret = regmap_read(data->regmap, REG_VOLTAGE_LOW, &v2); + if (ret < 0) + goto abort; + ret = ((v1 << 2) | (v2 >> 6)) * nct7802_vmul[nr]; + } else { /* limit */ + int shift = 8 - REG_VOLTAGE_LIMIT_MSB_SHIFT[index - 1][nr]; + + ret = regmap_read(data->regmap, + REG_VOLTAGE_LIMIT_LSB[index - 1][nr], &v1); + if (ret < 0) + goto abort; + ret = regmap_read(data->regmap, REG_VOLTAGE_LIMIT_MSB[nr], + &v2); + if (ret < 0) + goto abort; + ret = (v1 | ((v2 << shift) & 0x300)) * nct7802_vmul[nr]; + } +abort: + mutex_unlock(&data->access_lock); + return ret; +} + +static int nct7802_write_voltage(struct nct7802_data *data, int nr, int index, + unsigned long voltage) +{ + int shift = 8 - REG_VOLTAGE_LIMIT_MSB_SHIFT[index - 1][nr]; + int err; + + voltage = clamp_val(voltage, 0, 0x3ff * nct7802_vmul[nr]); + voltage = DIV_ROUND_CLOSEST(voltage, nct7802_vmul[nr]); + + mutex_lock(&data->access_lock); + err = regmap_write(data->regmap, + REG_VOLTAGE_LIMIT_LSB[index - 1][nr], + voltage & 0xff); + if (err < 0) + goto abort; + + err = regmap_update_bits(data->regmap, REG_VOLTAGE_LIMIT_MSB[nr], + 0x0300 >> shift, (voltage & 0x0300) >> shift); +abort: + mutex_unlock(&data->access_lock); + return err; +} + +static ssize_t show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct7802_data *data = dev_get_drvdata(dev); + int voltage; + + voltage = nct7802_read_voltage(data, sattr->nr, sattr->index); + if (voltage < 0) + return voltage; + + return sprintf(buf, "%d\n", voltage); +} + +static ssize_t store_in(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct7802_data *data = dev_get_drvdata(dev); + int index = sattr->index; + int nr = sattr->nr; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + err = nct7802_write_voltage(data, nr, index, val); + return err ? : count; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nct7802_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int err, temp; + + err = nct7802_read_temp(data, sattr->nr, sattr->index, &temp); + if (err < 0) + return err; + + return sprintf(buf, "%d\n", temp); +} + +static ssize_t store_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct7802_data *data = dev_get_drvdata(dev); + int nr = sattr->nr; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + val = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000); + + err = regmap_write(data->regmap, nr, val & 0xff); + return err ? : count; +} + +static ssize_t show_fan(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct7802_data *data = dev_get_drvdata(dev); + int speed; + + speed = nct7802_read_fan(data, sattr->index); + if (speed < 0) + return speed; + + return sprintf(buf, "%d\n", speed); +} + +static ssize_t show_fan_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct7802_data *data = dev_get_drvdata(dev); + int speed; + + speed = nct7802_read_fan_min(data, sattr->nr, sattr->index); + if (speed < 0) + return speed; + + return sprintf(buf, "%d\n", speed); +} + +static ssize_t store_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct7802_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + err = nct7802_write_fan_min(data, sattr->nr, sattr->index, val); + return err ? : count; +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nct7802_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int bit = sattr->index; + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, sattr->nr, &val); + if (ret < 0) + return ret; + + return sprintf(buf, "%u\n", !!(val & (1 << bit))); +} + +static ssize_t +show_beep(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct7802_data *data = dev_get_drvdata(dev); + unsigned int regval; + int err; + + err = regmap_read(data->regmap, sattr->nr, ®val); + if (err) + return err; + + return sprintf(buf, "%u\n", !!(regval & (1 << sattr->index))); +} + +static ssize_t +store_beep(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct7802_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + if (val > 1) + return -EINVAL; + + err = regmap_update_bits(data->regmap, sattr->nr, 1 << sattr->index, + val ? 1 << sattr->index : 0); + return err ? : count; +} + +static SENSOR_DEVICE_ATTR(temp1_type, S_IRUGO | S_IWUSR, + show_temp_type, store_temp_type, 0); +static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0x01, + REG_TEMP_LSB); +static SENSOR_DEVICE_ATTR_2(temp1_min, S_IRUGO | S_IWUSR, show_temp, + store_temp, 0x31, 0); +static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, show_temp, + store_temp, 0x30, 0); +static SENSOR_DEVICE_ATTR_2(temp1_crit, S_IRUGO | S_IWUSR, show_temp, + store_temp, 0x3a, 0); + +static SENSOR_DEVICE_ATTR(temp2_type, S_IRUGO | S_IWUSR, + show_temp_type, store_temp_type, 1); +static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0x02, + REG_TEMP_LSB); +static SENSOR_DEVICE_ATTR_2(temp2_min, S_IRUGO | S_IWUSR, show_temp, + store_temp, 0x33, 0); +static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp, + store_temp, 0x32, 0); +static SENSOR_DEVICE_ATTR_2(temp2_crit, S_IRUGO | S_IWUSR, show_temp, + store_temp, 0x3b, 0); + +static SENSOR_DEVICE_ATTR(temp3_type, S_IRUGO | S_IWUSR, + show_temp_type, store_temp_type, 2); +static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0x03, + REG_TEMP_LSB); +static SENSOR_DEVICE_ATTR_2(temp3_min, S_IRUGO | S_IWUSR, show_temp, + store_temp, 0x35, 0); +static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp, + store_temp, 0x34, 0); +static SENSOR_DEVICE_ATTR_2(temp3_crit, S_IRUGO | S_IWUSR, show_temp, + store_temp, 0x3c, 0); + +static SENSOR_DEVICE_ATTR_2(temp4_input, S_IRUGO, show_temp, NULL, 0x04, 0); +static SENSOR_DEVICE_ATTR_2(temp4_min, S_IRUGO | S_IWUSR, show_temp, + store_temp, 0x37, 0); +static SENSOR_DEVICE_ATTR_2(temp4_max, S_IRUGO | S_IWUSR, show_temp, + store_temp, 0x36, 0); +static SENSOR_DEVICE_ATTR_2(temp4_crit, S_IRUGO | S_IWUSR, show_temp, + store_temp, 0x3d, 0); + +static SENSOR_DEVICE_ATTR_2(temp5_input, S_IRUGO, show_temp, NULL, 0x06, + REG_TEMP_PECI_LSB); +static SENSOR_DEVICE_ATTR_2(temp5_min, S_IRUGO | S_IWUSR, show_temp, + store_temp, 0x39, 0); +static SENSOR_DEVICE_ATTR_2(temp5_max, S_IRUGO | S_IWUSR, show_temp, + store_temp, 0x38, 0); +static SENSOR_DEVICE_ATTR_2(temp5_crit, S_IRUGO | S_IWUSR, show_temp, + store_temp, 0x3e, 0); + +static SENSOR_DEVICE_ATTR_2(temp6_input, S_IRUGO, show_temp, NULL, 0x07, + REG_TEMP_PECI_LSB); + +static SENSOR_DEVICE_ATTR_2(temp1_min_alarm, S_IRUGO, show_alarm, NULL, + 0x18, 0); +static SENSOR_DEVICE_ATTR_2(temp2_min_alarm, S_IRUGO, show_alarm, NULL, + 0x18, 1); +static SENSOR_DEVICE_ATTR_2(temp3_min_alarm, S_IRUGO, show_alarm, NULL, + 0x18, 2); +static SENSOR_DEVICE_ATTR_2(temp4_min_alarm, S_IRUGO, show_alarm, NULL, + 0x18, 3); +static SENSOR_DEVICE_ATTR_2(temp5_min_alarm, S_IRUGO, show_alarm, NULL, + 0x18, 4); + +static SENSOR_DEVICE_ATTR_2(temp1_max_alarm, S_IRUGO, show_alarm, NULL, + 0x19, 0); +static SENSOR_DEVICE_ATTR_2(temp2_max_alarm, S_IRUGO, show_alarm, NULL, + 0x19, 1); +static SENSOR_DEVICE_ATTR_2(temp3_max_alarm, S_IRUGO, show_alarm, NULL, + 0x19, 2); +static SENSOR_DEVICE_ATTR_2(temp4_max_alarm, S_IRUGO, show_alarm, NULL, + 0x19, 3); +static SENSOR_DEVICE_ATTR_2(temp5_max_alarm, S_IRUGO, show_alarm, NULL, + 0x19, 4); + +static SENSOR_DEVICE_ATTR_2(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, + 0x1b, 0); +static SENSOR_DEVICE_ATTR_2(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, + 0x1b, 1); +static SENSOR_DEVICE_ATTR_2(temp3_crit_alarm, S_IRUGO, show_alarm, NULL, + 0x1b, 2); +static SENSOR_DEVICE_ATTR_2(temp4_crit_alarm, S_IRUGO, show_alarm, NULL, + 0x1b, 3); +static SENSOR_DEVICE_ATTR_2(temp5_crit_alarm, S_IRUGO, show_alarm, NULL, + 0x1b, 4); + +static SENSOR_DEVICE_ATTR_2(temp1_fault, S_IRUGO, show_alarm, NULL, 0x17, 0); +static SENSOR_DEVICE_ATTR_2(temp2_fault, S_IRUGO, show_alarm, NULL, 0x17, 1); +static SENSOR_DEVICE_ATTR_2(temp3_fault, S_IRUGO, show_alarm, NULL, 0x17, 2); + +static SENSOR_DEVICE_ATTR_2(temp1_beep, S_IRUGO | S_IWUSR, show_beep, + store_beep, 0x5c, 0); +static SENSOR_DEVICE_ATTR_2(temp2_beep, S_IRUGO | S_IWUSR, show_beep, + store_beep, 0x5c, 1); +static SENSOR_DEVICE_ATTR_2(temp3_beep, S_IRUGO | S_IWUSR, show_beep, + store_beep, 0x5c, 2); +static SENSOR_DEVICE_ATTR_2(temp4_beep, S_IRUGO | S_IWUSR, show_beep, + store_beep, 0x5c, 3); +static SENSOR_DEVICE_ATTR_2(temp5_beep, S_IRUGO | S_IWUSR, show_beep, + store_beep, 0x5c, 4); +static SENSOR_DEVICE_ATTR_2(temp6_beep, S_IRUGO | S_IWUSR, show_beep, + store_beep, 0x5c, 5); + +static struct attribute *nct7802_temp_attrs[] = { + &sensor_dev_attr_temp1_type.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_temp1_beep.dev_attr.attr, + + &sensor_dev_attr_temp2_type.dev_attr.attr, /* 10 */ + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp2_beep.dev_attr.attr, + + &sensor_dev_attr_temp3_type.dev_attr.attr, /* 20 */ + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp3_beep.dev_attr.attr, + + &sensor_dev_attr_temp4_input.dev_attr.attr, /* 30 */ + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_crit.dev_attr.attr, + &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_beep.dev_attr.attr, + + &sensor_dev_attr_temp5_input.dev_attr.attr, /* 38 */ + &sensor_dev_attr_temp5_min.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp5_crit.dev_attr.attr, + &sensor_dev_attr_temp5_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_beep.dev_attr.attr, + + &sensor_dev_attr_temp6_input.dev_attr.attr, /* 46 */ + &sensor_dev_attr_temp6_beep.dev_attr.attr, + + NULL +}; + +static umode_t nct7802_temp_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct7802_data *data = dev_get_drvdata(dev); + unsigned int reg; + int err; + + err = regmap_read(data->regmap, REG_MODE, ®); + if (err < 0) + return 0; + + if (index < 10 && + (reg & 03) != 0x01 && (reg & 0x03) != 0x02) /* RD1 */ + return 0; + + if (index >= 10 && index < 20 && + (reg & 0x0c) != 0x04 && (reg & 0x0c) != 0x08) /* RD2 */ + return 0; + if (index >= 20 && index < 30 && (reg & 0x30) != 0x20) /* RD3 */ + return 0; + + if (index >= 30 && index < 38) /* local */ + return attr->mode; + + err = regmap_read(data->regmap, REG_PECI_ENABLE, ®); + if (err < 0) + return 0; + + if (index >= 38 && index < 46 && !(reg & 0x01)) /* PECI 0 */ + return 0; + + if (index >= 0x46 && (!(reg & 0x02))) /* PECI 1 */ + return 0; + + return attr->mode; +} + +static const struct attribute_group nct7802_temp_group = { + .attrs = nct7802_temp_attrs, + .is_visible = nct7802_temp_is_visible, +}; + +static SENSOR_DEVICE_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0); +static SENSOR_DEVICE_ATTR_2(in0_min, S_IRUGO | S_IWUSR, show_in, store_in, + 0, 1); +static SENSOR_DEVICE_ATTR_2(in0_max, S_IRUGO | S_IWUSR, show_in, store_in, + 0, 2); +static SENSOR_DEVICE_ATTR_2(in0_alarm, S_IRUGO, show_alarm, NULL, 0x1e, 3); +static SENSOR_DEVICE_ATTR_2(in0_beep, S_IRUGO | S_IWUSR, show_beep, store_beep, + 0x5a, 3); + +static SENSOR_DEVICE_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 1, 0); + +static SENSOR_DEVICE_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 2, 0); +static SENSOR_DEVICE_ATTR_2(in2_min, S_IRUGO | S_IWUSR, show_in, store_in, + 2, 1); +static SENSOR_DEVICE_ATTR_2(in2_max, S_IRUGO | S_IWUSR, show_in, store_in, + 2, 2); +static SENSOR_DEVICE_ATTR_2(in2_alarm, S_IRUGO, show_alarm, NULL, 0x1e, 0); +static SENSOR_DEVICE_ATTR_2(in2_beep, S_IRUGO | S_IWUSR, show_beep, store_beep, + 0x5a, 0); + +static SENSOR_DEVICE_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 3, 0); +static SENSOR_DEVICE_ATTR_2(in3_min, S_IRUGO | S_IWUSR, show_in, store_in, + 3, 1); +static SENSOR_DEVICE_ATTR_2(in3_max, S_IRUGO | S_IWUSR, show_in, store_in, + 3, 2); +static SENSOR_DEVICE_ATTR_2(in3_alarm, S_IRUGO, show_alarm, NULL, 0x1e, 1); +static SENSOR_DEVICE_ATTR_2(in3_beep, S_IRUGO | S_IWUSR, show_beep, store_beep, + 0x5a, 1); + +static SENSOR_DEVICE_ATTR_2(in4_input, S_IRUGO, show_in, NULL, 4, 0); +static SENSOR_DEVICE_ATTR_2(in4_min, S_IRUGO | S_IWUSR, show_in, store_in, + 4, 1); +static SENSOR_DEVICE_ATTR_2(in4_max, S_IRUGO | S_IWUSR, show_in, store_in, + 4, 2); +static SENSOR_DEVICE_ATTR_2(in4_alarm, S_IRUGO, show_alarm, NULL, 0x1e, 2); +static SENSOR_DEVICE_ATTR_2(in4_beep, S_IRUGO | S_IWUSR, show_beep, store_beep, + 0x5a, 2); + +static struct attribute *nct7802_in_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in0_beep.dev_attr.attr, + + &sensor_dev_attr_in1_input.dev_attr.attr, /* 5 */ + + &sensor_dev_attr_in2_input.dev_attr.attr, /* 6 */ + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in2_beep.dev_attr.attr, + + &sensor_dev_attr_in3_input.dev_attr.attr, /* 11 */ + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in3_beep.dev_attr.attr, + + &sensor_dev_attr_in4_input.dev_attr.attr, /* 16 */ + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + &sensor_dev_attr_in4_beep.dev_attr.attr, + + NULL, +}; + +static umode_t nct7802_in_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct7802_data *data = dev_get_drvdata(dev); + unsigned int reg; + int err; + + if (index < 6) /* VCC, VCORE */ + return attr->mode; + + err = regmap_read(data->regmap, REG_MODE, ®); + if (err < 0) + return 0; + + if (index >= 6 && index < 11 && (reg & 0x03) != 0x03) /* VSEN1 */ + return 0; + if (index >= 11 && index < 16 && (reg & 0x0c) != 0x0c) /* VSEN2 */ + return 0; + if (index >= 16 && (reg & 0x30) != 0x30) /* VSEN3 */ + return 0; + + return attr->mode; +} + +static const struct attribute_group nct7802_in_group = { + .attrs = nct7802_in_attrs, + .is_visible = nct7802_in_is_visible, +}; + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0x10); +static SENSOR_DEVICE_ATTR_2(fan1_min, S_IRUGO | S_IWUSR, show_fan_min, + store_fan_min, 0x49, 0x4c); +static SENSOR_DEVICE_ATTR_2(fan1_alarm, S_IRUGO, show_alarm, NULL, 0x1a, 0); +static SENSOR_DEVICE_ATTR_2(fan1_beep, S_IRUGO | S_IWUSR, show_beep, store_beep, + 0x5b, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 0x11); +static SENSOR_DEVICE_ATTR_2(fan2_min, S_IRUGO | S_IWUSR, show_fan_min, + store_fan_min, 0x4a, 0x4d); +static SENSOR_DEVICE_ATTR_2(fan2_alarm, S_IRUGO, show_alarm, NULL, 0x1a, 1); +static SENSOR_DEVICE_ATTR_2(fan2_beep, S_IRUGO | S_IWUSR, show_beep, store_beep, + 0x5b, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 0x12); +static SENSOR_DEVICE_ATTR_2(fan3_min, S_IRUGO | S_IWUSR, show_fan_min, + store_fan_min, 0x4b, 0x4e); +static SENSOR_DEVICE_ATTR_2(fan3_alarm, S_IRUGO, show_alarm, NULL, 0x1a, 2); +static SENSOR_DEVICE_ATTR_2(fan3_beep, S_IRUGO | S_IWUSR, show_beep, store_beep, + 0x5b, 2); + +/* 7.2.89 Fan Control Output Type */ +static SENSOR_DEVICE_ATTR(pwm1_mode, S_IRUGO, show_pwm_mode, NULL, 0); +static SENSOR_DEVICE_ATTR(pwm2_mode, S_IRUGO, show_pwm_mode, NULL, 1); +static SENSOR_DEVICE_ATTR(pwm3_mode, S_IRUGO, show_pwm_mode, NULL, 2); + +/* 7.2.91... Fan Control Output Value */ +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, store_pwm, + REG_PWM(0)); +static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, show_pwm, store_pwm, + REG_PWM(1)); +static SENSOR_DEVICE_ATTR(pwm3, S_IRUGO | S_IWUSR, show_pwm, store_pwm, + REG_PWM(2)); + +/* 7.2.95... Temperature to Fan mapping Relationships Register */ +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, show_pwm_enable, + store_pwm_enable, 0); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IRUGO | S_IWUSR, show_pwm_enable, + store_pwm_enable, 1); +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IRUGO | S_IWUSR, show_pwm_enable, + store_pwm_enable, 2); + +static struct attribute *nct7802_fan_attrs[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_beep.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_beep.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_beep.dev_attr.attr, + + NULL +}; + +static umode_t nct7802_fan_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct7802_data *data = dev_get_drvdata(dev); + int fan = index / 4; /* 4 attributes per fan */ + unsigned int reg; + int err; + + err = regmap_read(data->regmap, REG_FAN_ENABLE, ®); + if (err < 0 || !(reg & (1 << fan))) + return 0; + + return attr->mode; +} + +static const struct attribute_group nct7802_fan_group = { + .attrs = nct7802_fan_attrs, + .is_visible = nct7802_fan_is_visible, +}; + +static struct attribute *nct7802_pwm_attrs[] = { + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_mode.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm2_mode.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_pwm3_mode.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, + NULL +}; + +static const struct attribute_group nct7802_pwm_group = { + .attrs = nct7802_pwm_attrs, +}; + +/* 7.2.115... 0x80-0x83, 0x84 Temperature (X-axis) transition */ +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point1_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x80, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point2_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x81, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point3_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x82, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point4_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x83, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point5_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x84, 0); + +/* 7.2.120... 0x85-0x88 PWM (Y-axis) transition */ +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x85); +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x86); +static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x87); +static SENSOR_DEVICE_ATTR(pwm1_auto_point4_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x88); +static SENSOR_DEVICE_ATTR(pwm1_auto_point5_pwm, S_IRUGO, show_pwm, NULL, 0); + +/* 7.2.124 Table 2 X-axis Transition Point 1 Register */ +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point1_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x90, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point2_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x91, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point3_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x92, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point4_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x93, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point5_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x94, 0); + +/* 7.2.129 Table 2 Y-axis Transition Point 1 Register */ +static SENSOR_DEVICE_ATTR(pwm2_auto_point1_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x95); +static SENSOR_DEVICE_ATTR(pwm2_auto_point2_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x96); +static SENSOR_DEVICE_ATTR(pwm2_auto_point3_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x97); +static SENSOR_DEVICE_ATTR(pwm2_auto_point4_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x98); +static SENSOR_DEVICE_ATTR(pwm2_auto_point5_pwm, S_IRUGO, show_pwm, NULL, 0); + +/* 7.2.133 Table 3 X-axis Transition Point 1 Register */ +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point1_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0xA0, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point2_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0xA1, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point3_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0xA2, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point4_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0xA3, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point5_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0xA4, 0); + +/* 7.2.138 Table 3 Y-axis Transition Point 1 Register */ +static SENSOR_DEVICE_ATTR(pwm3_auto_point1_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0xA5); +static SENSOR_DEVICE_ATTR(pwm3_auto_point2_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0xA6); +static SENSOR_DEVICE_ATTR(pwm3_auto_point3_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0xA7); +static SENSOR_DEVICE_ATTR(pwm3_auto_point4_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0xA8); +static SENSOR_DEVICE_ATTR(pwm3_auto_point5_pwm, S_IRUGO, show_pwm, NULL, 0); + +static struct attribute *nct7802_auto_point_attrs[] = { + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr, + + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr, + + &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point5_temp.dev_attr.attr, + + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point5_pwm.dev_attr.attr, + + &sensor_dev_attr_pwm3_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point5_temp.dev_attr.attr, + + &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point5_pwm.dev_attr.attr, + + NULL +}; + +static const struct attribute_group nct7802_auto_point_group = { + .attrs = nct7802_auto_point_attrs, +}; + +static const struct attribute_group *nct7802_groups[] = { + &nct7802_temp_group, + &nct7802_in_group, + &nct7802_fan_group, + &nct7802_pwm_group, + &nct7802_auto_point_group, + NULL +}; + +static int nct7802_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + int reg; + + /* + * Chip identification registers are only available in bank 0, + * so only attempt chip detection if bank 0 is selected + */ + reg = i2c_smbus_read_byte_data(client, REG_BANK); + if (reg != 0x00) + return -ENODEV; + + reg = i2c_smbus_read_byte_data(client, REG_VENDOR_ID); + if (reg != 0x50) + return -ENODEV; + + reg = i2c_smbus_read_byte_data(client, REG_CHIP_ID); + if (reg != 0xc3) + return -ENODEV; + + reg = i2c_smbus_read_byte_data(client, REG_VERSION_ID); + if (reg < 0 || (reg & 0xf0) != 0x20) + return -ENODEV; + + /* Also validate lower bits of voltage and temperature registers */ + reg = i2c_smbus_read_byte_data(client, REG_TEMP_LSB); + if (reg < 0 || (reg & 0x1f)) + return -ENODEV; + + reg = i2c_smbus_read_byte_data(client, REG_TEMP_PECI_LSB); + if (reg < 0 || (reg & 0x3f)) + return -ENODEV; + + reg = i2c_smbus_read_byte_data(client, REG_VOLTAGE_LOW); + if (reg < 0 || (reg & 0x3f)) + return -ENODEV; + + strlcpy(info->type, "nct7802", I2C_NAME_SIZE); + return 0; +} + +static bool nct7802_regmap_is_volatile(struct device *dev, unsigned int reg) +{ + return (reg != REG_BANK && reg <= 0x20) || + (reg >= REG_PWM(0) && reg <= REG_PWM(2)); +} + +static const struct regmap_config nct7802_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = nct7802_regmap_is_volatile, +}; + +static int nct7802_init_chip(struct nct7802_data *data) +{ + int err; + + /* Enable ADC */ + err = regmap_update_bits(data->regmap, REG_START, 0x01, 0x01); + if (err) + return err; + + /* Enable local temperature sensor */ + err = regmap_update_bits(data->regmap, REG_MODE, 0x40, 0x40); + if (err) + return err; + + /* Enable Vcore and VCC voltage monitoring */ + return regmap_update_bits(data->regmap, REG_VMON_ENABLE, 0x03, 0x03); +} + +static int nct7802_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct nct7802_data *data; + struct device *hwmon_dev; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + data->regmap = devm_regmap_init_i2c(client, &nct7802_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + mutex_init(&data->access_lock); + + ret = nct7802_init_chip(data); + if (ret < 0) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + nct7802_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const unsigned short nct7802_address_list[] = { + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, I2C_CLIENT_END +}; + +static const struct i2c_device_id nct7802_idtable[] = { + { "nct7802", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nct7802_idtable); + +static struct i2c_driver nct7802_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = DRVNAME, + }, + .detect = nct7802_detect, + .probe = nct7802_probe, + .id_table = nct7802_idtable, + .address_list = nct7802_address_list, +}; + +module_i2c_driver(nct7802_driver); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("NCT7802Y Hardware Monitoring Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/nct7904.c b/drivers/hwmon/nct7904.c new file mode 100644 index 000000000..7815ddf14 --- /dev/null +++ b/drivers/hwmon/nct7904.c @@ -0,0 +1,580 @@ +/* + * nct7904.c - driver for Nuvoton NCT7904D. + * + * Copyright (c) 2015 Kontron + * Author: Vadim V. Vlasov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#define VENDOR_ID_REG 0x7A /* Any bank */ +#define NUVOTON_ID 0x50 +#define CHIP_ID_REG 0x7B /* Any bank */ +#define NCT7904_ID 0xC5 +#define DEVICE_ID_REG 0x7C /* Any bank */ + +#define BANK_SEL_REG 0xFF +#define BANK_0 0x00 +#define BANK_1 0x01 +#define BANK_2 0x02 +#define BANK_3 0x03 +#define BANK_4 0x04 +#define BANK_MAX 0x04 + +#define FANIN_MAX 12 /* Counted from 1 */ +#define VSEN_MAX 21 /* VSEN1..14, 3VDD, VBAT, V3VSB, + LTD (not a voltage), VSEN17..19 */ +#define FANCTL_MAX 4 /* Counted from 1 */ +#define TCPU_MAX 8 /* Counted from 1 */ +#define TEMP_MAX 4 /* Counted from 1 */ + +#define VT_ADC_CTRL0_REG 0x20 /* Bank 0 */ +#define VT_ADC_CTRL1_REG 0x21 /* Bank 0 */ +#define VT_ADC_CTRL2_REG 0x22 /* Bank 0 */ +#define FANIN_CTRL0_REG 0x24 +#define FANIN_CTRL1_REG 0x25 +#define DTS_T_CTRL0_REG 0x26 +#define DTS_T_CTRL1_REG 0x27 +#define VT_ADC_MD_REG 0x2E + +#define VSEN1_HV_REG 0x40 /* Bank 0; 2 regs (HV/LV) per sensor */ +#define TEMP_CH1_HV_REG 0x42 /* Bank 0; same as VSEN2_HV */ +#define LTD_HV_REG 0x62 /* Bank 0; 2 regs in VSEN range */ +#define FANIN1_HV_REG 0x80 /* Bank 0; 2 regs (HV/LV) per sensor */ +#define T_CPU1_HV_REG 0xA0 /* Bank 0; 2 regs (HV/LV) per sensor */ + +#define PRTS_REG 0x03 /* Bank 2 */ +#define FANCTL1_FMR_REG 0x00 /* Bank 3; 1 reg per channel */ +#define FANCTL1_OUT_REG 0x10 /* Bank 3; 1 reg per channel */ + +static const unsigned short normal_i2c[] = { + 0x2d, 0x2e, I2C_CLIENT_END +}; + +struct nct7904_data { + struct i2c_client *client; + struct mutex bank_lock; + int bank_sel; + u32 fanin_mask; + u32 vsen_mask; + u32 tcpu_mask; + u8 fan_mode[FANCTL_MAX]; +}; + +/* Access functions */ +static int nct7904_bank_lock(struct nct7904_data *data, unsigned int bank) +{ + int ret; + + mutex_lock(&data->bank_lock); + if (data->bank_sel == bank) + return 0; + ret = i2c_smbus_write_byte_data(data->client, BANK_SEL_REG, bank); + if (ret == 0) + data->bank_sel = bank; + else + data->bank_sel = -1; + return ret; +} + +static inline void nct7904_bank_release(struct nct7904_data *data) +{ + mutex_unlock(&data->bank_lock); +} + +/* Read 1-byte register. Returns unsigned reg or -ERRNO on error. */ +static int nct7904_read_reg(struct nct7904_data *data, + unsigned int bank, unsigned int reg) +{ + struct i2c_client *client = data->client; + int ret; + + ret = nct7904_bank_lock(data, bank); + if (ret == 0) + ret = i2c_smbus_read_byte_data(client, reg); + + nct7904_bank_release(data); + return ret; +} + +/* + * Read 2-byte register. Returns register in big-endian format or + * -ERRNO on error. + */ +static int nct7904_read_reg16(struct nct7904_data *data, + unsigned int bank, unsigned int reg) +{ + struct i2c_client *client = data->client; + int ret, hi; + + ret = nct7904_bank_lock(data, bank); + if (ret == 0) { + ret = i2c_smbus_read_byte_data(client, reg); + if (ret >= 0) { + hi = ret; + ret = i2c_smbus_read_byte_data(client, reg + 1); + if (ret >= 0) + ret |= hi << 8; + } + } + + nct7904_bank_release(data); + return ret; +} + +/* Write 1-byte register. Returns 0 or -ERRNO on error. */ +static int nct7904_write_reg(struct nct7904_data *data, + unsigned int bank, unsigned int reg, u8 val) +{ + struct i2c_client *client = data->client; + int ret; + + ret = nct7904_bank_lock(data, bank); + if (ret == 0) + ret = i2c_smbus_write_byte_data(client, reg, val); + + nct7904_bank_release(data); + return ret; +} + +static int nct7904_read_fan(struct device *dev, u32 attr, int channel, + long *val) +{ + struct nct7904_data *data = dev_get_drvdata(dev); + unsigned int cnt, rpm; + int ret; + + switch (attr) { + case hwmon_fan_input: + ret = nct7904_read_reg16(data, BANK_0, + FANIN1_HV_REG + channel * 2); + if (ret < 0) + return ret; + cnt = ((ret & 0xff00) >> 3) | (ret & 0x1f); + if (cnt == 0x1fff) + rpm = 0; + else + rpm = 1350000 / cnt; + *val = rpm; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static umode_t nct7904_fan_is_visible(const void *_data, u32 attr, int channel) +{ + const struct nct7904_data *data = _data; + + if (attr == hwmon_fan_input && data->fanin_mask & (1 << channel)) + return S_IRUGO; + return 0; +} + +static u8 nct7904_chan_to_index[] = { + 0, /* Not used */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 18, 19, 20, 16 +}; + +static int nct7904_read_in(struct device *dev, u32 attr, int channel, + long *val) +{ + struct nct7904_data *data = dev_get_drvdata(dev); + int ret, volt, index; + + index = nct7904_chan_to_index[channel]; + + switch (attr) { + case hwmon_in_input: + ret = nct7904_read_reg16(data, BANK_0, + VSEN1_HV_REG + index * 2); + if (ret < 0) + return ret; + volt = ((ret & 0xff00) >> 5) | (ret & 0x7); + if (index < 14) + volt *= 2; /* 0.002V scale */ + else + volt *= 6; /* 0.006V scale */ + *val = volt; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static umode_t nct7904_in_is_visible(const void *_data, u32 attr, int channel) +{ + const struct nct7904_data *data = _data; + int index = nct7904_chan_to_index[channel]; + + if (channel > 0 && attr == hwmon_in_input && + (data->vsen_mask & BIT(index))) + return S_IRUGO; + + return 0; +} + +static int nct7904_read_temp(struct device *dev, u32 attr, int channel, + long *val) +{ + struct nct7904_data *data = dev_get_drvdata(dev); + int ret, temp; + + switch (attr) { + case hwmon_temp_input: + if (channel == 0) + ret = nct7904_read_reg16(data, BANK_0, LTD_HV_REG); + else + ret = nct7904_read_reg16(data, BANK_0, + T_CPU1_HV_REG + (channel - 1) * 2); + if (ret < 0) + return ret; + temp = ((ret & 0xff00) >> 5) | (ret & 0x7); + *val = sign_extend32(temp, 10) * 125; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static umode_t nct7904_temp_is_visible(const void *_data, u32 attr, int channel) +{ + const struct nct7904_data *data = _data; + + if (attr == hwmon_temp_input) { + if (channel == 0) { + if (data->vsen_mask & BIT(17)) + return S_IRUGO; + } else { + if (data->tcpu_mask & BIT(channel - 1)) + return S_IRUGO; + } + } + + return 0; +} + +static int nct7904_read_pwm(struct device *dev, u32 attr, int channel, + long *val) +{ + struct nct7904_data *data = dev_get_drvdata(dev); + int ret; + + switch (attr) { + case hwmon_pwm_input: + ret = nct7904_read_reg(data, BANK_3, FANCTL1_OUT_REG + channel); + if (ret < 0) + return ret; + *val = ret; + return 0; + case hwmon_pwm_enable: + ret = nct7904_read_reg(data, BANK_3, FANCTL1_FMR_REG + channel); + if (ret < 0) + return ret; + + *val = ret ? 2 : 1; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int nct7904_write_pwm(struct device *dev, u32 attr, int channel, + long val) +{ + struct nct7904_data *data = dev_get_drvdata(dev); + int ret; + + switch (attr) { + case hwmon_pwm_input: + if (val < 0 || val > 255) + return -EINVAL; + ret = nct7904_write_reg(data, BANK_3, FANCTL1_OUT_REG + channel, + val); + return ret; + case hwmon_pwm_enable: + if (val < 1 || val > 2 || + (val == 2 && !data->fan_mode[channel])) + return -EINVAL; + ret = nct7904_write_reg(data, BANK_3, FANCTL1_FMR_REG + channel, + val == 2 ? data->fan_mode[channel] : 0); + return ret; + default: + return -EOPNOTSUPP; + } +} + +static umode_t nct7904_pwm_is_visible(const void *_data, u32 attr, int channel) +{ + switch (attr) { + case hwmon_pwm_input: + case hwmon_pwm_enable: + return S_IRUGO | S_IWUSR; + default: + return 0; + } +} + +static int nct7904_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_in: + return nct7904_read_in(dev, attr, channel, val); + case hwmon_fan: + return nct7904_read_fan(dev, attr, channel, val); + case hwmon_pwm: + return nct7904_read_pwm(dev, attr, channel, val); + case hwmon_temp: + return nct7904_read_temp(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int nct7904_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_pwm: + return nct7904_write_pwm(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t nct7904_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_in: + return nct7904_in_is_visible(data, attr, channel); + case hwmon_fan: + return nct7904_fan_is_visible(data, attr, channel); + case hwmon_pwm: + return nct7904_pwm_is_visible(data, attr, channel); + case hwmon_temp: + return nct7904_temp_is_visible(data, attr, channel); + default: + return 0; + } +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int nct7904_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + + if (!i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_READ_BYTE | + I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + return -ENODEV; + + /* Determine the chip type. */ + if (i2c_smbus_read_byte_data(client, VENDOR_ID_REG) != NUVOTON_ID || + i2c_smbus_read_byte_data(client, CHIP_ID_REG) != NCT7904_ID || + (i2c_smbus_read_byte_data(client, DEVICE_ID_REG) & 0xf0) != 0x50 || + (i2c_smbus_read_byte_data(client, BANK_SEL_REG) & 0xf8) != 0x00) + return -ENODEV; + + strlcpy(info->type, "nct7904", I2C_NAME_SIZE); + + return 0; +} + +static const u32 nct7904_in_config[] = { + HWMON_I_INPUT, /* dummy, skipped in is_visible */ + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + 0 +}; + +static const struct hwmon_channel_info nct7904_in = { + .type = hwmon_in, + .config = nct7904_in_config, +}; + +static const u32 nct7904_fan_config[] = { + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + 0 +}; + +static const struct hwmon_channel_info nct7904_fan = { + .type = hwmon_fan, + .config = nct7904_fan_config, +}; + +static const u32 nct7904_pwm_config[] = { + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + 0 +}; + +static const struct hwmon_channel_info nct7904_pwm = { + .type = hwmon_pwm, + .config = nct7904_pwm_config, +}; + +static const u32 nct7904_temp_config[] = { + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + 0 +}; + +static const struct hwmon_channel_info nct7904_temp = { + .type = hwmon_temp, + .config = nct7904_temp_config, +}; + +static const struct hwmon_channel_info *nct7904_info[] = { + &nct7904_in, + &nct7904_fan, + &nct7904_pwm, + &nct7904_temp, + NULL +}; + +static const struct hwmon_ops nct7904_hwmon_ops = { + .is_visible = nct7904_is_visible, + .read = nct7904_read, + .write = nct7904_write, +}; + +static const struct hwmon_chip_info nct7904_chip_info = { + .ops = &nct7904_hwmon_ops, + .info = nct7904_info, +}; + +static int nct7904_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct nct7904_data *data; + struct device *hwmon_dev; + struct device *dev = &client->dev; + int ret, i; + u32 mask; + + data = devm_kzalloc(dev, sizeof(struct nct7904_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->bank_lock); + data->bank_sel = -1; + + /* Setup sensor groups. */ + /* FANIN attributes */ + ret = nct7904_read_reg16(data, BANK_0, FANIN_CTRL0_REG); + if (ret < 0) + return ret; + data->fanin_mask = (ret >> 8) | ((ret & 0xff) << 8); + + /* + * VSEN attributes + * + * Note: voltage sensors overlap with external temperature + * sensors. So, if we ever decide to support the latter + * we will have to adjust 'vsen_mask' accordingly. + */ + mask = 0; + ret = nct7904_read_reg16(data, BANK_0, VT_ADC_CTRL0_REG); + if (ret >= 0) + mask = (ret >> 8) | ((ret & 0xff) << 8); + ret = nct7904_read_reg(data, BANK_0, VT_ADC_CTRL2_REG); + if (ret >= 0) + mask |= (ret << 16); + data->vsen_mask = mask; + + /* CPU_TEMP attributes */ + ret = nct7904_read_reg16(data, BANK_0, DTS_T_CTRL0_REG); + if (ret < 0) + return ret; + data->tcpu_mask = ((ret >> 8) & 0xf) | ((ret & 0xf) << 4); + + for (i = 0; i < FANCTL_MAX; i++) { + ret = nct7904_read_reg(data, BANK_3, FANCTL1_FMR_REG + i); + if (ret < 0) + return ret; + data->fan_mode[i] = ret; + } + + hwmon_dev = + devm_hwmon_device_register_with_info(dev, client->name, data, + &nct7904_chip_info, NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id nct7904_id[] = { + {"nct7904", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, nct7904_id); + +static struct i2c_driver nct7904_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "nct7904", + }, + .probe = nct7904_probe, + .id_table = nct7904_id, + .detect = nct7904_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(nct7904_driver); + +MODULE_AUTHOR("Vadim V. Vlasov "); +MODULE_DESCRIPTION("Hwmon driver for NUVOTON NCT7904"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/npcm750-pwm-fan.c b/drivers/hwmon/npcm750-pwm-fan.c new file mode 100644 index 000000000..979b579bc --- /dev/null +++ b/drivers/hwmon/npcm750-pwm-fan.c @@ -0,0 +1,1057 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2014-2018 Nuvoton Technology corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* NPCM7XX PWM registers */ +#define NPCM7XX_PWM_REG_BASE(base, n) ((base) + ((n) * 0x1000L)) + +#define NPCM7XX_PWM_REG_PR(base, n) (NPCM7XX_PWM_REG_BASE(base, n) + 0x00) +#define NPCM7XX_PWM_REG_CSR(base, n) (NPCM7XX_PWM_REG_BASE(base, n) + 0x04) +#define NPCM7XX_PWM_REG_CR(base, n) (NPCM7XX_PWM_REG_BASE(base, n) + 0x08) +#define NPCM7XX_PWM_REG_CNRx(base, n, ch) \ + (NPCM7XX_PWM_REG_BASE(base, n) + 0x0C + (12 * (ch))) +#define NPCM7XX_PWM_REG_CMRx(base, n, ch) \ + (NPCM7XX_PWM_REG_BASE(base, n) + 0x10 + (12 * (ch))) +#define NPCM7XX_PWM_REG_PDRx(base, n, ch) \ + (NPCM7XX_PWM_REG_BASE(base, n) + 0x14 + (12 * (ch))) +#define NPCM7XX_PWM_REG_PIER(base, n) (NPCM7XX_PWM_REG_BASE(base, n) + 0x3C) +#define NPCM7XX_PWM_REG_PIIR(base, n) (NPCM7XX_PWM_REG_BASE(base, n) + 0x40) + +#define NPCM7XX_PWM_CTRL_CH0_MODE_BIT BIT(3) +#define NPCM7XX_PWM_CTRL_CH1_MODE_BIT BIT(11) +#define NPCM7XX_PWM_CTRL_CH2_MODE_BIT BIT(15) +#define NPCM7XX_PWM_CTRL_CH3_MODE_BIT BIT(19) + +#define NPCM7XX_PWM_CTRL_CH0_INV_BIT BIT(2) +#define NPCM7XX_PWM_CTRL_CH1_INV_BIT BIT(10) +#define NPCM7XX_PWM_CTRL_CH2_INV_BIT BIT(14) +#define NPCM7XX_PWM_CTRL_CH3_INV_BIT BIT(18) + +#define NPCM7XX_PWM_CTRL_CH0_EN_BIT BIT(0) +#define NPCM7XX_PWM_CTRL_CH1_EN_BIT BIT(8) +#define NPCM7XX_PWM_CTRL_CH2_EN_BIT BIT(12) +#define NPCM7XX_PWM_CTRL_CH3_EN_BIT BIT(16) + +/* Define the maximum PWM channel number */ +#define NPCM7XX_PWM_MAX_CHN_NUM 8 +#define NPCM7XX_PWM_MAX_CHN_NUM_IN_A_MODULE 4 +#define NPCM7XX_PWM_MAX_MODULES 2 + +/* Define the Counter Register, value = 100 for match 100% */ +#define NPCM7XX_PWM_COUNTER_DEFAULT_NUM 255 +#define NPCM7XX_PWM_CMR_DEFAULT_NUM 255 +#define NPCM7XX_PWM_CMR_MAX 255 + +/* default all PWM channels PRESCALE2 = 1 */ +#define NPCM7XX_PWM_PRESCALE2_DEFAULT_CH0 0x4 +#define NPCM7XX_PWM_PRESCALE2_DEFAULT_CH1 0x40 +#define NPCM7XX_PWM_PRESCALE2_DEFAULT_CH2 0x400 +#define NPCM7XX_PWM_PRESCALE2_DEFAULT_CH3 0x4000 + +#define PWM_OUTPUT_FREQ_25KHZ 25000 +#define PWN_CNT_DEFAULT 256 +#define MIN_PRESCALE1 2 +#define NPCM7XX_PWM_PRESCALE_SHIFT_CH01 8 + +#define NPCM7XX_PWM_PRESCALE2_DEFAULT (NPCM7XX_PWM_PRESCALE2_DEFAULT_CH0 | \ + NPCM7XX_PWM_PRESCALE2_DEFAULT_CH1 | \ + NPCM7XX_PWM_PRESCALE2_DEFAULT_CH2 | \ + NPCM7XX_PWM_PRESCALE2_DEFAULT_CH3) + +#define NPCM7XX_PWM_CTRL_MODE_DEFAULT (NPCM7XX_PWM_CTRL_CH0_MODE_BIT | \ + NPCM7XX_PWM_CTRL_CH1_MODE_BIT | \ + NPCM7XX_PWM_CTRL_CH2_MODE_BIT | \ + NPCM7XX_PWM_CTRL_CH3_MODE_BIT) + +/* NPCM7XX FAN Tacho registers */ +#define NPCM7XX_FAN_REG_BASE(base, n) ((base) + ((n) * 0x1000L)) + +#define NPCM7XX_FAN_REG_TCNT1(base, n) (NPCM7XX_FAN_REG_BASE(base, n) + 0x00) +#define NPCM7XX_FAN_REG_TCRA(base, n) (NPCM7XX_FAN_REG_BASE(base, n) + 0x02) +#define NPCM7XX_FAN_REG_TCRB(base, n) (NPCM7XX_FAN_REG_BASE(base, n) + 0x04) +#define NPCM7XX_FAN_REG_TCNT2(base, n) (NPCM7XX_FAN_REG_BASE(base, n) + 0x06) +#define NPCM7XX_FAN_REG_TPRSC(base, n) (NPCM7XX_FAN_REG_BASE(base, n) + 0x08) +#define NPCM7XX_FAN_REG_TCKC(base, n) (NPCM7XX_FAN_REG_BASE(base, n) + 0x0A) +#define NPCM7XX_FAN_REG_TMCTRL(base, n) (NPCM7XX_FAN_REG_BASE(base, n) + 0x0C) +#define NPCM7XX_FAN_REG_TICTRL(base, n) (NPCM7XX_FAN_REG_BASE(base, n) + 0x0E) +#define NPCM7XX_FAN_REG_TICLR(base, n) (NPCM7XX_FAN_REG_BASE(base, n) + 0x10) +#define NPCM7XX_FAN_REG_TIEN(base, n) (NPCM7XX_FAN_REG_BASE(base, n) + 0x12) +#define NPCM7XX_FAN_REG_TCPA(base, n) (NPCM7XX_FAN_REG_BASE(base, n) + 0x14) +#define NPCM7XX_FAN_REG_TCPB(base, n) (NPCM7XX_FAN_REG_BASE(base, n) + 0x16) +#define NPCM7XX_FAN_REG_TCPCFG(base, n) (NPCM7XX_FAN_REG_BASE(base, n) + 0x18) +#define NPCM7XX_FAN_REG_TINASEL(base, n) (NPCM7XX_FAN_REG_BASE(base, n) + 0x1A) +#define NPCM7XX_FAN_REG_TINBSEL(base, n) (NPCM7XX_FAN_REG_BASE(base, n) + 0x1C) + +#define NPCM7XX_FAN_TCKC_CLKX_NONE 0 +#define NPCM7XX_FAN_TCKC_CLK1_APB BIT(0) +#define NPCM7XX_FAN_TCKC_CLK2_APB BIT(3) + +#define NPCM7XX_FAN_TMCTRL_TBEN BIT(6) +#define NPCM7XX_FAN_TMCTRL_TAEN BIT(5) +#define NPCM7XX_FAN_TMCTRL_TBEDG BIT(4) +#define NPCM7XX_FAN_TMCTRL_TAEDG BIT(3) +#define NPCM7XX_FAN_TMCTRL_MODE_5 BIT(2) + +#define NPCM7XX_FAN_TICLR_CLEAR_ALL GENMASK(5, 0) +#define NPCM7XX_FAN_TICLR_TFCLR BIT(5) +#define NPCM7XX_FAN_TICLR_TECLR BIT(4) +#define NPCM7XX_FAN_TICLR_TDCLR BIT(3) +#define NPCM7XX_FAN_TICLR_TCCLR BIT(2) +#define NPCM7XX_FAN_TICLR_TBCLR BIT(1) +#define NPCM7XX_FAN_TICLR_TACLR BIT(0) + +#define NPCM7XX_FAN_TIEN_ENABLE_ALL GENMASK(5, 0) +#define NPCM7XX_FAN_TIEN_TFIEN BIT(5) +#define NPCM7XX_FAN_TIEN_TEIEN BIT(4) +#define NPCM7XX_FAN_TIEN_TDIEN BIT(3) +#define NPCM7XX_FAN_TIEN_TCIEN BIT(2) +#define NPCM7XX_FAN_TIEN_TBIEN BIT(1) +#define NPCM7XX_FAN_TIEN_TAIEN BIT(0) + +#define NPCM7XX_FAN_TICTRL_TFPND BIT(5) +#define NPCM7XX_FAN_TICTRL_TEPND BIT(4) +#define NPCM7XX_FAN_TICTRL_TDPND BIT(3) +#define NPCM7XX_FAN_TICTRL_TCPND BIT(2) +#define NPCM7XX_FAN_TICTRL_TBPND BIT(1) +#define NPCM7XX_FAN_TICTRL_TAPND BIT(0) + +#define NPCM7XX_FAN_TCPCFG_HIBEN BIT(7) +#define NPCM7XX_FAN_TCPCFG_EQBEN BIT(6) +#define NPCM7XX_FAN_TCPCFG_LOBEN BIT(5) +#define NPCM7XX_FAN_TCPCFG_CPBSEL BIT(4) +#define NPCM7XX_FAN_TCPCFG_HIAEN BIT(3) +#define NPCM7XX_FAN_TCPCFG_EQAEN BIT(2) +#define NPCM7XX_FAN_TCPCFG_LOAEN BIT(1) +#define NPCM7XX_FAN_TCPCFG_CPASEL BIT(0) + +/* FAN General Definition */ +/* Define the maximum FAN channel number */ +#define NPCM7XX_FAN_MAX_MODULE 8 +#define NPCM7XX_FAN_MAX_CHN_NUM_IN_A_MODULE 2 +#define NPCM7XX_FAN_MAX_CHN_NUM 16 + +/* + * Get Fan Tach Timeout (base on clock 214843.75Hz, 1 cnt = 4.654us) + * Timeout 94ms ~= 0x5000 + * (The minimum FAN speed could to support ~640RPM/pulse 1, + * 320RPM/pulse 2, ...-- 10.6Hz) + */ +#define NPCM7XX_FAN_TIMEOUT 0x5000 +#define NPCM7XX_FAN_TCNT 0xFFFF +#define NPCM7XX_FAN_TCPA (NPCM7XX_FAN_TCNT - NPCM7XX_FAN_TIMEOUT) +#define NPCM7XX_FAN_TCPB (NPCM7XX_FAN_TCNT - NPCM7XX_FAN_TIMEOUT) + +#define NPCM7XX_FAN_POLL_TIMER_200MS 200 +#define NPCM7XX_FAN_DEFAULT_PULSE_PER_REVOLUTION 2 +#define NPCM7XX_FAN_TINASEL_FANIN_DEFAULT 0 +#define NPCM7XX_FAN_CLK_PRESCALE 255 + +#define NPCM7XX_FAN_CMPA 0 +#define NPCM7XX_FAN_CMPB 1 + +/* Obtain the fan number */ +#define NPCM7XX_FAN_INPUT(fan, cmp) (((fan) << 1) + (cmp)) + +/* fan sample status */ +#define FAN_DISABLE 0xFF +#define FAN_INIT 0x00 +#define FAN_PREPARE_TO_GET_FIRST_CAPTURE 0x01 +#define FAN_ENOUGH_SAMPLE 0x02 + +struct npcm7xx_fan_dev { + u8 fan_st_flg; + u8 fan_pls_per_rev; + u16 fan_cnt; + u32 fan_cnt_tmp; +}; + +struct npcm7xx_cooling_device { + char name[THERMAL_NAME_LENGTH]; + struct npcm7xx_pwm_fan_data *data; + struct thermal_cooling_device *tcdev; + int pwm_port; + u8 *cooling_levels; + u8 max_state; + u8 cur_state; +}; + +struct npcm7xx_pwm_fan_data { + void __iomem *pwm_base; + void __iomem *fan_base; + unsigned long pwm_clk_freq; + unsigned long fan_clk_freq; + struct clk *pwm_clk; + struct clk *fan_clk; + struct mutex pwm_lock[NPCM7XX_PWM_MAX_MODULES]; + spinlock_t fan_lock[NPCM7XX_FAN_MAX_MODULE]; + int fan_irq[NPCM7XX_FAN_MAX_MODULE]; + bool pwm_present[NPCM7XX_PWM_MAX_CHN_NUM]; + bool fan_present[NPCM7XX_FAN_MAX_CHN_NUM]; + u32 input_clk_freq; + struct timer_list fan_timer; + struct npcm7xx_fan_dev fan_dev[NPCM7XX_FAN_MAX_CHN_NUM]; + struct npcm7xx_cooling_device *cdev[NPCM7XX_PWM_MAX_CHN_NUM]; + u8 fan_select; +}; + +static int npcm7xx_pwm_config_set(struct npcm7xx_pwm_fan_data *data, + int channel, u16 val) +{ + u32 pwm_ch = (channel % NPCM7XX_PWM_MAX_CHN_NUM_IN_A_MODULE); + u32 module = (channel / NPCM7XX_PWM_MAX_CHN_NUM_IN_A_MODULE); + u32 tmp_buf, ctrl_en_bit, env_bit; + + /* + * Config PWM Comparator register for setting duty cycle + */ + mutex_lock(&data->pwm_lock[module]); + + /* write new CMR value */ + iowrite32(val, NPCM7XX_PWM_REG_CMRx(data->pwm_base, module, pwm_ch)); + tmp_buf = ioread32(NPCM7XX_PWM_REG_CR(data->pwm_base, module)); + + switch (pwm_ch) { + case 0: + ctrl_en_bit = NPCM7XX_PWM_CTRL_CH0_EN_BIT; + env_bit = NPCM7XX_PWM_CTRL_CH0_INV_BIT; + break; + case 1: + ctrl_en_bit = NPCM7XX_PWM_CTRL_CH1_EN_BIT; + env_bit = NPCM7XX_PWM_CTRL_CH1_INV_BIT; + break; + case 2: + ctrl_en_bit = NPCM7XX_PWM_CTRL_CH2_EN_BIT; + env_bit = NPCM7XX_PWM_CTRL_CH2_INV_BIT; + break; + case 3: + ctrl_en_bit = NPCM7XX_PWM_CTRL_CH3_EN_BIT; + env_bit = NPCM7XX_PWM_CTRL_CH3_INV_BIT; + break; + default: + mutex_unlock(&data->pwm_lock[module]); + return -ENODEV; + } + + if (val == 0) { + /* Disable PWM */ + tmp_buf &= ~ctrl_en_bit; + tmp_buf |= env_bit; + } else { + /* Enable PWM */ + tmp_buf |= ctrl_en_bit; + tmp_buf &= ~env_bit; + } + + iowrite32(tmp_buf, NPCM7XX_PWM_REG_CR(data->pwm_base, module)); + mutex_unlock(&data->pwm_lock[module]); + + return 0; +} + +static inline void npcm7xx_fan_start_capture(struct npcm7xx_pwm_fan_data *data, + u8 fan, u8 cmp) +{ + u8 fan_id; + u8 reg_mode; + u8 reg_int; + unsigned long flags; + + fan_id = NPCM7XX_FAN_INPUT(fan, cmp); + + /* to check whether any fan tach is enable */ + if (data->fan_dev[fan_id].fan_st_flg != FAN_DISABLE) { + /* reset status */ + spin_lock_irqsave(&data->fan_lock[fan], flags); + + data->fan_dev[fan_id].fan_st_flg = FAN_INIT; + reg_int = ioread8(NPCM7XX_FAN_REG_TIEN(data->fan_base, fan)); + + /* + * the interrupt enable bits do not need to be cleared before + * it sets, the interrupt enable bits are cleared only on reset. + * the clock unit control register is behaving in the same + * manner that the interrupt enable register behave. + */ + if (cmp == NPCM7XX_FAN_CMPA) { + /* enable interrupt */ + iowrite8(reg_int | (NPCM7XX_FAN_TIEN_TAIEN | + NPCM7XX_FAN_TIEN_TEIEN), + NPCM7XX_FAN_REG_TIEN(data->fan_base, fan)); + + reg_mode = NPCM7XX_FAN_TCKC_CLK1_APB + | ioread8(NPCM7XX_FAN_REG_TCKC(data->fan_base, + fan)); + + /* start to Capture */ + iowrite8(reg_mode, NPCM7XX_FAN_REG_TCKC(data->fan_base, + fan)); + } else { + /* enable interrupt */ + iowrite8(reg_int | (NPCM7XX_FAN_TIEN_TBIEN | + NPCM7XX_FAN_TIEN_TFIEN), + NPCM7XX_FAN_REG_TIEN(data->fan_base, fan)); + + reg_mode = + NPCM7XX_FAN_TCKC_CLK2_APB + | ioread8(NPCM7XX_FAN_REG_TCKC(data->fan_base, + fan)); + + /* start to Capture */ + iowrite8(reg_mode, + NPCM7XX_FAN_REG_TCKC(data->fan_base, fan)); + } + + spin_unlock_irqrestore(&data->fan_lock[fan], flags); + } +} + +/* + * Enable a background timer to poll fan tach value, (200ms * 4) + * to polling all fan + */ +static void npcm7xx_fan_polling(struct timer_list *t) +{ + struct npcm7xx_pwm_fan_data *data; + int i; + + data = from_timer(data, t, fan_timer); + + /* + * Polling two module per one round, + * FAN01 & FAN89 / FAN23 & FAN1011 / FAN45 & FAN1213 / FAN67 & FAN1415 + */ + for (i = data->fan_select; i < NPCM7XX_FAN_MAX_MODULE; + i = i + 4) { + /* clear the flag and reset the counter (TCNT) */ + iowrite8(NPCM7XX_FAN_TICLR_CLEAR_ALL, + NPCM7XX_FAN_REG_TICLR(data->fan_base, i)); + + if (data->fan_present[i * 2]) { + iowrite16(NPCM7XX_FAN_TCNT, + NPCM7XX_FAN_REG_TCNT1(data->fan_base, i)); + npcm7xx_fan_start_capture(data, i, NPCM7XX_FAN_CMPA); + } + if (data->fan_present[(i * 2) + 1]) { + iowrite16(NPCM7XX_FAN_TCNT, + NPCM7XX_FAN_REG_TCNT2(data->fan_base, i)); + npcm7xx_fan_start_capture(data, i, NPCM7XX_FAN_CMPB); + } + } + + data->fan_select++; + data->fan_select &= 0x3; + + /* reset the timer interval */ + data->fan_timer.expires = jiffies + + msecs_to_jiffies(NPCM7XX_FAN_POLL_TIMER_200MS); + add_timer(&data->fan_timer); +} + +static inline void npcm7xx_fan_compute(struct npcm7xx_pwm_fan_data *data, + u8 fan, u8 cmp, u8 fan_id, u8 flag_int, + u8 flag_mode, u8 flag_clear) +{ + u8 reg_int; + u8 reg_mode; + u16 fan_cap; + + if (cmp == NPCM7XX_FAN_CMPA) + fan_cap = ioread16(NPCM7XX_FAN_REG_TCRA(data->fan_base, fan)); + else + fan_cap = ioread16(NPCM7XX_FAN_REG_TCRB(data->fan_base, fan)); + + /* clear capature flag, H/W will auto reset the NPCM7XX_FAN_TCNTx */ + iowrite8(flag_clear, NPCM7XX_FAN_REG_TICLR(data->fan_base, fan)); + + if (data->fan_dev[fan_id].fan_st_flg == FAN_INIT) { + /* First capture, drop it */ + data->fan_dev[fan_id].fan_st_flg = + FAN_PREPARE_TO_GET_FIRST_CAPTURE; + + /* reset counter */ + data->fan_dev[fan_id].fan_cnt_tmp = 0; + } else if (data->fan_dev[fan_id].fan_st_flg < FAN_ENOUGH_SAMPLE) { + /* + * collect the enough sample, + * (ex: 2 pulse fan need to get 2 sample) + */ + data->fan_dev[fan_id].fan_cnt_tmp += + (NPCM7XX_FAN_TCNT - fan_cap); + + data->fan_dev[fan_id].fan_st_flg++; + } else { + /* get enough sample or fan disable */ + if (data->fan_dev[fan_id].fan_st_flg == FAN_ENOUGH_SAMPLE) { + data->fan_dev[fan_id].fan_cnt_tmp += + (NPCM7XX_FAN_TCNT - fan_cap); + + /* compute finial average cnt per pulse */ + data->fan_dev[fan_id].fan_cnt = + data->fan_dev[fan_id].fan_cnt_tmp / + FAN_ENOUGH_SAMPLE; + + data->fan_dev[fan_id].fan_st_flg = FAN_INIT; + } + + reg_int = ioread8(NPCM7XX_FAN_REG_TIEN(data->fan_base, fan)); + + /* disable interrupt */ + iowrite8((reg_int & ~flag_int), + NPCM7XX_FAN_REG_TIEN(data->fan_base, fan)); + reg_mode = ioread8(NPCM7XX_FAN_REG_TCKC(data->fan_base, fan)); + + /* stop capturing */ + iowrite8((reg_mode & ~flag_mode), + NPCM7XX_FAN_REG_TCKC(data->fan_base, fan)); + } +} + +static inline void npcm7xx_check_cmp(struct npcm7xx_pwm_fan_data *data, + u8 fan, u8 cmp, u8 flag) +{ + u8 reg_int; + u8 reg_mode; + u8 flag_timeout; + u8 flag_cap; + u8 flag_clear; + u8 flag_int; + u8 flag_mode; + u8 fan_id; + + fan_id = NPCM7XX_FAN_INPUT(fan, cmp); + + if (cmp == NPCM7XX_FAN_CMPA) { + flag_cap = NPCM7XX_FAN_TICTRL_TAPND; + flag_timeout = NPCM7XX_FAN_TICTRL_TEPND; + flag_int = NPCM7XX_FAN_TIEN_TAIEN | NPCM7XX_FAN_TIEN_TEIEN; + flag_mode = NPCM7XX_FAN_TCKC_CLK1_APB; + flag_clear = NPCM7XX_FAN_TICLR_TACLR | NPCM7XX_FAN_TICLR_TECLR; + } else { + flag_cap = NPCM7XX_FAN_TICTRL_TBPND; + flag_timeout = NPCM7XX_FAN_TICTRL_TFPND; + flag_int = NPCM7XX_FAN_TIEN_TBIEN | NPCM7XX_FAN_TIEN_TFIEN; + flag_mode = NPCM7XX_FAN_TCKC_CLK2_APB; + flag_clear = NPCM7XX_FAN_TICLR_TBCLR | NPCM7XX_FAN_TICLR_TFCLR; + } + + if (flag & flag_timeout) { + reg_int = ioread8(NPCM7XX_FAN_REG_TIEN(data->fan_base, fan)); + + /* disable interrupt */ + iowrite8((reg_int & ~flag_int), + NPCM7XX_FAN_REG_TIEN(data->fan_base, fan)); + + /* clear interrupt flag */ + iowrite8(flag_clear, + NPCM7XX_FAN_REG_TICLR(data->fan_base, fan)); + + reg_mode = ioread8(NPCM7XX_FAN_REG_TCKC(data->fan_base, fan)); + + /* stop capturing */ + iowrite8((reg_mode & ~flag_mode), + NPCM7XX_FAN_REG_TCKC(data->fan_base, fan)); + + /* + * If timeout occurs (NPCM7XX_FAN_TIMEOUT), the fan doesn't + * connect or speed is lower than 10.6Hz (320RPM/pulse2). + * In these situation, the RPM output should be zero. + */ + data->fan_dev[fan_id].fan_cnt = 0; + } else { + /* input capture is occurred */ + if (flag & flag_cap) + npcm7xx_fan_compute(data, fan, cmp, fan_id, flag_int, + flag_mode, flag_clear); + } +} + +static irqreturn_t npcm7xx_fan_isr(int irq, void *dev_id) +{ + struct npcm7xx_pwm_fan_data *data = dev_id; + unsigned long flags; + int module; + u8 flag; + + module = irq - data->fan_irq[0]; + spin_lock_irqsave(&data->fan_lock[module], flags); + + flag = ioread8(NPCM7XX_FAN_REG_TICTRL(data->fan_base, module)); + if (flag > 0) { + npcm7xx_check_cmp(data, module, NPCM7XX_FAN_CMPA, flag); + npcm7xx_check_cmp(data, module, NPCM7XX_FAN_CMPB, flag); + spin_unlock_irqrestore(&data->fan_lock[module], flags); + return IRQ_HANDLED; + } + + spin_unlock_irqrestore(&data->fan_lock[module], flags); + + return IRQ_NONE; +} + +static int npcm7xx_read_pwm(struct device *dev, u32 attr, int channel, + long *val) +{ + struct npcm7xx_pwm_fan_data *data = dev_get_drvdata(dev); + u32 pmw_ch = (channel % NPCM7XX_PWM_MAX_CHN_NUM_IN_A_MODULE); + u32 module = (channel / NPCM7XX_PWM_MAX_CHN_NUM_IN_A_MODULE); + + switch (attr) { + case hwmon_pwm_input: + *val = ioread32 + (NPCM7XX_PWM_REG_CMRx(data->pwm_base, module, pmw_ch)); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int npcm7xx_write_pwm(struct device *dev, u32 attr, int channel, + long val) +{ + struct npcm7xx_pwm_fan_data *data = dev_get_drvdata(dev); + int err; + + switch (attr) { + case hwmon_pwm_input: + if (val < 0 || val > NPCM7XX_PWM_CMR_MAX) + return -EINVAL; + err = npcm7xx_pwm_config_set(data, channel, (u16)val); + break; + default: + err = -EOPNOTSUPP; + break; + } + + return err; +} + +static umode_t npcm7xx_pwm_is_visible(const void *_data, u32 attr, int channel) +{ + const struct npcm7xx_pwm_fan_data *data = _data; + + if (!data->pwm_present[channel]) + return 0; + + switch (attr) { + case hwmon_pwm_input: + return 0644; + default: + return 0; + } +} + +static int npcm7xx_read_fan(struct device *dev, u32 attr, int channel, + long *val) +{ + struct npcm7xx_pwm_fan_data *data = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_fan_input: + *val = 0; + if (data->fan_dev[channel].fan_cnt <= 0) + return data->fan_dev[channel].fan_cnt; + + /* Convert the raw reading to RPM */ + if (data->fan_dev[channel].fan_cnt > 0 && + data->fan_dev[channel].fan_pls_per_rev > 0) + *val = ((data->input_clk_freq * 60) / + (data->fan_dev[channel].fan_cnt * + data->fan_dev[channel].fan_pls_per_rev)); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static umode_t npcm7xx_fan_is_visible(const void *_data, u32 attr, int channel) +{ + const struct npcm7xx_pwm_fan_data *data = _data; + + if (!data->fan_present[channel]) + return 0; + + switch (attr) { + case hwmon_fan_input: + return 0444; + default: + return 0; + } +} + +static int npcm7xx_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_pwm: + return npcm7xx_read_pwm(dev, attr, channel, val); + case hwmon_fan: + return npcm7xx_read_fan(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int npcm7xx_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_pwm: + return npcm7xx_write_pwm(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t npcm7xx_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_pwm: + return npcm7xx_pwm_is_visible(data, attr, channel); + case hwmon_fan: + return npcm7xx_fan_is_visible(data, attr, channel); + default: + return 0; + } +} + +static const u32 npcm7xx_pwm_config[] = { + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + 0 +}; + +static const struct hwmon_channel_info npcm7xx_pwm = { + .type = hwmon_pwm, + .config = npcm7xx_pwm_config, +}; + +static const u32 npcm7xx_fan_config[] = { + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + 0 +}; + +static const struct hwmon_channel_info npcm7xx_fan = { + .type = hwmon_fan, + .config = npcm7xx_fan_config, +}; + +static const struct hwmon_channel_info *npcm7xx_info[] = { + &npcm7xx_pwm, + &npcm7xx_fan, + NULL +}; + +static const struct hwmon_ops npcm7xx_hwmon_ops = { + .is_visible = npcm7xx_is_visible, + .read = npcm7xx_read, + .write = npcm7xx_write, +}; + +static const struct hwmon_chip_info npcm7xx_chip_info = { + .ops = &npcm7xx_hwmon_ops, + .info = npcm7xx_info, +}; + +static u32 npcm7xx_pwm_init(struct npcm7xx_pwm_fan_data *data) +{ + int m, ch; + u32 prescale_val, output_freq; + + data->pwm_clk_freq = clk_get_rate(data->pwm_clk); + + /* Adjust NPCM7xx PWMs output frequency to ~25Khz */ + output_freq = data->pwm_clk_freq / PWN_CNT_DEFAULT; + prescale_val = DIV_ROUND_CLOSEST(output_freq, PWM_OUTPUT_FREQ_25KHZ); + + /* If prescale_val = 0, then the prescale output clock is stopped */ + if (prescale_val < MIN_PRESCALE1) + prescale_val = MIN_PRESCALE1; + /* + * prescale_val need to decrement in one because in the PWM Prescale + * register the Prescale value increment by one + */ + prescale_val--; + + /* Setting PWM Prescale Register value register to both modules */ + prescale_val |= (prescale_val << NPCM7XX_PWM_PRESCALE_SHIFT_CH01); + + for (m = 0; m < NPCM7XX_PWM_MAX_MODULES ; m++) { + iowrite32(prescale_val, NPCM7XX_PWM_REG_PR(data->pwm_base, m)); + iowrite32(NPCM7XX_PWM_PRESCALE2_DEFAULT, + NPCM7XX_PWM_REG_CSR(data->pwm_base, m)); + iowrite32(NPCM7XX_PWM_CTRL_MODE_DEFAULT, + NPCM7XX_PWM_REG_CR(data->pwm_base, m)); + + for (ch = 0; ch < NPCM7XX_PWM_MAX_CHN_NUM_IN_A_MODULE; ch++) { + iowrite32(NPCM7XX_PWM_COUNTER_DEFAULT_NUM, + NPCM7XX_PWM_REG_CNRx(data->pwm_base, m, ch)); + } + } + + return output_freq / ((prescale_val & 0xf) + 1); +} + +static void npcm7xx_fan_init(struct npcm7xx_pwm_fan_data *data) +{ + int md; + int ch; + int i; + u32 apb_clk_freq; + + for (md = 0; md < NPCM7XX_FAN_MAX_MODULE; md++) { + /* stop FAN0~7 clock */ + iowrite8(NPCM7XX_FAN_TCKC_CLKX_NONE, + NPCM7XX_FAN_REG_TCKC(data->fan_base, md)); + + /* disable all interrupt */ + iowrite8(0x00, NPCM7XX_FAN_REG_TIEN(data->fan_base, md)); + + /* clear all interrupt */ + iowrite8(NPCM7XX_FAN_TICLR_CLEAR_ALL, + NPCM7XX_FAN_REG_TICLR(data->fan_base, md)); + + /* set FAN0~7 clock prescaler */ + iowrite8(NPCM7XX_FAN_CLK_PRESCALE, + NPCM7XX_FAN_REG_TPRSC(data->fan_base, md)); + + /* set FAN0~7 mode (high-to-low transition) */ + iowrite8((NPCM7XX_FAN_TMCTRL_MODE_5 | NPCM7XX_FAN_TMCTRL_TBEN | + NPCM7XX_FAN_TMCTRL_TAEN), + NPCM7XX_FAN_REG_TMCTRL(data->fan_base, md)); + + /* set FAN0~7 Initial Count/Cap */ + iowrite16(NPCM7XX_FAN_TCNT, + NPCM7XX_FAN_REG_TCNT1(data->fan_base, md)); + iowrite16(NPCM7XX_FAN_TCNT, + NPCM7XX_FAN_REG_TCNT2(data->fan_base, md)); + + /* set FAN0~7 compare (equal to count) */ + iowrite8((NPCM7XX_FAN_TCPCFG_EQAEN | NPCM7XX_FAN_TCPCFG_EQBEN), + NPCM7XX_FAN_REG_TCPCFG(data->fan_base, md)); + + /* set FAN0~7 compare value */ + iowrite16(NPCM7XX_FAN_TCPA, + NPCM7XX_FAN_REG_TCPA(data->fan_base, md)); + iowrite16(NPCM7XX_FAN_TCPB, + NPCM7XX_FAN_REG_TCPB(data->fan_base, md)); + + /* set FAN0~7 fan input FANIN 0~15 */ + iowrite8(NPCM7XX_FAN_TINASEL_FANIN_DEFAULT, + NPCM7XX_FAN_REG_TINASEL(data->fan_base, md)); + iowrite8(NPCM7XX_FAN_TINASEL_FANIN_DEFAULT, + NPCM7XX_FAN_REG_TINBSEL(data->fan_base, md)); + + for (i = 0; i < NPCM7XX_FAN_MAX_CHN_NUM_IN_A_MODULE; i++) { + ch = md * NPCM7XX_FAN_MAX_CHN_NUM_IN_A_MODULE + i; + data->fan_dev[ch].fan_st_flg = FAN_DISABLE; + data->fan_dev[ch].fan_pls_per_rev = + NPCM7XX_FAN_DEFAULT_PULSE_PER_REVOLUTION; + data->fan_dev[ch].fan_cnt = 0; + } + } + + apb_clk_freq = clk_get_rate(data->fan_clk); + + /* Fan tach input clock = APB clock / prescalar, default is 255. */ + data->input_clk_freq = apb_clk_freq / (NPCM7XX_FAN_CLK_PRESCALE + 1); +} + +static int +npcm7xx_pwm_cz_get_max_state(struct thermal_cooling_device *tcdev, + unsigned long *state) +{ + struct npcm7xx_cooling_device *cdev = tcdev->devdata; + + *state = cdev->max_state; + + return 0; +} + +static int +npcm7xx_pwm_cz_get_cur_state(struct thermal_cooling_device *tcdev, + unsigned long *state) +{ + struct npcm7xx_cooling_device *cdev = tcdev->devdata; + + *state = cdev->cur_state; + + return 0; +} + +static int +npcm7xx_pwm_cz_set_cur_state(struct thermal_cooling_device *tcdev, + unsigned long state) +{ + struct npcm7xx_cooling_device *cdev = tcdev->devdata; + int ret; + + if (state > cdev->max_state) + return -EINVAL; + + cdev->cur_state = state; + ret = npcm7xx_pwm_config_set(cdev->data, cdev->pwm_port, + cdev->cooling_levels[cdev->cur_state]); + + return ret; +} + +static const struct thermal_cooling_device_ops npcm7xx_pwm_cool_ops = { + .get_max_state = npcm7xx_pwm_cz_get_max_state, + .get_cur_state = npcm7xx_pwm_cz_get_cur_state, + .set_cur_state = npcm7xx_pwm_cz_set_cur_state, +}; + +static int npcm7xx_create_pwm_cooling(struct device *dev, + struct device_node *child, + struct npcm7xx_pwm_fan_data *data, + u32 pwm_port, u8 num_levels) +{ + int ret; + struct npcm7xx_cooling_device *cdev; + + cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return -ENOMEM; + + cdev->cooling_levels = devm_kzalloc(dev, num_levels, GFP_KERNEL); + if (!cdev->cooling_levels) + return -ENOMEM; + + cdev->max_state = num_levels - 1; + ret = of_property_read_u8_array(child, "cooling-levels", + cdev->cooling_levels, + num_levels); + if (ret) { + dev_err(dev, "Property 'cooling-levels' cannot be read.\n"); + return ret; + } + snprintf(cdev->name, THERMAL_NAME_LENGTH, "%s%d", child->name, + pwm_port); + + cdev->tcdev = thermal_of_cooling_device_register(child, + cdev->name, + cdev, + &npcm7xx_pwm_cool_ops); + if (IS_ERR(cdev->tcdev)) + return PTR_ERR(cdev->tcdev); + + cdev->data = data; + cdev->pwm_port = pwm_port; + + data->cdev[pwm_port] = cdev; + + return 0; +} + +static int npcm7xx_en_pwm_fan(struct device *dev, + struct device_node *child, + struct npcm7xx_pwm_fan_data *data) +{ + u8 *fan_ch; + u32 pwm_port; + int ret, fan_cnt; + u8 index, ch; + + ret = of_property_read_u32(child, "reg", &pwm_port); + if (ret) + return ret; + + data->pwm_present[pwm_port] = true; + ret = npcm7xx_pwm_config_set(data, pwm_port, + NPCM7XX_PWM_CMR_DEFAULT_NUM); + + ret = of_property_count_u8_elems(child, "cooling-levels"); + if (ret > 0) { + ret = npcm7xx_create_pwm_cooling(dev, child, data, pwm_port, + ret); + if (ret) + return ret; + } + + fan_cnt = of_property_count_u8_elems(child, "fan-tach-ch"); + if (fan_cnt < 1) + return -EINVAL; + + fan_ch = devm_kcalloc(dev, fan_cnt, sizeof(*fan_ch), GFP_KERNEL); + if (!fan_ch) + return -ENOMEM; + + ret = of_property_read_u8_array(child, "fan-tach-ch", fan_ch, fan_cnt); + if (ret) + return ret; + + for (ch = 0; ch < fan_cnt; ch++) { + index = fan_ch[ch]; + data->fan_present[index] = true; + data->fan_dev[index].fan_st_flg = FAN_INIT; + } + + return 0; +} + +static int npcm7xx_pwm_fan_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np, *child; + struct npcm7xx_pwm_fan_data *data; + struct resource *res; + struct device *hwmon; + char name[20]; + int ret, cnt; + u32 output_freq; + u32 i; + + np = dev->of_node; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwm"); + if (!res) { + dev_err(dev, "pwm resource not found\n"); + return -ENODEV; + } + + data->pwm_base = devm_ioremap_resource(dev, res); + dev_dbg(dev, "pwm base resource is %pR\n", res); + if (IS_ERR(data->pwm_base)) + return PTR_ERR(data->pwm_base); + + data->pwm_clk = devm_clk_get(dev, "pwm"); + if (IS_ERR(data->pwm_clk)) { + dev_err(dev, "couldn't get pwm clock\n"); + return PTR_ERR(data->pwm_clk); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "fan"); + if (!res) { + dev_err(dev, "fan resource not found\n"); + return -ENODEV; + } + + data->fan_base = devm_ioremap_resource(dev, res); + dev_dbg(dev, "fan base resource is %pR\n", res); + if (IS_ERR(data->fan_base)) + return PTR_ERR(data->fan_base); + + data->fan_clk = devm_clk_get(dev, "fan"); + if (IS_ERR(data->fan_clk)) { + dev_err(dev, "couldn't get fan clock\n"); + return PTR_ERR(data->fan_clk); + } + + output_freq = npcm7xx_pwm_init(data); + npcm7xx_fan_init(data); + + for (cnt = 0; cnt < NPCM7XX_PWM_MAX_MODULES ; cnt++) + mutex_init(&data->pwm_lock[cnt]); + + for (i = 0; i < NPCM7XX_FAN_MAX_MODULE; i++) { + spin_lock_init(&data->fan_lock[i]); + + data->fan_irq[i] = platform_get_irq(pdev, i); + if (data->fan_irq[i] < 0) { + dev_err(dev, "get IRQ fan%d failed\n", i); + return data->fan_irq[i]; + } + + sprintf(name, "NPCM7XX-FAN-MD%d", i); + ret = devm_request_irq(dev, data->fan_irq[i], npcm7xx_fan_isr, + 0, name, (void *)data); + if (ret) { + dev_err(dev, "register IRQ fan%d failed\n", i); + return ret; + } + } + + for_each_child_of_node(np, child) { + ret = npcm7xx_en_pwm_fan(dev, child, data); + if (ret) { + dev_err(dev, "enable pwm and fan failed\n"); + of_node_put(child); + return ret; + } + } + + hwmon = devm_hwmon_device_register_with_info(dev, "npcm7xx_pwm_fan", + data, &npcm7xx_chip_info, + NULL); + if (IS_ERR(hwmon)) { + dev_err(dev, "unable to register hwmon device\n"); + return PTR_ERR(hwmon); + } + + for (i = 0; i < NPCM7XX_FAN_MAX_CHN_NUM; i++) { + if (data->fan_present[i]) { + /* fan timer initialization */ + data->fan_timer.expires = jiffies + + msecs_to_jiffies(NPCM7XX_FAN_POLL_TIMER_200MS); + timer_setup(&data->fan_timer, + npcm7xx_fan_polling, 0); + add_timer(&data->fan_timer); + break; + } + } + + pr_info("NPCM7XX PWM-FAN Driver probed, output Freq %dHz[PWM], input Freq %dHz[FAN]\n", + output_freq, data->input_clk_freq); + + return 0; +} + +static const struct of_device_id of_pwm_fan_match_table[] = { + { .compatible = "nuvoton,npcm750-pwm-fan", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_pwm_fan_match_table); + +static struct platform_driver npcm7xx_pwm_fan_driver = { + .probe = npcm7xx_pwm_fan_probe, + .driver = { + .name = "npcm7xx_pwm_fan", + .of_match_table = of_pwm_fan_match_table, + }, +}; + +module_platform_driver(npcm7xx_pwm_fan_driver); + +MODULE_DESCRIPTION("Nuvoton NPCM7XX PWM and Fan Tacho driver"); +MODULE_AUTHOR("Tomer Maimon "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/nsa320-hwmon.c b/drivers/hwmon/nsa320-hwmon.c new file mode 100644 index 000000000..5a16109cd --- /dev/null +++ b/drivers/hwmon/nsa320-hwmon.c @@ -0,0 +1,215 @@ +/* + * drivers/hwmon/nsa320-hwmon.c + * + * ZyXEL NSA320 Media Servers + * hardware monitoring + * + * Copyright (C) 2016 Adam Baker + * based on a board file driver + * Copyright (C) 2012 Peter Schildmann + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Tests for error return values rely upon this value being < 0x80 */ +#define MAGIC_NUMBER 0x55 + +/* + * The Zyxel hwmon MCU is a Holtek HT46R065 that is factory programmed + * to perform temperature and fan speed monitoring. It is read by taking + * the active pin low. The 32 bit output word is then clocked onto the + * data line. The MSB of the data word is a magic nuber to indicate it + * has been read correctly, the next byte is the fan speed (in hundreds + * of RPM) and the last two bytes are the temperature (in tenths of a + * degree) + */ + +struct nsa320_hwmon { + struct mutex update_lock; /* lock GPIO operations */ + unsigned long last_updated; /* jiffies */ + unsigned long mcu_data; + struct gpio_desc *act; + struct gpio_desc *clk; + struct gpio_desc *data; +}; + +enum nsa320_inputs { + NSA320_TEMP = 0, + NSA320_FAN = 1, +}; + +static const char * const nsa320_input_names[] = { + [NSA320_TEMP] = "System Temperature", + [NSA320_FAN] = "Chassis Fan", +}; + +/* + * Although this protocol looks similar to SPI the long delay + * between the active (aka chip select) signal and the shorter + * delay between clock pulses are needed for reliable operation. + * The delays provided are taken from the manufacturer kernel, + * testing suggest they probably incorporate a reasonable safety + * margin. (The single device tested became unreliable if the + * delay was reduced to 1/10th of this value.) + */ +static s32 nsa320_hwmon_update(struct device *dev) +{ + u32 mcu_data; + u32 mask; + struct nsa320_hwmon *hwmon = dev_get_drvdata(dev); + + mutex_lock(&hwmon->update_lock); + + mcu_data = hwmon->mcu_data; + + if (time_after(jiffies, hwmon->last_updated + HZ) || mcu_data == 0) { + gpiod_set_value(hwmon->act, 1); + msleep(100); + + mcu_data = 0; + for (mask = BIT(31); mask; mask >>= 1) { + gpiod_set_value(hwmon->clk, 0); + usleep_range(100, 200); + gpiod_set_value(hwmon->clk, 1); + usleep_range(100, 200); + if (gpiod_get_value(hwmon->data)) + mcu_data |= mask; + } + + gpiod_set_value(hwmon->act, 0); + dev_dbg(dev, "Read raw MCU data %08x\n", mcu_data); + + if ((mcu_data >> 24) != MAGIC_NUMBER) { + dev_dbg(dev, "Read invalid MCU data %08x\n", mcu_data); + mcu_data = -EIO; + } else { + hwmon->mcu_data = mcu_data; + hwmon->last_updated = jiffies; + } + } + + mutex_unlock(&hwmon->update_lock); + + return mcu_data; +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int channel = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%s\n", nsa320_input_names[channel]); +} + +static ssize_t temp1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + s32 mcu_data = nsa320_hwmon_update(dev); + + if (mcu_data < 0) + return mcu_data; + + return sprintf(buf, "%d\n", (mcu_data & 0xffff) * 100); +} + +static ssize_t fan1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + s32 mcu_data = nsa320_hwmon_update(dev); + + if (mcu_data < 0) + return mcu_data; + + return sprintf(buf, "%d\n", ((mcu_data & 0xff0000) >> 16) * 100); +} + +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, NSA320_TEMP); +static DEVICE_ATTR_RO(temp1_input); +static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, show_label, NULL, NSA320_FAN); +static DEVICE_ATTR_RO(fan1_input); + +static struct attribute *nsa320_attrs[] = { + &sensor_dev_attr_temp1_label.dev_attr.attr, + &dev_attr_temp1_input.attr, + &sensor_dev_attr_fan1_label.dev_attr.attr, + &dev_attr_fan1_input.attr, + NULL +}; + +ATTRIBUTE_GROUPS(nsa320); + +static const struct of_device_id of_nsa320_hwmon_match[] = { + { .compatible = "zyxel,nsa320-mcu", }, + { }, +}; + +static int nsa320_hwmon_probe(struct platform_device *pdev) +{ + struct nsa320_hwmon *hwmon; + struct device *classdev; + + hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + + /* Look up the GPIO pins to use */ + hwmon->act = devm_gpiod_get(&pdev->dev, "act", GPIOD_OUT_LOW); + if (IS_ERR(hwmon->act)) + return PTR_ERR(hwmon->act); + + hwmon->clk = devm_gpiod_get(&pdev->dev, "clk", GPIOD_OUT_HIGH); + if (IS_ERR(hwmon->clk)) + return PTR_ERR(hwmon->clk); + + hwmon->data = devm_gpiod_get(&pdev->dev, "data", GPIOD_IN); + if (IS_ERR(hwmon->data)) + return PTR_ERR(hwmon->data); + + mutex_init(&hwmon->update_lock); + + classdev = devm_hwmon_device_register_with_groups(&pdev->dev, + "nsa320", hwmon, nsa320_groups); + + return PTR_ERR_OR_ZERO(classdev); + +} + +/* All allocations use devres so remove() is not needed. */ + +static struct platform_driver nsa320_hwmon_driver = { + .probe = nsa320_hwmon_probe, + .driver = { + .name = "nsa320-hwmon", + .of_match_table = of_match_ptr(of_nsa320_hwmon_match), + }, +}; + +module_platform_driver(nsa320_hwmon_driver); + +MODULE_DEVICE_TABLE(of, of_nsa320_hwmon_match); +MODULE_AUTHOR("Peter Schildmann "); +MODULE_AUTHOR("Adam Baker "); +MODULE_DESCRIPTION("NSA320 Hardware Monitoring"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:nsa320-hwmon"); diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c new file mode 100644 index 000000000..c52d07c6b --- /dev/null +++ b/drivers/hwmon/ntc_thermistor.c @@ -0,0 +1,668 @@ +/* + * ntc_thermistor.c - NTC Thermistors + * + * Copyright (C) 2010 Samsung Electronics + * MyungJoo Ham + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +struct ntc_compensation { + int temp_c; + unsigned int ohm; +}; + +/* Order matters, ntc_match references the entries by index */ +static const struct platform_device_id ntc_thermistor_id[] = { + { "ncp15wb473", TYPE_NCPXXWB473 }, + { "ncp18wb473", TYPE_NCPXXWB473 }, + { "ncp21wb473", TYPE_NCPXXWB473 }, + { "ncp03wb473", TYPE_NCPXXWB473 }, + { "ncp15wl333", TYPE_NCPXXWL333 }, + { "b57330v2103", TYPE_B57330V2103}, + { "ncp03wf104", TYPE_NCPXXWF104 }, + { "ncp15xh103", TYPE_NCPXXXH103 }, + { }, +}; + +/* + * A compensation table should be sorted by the values of .ohm + * in descending order. + * The following compensation tables are from the specification of Murata NTC + * Thermistors Datasheet + */ +static const struct ntc_compensation ncpXXwb473[] = { + { .temp_c = -40, .ohm = 1747920 }, + { .temp_c = -35, .ohm = 1245428 }, + { .temp_c = -30, .ohm = 898485 }, + { .temp_c = -25, .ohm = 655802 }, + { .temp_c = -20, .ohm = 483954 }, + { .temp_c = -15, .ohm = 360850 }, + { .temp_c = -10, .ohm = 271697 }, + { .temp_c = -5, .ohm = 206463 }, + { .temp_c = 0, .ohm = 158214 }, + { .temp_c = 5, .ohm = 122259 }, + { .temp_c = 10, .ohm = 95227 }, + { .temp_c = 15, .ohm = 74730 }, + { .temp_c = 20, .ohm = 59065 }, + { .temp_c = 25, .ohm = 47000 }, + { .temp_c = 30, .ohm = 37643 }, + { .temp_c = 35, .ohm = 30334 }, + { .temp_c = 40, .ohm = 24591 }, + { .temp_c = 45, .ohm = 20048 }, + { .temp_c = 50, .ohm = 16433 }, + { .temp_c = 55, .ohm = 13539 }, + { .temp_c = 60, .ohm = 11209 }, + { .temp_c = 65, .ohm = 9328 }, + { .temp_c = 70, .ohm = 7798 }, + { .temp_c = 75, .ohm = 6544 }, + { .temp_c = 80, .ohm = 5518 }, + { .temp_c = 85, .ohm = 4674 }, + { .temp_c = 90, .ohm = 3972 }, + { .temp_c = 95, .ohm = 3388 }, + { .temp_c = 100, .ohm = 2902 }, + { .temp_c = 105, .ohm = 2494 }, + { .temp_c = 110, .ohm = 2150 }, + { .temp_c = 115, .ohm = 1860 }, + { .temp_c = 120, .ohm = 1615 }, + { .temp_c = 125, .ohm = 1406 }, +}; +static const struct ntc_compensation ncpXXwl333[] = { + { .temp_c = -40, .ohm = 1610154 }, + { .temp_c = -35, .ohm = 1130850 }, + { .temp_c = -30, .ohm = 802609 }, + { .temp_c = -25, .ohm = 575385 }, + { .temp_c = -20, .ohm = 416464 }, + { .temp_c = -15, .ohm = 304219 }, + { .temp_c = -10, .ohm = 224193 }, + { .temp_c = -5, .ohm = 166623 }, + { .temp_c = 0, .ohm = 124850 }, + { .temp_c = 5, .ohm = 94287 }, + { .temp_c = 10, .ohm = 71747 }, + { .temp_c = 15, .ohm = 54996 }, + { .temp_c = 20, .ohm = 42455 }, + { .temp_c = 25, .ohm = 33000 }, + { .temp_c = 30, .ohm = 25822 }, + { .temp_c = 35, .ohm = 20335 }, + { .temp_c = 40, .ohm = 16115 }, + { .temp_c = 45, .ohm = 12849 }, + { .temp_c = 50, .ohm = 10306 }, + { .temp_c = 55, .ohm = 8314 }, + { .temp_c = 60, .ohm = 6746 }, + { .temp_c = 65, .ohm = 5503 }, + { .temp_c = 70, .ohm = 4513 }, + { .temp_c = 75, .ohm = 3721 }, + { .temp_c = 80, .ohm = 3084 }, + { .temp_c = 85, .ohm = 2569 }, + { .temp_c = 90, .ohm = 2151 }, + { .temp_c = 95, .ohm = 1809 }, + { .temp_c = 100, .ohm = 1529 }, + { .temp_c = 105, .ohm = 1299 }, + { .temp_c = 110, .ohm = 1108 }, + { .temp_c = 115, .ohm = 949 }, + { .temp_c = 120, .ohm = 817 }, + { .temp_c = 125, .ohm = 707 }, +}; + +static const struct ntc_compensation ncpXXwf104[] = { + { .temp_c = -40, .ohm = 4397119 }, + { .temp_c = -35, .ohm = 3088599 }, + { .temp_c = -30, .ohm = 2197225 }, + { .temp_c = -25, .ohm = 1581881 }, + { .temp_c = -20, .ohm = 1151037 }, + { .temp_c = -15, .ohm = 846579 }, + { .temp_c = -10, .ohm = 628988 }, + { .temp_c = -5, .ohm = 471632 }, + { .temp_c = 0, .ohm = 357012 }, + { .temp_c = 5, .ohm = 272500 }, + { .temp_c = 10, .ohm = 209710 }, + { .temp_c = 15, .ohm = 162651 }, + { .temp_c = 20, .ohm = 127080 }, + { .temp_c = 25, .ohm = 100000 }, + { .temp_c = 30, .ohm = 79222 }, + { .temp_c = 35, .ohm = 63167 }, + { .temp_c = 40, .ohm = 50677 }, + { .temp_c = 45, .ohm = 40904 }, + { .temp_c = 50, .ohm = 33195 }, + { .temp_c = 55, .ohm = 27091 }, + { .temp_c = 60, .ohm = 22224 }, + { .temp_c = 65, .ohm = 18323 }, + { .temp_c = 70, .ohm = 15184 }, + { .temp_c = 75, .ohm = 12635 }, + { .temp_c = 80, .ohm = 10566 }, + { .temp_c = 85, .ohm = 8873 }, + { .temp_c = 90, .ohm = 7481 }, + { .temp_c = 95, .ohm = 6337 }, + { .temp_c = 100, .ohm = 5384 }, + { .temp_c = 105, .ohm = 4594 }, + { .temp_c = 110, .ohm = 3934 }, + { .temp_c = 115, .ohm = 3380 }, + { .temp_c = 120, .ohm = 2916 }, + { .temp_c = 125, .ohm = 2522 }, +}; + +static const struct ntc_compensation ncpXXxh103[] = { + { .temp_c = -40, .ohm = 247565 }, + { .temp_c = -35, .ohm = 181742 }, + { .temp_c = -30, .ohm = 135128 }, + { .temp_c = -25, .ohm = 101678 }, + { .temp_c = -20, .ohm = 77373 }, + { .temp_c = -15, .ohm = 59504 }, + { .temp_c = -10, .ohm = 46222 }, + { .temp_c = -5, .ohm = 36244 }, + { .temp_c = 0, .ohm = 28674 }, + { .temp_c = 5, .ohm = 22878 }, + { .temp_c = 10, .ohm = 18399 }, + { .temp_c = 15, .ohm = 14910 }, + { .temp_c = 20, .ohm = 12169 }, + { .temp_c = 25, .ohm = 10000 }, + { .temp_c = 30, .ohm = 8271 }, + { .temp_c = 35, .ohm = 6883 }, + { .temp_c = 40, .ohm = 5762 }, + { .temp_c = 45, .ohm = 4851 }, + { .temp_c = 50, .ohm = 4105 }, + { .temp_c = 55, .ohm = 3492 }, + { .temp_c = 60, .ohm = 2985 }, + { .temp_c = 65, .ohm = 2563 }, + { .temp_c = 70, .ohm = 2211 }, + { .temp_c = 75, .ohm = 1915 }, + { .temp_c = 80, .ohm = 1666 }, + { .temp_c = 85, .ohm = 1454 }, + { .temp_c = 90, .ohm = 1275 }, + { .temp_c = 95, .ohm = 1121 }, + { .temp_c = 100, .ohm = 990 }, + { .temp_c = 105, .ohm = 876 }, + { .temp_c = 110, .ohm = 779 }, + { .temp_c = 115, .ohm = 694 }, + { .temp_c = 120, .ohm = 620 }, + { .temp_c = 125, .ohm = 556 }, +}; + +/* + * The following compensation table is from the specification of EPCOS NTC + * Thermistors Datasheet + */ +static const struct ntc_compensation b57330v2103[] = { + { .temp_c = -40, .ohm = 190030 }, + { .temp_c = -35, .ohm = 145360 }, + { .temp_c = -30, .ohm = 112060 }, + { .temp_c = -25, .ohm = 87041 }, + { .temp_c = -20, .ohm = 68104 }, + { .temp_c = -15, .ohm = 53665 }, + { .temp_c = -10, .ohm = 42576 }, + { .temp_c = -5, .ohm = 34001 }, + { .temp_c = 0, .ohm = 27326 }, + { .temp_c = 5, .ohm = 22096 }, + { .temp_c = 10, .ohm = 17973 }, + { .temp_c = 15, .ohm = 14703 }, + { .temp_c = 20, .ohm = 12090 }, + { .temp_c = 25, .ohm = 10000 }, + { .temp_c = 30, .ohm = 8311 }, + { .temp_c = 35, .ohm = 6941 }, + { .temp_c = 40, .ohm = 5825 }, + { .temp_c = 45, .ohm = 4911 }, + { .temp_c = 50, .ohm = 4158 }, + { .temp_c = 55, .ohm = 3536 }, + { .temp_c = 60, .ohm = 3019 }, + { .temp_c = 65, .ohm = 2588 }, + { .temp_c = 70, .ohm = 2227 }, + { .temp_c = 75, .ohm = 1924 }, + { .temp_c = 80, .ohm = 1668 }, + { .temp_c = 85, .ohm = 1451 }, + { .temp_c = 90, .ohm = 1266 }, + { .temp_c = 95, .ohm = 1108 }, + { .temp_c = 100, .ohm = 973 }, + { .temp_c = 105, .ohm = 857 }, + { .temp_c = 110, .ohm = 757 }, + { .temp_c = 115, .ohm = 671 }, + { .temp_c = 120, .ohm = 596 }, + { .temp_c = 125, .ohm = 531 }, +}; + +struct ntc_data { + struct ntc_thermistor_platform_data *pdata; + const struct ntc_compensation *comp; + int n_comp; +}; + +#if defined(CONFIG_OF) && IS_ENABLED(CONFIG_IIO) +static int ntc_adc_iio_read(struct ntc_thermistor_platform_data *pdata) +{ + struct iio_channel *channel = pdata->chan; + int raw, uv, ret; + + ret = iio_read_channel_raw(channel, &raw); + if (ret < 0) { + pr_err("read channel() error: %d\n", ret); + return ret; + } + + ret = iio_convert_raw_to_processed(channel, raw, &uv, 1000); + if (ret < 0) { + /* Assume 12 bit ADC with vref at pullup_uv */ + uv = (pdata->pullup_uv * (s64)raw) >> 12; + } + + return uv; +} + +static const struct of_device_id ntc_match[] = { + { .compatible = "murata,ncp15wb473", + .data = &ntc_thermistor_id[0] }, + { .compatible = "murata,ncp18wb473", + .data = &ntc_thermistor_id[1] }, + { .compatible = "murata,ncp21wb473", + .data = &ntc_thermistor_id[2] }, + { .compatible = "murata,ncp03wb473", + .data = &ntc_thermistor_id[3] }, + { .compatible = "murata,ncp15wl333", + .data = &ntc_thermistor_id[4] }, + { .compatible = "epcos,b57330v2103", + .data = &ntc_thermistor_id[5]}, + { .compatible = "murata,ncp03wf104", + .data = &ntc_thermistor_id[6] }, + { .compatible = "murata,ncp15xh103", + .data = &ntc_thermistor_id[7] }, + + /* Usage of vendor name "ntc" is deprecated */ + { .compatible = "ntc,ncp15wb473", + .data = &ntc_thermistor_id[0] }, + { .compatible = "ntc,ncp18wb473", + .data = &ntc_thermistor_id[1] }, + { .compatible = "ntc,ncp21wb473", + .data = &ntc_thermistor_id[2] }, + { .compatible = "ntc,ncp03wb473", + .data = &ntc_thermistor_id[3] }, + { .compatible = "ntc,ncp15wl333", + .data = &ntc_thermistor_id[4] }, + { }, +}; +MODULE_DEVICE_TABLE(of, ntc_match); + +static struct ntc_thermistor_platform_data * +ntc_thermistor_parse_dt(struct device *dev) +{ + struct iio_channel *chan; + enum iio_chan_type type; + struct device_node *np = dev->of_node; + struct ntc_thermistor_platform_data *pdata; + int ret; + + if (!np) + return NULL; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + chan = devm_iio_channel_get(dev, NULL); + if (IS_ERR(chan)) + return ERR_CAST(chan); + + ret = iio_get_channel_type(chan, &type); + if (ret < 0) + return ERR_PTR(ret); + + if (type != IIO_VOLTAGE) + return ERR_PTR(-EINVAL); + + if (of_property_read_u32(np, "pullup-uv", &pdata->pullup_uv)) + return ERR_PTR(-ENODEV); + if (of_property_read_u32(np, "pullup-ohm", &pdata->pullup_ohm)) + return ERR_PTR(-ENODEV); + if (of_property_read_u32(np, "pulldown-ohm", &pdata->pulldown_ohm)) + return ERR_PTR(-ENODEV); + + if (of_find_property(np, "connected-positive", NULL)) + pdata->connect = NTC_CONNECTED_POSITIVE; + else /* status change should be possible if not always on. */ + pdata->connect = NTC_CONNECTED_GROUND; + + pdata->chan = chan; + pdata->read_uv = ntc_adc_iio_read; + + return pdata; +} +#else +static struct ntc_thermistor_platform_data * +ntc_thermistor_parse_dt(struct device *dev) +{ + return NULL; +} + +#define ntc_match NULL + +#endif + +static inline u64 div64_u64_safe(u64 dividend, u64 divisor) +{ + if (divisor == 0 && dividend == 0) + return 0; + if (divisor == 0) + return UINT_MAX; + return div64_u64(dividend, divisor); +} + +static int get_ohm_of_thermistor(struct ntc_data *data, unsigned int uv) +{ + struct ntc_thermistor_platform_data *pdata = data->pdata; + u32 puv = pdata->pullup_uv; + u64 n, puo, pdo; + puo = pdata->pullup_ohm; + pdo = pdata->pulldown_ohm; + + if (uv == 0) + return (pdata->connect == NTC_CONNECTED_POSITIVE) ? + INT_MAX : 0; + if (uv >= puv) + return (pdata->connect == NTC_CONNECTED_POSITIVE) ? + 0 : INT_MAX; + + if (pdata->connect == NTC_CONNECTED_POSITIVE && puo == 0) + n = div_u64(pdo * (puv - uv), uv); + else if (pdata->connect == NTC_CONNECTED_GROUND && pdo == 0) + n = div_u64(puo * uv, puv - uv); + else if (pdata->connect == NTC_CONNECTED_POSITIVE) + n = div64_u64_safe(pdo * puo * (puv - uv), + puo * uv - pdo * (puv - uv)); + else + n = div64_u64_safe(pdo * puo * uv, pdo * (puv - uv) - puo * uv); + + if (n > INT_MAX) + n = INT_MAX; + return n; +} + +static void lookup_comp(struct ntc_data *data, unsigned int ohm, + int *i_low, int *i_high) +{ + int start, end, mid; + + /* + * Handle special cases: Resistance is higher than or equal to + * resistance in first table entry, or resistance is lower or equal + * to resistance in last table entry. + * In these cases, return i_low == i_high, either pointing to the + * beginning or to the end of the table depending on the condition. + */ + if (ohm >= data->comp[0].ohm) { + *i_low = 0; + *i_high = 0; + return; + } + if (ohm <= data->comp[data->n_comp - 1].ohm) { + *i_low = data->n_comp - 1; + *i_high = data->n_comp - 1; + return; + } + + /* Do a binary search on compensation table */ + start = 0; + end = data->n_comp; + while (start < end) { + mid = start + (end - start) / 2; + /* + * start <= mid < end + * data->comp[start].ohm > ohm >= data->comp[end].ohm + * + * We could check for "ohm == data->comp[mid].ohm" here, but + * that is a quite unlikely condition, and we would have to + * check again after updating start. Check it at the end instead + * for simplicity. + */ + if (ohm >= data->comp[mid].ohm) { + end = mid; + } else { + start = mid + 1; + /* + * ohm >= data->comp[start].ohm might be true here, + * since we set start to mid + 1. In that case, we are + * done. We could keep going, but the condition is quite + * likely to occur, so it is worth checking for it. + */ + if (ohm >= data->comp[start].ohm) + end = start; + } + /* + * start <= end + * data->comp[start].ohm >= ohm >= data->comp[end].ohm + */ + } + /* + * start == end + * ohm >= data->comp[end].ohm + */ + *i_low = end; + if (ohm == data->comp[end].ohm) + *i_high = end; + else + *i_high = end - 1; +} + +static int get_temp_mc(struct ntc_data *data, unsigned int ohm) +{ + int low, high; + int temp; + + lookup_comp(data, ohm, &low, &high); + if (low == high) { + /* Unable to use linear approximation */ + temp = data->comp[low].temp_c * 1000; + } else { + temp = data->comp[low].temp_c * 1000 + + ((data->comp[high].temp_c - data->comp[low].temp_c) * + 1000 * ((int)ohm - (int)data->comp[low].ohm)) / + ((int)data->comp[high].ohm - (int)data->comp[low].ohm); + } + return temp; +} + +static int ntc_thermistor_get_ohm(struct ntc_data *data) +{ + int read_uv; + + if (data->pdata->read_ohm) + return data->pdata->read_ohm(); + + if (data->pdata->read_uv) { + read_uv = data->pdata->read_uv(data->pdata); + if (read_uv < 0) + return read_uv; + return get_ohm_of_thermistor(data, read_uv); + } + return -EINVAL; +} + +static int ntc_read_temp(void *data, int *temp) +{ + int ohm; + + ohm = ntc_thermistor_get_ohm(data); + if (ohm < 0) + return ohm; + + *temp = get_temp_mc(data, ohm); + + return 0; +} + +static ssize_t ntc_show_type(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "4\n"); +} + +static ssize_t ntc_show_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ntc_data *data = dev_get_drvdata(dev); + int ohm; + + ohm = ntc_thermistor_get_ohm(data); + if (ohm < 0) + return ohm; + + return sprintf(buf, "%d\n", get_temp_mc(data, ohm)); +} + +static SENSOR_DEVICE_ATTR(temp1_type, S_IRUGO, ntc_show_type, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, ntc_show_temp, NULL, 0); + +static struct attribute *ntc_attrs[] = { + &sensor_dev_attr_temp1_type.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + NULL, +}; +ATTRIBUTE_GROUPS(ntc); + +static const struct thermal_zone_of_device_ops ntc_of_thermal_ops = { + .get_temp = ntc_read_temp, +}; + +static int ntc_thermistor_probe(struct platform_device *pdev) +{ + struct thermal_zone_device *tz; + struct device *dev = &pdev->dev; + const struct of_device_id *of_id = + of_match_device(of_match_ptr(ntc_match), dev); + const struct platform_device_id *pdev_id; + struct ntc_thermistor_platform_data *pdata; + struct device *hwmon_dev; + struct ntc_data *data; + + pdata = ntc_thermistor_parse_dt(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + else if (pdata == NULL) + pdata = dev_get_platdata(dev); + + if (!pdata) { + dev_err(dev, "No platform init data supplied.\n"); + return -ENODEV; + } + + /* Either one of the two is required. */ + if (!pdata->read_uv && !pdata->read_ohm) { + dev_err(dev, + "Both read_uv and read_ohm missing. Need either one of the two.\n"); + return -EINVAL; + } + + if (pdata->read_uv && pdata->read_ohm) { + dev_warn(dev, + "Only one of read_uv and read_ohm is needed; ignoring read_uv.\n"); + pdata->read_uv = NULL; + } + + if (pdata->read_uv && (pdata->pullup_uv == 0 || + (pdata->pullup_ohm == 0 && pdata->connect == + NTC_CONNECTED_GROUND) || + (pdata->pulldown_ohm == 0 && pdata->connect == + NTC_CONNECTED_POSITIVE) || + (pdata->connect != NTC_CONNECTED_POSITIVE && + pdata->connect != NTC_CONNECTED_GROUND))) { + dev_err(dev, "Required data to use read_uv not supplied.\n"); + return -EINVAL; + } + + data = devm_kzalloc(dev, sizeof(struct ntc_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + pdev_id = of_id ? of_id->data : platform_get_device_id(pdev); + + data->pdata = pdata; + + switch (pdev_id->driver_data) { + case TYPE_NCPXXWB473: + data->comp = ncpXXwb473; + data->n_comp = ARRAY_SIZE(ncpXXwb473); + break; + case TYPE_NCPXXWL333: + data->comp = ncpXXwl333; + data->n_comp = ARRAY_SIZE(ncpXXwl333); + break; + case TYPE_B57330V2103: + data->comp = b57330v2103; + data->n_comp = ARRAY_SIZE(b57330v2103); + break; + case TYPE_NCPXXWF104: + data->comp = ncpXXwf104; + data->n_comp = ARRAY_SIZE(ncpXXwf104); + break; + case TYPE_NCPXXXH103: + data->comp = ncpXXxh103; + data->n_comp = ARRAY_SIZE(ncpXXxh103); + break; + default: + dev_err(dev, "Unknown device type: %lu(%s)\n", + pdev_id->driver_data, pdev_id->name); + return -EINVAL; + } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, pdev_id->name, + data, ntc_groups); + if (IS_ERR(hwmon_dev)) { + dev_err(dev, "unable to register as hwmon device.\n"); + return PTR_ERR(hwmon_dev); + } + + dev_info(dev, "Thermistor type: %s successfully probed.\n", + pdev_id->name); + + tz = devm_thermal_zone_of_sensor_register(dev, 0, data, + &ntc_of_thermal_ops); + if (IS_ERR(tz)) + dev_dbg(dev, "Failed to register to thermal fw.\n"); + + return 0; +} + +static struct platform_driver ntc_thermistor_driver = { + .driver = { + .name = "ntc-thermistor", + .of_match_table = of_match_ptr(ntc_match), + }, + .probe = ntc_thermistor_probe, + .id_table = ntc_thermistor_id, +}; + +module_platform_driver(ntc_thermistor_driver); + +MODULE_DESCRIPTION("NTC Thermistor Driver"); +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ntc-thermistor"); diff --git a/drivers/hwmon/pc87360.c b/drivers/hwmon/pc87360.c new file mode 100644 index 000000000..7e3697727 --- /dev/null +++ b/drivers/hwmon/pc87360.c @@ -0,0 +1,1818 @@ +/* + * pc87360.c - Part of lm_sensors, Linux kernel modules + * for hardware monitoring + * Copyright (C) 2004, 2007 Jean Delvare + * + * Copied from smsc47m1.c: + * Copyright (C) 2002 Mark D. Studebaker + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Supports the following chips: + * + * Chip #vin #fan #pwm #temp devid + * PC87360 - 2 2 - 0xE1 + * PC87363 - 2 2 - 0xE8 + * PC87364 - 3 3 - 0xE4 + * PC87365 11 3 3 2 0xE5 + * PC87366 11 3 3 3-4 0xE9 + * + * This driver assumes that no more than one chip is present, and one of + * the standard Super-I/O addresses is used (0x2E/0x2F or 0x4E/0x4F). + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static u8 devid; +static struct platform_device *pdev; +static unsigned short extra_isa[3]; +static u8 confreg[4]; + +static int init = 1; +module_param(init, int, 0); +MODULE_PARM_DESC(init, +"Chip initialization level:\n" +" 0: None\n" +"*1: Forcibly enable internal voltage and temperature channels, except in9\n" +" 2: Forcibly enable all voltage and temperature channels, except in9\n" +" 3: Forcibly enable all voltage and temperature channels, including in9"); + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +/* + * Super-I/O registers and operations + */ + +#define DEV 0x07 /* Register: Logical device select */ +#define DEVID 0x20 /* Register: Device ID */ +#define ACT 0x30 /* Register: Device activation */ +#define BASE 0x60 /* Register: Base address */ + +#define FSCM 0x09 /* Logical device: fans */ +#define VLM 0x0d /* Logical device: voltages */ +#define TMS 0x0e /* Logical device: temperatures */ +#define LDNI_MAX 3 +static const u8 logdev[LDNI_MAX] = { FSCM, VLM, TMS }; + +#define LD_FAN 0 +#define LD_IN 1 +#define LD_TEMP 2 + +static inline void superio_outb(int sioaddr, int reg, int val) +{ + outb(reg, sioaddr); + outb(val, sioaddr + 1); +} + +static inline int superio_inb(int sioaddr, int reg) +{ + outb(reg, sioaddr); + return inb(sioaddr + 1); +} + +static inline void superio_exit(int sioaddr) +{ + outb(0x02, sioaddr); + outb(0x02, sioaddr + 1); +} + +/* + * Logical devices + */ + +#define PC87360_EXTENT 0x10 +#define PC87365_REG_BANK 0x09 +#define NO_BANK 0xff + +/* + * Fan registers and conversions + */ + +/* nr has to be 0 or 1 (PC87360/87363) or 2 (PC87364/87365/87366) */ +#define PC87360_REG_PRESCALE(nr) (0x00 + 2 * (nr)) +#define PC87360_REG_PWM(nr) (0x01 + 2 * (nr)) +#define PC87360_REG_FAN_MIN(nr) (0x06 + 3 * (nr)) +#define PC87360_REG_FAN(nr) (0x07 + 3 * (nr)) +#define PC87360_REG_FAN_STATUS(nr) (0x08 + 3 * (nr)) + +#define FAN_FROM_REG(val, div) ((val) == 0 ? 0 : \ + 480000 / ((val) * (div))) +#define FAN_TO_REG(val, div) ((val) <= 100 ? 0 : \ + 480000 / ((val) * (div))) +#define FAN_DIV_FROM_REG(val) (1 << (((val) >> 5) & 0x03)) +#define FAN_STATUS_FROM_REG(val) ((val) & 0x07) + +#define FAN_CONFIG_MONITOR(val, nr) (((val) >> (2 + (nr) * 3)) & 1) +#define FAN_CONFIG_CONTROL(val, nr) (((val) >> (3 + (nr) * 3)) & 1) +#define FAN_CONFIG_INVERT(val, nr) (((val) >> (4 + (nr) * 3)) & 1) + +#define PWM_FROM_REG(val, inv) ((inv) ? 255 - (val) : (val)) +static inline u8 PWM_TO_REG(int val, int inv) +{ + if (inv) + val = 255 - val; + if (val < 0) + return 0; + if (val > 255) + return 255; + return val; +} + +/* + * Voltage registers and conversions + */ + +#define PC87365_REG_IN_CONVRATE 0x07 +#define PC87365_REG_IN_CONFIG 0x08 +#define PC87365_REG_IN 0x0B +#define PC87365_REG_IN_MIN 0x0D +#define PC87365_REG_IN_MAX 0x0C +#define PC87365_REG_IN_STATUS 0x0A +#define PC87365_REG_IN_ALARMS1 0x00 +#define PC87365_REG_IN_ALARMS2 0x01 +#define PC87365_REG_VID 0x06 + +#define IN_FROM_REG(val, ref) (((val) * (ref) + 128) / 256) +#define IN_TO_REG(val, ref) ((val) < 0 ? 0 : \ + (val) * 256 >= (ref) * 255 ? 255 : \ + ((val) * 256 + (ref) / 2) / (ref)) + +/* + * Temperature registers and conversions + */ + +#define PC87365_REG_TEMP_CONFIG 0x08 +#define PC87365_REG_TEMP 0x0B +#define PC87365_REG_TEMP_MIN 0x0D +#define PC87365_REG_TEMP_MAX 0x0C +#define PC87365_REG_TEMP_CRIT 0x0E +#define PC87365_REG_TEMP_STATUS 0x0A +#define PC87365_REG_TEMP_ALARMS 0x00 + +#define TEMP_FROM_REG(val) ((val) * 1000) +#define TEMP_TO_REG(val) ((val) < -55000 ? -55 : \ + (val) > 127000 ? 127 : \ + (val) < 0 ? ((val) - 500) / 1000 : \ + ((val) + 500) / 1000) + +/* + * Device data + */ + +struct pc87360_data { + const char *name; + struct device *hwmon_dev; + struct mutex lock; + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + int address[3]; + + u8 fannr, innr, tempnr; + + u8 fan[3]; /* Register value */ + u8 fan_min[3]; /* Register value */ + u8 fan_status[3]; /* Register value */ + u8 pwm[3]; /* Register value */ + u16 fan_conf; /* Configuration register values, combined */ + + u16 in_vref; /* 1 mV/bit */ + u8 in[14]; /* Register value */ + u8 in_min[14]; /* Register value */ + u8 in_max[14]; /* Register value */ + u8 in_crit[3]; /* Register value */ + u8 in_status[14]; /* Register value */ + u16 in_alarms; /* Register values, combined, masked */ + u8 vid_conf; /* Configuration register value */ + u8 vrm; + u8 vid; /* Register value */ + + s8 temp[3]; /* Register value */ + s8 temp_min[3]; /* Register value */ + s8 temp_max[3]; /* Register value */ + s8 temp_crit[3]; /* Register value */ + u8 temp_status[3]; /* Register value */ + u8 temp_alarms; /* Register value, masked */ +}; + +/* + * Functions declaration + */ + +static int pc87360_probe(struct platform_device *pdev); +static int pc87360_remove(struct platform_device *pdev); + +static int pc87360_read_value(struct pc87360_data *data, u8 ldi, u8 bank, + u8 reg); +static void pc87360_write_value(struct pc87360_data *data, u8 ldi, u8 bank, + u8 reg, u8 value); +static void pc87360_init_device(struct platform_device *pdev, + int use_thermistors); +static struct pc87360_data *pc87360_update_device(struct device *dev); + +/* + * Driver data + */ + +static struct platform_driver pc87360_driver = { + .driver = { + .name = "pc87360", + }, + .probe = pc87360_probe, + .remove = pc87360_remove, +}; + +/* + * Sysfs stuff + */ + +static ssize_t show_fan_input(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", FAN_FROM_REG(data->fan[attr->index], + FAN_DIV_FROM_REG(data->fan_status[attr->index]))); +} +static ssize_t show_fan_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", FAN_FROM_REG(data->fan_min[attr->index], + FAN_DIV_FROM_REG(data->fan_status[attr->index]))); +} +static ssize_t show_fan_div(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", + FAN_DIV_FROM_REG(data->fan_status[attr->index])); +} +static ssize_t show_fan_status(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", + FAN_STATUS_FROM_REG(data->fan_status[attr->index])); +} +static ssize_t set_fan_min(struct device *dev, + struct device_attribute *devattr, const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = dev_get_drvdata(dev); + long fan_min; + int err; + + err = kstrtol(buf, 10, &fan_min); + if (err) + return err; + + mutex_lock(&data->update_lock); + fan_min = FAN_TO_REG(fan_min, + FAN_DIV_FROM_REG(data->fan_status[attr->index])); + + /* If it wouldn't fit, change clock divisor */ + while (fan_min > 255 + && (data->fan_status[attr->index] & 0x60) != 0x60) { + fan_min >>= 1; + data->fan[attr->index] >>= 1; + data->fan_status[attr->index] += 0x20; + } + data->fan_min[attr->index] = fan_min > 255 ? 255 : fan_min; + pc87360_write_value(data, LD_FAN, NO_BANK, + PC87360_REG_FAN_MIN(attr->index), + data->fan_min[attr->index]); + + /* Write new divider, preserve alarm bits */ + pc87360_write_value(data, LD_FAN, NO_BANK, + PC87360_REG_FAN_STATUS(attr->index), + data->fan_status[attr->index] & 0xF9); + mutex_unlock(&data->update_lock); + + return count; +} + +static struct sensor_device_attribute fan_input[] = { + SENSOR_ATTR(fan1_input, S_IRUGO, show_fan_input, NULL, 0), + SENSOR_ATTR(fan2_input, S_IRUGO, show_fan_input, NULL, 1), + SENSOR_ATTR(fan3_input, S_IRUGO, show_fan_input, NULL, 2), +}; +static struct sensor_device_attribute fan_status[] = { + SENSOR_ATTR(fan1_status, S_IRUGO, show_fan_status, NULL, 0), + SENSOR_ATTR(fan2_status, S_IRUGO, show_fan_status, NULL, 1), + SENSOR_ATTR(fan3_status, S_IRUGO, show_fan_status, NULL, 2), +}; +static struct sensor_device_attribute fan_div[] = { + SENSOR_ATTR(fan1_div, S_IRUGO, show_fan_div, NULL, 0), + SENSOR_ATTR(fan2_div, S_IRUGO, show_fan_div, NULL, 1), + SENSOR_ATTR(fan3_div, S_IRUGO, show_fan_div, NULL, 2), +}; +static struct sensor_device_attribute fan_min[] = { + SENSOR_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min, set_fan_min, 0), + SENSOR_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min, set_fan_min, 1), + SENSOR_ATTR(fan3_min, S_IWUSR | S_IRUGO, show_fan_min, set_fan_min, 2), +}; + +#define FAN_UNIT_ATTRS(X) \ +{ &fan_input[X].dev_attr.attr, \ + &fan_status[X].dev_attr.attr, \ + &fan_div[X].dev_attr.attr, \ + &fan_min[X].dev_attr.attr, \ + NULL \ +} + +static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", + PWM_FROM_REG(data->pwm[attr->index], + FAN_CONFIG_INVERT(data->fan_conf, + attr->index))); +} +static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->pwm[attr->index] = PWM_TO_REG(val, + FAN_CONFIG_INVERT(data->fan_conf, attr->index)); + pc87360_write_value(data, LD_FAN, NO_BANK, PC87360_REG_PWM(attr->index), + data->pwm[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static struct sensor_device_attribute pwm[] = { + SENSOR_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 0), + SENSOR_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1), + SENSOR_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 2), +}; + +static struct attribute *pc8736x_fan_attr[][5] = { + FAN_UNIT_ATTRS(0), + FAN_UNIT_ATTRS(1), + FAN_UNIT_ATTRS(2) +}; + +static const struct attribute_group pc8736x_fan_attr_group[] = { + { .attrs = pc8736x_fan_attr[0], }, + { .attrs = pc8736x_fan_attr[1], }, + { .attrs = pc8736x_fan_attr[2], }, +}; + +static ssize_t show_in_input(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", IN_FROM_REG(data->in[attr->index], + data->in_vref)); +} +static ssize_t show_in_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", IN_FROM_REG(data->in_min[attr->index], + data->in_vref)); +} +static ssize_t show_in_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", IN_FROM_REG(data->in_max[attr->index], + data->in_vref)); +} +static ssize_t show_in_status(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", data->in_status[attr->index]); +} +static ssize_t set_in_min(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_min[attr->index] = IN_TO_REG(val, data->in_vref); + pc87360_write_value(data, LD_IN, attr->index, PC87365_REG_IN_MIN, + data->in_min[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t set_in_max(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_max[attr->index] = IN_TO_REG(val, + data->in_vref); + pc87360_write_value(data, LD_IN, attr->index, PC87365_REG_IN_MAX, + data->in_max[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static struct sensor_device_attribute in_input[] = { + SENSOR_ATTR(in0_input, S_IRUGO, show_in_input, NULL, 0), + SENSOR_ATTR(in1_input, S_IRUGO, show_in_input, NULL, 1), + SENSOR_ATTR(in2_input, S_IRUGO, show_in_input, NULL, 2), + SENSOR_ATTR(in3_input, S_IRUGO, show_in_input, NULL, 3), + SENSOR_ATTR(in4_input, S_IRUGO, show_in_input, NULL, 4), + SENSOR_ATTR(in5_input, S_IRUGO, show_in_input, NULL, 5), + SENSOR_ATTR(in6_input, S_IRUGO, show_in_input, NULL, 6), + SENSOR_ATTR(in7_input, S_IRUGO, show_in_input, NULL, 7), + SENSOR_ATTR(in8_input, S_IRUGO, show_in_input, NULL, 8), + SENSOR_ATTR(in9_input, S_IRUGO, show_in_input, NULL, 9), + SENSOR_ATTR(in10_input, S_IRUGO, show_in_input, NULL, 10), +}; +static struct sensor_device_attribute in_status[] = { + SENSOR_ATTR(in0_status, S_IRUGO, show_in_status, NULL, 0), + SENSOR_ATTR(in1_status, S_IRUGO, show_in_status, NULL, 1), + SENSOR_ATTR(in2_status, S_IRUGO, show_in_status, NULL, 2), + SENSOR_ATTR(in3_status, S_IRUGO, show_in_status, NULL, 3), + SENSOR_ATTR(in4_status, S_IRUGO, show_in_status, NULL, 4), + SENSOR_ATTR(in5_status, S_IRUGO, show_in_status, NULL, 5), + SENSOR_ATTR(in6_status, S_IRUGO, show_in_status, NULL, 6), + SENSOR_ATTR(in7_status, S_IRUGO, show_in_status, NULL, 7), + SENSOR_ATTR(in8_status, S_IRUGO, show_in_status, NULL, 8), + SENSOR_ATTR(in9_status, S_IRUGO, show_in_status, NULL, 9), + SENSOR_ATTR(in10_status, S_IRUGO, show_in_status, NULL, 10), +}; +static struct sensor_device_attribute in_min[] = { + SENSOR_ATTR(in0_min, S_IWUSR | S_IRUGO, show_in_min, set_in_min, 0), + SENSOR_ATTR(in1_min, S_IWUSR | S_IRUGO, show_in_min, set_in_min, 1), + SENSOR_ATTR(in2_min, S_IWUSR | S_IRUGO, show_in_min, set_in_min, 2), + SENSOR_ATTR(in3_min, S_IWUSR | S_IRUGO, show_in_min, set_in_min, 3), + SENSOR_ATTR(in4_min, S_IWUSR | S_IRUGO, show_in_min, set_in_min, 4), + SENSOR_ATTR(in5_min, S_IWUSR | S_IRUGO, show_in_min, set_in_min, 5), + SENSOR_ATTR(in6_min, S_IWUSR | S_IRUGO, show_in_min, set_in_min, 6), + SENSOR_ATTR(in7_min, S_IWUSR | S_IRUGO, show_in_min, set_in_min, 7), + SENSOR_ATTR(in8_min, S_IWUSR | S_IRUGO, show_in_min, set_in_min, 8), + SENSOR_ATTR(in9_min, S_IWUSR | S_IRUGO, show_in_min, set_in_min, 9), + SENSOR_ATTR(in10_min, S_IWUSR | S_IRUGO, show_in_min, set_in_min, 10), +}; +static struct sensor_device_attribute in_max[] = { + SENSOR_ATTR(in0_max, S_IWUSR | S_IRUGO, show_in_max, set_in_max, 0), + SENSOR_ATTR(in1_max, S_IWUSR | S_IRUGO, show_in_max, set_in_max, 1), + SENSOR_ATTR(in2_max, S_IWUSR | S_IRUGO, show_in_max, set_in_max, 2), + SENSOR_ATTR(in3_max, S_IWUSR | S_IRUGO, show_in_max, set_in_max, 3), + SENSOR_ATTR(in4_max, S_IWUSR | S_IRUGO, show_in_max, set_in_max, 4), + SENSOR_ATTR(in5_max, S_IWUSR | S_IRUGO, show_in_max, set_in_max, 5), + SENSOR_ATTR(in6_max, S_IWUSR | S_IRUGO, show_in_max, set_in_max, 6), + SENSOR_ATTR(in7_max, S_IWUSR | S_IRUGO, show_in_max, set_in_max, 7), + SENSOR_ATTR(in8_max, S_IWUSR | S_IRUGO, show_in_max, set_in_max, 8), + SENSOR_ATTR(in9_max, S_IWUSR | S_IRUGO, show_in_max, set_in_max, 9), + SENSOR_ATTR(in10_max, S_IWUSR | S_IRUGO, show_in_max, set_in_max, 10), +}; + +/* (temp & vin) channel status register alarm bits (pdf sec.11.5.12) */ +#define CHAN_ALM_MIN 0x02 /* min limit crossed */ +#define CHAN_ALM_MAX 0x04 /* max limit exceeded */ +#define TEMP_ALM_CRIT 0x08 /* temp crit exceeded (temp only) */ + +/* + * show_in_min/max_alarm() reads data from the per-channel status + * register (sec 11.5.12), not the vin event status registers (sec + * 11.5.2) that (legacy) show_in_alarm() resds (via data->in_alarms) + */ + +static ssize_t show_in_min_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct pc87360_data *data = pc87360_update_device(dev); + unsigned nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%u\n", !!(data->in_status[nr] & CHAN_ALM_MIN)); +} +static ssize_t show_in_max_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct pc87360_data *data = pc87360_update_device(dev); + unsigned nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%u\n", !!(data->in_status[nr] & CHAN_ALM_MAX)); +} + +static struct sensor_device_attribute in_min_alarm[] = { + SENSOR_ATTR(in0_min_alarm, S_IRUGO, show_in_min_alarm, NULL, 0), + SENSOR_ATTR(in1_min_alarm, S_IRUGO, show_in_min_alarm, NULL, 1), + SENSOR_ATTR(in2_min_alarm, S_IRUGO, show_in_min_alarm, NULL, 2), + SENSOR_ATTR(in3_min_alarm, S_IRUGO, show_in_min_alarm, NULL, 3), + SENSOR_ATTR(in4_min_alarm, S_IRUGO, show_in_min_alarm, NULL, 4), + SENSOR_ATTR(in5_min_alarm, S_IRUGO, show_in_min_alarm, NULL, 5), + SENSOR_ATTR(in6_min_alarm, S_IRUGO, show_in_min_alarm, NULL, 6), + SENSOR_ATTR(in7_min_alarm, S_IRUGO, show_in_min_alarm, NULL, 7), + SENSOR_ATTR(in8_min_alarm, S_IRUGO, show_in_min_alarm, NULL, 8), + SENSOR_ATTR(in9_min_alarm, S_IRUGO, show_in_min_alarm, NULL, 9), + SENSOR_ATTR(in10_min_alarm, S_IRUGO, show_in_min_alarm, NULL, 10), +}; +static struct sensor_device_attribute in_max_alarm[] = { + SENSOR_ATTR(in0_max_alarm, S_IRUGO, show_in_max_alarm, NULL, 0), + SENSOR_ATTR(in1_max_alarm, S_IRUGO, show_in_max_alarm, NULL, 1), + SENSOR_ATTR(in2_max_alarm, S_IRUGO, show_in_max_alarm, NULL, 2), + SENSOR_ATTR(in3_max_alarm, S_IRUGO, show_in_max_alarm, NULL, 3), + SENSOR_ATTR(in4_max_alarm, S_IRUGO, show_in_max_alarm, NULL, 4), + SENSOR_ATTR(in5_max_alarm, S_IRUGO, show_in_max_alarm, NULL, 5), + SENSOR_ATTR(in6_max_alarm, S_IRUGO, show_in_max_alarm, NULL, 6), + SENSOR_ATTR(in7_max_alarm, S_IRUGO, show_in_max_alarm, NULL, 7), + SENSOR_ATTR(in8_max_alarm, S_IRUGO, show_in_max_alarm, NULL, 8), + SENSOR_ATTR(in9_max_alarm, S_IRUGO, show_in_max_alarm, NULL, 9), + SENSOR_ATTR(in10_max_alarm, S_IRUGO, show_in_max_alarm, NULL, 10), +}; + +#define VIN_UNIT_ATTRS(X) \ + &in_input[X].dev_attr.attr, \ + &in_status[X].dev_attr.attr, \ + &in_min[X].dev_attr.attr, \ + &in_max[X].dev_attr.attr, \ + &in_min_alarm[X].dev_attr.attr, \ + &in_max_alarm[X].dev_attr.attr + +static ssize_t cpu0_vid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", vid_from_reg(data->vid, data->vrm)); +} +static DEVICE_ATTR_RO(cpu0_vid); + +static ssize_t vrm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pc87360_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%u\n", data->vrm); +} +static ssize_t vrm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pc87360_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 255) + return -EINVAL; + + data->vrm = val; + return count; +} +static DEVICE_ATTR_RW(vrm); + +static ssize_t alarms_in_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", data->in_alarms); +} +static DEVICE_ATTR_RO(alarms_in); + +static struct attribute *pc8736x_vin_attr_array[] = { + VIN_UNIT_ATTRS(0), + VIN_UNIT_ATTRS(1), + VIN_UNIT_ATTRS(2), + VIN_UNIT_ATTRS(3), + VIN_UNIT_ATTRS(4), + VIN_UNIT_ATTRS(5), + VIN_UNIT_ATTRS(6), + VIN_UNIT_ATTRS(7), + VIN_UNIT_ATTRS(8), + VIN_UNIT_ATTRS(9), + VIN_UNIT_ATTRS(10), + &dev_attr_cpu0_vid.attr, + &dev_attr_vrm.attr, + &dev_attr_alarms_in.attr, + NULL +}; +static const struct attribute_group pc8736x_vin_group = { + .attrs = pc8736x_vin_attr_array, +}; + +static ssize_t show_therm_input(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", IN_FROM_REG(data->in[attr->index], + data->in_vref)); +} +static ssize_t show_therm_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", IN_FROM_REG(data->in_min[attr->index], + data->in_vref)); +} +static ssize_t show_therm_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", IN_FROM_REG(data->in_max[attr->index], + data->in_vref)); +} +static ssize_t show_therm_crit(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", IN_FROM_REG(data->in_crit[attr->index-11], + data->in_vref)); +} +static ssize_t show_therm_status(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", data->in_status[attr->index]); +} + +static ssize_t set_therm_min(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_min[attr->index] = IN_TO_REG(val, data->in_vref); + pc87360_write_value(data, LD_IN, attr->index, PC87365_REG_TEMP_MIN, + data->in_min[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_therm_max(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_max[attr->index] = IN_TO_REG(val, data->in_vref); + pc87360_write_value(data, LD_IN, attr->index, PC87365_REG_TEMP_MAX, + data->in_max[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t set_therm_crit(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_crit[attr->index-11] = IN_TO_REG(val, data->in_vref); + pc87360_write_value(data, LD_IN, attr->index, PC87365_REG_TEMP_CRIT, + data->in_crit[attr->index-11]); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * the +11 term below reflects the fact that VLM units 11,12,13 are + * used in the chip to measure voltage across the thermistors + */ +static struct sensor_device_attribute therm_input[] = { + SENSOR_ATTR(temp4_input, S_IRUGO, show_therm_input, NULL, 0 + 11), + SENSOR_ATTR(temp5_input, S_IRUGO, show_therm_input, NULL, 1 + 11), + SENSOR_ATTR(temp6_input, S_IRUGO, show_therm_input, NULL, 2 + 11), +}; +static struct sensor_device_attribute therm_status[] = { + SENSOR_ATTR(temp4_status, S_IRUGO, show_therm_status, NULL, 0 + 11), + SENSOR_ATTR(temp5_status, S_IRUGO, show_therm_status, NULL, 1 + 11), + SENSOR_ATTR(temp6_status, S_IRUGO, show_therm_status, NULL, 2 + 11), +}; +static struct sensor_device_attribute therm_min[] = { + SENSOR_ATTR(temp4_min, S_IRUGO | S_IWUSR, + show_therm_min, set_therm_min, 0 + 11), + SENSOR_ATTR(temp5_min, S_IRUGO | S_IWUSR, + show_therm_min, set_therm_min, 1 + 11), + SENSOR_ATTR(temp6_min, S_IRUGO | S_IWUSR, + show_therm_min, set_therm_min, 2 + 11), +}; +static struct sensor_device_attribute therm_max[] = { + SENSOR_ATTR(temp4_max, S_IRUGO | S_IWUSR, + show_therm_max, set_therm_max, 0 + 11), + SENSOR_ATTR(temp5_max, S_IRUGO | S_IWUSR, + show_therm_max, set_therm_max, 1 + 11), + SENSOR_ATTR(temp6_max, S_IRUGO | S_IWUSR, + show_therm_max, set_therm_max, 2 + 11), +}; +static struct sensor_device_attribute therm_crit[] = { + SENSOR_ATTR(temp4_crit, S_IRUGO | S_IWUSR, + show_therm_crit, set_therm_crit, 0 + 11), + SENSOR_ATTR(temp5_crit, S_IRUGO | S_IWUSR, + show_therm_crit, set_therm_crit, 1 + 11), + SENSOR_ATTR(temp6_crit, S_IRUGO | S_IWUSR, + show_therm_crit, set_therm_crit, 2 + 11), +}; + +/* + * show_therm_min/max_alarm() reads data from the per-channel voltage + * status register (sec 11.5.12) + */ + +static ssize_t show_therm_min_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct pc87360_data *data = pc87360_update_device(dev); + unsigned nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%u\n", !!(data->in_status[nr] & CHAN_ALM_MIN)); +} +static ssize_t show_therm_max_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct pc87360_data *data = pc87360_update_device(dev); + unsigned nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%u\n", !!(data->in_status[nr] & CHAN_ALM_MAX)); +} +static ssize_t show_therm_crit_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct pc87360_data *data = pc87360_update_device(dev); + unsigned nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%u\n", !!(data->in_status[nr] & TEMP_ALM_CRIT)); +} + +static struct sensor_device_attribute therm_min_alarm[] = { + SENSOR_ATTR(temp4_min_alarm, S_IRUGO, + show_therm_min_alarm, NULL, 0 + 11), + SENSOR_ATTR(temp5_min_alarm, S_IRUGO, + show_therm_min_alarm, NULL, 1 + 11), + SENSOR_ATTR(temp6_min_alarm, S_IRUGO, + show_therm_min_alarm, NULL, 2 + 11), +}; +static struct sensor_device_attribute therm_max_alarm[] = { + SENSOR_ATTR(temp4_max_alarm, S_IRUGO, + show_therm_max_alarm, NULL, 0 + 11), + SENSOR_ATTR(temp5_max_alarm, S_IRUGO, + show_therm_max_alarm, NULL, 1 + 11), + SENSOR_ATTR(temp6_max_alarm, S_IRUGO, + show_therm_max_alarm, NULL, 2 + 11), +}; +static struct sensor_device_attribute therm_crit_alarm[] = { + SENSOR_ATTR(temp4_crit_alarm, S_IRUGO, + show_therm_crit_alarm, NULL, 0 + 11), + SENSOR_ATTR(temp5_crit_alarm, S_IRUGO, + show_therm_crit_alarm, NULL, 1 + 11), + SENSOR_ATTR(temp6_crit_alarm, S_IRUGO, + show_therm_crit_alarm, NULL, 2 + 11), +}; + +#define THERM_UNIT_ATTRS(X) \ + &therm_input[X].dev_attr.attr, \ + &therm_status[X].dev_attr.attr, \ + &therm_min[X].dev_attr.attr, \ + &therm_max[X].dev_attr.attr, \ + &therm_crit[X].dev_attr.attr, \ + &therm_min_alarm[X].dev_attr.attr, \ + &therm_max_alarm[X].dev_attr.attr, \ + &therm_crit_alarm[X].dev_attr.attr + +static struct attribute *pc8736x_therm_attr_array[] = { + THERM_UNIT_ATTRS(0), + THERM_UNIT_ATTRS(1), + THERM_UNIT_ATTRS(2), + NULL +}; +static const struct attribute_group pc8736x_therm_group = { + .attrs = pc8736x_therm_attr_array, +}; + +static ssize_t show_temp_input(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index])); +} + +static ssize_t show_temp_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_min[attr->index])); +} + +static ssize_t show_temp_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max[attr->index])); +} + +static ssize_t show_temp_crit(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%d\n", + TEMP_FROM_REG(data->temp_crit[attr->index])); +} + +static ssize_t show_temp_status(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%d\n", data->temp_status[attr->index]); +} + +static ssize_t set_temp_min(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_min[attr->index] = TEMP_TO_REG(val); + pc87360_write_value(data, LD_TEMP, attr->index, PC87365_REG_TEMP_MIN, + data->temp_min[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_temp_max(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_max[attr->index] = TEMP_TO_REG(val); + pc87360_write_value(data, LD_TEMP, attr->index, PC87365_REG_TEMP_MAX, + data->temp_max[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_temp_crit(struct device *dev, + struct device_attribute *devattr, const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pc87360_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_crit[attr->index] = TEMP_TO_REG(val); + pc87360_write_value(data, LD_TEMP, attr->index, PC87365_REG_TEMP_CRIT, + data->temp_crit[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static struct sensor_device_attribute temp_input[] = { + SENSOR_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL, 0), + SENSOR_ATTR(temp2_input, S_IRUGO, show_temp_input, NULL, 1), + SENSOR_ATTR(temp3_input, S_IRUGO, show_temp_input, NULL, 2), +}; +static struct sensor_device_attribute temp_status[] = { + SENSOR_ATTR(temp1_status, S_IRUGO, show_temp_status, NULL, 0), + SENSOR_ATTR(temp2_status, S_IRUGO, show_temp_status, NULL, 1), + SENSOR_ATTR(temp3_status, S_IRUGO, show_temp_status, NULL, 2), +}; +static struct sensor_device_attribute temp_min[] = { + SENSOR_ATTR(temp1_min, S_IRUGO | S_IWUSR, + show_temp_min, set_temp_min, 0), + SENSOR_ATTR(temp2_min, S_IRUGO | S_IWUSR, + show_temp_min, set_temp_min, 1), + SENSOR_ATTR(temp3_min, S_IRUGO | S_IWUSR, + show_temp_min, set_temp_min, 2), +}; +static struct sensor_device_attribute temp_max[] = { + SENSOR_ATTR(temp1_max, S_IRUGO | S_IWUSR, + show_temp_max, set_temp_max, 0), + SENSOR_ATTR(temp2_max, S_IRUGO | S_IWUSR, + show_temp_max, set_temp_max, 1), + SENSOR_ATTR(temp3_max, S_IRUGO | S_IWUSR, + show_temp_max, set_temp_max, 2), +}; +static struct sensor_device_attribute temp_crit[] = { + SENSOR_ATTR(temp1_crit, S_IRUGO | S_IWUSR, + show_temp_crit, set_temp_crit, 0), + SENSOR_ATTR(temp2_crit, S_IRUGO | S_IWUSR, + show_temp_crit, set_temp_crit, 1), + SENSOR_ATTR(temp3_crit, S_IRUGO | S_IWUSR, + show_temp_crit, set_temp_crit, 2), +}; + +static ssize_t alarms_temp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pc87360_data *data = pc87360_update_device(dev); + return sprintf(buf, "%u\n", data->temp_alarms); +} + +static DEVICE_ATTR_RO(alarms_temp); + +/* + * show_temp_min/max_alarm() reads data from the per-channel status + * register (sec 12.3.7), not the temp event status registers (sec + * 12.3.2) that show_temp_alarm() reads (via data->temp_alarms) + */ + +static ssize_t show_temp_min_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct pc87360_data *data = pc87360_update_device(dev); + unsigned nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%u\n", !!(data->temp_status[nr] & CHAN_ALM_MIN)); +} + +static ssize_t show_temp_max_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct pc87360_data *data = pc87360_update_device(dev); + unsigned nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%u\n", !!(data->temp_status[nr] & CHAN_ALM_MAX)); +} + +static ssize_t show_temp_crit_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct pc87360_data *data = pc87360_update_device(dev); + unsigned nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%u\n", !!(data->temp_status[nr] & TEMP_ALM_CRIT)); +} + +static struct sensor_device_attribute temp_min_alarm[] = { + SENSOR_ATTR(temp1_min_alarm, S_IRUGO, show_temp_min_alarm, NULL, 0), + SENSOR_ATTR(temp2_min_alarm, S_IRUGO, show_temp_min_alarm, NULL, 1), + SENSOR_ATTR(temp3_min_alarm, S_IRUGO, show_temp_min_alarm, NULL, 2), +}; + +static struct sensor_device_attribute temp_max_alarm[] = { + SENSOR_ATTR(temp1_max_alarm, S_IRUGO, show_temp_max_alarm, NULL, 0), + SENSOR_ATTR(temp2_max_alarm, S_IRUGO, show_temp_max_alarm, NULL, 1), + SENSOR_ATTR(temp3_max_alarm, S_IRUGO, show_temp_max_alarm, NULL, 2), +}; + +static struct sensor_device_attribute temp_crit_alarm[] = { + SENSOR_ATTR(temp1_crit_alarm, S_IRUGO, show_temp_crit_alarm, NULL, 0), + SENSOR_ATTR(temp2_crit_alarm, S_IRUGO, show_temp_crit_alarm, NULL, 1), + SENSOR_ATTR(temp3_crit_alarm, S_IRUGO, show_temp_crit_alarm, NULL, 2), +}; + +#define TEMP_FAULT 0x40 /* open diode */ +static ssize_t show_temp_fault(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct pc87360_data *data = pc87360_update_device(dev); + unsigned nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%u\n", !!(data->temp_status[nr] & TEMP_FAULT)); +} +static struct sensor_device_attribute temp_fault[] = { + SENSOR_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0), + SENSOR_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1), + SENSOR_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2), +}; + +#define TEMP_UNIT_ATTRS(X) \ +{ &temp_input[X].dev_attr.attr, \ + &temp_status[X].dev_attr.attr, \ + &temp_min[X].dev_attr.attr, \ + &temp_max[X].dev_attr.attr, \ + &temp_crit[X].dev_attr.attr, \ + &temp_min_alarm[X].dev_attr.attr, \ + &temp_max_alarm[X].dev_attr.attr, \ + &temp_crit_alarm[X].dev_attr.attr, \ + &temp_fault[X].dev_attr.attr, \ + NULL \ +} + +static struct attribute *pc8736x_temp_attr[][10] = { + TEMP_UNIT_ATTRS(0), + TEMP_UNIT_ATTRS(1), + TEMP_UNIT_ATTRS(2) +}; + +static const struct attribute_group pc8736x_temp_attr_group[] = { + { .attrs = pc8736x_temp_attr[0] }, + { .attrs = pc8736x_temp_attr[1] }, + { .attrs = pc8736x_temp_attr[2] } +}; + +static ssize_t name_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct pc87360_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", data->name); +} + +static DEVICE_ATTR_RO(name); + +/* + * Device detection, registration and update + */ + +static int __init pc87360_find(int sioaddr, u8 *devid, + unsigned short *addresses) +{ + u16 val; + int i; + int nrdev; /* logical device count */ + + /* No superio_enter */ + + /* Identify device */ + val = force_id ? force_id : superio_inb(sioaddr, DEVID); + switch (val) { + case 0xE1: /* PC87360 */ + case 0xE8: /* PC87363 */ + case 0xE4: /* PC87364 */ + nrdev = 1; + break; + case 0xE5: /* PC87365 */ + case 0xE9: /* PC87366 */ + nrdev = 3; + break; + default: + superio_exit(sioaddr); + return -ENODEV; + } + /* Remember the device id */ + *devid = val; + + for (i = 0; i < nrdev; i++) { + /* select logical device */ + superio_outb(sioaddr, DEV, logdev[i]); + + val = superio_inb(sioaddr, ACT); + if (!(val & 0x01)) { + pr_info("Device 0x%02x not activated\n", logdev[i]); + continue; + } + + val = (superio_inb(sioaddr, BASE) << 8) + | superio_inb(sioaddr, BASE + 1); + if (!val) { + pr_info("Base address not set for device 0x%02x\n", + logdev[i]); + continue; + } + + addresses[i] = val; + + if (i == 0) { /* Fans */ + confreg[0] = superio_inb(sioaddr, 0xF0); + confreg[1] = superio_inb(sioaddr, 0xF1); + + pr_debug("Fan %d: mon=%d ctrl=%d inv=%d\n", 1, + (confreg[0] >> 2) & 1, (confreg[0] >> 3) & 1, + (confreg[0] >> 4) & 1); + pr_debug("Fan %d: mon=%d ctrl=%d inv=%d\n", 2, + (confreg[0] >> 5) & 1, (confreg[0] >> 6) & 1, + (confreg[0] >> 7) & 1); + pr_debug("Fan %d: mon=%d ctrl=%d inv=%d\n", 3, + confreg[1] & 1, (confreg[1] >> 1) & 1, + (confreg[1] >> 2) & 1); + } else if (i == 1) { /* Voltages */ + /* Are we using thermistors? */ + if (*devid == 0xE9) { /* PC87366 */ + /* + * These registers are not logical-device + * specific, just that we won't need them if + * we don't use the VLM device + */ + confreg[2] = superio_inb(sioaddr, 0x2B); + confreg[3] = superio_inb(sioaddr, 0x25); + + if (confreg[2] & 0x40) { + pr_info("Using thermistors for temperature monitoring\n"); + } + if (confreg[3] & 0xE0) { + pr_info("VID inputs routed (mode %u)\n", + confreg[3] >> 5); + } + } + } + } + + superio_exit(sioaddr); + return 0; +} + +static void pc87360_remove_files(struct device *dev) +{ + int i; + + device_remove_file(dev, &dev_attr_name); + device_remove_file(dev, &dev_attr_alarms_temp); + for (i = 0; i < ARRAY_SIZE(pc8736x_temp_attr_group); i++) + sysfs_remove_group(&dev->kobj, &pc8736x_temp_attr_group[i]); + for (i = 0; i < ARRAY_SIZE(pc8736x_fan_attr_group); i++) { + sysfs_remove_group(&pdev->dev.kobj, &pc8736x_fan_attr_group[i]); + device_remove_file(dev, &pwm[i].dev_attr); + } + sysfs_remove_group(&dev->kobj, &pc8736x_therm_group); + sysfs_remove_group(&dev->kobj, &pc8736x_vin_group); +} + +static int pc87360_probe(struct platform_device *pdev) +{ + int i; + struct pc87360_data *data; + int err = 0; + const char *name; + int use_thermistors = 0; + struct device *dev = &pdev->dev; + + data = devm_kzalloc(dev, sizeof(struct pc87360_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + switch (devid) { + default: + name = "pc87360"; + data->fannr = 2; + break; + case 0xe8: + name = "pc87363"; + data->fannr = 2; + break; + case 0xe4: + name = "pc87364"; + data->fannr = 3; + break; + case 0xe5: + name = "pc87365"; + data->fannr = extra_isa[0] ? 3 : 0; + data->innr = extra_isa[1] ? 11 : 0; + data->tempnr = extra_isa[2] ? 2 : 0; + break; + case 0xe9: + name = "pc87366"; + data->fannr = extra_isa[0] ? 3 : 0; + data->innr = extra_isa[1] ? 14 : 0; + data->tempnr = extra_isa[2] ? 3 : 0; + break; + } + + data->name = name; + mutex_init(&data->lock); + mutex_init(&data->update_lock); + platform_set_drvdata(pdev, data); + + for (i = 0; i < LDNI_MAX; i++) { + data->address[i] = extra_isa[i]; + if (data->address[i] + && !devm_request_region(dev, extra_isa[i], PC87360_EXTENT, + pc87360_driver.driver.name)) { + dev_err(dev, + "Region 0x%x-0x%x already in use!\n", + extra_isa[i], extra_isa[i]+PC87360_EXTENT-1); + return -EBUSY; + } + } + + /* Retrieve the fans configuration from Super-I/O space */ + if (data->fannr) + data->fan_conf = confreg[0] | (confreg[1] << 8); + + /* + * Use the correct reference voltage + * Unless both the VLM and the TMS logical devices agree to + * use an external Vref, the internal one is used. + */ + if (data->innr) { + i = pc87360_read_value(data, LD_IN, NO_BANK, + PC87365_REG_IN_CONFIG); + if (data->tempnr) { + i &= pc87360_read_value(data, LD_TEMP, NO_BANK, + PC87365_REG_TEMP_CONFIG); + } + data->in_vref = (i&0x02) ? 3025 : 2966; + dev_dbg(dev, "Using %s reference voltage\n", + (i&0x02) ? "external" : "internal"); + + data->vid_conf = confreg[3]; + data->vrm = vid_which_vrm(); + } + + /* Fan clock dividers may be needed before any data is read */ + for (i = 0; i < data->fannr; i++) { + if (FAN_CONFIG_MONITOR(data->fan_conf, i)) + data->fan_status[i] = pc87360_read_value(data, + LD_FAN, NO_BANK, + PC87360_REG_FAN_STATUS(i)); + } + + if (init > 0) { + if (devid == 0xe9 && data->address[1]) /* PC87366 */ + use_thermistors = confreg[2] & 0x40; + + pc87360_init_device(pdev, use_thermistors); + } + + /* Register all-or-nothing sysfs groups */ + + if (data->innr) { + err = sysfs_create_group(&dev->kobj, &pc8736x_vin_group); + if (err) + goto error; + } + + if (data->innr == 14) { + err = sysfs_create_group(&dev->kobj, &pc8736x_therm_group); + if (err) + goto error; + } + + /* create device attr-files for varying sysfs groups */ + + if (data->tempnr) { + for (i = 0; i < data->tempnr; i++) { + err = sysfs_create_group(&dev->kobj, + &pc8736x_temp_attr_group[i]); + if (err) + goto error; + } + err = device_create_file(dev, &dev_attr_alarms_temp); + if (err) + goto error; + } + + for (i = 0; i < data->fannr; i++) { + if (FAN_CONFIG_MONITOR(data->fan_conf, i)) { + err = sysfs_create_group(&dev->kobj, + &pc8736x_fan_attr_group[i]); + if (err) + goto error; + } + if (FAN_CONFIG_CONTROL(data->fan_conf, i)) { + err = device_create_file(dev, &pwm[i].dev_attr); + if (err) + goto error; + } + } + + err = device_create_file(dev, &dev_attr_name); + if (err) + goto error; + + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto error; + } + return 0; + +error: + pc87360_remove_files(dev); + return err; +} + +static int pc87360_remove(struct platform_device *pdev) +{ + struct pc87360_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + pc87360_remove_files(&pdev->dev); + + return 0; +} + +/* + * ldi is the logical device index + * bank is for voltages and temperatures only + */ +static int pc87360_read_value(struct pc87360_data *data, u8 ldi, u8 bank, + u8 reg) +{ + int res; + + mutex_lock(&(data->lock)); + if (bank != NO_BANK) + outb_p(bank, data->address[ldi] + PC87365_REG_BANK); + res = inb_p(data->address[ldi] + reg); + mutex_unlock(&(data->lock)); + + return res; +} + +static void pc87360_write_value(struct pc87360_data *data, u8 ldi, u8 bank, + u8 reg, u8 value) +{ + mutex_lock(&(data->lock)); + if (bank != NO_BANK) + outb_p(bank, data->address[ldi] + PC87365_REG_BANK); + outb_p(value, data->address[ldi] + reg); + mutex_unlock(&(data->lock)); +} + +/* (temp & vin) channel conversion status register flags (pdf sec.11.5.12) */ +#define CHAN_CNVRTD 0x80 /* new data ready */ +#define CHAN_ENA 0x01 /* enabled channel (temp or vin) */ +#define CHAN_ALM_ENA 0x10 /* propagate to alarms-reg ?? (chk val!) */ +#define CHAN_READY (CHAN_ENA|CHAN_CNVRTD) /* sample ready mask */ + +#define TEMP_OTS_OE 0x20 /* OTS Output Enable */ +#define VIN_RW1C_MASK (CHAN_READY|CHAN_ALM_MAX|CHAN_ALM_MIN) /* 0x87 */ +#define TEMP_RW1C_MASK (VIN_RW1C_MASK|TEMP_ALM_CRIT|TEMP_FAULT) /* 0xCF */ + +static void pc87360_init_device(struct platform_device *pdev, + int use_thermistors) +{ + struct pc87360_data *data = platform_get_drvdata(pdev); + int i, nr; + const u8 init_in[14] = { 2, 2, 2, 2, 2, 2, 2, 1, 1, 3, 1, 2, 2, 2 }; + const u8 init_temp[3] = { 2, 2, 1 }; + u8 reg; + + if (init >= 2 && data->innr) { + reg = pc87360_read_value(data, LD_IN, NO_BANK, + PC87365_REG_IN_CONVRATE); + dev_info(&pdev->dev, + "VLM conversion set to 1s period, 160us delay\n"); + pc87360_write_value(data, LD_IN, NO_BANK, + PC87365_REG_IN_CONVRATE, + (reg & 0xC0) | 0x11); + } + + nr = data->innr < 11 ? data->innr : 11; + for (i = 0; i < nr; i++) { + reg = pc87360_read_value(data, LD_IN, i, + PC87365_REG_IN_STATUS); + dev_dbg(&pdev->dev, "bios in%d status:0x%02x\n", i, reg); + if (init >= init_in[i]) { + /* Forcibly enable voltage channel */ + if (!(reg & CHAN_ENA)) { + dev_dbg(&pdev->dev, "Forcibly enabling in%d\n", + i); + pc87360_write_value(data, LD_IN, i, + PC87365_REG_IN_STATUS, + (reg & 0x68) | 0x87); + } + } + } + + /* + * We can't blindly trust the Super-I/O space configuration bit, + * most BIOS won't set it properly + */ + dev_dbg(&pdev->dev, "bios thermistors:%d\n", use_thermistors); + for (i = 11; i < data->innr; i++) { + reg = pc87360_read_value(data, LD_IN, i, + PC87365_REG_TEMP_STATUS); + use_thermistors = use_thermistors || (reg & CHAN_ENA); + /* thermistors are temp[4-6], measured on vin[11-14] */ + dev_dbg(&pdev->dev, "bios temp%d_status:0x%02x\n", i-7, reg); + } + dev_dbg(&pdev->dev, "using thermistors:%d\n", use_thermistors); + + i = use_thermistors ? 2 : 0; + for (; i < data->tempnr; i++) { + reg = pc87360_read_value(data, LD_TEMP, i, + PC87365_REG_TEMP_STATUS); + dev_dbg(&pdev->dev, "bios temp%d_status:0x%02x\n", i + 1, reg); + if (init >= init_temp[i]) { + /* Forcibly enable temperature channel */ + if (!(reg & CHAN_ENA)) { + dev_dbg(&pdev->dev, + "Forcibly enabling temp%d\n", i + 1); + pc87360_write_value(data, LD_TEMP, i, + PC87365_REG_TEMP_STATUS, + 0xCF); + } + } + } + + if (use_thermistors) { + for (i = 11; i < data->innr; i++) { + if (init >= init_in[i]) { + /* + * The pin may already be used by thermal + * diodes + */ + reg = pc87360_read_value(data, LD_TEMP, + (i - 11) / 2, PC87365_REG_TEMP_STATUS); + if (reg & CHAN_ENA) { + dev_dbg(&pdev->dev, + "Skipping temp%d, pin already in use by temp%d\n", + i - 7, (i - 11) / 2); + continue; + } + + /* Forcibly enable thermistor channel */ + reg = pc87360_read_value(data, LD_IN, i, + PC87365_REG_IN_STATUS); + if (!(reg & CHAN_ENA)) { + dev_dbg(&pdev->dev, + "Forcibly enabling temp%d\n", + i - 7); + pc87360_write_value(data, LD_IN, i, + PC87365_REG_TEMP_STATUS, + (reg & 0x60) | 0x8F); + } + } + } + } + + if (data->innr) { + reg = pc87360_read_value(data, LD_IN, NO_BANK, + PC87365_REG_IN_CONFIG); + dev_dbg(&pdev->dev, "bios vin-cfg:0x%02x\n", reg); + if (reg & CHAN_ENA) { + dev_dbg(&pdev->dev, + "Forcibly enabling monitoring (VLM)\n"); + pc87360_write_value(data, LD_IN, NO_BANK, + PC87365_REG_IN_CONFIG, + reg & 0xFE); + } + } + + if (data->tempnr) { + reg = pc87360_read_value(data, LD_TEMP, NO_BANK, + PC87365_REG_TEMP_CONFIG); + dev_dbg(&pdev->dev, "bios temp-cfg:0x%02x\n", reg); + if (reg & CHAN_ENA) { + dev_dbg(&pdev->dev, + "Forcibly enabling monitoring (TMS)\n"); + pc87360_write_value(data, LD_TEMP, NO_BANK, + PC87365_REG_TEMP_CONFIG, + reg & 0xFE); + } + + if (init >= 2) { + /* Chip config as documented by National Semi. */ + pc87360_write_value(data, LD_TEMP, 0xF, 0xA, 0x08); + /* + * We voluntarily omit the bank here, in case the + * sequence itself matters. It shouldn't be a problem, + * since nobody else is supposed to access the + * device at that point. + */ + pc87360_write_value(data, LD_TEMP, NO_BANK, 0xB, 0x04); + pc87360_write_value(data, LD_TEMP, NO_BANK, 0xC, 0x35); + pc87360_write_value(data, LD_TEMP, NO_BANK, 0xD, 0x05); + pc87360_write_value(data, LD_TEMP, NO_BANK, 0xE, 0x05); + } + } +} + +static void pc87360_autodiv(struct device *dev, int nr) +{ + struct pc87360_data *data = dev_get_drvdata(dev); + u8 old_min = data->fan_min[nr]; + + /* Increase clock divider if needed and possible */ + if ((data->fan_status[nr] & 0x04) /* overflow flag */ + || (data->fan[nr] >= 224)) { /* next to overflow */ + if ((data->fan_status[nr] & 0x60) != 0x60) { + data->fan_status[nr] += 0x20; + data->fan_min[nr] >>= 1; + data->fan[nr] >>= 1; + dev_dbg(dev, + "Increasing clock divider to %d for fan %d\n", + FAN_DIV_FROM_REG(data->fan_status[nr]), nr + 1); + } + } else { + /* Decrease clock divider if possible */ + while (!(data->fan_min[nr] & 0x80) /* min "nails" divider */ + && data->fan[nr] < 85 /* bad accuracy */ + && (data->fan_status[nr] & 0x60) != 0x00) { + data->fan_status[nr] -= 0x20; + data->fan_min[nr] <<= 1; + data->fan[nr] <<= 1; + dev_dbg(dev, + "Decreasing clock divider to %d for fan %d\n", + FAN_DIV_FROM_REG(data->fan_status[nr]), + nr + 1); + } + } + + /* Write new fan min if it changed */ + if (old_min != data->fan_min[nr]) { + pc87360_write_value(data, LD_FAN, NO_BANK, + PC87360_REG_FAN_MIN(nr), + data->fan_min[nr]); + } +} + +static struct pc87360_data *pc87360_update_device(struct device *dev) +{ + struct pc87360_data *data = dev_get_drvdata(dev); + u8 i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ * 2) || !data->valid) { + dev_dbg(dev, "Data update\n"); + + /* Fans */ + for (i = 0; i < data->fannr; i++) { + if (FAN_CONFIG_MONITOR(data->fan_conf, i)) { + data->fan_status[i] = + pc87360_read_value(data, LD_FAN, + NO_BANK, PC87360_REG_FAN_STATUS(i)); + data->fan[i] = pc87360_read_value(data, LD_FAN, + NO_BANK, PC87360_REG_FAN(i)); + data->fan_min[i] = pc87360_read_value(data, + LD_FAN, NO_BANK, + PC87360_REG_FAN_MIN(i)); + /* Change clock divider if needed */ + pc87360_autodiv(dev, i); + /* Clear bits and write new divider */ + pc87360_write_value(data, LD_FAN, NO_BANK, + PC87360_REG_FAN_STATUS(i), + data->fan_status[i]); + } + if (FAN_CONFIG_CONTROL(data->fan_conf, i)) + data->pwm[i] = pc87360_read_value(data, LD_FAN, + NO_BANK, PC87360_REG_PWM(i)); + } + + /* Voltages */ + for (i = 0; i < data->innr; i++) { + data->in_status[i] = pc87360_read_value(data, LD_IN, i, + PC87365_REG_IN_STATUS); + /* Clear bits */ + pc87360_write_value(data, LD_IN, i, + PC87365_REG_IN_STATUS, + data->in_status[i]); + if ((data->in_status[i] & CHAN_READY) == CHAN_READY) { + data->in[i] = pc87360_read_value(data, LD_IN, + i, PC87365_REG_IN); + } + if (data->in_status[i] & CHAN_ENA) { + data->in_min[i] = pc87360_read_value(data, + LD_IN, i, + PC87365_REG_IN_MIN); + data->in_max[i] = pc87360_read_value(data, + LD_IN, i, + PC87365_REG_IN_MAX); + if (i >= 11) + data->in_crit[i-11] = + pc87360_read_value(data, LD_IN, + i, PC87365_REG_TEMP_CRIT); + } + } + if (data->innr) { + data->in_alarms = pc87360_read_value(data, LD_IN, + NO_BANK, PC87365_REG_IN_ALARMS1) + | ((pc87360_read_value(data, LD_IN, + NO_BANK, PC87365_REG_IN_ALARMS2) + & 0x07) << 8); + data->vid = (data->vid_conf & 0xE0) ? + pc87360_read_value(data, LD_IN, + NO_BANK, PC87365_REG_VID) : 0x1F; + } + + /* Temperatures */ + for (i = 0; i < data->tempnr; i++) { + data->temp_status[i] = pc87360_read_value(data, + LD_TEMP, i, + PC87365_REG_TEMP_STATUS); + /* Clear bits */ + pc87360_write_value(data, LD_TEMP, i, + PC87365_REG_TEMP_STATUS, + data->temp_status[i]); + if ((data->temp_status[i] & CHAN_READY) == CHAN_READY) { + data->temp[i] = pc87360_read_value(data, + LD_TEMP, i, + PC87365_REG_TEMP); + } + if (data->temp_status[i] & CHAN_ENA) { + data->temp_min[i] = pc87360_read_value(data, + LD_TEMP, i, + PC87365_REG_TEMP_MIN); + data->temp_max[i] = pc87360_read_value(data, + LD_TEMP, i, + PC87365_REG_TEMP_MAX); + data->temp_crit[i] = pc87360_read_value(data, + LD_TEMP, i, + PC87365_REG_TEMP_CRIT); + } + } + if (data->tempnr) { + data->temp_alarms = pc87360_read_value(data, LD_TEMP, + NO_BANK, PC87365_REG_TEMP_ALARMS) + & 0x3F; + } + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static int __init pc87360_device_add(unsigned short address) +{ + struct resource res[3]; + int err, i, res_count; + + pdev = platform_device_alloc("pc87360", address); + if (!pdev) { + err = -ENOMEM; + pr_err("Device allocation failed\n"); + goto exit; + } + + memset(res, 0, 3 * sizeof(struct resource)); + res_count = 0; + for (i = 0; i < 3; i++) { + if (!extra_isa[i]) + continue; + res[res_count].start = extra_isa[i]; + res[res_count].end = extra_isa[i] + PC87360_EXTENT - 1; + res[res_count].name = "pc87360", + res[res_count].flags = IORESOURCE_IO, + + err = acpi_check_resource_conflict(&res[res_count]); + if (err) + goto exit_device_put; + + res_count++; + } + + err = platform_device_add_resources(pdev, res, res_count); + if (err) { + pr_err("Device resources addition failed (%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + +static int __init pc87360_init(void) +{ + int err, i; + unsigned short address = 0; + + if (pc87360_find(0x2e, &devid, extra_isa) + && pc87360_find(0x4e, &devid, extra_isa)) { + pr_warn("PC8736x not detected, module not inserted\n"); + return -ENODEV; + } + + /* Arbitrarily pick one of the addresses */ + for (i = 0; i < 3; i++) { + if (extra_isa[i] != 0x0000) { + address = extra_isa[i]; + break; + } + } + + if (address == 0x0000) { + pr_warn("No active logical device, module not inserted\n"); + return -ENODEV; + } + + err = platform_driver_register(&pc87360_driver); + if (err) + goto exit; + + /* Sets global pdev as a side effect */ + err = pc87360_device_add(address); + if (err) + goto exit_driver; + + return 0; + + exit_driver: + platform_driver_unregister(&pc87360_driver); + exit: + return err; +} + +static void __exit pc87360_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&pc87360_driver); +} + + +MODULE_AUTHOR("Jean Delvare "); +MODULE_DESCRIPTION("PC8736x hardware monitor"); +MODULE_LICENSE("GPL"); + +module_init(pc87360_init); +module_exit(pc87360_exit); diff --git a/drivers/hwmon/pc87427.c b/drivers/hwmon/pc87427.c new file mode 100644 index 000000000..81a05cd1a --- /dev/null +++ b/drivers/hwmon/pc87427.c @@ -0,0 +1,1364 @@ +/* + * pc87427.c - hardware monitoring driver for the + * National Semiconductor PC87427 Super-I/O chip + * Copyright (C) 2006, 2008, 2010 Jean Delvare + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Supports the following chips: + * + * Chip #vin #fan #pwm #temp devid + * PC87427 - 8 4 6 0xF2 + * + * This driver assumes that no more than one chip is present. + * Only fans are fully supported so far. Temperatures are in read-only + * mode, and voltages aren't supported at all. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +static struct platform_device *pdev; + +#define DRVNAME "pc87427" + +/* + * The lock mutex protects both the I/O accesses (needed because the + * device is using banked registers) and the register cache (needed to keep + * the data in the registers and the cache in sync at any time). + */ +struct pc87427_data { + struct device *hwmon_dev; + struct mutex lock; + int address[2]; + const char *name; + + unsigned long last_updated; /* in jiffies */ + u8 fan_enabled; /* bit vector */ + u16 fan[8]; /* register values */ + u16 fan_min[8]; /* register values */ + u8 fan_status[8]; /* register values */ + + u8 pwm_enabled; /* bit vector */ + u8 pwm_auto_ok; /* bit vector */ + u8 pwm_enable[4]; /* register values */ + u8 pwm[4]; /* register values */ + + u8 temp_enabled; /* bit vector */ + s16 temp[6]; /* register values */ + s8 temp_min[6]; /* register values */ + s8 temp_max[6]; /* register values */ + s8 temp_crit[6]; /* register values */ + u8 temp_status[6]; /* register values */ + u8 temp_type[6]; /* register values */ +}; + +struct pc87427_sio_data { + unsigned short address[2]; + u8 has_fanin; + u8 has_fanout; +}; + +/* + * Super-I/O registers and operations + */ + +#define SIOREG_LDSEL 0x07 /* Logical device select */ +#define SIOREG_DEVID 0x20 /* Device ID */ +#define SIOREG_CF2 0x22 /* Configuration 2 */ +#define SIOREG_CF3 0x23 /* Configuration 3 */ +#define SIOREG_CF4 0x24 /* Configuration 4 */ +#define SIOREG_CF5 0x25 /* Configuration 5 */ +#define SIOREG_CFB 0x2B /* Configuration B */ +#define SIOREG_CFC 0x2C /* Configuration C */ +#define SIOREG_CFD 0x2D /* Configuration D */ +#define SIOREG_ACT 0x30 /* Device activation */ +#define SIOREG_MAP 0x50 /* I/O or memory mapping */ +#define SIOREG_IOBASE 0x60 /* I/O base address */ + +static const u8 logdev[2] = { 0x09, 0x14 }; +static const char *logdev_str[2] = { DRVNAME " FMC", DRVNAME " HMC" }; +#define LD_FAN 0 +#define LD_IN 1 +#define LD_TEMP 1 + +static inline int superio_enter(int sioaddr) +{ + if (!request_muxed_region(sioaddr, 2, DRVNAME)) + return -EBUSY; + return 0; +} + +static inline void superio_outb(int sioaddr, int reg, int val) +{ + outb(reg, sioaddr); + outb(val, sioaddr + 1); +} + +static inline int superio_inb(int sioaddr, int reg) +{ + outb(reg, sioaddr); + return inb(sioaddr + 1); +} + +static inline void superio_exit(int sioaddr) +{ + outb(0x02, sioaddr); + outb(0x02, sioaddr + 1); + release_region(sioaddr, 2); +} + +/* + * Logical devices + */ + +#define REGION_LENGTH 32 +#define PC87427_REG_BANK 0x0f +#define BANK_FM(nr) (nr) +#define BANK_FT(nr) (0x08 + (nr)) +#define BANK_FC(nr) (0x10 + (nr) * 2) +#define BANK_TM(nr) (nr) +#define BANK_VM(nr) (0x08 + (nr)) + +/* + * I/O access functions + */ + +/* ldi is the logical device index */ +static inline int pc87427_read8(struct pc87427_data *data, u8 ldi, u8 reg) +{ + return inb(data->address[ldi] + reg); +} + +/* Must be called with data->lock held, except during init */ +static inline int pc87427_read8_bank(struct pc87427_data *data, u8 ldi, + u8 bank, u8 reg) +{ + outb(bank, data->address[ldi] + PC87427_REG_BANK); + return inb(data->address[ldi] + reg); +} + +/* Must be called with data->lock held, except during init */ +static inline void pc87427_write8_bank(struct pc87427_data *data, u8 ldi, + u8 bank, u8 reg, u8 value) +{ + outb(bank, data->address[ldi] + PC87427_REG_BANK); + outb(value, data->address[ldi] + reg); +} + +/* + * Fan registers and conversions + */ + +/* fan data registers are 16-bit wide */ +#define PC87427_REG_FAN 0x12 +#define PC87427_REG_FAN_MIN 0x14 +#define PC87427_REG_FAN_STATUS 0x10 + +#define FAN_STATUS_STALL (1 << 3) +#define FAN_STATUS_LOSPD (1 << 1) +#define FAN_STATUS_MONEN (1 << 0) + +/* + * Dedicated function to read all registers related to a given fan input. + * This saves us quite a few locks and bank selections. + * Must be called with data->lock held. + * nr is from 0 to 7 + */ +static void pc87427_readall_fan(struct pc87427_data *data, u8 nr) +{ + int iobase = data->address[LD_FAN]; + + outb(BANK_FM(nr), iobase + PC87427_REG_BANK); + data->fan[nr] = inw(iobase + PC87427_REG_FAN); + data->fan_min[nr] = inw(iobase + PC87427_REG_FAN_MIN); + data->fan_status[nr] = inb(iobase + PC87427_REG_FAN_STATUS); + /* Clear fan alarm bits */ + outb(data->fan_status[nr], iobase + PC87427_REG_FAN_STATUS); +} + +/* + * The 2 LSB of fan speed registers are used for something different. + * The actual 2 LSB of the measurements are not available. + */ +static inline unsigned long fan_from_reg(u16 reg) +{ + reg &= 0xfffc; + if (reg == 0x0000 || reg == 0xfffc) + return 0; + return 5400000UL / reg; +} + +/* The 2 LSB of the fan speed limit registers are not significant. */ +static inline u16 fan_to_reg(unsigned long val) +{ + if (val < 83UL) + return 0xffff; + if (val >= 1350000UL) + return 0x0004; + return ((1350000UL + val / 2) / val) << 2; +} + +/* + * PWM registers and conversions + */ + +#define PC87427_REG_PWM_ENABLE 0x10 +#define PC87427_REG_PWM_DUTY 0x12 + +#define PWM_ENABLE_MODE_MASK (7 << 4) +#define PWM_ENABLE_CTLEN (1 << 0) + +#define PWM_MODE_MANUAL (0 << 4) +#define PWM_MODE_AUTO (1 << 4) +#define PWM_MODE_OFF (2 << 4) +#define PWM_MODE_ON (7 << 4) + +/* + * Dedicated function to read all registers related to a given PWM output. + * This saves us quite a few locks and bank selections. + * Must be called with data->lock held. + * nr is from 0 to 3 + */ +static void pc87427_readall_pwm(struct pc87427_data *data, u8 nr) +{ + int iobase = data->address[LD_FAN]; + + outb(BANK_FC(nr), iobase + PC87427_REG_BANK); + data->pwm_enable[nr] = inb(iobase + PC87427_REG_PWM_ENABLE); + data->pwm[nr] = inb(iobase + PC87427_REG_PWM_DUTY); +} + +static inline int pwm_enable_from_reg(u8 reg) +{ + switch (reg & PWM_ENABLE_MODE_MASK) { + case PWM_MODE_ON: + return 0; + case PWM_MODE_MANUAL: + case PWM_MODE_OFF: + return 1; + case PWM_MODE_AUTO: + return 2; + default: + return -EPROTO; + } +} + +static inline u8 pwm_enable_to_reg(unsigned long val, u8 pwmval) +{ + switch (val) { + default: + return PWM_MODE_ON; + case 1: + return pwmval ? PWM_MODE_MANUAL : PWM_MODE_OFF; + case 2: + return PWM_MODE_AUTO; + } +} + +/* + * Temperature registers and conversions + */ + +#define PC87427_REG_TEMP_STATUS 0x10 +#define PC87427_REG_TEMP 0x14 +#define PC87427_REG_TEMP_MAX 0x18 +#define PC87427_REG_TEMP_MIN 0x19 +#define PC87427_REG_TEMP_CRIT 0x1a +#define PC87427_REG_TEMP_TYPE 0x1d + +#define TEMP_STATUS_CHANEN (1 << 0) +#define TEMP_STATUS_LOWFLG (1 << 1) +#define TEMP_STATUS_HIGHFLG (1 << 2) +#define TEMP_STATUS_CRITFLG (1 << 3) +#define TEMP_STATUS_SENSERR (1 << 5) +#define TEMP_TYPE_MASK (3 << 5) + +#define TEMP_TYPE_THERMISTOR (1 << 5) +#define TEMP_TYPE_REMOTE_DIODE (2 << 5) +#define TEMP_TYPE_LOCAL_DIODE (3 << 5) + +/* + * Dedicated function to read all registers related to a given temperature + * input. This saves us quite a few locks and bank selections. + * Must be called with data->lock held. + * nr is from 0 to 5 + */ +static void pc87427_readall_temp(struct pc87427_data *data, u8 nr) +{ + int iobase = data->address[LD_TEMP]; + + outb(BANK_TM(nr), iobase + PC87427_REG_BANK); + data->temp[nr] = le16_to_cpu(inw(iobase + PC87427_REG_TEMP)); + data->temp_max[nr] = inb(iobase + PC87427_REG_TEMP_MAX); + data->temp_min[nr] = inb(iobase + PC87427_REG_TEMP_MIN); + data->temp_crit[nr] = inb(iobase + PC87427_REG_TEMP_CRIT); + data->temp_type[nr] = inb(iobase + PC87427_REG_TEMP_TYPE); + data->temp_status[nr] = inb(iobase + PC87427_REG_TEMP_STATUS); + /* Clear fan alarm bits */ + outb(data->temp_status[nr], iobase + PC87427_REG_TEMP_STATUS); +} + +static inline unsigned int temp_type_from_reg(u8 reg) +{ + switch (reg & TEMP_TYPE_MASK) { + case TEMP_TYPE_THERMISTOR: + return 4; + case TEMP_TYPE_REMOTE_DIODE: + case TEMP_TYPE_LOCAL_DIODE: + return 3; + default: + return 0; + } +} + +/* + * We assume 8-bit thermal sensors; 9-bit thermal sensors are possible + * too, but I have no idea how to figure out when they are used. + */ +static inline long temp_from_reg(s16 reg) +{ + return reg * 1000 / 256; +} + +static inline long temp_from_reg8(s8 reg) +{ + return reg * 1000; +} + +/* + * Data interface + */ + +static struct pc87427_data *pc87427_update_device(struct device *dev) +{ + struct pc87427_data *data = dev_get_drvdata(dev); + int i; + + mutex_lock(&data->lock); + if (!time_after(jiffies, data->last_updated + HZ) + && data->last_updated) + goto done; + + /* Fans */ + for (i = 0; i < 8; i++) { + if (!(data->fan_enabled & (1 << i))) + continue; + pc87427_readall_fan(data, i); + } + + /* PWM outputs */ + for (i = 0; i < 4; i++) { + if (!(data->pwm_enabled & (1 << i))) + continue; + pc87427_readall_pwm(data, i); + } + + /* Temperature channels */ + for (i = 0; i < 6; i++) { + if (!(data->temp_enabled & (1 << i))) + continue; + pc87427_readall_temp(data, i); + } + + data->last_updated = jiffies; + +done: + mutex_unlock(&data->lock); + return data; +} + +static ssize_t show_fan_input(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%lu\n", fan_from_reg(data->fan[nr])); +} + +static ssize_t show_fan_min(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%lu\n", fan_from_reg(data->fan_min[nr])); +} + +static ssize_t show_fan_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%d\n", !!(data->fan_status[nr] + & FAN_STATUS_LOSPD)); +} + +static ssize_t show_fan_fault(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%d\n", !!(data->fan_status[nr] + & FAN_STATUS_STALL)); +} + +static ssize_t set_fan_min(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct pc87427_data *data = dev_get_drvdata(dev); + int nr = to_sensor_dev_attr(devattr)->index; + unsigned long val; + int iobase = data->address[LD_FAN]; + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + + mutex_lock(&data->lock); + outb(BANK_FM(nr), iobase + PC87427_REG_BANK); + /* + * The low speed limit registers are read-only while monitoring + * is enabled, so we have to disable monitoring, then change the + * limit, and finally enable monitoring again. + */ + outb(0, iobase + PC87427_REG_FAN_STATUS); + data->fan_min[nr] = fan_to_reg(val); + outw(data->fan_min[nr], iobase + PC87427_REG_FAN_MIN); + outb(FAN_STATUS_MONEN, iobase + PC87427_REG_FAN_STATUS); + mutex_unlock(&data->lock); + + return count; +} + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_input, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_input, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan_input, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan_input, NULL, 3); +static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, show_fan_input, NULL, 4); +static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, show_fan_input, NULL, 5); +static SENSOR_DEVICE_ATTR(fan7_input, S_IRUGO, show_fan_input, NULL, 6); +static SENSOR_DEVICE_ATTR(fan8_input, S_IRUGO, show_fan_input, NULL, 7); + +static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 0); +static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 1); +static SENSOR_DEVICE_ATTR(fan3_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 2); +static SENSOR_DEVICE_ATTR(fan4_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 3); +static SENSOR_DEVICE_ATTR(fan5_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 4); +static SENSOR_DEVICE_ATTR(fan6_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 5); +static SENSOR_DEVICE_ATTR(fan7_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 6); +static SENSOR_DEVICE_ATTR(fan8_min, S_IWUSR | S_IRUGO, + show_fan_min, set_fan_min, 7); + +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(fan5_alarm, S_IRUGO, show_fan_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(fan6_alarm, S_IRUGO, show_fan_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(fan7_alarm, S_IRUGO, show_fan_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(fan8_alarm, S_IRUGO, show_fan_alarm, NULL, 7); + +static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, show_fan_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, show_fan_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_fault, S_IRUGO, show_fan_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_fault, S_IRUGO, show_fan_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(fan5_fault, S_IRUGO, show_fan_fault, NULL, 4); +static SENSOR_DEVICE_ATTR(fan6_fault, S_IRUGO, show_fan_fault, NULL, 5); +static SENSOR_DEVICE_ATTR(fan7_fault, S_IRUGO, show_fan_fault, NULL, 6); +static SENSOR_DEVICE_ATTR(fan8_fault, S_IRUGO, show_fan_fault, NULL, 7); + +static struct attribute *pc87427_attributes_fan[8][5] = { + { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan4_min.dev_attr.attr, + &sensor_dev_attr_fan4_alarm.dev_attr.attr, + &sensor_dev_attr_fan4_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan5_input.dev_attr.attr, + &sensor_dev_attr_fan5_min.dev_attr.attr, + &sensor_dev_attr_fan5_alarm.dev_attr.attr, + &sensor_dev_attr_fan5_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan6_input.dev_attr.attr, + &sensor_dev_attr_fan6_min.dev_attr.attr, + &sensor_dev_attr_fan6_alarm.dev_attr.attr, + &sensor_dev_attr_fan6_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan7_input.dev_attr.attr, + &sensor_dev_attr_fan7_min.dev_attr.attr, + &sensor_dev_attr_fan7_alarm.dev_attr.attr, + &sensor_dev_attr_fan7_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan8_input.dev_attr.attr, + &sensor_dev_attr_fan8_min.dev_attr.attr, + &sensor_dev_attr_fan8_alarm.dev_attr.attr, + &sensor_dev_attr_fan8_fault.dev_attr.attr, + NULL + } +}; + +static const struct attribute_group pc87427_group_fan[8] = { + { .attrs = pc87427_attributes_fan[0] }, + { .attrs = pc87427_attributes_fan[1] }, + { .attrs = pc87427_attributes_fan[2] }, + { .attrs = pc87427_attributes_fan[3] }, + { .attrs = pc87427_attributes_fan[4] }, + { .attrs = pc87427_attributes_fan[5] }, + { .attrs = pc87427_attributes_fan[6] }, + { .attrs = pc87427_attributes_fan[7] }, +}; + +/* + * Must be called with data->lock held and pc87427_readall_pwm() freshly + * called + */ +static void update_pwm_enable(struct pc87427_data *data, int nr, u8 mode) +{ + int iobase = data->address[LD_FAN]; + data->pwm_enable[nr] &= ~PWM_ENABLE_MODE_MASK; + data->pwm_enable[nr] |= mode; + outb(data->pwm_enable[nr], iobase + PC87427_REG_PWM_ENABLE); +} + +static ssize_t show_pwm_enable(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + int pwm_enable; + + pwm_enable = pwm_enable_from_reg(data->pwm_enable[nr]); + if (pwm_enable < 0) + return pwm_enable; + return sprintf(buf, "%d\n", pwm_enable); +} + +static ssize_t set_pwm_enable(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct pc87427_data *data = dev_get_drvdata(dev); + int nr = to_sensor_dev_attr(devattr)->index; + unsigned long val; + + if (kstrtoul(buf, 10, &val) < 0 || val > 2) + return -EINVAL; + /* Can't go to automatic mode if it isn't configured */ + if (val == 2 && !(data->pwm_auto_ok & (1 << nr))) + return -EINVAL; + + mutex_lock(&data->lock); + pc87427_readall_pwm(data, nr); + update_pwm_enable(data, nr, pwm_enable_to_reg(val, data->pwm[nr])); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_pwm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%d\n", (int)data->pwm[nr]); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct pc87427_data *data = dev_get_drvdata(dev); + int nr = to_sensor_dev_attr(devattr)->index; + unsigned long val; + int iobase = data->address[LD_FAN]; + u8 mode; + + if (kstrtoul(buf, 10, &val) < 0 || val > 0xff) + return -EINVAL; + + mutex_lock(&data->lock); + pc87427_readall_pwm(data, nr); + mode = data->pwm_enable[nr] & PWM_ENABLE_MODE_MASK; + if (mode != PWM_MODE_MANUAL && mode != PWM_MODE_OFF) { + dev_notice(dev, + "Can't set PWM%d duty cycle while not in manual mode\n", + nr + 1); + mutex_unlock(&data->lock); + return -EPERM; + } + + /* We may have to change the mode */ + if (mode == PWM_MODE_MANUAL && val == 0) { + /* Transition from Manual to Off */ + update_pwm_enable(data, nr, PWM_MODE_OFF); + mode = PWM_MODE_OFF; + dev_dbg(dev, "Switching PWM%d from %s to %s\n", nr + 1, + "manual", "off"); + } else if (mode == PWM_MODE_OFF && val != 0) { + /* Transition from Off to Manual */ + update_pwm_enable(data, nr, PWM_MODE_MANUAL); + mode = PWM_MODE_MANUAL; + dev_dbg(dev, "Switching PWM%d from %s to %s\n", nr + 1, + "off", "manual"); + } + + data->pwm[nr] = val; + if (mode == PWM_MODE_MANUAL) + outb(val, iobase + PC87427_REG_PWM_DUTY); + mutex_unlock(&data->lock); + + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + show_pwm_enable, set_pwm_enable, 0); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, + show_pwm_enable, set_pwm_enable, 1); +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, + show_pwm_enable, set_pwm_enable, 2); +static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO, + show_pwm_enable, set_pwm_enable, 3); + +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 2); +static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 3); + +static struct attribute *pc87427_attributes_pwm[4][3] = { + { + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_pwm4_enable.dev_attr.attr, + &sensor_dev_attr_pwm4.dev_attr.attr, + NULL + } +}; + +static const struct attribute_group pc87427_group_pwm[4] = { + { .attrs = pc87427_attributes_pwm[0] }, + { .attrs = pc87427_attributes_pwm[1] }, + { .attrs = pc87427_attributes_pwm[2] }, + { .attrs = pc87427_attributes_pwm[3] }, +}; + +static ssize_t show_temp_input(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%ld\n", temp_from_reg(data->temp[nr])); +} + +static ssize_t show_temp_min(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%ld\n", temp_from_reg8(data->temp_min[nr])); +} + +static ssize_t show_temp_max(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%ld\n", temp_from_reg8(data->temp_max[nr])); +} + +static ssize_t show_temp_crit(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%ld\n", temp_from_reg8(data->temp_crit[nr])); +} + +static ssize_t show_temp_type(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%u\n", temp_type_from_reg(data->temp_type[nr])); +} + +static ssize_t show_temp_min_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%d\n", !!(data->temp_status[nr] + & TEMP_STATUS_LOWFLG)); +} + +static ssize_t show_temp_max_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%d\n", !!(data->temp_status[nr] + & TEMP_STATUS_HIGHFLG)); +} + +static ssize_t show_temp_crit_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%d\n", !!(data->temp_status[nr] + & TEMP_STATUS_CRITFLG)); +} + +static ssize_t show_temp_fault(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%d\n", !!(data->temp_status[nr] + & TEMP_STATUS_SENSERR)); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp_input, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp_input, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp_input, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp_input, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO, show_temp_min, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO, show_temp_min, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_min, S_IRUGO, show_temp_min, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_min, S_IRUGO, show_temp_min, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_min, S_IRUGO, show_temp_min, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_min, S_IRUGO, show_temp_min, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp_max, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO, show_temp_max, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO, show_temp_max, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_max, S_IRUGO, show_temp_max, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_max, S_IRUGO, show_temp_max, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_max, S_IRUGO, show_temp_max, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp_crit, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO, show_temp_crit, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_crit, S_IRUGO, show_temp_crit, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_crit, S_IRUGO, show_temp_crit, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_crit, S_IRUGO, show_temp_crit, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_crit, S_IRUGO, show_temp_crit, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_type, S_IRUGO, show_temp_type, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_type, S_IRUGO, show_temp_type, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_type, S_IRUGO, show_temp_type, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_type, S_IRUGO, show_temp_type, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_type, S_IRUGO, show_temp_type, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, + show_temp_min_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, + show_temp_min_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, + show_temp_min_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, + show_temp_min_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_min_alarm, S_IRUGO, + show_temp_min_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_min_alarm, S_IRUGO, + show_temp_min_alarm, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, + show_temp_max_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, + show_temp_max_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, + show_temp_max_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, + show_temp_max_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_max_alarm, S_IRUGO, + show_temp_max_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_max_alarm, S_IRUGO, + show_temp_max_alarm, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, + show_temp_crit_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, + show_temp_crit_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_crit_alarm, S_IRUGO, + show_temp_crit_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_crit_alarm, S_IRUGO, + show_temp_crit_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_crit_alarm, S_IRUGO, + show_temp_crit_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_crit_alarm, S_IRUGO, + show_temp_crit_alarm, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_temp_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_temp_fault, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_fault, S_IRUGO, show_temp_fault, NULL, 5); + +static struct attribute *pc87427_attributes_temp[6][10] = { + { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_type.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_type.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp3_type.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_crit.dev_attr.attr, + &sensor_dev_attr_temp4_type.dev_attr.attr, + &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp5_min.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp5_crit.dev_attr.attr, + &sensor_dev_attr_temp5_type.dev_attr.attr, + &sensor_dev_attr_temp5_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp6_input.dev_attr.attr, + &sensor_dev_attr_temp6_min.dev_attr.attr, + &sensor_dev_attr_temp6_max.dev_attr.attr, + &sensor_dev_attr_temp6_crit.dev_attr.attr, + &sensor_dev_attr_temp6_type.dev_attr.attr, + &sensor_dev_attr_temp6_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp6_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp6_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp6_fault.dev_attr.attr, + NULL + } +}; + +static const struct attribute_group pc87427_group_temp[6] = { + { .attrs = pc87427_attributes_temp[0] }, + { .attrs = pc87427_attributes_temp[1] }, + { .attrs = pc87427_attributes_temp[2] }, + { .attrs = pc87427_attributes_temp[3] }, + { .attrs = pc87427_attributes_temp[4] }, + { .attrs = pc87427_attributes_temp[5] }, +}; + +static ssize_t name_show(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} +static DEVICE_ATTR_RO(name); + + +/* + * Device detection, attach and detach + */ + +static int pc87427_request_regions(struct platform_device *pdev, + int count) +{ + struct resource *res; + int i; + + for (i = 0; i < count; i++) { + res = platform_get_resource(pdev, IORESOURCE_IO, i); + if (!res) { + dev_err(&pdev->dev, "Missing resource #%d\n", i); + return -ENOENT; + } + if (!devm_request_region(&pdev->dev, res->start, + resource_size(res), DRVNAME)) { + dev_err(&pdev->dev, + "Failed to request region 0x%lx-0x%lx\n", + (unsigned long)res->start, + (unsigned long)res->end); + return -EBUSY; + } + } + return 0; +} + +static void pc87427_init_device(struct device *dev) +{ + struct pc87427_sio_data *sio_data = dev_get_platdata(dev); + struct pc87427_data *data = dev_get_drvdata(dev); + int i; + u8 reg; + + /* The FMC module should be ready */ + reg = pc87427_read8(data, LD_FAN, PC87427_REG_BANK); + if (!(reg & 0x80)) + dev_warn(dev, "%s module not ready!\n", "FMC"); + + /* Check which fans are enabled */ + for (i = 0; i < 8; i++) { + if (!(sio_data->has_fanin & (1 << i))) /* Not wired */ + continue; + reg = pc87427_read8_bank(data, LD_FAN, BANK_FM(i), + PC87427_REG_FAN_STATUS); + if (reg & FAN_STATUS_MONEN) + data->fan_enabled |= (1 << i); + } + + if (!data->fan_enabled) { + dev_dbg(dev, "Enabling monitoring of all fans\n"); + for (i = 0; i < 8; i++) { + if (!(sio_data->has_fanin & (1 << i))) /* Not wired */ + continue; + pc87427_write8_bank(data, LD_FAN, BANK_FM(i), + PC87427_REG_FAN_STATUS, + FAN_STATUS_MONEN); + } + data->fan_enabled = sio_data->has_fanin; + } + + /* Check which PWM outputs are enabled */ + for (i = 0; i < 4; i++) { + if (!(sio_data->has_fanout & (1 << i))) /* Not wired */ + continue; + reg = pc87427_read8_bank(data, LD_FAN, BANK_FC(i), + PC87427_REG_PWM_ENABLE); + if (reg & PWM_ENABLE_CTLEN) + data->pwm_enabled |= (1 << i); + + /* + * We don't expose an interface to reconfigure the automatic + * fan control mode, so only allow to return to this mode if + * it was originally set. + */ + if ((reg & PWM_ENABLE_MODE_MASK) == PWM_MODE_AUTO) { + dev_dbg(dev, "PWM%d is in automatic control mode\n", + i + 1); + data->pwm_auto_ok |= (1 << i); + } + } + + /* The HMC module should be ready */ + reg = pc87427_read8(data, LD_TEMP, PC87427_REG_BANK); + if (!(reg & 0x80)) + dev_warn(dev, "%s module not ready!\n", "HMC"); + + /* Check which temperature channels are enabled */ + for (i = 0; i < 6; i++) { + reg = pc87427_read8_bank(data, LD_TEMP, BANK_TM(i), + PC87427_REG_TEMP_STATUS); + if (reg & TEMP_STATUS_CHANEN) + data->temp_enabled |= (1 << i); + } +} + +static void pc87427_remove_files(struct device *dev) +{ + struct pc87427_data *data = dev_get_drvdata(dev); + int i; + + device_remove_file(dev, &dev_attr_name); + for (i = 0; i < 8; i++) { + if (!(data->fan_enabled & (1 << i))) + continue; + sysfs_remove_group(&dev->kobj, &pc87427_group_fan[i]); + } + for (i = 0; i < 4; i++) { + if (!(data->pwm_enabled & (1 << i))) + continue; + sysfs_remove_group(&dev->kobj, &pc87427_group_pwm[i]); + } + for (i = 0; i < 6; i++) { + if (!(data->temp_enabled & (1 << i))) + continue; + sysfs_remove_group(&dev->kobj, &pc87427_group_temp[i]); + } +} + +static int pc87427_probe(struct platform_device *pdev) +{ + struct pc87427_sio_data *sio_data = dev_get_platdata(&pdev->dev); + struct pc87427_data *data; + int i, err, res_count; + + data = devm_kzalloc(&pdev->dev, sizeof(struct pc87427_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->address[0] = sio_data->address[0]; + data->address[1] = sio_data->address[1]; + res_count = (data->address[0] != 0) + (data->address[1] != 0); + + err = pc87427_request_regions(pdev, res_count); + if (err) + return err; + + mutex_init(&data->lock); + data->name = "pc87427"; + platform_set_drvdata(pdev, data); + pc87427_init_device(&pdev->dev); + + /* Register sysfs hooks */ + err = device_create_file(&pdev->dev, &dev_attr_name); + if (err) + return err; + for (i = 0; i < 8; i++) { + if (!(data->fan_enabled & (1 << i))) + continue; + err = sysfs_create_group(&pdev->dev.kobj, + &pc87427_group_fan[i]); + if (err) + goto exit_remove_files; + } + for (i = 0; i < 4; i++) { + if (!(data->pwm_enabled & (1 << i))) + continue; + err = sysfs_create_group(&pdev->dev.kobj, + &pc87427_group_pwm[i]); + if (err) + goto exit_remove_files; + } + for (i = 0; i < 6; i++) { + if (!(data->temp_enabled & (1 << i))) + continue; + err = sysfs_create_group(&pdev->dev.kobj, + &pc87427_group_temp[i]); + if (err) + goto exit_remove_files; + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", err); + goto exit_remove_files; + } + + return 0; + +exit_remove_files: + pc87427_remove_files(&pdev->dev); + return err; +} + +static int pc87427_remove(struct platform_device *pdev) +{ + struct pc87427_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + pc87427_remove_files(&pdev->dev); + + return 0; +} + + +static struct platform_driver pc87427_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = pc87427_probe, + .remove = pc87427_remove, +}; + +static int __init pc87427_device_add(const struct pc87427_sio_data *sio_data) +{ + struct resource res[2] = { + { .flags = IORESOURCE_IO }, + { .flags = IORESOURCE_IO }, + }; + int err, i, res_count; + + res_count = 0; + for (i = 0; i < 2; i++) { + if (!sio_data->address[i]) + continue; + res[res_count].start = sio_data->address[i]; + res[res_count].end = sio_data->address[i] + REGION_LENGTH - 1; + res[res_count].name = logdev_str[i]; + + err = acpi_check_resource_conflict(&res[res_count]); + if (err) + goto exit; + + res_count++; + } + + pdev = platform_device_alloc(DRVNAME, res[0].start); + if (!pdev) { + err = -ENOMEM; + pr_err("Device allocation failed\n"); + goto exit; + } + + err = platform_device_add_resources(pdev, res, res_count); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add_data(pdev, sio_data, + sizeof(struct pc87427_sio_data)); + if (err) { + pr_err("Platform data allocation failed\n"); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + +static int __init pc87427_find(int sioaddr, struct pc87427_sio_data *sio_data) +{ + u16 val; + u8 cfg, cfg_b; + int i, err; + + err = superio_enter(sioaddr); + if (err) + return err; + + /* Identify device */ + val = force_id ? force_id : superio_inb(sioaddr, SIOREG_DEVID); + if (val != 0xf2) { /* PC87427 */ + err = -ENODEV; + goto exit; + } + + for (i = 0; i < 2; i++) { + sio_data->address[i] = 0; + /* Select logical device */ + superio_outb(sioaddr, SIOREG_LDSEL, logdev[i]); + + val = superio_inb(sioaddr, SIOREG_ACT); + if (!(val & 0x01)) { + pr_info("Logical device 0x%02x not activated\n", + logdev[i]); + continue; + } + + val = superio_inb(sioaddr, SIOREG_MAP); + if (val & 0x01) { + pr_warn("Logical device 0x%02x is memory-mapped, can't use\n", + logdev[i]); + continue; + } + + val = (superio_inb(sioaddr, SIOREG_IOBASE) << 8) + | superio_inb(sioaddr, SIOREG_IOBASE + 1); + if (!val) { + pr_info("I/O base address not set for logical device 0x%02x\n", + logdev[i]); + continue; + } + sio_data->address[i] = val; + } + + /* No point in loading the driver if everything is disabled */ + if (!sio_data->address[0] && !sio_data->address[1]) { + err = -ENODEV; + goto exit; + } + + /* Check which fan inputs are wired */ + sio_data->has_fanin = (1 << 2) | (1 << 3); /* FANIN2, FANIN3 */ + + cfg = superio_inb(sioaddr, SIOREG_CF2); + if (!(cfg & (1 << 3))) + sio_data->has_fanin |= (1 << 0); /* FANIN0 */ + if (!(cfg & (1 << 2))) + sio_data->has_fanin |= (1 << 4); /* FANIN4 */ + + cfg = superio_inb(sioaddr, SIOREG_CFD); + if (!(cfg & (1 << 0))) + sio_data->has_fanin |= (1 << 1); /* FANIN1 */ + + cfg = superio_inb(sioaddr, SIOREG_CF4); + if (!(cfg & (1 << 0))) + sio_data->has_fanin |= (1 << 7); /* FANIN7 */ + cfg_b = superio_inb(sioaddr, SIOREG_CFB); + if (!(cfg & (1 << 1)) && (cfg_b & (1 << 3))) + sio_data->has_fanin |= (1 << 5); /* FANIN5 */ + cfg = superio_inb(sioaddr, SIOREG_CF3); + if ((cfg & (1 << 3)) && !(cfg_b & (1 << 5))) + sio_data->has_fanin |= (1 << 6); /* FANIN6 */ + + /* Check which fan outputs are wired */ + sio_data->has_fanout = (1 << 0); /* FANOUT0 */ + if (cfg_b & (1 << 0)) + sio_data->has_fanout |= (1 << 3); /* FANOUT3 */ + + cfg = superio_inb(sioaddr, SIOREG_CFC); + if (!(cfg & (1 << 4))) { + if (cfg_b & (1 << 1)) + sio_data->has_fanout |= (1 << 1); /* FANOUT1 */ + if (cfg_b & (1 << 2)) + sio_data->has_fanout |= (1 << 2); /* FANOUT2 */ + } + + /* FANOUT1 and FANOUT2 can each be routed to 2 different pins */ + cfg = superio_inb(sioaddr, SIOREG_CF5); + if (cfg & (1 << 6)) + sio_data->has_fanout |= (1 << 1); /* FANOUT1 */ + if (cfg & (1 << 5)) + sio_data->has_fanout |= (1 << 2); /* FANOUT2 */ + +exit: + superio_exit(sioaddr); + return err; +} + +static int __init pc87427_init(void) +{ + int err; + struct pc87427_sio_data sio_data; + + if (pc87427_find(0x2e, &sio_data) + && pc87427_find(0x4e, &sio_data)) + return -ENODEV; + + err = platform_driver_register(&pc87427_driver); + if (err) + goto exit; + + /* Sets global pdev as a side effect */ + err = pc87427_device_add(&sio_data); + if (err) + goto exit_driver; + + return 0; + +exit_driver: + platform_driver_unregister(&pc87427_driver); +exit: + return err; +} + +static void __exit pc87427_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&pc87427_driver); +} + +MODULE_AUTHOR("Jean Delvare "); +MODULE_DESCRIPTION("PC87427 hardware monitoring driver"); +MODULE_LICENSE("GPL"); + +module_init(pc87427_init); +module_exit(pc87427_exit); diff --git a/drivers/hwmon/pcf8591.c b/drivers/hwmon/pcf8591.c new file mode 100644 index 000000000..60e25c85e --- /dev/null +++ b/drivers/hwmon/pcf8591.c @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2001-2004 Aurelien Jarno + * Ported to Linux 2.6 by Aurelien Jarno with + * the help of Jean Delvare + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +/* Insmod parameters */ + +static int input_mode; +module_param(input_mode, int, 0); +MODULE_PARM_DESC(input_mode, + "Analog input mode:\n" + " 0 = four single ended inputs\n" + " 1 = three differential inputs\n" + " 2 = single ended and differential mixed\n" + " 3 = two differential inputs\n"); + +/* + * The PCF8591 control byte + * 7 6 5 4 3 2 1 0 + * | 0 |AOEF| AIP | 0 |AINC| AICH | + */ + +/* Analog Output Enable Flag (analog output active if 1) */ +#define PCF8591_CONTROL_AOEF 0x40 + +/* + * Analog Input Programming + * 0x00 = four single ended inputs + * 0x10 = three differential inputs + * 0x20 = single ended and differential mixed + * 0x30 = two differential inputs + */ +#define PCF8591_CONTROL_AIP_MASK 0x30 + +/* Autoincrement Flag (switch on if 1) */ +#define PCF8591_CONTROL_AINC 0x04 + +/* + * Channel selection + * 0x00 = channel 0 + * 0x01 = channel 1 + * 0x02 = channel 2 + * 0x03 = channel 3 + */ +#define PCF8591_CONTROL_AICH_MASK 0x03 + +/* Initial values */ +#define PCF8591_INIT_CONTROL ((input_mode << 4) | PCF8591_CONTROL_AOEF) +#define PCF8591_INIT_AOUT 0 /* DAC out = 0 */ + +/* Conversions */ +#define REG_TO_SIGNED(reg) (((reg) & 0x80) ? ((reg) - 256) : (reg)) + +struct pcf8591_data { + struct device *hwmon_dev; + struct mutex update_lock; + + u8 control; + u8 aout; +}; + +static void pcf8591_init_client(struct i2c_client *client); +static int pcf8591_read_channel(struct device *dev, int channel); + +/* following are the sysfs callback functions */ +#define show_in_channel(channel) \ +static ssize_t show_in##channel##_input(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + return sprintf(buf, "%d\n", pcf8591_read_channel(dev, channel));\ +} \ +static DEVICE_ATTR(in##channel##_input, S_IRUGO, \ + show_in##channel##_input, NULL); + +show_in_channel(0); +show_in_channel(1); +show_in_channel(2); +show_in_channel(3); + +static ssize_t out0_output_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pcf8591_data *data = i2c_get_clientdata(to_i2c_client(dev)); + return sprintf(buf, "%d\n", data->aout * 10); +} + +static ssize_t out0_output_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long val; + struct i2c_client *client = to_i2c_client(dev); + struct pcf8591_data *data = i2c_get_clientdata(client); + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + val /= 10; + if (val > 255) + return -EINVAL; + + data->aout = val; + i2c_smbus_write_byte_data(client, data->control, data->aout); + return count; +} + +static DEVICE_ATTR_RW(out0_output); + +static ssize_t out0_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pcf8591_data *data = i2c_get_clientdata(to_i2c_client(dev)); + return sprintf(buf, "%u\n", !(!(data->control & PCF8591_CONTROL_AOEF))); +} + +static ssize_t out0_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pcf8591_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + if (val) + data->control |= PCF8591_CONTROL_AOEF; + else + data->control &= ~PCF8591_CONTROL_AOEF; + i2c_smbus_write_byte(client, data->control); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RW(out0_enable); + +static struct attribute *pcf8591_attributes[] = { + &dev_attr_out0_enable.attr, + &dev_attr_out0_output.attr, + &dev_attr_in0_input.attr, + &dev_attr_in1_input.attr, + NULL +}; + +static const struct attribute_group pcf8591_attr_group = { + .attrs = pcf8591_attributes, +}; + +static struct attribute *pcf8591_attributes_opt[] = { + &dev_attr_in2_input.attr, + &dev_attr_in3_input.attr, + NULL +}; + +static const struct attribute_group pcf8591_attr_group_opt = { + .attrs = pcf8591_attributes_opt, +}; + +/* + * Real code + */ + +static int pcf8591_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pcf8591_data *data; + int err; + + data = devm_kzalloc(&client->dev, sizeof(struct pcf8591_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Initialize the PCF8591 chip */ + pcf8591_init_client(client); + + /* Register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &pcf8591_attr_group); + if (err) + return err; + + /* Register input2 if not in "two differential inputs" mode */ + if (input_mode != 3) { + err = device_create_file(&client->dev, &dev_attr_in2_input); + if (err) + goto exit_sysfs_remove; + } + + /* Register input3 only in "four single ended inputs" mode */ + if (input_mode == 0) { + err = device_create_file(&client->dev, &dev_attr_in3_input); + if (err) + goto exit_sysfs_remove; + } + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_sysfs_remove; + } + + return 0; + +exit_sysfs_remove: + sysfs_remove_group(&client->dev.kobj, &pcf8591_attr_group_opt); + sysfs_remove_group(&client->dev.kobj, &pcf8591_attr_group); + return err; +} + +static int pcf8591_remove(struct i2c_client *client) +{ + struct pcf8591_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &pcf8591_attr_group_opt); + sysfs_remove_group(&client->dev.kobj, &pcf8591_attr_group); + return 0; +} + +/* Called when we have found a new PCF8591. */ +static void pcf8591_init_client(struct i2c_client *client) +{ + struct pcf8591_data *data = i2c_get_clientdata(client); + data->control = PCF8591_INIT_CONTROL; + data->aout = PCF8591_INIT_AOUT; + + i2c_smbus_write_byte_data(client, data->control, data->aout); + + /* + * The first byte transmitted contains the conversion code of the + * previous read cycle. FLUSH IT! + */ + i2c_smbus_read_byte(client); +} + +static int pcf8591_read_channel(struct device *dev, int channel) +{ + u8 value; + struct i2c_client *client = to_i2c_client(dev); + struct pcf8591_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); + + if ((data->control & PCF8591_CONTROL_AICH_MASK) != channel) { + data->control = (data->control & ~PCF8591_CONTROL_AICH_MASK) + | channel; + i2c_smbus_write_byte(client, data->control); + + /* + * The first byte transmitted contains the conversion code of + * the previous read cycle. FLUSH IT! + */ + i2c_smbus_read_byte(client); + } + value = i2c_smbus_read_byte(client); + + mutex_unlock(&data->update_lock); + + if ((channel == 2 && input_mode == 2) || + (channel != 3 && (input_mode == 1 || input_mode == 3))) + return 10 * REG_TO_SIGNED(value); + else + return 10 * value; +} + +static const struct i2c_device_id pcf8591_id[] = { + { "pcf8591", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pcf8591_id); + +static struct i2c_driver pcf8591_driver = { + .driver = { + .name = "pcf8591", + }, + .probe = pcf8591_probe, + .remove = pcf8591_remove, + .id_table = pcf8591_id, +}; + +static int __init pcf8591_init(void) +{ + if (input_mode < 0 || input_mode > 3) { + pr_warn("invalid input_mode (%d)\n", input_mode); + input_mode = 0; + } + return i2c_add_driver(&pcf8591_driver); +} + +static void __exit pcf8591_exit(void) +{ + i2c_del_driver(&pcf8591_driver); +} + +MODULE_AUTHOR("Aurelien Jarno "); +MODULE_DESCRIPTION("PCF8591 driver"); +MODULE_LICENSE("GPL"); + +module_init(pcf8591_init); +module_exit(pcf8591_exit); diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig new file mode 100644 index 000000000..a82018aaf --- /dev/null +++ b/drivers/hwmon/pmbus/Kconfig @@ -0,0 +1,201 @@ +# +# PMBus chip drivers configuration +# + +menuconfig PMBUS + tristate "PMBus support" + depends on I2C + default n + help + Say yes here if you want to enable PMBus support. + + This driver can also be built as a module. If so, the module will + be called pmbus_core. + +if PMBUS + +config SENSORS_PMBUS + tristate "Generic PMBus devices" + default y + help + If you say yes here you get hardware monitoring support for generic + PMBus devices, including but not limited to ADP4000, BMR453, BMR454, + MDT040, NCP4200, NCP4208, PDT003, PDT006, PDT012, TPS40400, TPS544B20, + TPS544B25, TPS544C20, TPS544C25, and UDT020. + + This driver can also be built as a module. If so, the module will + be called pmbus. + +config SENSORS_ADM1275 + tristate "Analog Devices ADM1275 and compatibles" + default n + help + If you say yes here you get hardware monitoring support for Analog + Devices ADM1075, ADM1272, ADM1275, ADM1276, ADM1278, ADM1293, + and ADM1294 Hot-Swap Controller and Digital Power Monitors. + + This driver can also be built as a module. If so, the module will + be called adm1275. + +config SENSORS_IBM_CFFPS + tristate "IBM Common Form Factor Power Supply" + depends on LEDS_CLASS + help + If you say yes here you get hardware monitoring support for the IBM + Common Form Factor power supply. + + This driver can also be built as a module. If so, the module will + be called ibm-cffps. + +config SENSORS_IR35221 + tristate "Infineon IR35221" + default n + help + If you say yes here you get hardware monitoring support for the + Infineon IR35221 controller. + + This driver can also be built as a module. If so, the module will + be called ir35521. + +config SENSORS_LM25066 + tristate "National Semiconductor LM25066 and compatibles" + default n + help + If you say yes here you get hardware monitoring support for National + Semiconductor LM25056, LM25066, LM5064, and LM5066. + + This driver can also be built as a module. If so, the module will + be called lm25066. + +config SENSORS_LTC2978 + tristate "Linear Technologies LTC2978 and compatibles" + default n + help + If you say yes here you get hardware monitoring support for Linear + Technology LTC2974, LTC2975, LTC2977, LTC2978, LTC2980, LTC3880, + LTC3883, LTC3886, LTC3887, LTCM2987, LTM4675, and LTM4676. + + This driver can also be built as a module. If so, the module will + be called ltc2978. + +config SENSORS_LTC2978_REGULATOR + bool "Regulator support for LTC2978 and compatibles" + depends on SENSORS_LTC2978 && REGULATOR + help + If you say yes here you get regulator support for Linear + Technology LTC2974, LTC2977, LTC2978, LTC3880, LTC3883, and LTM4676. + +config SENSORS_LTC3815 + tristate "Linear Technologies LTC3815" + default n + help + If you say yes here you get hardware monitoring support for Linear + Technology LTC3815. + + This driver can also be built as a module. If so, the module will + be called ltc3815. + +config SENSORS_MAX16064 + tristate "Maxim MAX16064" + default n + help + If you say yes here you get hardware monitoring support for Maxim + MAX16064. + + This driver can also be built as a module. If so, the module will + be called max16064. + +config SENSORS_MAX20751 + tristate "Maxim MAX20751" + default n + help + If you say yes here you get hardware monitoring support for Maxim + MAX20751. + + This driver can also be built as a module. If so, the module will + be called max20751. + +config SENSORS_MAX31785 + tristate "Maxim MAX31785 and compatibles" + default n + help + If you say yes here you get hardware monitoring support for Maxim + MAX31785. + + This driver can also be built as a module. If so, the module will + be called max31785. + +config SENSORS_MAX34440 + tristate "Maxim MAX34440 and compatibles" + default n + help + If you say yes here you get hardware monitoring support for Maxim + MAX34440, MAX34441, MAX34446, MAX34451, MAX34460, and MAX34461. + + This driver can also be built as a module. If so, the module will + be called max34440. + +config SENSORS_MAX8688 + tristate "Maxim MAX8688" + default n + help + If you say yes here you get hardware monitoring support for Maxim + MAX8688. + + This driver can also be built as a module. If so, the module will + be called max8688. + +config SENSORS_TPS40422 + tristate "TI TPS40422" + default n + help + If you say yes here you get hardware monitoring support for TI + TPS40422. + + This driver can also be built as a module. If so, the module will + be called tps40422. + +config SENSORS_TPS53679 + tristate "TI TPS53679" + help + If you say yes here you get hardware monitoring support for TI + TPS53679. + + This driver can also be built as a module. If so, the module will + be called tps53679. + +config SENSORS_UCD9000 + tristate "TI UCD90120, UCD90124, UCD90160, UCD9090, UCD90910" + default n + help + If you say yes here you get hardware monitoring support for TI + UCD90120, UCD90124, UCD90160, UCD9090, UCD90910, Sequencer and System + Health Controllers. + + This driver can also be built as a module. If so, the module will + be called ucd9000. + +config SENSORS_UCD9200 + tristate "TI UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, UCD9248" + default n + help + If you say yes here you get hardware monitoring support for TI + UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, and UCD9248 + Digital PWM System Controllers. + + This driver can also be built as a module. If so, the module will + be called ucd9200. + +config SENSORS_ZL6100 + tristate "Intersil ZL6100 and compatibles" + default n + help + If you say yes here you get hardware monitoring support for Intersil + ZL2004, ZL2005, ZL2006, ZL2008, ZL2105, ZL2106, ZL6100, ZL6105, + ZL9101M, and ZL9117M Digital DC/DC Controllers, as well as for + Ericsson BMR450, BMR451, BMR462, BMR463, and BMR464. + + This driver can also be built as a module. If so, the module will + be called zl6100. + +endif # PMBUS diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile new file mode 100644 index 000000000..ea0e39518 --- /dev/null +++ b/drivers/hwmon/pmbus/Makefile @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for PMBus chip drivers. +# + +obj-$(CONFIG_PMBUS) += pmbus_core.o +obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o +obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o +obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o +obj-$(CONFIG_SENSORS_IR35221) += ir35221.o +obj-$(CONFIG_SENSORS_LM25066) += lm25066.o +obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o +obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o +obj-$(CONFIG_SENSORS_MAX16064) += max16064.o +obj-$(CONFIG_SENSORS_MAX20751) += max20751.o +obj-$(CONFIG_SENSORS_MAX31785) += max31785.o +obj-$(CONFIG_SENSORS_MAX34440) += max34440.o +obj-$(CONFIG_SENSORS_MAX8688) += max8688.o +obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o +obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o +obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o +obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o +obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c new file mode 100644 index 000000000..a19cf9052 --- /dev/null +++ b/drivers/hwmon/pmbus/adm1275.c @@ -0,0 +1,694 @@ +/* + * Hardware monitoring driver for Analog Devices ADM1275 Hot-Swap Controller + * and Digital Power Monitor + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2018 Guenter Roeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +enum chips { adm1075, adm1272, adm1275, adm1276, adm1278, adm1293, adm1294 }; + +#define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0) +#define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5) +#define ADM1293_MFR_STATUS_VAUX_OV_WARN BIT(6) + +#define ADM1275_PEAK_IOUT 0xd0 +#define ADM1275_PEAK_VIN 0xd1 +#define ADM1275_PEAK_VOUT 0xd2 +#define ADM1275_PMON_CONFIG 0xd4 + +#define ADM1275_VIN_VOUT_SELECT BIT(6) +#define ADM1275_VRANGE BIT(5) +#define ADM1075_IRANGE_50 BIT(4) +#define ADM1075_IRANGE_25 BIT(3) +#define ADM1075_IRANGE_MASK (BIT(3) | BIT(4)) + +#define ADM1272_IRANGE BIT(0) + +#define ADM1278_TEMP1_EN BIT(3) +#define ADM1278_VIN_EN BIT(2) +#define ADM1278_VOUT_EN BIT(1) + +#define ADM1293_IRANGE_25 0 +#define ADM1293_IRANGE_50 BIT(6) +#define ADM1293_IRANGE_100 BIT(7) +#define ADM1293_IRANGE_200 (BIT(6) | BIT(7)) +#define ADM1293_IRANGE_MASK (BIT(6) | BIT(7)) + +#define ADM1293_VIN_SEL_012 BIT(2) +#define ADM1293_VIN_SEL_074 BIT(3) +#define ADM1293_VIN_SEL_210 (BIT(2) | BIT(3)) +#define ADM1293_VIN_SEL_MASK (BIT(2) | BIT(3)) + +#define ADM1293_VAUX_EN BIT(1) + +#define ADM1278_PEAK_TEMP 0xd7 +#define ADM1275_IOUT_WARN2_LIMIT 0xd7 +#define ADM1275_DEVICE_CONFIG 0xd8 + +#define ADM1275_IOUT_WARN2_SELECT BIT(4) + +#define ADM1276_PEAK_PIN 0xda +#define ADM1075_READ_VAUX 0xdd +#define ADM1075_VAUX_OV_WARN_LIMIT 0xde +#define ADM1075_VAUX_UV_WARN_LIMIT 0xdf +#define ADM1293_IOUT_MIN 0xe3 +#define ADM1293_PIN_MIN 0xe4 +#define ADM1075_VAUX_STATUS 0xf6 + +#define ADM1075_VAUX_OV_WARN BIT(7) +#define ADM1075_VAUX_UV_WARN BIT(6) + +struct adm1275_data { + int id; + bool have_oc_fault; + bool have_uc_fault; + bool have_vout; + bool have_vaux_status; + bool have_mfr_vaux_status; + bool have_iout_min; + bool have_pin_min; + bool have_pin_max; + bool have_temp_max; + struct pmbus_driver_info info; +}; + +#define to_adm1275_data(x) container_of(x, struct adm1275_data, info) + +struct coefficients { + s16 m; + s16 b; + s16 R; +}; + +static const struct coefficients adm1075_coefficients[] = { + [0] = { 27169, 0, -1 }, /* voltage */ + [1] = { 806, 20475, -1 }, /* current, irange25 */ + [2] = { 404, 20475, -1 }, /* current, irange50 */ + [3] = { 8549, 0, -1 }, /* power, irange25 */ + [4] = { 4279, 0, -1 }, /* power, irange50 */ +}; + +static const struct coefficients adm1272_coefficients[] = { + [0] = { 6770, 0, -2 }, /* voltage, vrange 60V */ + [1] = { 4062, 0, -2 }, /* voltage, vrange 100V */ + [2] = { 1326, 20480, -1 }, /* current, vsense range 15mV */ + [3] = { 663, 20480, -1 }, /* current, vsense range 30mV */ + [4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */ + [5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */ + [6] = { 17561, 0, -3 }, /* power, vrange 60V, irange 30mV */ + [7] = { 10535, 0, -3 }, /* power, vrange 100V, irange 30mV */ + [8] = { 42, 31871, -1 }, /* temperature */ + +}; + +static const struct coefficients adm1275_coefficients[] = { + [0] = { 19199, 0, -2 }, /* voltage, vrange set */ + [1] = { 6720, 0, -1 }, /* voltage, vrange not set */ + [2] = { 807, 20475, -1 }, /* current */ +}; + +static const struct coefficients adm1276_coefficients[] = { + [0] = { 19199, 0, -2 }, /* voltage, vrange set */ + [1] = { 6720, 0, -1 }, /* voltage, vrange not set */ + [2] = { 807, 20475, -1 }, /* current */ + [3] = { 6043, 0, -2 }, /* power, vrange set */ + [4] = { 2115, 0, -1 }, /* power, vrange not set */ +}; + +static const struct coefficients adm1278_coefficients[] = { + [0] = { 19599, 0, -2 }, /* voltage */ + [1] = { 800, 20475, -1 }, /* current */ + [2] = { 6123, 0, -2 }, /* power */ + [3] = { 42, 31880, -1 }, /* temperature */ +}; + +static const struct coefficients adm1293_coefficients[] = { + [0] = { 3333, -1, 0 }, /* voltage, vrange 1.2V */ + [1] = { 5552, -5, -1 }, /* voltage, vrange 7.4V */ + [2] = { 19604, -50, -2 }, /* voltage, vrange 21V */ + [3] = { 8000, -100, -2 }, /* current, irange25 */ + [4] = { 4000, -100, -2 }, /* current, irange50 */ + [5] = { 20000, -1000, -3 }, /* current, irange100 */ + [6] = { 10000, -1000, -3 }, /* current, irange200 */ + [7] = { 10417, 0, -1 }, /* power, 1.2V, irange25 */ + [8] = { 5208, 0, -1 }, /* power, 1.2V, irange50 */ + [9] = { 26042, 0, -2 }, /* power, 1.2V, irange100 */ + [10] = { 13021, 0, -2 }, /* power, 1.2V, irange200 */ + [11] = { 17351, 0, -2 }, /* power, 7.4V, irange25 */ + [12] = { 8676, 0, -2 }, /* power, 7.4V, irange50 */ + [13] = { 4338, 0, -2 }, /* power, 7.4V, irange100 */ + [14] = { 21689, 0, -3 }, /* power, 7.4V, irange200 */ + [15] = { 6126, 0, -2 }, /* power, 21V, irange25 */ + [16] = { 30631, 0, -3 }, /* power, 21V, irange50 */ + [17] = { 15316, 0, -3 }, /* power, 21V, irange100 */ + [18] = { 7658, 0, -3 }, /* power, 21V, irange200 */ +}; + +static int adm1275_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct adm1275_data *data = to_adm1275_data(info); + int ret = 0; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_IOUT_UC_FAULT_LIMIT: + if (!data->have_uc_fault) + return -ENXIO; + ret = pmbus_read_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT); + break; + case PMBUS_IOUT_OC_FAULT_LIMIT: + if (!data->have_oc_fault) + return -ENXIO; + ret = pmbus_read_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT); + break; + case PMBUS_VOUT_OV_WARN_LIMIT: + if (data->have_vout) + return -ENODATA; + ret = pmbus_read_word_data(client, 0, + ADM1075_VAUX_OV_WARN_LIMIT); + break; + case PMBUS_VOUT_UV_WARN_LIMIT: + if (data->have_vout) + return -ENODATA; + ret = pmbus_read_word_data(client, 0, + ADM1075_VAUX_UV_WARN_LIMIT); + break; + case PMBUS_READ_VOUT: + if (data->have_vout) + return -ENODATA; + ret = pmbus_read_word_data(client, 0, ADM1075_READ_VAUX); + break; + case PMBUS_VIRT_READ_IOUT_MIN: + if (!data->have_iout_min) + return -ENXIO; + ret = pmbus_read_word_data(client, 0, ADM1293_IOUT_MIN); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_IOUT); + break; + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_VOUT); + break; + case PMBUS_VIRT_READ_VIN_MAX: + ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_VIN); + break; + case PMBUS_VIRT_READ_PIN_MIN: + if (!data->have_pin_min) + return -ENXIO; + ret = pmbus_read_word_data(client, 0, ADM1293_PIN_MIN); + break; + case PMBUS_VIRT_READ_PIN_MAX: + if (!data->have_pin_max) + return -ENXIO; + ret = pmbus_read_word_data(client, 0, ADM1276_PEAK_PIN); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + if (!data->have_temp_max) + return -ENXIO; + ret = pmbus_read_word_data(client, 0, ADM1278_PEAK_TEMP); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_VIN_HISTORY: + break; + case PMBUS_VIRT_RESET_PIN_HISTORY: + if (!data->have_pin_max) + return -ENXIO; + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + if (!data->have_temp_max) + return -ENXIO; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int adm1275_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct adm1275_data *data = to_adm1275_data(info); + int ret; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_IOUT_UC_FAULT_LIMIT: + case PMBUS_IOUT_OC_FAULT_LIMIT: + ret = pmbus_write_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT, + word); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_IOUT, 0); + if (!ret && data->have_iout_min) + ret = pmbus_write_word_data(client, 0, + ADM1293_IOUT_MIN, 0); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_VOUT, 0); + break; + case PMBUS_VIRT_RESET_VIN_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_VIN, 0); + break; + case PMBUS_VIRT_RESET_PIN_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1276_PEAK_PIN, 0); + if (!ret && data->have_pin_min) + ret = pmbus_write_word_data(client, 0, + ADM1293_PIN_MIN, 0); + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1278_PEAK_TEMP, 0); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int adm1275_read_byte_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct adm1275_data *data = to_adm1275_data(info); + int mfr_status, ret; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_STATUS_IOUT: + ret = pmbus_read_byte_data(client, page, PMBUS_STATUS_IOUT); + if (ret < 0) + break; + if (!data->have_oc_fault && !data->have_uc_fault) + break; + mfr_status = pmbus_read_byte_data(client, page, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfr_status < 0) + return mfr_status; + if (mfr_status & ADM1275_MFR_STATUS_IOUT_WARN2) { + ret |= data->have_oc_fault ? + PB_IOUT_OC_FAULT : PB_IOUT_UC_FAULT; + } + break; + case PMBUS_STATUS_VOUT: + if (data->have_vout) + return -ENODATA; + ret = 0; + if (data->have_vaux_status) { + mfr_status = pmbus_read_byte_data(client, 0, + ADM1075_VAUX_STATUS); + if (mfr_status < 0) + return mfr_status; + if (mfr_status & ADM1075_VAUX_OV_WARN) + ret |= PB_VOLTAGE_OV_WARNING; + if (mfr_status & ADM1075_VAUX_UV_WARN) + ret |= PB_VOLTAGE_UV_WARNING; + } else if (data->have_mfr_vaux_status) { + mfr_status = pmbus_read_byte_data(client, page, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfr_status < 0) + return mfr_status; + if (mfr_status & ADM1293_MFR_STATUS_VAUX_OV_WARN) + ret |= PB_VOLTAGE_OV_WARNING; + if (mfr_status & ADM1293_MFR_STATUS_VAUX_UV_WARN) + ret |= PB_VOLTAGE_UV_WARNING; + } + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static const struct i2c_device_id adm1275_id[] = { + { "adm1075", adm1075 }, + { "adm1272", adm1272 }, + { "adm1275", adm1275 }, + { "adm1276", adm1276 }, + { "adm1278", adm1278 }, + { "adm1293", adm1293 }, + { "adm1294", adm1294 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adm1275_id); + +static int adm1275_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + s32 (*config_read_fn)(const struct i2c_client *client, u8 reg); + u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + int config, device_config; + int ret; + struct pmbus_driver_info *info; + struct adm1275_data *data; + const struct i2c_device_id *mid; + const struct coefficients *coefficients; + int vindex = -1, voindex = -1, cindex = -1, pindex = -1; + int tindex = -1; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA + | I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer ID\n"); + return ret; + } + if (ret != 3 || strncmp(block_buffer, "ADI", 3)) { + dev_err(&client->dev, "Unsupported Manufacturer ID\n"); + return -ENODEV; + } + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer Model\n"); + return ret; + } + for (mid = adm1275_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) + break; + } + if (!mid->name[0]) { + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + + if (id->driver_data != mid->driver_data) + dev_notice(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + id->name, mid->name); + + if (mid->driver_data == adm1272 || mid->driver_data == adm1278 || + mid->driver_data == adm1293 || mid->driver_data == adm1294) + config_read_fn = i2c_smbus_read_word_data; + else + config_read_fn = i2c_smbus_read_byte_data; + config = config_read_fn(client, ADM1275_PMON_CONFIG); + if (config < 0) + return config; + + device_config = config_read_fn(client, ADM1275_DEVICE_CONFIG); + if (device_config < 0) + return device_config; + + data = devm_kzalloc(&client->dev, sizeof(struct adm1275_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->id = mid->driver_data; + + info = &data->info; + + info->pages = 1; + info->format[PSC_VOLTAGE_IN] = direct; + info->format[PSC_VOLTAGE_OUT] = direct; + info->format[PSC_CURRENT_OUT] = direct; + info->format[PSC_POWER] = direct; + info->format[PSC_TEMPERATURE] = direct; + info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; + + info->read_word_data = adm1275_read_word_data; + info->read_byte_data = adm1275_read_byte_data; + info->write_word_data = adm1275_write_word_data; + + switch (data->id) { + case adm1075: + if (device_config & ADM1275_IOUT_WARN2_SELECT) + data->have_oc_fault = true; + else + data->have_uc_fault = true; + data->have_pin_max = true; + data->have_vaux_status = true; + + coefficients = adm1075_coefficients; + vindex = 0; + switch (config & ADM1075_IRANGE_MASK) { + case ADM1075_IRANGE_25: + cindex = 1; + pindex = 3; + break; + case ADM1075_IRANGE_50: + cindex = 2; + pindex = 4; + break; + default: + dev_err(&client->dev, "Invalid input current range"); + break; + } + + info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_PIN + | PMBUS_HAVE_STATUS_INPUT; + if (config & ADM1275_VIN_VOUT_SELECT) + info->func[0] |= + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + break; + case adm1272: + data->have_vout = true; + data->have_pin_max = true; + data->have_temp_max = true; + + coefficients = adm1272_coefficients; + vindex = (config & ADM1275_VRANGE) ? 1 : 0; + cindex = (config & ADM1272_IRANGE) ? 3 : 2; + /* pindex depends on the combination of the above */ + switch (config & (ADM1275_VRANGE | ADM1272_IRANGE)) { + case 0: + default: + pindex = 4; + break; + case ADM1275_VRANGE: + pindex = 5; + break; + case ADM1272_IRANGE: + pindex = 6; + break; + case ADM1275_VRANGE | ADM1272_IRANGE: + pindex = 7; + break; + } + tindex = 8; + + info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + + /* Enable VOUT if not enabled (it is disabled by default) */ + if (!(config & ADM1278_VOUT_EN)) { + config |= ADM1278_VOUT_EN; + ret = i2c_smbus_write_byte_data(client, + ADM1275_PMON_CONFIG, + config); + if (ret < 0) { + dev_err(&client->dev, + "Failed to enable VOUT monitoring\n"); + return -ENODEV; + } + } + + if (config & ADM1278_TEMP1_EN) + info->func[0] |= + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + if (config & ADM1278_VIN_EN) + info->func[0] |= PMBUS_HAVE_VIN; + break; + case adm1275: + if (device_config & ADM1275_IOUT_WARN2_SELECT) + data->have_oc_fault = true; + else + data->have_uc_fault = true; + data->have_vout = true; + + coefficients = adm1275_coefficients; + vindex = (config & ADM1275_VRANGE) ? 0 : 1; + cindex = 2; + + if (config & ADM1275_VIN_VOUT_SELECT) + info->func[0] |= + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + else + info->func[0] |= + PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT; + break; + case adm1276: + if (device_config & ADM1275_IOUT_WARN2_SELECT) + data->have_oc_fault = true; + else + data->have_uc_fault = true; + data->have_vout = true; + data->have_pin_max = true; + + coefficients = adm1276_coefficients; + vindex = (config & ADM1275_VRANGE) ? 0 : 1; + cindex = 2; + pindex = (config & ADM1275_VRANGE) ? 3 : 4; + + info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_PIN + | PMBUS_HAVE_STATUS_INPUT; + if (config & ADM1275_VIN_VOUT_SELECT) + info->func[0] |= + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + break; + case adm1278: + data->have_vout = true; + data->have_pin_max = true; + data->have_temp_max = true; + + coefficients = adm1278_coefficients; + vindex = 0; + cindex = 1; + pindex = 2; + tindex = 3; + + info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + + /* Enable VOUT if not enabled (it is disabled by default) */ + if (!(config & ADM1278_VOUT_EN)) { + config |= ADM1278_VOUT_EN; + ret = i2c_smbus_write_byte_data(client, + ADM1275_PMON_CONFIG, + config); + if (ret < 0) { + dev_err(&client->dev, + "Failed to enable VOUT monitoring\n"); + return -ENODEV; + } + } + + if (config & ADM1278_TEMP1_EN) + info->func[0] |= + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + if (config & ADM1278_VIN_EN) + info->func[0] |= PMBUS_HAVE_VIN; + break; + case adm1293: + case adm1294: + data->have_iout_min = true; + data->have_pin_min = true; + data->have_pin_max = true; + data->have_mfr_vaux_status = true; + + coefficients = adm1293_coefficients; + + voindex = 0; + switch (config & ADM1293_VIN_SEL_MASK) { + case ADM1293_VIN_SEL_012: /* 1.2V */ + vindex = 0; + break; + case ADM1293_VIN_SEL_074: /* 7.4V */ + vindex = 1; + break; + case ADM1293_VIN_SEL_210: /* 21V */ + vindex = 2; + break; + default: /* disabled */ + break; + } + + switch (config & ADM1293_IRANGE_MASK) { + case ADM1293_IRANGE_25: + cindex = 3; + break; + case ADM1293_IRANGE_50: + cindex = 4; + break; + case ADM1293_IRANGE_100: + cindex = 5; + break; + case ADM1293_IRANGE_200: + cindex = 6; + break; + } + + if (vindex >= 0) + pindex = 7 + vindex * 4 + (cindex - 3); + + if (config & ADM1293_VAUX_EN) + info->func[0] |= + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + + info->func[0] |= PMBUS_HAVE_PIN | + PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT; + + break; + default: + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + + if (voindex < 0) + voindex = vindex; + if (vindex >= 0) { + info->m[PSC_VOLTAGE_IN] = coefficients[vindex].m; + info->b[PSC_VOLTAGE_IN] = coefficients[vindex].b; + info->R[PSC_VOLTAGE_IN] = coefficients[vindex].R; + } + if (voindex >= 0) { + info->m[PSC_VOLTAGE_OUT] = coefficients[voindex].m; + info->b[PSC_VOLTAGE_OUT] = coefficients[voindex].b; + info->R[PSC_VOLTAGE_OUT] = coefficients[voindex].R; + } + if (cindex >= 0) { + info->m[PSC_CURRENT_OUT] = coefficients[cindex].m; + info->b[PSC_CURRENT_OUT] = coefficients[cindex].b; + info->R[PSC_CURRENT_OUT] = coefficients[cindex].R; + } + if (pindex >= 0) { + info->m[PSC_POWER] = coefficients[pindex].m; + info->b[PSC_POWER] = coefficients[pindex].b; + info->R[PSC_POWER] = coefficients[pindex].R; + } + if (tindex >= 0) { + info->m[PSC_TEMPERATURE] = coefficients[tindex].m; + info->b[PSC_TEMPERATURE] = coefficients[tindex].b; + info->R[PSC_TEMPERATURE] = coefficients[tindex].R; + } + + return pmbus_do_probe(client, id, info); +} + +static struct i2c_driver adm1275_driver = { + .driver = { + .name = "adm1275", + }, + .probe = adm1275_probe, + .remove = pmbus_do_remove, + .id_table = adm1275_id, +}; + +module_i2c_driver(adm1275_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1275 and compatibles"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c new file mode 100644 index 000000000..edc70ac7d --- /dev/null +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -0,0 +1,439 @@ +/* + * Copyright 2017 IBM Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmbus.h" + +#define CFFPS_FRU_CMD 0x9A +#define CFFPS_PN_CMD 0x9B +#define CFFPS_SN_CMD 0x9E +#define CFFPS_CCIN_CMD 0xBD +#define CFFPS_FW_CMD_START 0xFA +#define CFFPS_FW_NUM_BYTES 4 +#define CFFPS_SYS_CONFIG_CMD 0xDA + +#define CFFPS_INPUT_HISTORY_CMD 0xD6 +#define CFFPS_INPUT_HISTORY_SIZE 100 + +/* STATUS_MFR_SPECIFIC bits */ +#define CFFPS_MFR_FAN_FAULT BIT(0) +#define CFFPS_MFR_THERMAL_FAULT BIT(1) +#define CFFPS_MFR_OV_FAULT BIT(2) +#define CFFPS_MFR_UV_FAULT BIT(3) +#define CFFPS_MFR_PS_KILL BIT(4) +#define CFFPS_MFR_OC_FAULT BIT(5) +#define CFFPS_MFR_VAUX_FAULT BIT(6) +#define CFFPS_MFR_CURRENT_SHARE_WARNING BIT(7) + +#define CFFPS_LED_BLINK BIT(0) +#define CFFPS_LED_ON BIT(1) +#define CFFPS_LED_OFF BIT(2) +#define CFFPS_BLINK_RATE_MS 250 + +enum { + CFFPS_DEBUGFS_INPUT_HISTORY = 0, + CFFPS_DEBUGFS_FRU, + CFFPS_DEBUGFS_PN, + CFFPS_DEBUGFS_SN, + CFFPS_DEBUGFS_CCIN, + CFFPS_DEBUGFS_FW, + CFFPS_DEBUGFS_NUM_ENTRIES +}; + +struct ibm_cffps_input_history { + struct mutex update_lock; + unsigned long last_update; + + u8 byte_count; + u8 data[CFFPS_INPUT_HISTORY_SIZE]; +}; + +struct ibm_cffps { + struct i2c_client *client; + + struct ibm_cffps_input_history input_history; + + int debugfs_entries[CFFPS_DEBUGFS_NUM_ENTRIES]; + + char led_name[32]; + u8 led_state; + struct led_classdev led; +}; + +#define to_psu(x, y) container_of((x), struct ibm_cffps, debugfs_entries[(y)]) + +static ssize_t ibm_cffps_read_input_history(struct ibm_cffps *psu, + char __user *buf, size_t count, + loff_t *ppos) +{ + int rc; + u8 msgbuf0[1] = { CFFPS_INPUT_HISTORY_CMD }; + u8 msgbuf1[CFFPS_INPUT_HISTORY_SIZE + 1] = { 0 }; + struct i2c_msg msg[2] = { + { + .addr = psu->client->addr, + .flags = psu->client->flags, + .len = 1, + .buf = msgbuf0, + }, { + .addr = psu->client->addr, + .flags = psu->client->flags | I2C_M_RD, + .len = CFFPS_INPUT_HISTORY_SIZE + 1, + .buf = msgbuf1, + }, + }; + + if (!*ppos) { + mutex_lock(&psu->input_history.update_lock); + if (time_after(jiffies, psu->input_history.last_update + HZ)) { + /* + * Use a raw i2c transfer, since we need more bytes + * than Linux I2C supports through smbus xfr (only 32). + */ + rc = i2c_transfer(psu->client->adapter, msg, 2); + if (rc < 0) { + mutex_unlock(&psu->input_history.update_lock); + return rc; + } + + psu->input_history.byte_count = msgbuf1[0]; + memcpy(psu->input_history.data, &msgbuf1[1], + CFFPS_INPUT_HISTORY_SIZE); + psu->input_history.last_update = jiffies; + } + + mutex_unlock(&psu->input_history.update_lock); + } + + return simple_read_from_buffer(buf, count, ppos, + psu->input_history.data, + psu->input_history.byte_count); +} + +static ssize_t ibm_cffps_debugfs_op(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + u8 cmd; + int i, rc; + int *idxp = file->private_data; + int idx = *idxp; + struct ibm_cffps *psu = to_psu(idxp, idx); + char data[I2C_SMBUS_BLOCK_MAX] = { 0 }; + + switch (idx) { + case CFFPS_DEBUGFS_INPUT_HISTORY: + return ibm_cffps_read_input_history(psu, buf, count, ppos); + case CFFPS_DEBUGFS_FRU: + cmd = CFFPS_FRU_CMD; + break; + case CFFPS_DEBUGFS_PN: + cmd = CFFPS_PN_CMD; + break; + case CFFPS_DEBUGFS_SN: + cmd = CFFPS_SN_CMD; + break; + case CFFPS_DEBUGFS_CCIN: + rc = i2c_smbus_read_word_swapped(psu->client, CFFPS_CCIN_CMD); + if (rc < 0) + return rc; + + rc = snprintf(data, 5, "%04X", rc); + goto done; + case CFFPS_DEBUGFS_FW: + for (i = 0; i < CFFPS_FW_NUM_BYTES; ++i) { + rc = i2c_smbus_read_byte_data(psu->client, + CFFPS_FW_CMD_START + i); + if (rc < 0) + return rc; + + snprintf(&data[i * 2], 3, "%02X", rc); + } + + rc = i * 2; + goto done; + default: + return -EINVAL; + } + + rc = i2c_smbus_read_block_data(psu->client, cmd, data); + if (rc < 0) + return rc; + +done: + data[rc] = '\n'; + rc += 2; + + return simple_read_from_buffer(buf, count, ppos, data, rc); +} + +static const struct file_operations ibm_cffps_fops = { + .llseek = noop_llseek, + .read = ibm_cffps_debugfs_op, + .open = simple_open, +}; + +static int ibm_cffps_read_byte_data(struct i2c_client *client, int page, + int reg) +{ + int rc, mfr; + + switch (reg) { + case PMBUS_STATUS_VOUT: + case PMBUS_STATUS_IOUT: + case PMBUS_STATUS_TEMPERATURE: + case PMBUS_STATUS_FAN_12: + rc = pmbus_read_byte_data(client, page, reg); + if (rc < 0) + return rc; + + mfr = pmbus_read_byte_data(client, page, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfr < 0) + /* + * Return the status register instead of an error, + * since we successfully read status. + */ + return rc; + + /* Add MFR_SPECIFIC bits to the standard pmbus status regs. */ + if (reg == PMBUS_STATUS_FAN_12) { + if (mfr & CFFPS_MFR_FAN_FAULT) + rc |= PB_FAN_FAN1_FAULT; + } else if (reg == PMBUS_STATUS_TEMPERATURE) { + if (mfr & CFFPS_MFR_THERMAL_FAULT) + rc |= PB_TEMP_OT_FAULT; + } else if (reg == PMBUS_STATUS_VOUT) { + if (mfr & (CFFPS_MFR_OV_FAULT | CFFPS_MFR_VAUX_FAULT)) + rc |= PB_VOLTAGE_OV_FAULT; + if (mfr & CFFPS_MFR_UV_FAULT) + rc |= PB_VOLTAGE_UV_FAULT; + } else if (reg == PMBUS_STATUS_IOUT) { + if (mfr & CFFPS_MFR_OC_FAULT) + rc |= PB_IOUT_OC_FAULT; + if (mfr & CFFPS_MFR_CURRENT_SHARE_WARNING) + rc |= PB_CURRENT_SHARE_FAULT; + } + break; + default: + rc = -ENODATA; + break; + } + + return rc; +} + +static int ibm_cffps_read_word_data(struct i2c_client *client, int page, + int reg) +{ + int rc, mfr; + + switch (reg) { + case PMBUS_STATUS_WORD: + rc = pmbus_read_word_data(client, page, reg); + if (rc < 0) + return rc; + + mfr = pmbus_read_byte_data(client, page, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfr < 0) + /* + * Return the status register instead of an error, + * since we successfully read status. + */ + return rc; + + if (mfr & CFFPS_MFR_PS_KILL) + rc |= PB_STATUS_OFF; + break; + default: + rc = -ENODATA; + break; + } + + return rc; +} + +static int ibm_cffps_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + int rc; + struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); + + if (brightness == LED_OFF) { + psu->led_state = CFFPS_LED_OFF; + } else { + brightness = LED_FULL; + if (psu->led_state != CFFPS_LED_BLINK) + psu->led_state = CFFPS_LED_ON; + } + + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, + psu->led_state); + if (rc < 0) + return rc; + + led_cdev->brightness = brightness; + + return 0; +} + +static int ibm_cffps_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + int rc; + struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); + + psu->led_state = CFFPS_LED_BLINK; + + if (led_cdev->brightness == LED_OFF) + return 0; + + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, + CFFPS_LED_BLINK); + if (rc < 0) + return rc; + + *delay_on = CFFPS_BLINK_RATE_MS; + *delay_off = CFFPS_BLINK_RATE_MS; + + return 0; +} + +static void ibm_cffps_create_led_class(struct ibm_cffps *psu) +{ + int rc; + struct i2c_client *client = psu->client; + struct device *dev = &client->dev; + + snprintf(psu->led_name, sizeof(psu->led_name), "%s-%02x", client->name, + client->addr); + psu->led.name = psu->led_name; + psu->led.max_brightness = LED_FULL; + psu->led.brightness_set_blocking = ibm_cffps_led_brightness_set; + psu->led.blink_set = ibm_cffps_led_blink_set; + + rc = devm_led_classdev_register(dev, &psu->led); + if (rc) + dev_warn(dev, "failed to register led class: %d\n", rc); +} + +static struct pmbus_driver_info ibm_cffps_info = { + .pages = 1, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_FAN12 | PMBUS_HAVE_TEMP | + PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_STATUS_FAN12, + .read_byte_data = ibm_cffps_read_byte_data, + .read_word_data = ibm_cffps_read_word_data, +}; + +static struct pmbus_platform_data ibm_cffps_pdata = { + .flags = PMBUS_SKIP_STATUS_CHECK, +}; + +static int ibm_cffps_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int i, rc; + struct dentry *debugfs; + struct dentry *ibm_cffps_dir; + struct ibm_cffps *psu; + + client->dev.platform_data = &ibm_cffps_pdata; + rc = pmbus_do_probe(client, id, &ibm_cffps_info); + if (rc) + return rc; + + /* + * Don't fail the probe if there isn't enough memory for leds and + * debugfs. + */ + psu = devm_kzalloc(&client->dev, sizeof(*psu), GFP_KERNEL); + if (!psu) + return 0; + + psu->client = client; + mutex_init(&psu->input_history.update_lock); + psu->input_history.last_update = jiffies - HZ; + + ibm_cffps_create_led_class(psu); + + /* Don't fail the probe if we can't create debugfs */ + debugfs = pmbus_get_debugfs_dir(client); + if (!debugfs) + return 0; + + ibm_cffps_dir = debugfs_create_dir(client->name, debugfs); + if (!ibm_cffps_dir) + return 0; + + for (i = 0; i < CFFPS_DEBUGFS_NUM_ENTRIES; ++i) + psu->debugfs_entries[i] = i; + + debugfs_create_file("input_history", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_INPUT_HISTORY], + &ibm_cffps_fops); + debugfs_create_file("fru", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_FRU], + &ibm_cffps_fops); + debugfs_create_file("part_number", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_PN], + &ibm_cffps_fops); + debugfs_create_file("serial_number", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_SN], + &ibm_cffps_fops); + debugfs_create_file("ccin", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_CCIN], + &ibm_cffps_fops); + debugfs_create_file("fw_version", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_FW], + &ibm_cffps_fops); + + return 0; +} + +static const struct i2c_device_id ibm_cffps_id[] = { + { "ibm_cffps1", 1 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, ibm_cffps_id); + +static const struct of_device_id ibm_cffps_of_match[] = { + { .compatible = "ibm,cffps1" }, + {} +}; +MODULE_DEVICE_TABLE(of, ibm_cffps_of_match); + +static struct i2c_driver ibm_cffps_driver = { + .driver = { + .name = "ibm-cffps", + .of_match_table = ibm_cffps_of_match, + }, + .probe = ibm_cffps_probe, + .remove = pmbus_do_remove, + .id_table = ibm_cffps_id, +}; + +module_i2c_driver(ibm_cffps_driver); + +MODULE_AUTHOR("Eddie James"); +MODULE_DESCRIPTION("PMBus driver for IBM Common Form Factor power supplies"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/ir35221.c b/drivers/hwmon/pmbus/ir35221.c new file mode 100644 index 000000000..977315b0f --- /dev/null +++ b/drivers/hwmon/pmbus/ir35221.c @@ -0,0 +1,148 @@ +/* + * Hardware monitoring driver for IR35221 + * + * Copyright (C) IBM Corporation 2017. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include "pmbus.h" + +#define IR35221_MFR_VIN_PEAK 0xc5 +#define IR35221_MFR_VOUT_PEAK 0xc6 +#define IR35221_MFR_IOUT_PEAK 0xc7 +#define IR35221_MFR_TEMP_PEAK 0xc8 +#define IR35221_MFR_VIN_VALLEY 0xc9 +#define IR35221_MFR_VOUT_VALLEY 0xca +#define IR35221_MFR_IOUT_VALLEY 0xcb +#define IR35221_MFR_TEMP_VALLEY 0xcc + +static int ir35221_read_word_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VIN_MAX: + ret = pmbus_read_word_data(client, page, IR35221_MFR_VIN_PEAK); + break; + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, page, IR35221_MFR_VOUT_PEAK); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, page, IR35221_MFR_IOUT_PEAK); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, page, IR35221_MFR_TEMP_PEAK); + break; + case PMBUS_VIRT_READ_VIN_MIN: + ret = pmbus_read_word_data(client, page, + IR35221_MFR_VIN_VALLEY); + break; + case PMBUS_VIRT_READ_VOUT_MIN: + ret = pmbus_read_word_data(client, page, + IR35221_MFR_VOUT_VALLEY); + break; + case PMBUS_VIRT_READ_IOUT_MIN: + ret = pmbus_read_word_data(client, page, + IR35221_MFR_IOUT_VALLEY); + break; + case PMBUS_VIRT_READ_TEMP_MIN: + ret = pmbus_read_word_data(client, page, + IR35221_MFR_TEMP_VALLEY); + break; + default: + ret = -ENODATA; + break; + } + + return ret; +} + +static int ir35221_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pmbus_driver_info *info; + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA + | I2C_FUNC_SMBUS_READ_WORD_DATA + | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); + if (ret < 0) { + dev_err(&client->dev, "Failed to read PMBUS_MFR_ID\n"); + return ret; + } + if (ret != 2 || strncmp(buf, "RI", strlen("RI"))) { + dev_err(&client->dev, "MFR_ID unrecognised\n"); + return -ENODEV; + } + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) { + dev_err(&client->dev, "Failed to read PMBUS_MFR_MODEL\n"); + return ret; + } + if (ret != 2 || !(buf[0] == 0x6c && buf[1] == 0x00)) { + dev_err(&client->dev, "MFR_MODEL unrecognised\n"); + return -ENODEV; + } + + info = devm_kzalloc(&client->dev, sizeof(struct pmbus_driver_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->read_word_data = ir35221_read_word_data; + + info->pages = 2; + info->format[PSC_VOLTAGE_IN] = linear; + info->format[PSC_VOLTAGE_OUT] = linear; + info->format[PSC_CURRENT_IN] = linear; + info->format[PSC_CURRENT_OUT] = linear; + info->format[PSC_POWER] = linear; + info->format[PSC_TEMPERATURE] = linear; + + info->func[0] = PMBUS_HAVE_VIN + | PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN + | PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN + | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP; + info->func[1] = info->func[0]; + + return pmbus_do_probe(client, id, info); +} + +static const struct i2c_device_id ir35221_id[] = { + {"ir35221", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ir35221_id); + +static struct i2c_driver ir35221_driver = { + .driver = { + .name = "ir35221", + }, + .probe = ir35221_probe, + .remove = pmbus_do_remove, + .id_table = ir35221_id, +}; + +module_i2c_driver(ir35221_driver); + +MODULE_AUTHOR("Samuel Mendoza-Jonas +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +enum chips { lm25056, lm25066, lm5064, lm5066, lm5066i }; + +#define LM25066_READ_VAUX 0xd0 +#define LM25066_MFR_READ_IIN 0xd1 +#define LM25066_MFR_READ_PIN 0xd2 +#define LM25066_MFR_IIN_OC_WARN_LIMIT 0xd3 +#define LM25066_MFR_PIN_OP_WARN_LIMIT 0xd4 +#define LM25066_READ_PIN_PEAK 0xd5 +#define LM25066_CLEAR_PIN_PEAK 0xd6 +#define LM25066_DEVICE_SETUP 0xd9 +#define LM25066_READ_AVG_VIN 0xdc +#define LM25066_READ_AVG_VOUT 0xdd +#define LM25066_READ_AVG_IIN 0xde +#define LM25066_READ_AVG_PIN 0xdf + +#define LM25066_DEV_SETUP_CL BIT(4) /* Current limit */ + +/* LM25056 only */ + +#define LM25056_VAUX_OV_WARN_LIMIT 0xe3 +#define LM25056_VAUX_UV_WARN_LIMIT 0xe4 + +#define LM25056_MFR_STS_VAUX_OV_WARN BIT(1) +#define LM25056_MFR_STS_VAUX_UV_WARN BIT(0) + +struct __coeff { + short m, b, R; +}; + +#define PSC_CURRENT_IN_L (PSC_NUM_CLASSES) +#define PSC_POWER_L (PSC_NUM_CLASSES + 1) + +static struct __coeff lm25066_coeff[][PSC_NUM_CLASSES + 2] = { + [lm25056] = { + [PSC_VOLTAGE_IN] = { + .m = 16296, + .b = 1343, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 13797, + .b = -1833, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 6726, + .b = -537, + .R = -2, + }, + [PSC_POWER] = { + .m = 5501, + .b = -2908, + .R = -3, + }, + [PSC_POWER_L] = { + .m = 26882, + .b = -5646, + .R = -4, + }, + [PSC_TEMPERATURE] = { + .m = 1580, + .b = -14500, + .R = -2, + }, + }, + [lm25066] = { + [PSC_VOLTAGE_IN] = { + .m = 22070, + .b = -1800, + .R = -2, + }, + [PSC_VOLTAGE_OUT] = { + .m = 22070, + .b = -1800, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 13661, + .b = -5200, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 6852, + .b = -3100, + .R = -2, + }, + [PSC_POWER] = { + .m = 736, + .b = -3300, + .R = -2, + }, + [PSC_POWER_L] = { + .m = 369, + .b = -1900, + .R = -2, + }, + [PSC_TEMPERATURE] = { + .m = 16, + }, + }, + [lm5064] = { + [PSC_VOLTAGE_IN] = { + .m = 4611, + .b = -642, + .R = -2, + }, + [PSC_VOLTAGE_OUT] = { + .m = 4621, + .b = 423, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 10742, + .b = 1552, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 5456, + .b = 2118, + .R = -2, + }, + [PSC_POWER] = { + .m = 1204, + .b = 8524, + .R = -3, + }, + [PSC_POWER_L] = { + .m = 612, + .b = 11202, + .R = -3, + }, + [PSC_TEMPERATURE] = { + .m = 16, + }, + }, + [lm5066] = { + [PSC_VOLTAGE_IN] = { + .m = 4587, + .b = -1200, + .R = -2, + }, + [PSC_VOLTAGE_OUT] = { + .m = 4587, + .b = -2400, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 10753, + .b = -1200, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 5405, + .b = -600, + .R = -2, + }, + [PSC_POWER] = { + .m = 1204, + .b = -6000, + .R = -3, + }, + [PSC_POWER_L] = { + .m = 605, + .b = -8000, + .R = -3, + }, + [PSC_TEMPERATURE] = { + .m = 16, + }, + }, + [lm5066i] = { + [PSC_VOLTAGE_IN] = { + .m = 4617, + .b = -140, + .R = -2, + }, + [PSC_VOLTAGE_OUT] = { + .m = 4602, + .b = 500, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 15076, + .b = -504, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 7645, + .b = 100, + .R = -2, + }, + [PSC_POWER] = { + .m = 1701, + .b = -4000, + .R = -3, + }, + [PSC_POWER_L] = { + .m = 861, + .b = -965, + .R = -3, + }, + [PSC_TEMPERATURE] = { + .m = 16, + }, + }, +}; + +struct lm25066_data { + int id; + u16 rlimit; /* Maximum register value */ + struct pmbus_driver_info info; +}; + +#define to_lm25066_data(x) container_of(x, struct lm25066_data, info) + +static int lm25066_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct lm25066_data *data = to_lm25066_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VMON: + ret = pmbus_read_word_data(client, 0, LM25066_READ_VAUX); + if (ret < 0) + break; + /* Adjust returned value to match VIN coefficients */ + switch (data->id) { + case lm25056: + /* VIN: 6.14 mV VAUX: 293 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 293, 6140); + break; + case lm25066: + /* VIN: 4.54 mV VAUX: 283.2 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 2832, 45400); + break; + case lm5064: + /* VIN: 4.53 mV VAUX: 700 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 70, 453); + break; + case lm5066: + case lm5066i: + /* VIN: 2.18 mV VAUX: 725 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 725, 2180); + break; + } + break; + case PMBUS_READ_IIN: + ret = pmbus_read_word_data(client, 0, LM25066_MFR_READ_IIN); + break; + case PMBUS_READ_PIN: + ret = pmbus_read_word_data(client, 0, LM25066_MFR_READ_PIN); + break; + case PMBUS_IIN_OC_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, + LM25066_MFR_IIN_OC_WARN_LIMIT); + break; + case PMBUS_PIN_OP_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, + LM25066_MFR_PIN_OP_WARN_LIMIT); + break; + case PMBUS_VIRT_READ_VIN_AVG: + ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_VIN); + break; + case PMBUS_VIRT_READ_VOUT_AVG: + ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_VOUT); + break; + case PMBUS_VIRT_READ_IIN_AVG: + ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_IIN); + break; + case PMBUS_VIRT_READ_PIN_AVG: + ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_PIN); + break; + case PMBUS_VIRT_READ_PIN_MAX: + ret = pmbus_read_word_data(client, 0, LM25066_READ_PIN_PEAK); + break; + case PMBUS_VIRT_RESET_PIN_HISTORY: + ret = 0; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int lm25056_read_word_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, + LM25056_VAUX_UV_WARN_LIMIT); + if (ret < 0) + break; + /* Adjust returned value to match VIN coefficients */ + ret = DIV_ROUND_CLOSEST(ret * 293, 6140); + break; + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, + LM25056_VAUX_OV_WARN_LIMIT); + if (ret < 0) + break; + /* Adjust returned value to match VIN coefficients */ + ret = DIV_ROUND_CLOSEST(ret * 293, 6140); + break; + default: + ret = lm25066_read_word_data(client, page, reg); + break; + } + return ret; +} + +static int lm25056_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret, s; + + switch (reg) { + case PMBUS_VIRT_STATUS_VMON: + ret = pmbus_read_byte_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (ret < 0) + break; + s = 0; + if (ret & LM25056_MFR_STS_VAUX_UV_WARN) + s |= PB_VOLTAGE_UV_WARNING; + if (ret & LM25056_MFR_STS_VAUX_OV_WARN) + s |= PB_VOLTAGE_OV_WARNING; + ret = s; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct lm25066_data *data = to_lm25066_data(info); + int ret; + + switch (reg) { + case PMBUS_POUT_OP_FAULT_LIMIT: + case PMBUS_POUT_OP_WARN_LIMIT: + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + case PMBUS_IIN_OC_FAULT_LIMIT: + case PMBUS_VIN_UV_WARN_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + case PMBUS_VIN_OV_FAULT_LIMIT: + case PMBUS_VIN_OV_WARN_LIMIT: + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); + ret = pmbus_write_word_data(client, 0, reg, word); + pmbus_clear_cache(client); + break; + case PMBUS_IIN_OC_WARN_LIMIT: + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); + ret = pmbus_write_word_data(client, 0, + LM25066_MFR_IIN_OC_WARN_LIMIT, + word); + pmbus_clear_cache(client); + break; + case PMBUS_PIN_OP_WARN_LIMIT: + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); + ret = pmbus_write_word_data(client, 0, + LM25066_MFR_PIN_OP_WARN_LIMIT, + word); + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + /* Adjust from VIN coefficients (for LM25056) */ + word = DIV_ROUND_CLOSEST((int)word * 6140, 293); + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); + ret = pmbus_write_word_data(client, 0, + LM25056_VAUX_UV_WARN_LIMIT, word); + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + /* Adjust from VIN coefficients (for LM25056) */ + word = DIV_ROUND_CLOSEST((int)word * 6140, 293); + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); + ret = pmbus_write_word_data(client, 0, + LM25056_VAUX_OV_WARN_LIMIT, word); + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_RESET_PIN_HISTORY: + ret = pmbus_write_byte(client, 0, LM25066_CLEAR_PIN_PEAK); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int lm25066_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int config; + struct lm25066_data *data; + struct pmbus_driver_info *info; + struct __coeff *coeff; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(struct lm25066_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + config = i2c_smbus_read_byte_data(client, LM25066_DEVICE_SETUP); + if (config < 0) + return config; + + data->id = id->driver_data; + info = &data->info; + + info->pages = 1; + info->format[PSC_VOLTAGE_IN] = direct; + info->format[PSC_VOLTAGE_OUT] = direct; + info->format[PSC_CURRENT_IN] = direct; + info->format[PSC_TEMPERATURE] = direct; + info->format[PSC_POWER] = direct; + + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VMON + | PMBUS_HAVE_PIN | PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + + if (data->id == lm25056) { + info->func[0] |= PMBUS_HAVE_STATUS_VMON; + info->read_word_data = lm25056_read_word_data; + info->read_byte_data = lm25056_read_byte_data; + data->rlimit = 0x0fff; + } else { + info->func[0] |= PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + info->read_word_data = lm25066_read_word_data; + data->rlimit = 0x0fff; + } + info->write_word_data = lm25066_write_word_data; + + coeff = &lm25066_coeff[data->id][0]; + info->m[PSC_TEMPERATURE] = coeff[PSC_TEMPERATURE].m; + info->b[PSC_TEMPERATURE] = coeff[PSC_TEMPERATURE].b; + info->R[PSC_TEMPERATURE] = coeff[PSC_TEMPERATURE].R; + info->m[PSC_VOLTAGE_IN] = coeff[PSC_VOLTAGE_IN].m; + info->b[PSC_VOLTAGE_IN] = coeff[PSC_VOLTAGE_IN].b; + info->R[PSC_VOLTAGE_IN] = coeff[PSC_VOLTAGE_IN].R; + info->m[PSC_VOLTAGE_OUT] = coeff[PSC_VOLTAGE_OUT].m; + info->b[PSC_VOLTAGE_OUT] = coeff[PSC_VOLTAGE_OUT].b; + info->R[PSC_VOLTAGE_OUT] = coeff[PSC_VOLTAGE_OUT].R; + info->R[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].R; + info->R[PSC_POWER] = coeff[PSC_POWER].R; + if (config & LM25066_DEV_SETUP_CL) { + info->m[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN_L].m; + info->b[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN_L].b; + info->m[PSC_POWER] = coeff[PSC_POWER_L].m; + info->b[PSC_POWER] = coeff[PSC_POWER_L].b; + } else { + info->m[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].m; + info->b[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].b; + info->m[PSC_POWER] = coeff[PSC_POWER].m; + info->b[PSC_POWER] = coeff[PSC_POWER].b; + } + + return pmbus_do_probe(client, id, info); +} + +static const struct i2c_device_id lm25066_id[] = { + {"lm25056", lm25056}, + {"lm25066", lm25066}, + {"lm5064", lm5064}, + {"lm5066", lm5066}, + {"lm5066i", lm5066i}, + { } +}; + +MODULE_DEVICE_TABLE(i2c, lm25066_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver lm25066_driver = { + .driver = { + .name = "lm25066", + }, + .probe = lm25066_probe, + .remove = pmbus_do_remove, + .id_table = lm25066_id, +}; + +module_i2c_driver(lm25066_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for LM25066 and compatible chips"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c new file mode 100644 index 000000000..94eea2ac6 --- /dev/null +++ b/drivers/hwmon/pmbus/ltc2978.c @@ -0,0 +1,792 @@ +/* + * Hardware monitoring driver for LTC2978 and compatible chips. + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2013, 2014, 2015 Guenter Roeck + * Copyright (c) 2015 Linear Technology + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +enum chips { ltc2974, ltc2975, ltc2977, ltc2978, ltc2980, ltc3880, ltc3882, + ltc3883, ltc3886, ltc3887, ltm2987, ltm4675, ltm4676 }; + +/* Common for all chips */ +#define LTC2978_MFR_VOUT_PEAK 0xdd +#define LTC2978_MFR_VIN_PEAK 0xde +#define LTC2978_MFR_TEMPERATURE_PEAK 0xdf +#define LTC2978_MFR_SPECIAL_ID 0xe7 /* Undocumented on LTC3882 */ +#define LTC2978_MFR_COMMON 0xef + +/* LTC2974, LTC2975, LCT2977, LTC2980, LTC2978, and LTM2987 */ +#define LTC2978_MFR_VOUT_MIN 0xfb +#define LTC2978_MFR_VIN_MIN 0xfc +#define LTC2978_MFR_TEMPERATURE_MIN 0xfd + +/* LTC2974, LTC2975 */ +#define LTC2974_MFR_IOUT_PEAK 0xd7 +#define LTC2974_MFR_IOUT_MIN 0xd8 + +/* LTC3880, LTC3882, LTC3883, LTC3887, LTM4675, and LTM4676 */ +#define LTC3880_MFR_IOUT_PEAK 0xd7 +#define LTC3880_MFR_CLEAR_PEAKS 0xe3 +#define LTC3880_MFR_TEMPERATURE2_PEAK 0xf4 + +/* LTC3883 and LTC3886 only */ +#define LTC3883_MFR_IIN_PEAK 0xe1 + +/* LTC2975 only */ +#define LTC2975_MFR_IIN_PEAK 0xc4 +#define LTC2975_MFR_IIN_MIN 0xc5 +#define LTC2975_MFR_PIN_PEAK 0xc6 +#define LTC2975_MFR_PIN_MIN 0xc7 + +#define LTC2978_ID_MASK 0xfff0 + +#define LTC2974_ID 0x0210 +#define LTC2975_ID 0x0220 +#define LTC2977_ID 0x0130 +#define LTC2978_ID_REV1 0x0110 /* Early revision */ +#define LTC2978_ID_REV2 0x0120 +#define LTC2980_ID_A 0x8030 /* A/B for two die IDs */ +#define LTC2980_ID_B 0x8040 +#define LTC3880_ID 0x4020 +#define LTC3882_ID 0x4200 +#define LTC3882_ID_D1 0x4240 /* Dash 1 */ +#define LTC3883_ID 0x4300 +#define LTC3886_ID 0x4600 +#define LTC3887_ID 0x4700 +#define LTM2987_ID_A 0x8010 /* A/B for two die IDs */ +#define LTM2987_ID_B 0x8020 +#define LTM4675_ID 0x47a0 +#define LTM4676_ID_REV1 0x4400 +#define LTM4676_ID_REV2 0x4480 +#define LTM4676A_ID 0x47e0 + +#define LTC2974_NUM_PAGES 4 +#define LTC2978_NUM_PAGES 8 +#define LTC3880_NUM_PAGES 2 +#define LTC3883_NUM_PAGES 1 + +#define LTC_POLL_TIMEOUT 100 /* in milli-seconds */ + +#define LTC_NOT_BUSY BIT(6) +#define LTC_NOT_PENDING BIT(5) + +/* + * LTC2978 clears peak data whenever the CLEAR_FAULTS command is executed, which + * happens pretty much each time chip data is updated. Raw peak data therefore + * does not provide much value. To be able to provide useful peak data, keep an + * internal cache of measured peak data, which is only cleared if an explicit + * "clear peak" command is executed for the sensor in question. + */ + +struct ltc2978_data { + enum chips id; + u16 vin_min, vin_max; + u16 temp_min[LTC2974_NUM_PAGES], temp_max[LTC2974_NUM_PAGES]; + u16 vout_min[LTC2978_NUM_PAGES], vout_max[LTC2978_NUM_PAGES]; + u16 iout_min[LTC2974_NUM_PAGES], iout_max[LTC2974_NUM_PAGES]; + u16 iin_min, iin_max; + u16 pin_min, pin_max; + u16 temp2_max; + struct pmbus_driver_info info; + u32 features; +}; +#define to_ltc2978_data(x) container_of(x, struct ltc2978_data, info) + +#define FEAT_CLEAR_PEAKS BIT(0) +#define FEAT_NEEDS_POLLING BIT(1) + +#define has_clear_peaks(d) ((d)->features & FEAT_CLEAR_PEAKS) +#define needs_polling(d) ((d)->features & FEAT_NEEDS_POLLING) + +static int ltc_wait_ready(struct i2c_client *client) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(LTC_POLL_TIMEOUT); + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int status; + u8 mask; + + if (!needs_polling(data)) + return 0; + + /* + * LTC3883 does not support LTC_NOT_PENDING, even though + * the datasheet claims that it does. + */ + mask = LTC_NOT_BUSY; + if (data->id != ltc3883) + mask |= LTC_NOT_PENDING; + + do { + status = pmbus_read_byte_data(client, 0, LTC2978_MFR_COMMON); + if (status == -EBADMSG || status == -ENXIO) { + /* PEC error or NACK: chip may be busy, try again */ + usleep_range(50, 100); + continue; + } + if (status < 0) + return status; + + if ((status & mask) == mask) + return 0; + + usleep_range(50, 100); + } while (time_before(jiffies, timeout)); + + return -ETIMEDOUT; +} + +static int ltc_read_word_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + ret = ltc_wait_ready(client); + if (ret < 0) + return ret; + + return pmbus_read_word_data(client, page, reg); +} + +static int ltc_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + ret = ltc_wait_ready(client); + if (ret < 0) + return ret; + + return pmbus_read_byte_data(client, page, reg); +} + +static int ltc_write_byte(struct i2c_client *client, int page, u8 byte) +{ + int ret; + + ret = ltc_wait_ready(client); + if (ret < 0) + return ret; + + return pmbus_write_byte(client, page, byte); +} + +static inline int lin11_to_val(int data) +{ + s16 e = ((s16)data) >> 11; + s32 m = (((s16)(data << 5)) >> 5); + + /* + * mantissa is 10 bit + sign, exponent adds up to 15 bit. + * Add 6 bit to exponent for maximum accuracy (10 + 15 + 6 = 31). + */ + e += 6; + return (e < 0 ? m >> -e : m << e); +} + +static int ltc_get_max(struct ltc2978_data *data, struct i2c_client *client, + int page, int reg, u16 *pmax) +{ + int ret; + + ret = ltc_read_word_data(client, page, reg); + if (ret >= 0) { + if (lin11_to_val(ret) > lin11_to_val(*pmax)) + *pmax = ret; + ret = *pmax; + } + return ret; +} + +static int ltc_get_min(struct ltc2978_data *data, struct i2c_client *client, + int page, int reg, u16 *pmin) +{ + int ret; + + ret = ltc_read_word_data(client, page, reg); + if (ret >= 0) { + if (lin11_to_val(ret) < lin11_to_val(*pmin)) + *pmin = ret; + ret = *pmin; + } + return ret; +} + +static int ltc2978_read_word_data_common(struct i2c_client *client, int page, + int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VIN_MAX: + ret = ltc_get_max(data, client, page, LTC2978_MFR_VIN_PEAK, + &data->vin_max); + break; + case PMBUS_VIRT_READ_VOUT_MAX: + ret = ltc_read_word_data(client, page, LTC2978_MFR_VOUT_PEAK); + if (ret >= 0) { + /* + * VOUT is 16 bit unsigned with fixed exponent, + * so we can compare it directly + */ + if (ret > data->vout_max[page]) + data->vout_max[page] = ret; + ret = data->vout_max[page]; + } + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = ltc_get_max(data, client, page, + LTC2978_MFR_TEMPERATURE_PEAK, + &data->temp_max[page]); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = 0; + break; + default: + ret = ltc_wait_ready(client); + if (ret < 0) + return ret; + ret = -ENODATA; + break; + } + return ret; +} + +static int ltc2978_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VIN_MIN: + ret = ltc_get_min(data, client, page, LTC2978_MFR_VIN_MIN, + &data->vin_min); + break; + case PMBUS_VIRT_READ_VOUT_MIN: + ret = ltc_read_word_data(client, page, LTC2978_MFR_VOUT_MIN); + if (ret >= 0) { + /* + * VOUT_MIN is known to not be supported on some lots + * of LTC2978 revision 1, and will return the maximum + * possible voltage if read. If VOUT_MAX is valid and + * lower than the reading of VOUT_MIN, use it instead. + */ + if (data->vout_max[page] && ret > data->vout_max[page]) + ret = data->vout_max[page]; + if (ret < data->vout_min[page]) + data->vout_min[page] = ret; + ret = data->vout_min[page]; + } + break; + case PMBUS_VIRT_READ_TEMP_MIN: + ret = ltc_get_min(data, client, page, + LTC2978_MFR_TEMPERATURE_MIN, + &data->temp_min[page]); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_READ_TEMP2_MAX: + case PMBUS_VIRT_RESET_TEMP2_HISTORY: + ret = -ENXIO; + break; + default: + ret = ltc2978_read_word_data_common(client, page, reg); + break; + } + return ret; +} + +static int ltc2974_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_IOUT_MAX: + ret = ltc_get_max(data, client, page, LTC2974_MFR_IOUT_PEAK, + &data->iout_max[page]); + break; + case PMBUS_VIRT_READ_IOUT_MIN: + ret = ltc_get_min(data, client, page, LTC2974_MFR_IOUT_MIN, + &data->iout_min[page]); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + ret = 0; + break; + default: + ret = ltc2978_read_word_data(client, page, reg); + break; + } + return ret; +} + +static int ltc2975_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_IIN_MAX: + ret = ltc_get_max(data, client, page, LTC2975_MFR_IIN_PEAK, + &data->iin_max); + break; + case PMBUS_VIRT_READ_IIN_MIN: + ret = ltc_get_min(data, client, page, LTC2975_MFR_IIN_MIN, + &data->iin_min); + break; + case PMBUS_VIRT_READ_PIN_MAX: + ret = ltc_get_max(data, client, page, LTC2975_MFR_PIN_PEAK, + &data->pin_max); + break; + case PMBUS_VIRT_READ_PIN_MIN: + ret = ltc_get_min(data, client, page, LTC2975_MFR_PIN_MIN, + &data->pin_min); + break; + case PMBUS_VIRT_RESET_IIN_HISTORY: + case PMBUS_VIRT_RESET_PIN_HISTORY: + ret = 0; + break; + default: + ret = ltc2978_read_word_data(client, page, reg); + break; + } + return ret; +} + +static int ltc3880_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_IOUT_MAX: + ret = ltc_get_max(data, client, page, LTC3880_MFR_IOUT_PEAK, + &data->iout_max[page]); + break; + case PMBUS_VIRT_READ_TEMP2_MAX: + ret = ltc_get_max(data, client, page, + LTC3880_MFR_TEMPERATURE2_PEAK, + &data->temp2_max); + break; + case PMBUS_VIRT_READ_VIN_MIN: + case PMBUS_VIRT_READ_VOUT_MIN: + case PMBUS_VIRT_READ_TEMP_MIN: + ret = -ENXIO; + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP2_HISTORY: + ret = 0; + break; + default: + ret = ltc2978_read_word_data_common(client, page, reg); + break; + } + return ret; +} + +static int ltc3883_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_IIN_MAX: + ret = ltc_get_max(data, client, page, LTC3883_MFR_IIN_PEAK, + &data->iin_max); + break; + case PMBUS_VIRT_RESET_IIN_HISTORY: + ret = 0; + break; + default: + ret = ltc3880_read_word_data(client, page, reg); + break; + } + return ret; +} + +static int ltc2978_clear_peaks(struct ltc2978_data *data, + struct i2c_client *client, int page) +{ + int ret; + + if (has_clear_peaks(data)) + ret = ltc_write_byte(client, 0, LTC3880_MFR_CLEAR_PEAKS); + else + ret = ltc_write_byte(client, page, PMBUS_CLEAR_FAULTS); + + return ret; +} + +static int ltc2978_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_RESET_IIN_HISTORY: + data->iin_max = 0x7c00; + data->iin_min = 0x7bff; + ret = ltc2978_clear_peaks(data, client, 0); + break; + case PMBUS_VIRT_RESET_PIN_HISTORY: + data->pin_max = 0x7c00; + data->pin_min = 0x7bff; + ret = ltc2978_clear_peaks(data, client, 0); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + data->iout_max[page] = 0x7c00; + data->iout_min[page] = 0xfbff; + ret = ltc2978_clear_peaks(data, client, page); + break; + case PMBUS_VIRT_RESET_TEMP2_HISTORY: + data->temp2_max = 0x7c00; + ret = ltc2978_clear_peaks(data, client, page); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + data->vout_min[page] = 0xffff; + data->vout_max[page] = 0; + ret = ltc2978_clear_peaks(data, client, page); + break; + case PMBUS_VIRT_RESET_VIN_HISTORY: + data->vin_min = 0x7bff; + data->vin_max = 0x7c00; + ret = ltc2978_clear_peaks(data, client, page); + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + data->temp_min[page] = 0x7bff; + data->temp_max[page] = 0x7c00; + ret = ltc2978_clear_peaks(data, client, page); + break; + default: + ret = ltc_wait_ready(client); + if (ret < 0) + return ret; + ret = -ENODATA; + break; + } + return ret; +} + +static const struct i2c_device_id ltc2978_id[] = { + {"ltc2974", ltc2974}, + {"ltc2975", ltc2975}, + {"ltc2977", ltc2977}, + {"ltc2978", ltc2978}, + {"ltc2980", ltc2980}, + {"ltc3880", ltc3880}, + {"ltc3882", ltc3882}, + {"ltc3883", ltc3883}, + {"ltc3886", ltc3886}, + {"ltc3887", ltc3887}, + {"ltm2987", ltm2987}, + {"ltm4675", ltm4675}, + {"ltm4676", ltm4676}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ltc2978_id); + +#if IS_ENABLED(CONFIG_SENSORS_LTC2978_REGULATOR) +static const struct regulator_desc ltc2978_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), + PMBUS_REGULATOR("vout", 1), + PMBUS_REGULATOR("vout", 2), + PMBUS_REGULATOR("vout", 3), + PMBUS_REGULATOR("vout", 4), + PMBUS_REGULATOR("vout", 5), + PMBUS_REGULATOR("vout", 6), + PMBUS_REGULATOR("vout", 7), +}; +#endif /* CONFIG_SENSORS_LTC2978_REGULATOR */ + +static int ltc2978_get_id(struct i2c_client *client) +{ + int chip_id; + + chip_id = i2c_smbus_read_word_data(client, LTC2978_MFR_SPECIAL_ID); + if (chip_id < 0) { + const struct i2c_device_id *id; + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); + if (ret < 0) + return ret; + if (ret < 3 || strncmp(buf, "LTC", 3)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) + return ret; + for (id = <c2978_id[0]; strlen(id->name); id++) { + if (!strncasecmp(id->name, buf, strlen(id->name))) + return (int)id->driver_data; + } + return -ENODEV; + } + + chip_id &= LTC2978_ID_MASK; + + if (chip_id == LTC2974_ID) + return ltc2974; + else if (chip_id == LTC2975_ID) + return ltc2975; + else if (chip_id == LTC2977_ID) + return ltc2977; + else if (chip_id == LTC2978_ID_REV1 || chip_id == LTC2978_ID_REV2) + return ltc2978; + else if (chip_id == LTC2980_ID_A || chip_id == LTC2980_ID_B) + return ltc2980; + else if (chip_id == LTC3880_ID) + return ltc3880; + else if (chip_id == LTC3882_ID || chip_id == LTC3882_ID_D1) + return ltc3882; + else if (chip_id == LTC3883_ID) + return ltc3883; + else if (chip_id == LTC3886_ID) + return ltc3886; + else if (chip_id == LTC3887_ID) + return ltc3887; + else if (chip_id == LTM2987_ID_A || chip_id == LTM2987_ID_B) + return ltm2987; + else if (chip_id == LTM4675_ID) + return ltm4675; + else if (chip_id == LTM4676_ID_REV1 || chip_id == LTM4676_ID_REV2 || + chip_id == LTM4676A_ID) + return ltm4676; + + dev_err(&client->dev, "Unsupported chip ID 0x%x\n", chip_id); + return -ENODEV; +} + +static int ltc2978_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int i, chip_id; + struct ltc2978_data *data; + struct pmbus_driver_info *info; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(struct ltc2978_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + chip_id = ltc2978_get_id(client); + if (chip_id < 0) + return chip_id; + + data->id = chip_id; + if (data->id != id->driver_data) + dev_warn(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + id->name, + ltc2978_id[data->id].name); + + info = &data->info; + info->write_word_data = ltc2978_write_word_data; + info->write_byte = ltc_write_byte; + info->read_word_data = ltc_read_word_data; + info->read_byte_data = ltc_read_byte_data; + + data->vin_min = 0x7bff; + data->vin_max = 0x7c00; + for (i = 0; i < ARRAY_SIZE(data->vout_min); i++) + data->vout_min[i] = 0xffff; + for (i = 0; i < ARRAY_SIZE(data->iout_min); i++) + data->iout_min[i] = 0xfbff; + for (i = 0; i < ARRAY_SIZE(data->iout_max); i++) + data->iout_max[i] = 0x7c00; + for (i = 0; i < ARRAY_SIZE(data->temp_min); i++) + data->temp_min[i] = 0x7bff; + for (i = 0; i < ARRAY_SIZE(data->temp_max); i++) + data->temp_max[i] = 0x7c00; + data->temp2_max = 0x7c00; + + switch (data->id) { + case ltc2974: + info->read_word_data = ltc2974_read_word_data; + info->pages = LTC2974_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP2; + for (i = 0; i < info->pages; i++) { + info->func[i] |= PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; + } + break; + case ltc2975: + info->read_word_data = ltc2975_read_word_data; + info->pages = LTC2974_NUM_PAGES; + info->func[0] = PMBUS_HAVE_IIN | PMBUS_HAVE_PIN + | PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP2; + for (i = 0; i < info->pages; i++) { + info->func[i] |= PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; + } + break; + case ltc2977: + case ltc2978: + case ltc2980: + case ltm2987: + info->read_word_data = ltc2978_read_word_data; + info->pages = LTC2978_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + for (i = 1; i < LTC2978_NUM_PAGES; i++) { + info->func[i] = PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_VOUT; + } + break; + case ltc3880: + case ltc3887: + case ltm4675: + case ltm4676: + data->features |= FEAT_CLEAR_PEAKS | FEAT_NEEDS_POLLING; + info->read_word_data = ltc3880_read_word_data; + info->pages = LTC3880_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN + | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + break; + case ltc3882: + data->features |= FEAT_CLEAR_PEAKS | FEAT_NEEDS_POLLING; + info->read_word_data = ltc3880_read_word_data; + info->pages = LTC3880_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN + | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + break; + case ltc3883: + data->features |= FEAT_CLEAR_PEAKS | FEAT_NEEDS_POLLING; + info->read_word_data = ltc3883_read_word_data; + info->pages = LTC3883_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN + | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + break; + case ltc3886: + data->features |= FEAT_CLEAR_PEAKS | FEAT_NEEDS_POLLING; + info->read_word_data = ltc3883_read_word_data; + info->pages = LTC3880_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN + | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + break; + default: + return -ENODEV; + } + +#if IS_ENABLED(CONFIG_SENSORS_LTC2978_REGULATOR) + info->num_regulators = info->pages; + info->reg_desc = ltc2978_reg_desc; + if (info->num_regulators > ARRAY_SIZE(ltc2978_reg_desc)) { + dev_err(&client->dev, "num_regulators too large!"); + info->num_regulators = ARRAY_SIZE(ltc2978_reg_desc); + } +#endif + + return pmbus_do_probe(client, id, info); +} + +#ifdef CONFIG_OF +static const struct of_device_id ltc2978_of_match[] = { + { .compatible = "lltc,ltc2974" }, + { .compatible = "lltc,ltc2975" }, + { .compatible = "lltc,ltc2977" }, + { .compatible = "lltc,ltc2978" }, + { .compatible = "lltc,ltc2980" }, + { .compatible = "lltc,ltc3880" }, + { .compatible = "lltc,ltc3882" }, + { .compatible = "lltc,ltc3883" }, + { .compatible = "lltc,ltc3886" }, + { .compatible = "lltc,ltc3887" }, + { .compatible = "lltc,ltm2987" }, + { .compatible = "lltc,ltm4675" }, + { .compatible = "lltc,ltm4676" }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc2978_of_match); +#endif + +static struct i2c_driver ltc2978_driver = { + .driver = { + .name = "ltc2978", + .of_match_table = of_match_ptr(ltc2978_of_match), + }, + .probe = ltc2978_probe, + .remove = pmbus_do_remove, + .id_table = ltc2978_id, +}; + +module_i2c_driver(ltc2978_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for LTC2978 and comppatible chips"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/ltc3815.c b/drivers/hwmon/pmbus/ltc3815.c new file mode 100644 index 000000000..bb32e6276 --- /dev/null +++ b/drivers/hwmon/pmbus/ltc3815.c @@ -0,0 +1,215 @@ +/* + * Hardware monitoring driver for LTC3815 + * + * Copyright (c) 2015 Linear Technology + * Copyright (c) 2015 Guenter Roeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +#define LTC3815_MFR_IOUT_PEAK 0xd7 +#define LTC3815_MFR_VOUT_PEAK 0xdd +#define LTC3815_MFR_VIN_PEAK 0xde +#define LTC3815_MFR_TEMP_PEAK 0xdf +#define LTC3815_MFR_IIN_PEAK 0xe1 +#define LTC3815_MFR_SPECIAL_ID 0xe7 + +#define LTC3815_ID 0x8000 +#define LTC3815_ID_MASK 0xff00 + +static int ltc3815_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VOUT_MODE: + /* + * The chip returns 0x3e, suggesting VID mode with manufacturer + * specific VID codes. Since the output voltage is reported + * with a LSB of 0.5mV, override and report direct mode with + * appropriate coefficients. + */ + ret = 0x40; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int ltc3815_write_byte(struct i2c_client *client, int page, u8 reg) +{ + int ret; + + switch (reg) { + case PMBUS_CLEAR_FAULTS: + /* + * LTC3815 does not support the CLEAR_FAULTS command. + * Emulate it by clearing the status register. + */ + ret = pmbus_read_word_data(client, 0, PMBUS_STATUS_WORD); + if (ret > 0) { + pmbus_write_word_data(client, 0, PMBUS_STATUS_WORD, + ret); + ret = 0; + } + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int ltc3815_read_word_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VIN_MAX: + ret = pmbus_read_word_data(client, page, LTC3815_MFR_VIN_PEAK); + break; + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, page, LTC3815_MFR_VOUT_PEAK); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, page, LTC3815_MFR_TEMP_PEAK); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, page, LTC3815_MFR_IOUT_PEAK); + break; + case PMBUS_VIRT_READ_IIN_MAX: + ret = pmbus_read_word_data(client, page, LTC3815_MFR_IIN_PEAK); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_IIN_HISTORY: + ret = 0; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int ltc3815_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_RESET_IIN_HISTORY: + ret = pmbus_write_word_data(client, page, + LTC3815_MFR_IIN_PEAK, 0); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + ret = pmbus_write_word_data(client, page, + LTC3815_MFR_IOUT_PEAK, 0); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = pmbus_write_word_data(client, page, + LTC3815_MFR_VOUT_PEAK, 0); + break; + case PMBUS_VIRT_RESET_VIN_HISTORY: + ret = pmbus_write_word_data(client, page, + LTC3815_MFR_VIN_PEAK, 0); + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = pmbus_write_word_data(client, page, + LTC3815_MFR_TEMP_PEAK, 0); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static const struct i2c_device_id ltc3815_id[] = { + {"ltc3815", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc3815_id); + +static struct pmbus_driver_info ltc3815_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_IN] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 250, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 0, + .m[PSC_VOLTAGE_OUT] = 2, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_IN] = 1, + .b[PSC_CURRENT_IN] = 0, + .R[PSC_CURRENT_IN] = 2, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 2, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP, + .read_byte_data = ltc3815_read_byte_data, + .read_word_data = ltc3815_read_word_data, + .write_byte = ltc3815_write_byte, + .write_word_data = ltc3815_write_word_data, +}; + +static int ltc3815_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int chip_id; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -ENODEV; + + chip_id = i2c_smbus_read_word_data(client, LTC3815_MFR_SPECIAL_ID); + if (chip_id < 0) + return chip_id; + if ((chip_id & LTC3815_ID_MASK) != LTC3815_ID) + return -ENODEV; + + return pmbus_do_probe(client, id, <c3815_info); +} + +static struct i2c_driver ltc3815_driver = { + .driver = { + .name = "ltc3815", + }, + .probe = ltc3815_probe, + .remove = pmbus_do_remove, + .id_table = ltc3815_id, +}; + +module_i2c_driver(ltc3815_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for LTC3815"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/max16064.c b/drivers/hwmon/pmbus/max16064.c new file mode 100644 index 000000000..fa237a3c3 --- /dev/null +++ b/drivers/hwmon/pmbus/max16064.c @@ -0,0 +1,127 @@ +/* + * Hardware monitoring driver for Maxim MAX16064 + * + * Copyright (c) 2011 Ericsson AB. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include "pmbus.h" + +#define MAX16064_MFR_VOUT_PEAK 0xd4 +#define MAX16064_MFR_TEMPERATURE_PEAK 0xd6 + +static int max16064_read_word_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, page, + MAX16064_MFR_VOUT_PEAK); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, page, + MAX16064_MFR_TEMPERATURE_PEAK); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = 0; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max16064_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX16064_MFR_VOUT_PEAK, 0); + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX16064_MFR_TEMPERATURE_PEAK, + 0xffff); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static struct pmbus_driver_info max16064_info = { + .pages = 4, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 19995, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -1, + .m[PSC_VOLTAGE_OUT] = 19995, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = -1, + .m[PSC_TEMPERATURE] = -7612, + .b[PSC_TEMPERATURE] = 335, + .R[PSC_TEMPERATURE] = -3, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_TEMP, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .read_word_data = max16064_read_word_data, + .write_word_data = max16064_write_word_data, +}; + +static int max16064_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return pmbus_do_probe(client, id, &max16064_info); +} + +static const struct i2c_device_id max16064_id[] = { + {"max16064", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max16064_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max16064_driver = { + .driver = { + .name = "max16064", + }, + .probe = max16064_probe, + .remove = pmbus_do_remove, + .id_table = max16064_id, +}; + +module_i2c_driver(max16064_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX16064"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/max20751.c b/drivers/hwmon/pmbus/max20751.c new file mode 100644 index 000000000..ab74aeae8 --- /dev/null +++ b/drivers/hwmon/pmbus/max20751.c @@ -0,0 +1,64 @@ +/* + * Hardware monitoring driver for Maxim MAX20751 + * + * Copyright (c) 2015 Guenter Roeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include "pmbus.h" + +static struct pmbus_driver_info max20751_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = vid, + .vrm_version = vr12, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT, +}; + +static int max20751_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return pmbus_do_probe(client, id, &max20751_info); +} + +static const struct i2c_device_id max20751_id[] = { + {"max20751", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max20751_id); + +static struct i2c_driver max20751_driver = { + .driver = { + .name = "max20751", + }, + .probe = max20751_probe, + .remove = pmbus_do_remove, + .id_table = max20751_id, +}; + +module_i2c_driver(max20751_driver); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX20751"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c new file mode 100644 index 000000000..c9dc8799b --- /dev/null +++ b/drivers/hwmon/pmbus/max31785.c @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2017 IBM Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include "pmbus.h" + +enum max31785_regs { + MFR_REVISION = 0x9b, + MFR_FAN_CONFIG = 0xf1, +}; + +#define MAX31785 0x3030 +#define MAX31785A 0x3040 + +#define MFR_FAN_CONFIG_DUAL_TACH BIT(12) + +#define MAX31785_NR_PAGES 23 +#define MAX31785_NR_FAN_PAGES 6 + +static int max31785_read_byte_data(struct i2c_client *client, int page, + int reg) +{ + if (page < MAX31785_NR_PAGES) + return -ENODATA; + + switch (reg) { + case PMBUS_VOUT_MODE: + return -ENOTSUPP; + case PMBUS_FAN_CONFIG_12: + return pmbus_read_byte_data(client, page - MAX31785_NR_PAGES, + reg); + } + + return -ENODATA; +} + +static int max31785_write_byte(struct i2c_client *client, int page, u8 value) +{ + if (page < MAX31785_NR_PAGES) + return -ENODATA; + + return -ENOTSUPP; +} + +static int max31785_read_long_data(struct i2c_client *client, int page, + int reg, u32 *data) +{ + unsigned char cmdbuf[1]; + unsigned char rspbuf[4]; + int rc; + + struct i2c_msg msg[2] = { + { + .addr = client->addr, + .flags = 0, + .len = sizeof(cmdbuf), + .buf = cmdbuf, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = sizeof(rspbuf), + .buf = rspbuf, + }, + }; + + cmdbuf[0] = reg; + + rc = pmbus_set_page(client, page); + if (rc < 0) + return rc; + + rc = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (rc < 0) + return rc; + + *data = (rspbuf[0] << (0 * 8)) | (rspbuf[1] << (1 * 8)) | + (rspbuf[2] << (2 * 8)) | (rspbuf[3] << (3 * 8)); + + return rc; +} + +static int max31785_get_pwm(struct i2c_client *client, int page) +{ + int rv; + + rv = pmbus_get_fan_rate_device(client, page, 0, percent); + if (rv < 0) + return rv; + else if (rv >= 0x8000) + return 0; + else if (rv >= 0x2711) + return 0x2710; + + return rv; +} + +static int max31785_get_pwm_mode(struct i2c_client *client, int page) +{ + int config; + int command; + + config = pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12); + if (config < 0) + return config; + + command = pmbus_read_word_data(client, page, PMBUS_FAN_COMMAND_1); + if (command < 0) + return command; + + if (config & PB_FAN_1_RPM) + return (command >= 0x8000) ? 3 : 2; + + if (command >= 0x8000) + return 3; + else if (command >= 0x2711) + return 0; + + return 1; +} + +static int max31785_read_word_data(struct i2c_client *client, int page, + int reg) +{ + u32 val; + int rv; + + switch (reg) { + case PMBUS_READ_FAN_SPEED_1: + if (page < MAX31785_NR_PAGES) + return -ENODATA; + + rv = max31785_read_long_data(client, page - MAX31785_NR_PAGES, + reg, &val); + if (rv < 0) + return rv; + + rv = (val >> 16) & 0xffff; + break; + case PMBUS_FAN_COMMAND_1: + /* + * PMBUS_FAN_COMMAND_x is probed to judge whether or not to + * expose fan control registers. + * + * Don't expose fan_target attribute for virtual pages. + */ + rv = (page >= MAX31785_NR_PAGES) ? -ENOTSUPP : -ENODATA; + break; + case PMBUS_VIRT_PWM_1: + rv = max31785_get_pwm(client, page); + break; + case PMBUS_VIRT_PWM_ENABLE_1: + rv = max31785_get_pwm_mode(client, page); + break; + default: + rv = -ENODATA; + break; + } + + return rv; +} + +static inline u32 max31785_scale_pwm(u32 sensor_val) +{ + /* + * The datasheet describes the accepted value range for manual PWM as + * [0, 0x2710], while the hwmon pwmX sysfs interface accepts values in + * [0, 255]. The MAX31785 uses DIRECT mode to scale the FAN_COMMAND + * registers and in PWM mode the coefficients are m=1, b=0, R=2. The + * important observation here is that 0x2710 == 10000 == 100 * 100. + * + * R=2 (== 10^2 == 100) accounts for scaling the value provided at the + * sysfs interface into the required hardware resolution, but it does + * not yet yield a value that we can write to the device (this initial + * scaling is handled by pmbus_data2reg()). Multiplying by 100 below + * translates the parameter value into the percentage units required by + * PMBus, and then we scale back by 255 as required by the hwmon pwmX + * interface to yield the percentage value at the appropriate + * resolution for hardware. + */ + return (sensor_val * 100) / 255; +} + +static int max31785_pwm_enable(struct i2c_client *client, int page, + u16 word) +{ + int config = 0; + int rate; + + switch (word) { + case 0: + rate = 0x7fff; + break; + case 1: + rate = pmbus_get_fan_rate_cached(client, page, 0, percent); + if (rate < 0) + return rate; + rate = max31785_scale_pwm(rate); + break; + case 2: + config = PB_FAN_1_RPM; + rate = pmbus_get_fan_rate_cached(client, page, 0, rpm); + if (rate < 0) + return rate; + break; + case 3: + rate = 0xffff; + break; + default: + return -EINVAL; + } + + return pmbus_update_fan(client, page, 0, config, PB_FAN_1_RPM, rate); +} + +static int max31785_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + switch (reg) { + case PMBUS_VIRT_PWM_1: + return pmbus_update_fan(client, page, 0, 0, PB_FAN_1_RPM, + max31785_scale_pwm(word)); + case PMBUS_VIRT_PWM_ENABLE_1: + return max31785_pwm_enable(client, page, word); + default: + break; + } + + return -ENODATA; +} + +#define MAX31785_FAN_FUNCS \ + (PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_PWM12) + +#define MAX31785_TEMP_FUNCS \ + (PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP) + +#define MAX31785_VOUT_FUNCS \ + (PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT) + +#define MAX37185_NUM_FAN_PAGES 6 + +static const struct pmbus_driver_info max31785_info = { + .pages = MAX31785_NR_PAGES, + + .write_word_data = max31785_write_word_data, + .read_byte_data = max31785_read_byte_data, + .read_word_data = max31785_read_word_data, + .write_byte = max31785_write_byte, + + /* RPM */ + .format[PSC_FAN] = direct, + .m[PSC_FAN] = 1, + .b[PSC_FAN] = 0, + .R[PSC_FAN] = 0, + /* PWM */ + .format[PSC_PWM] = direct, + .m[PSC_PWM] = 1, + .b[PSC_PWM] = 0, + .R[PSC_PWM] = 2, + .func[0] = MAX31785_FAN_FUNCS, + .func[1] = MAX31785_FAN_FUNCS, + .func[2] = MAX31785_FAN_FUNCS, + .func[3] = MAX31785_FAN_FUNCS, + .func[4] = MAX31785_FAN_FUNCS, + .func[5] = MAX31785_FAN_FUNCS, + + .format[PSC_TEMPERATURE] = direct, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[6] = MAX31785_TEMP_FUNCS, + .func[7] = MAX31785_TEMP_FUNCS, + .func[8] = MAX31785_TEMP_FUNCS, + .func[9] = MAX31785_TEMP_FUNCS, + .func[10] = MAX31785_TEMP_FUNCS, + .func[11] = MAX31785_TEMP_FUNCS, + .func[12] = MAX31785_TEMP_FUNCS, + .func[13] = MAX31785_TEMP_FUNCS, + .func[14] = MAX31785_TEMP_FUNCS, + .func[15] = MAX31785_TEMP_FUNCS, + .func[16] = MAX31785_TEMP_FUNCS, + + .format[PSC_VOLTAGE_OUT] = direct, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 0, + .func[17] = MAX31785_VOUT_FUNCS, + .func[18] = MAX31785_VOUT_FUNCS, + .func[19] = MAX31785_VOUT_FUNCS, + .func[20] = MAX31785_VOUT_FUNCS, + .func[21] = MAX31785_VOUT_FUNCS, + .func[22] = MAX31785_VOUT_FUNCS, +}; + +static int max31785_configure_dual_tach(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int ret; + int i; + + for (i = 0; i < MAX31785_NR_FAN_PAGES; i++) { + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, MFR_FAN_CONFIG); + if (ret < 0) + return ret; + + if (ret & MFR_FAN_CONFIG_DUAL_TACH) { + int virtual = MAX31785_NR_PAGES + i; + + info->pages = virtual + 1; + info->func[virtual] |= PMBUS_HAVE_FAN12; + info->func[virtual] |= PMBUS_PAGE_VIRTUAL; + } + } + + return 0; +} + +static int max31785_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct pmbus_driver_info *info; + bool dual_tach = false; + s64 ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + *info = max31785_info; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 255); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, MFR_REVISION); + if (ret < 0) + return ret; + + if (ret == MAX31785A) { + dual_tach = true; + } else if (ret == MAX31785) { + if (!strcmp("max31785a", id->name)) + dev_warn(dev, "Expected max3175a, found max31785: cannot provide secondary tachometer readings\n"); + } else { + return -ENODEV; + } + + if (dual_tach) { + ret = max31785_configure_dual_tach(client, info); + if (ret < 0) + return ret; + } + + return pmbus_do_probe(client, id, info); +} + +static const struct i2c_device_id max31785_id[] = { + { "max31785", 0 }, + { "max31785a", 0 }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, max31785_id); + +static const struct of_device_id max31785_of_match[] = { + { .compatible = "maxim,max31785" }, + { .compatible = "maxim,max31785a" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, max31785_of_match); + +static struct i2c_driver max31785_driver = { + .driver = { + .name = "max31785", + .of_match_table = max31785_of_match, + }, + .probe = max31785_probe, + .remove = pmbus_do_remove, + .id_table = max31785_id, +}; + +module_i2c_driver(max31785_driver); + +MODULE_AUTHOR("Andrew Jeffery "); +MODULE_DESCRIPTION("PMBus driver for the Maxim MAX31785"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c new file mode 100644 index 000000000..9af5ab52c --- /dev/null +++ b/drivers/hwmon/pmbus/max34440.c @@ -0,0 +1,520 @@ +/* + * Hardware monitoring driver for Maxim MAX34440/MAX34441 + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +enum chips { max34440, max34441, max34446, max34451, max34460, max34461 }; + +#define MAX34440_MFR_VOUT_PEAK 0xd4 +#define MAX34440_MFR_IOUT_PEAK 0xd5 +#define MAX34440_MFR_TEMPERATURE_PEAK 0xd6 +#define MAX34440_MFR_VOUT_MIN 0xd7 + +#define MAX34446_MFR_POUT_PEAK 0xe0 +#define MAX34446_MFR_POUT_AVG 0xe1 +#define MAX34446_MFR_IOUT_AVG 0xe2 +#define MAX34446_MFR_TEMPERATURE_AVG 0xe3 + +#define MAX34440_STATUS_OC_WARN BIT(0) +#define MAX34440_STATUS_OC_FAULT BIT(1) +#define MAX34440_STATUS_OT_FAULT BIT(5) +#define MAX34440_STATUS_OT_WARN BIT(6) + +#define MAX34451_MFR_CHANNEL_CONFIG 0xe4 +#define MAX34451_MFR_CHANNEL_CONFIG_SEL_MASK 0x3f + +struct max34440_data { + int id; + struct pmbus_driver_info info; +}; + +#define to_max34440_data(x) container_of(x, struct max34440_data, info) + +static int max34440_read_word_data(struct i2c_client *client, int page, int reg) +{ + int ret; + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct max34440_data *data = to_max34440_data(info); + + switch (reg) { + case PMBUS_VIRT_READ_VOUT_MIN: + ret = pmbus_read_word_data(client, page, + MAX34440_MFR_VOUT_MIN); + break; + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, page, + MAX34440_MFR_VOUT_PEAK); + break; + case PMBUS_VIRT_READ_IOUT_AVG: + if (data->id != max34446 && data->id != max34451) + return -ENXIO; + ret = pmbus_read_word_data(client, page, + MAX34446_MFR_IOUT_AVG); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, page, + MAX34440_MFR_IOUT_PEAK); + break; + case PMBUS_VIRT_READ_POUT_AVG: + if (data->id != max34446) + return -ENXIO; + ret = pmbus_read_word_data(client, page, + MAX34446_MFR_POUT_AVG); + break; + case PMBUS_VIRT_READ_POUT_MAX: + if (data->id != max34446) + return -ENXIO; + ret = pmbus_read_word_data(client, page, + MAX34446_MFR_POUT_PEAK); + break; + case PMBUS_VIRT_READ_TEMP_AVG: + if (data->id != max34446 && data->id != max34460 && + data->id != max34461) + return -ENXIO; + ret = pmbus_read_word_data(client, page, + MAX34446_MFR_TEMPERATURE_AVG); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, page, + MAX34440_MFR_TEMPERATURE_PEAK); + break; + case PMBUS_VIRT_RESET_POUT_HISTORY: + if (data->id != max34446) + return -ENXIO; + ret = 0; + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = 0; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max34440_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct max34440_data *data = to_max34440_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_RESET_POUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_POUT_PEAK, 0); + if (ret) + break; + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_POUT_AVG, 0); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_VOUT_MIN, 0x7fff); + if (ret) + break; + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_VOUT_PEAK, 0); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_IOUT_PEAK, 0); + if (!ret && (data->id == max34446 || data->id == max34451)) + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_IOUT_AVG, 0); + + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_TEMPERATURE_PEAK, + 0x8000); + if (!ret && data->id == max34446) + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_TEMPERATURE_AVG, 0); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max34440_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret = 0; + int mfg_status; + + if (page >= 0) { + ret = pmbus_set_page(client, page); + if (ret < 0) + return ret; + } + + switch (reg) { + case PMBUS_STATUS_IOUT: + mfg_status = pmbus_read_word_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX34440_STATUS_OC_WARN) + ret |= PB_IOUT_OC_WARNING; + if (mfg_status & MAX34440_STATUS_OC_FAULT) + ret |= PB_IOUT_OC_FAULT; + break; + case PMBUS_STATUS_TEMPERATURE: + mfg_status = pmbus_read_word_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX34440_STATUS_OT_WARN) + ret |= PB_TEMP_OT_WARNING; + if (mfg_status & MAX34440_STATUS_OT_FAULT) + ret |= PB_TEMP_OT_FAULT; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max34451_set_supported_funcs(struct i2c_client *client, + struct max34440_data *data) +{ + /* + * Each of the channel 0-15 can be configured to monitor the following + * functions based on MFR_CHANNEL_CONFIG[5:0] + * 0x10: Sequencing + voltage monitoring (only valid for PAGES 0–11) + * 0x20: Voltage monitoring (no sequencing) + * 0x21: Voltage read only + * 0x22: Current monitoring + * 0x23: Current read only + * 0x30: General-purpose input active low + * 0x34: General-purpose input active high + * 0x00: Disabled + */ + + int page, rv; + + for (page = 0; page < 16; page++) { + rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (rv < 0) + return rv; + + rv = i2c_smbus_read_word_data(client, + MAX34451_MFR_CHANNEL_CONFIG); + if (rv < 0) + return rv; + + switch (rv & MAX34451_MFR_CHANNEL_CONFIG_SEL_MASK) { + case 0x10: + case 0x20: + data->info.func[page] = PMBUS_HAVE_VOUT | + PMBUS_HAVE_STATUS_VOUT; + break; + case 0x21: + data->info.func[page] = PMBUS_HAVE_VOUT; + break; + case 0x22: + data->info.func[page] = PMBUS_HAVE_IOUT | + PMBUS_HAVE_STATUS_IOUT; + break; + case 0x23: + data->info.func[page] = PMBUS_HAVE_IOUT; + break; + default: + break; + } + } + + return 0; +} + +static struct pmbus_driver_info max34440_info[] = { + [max34440] = { + .pages = 14, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, /* R = 0 in datasheet reflects mV */ + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, /* R = 0 in datasheet reflects mV */ + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 3, /* R = 0 in datasheet reflects mA */ + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[7] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[8] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[9] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[10] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[11] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[12] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[13] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34440_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, + [max34441] = { + .pages = 12, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_FAN] = direct, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .m[PSC_FAN] = 1, + .b[PSC_FAN] = 0, + .R[PSC_FAN] = 0, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[5] = PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12, + .func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[7] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[8] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[9] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[10] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[11] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34440_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, + [max34446] = { + .pages = 7, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 3, + .m[PSC_POWER] = 1, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[4] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[5] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34440_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, + [max34451] = { + .pages = 21, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 2, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + /* func 0-15 is set dynamically before probing */ + .func[16] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[19] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, + [max34460] = { + .pages = 18, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[6] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[7] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[8] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[9] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[10] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[11] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[13] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[14] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[15] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[16] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, + [max34461] = { + .pages = 23, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[6] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[7] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[8] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[9] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[10] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[11] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[12] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[13] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[14] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[15] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + /* page 16 is reserved */ + .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[19] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[21] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, +}; + +static int max34440_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max34440_data *data; + int rv; + + data = devm_kzalloc(&client->dev, sizeof(struct max34440_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + data->id = id->driver_data; + data->info = max34440_info[id->driver_data]; + + if (data->id == max34451) { + rv = max34451_set_supported_funcs(client, data); + if (rv) + return rv; + } + + return pmbus_do_probe(client, id, &data->info); +} + +static const struct i2c_device_id max34440_id[] = { + {"max34440", max34440}, + {"max34441", max34441}, + {"max34446", max34446}, + {"max34451", max34451}, + {"max34460", max34460}, + {"max34461", max34461}, + {} +}; +MODULE_DEVICE_TABLE(i2c, max34440_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max34440_driver = { + .driver = { + .name = "max34440", + }, + .probe = max34440_probe, + .remove = pmbus_do_remove, + .id_table = max34440_id, +}; + +module_i2c_driver(max34440_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX34440/MAX34441"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/max8688.c b/drivers/hwmon/pmbus/max8688.c new file mode 100644 index 000000000..e951f9b87 --- /dev/null +++ b/drivers/hwmon/pmbus/max8688.c @@ -0,0 +1,205 @@ +/* + * Hardware monitoring driver for Maxim MAX8688 + * + * Copyright (c) 2011 Ericsson AB. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +#define MAX8688_MFR_VOUT_PEAK 0xd4 +#define MAX8688_MFR_IOUT_PEAK 0xd5 +#define MAX8688_MFR_TEMPERATURE_PEAK 0xd6 +#define MAX8688_MFG_STATUS 0xd8 + +#define MAX8688_STATUS_OC_FAULT BIT(4) +#define MAX8688_STATUS_OV_FAULT BIT(5) +#define MAX8688_STATUS_OV_WARNING BIT(8) +#define MAX8688_STATUS_UV_FAULT BIT(9) +#define MAX8688_STATUS_UV_WARNING BIT(10) +#define MAX8688_STATUS_UC_FAULT BIT(11) +#define MAX8688_STATUS_OC_WARNING BIT(12) +#define MAX8688_STATUS_OT_FAULT BIT(13) +#define MAX8688_STATUS_OT_WARNING BIT(14) + +static int max8688_read_word_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, 0, MAX8688_MFR_VOUT_PEAK); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, 0, MAX8688_MFR_IOUT_PEAK); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, 0, + MAX8688_MFR_TEMPERATURE_PEAK); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = 0; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max8688_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = pmbus_write_word_data(client, 0, MAX8688_MFR_VOUT_PEAK, + 0); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + ret = pmbus_write_word_data(client, 0, MAX8688_MFR_IOUT_PEAK, + 0); + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = pmbus_write_word_data(client, 0, + MAX8688_MFR_TEMPERATURE_PEAK, + 0xffff); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max8688_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret = 0; + int mfg_status; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_STATUS_VOUT: + mfg_status = pmbus_read_word_data(client, 0, + MAX8688_MFG_STATUS); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX8688_STATUS_UV_WARNING) + ret |= PB_VOLTAGE_UV_WARNING; + if (mfg_status & MAX8688_STATUS_UV_FAULT) + ret |= PB_VOLTAGE_UV_FAULT; + if (mfg_status & MAX8688_STATUS_OV_WARNING) + ret |= PB_VOLTAGE_OV_WARNING; + if (mfg_status & MAX8688_STATUS_OV_FAULT) + ret |= PB_VOLTAGE_OV_FAULT; + break; + case PMBUS_STATUS_IOUT: + mfg_status = pmbus_read_word_data(client, 0, + MAX8688_MFG_STATUS); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX8688_STATUS_UC_FAULT) + ret |= PB_IOUT_UC_FAULT; + if (mfg_status & MAX8688_STATUS_OC_WARNING) + ret |= PB_IOUT_OC_WARNING; + if (mfg_status & MAX8688_STATUS_OC_FAULT) + ret |= PB_IOUT_OC_FAULT; + break; + case PMBUS_STATUS_TEMPERATURE: + mfg_status = pmbus_read_word_data(client, 0, + MAX8688_MFG_STATUS); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX8688_STATUS_OT_WARNING) + ret |= PB_TEMP_OT_WARNING; + if (mfg_status & MAX8688_STATUS_OT_FAULT) + ret |= PB_TEMP_OT_FAULT; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static struct pmbus_driver_info max8688_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_VOLTAGE_IN] = 19995, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -1, + .m[PSC_VOLTAGE_OUT] = 19995, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = -1, + .m[PSC_CURRENT_OUT] = 23109, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = -2, + .m[PSC_TEMPERATURE] = -7612, + .b[PSC_TEMPERATURE] = 335, + .R[PSC_TEMPERATURE] = -3, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max8688_read_byte_data, + .read_word_data = max8688_read_word_data, + .write_word_data = max8688_write_word_data, +}; + +static int max8688_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return pmbus_do_probe(client, id, &max8688_info); +} + +static const struct i2c_device_id max8688_id[] = { + {"max8688", 0}, + { } +}; + +MODULE_DEVICE_TABLE(i2c, max8688_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max8688_driver = { + .driver = { + .name = "max8688", + }, + .probe = max8688_probe, + .remove = pmbus_do_remove, + .id_table = max8688_id, +}; + +module_i2c_driver(max8688_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX8688"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/pmbus.c b/drivers/hwmon/pmbus/pmbus.c new file mode 100644 index 000000000..7688dab32 --- /dev/null +++ b/drivers/hwmon/pmbus/pmbus.c @@ -0,0 +1,239 @@ +/* + * Hardware monitoring driver for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +/* + * Find sensor groups and status registers on each page. + */ +static void pmbus_find_sensor_groups(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int page; + + /* Sensors detected on page 0 only */ + if (pmbus_check_word_register(client, 0, PMBUS_READ_VIN)) + info->func[0] |= PMBUS_HAVE_VIN; + if (pmbus_check_word_register(client, 0, PMBUS_READ_VCAP)) + info->func[0] |= PMBUS_HAVE_VCAP; + if (pmbus_check_word_register(client, 0, PMBUS_READ_IIN)) + info->func[0] |= PMBUS_HAVE_IIN; + if (pmbus_check_word_register(client, 0, PMBUS_READ_PIN)) + info->func[0] |= PMBUS_HAVE_PIN; + if (info->func[0] + && pmbus_check_byte_register(client, 0, PMBUS_STATUS_INPUT)) + info->func[0] |= PMBUS_HAVE_STATUS_INPUT; + if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_12) && + pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_1)) { + info->func[0] |= PMBUS_HAVE_FAN12; + if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_12)) + info->func[0] |= PMBUS_HAVE_STATUS_FAN12; + } + if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_34) && + pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_3)) { + info->func[0] |= PMBUS_HAVE_FAN34; + if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_34)) + info->func[0] |= PMBUS_HAVE_STATUS_FAN34; + } + if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_1)) + info->func[0] |= PMBUS_HAVE_TEMP; + if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_2)) + info->func[0] |= PMBUS_HAVE_TEMP2; + if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_3)) + info->func[0] |= PMBUS_HAVE_TEMP3; + if (info->func[0] & (PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 + | PMBUS_HAVE_TEMP3) + && pmbus_check_byte_register(client, 0, + PMBUS_STATUS_TEMPERATURE)) + info->func[0] |= PMBUS_HAVE_STATUS_TEMP; + + /* Sensors detected on all pages */ + for (page = 0; page < info->pages; page++) { + if (pmbus_check_word_register(client, page, PMBUS_READ_VOUT)) { + info->func[page] |= PMBUS_HAVE_VOUT; + if (pmbus_check_byte_register(client, page, + PMBUS_STATUS_VOUT)) + info->func[page] |= PMBUS_HAVE_STATUS_VOUT; + } + if (pmbus_check_word_register(client, page, PMBUS_READ_IOUT)) { + info->func[page] |= PMBUS_HAVE_IOUT; + if (pmbus_check_byte_register(client, 0, + PMBUS_STATUS_IOUT)) + info->func[page] |= PMBUS_HAVE_STATUS_IOUT; + } + if (pmbus_check_word_register(client, page, PMBUS_READ_POUT)) + info->func[page] |= PMBUS_HAVE_POUT; + } +} + +/* + * Identify chip parameters. + */ +static int pmbus_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int ret = 0; + + if (!info->pages) { + /* + * Check if the PAGE command is supported. If it is, + * keep setting the page number until it fails or until the + * maximum number of pages has been reached. Assume that + * this is the number of pages supported by the chip. + */ + if (pmbus_check_byte_register(client, 0, PMBUS_PAGE)) { + int page; + + for (page = 1; page < PMBUS_PAGES; page++) { + if (pmbus_set_page(client, page) < 0) + break; + } + pmbus_set_page(client, 0); + info->pages = page; + } else { + info->pages = 1; + } + + pmbus_clear_faults(client); + } + + if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE)) { + int vout_mode; + + vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode >= 0 && vout_mode != 0xff) { + switch (vout_mode >> 5) { + case 0: + break; + case 1: + info->format[PSC_VOLTAGE_OUT] = vid; + info->vrm_version = vr11; + break; + case 2: + info->format[PSC_VOLTAGE_OUT] = direct; + break; + default: + ret = -ENODEV; + goto abort; + } + } + } + + /* + * We should check if the COEFFICIENTS register is supported. + * If it is, and the chip is configured for direct mode, we can read + * the coefficients from the chip, one set per group of sensor + * registers. + * + * To do this, we will need access to a chip which actually supports the + * COEFFICIENTS command, since the command is too complex to implement + * without testing it. Until then, abort if a chip configured for direct + * mode was detected. + */ + if (info->format[PSC_VOLTAGE_OUT] == direct) { + ret = -ENODEV; + goto abort; + } + + /* Try to find sensor groups */ + pmbus_find_sensor_groups(client, info); +abort: + return ret; +} + +static int pmbus_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pmbus_driver_info *info; + struct pmbus_platform_data *pdata = NULL; + struct device *dev = &client->dev; + + info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (!strcmp(id->name, "dps460") || !strcmp(id->name, "dps800") || + !strcmp(id->name, "sgd009")) { + pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->flags = PMBUS_SKIP_STATUS_CHECK; + } + + info->pages = id->driver_data; + info->identify = pmbus_identify; + dev->platform_data = pdata; + + return pmbus_do_probe(client, id, info); +} + +/* + * Use driver_data to set the number of pages supported by the chip. + */ +static const struct i2c_device_id pmbus_id[] = { + {"adp4000", 1}, + {"bmr453", 1}, + {"bmr454", 1}, + {"dps460", 1}, + {"dps800", 1}, + {"mdt040", 1}, + {"ncp4200", 1}, + {"ncp4208", 1}, + {"pdt003", 1}, + {"pdt006", 1}, + {"pdt012", 1}, + {"pmbus", 0}, + {"sgd009", 1}, + {"tps40400", 1}, + {"tps544b20", 1}, + {"tps544b25", 1}, + {"tps544c20", 1}, + {"tps544c25", 1}, + {"udt020", 1}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, pmbus_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver pmbus_driver = { + .driver = { + .name = "pmbus", + }, + .probe = pmbus_probe, + .remove = pmbus_do_remove, + .id_table = pmbus_id, +}; + +module_i2c_driver(pmbus_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("Generic PMBus driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h new file mode 100644 index 000000000..9d731de85 --- /dev/null +++ b/drivers/hwmon/pmbus/pmbus.h @@ -0,0 +1,467 @@ +/* + * pmbus.h - Common defines and structures for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef PMBUS_H +#define PMBUS_H + +#include +#include + +/* + * Registers + */ +enum pmbus_regs { + PMBUS_PAGE = 0x00, + PMBUS_OPERATION = 0x01, + PMBUS_ON_OFF_CONFIG = 0x02, + PMBUS_CLEAR_FAULTS = 0x03, + PMBUS_PHASE = 0x04, + + PMBUS_CAPABILITY = 0x19, + PMBUS_QUERY = 0x1A, + + PMBUS_VOUT_MODE = 0x20, + PMBUS_VOUT_COMMAND = 0x21, + PMBUS_VOUT_TRIM = 0x22, + PMBUS_VOUT_CAL_OFFSET = 0x23, + PMBUS_VOUT_MAX = 0x24, + PMBUS_VOUT_MARGIN_HIGH = 0x25, + PMBUS_VOUT_MARGIN_LOW = 0x26, + PMBUS_VOUT_TRANSITION_RATE = 0x27, + PMBUS_VOUT_DROOP = 0x28, + PMBUS_VOUT_SCALE_LOOP = 0x29, + PMBUS_VOUT_SCALE_MONITOR = 0x2A, + + PMBUS_COEFFICIENTS = 0x30, + PMBUS_POUT_MAX = 0x31, + + PMBUS_FAN_CONFIG_12 = 0x3A, + PMBUS_FAN_COMMAND_1 = 0x3B, + PMBUS_FAN_COMMAND_2 = 0x3C, + PMBUS_FAN_CONFIG_34 = 0x3D, + PMBUS_FAN_COMMAND_3 = 0x3E, + PMBUS_FAN_COMMAND_4 = 0x3F, + + PMBUS_VOUT_OV_FAULT_LIMIT = 0x40, + PMBUS_VOUT_OV_FAULT_RESPONSE = 0x41, + PMBUS_VOUT_OV_WARN_LIMIT = 0x42, + PMBUS_VOUT_UV_WARN_LIMIT = 0x43, + PMBUS_VOUT_UV_FAULT_LIMIT = 0x44, + PMBUS_VOUT_UV_FAULT_RESPONSE = 0x45, + PMBUS_IOUT_OC_FAULT_LIMIT = 0x46, + PMBUS_IOUT_OC_FAULT_RESPONSE = 0x47, + PMBUS_IOUT_OC_LV_FAULT_LIMIT = 0x48, + PMBUS_IOUT_OC_LV_FAULT_RESPONSE = 0x49, + PMBUS_IOUT_OC_WARN_LIMIT = 0x4A, + PMBUS_IOUT_UC_FAULT_LIMIT = 0x4B, + PMBUS_IOUT_UC_FAULT_RESPONSE = 0x4C, + + PMBUS_OT_FAULT_LIMIT = 0x4F, + PMBUS_OT_FAULT_RESPONSE = 0x50, + PMBUS_OT_WARN_LIMIT = 0x51, + PMBUS_UT_WARN_LIMIT = 0x52, + PMBUS_UT_FAULT_LIMIT = 0x53, + PMBUS_UT_FAULT_RESPONSE = 0x54, + PMBUS_VIN_OV_FAULT_LIMIT = 0x55, + PMBUS_VIN_OV_FAULT_RESPONSE = 0x56, + PMBUS_VIN_OV_WARN_LIMIT = 0x57, + PMBUS_VIN_UV_WARN_LIMIT = 0x58, + PMBUS_VIN_UV_FAULT_LIMIT = 0x59, + + PMBUS_IIN_OC_FAULT_LIMIT = 0x5B, + PMBUS_IIN_OC_WARN_LIMIT = 0x5D, + + PMBUS_POUT_OP_FAULT_LIMIT = 0x68, + PMBUS_POUT_OP_WARN_LIMIT = 0x6A, + PMBUS_PIN_OP_WARN_LIMIT = 0x6B, + + PMBUS_STATUS_BYTE = 0x78, + PMBUS_STATUS_WORD = 0x79, + PMBUS_STATUS_VOUT = 0x7A, + PMBUS_STATUS_IOUT = 0x7B, + PMBUS_STATUS_INPUT = 0x7C, + PMBUS_STATUS_TEMPERATURE = 0x7D, + PMBUS_STATUS_CML = 0x7E, + PMBUS_STATUS_OTHER = 0x7F, + PMBUS_STATUS_MFR_SPECIFIC = 0x80, + PMBUS_STATUS_FAN_12 = 0x81, + PMBUS_STATUS_FAN_34 = 0x82, + + PMBUS_READ_VIN = 0x88, + PMBUS_READ_IIN = 0x89, + PMBUS_READ_VCAP = 0x8A, + PMBUS_READ_VOUT = 0x8B, + PMBUS_READ_IOUT = 0x8C, + PMBUS_READ_TEMPERATURE_1 = 0x8D, + PMBUS_READ_TEMPERATURE_2 = 0x8E, + PMBUS_READ_TEMPERATURE_3 = 0x8F, + PMBUS_READ_FAN_SPEED_1 = 0x90, + PMBUS_READ_FAN_SPEED_2 = 0x91, + PMBUS_READ_FAN_SPEED_3 = 0x92, + PMBUS_READ_FAN_SPEED_4 = 0x93, + PMBUS_READ_DUTY_CYCLE = 0x94, + PMBUS_READ_FREQUENCY = 0x95, + PMBUS_READ_POUT = 0x96, + PMBUS_READ_PIN = 0x97, + + PMBUS_REVISION = 0x98, + PMBUS_MFR_ID = 0x99, + PMBUS_MFR_MODEL = 0x9A, + PMBUS_MFR_REVISION = 0x9B, + PMBUS_MFR_LOCATION = 0x9C, + PMBUS_MFR_DATE = 0x9D, + PMBUS_MFR_SERIAL = 0x9E, + +/* + * Virtual registers. + * Useful to support attributes which are not supported by standard PMBus + * registers but exist as manufacturer specific registers on individual chips. + * Must be mapped to real registers in device specific code. + * + * Semantics: + * Virtual registers are all word size. + * READ registers are read-only; writes are either ignored or return an error. + * RESET registers are read/write. Reading reset registers returns zero + * (used for detection), writing any value causes the associated history to be + * reset. + * Virtual registers have to be handled in device specific driver code. Chip + * driver code returns non-negative register values if a virtual register is + * supported, or a negative error code if not. The chip driver may return + * -ENODATA or any other error code in this case, though an error code other + * than -ENODATA is handled more efficiently and thus preferred. Either case, + * the calling PMBus core code will abort if the chip driver returns an error + * code when reading or writing virtual registers. + */ + PMBUS_VIRT_BASE = 0x100, + PMBUS_VIRT_READ_TEMP_AVG, + PMBUS_VIRT_READ_TEMP_MIN, + PMBUS_VIRT_READ_TEMP_MAX, + PMBUS_VIRT_RESET_TEMP_HISTORY, + PMBUS_VIRT_READ_VIN_AVG, + PMBUS_VIRT_READ_VIN_MIN, + PMBUS_VIRT_READ_VIN_MAX, + PMBUS_VIRT_RESET_VIN_HISTORY, + PMBUS_VIRT_READ_IIN_AVG, + PMBUS_VIRT_READ_IIN_MIN, + PMBUS_VIRT_READ_IIN_MAX, + PMBUS_VIRT_RESET_IIN_HISTORY, + PMBUS_VIRT_READ_PIN_AVG, + PMBUS_VIRT_READ_PIN_MIN, + PMBUS_VIRT_READ_PIN_MAX, + PMBUS_VIRT_RESET_PIN_HISTORY, + PMBUS_VIRT_READ_POUT_AVG, + PMBUS_VIRT_READ_POUT_MIN, + PMBUS_VIRT_READ_POUT_MAX, + PMBUS_VIRT_RESET_POUT_HISTORY, + PMBUS_VIRT_READ_VOUT_AVG, + PMBUS_VIRT_READ_VOUT_MIN, + PMBUS_VIRT_READ_VOUT_MAX, + PMBUS_VIRT_RESET_VOUT_HISTORY, + PMBUS_VIRT_READ_IOUT_AVG, + PMBUS_VIRT_READ_IOUT_MIN, + PMBUS_VIRT_READ_IOUT_MAX, + PMBUS_VIRT_RESET_IOUT_HISTORY, + PMBUS_VIRT_READ_TEMP2_AVG, + PMBUS_VIRT_READ_TEMP2_MIN, + PMBUS_VIRT_READ_TEMP2_MAX, + PMBUS_VIRT_RESET_TEMP2_HISTORY, + + PMBUS_VIRT_READ_VMON, + PMBUS_VIRT_VMON_UV_WARN_LIMIT, + PMBUS_VIRT_VMON_OV_WARN_LIMIT, + PMBUS_VIRT_VMON_UV_FAULT_LIMIT, + PMBUS_VIRT_VMON_OV_FAULT_LIMIT, + PMBUS_VIRT_STATUS_VMON, + + /* + * RPM and PWM Fan control + * + * Drivers wanting to expose PWM control must define the behaviour of + * PMBUS_VIRT_PWM_[1-4] and PMBUS_VIRT_PWM_ENABLE_[1-4] in the + * {read,write}_word_data callback. + * + * pmbus core provides a default implementation for + * PMBUS_VIRT_FAN_TARGET_[1-4]. + * + * TARGET, PWM and PWM_ENABLE members must be defined sequentially; + * pmbus core uses the difference between the provided register and + * it's _1 counterpart to calculate the FAN/PWM ID. + */ + PMBUS_VIRT_FAN_TARGET_1, + PMBUS_VIRT_FAN_TARGET_2, + PMBUS_VIRT_FAN_TARGET_3, + PMBUS_VIRT_FAN_TARGET_4, + PMBUS_VIRT_PWM_1, + PMBUS_VIRT_PWM_2, + PMBUS_VIRT_PWM_3, + PMBUS_VIRT_PWM_4, + PMBUS_VIRT_PWM_ENABLE_1, + PMBUS_VIRT_PWM_ENABLE_2, + PMBUS_VIRT_PWM_ENABLE_3, + PMBUS_VIRT_PWM_ENABLE_4, +}; + +/* + * OPERATION + */ +#define PB_OPERATION_CONTROL_ON BIT(7) + +/* + * CAPABILITY + */ +#define PB_CAPABILITY_SMBALERT BIT(4) +#define PB_CAPABILITY_ERROR_CHECK BIT(7) + +/* + * VOUT_MODE + */ +#define PB_VOUT_MODE_MODE_MASK 0xe0 +#define PB_VOUT_MODE_PARAM_MASK 0x1f + +#define PB_VOUT_MODE_LINEAR 0x00 +#define PB_VOUT_MODE_VID 0x20 +#define PB_VOUT_MODE_DIRECT 0x40 + +/* + * Fan configuration + */ +#define PB_FAN_2_PULSE_MASK (BIT(0) | BIT(1)) +#define PB_FAN_2_RPM BIT(2) +#define PB_FAN_2_INSTALLED BIT(3) +#define PB_FAN_1_PULSE_MASK (BIT(4) | BIT(5)) +#define PB_FAN_1_RPM BIT(6) +#define PB_FAN_1_INSTALLED BIT(7) + +enum pmbus_fan_mode { percent = 0, rpm }; + +/* + * STATUS_BYTE, STATUS_WORD (lower) + */ +#define PB_STATUS_NONE_ABOVE BIT(0) +#define PB_STATUS_CML BIT(1) +#define PB_STATUS_TEMPERATURE BIT(2) +#define PB_STATUS_VIN_UV BIT(3) +#define PB_STATUS_IOUT_OC BIT(4) +#define PB_STATUS_VOUT_OV BIT(5) +#define PB_STATUS_OFF BIT(6) +#define PB_STATUS_BUSY BIT(7) + +/* + * STATUS_WORD (upper) + */ +#define PB_STATUS_UNKNOWN BIT(8) +#define PB_STATUS_OTHER BIT(9) +#define PB_STATUS_FANS BIT(10) +#define PB_STATUS_POWER_GOOD_N BIT(11) +#define PB_STATUS_WORD_MFR BIT(12) +#define PB_STATUS_INPUT BIT(13) +#define PB_STATUS_IOUT_POUT BIT(14) +#define PB_STATUS_VOUT BIT(15) + +/* + * STATUS_IOUT + */ +#define PB_POUT_OP_WARNING BIT(0) +#define PB_POUT_OP_FAULT BIT(1) +#define PB_POWER_LIMITING BIT(2) +#define PB_CURRENT_SHARE_FAULT BIT(3) +#define PB_IOUT_UC_FAULT BIT(4) +#define PB_IOUT_OC_WARNING BIT(5) +#define PB_IOUT_OC_LV_FAULT BIT(6) +#define PB_IOUT_OC_FAULT BIT(7) + +/* + * STATUS_VOUT, STATUS_INPUT + */ +#define PB_VOLTAGE_VIN_OFF BIT(3) +#define PB_VOLTAGE_UV_FAULT BIT(4) +#define PB_VOLTAGE_UV_WARNING BIT(5) +#define PB_VOLTAGE_OV_WARNING BIT(6) +#define PB_VOLTAGE_OV_FAULT BIT(7) + +/* + * STATUS_INPUT + */ +#define PB_PIN_OP_WARNING BIT(0) +#define PB_IIN_OC_WARNING BIT(1) +#define PB_IIN_OC_FAULT BIT(2) + +/* + * STATUS_TEMPERATURE + */ +#define PB_TEMP_UT_FAULT BIT(4) +#define PB_TEMP_UT_WARNING BIT(5) +#define PB_TEMP_OT_WARNING BIT(6) +#define PB_TEMP_OT_FAULT BIT(7) + +/* + * STATUS_FAN + */ +#define PB_FAN_AIRFLOW_WARNING BIT(0) +#define PB_FAN_AIRFLOW_FAULT BIT(1) +#define PB_FAN_FAN2_SPEED_OVERRIDE BIT(2) +#define PB_FAN_FAN1_SPEED_OVERRIDE BIT(3) +#define PB_FAN_FAN2_WARNING BIT(4) +#define PB_FAN_FAN1_WARNING BIT(5) +#define PB_FAN_FAN2_FAULT BIT(6) +#define PB_FAN_FAN1_FAULT BIT(7) + +/* + * CML_FAULT_STATUS + */ +#define PB_CML_FAULT_OTHER_MEM_LOGIC BIT(0) +#define PB_CML_FAULT_OTHER_COMM BIT(1) +#define PB_CML_FAULT_PROCESSOR BIT(3) +#define PB_CML_FAULT_MEMORY BIT(4) +#define PB_CML_FAULT_PACKET_ERROR BIT(5) +#define PB_CML_FAULT_INVALID_DATA BIT(6) +#define PB_CML_FAULT_INVALID_COMMAND BIT(7) + +enum pmbus_sensor_classes { + PSC_VOLTAGE_IN = 0, + PSC_VOLTAGE_OUT, + PSC_CURRENT_IN, + PSC_CURRENT_OUT, + PSC_POWER, + PSC_TEMPERATURE, + PSC_FAN, + PSC_PWM, + PSC_NUM_CLASSES /* Number of power sensor classes */ +}; + +#define PMBUS_PAGES 32 /* Per PMBus specification */ + +/* Functionality bit mask */ +#define PMBUS_HAVE_VIN BIT(0) +#define PMBUS_HAVE_VCAP BIT(1) +#define PMBUS_HAVE_VOUT BIT(2) +#define PMBUS_HAVE_IIN BIT(3) +#define PMBUS_HAVE_IOUT BIT(4) +#define PMBUS_HAVE_PIN BIT(5) +#define PMBUS_HAVE_POUT BIT(6) +#define PMBUS_HAVE_FAN12 BIT(7) +#define PMBUS_HAVE_FAN34 BIT(8) +#define PMBUS_HAVE_TEMP BIT(9) +#define PMBUS_HAVE_TEMP2 BIT(10) +#define PMBUS_HAVE_TEMP3 BIT(11) +#define PMBUS_HAVE_STATUS_VOUT BIT(12) +#define PMBUS_HAVE_STATUS_IOUT BIT(13) +#define PMBUS_HAVE_STATUS_INPUT BIT(14) +#define PMBUS_HAVE_STATUS_TEMP BIT(15) +#define PMBUS_HAVE_STATUS_FAN12 BIT(16) +#define PMBUS_HAVE_STATUS_FAN34 BIT(17) +#define PMBUS_HAVE_VMON BIT(18) +#define PMBUS_HAVE_STATUS_VMON BIT(19) +#define PMBUS_HAVE_PWM12 BIT(20) +#define PMBUS_HAVE_PWM34 BIT(21) + +#define PMBUS_PAGE_VIRTUAL BIT(31) + +enum pmbus_data_format { linear = 0, direct, vid }; +enum vrm_version { vr11 = 0, vr12, vr13 }; + +struct pmbus_driver_info { + int pages; /* Total number of pages */ + enum pmbus_data_format format[PSC_NUM_CLASSES]; + enum vrm_version vrm_version; + /* + * Support one set of coefficients for each sensor type + * Used for chips providing data in direct mode. + */ + int m[PSC_NUM_CLASSES]; /* mantissa for direct data format */ + int b[PSC_NUM_CLASSES]; /* offset */ + int R[PSC_NUM_CLASSES]; /* exponent */ + + u32 func[PMBUS_PAGES]; /* Functionality, per page */ + /* + * The following functions map manufacturing specific register values + * to PMBus standard register values. Specify only if mapping is + * necessary. + * Functions return the register value (read) or zero (write) if + * successful. A return value of -ENODATA indicates that there is no + * manufacturer specific register, but that a standard PMBus register + * may exist. Any other negative return value indicates that the + * register does not exist, and that no attempt should be made to read + * the standard register. + */ + int (*read_byte_data)(struct i2c_client *client, int page, int reg); + int (*read_word_data)(struct i2c_client *client, int page, int reg); + int (*write_word_data)(struct i2c_client *client, int page, int reg, + u16 word); + int (*write_byte)(struct i2c_client *client, int page, u8 value); + /* + * The identify function determines supported PMBus functionality. + * This function is only necessary if a chip driver supports multiple + * chips, and the chip functionality is not pre-determined. + */ + int (*identify)(struct i2c_client *client, + struct pmbus_driver_info *info); + + /* Regulator functionality, if supported by this chip driver. */ + int num_regulators; + const struct regulator_desc *reg_desc; +}; + +/* Regulator ops */ + +extern const struct regulator_ops pmbus_regulator_ops; + +/* Macro for filling in array of struct regulator_desc */ +#define PMBUS_REGULATOR(_name, _id) \ + [_id] = { \ + .name = (_name # _id), \ + .id = (_id), \ + .of_match = of_match_ptr(_name # _id), \ + .regulators_node = of_match_ptr("regulators"), \ + .ops = &pmbus_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + } + +/* Function declarations */ + +void pmbus_clear_cache(struct i2c_client *client); +int pmbus_set_page(struct i2c_client *client, int page); +int pmbus_read_word_data(struct i2c_client *client, int page, u8 reg); +int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg, u16 word); +int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg); +int pmbus_write_byte(struct i2c_client *client, int page, u8 value); +int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, + u8 value); +int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, + u8 mask, u8 value); +void pmbus_clear_faults(struct i2c_client *client); +bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg); +bool pmbus_check_word_register(struct i2c_client *client, int page, int reg); +int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, + struct pmbus_driver_info *info); +int pmbus_do_remove(struct i2c_client *client); +const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client + *client); +int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode); +int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode); +int pmbus_update_fan(struct i2c_client *client, int page, int id, + u8 config, u8 mask, u16 command); +struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client); + +#endif /* PMBUS_H */ diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c new file mode 100644 index 000000000..bef28e955 --- /dev/null +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -0,0 +1,2450 @@ +/* + * Hardware monitoring driver for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +/* + * Number of additional attribute pointers to allocate + * with each call to krealloc + */ +#define PMBUS_ATTR_ALLOC_SIZE 32 + +/* + * Index into status register array, per status register group + */ +#define PB_STATUS_BASE 0 +#define PB_STATUS_VOUT_BASE (PB_STATUS_BASE + PMBUS_PAGES) +#define PB_STATUS_IOUT_BASE (PB_STATUS_VOUT_BASE + PMBUS_PAGES) +#define PB_STATUS_FAN_BASE (PB_STATUS_IOUT_BASE + PMBUS_PAGES) +#define PB_STATUS_FAN34_BASE (PB_STATUS_FAN_BASE + PMBUS_PAGES) +#define PB_STATUS_TEMP_BASE (PB_STATUS_FAN34_BASE + PMBUS_PAGES) +#define PB_STATUS_INPUT_BASE (PB_STATUS_TEMP_BASE + PMBUS_PAGES) +#define PB_STATUS_VMON_BASE (PB_STATUS_INPUT_BASE + 1) + +#define PB_NUM_STATUS_REG (PB_STATUS_VMON_BASE + 1) + +#define PMBUS_NAME_SIZE 24 + +struct pmbus_sensor { + struct pmbus_sensor *next; + char name[PMBUS_NAME_SIZE]; /* sysfs sensor name */ + struct device_attribute attribute; + u8 page; /* page number */ + u16 reg; /* register */ + enum pmbus_sensor_classes class; /* sensor class */ + bool update; /* runtime sensor update needed */ + bool convert; /* Whether or not to apply linear/vid/direct */ + int data; /* Sensor data. + Negative if there was a read error */ +}; +#define to_pmbus_sensor(_attr) \ + container_of(_attr, struct pmbus_sensor, attribute) + +struct pmbus_boolean { + char name[PMBUS_NAME_SIZE]; /* sysfs boolean name */ + struct sensor_device_attribute attribute; + struct pmbus_sensor *s1; + struct pmbus_sensor *s2; +}; +#define to_pmbus_boolean(_attr) \ + container_of(_attr, struct pmbus_boolean, attribute) + +struct pmbus_label { + char name[PMBUS_NAME_SIZE]; /* sysfs label name */ + struct device_attribute attribute; + char label[PMBUS_NAME_SIZE]; /* label */ +}; +#define to_pmbus_label(_attr) \ + container_of(_attr, struct pmbus_label, attribute) + +struct pmbus_data { + struct device *dev; + struct device *hwmon_dev; + + u32 flags; /* from platform data */ + + int exponent[PMBUS_PAGES]; + /* linear mode: exponent for output voltages */ + + const struct pmbus_driver_info *info; + + int max_attributes; + int num_attributes; + struct attribute_group group; + const struct attribute_group *groups[2]; + struct dentry *debugfs; /* debugfs device directory */ + + struct pmbus_sensor *sensors; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + + /* + * A single status register covers multiple attributes, + * so we keep them all together. + */ + u16 status[PB_NUM_STATUS_REG]; + + bool has_status_word; /* device uses STATUS_WORD register */ + int (*read_status)(struct i2c_client *client, int page); + + u8 currpage; +}; + +struct pmbus_debugfs_entry { + struct i2c_client *client; + u8 page; + u8 reg; +}; + +static const int pmbus_fan_rpm_mask[] = { + PB_FAN_1_RPM, + PB_FAN_2_RPM, + PB_FAN_1_RPM, + PB_FAN_2_RPM, +}; + +static const int pmbus_fan_config_registers[] = { + PMBUS_FAN_CONFIG_12, + PMBUS_FAN_CONFIG_12, + PMBUS_FAN_CONFIG_34, + PMBUS_FAN_CONFIG_34 +}; + +static const int pmbus_fan_command_registers[] = { + PMBUS_FAN_COMMAND_1, + PMBUS_FAN_COMMAND_2, + PMBUS_FAN_COMMAND_3, + PMBUS_FAN_COMMAND_4, +}; + +void pmbus_clear_cache(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + + data->valid = false; +} +EXPORT_SYMBOL_GPL(pmbus_clear_cache); + +int pmbus_set_page(struct i2c_client *client, int page) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + int rv; + + if (page < 0 || page == data->currpage) + return 0; + + if (!(data->info->func[page] & PMBUS_PAGE_VIRTUAL)) { + rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (rv < 0) + return rv; + + rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE); + if (rv < 0) + return rv; + + if (rv != page) + return -EIO; + } + + data->currpage = page; + + return 0; +} +EXPORT_SYMBOL_GPL(pmbus_set_page); + +int pmbus_write_byte(struct i2c_client *client, int page, u8 value) +{ + int rv; + + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + + return i2c_smbus_write_byte(client, value); +} +EXPORT_SYMBOL_GPL(pmbus_write_byte); + +/* + * _pmbus_write_byte() is similar to pmbus_write_byte(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_write_byte(struct i2c_client *client, int page, u8 value) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->write_byte) { + status = info->write_byte(client, page, value); + if (status != -ENODATA) + return status; + } + return pmbus_write_byte(client, page, value); +} + +int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg, + u16 word) +{ + int rv; + + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + + return i2c_smbus_write_word_data(client, reg, word); +} +EXPORT_SYMBOL_GPL(pmbus_write_word_data); + + +static int pmbus_write_virt_reg(struct i2c_client *client, int page, int reg, + u16 word) +{ + int bit; + int id; + int rv; + + switch (reg) { + case PMBUS_VIRT_FAN_TARGET_1 ... PMBUS_VIRT_FAN_TARGET_4: + id = reg - PMBUS_VIRT_FAN_TARGET_1; + bit = pmbus_fan_rpm_mask[id]; + rv = pmbus_update_fan(client, page, id, bit, bit, word); + break; + default: + rv = -ENXIO; + break; + } + + return rv; +} + +/* + * _pmbus_write_word_data() is similar to pmbus_write_word_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->write_word_data) { + status = info->write_word_data(client, page, reg, word); + if (status != -ENODATA) + return status; + } + + if (reg >= PMBUS_VIRT_BASE) + return pmbus_write_virt_reg(client, page, reg, word); + + return pmbus_write_word_data(client, page, reg, word); +} + +int pmbus_update_fan(struct i2c_client *client, int page, int id, + u8 config, u8 mask, u16 command) +{ + int from; + int rv; + u8 to; + + from = pmbus_read_byte_data(client, page, + pmbus_fan_config_registers[id]); + if (from < 0) + return from; + + to = (from & ~mask) | (config & mask); + if (to != from) { + rv = pmbus_write_byte_data(client, page, + pmbus_fan_config_registers[id], to); + if (rv < 0) + return rv; + } + + return _pmbus_write_word_data(client, page, + pmbus_fan_command_registers[id], command); +} +EXPORT_SYMBOL_GPL(pmbus_update_fan); + +int pmbus_read_word_data(struct i2c_client *client, int page, u8 reg) +{ + int rv; + + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + + return i2c_smbus_read_word_data(client, reg); +} +EXPORT_SYMBOL_GPL(pmbus_read_word_data); + +static int pmbus_read_virt_reg(struct i2c_client *client, int page, int reg) +{ + int rv; + int id; + + switch (reg) { + case PMBUS_VIRT_FAN_TARGET_1 ... PMBUS_VIRT_FAN_TARGET_4: + id = reg - PMBUS_VIRT_FAN_TARGET_1; + rv = pmbus_get_fan_rate_device(client, page, id, rpm); + break; + default: + rv = -ENXIO; + break; + } + + return rv; +} + +/* + * _pmbus_read_word_data() is similar to pmbus_read_word_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_read_word_data(struct i2c_client *client, int page, int reg) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->read_word_data) { + status = info->read_word_data(client, page, reg); + if (status != -ENODATA) + return status; + } + + if (reg >= PMBUS_VIRT_BASE) + return pmbus_read_virt_reg(client, page, reg); + + return pmbus_read_word_data(client, page, reg); +} + +int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg) +{ + int rv; + + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + + return i2c_smbus_read_byte_data(client, reg); +} +EXPORT_SYMBOL_GPL(pmbus_read_byte_data); + +int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, u8 value) +{ + int rv; + + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + + return i2c_smbus_write_byte_data(client, reg, value); +} +EXPORT_SYMBOL_GPL(pmbus_write_byte_data); + +int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, + u8 mask, u8 value) +{ + unsigned int tmp; + int rv; + + rv = pmbus_read_byte_data(client, page, reg); + if (rv < 0) + return rv; + + tmp = (rv & ~mask) | (value & mask); + + if (tmp != rv) + rv = pmbus_write_byte_data(client, page, reg, tmp); + + return rv; +} +EXPORT_SYMBOL_GPL(pmbus_update_byte_data); + +/* + * _pmbus_read_byte_data() is similar to pmbus_read_byte_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->read_byte_data) { + status = info->read_byte_data(client, page, reg); + if (status != -ENODATA) + return status; + } + return pmbus_read_byte_data(client, page, reg); +} + +static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page, + int reg) +{ + struct pmbus_sensor *sensor; + + for (sensor = data->sensors; sensor; sensor = sensor->next) { + if (sensor->page == page && sensor->reg == reg) + return sensor; + } + + return ERR_PTR(-EINVAL); +} + +static int pmbus_get_fan_rate(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode, + bool from_cache) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + bool want_rpm, have_rpm; + struct pmbus_sensor *s; + int config; + int reg; + + want_rpm = (mode == rpm); + + if (from_cache) { + reg = want_rpm ? PMBUS_VIRT_FAN_TARGET_1 : PMBUS_VIRT_PWM_1; + s = pmbus_find_sensor(data, page, reg + id); + if (IS_ERR(s)) + return PTR_ERR(s); + + return s->data; + } + + config = pmbus_read_byte_data(client, page, + pmbus_fan_config_registers[id]); + if (config < 0) + return config; + + have_rpm = !!(config & pmbus_fan_rpm_mask[id]); + if (want_rpm == have_rpm) + return pmbus_read_word_data(client, page, + pmbus_fan_command_registers[id]); + + /* Can't sensibly map between RPM and PWM, just return zero */ + return 0; +} + +int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode) +{ + return pmbus_get_fan_rate(client, page, id, mode, false); +} +EXPORT_SYMBOL_GPL(pmbus_get_fan_rate_device); + +int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode) +{ + return pmbus_get_fan_rate(client, page, id, mode, true); +} +EXPORT_SYMBOL_GPL(pmbus_get_fan_rate_cached); + +static void pmbus_clear_fault_page(struct i2c_client *client, int page) +{ + _pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); +} + +void pmbus_clear_faults(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + int i; + + for (i = 0; i < data->info->pages; i++) + pmbus_clear_fault_page(client, i); +} +EXPORT_SYMBOL_GPL(pmbus_clear_faults); + +static int pmbus_check_status_cml(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + int status, status2; + + status = data->read_status(client, -1); + if (status < 0 || (status & PB_STATUS_CML)) { + status2 = _pmbus_read_byte_data(client, -1, PMBUS_STATUS_CML); + if (status2 < 0 || (status2 & PB_CML_FAULT_INVALID_COMMAND)) + return -EIO; + } + return 0; +} + +static bool pmbus_check_register(struct i2c_client *client, + int (*func)(struct i2c_client *client, + int page, int reg), + int page, int reg) +{ + int rv; + struct pmbus_data *data = i2c_get_clientdata(client); + + rv = func(client, page, reg); + if (rv >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK)) + rv = pmbus_check_status_cml(client); + pmbus_clear_fault_page(client, -1); + return rv >= 0; +} + +static bool pmbus_check_status_register(struct i2c_client *client, int page) +{ + int status; + struct pmbus_data *data = i2c_get_clientdata(client); + + status = data->read_status(client, page); + if (status >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK) && + (status & PB_STATUS_CML)) { + status = _pmbus_read_byte_data(client, -1, PMBUS_STATUS_CML); + if (status < 0 || (status & PB_CML_FAULT_INVALID_COMMAND)) + status = -EIO; + } + + pmbus_clear_fault_page(client, -1); + return status >= 0; +} + +bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg) +{ + return pmbus_check_register(client, _pmbus_read_byte_data, page, reg); +} +EXPORT_SYMBOL_GPL(pmbus_check_byte_register); + +bool pmbus_check_word_register(struct i2c_client *client, int page, int reg) +{ + return pmbus_check_register(client, _pmbus_read_word_data, page, reg); +} +EXPORT_SYMBOL_GPL(pmbus_check_word_register); + +const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + + return data->info; +} +EXPORT_SYMBOL_GPL(pmbus_get_driver_info); + +static struct _pmbus_status { + u32 func; + u16 base; + u16 reg; +} pmbus_status[] = { + { PMBUS_HAVE_STATUS_VOUT, PB_STATUS_VOUT_BASE, PMBUS_STATUS_VOUT }, + { PMBUS_HAVE_STATUS_IOUT, PB_STATUS_IOUT_BASE, PMBUS_STATUS_IOUT }, + { PMBUS_HAVE_STATUS_TEMP, PB_STATUS_TEMP_BASE, + PMBUS_STATUS_TEMPERATURE }, + { PMBUS_HAVE_STATUS_FAN12, PB_STATUS_FAN_BASE, PMBUS_STATUS_FAN_12 }, + { PMBUS_HAVE_STATUS_FAN34, PB_STATUS_FAN34_BASE, PMBUS_STATUS_FAN_34 }, +}; + +static struct pmbus_data *pmbus_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + struct pmbus_sensor *sensor; + + mutex_lock(&data->update_lock); + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + int i, j; + + for (i = 0; i < info->pages; i++) { + data->status[PB_STATUS_BASE + i] + = data->read_status(client, i); + for (j = 0; j < ARRAY_SIZE(pmbus_status); j++) { + struct _pmbus_status *s = &pmbus_status[j]; + + if (!(info->func[i] & s->func)) + continue; + data->status[s->base + i] + = _pmbus_read_byte_data(client, i, + s->reg); + } + } + + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) + data->status[PB_STATUS_INPUT_BASE] + = _pmbus_read_byte_data(client, 0, + PMBUS_STATUS_INPUT); + + if (info->func[0] & PMBUS_HAVE_STATUS_VMON) + data->status[PB_STATUS_VMON_BASE] + = _pmbus_read_byte_data(client, 0, + PMBUS_VIRT_STATUS_VMON); + + for (sensor = data->sensors; sensor; sensor = sensor->next) { + if (!data->valid || sensor->update) + sensor->data + = _pmbus_read_word_data(client, + sensor->page, + sensor->reg); + } + pmbus_clear_faults(client); + data->last_updated = jiffies; + data->valid = 1; + } + mutex_unlock(&data->update_lock); + return data; +} + +/* + * Convert linear sensor values to milli- or micro-units + * depending on sensor type. + */ +static long pmbus_reg2data_linear(struct pmbus_data *data, + struct pmbus_sensor *sensor) +{ + s16 exponent; + s32 mantissa; + long val; + + if (sensor->class == PSC_VOLTAGE_OUT) { /* LINEAR16 */ + exponent = data->exponent[sensor->page]; + mantissa = (u16) sensor->data; + } else { /* LINEAR11 */ + exponent = ((s16)sensor->data) >> 11; + mantissa = ((s16)((sensor->data & 0x7ff) << 5)) >> 5; + } + + val = mantissa; + + /* scale result to milli-units for all sensors except fans */ + if (sensor->class != PSC_FAN) + val = val * 1000L; + + /* scale result to micro-units for power sensors */ + if (sensor->class == PSC_POWER) + val = val * 1000L; + + if (exponent >= 0) + val <<= exponent; + else + val >>= -exponent; + + return val; +} + +/* + * Convert direct sensor values to milli- or micro-units + * depending on sensor type. + */ +static long pmbus_reg2data_direct(struct pmbus_data *data, + struct pmbus_sensor *sensor) +{ + s64 b, val = (s16)sensor->data; + s32 m, R; + + m = data->info->m[sensor->class]; + b = data->info->b[sensor->class]; + R = data->info->R[sensor->class]; + + if (m == 0) + return 0; + + /* X = 1/m * (Y * 10^-R - b) */ + R = -R; + /* scale result to milli-units for everything but fans */ + if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) { + R += 3; + b *= 1000; + } + + /* scale result to micro-units for power sensors */ + if (sensor->class == PSC_POWER) { + R += 3; + b *= 1000; + } + + while (R > 0) { + val *= 10; + R--; + } + while (R < 0) { + val = div_s64(val + 5LL, 10L); /* round closest */ + R++; + } + + val = div_s64(val - b, m); + return clamp_val(val, LONG_MIN, LONG_MAX); +} + +/* + * Convert VID sensor values to milli- or micro-units + * depending on sensor type. + */ +static long pmbus_reg2data_vid(struct pmbus_data *data, + struct pmbus_sensor *sensor) +{ + long val = sensor->data; + long rv = 0; + + switch (data->info->vrm_version) { + case vr11: + if (val >= 0x02 && val <= 0xb2) + rv = DIV_ROUND_CLOSEST(160000 - (val - 2) * 625, 100); + break; + case vr12: + if (val >= 0x01) + rv = 250 + (val - 1) * 5; + break; + case vr13: + if (val >= 0x01) + rv = 500 + (val - 1) * 10; + break; + } + return rv; +} + +static long pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor) +{ + long val; + + if (!sensor->convert) + return sensor->data; + + switch (data->info->format[sensor->class]) { + case direct: + val = pmbus_reg2data_direct(data, sensor); + break; + case vid: + val = pmbus_reg2data_vid(data, sensor); + break; + case linear: + default: + val = pmbus_reg2data_linear(data, sensor); + break; + } + return val; +} + +#define MAX_MANTISSA (1023 * 1000) +#define MIN_MANTISSA (511 * 1000) + +static u16 pmbus_data2reg_linear(struct pmbus_data *data, + struct pmbus_sensor *sensor, long val) +{ + s16 exponent = 0, mantissa; + bool negative = false; + + /* simple case */ + if (val == 0) + return 0; + + if (sensor->class == PSC_VOLTAGE_OUT) { + /* LINEAR16 does not support negative voltages */ + if (val < 0) + return 0; + + /* + * For a static exponents, we don't have a choice + * but to adjust the value to it. + */ + if (data->exponent[sensor->page] < 0) + val <<= -data->exponent[sensor->page]; + else + val >>= data->exponent[sensor->page]; + val = DIV_ROUND_CLOSEST(val, 1000); + return val & 0xffff; + } + + if (val < 0) { + negative = true; + val = -val; + } + + /* Power is in uW. Convert to mW before converting. */ + if (sensor->class == PSC_POWER) + val = DIV_ROUND_CLOSEST(val, 1000L); + + /* + * For simplicity, convert fan data to milli-units + * before calculating the exponent. + */ + if (sensor->class == PSC_FAN) + val = val * 1000; + + /* Reduce large mantissa until it fits into 10 bit */ + while (val >= MAX_MANTISSA && exponent < 15) { + exponent++; + val >>= 1; + } + /* Increase small mantissa to improve precision */ + while (val < MIN_MANTISSA && exponent > -15) { + exponent--; + val <<= 1; + } + + /* Convert mantissa from milli-units to units */ + mantissa = DIV_ROUND_CLOSEST(val, 1000); + + /* Ensure that resulting number is within range */ + if (mantissa > 0x3ff) + mantissa = 0x3ff; + + /* restore sign */ + if (negative) + mantissa = -mantissa; + + /* Convert to 5 bit exponent, 11 bit mantissa */ + return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); +} + +static u16 pmbus_data2reg_direct(struct pmbus_data *data, + struct pmbus_sensor *sensor, long val) +{ + s64 b, val64 = val; + s32 m, R; + + m = data->info->m[sensor->class]; + b = data->info->b[sensor->class]; + R = data->info->R[sensor->class]; + + /* Power is in uW. Adjust R and b. */ + if (sensor->class == PSC_POWER) { + R -= 3; + b *= 1000; + } + + /* Calculate Y = (m * X + b) * 10^R */ + if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) { + R -= 3; /* Adjust R and b for data in milli-units */ + b *= 1000; + } + val64 = val64 * m + b; + + while (R > 0) { + val64 *= 10; + R--; + } + while (R < 0) { + val64 = div_s64(val64 + 5LL, 10L); /* round closest */ + R++; + } + + return (u16)clamp_val(val64, S16_MIN, S16_MAX); +} + +static u16 pmbus_data2reg_vid(struct pmbus_data *data, + struct pmbus_sensor *sensor, long val) +{ + val = clamp_val(val, 500, 1600); + + return 2 + DIV_ROUND_CLOSEST((1600 - val) * 100, 625); +} + +static u16 pmbus_data2reg(struct pmbus_data *data, + struct pmbus_sensor *sensor, long val) +{ + u16 regval; + + if (!sensor->convert) + return val; + + switch (data->info->format[sensor->class]) { + case direct: + regval = pmbus_data2reg_direct(data, sensor, val); + break; + case vid: + regval = pmbus_data2reg_vid(data, sensor, val); + break; + case linear: + default: + regval = pmbus_data2reg_linear(data, sensor, val); + break; + } + return regval; +} + +/* + * Return boolean calculated from converted data. + * defines a status register index and mask. + * The mask is in the lower 8 bits, the register index is in bits 8..23. + * + * The associated pmbus_boolean structure contains optional pointers to two + * sensor attributes. If specified, those attributes are compared against each + * other to determine if a limit has been exceeded. + * + * If the sensor attribute pointers are NULL, the function returns true if + * (status[reg] & mask) is true. + * + * If sensor attribute pointers are provided, a comparison against a specified + * limit has to be performed to determine the boolean result. + * In this case, the function returns true if v1 >= v2 (where v1 and v2 are + * sensor values referenced by sensor attribute pointers s1 and s2). + * + * To determine if an object exceeds upper limits, specify = . + * To determine if an object exceeds lower limits, specify = . + * + * If a negative value is stored in any of the referenced registers, this value + * reflects an error code which will be returned. + */ +static int pmbus_get_boolean(struct pmbus_data *data, struct pmbus_boolean *b, + int index) +{ + struct pmbus_sensor *s1 = b->s1; + struct pmbus_sensor *s2 = b->s2; + u16 reg = (index >> 16) & 0xffff; + u16 mask = index & 0xffff; + int ret, status; + u16 regval; + + status = data->status[reg]; + if (status < 0) + return status; + + regval = status & mask; + if (!s1 && !s2) { + ret = !!regval; + } else if (!s1 || !s2) { + WARN(1, "Bad boolean descriptor %p: s1=%p, s2=%p\n", b, s1, s2); + return 0; + } else { + long v1, v2; + + if (s1->data < 0) + return s1->data; + if (s2->data < 0) + return s2->data; + + v1 = pmbus_reg2data(data, s1); + v2 = pmbus_reg2data(data, s2); + ret = !!(regval && v1 >= v2); + } + return ret; +} + +static ssize_t pmbus_show_boolean(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pmbus_boolean *boolean = to_pmbus_boolean(attr); + struct pmbus_data *data = pmbus_update_device(dev); + int val; + + val = pmbus_get_boolean(data, boolean, attr->index); + if (val < 0) + return val; + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t pmbus_show_sensor(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct pmbus_data *data = pmbus_update_device(dev); + struct pmbus_sensor *sensor = to_pmbus_sensor(devattr); + + if (sensor->data < 0) + return sensor->data; + + return snprintf(buf, PAGE_SIZE, "%ld\n", pmbus_reg2data(data, sensor)); +} + +static ssize_t pmbus_set_sensor(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor *sensor = to_pmbus_sensor(devattr); + ssize_t rv = count; + long val = 0; + int ret; + u16 regval; + + if (kstrtol(buf, 10, &val) < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + regval = pmbus_data2reg(data, sensor, val); + ret = _pmbus_write_word_data(client, sensor->page, sensor->reg, regval); + if (ret < 0) + rv = ret; + else + sensor->data = regval; + mutex_unlock(&data->update_lock); + return rv; +} + +static ssize_t pmbus_show_label(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct pmbus_label *label = to_pmbus_label(da); + + return snprintf(buf, PAGE_SIZE, "%s\n", label->label); +} + +static int pmbus_add_attribute(struct pmbus_data *data, struct attribute *attr) +{ + if (data->num_attributes >= data->max_attributes - 1) { + int new_max_attrs = data->max_attributes + PMBUS_ATTR_ALLOC_SIZE; + void *new_attrs = krealloc(data->group.attrs, + new_max_attrs * sizeof(void *), + GFP_KERNEL); + if (!new_attrs) + return -ENOMEM; + data->group.attrs = new_attrs; + data->max_attributes = new_max_attrs; + } + + data->group.attrs[data->num_attributes++] = attr; + data->group.attrs[data->num_attributes] = NULL; + return 0; +} + +static void pmbus_dev_attr_init(struct device_attribute *dev_attr, + const char *name, + umode_t mode, + ssize_t (*show)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*store)(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count)) +{ + sysfs_attr_init(&dev_attr->attr); + dev_attr->attr.name = name; + dev_attr->attr.mode = mode; + dev_attr->show = show; + dev_attr->store = store; +} + +static void pmbus_attr_init(struct sensor_device_attribute *a, + const char *name, + umode_t mode, + ssize_t (*show)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*store)(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count), + int idx) +{ + pmbus_dev_attr_init(&a->dev_attr, name, mode, show, store); + a->index = idx; +} + +static int pmbus_add_boolean(struct pmbus_data *data, + const char *name, const char *type, int seq, + struct pmbus_sensor *s1, + struct pmbus_sensor *s2, + u16 reg, u16 mask) +{ + struct pmbus_boolean *boolean; + struct sensor_device_attribute *a; + + boolean = devm_kzalloc(data->dev, sizeof(*boolean), GFP_KERNEL); + if (!boolean) + return -ENOMEM; + + a = &boolean->attribute; + + snprintf(boolean->name, sizeof(boolean->name), "%s%d_%s", + name, seq, type); + boolean->s1 = s1; + boolean->s2 = s2; + pmbus_attr_init(a, boolean->name, S_IRUGO, pmbus_show_boolean, NULL, + (reg << 16) | mask); + + return pmbus_add_attribute(data, &a->dev_attr.attr); +} + +static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data, + const char *name, const char *type, + int seq, int page, int reg, + enum pmbus_sensor_classes class, + bool update, bool readonly, + bool convert) +{ + struct pmbus_sensor *sensor; + struct device_attribute *a; + + sensor = devm_kzalloc(data->dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return NULL; + a = &sensor->attribute; + + if (type) + snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s", + name, seq, type); + else + snprintf(sensor->name, sizeof(sensor->name), "%s%d", + name, seq); + + sensor->page = page; + sensor->reg = reg; + sensor->class = class; + sensor->update = update; + sensor->convert = convert; + pmbus_dev_attr_init(a, sensor->name, + readonly ? S_IRUGO : S_IRUGO | S_IWUSR, + pmbus_show_sensor, pmbus_set_sensor); + + if (pmbus_add_attribute(data, &a->attr)) + return NULL; + + sensor->next = data->sensors; + data->sensors = sensor; + + return sensor; +} + +static int pmbus_add_label(struct pmbus_data *data, + const char *name, int seq, + const char *lstring, int index) +{ + struct pmbus_label *label; + struct device_attribute *a; + + label = devm_kzalloc(data->dev, sizeof(*label), GFP_KERNEL); + if (!label) + return -ENOMEM; + + a = &label->attribute; + + snprintf(label->name, sizeof(label->name), "%s%d_label", name, seq); + if (!index) + strncpy(label->label, lstring, sizeof(label->label) - 1); + else + snprintf(label->label, sizeof(label->label), "%s%d", lstring, + index); + + pmbus_dev_attr_init(a, label->name, S_IRUGO, pmbus_show_label, NULL); + return pmbus_add_attribute(data, &a->attr); +} + +/* + * Search for attributes. Allocate sensors, booleans, and labels as needed. + */ + +/* + * The pmbus_limit_attr structure describes a single limit attribute + * and its associated alarm attribute. + */ +struct pmbus_limit_attr { + u16 reg; /* Limit register */ + u16 sbit; /* Alarm attribute status bit */ + bool update; /* True if register needs updates */ + bool low; /* True if low limit; for limits with compare + functions only */ + const char *attr; /* Attribute name */ + const char *alarm; /* Alarm attribute name */ +}; + +/* + * The pmbus_sensor_attr structure describes one sensor attribute. This + * description includes a reference to the associated limit attributes. + */ +struct pmbus_sensor_attr { + u16 reg; /* sensor register */ + u16 gbit; /* generic status bit */ + u8 nlimit; /* # of limit registers */ + enum pmbus_sensor_classes class;/* sensor class */ + const char *label; /* sensor label */ + bool paged; /* true if paged sensor */ + bool update; /* true if update needed */ + bool compare; /* true if compare function needed */ + u32 func; /* sensor mask */ + u32 sfunc; /* sensor status mask */ + int sbase; /* status base register */ + const struct pmbus_limit_attr *limit;/* limit registers */ +}; + +/* + * Add a set of limit attributes and, if supported, the associated + * alarm attributes. + * returns 0 if no alarm register found, 1 if an alarm register was found, + * < 0 on errors. + */ +static int pmbus_add_limit_attrs(struct i2c_client *client, + struct pmbus_data *data, + const struct pmbus_driver_info *info, + const char *name, int index, int page, + struct pmbus_sensor *base, + const struct pmbus_sensor_attr *attr) +{ + const struct pmbus_limit_attr *l = attr->limit; + int nlimit = attr->nlimit; + int have_alarm = 0; + int i, ret; + struct pmbus_sensor *curr; + + for (i = 0; i < nlimit; i++) { + if (pmbus_check_word_register(client, page, l->reg)) { + curr = pmbus_add_sensor(data, name, l->attr, index, + page, l->reg, attr->class, + attr->update || l->update, + false, true); + if (!curr) + return -ENOMEM; + if (l->sbit && (info->func[page] & attr->sfunc)) { + ret = pmbus_add_boolean(data, name, + l->alarm, index, + attr->compare ? l->low ? curr : base + : NULL, + attr->compare ? l->low ? base : curr + : NULL, + attr->sbase + page, l->sbit); + if (ret) + return ret; + have_alarm = 1; + } + } + l++; + } + return have_alarm; +} + +static int pmbus_add_sensor_attrs_one(struct i2c_client *client, + struct pmbus_data *data, + const struct pmbus_driver_info *info, + const char *name, + int index, int page, + const struct pmbus_sensor_attr *attr, + bool paged) +{ + struct pmbus_sensor *base; + bool upper = !!(attr->gbit & 0xff00); /* need to check STATUS_WORD */ + int ret; + + if (attr->label) { + ret = pmbus_add_label(data, name, index, attr->label, + paged ? page + 1 : 0); + if (ret) + return ret; + } + base = pmbus_add_sensor(data, name, "input", index, page, attr->reg, + attr->class, true, true, true); + if (!base) + return -ENOMEM; + if (attr->sfunc) { + ret = pmbus_add_limit_attrs(client, data, info, name, + index, page, base, attr); + if (ret < 0) + return ret; + /* + * Add generic alarm attribute only if there are no individual + * alarm attributes, if there is a global alarm bit, and if + * the generic status register (word or byte, depending on + * which global bit is set) for this page is accessible. + */ + if (!ret && attr->gbit && + (!upper || (upper && data->has_status_word)) && + pmbus_check_status_register(client, page)) { + ret = pmbus_add_boolean(data, name, "alarm", index, + NULL, NULL, + PB_STATUS_BASE + page, + attr->gbit); + if (ret) + return ret; + } + } + return 0; +} + +static bool pmbus_sensor_is_paged(const struct pmbus_driver_info *info, + const struct pmbus_sensor_attr *attr) +{ + int p; + + if (attr->paged) + return true; + + /* + * Some attributes may be present on more than one page despite + * not being marked with the paged attribute. If that is the case, + * then treat the sensor as being paged and add the page suffix to the + * attribute name. + * We don't just add the paged attribute to all such attributes, in + * order to maintain the un-suffixed labels in the case where the + * attribute is only on page 0. + */ + for (p = 1; p < info->pages; p++) { + if (info->func[p] & attr->func) + return true; + } + return false; +} + +static int pmbus_add_sensor_attrs(struct i2c_client *client, + struct pmbus_data *data, + const char *name, + const struct pmbus_sensor_attr *attrs, + int nattrs) +{ + const struct pmbus_driver_info *info = data->info; + int index, i; + int ret; + + index = 1; + for (i = 0; i < nattrs; i++) { + int page, pages; + bool paged = pmbus_sensor_is_paged(info, attrs); + + pages = paged ? info->pages : 1; + for (page = 0; page < pages; page++) { + if (!(info->func[page] & attrs->func)) + continue; + ret = pmbus_add_sensor_attrs_one(client, data, info, + name, index, page, + attrs, paged); + if (ret) + return ret; + index++; + } + attrs++; + } + return 0; +} + +static const struct pmbus_limit_attr vin_limit_attrs[] = { + { + .reg = PMBUS_VIN_UV_WARN_LIMIT, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_VOLTAGE_UV_WARNING, + }, { + .reg = PMBUS_VIN_UV_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_VOLTAGE_UV_FAULT | PB_VOLTAGE_VIN_OFF, + }, { + .reg = PMBUS_VIN_OV_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_VOLTAGE_OV_WARNING, + }, { + .reg = PMBUS_VIN_OV_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_VOLTAGE_OV_FAULT, + }, { + .reg = PMBUS_VIRT_READ_VIN_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_VIN_MIN, + .update = true, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_VIN_MAX, + .update = true, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_VIN_HISTORY, + .attr = "reset_history", + }, +}; + +static const struct pmbus_limit_attr vmon_limit_attrs[] = { + { + .reg = PMBUS_VIRT_VMON_UV_WARN_LIMIT, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_VOLTAGE_UV_WARNING, + }, { + .reg = PMBUS_VIRT_VMON_UV_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_VOLTAGE_UV_FAULT, + }, { + .reg = PMBUS_VIRT_VMON_OV_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_VOLTAGE_OV_WARNING, + }, { + .reg = PMBUS_VIRT_VMON_OV_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_VOLTAGE_OV_FAULT, + } +}; + +static const struct pmbus_limit_attr vout_limit_attrs[] = { + { + .reg = PMBUS_VOUT_UV_WARN_LIMIT, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_VOLTAGE_UV_WARNING, + }, { + .reg = PMBUS_VOUT_UV_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_VOLTAGE_UV_FAULT, + }, { + .reg = PMBUS_VOUT_OV_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_VOLTAGE_OV_WARNING, + }, { + .reg = PMBUS_VOUT_OV_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_VOLTAGE_OV_FAULT, + }, { + .reg = PMBUS_VIRT_READ_VOUT_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_VOUT_MIN, + .update = true, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_VOUT_MAX, + .update = true, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_VOUT_HISTORY, + .attr = "reset_history", + } +}; + +static const struct pmbus_sensor_attr voltage_attributes[] = { + { + .reg = PMBUS_READ_VIN, + .class = PSC_VOLTAGE_IN, + .label = "vin", + .func = PMBUS_HAVE_VIN, + .sfunc = PMBUS_HAVE_STATUS_INPUT, + .sbase = PB_STATUS_INPUT_BASE, + .gbit = PB_STATUS_VIN_UV, + .limit = vin_limit_attrs, + .nlimit = ARRAY_SIZE(vin_limit_attrs), + }, { + .reg = PMBUS_VIRT_READ_VMON, + .class = PSC_VOLTAGE_IN, + .label = "vmon", + .func = PMBUS_HAVE_VMON, + .sfunc = PMBUS_HAVE_STATUS_VMON, + .sbase = PB_STATUS_VMON_BASE, + .limit = vmon_limit_attrs, + .nlimit = ARRAY_SIZE(vmon_limit_attrs), + }, { + .reg = PMBUS_READ_VCAP, + .class = PSC_VOLTAGE_IN, + .label = "vcap", + .func = PMBUS_HAVE_VCAP, + }, { + .reg = PMBUS_READ_VOUT, + .class = PSC_VOLTAGE_OUT, + .label = "vout", + .paged = true, + .func = PMBUS_HAVE_VOUT, + .sfunc = PMBUS_HAVE_STATUS_VOUT, + .sbase = PB_STATUS_VOUT_BASE, + .gbit = PB_STATUS_VOUT_OV, + .limit = vout_limit_attrs, + .nlimit = ARRAY_SIZE(vout_limit_attrs), + } +}; + +/* Current attributes */ + +static const struct pmbus_limit_attr iin_limit_attrs[] = { + { + .reg = PMBUS_IIN_OC_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_IIN_OC_WARNING, + }, { + .reg = PMBUS_IIN_OC_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_IIN_OC_FAULT, + }, { + .reg = PMBUS_VIRT_READ_IIN_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_IIN_MIN, + .update = true, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_IIN_MAX, + .update = true, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_IIN_HISTORY, + .attr = "reset_history", + } +}; + +static const struct pmbus_limit_attr iout_limit_attrs[] = { + { + .reg = PMBUS_IOUT_OC_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_IOUT_OC_WARNING, + }, { + .reg = PMBUS_IOUT_UC_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_IOUT_UC_FAULT, + }, { + .reg = PMBUS_IOUT_OC_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_IOUT_OC_FAULT, + }, { + .reg = PMBUS_VIRT_READ_IOUT_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_IOUT_MIN, + .update = true, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_IOUT_MAX, + .update = true, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_IOUT_HISTORY, + .attr = "reset_history", + } +}; + +static const struct pmbus_sensor_attr current_attributes[] = { + { + .reg = PMBUS_READ_IIN, + .class = PSC_CURRENT_IN, + .label = "iin", + .func = PMBUS_HAVE_IIN, + .sfunc = PMBUS_HAVE_STATUS_INPUT, + .sbase = PB_STATUS_INPUT_BASE, + .gbit = PB_STATUS_INPUT, + .limit = iin_limit_attrs, + .nlimit = ARRAY_SIZE(iin_limit_attrs), + }, { + .reg = PMBUS_READ_IOUT, + .class = PSC_CURRENT_OUT, + .label = "iout", + .paged = true, + .func = PMBUS_HAVE_IOUT, + .sfunc = PMBUS_HAVE_STATUS_IOUT, + .sbase = PB_STATUS_IOUT_BASE, + .gbit = PB_STATUS_IOUT_OC, + .limit = iout_limit_attrs, + .nlimit = ARRAY_SIZE(iout_limit_attrs), + } +}; + +/* Power attributes */ + +static const struct pmbus_limit_attr pin_limit_attrs[] = { + { + .reg = PMBUS_PIN_OP_WARN_LIMIT, + .attr = "max", + .alarm = "alarm", + .sbit = PB_PIN_OP_WARNING, + }, { + .reg = PMBUS_VIRT_READ_PIN_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_PIN_MIN, + .update = true, + .attr = "input_lowest", + }, { + .reg = PMBUS_VIRT_READ_PIN_MAX, + .update = true, + .attr = "input_highest", + }, { + .reg = PMBUS_VIRT_RESET_PIN_HISTORY, + .attr = "reset_history", + } +}; + +static const struct pmbus_limit_attr pout_limit_attrs[] = { + { + .reg = PMBUS_POUT_MAX, + .attr = "cap", + .alarm = "cap_alarm", + .sbit = PB_POWER_LIMITING, + }, { + .reg = PMBUS_POUT_OP_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_POUT_OP_WARNING, + }, { + .reg = PMBUS_POUT_OP_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_POUT_OP_FAULT, + }, { + .reg = PMBUS_VIRT_READ_POUT_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_POUT_MIN, + .update = true, + .attr = "input_lowest", + }, { + .reg = PMBUS_VIRT_READ_POUT_MAX, + .update = true, + .attr = "input_highest", + }, { + .reg = PMBUS_VIRT_RESET_POUT_HISTORY, + .attr = "reset_history", + } +}; + +static const struct pmbus_sensor_attr power_attributes[] = { + { + .reg = PMBUS_READ_PIN, + .class = PSC_POWER, + .label = "pin", + .func = PMBUS_HAVE_PIN, + .sfunc = PMBUS_HAVE_STATUS_INPUT, + .sbase = PB_STATUS_INPUT_BASE, + .gbit = PB_STATUS_INPUT, + .limit = pin_limit_attrs, + .nlimit = ARRAY_SIZE(pin_limit_attrs), + }, { + .reg = PMBUS_READ_POUT, + .class = PSC_POWER, + .label = "pout", + .paged = true, + .func = PMBUS_HAVE_POUT, + .sfunc = PMBUS_HAVE_STATUS_IOUT, + .sbase = PB_STATUS_IOUT_BASE, + .limit = pout_limit_attrs, + .nlimit = ARRAY_SIZE(pout_limit_attrs), + } +}; + +/* Temperature atributes */ + +static const struct pmbus_limit_attr temp_limit_attrs[] = { + { + .reg = PMBUS_UT_WARN_LIMIT, + .low = true, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_TEMP_UT_WARNING, + }, { + .reg = PMBUS_UT_FAULT_LIMIT, + .low = true, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_TEMP_UT_FAULT, + }, { + .reg = PMBUS_OT_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_TEMP_OT_WARNING, + }, { + .reg = PMBUS_OT_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_TEMP_OT_FAULT, + }, { + .reg = PMBUS_VIRT_READ_TEMP_MIN, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_TEMP_AVG, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_TEMP_MAX, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_TEMP_HISTORY, + .attr = "reset_history", + } +}; + +static const struct pmbus_limit_attr temp_limit_attrs2[] = { + { + .reg = PMBUS_UT_WARN_LIMIT, + .low = true, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_TEMP_UT_WARNING, + }, { + .reg = PMBUS_UT_FAULT_LIMIT, + .low = true, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_TEMP_UT_FAULT, + }, { + .reg = PMBUS_OT_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_TEMP_OT_WARNING, + }, { + .reg = PMBUS_OT_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_TEMP_OT_FAULT, + }, { + .reg = PMBUS_VIRT_READ_TEMP2_MIN, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_TEMP2_AVG, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_TEMP2_MAX, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_TEMP2_HISTORY, + .attr = "reset_history", + } +}; + +static const struct pmbus_limit_attr temp_limit_attrs3[] = { + { + .reg = PMBUS_UT_WARN_LIMIT, + .low = true, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_TEMP_UT_WARNING, + }, { + .reg = PMBUS_UT_FAULT_LIMIT, + .low = true, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_TEMP_UT_FAULT, + }, { + .reg = PMBUS_OT_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_TEMP_OT_WARNING, + }, { + .reg = PMBUS_OT_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_TEMP_OT_FAULT, + } +}; + +static const struct pmbus_sensor_attr temp_attributes[] = { + { + .reg = PMBUS_READ_TEMPERATURE_1, + .class = PSC_TEMPERATURE, + .paged = true, + .update = true, + .compare = true, + .func = PMBUS_HAVE_TEMP, + .sfunc = PMBUS_HAVE_STATUS_TEMP, + .sbase = PB_STATUS_TEMP_BASE, + .gbit = PB_STATUS_TEMPERATURE, + .limit = temp_limit_attrs, + .nlimit = ARRAY_SIZE(temp_limit_attrs), + }, { + .reg = PMBUS_READ_TEMPERATURE_2, + .class = PSC_TEMPERATURE, + .paged = true, + .update = true, + .compare = true, + .func = PMBUS_HAVE_TEMP2, + .sfunc = PMBUS_HAVE_STATUS_TEMP, + .sbase = PB_STATUS_TEMP_BASE, + .gbit = PB_STATUS_TEMPERATURE, + .limit = temp_limit_attrs2, + .nlimit = ARRAY_SIZE(temp_limit_attrs2), + }, { + .reg = PMBUS_READ_TEMPERATURE_3, + .class = PSC_TEMPERATURE, + .paged = true, + .update = true, + .compare = true, + .func = PMBUS_HAVE_TEMP3, + .sfunc = PMBUS_HAVE_STATUS_TEMP, + .sbase = PB_STATUS_TEMP_BASE, + .gbit = PB_STATUS_TEMPERATURE, + .limit = temp_limit_attrs3, + .nlimit = ARRAY_SIZE(temp_limit_attrs3), + } +}; + +static const int pmbus_fan_registers[] = { + PMBUS_READ_FAN_SPEED_1, + PMBUS_READ_FAN_SPEED_2, + PMBUS_READ_FAN_SPEED_3, + PMBUS_READ_FAN_SPEED_4 +}; + +static const int pmbus_fan_status_registers[] = { + PMBUS_STATUS_FAN_12, + PMBUS_STATUS_FAN_12, + PMBUS_STATUS_FAN_34, + PMBUS_STATUS_FAN_34 +}; + +static const u32 pmbus_fan_flags[] = { + PMBUS_HAVE_FAN12, + PMBUS_HAVE_FAN12, + PMBUS_HAVE_FAN34, + PMBUS_HAVE_FAN34 +}; + +static const u32 pmbus_fan_status_flags[] = { + PMBUS_HAVE_STATUS_FAN12, + PMBUS_HAVE_STATUS_FAN12, + PMBUS_HAVE_STATUS_FAN34, + PMBUS_HAVE_STATUS_FAN34 +}; + +/* Fans */ + +/* Precondition: FAN_CONFIG_x_y and FAN_COMMAND_x must exist for the fan ID */ +static int pmbus_add_fan_ctrl(struct i2c_client *client, + struct pmbus_data *data, int index, int page, int id, + u8 config) +{ + struct pmbus_sensor *sensor; + + sensor = pmbus_add_sensor(data, "fan", "target", index, page, + PMBUS_VIRT_FAN_TARGET_1 + id, PSC_FAN, + false, false, true); + + if (!sensor) + return -ENOMEM; + + if (!((data->info->func[page] & PMBUS_HAVE_PWM12) || + (data->info->func[page] & PMBUS_HAVE_PWM34))) + return 0; + + sensor = pmbus_add_sensor(data, "pwm", NULL, index, page, + PMBUS_VIRT_PWM_1 + id, PSC_PWM, + false, false, true); + + if (!sensor) + return -ENOMEM; + + sensor = pmbus_add_sensor(data, "pwm", "enable", index, page, + PMBUS_VIRT_PWM_ENABLE_1 + id, PSC_PWM, + true, false, false); + + if (!sensor) + return -ENOMEM; + + return 0; +} + +static int pmbus_add_fan_attributes(struct i2c_client *client, + struct pmbus_data *data) +{ + const struct pmbus_driver_info *info = data->info; + int index = 1; + int page; + int ret; + + for (page = 0; page < info->pages; page++) { + int f; + + for (f = 0; f < ARRAY_SIZE(pmbus_fan_registers); f++) { + int regval; + + if (!(info->func[page] & pmbus_fan_flags[f])) + break; + + if (!pmbus_check_word_register(client, page, + pmbus_fan_registers[f])) + break; + + /* + * Skip fan if not installed. + * Each fan configuration register covers multiple fans, + * so we have to do some magic. + */ + regval = _pmbus_read_byte_data(client, page, + pmbus_fan_config_registers[f]); + if (regval < 0 || + (!(regval & (PB_FAN_1_INSTALLED >> ((f & 1) * 4))))) + continue; + + if (pmbus_add_sensor(data, "fan", "input", index, + page, pmbus_fan_registers[f], + PSC_FAN, true, true, true) == NULL) + return -ENOMEM; + + /* Fan control */ + if (pmbus_check_word_register(client, page, + pmbus_fan_command_registers[f])) { + ret = pmbus_add_fan_ctrl(client, data, index, + page, f, regval); + if (ret < 0) + return ret; + } + + /* + * Each fan status register covers multiple fans, + * so we have to do some magic. + */ + if ((info->func[page] & pmbus_fan_status_flags[f]) && + pmbus_check_byte_register(client, + page, pmbus_fan_status_registers[f])) { + int base; + + if (f > 1) /* fan 3, 4 */ + base = PB_STATUS_FAN34_BASE + page; + else + base = PB_STATUS_FAN_BASE + page; + ret = pmbus_add_boolean(data, "fan", + "alarm", index, NULL, NULL, base, + PB_FAN_FAN1_WARNING >> (f & 1)); + if (ret) + return ret; + ret = pmbus_add_boolean(data, "fan", + "fault", index, NULL, NULL, base, + PB_FAN_FAN1_FAULT >> (f & 1)); + if (ret) + return ret; + } + index++; + } + } + return 0; +} + +static int pmbus_find_attributes(struct i2c_client *client, + struct pmbus_data *data) +{ + int ret; + + /* Voltage sensors */ + ret = pmbus_add_sensor_attrs(client, data, "in", voltage_attributes, + ARRAY_SIZE(voltage_attributes)); + if (ret) + return ret; + + /* Current sensors */ + ret = pmbus_add_sensor_attrs(client, data, "curr", current_attributes, + ARRAY_SIZE(current_attributes)); + if (ret) + return ret; + + /* Power sensors */ + ret = pmbus_add_sensor_attrs(client, data, "power", power_attributes, + ARRAY_SIZE(power_attributes)); + if (ret) + return ret; + + /* Temperature sensors */ + ret = pmbus_add_sensor_attrs(client, data, "temp", temp_attributes, + ARRAY_SIZE(temp_attributes)); + if (ret) + return ret; + + /* Fans */ + ret = pmbus_add_fan_attributes(client, data); + return ret; +} + +/* + * Identify chip parameters. + * This function is called for all chips. + */ +static int pmbus_identify_common(struct i2c_client *client, + struct pmbus_data *data, int page) +{ + int vout_mode = -1; + + if (pmbus_check_byte_register(client, page, PMBUS_VOUT_MODE)) + vout_mode = _pmbus_read_byte_data(client, page, + PMBUS_VOUT_MODE); + if (vout_mode >= 0 && vout_mode != 0xff) { + /* + * Not all chips support the VOUT_MODE command, + * so a failure to read it is not an error. + */ + switch (vout_mode >> 5) { + case 0: /* linear mode */ + if (data->info->format[PSC_VOLTAGE_OUT] != linear) + return -ENODEV; + + data->exponent[page] = ((s8)(vout_mode << 3)) >> 3; + break; + case 1: /* VID mode */ + if (data->info->format[PSC_VOLTAGE_OUT] != vid) + return -ENODEV; + break; + case 2: /* direct mode */ + if (data->info->format[PSC_VOLTAGE_OUT] != direct) + return -ENODEV; + break; + default: + return -ENODEV; + } + } + + pmbus_clear_fault_page(client, page); + return 0; +} + +static int pmbus_read_status_byte(struct i2c_client *client, int page) +{ + return _pmbus_read_byte_data(client, page, PMBUS_STATUS_BYTE); +} + +static int pmbus_read_status_word(struct i2c_client *client, int page) +{ + return _pmbus_read_word_data(client, page, PMBUS_STATUS_WORD); +} + +static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, + struct pmbus_driver_info *info) +{ + struct device *dev = &client->dev; + int page, ret; + + /* + * Some PMBus chips don't support PMBUS_STATUS_WORD, so try + * to use PMBUS_STATUS_BYTE instead if that is the case. + * Bail out if both registers are not supported. + */ + data->read_status = pmbus_read_status_word; + ret = i2c_smbus_read_word_data(client, PMBUS_STATUS_WORD); + if (ret < 0 || ret == 0xffff) { + data->read_status = pmbus_read_status_byte; + ret = i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE); + if (ret < 0 || ret == 0xff) { + dev_err(dev, "PMBus status register not found\n"); + return -ENODEV; + } + } else { + data->has_status_word = true; + } + + /* Enable PEC if the controller supports it */ + ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY); + if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) + client->flags |= I2C_CLIENT_PEC; + + if (data->info->pages) + pmbus_clear_faults(client); + else + pmbus_clear_fault_page(client, -1); + + if (info->identify) { + ret = (*info->identify)(client, info); + if (ret < 0) { + dev_err(dev, "Chip identification failed\n"); + return ret; + } + } + + if (info->pages <= 0 || info->pages > PMBUS_PAGES) { + dev_err(dev, "Bad number of PMBus pages: %d\n", info->pages); + return -ENODEV; + } + + for (page = 0; page < info->pages; page++) { + ret = pmbus_identify_common(client, data, page); + if (ret < 0) { + dev_err(dev, "Failed to identify chip capabilities\n"); + return ret; + } + } + return 0; +} + +#if IS_ENABLED(CONFIG_REGULATOR) +static int pmbus_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct device *dev = rdev_get_dev(rdev); + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + u8 page = rdev_get_id(rdev); + int ret; + + mutex_lock(&data->update_lock); + ret = pmbus_read_byte_data(client, page, PMBUS_OPERATION); + mutex_unlock(&data->update_lock); + + if (ret < 0) + return ret; + + return !!(ret & PB_OPERATION_CONTROL_ON); +} + +static int _pmbus_regulator_on_off(struct regulator_dev *rdev, bool enable) +{ + struct device *dev = rdev_get_dev(rdev); + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + u8 page = rdev_get_id(rdev); + int ret; + + mutex_lock(&data->update_lock); + ret = pmbus_update_byte_data(client, page, PMBUS_OPERATION, + PB_OPERATION_CONTROL_ON, + enable ? PB_OPERATION_CONTROL_ON : 0); + mutex_unlock(&data->update_lock); + + return ret; +} + +static int pmbus_regulator_enable(struct regulator_dev *rdev) +{ + return _pmbus_regulator_on_off(rdev, 1); +} + +static int pmbus_regulator_disable(struct regulator_dev *rdev) +{ + return _pmbus_regulator_on_off(rdev, 0); +} + +const struct regulator_ops pmbus_regulator_ops = { + .enable = pmbus_regulator_enable, + .disable = pmbus_regulator_disable, + .is_enabled = pmbus_regulator_is_enabled, +}; +EXPORT_SYMBOL_GPL(pmbus_regulator_ops); + +static int pmbus_regulator_register(struct pmbus_data *data) +{ + struct device *dev = data->dev; + const struct pmbus_driver_info *info = data->info; + const struct pmbus_platform_data *pdata = dev_get_platdata(dev); + struct regulator_dev *rdev; + int i; + + for (i = 0; i < info->num_regulators; i++) { + struct regulator_config config = { }; + + config.dev = dev; + config.driver_data = data; + + if (pdata && pdata->reg_init_data) + config.init_data = &pdata->reg_init_data[i]; + + rdev = devm_regulator_register(dev, &info->reg_desc[i], + &config); + if (IS_ERR(rdev)) { + dev_err(dev, "Failed to register %s regulator\n", + info->reg_desc[i].name); + return PTR_ERR(rdev); + } + } + + return 0; +} +#else +static int pmbus_regulator_register(struct pmbus_data *data) +{ + return 0; +} +#endif + +static struct dentry *pmbus_debugfs_dir; /* pmbus debugfs directory */ + +#if IS_ENABLED(CONFIG_DEBUG_FS) +static int pmbus_debugfs_get(void *data, u64 *val) +{ + int rc; + struct pmbus_debugfs_entry *entry = data; + + rc = _pmbus_read_byte_data(entry->client, entry->page, entry->reg); + if (rc < 0) + return rc; + + *val = rc; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops, pmbus_debugfs_get, NULL, + "0x%02llx\n"); + +static int pmbus_debugfs_get_status(void *data, u64 *val) +{ + int rc; + struct pmbus_debugfs_entry *entry = data; + struct pmbus_data *pdata = i2c_get_clientdata(entry->client); + + rc = pdata->read_status(entry->client, entry->page); + if (rc < 0) + return rc; + + *val = rc; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops_status, pmbus_debugfs_get_status, + NULL, "0x%04llx\n"); + +static int pmbus_init_debugfs(struct i2c_client *client, + struct pmbus_data *data) +{ + int i, idx = 0; + char name[PMBUS_NAME_SIZE]; + struct pmbus_debugfs_entry *entries; + + if (!pmbus_debugfs_dir) + return -ENODEV; + + /* + * Create the debugfs directory for this device. Use the hwmon device + * name to avoid conflicts (hwmon numbers are globally unique). + */ + data->debugfs = debugfs_create_dir(dev_name(data->hwmon_dev), + pmbus_debugfs_dir); + if (IS_ERR_OR_NULL(data->debugfs)) { + data->debugfs = NULL; + return -ENODEV; + } + + /* Allocate the max possible entries we need. */ + entries = devm_kcalloc(data->dev, + data->info->pages * 10, sizeof(*entries), + GFP_KERNEL); + if (!entries) + return -ENOMEM; + + for (i = 0; i < data->info->pages; ++i) { + /* Check accessibility of status register if it's not page 0 */ + if (!i || pmbus_check_status_register(client, i)) { + /* No need to set reg as we have special read op. */ + entries[idx].client = client; + entries[idx].page = i; + scnprintf(name, PMBUS_NAME_SIZE, "status%d", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops_status); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_VOUT) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_VOUT; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_vout", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_IOUT) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_IOUT; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_iout", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_INPUT) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_INPUT; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_input", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_TEMP) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_TEMPERATURE; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_temp", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (pmbus_check_byte_register(client, i, PMBUS_STATUS_CML)) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_CML; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_cml", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (pmbus_check_byte_register(client, i, PMBUS_STATUS_OTHER)) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_OTHER; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_other", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (pmbus_check_byte_register(client, i, + PMBUS_STATUS_MFR_SPECIFIC)) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_MFR_SPECIFIC; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_mfr", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_FAN12) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_FAN_12; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_fan12", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_FAN34) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_FAN_34; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_fan34", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + } + + return 0; +} +#else +static int pmbus_init_debugfs(struct i2c_client *client, + struct pmbus_data *data) +{ + return 0; +} +#endif /* IS_ENABLED(CONFIG_DEBUG_FS) */ + +int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, + struct pmbus_driver_info *info) +{ + struct device *dev = &client->dev; + const struct pmbus_platform_data *pdata = dev_get_platdata(dev); + struct pmbus_data *data; + int ret; + + if (!info) + return -ENODEV; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WRITE_BYTE + | I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + data->dev = dev; + + if (pdata) + data->flags = pdata->flags; + data->info = info; + + ret = pmbus_init_common(client, data, info); + if (ret < 0) + return ret; + + ret = pmbus_find_attributes(client, data); + if (ret) + goto out_kfree; + + /* + * If there are no attributes, something is wrong. + * Bail out instead of trying to register nothing. + */ + if (!data->num_attributes) { + dev_err(dev, "No attributes found\n"); + ret = -ENODEV; + goto out_kfree; + } + + data->groups[0] = &data->group; + data->hwmon_dev = hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + dev_err(dev, "Failed to register hwmon device\n"); + goto out_kfree; + } + + ret = pmbus_regulator_register(data); + if (ret) + goto out_unregister; + + ret = pmbus_init_debugfs(client, data); + if (ret) + dev_warn(dev, "Failed to register debugfs\n"); + + return 0; + +out_unregister: + hwmon_device_unregister(data->hwmon_dev); +out_kfree: + kfree(data->group.attrs); + return ret; +} +EXPORT_SYMBOL_GPL(pmbus_do_probe); + +int pmbus_do_remove(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + + debugfs_remove_recursive(data->debugfs); + + hwmon_device_unregister(data->hwmon_dev); + kfree(data->group.attrs); + return 0; +} +EXPORT_SYMBOL_GPL(pmbus_do_remove); + +struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + + return data->debugfs; +} +EXPORT_SYMBOL_GPL(pmbus_get_debugfs_dir); + +static int __init pmbus_core_init(void) +{ + pmbus_debugfs_dir = debugfs_create_dir("pmbus", NULL); + if (IS_ERR(pmbus_debugfs_dir)) + pmbus_debugfs_dir = NULL; + + return 0; +} + +static void __exit pmbus_core_exit(void) +{ + debugfs_remove_recursive(pmbus_debugfs_dir); +} + +module_init(pmbus_core_init); +module_exit(pmbus_core_exit); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/tps40422.c b/drivers/hwmon/pmbus/tps40422.c new file mode 100644 index 000000000..32803825d --- /dev/null +++ b/drivers/hwmon/pmbus/tps40422.c @@ -0,0 +1,64 @@ +/* + * Hardware monitoring driver for TI TPS40422 + * + * Copyright (c) 2014 Nokia Solutions and Networks. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include "pmbus.h" + +static struct pmbus_driver_info tps40422_info = { + .pages = 2, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = linear, + .format[PSC_TEMPERATURE] = linear, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_TEMP2 + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_TEMP2 + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, +}; + +static int tps40422_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return pmbus_do_probe(client, id, &tps40422_info); +} + +static const struct i2c_device_id tps40422_id[] = { + {"tps40422", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, tps40422_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver tps40422_driver = { + .driver = { + .name = "tps40422", + }, + .probe = tps40422_probe, + .remove = pmbus_do_remove, + .id_table = tps40422_id, +}; + +module_i2c_driver(tps40422_driver); + +MODULE_AUTHOR("Zhu Laiwen "); +MODULE_DESCRIPTION("PMBus driver for TI TPS40422"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c new file mode 100644 index 000000000..2bc352c53 --- /dev/null +++ b/drivers/hwmon/pmbus/tps53679.c @@ -0,0 +1,120 @@ +/* + * Hardware monitoring driver for Texas Instruments TPS53679 + * + * Copyright (c) 2017 Mellanox Technologies. All rights reserved. + * Copyright (c) 2017 Vadim Pasternak + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include "pmbus.h" + +#define TPS53679_PROT_VR12_5MV 0x01 /* VR12.0 mode, 5-mV DAC */ +#define TPS53679_PROT_VR12_5_10MV 0x02 /* VR12.5 mode, 10-mV DAC */ +#define TPS53679_PROT_VR13_10MV 0x04 /* VR13.0 mode, 10-mV DAC */ +#define TPS53679_PROT_IMVP8_5MV 0x05 /* IMVP8 mode, 5-mV DAC */ +#define TPS53679_PROT_VR13_5MV 0x07 /* VR13.0 mode, 5-mV DAC */ +#define TPS53679_PAGE_NUM 2 + +static int tps53679_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + u8 vout_params; + int ret; + + /* Read the register with VOUT scaling value.*/ + ret = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (ret < 0) + return ret; + + vout_params = ret & GENMASK(4, 0); + + switch (vout_params) { + case TPS53679_PROT_VR13_10MV: + case TPS53679_PROT_VR12_5_10MV: + info->vrm_version = vr13; + break; + case TPS53679_PROT_VR13_5MV: + case TPS53679_PROT_VR12_5MV: + case TPS53679_PROT_IMVP8_5MV: + info->vrm_version = vr12; + break; + default: + return -EINVAL; + } + + return 0; +} + +static struct pmbus_driver_info tps53679_info = { + .pages = TPS53679_PAGE_NUM, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = vid, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT, + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT, + .identify = tps53679_identify, +}; + +static int tps53679_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pmbus_driver_info *info; + + info = devm_kmemdup(&client->dev, &tps53679_info, sizeof(*info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + return pmbus_do_probe(client, id, info); +} + +static const struct i2c_device_id tps53679_id[] = { + {"tps53679", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, tps53679_id); + +static const struct of_device_id tps53679_of_match[] = { + {.compatible = "ti,tps53679"}, + {} +}; +MODULE_DEVICE_TABLE(of, tps53679_of_match); + +static struct i2c_driver tps53679_driver = { + .driver = { + .name = "tps53679", + .of_match_table = of_match_ptr(tps53679_of_match), + }, + .probe = tps53679_probe, + .remove = pmbus_do_remove, + .id_table = tps53679_id, +}; + +module_i2c_driver(tps53679_driver); + +MODULE_AUTHOR("Vadim Pasternak "); +MODULE_DESCRIPTION("PMBus driver for Texas Instruments TPS53679"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c new file mode 100644 index 000000000..ae93885fc --- /dev/null +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -0,0 +1,632 @@ +/* + * Hardware monitoring driver for UCD90xxx Sequencer and System Health + * Controller series + * + * Copyright (C) 2011 Ericsson AB. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd9090, ucd90910 }; + +#define UCD9000_MONITOR_CONFIG 0xd5 +#define UCD9000_NUM_PAGES 0xd6 +#define UCD9000_FAN_CONFIG_INDEX 0xe7 +#define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_MFR_STATUS 0xf3 +#define UCD9000_GPIO_SELECT 0xfa +#define UCD9000_GPIO_CONFIG 0xfb +#define UCD9000_DEVICE_ID 0xfd + +/* GPIO CONFIG bits */ +#define UCD9000_GPIO_CONFIG_ENABLE BIT(0) +#define UCD9000_GPIO_CONFIG_OUT_ENABLE BIT(1) +#define UCD9000_GPIO_CONFIG_OUT_VALUE BIT(2) +#define UCD9000_GPIO_CONFIG_STATUS BIT(3) +#define UCD9000_GPIO_INPUT 0 +#define UCD9000_GPIO_OUTPUT 1 + +#define UCD9000_MON_TYPE(x) (((x) >> 5) & 0x07) +#define UCD9000_MON_PAGE(x) ((x) & 0x0f) + +#define UCD9000_MON_VOLTAGE 1 +#define UCD9000_MON_TEMPERATURE 2 +#define UCD9000_MON_CURRENT 3 +#define UCD9000_MON_VOLTAGE_HW 4 + +#define UCD9000_NUM_FAN 4 + +#define UCD9000_GPIO_NAME_LEN 16 +#define UCD9090_NUM_GPIOS 23 +#define UCD901XX_NUM_GPIOS 26 +#define UCD90910_NUM_GPIOS 26 + +#define UCD9000_DEBUGFS_NAME_LEN 24 +#define UCD9000_GPI_COUNT 8 + +struct ucd9000_data { + u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; + struct pmbus_driver_info info; +#ifdef CONFIG_GPIOLIB + struct gpio_chip gpio; +#endif + struct dentry *debugfs; +}; +#define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) + +struct ucd9000_debugfs_entry { + struct i2c_client *client; + u8 index; +}; + +static int ucd9000_get_fan_config(struct i2c_client *client, int fan) +{ + int fan_config = 0; + struct ucd9000_data *data + = to_ucd9000_data(pmbus_get_driver_info(client)); + + if (data->fan_data[fan][3] & 1) + fan_config |= PB_FAN_2_INSTALLED; /* Use lower bit position */ + + /* Pulses/revolution */ + fan_config |= (data->fan_data[fan][3] & 0x06) >> 1; + + return fan_config; +} + +static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret = 0; + int fan_config; + + switch (reg) { + case PMBUS_FAN_CONFIG_12: + if (page > 0) + return -ENXIO; + + ret = ucd9000_get_fan_config(client, 0); + if (ret < 0) + return ret; + fan_config = ret << 4; + ret = ucd9000_get_fan_config(client, 1); + if (ret < 0) + return ret; + fan_config |= ret; + ret = fan_config; + break; + case PMBUS_FAN_CONFIG_34: + if (page > 0) + return -ENXIO; + + ret = ucd9000_get_fan_config(client, 2); + if (ret < 0) + return ret; + fan_config = ret << 4; + ret = ucd9000_get_fan_config(client, 3); + if (ret < 0) + return ret; + fan_config |= ret; + ret = fan_config; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static const struct i2c_device_id ucd9000_id[] = { + {"ucd9000", ucd9000}, + {"ucd90120", ucd90120}, + {"ucd90124", ucd90124}, + {"ucd90160", ucd90160}, + {"ucd9090", ucd9090}, + {"ucd90910", ucd90910}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ucd9000_id); + +static const struct of_device_id ucd9000_of_match[] = { + { + .compatible = "ti,ucd9000", + .data = (void *)ucd9000 + }, + { + .compatible = "ti,ucd90120", + .data = (void *)ucd90120 + }, + { + .compatible = "ti,ucd90124", + .data = (void *)ucd90124 + }, + { + .compatible = "ti,ucd90160", + .data = (void *)ucd90160 + }, + { + .compatible = "ti,ucd9090", + .data = (void *)ucd9090 + }, + { + .compatible = "ti,ucd90910", + .data = (void *)ucd90910 + }, + { }, +}; +MODULE_DEVICE_TABLE(of, ucd9000_of_match); + +#ifdef CONFIG_GPIOLIB +static int ucd9000_gpio_read_config(struct i2c_client *client, + unsigned int offset) +{ + int ret; + + /* No page set required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_SELECT, offset); + if (ret < 0) + return ret; + + return i2c_smbus_read_byte_data(client, UCD9000_GPIO_CONFIG); +} + +static int ucd9000_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !!(ret & UCD9000_GPIO_CONFIG_STATUS); +} + +static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) { + dev_dbg(&client->dev, "failed to read GPIO %d config: %d\n", + offset, ret); + return; + } + + if (value) { + if (ret & UCD9000_GPIO_CONFIG_STATUS) + return; + + ret |= UCD9000_GPIO_CONFIG_STATUS; + } else { + if (!(ret & UCD9000_GPIO_CONFIG_STATUS)) + return; + + ret &= ~UCD9000_GPIO_CONFIG_STATUS; + } + + ret |= UCD9000_GPIO_CONFIG_ENABLE; + + /* Page set not required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) { + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); + return; + } + + ret &= ~UCD9000_GPIO_CONFIG_ENABLE; + + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); +} + +static int ucd9000_gpio_get_direction(struct gpio_chip *gc, + unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !(ret & UCD9000_GPIO_CONFIG_OUT_ENABLE); +} + +static int ucd9000_gpio_set_direction(struct gpio_chip *gc, + unsigned int offset, bool direction_out, + int requested_out) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret, config, out_val; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + if (direction_out) { + out_val = requested_out ? UCD9000_GPIO_CONFIG_OUT_VALUE : 0; + + if (ret & UCD9000_GPIO_CONFIG_OUT_ENABLE) { + if ((ret & UCD9000_GPIO_CONFIG_OUT_VALUE) == out_val) + return 0; + } else { + ret |= UCD9000_GPIO_CONFIG_OUT_ENABLE; + } + + if (out_val) + ret |= UCD9000_GPIO_CONFIG_OUT_VALUE; + else + ret &= ~UCD9000_GPIO_CONFIG_OUT_VALUE; + + } else { + if (!(ret & UCD9000_GPIO_CONFIG_OUT_ENABLE)) + return 0; + + ret &= ~UCD9000_GPIO_CONFIG_OUT_ENABLE; + } + + ret |= UCD9000_GPIO_CONFIG_ENABLE; + config = ret; + + /* Page set not required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, config); + if (ret < 0) + return ret; + + config &= ~UCD9000_GPIO_CONFIG_ENABLE; + + return i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, config); +} + +static int ucd9000_gpio_direction_input(struct gpio_chip *gc, + unsigned int offset) +{ + return ucd9000_gpio_set_direction(gc, offset, UCD9000_GPIO_INPUT, 0); +} + +static int ucd9000_gpio_direction_output(struct gpio_chip *gc, + unsigned int offset, int val) +{ + return ucd9000_gpio_set_direction(gc, offset, UCD9000_GPIO_OUTPUT, + val); +} + +static void ucd9000_probe_gpio(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + int rc; + + switch (mid->driver_data) { + case ucd9090: + data->gpio.ngpio = UCD9090_NUM_GPIOS; + break; + case ucd90120: + case ucd90124: + case ucd90160: + data->gpio.ngpio = UCD901XX_NUM_GPIOS; + break; + case ucd90910: + data->gpio.ngpio = UCD90910_NUM_GPIOS; + break; + default: + return; /* GPIO support is optional. */ + } + + /* + * Pinmux support has not been added to the new gpio_chip. + * This support should be added when possible given the mux + * behavior of these IO devices. + */ + data->gpio.label = client->name; + data->gpio.get_direction = ucd9000_gpio_get_direction; + data->gpio.direction_input = ucd9000_gpio_direction_input; + data->gpio.direction_output = ucd9000_gpio_direction_output; + data->gpio.get = ucd9000_gpio_get; + data->gpio.set = ucd9000_gpio_set; + data->gpio.can_sleep = true; + data->gpio.base = -1; + data->gpio.parent = &client->dev; + + rc = devm_gpiochip_add_data(&client->dev, &data->gpio, client); + if (rc) + dev_warn(&client->dev, "Could not add gpiochip: %d\n", rc); +} +#else +static void ucd9000_probe_gpio(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ +} +#endif /* CONFIG_GPIOLIB */ + +#ifdef CONFIG_DEBUG_FS +static int ucd9000_get_mfr_status(struct i2c_client *client, u8 *buffer) +{ + int ret = pmbus_set_page(client, 0); + + if (ret < 0) + return ret; + + return i2c_smbus_read_block_data(client, UCD9000_MFR_STATUS, buffer); +} + +static int ucd9000_debugfs_show_mfr_status_bit(void *data, u64 *val) +{ + struct ucd9000_debugfs_entry *entry = data; + struct i2c_client *client = entry->client; + u8 buffer[I2C_SMBUS_BLOCK_MAX]; + int ret; + + ret = ucd9000_get_mfr_status(client, buffer); + if (ret < 0) + return ret; + + /* + * Attribute only created for devices with gpi fault bits at bits + * 16-23, which is the second byte of the response. + */ + *val = !!(buffer[1] & BIT(entry->index)); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_bit, + ucd9000_debugfs_show_mfr_status_bit, NULL, "%1lld\n"); + +static ssize_t ucd9000_debugfs_read_mfr_status(struct file *file, + char __user *buf, size_t count, + loff_t *ppos) +{ + struct i2c_client *client = file->private_data; + u8 buffer[I2C_SMBUS_BLOCK_MAX]; + char str[(I2C_SMBUS_BLOCK_MAX * 2) + 2]; + char *res; + int rc; + + rc = ucd9000_get_mfr_status(client, buffer); + if (rc < 0) + return rc; + + res = bin2hex(str, buffer, min(rc, I2C_SMBUS_BLOCK_MAX)); + *res++ = '\n'; + *res = 0; + + return simple_read_from_buffer(buf, count, ppos, str, res - str); +} + +static const struct file_operations ucd9000_debugfs_show_mfr_status_fops = { + .llseek = noop_llseek, + .read = ucd9000_debugfs_read_mfr_status, + .open = simple_open, +}; + +static int ucd9000_init_debugfs(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + struct dentry *debugfs; + struct ucd9000_debugfs_entry *entries; + int i; + char name[UCD9000_DEBUGFS_NAME_LEN]; + + debugfs = pmbus_get_debugfs_dir(client); + if (!debugfs) + return -ENOENT; + + data->debugfs = debugfs_create_dir(client->name, debugfs); + if (!data->debugfs) + return -ENOENT; + + /* + * Of the chips this driver supports, only the UCD9090, UCD90160, + * and UCD90910 report GPI faults in their MFR_STATUS register, so only + * create the GPI fault debugfs attributes for those chips. + */ + if (mid->driver_data == ucd9090 || mid->driver_data == ucd90160 || + mid->driver_data == ucd90910) { + entries = devm_kcalloc(&client->dev, + UCD9000_GPI_COUNT, sizeof(*entries), + GFP_KERNEL); + if (!entries) + return -ENOMEM; + + for (i = 0; i < UCD9000_GPI_COUNT; i++) { + entries[i].client = client; + entries[i].index = i; + scnprintf(name, UCD9000_DEBUGFS_NAME_LEN, + "gpi%d_alarm", i + 1); + debugfs_create_file(name, 0444, data->debugfs, + &entries[i], + &ucd9000_debugfs_mfr_status_bit); + } + } + + scnprintf(name, UCD9000_DEBUGFS_NAME_LEN, "mfr_status"); + debugfs_create_file(name, 0444, data->debugfs, client, + &ucd9000_debugfs_show_mfr_status_fops); + + return 0; +} +#else +static int ucd9000_init_debugfs(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +static int ucd9000_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + struct ucd9000_data *data; + struct pmbus_driver_info *info; + const struct i2c_device_id *mid; + enum chips chip; + int i, ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, UCD9000_DEVICE_ID, + block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read device ID\n"); + return ret; + } + block_buffer[ret] = '\0'; + dev_info(&client->dev, "Device ID %s\n", block_buffer); + + for (mid = ucd9000_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) + break; + } + if (!mid->name[0]) { + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + + if (client->dev.of_node) + chip = (enum chips)of_device_get_match_data(&client->dev); + else + chip = id->driver_data; + + if (chip != ucd9000 && chip != mid->driver_data) + dev_notice(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + id->name, mid->name); + + data = devm_kzalloc(&client->dev, sizeof(struct ucd9000_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + info = &data->info; + + ret = i2c_smbus_read_byte_data(client, UCD9000_NUM_PAGES); + if (ret < 0) { + dev_err(&client->dev, + "Failed to read number of active pages\n"); + return ret; + } + info->pages = ret; + if (!info->pages) { + dev_err(&client->dev, "No pages configured\n"); + return -ENODEV; + } + + /* The internal temperature sensor is always active */ + info->func[0] = PMBUS_HAVE_TEMP; + + /* Everything else is configurable */ + ret = i2c_smbus_read_block_data(client, UCD9000_MONITOR_CONFIG, + block_buffer); + if (ret <= 0) { + dev_err(&client->dev, "Failed to read configuration data\n"); + return -ENODEV; + } + for (i = 0; i < ret; i++) { + int page = UCD9000_MON_PAGE(block_buffer[i]); + + if (page >= info->pages) + continue; + + switch (UCD9000_MON_TYPE(block_buffer[i])) { + case UCD9000_MON_VOLTAGE: + case UCD9000_MON_VOLTAGE_HW: + info->func[page] |= PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_VOUT; + break; + case UCD9000_MON_TEMPERATURE: + info->func[page] |= PMBUS_HAVE_TEMP2 + | PMBUS_HAVE_STATUS_TEMP; + break; + case UCD9000_MON_CURRENT: + info->func[page] |= PMBUS_HAVE_IOUT + | PMBUS_HAVE_STATUS_IOUT; + break; + default: + break; + } + } + + /* Fan configuration */ + if (mid->driver_data == ucd90124) { + for (i = 0; i < UCD9000_NUM_FAN; i++) { + i2c_smbus_write_byte_data(client, + UCD9000_FAN_CONFIG_INDEX, i); + ret = i2c_smbus_read_block_data(client, + UCD9000_FAN_CONFIG, + data->fan_data[i]); + if (ret < 0) + return ret; + } + i2c_smbus_write_byte_data(client, UCD9000_FAN_CONFIG_INDEX, 0); + + info->read_byte_data = ucd9000_read_byte_data; + info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 + | PMBUS_HAVE_FAN34 | PMBUS_HAVE_STATUS_FAN34; + } + + ucd9000_probe_gpio(client, mid, data); + + ret = pmbus_do_probe(client, mid, info); + if (ret) + return ret; + + ret = ucd9000_init_debugfs(client, mid, data); + if (ret) + dev_warn(&client->dev, "Failed to register debugfs: %d\n", + ret); + + return 0; +} + +/* This is the driver that will be inserted */ +static struct i2c_driver ucd9000_driver = { + .driver = { + .name = "ucd9000", + .of_match_table = of_match_ptr(ucd9000_of_match), + }, + .probe = ucd9000_probe, + .remove = pmbus_do_remove, + .id_table = ucd9000_id, +}; + +module_i2c_driver(ucd9000_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for TI UCD90xxx"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/ucd9200.c b/drivers/hwmon/pmbus/ucd9200.c new file mode 100644 index 000000000..3ed945858 --- /dev/null +++ b/drivers/hwmon/pmbus/ucd9200.c @@ -0,0 +1,226 @@ +/* + * Hardware monitoring driver for ucd9200 series Digital PWM System Controllers + * + * Copyright (C) 2011 Ericsson AB. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +#define UCD9200_PHASE_INFO 0xd2 +#define UCD9200_DEVICE_ID 0xfd + +enum chips { ucd9200, ucd9220, ucd9222, ucd9224, ucd9240, ucd9244, ucd9246, + ucd9248 }; + +static const struct i2c_device_id ucd9200_id[] = { + {"ucd9200", ucd9200}, + {"ucd9220", ucd9220}, + {"ucd9222", ucd9222}, + {"ucd9224", ucd9224}, + {"ucd9240", ucd9240}, + {"ucd9244", ucd9244}, + {"ucd9246", ucd9246}, + {"ucd9248", ucd9248}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ucd9200_id); + +static const struct of_device_id ucd9200_of_match[] = { + { + .compatible = "ti,cd9200", + .data = (void *)ucd9200 + }, + { + .compatible = "ti,cd9220", + .data = (void *)ucd9220 + }, + { + .compatible = "ti,cd9222", + .data = (void *)ucd9222 + }, + { + .compatible = "ti,cd9224", + .data = (void *)ucd9224 + }, + { + .compatible = "ti,cd9240", + .data = (void *)ucd9240 + }, + { + .compatible = "ti,cd9244", + .data = (void *)ucd9244 + }, + { + .compatible = "ti,cd9246", + .data = (void *)ucd9246 + }, + { + .compatible = "ti,cd9248", + .data = (void *)ucd9248 + }, + { }, +}; +MODULE_DEVICE_TABLE(of, ucd9200_of_match); + +static int ucd9200_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + struct pmbus_driver_info *info; + const struct i2c_device_id *mid; + enum chips chip; + int i, j, ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, UCD9200_DEVICE_ID, + block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read device ID\n"); + return ret; + } + block_buffer[ret] = '\0'; + dev_info(&client->dev, "Device ID %s\n", block_buffer); + + for (mid = ucd9200_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) + break; + } + if (!mid->name[0]) { + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + + if (client->dev.of_node) + chip = (enum chips)of_device_get_match_data(&client->dev); + else + chip = id->driver_data; + + if (chip != ucd9200 && chip != mid->driver_data) + dev_notice(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + id->name, mid->name); + + info = devm_kzalloc(&client->dev, sizeof(struct pmbus_driver_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + ret = i2c_smbus_read_block_data(client, UCD9200_PHASE_INFO, + block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read phase information\n"); + return ret; + } + + /* + * Calculate number of configured pages (rails) from PHASE_INFO + * register. + * Rails have to be sequential, so we can abort after finding + * the first unconfigured rail. + */ + info->pages = 0; + for (i = 0; i < ret; i++) { + if (!block_buffer[i]) + break; + info->pages++; + } + if (!info->pages) { + dev_err(&client->dev, "No rails configured\n"); + return -ENODEV; + } + dev_info(&client->dev, "%d rails configured\n", info->pages); + + /* + * Set PHASE registers on all pages to 0xff to ensure that phase + * specific commands will apply to all phases of a given page (rail). + * This only affects the READ_IOUT and READ_TEMPERATURE2 registers. + * READ_IOUT will return the sum of currents of all phases of a rail, + * and READ_TEMPERATURE2 will return the maximum temperature detected + * for the the phases of the rail. + */ + for (i = 0; i < info->pages; i++) { + /* + * Setting PAGE & PHASE fails once in a while for no obvious + * reason, so we need to retry a couple of times. + */ + for (j = 0; j < 3; j++) { + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); + if (ret < 0) + continue; + ret = i2c_smbus_write_byte_data(client, PMBUS_PHASE, + 0xff); + if (ret < 0) + continue; + break; + } + if (ret < 0) { + dev_err(&client->dev, + "Failed to initialize PHASE registers\n"); + return ret; + } + } + if (info->pages > 1) + i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP | + PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + + for (i = 1; i < info->pages; i++) + info->func[i] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_POUT | + PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + + /* ucd9240 supports a single fan */ + if (mid->driver_data == ucd9240) + info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12; + + return pmbus_do_probe(client, mid, info); +} + +/* This is the driver that will be inserted */ +static struct i2c_driver ucd9200_driver = { + .driver = { + .name = "ucd9200", + .of_match_table = of_match_ptr(ucd9200_of_match), + }, + .probe = ucd9200_probe, + .remove = pmbus_do_remove, + .id_table = ucd9200_id, +}; + +module_i2c_driver(ucd9200_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for TI UCD922x, UCD924x"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/zl6100.c b/drivers/hwmon/pmbus/zl6100.c new file mode 100644 index 000000000..771802d7e --- /dev/null +++ b/drivers/hwmon/pmbus/zl6100.c @@ -0,0 +1,420 @@ +/* + * Hardware monitoring driver for ZL6100 and compatibles + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +enum chips { zl2004, zl2005, zl2006, zl2008, zl2105, zl2106, zl6100, zl6105, + zl9101, zl9117 }; + +struct zl6100_data { + int id; + ktime_t access; /* chip access time */ + int delay; /* Delay between chip accesses in uS */ + struct pmbus_driver_info info; +}; + +#define to_zl6100_data(x) container_of(x, struct zl6100_data, info) + +#define ZL6100_MFR_CONFIG 0xd0 +#define ZL6100_DEVICE_ID 0xe4 + +#define ZL6100_MFR_XTEMP_ENABLE BIT(7) + +#define MFR_VMON_OV_FAULT_LIMIT 0xf5 +#define MFR_VMON_UV_FAULT_LIMIT 0xf6 +#define MFR_READ_VMON 0xf7 + +#define VMON_UV_WARNING BIT(5) +#define VMON_OV_WARNING BIT(4) +#define VMON_UV_FAULT BIT(1) +#define VMON_OV_FAULT BIT(0) + +#define ZL6100_WAIT_TIME 1000 /* uS */ + +static ushort delay = ZL6100_WAIT_TIME; +module_param(delay, ushort, 0644); +MODULE_PARM_DESC(delay, "Delay between chip accesses in uS"); + +/* Convert linear sensor value to milli-units */ +static long zl6100_l2d(s16 l) +{ + s16 exponent; + s32 mantissa; + long val; + + exponent = l >> 11; + mantissa = ((s16)((l & 0x7ff) << 5)) >> 5; + + val = mantissa; + + /* scale result to milli-units */ + val = val * 1000L; + + if (exponent >= 0) + val <<= exponent; + else + val >>= -exponent; + + return val; +} + +#define MAX_MANTISSA (1023 * 1000) +#define MIN_MANTISSA (511 * 1000) + +static u16 zl6100_d2l(long val) +{ + s16 exponent = 0, mantissa; + bool negative = false; + + /* simple case */ + if (val == 0) + return 0; + + if (val < 0) { + negative = true; + val = -val; + } + + /* Reduce large mantissa until it fits into 10 bit */ + while (val >= MAX_MANTISSA && exponent < 15) { + exponent++; + val >>= 1; + } + /* Increase small mantissa to improve precision */ + while (val < MIN_MANTISSA && exponent > -15) { + exponent--; + val <<= 1; + } + + /* Convert mantissa from milli-units to units */ + mantissa = DIV_ROUND_CLOSEST(val, 1000); + + /* Ensure that resulting number is within range */ + if (mantissa > 0x3ff) + mantissa = 0x3ff; + + /* restore sign */ + if (negative) + mantissa = -mantissa; + + /* Convert to 5 bit exponent, 11 bit mantissa */ + return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); +} + +/* Some chips need a delay between accesses */ +static inline void zl6100_wait(const struct zl6100_data *data) +{ + if (data->delay) { + s64 delta = ktime_us_delta(ktime_get(), data->access); + if (delta < data->delay) + udelay(data->delay - delta); + } +} + +static int zl6100_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct zl6100_data *data = to_zl6100_data(info); + int ret, vreg; + + if (page > 0) + return -ENXIO; + + if (data->id == zl2005) { + /* + * Limit register detection is not reliable on ZL2005. + * Make sure registers are not erroneously detected. + */ + switch (reg) { + case PMBUS_VOUT_OV_WARN_LIMIT: + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + return -ENXIO; + } + } + + switch (reg) { + case PMBUS_VIRT_READ_VMON: + vreg = MFR_READ_VMON; + break; + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + case PMBUS_VIRT_VMON_OV_FAULT_LIMIT: + vreg = MFR_VMON_OV_FAULT_LIMIT; + break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + case PMBUS_VIRT_VMON_UV_FAULT_LIMIT: + vreg = MFR_VMON_UV_FAULT_LIMIT; + break; + default: + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + vreg = reg; + break; + } + + zl6100_wait(data); + ret = pmbus_read_word_data(client, page, vreg); + data->access = ktime_get(); + if (ret < 0) + return ret; + + switch (reg) { + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + ret = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(ret) * 9, 10)); + break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + ret = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(ret) * 11, 10)); + break; + } + + return ret; +} + +static int zl6100_read_byte_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct zl6100_data *data = to_zl6100_data(info); + int ret, status; + + if (page > 0) + return -ENXIO; + + zl6100_wait(data); + + switch (reg) { + case PMBUS_VIRT_STATUS_VMON: + ret = pmbus_read_byte_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (ret < 0) + break; + + status = 0; + if (ret & VMON_UV_WARNING) + status |= PB_VOLTAGE_UV_WARNING; + if (ret & VMON_OV_WARNING) + status |= PB_VOLTAGE_OV_WARNING; + if (ret & VMON_UV_FAULT) + status |= PB_VOLTAGE_UV_FAULT; + if (ret & VMON_OV_FAULT) + status |= PB_VOLTAGE_OV_FAULT; + ret = status; + break; + default: + ret = pmbus_read_byte_data(client, page, reg); + break; + } + data->access = ktime_get(); + + return ret; +} + +static int zl6100_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct zl6100_data *data = to_zl6100_data(info); + int ret, vreg; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + word = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(word) * 10, 9)); + vreg = MFR_VMON_OV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_OV_FAULT_LIMIT: + vreg = MFR_VMON_OV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + word = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(word) * 10, 11)); + vreg = MFR_VMON_UV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_UV_FAULT_LIMIT: + vreg = MFR_VMON_UV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + default: + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + vreg = reg; + } + + zl6100_wait(data); + ret = pmbus_write_word_data(client, page, vreg, word); + data->access = ktime_get(); + + return ret; +} + +static int zl6100_write_byte(struct i2c_client *client, int page, u8 value) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct zl6100_data *data = to_zl6100_data(info); + int ret; + + if (page > 0) + return -ENXIO; + + zl6100_wait(data); + ret = pmbus_write_byte(client, page, value); + data->access = ktime_get(); + + return ret; +} + +static const struct i2c_device_id zl6100_id[] = { + {"bmr450", zl2005}, + {"bmr451", zl2005}, + {"bmr462", zl2008}, + {"bmr463", zl2008}, + {"bmr464", zl2008}, + {"zl2004", zl2004}, + {"zl2005", zl2005}, + {"zl2006", zl2006}, + {"zl2008", zl2008}, + {"zl2105", zl2105}, + {"zl2106", zl2106}, + {"zl6100", zl6100}, + {"zl6105", zl6105}, + {"zl9101", zl9101}, + {"zl9117", zl9117}, + { } +}; +MODULE_DEVICE_TABLE(i2c, zl6100_id); + +static int zl6100_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct zl6100_data *data; + struct pmbus_driver_info *info; + u8 device_id[I2C_SMBUS_BLOCK_MAX + 1]; + const struct i2c_device_id *mid; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA + | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, ZL6100_DEVICE_ID, + device_id); + if (ret < 0) { + dev_err(&client->dev, "Failed to read device ID\n"); + return ret; + } + device_id[ret] = '\0'; + dev_info(&client->dev, "Device ID %s\n", device_id); + + mid = NULL; + for (mid = zl6100_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, device_id, strlen(mid->name))) + break; + } + if (!mid->name[0]) { + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + if (id->driver_data != mid->driver_data) + dev_notice(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + id->name, mid->name); + + data = devm_kzalloc(&client->dev, sizeof(struct zl6100_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->id = mid->driver_data; + + /* + * According to information from the chip vendor, all currently + * supported chips are known to require a wait time between I2C + * accesses. + */ + data->delay = delay; + + /* + * Since there was a direct I2C device access above, wait before + * accessing the chip again. + */ + data->access = ktime_get(); + zl6100_wait(data); + + info = &data->info; + + info->pages = 1; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + + /* + * ZL2004, ZL9101M, and ZL9117M support monitoring an extra voltage + * (VMON for ZL2004, VDRV for ZL9101M and ZL9117M). Report it as vmon. + */ + if (data->id == zl2004 || data->id == zl9101 || data->id == zl9117) + info->func[0] |= PMBUS_HAVE_VMON | PMBUS_HAVE_STATUS_VMON; + + ret = i2c_smbus_read_word_data(client, ZL6100_MFR_CONFIG); + if (ret < 0) + return ret; + + if (ret & ZL6100_MFR_XTEMP_ENABLE) + info->func[0] |= PMBUS_HAVE_TEMP2; + + data->access = ktime_get(); + zl6100_wait(data); + + info->read_word_data = zl6100_read_word_data; + info->read_byte_data = zl6100_read_byte_data; + info->write_word_data = zl6100_write_word_data; + info->write_byte = zl6100_write_byte; + + return pmbus_do_probe(client, mid, info); +} + +static struct i2c_driver zl6100_driver = { + .driver = { + .name = "zl6100", + }, + .probe = zl6100_probe, + .remove = pmbus_do_remove, + .id_table = zl6100_id, +}; + +module_i2c_driver(zl6100_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for ZL6100 and compatibles"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/powr1220.c b/drivers/hwmon/powr1220.c new file mode 100644 index 000000000..3014e4ac7 --- /dev/null +++ b/drivers/hwmon/powr1220.c @@ -0,0 +1,391 @@ +/* + * powr1220.c - Driver for the Lattice POWR1220 programmable power supply + * and monitor. Users can read all ADC inputs along with their labels + * using the sysfs nodes. + * + * Copyright (c) 2014 Echo360 http://www.echo360.com + * Scott Kanowitz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADC_STEP_MV 2 +#define ADC_MAX_LOW_MEASUREMENT_MV 2000 + +enum powr1220_regs { + VMON_STATUS0, + VMON_STATUS1, + VMON_STATUS2, + OUTPUT_STATUS0, + OUTPUT_STATUS1, + OUTPUT_STATUS2, + INPUT_STATUS, + ADC_VALUE_LOW, + ADC_VALUE_HIGH, + ADC_MUX, + UES_BYTE0, + UES_BYTE1, + UES_BYTE2, + UES_BYTE3, + GP_OUTPUT1, + GP_OUTPUT2, + GP_OUTPUT3, + INPUT_VALUE, + RESET, + TRIM1_TRIM, + TRIM2_TRIM, + TRIM3_TRIM, + TRIM4_TRIM, + TRIM5_TRIM, + TRIM6_TRIM, + TRIM7_TRIM, + TRIM8_TRIM, + MAX_POWR1220_REGS +}; + +enum powr1220_adc_values { + VMON1, + VMON2, + VMON3, + VMON4, + VMON5, + VMON6, + VMON7, + VMON8, + VMON9, + VMON10, + VMON11, + VMON12, + VCCA, + VCCINP, + MAX_POWR1220_ADC_VALUES +}; + +struct powr1220_data { + struct i2c_client *client; + struct mutex update_lock; + bool adc_valid[MAX_POWR1220_ADC_VALUES]; + /* the next value is in jiffies */ + unsigned long adc_last_updated[MAX_POWR1220_ADC_VALUES]; + + /* values */ + int adc_maxes[MAX_POWR1220_ADC_VALUES]; + int adc_values[MAX_POWR1220_ADC_VALUES]; +}; + +static const char * const input_names[] = { + [VMON1] = "vmon1", + [VMON2] = "vmon2", + [VMON3] = "vmon3", + [VMON4] = "vmon4", + [VMON5] = "vmon5", + [VMON6] = "vmon6", + [VMON7] = "vmon7", + [VMON8] = "vmon8", + [VMON9] = "vmon9", + [VMON10] = "vmon10", + [VMON11] = "vmon11", + [VMON12] = "vmon12", + [VCCA] = "vcca", + [VCCINP] = "vccinp", +}; + +/* Reads the specified ADC channel */ +static int powr1220_read_adc(struct device *dev, int ch_num) +{ + struct powr1220_data *data = dev_get_drvdata(dev); + int reading; + int result; + int adc_range = 0; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->adc_last_updated[ch_num] + HZ) || + !data->adc_valid[ch_num]) { + /* + * figure out if we need to use the attenuator for + * high inputs or inputs that we don't yet have a measurement + * for. We dynamically set the attenuator depending on the + * max reading. + */ + if (data->adc_maxes[ch_num] > ADC_MAX_LOW_MEASUREMENT_MV || + data->adc_maxes[ch_num] == 0) + adc_range = 1 << 4; + + /* set the attenuator and mux */ + result = i2c_smbus_write_byte_data(data->client, ADC_MUX, + adc_range | ch_num); + if (result) + goto exit; + + /* + * wait at least Tconvert time (200 us) for the + * conversion to complete + */ + udelay(200); + + /* get the ADC reading */ + result = i2c_smbus_read_byte_data(data->client, ADC_VALUE_LOW); + if (result < 0) + goto exit; + + reading = result >> 4; + + /* get the upper half of the reading */ + result = i2c_smbus_read_byte_data(data->client, ADC_VALUE_HIGH); + if (result < 0) + goto exit; + + reading |= result << 4; + + /* now convert the reading to a voltage */ + reading *= ADC_STEP_MV; + data->adc_values[ch_num] = reading; + data->adc_valid[ch_num] = true; + data->adc_last_updated[ch_num] = jiffies; + result = reading; + + if (reading > data->adc_maxes[ch_num]) + data->adc_maxes[ch_num] = reading; + } else { + result = data->adc_values[ch_num]; + } + +exit: + mutex_unlock(&data->update_lock); + + return result; +} + +/* Shows the voltage associated with the specified ADC channel */ +static ssize_t powr1220_show_voltage(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + int adc_val = powr1220_read_adc(dev, attr->index); + + if (adc_val < 0) + return adc_val; + + return sprintf(buf, "%d\n", adc_val); +} + +/* Shows the maximum setting associated with the specified ADC channel */ +static ssize_t powr1220_show_max(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + struct powr1220_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", data->adc_maxes[attr->index]); +} + +/* Shows the label associated with the specified ADC channel */ +static ssize_t powr1220_show_label(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + return sprintf(buf, "%s\n", input_names[attr->index]); +} + +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, powr1220_show_voltage, NULL, + VMON1); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, powr1220_show_voltage, NULL, + VMON2); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, powr1220_show_voltage, NULL, + VMON3); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, powr1220_show_voltage, NULL, + VMON4); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, powr1220_show_voltage, NULL, + VMON5); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, powr1220_show_voltage, NULL, + VMON6); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, powr1220_show_voltage, NULL, + VMON7); +static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, powr1220_show_voltage, NULL, + VMON8); +static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, powr1220_show_voltage, NULL, + VMON9); +static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, powr1220_show_voltage, NULL, + VMON10); +static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, powr1220_show_voltage, NULL, + VMON11); +static SENSOR_DEVICE_ATTR(in11_input, S_IRUGO, powr1220_show_voltage, NULL, + VMON12); +static SENSOR_DEVICE_ATTR(in12_input, S_IRUGO, powr1220_show_voltage, NULL, + VCCA); +static SENSOR_DEVICE_ATTR(in13_input, S_IRUGO, powr1220_show_voltage, NULL, + VCCINP); + +static SENSOR_DEVICE_ATTR(in0_highest, S_IRUGO, powr1220_show_max, NULL, + VMON1); +static SENSOR_DEVICE_ATTR(in1_highest, S_IRUGO, powr1220_show_max, NULL, + VMON2); +static SENSOR_DEVICE_ATTR(in2_highest, S_IRUGO, powr1220_show_max, NULL, + VMON3); +static SENSOR_DEVICE_ATTR(in3_highest, S_IRUGO, powr1220_show_max, NULL, + VMON4); +static SENSOR_DEVICE_ATTR(in4_highest, S_IRUGO, powr1220_show_max, NULL, + VMON5); +static SENSOR_DEVICE_ATTR(in5_highest, S_IRUGO, powr1220_show_max, NULL, + VMON6); +static SENSOR_DEVICE_ATTR(in6_highest, S_IRUGO, powr1220_show_max, NULL, + VMON7); +static SENSOR_DEVICE_ATTR(in7_highest, S_IRUGO, powr1220_show_max, NULL, + VMON8); +static SENSOR_DEVICE_ATTR(in8_highest, S_IRUGO, powr1220_show_max, NULL, + VMON9); +static SENSOR_DEVICE_ATTR(in9_highest, S_IRUGO, powr1220_show_max, NULL, + VMON10); +static SENSOR_DEVICE_ATTR(in10_highest, S_IRUGO, powr1220_show_max, NULL, + VMON11); +static SENSOR_DEVICE_ATTR(in11_highest, S_IRUGO, powr1220_show_max, NULL, + VMON12); +static SENSOR_DEVICE_ATTR(in12_highest, S_IRUGO, powr1220_show_max, NULL, + VCCA); +static SENSOR_DEVICE_ATTR(in13_highest, S_IRUGO, powr1220_show_max, NULL, + VCCINP); + +static SENSOR_DEVICE_ATTR(in0_label, S_IRUGO, powr1220_show_label, NULL, + VMON1); +static SENSOR_DEVICE_ATTR(in1_label, S_IRUGO, powr1220_show_label, NULL, + VMON2); +static SENSOR_DEVICE_ATTR(in2_label, S_IRUGO, powr1220_show_label, NULL, + VMON3); +static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, powr1220_show_label, NULL, + VMON4); +static SENSOR_DEVICE_ATTR(in4_label, S_IRUGO, powr1220_show_label, NULL, + VMON5); +static SENSOR_DEVICE_ATTR(in5_label, S_IRUGO, powr1220_show_label, NULL, + VMON6); +static SENSOR_DEVICE_ATTR(in6_label, S_IRUGO, powr1220_show_label, NULL, + VMON7); +static SENSOR_DEVICE_ATTR(in7_label, S_IRUGO, powr1220_show_label, NULL, + VMON8); +static SENSOR_DEVICE_ATTR(in8_label, S_IRUGO, powr1220_show_label, NULL, + VMON9); +static SENSOR_DEVICE_ATTR(in9_label, S_IRUGO, powr1220_show_label, NULL, + VMON10); +static SENSOR_DEVICE_ATTR(in10_label, S_IRUGO, powr1220_show_label, NULL, + VMON11); +static SENSOR_DEVICE_ATTR(in11_label, S_IRUGO, powr1220_show_label, NULL, + VMON12); +static SENSOR_DEVICE_ATTR(in12_label, S_IRUGO, powr1220_show_label, NULL, + VCCA); +static SENSOR_DEVICE_ATTR(in13_label, S_IRUGO, powr1220_show_label, NULL, + VCCINP); + +static struct attribute *powr1220_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in11_input.dev_attr.attr, + &sensor_dev_attr_in12_input.dev_attr.attr, + &sensor_dev_attr_in13_input.dev_attr.attr, + + &sensor_dev_attr_in0_highest.dev_attr.attr, + &sensor_dev_attr_in1_highest.dev_attr.attr, + &sensor_dev_attr_in2_highest.dev_attr.attr, + &sensor_dev_attr_in3_highest.dev_attr.attr, + &sensor_dev_attr_in4_highest.dev_attr.attr, + &sensor_dev_attr_in5_highest.dev_attr.attr, + &sensor_dev_attr_in6_highest.dev_attr.attr, + &sensor_dev_attr_in7_highest.dev_attr.attr, + &sensor_dev_attr_in8_highest.dev_attr.attr, + &sensor_dev_attr_in9_highest.dev_attr.attr, + &sensor_dev_attr_in10_highest.dev_attr.attr, + &sensor_dev_attr_in11_highest.dev_attr.attr, + &sensor_dev_attr_in12_highest.dev_attr.attr, + &sensor_dev_attr_in13_highest.dev_attr.attr, + + &sensor_dev_attr_in0_label.dev_attr.attr, + &sensor_dev_attr_in1_label.dev_attr.attr, + &sensor_dev_attr_in2_label.dev_attr.attr, + &sensor_dev_attr_in3_label.dev_attr.attr, + &sensor_dev_attr_in4_label.dev_attr.attr, + &sensor_dev_attr_in5_label.dev_attr.attr, + &sensor_dev_attr_in6_label.dev_attr.attr, + &sensor_dev_attr_in7_label.dev_attr.attr, + &sensor_dev_attr_in8_label.dev_attr.attr, + &sensor_dev_attr_in9_label.dev_attr.attr, + &sensor_dev_attr_in10_label.dev_attr.attr, + &sensor_dev_attr_in11_label.dev_attr.attr, + &sensor_dev_attr_in12_label.dev_attr.attr, + &sensor_dev_attr_in13_label.dev_attr.attr, + + NULL +}; + +ATTRIBUTE_GROUPS(powr1220); + +static int powr1220_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct powr1220_data *data; + struct device *hwmon_dev; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->update_lock); + data->client = client; + + hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, + client->name, data, powr1220_groups); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id powr1220_ids[] = { + { "powr1220", 0, }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, powr1220_ids); + +static struct i2c_driver powr1220_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "powr1220", + }, + .probe = powr1220_probe, + .id_table = powr1220_ids, +}; + +module_i2c_driver(powr1220_driver); + +MODULE_AUTHOR("Scott Kanowitz"); +MODULE_DESCRIPTION("POWR1220 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c new file mode 100644 index 000000000..65de80bd6 --- /dev/null +++ b/drivers/hwmon/pwm-fan.c @@ -0,0 +1,355 @@ +/* + * pwm-fan.c - Hwmon driver for fans connected to PWM lines. + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * + * Author: Kamil Debski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_PWM 255 + +struct pwm_fan_ctx { + struct mutex lock; + struct pwm_device *pwm; + unsigned int pwm_value; + unsigned int pwm_fan_state; + unsigned int pwm_fan_max_state; + unsigned int *pwm_fan_cooling_levels; + struct thermal_cooling_device *cdev; +}; + +static int __set_pwm(struct pwm_fan_ctx *ctx, unsigned long pwm) +{ + unsigned long period; + int ret = 0; + struct pwm_state state = { }; + + mutex_lock(&ctx->lock); + if (ctx->pwm_value == pwm) + goto exit_set_pwm_err; + + pwm_init_state(ctx->pwm, &state); + period = ctx->pwm->args.period; + state.duty_cycle = DIV_ROUND_UP(pwm * (period - 1), MAX_PWM); + state.enabled = pwm ? true : false; + + ret = pwm_apply_state(ctx->pwm, &state); + if (!ret) + ctx->pwm_value = pwm; +exit_set_pwm_err: + mutex_unlock(&ctx->lock); + return ret; +} + +static void pwm_fan_update_state(struct pwm_fan_ctx *ctx, unsigned long pwm) +{ + int i; + + for (i = 0; i < ctx->pwm_fan_max_state; ++i) + if (pwm < ctx->pwm_fan_cooling_levels[i + 1]) + break; + + ctx->pwm_fan_state = i; +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); + unsigned long pwm; + int ret; + + if (kstrtoul(buf, 10, &pwm) || pwm > MAX_PWM) + return -EINVAL; + + ret = __set_pwm(ctx, pwm); + if (ret) + return ret; + + pwm_fan_update_state(ctx, pwm); + return count; +} + +static ssize_t show_pwm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", ctx->pwm_value); +} + + +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm, 0); + +static struct attribute *pwm_fan_attrs[] = { + &sensor_dev_attr_pwm1.dev_attr.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(pwm_fan); + +/* thermal cooling device callbacks */ +static int pwm_fan_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct pwm_fan_ctx *ctx = cdev->devdata; + + if (!ctx) + return -EINVAL; + + *state = ctx->pwm_fan_max_state; + + return 0; +} + +static int pwm_fan_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct pwm_fan_ctx *ctx = cdev->devdata; + + if (!ctx) + return -EINVAL; + + *state = ctx->pwm_fan_state; + + return 0; +} + +static int +pwm_fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) +{ + struct pwm_fan_ctx *ctx = cdev->devdata; + int ret; + + if (!ctx || (state > ctx->pwm_fan_max_state)) + return -EINVAL; + + if (state == ctx->pwm_fan_state) + return 0; + + ret = __set_pwm(ctx, ctx->pwm_fan_cooling_levels[state]); + if (ret) { + dev_err(&cdev->device, "Cannot set pwm!\n"); + return ret; + } + + ctx->pwm_fan_state = state; + + return ret; +} + +static const struct thermal_cooling_device_ops pwm_fan_cooling_ops = { + .get_max_state = pwm_fan_get_max_state, + .get_cur_state = pwm_fan_get_cur_state, + .set_cur_state = pwm_fan_set_cur_state, +}; + +static int pwm_fan_of_get_cooling_data(struct device *dev, + struct pwm_fan_ctx *ctx) +{ + struct device_node *np = dev->of_node; + int num, i, ret; + + if (!of_find_property(np, "cooling-levels", NULL)) + return 0; + + ret = of_property_count_u32_elems(np, "cooling-levels"); + if (ret <= 0) { + dev_err(dev, "Wrong data!\n"); + return ret ? : -EINVAL; + } + + num = ret; + ctx->pwm_fan_cooling_levels = devm_kcalloc(dev, num, sizeof(u32), + GFP_KERNEL); + if (!ctx->pwm_fan_cooling_levels) + return -ENOMEM; + + ret = of_property_read_u32_array(np, "cooling-levels", + ctx->pwm_fan_cooling_levels, num); + if (ret) { + dev_err(dev, "Property 'cooling-levels' cannot be read!\n"); + return ret; + } + + for (i = 0; i < num; i++) { + if (ctx->pwm_fan_cooling_levels[i] > MAX_PWM) { + dev_err(dev, "PWM fan state[%d]:%d > %d\n", i, + ctx->pwm_fan_cooling_levels[i], MAX_PWM); + return -EINVAL; + } + } + + ctx->pwm_fan_max_state = num - 1; + + return 0; +} + +static int pwm_fan_probe(struct platform_device *pdev) +{ + struct thermal_cooling_device *cdev; + struct pwm_fan_ctx *ctx; + struct device *hwmon; + int ret; + struct pwm_state state = { }; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + mutex_init(&ctx->lock); + + ctx->pwm = devm_of_pwm_get(&pdev->dev, pdev->dev.of_node, NULL); + if (IS_ERR(ctx->pwm)) { + ret = PTR_ERR(ctx->pwm); + + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Could not get PWM: %d\n", ret); + + return ret; + } + + platform_set_drvdata(pdev, ctx); + + ctx->pwm_value = MAX_PWM; + + /* Set duty cycle to maximum allowed and enable PWM output */ + pwm_init_state(ctx->pwm, &state); + state.duty_cycle = ctx->pwm->args.period - 1; + state.enabled = true; + + ret = pwm_apply_state(ctx->pwm, &state); + if (ret) { + dev_err(&pdev->dev, "Failed to configure PWM\n"); + return ret; + } + + hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, "pwmfan", + ctx, pwm_fan_groups); + if (IS_ERR(hwmon)) { + dev_err(&pdev->dev, "Failed to register hwmon device\n"); + ret = PTR_ERR(hwmon); + goto err_pwm_disable; + } + + ret = pwm_fan_of_get_cooling_data(&pdev->dev, ctx); + if (ret) + goto err_pwm_disable; + + ctx->pwm_fan_state = ctx->pwm_fan_max_state; + if (IS_ENABLED(CONFIG_THERMAL)) { + cdev = thermal_of_cooling_device_register(pdev->dev.of_node, + "pwm-fan", ctx, + &pwm_fan_cooling_ops); + if (IS_ERR(cdev)) { + dev_err(&pdev->dev, + "Failed to register pwm-fan as cooling device"); + ret = PTR_ERR(cdev); + goto err_pwm_disable; + } + ctx->cdev = cdev; + thermal_cdev_update(cdev); + } + + return 0; + +err_pwm_disable: + state.enabled = false; + pwm_apply_state(ctx->pwm, &state); + + return ret; +} + +static int pwm_fan_remove(struct platform_device *pdev) +{ + struct pwm_fan_ctx *ctx = platform_get_drvdata(pdev); + + thermal_cooling_device_unregister(ctx->cdev); + if (ctx->pwm_value) + pwm_disable(ctx->pwm); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int pwm_fan_suspend(struct device *dev) +{ + struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); + struct pwm_args args; + int ret; + + pwm_get_args(ctx->pwm, &args); + + if (ctx->pwm_value) { + ret = pwm_config(ctx->pwm, 0, args.period); + if (ret < 0) + return ret; + + pwm_disable(ctx->pwm); + } + + return 0; +} + +static int pwm_fan_resume(struct device *dev) +{ + struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); + struct pwm_args pargs; + unsigned long duty; + int ret; + + if (ctx->pwm_value == 0) + return 0; + + pwm_get_args(ctx->pwm, &pargs); + duty = DIV_ROUND_UP(ctx->pwm_value * (pargs.period - 1), MAX_PWM); + ret = pwm_config(ctx->pwm, duty, pargs.period); + if (ret) + return ret; + return pwm_enable(ctx->pwm); +} +#endif + +static SIMPLE_DEV_PM_OPS(pwm_fan_pm, pwm_fan_suspend, pwm_fan_resume); + +static const struct of_device_id of_pwm_fan_match[] = { + { .compatible = "pwm-fan", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_pwm_fan_match); + +static struct platform_driver pwm_fan_driver = { + .probe = pwm_fan_probe, + .remove = pwm_fan_remove, + .driver = { + .name = "pwm-fan", + .pm = &pwm_fan_pm, + .of_match_table = of_pwm_fan_match, + }, +}; + +module_platform_driver(pwm_fan_driver); + +MODULE_AUTHOR("Kamil Debski "); +MODULE_ALIAS("platform:pwm-fan"); +MODULE_DESCRIPTION("PWM FAN driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/raspberrypi-hwmon.c b/drivers/hwmon/raspberrypi-hwmon.c new file mode 100644 index 000000000..0d0457245 --- /dev/null +++ b/drivers/hwmon/raspberrypi-hwmon.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Raspberry Pi voltage sensor driver + * + * Based on firmware/raspberrypi.c by Noralf Trønnes + * + * Copyright (C) 2018 Stefan Wahren + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define UNDERVOLTAGE_STICKY_BIT BIT(16) + +struct rpi_hwmon_data { + struct device *hwmon_dev; + struct rpi_firmware *fw; + u32 last_throttled; + struct delayed_work get_values_poll_work; +}; + +static void rpi_firmware_get_throttled(struct rpi_hwmon_data *data) +{ + u32 new_uv, old_uv, value; + int ret; + + /* Request firmware to clear sticky bits */ + value = 0xffff; + + ret = rpi_firmware_property(data->fw, RPI_FIRMWARE_GET_THROTTLED, + &value, sizeof(value)); + if (ret) { + dev_err_once(data->hwmon_dev, "Failed to get throttled (%d)\n", + ret); + return; + } + + new_uv = value & UNDERVOLTAGE_STICKY_BIT; + old_uv = data->last_throttled & UNDERVOLTAGE_STICKY_BIT; + data->last_throttled = value; + + if (new_uv == old_uv) + return; + + if (new_uv) + dev_crit(data->hwmon_dev, "Undervoltage detected!\n"); + else + dev_info(data->hwmon_dev, "Voltage normalised\n"); + + sysfs_notify(&data->hwmon_dev->kobj, NULL, "in0_lcrit_alarm"); +} + +static void get_values_poll(struct work_struct *work) +{ + struct rpi_hwmon_data *data; + + data = container_of(work, struct rpi_hwmon_data, + get_values_poll_work.work); + + rpi_firmware_get_throttled(data); + + /* + * We can't run faster than the sticky shift (100ms) since we get + * flipping in the sticky bits that are cleared. + */ + schedule_delayed_work(&data->get_values_poll_work, 2 * HZ); +} + +static int rpi_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct rpi_hwmon_data *data = dev_get_drvdata(dev); + + *val = !!(data->last_throttled & UNDERVOLTAGE_STICKY_BIT); + return 0; +} + +static umode_t rpi_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + return 0444; +} + +static const u32 rpi_in_config[] = { + HWMON_I_LCRIT_ALARM, + 0 +}; + +static const struct hwmon_channel_info rpi_in = { + .type = hwmon_in, + .config = rpi_in_config, +}; + +static const struct hwmon_channel_info *rpi_info[] = { + &rpi_in, + NULL +}; + +static const struct hwmon_ops rpi_hwmon_ops = { + .is_visible = rpi_is_visible, + .read = rpi_read, +}; + +static const struct hwmon_chip_info rpi_chip_info = { + .ops = &rpi_hwmon_ops, + .info = rpi_info, +}; + +static int rpi_hwmon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rpi_hwmon_data *data; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* Parent driver assure that firmware is correct */ + data->fw = dev_get_drvdata(dev->parent); + + data->hwmon_dev = devm_hwmon_device_register_with_info(dev, "rpi_volt", + data, + &rpi_chip_info, + NULL); + + INIT_DELAYED_WORK(&data->get_values_poll_work, get_values_poll); + platform_set_drvdata(pdev, data); + + if (!PTR_ERR_OR_ZERO(data->hwmon_dev)) + schedule_delayed_work(&data->get_values_poll_work, 2 * HZ); + + return PTR_ERR_OR_ZERO(data->hwmon_dev); +} + +static int rpi_hwmon_remove(struct platform_device *pdev) +{ + struct rpi_hwmon_data *data = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&data->get_values_poll_work); + + return 0; +} + +static struct platform_driver rpi_hwmon_driver = { + .probe = rpi_hwmon_probe, + .remove = rpi_hwmon_remove, + .driver = { + .name = "raspberrypi-hwmon", + }, +}; +module_platform_driver(rpi_hwmon_driver); + +MODULE_AUTHOR("Stefan Wahren "); +MODULE_DESCRIPTION("Raspberry Pi voltage sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:raspberrypi-hwmon"); diff --git a/drivers/hwmon/s3c-hwmon.c b/drivers/hwmon/s3c-hwmon.c new file mode 100644 index 000000000..0c4710d35 --- /dev/null +++ b/drivers/hwmon/s3c-hwmon.c @@ -0,0 +1,391 @@ +/* linux/drivers/hwmon/s3c-hwmon.c + * + * Copyright (C) 2005, 2008, 2009 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks + * + * S3C24XX/S3C64XX ADC hwmon support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +struct s3c_hwmon_attr { + struct sensor_device_attribute in; + struct sensor_device_attribute label; + char in_name[12]; + char label_name[12]; +}; + +/** + * struct s3c_hwmon - ADC hwmon client information + * @lock: Access lock to serialise the conversions. + * @client: The client we registered with the S3C ADC core. + * @hwmon_dev: The hwmon device we created. + * @attr: The holders for the channel attributes. +*/ +struct s3c_hwmon { + struct mutex lock; + struct s3c_adc_client *client; + struct device *hwmon_dev; + + struct s3c_hwmon_attr attrs[8]; +}; + +/** + * s3c_hwmon_read_ch - read a value from a given adc channel. + * @dev: The device. + * @hwmon: Our state. + * @channel: The channel we're reading from. + * + * Read a value from the @channel with the proper locking and sleep until + * either the read completes or we timeout awaiting the ADC core to get + * back to us. + */ +static int s3c_hwmon_read_ch(struct device *dev, + struct s3c_hwmon *hwmon, int channel) +{ + int ret; + + ret = mutex_lock_interruptible(&hwmon->lock); + if (ret < 0) + return ret; + + dev_dbg(dev, "reading channel %d\n", channel); + + ret = s3c_adc_read(hwmon->client, channel); + mutex_unlock(&hwmon->lock); + + return ret; +} + +#ifdef CONFIG_SENSORS_S3C_RAW +/** + * s3c_hwmon_show_raw - show a conversion from the raw channel number. + * @dev: The device that the attribute belongs to. + * @attr: The attribute being read. + * @buf: The result buffer. + * + * This show deals with the raw attribute, registered for each possible + * ADC channel. This does a conversion and returns the raw (un-scaled) + * value returned from the hardware. + */ +static ssize_t s3c_hwmon_show_raw(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct s3c_hwmon *adc = platform_get_drvdata(to_platform_device(dev)); + struct sensor_device_attribute *sa = to_sensor_dev_attr(attr); + int ret; + + ret = s3c_hwmon_read_ch(dev, adc, sa->index); + + return (ret < 0) ? ret : snprintf(buf, PAGE_SIZE, "%d\n", ret); +} + +static SENSOR_DEVICE_ATTR(adc0_raw, S_IRUGO, s3c_hwmon_show_raw, NULL, 0); +static SENSOR_DEVICE_ATTR(adc1_raw, S_IRUGO, s3c_hwmon_show_raw, NULL, 1); +static SENSOR_DEVICE_ATTR(adc2_raw, S_IRUGO, s3c_hwmon_show_raw, NULL, 2); +static SENSOR_DEVICE_ATTR(adc3_raw, S_IRUGO, s3c_hwmon_show_raw, NULL, 3); +static SENSOR_DEVICE_ATTR(adc4_raw, S_IRUGO, s3c_hwmon_show_raw, NULL, 4); +static SENSOR_DEVICE_ATTR(adc5_raw, S_IRUGO, s3c_hwmon_show_raw, NULL, 5); +static SENSOR_DEVICE_ATTR(adc6_raw, S_IRUGO, s3c_hwmon_show_raw, NULL, 6); +static SENSOR_DEVICE_ATTR(adc7_raw, S_IRUGO, s3c_hwmon_show_raw, NULL, 7); + +static struct attribute *s3c_hwmon_attrs[9] = { + &sensor_dev_attr_adc0_raw.dev_attr.attr, + &sensor_dev_attr_adc1_raw.dev_attr.attr, + &sensor_dev_attr_adc2_raw.dev_attr.attr, + &sensor_dev_attr_adc3_raw.dev_attr.attr, + &sensor_dev_attr_adc4_raw.dev_attr.attr, + &sensor_dev_attr_adc5_raw.dev_attr.attr, + &sensor_dev_attr_adc6_raw.dev_attr.attr, + &sensor_dev_attr_adc7_raw.dev_attr.attr, + NULL, +}; + +static struct attribute_group s3c_hwmon_attrgroup = { + .attrs = s3c_hwmon_attrs, +}; + +static inline int s3c_hwmon_add_raw(struct device *dev) +{ + return sysfs_create_group(&dev->kobj, &s3c_hwmon_attrgroup); +} + +static inline void s3c_hwmon_remove_raw(struct device *dev) +{ + sysfs_remove_group(&dev->kobj, &s3c_hwmon_attrgroup); +} + +#else + +static inline int s3c_hwmon_add_raw(struct device *dev) { return 0; } +static inline void s3c_hwmon_remove_raw(struct device *dev) { } + +#endif /* CONFIG_SENSORS_S3C_RAW */ + +/** + * s3c_hwmon_ch_show - show value of a given channel + * @dev: The device that the attribute belongs to. + * @attr: The attribute being read. + * @buf: The result buffer. + * + * Read a value from the ADC and scale it before returning it to the + * caller. The scale factor is gained from the channel configuration + * passed via the platform data when the device was registered. + */ +static ssize_t s3c_hwmon_ch_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sen_attr = to_sensor_dev_attr(attr); + struct s3c_hwmon *hwmon = platform_get_drvdata(to_platform_device(dev)); + struct s3c_hwmon_pdata *pdata = dev_get_platdata(dev); + struct s3c_hwmon_chcfg *cfg; + int ret; + + cfg = pdata->in[sen_attr->index]; + + ret = s3c_hwmon_read_ch(dev, hwmon, sen_attr->index); + if (ret < 0) + return ret; + + ret *= cfg->mult; + ret = DIV_ROUND_CLOSEST(ret, cfg->div); + + return snprintf(buf, PAGE_SIZE, "%d\n", ret); +} + +/** + * s3c_hwmon_label_show - show label name of the given channel. + * @dev: The device that the attribute belongs to. + * @attr: The attribute being read. + * @buf: The result buffer. + * + * Return the label name of a given channel + */ +static ssize_t s3c_hwmon_label_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sen_attr = to_sensor_dev_attr(attr); + struct s3c_hwmon_pdata *pdata = dev_get_platdata(dev); + struct s3c_hwmon_chcfg *cfg; + + cfg = pdata->in[sen_attr->index]; + + return snprintf(buf, PAGE_SIZE, "%s\n", cfg->name); +} + +/** + * s3c_hwmon_create_attr - create hwmon attribute for given channel. + * @dev: The device to create the attribute on. + * @cfg: The channel configuration passed from the platform data. + * @channel: The ADC channel number to process. + * + * Create the scaled attribute for use with hwmon from the specified + * platform data in @pdata. The sysfs entry is handled by the routine + * s3c_hwmon_ch_show(). + * + * The attribute name is taken from the configuration data if present + * otherwise the name is taken by concatenating in_ with the channel + * number. + */ +static int s3c_hwmon_create_attr(struct device *dev, + struct s3c_hwmon_chcfg *cfg, + struct s3c_hwmon_attr *attrs, + int channel) +{ + struct sensor_device_attribute *attr; + int ret; + + snprintf(attrs->in_name, sizeof(attrs->in_name), "in%d_input", channel); + + attr = &attrs->in; + attr->index = channel; + sysfs_attr_init(&attr->dev_attr.attr); + attr->dev_attr.attr.name = attrs->in_name; + attr->dev_attr.attr.mode = S_IRUGO; + attr->dev_attr.show = s3c_hwmon_ch_show; + + ret = device_create_file(dev, &attr->dev_attr); + if (ret < 0) { + dev_err(dev, "failed to create input attribute\n"); + return ret; + } + + /* if this has a name, add a label */ + if (cfg->name) { + snprintf(attrs->label_name, sizeof(attrs->label_name), + "in%d_label", channel); + + attr = &attrs->label; + attr->index = channel; + sysfs_attr_init(&attr->dev_attr.attr); + attr->dev_attr.attr.name = attrs->label_name; + attr->dev_attr.attr.mode = S_IRUGO; + attr->dev_attr.show = s3c_hwmon_label_show; + + ret = device_create_file(dev, &attr->dev_attr); + if (ret < 0) { + device_remove_file(dev, &attrs->in.dev_attr); + dev_err(dev, "failed to create label attribute\n"); + } + } + + return ret; +} + +static void s3c_hwmon_remove_attr(struct device *dev, + struct s3c_hwmon_attr *attrs) +{ + device_remove_file(dev, &attrs->in.dev_attr); + device_remove_file(dev, &attrs->label.dev_attr); +} + +/** + * s3c_hwmon_probe - device probe entry. + * @dev: The device being probed. +*/ +static int s3c_hwmon_probe(struct platform_device *dev) +{ + struct s3c_hwmon_pdata *pdata = dev_get_platdata(&dev->dev); + struct s3c_hwmon *hwmon; + int ret = 0; + int i; + + if (!pdata) { + dev_err(&dev->dev, "no platform data supplied\n"); + return -EINVAL; + } + + hwmon = devm_kzalloc(&dev->dev, sizeof(struct s3c_hwmon), GFP_KERNEL); + if (hwmon == NULL) + return -ENOMEM; + + platform_set_drvdata(dev, hwmon); + + mutex_init(&hwmon->lock); + + /* Register with the core ADC driver. */ + + hwmon->client = s3c_adc_register(dev, NULL, NULL, 0); + if (IS_ERR(hwmon->client)) { + dev_err(&dev->dev, "cannot register adc\n"); + return PTR_ERR(hwmon->client); + } + + /* add attributes for our adc devices. */ + + ret = s3c_hwmon_add_raw(&dev->dev); + if (ret) + goto err_registered; + + /* register with the hwmon core */ + + hwmon->hwmon_dev = hwmon_device_register(&dev->dev); + if (IS_ERR(hwmon->hwmon_dev)) { + dev_err(&dev->dev, "error registering with hwmon\n"); + ret = PTR_ERR(hwmon->hwmon_dev); + goto err_raw_attribute; + } + + for (i = 0; i < ARRAY_SIZE(pdata->in); i++) { + struct s3c_hwmon_chcfg *cfg = pdata->in[i]; + + if (!cfg) + continue; + + if (cfg->mult >= 0x10000) + dev_warn(&dev->dev, + "channel %d multiplier too large\n", + i); + + if (cfg->div == 0) { + dev_err(&dev->dev, "channel %d divider zero\n", i); + continue; + } + + ret = s3c_hwmon_create_attr(&dev->dev, pdata->in[i], + &hwmon->attrs[i], i); + if (ret) { + dev_err(&dev->dev, + "error creating channel %d\n", i); + + for (i--; i >= 0; i--) + s3c_hwmon_remove_attr(&dev->dev, + &hwmon->attrs[i]); + + goto err_hwmon_register; + } + } + + return 0; + + err_hwmon_register: + hwmon_device_unregister(hwmon->hwmon_dev); + + err_raw_attribute: + s3c_hwmon_remove_raw(&dev->dev); + + err_registered: + s3c_adc_release(hwmon->client); + + return ret; +} + +static int s3c_hwmon_remove(struct platform_device *dev) +{ + struct s3c_hwmon *hwmon = platform_get_drvdata(dev); + int i; + + s3c_hwmon_remove_raw(&dev->dev); + + for (i = 0; i < ARRAY_SIZE(hwmon->attrs); i++) + s3c_hwmon_remove_attr(&dev->dev, &hwmon->attrs[i]); + + hwmon_device_unregister(hwmon->hwmon_dev); + s3c_adc_release(hwmon->client); + + return 0; +} + +static struct platform_driver s3c_hwmon_driver = { + .driver = { + .name = "s3c-hwmon", + }, + .probe = s3c_hwmon_probe, + .remove = s3c_hwmon_remove, +}; + +module_platform_driver(s3c_hwmon_driver); + +MODULE_AUTHOR("Ben Dooks "); +MODULE_DESCRIPTION("S3C ADC HWMon driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:s3c-hwmon"); diff --git a/drivers/hwmon/sch5627.c b/drivers/hwmon/sch5627.c new file mode 100644 index 000000000..91544f231 --- /dev/null +++ b/drivers/hwmon/sch5627.c @@ -0,0 +1,604 @@ +/*************************************************************************** + * Copyright (C) 2010-2012 Hans de Goede * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sch56xx-common.h" + +#define DRVNAME "sch5627" +#define DEVNAME DRVNAME /* We only support one model */ + +#define SCH5627_HWMON_ID 0xa5 +#define SCH5627_COMPANY_ID 0x5c +#define SCH5627_PRIMARY_ID 0xa0 + +#define SCH5627_REG_BUILD_CODE 0x39 +#define SCH5627_REG_BUILD_ID 0x3a +#define SCH5627_REG_HWMON_ID 0x3c +#define SCH5627_REG_HWMON_REV 0x3d +#define SCH5627_REG_COMPANY_ID 0x3e +#define SCH5627_REG_PRIMARY_ID 0x3f +#define SCH5627_REG_CTRL 0x40 + +#define SCH5627_NO_TEMPS 8 +#define SCH5627_NO_FANS 4 +#define SCH5627_NO_IN 5 + +static const u16 SCH5627_REG_TEMP_MSB[SCH5627_NO_TEMPS] = { + 0x2B, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x180, 0x181 }; +static const u16 SCH5627_REG_TEMP_LSN[SCH5627_NO_TEMPS] = { + 0xE2, 0xE1, 0xE1, 0xE5, 0xE5, 0xE6, 0x182, 0x182 }; +static const u16 SCH5627_REG_TEMP_HIGH_NIBBLE[SCH5627_NO_TEMPS] = { + 0, 0, 1, 1, 0, 0, 0, 1 }; +static const u16 SCH5627_REG_TEMP_HIGH[SCH5627_NO_TEMPS] = { + 0x61, 0x57, 0x59, 0x5B, 0x5D, 0x5F, 0x184, 0x186 }; +static const u16 SCH5627_REG_TEMP_ABS[SCH5627_NO_TEMPS] = { + 0x9B, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x1A8, 0x1A9 }; + +static const u16 SCH5627_REG_FAN[SCH5627_NO_FANS] = { + 0x2C, 0x2E, 0x30, 0x32 }; +static const u16 SCH5627_REG_FAN_MIN[SCH5627_NO_FANS] = { + 0x62, 0x64, 0x66, 0x68 }; + +static const u16 SCH5627_REG_IN_MSB[SCH5627_NO_IN] = { + 0x22, 0x23, 0x24, 0x25, 0x189 }; +static const u16 SCH5627_REG_IN_LSN[SCH5627_NO_IN] = { + 0xE4, 0xE4, 0xE3, 0xE3, 0x18A }; +static const u16 SCH5627_REG_IN_HIGH_NIBBLE[SCH5627_NO_IN] = { + 1, 0, 1, 0, 1 }; +static const u16 SCH5627_REG_IN_FACTOR[SCH5627_NO_IN] = { + 10745, 3660, 9765, 10745, 3660 }; +static const char * const SCH5627_IN_LABELS[SCH5627_NO_IN] = { + "VCC", "VTT", "VBAT", "VTR", "V_IN" }; + +struct sch5627_data { + unsigned short addr; + struct device *hwmon_dev; + struct sch56xx_watchdog_data *watchdog; + u8 control; + u8 temp_max[SCH5627_NO_TEMPS]; + u8 temp_crit[SCH5627_NO_TEMPS]; + u16 fan_min[SCH5627_NO_FANS]; + + struct mutex update_lock; + unsigned long last_battery; /* In jiffies */ + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + u16 temp[SCH5627_NO_TEMPS]; + u16 fan[SCH5627_NO_FANS]; + u16 in[SCH5627_NO_IN]; +}; + +static struct sch5627_data *sch5627_update_device(struct device *dev) +{ + struct sch5627_data *data = dev_get_drvdata(dev); + struct sch5627_data *ret = data; + int i, val; + + mutex_lock(&data->update_lock); + + /* Trigger a Vbat voltage measurement every 5 minutes */ + if (time_after(jiffies, data->last_battery + 300 * HZ)) { + sch56xx_write_virtual_reg(data->addr, SCH5627_REG_CTRL, + data->control | 0x10); + data->last_battery = jiffies; + } + + /* Cache the values for 1 second */ + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + for (i = 0; i < SCH5627_NO_TEMPS; i++) { + val = sch56xx_read_virtual_reg12(data->addr, + SCH5627_REG_TEMP_MSB[i], + SCH5627_REG_TEMP_LSN[i], + SCH5627_REG_TEMP_HIGH_NIBBLE[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp[i] = val; + } + + for (i = 0; i < SCH5627_NO_FANS; i++) { + val = sch56xx_read_virtual_reg16(data->addr, + SCH5627_REG_FAN[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->fan[i] = val; + } + + for (i = 0; i < SCH5627_NO_IN; i++) { + val = sch56xx_read_virtual_reg12(data->addr, + SCH5627_REG_IN_MSB[i], + SCH5627_REG_IN_LSN[i], + SCH5627_REG_IN_HIGH_NIBBLE[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->in[i] = val; + } + + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +static int sch5627_read_limits(struct sch5627_data *data) +{ + int i, val; + + for (i = 0; i < SCH5627_NO_TEMPS; i++) { + /* + * Note what SMSC calls ABS, is what lm_sensors calls max + * (aka high), and HIGH is what lm_sensors calls crit. + */ + val = sch56xx_read_virtual_reg(data->addr, + SCH5627_REG_TEMP_ABS[i]); + if (val < 0) + return val; + data->temp_max[i] = val; + + val = sch56xx_read_virtual_reg(data->addr, + SCH5627_REG_TEMP_HIGH[i]); + if (val < 0) + return val; + data->temp_crit[i] = val; + } + for (i = 0; i < SCH5627_NO_FANS; i++) { + val = sch56xx_read_virtual_reg16(data->addr, + SCH5627_REG_FAN_MIN[i]); + if (val < 0) + return val; + data->fan_min[i] = val; + } + + return 0; +} + +static int reg_to_temp(u16 reg) +{ + return (reg * 625) / 10 - 64000; +} + +static int reg_to_temp_limit(u8 reg) +{ + return (reg - 64) * 1000; +} + +static int reg_to_rpm(u16 reg) +{ + if (reg == 0) + return -EIO; + if (reg == 0xffff) + return 0; + + return 5400540 / reg; +} + +static ssize_t name_show(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", DEVNAME); +} + +static ssize_t show_temp(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = sch5627_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = reg_to_temp(data->temp[attr->index]); + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_temp_fault(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = sch5627_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return snprintf(buf, PAGE_SIZE, "%d\n", data->temp[attr->index] == 0); +} + +static ssize_t show_temp_max(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = dev_get_drvdata(dev); + int val; + + val = reg_to_temp_limit(data->temp_max[attr->index]); + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_temp_crit(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = dev_get_drvdata(dev); + int val; + + val = reg_to_temp_limit(data->temp_crit[attr->index]); + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_fan(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = sch5627_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = reg_to_rpm(data->fan[attr->index]); + if (val < 0) + return val; + + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_fan_fault(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = sch5627_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return snprintf(buf, PAGE_SIZE, "%d\n", + data->fan[attr->index] == 0xffff); +} + +static ssize_t show_fan_min(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = dev_get_drvdata(dev); + int val = reg_to_rpm(data->fan_min[attr->index]); + if (val < 0) + return val; + + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_in(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5627_data *data = sch5627_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = DIV_ROUND_CLOSEST( + data->in[attr->index] * SCH5627_REG_IN_FACTOR[attr->index], + 10000); + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_in_label(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return snprintf(buf, PAGE_SIZE, "%s\n", + SCH5627_IN_LABELS[attr->index]); +} + +static DEVICE_ATTR_RO(name); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_temp, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_temp, NULL, 7); +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_temp_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_temp_fault, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_fault, S_IRUGO, show_temp_fault, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_fault, S_IRUGO, show_temp_fault, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_fault, S_IRUGO, show_temp_fault, NULL, 7); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp_max, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO, show_temp_max, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO, show_temp_max, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_max, S_IRUGO, show_temp_max, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_max, S_IRUGO, show_temp_max, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_max, S_IRUGO, show_temp_max, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_max, S_IRUGO, show_temp_max, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_max, S_IRUGO, show_temp_max, NULL, 7); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp_crit, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO, show_temp_crit, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_crit, S_IRUGO, show_temp_crit, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_crit, S_IRUGO, show_temp_crit, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_crit, S_IRUGO, show_temp_crit, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_crit, S_IRUGO, show_temp_crit, NULL, 5); +static SENSOR_DEVICE_ATTR(temp7_crit, S_IRUGO, show_temp_crit, NULL, 6); +static SENSOR_DEVICE_ATTR(temp8_crit, S_IRUGO, show_temp_crit, NULL, 7); + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3); +static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, show_fan_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, show_fan_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_fault, S_IRUGO, show_fan_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_fault, S_IRUGO, show_fan_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO, show_fan_min, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO, show_fan_min, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_min, S_IRUGO, show_fan_min, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_min, S_IRUGO, show_fan_min, NULL, 3); + +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_in, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_in, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_in, NULL, 4); +static SENSOR_DEVICE_ATTR(in0_label, S_IRUGO, show_in_label, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_label, S_IRUGO, show_in_label, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_label, S_IRUGO, show_in_label, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, show_in_label, NULL, 3); + +static struct attribute *sch5627_attributes[] = { + &dev_attr_name.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp6_input.dev_attr.attr, + &sensor_dev_attr_temp7_input.dev_attr.attr, + &sensor_dev_attr_temp8_input.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + &sensor_dev_attr_temp5_fault.dev_attr.attr, + &sensor_dev_attr_temp6_fault.dev_attr.attr, + &sensor_dev_attr_temp7_fault.dev_attr.attr, + &sensor_dev_attr_temp8_fault.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp6_max.dev_attr.attr, + &sensor_dev_attr_temp7_max.dev_attr.attr, + &sensor_dev_attr_temp8_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp4_crit.dev_attr.attr, + &sensor_dev_attr_temp5_crit.dev_attr.attr, + &sensor_dev_attr_temp6_crit.dev_attr.attr, + &sensor_dev_attr_temp7_crit.dev_attr.attr, + &sensor_dev_attr_temp8_crit.dev_attr.attr, + + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan1_fault.dev_attr.attr, + &sensor_dev_attr_fan2_fault.dev_attr.attr, + &sensor_dev_attr_fan3_fault.dev_attr.attr, + &sensor_dev_attr_fan4_fault.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan4_min.dev_attr.attr, + + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in0_label.dev_attr.attr, + &sensor_dev_attr_in1_label.dev_attr.attr, + &sensor_dev_attr_in2_label.dev_attr.attr, + &sensor_dev_attr_in3_label.dev_attr.attr, + /* No in4_label as in4 is a generic input pin */ + + NULL +}; + +static const struct attribute_group sch5627_group = { + .attrs = sch5627_attributes, +}; + +static int sch5627_remove(struct platform_device *pdev) +{ + struct sch5627_data *data = platform_get_drvdata(pdev); + + if (data->watchdog) + sch56xx_watchdog_unregister(data->watchdog); + + if (data->hwmon_dev) + hwmon_device_unregister(data->hwmon_dev); + + sysfs_remove_group(&pdev->dev.kobj, &sch5627_group); + + return 0; +} + +static int sch5627_probe(struct platform_device *pdev) +{ + struct sch5627_data *data; + int err, build_code, build_id, hwmon_rev, val; + + data = devm_kzalloc(&pdev->dev, sizeof(struct sch5627_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start; + mutex_init(&data->update_lock); + platform_set_drvdata(pdev, data); + + val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_HWMON_ID); + if (val < 0) { + err = val; + goto error; + } + if (val != SCH5627_HWMON_ID) { + pr_err("invalid %s id: 0x%02X (expected 0x%02X)\n", "hwmon", + val, SCH5627_HWMON_ID); + err = -ENODEV; + goto error; + } + + val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_COMPANY_ID); + if (val < 0) { + err = val; + goto error; + } + if (val != SCH5627_COMPANY_ID) { + pr_err("invalid %s id: 0x%02X (expected 0x%02X)\n", "company", + val, SCH5627_COMPANY_ID); + err = -ENODEV; + goto error; + } + + val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_PRIMARY_ID); + if (val < 0) { + err = val; + goto error; + } + if (val != SCH5627_PRIMARY_ID) { + pr_err("invalid %s id: 0x%02X (expected 0x%02X)\n", "primary", + val, SCH5627_PRIMARY_ID); + err = -ENODEV; + goto error; + } + + build_code = sch56xx_read_virtual_reg(data->addr, + SCH5627_REG_BUILD_CODE); + if (build_code < 0) { + err = build_code; + goto error; + } + + build_id = sch56xx_read_virtual_reg16(data->addr, + SCH5627_REG_BUILD_ID); + if (build_id < 0) { + err = build_id; + goto error; + } + + hwmon_rev = sch56xx_read_virtual_reg(data->addr, + SCH5627_REG_HWMON_REV); + if (hwmon_rev < 0) { + err = hwmon_rev; + goto error; + } + + val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_CTRL); + if (val < 0) { + err = val; + goto error; + } + data->control = val; + if (!(data->control & 0x01)) { + pr_err("hardware monitoring not enabled\n"); + err = -ENODEV; + goto error; + } + /* Trigger a Vbat voltage measurement, so that we get a valid reading + the first time we read Vbat */ + sch56xx_write_virtual_reg(data->addr, SCH5627_REG_CTRL, + data->control | 0x10); + data->last_battery = jiffies; + + /* + * Read limits, we do this only once as reading a register on + * the sch5627 is quite expensive (and they don't change). + */ + err = sch5627_read_limits(data); + if (err) + goto error; + + pr_info("found %s chip at %#hx\n", DEVNAME, data->addr); + pr_info("firmware build: code 0x%02X, id 0x%04X, hwmon: rev 0x%02X\n", + build_code, build_id, hwmon_rev); + + /* Register sysfs interface files */ + err = sysfs_create_group(&pdev->dev.kobj, &sch5627_group); + if (err) + goto error; + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + data->hwmon_dev = NULL; + goto error; + } + + /* Note failing to register the watchdog is not a fatal error */ + data->watchdog = sch56xx_watchdog_register(&pdev->dev, data->addr, + (build_code << 24) | (build_id << 8) | hwmon_rev, + &data->update_lock, 1); + + return 0; + +error: + sch5627_remove(pdev); + return err; +} + +static struct platform_driver sch5627_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = sch5627_probe, + .remove = sch5627_remove, +}; + +module_platform_driver(sch5627_driver); + +MODULE_DESCRIPTION("SMSC SCH5627 Hardware Monitoring Driver"); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/sch5636.c b/drivers/hwmon/sch5636.c new file mode 100644 index 000000000..d24d7b604 --- /dev/null +++ b/drivers/hwmon/sch5636.c @@ -0,0 +1,534 @@ +/*************************************************************************** + * Copyright (C) 2011-2012 Hans de Goede * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sch56xx-common.h" + +#define DRVNAME "sch5636" +#define DEVNAME "theseus" /* We only support one model for now */ + +#define SCH5636_REG_FUJITSU_ID 0x780 +#define SCH5636_REG_FUJITSU_REV 0x783 + +#define SCH5636_NO_INS 5 +#define SCH5636_NO_TEMPS 16 +#define SCH5636_NO_FANS 8 + +static const u16 SCH5636_REG_IN_VAL[SCH5636_NO_INS] = { + 0x22, 0x23, 0x24, 0x25, 0x189 }; +static const u16 SCH5636_REG_IN_FACTORS[SCH5636_NO_INS] = { + 4400, 1500, 4000, 4400, 16000 }; +static const char * const SCH5636_IN_LABELS[SCH5636_NO_INS] = { + "3.3V", "VREF", "VBAT", "3.3AUX", "12V" }; + +static const u16 SCH5636_REG_TEMP_VAL[SCH5636_NO_TEMPS] = { + 0x2B, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x180, 0x181, + 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C }; +#define SCH5636_REG_TEMP_CTRL(i) (0x790 + (i)) +#define SCH5636_TEMP_WORKING 0x01 +#define SCH5636_TEMP_ALARM 0x02 +#define SCH5636_TEMP_DEACTIVATED 0x80 + +static const u16 SCH5636_REG_FAN_VAL[SCH5636_NO_FANS] = { + 0x2C, 0x2E, 0x30, 0x32, 0x62, 0x64, 0x66, 0x68 }; +#define SCH5636_REG_FAN_CTRL(i) (0x880 + (i)) +/* FAULT in datasheet, but acts as an alarm */ +#define SCH5636_FAN_ALARM 0x04 +#define SCH5636_FAN_NOT_PRESENT 0x08 +#define SCH5636_FAN_DEACTIVATED 0x80 + + +struct sch5636_data { + unsigned short addr; + struct device *hwmon_dev; + struct sch56xx_watchdog_data *watchdog; + + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + u8 in[SCH5636_NO_INS]; + u8 temp_val[SCH5636_NO_TEMPS]; + u8 temp_ctrl[SCH5636_NO_TEMPS]; + u16 fan_val[SCH5636_NO_FANS]; + u8 fan_ctrl[SCH5636_NO_FANS]; +}; + +static struct sch5636_data *sch5636_update_device(struct device *dev) +{ + struct sch5636_data *data = dev_get_drvdata(dev); + struct sch5636_data *ret = data; + int i, val; + + mutex_lock(&data->update_lock); + + /* Cache the values for 1 second */ + if (data->valid && !time_after(jiffies, data->last_updated + HZ)) + goto abort; + + for (i = 0; i < SCH5636_NO_INS; i++) { + val = sch56xx_read_virtual_reg(data->addr, + SCH5636_REG_IN_VAL[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->in[i] = val; + } + + for (i = 0; i < SCH5636_NO_TEMPS; i++) { + if (data->temp_ctrl[i] & SCH5636_TEMP_DEACTIVATED) + continue; + + val = sch56xx_read_virtual_reg(data->addr, + SCH5636_REG_TEMP_VAL[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp_val[i] = val; + + val = sch56xx_read_virtual_reg(data->addr, + SCH5636_REG_TEMP_CTRL(i)); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->temp_ctrl[i] = val; + /* Alarms need to be explicitly write-cleared */ + if (val & SCH5636_TEMP_ALARM) { + sch56xx_write_virtual_reg(data->addr, + SCH5636_REG_TEMP_CTRL(i), val); + } + } + + for (i = 0; i < SCH5636_NO_FANS; i++) { + if (data->fan_ctrl[i] & SCH5636_FAN_DEACTIVATED) + continue; + + val = sch56xx_read_virtual_reg16(data->addr, + SCH5636_REG_FAN_VAL[i]); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->fan_val[i] = val; + + val = sch56xx_read_virtual_reg(data->addr, + SCH5636_REG_FAN_CTRL(i)); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->fan_ctrl[i] = val; + /* Alarms need to be explicitly write-cleared */ + if (val & SCH5636_FAN_ALARM) { + sch56xx_write_virtual_reg(data->addr, + SCH5636_REG_FAN_CTRL(i), val); + } + } + + data->last_updated = jiffies; + data->valid = 1; +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +static int reg_to_rpm(u16 reg) +{ + if (reg == 0) + return -EIO; + if (reg == 0xffff) + return 0; + + return 5400540 / reg; +} + +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", DEVNAME); +} + +static ssize_t show_in_value(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5636_data *data = sch5636_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = DIV_ROUND_CLOSEST( + data->in[attr->index] * SCH5636_REG_IN_FACTORS[attr->index], + 255); + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_in_label(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return snprintf(buf, PAGE_SIZE, "%s\n", + SCH5636_IN_LABELS[attr->index]); +} + +static ssize_t show_temp_value(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5636_data *data = sch5636_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = (data->temp_val[attr->index] - 64) * 1000; + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_temp_fault(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5636_data *data = sch5636_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = (data->temp_ctrl[attr->index] & SCH5636_TEMP_WORKING) ? 0 : 1; + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_temp_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5636_data *data = sch5636_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = (data->temp_ctrl[attr->index] & SCH5636_TEMP_ALARM) ? 1 : 0; + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_fan_value(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5636_data *data = sch5636_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = reg_to_rpm(data->fan_val[attr->index]); + if (val < 0) + return val; + + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_fan_fault(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5636_data *data = sch5636_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = (data->fan_ctrl[attr->index] & SCH5636_FAN_NOT_PRESENT) ? 1 : 0; + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t show_fan_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct sch5636_data *data = sch5636_update_device(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = (data->fan_ctrl[attr->index] & SCH5636_FAN_ALARM) ? 1 : 0; + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static struct sensor_device_attribute sch5636_attr[] = { + SENSOR_ATTR(name, 0444, show_name, NULL, 0), + SENSOR_ATTR(in0_input, 0444, show_in_value, NULL, 0), + SENSOR_ATTR(in0_label, 0444, show_in_label, NULL, 0), + SENSOR_ATTR(in1_input, 0444, show_in_value, NULL, 1), + SENSOR_ATTR(in1_label, 0444, show_in_label, NULL, 1), + SENSOR_ATTR(in2_input, 0444, show_in_value, NULL, 2), + SENSOR_ATTR(in2_label, 0444, show_in_label, NULL, 2), + SENSOR_ATTR(in3_input, 0444, show_in_value, NULL, 3), + SENSOR_ATTR(in3_label, 0444, show_in_label, NULL, 3), + SENSOR_ATTR(in4_input, 0444, show_in_value, NULL, 4), + SENSOR_ATTR(in4_label, 0444, show_in_label, NULL, 4), +}; + +static struct sensor_device_attribute sch5636_temp_attr[] = { + SENSOR_ATTR(temp1_input, 0444, show_temp_value, NULL, 0), + SENSOR_ATTR(temp1_fault, 0444, show_temp_fault, NULL, 0), + SENSOR_ATTR(temp1_alarm, 0444, show_temp_alarm, NULL, 0), + SENSOR_ATTR(temp2_input, 0444, show_temp_value, NULL, 1), + SENSOR_ATTR(temp2_fault, 0444, show_temp_fault, NULL, 1), + SENSOR_ATTR(temp2_alarm, 0444, show_temp_alarm, NULL, 1), + SENSOR_ATTR(temp3_input, 0444, show_temp_value, NULL, 2), + SENSOR_ATTR(temp3_fault, 0444, show_temp_fault, NULL, 2), + SENSOR_ATTR(temp3_alarm, 0444, show_temp_alarm, NULL, 2), + SENSOR_ATTR(temp4_input, 0444, show_temp_value, NULL, 3), + SENSOR_ATTR(temp4_fault, 0444, show_temp_fault, NULL, 3), + SENSOR_ATTR(temp4_alarm, 0444, show_temp_alarm, NULL, 3), + SENSOR_ATTR(temp5_input, 0444, show_temp_value, NULL, 4), + SENSOR_ATTR(temp5_fault, 0444, show_temp_fault, NULL, 4), + SENSOR_ATTR(temp5_alarm, 0444, show_temp_alarm, NULL, 4), + SENSOR_ATTR(temp6_input, 0444, show_temp_value, NULL, 5), + SENSOR_ATTR(temp6_fault, 0444, show_temp_fault, NULL, 5), + SENSOR_ATTR(temp6_alarm, 0444, show_temp_alarm, NULL, 5), + SENSOR_ATTR(temp7_input, 0444, show_temp_value, NULL, 6), + SENSOR_ATTR(temp7_fault, 0444, show_temp_fault, NULL, 6), + SENSOR_ATTR(temp7_alarm, 0444, show_temp_alarm, NULL, 6), + SENSOR_ATTR(temp8_input, 0444, show_temp_value, NULL, 7), + SENSOR_ATTR(temp8_fault, 0444, show_temp_fault, NULL, 7), + SENSOR_ATTR(temp8_alarm, 0444, show_temp_alarm, NULL, 7), + SENSOR_ATTR(temp9_input, 0444, show_temp_value, NULL, 8), + SENSOR_ATTR(temp9_fault, 0444, show_temp_fault, NULL, 8), + SENSOR_ATTR(temp9_alarm, 0444, show_temp_alarm, NULL, 8), + SENSOR_ATTR(temp10_input, 0444, show_temp_value, NULL, 9), + SENSOR_ATTR(temp10_fault, 0444, show_temp_fault, NULL, 9), + SENSOR_ATTR(temp10_alarm, 0444, show_temp_alarm, NULL, 9), + SENSOR_ATTR(temp11_input, 0444, show_temp_value, NULL, 10), + SENSOR_ATTR(temp11_fault, 0444, show_temp_fault, NULL, 10), + SENSOR_ATTR(temp11_alarm, 0444, show_temp_alarm, NULL, 10), + SENSOR_ATTR(temp12_input, 0444, show_temp_value, NULL, 11), + SENSOR_ATTR(temp12_fault, 0444, show_temp_fault, NULL, 11), + SENSOR_ATTR(temp12_alarm, 0444, show_temp_alarm, NULL, 11), + SENSOR_ATTR(temp13_input, 0444, show_temp_value, NULL, 12), + SENSOR_ATTR(temp13_fault, 0444, show_temp_fault, NULL, 12), + SENSOR_ATTR(temp13_alarm, 0444, show_temp_alarm, NULL, 12), + SENSOR_ATTR(temp14_input, 0444, show_temp_value, NULL, 13), + SENSOR_ATTR(temp14_fault, 0444, show_temp_fault, NULL, 13), + SENSOR_ATTR(temp14_alarm, 0444, show_temp_alarm, NULL, 13), + SENSOR_ATTR(temp15_input, 0444, show_temp_value, NULL, 14), + SENSOR_ATTR(temp15_fault, 0444, show_temp_fault, NULL, 14), + SENSOR_ATTR(temp15_alarm, 0444, show_temp_alarm, NULL, 14), + SENSOR_ATTR(temp16_input, 0444, show_temp_value, NULL, 15), + SENSOR_ATTR(temp16_fault, 0444, show_temp_fault, NULL, 15), + SENSOR_ATTR(temp16_alarm, 0444, show_temp_alarm, NULL, 15), +}; + +static struct sensor_device_attribute sch5636_fan_attr[] = { + SENSOR_ATTR(fan1_input, 0444, show_fan_value, NULL, 0), + SENSOR_ATTR(fan1_fault, 0444, show_fan_fault, NULL, 0), + SENSOR_ATTR(fan1_alarm, 0444, show_fan_alarm, NULL, 0), + SENSOR_ATTR(fan2_input, 0444, show_fan_value, NULL, 1), + SENSOR_ATTR(fan2_fault, 0444, show_fan_fault, NULL, 1), + SENSOR_ATTR(fan2_alarm, 0444, show_fan_alarm, NULL, 1), + SENSOR_ATTR(fan3_input, 0444, show_fan_value, NULL, 2), + SENSOR_ATTR(fan3_fault, 0444, show_fan_fault, NULL, 2), + SENSOR_ATTR(fan3_alarm, 0444, show_fan_alarm, NULL, 2), + SENSOR_ATTR(fan4_input, 0444, show_fan_value, NULL, 3), + SENSOR_ATTR(fan4_fault, 0444, show_fan_fault, NULL, 3), + SENSOR_ATTR(fan4_alarm, 0444, show_fan_alarm, NULL, 3), + SENSOR_ATTR(fan5_input, 0444, show_fan_value, NULL, 4), + SENSOR_ATTR(fan5_fault, 0444, show_fan_fault, NULL, 4), + SENSOR_ATTR(fan5_alarm, 0444, show_fan_alarm, NULL, 4), + SENSOR_ATTR(fan6_input, 0444, show_fan_value, NULL, 5), + SENSOR_ATTR(fan6_fault, 0444, show_fan_fault, NULL, 5), + SENSOR_ATTR(fan6_alarm, 0444, show_fan_alarm, NULL, 5), + SENSOR_ATTR(fan7_input, 0444, show_fan_value, NULL, 6), + SENSOR_ATTR(fan7_fault, 0444, show_fan_fault, NULL, 6), + SENSOR_ATTR(fan7_alarm, 0444, show_fan_alarm, NULL, 6), + SENSOR_ATTR(fan8_input, 0444, show_fan_value, NULL, 7), + SENSOR_ATTR(fan8_fault, 0444, show_fan_fault, NULL, 7), + SENSOR_ATTR(fan8_alarm, 0444, show_fan_alarm, NULL, 7), +}; + +static int sch5636_remove(struct platform_device *pdev) +{ + struct sch5636_data *data = platform_get_drvdata(pdev); + int i; + + if (data->watchdog) + sch56xx_watchdog_unregister(data->watchdog); + + if (data->hwmon_dev) + hwmon_device_unregister(data->hwmon_dev); + + for (i = 0; i < ARRAY_SIZE(sch5636_attr); i++) + device_remove_file(&pdev->dev, &sch5636_attr[i].dev_attr); + + for (i = 0; i < SCH5636_NO_TEMPS * 3; i++) + device_remove_file(&pdev->dev, + &sch5636_temp_attr[i].dev_attr); + + for (i = 0; i < SCH5636_NO_FANS * 3; i++) + device_remove_file(&pdev->dev, + &sch5636_fan_attr[i].dev_attr); + + return 0; +} + +static int sch5636_probe(struct platform_device *pdev) +{ + struct sch5636_data *data; + int i, err, val, revision[2]; + char id[4]; + + data = devm_kzalloc(&pdev->dev, sizeof(struct sch5636_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start; + mutex_init(&data->update_lock); + platform_set_drvdata(pdev, data); + + for (i = 0; i < 3; i++) { + val = sch56xx_read_virtual_reg(data->addr, + SCH5636_REG_FUJITSU_ID + i); + if (val < 0) { + pr_err("Could not read Fujitsu id byte at %#x\n", + SCH5636_REG_FUJITSU_ID + i); + err = val; + goto error; + } + id[i] = val; + } + id[i] = '\0'; + + if (strcmp(id, "THS")) { + pr_err("Unknown Fujitsu id: %02x%02x%02x\n", + id[0], id[1], id[2]); + err = -ENODEV; + goto error; + } + + for (i = 0; i < 2; i++) { + val = sch56xx_read_virtual_reg(data->addr, + SCH5636_REG_FUJITSU_REV + i); + if (val < 0) { + err = val; + goto error; + } + revision[i] = val; + } + pr_info("Found %s chip at %#hx, revision: %d.%02d\n", DEVNAME, + data->addr, revision[0], revision[1]); + + /* Read all temp + fan ctrl registers to determine which are active */ + for (i = 0; i < SCH5636_NO_TEMPS; i++) { + val = sch56xx_read_virtual_reg(data->addr, + SCH5636_REG_TEMP_CTRL(i)); + if (unlikely(val < 0)) { + err = val; + goto error; + } + data->temp_ctrl[i] = val; + } + + for (i = 0; i < SCH5636_NO_FANS; i++) { + val = sch56xx_read_virtual_reg(data->addr, + SCH5636_REG_FAN_CTRL(i)); + if (unlikely(val < 0)) { + err = val; + goto error; + } + data->fan_ctrl[i] = val; + } + + for (i = 0; i < ARRAY_SIZE(sch5636_attr); i++) { + err = device_create_file(&pdev->dev, + &sch5636_attr[i].dev_attr); + if (err) + goto error; + } + + for (i = 0; i < (SCH5636_NO_TEMPS * 3); i++) { + if (data->temp_ctrl[i/3] & SCH5636_TEMP_DEACTIVATED) + continue; + + err = device_create_file(&pdev->dev, + &sch5636_temp_attr[i].dev_attr); + if (err) + goto error; + } + + for (i = 0; i < (SCH5636_NO_FANS * 3); i++) { + if (data->fan_ctrl[i/3] & SCH5636_FAN_DEACTIVATED) + continue; + + err = device_create_file(&pdev->dev, + &sch5636_fan_attr[i].dev_attr); + if (err) + goto error; + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + data->hwmon_dev = NULL; + goto error; + } + + /* Note failing to register the watchdog is not a fatal error */ + data->watchdog = sch56xx_watchdog_register(&pdev->dev, data->addr, + (revision[0] << 8) | revision[1], + &data->update_lock, 0); + + return 0; + +error: + sch5636_remove(pdev); + return err; +} + +static struct platform_driver sch5636_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = sch5636_probe, + .remove = sch5636_remove, +}; + +module_platform_driver(sch5636_driver); + +MODULE_DESCRIPTION("SMSC SCH5636 Hardware Monitoring Driver"); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/sch56xx-common.c b/drivers/hwmon/sch56xx-common.c new file mode 100644 index 000000000..e1c4e6937 --- /dev/null +++ b/drivers/hwmon/sch56xx-common.c @@ -0,0 +1,589 @@ +/*************************************************************************** + * Copyright (C) 2010-2012 Hans de Goede * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sch56xx-common.h" + +/* Insmod parameters */ +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +#define SIO_SCH56XX_LD_EM 0x0C /* Embedded uController Logical Dev */ +#define SIO_UNLOCK_KEY 0x55 /* Key to enable Super-I/O */ +#define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */ + +#define SIO_REG_LDSEL 0x07 /* Logical device select */ +#define SIO_REG_DEVID 0x20 /* Device ID */ +#define SIO_REG_ENABLE 0x30 /* Logical device enable */ +#define SIO_REG_ADDR 0x66 /* Logical device address (2 bytes) */ + +#define SIO_SCH5627_ID 0xC6 /* Chipset ID */ +#define SIO_SCH5636_ID 0xC7 /* Chipset ID */ + +#define REGION_LENGTH 10 + +#define SCH56XX_CMD_READ 0x02 +#define SCH56XX_CMD_WRITE 0x03 + +/* Watchdog registers */ +#define SCH56XX_REG_WDOG_PRESET 0x58B +#define SCH56XX_REG_WDOG_CONTROL 0x58C +#define SCH56XX_WDOG_TIME_BASE_SEC 0x01 +#define SCH56XX_REG_WDOG_OUTPUT_ENABLE 0x58E +#define SCH56XX_WDOG_OUTPUT_ENABLE 0x02 + +struct sch56xx_watchdog_data { + u16 addr; + struct mutex *io_lock; + struct watchdog_info wdinfo; + struct watchdog_device wddev; + u8 watchdog_preset; + u8 watchdog_control; + u8 watchdog_output_enable; +}; + +static struct platform_device *sch56xx_pdev; + +/* Super I/O functions */ +static inline int superio_inb(int base, int reg) +{ + outb(reg, base); + return inb(base + 1); +} + +static inline int superio_enter(int base) +{ + /* Don't step on other drivers' I/O space by accident */ + if (!request_muxed_region(base, 2, "sch56xx")) { + pr_err("I/O address 0x%04x already in use\n", base); + return -EBUSY; + } + + outb(SIO_UNLOCK_KEY, base); + + return 0; +} + +static inline void superio_select(int base, int ld) +{ + outb(SIO_REG_LDSEL, base); + outb(ld, base + 1); +} + +static inline void superio_exit(int base) +{ + outb(SIO_LOCK_KEY, base); + release_region(base, 2); +} + +static int sch56xx_send_cmd(u16 addr, u8 cmd, u16 reg, u8 v) +{ + u8 val; + int i; + /* + * According to SMSC for the commands we use the maximum time for + * the EM to respond is 15 ms, but testing shows in practice it + * responds within 15-32 reads, so we first busy poll, and if + * that fails sleep a bit and try again until we are way past + * the 15 ms maximum response time. + */ + const int max_busy_polls = 64; + const int max_lazy_polls = 32; + + /* (Optional) Write-Clear the EC to Host Mailbox Register */ + val = inb(addr + 1); + outb(val, addr + 1); + + /* Set Mailbox Address Pointer to first location in Region 1 */ + outb(0x00, addr + 2); + outb(0x80, addr + 3); + + /* Write Request Packet Header */ + outb(cmd, addr + 4); /* VREG Access Type read:0x02 write:0x03 */ + outb(0x01, addr + 5); /* # of Entries: 1 Byte (8-bit) */ + outb(0x04, addr + 2); /* Mailbox AP to first data entry loc. */ + + /* Write Value field */ + if (cmd == SCH56XX_CMD_WRITE) + outb(v, addr + 4); + + /* Write Address field */ + outb(reg & 0xff, addr + 6); + outb(reg >> 8, addr + 7); + + /* Execute the Random Access Command */ + outb(0x01, addr); /* Write 01h to the Host-to-EC register */ + + /* EM Interface Polling "Algorithm" */ + for (i = 0; i < max_busy_polls + max_lazy_polls; i++) { + if (i >= max_busy_polls) + msleep(1); + /* Read Interrupt source Register */ + val = inb(addr + 8); + /* Write Clear the interrupt source bits */ + if (val) + outb(val, addr + 8); + /* Command Completed ? */ + if (val & 0x01) + break; + } + if (i == max_busy_polls + max_lazy_polls) { + pr_err("Max retries exceeded reading virtual register 0x%04hx (%d)\n", + reg, 1); + return -EIO; + } + + /* + * According to SMSC we may need to retry this, but sofar I've always + * seen this succeed in 1 try. + */ + for (i = 0; i < max_busy_polls; i++) { + /* Read EC-to-Host Register */ + val = inb(addr + 1); + /* Command Completed ? */ + if (val == 0x01) + break; + + if (i == 0) + pr_warn("EC reports: 0x%02x reading virtual register 0x%04hx\n", + (unsigned int)val, reg); + } + if (i == max_busy_polls) { + pr_err("Max retries exceeded reading virtual register 0x%04hx (%d)\n", + reg, 2); + return -EIO; + } + + /* + * According to the SMSC app note we should now do: + * + * Set Mailbox Address Pointer to first location in Region 1 * + * outb(0x00, addr + 2); + * outb(0x80, addr + 3); + * + * But if we do that things don't work, so let's not. + */ + + /* Read Value field */ + if (cmd == SCH56XX_CMD_READ) + return inb(addr + 4); + + return 0; +} + +int sch56xx_read_virtual_reg(u16 addr, u16 reg) +{ + return sch56xx_send_cmd(addr, SCH56XX_CMD_READ, reg, 0); +} +EXPORT_SYMBOL(sch56xx_read_virtual_reg); + +int sch56xx_write_virtual_reg(u16 addr, u16 reg, u8 val) +{ + return sch56xx_send_cmd(addr, SCH56XX_CMD_WRITE, reg, val); +} +EXPORT_SYMBOL(sch56xx_write_virtual_reg); + +int sch56xx_read_virtual_reg16(u16 addr, u16 reg) +{ + int lsb, msb; + + /* Read LSB first, this will cause the matching MSB to be latched */ + lsb = sch56xx_read_virtual_reg(addr, reg); + if (lsb < 0) + return lsb; + + msb = sch56xx_read_virtual_reg(addr, reg + 1); + if (msb < 0) + return msb; + + return lsb | (msb << 8); +} +EXPORT_SYMBOL(sch56xx_read_virtual_reg16); + +int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg, + int high_nibble) +{ + int msb, lsn; + + /* Read MSB first, this will cause the matching LSN to be latched */ + msb = sch56xx_read_virtual_reg(addr, msb_reg); + if (msb < 0) + return msb; + + lsn = sch56xx_read_virtual_reg(addr, lsn_reg); + if (lsn < 0) + return lsn; + + if (high_nibble) + return (msb << 4) | (lsn >> 4); + else + return (msb << 4) | (lsn & 0x0f); +} +EXPORT_SYMBOL(sch56xx_read_virtual_reg12); + +/* + * Watchdog routines + */ + +static int watchdog_set_timeout(struct watchdog_device *wddev, + unsigned int timeout) +{ + struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev); + unsigned int resolution; + u8 control; + int ret; + + /* 1 second or 60 second resolution? */ + if (timeout <= 255) + resolution = 1; + else + resolution = 60; + + if (timeout < resolution || timeout > (resolution * 255)) + return -EINVAL; + + if (resolution == 1) + control = data->watchdog_control | SCH56XX_WDOG_TIME_BASE_SEC; + else + control = data->watchdog_control & ~SCH56XX_WDOG_TIME_BASE_SEC; + + if (data->watchdog_control != control) { + mutex_lock(data->io_lock); + ret = sch56xx_write_virtual_reg(data->addr, + SCH56XX_REG_WDOG_CONTROL, + control); + mutex_unlock(data->io_lock); + if (ret) + return ret; + + data->watchdog_control = control; + } + + /* + * Remember new timeout value, but do not write as that (re)starts + * the watchdog countdown. + */ + data->watchdog_preset = DIV_ROUND_UP(timeout, resolution); + wddev->timeout = data->watchdog_preset * resolution; + + return 0; +} + +static int watchdog_start(struct watchdog_device *wddev) +{ + struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev); + int ret; + u8 val; + + /* + * The sch56xx's watchdog cannot really be started / stopped + * it is always running, but we can avoid the timer expiring + * from causing a system reset by clearing the output enable bit. + * + * The sch56xx's watchdog will set the watchdog event bit, bit 0 + * of the second interrupt source register (at base-address + 9), + * when the timer expires. + * + * This will only cause a system reset if the 0-1 flank happens when + * output enable is true. Setting output enable after the flank will + * not cause a reset, nor will the timer expiring a second time. + * This means we must clear the watchdog event bit in case it is set. + * + * The timer may still be running (after a recent watchdog_stop) and + * mere milliseconds away from expiring, so the timer must be reset + * first! + */ + + mutex_lock(data->io_lock); + + /* 1. Reset the watchdog countdown counter */ + ret = sch56xx_write_virtual_reg(data->addr, SCH56XX_REG_WDOG_PRESET, + data->watchdog_preset); + if (ret) + goto leave; + + /* 2. Enable output */ + val = data->watchdog_output_enable | SCH56XX_WDOG_OUTPUT_ENABLE; + ret = sch56xx_write_virtual_reg(data->addr, + SCH56XX_REG_WDOG_OUTPUT_ENABLE, val); + if (ret) + goto leave; + + data->watchdog_output_enable = val; + + /* 3. Clear the watchdog event bit if set */ + val = inb(data->addr + 9); + if (val & 0x01) + outb(0x01, data->addr + 9); + +leave: + mutex_unlock(data->io_lock); + return ret; +} + +static int watchdog_trigger(struct watchdog_device *wddev) +{ + struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev); + int ret; + + /* Reset the watchdog countdown counter */ + mutex_lock(data->io_lock); + ret = sch56xx_write_virtual_reg(data->addr, SCH56XX_REG_WDOG_PRESET, + data->watchdog_preset); + mutex_unlock(data->io_lock); + + return ret; +} + +static int watchdog_stop(struct watchdog_device *wddev) +{ + struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev); + int ret = 0; + u8 val; + + val = data->watchdog_output_enable & ~SCH56XX_WDOG_OUTPUT_ENABLE; + mutex_lock(data->io_lock); + ret = sch56xx_write_virtual_reg(data->addr, + SCH56XX_REG_WDOG_OUTPUT_ENABLE, val); + mutex_unlock(data->io_lock); + if (ret) + return ret; + + data->watchdog_output_enable = val; + return 0; +} + +static const struct watchdog_ops watchdog_ops = { + .owner = THIS_MODULE, + .start = watchdog_start, + .stop = watchdog_stop, + .ping = watchdog_trigger, + .set_timeout = watchdog_set_timeout, +}; + +struct sch56xx_watchdog_data *sch56xx_watchdog_register(struct device *parent, + u16 addr, u32 revision, struct mutex *io_lock, int check_enabled) +{ + struct sch56xx_watchdog_data *data; + int err, control, output_enable; + + /* Cache the watchdog registers */ + mutex_lock(io_lock); + control = + sch56xx_read_virtual_reg(addr, SCH56XX_REG_WDOG_CONTROL); + output_enable = + sch56xx_read_virtual_reg(addr, SCH56XX_REG_WDOG_OUTPUT_ENABLE); + mutex_unlock(io_lock); + + if (control < 0) + return NULL; + if (output_enable < 0) + return NULL; + if (check_enabled && !(output_enable & SCH56XX_WDOG_OUTPUT_ENABLE)) { + pr_warn("Watchdog not enabled by BIOS, not registering\n"); + return NULL; + } + + data = kzalloc(sizeof(struct sch56xx_watchdog_data), GFP_KERNEL); + if (!data) + return NULL; + + data->addr = addr; + data->io_lock = io_lock; + + strlcpy(data->wdinfo.identity, "sch56xx watchdog", + sizeof(data->wdinfo.identity)); + data->wdinfo.firmware_version = revision; + data->wdinfo.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT; + if (!nowayout) + data->wdinfo.options |= WDIOF_MAGICCLOSE; + + data->wddev.info = &data->wdinfo; + data->wddev.ops = &watchdog_ops; + data->wddev.parent = parent; + data->wddev.timeout = 60; + data->wddev.min_timeout = 1; + data->wddev.max_timeout = 255 * 60; + if (nowayout) + set_bit(WDOG_NO_WAY_OUT, &data->wddev.status); + if (output_enable & SCH56XX_WDOG_OUTPUT_ENABLE) + set_bit(WDOG_HW_RUNNING, &data->wddev.status); + + /* Since the watchdog uses a downcounter there is no register to read + the BIOS set timeout from (if any was set at all) -> + Choose a preset which will give us a 1 minute timeout */ + if (control & SCH56XX_WDOG_TIME_BASE_SEC) + data->watchdog_preset = 60; /* seconds */ + else + data->watchdog_preset = 1; /* minute */ + + data->watchdog_control = control; + data->watchdog_output_enable = output_enable; + + watchdog_set_drvdata(&data->wddev, data); + err = watchdog_register_device(&data->wddev); + if (err) { + pr_err("Registering watchdog chardev: %d\n", err); + kfree(data); + return NULL; + } + + return data; +} +EXPORT_SYMBOL(sch56xx_watchdog_register); + +void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data) +{ + watchdog_unregister_device(&data->wddev); + kfree(data); +} +EXPORT_SYMBOL(sch56xx_watchdog_unregister); + +/* + * platform dev find, add and remove functions + */ + +static int __init sch56xx_find(int sioaddr, const char **name) +{ + u8 devid; + unsigned short address; + int err; + + err = superio_enter(sioaddr); + if (err) + return err; + + devid = superio_inb(sioaddr, SIO_REG_DEVID); + switch (devid) { + case SIO_SCH5627_ID: + *name = "sch5627"; + break; + case SIO_SCH5636_ID: + *name = "sch5636"; + break; + default: + pr_debug("Unsupported device id: 0x%02x\n", + (unsigned int)devid); + err = -ENODEV; + goto exit; + } + + superio_select(sioaddr, SIO_SCH56XX_LD_EM); + + if (!(superio_inb(sioaddr, SIO_REG_ENABLE) & 0x01)) { + pr_warn("Device not activated\n"); + err = -ENODEV; + goto exit; + } + + /* + * Warning the order of the low / high byte is the other way around + * as on most other superio devices!! + */ + address = superio_inb(sioaddr, SIO_REG_ADDR) | + superio_inb(sioaddr, SIO_REG_ADDR + 1) << 8; + if (address == 0) { + pr_warn("Base address not set\n"); + err = -ENODEV; + goto exit; + } + err = address; + +exit: + superio_exit(sioaddr); + return err; +} + +static int __init sch56xx_device_add(int address, const char *name) +{ + struct resource res = { + .start = address, + .end = address + REGION_LENGTH - 1, + .flags = IORESOURCE_IO, + }; + int err; + + sch56xx_pdev = platform_device_alloc(name, address); + if (!sch56xx_pdev) + return -ENOMEM; + + res.name = sch56xx_pdev->name; + err = acpi_check_resource_conflict(&res); + if (err) + goto exit_device_put; + + err = platform_device_add_resources(sch56xx_pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed\n"); + goto exit_device_put; + } + + err = platform_device_add(sch56xx_pdev); + if (err) { + pr_err("Device addition failed\n"); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(sch56xx_pdev); + + return err; +} + +static int __init sch56xx_init(void) +{ + int address; + const char *name = NULL; + + address = sch56xx_find(0x4e, &name); + if (address < 0) + address = sch56xx_find(0x2e, &name); + if (address < 0) + return address; + + return sch56xx_device_add(address, name); +} + +static void __exit sch56xx_exit(void) +{ + platform_device_unregister(sch56xx_pdev); +} + +MODULE_DESCRIPTION("SMSC SCH56xx Hardware Monitoring Common Code"); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL"); + +module_init(sch56xx_init); +module_exit(sch56xx_exit); diff --git a/drivers/hwmon/sch56xx-common.h b/drivers/hwmon/sch56xx-common.h new file mode 100644 index 000000000..704ea2c6d --- /dev/null +++ b/drivers/hwmon/sch56xx-common.h @@ -0,0 +1,32 @@ +/*************************************************************************** + * Copyright (C) 2010-2012 Hans de Goede * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include + +struct sch56xx_watchdog_data; + +int sch56xx_read_virtual_reg(u16 addr, u16 reg); +int sch56xx_write_virtual_reg(u16 addr, u16 reg, u8 val); +int sch56xx_read_virtual_reg16(u16 addr, u16 reg); +int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg, + int high_nibble); + +struct sch56xx_watchdog_data *sch56xx_watchdog_register(struct device *parent, + u16 addr, u32 revision, struct mutex *io_lock, int check_enabled); +void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data); diff --git a/drivers/hwmon/scmi-hwmon.c b/drivers/hwmon/scmi-hwmon.c new file mode 100644 index 000000000..91bfecdb3 --- /dev/null +++ b/drivers/hwmon/scmi-hwmon.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface(SCMI) based hwmon sensor driver + * + * Copyright (C) 2018 ARM Ltd. + * Sudeep Holla + */ + +#include +#include +#include +#include +#include +#include + +struct scmi_sensors { + const struct scmi_handle *handle; + const struct scmi_sensor_info **info[hwmon_max]; +}; + +static int scmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + u64 value; + const struct scmi_sensor_info *sensor; + struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev); + const struct scmi_handle *h = scmi_sensors->handle; + + sensor = *(scmi_sensors->info[type] + channel); + ret = h->sensor_ops->reading_get(h, sensor->id, false, &value); + if (!ret) + *val = value; + + return ret; +} + +static int +scmi_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + const struct scmi_sensor_info *sensor; + struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev); + + sensor = *(scmi_sensors->info[type] + channel); + *str = sensor->name; + + return 0; +} + +static umode_t +scmi_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct scmi_sensor_info *sensor; + const struct scmi_sensors *scmi_sensors = drvdata; + + sensor = *(scmi_sensors->info[type] + channel); + if (sensor && sensor->name) + return S_IRUGO; + + return 0; +} + +static const struct hwmon_ops scmi_hwmon_ops = { + .is_visible = scmi_hwmon_is_visible, + .read = scmi_hwmon_read, + .read_string = scmi_hwmon_read_string, +}; + +static struct hwmon_chip_info scmi_chip_info = { + .ops = &scmi_hwmon_ops, + .info = NULL, +}; + +static int scmi_hwmon_add_chan_info(struct hwmon_channel_info *scmi_hwmon_chan, + struct device *dev, int num, + enum hwmon_sensor_types type, u32 config) +{ + int i; + u32 *cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); + + if (!cfg) + return -ENOMEM; + + scmi_hwmon_chan->type = type; + scmi_hwmon_chan->config = cfg; + for (i = 0; i < num; i++, cfg++) + *cfg = config; + + return 0; +} + +static enum hwmon_sensor_types scmi_types[] = { + [TEMPERATURE_C] = hwmon_temp, + [VOLTAGE] = hwmon_in, + [CURRENT] = hwmon_curr, + [POWER] = hwmon_power, + [ENERGY] = hwmon_energy, +}; + +static u32 hwmon_attributes[hwmon_max] = { + [hwmon_chip] = HWMON_C_REGISTER_TZ, + [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, + [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, + [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, + [hwmon_power] = HWMON_P_INPUT | HWMON_P_LABEL, + [hwmon_energy] = HWMON_E_INPUT | HWMON_E_LABEL, +}; + +static int scmi_hwmon_probe(struct scmi_device *sdev) +{ + int i, idx; + u16 nr_sensors; + enum hwmon_sensor_types type; + struct scmi_sensors *scmi_sensors; + const struct scmi_sensor_info *sensor; + int nr_count[hwmon_max] = {0}, nr_types = 0; + const struct hwmon_chip_info *chip_info; + struct device *hwdev, *dev = &sdev->dev; + struct hwmon_channel_info *scmi_hwmon_chan; + const struct hwmon_channel_info **ptr_scmi_ci; + const struct scmi_handle *handle = sdev->handle; + + if (!handle || !handle->sensor_ops) + return -ENODEV; + + nr_sensors = handle->sensor_ops->count_get(handle); + if (!nr_sensors) + return -EIO; + + scmi_sensors = devm_kzalloc(dev, sizeof(*scmi_sensors), GFP_KERNEL); + if (!scmi_sensors) + return -ENOMEM; + + scmi_sensors->handle = handle; + + for (i = 0; i < nr_sensors; i++) { + sensor = handle->sensor_ops->info_get(handle, i); + if (!sensor) + return -EINVAL; + + switch (sensor->type) { + case TEMPERATURE_C: + case VOLTAGE: + case CURRENT: + case POWER: + case ENERGY: + type = scmi_types[sensor->type]; + if (!nr_count[type]) + nr_types++; + nr_count[type]++; + break; + } + } + + if (nr_count[hwmon_temp]) + nr_count[hwmon_chip]++, nr_types++; + + scmi_hwmon_chan = devm_kcalloc(dev, nr_types, sizeof(*scmi_hwmon_chan), + GFP_KERNEL); + if (!scmi_hwmon_chan) + return -ENOMEM; + + ptr_scmi_ci = devm_kcalloc(dev, nr_types + 1, sizeof(*ptr_scmi_ci), + GFP_KERNEL); + if (!ptr_scmi_ci) + return -ENOMEM; + + scmi_chip_info.info = ptr_scmi_ci; + chip_info = &scmi_chip_info; + + for (type = 0; type < hwmon_max; type++) { + if (!nr_count[type]) + continue; + + scmi_hwmon_add_chan_info(scmi_hwmon_chan, dev, nr_count[type], + type, hwmon_attributes[type]); + *ptr_scmi_ci++ = scmi_hwmon_chan++; + + scmi_sensors->info[type] = + devm_kcalloc(dev, nr_count[type], + sizeof(*scmi_sensors->info), GFP_KERNEL); + if (!scmi_sensors->info[type]) + return -ENOMEM; + } + + for (i = nr_sensors - 1; i >= 0 ; i--) { + sensor = handle->sensor_ops->info_get(handle, i); + if (!sensor) + continue; + + switch (sensor->type) { + case TEMPERATURE_C: + case VOLTAGE: + case CURRENT: + case POWER: + case ENERGY: + type = scmi_types[sensor->type]; + idx = --nr_count[type]; + *(scmi_sensors->info[type] + idx) = sensor; + break; + } + } + + hwdev = devm_hwmon_device_register_with_info(dev, "scmi_sensors", + scmi_sensors, chip_info, + NULL); + + return PTR_ERR_OR_ZERO(hwdev); +} + +static const struct scmi_device_id scmi_id_table[] = { + { SCMI_PROTOCOL_SENSOR }, + { }, +}; +MODULE_DEVICE_TABLE(scmi, scmi_id_table); + +static struct scmi_driver scmi_hwmon_drv = { + .name = "scmi-hwmon", + .probe = scmi_hwmon_probe, + .id_table = scmi_id_table, +}; +module_scmi_driver(scmi_hwmon_drv); + +MODULE_AUTHOR("Sudeep Holla "); +MODULE_DESCRIPTION("ARM SCMI HWMON interface driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/scpi-hwmon.c b/drivers/hwmon/scpi-hwmon.c new file mode 100644 index 000000000..562f3e287 --- /dev/null +++ b/drivers/hwmon/scpi-hwmon.c @@ -0,0 +1,318 @@ +/* + * System Control and Power Interface(SCPI) based hwmon sensor driver + * + * Copyright (C) 2015 ARM Ltd. + * Punit Agrawal + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct sensor_data { + unsigned int scale; + struct scpi_sensor_info info; + struct device_attribute dev_attr_input; + struct device_attribute dev_attr_label; + char input[20]; + char label[20]; +}; + +struct scpi_thermal_zone { + int sensor_id; + struct scpi_sensors *scpi_sensors; +}; + +struct scpi_sensors { + struct scpi_ops *scpi_ops; + struct sensor_data *data; + struct list_head thermal_zones; + struct attribute **attrs; + struct attribute_group group; + const struct attribute_group *groups[2]; +}; + +static const u32 gxbb_scpi_scale[] = { + [TEMPERATURE] = 1, /* (celsius) */ + [VOLTAGE] = 1000, /* (millivolts) */ + [CURRENT] = 1000, /* (milliamperes) */ + [POWER] = 1000000, /* (microwatts) */ + [ENERGY] = 1000000, /* (microjoules) */ +}; + +static const u32 scpi_scale[] = { + [TEMPERATURE] = 1000, /* (millicelsius) */ + [VOLTAGE] = 1000, /* (millivolts) */ + [CURRENT] = 1000, /* (milliamperes) */ + [POWER] = 1000000, /* (microwatts) */ + [ENERGY] = 1000000, /* (microjoules) */ +}; + +static void scpi_scale_reading(u64 *value, struct sensor_data *sensor) +{ + if (scpi_scale[sensor->info.class] != sensor->scale) { + *value *= scpi_scale[sensor->info.class]; + do_div(*value, sensor->scale); + } +} + +static int scpi_read_temp(void *dev, int *temp) +{ + struct scpi_thermal_zone *zone = dev; + struct scpi_sensors *scpi_sensors = zone->scpi_sensors; + struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops; + struct sensor_data *sensor = &scpi_sensors->data[zone->sensor_id]; + u64 value; + int ret; + + ret = scpi_ops->sensor_get_value(sensor->info.sensor_id, &value); + if (ret) + return ret; + + scpi_scale_reading(&value, sensor); + + *temp = value; + return 0; +} + +/* hwmon callback functions */ +static ssize_t +scpi_show_sensor(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct scpi_sensors *scpi_sensors = dev_get_drvdata(dev); + struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops; + struct sensor_data *sensor; + u64 value; + int ret; + + sensor = container_of(attr, struct sensor_data, dev_attr_input); + + ret = scpi_ops->sensor_get_value(sensor->info.sensor_id, &value); + if (ret) + return ret; + + scpi_scale_reading(&value, sensor); + + /* + * Temperature sensor values are treated as signed values based on + * observation even though that is not explicitly specified, and + * because an unsigned u64 temperature does not really make practical + * sense especially when the temperature is below zero degrees Celsius. + */ + if (sensor->info.class == TEMPERATURE) + return sprintf(buf, "%lld\n", (s64)value); + + return sprintf(buf, "%llu\n", value); +} + +static ssize_t +scpi_show_label(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_data *sensor; + + sensor = container_of(attr, struct sensor_data, dev_attr_label); + + return sprintf(buf, "%s\n", sensor->info.name); +} + +static const struct thermal_zone_of_device_ops scpi_sensor_ops = { + .get_temp = scpi_read_temp, +}; + +static const struct of_device_id scpi_of_match[] = { + {.compatible = "arm,scpi-sensors", .data = &scpi_scale}, + {.compatible = "amlogic,meson-gxbb-scpi-sensors", .data = &gxbb_scpi_scale}, + {}, +}; +MODULE_DEVICE_TABLE(of, scpi_of_match); + +static int scpi_hwmon_probe(struct platform_device *pdev) +{ + u16 nr_sensors, i; + const u32 *scale; + int num_temp = 0, num_volt = 0, num_current = 0, num_power = 0; + int num_energy = 0; + struct scpi_ops *scpi_ops; + struct device *hwdev, *dev = &pdev->dev; + struct scpi_sensors *scpi_sensors; + const struct of_device_id *of_id; + int idx, ret; + + scpi_ops = get_scpi_ops(); + if (!scpi_ops) + return -EPROBE_DEFER; + + ret = scpi_ops->sensor_get_capability(&nr_sensors); + if (ret) + return ret; + + if (!nr_sensors) + return -ENODEV; + + scpi_sensors = devm_kzalloc(dev, sizeof(*scpi_sensors), GFP_KERNEL); + if (!scpi_sensors) + return -ENOMEM; + + scpi_sensors->data = devm_kcalloc(dev, nr_sensors, + sizeof(*scpi_sensors->data), GFP_KERNEL); + if (!scpi_sensors->data) + return -ENOMEM; + + scpi_sensors->attrs = devm_kcalloc(dev, (nr_sensors * 2) + 1, + sizeof(*scpi_sensors->attrs), GFP_KERNEL); + if (!scpi_sensors->attrs) + return -ENOMEM; + + scpi_sensors->scpi_ops = scpi_ops; + + of_id = of_match_device(scpi_of_match, &pdev->dev); + if (!of_id) { + dev_err(&pdev->dev, "Unable to initialize scpi-hwmon data\n"); + return -ENODEV; + } + scale = of_id->data; + + for (i = 0, idx = 0; i < nr_sensors; i++) { + struct sensor_data *sensor = &scpi_sensors->data[idx]; + + ret = scpi_ops->sensor_get_info(i, &sensor->info); + if (ret) + return ret; + + switch (sensor->info.class) { + case TEMPERATURE: + snprintf(sensor->input, sizeof(sensor->input), + "temp%d_input", num_temp + 1); + snprintf(sensor->label, sizeof(sensor->input), + "temp%d_label", num_temp + 1); + num_temp++; + break; + case VOLTAGE: + snprintf(sensor->input, sizeof(sensor->input), + "in%d_input", num_volt); + snprintf(sensor->label, sizeof(sensor->input), + "in%d_label", num_volt); + num_volt++; + break; + case CURRENT: + snprintf(sensor->input, sizeof(sensor->input), + "curr%d_input", num_current + 1); + snprintf(sensor->label, sizeof(sensor->input), + "curr%d_label", num_current + 1); + num_current++; + break; + case POWER: + snprintf(sensor->input, sizeof(sensor->input), + "power%d_input", num_power + 1); + snprintf(sensor->label, sizeof(sensor->input), + "power%d_label", num_power + 1); + num_power++; + break; + case ENERGY: + snprintf(sensor->input, sizeof(sensor->input), + "energy%d_input", num_energy + 1); + snprintf(sensor->label, sizeof(sensor->input), + "energy%d_label", num_energy + 1); + num_energy++; + break; + default: + continue; + } + + sensor->scale = scale[sensor->info.class]; + + sensor->dev_attr_input.attr.mode = S_IRUGO; + sensor->dev_attr_input.show = scpi_show_sensor; + sensor->dev_attr_input.attr.name = sensor->input; + + sensor->dev_attr_label.attr.mode = S_IRUGO; + sensor->dev_attr_label.show = scpi_show_label; + sensor->dev_attr_label.attr.name = sensor->label; + + scpi_sensors->attrs[idx << 1] = &sensor->dev_attr_input.attr; + scpi_sensors->attrs[(idx << 1) + 1] = &sensor->dev_attr_label.attr; + + sysfs_attr_init(scpi_sensors->attrs[idx << 1]); + sysfs_attr_init(scpi_sensors->attrs[(idx << 1) + 1]); + idx++; + } + + scpi_sensors->group.attrs = scpi_sensors->attrs; + scpi_sensors->groups[0] = &scpi_sensors->group; + + platform_set_drvdata(pdev, scpi_sensors); + + hwdev = devm_hwmon_device_register_with_groups(dev, + "scpi_sensors", scpi_sensors, scpi_sensors->groups); + + if (IS_ERR(hwdev)) + return PTR_ERR(hwdev); + + /* + * Register the temperature sensors with the thermal framework + * to allow their usage in setting up the thermal zones from + * device tree. + * + * NOTE: Not all temperature sensors maybe used for thermal + * control + */ + INIT_LIST_HEAD(&scpi_sensors->thermal_zones); + for (i = 0; i < nr_sensors; i++) { + struct sensor_data *sensor = &scpi_sensors->data[i]; + struct thermal_zone_device *z; + struct scpi_thermal_zone *zone; + + if (sensor->info.class != TEMPERATURE) + continue; + + zone = devm_kzalloc(dev, sizeof(*zone), GFP_KERNEL); + if (!zone) + return -ENOMEM; + + zone->sensor_id = i; + zone->scpi_sensors = scpi_sensors; + z = devm_thermal_zone_of_sensor_register(dev, + sensor->info.sensor_id, + zone, + &scpi_sensor_ops); + /* + * The call to thermal_zone_of_sensor_register returns + * an error for sensors that are not associated with + * any thermal zones or if the thermal subsystem is + * not configured. + */ + if (IS_ERR(z)) { + devm_kfree(dev, zone); + continue; + } + } + + return 0; +} + +static struct platform_driver scpi_hwmon_platdrv = { + .driver = { + .name = "scpi-hwmon", + .of_match_table = scpi_of_match, + }, + .probe = scpi_hwmon_probe, +}; +module_platform_driver(scpi_hwmon_platdrv); + +MODULE_AUTHOR("Punit Agrawal "); +MODULE_DESCRIPTION("ARM SCPI HWMON interface driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c new file mode 100644 index 000000000..2be77752c --- /dev/null +++ b/drivers/hwmon/sht15.c @@ -0,0 +1,1076 @@ +/* + * sht15.c - support for the SHT15 Temperature and Humidity Sensor + * + * Portions Copyright (c) 2010-2012 Savoir-faire Linux Inc. + * Jerome Oufella + * Vivien Didelot + * + * Copyright (c) 2009 Jonathan Cameron + * + * Copyright (c) 2007 Wouter Horre + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * For further information, see the Documentation/hwmon/sht15 file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Commands */ +#define SHT15_MEASURE_TEMP 0x03 +#define SHT15_MEASURE_RH 0x05 +#define SHT15_WRITE_STATUS 0x06 +#define SHT15_READ_STATUS 0x07 +#define SHT15_SOFT_RESET 0x1E + +/* Min timings */ +#define SHT15_TSCKL 100 /* (nsecs) clock low */ +#define SHT15_TSCKH 100 /* (nsecs) clock high */ +#define SHT15_TSU 150 /* (nsecs) data setup time */ +#define SHT15_TSRST 11 /* (msecs) soft reset time */ + +/* Status Register Bits */ +#define SHT15_STATUS_LOW_RESOLUTION 0x01 +#define SHT15_STATUS_NO_OTP_RELOAD 0x02 +#define SHT15_STATUS_HEATER 0x04 +#define SHT15_STATUS_LOW_BATTERY 0x40 + +/* List of supported chips */ +enum sht15_chips { sht10, sht11, sht15, sht71, sht75 }; + +/* Actions the driver may be doing */ +enum sht15_state { + SHT15_READING_NOTHING, + SHT15_READING_TEMP, + SHT15_READING_HUMID +}; + +/** + * struct sht15_temppair - elements of voltage dependent temp calc + * @vdd: supply voltage in microvolts + * @d1: see data sheet + */ +struct sht15_temppair { + int vdd; /* microvolts */ + int d1; +}; + +/* Table 9 from datasheet - relates temperature calculation to supply voltage */ +static const struct sht15_temppair temppoints[] = { + { 2500000, -39400 }, + { 3000000, -39600 }, + { 3500000, -39700 }, + { 4000000, -39800 }, + { 5000000, -40100 }, +}; + +/* Table from CRC datasheet, section 2.4 */ +static const u8 sht15_crc8_table[] = { + 0, 49, 98, 83, 196, 245, 166, 151, + 185, 136, 219, 234, 125, 76, 31, 46, + 67, 114, 33, 16, 135, 182, 229, 212, + 250, 203, 152, 169, 62, 15, 92, 109, + 134, 183, 228, 213, 66, 115, 32, 17, + 63, 14, 93, 108, 251, 202, 153, 168, + 197, 244, 167, 150, 1, 48, 99, 82, + 124, 77, 30, 47, 184, 137, 218, 235, + 61, 12, 95, 110, 249, 200, 155, 170, + 132, 181, 230, 215, 64, 113, 34, 19, + 126, 79, 28, 45, 186, 139, 216, 233, + 199, 246, 165, 148, 3, 50, 97, 80, + 187, 138, 217, 232, 127, 78, 29, 44, + 2, 51, 96, 81, 198, 247, 164, 149, + 248, 201, 154, 171, 60, 13, 94, 111, + 65, 112, 35, 18, 133, 180, 231, 214, + 122, 75, 24, 41, 190, 143, 220, 237, + 195, 242, 161, 144, 7, 54, 101, 84, + 57, 8, 91, 106, 253, 204, 159, 174, + 128, 177, 226, 211, 68, 117, 38, 23, + 252, 205, 158, 175, 56, 9, 90, 107, + 69, 116, 39, 22, 129, 176, 227, 210, + 191, 142, 221, 236, 123, 74, 25, 40, + 6, 55, 100, 85, 194, 243, 160, 145, + 71, 118, 37, 20, 131, 178, 225, 208, + 254, 207, 156, 173, 58, 11, 88, 105, + 4, 53, 102, 87, 192, 241, 162, 147, + 189, 140, 223, 238, 121, 72, 27, 42, + 193, 240, 163, 146, 5, 52, 103, 86, + 120, 73, 26, 43, 188, 141, 222, 239, + 130, 179, 224, 209, 70, 119, 36, 21, + 59, 10, 89, 104, 255, 206, 157, 172 +}; + +/** + * struct sht15_data - device instance specific data + * @sck: clock GPIO line + * @data: data GPIO line + * @read_work: bh of interrupt handler. + * @wait_queue: wait queue for getting values from device. + * @val_temp: last temperature value read from device. + * @val_humid: last humidity value read from device. + * @val_status: last status register value read from device. + * @checksum_ok: last value read from the device passed CRC validation. + * @checksumming: flag used to enable the data validation with CRC. + * @state: state identifying the action the driver is doing. + * @measurements_valid: are the current stored measures valid (start condition). + * @status_valid: is the current stored status valid (start condition). + * @last_measurement: time of last measure. + * @last_status: time of last status reading. + * @read_lock: mutex to ensure only one read in progress at a time. + * @dev: associate device structure. + * @hwmon_dev: device associated with hwmon subsystem. + * @reg: associated regulator (if specified). + * @nb: notifier block to handle notifications of voltage + * changes. + * @supply_uv: local copy of supply voltage used to allow use of + * regulator consumer if available. + * @supply_uv_valid: indicates that an updated value has not yet been + * obtained from the regulator and so any calculations + * based upon it will be invalid. + * @update_supply_work: work struct that is used to update the supply_uv. + * @interrupt_handled: flag used to indicate a handler has been scheduled. + */ +struct sht15_data { + struct gpio_desc *sck; + struct gpio_desc *data; + struct work_struct read_work; + wait_queue_head_t wait_queue; + uint16_t val_temp; + uint16_t val_humid; + u8 val_status; + bool checksum_ok; + bool checksumming; + enum sht15_state state; + bool measurements_valid; + bool status_valid; + unsigned long last_measurement; + unsigned long last_status; + struct mutex read_lock; + struct device *dev; + struct device *hwmon_dev; + struct regulator *reg; + struct notifier_block nb; + int supply_uv; + bool supply_uv_valid; + struct work_struct update_supply_work; + atomic_t interrupt_handled; +}; + +/** + * sht15_crc8() - compute crc8 + * @data: sht15 specific data. + * @value: sht15 retrieved data. + * @len: Length of retrieved data + * + * This implements section 2 of the CRC datasheet. + */ +static u8 sht15_crc8(struct sht15_data *data, + const u8 *value, + int len) +{ + u8 crc = bitrev8(data->val_status & 0x0F); + + while (len--) { + crc = sht15_crc8_table[*value ^ crc]; + value++; + } + + return crc; +} + +/** + * sht15_connection_reset() - reset the comms interface + * @data: sht15 specific data + * + * This implements section 3.4 of the data sheet + */ +static int sht15_connection_reset(struct sht15_data *data) +{ + int i, err; + + err = gpiod_direction_output(data->data, 1); + if (err) + return err; + ndelay(SHT15_TSCKL); + gpiod_set_value(data->sck, 0); + ndelay(SHT15_TSCKL); + for (i = 0; i < 9; ++i) { + gpiod_set_value(data->sck, 1); + ndelay(SHT15_TSCKH); + gpiod_set_value(data->sck, 0); + ndelay(SHT15_TSCKL); + } + return 0; +} + +/** + * sht15_send_bit() - send an individual bit to the device + * @data: device state data + * @val: value of bit to be sent + */ +static inline void sht15_send_bit(struct sht15_data *data, int val) +{ + gpiod_set_value(data->data, val); + ndelay(SHT15_TSU); + gpiod_set_value(data->sck, 1); + ndelay(SHT15_TSCKH); + gpiod_set_value(data->sck, 0); + ndelay(SHT15_TSCKL); /* clock low time */ +} + +/** + * sht15_transmission_start() - specific sequence for new transmission + * @data: device state data + * + * Timings for this are not documented on the data sheet, so very + * conservative ones used in implementation. This implements + * figure 12 on the data sheet. + */ +static int sht15_transmission_start(struct sht15_data *data) +{ + int err; + + /* ensure data is high and output */ + err = gpiod_direction_output(data->data, 1); + if (err) + return err; + ndelay(SHT15_TSU); + gpiod_set_value(data->sck, 0); + ndelay(SHT15_TSCKL); + gpiod_set_value(data->sck, 1); + ndelay(SHT15_TSCKH); + gpiod_set_value(data->data, 0); + ndelay(SHT15_TSU); + gpiod_set_value(data->sck, 0); + ndelay(SHT15_TSCKL); + gpiod_set_value(data->sck, 1); + ndelay(SHT15_TSCKH); + gpiod_set_value(data->data, 1); + ndelay(SHT15_TSU); + gpiod_set_value(data->sck, 0); + ndelay(SHT15_TSCKL); + return 0; +} + +/** + * sht15_send_byte() - send a single byte to the device + * @data: device state + * @byte: value to be sent + */ +static void sht15_send_byte(struct sht15_data *data, u8 byte) +{ + int i; + + for (i = 0; i < 8; i++) { + sht15_send_bit(data, !!(byte & 0x80)); + byte <<= 1; + } +} + +/** + * sht15_wait_for_response() - checks for ack from device + * @data: device state + */ +static int sht15_wait_for_response(struct sht15_data *data) +{ + int err; + + err = gpiod_direction_input(data->data); + if (err) + return err; + gpiod_set_value(data->sck, 1); + ndelay(SHT15_TSCKH); + if (gpiod_get_value(data->data)) { + gpiod_set_value(data->sck, 0); + dev_err(data->dev, "Command not acknowledged\n"); + err = sht15_connection_reset(data); + if (err) + return err; + return -EIO; + } + gpiod_set_value(data->sck, 0); + ndelay(SHT15_TSCKL); + return 0; +} + +/** + * sht15_send_cmd() - Sends a command to the device. + * @data: device state + * @cmd: command byte to be sent + * + * On entry, sck is output low, data is output pull high + * and the interrupt disabled. + */ +static int sht15_send_cmd(struct sht15_data *data, u8 cmd) +{ + int err; + + err = sht15_transmission_start(data); + if (err) + return err; + sht15_send_byte(data, cmd); + return sht15_wait_for_response(data); +} + +/** + * sht15_soft_reset() - send a soft reset command + * @data: sht15 specific data. + * + * As described in section 3.2 of the datasheet. + */ +static int sht15_soft_reset(struct sht15_data *data) +{ + int ret; + + ret = sht15_send_cmd(data, SHT15_SOFT_RESET); + if (ret) + return ret; + msleep(SHT15_TSRST); + /* device resets default hardware status register value */ + data->val_status = 0; + + return ret; +} + +/** + * sht15_ack() - send a ack + * @data: sht15 specific data. + * + * Each byte of data is acknowledged by pulling the data line + * low for one clock pulse. + */ +static int sht15_ack(struct sht15_data *data) +{ + int err; + + err = gpiod_direction_output(data->data, 0); + if (err) + return err; + ndelay(SHT15_TSU); + gpiod_set_value(data->sck, 1); + ndelay(SHT15_TSU); + gpiod_set_value(data->sck, 0); + ndelay(SHT15_TSU); + gpiod_set_value(data->data, 1); + + return gpiod_direction_input(data->data); +} + +/** + * sht15_end_transmission() - notify device of end of transmission + * @data: device state. + * + * This is basically a NAK (single clock pulse, data high). + */ +static int sht15_end_transmission(struct sht15_data *data) +{ + int err; + + err = gpiod_direction_output(data->data, 1); + if (err) + return err; + ndelay(SHT15_TSU); + gpiod_set_value(data->sck, 1); + ndelay(SHT15_TSCKH); + gpiod_set_value(data->sck, 0); + ndelay(SHT15_TSCKL); + return 0; +} + +/** + * sht15_read_byte() - Read a byte back from the device + * @data: device state. + */ +static u8 sht15_read_byte(struct sht15_data *data) +{ + int i; + u8 byte = 0; + + for (i = 0; i < 8; ++i) { + byte <<= 1; + gpiod_set_value(data->sck, 1); + ndelay(SHT15_TSCKH); + byte |= !!gpiod_get_value(data->data); + gpiod_set_value(data->sck, 0); + ndelay(SHT15_TSCKL); + } + return byte; +} + +/** + * sht15_send_status() - write the status register byte + * @data: sht15 specific data. + * @status: the byte to set the status register with. + * + * As described in figure 14 and table 5 of the datasheet. + */ +static int sht15_send_status(struct sht15_data *data, u8 status) +{ + int err; + + err = sht15_send_cmd(data, SHT15_WRITE_STATUS); + if (err) + return err; + err = gpiod_direction_output(data->data, 1); + if (err) + return err; + ndelay(SHT15_TSU); + sht15_send_byte(data, status); + err = sht15_wait_for_response(data); + if (err) + return err; + + data->val_status = status; + return 0; +} + +/** + * sht15_update_status() - get updated status register from device if too old + * @data: device instance specific data. + * + * As described in figure 15 and table 5 of the datasheet. + */ +static int sht15_update_status(struct sht15_data *data) +{ + int ret = 0; + u8 status; + u8 previous_config; + u8 dev_checksum = 0; + u8 checksum_vals[2]; + int timeout = HZ; + + mutex_lock(&data->read_lock); + if (time_after(jiffies, data->last_status + timeout) + || !data->status_valid) { + ret = sht15_send_cmd(data, SHT15_READ_STATUS); + if (ret) + goto unlock; + status = sht15_read_byte(data); + + if (data->checksumming) { + sht15_ack(data); + dev_checksum = bitrev8(sht15_read_byte(data)); + checksum_vals[0] = SHT15_READ_STATUS; + checksum_vals[1] = status; + data->checksum_ok = (sht15_crc8(data, checksum_vals, 2) + == dev_checksum); + } + + ret = sht15_end_transmission(data); + if (ret) + goto unlock; + + /* + * Perform checksum validation on the received data. + * Specification mentions that in case a checksum verification + * fails, a soft reset command must be sent to the device. + */ + if (data->checksumming && !data->checksum_ok) { + previous_config = data->val_status & 0x07; + ret = sht15_soft_reset(data); + if (ret) + goto unlock; + if (previous_config) { + ret = sht15_send_status(data, previous_config); + if (ret) { + dev_err(data->dev, + "CRC validation failed, unable " + "to restore device settings\n"); + goto unlock; + } + } + ret = -EAGAIN; + goto unlock; + } + + data->val_status = status; + data->status_valid = true; + data->last_status = jiffies; + } + +unlock: + mutex_unlock(&data->read_lock); + return ret; +} + +/** + * sht15_measurement() - get a new value from device + * @data: device instance specific data + * @command: command sent to request value + * @timeout_msecs: timeout after which comms are assumed + * to have failed are reset. + */ +static int sht15_measurement(struct sht15_data *data, + int command, + int timeout_msecs) +{ + int ret; + u8 previous_config; + + ret = sht15_send_cmd(data, command); + if (ret) + return ret; + + ret = gpiod_direction_input(data->data); + if (ret) + return ret; + atomic_set(&data->interrupt_handled, 0); + + enable_irq(gpiod_to_irq(data->data)); + if (gpiod_get_value(data->data) == 0) { + disable_irq_nosync(gpiod_to_irq(data->data)); + /* Only relevant if the interrupt hasn't occurred. */ + if (!atomic_read(&data->interrupt_handled)) + schedule_work(&data->read_work); + } + ret = wait_event_timeout(data->wait_queue, + (data->state == SHT15_READING_NOTHING), + msecs_to_jiffies(timeout_msecs)); + if (data->state != SHT15_READING_NOTHING) { /* I/O error occurred */ + data->state = SHT15_READING_NOTHING; + return -EIO; + } else if (ret == 0) { /* timeout occurred */ + disable_irq_nosync(gpiod_to_irq(data->data)); + ret = sht15_connection_reset(data); + if (ret) + return ret; + return -ETIME; + } + + /* + * Perform checksum validation on the received data. + * Specification mentions that in case a checksum verification fails, + * a soft reset command must be sent to the device. + */ + if (data->checksumming && !data->checksum_ok) { + previous_config = data->val_status & 0x07; + ret = sht15_soft_reset(data); + if (ret) + return ret; + if (previous_config) { + ret = sht15_send_status(data, previous_config); + if (ret) { + dev_err(data->dev, + "CRC validation failed, unable " + "to restore device settings\n"); + return ret; + } + } + return -EAGAIN; + } + + return 0; +} + +/** + * sht15_update_measurements() - get updated measures from device if too old + * @data: device state + */ +static int sht15_update_measurements(struct sht15_data *data) +{ + int ret = 0; + int timeout = HZ; + + mutex_lock(&data->read_lock); + if (time_after(jiffies, data->last_measurement + timeout) + || !data->measurements_valid) { + data->state = SHT15_READING_HUMID; + ret = sht15_measurement(data, SHT15_MEASURE_RH, 160); + if (ret) + goto unlock; + data->state = SHT15_READING_TEMP; + ret = sht15_measurement(data, SHT15_MEASURE_TEMP, 400); + if (ret) + goto unlock; + data->measurements_valid = true; + data->last_measurement = jiffies; + } + +unlock: + mutex_unlock(&data->read_lock); + return ret; +} + +/** + * sht15_calc_temp() - convert the raw reading to a temperature + * @data: device state + * + * As per section 4.3 of the data sheet. + */ +static inline int sht15_calc_temp(struct sht15_data *data) +{ + int d1 = temppoints[0].d1; + int d2 = (data->val_status & SHT15_STATUS_LOW_RESOLUTION) ? 40 : 10; + int i; + + for (i = ARRAY_SIZE(temppoints) - 1; i > 0; i--) + /* Find pointer to interpolate */ + if (data->supply_uv > temppoints[i - 1].vdd) { + d1 = (data->supply_uv - temppoints[i - 1].vdd) + * (temppoints[i].d1 - temppoints[i - 1].d1) + / (temppoints[i].vdd - temppoints[i - 1].vdd) + + temppoints[i - 1].d1; + break; + } + + return data->val_temp * d2 + d1; +} + +/** + * sht15_calc_humid() - using last temperature convert raw to humid + * @data: device state + * + * This is the temperature compensated version as per section 4.2 of + * the data sheet. + * + * The sensor is assumed to be V3, which is compatible with V4. + * Humidity conversion coefficients are shown in table 7 of the datasheet. + */ +static inline int sht15_calc_humid(struct sht15_data *data) +{ + int rh_linear; /* milli percent */ + int temp = sht15_calc_temp(data); + int c2, c3; + int t2; + const int c1 = -4; + + if (data->val_status & SHT15_STATUS_LOW_RESOLUTION) { + c2 = 648000; /* x 10 ^ -6 */ + c3 = -7200; /* x 10 ^ -7 */ + t2 = 1280; + } else { + c2 = 40500; /* x 10 ^ -6 */ + c3 = -28; /* x 10 ^ -7 */ + t2 = 80; + } + + rh_linear = c1 * 1000 + + c2 * data->val_humid / 1000 + + (data->val_humid * data->val_humid * c3) / 10000; + return (temp - 25000) * (10000 + t2 * data->val_humid) + / 1000000 + rh_linear; +} + +/** + * sht15_show_status() - show status information in sysfs + * @dev: device. + * @attr: device attribute. + * @buf: sysfs buffer where information is written to. + * + * Will be called on read access to temp1_fault, humidity1_fault + * and heater_enable sysfs attributes. + * Returns number of bytes written into buffer, negative errno on error. + */ +static ssize_t sht15_show_status(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + struct sht15_data *data = dev_get_drvdata(dev); + u8 bit = to_sensor_dev_attr(attr)->index; + + ret = sht15_update_status(data); + + return ret ? ret : sprintf(buf, "%d\n", !!(data->val_status & bit)); +} + +/** + * sht15_store_heater() - change heater state via sysfs + * @dev: device. + * @attr: device attribute. + * @buf: sysfs buffer to read the new heater state from. + * @count: length of the data. + * + * Will be called on write access to heater_enable sysfs attribute. + * Returns number of bytes actually decoded, negative errno on error. + */ +static ssize_t sht15_store_heater(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct sht15_data *data = dev_get_drvdata(dev); + long value; + u8 status; + + if (kstrtol(buf, 10, &value)) + return -EINVAL; + + mutex_lock(&data->read_lock); + status = data->val_status & 0x07; + if (!!value) + status |= SHT15_STATUS_HEATER; + else + status &= ~SHT15_STATUS_HEATER; + + ret = sht15_send_status(data, status); + mutex_unlock(&data->read_lock); + + return ret ? ret : count; +} + +/** + * sht15_show_temp() - show temperature measurement value in sysfs + * @dev: device. + * @attr: device attribute. + * @buf: sysfs buffer where measurement values are written to. + * + * Will be called on read access to temp1_input sysfs attribute. + * Returns number of bytes written into buffer, negative errno on error. + */ +static ssize_t sht15_show_temp(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + struct sht15_data *data = dev_get_drvdata(dev); + + /* Technically no need to read humidity as well */ + ret = sht15_update_measurements(data); + + return ret ? ret : sprintf(buf, "%d\n", + sht15_calc_temp(data)); +} + +/** + * sht15_show_humidity() - show humidity measurement value in sysfs + * @dev: device. + * @attr: device attribute. + * @buf: sysfs buffer where measurement values are written to. + * + * Will be called on read access to humidity1_input sysfs attribute. + * Returns number of bytes written into buffer, negative errno on error. + */ +static ssize_t sht15_show_humidity(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + struct sht15_data *data = dev_get_drvdata(dev); + + ret = sht15_update_measurements(data); + + return ret ? ret : sprintf(buf, "%d\n", sht15_calc_humid(data)); +} + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + return sprintf(buf, "%s\n", pdev->name); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, + sht15_show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(humidity1_input, S_IRUGO, + sht15_show_humidity, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, sht15_show_status, NULL, + SHT15_STATUS_LOW_BATTERY); +static SENSOR_DEVICE_ATTR(humidity1_fault, S_IRUGO, sht15_show_status, NULL, + SHT15_STATUS_LOW_BATTERY); +static SENSOR_DEVICE_ATTR(heater_enable, S_IRUGO | S_IWUSR, sht15_show_status, + sht15_store_heater, SHT15_STATUS_HEATER); +static DEVICE_ATTR_RO(name); +static struct attribute *sht15_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_humidity1_input.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_humidity1_fault.dev_attr.attr, + &sensor_dev_attr_heater_enable.dev_attr.attr, + &dev_attr_name.attr, + NULL, +}; + +static const struct attribute_group sht15_attr_group = { + .attrs = sht15_attrs, +}; + +static irqreturn_t sht15_interrupt_fired(int irq, void *d) +{ + struct sht15_data *data = d; + + /* First disable the interrupt */ + disable_irq_nosync(irq); + atomic_inc(&data->interrupt_handled); + /* Then schedule a reading work struct */ + if (data->state != SHT15_READING_NOTHING) + schedule_work(&data->read_work); + return IRQ_HANDLED; +} + +static void sht15_bh_read_data(struct work_struct *work_s) +{ + uint16_t val = 0; + u8 dev_checksum = 0; + u8 checksum_vals[3]; + struct sht15_data *data + = container_of(work_s, struct sht15_data, + read_work); + + /* Firstly, verify the line is low */ + if (gpiod_get_value(data->data)) { + /* + * If not, then start the interrupt again - care here as could + * have gone low in meantime so verify it hasn't! + */ + atomic_set(&data->interrupt_handled, 0); + enable_irq(gpiod_to_irq(data->data)); + /* If still not occurred or another handler was scheduled */ + if (gpiod_get_value(data->data) + || atomic_read(&data->interrupt_handled)) + return; + } + + /* Read the data back from the device */ + val = sht15_read_byte(data); + val <<= 8; + if (sht15_ack(data)) + goto wakeup; + val |= sht15_read_byte(data); + + if (data->checksumming) { + /* + * Ask the device for a checksum and read it back. + * Note: the device sends the checksum byte reversed. + */ + if (sht15_ack(data)) + goto wakeup; + dev_checksum = bitrev8(sht15_read_byte(data)); + checksum_vals[0] = (data->state == SHT15_READING_TEMP) ? + SHT15_MEASURE_TEMP : SHT15_MEASURE_RH; + checksum_vals[1] = (u8) (val >> 8); + checksum_vals[2] = (u8) val; + data->checksum_ok + = (sht15_crc8(data, checksum_vals, 3) == dev_checksum); + } + + /* Tell the device we are done */ + if (sht15_end_transmission(data)) + goto wakeup; + + switch (data->state) { + case SHT15_READING_TEMP: + data->val_temp = val; + break; + case SHT15_READING_HUMID: + data->val_humid = val; + break; + default: + break; + } + + data->state = SHT15_READING_NOTHING; +wakeup: + wake_up(&data->wait_queue); +} + +static void sht15_update_voltage(struct work_struct *work_s) +{ + struct sht15_data *data + = container_of(work_s, struct sht15_data, + update_supply_work); + data->supply_uv = regulator_get_voltage(data->reg); +} + +/** + * sht15_invalidate_voltage() - mark supply voltage invalid when notified by reg + * @nb: associated notification structure + * @event: voltage regulator state change event code + * @ignored: function parameter - ignored here + * + * Note that as the notification code holds the regulator lock, we have + * to schedule an update of the supply voltage rather than getting it directly. + */ +static int sht15_invalidate_voltage(struct notifier_block *nb, + unsigned long event, + void *ignored) +{ + struct sht15_data *data = container_of(nb, struct sht15_data, nb); + + if (event == REGULATOR_EVENT_VOLTAGE_CHANGE) + data->supply_uv_valid = false; + schedule_work(&data->update_supply_work); + + return NOTIFY_OK; +} + +#ifdef CONFIG_OF +static const struct of_device_id sht15_dt_match[] = { + { .compatible = "sensirion,sht15" }, + { }, +}; +MODULE_DEVICE_TABLE(of, sht15_dt_match); +#endif + +static int sht15_probe(struct platform_device *pdev) +{ + int ret; + struct sht15_data *data; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + INIT_WORK(&data->read_work, sht15_bh_read_data); + INIT_WORK(&data->update_supply_work, sht15_update_voltage); + platform_set_drvdata(pdev, data); + mutex_init(&data->read_lock); + data->dev = &pdev->dev; + init_waitqueue_head(&data->wait_queue); + + /* + * If a regulator is available, + * query what the supply voltage actually is! + */ + data->reg = devm_regulator_get_optional(data->dev, "vcc"); + if (!IS_ERR(data->reg)) { + int voltage; + + voltage = regulator_get_voltage(data->reg); + if (voltage) + data->supply_uv = voltage; + + ret = regulator_enable(data->reg); + if (ret != 0) { + dev_err(&pdev->dev, + "failed to enable regulator: %d\n", ret); + return ret; + } + + /* + * Setup a notifier block to update this if another device + * causes the voltage to change + */ + data->nb.notifier_call = &sht15_invalidate_voltage; + ret = regulator_register_notifier(data->reg, &data->nb); + if (ret) { + dev_err(&pdev->dev, + "regulator notifier request failed\n"); + regulator_disable(data->reg); + return ret; + } + } + + /* Try requesting the GPIOs */ + data->sck = devm_gpiod_get(&pdev->dev, "clk", GPIOD_OUT_LOW); + if (IS_ERR(data->sck)) { + ret = PTR_ERR(data->sck); + dev_err(&pdev->dev, "clock line GPIO request failed\n"); + goto err_release_reg; + } + data->data = devm_gpiod_get(&pdev->dev, "data", GPIOD_IN); + if (IS_ERR(data->data)) { + ret = PTR_ERR(data->data); + dev_err(&pdev->dev, "data line GPIO request failed\n"); + goto err_release_reg; + } + + ret = devm_request_irq(&pdev->dev, gpiod_to_irq(data->data), + sht15_interrupt_fired, + IRQF_TRIGGER_FALLING, + "sht15 data", + data); + if (ret) { + dev_err(&pdev->dev, "failed to get irq for data line\n"); + goto err_release_reg; + } + disable_irq_nosync(gpiod_to_irq(data->data)); + ret = sht15_connection_reset(data); + if (ret) + goto err_release_reg; + ret = sht15_soft_reset(data); + if (ret) + goto err_release_reg; + + ret = sysfs_create_group(&pdev->dev.kobj, &sht15_attr_group); + if (ret) { + dev_err(&pdev->dev, "sysfs create failed\n"); + goto err_release_reg; + } + + data->hwmon_dev = hwmon_device_register(data->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto err_release_sysfs_group; + } + + return 0; + +err_release_sysfs_group: + sysfs_remove_group(&pdev->dev.kobj, &sht15_attr_group); +err_release_reg: + if (!IS_ERR(data->reg)) { + regulator_unregister_notifier(data->reg, &data->nb); + regulator_disable(data->reg); + } + return ret; +} + +static int sht15_remove(struct platform_device *pdev) +{ + struct sht15_data *data = platform_get_drvdata(pdev); + + /* + * Make sure any reads from the device are done and + * prevent new ones beginning + */ + mutex_lock(&data->read_lock); + if (sht15_soft_reset(data)) { + mutex_unlock(&data->read_lock); + return -EFAULT; + } + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &sht15_attr_group); + if (!IS_ERR(data->reg)) { + regulator_unregister_notifier(data->reg, &data->nb); + regulator_disable(data->reg); + } + + mutex_unlock(&data->read_lock); + + return 0; +} + +static const struct platform_device_id sht15_device_ids[] = { + { "sht10", sht10 }, + { "sht11", sht11 }, + { "sht15", sht15 }, + { "sht71", sht71 }, + { "sht75", sht75 }, + { } +}; +MODULE_DEVICE_TABLE(platform, sht15_device_ids); + +static struct platform_driver sht15_driver = { + .driver = { + .name = "sht15", + .of_match_table = of_match_ptr(sht15_dt_match), + }, + .probe = sht15_probe, + .remove = sht15_remove, + .id_table = sht15_device_ids, +}; +module_platform_driver(sht15_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Sensirion SHT15 temperature and humidity sensor driver"); diff --git a/drivers/hwmon/sht21.c b/drivers/hwmon/sht21.c new file mode 100644 index 000000000..2c7ba7092 --- /dev/null +++ b/drivers/hwmon/sht21.c @@ -0,0 +1,313 @@ +/* Sensirion SHT21 humidity and temperature sensor driver + * + * Copyright (C) 2010 Urs Fleisch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA + * + * Data sheet available at http://www.sensirion.com/file/datasheet_sht21 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* I2C command bytes */ +#define SHT21_TRIG_T_MEASUREMENT_HM 0xe3 +#define SHT21_TRIG_RH_MEASUREMENT_HM 0xe5 +#define SHT21_READ_SNB_CMD1 0xFA +#define SHT21_READ_SNB_CMD2 0x0F +#define SHT21_READ_SNAC_CMD1 0xFC +#define SHT21_READ_SNAC_CMD2 0xC9 + +/** + * struct sht21 - SHT21 device specific data + * @client: I2C client device + * @lock: mutex to protect measurement values + * @last_update: time of last update (jiffies) + * @temperature: cached temperature measurement value + * @humidity: cached humidity measurement value + * @valid: only 0 before first measurement is taken + * @eic: cached electronic identification code text + */ +struct sht21 { + struct i2c_client *client; + struct mutex lock; + unsigned long last_update; + int temperature; + int humidity; + char valid; + char eic[18]; +}; + +/** + * sht21_temp_ticks_to_millicelsius() - convert raw temperature ticks to + * milli celsius + * @ticks: temperature ticks value received from sensor + */ +static inline int sht21_temp_ticks_to_millicelsius(int ticks) +{ + ticks &= ~0x0003; /* clear status bits */ + /* + * Formula T = -46.85 + 175.72 * ST / 2^16 from data sheet 6.2, + * optimized for integer fixed point (3 digits) arithmetic + */ + return ((21965 * ticks) >> 13) - 46850; +} + +/** + * sht21_rh_ticks_to_per_cent_mille() - convert raw humidity ticks to + * one-thousandths of a percent relative humidity + * @ticks: humidity ticks value received from sensor + */ +static inline int sht21_rh_ticks_to_per_cent_mille(int ticks) +{ + ticks &= ~0x0003; /* clear status bits */ + /* + * Formula RH = -6 + 125 * SRH / 2^16 from data sheet 6.1, + * optimized for integer fixed point (3 digits) arithmetic + */ + return ((15625 * ticks) >> 13) - 6000; +} + +/** + * sht21_update_measurements() - get updated measurements from device + * @dev: device + * + * Returns 0 on success, else negative errno. + */ +static int sht21_update_measurements(struct device *dev) +{ + int ret = 0; + struct sht21 *sht21 = dev_get_drvdata(dev); + struct i2c_client *client = sht21->client; + + mutex_lock(&sht21->lock); + /* + * Data sheet 2.4: + * SHT2x should not be active for more than 10% of the time - e.g. + * maximum two measurements per second at 12bit accuracy shall be made. + */ + if (time_after(jiffies, sht21->last_update + HZ / 2) || !sht21->valid) { + ret = i2c_smbus_read_word_swapped(client, + SHT21_TRIG_T_MEASUREMENT_HM); + if (ret < 0) + goto out; + sht21->temperature = sht21_temp_ticks_to_millicelsius(ret); + ret = i2c_smbus_read_word_swapped(client, + SHT21_TRIG_RH_MEASUREMENT_HM); + if (ret < 0) + goto out; + sht21->humidity = sht21_rh_ticks_to_per_cent_mille(ret); + sht21->last_update = jiffies; + sht21->valid = 1; + } +out: + mutex_unlock(&sht21->lock); + + return ret >= 0 ? 0 : ret; +} + +/** + * sht21_show_temperature() - show temperature measurement value in sysfs + * @dev: device + * @attr: device attribute + * @buf: sysfs buffer (PAGE_SIZE) where measurement values are written to + * + * Will be called on read access to temp1_input sysfs attribute. + * Returns number of bytes written into buffer, negative errno on error. + */ +static ssize_t sht21_show_temperature(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sht21 *sht21 = dev_get_drvdata(dev); + int ret; + + ret = sht21_update_measurements(dev); + if (ret < 0) + return ret; + return sprintf(buf, "%d\n", sht21->temperature); +} + +/** + * sht21_show_humidity() - show humidity measurement value in sysfs + * @dev: device + * @attr: device attribute + * @buf: sysfs buffer (PAGE_SIZE) where measurement values are written to + * + * Will be called on read access to humidity1_input sysfs attribute. + * Returns number of bytes written into buffer, negative errno on error. + */ +static ssize_t sht21_show_humidity(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sht21 *sht21 = dev_get_drvdata(dev); + int ret; + + ret = sht21_update_measurements(dev); + if (ret < 0) + return ret; + return sprintf(buf, "%d\n", sht21->humidity); +} + +static ssize_t eic_read(struct sht21 *sht21) +{ + struct i2c_client *client = sht21->client; + u8 tx[2]; + u8 rx[8]; + u8 eic[8]; + struct i2c_msg msgs[2] = { + { + .addr = client->addr, + .flags = 0, + .len = 2, + .buf = tx, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = 8, + .buf = rx, + }, + }; + int ret; + + tx[0] = SHT21_READ_SNB_CMD1; + tx[1] = SHT21_READ_SNB_CMD2; + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret < 0) + goto out; + eic[2] = rx[0]; + eic[3] = rx[2]; + eic[4] = rx[4]; + eic[5] = rx[6]; + + tx[0] = SHT21_READ_SNAC_CMD1; + tx[1] = SHT21_READ_SNAC_CMD2; + msgs[1].len = 6; + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret < 0) + goto out; + eic[0] = rx[3]; + eic[1] = rx[4]; + eic[6] = rx[0]; + eic[7] = rx[1]; + + ret = snprintf(sht21->eic, sizeof(sht21->eic), + "%02x%02x%02x%02x%02x%02x%02x%02x\n", + eic[0], eic[1], eic[2], eic[3], + eic[4], eic[5], eic[6], eic[7]); +out: + if (ret < 0) + sht21->eic[0] = 0; + + return ret; +} + +/** + * eic_show() - show Electronic Identification Code in sysfs + * @dev: device + * @attr: device attribute + * @buf: sysfs buffer (PAGE_SIZE) where EIC is written + * + * Will be called on read access to eic sysfs attribute. + * Returns number of bytes written into buffer, negative errno on error. + */ +static ssize_t eic_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sht21 *sht21 = dev_get_drvdata(dev); + int ret; + + ret = sizeof(sht21->eic) - 1; + mutex_lock(&sht21->lock); + if (!sht21->eic[0]) + ret = eic_read(sht21); + if (ret > 0) + memcpy(buf, sht21->eic, ret); + mutex_unlock(&sht21->lock); + return ret; +} + +/* sysfs attributes */ +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, sht21_show_temperature, + NULL, 0); +static SENSOR_DEVICE_ATTR(humidity1_input, S_IRUGO, sht21_show_humidity, + NULL, 0); +static DEVICE_ATTR_RO(eic); + +static struct attribute *sht21_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_humidity1_input.dev_attr.attr, + &dev_attr_eic.attr, + NULL +}; + +ATTRIBUTE_GROUPS(sht21); + +static int sht21_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct sht21 *sht21; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA)) { + dev_err(&client->dev, + "adapter does not support SMBus word transactions\n"); + return -ENODEV; + } + + sht21 = devm_kzalloc(dev, sizeof(*sht21), GFP_KERNEL); + if (!sht21) + return -ENOMEM; + + sht21->client = client; + + mutex_init(&sht21->lock); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + sht21, sht21_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +/* Device ID table */ +static const struct i2c_device_id sht21_id[] = { + { "sht21", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sht21_id); + +static struct i2c_driver sht21_driver = { + .driver.name = "sht21", + .probe = sht21_probe, + .id_table = sht21_id, +}; + +module_i2c_driver(sht21_driver); + +MODULE_AUTHOR("Urs Fleisch "); +MODULE_DESCRIPTION("Sensirion SHT21 humidity and temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c new file mode 100644 index 000000000..370b57daf --- /dev/null +++ b/drivers/hwmon/sht3x.c @@ -0,0 +1,782 @@ +/* Sensirion SHT3x-DIS humidity and temperature sensor driver. + * The SHT3x comes in many different versions, this driver is for the + * I2C version only. + * + * Copyright (C) 2016 Sensirion AG, Switzerland + * Author: David Frey + * Author: Pascal Sachs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* commands (high precision mode) */ +static const unsigned char sht3x_cmd_measure_blocking_hpm[] = { 0x2c, 0x06 }; +static const unsigned char sht3x_cmd_measure_nonblocking_hpm[] = { 0x24, 0x00 }; + +/* commands (low power mode) */ +static const unsigned char sht3x_cmd_measure_blocking_lpm[] = { 0x2c, 0x10 }; +static const unsigned char sht3x_cmd_measure_nonblocking_lpm[] = { 0x24, 0x16 }; + +/* commands for periodic mode */ +static const unsigned char sht3x_cmd_measure_periodic_mode[] = { 0xe0, 0x00 }; +static const unsigned char sht3x_cmd_break[] = { 0x30, 0x93 }; + +/* commands for heater control */ +static const unsigned char sht3x_cmd_heater_on[] = { 0x30, 0x6d }; +static const unsigned char sht3x_cmd_heater_off[] = { 0x30, 0x66 }; + +/* other commands */ +static const unsigned char sht3x_cmd_read_status_reg[] = { 0xf3, 0x2d }; +static const unsigned char sht3x_cmd_clear_status_reg[] = { 0x30, 0x41 }; + +/* delays for non-blocking i2c commands, both in us */ +#define SHT3X_NONBLOCKING_WAIT_TIME_HPM 15000 +#define SHT3X_NONBLOCKING_WAIT_TIME_LPM 4000 + +#define SHT3X_WORD_LEN 2 +#define SHT3X_CMD_LENGTH 2 +#define SHT3X_CRC8_LEN 1 +#define SHT3X_RESPONSE_LENGTH 6 +#define SHT3X_CRC8_POLYNOMIAL 0x31 +#define SHT3X_CRC8_INIT 0xFF +#define SHT3X_MIN_TEMPERATURE -45000 +#define SHT3X_MAX_TEMPERATURE 130000 +#define SHT3X_MIN_HUMIDITY 0 +#define SHT3X_MAX_HUMIDITY 100000 + +enum sht3x_chips { + sht3x, + sts3x, +}; + +enum sht3x_limits { + limit_max = 0, + limit_max_hyst, + limit_min, + limit_min_hyst, +}; + +DECLARE_CRC8_TABLE(sht3x_crc8_table); + +/* periodic measure commands (high precision mode) */ +static const char periodic_measure_commands_hpm[][SHT3X_CMD_LENGTH] = { + /* 0.5 measurements per second */ + {0x20, 0x32}, + /* 1 measurements per second */ + {0x21, 0x30}, + /* 2 measurements per second */ + {0x22, 0x36}, + /* 4 measurements per second */ + {0x23, 0x34}, + /* 10 measurements per second */ + {0x27, 0x37}, +}; + +/* periodic measure commands (low power mode) */ +static const char periodic_measure_commands_lpm[][SHT3X_CMD_LENGTH] = { + /* 0.5 measurements per second */ + {0x20, 0x2f}, + /* 1 measurements per second */ + {0x21, 0x2d}, + /* 2 measurements per second */ + {0x22, 0x2b}, + /* 4 measurements per second */ + {0x23, 0x29}, + /* 10 measurements per second */ + {0x27, 0x2a}, +}; + +struct sht3x_limit_commands { + const char read_command[SHT3X_CMD_LENGTH]; + const char write_command[SHT3X_CMD_LENGTH]; +}; + +static const struct sht3x_limit_commands limit_commands[] = { + /* temp1_max, humidity1_max */ + [limit_max] = { {0xe1, 0x1f}, {0x61, 0x1d} }, + /* temp_1_max_hyst, humidity1_max_hyst */ + [limit_max_hyst] = { {0xe1, 0x14}, {0x61, 0x16} }, + /* temp1_min, humidity1_min */ + [limit_min] = { {0xe1, 0x02}, {0x61, 0x00} }, + /* temp_1_min_hyst, humidity1_min_hyst */ + [limit_min_hyst] = { {0xe1, 0x09}, {0x61, 0x0B} }, +}; + +#define SHT3X_NUM_LIMIT_CMD ARRAY_SIZE(limit_commands) + +static const u16 mode_to_update_interval[] = { + 0, + 2000, + 1000, + 500, + 250, + 100, +}; + +struct sht3x_data { + struct i2c_client *client; + struct mutex i2c_lock; /* lock for sending i2c commands */ + struct mutex data_lock; /* lock for updating driver data */ + + u8 mode; + const unsigned char *command; + u32 wait_time; /* in us*/ + unsigned long last_update; /* last update in periodic mode*/ + + struct sht3x_platform_data setup; + + /* + * cached values for temperature and humidity and limits + * the limits arrays have the following order: + * max, max_hyst, min, min_hyst + */ + int temperature; + int temperature_limits[SHT3X_NUM_LIMIT_CMD]; + u32 humidity; + u32 humidity_limits[SHT3X_NUM_LIMIT_CMD]; +}; + +static u8 get_mode_from_update_interval(u16 value) +{ + size_t index; + u8 number_of_modes = ARRAY_SIZE(mode_to_update_interval); + + if (value == 0) + return 0; + + /* find next faster update interval */ + for (index = 1; index < number_of_modes; index++) { + if (mode_to_update_interval[index] <= value) + return index; + } + + return number_of_modes - 1; +} + +static int sht3x_read_from_command(struct i2c_client *client, + struct sht3x_data *data, + const char *command, + char *buf, int length, u32 wait_time) +{ + int ret; + + mutex_lock(&data->i2c_lock); + ret = i2c_master_send(client, command, SHT3X_CMD_LENGTH); + + if (ret != SHT3X_CMD_LENGTH) { + ret = ret < 0 ? ret : -EIO; + goto out; + } + + if (wait_time) + usleep_range(wait_time, wait_time + 1000); + + ret = i2c_master_recv(client, buf, length); + if (ret != length) { + ret = ret < 0 ? ret : -EIO; + goto out; + } + + ret = 0; +out: + mutex_unlock(&data->i2c_lock); + return ret; +} + +static int sht3x_extract_temperature(u16 raw) +{ + /* + * From datasheet: + * T = -45 + 175 * ST / 2^16 + * Adapted for integer fixed point (3 digit) arithmetic. + */ + return ((21875 * (int)raw) >> 13) - 45000; +} + +static u32 sht3x_extract_humidity(u16 raw) +{ + /* + * From datasheet: + * RH = 100 * SRH / 2^16 + * Adapted for integer fixed point (3 digit) arithmetic. + */ + return (12500 * (u32)raw) >> 13; +} + +static struct sht3x_data *sht3x_update_client(struct device *dev) +{ + struct sht3x_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u16 interval_ms = mode_to_update_interval[data->mode]; + unsigned long interval_jiffies = msecs_to_jiffies(interval_ms); + unsigned char buf[SHT3X_RESPONSE_LENGTH]; + u16 val; + int ret = 0; + + mutex_lock(&data->data_lock); + /* + * Only update cached readings once per update interval in periodic + * mode. In single shot mode the sensor measures values on demand, so + * every time the sysfs interface is called, a measurement is triggered. + * In periodic mode however, the measurement process is handled + * internally by the sensor and reading out sensor values only makes + * sense if a new reading is available. + */ + if (time_after(jiffies, data->last_update + interval_jiffies)) { + ret = sht3x_read_from_command(client, data, data->command, buf, + sizeof(buf), data->wait_time); + if (ret) + goto out; + + val = be16_to_cpup((__be16 *)buf); + data->temperature = sht3x_extract_temperature(val); + val = be16_to_cpup((__be16 *)(buf + 3)); + data->humidity = sht3x_extract_humidity(val); + data->last_update = jiffies; + } + +out: + mutex_unlock(&data->data_lock); + if (ret) + return ERR_PTR(ret); + + return data; +} + +/* sysfs attributes */ +static ssize_t temp1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sht3x_data *data = sht3x_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->temperature); +} + +static ssize_t humidity1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sht3x_data *data = sht3x_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%u\n", data->humidity); +} + +/* + * limits_update must only be called from probe or with data_lock held + */ +static int limits_update(struct sht3x_data *data) +{ + int ret; + u8 index; + int temperature; + u32 humidity; + u16 raw; + char buffer[SHT3X_RESPONSE_LENGTH]; + const struct sht3x_limit_commands *commands; + struct i2c_client *client = data->client; + + for (index = 0; index < SHT3X_NUM_LIMIT_CMD; index++) { + commands = &limit_commands[index]; + ret = sht3x_read_from_command(client, data, + commands->read_command, buffer, + SHT3X_RESPONSE_LENGTH, 0); + + if (ret) + return ret; + + raw = be16_to_cpup((__be16 *)buffer); + temperature = sht3x_extract_temperature((raw & 0x01ff) << 7); + humidity = sht3x_extract_humidity(raw & 0xfe00); + data->temperature_limits[index] = temperature; + data->humidity_limits[index] = humidity; + } + + return ret; +} + +static ssize_t temp1_limit_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sht3x_data *data = dev_get_drvdata(dev); + u8 index = to_sensor_dev_attr(attr)->index; + int temperature_limit = data->temperature_limits[index]; + + return scnprintf(buf, PAGE_SIZE, "%d\n", temperature_limit); +} + +static ssize_t humidity1_limit_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sht3x_data *data = dev_get_drvdata(dev); + u8 index = to_sensor_dev_attr(attr)->index; + u32 humidity_limit = data->humidity_limits[index]; + + return scnprintf(buf, PAGE_SIZE, "%u\n", humidity_limit); +} + +/* + * limit_store must only be called with data_lock held + */ +static size_t limit_store(struct device *dev, + size_t count, + u8 index, + int temperature, + u32 humidity) +{ + char buffer[SHT3X_CMD_LENGTH + SHT3X_WORD_LEN + SHT3X_CRC8_LEN]; + char *position = buffer; + int ret; + u16 raw; + struct sht3x_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + const struct sht3x_limit_commands *commands; + + commands = &limit_commands[index]; + + memcpy(position, commands->write_command, SHT3X_CMD_LENGTH); + position += SHT3X_CMD_LENGTH; + /* + * ST = (T + 45) / 175 * 2^16 + * SRH = RH / 100 * 2^16 + * adapted for fixed point arithmetic and packed the same as + * in limit_show() + */ + raw = ((u32)(temperature + 45000) * 24543) >> (16 + 7); + raw |= ((humidity * 42950) >> 16) & 0xfe00; + + *((__be16 *)position) = cpu_to_be16(raw); + position += SHT3X_WORD_LEN; + *position = crc8(sht3x_crc8_table, + position - SHT3X_WORD_LEN, + SHT3X_WORD_LEN, + SHT3X_CRC8_INIT); + + mutex_lock(&data->i2c_lock); + ret = i2c_master_send(client, buffer, sizeof(buffer)); + mutex_unlock(&data->i2c_lock); + + if (ret != sizeof(buffer)) + return ret < 0 ? ret : -EIO; + + data->temperature_limits[index] = temperature; + data->humidity_limits[index] = humidity; + return count; +} + +static ssize_t temp1_limit_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int temperature; + int ret; + struct sht3x_data *data = dev_get_drvdata(dev); + u8 index = to_sensor_dev_attr(attr)->index; + + ret = kstrtoint(buf, 0, &temperature); + if (ret) + return ret; + + temperature = clamp_val(temperature, SHT3X_MIN_TEMPERATURE, + SHT3X_MAX_TEMPERATURE); + mutex_lock(&data->data_lock); + ret = limit_store(dev, count, index, temperature, + data->humidity_limits[index]); + mutex_unlock(&data->data_lock); + + return ret; +} + +static ssize_t humidity1_limit_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + u32 humidity; + int ret; + struct sht3x_data *data = dev_get_drvdata(dev); + u8 index = to_sensor_dev_attr(attr)->index; + + ret = kstrtou32(buf, 0, &humidity); + if (ret) + return ret; + + humidity = clamp_val(humidity, SHT3X_MIN_HUMIDITY, SHT3X_MAX_HUMIDITY); + mutex_lock(&data->data_lock); + ret = limit_store(dev, count, index, data->temperature_limits[index], + humidity); + mutex_unlock(&data->data_lock); + + return ret; +} + +static void sht3x_select_command(struct sht3x_data *data) +{ + /* + * In blocking mode (clock stretching mode) the I2C bus + * is blocked for other traffic, thus the call to i2c_master_recv() + * will wait until the data is ready. For non blocking mode, we + * have to wait ourselves. + */ + if (data->mode > 0) { + data->command = sht3x_cmd_measure_periodic_mode; + data->wait_time = 0; + } else if (data->setup.blocking_io) { + data->command = data->setup.high_precision ? + sht3x_cmd_measure_blocking_hpm : + sht3x_cmd_measure_blocking_lpm; + data->wait_time = 0; + } else { + if (data->setup.high_precision) { + data->command = sht3x_cmd_measure_nonblocking_hpm; + data->wait_time = SHT3X_NONBLOCKING_WAIT_TIME_HPM; + } else { + data->command = sht3x_cmd_measure_nonblocking_lpm; + data->wait_time = SHT3X_NONBLOCKING_WAIT_TIME_LPM; + } + } +} + +static int status_register_read(struct device *dev, + struct device_attribute *attr, + char *buffer, int length) +{ + int ret; + struct sht3x_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + ret = sht3x_read_from_command(client, data, sht3x_cmd_read_status_reg, + buffer, length, 0); + + return ret; +} + +static ssize_t temp1_alarm_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + char buffer[SHT3X_WORD_LEN + SHT3X_CRC8_LEN]; + int ret; + + ret = status_register_read(dev, attr, buffer, + SHT3X_WORD_LEN + SHT3X_CRC8_LEN); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%d\n", !!(buffer[0] & 0x04)); +} + +static ssize_t humidity1_alarm_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + char buffer[SHT3X_WORD_LEN + SHT3X_CRC8_LEN]; + int ret; + + ret = status_register_read(dev, attr, buffer, + SHT3X_WORD_LEN + SHT3X_CRC8_LEN); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%d\n", !!(buffer[0] & 0x08)); +} + +static ssize_t heater_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + char buffer[SHT3X_WORD_LEN + SHT3X_CRC8_LEN]; + int ret; + + ret = status_register_read(dev, attr, buffer, + SHT3X_WORD_LEN + SHT3X_CRC8_LEN); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%d\n", !!(buffer[0] & 0x20)); +} + +static ssize_t heater_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct sht3x_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret; + bool status; + + ret = kstrtobool(buf, &status); + if (ret) + return ret; + + mutex_lock(&data->i2c_lock); + + if (status) + ret = i2c_master_send(client, (char *)&sht3x_cmd_heater_on, + SHT3X_CMD_LENGTH); + else + ret = i2c_master_send(client, (char *)&sht3x_cmd_heater_off, + SHT3X_CMD_LENGTH); + + mutex_unlock(&data->i2c_lock); + + return ret; +} + +static ssize_t update_interval_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sht3x_data *data = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%u\n", + mode_to_update_interval[data->mode]); +} + +static ssize_t update_interval_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + u16 update_interval; + u8 mode; + int ret; + const char *command; + struct sht3x_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + ret = kstrtou16(buf, 0, &update_interval); + if (ret) + return ret; + + mode = get_mode_from_update_interval(update_interval); + + mutex_lock(&data->data_lock); + /* mode did not change */ + if (mode == data->mode) { + mutex_unlock(&data->data_lock); + return count; + } + + mutex_lock(&data->i2c_lock); + /* + * Abort periodic measure mode. + * To do any changes to the configuration while in periodic mode, we + * have to send a break command to the sensor, which then falls back + * to single shot (mode = 0). + */ + if (data->mode > 0) { + ret = i2c_master_send(client, sht3x_cmd_break, + SHT3X_CMD_LENGTH); + if (ret != SHT3X_CMD_LENGTH) + goto out; + data->mode = 0; + } + + if (mode > 0) { + if (data->setup.high_precision) + command = periodic_measure_commands_hpm[mode - 1]; + else + command = periodic_measure_commands_lpm[mode - 1]; + + /* select mode */ + ret = i2c_master_send(client, command, SHT3X_CMD_LENGTH); + if (ret != SHT3X_CMD_LENGTH) + goto out; + } + + /* select mode and command */ + data->mode = mode; + sht3x_select_command(data); + +out: + mutex_unlock(&data->i2c_lock); + mutex_unlock(&data->data_lock); + if (ret != SHT3X_CMD_LENGTH) + return ret < 0 ? ret : -EIO; + + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, temp1_input_show, NULL, 0); +static SENSOR_DEVICE_ATTR(humidity1_input, S_IRUGO, humidity1_input_show, + NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, + temp1_limit_show, temp1_limit_store, + limit_max); +static SENSOR_DEVICE_ATTR(humidity1_max, S_IRUGO | S_IWUSR, + humidity1_limit_show, humidity1_limit_store, + limit_max); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IRUGO | S_IWUSR, + temp1_limit_show, temp1_limit_store, + limit_max_hyst); +static SENSOR_DEVICE_ATTR(humidity1_max_hyst, S_IRUGO | S_IWUSR, + humidity1_limit_show, humidity1_limit_store, + limit_max_hyst); +static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, + temp1_limit_show, temp1_limit_store, + limit_min); +static SENSOR_DEVICE_ATTR(humidity1_min, S_IRUGO | S_IWUSR, + humidity1_limit_show, humidity1_limit_store, + limit_min); +static SENSOR_DEVICE_ATTR(temp1_min_hyst, S_IRUGO | S_IWUSR, + temp1_limit_show, temp1_limit_store, + limit_min_hyst); +static SENSOR_DEVICE_ATTR(humidity1_min_hyst, S_IRUGO | S_IWUSR, + humidity1_limit_show, humidity1_limit_store, + limit_min_hyst); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, temp1_alarm_show, NULL, 0); +static SENSOR_DEVICE_ATTR(humidity1_alarm, S_IRUGO, humidity1_alarm_show, + NULL, 0); +static SENSOR_DEVICE_ATTR(heater_enable, S_IRUGO | S_IWUSR, + heater_enable_show, heater_enable_store, 0); +static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR, + update_interval_show, update_interval_store, 0); + +static struct attribute *sht3x_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_humidity1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_humidity1_max.dev_attr.attr, + &sensor_dev_attr_humidity1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_min_hyst.dev_attr.attr, + &sensor_dev_attr_humidity1_min.dev_attr.attr, + &sensor_dev_attr_humidity1_min_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_humidity1_alarm.dev_attr.attr, + &sensor_dev_attr_heater_enable.dev_attr.attr, + &sensor_dev_attr_update_interval.dev_attr.attr, + NULL +}; + +static struct attribute *sts3x_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(sht3x); +ATTRIBUTE_GROUPS(sts3x); + +static int sht3x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct sht3x_data *data; + struct device *hwmon_dev; + struct i2c_adapter *adap = client->adapter; + struct device *dev = &client->dev; + const struct attribute_group **attribute_groups; + + /* + * we require full i2c support since the sht3x uses multi-byte read and + * writes as well as multi-byte commands which are not supported by + * the smbus protocol + */ + if (!i2c_check_functionality(adap, I2C_FUNC_I2C)) + return -ENODEV; + + ret = i2c_master_send(client, sht3x_cmd_clear_status_reg, + SHT3X_CMD_LENGTH); + if (ret != SHT3X_CMD_LENGTH) + return ret < 0 ? ret : -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->setup.blocking_io = false; + data->setup.high_precision = true; + data->mode = 0; + data->last_update = jiffies - msecs_to_jiffies(3000); + data->client = client; + crc8_populate_msb(sht3x_crc8_table, SHT3X_CRC8_POLYNOMIAL); + + if (client->dev.platform_data) + data->setup = *(struct sht3x_platform_data *)dev->platform_data; + + sht3x_select_command(data); + + mutex_init(&data->i2c_lock); + mutex_init(&data->data_lock); + + /* + * An attempt to read limits register too early + * causes a NACK response from the chip. + * Waiting for an empirical delay of 500 us solves the issue. + */ + usleep_range(500, 600); + + ret = limits_update(data); + if (ret) + return ret; + + if (id->driver_data == sts3x) + attribute_groups = sts3x_groups; + else + attribute_groups = sht3x_groups; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + client->name, + data, + attribute_groups); + + if (IS_ERR(hwmon_dev)) + dev_dbg(dev, "unable to register hwmon device\n"); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +/* device ID table */ +static const struct i2c_device_id sht3x_ids[] = { + {"sht3x", sht3x}, + {"sts3x", sts3x}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, sht3x_ids); + +static struct i2c_driver sht3x_i2c_driver = { + .driver.name = "sht3x", + .probe = sht3x_probe, + .id_table = sht3x_ids, +}; + +module_i2c_driver(sht3x_i2c_driver); + +MODULE_AUTHOR("David Frey "); +MODULE_AUTHOR("Pascal Sachs "); +MODULE_DESCRIPTION("Sensirion SHT3x humidity and temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/shtc1.c b/drivers/hwmon/shtc1.c new file mode 100644 index 000000000..2a1853959 --- /dev/null +++ b/drivers/hwmon/shtc1.c @@ -0,0 +1,251 @@ +/* Sensirion SHTC1 humidity and temperature sensor driver + * + * Copyright (C) 2014 Sensirion AG, Switzerland + * Author: Johannes Winkelmann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* commands (high precision mode) */ +static const unsigned char shtc1_cmd_measure_blocking_hpm[] = { 0x7C, 0xA2 }; +static const unsigned char shtc1_cmd_measure_nonblocking_hpm[] = { 0x78, 0x66 }; + +/* commands (low precision mode) */ +static const unsigned char shtc1_cmd_measure_blocking_lpm[] = { 0x64, 0x58 }; +static const unsigned char shtc1_cmd_measure_nonblocking_lpm[] = { 0x60, 0x9c }; + +/* command for reading the ID register */ +static const unsigned char shtc1_cmd_read_id_reg[] = { 0xef, 0xc8 }; + +/* constants for reading the ID register */ +#define SHTC1_ID 0x07 +#define SHTC1_ID_REG_MASK 0x3f + +/* delays for non-blocking i2c commands, both in us */ +#define SHTC1_NONBLOCKING_WAIT_TIME_HPM 14400 +#define SHTC1_NONBLOCKING_WAIT_TIME_LPM 1000 + +#define SHTC1_CMD_LENGTH 2 +#define SHTC1_RESPONSE_LENGTH 6 + +struct shtc1_data { + struct i2c_client *client; + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + + const unsigned char *command; + unsigned int nonblocking_wait_time; /* in us */ + + struct shtc1_platform_data setup; + + int temperature; /* 1000 * temperature in dgr C */ + int humidity; /* 1000 * relative humidity in %RH */ +}; + +static int shtc1_update_values(struct i2c_client *client, + struct shtc1_data *data, + char *buf, int bufsize) +{ + int ret = i2c_master_send(client, data->command, SHTC1_CMD_LENGTH); + if (ret != SHTC1_CMD_LENGTH) { + dev_err(&client->dev, "failed to send command: %d\n", ret); + return ret < 0 ? ret : -EIO; + } + + /* + * In blocking mode (clock stretching mode) the I2C bus + * is blocked for other traffic, thus the call to i2c_master_recv() + * will wait until the data is ready. For non blocking mode, we + * have to wait ourselves. + */ + if (!data->setup.blocking_io) + usleep_range(data->nonblocking_wait_time, + data->nonblocking_wait_time + 1000); + + ret = i2c_master_recv(client, buf, bufsize); + if (ret != bufsize) { + dev_err(&client->dev, "failed to read values: %d\n", ret); + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +/* sysfs attributes */ +static struct shtc1_data *shtc1_update_client(struct device *dev) +{ + struct shtc1_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned char buf[SHTC1_RESPONSE_LENGTH]; + int val; + int ret = 0; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ / 10) || !data->valid) { + ret = shtc1_update_values(client, data, buf, sizeof(buf)); + if (ret) + goto out; + + /* + * From datasheet: + * T = -45 + 175 * ST / 2^16 + * RH = 100 * SRH / 2^16 + * + * Adapted for integer fixed point (3 digit) arithmetic. + */ + val = be16_to_cpup((__be16 *)buf); + data->temperature = ((21875 * val) >> 13) - 45000; + val = be16_to_cpup((__be16 *)(buf + 3)); + data->humidity = ((12500 * val) >> 13); + + data->last_updated = jiffies; + data->valid = true; + } + +out: + mutex_unlock(&data->update_lock); + + return ret == 0 ? data : ERR_PTR(ret); +} + +static ssize_t temp1_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct shtc1_data *data = shtc1_update_client(dev); + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->temperature); +} + +static ssize_t humidity1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct shtc1_data *data = shtc1_update_client(dev); + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->humidity); +} + +static DEVICE_ATTR_RO(temp1_input); +static DEVICE_ATTR_RO(humidity1_input); + +static struct attribute *shtc1_attrs[] = { + &dev_attr_temp1_input.attr, + &dev_attr_humidity1_input.attr, + NULL +}; + +ATTRIBUTE_GROUPS(shtc1); + +static void shtc1_select_command(struct shtc1_data *data) +{ + if (data->setup.high_precision) { + data->command = data->setup.blocking_io ? + shtc1_cmd_measure_blocking_hpm : + shtc1_cmd_measure_nonblocking_hpm; + data->nonblocking_wait_time = SHTC1_NONBLOCKING_WAIT_TIME_HPM; + + } else { + data->command = data->setup.blocking_io ? + shtc1_cmd_measure_blocking_lpm : + shtc1_cmd_measure_nonblocking_lpm; + data->nonblocking_wait_time = SHTC1_NONBLOCKING_WAIT_TIME_LPM; + } +} + +static int shtc1_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + char id_reg[2]; + struct shtc1_data *data; + struct device *hwmon_dev; + struct i2c_adapter *adap = client->adapter; + struct device *dev = &client->dev; + + if (!i2c_check_functionality(adap, I2C_FUNC_I2C)) { + dev_err(dev, "plain i2c transactions not supported\n"); + return -ENODEV; + } + + ret = i2c_master_send(client, shtc1_cmd_read_id_reg, SHTC1_CMD_LENGTH); + if (ret != SHTC1_CMD_LENGTH) { + dev_err(dev, "could not send read_id_reg command: %d\n", ret); + return ret < 0 ? ret : -ENODEV; + } + ret = i2c_master_recv(client, id_reg, sizeof(id_reg)); + if (ret != sizeof(id_reg)) { + dev_err(dev, "could not read ID register: %d\n", ret); + return -ENODEV; + } + if ((id_reg[1] & SHTC1_ID_REG_MASK) != SHTC1_ID) { + dev_err(dev, "ID register doesn't match\n"); + return -ENODEV; + } + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->setup.blocking_io = false; + data->setup.high_precision = true; + data->client = client; + + if (client->dev.platform_data) + data->setup = *(struct shtc1_platform_data *)dev->platform_data; + shtc1_select_command(data); + mutex_init(&data->update_lock); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + client->name, + data, + shtc1_groups); + if (IS_ERR(hwmon_dev)) + dev_dbg(dev, "unable to register hwmon device\n"); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +/* device ID table */ +static const struct i2c_device_id shtc1_id[] = { + { "shtc1", 0 }, + { "shtw1", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, shtc1_id); + +static struct i2c_driver shtc1_i2c_driver = { + .driver.name = "shtc1", + .probe = shtc1_probe, + .id_table = shtc1_id, +}; + +module_i2c_driver(shtc1_i2c_driver); + +MODULE_AUTHOR("Johannes Winkelmann "); +MODULE_DESCRIPTION("Sensirion SHTC1 humidity and temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/sis5595.c b/drivers/hwmon/sis5595.c new file mode 100644 index 000000000..6d789aab5 --- /dev/null +++ b/drivers/hwmon/sis5595.c @@ -0,0 +1,932 @@ +/* + * sis5595.c - Part of lm_sensors, Linux kernel modules + * for hardware monitoring + * + * Copyright (C) 1998 - 2001 Frodo Looijaard , + * Kyösti Mälkki , and + * Mark D. Studebaker + * Ported to Linux 2.6 by Aurelien Jarno with + * the help of Jean Delvare + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * SiS southbridge has a LM78-like chip integrated on the same IC. + * This driver is a customized copy of lm78.c + * + * Supports following revisions: + * Version PCI ID PCI Revision + * 1 1039/0008 AF or less + * 2 1039/0008 B0 or greater + * + * Note: these chips contain a 0008 device which is incompatible with the + * 5595. We recognize these by the presence of the listed + * "blacklist" PCI ID and refuse to load. + * + * NOT SUPPORTED PCI ID BLACKLIST PCI ID + * 540 0008 0540 + * 550 0008 0550 + * 5513 0008 5511 + * 5581 0008 5597 + * 5582 0008 5597 + * 5597 0008 5597 + * 5598 0008 5597/5598 + * 630 0008 0630 + * 645 0008 0645 + * 730 0008 0730 + * 735 0008 0735 + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * If force_addr is set to anything different from 0, we forcibly enable + * the device at the given address. + */ +static u16 force_addr; +module_param(force_addr, ushort, 0); +MODULE_PARM_DESC(force_addr, + "Initialize the base address of the sensors"); + +static struct platform_device *pdev; + +/* Many SIS5595 constants specified below */ + +/* Length of ISA address segment */ +#define SIS5595_EXTENT 8 +/* PCI Config Registers */ +#define SIS5595_BASE_REG 0x68 +#define SIS5595_PIN_REG 0x7A +#define SIS5595_ENABLE_REG 0x7B + +/* Where are the ISA address/data registers relative to the base address */ +#define SIS5595_ADDR_REG_OFFSET 5 +#define SIS5595_DATA_REG_OFFSET 6 + +/* The SIS5595 registers */ +#define SIS5595_REG_IN_MAX(nr) (0x2b + (nr) * 2) +#define SIS5595_REG_IN_MIN(nr) (0x2c + (nr) * 2) +#define SIS5595_REG_IN(nr) (0x20 + (nr)) + +#define SIS5595_REG_FAN_MIN(nr) (0x3b + (nr)) +#define SIS5595_REG_FAN(nr) (0x28 + (nr)) + +/* + * On the first version of the chip, the temp registers are separate. + * On the second version, + * TEMP pin is shared with IN4, configured in PCI register 0x7A. + * The registers are the same as well. + * OVER and HYST are really MAX and MIN. + */ + +#define REV2MIN 0xb0 +#define SIS5595_REG_TEMP (((data->revision) >= REV2MIN) ? \ + SIS5595_REG_IN(4) : 0x27) +#define SIS5595_REG_TEMP_OVER (((data->revision) >= REV2MIN) ? \ + SIS5595_REG_IN_MAX(4) : 0x39) +#define SIS5595_REG_TEMP_HYST (((data->revision) >= REV2MIN) ? \ + SIS5595_REG_IN_MIN(4) : 0x3a) + +#define SIS5595_REG_CONFIG 0x40 +#define SIS5595_REG_ALARM1 0x41 +#define SIS5595_REG_ALARM2 0x42 +#define SIS5595_REG_FANDIV 0x47 + +/* + * Conversions. Limit checking is only done on the TO_REG + * variants. + */ + +/* + * IN: mV, (0V to 4.08V) + * REG: 16mV/bit + */ +static inline u8 IN_TO_REG(unsigned long val) +{ + unsigned long nval = clamp_val(val, 0, 4080); + return (nval + 8) / 16; +} +#define IN_FROM_REG(val) ((val) * 16) + +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm <= 0) + return 255; + if (rpm > 1350000) + return 1; + return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 254); +} + +static inline int FAN_FROM_REG(u8 val, int div) +{ + return val == 0 ? -1 : val == 255 ? 0 : 1350000 / (val * div); +} + +/* + * TEMP: mC (-54.12C to +157.53C) + * REG: 0.83C/bit + 52.12, two's complement + */ +static inline int TEMP_FROM_REG(s8 val) +{ + return val * 830 + 52120; +} +static inline s8 TEMP_TO_REG(long val) +{ + int nval = clamp_val(val, -54120, 157530) ; + return nval < 0 ? (nval - 5212 - 415) / 830 : (nval - 5212 + 415) / 830; +} + +/* + * FAN DIV: 1, 2, 4, or 8 (defaults to 2) + * REG: 0, 1, 2, or 3 (respectively) (defaults to 1) + */ +static inline u8 DIV_TO_REG(int val) +{ + return val == 8 ? 3 : val == 4 ? 2 : val == 1 ? 0 : 1; +} +#define DIV_FROM_REG(val) (1 << (val)) + +/* + * For each registered chip, we need to keep some data in memory. + * The structure is dynamically allocated. + */ +struct sis5595_data { + unsigned short addr; + const char *name; + struct device *hwmon_dev; + struct mutex lock; + + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + char maxins; /* == 3 if temp enabled, otherwise == 4 */ + u8 revision; /* Reg. value */ + + u8 in[5]; /* Register value */ + u8 in_max[5]; /* Register value */ + u8 in_min[5]; /* Register value */ + u8 fan[2]; /* Register value */ + u8 fan_min[2]; /* Register value */ + s8 temp; /* Register value */ + s8 temp_over; /* Register value */ + s8 temp_hyst; /* Register value */ + u8 fan_div[2]; /* Register encoding, shifted right */ + u16 alarms; /* Register encoding, combined */ +}; + +static struct pci_dev *s_bridge; /* pointer to the (only) sis5595 */ + +static int sis5595_probe(struct platform_device *pdev); +static int sis5595_remove(struct platform_device *pdev); + +static int sis5595_read_value(struct sis5595_data *data, u8 reg); +static void sis5595_write_value(struct sis5595_data *data, u8 reg, u8 value); +static struct sis5595_data *sis5595_update_device(struct device *dev); +static void sis5595_init_device(struct sis5595_data *data); + +static struct platform_driver sis5595_driver = { + .driver = { + .name = "sis5595", + }, + .probe = sis5595_probe, + .remove = sis5595_remove, +}; + +/* 4 Voltages */ +static ssize_t show_in(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sis5595_data *data = sis5595_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + return sprintf(buf, "%d\n", IN_FROM_REG(data->in[nr])); +} + +static ssize_t show_in_min(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sis5595_data *data = sis5595_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + return sprintf(buf, "%d\n", IN_FROM_REG(data->in_min[nr])); +} + +static ssize_t show_in_max(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sis5595_data *data = sis5595_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + return sprintf(buf, "%d\n", IN_FROM_REG(data->in_max[nr])); +} + +static ssize_t set_in_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sis5595_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_min[nr] = IN_TO_REG(val); + sis5595_write_value(data, SIS5595_REG_IN_MIN(nr), data->in_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_in_max(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sis5595_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_max[nr] = IN_TO_REG(val); + sis5595_write_value(data, SIS5595_REG_IN_MAX(nr), data->in_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define show_in_offset(offset) \ +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, \ + show_in, NULL, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \ + show_in_min, set_in_min, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \ + show_in_max, set_in_max, offset); + +show_in_offset(0); +show_in_offset(1); +show_in_offset(2); +show_in_offset(3); +show_in_offset(4); + +/* Temperature */ +static ssize_t temp1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sis5595_data *data = sis5595_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp)); +} + +static ssize_t temp1_max_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sis5595_data *data = sis5595_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_over)); +} + +static ssize_t temp1_max_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct sis5595_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_over = TEMP_TO_REG(val); + sis5595_write_value(data, SIS5595_REG_TEMP_OVER, data->temp_over); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t temp1_max_hyst_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sis5595_data *data = sis5595_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_hyst)); +} + +static ssize_t temp1_max_hyst_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sis5595_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_hyst = TEMP_TO_REG(val); + sis5595_write_value(data, SIS5595_REG_TEMP_HYST, data->temp_hyst); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RO(temp1_input); +static DEVICE_ATTR_RW(temp1_max); +static DEVICE_ATTR_RW(temp1_max_hyst); + +/* 2 Fans */ +static ssize_t show_fan(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sis5595_data *data = sis5595_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr], + DIV_FROM_REG(data->fan_div[nr]))); +} + +static ssize_t show_fan_min(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sis5595_data *data = sis5595_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr]))); +} + +static ssize_t set_fan_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sis5595_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_min[nr] = FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); + sis5595_write_value(data, SIS5595_REG_FAN_MIN(nr), data->fan_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_fan_div(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sis5595_data *data = sis5595_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[nr])); +} + +/* + * Note: we save and restore the fan minimum here, because its value is + * determined in part by the fan divisor. This follows the principle of + * least surprise; the user doesn't expect the fan minimum to change just + * because the divisor changed. + */ +static ssize_t set_fan_div(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sis5595_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + unsigned long min; + int reg; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + min = FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr])); + reg = sis5595_read_value(data, SIS5595_REG_FANDIV); + + switch (val) { + case 1: + data->fan_div[nr] = 0; + break; + case 2: + data->fan_div[nr] = 1; + break; + case 4: + data->fan_div[nr] = 2; + break; + case 8: + data->fan_div[nr] = 3; + break; + default: + dev_err(dev, + "fan_div value %ld not supported. Choose one of 1, 2, 4 or 8!\n", + val); + mutex_unlock(&data->update_lock); + return -EINVAL; + } + + switch (nr) { + case 0: + reg = (reg & 0xcf) | (data->fan_div[nr] << 4); + break; + case 1: + reg = (reg & 0x3f) | (data->fan_div[nr] << 6); + break; + } + sis5595_write_value(data, SIS5595_REG_FANDIV, reg); + data->fan_min[nr] = + FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr])); + sis5595_write_value(data, SIS5595_REG_FAN_MIN(nr), data->fan_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define show_fan_offset(offset) \ +static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \ + show_fan, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ + show_fan_min, set_fan_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR, \ + show_fan_div, set_fan_div, offset - 1); + +show_fan_offset(1); +show_fan_offset(2); + +/* Alarms */ +static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sis5595_data *data = sis5595_update_device(dev); + return sprintf(buf, "%d\n", data->alarms); +} +static DEVICE_ATTR_RO(alarms); + +static ssize_t show_alarm(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sis5595_data *data = sis5595_update_device(dev); + int nr = to_sensor_dev_attr(da)->index; + return sprintf(buf, "%u\n", (data->alarms >> nr) & 1); +} +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 15); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 15); + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sis5595_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", data->name); +} +static DEVICE_ATTR_RO(name); + +static struct attribute *sis5595_attributes[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + + &dev_attr_alarms.attr, + &dev_attr_name.attr, + NULL +}; + +static const struct attribute_group sis5595_group = { + .attrs = sis5595_attributes, +}; + +static struct attribute *sis5595_attributes_in4[] = { + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group sis5595_group_in4 = { + .attrs = sis5595_attributes_in4, +}; + +static struct attribute *sis5595_attributes_temp1[] = { + &dev_attr_temp1_input.attr, + &dev_attr_temp1_max.attr, + &dev_attr_temp1_max_hyst.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group sis5595_group_temp1 = { + .attrs = sis5595_attributes_temp1, +}; + +/* This is called when the module is loaded */ +static int sis5595_probe(struct platform_device *pdev) +{ + int err = 0; + int i; + struct sis5595_data *data; + struct resource *res; + char val; + + /* Reserve the ISA region */ + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(&pdev->dev, res->start, SIS5595_EXTENT, + sis5595_driver.driver.name)) + return -EBUSY; + + data = devm_kzalloc(&pdev->dev, sizeof(struct sis5595_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->lock); + mutex_init(&data->update_lock); + data->addr = res->start; + data->name = "sis5595"; + platform_set_drvdata(pdev, data); + + /* + * Check revision and pin registers to determine whether 4 or 5 voltages + */ + data->revision = s_bridge->revision; + /* 4 voltages, 1 temp */ + data->maxins = 3; + if (data->revision >= REV2MIN) { + pci_read_config_byte(s_bridge, SIS5595_PIN_REG, &val); + if (!(val & 0x80)) + /* 5 voltages, no temps */ + data->maxins = 4; + } + + /* Initialize the SIS5595 chip */ + sis5595_init_device(data); + + /* A few vars need to be filled upon startup */ + for (i = 0; i < 2; i++) { + data->fan_min[i] = sis5595_read_value(data, + SIS5595_REG_FAN_MIN(i)); + } + + /* Register sysfs hooks */ + err = sysfs_create_group(&pdev->dev.kobj, &sis5595_group); + if (err) + return err; + if (data->maxins == 4) { + err = sysfs_create_group(&pdev->dev.kobj, &sis5595_group_in4); + if (err) + goto exit_remove_files; + } else { + err = sysfs_create_group(&pdev->dev.kobj, &sis5595_group_temp1); + if (err) + goto exit_remove_files; + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_files; + } + + return 0; + +exit_remove_files: + sysfs_remove_group(&pdev->dev.kobj, &sis5595_group); + sysfs_remove_group(&pdev->dev.kobj, &sis5595_group_in4); + sysfs_remove_group(&pdev->dev.kobj, &sis5595_group_temp1); + return err; +} + +static int sis5595_remove(struct platform_device *pdev) +{ + struct sis5595_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &sis5595_group); + sysfs_remove_group(&pdev->dev.kobj, &sis5595_group_in4); + sysfs_remove_group(&pdev->dev.kobj, &sis5595_group_temp1); + + return 0; +} + + +/* ISA access must be locked explicitly. */ +static int sis5595_read_value(struct sis5595_data *data, u8 reg) +{ + int res; + + mutex_lock(&data->lock); + outb_p(reg, data->addr + SIS5595_ADDR_REG_OFFSET); + res = inb_p(data->addr + SIS5595_DATA_REG_OFFSET); + mutex_unlock(&data->lock); + return res; +} + +static void sis5595_write_value(struct sis5595_data *data, u8 reg, u8 value) +{ + mutex_lock(&data->lock); + outb_p(reg, data->addr + SIS5595_ADDR_REG_OFFSET); + outb_p(value, data->addr + SIS5595_DATA_REG_OFFSET); + mutex_unlock(&data->lock); +} + +/* Called when we have found a new SIS5595. */ +static void sis5595_init_device(struct sis5595_data *data) +{ + u8 config = sis5595_read_value(data, SIS5595_REG_CONFIG); + if (!(config & 0x01)) + sis5595_write_value(data, SIS5595_REG_CONFIG, + (config & 0xf7) | 0x01); +} + +static struct sis5595_data *sis5595_update_device(struct device *dev) +{ + struct sis5595_data *data = dev_get_drvdata(dev); + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + + for (i = 0; i <= data->maxins; i++) { + data->in[i] = + sis5595_read_value(data, SIS5595_REG_IN(i)); + data->in_min[i] = + sis5595_read_value(data, + SIS5595_REG_IN_MIN(i)); + data->in_max[i] = + sis5595_read_value(data, + SIS5595_REG_IN_MAX(i)); + } + for (i = 0; i < 2; i++) { + data->fan[i] = + sis5595_read_value(data, SIS5595_REG_FAN(i)); + data->fan_min[i] = + sis5595_read_value(data, + SIS5595_REG_FAN_MIN(i)); + } + if (data->maxins == 3) { + data->temp = + sis5595_read_value(data, SIS5595_REG_TEMP); + data->temp_over = + sis5595_read_value(data, SIS5595_REG_TEMP_OVER); + data->temp_hyst = + sis5595_read_value(data, SIS5595_REG_TEMP_HYST); + } + i = sis5595_read_value(data, SIS5595_REG_FANDIV); + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = i >> 6; + data->alarms = + sis5595_read_value(data, SIS5595_REG_ALARM1) | + (sis5595_read_value(data, SIS5595_REG_ALARM2) << 8); + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static const struct pci_device_id sis5595_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503) }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, sis5595_pci_ids); + +static int blacklist[] = { + PCI_DEVICE_ID_SI_540, + PCI_DEVICE_ID_SI_550, + PCI_DEVICE_ID_SI_630, + PCI_DEVICE_ID_SI_645, + PCI_DEVICE_ID_SI_730, + PCI_DEVICE_ID_SI_735, + PCI_DEVICE_ID_SI_5511, /* + * 5513 chip has the 0008 device but + * that ID shows up in other chips so we + * use the 5511 ID for recognition + */ + PCI_DEVICE_ID_SI_5597, + PCI_DEVICE_ID_SI_5598, + 0 }; + +static int sis5595_device_add(unsigned short address) +{ + struct resource res = { + .start = address, + .end = address + SIS5595_EXTENT - 1, + .name = "sis5595", + .flags = IORESOURCE_IO, + }; + int err; + + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + + pdev = platform_device_alloc("sis5595", address); + if (!pdev) { + err = -ENOMEM; + pr_err("Device allocation failed\n"); + goto exit; + } + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + +static int sis5595_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + u16 address; + u8 enable; + int *i; + + for (i = blacklist; *i != 0; i++) { + struct pci_dev *d; + d = pci_get_device(PCI_VENDOR_ID_SI, *i, NULL); + if (d) { + dev_err(&d->dev, + "Looked for SIS5595 but found unsupported device %.4x\n", + *i); + pci_dev_put(d); + return -ENODEV; + } + } + + force_addr &= ~(SIS5595_EXTENT - 1); + if (force_addr) { + dev_warn(&dev->dev, "Forcing ISA address 0x%x\n", force_addr); + pci_write_config_word(dev, SIS5595_BASE_REG, force_addr); + } + + if (PCIBIOS_SUCCESSFUL != + pci_read_config_word(dev, SIS5595_BASE_REG, &address)) { + dev_err(&dev->dev, "Failed to read ISA address\n"); + return -ENODEV; + } + + address &= ~(SIS5595_EXTENT - 1); + if (!address) { + dev_err(&dev->dev, + "Base address not set - upgrade BIOS or use force_addr=0xaddr\n"); + return -ENODEV; + } + if (force_addr && address != force_addr) { + /* doesn't work for some chips? */ + dev_err(&dev->dev, "Failed to force ISA address\n"); + return -ENODEV; + } + + if (PCIBIOS_SUCCESSFUL != + pci_read_config_byte(dev, SIS5595_ENABLE_REG, &enable)) { + dev_err(&dev->dev, "Failed to read enable register\n"); + return -ENODEV; + } + if (!(enable & 0x80)) { + if ((PCIBIOS_SUCCESSFUL != + pci_write_config_byte(dev, SIS5595_ENABLE_REG, + enable | 0x80)) + || (PCIBIOS_SUCCESSFUL != + pci_read_config_byte(dev, SIS5595_ENABLE_REG, &enable)) + || (!(enable & 0x80))) { + /* doesn't work for some chips! */ + dev_err(&dev->dev, "Failed to enable HWM device\n"); + return -ENODEV; + } + } + + if (platform_driver_register(&sis5595_driver)) { + dev_dbg(&dev->dev, "Failed to register sis5595 driver\n"); + goto exit; + } + + s_bridge = pci_dev_get(dev); + /* Sets global pdev as a side effect */ + if (sis5595_device_add(address)) + goto exit_unregister; + + /* + * Always return failure here. This is to allow other drivers to bind + * to this pci device. We don't really want to have control over the + * pci device, we only wanted to read as few register values from it. + */ + return -ENODEV; + +exit_unregister: + pci_dev_put(dev); + platform_driver_unregister(&sis5595_driver); +exit: + return -ENODEV; +} + +static struct pci_driver sis5595_pci_driver = { + .name = "sis5595", + .id_table = sis5595_pci_ids, + .probe = sis5595_pci_probe, +}; + +static int __init sm_sis5595_init(void) +{ + return pci_register_driver(&sis5595_pci_driver); +} + +static void __exit sm_sis5595_exit(void) +{ + pci_unregister_driver(&sis5595_pci_driver); + if (s_bridge != NULL) { + platform_device_unregister(pdev); + platform_driver_unregister(&sis5595_driver); + pci_dev_put(s_bridge); + s_bridge = NULL; + } +} + +MODULE_AUTHOR("Aurelien Jarno "); +MODULE_DESCRIPTION("SiS 5595 Sensor device"); +MODULE_LICENSE("GPL"); + +module_init(sm_sis5595_init); +module_exit(sm_sis5595_exit); diff --git a/drivers/hwmon/smm665.c b/drivers/hwmon/smm665.c new file mode 100644 index 000000000..627c9c3a8 --- /dev/null +++ b/drivers/hwmon/smm665.c @@ -0,0 +1,709 @@ +/* + * Driver for SMM665 Power Controller / Monitor + * + * Copyright (C) 2010 Ericsson AB. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This driver should also work for SMM465, SMM764, and SMM766, but is untested + * for those chips. Only monitoring functionality is implemented. + * + * Datasheets: + * http://www.summitmicro.com/prod_select/summary/SMM665/SMM665B_2089_20.pdf + * http://www.summitmicro.com/prod_select/summary/SMM766B/SMM766B_2122.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Internal reference voltage (VREF, x 1000 */ +#define SMM665_VREF_ADC_X1000 1250 + +/* module parameters */ +static int vref = SMM665_VREF_ADC_X1000; +module_param(vref, int, 0); +MODULE_PARM_DESC(vref, "Reference voltage in mV"); + +enum chips { smm465, smm665, smm665c, smm764, smm766 }; + +/* + * ADC channel addresses + */ +#define SMM665_MISC16_ADC_DATA_A 0x00 +#define SMM665_MISC16_ADC_DATA_B 0x01 +#define SMM665_MISC16_ADC_DATA_C 0x02 +#define SMM665_MISC16_ADC_DATA_D 0x03 +#define SMM665_MISC16_ADC_DATA_E 0x04 +#define SMM665_MISC16_ADC_DATA_F 0x05 +#define SMM665_MISC16_ADC_DATA_VDD 0x06 +#define SMM665_MISC16_ADC_DATA_12V 0x07 +#define SMM665_MISC16_ADC_DATA_INT_TEMP 0x08 +#define SMM665_MISC16_ADC_DATA_AIN1 0x09 +#define SMM665_MISC16_ADC_DATA_AIN2 0x0a + +/* + * Command registers + */ +#define SMM665_MISC8_CMD_STS 0x80 +#define SMM665_MISC8_STATUS1 0x81 +#define SMM665_MISC8_STATUSS2 0x82 +#define SMM665_MISC8_IO_POLARITY 0x83 +#define SMM665_MISC8_PUP_POLARITY 0x84 +#define SMM665_MISC8_ADOC_STATUS1 0x85 +#define SMM665_MISC8_ADOC_STATUS2 0x86 +#define SMM665_MISC8_WRITE_PROT 0x87 +#define SMM665_MISC8_STS_TRACK 0x88 + +/* + * Configuration registers and register groups + */ +#define SMM665_ADOC_ENABLE 0x0d +#define SMM665_LIMIT_BASE 0x80 /* First limit register */ + +/* + * Limit register bit masks + */ +#define SMM665_TRIGGER_RST 0x8000 +#define SMM665_TRIGGER_HEALTHY 0x4000 +#define SMM665_TRIGGER_POWEROFF 0x2000 +#define SMM665_TRIGGER_SHUTDOWN 0x1000 +#define SMM665_ADC_MASK 0x03ff + +#define smm665_is_critical(lim) ((lim) & (SMM665_TRIGGER_RST \ + | SMM665_TRIGGER_POWEROFF \ + | SMM665_TRIGGER_SHUTDOWN)) +/* + * Fault register bit definitions + * Values are merged from status registers 1/2, + * with status register 1 providing the upper 8 bits. + */ +#define SMM665_FAULT_A 0x0001 +#define SMM665_FAULT_B 0x0002 +#define SMM665_FAULT_C 0x0004 +#define SMM665_FAULT_D 0x0008 +#define SMM665_FAULT_E 0x0010 +#define SMM665_FAULT_F 0x0020 +#define SMM665_FAULT_VDD 0x0040 +#define SMM665_FAULT_12V 0x0080 +#define SMM665_FAULT_TEMP 0x0100 +#define SMM665_FAULT_AIN1 0x0200 +#define SMM665_FAULT_AIN2 0x0400 + +/* + * I2C Register addresses + * + * The configuration register needs to be the configured base register. + * The command/status register address is derived from it. + */ +#define SMM665_REGMASK 0x78 +#define SMM665_CMDREG_BASE 0x48 +#define SMM665_CONFREG_BASE 0x50 + +/* + * Equations given by chip manufacturer to calculate voltage/temperature values + * vref = Reference voltage on VREF_ADC pin (module parameter) + * adc = 10bit ADC value read back from registers + */ + +/* Voltage A-F and VDD */ +#define SMM665_VMON_ADC_TO_VOLTS(adc) ((adc) * vref / 256) + +/* Voltage 12VIN */ +#define SMM665_12VIN_ADC_TO_VOLTS(adc) ((adc) * vref * 3 / 256) + +/* Voltage AIN1, AIN2 */ +#define SMM665_AIN_ADC_TO_VOLTS(adc) ((adc) * vref / 512) + +/* Temp Sensor */ +#define SMM665_TEMP_ADC_TO_CELSIUS(adc) (((adc) <= 511) ? \ + ((int)(adc) * 1000 / 4) : \ + (((int)(adc) - 0x400) * 1000 / 4)) + +#define SMM665_NUM_ADC 11 + +/* + * Chip dependent ADC conversion time, in uS + */ +#define SMM665_ADC_WAIT_SMM665 70 +#define SMM665_ADC_WAIT_SMM766 185 + +struct smm665_data { + enum chips type; + int conversion_time; /* ADC conversion time */ + struct i2c_client *client; + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + u16 adc[SMM665_NUM_ADC]; /* adc values (raw) */ + u16 faults; /* fault status */ + /* The following values are in mV */ + int critical_min_limit[SMM665_NUM_ADC]; + int alarm_min_limit[SMM665_NUM_ADC]; + int critical_max_limit[SMM665_NUM_ADC]; + int alarm_max_limit[SMM665_NUM_ADC]; + struct i2c_client *cmdreg; +}; + +/* + * smm665_read16() + * + * Read 16 bit value from , . Upper 8 bits are in . + */ +static int smm665_read16(struct i2c_client *client, int reg) +{ + int rv, val; + + rv = i2c_smbus_read_byte_data(client, reg); + if (rv < 0) + return rv; + val = rv << 8; + rv = i2c_smbus_read_byte_data(client, reg + 1); + if (rv < 0) + return rv; + val |= rv; + return val; +} + +/* + * Read adc value. + */ +static int smm665_read_adc(struct smm665_data *data, int adc) +{ + struct i2c_client *client = data->cmdreg; + int rv; + int radc; + + /* + * Algorithm for reading ADC, per SMM665 datasheet + * + * {[S][addr][W][Ack]} {[offset][Ack]} {[S][addr][R][Nack]} + * [wait conversion time] + * {[S][addr][R][Ack]} {[datahi][Ack]} {[datalo][Ack][P]} + * + * To implement the first part of this exchange, + * do a full read transaction and expect a failure/Nack. + * This sets up the address pointer on the SMM665 + * and starts the ADC conversion. + * Then do a two-byte read transaction. + */ + rv = i2c_smbus_read_byte_data(client, adc << 3); + if (rv != -ENXIO) { + /* + * We expect ENXIO to reflect NACK + * (per Documentation/i2c/fault-codes). + * Everything else is an error. + */ + dev_dbg(&client->dev, + "Unexpected return code %d when setting ADC index", rv); + return (rv < 0) ? rv : -EIO; + } + + udelay(data->conversion_time); + + /* + * Now read two bytes. + * + * Neither i2c_smbus_read_byte() nor + * i2c_smbus_read_block_data() worked here, + * so use i2c_smbus_read_word_swapped() instead. + * We could also try to use i2c_master_recv(), + * but that is not always supported. + */ + rv = i2c_smbus_read_word_swapped(client, 0); + if (rv < 0) { + dev_dbg(&client->dev, "Failed to read ADC value: error %d", rv); + return rv; + } + /* + * Validate/verify readback adc channel (in bit 11..14). + */ + radc = (rv >> 11) & 0x0f; + if (radc != adc) { + dev_dbg(&client->dev, "Unexpected RADC: Expected %d got %d", + adc, radc); + return -EIO; + } + + return rv & SMM665_ADC_MASK; +} + +static struct smm665_data *smm665_update_device(struct device *dev) +{ + struct smm665_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + struct smm665_data *ret = data; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + int i, val; + + /* + * read status registers + */ + val = smm665_read16(client, SMM665_MISC8_STATUS1); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->faults = val; + + /* Read adc registers */ + for (i = 0; i < SMM665_NUM_ADC; i++) { + val = smm665_read_adc(data, i); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->adc[i] = val; + } + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +/* Return converted value from given adc */ +static int smm665_convert(u16 adcval, int index) +{ + int val = 0; + + switch (index) { + case SMM665_MISC16_ADC_DATA_12V: + val = SMM665_12VIN_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK); + break; + + case SMM665_MISC16_ADC_DATA_VDD: + case SMM665_MISC16_ADC_DATA_A: + case SMM665_MISC16_ADC_DATA_B: + case SMM665_MISC16_ADC_DATA_C: + case SMM665_MISC16_ADC_DATA_D: + case SMM665_MISC16_ADC_DATA_E: + case SMM665_MISC16_ADC_DATA_F: + val = SMM665_VMON_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK); + break; + + case SMM665_MISC16_ADC_DATA_AIN1: + case SMM665_MISC16_ADC_DATA_AIN2: + val = SMM665_AIN_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK); + break; + + case SMM665_MISC16_ADC_DATA_INT_TEMP: + val = SMM665_TEMP_ADC_TO_CELSIUS(adcval & SMM665_ADC_MASK); + break; + + default: + /* If we get here, the developer messed up */ + WARN_ON_ONCE(1); + break; + } + + return val; +} + +static int smm665_get_min(struct device *dev, int index) +{ + struct smm665_data *data = dev_get_drvdata(dev); + + return data->alarm_min_limit[index]; +} + +static int smm665_get_max(struct device *dev, int index) +{ + struct smm665_data *data = dev_get_drvdata(dev); + + return data->alarm_max_limit[index]; +} + +static int smm665_get_lcrit(struct device *dev, int index) +{ + struct smm665_data *data = dev_get_drvdata(dev); + + return data->critical_min_limit[index]; +} + +static int smm665_get_crit(struct device *dev, int index) +{ + struct smm665_data *data = dev_get_drvdata(dev); + + return data->critical_max_limit[index]; +} + +static ssize_t smm665_show_crit_alarm(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct smm665_data *data = smm665_update_device(dev); + int val = 0; + + if (IS_ERR(data)) + return PTR_ERR(data); + + if (data->faults & (1 << attr->index)) + val = 1; + + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t smm665_show_input(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct smm665_data *data = smm665_update_device(dev); + int adc = attr->index; + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = smm665_convert(data->adc[adc], adc); + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +#define SMM665_SHOW(what) \ +static ssize_t smm665_show_##what(struct device *dev, \ + struct device_attribute *da, char *buf) \ +{ \ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); \ + const int val = smm665_get_##what(dev, attr->index); \ + return snprintf(buf, PAGE_SIZE, "%d\n", val); \ +} + +SMM665_SHOW(min); +SMM665_SHOW(max); +SMM665_SHOW(lcrit); +SMM665_SHOW(crit); + +/* + * These macros are used below in constructing device attribute objects + * for use with sysfs_create_group() to make a sysfs device file + * for each register. + */ + +#define SMM665_ATTR(name, type, cmd_idx) \ + static SENSOR_DEVICE_ATTR(name##_##type, S_IRUGO, \ + smm665_show_##type, NULL, cmd_idx) + +/* Construct a sensor_device_attribute structure for each register */ + +/* Input voltages */ +SMM665_ATTR(in1, input, SMM665_MISC16_ADC_DATA_12V); +SMM665_ATTR(in2, input, SMM665_MISC16_ADC_DATA_VDD); +SMM665_ATTR(in3, input, SMM665_MISC16_ADC_DATA_A); +SMM665_ATTR(in4, input, SMM665_MISC16_ADC_DATA_B); +SMM665_ATTR(in5, input, SMM665_MISC16_ADC_DATA_C); +SMM665_ATTR(in6, input, SMM665_MISC16_ADC_DATA_D); +SMM665_ATTR(in7, input, SMM665_MISC16_ADC_DATA_E); +SMM665_ATTR(in8, input, SMM665_MISC16_ADC_DATA_F); +SMM665_ATTR(in9, input, SMM665_MISC16_ADC_DATA_AIN1); +SMM665_ATTR(in10, input, SMM665_MISC16_ADC_DATA_AIN2); + +/* Input voltages min */ +SMM665_ATTR(in1, min, SMM665_MISC16_ADC_DATA_12V); +SMM665_ATTR(in2, min, SMM665_MISC16_ADC_DATA_VDD); +SMM665_ATTR(in3, min, SMM665_MISC16_ADC_DATA_A); +SMM665_ATTR(in4, min, SMM665_MISC16_ADC_DATA_B); +SMM665_ATTR(in5, min, SMM665_MISC16_ADC_DATA_C); +SMM665_ATTR(in6, min, SMM665_MISC16_ADC_DATA_D); +SMM665_ATTR(in7, min, SMM665_MISC16_ADC_DATA_E); +SMM665_ATTR(in8, min, SMM665_MISC16_ADC_DATA_F); +SMM665_ATTR(in9, min, SMM665_MISC16_ADC_DATA_AIN1); +SMM665_ATTR(in10, min, SMM665_MISC16_ADC_DATA_AIN2); + +/* Input voltages max */ +SMM665_ATTR(in1, max, SMM665_MISC16_ADC_DATA_12V); +SMM665_ATTR(in2, max, SMM665_MISC16_ADC_DATA_VDD); +SMM665_ATTR(in3, max, SMM665_MISC16_ADC_DATA_A); +SMM665_ATTR(in4, max, SMM665_MISC16_ADC_DATA_B); +SMM665_ATTR(in5, max, SMM665_MISC16_ADC_DATA_C); +SMM665_ATTR(in6, max, SMM665_MISC16_ADC_DATA_D); +SMM665_ATTR(in7, max, SMM665_MISC16_ADC_DATA_E); +SMM665_ATTR(in8, max, SMM665_MISC16_ADC_DATA_F); +SMM665_ATTR(in9, max, SMM665_MISC16_ADC_DATA_AIN1); +SMM665_ATTR(in10, max, SMM665_MISC16_ADC_DATA_AIN2); + +/* Input voltages lcrit */ +SMM665_ATTR(in1, lcrit, SMM665_MISC16_ADC_DATA_12V); +SMM665_ATTR(in2, lcrit, SMM665_MISC16_ADC_DATA_VDD); +SMM665_ATTR(in3, lcrit, SMM665_MISC16_ADC_DATA_A); +SMM665_ATTR(in4, lcrit, SMM665_MISC16_ADC_DATA_B); +SMM665_ATTR(in5, lcrit, SMM665_MISC16_ADC_DATA_C); +SMM665_ATTR(in6, lcrit, SMM665_MISC16_ADC_DATA_D); +SMM665_ATTR(in7, lcrit, SMM665_MISC16_ADC_DATA_E); +SMM665_ATTR(in8, lcrit, SMM665_MISC16_ADC_DATA_F); +SMM665_ATTR(in9, lcrit, SMM665_MISC16_ADC_DATA_AIN1); +SMM665_ATTR(in10, lcrit, SMM665_MISC16_ADC_DATA_AIN2); + +/* Input voltages crit */ +SMM665_ATTR(in1, crit, SMM665_MISC16_ADC_DATA_12V); +SMM665_ATTR(in2, crit, SMM665_MISC16_ADC_DATA_VDD); +SMM665_ATTR(in3, crit, SMM665_MISC16_ADC_DATA_A); +SMM665_ATTR(in4, crit, SMM665_MISC16_ADC_DATA_B); +SMM665_ATTR(in5, crit, SMM665_MISC16_ADC_DATA_C); +SMM665_ATTR(in6, crit, SMM665_MISC16_ADC_DATA_D); +SMM665_ATTR(in7, crit, SMM665_MISC16_ADC_DATA_E); +SMM665_ATTR(in8, crit, SMM665_MISC16_ADC_DATA_F); +SMM665_ATTR(in9, crit, SMM665_MISC16_ADC_DATA_AIN1); +SMM665_ATTR(in10, crit, SMM665_MISC16_ADC_DATA_AIN2); + +/* critical alarms */ +SMM665_ATTR(in1, crit_alarm, SMM665_FAULT_12V); +SMM665_ATTR(in2, crit_alarm, SMM665_FAULT_VDD); +SMM665_ATTR(in3, crit_alarm, SMM665_FAULT_A); +SMM665_ATTR(in4, crit_alarm, SMM665_FAULT_B); +SMM665_ATTR(in5, crit_alarm, SMM665_FAULT_C); +SMM665_ATTR(in6, crit_alarm, SMM665_FAULT_D); +SMM665_ATTR(in7, crit_alarm, SMM665_FAULT_E); +SMM665_ATTR(in8, crit_alarm, SMM665_FAULT_F); +SMM665_ATTR(in9, crit_alarm, SMM665_FAULT_AIN1); +SMM665_ATTR(in10, crit_alarm, SMM665_FAULT_AIN2); + +/* Temperature */ +SMM665_ATTR(temp1, input, SMM665_MISC16_ADC_DATA_INT_TEMP); +SMM665_ATTR(temp1, min, SMM665_MISC16_ADC_DATA_INT_TEMP); +SMM665_ATTR(temp1, max, SMM665_MISC16_ADC_DATA_INT_TEMP); +SMM665_ATTR(temp1, lcrit, SMM665_MISC16_ADC_DATA_INT_TEMP); +SMM665_ATTR(temp1, crit, SMM665_MISC16_ADC_DATA_INT_TEMP); +SMM665_ATTR(temp1, crit_alarm, SMM665_FAULT_TEMP); + +/* + * Finally, construct an array of pointers to members of the above objects, + * as required for sysfs_create_group() + */ +static struct attribute *smm665_attrs[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_lcrit.dev_attr.attr, + &sensor_dev_attr_in1_crit.dev_attr.attr, + &sensor_dev_attr_in1_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_lcrit.dev_attr.attr, + &sensor_dev_attr_in2_crit.dev_attr.attr, + &sensor_dev_attr_in2_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_lcrit.dev_attr.attr, + &sensor_dev_attr_in3_crit.dev_attr.attr, + &sensor_dev_attr_in3_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_lcrit.dev_attr.attr, + &sensor_dev_attr_in4_crit.dev_attr.attr, + &sensor_dev_attr_in4_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in5_lcrit.dev_attr.attr, + &sensor_dev_attr_in5_crit.dev_attr.attr, + &sensor_dev_attr_in5_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in6_lcrit.dev_attr.attr, + &sensor_dev_attr_in6_crit.dev_attr.attr, + &sensor_dev_attr_in6_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in7_lcrit.dev_attr.attr, + &sensor_dev_attr_in7_crit.dev_attr.attr, + &sensor_dev_attr_in7_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in8_min.dev_attr.attr, + &sensor_dev_attr_in8_max.dev_attr.attr, + &sensor_dev_attr_in8_lcrit.dev_attr.attr, + &sensor_dev_attr_in8_crit.dev_attr.attr, + &sensor_dev_attr_in8_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in9_min.dev_attr.attr, + &sensor_dev_attr_in9_max.dev_attr.attr, + &sensor_dev_attr_in9_lcrit.dev_attr.attr, + &sensor_dev_attr_in9_crit.dev_attr.attr, + &sensor_dev_attr_in9_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in10_min.dev_attr.attr, + &sensor_dev_attr_in10_max.dev_attr.attr, + &sensor_dev_attr_in10_lcrit.dev_attr.attr, + &sensor_dev_attr_in10_crit.dev_attr.attr, + &sensor_dev_attr_in10_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_lcrit.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + + NULL, +}; + +ATTRIBUTE_GROUPS(smm665); + +static int smm665_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct smm665_data *data; + struct device *hwmon_dev; + int i, ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + if (i2c_smbus_read_byte_data(client, SMM665_ADOC_ENABLE) < 0) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + data->client = client; + data->type = id->driver_data; + data->cmdreg = i2c_new_dummy(adapter, (client->addr & ~SMM665_REGMASK) + | SMM665_CMDREG_BASE); + if (!data->cmdreg) + return -ENOMEM; + + switch (data->type) { + case smm465: + case smm665: + data->conversion_time = SMM665_ADC_WAIT_SMM665; + break; + case smm665c: + case smm764: + case smm766: + data->conversion_time = SMM665_ADC_WAIT_SMM766; + break; + } + + ret = -ENODEV; + if (i2c_smbus_read_byte_data(data->cmdreg, SMM665_MISC8_CMD_STS) < 0) + goto out_unregister; + + /* + * Read limits. + * + * Limit registers start with register SMM665_LIMIT_BASE. + * Each channel uses 8 registers, providing four limit values + * per channel. Each limit value requires two registers, with the + * high byte in the first register and the low byte in the second + * register. The first two limits are under limit values, followed + * by two over limit values. + * + * Limit register order matches the ADC register order, so we use + * ADC register defines throughout the code to index limit registers. + * + * We save the first retrieved value both as "critical" and "alarm" + * value. The second value overwrites either the critical or the + * alarm value, depending on its configuration. This ensures that both + * critical and alarm values are initialized, even if both registers are + * configured as critical or non-critical. + */ + for (i = 0; i < SMM665_NUM_ADC; i++) { + int val; + + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8); + if (unlikely(val < 0)) + goto out_unregister; + data->critical_min_limit[i] = data->alarm_min_limit[i] + = smm665_convert(val, i); + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 2); + if (unlikely(val < 0)) + goto out_unregister; + if (smm665_is_critical(val)) + data->critical_min_limit[i] = smm665_convert(val, i); + else + data->alarm_min_limit[i] = smm665_convert(val, i); + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 4); + if (unlikely(val < 0)) + goto out_unregister; + data->critical_max_limit[i] = data->alarm_max_limit[i] + = smm665_convert(val, i); + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 6); + if (unlikely(val < 0)) + goto out_unregister; + if (smm665_is_critical(val)) + data->critical_max_limit[i] = smm665_convert(val, i); + else + data->alarm_max_limit[i] = smm665_convert(val, i); + } + + hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, + client->name, data, + smm665_groups); + if (IS_ERR(hwmon_dev)) { + ret = PTR_ERR(hwmon_dev); + goto out_unregister; + } + + return 0; + +out_unregister: + i2c_unregister_device(data->cmdreg); + return ret; +} + +static int smm665_remove(struct i2c_client *client) +{ + struct smm665_data *data = i2c_get_clientdata(client); + + i2c_unregister_device(data->cmdreg); + return 0; +} + +static const struct i2c_device_id smm665_id[] = { + {"smm465", smm465}, + {"smm665", smm665}, + {"smm665c", smm665c}, + {"smm764", smm764}, + {"smm766", smm766}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, smm665_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver smm665_driver = { + .driver = { + .name = "smm665", + }, + .probe = smm665_probe, + .remove = smm665_remove, + .id_table = smm665_id, +}; + +module_i2c_driver(smm665_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("SMM665 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/smsc47b397.c b/drivers/hwmon/smsc47b397.c new file mode 100644 index 000000000..cbdb5c499 --- /dev/null +++ b/drivers/hwmon/smsc47b397.c @@ -0,0 +1,383 @@ +/* + * smsc47b397.c - Part of lm_sensors, Linux kernel modules + * for hardware monitoring + * + * Supports the SMSC LPC47B397-NC Super-I/O chip. + * + * Author/Maintainer: Mark M. Hoffman + * Copyright (C) 2004 Utilitek Systems, Inc. + * + * derived in part from smsc47m1.c: + * Copyright (C) 2002 Mark D. Studebaker + * Copyright (C) 2004 Jean Delvare + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +static struct platform_device *pdev; + +#define DRVNAME "smsc47b397" + +/* Super-I/0 registers and commands */ + +#define REG 0x2e /* The register to read/write */ +#define VAL 0x2f /* The value to read/write */ + +static inline void superio_outb(int reg, int val) +{ + outb(reg, REG); + outb(val, VAL); +} + +static inline int superio_inb(int reg) +{ + outb(reg, REG); + return inb(VAL); +} + +/* select superio logical device */ +static inline void superio_select(int ld) +{ + superio_outb(0x07, ld); +} + +static inline int superio_enter(void) +{ + if (!request_muxed_region(REG, 2, DRVNAME)) + return -EBUSY; + + outb(0x55, REG); + return 0; +} + +static inline void superio_exit(void) +{ + outb(0xAA, REG); + release_region(REG, 2); +} + +#define SUPERIO_REG_DEVID 0x20 +#define SUPERIO_REG_DEVREV 0x21 +#define SUPERIO_REG_BASE_MSB 0x60 +#define SUPERIO_REG_BASE_LSB 0x61 +#define SUPERIO_REG_LD8 0x08 + +#define SMSC_EXTENT 0x02 + +/* 0 <= nr <= 3 */ +static u8 smsc47b397_reg_temp[] = {0x25, 0x26, 0x27, 0x80}; +#define SMSC47B397_REG_TEMP(nr) (smsc47b397_reg_temp[(nr)]) + +/* 0 <= nr <= 3 */ +#define SMSC47B397_REG_FAN_LSB(nr) (0x28 + 2 * (nr)) +#define SMSC47B397_REG_FAN_MSB(nr) (0x29 + 2 * (nr)) + +struct smsc47b397_data { + unsigned short addr; + struct mutex lock; + + struct mutex update_lock; + unsigned long last_updated; /* in jiffies */ + int valid; + + /* register values */ + u16 fan[4]; + u8 temp[4]; +}; + +static int smsc47b397_read_value(struct smsc47b397_data *data, u8 reg) +{ + int res; + + mutex_lock(&data->lock); + outb(reg, data->addr); + res = inb_p(data->addr + 1); + mutex_unlock(&data->lock); + return res; +} + +static struct smsc47b397_data *smsc47b397_update_device(struct device *dev) +{ + struct smsc47b397_data *data = dev_get_drvdata(dev); + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + dev_dbg(dev, "starting device update...\n"); + + /* 4 temperature inputs, 4 fan inputs */ + for (i = 0; i < 4; i++) { + data->temp[i] = smsc47b397_read_value(data, + SMSC47B397_REG_TEMP(i)); + + /* must read LSB first */ + data->fan[i] = smsc47b397_read_value(data, + SMSC47B397_REG_FAN_LSB(i)); + data->fan[i] |= smsc47b397_read_value(data, + SMSC47B397_REG_FAN_MSB(i)) << 8; + } + + data->last_updated = jiffies; + data->valid = 1; + + dev_dbg(dev, "... device update complete\n"); + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * TEMP: 0.001C/bit (-128C to +127C) + * REG: 1C/bit, two's complement + */ +static int temp_from_reg(u8 reg) +{ + return (s8)reg * 1000; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct smsc47b397_data *data = smsc47b397_update_device(dev); + return sprintf(buf, "%d\n", temp_from_reg(data->temp[attr->index])); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3); + +/* + * FAN: 1 RPM/bit + * REG: count of 90kHz pulses / revolution + */ +static int fan_from_reg(u16 reg) +{ + if (reg == 0 || reg == 0xffff) + return 0; + return 90000 * 60 / reg; +} + +static ssize_t show_fan(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct smsc47b397_data *data = smsc47b397_update_device(dev); + return sprintf(buf, "%d\n", fan_from_reg(data->fan[attr->index])); +} +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2); +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3); + +static struct attribute *smsc47b397_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan4_input.dev_attr.attr, + + NULL +}; + +ATTRIBUTE_GROUPS(smsc47b397); + +static int smsc47b397_probe(struct platform_device *pdev); + +static struct platform_driver smsc47b397_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = smsc47b397_probe, +}; + +static int smsc47b397_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct smsc47b397_data *data; + struct device *hwmon_dev; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(dev, res->start, SMSC_EXTENT, + smsc47b397_driver.driver.name)) { + dev_err(dev, "Region 0x%lx-0x%lx already in use!\n", + (unsigned long)res->start, + (unsigned long)res->start + SMSC_EXTENT - 1); + return -EBUSY; + } + + data = devm_kzalloc(dev, sizeof(struct smsc47b397_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = res->start; + mutex_init(&data->lock); + mutex_init(&data->update_lock); + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "smsc47b397", + data, + smsc47b397_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static int __init smsc47b397_device_add(unsigned short address) +{ + struct resource res = { + .start = address, + .end = address + SMSC_EXTENT - 1, + .name = DRVNAME, + .flags = IORESOURCE_IO, + }; + int err; + + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + + pdev = platform_device_alloc(DRVNAME, address); + if (!pdev) { + err = -ENOMEM; + pr_err("Device allocation failed\n"); + goto exit; + } + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + +static int __init smsc47b397_find(void) +{ + u8 id, rev; + char *name; + unsigned short addr; + int err; + + err = superio_enter(); + if (err) + return err; + + id = force_id ? force_id : superio_inb(SUPERIO_REG_DEVID); + + switch (id) { + case 0x81: + name = "SCH5307-NS"; + break; + case 0x6f: + name = "LPC47B397-NC"; + break; + case 0x85: + case 0x8c: + name = "SCH5317"; + break; + default: + superio_exit(); + return -ENODEV; + } + + rev = superio_inb(SUPERIO_REG_DEVREV); + + superio_select(SUPERIO_REG_LD8); + addr = (superio_inb(SUPERIO_REG_BASE_MSB) << 8) + | superio_inb(SUPERIO_REG_BASE_LSB); + + pr_info("found SMSC %s (base address 0x%04x, revision %u)\n", + name, addr, rev); + + superio_exit(); + return addr; +} + +static int __init smsc47b397_init(void) +{ + unsigned short address; + int ret; + + ret = smsc47b397_find(); + if (ret < 0) + return ret; + address = ret; + + ret = platform_driver_register(&smsc47b397_driver); + if (ret) + goto exit; + + /* Sets global pdev as a side effect */ + ret = smsc47b397_device_add(address); + if (ret) + goto exit_driver; + + return 0; + +exit_driver: + platform_driver_unregister(&smsc47b397_driver); +exit: + return ret; +} + +static void __exit smsc47b397_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&smsc47b397_driver); +} + +MODULE_AUTHOR("Mark M. Hoffman "); +MODULE_DESCRIPTION("SMSC LPC47B397 driver"); +MODULE_LICENSE("GPL"); + +module_init(smsc47b397_init); +module_exit(smsc47b397_exit); diff --git a/drivers/hwmon/smsc47m1.c b/drivers/hwmon/smsc47m1.c new file mode 100644 index 000000000..5eeac9853 --- /dev/null +++ b/drivers/hwmon/smsc47m1.c @@ -0,0 +1,956 @@ +/* + * smsc47m1.c - Part of lm_sensors, Linux kernel modules + * for hardware monitoring + * + * Supports the SMSC LPC47B27x, LPC47M10x, LPC47M112, LPC47M13x, + * LPC47M14x, LPC47M15x, LPC47M192, LPC47M292 and LPC47M997 + * Super-I/O chips. + * + * Copyright (C) 2002 Mark D. Studebaker + * Copyright (C) 2004-2007 Jean Delvare + * Ported to Linux 2.6 by Gabriele Gorla + * and Jean Delvare + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +static struct platform_device *pdev; + +#define DRVNAME "smsc47m1" +enum chips { smsc47m1, smsc47m2 }; + +/* Super-I/0 registers and commands */ + +#define REG 0x2e /* The register to read/write */ +#define VAL 0x2f /* The value to read/write */ + +static inline void +superio_outb(int reg, int val) +{ + outb(reg, REG); + outb(val, VAL); +} + +static inline int +superio_inb(int reg) +{ + outb(reg, REG); + return inb(VAL); +} + +/* logical device for fans is 0x0A */ +#define superio_select() superio_outb(0x07, 0x0A) + +static inline int +superio_enter(void) +{ + if (!request_muxed_region(REG, 2, DRVNAME)) + return -EBUSY; + + outb(0x55, REG); + return 0; +} + +static inline void +superio_exit(void) +{ + outb(0xAA, REG); + release_region(REG, 2); +} + +#define SUPERIO_REG_ACT 0x30 +#define SUPERIO_REG_BASE 0x60 +#define SUPERIO_REG_DEVID 0x20 +#define SUPERIO_REG_DEVREV 0x21 + +/* Logical device registers */ + +#define SMSC_EXTENT 0x80 + +/* nr is 0 or 1 in the macros below */ +#define SMSC47M1_REG_ALARM 0x04 +#define SMSC47M1_REG_TPIN(nr) (0x34 - (nr)) +#define SMSC47M1_REG_PPIN(nr) (0x36 - (nr)) +#define SMSC47M1_REG_FANDIV 0x58 + +static const u8 SMSC47M1_REG_FAN[3] = { 0x59, 0x5a, 0x6b }; +static const u8 SMSC47M1_REG_FAN_PRELOAD[3] = { 0x5b, 0x5c, 0x6c }; +static const u8 SMSC47M1_REG_PWM[3] = { 0x56, 0x57, 0x69 }; + +#define SMSC47M2_REG_ALARM6 0x09 +#define SMSC47M2_REG_TPIN1 0x38 +#define SMSC47M2_REG_TPIN2 0x37 +#define SMSC47M2_REG_TPIN3 0x2d +#define SMSC47M2_REG_PPIN3 0x2c +#define SMSC47M2_REG_FANDIV3 0x6a + +#define MIN_FROM_REG(reg, div) ((reg) >= 192 ? 0 : \ + 983040 / ((192 - (reg)) * (div))) +#define FAN_FROM_REG(reg, div, preload) ((reg) <= (preload) || (reg) == 255 ? \ + 0 : \ + 983040 / (((reg) - (preload)) * (div))) +#define DIV_FROM_REG(reg) (1 << (reg)) +#define PWM_FROM_REG(reg) (((reg) & 0x7E) << 1) +#define PWM_EN_FROM_REG(reg) ((~(reg)) & 0x01) +#define PWM_TO_REG(reg) (((reg) >> 1) & 0x7E) + +struct smsc47m1_data { + unsigned short addr; + const char *name; + enum chips type; + struct device *hwmon_dev; + + struct mutex update_lock; + unsigned long last_updated; /* In jiffies */ + + u8 fan[3]; /* Register value */ + u8 fan_preload[3]; /* Register value */ + u8 fan_div[3]; /* Register encoding, shifted right */ + u8 alarms; /* Register encoding */ + u8 pwm[3]; /* Register value (bit 0 is disable) */ +}; + +struct smsc47m1_sio_data { + enum chips type; + u8 activate; /* Remember initial device state */ +}; + +static inline int smsc47m1_read_value(struct smsc47m1_data *data, u8 reg) +{ + return inb_p(data->addr + reg); +} + +static inline void smsc47m1_write_value(struct smsc47m1_data *data, u8 reg, + u8 value) +{ + outb_p(value, data->addr + reg); +} + +static struct smsc47m1_data *smsc47m1_update_device(struct device *dev, + int init) +{ + struct smsc47m1_data *data = dev_get_drvdata(dev); + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) || init) { + int i, fan_nr; + fan_nr = data->type == smsc47m2 ? 3 : 2; + + for (i = 0; i < fan_nr; i++) { + data->fan[i] = smsc47m1_read_value(data, + SMSC47M1_REG_FAN[i]); + data->fan_preload[i] = smsc47m1_read_value(data, + SMSC47M1_REG_FAN_PRELOAD[i]); + data->pwm[i] = smsc47m1_read_value(data, + SMSC47M1_REG_PWM[i]); + } + + i = smsc47m1_read_value(data, SMSC47M1_REG_FANDIV); + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = i >> 6; + + data->alarms = smsc47m1_read_value(data, + SMSC47M1_REG_ALARM) >> 6; + /* Clear alarms if needed */ + if (data->alarms) + smsc47m1_write_value(data, SMSC47M1_REG_ALARM, 0xC0); + + if (fan_nr >= 3) { + data->fan_div[2] = (smsc47m1_read_value(data, + SMSC47M2_REG_FANDIV3) >> 4) & 0x03; + data->alarms |= (smsc47m1_read_value(data, + SMSC47M2_REG_ALARM6) & 0x40) >> 4; + /* Clear alarm if needed */ + if (data->alarms & 0x04) + smsc47m1_write_value(data, + SMSC47M2_REG_ALARM6, + 0x40); + } + + data->last_updated = jiffies; + } + + mutex_unlock(&data->update_lock); + return data; +} + +static ssize_t get_fan(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct smsc47m1_data *data = smsc47m1_update_device(dev, 0); + int nr = attr->index; + /* + * This chip (stupidly) stops monitoring fan speed if PWM is + * enabled and duty cycle is 0%. This is fine if the monitoring + * and control concern the same fan, but troublesome if they are + * not (which could as well happen). + */ + int rpm = (data->pwm[nr] & 0x7F) == 0x00 ? 0 : + FAN_FROM_REG(data->fan[nr], + DIV_FROM_REG(data->fan_div[nr]), + data->fan_preload[nr]); + return sprintf(buf, "%d\n", rpm); +} + +static ssize_t get_fan_min(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct smsc47m1_data *data = smsc47m1_update_device(dev, 0); + int nr = attr->index; + int rpm = MIN_FROM_REG(data->fan_preload[nr], + DIV_FROM_REG(data->fan_div[nr])); + return sprintf(buf, "%d\n", rpm); +} + +static ssize_t get_fan_div(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct smsc47m1_data *data = smsc47m1_update_device(dev, 0); + return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[attr->index])); +} + +static ssize_t get_fan_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + int bitnr = to_sensor_dev_attr(devattr)->index; + struct smsc47m1_data *data = smsc47m1_update_device(dev, 0); + return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1); +} + +static ssize_t get_pwm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct smsc47m1_data *data = smsc47m1_update_device(dev, 0); + return sprintf(buf, "%d\n", PWM_FROM_REG(data->pwm[attr->index])); +} + +static ssize_t get_pwm_en(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct smsc47m1_data *data = smsc47m1_update_device(dev, 0); + return sprintf(buf, "%d\n", PWM_EN_FROM_REG(data->pwm[attr->index])); +} + +static ssize_t alarms_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct smsc47m1_data *data = smsc47m1_update_device(dev, 0); + return sprintf(buf, "%d\n", data->alarms); +} + +static ssize_t set_fan_min(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct smsc47m1_data *data = dev_get_drvdata(dev); + int nr = attr->index; + long rpmdiv; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + rpmdiv = val * DIV_FROM_REG(data->fan_div[nr]); + + if (983040 > 192 * rpmdiv || 2 * rpmdiv > 983040) { + mutex_unlock(&data->update_lock); + return -EINVAL; + } + + data->fan_preload[nr] = 192 - ((983040 + rpmdiv / 2) / rpmdiv); + smsc47m1_write_value(data, SMSC47M1_REG_FAN_PRELOAD[nr], + data->fan_preload[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +/* + * Note: we save and restore the fan minimum here, because its value is + * determined in part by the fan clock divider. This follows the principle + * of least surprise; the user doesn't expect the fan minimum to change just + * because the divider changed. + */ +static ssize_t set_fan_div(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct smsc47m1_data *data = dev_get_drvdata(dev); + int nr = attr->index; + long new_div; + int err; + long tmp; + u8 old_div = DIV_FROM_REG(data->fan_div[nr]); + + err = kstrtol(buf, 10, &new_div); + if (err) + return err; + + if (new_div == old_div) /* No change */ + return count; + + mutex_lock(&data->update_lock); + switch (new_div) { + case 1: + data->fan_div[nr] = 0; + break; + case 2: + data->fan_div[nr] = 1; + break; + case 4: + data->fan_div[nr] = 2; + break; + case 8: + data->fan_div[nr] = 3; + break; + default: + mutex_unlock(&data->update_lock); + return -EINVAL; + } + + switch (nr) { + case 0: + case 1: + tmp = smsc47m1_read_value(data, SMSC47M1_REG_FANDIV) + & ~(0x03 << (4 + 2 * nr)); + tmp |= data->fan_div[nr] << (4 + 2 * nr); + smsc47m1_write_value(data, SMSC47M1_REG_FANDIV, tmp); + break; + case 2: + tmp = smsc47m1_read_value(data, SMSC47M2_REG_FANDIV3) & 0xCF; + tmp |= data->fan_div[2] << 4; + smsc47m1_write_value(data, SMSC47M2_REG_FANDIV3, tmp); + break; + } + + /* Preserve fan min */ + tmp = 192 - (old_div * (192 - data->fan_preload[nr]) + + new_div / 2) / new_div; + data->fan_preload[nr] = clamp_val(tmp, 0, 191); + smsc47m1_write_value(data, SMSC47M1_REG_FAN_PRELOAD[nr], + data->fan_preload[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct smsc47m1_data *data = dev_get_drvdata(dev); + int nr = attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + if (val < 0 || val > 255) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->pwm[nr] &= 0x81; /* Preserve additional bits */ + data->pwm[nr] |= PWM_TO_REG(val); + smsc47m1_write_value(data, SMSC47M1_REG_PWM[nr], + data->pwm[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t set_pwm_en(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct smsc47m1_data *data = dev_get_drvdata(dev); + int nr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 1) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->pwm[nr] &= 0xFE; /* preserve the other bits */ + data->pwm[nr] |= !val; + smsc47m1_write_value(data, SMSC47M1_REG_PWM[nr], + data->pwm[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +#define fan_present(offset) \ +static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, get_fan, \ + NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ + get_fan_min, set_fan_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR, \ + get_fan_div, set_fan_div, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_alarm, S_IRUGO, get_fan_alarm, \ + NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(pwm##offset, S_IRUGO | S_IWUSR, \ + get_pwm, set_pwm, offset - 1); \ +static SENSOR_DEVICE_ATTR(pwm##offset##_enable, S_IRUGO | S_IWUSR, \ + get_pwm_en, set_pwm_en, offset - 1) + +fan_present(1); +fan_present(2); +fan_present(3); + +static DEVICE_ATTR_RO(alarms); + +static ssize_t name_show(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct smsc47m1_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} +static DEVICE_ATTR_RO(name); + +static struct attribute *smsc47m1_attributes_fan1[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group smsc47m1_group_fan1 = { + .attrs = smsc47m1_attributes_fan1, +}; + +static struct attribute *smsc47m1_attributes_fan2[] = { + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group smsc47m1_group_fan2 = { + .attrs = smsc47m1_attributes_fan2, +}; + +static struct attribute *smsc47m1_attributes_fan3[] = { + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan3_div.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group smsc47m1_group_fan3 = { + .attrs = smsc47m1_attributes_fan3, +}; + +static struct attribute *smsc47m1_attributes_pwm1[] = { + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + NULL +}; + +static const struct attribute_group smsc47m1_group_pwm1 = { + .attrs = smsc47m1_attributes_pwm1, +}; + +static struct attribute *smsc47m1_attributes_pwm2[] = { + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + NULL +}; + +static const struct attribute_group smsc47m1_group_pwm2 = { + .attrs = smsc47m1_attributes_pwm2, +}; + +static struct attribute *smsc47m1_attributes_pwm3[] = { + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + NULL +}; + +static const struct attribute_group smsc47m1_group_pwm3 = { + .attrs = smsc47m1_attributes_pwm3, +}; + +static struct attribute *smsc47m1_attributes[] = { + &dev_attr_alarms.attr, + &dev_attr_name.attr, + NULL +}; + +static const struct attribute_group smsc47m1_group = { + .attrs = smsc47m1_attributes, +}; + +static int __init smsc47m1_find(struct smsc47m1_sio_data *sio_data) +{ + u8 val; + unsigned short addr; + int err; + + err = superio_enter(); + if (err) + return err; + + val = force_id ? force_id : superio_inb(SUPERIO_REG_DEVID); + + /* + * SMSC LPC47M10x/LPC47M112/LPC47M13x (device id 0x59), LPC47M14x + * (device id 0x5F) and LPC47B27x (device id 0x51) have fan control. + * The LPC47M15x and LPC47M192 chips "with hardware monitoring block" + * can do much more besides (device id 0x60). + * The LPC47M997 is undocumented, but seems to be compatible with + * the LPC47M192, and has the same device id. + * The LPC47M292 (device id 0x6B) is somewhat compatible, but it + * supports a 3rd fan, and the pin configuration registers are + * unfortunately different. + * The LPC47M233 has the same device id (0x6B) but is not compatible. + * We check the high bit of the device revision register to + * differentiate them. + */ + switch (val) { + case 0x51: + pr_info("Found SMSC LPC47B27x\n"); + sio_data->type = smsc47m1; + break; + case 0x59: + pr_info("Found SMSC LPC47M10x/LPC47M112/LPC47M13x\n"); + sio_data->type = smsc47m1; + break; + case 0x5F: + pr_info("Found SMSC LPC47M14x\n"); + sio_data->type = smsc47m1; + break; + case 0x60: + pr_info("Found SMSC LPC47M15x/LPC47M192/LPC47M997\n"); + sio_data->type = smsc47m1; + break; + case 0x6B: + if (superio_inb(SUPERIO_REG_DEVREV) & 0x80) { + pr_debug("Found SMSC LPC47M233, unsupported\n"); + superio_exit(); + return -ENODEV; + } + + pr_info("Found SMSC LPC47M292\n"); + sio_data->type = smsc47m2; + break; + default: + superio_exit(); + return -ENODEV; + } + + superio_select(); + addr = (superio_inb(SUPERIO_REG_BASE) << 8) + | superio_inb(SUPERIO_REG_BASE + 1); + if (addr == 0) { + pr_info("Device address not set, will not use\n"); + superio_exit(); + return -ENODEV; + } + + /* + * Enable only if address is set (needed at least on the + * Compaq Presario S4000NX) + */ + sio_data->activate = superio_inb(SUPERIO_REG_ACT); + if ((sio_data->activate & 0x01) == 0) { + pr_info("Enabling device\n"); + superio_outb(SUPERIO_REG_ACT, sio_data->activate | 0x01); + } + + superio_exit(); + return addr; +} + +/* Restore device to its initial state */ +static void smsc47m1_restore(const struct smsc47m1_sio_data *sio_data) +{ + if ((sio_data->activate & 0x01) == 0) { + if (!superio_enter()) { + superio_select(); + pr_info("Disabling device\n"); + superio_outb(SUPERIO_REG_ACT, sio_data->activate); + superio_exit(); + } else { + pr_warn("Failed to disable device\n"); + } + } +} + +#define CHECK 1 +#define REQUEST 2 + +/* + * This function can be used to: + * - test for resource conflicts with ACPI + * - request the resources + * We only allocate the I/O ports we really need, to minimize the risk of + * conflicts with ACPI or with other drivers. + */ +static int __init smsc47m1_handle_resources(unsigned short address, + enum chips type, int action, + struct device *dev) +{ + static const u8 ports_m1[] = { + /* register, region length */ + 0x04, 1, + 0x33, 4, + 0x56, 7, + }; + + static const u8 ports_m2[] = { + /* register, region length */ + 0x04, 1, + 0x09, 1, + 0x2c, 2, + 0x35, 4, + 0x56, 7, + 0x69, 4, + }; + + int i, ports_size, err; + const u8 *ports; + + switch (type) { + case smsc47m1: + default: + ports = ports_m1; + ports_size = ARRAY_SIZE(ports_m1); + break; + case smsc47m2: + ports = ports_m2; + ports_size = ARRAY_SIZE(ports_m2); + break; + } + + for (i = 0; i + 1 < ports_size; i += 2) { + unsigned short start = address + ports[i]; + unsigned short len = ports[i + 1]; + + switch (action) { + case CHECK: + /* Only check for conflicts */ + err = acpi_check_region(start, len, DRVNAME); + if (err) + return err; + break; + case REQUEST: + /* Request the resources */ + if (!devm_request_region(dev, start, len, DRVNAME)) { + dev_err(dev, + "Region 0x%hx-0x%hx already in use!\n", + start, start + len); + return -EBUSY; + } + break; + } + } + + return 0; +} + +static void smsc47m1_remove_files(struct device *dev) +{ + sysfs_remove_group(&dev->kobj, &smsc47m1_group); + sysfs_remove_group(&dev->kobj, &smsc47m1_group_fan1); + sysfs_remove_group(&dev->kobj, &smsc47m1_group_fan2); + sysfs_remove_group(&dev->kobj, &smsc47m1_group_fan3); + sysfs_remove_group(&dev->kobj, &smsc47m1_group_pwm1); + sysfs_remove_group(&dev->kobj, &smsc47m1_group_pwm2); + sysfs_remove_group(&dev->kobj, &smsc47m1_group_pwm3); +} + +static int __init smsc47m1_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct smsc47m1_sio_data *sio_data = dev_get_platdata(dev); + struct smsc47m1_data *data; + struct resource *res; + int err; + int fan1, fan2, fan3, pwm1, pwm2, pwm3; + + static const char * const names[] = { + "smsc47m1", + "smsc47m2", + }; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + err = smsc47m1_handle_resources(res->start, sio_data->type, + REQUEST, dev); + if (err < 0) + return err; + + data = devm_kzalloc(dev, sizeof(struct smsc47m1_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = res->start; + data->type = sio_data->type; + data->name = names[sio_data->type]; + mutex_init(&data->update_lock); + platform_set_drvdata(pdev, data); + + /* + * If no function is properly configured, there's no point in + * actually registering the chip. + */ + pwm1 = (smsc47m1_read_value(data, SMSC47M1_REG_PPIN(0)) & 0x05) + == 0x04; + pwm2 = (smsc47m1_read_value(data, SMSC47M1_REG_PPIN(1)) & 0x05) + == 0x04; + if (data->type == smsc47m2) { + fan1 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN1) + & 0x0d) == 0x09; + fan2 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN2) + & 0x0d) == 0x09; + fan3 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN3) + & 0x0d) == 0x0d; + pwm3 = (smsc47m1_read_value(data, SMSC47M2_REG_PPIN3) + & 0x0d) == 0x08; + } else { + fan1 = (smsc47m1_read_value(data, SMSC47M1_REG_TPIN(0)) + & 0x05) == 0x05; + fan2 = (smsc47m1_read_value(data, SMSC47M1_REG_TPIN(1)) + & 0x05) == 0x05; + fan3 = 0; + pwm3 = 0; + } + if (!(fan1 || fan2 || fan3 || pwm1 || pwm2 || pwm3)) { + dev_warn(dev, "Device not configured, will not use\n"); + return -ENODEV; + } + + /* + * Some values (fan min, clock dividers, pwm registers) may be + * needed before any update is triggered, so we better read them + * at least once here. We don't usually do it that way, but in + * this particular case, manually reading 5 registers out of 8 + * doesn't make much sense and we're better using the existing + * function. + */ + smsc47m1_update_device(dev, 1); + + /* Register sysfs hooks */ + if (fan1) { + err = sysfs_create_group(&dev->kobj, + &smsc47m1_group_fan1); + if (err) + goto error_remove_files; + } else + dev_dbg(dev, "Fan 1 not enabled by hardware, skipping\n"); + + if (fan2) { + err = sysfs_create_group(&dev->kobj, + &smsc47m1_group_fan2); + if (err) + goto error_remove_files; + } else + dev_dbg(dev, "Fan 2 not enabled by hardware, skipping\n"); + + if (fan3) { + err = sysfs_create_group(&dev->kobj, + &smsc47m1_group_fan3); + if (err) + goto error_remove_files; + } else if (data->type == smsc47m2) + dev_dbg(dev, "Fan 3 not enabled by hardware, skipping\n"); + + if (pwm1) { + err = sysfs_create_group(&dev->kobj, + &smsc47m1_group_pwm1); + if (err) + goto error_remove_files; + } else + dev_dbg(dev, "PWM 1 not enabled by hardware, skipping\n"); + + if (pwm2) { + err = sysfs_create_group(&dev->kobj, + &smsc47m1_group_pwm2); + if (err) + goto error_remove_files; + } else + dev_dbg(dev, "PWM 2 not enabled by hardware, skipping\n"); + + if (pwm3) { + err = sysfs_create_group(&dev->kobj, + &smsc47m1_group_pwm3); + if (err) + goto error_remove_files; + } else if (data->type == smsc47m2) + dev_dbg(dev, "PWM 3 not enabled by hardware, skipping\n"); + + err = sysfs_create_group(&dev->kobj, &smsc47m1_group); + if (err) + goto error_remove_files; + + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto error_remove_files; + } + + return 0; + +error_remove_files: + smsc47m1_remove_files(dev); + return err; +} + +static int __exit smsc47m1_remove(struct platform_device *pdev) +{ + struct smsc47m1_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + smsc47m1_remove_files(&pdev->dev); + + return 0; +} + +static struct platform_driver smsc47m1_driver = { + .driver = { + .name = DRVNAME, + }, + .remove = __exit_p(smsc47m1_remove), +}; + +static int __init smsc47m1_device_add(unsigned short address, + const struct smsc47m1_sio_data *sio_data) +{ + struct resource res = { + .start = address, + .end = address + SMSC_EXTENT - 1, + .name = DRVNAME, + .flags = IORESOURCE_IO, + }; + int err; + + err = smsc47m1_handle_resources(address, sio_data->type, CHECK, NULL); + if (err) + goto exit; + + pdev = platform_device_alloc(DRVNAME, address); + if (!pdev) { + err = -ENOMEM; + pr_err("Device allocation failed\n"); + goto exit; + } + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add_data(pdev, sio_data, + sizeof(struct smsc47m1_sio_data)); + if (err) { + pr_err("Platform data allocation failed\n"); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + +static int __init sm_smsc47m1_init(void) +{ + int err; + unsigned short address; + struct smsc47m1_sio_data sio_data; + + err = smsc47m1_find(&sio_data); + if (err < 0) + return err; + address = err; + + /* Sets global pdev as a side effect */ + err = smsc47m1_device_add(address, &sio_data); + if (err) + return err; + + err = platform_driver_probe(&smsc47m1_driver, smsc47m1_probe); + if (err) + goto exit_device; + + return 0; + +exit_device: + platform_device_unregister(pdev); + smsc47m1_restore(&sio_data); + return err; +} + +static void __exit sm_smsc47m1_exit(void) +{ + platform_driver_unregister(&smsc47m1_driver); + smsc47m1_restore(dev_get_platdata(&pdev->dev)); + platform_device_unregister(pdev); +} + +MODULE_AUTHOR("Mark D. Studebaker "); +MODULE_DESCRIPTION("SMSC LPC47M1xx fan sensors driver"); +MODULE_LICENSE("GPL"); + +module_init(sm_smsc47m1_init); +module_exit(sm_smsc47m1_exit); diff --git a/drivers/hwmon/smsc47m192.c b/drivers/hwmon/smsc47m192.c new file mode 100644 index 000000000..698940803 --- /dev/null +++ b/drivers/hwmon/smsc47m192.c @@ -0,0 +1,645 @@ +/* + * smsc47m192.c - Support for hardware monitoring block of + * SMSC LPC47M192 and compatible Super I/O chips + * + * Copyright (C) 2006 Hartmut Rick + * + * Derived from lm78.c and other chip drivers. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END }; + +/* SMSC47M192 registers */ +#define SMSC47M192_REG_IN(nr) ((nr) < 6 ? (0x20 + (nr)) : \ + (0x50 + (nr) - 6)) +#define SMSC47M192_REG_IN_MAX(nr) ((nr) < 6 ? (0x2b + (nr) * 2) : \ + (0x54 + (((nr) - 6) * 2))) +#define SMSC47M192_REG_IN_MIN(nr) ((nr) < 6 ? (0x2c + (nr) * 2) : \ + (0x55 + (((nr) - 6) * 2))) +static u8 SMSC47M192_REG_TEMP[3] = { 0x27, 0x26, 0x52 }; +static u8 SMSC47M192_REG_TEMP_MAX[3] = { 0x39, 0x37, 0x58 }; +static u8 SMSC47M192_REG_TEMP_MIN[3] = { 0x3A, 0x38, 0x59 }; +#define SMSC47M192_REG_TEMP_OFFSET(nr) ((nr) == 2 ? 0x1e : 0x1f) +#define SMSC47M192_REG_ALARM1 0x41 +#define SMSC47M192_REG_ALARM2 0x42 +#define SMSC47M192_REG_VID 0x47 +#define SMSC47M192_REG_VID4 0x49 +#define SMSC47M192_REG_CONFIG 0x40 +#define SMSC47M192_REG_SFR 0x4f +#define SMSC47M192_REG_COMPANY_ID 0x3e +#define SMSC47M192_REG_VERSION 0x3f + +/* generalised scaling with integer rounding */ +static inline int SCALE(long val, int mul, int div) +{ + if (val < 0) + return (val * mul - div / 2) / div; + else + return (val * mul + div / 2) / div; +} + +/* Conversions */ + +/* smsc47m192 internally scales voltage measurements */ +static const u16 nom_mv[] = { 2500, 2250, 3300, 5000, 12000, 3300, 1500, 1800 }; + +static inline unsigned int IN_FROM_REG(u8 reg, int n) +{ + return SCALE(reg, nom_mv[n], 192); +} + +static inline u8 IN_TO_REG(unsigned long val, int n) +{ + val = clamp_val(val, 0, nom_mv[n] * 255 / 192); + return SCALE(val, 192, nom_mv[n]); +} + +/* + * TEMP: 0.001 degC units (-128C to +127C) + * REG: 1C/bit, two's complement + */ +static inline s8 TEMP_TO_REG(long val) +{ + return SCALE(clamp_val(val, -128000, 127000), 1, 1000); +} + +static inline int TEMP_FROM_REG(s8 val) +{ + return val * 1000; +} + +struct smsc47m192_data { + struct i2c_client *client; + const struct attribute_group *groups[3]; + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[8]; /* Register value */ + u8 in_max[8]; /* Register value */ + u8 in_min[8]; /* Register value */ + s8 temp[3]; /* Register value */ + s8 temp_max[3]; /* Register value */ + s8 temp_min[3]; /* Register value */ + s8 temp_offset[3]; /* Register value */ + u16 alarms; /* Register encoding, combined */ + u8 vid; /* Register encoding, combined */ + u8 vrm; +}; + +static struct smsc47m192_data *smsc47m192_update_device(struct device *dev) +{ + struct smsc47m192_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int i, config; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + u8 sfr = i2c_smbus_read_byte_data(client, SMSC47M192_REG_SFR); + + dev_dbg(&client->dev, "Starting smsc47m192 update\n"); + + for (i = 0; i <= 7; i++) { + data->in[i] = i2c_smbus_read_byte_data(client, + SMSC47M192_REG_IN(i)); + data->in_min[i] = i2c_smbus_read_byte_data(client, + SMSC47M192_REG_IN_MIN(i)); + data->in_max[i] = i2c_smbus_read_byte_data(client, + SMSC47M192_REG_IN_MAX(i)); + } + for (i = 0; i < 3; i++) { + data->temp[i] = i2c_smbus_read_byte_data(client, + SMSC47M192_REG_TEMP[i]); + data->temp_max[i] = i2c_smbus_read_byte_data(client, + SMSC47M192_REG_TEMP_MAX[i]); + data->temp_min[i] = i2c_smbus_read_byte_data(client, + SMSC47M192_REG_TEMP_MIN[i]); + } + for (i = 1; i < 3; i++) + data->temp_offset[i] = i2c_smbus_read_byte_data(client, + SMSC47M192_REG_TEMP_OFFSET(i)); + /* + * first offset is temp_offset[0] if SFR bit 4 is set, + * temp_offset[1] otherwise + */ + if (sfr & 0x10) { + data->temp_offset[0] = data->temp_offset[1]; + data->temp_offset[1] = 0; + } else + data->temp_offset[0] = 0; + + data->vid = i2c_smbus_read_byte_data(client, SMSC47M192_REG_VID) + & 0x0f; + config = i2c_smbus_read_byte_data(client, + SMSC47M192_REG_CONFIG); + if (config & 0x20) + data->vid |= (i2c_smbus_read_byte_data(client, + SMSC47M192_REG_VID4) & 0x01) << 4; + data->alarms = i2c_smbus_read_byte_data(client, + SMSC47M192_REG_ALARM1) | + (i2c_smbus_read_byte_data(client, + SMSC47M192_REG_ALARM2) << 8); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* Voltages */ +static ssize_t show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct smsc47m192_data *data = smsc47m192_update_device(dev); + return sprintf(buf, "%d\n", IN_FROM_REG(data->in[nr], nr)); +} + +static ssize_t show_in_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct smsc47m192_data *data = smsc47m192_update_device(dev); + return sprintf(buf, "%d\n", IN_FROM_REG(data->in_min[nr], nr)); +} + +static ssize_t show_in_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct smsc47m192_data *data = smsc47m192_update_device(dev); + return sprintf(buf, "%d\n", IN_FROM_REG(data->in_max[nr], nr)); +} + +static ssize_t set_in_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct smsc47m192_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_min[nr] = IN_TO_REG(val, nr); + i2c_smbus_write_byte_data(client, SMSC47M192_REG_IN_MIN(nr), + data->in_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_in_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct smsc47m192_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_max[nr] = IN_TO_REG(val, nr); + i2c_smbus_write_byte_data(client, SMSC47M192_REG_IN_MAX(nr), + data->in_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define show_in_offset(offset) \ +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, \ + show_in, NULL, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \ + show_in_min, set_in_min, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \ + show_in_max, set_in_max, offset); + +show_in_offset(0) +show_in_offset(1) +show_in_offset(2) +show_in_offset(3) +show_in_offset(4) +show_in_offset(5) +show_in_offset(6) +show_in_offset(7) + +/* Temperatures */ +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct smsc47m192_data *data = smsc47m192_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[nr])); +} + +static ssize_t show_temp_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct smsc47m192_data *data = smsc47m192_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_min[nr])); +} + +static ssize_t show_temp_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct smsc47m192_data *data = smsc47m192_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max[nr])); +} + +static ssize_t set_temp_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct smsc47m192_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_min[nr] = TEMP_TO_REG(val); + i2c_smbus_write_byte_data(client, SMSC47M192_REG_TEMP_MIN[nr], + data->temp_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct smsc47m192_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_max[nr] = TEMP_TO_REG(val); + i2c_smbus_write_byte_data(client, SMSC47M192_REG_TEMP_MAX[nr], + data->temp_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp_offset(struct device *dev, struct device_attribute + *attr, char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct smsc47m192_data *data = smsc47m192_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_offset[nr])); +} + +static ssize_t set_temp_offset(struct device *dev, struct device_attribute + *attr, const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct smsc47m192_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 sfr = i2c_smbus_read_byte_data(client, SMSC47M192_REG_SFR); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_offset[nr] = TEMP_TO_REG(val); + if (nr > 1) + i2c_smbus_write_byte_data(client, + SMSC47M192_REG_TEMP_OFFSET(nr), data->temp_offset[nr]); + else if (data->temp_offset[nr] != 0) { + /* + * offset[0] and offset[1] share the same register, + * SFR bit 4 activates offset[0] + */ + i2c_smbus_write_byte_data(client, SMSC47M192_REG_SFR, + (sfr & 0xef) | (nr == 0 ? 0x10 : 0)); + data->temp_offset[1-nr] = 0; + i2c_smbus_write_byte_data(client, + SMSC47M192_REG_TEMP_OFFSET(nr), data->temp_offset[nr]); + } else if ((sfr & 0x10) == (nr == 0 ? 0x10 : 0)) + i2c_smbus_write_byte_data(client, + SMSC47M192_REG_TEMP_OFFSET(nr), 0); + mutex_unlock(&data->update_lock); + return count; +} + +#define show_temp_index(index) \ +static SENSOR_DEVICE_ATTR(temp##index##_input, S_IRUGO, \ + show_temp, NULL, index-1); \ +static SENSOR_DEVICE_ATTR(temp##index##_min, S_IRUGO | S_IWUSR, \ + show_temp_min, set_temp_min, index-1); \ +static SENSOR_DEVICE_ATTR(temp##index##_max, S_IRUGO | S_IWUSR, \ + show_temp_max, set_temp_max, index-1); \ +static SENSOR_DEVICE_ATTR(temp##index##_offset, S_IRUGO | S_IWUSR, \ + show_temp_offset, set_temp_offset, index-1); + +show_temp_index(1) +show_temp_index(2) +show_temp_index(3) + +/* VID */ +static ssize_t cpu0_vid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct smsc47m192_data *data = smsc47m192_update_device(dev); + return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); +} +static DEVICE_ATTR_RO(cpu0_vid); + +static ssize_t vrm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct smsc47m192_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", data->vrm); +} + +static ssize_t vrm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct smsc47m192_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + if (val > 255) + return -EINVAL; + + data->vrm = val; + return count; +} +static DEVICE_ATTR_RW(vrm); + +/* Alarms */ +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct smsc47m192_data *data = smsc47m192_update_device(dev); + return sprintf(buf, "%u\n", (data->alarms & nr) ? 1 : 0); +} + +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 0x0010); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 0x0020); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 0x0040); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 0x4000); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_alarm, NULL, 0x8000); +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0x0001); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 0x0002); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 0x0004); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 0x0008); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 0x0100); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 0x0200); +static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 0x0400); +static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 0x0800); + +static struct attribute *smsc47m192_attributes[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in6_alarm.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in7_alarm.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_offset.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_offset.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_offset.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + + &dev_attr_cpu0_vid.attr, + &dev_attr_vrm.attr, + NULL +}; + +static const struct attribute_group smsc47m192_group = { + .attrs = smsc47m192_attributes, +}; + +static struct attribute *smsc47m192_attributes_in4[] = { + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group smsc47m192_group_in4 = { + .attrs = smsc47m192_attributes_in4, +}; + +static void smsc47m192_init_client(struct i2c_client *client) +{ + int i; + u8 config = i2c_smbus_read_byte_data(client, SMSC47M192_REG_CONFIG); + u8 sfr = i2c_smbus_read_byte_data(client, SMSC47M192_REG_SFR); + + /* select cycle mode (pause 1 sec between updates) */ + i2c_smbus_write_byte_data(client, SMSC47M192_REG_SFR, + (sfr & 0xfd) | 0x02); + if (!(config & 0x01)) { + /* initialize alarm limits */ + for (i = 0; i < 8; i++) { + i2c_smbus_write_byte_data(client, + SMSC47M192_REG_IN_MIN(i), 0); + i2c_smbus_write_byte_data(client, + SMSC47M192_REG_IN_MAX(i), 0xff); + } + for (i = 0; i < 3; i++) { + i2c_smbus_write_byte_data(client, + SMSC47M192_REG_TEMP_MIN[i], 0x80); + i2c_smbus_write_byte_data(client, + SMSC47M192_REG_TEMP_MAX[i], 0x7f); + } + + /* start monitoring */ + i2c_smbus_write_byte_data(client, SMSC47M192_REG_CONFIG, + (config & 0xf7) | 0x01); + } +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int smsc47m192_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int version; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* Detection criteria from sensors_detect script */ + version = i2c_smbus_read_byte_data(client, SMSC47M192_REG_VERSION); + if (i2c_smbus_read_byte_data(client, + SMSC47M192_REG_COMPANY_ID) == 0x55 + && (version & 0xf0) == 0x20 + && (i2c_smbus_read_byte_data(client, + SMSC47M192_REG_VID) & 0x70) == 0x00 + && (i2c_smbus_read_byte_data(client, + SMSC47M192_REG_VID4) & 0xfe) == 0x80) { + dev_info(&adapter->dev, + "found SMSC47M192 or compatible, " + "version 2, stepping A%d\n", version & 0x0f); + } else { + dev_dbg(&adapter->dev, + "SMSC47M192 detection failed at 0x%02x\n", + client->addr); + return -ENODEV; + } + + strlcpy(info->type, "smsc47m192", I2C_NAME_SIZE); + + return 0; +} + +static int smsc47m192_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct smsc47m192_data *data; + int config; + + data = devm_kzalloc(dev, sizeof(struct smsc47m192_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + data->vrm = vid_which_vrm(); + mutex_init(&data->update_lock); + + /* Initialize the SMSC47M192 chip */ + smsc47m192_init_client(client); + + /* sysfs hooks */ + data->groups[0] = &smsc47m192_group; + /* Pin 110 is either in4 (+12V) or VID4 */ + config = i2c_smbus_read_byte_data(client, SMSC47M192_REG_CONFIG); + if (!(config & 0x20)) + data->groups[1] = &smsc47m192_group_in4; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id smsc47m192_id[] = { + { "smsc47m192", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, smsc47m192_id); + +static struct i2c_driver smsc47m192_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "smsc47m192", + }, + .probe = smsc47m192_probe, + .id_table = smsc47m192_id, + .detect = smsc47m192_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(smsc47m192_driver); + +MODULE_AUTHOR("Hartmut Rick "); +MODULE_DESCRIPTION("SMSC47M192 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/stts751.c b/drivers/hwmon/stts751.c new file mode 100644 index 000000000..7fe152d92 --- /dev/null +++ b/drivers/hwmon/stts751.c @@ -0,0 +1,845 @@ +/* + * STTS751 sensor driver + * + * Copyright (C) 2016-2017 Istituto Italiano di Tecnologia - RBCS - EDL + * Robotics, Brain and Cognitive Sciences department + * Electronic Design Laboratory + * + * Written by Andrea Merello + * + * Based on LM95241 driver and LM90 driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEVNAME "stts751" + +static const unsigned short normal_i2c[] = { + 0x48, 0x49, 0x38, 0x39, /* STTS751-0 */ + 0x4A, 0x4B, 0x3A, 0x3B, /* STTS751-1 */ + I2C_CLIENT_END }; + +#define STTS751_REG_TEMP_H 0x00 +#define STTS751_REG_STATUS 0x01 +#define STTS751_STATUS_TRIPT BIT(0) +#define STTS751_STATUS_TRIPL BIT(5) +#define STTS751_STATUS_TRIPH BIT(6) +#define STTS751_REG_TEMP_L 0x02 +#define STTS751_REG_CONF 0x03 +#define STTS751_CONF_RES_MASK 0x0C +#define STTS751_CONF_RES_SHIFT 2 +#define STTS751_CONF_EVENT_DIS BIT(7) +#define STTS751_CONF_STOP BIT(6) +#define STTS751_REG_RATE 0x04 +#define STTS751_REG_HLIM_H 0x05 +#define STTS751_REG_HLIM_L 0x06 +#define STTS751_REG_LLIM_H 0x07 +#define STTS751_REG_LLIM_L 0x08 +#define STTS751_REG_TLIM 0x20 +#define STTS751_REG_HYST 0x21 +#define STTS751_REG_SMBUS_TO 0x22 + +#define STTS751_REG_PROD_ID 0xFD +#define STTS751_REG_MAN_ID 0xFE +#define STTS751_REG_REV_ID 0xFF + +#define STTS751_0_PROD_ID 0x00 +#define STTS751_1_PROD_ID 0x01 +#define ST_MAN_ID 0x53 + +/* + * Possible update intervals are (in mS): + * 16000, 8000, 4000, 2000, 1000, 500, 250, 125, 62.5, 31.25 + * However we are not going to complicate things too much and we stick to the + * approx value in mS. + */ +static const int stts751_intervals[] = { + 16000, 8000, 4000, 2000, 1000, 500, 250, 125, 63, 31 +}; + +static const struct i2c_device_id stts751_id[] = { + { "stts751", 0 }, + { } +}; + +static const struct of_device_id stts751_of_match[] = { + { .compatible = "stts751" }, + { }, +}; +MODULE_DEVICE_TABLE(of, stts751_of_match); + +struct stts751_priv { + struct device *dev; + struct i2c_client *client; + struct mutex access_lock; + u8 interval; + int res; + int event_max, event_min; + int therm; + int hyst; + bool smbus_timeout; + int temp; + unsigned long last_update, last_alert_update; + u8 config; + bool min_alert, max_alert, therm_trip; + bool data_valid, alert_valid; + bool notify_max, notify_min; +}; + +/* + * These functions converts temperature from HW format to integer format and + * vice-vers. They are (mostly) taken from lm90 driver. Unit is in mC. + */ +static int stts751_to_deg(s16 hw_val) +{ + return hw_val * 125 / 32; +} + +static s32 stts751_to_hw(int val) +{ + return DIV_ROUND_CLOSEST(val, 125) * 32; +} + +static int stts751_adjust_resolution(struct stts751_priv *priv) +{ + u8 res; + + switch (priv->interval) { + case 9: + /* 10 bits */ + res = 0; + break; + case 8: + /* 11 bits */ + res = 1; + break; + default: + /* 12 bits */ + res = 3; + break; + } + + if (priv->res == res) + return 0; + + priv->config &= ~STTS751_CONF_RES_MASK; + priv->config |= res << STTS751_CONF_RES_SHIFT; + dev_dbg(&priv->client->dev, "setting res %d. config %x", + res, priv->config); + priv->res = res; + + return i2c_smbus_write_byte_data(priv->client, + STTS751_REG_CONF, priv->config); +} + +static int stts751_update_temp(struct stts751_priv *priv) +{ + s32 integer1, integer2, frac; + + /* + * There is a trick here, like in the lm90 driver. We have to read two + * registers to get the sensor temperature, but we have to beware a + * conversion could occur between the readings. We could use the + * one-shot conversion register, but we don't want to do this (disables + * hardware monitoring). So the solution used here is to read the high + * byte once, then the low byte, then the high byte again. If the new + * high byte matches the old one, then we have a valid reading. Else we + * have to read the low byte again, and now we believe we have a correct + * reading. + */ + integer1 = i2c_smbus_read_byte_data(priv->client, STTS751_REG_TEMP_H); + if (integer1 < 0) { + dev_dbg(&priv->client->dev, + "I2C read failed (temp H). ret: %x\n", integer1); + return integer1; + } + + frac = i2c_smbus_read_byte_data(priv->client, STTS751_REG_TEMP_L); + if (frac < 0) { + dev_dbg(&priv->client->dev, + "I2C read failed (temp L). ret: %x\n", frac); + return frac; + } + + integer2 = i2c_smbus_read_byte_data(priv->client, STTS751_REG_TEMP_H); + if (integer2 < 0) { + dev_dbg(&priv->client->dev, + "I2C 2nd read failed (temp H). ret: %x\n", integer2); + return integer2; + } + + if (integer1 != integer2) { + frac = i2c_smbus_read_byte_data(priv->client, + STTS751_REG_TEMP_L); + if (frac < 0) { + dev_dbg(&priv->client->dev, + "I2C 2nd read failed (temp L). ret: %x\n", + frac); + return frac; + } + } + + priv->temp = stts751_to_deg((integer1 << 8) | frac); + return 0; +} + +static int stts751_set_temp_reg16(struct stts751_priv *priv, int temp, + u8 hreg, u8 lreg) +{ + s32 hwval; + int ret; + + hwval = stts751_to_hw(temp); + + ret = i2c_smbus_write_byte_data(priv->client, hreg, hwval >> 8); + if (ret) + return ret; + + return i2c_smbus_write_byte_data(priv->client, lreg, hwval & 0xff); +} + +static int stts751_set_temp_reg8(struct stts751_priv *priv, int temp, u8 reg) +{ + s32 hwval; + + hwval = stts751_to_hw(temp); + return i2c_smbus_write_byte_data(priv->client, reg, hwval >> 8); +} + +static int stts751_read_reg16(struct stts751_priv *priv, int *temp, + u8 hreg, u8 lreg) +{ + int integer, frac; + + integer = i2c_smbus_read_byte_data(priv->client, hreg); + if (integer < 0) + return integer; + + frac = i2c_smbus_read_byte_data(priv->client, lreg); + if (frac < 0) + return frac; + + *temp = stts751_to_deg((integer << 8) | frac); + + return 0; +} + +static int stts751_read_reg8(struct stts751_priv *priv, int *temp, u8 reg) +{ + int integer; + + integer = i2c_smbus_read_byte_data(priv->client, reg); + if (integer < 0) + return integer; + + *temp = stts751_to_deg(integer << 8); + + return 0; +} + +/* + * Update alert flags without waiting for cache to expire. We detects alerts + * immediately for the sake of the alert handler; we still need to deal with + * caching to workaround the fact that alarm flags int the status register, + * despite what the datasheet claims, gets always cleared on read. + */ +static int stts751_update_alert(struct stts751_priv *priv) +{ + int ret; + bool conv_done; + int cache_time = msecs_to_jiffies(stts751_intervals[priv->interval]); + + /* + * Add another 10% because if we run faster than the HW conversion + * rate we will end up in reporting incorrectly alarms. + */ + cache_time += cache_time / 10; + + ret = i2c_smbus_read_byte_data(priv->client, STTS751_REG_STATUS); + if (ret < 0) + return ret; + + dev_dbg(&priv->client->dev, "status reg %x\n", ret); + conv_done = ret & (STTS751_STATUS_TRIPH | STTS751_STATUS_TRIPL); + /* + * Reset the cache if the cache time expired, or if we are sure + * we have valid data from a device conversion, or if we know + * our cache has been never written. + * + * Note that when the cache has been never written the point is + * to correctly initialize the timestamp, rather than clearing + * the cache values. + * + * Note that updating the cache timestamp when we get an alarm flag + * is required, otherwise we could incorrectly report alarms to be zero. + */ + if (time_after(jiffies, priv->last_alert_update + cache_time) || + conv_done || !priv->alert_valid) { + priv->max_alert = false; + priv->min_alert = false; + priv->alert_valid = true; + priv->last_alert_update = jiffies; + dev_dbg(&priv->client->dev, "invalidating alert cache\n"); + } + + priv->max_alert |= !!(ret & STTS751_STATUS_TRIPH); + priv->min_alert |= !!(ret & STTS751_STATUS_TRIPL); + priv->therm_trip = !!(ret & STTS751_STATUS_TRIPT); + + dev_dbg(&priv->client->dev, "max_alert: %d, min_alert: %d, therm_trip: %d\n", + priv->max_alert, priv->min_alert, priv->therm_trip); + + return 0; +} + +static void stts751_alert(struct i2c_client *client, + enum i2c_alert_protocol type, unsigned int data) +{ + int ret; + struct stts751_priv *priv = i2c_get_clientdata(client); + + if (type != I2C_PROTOCOL_SMBUS_ALERT) + return; + + dev_dbg(&client->dev, "alert!"); + + mutex_lock(&priv->access_lock); + ret = stts751_update_alert(priv); + if (ret < 0) { + /* default to worst case */ + priv->max_alert = true; + priv->min_alert = true; + + dev_warn(priv->dev, + "Alert received, but can't communicate to the device. Triggering all alarms!"); + } + + if (priv->max_alert) { + if (priv->notify_max) + dev_notice(priv->dev, "got alert for HIGH temperature"); + priv->notify_max = false; + + /* unblock alert poll */ + sysfs_notify(&priv->dev->kobj, NULL, "temp1_max_alarm"); + } + + if (priv->min_alert) { + if (priv->notify_min) + dev_notice(priv->dev, "got alert for LOW temperature"); + priv->notify_min = false; + + /* unblock alert poll */ + sysfs_notify(&priv->dev->kobj, NULL, "temp1_min_alarm"); + } + + if (priv->min_alert || priv->max_alert) + kobject_uevent(&priv->dev->kobj, KOBJ_CHANGE); + + mutex_unlock(&priv->access_lock); +} + +static int stts751_update(struct stts751_priv *priv) +{ + int ret; + int cache_time = msecs_to_jiffies(stts751_intervals[priv->interval]); + + if (time_after(jiffies, priv->last_update + cache_time) || + !priv->data_valid) { + ret = stts751_update_temp(priv); + if (ret) + return ret; + + ret = stts751_update_alert(priv); + if (ret) + return ret; + priv->data_valid = true; + priv->last_update = jiffies; + } + + return 0; +} + +static ssize_t show_max_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + struct stts751_priv *priv = dev_get_drvdata(dev); + + mutex_lock(&priv->access_lock); + ret = stts751_update(priv); + if (!ret) + priv->notify_max = true; + mutex_unlock(&priv->access_lock); + if (ret < 0) + return ret; + + return snprintf(buf, PAGE_SIZE, "%d\n", priv->max_alert); +} + +static ssize_t show_min_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + struct stts751_priv *priv = dev_get_drvdata(dev); + + mutex_lock(&priv->access_lock); + ret = stts751_update(priv); + if (!ret) + priv->notify_min = true; + mutex_unlock(&priv->access_lock); + if (ret < 0) + return ret; + + return snprintf(buf, PAGE_SIZE, "%d\n", priv->min_alert); +} + +static ssize_t show_input(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + struct stts751_priv *priv = dev_get_drvdata(dev); + + mutex_lock(&priv->access_lock); + ret = stts751_update(priv); + mutex_unlock(&priv->access_lock); + if (ret < 0) + return ret; + + return snprintf(buf, PAGE_SIZE, "%d\n", priv->temp); +} + +static ssize_t show_therm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct stts751_priv *priv = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", priv->therm); +} + +static ssize_t set_therm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + long temp; + struct stts751_priv *priv = dev_get_drvdata(dev); + + if (kstrtol(buf, 10, &temp) < 0) + return -EINVAL; + + /* HW works in range -64C to +127.937C */ + temp = clamp_val(temp, -64000, 127937); + mutex_lock(&priv->access_lock); + ret = stts751_set_temp_reg8(priv, temp, STTS751_REG_TLIM); + if (ret) + goto exit; + + dev_dbg(&priv->client->dev, "setting therm %ld", temp); + + /* + * hysteresis reg is relative to therm, so the HW does not need to be + * adjusted, we need to update our local copy only. + */ + priv->hyst = temp - (priv->therm - priv->hyst); + priv->therm = temp; + +exit: + mutex_unlock(&priv->access_lock); + if (ret) + return ret; + + return count; +} + +static ssize_t show_hyst(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct stts751_priv *priv = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", priv->hyst); +} + +static ssize_t set_hyst(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + long temp; + + struct stts751_priv *priv = dev_get_drvdata(dev); + + if (kstrtol(buf, 10, &temp) < 0) + return -EINVAL; + + mutex_lock(&priv->access_lock); + /* HW works in range -64C to +127.937C */ + temp = clamp_val(temp, -64000, priv->therm); + priv->hyst = temp; + dev_dbg(&priv->client->dev, "setting hyst %ld", temp); + temp = priv->therm - temp; + ret = stts751_set_temp_reg8(priv, temp, STTS751_REG_HYST); + mutex_unlock(&priv->access_lock); + if (ret) + return ret; + + return count; +} + +static ssize_t show_therm_trip(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + struct stts751_priv *priv = dev_get_drvdata(dev); + + mutex_lock(&priv->access_lock); + ret = stts751_update(priv); + mutex_unlock(&priv->access_lock); + if (ret < 0) + return ret; + + return snprintf(buf, PAGE_SIZE, "%d\n", priv->therm_trip); +} + +static ssize_t show_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct stts751_priv *priv = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", priv->event_max); +} + +static ssize_t set_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + long temp; + struct stts751_priv *priv = dev_get_drvdata(dev); + + if (kstrtol(buf, 10, &temp) < 0) + return -EINVAL; + + mutex_lock(&priv->access_lock); + /* HW works in range -64C to +127.937C */ + temp = clamp_val(temp, priv->event_min, 127937); + ret = stts751_set_temp_reg16(priv, temp, + STTS751_REG_HLIM_H, STTS751_REG_HLIM_L); + if (ret) + goto exit; + + dev_dbg(&priv->client->dev, "setting event max %ld", temp); + priv->event_max = temp; + ret = count; +exit: + mutex_unlock(&priv->access_lock); + return ret; +} + +static ssize_t show_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct stts751_priv *priv = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", priv->event_min); +} + +static ssize_t set_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + long temp; + struct stts751_priv *priv = dev_get_drvdata(dev); + + if (kstrtol(buf, 10, &temp) < 0) + return -EINVAL; + + mutex_lock(&priv->access_lock); + /* HW works in range -64C to +127.937C */ + temp = clamp_val(temp, -64000, priv->event_max); + ret = stts751_set_temp_reg16(priv, temp, + STTS751_REG_LLIM_H, STTS751_REG_LLIM_L); + if (ret) + goto exit; + + dev_dbg(&priv->client->dev, "setting event min %ld", temp); + priv->event_min = temp; + ret = count; +exit: + mutex_unlock(&priv->access_lock); + return ret; +} + +static ssize_t show_interval(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct stts751_priv *priv = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", + stts751_intervals[priv->interval]); +} + +static ssize_t set_interval(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long val; + int idx; + int ret = count; + struct stts751_priv *priv = dev_get_drvdata(dev); + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + + idx = find_closest_descending(val, stts751_intervals, + ARRAY_SIZE(stts751_intervals)); + + dev_dbg(&priv->client->dev, "setting interval. req:%lu, idx: %d, val: %d", + val, idx, stts751_intervals[idx]); + + mutex_lock(&priv->access_lock); + if (priv->interval == idx) + goto exit; + + /* + * In early development stages I've become suspicious about the chip + * starting to misbehave if I ever set, even briefly, an invalid + * configuration. While I'm not sure this is really needed, be + * conservative and set rate/resolution in such an order that avoids + * passing through an invalid configuration. + */ + + /* speed up: lower the resolution, then modify convrate */ + if (priv->interval < idx) { + dev_dbg(&priv->client->dev, "lower resolution, then modify convrate"); + priv->interval = idx; + ret = stts751_adjust_resolution(priv); + if (ret) + goto exit; + } + + ret = i2c_smbus_write_byte_data(priv->client, STTS751_REG_RATE, idx); + if (ret) + goto exit; + /* slow down: modify convrate, then raise resolution */ + if (priv->interval != idx) { + dev_dbg(&priv->client->dev, "modify convrate, then raise resolution"); + priv->interval = idx; + ret = stts751_adjust_resolution(priv); + if (ret) + goto exit; + } + ret = count; +exit: + mutex_unlock(&priv->access_lock); + + return ret; +} + +static int stts751_detect(struct i2c_client *new_client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = new_client->adapter; + const char *name; + int tmp; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + tmp = i2c_smbus_read_byte_data(new_client, STTS751_REG_MAN_ID); + if (tmp != ST_MAN_ID) + return -ENODEV; + + /* lower temperaure registers always have bits 0-3 set to zero */ + tmp = i2c_smbus_read_byte_data(new_client, STTS751_REG_TEMP_L); + if (tmp & 0xf) + return -ENODEV; + + tmp = i2c_smbus_read_byte_data(new_client, STTS751_REG_HLIM_L); + if (tmp & 0xf) + return -ENODEV; + + tmp = i2c_smbus_read_byte_data(new_client, STTS751_REG_LLIM_L); + if (tmp & 0xf) + return -ENODEV; + + /* smbus timeout register always have bits 0-7 set to zero */ + tmp = i2c_smbus_read_byte_data(new_client, STTS751_REG_SMBUS_TO); + if (tmp & 0x7f) + return -ENODEV; + + tmp = i2c_smbus_read_byte_data(new_client, STTS751_REG_PROD_ID); + + switch (tmp) { + case STTS751_0_PROD_ID: + name = "STTS751-0"; + break; + case STTS751_1_PROD_ID: + name = "STTS751-1"; + break; + default: + return -ENODEV; + } + dev_dbg(&new_client->dev, "Chip %s detected", name); + + strlcpy(info->type, stts751_id[0].name, I2C_NAME_SIZE); + return 0; +} + +static int stts751_read_chip_config(struct stts751_priv *priv) +{ + int ret; + int tmp; + + ret = i2c_smbus_read_byte_data(priv->client, STTS751_REG_CONF); + if (ret < 0) + return ret; + priv->config = ret; + priv->res = (ret & STTS751_CONF_RES_MASK) >> STTS751_CONF_RES_SHIFT; + + ret = i2c_smbus_read_byte_data(priv->client, STTS751_REG_RATE); + if (ret < 0) + return ret; + if (ret >= ARRAY_SIZE(stts751_intervals)) { + dev_err(priv->dev, "Unrecognized conversion rate 0x%x\n", ret); + return -ENODEV; + } + priv->interval = ret; + + ret = stts751_read_reg16(priv, &priv->event_max, + STTS751_REG_HLIM_H, STTS751_REG_HLIM_L); + if (ret) + return ret; + + ret = stts751_read_reg16(priv, &priv->event_min, + STTS751_REG_LLIM_H, STTS751_REG_LLIM_L); + if (ret) + return ret; + + ret = stts751_read_reg8(priv, &priv->therm, STTS751_REG_TLIM); + if (ret) + return ret; + + ret = stts751_read_reg8(priv, &tmp, STTS751_REG_HYST); + if (ret) + return ret; + priv->hyst = priv->therm - tmp; + + return 0; +} + +static SENSOR_DEVICE_ATTR(temp1_input, 0444, show_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, 0644, show_min, set_min, 0); +static SENSOR_DEVICE_ATTR(temp1_max, 0644, show_max, set_max, 0); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, 0444, show_min_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, 0444, show_max_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_crit, 0644, show_therm, set_therm, 0); +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, 0644, show_hyst, set_hyst, 0); +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, 0444, show_therm_trip, NULL, 0); +static SENSOR_DEVICE_ATTR(update_interval, 0644, + show_interval, set_interval, 0); + +static struct attribute *stts751_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_update_interval.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(stts751); + +static int stts751_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct stts751_priv *priv; + int ret; + bool smbus_nto; + int rev_id; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + priv->notify_max = true; + priv->notify_min = true; + i2c_set_clientdata(client, priv); + mutex_init(&priv->access_lock); + + if (device_property_present(&client->dev, + "smbus-timeout-disable")) { + smbus_nto = device_property_read_bool(&client->dev, + "smbus-timeout-disable"); + + ret = i2c_smbus_write_byte_data(client, STTS751_REG_SMBUS_TO, + smbus_nto ? 0 : 0x80); + if (ret) + return ret; + } + + rev_id = i2c_smbus_read_byte_data(client, STTS751_REG_REV_ID); + if (rev_id < 0) + return -ENODEV; + if (rev_id != 0x1) { + dev_dbg(&client->dev, "Chip revision 0x%x is untested\n", + rev_id); + } + + ret = stts751_read_chip_config(priv); + if (ret) + return ret; + + priv->config &= ~(STTS751_CONF_STOP | STTS751_CONF_EVENT_DIS); + ret = i2c_smbus_write_byte_data(client, STTS751_REG_CONF, priv->config); + if (ret) + return ret; + + priv->dev = devm_hwmon_device_register_with_groups(&client->dev, + client->name, priv, + stts751_groups); + return PTR_ERR_OR_ZERO(priv->dev); +} + +MODULE_DEVICE_TABLE(i2c, stts751_id); + +static struct i2c_driver stts751_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = DEVNAME, + .of_match_table = of_match_ptr(stts751_of_match), + }, + .probe = stts751_probe, + .id_table = stts751_id, + .detect = stts751_detect, + .alert = stts751_alert, + .address_list = normal_i2c, +}; + +module_i2c_driver(stts751_driver); + +MODULE_AUTHOR("Andrea Merello "); +MODULE_DESCRIPTION("STTS751 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/tc654.c b/drivers/hwmon/tc654.c new file mode 100644 index 000000000..18136e1f9 --- /dev/null +++ b/drivers/hwmon/tc654.c @@ -0,0 +1,514 @@ +/* + * tc654.c - Linux kernel modules for fan speed controller + * + * Copyright (C) 2016 Allied Telesis Labs NZ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum tc654_regs { + TC654_REG_RPM1 = 0x00, /* RPM Output 1 */ + TC654_REG_RPM2 = 0x01, /* RPM Output 2 */ + TC654_REG_FAN_FAULT1 = 0x02, /* Fan Fault 1 Threshold */ + TC654_REG_FAN_FAULT2 = 0x03, /* Fan Fault 2 Threshold */ + TC654_REG_CONFIG = 0x04, /* Configuration */ + TC654_REG_STATUS = 0x05, /* Status */ + TC654_REG_DUTY_CYCLE = 0x06, /* Fan Speed Duty Cycle */ + TC654_REG_MFR_ID = 0x07, /* Manufacturer Identification */ + TC654_REG_VER_ID = 0x08, /* Version Identification */ +}; + +/* Macros to easily index the registers */ +#define TC654_REG_RPM(idx) (TC654_REG_RPM1 + (idx)) +#define TC654_REG_FAN_FAULT(idx) (TC654_REG_FAN_FAULT1 + (idx)) + +/* Config register bits */ +#define TC654_REG_CONFIG_RES BIT(6) /* Resolution Selection */ +#define TC654_REG_CONFIG_DUTYC BIT(5) /* Duty Cycle Control */ +#define TC654_REG_CONFIG_SDM BIT(0) /* Shutdown Mode */ + +/* Status register bits */ +#define TC654_REG_STATUS_F2F BIT(1) /* Fan 2 Fault */ +#define TC654_REG_STATUS_F1F BIT(0) /* Fan 1 Fault */ + +/* RPM resolution for RPM Output registers */ +#define TC654_HIGH_RPM_RESOLUTION 25 /* 25 RPM resolution */ +#define TC654_LOW_RPM_RESOLUTION 50 /* 50 RPM resolution */ + +/* Convert to the fan fault RPM threshold from register value */ +#define TC654_FAN_FAULT_FROM_REG(val) ((val) * 50) /* 50 RPM resolution */ + +/* Convert to register value from the fan fault RPM threshold */ +#define TC654_FAN_FAULT_TO_REG(val) (((val) / 50) & 0xff) + +/* Register data is read (and cached) at most once per second. */ +#define TC654_UPDATE_INTERVAL HZ + +struct tc654_data { + struct i2c_client *client; + + /* update mutex */ + struct mutex update_lock; + + /* tc654 register cache */ + bool valid; + unsigned long last_updated; /* in jiffies */ + + u8 rpm_output[2]; /* The fan RPM data for fans 1 and 2 is then + * written to registers RPM1 and RPM2 + */ + u8 fan_fault[2]; /* The Fan Fault Threshold Registers are used to + * set the fan fault threshold levels for fan 1 + * and fan 2 + */ + u8 config; /* The Configuration Register is an 8-bit read/ + * writable multi-function control register + * 7: Fan Fault Clear + * 1 = Clear Fan Fault + * 0 = Normal Operation (default) + * 6: Resolution Selection for RPM Output Registers + * RPM Output Registers (RPM1 and RPM2) will be + * set for + * 1 = 25 RPM (9-bit) resolution + * 0 = 50 RPM (8-bit) resolution (default) + * 5: Duty Cycle Control Method + * The V OUT duty cycle will be controlled via + * 1 = the SMBus interface. + * 0 = via the V IN analog input pin. (default) + * 4,3: Fan 2 Pulses Per Rotation + * 00 = 1 + * 01 = 2 (default) + * 10 = 4 + * 11 = 8 + * 2,1: Fan 1 Pulses Per Rotation + * 00 = 1 + * 01 = 2 (default) + * 10 = 4 + * 11 = 8 + * 0: Shutdown Mode + * 1 = Shutdown mode. + * 0 = Normal operation. (default) + */ + u8 status; /* The Status register provides all the information + * about what is going on within the TC654/TC655 + * devices. + * 7,6: Unimplemented, Read as '0' + * 5: Over-Temperature Fault Condition + * 1 = Over-Temperature condition has occurred + * 0 = Normal operation. V IN is less than 2.6V + * 4: RPM2 Counter Overflow + * 1 = Fault condition + * 0 = Normal operation + * 3: RPM1 Counter Overflow + * 1 = Fault condition + * 0 = Normal operation + * 2: V IN Input Status + * 1 = V IN is open + * 0 = Normal operation. voltage present at V IN + * 1: Fan 2 Fault + * 1 = Fault condition + * 0 = Normal operation + * 0: Fan 1 Fault + * 1 = Fault condition + * 0 = Normal operation + */ + u8 duty_cycle; /* The DUTY_CYCLE register is a 4-bit read/ + * writable register used to control the duty + * cycle of the V OUT output. + */ +}; + +/* helper to grab and cache data, at most one time per second */ +static struct tc654_data *tc654_update_client(struct device *dev) +{ + struct tc654_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret = 0; + + mutex_lock(&data->update_lock); + if (time_before(jiffies, data->last_updated + TC654_UPDATE_INTERVAL) && + likely(data->valid)) + goto out; + + ret = i2c_smbus_read_byte_data(client, TC654_REG_RPM(0)); + if (ret < 0) + goto out; + data->rpm_output[0] = ret; + + ret = i2c_smbus_read_byte_data(client, TC654_REG_RPM(1)); + if (ret < 0) + goto out; + data->rpm_output[1] = ret; + + ret = i2c_smbus_read_byte_data(client, TC654_REG_FAN_FAULT(0)); + if (ret < 0) + goto out; + data->fan_fault[0] = ret; + + ret = i2c_smbus_read_byte_data(client, TC654_REG_FAN_FAULT(1)); + if (ret < 0) + goto out; + data->fan_fault[1] = ret; + + ret = i2c_smbus_read_byte_data(client, TC654_REG_CONFIG); + if (ret < 0) + goto out; + data->config = ret; + + ret = i2c_smbus_read_byte_data(client, TC654_REG_STATUS); + if (ret < 0) + goto out; + data->status = ret; + + ret = i2c_smbus_read_byte_data(client, TC654_REG_DUTY_CYCLE); + if (ret < 0) + goto out; + data->duty_cycle = ret & 0x0f; + + data->last_updated = jiffies; + data->valid = true; +out: + mutex_unlock(&data->update_lock); + + if (ret < 0) /* upon error, encode it in return value */ + data = ERR_PTR(ret); + + return data; +} + +/* + * sysfs attributes + */ + +static ssize_t show_fan(struct device *dev, struct device_attribute *da, + char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct tc654_data *data = tc654_update_client(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + if (data->config & TC654_REG_CONFIG_RES) + val = data->rpm_output[nr] * TC654_HIGH_RPM_RESOLUTION; + else + val = data->rpm_output[nr] * TC654_LOW_RPM_RESOLUTION; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t show_fan_min(struct device *dev, struct device_attribute *da, + char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct tc654_data *data = tc654_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", + TC654_FAN_FAULT_FROM_REG(data->fan_fault[nr])); +} + +static ssize_t set_fan_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(da)->index; + struct tc654_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + val = clamp_val(val, 0, 12750); + + mutex_lock(&data->update_lock); + + data->fan_fault[nr] = TC654_FAN_FAULT_TO_REG(val); + ret = i2c_smbus_write_byte_data(client, TC654_REG_FAN_FAULT(nr), + data->fan_fault[nr]); + + mutex_unlock(&data->update_lock); + return ret < 0 ? ret : count; +} + +static ssize_t show_fan_alarm(struct device *dev, struct device_attribute *da, + char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct tc654_data *data = tc654_update_client(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + if (nr == 0) + val = !!(data->status & TC654_REG_STATUS_F1F); + else + val = !!(data->status & TC654_REG_STATUS_F2F); + + return sprintf(buf, "%d\n", val); +} + +static const u8 TC654_FAN_PULSE_SHIFT[] = { 1, 3 }; + +static ssize_t show_fan_pulses(struct device *dev, struct device_attribute *da, + char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct tc654_data *data = tc654_update_client(dev); + u8 val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = BIT((data->config >> TC654_FAN_PULSE_SHIFT[nr]) & 0x03); + return sprintf(buf, "%d\n", val); +} + +static ssize_t set_fan_pulses(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(da)->index; + struct tc654_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 config; + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + switch (val) { + case 1: + config = 0; + break; + case 2: + config = 1; + break; + case 4: + config = 2; + break; + case 8: + config = 3; + break; + default: + return -EINVAL; + } + + mutex_lock(&data->update_lock); + + data->config &= ~(0x03 << TC654_FAN_PULSE_SHIFT[nr]); + data->config |= (config << TC654_FAN_PULSE_SHIFT[nr]); + ret = i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config); + + mutex_unlock(&data->update_lock); + return ret < 0 ? ret : count; +} + +static ssize_t show_pwm_mode(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct tc654_data *data = tc654_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", !!(data->config & TC654_REG_CONFIG_DUTYC)); +} + +static ssize_t set_pwm_mode(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct tc654_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + if (val != 0 && val != 1) + return -EINVAL; + + mutex_lock(&data->update_lock); + + if (val) + data->config |= TC654_REG_CONFIG_DUTYC; + else + data->config &= ~TC654_REG_CONFIG_DUTYC; + + ret = i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config); + + mutex_unlock(&data->update_lock); + return ret < 0 ? ret : count; +} + +static const int tc654_pwm_map[16] = { 77, 88, 102, 112, 124, 136, 148, 160, + 172, 184, 196, 207, 219, 231, 243, 255}; + +static ssize_t show_pwm(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct tc654_data *data = tc654_update_client(dev); + int pwm; + + if (IS_ERR(data)) + return PTR_ERR(data); + + if (data->config & TC654_REG_CONFIG_SDM) + pwm = 0; + else + pwm = tc654_pwm_map[data->duty_cycle]; + + return sprintf(buf, "%d\n", pwm); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct tc654_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + if (val > 255) + return -EINVAL; + + mutex_lock(&data->update_lock); + + if (val == 0) + data->config |= TC654_REG_CONFIG_SDM; + else + data->config &= ~TC654_REG_CONFIG_SDM; + + data->duty_cycle = find_closest(val, tc654_pwm_map, + ARRAY_SIZE(tc654_pwm_map)); + + ret = i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config); + if (ret < 0) + goto out; + + ret = i2c_smbus_write_byte_data(client, TC654_REG_DUTY_CYCLE, + data->duty_cycle); + +out: + mutex_unlock(&data->update_lock); + return ret < 0 ? ret : count; +} + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); +static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min, + set_fan_min, 0); +static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min, + set_fan_min, 1); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(fan1_pulses, S_IWUSR | S_IRUGO, show_fan_pulses, + set_fan_pulses, 0); +static SENSOR_DEVICE_ATTR(fan2_pulses, S_IWUSR | S_IRUGO, show_fan_pulses, + set_fan_pulses, 1); +static SENSOR_DEVICE_ATTR(pwm1_mode, S_IWUSR | S_IRUGO, + show_pwm_mode, set_pwm_mode, 0); +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, + set_pwm, 0); + +/* Driver data */ +static struct attribute *tc654_attrs[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_pulses.dev_attr.attr, + &sensor_dev_attr_fan2_pulses.dev_attr.attr, + &sensor_dev_attr_pwm1_mode.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(tc654); + +/* + * device probe and removal + */ + +static int tc654_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct tc654_data *data; + struct device *hwmon_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(struct tc654_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + ret = i2c_smbus_read_byte_data(client, TC654_REG_CONFIG); + if (ret < 0) + return ret; + + data->config = ret; + + hwmon_dev = + devm_hwmon_device_register_with_groups(dev, client->name, data, + tc654_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id tc654_id[] = { + {"tc654", 0}, + {"tc655", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, tc654_id); + +static struct i2c_driver tc654_driver = { + .driver = { + .name = "tc654", + }, + .probe = tc654_probe, + .id_table = tc654_id, +}; + +module_i2c_driver(tc654_driver); + +MODULE_AUTHOR("Allied Telesis Labs"); +MODULE_DESCRIPTION("Microchip TC654/TC655 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/tc74.c b/drivers/hwmon/tc74.c new file mode 100644 index 000000000..d95165158 --- /dev/null +++ b/drivers/hwmon/tc74.c @@ -0,0 +1,177 @@ +/* + * An hwmon driver for the Microchip TC74 + * + * Copyright 2015 Maciej Szmigiero + * + * Based on ad7414.c: + * Copyright 2006 Stefan Roese, DENX Software Engineering + * Copyright 2008 Sean MacLennan, PIKA Technologies + * Copyright 2008 Frank Edelhaeuser, Spansion Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* TC74 registers */ +#define TC74_REG_TEMP 0x00 +#define TC74_REG_CONFIG 0x01 + +struct tc74_data { + struct i2c_client *client; + struct mutex lock; /* atomic read data updates */ + bool valid; /* validity of fields below */ + unsigned long next_update; /* In jiffies */ + s8 temp_input; /* Temp value in dC */ +}; + +static int tc74_update_device(struct device *dev) +{ + struct tc74_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret; + + ret = mutex_lock_interruptible(&data->lock); + if (ret) + return ret; + + if (time_after(jiffies, data->next_update) || !data->valid) { + s32 value; + + value = i2c_smbus_read_byte_data(client, TC74_REG_CONFIG); + if (value < 0) { + dev_dbg(&client->dev, "TC74_REG_CONFIG read err %d\n", + (int)value); + + ret = value; + goto ret_unlock; + } + + if (!(value & BIT(6))) { + /* not ready yet */ + + ret = -EAGAIN; + goto ret_unlock; + } + + value = i2c_smbus_read_byte_data(client, TC74_REG_TEMP); + if (value < 0) { + dev_dbg(&client->dev, "TC74_REG_TEMP read err %d\n", + (int)value); + + ret = value; + goto ret_unlock; + } + + data->temp_input = value; + data->next_update = jiffies + HZ / 4; + data->valid = true; + } + +ret_unlock: + mutex_unlock(&data->lock); + + return ret; +} + +static ssize_t show_temp_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tc74_data *data = dev_get_drvdata(dev); + int ret; + + ret = tc74_update_device(dev); + if (ret) + return ret; + + return sprintf(buf, "%d\n", data->temp_input * 1000); +} +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL, 0); + +static struct attribute *tc74_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(tc74); + +static int tc74_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + struct device *dev = &client->dev; + struct tc74_data *data; + struct device *hwmon_dev; + s32 conf; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EOPNOTSUPP; + + data = devm_kzalloc(dev, sizeof(struct tc74_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->lock); + + /* Make sure the chip is powered up. */ + conf = i2c_smbus_read_byte_data(client, TC74_REG_CONFIG); + if (conf < 0) { + dev_err(dev, "unable to read config register\n"); + + return conf; + } + + if (conf & 0x3f) { + dev_err(dev, "invalid config register value\n"); + + return -ENODEV; + } + + if (conf & BIT(7)) { + s32 ret; + + conf &= ~BIT(7); + + ret = i2c_smbus_write_byte_data(client, TC74_REG_CONFIG, conf); + if (ret) + dev_warn(dev, "unable to disable STANDBY\n"); + } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + client->name, + data, tc74_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id tc74_id[] = { + { "tc74", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, tc74_id); + +static struct i2c_driver tc74_driver = { + .driver = { + .name = "tc74", + }, + .probe = tc74_probe, + .id_table = tc74_id, +}; + +module_i2c_driver(tc74_driver); + +MODULE_AUTHOR("Maciej Szmigiero "); + +MODULE_DESCRIPTION("TC74 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/thmc50.c b/drivers/hwmon/thmc50.c new file mode 100644 index 000000000..6a0ee9031 --- /dev/null +++ b/drivers/hwmon/thmc50.c @@ -0,0 +1,445 @@ +/* + * thmc50.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 2007 Krzysztof Helt + * Based on 2.4 driver by Frodo Looijaard and + * Philip Edelbrock + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END }; + +/* Insmod parameters */ +enum chips { thmc50, adm1022 }; + +static unsigned short adm1022_temp3[16]; +static unsigned int adm1022_temp3_num; +module_param_array(adm1022_temp3, ushort, &adm1022_temp3_num, 0); +MODULE_PARM_DESC(adm1022_temp3, + "List of adapter,address pairs to enable 3rd temperature (ADM1022 only)"); + +/* Many THMC50 constants specified below */ + +/* The THMC50 registers */ +#define THMC50_REG_CONF 0x40 +#define THMC50_REG_COMPANY_ID 0x3E +#define THMC50_REG_DIE_CODE 0x3F +#define THMC50_REG_ANALOG_OUT 0x19 +/* + * The mirror status register cannot be used as + * reading it does not clear alarms. + */ +#define THMC50_REG_INTR 0x41 + +static const u8 THMC50_REG_TEMP[] = { 0x27, 0x26, 0x20 }; +static const u8 THMC50_REG_TEMP_MIN[] = { 0x3A, 0x38, 0x2C }; +static const u8 THMC50_REG_TEMP_MAX[] = { 0x39, 0x37, 0x2B }; +static const u8 THMC50_REG_TEMP_CRITICAL[] = { 0x13, 0x14, 0x14 }; +static const u8 THMC50_REG_TEMP_DEFAULT[] = { 0x17, 0x18, 0x18 }; + +#define THMC50_REG_CONF_nFANOFF 0x20 +#define THMC50_REG_CONF_PROGRAMMED 0x08 + +/* Each client has this additional data */ +struct thmc50_data { + struct i2c_client *client; + const struct attribute_group *groups[3]; + + struct mutex update_lock; + enum chips type; + unsigned long last_updated; /* In jiffies */ + char has_temp3; /* !=0 if it is ADM1022 in temp3 mode */ + char valid; /* !=0 if following fields are valid */ + + /* Register values */ + s8 temp_input[3]; + s8 temp_max[3]; + s8 temp_min[3]; + s8 temp_critical[3]; + u8 analog_out; + u8 alarms; +}; + +static struct thmc50_data *thmc50_update_device(struct device *dev) +{ + struct thmc50_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int timeout = HZ / 5 + (data->type == thmc50 ? HZ : 0); + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + timeout) + || !data->valid) { + + int temps = data->has_temp3 ? 3 : 2; + int i; + int prog = i2c_smbus_read_byte_data(client, THMC50_REG_CONF); + + prog &= THMC50_REG_CONF_PROGRAMMED; + + for (i = 0; i < temps; i++) { + data->temp_input[i] = i2c_smbus_read_byte_data(client, + THMC50_REG_TEMP[i]); + data->temp_max[i] = i2c_smbus_read_byte_data(client, + THMC50_REG_TEMP_MAX[i]); + data->temp_min[i] = i2c_smbus_read_byte_data(client, + THMC50_REG_TEMP_MIN[i]); + data->temp_critical[i] = + i2c_smbus_read_byte_data(client, + prog ? THMC50_REG_TEMP_CRITICAL[i] + : THMC50_REG_TEMP_DEFAULT[i]); + } + data->analog_out = + i2c_smbus_read_byte_data(client, THMC50_REG_ANALOG_OUT); + data->alarms = + i2c_smbus_read_byte_data(client, THMC50_REG_INTR); + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static ssize_t show_analog_out(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct thmc50_data *data = thmc50_update_device(dev); + return sprintf(buf, "%d\n", data->analog_out); +} + +static ssize_t set_analog_out(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct thmc50_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int config; + unsigned long tmp; + int err; + + err = kstrtoul(buf, 10, &tmp); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->analog_out = clamp_val(tmp, 0, 255); + i2c_smbus_write_byte_data(client, THMC50_REG_ANALOG_OUT, + data->analog_out); + + config = i2c_smbus_read_byte_data(client, THMC50_REG_CONF); + if (data->analog_out == 0) + config &= ~THMC50_REG_CONF_nFANOFF; + else + config |= THMC50_REG_CONF_nFANOFF; + i2c_smbus_write_byte_data(client, THMC50_REG_CONF, config); + + mutex_unlock(&data->update_lock); + return count; +} + +/* There is only one PWM mode = DC */ +static ssize_t show_pwm_mode(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "0\n"); +} + +/* Temperatures */ +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct thmc50_data *data = thmc50_update_device(dev); + return sprintf(buf, "%d\n", data->temp_input[nr] * 1000); +} + +static ssize_t show_temp_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct thmc50_data *data = thmc50_update_device(dev); + return sprintf(buf, "%d\n", data->temp_min[nr] * 1000); +} + +static ssize_t set_temp_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct thmc50_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_min[nr] = clamp_val(val / 1000, -128, 127); + i2c_smbus_write_byte_data(client, THMC50_REG_TEMP_MIN[nr], + data->temp_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct thmc50_data *data = thmc50_update_device(dev); + return sprintf(buf, "%d\n", data->temp_max[nr] * 1000); +} + +static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct thmc50_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_max[nr] = clamp_val(val / 1000, -128, 127); + i2c_smbus_write_byte_data(client, THMC50_REG_TEMP_MAX[nr], + data->temp_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp_critical(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct thmc50_data *data = thmc50_update_device(dev); + return sprintf(buf, "%d\n", data->temp_critical[nr] * 1000); +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + struct thmc50_data *data = thmc50_update_device(dev); + + return sprintf(buf, "%u\n", (data->alarms >> index) & 1); +} + +#define temp_reg(offset) \ +static SENSOR_DEVICE_ATTR(temp##offset##_input, S_IRUGO, show_temp, \ + NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_min, S_IRUGO | S_IWUSR, \ + show_temp_min, set_temp_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max, S_IRUGO | S_IWUSR, \ + show_temp_max, set_temp_max, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_crit, S_IRUGO, \ + show_temp_critical, NULL, offset - 1); + +temp_reg(1); +temp_reg(2); +temp_reg(3); + +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_alarm, NULL, 2); + +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_analog_out, + set_analog_out, 0); +static SENSOR_DEVICE_ATTR(pwm1_mode, S_IRUGO, show_pwm_mode, NULL, 0); + +static struct attribute *thmc50_attributes[] = { + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_mode.dev_attr.attr, + NULL +}; + +static const struct attribute_group thmc50_group = { + .attrs = thmc50_attributes, +}; + +/* for ADM1022 3rd temperature mode */ +static struct attribute *temp3_attributes[] = { + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + NULL +}; + +static const struct attribute_group temp3_group = { + .attrs = temp3_attributes, +}; + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int thmc50_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + unsigned company; + unsigned revision; + unsigned config; + struct i2c_adapter *adapter = client->adapter; + const char *type_name; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + pr_debug("thmc50: detect failed, smbus byte data not supported!\n"); + return -ENODEV; + } + + pr_debug("thmc50: Probing for THMC50 at 0x%2X on bus %d\n", + client->addr, i2c_adapter_id(client->adapter)); + + company = i2c_smbus_read_byte_data(client, THMC50_REG_COMPANY_ID); + revision = i2c_smbus_read_byte_data(client, THMC50_REG_DIE_CODE); + config = i2c_smbus_read_byte_data(client, THMC50_REG_CONF); + if (revision < 0xc0 || (config & 0x10)) + return -ENODEV; + + if (company == 0x41) { + int id = i2c_adapter_id(client->adapter); + int i; + + type_name = "adm1022"; + for (i = 0; i + 1 < adm1022_temp3_num; i += 2) + if (adm1022_temp3[i] == id && + adm1022_temp3[i + 1] == client->addr) { + /* enable 2nd remote temp */ + config |= (1 << 7); + i2c_smbus_write_byte_data(client, + THMC50_REG_CONF, + config); + break; + } + } else if (company == 0x49) { + type_name = "thmc50"; + } else { + pr_debug("thmc50: Detection of THMC50/ADM1022 failed\n"); + return -ENODEV; + } + + pr_debug("thmc50: Detected %s (version %x, revision %x)\n", + type_name, (revision >> 4) - 0xc, revision & 0xf); + + strlcpy(info->type, type_name, I2C_NAME_SIZE); + + return 0; +} + +static void thmc50_init_client(struct thmc50_data *data) +{ + struct i2c_client *client = data->client; + int config; + + data->analog_out = i2c_smbus_read_byte_data(client, + THMC50_REG_ANALOG_OUT); + /* set up to at least 1 */ + if (data->analog_out == 0) { + data->analog_out = 1; + i2c_smbus_write_byte_data(client, THMC50_REG_ANALOG_OUT, + data->analog_out); + } + config = i2c_smbus_read_byte_data(client, THMC50_REG_CONF); + config |= 0x1; /* start the chip if it is in standby mode */ + if (data->type == adm1022 && (config & (1 << 7))) + data->has_temp3 = 1; + i2c_smbus_write_byte_data(client, THMC50_REG_CONF, config); +} + +static int thmc50_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct thmc50_data *data; + struct device *hwmon_dev; + int idx = 0; + + data = devm_kzalloc(dev, sizeof(struct thmc50_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + data->type = id->driver_data; + mutex_init(&data->update_lock); + + thmc50_init_client(data); + + /* sysfs hooks */ + data->groups[idx++] = &thmc50_group; + + /* Register additional ADM1022 sysfs hooks */ + if (data->has_temp3) + data->groups[idx++] = &temp3_group; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id thmc50_id[] = { + { "adm1022", adm1022 }, + { "thmc50", thmc50 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, thmc50_id); + +static struct i2c_driver thmc50_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "thmc50", + }, + .probe = thmc50_probe, + .id_table = thmc50_id, + .detect = thmc50_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(thmc50_driver); + +MODULE_AUTHOR("Krzysztof Helt "); +MODULE_DESCRIPTION("THMC50 driver"); diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c new file mode 100644 index 000000000..dfc40c740 --- /dev/null +++ b/drivers/hwmon/tmp102.c @@ -0,0 +1,341 @@ +/* Texas Instruments TMP102 SMBus temperature sensor driver + * + * Copyright (C) 2010 Steven King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "tmp102" + +#define TMP102_TEMP_REG 0x00 +#define TMP102_CONF_REG 0x01 +/* note: these bit definitions are byte swapped */ +#define TMP102_CONF_SD 0x0100 +#define TMP102_CONF_TM 0x0200 +#define TMP102_CONF_POL 0x0400 +#define TMP102_CONF_F0 0x0800 +#define TMP102_CONF_F1 0x1000 +#define TMP102_CONF_R0 0x2000 +#define TMP102_CONF_R1 0x4000 +#define TMP102_CONF_OS 0x8000 +#define TMP102_CONF_EM 0x0010 +#define TMP102_CONF_AL 0x0020 +#define TMP102_CONF_CR0 0x0040 +#define TMP102_CONF_CR1 0x0080 +#define TMP102_TLOW_REG 0x02 +#define TMP102_THIGH_REG 0x03 + +#define TMP102_CONFREG_MASK (TMP102_CONF_SD | TMP102_CONF_TM | \ + TMP102_CONF_POL | TMP102_CONF_F0 | \ + TMP102_CONF_F1 | TMP102_CONF_OS | \ + TMP102_CONF_EM | TMP102_CONF_AL | \ + TMP102_CONF_CR0 | TMP102_CONF_CR1) + +#define TMP102_CONFIG_CLEAR (TMP102_CONF_SD | TMP102_CONF_OS | \ + TMP102_CONF_CR0) +#define TMP102_CONFIG_SET (TMP102_CONF_TM | TMP102_CONF_EM | \ + TMP102_CONF_CR1) + +#define CONVERSION_TIME_MS 35 /* in milli-seconds */ + +struct tmp102 { + struct regmap *regmap; + u16 config_orig; + unsigned long ready_time; +}; + +/* convert left adjusted 13-bit TMP102 register value to milliCelsius */ +static inline int tmp102_reg_to_mC(s16 val) +{ + return ((val & ~0x01) * 1000) / 128; +} + +/* convert milliCelsius to left adjusted 13-bit TMP102 register value */ +static inline u16 tmp102_mC_to_reg(int val) +{ + return (val * 128) / 1000; +} + +static int tmp102_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *temp) +{ + struct tmp102 *tmp102 = dev_get_drvdata(dev); + unsigned int regval; + int err, reg; + + switch (attr) { + case hwmon_temp_input: + /* Is it too early to return a conversion ? */ + if (time_before(jiffies, tmp102->ready_time)) { + dev_dbg(dev, "%s: Conversion not ready yet..\n", __func__); + return -EAGAIN; + } + reg = TMP102_TEMP_REG; + break; + case hwmon_temp_max_hyst: + reg = TMP102_TLOW_REG; + break; + case hwmon_temp_max: + reg = TMP102_THIGH_REG; + break; + default: + return -EOPNOTSUPP; + } + + err = regmap_read(tmp102->regmap, reg, ®val); + if (err < 0) + return err; + *temp = tmp102_reg_to_mC(regval); + + return 0; +} + +static int tmp102_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long temp) +{ + struct tmp102 *tmp102 = dev_get_drvdata(dev); + int reg; + + switch (attr) { + case hwmon_temp_max_hyst: + reg = TMP102_TLOW_REG; + break; + case hwmon_temp_max: + reg = TMP102_THIGH_REG; + break; + default: + return -EOPNOTSUPP; + } + + temp = clamp_val(temp, -256000, 255000); + return regmap_write(tmp102->regmap, reg, tmp102_mC_to_reg(temp)); +} + +static umode_t tmp102_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type != hwmon_temp) + return 0; + + switch (attr) { + case hwmon_temp_input: + return S_IRUGO; + case hwmon_temp_max_hyst: + case hwmon_temp_max: + return S_IRUGO | S_IWUSR; + default: + return 0; + } +} + +static u32 tmp102_chip_config[] = { + HWMON_C_REGISTER_TZ, + 0 +}; + +static const struct hwmon_channel_info tmp102_chip = { + .type = hwmon_chip, + .config = tmp102_chip_config, +}; + +static u32 tmp102_temp_config[] = { + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST, + 0 +}; + +static const struct hwmon_channel_info tmp102_temp = { + .type = hwmon_temp, + .config = tmp102_temp_config, +}; + +static const struct hwmon_channel_info *tmp102_info[] = { + &tmp102_chip, + &tmp102_temp, + NULL +}; + +static const struct hwmon_ops tmp102_hwmon_ops = { + .is_visible = tmp102_is_visible, + .read = tmp102_read, + .write = tmp102_write, +}; + +static const struct hwmon_chip_info tmp102_chip_info = { + .ops = &tmp102_hwmon_ops, + .info = tmp102_info, +}; + +static void tmp102_restore_config(void *data) +{ + struct tmp102 *tmp102 = data; + + regmap_write(tmp102->regmap, TMP102_CONF_REG, tmp102->config_orig); +} + +static bool tmp102_is_writeable_reg(struct device *dev, unsigned int reg) +{ + return reg != TMP102_TEMP_REG; +} + +static bool tmp102_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg == TMP102_TEMP_REG; +} + +static const struct regmap_config tmp102_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = TMP102_THIGH_REG, + .writeable_reg = tmp102_is_writeable_reg, + .volatile_reg = tmp102_is_volatile_reg, + .val_format_endian = REGMAP_ENDIAN_BIG, + .cache_type = REGCACHE_RBTREE, + .use_single_rw = true, +}; + +static int tmp102_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct tmp102 *tmp102; + unsigned int regval; + int err; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA)) { + dev_err(dev, + "adapter doesn't support SMBus word transactions\n"); + return -ENODEV; + } + + tmp102 = devm_kzalloc(dev, sizeof(*tmp102), GFP_KERNEL); + if (!tmp102) + return -ENOMEM; + + i2c_set_clientdata(client, tmp102); + + tmp102->regmap = devm_regmap_init_i2c(client, &tmp102_regmap_config); + if (IS_ERR(tmp102->regmap)) + return PTR_ERR(tmp102->regmap); + + err = regmap_read(tmp102->regmap, TMP102_CONF_REG, ®val); + if (err < 0) { + dev_err(dev, "error reading config register\n"); + return err; + } + + if ((regval & ~TMP102_CONFREG_MASK) != + (TMP102_CONF_R0 | TMP102_CONF_R1)) { + dev_err(dev, "unexpected config register value\n"); + return -ENODEV; + } + + tmp102->config_orig = regval; + + err = devm_add_action_or_reset(dev, tmp102_restore_config, tmp102); + if (err) + return err; + + regval &= ~TMP102_CONFIG_CLEAR; + regval |= TMP102_CONFIG_SET; + + err = regmap_write(tmp102->regmap, TMP102_CONF_REG, regval); + if (err < 0) { + dev_err(dev, "error writing config register\n"); + return err; + } + + /* + * Mark that we are not ready with data until the first + * conversion is complete + */ + tmp102->ready_time = jiffies + msecs_to_jiffies(CONVERSION_TIME_MS); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + tmp102, + &tmp102_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) { + dev_dbg(dev, "unable to register hwmon device\n"); + return PTR_ERR(hwmon_dev); + } + dev_info(dev, "initialized\n"); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tmp102_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tmp102 *tmp102 = i2c_get_clientdata(client); + + return regmap_update_bits(tmp102->regmap, TMP102_CONF_REG, + TMP102_CONF_SD, TMP102_CONF_SD); +} + +static int tmp102_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tmp102 *tmp102 = i2c_get_clientdata(client); + int err; + + err = regmap_update_bits(tmp102->regmap, TMP102_CONF_REG, + TMP102_CONF_SD, 0); + + tmp102->ready_time = jiffies + msecs_to_jiffies(CONVERSION_TIME_MS); + + return err; +} +#endif /* CONFIG_PM */ + +static SIMPLE_DEV_PM_OPS(tmp102_dev_pm_ops, tmp102_suspend, tmp102_resume); + +static const struct i2c_device_id tmp102_id[] = { + { "tmp102", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tmp102_id); + +static const struct of_device_id tmp102_of_match[] = { + { .compatible = "ti,tmp102" }, + { }, +}; +MODULE_DEVICE_TABLE(of, tmp102_of_match); + +static struct i2c_driver tmp102_driver = { + .driver.name = DRIVER_NAME, + .driver.of_match_table = of_match_ptr(tmp102_of_match), + .driver.pm = &tmp102_dev_pm_ops, + .probe = tmp102_probe, + .id_table = tmp102_id, +}; + +module_i2c_driver(tmp102_driver); + +MODULE_AUTHOR("Steven King "); +MODULE_DESCRIPTION("Texas Instruments TMP102 temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/tmp103.c b/drivers/hwmon/tmp103.c new file mode 100644 index 000000000..7f85b1454 --- /dev/null +++ b/drivers/hwmon/tmp103.c @@ -0,0 +1,197 @@ +/* + * Texas Instruments TMP103 SMBus temperature sensor driver + * Copyright (C) 2014 Heiko Schocher + * + * Based on: + * Texas Instruments TMP102 SMBus temperature sensor driver + * + * Copyright (C) 2010 Steven King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TMP103_TEMP_REG 0x00 +#define TMP103_CONF_REG 0x01 +#define TMP103_TLOW_REG 0x02 +#define TMP103_THIGH_REG 0x03 + +#define TMP103_CONF_M0 0x01 +#define TMP103_CONF_M1 0x02 +#define TMP103_CONF_LC 0x04 +#define TMP103_CONF_FL 0x08 +#define TMP103_CONF_FH 0x10 +#define TMP103_CONF_CR0 0x20 +#define TMP103_CONF_CR1 0x40 +#define TMP103_CONF_ID 0x80 +#define TMP103_CONF_SD (TMP103_CONF_M1) +#define TMP103_CONF_SD_MASK (TMP103_CONF_M0 | TMP103_CONF_M1) + +#define TMP103_CONFIG (TMP103_CONF_CR1 | TMP103_CONF_M1) +#define TMP103_CONFIG_MASK (TMP103_CONF_CR0 | TMP103_CONF_CR1 | \ + TMP103_CONF_M0 | TMP103_CONF_M1) + +static inline int tmp103_reg_to_mc(s8 val) +{ + return val * 1000; +} + +static inline u8 tmp103_mc_to_reg(int val) +{ + return DIV_ROUND_CLOSEST(val, 1000); +} + +static ssize_t tmp103_show_temp(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sda = to_sensor_dev_attr(attr); + struct regmap *regmap = dev_get_drvdata(dev); + unsigned int regval; + int ret; + + ret = regmap_read(regmap, sda->index, ®val); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", tmp103_reg_to_mc(regval)); +} + +static ssize_t tmp103_set_temp(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sda = to_sensor_dev_attr(attr); + struct regmap *regmap = dev_get_drvdata(dev); + long val; + int ret; + + if (kstrtol(buf, 10, &val) < 0) + return -EINVAL; + + val = clamp_val(val, -55000, 127000); + ret = regmap_write(regmap, sda->index, tmp103_mc_to_reg(val)); + return ret ? ret : count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, tmp103_show_temp, NULL , + TMP103_TEMP_REG); + +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, tmp103_show_temp, + tmp103_set_temp, TMP103_TLOW_REG); + +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, tmp103_show_temp, + tmp103_set_temp, TMP103_THIGH_REG); + +static struct attribute *tmp103_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(tmp103); + +static bool tmp103_regmap_is_volatile(struct device *dev, unsigned int reg) +{ + return reg == TMP103_TEMP_REG; +} + +static const struct regmap_config tmp103_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = TMP103_THIGH_REG, + .volatile_reg = tmp103_regmap_is_volatile, +}; + +static int tmp103_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(client, &tmp103_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(regmap); + } + + ret = regmap_update_bits(regmap, TMP103_CONF_REG, TMP103_CONFIG_MASK, + TMP103_CONFIG); + if (ret < 0) { + dev_err(&client->dev, "error writing config register\n"); + return ret; + } + + i2c_set_clientdata(client, regmap); + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + regmap, tmp103_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static int __maybe_unused tmp103_suspend(struct device *dev) +{ + struct regmap *regmap = dev_get_drvdata(dev); + + return regmap_update_bits(regmap, TMP103_CONF_REG, + TMP103_CONF_SD_MASK, 0); +} + +static int __maybe_unused tmp103_resume(struct device *dev) +{ + struct regmap *regmap = dev_get_drvdata(dev); + + return regmap_update_bits(regmap, TMP103_CONF_REG, + TMP103_CONF_SD_MASK, TMP103_CONF_SD); +} + +static SIMPLE_DEV_PM_OPS(tmp103_dev_pm_ops, tmp103_suspend, tmp103_resume); + +static const struct i2c_device_id tmp103_id[] = { + { "tmp103", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tmp103_id); + +static const struct of_device_id tmp103_of_match[] = { + { .compatible = "ti,tmp103" }, + { }, +}; +MODULE_DEVICE_TABLE(of, tmp103_of_match); + +static struct i2c_driver tmp103_driver = { + .driver = { + .name = "tmp103", + .of_match_table = of_match_ptr(tmp103_of_match), + .pm = &tmp103_dev_pm_ops, + }, + .probe = tmp103_probe, + .id_table = tmp103_id, +}; + +module_i2c_driver(tmp103_driver); + +MODULE_AUTHOR("Heiko Schocher "); +MODULE_DESCRIPTION("Texas Instruments TMP103 temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/tmp108.c b/drivers/hwmon/tmp108.c new file mode 100644 index 000000000..91bb94639 --- /dev/null +++ b/drivers/hwmon/tmp108.c @@ -0,0 +1,469 @@ +/* Texas Instruments TMP108 SMBus temperature sensor driver + * + * Copyright (C) 2016 John Muir + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "tmp108" + +#define TMP108_REG_TEMP 0x00 +#define TMP108_REG_CONF 0x01 +#define TMP108_REG_TLOW 0x02 +#define TMP108_REG_THIGH 0x03 + +#define TMP108_TEMP_MIN_MC -50000 /* Minimum millicelcius. */ +#define TMP108_TEMP_MAX_MC 127937 /* Maximum millicelcius. */ + +/* Configuration register bits. + * Note: these bit definitions are byte swapped. + */ +#define TMP108_CONF_M0 0x0100 /* Sensor mode. */ +#define TMP108_CONF_M1 0x0200 +#define TMP108_CONF_TM 0x0400 /* Thermostat mode. */ +#define TMP108_CONF_FL 0x0800 /* Watchdog flag - TLOW */ +#define TMP108_CONF_FH 0x1000 /* Watchdog flag - THIGH */ +#define TMP108_CONF_CR0 0x2000 /* Conversion rate. */ +#define TMP108_CONF_CR1 0x4000 +#define TMP108_CONF_ID 0x8000 +#define TMP108_CONF_HYS0 0x0010 /* Hysteresis. */ +#define TMP108_CONF_HYS1 0x0020 +#define TMP108_CONF_POL 0x0080 /* Polarity of alert. */ + +/* Defaults set by the hardware upon reset. */ +#define TMP108_CONF_DEFAULTS (TMP108_CONF_CR0 | TMP108_CONF_TM |\ + TMP108_CONF_HYS0 | TMP108_CONF_M1) +/* These bits are read-only. */ +#define TMP108_CONF_READ_ONLY (TMP108_CONF_FL | TMP108_CONF_FH |\ + TMP108_CONF_ID) + +#define TMP108_CONF_MODE_MASK (TMP108_CONF_M0|TMP108_CONF_M1) +#define TMP108_MODE_SHUTDOWN 0x0000 +#define TMP108_MODE_ONE_SHOT TMP108_CONF_M0 +#define TMP108_MODE_CONTINUOUS TMP108_CONF_M1 /* Default */ + /* When M1 is set, M0 is ignored. */ + +#define TMP108_CONF_CONVRATE_MASK (TMP108_CONF_CR0|TMP108_CONF_CR1) +#define TMP108_CONVRATE_0P25HZ 0x0000 +#define TMP108_CONVRATE_1HZ TMP108_CONF_CR0 /* Default */ +#define TMP108_CONVRATE_4HZ TMP108_CONF_CR1 +#define TMP108_CONVRATE_16HZ (TMP108_CONF_CR0|TMP108_CONF_CR1) + +#define TMP108_CONF_HYSTERESIS_MASK (TMP108_CONF_HYS0|TMP108_CONF_HYS1) +#define TMP108_HYSTERESIS_0C 0x0000 +#define TMP108_HYSTERESIS_1C TMP108_CONF_HYS0 /* Default */ +#define TMP108_HYSTERESIS_2C TMP108_CONF_HYS1 +#define TMP108_HYSTERESIS_4C (TMP108_CONF_HYS0|TMP108_CONF_HYS1) + +#define TMP108_CONVERSION_TIME_MS 30 /* in milli-seconds */ + +struct tmp108 { + struct regmap *regmap; + u16 orig_config; + unsigned long ready_time; +}; + +/* convert 12-bit TMP108 register value to milliCelsius */ +static inline int tmp108_temp_reg_to_mC(s16 val) +{ + return (val & ~0x0f) * 1000 / 256; +} + +/* convert milliCelsius to left adjusted 12-bit TMP108 register value */ +static inline u16 tmp108_mC_to_temp_reg(int val) +{ + return (val * 256) / 1000; +} + +static int tmp108_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *temp) +{ + struct tmp108 *tmp108 = dev_get_drvdata(dev); + unsigned int regval; + int err, hyst; + + if (type == hwmon_chip) { + if (attr == hwmon_chip_update_interval) { + err = regmap_read(tmp108->regmap, TMP108_REG_CONF, + ®val); + if (err < 0) + return err; + switch (regval & TMP108_CONF_CONVRATE_MASK) { + case TMP108_CONVRATE_0P25HZ: + default: + *temp = 4000; + break; + case TMP108_CONVRATE_1HZ: + *temp = 1000; + break; + case TMP108_CONVRATE_4HZ: + *temp = 250; + break; + case TMP108_CONVRATE_16HZ: + *temp = 63; + break; + } + return 0; + } + return -EOPNOTSUPP; + } + + switch (attr) { + case hwmon_temp_input: + /* Is it too early to return a conversion ? */ + if (time_before(jiffies, tmp108->ready_time)) { + dev_dbg(dev, "%s: Conversion not ready yet..\n", + __func__); + return -EAGAIN; + } + err = regmap_read(tmp108->regmap, TMP108_REG_TEMP, ®val); + if (err < 0) + return err; + *temp = tmp108_temp_reg_to_mC(regval); + break; + case hwmon_temp_min: + case hwmon_temp_max: + err = regmap_read(tmp108->regmap, attr == hwmon_temp_min ? + TMP108_REG_TLOW : TMP108_REG_THIGH, ®val); + if (err < 0) + return err; + *temp = tmp108_temp_reg_to_mC(regval); + break; + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + err = regmap_read(tmp108->regmap, TMP108_REG_CONF, ®val); + if (err < 0) + return err; + *temp = !!(regval & (attr == hwmon_temp_min_alarm ? + TMP108_CONF_FL : TMP108_CONF_FH)); + break; + case hwmon_temp_min_hyst: + case hwmon_temp_max_hyst: + err = regmap_read(tmp108->regmap, TMP108_REG_CONF, ®val); + if (err < 0) + return err; + switch (regval & TMP108_CONF_HYSTERESIS_MASK) { + case TMP108_HYSTERESIS_0C: + default: + hyst = 0; + break; + case TMP108_HYSTERESIS_1C: + hyst = 1000; + break; + case TMP108_HYSTERESIS_2C: + hyst = 2000; + break; + case TMP108_HYSTERESIS_4C: + hyst = 4000; + break; + } + err = regmap_read(tmp108->regmap, attr == hwmon_temp_min_hyst ? + TMP108_REG_TLOW : TMP108_REG_THIGH, ®val); + if (err < 0) + return err; + *temp = tmp108_temp_reg_to_mC(regval); + if (attr == hwmon_temp_min_hyst) + *temp += hyst; + else + *temp -= hyst; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int tmp108_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long temp) +{ + struct tmp108 *tmp108 = dev_get_drvdata(dev); + u32 regval, mask; + int err; + + if (type == hwmon_chip) { + if (attr == hwmon_chip_update_interval) { + if (temp < 156) + mask = TMP108_CONVRATE_16HZ; + else if (temp < 625) + mask = TMP108_CONVRATE_4HZ; + else if (temp < 2500) + mask = TMP108_CONVRATE_1HZ; + else + mask = TMP108_CONVRATE_0P25HZ; + return regmap_update_bits(tmp108->regmap, + TMP108_REG_CONF, + TMP108_CONF_CONVRATE_MASK, + mask); + } + return -EOPNOTSUPP; + } + + switch (attr) { + case hwmon_temp_min: + case hwmon_temp_max: + temp = clamp_val(temp, TMP108_TEMP_MIN_MC, TMP108_TEMP_MAX_MC); + return regmap_write(tmp108->regmap, + attr == hwmon_temp_min ? + TMP108_REG_TLOW : TMP108_REG_THIGH, + tmp108_mC_to_temp_reg(temp)); + case hwmon_temp_min_hyst: + case hwmon_temp_max_hyst: + temp = clamp_val(temp, TMP108_TEMP_MIN_MC, TMP108_TEMP_MAX_MC); + err = regmap_read(tmp108->regmap, + attr == hwmon_temp_min_hyst ? + TMP108_REG_TLOW : TMP108_REG_THIGH, + ®val); + if (err < 0) + return err; + if (attr == hwmon_temp_min_hyst) + temp -= tmp108_temp_reg_to_mC(regval); + else + temp = tmp108_temp_reg_to_mC(regval) - temp; + if (temp < 500) + mask = TMP108_HYSTERESIS_0C; + else if (temp < 1500) + mask = TMP108_HYSTERESIS_1C; + else if (temp < 3000) + mask = TMP108_HYSTERESIS_2C; + else + mask = TMP108_HYSTERESIS_4C; + return regmap_update_bits(tmp108->regmap, TMP108_REG_CONF, + TMP108_CONF_HYSTERESIS_MASK, mask); + default: + return -EOPNOTSUPP; + } +} + +static umode_t tmp108_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type == hwmon_chip && attr == hwmon_chip_update_interval) + return 0644; + + if (type != hwmon_temp) + return 0; + + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + return 0444; + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_min_hyst: + case hwmon_temp_max_hyst: + return 0644; + default: + return 0; + } +} + +static u32 tmp108_chip_config[] = { + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL, + 0 +}; + +static const struct hwmon_channel_info tmp108_chip = { + .type = hwmon_chip, + .config = tmp108_chip_config, +}; + +static u32 tmp108_temp_config[] = { + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_MIN_HYST + | HWMON_T_MAX_HYST | HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM, + 0 +}; + +static const struct hwmon_channel_info tmp108_temp = { + .type = hwmon_temp, + .config = tmp108_temp_config, +}; + +static const struct hwmon_channel_info *tmp108_info[] = { + &tmp108_chip, + &tmp108_temp, + NULL +}; + +static const struct hwmon_ops tmp108_hwmon_ops = { + .is_visible = tmp108_is_visible, + .read = tmp108_read, + .write = tmp108_write, +}; + +static const struct hwmon_chip_info tmp108_chip_info = { + .ops = &tmp108_hwmon_ops, + .info = tmp108_info, +}; + +static void tmp108_restore_config(void *data) +{ + struct tmp108 *tmp108 = data; + + regmap_write(tmp108->regmap, TMP108_REG_CONF, tmp108->orig_config); +} + +static bool tmp108_is_writeable_reg(struct device *dev, unsigned int reg) +{ + return reg != TMP108_REG_TEMP; +} + +static bool tmp108_is_volatile_reg(struct device *dev, unsigned int reg) +{ + /* Configuration register must be volatile to enable FL and FH. */ + return reg == TMP108_REG_TEMP || reg == TMP108_REG_CONF; +} + +static const struct regmap_config tmp108_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = TMP108_REG_THIGH, + .writeable_reg = tmp108_is_writeable_reg, + .volatile_reg = tmp108_is_volatile_reg, + .val_format_endian = REGMAP_ENDIAN_BIG, + .cache_type = REGCACHE_RBTREE, + .use_single_rw = true, +}; + +static int tmp108_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct tmp108 *tmp108; + int err; + u32 config; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA)) { + dev_err(dev, + "adapter doesn't support SMBus word transactions\n"); + return -ENODEV; + } + + tmp108 = devm_kzalloc(dev, sizeof(*tmp108), GFP_KERNEL); + if (!tmp108) + return -ENOMEM; + + dev_set_drvdata(dev, tmp108); + + tmp108->regmap = devm_regmap_init_i2c(client, &tmp108_regmap_config); + if (IS_ERR(tmp108->regmap)) { + err = PTR_ERR(tmp108->regmap); + dev_err(dev, "regmap init failed: %d", err); + return err; + } + + err = regmap_read(tmp108->regmap, TMP108_REG_CONF, &config); + if (err < 0) { + dev_err(dev, "error reading config register: %d", err); + return err; + } + tmp108->orig_config = config; + + /* Only continuous mode is supported. */ + config &= ~TMP108_CONF_MODE_MASK; + config |= TMP108_MODE_CONTINUOUS; + + /* Only comparator mode is supported. */ + config &= ~TMP108_CONF_TM; + + err = regmap_write(tmp108->regmap, TMP108_REG_CONF, config); + if (err < 0) { + dev_err(dev, "error writing config register: %d", err); + return err; + } + + tmp108->ready_time = jiffies; + if ((tmp108->orig_config & TMP108_CONF_MODE_MASK) == + TMP108_MODE_SHUTDOWN) + tmp108->ready_time += + msecs_to_jiffies(TMP108_CONVERSION_TIME_MS); + + err = devm_add_action_or_reset(dev, tmp108_restore_config, tmp108); + if (err) { + dev_err(dev, "add action or reset failed: %d", err); + return err; + } + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + tmp108, + &tmp108_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static int __maybe_unused tmp108_suspend(struct device *dev) +{ + struct tmp108 *tmp108 = dev_get_drvdata(dev); + + return regmap_update_bits(tmp108->regmap, TMP108_REG_CONF, + TMP108_CONF_MODE_MASK, TMP108_MODE_SHUTDOWN); +} + +static int __maybe_unused tmp108_resume(struct device *dev) +{ + struct tmp108 *tmp108 = dev_get_drvdata(dev); + int err; + + err = regmap_update_bits(tmp108->regmap, TMP108_REG_CONF, + TMP108_CONF_MODE_MASK, TMP108_MODE_CONTINUOUS); + tmp108->ready_time = jiffies + + msecs_to_jiffies(TMP108_CONVERSION_TIME_MS); + return err; +} + +static SIMPLE_DEV_PM_OPS(tmp108_dev_pm_ops, tmp108_suspend, tmp108_resume); + +static const struct i2c_device_id tmp108_i2c_ids[] = { + { "tmp108", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tmp108_i2c_ids); + +#ifdef CONFIG_OF +static const struct of_device_id tmp108_of_ids[] = { + { .compatible = "ti,tmp108", }, + {} +}; +MODULE_DEVICE_TABLE(of, tmp108_of_ids); +#endif + +static struct i2c_driver tmp108_driver = { + .driver = { + .name = DRIVER_NAME, + .pm = &tmp108_dev_pm_ops, + .of_match_table = of_match_ptr(tmp108_of_ids), + }, + .probe = tmp108_probe, + .id_table = tmp108_i2c_ids, +}; + +module_i2c_driver(tmp108_driver); + +MODULE_AUTHOR("John Muir "); +MODULE_DESCRIPTION("Texas Instruments TMP108 temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c new file mode 100644 index 000000000..99b0d7e0a --- /dev/null +++ b/drivers/hwmon/tmp401.c @@ -0,0 +1,785 @@ +/* tmp401.c + * + * Copyright (C) 2007,2008 Hans de Goede + * Preliminary tmp411 support by: + * Gabriel Konat, Sander Leget, Wouter Willems + * Copyright (C) 2009 Andre Prendel + * + * Cleanup and support for TMP431 and TMP432 by Guenter Roeck + * Copyright (c) 2013 Guenter Roeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Driver for the Texas Instruments TMP401 SMBUS temperature sensor IC. + * + * Note this IC is in some aspect similar to the LM90, but it has quite a + * few differences too, for example the local temp has a higher resolution + * and thus has 16 bits registers for its value and limit instead of 8 bits. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4c, 0x4d, + 0x4e, 0x4f, I2C_CLIENT_END }; + +enum chips { tmp401, tmp411, tmp431, tmp432, tmp435, tmp461 }; + +/* + * The TMP401 registers, note some registers have different addresses for + * reading and writing + */ +#define TMP401_STATUS 0x02 +#define TMP401_CONFIG_READ 0x03 +#define TMP401_CONFIG_WRITE 0x09 +#define TMP401_CONVERSION_RATE_READ 0x04 +#define TMP401_CONVERSION_RATE_WRITE 0x0A +#define TMP401_TEMP_CRIT_HYST 0x21 +#define TMP401_MANUFACTURER_ID_REG 0xFE +#define TMP401_DEVICE_ID_REG 0xFF + +static const u8 TMP401_TEMP_MSB_READ[7][2] = { + { 0x00, 0x01 }, /* temp */ + { 0x06, 0x08 }, /* low limit */ + { 0x05, 0x07 }, /* high limit */ + { 0x20, 0x19 }, /* therm (crit) limit */ + { 0x30, 0x34 }, /* lowest */ + { 0x32, 0x36 }, /* highest */ + { 0, 0x11 }, /* offset */ +}; + +static const u8 TMP401_TEMP_MSB_WRITE[7][2] = { + { 0, 0 }, /* temp (unused) */ + { 0x0C, 0x0E }, /* low limit */ + { 0x0B, 0x0D }, /* high limit */ + { 0x20, 0x19 }, /* therm (crit) limit */ + { 0x30, 0x34 }, /* lowest */ + { 0x32, 0x36 }, /* highest */ + { 0, 0x11 }, /* offset */ +}; + +static const u8 TMP432_TEMP_MSB_READ[4][3] = { + { 0x00, 0x01, 0x23 }, /* temp */ + { 0x06, 0x08, 0x16 }, /* low limit */ + { 0x05, 0x07, 0x15 }, /* high limit */ + { 0x20, 0x19, 0x1A }, /* therm (crit) limit */ +}; + +static const u8 TMP432_TEMP_MSB_WRITE[4][3] = { + { 0, 0, 0 }, /* temp - unused */ + { 0x0C, 0x0E, 0x16 }, /* low limit */ + { 0x0B, 0x0D, 0x15 }, /* high limit */ + { 0x20, 0x19, 0x1A }, /* therm (crit) limit */ +}; + +/* [0] = fault, [1] = low, [2] = high, [3] = therm/crit */ +static const u8 TMP432_STATUS_REG[] = { + 0x1b, 0x36, 0x35, 0x37 }; + +/* Flags */ +#define TMP401_CONFIG_RANGE BIT(2) +#define TMP401_CONFIG_SHUTDOWN BIT(6) +#define TMP401_STATUS_LOCAL_CRIT BIT(0) +#define TMP401_STATUS_REMOTE_CRIT BIT(1) +#define TMP401_STATUS_REMOTE_OPEN BIT(2) +#define TMP401_STATUS_REMOTE_LOW BIT(3) +#define TMP401_STATUS_REMOTE_HIGH BIT(4) +#define TMP401_STATUS_LOCAL_LOW BIT(5) +#define TMP401_STATUS_LOCAL_HIGH BIT(6) + +/* On TMP432, each status has its own register */ +#define TMP432_STATUS_LOCAL BIT(0) +#define TMP432_STATUS_REMOTE1 BIT(1) +#define TMP432_STATUS_REMOTE2 BIT(2) + +/* Manufacturer / Device ID's */ +#define TMP401_MANUFACTURER_ID 0x55 +#define TMP401_DEVICE_ID 0x11 +#define TMP411A_DEVICE_ID 0x12 +#define TMP411B_DEVICE_ID 0x13 +#define TMP411C_DEVICE_ID 0x10 +#define TMP431_DEVICE_ID 0x31 +#define TMP432_DEVICE_ID 0x32 +#define TMP435_DEVICE_ID 0x35 + +/* + * Driver data (common to all clients) + */ + +static const struct i2c_device_id tmp401_id[] = { + { "tmp401", tmp401 }, + { "tmp411", tmp411 }, + { "tmp431", tmp431 }, + { "tmp432", tmp432 }, + { "tmp435", tmp435 }, + { "tmp461", tmp461 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tmp401_id); + +/* + * Client data (each client gets its own) + */ + +struct tmp401_data { + struct i2c_client *client; + const struct attribute_group *groups[3]; + struct mutex update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + enum chips kind; + + unsigned int update_interval; /* in milliseconds */ + + /* register values */ + u8 status[4]; + u8 config; + u16 temp[7][3]; + u8 temp_crit_hyst; +}; + +/* + * Sysfs attr show / store functions + */ + +static int tmp401_register_to_temp(u16 reg, u8 config) +{ + int temp = reg; + + if (config & TMP401_CONFIG_RANGE) + temp -= 64 * 256; + + return DIV_ROUND_CLOSEST(temp * 125, 32); +} + +static u16 tmp401_temp_to_register(long temp, u8 config, int zbits) +{ + if (config & TMP401_CONFIG_RANGE) { + temp = clamp_val(temp, -64000, 191000); + temp += 64000; + } else + temp = clamp_val(temp, 0, 127000); + + return DIV_ROUND_CLOSEST(temp * (1 << (8 - zbits)), 1000) << zbits; +} + +static int tmp401_update_device_reg16(struct i2c_client *client, + struct tmp401_data *data) +{ + int i, j, val; + int num_regs = data->kind == tmp411 ? 6 : 4; + int num_sensors = data->kind == tmp432 ? 3 : 2; + + for (i = 0; i < num_sensors; i++) { /* local / r1 / r2 */ + for (j = 0; j < num_regs; j++) { /* temp / low / ... */ + u8 regaddr; + + regaddr = data->kind == tmp432 ? + TMP432_TEMP_MSB_READ[j][i] : + TMP401_TEMP_MSB_READ[j][i]; + if (j == 3) { /* crit is msb only */ + val = i2c_smbus_read_byte_data(client, regaddr); + } else { + val = i2c_smbus_read_word_swapped(client, + regaddr); + } + if (val < 0) + return val; + + data->temp[j][i] = j == 3 ? val << 8 : val; + } + } + return 0; +} + +static struct tmp401_data *tmp401_update_device(struct device *dev) +{ + struct tmp401_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + struct tmp401_data *ret = data; + int i, val; + unsigned long next_update; + + mutex_lock(&data->update_lock); + + next_update = data->last_updated + + msecs_to_jiffies(data->update_interval); + if (time_after(jiffies, next_update) || !data->valid) { + if (data->kind != tmp432) { + /* + * The driver uses the TMP432 status format internally. + * Convert status to TMP432 format for other chips. + */ + val = i2c_smbus_read_byte_data(client, TMP401_STATUS); + if (val < 0) { + ret = ERR_PTR(val); + goto abort; + } + data->status[0] = + (val & TMP401_STATUS_REMOTE_OPEN) >> 1; + data->status[1] = + ((val & TMP401_STATUS_REMOTE_LOW) >> 2) | + ((val & TMP401_STATUS_LOCAL_LOW) >> 5); + data->status[2] = + ((val & TMP401_STATUS_REMOTE_HIGH) >> 3) | + ((val & TMP401_STATUS_LOCAL_HIGH) >> 6); + data->status[3] = val & (TMP401_STATUS_LOCAL_CRIT + | TMP401_STATUS_REMOTE_CRIT); + } else { + for (i = 0; i < ARRAY_SIZE(data->status); i++) { + val = i2c_smbus_read_byte_data(client, + TMP432_STATUS_REG[i]); + if (val < 0) { + ret = ERR_PTR(val); + goto abort; + } + data->status[i] = val; + } + } + + val = i2c_smbus_read_byte_data(client, TMP401_CONFIG_READ); + if (val < 0) { + ret = ERR_PTR(val); + goto abort; + } + data->config = val; + val = tmp401_update_device_reg16(client, data); + if (val < 0) { + ret = ERR_PTR(val); + goto abort; + } + val = i2c_smbus_read_byte_data(client, TMP401_TEMP_CRIT_HYST); + if (val < 0) { + ret = ERR_PTR(val); + goto abort; + } + data->temp_crit_hyst = val; + + data->last_updated = jiffies; + data->valid = 1; + } + +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t show_temp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int nr = to_sensor_dev_attr_2(devattr)->nr; + int index = to_sensor_dev_attr_2(devattr)->index; + struct tmp401_data *data = tmp401_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", + tmp401_register_to_temp(data->temp[nr][index], data->config)); +} + +static ssize_t show_temp_crit_hyst(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int temp, index = to_sensor_dev_attr(devattr)->index; + struct tmp401_data *data = tmp401_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + temp = tmp401_register_to_temp(data->temp[3][index], data->config); + temp -= data->temp_crit_hyst * 1000; + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", temp); +} + +static ssize_t show_status(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int nr = to_sensor_dev_attr_2(devattr)->nr; + int mask = to_sensor_dev_attr_2(devattr)->index; + struct tmp401_data *data = tmp401_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", !!(data->status[nr] & mask)); +} + +static ssize_t store_temp(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr_2(devattr)->nr; + int index = to_sensor_dev_attr_2(devattr)->index; + struct tmp401_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + u16 reg; + u8 regaddr; + + if (kstrtol(buf, 10, &val)) + return -EINVAL; + + reg = tmp401_temp_to_register(val, data->config, nr == 3 ? 8 : 4); + + mutex_lock(&data->update_lock); + + regaddr = data->kind == tmp432 ? TMP432_TEMP_MSB_WRITE[nr][index] + : TMP401_TEMP_MSB_WRITE[nr][index]; + if (nr == 3) { /* crit is msb only */ + i2c_smbus_write_byte_data(client, regaddr, reg >> 8); + } else { + /* Hardware expects big endian data --> use _swapped */ + i2c_smbus_write_word_swapped(client, regaddr, reg); + } + data->temp[nr][index] = reg; + + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t store_temp_crit_hyst(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + int temp, index = to_sensor_dev_attr(devattr)->index; + struct tmp401_data *data = tmp401_update_device(dev); + long val; + u8 reg; + + if (IS_ERR(data)) + return PTR_ERR(data); + + if (kstrtol(buf, 10, &val)) + return -EINVAL; + + if (data->config & TMP401_CONFIG_RANGE) + val = clamp_val(val, -64000, 191000); + else + val = clamp_val(val, 0, 127000); + + mutex_lock(&data->update_lock); + temp = tmp401_register_to_temp(data->temp[3][index], data->config); + val = clamp_val(val, temp - 255000, temp); + reg = ((temp - val) + 500) / 1000; + + i2c_smbus_write_byte_data(data->client, TMP401_TEMP_CRIT_HYST, + reg); + + data->temp_crit_hyst = reg; + + mutex_unlock(&data->update_lock); + + return count; +} + +/* + * Resets the historical measurements of minimum and maximum temperatures. + * This is done by writing any value to any of the minimum/maximum registers + * (0x30-0x37). + */ +static ssize_t reset_temp_history(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count) +{ + struct tmp401_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + + if (kstrtol(buf, 10, &val)) + return -EINVAL; + + if (val != 1) { + dev_err(dev, + "temp_reset_history value %ld not supported. Use 1 to reset the history!\n", + val); + return -EINVAL; + } + mutex_lock(&data->update_lock); + i2c_smbus_write_byte_data(client, TMP401_TEMP_MSB_WRITE[5][0], val); + data->valid = 0; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t update_interval_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tmp401_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", data->update_interval); +} + +static ssize_t update_interval_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tmp401_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err, rate; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + /* + * For valid rates, interval can be calculated as + * interval = (1 << (7 - rate)) * 125; + * Rounded rate is therefore + * rate = 7 - __fls(interval * 4 / (125 * 3)); + * Use clamp_val() to avoid overflows, and to ensure valid input + * for __fls. + */ + val = clamp_val(val, 125, 16000); + rate = 7 - __fls(val * 4 / (125 * 3)); + mutex_lock(&data->update_lock); + i2c_smbus_write_byte_data(client, TMP401_CONVERSION_RATE_WRITE, rate); + data->update_interval = (1 << (7 - rate)) * 125; + mutex_unlock(&data->update_lock); + + return count; +} + +static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0); +static SENSOR_DEVICE_ATTR_2(temp1_min, S_IWUSR | S_IRUGO, show_temp, + store_temp, 1, 0); +static SENSOR_DEVICE_ATTR_2(temp1_max, S_IWUSR | S_IRUGO, show_temp, + store_temp, 2, 0); +static SENSOR_DEVICE_ATTR_2(temp1_crit, S_IWUSR | S_IRUGO, show_temp, + store_temp, 3, 0); +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO, + show_temp_crit_hyst, store_temp_crit_hyst, 0); +static SENSOR_DEVICE_ATTR_2(temp1_min_alarm, S_IRUGO, show_status, NULL, + 1, TMP432_STATUS_LOCAL); +static SENSOR_DEVICE_ATTR_2(temp1_max_alarm, S_IRUGO, show_status, NULL, + 2, TMP432_STATUS_LOCAL); +static SENSOR_DEVICE_ATTR_2(temp1_crit_alarm, S_IRUGO, show_status, NULL, + 3, TMP432_STATUS_LOCAL); +static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 1); +static SENSOR_DEVICE_ATTR_2(temp2_min, S_IWUSR | S_IRUGO, show_temp, + store_temp, 1, 1); +static SENSOR_DEVICE_ATTR_2(temp2_max, S_IWUSR | S_IRUGO, show_temp, + store_temp, 2, 1); +static SENSOR_DEVICE_ATTR_2(temp2_crit, S_IWUSR | S_IRUGO, show_temp, + store_temp, 3, 1); +static SENSOR_DEVICE_ATTR(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, + NULL, 1); +static SENSOR_DEVICE_ATTR_2(temp2_fault, S_IRUGO, show_status, NULL, + 0, TMP432_STATUS_REMOTE1); +static SENSOR_DEVICE_ATTR_2(temp2_min_alarm, S_IRUGO, show_status, NULL, + 1, TMP432_STATUS_REMOTE1); +static SENSOR_DEVICE_ATTR_2(temp2_max_alarm, S_IRUGO, show_status, NULL, + 2, TMP432_STATUS_REMOTE1); +static SENSOR_DEVICE_ATTR_2(temp2_crit_alarm, S_IRUGO, show_status, NULL, + 3, TMP432_STATUS_REMOTE1); + +static DEVICE_ATTR_RW(update_interval); + +static struct attribute *tmp401_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + + &dev_attr_update_interval.attr, + + NULL +}; + +static const struct attribute_group tmp401_group = { + .attrs = tmp401_attributes, +}; + +/* + * Additional features of the TMP411 chip. + * The TMP411 stores the minimum and maximum + * temperature measured since power-on, chip-reset, or + * minimum and maximum register reset for both the local + * and remote channels. + */ +static SENSOR_DEVICE_ATTR_2(temp1_lowest, S_IRUGO, show_temp, NULL, 4, 0); +static SENSOR_DEVICE_ATTR_2(temp1_highest, S_IRUGO, show_temp, NULL, 5, 0); +static SENSOR_DEVICE_ATTR_2(temp2_lowest, S_IRUGO, show_temp, NULL, 4, 1); +static SENSOR_DEVICE_ATTR_2(temp2_highest, S_IRUGO, show_temp, NULL, 5, 1); +static SENSOR_DEVICE_ATTR(temp_reset_history, S_IWUSR, NULL, reset_temp_history, + 0); + +static struct attribute *tmp411_attributes[] = { + &sensor_dev_attr_temp1_highest.dev_attr.attr, + &sensor_dev_attr_temp1_lowest.dev_attr.attr, + &sensor_dev_attr_temp2_highest.dev_attr.attr, + &sensor_dev_attr_temp2_lowest.dev_attr.attr, + &sensor_dev_attr_temp_reset_history.dev_attr.attr, + NULL +}; + +static const struct attribute_group tmp411_group = { + .attrs = tmp411_attributes, +}; + +static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 2); +static SENSOR_DEVICE_ATTR_2(temp3_min, S_IWUSR | S_IRUGO, show_temp, + store_temp, 1, 2); +static SENSOR_DEVICE_ATTR_2(temp3_max, S_IWUSR | S_IRUGO, show_temp, + store_temp, 2, 2); +static SENSOR_DEVICE_ATTR_2(temp3_crit, S_IWUSR | S_IRUGO, show_temp, + store_temp, 3, 2); +static SENSOR_DEVICE_ATTR(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, + NULL, 2); +static SENSOR_DEVICE_ATTR_2(temp3_fault, S_IRUGO, show_status, NULL, + 0, TMP432_STATUS_REMOTE2); +static SENSOR_DEVICE_ATTR_2(temp3_min_alarm, S_IRUGO, show_status, NULL, + 1, TMP432_STATUS_REMOTE2); +static SENSOR_DEVICE_ATTR_2(temp3_max_alarm, S_IRUGO, show_status, NULL, + 2, TMP432_STATUS_REMOTE2); +static SENSOR_DEVICE_ATTR_2(temp3_crit_alarm, S_IRUGO, show_status, NULL, + 3, TMP432_STATUS_REMOTE2); + +static struct attribute *tmp432_attributes[] = { + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp3_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, + + NULL +}; + +static const struct attribute_group tmp432_group = { + .attrs = tmp432_attributes, +}; + +/* + * Additional features of the TMP461 chip. + * The TMP461 temperature offset for the remote channel. + */ +static SENSOR_DEVICE_ATTR_2(temp2_offset, S_IWUSR | S_IRUGO, show_temp, + store_temp, 6, 1); + +static struct attribute *tmp461_attributes[] = { + &sensor_dev_attr_temp2_offset.dev_attr.attr, + NULL +}; + +static const struct attribute_group tmp461_group = { + .attrs = tmp461_attributes, +}; + +/* + * Begin non sysfs callback code (aka Real code) + */ + +static int tmp401_init_client(struct tmp401_data *data, + struct i2c_client *client) +{ + int config, config_orig, status = 0; + + /* Set the conversion rate to 2 Hz */ + i2c_smbus_write_byte_data(client, TMP401_CONVERSION_RATE_WRITE, 5); + data->update_interval = 500; + + /* Start conversions (disable shutdown if necessary) */ + config = i2c_smbus_read_byte_data(client, TMP401_CONFIG_READ); + if (config < 0) + return config; + + config_orig = config; + config &= ~TMP401_CONFIG_SHUTDOWN; + + if (config != config_orig) + status = i2c_smbus_write_byte_data(client, + TMP401_CONFIG_WRITE, + config); + + return status; +} + +static int tmp401_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + enum chips kind; + struct i2c_adapter *adapter = client->adapter; + u8 reg; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* Detect and identify the chip */ + reg = i2c_smbus_read_byte_data(client, TMP401_MANUFACTURER_ID_REG); + if (reg != TMP401_MANUFACTURER_ID) + return -ENODEV; + + reg = i2c_smbus_read_byte_data(client, TMP401_DEVICE_ID_REG); + + switch (reg) { + case TMP401_DEVICE_ID: + if (client->addr != 0x4c) + return -ENODEV; + kind = tmp401; + break; + case TMP411A_DEVICE_ID: + if (client->addr != 0x4c) + return -ENODEV; + kind = tmp411; + break; + case TMP411B_DEVICE_ID: + if (client->addr != 0x4d) + return -ENODEV; + kind = tmp411; + break; + case TMP411C_DEVICE_ID: + if (client->addr != 0x4e) + return -ENODEV; + kind = tmp411; + break; + case TMP431_DEVICE_ID: + if (client->addr != 0x4c && client->addr != 0x4d) + return -ENODEV; + kind = tmp431; + break; + case TMP432_DEVICE_ID: + if (client->addr != 0x4c && client->addr != 0x4d) + return -ENODEV; + kind = tmp432; + break; + case TMP435_DEVICE_ID: + kind = tmp435; + break; + default: + return -ENODEV; + } + + reg = i2c_smbus_read_byte_data(client, TMP401_CONFIG_READ); + if (reg & 0x1b) + return -ENODEV; + + reg = i2c_smbus_read_byte_data(client, TMP401_CONVERSION_RATE_READ); + /* Datasheet says: 0x1-0x6 */ + if (reg > 15) + return -ENODEV; + + strlcpy(info->type, tmp401_id[kind].name, I2C_NAME_SIZE); + + return 0; +} + +static int tmp401_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + static const char * const names[] = { + "TMP401", "TMP411", "TMP431", "TMP432", "TMP435", "TMP461" + }; + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct tmp401_data *data; + int groups = 0, status; + + data = devm_kzalloc(dev, sizeof(struct tmp401_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + data->kind = id->driver_data; + + /* Initialize the TMP401 chip */ + status = tmp401_init_client(data, client); + if (status < 0) + return status; + + /* Register sysfs hooks */ + data->groups[groups++] = &tmp401_group; + + /* Register additional tmp411 sysfs hooks */ + if (data->kind == tmp411) + data->groups[groups++] = &tmp411_group; + + /* Register additional tmp432 sysfs hooks */ + if (data->kind == tmp432) + data->groups[groups++] = &tmp432_group; + + if (data->kind == tmp461) + data->groups[groups++] = &tmp461_group; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, data->groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + dev_info(dev, "Detected TI %s chip\n", names[data->kind]); + + return 0; +} + +static const struct of_device_id __maybe_unused tmp4xx_of_match[] = { + { .compatible = "ti,tmp401", }, + { .compatible = "ti,tmp411", }, + { .compatible = "ti,tmp431", }, + { .compatible = "ti,tmp432", }, + { .compatible = "ti,tmp435", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tmp4xx_of_match); + +static struct i2c_driver tmp401_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "tmp401", + .of_match_table = of_match_ptr(tmp4xx_of_match), + }, + .probe = tmp401_probe, + .id_table = tmp401_id, + .detect = tmp401_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(tmp401_driver); + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("Texas Instruments TMP401 temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/tmp421.c b/drivers/hwmon/tmp421.c new file mode 100644 index 000000000..cdd01a848 --- /dev/null +++ b/drivers/hwmon/tmp421.c @@ -0,0 +1,335 @@ +/* tmp421.c + * + * Copyright (C) 2009 Andre Prendel + * Preliminary support by: + * Melvin Rook, Raymond Ng + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Driver for the Texas Instruments TMP421 SMBus temperature sensor IC. + * Supported models: TMP421, TMP422, TMP423, TMP441, TMP442 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2a, 0x4c, 0x4d, 0x4e, 0x4f, + I2C_CLIENT_END }; + +enum chips { tmp421, tmp422, tmp423, tmp441, tmp442 }; + +/* The TMP421 registers */ +#define TMP421_STATUS_REG 0x08 +#define TMP421_CONFIG_REG_1 0x09 +#define TMP421_CONVERSION_RATE_REG 0x0B +#define TMP421_MANUFACTURER_ID_REG 0xFE +#define TMP421_DEVICE_ID_REG 0xFF + +static const u8 TMP421_TEMP_MSB[4] = { 0x00, 0x01, 0x02, 0x03 }; +static const u8 TMP421_TEMP_LSB[4] = { 0x10, 0x11, 0x12, 0x13 }; + +/* Flags */ +#define TMP421_CONFIG_SHUTDOWN 0x40 +#define TMP421_CONFIG_RANGE 0x04 + +/* Manufacturer / Device ID's */ +#define TMP421_MANUFACTURER_ID 0x55 +#define TMP421_DEVICE_ID 0x21 +#define TMP422_DEVICE_ID 0x22 +#define TMP423_DEVICE_ID 0x23 +#define TMP441_DEVICE_ID 0x41 +#define TMP442_DEVICE_ID 0x42 + +static const struct i2c_device_id tmp421_id[] = { + { "tmp421", 2 }, + { "tmp422", 3 }, + { "tmp423", 4 }, + { "tmp441", 2 }, + { "tmp442", 3 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tmp421_id); + +static const struct of_device_id tmp421_of_match[] = { + { + .compatible = "ti,tmp421", + .data = (void *)2 + }, + { + .compatible = "ti,tmp422", + .data = (void *)3 + }, + { + .compatible = "ti,tmp423", + .data = (void *)4 + }, + { + .compatible = "ti,tmp441", + .data = (void *)2 + }, + { + .compatible = "ti,tmp442", + .data = (void *)3 + }, + { }, +}; +MODULE_DEVICE_TABLE(of, tmp421_of_match); + +struct tmp421_data { + struct i2c_client *client; + struct mutex update_lock; + u32 temp_config[5]; + struct hwmon_channel_info temp_info; + const struct hwmon_channel_info *info[2]; + struct hwmon_chip_info chip; + char valid; + unsigned long last_updated; + unsigned long channels; + u8 config; + s16 temp[4]; +}; + +static int temp_from_raw(u16 reg, bool extended) +{ + /* Mask out status bits */ + int temp = reg & ~0xf; + + if (extended) + temp = temp - 64 * 256; + else + temp = (s16)temp; + + return DIV_ROUND_CLOSEST(temp * 1000, 256); +} + +static struct tmp421_data *tmp421_update_device(struct device *dev) +{ + struct tmp421_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + 2 * HZ) || !data->valid) { + data->config = i2c_smbus_read_byte_data(client, + TMP421_CONFIG_REG_1); + + for (i = 0; i < data->channels; i++) { + data->temp[i] = i2c_smbus_read_byte_data(client, + TMP421_TEMP_MSB[i]) << 8; + data->temp[i] |= i2c_smbus_read_byte_data(client, + TMP421_TEMP_LSB[i]); + } + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static int tmp421_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct tmp421_data *tmp421 = tmp421_update_device(dev); + + switch (attr) { + case hwmon_temp_input: + *val = temp_from_raw(tmp421->temp[channel], + tmp421->config & TMP421_CONFIG_RANGE); + return 0; + case hwmon_temp_fault: + /* + * Any of OPEN or /PVLD bits indicate a hardware mulfunction + * and the conversion result may be incorrect + */ + *val = !!(tmp421->temp[channel] & 0x03); + return 0; + default: + return -EOPNOTSUPP; + } + +} + +static umode_t tmp421_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (attr) { + case hwmon_temp_fault: + case hwmon_temp_input: + return 0444; + default: + return 0; + } +} + +static int tmp421_init_client(struct i2c_client *client) +{ + int config, config_orig; + + /* Set the conversion rate to 2 Hz */ + i2c_smbus_write_byte_data(client, TMP421_CONVERSION_RATE_REG, 0x05); + + /* Start conversions (disable shutdown if necessary) */ + config = i2c_smbus_read_byte_data(client, TMP421_CONFIG_REG_1); + if (config < 0) { + dev_err(&client->dev, + "Could not read configuration register (%d)\n", config); + return config; + } + + config_orig = config; + config &= ~TMP421_CONFIG_SHUTDOWN; + + if (config != config_orig) { + dev_info(&client->dev, "Enable monitoring chip\n"); + i2c_smbus_write_byte_data(client, TMP421_CONFIG_REG_1, config); + } + + return 0; +} + +static int tmp421_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + enum chips kind; + struct i2c_adapter *adapter = client->adapter; + const char * const names[] = { "TMP421", "TMP422", "TMP423", + "TMP441", "TMP442" }; + int addr = client->addr; + u8 reg; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + reg = i2c_smbus_read_byte_data(client, TMP421_MANUFACTURER_ID_REG); + if (reg != TMP421_MANUFACTURER_ID) + return -ENODEV; + + reg = i2c_smbus_read_byte_data(client, TMP421_CONVERSION_RATE_REG); + if (reg & 0xf8) + return -ENODEV; + + reg = i2c_smbus_read_byte_data(client, TMP421_STATUS_REG); + if (reg & 0x7f) + return -ENODEV; + + reg = i2c_smbus_read_byte_data(client, TMP421_DEVICE_ID_REG); + switch (reg) { + case TMP421_DEVICE_ID: + kind = tmp421; + break; + case TMP422_DEVICE_ID: + if (addr == 0x2a) + return -ENODEV; + kind = tmp422; + break; + case TMP423_DEVICE_ID: + if (addr != 0x4c && addr != 0x4d) + return -ENODEV; + kind = tmp423; + break; + case TMP441_DEVICE_ID: + kind = tmp441; + break; + case TMP442_DEVICE_ID: + if (addr != 0x4c && addr != 0x4d) + return -ENODEV; + kind = tmp442; + break; + default: + return -ENODEV; + } + + strlcpy(info->type, tmp421_id[kind].name, I2C_NAME_SIZE); + dev_info(&adapter->dev, "Detected TI %s chip at 0x%02x\n", + names[kind], client->addr); + + return 0; +} + +static const struct hwmon_ops tmp421_ops = { + .is_visible = tmp421_is_visible, + .read = tmp421_read, +}; + +static int tmp421_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct tmp421_data *data; + int i, err; + + data = devm_kzalloc(dev, sizeof(struct tmp421_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->update_lock); + if (client->dev.of_node) + data->channels = (unsigned long) + of_device_get_match_data(&client->dev); + else + data->channels = id->driver_data; + data->client = client; + + err = tmp421_init_client(client); + if (err) + return err; + + for (i = 0; i < data->channels; i++) + data->temp_config[i] = HWMON_T_INPUT | HWMON_T_FAULT; + + data->chip.ops = &tmp421_ops; + data->chip.info = data->info; + + data->info[0] = &data->temp_info; + + data->temp_info.type = hwmon_temp; + data->temp_info.config = data->temp_config; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, + &data->chip, + NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct i2c_driver tmp421_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "tmp421", + .of_match_table = of_match_ptr(tmp421_of_match), + }, + .probe = tmp421_probe, + .id_table = tmp421_id, + .detect = tmp421_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(tmp421_driver); + +MODULE_AUTHOR("Andre Prendel "); +MODULE_DESCRIPTION("Texas Instruments TMP421/422/423/441/442 temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ultra45_env.c b/drivers/hwmon/ultra45_env.c new file mode 100644 index 000000000..f2816c7c9 --- /dev/null +++ b/drivers/hwmon/ultra45_env.c @@ -0,0 +1,323 @@ +/* + * ultra45_env.c: Driver for Ultra45 PIC16F747 environmental monitor. + * + * Copyright (C) 2008 David S. Miller + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_MODULE_VERSION "0.1" + +MODULE_AUTHOR("David S. Miller (davem@davemloft.net)"); +MODULE_DESCRIPTION("Ultra45 environmental monitor driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_MODULE_VERSION); + +/* PIC device registers */ +#define REG_CMD 0x00UL +#define REG_CMD_RESET 0x80 +#define REG_CMD_ESTAR 0x01 +#define REG_STAT 0x01UL +#define REG_STAT_FWVER 0xf0 +#define REG_STAT_TGOOD 0x08 +#define REG_STAT_STALE 0x04 +#define REG_STAT_BUSY 0x02 +#define REG_STAT_FAULT 0x01 +#define REG_DATA 0x40UL +#define REG_ADDR 0x41UL +#define REG_SIZE 0x42UL + +/* Registers accessed indirectly via REG_DATA/REG_ADDR */ +#define IREG_FAN0 0x00 +#define IREG_FAN1 0x01 +#define IREG_FAN2 0x02 +#define IREG_FAN3 0x03 +#define IREG_FAN4 0x04 +#define IREG_FAN5 0x05 +#define IREG_LCL_TEMP 0x06 +#define IREG_RMT1_TEMP 0x07 +#define IREG_RMT2_TEMP 0x08 +#define IREG_RMT3_TEMP 0x09 +#define IREG_LM95221_TEMP 0x0a +#define IREG_FIRE_TEMP 0x0b +#define IREG_LSI1064_TEMP 0x0c +#define IREG_FRONT_TEMP 0x0d +#define IREG_FAN_STAT 0x0e +#define IREG_VCORE0 0x0f +#define IREG_VCORE1 0x10 +#define IREG_VMEM0 0x11 +#define IREG_VMEM1 0x12 +#define IREG_PSU_TEMP 0x13 + +struct env { + void __iomem *regs; + spinlock_t lock; + + struct device *hwmon_dev; +}; + +static u8 env_read(struct env *p, u8 ireg) +{ + u8 ret; + + spin_lock(&p->lock); + writeb(ireg, p->regs + REG_ADDR); + ret = readb(p->regs + REG_DATA); + spin_unlock(&p->lock); + + return ret; +} + +static void env_write(struct env *p, u8 ireg, u8 val) +{ + spin_lock(&p->lock); + writeb(ireg, p->regs + REG_ADDR); + writeb(val, p->regs + REG_DATA); + spin_unlock(&p->lock); +} + +/* + * There seems to be a adr7462 providing these values, thus a lot + * of these calculations are borrowed from the adt7470 driver. + */ +#define FAN_PERIOD_TO_RPM(x) ((90000 * 60) / (x)) +#define FAN_RPM_TO_PERIOD FAN_PERIOD_TO_RPM +#define FAN_PERIOD_INVALID (0xff << 8) +#define FAN_DATA_VALID(x) ((x) && (x) != FAN_PERIOD_INVALID) + +static ssize_t show_fan_speed(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int fan_nr = to_sensor_dev_attr(attr)->index; + struct env *p = dev_get_drvdata(dev); + int rpm, period; + u8 val; + + val = env_read(p, IREG_FAN0 + fan_nr); + period = (int) val << 8; + if (FAN_DATA_VALID(period)) + rpm = FAN_PERIOD_TO_RPM(period); + else + rpm = 0; + + return sprintf(buf, "%d\n", rpm); +} + +static ssize_t set_fan_speed(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int fan_nr = to_sensor_dev_attr(attr)->index; + unsigned long rpm; + struct env *p = dev_get_drvdata(dev); + int period; + u8 val; + int err; + + err = kstrtoul(buf, 10, &rpm); + if (err) + return err; + + if (!rpm) + return -EINVAL; + + period = FAN_RPM_TO_PERIOD(rpm); + val = period >> 8; + env_write(p, IREG_FAN0 + fan_nr, val); + + return count; +} + +static ssize_t show_fan_fault(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int fan_nr = to_sensor_dev_attr(attr)->index; + struct env *p = dev_get_drvdata(dev); + u8 val = env_read(p, IREG_FAN_STAT); + return sprintf(buf, "%d\n", (val & (1 << fan_nr)) ? 1 : 0); +} + +#define fan(index) \ +static SENSOR_DEVICE_ATTR(fan##index##_speed, S_IRUGO | S_IWUSR, \ + show_fan_speed, set_fan_speed, index); \ +static SENSOR_DEVICE_ATTR(fan##index##_fault, S_IRUGO, \ + show_fan_fault, NULL, index) + +fan(0); +fan(1); +fan(2); +fan(3); +fan(4); + +static SENSOR_DEVICE_ATTR(psu_fan_fault, S_IRUGO, show_fan_fault, NULL, 6); + +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int temp_nr = to_sensor_dev_attr(attr)->index; + struct env *p = dev_get_drvdata(dev); + s8 val; + + val = env_read(p, IREG_LCL_TEMP + temp_nr); + return sprintf(buf, "%d\n", ((int) val) - 64); +} + +static SENSOR_DEVICE_ATTR(adt7462_local_temp, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(cpu0_temp, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(cpu1_temp, S_IRUGO, show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(motherboard_temp, S_IRUGO, show_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(lm95221_local_temp, S_IRUGO, show_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(fire_temp, S_IRUGO, show_temp, NULL, 5); +static SENSOR_DEVICE_ATTR(lsi1064_local_temp, S_IRUGO, show_temp, NULL, 6); +static SENSOR_DEVICE_ATTR(front_panel_temp, S_IRUGO, show_temp, NULL, 7); +static SENSOR_DEVICE_ATTR(psu_temp, S_IRUGO, show_temp, NULL, 13); + +static ssize_t show_stat_bit(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + struct env *p = dev_get_drvdata(dev); + u8 val; + + val = readb(p->regs + REG_STAT); + return sprintf(buf, "%d\n", (val & (1 << index)) ? 1 : 0); +} + +static SENSOR_DEVICE_ATTR(fan_failure, S_IRUGO, show_stat_bit, NULL, 0); +static SENSOR_DEVICE_ATTR(env_bus_busy, S_IRUGO, show_stat_bit, NULL, 1); +static SENSOR_DEVICE_ATTR(env_data_stale, S_IRUGO, show_stat_bit, NULL, 2); +static SENSOR_DEVICE_ATTR(tpm_self_test_passed, S_IRUGO, show_stat_bit, NULL, + 3); + +static ssize_t show_fwver(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct env *p = dev_get_drvdata(dev); + u8 val; + + val = readb(p->regs + REG_STAT); + return sprintf(buf, "%d\n", val >> 4); +} + +static SENSOR_DEVICE_ATTR(firmware_version, S_IRUGO, show_fwver, NULL, 0); + +static ssize_t show_name(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "ultra45\n"); +} + +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); + +static struct attribute *env_attributes[] = { + &sensor_dev_attr_fan0_speed.dev_attr.attr, + &sensor_dev_attr_fan0_fault.dev_attr.attr, + &sensor_dev_attr_fan1_speed.dev_attr.attr, + &sensor_dev_attr_fan1_fault.dev_attr.attr, + &sensor_dev_attr_fan2_speed.dev_attr.attr, + &sensor_dev_attr_fan2_fault.dev_attr.attr, + &sensor_dev_attr_fan3_speed.dev_attr.attr, + &sensor_dev_attr_fan3_fault.dev_attr.attr, + &sensor_dev_attr_fan4_speed.dev_attr.attr, + &sensor_dev_attr_fan4_fault.dev_attr.attr, + &sensor_dev_attr_psu_fan_fault.dev_attr.attr, + &sensor_dev_attr_adt7462_local_temp.dev_attr.attr, + &sensor_dev_attr_cpu0_temp.dev_attr.attr, + &sensor_dev_attr_cpu1_temp.dev_attr.attr, + &sensor_dev_attr_motherboard_temp.dev_attr.attr, + &sensor_dev_attr_lm95221_local_temp.dev_attr.attr, + &sensor_dev_attr_fire_temp.dev_attr.attr, + &sensor_dev_attr_lsi1064_local_temp.dev_attr.attr, + &sensor_dev_attr_front_panel_temp.dev_attr.attr, + &sensor_dev_attr_psu_temp.dev_attr.attr, + &sensor_dev_attr_fan_failure.dev_attr.attr, + &sensor_dev_attr_env_bus_busy.dev_attr.attr, + &sensor_dev_attr_env_data_stale.dev_attr.attr, + &sensor_dev_attr_tpm_self_test_passed.dev_attr.attr, + &sensor_dev_attr_firmware_version.dev_attr.attr, + &sensor_dev_attr_name.dev_attr.attr, + NULL, +}; + +static const struct attribute_group env_group = { + .attrs = env_attributes, +}; + +static int env_probe(struct platform_device *op) +{ + struct env *p = devm_kzalloc(&op->dev, sizeof(*p), GFP_KERNEL); + int err = -ENOMEM; + + if (!p) + goto out; + + spin_lock_init(&p->lock); + + p->regs = of_ioremap(&op->resource[0], 0, REG_SIZE, "pic16f747"); + if (!p->regs) + goto out; + + err = sysfs_create_group(&op->dev.kobj, &env_group); + if (err) + goto out_iounmap; + + p->hwmon_dev = hwmon_device_register(&op->dev); + if (IS_ERR(p->hwmon_dev)) { + err = PTR_ERR(p->hwmon_dev); + goto out_sysfs_remove_group; + } + + platform_set_drvdata(op, p); + err = 0; + +out: + return err; + +out_sysfs_remove_group: + sysfs_remove_group(&op->dev.kobj, &env_group); + +out_iounmap: + of_iounmap(&op->resource[0], p->regs, REG_SIZE); + + goto out; +} + +static int env_remove(struct platform_device *op) +{ + struct env *p = platform_get_drvdata(op); + + if (p) { + sysfs_remove_group(&op->dev.kobj, &env_group); + hwmon_device_unregister(p->hwmon_dev); + of_iounmap(&op->resource[0], p->regs, REG_SIZE); + } + + return 0; +} + +static const struct of_device_id env_match[] = { + { + .name = "env-monitor", + .compatible = "SUNW,ebus-pic16f747-env", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, env_match); + +static struct platform_driver env_driver = { + .driver = { + .name = "ultra45_env", + .of_match_table = env_match, + }, + .probe = env_probe, + .remove = env_remove, +}; + +module_platform_driver(env_driver); diff --git a/drivers/hwmon/vexpress-hwmon.c b/drivers/hwmon/vexpress-hwmon.c new file mode 100644 index 000000000..8ba419d34 --- /dev/null +++ b/drivers/hwmon/vexpress-hwmon.c @@ -0,0 +1,259 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Copyright (C) 2012 ARM Limited + */ + +#define DRVNAME "vexpress-hwmon" +#define pr_fmt(fmt) DRVNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct vexpress_hwmon_data { + struct device *hwmon_dev; + struct regmap *reg; +}; + +static ssize_t vexpress_hwmon_label_show(struct device *dev, + struct device_attribute *dev_attr, char *buffer) +{ + const char *label = of_get_property(dev->of_node, "label", NULL); + + return snprintf(buffer, PAGE_SIZE, "%s\n", label); +} + +static ssize_t vexpress_hwmon_u32_show(struct device *dev, + struct device_attribute *dev_attr, char *buffer) +{ + struct vexpress_hwmon_data *data = dev_get_drvdata(dev); + int err; + u32 value; + + err = regmap_read(data->reg, 0, &value); + if (err) + return err; + + return snprintf(buffer, PAGE_SIZE, "%u\n", value / + to_sensor_dev_attr(dev_attr)->index); +} + +static ssize_t vexpress_hwmon_u64_show(struct device *dev, + struct device_attribute *dev_attr, char *buffer) +{ + struct vexpress_hwmon_data *data = dev_get_drvdata(dev); + int err; + u32 value_hi, value_lo; + + err = regmap_read(data->reg, 0, &value_lo); + if (err) + return err; + + err = regmap_read(data->reg, 1, &value_hi); + if (err) + return err; + + return snprintf(buffer, PAGE_SIZE, "%llu\n", + div_u64(((u64)value_hi << 32) | value_lo, + to_sensor_dev_attr(dev_attr)->index)); +} + +static umode_t vexpress_hwmon_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct device_attribute *dev_attr = container_of(attr, + struct device_attribute, attr); + + if (dev_attr->show == vexpress_hwmon_label_show && + !of_get_property(dev->of_node, "label", NULL)) + return 0; + + return attr->mode; +} + +struct vexpress_hwmon_type { + const char *name; + const struct attribute_group **attr_groups; +}; + +#if !defined(CONFIG_REGULATOR_VEXPRESS) +static DEVICE_ATTR(in1_label, S_IRUGO, vexpress_hwmon_label_show, NULL); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, vexpress_hwmon_u32_show, + NULL, 1000); +static struct attribute *vexpress_hwmon_attrs_volt[] = { + &dev_attr_in1_label.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + NULL +}; +static struct attribute_group vexpress_hwmon_group_volt = { + .is_visible = vexpress_hwmon_attr_is_visible, + .attrs = vexpress_hwmon_attrs_volt, +}; +static struct vexpress_hwmon_type vexpress_hwmon_volt = { + .name = "vexpress_volt", + .attr_groups = (const struct attribute_group *[]) { + &vexpress_hwmon_group_volt, + NULL, + }, +}; +#endif + +static DEVICE_ATTR(curr1_label, S_IRUGO, vexpress_hwmon_label_show, NULL); +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, vexpress_hwmon_u32_show, + NULL, 1000); +static struct attribute *vexpress_hwmon_attrs_amp[] = { + &dev_attr_curr1_label.attr, + &sensor_dev_attr_curr1_input.dev_attr.attr, + NULL +}; +static struct attribute_group vexpress_hwmon_group_amp = { + .is_visible = vexpress_hwmon_attr_is_visible, + .attrs = vexpress_hwmon_attrs_amp, +}; +static struct vexpress_hwmon_type vexpress_hwmon_amp = { + .name = "vexpress_amp", + .attr_groups = (const struct attribute_group *[]) { + &vexpress_hwmon_group_amp, + NULL + }, +}; + +static DEVICE_ATTR(temp1_label, S_IRUGO, vexpress_hwmon_label_show, NULL); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, vexpress_hwmon_u32_show, + NULL, 1000); +static struct attribute *vexpress_hwmon_attrs_temp[] = { + &dev_attr_temp1_label.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + NULL +}; +static struct attribute_group vexpress_hwmon_group_temp = { + .is_visible = vexpress_hwmon_attr_is_visible, + .attrs = vexpress_hwmon_attrs_temp, +}; +static struct vexpress_hwmon_type vexpress_hwmon_temp = { + .name = "vexpress_temp", + .attr_groups = (const struct attribute_group *[]) { + &vexpress_hwmon_group_temp, + NULL + }, +}; + +static DEVICE_ATTR(power1_label, S_IRUGO, vexpress_hwmon_label_show, NULL); +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, vexpress_hwmon_u32_show, + NULL, 1); +static struct attribute *vexpress_hwmon_attrs_power[] = { + &dev_attr_power1_label.attr, + &sensor_dev_attr_power1_input.dev_attr.attr, + NULL +}; +static struct attribute_group vexpress_hwmon_group_power = { + .is_visible = vexpress_hwmon_attr_is_visible, + .attrs = vexpress_hwmon_attrs_power, +}; +static struct vexpress_hwmon_type vexpress_hwmon_power = { + .name = "vexpress_power", + .attr_groups = (const struct attribute_group *[]) { + &vexpress_hwmon_group_power, + NULL + }, +}; + +static DEVICE_ATTR(energy1_label, S_IRUGO, vexpress_hwmon_label_show, NULL); +static SENSOR_DEVICE_ATTR(energy1_input, S_IRUGO, vexpress_hwmon_u64_show, + NULL, 1); +static struct attribute *vexpress_hwmon_attrs_energy[] = { + &dev_attr_energy1_label.attr, + &sensor_dev_attr_energy1_input.dev_attr.attr, + NULL +}; +static struct attribute_group vexpress_hwmon_group_energy = { + .is_visible = vexpress_hwmon_attr_is_visible, + .attrs = vexpress_hwmon_attrs_energy, +}; +static struct vexpress_hwmon_type vexpress_hwmon_energy = { + .name = "vexpress_energy", + .attr_groups = (const struct attribute_group *[]) { + &vexpress_hwmon_group_energy, + NULL + }, +}; + +static const struct of_device_id vexpress_hwmon_of_match[] = { +#if !defined(CONFIG_REGULATOR_VEXPRESS) + { + .compatible = "arm,vexpress-volt", + .data = &vexpress_hwmon_volt, + }, +#endif + { + .compatible = "arm,vexpress-amp", + .data = &vexpress_hwmon_amp, + }, { + .compatible = "arm,vexpress-temp", + .data = &vexpress_hwmon_temp, + }, { + .compatible = "arm,vexpress-power", + .data = &vexpress_hwmon_power, + }, { + .compatible = "arm,vexpress-energy", + .data = &vexpress_hwmon_energy, + }, + {} +}; +MODULE_DEVICE_TABLE(of, vexpress_hwmon_of_match); + +static int vexpress_hwmon_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct vexpress_hwmon_data *data; + const struct vexpress_hwmon_type *type; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + platform_set_drvdata(pdev, data); + + match = of_match_device(vexpress_hwmon_of_match, &pdev->dev); + if (!match) + return -ENODEV; + type = match->data; + + data->reg = devm_regmap_init_vexpress_config(&pdev->dev); + if (IS_ERR(data->reg)) + return PTR_ERR(data->reg); + + data->hwmon_dev = devm_hwmon_device_register_with_groups(&pdev->dev, + type->name, data, type->attr_groups); + + return PTR_ERR_OR_ZERO(data->hwmon_dev); +} + +static struct platform_driver vexpress_hwmon_driver = { + .probe = vexpress_hwmon_probe, + .driver = { + .name = DRVNAME, + .of_match_table = vexpress_hwmon_of_match, + }, +}; + +module_platform_driver(vexpress_hwmon_driver); + +MODULE_AUTHOR("Pawel Moll "); +MODULE_DESCRIPTION("Versatile Express hwmon sensors driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:vexpress-hwmon"); diff --git a/drivers/hwmon/via-cputemp.c b/drivers/hwmon/via-cputemp.c new file mode 100644 index 000000000..0e81f287d --- /dev/null +++ b/drivers/hwmon/via-cputemp.c @@ -0,0 +1,343 @@ +/* + * via-cputemp.c - Driver for VIA CPU core temperature monitoring + * Copyright (C) 2009 VIA Technologies, Inc. + * + * based on existing coretemp.c, which is + * + * Copyright (C) 2007 Rudolf Marek + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "via_cputemp" + +enum { SHOW_TEMP, SHOW_LABEL, SHOW_NAME }; + +/* + * Functions declaration + */ + +struct via_cputemp_data { + struct device *hwmon_dev; + const char *name; + u8 vrm; + u32 id; + u32 msr_temp; + u32 msr_vid; +}; + +/* + * Sysfs stuff + */ + +static ssize_t show_name(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + int ret; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct via_cputemp_data *data = dev_get_drvdata(dev); + + if (attr->index == SHOW_NAME) + ret = sprintf(buf, "%s\n", data->name); + else /* show label */ + ret = sprintf(buf, "Core %d\n", data->id); + return ret; +} + +static ssize_t show_temp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct via_cputemp_data *data = dev_get_drvdata(dev); + u32 eax, edx; + int err; + + err = rdmsr_safe_on_cpu(data->id, data->msr_temp, &eax, &edx); + if (err) + return -EAGAIN; + + return sprintf(buf, "%lu\n", ((unsigned long)eax & 0xffffff) * 1000); +} + +static ssize_t cpu0_vid_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct via_cputemp_data *data = dev_get_drvdata(dev); + u32 eax, edx; + int err; + + err = rdmsr_safe_on_cpu(data->id, data->msr_vid, &eax, &edx); + if (err) + return -EAGAIN; + + return sprintf(buf, "%d\n", vid_from_reg(~edx & 0x7f, data->vrm)); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, + SHOW_TEMP); +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_name, NULL, SHOW_LABEL); +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, SHOW_NAME); + +static struct attribute *via_cputemp_attributes[] = { + &sensor_dev_attr_name.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + NULL +}; + +static const struct attribute_group via_cputemp_group = { + .attrs = via_cputemp_attributes, +}; + +/* Optional attributes */ +static DEVICE_ATTR_RO(cpu0_vid); + +static int via_cputemp_probe(struct platform_device *pdev) +{ + struct via_cputemp_data *data; + struct cpuinfo_x86 *c = &cpu_data(pdev->id); + int err; + u32 eax, edx; + + data = devm_kzalloc(&pdev->dev, sizeof(struct via_cputemp_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->id = pdev->id; + data->name = "via_cputemp"; + + if (c->x86 == 7) { + data->msr_temp = 0x1423; + } else { + switch (c->x86_model) { + case 0xA: + /* C7 A */ + case 0xD: + /* C7 D */ + data->msr_temp = 0x1169; + data->msr_vid = 0x198; + break; + case 0xF: + /* Nano */ + data->msr_temp = 0x1423; + break; + default: + return -ENODEV; + } + } + + /* test if we can access the TEMPERATURE MSR */ + err = rdmsr_safe_on_cpu(data->id, data->msr_temp, &eax, &edx); + if (err) { + dev_err(&pdev->dev, + "Unable to access TEMPERATURE MSR, giving up\n"); + return err; + } + + platform_set_drvdata(pdev, data); + + err = sysfs_create_group(&pdev->dev.kobj, &via_cputemp_group); + if (err) + return err; + + if (data->msr_vid) + data->vrm = vid_which_vrm(); + + if (data->vrm) { + err = device_create_file(&pdev->dev, &dev_attr_cpu0_vid); + if (err) + goto exit_remove; + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", + err); + goto exit_remove; + } + + return 0; + +exit_remove: + if (data->vrm) + device_remove_file(&pdev->dev, &dev_attr_cpu0_vid); + sysfs_remove_group(&pdev->dev.kobj, &via_cputemp_group); + return err; +} + +static int via_cputemp_remove(struct platform_device *pdev) +{ + struct via_cputemp_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + if (data->vrm) + device_remove_file(&pdev->dev, &dev_attr_cpu0_vid); + sysfs_remove_group(&pdev->dev.kobj, &via_cputemp_group); + return 0; +} + +static struct platform_driver via_cputemp_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = via_cputemp_probe, + .remove = via_cputemp_remove, +}; + +struct pdev_entry { + struct list_head list; + struct platform_device *pdev; + unsigned int cpu; +}; + +static LIST_HEAD(pdev_list); +static DEFINE_MUTEX(pdev_list_mutex); + +static int via_cputemp_online(unsigned int cpu) +{ + int err; + struct platform_device *pdev; + struct pdev_entry *pdev_entry; + + pdev = platform_device_alloc(DRVNAME, cpu); + if (!pdev) { + err = -ENOMEM; + pr_err("Device allocation failed\n"); + goto exit; + } + + pdev_entry = kzalloc(sizeof(struct pdev_entry), GFP_KERNEL); + if (!pdev_entry) { + err = -ENOMEM; + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_free; + } + + pdev_entry->pdev = pdev; + pdev_entry->cpu = cpu; + mutex_lock(&pdev_list_mutex); + list_add_tail(&pdev_entry->list, &pdev_list); + mutex_unlock(&pdev_list_mutex); + + return 0; + +exit_device_free: + kfree(pdev_entry); +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + +static int via_cputemp_down_prep(unsigned int cpu) +{ + struct pdev_entry *p; + + mutex_lock(&pdev_list_mutex); + list_for_each_entry(p, &pdev_list, list) { + if (p->cpu == cpu) { + platform_device_unregister(p->pdev); + list_del(&p->list); + mutex_unlock(&pdev_list_mutex); + kfree(p); + return 0; + } + } + mutex_unlock(&pdev_list_mutex); + return 0; +} + +static const struct x86_cpu_id __initconst cputemp_ids[] = { + { X86_VENDOR_CENTAUR, 6, 0xa, }, /* C7 A */ + { X86_VENDOR_CENTAUR, 6, 0xd, }, /* C7 D */ + { X86_VENDOR_CENTAUR, 6, 0xf, }, /* Nano */ + { X86_VENDOR_CENTAUR, 7, X86_MODEL_ANY, }, + {} +}; +MODULE_DEVICE_TABLE(x86cpu, cputemp_ids); + +static enum cpuhp_state via_temp_online; + +static int __init via_cputemp_init(void) +{ + int err; + + if (!x86_match_cpu(cputemp_ids)) + return -ENODEV; + + err = platform_driver_register(&via_cputemp_driver); + if (err) + goto exit; + + err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "hwmon/via:online", + via_cputemp_online, via_cputemp_down_prep); + if (err < 0) + goto exit_driver_unreg; + via_temp_online = err; + +#ifndef CONFIG_HOTPLUG_CPU + if (list_empty(&pdev_list)) { + err = -ENODEV; + goto exit_hp_unreg; + } +#endif + return 0; + +#ifndef CONFIG_HOTPLUG_CPU +exit_hp_unreg: + cpuhp_remove_state_nocalls(via_temp_online); +#endif +exit_driver_unreg: + platform_driver_unregister(&via_cputemp_driver); +exit: + return err; +} + +static void __exit via_cputemp_exit(void) +{ + cpuhp_remove_state(via_temp_online); + platform_driver_unregister(&via_cputemp_driver); +} + +MODULE_AUTHOR("Harald Welte "); +MODULE_DESCRIPTION("VIA CPU temperature monitor"); +MODULE_LICENSE("GPL"); + +module_init(via_cputemp_init) +module_exit(via_cputemp_exit) diff --git a/drivers/hwmon/via686a.c b/drivers/hwmon/via686a.c new file mode 100644 index 000000000..81f35e3a0 --- /dev/null +++ b/drivers/hwmon/via686a.c @@ -0,0 +1,964 @@ +/* + * via686a.c - Part of lm_sensors, Linux kernel modules + * for hardware monitoring + * + * Copyright (c) 1998 - 2002 Frodo Looijaard , + * Kyösti Mälkki , + * Mark Studebaker , + * and Bob Dougherty + * + * (Some conversion-factor data were contributed by Jonathan Teh Soon Yew + * and Alex van Kaam .) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Supports the Via VT82C686A, VT82C686B south bridges. + * Reports all as a 686A. + * Warning - only supports a single device. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * If force_addr is set to anything different from 0, we forcibly enable + * the device at the given address. + */ +static unsigned short force_addr; +module_param(force_addr, ushort, 0); +MODULE_PARM_DESC(force_addr, + "Initialize the base address of the sensors"); + +static struct platform_device *pdev; + +/* + * The Via 686a southbridge has a LM78-like chip integrated on the same IC. + * This driver is a customized copy of lm78.c + */ + +/* Many VIA686A constants specified below */ + +/* Length of ISA address segment */ +#define VIA686A_EXTENT 0x80 +#define VIA686A_BASE_REG 0x70 +#define VIA686A_ENABLE_REG 0x74 + +/* The VIA686A registers */ +/* ins numbered 0-4 */ +#define VIA686A_REG_IN_MAX(nr) (0x2b + ((nr) * 2)) +#define VIA686A_REG_IN_MIN(nr) (0x2c + ((nr) * 2)) +#define VIA686A_REG_IN(nr) (0x22 + (nr)) + +/* fans numbered 1-2 */ +#define VIA686A_REG_FAN_MIN(nr) (0x3a + (nr)) +#define VIA686A_REG_FAN(nr) (0x28 + (nr)) + +/* temps numbered 1-3 */ +static const u8 VIA686A_REG_TEMP[] = { 0x20, 0x21, 0x1f }; +static const u8 VIA686A_REG_TEMP_OVER[] = { 0x39, 0x3d, 0x1d }; +static const u8 VIA686A_REG_TEMP_HYST[] = { 0x3a, 0x3e, 0x1e }; +/* bits 7-6 */ +#define VIA686A_REG_TEMP_LOW1 0x4b +/* 2 = bits 5-4, 3 = bits 7-6 */ +#define VIA686A_REG_TEMP_LOW23 0x49 + +#define VIA686A_REG_ALARM1 0x41 +#define VIA686A_REG_ALARM2 0x42 +#define VIA686A_REG_FANDIV 0x47 +#define VIA686A_REG_CONFIG 0x40 +/* + * The following register sets temp interrupt mode (bits 1-0 for temp1, + * 3-2 for temp2, 5-4 for temp3). Modes are: + * 00 interrupt stays as long as value is out-of-range + * 01 interrupt is cleared once register is read (default) + * 10 comparator mode- like 00, but ignores hysteresis + * 11 same as 00 + */ +#define VIA686A_REG_TEMP_MODE 0x4b +/* We'll just assume that you want to set all 3 simultaneously: */ +#define VIA686A_TEMP_MODE_MASK 0x3F +#define VIA686A_TEMP_MODE_CONTINUOUS 0x00 + +/* + * Conversions. Limit checking is only done on the TO_REG + * variants. + * + ******** VOLTAGE CONVERSIONS (Bob Dougherty) ******** + * From HWMon.cpp (Copyright 1998-2000 Jonathan Teh Soon Yew): + * voltagefactor[0]=1.25/2628; (2628/1.25=2102.4) // Vccp + * voltagefactor[1]=1.25/2628; (2628/1.25=2102.4) // +2.5V + * voltagefactor[2]=1.67/2628; (2628/1.67=1573.7) // +3.3V + * voltagefactor[3]=2.6/2628; (2628/2.60=1010.8) // +5V + * voltagefactor[4]=6.3/2628; (2628/6.30=417.14) // +12V + * in[i]=(data[i+2]*25.0+133)*voltagefactor[i]; + * That is: + * volts = (25*regVal+133)*factor + * regVal = (volts/factor-133)/25 + * (These conversions were contributed by Jonathan Teh Soon Yew + * ) + */ +static inline u8 IN_TO_REG(long val, int in_num) +{ + /* + * To avoid floating point, we multiply constants by 10 (100 for +12V). + * Rounding is done (120500 is actually 133000 - 12500). + * Remember that val is expressed in 0.001V/bit, which is why we divide + * by an additional 10000 (100000 for +12V): 1000 for val and 10 (100) + * for the constants. + */ + if (in_num <= 1) + return (u8) clamp_val((val * 21024 - 1205000) / 250000, 0, 255); + else if (in_num == 2) + return (u8) clamp_val((val * 15737 - 1205000) / 250000, 0, 255); + else if (in_num == 3) + return (u8) clamp_val((val * 10108 - 1205000) / 250000, 0, 255); + else + return (u8) clamp_val((val * 41714 - 12050000) / 2500000, 0, + 255); +} + +static inline long IN_FROM_REG(u8 val, int in_num) +{ + /* + * To avoid floating point, we multiply constants by 10 (100 for +12V). + * We also multiply them by 1000 because we want 0.001V/bit for the + * output value. Rounding is done. + */ + if (in_num <= 1) + return (long) ((250000 * val + 1330000 + 21024 / 2) / 21024); + else if (in_num == 2) + return (long) ((250000 * val + 1330000 + 15737 / 2) / 15737); + else if (in_num == 3) + return (long) ((250000 * val + 1330000 + 10108 / 2) / 10108); + else + return (long) ((2500000 * val + 13300000 + 41714 / 2) / 41714); +} + +/********* FAN RPM CONVERSIONS ********/ +/* + * Higher register values = slower fans (the fan's strobe gates a counter). + * But this chip saturates back at 0, not at 255 like all the other chips. + * So, 0 means 0 RPM + */ +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 0; + rpm = clamp_val(rpm, 1, 1000000); + return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 255); +} + +#define FAN_FROM_REG(val, div) ((val) == 0 ? 0 : (val) == 255 ? 0 : 1350000 / \ + ((val) * (div))) + +/******** TEMP CONVERSIONS (Bob Dougherty) *********/ +/* + * linear fits from HWMon.cpp (Copyright 1998-2000 Jonathan Teh Soon Yew) + * if(temp<169) + * return double(temp)*0.427-32.08; + * else if(temp>=169 && temp<=202) + * return double(temp)*0.582-58.16; + * else + * return double(temp)*0.924-127.33; + * + * A fifth-order polynomial fits the unofficial data (provided by Alex van + * Kaam ) a bit better. It also give more reasonable + * numbers on my machine (ie. they agree with what my BIOS tells me). + * Here's the fifth-order fit to the 8-bit data: + * temp = 1.625093e-10*val^5 - 1.001632e-07*val^4 + 2.457653e-05*val^3 - + * 2.967619e-03*val^2 + 2.175144e-01*val - 7.090067e+0. + * + * (2000-10-25- RFD: thanks to Uwe Andersen for + * finding my typos in this formula!) + * + * Alas, none of the elegant function-fit solutions will work because we + * aren't allowed to use floating point in the kernel and doing it with + * integers doesn't provide enough precision. So we'll do boring old + * look-up table stuff. The unofficial data (see below) have effectively + * 7-bit resolution (they are rounded to the nearest degree). I'm assuming + * that the transfer function of the device is monotonic and smooth, so a + * smooth function fit to the data will allow us to get better precision. + * I used the 5th-order poly fit described above and solved for + * VIA register values 0-255. I *10 before rounding, so we get tenth-degree + * precision. (I could have done all 1024 values for our 10-bit readings, + * but the function is very linear in the useful range (0-80 deg C), so + * we'll just use linear interpolation for 10-bit readings.) So, temp_lut + * is the temp at via register values 0-255: + */ +static const s16 temp_lut[] = { + -709, -688, -667, -646, -627, -607, -589, -570, -553, -536, -519, + -503, -487, -471, -456, -442, -428, -414, -400, -387, -375, + -362, -350, -339, -327, -316, -305, -295, -285, -275, -265, + -255, -246, -237, -229, -220, -212, -204, -196, -188, -180, + -173, -166, -159, -152, -145, -139, -132, -126, -120, -114, + -108, -102, -96, -91, -85, -80, -74, -69, -64, -59, -54, -49, + -44, -39, -34, -29, -25, -20, -15, -11, -6, -2, 3, 7, 12, 16, + 20, 25, 29, 33, 37, 42, 46, 50, 54, 59, 63, 67, 71, 75, 79, 84, + 88, 92, 96, 100, 104, 109, 113, 117, 121, 125, 130, 134, 138, + 142, 146, 151, 155, 159, 163, 168, 172, 176, 181, 185, 189, + 193, 198, 202, 206, 211, 215, 219, 224, 228, 232, 237, 241, + 245, 250, 254, 259, 263, 267, 272, 276, 281, 285, 290, 294, + 299, 303, 307, 312, 316, 321, 325, 330, 334, 339, 344, 348, + 353, 357, 362, 366, 371, 376, 380, 385, 390, 395, 399, 404, + 409, 414, 419, 423, 428, 433, 438, 443, 449, 454, 459, 464, + 469, 475, 480, 486, 491, 497, 502, 508, 514, 520, 526, 532, + 538, 544, 551, 557, 564, 571, 578, 584, 592, 599, 606, 614, + 621, 629, 637, 645, 654, 662, 671, 680, 689, 698, 708, 718, + 728, 738, 749, 759, 770, 782, 793, 805, 818, 830, 843, 856, + 870, 883, 898, 912, 927, 943, 958, 975, 991, 1008, 1026, 1044, + 1062, 1081, 1101, 1121, 1141, 1162, 1184, 1206, 1229, 1252, + 1276, 1301, 1326, 1352, 1378, 1406, 1434, 1462 +}; + +/* + * the original LUT values from Alex van Kaam + * (for via register values 12-240): + * {-50,-49,-47,-45,-43,-41,-39,-38,-37,-35,-34,-33,-32,-31, + * -30,-29,-28,-27,-26,-25,-24,-24,-23,-22,-21,-20,-20,-19,-18,-17,-17,-16,-15, + * -15,-14,-14,-13,-12,-12,-11,-11,-10,-9,-9,-8,-8,-7,-7,-6,-6,-5,-5,-4,-4,-3, + * -3,-2,-2,-1,-1,0,0,1,1,1,3,3,3,4,4,4,5,5,5,6,6,7,7,8,8,9,9,9,10,10,11,11,12, + * 12,12,13,13,13,14,14,15,15,16,16,16,17,17,18,18,19,19,20,20,21,21,21,22,22, + * 22,23,23,24,24,25,25,26,26,26,27,27,27,28,28,29,29,30,30,30,31,31,32,32,33, + * 33,34,34,35,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45, + * 45,46,46,47,48,48,49,49,50,51,51,52,52,53,53,54,55,55,56,57,57,58,59,59,60, + * 61,62,62,63,64,65,66,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,83,84, + * 85,86,88,89,91,92,94,96,97,99,101,103,105,107,109,110}; + * + * + * Here's the reverse LUT. I got it by doing a 6-th order poly fit (needed + * an extra term for a good fit to these inverse data!) and then + * solving for each temp value from -50 to 110 (the useable range for + * this chip). Here's the fit: + * viaRegVal = -1.160370e-10*val^6 +3.193693e-08*val^5 - 1.464447e-06*val^4 + * - 2.525453e-04*val^3 + 1.424593e-02*val^2 + 2.148941e+00*val +7.275808e+01) + * Note that n=161: + */ +static const u8 via_lut[] = { + 12, 12, 13, 14, 14, 15, 16, 16, 17, 18, 18, 19, 20, 20, 21, 22, 23, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 39, 40, + 41, 43, 45, 46, 48, 49, 51, 53, 55, 57, 59, 60, 62, 64, 66, + 69, 71, 73, 75, 77, 79, 82, 84, 86, 88, 91, 93, 95, 98, 100, + 103, 105, 107, 110, 112, 115, 117, 119, 122, 124, 126, 129, + 131, 134, 136, 138, 140, 143, 145, 147, 150, 152, 154, 156, + 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, + 182, 183, 185, 187, 188, 190, 192, 193, 195, 196, 198, 199, + 200, 202, 203, 205, 206, 207, 208, 209, 210, 211, 212, 213, + 214, 215, 216, 217, 218, 219, 220, 221, 222, 222, 223, 224, + 225, 226, 226, 227, 228, 228, 229, 230, 230, 231, 232, 232, + 233, 233, 234, 235, 235, 236, 236, 237, 237, 238, 238, 239, + 239, 240 +}; + +/* + * Converting temps to (8-bit) hyst and over registers + * No interpolation here. + * The +50 is because the temps start at -50 + */ +static inline u8 TEMP_TO_REG(long val) +{ + return via_lut[val <= -50000 ? 0 : val >= 110000 ? 160 : + (val < 0 ? val - 500 : val + 500) / 1000 + 50]; +} + +/* for 8-bit temperature hyst and over registers */ +#define TEMP_FROM_REG(val) ((long)temp_lut[val] * 100) + +/* for 10-bit temperature readings */ +static inline long TEMP_FROM_REG10(u16 val) +{ + u16 eight_bits = val >> 2; + u16 two_bits = val & 3; + + /* no interpolation for these */ + if (two_bits == 0 || eight_bits == 255) + return TEMP_FROM_REG(eight_bits); + + /* do some linear interpolation */ + return (temp_lut[eight_bits] * (4 - two_bits) + + temp_lut[eight_bits + 1] * two_bits) * 25; +} + +#define DIV_FROM_REG(val) (1 << (val)) +#define DIV_TO_REG(val) ((val) == 8 ? 3 : (val) == 4 ? 2 : (val) == 1 ? 0 : 1) + +/* + * For each registered chip, we need to keep some data in memory. + * The structure is dynamically allocated. + */ +struct via686a_data { + unsigned short addr; + const char *name; + struct device *hwmon_dev; + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[5]; /* Register value */ + u8 in_max[5]; /* Register value */ + u8 in_min[5]; /* Register value */ + u8 fan[2]; /* Register value */ + u8 fan_min[2]; /* Register value */ + u16 temp[3]; /* Register value 10 bit */ + u8 temp_over[3]; /* Register value */ + u8 temp_hyst[3]; /* Register value */ + u8 fan_div[2]; /* Register encoding, shifted right */ + u16 alarms; /* Register encoding, combined */ +}; + +static struct pci_dev *s_bridge; /* pointer to the (only) via686a */ + +static int via686a_probe(struct platform_device *pdev); +static int via686a_remove(struct platform_device *pdev); + +static inline int via686a_read_value(struct via686a_data *data, u8 reg) +{ + return inb_p(data->addr + reg); +} + +static inline void via686a_write_value(struct via686a_data *data, u8 reg, + u8 value) +{ + outb_p(value, data->addr + reg); +} + +static struct via686a_data *via686a_update_device(struct device *dev); +static void via686a_init_device(struct via686a_data *data); + +/* following are the sysfs callback functions */ + +/* 7 voltage sensors */ +static ssize_t show_in(struct device *dev, struct device_attribute *da, + char *buf) { + struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + return sprintf(buf, "%ld\n", IN_FROM_REG(data->in[nr], nr)); +} + +static ssize_t show_in_min(struct device *dev, struct device_attribute *da, + char *buf) { + struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + return sprintf(buf, "%ld\n", IN_FROM_REG(data->in_min[nr], nr)); +} + +static ssize_t show_in_max(struct device *dev, struct device_attribute *da, + char *buf) { + struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + return sprintf(buf, "%ld\n", IN_FROM_REG(data->in_max[nr], nr)); +} + +static ssize_t set_in_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { + struct via686a_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_min[nr] = IN_TO_REG(val, nr); + via686a_write_value(data, VIA686A_REG_IN_MIN(nr), + data->in_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t set_in_max(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { + struct via686a_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_max[nr] = IN_TO_REG(val, nr); + via686a_write_value(data, VIA686A_REG_IN_MAX(nr), + data->in_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} +#define show_in_offset(offset) \ +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, \ + show_in, NULL, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \ + show_in_min, set_in_min, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \ + show_in_max, set_in_max, offset); + +show_in_offset(0); +show_in_offset(1); +show_in_offset(2); +show_in_offset(3); +show_in_offset(4); + +/* 3 temperatures */ +static ssize_t show_temp(struct device *dev, struct device_attribute *da, + char *buf) { + struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + return sprintf(buf, "%ld\n", TEMP_FROM_REG10(data->temp[nr])); +} +static ssize_t show_temp_over(struct device *dev, struct device_attribute *da, + char *buf) { + struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + return sprintf(buf, "%ld\n", TEMP_FROM_REG(data->temp_over[nr])); +} +static ssize_t show_temp_hyst(struct device *dev, struct device_attribute *da, + char *buf) { + struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + return sprintf(buf, "%ld\n", TEMP_FROM_REG(data->temp_hyst[nr])); +} +static ssize_t set_temp_over(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { + struct via686a_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_over[nr] = TEMP_TO_REG(val); + via686a_write_value(data, VIA686A_REG_TEMP_OVER[nr], + data->temp_over[nr]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t set_temp_hyst(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { + struct via686a_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_hyst[nr] = TEMP_TO_REG(val); + via686a_write_value(data, VIA686A_REG_TEMP_HYST[nr], + data->temp_hyst[nr]); + mutex_unlock(&data->update_lock); + return count; +} +#define show_temp_offset(offset) \ +static SENSOR_DEVICE_ATTR(temp##offset##_input, S_IRUGO, \ + show_temp, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max, S_IRUGO | S_IWUSR, \ + show_temp_over, set_temp_over, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max_hyst, S_IRUGO | S_IWUSR, \ + show_temp_hyst, set_temp_hyst, offset - 1); + +show_temp_offset(1); +show_temp_offset(2); +show_temp_offset(3); + +/* 2 Fans */ +static ssize_t show_fan(struct device *dev, struct device_attribute *da, + char *buf) { + struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr], + DIV_FROM_REG(data->fan_div[nr]))); +} +static ssize_t show_fan_min(struct device *dev, struct device_attribute *da, + char *buf) { + struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + return sprintf(buf, "%d\n", + FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr]))); +} +static ssize_t show_fan_div(struct device *dev, struct device_attribute *da, + char *buf) { + struct via686a_data *data = via686a_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[nr])); +} +static ssize_t set_fan_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { + struct via686a_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_min[nr] = FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); + via686a_write_value(data, VIA686A_REG_FAN_MIN(nr+1), data->fan_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t set_fan_div(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) { + struct via686a_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int nr = attr->index; + int old; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + old = via686a_read_value(data, VIA686A_REG_FANDIV); + data->fan_div[nr] = DIV_TO_REG(val); + old = (old & 0x0f) | (data->fan_div[1] << 6) | (data->fan_div[0] << 4); + via686a_write_value(data, VIA686A_REG_FANDIV, old); + mutex_unlock(&data->update_lock); + return count; +} + +#define show_fan_offset(offset) \ +static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \ + show_fan, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ + show_fan_min, set_fan_min, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR, \ + show_fan_div, set_fan_div, offset - 1); + +show_fan_offset(1); +show_fan_offset(2); + +/* Alarms */ +static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct via686a_data *data = via686a_update_device(dev); + return sprintf(buf, "%u\n", data->alarms); +} + +static DEVICE_ATTR_RO(alarms); + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct via686a_data *data = via686a_update_device(dev); + return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1); +} +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 15); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 7); + +static ssize_t name_show(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct via686a_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", data->name); +} +static DEVICE_ATTR_RO(name); + +static struct attribute *via686a_attributes[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + + &dev_attr_alarms.attr, + &dev_attr_name.attr, + NULL +}; + +static const struct attribute_group via686a_group = { + .attrs = via686a_attributes, +}; + +static struct platform_driver via686a_driver = { + .driver = { + .name = "via686a", + }, + .probe = via686a_probe, + .remove = via686a_remove, +}; + + +/* This is called when the module is loaded */ +static int via686a_probe(struct platform_device *pdev) +{ + struct via686a_data *data; + struct resource *res; + int err; + + /* Reserve the ISA region */ + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(&pdev->dev, res->start, VIA686A_EXTENT, + via686a_driver.driver.name)) { + dev_err(&pdev->dev, "Region 0x%lx-0x%lx already in use!\n", + (unsigned long)res->start, (unsigned long)res->end); + return -ENODEV; + } + + data = devm_kzalloc(&pdev->dev, sizeof(struct via686a_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + platform_set_drvdata(pdev, data); + data->addr = res->start; + data->name = "via686a"; + mutex_init(&data->update_lock); + + /* Initialize the VIA686A chip */ + via686a_init_device(data); + + /* Register sysfs hooks */ + err = sysfs_create_group(&pdev->dev.kobj, &via686a_group); + if (err) + return err; + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_files; + } + + return 0; + +exit_remove_files: + sysfs_remove_group(&pdev->dev.kobj, &via686a_group); + return err; +} + +static int via686a_remove(struct platform_device *pdev) +{ + struct via686a_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &via686a_group); + + return 0; +} + +static void via686a_update_fan_div(struct via686a_data *data) +{ + int reg = via686a_read_value(data, VIA686A_REG_FANDIV); + data->fan_div[0] = (reg >> 4) & 0x03; + data->fan_div[1] = reg >> 6; +} + +static void via686a_init_device(struct via686a_data *data) +{ + u8 reg; + + /* Start monitoring */ + reg = via686a_read_value(data, VIA686A_REG_CONFIG); + via686a_write_value(data, VIA686A_REG_CONFIG, (reg | 0x01) & 0x7F); + + /* Configure temp interrupt mode for continuous-interrupt operation */ + reg = via686a_read_value(data, VIA686A_REG_TEMP_MODE); + via686a_write_value(data, VIA686A_REG_TEMP_MODE, + (reg & ~VIA686A_TEMP_MODE_MASK) + | VIA686A_TEMP_MODE_CONTINUOUS); + + /* Pre-read fan clock divisor values */ + via686a_update_fan_div(data); +} + +static struct via686a_data *via686a_update_device(struct device *dev) +{ + struct via686a_data *data = dev_get_drvdata(dev); + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + for (i = 0; i <= 4; i++) { + data->in[i] = + via686a_read_value(data, VIA686A_REG_IN(i)); + data->in_min[i] = via686a_read_value(data, + VIA686A_REG_IN_MIN + (i)); + data->in_max[i] = + via686a_read_value(data, VIA686A_REG_IN_MAX(i)); + } + for (i = 1; i <= 2; i++) { + data->fan[i - 1] = + via686a_read_value(data, VIA686A_REG_FAN(i)); + data->fan_min[i - 1] = via686a_read_value(data, + VIA686A_REG_FAN_MIN(i)); + } + for (i = 0; i <= 2; i++) { + data->temp[i] = via686a_read_value(data, + VIA686A_REG_TEMP[i]) << 2; + data->temp_over[i] = + via686a_read_value(data, + VIA686A_REG_TEMP_OVER[i]); + data->temp_hyst[i] = + via686a_read_value(data, + VIA686A_REG_TEMP_HYST[i]); + } + /* + * add in lower 2 bits + * temp1 uses bits 7-6 of VIA686A_REG_TEMP_LOW1 + * temp2 uses bits 5-4 of VIA686A_REG_TEMP_LOW23 + * temp3 uses bits 7-6 of VIA686A_REG_TEMP_LOW23 + */ + data->temp[0] |= (via686a_read_value(data, + VIA686A_REG_TEMP_LOW1) + & 0xc0) >> 6; + data->temp[1] |= + (via686a_read_value(data, VIA686A_REG_TEMP_LOW23) & + 0x30) >> 4; + data->temp[2] |= + (via686a_read_value(data, VIA686A_REG_TEMP_LOW23) & + 0xc0) >> 6; + + via686a_update_fan_div(data); + data->alarms = + via686a_read_value(data, + VIA686A_REG_ALARM1) | + (via686a_read_value(data, VIA686A_REG_ALARM2) << 8); + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static const struct pci_device_id via686a_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C686_4) }, + { } +}; +MODULE_DEVICE_TABLE(pci, via686a_pci_ids); + +static int via686a_device_add(unsigned short address) +{ + struct resource res = { + .start = address, + .end = address + VIA686A_EXTENT - 1, + .name = "via686a", + .flags = IORESOURCE_IO, + }; + int err; + + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + + pdev = platform_device_alloc("via686a", address); + if (!pdev) { + err = -ENOMEM; + pr_err("Device allocation failed\n"); + goto exit; + } + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + +static int via686a_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + u16 address, val; + + if (force_addr) { + address = force_addr & ~(VIA686A_EXTENT - 1); + dev_warn(&dev->dev, "Forcing ISA address 0x%x\n", address); + if (PCIBIOS_SUCCESSFUL != + pci_write_config_word(dev, VIA686A_BASE_REG, address | 1)) + return -ENODEV; + } + if (PCIBIOS_SUCCESSFUL != + pci_read_config_word(dev, VIA686A_BASE_REG, &val)) + return -ENODEV; + + address = val & ~(VIA686A_EXTENT - 1); + if (address == 0) { + dev_err(&dev->dev, + "base address not set - upgrade BIOS or use force_addr=0xaddr\n"); + return -ENODEV; + } + + if (PCIBIOS_SUCCESSFUL != + pci_read_config_word(dev, VIA686A_ENABLE_REG, &val)) + return -ENODEV; + if (!(val & 0x0001)) { + if (!force_addr) { + dev_warn(&dev->dev, + "Sensors disabled, enable with force_addr=0x%x\n", + address); + return -ENODEV; + } + + dev_warn(&dev->dev, "Enabling sensors\n"); + if (PCIBIOS_SUCCESSFUL != + pci_write_config_word(dev, VIA686A_ENABLE_REG, + val | 0x0001)) + return -ENODEV; + } + + if (platform_driver_register(&via686a_driver)) + goto exit; + + /* Sets global pdev as a side effect */ + if (via686a_device_add(address)) + goto exit_unregister; + + /* + * Always return failure here. This is to allow other drivers to bind + * to this pci device. We don't really want to have control over the + * pci device, we only wanted to read as few register values from it. + */ + s_bridge = pci_dev_get(dev); + return -ENODEV; + +exit_unregister: + platform_driver_unregister(&via686a_driver); +exit: + return -ENODEV; +} + +static struct pci_driver via686a_pci_driver = { + .name = "via686a", + .id_table = via686a_pci_ids, + .probe = via686a_pci_probe, +}; + +static int __init sm_via686a_init(void) +{ + return pci_register_driver(&via686a_pci_driver); +} + +static void __exit sm_via686a_exit(void) +{ + pci_unregister_driver(&via686a_pci_driver); + if (s_bridge != NULL) { + platform_device_unregister(pdev); + platform_driver_unregister(&via686a_driver); + pci_dev_put(s_bridge); + s_bridge = NULL; + } +} + +MODULE_AUTHOR("Kyösti Mälkki , " + "Mark Studebaker " + "and Bob Dougherty "); +MODULE_DESCRIPTION("VIA 686A Sensor device"); +MODULE_LICENSE("GPL"); + +module_init(sm_via686a_init); +module_exit(sm_via686a_exit); diff --git a/drivers/hwmon/vt1211.c b/drivers/hwmon/vt1211.c new file mode 100644 index 000000000..95d5e8ec8 --- /dev/null +++ b/drivers/hwmon/vt1211.c @@ -0,0 +1,1380 @@ +/* + * vt1211.c - driver for the VIA VT1211 Super-I/O chip integrated hardware + * monitoring features + * Copyright (C) 2006 Juerg Haefliger + * + * This driver is based on the driver for kernel 2.4 by Mark D. Studebaker + * and its port to kernel 2.6 by Lars Ekman. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int uch_config = -1; +module_param(uch_config, int, 0); +MODULE_PARM_DESC(uch_config, "Initialize the universal channel configuration"); + +static int int_mode = -1; +module_param(int_mode, int, 0); +MODULE_PARM_DESC(int_mode, "Force the temperature interrupt mode"); + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +static struct platform_device *pdev; + +#define DRVNAME "vt1211" + +/* --------------------------------------------------------------------- + * Registers + * + * The sensors are defined as follows. + * + * Sensor Voltage Mode Temp Mode Notes (from the datasheet) + * -------- ------------ --------- -------------------------- + * Reading 1 temp1 Intel thermal diode + * Reading 3 temp2 Internal thermal diode + * UCH1/Reading2 in0 temp3 NTC type thermistor + * UCH2 in1 temp4 +2.5V + * UCH3 in2 temp5 VccP + * UCH4 in3 temp6 +5V + * UCH5 in4 temp7 +12V + * 3.3V in5 Internal VDD (+3.3V) + * + * --------------------------------------------------------------------- */ + +/* Voltages (in) numbered 0-5 (ix) */ +#define VT1211_REG_IN(ix) (0x21 + (ix)) +#define VT1211_REG_IN_MIN(ix) ((ix) == 0 ? 0x3e : 0x2a + 2 * (ix)) +#define VT1211_REG_IN_MAX(ix) ((ix) == 0 ? 0x3d : 0x29 + 2 * (ix)) + +/* Temperatures (temp) numbered 0-6 (ix) */ +static u8 regtemp[] = {0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25}; +static u8 regtempmax[] = {0x39, 0x1d, 0x3d, 0x2b, 0x2d, 0x2f, 0x31}; +static u8 regtemphyst[] = {0x3a, 0x1e, 0x3e, 0x2c, 0x2e, 0x30, 0x32}; + +/* Fans numbered 0-1 (ix) */ +#define VT1211_REG_FAN(ix) (0x29 + (ix)) +#define VT1211_REG_FAN_MIN(ix) (0x3b + (ix)) +#define VT1211_REG_FAN_DIV 0x47 + +/* PWMs numbered 0-1 (ix) */ +/* Auto points numbered 0-3 (ap) */ +#define VT1211_REG_PWM(ix) (0x60 + (ix)) +#define VT1211_REG_PWM_CLK 0x50 +#define VT1211_REG_PWM_CTL 0x51 +#define VT1211_REG_PWM_AUTO_TEMP(ap) (0x55 - (ap)) +#define VT1211_REG_PWM_AUTO_PWM(ix, ap) (0x58 + 2 * (ix) - (ap)) + +/* Miscellaneous registers */ +#define VT1211_REG_CONFIG 0x40 +#define VT1211_REG_ALARM1 0x41 +#define VT1211_REG_ALARM2 0x42 +#define VT1211_REG_VID 0x45 +#define VT1211_REG_UCH_CONFIG 0x4a +#define VT1211_REG_TEMP1_CONFIG 0x4b +#define VT1211_REG_TEMP2_CONFIG 0x4c + +/* In, temp & fan alarm bits */ +static const u8 bitalarmin[] = {11, 0, 1, 3, 8, 2, 9}; +static const u8 bitalarmtemp[] = {4, 15, 11, 0, 1, 3, 8}; +static const u8 bitalarmfan[] = {6, 7}; + +/* --------------------------------------------------------------------- + * Data structures and manipulation thereof + * --------------------------------------------------------------------- */ + +struct vt1211_data { + unsigned short addr; + const char *name; + struct device *hwmon_dev; + + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* Register values */ + u8 in[6]; + u8 in_max[6]; + u8 in_min[6]; + u8 temp[7]; + u8 temp_max[7]; + u8 temp_hyst[7]; + u8 fan[2]; + u8 fan_min[2]; + u8 fan_div[2]; + u8 fan_ctl; + u8 pwm[2]; + u8 pwm_ctl[2]; + u8 pwm_clk; + u8 pwm_auto_temp[4]; + u8 pwm_auto_pwm[2][4]; + u8 vid; /* Read once at init time */ + u8 vrm; + u8 uch_config; /* Read once at init time */ + u16 alarms; +}; + +/* ix = [0-5] */ +#define ISVOLT(ix, uch_config) ((ix) > 4 ? 1 : \ + !(((uch_config) >> ((ix) + 2)) & 1)) + +/* ix = [0-6] */ +#define ISTEMP(ix, uch_config) ((ix) < 2 ? 1 : \ + ((uch_config) >> (ix)) & 1) + +/* + * in5 (ix = 5) is special. It's the internal 3.3V so it's scaled in the + * driver according to the VT1211 BIOS porting guide + */ +#define IN_FROM_REG(ix, reg) ((reg) < 3 ? 0 : (ix) == 5 ? \ + (((reg) - 3) * 15882 + 479) / 958 : \ + (((reg) - 3) * 10000 + 479) / 958) +#define IN_TO_REG(ix, val) (clamp_val((ix) == 5 ? \ + ((val) * 958 + 7941) / 15882 + 3 : \ + ((val) * 958 + 5000) / 10000 + 3, 0, 255)) + +/* + * temp1 (ix = 0) is an intel thermal diode which is scaled in user space. + * temp2 (ix = 1) is the internal temp diode so it's scaled in the driver + * according to some measurements that I took on an EPIA M10000. + * temp3-7 are thermistor based so the driver returns the voltage measured at + * the pin (range 0V - 2.2V). + */ +#define TEMP_FROM_REG(ix, reg) ((ix) == 0 ? (reg) * 1000 : \ + (ix) == 1 ? (reg) < 51 ? 0 : \ + ((reg) - 51) * 1000 : \ + ((253 - (reg)) * 2200 + 105) / 210) +#define TEMP_TO_REG(ix, val) clamp_val( \ + ((ix) == 0 ? ((val) + 500) / 1000 : \ + (ix) == 1 ? ((val) + 500) / 1000 + 51 : \ + 253 - ((val) * 210 + 1100) / 2200), 0, 255) + +#define DIV_FROM_REG(reg) (1 << (reg)) + +#define RPM_FROM_REG(reg, div) (((reg) == 0) || ((reg) == 255) ? 0 : \ + 1310720 / (reg) / DIV_FROM_REG(div)) +#define RPM_TO_REG(val, div) ((val) == 0 ? 255 : \ + clamp_val((1310720 / (val) / \ + DIV_FROM_REG(div)), 1, 254)) + +/* --------------------------------------------------------------------- + * Super-I/O constants and functions + * --------------------------------------------------------------------- */ + +/* + * Configuration index port registers + * The vt1211 can live at 2 different addresses so we need to probe both + */ +#define SIO_REG_CIP1 0x2e +#define SIO_REG_CIP2 0x4e + +/* Configuration registers */ +#define SIO_VT1211_LDN 0x07 /* logical device number */ +#define SIO_VT1211_DEVID 0x20 /* device ID */ +#define SIO_VT1211_DEVREV 0x21 /* device revision */ +#define SIO_VT1211_ACTIVE 0x30 /* HW monitor active */ +#define SIO_VT1211_BADDR 0x60 /* base I/O address */ +#define SIO_VT1211_ID 0x3c /* VT1211 device ID */ + +/* VT1211 logical device numbers */ +#define SIO_VT1211_LDN_HWMON 0x0b /* HW monitor */ + +static inline void superio_outb(int sio_cip, int reg, int val) +{ + outb(reg, sio_cip); + outb(val, sio_cip + 1); +} + +static inline int superio_inb(int sio_cip, int reg) +{ + outb(reg, sio_cip); + return inb(sio_cip + 1); +} + +static inline void superio_select(int sio_cip, int ldn) +{ + outb(SIO_VT1211_LDN, sio_cip); + outb(ldn, sio_cip + 1); +} + +static inline int superio_enter(int sio_cip) +{ + if (!request_muxed_region(sio_cip, 2, DRVNAME)) + return -EBUSY; + + outb(0x87, sio_cip); + outb(0x87, sio_cip); + + return 0; +} + +static inline void superio_exit(int sio_cip) +{ + outb(0xaa, sio_cip); + release_region(sio_cip, 2); +} + +/* --------------------------------------------------------------------- + * Device I/O access + * --------------------------------------------------------------------- */ + +static inline u8 vt1211_read8(struct vt1211_data *data, u8 reg) +{ + return inb(data->addr + reg); +} + +static inline void vt1211_write8(struct vt1211_data *data, u8 reg, u8 val) +{ + outb(val, data->addr + reg); +} + +static struct vt1211_data *vt1211_update_device(struct device *dev) +{ + struct vt1211_data *data = dev_get_drvdata(dev); + int ix, val; + + mutex_lock(&data->update_lock); + + /* registers cache is refreshed after 1 second */ + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + /* read VID */ + data->vid = vt1211_read8(data, VT1211_REG_VID) & 0x1f; + + /* voltage (in) registers */ + for (ix = 0; ix < ARRAY_SIZE(data->in); ix++) { + if (ISVOLT(ix, data->uch_config)) { + data->in[ix] = vt1211_read8(data, + VT1211_REG_IN(ix)); + data->in_min[ix] = vt1211_read8(data, + VT1211_REG_IN_MIN(ix)); + data->in_max[ix] = vt1211_read8(data, + VT1211_REG_IN_MAX(ix)); + } + } + + /* temp registers */ + for (ix = 0; ix < ARRAY_SIZE(data->temp); ix++) { + if (ISTEMP(ix, data->uch_config)) { + data->temp[ix] = vt1211_read8(data, + regtemp[ix]); + data->temp_max[ix] = vt1211_read8(data, + regtempmax[ix]); + data->temp_hyst[ix] = vt1211_read8(data, + regtemphyst[ix]); + } + } + + /* fan & pwm registers */ + for (ix = 0; ix < ARRAY_SIZE(data->fan); ix++) { + data->fan[ix] = vt1211_read8(data, + VT1211_REG_FAN(ix)); + data->fan_min[ix] = vt1211_read8(data, + VT1211_REG_FAN_MIN(ix)); + data->pwm[ix] = vt1211_read8(data, + VT1211_REG_PWM(ix)); + } + val = vt1211_read8(data, VT1211_REG_FAN_DIV); + data->fan_div[0] = (val >> 4) & 3; + data->fan_div[1] = (val >> 6) & 3; + data->fan_ctl = val & 0xf; + + val = vt1211_read8(data, VT1211_REG_PWM_CTL); + data->pwm_ctl[0] = val & 0xf; + data->pwm_ctl[1] = (val >> 4) & 0xf; + + data->pwm_clk = vt1211_read8(data, VT1211_REG_PWM_CLK); + + /* pwm & temp auto point registers */ + data->pwm_auto_pwm[0][1] = vt1211_read8(data, + VT1211_REG_PWM_AUTO_PWM(0, 1)); + data->pwm_auto_pwm[0][2] = vt1211_read8(data, + VT1211_REG_PWM_AUTO_PWM(0, 2)); + data->pwm_auto_pwm[1][1] = vt1211_read8(data, + VT1211_REG_PWM_AUTO_PWM(1, 1)); + data->pwm_auto_pwm[1][2] = vt1211_read8(data, + VT1211_REG_PWM_AUTO_PWM(1, 2)); + for (ix = 0; ix < ARRAY_SIZE(data->pwm_auto_temp); ix++) { + data->pwm_auto_temp[ix] = vt1211_read8(data, + VT1211_REG_PWM_AUTO_TEMP(ix)); + } + + /* alarm registers */ + data->alarms = (vt1211_read8(data, VT1211_REG_ALARM2) << 8) | + vt1211_read8(data, VT1211_REG_ALARM1); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* --------------------------------------------------------------------- + * Voltage sysfs interfaces + * ix = [0-5] + * --------------------------------------------------------------------- */ + +#define SHOW_IN_INPUT 0 +#define SHOW_SET_IN_MIN 1 +#define SHOW_SET_IN_MAX 2 +#define SHOW_IN_ALARM 3 + +static ssize_t show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct vt1211_data *data = vt1211_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr_2 = + to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int res; + + switch (fn) { + case SHOW_IN_INPUT: + res = IN_FROM_REG(ix, data->in[ix]); + break; + case SHOW_SET_IN_MIN: + res = IN_FROM_REG(ix, data->in_min[ix]); + break; + case SHOW_SET_IN_MAX: + res = IN_FROM_REG(ix, data->in_max[ix]); + break; + case SHOW_IN_ALARM: + res = (data->alarms >> bitalarmin[ix]) & 1; + break; + default: + res = 0; + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + + return sprintf(buf, "%d\n", res); +} + +static ssize_t set_in(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct vt1211_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sensor_attr_2 = + to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + switch (fn) { + case SHOW_SET_IN_MIN: + data->in_min[ix] = IN_TO_REG(ix, val); + vt1211_write8(data, VT1211_REG_IN_MIN(ix), data->in_min[ix]); + break; + case SHOW_SET_IN_MAX: + data->in_max[ix] = IN_TO_REG(ix, val); + vt1211_write8(data, VT1211_REG_IN_MAX(ix), data->in_max[ix]); + break; + default: + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + mutex_unlock(&data->update_lock); + + return count; +} + +/* --------------------------------------------------------------------- + * Temperature sysfs interfaces + * ix = [0-6] + * --------------------------------------------------------------------- */ + +#define SHOW_TEMP_INPUT 0 +#define SHOW_SET_TEMP_MAX 1 +#define SHOW_SET_TEMP_MAX_HYST 2 +#define SHOW_TEMP_ALARM 3 + +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct vt1211_data *data = vt1211_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr_2 = + to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int res; + + switch (fn) { + case SHOW_TEMP_INPUT: + res = TEMP_FROM_REG(ix, data->temp[ix]); + break; + case SHOW_SET_TEMP_MAX: + res = TEMP_FROM_REG(ix, data->temp_max[ix]); + break; + case SHOW_SET_TEMP_MAX_HYST: + res = TEMP_FROM_REG(ix, data->temp_hyst[ix]); + break; + case SHOW_TEMP_ALARM: + res = (data->alarms >> bitalarmtemp[ix]) & 1; + break; + default: + res = 0; + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + + return sprintf(buf, "%d\n", res); +} + +static ssize_t set_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct vt1211_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sensor_attr_2 = + to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + switch (fn) { + case SHOW_SET_TEMP_MAX: + data->temp_max[ix] = TEMP_TO_REG(ix, val); + vt1211_write8(data, regtempmax[ix], + data->temp_max[ix]); + break; + case SHOW_SET_TEMP_MAX_HYST: + data->temp_hyst[ix] = TEMP_TO_REG(ix, val); + vt1211_write8(data, regtemphyst[ix], + data->temp_hyst[ix]); + break; + default: + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + mutex_unlock(&data->update_lock); + + return count; +} + +/* --------------------------------------------------------------------- + * Fan sysfs interfaces + * ix = [0-1] + * --------------------------------------------------------------------- */ + +#define SHOW_FAN_INPUT 0 +#define SHOW_SET_FAN_MIN 1 +#define SHOW_SET_FAN_DIV 2 +#define SHOW_FAN_ALARM 3 + +static ssize_t show_fan(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct vt1211_data *data = vt1211_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr_2 = + to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int res; + + switch (fn) { + case SHOW_FAN_INPUT: + res = RPM_FROM_REG(data->fan[ix], data->fan_div[ix]); + break; + case SHOW_SET_FAN_MIN: + res = RPM_FROM_REG(data->fan_min[ix], data->fan_div[ix]); + break; + case SHOW_SET_FAN_DIV: + res = DIV_FROM_REG(data->fan_div[ix]); + break; + case SHOW_FAN_ALARM: + res = (data->alarms >> bitalarmfan[ix]) & 1; + break; + default: + res = 0; + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + + return sprintf(buf, "%d\n", res); +} + +static ssize_t set_fan(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct vt1211_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sensor_attr_2 = + to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int reg; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + /* sync the data cache */ + reg = vt1211_read8(data, VT1211_REG_FAN_DIV); + data->fan_div[0] = (reg >> 4) & 3; + data->fan_div[1] = (reg >> 6) & 3; + data->fan_ctl = reg & 0xf; + + switch (fn) { + case SHOW_SET_FAN_MIN: + data->fan_min[ix] = RPM_TO_REG(val, data->fan_div[ix]); + vt1211_write8(data, VT1211_REG_FAN_MIN(ix), + data->fan_min[ix]); + break; + case SHOW_SET_FAN_DIV: + switch (val) { + case 1: + data->fan_div[ix] = 0; + break; + case 2: + data->fan_div[ix] = 1; + break; + case 4: + data->fan_div[ix] = 2; + break; + case 8: + data->fan_div[ix] = 3; + break; + default: + count = -EINVAL; + dev_warn(dev, + "fan div value %ld not supported. Choose one of 1, 2, 4, or 8.\n", + val); + goto EXIT; + } + vt1211_write8(data, VT1211_REG_FAN_DIV, + ((data->fan_div[1] << 6) | + (data->fan_div[0] << 4) | + data->fan_ctl)); + break; + default: + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + +EXIT: + mutex_unlock(&data->update_lock); + return count; +} + +/* --------------------------------------------------------------------- + * PWM sysfs interfaces + * ix = [0-1] + * --------------------------------------------------------------------- */ + +#define SHOW_PWM 0 +#define SHOW_SET_PWM_ENABLE 1 +#define SHOW_SET_PWM_FREQ 2 +#define SHOW_SET_PWM_AUTO_CHANNELS_TEMP 3 + +static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct vt1211_data *data = vt1211_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr_2 = + to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int res; + + switch (fn) { + case SHOW_PWM: + res = data->pwm[ix]; + break; + case SHOW_SET_PWM_ENABLE: + res = ((data->pwm_ctl[ix] >> 3) & 1) ? 2 : 0; + break; + case SHOW_SET_PWM_FREQ: + res = 90000 >> (data->pwm_clk & 7); + break; + case SHOW_SET_PWM_AUTO_CHANNELS_TEMP: + res = (data->pwm_ctl[ix] & 7) + 1; + break; + default: + res = 0; + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + + return sprintf(buf, "%d\n", res); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct vt1211_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sensor_attr_2 = + to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int fn = sensor_attr_2->nr; + int tmp, reg; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + switch (fn) { + case SHOW_SET_PWM_ENABLE: + /* sync the data cache */ + reg = vt1211_read8(data, VT1211_REG_FAN_DIV); + data->fan_div[0] = (reg >> 4) & 3; + data->fan_div[1] = (reg >> 6) & 3; + data->fan_ctl = reg & 0xf; + reg = vt1211_read8(data, VT1211_REG_PWM_CTL); + data->pwm_ctl[0] = reg & 0xf; + data->pwm_ctl[1] = (reg >> 4) & 0xf; + switch (val) { + case 0: + data->pwm_ctl[ix] &= 7; + /* + * disable SmartGuardian if both PWM outputs are + * disabled + */ + if ((data->pwm_ctl[ix ^ 1] & 1) == 0) + data->fan_ctl &= 0xe; + break; + case 2: + data->pwm_ctl[ix] |= 8; + data->fan_ctl |= 1; + break; + default: + count = -EINVAL; + dev_warn(dev, + "pwm mode %ld not supported. Choose one of 0 or 2.\n", + val); + goto EXIT; + } + vt1211_write8(data, VT1211_REG_PWM_CTL, + ((data->pwm_ctl[1] << 4) | + data->pwm_ctl[0])); + vt1211_write8(data, VT1211_REG_FAN_DIV, + ((data->fan_div[1] << 6) | + (data->fan_div[0] << 4) | + data->fan_ctl)); + break; + case SHOW_SET_PWM_FREQ: + val = 135000 / clamp_val(val, 135000 >> 7, 135000); + /* calculate tmp = log2(val) */ + tmp = 0; + for (val >>= 1; val > 0; val >>= 1) + tmp++; + /* sync the data cache */ + reg = vt1211_read8(data, VT1211_REG_PWM_CLK); + data->pwm_clk = (reg & 0xf8) | tmp; + vt1211_write8(data, VT1211_REG_PWM_CLK, data->pwm_clk); + break; + case SHOW_SET_PWM_AUTO_CHANNELS_TEMP: + if (val < 1 || val > 7) { + count = -EINVAL; + dev_warn(dev, + "temp channel %ld not supported. Choose a value between 1 and 7.\n", + val); + goto EXIT; + } + if (!ISTEMP(val - 1, data->uch_config)) { + count = -EINVAL; + dev_warn(dev, "temp channel %ld is not available.\n", + val); + goto EXIT; + } + /* sync the data cache */ + reg = vt1211_read8(data, VT1211_REG_PWM_CTL); + data->pwm_ctl[0] = reg & 0xf; + data->pwm_ctl[1] = (reg >> 4) & 0xf; + data->pwm_ctl[ix] = (data->pwm_ctl[ix] & 8) | (val - 1); + vt1211_write8(data, VT1211_REG_PWM_CTL, + ((data->pwm_ctl[1] << 4) | data->pwm_ctl[0])); + break; + default: + dev_dbg(dev, "Unknown attr fetch (%d)\n", fn); + } + +EXIT: + mutex_unlock(&data->update_lock); + return count; +} + +/* --------------------------------------------------------------------- + * PWM auto point definitions + * ix = [0-1] + * ap = [0-3] + * --------------------------------------------------------------------- */ + +/* + * pwm[ix+1]_auto_point[ap+1]_temp mapping table: + * Note that there is only a single set of temp auto points that controls both + * PWM controllers. We still create 2 sets of sysfs files to make it look + * more consistent even though they map to the same registers. + * + * ix ap : description + * ------------------- + * 0 0 : pwm1/2 off temperature (pwm_auto_temp[0]) + * 0 1 : pwm1/2 low speed temperature (pwm_auto_temp[1]) + * 0 2 : pwm1/2 high speed temperature (pwm_auto_temp[2]) + * 0 3 : pwm1/2 full speed temperature (pwm_auto_temp[3]) + * 1 0 : pwm1/2 off temperature (pwm_auto_temp[0]) + * 1 1 : pwm1/2 low speed temperature (pwm_auto_temp[1]) + * 1 2 : pwm1/2 high speed temperature (pwm_auto_temp[2]) + * 1 3 : pwm1/2 full speed temperature (pwm_auto_temp[3]) + */ + +static ssize_t show_pwm_auto_point_temp(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct vt1211_data *data = vt1211_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr_2 = + to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int ap = sensor_attr_2->nr; + + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->pwm_ctl[ix] & 7, + data->pwm_auto_temp[ap])); +} + +static ssize_t set_pwm_auto_point_temp(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct vt1211_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sensor_attr_2 = + to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int ap = sensor_attr_2->nr; + int reg; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + + mutex_lock(&data->update_lock); + + /* sync the data cache */ + reg = vt1211_read8(data, VT1211_REG_PWM_CTL); + data->pwm_ctl[0] = reg & 0xf; + data->pwm_ctl[1] = (reg >> 4) & 0xf; + + data->pwm_auto_temp[ap] = TEMP_TO_REG(data->pwm_ctl[ix] & 7, val); + vt1211_write8(data, VT1211_REG_PWM_AUTO_TEMP(ap), + data->pwm_auto_temp[ap]); + mutex_unlock(&data->update_lock); + + return count; +} + +/* + * pwm[ix+1]_auto_point[ap+1]_pwm mapping table: + * Note that the PWM auto points 0 & 3 are hard-wired in the VT1211 and can't + * be changed. + * + * ix ap : description + * ------------------- + * 0 0 : pwm1 off (pwm_auto_pwm[0][0], hard-wired to 0) + * 0 1 : pwm1 low speed duty cycle (pwm_auto_pwm[0][1]) + * 0 2 : pwm1 high speed duty cycle (pwm_auto_pwm[0][2]) + * 0 3 : pwm1 full speed (pwm_auto_pwm[0][3], hard-wired to 255) + * 1 0 : pwm2 off (pwm_auto_pwm[1][0], hard-wired to 0) + * 1 1 : pwm2 low speed duty cycle (pwm_auto_pwm[1][1]) + * 1 2 : pwm2 high speed duty cycle (pwm_auto_pwm[1][2]) + * 1 3 : pwm2 full speed (pwm_auto_pwm[1][3], hard-wired to 255) + */ + +static ssize_t show_pwm_auto_point_pwm(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct vt1211_data *data = vt1211_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr_2 = + to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int ap = sensor_attr_2->nr; + + return sprintf(buf, "%d\n", data->pwm_auto_pwm[ix][ap]); +} + +static ssize_t set_pwm_auto_point_pwm(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct vt1211_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sensor_attr_2 = + to_sensor_dev_attr_2(attr); + int ix = sensor_attr_2->index; + int ap = sensor_attr_2->nr; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->pwm_auto_pwm[ix][ap] = clamp_val(val, 0, 255); + vt1211_write8(data, VT1211_REG_PWM_AUTO_PWM(ix, ap), + data->pwm_auto_pwm[ix][ap]); + mutex_unlock(&data->update_lock); + + return count; +} + +/* --------------------------------------------------------------------- + * Miscellaneous sysfs interfaces (VRM, VID, name, and (legacy) alarms) + * --------------------------------------------------------------------- */ + +static ssize_t show_vrm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct vt1211_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", data->vrm); +} + +static ssize_t set_vrm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct vt1211_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 255) + return -EINVAL; + + data->vrm = val; + + return count; +} + +static ssize_t show_vid(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct vt1211_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); +} + +static ssize_t show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vt1211_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} + +static ssize_t show_alarms(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vt1211_data *data = vt1211_update_device(dev); + + return sprintf(buf, "%d\n", data->alarms); +} + +/* --------------------------------------------------------------------- + * Device attribute structs + * --------------------------------------------------------------------- */ + +#define SENSOR_ATTR_IN(ix) \ +{ SENSOR_ATTR_2(in##ix##_input, S_IRUGO, \ + show_in, NULL, SHOW_IN_INPUT, ix), \ + SENSOR_ATTR_2(in##ix##_min, S_IRUGO | S_IWUSR, \ + show_in, set_in, SHOW_SET_IN_MIN, ix), \ + SENSOR_ATTR_2(in##ix##_max, S_IRUGO | S_IWUSR, \ + show_in, set_in, SHOW_SET_IN_MAX, ix), \ + SENSOR_ATTR_2(in##ix##_alarm, S_IRUGO, \ + show_in, NULL, SHOW_IN_ALARM, ix) \ +} + +static struct sensor_device_attribute_2 vt1211_sysfs_in[][4] = { + SENSOR_ATTR_IN(0), + SENSOR_ATTR_IN(1), + SENSOR_ATTR_IN(2), + SENSOR_ATTR_IN(3), + SENSOR_ATTR_IN(4), + SENSOR_ATTR_IN(5) +}; + +#define IN_UNIT_ATTRS(X) \ +{ &vt1211_sysfs_in[X][0].dev_attr.attr, \ + &vt1211_sysfs_in[X][1].dev_attr.attr, \ + &vt1211_sysfs_in[X][2].dev_attr.attr, \ + &vt1211_sysfs_in[X][3].dev_attr.attr, \ + NULL \ +} + +static struct attribute *vt1211_in_attr[][5] = { + IN_UNIT_ATTRS(0), + IN_UNIT_ATTRS(1), + IN_UNIT_ATTRS(2), + IN_UNIT_ATTRS(3), + IN_UNIT_ATTRS(4), + IN_UNIT_ATTRS(5) +}; + +static const struct attribute_group vt1211_in_attr_group[] = { + { .attrs = vt1211_in_attr[0] }, + { .attrs = vt1211_in_attr[1] }, + { .attrs = vt1211_in_attr[2] }, + { .attrs = vt1211_in_attr[3] }, + { .attrs = vt1211_in_attr[4] }, + { .attrs = vt1211_in_attr[5] } +}; + +#define SENSOR_ATTR_TEMP(ix) \ +{ SENSOR_ATTR_2(temp##ix##_input, S_IRUGO, \ + show_temp, NULL, SHOW_TEMP_INPUT, ix-1), \ + SENSOR_ATTR_2(temp##ix##_max, S_IRUGO | S_IWUSR, \ + show_temp, set_temp, SHOW_SET_TEMP_MAX, ix-1), \ + SENSOR_ATTR_2(temp##ix##_max_hyst, S_IRUGO | S_IWUSR, \ + show_temp, set_temp, SHOW_SET_TEMP_MAX_HYST, ix-1), \ + SENSOR_ATTR_2(temp##ix##_alarm, S_IRUGO, \ + show_temp, NULL, SHOW_TEMP_ALARM, ix-1) \ +} + +static struct sensor_device_attribute_2 vt1211_sysfs_temp[][4] = { + SENSOR_ATTR_TEMP(1), + SENSOR_ATTR_TEMP(2), + SENSOR_ATTR_TEMP(3), + SENSOR_ATTR_TEMP(4), + SENSOR_ATTR_TEMP(5), + SENSOR_ATTR_TEMP(6), + SENSOR_ATTR_TEMP(7), +}; + +#define TEMP_UNIT_ATTRS(X) \ +{ &vt1211_sysfs_temp[X][0].dev_attr.attr, \ + &vt1211_sysfs_temp[X][1].dev_attr.attr, \ + &vt1211_sysfs_temp[X][2].dev_attr.attr, \ + &vt1211_sysfs_temp[X][3].dev_attr.attr, \ + NULL \ +} + +static struct attribute *vt1211_temp_attr[][5] = { + TEMP_UNIT_ATTRS(0), + TEMP_UNIT_ATTRS(1), + TEMP_UNIT_ATTRS(2), + TEMP_UNIT_ATTRS(3), + TEMP_UNIT_ATTRS(4), + TEMP_UNIT_ATTRS(5), + TEMP_UNIT_ATTRS(6) +}; + +static const struct attribute_group vt1211_temp_attr_group[] = { + { .attrs = vt1211_temp_attr[0] }, + { .attrs = vt1211_temp_attr[1] }, + { .attrs = vt1211_temp_attr[2] }, + { .attrs = vt1211_temp_attr[3] }, + { .attrs = vt1211_temp_attr[4] }, + { .attrs = vt1211_temp_attr[5] }, + { .attrs = vt1211_temp_attr[6] } +}; + +#define SENSOR_ATTR_FAN(ix) \ + SENSOR_ATTR_2(fan##ix##_input, S_IRUGO, \ + show_fan, NULL, SHOW_FAN_INPUT, ix-1), \ + SENSOR_ATTR_2(fan##ix##_min, S_IRUGO | S_IWUSR, \ + show_fan, set_fan, SHOW_SET_FAN_MIN, ix-1), \ + SENSOR_ATTR_2(fan##ix##_div, S_IRUGO | S_IWUSR, \ + show_fan, set_fan, SHOW_SET_FAN_DIV, ix-1), \ + SENSOR_ATTR_2(fan##ix##_alarm, S_IRUGO, \ + show_fan, NULL, SHOW_FAN_ALARM, ix-1) + +#define SENSOR_ATTR_PWM(ix) \ + SENSOR_ATTR_2(pwm##ix, S_IRUGO, \ + show_pwm, NULL, SHOW_PWM, ix-1), \ + SENSOR_ATTR_2(pwm##ix##_enable, S_IRUGO | S_IWUSR, \ + show_pwm, set_pwm, SHOW_SET_PWM_ENABLE, ix-1), \ + SENSOR_ATTR_2(pwm##ix##_auto_channels_temp, S_IRUGO | S_IWUSR, \ + show_pwm, set_pwm, SHOW_SET_PWM_AUTO_CHANNELS_TEMP, ix-1) + +#define SENSOR_ATTR_PWM_FREQ(ix) \ + SENSOR_ATTR_2(pwm##ix##_freq, S_IRUGO | S_IWUSR, \ + show_pwm, set_pwm, SHOW_SET_PWM_FREQ, ix-1) + +#define SENSOR_ATTR_PWM_FREQ_RO(ix) \ + SENSOR_ATTR_2(pwm##ix##_freq, S_IRUGO, \ + show_pwm, NULL, SHOW_SET_PWM_FREQ, ix-1) + +#define SENSOR_ATTR_PWM_AUTO_POINT_TEMP(ix, ap) \ + SENSOR_ATTR_2(pwm##ix##_auto_point##ap##_temp, S_IRUGO | S_IWUSR, \ + show_pwm_auto_point_temp, set_pwm_auto_point_temp, \ + ap-1, ix-1) + +#define SENSOR_ATTR_PWM_AUTO_POINT_TEMP_RO(ix, ap) \ + SENSOR_ATTR_2(pwm##ix##_auto_point##ap##_temp, S_IRUGO, \ + show_pwm_auto_point_temp, NULL, \ + ap-1, ix-1) + +#define SENSOR_ATTR_PWM_AUTO_POINT_PWM(ix, ap) \ + SENSOR_ATTR_2(pwm##ix##_auto_point##ap##_pwm, S_IRUGO | S_IWUSR, \ + show_pwm_auto_point_pwm, set_pwm_auto_point_pwm, \ + ap-1, ix-1) + +#define SENSOR_ATTR_PWM_AUTO_POINT_PWM_RO(ix, ap) \ + SENSOR_ATTR_2(pwm##ix##_auto_point##ap##_pwm, S_IRUGO, \ + show_pwm_auto_point_pwm, NULL, \ + ap-1, ix-1) + +static struct sensor_device_attribute_2 vt1211_sysfs_fan_pwm[] = { + SENSOR_ATTR_FAN(1), + SENSOR_ATTR_FAN(2), + SENSOR_ATTR_PWM(1), + SENSOR_ATTR_PWM(2), + SENSOR_ATTR_PWM_FREQ(1), + SENSOR_ATTR_PWM_FREQ_RO(2), + SENSOR_ATTR_PWM_AUTO_POINT_TEMP(1, 1), + SENSOR_ATTR_PWM_AUTO_POINT_TEMP(1, 2), + SENSOR_ATTR_PWM_AUTO_POINT_TEMP(1, 3), + SENSOR_ATTR_PWM_AUTO_POINT_TEMP(1, 4), + SENSOR_ATTR_PWM_AUTO_POINT_TEMP_RO(2, 1), + SENSOR_ATTR_PWM_AUTO_POINT_TEMP_RO(2, 2), + SENSOR_ATTR_PWM_AUTO_POINT_TEMP_RO(2, 3), + SENSOR_ATTR_PWM_AUTO_POINT_TEMP_RO(2, 4), + SENSOR_ATTR_PWM_AUTO_POINT_PWM_RO(1, 1), + SENSOR_ATTR_PWM_AUTO_POINT_PWM(1, 2), + SENSOR_ATTR_PWM_AUTO_POINT_PWM(1, 3), + SENSOR_ATTR_PWM_AUTO_POINT_PWM_RO(1, 4), + SENSOR_ATTR_PWM_AUTO_POINT_PWM_RO(2, 1), + SENSOR_ATTR_PWM_AUTO_POINT_PWM(2, 2), + SENSOR_ATTR_PWM_AUTO_POINT_PWM(2, 3), + SENSOR_ATTR_PWM_AUTO_POINT_PWM_RO(2, 4), +}; + +static struct device_attribute vt1211_sysfs_misc[] = { + __ATTR(vrm, S_IRUGO | S_IWUSR, show_vrm, set_vrm), + __ATTR(cpu0_vid, S_IRUGO, show_vid, NULL), + __ATTR(name, S_IRUGO, show_name, NULL), + __ATTR(alarms, S_IRUGO, show_alarms, NULL), +}; + +/* --------------------------------------------------------------------- + * Device registration and initialization + * --------------------------------------------------------------------- */ + +static void vt1211_init_device(struct vt1211_data *data) +{ + /* set VRM */ + data->vrm = vid_which_vrm(); + + /* Read (and initialize) UCH config */ + data->uch_config = vt1211_read8(data, VT1211_REG_UCH_CONFIG); + if (uch_config > -1) { + data->uch_config = (data->uch_config & 0x83) | + (uch_config << 2); + vt1211_write8(data, VT1211_REG_UCH_CONFIG, data->uch_config); + } + + /* + * Initialize the interrupt mode (if request at module load time). + * The VT1211 implements 3 different modes for clearing interrupts: + * 0: Clear INT when status register is read. Regenerate INT as long + * as temp stays above hysteresis limit. + * 1: Clear INT when status register is read. DON'T regenerate INT + * until temp falls below hysteresis limit and exceeds hot limit + * again. + * 2: Clear INT when temp falls below max limit. + * + * The driver only allows to force mode 0 since that's the only one + * that makes sense for 'sensors' + */ + if (int_mode == 0) { + vt1211_write8(data, VT1211_REG_TEMP1_CONFIG, 0); + vt1211_write8(data, VT1211_REG_TEMP2_CONFIG, 0); + } + + /* Fill in some hard wired values into our data struct */ + data->pwm_auto_pwm[0][3] = 255; + data->pwm_auto_pwm[1][3] = 255; +} + +static void vt1211_remove_sysfs(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int i; + + for (i = 0; i < ARRAY_SIZE(vt1211_in_attr_group); i++) + sysfs_remove_group(&dev->kobj, &vt1211_in_attr_group[i]); + + for (i = 0; i < ARRAY_SIZE(vt1211_temp_attr_group); i++) + sysfs_remove_group(&dev->kobj, &vt1211_temp_attr_group[i]); + + for (i = 0; i < ARRAY_SIZE(vt1211_sysfs_fan_pwm); i++) { + device_remove_file(dev, + &vt1211_sysfs_fan_pwm[i].dev_attr); + } + for (i = 0; i < ARRAY_SIZE(vt1211_sysfs_misc); i++) + device_remove_file(dev, &vt1211_sysfs_misc[i]); +} + +static int vt1211_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vt1211_data *data; + struct resource *res; + int i, err; + + data = devm_kzalloc(dev, sizeof(struct vt1211_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(dev, res->start, resource_size(res), + DRVNAME)) { + dev_err(dev, "Failed to request region 0x%lx-0x%lx\n", + (unsigned long)res->start, (unsigned long)res->end); + return -EBUSY; + } + data->addr = res->start; + data->name = DRVNAME; + mutex_init(&data->update_lock); + + platform_set_drvdata(pdev, data); + + /* Initialize the VT1211 chip */ + vt1211_init_device(data); + + /* Create sysfs interface files */ + for (i = 0; i < ARRAY_SIZE(vt1211_in_attr_group); i++) { + if (ISVOLT(i, data->uch_config)) { + err = sysfs_create_group(&dev->kobj, + &vt1211_in_attr_group[i]); + if (err) + goto EXIT_DEV_REMOVE; + } + } + for (i = 0; i < ARRAY_SIZE(vt1211_temp_attr_group); i++) { + if (ISTEMP(i, data->uch_config)) { + err = sysfs_create_group(&dev->kobj, + &vt1211_temp_attr_group[i]); + if (err) + goto EXIT_DEV_REMOVE; + } + } + for (i = 0; i < ARRAY_SIZE(vt1211_sysfs_fan_pwm); i++) { + err = device_create_file(dev, + &vt1211_sysfs_fan_pwm[i].dev_attr); + if (err) + goto EXIT_DEV_REMOVE; + } + for (i = 0; i < ARRAY_SIZE(vt1211_sysfs_misc); i++) { + err = device_create_file(dev, + &vt1211_sysfs_misc[i]); + if (err) + goto EXIT_DEV_REMOVE; + } + + /* Register device */ + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(dev, "Class registration failed (%d)\n", err); + goto EXIT_DEV_REMOVE_SILENT; + } + + return 0; + +EXIT_DEV_REMOVE: + dev_err(dev, "Sysfs interface creation failed (%d)\n", err); +EXIT_DEV_REMOVE_SILENT: + vt1211_remove_sysfs(pdev); + return err; +} + +static int vt1211_remove(struct platform_device *pdev) +{ + struct vt1211_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + vt1211_remove_sysfs(pdev); + + return 0; +} + +static struct platform_driver vt1211_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = vt1211_probe, + .remove = vt1211_remove, +}; + +static int __init vt1211_device_add(unsigned short address) +{ + struct resource res = { + .start = address, + .end = address + 0x7f, + .flags = IORESOURCE_IO, + }; + int err; + + pdev = platform_device_alloc(DRVNAME, address); + if (!pdev) { + err = -ENOMEM; + pr_err("Device allocation failed (%d)\n", err); + goto EXIT; + } + + res.name = pdev->name; + err = acpi_check_resource_conflict(&res); + if (err) + goto EXIT_DEV_PUT; + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto EXIT_DEV_PUT; + } + + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto EXIT_DEV_PUT; + } + + return 0; + +EXIT_DEV_PUT: + platform_device_put(pdev); +EXIT: + return err; +} + +static int __init vt1211_find(int sio_cip, unsigned short *address) +{ + int err; + int devid; + + err = superio_enter(sio_cip); + if (err) + return err; + + err = -ENODEV; + devid = force_id ? force_id : superio_inb(sio_cip, SIO_VT1211_DEVID); + if (devid != SIO_VT1211_ID) + goto EXIT; + + superio_select(sio_cip, SIO_VT1211_LDN_HWMON); + + if ((superio_inb(sio_cip, SIO_VT1211_ACTIVE) & 1) == 0) { + pr_warn("HW monitor is disabled, skipping\n"); + goto EXIT; + } + + *address = ((superio_inb(sio_cip, SIO_VT1211_BADDR) << 8) | + (superio_inb(sio_cip, SIO_VT1211_BADDR + 1))) & 0xff00; + if (*address == 0) { + pr_warn("Base address is not set, skipping\n"); + goto EXIT; + } + + err = 0; + pr_info("Found VT1211 chip at 0x%04x, revision %u\n", + *address, superio_inb(sio_cip, SIO_VT1211_DEVREV)); + +EXIT: + superio_exit(sio_cip); + return err; +} + +static int __init vt1211_init(void) +{ + int err; + unsigned short address = 0; + + err = vt1211_find(SIO_REG_CIP1, &address); + if (err) { + err = vt1211_find(SIO_REG_CIP2, &address); + if (err) + goto EXIT; + } + + if ((uch_config < -1) || (uch_config > 31)) { + err = -EINVAL; + pr_warn("Invalid UCH configuration %d. Choose a value between 0 and 31.\n", + uch_config); + goto EXIT; + } + + if ((int_mode < -1) || (int_mode > 0)) { + err = -EINVAL; + pr_warn("Invalid interrupt mode %d. Only mode 0 is supported.\n", + int_mode); + goto EXIT; + } + + err = platform_driver_register(&vt1211_driver); + if (err) + goto EXIT; + + /* Sets global pdev as a side effect */ + err = vt1211_device_add(address); + if (err) + goto EXIT_DRV_UNREGISTER; + + return 0; + +EXIT_DRV_UNREGISTER: + platform_driver_unregister(&vt1211_driver); +EXIT: + return err; +} + +static void __exit vt1211_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&vt1211_driver); +} + +MODULE_AUTHOR("Juerg Haefliger "); +MODULE_DESCRIPTION("VT1211 sensors"); +MODULE_LICENSE("GPL"); + +module_init(vt1211_init); +module_exit(vt1211_exit); diff --git a/drivers/hwmon/vt8231.c b/drivers/hwmon/vt8231.c new file mode 100644 index 000000000..367b5eb53 --- /dev/null +++ b/drivers/hwmon/vt8231.c @@ -0,0 +1,1073 @@ +/* + * vt8231.c - Part of lm_sensors, Linux kernel modules + * for hardware monitoring + * + * Copyright (c) 2005 Roger Lucas + * Copyright (c) 2002 Mark D. Studebaker + * Aaron M. Marsh + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Supports VIA VT8231 South Bridge embedded sensors + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int force_addr; +module_param(force_addr, int, 0); +MODULE_PARM_DESC(force_addr, "Initialize the base address of the sensors"); + +static struct platform_device *pdev; + +#define VT8231_EXTENT 0x80 +#define VT8231_BASE_REG 0x70 +#define VT8231_ENABLE_REG 0x74 + +/* + * The VT8231 registers + * + * The reset value for the input channel configuration is used (Reg 0x4A=0x07) + * which sets the selected inputs marked with '*' below if multiple options are + * possible: + * + * Voltage Mode Temperature Mode + * Sensor Linux Id Linux Id VIA Id + * -------- -------- -------- ------ + * CPU Diode N/A temp1 0 + * UIC1 in0 temp2 * 1 + * UIC2 in1 * temp3 2 + * UIC3 in2 * temp4 3 + * UIC4 in3 * temp5 4 + * UIC5 in4 * temp6 5 + * 3.3V in5 N/A + * + * Note that the BIOS may set the configuration register to a different value + * to match the motherboard configuration. + */ + +/* fans numbered 0-1 */ +#define VT8231_REG_FAN_MIN(nr) (0x3b + (nr)) +#define VT8231_REG_FAN(nr) (0x29 + (nr)) + +/* Voltage inputs numbered 0-5 */ + +static const u8 regvolt[] = { 0x21, 0x22, 0x23, 0x24, 0x25, 0x26 }; +static const u8 regvoltmax[] = { 0x3d, 0x2b, 0x2d, 0x2f, 0x31, 0x33 }; +static const u8 regvoltmin[] = { 0x3e, 0x2c, 0x2e, 0x30, 0x32, 0x34 }; + +/* + * Temperatures are numbered 1-6 according to the Linux kernel specification. + * + * In the VIA datasheet, however, the temperatures are numbered from zero. + * Since it is important that this driver can easily be compared to the VIA + * datasheet, we will use the VIA numbering within this driver and map the + * kernel sysfs device name to the VIA number in the sysfs callback. + */ + +#define VT8231_REG_TEMP_LOW01 0x49 +#define VT8231_REG_TEMP_LOW25 0x4d + +static const u8 regtemp[] = { 0x1f, 0x21, 0x22, 0x23, 0x24, 0x25 }; +static const u8 regtempmax[] = { 0x39, 0x3d, 0x2b, 0x2d, 0x2f, 0x31 }; +static const u8 regtempmin[] = { 0x3a, 0x3e, 0x2c, 0x2e, 0x30, 0x32 }; + +#define TEMP_FROM_REG(reg) (((253 * 4 - (reg)) * 550 + 105) / 210) +#define TEMP_MAXMIN_FROM_REG(reg) (((253 - (reg)) * 2200 + 105) / 210) +#define TEMP_MAXMIN_TO_REG(val) (253 - ((val) * 210 + 1100) / 2200) + +#define VT8231_REG_CONFIG 0x40 +#define VT8231_REG_ALARM1 0x41 +#define VT8231_REG_ALARM2 0x42 +#define VT8231_REG_FANDIV 0x47 +#define VT8231_REG_UCH_CONFIG 0x4a +#define VT8231_REG_TEMP1_CONFIG 0x4b +#define VT8231_REG_TEMP2_CONFIG 0x4c + +/* + * temps 0-5 as numbered in VIA datasheet - see later for mapping to Linux + * numbering + */ +#define ISTEMP(i, ch_config) ((i) == 0 ? 1 : \ + ((ch_config) >> ((i)+1)) & 0x01) +/* voltages 0-5 */ +#define ISVOLT(i, ch_config) ((i) == 5 ? 1 : \ + !(((ch_config) >> ((i)+2)) & 0x01)) + +#define DIV_FROM_REG(val) (1 << (val)) + +/* + * NB The values returned here are NOT temperatures. The calibration curves + * for the thermistor curves are board-specific and must go in the + * sensors.conf file. Temperature sensors are actually ten bits, but the + * VIA datasheet only considers the 8 MSBs obtained from the regtemp[] + * register. The temperature value returned should have a magnitude of 3, + * so we use the VIA scaling as the "true" scaling and use the remaining 2 + * LSBs as fractional precision. + * + * All the on-chip hardware temperature comparisons for the alarms are only + * 8-bits wide, and compare against the 8 MSBs of the temperature. The bits + * in the registers VT8231_REG_TEMP_LOW01 and VT8231_REG_TEMP_LOW25 are + * ignored. + */ + +/* + ****** FAN RPM CONVERSIONS ******** + * This chip saturates back at 0, not at 255 like many the other chips. + * So, 0 means 0 RPM + */ +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm <= 0 || rpm > 1310720) + return 0; + return clamp_val(1310720 / (rpm * div), 1, 255); +} + +#define FAN_FROM_REG(val, div) ((val) == 0 ? 0 : 1310720 / ((val) * (div))) + +struct vt8231_data { + unsigned short addr; + const char *name; + + struct mutex update_lock; + struct device *hwmon_dev; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[6]; /* Register value */ + u8 in_max[6]; /* Register value */ + u8 in_min[6]; /* Register value */ + u16 temp[6]; /* Register value 10 bit, right aligned */ + u8 temp_max[6]; /* Register value */ + u8 temp_min[6]; /* Register value */ + u8 fan[2]; /* Register value */ + u8 fan_min[2]; /* Register value */ + u8 fan_div[2]; /* Register encoding, shifted right */ + u16 alarms; /* Register encoding */ + u8 uch_config; +}; + +static struct pci_dev *s_bridge; +static int vt8231_probe(struct platform_device *pdev); +static int vt8231_remove(struct platform_device *pdev); +static struct vt8231_data *vt8231_update_device(struct device *dev); +static void vt8231_init_device(struct vt8231_data *data); + +static inline int vt8231_read_value(struct vt8231_data *data, u8 reg) +{ + return inb_p(data->addr + reg); +} + +static inline void vt8231_write_value(struct vt8231_data *data, u8 reg, + u8 value) +{ + outb_p(value, data->addr + reg); +} + +/* following are the sysfs callback functions */ +static ssize_t show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct vt8231_data *data = vt8231_update_device(dev); + + return sprintf(buf, "%d\n", ((data->in[nr] - 3) * 10000) / 958); +} + +static ssize_t show_in_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct vt8231_data *data = vt8231_update_device(dev); + + return sprintf(buf, "%d\n", ((data->in_min[nr] - 3) * 10000) / 958); +} + +static ssize_t show_in_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct vt8231_data *data = vt8231_update_device(dev); + + return sprintf(buf, "%d\n", (((data->in_max[nr] - 3) * 10000) / 958)); +} + +static ssize_t set_in_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct vt8231_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_min[nr] = clamp_val(((val * 958) / 10000) + 3, 0, 255); + vt8231_write_value(data, regvoltmin[nr], data->in_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_in_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct vt8231_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_max[nr] = clamp_val(((val * 958) / 10000) + 3, 0, 255); + vt8231_write_value(data, regvoltmax[nr], data->in_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +/* Special case for input 5 as this has 3.3V scaling built into the chip */ +static ssize_t in5_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vt8231_data *data = vt8231_update_device(dev); + + return sprintf(buf, "%d\n", + (((data->in[5] - 3) * 10000 * 54) / (958 * 34))); +} + +static ssize_t in5_min_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct vt8231_data *data = vt8231_update_device(dev); + + return sprintf(buf, "%d\n", + (((data->in_min[5] - 3) * 10000 * 54) / (958 * 34))); +} + +static ssize_t in5_max_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct vt8231_data *data = vt8231_update_device(dev); + + return sprintf(buf, "%d\n", + (((data->in_max[5] - 3) * 10000 * 54) / (958 * 34))); +} + +static ssize_t in5_min_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct vt8231_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_min[5] = clamp_val(((val * 958 * 34) / (10000 * 54)) + 3, + 0, 255); + vt8231_write_value(data, regvoltmin[5], data->in_min[5]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t in5_max_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct vt8231_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_max[5] = clamp_val(((val * 958 * 34) / (10000 * 54)) + 3, + 0, 255); + vt8231_write_value(data, regvoltmax[5], data->in_max[5]); + mutex_unlock(&data->update_lock); + return count; +} + +#define define_voltage_sysfs(offset) \ +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, \ + show_in, NULL, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \ + show_in_min, set_in_min, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \ + show_in_max, set_in_max, offset) + +define_voltage_sysfs(0); +define_voltage_sysfs(1); +define_voltage_sysfs(2); +define_voltage_sysfs(3); +define_voltage_sysfs(4); + +static DEVICE_ATTR_RO(in5_input); +static DEVICE_ATTR_RW(in5_min); +static DEVICE_ATTR_RW(in5_max); + +/* Temperatures */ +static ssize_t temp1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vt8231_data *data = vt8231_update_device(dev); + return sprintf(buf, "%d\n", data->temp[0] * 250); +} + +static ssize_t temp1_max_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct vt8231_data *data = vt8231_update_device(dev); + return sprintf(buf, "%d\n", data->temp_max[0] * 1000); +} + +static ssize_t temp1_max_hyst_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vt8231_data *data = vt8231_update_device(dev); + return sprintf(buf, "%d\n", data->temp_min[0] * 1000); +} + +static ssize_t temp1_max_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct vt8231_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_max[0] = clamp_val((val + 500) / 1000, 0, 255); + vt8231_write_value(data, regtempmax[0], data->temp_max[0]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t temp1_max_hyst_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct vt8231_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_min[0] = clamp_val((val + 500) / 1000, 0, 255); + vt8231_write_value(data, regtempmin[0], data->temp_min[0]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct vt8231_data *data = vt8231_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[nr])); +} + +static ssize_t show_temp_max(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct vt8231_data *data = vt8231_update_device(dev); + return sprintf(buf, "%d\n", TEMP_MAXMIN_FROM_REG(data->temp_max[nr])); +} + +static ssize_t show_temp_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct vt8231_data *data = vt8231_update_device(dev); + return sprintf(buf, "%d\n", TEMP_MAXMIN_FROM_REG(data->temp_min[nr])); +} + +static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct vt8231_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_max[nr] = clamp_val(TEMP_MAXMIN_TO_REG(val), 0, 255); + vt8231_write_value(data, regtempmax[nr], data->temp_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t set_temp_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct vt8231_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_min[nr] = clamp_val(TEMP_MAXMIN_TO_REG(val), 0, 255); + vt8231_write_value(data, regtempmin[nr], data->temp_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Note that these map the Linux temperature sensor numbering (1-6) to the VIA + * temperature sensor numbering (0-5) + */ +#define define_temperature_sysfs(offset) \ +static SENSOR_DEVICE_ATTR(temp##offset##_input, S_IRUGO, \ + show_temp, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max, S_IRUGO | S_IWUSR, \ + show_temp_max, set_temp_max, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max_hyst, S_IRUGO | S_IWUSR, \ + show_temp_min, set_temp_min, offset - 1) + +static DEVICE_ATTR_RO(temp1_input); +static DEVICE_ATTR_RW(temp1_max); +static DEVICE_ATTR_RW(temp1_max_hyst); + +define_temperature_sysfs(2); +define_temperature_sysfs(3); +define_temperature_sysfs(4); +define_temperature_sysfs(5); +define_temperature_sysfs(6); + +/* Fans */ +static ssize_t show_fan(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct vt8231_data *data = vt8231_update_device(dev); + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr], + DIV_FROM_REG(data->fan_div[nr]))); +} + +static ssize_t show_fan_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct vt8231_data *data = vt8231_update_device(dev); + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr]))); +} + +static ssize_t show_fan_div(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct vt8231_data *data = vt8231_update_device(dev); + return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[nr])); +} + +static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct vt8231_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_min[nr] = FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); + vt8231_write_value(data, VT8231_REG_FAN_MIN(nr), data->fan_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct vt8231_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + unsigned long val; + int nr = sensor_attr->index; + int old = vt8231_read_value(data, VT8231_REG_FANDIV); + long min = FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr])); + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + switch (val) { + case 1: + data->fan_div[nr] = 0; + break; + case 2: + data->fan_div[nr] = 1; + break; + case 4: + data->fan_div[nr] = 2; + break; + case 8: + data->fan_div[nr] = 3; + break; + default: + dev_err(dev, + "fan_div value %ld not supported. Choose one of 1, 2, 4 or 8!\n", + val); + mutex_unlock(&data->update_lock); + return -EINVAL; + } + + /* Correct the fan minimum speed */ + data->fan_min[nr] = FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr])); + vt8231_write_value(data, VT8231_REG_FAN_MIN(nr), data->fan_min[nr]); + + old = (old & 0x0f) | (data->fan_div[1] << 6) | (data->fan_div[0] << 4); + vt8231_write_value(data, VT8231_REG_FANDIV, old); + mutex_unlock(&data->update_lock); + return count; +} + + +#define define_fan_sysfs(offset) \ +static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \ + show_fan, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR, \ + show_fan_div, set_fan_div, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ + show_fan_min, set_fan_min, offset - 1) + +define_fan_sysfs(1); +define_fan_sysfs(2); + +/* Alarms */ +static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct vt8231_data *data = vt8231_update_device(dev); + return sprintf(buf, "%d\n", data->alarms); +} +static DEVICE_ATTR_RO(alarms); + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct vt8231_data *data = vt8231_update_device(dev); + return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1); +} +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp4_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp5_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp6_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 7); + +static ssize_t name_show(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct vt8231_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", data->name); +} +static DEVICE_ATTR_RO(name); + +static struct attribute *vt8231_attributes_temps[6][5] = { + { + &dev_attr_temp1_input.attr, + &dev_attr_temp1_max_hyst.attr, + &dev_attr_temp1_max.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_alarm.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp5_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp5_alarm.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp6_input.dev_attr.attr, + &sensor_dev_attr_temp6_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp6_max.dev_attr.attr, + &sensor_dev_attr_temp6_alarm.dev_attr.attr, + NULL + } +}; + +static const struct attribute_group vt8231_group_temps[6] = { + { .attrs = vt8231_attributes_temps[0] }, + { .attrs = vt8231_attributes_temps[1] }, + { .attrs = vt8231_attributes_temps[2] }, + { .attrs = vt8231_attributes_temps[3] }, + { .attrs = vt8231_attributes_temps[4] }, + { .attrs = vt8231_attributes_temps[5] }, +}; + +static struct attribute *vt8231_attributes_volts[6][5] = { + { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + NULL + }, { + &dev_attr_in5_input.attr, + &dev_attr_in5_min.attr, + &dev_attr_in5_max.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + NULL + } +}; + +static const struct attribute_group vt8231_group_volts[6] = { + { .attrs = vt8231_attributes_volts[0] }, + { .attrs = vt8231_attributes_volts[1] }, + { .attrs = vt8231_attributes_volts[2] }, + { .attrs = vt8231_attributes_volts[3] }, + { .attrs = vt8231_attributes_volts[4] }, + { .attrs = vt8231_attributes_volts[5] }, +}; + +static struct attribute *vt8231_attributes[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &dev_attr_alarms.attr, + &dev_attr_name.attr, + NULL +}; + +static const struct attribute_group vt8231_group = { + .attrs = vt8231_attributes, +}; + +static struct platform_driver vt8231_driver = { + .driver = { + .name = "vt8231", + }, + .probe = vt8231_probe, + .remove = vt8231_remove, +}; + +static const struct pci_device_id vt8231_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8231_4) }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, vt8231_pci_ids); + +static int vt8231_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id); + +static struct pci_driver vt8231_pci_driver = { + .name = "vt8231", + .id_table = vt8231_pci_ids, + .probe = vt8231_pci_probe, +}; + +static int vt8231_probe(struct platform_device *pdev) +{ + struct resource *res; + struct vt8231_data *data; + int err = 0, i; + + /* Reserve the ISA region */ + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(&pdev->dev, res->start, VT8231_EXTENT, + vt8231_driver.driver.name)) { + dev_err(&pdev->dev, "Region 0x%lx-0x%lx already in use!\n", + (unsigned long)res->start, (unsigned long)res->end); + return -ENODEV; + } + + data = devm_kzalloc(&pdev->dev, sizeof(struct vt8231_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + platform_set_drvdata(pdev, data); + data->addr = res->start; + data->name = "vt8231"; + + mutex_init(&data->update_lock); + vt8231_init_device(data); + + /* Register sysfs hooks */ + err = sysfs_create_group(&pdev->dev.kobj, &vt8231_group); + if (err) + return err; + + /* Must update device information to find out the config field */ + data->uch_config = vt8231_read_value(data, VT8231_REG_UCH_CONFIG); + + for (i = 0; i < ARRAY_SIZE(vt8231_group_temps); i++) { + if (ISTEMP(i, data->uch_config)) { + err = sysfs_create_group(&pdev->dev.kobj, + &vt8231_group_temps[i]); + if (err) + goto exit_remove_files; + } + } + + for (i = 0; i < ARRAY_SIZE(vt8231_group_volts); i++) { + if (ISVOLT(i, data->uch_config)) { + err = sysfs_create_group(&pdev->dev.kobj, + &vt8231_group_volts[i]); + if (err) + goto exit_remove_files; + } + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_files; + } + return 0; + +exit_remove_files: + for (i = 0; i < ARRAY_SIZE(vt8231_group_volts); i++) + sysfs_remove_group(&pdev->dev.kobj, &vt8231_group_volts[i]); + + for (i = 0; i < ARRAY_SIZE(vt8231_group_temps); i++) + sysfs_remove_group(&pdev->dev.kobj, &vt8231_group_temps[i]); + + sysfs_remove_group(&pdev->dev.kobj, &vt8231_group); + return err; +} + +static int vt8231_remove(struct platform_device *pdev) +{ + struct vt8231_data *data = platform_get_drvdata(pdev); + int i; + + hwmon_device_unregister(data->hwmon_dev); + + for (i = 0; i < ARRAY_SIZE(vt8231_group_volts); i++) + sysfs_remove_group(&pdev->dev.kobj, &vt8231_group_volts[i]); + + for (i = 0; i < ARRAY_SIZE(vt8231_group_temps); i++) + sysfs_remove_group(&pdev->dev.kobj, &vt8231_group_temps[i]); + + sysfs_remove_group(&pdev->dev.kobj, &vt8231_group); + + return 0; +} + +static void vt8231_init_device(struct vt8231_data *data) +{ + vt8231_write_value(data, VT8231_REG_TEMP1_CONFIG, 0); + vt8231_write_value(data, VT8231_REG_TEMP2_CONFIG, 0); +} + +static struct vt8231_data *vt8231_update_device(struct device *dev) +{ + struct vt8231_data *data = dev_get_drvdata(dev); + int i; + u16 low; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + for (i = 0; i < 6; i++) { + if (ISVOLT(i, data->uch_config)) { + data->in[i] = vt8231_read_value(data, + regvolt[i]); + data->in_min[i] = vt8231_read_value(data, + regvoltmin[i]); + data->in_max[i] = vt8231_read_value(data, + regvoltmax[i]); + } + } + for (i = 0; i < 2; i++) { + data->fan[i] = vt8231_read_value(data, + VT8231_REG_FAN(i)); + data->fan_min[i] = vt8231_read_value(data, + VT8231_REG_FAN_MIN(i)); + } + + low = vt8231_read_value(data, VT8231_REG_TEMP_LOW01); + low = (low >> 6) | ((low & 0x30) >> 2) + | (vt8231_read_value(data, VT8231_REG_TEMP_LOW25) << 4); + for (i = 0; i < 6; i++) { + if (ISTEMP(i, data->uch_config)) { + data->temp[i] = (vt8231_read_value(data, + regtemp[i]) << 2) + | ((low >> (2 * i)) & 0x03); + data->temp_max[i] = vt8231_read_value(data, + regtempmax[i]); + data->temp_min[i] = vt8231_read_value(data, + regtempmin[i]); + } + } + + i = vt8231_read_value(data, VT8231_REG_FANDIV); + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = i >> 6; + data->alarms = vt8231_read_value(data, VT8231_REG_ALARM1) | + (vt8231_read_value(data, VT8231_REG_ALARM2) << 8); + + /* Set alarm flags correctly */ + if (!data->fan[0] && data->fan_min[0]) + data->alarms |= 0x40; + else if (data->fan[0] && !data->fan_min[0]) + data->alarms &= ~0x40; + + if (!data->fan[1] && data->fan_min[1]) + data->alarms |= 0x80; + else if (data->fan[1] && !data->fan_min[1]) + data->alarms &= ~0x80; + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static int vt8231_device_add(unsigned short address) +{ + struct resource res = { + .start = address, + .end = address + VT8231_EXTENT - 1, + .name = "vt8231", + .flags = IORESOURCE_IO, + }; + int err; + + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + + pdev = platform_device_alloc("vt8231", address); + if (!pdev) { + err = -ENOMEM; + pr_err("Device allocation failed\n"); + goto exit; + } + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + +static int vt8231_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + u16 address, val; + if (force_addr) { + address = force_addr & 0xff00; + dev_warn(&dev->dev, "Forcing ISA address 0x%x\n", + address); + + if (PCIBIOS_SUCCESSFUL != + pci_write_config_word(dev, VT8231_BASE_REG, address | 1)) + return -ENODEV; + } + + if (PCIBIOS_SUCCESSFUL != pci_read_config_word(dev, VT8231_BASE_REG, + &val)) + return -ENODEV; + + address = val & ~(VT8231_EXTENT - 1); + if (address == 0) { + dev_err(&dev->dev, "base address not set - upgrade BIOS or use force_addr=0xaddr\n"); + return -ENODEV; + } + + if (PCIBIOS_SUCCESSFUL != pci_read_config_word(dev, VT8231_ENABLE_REG, + &val)) + return -ENODEV; + + if (!(val & 0x0001)) { + dev_warn(&dev->dev, "enabling sensors\n"); + if (PCIBIOS_SUCCESSFUL != + pci_write_config_word(dev, VT8231_ENABLE_REG, + val | 0x0001)) + return -ENODEV; + } + + if (platform_driver_register(&vt8231_driver)) + goto exit; + + /* Sets global pdev as a side effect */ + if (vt8231_device_add(address)) + goto exit_unregister; + + /* + * Always return failure here. This is to allow other drivers to bind + * to this pci device. We don't really want to have control over the + * pci device, we only wanted to read as few register values from it. + */ + + /* + * We do, however, mark ourselves as using the PCI device to stop it + * getting unloaded. + */ + s_bridge = pci_dev_get(dev); + return -ENODEV; + +exit_unregister: + platform_driver_unregister(&vt8231_driver); +exit: + return -ENODEV; +} + +static int __init sm_vt8231_init(void) +{ + return pci_register_driver(&vt8231_pci_driver); +} + +static void __exit sm_vt8231_exit(void) +{ + pci_unregister_driver(&vt8231_pci_driver); + if (s_bridge != NULL) { + platform_device_unregister(pdev); + platform_driver_unregister(&vt8231_driver); + pci_dev_put(s_bridge); + s_bridge = NULL; + } +} + +MODULE_AUTHOR("Roger Lucas "); +MODULE_DESCRIPTION("VT8231 sensors"); +MODULE_LICENSE("GPL"); + +module_init(sm_vt8231_init); +module_exit(sm_vt8231_exit); diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c new file mode 100644 index 000000000..ad68b6d9f --- /dev/null +++ b/drivers/hwmon/w83627ehf.c @@ -0,0 +1,2893 @@ +/* + * w83627ehf - Driver for the hardware monitoring functionality of + * the Winbond W83627EHF Super-I/O chip + * Copyright (C) 2005-2012 Jean Delvare + * Copyright (C) 2006 Yuan Mu (Winbond), + * Rudolf Marek + * David Hubbard + * Daniel J Blueman + * Copyright (C) 2010 Sheng-Yuan Huang (Nuvoton) (PS00) + * + * Shamelessly ripped from the w83627hf driver + * Copyright (C) 2003 Mark Studebaker + * + * Thanks to Leon Moonen, Steve Cliffe and Grant Coady for their help + * in testing and debugging this driver. + * + * This driver also supports the W83627EHG, which is the lead-free + * version of the W83627EHF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Supports the following chips: + * + * Chip #vin #fan #pwm #temp chip IDs man ID + * w83627ehf 10 5 4 3 0x8850 0x88 0x5ca3 + * 0x8860 0xa1 + * w83627dhg 9 5 4 3 0xa020 0xc1 0x5ca3 + * w83627dhg-p 9 5 4 3 0xb070 0xc1 0x5ca3 + * w83627uhg 8 2 2 3 0xa230 0xc1 0x5ca3 + * w83667hg 9 5 3 3 0xa510 0xc1 0x5ca3 + * w83667hg-b 9 5 3 4 0xb350 0xc1 0x5ca3 + * nct6775f 9 4 3 9 0xb470 0xc1 0x5ca3 + * nct6776f 9 5 3 9 0xC330 0xc1 0x5ca3 + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lm75.h" + +enum kinds { + w83627ehf, w83627dhg, w83627dhg_p, w83627uhg, + w83667hg, w83667hg_b, nct6775, nct6776, +}; + +/* used to set data->name = w83627ehf_device_names[data->sio_kind] */ +static const char * const w83627ehf_device_names[] = { + "w83627ehf", + "w83627dhg", + "w83627dhg", + "w83627uhg", + "w83667hg", + "w83667hg", + "nct6775", + "nct6776", +}; + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +static unsigned short fan_debounce; +module_param(fan_debounce, ushort, 0); +MODULE_PARM_DESC(fan_debounce, "Enable debouncing for fan RPM signal"); + +#define DRVNAME "w83627ehf" + +/* + * Super-I/O constants and functions + */ + +#define W83627EHF_LD_HWM 0x0b +#define W83667HG_LD_VID 0x0d + +#define SIO_REG_LDSEL 0x07 /* Logical device select */ +#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ +#define SIO_REG_EN_VRM10 0x2C /* GPIO3, GPIO4 selection */ +#define SIO_REG_ENABLE 0x30 /* Logical device enable */ +#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ +#define SIO_REG_VID_CTRL 0xF0 /* VID control */ +#define SIO_REG_VID_DATA 0xF1 /* VID data */ + +#define SIO_W83627EHF_ID 0x8850 +#define SIO_W83627EHG_ID 0x8860 +#define SIO_W83627DHG_ID 0xa020 +#define SIO_W83627DHG_P_ID 0xb070 +#define SIO_W83627UHG_ID 0xa230 +#define SIO_W83667HG_ID 0xa510 +#define SIO_W83667HG_B_ID 0xb350 +#define SIO_NCT6775_ID 0xb470 +#define SIO_NCT6776_ID 0xc330 +#define SIO_ID_MASK 0xFFF0 + +static inline void +superio_outb(int ioreg, int reg, int val) +{ + outb(reg, ioreg); + outb(val, ioreg + 1); +} + +static inline int +superio_inb(int ioreg, int reg) +{ + outb(reg, ioreg); + return inb(ioreg + 1); +} + +static inline void +superio_select(int ioreg, int ld) +{ + outb(SIO_REG_LDSEL, ioreg); + outb(ld, ioreg + 1); +} + +static inline int +superio_enter(int ioreg) +{ + if (!request_muxed_region(ioreg, 2, DRVNAME)) + return -EBUSY; + + outb(0x87, ioreg); + outb(0x87, ioreg); + + return 0; +} + +static inline void +superio_exit(int ioreg) +{ + outb(0xaa, ioreg); + outb(0x02, ioreg); + outb(0x02, ioreg + 1); + release_region(ioreg, 2); +} + +/* + * ISA constants + */ + +#define IOREGION_ALIGNMENT (~7) +#define IOREGION_OFFSET 5 +#define IOREGION_LENGTH 2 +#define ADDR_REG_OFFSET 0 +#define DATA_REG_OFFSET 1 + +#define W83627EHF_REG_BANK 0x4E +#define W83627EHF_REG_CONFIG 0x40 + +/* + * Not currently used: + * REG_MAN_ID has the value 0x5ca3 for all supported chips. + * REG_CHIP_ID == 0x88/0xa1/0xc1 depending on chip model. + * REG_MAN_ID is at port 0x4f + * REG_CHIP_ID is at port 0x58 + */ + +static const u16 W83627EHF_REG_FAN[] = { 0x28, 0x29, 0x2a, 0x3f, 0x553 }; +static const u16 W83627EHF_REG_FAN_MIN[] = { 0x3b, 0x3c, 0x3d, 0x3e, 0x55c }; + +/* The W83627EHF registers for nr=7,8,9 are in bank 5 */ +#define W83627EHF_REG_IN_MAX(nr) ((nr < 7) ? (0x2b + (nr) * 2) : \ + (0x554 + (((nr) - 7) * 2))) +#define W83627EHF_REG_IN_MIN(nr) ((nr < 7) ? (0x2c + (nr) * 2) : \ + (0x555 + (((nr) - 7) * 2))) +#define W83627EHF_REG_IN(nr) ((nr < 7) ? (0x20 + (nr)) : \ + (0x550 + (nr) - 7)) + +static const u16 W83627EHF_REG_TEMP[] = { 0x27, 0x150, 0x250, 0x7e }; +static const u16 W83627EHF_REG_TEMP_HYST[] = { 0x3a, 0x153, 0x253, 0 }; +static const u16 W83627EHF_REG_TEMP_OVER[] = { 0x39, 0x155, 0x255, 0 }; +static const u16 W83627EHF_REG_TEMP_CONFIG[] = { 0, 0x152, 0x252, 0 }; + +/* Fan clock dividers are spread over the following five registers */ +#define W83627EHF_REG_FANDIV1 0x47 +#define W83627EHF_REG_FANDIV2 0x4B +#define W83627EHF_REG_VBAT 0x5D +#define W83627EHF_REG_DIODE 0x59 +#define W83627EHF_REG_SMI_OVT 0x4C + +/* NCT6775F has its own fan divider registers */ +#define NCT6775_REG_FANDIV1 0x506 +#define NCT6775_REG_FANDIV2 0x507 +#define NCT6775_REG_FAN_DEBOUNCE 0xf0 + +#define W83627EHF_REG_ALARM1 0x459 +#define W83627EHF_REG_ALARM2 0x45A +#define W83627EHF_REG_ALARM3 0x45B + +#define W83627EHF_REG_CASEOPEN_DET 0x42 /* SMI STATUS #2 */ +#define W83627EHF_REG_CASEOPEN_CLR 0x46 /* SMI MASK #3 */ + +/* SmartFan registers */ +#define W83627EHF_REG_FAN_STEPUP_TIME 0x0f +#define W83627EHF_REG_FAN_STEPDOWN_TIME 0x0e + +/* DC or PWM output fan configuration */ +static const u8 W83627EHF_REG_PWM_ENABLE[] = { + 0x04, /* SYS FAN0 output mode and PWM mode */ + 0x04, /* CPU FAN0 output mode and PWM mode */ + 0x12, /* AUX FAN mode */ + 0x62, /* CPU FAN1 mode */ +}; + +static const u8 W83627EHF_PWM_MODE_SHIFT[] = { 0, 1, 0, 6 }; +static const u8 W83627EHF_PWM_ENABLE_SHIFT[] = { 2, 4, 1, 4 }; + +/* FAN Duty Cycle, be used to control */ +static const u16 W83627EHF_REG_PWM[] = { 0x01, 0x03, 0x11, 0x61 }; +static const u16 W83627EHF_REG_TARGET[] = { 0x05, 0x06, 0x13, 0x63 }; +static const u8 W83627EHF_REG_TOLERANCE[] = { 0x07, 0x07, 0x14, 0x62 }; + +/* Advanced Fan control, some values are common for all fans */ +static const u16 W83627EHF_REG_FAN_START_OUTPUT[] = { 0x0a, 0x0b, 0x16, 0x65 }; +static const u16 W83627EHF_REG_FAN_STOP_OUTPUT[] = { 0x08, 0x09, 0x15, 0x64 }; +static const u16 W83627EHF_REG_FAN_STOP_TIME[] = { 0x0c, 0x0d, 0x17, 0x66 }; + +static const u16 W83627EHF_REG_FAN_MAX_OUTPUT_COMMON[] + = { 0xff, 0x67, 0xff, 0x69 }; +static const u16 W83627EHF_REG_FAN_STEP_OUTPUT_COMMON[] + = { 0xff, 0x68, 0xff, 0x6a }; + +static const u16 W83627EHF_REG_FAN_MAX_OUTPUT_W83667_B[] = { 0x67, 0x69, 0x6b }; +static const u16 W83627EHF_REG_FAN_STEP_OUTPUT_W83667_B[] + = { 0x68, 0x6a, 0x6c }; + +static const u16 W83627EHF_REG_TEMP_OFFSET[] = { 0x454, 0x455, 0x456 }; + +static const u16 NCT6775_REG_TARGET[] = { 0x101, 0x201, 0x301 }; +static const u16 NCT6775_REG_FAN_MODE[] = { 0x102, 0x202, 0x302 }; +static const u16 NCT6775_REG_FAN_STOP_OUTPUT[] = { 0x105, 0x205, 0x305 }; +static const u16 NCT6775_REG_FAN_START_OUTPUT[] = { 0x106, 0x206, 0x306 }; +static const u16 NCT6775_REG_FAN_STOP_TIME[] = { 0x107, 0x207, 0x307 }; +static const u16 NCT6775_REG_PWM[] = { 0x109, 0x209, 0x309 }; +static const u16 NCT6775_REG_FAN_MAX_OUTPUT[] = { 0x10a, 0x20a, 0x30a }; +static const u16 NCT6775_REG_FAN_STEP_OUTPUT[] = { 0x10b, 0x20b, 0x30b }; +static const u16 NCT6775_REG_FAN[] = { 0x630, 0x632, 0x634, 0x636, 0x638 }; +static const u16 NCT6776_REG_FAN_MIN[] = { 0x63a, 0x63c, 0x63e, 0x640, 0x642}; + +static const u16 NCT6775_REG_TEMP[] + = { 0x27, 0x150, 0x250, 0x73, 0x75, 0x77, 0x62b, 0x62c, 0x62d }; +static const u16 NCT6775_REG_TEMP_CONFIG[] + = { 0, 0x152, 0x252, 0, 0, 0, 0x628, 0x629, 0x62A }; +static const u16 NCT6775_REG_TEMP_HYST[] + = { 0x3a, 0x153, 0x253, 0, 0, 0, 0x673, 0x678, 0x67D }; +static const u16 NCT6775_REG_TEMP_OVER[] + = { 0x39, 0x155, 0x255, 0, 0, 0, 0x672, 0x677, 0x67C }; +static const u16 NCT6775_REG_TEMP_SOURCE[] + = { 0x621, 0x622, 0x623, 0x100, 0x200, 0x300, 0x624, 0x625, 0x626 }; + +static const char *const w83667hg_b_temp_label[] = { + "SYSTIN", + "CPUTIN", + "AUXTIN", + "AMDTSI", + "PECI Agent 1", + "PECI Agent 2", + "PECI Agent 3", + "PECI Agent 4" +}; + +static const char *const nct6775_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN", + "AMD SB-TSI", + "PECI Agent 0", + "PECI Agent 1", + "PECI Agent 2", + "PECI Agent 3", + "PECI Agent 4", + "PECI Agent 5", + "PECI Agent 6", + "PECI Agent 7", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "PCH_DIM0_TEMP", + "PCH_DIM1_TEMP", + "PCH_DIM2_TEMP", + "PCH_DIM3_TEMP" +}; + +static const char *const nct6776_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN", + "SMBUSMASTER 0", + "SMBUSMASTER 1", + "SMBUSMASTER 2", + "SMBUSMASTER 3", + "SMBUSMASTER 4", + "SMBUSMASTER 5", + "SMBUSMASTER 6", + "SMBUSMASTER 7", + "PECI Agent 0", + "PECI Agent 1", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "PCH_DIM0_TEMP", + "PCH_DIM1_TEMP", + "PCH_DIM2_TEMP", + "PCH_DIM3_TEMP", + "BYTE_TEMP" +}; + +#define NUM_REG_TEMP ARRAY_SIZE(NCT6775_REG_TEMP) + +static int is_word_sized(u16 reg) +{ + return ((((reg & 0xff00) == 0x100 + || (reg & 0xff00) == 0x200) + && ((reg & 0x00ff) == 0x50 + || (reg & 0x00ff) == 0x53 + || (reg & 0x00ff) == 0x55)) + || (reg & 0xfff0) == 0x630 + || reg == 0x640 || reg == 0x642 + || ((reg & 0xfff0) == 0x650 + && (reg & 0x000f) >= 0x06) + || reg == 0x73 || reg == 0x75 || reg == 0x77 + ); +} + +/* + * Conversions + */ + +/* 1 is PWM mode, output in ms */ +static inline unsigned int step_time_from_reg(u8 reg, u8 mode) +{ + return mode ? 100 * reg : 400 * reg; +} + +static inline u8 step_time_to_reg(unsigned int msec, u8 mode) +{ + return clamp_val((mode ? (msec + 50) / 100 : (msec + 200) / 400), + 1, 255); +} + +static unsigned int fan_from_reg8(u16 reg, unsigned int divreg) +{ + if (reg == 0 || reg == 255) + return 0; + return 1350000U / (reg << divreg); +} + +static unsigned int fan_from_reg13(u16 reg, unsigned int divreg) +{ + if ((reg & 0xff1f) == 0xff1f) + return 0; + + reg = (reg & 0x1f) | ((reg & 0xff00) >> 3); + + if (reg == 0) + return 0; + + return 1350000U / reg; +} + +static unsigned int fan_from_reg16(u16 reg, unsigned int divreg) +{ + if (reg == 0 || reg == 0xffff) + return 0; + + /* + * Even though the registers are 16 bit wide, the fan divisor + * still applies. + */ + return 1350000U / (reg << divreg); +} + +static inline unsigned int +div_from_reg(u8 reg) +{ + return 1 << reg; +} + +/* + * Some of the voltage inputs have internal scaling, the tables below + * contain 8 (the ADC LSB in mV) * scaling factor * 100 + */ +static const u16 scale_in_common[10] = { + 800, 800, 1600, 1600, 800, 800, 800, 1600, 1600, 800 +}; +static const u16 scale_in_w83627uhg[9] = { + 800, 800, 3328, 3424, 800, 800, 0, 3328, 3400 +}; + +static inline long in_from_reg(u8 reg, u8 nr, const u16 *scale_in) +{ + return DIV_ROUND_CLOSEST(reg * scale_in[nr], 100); +} + +static inline u8 in_to_reg(u32 val, u8 nr, const u16 *scale_in) +{ + return clamp_val(DIV_ROUND_CLOSEST(val * 100, scale_in[nr]), 0, 255); +} + +/* + * Data structures and manipulation thereof + */ + +struct w83627ehf_data { + int addr; /* IO base of hw monitor block */ + const char *name; + + struct device *hwmon_dev; + struct mutex lock; + + u16 reg_temp[NUM_REG_TEMP]; + u16 reg_temp_over[NUM_REG_TEMP]; + u16 reg_temp_hyst[NUM_REG_TEMP]; + u16 reg_temp_config[NUM_REG_TEMP]; + u8 temp_src[NUM_REG_TEMP]; + const char * const *temp_label; + + const u16 *REG_PWM; + const u16 *REG_TARGET; + const u16 *REG_FAN; + const u16 *REG_FAN_MIN; + const u16 *REG_FAN_START_OUTPUT; + const u16 *REG_FAN_STOP_OUTPUT; + const u16 *REG_FAN_STOP_TIME; + const u16 *REG_FAN_MAX_OUTPUT; + const u16 *REG_FAN_STEP_OUTPUT; + const u16 *scale_in; + + unsigned int (*fan_from_reg)(u16 reg, unsigned int divreg); + unsigned int (*fan_from_reg_min)(u16 reg, unsigned int divreg); + + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* Register values */ + u8 bank; /* current register bank */ + u8 in_num; /* number of in inputs we have */ + u8 in[10]; /* Register value */ + u8 in_max[10]; /* Register value */ + u8 in_min[10]; /* Register value */ + unsigned int rpm[5]; + u16 fan_min[5]; + u8 fan_div[5]; + u8 has_fan; /* some fan inputs can be disabled */ + u8 has_fan_min; /* some fans don't have min register */ + bool has_fan_div; + u8 temp_type[3]; + s8 temp_offset[3]; + s16 temp[9]; + s16 temp_max[9]; + s16 temp_max_hyst[9]; + u32 alarms; + u8 caseopen; + + u8 pwm_mode[4]; /* 0->DC variable voltage, 1->PWM variable duty cycle */ + u8 pwm_enable[4]; /* 1->manual + * 2->thermal cruise mode (also called SmartFan I) + * 3->fan speed cruise mode + * 4->variable thermal cruise (also called + * SmartFan III) + * 5->enhanced variable thermal cruise (also called + * SmartFan IV) + */ + u8 pwm_enable_orig[4]; /* original value of pwm_enable */ + u8 pwm_num; /* number of pwm */ + u8 pwm[4]; + u8 target_temp[4]; + u8 tolerance[4]; + + u8 fan_start_output[4]; /* minimum fan speed when spinning up */ + u8 fan_stop_output[4]; /* minimum fan speed when spinning down */ + u8 fan_stop_time[4]; /* time at minimum before disabling fan */ + u8 fan_max_output[4]; /* maximum fan speed */ + u8 fan_step_output[4]; /* rate of change output value */ + + u8 vid; + u8 vrm; + + u16 have_temp; + u16 have_temp_offset; + u8 in6_skip:1; + u8 temp3_val_only:1; + +#ifdef CONFIG_PM + /* Remember extra register values over suspend/resume */ + u8 vbat; + u8 fandiv1; + u8 fandiv2; +#endif +}; + +struct w83627ehf_sio_data { + int sioreg; + enum kinds kind; +}; + +/* + * On older chips, only registers 0x50-0x5f are banked. + * On more recent chips, all registers are banked. + * Assume that is the case and set the bank number for each access. + * Cache the bank number so it only needs to be set if it changes. + */ +static inline void w83627ehf_set_bank(struct w83627ehf_data *data, u16 reg) +{ + u8 bank = reg >> 8; + if (data->bank != bank) { + outb_p(W83627EHF_REG_BANK, data->addr + ADDR_REG_OFFSET); + outb_p(bank, data->addr + DATA_REG_OFFSET); + data->bank = bank; + } +} + +static u16 w83627ehf_read_value(struct w83627ehf_data *data, u16 reg) +{ + int res, word_sized = is_word_sized(reg); + + mutex_lock(&data->lock); + + w83627ehf_set_bank(data, reg); + outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); + res = inb_p(data->addr + DATA_REG_OFFSET); + if (word_sized) { + outb_p((reg & 0xff) + 1, + data->addr + ADDR_REG_OFFSET); + res = (res << 8) + inb_p(data->addr + DATA_REG_OFFSET); + } + + mutex_unlock(&data->lock); + return res; +} + +static int w83627ehf_write_value(struct w83627ehf_data *data, u16 reg, + u16 value) +{ + int word_sized = is_word_sized(reg); + + mutex_lock(&data->lock); + + w83627ehf_set_bank(data, reg); + outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); + if (word_sized) { + outb_p(value >> 8, data->addr + DATA_REG_OFFSET); + outb_p((reg & 0xff) + 1, + data->addr + ADDR_REG_OFFSET); + } + outb_p(value & 0xff, data->addr + DATA_REG_OFFSET); + + mutex_unlock(&data->lock); + return 0; +} + +/* We left-align 8-bit temperature values to make the code simpler */ +static u16 w83627ehf_read_temp(struct w83627ehf_data *data, u16 reg) +{ + u16 res; + + res = w83627ehf_read_value(data, reg); + if (!is_word_sized(reg)) + res <<= 8; + + return res; +} + +static int w83627ehf_write_temp(struct w83627ehf_data *data, u16 reg, + u16 value) +{ + if (!is_word_sized(reg)) + value >>= 8; + return w83627ehf_write_value(data, reg, value); +} + +/* This function assumes that the caller holds data->update_lock */ +static void nct6775_write_fan_div(struct w83627ehf_data *data, int nr) +{ + u8 reg; + + switch (nr) { + case 0: + reg = (w83627ehf_read_value(data, NCT6775_REG_FANDIV1) & 0x70) + | (data->fan_div[0] & 0x7); + w83627ehf_write_value(data, NCT6775_REG_FANDIV1, reg); + break; + case 1: + reg = (w83627ehf_read_value(data, NCT6775_REG_FANDIV1) & 0x7) + | ((data->fan_div[1] << 4) & 0x70); + w83627ehf_write_value(data, NCT6775_REG_FANDIV1, reg); + break; + case 2: + reg = (w83627ehf_read_value(data, NCT6775_REG_FANDIV2) & 0x70) + | (data->fan_div[2] & 0x7); + w83627ehf_write_value(data, NCT6775_REG_FANDIV2, reg); + break; + case 3: + reg = (w83627ehf_read_value(data, NCT6775_REG_FANDIV2) & 0x7) + | ((data->fan_div[3] << 4) & 0x70); + w83627ehf_write_value(data, NCT6775_REG_FANDIV2, reg); + break; + } +} + +/* This function assumes that the caller holds data->update_lock */ +static void w83627ehf_write_fan_div(struct w83627ehf_data *data, int nr) +{ + u8 reg; + + switch (nr) { + case 0: + reg = (w83627ehf_read_value(data, W83627EHF_REG_FANDIV1) & 0xcf) + | ((data->fan_div[0] & 0x03) << 4); + /* fan5 input control bit is write only, compute the value */ + reg |= (data->has_fan & (1 << 4)) ? 1 : 0; + w83627ehf_write_value(data, W83627EHF_REG_FANDIV1, reg); + reg = (w83627ehf_read_value(data, W83627EHF_REG_VBAT) & 0xdf) + | ((data->fan_div[0] & 0x04) << 3); + w83627ehf_write_value(data, W83627EHF_REG_VBAT, reg); + break; + case 1: + reg = (w83627ehf_read_value(data, W83627EHF_REG_FANDIV1) & 0x3f) + | ((data->fan_div[1] & 0x03) << 6); + /* fan5 input control bit is write only, compute the value */ + reg |= (data->has_fan & (1 << 4)) ? 1 : 0; + w83627ehf_write_value(data, W83627EHF_REG_FANDIV1, reg); + reg = (w83627ehf_read_value(data, W83627EHF_REG_VBAT) & 0xbf) + | ((data->fan_div[1] & 0x04) << 4); + w83627ehf_write_value(data, W83627EHF_REG_VBAT, reg); + break; + case 2: + reg = (w83627ehf_read_value(data, W83627EHF_REG_FANDIV2) & 0x3f) + | ((data->fan_div[2] & 0x03) << 6); + w83627ehf_write_value(data, W83627EHF_REG_FANDIV2, reg); + reg = (w83627ehf_read_value(data, W83627EHF_REG_VBAT) & 0x7f) + | ((data->fan_div[2] & 0x04) << 5); + w83627ehf_write_value(data, W83627EHF_REG_VBAT, reg); + break; + case 3: + reg = (w83627ehf_read_value(data, W83627EHF_REG_DIODE) & 0xfc) + | (data->fan_div[3] & 0x03); + w83627ehf_write_value(data, W83627EHF_REG_DIODE, reg); + reg = (w83627ehf_read_value(data, W83627EHF_REG_SMI_OVT) & 0x7f) + | ((data->fan_div[3] & 0x04) << 5); + w83627ehf_write_value(data, W83627EHF_REG_SMI_OVT, reg); + break; + case 4: + reg = (w83627ehf_read_value(data, W83627EHF_REG_DIODE) & 0x73) + | ((data->fan_div[4] & 0x03) << 2) + | ((data->fan_div[4] & 0x04) << 5); + w83627ehf_write_value(data, W83627EHF_REG_DIODE, reg); + break; + } +} + +static void w83627ehf_write_fan_div_common(struct device *dev, + struct w83627ehf_data *data, int nr) +{ + struct w83627ehf_sio_data *sio_data = dev_get_platdata(dev); + + if (sio_data->kind == nct6776) + ; /* no dividers, do nothing */ + else if (sio_data->kind == nct6775) + nct6775_write_fan_div(data, nr); + else + w83627ehf_write_fan_div(data, nr); +} + +static void nct6775_update_fan_div(struct w83627ehf_data *data) +{ + u8 i; + + i = w83627ehf_read_value(data, NCT6775_REG_FANDIV1); + data->fan_div[0] = i & 0x7; + data->fan_div[1] = (i & 0x70) >> 4; + i = w83627ehf_read_value(data, NCT6775_REG_FANDIV2); + data->fan_div[2] = i & 0x7; + if (data->has_fan & (1<<3)) + data->fan_div[3] = (i & 0x70) >> 4; +} + +static void w83627ehf_update_fan_div(struct w83627ehf_data *data) +{ + int i; + + i = w83627ehf_read_value(data, W83627EHF_REG_FANDIV1); + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = (i >> 6) & 0x03; + i = w83627ehf_read_value(data, W83627EHF_REG_FANDIV2); + data->fan_div[2] = (i >> 6) & 0x03; + i = w83627ehf_read_value(data, W83627EHF_REG_VBAT); + data->fan_div[0] |= (i >> 3) & 0x04; + data->fan_div[1] |= (i >> 4) & 0x04; + data->fan_div[2] |= (i >> 5) & 0x04; + if (data->has_fan & ((1 << 3) | (1 << 4))) { + i = w83627ehf_read_value(data, W83627EHF_REG_DIODE); + data->fan_div[3] = i & 0x03; + data->fan_div[4] = ((i >> 2) & 0x03) + | ((i >> 5) & 0x04); + } + if (data->has_fan & (1 << 3)) { + i = w83627ehf_read_value(data, W83627EHF_REG_SMI_OVT); + data->fan_div[3] |= (i >> 5) & 0x04; + } +} + +static void w83627ehf_update_fan_div_common(struct device *dev, + struct w83627ehf_data *data) +{ + struct w83627ehf_sio_data *sio_data = dev_get_platdata(dev); + + if (sio_data->kind == nct6776) + ; /* no dividers, do nothing */ + else if (sio_data->kind == nct6775) + nct6775_update_fan_div(data); + else + w83627ehf_update_fan_div(data); +} + +static void nct6775_update_pwm(struct w83627ehf_data *data) +{ + int i; + int pwmcfg, fanmodecfg; + + for (i = 0; i < data->pwm_num; i++) { + pwmcfg = w83627ehf_read_value(data, + W83627EHF_REG_PWM_ENABLE[i]); + fanmodecfg = w83627ehf_read_value(data, + NCT6775_REG_FAN_MODE[i]); + data->pwm_mode[i] = + ((pwmcfg >> W83627EHF_PWM_MODE_SHIFT[i]) & 1) ? 0 : 1; + data->pwm_enable[i] = ((fanmodecfg >> 4) & 7) + 1; + data->tolerance[i] = fanmodecfg & 0x0f; + data->pwm[i] = w83627ehf_read_value(data, data->REG_PWM[i]); + } +} + +static void w83627ehf_update_pwm(struct w83627ehf_data *data) +{ + int i; + int pwmcfg = 0, tolerance = 0; /* shut up the compiler */ + + for (i = 0; i < data->pwm_num; i++) { + if (!(data->has_fan & (1 << i))) + continue; + + /* pwmcfg, tolerance mapped for i=0, i=1 to same reg */ + if (i != 1) { + pwmcfg = w83627ehf_read_value(data, + W83627EHF_REG_PWM_ENABLE[i]); + tolerance = w83627ehf_read_value(data, + W83627EHF_REG_TOLERANCE[i]); + } + data->pwm_mode[i] = + ((pwmcfg >> W83627EHF_PWM_MODE_SHIFT[i]) & 1) ? 0 : 1; + data->pwm_enable[i] = ((pwmcfg >> W83627EHF_PWM_ENABLE_SHIFT[i]) + & 3) + 1; + data->pwm[i] = w83627ehf_read_value(data, data->REG_PWM[i]); + + data->tolerance[i] = (tolerance >> (i == 1 ? 4 : 0)) & 0x0f; + } +} + +static void w83627ehf_update_pwm_common(struct device *dev, + struct w83627ehf_data *data) +{ + struct w83627ehf_sio_data *sio_data = dev_get_platdata(dev); + + if (sio_data->kind == nct6775 || sio_data->kind == nct6776) + nct6775_update_pwm(data); + else + w83627ehf_update_pwm(data); +} + +static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) +{ + struct w83627ehf_data *data = dev_get_drvdata(dev); + struct w83627ehf_sio_data *sio_data = dev_get_platdata(dev); + + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ/2) + || !data->valid) { + /* Fan clock dividers */ + w83627ehf_update_fan_div_common(dev, data); + + /* Measured voltages and limits */ + for (i = 0; i < data->in_num; i++) { + if ((i == 6) && data->in6_skip) + continue; + + data->in[i] = w83627ehf_read_value(data, + W83627EHF_REG_IN(i)); + data->in_min[i] = w83627ehf_read_value(data, + W83627EHF_REG_IN_MIN(i)); + data->in_max[i] = w83627ehf_read_value(data, + W83627EHF_REG_IN_MAX(i)); + } + + /* Measured fan speeds and limits */ + for (i = 0; i < 5; i++) { + u16 reg; + + if (!(data->has_fan & (1 << i))) + continue; + + reg = w83627ehf_read_value(data, data->REG_FAN[i]); + data->rpm[i] = data->fan_from_reg(reg, + data->fan_div[i]); + + if (data->has_fan_min & (1 << i)) + data->fan_min[i] = w83627ehf_read_value(data, + data->REG_FAN_MIN[i]); + + /* + * If we failed to measure the fan speed and clock + * divider can be increased, let's try that for next + * time + */ + if (data->has_fan_div + && (reg >= 0xff || (sio_data->kind == nct6775 + && reg == 0x00)) + && data->fan_div[i] < 0x07) { + dev_dbg(dev, + "Increasing fan%d clock divider from %u to %u\n", + i + 1, div_from_reg(data->fan_div[i]), + div_from_reg(data->fan_div[i] + 1)); + data->fan_div[i]++; + w83627ehf_write_fan_div_common(dev, data, i); + /* Preserve min limit if possible */ + if ((data->has_fan_min & (1 << i)) + && data->fan_min[i] >= 2 + && data->fan_min[i] != 255) + w83627ehf_write_value(data, + data->REG_FAN_MIN[i], + (data->fan_min[i] /= 2)); + } + } + + w83627ehf_update_pwm_common(dev, data); + + for (i = 0; i < data->pwm_num; i++) { + if (!(data->has_fan & (1 << i))) + continue; + + data->fan_start_output[i] = + w83627ehf_read_value(data, + data->REG_FAN_START_OUTPUT[i]); + data->fan_stop_output[i] = + w83627ehf_read_value(data, + data->REG_FAN_STOP_OUTPUT[i]); + data->fan_stop_time[i] = + w83627ehf_read_value(data, + data->REG_FAN_STOP_TIME[i]); + + if (data->REG_FAN_MAX_OUTPUT && + data->REG_FAN_MAX_OUTPUT[i] != 0xff) + data->fan_max_output[i] = + w83627ehf_read_value(data, + data->REG_FAN_MAX_OUTPUT[i]); + + if (data->REG_FAN_STEP_OUTPUT && + data->REG_FAN_STEP_OUTPUT[i] != 0xff) + data->fan_step_output[i] = + w83627ehf_read_value(data, + data->REG_FAN_STEP_OUTPUT[i]); + + data->target_temp[i] = + w83627ehf_read_value(data, + data->REG_TARGET[i]) & + (data->pwm_mode[i] == 1 ? 0x7f : 0xff); + } + + /* Measured temperatures and limits */ + for (i = 0; i < NUM_REG_TEMP; i++) { + if (!(data->have_temp & (1 << i))) + continue; + data->temp[i] = w83627ehf_read_temp(data, + data->reg_temp[i]); + if (data->reg_temp_over[i]) + data->temp_max[i] + = w83627ehf_read_temp(data, + data->reg_temp_over[i]); + if (data->reg_temp_hyst[i]) + data->temp_max_hyst[i] + = w83627ehf_read_temp(data, + data->reg_temp_hyst[i]); + if (i > 2) + continue; + if (data->have_temp_offset & (1 << i)) + data->temp_offset[i] + = w83627ehf_read_value(data, + W83627EHF_REG_TEMP_OFFSET[i]); + } + + data->alarms = w83627ehf_read_value(data, + W83627EHF_REG_ALARM1) | + (w83627ehf_read_value(data, + W83627EHF_REG_ALARM2) << 8) | + (w83627ehf_read_value(data, + W83627EHF_REG_ALARM3) << 16); + + data->caseopen = w83627ehf_read_value(data, + W83627EHF_REG_CASEOPEN_DET); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + return data; +} + +/* + * Sysfs callback functions + */ +#define show_in_reg(reg) \ +static ssize_t \ +show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct w83627ehf_data *data = w83627ehf_update_device(dev); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ + int nr = sensor_attr->index; \ + return sprintf(buf, "%ld\n", in_from_reg(data->reg[nr], nr, \ + data->scale_in)); \ +} +show_in_reg(in) +show_in_reg(in_min) +show_in_reg(in_max) + +#define store_in_reg(REG, reg) \ +static ssize_t \ +store_in_##reg(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct w83627ehf_data *data = dev_get_drvdata(dev); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ + int nr = sensor_attr->index; \ + unsigned long val; \ + int err; \ + err = kstrtoul(buf, 10, &val); \ + if (err < 0) \ + return err; \ + mutex_lock(&data->update_lock); \ + data->in_##reg[nr] = in_to_reg(val, nr, data->scale_in); \ + w83627ehf_write_value(data, W83627EHF_REG_IN_##REG(nr), \ + data->in_##reg[nr]); \ + mutex_unlock(&data->update_lock); \ + return count; \ +} + +store_in_reg(MIN, min) +store_in_reg(MAX, max) + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w83627ehf_data *data = w83627ehf_update_device(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + return sprintf(buf, "%u\n", (data->alarms >> nr) & 0x01); +} + +static struct sensor_device_attribute sda_in_input[] = { + SENSOR_ATTR(in0_input, S_IRUGO, show_in, NULL, 0), + SENSOR_ATTR(in1_input, S_IRUGO, show_in, NULL, 1), + SENSOR_ATTR(in2_input, S_IRUGO, show_in, NULL, 2), + SENSOR_ATTR(in3_input, S_IRUGO, show_in, NULL, 3), + SENSOR_ATTR(in4_input, S_IRUGO, show_in, NULL, 4), + SENSOR_ATTR(in5_input, S_IRUGO, show_in, NULL, 5), + SENSOR_ATTR(in6_input, S_IRUGO, show_in, NULL, 6), + SENSOR_ATTR(in7_input, S_IRUGO, show_in, NULL, 7), + SENSOR_ATTR(in8_input, S_IRUGO, show_in, NULL, 8), + SENSOR_ATTR(in9_input, S_IRUGO, show_in, NULL, 9), +}; + +static struct sensor_device_attribute sda_in_alarm[] = { + SENSOR_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0), + SENSOR_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1), + SENSOR_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2), + SENSOR_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3), + SENSOR_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 8), + SENSOR_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 21), + SENSOR_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 20), + SENSOR_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 16), + SENSOR_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 17), + SENSOR_ATTR(in9_alarm, S_IRUGO, show_alarm, NULL, 19), +}; + +static struct sensor_device_attribute sda_in_min[] = { + SENSOR_ATTR(in0_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 0), + SENSOR_ATTR(in1_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 1), + SENSOR_ATTR(in2_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 2), + SENSOR_ATTR(in3_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 3), + SENSOR_ATTR(in4_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 4), + SENSOR_ATTR(in5_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 5), + SENSOR_ATTR(in6_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 6), + SENSOR_ATTR(in7_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 7), + SENSOR_ATTR(in8_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 8), + SENSOR_ATTR(in9_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 9), +}; + +static struct sensor_device_attribute sda_in_max[] = { + SENSOR_ATTR(in0_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 0), + SENSOR_ATTR(in1_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 1), + SENSOR_ATTR(in2_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 2), + SENSOR_ATTR(in3_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 3), + SENSOR_ATTR(in4_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 4), + SENSOR_ATTR(in5_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 5), + SENSOR_ATTR(in6_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 6), + SENSOR_ATTR(in7_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 7), + SENSOR_ATTR(in8_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 8), + SENSOR_ATTR(in9_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 9), +}; + +static ssize_t +show_fan(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627ehf_data *data = w83627ehf_update_device(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + return sprintf(buf, "%d\n", data->rpm[nr]); +} + +static ssize_t +show_fan_min(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627ehf_data *data = w83627ehf_update_device(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + return sprintf(buf, "%d\n", + data->fan_from_reg_min(data->fan_min[nr], + data->fan_div[nr])); +} + +static ssize_t +show_fan_div(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w83627ehf_data *data = w83627ehf_update_device(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + return sprintf(buf, "%u\n", div_from_reg(data->fan_div[nr])); +} + +static ssize_t +store_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w83627ehf_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + unsigned long val; + int err; + unsigned int reg; + u8 new_div; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + mutex_lock(&data->update_lock); + if (!data->has_fan_div) { + /* + * Only NCT6776F for now, so we know that this is a 13 bit + * register + */ + if (!val) { + val = 0xff1f; + } else { + if (val > 1350000U) + val = 135000U; + val = 1350000U / val; + val = (val & 0x1f) | ((val << 3) & 0xff00); + } + data->fan_min[nr] = val; + goto done; /* Leave fan divider alone */ + } + if (!val) { + /* No min limit, alarm disabled */ + data->fan_min[nr] = 255; + new_div = data->fan_div[nr]; /* No change */ + dev_info(dev, "fan%u low limit and alarm disabled\n", nr + 1); + } else if ((reg = 1350000U / val) >= 128 * 255) { + /* + * Speed below this value cannot possibly be represented, + * even with the highest divider (128) + */ + data->fan_min[nr] = 254; + new_div = 7; /* 128 == (1 << 7) */ + dev_warn(dev, + "fan%u low limit %lu below minimum %u, set to minimum\n", + nr + 1, val, data->fan_from_reg_min(254, 7)); + } else if (!reg) { + /* + * Speed above this value cannot possibly be represented, + * even with the lowest divider (1) + */ + data->fan_min[nr] = 1; + new_div = 0; /* 1 == (1 << 0) */ + dev_warn(dev, + "fan%u low limit %lu above maximum %u, set to maximum\n", + nr + 1, val, data->fan_from_reg_min(1, 0)); + } else { + /* + * Automatically pick the best divider, i.e. the one such + * that the min limit will correspond to a register value + * in the 96..192 range + */ + new_div = 0; + while (reg > 192 && new_div < 7) { + reg >>= 1; + new_div++; + } + data->fan_min[nr] = reg; + } + + /* + * Write both the fan clock divider (if it changed) and the new + * fan min (unconditionally) + */ + if (new_div != data->fan_div[nr]) { + dev_dbg(dev, "fan%u clock divider changed from %u to %u\n", + nr + 1, div_from_reg(data->fan_div[nr]), + div_from_reg(new_div)); + data->fan_div[nr] = new_div; + w83627ehf_write_fan_div_common(dev, data, nr); + /* Give the chip time to sample a new speed value */ + data->last_updated = jiffies; + } +done: + w83627ehf_write_value(data, data->REG_FAN_MIN[nr], + data->fan_min[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static struct sensor_device_attribute sda_fan_input[] = { + SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0), + SENSOR_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1), + SENSOR_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2), + SENSOR_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3), + SENSOR_ATTR(fan5_input, S_IRUGO, show_fan, NULL, 4), +}; + +static struct sensor_device_attribute sda_fan_alarm[] = { + SENSOR_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 6), + SENSOR_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 7), + SENSOR_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, 11), + SENSOR_ATTR(fan4_alarm, S_IRUGO, show_alarm, NULL, 10), + SENSOR_ATTR(fan5_alarm, S_IRUGO, show_alarm, NULL, 23), +}; + +static struct sensor_device_attribute sda_fan_min[] = { + SENSOR_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min, + store_fan_min, 0), + SENSOR_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min, + store_fan_min, 1), + SENSOR_ATTR(fan3_min, S_IWUSR | S_IRUGO, show_fan_min, + store_fan_min, 2), + SENSOR_ATTR(fan4_min, S_IWUSR | S_IRUGO, show_fan_min, + store_fan_min, 3), + SENSOR_ATTR(fan5_min, S_IWUSR | S_IRUGO, show_fan_min, + store_fan_min, 4), +}; + +static struct sensor_device_attribute sda_fan_div[] = { + SENSOR_ATTR(fan1_div, S_IRUGO, show_fan_div, NULL, 0), + SENSOR_ATTR(fan2_div, S_IRUGO, show_fan_div, NULL, 1), + SENSOR_ATTR(fan3_div, S_IRUGO, show_fan_div, NULL, 2), + SENSOR_ATTR(fan4_div, S_IRUGO, show_fan_div, NULL, 3), + SENSOR_ATTR(fan5_div, S_IRUGO, show_fan_div, NULL, 4), +}; + +static ssize_t +show_temp_label(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627ehf_data *data = w83627ehf_update_device(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + return sprintf(buf, "%s\n", data->temp_label[data->temp_src[nr]]); +} + +#define show_temp_reg(addr, reg) \ +static ssize_t \ +show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct w83627ehf_data *data = w83627ehf_update_device(dev); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ + int nr = sensor_attr->index; \ + return sprintf(buf, "%d\n", LM75_TEMP_FROM_REG(data->reg[nr])); \ +} +show_temp_reg(reg_temp, temp); +show_temp_reg(reg_temp_over, temp_max); +show_temp_reg(reg_temp_hyst, temp_max_hyst); + +#define store_temp_reg(addr, reg) \ +static ssize_t \ +store_##reg(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct w83627ehf_data *data = dev_get_drvdata(dev); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ + int nr = sensor_attr->index; \ + int err; \ + long val; \ + err = kstrtol(buf, 10, &val); \ + if (err < 0) \ + return err; \ + mutex_lock(&data->update_lock); \ + data->reg[nr] = LM75_TEMP_TO_REG(val); \ + w83627ehf_write_temp(data, data->addr[nr], data->reg[nr]); \ + mutex_unlock(&data->update_lock); \ + return count; \ +} +store_temp_reg(reg_temp_over, temp_max); +store_temp_reg(reg_temp_hyst, temp_max_hyst); + +static ssize_t +show_temp_offset(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627ehf_data *data = w83627ehf_update_device(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + + return sprintf(buf, "%d\n", + data->temp_offset[sensor_attr->index] * 1000); +} + +static ssize_t +store_temp_offset(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w83627ehf_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), -128, 127); + + mutex_lock(&data->update_lock); + data->temp_offset[nr] = val; + w83627ehf_write_value(data, W83627EHF_REG_TEMP_OFFSET[nr], val); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_temp_type(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627ehf_data *data = w83627ehf_update_device(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + return sprintf(buf, "%d\n", (int)data->temp_type[nr]); +} + +static struct sensor_device_attribute sda_temp_input[] = { + SENSOR_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0), + SENSOR_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1), + SENSOR_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2), + SENSOR_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3), + SENSOR_ATTR(temp5_input, S_IRUGO, show_temp, NULL, 4), + SENSOR_ATTR(temp6_input, S_IRUGO, show_temp, NULL, 5), + SENSOR_ATTR(temp7_input, S_IRUGO, show_temp, NULL, 6), + SENSOR_ATTR(temp8_input, S_IRUGO, show_temp, NULL, 7), + SENSOR_ATTR(temp9_input, S_IRUGO, show_temp, NULL, 8), +}; + +static struct sensor_device_attribute sda_temp_label[] = { + SENSOR_ATTR(temp1_label, S_IRUGO, show_temp_label, NULL, 0), + SENSOR_ATTR(temp2_label, S_IRUGO, show_temp_label, NULL, 1), + SENSOR_ATTR(temp3_label, S_IRUGO, show_temp_label, NULL, 2), + SENSOR_ATTR(temp4_label, S_IRUGO, show_temp_label, NULL, 3), + SENSOR_ATTR(temp5_label, S_IRUGO, show_temp_label, NULL, 4), + SENSOR_ATTR(temp6_label, S_IRUGO, show_temp_label, NULL, 5), + SENSOR_ATTR(temp7_label, S_IRUGO, show_temp_label, NULL, 6), + SENSOR_ATTR(temp8_label, S_IRUGO, show_temp_label, NULL, 7), + SENSOR_ATTR(temp9_label, S_IRUGO, show_temp_label, NULL, 8), +}; + +static struct sensor_device_attribute sda_temp_max[] = { + SENSOR_ATTR(temp1_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 0), + SENSOR_ATTR(temp2_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 1), + SENSOR_ATTR(temp3_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 2), + SENSOR_ATTR(temp4_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 3), + SENSOR_ATTR(temp5_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 4), + SENSOR_ATTR(temp6_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 5), + SENSOR_ATTR(temp7_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 6), + SENSOR_ATTR(temp8_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 7), + SENSOR_ATTR(temp9_max, S_IRUGO | S_IWUSR, show_temp_max, + store_temp_max, 8), +}; + +static struct sensor_device_attribute sda_temp_max_hyst[] = { + SENSOR_ATTR(temp1_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 0), + SENSOR_ATTR(temp2_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 1), + SENSOR_ATTR(temp3_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 2), + SENSOR_ATTR(temp4_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 3), + SENSOR_ATTR(temp5_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 4), + SENSOR_ATTR(temp6_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 5), + SENSOR_ATTR(temp7_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 6), + SENSOR_ATTR(temp8_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 7), + SENSOR_ATTR(temp9_max_hyst, S_IRUGO | S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 8), +}; + +static struct sensor_device_attribute sda_temp_alarm[] = { + SENSOR_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 4), + SENSOR_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 5), + SENSOR_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 13), +}; + +static struct sensor_device_attribute sda_temp_type[] = { + SENSOR_ATTR(temp1_type, S_IRUGO, show_temp_type, NULL, 0), + SENSOR_ATTR(temp2_type, S_IRUGO, show_temp_type, NULL, 1), + SENSOR_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2), +}; + +static struct sensor_device_attribute sda_temp_offset[] = { + SENSOR_ATTR(temp1_offset, S_IRUGO | S_IWUSR, show_temp_offset, + store_temp_offset, 0), + SENSOR_ATTR(temp2_offset, S_IRUGO | S_IWUSR, show_temp_offset, + store_temp_offset, 1), + SENSOR_ATTR(temp3_offset, S_IRUGO | S_IWUSR, show_temp_offset, + store_temp_offset, 2), +}; + +#define show_pwm_reg(reg) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct w83627ehf_data *data = w83627ehf_update_device(dev); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ + int nr = sensor_attr->index; \ + return sprintf(buf, "%d\n", data->reg[nr]); \ +} + +show_pwm_reg(pwm_mode) +show_pwm_reg(pwm_enable) +show_pwm_reg(pwm) + +static ssize_t +store_pwm_mode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w83627ehf_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct w83627ehf_sio_data *sio_data = dev_get_platdata(dev); + int nr = sensor_attr->index; + unsigned long val; + int err; + u16 reg; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (val > 1) + return -EINVAL; + + /* On NCT67766F, DC mode is only supported for pwm1 */ + if (sio_data->kind == nct6776 && nr && val != 1) + return -EINVAL; + + mutex_lock(&data->update_lock); + reg = w83627ehf_read_value(data, W83627EHF_REG_PWM_ENABLE[nr]); + data->pwm_mode[nr] = val; + reg &= ~(1 << W83627EHF_PWM_MODE_SHIFT[nr]); + if (!val) + reg |= 1 << W83627EHF_PWM_MODE_SHIFT[nr]; + w83627ehf_write_value(data, W83627EHF_REG_PWM_ENABLE[nr], reg); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +store_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w83627ehf_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(val, 0, 255); + + mutex_lock(&data->update_lock); + data->pwm[nr] = val; + w83627ehf_write_value(data, data->REG_PWM[nr], val); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +store_pwm_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w83627ehf_data *data = dev_get_drvdata(dev); + struct w83627ehf_sio_data *sio_data = dev_get_platdata(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + unsigned long val; + int err; + u16 reg; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (!val || (val > 4 && val != data->pwm_enable_orig[nr])) + return -EINVAL; + /* SmartFan III mode is not supported on NCT6776F */ + if (sio_data->kind == nct6776 && val == 4) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->pwm_enable[nr] = val; + if (sio_data->kind == nct6775 || sio_data->kind == nct6776) { + reg = w83627ehf_read_value(data, + NCT6775_REG_FAN_MODE[nr]); + reg &= 0x0f; + reg |= (val - 1) << 4; + w83627ehf_write_value(data, + NCT6775_REG_FAN_MODE[nr], reg); + } else { + reg = w83627ehf_read_value(data, W83627EHF_REG_PWM_ENABLE[nr]); + reg &= ~(0x03 << W83627EHF_PWM_ENABLE_SHIFT[nr]); + reg |= (val - 1) << W83627EHF_PWM_ENABLE_SHIFT[nr]; + w83627ehf_write_value(data, W83627EHF_REG_PWM_ENABLE[nr], reg); + } + mutex_unlock(&data->update_lock); + return count; +} + + +#define show_tol_temp(reg) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct w83627ehf_data *data = w83627ehf_update_device(dev); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ + int nr = sensor_attr->index; \ + return sprintf(buf, "%d\n", data->reg[nr] * 1000); \ +} + +show_tol_temp(tolerance) +show_tol_temp(target_temp) + +static ssize_t +store_target_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w83627ehf_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, 127); + + mutex_lock(&data->update_lock); + data->target_temp[nr] = val; + w83627ehf_write_value(data, data->REG_TARGET[nr], val); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +store_tolerance(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w83627ehf_data *data = dev_get_drvdata(dev); + struct w83627ehf_sio_data *sio_data = dev_get_platdata(dev); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + u16 reg; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + /* Limit the temp to 0C - 15C */ + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, 15); + + mutex_lock(&data->update_lock); + if (sio_data->kind == nct6775 || sio_data->kind == nct6776) { + /* Limit tolerance further for NCT6776F */ + if (sio_data->kind == nct6776 && val > 7) + val = 7; + reg = w83627ehf_read_value(data, NCT6775_REG_FAN_MODE[nr]); + reg = (reg & 0xf0) | val; + w83627ehf_write_value(data, NCT6775_REG_FAN_MODE[nr], reg); + } else { + reg = w83627ehf_read_value(data, W83627EHF_REG_TOLERANCE[nr]); + if (nr == 1) + reg = (reg & 0x0f) | (val << 4); + else + reg = (reg & 0xf0) | val; + w83627ehf_write_value(data, W83627EHF_REG_TOLERANCE[nr], reg); + } + data->tolerance[nr] = val; + mutex_unlock(&data->update_lock); + return count; +} + +static struct sensor_device_attribute sda_pwm[] = { + SENSOR_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0), + SENSOR_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 1), + SENSOR_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 2), + SENSOR_ATTR(pwm4, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 3), +}; + +static struct sensor_device_attribute sda_pwm_mode[] = { + SENSOR_ATTR(pwm1_mode, S_IWUSR | S_IRUGO, show_pwm_mode, + store_pwm_mode, 0), + SENSOR_ATTR(pwm2_mode, S_IWUSR | S_IRUGO, show_pwm_mode, + store_pwm_mode, 1), + SENSOR_ATTR(pwm3_mode, S_IWUSR | S_IRUGO, show_pwm_mode, + store_pwm_mode, 2), + SENSOR_ATTR(pwm4_mode, S_IWUSR | S_IRUGO, show_pwm_mode, + store_pwm_mode, 3), +}; + +static struct sensor_device_attribute sda_pwm_enable[] = { + SENSOR_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, show_pwm_enable, + store_pwm_enable, 0), + SENSOR_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, show_pwm_enable, + store_pwm_enable, 1), + SENSOR_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, show_pwm_enable, + store_pwm_enable, 2), + SENSOR_ATTR(pwm4_enable, S_IWUSR | S_IRUGO, show_pwm_enable, + store_pwm_enable, 3), +}; + +static struct sensor_device_attribute sda_target_temp[] = { + SENSOR_ATTR(pwm1_target, S_IWUSR | S_IRUGO, show_target_temp, + store_target_temp, 0), + SENSOR_ATTR(pwm2_target, S_IWUSR | S_IRUGO, show_target_temp, + store_target_temp, 1), + SENSOR_ATTR(pwm3_target, S_IWUSR | S_IRUGO, show_target_temp, + store_target_temp, 2), + SENSOR_ATTR(pwm4_target, S_IWUSR | S_IRUGO, show_target_temp, + store_target_temp, 3), +}; + +static struct sensor_device_attribute sda_tolerance[] = { + SENSOR_ATTR(pwm1_tolerance, S_IWUSR | S_IRUGO, show_tolerance, + store_tolerance, 0), + SENSOR_ATTR(pwm2_tolerance, S_IWUSR | S_IRUGO, show_tolerance, + store_tolerance, 1), + SENSOR_ATTR(pwm3_tolerance, S_IWUSR | S_IRUGO, show_tolerance, + store_tolerance, 2), + SENSOR_ATTR(pwm4_tolerance, S_IWUSR | S_IRUGO, show_tolerance, + store_tolerance, 3), +}; + +/* Smart Fan registers */ + +#define fan_functions(reg, REG) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct w83627ehf_data *data = w83627ehf_update_device(dev); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ + int nr = sensor_attr->index; \ + return sprintf(buf, "%d\n", data->reg[nr]); \ +} \ +static ssize_t \ +store_##reg(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct w83627ehf_data *data = dev_get_drvdata(dev); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ + int nr = sensor_attr->index; \ + unsigned long val; \ + int err; \ + err = kstrtoul(buf, 10, &val); \ + if (err < 0) \ + return err; \ + val = clamp_val(val, 1, 255); \ + mutex_lock(&data->update_lock); \ + data->reg[nr] = val; \ + w83627ehf_write_value(data, data->REG_##REG[nr], val); \ + mutex_unlock(&data->update_lock); \ + return count; \ +} + +fan_functions(fan_start_output, FAN_START_OUTPUT) +fan_functions(fan_stop_output, FAN_STOP_OUTPUT) +fan_functions(fan_max_output, FAN_MAX_OUTPUT) +fan_functions(fan_step_output, FAN_STEP_OUTPUT) + +#define fan_time_functions(reg, REG) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct w83627ehf_data *data = w83627ehf_update_device(dev); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ + int nr = sensor_attr->index; \ + return sprintf(buf, "%d\n", \ + step_time_from_reg(data->reg[nr], \ + data->pwm_mode[nr])); \ +} \ +\ +static ssize_t \ +store_##reg(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct w83627ehf_data *data = dev_get_drvdata(dev); \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ + int nr = sensor_attr->index; \ + unsigned long val; \ + int err; \ + err = kstrtoul(buf, 10, &val); \ + if (err < 0) \ + return err; \ + val = step_time_to_reg(val, data->pwm_mode[nr]); \ + mutex_lock(&data->update_lock); \ + data->reg[nr] = val; \ + w83627ehf_write_value(data, data->REG_##REG[nr], val); \ + mutex_unlock(&data->update_lock); \ + return count; \ +} \ + +fan_time_functions(fan_stop_time, FAN_STOP_TIME) + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w83627ehf_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} +static DEVICE_ATTR_RO(name); + +static struct sensor_device_attribute sda_sf3_arrays_fan4[] = { + SENSOR_ATTR(pwm4_stop_time, S_IWUSR | S_IRUGO, show_fan_stop_time, + store_fan_stop_time, 3), + SENSOR_ATTR(pwm4_start_output, S_IWUSR | S_IRUGO, show_fan_start_output, + store_fan_start_output, 3), + SENSOR_ATTR(pwm4_stop_output, S_IWUSR | S_IRUGO, show_fan_stop_output, + store_fan_stop_output, 3), + SENSOR_ATTR(pwm4_max_output, S_IWUSR | S_IRUGO, show_fan_max_output, + store_fan_max_output, 3), + SENSOR_ATTR(pwm4_step_output, S_IWUSR | S_IRUGO, show_fan_step_output, + store_fan_step_output, 3), +}; + +static struct sensor_device_attribute sda_sf3_arrays_fan3[] = { + SENSOR_ATTR(pwm3_stop_time, S_IWUSR | S_IRUGO, show_fan_stop_time, + store_fan_stop_time, 2), + SENSOR_ATTR(pwm3_start_output, S_IWUSR | S_IRUGO, show_fan_start_output, + store_fan_start_output, 2), + SENSOR_ATTR(pwm3_stop_output, S_IWUSR | S_IRUGO, show_fan_stop_output, + store_fan_stop_output, 2), +}; + +static struct sensor_device_attribute sda_sf3_arrays[] = { + SENSOR_ATTR(pwm1_stop_time, S_IWUSR | S_IRUGO, show_fan_stop_time, + store_fan_stop_time, 0), + SENSOR_ATTR(pwm2_stop_time, S_IWUSR | S_IRUGO, show_fan_stop_time, + store_fan_stop_time, 1), + SENSOR_ATTR(pwm1_start_output, S_IWUSR | S_IRUGO, show_fan_start_output, + store_fan_start_output, 0), + SENSOR_ATTR(pwm2_start_output, S_IWUSR | S_IRUGO, show_fan_start_output, + store_fan_start_output, 1), + SENSOR_ATTR(pwm1_stop_output, S_IWUSR | S_IRUGO, show_fan_stop_output, + store_fan_stop_output, 0), + SENSOR_ATTR(pwm2_stop_output, S_IWUSR | S_IRUGO, show_fan_stop_output, + store_fan_stop_output, 1), +}; + + +/* + * pwm1 and pwm3 don't support max and step settings on all chips. + * Need to check support while generating/removing attribute files. + */ +static struct sensor_device_attribute sda_sf3_max_step_arrays[] = { + SENSOR_ATTR(pwm1_max_output, S_IWUSR | S_IRUGO, show_fan_max_output, + store_fan_max_output, 0), + SENSOR_ATTR(pwm1_step_output, S_IWUSR | S_IRUGO, show_fan_step_output, + store_fan_step_output, 0), + SENSOR_ATTR(pwm2_max_output, S_IWUSR | S_IRUGO, show_fan_max_output, + store_fan_max_output, 1), + SENSOR_ATTR(pwm2_step_output, S_IWUSR | S_IRUGO, show_fan_step_output, + store_fan_step_output, 1), + SENSOR_ATTR(pwm3_max_output, S_IWUSR | S_IRUGO, show_fan_max_output, + store_fan_max_output, 2), + SENSOR_ATTR(pwm3_step_output, S_IWUSR | S_IRUGO, show_fan_step_output, + store_fan_step_output, 2), +}; + +static ssize_t +cpu0_vid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627ehf_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); +} +static DEVICE_ATTR_RO(cpu0_vid); + + +/* Case open detection */ + +static ssize_t +show_caseopen(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627ehf_data *data = w83627ehf_update_device(dev); + + return sprintf(buf, "%d\n", + !!(data->caseopen & to_sensor_dev_attr_2(attr)->index)); +} + +static ssize_t +clear_caseopen(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w83627ehf_data *data = dev_get_drvdata(dev); + unsigned long val; + u16 reg, mask; + + if (kstrtoul(buf, 10, &val) || val != 0) + return -EINVAL; + + mask = to_sensor_dev_attr_2(attr)->nr; + + mutex_lock(&data->update_lock); + reg = w83627ehf_read_value(data, W83627EHF_REG_CASEOPEN_CLR); + w83627ehf_write_value(data, W83627EHF_REG_CASEOPEN_CLR, reg | mask); + w83627ehf_write_value(data, W83627EHF_REG_CASEOPEN_CLR, reg & ~mask); + data->valid = 0; /* Force cache refresh */ + mutex_unlock(&data->update_lock); + + return count; +} + +static struct sensor_device_attribute_2 sda_caseopen[] = { + SENSOR_ATTR_2(intrusion0_alarm, S_IWUSR | S_IRUGO, show_caseopen, + clear_caseopen, 0x80, 0x10), + SENSOR_ATTR_2(intrusion1_alarm, S_IWUSR | S_IRUGO, show_caseopen, + clear_caseopen, 0x40, 0x40), +}; + +/* + * Driver and device management + */ + +static void w83627ehf_device_remove_files(struct device *dev) +{ + /* + * some entries in the following arrays may not have been used in + * device_create_file(), but device_remove_file() will ignore them + */ + int i; + struct w83627ehf_data *data = dev_get_drvdata(dev); + + for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays); i++) + device_remove_file(dev, &sda_sf3_arrays[i].dev_attr); + for (i = 0; i < ARRAY_SIZE(sda_sf3_max_step_arrays); i++) { + struct sensor_device_attribute *attr = + &sda_sf3_max_step_arrays[i]; + if (data->REG_FAN_STEP_OUTPUT && + data->REG_FAN_STEP_OUTPUT[attr->index] != 0xff) + device_remove_file(dev, &attr->dev_attr); + } + for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays_fan3); i++) + device_remove_file(dev, &sda_sf3_arrays_fan3[i].dev_attr); + for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays_fan4); i++) + device_remove_file(dev, &sda_sf3_arrays_fan4[i].dev_attr); + for (i = 0; i < data->in_num; i++) { + if ((i == 6) && data->in6_skip) + continue; + device_remove_file(dev, &sda_in_input[i].dev_attr); + device_remove_file(dev, &sda_in_alarm[i].dev_attr); + device_remove_file(dev, &sda_in_min[i].dev_attr); + device_remove_file(dev, &sda_in_max[i].dev_attr); + } + for (i = 0; i < 5; i++) { + device_remove_file(dev, &sda_fan_input[i].dev_attr); + device_remove_file(dev, &sda_fan_alarm[i].dev_attr); + device_remove_file(dev, &sda_fan_div[i].dev_attr); + device_remove_file(dev, &sda_fan_min[i].dev_attr); + } + for (i = 0; i < data->pwm_num; i++) { + device_remove_file(dev, &sda_pwm[i].dev_attr); + device_remove_file(dev, &sda_pwm_mode[i].dev_attr); + device_remove_file(dev, &sda_pwm_enable[i].dev_attr); + device_remove_file(dev, &sda_target_temp[i].dev_attr); + device_remove_file(dev, &sda_tolerance[i].dev_attr); + } + for (i = 0; i < NUM_REG_TEMP; i++) { + if (!(data->have_temp & (1 << i))) + continue; + device_remove_file(dev, &sda_temp_input[i].dev_attr); + device_remove_file(dev, &sda_temp_label[i].dev_attr); + if (i == 2 && data->temp3_val_only) + continue; + device_remove_file(dev, &sda_temp_max[i].dev_attr); + device_remove_file(dev, &sda_temp_max_hyst[i].dev_attr); + if (i > 2) + continue; + device_remove_file(dev, &sda_temp_alarm[i].dev_attr); + device_remove_file(dev, &sda_temp_type[i].dev_attr); + device_remove_file(dev, &sda_temp_offset[i].dev_attr); + } + + device_remove_file(dev, &sda_caseopen[0].dev_attr); + device_remove_file(dev, &sda_caseopen[1].dev_attr); + + device_remove_file(dev, &dev_attr_name); + device_remove_file(dev, &dev_attr_cpu0_vid); +} + +/* Get the monitoring functions started */ +static inline void w83627ehf_init_device(struct w83627ehf_data *data, + enum kinds kind) +{ + int i; + u8 tmp, diode; + + /* Start monitoring is needed */ + tmp = w83627ehf_read_value(data, W83627EHF_REG_CONFIG); + if (!(tmp & 0x01)) + w83627ehf_write_value(data, W83627EHF_REG_CONFIG, + tmp | 0x01); + + /* Enable temperature sensors if needed */ + for (i = 0; i < NUM_REG_TEMP; i++) { + if (!(data->have_temp & (1 << i))) + continue; + if (!data->reg_temp_config[i]) + continue; + tmp = w83627ehf_read_value(data, + data->reg_temp_config[i]); + if (tmp & 0x01) + w83627ehf_write_value(data, + data->reg_temp_config[i], + tmp & 0xfe); + } + + /* Enable VBAT monitoring if needed */ + tmp = w83627ehf_read_value(data, W83627EHF_REG_VBAT); + if (!(tmp & 0x01)) + w83627ehf_write_value(data, W83627EHF_REG_VBAT, tmp | 0x01); + + /* Get thermal sensor types */ + switch (kind) { + case w83627ehf: + diode = w83627ehf_read_value(data, W83627EHF_REG_DIODE); + break; + case w83627uhg: + diode = 0x00; + break; + default: + diode = 0x70; + } + for (i = 0; i < 3; i++) { + const char *label = NULL; + + if (data->temp_label) + label = data->temp_label[data->temp_src[i]]; + + /* Digital source overrides analog type */ + if (label && strncmp(label, "PECI", 4) == 0) + data->temp_type[i] = 6; + else if (label && strncmp(label, "AMD", 3) == 0) + data->temp_type[i] = 5; + else if ((tmp & (0x02 << i))) + data->temp_type[i] = (diode & (0x10 << i)) ? 1 : 3; + else + data->temp_type[i] = 4; /* thermistor */ + } +} + +static void w82627ehf_swap_tempreg(struct w83627ehf_data *data, + int r1, int r2) +{ + swap(data->temp_src[r1], data->temp_src[r2]); + swap(data->reg_temp[r1], data->reg_temp[r2]); + swap(data->reg_temp_over[r1], data->reg_temp_over[r2]); + swap(data->reg_temp_hyst[r1], data->reg_temp_hyst[r2]); + swap(data->reg_temp_config[r1], data->reg_temp_config[r2]); +} + +static void +w83627ehf_set_temp_reg_ehf(struct w83627ehf_data *data, int n_temp) +{ + int i; + + for (i = 0; i < n_temp; i++) { + data->reg_temp[i] = W83627EHF_REG_TEMP[i]; + data->reg_temp_over[i] = W83627EHF_REG_TEMP_OVER[i]; + data->reg_temp_hyst[i] = W83627EHF_REG_TEMP_HYST[i]; + data->reg_temp_config[i] = W83627EHF_REG_TEMP_CONFIG[i]; + } +} + +static void +w83627ehf_check_fan_inputs(const struct w83627ehf_sio_data *sio_data, + struct w83627ehf_data *data) +{ + int fan3pin, fan4pin, fan4min, fan5pin, regval; + + /* The W83627UHG is simple, only two fan inputs, no config */ + if (sio_data->kind == w83627uhg) { + data->has_fan = 0x03; /* fan1 and fan2 */ + data->has_fan_min = 0x03; + return; + } + + /* fan4 and fan5 share some pins with the GPIO and serial flash */ + if (sio_data->kind == nct6775) { + /* On NCT6775, fan4 shares pins with the fdc interface */ + fan3pin = 1; + fan4pin = !(superio_inb(sio_data->sioreg, 0x2A) & 0x80); + fan4min = 0; + fan5pin = 0; + } else if (sio_data->kind == nct6776) { + bool gpok = superio_inb(sio_data->sioreg, 0x27) & 0x80; + + superio_select(sio_data->sioreg, W83627EHF_LD_HWM); + regval = superio_inb(sio_data->sioreg, SIO_REG_ENABLE); + + if (regval & 0x80) + fan3pin = gpok; + else + fan3pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x40); + + if (regval & 0x40) + fan4pin = gpok; + else + fan4pin = !!(superio_inb(sio_data->sioreg, 0x1C) & 0x01); + + if (regval & 0x20) + fan5pin = gpok; + else + fan5pin = !!(superio_inb(sio_data->sioreg, 0x1C) & 0x02); + + fan4min = fan4pin; + } else if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) { + fan3pin = 1; + fan4pin = superio_inb(sio_data->sioreg, 0x27) & 0x40; + fan5pin = superio_inb(sio_data->sioreg, 0x27) & 0x20; + fan4min = fan4pin; + } else { + fan3pin = 1; + fan4pin = !(superio_inb(sio_data->sioreg, 0x29) & 0x06); + fan5pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x02); + fan4min = fan4pin; + } + + data->has_fan = data->has_fan_min = 0x03; /* fan1 and fan2 */ + data->has_fan |= (fan3pin << 2); + data->has_fan_min |= (fan3pin << 2); + + if (sio_data->kind == nct6775 || sio_data->kind == nct6776) { + /* + * NCT6775F and NCT6776F don't have the W83627EHF_REG_FANDIV1 + * register + */ + data->has_fan |= (fan4pin << 3) | (fan5pin << 4); + data->has_fan_min |= (fan4min << 3) | (fan5pin << 4); + } else { + /* + * It looks like fan4 and fan5 pins can be alternatively used + * as fan on/off switches, but fan5 control is write only :/ + * We assume that if the serial interface is disabled, designers + * connected fan5 as input unless they are emitting log 1, which + * is not the default. + */ + regval = w83627ehf_read_value(data, W83627EHF_REG_FANDIV1); + if ((regval & (1 << 2)) && fan4pin) { + data->has_fan |= (1 << 3); + data->has_fan_min |= (1 << 3); + } + if (!(regval & (1 << 1)) && fan5pin) { + data->has_fan |= (1 << 4); + data->has_fan_min |= (1 << 4); + } + } +} + +static int w83627ehf_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct w83627ehf_sio_data *sio_data = dev_get_platdata(dev); + struct w83627ehf_data *data; + struct resource *res; + u8 en_vrm10; + int i, err = 0; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!request_region(res->start, IOREGION_LENGTH, DRVNAME)) { + err = -EBUSY; + dev_err(dev, "Failed to request region 0x%lx-0x%lx\n", + (unsigned long)res->start, + (unsigned long)res->start + IOREGION_LENGTH - 1); + goto exit; + } + + data = devm_kzalloc(&pdev->dev, sizeof(struct w83627ehf_data), + GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit_release; + } + + data->addr = res->start; + mutex_init(&data->lock); + mutex_init(&data->update_lock); + data->name = w83627ehf_device_names[sio_data->kind]; + data->bank = 0xff; /* Force initial bank selection */ + platform_set_drvdata(pdev, data); + + /* 627EHG and 627EHF have 10 voltage inputs; 627DHG and 667HG have 9 */ + data->in_num = (sio_data->kind == w83627ehf) ? 10 : 9; + /* 667HG, NCT6775F, and NCT6776F have 3 pwms, and 627UHG has only 2 */ + switch (sio_data->kind) { + default: + data->pwm_num = 4; + break; + case w83667hg: + case w83667hg_b: + case nct6775: + case nct6776: + data->pwm_num = 3; + break; + case w83627uhg: + data->pwm_num = 2; + break; + } + + /* Default to 3 temperature inputs, code below will adjust as needed */ + data->have_temp = 0x07; + + /* Deal with temperature register setup first. */ + if (sio_data->kind == nct6775 || sio_data->kind == nct6776) { + int mask = 0; + + /* + * Display temperature sensor output only if it monitors + * a source other than one already reported. Always display + * first three temperature registers, though. + */ + for (i = 0; i < NUM_REG_TEMP; i++) { + u8 src; + + data->reg_temp[i] = NCT6775_REG_TEMP[i]; + data->reg_temp_over[i] = NCT6775_REG_TEMP_OVER[i]; + data->reg_temp_hyst[i] = NCT6775_REG_TEMP_HYST[i]; + data->reg_temp_config[i] = NCT6775_REG_TEMP_CONFIG[i]; + + src = w83627ehf_read_value(data, + NCT6775_REG_TEMP_SOURCE[i]); + src &= 0x1f; + if (src && !(mask & (1 << src))) { + data->have_temp |= 1 << i; + mask |= 1 << src; + } + + data->temp_src[i] = src; + + /* + * Now do some register swapping if index 0..2 don't + * point to SYSTIN(1), CPUIN(2), and AUXIN(3). + * Idea is to have the first three attributes + * report SYSTIN, CPUIN, and AUXIN if possible + * without overriding the basic system configuration. + */ + if (i > 0 && data->temp_src[0] != 1 + && data->temp_src[i] == 1) + w82627ehf_swap_tempreg(data, 0, i); + if (i > 1 && data->temp_src[1] != 2 + && data->temp_src[i] == 2) + w82627ehf_swap_tempreg(data, 1, i); + if (i > 2 && data->temp_src[2] != 3 + && data->temp_src[i] == 3) + w82627ehf_swap_tempreg(data, 2, i); + } + if (sio_data->kind == nct6776) { + /* + * On NCT6776, AUXTIN and VIN3 pins are shared. + * Only way to detect it is to check if AUXTIN is used + * as a temperature source, and if that source is + * enabled. + * + * If that is the case, disable in6, which reports VIN3. + * Otherwise disable temp3. + */ + if (data->temp_src[2] == 3) { + u8 reg; + + if (data->reg_temp_config[2]) + reg = w83627ehf_read_value(data, + data->reg_temp_config[2]); + else + reg = 0; /* Assume AUXTIN is used */ + + if (reg & 0x01) + data->have_temp &= ~(1 << 2); + else + data->in6_skip = 1; + } + data->temp_label = nct6776_temp_label; + } else { + data->temp_label = nct6775_temp_label; + } + data->have_temp_offset = data->have_temp & 0x07; + for (i = 0; i < 3; i++) { + if (data->temp_src[i] > 3) + data->have_temp_offset &= ~(1 << i); + } + } else if (sio_data->kind == w83667hg_b) { + u8 reg; + + w83627ehf_set_temp_reg_ehf(data, 4); + + /* + * Temperature sources are selected with bank 0, registers 0x49 + * and 0x4a. + */ + reg = w83627ehf_read_value(data, 0x4a); + data->temp_src[0] = reg >> 5; + reg = w83627ehf_read_value(data, 0x49); + data->temp_src[1] = reg & 0x07; + data->temp_src[2] = (reg >> 4) & 0x07; + + /* + * W83667HG-B has another temperature register at 0x7e. + * The temperature source is selected with register 0x7d. + * Support it if the source differs from already reported + * sources. + */ + reg = w83627ehf_read_value(data, 0x7d); + reg &= 0x07; + if (reg != data->temp_src[0] && reg != data->temp_src[1] + && reg != data->temp_src[2]) { + data->temp_src[3] = reg; + data->have_temp |= 1 << 3; + } + + /* + * Chip supports either AUXTIN or VIN3. Try to find out which + * one. + */ + reg = w83627ehf_read_value(data, W83627EHF_REG_TEMP_CONFIG[2]); + if (data->temp_src[2] == 2 && (reg & 0x01)) + data->have_temp &= ~(1 << 2); + + if ((data->temp_src[2] == 2 && (data->have_temp & (1 << 2))) + || (data->temp_src[3] == 2 && (data->have_temp & (1 << 3)))) + data->in6_skip = 1; + + data->temp_label = w83667hg_b_temp_label; + data->have_temp_offset = data->have_temp & 0x07; + for (i = 0; i < 3; i++) { + if (data->temp_src[i] > 2) + data->have_temp_offset &= ~(1 << i); + } + } else if (sio_data->kind == w83627uhg) { + u8 reg; + + w83627ehf_set_temp_reg_ehf(data, 3); + + /* + * Temperature sources for temp2 and temp3 are selected with + * bank 0, registers 0x49 and 0x4a. + */ + data->temp_src[0] = 0; /* SYSTIN */ + reg = w83627ehf_read_value(data, 0x49) & 0x07; + /* Adjust to have the same mapping as other source registers */ + if (reg == 0) + data->temp_src[1] = 1; + else if (reg >= 2 && reg <= 5) + data->temp_src[1] = reg + 2; + else /* should never happen */ + data->have_temp &= ~(1 << 1); + reg = w83627ehf_read_value(data, 0x4a); + data->temp_src[2] = reg >> 5; + + /* + * Skip temp3 if source is invalid or the same as temp1 + * or temp2. + */ + if (data->temp_src[2] == 2 || data->temp_src[2] == 3 || + data->temp_src[2] == data->temp_src[0] || + ((data->have_temp & (1 << 1)) && + data->temp_src[2] == data->temp_src[1])) + data->have_temp &= ~(1 << 2); + else + data->temp3_val_only = 1; /* No limit regs */ + + data->in6_skip = 1; /* No VIN3 */ + + data->temp_label = w83667hg_b_temp_label; + data->have_temp_offset = data->have_temp & 0x03; + for (i = 0; i < 3; i++) { + if (data->temp_src[i] > 1) + data->have_temp_offset &= ~(1 << i); + } + } else { + w83627ehf_set_temp_reg_ehf(data, 3); + + /* Temperature sources are fixed */ + + if (sio_data->kind == w83667hg) { + u8 reg; + + /* + * Chip supports either AUXTIN or VIN3. Try to find + * out which one. + */ + reg = w83627ehf_read_value(data, + W83627EHF_REG_TEMP_CONFIG[2]); + if (reg & 0x01) + data->have_temp &= ~(1 << 2); + else + data->in6_skip = 1; + } + data->have_temp_offset = data->have_temp & 0x07; + } + + if (sio_data->kind == nct6775) { + data->has_fan_div = true; + data->fan_from_reg = fan_from_reg16; + data->fan_from_reg_min = fan_from_reg8; + data->REG_PWM = NCT6775_REG_PWM; + data->REG_TARGET = NCT6775_REG_TARGET; + data->REG_FAN = NCT6775_REG_FAN; + data->REG_FAN_MIN = W83627EHF_REG_FAN_MIN; + data->REG_FAN_START_OUTPUT = NCT6775_REG_FAN_START_OUTPUT; + data->REG_FAN_STOP_OUTPUT = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_FAN_STOP_TIME = NCT6775_REG_FAN_STOP_TIME; + data->REG_FAN_MAX_OUTPUT = NCT6775_REG_FAN_MAX_OUTPUT; + data->REG_FAN_STEP_OUTPUT = NCT6775_REG_FAN_STEP_OUTPUT; + } else if (sio_data->kind == nct6776) { + data->has_fan_div = false; + data->fan_from_reg = fan_from_reg13; + data->fan_from_reg_min = fan_from_reg13; + data->REG_PWM = NCT6775_REG_PWM; + data->REG_TARGET = NCT6775_REG_TARGET; + data->REG_FAN = NCT6775_REG_FAN; + data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; + data->REG_FAN_START_OUTPUT = NCT6775_REG_FAN_START_OUTPUT; + data->REG_FAN_STOP_OUTPUT = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_FAN_STOP_TIME = NCT6775_REG_FAN_STOP_TIME; + } else if (sio_data->kind == w83667hg_b) { + data->has_fan_div = true; + data->fan_from_reg = fan_from_reg8; + data->fan_from_reg_min = fan_from_reg8; + data->REG_PWM = W83627EHF_REG_PWM; + data->REG_TARGET = W83627EHF_REG_TARGET; + data->REG_FAN = W83627EHF_REG_FAN; + data->REG_FAN_MIN = W83627EHF_REG_FAN_MIN; + data->REG_FAN_START_OUTPUT = W83627EHF_REG_FAN_START_OUTPUT; + data->REG_FAN_STOP_OUTPUT = W83627EHF_REG_FAN_STOP_OUTPUT; + data->REG_FAN_STOP_TIME = W83627EHF_REG_FAN_STOP_TIME; + data->REG_FAN_MAX_OUTPUT = + W83627EHF_REG_FAN_MAX_OUTPUT_W83667_B; + data->REG_FAN_STEP_OUTPUT = + W83627EHF_REG_FAN_STEP_OUTPUT_W83667_B; + } else { + data->has_fan_div = true; + data->fan_from_reg = fan_from_reg8; + data->fan_from_reg_min = fan_from_reg8; + data->REG_PWM = W83627EHF_REG_PWM; + data->REG_TARGET = W83627EHF_REG_TARGET; + data->REG_FAN = W83627EHF_REG_FAN; + data->REG_FAN_MIN = W83627EHF_REG_FAN_MIN; + data->REG_FAN_START_OUTPUT = W83627EHF_REG_FAN_START_OUTPUT; + data->REG_FAN_STOP_OUTPUT = W83627EHF_REG_FAN_STOP_OUTPUT; + data->REG_FAN_STOP_TIME = W83627EHF_REG_FAN_STOP_TIME; + data->REG_FAN_MAX_OUTPUT = + W83627EHF_REG_FAN_MAX_OUTPUT_COMMON; + data->REG_FAN_STEP_OUTPUT = + W83627EHF_REG_FAN_STEP_OUTPUT_COMMON; + } + + /* Setup input voltage scaling factors */ + if (sio_data->kind == w83627uhg) + data->scale_in = scale_in_w83627uhg; + else + data->scale_in = scale_in_common; + + /* Initialize the chip */ + w83627ehf_init_device(data, sio_data->kind); + + data->vrm = vid_which_vrm(); + + err = superio_enter(sio_data->sioreg); + if (err) + goto exit_release; + + /* Read VID value */ + if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b || + sio_data->kind == nct6775 || sio_data->kind == nct6776) { + /* + * W83667HG has different pins for VID input and output, so + * we can get the VID input values directly at logical device D + * 0xe3. + */ + superio_select(sio_data->sioreg, W83667HG_LD_VID); + data->vid = superio_inb(sio_data->sioreg, 0xe3); + err = device_create_file(dev, &dev_attr_cpu0_vid); + if (err) { + superio_exit(sio_data->sioreg); + goto exit_release; + } + } else if (sio_data->kind != w83627uhg) { + superio_select(sio_data->sioreg, W83627EHF_LD_HWM); + if (superio_inb(sio_data->sioreg, SIO_REG_VID_CTRL) & 0x80) { + /* + * Set VID input sensibility if needed. In theory the + * BIOS should have set it, but in practice it's not + * always the case. We only do it for the W83627EHF/EHG + * because the W83627DHG is more complex in this + * respect. + */ + if (sio_data->kind == w83627ehf) { + en_vrm10 = superio_inb(sio_data->sioreg, + SIO_REG_EN_VRM10); + if ((en_vrm10 & 0x08) && data->vrm == 90) { + dev_warn(dev, + "Setting VID input voltage to TTL\n"); + superio_outb(sio_data->sioreg, + SIO_REG_EN_VRM10, + en_vrm10 & ~0x08); + } else if (!(en_vrm10 & 0x08) + && data->vrm == 100) { + dev_warn(dev, + "Setting VID input voltage to VRM10\n"); + superio_outb(sio_data->sioreg, + SIO_REG_EN_VRM10, + en_vrm10 | 0x08); + } + } + + data->vid = superio_inb(sio_data->sioreg, + SIO_REG_VID_DATA); + if (sio_data->kind == w83627ehf) /* 6 VID pins only */ + data->vid &= 0x3f; + + err = device_create_file(dev, &dev_attr_cpu0_vid); + if (err) { + superio_exit(sio_data->sioreg); + goto exit_release; + } + } else { + dev_info(dev, + "VID pins in output mode, CPU VID not available\n"); + } + } + + if (fan_debounce && + (sio_data->kind == nct6775 || sio_data->kind == nct6776)) { + u8 tmp; + + superio_select(sio_data->sioreg, W83627EHF_LD_HWM); + tmp = superio_inb(sio_data->sioreg, NCT6775_REG_FAN_DEBOUNCE); + if (sio_data->kind == nct6776) + superio_outb(sio_data->sioreg, NCT6775_REG_FAN_DEBOUNCE, + 0x3e | tmp); + else + superio_outb(sio_data->sioreg, NCT6775_REG_FAN_DEBOUNCE, + 0x1e | tmp); + pr_info("Enabled fan debounce for chip %s\n", data->name); + } + + w83627ehf_check_fan_inputs(sio_data, data); + + superio_exit(sio_data->sioreg); + + /* Read fan clock dividers immediately */ + w83627ehf_update_fan_div_common(dev, data); + + /* Read pwm data to save original values */ + w83627ehf_update_pwm_common(dev, data); + for (i = 0; i < data->pwm_num; i++) + data->pwm_enable_orig[i] = data->pwm_enable[i]; + + /* Register sysfs hooks */ + for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays); i++) { + err = device_create_file(dev, &sda_sf3_arrays[i].dev_attr); + if (err) + goto exit_remove; + } + + for (i = 0; i < ARRAY_SIZE(sda_sf3_max_step_arrays); i++) { + struct sensor_device_attribute *attr = + &sda_sf3_max_step_arrays[i]; + if (data->REG_FAN_STEP_OUTPUT && + data->REG_FAN_STEP_OUTPUT[attr->index] != 0xff) { + err = device_create_file(dev, &attr->dev_attr); + if (err) + goto exit_remove; + } + } + /* if fan3 and fan4 are enabled create the sf3 files for them */ + if ((data->has_fan & (1 << 2)) && data->pwm_num >= 3) + for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays_fan3); i++) { + err = device_create_file(dev, + &sda_sf3_arrays_fan3[i].dev_attr); + if (err) + goto exit_remove; + } + if ((data->has_fan & (1 << 3)) && data->pwm_num >= 4) + for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays_fan4); i++) { + err = device_create_file(dev, + &sda_sf3_arrays_fan4[i].dev_attr); + if (err) + goto exit_remove; + } + + for (i = 0; i < data->in_num; i++) { + if ((i == 6) && data->in6_skip) + continue; + if ((err = device_create_file(dev, &sda_in_input[i].dev_attr)) + || (err = device_create_file(dev, + &sda_in_alarm[i].dev_attr)) + || (err = device_create_file(dev, + &sda_in_min[i].dev_attr)) + || (err = device_create_file(dev, + &sda_in_max[i].dev_attr))) + goto exit_remove; + } + + for (i = 0; i < 5; i++) { + if (data->has_fan & (1 << i)) { + if ((err = device_create_file(dev, + &sda_fan_input[i].dev_attr)) + || (err = device_create_file(dev, + &sda_fan_alarm[i].dev_attr))) + goto exit_remove; + if (sio_data->kind != nct6776) { + err = device_create_file(dev, + &sda_fan_div[i].dev_attr); + if (err) + goto exit_remove; + } + if (data->has_fan_min & (1 << i)) { + err = device_create_file(dev, + &sda_fan_min[i].dev_attr); + if (err) + goto exit_remove; + } + if (i < data->pwm_num && + ((err = device_create_file(dev, + &sda_pwm[i].dev_attr)) + || (err = device_create_file(dev, + &sda_pwm_mode[i].dev_attr)) + || (err = device_create_file(dev, + &sda_pwm_enable[i].dev_attr)) + || (err = device_create_file(dev, + &sda_target_temp[i].dev_attr)) + || (err = device_create_file(dev, + &sda_tolerance[i].dev_attr)))) + goto exit_remove; + } + } + + for (i = 0; i < NUM_REG_TEMP; i++) { + if (!(data->have_temp & (1 << i))) + continue; + err = device_create_file(dev, &sda_temp_input[i].dev_attr); + if (err) + goto exit_remove; + if (data->temp_label) { + err = device_create_file(dev, + &sda_temp_label[i].dev_attr); + if (err) + goto exit_remove; + } + if (i == 2 && data->temp3_val_only) + continue; + if (data->reg_temp_over[i]) { + err = device_create_file(dev, + &sda_temp_max[i].dev_attr); + if (err) + goto exit_remove; + } + if (data->reg_temp_hyst[i]) { + err = device_create_file(dev, + &sda_temp_max_hyst[i].dev_attr); + if (err) + goto exit_remove; + } + if (i > 2) + continue; + if ((err = device_create_file(dev, + &sda_temp_alarm[i].dev_attr)) + || (err = device_create_file(dev, + &sda_temp_type[i].dev_attr))) + goto exit_remove; + if (data->have_temp_offset & (1 << i)) { + err = device_create_file(dev, + &sda_temp_offset[i].dev_attr); + if (err) + goto exit_remove; + } + } + + err = device_create_file(dev, &sda_caseopen[0].dev_attr); + if (err) + goto exit_remove; + + if (sio_data->kind == nct6776) { + err = device_create_file(dev, &sda_caseopen[1].dev_attr); + if (err) + goto exit_remove; + } + + err = device_create_file(dev, &dev_attr_name); + if (err) + goto exit_remove; + + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + w83627ehf_device_remove_files(dev); +exit_release: + release_region(res->start, IOREGION_LENGTH); +exit: + return err; +} + +static int w83627ehf_remove(struct platform_device *pdev) +{ + struct w83627ehf_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + w83627ehf_device_remove_files(&pdev->dev); + release_region(data->addr, IOREGION_LENGTH); + + return 0; +} + +#ifdef CONFIG_PM +static int w83627ehf_suspend(struct device *dev) +{ + struct w83627ehf_data *data = w83627ehf_update_device(dev); + struct w83627ehf_sio_data *sio_data = dev_get_platdata(dev); + + mutex_lock(&data->update_lock); + data->vbat = w83627ehf_read_value(data, W83627EHF_REG_VBAT); + if (sio_data->kind == nct6775) { + data->fandiv1 = w83627ehf_read_value(data, NCT6775_REG_FANDIV1); + data->fandiv2 = w83627ehf_read_value(data, NCT6775_REG_FANDIV2); + } + mutex_unlock(&data->update_lock); + + return 0; +} + +static int w83627ehf_resume(struct device *dev) +{ + struct w83627ehf_data *data = dev_get_drvdata(dev); + struct w83627ehf_sio_data *sio_data = dev_get_platdata(dev); + int i; + + mutex_lock(&data->update_lock); + data->bank = 0xff; /* Force initial bank selection */ + + /* Restore limits */ + for (i = 0; i < data->in_num; i++) { + if ((i == 6) && data->in6_skip) + continue; + + w83627ehf_write_value(data, W83627EHF_REG_IN_MIN(i), + data->in_min[i]); + w83627ehf_write_value(data, W83627EHF_REG_IN_MAX(i), + data->in_max[i]); + } + + for (i = 0; i < 5; i++) { + if (!(data->has_fan_min & (1 << i))) + continue; + + w83627ehf_write_value(data, data->REG_FAN_MIN[i], + data->fan_min[i]); + } + + for (i = 0; i < NUM_REG_TEMP; i++) { + if (!(data->have_temp & (1 << i))) + continue; + + if (data->reg_temp_over[i]) + w83627ehf_write_temp(data, data->reg_temp_over[i], + data->temp_max[i]); + if (data->reg_temp_hyst[i]) + w83627ehf_write_temp(data, data->reg_temp_hyst[i], + data->temp_max_hyst[i]); + if (i > 2) + continue; + if (data->have_temp_offset & (1 << i)) + w83627ehf_write_value(data, + W83627EHF_REG_TEMP_OFFSET[i], + data->temp_offset[i]); + } + + /* Restore other settings */ + w83627ehf_write_value(data, W83627EHF_REG_VBAT, data->vbat); + if (sio_data->kind == nct6775) { + w83627ehf_write_value(data, NCT6775_REG_FANDIV1, data->fandiv1); + w83627ehf_write_value(data, NCT6775_REG_FANDIV2, data->fandiv2); + } + + /* Force re-reading all values */ + data->valid = 0; + mutex_unlock(&data->update_lock); + + return 0; +} + +static const struct dev_pm_ops w83627ehf_dev_pm_ops = { + .suspend = w83627ehf_suspend, + .resume = w83627ehf_resume, + .freeze = w83627ehf_suspend, + .restore = w83627ehf_resume, +}; + +#define W83627EHF_DEV_PM_OPS (&w83627ehf_dev_pm_ops) +#else +#define W83627EHF_DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + +static struct platform_driver w83627ehf_driver = { + .driver = { + .name = DRVNAME, + .pm = W83627EHF_DEV_PM_OPS, + }, + .probe = w83627ehf_probe, + .remove = w83627ehf_remove, +}; + +/* w83627ehf_find() looks for a '627 in the Super-I/O config space */ +static int __init w83627ehf_find(int sioaddr, unsigned short *addr, + struct w83627ehf_sio_data *sio_data) +{ + static const char sio_name_W83627EHF[] __initconst = "W83627EHF"; + static const char sio_name_W83627EHG[] __initconst = "W83627EHG"; + static const char sio_name_W83627DHG[] __initconst = "W83627DHG"; + static const char sio_name_W83627DHG_P[] __initconst = "W83627DHG-P"; + static const char sio_name_W83627UHG[] __initconst = "W83627UHG"; + static const char sio_name_W83667HG[] __initconst = "W83667HG"; + static const char sio_name_W83667HG_B[] __initconst = "W83667HG-B"; + static const char sio_name_NCT6775[] __initconst = "NCT6775F"; + static const char sio_name_NCT6776[] __initconst = "NCT6776F"; + + u16 val; + const char *sio_name; + int err; + + err = superio_enter(sioaddr); + if (err) + return err; + + if (force_id) + val = force_id; + else + val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) + | superio_inb(sioaddr, SIO_REG_DEVID + 1); + switch (val & SIO_ID_MASK) { + case SIO_W83627EHF_ID: + sio_data->kind = w83627ehf; + sio_name = sio_name_W83627EHF; + break; + case SIO_W83627EHG_ID: + sio_data->kind = w83627ehf; + sio_name = sio_name_W83627EHG; + break; + case SIO_W83627DHG_ID: + sio_data->kind = w83627dhg; + sio_name = sio_name_W83627DHG; + break; + case SIO_W83627DHG_P_ID: + sio_data->kind = w83627dhg_p; + sio_name = sio_name_W83627DHG_P; + break; + case SIO_W83627UHG_ID: + sio_data->kind = w83627uhg; + sio_name = sio_name_W83627UHG; + break; + case SIO_W83667HG_ID: + sio_data->kind = w83667hg; + sio_name = sio_name_W83667HG; + break; + case SIO_W83667HG_B_ID: + sio_data->kind = w83667hg_b; + sio_name = sio_name_W83667HG_B; + break; + case SIO_NCT6775_ID: + sio_data->kind = nct6775; + sio_name = sio_name_NCT6775; + break; + case SIO_NCT6776_ID: + sio_data->kind = nct6776; + sio_name = sio_name_NCT6776; + break; + default: + if (val != 0xffff) + pr_debug("unsupported chip ID: 0x%04x\n", val); + superio_exit(sioaddr); + return -ENODEV; + } + + /* We have a known chip, find the HWM I/O address */ + superio_select(sioaddr, W83627EHF_LD_HWM); + val = (superio_inb(sioaddr, SIO_REG_ADDR) << 8) + | superio_inb(sioaddr, SIO_REG_ADDR + 1); + *addr = val & IOREGION_ALIGNMENT; + if (*addr == 0) { + pr_err("Refusing to enable a Super-I/O device with a base I/O port 0\n"); + superio_exit(sioaddr); + return -ENODEV; + } + + /* Activate logical device if needed */ + val = superio_inb(sioaddr, SIO_REG_ENABLE); + if (!(val & 0x01)) { + pr_warn("Forcibly enabling Super-I/O. Sensor is probably unusable.\n"); + superio_outb(sioaddr, SIO_REG_ENABLE, val | 0x01); + } + + superio_exit(sioaddr); + pr_info("Found %s chip at %#x\n", sio_name, *addr); + sio_data->sioreg = sioaddr; + + return 0; +} + +/* + * when Super-I/O functions move to a separate file, the Super-I/O + * bus will manage the lifetime of the device and this module will only keep + * track of the w83627ehf driver. But since we platform_device_alloc(), we + * must keep track of the device + */ +static struct platform_device *pdev; + +static int __init sensors_w83627ehf_init(void) +{ + int err; + unsigned short address; + struct resource res; + struct w83627ehf_sio_data sio_data; + + /* + * initialize sio_data->kind and sio_data->sioreg. + * + * when Super-I/O functions move to a separate file, the Super-I/O + * driver will probe 0x2e and 0x4e and auto-detect the presence of a + * w83627ehf hardware monitor, and call probe() + */ + if (w83627ehf_find(0x2e, &address, &sio_data) && + w83627ehf_find(0x4e, &address, &sio_data)) + return -ENODEV; + + err = platform_driver_register(&w83627ehf_driver); + if (err) + goto exit; + + pdev = platform_device_alloc(DRVNAME, address); + if (!pdev) { + err = -ENOMEM; + pr_err("Device allocation failed\n"); + goto exit_unregister; + } + + err = platform_device_add_data(pdev, &sio_data, + sizeof(struct w83627ehf_sio_data)); + if (err) { + pr_err("Platform data allocation failed\n"); + goto exit_device_put; + } + + memset(&res, 0, sizeof(res)); + res.name = DRVNAME; + res.start = address + IOREGION_OFFSET; + res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1; + res.flags = IORESOURCE_IO; + + err = acpi_check_resource_conflict(&res); + if (err) + goto exit_device_put; + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + /* platform_device_add calls probe() */ + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit_unregister: + platform_driver_unregister(&w83627ehf_driver); +exit: + return err; +} + +static void __exit sensors_w83627ehf_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&w83627ehf_driver); +} + +MODULE_AUTHOR("Jean Delvare "); +MODULE_DESCRIPTION("W83627EHF driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_w83627ehf_init); +module_exit(sensors_w83627ehf_exit); diff --git a/drivers/hwmon/w83627hf.c b/drivers/hwmon/w83627hf.c new file mode 100644 index 000000000..a575e1cdb --- /dev/null +++ b/drivers/hwmon/w83627hf.c @@ -0,0 +1,2043 @@ +/* + * w83627hf.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (c) 1998 - 2003 Frodo Looijaard , + * Philip Edelbrock , + * and Mark Studebaker + * Ported to 2.6 by Bernhard C. Schrenk + * Copyright (c) 2007 - 1012 Jean Delvare + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Supports following chips: + * + * Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA + * w83627hf 9 3 2 3 0x20 0x5ca3 no yes(LPC) + * w83627thf 7 3 3 3 0x90 0x5ca3 no yes(LPC) + * w83637hf 7 3 3 3 0x80 0x5ca3 no yes(LPC) + * w83687thf 7 3 3 3 0x90 0x5ca3 no yes(LPC) + * w83697hf 8 2 2 2 0x60 0x5ca3 no yes(LPC) + * + * For other winbond chips, and for i2c support in the above chips, + * use w83781d.c. + * + * Note: automatic ("cruise") fan control for 697, 637 & 627thf not + * supported yet. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lm75.h" + +static struct platform_device *pdev; + +#define DRVNAME "w83627hf" +enum chips { w83627hf, w83627thf, w83697hf, w83637hf, w83687thf }; + +struct w83627hf_sio_data { + enum chips type; + int sioaddr; +}; + +static u8 force_i2c = 0x1f; +module_param(force_i2c, byte, 0); +MODULE_PARM_DESC(force_i2c, + "Initialize the i2c address of the sensors"); + +static bool init = 1; +module_param(init, bool, 0); +MODULE_PARM_DESC(init, "Set to zero to bypass chip initialization"); + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +/* modified from kernel/include/traps.c */ +#define DEV 0x07 /* Register: Logical device select */ + +/* logical device numbers for superio_select (below) */ +#define W83627HF_LD_FDC 0x00 +#define W83627HF_LD_PRT 0x01 +#define W83627HF_LD_UART1 0x02 +#define W83627HF_LD_UART2 0x03 +#define W83627HF_LD_KBC 0x05 +#define W83627HF_LD_CIR 0x06 /* w83627hf only */ +#define W83627HF_LD_GAME 0x07 +#define W83627HF_LD_MIDI 0x07 +#define W83627HF_LD_GPIO1 0x07 +#define W83627HF_LD_GPIO5 0x07 /* w83627thf only */ +#define W83627HF_LD_GPIO2 0x08 +#define W83627HF_LD_GPIO3 0x09 +#define W83627HF_LD_GPIO4 0x09 /* w83627thf only */ +#define W83627HF_LD_ACPI 0x0a +#define W83627HF_LD_HWM 0x0b + +#define DEVID 0x20 /* Register: Device ID */ + +#define W83627THF_GPIO5_EN 0x30 /* w83627thf only */ +#define W83627THF_GPIO5_IOSR 0xf3 /* w83627thf only */ +#define W83627THF_GPIO5_DR 0xf4 /* w83627thf only */ + +#define W83687THF_VID_EN 0x29 /* w83687thf only */ +#define W83687THF_VID_CFG 0xF0 /* w83687thf only */ +#define W83687THF_VID_DATA 0xF1 /* w83687thf only */ + +static inline void +superio_outb(struct w83627hf_sio_data *sio, int reg, int val) +{ + outb(reg, sio->sioaddr); + outb(val, sio->sioaddr + 1); +} + +static inline int +superio_inb(struct w83627hf_sio_data *sio, int reg) +{ + outb(reg, sio->sioaddr); + return inb(sio->sioaddr + 1); +} + +static inline void +superio_select(struct w83627hf_sio_data *sio, int ld) +{ + outb(DEV, sio->sioaddr); + outb(ld, sio->sioaddr + 1); +} + +static inline int +superio_enter(struct w83627hf_sio_data *sio) +{ + if (!request_muxed_region(sio->sioaddr, 2, DRVNAME)) + return -EBUSY; + + outb(0x87, sio->sioaddr); + outb(0x87, sio->sioaddr); + + return 0; +} + +static inline void +superio_exit(struct w83627hf_sio_data *sio) +{ + outb(0xAA, sio->sioaddr); + release_region(sio->sioaddr, 2); +} + +#define W627_DEVID 0x52 +#define W627THF_DEVID 0x82 +#define W697_DEVID 0x60 +#define W637_DEVID 0x70 +#define W687THF_DEVID 0x85 +#define WINB_ACT_REG 0x30 +#define WINB_BASE_REG 0x60 +/* Constants specified below */ + +/* Alignment of the base address */ +#define WINB_ALIGNMENT ~7 + +/* Offset & size of I/O region we are interested in */ +#define WINB_REGION_OFFSET 5 +#define WINB_REGION_SIZE 2 + +/* Where are the sensors address/data registers relative to the region offset */ +#define W83781D_ADDR_REG_OFFSET 0 +#define W83781D_DATA_REG_OFFSET 1 + +/* The W83781D registers */ +/* The W83782D registers for nr=7,8 are in bank 5 */ +#define W83781D_REG_IN_MAX(nr) ((nr < 7) ? (0x2b + (nr) * 2) : \ + (0x554 + (((nr) - 7) * 2))) +#define W83781D_REG_IN_MIN(nr) ((nr < 7) ? (0x2c + (nr) * 2) : \ + (0x555 + (((nr) - 7) * 2))) +#define W83781D_REG_IN(nr) ((nr < 7) ? (0x20 + (nr)) : \ + (0x550 + (nr) - 7)) + +/* nr:0-2 for fans:1-3 */ +#define W83627HF_REG_FAN_MIN(nr) (0x3b + (nr)) +#define W83627HF_REG_FAN(nr) (0x28 + (nr)) + +#define W83627HF_REG_TEMP2_CONFIG 0x152 +#define W83627HF_REG_TEMP3_CONFIG 0x252 +/* these are zero-based, unlike config constants above */ +static const u16 w83627hf_reg_temp[] = { 0x27, 0x150, 0x250 }; +static const u16 w83627hf_reg_temp_hyst[] = { 0x3A, 0x153, 0x253 }; +static const u16 w83627hf_reg_temp_over[] = { 0x39, 0x155, 0x255 }; + +#define W83781D_REG_BANK 0x4E + +#define W83781D_REG_CONFIG 0x40 +#define W83781D_REG_ALARM1 0x459 +#define W83781D_REG_ALARM2 0x45A +#define W83781D_REG_ALARM3 0x45B + +#define W83781D_REG_BEEP_CONFIG 0x4D +#define W83781D_REG_BEEP_INTS1 0x56 +#define W83781D_REG_BEEP_INTS2 0x57 +#define W83781D_REG_BEEP_INTS3 0x453 + +#define W83781D_REG_VID_FANDIV 0x47 + +#define W83781D_REG_CHIPID 0x49 +#define W83781D_REG_WCHIPID 0x58 +#define W83781D_REG_CHIPMAN 0x4F +#define W83781D_REG_PIN 0x4B + +#define W83781D_REG_VBAT 0x5D + +#define W83627HF_REG_PWM1 0x5A +#define W83627HF_REG_PWM2 0x5B + +static const u8 W83627THF_REG_PWM_ENABLE[] = { + 0x04, /* FAN 1 mode */ + 0x04, /* FAN 2 mode */ + 0x12, /* FAN AUX mode */ +}; +static const u8 W83627THF_PWM_ENABLE_SHIFT[] = { 2, 4, 1 }; + +#define W83627THF_REG_PWM1 0x01 /* 697HF/637HF/687THF too */ +#define W83627THF_REG_PWM2 0x03 /* 697HF/637HF/687THF too */ +#define W83627THF_REG_PWM3 0x11 /* 637HF/687THF too */ + +#define W83627THF_REG_VRM_OVT_CFG 0x18 /* 637HF/687THF too */ + +static const u8 regpwm_627hf[] = { W83627HF_REG_PWM1, W83627HF_REG_PWM2 }; +static const u8 regpwm[] = { W83627THF_REG_PWM1, W83627THF_REG_PWM2, + W83627THF_REG_PWM3 }; +#define W836X7HF_REG_PWM(type, nr) (((type) == w83627hf) ? \ + regpwm_627hf[nr] : regpwm[nr]) + +#define W83627HF_REG_PWM_FREQ 0x5C /* Only for the 627HF */ + +#define W83637HF_REG_PWM_FREQ1 0x00 /* 697HF/687THF too */ +#define W83637HF_REG_PWM_FREQ2 0x02 /* 697HF/687THF too */ +#define W83637HF_REG_PWM_FREQ3 0x10 /* 687THF too */ + +static const u8 W83637HF_REG_PWM_FREQ[] = { W83637HF_REG_PWM_FREQ1, + W83637HF_REG_PWM_FREQ2, + W83637HF_REG_PWM_FREQ3 }; + +#define W83627HF_BASE_PWM_FREQ 46870 + +#define W83781D_REG_I2C_ADDR 0x48 +#define W83781D_REG_I2C_SUBADDR 0x4A + +/* Sensor selection */ +#define W83781D_REG_SCFG1 0x5D +static const u8 BIT_SCFG1[] = { 0x02, 0x04, 0x08 }; +#define W83781D_REG_SCFG2 0x59 +static const u8 BIT_SCFG2[] = { 0x10, 0x20, 0x40 }; +#define W83781D_DEFAULT_BETA 3435 + +/* + * Conversions. Limit checking is only done on the TO_REG + * variants. Note that you should be a bit careful with which arguments + * these macros are called: arguments may be evaluated more than once. + * Fixing this is just not worth it. + */ +#define IN_TO_REG(val) (clamp_val((((val) + 8) / 16), 0, 255)) +#define IN_FROM_REG(val) ((val) * 16) + +static inline u8 FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 255; + rpm = clamp_val(rpm, 1, 1000000); + return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 254); +} + +#define TEMP_MIN (-128000) +#define TEMP_MAX ( 127000) + +/* + * TEMP: 0.001C/bit (-128C to +127C) + * REG: 1C/bit, two's complement + */ +static u8 TEMP_TO_REG(long temp) +{ + int ntemp = clamp_val(temp, TEMP_MIN, TEMP_MAX); + ntemp += (ntemp < 0 ? -500 : 500); + return (u8)(ntemp / 1000); +} + +static int TEMP_FROM_REG(u8 reg) +{ + return (s8)reg * 1000; +} + +#define FAN_FROM_REG(val,div) ((val)==0?-1:(val)==255?0:1350000/((val)*(div))) + +#define PWM_TO_REG(val) (clamp_val((val), 0, 255)) + +static inline unsigned long pwm_freq_from_reg_627hf(u8 reg) +{ + unsigned long freq; + freq = W83627HF_BASE_PWM_FREQ >> reg; + return freq; +} +static inline u8 pwm_freq_to_reg_627hf(unsigned long val) +{ + u8 i; + /* + * Only 5 dividers (1 2 4 8 16) + * Search for the nearest available frequency + */ + for (i = 0; i < 4; i++) { + if (val > (((W83627HF_BASE_PWM_FREQ >> i) + + (W83627HF_BASE_PWM_FREQ >> (i+1))) / 2)) + break; + } + return i; +} + +static inline unsigned long pwm_freq_from_reg(u8 reg) +{ + /* Clock bit 8 -> 180 kHz or 24 MHz */ + unsigned long clock = (reg & 0x80) ? 180000UL : 24000000UL; + + reg &= 0x7f; + /* This should not happen but anyway... */ + if (reg == 0) + reg++; + return clock / (reg << 8); +} +static inline u8 pwm_freq_to_reg(unsigned long val) +{ + /* Minimum divider value is 0x01 and maximum is 0x7F */ + if (val >= 93750) /* The highest we can do */ + return 0x01; + if (val >= 720) /* Use 24 MHz clock */ + return 24000000UL / (val << 8); + if (val < 6) /* The lowest we can do */ + return 0xFF; + else /* Use 180 kHz clock */ + return 0x80 | (180000UL / (val << 8)); +} + +#define BEEP_MASK_FROM_REG(val) ((val) & 0xff7fff) +#define BEEP_MASK_TO_REG(val) ((val) & 0xff7fff) + +#define DIV_FROM_REG(val) (1 << (val)) + +static inline u8 DIV_TO_REG(long val) +{ + int i; + val = clamp_val(val, 1, 128) >> 1; + for (i = 0; i < 7; i++) { + if (val == 0) + break; + val >>= 1; + } + return (u8)i; +} + +/* + * For each registered chip, we need to keep some data in memory. + * The structure is dynamically allocated. + */ +struct w83627hf_data { + unsigned short addr; + const char *name; + struct device *hwmon_dev; + struct mutex lock; + enum chips type; + + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + u8 in[9]; /* Register value */ + u8 in_max[9]; /* Register value */ + u8 in_min[9]; /* Register value */ + u8 fan[3]; /* Register value */ + u8 fan_min[3]; /* Register value */ + u16 temp[3]; /* Register value */ + u16 temp_max[3]; /* Register value */ + u16 temp_max_hyst[3]; /* Register value */ + u8 fan_div[3]; /* Register encoding, shifted right */ + u8 vid; /* Register encoding, combined */ + u32 alarms; /* Register encoding, combined */ + u32 beep_mask; /* Register encoding, combined */ + u8 pwm[3]; /* Register value */ + u8 pwm_enable[3]; /* 1 = manual + * 2 = thermal cruise (also called SmartFan I) + * 3 = fan speed cruise + */ + u8 pwm_freq[3]; /* Register value */ + u16 sens[3]; /* 1 = pentium diode; 2 = 3904 diode; + * 4 = thermistor + */ + u8 vrm; + u8 vrm_ovt; /* Register value, 627THF/637HF/687THF only */ + +#ifdef CONFIG_PM + /* Remember extra register values over suspend/resume */ + u8 scfg1; + u8 scfg2; +#endif +}; + + +static int w83627hf_probe(struct platform_device *pdev); +static int w83627hf_remove(struct platform_device *pdev); + +static int w83627hf_read_value(struct w83627hf_data *data, u16 reg); +static int w83627hf_write_value(struct w83627hf_data *data, u16 reg, u16 value); +static void w83627hf_update_fan_div(struct w83627hf_data *data); +static struct w83627hf_data *w83627hf_update_device(struct device *dev); +static void w83627hf_init_device(struct platform_device *pdev); + +#ifdef CONFIG_PM +static int w83627hf_suspend(struct device *dev) +{ + struct w83627hf_data *data = w83627hf_update_device(dev); + + mutex_lock(&data->update_lock); + data->scfg1 = w83627hf_read_value(data, W83781D_REG_SCFG1); + data->scfg2 = w83627hf_read_value(data, W83781D_REG_SCFG2); + mutex_unlock(&data->update_lock); + + return 0; +} + +static int w83627hf_resume(struct device *dev) +{ + struct w83627hf_data *data = dev_get_drvdata(dev); + int i, num_temps = (data->type == w83697hf) ? 2 : 3; + + /* Restore limits */ + mutex_lock(&data->update_lock); + for (i = 0; i <= 8; i++) { + /* skip missing sensors */ + if (((data->type == w83697hf) && (i == 1)) || + ((data->type != w83627hf && data->type != w83697hf) + && (i == 5 || i == 6))) + continue; + w83627hf_write_value(data, W83781D_REG_IN_MAX(i), + data->in_max[i]); + w83627hf_write_value(data, W83781D_REG_IN_MIN(i), + data->in_min[i]); + } + for (i = 0; i <= 2; i++) + w83627hf_write_value(data, W83627HF_REG_FAN_MIN(i), + data->fan_min[i]); + for (i = 0; i < num_temps; i++) { + w83627hf_write_value(data, w83627hf_reg_temp_over[i], + data->temp_max[i]); + w83627hf_write_value(data, w83627hf_reg_temp_hyst[i], + data->temp_max_hyst[i]); + } + + /* Fixup BIOS bugs */ + if (data->type == w83627thf || data->type == w83637hf || + data->type == w83687thf) + w83627hf_write_value(data, W83627THF_REG_VRM_OVT_CFG, + data->vrm_ovt); + w83627hf_write_value(data, W83781D_REG_SCFG1, data->scfg1); + w83627hf_write_value(data, W83781D_REG_SCFG2, data->scfg2); + + /* Force re-reading all values */ + data->valid = 0; + mutex_unlock(&data->update_lock); + + return 0; +} + +static const struct dev_pm_ops w83627hf_dev_pm_ops = { + .suspend = w83627hf_suspend, + .resume = w83627hf_resume, +}; + +#define W83627HF_DEV_PM_OPS (&w83627hf_dev_pm_ops) +#else +#define W83627HF_DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + +static struct platform_driver w83627hf_driver = { + .driver = { + .name = DRVNAME, + .pm = W83627HF_DEV_PM_OPS, + }, + .probe = w83627hf_probe, + .remove = w83627hf_remove, +}; + +static ssize_t +show_in_input(struct device *dev, struct device_attribute *devattr, char *buf) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = w83627hf_update_device(dev); + return sprintf(buf, "%ld\n", (long)IN_FROM_REG(data->in[nr])); +} +static ssize_t +show_in_min(struct device *dev, struct device_attribute *devattr, char *buf) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = w83627hf_update_device(dev); + return sprintf(buf, "%ld\n", (long)IN_FROM_REG(data->in_min[nr])); +} +static ssize_t +show_in_max(struct device *dev, struct device_attribute *devattr, char *buf) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = w83627hf_update_device(dev); + return sprintf(buf, "%ld\n", (long)IN_FROM_REG(data->in_max[nr])); +} +static ssize_t +store_in_min(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_min[nr] = IN_TO_REG(val); + w83627hf_write_value(data, W83781D_REG_IN_MIN(nr), data->in_min[nr]); + mutex_unlock(&data->update_lock); + return count; +} +static ssize_t +store_in_max(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = dev_get_drvdata(dev); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->in_max[nr] = IN_TO_REG(val); + w83627hf_write_value(data, W83781D_REG_IN_MAX(nr), data->in_max[nr]); + mutex_unlock(&data->update_lock); + return count; +} +#define sysfs_vin_decl(offset) \ +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, \ + show_in_input, NULL, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_min, S_IRUGO|S_IWUSR, \ + show_in_min, store_in_min, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_max, S_IRUGO|S_IWUSR, \ + show_in_max, store_in_max, offset); + +sysfs_vin_decl(1); +sysfs_vin_decl(2); +sysfs_vin_decl(3); +sysfs_vin_decl(4); +sysfs_vin_decl(5); +sysfs_vin_decl(6); +sysfs_vin_decl(7); +sysfs_vin_decl(8); + +/* use a different set of functions for in0 */ +static ssize_t show_in_0(struct w83627hf_data *data, char *buf, u8 reg) +{ + long in0; + + if ((data->vrm_ovt & 0x01) && + (w83627thf == data->type || w83637hf == data->type + || w83687thf == data->type)) + + /* use VRM9 calculation */ + in0 = (long)((reg * 488 + 70000 + 50) / 100); + else + /* use VRM8 (standard) calculation */ + in0 = (long)IN_FROM_REG(reg); + + return sprintf(buf,"%ld\n", in0); +} + +static ssize_t in0_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct w83627hf_data *data = w83627hf_update_device(dev); + return show_in_0(data, buf, data->in[0]); +} + +static ssize_t in0_min_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w83627hf_data *data = w83627hf_update_device(dev); + return show_in_0(data, buf, data->in_min[0]); +} + +static ssize_t in0_max_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w83627hf_data *data = w83627hf_update_device(dev); + return show_in_0(data, buf, data->in_max[0]); +} + +static ssize_t in0_min_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct w83627hf_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + if ((data->vrm_ovt & 0x01) && + (w83627thf == data->type || w83637hf == data->type + || w83687thf == data->type)) + + /* use VRM9 calculation */ + data->in_min[0] = + clamp_val(((val * 100) - 70000 + 244) / 488, 0, 255); + else + /* use VRM8 (standard) calculation */ + data->in_min[0] = IN_TO_REG(val); + + w83627hf_write_value(data, W83781D_REG_IN_MIN(0), data->in_min[0]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t in0_max_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct w83627hf_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + if ((data->vrm_ovt & 0x01) && + (w83627thf == data->type || w83637hf == data->type + || w83687thf == data->type)) + + /* use VRM9 calculation */ + data->in_max[0] = + clamp_val(((val * 100) - 70000 + 244) / 488, 0, 255); + else + /* use VRM8 (standard) calculation */ + data->in_max[0] = IN_TO_REG(val); + + w83627hf_write_value(data, W83781D_REG_IN_MAX(0), data->in_max[0]); + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RO(in0_input); +static DEVICE_ATTR_RW(in0_min); +static DEVICE_ATTR_RW(in0_max); + +static ssize_t +show_fan_input(struct device *dev, struct device_attribute *devattr, char *buf) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = w83627hf_update_device(dev); + return sprintf(buf, "%ld\n", FAN_FROM_REG(data->fan[nr], + (long)DIV_FROM_REG(data->fan_div[nr]))); +} +static ssize_t +show_fan_min(struct device *dev, struct device_attribute *devattr, char *buf) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = w83627hf_update_device(dev); + return sprintf(buf, "%ld\n", FAN_FROM_REG(data->fan_min[nr], + (long)DIV_FROM_REG(data->fan_div[nr]))); +} +static ssize_t +store_fan_min(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_min[nr] = FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); + w83627hf_write_value(data, W83627HF_REG_FAN_MIN(nr), + data->fan_min[nr]); + + mutex_unlock(&data->update_lock); + return count; +} +#define sysfs_fan_decl(offset) \ +static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \ + show_fan_input, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ + show_fan_min, store_fan_min, offset - 1); + +sysfs_fan_decl(1); +sysfs_fan_decl(2); +sysfs_fan_decl(3); + +static ssize_t +show_temp(struct device *dev, struct device_attribute *devattr, char *buf) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = w83627hf_update_device(dev); + + u16 tmp = data->temp[nr]; + return sprintf(buf, "%ld\n", (nr) ? (long) LM75_TEMP_FROM_REG(tmp) + : (long) TEMP_FROM_REG(tmp)); +} + +static ssize_t +show_temp_max(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = w83627hf_update_device(dev); + + u16 tmp = data->temp_max[nr]; + return sprintf(buf, "%ld\n", (nr) ? (long) LM75_TEMP_FROM_REG(tmp) + : (long) TEMP_FROM_REG(tmp)); +} + +static ssize_t +show_temp_max_hyst(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = w83627hf_update_device(dev); + + u16 tmp = data->temp_max_hyst[nr]; + return sprintf(buf, "%ld\n", (nr) ? (long) LM75_TEMP_FROM_REG(tmp) + : (long) TEMP_FROM_REG(tmp)); +} + +static ssize_t +store_temp_max(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = dev_get_drvdata(dev); + u16 tmp; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + tmp = (nr) ? LM75_TEMP_TO_REG(val) : TEMP_TO_REG(val); + mutex_lock(&data->update_lock); + data->temp_max[nr] = tmp; + w83627hf_write_value(data, w83627hf_reg_temp_over[nr], tmp); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +store_temp_max_hyst(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = dev_get_drvdata(dev); + u16 tmp; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + tmp = (nr) ? LM75_TEMP_TO_REG(val) : TEMP_TO_REG(val); + mutex_lock(&data->update_lock); + data->temp_max_hyst[nr] = tmp; + w83627hf_write_value(data, w83627hf_reg_temp_hyst[nr], tmp); + mutex_unlock(&data->update_lock); + return count; +} + +#define sysfs_temp_decl(offset) \ +static SENSOR_DEVICE_ATTR(temp##offset##_input, S_IRUGO, \ + show_temp, NULL, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max, S_IRUGO|S_IWUSR, \ + show_temp_max, store_temp_max, offset - 1); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max_hyst, S_IRUGO|S_IWUSR, \ + show_temp_max_hyst, store_temp_max_hyst, offset - 1); + +sysfs_temp_decl(1); +sysfs_temp_decl(2); +sysfs_temp_decl(3); + +static ssize_t +cpu0_vid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627hf_data *data = w83627hf_update_device(dev); + return sprintf(buf, "%ld\n", (long) vid_from_reg(data->vid, data->vrm)); +} +static DEVICE_ATTR_RO(cpu0_vid); + +static ssize_t +vrm_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627hf_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%ld\n", (long) data->vrm); +} +static ssize_t +vrm_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct w83627hf_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 255) + return -EINVAL; + data->vrm = val; + + return count; +} +static DEVICE_ATTR_RW(vrm); + +static ssize_t +alarms_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627hf_data *data = w83627hf_update_device(dev); + return sprintf(buf, "%ld\n", (long) data->alarms); +} +static DEVICE_ATTR_RO(alarms); + +static ssize_t +show_alarm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627hf_data *data = w83627hf_update_device(dev); + int bitnr = to_sensor_dev_attr(attr)->index; + return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1); +} +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 16); +static SENSOR_DEVICE_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 17); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 13); + +static ssize_t +beep_mask_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627hf_data *data = w83627hf_update_device(dev); + return sprintf(buf, "%ld\n", + (long)BEEP_MASK_FROM_REG(data->beep_mask)); +} + +static ssize_t +beep_mask_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w83627hf_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + /* preserve beep enable */ + data->beep_mask = (data->beep_mask & 0x8000) + | BEEP_MASK_TO_REG(val); + w83627hf_write_value(data, W83781D_REG_BEEP_INTS1, + data->beep_mask & 0xff); + w83627hf_write_value(data, W83781D_REG_BEEP_INTS3, + ((data->beep_mask) >> 16) & 0xff); + w83627hf_write_value(data, W83781D_REG_BEEP_INTS2, + (data->beep_mask >> 8) & 0xff); + + mutex_unlock(&data->update_lock); + return count; +} + +static DEVICE_ATTR_RW(beep_mask); + +static ssize_t +show_beep(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83627hf_data *data = w83627hf_update_device(dev); + int bitnr = to_sensor_dev_attr(attr)->index; + return sprintf(buf, "%u\n", (data->beep_mask >> bitnr) & 1); +} + +static ssize_t +store_beep(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w83627hf_data *data = dev_get_drvdata(dev); + int bitnr = to_sensor_dev_attr(attr)->index; + u8 reg; + unsigned long bit; + int err; + + err = kstrtoul(buf, 10, &bit); + if (err) + return err; + + if (bit & ~1) + return -EINVAL; + + mutex_lock(&data->update_lock); + if (bit) + data->beep_mask |= (1 << bitnr); + else + data->beep_mask &= ~(1 << bitnr); + + if (bitnr < 8) { + reg = w83627hf_read_value(data, W83781D_REG_BEEP_INTS1); + if (bit) + reg |= (1 << bitnr); + else + reg &= ~(1 << bitnr); + w83627hf_write_value(data, W83781D_REG_BEEP_INTS1, reg); + } else if (bitnr < 16) { + reg = w83627hf_read_value(data, W83781D_REG_BEEP_INTS2); + if (bit) + reg |= (1 << (bitnr - 8)); + else + reg &= ~(1 << (bitnr - 8)); + w83627hf_write_value(data, W83781D_REG_BEEP_INTS2, reg); + } else { + reg = w83627hf_read_value(data, W83781D_REG_BEEP_INTS3); + if (bit) + reg |= (1 << (bitnr - 16)); + else + reg &= ~(1 << (bitnr - 16)); + w83627hf_write_value(data, W83781D_REG_BEEP_INTS3, reg); + } + mutex_unlock(&data->update_lock); + + return count; +} + +static SENSOR_DEVICE_ATTR(in0_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 0); +static SENSOR_DEVICE_ATTR(in1_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 1); +static SENSOR_DEVICE_ATTR(in2_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 2); +static SENSOR_DEVICE_ATTR(in3_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 3); +static SENSOR_DEVICE_ATTR(in4_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 8); +static SENSOR_DEVICE_ATTR(in5_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 9); +static SENSOR_DEVICE_ATTR(in6_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 10); +static SENSOR_DEVICE_ATTR(in7_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 16); +static SENSOR_DEVICE_ATTR(in8_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 17); +static SENSOR_DEVICE_ATTR(fan1_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 6); +static SENSOR_DEVICE_ATTR(fan2_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 7); +static SENSOR_DEVICE_ATTR(fan3_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 11); +static SENSOR_DEVICE_ATTR(temp1_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 4); +static SENSOR_DEVICE_ATTR(temp2_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 5); +static SENSOR_DEVICE_ATTR(temp3_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 13); +static SENSOR_DEVICE_ATTR(beep_enable, S_IRUGO | S_IWUSR, + show_beep, store_beep, 15); + +static ssize_t +show_fan_div(struct device *dev, struct device_attribute *devattr, char *buf) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = w83627hf_update_device(dev); + return sprintf(buf, "%ld\n", + (long) DIV_FROM_REG(data->fan_div[nr])); +} +/* + * Note: we save and restore the fan minimum here, because its value is + * determined in part by the fan divisor. This follows the principle of + * least surprise; the user doesn't expect the fan minimum to change just + * because the divisor changed. + */ +static ssize_t +store_fan_div(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = dev_get_drvdata(dev); + unsigned long min; + u8 reg; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + /* Save fan_min */ + min = FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr])); + + data->fan_div[nr] = DIV_TO_REG(val); + + reg = (w83627hf_read_value(data, nr==2 ? W83781D_REG_PIN : W83781D_REG_VID_FANDIV) + & (nr==0 ? 0xcf : 0x3f)) + | ((data->fan_div[nr] & 0x03) << (nr==0 ? 4 : 6)); + w83627hf_write_value(data, nr==2 ? W83781D_REG_PIN : W83781D_REG_VID_FANDIV, reg); + + reg = (w83627hf_read_value(data, W83781D_REG_VBAT) + & ~(1 << (5 + nr))) + | ((data->fan_div[nr] & 0x04) << (3 + nr)); + w83627hf_write_value(data, W83781D_REG_VBAT, reg); + + /* Restore fan_min */ + data->fan_min[nr] = FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr])); + w83627hf_write_value(data, W83627HF_REG_FAN_MIN(nr), data->fan_min[nr]); + + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO|S_IWUSR, + show_fan_div, store_fan_div, 0); +static SENSOR_DEVICE_ATTR(fan2_div, S_IRUGO|S_IWUSR, + show_fan_div, store_fan_div, 1); +static SENSOR_DEVICE_ATTR(fan3_div, S_IRUGO|S_IWUSR, + show_fan_div, store_fan_div, 2); + +static ssize_t +show_pwm(struct device *dev, struct device_attribute *devattr, char *buf) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = w83627hf_update_device(dev); + return sprintf(buf, "%ld\n", (long) data->pwm[nr]); +} + +static ssize_t +store_pwm(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + if (data->type == w83627thf) { + /* bits 0-3 are reserved in 627THF */ + data->pwm[nr] = PWM_TO_REG(val) & 0xf0; + w83627hf_write_value(data, + W836X7HF_REG_PWM(data->type, nr), + data->pwm[nr] | + (w83627hf_read_value(data, + W836X7HF_REG_PWM(data->type, nr)) & 0x0f)); + } else { + data->pwm[nr] = PWM_TO_REG(val); + w83627hf_write_value(data, + W836X7HF_REG_PWM(data->type, nr), + data->pwm[nr]); + } + + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm3, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 2); + +static ssize_t +show_pwm_enable(struct device *dev, struct device_attribute *devattr, char *buf) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = w83627hf_update_device(dev); + return sprintf(buf, "%d\n", data->pwm_enable[nr]); +} + +static ssize_t +store_pwm_enable(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = dev_get_drvdata(dev); + u8 reg; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (!val || val > 3) /* modes 1, 2 and 3 are supported */ + return -EINVAL; + mutex_lock(&data->update_lock); + data->pwm_enable[nr] = val; + reg = w83627hf_read_value(data, W83627THF_REG_PWM_ENABLE[nr]); + reg &= ~(0x03 << W83627THF_PWM_ENABLE_SHIFT[nr]); + reg |= (val - 1) << W83627THF_PWM_ENABLE_SHIFT[nr]; + w83627hf_write_value(data, W83627THF_REG_PWM_ENABLE[nr], reg); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IRUGO|S_IWUSR, show_pwm_enable, + store_pwm_enable, 0); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IRUGO|S_IWUSR, show_pwm_enable, + store_pwm_enable, 1); +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IRUGO|S_IWUSR, show_pwm_enable, + store_pwm_enable, 2); + +static ssize_t +show_pwm_freq(struct device *dev, struct device_attribute *devattr, char *buf) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = w83627hf_update_device(dev); + if (data->type == w83627hf) + return sprintf(buf, "%ld\n", + pwm_freq_from_reg_627hf(data->pwm_freq[nr])); + else + return sprintf(buf, "%ld\n", + pwm_freq_from_reg(data->pwm_freq[nr])); +} + +static ssize_t +store_pwm_freq(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = dev_get_drvdata(dev); + static const u8 mask[]={0xF8, 0x8F}; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + if (data->type == w83627hf) { + data->pwm_freq[nr] = pwm_freq_to_reg_627hf(val); + w83627hf_write_value(data, W83627HF_REG_PWM_FREQ, + (data->pwm_freq[nr] << (nr*4)) | + (w83627hf_read_value(data, + W83627HF_REG_PWM_FREQ) & mask[nr])); + } else { + data->pwm_freq[nr] = pwm_freq_to_reg(val); + w83627hf_write_value(data, W83637HF_REG_PWM_FREQ[nr], + data->pwm_freq[nr]); + } + + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1_freq, S_IRUGO|S_IWUSR, + show_pwm_freq, store_pwm_freq, 0); +static SENSOR_DEVICE_ATTR(pwm2_freq, S_IRUGO|S_IWUSR, + show_pwm_freq, store_pwm_freq, 1); +static SENSOR_DEVICE_ATTR(pwm3_freq, S_IRUGO|S_IWUSR, + show_pwm_freq, store_pwm_freq, 2); + +static ssize_t +show_temp_type(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = w83627hf_update_device(dev); + return sprintf(buf, "%ld\n", (long) data->sens[nr]); +} + +static ssize_t +store_temp_type(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(devattr)->index; + struct w83627hf_data *data = dev_get_drvdata(dev); + unsigned long val; + u32 tmp; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + switch (val) { + case 1: /* PII/Celeron diode */ + tmp = w83627hf_read_value(data, W83781D_REG_SCFG1); + w83627hf_write_value(data, W83781D_REG_SCFG1, + tmp | BIT_SCFG1[nr]); + tmp = w83627hf_read_value(data, W83781D_REG_SCFG2); + w83627hf_write_value(data, W83781D_REG_SCFG2, + tmp | BIT_SCFG2[nr]); + data->sens[nr] = val; + break; + case 2: /* 3904 */ + tmp = w83627hf_read_value(data, W83781D_REG_SCFG1); + w83627hf_write_value(data, W83781D_REG_SCFG1, + tmp | BIT_SCFG1[nr]); + tmp = w83627hf_read_value(data, W83781D_REG_SCFG2); + w83627hf_write_value(data, W83781D_REG_SCFG2, + tmp & ~BIT_SCFG2[nr]); + data->sens[nr] = val; + break; + case W83781D_DEFAULT_BETA: + dev_warn(dev, "Sensor type %d is deprecated, please use 4 " + "instead\n", W83781D_DEFAULT_BETA); + /* fall through */ + case 4: /* thermistor */ + tmp = w83627hf_read_value(data, W83781D_REG_SCFG1); + w83627hf_write_value(data, W83781D_REG_SCFG1, + tmp & ~BIT_SCFG1[nr]); + data->sens[nr] = val; + break; + default: + dev_err(dev, + "Invalid sensor type %ld; must be 1, 2, or 4\n", + (long) val); + break; + } + + mutex_unlock(&data->update_lock); + return count; +} + +#define sysfs_temp_type(offset) \ +static SENSOR_DEVICE_ATTR(temp##offset##_type, S_IRUGO | S_IWUSR, \ + show_temp_type, store_temp_type, offset - 1); + +sysfs_temp_type(1); +sysfs_temp_type(2); +sysfs_temp_type(3); + +static ssize_t +name_show(struct device *dev, struct device_attribute *devattr, char *buf) +{ + struct w83627hf_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} +static DEVICE_ATTR_RO(name); + +static int __init w83627hf_find(int sioaddr, unsigned short *addr, + struct w83627hf_sio_data *sio_data) +{ + int err; + u16 val; + + static __initconst char *const names[] = { + "W83627HF", + "W83627THF", + "W83697HF", + "W83637HF", + "W83687THF", + }; + + sio_data->sioaddr = sioaddr; + err = superio_enter(sio_data); + if (err) + return err; + + err = -ENODEV; + val = force_id ? force_id : superio_inb(sio_data, DEVID); + switch (val) { + case W627_DEVID: + sio_data->type = w83627hf; + break; + case W627THF_DEVID: + sio_data->type = w83627thf; + break; + case W697_DEVID: + sio_data->type = w83697hf; + break; + case W637_DEVID: + sio_data->type = w83637hf; + break; + case W687THF_DEVID: + sio_data->type = w83687thf; + break; + case 0xff: /* No device at all */ + goto exit; + default: + pr_debug(DRVNAME ": Unsupported chip (DEVID=0x%02x)\n", val); + goto exit; + } + + superio_select(sio_data, W83627HF_LD_HWM); + val = (superio_inb(sio_data, WINB_BASE_REG) << 8) | + superio_inb(sio_data, WINB_BASE_REG + 1); + *addr = val & WINB_ALIGNMENT; + if (*addr == 0) { + pr_warn("Base address not set, skipping\n"); + goto exit; + } + + val = superio_inb(sio_data, WINB_ACT_REG); + if (!(val & 0x01)) { + pr_warn("Enabling HWM logical device\n"); + superio_outb(sio_data, WINB_ACT_REG, val | 0x01); + } + + err = 0; + pr_info(DRVNAME ": Found %s chip at %#x\n", + names[sio_data->type], *addr); + + exit: + superio_exit(sio_data); + return err; +} + +#define VIN_UNIT_ATTRS(_X_) \ + &sensor_dev_attr_in##_X_##_input.dev_attr.attr, \ + &sensor_dev_attr_in##_X_##_min.dev_attr.attr, \ + &sensor_dev_attr_in##_X_##_max.dev_attr.attr, \ + &sensor_dev_attr_in##_X_##_alarm.dev_attr.attr, \ + &sensor_dev_attr_in##_X_##_beep.dev_attr.attr + +#define FAN_UNIT_ATTRS(_X_) \ + &sensor_dev_attr_fan##_X_##_input.dev_attr.attr, \ + &sensor_dev_attr_fan##_X_##_min.dev_attr.attr, \ + &sensor_dev_attr_fan##_X_##_div.dev_attr.attr, \ + &sensor_dev_attr_fan##_X_##_alarm.dev_attr.attr, \ + &sensor_dev_attr_fan##_X_##_beep.dev_attr.attr + +#define TEMP_UNIT_ATTRS(_X_) \ + &sensor_dev_attr_temp##_X_##_input.dev_attr.attr, \ + &sensor_dev_attr_temp##_X_##_max.dev_attr.attr, \ + &sensor_dev_attr_temp##_X_##_max_hyst.dev_attr.attr, \ + &sensor_dev_attr_temp##_X_##_type.dev_attr.attr, \ + &sensor_dev_attr_temp##_X_##_alarm.dev_attr.attr, \ + &sensor_dev_attr_temp##_X_##_beep.dev_attr.attr + +static struct attribute *w83627hf_attributes[] = { + &dev_attr_in0_input.attr, + &dev_attr_in0_min.attr, + &dev_attr_in0_max.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in0_beep.dev_attr.attr, + VIN_UNIT_ATTRS(2), + VIN_UNIT_ATTRS(3), + VIN_UNIT_ATTRS(4), + VIN_UNIT_ATTRS(7), + VIN_UNIT_ATTRS(8), + + FAN_UNIT_ATTRS(1), + FAN_UNIT_ATTRS(2), + + TEMP_UNIT_ATTRS(1), + TEMP_UNIT_ATTRS(2), + + &dev_attr_alarms.attr, + &sensor_dev_attr_beep_enable.dev_attr.attr, + &dev_attr_beep_mask.attr, + + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &dev_attr_name.attr, + NULL +}; + +static const struct attribute_group w83627hf_group = { + .attrs = w83627hf_attributes, +}; + +static struct attribute *w83627hf_attributes_opt[] = { + VIN_UNIT_ATTRS(1), + VIN_UNIT_ATTRS(5), + VIN_UNIT_ATTRS(6), + + FAN_UNIT_ATTRS(3), + TEMP_UNIT_ATTRS(3), + &sensor_dev_attr_pwm3.dev_attr.attr, + + &sensor_dev_attr_pwm1_freq.dev_attr.attr, + &sensor_dev_attr_pwm2_freq.dev_attr.attr, + &sensor_dev_attr_pwm3_freq.dev_attr.attr, + + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + + NULL +}; + +static const struct attribute_group w83627hf_group_opt = { + .attrs = w83627hf_attributes_opt, +}; + +static int w83627hf_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct w83627hf_sio_data *sio_data = dev_get_platdata(dev); + struct w83627hf_data *data; + struct resource *res; + int err, i; + + static const char *names[] = { + "w83627hf", + "w83627thf", + "w83697hf", + "w83637hf", + "w83687thf", + }; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(dev, res->start, WINB_REGION_SIZE, DRVNAME)) { + dev_err(dev, "Failed to request region 0x%lx-0x%lx\n", + (unsigned long)res->start, + (unsigned long)(res->start + WINB_REGION_SIZE - 1)); + return -EBUSY; + } + + data = devm_kzalloc(dev, sizeof(struct w83627hf_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = res->start; + data->type = sio_data->type; + data->name = names[sio_data->type]; + mutex_init(&data->lock); + mutex_init(&data->update_lock); + platform_set_drvdata(pdev, data); + + /* Initialize the chip */ + w83627hf_init_device(pdev); + + /* A few vars need to be filled upon startup */ + for (i = 0; i <= 2; i++) + data->fan_min[i] = w83627hf_read_value( + data, W83627HF_REG_FAN_MIN(i)); + w83627hf_update_fan_div(data); + + /* Register common device attributes */ + err = sysfs_create_group(&dev->kobj, &w83627hf_group); + if (err) + return err; + + /* Register chip-specific device attributes */ + if (data->type == w83627hf || data->type == w83697hf) + if ((err = device_create_file(dev, + &sensor_dev_attr_in5_input.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_in5_min.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_in5_max.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_in5_alarm.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_in5_beep.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_in6_input.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_in6_min.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_in6_max.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_in6_alarm.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_in6_beep.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_pwm1_freq.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_pwm2_freq.dev_attr))) + goto error; + + if (data->type != w83697hf) + if ((err = device_create_file(dev, + &sensor_dev_attr_in1_input.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_in1_min.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_in1_max.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_in1_alarm.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_in1_beep.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_fan3_input.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_fan3_min.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_fan3_div.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_fan3_alarm.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_fan3_beep.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_temp3_input.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_temp3_max.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_temp3_max_hyst.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_temp3_alarm.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_temp3_beep.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_temp3_type.dev_attr))) + goto error; + + if (data->type != w83697hf && data->vid != 0xff) { + /* Convert VID to voltage based on VRM */ + data->vrm = vid_which_vrm(); + + if ((err = device_create_file(dev, &dev_attr_cpu0_vid)) + || (err = device_create_file(dev, &dev_attr_vrm))) + goto error; + } + + if (data->type == w83627thf || data->type == w83637hf + || data->type == w83687thf) { + err = device_create_file(dev, &sensor_dev_attr_pwm3.dev_attr); + if (err) + goto error; + } + + if (data->type == w83637hf || data->type == w83687thf) + if ((err = device_create_file(dev, + &sensor_dev_attr_pwm1_freq.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_pwm2_freq.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_pwm3_freq.dev_attr))) + goto error; + + if (data->type != w83627hf) + if ((err = device_create_file(dev, + &sensor_dev_attr_pwm1_enable.dev_attr)) + || (err = device_create_file(dev, + &sensor_dev_attr_pwm2_enable.dev_attr))) + goto error; + + if (data->type == w83627thf || data->type == w83637hf + || data->type == w83687thf) { + err = device_create_file(dev, + &sensor_dev_attr_pwm3_enable.dev_attr); + if (err) + goto error; + } + + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto error; + } + + return 0; + + error: + sysfs_remove_group(&dev->kobj, &w83627hf_group); + sysfs_remove_group(&dev->kobj, &w83627hf_group_opt); + return err; +} + +static int w83627hf_remove(struct platform_device *pdev) +{ + struct w83627hf_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + + sysfs_remove_group(&pdev->dev.kobj, &w83627hf_group); + sysfs_remove_group(&pdev->dev.kobj, &w83627hf_group_opt); + + return 0; +} + + +/* Registers 0x50-0x5f are banked */ +static inline void w83627hf_set_bank(struct w83627hf_data *data, u16 reg) +{ + if ((reg & 0x00f0) == 0x50) { + outb_p(W83781D_REG_BANK, data->addr + W83781D_ADDR_REG_OFFSET); + outb_p(reg >> 8, data->addr + W83781D_DATA_REG_OFFSET); + } +} + +/* Not strictly necessary, but play it safe for now */ +static inline void w83627hf_reset_bank(struct w83627hf_data *data, u16 reg) +{ + if (reg & 0xff00) { + outb_p(W83781D_REG_BANK, data->addr + W83781D_ADDR_REG_OFFSET); + outb_p(0, data->addr + W83781D_DATA_REG_OFFSET); + } +} + +static int w83627hf_read_value(struct w83627hf_data *data, u16 reg) +{ + int res, word_sized; + + mutex_lock(&data->lock); + word_sized = (((reg & 0xff00) == 0x100) + || ((reg & 0xff00) == 0x200)) + && (((reg & 0x00ff) == 0x50) + || ((reg & 0x00ff) == 0x53) + || ((reg & 0x00ff) == 0x55)); + w83627hf_set_bank(data, reg); + outb_p(reg & 0xff, data->addr + W83781D_ADDR_REG_OFFSET); + res = inb_p(data->addr + W83781D_DATA_REG_OFFSET); + if (word_sized) { + outb_p((reg & 0xff) + 1, + data->addr + W83781D_ADDR_REG_OFFSET); + res = + (res << 8) + inb_p(data->addr + + W83781D_DATA_REG_OFFSET); + } + w83627hf_reset_bank(data, reg); + mutex_unlock(&data->lock); + return res; +} + +static int w83627thf_read_gpio5(struct platform_device *pdev) +{ + struct w83627hf_sio_data *sio_data = dev_get_platdata(&pdev->dev); + int res = 0xff, sel; + + if (superio_enter(sio_data)) { + /* + * Some other driver reserved the address space for itself. + * We don't want to fail driver instantiation because of that, + * so display a warning and keep going. + */ + dev_warn(&pdev->dev, + "Can not read VID data: Failed to enable SuperIO access\n"); + return res; + } + + superio_select(sio_data, W83627HF_LD_GPIO5); + + res = 0xff; + + /* Make sure these GPIO pins are enabled */ + if (!(superio_inb(sio_data, W83627THF_GPIO5_EN) & (1<<3))) { + dev_dbg(&pdev->dev, "GPIO5 disabled, no VID function\n"); + goto exit; + } + + /* + * Make sure the pins are configured for input + * There must be at least five (VRM 9), and possibly 6 (VRM 10) + */ + sel = superio_inb(sio_data, W83627THF_GPIO5_IOSR) & 0x3f; + if ((sel & 0x1f) != 0x1f) { + dev_dbg(&pdev->dev, "GPIO5 not configured for VID " + "function\n"); + goto exit; + } + + dev_info(&pdev->dev, "Reading VID from GPIO5\n"); + res = superio_inb(sio_data, W83627THF_GPIO5_DR) & sel; + +exit: + superio_exit(sio_data); + return res; +} + +static int w83687thf_read_vid(struct platform_device *pdev) +{ + struct w83627hf_sio_data *sio_data = dev_get_platdata(&pdev->dev); + int res = 0xff; + + if (superio_enter(sio_data)) { + /* + * Some other driver reserved the address space for itself. + * We don't want to fail driver instantiation because of that, + * so display a warning and keep going. + */ + dev_warn(&pdev->dev, + "Can not read VID data: Failed to enable SuperIO access\n"); + return res; + } + + superio_select(sio_data, W83627HF_LD_HWM); + + /* Make sure these GPIO pins are enabled */ + if (!(superio_inb(sio_data, W83687THF_VID_EN) & (1 << 2))) { + dev_dbg(&pdev->dev, "VID disabled, no VID function\n"); + goto exit; + } + + /* Make sure the pins are configured for input */ + if (!(superio_inb(sio_data, W83687THF_VID_CFG) & (1 << 4))) { + dev_dbg(&pdev->dev, "VID configured as output, " + "no VID function\n"); + goto exit; + } + + res = superio_inb(sio_data, W83687THF_VID_DATA) & 0x3f; + +exit: + superio_exit(sio_data); + return res; +} + +static int w83627hf_write_value(struct w83627hf_data *data, u16 reg, u16 value) +{ + int word_sized; + + mutex_lock(&data->lock); + word_sized = (((reg & 0xff00) == 0x100) + || ((reg & 0xff00) == 0x200)) + && (((reg & 0x00ff) == 0x53) + || ((reg & 0x00ff) == 0x55)); + w83627hf_set_bank(data, reg); + outb_p(reg & 0xff, data->addr + W83781D_ADDR_REG_OFFSET); + if (word_sized) { + outb_p(value >> 8, + data->addr + W83781D_DATA_REG_OFFSET); + outb_p((reg & 0xff) + 1, + data->addr + W83781D_ADDR_REG_OFFSET); + } + outb_p(value & 0xff, + data->addr + W83781D_DATA_REG_OFFSET); + w83627hf_reset_bank(data, reg); + mutex_unlock(&data->lock); + return 0; +} + +static void w83627hf_init_device(struct platform_device *pdev) +{ + struct w83627hf_data *data = platform_get_drvdata(pdev); + int i; + enum chips type = data->type; + u8 tmp; + + /* Minimize conflicts with other winbond i2c-only clients... */ + /* disable i2c subclients... how to disable main i2c client?? */ + /* force i2c address to relatively uncommon address */ + if (type == w83627hf) { + w83627hf_write_value(data, W83781D_REG_I2C_SUBADDR, 0x89); + w83627hf_write_value(data, W83781D_REG_I2C_ADDR, force_i2c); + } + + /* Read VID only once */ + if (type == w83627hf || type == w83637hf) { + int lo = w83627hf_read_value(data, W83781D_REG_VID_FANDIV); + int hi = w83627hf_read_value(data, W83781D_REG_CHIPID); + data->vid = (lo & 0x0f) | ((hi & 0x01) << 4); + } else if (type == w83627thf) { + data->vid = w83627thf_read_gpio5(pdev); + } else if (type == w83687thf) { + data->vid = w83687thf_read_vid(pdev); + } + + /* Read VRM & OVT Config only once */ + if (type == w83627thf || type == w83637hf || type == w83687thf) { + data->vrm_ovt = + w83627hf_read_value(data, W83627THF_REG_VRM_OVT_CFG); + } + + tmp = w83627hf_read_value(data, W83781D_REG_SCFG1); + for (i = 1; i <= 3; i++) { + if (!(tmp & BIT_SCFG1[i - 1])) { + data->sens[i - 1] = 4; + } else { + if (w83627hf_read_value + (data, + W83781D_REG_SCFG2) & BIT_SCFG2[i - 1]) + data->sens[i - 1] = 1; + else + data->sens[i - 1] = 2; + } + if ((type == w83697hf) && (i == 2)) + break; + } + + if(init) { + /* Enable temp2 */ + tmp = w83627hf_read_value(data, W83627HF_REG_TEMP2_CONFIG); + if (tmp & 0x01) { + dev_warn(&pdev->dev, "Enabling temp2, readings " + "might not make sense\n"); + w83627hf_write_value(data, W83627HF_REG_TEMP2_CONFIG, + tmp & 0xfe); + } + + /* Enable temp3 */ + if (type != w83697hf) { + tmp = w83627hf_read_value(data, + W83627HF_REG_TEMP3_CONFIG); + if (tmp & 0x01) { + dev_warn(&pdev->dev, "Enabling temp3, " + "readings might not make sense\n"); + w83627hf_write_value(data, + W83627HF_REG_TEMP3_CONFIG, tmp & 0xfe); + } + } + } + + /* Start monitoring */ + w83627hf_write_value(data, W83781D_REG_CONFIG, + (w83627hf_read_value(data, + W83781D_REG_CONFIG) & 0xf7) + | 0x01); + + /* Enable VBAT monitoring if needed */ + tmp = w83627hf_read_value(data, W83781D_REG_VBAT); + if (!(tmp & 0x01)) + w83627hf_write_value(data, W83781D_REG_VBAT, tmp | 0x01); +} + +static void w83627hf_update_fan_div(struct w83627hf_data *data) +{ + int reg; + + reg = w83627hf_read_value(data, W83781D_REG_VID_FANDIV); + data->fan_div[0] = (reg >> 4) & 0x03; + data->fan_div[1] = (reg >> 6) & 0x03; + if (data->type != w83697hf) { + data->fan_div[2] = (w83627hf_read_value(data, + W83781D_REG_PIN) >> 6) & 0x03; + } + reg = w83627hf_read_value(data, W83781D_REG_VBAT); + data->fan_div[0] |= (reg >> 3) & 0x04; + data->fan_div[1] |= (reg >> 4) & 0x04; + if (data->type != w83697hf) + data->fan_div[2] |= (reg >> 5) & 0x04; +} + +static struct w83627hf_data *w83627hf_update_device(struct device *dev) +{ + struct w83627hf_data *data = dev_get_drvdata(dev); + int i, num_temps = (data->type == w83697hf) ? 2 : 3; + int num_pwms = (data->type == w83697hf) ? 2 : 3; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + for (i = 0; i <= 8; i++) { + /* skip missing sensors */ + if (((data->type == w83697hf) && (i == 1)) || + ((data->type != w83627hf && data->type != w83697hf) + && (i == 5 || i == 6))) + continue; + data->in[i] = + w83627hf_read_value(data, W83781D_REG_IN(i)); + data->in_min[i] = + w83627hf_read_value(data, + W83781D_REG_IN_MIN(i)); + data->in_max[i] = + w83627hf_read_value(data, + W83781D_REG_IN_MAX(i)); + } + for (i = 0; i <= 2; i++) { + data->fan[i] = + w83627hf_read_value(data, W83627HF_REG_FAN(i)); + data->fan_min[i] = + w83627hf_read_value(data, + W83627HF_REG_FAN_MIN(i)); + } + for (i = 0; i <= 2; i++) { + u8 tmp = w83627hf_read_value(data, + W836X7HF_REG_PWM(data->type, i)); + /* bits 0-3 are reserved in 627THF */ + if (data->type == w83627thf) + tmp &= 0xf0; + data->pwm[i] = tmp; + if (i == 1 && + (data->type == w83627hf || data->type == w83697hf)) + break; + } + if (data->type == w83627hf) { + u8 tmp = w83627hf_read_value(data, + W83627HF_REG_PWM_FREQ); + data->pwm_freq[0] = tmp & 0x07; + data->pwm_freq[1] = (tmp >> 4) & 0x07; + } else if (data->type != w83627thf) { + for (i = 1; i <= 3; i++) { + data->pwm_freq[i - 1] = + w83627hf_read_value(data, + W83637HF_REG_PWM_FREQ[i - 1]); + if (i == 2 && (data->type == w83697hf)) + break; + } + } + if (data->type != w83627hf) { + for (i = 0; i < num_pwms; i++) { + u8 tmp = w83627hf_read_value(data, + W83627THF_REG_PWM_ENABLE[i]); + data->pwm_enable[i] = + ((tmp >> W83627THF_PWM_ENABLE_SHIFT[i]) + & 0x03) + 1; + } + } + for (i = 0; i < num_temps; i++) { + data->temp[i] = w83627hf_read_value( + data, w83627hf_reg_temp[i]); + data->temp_max[i] = w83627hf_read_value( + data, w83627hf_reg_temp_over[i]); + data->temp_max_hyst[i] = w83627hf_read_value( + data, w83627hf_reg_temp_hyst[i]); + } + + w83627hf_update_fan_div(data); + + data->alarms = + w83627hf_read_value(data, W83781D_REG_ALARM1) | + (w83627hf_read_value(data, W83781D_REG_ALARM2) << 8) | + (w83627hf_read_value(data, W83781D_REG_ALARM3) << 16); + i = w83627hf_read_value(data, W83781D_REG_BEEP_INTS2); + data->beep_mask = (i << 8) | + w83627hf_read_value(data, W83781D_REG_BEEP_INTS1) | + w83627hf_read_value(data, W83781D_REG_BEEP_INTS3) << 16; + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static int __init w83627hf_device_add(unsigned short address, + const struct w83627hf_sio_data *sio_data) +{ + struct resource res = { + .start = address + WINB_REGION_OFFSET, + .end = address + WINB_REGION_OFFSET + WINB_REGION_SIZE - 1, + .name = DRVNAME, + .flags = IORESOURCE_IO, + }; + int err; + + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + + pdev = platform_device_alloc(DRVNAME, address); + if (!pdev) { + err = -ENOMEM; + pr_err("Device allocation failed\n"); + goto exit; + } + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add_data(pdev, sio_data, + sizeof(struct w83627hf_sio_data)); + if (err) { + pr_err("Platform data allocation failed\n"); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + +exit_device_put: + platform_device_put(pdev); +exit: + return err; +} + +static int __init sensors_w83627hf_init(void) +{ + int err; + unsigned short address; + struct w83627hf_sio_data sio_data; + + if (w83627hf_find(0x2e, &address, &sio_data) + && w83627hf_find(0x4e, &address, &sio_data)) + return -ENODEV; + + err = platform_driver_register(&w83627hf_driver); + if (err) + goto exit; + + /* Sets global pdev as a side effect */ + err = w83627hf_device_add(address, &sio_data); + if (err) + goto exit_driver; + + return 0; + +exit_driver: + platform_driver_unregister(&w83627hf_driver); +exit: + return err; +} + +static void __exit sensors_w83627hf_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&w83627hf_driver); +} + +MODULE_AUTHOR("Frodo Looijaard , " + "Philip Edelbrock , " + "and Mark Studebaker "); +MODULE_DESCRIPTION("W83627HF driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_w83627hf_init); +module_exit(sensors_w83627hf_exit); diff --git a/drivers/hwmon/w83773g.c b/drivers/hwmon/w83773g.c new file mode 100644 index 000000000..e858093ac --- /dev/null +++ b/drivers/hwmon/w83773g.c @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2017 IBM Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Driver for the Nuvoton W83773G SMBus temperature sensor IC. + * Supported models: W83773G + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* W83773 has 3 channels */ +#define W83773_CHANNELS 3 + +/* The W83773 registers */ +#define W83773_CONVERSION_RATE_REG_READ 0x04 +#define W83773_CONVERSION_RATE_REG_WRITE 0x0A +#define W83773_MANUFACTURER_ID_REG 0xFE +#define W83773_LOCAL_TEMP 0x00 + +static const u8 W83773_STATUS[2] = { 0x02, 0x17 }; + +static const u8 W83773_TEMP_LSB[2] = { 0x10, 0x25 }; +static const u8 W83773_TEMP_MSB[2] = { 0x01, 0x24 }; + +static const u8 W83773_OFFSET_LSB[2] = { 0x12, 0x16 }; +static const u8 W83773_OFFSET_MSB[2] = { 0x11, 0x15 }; + +/* this is the number of sensors in the device */ +static const struct i2c_device_id w83773_id[] = { + { "w83773g" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, w83773_id); + +static const struct of_device_id w83773_of_match[] = { + { + .compatible = "nuvoton,w83773g" + }, + { }, +}; +MODULE_DEVICE_TABLE(of, w83773_of_match); + +static inline long temp_of_local(s8 reg) +{ + return reg * 1000; +} + +static inline long temp_of_remote(s8 hb, u8 lb) +{ + return (hb << 3 | lb >> 5) * 125; +} + +static int get_local_temp(struct regmap *regmap, long *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(regmap, W83773_LOCAL_TEMP, ®val); + if (ret < 0) + return ret; + + *val = temp_of_local(regval); + return 0; +} + +static int get_remote_temp(struct regmap *regmap, int index, long *val) +{ + unsigned int regval_high; + unsigned int regval_low; + int ret; + + ret = regmap_read(regmap, W83773_TEMP_MSB[index], ®val_high); + if (ret < 0) + return ret; + + ret = regmap_read(regmap, W83773_TEMP_LSB[index], ®val_low); + if (ret < 0) + return ret; + + *val = temp_of_remote(regval_high, regval_low); + return 0; +} + +static int get_fault(struct regmap *regmap, int index, long *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(regmap, W83773_STATUS[index], ®val); + if (ret < 0) + return ret; + + *val = (regval & 0x04) >> 2; + return 0; +} + +static int get_offset(struct regmap *regmap, int index, long *val) +{ + unsigned int regval_high; + unsigned int regval_low; + int ret; + + ret = regmap_read(regmap, W83773_OFFSET_MSB[index], ®val_high); + if (ret < 0) + return ret; + + ret = regmap_read(regmap, W83773_OFFSET_LSB[index], ®val_low); + if (ret < 0) + return ret; + + *val = temp_of_remote(regval_high, regval_low); + return 0; +} + +static int set_offset(struct regmap *regmap, int index, long val) +{ + int ret; + u8 high_byte; + u8 low_byte; + + val = clamp_val(val, -127825, 127825); + /* offset value equals to (high_byte << 3 | low_byte >> 5) * 125 */ + val /= 125; + high_byte = val >> 3; + low_byte = (val & 0x07) << 5; + + ret = regmap_write(regmap, W83773_OFFSET_MSB[index], high_byte); + if (ret < 0) + return ret; + + return regmap_write(regmap, W83773_OFFSET_LSB[index], low_byte); +} + +static int get_update_interval(struct regmap *regmap, long *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(regmap, W83773_CONVERSION_RATE_REG_READ, ®val); + if (ret < 0) + return ret; + + *val = 16000 >> regval; + return 0; +} + +static int set_update_interval(struct regmap *regmap, long val) +{ + int rate; + + /* + * For valid rates, interval can be calculated as + * interval = (1 << (8 - rate)) * 62.5; + * Rounded rate is therefore + * rate = 8 - __fls(interval * 8 / (62.5 * 7)); + * Use clamp_val() to avoid overflows, and to ensure valid input + * for __fls. + */ + val = clamp_val(val, 62, 16000) * 10; + rate = 8 - __fls((val * 8 / (625 * 7))); + return regmap_write(regmap, W83773_CONVERSION_RATE_REG_WRITE, rate); +} + +static int w83773_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct regmap *regmap = dev_get_drvdata(dev); + + if (type == hwmon_chip) { + if (attr == hwmon_chip_update_interval) + return get_update_interval(regmap, val); + return -EOPNOTSUPP; + } + + switch (attr) { + case hwmon_temp_input: + if (channel == 0) + return get_local_temp(regmap, val); + return get_remote_temp(regmap, channel - 1, val); + case hwmon_temp_fault: + return get_fault(regmap, channel - 1, val); + case hwmon_temp_offset: + return get_offset(regmap, channel - 1, val); + default: + return -EOPNOTSUPP; + } +} + +static int w83773_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct regmap *regmap = dev_get_drvdata(dev); + + if (type == hwmon_chip && attr == hwmon_chip_update_interval) + return set_update_interval(regmap, val); + + if (type == hwmon_temp && attr == hwmon_temp_offset) + return set_offset(regmap, channel - 1, val); + + return -EOPNOTSUPP; +} + +static umode_t w83773_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return 0644; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_fault: + return 0444; + case hwmon_temp_offset: + return 0644; + } + break; + default: + break; + } + return 0; +} + +static const u32 w83773_chip_config[] = { + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL, + 0 +}; + +static const struct hwmon_channel_info w83773_chip = { + .type = hwmon_chip, + .config = w83773_chip_config, +}; + +static const u32 w83773_temp_config[] = { + HWMON_T_INPUT, + HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_OFFSET, + HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_OFFSET, + 0 +}; + +static const struct hwmon_channel_info w83773_temp = { + .type = hwmon_temp, + .config = w83773_temp_config, +}; + +static const struct hwmon_channel_info *w83773_info[] = { + &w83773_chip, + &w83773_temp, + NULL +}; + +static const struct hwmon_ops w83773_ops = { + .is_visible = w83773_is_visible, + .read = w83773_read, + .write = w83773_write, +}; + +static const struct hwmon_chip_info w83773_chip_info = { + .ops = &w83773_ops, + .info = w83773_info, +}; + +static const struct regmap_config w83773_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int w83773_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(client, &w83773_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(regmap); + } + + /* Set the conversion rate to 2 Hz */ + ret = regmap_write(regmap, W83773_CONVERSION_RATE_REG_WRITE, 0x05); + if (ret < 0) { + dev_err(&client->dev, "error writing config rate register\n"); + return ret; + } + + i2c_set_clientdata(client, regmap); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, + client->name, + regmap, + &w83773_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct i2c_driver w83773_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "w83773g", + .of_match_table = of_match_ptr(w83773_of_match), + }, + .probe = w83773_probe, + .id_table = w83773_id, +}; + +module_i2c_driver(w83773_driver); + +MODULE_AUTHOR("Lei YU "); +MODULE_DESCRIPTION("W83773G temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/w83781d.c b/drivers/hwmon/w83781d.c new file mode 100644 index 000000000..2b0f182da --- /dev/null +++ b/drivers/hwmon/w83781d.c @@ -0,0 +1,2098 @@ +/* + * w83781d.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (c) 1998 - 2001 Frodo Looijaard , + * Philip Edelbrock , + * and Mark Studebaker + * Copyright (c) 2007 - 2008 Jean Delvare + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Supports following chips: + * + * Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA + * as99127f 7 3 0 3 0x31 0x12c3 yes no + * as99127f rev.2 (type_name = as99127f) 0x31 0x5ca3 yes no + * w83781d 7 3 0 3 0x10-1 0x5ca3 yes yes + * w83782d 9 3 2-4 3 0x30 0x5ca3 yes yes + * w83783s 5-6 3 2 1-2 0x40 0x5ca3 yes no + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_ISA +#include +#include +#include +#endif + +#include "lm75.h" + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, I2C_CLIENT_END }; + +enum chips { w83781d, w83782d, w83783s, as99127f }; + +/* Insmod parameters */ +static unsigned short force_subclients[4]; +module_param_array(force_subclients, short, NULL, 0); +MODULE_PARM_DESC(force_subclients, + "List of subclient addresses: {bus, clientaddr, subclientaddr1, subclientaddr2}"); + +static bool reset; +module_param(reset, bool, 0); +MODULE_PARM_DESC(reset, "Set to one to reset chip on load"); + +static bool init = 1; +module_param(init, bool, 0); +MODULE_PARM_DESC(init, "Set to zero to bypass chip initialization"); + +/* Constants specified below */ + +/* Length of ISA address segment */ +#define W83781D_EXTENT 8 + +/* Where are the ISA address/data registers relative to the base address */ +#define W83781D_ADDR_REG_OFFSET 5 +#define W83781D_DATA_REG_OFFSET 6 + +/* The device registers */ +/* in nr from 0 to 8 */ +#define W83781D_REG_IN_MAX(nr) ((nr < 7) ? (0x2b + (nr) * 2) : \ + (0x554 + (((nr) - 7) * 2))) +#define W83781D_REG_IN_MIN(nr) ((nr < 7) ? (0x2c + (nr) * 2) : \ + (0x555 + (((nr) - 7) * 2))) +#define W83781D_REG_IN(nr) ((nr < 7) ? (0x20 + (nr)) : \ + (0x550 + (nr) - 7)) + +/* fan nr from 0 to 2 */ +#define W83781D_REG_FAN_MIN(nr) (0x3b + (nr)) +#define W83781D_REG_FAN(nr) (0x28 + (nr)) + +#define W83781D_REG_BANK 0x4E +#define W83781D_REG_TEMP2_CONFIG 0x152 +#define W83781D_REG_TEMP3_CONFIG 0x252 +/* temp nr from 1 to 3 */ +#define W83781D_REG_TEMP(nr) ((nr == 3) ? (0x0250) : \ + ((nr == 2) ? (0x0150) : \ + (0x27))) +#define W83781D_REG_TEMP_HYST(nr) ((nr == 3) ? (0x253) : \ + ((nr == 2) ? (0x153) : \ + (0x3A))) +#define W83781D_REG_TEMP_OVER(nr) ((nr == 3) ? (0x255) : \ + ((nr == 2) ? (0x155) : \ + (0x39))) + +#define W83781D_REG_CONFIG 0x40 + +/* Interrupt status (W83781D, AS99127F) */ +#define W83781D_REG_ALARM1 0x41 +#define W83781D_REG_ALARM2 0x42 + +/* Real-time status (W83782D, W83783S) */ +#define W83782D_REG_ALARM1 0x459 +#define W83782D_REG_ALARM2 0x45A +#define W83782D_REG_ALARM3 0x45B + +#define W83781D_REG_BEEP_CONFIG 0x4D +#define W83781D_REG_BEEP_INTS1 0x56 +#define W83781D_REG_BEEP_INTS2 0x57 +#define W83781D_REG_BEEP_INTS3 0x453 /* not on W83781D */ + +#define W83781D_REG_VID_FANDIV 0x47 + +#define W83781D_REG_CHIPID 0x49 +#define W83781D_REG_WCHIPID 0x58 +#define W83781D_REG_CHIPMAN 0x4F +#define W83781D_REG_PIN 0x4B + +/* 782D/783S only */ +#define W83781D_REG_VBAT 0x5D + +/* PWM 782D (1-4) and 783S (1-2) only */ +static const u8 W83781D_REG_PWM[] = { 0x5B, 0x5A, 0x5E, 0x5F }; +#define W83781D_REG_PWMCLK12 0x5C +#define W83781D_REG_PWMCLK34 0x45C + +#define W83781D_REG_I2C_ADDR 0x48 +#define W83781D_REG_I2C_SUBADDR 0x4A + +/* + * The following are undocumented in the data sheets however we + * received the information in an email from Winbond tech support + */ +/* Sensor selection - not on 781d */ +#define W83781D_REG_SCFG1 0x5D +static const u8 BIT_SCFG1[] = { 0x02, 0x04, 0x08 }; + +#define W83781D_REG_SCFG2 0x59 +static const u8 BIT_SCFG2[] = { 0x10, 0x20, 0x40 }; + +#define W83781D_DEFAULT_BETA 3435 + +/* Conversions */ +#define IN_TO_REG(val) clamp_val(((val) + 8) / 16, 0, 255) +#define IN_FROM_REG(val) ((val) * 16) + +static inline u8 +FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 255; + rpm = clamp_val(rpm, 1, 1000000); + return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 254); +} + +static inline long +FAN_FROM_REG(u8 val, int div) +{ + if (val == 0) + return -1; + if (val == 255) + return 0; + return 1350000 / (val * div); +} + +#define TEMP_TO_REG(val) clamp_val((val) / 1000, -127, 128) +#define TEMP_FROM_REG(val) ((val) * 1000) + +#define BEEP_MASK_FROM_REG(val, type) ((type) == as99127f ? \ + (~(val)) & 0x7fff : (val) & 0xff7fff) +#define BEEP_MASK_TO_REG(val, type) ((type) == as99127f ? \ + (~(val)) & 0x7fff : (val) & 0xff7fff) + +#define DIV_FROM_REG(val) (1 << (val)) + +static inline u8 +DIV_TO_REG(long val, enum chips type) +{ + int i; + val = clamp_val(val, 1, + ((type == w83781d || type == as99127f) ? 8 : 128)) >> 1; + for (i = 0; i < 7; i++) { + if (val == 0) + break; + val >>= 1; + } + return i; +} + +struct w83781d_data { + struct i2c_client *client; + struct device *hwmon_dev; + struct mutex lock; + enum chips type; + + /* For ISA device only */ + const char *name; + int isa_addr; + + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + struct i2c_client *lm75[2]; /* for secondary I2C addresses */ + /* array of 2 pointers to subclients */ + + u8 in[9]; /* Register value - 8 & 9 for 782D only */ + u8 in_max[9]; /* Register value - 8 & 9 for 782D only */ + u8 in_min[9]; /* Register value - 8 & 9 for 782D only */ + u8 fan[3]; /* Register value */ + u8 fan_min[3]; /* Register value */ + s8 temp; /* Register value */ + s8 temp_max; /* Register value */ + s8 temp_max_hyst; /* Register value */ + u16 temp_add[2]; /* Register value */ + u16 temp_max_add[2]; /* Register value */ + u16 temp_max_hyst_add[2]; /* Register value */ + u8 fan_div[3]; /* Register encoding, shifted right */ + u8 vid; /* Register encoding, combined */ + u32 alarms; /* Register encoding, combined */ + u32 beep_mask; /* Register encoding, combined */ + u8 pwm[4]; /* Register value */ + u8 pwm2_enable; /* Boolean */ + u16 sens[3]; /* + * 782D/783S only. + * 1 = pentium diode; 2 = 3904 diode; + * 4 = thermistor + */ + u8 vrm; +}; + +static struct w83781d_data *w83781d_data_if_isa(void); +static int w83781d_alias_detect(struct i2c_client *client, u8 chipid); + +static int w83781d_read_value(struct w83781d_data *data, u16 reg); +static int w83781d_write_value(struct w83781d_data *data, u16 reg, u16 value); +static struct w83781d_data *w83781d_update_device(struct device *dev); +static void w83781d_init_device(struct device *dev); + +/* following are the sysfs callback functions */ +#define show_in_reg(reg) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *da, \ + char *buf) \ +{ \ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); \ + struct w83781d_data *data = w83781d_update_device(dev); \ + return sprintf(buf, "%ld\n", \ + (long)IN_FROM_REG(data->reg[attr->index])); \ +} +show_in_reg(in); +show_in_reg(in_min); +show_in_reg(in_max); + +#define store_in_reg(REG, reg) \ +static ssize_t store_in_##reg(struct device *dev, struct device_attribute \ + *da, const char *buf, size_t count) \ +{ \ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); \ + struct w83781d_data *data = dev_get_drvdata(dev); \ + int nr = attr->index; \ + unsigned long val; \ + int err = kstrtoul(buf, 10, &val); \ + if (err) \ + return err; \ + mutex_lock(&data->update_lock); \ + data->in_##reg[nr] = IN_TO_REG(val); \ + w83781d_write_value(data, W83781D_REG_IN_##REG(nr), \ + data->in_##reg[nr]); \ + \ + mutex_unlock(&data->update_lock); \ + return count; \ +} +store_in_reg(MIN, min); +store_in_reg(MAX, max); + +#define sysfs_in_offsets(offset) \ +static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, \ + show_in, NULL, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \ + show_in_min, store_in_min, offset); \ +static SENSOR_DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \ + show_in_max, store_in_max, offset) + +sysfs_in_offsets(0); +sysfs_in_offsets(1); +sysfs_in_offsets(2); +sysfs_in_offsets(3); +sysfs_in_offsets(4); +sysfs_in_offsets(5); +sysfs_in_offsets(6); +sysfs_in_offsets(7); +sysfs_in_offsets(8); + +#define show_fan_reg(reg) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *da, \ + char *buf) \ +{ \ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); \ + struct w83781d_data *data = w83781d_update_device(dev); \ + return sprintf(buf, "%ld\n", \ + FAN_FROM_REG(data->reg[attr->index], \ + DIV_FROM_REG(data->fan_div[attr->index]))); \ +} +show_fan_reg(fan); +show_fan_reg(fan_min); + +static ssize_t +store_fan_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct w83781d_data *data = dev_get_drvdata(dev); + int nr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_min[nr] = + FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); + w83781d_write_value(data, W83781D_REG_FAN_MIN(nr), + data->fan_min[nr]); + + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); +static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR, + show_fan_min, store_fan_min, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); +static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO | S_IWUSR, + show_fan_min, store_fan_min, 1); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2); +static SENSOR_DEVICE_ATTR(fan3_min, S_IRUGO | S_IWUSR, + show_fan_min, store_fan_min, 2); + +#define show_temp_reg(reg) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *da, \ + char *buf) \ +{ \ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); \ + struct w83781d_data *data = w83781d_update_device(dev); \ + int nr = attr->index; \ + if (nr >= 2) { /* TEMP2 and TEMP3 */ \ + return sprintf(buf, "%d\n", \ + LM75_TEMP_FROM_REG(data->reg##_add[nr-2])); \ + } else { /* TEMP1 */ \ + return sprintf(buf, "%ld\n", (long)TEMP_FROM_REG(data->reg)); \ + } \ +} +show_temp_reg(temp); +show_temp_reg(temp_max); +show_temp_reg(temp_max_hyst); + +#define store_temp_reg(REG, reg) \ +static ssize_t store_temp_##reg(struct device *dev, \ + struct device_attribute *da, const char *buf, size_t count) \ +{ \ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); \ + struct w83781d_data *data = dev_get_drvdata(dev); \ + int nr = attr->index; \ + long val; \ + int err = kstrtol(buf, 10, &val); \ + if (err) \ + return err; \ + mutex_lock(&data->update_lock); \ + \ + if (nr >= 2) { /* TEMP2 and TEMP3 */ \ + data->temp_##reg##_add[nr-2] = LM75_TEMP_TO_REG(val); \ + w83781d_write_value(data, W83781D_REG_TEMP_##REG(nr), \ + data->temp_##reg##_add[nr-2]); \ + } else { /* TEMP1 */ \ + data->temp_##reg = TEMP_TO_REG(val); \ + w83781d_write_value(data, W83781D_REG_TEMP_##REG(nr), \ + data->temp_##reg); \ + } \ + \ + mutex_unlock(&data->update_lock); \ + return count; \ +} +store_temp_reg(OVER, max); +store_temp_reg(HYST, max_hyst); + +#define sysfs_temp_offsets(offset) \ +static SENSOR_DEVICE_ATTR(temp##offset##_input, S_IRUGO, \ + show_temp, NULL, offset); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max, S_IRUGO | S_IWUSR, \ + show_temp_max, store_temp_max, offset); \ +static SENSOR_DEVICE_ATTR(temp##offset##_max_hyst, S_IRUGO | S_IWUSR, \ + show_temp_max_hyst, store_temp_max_hyst, offset); + +sysfs_temp_offsets(1); +sysfs_temp_offsets(2); +sysfs_temp_offsets(3); + +static ssize_t +cpu0_vid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83781d_data *data = w83781d_update_device(dev); + return sprintf(buf, "%ld\n", (long) vid_from_reg(data->vid, data->vrm)); +} + +static DEVICE_ATTR_RO(cpu0_vid); + +static ssize_t +vrm_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83781d_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%ld\n", (long) data->vrm); +} + +static ssize_t +vrm_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct w83781d_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + data->vrm = clamp_val(val, 0, 255); + + return count; +} + +static DEVICE_ATTR_RW(vrm); + +static ssize_t +alarms_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83781d_data *data = w83781d_update_device(dev); + return sprintf(buf, "%u\n", data->alarms); +} + +static DEVICE_ATTR_RO(alarms); + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w83781d_data *data = w83781d_update_device(dev); + int bitnr = to_sensor_dev_attr(attr)->index; + return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1); +} + +/* The W83781D has a single alarm bit for temp2 and temp3 */ +static ssize_t show_temp3_alarm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct w83781d_data *data = w83781d_update_device(dev); + int bitnr = (data->type == w83781d) ? 5 : 13; + return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1); +} + +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 16); +static SENSOR_DEVICE_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 17); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_temp3_alarm, NULL, 0); + +static ssize_t beep_mask_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct w83781d_data *data = w83781d_update_device(dev); + return sprintf(buf, "%ld\n", + (long)BEEP_MASK_FROM_REG(data->beep_mask, data->type)); +} + +static ssize_t +beep_mask_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w83781d_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->beep_mask &= 0x8000; /* preserve beep enable */ + data->beep_mask |= BEEP_MASK_TO_REG(val, data->type); + w83781d_write_value(data, W83781D_REG_BEEP_INTS1, + data->beep_mask & 0xff); + w83781d_write_value(data, W83781D_REG_BEEP_INTS2, + (data->beep_mask >> 8) & 0xff); + if (data->type != w83781d && data->type != as99127f) { + w83781d_write_value(data, W83781D_REG_BEEP_INTS3, + ((data->beep_mask) >> 16) & 0xff); + } + mutex_unlock(&data->update_lock); + + return count; +} + +static DEVICE_ATTR_RW(beep_mask); + +static ssize_t show_beep(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w83781d_data *data = w83781d_update_device(dev); + int bitnr = to_sensor_dev_attr(attr)->index; + return sprintf(buf, "%u\n", (data->beep_mask >> bitnr) & 1); +} + +static ssize_t +store_beep(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w83781d_data *data = dev_get_drvdata(dev); + int bitnr = to_sensor_dev_attr(attr)->index; + u8 reg; + unsigned long bit; + int err; + + err = kstrtoul(buf, 10, &bit); + if (err) + return err; + + if (bit & ~1) + return -EINVAL; + + mutex_lock(&data->update_lock); + if (bit) + data->beep_mask |= (1 << bitnr); + else + data->beep_mask &= ~(1 << bitnr); + + if (bitnr < 8) { + reg = w83781d_read_value(data, W83781D_REG_BEEP_INTS1); + if (bit) + reg |= (1 << bitnr); + else + reg &= ~(1 << bitnr); + w83781d_write_value(data, W83781D_REG_BEEP_INTS1, reg); + } else if (bitnr < 16) { + reg = w83781d_read_value(data, W83781D_REG_BEEP_INTS2); + if (bit) + reg |= (1 << (bitnr - 8)); + else + reg &= ~(1 << (bitnr - 8)); + w83781d_write_value(data, W83781D_REG_BEEP_INTS2, reg); + } else { + reg = w83781d_read_value(data, W83781D_REG_BEEP_INTS3); + if (bit) + reg |= (1 << (bitnr - 16)); + else + reg &= ~(1 << (bitnr - 16)); + w83781d_write_value(data, W83781D_REG_BEEP_INTS3, reg); + } + mutex_unlock(&data->update_lock); + + return count; +} + +/* The W83781D has a single beep bit for temp2 and temp3 */ +static ssize_t show_temp3_beep(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct w83781d_data *data = w83781d_update_device(dev); + int bitnr = (data->type == w83781d) ? 5 : 13; + return sprintf(buf, "%u\n", (data->beep_mask >> bitnr) & 1); +} + +static SENSOR_DEVICE_ATTR(in0_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 0); +static SENSOR_DEVICE_ATTR(in1_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 1); +static SENSOR_DEVICE_ATTR(in2_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 2); +static SENSOR_DEVICE_ATTR(in3_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 3); +static SENSOR_DEVICE_ATTR(in4_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 8); +static SENSOR_DEVICE_ATTR(in5_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 9); +static SENSOR_DEVICE_ATTR(in6_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 10); +static SENSOR_DEVICE_ATTR(in7_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 16); +static SENSOR_DEVICE_ATTR(in8_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 17); +static SENSOR_DEVICE_ATTR(fan1_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 6); +static SENSOR_DEVICE_ATTR(fan2_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 7); +static SENSOR_DEVICE_ATTR(fan3_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 11); +static SENSOR_DEVICE_ATTR(temp1_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 4); +static SENSOR_DEVICE_ATTR(temp2_beep, S_IRUGO | S_IWUSR, + show_beep, store_beep, 5); +static SENSOR_DEVICE_ATTR(temp3_beep, S_IRUGO, + show_temp3_beep, store_beep, 13); +static SENSOR_DEVICE_ATTR(beep_enable, S_IRUGO | S_IWUSR, + show_beep, store_beep, 15); + +static ssize_t +show_fan_div(struct device *dev, struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct w83781d_data *data = w83781d_update_device(dev); + return sprintf(buf, "%ld\n", + (long) DIV_FROM_REG(data->fan_div[attr->index])); +} + +/* + * Note: we save and restore the fan minimum here, because its value is + * determined in part by the fan divisor. This follows the principle of + * least surprise; the user doesn't expect the fan minimum to change just + * because the divisor changed. + */ +static ssize_t +store_fan_div(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct w83781d_data *data = dev_get_drvdata(dev); + unsigned long min; + int nr = attr->index; + u8 reg; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + /* Save fan_min */ + min = FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr])); + + data->fan_div[nr] = DIV_TO_REG(val, data->type); + + reg = (w83781d_read_value(data, nr == 2 ? + W83781D_REG_PIN : W83781D_REG_VID_FANDIV) + & (nr == 0 ? 0xcf : 0x3f)) + | ((data->fan_div[nr] & 0x03) << (nr == 0 ? 4 : 6)); + w83781d_write_value(data, nr == 2 ? + W83781D_REG_PIN : W83781D_REG_VID_FANDIV, reg); + + /* w83781d and as99127f don't have extended divisor bits */ + if (data->type != w83781d && data->type != as99127f) { + reg = (w83781d_read_value(data, W83781D_REG_VBAT) + & ~(1 << (5 + nr))) + | ((data->fan_div[nr] & 0x04) << (3 + nr)); + w83781d_write_value(data, W83781D_REG_VBAT, reg); + } + + /* Restore fan_min */ + data->fan_min[nr] = FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr])); + w83781d_write_value(data, W83781D_REG_FAN_MIN(nr), data->fan_min[nr]); + + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR, + show_fan_div, store_fan_div, 0); +static SENSOR_DEVICE_ATTR(fan2_div, S_IRUGO | S_IWUSR, + show_fan_div, store_fan_div, 1); +static SENSOR_DEVICE_ATTR(fan3_div, S_IRUGO | S_IWUSR, + show_fan_div, store_fan_div, 2); + +static ssize_t +show_pwm(struct device *dev, struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct w83781d_data *data = w83781d_update_device(dev); + return sprintf(buf, "%d\n", (int)data->pwm[attr->index]); +} + +static ssize_t +pwm2_enable_show(struct device *dev, struct device_attribute *da, char *buf) +{ + struct w83781d_data *data = w83781d_update_device(dev); + return sprintf(buf, "%d\n", (int)data->pwm2_enable); +} + +static ssize_t +store_pwm(struct device *dev, struct device_attribute *da, const char *buf, + size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct w83781d_data *data = dev_get_drvdata(dev); + int nr = attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->pwm[nr] = clamp_val(val, 0, 255); + w83781d_write_value(data, W83781D_REG_PWM[nr], data->pwm[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +pwm2_enable_store(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct w83781d_data *data = dev_get_drvdata(dev); + unsigned long val; + u32 reg; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + switch (val) { + case 0: + case 1: + reg = w83781d_read_value(data, W83781D_REG_PWMCLK12); + w83781d_write_value(data, W83781D_REG_PWMCLK12, + (reg & 0xf7) | (val << 3)); + + reg = w83781d_read_value(data, W83781D_REG_BEEP_CONFIG); + w83781d_write_value(data, W83781D_REG_BEEP_CONFIG, + (reg & 0xef) | (!val << 4)); + + data->pwm2_enable = val; + break; + + default: + mutex_unlock(&data->update_lock); + return -EINVAL; + } + + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, store_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, show_pwm, store_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm3, S_IRUGO | S_IWUSR, show_pwm, store_pwm, 2); +static SENSOR_DEVICE_ATTR(pwm4, S_IRUGO | S_IWUSR, show_pwm, store_pwm, 3); +/* only PWM2 can be enabled/disabled */ +static DEVICE_ATTR_RW(pwm2_enable); + +static ssize_t +show_sensor(struct device *dev, struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct w83781d_data *data = w83781d_update_device(dev); + return sprintf(buf, "%d\n", (int)data->sens[attr->index]); +} + +static ssize_t +store_sensor(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct w83781d_data *data = dev_get_drvdata(dev); + int nr = attr->index; + unsigned long val; + u32 tmp; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + switch (val) { + case 1: /* PII/Celeron diode */ + tmp = w83781d_read_value(data, W83781D_REG_SCFG1); + w83781d_write_value(data, W83781D_REG_SCFG1, + tmp | BIT_SCFG1[nr]); + tmp = w83781d_read_value(data, W83781D_REG_SCFG2); + w83781d_write_value(data, W83781D_REG_SCFG2, + tmp | BIT_SCFG2[nr]); + data->sens[nr] = val; + break; + case 2: /* 3904 */ + tmp = w83781d_read_value(data, W83781D_REG_SCFG1); + w83781d_write_value(data, W83781D_REG_SCFG1, + tmp | BIT_SCFG1[nr]); + tmp = w83781d_read_value(data, W83781D_REG_SCFG2); + w83781d_write_value(data, W83781D_REG_SCFG2, + tmp & ~BIT_SCFG2[nr]); + data->sens[nr] = val; + break; + case W83781D_DEFAULT_BETA: + dev_warn(dev, + "Sensor type %d is deprecated, please use 4 instead\n", + W83781D_DEFAULT_BETA); + /* fall through */ + case 4: /* thermistor */ + tmp = w83781d_read_value(data, W83781D_REG_SCFG1); + w83781d_write_value(data, W83781D_REG_SCFG1, + tmp & ~BIT_SCFG1[nr]); + data->sens[nr] = val; + break; + default: + dev_err(dev, "Invalid sensor type %ld; must be 1, 2, or 4\n", + (long) val); + break; + } + + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_type, S_IRUGO | S_IWUSR, + show_sensor, store_sensor, 0); +static SENSOR_DEVICE_ATTR(temp2_type, S_IRUGO | S_IWUSR, + show_sensor, store_sensor, 1); +static SENSOR_DEVICE_ATTR(temp3_type, S_IRUGO | S_IWUSR, + show_sensor, store_sensor, 2); + +/* + * Assumes that adapter is of I2C, not ISA variety. + * OTHERWISE DON'T CALL THIS + */ +static int +w83781d_detect_subclients(struct i2c_client *new_client) +{ + int i, val1 = 0, id; + int err; + int address = new_client->addr; + unsigned short sc_addr[2]; + struct i2c_adapter *adapter = new_client->adapter; + struct w83781d_data *data = i2c_get_clientdata(new_client); + enum chips kind = data->type; + int num_sc = 1; + + id = i2c_adapter_id(adapter); + + if (force_subclients[0] == id && force_subclients[1] == address) { + for (i = 2; i <= 3; i++) { + if (force_subclients[i] < 0x48 || + force_subclients[i] > 0x4f) { + dev_err(&new_client->dev, + "Invalid subclient address %d; must be 0x48-0x4f\n", + force_subclients[i]); + err = -EINVAL; + goto ERROR_SC_1; + } + } + w83781d_write_value(data, W83781D_REG_I2C_SUBADDR, + (force_subclients[2] & 0x07) | + ((force_subclients[3] & 0x07) << 4)); + sc_addr[0] = force_subclients[2]; + } else { + val1 = w83781d_read_value(data, W83781D_REG_I2C_SUBADDR); + sc_addr[0] = 0x48 + (val1 & 0x07); + } + + if (kind != w83783s) { + num_sc = 2; + if (force_subclients[0] == id && + force_subclients[1] == address) { + sc_addr[1] = force_subclients[3]; + } else { + sc_addr[1] = 0x48 + ((val1 >> 4) & 0x07); + } + if (sc_addr[0] == sc_addr[1]) { + dev_err(&new_client->dev, + "Duplicate addresses 0x%x for subclients.\n", + sc_addr[0]); + err = -EBUSY; + goto ERROR_SC_2; + } + } + + for (i = 0; i < num_sc; i++) { + data->lm75[i] = i2c_new_dummy(adapter, sc_addr[i]); + if (!data->lm75[i]) { + dev_err(&new_client->dev, + "Subclient %d registration at address 0x%x failed.\n", + i, sc_addr[i]); + err = -ENOMEM; + if (i == 1) + goto ERROR_SC_3; + goto ERROR_SC_2; + } + } + + return 0; + +/* Undo inits in case of errors */ +ERROR_SC_3: + i2c_unregister_device(data->lm75[0]); +ERROR_SC_2: +ERROR_SC_1: + return err; +} + +#define IN_UNIT_ATTRS(X) \ + &sensor_dev_attr_in##X##_input.dev_attr.attr, \ + &sensor_dev_attr_in##X##_min.dev_attr.attr, \ + &sensor_dev_attr_in##X##_max.dev_attr.attr, \ + &sensor_dev_attr_in##X##_alarm.dev_attr.attr, \ + &sensor_dev_attr_in##X##_beep.dev_attr.attr + +#define FAN_UNIT_ATTRS(X) \ + &sensor_dev_attr_fan##X##_input.dev_attr.attr, \ + &sensor_dev_attr_fan##X##_min.dev_attr.attr, \ + &sensor_dev_attr_fan##X##_div.dev_attr.attr, \ + &sensor_dev_attr_fan##X##_alarm.dev_attr.attr, \ + &sensor_dev_attr_fan##X##_beep.dev_attr.attr + +#define TEMP_UNIT_ATTRS(X) \ + &sensor_dev_attr_temp##X##_input.dev_attr.attr, \ + &sensor_dev_attr_temp##X##_max.dev_attr.attr, \ + &sensor_dev_attr_temp##X##_max_hyst.dev_attr.attr, \ + &sensor_dev_attr_temp##X##_alarm.dev_attr.attr, \ + &sensor_dev_attr_temp##X##_beep.dev_attr.attr + +static struct attribute *w83781d_attributes[] = { + IN_UNIT_ATTRS(0), + IN_UNIT_ATTRS(2), + IN_UNIT_ATTRS(3), + IN_UNIT_ATTRS(4), + IN_UNIT_ATTRS(5), + IN_UNIT_ATTRS(6), + FAN_UNIT_ATTRS(1), + FAN_UNIT_ATTRS(2), + FAN_UNIT_ATTRS(3), + TEMP_UNIT_ATTRS(1), + TEMP_UNIT_ATTRS(2), + &dev_attr_cpu0_vid.attr, + &dev_attr_vrm.attr, + &dev_attr_alarms.attr, + &dev_attr_beep_mask.attr, + &sensor_dev_attr_beep_enable.dev_attr.attr, + NULL +}; +static const struct attribute_group w83781d_group = { + .attrs = w83781d_attributes, +}; + +static struct attribute *w83781d_attributes_in1[] = { + IN_UNIT_ATTRS(1), + NULL +}; +static const struct attribute_group w83781d_group_in1 = { + .attrs = w83781d_attributes_in1, +}; + +static struct attribute *w83781d_attributes_in78[] = { + IN_UNIT_ATTRS(7), + IN_UNIT_ATTRS(8), + NULL +}; +static const struct attribute_group w83781d_group_in78 = { + .attrs = w83781d_attributes_in78, +}; + +static struct attribute *w83781d_attributes_temp3[] = { + TEMP_UNIT_ATTRS(3), + NULL +}; +static const struct attribute_group w83781d_group_temp3 = { + .attrs = w83781d_attributes_temp3, +}; + +static struct attribute *w83781d_attributes_pwm12[] = { + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &dev_attr_pwm2_enable.attr, + NULL +}; +static const struct attribute_group w83781d_group_pwm12 = { + .attrs = w83781d_attributes_pwm12, +}; + +static struct attribute *w83781d_attributes_pwm34[] = { + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm4.dev_attr.attr, + NULL +}; +static const struct attribute_group w83781d_group_pwm34 = { + .attrs = w83781d_attributes_pwm34, +}; + +static struct attribute *w83781d_attributes_other[] = { + &sensor_dev_attr_temp1_type.dev_attr.attr, + &sensor_dev_attr_temp2_type.dev_attr.attr, + &sensor_dev_attr_temp3_type.dev_attr.attr, + NULL +}; +static const struct attribute_group w83781d_group_other = { + .attrs = w83781d_attributes_other, +}; + +/* No clean up is done on error, it's up to the caller */ +static int +w83781d_create_files(struct device *dev, int kind, int is_isa) +{ + int err; + + err = sysfs_create_group(&dev->kobj, &w83781d_group); + if (err) + return err; + + if (kind != w83783s) { + err = sysfs_create_group(&dev->kobj, &w83781d_group_in1); + if (err) + return err; + } + if (kind != as99127f && kind != w83781d && kind != w83783s) { + err = sysfs_create_group(&dev->kobj, &w83781d_group_in78); + if (err) + return err; + } + if (kind != w83783s) { + err = sysfs_create_group(&dev->kobj, &w83781d_group_temp3); + if (err) + return err; + + if (kind != w83781d) { + err = sysfs_chmod_file(&dev->kobj, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + S_IRUGO | S_IWUSR); + if (err) + return err; + } + } + + if (kind != w83781d && kind != as99127f) { + err = sysfs_create_group(&dev->kobj, &w83781d_group_pwm12); + if (err) + return err; + } + if (kind == w83782d && !is_isa) { + err = sysfs_create_group(&dev->kobj, &w83781d_group_pwm34); + if (err) + return err; + } + + if (kind != as99127f && kind != w83781d) { + err = device_create_file(dev, + &sensor_dev_attr_temp1_type.dev_attr); + if (err) + return err; + err = device_create_file(dev, + &sensor_dev_attr_temp2_type.dev_attr); + if (err) + return err; + if (kind != w83783s) { + err = device_create_file(dev, + &sensor_dev_attr_temp3_type.dev_attr); + if (err) + return err; + } + } + + return 0; +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int +w83781d_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + int val1, val2; + struct w83781d_data *isa = w83781d_data_if_isa(); + struct i2c_adapter *adapter = client->adapter; + int address = client->addr; + const char *client_name; + enum vendor { winbond, asus } vendid; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* + * We block updates of the ISA device to minimize the risk of + * concurrent access to the same W83781D chip through different + * interfaces. + */ + if (isa) + mutex_lock(&isa->update_lock); + + if (i2c_smbus_read_byte_data(client, W83781D_REG_CONFIG) & 0x80) { + dev_dbg(&adapter->dev, + "Detection of w83781d chip failed at step 3\n"); + goto err_nodev; + } + + val1 = i2c_smbus_read_byte_data(client, W83781D_REG_BANK); + val2 = i2c_smbus_read_byte_data(client, W83781D_REG_CHIPMAN); + /* Check for Winbond or Asus ID if in bank 0 */ + if (!(val1 & 0x07) && + ((!(val1 & 0x80) && val2 != 0xa3 && val2 != 0xc3) || + ((val1 & 0x80) && val2 != 0x5c && val2 != 0x12))) { + dev_dbg(&adapter->dev, + "Detection of w83781d chip failed at step 4\n"); + goto err_nodev; + } + /* + * If Winbond SMBus, check address at 0x48. + * Asus doesn't support, except for as99127f rev.2 + */ + if ((!(val1 & 0x80) && val2 == 0xa3) || + ((val1 & 0x80) && val2 == 0x5c)) { + if (i2c_smbus_read_byte_data(client, W83781D_REG_I2C_ADDR) + != address) { + dev_dbg(&adapter->dev, + "Detection of w83781d chip failed at step 5\n"); + goto err_nodev; + } + } + + /* Put it now into bank 0 and Vendor ID High Byte */ + i2c_smbus_write_byte_data(client, W83781D_REG_BANK, + (i2c_smbus_read_byte_data(client, W83781D_REG_BANK) + & 0x78) | 0x80); + + /* Get the vendor ID */ + val2 = i2c_smbus_read_byte_data(client, W83781D_REG_CHIPMAN); + if (val2 == 0x5c) + vendid = winbond; + else if (val2 == 0x12) + vendid = asus; + else { + dev_dbg(&adapter->dev, + "w83781d chip vendor is neither Winbond nor Asus\n"); + goto err_nodev; + } + + /* Determine the chip type. */ + val1 = i2c_smbus_read_byte_data(client, W83781D_REG_WCHIPID); + if ((val1 == 0x10 || val1 == 0x11) && vendid == winbond) + client_name = "w83781d"; + else if (val1 == 0x30 && vendid == winbond) + client_name = "w83782d"; + else if (val1 == 0x40 && vendid == winbond && address == 0x2d) + client_name = "w83783s"; + else if (val1 == 0x31) + client_name = "as99127f"; + else + goto err_nodev; + + if (val1 <= 0x30 && w83781d_alias_detect(client, val1)) { + dev_dbg(&adapter->dev, + "Device at 0x%02x appears to be the same as ISA device\n", + address); + goto err_nodev; + } + + if (isa) + mutex_unlock(&isa->update_lock); + + strlcpy(info->type, client_name, I2C_NAME_SIZE); + + return 0; + + err_nodev: + if (isa) + mutex_unlock(&isa->update_lock); + return -ENODEV; +} + +static void w83781d_remove_files(struct device *dev) +{ + sysfs_remove_group(&dev->kobj, &w83781d_group); + sysfs_remove_group(&dev->kobj, &w83781d_group_in1); + sysfs_remove_group(&dev->kobj, &w83781d_group_in78); + sysfs_remove_group(&dev->kobj, &w83781d_group_temp3); + sysfs_remove_group(&dev->kobj, &w83781d_group_pwm12); + sysfs_remove_group(&dev->kobj, &w83781d_group_pwm34); + sysfs_remove_group(&dev->kobj, &w83781d_group_other); +} + +static int +w83781d_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct w83781d_data *data; + int err; + + data = devm_kzalloc(dev, sizeof(struct w83781d_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->lock); + mutex_init(&data->update_lock); + + data->type = id->driver_data; + data->client = client; + + /* attach secondary i2c lm75-like clients */ + err = w83781d_detect_subclients(client); + if (err) + return err; + + /* Initialize the chip */ + w83781d_init_device(dev); + + /* Register sysfs hooks */ + err = w83781d_create_files(dev, data->type, 0); + if (err) + goto exit_remove_files; + + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_files; + } + + return 0; + + exit_remove_files: + w83781d_remove_files(dev); + i2c_unregister_device(data->lm75[0]); + i2c_unregister_device(data->lm75[1]); + return err; +} + +static int +w83781d_remove(struct i2c_client *client) +{ + struct w83781d_data *data = i2c_get_clientdata(client); + struct device *dev = &client->dev; + + hwmon_device_unregister(data->hwmon_dev); + w83781d_remove_files(dev); + + i2c_unregister_device(data->lm75[0]); + i2c_unregister_device(data->lm75[1]); + + return 0; +} + +static int +w83781d_read_value_i2c(struct w83781d_data *data, u16 reg) +{ + struct i2c_client *client = data->client; + int res, bank; + struct i2c_client *cl; + + bank = (reg >> 8) & 0x0f; + if (bank > 2) + /* switch banks */ + i2c_smbus_write_byte_data(client, W83781D_REG_BANK, + bank); + if (bank == 0 || bank > 2) { + res = i2c_smbus_read_byte_data(client, reg & 0xff); + } else { + /* switch to subclient */ + cl = data->lm75[bank - 1]; + /* convert from ISA to LM75 I2C addresses */ + switch (reg & 0xff) { + case 0x50: /* TEMP */ + res = i2c_smbus_read_word_swapped(cl, 0); + break; + case 0x52: /* CONFIG */ + res = i2c_smbus_read_byte_data(cl, 1); + break; + case 0x53: /* HYST */ + res = i2c_smbus_read_word_swapped(cl, 2); + break; + case 0x55: /* OVER */ + default: + res = i2c_smbus_read_word_swapped(cl, 3); + break; + } + } + if (bank > 2) + i2c_smbus_write_byte_data(client, W83781D_REG_BANK, 0); + + return res; +} + +static int +w83781d_write_value_i2c(struct w83781d_data *data, u16 reg, u16 value) +{ + struct i2c_client *client = data->client; + int bank; + struct i2c_client *cl; + + bank = (reg >> 8) & 0x0f; + if (bank > 2) + /* switch banks */ + i2c_smbus_write_byte_data(client, W83781D_REG_BANK, + bank); + if (bank == 0 || bank > 2) { + i2c_smbus_write_byte_data(client, reg & 0xff, + value & 0xff); + } else { + /* switch to subclient */ + cl = data->lm75[bank - 1]; + /* convert from ISA to LM75 I2C addresses */ + switch (reg & 0xff) { + case 0x52: /* CONFIG */ + i2c_smbus_write_byte_data(cl, 1, value & 0xff); + break; + case 0x53: /* HYST */ + i2c_smbus_write_word_swapped(cl, 2, value); + break; + case 0x55: /* OVER */ + i2c_smbus_write_word_swapped(cl, 3, value); + break; + } + } + if (bank > 2) + i2c_smbus_write_byte_data(client, W83781D_REG_BANK, 0); + + return 0; +} + +static void +w83781d_init_device(struct device *dev) +{ + struct w83781d_data *data = dev_get_drvdata(dev); + int i, p; + int type = data->type; + u8 tmp; + + if (reset && type != as99127f) { /* + * this resets registers we don't have + * documentation for on the as99127f + */ + /* + * Resetting the chip has been the default for a long time, + * but it causes the BIOS initializations (fan clock dividers, + * thermal sensor types...) to be lost, so it is now optional. + * It might even go away if nobody reports it as being useful, + * as I see very little reason why this would be needed at + * all. + */ + dev_info(dev, + "If reset=1 solved a problem you were having, please report!\n"); + + /* save these registers */ + i = w83781d_read_value(data, W83781D_REG_BEEP_CONFIG); + p = w83781d_read_value(data, W83781D_REG_PWMCLK12); + /* + * Reset all except Watchdog values and last conversion values + * This sets fan-divs to 2, among others + */ + w83781d_write_value(data, W83781D_REG_CONFIG, 0x80); + /* + * Restore the registers and disable power-on abnormal beep. + * This saves FAN 1/2/3 input/output values set by BIOS. + */ + w83781d_write_value(data, W83781D_REG_BEEP_CONFIG, i | 0x80); + w83781d_write_value(data, W83781D_REG_PWMCLK12, p); + /* + * Disable master beep-enable (reset turns it on). + * Individual beep_mask should be reset to off but for some + * reason disabling this bit helps some people not get beeped + */ + w83781d_write_value(data, W83781D_REG_BEEP_INTS2, 0); + } + + /* + * Disable power-on abnormal beep, as advised by the datasheet. + * Already done if reset=1. + */ + if (init && !reset && type != as99127f) { + i = w83781d_read_value(data, W83781D_REG_BEEP_CONFIG); + w83781d_write_value(data, W83781D_REG_BEEP_CONFIG, i | 0x80); + } + + data->vrm = vid_which_vrm(); + + if ((type != w83781d) && (type != as99127f)) { + tmp = w83781d_read_value(data, W83781D_REG_SCFG1); + for (i = 1; i <= 3; i++) { + if (!(tmp & BIT_SCFG1[i - 1])) { + data->sens[i - 1] = 4; + } else { + if (w83781d_read_value + (data, + W83781D_REG_SCFG2) & BIT_SCFG2[i - 1]) + data->sens[i - 1] = 1; + else + data->sens[i - 1] = 2; + } + if (type == w83783s && i == 2) + break; + } + } + + if (init && type != as99127f) { + /* Enable temp2 */ + tmp = w83781d_read_value(data, W83781D_REG_TEMP2_CONFIG); + if (tmp & 0x01) { + dev_warn(dev, + "Enabling temp2, readings might not make sense\n"); + w83781d_write_value(data, W83781D_REG_TEMP2_CONFIG, + tmp & 0xfe); + } + + /* Enable temp3 */ + if (type != w83783s) { + tmp = w83781d_read_value(data, + W83781D_REG_TEMP3_CONFIG); + if (tmp & 0x01) { + dev_warn(dev, + "Enabling temp3, readings might not make sense\n"); + w83781d_write_value(data, + W83781D_REG_TEMP3_CONFIG, tmp & 0xfe); + } + } + } + + /* Start monitoring */ + w83781d_write_value(data, W83781D_REG_CONFIG, + (w83781d_read_value(data, + W83781D_REG_CONFIG) & 0xf7) + | 0x01); + + /* A few vars need to be filled upon startup */ + for (i = 0; i < 3; i++) { + data->fan_min[i] = w83781d_read_value(data, + W83781D_REG_FAN_MIN(i)); + } + + mutex_init(&data->update_lock); +} + +static struct w83781d_data *w83781d_update_device(struct device *dev) +{ + struct w83781d_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + dev_dbg(dev, "Starting device update\n"); + + for (i = 0; i <= 8; i++) { + if (data->type == w83783s && i == 1) + continue; /* 783S has no in1 */ + data->in[i] = + w83781d_read_value(data, W83781D_REG_IN(i)); + data->in_min[i] = + w83781d_read_value(data, W83781D_REG_IN_MIN(i)); + data->in_max[i] = + w83781d_read_value(data, W83781D_REG_IN_MAX(i)); + if ((data->type != w83782d) && (i == 6)) + break; + } + for (i = 0; i < 3; i++) { + data->fan[i] = + w83781d_read_value(data, W83781D_REG_FAN(i)); + data->fan_min[i] = + w83781d_read_value(data, W83781D_REG_FAN_MIN(i)); + } + if (data->type != w83781d && data->type != as99127f) { + for (i = 0; i < 4; i++) { + data->pwm[i] = + w83781d_read_value(data, + W83781D_REG_PWM[i]); + /* Only W83782D on SMBus has PWM3 and PWM4 */ + if ((data->type != w83782d || !client) + && i == 1) + break; + } + /* Only PWM2 can be disabled */ + data->pwm2_enable = (w83781d_read_value(data, + W83781D_REG_PWMCLK12) & 0x08) >> 3; + } + + data->temp = w83781d_read_value(data, W83781D_REG_TEMP(1)); + data->temp_max = + w83781d_read_value(data, W83781D_REG_TEMP_OVER(1)); + data->temp_max_hyst = + w83781d_read_value(data, W83781D_REG_TEMP_HYST(1)); + data->temp_add[0] = + w83781d_read_value(data, W83781D_REG_TEMP(2)); + data->temp_max_add[0] = + w83781d_read_value(data, W83781D_REG_TEMP_OVER(2)); + data->temp_max_hyst_add[0] = + w83781d_read_value(data, W83781D_REG_TEMP_HYST(2)); + if (data->type != w83783s) { + data->temp_add[1] = + w83781d_read_value(data, W83781D_REG_TEMP(3)); + data->temp_max_add[1] = + w83781d_read_value(data, + W83781D_REG_TEMP_OVER(3)); + data->temp_max_hyst_add[1] = + w83781d_read_value(data, + W83781D_REG_TEMP_HYST(3)); + } + i = w83781d_read_value(data, W83781D_REG_VID_FANDIV); + data->vid = i & 0x0f; + data->vid |= (w83781d_read_value(data, + W83781D_REG_CHIPID) & 0x01) << 4; + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = (i >> 6) & 0x03; + data->fan_div[2] = (w83781d_read_value(data, + W83781D_REG_PIN) >> 6) & 0x03; + if ((data->type != w83781d) && (data->type != as99127f)) { + i = w83781d_read_value(data, W83781D_REG_VBAT); + data->fan_div[0] |= (i >> 3) & 0x04; + data->fan_div[1] |= (i >> 4) & 0x04; + data->fan_div[2] |= (i >> 5) & 0x04; + } + if (data->type == w83782d) { + data->alarms = w83781d_read_value(data, + W83782D_REG_ALARM1) + | (w83781d_read_value(data, + W83782D_REG_ALARM2) << 8) + | (w83781d_read_value(data, + W83782D_REG_ALARM3) << 16); + } else if (data->type == w83783s) { + data->alarms = w83781d_read_value(data, + W83782D_REG_ALARM1) + | (w83781d_read_value(data, + W83782D_REG_ALARM2) << 8); + } else { + /* + * No real-time status registers, fall back to + * interrupt status registers + */ + data->alarms = w83781d_read_value(data, + W83781D_REG_ALARM1) + | (w83781d_read_value(data, + W83781D_REG_ALARM2) << 8); + } + i = w83781d_read_value(data, W83781D_REG_BEEP_INTS2); + data->beep_mask = (i << 8) + + w83781d_read_value(data, W83781D_REG_BEEP_INTS1); + if ((data->type != w83781d) && (data->type != as99127f)) { + data->beep_mask |= + w83781d_read_value(data, + W83781D_REG_BEEP_INTS3) << 16; + } + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static const struct i2c_device_id w83781d_ids[] = { + { "w83781d", w83781d, }, + { "w83782d", w83782d, }, + { "w83783s", w83783s, }, + { "as99127f", as99127f }, + { /* LIST END */ } +}; +MODULE_DEVICE_TABLE(i2c, w83781d_ids); + +static struct i2c_driver w83781d_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "w83781d", + }, + .probe = w83781d_probe, + .remove = w83781d_remove, + .id_table = w83781d_ids, + .detect = w83781d_detect, + .address_list = normal_i2c, +}; + +/* + * ISA related code + */ +#ifdef CONFIG_ISA + +/* ISA device, if found */ +static struct platform_device *pdev; + +static unsigned short isa_address = 0x290; + +/* + * I2C devices get this name attribute automatically, but for ISA devices + * we must create it by ourselves. + */ +static ssize_t +name_show(struct device *dev, struct device_attribute *devattr, char *buf) +{ + struct w83781d_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", data->name); +} +static DEVICE_ATTR_RO(name); + +static struct w83781d_data *w83781d_data_if_isa(void) +{ + return pdev ? platform_get_drvdata(pdev) : NULL; +} + +/* Returns 1 if the I2C chip appears to be an alias of the ISA chip */ +static int w83781d_alias_detect(struct i2c_client *client, u8 chipid) +{ + struct w83781d_data *isa; + int i; + + if (!pdev) /* No ISA chip */ + return 0; + + isa = platform_get_drvdata(pdev); + + if (w83781d_read_value(isa, W83781D_REG_I2C_ADDR) != client->addr) + return 0; /* Address doesn't match */ + if (w83781d_read_value(isa, W83781D_REG_WCHIPID) != chipid) + return 0; /* Chip type doesn't match */ + + /* + * We compare all the limit registers, the config register and the + * interrupt mask registers + */ + for (i = 0x2b; i <= 0x3d; i++) { + if (w83781d_read_value(isa, i) != + i2c_smbus_read_byte_data(client, i)) + return 0; + } + if (w83781d_read_value(isa, W83781D_REG_CONFIG) != + i2c_smbus_read_byte_data(client, W83781D_REG_CONFIG)) + return 0; + for (i = 0x43; i <= 0x46; i++) { + if (w83781d_read_value(isa, i) != + i2c_smbus_read_byte_data(client, i)) + return 0; + } + + return 1; +} + +static int +w83781d_read_value_isa(struct w83781d_data *data, u16 reg) +{ + int word_sized, res; + + word_sized = (((reg & 0xff00) == 0x100) + || ((reg & 0xff00) == 0x200)) + && (((reg & 0x00ff) == 0x50) + || ((reg & 0x00ff) == 0x53) + || ((reg & 0x00ff) == 0x55)); + if (reg & 0xff00) { + outb_p(W83781D_REG_BANK, + data->isa_addr + W83781D_ADDR_REG_OFFSET); + outb_p(reg >> 8, + data->isa_addr + W83781D_DATA_REG_OFFSET); + } + outb_p(reg & 0xff, data->isa_addr + W83781D_ADDR_REG_OFFSET); + res = inb_p(data->isa_addr + W83781D_DATA_REG_OFFSET); + if (word_sized) { + outb_p((reg & 0xff) + 1, + data->isa_addr + W83781D_ADDR_REG_OFFSET); + res = + (res << 8) + inb_p(data->isa_addr + + W83781D_DATA_REG_OFFSET); + } + if (reg & 0xff00) { + outb_p(W83781D_REG_BANK, + data->isa_addr + W83781D_ADDR_REG_OFFSET); + outb_p(0, data->isa_addr + W83781D_DATA_REG_OFFSET); + } + return res; +} + +static void +w83781d_write_value_isa(struct w83781d_data *data, u16 reg, u16 value) +{ + int word_sized; + + word_sized = (((reg & 0xff00) == 0x100) + || ((reg & 0xff00) == 0x200)) + && (((reg & 0x00ff) == 0x53) + || ((reg & 0x00ff) == 0x55)); + if (reg & 0xff00) { + outb_p(W83781D_REG_BANK, + data->isa_addr + W83781D_ADDR_REG_OFFSET); + outb_p(reg >> 8, + data->isa_addr + W83781D_DATA_REG_OFFSET); + } + outb_p(reg & 0xff, data->isa_addr + W83781D_ADDR_REG_OFFSET); + if (word_sized) { + outb_p(value >> 8, + data->isa_addr + W83781D_DATA_REG_OFFSET); + outb_p((reg & 0xff) + 1, + data->isa_addr + W83781D_ADDR_REG_OFFSET); + } + outb_p(value & 0xff, data->isa_addr + W83781D_DATA_REG_OFFSET); + if (reg & 0xff00) { + outb_p(W83781D_REG_BANK, + data->isa_addr + W83781D_ADDR_REG_OFFSET); + outb_p(0, data->isa_addr + W83781D_DATA_REG_OFFSET); + } +} + +/* + * The SMBus locks itself, usually, but nothing may access the Winbond between + * bank switches. ISA access must always be locked explicitly! + * We ignore the W83781D BUSY flag at this moment - it could lead to deadlocks, + * would slow down the W83781D access and should not be necessary. + * There are some ugly typecasts here, but the good news is - they should + * nowhere else be necessary! + */ +static int +w83781d_read_value(struct w83781d_data *data, u16 reg) +{ + struct i2c_client *client = data->client; + int res; + + mutex_lock(&data->lock); + if (client) + res = w83781d_read_value_i2c(data, reg); + else + res = w83781d_read_value_isa(data, reg); + mutex_unlock(&data->lock); + return res; +} + +static int +w83781d_write_value(struct w83781d_data *data, u16 reg, u16 value) +{ + struct i2c_client *client = data->client; + + mutex_lock(&data->lock); + if (client) + w83781d_write_value_i2c(data, reg, value); + else + w83781d_write_value_isa(data, reg, value); + mutex_unlock(&data->lock); + return 0; +} + +static int +w83781d_isa_probe(struct platform_device *pdev) +{ + int err, reg; + struct w83781d_data *data; + struct resource *res; + + /* Reserve the ISA region */ + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!devm_request_region(&pdev->dev, + res->start + W83781D_ADDR_REG_OFFSET, 2, + "w83781d")) + return -EBUSY; + + data = devm_kzalloc(&pdev->dev, sizeof(struct w83781d_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->lock); + data->isa_addr = res->start; + platform_set_drvdata(pdev, data); + + reg = w83781d_read_value(data, W83781D_REG_WCHIPID); + switch (reg) { + case 0x30: + data->type = w83782d; + data->name = "w83782d"; + break; + default: + data->type = w83781d; + data->name = "w83781d"; + } + + /* Initialize the W83781D chip */ + w83781d_init_device(&pdev->dev); + + /* Register sysfs hooks */ + err = w83781d_create_files(&pdev->dev, data->type, 1); + if (err) + goto exit_remove_files; + + err = device_create_file(&pdev->dev, &dev_attr_name); + if (err) + goto exit_remove_files; + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_files; + } + + return 0; + + exit_remove_files: + w83781d_remove_files(&pdev->dev); + device_remove_file(&pdev->dev, &dev_attr_name); + return err; +} + +static int +w83781d_isa_remove(struct platform_device *pdev) +{ + struct w83781d_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + w83781d_remove_files(&pdev->dev); + device_remove_file(&pdev->dev, &dev_attr_name); + + return 0; +} + +static struct platform_driver w83781d_isa_driver = { + .driver = { + .name = "w83781d", + }, + .probe = w83781d_isa_probe, + .remove = w83781d_isa_remove, +}; + +/* return 1 if a supported chip is found, 0 otherwise */ +static int __init +w83781d_isa_found(unsigned short address) +{ + int val, save, found = 0; + int port; + + /* + * Some boards declare base+0 to base+7 as a PNP device, some base+4 + * to base+7 and some base+5 to base+6. So we better request each port + * individually for the probing phase. + */ + for (port = address; port < address + W83781D_EXTENT; port++) { + if (!request_region(port, 1, "w83781d")) { + pr_debug("Failed to request port 0x%x\n", port); + goto release; + } + } + +#define REALLY_SLOW_IO + /* + * We need the timeouts for at least some W83781D-like + * chips. But only if we read 'undefined' registers. + */ + val = inb_p(address + 1); + if (inb_p(address + 2) != val + || inb_p(address + 3) != val + || inb_p(address + 7) != val) { + pr_debug("Detection failed at step %d\n", 1); + goto release; + } +#undef REALLY_SLOW_IO + + /* + * We should be able to change the 7 LSB of the address port. The + * MSB (busy flag) should be clear initially, set after the write. + */ + save = inb_p(address + W83781D_ADDR_REG_OFFSET); + if (save & 0x80) { + pr_debug("Detection failed at step %d\n", 2); + goto release; + } + val = ~save & 0x7f; + outb_p(val, address + W83781D_ADDR_REG_OFFSET); + if (inb_p(address + W83781D_ADDR_REG_OFFSET) != (val | 0x80)) { + outb_p(save, address + W83781D_ADDR_REG_OFFSET); + pr_debug("Detection failed at step %d\n", 3); + goto release; + } + + /* We found a device, now see if it could be a W83781D */ + outb_p(W83781D_REG_CONFIG, address + W83781D_ADDR_REG_OFFSET); + val = inb_p(address + W83781D_DATA_REG_OFFSET); + if (val & 0x80) { + pr_debug("Detection failed at step %d\n", 4); + goto release; + } + outb_p(W83781D_REG_BANK, address + W83781D_ADDR_REG_OFFSET); + save = inb_p(address + W83781D_DATA_REG_OFFSET); + outb_p(W83781D_REG_CHIPMAN, address + W83781D_ADDR_REG_OFFSET); + val = inb_p(address + W83781D_DATA_REG_OFFSET); + if ((!(save & 0x80) && (val != 0xa3)) + || ((save & 0x80) && (val != 0x5c))) { + pr_debug("Detection failed at step %d\n", 5); + goto release; + } + outb_p(W83781D_REG_I2C_ADDR, address + W83781D_ADDR_REG_OFFSET); + val = inb_p(address + W83781D_DATA_REG_OFFSET); + if (val < 0x03 || val > 0x77) { /* Not a valid I2C address */ + pr_debug("Detection failed at step %d\n", 6); + goto release; + } + + /* The busy flag should be clear again */ + if (inb_p(address + W83781D_ADDR_REG_OFFSET) & 0x80) { + pr_debug("Detection failed at step %d\n", 7); + goto release; + } + + /* Determine the chip type */ + outb_p(W83781D_REG_BANK, address + W83781D_ADDR_REG_OFFSET); + save = inb_p(address + W83781D_DATA_REG_OFFSET); + outb_p(save & 0xf8, address + W83781D_DATA_REG_OFFSET); + outb_p(W83781D_REG_WCHIPID, address + W83781D_ADDR_REG_OFFSET); + val = inb_p(address + W83781D_DATA_REG_OFFSET); + if ((val & 0xfe) == 0x10 /* W83781D */ + || val == 0x30) /* W83782D */ + found = 1; + + if (found) + pr_info("Found a %s chip at %#x\n", + val == 0x30 ? "W83782D" : "W83781D", (int)address); + + release: + for (port--; port >= address; port--) + release_region(port, 1); + return found; +} + +static int __init +w83781d_isa_device_add(unsigned short address) +{ + struct resource res = { + .start = address, + .end = address + W83781D_EXTENT - 1, + .name = "w83781d", + .flags = IORESOURCE_IO, + }; + int err; + + pdev = platform_device_alloc("w83781d", address); + if (!pdev) { + err = -ENOMEM; + pr_err("Device allocation failed\n"); + goto exit; + } + + err = platform_device_add_resources(pdev, &res, 1); + if (err) { + pr_err("Device resource addition failed (%d)\n", err); + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + pr_err("Device addition failed (%d)\n", err); + goto exit_device_put; + } + + return 0; + + exit_device_put: + platform_device_put(pdev); + exit: + pdev = NULL; + return err; +} + +static int __init +w83781d_isa_register(void) +{ + int res; + + if (w83781d_isa_found(isa_address)) { + res = platform_driver_register(&w83781d_isa_driver); + if (res) + goto exit; + + /* Sets global pdev as a side effect */ + res = w83781d_isa_device_add(isa_address); + if (res) + goto exit_unreg_isa_driver; + } + + return 0; + +exit_unreg_isa_driver: + platform_driver_unregister(&w83781d_isa_driver); +exit: + return res; +} + +static void +w83781d_isa_unregister(void) +{ + if (pdev) { + platform_device_unregister(pdev); + platform_driver_unregister(&w83781d_isa_driver); + } +} +#else /* !CONFIG_ISA */ + +static struct w83781d_data *w83781d_data_if_isa(void) +{ + return NULL; +} + +static int +w83781d_alias_detect(struct i2c_client *client, u8 chipid) +{ + return 0; +} + +static int +w83781d_read_value(struct w83781d_data *data, u16 reg) +{ + int res; + + mutex_lock(&data->lock); + res = w83781d_read_value_i2c(data, reg); + mutex_unlock(&data->lock); + + return res; +} + +static int +w83781d_write_value(struct w83781d_data *data, u16 reg, u16 value) +{ + mutex_lock(&data->lock); + w83781d_write_value_i2c(data, reg, value); + mutex_unlock(&data->lock); + + return 0; +} + +static int __init +w83781d_isa_register(void) +{ + return 0; +} + +static void +w83781d_isa_unregister(void) +{ +} +#endif /* CONFIG_ISA */ + +static int __init +sensors_w83781d_init(void) +{ + int res; + + /* + * We register the ISA device first, so that we can skip the + * registration of an I2C interface to the same device. + */ + res = w83781d_isa_register(); + if (res) + goto exit; + + res = i2c_add_driver(&w83781d_driver); + if (res) + goto exit_unreg_isa; + + return 0; + + exit_unreg_isa: + w83781d_isa_unregister(); + exit: + return res; +} + +static void __exit +sensors_w83781d_exit(void) +{ + w83781d_isa_unregister(); + i2c_del_driver(&w83781d_driver); +} + +MODULE_AUTHOR("Frodo Looijaard , " + "Philip Edelbrock , " + "and Mark Studebaker "); +MODULE_DESCRIPTION("W83781D driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_w83781d_init); +module_exit(sensors_w83781d_exit); diff --git a/drivers/hwmon/w83791d.c b/drivers/hwmon/w83791d.c new file mode 100644 index 000000000..28fa3bd2c --- /dev/null +++ b/drivers/hwmon/w83791d.c @@ -0,0 +1,1695 @@ +/* + * w83791d.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * + * Copyright (C) 2006-2007 Charles Spirakis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Supports following chips: + * + * Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA + * w83791d 10 5 5 3 0x71 0x5ca3 yes no + * + * The w83791d chip appears to be part way between the 83781d and the + * 83792d. Thus, this file is derived from both the w83792d.c and + * w83781d.c files. + * + * The w83791g chip is the same as the w83791d but lead-free. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NUMBER_OF_VIN 10 +#define NUMBER_OF_FANIN 5 +#define NUMBER_OF_TEMPIN 3 +#define NUMBER_OF_PWM 5 + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f, + I2C_CLIENT_END }; + +/* Insmod parameters */ + +static unsigned short force_subclients[4]; +module_param_array(force_subclients, short, NULL, 0); +MODULE_PARM_DESC(force_subclients, + "List of subclient addresses: {bus, clientaddr, subclientaddr1, subclientaddr2}"); + +static bool reset; +module_param(reset, bool, 0); +MODULE_PARM_DESC(reset, "Set to one to force a hardware chip reset"); + +static bool init; +module_param(init, bool, 0); +MODULE_PARM_DESC(init, "Set to one to force extra software initialization"); + +/* The W83791D registers */ +static const u8 W83791D_REG_IN[NUMBER_OF_VIN] = { + 0x20, /* VCOREA in DataSheet */ + 0x21, /* VINR0 in DataSheet */ + 0x22, /* +3.3VIN in DataSheet */ + 0x23, /* VDD5V in DataSheet */ + 0x24, /* +12VIN in DataSheet */ + 0x25, /* -12VIN in DataSheet */ + 0x26, /* -5VIN in DataSheet */ + 0xB0, /* 5VSB in DataSheet */ + 0xB1, /* VBAT in DataSheet */ + 0xB2 /* VINR1 in DataSheet */ +}; + +static const u8 W83791D_REG_IN_MAX[NUMBER_OF_VIN] = { + 0x2B, /* VCOREA High Limit in DataSheet */ + 0x2D, /* VINR0 High Limit in DataSheet */ + 0x2F, /* +3.3VIN High Limit in DataSheet */ + 0x31, /* VDD5V High Limit in DataSheet */ + 0x33, /* +12VIN High Limit in DataSheet */ + 0x35, /* -12VIN High Limit in DataSheet */ + 0x37, /* -5VIN High Limit in DataSheet */ + 0xB4, /* 5VSB High Limit in DataSheet */ + 0xB6, /* VBAT High Limit in DataSheet */ + 0xB8 /* VINR1 High Limit in DataSheet */ +}; +static const u8 W83791D_REG_IN_MIN[NUMBER_OF_VIN] = { + 0x2C, /* VCOREA Low Limit in DataSheet */ + 0x2E, /* VINR0 Low Limit in DataSheet */ + 0x30, /* +3.3VIN Low Limit in DataSheet */ + 0x32, /* VDD5V Low Limit in DataSheet */ + 0x34, /* +12VIN Low Limit in DataSheet */ + 0x36, /* -12VIN Low Limit in DataSheet */ + 0x38, /* -5VIN Low Limit in DataSheet */ + 0xB5, /* 5VSB Low Limit in DataSheet */ + 0xB7, /* VBAT Low Limit in DataSheet */ + 0xB9 /* VINR1 Low Limit in DataSheet */ +}; +static const u8 W83791D_REG_FAN[NUMBER_OF_FANIN] = { + 0x28, /* FAN 1 Count in DataSheet */ + 0x29, /* FAN 2 Count in DataSheet */ + 0x2A, /* FAN 3 Count in DataSheet */ + 0xBA, /* FAN 4 Count in DataSheet */ + 0xBB, /* FAN 5 Count in DataSheet */ +}; +static const u8 W83791D_REG_FAN_MIN[NUMBER_OF_FANIN] = { + 0x3B, /* FAN 1 Count Low Limit in DataSheet */ + 0x3C, /* FAN 2 Count Low Limit in DataSheet */ + 0x3D, /* FAN 3 Count Low Limit in DataSheet */ + 0xBC, /* FAN 4 Count Low Limit in DataSheet */ + 0xBD, /* FAN 5 Count Low Limit in DataSheet */ +}; + +static const u8 W83791D_REG_PWM[NUMBER_OF_PWM] = { + 0x81, /* PWM 1 duty cycle register in DataSheet */ + 0x83, /* PWM 2 duty cycle register in DataSheet */ + 0x94, /* PWM 3 duty cycle register in DataSheet */ + 0xA0, /* PWM 4 duty cycle register in DataSheet */ + 0xA1, /* PWM 5 duty cycle register in DataSheet */ +}; + +static const u8 W83791D_REG_TEMP_TARGET[3] = { + 0x85, /* PWM 1 target temperature for temp 1 */ + 0x86, /* PWM 2 target temperature for temp 2 */ + 0x96, /* PWM 3 target temperature for temp 3 */ +}; + +static const u8 W83791D_REG_TEMP_TOL[2] = { + 0x87, /* PWM 1/2 temperature tolerance */ + 0x97, /* PWM 3 temperature tolerance */ +}; + +static const u8 W83791D_REG_FAN_CFG[2] = { + 0x84, /* FAN 1/2 configuration */ + 0x95, /* FAN 3 configuration */ +}; + +static const u8 W83791D_REG_FAN_DIV[3] = { + 0x47, /* contains FAN1 and FAN2 Divisor */ + 0x4b, /* contains FAN3 Divisor */ + 0x5C, /* contains FAN4 and FAN5 Divisor */ +}; + +#define W83791D_REG_BANK 0x4E +#define W83791D_REG_TEMP2_CONFIG 0xC2 +#define W83791D_REG_TEMP3_CONFIG 0xCA + +static const u8 W83791D_REG_TEMP1[3] = { + 0x27, /* TEMP 1 in DataSheet */ + 0x39, /* TEMP 1 Over in DataSheet */ + 0x3A, /* TEMP 1 Hyst in DataSheet */ +}; + +static const u8 W83791D_REG_TEMP_ADD[2][6] = { + {0xC0, /* TEMP 2 in DataSheet */ + 0xC1, /* TEMP 2(0.5 deg) in DataSheet */ + 0xC5, /* TEMP 2 Over High part in DataSheet */ + 0xC6, /* TEMP 2 Over Low part in DataSheet */ + 0xC3, /* TEMP 2 Thyst High part in DataSheet */ + 0xC4}, /* TEMP 2 Thyst Low part in DataSheet */ + {0xC8, /* TEMP 3 in DataSheet */ + 0xC9, /* TEMP 3(0.5 deg) in DataSheet */ + 0xCD, /* TEMP 3 Over High part in DataSheet */ + 0xCE, /* TEMP 3 Over Low part in DataSheet */ + 0xCB, /* TEMP 3 Thyst High part in DataSheet */ + 0xCC} /* TEMP 3 Thyst Low part in DataSheet */ +}; + +#define W83791D_REG_BEEP_CONFIG 0x4D + +static const u8 W83791D_REG_BEEP_CTRL[3] = { + 0x56, /* BEEP Control Register 1 */ + 0x57, /* BEEP Control Register 2 */ + 0xA3, /* BEEP Control Register 3 */ +}; + +#define W83791D_REG_GPIO 0x15 +#define W83791D_REG_CONFIG 0x40 +#define W83791D_REG_VID_FANDIV 0x47 +#define W83791D_REG_DID_VID4 0x49 +#define W83791D_REG_WCHIPID 0x58 +#define W83791D_REG_CHIPMAN 0x4F +#define W83791D_REG_PIN 0x4B +#define W83791D_REG_I2C_SUBADDR 0x4A + +#define W83791D_REG_ALARM1 0xA9 /* realtime status register1 */ +#define W83791D_REG_ALARM2 0xAA /* realtime status register2 */ +#define W83791D_REG_ALARM3 0xAB /* realtime status register3 */ + +#define W83791D_REG_VBAT 0x5D +#define W83791D_REG_I2C_ADDR 0x48 + +/* + * The SMBus locks itself. The Winbond W83791D has a bank select register + * (index 0x4e), but the driver only accesses registers in bank 0. Since + * we don't switch banks, we don't need any special code to handle + * locking access between bank switches + */ +static inline int w83791d_read(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static inline int w83791d_write(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +/* + * The analog voltage inputs have 16mV LSB. Since the sysfs output is + * in mV as would be measured on the chip input pin, need to just + * multiply/divide by 16 to translate from/to register values. + */ +#define IN_TO_REG(val) (clamp_val((((val) + 8) / 16), 0, 255)) +#define IN_FROM_REG(val) ((val) * 16) + +static u8 fan_to_reg(long rpm, int div) +{ + if (rpm == 0) + return 255; + rpm = clamp_val(rpm, 1, 1000000); + return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 254); +} + +#define FAN_FROM_REG(val, div) ((val) == 0 ? -1 : \ + ((val) == 255 ? 0 : \ + 1350000 / ((val) * (div)))) + +/* for temp1 which is 8-bit resolution, LSB = 1 degree Celsius */ +#define TEMP1_FROM_REG(val) ((val) * 1000) +#define TEMP1_TO_REG(val) ((val) <= -128000 ? -128 : \ + (val) >= 127000 ? 127 : \ + (val) < 0 ? ((val) - 500) / 1000 : \ + ((val) + 500) / 1000) + +/* + * for temp2 and temp3 which are 9-bit resolution, LSB = 0.5 degree Celsius + * Assumes the top 8 bits are the integral amount and the bottom 8 bits + * are the fractional amount. Since we only have 0.5 degree resolution, + * the bottom 7 bits will always be zero + */ +#define TEMP23_FROM_REG(val) ((val) / 128 * 500) +#define TEMP23_TO_REG(val) (DIV_ROUND_CLOSEST(clamp_val((val), -128000, \ + 127500), 500) * 128) + +/* for thermal cruise target temp, 7-bits, LSB = 1 degree Celsius */ +#define TARGET_TEMP_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val((val), 0, 127000), \ + 1000) + +/* for thermal cruise temp tolerance, 4-bits, LSB = 1 degree Celsius */ +#define TOL_TEMP_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val((val), 0, 15000), \ + 1000) + +#define BEEP_MASK_TO_REG(val) ((val) & 0xffffff) +#define BEEP_MASK_FROM_REG(val) ((val) & 0xffffff) + +#define DIV_FROM_REG(val) (1 << (val)) + +static u8 div_to_reg(int nr, long val) +{ + int i; + + /* fan divisors max out at 128 */ + val = clamp_val(val, 1, 128) >> 1; + for (i = 0; i < 7; i++) { + if (val == 0) + break; + val >>= 1; + } + return (u8) i; +} + +struct w83791d_data { + struct device *hwmon_dev; + struct mutex update_lock; + + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* array of 2 pointers to subclients */ + struct i2c_client *lm75[2]; + + /* volts */ + u8 in[NUMBER_OF_VIN]; /* Register value */ + u8 in_max[NUMBER_OF_VIN]; /* Register value */ + u8 in_min[NUMBER_OF_VIN]; /* Register value */ + + /* fans */ + u8 fan[NUMBER_OF_FANIN]; /* Register value */ + u8 fan_min[NUMBER_OF_FANIN]; /* Register value */ + u8 fan_div[NUMBER_OF_FANIN]; /* Register encoding, shifted right */ + + /* Temperature sensors */ + + s8 temp1[3]; /* current, over, thyst */ + s16 temp_add[2][3]; /* fixed point value. Top 8 bits are the + * integral part, bottom 8 bits are the + * fractional part. We only use the top + * 9 bits as the resolution is only + * to the 0.5 degree C... + * two sensors with three values + * (cur, over, hyst) + */ + + /* PWMs */ + u8 pwm[5]; /* pwm duty cycle */ + u8 pwm_enable[3]; /* pwm enable status for fan 1-3 + * (fan 4-5 only support manual mode) + */ + + u8 temp_target[3]; /* pwm 1-3 target temperature */ + u8 temp_tolerance[3]; /* pwm 1-3 temperature tolerance */ + + /* Misc */ + u32 alarms; /* realtime status register encoding,combined */ + u8 beep_enable; /* Global beep enable */ + u32 beep_mask; /* Mask off specific beeps */ + u8 vid; /* Register encoding, combined */ + u8 vrm; /* hwmon-vid */ +}; + +static int w83791d_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int w83791d_detect(struct i2c_client *client, + struct i2c_board_info *info); +static int w83791d_remove(struct i2c_client *client); + +static int w83791d_read(struct i2c_client *client, u8 reg); +static int w83791d_write(struct i2c_client *client, u8 reg, u8 value); +static struct w83791d_data *w83791d_update_device(struct device *dev); + +#ifdef DEBUG +static void w83791d_print_debug(struct w83791d_data *data, struct device *dev); +#endif + +static void w83791d_init_client(struct i2c_client *client); + +static const struct i2c_device_id w83791d_id[] = { + { "w83791d", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, w83791d_id); + +static struct i2c_driver w83791d_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "w83791d", + }, + .probe = w83791d_probe, + .remove = w83791d_remove, + .id_table = w83791d_id, + .detect = w83791d_detect, + .address_list = normal_i2c, +}; + +/* following are the sysfs callback functions */ +#define show_in_reg(reg) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ + struct w83791d_data *data = w83791d_update_device(dev); \ + int nr = sensor_attr->index; \ + return sprintf(buf, "%d\n", IN_FROM_REG(data->reg[nr])); \ +} + +show_in_reg(in); +show_in_reg(in_min); +show_in_reg(in_max); + +#define store_in_reg(REG, reg) \ +static ssize_t store_in_##reg(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ + struct i2c_client *client = to_i2c_client(dev); \ + struct w83791d_data *data = i2c_get_clientdata(client); \ + int nr = sensor_attr->index; \ + unsigned long val; \ + int err = kstrtoul(buf, 10, &val); \ + if (err) \ + return err; \ + mutex_lock(&data->update_lock); \ + data->in_##reg[nr] = IN_TO_REG(val); \ + w83791d_write(client, W83791D_REG_IN_##REG[nr], data->in_##reg[nr]); \ + mutex_unlock(&data->update_lock); \ + \ + return count; \ +} +store_in_reg(MIN, min); +store_in_reg(MAX, max); + +static struct sensor_device_attribute sda_in_input[] = { + SENSOR_ATTR(in0_input, S_IRUGO, show_in, NULL, 0), + SENSOR_ATTR(in1_input, S_IRUGO, show_in, NULL, 1), + SENSOR_ATTR(in2_input, S_IRUGO, show_in, NULL, 2), + SENSOR_ATTR(in3_input, S_IRUGO, show_in, NULL, 3), + SENSOR_ATTR(in4_input, S_IRUGO, show_in, NULL, 4), + SENSOR_ATTR(in5_input, S_IRUGO, show_in, NULL, 5), + SENSOR_ATTR(in6_input, S_IRUGO, show_in, NULL, 6), + SENSOR_ATTR(in7_input, S_IRUGO, show_in, NULL, 7), + SENSOR_ATTR(in8_input, S_IRUGO, show_in, NULL, 8), + SENSOR_ATTR(in9_input, S_IRUGO, show_in, NULL, 9), +}; + +static struct sensor_device_attribute sda_in_min[] = { + SENSOR_ATTR(in0_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 0), + SENSOR_ATTR(in1_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 1), + SENSOR_ATTR(in2_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 2), + SENSOR_ATTR(in3_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 3), + SENSOR_ATTR(in4_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 4), + SENSOR_ATTR(in5_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 5), + SENSOR_ATTR(in6_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 6), + SENSOR_ATTR(in7_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 7), + SENSOR_ATTR(in8_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 8), + SENSOR_ATTR(in9_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 9), +}; + +static struct sensor_device_attribute sda_in_max[] = { + SENSOR_ATTR(in0_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 0), + SENSOR_ATTR(in1_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 1), + SENSOR_ATTR(in2_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 2), + SENSOR_ATTR(in3_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 3), + SENSOR_ATTR(in4_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 4), + SENSOR_ATTR(in5_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 5), + SENSOR_ATTR(in6_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 6), + SENSOR_ATTR(in7_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 7), + SENSOR_ATTR(in8_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 8), + SENSOR_ATTR(in9_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 9), +}; + + +static ssize_t show_beep(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = + to_sensor_dev_attr(attr); + struct w83791d_data *data = w83791d_update_device(dev); + int bitnr = sensor_attr->index; + + return sprintf(buf, "%d\n", (data->beep_mask >> bitnr) & 1); +} + +static ssize_t store_beep(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = + to_sensor_dev_attr(attr); + struct i2c_client *client = to_i2c_client(dev); + struct w83791d_data *data = i2c_get_clientdata(client); + int bitnr = sensor_attr->index; + int bytenr = bitnr / 8; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + val = val ? 1 : 0; + + mutex_lock(&data->update_lock); + + data->beep_mask &= ~(0xff << (bytenr * 8)); + data->beep_mask |= w83791d_read(client, W83791D_REG_BEEP_CTRL[bytenr]) + << (bytenr * 8); + + data->beep_mask &= ~(1 << bitnr); + data->beep_mask |= val << bitnr; + + w83791d_write(client, W83791D_REG_BEEP_CTRL[bytenr], + (data->beep_mask >> (bytenr * 8)) & 0xff); + + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = + to_sensor_dev_attr(attr); + struct w83791d_data *data = w83791d_update_device(dev); + int bitnr = sensor_attr->index; + + return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1); +} + +/* + * Note: The bitmask for the beep enable/disable is different than + * the bitmask for the alarm. + */ +static struct sensor_device_attribute sda_in_beep[] = { + SENSOR_ATTR(in0_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 0), + SENSOR_ATTR(in1_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 13), + SENSOR_ATTR(in2_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 2), + SENSOR_ATTR(in3_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 3), + SENSOR_ATTR(in4_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 8), + SENSOR_ATTR(in5_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 9), + SENSOR_ATTR(in6_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 10), + SENSOR_ATTR(in7_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 16), + SENSOR_ATTR(in8_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 17), + SENSOR_ATTR(in9_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 14), +}; + +static struct sensor_device_attribute sda_in_alarm[] = { + SENSOR_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0), + SENSOR_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1), + SENSOR_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2), + SENSOR_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3), + SENSOR_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 8), + SENSOR_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 9), + SENSOR_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 10), + SENSOR_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 19), + SENSOR_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 20), + SENSOR_ATTR(in9_alarm, S_IRUGO, show_alarm, NULL, 14), +}; + +#define show_fan_reg(reg) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct sensor_device_attribute *sensor_attr = \ + to_sensor_dev_attr(attr); \ + struct w83791d_data *data = w83791d_update_device(dev); \ + int nr = sensor_attr->index; \ + return sprintf(buf, "%d\n", \ + FAN_FROM_REG(data->reg[nr], DIV_FROM_REG(data->fan_div[nr]))); \ +} + +show_fan_reg(fan); +show_fan_reg(fan_min); + +static ssize_t store_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct i2c_client *client = to_i2c_client(dev); + struct w83791d_data *data = i2c_get_clientdata(client); + int nr = sensor_attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_min[nr] = fan_to_reg(val, DIV_FROM_REG(data->fan_div[nr])); + w83791d_write(client, W83791D_REG_FAN_MIN[nr], data->fan_min[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_fan_div(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct w83791d_data *data = w83791d_update_device(dev); + return sprintf(buf, "%u\n", DIV_FROM_REG(data->fan_div[nr])); +} + +/* + * Note: we save and restore the fan minimum here, because its value is + * determined in part by the fan divisor. This follows the principle of + * least surprise; the user doesn't expect the fan minimum to change just + * because the divisor changed. + */ +static ssize_t store_fan_div(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct i2c_client *client = to_i2c_client(dev); + struct w83791d_data *data = i2c_get_clientdata(client); + int nr = sensor_attr->index; + unsigned long min; + u8 tmp_fan_div; + u8 fan_div_reg; + u8 vbat_reg; + int indx = 0; + u8 keep_mask = 0; + u8 new_shift = 0; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + /* Save fan_min */ + min = FAN_FROM_REG(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr])); + + mutex_lock(&data->update_lock); + data->fan_div[nr] = div_to_reg(nr, val); + + switch (nr) { + case 0: + indx = 0; + keep_mask = 0xcf; + new_shift = 4; + break; + case 1: + indx = 0; + keep_mask = 0x3f; + new_shift = 6; + break; + case 2: + indx = 1; + keep_mask = 0x3f; + new_shift = 6; + break; + case 3: + indx = 2; + keep_mask = 0xf8; + new_shift = 0; + break; + case 4: + indx = 2; + keep_mask = 0x8f; + new_shift = 4; + break; +#ifdef DEBUG + default: + dev_warn(dev, "store_fan_div: Unexpected nr seen: %d\n", nr); + count = -EINVAL; + goto err_exit; +#endif + } + + fan_div_reg = w83791d_read(client, W83791D_REG_FAN_DIV[indx]) + & keep_mask; + tmp_fan_div = (data->fan_div[nr] << new_shift) & ~keep_mask; + + w83791d_write(client, W83791D_REG_FAN_DIV[indx], + fan_div_reg | tmp_fan_div); + + /* Bit 2 of fans 0-2 is stored in the vbat register (bits 5-7) */ + if (nr < 3) { + keep_mask = ~(1 << (nr + 5)); + vbat_reg = w83791d_read(client, W83791D_REG_VBAT) + & keep_mask; + tmp_fan_div = (data->fan_div[nr] << (3 + nr)) & ~keep_mask; + w83791d_write(client, W83791D_REG_VBAT, + vbat_reg | tmp_fan_div); + } + + /* Restore fan_min */ + data->fan_min[nr] = fan_to_reg(min, DIV_FROM_REG(data->fan_div[nr])); + w83791d_write(client, W83791D_REG_FAN_MIN[nr], data->fan_min[nr]); + +#ifdef DEBUG +err_exit: +#endif + mutex_unlock(&data->update_lock); + + return count; +} + +static struct sensor_device_attribute sda_fan_input[] = { + SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0), + SENSOR_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1), + SENSOR_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2), + SENSOR_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3), + SENSOR_ATTR(fan5_input, S_IRUGO, show_fan, NULL, 4), +}; + +static struct sensor_device_attribute sda_fan_min[] = { + SENSOR_ATTR(fan1_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 0), + SENSOR_ATTR(fan2_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 1), + SENSOR_ATTR(fan3_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 2), + SENSOR_ATTR(fan4_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 3), + SENSOR_ATTR(fan5_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 4), +}; + +static struct sensor_device_attribute sda_fan_div[] = { + SENSOR_ATTR(fan1_div, S_IWUSR | S_IRUGO, + show_fan_div, store_fan_div, 0), + SENSOR_ATTR(fan2_div, S_IWUSR | S_IRUGO, + show_fan_div, store_fan_div, 1), + SENSOR_ATTR(fan3_div, S_IWUSR | S_IRUGO, + show_fan_div, store_fan_div, 2), + SENSOR_ATTR(fan4_div, S_IWUSR | S_IRUGO, + show_fan_div, store_fan_div, 3), + SENSOR_ATTR(fan5_div, S_IWUSR | S_IRUGO, + show_fan_div, store_fan_div, 4), +}; + +static struct sensor_device_attribute sda_fan_beep[] = { + SENSOR_ATTR(fan1_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 6), + SENSOR_ATTR(fan2_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 7), + SENSOR_ATTR(fan3_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 11), + SENSOR_ATTR(fan4_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 21), + SENSOR_ATTR(fan5_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 22), +}; + +static struct sensor_device_attribute sda_fan_alarm[] = { + SENSOR_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 6), + SENSOR_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 7), + SENSOR_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, 11), + SENSOR_ATTR(fan4_alarm, S_IRUGO, show_alarm, NULL, 21), + SENSOR_ATTR(fan5_alarm, S_IRUGO, show_alarm, NULL, 22), +}; + +/* read/write PWMs */ +static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct w83791d_data *data = w83791d_update_device(dev); + return sprintf(buf, "%u\n", data->pwm[nr]); +} + +static ssize_t store_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct i2c_client *client = to_i2c_client(dev); + struct w83791d_data *data = i2c_get_clientdata(client); + int nr = sensor_attr->index; + unsigned long val; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->pwm[nr] = clamp_val(val, 0, 255); + w83791d_write(client, W83791D_REG_PWM[nr], data->pwm[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static struct sensor_device_attribute sda_pwm[] = { + SENSOR_ATTR(pwm1, S_IWUSR | S_IRUGO, + show_pwm, store_pwm, 0), + SENSOR_ATTR(pwm2, S_IWUSR | S_IRUGO, + show_pwm, store_pwm, 1), + SENSOR_ATTR(pwm3, S_IWUSR | S_IRUGO, + show_pwm, store_pwm, 2), + SENSOR_ATTR(pwm4, S_IWUSR | S_IRUGO, + show_pwm, store_pwm, 3), + SENSOR_ATTR(pwm5, S_IWUSR | S_IRUGO, + show_pwm, store_pwm, 4), +}; + +static ssize_t show_pwmenable(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct w83791d_data *data = w83791d_update_device(dev); + return sprintf(buf, "%u\n", data->pwm_enable[nr] + 1); +} + +static ssize_t store_pwmenable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct i2c_client *client = to_i2c_client(dev); + struct w83791d_data *data = i2c_get_clientdata(client); + int nr = sensor_attr->index; + unsigned long val; + u8 reg_cfg_tmp; + u8 reg_idx = 0; + u8 val_shift = 0; + u8 keep_mask = 0; + + int ret = kstrtoul(buf, 10, &val); + + if (ret || val < 1 || val > 3) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->pwm_enable[nr] = val - 1; + switch (nr) { + case 0: + reg_idx = 0; + val_shift = 2; + keep_mask = 0xf3; + break; + case 1: + reg_idx = 0; + val_shift = 4; + keep_mask = 0xcf; + break; + case 2: + reg_idx = 1; + val_shift = 2; + keep_mask = 0xf3; + break; + } + + reg_cfg_tmp = w83791d_read(client, W83791D_REG_FAN_CFG[reg_idx]); + reg_cfg_tmp = (reg_cfg_tmp & keep_mask) | + data->pwm_enable[nr] << val_shift; + + w83791d_write(client, W83791D_REG_FAN_CFG[reg_idx], reg_cfg_tmp); + mutex_unlock(&data->update_lock); + + return count; +} +static struct sensor_device_attribute sda_pwmenable[] = { + SENSOR_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + show_pwmenable, store_pwmenable, 0), + SENSOR_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, + show_pwmenable, store_pwmenable, 1), + SENSOR_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, + show_pwmenable, store_pwmenable, 2), +}; + +/* For Smart Fan I / Thermal Cruise */ +static ssize_t show_temp_target(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct w83791d_data *data = w83791d_update_device(dev); + int nr = sensor_attr->index; + return sprintf(buf, "%d\n", TEMP1_FROM_REG(data->temp_target[nr])); +} + +static ssize_t store_temp_target(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct i2c_client *client = to_i2c_client(dev); + struct w83791d_data *data = i2c_get_clientdata(client); + int nr = sensor_attr->index; + long val; + u8 target_mask; + + if (kstrtol(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->temp_target[nr] = TARGET_TEMP_TO_REG(val); + target_mask = w83791d_read(client, + W83791D_REG_TEMP_TARGET[nr]) & 0x80; + w83791d_write(client, W83791D_REG_TEMP_TARGET[nr], + data->temp_target[nr] | target_mask); + mutex_unlock(&data->update_lock); + return count; +} + +static struct sensor_device_attribute sda_temp_target[] = { + SENSOR_ATTR(temp1_target, S_IWUSR | S_IRUGO, + show_temp_target, store_temp_target, 0), + SENSOR_ATTR(temp2_target, S_IWUSR | S_IRUGO, + show_temp_target, store_temp_target, 1), + SENSOR_ATTR(temp3_target, S_IWUSR | S_IRUGO, + show_temp_target, store_temp_target, 2), +}; + +static ssize_t show_temp_tolerance(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct w83791d_data *data = w83791d_update_device(dev); + int nr = sensor_attr->index; + return sprintf(buf, "%d\n", TEMP1_FROM_REG(data->temp_tolerance[nr])); +} + +static ssize_t store_temp_tolerance(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + struct i2c_client *client = to_i2c_client(dev); + struct w83791d_data *data = i2c_get_clientdata(client); + int nr = sensor_attr->index; + unsigned long val; + u8 target_mask; + u8 reg_idx = 0; + u8 val_shift = 0; + u8 keep_mask = 0; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + switch (nr) { + case 0: + reg_idx = 0; + val_shift = 0; + keep_mask = 0xf0; + break; + case 1: + reg_idx = 0; + val_shift = 4; + keep_mask = 0x0f; + break; + case 2: + reg_idx = 1; + val_shift = 0; + keep_mask = 0xf0; + break; + } + + mutex_lock(&data->update_lock); + data->temp_tolerance[nr] = TOL_TEMP_TO_REG(val); + target_mask = w83791d_read(client, + W83791D_REG_TEMP_TOL[reg_idx]) & keep_mask; + w83791d_write(client, W83791D_REG_TEMP_TOL[reg_idx], + (data->temp_tolerance[nr] << val_shift) | target_mask); + mutex_unlock(&data->update_lock); + return count; +} + +static struct sensor_device_attribute sda_temp_tolerance[] = { + SENSOR_ATTR(temp1_tolerance, S_IWUSR | S_IRUGO, + show_temp_tolerance, store_temp_tolerance, 0), + SENSOR_ATTR(temp2_tolerance, S_IWUSR | S_IRUGO, + show_temp_tolerance, store_temp_tolerance, 1), + SENSOR_ATTR(temp3_tolerance, S_IWUSR | S_IRUGO, + show_temp_tolerance, store_temp_tolerance, 2), +}; + +/* read/write the temperature1, includes measured value and limits */ +static ssize_t show_temp1(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct w83791d_data *data = w83791d_update_device(dev); + return sprintf(buf, "%d\n", TEMP1_FROM_REG(data->temp1[attr->index])); +} + +static ssize_t store_temp1(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct i2c_client *client = to_i2c_client(dev); + struct w83791d_data *data = i2c_get_clientdata(client); + int nr = attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp1[nr] = TEMP1_TO_REG(val); + w83791d_write(client, W83791D_REG_TEMP1[nr], data->temp1[nr]); + mutex_unlock(&data->update_lock); + return count; +} + +/* read/write temperature2-3, includes measured value and limits */ +static ssize_t show_temp23(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct w83791d_data *data = w83791d_update_device(dev); + int nr = attr->nr; + int index = attr->index; + return sprintf(buf, "%d\n", TEMP23_FROM_REG(data->temp_add[nr][index])); +} + +static ssize_t store_temp23(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr); + struct i2c_client *client = to_i2c_client(dev); + struct w83791d_data *data = i2c_get_clientdata(client); + long val; + int err; + int nr = attr->nr; + int index = attr->index; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_add[nr][index] = TEMP23_TO_REG(val); + w83791d_write(client, W83791D_REG_TEMP_ADD[nr][index * 2], + data->temp_add[nr][index] >> 8); + w83791d_write(client, W83791D_REG_TEMP_ADD[nr][index * 2 + 1], + data->temp_add[nr][index] & 0x80); + mutex_unlock(&data->update_lock); + + return count; +} + +static struct sensor_device_attribute_2 sda_temp_input[] = { + SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp1, NULL, 0, 0), + SENSOR_ATTR_2(temp2_input, S_IRUGO, show_temp23, NULL, 0, 0), + SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp23, NULL, 1, 0), +}; + +static struct sensor_device_attribute_2 sda_temp_max[] = { + SENSOR_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, + show_temp1, store_temp1, 0, 1), + SENSOR_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, + show_temp23, store_temp23, 0, 1), + SENSOR_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, + show_temp23, store_temp23, 1, 1), +}; + +static struct sensor_device_attribute_2 sda_temp_max_hyst[] = { + SENSOR_ATTR_2(temp1_max_hyst, S_IRUGO | S_IWUSR, + show_temp1, store_temp1, 0, 2), + SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO | S_IWUSR, + show_temp23, store_temp23, 0, 2), + SENSOR_ATTR_2(temp3_max_hyst, S_IRUGO | S_IWUSR, + show_temp23, store_temp23, 1, 2), +}; + +/* + * Note: The bitmask for the beep enable/disable is different than + * the bitmask for the alarm. + */ +static struct sensor_device_attribute sda_temp_beep[] = { + SENSOR_ATTR(temp1_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 4), + SENSOR_ATTR(temp2_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 5), + SENSOR_ATTR(temp3_beep, S_IWUSR | S_IRUGO, show_beep, store_beep, 1), +}; + +static struct sensor_device_attribute sda_temp_alarm[] = { + SENSOR_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 4), + SENSOR_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 5), + SENSOR_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 13), +}; + +/* get realtime status of all sensors items: voltage, temp, fan */ +static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w83791d_data *data = w83791d_update_device(dev); + return sprintf(buf, "%u\n", data->alarms); +} + +static DEVICE_ATTR_RO(alarms); + +/* Beep control */ + +#define GLOBAL_BEEP_ENABLE_SHIFT 15 +#define GLOBAL_BEEP_ENABLE_MASK (1 << GLOBAL_BEEP_ENABLE_SHIFT) + +static ssize_t show_beep_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct w83791d_data *data = w83791d_update_device(dev); + return sprintf(buf, "%d\n", data->beep_enable); +} + +static ssize_t show_beep_mask(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct w83791d_data *data = w83791d_update_device(dev); + return sprintf(buf, "%d\n", BEEP_MASK_FROM_REG(data->beep_mask)); +} + + +static ssize_t store_beep_mask(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83791d_data *data = i2c_get_clientdata(client); + int i; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + /* + * The beep_enable state overrides any enabling request from + * the masks + */ + data->beep_mask = BEEP_MASK_TO_REG(val) & ~GLOBAL_BEEP_ENABLE_MASK; + data->beep_mask |= (data->beep_enable << GLOBAL_BEEP_ENABLE_SHIFT); + + val = data->beep_mask; + + for (i = 0; i < 3; i++) { + w83791d_write(client, W83791D_REG_BEEP_CTRL[i], (val & 0xff)); + val >>= 8; + } + + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t store_beep_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83791d_data *data = i2c_get_clientdata(client); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + + data->beep_enable = val ? 1 : 0; + + /* Keep the full mask value in sync with the current enable */ + data->beep_mask &= ~GLOBAL_BEEP_ENABLE_MASK; + data->beep_mask |= (data->beep_enable << GLOBAL_BEEP_ENABLE_SHIFT); + + /* + * The global control is in the second beep control register + * so only need to update that register + */ + val = (data->beep_mask >> 8) & 0xff; + + w83791d_write(client, W83791D_REG_BEEP_CTRL[1], val); + + mutex_unlock(&data->update_lock); + + return count; +} + +static struct sensor_device_attribute sda_beep_ctrl[] = { + SENSOR_ATTR(beep_enable, S_IRUGO | S_IWUSR, + show_beep_enable, store_beep_enable, 0), + SENSOR_ATTR(beep_mask, S_IRUGO | S_IWUSR, + show_beep_mask, store_beep_mask, 1) +}; + +/* cpu voltage regulation information */ +static ssize_t cpu0_vid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct w83791d_data *data = w83791d_update_device(dev); + return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); +} + +static DEVICE_ATTR_RO(cpu0_vid); + +static ssize_t vrm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w83791d_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", data->vrm); +} + +static ssize_t vrm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w83791d_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + /* + * No lock needed as vrm is internal to the driver + * (not read from a chip register) and so is not + * updated in w83791d_update_device() + */ + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 255) + return -EINVAL; + + data->vrm = val; + return count; +} + +static DEVICE_ATTR_RW(vrm); + +#define IN_UNIT_ATTRS(X) \ + &sda_in_input[X].dev_attr.attr, \ + &sda_in_min[X].dev_attr.attr, \ + &sda_in_max[X].dev_attr.attr, \ + &sda_in_beep[X].dev_attr.attr, \ + &sda_in_alarm[X].dev_attr.attr + +#define FAN_UNIT_ATTRS(X) \ + &sda_fan_input[X].dev_attr.attr, \ + &sda_fan_min[X].dev_attr.attr, \ + &sda_fan_div[X].dev_attr.attr, \ + &sda_fan_beep[X].dev_attr.attr, \ + &sda_fan_alarm[X].dev_attr.attr + +#define TEMP_UNIT_ATTRS(X) \ + &sda_temp_input[X].dev_attr.attr, \ + &sda_temp_max[X].dev_attr.attr, \ + &sda_temp_max_hyst[X].dev_attr.attr, \ + &sda_temp_beep[X].dev_attr.attr, \ + &sda_temp_alarm[X].dev_attr.attr + +static struct attribute *w83791d_attributes[] = { + IN_UNIT_ATTRS(0), + IN_UNIT_ATTRS(1), + IN_UNIT_ATTRS(2), + IN_UNIT_ATTRS(3), + IN_UNIT_ATTRS(4), + IN_UNIT_ATTRS(5), + IN_UNIT_ATTRS(6), + IN_UNIT_ATTRS(7), + IN_UNIT_ATTRS(8), + IN_UNIT_ATTRS(9), + FAN_UNIT_ATTRS(0), + FAN_UNIT_ATTRS(1), + FAN_UNIT_ATTRS(2), + TEMP_UNIT_ATTRS(0), + TEMP_UNIT_ATTRS(1), + TEMP_UNIT_ATTRS(2), + &dev_attr_alarms.attr, + &sda_beep_ctrl[0].dev_attr.attr, + &sda_beep_ctrl[1].dev_attr.attr, + &dev_attr_cpu0_vid.attr, + &dev_attr_vrm.attr, + &sda_pwm[0].dev_attr.attr, + &sda_pwm[1].dev_attr.attr, + &sda_pwm[2].dev_attr.attr, + &sda_pwmenable[0].dev_attr.attr, + &sda_pwmenable[1].dev_attr.attr, + &sda_pwmenable[2].dev_attr.attr, + &sda_temp_target[0].dev_attr.attr, + &sda_temp_target[1].dev_attr.attr, + &sda_temp_target[2].dev_attr.attr, + &sda_temp_tolerance[0].dev_attr.attr, + &sda_temp_tolerance[1].dev_attr.attr, + &sda_temp_tolerance[2].dev_attr.attr, + NULL +}; + +static const struct attribute_group w83791d_group = { + .attrs = w83791d_attributes, +}; + +/* + * Separate group of attributes for fan/pwm 4-5. Their pins can also be + * in use for GPIO in which case their sysfs-interface should not be made + * available + */ +static struct attribute *w83791d_attributes_fanpwm45[] = { + FAN_UNIT_ATTRS(3), + FAN_UNIT_ATTRS(4), + &sda_pwm[3].dev_attr.attr, + &sda_pwm[4].dev_attr.attr, + NULL +}; + +static const struct attribute_group w83791d_group_fanpwm45 = { + .attrs = w83791d_attributes_fanpwm45, +}; + +static int w83791d_detect_subclients(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + struct w83791d_data *data = i2c_get_clientdata(client); + int address = client->addr; + int i, id, err; + u8 val; + + id = i2c_adapter_id(adapter); + if (force_subclients[0] == id && force_subclients[1] == address) { + for (i = 2; i <= 3; i++) { + if (force_subclients[i] < 0x48 || + force_subclients[i] > 0x4f) { + dev_err(&client->dev, + "invalid subclient " + "address %d; must be 0x48-0x4f\n", + force_subclients[i]); + err = -ENODEV; + goto error_sc_0; + } + } + w83791d_write(client, W83791D_REG_I2C_SUBADDR, + (force_subclients[2] & 0x07) | + ((force_subclients[3] & 0x07) << 4)); + } + + val = w83791d_read(client, W83791D_REG_I2C_SUBADDR); + if (!(val & 0x08)) + data->lm75[0] = i2c_new_dummy(adapter, 0x48 + (val & 0x7)); + if (!(val & 0x80)) { + if ((data->lm75[0] != NULL) && + ((val & 0x7) == ((val >> 4) & 0x7))) { + dev_err(&client->dev, + "duplicate addresses 0x%x, " + "use force_subclient\n", + data->lm75[0]->addr); + err = -ENODEV; + goto error_sc_1; + } + data->lm75[1] = i2c_new_dummy(adapter, + 0x48 + ((val >> 4) & 0x7)); + } + + return 0; + +/* Undo inits in case of errors */ + +error_sc_1: + i2c_unregister_device(data->lm75[0]); +error_sc_0: + return err; +} + + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int w83791d_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int val1, val2; + unsigned short address = client->addr; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + if (w83791d_read(client, W83791D_REG_CONFIG) & 0x80) + return -ENODEV; + + val1 = w83791d_read(client, W83791D_REG_BANK); + val2 = w83791d_read(client, W83791D_REG_CHIPMAN); + /* Check for Winbond ID if in bank 0 */ + if (!(val1 & 0x07)) { + if ((!(val1 & 0x80) && val2 != 0xa3) || + ((val1 & 0x80) && val2 != 0x5c)) { + return -ENODEV; + } + } + /* + * If Winbond chip, address of chip and W83791D_REG_I2C_ADDR + * should match + */ + if (w83791d_read(client, W83791D_REG_I2C_ADDR) != address) + return -ENODEV; + + /* We want bank 0 and Vendor ID high byte */ + val1 = w83791d_read(client, W83791D_REG_BANK) & 0x78; + w83791d_write(client, W83791D_REG_BANK, val1 | 0x80); + + /* Verify it is a Winbond w83791d */ + val1 = w83791d_read(client, W83791D_REG_WCHIPID); + val2 = w83791d_read(client, W83791D_REG_CHIPMAN); + if (val1 != 0x71 || val2 != 0x5c) + return -ENODEV; + + strlcpy(info->type, "w83791d", I2C_NAME_SIZE); + + return 0; +} + +static int w83791d_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct w83791d_data *data; + struct device *dev = &client->dev; + int i, err; + u8 has_fanpwm45; + +#ifdef DEBUG + int val1; + val1 = w83791d_read(client, W83791D_REG_DID_VID4); + dev_dbg(dev, "Device ID version: %d.%d (0x%02x)\n", + (val1 >> 5) & 0x07, (val1 >> 1) & 0x0f, val1); +#endif + + data = devm_kzalloc(&client->dev, sizeof(struct w83791d_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + err = w83791d_detect_subclients(client); + if (err) + return err; + + /* Initialize the chip */ + w83791d_init_client(client); + + /* + * If the fan_div is changed, make sure there is a rational + * fan_min in place + */ + for (i = 0; i < NUMBER_OF_FANIN; i++) + data->fan_min[i] = w83791d_read(client, W83791D_REG_FAN_MIN[i]); + + /* Register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &w83791d_group); + if (err) + goto error3; + + /* Check if pins of fan/pwm 4-5 are in use as GPIO */ + has_fanpwm45 = w83791d_read(client, W83791D_REG_GPIO) & 0x10; + if (has_fanpwm45) { + err = sysfs_create_group(&client->dev.kobj, + &w83791d_group_fanpwm45); + if (err) + goto error4; + } + + /* Everything is ready, now register the working device */ + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto error5; + } + + return 0; + +error5: + if (has_fanpwm45) + sysfs_remove_group(&client->dev.kobj, &w83791d_group_fanpwm45); +error4: + sysfs_remove_group(&client->dev.kobj, &w83791d_group); +error3: + i2c_unregister_device(data->lm75[0]); + i2c_unregister_device(data->lm75[1]); + return err; +} + +static int w83791d_remove(struct i2c_client *client) +{ + struct w83791d_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &w83791d_group); + + i2c_unregister_device(data->lm75[0]); + i2c_unregister_device(data->lm75[1]); + + return 0; +} + +static void w83791d_init_client(struct i2c_client *client) +{ + struct w83791d_data *data = i2c_get_clientdata(client); + u8 tmp; + u8 old_beep; + + /* + * The difference between reset and init is that reset + * does a hard reset of the chip via index 0x40, bit 7, + * but init simply forces certain registers to have "sane" + * values. The hope is that the BIOS has done the right + * thing (which is why the default is reset=0, init=0), + * but if not, reset is the hard hammer and init + * is the soft mallet both of which are trying to whack + * things into place... + * NOTE: The data sheet makes a distinction between + * "power on defaults" and "reset by MR". As far as I can tell, + * the hard reset puts everything into a power-on state so I'm + * not sure what "reset by MR" means or how it can happen. + */ + if (reset || init) { + /* keep some BIOS settings when we... */ + old_beep = w83791d_read(client, W83791D_REG_BEEP_CONFIG); + + if (reset) { + /* ... reset the chip and ... */ + w83791d_write(client, W83791D_REG_CONFIG, 0x80); + } + + /* ... disable power-on abnormal beep */ + w83791d_write(client, W83791D_REG_BEEP_CONFIG, old_beep | 0x80); + + /* disable the global beep (not done by hard reset) */ + tmp = w83791d_read(client, W83791D_REG_BEEP_CTRL[1]); + w83791d_write(client, W83791D_REG_BEEP_CTRL[1], tmp & 0xef); + + if (init) { + /* Make sure monitoring is turned on for add-ons */ + tmp = w83791d_read(client, W83791D_REG_TEMP2_CONFIG); + if (tmp & 1) { + w83791d_write(client, W83791D_REG_TEMP2_CONFIG, + tmp & 0xfe); + } + + tmp = w83791d_read(client, W83791D_REG_TEMP3_CONFIG); + if (tmp & 1) { + w83791d_write(client, W83791D_REG_TEMP3_CONFIG, + tmp & 0xfe); + } + + /* Start monitoring */ + tmp = w83791d_read(client, W83791D_REG_CONFIG) & 0xf7; + w83791d_write(client, W83791D_REG_CONFIG, tmp | 0x01); + } + } + + data->vrm = vid_which_vrm(); +} + +static struct w83791d_data *w83791d_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83791d_data *data = i2c_get_clientdata(client); + int i, j; + u8 reg_array_tmp[3]; + u8 vbat_reg; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + (HZ * 3)) + || !data->valid) { + dev_dbg(dev, "Starting w83791d device update\n"); + + /* Update the voltages measured value and limits */ + for (i = 0; i < NUMBER_OF_VIN; i++) { + data->in[i] = w83791d_read(client, + W83791D_REG_IN[i]); + data->in_max[i] = w83791d_read(client, + W83791D_REG_IN_MAX[i]); + data->in_min[i] = w83791d_read(client, + W83791D_REG_IN_MIN[i]); + } + + /* Update the fan counts and limits */ + for (i = 0; i < NUMBER_OF_FANIN; i++) { + /* Update the Fan measured value and limits */ + data->fan[i] = w83791d_read(client, + W83791D_REG_FAN[i]); + data->fan_min[i] = w83791d_read(client, + W83791D_REG_FAN_MIN[i]); + } + + /* Update the fan divisor */ + for (i = 0; i < 3; i++) { + reg_array_tmp[i] = w83791d_read(client, + W83791D_REG_FAN_DIV[i]); + } + data->fan_div[0] = (reg_array_tmp[0] >> 4) & 0x03; + data->fan_div[1] = (reg_array_tmp[0] >> 6) & 0x03; + data->fan_div[2] = (reg_array_tmp[1] >> 6) & 0x03; + data->fan_div[3] = reg_array_tmp[2] & 0x07; + data->fan_div[4] = (reg_array_tmp[2] >> 4) & 0x07; + + /* + * The fan divisor for fans 0-2 get bit 2 from + * bits 5-7 respectively of vbat register + */ + vbat_reg = w83791d_read(client, W83791D_REG_VBAT); + for (i = 0; i < 3; i++) + data->fan_div[i] |= (vbat_reg >> (3 + i)) & 0x04; + + /* Update PWM duty cycle */ + for (i = 0; i < NUMBER_OF_PWM; i++) { + data->pwm[i] = w83791d_read(client, + W83791D_REG_PWM[i]); + } + + /* Update PWM enable status */ + for (i = 0; i < 2; i++) { + reg_array_tmp[i] = w83791d_read(client, + W83791D_REG_FAN_CFG[i]); + } + data->pwm_enable[0] = (reg_array_tmp[0] >> 2) & 0x03; + data->pwm_enable[1] = (reg_array_tmp[0] >> 4) & 0x03; + data->pwm_enable[2] = (reg_array_tmp[1] >> 2) & 0x03; + + /* Update PWM target temperature */ + for (i = 0; i < 3; i++) { + data->temp_target[i] = w83791d_read(client, + W83791D_REG_TEMP_TARGET[i]) & 0x7f; + } + + /* Update PWM temperature tolerance */ + for (i = 0; i < 2; i++) { + reg_array_tmp[i] = w83791d_read(client, + W83791D_REG_TEMP_TOL[i]); + } + data->temp_tolerance[0] = reg_array_tmp[0] & 0x0f; + data->temp_tolerance[1] = (reg_array_tmp[0] >> 4) & 0x0f; + data->temp_tolerance[2] = reg_array_tmp[1] & 0x0f; + + /* Update the first temperature sensor */ + for (i = 0; i < 3; i++) { + data->temp1[i] = w83791d_read(client, + W83791D_REG_TEMP1[i]); + } + + /* Update the rest of the temperature sensors */ + for (i = 0; i < 2; i++) { + for (j = 0; j < 3; j++) { + data->temp_add[i][j] = + (w83791d_read(client, + W83791D_REG_TEMP_ADD[i][j * 2]) << 8) | + w83791d_read(client, + W83791D_REG_TEMP_ADD[i][j * 2 + 1]); + } + } + + /* Update the realtime status */ + data->alarms = + w83791d_read(client, W83791D_REG_ALARM1) + + (w83791d_read(client, W83791D_REG_ALARM2) << 8) + + (w83791d_read(client, W83791D_REG_ALARM3) << 16); + + /* Update the beep configuration information */ + data->beep_mask = + w83791d_read(client, W83791D_REG_BEEP_CTRL[0]) + + (w83791d_read(client, W83791D_REG_BEEP_CTRL[1]) << 8) + + (w83791d_read(client, W83791D_REG_BEEP_CTRL[2]) << 16); + + /* Extract global beep enable flag */ + data->beep_enable = + (data->beep_mask >> GLOBAL_BEEP_ENABLE_SHIFT) & 0x01; + + /* Update the cpu voltage information */ + i = w83791d_read(client, W83791D_REG_VID_FANDIV); + data->vid = i & 0x0f; + data->vid |= (w83791d_read(client, W83791D_REG_DID_VID4) & 0x01) + << 4; + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + +#ifdef DEBUG + w83791d_print_debug(data, dev); +#endif + + return data; +} + +#ifdef DEBUG +static void w83791d_print_debug(struct w83791d_data *data, struct device *dev) +{ + int i = 0, j = 0; + + dev_dbg(dev, "======Start of w83791d debug values======\n"); + dev_dbg(dev, "%d set of Voltages: ===>\n", NUMBER_OF_VIN); + for (i = 0; i < NUMBER_OF_VIN; i++) { + dev_dbg(dev, "vin[%d] is: 0x%02x\n", i, data->in[i]); + dev_dbg(dev, "vin[%d] min is: 0x%02x\n", i, data->in_min[i]); + dev_dbg(dev, "vin[%d] max is: 0x%02x\n", i, data->in_max[i]); + } + dev_dbg(dev, "%d set of Fan Counts/Divisors: ===>\n", NUMBER_OF_FANIN); + for (i = 0; i < NUMBER_OF_FANIN; i++) { + dev_dbg(dev, "fan[%d] is: 0x%02x\n", i, data->fan[i]); + dev_dbg(dev, "fan[%d] min is: 0x%02x\n", i, data->fan_min[i]); + dev_dbg(dev, "fan_div[%d] is: 0x%02x\n", i, data->fan_div[i]); + } + + /* + * temperature math is signed, but only print out the + * bits that matter + */ + dev_dbg(dev, "%d set of Temperatures: ===>\n", NUMBER_OF_TEMPIN); + for (i = 0; i < 3; i++) + dev_dbg(dev, "temp1[%d] is: 0x%02x\n", i, (u8) data->temp1[i]); + for (i = 0; i < 2; i++) { + for (j = 0; j < 3; j++) { + dev_dbg(dev, "temp_add[%d][%d] is: 0x%04x\n", i, j, + (u16) data->temp_add[i][j]); + } + } + + dev_dbg(dev, "Misc Information: ===>\n"); + dev_dbg(dev, "alarm is: 0x%08x\n", data->alarms); + dev_dbg(dev, "beep_mask is: 0x%08x\n", data->beep_mask); + dev_dbg(dev, "beep_enable is: %d\n", data->beep_enable); + dev_dbg(dev, "vid is: 0x%02x\n", data->vid); + dev_dbg(dev, "vrm is: 0x%02x\n", data->vrm); + dev_dbg(dev, "=======End of w83791d debug values========\n"); + dev_dbg(dev, "\n"); +} +#endif + +module_i2c_driver(w83791d_driver); + +MODULE_AUTHOR("Charles Spirakis "); +MODULE_DESCRIPTION("W83791D driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/w83792d.c b/drivers/hwmon/w83792d.c new file mode 100644 index 000000000..76aa39e53 --- /dev/null +++ b/drivers/hwmon/w83792d.c @@ -0,0 +1,1680 @@ +/* + * w83792d.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 2004, 2005 Winbond Electronics Corp. + * Shane Huang, + * Rudolf Marek + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Note: + * 1. This driver is only for 2.6 kernel, 2.4 kernel need a different driver. + * 2. This driver is only for Winbond W83792D C version device, there + * are also some motherboards with B version W83792D device. The + * calculation method to in6-in7(measured value, limits) is a little + * different between C and B version. C or B version can be identified + * by CR[0x49h]. + */ + +/* + * Supports following chips: + * + * Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA + * w83792d 9 7 7 3 0x7a 0x5ca3 yes no + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f, + I2C_CLIENT_END }; + +/* Insmod parameters */ + +static unsigned short force_subclients[4]; +module_param_array(force_subclients, short, NULL, 0); +MODULE_PARM_DESC(force_subclients, + "List of subclient addresses: {bus, clientaddr, subclientaddr1, subclientaddr2}"); + +static bool init; +module_param(init, bool, 0); +MODULE_PARM_DESC(init, "Set to one to force chip initialization"); + +/* The W83792D registers */ +static const u8 W83792D_REG_IN[9] = { + 0x20, /* Vcore A in DataSheet */ + 0x21, /* Vcore B in DataSheet */ + 0x22, /* VIN0 in DataSheet */ + 0x23, /* VIN1 in DataSheet */ + 0x24, /* VIN2 in DataSheet */ + 0x25, /* VIN3 in DataSheet */ + 0x26, /* 5VCC in DataSheet */ + 0xB0, /* 5VSB in DataSheet */ + 0xB1 /* VBAT in DataSheet */ +}; +#define W83792D_REG_LOW_BITS1 0x3E /* Low Bits I in DataSheet */ +#define W83792D_REG_LOW_BITS2 0x3F /* Low Bits II in DataSheet */ +static const u8 W83792D_REG_IN_MAX[9] = { + 0x2B, /* Vcore A High Limit in DataSheet */ + 0x2D, /* Vcore B High Limit in DataSheet */ + 0x2F, /* VIN0 High Limit in DataSheet */ + 0x31, /* VIN1 High Limit in DataSheet */ + 0x33, /* VIN2 High Limit in DataSheet */ + 0x35, /* VIN3 High Limit in DataSheet */ + 0x37, /* 5VCC High Limit in DataSheet */ + 0xB4, /* 5VSB High Limit in DataSheet */ + 0xB6 /* VBAT High Limit in DataSheet */ +}; +static const u8 W83792D_REG_IN_MIN[9] = { + 0x2C, /* Vcore A Low Limit in DataSheet */ + 0x2E, /* Vcore B Low Limit in DataSheet */ + 0x30, /* VIN0 Low Limit in DataSheet */ + 0x32, /* VIN1 Low Limit in DataSheet */ + 0x34, /* VIN2 Low Limit in DataSheet */ + 0x36, /* VIN3 Low Limit in DataSheet */ + 0x38, /* 5VCC Low Limit in DataSheet */ + 0xB5, /* 5VSB Low Limit in DataSheet */ + 0xB7 /* VBAT Low Limit in DataSheet */ +}; +static const u8 W83792D_REG_FAN[7] = { + 0x28, /* FAN 1 Count in DataSheet */ + 0x29, /* FAN 2 Count in DataSheet */ + 0x2A, /* FAN 3 Count in DataSheet */ + 0xB8, /* FAN 4 Count in DataSheet */ + 0xB9, /* FAN 5 Count in DataSheet */ + 0xBA, /* FAN 6 Count in DataSheet */ + 0xBE /* FAN 7 Count in DataSheet */ +}; +static const u8 W83792D_REG_FAN_MIN[7] = { + 0x3B, /* FAN 1 Count Low Limit in DataSheet */ + 0x3C, /* FAN 2 Count Low Limit in DataSheet */ + 0x3D, /* FAN 3 Count Low Limit in DataSheet */ + 0xBB, /* FAN 4 Count Low Limit in DataSheet */ + 0xBC, /* FAN 5 Count Low Limit in DataSheet */ + 0xBD, /* FAN 6 Count Low Limit in DataSheet */ + 0xBF /* FAN 7 Count Low Limit in DataSheet */ +}; +#define W83792D_REG_FAN_CFG 0x84 /* FAN Configuration in DataSheet */ +static const u8 W83792D_REG_FAN_DIV[4] = { + 0x47, /* contains FAN2 and FAN1 Divisor */ + 0x5B, /* contains FAN4 and FAN3 Divisor */ + 0x5C, /* contains FAN6 and FAN5 Divisor */ + 0x9E /* contains FAN7 Divisor. */ +}; +static const u8 W83792D_REG_PWM[7] = { + 0x81, /* FAN 1 Duty Cycle, be used to control */ + 0x83, /* FAN 2 Duty Cycle, be used to control */ + 0x94, /* FAN 3 Duty Cycle, be used to control */ + 0xA3, /* FAN 4 Duty Cycle, be used to control */ + 0xA4, /* FAN 5 Duty Cycle, be used to control */ + 0xA5, /* FAN 6 Duty Cycle, be used to control */ + 0xA6 /* FAN 7 Duty Cycle, be used to control */ +}; +#define W83792D_REG_BANK 0x4E +#define W83792D_REG_TEMP2_CONFIG 0xC2 +#define W83792D_REG_TEMP3_CONFIG 0xCA + +static const u8 W83792D_REG_TEMP1[3] = { + 0x27, /* TEMP 1 in DataSheet */ + 0x39, /* TEMP 1 Over in DataSheet */ + 0x3A, /* TEMP 1 Hyst in DataSheet */ +}; + +static const u8 W83792D_REG_TEMP_ADD[2][6] = { + { 0xC0, /* TEMP 2 in DataSheet */ + 0xC1, /* TEMP 2(0.5 deg) in DataSheet */ + 0xC5, /* TEMP 2 Over High part in DataSheet */ + 0xC6, /* TEMP 2 Over Low part in DataSheet */ + 0xC3, /* TEMP 2 Thyst High part in DataSheet */ + 0xC4 }, /* TEMP 2 Thyst Low part in DataSheet */ + { 0xC8, /* TEMP 3 in DataSheet */ + 0xC9, /* TEMP 3(0.5 deg) in DataSheet */ + 0xCD, /* TEMP 3 Over High part in DataSheet */ + 0xCE, /* TEMP 3 Over Low part in DataSheet */ + 0xCB, /* TEMP 3 Thyst High part in DataSheet */ + 0xCC } /* TEMP 3 Thyst Low part in DataSheet */ +}; + +static const u8 W83792D_REG_THERMAL[3] = { + 0x85, /* SmartFanI: Fan1 target value */ + 0x86, /* SmartFanI: Fan2 target value */ + 0x96 /* SmartFanI: Fan3 target value */ +}; + +static const u8 W83792D_REG_TOLERANCE[3] = { + 0x87, /* (bit3-0)SmartFan Fan1 tolerance */ + 0x87, /* (bit7-4)SmartFan Fan2 tolerance */ + 0x97 /* (bit3-0)SmartFan Fan3 tolerance */ +}; + +static const u8 W83792D_REG_POINTS[3][4] = { + { 0x85, /* SmartFanII: Fan1 temp point 1 */ + 0xE3, /* SmartFanII: Fan1 temp point 2 */ + 0xE4, /* SmartFanII: Fan1 temp point 3 */ + 0xE5 }, /* SmartFanII: Fan1 temp point 4 */ + { 0x86, /* SmartFanII: Fan2 temp point 1 */ + 0xE6, /* SmartFanII: Fan2 temp point 2 */ + 0xE7, /* SmartFanII: Fan2 temp point 3 */ + 0xE8 }, /* SmartFanII: Fan2 temp point 4 */ + { 0x96, /* SmartFanII: Fan3 temp point 1 */ + 0xE9, /* SmartFanII: Fan3 temp point 2 */ + 0xEA, /* SmartFanII: Fan3 temp point 3 */ + 0xEB } /* SmartFanII: Fan3 temp point 4 */ +}; + +static const u8 W83792D_REG_LEVELS[3][4] = { + { 0x88, /* (bit3-0) SmartFanII: Fan1 Non-Stop */ + 0x88, /* (bit7-4) SmartFanII: Fan1 Level 1 */ + 0xE0, /* (bit7-4) SmartFanII: Fan1 Level 2 */ + 0xE0 }, /* (bit3-0) SmartFanII: Fan1 Level 3 */ + { 0x89, /* (bit3-0) SmartFanII: Fan2 Non-Stop */ + 0x89, /* (bit7-4) SmartFanII: Fan2 Level 1 */ + 0xE1, /* (bit7-4) SmartFanII: Fan2 Level 2 */ + 0xE1 }, /* (bit3-0) SmartFanII: Fan2 Level 3 */ + { 0x98, /* (bit3-0) SmartFanII: Fan3 Non-Stop */ + 0x98, /* (bit7-4) SmartFanII: Fan3 Level 1 */ + 0xE2, /* (bit7-4) SmartFanII: Fan3 Level 2 */ + 0xE2 } /* (bit3-0) SmartFanII: Fan3 Level 3 */ +}; + +#define W83792D_REG_GPIO_EN 0x1A +#define W83792D_REG_CONFIG 0x40 +#define W83792D_REG_VID_FANDIV 0x47 +#define W83792D_REG_CHIPID 0x49 +#define W83792D_REG_WCHIPID 0x58 +#define W83792D_REG_CHIPMAN 0x4F +#define W83792D_REG_PIN 0x4B +#define W83792D_REG_I2C_SUBADDR 0x4A + +#define W83792D_REG_ALARM1 0xA9 /* realtime status register1 */ +#define W83792D_REG_ALARM2 0xAA /* realtime status register2 */ +#define W83792D_REG_ALARM3 0xAB /* realtime status register3 */ +#define W83792D_REG_CHASSIS 0x42 /* Bit 5: Case Open status bit */ +#define W83792D_REG_CHASSIS_CLR 0x44 /* Bit 7: Case Open CLR_CHS/Reset bit */ + +/* control in0/in1 's limit modifiability */ +#define W83792D_REG_VID_IN_B 0x17 + +#define W83792D_REG_VBAT 0x5D +#define W83792D_REG_I2C_ADDR 0x48 + +/* + * Conversions. Rounding and limit checking is only done on the TO_REG + * variants. Note that you should be a bit careful with which arguments + * these macros are called: arguments may be evaluated more than once. + * Fixing this is just not worth it. + */ +#define IN_FROM_REG(nr, val) (((nr) <= 1) ? ((val) * 2) : \ + ((((nr) == 6) || ((nr) == 7)) ? ((val) * 6) : ((val) * 4))) +#define IN_TO_REG(nr, val) (((nr) <= 1) ? ((val) / 2) : \ + ((((nr) == 6) || ((nr) == 7)) ? ((val) / 6) : ((val) / 4))) + +static inline u8 +FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 255; + rpm = clamp_val(rpm, 1, 1000000); + return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 254); +} + +#define FAN_FROM_REG(val, div) ((val) == 0 ? -1 : \ + ((val) == 255 ? 0 : \ + 1350000 / ((val) * (div)))) + +/* for temp1 */ +#define TEMP1_TO_REG(val) (clamp_val(((val) < 0 ? (val) + 0x100 * 1000 \ + : (val)) / 1000, 0, 0xff)) +#define TEMP1_FROM_REG(val) (((val) & 0x80 ? (val)-0x100 : (val)) * 1000) +/* for temp2 and temp3, because they need additional resolution */ +#define TEMP_ADD_FROM_REG(val1, val2) \ + ((((val1) & 0x80 ? (val1)-0x100 \ + : (val1)) * 1000) + ((val2 & 0x80) ? 500 : 0)) +#define TEMP_ADD_TO_REG_HIGH(val) \ + (clamp_val(((val) < 0 ? (val) + 0x100 * 1000 : (val)) / 1000, 0, 0xff)) +#define TEMP_ADD_TO_REG_LOW(val) ((val%1000) ? 0x80 : 0x00) + +#define DIV_FROM_REG(val) (1 << (val)) + +static inline u8 +DIV_TO_REG(long val) +{ + int i; + val = clamp_val(val, 1, 128) >> 1; + for (i = 0; i < 7; i++) { + if (val == 0) + break; + val >>= 1; + } + return (u8)i; +} + +struct w83792d_data { + struct device *hwmon_dev; + + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* array of 2 pointers to subclients */ + struct i2c_client *lm75[2]; + + u8 in[9]; /* Register value */ + u8 in_max[9]; /* Register value */ + u8 in_min[9]; /* Register value */ + u16 low_bits; /* Additional resolution to voltage in6-0 */ + u8 fan[7]; /* Register value */ + u8 fan_min[7]; /* Register value */ + u8 temp1[3]; /* current, over, thyst */ + u8 temp_add[2][6]; /* Register value */ + u8 fan_div[7]; /* Register encoding, shifted right */ + u8 pwm[7]; /* The 7 PWM outputs */ + u8 pwmenable[3]; + u32 alarms; /* realtime status register encoding,combined */ + u8 chassis; /* Chassis status */ + u8 thermal_cruise[3]; /* Smart FanI: Fan1,2,3 target value */ + u8 tolerance[3]; /* Fan1,2,3 tolerance(Smart Fan I/II) */ + u8 sf2_points[3][4]; /* Smart FanII: Fan1,2,3 temperature points */ + u8 sf2_levels[3][4]; /* Smart FanII: Fan1,2,3 duty cycle levels */ +}; + +static int w83792d_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int w83792d_detect(struct i2c_client *client, + struct i2c_board_info *info); +static int w83792d_remove(struct i2c_client *client); +static struct w83792d_data *w83792d_update_device(struct device *dev); + +#ifdef DEBUG +static void w83792d_print_debug(struct w83792d_data *data, struct device *dev); +#endif + +static void w83792d_init_client(struct i2c_client *client); + +static const struct i2c_device_id w83792d_id[] = { + { "w83792d", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, w83792d_id); + +static struct i2c_driver w83792d_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "w83792d", + }, + .probe = w83792d_probe, + .remove = w83792d_remove, + .id_table = w83792d_id, + .detect = w83792d_detect, + .address_list = normal_i2c, +}; + +static inline long in_count_from_reg(int nr, struct w83792d_data *data) +{ + /* in7 and in8 do not have low bits, but the formula still works */ + return (data->in[nr] << 2) | ((data->low_bits >> (2 * nr)) & 0x03); +} + +/* + * The SMBus locks itself. The Winbond W83792D chip has a bank register, + * but the driver only accesses registers in bank 0, so we don't have + * to switch banks and lock access between switches. + */ +static inline int w83792d_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static inline int +w83792d_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +/* following are the sysfs callback functions */ +static ssize_t show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct w83792d_data *data = w83792d_update_device(dev); + return sprintf(buf, "%ld\n", + IN_FROM_REG(nr, in_count_from_reg(nr, data))); +} + +#define show_in_reg(reg) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct sensor_device_attribute *sensor_attr \ + = to_sensor_dev_attr(attr); \ + int nr = sensor_attr->index; \ + struct w83792d_data *data = w83792d_update_device(dev); \ + return sprintf(buf, "%ld\n", \ + (long)(IN_FROM_REG(nr, data->reg[nr]) * 4)); \ +} + +show_in_reg(in_min); +show_in_reg(in_max); + +#define store_in_reg(REG, reg) \ +static ssize_t store_in_##reg(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct sensor_device_attribute *sensor_attr \ + = to_sensor_dev_attr(attr); \ + int nr = sensor_attr->index; \ + struct i2c_client *client = to_i2c_client(dev); \ + struct w83792d_data *data = i2c_get_clientdata(client); \ + unsigned long val; \ + int err = kstrtoul(buf, 10, &val); \ + if (err) \ + return err; \ + mutex_lock(&data->update_lock); \ + data->in_##reg[nr] = clamp_val(IN_TO_REG(nr, val) / 4, 0, 255); \ + w83792d_write_value(client, W83792D_REG_IN_##REG[nr], \ + data->in_##reg[nr]); \ + mutex_unlock(&data->update_lock); \ + \ + return count; \ +} +store_in_reg(MIN, min); +store_in_reg(MAX, max); + +#define show_fan_reg(reg) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct sensor_device_attribute *sensor_attr \ + = to_sensor_dev_attr(attr); \ + int nr = sensor_attr->index - 1; \ + struct w83792d_data *data = w83792d_update_device(dev); \ + return sprintf(buf, "%d\n", \ + FAN_FROM_REG(data->reg[nr], DIV_FROM_REG(data->fan_div[nr]))); \ +} + +show_fan_reg(fan); +show_fan_reg(fan_min); + +static ssize_t +store_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index - 1; + struct i2c_client *client = to_i2c_client(dev); + struct w83792d_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_min[nr] = FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); + w83792d_write_value(client, W83792D_REG_FAN_MIN[nr], + data->fan_min[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_fan_div(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct w83792d_data *data = w83792d_update_device(dev); + return sprintf(buf, "%u\n", DIV_FROM_REG(data->fan_div[nr - 1])); +} + +/* + * Note: we save and restore the fan minimum here, because its value is + * determined in part by the fan divisor. This follows the principle of + * least surprise; the user doesn't expect the fan minimum to change just + * because the divisor changed. + */ +static ssize_t +store_fan_div(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index - 1; + struct i2c_client *client = to_i2c_client(dev); + struct w83792d_data *data = i2c_get_clientdata(client); + unsigned long min; + /*u8 reg;*/ + u8 fan_div_reg = 0; + u8 tmp_fan_div; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + /* Save fan_min */ + mutex_lock(&data->update_lock); + min = FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr])); + + data->fan_div[nr] = DIV_TO_REG(val); + + fan_div_reg = w83792d_read_value(client, W83792D_REG_FAN_DIV[nr >> 1]); + fan_div_reg &= (nr & 0x01) ? 0x8f : 0xf8; + tmp_fan_div = (nr & 0x01) ? (((data->fan_div[nr]) << 4) & 0x70) + : ((data->fan_div[nr]) & 0x07); + w83792d_write_value(client, W83792D_REG_FAN_DIV[nr >> 1], + fan_div_reg | tmp_fan_div); + + /* Restore fan_min */ + data->fan_min[nr] = FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr])); + w83792d_write_value(client, W83792D_REG_FAN_MIN[nr], data->fan_min[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +/* read/write the temperature1, includes measured value and limits */ + +static ssize_t show_temp1(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct w83792d_data *data = w83792d_update_device(dev); + return sprintf(buf, "%d\n", TEMP1_FROM_REG(data->temp1[nr])); +} + +static ssize_t store_temp1(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct i2c_client *client = to_i2c_client(dev); + struct w83792d_data *data = i2c_get_clientdata(client); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp1[nr] = TEMP1_TO_REG(val); + w83792d_write_value(client, W83792D_REG_TEMP1[nr], + data->temp1[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +/* read/write the temperature2-3, includes measured value and limits */ + +static ssize_t show_temp23(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr + = to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83792d_data *data = w83792d_update_device(dev); + return sprintf(buf, "%ld\n", + (long)TEMP_ADD_FROM_REG(data->temp_add[nr][index], + data->temp_add[nr][index+1])); +} + +static ssize_t store_temp23(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr + = to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct i2c_client *client = to_i2c_client(dev); + struct w83792d_data *data = i2c_get_clientdata(client); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp_add[nr][index] = TEMP_ADD_TO_REG_HIGH(val); + data->temp_add[nr][index+1] = TEMP_ADD_TO_REG_LOW(val); + w83792d_write_value(client, W83792D_REG_TEMP_ADD[nr][index], + data->temp_add[nr][index]); + w83792d_write_value(client, W83792D_REG_TEMP_ADD[nr][index+1], + data->temp_add[nr][index+1]); + mutex_unlock(&data->update_lock); + + return count; +} + +/* get realtime status of all sensors items: voltage, temp, fan */ +static ssize_t +alarms_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83792d_data *data = w83792d_update_device(dev); + return sprintf(buf, "%d\n", data->alarms); +} + +static ssize_t show_alarm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct w83792d_data *data = w83792d_update_device(dev); + return sprintf(buf, "%d\n", (data->alarms >> nr) & 1); +} + +static ssize_t +show_pwm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct w83792d_data *data = w83792d_update_device(dev); + return sprintf(buf, "%d\n", (data->pwm[nr] & 0x0f) << 4); +} + +static ssize_t +show_pwmenable(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index - 1; + struct w83792d_data *data = w83792d_update_device(dev); + long pwm_enable_tmp = 1; + + switch (data->pwmenable[nr]) { + case 0: + pwm_enable_tmp = 1; /* manual mode */ + break; + case 1: + pwm_enable_tmp = 3; /*thermal cruise/Smart Fan I */ + break; + case 2: + pwm_enable_tmp = 2; /* Smart Fan II */ + break; + } + + return sprintf(buf, "%ld\n", pwm_enable_tmp); +} + +static ssize_t +store_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct i2c_client *client = to_i2c_client(dev); + struct w83792d_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + val = clamp_val(val, 0, 255) >> 4; + + mutex_lock(&data->update_lock); + val |= w83792d_read_value(client, W83792D_REG_PWM[nr]) & 0xf0; + data->pwm[nr] = val; + w83792d_write_value(client, W83792D_REG_PWM[nr], data->pwm[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +store_pwmenable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index - 1; + struct i2c_client *client = to_i2c_client(dev); + struct w83792d_data *data = i2c_get_clientdata(client); + u8 fan_cfg_tmp, cfg1_tmp, cfg2_tmp, cfg3_tmp, cfg4_tmp; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val < 1 || val > 3) + return -EINVAL; + + mutex_lock(&data->update_lock); + switch (val) { + case 1: + data->pwmenable[nr] = 0; /* manual mode */ + break; + case 2: + data->pwmenable[nr] = 2; /* Smart Fan II */ + break; + case 3: + data->pwmenable[nr] = 1; /* thermal cruise/Smart Fan I */ + break; + } + cfg1_tmp = data->pwmenable[0]; + cfg2_tmp = (data->pwmenable[1]) << 2; + cfg3_tmp = (data->pwmenable[2]) << 4; + cfg4_tmp = w83792d_read_value(client, W83792D_REG_FAN_CFG) & 0xc0; + fan_cfg_tmp = ((cfg4_tmp | cfg3_tmp) | cfg2_tmp) | cfg1_tmp; + w83792d_write_value(client, W83792D_REG_FAN_CFG, fan_cfg_tmp); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_pwm_mode(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct w83792d_data *data = w83792d_update_device(dev); + return sprintf(buf, "%d\n", data->pwm[nr] >> 7); +} + +static ssize_t +store_pwm_mode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct i2c_client *client = to_i2c_client(dev); + struct w83792d_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + if (val > 1) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->pwm[nr] = w83792d_read_value(client, W83792D_REG_PWM[nr]); + if (val) { /* PWM mode */ + data->pwm[nr] |= 0x80; + } else { /* DC mode */ + data->pwm[nr] &= 0x7f; + } + w83792d_write_value(client, W83792D_REG_PWM[nr], data->pwm[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +intrusion0_alarm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w83792d_data *data = w83792d_update_device(dev); + return sprintf(buf, "%d\n", data->chassis); +} + +static ssize_t +intrusion0_alarm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83792d_data *data = i2c_get_clientdata(client); + unsigned long val; + u8 reg; + + if (kstrtoul(buf, 10, &val) || val != 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + reg = w83792d_read_value(client, W83792D_REG_CHASSIS_CLR); + w83792d_write_value(client, W83792D_REG_CHASSIS_CLR, reg | 0x80); + data->valid = 0; /* Force cache refresh */ + mutex_unlock(&data->update_lock); + + return count; +} + +/* For Smart Fan I / Thermal Cruise */ +static ssize_t +show_thermal_cruise(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct w83792d_data *data = w83792d_update_device(dev); + return sprintf(buf, "%ld\n", (long)data->thermal_cruise[nr-1]); +} + +static ssize_t +store_thermal_cruise(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index - 1; + struct i2c_client *client = to_i2c_client(dev); + struct w83792d_data *data = i2c_get_clientdata(client); + u8 target_tmp = 0, target_mask = 0; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + target_tmp = val; + target_tmp = target_tmp & 0x7f; + mutex_lock(&data->update_lock); + target_mask = w83792d_read_value(client, + W83792D_REG_THERMAL[nr]) & 0x80; + data->thermal_cruise[nr] = clamp_val(target_tmp, 0, 255); + w83792d_write_value(client, W83792D_REG_THERMAL[nr], + (data->thermal_cruise[nr]) | target_mask); + mutex_unlock(&data->update_lock); + + return count; +} + +/* For Smart Fan I/Thermal Cruise and Smart Fan II */ +static ssize_t +show_tolerance(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + struct w83792d_data *data = w83792d_update_device(dev); + return sprintf(buf, "%ld\n", (long)data->tolerance[nr-1]); +} + +static ssize_t +store_tolerance(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index - 1; + struct i2c_client *client = to_i2c_client(dev); + struct w83792d_data *data = i2c_get_clientdata(client); + u8 tol_tmp, tol_mask; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + tol_mask = w83792d_read_value(client, + W83792D_REG_TOLERANCE[nr]) & ((nr == 1) ? 0x0f : 0xf0); + tol_tmp = clamp_val(val, 0, 15); + tol_tmp &= 0x0f; + data->tolerance[nr] = tol_tmp; + if (nr == 1) + tol_tmp <<= 4; + w83792d_write_value(client, W83792D_REG_TOLERANCE[nr], + tol_mask | tol_tmp); + mutex_unlock(&data->update_lock); + + return count; +} + +/* For Smart Fan II */ +static ssize_t +show_sf2_point(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr + = to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83792d_data *data = w83792d_update_device(dev); + return sprintf(buf, "%ld\n", (long)data->sf2_points[index-1][nr-1]); +} + +static ssize_t +store_sf2_point(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr + = to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr - 1; + int index = sensor_attr->index - 1; + struct i2c_client *client = to_i2c_client(dev); + struct w83792d_data *data = i2c_get_clientdata(client); + u8 mask_tmp = 0; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->sf2_points[index][nr] = clamp_val(val, 0, 127); + mask_tmp = w83792d_read_value(client, + W83792D_REG_POINTS[index][nr]) & 0x80; + w83792d_write_value(client, W83792D_REG_POINTS[index][nr], + mask_tmp|data->sf2_points[index][nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_sf2_level(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr + = to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83792d_data *data = w83792d_update_device(dev); + return sprintf(buf, "%d\n", + (((data->sf2_levels[index-1][nr]) * 100) / 15)); +} + +static ssize_t +store_sf2_level(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr + = to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index - 1; + struct i2c_client *client = to_i2c_client(dev); + struct w83792d_data *data = i2c_get_clientdata(client); + u8 mask_tmp = 0, level_tmp = 0; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->sf2_levels[index][nr] = clamp_val((val * 15) / 100, 0, 15); + mask_tmp = w83792d_read_value(client, W83792D_REG_LEVELS[index][nr]) + & ((nr == 3) ? 0xf0 : 0x0f); + if (nr == 3) + level_tmp = data->sf2_levels[index][nr]; + else + level_tmp = data->sf2_levels[index][nr] << 4; + w83792d_write_value(client, W83792D_REG_LEVELS[index][nr], + level_tmp | mask_tmp); + mutex_unlock(&data->update_lock); + + return count; +} + + +static int +w83792d_detect_subclients(struct i2c_client *new_client) +{ + int i, id, err; + int address = new_client->addr; + u8 val; + struct i2c_adapter *adapter = new_client->adapter; + struct w83792d_data *data = i2c_get_clientdata(new_client); + + id = i2c_adapter_id(adapter); + if (force_subclients[0] == id && force_subclients[1] == address) { + for (i = 2; i <= 3; i++) { + if (force_subclients[i] < 0x48 || + force_subclients[i] > 0x4f) { + dev_err(&new_client->dev, + "invalid subclient address %d; must be 0x48-0x4f\n", + force_subclients[i]); + err = -ENODEV; + goto ERROR_SC_0; + } + } + w83792d_write_value(new_client, W83792D_REG_I2C_SUBADDR, + (force_subclients[2] & 0x07) | + ((force_subclients[3] & 0x07) << 4)); + } + + val = w83792d_read_value(new_client, W83792D_REG_I2C_SUBADDR); + if (!(val & 0x08)) + data->lm75[0] = i2c_new_dummy(adapter, 0x48 + (val & 0x7)); + if (!(val & 0x80)) { + if ((data->lm75[0] != NULL) && + ((val & 0x7) == ((val >> 4) & 0x7))) { + dev_err(&new_client->dev, + "duplicate addresses 0x%x, use force_subclient\n", + data->lm75[0]->addr); + err = -ENODEV; + goto ERROR_SC_1; + } + data->lm75[1] = i2c_new_dummy(adapter, + 0x48 + ((val >> 4) & 0x7)); + } + + return 0; + +/* Undo inits in case of errors */ + +ERROR_SC_1: + i2c_unregister_device(data->lm75[0]); +ERROR_SC_0: + return err; +} + +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_in, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_in, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_in, NULL, 4); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, show_in, NULL, 5); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, show_in, NULL, 6); +static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, show_in, NULL, 7); +static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, show_in, NULL, 8); +static SENSOR_DEVICE_ATTR(in0_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 0); +static SENSOR_DEVICE_ATTR(in1_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 1); +static SENSOR_DEVICE_ATTR(in2_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 2); +static SENSOR_DEVICE_ATTR(in3_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 3); +static SENSOR_DEVICE_ATTR(in4_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 4); +static SENSOR_DEVICE_ATTR(in5_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 5); +static SENSOR_DEVICE_ATTR(in6_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 6); +static SENSOR_DEVICE_ATTR(in7_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 7); +static SENSOR_DEVICE_ATTR(in8_min, S_IWUSR | S_IRUGO, + show_in_min, store_in_min, 8); +static SENSOR_DEVICE_ATTR(in0_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 0); +static SENSOR_DEVICE_ATTR(in1_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 1); +static SENSOR_DEVICE_ATTR(in2_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 2); +static SENSOR_DEVICE_ATTR(in3_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 3); +static SENSOR_DEVICE_ATTR(in4_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 4); +static SENSOR_DEVICE_ATTR(in5_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 5); +static SENSOR_DEVICE_ATTR(in6_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 6); +static SENSOR_DEVICE_ATTR(in7_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 7); +static SENSOR_DEVICE_ATTR(in8_max, S_IWUSR | S_IRUGO, + show_in_max, store_in_max, 8); +static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp1, NULL, 0, 0); +static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp23, NULL, 0, 0); +static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp23, NULL, 1, 0); +static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, + show_temp1, store_temp1, 0, 1); +static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp23, + store_temp23, 0, 2); +static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp23, + store_temp23, 1, 2); +static SENSOR_DEVICE_ATTR_2(temp1_max_hyst, S_IRUGO | S_IWUSR, + show_temp1, store_temp1, 0, 2); +static SENSOR_DEVICE_ATTR_2(temp2_max_hyst, S_IRUGO | S_IWUSR, + show_temp23, store_temp23, 0, 4); +static SENSOR_DEVICE_ATTR_2(temp3_max_hyst, S_IRUGO | S_IWUSR, + show_temp23, store_temp23, 1, 4); +static DEVICE_ATTR_RO(alarms); +static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 8); +static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 9); +static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 10); +static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 11); +static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 12); +static SENSOR_DEVICE_ATTR(fan7_alarm, S_IRUGO, show_alarm, NULL, 15); +static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 19); +static SENSOR_DEVICE_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 20); +static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO, show_alarm, NULL, 21); +static SENSOR_DEVICE_ATTR(fan5_alarm, S_IRUGO, show_alarm, NULL, 22); +static SENSOR_DEVICE_ATTR(fan6_alarm, S_IRUGO, show_alarm, NULL, 23); +static DEVICE_ATTR_RW(intrusion0_alarm); +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 2); +static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 3); +static SENSOR_DEVICE_ATTR(pwm5, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 4); +static SENSOR_DEVICE_ATTR(pwm6, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 5); +static SENSOR_DEVICE_ATTR(pwm7, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 6); +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + show_pwmenable, store_pwmenable, 1); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, + show_pwmenable, store_pwmenable, 2); +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, + show_pwmenable, store_pwmenable, 3); +static SENSOR_DEVICE_ATTR(pwm1_mode, S_IWUSR | S_IRUGO, + show_pwm_mode, store_pwm_mode, 0); +static SENSOR_DEVICE_ATTR(pwm2_mode, S_IWUSR | S_IRUGO, + show_pwm_mode, store_pwm_mode, 1); +static SENSOR_DEVICE_ATTR(pwm3_mode, S_IWUSR | S_IRUGO, + show_pwm_mode, store_pwm_mode, 2); +static SENSOR_DEVICE_ATTR(pwm4_mode, S_IWUSR | S_IRUGO, + show_pwm_mode, store_pwm_mode, 3); +static SENSOR_DEVICE_ATTR(pwm5_mode, S_IWUSR | S_IRUGO, + show_pwm_mode, store_pwm_mode, 4); +static SENSOR_DEVICE_ATTR(pwm6_mode, S_IWUSR | S_IRUGO, + show_pwm_mode, store_pwm_mode, 5); +static SENSOR_DEVICE_ATTR(pwm7_mode, S_IWUSR | S_IRUGO, + show_pwm_mode, store_pwm_mode, 6); +static SENSOR_DEVICE_ATTR(tolerance1, S_IWUSR | S_IRUGO, + show_tolerance, store_tolerance, 1); +static SENSOR_DEVICE_ATTR(tolerance2, S_IWUSR | S_IRUGO, + show_tolerance, store_tolerance, 2); +static SENSOR_DEVICE_ATTR(tolerance3, S_IWUSR | S_IRUGO, + show_tolerance, store_tolerance, 3); +static SENSOR_DEVICE_ATTR(thermal_cruise1, S_IWUSR | S_IRUGO, + show_thermal_cruise, store_thermal_cruise, 1); +static SENSOR_DEVICE_ATTR(thermal_cruise2, S_IWUSR | S_IRUGO, + show_thermal_cruise, store_thermal_cruise, 2); +static SENSOR_DEVICE_ATTR(thermal_cruise3, S_IWUSR | S_IRUGO, + show_thermal_cruise, store_thermal_cruise, 3); +static SENSOR_DEVICE_ATTR_2(sf2_point1_fan1, S_IRUGO | S_IWUSR, + show_sf2_point, store_sf2_point, 1, 1); +static SENSOR_DEVICE_ATTR_2(sf2_point2_fan1, S_IRUGO | S_IWUSR, + show_sf2_point, store_sf2_point, 2, 1); +static SENSOR_DEVICE_ATTR_2(sf2_point3_fan1, S_IRUGO | S_IWUSR, + show_sf2_point, store_sf2_point, 3, 1); +static SENSOR_DEVICE_ATTR_2(sf2_point4_fan1, S_IRUGO | S_IWUSR, + show_sf2_point, store_sf2_point, 4, 1); +static SENSOR_DEVICE_ATTR_2(sf2_point1_fan2, S_IRUGO | S_IWUSR, + show_sf2_point, store_sf2_point, 1, 2); +static SENSOR_DEVICE_ATTR_2(sf2_point2_fan2, S_IRUGO | S_IWUSR, + show_sf2_point, store_sf2_point, 2, 2); +static SENSOR_DEVICE_ATTR_2(sf2_point3_fan2, S_IRUGO | S_IWUSR, + show_sf2_point, store_sf2_point, 3, 2); +static SENSOR_DEVICE_ATTR_2(sf2_point4_fan2, S_IRUGO | S_IWUSR, + show_sf2_point, store_sf2_point, 4, 2); +static SENSOR_DEVICE_ATTR_2(sf2_point1_fan3, S_IRUGO | S_IWUSR, + show_sf2_point, store_sf2_point, 1, 3); +static SENSOR_DEVICE_ATTR_2(sf2_point2_fan3, S_IRUGO | S_IWUSR, + show_sf2_point, store_sf2_point, 2, 3); +static SENSOR_DEVICE_ATTR_2(sf2_point3_fan3, S_IRUGO | S_IWUSR, + show_sf2_point, store_sf2_point, 3, 3); +static SENSOR_DEVICE_ATTR_2(sf2_point4_fan3, S_IRUGO | S_IWUSR, + show_sf2_point, store_sf2_point, 4, 3); +static SENSOR_DEVICE_ATTR_2(sf2_level1_fan1, S_IRUGO | S_IWUSR, + show_sf2_level, store_sf2_level, 1, 1); +static SENSOR_DEVICE_ATTR_2(sf2_level2_fan1, S_IRUGO | S_IWUSR, + show_sf2_level, store_sf2_level, 2, 1); +static SENSOR_DEVICE_ATTR_2(sf2_level3_fan1, S_IRUGO | S_IWUSR, + show_sf2_level, store_sf2_level, 3, 1); +static SENSOR_DEVICE_ATTR_2(sf2_level1_fan2, S_IRUGO | S_IWUSR, + show_sf2_level, store_sf2_level, 1, 2); +static SENSOR_DEVICE_ATTR_2(sf2_level2_fan2, S_IRUGO | S_IWUSR, + show_sf2_level, store_sf2_level, 2, 2); +static SENSOR_DEVICE_ATTR_2(sf2_level3_fan2, S_IRUGO | S_IWUSR, + show_sf2_level, store_sf2_level, 3, 2); +static SENSOR_DEVICE_ATTR_2(sf2_level1_fan3, S_IRUGO | S_IWUSR, + show_sf2_level, store_sf2_level, 1, 3); +static SENSOR_DEVICE_ATTR_2(sf2_level2_fan3, S_IRUGO | S_IWUSR, + show_sf2_level, store_sf2_level, 2, 3); +static SENSOR_DEVICE_ATTR_2(sf2_level3_fan3, S_IRUGO | S_IWUSR, + show_sf2_level, store_sf2_level, 3, 3); +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 1); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 2); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 3); +static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 4); +static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, show_fan, NULL, 5); +static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, show_fan, NULL, 6); +static SENSOR_DEVICE_ATTR(fan7_input, S_IRUGO, show_fan, NULL, 7); +static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 1); +static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 2); +static SENSOR_DEVICE_ATTR(fan3_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 3); +static SENSOR_DEVICE_ATTR(fan4_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 4); +static SENSOR_DEVICE_ATTR(fan5_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 5); +static SENSOR_DEVICE_ATTR(fan6_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 6); +static SENSOR_DEVICE_ATTR(fan7_min, S_IWUSR | S_IRUGO, + show_fan_min, store_fan_min, 7); +static SENSOR_DEVICE_ATTR(fan1_div, S_IWUSR | S_IRUGO, + show_fan_div, store_fan_div, 1); +static SENSOR_DEVICE_ATTR(fan2_div, S_IWUSR | S_IRUGO, + show_fan_div, store_fan_div, 2); +static SENSOR_DEVICE_ATTR(fan3_div, S_IWUSR | S_IRUGO, + show_fan_div, store_fan_div, 3); +static SENSOR_DEVICE_ATTR(fan4_div, S_IWUSR | S_IRUGO, + show_fan_div, store_fan_div, 4); +static SENSOR_DEVICE_ATTR(fan5_div, S_IWUSR | S_IRUGO, + show_fan_div, store_fan_div, 5); +static SENSOR_DEVICE_ATTR(fan6_div, S_IWUSR | S_IRUGO, + show_fan_div, store_fan_div, 6); +static SENSOR_DEVICE_ATTR(fan7_div, S_IWUSR | S_IRUGO, + show_fan_div, store_fan_div, 7); + +static struct attribute *w83792d_attributes_fan[4][7] = { + { + &sensor_dev_attr_fan4_input.dev_attr.attr, + &sensor_dev_attr_fan4_min.dev_attr.attr, + &sensor_dev_attr_fan4_div.dev_attr.attr, + &sensor_dev_attr_fan4_alarm.dev_attr.attr, + &sensor_dev_attr_pwm4.dev_attr.attr, + &sensor_dev_attr_pwm4_mode.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan5_input.dev_attr.attr, + &sensor_dev_attr_fan5_min.dev_attr.attr, + &sensor_dev_attr_fan5_div.dev_attr.attr, + &sensor_dev_attr_fan5_alarm.dev_attr.attr, + &sensor_dev_attr_pwm5.dev_attr.attr, + &sensor_dev_attr_pwm5_mode.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan6_input.dev_attr.attr, + &sensor_dev_attr_fan6_min.dev_attr.attr, + &sensor_dev_attr_fan6_div.dev_attr.attr, + &sensor_dev_attr_fan6_alarm.dev_attr.attr, + &sensor_dev_attr_pwm6.dev_attr.attr, + &sensor_dev_attr_pwm6_mode.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_fan7_input.dev_attr.attr, + &sensor_dev_attr_fan7_min.dev_attr.attr, + &sensor_dev_attr_fan7_div.dev_attr.attr, + &sensor_dev_attr_fan7_alarm.dev_attr.attr, + &sensor_dev_attr_pwm7.dev_attr.attr, + &sensor_dev_attr_pwm7_mode.dev_attr.attr, + NULL + } +}; + +static const struct attribute_group w83792d_group_fan[4] = { + { .attrs = w83792d_attributes_fan[0] }, + { .attrs = w83792d_attributes_fan[1] }, + { .attrs = w83792d_attributes_fan[2] }, + { .attrs = w83792d_attributes_fan[3] }, +}; + +static struct attribute *w83792d_attributes[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in8_max.dev_attr.attr, + &sensor_dev_attr_in8_min.dev_attr.attr, + &sensor_dev_attr_in0_alarm.dev_attr.attr, + &sensor_dev_attr_in1_alarm.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + &sensor_dev_attr_in3_alarm.dev_attr.attr, + &sensor_dev_attr_in4_alarm.dev_attr.attr, + &sensor_dev_attr_in5_alarm.dev_attr.attr, + &sensor_dev_attr_in6_alarm.dev_attr.attr, + &sensor_dev_attr_in7_alarm.dev_attr.attr, + &sensor_dev_attr_in8_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_alarm.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_mode.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm2_mode.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm3_mode.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &dev_attr_alarms.attr, + &dev_attr_intrusion0_alarm.attr, + &sensor_dev_attr_tolerance1.dev_attr.attr, + &sensor_dev_attr_thermal_cruise1.dev_attr.attr, + &sensor_dev_attr_tolerance2.dev_attr.attr, + &sensor_dev_attr_thermal_cruise2.dev_attr.attr, + &sensor_dev_attr_tolerance3.dev_attr.attr, + &sensor_dev_attr_thermal_cruise3.dev_attr.attr, + &sensor_dev_attr_sf2_point1_fan1.dev_attr.attr, + &sensor_dev_attr_sf2_point2_fan1.dev_attr.attr, + &sensor_dev_attr_sf2_point3_fan1.dev_attr.attr, + &sensor_dev_attr_sf2_point4_fan1.dev_attr.attr, + &sensor_dev_attr_sf2_point1_fan2.dev_attr.attr, + &sensor_dev_attr_sf2_point2_fan2.dev_attr.attr, + &sensor_dev_attr_sf2_point3_fan2.dev_attr.attr, + &sensor_dev_attr_sf2_point4_fan2.dev_attr.attr, + &sensor_dev_attr_sf2_point1_fan3.dev_attr.attr, + &sensor_dev_attr_sf2_point2_fan3.dev_attr.attr, + &sensor_dev_attr_sf2_point3_fan3.dev_attr.attr, + &sensor_dev_attr_sf2_point4_fan3.dev_attr.attr, + &sensor_dev_attr_sf2_level1_fan1.dev_attr.attr, + &sensor_dev_attr_sf2_level2_fan1.dev_attr.attr, + &sensor_dev_attr_sf2_level3_fan1.dev_attr.attr, + &sensor_dev_attr_sf2_level1_fan2.dev_attr.attr, + &sensor_dev_attr_sf2_level2_fan2.dev_attr.attr, + &sensor_dev_attr_sf2_level3_fan2.dev_attr.attr, + &sensor_dev_attr_sf2_level1_fan3.dev_attr.attr, + &sensor_dev_attr_sf2_level2_fan3.dev_attr.attr, + &sensor_dev_attr_sf2_level3_fan3.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan2_div.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + &sensor_dev_attr_fan3_min.dev_attr.attr, + &sensor_dev_attr_fan3_div.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group w83792d_group = { + .attrs = w83792d_attributes, +}; + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int +w83792d_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int val1, val2; + unsigned short address = client->addr; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + if (w83792d_read_value(client, W83792D_REG_CONFIG) & 0x80) + return -ENODEV; + + val1 = w83792d_read_value(client, W83792D_REG_BANK); + val2 = w83792d_read_value(client, W83792D_REG_CHIPMAN); + /* Check for Winbond ID if in bank 0 */ + if (!(val1 & 0x07)) { /* is Bank0 */ + if ((!(val1 & 0x80) && val2 != 0xa3) || + ((val1 & 0x80) && val2 != 0x5c)) + return -ENODEV; + } + /* + * If Winbond chip, address of chip and W83792D_REG_I2C_ADDR + * should match + */ + if (w83792d_read_value(client, W83792D_REG_I2C_ADDR) != address) + return -ENODEV; + + /* Put it now into bank 0 and Vendor ID High Byte */ + w83792d_write_value(client, + W83792D_REG_BANK, + (w83792d_read_value(client, + W83792D_REG_BANK) & 0x78) | 0x80); + + /* Determine the chip type. */ + val1 = w83792d_read_value(client, W83792D_REG_WCHIPID); + val2 = w83792d_read_value(client, W83792D_REG_CHIPMAN); + if (val1 != 0x7a || val2 != 0x5c) + return -ENODEV; + + strlcpy(info->type, "w83792d", I2C_NAME_SIZE); + + return 0; +} + +static int +w83792d_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct w83792d_data *data; + struct device *dev = &client->dev; + int i, val1, err; + + data = devm_kzalloc(dev, sizeof(struct w83792d_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + err = w83792d_detect_subclients(client); + if (err) + return err; + + /* Initialize the chip */ + w83792d_init_client(client); + + /* A few vars need to be filled upon startup */ + for (i = 0; i < 7; i++) { + data->fan_min[i] = w83792d_read_value(client, + W83792D_REG_FAN_MIN[i]); + } + + /* Register sysfs hooks */ + err = sysfs_create_group(&dev->kobj, &w83792d_group); + if (err) + goto exit_i2c_unregister; + + /* + * Read GPIO enable register to check if pins for fan 4,5 are used as + * GPIO + */ + val1 = w83792d_read_value(client, W83792D_REG_GPIO_EN); + + if (!(val1 & 0x40)) { + err = sysfs_create_group(&dev->kobj, &w83792d_group_fan[0]); + if (err) + goto exit_remove_files; + } + + if (!(val1 & 0x20)) { + err = sysfs_create_group(&dev->kobj, &w83792d_group_fan[1]); + if (err) + goto exit_remove_files; + } + + val1 = w83792d_read_value(client, W83792D_REG_PIN); + if (val1 & 0x40) { + err = sysfs_create_group(&dev->kobj, &w83792d_group_fan[2]); + if (err) + goto exit_remove_files; + } + + if (val1 & 0x04) { + err = sysfs_create_group(&dev->kobj, &w83792d_group_fan[3]); + if (err) + goto exit_remove_files; + } + + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_files; + } + + return 0; + +exit_remove_files: + sysfs_remove_group(&dev->kobj, &w83792d_group); + for (i = 0; i < ARRAY_SIZE(w83792d_group_fan); i++) + sysfs_remove_group(&dev->kobj, &w83792d_group_fan[i]); +exit_i2c_unregister: + i2c_unregister_device(data->lm75[0]); + i2c_unregister_device(data->lm75[1]); + return err; +} + +static int +w83792d_remove(struct i2c_client *client) +{ + struct w83792d_data *data = i2c_get_clientdata(client); + int i; + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &w83792d_group); + for (i = 0; i < ARRAY_SIZE(w83792d_group_fan); i++) + sysfs_remove_group(&client->dev.kobj, + &w83792d_group_fan[i]); + + i2c_unregister_device(data->lm75[0]); + i2c_unregister_device(data->lm75[1]); + + return 0; +} + +static void +w83792d_init_client(struct i2c_client *client) +{ + u8 temp2_cfg, temp3_cfg, vid_in_b; + + if (init) + w83792d_write_value(client, W83792D_REG_CONFIG, 0x80); + + /* + * Clear the bit6 of W83792D_REG_VID_IN_B(set it into 0): + * W83792D_REG_VID_IN_B bit6 = 0: the high/low limit of + * vin0/vin1 can be modified by user; + * W83792D_REG_VID_IN_B bit6 = 1: the high/low limit of + * vin0/vin1 auto-updated, can NOT be modified by user. + */ + vid_in_b = w83792d_read_value(client, W83792D_REG_VID_IN_B); + w83792d_write_value(client, W83792D_REG_VID_IN_B, + vid_in_b & 0xbf); + + temp2_cfg = w83792d_read_value(client, W83792D_REG_TEMP2_CONFIG); + temp3_cfg = w83792d_read_value(client, W83792D_REG_TEMP3_CONFIG); + w83792d_write_value(client, W83792D_REG_TEMP2_CONFIG, + temp2_cfg & 0xe6); + w83792d_write_value(client, W83792D_REG_TEMP3_CONFIG, + temp3_cfg & 0xe6); + + /* Start monitoring */ + w83792d_write_value(client, W83792D_REG_CONFIG, + (w83792d_read_value(client, + W83792D_REG_CONFIG) & 0xf7) + | 0x01); +} + +static struct w83792d_data *w83792d_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83792d_data *data = i2c_get_clientdata(client); + int i, j; + u8 reg_array_tmp[4], reg_tmp; + + mutex_lock(&data->update_lock); + + if (time_after + (jiffies - data->last_updated, (unsigned long) (HZ * 3)) + || time_before(jiffies, data->last_updated) || !data->valid) { + dev_dbg(dev, "Starting device update\n"); + + /* Update the voltages measured value and limits */ + for (i = 0; i < 9; i++) { + data->in[i] = w83792d_read_value(client, + W83792D_REG_IN[i]); + data->in_max[i] = w83792d_read_value(client, + W83792D_REG_IN_MAX[i]); + data->in_min[i] = w83792d_read_value(client, + W83792D_REG_IN_MIN[i]); + } + data->low_bits = w83792d_read_value(client, + W83792D_REG_LOW_BITS1) + + (w83792d_read_value(client, + W83792D_REG_LOW_BITS2) << 8); + for (i = 0; i < 7; i++) { + /* Update the Fan measured value and limits */ + data->fan[i] = w83792d_read_value(client, + W83792D_REG_FAN[i]); + data->fan_min[i] = w83792d_read_value(client, + W83792D_REG_FAN_MIN[i]); + /* Update the PWM/DC Value and PWM/DC flag */ + data->pwm[i] = w83792d_read_value(client, + W83792D_REG_PWM[i]); + } + + reg_tmp = w83792d_read_value(client, W83792D_REG_FAN_CFG); + data->pwmenable[0] = reg_tmp & 0x03; + data->pwmenable[1] = (reg_tmp>>2) & 0x03; + data->pwmenable[2] = (reg_tmp>>4) & 0x03; + + for (i = 0; i < 3; i++) { + data->temp1[i] = w83792d_read_value(client, + W83792D_REG_TEMP1[i]); + } + for (i = 0; i < 2; i++) { + for (j = 0; j < 6; j++) { + data->temp_add[i][j] = w83792d_read_value( + client, W83792D_REG_TEMP_ADD[i][j]); + } + } + + /* Update the Fan Divisor */ + for (i = 0; i < 4; i++) { + reg_array_tmp[i] = w83792d_read_value(client, + W83792D_REG_FAN_DIV[i]); + } + data->fan_div[0] = reg_array_tmp[0] & 0x07; + data->fan_div[1] = (reg_array_tmp[0] >> 4) & 0x07; + data->fan_div[2] = reg_array_tmp[1] & 0x07; + data->fan_div[3] = (reg_array_tmp[1] >> 4) & 0x07; + data->fan_div[4] = reg_array_tmp[2] & 0x07; + data->fan_div[5] = (reg_array_tmp[2] >> 4) & 0x07; + data->fan_div[6] = reg_array_tmp[3] & 0x07; + + /* Update the realtime status */ + data->alarms = w83792d_read_value(client, W83792D_REG_ALARM1) + + (w83792d_read_value(client, W83792D_REG_ALARM2) << 8) + + (w83792d_read_value(client, W83792D_REG_ALARM3) << 16); + + /* Update CaseOpen status and it's CLR_CHS. */ + data->chassis = (w83792d_read_value(client, + W83792D_REG_CHASSIS) >> 5) & 0x01; + + /* Update Thermal Cruise/Smart Fan I target value */ + for (i = 0; i < 3; i++) { + data->thermal_cruise[i] = + w83792d_read_value(client, + W83792D_REG_THERMAL[i]) & 0x7f; + } + + /* Update Smart Fan I/II tolerance */ + reg_tmp = w83792d_read_value(client, W83792D_REG_TOLERANCE[0]); + data->tolerance[0] = reg_tmp & 0x0f; + data->tolerance[1] = (reg_tmp >> 4) & 0x0f; + data->tolerance[2] = w83792d_read_value(client, + W83792D_REG_TOLERANCE[2]) & 0x0f; + + /* Update Smart Fan II temperature points */ + for (i = 0; i < 3; i++) { + for (j = 0; j < 4; j++) { + data->sf2_points[i][j] + = w83792d_read_value(client, + W83792D_REG_POINTS[i][j]) & 0x7f; + } + } + + /* Update Smart Fan II duty cycle levels */ + for (i = 0; i < 3; i++) { + reg_tmp = w83792d_read_value(client, + W83792D_REG_LEVELS[i][0]); + data->sf2_levels[i][0] = reg_tmp & 0x0f; + data->sf2_levels[i][1] = (reg_tmp >> 4) & 0x0f; + reg_tmp = w83792d_read_value(client, + W83792D_REG_LEVELS[i][2]); + data->sf2_levels[i][2] = (reg_tmp >> 4) & 0x0f; + data->sf2_levels[i][3] = reg_tmp & 0x0f; + } + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + +#ifdef DEBUG + w83792d_print_debug(data, dev); +#endif + + return data; +} + +#ifdef DEBUG +static void w83792d_print_debug(struct w83792d_data *data, struct device *dev) +{ + int i = 0, j = 0; + dev_dbg(dev, "==========The following is the debug message...========\n"); + dev_dbg(dev, "9 set of Voltages: =====>\n"); + for (i = 0; i < 9; i++) { + dev_dbg(dev, "vin[%d] is: 0x%x\n", i, data->in[i]); + dev_dbg(dev, "vin[%d] max is: 0x%x\n", i, data->in_max[i]); + dev_dbg(dev, "vin[%d] min is: 0x%x\n", i, data->in_min[i]); + } + dev_dbg(dev, "Low Bit1 is: 0x%x\n", data->low_bits & 0xff); + dev_dbg(dev, "Low Bit2 is: 0x%x\n", data->low_bits >> 8); + dev_dbg(dev, "7 set of Fan Counts and Duty Cycles: =====>\n"); + for (i = 0; i < 7; i++) { + dev_dbg(dev, "fan[%d] is: 0x%x\n", i, data->fan[i]); + dev_dbg(dev, "fan[%d] min is: 0x%x\n", i, data->fan_min[i]); + dev_dbg(dev, "pwm[%d] is: 0x%x\n", i, data->pwm[i]); + } + dev_dbg(dev, "3 set of Temperatures: =====>\n"); + for (i = 0; i < 3; i++) + dev_dbg(dev, "temp1[%d] is: 0x%x\n", i, data->temp1[i]); + + for (i = 0; i < 2; i++) { + for (j = 0; j < 6; j++) { + dev_dbg(dev, "temp_add[%d][%d] is: 0x%x\n", i, j, + data->temp_add[i][j]); + } + } + + for (i = 0; i < 7; i++) + dev_dbg(dev, "fan_div[%d] is: 0x%x\n", i, data->fan_div[i]); + + dev_dbg(dev, "==========End of the debug message...================\n"); + dev_dbg(dev, "\n"); +} +#endif + +module_i2c_driver(w83792d_driver); + +MODULE_AUTHOR("Shane Huang (Winbond)"); +MODULE_DESCRIPTION("W83792AD/D driver for linux-2.6"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/w83793.c b/drivers/hwmon/w83793.c new file mode 100644 index 000000000..0af0f6283 --- /dev/null +++ b/drivers/hwmon/w83793.c @@ -0,0 +1,2178 @@ +/* + * w83793.c - Linux kernel driver for hardware monitoring + * Copyright (C) 2006 Winbond Electronics Corp. + * Yuan Mu + * Rudolf Marek + * Copyright (C) 2009-2010 Sven Anders , ANDURAS AG. + * Watchdog driver part + * (Based partially on fschmd driver, + * Copyright 2007-2008 by Hans de Goede) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation - version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA. + */ + +/* + * Supports following chips: + * + * Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA + * w83793 10 12 8 6 0x7b 0x5ca3 yes no + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Default values */ +#define WATCHDOG_TIMEOUT 2 /* 2 minute default timeout */ + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f, + I2C_CLIENT_END }; + +/* Insmod parameters */ + +static unsigned short force_subclients[4]; +module_param_array(force_subclients, short, NULL, 0); +MODULE_PARM_DESC(force_subclients, + "List of subclient addresses: {bus, clientaddr, subclientaddr1, subclientaddr2}"); + +static bool reset; +module_param(reset, bool, 0); +MODULE_PARM_DESC(reset, "Set to 1 to reset chip, not recommended"); + +static int timeout = WATCHDOG_TIMEOUT; /* default timeout in minutes */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in minutes. 2<= timeout <=255 (default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * Address 0x00, 0x0d, 0x0e, 0x0f in all three banks are reserved + * as ID, Bank Select registers + */ +#define W83793_REG_BANKSEL 0x00 +#define W83793_REG_VENDORID 0x0d +#define W83793_REG_CHIPID 0x0e +#define W83793_REG_DEVICEID 0x0f + +#define W83793_REG_CONFIG 0x40 +#define W83793_REG_MFC 0x58 +#define W83793_REG_FANIN_CTRL 0x5c +#define W83793_REG_FANIN_SEL 0x5d +#define W83793_REG_I2C_ADDR 0x0b +#define W83793_REG_I2C_SUBADDR 0x0c +#define W83793_REG_VID_INA 0x05 +#define W83793_REG_VID_INB 0x06 +#define W83793_REG_VID_LATCHA 0x07 +#define W83793_REG_VID_LATCHB 0x08 +#define W83793_REG_VID_CTRL 0x59 + +#define W83793_REG_WDT_LOCK 0x01 +#define W83793_REG_WDT_ENABLE 0x02 +#define W83793_REG_WDT_STATUS 0x03 +#define W83793_REG_WDT_TIMEOUT 0x04 + +static u16 W83793_REG_TEMP_MODE[2] = { 0x5e, 0x5f }; + +#define TEMP_READ 0 +#define TEMP_CRIT 1 +#define TEMP_CRIT_HYST 2 +#define TEMP_WARN 3 +#define TEMP_WARN_HYST 4 +/* + * only crit and crit_hyst affect real-time alarm status + * current crit crit_hyst warn warn_hyst + */ +static u16 W83793_REG_TEMP[][5] = { + {0x1c, 0x78, 0x79, 0x7a, 0x7b}, + {0x1d, 0x7c, 0x7d, 0x7e, 0x7f}, + {0x1e, 0x80, 0x81, 0x82, 0x83}, + {0x1f, 0x84, 0x85, 0x86, 0x87}, + {0x20, 0x88, 0x89, 0x8a, 0x8b}, + {0x21, 0x8c, 0x8d, 0x8e, 0x8f}, +}; + +#define W83793_REG_TEMP_LOW_BITS 0x22 + +#define W83793_REG_BEEP(index) (0x53 + (index)) +#define W83793_REG_ALARM(index) (0x4b + (index)) + +#define W83793_REG_CLR_CHASSIS 0x4a /* SMI MASK4 */ +#define W83793_REG_IRQ_CTRL 0x50 +#define W83793_REG_OVT_CTRL 0x51 +#define W83793_REG_OVT_BEEP 0x52 + +#define IN_READ 0 +#define IN_MAX 1 +#define IN_LOW 2 +static const u16 W83793_REG_IN[][3] = { + /* Current, High, Low */ + {0x10, 0x60, 0x61}, /* Vcore A */ + {0x11, 0x62, 0x63}, /* Vcore B */ + {0x12, 0x64, 0x65}, /* Vtt */ + {0x14, 0x6a, 0x6b}, /* VSEN1 */ + {0x15, 0x6c, 0x6d}, /* VSEN2 */ + {0x16, 0x6e, 0x6f}, /* +3VSEN */ + {0x17, 0x70, 0x71}, /* +12VSEN */ + {0x18, 0x72, 0x73}, /* 5VDD */ + {0x19, 0x74, 0x75}, /* 5VSB */ + {0x1a, 0x76, 0x77}, /* VBAT */ +}; + +/* Low Bits of Vcore A/B Vtt Read/High/Low */ +static const u16 W83793_REG_IN_LOW_BITS[] = { 0x1b, 0x68, 0x69 }; +static u8 scale_in[] = { 2, 2, 2, 16, 16, 16, 8, 24, 24, 16 }; +static u8 scale_in_add[] = { 0, 0, 0, 0, 0, 0, 0, 150, 150, 0 }; + +#define W83793_REG_FAN(index) (0x23 + 2 * (index)) /* High byte */ +#define W83793_REG_FAN_MIN(index) (0x90 + 2 * (index)) /* High byte */ + +#define W83793_REG_PWM_DEFAULT 0xb2 +#define W83793_REG_PWM_ENABLE 0x207 +#define W83793_REG_PWM_UPTIME 0xc3 /* Unit in 0.1 second */ +#define W83793_REG_PWM_DOWNTIME 0xc4 /* Unit in 0.1 second */ +#define W83793_REG_TEMP_CRITICAL 0xc5 + +#define PWM_DUTY 0 +#define PWM_START 1 +#define PWM_NONSTOP 2 +#define PWM_STOP_TIME 3 +#define W83793_REG_PWM(index, nr) (((nr) == 0 ? 0xb3 : \ + (nr) == 1 ? 0x220 : 0x218) + (index)) + +/* bit field, fan1 is bit0, fan2 is bit1 ... */ +#define W83793_REG_TEMP_FAN_MAP(index) (0x201 + (index)) +#define W83793_REG_TEMP_TOL(index) (0x208 + (index)) +#define W83793_REG_TEMP_CRUISE(index) (0x210 + (index)) +#define W83793_REG_PWM_STOP_TIME(index) (0x228 + (index)) +#define W83793_REG_SF2_TEMP(index, nr) (0x230 + ((index) << 4) + (nr)) +#define W83793_REG_SF2_PWM(index, nr) (0x238 + ((index) << 4) + (nr)) + +static inline unsigned long FAN_FROM_REG(u16 val) +{ + if ((val >= 0xfff) || (val == 0)) + return 0; + return 1350000UL / val; +} + +static inline u16 FAN_TO_REG(long rpm) +{ + if (rpm <= 0) + return 0x0fff; + return clamp_val((1350000 + (rpm >> 1)) / rpm, 1, 0xffe); +} + +static inline unsigned long TIME_FROM_REG(u8 reg) +{ + return reg * 100; +} + +static inline u8 TIME_TO_REG(unsigned long val) +{ + return clamp_val((val + 50) / 100, 0, 0xff); +} + +static inline long TEMP_FROM_REG(s8 reg) +{ + return reg * 1000; +} + +static inline s8 TEMP_TO_REG(long val, s8 min, s8 max) +{ + return clamp_val((val + (val < 0 ? -500 : 500)) / 1000, min, max); +} + +struct w83793_data { + struct i2c_client *lm75[2]; + struct device *hwmon_dev; + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + unsigned long last_nonvolatile; /* In jiffies, last time we update the + * nonvolatile registers + */ + + u8 bank; + u8 vrm; + u8 vid[2]; + u8 in[10][3]; /* Register value, read/high/low */ + u8 in_low_bits[3]; /* Additional resolution for VCore A/B Vtt */ + + u16 has_fan; /* Only fan1- fan5 has own pins */ + u16 fan[12]; /* Register value combine */ + u16 fan_min[12]; /* Register value combine */ + + s8 temp[6][5]; /* current, crit, crit_hyst,warn, warn_hyst */ + u8 temp_low_bits; /* Additional resolution TD1-TD4 */ + u8 temp_mode[2]; /* byte 0: Temp D1-D4 mode each has 2 bits + * byte 1: Temp R1,R2 mode, each has 1 bit + */ + u8 temp_critical; /* If reached all fan will be at full speed */ + u8 temp_fan_map[6]; /* Temp controls which pwm fan, bit field */ + + u8 has_pwm; + u8 has_temp; + u8 has_vid; + u8 pwm_enable; /* Register value, each Temp has 1 bit */ + u8 pwm_uptime; /* Register value */ + u8 pwm_downtime; /* Register value */ + u8 pwm_default; /* All fan default pwm, next poweron valid */ + u8 pwm[8][3]; /* Register value */ + u8 pwm_stop_time[8]; + u8 temp_cruise[6]; + + u8 alarms[5]; /* realtime status registers */ + u8 beeps[5]; + u8 beep_enable; + u8 tolerance[3]; /* Temp tolerance(Smart Fan I/II) */ + u8 sf2_pwm[6][7]; /* Smart FanII: Fan duty cycle */ + u8 sf2_temp[6][7]; /* Smart FanII: Temp level point */ + + /* watchdog */ + struct i2c_client *client; + struct mutex watchdog_lock; + struct list_head list; /* member of the watchdog_data_list */ + struct kref kref; + struct miscdevice watchdog_miscdev; + unsigned long watchdog_is_open; + char watchdog_expect_close; + char watchdog_name[10]; /* must be unique to avoid sysfs conflict */ + unsigned int watchdog_caused_reboot; + int watchdog_timeout; /* watchdog timeout in minutes */ +}; + +/* + * Somewhat ugly :( global data pointer list with all devices, so that + * we can find our device data as when using misc_register. There is no + * other method to get to one's device data from the open file-op and + * for usage in the reboot notifier callback. + */ +static LIST_HEAD(watchdog_data_list); + +/* Note this lock not only protect list access, but also data.kref access */ +static DEFINE_MUTEX(watchdog_data_mutex); + +/* + * Release our data struct when we're detached from the i2c client *and* all + * references to our watchdog device are released + */ +static void w83793_release_resources(struct kref *ref) +{ + struct w83793_data *data = container_of(ref, struct w83793_data, kref); + kfree(data); +} + +static u8 w83793_read_value(struct i2c_client *client, u16 reg); +static int w83793_write_value(struct i2c_client *client, u16 reg, u8 value); +static int w83793_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int w83793_detect(struct i2c_client *client, + struct i2c_board_info *info); +static int w83793_remove(struct i2c_client *client); +static void w83793_init_client(struct i2c_client *client); +static void w83793_update_nonvolatile(struct device *dev); +static struct w83793_data *w83793_update_device(struct device *dev); + +static const struct i2c_device_id w83793_id[] = { + { "w83793", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, w83793_id); + +static struct i2c_driver w83793_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "w83793", + }, + .probe = w83793_probe, + .remove = w83793_remove, + .id_table = w83793_id, + .detect = w83793_detect, + .address_list = normal_i2c, +}; + +static ssize_t +vrm_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83793_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", data->vrm); +} + +static ssize_t +show_vid(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83793_data *data = w83793_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index; + + return sprintf(buf, "%d\n", vid_from_reg(data->vid[index], data->vrm)); +} + +static ssize_t +vrm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w83793_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 255) + return -EINVAL; + + data->vrm = val; + return count; +} + +#define ALARM_STATUS 0 +#define BEEP_ENABLE 1 +static ssize_t +show_alarm_beep(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83793_data *data = w83793_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index >> 3; + int bit = sensor_attr->index & 0x07; + u8 val; + + if (nr == ALARM_STATUS) { + val = (data->alarms[index] >> (bit)) & 1; + } else { /* BEEP_ENABLE */ + val = (data->beeps[index] >> (bit)) & 1; + } + + return sprintf(buf, "%u\n", val); +} + +static ssize_t +store_beep(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index >> 3; + int shift = sensor_attr->index & 0x07; + u8 beep_bit = 1 << shift; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 1) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->beeps[index] = w83793_read_value(client, W83793_REG_BEEP(index)); + data->beeps[index] &= ~beep_bit; + data->beeps[index] |= val << shift; + w83793_write_value(client, W83793_REG_BEEP(index), data->beeps[index]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_beep_enable(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83793_data *data = w83793_update_device(dev); + return sprintf(buf, "%u\n", (data->beep_enable >> 1) & 0x01); +} + +static ssize_t +store_beep_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 1) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->beep_enable = w83793_read_value(client, W83793_REG_OVT_BEEP) + & 0xfd; + data->beep_enable |= val << 1; + w83793_write_value(client, W83793_REG_OVT_BEEP, data->beep_enable); + mutex_unlock(&data->update_lock); + + return count; +} + +/* Write 0 to clear chassis alarm */ +static ssize_t +store_chassis_clear(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + unsigned long val; + u8 reg; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + if (val) + return -EINVAL; + + mutex_lock(&data->update_lock); + reg = w83793_read_value(client, W83793_REG_CLR_CHASSIS); + w83793_write_value(client, W83793_REG_CLR_CHASSIS, reg | 0x80); + data->valid = 0; /* Force cache refresh */ + mutex_unlock(&data->update_lock); + return count; +} + +#define FAN_INPUT 0 +#define FAN_MIN 1 +static ssize_t +show_fan(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83793_data *data = w83793_update_device(dev); + u16 val; + + if (nr == FAN_INPUT) + val = data->fan[index] & 0x0fff; + else + val = data->fan_min[index] & 0x0fff; + + return sprintf(buf, "%lu\n", FAN_FROM_REG(val)); +} + +static ssize_t +store_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index; + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + val = FAN_TO_REG(val); + + mutex_lock(&data->update_lock); + data->fan_min[index] = val; + w83793_write_value(client, W83793_REG_FAN_MIN(index), + (val >> 8) & 0xff); + w83793_write_value(client, W83793_REG_FAN_MIN(index) + 1, val & 0xff); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_pwm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + struct w83793_data *data = w83793_update_device(dev); + u16 val; + int nr = sensor_attr->nr; + int index = sensor_attr->index; + + if (nr == PWM_STOP_TIME) + val = TIME_FROM_REG(data->pwm_stop_time[index]); + else + val = (data->pwm[index][nr] & 0x3f) << 2; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t +store_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + if (nr == PWM_STOP_TIME) { + val = TIME_TO_REG(val); + data->pwm_stop_time[index] = val; + w83793_write_value(client, W83793_REG_PWM_STOP_TIME(index), + val); + } else { + val = clamp_val(val, 0, 0xff) >> 2; + data->pwm[index][nr] = + w83793_read_value(client, W83793_REG_PWM(index, nr)) & 0xc0; + data->pwm[index][nr] |= val; + w83793_write_value(client, W83793_REG_PWM(index, nr), + data->pwm[index][nr]); + } + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83793_data *data = w83793_update_device(dev); + long temp = TEMP_FROM_REG(data->temp[index][nr]); + + if (nr == TEMP_READ && index < 4) { /* Only TD1-TD4 have low bits */ + int low = ((data->temp_low_bits >> (index * 2)) & 0x03) * 250; + temp += temp > 0 ? low : -low; + } + return sprintf(buf, "%ld\n", temp); +} + +static ssize_t +store_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + long tmp; + int err; + + err = kstrtol(buf, 10, &tmp); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp[index][nr] = TEMP_TO_REG(tmp, -128, 127); + w83793_write_value(client, W83793_REG_TEMP[index][nr], + data->temp[index][nr]); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * TD1-TD4 + * each has 4 mode:(2 bits) + * 0: Stop monitor + * 1: Use internal temp sensor(default) + * 2: Reserved + * 3: Use sensor in Intel CPU and get result by PECI + * + * TR1-TR2 + * each has 2 mode:(1 bit) + * 0: Disable temp sensor monitor + * 1: To enable temp sensors monitor + */ + +/* 0 disable, 6 PECI */ +static u8 TO_TEMP_MODE[] = { 0, 0, 0, 6 }; + +static ssize_t +show_temp_mode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83793_data *data = w83793_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index; + u8 mask = (index < 4) ? 0x03 : 0x01; + u8 shift = (index < 4) ? (2 * index) : (index - 4); + u8 tmp; + index = (index < 4) ? 0 : 1; + + tmp = (data->temp_mode[index] >> shift) & mask; + + /* for the internal sensor, found out if diode or thermistor */ + if (tmp == 1) + tmp = index == 0 ? 3 : 4; + else + tmp = TO_TEMP_MODE[tmp]; + + return sprintf(buf, "%d\n", tmp); +} + +static ssize_t +store_temp_mode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index; + u8 mask = (index < 4) ? 0x03 : 0x01; + u8 shift = (index < 4) ? (2 * index) : (index - 4); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + /* transform the sysfs interface values into table above */ + if ((val == 6) && (index < 4)) { + val -= 3; + } else if ((val == 3 && index < 4) + || (val == 4 && index >= 4)) { + /* transform diode or thermistor into internal enable */ + val = !!val; + } else { + return -EINVAL; + } + + index = (index < 4) ? 0 : 1; + mutex_lock(&data->update_lock); + data->temp_mode[index] = + w83793_read_value(client, W83793_REG_TEMP_MODE[index]); + data->temp_mode[index] &= ~(mask << shift); + data->temp_mode[index] |= val << shift; + w83793_write_value(client, W83793_REG_TEMP_MODE[index], + data->temp_mode[index]); + mutex_unlock(&data->update_lock); + + return count; +} + +#define SETUP_PWM_DEFAULT 0 +#define SETUP_PWM_UPTIME 1 /* Unit in 0.1s */ +#define SETUP_PWM_DOWNTIME 2 /* Unit in 0.1s */ +#define SETUP_TEMP_CRITICAL 3 +static ssize_t +show_sf_setup(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + struct w83793_data *data = w83793_update_device(dev); + u32 val = 0; + + if (nr == SETUP_PWM_DEFAULT) + val = (data->pwm_default & 0x3f) << 2; + else if (nr == SETUP_PWM_UPTIME) + val = TIME_FROM_REG(data->pwm_uptime); + else if (nr == SETUP_PWM_DOWNTIME) + val = TIME_FROM_REG(data->pwm_downtime); + else if (nr == SETUP_TEMP_CRITICAL) + val = TEMP_FROM_REG(data->temp_critical & 0x7f); + + return sprintf(buf, "%d\n", val); +} + +static ssize_t +store_sf_setup(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + if (nr == SETUP_PWM_DEFAULT) { + data->pwm_default = + w83793_read_value(client, W83793_REG_PWM_DEFAULT) & 0xc0; + data->pwm_default |= clamp_val(val, 0, 0xff) >> 2; + w83793_write_value(client, W83793_REG_PWM_DEFAULT, + data->pwm_default); + } else if (nr == SETUP_PWM_UPTIME) { + data->pwm_uptime = TIME_TO_REG(val); + data->pwm_uptime += data->pwm_uptime == 0 ? 1 : 0; + w83793_write_value(client, W83793_REG_PWM_UPTIME, + data->pwm_uptime); + } else if (nr == SETUP_PWM_DOWNTIME) { + data->pwm_downtime = TIME_TO_REG(val); + data->pwm_downtime += data->pwm_downtime == 0 ? 1 : 0; + w83793_write_value(client, W83793_REG_PWM_DOWNTIME, + data->pwm_downtime); + } else { /* SETUP_TEMP_CRITICAL */ + data->temp_critical = + w83793_read_value(client, W83793_REG_TEMP_CRITICAL) & 0x80; + data->temp_critical |= TEMP_TO_REG(val, 0, 0x7f); + w83793_write_value(client, W83793_REG_TEMP_CRITICAL, + data->temp_critical); + } + + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Temp SmartFan control + * TEMP_FAN_MAP + * Temp channel control which pwm fan, bitfield, bit 0 indicate pwm1... + * It's possible two or more temp channels control the same fan, w83793 + * always prefers to pick the most critical request and applies it to + * the related Fan. + * It's possible one fan is not in any mapping of 6 temp channels, this + * means the fan is manual mode + * + * TEMP_PWM_ENABLE + * Each temp channel has its own SmartFan mode, and temp channel + * control fans that are set by TEMP_FAN_MAP + * 0: SmartFanII mode + * 1: Thermal Cruise Mode + * + * TEMP_CRUISE + * Target temperature in thermal cruise mode, w83793 will try to turn + * fan speed to keep the temperature of target device around this + * temperature. + * + * TEMP_TOLERANCE + * If Temp higher or lower than target with this tolerance, w83793 + * will take actions to speed up or slow down the fan to keep the + * temperature within the tolerance range. + */ + +#define TEMP_FAN_MAP 0 +#define TEMP_PWM_ENABLE 1 +#define TEMP_CRUISE 2 +#define TEMP_TOLERANCE 3 +static ssize_t +show_sf_ctrl(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83793_data *data = w83793_update_device(dev); + u32 val; + + if (nr == TEMP_FAN_MAP) { + val = data->temp_fan_map[index]; + } else if (nr == TEMP_PWM_ENABLE) { + /* +2 to transform into 2 and 3 to conform with sysfs intf */ + val = ((data->pwm_enable >> index) & 0x01) + 2; + } else if (nr == TEMP_CRUISE) { + val = TEMP_FROM_REG(data->temp_cruise[index] & 0x7f); + } else { /* TEMP_TOLERANCE */ + val = data->tolerance[index >> 1] >> ((index & 0x01) ? 4 : 0); + val = TEMP_FROM_REG(val & 0x0f); + } + return sprintf(buf, "%d\n", val); +} + +static ssize_t +store_sf_ctrl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + if (nr == TEMP_FAN_MAP) { + val = clamp_val(val, 0, 255); + w83793_write_value(client, W83793_REG_TEMP_FAN_MAP(index), val); + data->temp_fan_map[index] = val; + } else if (nr == TEMP_PWM_ENABLE) { + if (val == 2 || val == 3) { + data->pwm_enable = + w83793_read_value(client, W83793_REG_PWM_ENABLE); + if (val - 2) + data->pwm_enable |= 1 << index; + else + data->pwm_enable &= ~(1 << index); + w83793_write_value(client, W83793_REG_PWM_ENABLE, + data->pwm_enable); + } else { + mutex_unlock(&data->update_lock); + return -EINVAL; + } + } else if (nr == TEMP_CRUISE) { + data->temp_cruise[index] = + w83793_read_value(client, W83793_REG_TEMP_CRUISE(index)); + data->temp_cruise[index] &= 0x80; + data->temp_cruise[index] |= TEMP_TO_REG(val, 0, 0x7f); + + w83793_write_value(client, W83793_REG_TEMP_CRUISE(index), + data->temp_cruise[index]); + } else { /* TEMP_TOLERANCE */ + int i = index >> 1; + u8 shift = (index & 0x01) ? 4 : 0; + data->tolerance[i] = + w83793_read_value(client, W83793_REG_TEMP_TOL(i)); + + data->tolerance[i] &= ~(0x0f << shift); + data->tolerance[i] |= TEMP_TO_REG(val, 0, 0x0f) << shift; + w83793_write_value(client, W83793_REG_TEMP_TOL(i), + data->tolerance[i]); + } + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_sf2_pwm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83793_data *data = w83793_update_device(dev); + + return sprintf(buf, "%d\n", (data->sf2_pwm[index][nr] & 0x3f) << 2); +} + +static ssize_t +store_sf2_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + val = clamp_val(val, 0, 0xff) >> 2; + + mutex_lock(&data->update_lock); + data->sf2_pwm[index][nr] = + w83793_read_value(client, W83793_REG_SF2_PWM(index, nr)) & 0xc0; + data->sf2_pwm[index][nr] |= val; + w83793_write_value(client, W83793_REG_SF2_PWM(index, nr), + data->sf2_pwm[index][nr]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_sf2_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83793_data *data = w83793_update_device(dev); + + return sprintf(buf, "%ld\n", + TEMP_FROM_REG(data->sf2_temp[index][nr] & 0x7f)); +} + +static ssize_t +store_sf2_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + val = TEMP_TO_REG(val, 0, 0x7f); + + mutex_lock(&data->update_lock); + data->sf2_temp[index][nr] = + w83793_read_value(client, W83793_REG_SF2_TEMP(index, nr)) & 0x80; + data->sf2_temp[index][nr] |= val; + w83793_write_value(client, W83793_REG_SF2_TEMP(index, nr), + data->sf2_temp[index][nr]); + mutex_unlock(&data->update_lock); + return count; +} + +/* only Vcore A/B and Vtt have additional 2 bits precision */ +static ssize_t +show_in(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83793_data *data = w83793_update_device(dev); + u16 val = data->in[index][nr]; + + if (index < 3) { + val <<= 2; + val += (data->in_low_bits[nr] >> (index * 2)) & 0x3; + } + /* voltage inputs 5VDD and 5VSB needs 150mV offset */ + val = val * scale_in[index] + scale_in_add[index]; + return sprintf(buf, "%d\n", val); +} + +static ssize_t +store_in(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + val = (val + scale_in[index] / 2) / scale_in[index]; + + mutex_lock(&data->update_lock); + if (index > 2) { + /* fix the limit values of 5VDD and 5VSB to ALARM mechanism */ + if (nr == 1 || nr == 2) + val -= scale_in_add[index] / scale_in[index]; + val = clamp_val(val, 0, 255); + } else { + val = clamp_val(val, 0, 0x3FF); + data->in_low_bits[nr] = + w83793_read_value(client, W83793_REG_IN_LOW_BITS[nr]); + data->in_low_bits[nr] &= ~(0x03 << (2 * index)); + data->in_low_bits[nr] |= (val & 0x03) << (2 * index); + w83793_write_value(client, W83793_REG_IN_LOW_BITS[nr], + data->in_low_bits[nr]); + val >>= 2; + } + data->in[index][nr] = val; + w83793_write_value(client, W83793_REG_IN[index][nr], + data->in[index][nr]); + mutex_unlock(&data->update_lock); + return count; +} + +#define NOT_USED -1 + +#define SENSOR_ATTR_IN(index) \ + SENSOR_ATTR_2(in##index##_input, S_IRUGO, show_in, NULL, \ + IN_READ, index), \ + SENSOR_ATTR_2(in##index##_max, S_IRUGO | S_IWUSR, show_in, \ + store_in, IN_MAX, index), \ + SENSOR_ATTR_2(in##index##_min, S_IRUGO | S_IWUSR, show_in, \ + store_in, IN_LOW, index), \ + SENSOR_ATTR_2(in##index##_alarm, S_IRUGO, show_alarm_beep, \ + NULL, ALARM_STATUS, index + ((index > 2) ? 1 : 0)), \ + SENSOR_ATTR_2(in##index##_beep, S_IWUSR | S_IRUGO, \ + show_alarm_beep, store_beep, BEEP_ENABLE, \ + index + ((index > 2) ? 1 : 0)) + +#define SENSOR_ATTR_FAN(index) \ + SENSOR_ATTR_2(fan##index##_alarm, S_IRUGO, show_alarm_beep, \ + NULL, ALARM_STATUS, index + 17), \ + SENSOR_ATTR_2(fan##index##_beep, S_IWUSR | S_IRUGO, \ + show_alarm_beep, store_beep, BEEP_ENABLE, index + 17), \ + SENSOR_ATTR_2(fan##index##_input, S_IRUGO, show_fan, \ + NULL, FAN_INPUT, index - 1), \ + SENSOR_ATTR_2(fan##index##_min, S_IWUSR | S_IRUGO, \ + show_fan, store_fan_min, FAN_MIN, index - 1) + +#define SENSOR_ATTR_PWM(index) \ + SENSOR_ATTR_2(pwm##index, S_IWUSR | S_IRUGO, show_pwm, \ + store_pwm, PWM_DUTY, index - 1), \ + SENSOR_ATTR_2(pwm##index##_nonstop, S_IWUSR | S_IRUGO, \ + show_pwm, store_pwm, PWM_NONSTOP, index - 1), \ + SENSOR_ATTR_2(pwm##index##_start, S_IWUSR | S_IRUGO, \ + show_pwm, store_pwm, PWM_START, index - 1), \ + SENSOR_ATTR_2(pwm##index##_stop_time, S_IWUSR | S_IRUGO, \ + show_pwm, store_pwm, PWM_STOP_TIME, index - 1) + +#define SENSOR_ATTR_TEMP(index) \ + SENSOR_ATTR_2(temp##index##_type, S_IRUGO | S_IWUSR, \ + show_temp_mode, store_temp_mode, NOT_USED, index - 1), \ + SENSOR_ATTR_2(temp##index##_input, S_IRUGO, show_temp, \ + NULL, TEMP_READ, index - 1), \ + SENSOR_ATTR_2(temp##index##_max, S_IRUGO | S_IWUSR, show_temp, \ + store_temp, TEMP_CRIT, index - 1), \ + SENSOR_ATTR_2(temp##index##_max_hyst, S_IRUGO | S_IWUSR, \ + show_temp, store_temp, TEMP_CRIT_HYST, index - 1), \ + SENSOR_ATTR_2(temp##index##_warn, S_IRUGO | S_IWUSR, show_temp, \ + store_temp, TEMP_WARN, index - 1), \ + SENSOR_ATTR_2(temp##index##_warn_hyst, S_IRUGO | S_IWUSR, \ + show_temp, store_temp, TEMP_WARN_HYST, index - 1), \ + SENSOR_ATTR_2(temp##index##_alarm, S_IRUGO, \ + show_alarm_beep, NULL, ALARM_STATUS, index + 11), \ + SENSOR_ATTR_2(temp##index##_beep, S_IWUSR | S_IRUGO, \ + show_alarm_beep, store_beep, BEEP_ENABLE, index + 11), \ + SENSOR_ATTR_2(temp##index##_auto_channels_pwm, \ + S_IRUGO | S_IWUSR, show_sf_ctrl, store_sf_ctrl, \ + TEMP_FAN_MAP, index - 1), \ + SENSOR_ATTR_2(temp##index##_pwm_enable, S_IWUSR | S_IRUGO, \ + show_sf_ctrl, store_sf_ctrl, TEMP_PWM_ENABLE, \ + index - 1), \ + SENSOR_ATTR_2(thermal_cruise##index, S_IRUGO | S_IWUSR, \ + show_sf_ctrl, store_sf_ctrl, TEMP_CRUISE, index - 1), \ + SENSOR_ATTR_2(tolerance##index, S_IRUGO | S_IWUSR, show_sf_ctrl,\ + store_sf_ctrl, TEMP_TOLERANCE, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point1_pwm, S_IRUGO | S_IWUSR, \ + show_sf2_pwm, store_sf2_pwm, 0, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point2_pwm, S_IRUGO | S_IWUSR, \ + show_sf2_pwm, store_sf2_pwm, 1, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point3_pwm, S_IRUGO | S_IWUSR, \ + show_sf2_pwm, store_sf2_pwm, 2, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point4_pwm, S_IRUGO | S_IWUSR, \ + show_sf2_pwm, store_sf2_pwm, 3, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point5_pwm, S_IRUGO | S_IWUSR, \ + show_sf2_pwm, store_sf2_pwm, 4, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point6_pwm, S_IRUGO | S_IWUSR, \ + show_sf2_pwm, store_sf2_pwm, 5, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point7_pwm, S_IRUGO | S_IWUSR, \ + show_sf2_pwm, store_sf2_pwm, 6, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point1_temp, S_IRUGO | S_IWUSR,\ + show_sf2_temp, store_sf2_temp, 0, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point2_temp, S_IRUGO | S_IWUSR,\ + show_sf2_temp, store_sf2_temp, 1, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point3_temp, S_IRUGO | S_IWUSR,\ + show_sf2_temp, store_sf2_temp, 2, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point4_temp, S_IRUGO | S_IWUSR,\ + show_sf2_temp, store_sf2_temp, 3, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point5_temp, S_IRUGO | S_IWUSR,\ + show_sf2_temp, store_sf2_temp, 4, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point6_temp, S_IRUGO | S_IWUSR,\ + show_sf2_temp, store_sf2_temp, 5, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point7_temp, S_IRUGO | S_IWUSR,\ + show_sf2_temp, store_sf2_temp, 6, index - 1) + +static struct sensor_device_attribute_2 w83793_sensor_attr_2[] = { + SENSOR_ATTR_IN(0), + SENSOR_ATTR_IN(1), + SENSOR_ATTR_IN(2), + SENSOR_ATTR_IN(3), + SENSOR_ATTR_IN(4), + SENSOR_ATTR_IN(5), + SENSOR_ATTR_IN(6), + SENSOR_ATTR_IN(7), + SENSOR_ATTR_IN(8), + SENSOR_ATTR_IN(9), + SENSOR_ATTR_FAN(1), + SENSOR_ATTR_FAN(2), + SENSOR_ATTR_FAN(3), + SENSOR_ATTR_FAN(4), + SENSOR_ATTR_FAN(5), + SENSOR_ATTR_PWM(1), + SENSOR_ATTR_PWM(2), + SENSOR_ATTR_PWM(3), +}; + +static struct sensor_device_attribute_2 w83793_temp[] = { + SENSOR_ATTR_TEMP(1), + SENSOR_ATTR_TEMP(2), + SENSOR_ATTR_TEMP(3), + SENSOR_ATTR_TEMP(4), + SENSOR_ATTR_TEMP(5), + SENSOR_ATTR_TEMP(6), +}; + +/* Fan6-Fan12 */ +static struct sensor_device_attribute_2 w83793_left_fan[] = { + SENSOR_ATTR_FAN(6), + SENSOR_ATTR_FAN(7), + SENSOR_ATTR_FAN(8), + SENSOR_ATTR_FAN(9), + SENSOR_ATTR_FAN(10), + SENSOR_ATTR_FAN(11), + SENSOR_ATTR_FAN(12), +}; + +/* Pwm4-Pwm8 */ +static struct sensor_device_attribute_2 w83793_left_pwm[] = { + SENSOR_ATTR_PWM(4), + SENSOR_ATTR_PWM(5), + SENSOR_ATTR_PWM(6), + SENSOR_ATTR_PWM(7), + SENSOR_ATTR_PWM(8), +}; + +static struct sensor_device_attribute_2 w83793_vid[] = { + SENSOR_ATTR_2(cpu0_vid, S_IRUGO, show_vid, NULL, NOT_USED, 0), + SENSOR_ATTR_2(cpu1_vid, S_IRUGO, show_vid, NULL, NOT_USED, 1), +}; +static DEVICE_ATTR_RW(vrm); + +static struct sensor_device_attribute_2 sda_single_files[] = { + SENSOR_ATTR_2(intrusion0_alarm, S_IWUSR | S_IRUGO, show_alarm_beep, + store_chassis_clear, ALARM_STATUS, 30), + SENSOR_ATTR_2(beep_enable, S_IWUSR | S_IRUGO, show_beep_enable, + store_beep_enable, NOT_USED, NOT_USED), + SENSOR_ATTR_2(pwm_default, S_IWUSR | S_IRUGO, show_sf_setup, + store_sf_setup, SETUP_PWM_DEFAULT, NOT_USED), + SENSOR_ATTR_2(pwm_uptime, S_IWUSR | S_IRUGO, show_sf_setup, + store_sf_setup, SETUP_PWM_UPTIME, NOT_USED), + SENSOR_ATTR_2(pwm_downtime, S_IWUSR | S_IRUGO, show_sf_setup, + store_sf_setup, SETUP_PWM_DOWNTIME, NOT_USED), + SENSOR_ATTR_2(temp_critical, S_IWUSR | S_IRUGO, show_sf_setup, + store_sf_setup, SETUP_TEMP_CRITICAL, NOT_USED), +}; + +static void w83793_init_client(struct i2c_client *client) +{ + if (reset) + w83793_write_value(client, W83793_REG_CONFIG, 0x80); + + /* Start monitoring */ + w83793_write_value(client, W83793_REG_CONFIG, + w83793_read_value(client, W83793_REG_CONFIG) | 0x01); +} + +/* + * Watchdog routines + */ + +static int watchdog_set_timeout(struct w83793_data *data, int timeout) +{ + unsigned int mtimeout; + int ret; + + mtimeout = DIV_ROUND_UP(timeout, 60); + + if (mtimeout > 255) + return -EINVAL; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + data->watchdog_timeout = mtimeout; + + /* Set Timeout value (in Minutes) */ + w83793_write_value(data->client, W83793_REG_WDT_TIMEOUT, + data->watchdog_timeout); + + ret = mtimeout * 60; + +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_get_timeout(struct w83793_data *data) +{ + int timeout; + + mutex_lock(&data->watchdog_lock); + timeout = data->watchdog_timeout * 60; + mutex_unlock(&data->watchdog_lock); + + return timeout; +} + +static int watchdog_trigger(struct w83793_data *data) +{ + int ret = 0; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + /* Set Timeout value (in Minutes) */ + w83793_write_value(data->client, W83793_REG_WDT_TIMEOUT, + data->watchdog_timeout); + +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_enable(struct w83793_data *data) +{ + int ret = 0; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + /* Set initial timeout */ + w83793_write_value(data->client, W83793_REG_WDT_TIMEOUT, + data->watchdog_timeout); + + /* Enable Soft Watchdog */ + w83793_write_value(data->client, W83793_REG_WDT_LOCK, 0x55); + +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_disable(struct w83793_data *data) +{ + int ret = 0; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + /* Disable Soft Watchdog */ + w83793_write_value(data->client, W83793_REG_WDT_LOCK, 0xAA); + +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_open(struct inode *inode, struct file *filp) +{ + struct w83793_data *pos, *data = NULL; + int watchdog_is_open; + + /* + * We get called from drivers/char/misc.c with misc_mtx hold, and we + * call misc_register() from w83793_probe() with watchdog_data_mutex + * hold, as misc_register() takes the misc_mtx lock, this is a possible + * deadlock, so we use mutex_trylock here. + */ + if (!mutex_trylock(&watchdog_data_mutex)) + return -ERESTARTSYS; + list_for_each_entry(pos, &watchdog_data_list, list) { + if (pos->watchdog_miscdev.minor == iminor(inode)) { + data = pos; + break; + } + } + + /* Check, if device is already open */ + watchdog_is_open = test_and_set_bit(0, &data->watchdog_is_open); + + /* + * Increase data reference counter (if not already done). + * Note we can never not have found data, so we don't check for this + */ + if (!watchdog_is_open) + kref_get(&data->kref); + + mutex_unlock(&watchdog_data_mutex); + + /* Check, if device is already open and possibly issue error */ + if (watchdog_is_open) + return -EBUSY; + + /* Enable Soft Watchdog */ + watchdog_enable(data); + + /* Store pointer to data into filp's private data */ + filp->private_data = data; + + return nonseekable_open(inode, filp); +} + +static int watchdog_close(struct inode *inode, struct file *filp) +{ + struct w83793_data *data = filp->private_data; + + if (data->watchdog_expect_close) { + watchdog_disable(data); + data->watchdog_expect_close = 0; + } else { + watchdog_trigger(data); + dev_crit(&data->client->dev, + "unexpected close, not stopping watchdog!\n"); + } + + clear_bit(0, &data->watchdog_is_open); + + /* Decrease data reference counter */ + mutex_lock(&watchdog_data_mutex); + kref_put(&data->kref, w83793_release_resources); + mutex_unlock(&watchdog_data_mutex); + + return 0; +} + +static ssize_t watchdog_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offset) +{ + ssize_t ret; + struct w83793_data *data = filp->private_data; + + if (count) { + if (!nowayout) { + size_t i; + + /* Clear it in case it was set with a previous write */ + data->watchdog_expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + data->watchdog_expect_close = 1; + } + } + ret = watchdog_trigger(data); + if (ret < 0) + return ret; + } + return count; +} + +static long watchdog_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | + WDIOF_CARDRESET, + .identity = "w83793 watchdog" + }; + + int val, ret = 0; + struct w83793_data *data = filp->private_data; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (!nowayout) + ident.options |= WDIOF_MAGICCLOSE; + if (copy_to_user((void __user *)arg, &ident, sizeof(ident))) + ret = -EFAULT; + break; + + case WDIOC_GETSTATUS: + val = data->watchdog_caused_reboot ? WDIOF_CARDRESET : 0; + ret = put_user(val, (int __user *)arg); + break; + + case WDIOC_GETBOOTSTATUS: + ret = put_user(0, (int __user *)arg); + break; + + case WDIOC_KEEPALIVE: + ret = watchdog_trigger(data); + break; + + case WDIOC_GETTIMEOUT: + val = watchdog_get_timeout(data); + ret = put_user(val, (int __user *)arg); + break; + + case WDIOC_SETTIMEOUT: + if (get_user(val, (int __user *)arg)) { + ret = -EFAULT; + break; + } + ret = watchdog_set_timeout(data, val); + if (ret > 0) + ret = put_user(ret, (int __user *)arg); + break; + + case WDIOC_SETOPTIONS: + if (get_user(val, (int __user *)arg)) { + ret = -EFAULT; + break; + } + + if (val & WDIOS_DISABLECARD) + ret = watchdog_disable(data); + else if (val & WDIOS_ENABLECARD) + ret = watchdog_enable(data); + else + ret = -EINVAL; + + break; + default: + ret = -ENOTTY; + } + return ret; +} + +static const struct file_operations watchdog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = watchdog_open, + .release = watchdog_close, + .write = watchdog_write, + .unlocked_ioctl = watchdog_ioctl, +}; + +/* + * Notifier for system down + */ + +static int watchdog_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + struct w83793_data *data = NULL; + + if (code == SYS_DOWN || code == SYS_HALT) { + + /* Disable each registered watchdog */ + mutex_lock(&watchdog_data_mutex); + list_for_each_entry(data, &watchdog_data_list, list) { + if (data->watchdog_miscdev.minor) + watchdog_disable(data); + } + mutex_unlock(&watchdog_data_mutex); + } + + return NOTIFY_DONE; +} + +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block watchdog_notifier = { + .notifier_call = watchdog_notify_sys, +}; + +/* + * Init / remove routines + */ + +static int w83793_remove(struct i2c_client *client) +{ + struct w83793_data *data = i2c_get_clientdata(client); + struct device *dev = &client->dev; + int i, tmp; + + /* Unregister the watchdog (if registered) */ + if (data->watchdog_miscdev.minor) { + misc_deregister(&data->watchdog_miscdev); + + if (data->watchdog_is_open) { + dev_warn(&client->dev, + "i2c client detached with watchdog open! " + "Stopping watchdog.\n"); + watchdog_disable(data); + } + + mutex_lock(&watchdog_data_mutex); + list_del(&data->list); + mutex_unlock(&watchdog_data_mutex); + + /* Tell the watchdog code the client is gone */ + mutex_lock(&data->watchdog_lock); + data->client = NULL; + mutex_unlock(&data->watchdog_lock); + } + + /* Reset Configuration Register to Disable Watch Dog Registers */ + tmp = w83793_read_value(client, W83793_REG_CONFIG); + w83793_write_value(client, W83793_REG_CONFIG, tmp & ~0x04); + + unregister_reboot_notifier(&watchdog_notifier); + + hwmon_device_unregister(data->hwmon_dev); + + for (i = 0; i < ARRAY_SIZE(w83793_sensor_attr_2); i++) + device_remove_file(dev, + &w83793_sensor_attr_2[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(sda_single_files); i++) + device_remove_file(dev, &sda_single_files[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(w83793_vid); i++) + device_remove_file(dev, &w83793_vid[i].dev_attr); + device_remove_file(dev, &dev_attr_vrm); + + for (i = 0; i < ARRAY_SIZE(w83793_left_fan); i++) + device_remove_file(dev, &w83793_left_fan[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(w83793_left_pwm); i++) + device_remove_file(dev, &w83793_left_pwm[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(w83793_temp); i++) + device_remove_file(dev, &w83793_temp[i].dev_attr); + + i2c_unregister_device(data->lm75[0]); + i2c_unregister_device(data->lm75[1]); + + /* Decrease data reference counter */ + mutex_lock(&watchdog_data_mutex); + kref_put(&data->kref, w83793_release_resources); + mutex_unlock(&watchdog_data_mutex); + + return 0; +} + +static int +w83793_detect_subclients(struct i2c_client *client) +{ + int i, id, err; + int address = client->addr; + u8 tmp; + struct i2c_adapter *adapter = client->adapter; + struct w83793_data *data = i2c_get_clientdata(client); + + id = i2c_adapter_id(adapter); + if (force_subclients[0] == id && force_subclients[1] == address) { + for (i = 2; i <= 3; i++) { + if (force_subclients[i] < 0x48 + || force_subclients[i] > 0x4f) { + dev_err(&client->dev, + "invalid subclient " + "address %d; must be 0x48-0x4f\n", + force_subclients[i]); + err = -EINVAL; + goto ERROR_SC_0; + } + } + w83793_write_value(client, W83793_REG_I2C_SUBADDR, + (force_subclients[2] & 0x07) | + ((force_subclients[3] & 0x07) << 4)); + } + + tmp = w83793_read_value(client, W83793_REG_I2C_SUBADDR); + if (!(tmp & 0x08)) + data->lm75[0] = i2c_new_dummy(adapter, 0x48 + (tmp & 0x7)); + if (!(tmp & 0x80)) { + if ((data->lm75[0] != NULL) + && ((tmp & 0x7) == ((tmp >> 4) & 0x7))) { + dev_err(&client->dev, + "duplicate addresses 0x%x, " + "use force_subclients\n", data->lm75[0]->addr); + err = -ENODEV; + goto ERROR_SC_1; + } + data->lm75[1] = i2c_new_dummy(adapter, + 0x48 + ((tmp >> 4) & 0x7)); + } + + return 0; + + /* Undo inits in case of errors */ + +ERROR_SC_1: + i2c_unregister_device(data->lm75[0]); +ERROR_SC_0: + return err; +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int w83793_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + u8 tmp, bank, chip_id; + struct i2c_adapter *adapter = client->adapter; + unsigned short address = client->addr; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + bank = i2c_smbus_read_byte_data(client, W83793_REG_BANKSEL); + + tmp = bank & 0x80 ? 0x5c : 0xa3; + /* Check Winbond vendor ID */ + if (tmp != i2c_smbus_read_byte_data(client, W83793_REG_VENDORID)) { + pr_debug("w83793: Detection failed at check vendor id\n"); + return -ENODEV; + } + + /* + * If Winbond chip, address of chip and W83793_REG_I2C_ADDR + * should match + */ + if ((bank & 0x07) == 0 + && i2c_smbus_read_byte_data(client, W83793_REG_I2C_ADDR) != + (address << 1)) { + pr_debug("w83793: Detection failed at check i2c addr\n"); + return -ENODEV; + } + + /* Determine the chip type now */ + chip_id = i2c_smbus_read_byte_data(client, W83793_REG_CHIPID); + if (chip_id != 0x7b) + return -ENODEV; + + strlcpy(info->type, "w83793", I2C_NAME_SIZE); + + return 0; +} + +static int w83793_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + static const int watchdog_minors[] = { + WATCHDOG_MINOR, 212, 213, 214, 215 + }; + struct w83793_data *data; + int i, tmp, val, err; + int files_fan = ARRAY_SIZE(w83793_left_fan) / 7; + int files_pwm = ARRAY_SIZE(w83793_left_pwm) / 5; + int files_temp = ARRAY_SIZE(w83793_temp) / 6; + + data = kzalloc(sizeof(struct w83793_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(client, data); + data->bank = i2c_smbus_read_byte_data(client, W83793_REG_BANKSEL); + mutex_init(&data->update_lock); + mutex_init(&data->watchdog_lock); + INIT_LIST_HEAD(&data->list); + kref_init(&data->kref); + + /* + * Store client pointer in our data struct for watchdog usage + * (where the client is found through a data ptr instead of the + * otherway around) + */ + data->client = client; + + err = w83793_detect_subclients(client); + if (err) + goto free_mem; + + /* Initialize the chip */ + w83793_init_client(client); + + /* + * Only fan 1-5 has their own input pins, + * Pwm 1-3 has their own pins + */ + data->has_fan = 0x1f; + data->has_pwm = 0x07; + tmp = w83793_read_value(client, W83793_REG_MFC); + val = w83793_read_value(client, W83793_REG_FANIN_CTRL); + + /* check the function of pins 49-56 */ + if (tmp & 0x80) { + data->has_vid |= 0x2; /* has VIDB */ + } else { + data->has_pwm |= 0x18; /* pwm 4,5 */ + if (val & 0x01) { /* fan 6 */ + data->has_fan |= 0x20; + data->has_pwm |= 0x20; + } + if (val & 0x02) { /* fan 7 */ + data->has_fan |= 0x40; + data->has_pwm |= 0x40; + } + if (!(tmp & 0x40) && (val & 0x04)) { /* fan 8 */ + data->has_fan |= 0x80; + data->has_pwm |= 0x80; + } + } + + /* check the function of pins 37-40 */ + if (!(tmp & 0x29)) + data->has_vid |= 0x1; /* has VIDA */ + if (0x08 == (tmp & 0x0c)) { + if (val & 0x08) /* fan 9 */ + data->has_fan |= 0x100; + if (val & 0x10) /* fan 10 */ + data->has_fan |= 0x200; + } + if (0x20 == (tmp & 0x30)) { + if (val & 0x20) /* fan 11 */ + data->has_fan |= 0x400; + if (val & 0x40) /* fan 12 */ + data->has_fan |= 0x800; + } + + if ((tmp & 0x01) && (val & 0x04)) { /* fan 8, second location */ + data->has_fan |= 0x80; + data->has_pwm |= 0x80; + } + + tmp = w83793_read_value(client, W83793_REG_FANIN_SEL); + if ((tmp & 0x01) && (val & 0x08)) { /* fan 9, second location */ + data->has_fan |= 0x100; + } + if ((tmp & 0x02) && (val & 0x10)) { /* fan 10, second location */ + data->has_fan |= 0x200; + } + if ((tmp & 0x04) && (val & 0x20)) { /* fan 11, second location */ + data->has_fan |= 0x400; + } + if ((tmp & 0x08) && (val & 0x40)) { /* fan 12, second location */ + data->has_fan |= 0x800; + } + + /* check the temp1-6 mode, ignore former AMDSI selected inputs */ + tmp = w83793_read_value(client, W83793_REG_TEMP_MODE[0]); + if (tmp & 0x01) + data->has_temp |= 0x01; + if (tmp & 0x04) + data->has_temp |= 0x02; + if (tmp & 0x10) + data->has_temp |= 0x04; + if (tmp & 0x40) + data->has_temp |= 0x08; + + tmp = w83793_read_value(client, W83793_REG_TEMP_MODE[1]); + if (tmp & 0x01) + data->has_temp |= 0x10; + if (tmp & 0x02) + data->has_temp |= 0x20; + + /* Register sysfs hooks */ + for (i = 0; i < ARRAY_SIZE(w83793_sensor_attr_2); i++) { + err = device_create_file(dev, + &w83793_sensor_attr_2[i].dev_attr); + if (err) + goto exit_remove; + } + + for (i = 0; i < ARRAY_SIZE(w83793_vid); i++) { + if (!(data->has_vid & (1 << i))) + continue; + err = device_create_file(dev, &w83793_vid[i].dev_attr); + if (err) + goto exit_remove; + } + if (data->has_vid) { + data->vrm = vid_which_vrm(); + err = device_create_file(dev, &dev_attr_vrm); + if (err) + goto exit_remove; + } + + for (i = 0; i < ARRAY_SIZE(sda_single_files); i++) { + err = device_create_file(dev, &sda_single_files[i].dev_attr); + if (err) + goto exit_remove; + + } + + for (i = 0; i < 6; i++) { + int j; + if (!(data->has_temp & (1 << i))) + continue; + for (j = 0; j < files_temp; j++) { + err = device_create_file(dev, + &w83793_temp[(i) * files_temp + + j].dev_attr); + if (err) + goto exit_remove; + } + } + + for (i = 5; i < 12; i++) { + int j; + if (!(data->has_fan & (1 << i))) + continue; + for (j = 0; j < files_fan; j++) { + err = device_create_file(dev, + &w83793_left_fan[(i - 5) * files_fan + + j].dev_attr); + if (err) + goto exit_remove; + } + } + + for (i = 3; i < 8; i++) { + int j; + if (!(data->has_pwm & (1 << i))) + continue; + for (j = 0; j < files_pwm; j++) { + err = device_create_file(dev, + &w83793_left_pwm[(i - 3) * files_pwm + + j].dev_attr); + if (err) + goto exit_remove; + } + } + + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + /* Watchdog initialization */ + + /* Register boot notifier */ + err = register_reboot_notifier(&watchdog_notifier); + if (err != 0) { + dev_err(&client->dev, + "cannot register reboot notifier (err=%d)\n", err); + goto exit_devunreg; + } + + /* + * Enable Watchdog registers. + * Set Configuration Register to Enable Watch Dog Registers + * (Bit 2) = XXXX, X1XX. + */ + tmp = w83793_read_value(client, W83793_REG_CONFIG); + w83793_write_value(client, W83793_REG_CONFIG, tmp | 0x04); + + /* Set the default watchdog timeout */ + data->watchdog_timeout = timeout; + + /* Check, if last reboot was caused by watchdog */ + data->watchdog_caused_reboot = + w83793_read_value(data->client, W83793_REG_WDT_STATUS) & 0x01; + + /* Disable Soft Watchdog during initialiation */ + watchdog_disable(data); + + /* + * We take the data_mutex lock early so that watchdog_open() cannot + * run when misc_register() has completed, but we've not yet added + * our data to the watchdog_data_list (and set the default timeout) + */ + mutex_lock(&watchdog_data_mutex); + for (i = 0; i < ARRAY_SIZE(watchdog_minors); i++) { + /* Register our watchdog part */ + snprintf(data->watchdog_name, sizeof(data->watchdog_name), + "watchdog%c", (i == 0) ? '\0' : ('0' + i)); + data->watchdog_miscdev.name = data->watchdog_name; + data->watchdog_miscdev.fops = &watchdog_fops; + data->watchdog_miscdev.minor = watchdog_minors[i]; + + err = misc_register(&data->watchdog_miscdev); + if (err == -EBUSY) + continue; + if (err) { + data->watchdog_miscdev.minor = 0; + dev_err(&client->dev, + "Registering watchdog chardev: %d\n", err); + break; + } + + list_add(&data->list, &watchdog_data_list); + + dev_info(&client->dev, + "Registered watchdog chardev major 10, minor: %d\n", + watchdog_minors[i]); + break; + } + if (i == ARRAY_SIZE(watchdog_minors)) { + data->watchdog_miscdev.minor = 0; + dev_warn(&client->dev, + "Couldn't register watchdog chardev (due to no free minor)\n"); + } + + mutex_unlock(&watchdog_data_mutex); + + return 0; + + /* Unregister hwmon device */ + +exit_devunreg: + + hwmon_device_unregister(data->hwmon_dev); + + /* Unregister sysfs hooks */ + +exit_remove: + for (i = 0; i < ARRAY_SIZE(w83793_sensor_attr_2); i++) + device_remove_file(dev, &w83793_sensor_attr_2[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(sda_single_files); i++) + device_remove_file(dev, &sda_single_files[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(w83793_vid); i++) + device_remove_file(dev, &w83793_vid[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(w83793_left_fan); i++) + device_remove_file(dev, &w83793_left_fan[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(w83793_left_pwm); i++) + device_remove_file(dev, &w83793_left_pwm[i].dev_attr); + + for (i = 0; i < ARRAY_SIZE(w83793_temp); i++) + device_remove_file(dev, &w83793_temp[i].dev_attr); + + i2c_unregister_device(data->lm75[0]); + i2c_unregister_device(data->lm75[1]); +free_mem: + kfree(data); +exit: + return err; +} + +static void w83793_update_nonvolatile(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + int i, j; + /* + * They are somewhat "stable" registers, and to update them every time + * takes so much time, it's just not worthy. Update them in a long + * interval to avoid exception. + */ + if (!(time_after(jiffies, data->last_nonvolatile + HZ * 300) + || !data->valid)) + return; + /* update voltage limits */ + for (i = 1; i < 3; i++) { + for (j = 0; j < ARRAY_SIZE(data->in); j++) { + data->in[j][i] = + w83793_read_value(client, W83793_REG_IN[j][i]); + } + data->in_low_bits[i] = + w83793_read_value(client, W83793_REG_IN_LOW_BITS[i]); + } + + for (i = 0; i < ARRAY_SIZE(data->fan_min); i++) { + /* Update the Fan measured value and limits */ + if (!(data->has_fan & (1 << i))) + continue; + data->fan_min[i] = + w83793_read_value(client, W83793_REG_FAN_MIN(i)) << 8; + data->fan_min[i] |= + w83793_read_value(client, W83793_REG_FAN_MIN(i) + 1); + } + + for (i = 0; i < ARRAY_SIZE(data->temp_fan_map); i++) { + if (!(data->has_temp & (1 << i))) + continue; + data->temp_fan_map[i] = + w83793_read_value(client, W83793_REG_TEMP_FAN_MAP(i)); + for (j = 1; j < 5; j++) { + data->temp[i][j] = + w83793_read_value(client, W83793_REG_TEMP[i][j]); + } + data->temp_cruise[i] = + w83793_read_value(client, W83793_REG_TEMP_CRUISE(i)); + for (j = 0; j < 7; j++) { + data->sf2_pwm[i][j] = + w83793_read_value(client, W83793_REG_SF2_PWM(i, j)); + data->sf2_temp[i][j] = + w83793_read_value(client, + W83793_REG_SF2_TEMP(i, j)); + } + } + + for (i = 0; i < ARRAY_SIZE(data->temp_mode); i++) + data->temp_mode[i] = + w83793_read_value(client, W83793_REG_TEMP_MODE[i]); + + for (i = 0; i < ARRAY_SIZE(data->tolerance); i++) { + data->tolerance[i] = + w83793_read_value(client, W83793_REG_TEMP_TOL(i)); + } + + for (i = 0; i < ARRAY_SIZE(data->pwm); i++) { + if (!(data->has_pwm & (1 << i))) + continue; + data->pwm[i][PWM_NONSTOP] = + w83793_read_value(client, W83793_REG_PWM(i, PWM_NONSTOP)); + data->pwm[i][PWM_START] = + w83793_read_value(client, W83793_REG_PWM(i, PWM_START)); + data->pwm_stop_time[i] = + w83793_read_value(client, W83793_REG_PWM_STOP_TIME(i)); + } + + data->pwm_default = w83793_read_value(client, W83793_REG_PWM_DEFAULT); + data->pwm_enable = w83793_read_value(client, W83793_REG_PWM_ENABLE); + data->pwm_uptime = w83793_read_value(client, W83793_REG_PWM_UPTIME); + data->pwm_downtime = w83793_read_value(client, W83793_REG_PWM_DOWNTIME); + data->temp_critical = + w83793_read_value(client, W83793_REG_TEMP_CRITICAL); + data->beep_enable = w83793_read_value(client, W83793_REG_OVT_BEEP); + + for (i = 0; i < ARRAY_SIZE(data->beeps); i++) + data->beeps[i] = w83793_read_value(client, W83793_REG_BEEP(i)); + + data->last_nonvolatile = jiffies; +} + +static struct w83793_data *w83793_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83793_data *data = i2c_get_clientdata(client); + int i; + + mutex_lock(&data->update_lock); + + if (!(time_after(jiffies, data->last_updated + HZ * 2) + || !data->valid)) + goto END; + + /* Update the voltages measured value and limits */ + for (i = 0; i < ARRAY_SIZE(data->in); i++) + data->in[i][IN_READ] = + w83793_read_value(client, W83793_REG_IN[i][IN_READ]); + + data->in_low_bits[IN_READ] = + w83793_read_value(client, W83793_REG_IN_LOW_BITS[IN_READ]); + + for (i = 0; i < ARRAY_SIZE(data->fan); i++) { + if (!(data->has_fan & (1 << i))) + continue; + data->fan[i] = + w83793_read_value(client, W83793_REG_FAN(i)) << 8; + data->fan[i] |= + w83793_read_value(client, W83793_REG_FAN(i) + 1); + } + + for (i = 0; i < ARRAY_SIZE(data->temp); i++) { + if (!(data->has_temp & (1 << i))) + continue; + data->temp[i][TEMP_READ] = + w83793_read_value(client, W83793_REG_TEMP[i][TEMP_READ]); + } + + data->temp_low_bits = + w83793_read_value(client, W83793_REG_TEMP_LOW_BITS); + + for (i = 0; i < ARRAY_SIZE(data->pwm); i++) { + if (data->has_pwm & (1 << i)) + data->pwm[i][PWM_DUTY] = + w83793_read_value(client, + W83793_REG_PWM(i, PWM_DUTY)); + } + + for (i = 0; i < ARRAY_SIZE(data->alarms); i++) + data->alarms[i] = + w83793_read_value(client, W83793_REG_ALARM(i)); + if (data->has_vid & 0x01) + data->vid[0] = w83793_read_value(client, W83793_REG_VID_INA); + if (data->has_vid & 0x02) + data->vid[1] = w83793_read_value(client, W83793_REG_VID_INB); + w83793_update_nonvolatile(dev); + data->last_updated = jiffies; + data->valid = 1; + +END: + mutex_unlock(&data->update_lock); + return data; +} + +/* + * Ignore the possibility that somebody change bank outside the driver + * Must be called with data->update_lock held, except during initialization + */ +static u8 w83793_read_value(struct i2c_client *client, u16 reg) +{ + struct w83793_data *data = i2c_get_clientdata(client); + u8 res = 0xff; + u8 new_bank = reg >> 8; + + new_bank |= data->bank & 0xfc; + if (data->bank != new_bank) { + if (i2c_smbus_write_byte_data + (client, W83793_REG_BANKSEL, new_bank) >= 0) + data->bank = new_bank; + else { + dev_err(&client->dev, + "set bank to %d failed, fall back " + "to bank %d, read reg 0x%x error\n", + new_bank, data->bank, reg); + res = 0x0; /* read 0x0 from the chip */ + goto END; + } + } + res = i2c_smbus_read_byte_data(client, reg & 0xff); +END: + return res; +} + +/* Must be called with data->update_lock held, except during initialization */ +static int w83793_write_value(struct i2c_client *client, u16 reg, u8 value) +{ + struct w83793_data *data = i2c_get_clientdata(client); + int res; + u8 new_bank = reg >> 8; + + new_bank |= data->bank & 0xfc; + if (data->bank != new_bank) { + res = i2c_smbus_write_byte_data(client, W83793_REG_BANKSEL, + new_bank); + if (res < 0) { + dev_err(&client->dev, + "set bank to %d failed, fall back " + "to bank %d, write reg 0x%x error\n", + new_bank, data->bank, reg); + goto END; + } + data->bank = new_bank; + } + + res = i2c_smbus_write_byte_data(client, reg & 0xff, value); +END: + return res; +} + +module_i2c_driver(w83793_driver); + +MODULE_AUTHOR("Yuan Mu, Sven Anders"); +MODULE_DESCRIPTION("w83793 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/w83795.c b/drivers/hwmon/w83795.c new file mode 100644 index 000000000..1bb80f992 --- /dev/null +++ b/drivers/hwmon/w83795.c @@ -0,0 +1,2285 @@ +/* + * w83795.c - Linux kernel driver for hardware monitoring + * Copyright (C) 2008 Nuvoton Technology Corp. + * Wei Song + * Copyright (C) 2010 Jean Delvare + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation - version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA. + * + * Supports following chips: + * + * Chip #vin #fanin #pwm #temp #dts wchipid vendid i2c ISA + * w83795g 21 14 8 6 8 0x79 0x5ca3 yes no + * w83795adg 18 14 2 6 8 0x79 0x5ca3 yes no + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { + 0x2c, 0x2d, 0x2e, 0x2f, I2C_CLIENT_END +}; + + +static bool reset; +module_param(reset, bool, 0); +MODULE_PARM_DESC(reset, "Set to 1 to reset chip, not recommended"); + + +#define W83795_REG_BANKSEL 0x00 +#define W83795_REG_VENDORID 0xfd +#define W83795_REG_CHIPID 0xfe +#define W83795_REG_DEVICEID 0xfb +#define W83795_REG_DEVICEID_A 0xff + +#define W83795_REG_I2C_ADDR 0xfc +#define W83795_REG_CONFIG 0x01 +#define W83795_REG_CONFIG_CONFIG48 0x04 +#define W83795_REG_CONFIG_START 0x01 + +/* Multi-Function Pin Ctrl Registers */ +#define W83795_REG_VOLT_CTRL1 0x02 +#define W83795_REG_VOLT_CTRL2 0x03 +#define W83795_REG_TEMP_CTRL1 0x04 +#define W83795_REG_TEMP_CTRL2 0x05 +#define W83795_REG_FANIN_CTRL1 0x06 +#define W83795_REG_FANIN_CTRL2 0x07 +#define W83795_REG_VMIGB_CTRL 0x08 + +#define TEMP_READ 0 +#define TEMP_CRIT 1 +#define TEMP_CRIT_HYST 2 +#define TEMP_WARN 3 +#define TEMP_WARN_HYST 4 +/* + * only crit and crit_hyst affect real-time alarm status + * current crit crit_hyst warn warn_hyst + */ +static const u16 W83795_REG_TEMP[][5] = { + {0x21, 0x96, 0x97, 0x98, 0x99}, /* TD1/TR1 */ + {0x22, 0x9a, 0x9b, 0x9c, 0x9d}, /* TD2/TR2 */ + {0x23, 0x9e, 0x9f, 0xa0, 0xa1}, /* TD3/TR3 */ + {0x24, 0xa2, 0xa3, 0xa4, 0xa5}, /* TD4/TR4 */ + {0x1f, 0xa6, 0xa7, 0xa8, 0xa9}, /* TR5 */ + {0x20, 0xaa, 0xab, 0xac, 0xad}, /* TR6 */ +}; + +#define IN_READ 0 +#define IN_MAX 1 +#define IN_LOW 2 +static const u16 W83795_REG_IN[][3] = { + /* Current, HL, LL */ + {0x10, 0x70, 0x71}, /* VSEN1 */ + {0x11, 0x72, 0x73}, /* VSEN2 */ + {0x12, 0x74, 0x75}, /* VSEN3 */ + {0x13, 0x76, 0x77}, /* VSEN4 */ + {0x14, 0x78, 0x79}, /* VSEN5 */ + {0x15, 0x7a, 0x7b}, /* VSEN6 */ + {0x16, 0x7c, 0x7d}, /* VSEN7 */ + {0x17, 0x7e, 0x7f}, /* VSEN8 */ + {0x18, 0x80, 0x81}, /* VSEN9 */ + {0x19, 0x82, 0x83}, /* VSEN10 */ + {0x1A, 0x84, 0x85}, /* VSEN11 */ + {0x1B, 0x86, 0x87}, /* VTT */ + {0x1C, 0x88, 0x89}, /* 3VDD */ + {0x1D, 0x8a, 0x8b}, /* 3VSB */ + {0x1E, 0x8c, 0x8d}, /* VBAT */ + {0x1F, 0xa6, 0xa7}, /* VSEN12 */ + {0x20, 0xaa, 0xab}, /* VSEN13 */ + {0x21, 0x96, 0x97}, /* VSEN14 */ + {0x22, 0x9a, 0x9b}, /* VSEN15 */ + {0x23, 0x9e, 0x9f}, /* VSEN16 */ + {0x24, 0xa2, 0xa3}, /* VSEN17 */ +}; +#define W83795_REG_VRLSB 0x3C + +static const u8 W83795_REG_IN_HL_LSB[] = { + 0x8e, /* VSEN1-4 */ + 0x90, /* VSEN5-8 */ + 0x92, /* VSEN9-11 */ + 0x94, /* VTT, 3VDD, 3VSB, 3VBAT */ + 0xa8, /* VSEN12 */ + 0xac, /* VSEN13 */ + 0x98, /* VSEN14 */ + 0x9c, /* VSEN15 */ + 0xa0, /* VSEN16 */ + 0xa4, /* VSEN17 */ +}; + +#define IN_LSB_REG(index, type) \ + (((type) == 1) ? W83795_REG_IN_HL_LSB[(index)] \ + : (W83795_REG_IN_HL_LSB[(index)] + 1)) + +#define IN_LSB_SHIFT 0 +#define IN_LSB_IDX 1 +static const u8 IN_LSB_SHIFT_IDX[][2] = { + /* High/Low LSB shift, LSB No. */ + {0x00, 0x00}, /* VSEN1 */ + {0x02, 0x00}, /* VSEN2 */ + {0x04, 0x00}, /* VSEN3 */ + {0x06, 0x00}, /* VSEN4 */ + {0x00, 0x01}, /* VSEN5 */ + {0x02, 0x01}, /* VSEN6 */ + {0x04, 0x01}, /* VSEN7 */ + {0x06, 0x01}, /* VSEN8 */ + {0x00, 0x02}, /* VSEN9 */ + {0x02, 0x02}, /* VSEN10 */ + {0x04, 0x02}, /* VSEN11 */ + {0x00, 0x03}, /* VTT */ + {0x02, 0x03}, /* 3VDD */ + {0x04, 0x03}, /* 3VSB */ + {0x06, 0x03}, /* VBAT */ + {0x06, 0x04}, /* VSEN12 */ + {0x06, 0x05}, /* VSEN13 */ + {0x06, 0x06}, /* VSEN14 */ + {0x06, 0x07}, /* VSEN15 */ + {0x06, 0x08}, /* VSEN16 */ + {0x06, 0x09}, /* VSEN17 */ +}; + + +#define W83795_REG_FAN(index) (0x2E + (index)) +#define W83795_REG_FAN_MIN_HL(index) (0xB6 + (index)) +#define W83795_REG_FAN_MIN_LSB(index) (0xC4 + (index) / 2) +#define W83795_REG_FAN_MIN_LSB_SHIFT(index) \ + (((index) & 1) ? 4 : 0) + +#define W83795_REG_VID_CTRL 0x6A + +#define W83795_REG_ALARM_CTRL 0x40 +#define ALARM_CTRL_RTSACS (1 << 7) +#define W83795_REG_ALARM(index) (0x41 + (index)) +#define W83795_REG_CLR_CHASSIS 0x4D +#define W83795_REG_BEEP(index) (0x50 + (index)) + +#define W83795_REG_OVT_CFG 0x58 +#define OVT_CFG_SEL (1 << 7) + + +#define W83795_REG_FCMS1 0x201 +#define W83795_REG_FCMS2 0x208 +#define W83795_REG_TFMR(index) (0x202 + (index)) +#define W83795_REG_FOMC 0x20F + +#define W83795_REG_TSS(index) (0x209 + (index)) + +#define TSS_MAP_RESERVED 0xff +static const u8 tss_map[4][6] = { + { 0, 1, 2, 3, 4, 5}, + { 6, 7, 8, 9, 0, 1}, + {10, 11, 12, 13, 2, 3}, + { 4, 5, 4, 5, TSS_MAP_RESERVED, TSS_MAP_RESERVED}, +}; + +#define PWM_OUTPUT 0 +#define PWM_FREQ 1 +#define PWM_START 2 +#define PWM_NONSTOP 3 +#define PWM_STOP_TIME 4 +#define W83795_REG_PWM(index, nr) (0x210 + (nr) * 8 + (index)) + +#define W83795_REG_FTSH(index) (0x240 + (index) * 2) +#define W83795_REG_FTSL(index) (0x241 + (index) * 2) +#define W83795_REG_TFTS 0x250 + +#define TEMP_PWM_TTTI 0 +#define TEMP_PWM_CTFS 1 +#define TEMP_PWM_HCT 2 +#define TEMP_PWM_HOT 3 +#define W83795_REG_TTTI(index) (0x260 + (index)) +#define W83795_REG_CTFS(index) (0x268 + (index)) +#define W83795_REG_HT(index) (0x270 + (index)) + +#define SF4_TEMP 0 +#define SF4_PWM 1 +#define W83795_REG_SF4_TEMP(temp_num, index) \ + (0x280 + 0x10 * (temp_num) + (index)) +#define W83795_REG_SF4_PWM(temp_num, index) \ + (0x288 + 0x10 * (temp_num) + (index)) + +#define W83795_REG_DTSC 0x301 +#define W83795_REG_DTSE 0x302 +#define W83795_REG_DTS(index) (0x26 + (index)) +#define W83795_REG_PECI_TBASE(index) (0x320 + (index)) + +#define DTS_CRIT 0 +#define DTS_CRIT_HYST 1 +#define DTS_WARN 2 +#define DTS_WARN_HYST 3 +#define W83795_REG_DTS_EXT(index) (0xB2 + (index)) + +#define SETUP_PWM_DEFAULT 0 +#define SETUP_PWM_UPTIME 1 +#define SETUP_PWM_DOWNTIME 2 +#define W83795_REG_SETUP_PWM(index) (0x20C + (index)) + +static inline u16 in_from_reg(u8 index, u16 val) +{ + /* 3VDD, 3VSB and VBAT: 6 mV/bit; other inputs: 2 mV/bit */ + if (index >= 12 && index <= 14) + return val * 6; + else + return val * 2; +} + +static inline u16 in_to_reg(u8 index, u16 val) +{ + if (index >= 12 && index <= 14) + return val / 6; + else + return val / 2; +} + +static inline unsigned long fan_from_reg(u16 val) +{ + if ((val == 0xfff) || (val == 0)) + return 0; + return 1350000UL / val; +} + +static inline u16 fan_to_reg(long rpm) +{ + if (rpm <= 0) + return 0x0fff; + return clamp_val((1350000 + (rpm >> 1)) / rpm, 1, 0xffe); +} + +static inline unsigned long time_from_reg(u8 reg) +{ + return reg * 100; +} + +static inline u8 time_to_reg(unsigned long val) +{ + return clamp_val((val + 50) / 100, 0, 0xff); +} + +static inline long temp_from_reg(s8 reg) +{ + return reg * 1000; +} + +static inline s8 temp_to_reg(long val, s8 min, s8 max) +{ + return clamp_val(val / 1000, min, max); +} + +static const u16 pwm_freq_cksel0[16] = { + 1024, 512, 341, 256, 205, 171, 146, 128, + 85, 64, 32, 16, 8, 4, 2, 1 +}; + +static unsigned int pwm_freq_from_reg(u8 reg, u16 clkin) +{ + unsigned long base_clock; + + if (reg & 0x80) { + base_clock = clkin * 1000 / ((clkin == 48000) ? 384 : 256); + return base_clock / ((reg & 0x7f) + 1); + } else + return pwm_freq_cksel0[reg & 0x0f]; +} + +static u8 pwm_freq_to_reg(unsigned long val, u16 clkin) +{ + unsigned long base_clock; + u8 reg0, reg1; + unsigned long best0, best1; + + /* Best fit for cksel = 0 */ + reg0 = find_closest_descending(val, pwm_freq_cksel0, + ARRAY_SIZE(pwm_freq_cksel0)); + if (val < 375) /* cksel = 1 can't beat this */ + return reg0; + best0 = pwm_freq_cksel0[reg0]; + + /* Best fit for cksel = 1 */ + base_clock = clkin * 1000 / ((clkin == 48000) ? 384 : 256); + reg1 = clamp_val(DIV_ROUND_CLOSEST(base_clock, val), 1, 128); + best1 = base_clock / reg1; + reg1 = 0x80 | (reg1 - 1); + + /* Choose the closest one */ + if (abs(val - best0) > abs(val - best1)) + return reg1; + else + return reg0; +} + +enum chip_types {w83795g, w83795adg}; + +struct w83795_data { + struct device *hwmon_dev; + struct mutex update_lock; + unsigned long last_updated; /* In jiffies */ + enum chip_types chip_type; + + u8 bank; + + u32 has_in; /* Enable monitor VIN or not */ + u8 has_dyn_in; /* Only in2-0 can have this */ + u16 in[21][3]; /* Register value, read/high/low */ + u8 in_lsb[10][3]; /* LSB Register value, high/low */ + u8 has_gain; /* has gain: in17-20 * 8 */ + + u16 has_fan; /* Enable fan14-1 or not */ + u16 fan[14]; /* Register value combine */ + u16 fan_min[14]; /* Register value combine */ + + u8 has_temp; /* Enable monitor temp6-1 or not */ + s8 temp[6][5]; /* current, crit, crit_hyst, warn, warn_hyst */ + u8 temp_read_vrlsb[6]; + u8 temp_mode; /* Bit vector, 0 = TR, 1 = TD */ + u8 temp_src[3]; /* Register value */ + + u8 enable_dts; /* + * Enable PECI and SB-TSI, + * bit 0: =1 enable, =0 disable, + * bit 1: =1 AMD SB-TSI, =0 Intel PECI + */ + u8 has_dts; /* Enable monitor DTS temp */ + s8 dts[8]; /* Register value */ + u8 dts_read_vrlsb[8]; /* Register value */ + s8 dts_ext[4]; /* Register value */ + + u8 has_pwm; /* + * 795g supports 8 pwm, 795adg only supports 2, + * no config register, only affected by chip + * type + */ + u8 pwm[8][5]; /* + * Register value, output, freq, start, + * non stop, stop time + */ + u16 clkin; /* CLKIN frequency in kHz */ + u8 pwm_fcms[2]; /* Register value */ + u8 pwm_tfmr[6]; /* Register value */ + u8 pwm_fomc; /* Register value */ + + u16 target_speed[8]; /* + * Register value, target speed for speed + * cruise + */ + u8 tol_speed; /* tolerance of target speed */ + u8 pwm_temp[6][4]; /* TTTI, CTFS, HCT, HOT */ + u8 sf4_reg[6][2][7]; /* 6 temp, temp/dcpwm, 7 registers */ + + u8 setup_pwm[3]; /* Register value */ + + u8 alarms[6]; /* Register value */ + u8 enable_beep; + u8 beeps[6]; /* Register value */ + + char valid; + char valid_limits; + char valid_pwm_config; +}; + +/* + * Hardware access + * We assume that nobdody can change the bank outside the driver. + */ + +/* Must be called with data->update_lock held, except during initialization */ +static int w83795_set_bank(struct i2c_client *client, u8 bank) +{ + struct w83795_data *data = i2c_get_clientdata(client); + int err; + + /* If the same bank is already set, nothing to do */ + if ((data->bank & 0x07) == bank) + return 0; + + /* Change to new bank, preserve all other bits */ + bank |= data->bank & ~0x07; + err = i2c_smbus_write_byte_data(client, W83795_REG_BANKSEL, bank); + if (err < 0) { + dev_err(&client->dev, + "Failed to set bank to %d, err %d\n", + (int)bank, err); + return err; + } + data->bank = bank; + + return 0; +} + +/* Must be called with data->update_lock held, except during initialization */ +static u8 w83795_read(struct i2c_client *client, u16 reg) +{ + int err; + + err = w83795_set_bank(client, reg >> 8); + if (err < 0) + return 0x00; /* Arbitrary */ + + err = i2c_smbus_read_byte_data(client, reg & 0xff); + if (err < 0) { + dev_err(&client->dev, + "Failed to read from register 0x%03x, err %d\n", + (int)reg, err); + return 0x00; /* Arbitrary */ + } + return err; +} + +/* Must be called with data->update_lock held, except during initialization */ +static int w83795_write(struct i2c_client *client, u16 reg, u8 value) +{ + int err; + + err = w83795_set_bank(client, reg >> 8); + if (err < 0) + return err; + + err = i2c_smbus_write_byte_data(client, reg & 0xff, value); + if (err < 0) + dev_err(&client->dev, + "Failed to write to register 0x%03x, err %d\n", + (int)reg, err); + return err; +} + +static void w83795_update_limits(struct i2c_client *client) +{ + struct w83795_data *data = i2c_get_clientdata(client); + int i, limit; + u8 lsb; + + /* Read the voltage limits */ + for (i = 0; i < ARRAY_SIZE(data->in); i++) { + if (!(data->has_in & (1 << i))) + continue; + data->in[i][IN_MAX] = + w83795_read(client, W83795_REG_IN[i][IN_MAX]); + data->in[i][IN_LOW] = + w83795_read(client, W83795_REG_IN[i][IN_LOW]); + } + for (i = 0; i < ARRAY_SIZE(data->in_lsb); i++) { + if ((i == 2 && data->chip_type == w83795adg) || + (i >= 4 && !(data->has_in & (1 << (i + 11))))) + continue; + data->in_lsb[i][IN_MAX] = + w83795_read(client, IN_LSB_REG(i, IN_MAX)); + data->in_lsb[i][IN_LOW] = + w83795_read(client, IN_LSB_REG(i, IN_LOW)); + } + + /* Read the fan limits */ + lsb = 0; /* Silent false gcc warning */ + for (i = 0; i < ARRAY_SIZE(data->fan); i++) { + /* + * Each register contains LSB for 2 fans, but we want to + * read it only once to save time + */ + if ((i & 1) == 0 && (data->has_fan & (3 << i))) + lsb = w83795_read(client, W83795_REG_FAN_MIN_LSB(i)); + + if (!(data->has_fan & (1 << i))) + continue; + data->fan_min[i] = + w83795_read(client, W83795_REG_FAN_MIN_HL(i)) << 4; + data->fan_min[i] |= + (lsb >> W83795_REG_FAN_MIN_LSB_SHIFT(i)) & 0x0F; + } + + /* Read the temperature limits */ + for (i = 0; i < ARRAY_SIZE(data->temp); i++) { + if (!(data->has_temp & (1 << i))) + continue; + for (limit = TEMP_CRIT; limit <= TEMP_WARN_HYST; limit++) + data->temp[i][limit] = + w83795_read(client, W83795_REG_TEMP[i][limit]); + } + + /* Read the DTS limits */ + if (data->enable_dts) { + for (limit = DTS_CRIT; limit <= DTS_WARN_HYST; limit++) + data->dts_ext[limit] = + w83795_read(client, W83795_REG_DTS_EXT(limit)); + } + + /* Read beep settings */ + if (data->enable_beep) { + for (i = 0; i < ARRAY_SIZE(data->beeps); i++) + data->beeps[i] = + w83795_read(client, W83795_REG_BEEP(i)); + } + + data->valid_limits = 1; +} + +static struct w83795_data *w83795_update_pwm_config(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = i2c_get_clientdata(client); + int i, tmp; + + mutex_lock(&data->update_lock); + + if (data->valid_pwm_config) + goto END; + + /* Read temperature source selection */ + for (i = 0; i < ARRAY_SIZE(data->temp_src); i++) + data->temp_src[i] = w83795_read(client, W83795_REG_TSS(i)); + + /* Read automatic fan speed control settings */ + data->pwm_fcms[0] = w83795_read(client, W83795_REG_FCMS1); + data->pwm_fcms[1] = w83795_read(client, W83795_REG_FCMS2); + for (i = 0; i < ARRAY_SIZE(data->pwm_tfmr); i++) + data->pwm_tfmr[i] = w83795_read(client, W83795_REG_TFMR(i)); + data->pwm_fomc = w83795_read(client, W83795_REG_FOMC); + for (i = 0; i < data->has_pwm; i++) { + for (tmp = PWM_FREQ; tmp <= PWM_STOP_TIME; tmp++) + data->pwm[i][tmp] = + w83795_read(client, W83795_REG_PWM(i, tmp)); + } + for (i = 0; i < ARRAY_SIZE(data->target_speed); i++) { + data->target_speed[i] = + w83795_read(client, W83795_REG_FTSH(i)) << 4; + data->target_speed[i] |= + w83795_read(client, W83795_REG_FTSL(i)) >> 4; + } + data->tol_speed = w83795_read(client, W83795_REG_TFTS) & 0x3f; + + for (i = 0; i < ARRAY_SIZE(data->pwm_temp); i++) { + data->pwm_temp[i][TEMP_PWM_TTTI] = + w83795_read(client, W83795_REG_TTTI(i)) & 0x7f; + data->pwm_temp[i][TEMP_PWM_CTFS] = + w83795_read(client, W83795_REG_CTFS(i)); + tmp = w83795_read(client, W83795_REG_HT(i)); + data->pwm_temp[i][TEMP_PWM_HCT] = tmp >> 4; + data->pwm_temp[i][TEMP_PWM_HOT] = tmp & 0x0f; + } + + /* Read SmartFanIV trip points */ + for (i = 0; i < ARRAY_SIZE(data->sf4_reg); i++) { + for (tmp = 0; tmp < 7; tmp++) { + data->sf4_reg[i][SF4_TEMP][tmp] = + w83795_read(client, + W83795_REG_SF4_TEMP(i, tmp)); + data->sf4_reg[i][SF4_PWM][tmp] = + w83795_read(client, W83795_REG_SF4_PWM(i, tmp)); + } + } + + /* Read setup PWM */ + for (i = 0; i < ARRAY_SIZE(data->setup_pwm); i++) + data->setup_pwm[i] = + w83795_read(client, W83795_REG_SETUP_PWM(i)); + + data->valid_pwm_config = 1; + +END: + mutex_unlock(&data->update_lock); + return data; +} + +static struct w83795_data *w83795_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = i2c_get_clientdata(client); + u16 tmp; + u8 intrusion; + int i; + + mutex_lock(&data->update_lock); + + if (!data->valid_limits) + w83795_update_limits(client); + + if (!(time_after(jiffies, data->last_updated + HZ * 2) + || !data->valid)) + goto END; + + /* Update the voltages value */ + for (i = 0; i < ARRAY_SIZE(data->in); i++) { + if (!(data->has_in & (1 << i))) + continue; + tmp = w83795_read(client, W83795_REG_IN[i][IN_READ]) << 2; + tmp |= w83795_read(client, W83795_REG_VRLSB) >> 6; + data->in[i][IN_READ] = tmp; + } + + /* in0-2 can have dynamic limits (W83795G only) */ + if (data->has_dyn_in) { + u8 lsb_max = w83795_read(client, IN_LSB_REG(0, IN_MAX)); + u8 lsb_low = w83795_read(client, IN_LSB_REG(0, IN_LOW)); + + for (i = 0; i < 3; i++) { + if (!(data->has_dyn_in & (1 << i))) + continue; + data->in[i][IN_MAX] = + w83795_read(client, W83795_REG_IN[i][IN_MAX]); + data->in[i][IN_LOW] = + w83795_read(client, W83795_REG_IN[i][IN_LOW]); + data->in_lsb[i][IN_MAX] = (lsb_max >> (2 * i)) & 0x03; + data->in_lsb[i][IN_LOW] = (lsb_low >> (2 * i)) & 0x03; + } + } + + /* Update fan */ + for (i = 0; i < ARRAY_SIZE(data->fan); i++) { + if (!(data->has_fan & (1 << i))) + continue; + data->fan[i] = w83795_read(client, W83795_REG_FAN(i)) << 4; + data->fan[i] |= w83795_read(client, W83795_REG_VRLSB) >> 4; + } + + /* Update temperature */ + for (i = 0; i < ARRAY_SIZE(data->temp); i++) { + data->temp[i][TEMP_READ] = + w83795_read(client, W83795_REG_TEMP[i][TEMP_READ]); + data->temp_read_vrlsb[i] = + w83795_read(client, W83795_REG_VRLSB); + } + + /* Update dts temperature */ + if (data->enable_dts) { + for (i = 0; i < ARRAY_SIZE(data->dts); i++) { + if (!(data->has_dts & (1 << i))) + continue; + data->dts[i] = + w83795_read(client, W83795_REG_DTS(i)); + data->dts_read_vrlsb[i] = + w83795_read(client, W83795_REG_VRLSB); + } + } + + /* Update pwm output */ + for (i = 0; i < data->has_pwm; i++) { + data->pwm[i][PWM_OUTPUT] = + w83795_read(client, W83795_REG_PWM(i, PWM_OUTPUT)); + } + + /* + * Update intrusion and alarms + * It is important to read intrusion first, because reading from + * register SMI STS6 clears the interrupt status temporarily. + */ + tmp = w83795_read(client, W83795_REG_ALARM_CTRL); + /* Switch to interrupt status for intrusion if needed */ + if (tmp & ALARM_CTRL_RTSACS) + w83795_write(client, W83795_REG_ALARM_CTRL, + tmp & ~ALARM_CTRL_RTSACS); + intrusion = w83795_read(client, W83795_REG_ALARM(5)) & (1 << 6); + /* Switch to real-time alarms */ + w83795_write(client, W83795_REG_ALARM_CTRL, tmp | ALARM_CTRL_RTSACS); + for (i = 0; i < ARRAY_SIZE(data->alarms); i++) + data->alarms[i] = w83795_read(client, W83795_REG_ALARM(i)); + data->alarms[5] |= intrusion; + /* Restore original configuration if needed */ + if (!(tmp & ALARM_CTRL_RTSACS)) + w83795_write(client, W83795_REG_ALARM_CTRL, + tmp & ~ALARM_CTRL_RTSACS); + + data->last_updated = jiffies; + data->valid = 1; + +END: + mutex_unlock(&data->update_lock); + return data; +} + +/* + * Sysfs attributes + */ + +#define ALARM_STATUS 0 +#define BEEP_ENABLE 1 +static ssize_t +show_alarm_beep(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83795_data *data = w83795_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index >> 3; + int bit = sensor_attr->index & 0x07; + u8 val; + + if (nr == ALARM_STATUS) + val = (data->alarms[index] >> bit) & 1; + else /* BEEP_ENABLE */ + val = (data->beeps[index] >> bit) & 1; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t +store_beep(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index >> 3; + int shift = sensor_attr->index & 0x07; + u8 beep_bit = 1 << shift; + unsigned long val; + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + if (val != 0 && val != 1) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->beeps[index] = w83795_read(client, W83795_REG_BEEP(index)); + data->beeps[index] &= ~beep_bit; + data->beeps[index] |= val << shift; + w83795_write(client, W83795_REG_BEEP(index), data->beeps[index]); + mutex_unlock(&data->update_lock); + + return count; +} + +/* Write 0 to clear chassis alarm */ +static ssize_t +store_chassis_clear(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = i2c_get_clientdata(client); + unsigned long val; + + if (kstrtoul(buf, 10, &val) < 0 || val != 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + val = w83795_read(client, W83795_REG_CLR_CHASSIS); + val |= 0x80; + w83795_write(client, W83795_REG_CLR_CHASSIS, val); + + /* Clear status and force cache refresh */ + w83795_read(client, W83795_REG_ALARM(5)); + data->valid = 0; + mutex_unlock(&data->update_lock); + return count; +} + +#define FAN_INPUT 0 +#define FAN_MIN 1 +static ssize_t +show_fan(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83795_data *data = w83795_update_device(dev); + u16 val; + + if (nr == FAN_INPUT) + val = data->fan[index] & 0x0fff; + else + val = data->fan_min[index] & 0x0fff; + + return sprintf(buf, "%lu\n", fan_from_reg(val)); +} + +static ssize_t +store_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index; + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = i2c_get_clientdata(client); + unsigned long val; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + val = fan_to_reg(val); + + mutex_lock(&data->update_lock); + data->fan_min[index] = val; + w83795_write(client, W83795_REG_FAN_MIN_HL(index), (val >> 4) & 0xff); + val &= 0x0f; + if (index & 1) { + val <<= 4; + val |= w83795_read(client, W83795_REG_FAN_MIN_LSB(index)) + & 0x0f; + } else { + val |= w83795_read(client, W83795_REG_FAN_MIN_LSB(index)) + & 0xf0; + } + w83795_write(client, W83795_REG_FAN_MIN_LSB(index), val & 0xff); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_pwm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83795_data *data; + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + unsigned int val; + + data = nr == PWM_OUTPUT ? w83795_update_device(dev) + : w83795_update_pwm_config(dev); + + switch (nr) { + case PWM_STOP_TIME: + val = time_from_reg(data->pwm[index][nr]); + break; + case PWM_FREQ: + val = pwm_freq_from_reg(data->pwm[index][nr], data->clkin); + break; + default: + val = data->pwm[index][nr]; + break; + } + + return sprintf(buf, "%u\n", val); +} + +static ssize_t +store_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + unsigned long val; + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + switch (nr) { + case PWM_STOP_TIME: + val = time_to_reg(val); + break; + case PWM_FREQ: + val = pwm_freq_to_reg(val, data->clkin); + break; + default: + val = clamp_val(val, 0, 0xff); + break; + } + w83795_write(client, W83795_REG_PWM(index, nr), val); + data->pwm[index][nr] = val; + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_pwm_enable(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + struct w83795_data *data = w83795_update_pwm_config(dev); + int index = sensor_attr->index; + u8 tmp; + + /* Speed cruise mode */ + if (data->pwm_fcms[0] & (1 << index)) { + tmp = 2; + goto out; + } + /* Thermal cruise or SmartFan IV mode */ + for (tmp = 0; tmp < 6; tmp++) { + if (data->pwm_tfmr[tmp] & (1 << index)) { + tmp = 3; + goto out; + } + } + /* Manual mode */ + tmp = 1; + +out: + return sprintf(buf, "%u\n", tmp); +} + +static ssize_t +store_pwm_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = w83795_update_pwm_config(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index; + unsigned long val; + int i; + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + if (val < 1 || val > 2) + return -EINVAL; + +#ifndef CONFIG_SENSORS_W83795_FANCTRL + if (val > 1) { + dev_warn(dev, "Automatic fan speed control support disabled\n"); + dev_warn(dev, "Build with CONFIG_SENSORS_W83795_FANCTRL=y if you want it\n"); + return -EOPNOTSUPP; + } +#endif + + mutex_lock(&data->update_lock); + switch (val) { + case 1: + /* Clear speed cruise mode bits */ + data->pwm_fcms[0] &= ~(1 << index); + w83795_write(client, W83795_REG_FCMS1, data->pwm_fcms[0]); + /* Clear thermal cruise mode bits */ + for (i = 0; i < 6; i++) { + data->pwm_tfmr[i] &= ~(1 << index); + w83795_write(client, W83795_REG_TFMR(i), + data->pwm_tfmr[i]); + } + break; + case 2: + data->pwm_fcms[0] |= (1 << index); + w83795_write(client, W83795_REG_FCMS1, data->pwm_fcms[0]); + break; + } + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_pwm_mode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83795_data *data = w83795_update_pwm_config(dev); + int index = to_sensor_dev_attr_2(attr)->index; + unsigned int mode; + + if (data->pwm_fomc & (1 << index)) + mode = 0; /* DC */ + else + mode = 1; /* PWM */ + + return sprintf(buf, "%u\n", mode); +} + +/* + * Check whether a given temperature source can ever be useful. + * Returns the number of selectable temperature channels which are + * enabled. + */ +static int w83795_tss_useful(const struct w83795_data *data, int tsrc) +{ + int useful = 0, i; + + for (i = 0; i < 4; i++) { + if (tss_map[i][tsrc] == TSS_MAP_RESERVED) + continue; + if (tss_map[i][tsrc] < 6) /* Analog */ + useful += (data->has_temp >> tss_map[i][tsrc]) & 1; + else /* Digital */ + useful += (data->has_dts >> (tss_map[i][tsrc] - 6)) & 1; + } + + return useful; +} + +static ssize_t +show_temp_src(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + struct w83795_data *data = w83795_update_pwm_config(dev); + int index = sensor_attr->index; + u8 tmp = data->temp_src[index / 2]; + + if (index & 1) + tmp >>= 4; /* Pick high nibble */ + else + tmp &= 0x0f; /* Pick low nibble */ + + /* Look-up the actual temperature channel number */ + if (tmp >= 4 || tss_map[tmp][index] == TSS_MAP_RESERVED) + return -EINVAL; /* Shouldn't happen */ + + return sprintf(buf, "%u\n", (unsigned int)tss_map[tmp][index] + 1); +} + +static ssize_t +store_temp_src(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = w83795_update_pwm_config(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index; + int tmp; + unsigned long channel; + u8 val = index / 2; + + if (kstrtoul(buf, 10, &channel) < 0 || + channel < 1 || channel > 14) + return -EINVAL; + + /* Check if request can be fulfilled */ + for (tmp = 0; tmp < 4; tmp++) { + if (tss_map[tmp][index] == channel - 1) + break; + } + if (tmp == 4) /* No match */ + return -EINVAL; + + mutex_lock(&data->update_lock); + if (index & 1) { + tmp <<= 4; + data->temp_src[val] &= 0x0f; + } else { + data->temp_src[val] &= 0xf0; + } + data->temp_src[val] |= tmp; + w83795_write(client, W83795_REG_TSS(val), data->temp_src[val]); + mutex_unlock(&data->update_lock); + + return count; +} + +#define TEMP_PWM_ENABLE 0 +#define TEMP_PWM_FAN_MAP 1 +static ssize_t +show_temp_pwm_enable(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w83795_data *data = w83795_update_pwm_config(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + u8 tmp = 0xff; + + switch (nr) { + case TEMP_PWM_ENABLE: + tmp = (data->pwm_fcms[1] >> index) & 1; + if (tmp) + tmp = 4; + else + tmp = 3; + break; + case TEMP_PWM_FAN_MAP: + tmp = data->pwm_tfmr[index]; + break; + } + + return sprintf(buf, "%u\n", tmp); +} + +static ssize_t +store_temp_pwm_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = w83795_update_pwm_config(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + unsigned long tmp; + + if (kstrtoul(buf, 10, &tmp) < 0) + return -EINVAL; + + switch (nr) { + case TEMP_PWM_ENABLE: + if (tmp != 3 && tmp != 4) + return -EINVAL; + tmp -= 3; + mutex_lock(&data->update_lock); + data->pwm_fcms[1] &= ~(1 << index); + data->pwm_fcms[1] |= tmp << index; + w83795_write(client, W83795_REG_FCMS2, data->pwm_fcms[1]); + mutex_unlock(&data->update_lock); + break; + case TEMP_PWM_FAN_MAP: + mutex_lock(&data->update_lock); + tmp = clamp_val(tmp, 0, 0xff); + w83795_write(client, W83795_REG_TFMR(index), tmp); + data->pwm_tfmr[index] = tmp; + mutex_unlock(&data->update_lock); + break; + } + return count; +} + +#define FANIN_TARGET 0 +#define FANIN_TOL 1 +static ssize_t +show_fanin(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83795_data *data = w83795_update_pwm_config(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + u16 tmp = 0; + + switch (nr) { + case FANIN_TARGET: + tmp = fan_from_reg(data->target_speed[index]); + break; + case FANIN_TOL: + tmp = data->tol_speed; + break; + } + + return sprintf(buf, "%u\n", tmp); +} + +static ssize_t +store_fanin(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + unsigned long val; + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + switch (nr) { + case FANIN_TARGET: + val = fan_to_reg(clamp_val(val, 0, 0xfff)); + w83795_write(client, W83795_REG_FTSH(index), val >> 4); + w83795_write(client, W83795_REG_FTSL(index), (val << 4) & 0xf0); + data->target_speed[index] = val; + break; + case FANIN_TOL: + val = clamp_val(val, 0, 0x3f); + w83795_write(client, W83795_REG_TFTS, val); + data->tol_speed = val; + break; + } + mutex_unlock(&data->update_lock); + + return count; +} + + +static ssize_t +show_temp_pwm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83795_data *data = w83795_update_pwm_config(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + long tmp = temp_from_reg(data->pwm_temp[index][nr]); + + return sprintf(buf, "%ld\n", tmp); +} + +static ssize_t +store_temp_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + unsigned long val; + u8 tmp; + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + val /= 1000; + + mutex_lock(&data->update_lock); + switch (nr) { + case TEMP_PWM_TTTI: + val = clamp_val(val, 0, 0x7f); + w83795_write(client, W83795_REG_TTTI(index), val); + break; + case TEMP_PWM_CTFS: + val = clamp_val(val, 0, 0x7f); + w83795_write(client, W83795_REG_CTFS(index), val); + break; + case TEMP_PWM_HCT: + val = clamp_val(val, 0, 0x0f); + tmp = w83795_read(client, W83795_REG_HT(index)); + tmp &= 0x0f; + tmp |= (val << 4) & 0xf0; + w83795_write(client, W83795_REG_HT(index), tmp); + break; + case TEMP_PWM_HOT: + val = clamp_val(val, 0, 0x0f); + tmp = w83795_read(client, W83795_REG_HT(index)); + tmp &= 0xf0; + tmp |= val & 0x0f; + w83795_write(client, W83795_REG_HT(index), tmp); + break; + } + data->pwm_temp[index][nr] = val; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_sf4_pwm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83795_data *data = w83795_update_pwm_config(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + + return sprintf(buf, "%u\n", data->sf4_reg[index][SF4_PWM][nr]); +} + +static ssize_t +store_sf4_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + unsigned long val; + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + w83795_write(client, W83795_REG_SF4_PWM(index, nr), val); + data->sf4_reg[index][SF4_PWM][nr] = val; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_sf4_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83795_data *data = w83795_update_pwm_config(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + + return sprintf(buf, "%u\n", + (data->sf4_reg[index][SF4_TEMP][nr]) * 1000); +} + +static ssize_t +store_sf4_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + unsigned long val; + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + val /= 1000; + + mutex_lock(&data->update_lock); + w83795_write(client, W83795_REG_SF4_TEMP(index, nr), val); + data->sf4_reg[index][SF4_TEMP][nr] = val; + mutex_unlock(&data->update_lock); + + return count; +} + + +static ssize_t +show_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83795_data *data = w83795_update_device(dev); + long temp = temp_from_reg(data->temp[index][nr]); + + if (nr == TEMP_READ) + temp += (data->temp_read_vrlsb[index] >> 6) * 250; + return sprintf(buf, "%ld\n", temp); +} + +static ssize_t +store_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = i2c_get_clientdata(client); + long tmp; + + if (kstrtol(buf, 10, &tmp) < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->temp[index][nr] = temp_to_reg(tmp, -128, 127); + w83795_write(client, W83795_REG_TEMP[index][nr], data->temp[index][nr]); + mutex_unlock(&data->update_lock); + return count; +} + + +static ssize_t +show_dts_mode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83795_data *data = dev_get_drvdata(dev); + int tmp; + + if (data->enable_dts & 2) + tmp = 5; + else + tmp = 6; + + return sprintf(buf, "%d\n", tmp); +} + +static ssize_t +show_dts(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index; + struct w83795_data *data = w83795_update_device(dev); + long temp = temp_from_reg(data->dts[index]); + + temp += (data->dts_read_vrlsb[index] >> 6) * 250; + return sprintf(buf, "%ld\n", temp); +} + +static ssize_t +show_dts_ext(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + struct w83795_data *data = dev_get_drvdata(dev); + long temp = temp_from_reg(data->dts_ext[nr]); + + return sprintf(buf, "%ld\n", temp); +} + +static ssize_t +store_dts_ext(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = i2c_get_clientdata(client); + long tmp; + + if (kstrtol(buf, 10, &tmp) < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->dts_ext[nr] = temp_to_reg(tmp, -128, 127); + w83795_write(client, W83795_REG_DTS_EXT(nr), data->dts_ext[nr]); + mutex_unlock(&data->update_lock); + return count; +} + + +static ssize_t +show_temp_mode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct w83795_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index; + int tmp; + + if (data->temp_mode & (1 << index)) + tmp = 3; /* Thermal diode */ + else + tmp = 4; /* Thermistor */ + + return sprintf(buf, "%d\n", tmp); +} + +/* Only for temp1-4 (temp5-6 can only be thermistor) */ +static ssize_t +store_temp_mode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int index = sensor_attr->index; + int reg_shift; + unsigned long val; + u8 tmp; + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + if ((val != 4) && (val != 3)) + return -EINVAL; + + mutex_lock(&data->update_lock); + if (val == 3) { + /* Thermal diode */ + val = 0x01; + data->temp_mode |= 1 << index; + } else if (val == 4) { + /* Thermistor */ + val = 0x03; + data->temp_mode &= ~(1 << index); + } + + reg_shift = 2 * index; + tmp = w83795_read(client, W83795_REG_TEMP_CTRL2); + tmp &= ~(0x03 << reg_shift); + tmp |= val << reg_shift; + w83795_write(client, W83795_REG_TEMP_CTRL2, tmp); + + mutex_unlock(&data->update_lock); + return count; +} + + +/* show/store VIN */ +static ssize_t +show_in(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83795_data *data = w83795_update_device(dev); + u16 val = data->in[index][nr]; + u8 lsb_idx; + + switch (nr) { + case IN_READ: + /* calculate this value again by sensors as sensors3.conf */ + if ((index >= 17) && + !((data->has_gain >> (index - 17)) & 1)) + val *= 8; + break; + case IN_MAX: + case IN_LOW: + lsb_idx = IN_LSB_SHIFT_IDX[index][IN_LSB_IDX]; + val <<= 2; + val |= (data->in_lsb[lsb_idx][nr] >> + IN_LSB_SHIFT_IDX[index][IN_LSB_SHIFT]) & 0x03; + if ((index >= 17) && + !((data->has_gain >> (index - 17)) & 1)) + val *= 8; + break; + } + val = in_from_reg(index, val); + + return sprintf(buf, "%d\n", val); +} + +static ssize_t +store_in(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = i2c_get_clientdata(client); + unsigned long val; + u8 tmp; + u8 lsb_idx; + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + val = in_to_reg(index, val); + + if ((index >= 17) && + !((data->has_gain >> (index - 17)) & 1)) + val /= 8; + val = clamp_val(val, 0, 0x3FF); + mutex_lock(&data->update_lock); + + lsb_idx = IN_LSB_SHIFT_IDX[index][IN_LSB_IDX]; + tmp = w83795_read(client, IN_LSB_REG(lsb_idx, nr)); + tmp &= ~(0x03 << IN_LSB_SHIFT_IDX[index][IN_LSB_SHIFT]); + tmp |= (val & 0x03) << IN_LSB_SHIFT_IDX[index][IN_LSB_SHIFT]; + w83795_write(client, IN_LSB_REG(lsb_idx, nr), tmp); + data->in_lsb[lsb_idx][nr] = tmp; + + tmp = (val >> 2) & 0xff; + w83795_write(client, W83795_REG_IN[index][nr], tmp); + data->in[index][nr] = tmp; + + mutex_unlock(&data->update_lock); + return count; +} + + +#ifdef CONFIG_SENSORS_W83795_FANCTRL +static ssize_t +show_sf_setup(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + struct w83795_data *data = w83795_update_pwm_config(dev); + u16 val = data->setup_pwm[nr]; + + switch (nr) { + case SETUP_PWM_UPTIME: + case SETUP_PWM_DOWNTIME: + val = time_from_reg(val); + break; + } + + return sprintf(buf, "%d\n", val); +} + +static ssize_t +store_sf_setup(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + struct i2c_client *client = to_i2c_client(dev); + struct w83795_data *data = i2c_get_clientdata(client); + unsigned long val; + + if (kstrtoul(buf, 10, &val) < 0) + return -EINVAL; + + switch (nr) { + case SETUP_PWM_DEFAULT: + val = clamp_val(val, 0, 0xff); + break; + case SETUP_PWM_UPTIME: + case SETUP_PWM_DOWNTIME: + val = time_to_reg(val); + if (val == 0) + return -EINVAL; + break; + } + + mutex_lock(&data->update_lock); + data->setup_pwm[nr] = val; + w83795_write(client, W83795_REG_SETUP_PWM(nr), val); + mutex_unlock(&data->update_lock); + return count; +} +#endif + + +#define NOT_USED -1 + +/* + * Don't change the attribute order, _max, _min and _beep are accessed by index + * somewhere else in the code + */ +#define SENSOR_ATTR_IN(index) { \ + SENSOR_ATTR_2(in##index##_input, S_IRUGO, show_in, NULL, \ + IN_READ, index), \ + SENSOR_ATTR_2(in##index##_max, S_IRUGO | S_IWUSR, show_in, \ + store_in, IN_MAX, index), \ + SENSOR_ATTR_2(in##index##_min, S_IRUGO | S_IWUSR, show_in, \ + store_in, IN_LOW, index), \ + SENSOR_ATTR_2(in##index##_alarm, S_IRUGO, show_alarm_beep, \ + NULL, ALARM_STATUS, index + ((index > 14) ? 1 : 0)), \ + SENSOR_ATTR_2(in##index##_beep, S_IWUSR | S_IRUGO, \ + show_alarm_beep, store_beep, BEEP_ENABLE, \ + index + ((index > 14) ? 1 : 0)) } + +/* + * Don't change the attribute order, _beep is accessed by index + * somewhere else in the code + */ +#define SENSOR_ATTR_FAN(index) { \ + SENSOR_ATTR_2(fan##index##_input, S_IRUGO, show_fan, \ + NULL, FAN_INPUT, index - 1), \ + SENSOR_ATTR_2(fan##index##_min, S_IWUSR | S_IRUGO, \ + show_fan, store_fan_min, FAN_MIN, index - 1), \ + SENSOR_ATTR_2(fan##index##_alarm, S_IRUGO, show_alarm_beep, \ + NULL, ALARM_STATUS, index + 31), \ + SENSOR_ATTR_2(fan##index##_beep, S_IWUSR | S_IRUGO, \ + show_alarm_beep, store_beep, BEEP_ENABLE, index + 31) } + +#define SENSOR_ATTR_PWM(index) { \ + SENSOR_ATTR_2(pwm##index, S_IWUSR | S_IRUGO, show_pwm, \ + store_pwm, PWM_OUTPUT, index - 1), \ + SENSOR_ATTR_2(pwm##index##_enable, S_IWUSR | S_IRUGO, \ + show_pwm_enable, store_pwm_enable, NOT_USED, index - 1), \ + SENSOR_ATTR_2(pwm##index##_mode, S_IRUGO, \ + show_pwm_mode, NULL, NOT_USED, index - 1), \ + SENSOR_ATTR_2(pwm##index##_freq, S_IWUSR | S_IRUGO, \ + show_pwm, store_pwm, PWM_FREQ, index - 1), \ + SENSOR_ATTR_2(pwm##index##_nonstop, S_IWUSR | S_IRUGO, \ + show_pwm, store_pwm, PWM_NONSTOP, index - 1), \ + SENSOR_ATTR_2(pwm##index##_start, S_IWUSR | S_IRUGO, \ + show_pwm, store_pwm, PWM_START, index - 1), \ + SENSOR_ATTR_2(pwm##index##_stop_time, S_IWUSR | S_IRUGO, \ + show_pwm, store_pwm, PWM_STOP_TIME, index - 1), \ + SENSOR_ATTR_2(fan##index##_target, S_IWUSR | S_IRUGO, \ + show_fanin, store_fanin, FANIN_TARGET, index - 1) } + +/* + * Don't change the attribute order, _beep is accessed by index + * somewhere else in the code + */ +#define SENSOR_ATTR_DTS(index) { \ + SENSOR_ATTR_2(temp##index##_type, S_IRUGO , \ + show_dts_mode, NULL, NOT_USED, index - 7), \ + SENSOR_ATTR_2(temp##index##_input, S_IRUGO, show_dts, \ + NULL, NOT_USED, index - 7), \ + SENSOR_ATTR_2(temp##index##_crit, S_IRUGO | S_IWUSR, show_dts_ext, \ + store_dts_ext, DTS_CRIT, NOT_USED), \ + SENSOR_ATTR_2(temp##index##_crit_hyst, S_IRUGO | S_IWUSR, \ + show_dts_ext, store_dts_ext, DTS_CRIT_HYST, NOT_USED), \ + SENSOR_ATTR_2(temp##index##_max, S_IRUGO | S_IWUSR, show_dts_ext, \ + store_dts_ext, DTS_WARN, NOT_USED), \ + SENSOR_ATTR_2(temp##index##_max_hyst, S_IRUGO | S_IWUSR, \ + show_dts_ext, store_dts_ext, DTS_WARN_HYST, NOT_USED), \ + SENSOR_ATTR_2(temp##index##_alarm, S_IRUGO, \ + show_alarm_beep, NULL, ALARM_STATUS, index + 17), \ + SENSOR_ATTR_2(temp##index##_beep, S_IWUSR | S_IRUGO, \ + show_alarm_beep, store_beep, BEEP_ENABLE, index + 17) } + +/* + * Don't change the attribute order, _beep is accessed by index + * somewhere else in the code + */ +#define SENSOR_ATTR_TEMP(index) { \ + SENSOR_ATTR_2(temp##index##_type, S_IRUGO | (index < 5 ? S_IWUSR : 0), \ + show_temp_mode, store_temp_mode, NOT_USED, index - 1), \ + SENSOR_ATTR_2(temp##index##_input, S_IRUGO, show_temp, \ + NULL, TEMP_READ, index - 1), \ + SENSOR_ATTR_2(temp##index##_crit, S_IRUGO | S_IWUSR, show_temp, \ + store_temp, TEMP_CRIT, index - 1), \ + SENSOR_ATTR_2(temp##index##_crit_hyst, S_IRUGO | S_IWUSR, \ + show_temp, store_temp, TEMP_CRIT_HYST, index - 1), \ + SENSOR_ATTR_2(temp##index##_max, S_IRUGO | S_IWUSR, show_temp, \ + store_temp, TEMP_WARN, index - 1), \ + SENSOR_ATTR_2(temp##index##_max_hyst, S_IRUGO | S_IWUSR, \ + show_temp, store_temp, TEMP_WARN_HYST, index - 1), \ + SENSOR_ATTR_2(temp##index##_alarm, S_IRUGO, \ + show_alarm_beep, NULL, ALARM_STATUS, \ + index + (index > 4 ? 11 : 17)), \ + SENSOR_ATTR_2(temp##index##_beep, S_IWUSR | S_IRUGO, \ + show_alarm_beep, store_beep, BEEP_ENABLE, \ + index + (index > 4 ? 11 : 17)), \ + SENSOR_ATTR_2(temp##index##_pwm_enable, S_IWUSR | S_IRUGO, \ + show_temp_pwm_enable, store_temp_pwm_enable, \ + TEMP_PWM_ENABLE, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_channels_pwm, S_IWUSR | S_IRUGO, \ + show_temp_pwm_enable, store_temp_pwm_enable, \ + TEMP_PWM_FAN_MAP, index - 1), \ + SENSOR_ATTR_2(thermal_cruise##index, S_IWUSR | S_IRUGO, \ + show_temp_pwm, store_temp_pwm, TEMP_PWM_TTTI, index - 1), \ + SENSOR_ATTR_2(temp##index##_warn, S_IWUSR | S_IRUGO, \ + show_temp_pwm, store_temp_pwm, TEMP_PWM_CTFS, index - 1), \ + SENSOR_ATTR_2(temp##index##_warn_hyst, S_IWUSR | S_IRUGO, \ + show_temp_pwm, store_temp_pwm, TEMP_PWM_HCT, index - 1), \ + SENSOR_ATTR_2(temp##index##_operation_hyst, S_IWUSR | S_IRUGO, \ + show_temp_pwm, store_temp_pwm, TEMP_PWM_HOT, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point1_pwm, S_IRUGO | S_IWUSR, \ + show_sf4_pwm, store_sf4_pwm, 0, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point2_pwm, S_IRUGO | S_IWUSR, \ + show_sf4_pwm, store_sf4_pwm, 1, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point3_pwm, S_IRUGO | S_IWUSR, \ + show_sf4_pwm, store_sf4_pwm, 2, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point4_pwm, S_IRUGO | S_IWUSR, \ + show_sf4_pwm, store_sf4_pwm, 3, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point5_pwm, S_IRUGO | S_IWUSR, \ + show_sf4_pwm, store_sf4_pwm, 4, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point6_pwm, S_IRUGO | S_IWUSR, \ + show_sf4_pwm, store_sf4_pwm, 5, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point7_pwm, S_IRUGO | S_IWUSR, \ + show_sf4_pwm, store_sf4_pwm, 6, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point1_temp, S_IRUGO | S_IWUSR,\ + show_sf4_temp, store_sf4_temp, 0, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point2_temp, S_IRUGO | S_IWUSR,\ + show_sf4_temp, store_sf4_temp, 1, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point3_temp, S_IRUGO | S_IWUSR,\ + show_sf4_temp, store_sf4_temp, 2, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point4_temp, S_IRUGO | S_IWUSR,\ + show_sf4_temp, store_sf4_temp, 3, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point5_temp, S_IRUGO | S_IWUSR,\ + show_sf4_temp, store_sf4_temp, 4, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point6_temp, S_IRUGO | S_IWUSR,\ + show_sf4_temp, store_sf4_temp, 5, index - 1), \ + SENSOR_ATTR_2(temp##index##_auto_point7_temp, S_IRUGO | S_IWUSR,\ + show_sf4_temp, store_sf4_temp, 6, index - 1) } + + +static struct sensor_device_attribute_2 w83795_in[][5] = { + SENSOR_ATTR_IN(0), + SENSOR_ATTR_IN(1), + SENSOR_ATTR_IN(2), + SENSOR_ATTR_IN(3), + SENSOR_ATTR_IN(4), + SENSOR_ATTR_IN(5), + SENSOR_ATTR_IN(6), + SENSOR_ATTR_IN(7), + SENSOR_ATTR_IN(8), + SENSOR_ATTR_IN(9), + SENSOR_ATTR_IN(10), + SENSOR_ATTR_IN(11), + SENSOR_ATTR_IN(12), + SENSOR_ATTR_IN(13), + SENSOR_ATTR_IN(14), + SENSOR_ATTR_IN(15), + SENSOR_ATTR_IN(16), + SENSOR_ATTR_IN(17), + SENSOR_ATTR_IN(18), + SENSOR_ATTR_IN(19), + SENSOR_ATTR_IN(20), +}; + +static const struct sensor_device_attribute_2 w83795_fan[][4] = { + SENSOR_ATTR_FAN(1), + SENSOR_ATTR_FAN(2), + SENSOR_ATTR_FAN(3), + SENSOR_ATTR_FAN(4), + SENSOR_ATTR_FAN(5), + SENSOR_ATTR_FAN(6), + SENSOR_ATTR_FAN(7), + SENSOR_ATTR_FAN(8), + SENSOR_ATTR_FAN(9), + SENSOR_ATTR_FAN(10), + SENSOR_ATTR_FAN(11), + SENSOR_ATTR_FAN(12), + SENSOR_ATTR_FAN(13), + SENSOR_ATTR_FAN(14), +}; + +static const struct sensor_device_attribute_2 w83795_temp[][28] = { + SENSOR_ATTR_TEMP(1), + SENSOR_ATTR_TEMP(2), + SENSOR_ATTR_TEMP(3), + SENSOR_ATTR_TEMP(4), + SENSOR_ATTR_TEMP(5), + SENSOR_ATTR_TEMP(6), +}; + +static const struct sensor_device_attribute_2 w83795_dts[][8] = { + SENSOR_ATTR_DTS(7), + SENSOR_ATTR_DTS(8), + SENSOR_ATTR_DTS(9), + SENSOR_ATTR_DTS(10), + SENSOR_ATTR_DTS(11), + SENSOR_ATTR_DTS(12), + SENSOR_ATTR_DTS(13), + SENSOR_ATTR_DTS(14), +}; + +static const struct sensor_device_attribute_2 w83795_pwm[][8] = { + SENSOR_ATTR_PWM(1), + SENSOR_ATTR_PWM(2), + SENSOR_ATTR_PWM(3), + SENSOR_ATTR_PWM(4), + SENSOR_ATTR_PWM(5), + SENSOR_ATTR_PWM(6), + SENSOR_ATTR_PWM(7), + SENSOR_ATTR_PWM(8), +}; + +static const struct sensor_device_attribute_2 w83795_tss[6] = { + SENSOR_ATTR_2(temp1_source_sel, S_IWUSR | S_IRUGO, + show_temp_src, store_temp_src, NOT_USED, 0), + SENSOR_ATTR_2(temp2_source_sel, S_IWUSR | S_IRUGO, + show_temp_src, store_temp_src, NOT_USED, 1), + SENSOR_ATTR_2(temp3_source_sel, S_IWUSR | S_IRUGO, + show_temp_src, store_temp_src, NOT_USED, 2), + SENSOR_ATTR_2(temp4_source_sel, S_IWUSR | S_IRUGO, + show_temp_src, store_temp_src, NOT_USED, 3), + SENSOR_ATTR_2(temp5_source_sel, S_IWUSR | S_IRUGO, + show_temp_src, store_temp_src, NOT_USED, 4), + SENSOR_ATTR_2(temp6_source_sel, S_IWUSR | S_IRUGO, + show_temp_src, store_temp_src, NOT_USED, 5), +}; + +static const struct sensor_device_attribute_2 sda_single_files[] = { + SENSOR_ATTR_2(intrusion0_alarm, S_IWUSR | S_IRUGO, show_alarm_beep, + store_chassis_clear, ALARM_STATUS, 46), +#ifdef CONFIG_SENSORS_W83795_FANCTRL + SENSOR_ATTR_2(speed_cruise_tolerance, S_IWUSR | S_IRUGO, show_fanin, + store_fanin, FANIN_TOL, NOT_USED), + SENSOR_ATTR_2(pwm_default, S_IWUSR | S_IRUGO, show_sf_setup, + store_sf_setup, SETUP_PWM_DEFAULT, NOT_USED), + SENSOR_ATTR_2(pwm_uptime, S_IWUSR | S_IRUGO, show_sf_setup, + store_sf_setup, SETUP_PWM_UPTIME, NOT_USED), + SENSOR_ATTR_2(pwm_downtime, S_IWUSR | S_IRUGO, show_sf_setup, + store_sf_setup, SETUP_PWM_DOWNTIME, NOT_USED), +#endif +}; + +static const struct sensor_device_attribute_2 sda_beep_files[] = { + SENSOR_ATTR_2(intrusion0_beep, S_IWUSR | S_IRUGO, show_alarm_beep, + store_beep, BEEP_ENABLE, 46), + SENSOR_ATTR_2(beep_enable, S_IWUSR | S_IRUGO, show_alarm_beep, + store_beep, BEEP_ENABLE, 47), +}; + +/* + * Driver interface + */ + +static void w83795_init_client(struct i2c_client *client) +{ + struct w83795_data *data = i2c_get_clientdata(client); + static const u16 clkin[4] = { /* in kHz */ + 14318, 24000, 33333, 48000 + }; + u8 config; + + if (reset) + w83795_write(client, W83795_REG_CONFIG, 0x80); + + /* Start monitoring if needed */ + config = w83795_read(client, W83795_REG_CONFIG); + if (!(config & W83795_REG_CONFIG_START)) { + dev_info(&client->dev, "Enabling monitoring operations\n"); + w83795_write(client, W83795_REG_CONFIG, + config | W83795_REG_CONFIG_START); + } + + data->clkin = clkin[(config >> 3) & 0x3]; + dev_dbg(&client->dev, "clkin = %u kHz\n", data->clkin); +} + +static int w83795_get_device_id(struct i2c_client *client) +{ + int device_id; + + device_id = i2c_smbus_read_byte_data(client, W83795_REG_DEVICEID); + + /* + * Special case for rev. A chips; can't be checked first because later + * revisions emulate this for compatibility + */ + if (device_id < 0 || (device_id & 0xf0) != 0x50) { + int alt_id; + + alt_id = i2c_smbus_read_byte_data(client, + W83795_REG_DEVICEID_A); + if (alt_id == 0x50) + device_id = alt_id; + } + + return device_id; +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int w83795_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + int bank, vendor_id, device_id, expected, i2c_addr, config; + struct i2c_adapter *adapter = client->adapter; + unsigned short address = client->addr; + const char *chip_name; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + bank = i2c_smbus_read_byte_data(client, W83795_REG_BANKSEL); + if (bank < 0 || (bank & 0x7c)) { + dev_dbg(&adapter->dev, + "w83795: Detection failed at addr 0x%02hx, check %s\n", + address, "bank"); + return -ENODEV; + } + + /* Check Nuvoton vendor ID */ + vendor_id = i2c_smbus_read_byte_data(client, W83795_REG_VENDORID); + expected = bank & 0x80 ? 0x5c : 0xa3; + if (vendor_id != expected) { + dev_dbg(&adapter->dev, + "w83795: Detection failed at addr 0x%02hx, check %s\n", + address, "vendor id"); + return -ENODEV; + } + + /* Check device ID */ + device_id = w83795_get_device_id(client) | + (i2c_smbus_read_byte_data(client, W83795_REG_CHIPID) << 8); + if ((device_id >> 4) != 0x795) { + dev_dbg(&adapter->dev, + "w83795: Detection failed at addr 0x%02hx, check %s\n", + address, "device id\n"); + return -ENODEV; + } + + /* + * If Nuvoton chip, address of chip and W83795_REG_I2C_ADDR + * should match + */ + if ((bank & 0x07) == 0) { + i2c_addr = i2c_smbus_read_byte_data(client, + W83795_REG_I2C_ADDR); + if ((i2c_addr & 0x7f) != address) { + dev_dbg(&adapter->dev, + "w83795: Detection failed at addr 0x%02hx, " + "check %s\n", address, "i2c addr"); + return -ENODEV; + } + } + + /* + * Check 795 chip type: 795G or 795ADG + * Usually we don't write to chips during detection, but here we don't + * quite have the choice; hopefully it's OK, we are about to return + * success anyway + */ + if ((bank & 0x07) != 0) + i2c_smbus_write_byte_data(client, W83795_REG_BANKSEL, + bank & ~0x07); + config = i2c_smbus_read_byte_data(client, W83795_REG_CONFIG); + if (config & W83795_REG_CONFIG_CONFIG48) + chip_name = "w83795adg"; + else + chip_name = "w83795g"; + + strlcpy(info->type, chip_name, I2C_NAME_SIZE); + dev_info(&adapter->dev, "Found %s rev. %c at 0x%02hx\n", chip_name, + 'A' + (device_id & 0xf), address); + + return 0; +} + +#ifdef CONFIG_SENSORS_W83795_FANCTRL +#define NUM_PWM_ATTRIBUTES ARRAY_SIZE(w83795_pwm[0]) +#define NUM_TEMP_ATTRIBUTES ARRAY_SIZE(w83795_temp[0]) +#else +#define NUM_PWM_ATTRIBUTES 4 +#define NUM_TEMP_ATTRIBUTES 8 +#endif + +static int w83795_handle_files(struct device *dev, int (*fn)(struct device *, + const struct device_attribute *)) +{ + struct w83795_data *data = dev_get_drvdata(dev); + int err, i, j; + + for (i = 0; i < ARRAY_SIZE(w83795_in); i++) { + if (!(data->has_in & (1 << i))) + continue; + for (j = 0; j < ARRAY_SIZE(w83795_in[0]); j++) { + if (j == 4 && !data->enable_beep) + continue; + err = fn(dev, &w83795_in[i][j].dev_attr); + if (err) + return err; + } + } + + for (i = 0; i < ARRAY_SIZE(w83795_fan); i++) { + if (!(data->has_fan & (1 << i))) + continue; + for (j = 0; j < ARRAY_SIZE(w83795_fan[0]); j++) { + if (j == 3 && !data->enable_beep) + continue; + err = fn(dev, &w83795_fan[i][j].dev_attr); + if (err) + return err; + } + } + + for (i = 0; i < ARRAY_SIZE(w83795_tss); i++) { + j = w83795_tss_useful(data, i); + if (!j) + continue; + err = fn(dev, &w83795_tss[i].dev_attr); + if (err) + return err; + } + + for (i = 0; i < ARRAY_SIZE(sda_single_files); i++) { + err = fn(dev, &sda_single_files[i].dev_attr); + if (err) + return err; + } + + if (data->enable_beep) { + for (i = 0; i < ARRAY_SIZE(sda_beep_files); i++) { + err = fn(dev, &sda_beep_files[i].dev_attr); + if (err) + return err; + } + } + + for (i = 0; i < data->has_pwm; i++) { + for (j = 0; j < NUM_PWM_ATTRIBUTES; j++) { + err = fn(dev, &w83795_pwm[i][j].dev_attr); + if (err) + return err; + } + } + + for (i = 0; i < ARRAY_SIZE(w83795_temp); i++) { + if (!(data->has_temp & (1 << i))) + continue; + for (j = 0; j < NUM_TEMP_ATTRIBUTES; j++) { + if (j == 7 && !data->enable_beep) + continue; + err = fn(dev, &w83795_temp[i][j].dev_attr); + if (err) + return err; + } + } + + if (data->enable_dts) { + for (i = 0; i < ARRAY_SIZE(w83795_dts); i++) { + if (!(data->has_dts & (1 << i))) + continue; + for (j = 0; j < ARRAY_SIZE(w83795_dts[0]); j++) { + if (j == 7 && !data->enable_beep) + continue; + err = fn(dev, &w83795_dts[i][j].dev_attr); + if (err) + return err; + } + } + } + + return 0; +} + +/* We need a wrapper that fits in w83795_handle_files */ +static int device_remove_file_wrapper(struct device *dev, + const struct device_attribute *attr) +{ + device_remove_file(dev, attr); + return 0; +} + +static void w83795_check_dynamic_in_limits(struct i2c_client *client) +{ + struct w83795_data *data = i2c_get_clientdata(client); + u8 vid_ctl; + int i, err_max, err_min; + + vid_ctl = w83795_read(client, W83795_REG_VID_CTRL); + + /* Return immediately if VRM isn't configured */ + if ((vid_ctl & 0x07) == 0x00 || (vid_ctl & 0x07) == 0x07) + return; + + data->has_dyn_in = (vid_ctl >> 3) & 0x07; + for (i = 0; i < 2; i++) { + if (!(data->has_dyn_in & (1 << i))) + continue; + + /* Voltage limits in dynamic mode, switch to read-only */ + err_max = sysfs_chmod_file(&client->dev.kobj, + &w83795_in[i][2].dev_attr.attr, + S_IRUGO); + err_min = sysfs_chmod_file(&client->dev.kobj, + &w83795_in[i][3].dev_attr.attr, + S_IRUGO); + if (err_max || err_min) + dev_warn(&client->dev, + "Failed to set in%d limits read-only (%d, %d)\n", + i, err_max, err_min); + else + dev_info(&client->dev, + "in%d limits set dynamically from VID\n", i); + } +} + +/* Check pins that can be used for either temperature or voltage monitoring */ +static void w83795_apply_temp_config(struct w83795_data *data, u8 config, + int temp_chan, int in_chan) +{ + /* config is a 2-bit value */ + switch (config) { + case 0x2: /* Voltage monitoring */ + data->has_in |= 1 << in_chan; + break; + case 0x1: /* Thermal diode */ + if (temp_chan >= 4) + break; + data->temp_mode |= 1 << temp_chan; + /* fall through */ + case 0x3: /* Thermistor */ + data->has_temp |= 1 << temp_chan; + break; + } +} + +static int w83795_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int i; + u8 tmp; + struct device *dev = &client->dev; + struct w83795_data *data; + int err; + + data = devm_kzalloc(dev, sizeof(struct w83795_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->chip_type = id->driver_data; + data->bank = i2c_smbus_read_byte_data(client, W83795_REG_BANKSEL); + mutex_init(&data->update_lock); + + /* Initialize the chip */ + w83795_init_client(client); + + /* Check which voltages and fans are present */ + data->has_in = w83795_read(client, W83795_REG_VOLT_CTRL1) + | (w83795_read(client, W83795_REG_VOLT_CTRL2) << 8); + data->has_fan = w83795_read(client, W83795_REG_FANIN_CTRL1) + | (w83795_read(client, W83795_REG_FANIN_CTRL2) << 8); + + /* Check which analog temperatures and extra voltages are present */ + tmp = w83795_read(client, W83795_REG_TEMP_CTRL1); + if (tmp & 0x20) + data->enable_dts = 1; + w83795_apply_temp_config(data, (tmp >> 2) & 0x3, 5, 16); + w83795_apply_temp_config(data, tmp & 0x3, 4, 15); + tmp = w83795_read(client, W83795_REG_TEMP_CTRL2); + w83795_apply_temp_config(data, tmp >> 6, 3, 20); + w83795_apply_temp_config(data, (tmp >> 4) & 0x3, 2, 19); + w83795_apply_temp_config(data, (tmp >> 2) & 0x3, 1, 18); + w83795_apply_temp_config(data, tmp & 0x3, 0, 17); + + /* Check DTS enable status */ + if (data->enable_dts) { + if (1 & w83795_read(client, W83795_REG_DTSC)) + data->enable_dts |= 2; + data->has_dts = w83795_read(client, W83795_REG_DTSE); + } + + /* Report PECI Tbase values */ + if (data->enable_dts == 1) { + for (i = 0; i < 8; i++) { + if (!(data->has_dts & (1 << i))) + continue; + tmp = w83795_read(client, W83795_REG_PECI_TBASE(i)); + dev_info(&client->dev, + "PECI agent %d Tbase temperature: %u\n", + i + 1, (unsigned int)tmp & 0x7f); + } + } + + data->has_gain = w83795_read(client, W83795_REG_VMIGB_CTRL) & 0x0f; + + /* pwm and smart fan */ + if (data->chip_type == w83795g) + data->has_pwm = 8; + else + data->has_pwm = 2; + + /* Check if BEEP pin is available */ + if (data->chip_type == w83795g) { + /* The W83795G has a dedicated BEEP pin */ + data->enable_beep = 1; + } else { + /* + * The W83795ADG has a shared pin for OVT# and BEEP, so you + * can't have both + */ + tmp = w83795_read(client, W83795_REG_OVT_CFG); + if ((tmp & OVT_CFG_SEL) == 0) + data->enable_beep = 1; + } + + err = w83795_handle_files(dev, device_create_file); + if (err) + goto exit_remove; + + if (data->chip_type == w83795g) + w83795_check_dynamic_in_limits(client); + + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + w83795_handle_files(dev, device_remove_file_wrapper); + return err; +} + +static int w83795_remove(struct i2c_client *client) +{ + struct w83795_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + w83795_handle_files(&client->dev, device_remove_file_wrapper); + + return 0; +} + + +static const struct i2c_device_id w83795_id[] = { + { "w83795g", w83795g }, + { "w83795adg", w83795adg }, + { } +}; +MODULE_DEVICE_TABLE(i2c, w83795_id); + +static struct i2c_driver w83795_driver = { + .driver = { + .name = "w83795", + }, + .probe = w83795_probe, + .remove = w83795_remove, + .id_table = w83795_id, + + .class = I2C_CLASS_HWMON, + .detect = w83795_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(w83795_driver); + +MODULE_AUTHOR("Wei Song, Jean Delvare "); +MODULE_DESCRIPTION("W83795G/ADG hardware monitoring driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/w83l785ts.c b/drivers/hwmon/w83l785ts.c new file mode 100644 index 000000000..ac3043122 --- /dev/null +++ b/drivers/hwmon/w83l785ts.c @@ -0,0 +1,300 @@ +/* + * w83l785ts.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 2003-2009 Jean Delvare + * + * Inspired from the lm83 driver. The W83L785TS-S is a sensor chip made + * by Winbond. It reports a single external temperature with a 1 deg + * resolution and a 3 deg accuracy. Datasheet can be obtained from + * Winbond's website at: + * http://www.winbond-usa.com/products/winbond_products/pdfs/PCIC/W83L785TS-S.pdf + * + * Ported to Linux 2.6 by Wolfgang Ziegler and Jean Delvare + * . + * + * Thanks to James Bolt for benchmarking the read + * error handling mechanism. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* How many retries on register read error */ +#define MAX_RETRIES 5 + +/* + * Address to scan + * Address is fully defined internally and cannot be changed. + */ + +static const unsigned short normal_i2c[] = { 0x2e, I2C_CLIENT_END }; + +/* + * The W83L785TS-S registers + * Manufacturer ID is 0x5CA3 for Winbond. + */ + +#define W83L785TS_REG_MAN_ID1 0x4D +#define W83L785TS_REG_MAN_ID2 0x4C +#define W83L785TS_REG_CHIP_ID 0x4E +#define W83L785TS_REG_CONFIG 0x40 +#define W83L785TS_REG_TYPE 0x52 +#define W83L785TS_REG_TEMP 0x27 +#define W83L785TS_REG_TEMP_OVER 0x53 /* not sure about this one */ + +/* + * Conversions + * The W83L785TS-S uses signed 8-bit values. + */ + +#define TEMP_FROM_REG(val) ((val) * 1000) + +/* + * Functions declaration + */ + +static int w83l785ts_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int w83l785ts_detect(struct i2c_client *client, + struct i2c_board_info *info); +static int w83l785ts_remove(struct i2c_client *client); +static u8 w83l785ts_read_value(struct i2c_client *client, u8 reg, u8 defval); +static struct w83l785ts_data *w83l785ts_update_device(struct device *dev); + +/* + * Driver data (common to all clients) + */ + +static const struct i2c_device_id w83l785ts_id[] = { + { "w83l785ts", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, w83l785ts_id); + +static struct i2c_driver w83l785ts_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "w83l785ts", + }, + .probe = w83l785ts_probe, + .remove = w83l785ts_remove, + .id_table = w83l785ts_id, + .detect = w83l785ts_detect, + .address_list = normal_i2c, +}; + +/* + * Client data (each client gets its own) + */ + +struct w83l785ts_data { + struct device *hwmon_dev; + struct mutex update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* registers values */ + s8 temp[2]; /* 0: input, 1: critical limit */ +}; + +/* + * Sysfs stuff + */ + +static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct w83l785ts_data *data = w83l785ts_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index])); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp, NULL, 1); + +/* + * Real code + */ + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int w83l785ts_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + u16 man_id; + u8 chip_id; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* detection */ + if ((w83l785ts_read_value(client, W83L785TS_REG_CONFIG, 0) & 0x80) + || (w83l785ts_read_value(client, W83L785TS_REG_TYPE, 0) & 0xFC)) { + dev_dbg(&adapter->dev, + "W83L785TS-S detection failed at 0x%02x\n", + client->addr); + return -ENODEV; + } + + /* Identification */ + man_id = (w83l785ts_read_value(client, W83L785TS_REG_MAN_ID1, 0) << 8) + + w83l785ts_read_value(client, W83L785TS_REG_MAN_ID2, 0); + chip_id = w83l785ts_read_value(client, W83L785TS_REG_CHIP_ID, 0); + + if (man_id != 0x5CA3 /* Winbond */ + || chip_id != 0x70) { /* W83L785TS-S */ + dev_dbg(&adapter->dev, + "Unsupported chip (man_id=0x%04X, chip_id=0x%02X)\n", + man_id, chip_id); + return -ENODEV; + } + + strlcpy(info->type, "w83l785ts", I2C_NAME_SIZE); + + return 0; +} + +static int w83l785ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct w83l785ts_data *data; + struct device *dev = &client->dev; + int err; + + data = devm_kzalloc(dev, sizeof(struct w83l785ts_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* + * Initialize the W83L785TS chip + * Nothing yet, assume it is already started. + */ + + err = device_create_file(dev, &sensor_dev_attr_temp1_input.dev_attr); + if (err) + return err; + + err = device_create_file(dev, &sensor_dev_attr_temp1_max.dev_attr); + if (err) + goto exit_remove; + + /* Register sysfs hooks */ + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + device_remove_file(dev, &sensor_dev_attr_temp1_input.dev_attr); + device_remove_file(dev, &sensor_dev_attr_temp1_max.dev_attr); + return err; +} + +static int w83l785ts_remove(struct i2c_client *client) +{ + struct w83l785ts_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + device_remove_file(&client->dev, + &sensor_dev_attr_temp1_input.dev_attr); + device_remove_file(&client->dev, + &sensor_dev_attr_temp1_max.dev_attr); + + return 0; +} + +static u8 w83l785ts_read_value(struct i2c_client *client, u8 reg, u8 defval) +{ + int value, i; + struct device *dev; + const char *prefix; + + /* + * We might be called during detection, at which point the client + * isn't yet fully initialized, so we can't use dev_dbg on it + */ + if (i2c_get_clientdata(client)) { + dev = &client->dev; + prefix = ""; + } else { + dev = &client->adapter->dev; + prefix = "w83l785ts: "; + } + + /* + * Frequent read errors have been reported on Asus boards, so we + * retry on read errors. If it still fails (unlikely), return the + * default value requested by the caller. + */ + for (i = 1; i <= MAX_RETRIES; i++) { + value = i2c_smbus_read_byte_data(client, reg); + if (value >= 0) { + dev_dbg(dev, "%sRead 0x%02x from register 0x%02x.\n", + prefix, value, reg); + return value; + } + dev_dbg(dev, "%sRead failed, will retry in %d.\n", prefix, i); + msleep(i); + } + + dev_err(dev, "%sCouldn't read value from register 0x%02x.\n", prefix, + reg); + return defval; +} + +static struct w83l785ts_data *w83l785ts_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct w83l785ts_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); + + if (!data->valid || time_after(jiffies, data->last_updated + HZ * 2)) { + dev_dbg(&client->dev, "Updating w83l785ts data.\n"); + data->temp[0] = w83l785ts_read_value(client, + W83L785TS_REG_TEMP, data->temp[0]); + data->temp[1] = w83l785ts_read_value(client, + W83L785TS_REG_TEMP_OVER, data->temp[1]); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +module_i2c_driver(w83l785ts_driver); + +MODULE_AUTHOR("Jean Delvare "); +MODULE_DESCRIPTION("W83L785TS-S driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/w83l786ng.c b/drivers/hwmon/w83l786ng.c new file mode 100644 index 000000000..330299613 --- /dev/null +++ b/drivers/hwmon/w83l786ng.c @@ -0,0 +1,778 @@ +/* + * w83l786ng.c - Linux kernel driver for hardware monitoring + * Copyright (c) 2007 Kevin Lo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation - version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA. + */ + +/* + * Supports following chips: + * + * Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA + * w83l786ng 3 2 2 2 0x7b 0x5ca3 yes no + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x2e, 0x2f, I2C_CLIENT_END }; + +/* Insmod parameters */ + +static bool reset; +module_param(reset, bool, 0); +MODULE_PARM_DESC(reset, "Set to 1 to reset chip, not recommended"); + +#define W83L786NG_REG_IN_MIN(nr) (0x2C + (nr) * 2) +#define W83L786NG_REG_IN_MAX(nr) (0x2B + (nr) * 2) +#define W83L786NG_REG_IN(nr) ((nr) + 0x20) + +#define W83L786NG_REG_FAN(nr) ((nr) + 0x28) +#define W83L786NG_REG_FAN_MIN(nr) ((nr) + 0x3B) + +#define W83L786NG_REG_CONFIG 0x40 +#define W83L786NG_REG_ALARM1 0x41 +#define W83L786NG_REG_ALARM2 0x42 +#define W83L786NG_REG_GPIO_EN 0x47 +#define W83L786NG_REG_MAN_ID2 0x4C +#define W83L786NG_REG_MAN_ID1 0x4D +#define W83L786NG_REG_CHIP_ID 0x4E + +#define W83L786NG_REG_DIODE 0x53 +#define W83L786NG_REG_FAN_DIV 0x54 +#define W83L786NG_REG_FAN_CFG 0x80 + +#define W83L786NG_REG_TOLERANCE 0x8D + +static const u8 W83L786NG_REG_TEMP[2][3] = { + { 0x25, /* TEMP 0 in DataSheet */ + 0x35, /* TEMP 0 Over in DataSheet */ + 0x36 }, /* TEMP 0 Hyst in DataSheet */ + { 0x26, /* TEMP 1 in DataSheet */ + 0x37, /* TEMP 1 Over in DataSheet */ + 0x38 } /* TEMP 1 Hyst in DataSheet */ +}; + +static const u8 W83L786NG_PWM_MODE_SHIFT[] = {6, 7}; +static const u8 W83L786NG_PWM_ENABLE_SHIFT[] = {2, 4}; + +/* FAN Duty Cycle, be used to control */ +static const u8 W83L786NG_REG_PWM[] = {0x81, 0x87}; + + +static inline u8 +FAN_TO_REG(long rpm, int div) +{ + if (rpm == 0) + return 255; + rpm = clamp_val(rpm, 1, 1000000); + return clamp_val((1350000 + rpm * div / 2) / (rpm * div), 1, 254); +} + +#define FAN_FROM_REG(val, div) ((val) == 0 ? -1 : \ + ((val) == 255 ? 0 : \ + 1350000 / ((val) * (div)))) + +/* for temp */ +#define TEMP_TO_REG(val) (clamp_val(((val) < 0 ? (val) + 0x100 * 1000 \ + : (val)) / 1000, 0, 0xff)) +#define TEMP_FROM_REG(val) (((val) & 0x80 ? \ + (val) - 0x100 : (val)) * 1000) + +/* + * The analog voltage inputs have 8mV LSB. Since the sysfs output is + * in mV as would be measured on the chip input pin, need to just + * multiply/divide by 8 to translate from/to register values. + */ +#define IN_TO_REG(val) (clamp_val((((val) + 4) / 8), 0, 255)) +#define IN_FROM_REG(val) ((val) * 8) + +#define DIV_FROM_REG(val) (1 << (val)) + +static inline u8 +DIV_TO_REG(long val) +{ + int i; + val = clamp_val(val, 1, 128) >> 1; + for (i = 0; i < 7; i++) { + if (val == 0) + break; + val >>= 1; + } + return (u8)i; +} + +struct w83l786ng_data { + struct i2c_client *client; + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + unsigned long last_nonvolatile; /* In jiffies, last time we update the + * nonvolatile registers */ + + u8 in[3]; + u8 in_max[3]; + u8 in_min[3]; + u8 fan[2]; + u8 fan_div[2]; + u8 fan_min[2]; + u8 temp_type[2]; + u8 temp[2][3]; + u8 pwm[2]; + u8 pwm_mode[2]; /* 0->DC variable voltage + * 1->PWM variable duty cycle */ + + u8 pwm_enable[2]; /* 1->manual + * 2->thermal cruise (also called SmartFan I) */ + u8 tolerance[2]; +}; + +static u8 +w83l786ng_read_value(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int +w83l786ng_write_value(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static struct w83l786ng_data *w83l786ng_update_device(struct device *dev) +{ + struct w83l786ng_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int i, j; + u8 reg_tmp, pwmcfg; + + mutex_lock(&data->update_lock); + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + dev_dbg(&client->dev, "Updating w83l786ng data.\n"); + + /* Update the voltages measured value and limits */ + for (i = 0; i < 3; i++) { + data->in[i] = w83l786ng_read_value(client, + W83L786NG_REG_IN(i)); + data->in_min[i] = w83l786ng_read_value(client, + W83L786NG_REG_IN_MIN(i)); + data->in_max[i] = w83l786ng_read_value(client, + W83L786NG_REG_IN_MAX(i)); + } + + /* Update the fan counts and limits */ + for (i = 0; i < 2; i++) { + data->fan[i] = w83l786ng_read_value(client, + W83L786NG_REG_FAN(i)); + data->fan_min[i] = w83l786ng_read_value(client, + W83L786NG_REG_FAN_MIN(i)); + } + + /* Update the fan divisor */ + reg_tmp = w83l786ng_read_value(client, W83L786NG_REG_FAN_DIV); + data->fan_div[0] = reg_tmp & 0x07; + data->fan_div[1] = (reg_tmp >> 4) & 0x07; + + pwmcfg = w83l786ng_read_value(client, W83L786NG_REG_FAN_CFG); + for (i = 0; i < 2; i++) { + data->pwm_mode[i] = + ((pwmcfg >> W83L786NG_PWM_MODE_SHIFT[i]) & 1) + ? 0 : 1; + data->pwm_enable[i] = + ((pwmcfg >> W83L786NG_PWM_ENABLE_SHIFT[i]) & 3) + 1; + data->pwm[i] = + (w83l786ng_read_value(client, W83L786NG_REG_PWM[i]) + & 0x0f) * 0x11; + } + + + /* Update the temperature sensors */ + for (i = 0; i < 2; i++) { + for (j = 0; j < 3; j++) { + data->temp[i][j] = w83l786ng_read_value(client, + W83L786NG_REG_TEMP[i][j]); + } + } + + /* Update Smart Fan I/II tolerance */ + reg_tmp = w83l786ng_read_value(client, W83L786NG_REG_TOLERANCE); + data->tolerance[0] = reg_tmp & 0x0f; + data->tolerance[1] = (reg_tmp >> 4) & 0x0f; + + data->last_updated = jiffies; + data->valid = 1; + + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* following are the sysfs callback functions */ +#define show_in_reg(reg) \ +static ssize_t \ +show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + int nr = to_sensor_dev_attr(attr)->index; \ + struct w83l786ng_data *data = w83l786ng_update_device(dev); \ + return sprintf(buf, "%d\n", IN_FROM_REG(data->reg[nr])); \ +} + +show_in_reg(in) +show_in_reg(in_min) +show_in_reg(in_max) + +#define store_in_reg(REG, reg) \ +static ssize_t \ +store_in_##reg(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + int nr = to_sensor_dev_attr(attr)->index; \ + struct w83l786ng_data *data = dev_get_drvdata(dev); \ + struct i2c_client *client = data->client; \ + unsigned long val; \ + int err = kstrtoul(buf, 10, &val); \ + if (err) \ + return err; \ + mutex_lock(&data->update_lock); \ + data->in_##reg[nr] = IN_TO_REG(val); \ + w83l786ng_write_value(client, W83L786NG_REG_IN_##REG(nr), \ + data->in_##reg[nr]); \ + mutex_unlock(&data->update_lock); \ + return count; \ +} + +store_in_reg(MIN, min) +store_in_reg(MAX, max) + +static struct sensor_device_attribute sda_in_input[] = { + SENSOR_ATTR(in0_input, S_IRUGO, show_in, NULL, 0), + SENSOR_ATTR(in1_input, S_IRUGO, show_in, NULL, 1), + SENSOR_ATTR(in2_input, S_IRUGO, show_in, NULL, 2), +}; + +static struct sensor_device_attribute sda_in_min[] = { + SENSOR_ATTR(in0_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 0), + SENSOR_ATTR(in1_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 1), + SENSOR_ATTR(in2_min, S_IWUSR | S_IRUGO, show_in_min, store_in_min, 2), +}; + +static struct sensor_device_attribute sda_in_max[] = { + SENSOR_ATTR(in0_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 0), + SENSOR_ATTR(in1_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 1), + SENSOR_ATTR(in2_max, S_IWUSR | S_IRUGO, show_in_max, store_in_max, 2), +}; + +#define show_fan_reg(reg) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + int nr = to_sensor_dev_attr(attr)->index; \ + struct w83l786ng_data *data = w83l786ng_update_device(dev); \ + return sprintf(buf, "%d\n", \ + FAN_FROM_REG(data->reg[nr], DIV_FROM_REG(data->fan_div[nr]))); \ +} + +show_fan_reg(fan); +show_fan_reg(fan_min); + +static ssize_t +store_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct w83l786ng_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->fan_min[nr] = FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); + w83l786ng_write_value(client, W83L786NG_REG_FAN_MIN(nr), + data->fan_min[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_fan_div(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct w83l786ng_data *data = w83l786ng_update_device(dev); + return sprintf(buf, "%u\n", DIV_FROM_REG(data->fan_div[nr])); +} + +/* + * Note: we save and restore the fan minimum here, because its value is + * determined in part by the fan divisor. This follows the principle of + * least surprise; the user doesn't expect the fan minimum to change just + * because the divisor changed. + */ +static ssize_t +store_fan_div(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct w83l786ng_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + unsigned long min; + u8 tmp_fan_div; + u8 fan_div_reg; + u8 keep_mask = 0; + u8 new_shift = 0; + + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + /* Save fan_min */ + mutex_lock(&data->update_lock); + min = FAN_FROM_REG(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr])); + + data->fan_div[nr] = DIV_TO_REG(val); + + switch (nr) { + case 0: + keep_mask = 0xf8; + new_shift = 0; + break; + case 1: + keep_mask = 0x8f; + new_shift = 4; + break; + } + + fan_div_reg = w83l786ng_read_value(client, W83L786NG_REG_FAN_DIV) + & keep_mask; + + tmp_fan_div = (data->fan_div[nr] << new_shift) & ~keep_mask; + + w83l786ng_write_value(client, W83L786NG_REG_FAN_DIV, + fan_div_reg | tmp_fan_div); + + /* Restore fan_min */ + data->fan_min[nr] = FAN_TO_REG(min, DIV_FROM_REG(data->fan_div[nr])); + w83l786ng_write_value(client, W83L786NG_REG_FAN_MIN(nr), + data->fan_min[nr]); + mutex_unlock(&data->update_lock); + + return count; +} + +static struct sensor_device_attribute sda_fan_input[] = { + SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0), + SENSOR_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1), +}; + +static struct sensor_device_attribute sda_fan_min[] = { + SENSOR_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min, + store_fan_min, 0), + SENSOR_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min, + store_fan_min, 1), +}; + +static struct sensor_device_attribute sda_fan_div[] = { + SENSOR_ATTR(fan1_div, S_IWUSR | S_IRUGO, show_fan_div, + store_fan_div, 0), + SENSOR_ATTR(fan2_div, S_IWUSR | S_IRUGO, show_fan_div, + store_fan_div, 1), +}; + + +/* read/write the temperature, includes measured value and limits */ + +static ssize_t +show_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83l786ng_data *data = w83l786ng_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[nr][index])); +} + +static ssize_t +store_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int index = sensor_attr->index; + struct w83l786ng_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + data->temp[nr][index] = TEMP_TO_REG(val); + w83l786ng_write_value(client, W83L786NG_REG_TEMP[nr][index], + data->temp[nr][index]); + mutex_unlock(&data->update_lock); + + return count; +} + +static struct sensor_device_attribute_2 sda_temp_input[] = { + SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0), + SENSOR_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 1, 0), +}; + +static struct sensor_device_attribute_2 sda_temp_max[] = { + SENSOR_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0, 1), + SENSOR_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, + show_temp, store_temp, 1, 1), +}; + +static struct sensor_device_attribute_2 sda_temp_max_hyst[] = { + SENSOR_ATTR_2(temp1_max_hyst, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0, 2), + SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO | S_IWUSR, + show_temp, store_temp, 1, 2), +}; + +#define show_pwm_reg(reg) \ +static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct w83l786ng_data *data = w83l786ng_update_device(dev); \ + int nr = to_sensor_dev_attr(attr)->index; \ + return sprintf(buf, "%d\n", data->reg[nr]); \ +} + +show_pwm_reg(pwm_mode) +show_pwm_reg(pwm_enable) +show_pwm_reg(pwm) + +static ssize_t +store_pwm_mode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct w83l786ng_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 reg; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (val > 1) + return -EINVAL; + mutex_lock(&data->update_lock); + data->pwm_mode[nr] = val; + reg = w83l786ng_read_value(client, W83L786NG_REG_FAN_CFG); + reg &= ~(1 << W83L786NG_PWM_MODE_SHIFT[nr]); + if (!val) + reg |= 1 << W83L786NG_PWM_MODE_SHIFT[nr]; + w83l786ng_write_value(client, W83L786NG_REG_FAN_CFG, reg); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +store_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct w83l786ng_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + val = clamp_val(val, 0, 255); + val = DIV_ROUND_CLOSEST(val, 0x11); + + mutex_lock(&data->update_lock); + data->pwm[nr] = val * 0x11; + val |= w83l786ng_read_value(client, W83L786NG_REG_PWM[nr]) & 0xf0; + w83l786ng_write_value(client, W83L786NG_REG_PWM[nr], val); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +store_pwm_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct w83l786ng_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 reg; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + if (!val || val > 2) /* only modes 1 and 2 are supported */ + return -EINVAL; + + mutex_lock(&data->update_lock); + reg = w83l786ng_read_value(client, W83L786NG_REG_FAN_CFG); + data->pwm_enable[nr] = val; + reg &= ~(0x03 << W83L786NG_PWM_ENABLE_SHIFT[nr]); + reg |= (val - 1) << W83L786NG_PWM_ENABLE_SHIFT[nr]; + w83l786ng_write_value(client, W83L786NG_REG_FAN_CFG, reg); + mutex_unlock(&data->update_lock); + return count; +} + +static struct sensor_device_attribute sda_pwm[] = { + SENSOR_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0), + SENSOR_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, store_pwm, 1), +}; + +static struct sensor_device_attribute sda_pwm_mode[] = { + SENSOR_ATTR(pwm1_mode, S_IWUSR | S_IRUGO, show_pwm_mode, + store_pwm_mode, 0), + SENSOR_ATTR(pwm2_mode, S_IWUSR | S_IRUGO, show_pwm_mode, + store_pwm_mode, 1), +}; + +static struct sensor_device_attribute sda_pwm_enable[] = { + SENSOR_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, show_pwm_enable, + store_pwm_enable, 0), + SENSOR_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, show_pwm_enable, + store_pwm_enable, 1), +}; + +/* For Smart Fan I/Thermal Cruise and Smart Fan II */ +static ssize_t +show_tolerance(struct device *dev, struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct w83l786ng_data *data = w83l786ng_update_device(dev); + return sprintf(buf, "%ld\n", (long)data->tolerance[nr]); +} + +static ssize_t +store_tolerance(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct w83l786ng_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 tol_tmp, tol_mask; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + + mutex_lock(&data->update_lock); + tol_mask = w83l786ng_read_value(client, + W83L786NG_REG_TOLERANCE) & ((nr == 1) ? 0x0f : 0xf0); + tol_tmp = clamp_val(val, 0, 15); + tol_tmp &= 0x0f; + data->tolerance[nr] = tol_tmp; + if (nr == 1) + tol_tmp <<= 4; + + w83l786ng_write_value(client, W83L786NG_REG_TOLERANCE, + tol_mask | tol_tmp); + mutex_unlock(&data->update_lock); + return count; +} + +static struct sensor_device_attribute sda_tolerance[] = { + SENSOR_ATTR(pwm1_tolerance, S_IWUSR | S_IRUGO, + show_tolerance, store_tolerance, 0), + SENSOR_ATTR(pwm2_tolerance, S_IWUSR | S_IRUGO, + show_tolerance, store_tolerance, 1), +}; + + +#define IN_UNIT_ATTRS(X) \ + &sda_in_input[X].dev_attr.attr, \ + &sda_in_min[X].dev_attr.attr, \ + &sda_in_max[X].dev_attr.attr + +#define FAN_UNIT_ATTRS(X) \ + &sda_fan_input[X].dev_attr.attr, \ + &sda_fan_min[X].dev_attr.attr, \ + &sda_fan_div[X].dev_attr.attr + +#define TEMP_UNIT_ATTRS(X) \ + &sda_temp_input[X].dev_attr.attr, \ + &sda_temp_max[X].dev_attr.attr, \ + &sda_temp_max_hyst[X].dev_attr.attr + +#define PWM_UNIT_ATTRS(X) \ + &sda_pwm[X].dev_attr.attr, \ + &sda_pwm_mode[X].dev_attr.attr, \ + &sda_pwm_enable[X].dev_attr.attr + +#define TOLERANCE_UNIT_ATTRS(X) \ + &sda_tolerance[X].dev_attr.attr + +static struct attribute *w83l786ng_attrs[] = { + IN_UNIT_ATTRS(0), + IN_UNIT_ATTRS(1), + IN_UNIT_ATTRS(2), + FAN_UNIT_ATTRS(0), + FAN_UNIT_ATTRS(1), + TEMP_UNIT_ATTRS(0), + TEMP_UNIT_ATTRS(1), + PWM_UNIT_ATTRS(0), + PWM_UNIT_ATTRS(1), + TOLERANCE_UNIT_ATTRS(0), + TOLERANCE_UNIT_ATTRS(1), + NULL +}; + +ATTRIBUTE_GROUPS(w83l786ng); + +static int +w83l786ng_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + u16 man_id; + u8 chip_id; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* Detection */ + if ((w83l786ng_read_value(client, W83L786NG_REG_CONFIG) & 0x80)) { + dev_dbg(&adapter->dev, "W83L786NG detection failed at 0x%02x\n", + client->addr); + return -ENODEV; + } + + /* Identification */ + man_id = (w83l786ng_read_value(client, W83L786NG_REG_MAN_ID1) << 8) + + w83l786ng_read_value(client, W83L786NG_REG_MAN_ID2); + chip_id = w83l786ng_read_value(client, W83L786NG_REG_CHIP_ID); + + if (man_id != 0x5CA3 || /* Winbond */ + chip_id != 0x80) { /* W83L786NG */ + dev_dbg(&adapter->dev, + "Unsupported chip (man_id=0x%04X, chip_id=0x%02X)\n", + man_id, chip_id); + return -ENODEV; + } + + strlcpy(info->type, "w83l786ng", I2C_NAME_SIZE); + + return 0; +} + +static void w83l786ng_init_client(struct i2c_client *client) +{ + u8 tmp; + + if (reset) + w83l786ng_write_value(client, W83L786NG_REG_CONFIG, 0x80); + + /* Start monitoring */ + tmp = w83l786ng_read_value(client, W83L786NG_REG_CONFIG); + if (!(tmp & 0x01)) + w83l786ng_write_value(client, W83L786NG_REG_CONFIG, tmp | 0x01); +} + +static int +w83l786ng_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct w83l786ng_data *data; + struct device *hwmon_dev; + int i; + u8 reg_tmp; + + data = devm_kzalloc(dev, sizeof(struct w83l786ng_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + /* Initialize the chip */ + w83l786ng_init_client(client); + + /* A few vars need to be filled upon startup */ + for (i = 0; i < 2; i++) { + data->fan_min[i] = w83l786ng_read_value(client, + W83L786NG_REG_FAN_MIN(i)); + } + + /* Update the fan divisor */ + reg_tmp = w83l786ng_read_value(client, W83L786NG_REG_FAN_DIV); + data->fan_div[0] = reg_tmp & 0x07; + data->fan_div[1] = (reg_tmp >> 4) & 0x07; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + w83l786ng_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id w83l786ng_id[] = { + { "w83l786ng", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, w83l786ng_id); + +static struct i2c_driver w83l786ng_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "w83l786ng", + }, + .probe = w83l786ng_probe, + .id_table = w83l786ng_id, + .detect = w83l786ng_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(w83l786ng_driver); + +MODULE_AUTHOR("Kevin Lo"); +MODULE_DESCRIPTION("w83l786ng driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/wm831x-hwmon.c b/drivers/hwmon/wm831x-hwmon.c new file mode 100644 index 000000000..a16cce72e --- /dev/null +++ b/drivers/hwmon/wm831x-hwmon.c @@ -0,0 +1,165 @@ +/* + * drivers/hwmon/wm831x-hwmon.c - Wolfson Microelectronics WM831x PMIC + * hardware monitoring features. + * + * Copyright (C) 2009 Wolfson Microelectronics plc + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static const char * const input_names[] = { + [WM831X_AUX_SYSVDD] = "SYSVDD", + [WM831X_AUX_USB] = "USB", + [WM831X_AUX_BKUP_BATT] = "Backup battery", + [WM831X_AUX_BATT] = "Battery", + [WM831X_AUX_WALL] = "WALL", + [WM831X_AUX_CHIP_TEMP] = "PMIC", + [WM831X_AUX_BATT_TEMP] = "Battery", +}; + +static ssize_t show_voltage(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm831x *wm831x = dev_get_drvdata(dev); + int channel = to_sensor_dev_attr(attr)->index; + int ret; + + ret = wm831x_auxadc_read_uv(wm831x, channel); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", DIV_ROUND_CLOSEST(ret, 1000)); +} + +static ssize_t show_chip_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm831x *wm831x = dev_get_drvdata(dev); + int channel = to_sensor_dev_attr(attr)->index; + int ret; + + ret = wm831x_auxadc_read(wm831x, channel); + if (ret < 0) + return ret; + + /* Degrees celsius = (512.18-ret) / 1.0983 */ + ret = 512180 - (ret * 1000); + ret = DIV_ROUND_CLOSEST(ret * 10000, 10983); + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int channel = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%s\n", input_names[channel]); +} + +#define WM831X_VOLTAGE(id, name) \ + static SENSOR_DEVICE_ATTR(in##id##_input, S_IRUGO, show_voltage, \ + NULL, name) + +#define WM831X_NAMED_VOLTAGE(id, name) \ + WM831X_VOLTAGE(id, name); \ + static SENSOR_DEVICE_ATTR(in##id##_label, S_IRUGO, show_label, \ + NULL, name) + +WM831X_VOLTAGE(0, WM831X_AUX_AUX1); +WM831X_VOLTAGE(1, WM831X_AUX_AUX2); +WM831X_VOLTAGE(2, WM831X_AUX_AUX3); +WM831X_VOLTAGE(3, WM831X_AUX_AUX4); + +WM831X_NAMED_VOLTAGE(4, WM831X_AUX_SYSVDD); +WM831X_NAMED_VOLTAGE(5, WM831X_AUX_USB); +WM831X_NAMED_VOLTAGE(6, WM831X_AUX_BATT); +WM831X_NAMED_VOLTAGE(7, WM831X_AUX_WALL); +WM831X_NAMED_VOLTAGE(8, WM831X_AUX_BKUP_BATT); + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_chip_temp, NULL, + WM831X_AUX_CHIP_TEMP); +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, + WM831X_AUX_CHIP_TEMP); +/* + * Report as a voltage since conversion depends on external components + * and that's what the ABI wants. + */ +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_voltage, NULL, + WM831X_AUX_BATT_TEMP); +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, + WM831X_AUX_BATT_TEMP); + +static struct attribute *wm831x_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_label.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_label.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_label.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_label.dev_attr.attr, + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in8_label.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_label.dev_attr.attr, + + NULL +}; + +ATTRIBUTE_GROUPS(wm831x); + +static int wm831x_hwmon_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct device *hwmon_dev; + + hwmon_dev = devm_hwmon_device_register_with_groups(&pdev->dev, "wm831x", + wm831x, + wm831x_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct platform_driver wm831x_hwmon_driver = { + .probe = wm831x_hwmon_probe, + .driver = { + .name = "wm831x-hwmon", + }, +}; + +module_platform_driver(wm831x_hwmon_driver); + +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("WM831x Hardware Monitoring"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm831x-hwmon"); diff --git a/drivers/hwmon/wm8350-hwmon.c b/drivers/hwmon/wm8350-hwmon.c new file mode 100644 index 000000000..31af438ff --- /dev/null +++ b/drivers/hwmon/wm8350-hwmon.c @@ -0,0 +1,104 @@ +/* + * drivers/hwmon/wm8350-hwmon.c - Wolfson Microelectronics WM8350 PMIC + * hardware monitoring features. + * + * Copyright (C) 2009 Wolfson Microelectronics plc + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +static const char * const input_names[] = { + [WM8350_AUXADC_USB] = "USB", + [WM8350_AUXADC_LINE] = "Line", + [WM8350_AUXADC_BATT] = "Battery", +}; + +static ssize_t show_voltage(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm8350 *wm8350 = dev_get_drvdata(dev); + int channel = to_sensor_dev_attr(attr)->index; + int val; + + val = wm8350_read_auxadc(wm8350, channel, 0, 0) * WM8350_AUX_COEFF; + val = DIV_ROUND_CLOSEST(val, 1000); + + return sprintf(buf, "%d\n", val); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int channel = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%s\n", input_names[channel]); +} + +#define WM8350_NAMED_VOLTAGE(id, name) \ + static SENSOR_DEVICE_ATTR(in##id##_input, S_IRUGO, show_voltage,\ + NULL, name); \ + static SENSOR_DEVICE_ATTR(in##id##_label, S_IRUGO, show_label, \ + NULL, name) + +WM8350_NAMED_VOLTAGE(0, WM8350_AUXADC_USB); +WM8350_NAMED_VOLTAGE(1, WM8350_AUXADC_BATT); +WM8350_NAMED_VOLTAGE(2, WM8350_AUXADC_LINE); + +static struct attribute *wm8350_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_label.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_label.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_label.dev_attr.attr, + + NULL, +}; + +ATTRIBUTE_GROUPS(wm8350); + +static int wm8350_hwmon_probe(struct platform_device *pdev) +{ + struct wm8350 *wm8350 = platform_get_drvdata(pdev); + struct device *hwmon_dev; + + hwmon_dev = devm_hwmon_device_register_with_groups(&pdev->dev, "wm8350", + wm8350, + wm8350_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct platform_driver wm8350_hwmon_driver = { + .probe = wm8350_hwmon_probe, + .driver = { + .name = "wm8350-hwmon", + }, +}; + +module_platform_driver(wm8350_hwmon_driver); + +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("WM8350 Hardware Monitoring"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8350-hwmon"); diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c new file mode 100644 index 000000000..a3cd91f23 --- /dev/null +++ b/drivers/hwmon/xgene-hwmon.c @@ -0,0 +1,811 @@ +/* + * APM X-Gene SoC Hardware Monitoring Driver + * + * Copyright (c) 2016, Applied Micro Circuits Corporation + * Author: Loc Ho + * Hoan Tran + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * This driver provides the following features: + * - Retrieve CPU total power (uW) + * - Retrieve IO total power (uW) + * - Retrieve SoC temperature (milli-degree C) and alarm + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* SLIMpro message defines */ +#define MSG_TYPE_DBG 0 +#define MSG_TYPE_ERR 7 +#define MSG_TYPE_PWRMGMT 9 + +#define MSG_TYPE(v) (((v) & 0xF0000000) >> 28) +#define MSG_TYPE_SET(v) (((v) << 28) & 0xF0000000) +#define MSG_SUBTYPE(v) (((v) & 0x0F000000) >> 24) +#define MSG_SUBTYPE_SET(v) (((v) << 24) & 0x0F000000) + +#define DBG_SUBTYPE_SENSOR_READ 4 +#define SENSOR_RD_MSG 0x04FFE902 +#define SENSOR_RD_EN_ADDR(a) ((a) & 0x000FFFFF) +#define PMD_PWR_REG 0x20 +#define PMD_PWR_MW_REG 0x26 +#define SOC_PWR_REG 0x21 +#define SOC_PWR_MW_REG 0x27 +#define SOC_TEMP_REG 0x10 + +#define TEMP_NEGATIVE_BIT 8 +#define SENSOR_INVALID_DATA BIT(15) + +#define PWRMGMT_SUBTYPE_TPC 1 +#define TPC_ALARM 2 +#define TPC_GET_ALARM 3 +#define TPC_CMD(v) (((v) & 0x00FF0000) >> 16) +#define TPC_CMD_SET(v) (((v) << 16) & 0x00FF0000) +#define TPC_EN_MSG(hndl, cmd, type) \ + (MSG_TYPE_SET(MSG_TYPE_PWRMGMT) | \ + MSG_SUBTYPE_SET(hndl) | TPC_CMD_SET(cmd) | type) + +/* PCC defines */ +#define PCC_SIGNATURE_MASK 0x50424300 +#define PCCC_GENERATE_DB_INT BIT(15) +#define PCCS_CMD_COMPLETE BIT(0) +#define PCCS_SCI_DOORBEL BIT(1) +#define PCCS_PLATFORM_NOTIFICATION BIT(3) +/* + * Arbitrary retries in case the remote processor is slow to respond + * to PCC commands + */ +#define PCC_NUM_RETRIES 500 + +#define ASYNC_MSG_FIFO_SIZE 16 +#define MBOX_OP_TIMEOUTMS 1000 + +#define WATT_TO_mWATT(x) ((x) * 1000) +#define mWATT_TO_uWATT(x) ((x) * 1000) +#define CELSIUS_TO_mCELSIUS(x) ((x) * 1000) + +#define to_xgene_hwmon_dev(cl) \ + container_of(cl, struct xgene_hwmon_dev, mbox_client) + +enum xgene_hwmon_version { + XGENE_HWMON_V1 = 0, + XGENE_HWMON_V2 = 1, +}; + +struct slimpro_resp_msg { + u32 msg; + u32 param1; + u32 param2; +} __packed; + +struct xgene_hwmon_dev { + struct device *dev; + struct mbox_chan *mbox_chan; + struct mbox_client mbox_client; + int mbox_idx; + + spinlock_t kfifo_lock; + struct mutex rd_mutex; + struct completion rd_complete; + int resp_pending; + struct slimpro_resp_msg sync_msg; + + struct work_struct workq; + struct kfifo_rec_ptr_1 async_msg_fifo; + + struct device *hwmon_dev; + bool temp_critical_alarm; + + phys_addr_t comm_base_addr; + void *pcc_comm_addr; + u64 usecs_lat; +}; + +/* + * This function tests and clears a bitmask then returns its old value + */ +static u16 xgene_word_tst_and_clr(u16 *addr, u16 mask) +{ + u16 ret, val; + + val = le16_to_cpu(READ_ONCE(*addr)); + ret = val & mask; + val &= ~mask; + WRITE_ONCE(*addr, cpu_to_le16(val)); + + return ret; +} + +static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg) +{ + struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr; + u32 *ptr = (void *)(generic_comm_base + 1); + int rc, i; + u16 val; + + mutex_lock(&ctx->rd_mutex); + init_completion(&ctx->rd_complete); + ctx->resp_pending = true; + + /* Write signature for subspace */ + WRITE_ONCE(generic_comm_base->signature, + cpu_to_le32(PCC_SIGNATURE_MASK | ctx->mbox_idx)); + + /* Write to the shared command region */ + WRITE_ONCE(generic_comm_base->command, + cpu_to_le16(MSG_TYPE(msg[0]) | PCCC_GENERATE_DB_INT)); + + /* Flip CMD COMPLETE bit */ + val = le16_to_cpu(READ_ONCE(generic_comm_base->status)); + val &= ~PCCS_CMD_COMPLETE; + WRITE_ONCE(generic_comm_base->status, cpu_to_le16(val)); + + /* Copy the message to the PCC comm space */ + for (i = 0; i < sizeof(struct slimpro_resp_msg) / 4; i++) + WRITE_ONCE(ptr[i], cpu_to_le32(msg[i])); + + /* Ring the doorbell */ + rc = mbox_send_message(ctx->mbox_chan, msg); + if (rc < 0) { + dev_err(ctx->dev, "Mailbox send error %d\n", rc); + goto err; + } + if (!wait_for_completion_timeout(&ctx->rd_complete, + usecs_to_jiffies(ctx->usecs_lat))) { + dev_err(ctx->dev, "Mailbox operation timed out\n"); + rc = -ETIMEDOUT; + goto err; + } + + /* Check for error message */ + if (MSG_TYPE(ctx->sync_msg.msg) == MSG_TYPE_ERR) { + rc = -EINVAL; + goto err; + } + + msg[0] = ctx->sync_msg.msg; + msg[1] = ctx->sync_msg.param1; + msg[2] = ctx->sync_msg.param2; + +err: + mbox_chan_txdone(ctx->mbox_chan, 0); + ctx->resp_pending = false; + mutex_unlock(&ctx->rd_mutex); + return rc; +} + +static int xgene_hwmon_rd(struct xgene_hwmon_dev *ctx, u32 *msg) +{ + int rc; + + mutex_lock(&ctx->rd_mutex); + init_completion(&ctx->rd_complete); + ctx->resp_pending = true; + + rc = mbox_send_message(ctx->mbox_chan, msg); + if (rc < 0) { + dev_err(ctx->dev, "Mailbox send error %d\n", rc); + goto err; + } + + if (!wait_for_completion_timeout(&ctx->rd_complete, + msecs_to_jiffies(MBOX_OP_TIMEOUTMS))) { + dev_err(ctx->dev, "Mailbox operation timed out\n"); + rc = -ETIMEDOUT; + goto err; + } + + /* Check for error message */ + if (MSG_TYPE(ctx->sync_msg.msg) == MSG_TYPE_ERR) { + rc = -EINVAL; + goto err; + } + + msg[0] = ctx->sync_msg.msg; + msg[1] = ctx->sync_msg.param1; + msg[2] = ctx->sync_msg.param2; + +err: + ctx->resp_pending = false; + mutex_unlock(&ctx->rd_mutex); + return rc; +} + +static int xgene_hwmon_reg_map_rd(struct xgene_hwmon_dev *ctx, u32 addr, + u32 *data) +{ + u32 msg[3]; + int rc; + + msg[0] = SENSOR_RD_MSG; + msg[1] = SENSOR_RD_EN_ADDR(addr); + msg[2] = 0; + + if (acpi_disabled) + rc = xgene_hwmon_rd(ctx, msg); + else + rc = xgene_hwmon_pcc_rd(ctx, msg); + + if (rc < 0) + return rc; + + /* + * Check if sensor data is valid. + */ + if (msg[1] & SENSOR_INVALID_DATA) + return -ENODATA; + + *data = msg[1]; + + return rc; +} + +static int xgene_hwmon_get_notification_msg(struct xgene_hwmon_dev *ctx, + u32 *amsg) +{ + u32 msg[3]; + int rc; + + msg[0] = TPC_EN_MSG(PWRMGMT_SUBTYPE_TPC, TPC_GET_ALARM, 0); + msg[1] = 0; + msg[2] = 0; + + rc = xgene_hwmon_pcc_rd(ctx, msg); + if (rc < 0) + return rc; + + amsg[0] = msg[0]; + amsg[1] = msg[1]; + amsg[2] = msg[2]; + + return rc; +} + +static int xgene_hwmon_get_cpu_pwr(struct xgene_hwmon_dev *ctx, u32 *val) +{ + u32 watt, mwatt; + int rc; + + rc = xgene_hwmon_reg_map_rd(ctx, PMD_PWR_REG, &watt); + if (rc < 0) + return rc; + + rc = xgene_hwmon_reg_map_rd(ctx, PMD_PWR_MW_REG, &mwatt); + if (rc < 0) + return rc; + + *val = WATT_TO_mWATT(watt) + mwatt; + return 0; +} + +static int xgene_hwmon_get_io_pwr(struct xgene_hwmon_dev *ctx, u32 *val) +{ + u32 watt, mwatt; + int rc; + + rc = xgene_hwmon_reg_map_rd(ctx, SOC_PWR_REG, &watt); + if (rc < 0) + return rc; + + rc = xgene_hwmon_reg_map_rd(ctx, SOC_PWR_MW_REG, &mwatt); + if (rc < 0) + return rc; + + *val = WATT_TO_mWATT(watt) + mwatt; + return 0; +} + +static int xgene_hwmon_get_temp(struct xgene_hwmon_dev *ctx, u32 *val) +{ + return xgene_hwmon_reg_map_rd(ctx, SOC_TEMP_REG, val); +} + +/* + * Sensor temperature/power functions + */ +static ssize_t temp1_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev); + int rc, temp; + u32 val; + + rc = xgene_hwmon_get_temp(ctx, &val); + if (rc < 0) + return rc; + + temp = sign_extend32(val, TEMP_NEGATIVE_BIT); + + return snprintf(buf, PAGE_SIZE, "%d\n", CELSIUS_TO_mCELSIUS(temp)); +} + +static ssize_t temp1_label_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "SoC Temperature\n"); +} + +static ssize_t temp1_critical_alarm_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", ctx->temp_critical_alarm); +} + +static ssize_t power1_label_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "CPU power\n"); +} + +static ssize_t power2_label_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "IO power\n"); +} + +static ssize_t power1_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev); + u32 val; + int rc; + + rc = xgene_hwmon_get_cpu_pwr(ctx, &val); + if (rc < 0) + return rc; + + return snprintf(buf, PAGE_SIZE, "%u\n", mWATT_TO_uWATT(val)); +} + +static ssize_t power2_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct xgene_hwmon_dev *ctx = dev_get_drvdata(dev); + u32 val; + int rc; + + rc = xgene_hwmon_get_io_pwr(ctx, &val); + if (rc < 0) + return rc; + + return snprintf(buf, PAGE_SIZE, "%u\n", mWATT_TO_uWATT(val)); +} + +static DEVICE_ATTR_RO(temp1_label); +static DEVICE_ATTR_RO(temp1_input); +static DEVICE_ATTR_RO(temp1_critical_alarm); +static DEVICE_ATTR_RO(power1_label); +static DEVICE_ATTR_RO(power1_input); +static DEVICE_ATTR_RO(power2_label); +static DEVICE_ATTR_RO(power2_input); + +static struct attribute *xgene_hwmon_attrs[] = { + &dev_attr_temp1_label.attr, + &dev_attr_temp1_input.attr, + &dev_attr_temp1_critical_alarm.attr, + &dev_attr_power1_label.attr, + &dev_attr_power1_input.attr, + &dev_attr_power2_label.attr, + &dev_attr_power2_input.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(xgene_hwmon); + +static int xgene_hwmon_tpc_alarm(struct xgene_hwmon_dev *ctx, + struct slimpro_resp_msg *amsg) +{ + ctx->temp_critical_alarm = !!amsg->param2; + sysfs_notify(&ctx->dev->kobj, NULL, "temp1_critical_alarm"); + + return 0; +} + +static void xgene_hwmon_process_pwrmsg(struct xgene_hwmon_dev *ctx, + struct slimpro_resp_msg *amsg) +{ + if ((MSG_SUBTYPE(amsg->msg) == PWRMGMT_SUBTYPE_TPC) && + (TPC_CMD(amsg->msg) == TPC_ALARM)) + xgene_hwmon_tpc_alarm(ctx, amsg); +} + +/* + * This function is called to process async work queue + */ +static void xgene_hwmon_evt_work(struct work_struct *work) +{ + struct slimpro_resp_msg amsg; + struct xgene_hwmon_dev *ctx; + int ret; + + ctx = container_of(work, struct xgene_hwmon_dev, workq); + while (kfifo_out_spinlocked(&ctx->async_msg_fifo, &amsg, + sizeof(struct slimpro_resp_msg), + &ctx->kfifo_lock)) { + /* + * If PCC, send a consumer command to Platform to get info + * If Slimpro Mailbox, get message from specific FIFO + */ + if (!acpi_disabled) { + ret = xgene_hwmon_get_notification_msg(ctx, + (u32 *)&amsg); + if (ret < 0) + continue; + } + + if (MSG_TYPE(amsg.msg) == MSG_TYPE_PWRMGMT) + xgene_hwmon_process_pwrmsg(ctx, &amsg); + } +} + +static int xgene_hwmon_rx_ready(struct xgene_hwmon_dev *ctx, void *msg) +{ + if (IS_ERR_OR_NULL(ctx->hwmon_dev) && !ctx->resp_pending) { + /* Enqueue to the FIFO */ + kfifo_in_spinlocked(&ctx->async_msg_fifo, msg, + sizeof(struct slimpro_resp_msg), + &ctx->kfifo_lock); + return -ENODEV; + } + + return 0; +} + +/* + * This function is called when the SLIMpro Mailbox received a message + */ +static void xgene_hwmon_rx_cb(struct mbox_client *cl, void *msg) +{ + struct xgene_hwmon_dev *ctx = to_xgene_hwmon_dev(cl); + + /* + * While the driver registers with the mailbox framework, an interrupt + * can be pending before the probe function completes its + * initialization. If such condition occurs, just queue up the message + * as the driver is not ready for servicing the callback. + */ + if (xgene_hwmon_rx_ready(ctx, msg) < 0) + return; + + /* + * Response message format: + * msg[0] is the return code of the operation + * msg[1] is the first parameter word + * msg[2] is the second parameter word + * + * As message only supports dword size, just assign it. + */ + + /* Check for sync query */ + if (ctx->resp_pending && + ((MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_ERR) || + (MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_DBG && + MSG_SUBTYPE(((u32 *)msg)[0]) == DBG_SUBTYPE_SENSOR_READ) || + (MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_PWRMGMT && + MSG_SUBTYPE(((u32 *)msg)[0]) == PWRMGMT_SUBTYPE_TPC && + TPC_CMD(((u32 *)msg)[0]) == TPC_ALARM))) { + ctx->sync_msg.msg = ((u32 *)msg)[0]; + ctx->sync_msg.param1 = ((u32 *)msg)[1]; + ctx->sync_msg.param2 = ((u32 *)msg)[2]; + + /* Operation waiting for response */ + complete(&ctx->rd_complete); + + return; + } + + /* Enqueue to the FIFO */ + kfifo_in_spinlocked(&ctx->async_msg_fifo, msg, + sizeof(struct slimpro_resp_msg), &ctx->kfifo_lock); + /* Schedule the bottom handler */ + schedule_work(&ctx->workq); +} + +/* + * This function is called when the PCC Mailbox received a message + */ +static void xgene_hwmon_pcc_rx_cb(struct mbox_client *cl, void *msg) +{ + struct xgene_hwmon_dev *ctx = to_xgene_hwmon_dev(cl); + struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr; + struct slimpro_resp_msg amsg; + + /* + * While the driver registers with the mailbox framework, an interrupt + * can be pending before the probe function completes its + * initialization. If such condition occurs, just queue up the message + * as the driver is not ready for servicing the callback. + */ + if (xgene_hwmon_rx_ready(ctx, &amsg) < 0) + return; + + msg = generic_comm_base + 1; + /* Check if platform sends interrupt */ + if (!xgene_word_tst_and_clr(&generic_comm_base->status, + PCCS_SCI_DOORBEL)) + return; + + /* + * Response message format: + * msg[0] is the return code of the operation + * msg[1] is the first parameter word + * msg[2] is the second parameter word + * + * As message only supports dword size, just assign it. + */ + + /* Check for sync query */ + if (ctx->resp_pending && + ((MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_ERR) || + (MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_DBG && + MSG_SUBTYPE(((u32 *)msg)[0]) == DBG_SUBTYPE_SENSOR_READ) || + (MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_PWRMGMT && + MSG_SUBTYPE(((u32 *)msg)[0]) == PWRMGMT_SUBTYPE_TPC && + TPC_CMD(((u32 *)msg)[0]) == TPC_ALARM))) { + /* Check if platform completes command */ + if (xgene_word_tst_and_clr(&generic_comm_base->status, + PCCS_CMD_COMPLETE)) { + ctx->sync_msg.msg = ((u32 *)msg)[0]; + ctx->sync_msg.param1 = ((u32 *)msg)[1]; + ctx->sync_msg.param2 = ((u32 *)msg)[2]; + + /* Operation waiting for response */ + complete(&ctx->rd_complete); + + return; + } + } + + /* + * Platform notifies interrupt to OSPM. + * OPSM schedules a consumer command to get this information + * in a workqueue. Platform must wait until OSPM has issued + * a consumer command that serves this notification. + */ + + /* Enqueue to the FIFO */ + kfifo_in_spinlocked(&ctx->async_msg_fifo, &amsg, + sizeof(struct slimpro_resp_msg), &ctx->kfifo_lock); + /* Schedule the bottom handler */ + schedule_work(&ctx->workq); +} + +static void xgene_hwmon_tx_done(struct mbox_client *cl, void *msg, int ret) +{ + if (ret) { + dev_dbg(cl->dev, "TX did not complete: CMD sent:%x, ret:%d\n", + *(u16 *)msg, ret); + } else { + dev_dbg(cl->dev, "TX completed. CMD sent:%x, ret:%d\n", + *(u16 *)msg, ret); + } +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id xgene_hwmon_acpi_match[] = { + {"APMC0D29", XGENE_HWMON_V1}, + {"APMC0D8A", XGENE_HWMON_V2}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, xgene_hwmon_acpi_match); +#endif + +static int xgene_hwmon_probe(struct platform_device *pdev) +{ + struct xgene_hwmon_dev *ctx; + struct mbox_client *cl; + int rc; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->dev = &pdev->dev; + platform_set_drvdata(pdev, ctx); + cl = &ctx->mbox_client; + + spin_lock_init(&ctx->kfifo_lock); + mutex_init(&ctx->rd_mutex); + + rc = kfifo_alloc(&ctx->async_msg_fifo, + sizeof(struct slimpro_resp_msg) * ASYNC_MSG_FIFO_SIZE, + GFP_KERNEL); + if (rc) + return -ENOMEM; + + INIT_WORK(&ctx->workq, xgene_hwmon_evt_work); + + /* Request mailbox channel */ + cl->dev = &pdev->dev; + cl->tx_done = xgene_hwmon_tx_done; + cl->tx_block = false; + cl->tx_tout = MBOX_OP_TIMEOUTMS; + cl->knows_txdone = false; + if (acpi_disabled) { + cl->rx_callback = xgene_hwmon_rx_cb; + ctx->mbox_chan = mbox_request_channel(cl, 0); + if (IS_ERR(ctx->mbox_chan)) { + dev_err(&pdev->dev, + "SLIMpro mailbox channel request failed\n"); + rc = -ENODEV; + goto out_mbox_free; + } + } else { + struct acpi_pcct_hw_reduced *cppc_ss; + const struct acpi_device_id *acpi_id; + int version; + + acpi_id = acpi_match_device(pdev->dev.driver->acpi_match_table, + &pdev->dev); + if (!acpi_id) + return -EINVAL; + + version = (int)acpi_id->driver_data; + + if (device_property_read_u32(&pdev->dev, "pcc-channel", + &ctx->mbox_idx)) { + dev_err(&pdev->dev, "no pcc-channel property\n"); + rc = -ENODEV; + goto out_mbox_free; + } + + cl->rx_callback = xgene_hwmon_pcc_rx_cb; + ctx->mbox_chan = pcc_mbox_request_channel(cl, ctx->mbox_idx); + if (IS_ERR(ctx->mbox_chan)) { + dev_err(&pdev->dev, + "PPC channel request failed\n"); + rc = -ENODEV; + goto out_mbox_free; + } + + /* + * The PCC mailbox controller driver should + * have parsed the PCCT (global table of all + * PCC channels) and stored pointers to the + * subspace communication region in con_priv. + */ + cppc_ss = ctx->mbox_chan->con_priv; + if (!cppc_ss) { + dev_err(&pdev->dev, "PPC subspace not found\n"); + rc = -ENODEV; + goto out; + } + + if (!ctx->mbox_chan->mbox->txdone_irq) { + dev_err(&pdev->dev, "PCC IRQ not supported\n"); + rc = -ENODEV; + goto out; + } + + /* + * This is the shared communication region + * for the OS and Platform to communicate over. + */ + ctx->comm_base_addr = cppc_ss->base_address; + if (ctx->comm_base_addr) { + if (version == XGENE_HWMON_V2) + ctx->pcc_comm_addr = (void __force *)ioremap( + ctx->comm_base_addr, + cppc_ss->length); + else + ctx->pcc_comm_addr = memremap( + ctx->comm_base_addr, + cppc_ss->length, + MEMREMAP_WB); + } else { + dev_err(&pdev->dev, "Failed to get PCC comm region\n"); + rc = -ENODEV; + goto out; + } + + if (!ctx->pcc_comm_addr) { + dev_err(&pdev->dev, + "Failed to ioremap PCC comm region\n"); + rc = -ENOMEM; + goto out; + } + + /* + * cppc_ss->latency is just a Nominal value. In reality + * the remote processor could be much slower to reply. + * So add an arbitrary amount of wait on top of Nominal. + */ + ctx->usecs_lat = PCC_NUM_RETRIES * cppc_ss->latency; + } + + ctx->hwmon_dev = hwmon_device_register_with_groups(ctx->dev, + "apm_xgene", + ctx, + xgene_hwmon_groups); + if (IS_ERR(ctx->hwmon_dev)) { + dev_err(&pdev->dev, "Failed to register HW monitor device\n"); + rc = PTR_ERR(ctx->hwmon_dev); + goto out; + } + + /* + * Schedule the bottom handler if there is a pending message. + */ + schedule_work(&ctx->workq); + + dev_info(&pdev->dev, "APM X-Gene SoC HW monitor driver registered\n"); + + return 0; + +out: + if (acpi_disabled) + mbox_free_channel(ctx->mbox_chan); + else + pcc_mbox_free_channel(ctx->mbox_chan); +out_mbox_free: + kfifo_free(&ctx->async_msg_fifo); + + return rc; +} + +static int xgene_hwmon_remove(struct platform_device *pdev) +{ + struct xgene_hwmon_dev *ctx = platform_get_drvdata(pdev); + + hwmon_device_unregister(ctx->hwmon_dev); + kfifo_free(&ctx->async_msg_fifo); + if (acpi_disabled) + mbox_free_channel(ctx->mbox_chan); + else + pcc_mbox_free_channel(ctx->mbox_chan); + + return 0; +} + +static const struct of_device_id xgene_hwmon_of_match[] = { + {.compatible = "apm,xgene-slimpro-hwmon"}, + {} +}; +MODULE_DEVICE_TABLE(of, xgene_hwmon_of_match); + +static struct platform_driver xgene_hwmon_driver __refdata = { + .probe = xgene_hwmon_probe, + .remove = xgene_hwmon_remove, + .driver = { + .name = "xgene-slimpro-hwmon", + .of_match_table = xgene_hwmon_of_match, + .acpi_match_table = ACPI_PTR(xgene_hwmon_acpi_match), + }, +}; +module_platform_driver(xgene_hwmon_driver); + +MODULE_DESCRIPTION("APM X-Gene SoC hardware monitor"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3