Created
August 26, 2022 17:50
-
-
Save shayne/bc9f3778b53134d3274f9794eba4f874 to your computer and use it in GitHub Desktop.
OpenWrt patch to add support for emc2301 pwm fan support cm4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/target/linux/bcm27xx/bcm2711/config-5.10 b/target/linux/bcm27xx/bcm2711/config-5.10 | |
index 03d84378b3..c6a08891e5 100644 | |
--- a/target/linux/bcm27xx/bcm2711/config-5.10 | |
+++ b/target/linux/bcm27xx/bcm2711/config-5.10 | |
@@ -57,7 +57,7 @@ CONFIG_BCM2835_DEVGPIOMEM=y | |
CONFIG_BCM2835_MBOX=y | |
CONFIG_BCM2835_POWER=y | |
# CONFIG_BCM2835_SMI is not set | |
-# CONFIG_BCM2835_THERMAL is not set | |
+CONFIG_BCM2835_THERMAL=y | |
CONFIG_BCM2835_VCHIQ=y | |
# CONFIG_BCM2835_VCHIQ_MMAL is not set | |
CONFIG_BCM2835_WDT=y | |
@@ -239,14 +239,28 @@ CONFIG_HAS_IOMEM=y | |
CONFIG_HAS_IOPORT_MAP=y | |
CONFIG_HOLES_IN_ZONE=y | |
CONFIG_HOTPLUG_CPU=y | |
+CONFIG_HWMON=y | |
CONFIG_HW_CONSOLE=y | |
CONFIG_HW_RANDOM=y | |
# CONFIG_HW_RANDOM_BCM2835 is not set | |
CONFIG_HW_RANDOM_IPROC_RNG200=y | |
CONFIG_I2C=y | |
-# CONFIG_I2C_BCM2708 is not set | |
+CONFIG_I2C_ALGOBIT=y | |
+CONFIG_I2C_BCM2708=y | |
+CONFIG_I2C_BCM2708_BAUDRATE=100000 | |
+CONFIG_I2C_BCM2835=y | |
CONFIG_I2C_BOARDINFO=y | |
-# CONFIG_I2C_BRCMSTB is not set | |
+CONFIG_I2C_BRCMSTB=y | |
+CONFIG_I2C_CHARDEV=y | |
+CONFIG_I2C_COMPAT=y | |
+CONFIG_I2C_GPIO=y | |
+CONFIG_I2C_HELPER_AUTO=y | |
+CONFIG_I2C_MUX=y | |
+CONFIG_I2C_MUX_GPMUX=y | |
+CONFIG_I2C_MUX_PCA954x=y | |
+CONFIG_I2C_MUX_PINCTRL=y | |
+CONFIG_I2C_ROBOTFUZZ_OSIF=y | |
+CONFIG_I2C_TINY_USB=y | |
CONFIG_IKCONFIG=y | |
CONFIG_IKCONFIG_PROC=y | |
CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 | |
@@ -302,6 +316,7 @@ CONFIG_MMC_SDHCI_IPROC=y | |
CONFIG_MMC_SDHCI_PLTFM=y | |
CONFIG_MODULES_USE_ELF_RELA=y | |
# CONFIG_MTD is not set | |
+CONFIG_MULTIPLEXER=y | |
CONFIG_MUTEX_SPIN_ON_OWNER=y | |
CONFIG_NEED_DMA_MAP_STATE=y | |
CONFIG_NEED_SG_DMA_LENGTH=y | |
@@ -354,6 +369,7 @@ CONFIG_PM_SLEEP=y | |
CONFIG_PM_SLEEP_SMP=y | |
CONFIG_POWER_RESET=y | |
CONFIG_POWER_SUPPLY=y | |
+CONFIG_POWER_SUPPLY_HWMON=y | |
CONFIG_PRINTK_TIME=y | |
CONFIG_QUEUED_RWLOCKS=y | |
CONFIG_QUEUED_SPINLOCKS=y | |
@@ -381,6 +397,9 @@ CONFIG_RWSEM_SPIN_ON_OWNER=y | |
CONFIG_SCSI=y | |
# CONFIG_SCSI_LOWLEVEL is not set | |
# CONFIG_SCSI_PROC_FS is not set | |
+CONFIG_SENSORS_EMC2301=y | |
+CONFIG_SENSORS_RASPBERRYPI_HWMON=y | |
+# CONFIG_SENSORS_RPI_POE_FAN is not set | |
CONFIG_SERIAL_8250_BCM2835AUX=y | |
# CONFIG_SERIAL_8250_DMA is not set | |
CONFIG_SERIAL_8250_EXTENDED=y | |
@@ -417,6 +436,7 @@ CONFIG_THERMAL=y | |
CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y | |
CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS=0 | |
CONFIG_THERMAL_GOV_STEP_WISE=y | |
+CONFIG_THERMAL_HWMON=y | |
CONFIG_THERMAL_OF=y | |
CONFIG_THERMAL_WRITABLE_TRIPS=y | |
CONFIG_THREAD_INFO_IN_TASK=y | |
diff --git a/target/linux/bcm27xx/patches-5.10/999-0000-Add-emc2301-cm4io-fan-support.patch b/target/linux/bcm27xx/patches-5.10/999-0000-Add-emc2301-cm4io-fan-support.patch | |
new file mode 100644 | |
index 0000000000..8acd725d8a | |
--- /dev/null | |
+++ b/target/linux/bcm27xx/patches-5.10/999-0000-Add-emc2301-cm4io-fan-support.patch | |
@@ -0,0 +1,630 @@ | |
+From 28159fb33035dd138fed590c920bb7d29facfabc Mon Sep 17 00:00:00 2001 | |
+From: shayne <[email protected]> | |
+Date: Wed, 29 Jun 2022 17:33:39 -0400 | |
+Subject: [PATCH] Add emc2301 cm4io-fan support | |
+ | |
+--- | |
+ arch/arm/boot/dts/overlays/Makefile | 1 + | |
+ .../boot/dts/overlays/cm4io-fan-overlay.dts | 72 +++ | |
+ drivers/hwmon/Kconfig | 10 + | |
+ drivers/hwmon/Makefile | 1 + | |
+ drivers/hwmon/emc2301.c | 483 ++++++++++++++++++ | |
+ 5 files changed, 567 insertions(+) | |
+ create mode 100644 arch/arm/boot/dts/overlays/cm4io-fan-overlay.dts | |
+ create mode 100644 drivers/hwmon/emc2301.c | |
+ | |
+diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile | |
+index fbcc3e35a..3921296fd 100644 | |
+--- a/arch/arm/boot/dts/overlays/Makefile | |
++++ b/arch/arm/boot/dts/overlays/Makefile | |
+@@ -33,6 +33,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \ | |
+ cap1106.dtbo \ | |
+ chipdip-dac.dtbo \ | |
+ cma.dtbo \ | |
++ cm4io-fan.dtbo \ | |
+ dht11.dtbo \ | |
+ dionaudio-loco.dtbo \ | |
+ dionaudio-loco-v2.dtbo \ | |
+diff --git a/arch/arm/boot/dts/overlays/cm4io-fan-overlay.dts b/arch/arm/boot/dts/overlays/cm4io-fan-overlay.dts | |
+new file mode 100644 | |
+index 000000000..901fd332b | |
+--- /dev/null | |
++++ b/arch/arm/boot/dts/overlays/cm4io-fan-overlay.dts | |
+@@ -0,0 +1,72 @@ | |
++/* | |
++ * Overlay for EMC2301 fan control chip on the Raspberry Pi Compute Module 4 IO board. | |
++ */ | |
++#include <dt-bindings/thermal/thermal.h> | |
++ | |
++/dts-v1/; | |
++/plugin/; | |
++ | |
++/ { | |
++ compatible = "brcm,bcm2835"; | |
++ | |
++ fragment@0 { | |
++ target = <&i2c_csi_dsi>; | |
++ __overlay__ { | |
++ status = "okay"; | |
++ }; | |
++ }; | |
++ | |
++ fragment@1 { | |
++ target = <&i2c_csi_dsi>; | |
++ __overlay__ { | |
++ fanctrl: emc2301@2f { | |
++ reg = <0x2f>; | |
++ compatible = "microchip,emc2301"; | |
++ #cooling-cells = <0x02>; | |
++ | |
++ fan0: fan@0 { | |
++ min-rpm = /bits/ 16 <3500>; | |
++ max-rpm = /bits/ 16 <5500>; | |
++ }; | |
++ }; | |
++ }; | |
++ }; | |
++ | |
++ fragment@2 { | |
++ target = <&cpu_thermal>; | |
++ polling-delay = <2000>; /* milliseconds */ | |
++ __overlay__ { | |
++ trips { | |
++ fanmid0: fanmid0 { | |
++ temperature = <50000>; | |
++ hysteresis = <2000>; | |
++ type = "active"; | |
++ }; | |
++ fanmax0: fanmax0 { | |
++ temperature = <75000>; | |
++ hysteresis = <2000>; | |
++ type = "active"; | |
++ }; | |
++ }; | |
++ cooling-maps { | |
++ map0 { | |
++ trip = <&fanmid0>; | |
++ cooling-device = <&fanctrl 2 6>; | |
++ }; | |
++ map1 { | |
++ trip = <&fanmax0>; | |
++ cooling-device = <&fanctrl 7 THERMAL_NO_LIMIT>; | |
++ }; | |
++ }; | |
++ }; | |
++ }; | |
++ | |
++ __overrides__ { | |
++ midtemp = <&fanmid0>,"temperature:0"; | |
++ midtemp_hyst = <&fanmid0>,"hysteresis:0"; | |
++ maxtemp = <&fanmax0>,"temperature:0"; | |
++ maxtemp_hyst = <&fanmax0>,"hysteresis:0"; | |
++ minrpm = <&fan0>,"min-rpm;0"; | |
++ maxrpm = <&fan0>,"max-rpm;0"; | |
++ }; | |
++}; | |
+diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig | |
+index a14a97785..513f30a0a 100644 | |
+--- a/drivers/hwmon/Kconfig | |
++++ b/drivers/hwmon/Kconfig | |
+@@ -1612,6 +1612,16 @@ config SENSORS_EMC2103 | |
+ This driver can also be built as a module. If so, the module | |
+ will be called emc2103. | |
+ | |
++config SENSORS_EMC2301 | |
++ tristate "SMSC EMC2301" | |
++ depends on I2C | |
++ help | |
++ If you say yes here you get support for the temperature | |
++ and fan sensors of the SMSC EMC2301 chips. | |
++ | |
++ This driver can also be built as a module. If so, the module | |
++ will be called emc2301. | |
++ | |
+ config SENSORS_EMC6W201 | |
+ tristate "SMSC EMC6W201" | |
+ depends on I2C | |
+diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile | |
+index dc68c9788..ca2ffe8e5 100644 | |
+--- a/drivers/hwmon/Makefile | |
++++ b/drivers/hwmon/Makefile | |
+@@ -66,6 +66,7 @@ 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_EMC2301) += emc2301.o | |
+ obj-$(CONFIG_SENSORS_EMC6W201) += emc6w201.o | |
+ obj-$(CONFIG_SENSORS_F71805F) += f71805f.o | |
+ obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o | |
+diff --git a/drivers/hwmon/emc2301.c b/drivers/hwmon/emc2301.c | |
+new file mode 100644 | |
+index 000000000..250651d8f | |
+--- /dev/null | |
++++ b/drivers/hwmon/emc2301.c | |
+@@ -0,0 +1,483 @@ | |
++/* | |
++ * Driver for the Microchip/SMSC EMC2301 Fan controller | |
++ * | |
++ * Copyright (C) 2018-2020 Traverse Technologies | |
++ * Author: Mathew McBride <[email protected]> | |
++ * | |
++ * Based in part on an earlier implementation by | |
++ * Reinhard Pfau <[email protected]> | |
++ * | |
++ * SPDX-License-Identifier: GPL-2.0 | |
++ * | |
++ */ | |
++ | |
++#include <linux/err.h> | |
++#include <linux/hwmon.h> | |
++#include <linux/hwmon-sysfs.h> | |
++#include <linux/i2c.h> | |
++#include <linux/kernel.h> | |
++#include <linux/module.h> | |
++#include <linux/version.h> | |
++#include <linux/thermal.h> | |
++ | |
++#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 2, 0) | |
++#define HWMON_CHANNEL_INFO(stype, ...) \ | |
++ (&(struct hwmon_channel_info){ .type = hwmon_##stype, .config = (u32[]){ __VA_ARGS__, 0 } }) | |
++#endif | |
++/* | |
++ * Factor by equations [2] and [3] from data sheet; valid for fans where the | |
++ * number of edges equals (poles * 2 + 1). | |
++ */ | |
++#define FAN_RPM_FACTOR 3932160 | |
++#define FAN_TACH_MULTIPLIER 1 | |
++ | |
++#define TACH_HIGH_MASK GENMASK(12, 5) | |
++#define TACH_LOW_MASK GENMASK(4, 0) | |
++ | |
++#define EMC230X_REG_PRODUCT_ID 0xFD | |
++ | |
++#define EMC230X_REG_MINTACH 0x38 | |
++ | |
++#define EMC230X_MAX_COOLING_STATE 7 | |
++ | |
++static bool register_cdev = 1; | |
++module_param(register_cdev, bool, 0); | |
++MODULE_PARM_DESC(register_cdev, "Set to zero to disable registering a cooling device"); | |
++ | |
++struct emc2301_data { | |
++ struct device *dev; | |
++ struct i2c_client *i2c; | |
++ u8 num_fans; | |
++ u16 minimum_rpm[5]; | |
++ struct thermal_cooling_device *cdev; | |
++ | |
++ u8 current_cooling_state; | |
++ u16 cooling_step[5]; | |
++ u16 min_rpm[5]; | |
++ u16 max_rpm[5]; | |
++ u16 setpoint[5]; | |
++}; | |
++ | |
++static u16 emc2301_read_fan_tach_int(struct emc2301_data *data, int channel) | |
++{ | |
++ struct i2c_client *i2c = data->i2c; | |
++ | |
++ u8 channel_reg; | |
++ u8 channel_high, channel_low; | |
++ u16 channel_combined; | |
++ | |
++ channel_reg = 0x3e + (channel * 0x02); | |
++#if 0 | |
++ dev_dbg(data->dev, "Reading channel %d register %X\n", channel, channel_reg); | |
++#endif | |
++ | |
++ channel_high = i2c_smbus_read_byte_data(i2c, channel_reg); | |
++ | |
++ channel_low = i2c_smbus_read_byte_data(i2c, channel_reg + 0x01); | |
++ channel_combined = ((u16)channel_high << 5) | (channel_low >> 3); | |
++ | |
++#if 0 | |
++ dev_dbg(data->dev, "Got values %04X for channel %d\n", channel_combined, channel); | |
++#endif | |
++ | |
++ return channel_combined; | |
++} | |
++ | |
++static u16 emc2301_read_fan_tach(struct device *dev, int channel) | |
++{ | |
++ struct emc2301_data *data = dev_get_drvdata(dev); | |
++ return emc2301_read_fan_tach_int(data, channel); | |
++} | |
++ | |
++static int emc2301_read_fan_rpm(struct device *dev, int channel, long *val) | |
++{ | |
++ u16 channel_tach; | |
++ u16 fan_rpm; | |
++ | |
++ channel_tach = emc2301_read_fan_tach(dev, channel); | |
++ | |
++ fan_rpm = (FAN_RPM_FACTOR * FAN_TACH_MULTIPLIER) / channel_tach; | |
++ *val = fan_rpm; | |
++ | |
++ return 0; | |
++} | |
++ | |
++/* Write a target RPM to the TACH target register | |
++ * This requires RPM speed control to be enabled with | |
++ * EN_ALGO in the fan configuration register. | |
++ */ | |
++static int emc2301_set_fan_rpm(struct emc2301_data *devdata, int channel, long target_rpm) | |
++{ | |
++ long rpm_high, rpm_low; | |
++ long target_tach; | |
++ u8 channel_reg; | |
++ | |
++ channel_reg = 0x3c + (channel * 10); | |
++ | |
++ target_tach = (FAN_RPM_FACTOR * FAN_TACH_MULTIPLIER) / target_rpm; | |
++ dev_dbg(devdata->dev, "RPM %ld requested, target tach is %ld\n", target_rpm, target_tach); | |
++ | |
++ rpm_high = (target_tach & TACH_HIGH_MASK) >> 5; | |
++ rpm_low = (target_tach & TACH_LOW_MASK); | |
++ | |
++#if 0 | |
++ dev_dbg(devdata->dev, "Writing %02lX %02lX to %02X+%02X\n", rpm_low, rpm_high, | |
++ channel_reg, channel_reg+1); | |
++#endif | |
++ | |
++ i2c_smbus_write_byte_data(devdata->i2c, channel_reg, rpm_low); | |
++ i2c_smbus_write_byte_data(devdata->i2c, channel_reg + 1, rpm_high); | |
++ | |
++ devdata->setpoint[channel] = (u16)target_rpm; | |
++ | |
++ return 0; | |
++} | |
++ | |
++static int emc2301_fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) | |
++{ | |
++ *state = EMC230X_MAX_COOLING_STATE; | |
++ return 0; | |
++} | |
++ | |
++static int emc2301_fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) | |
++{ | |
++ struct emc2301_data *devdata = cdev->devdata; | |
++ *state = devdata->current_cooling_state; | |
++ | |
++ return 0; | |
++} | |
++ | |
++static int emc2301_fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) | |
++{ | |
++ struct emc2301_data *devdata = cdev->devdata; | |
++ struct device *dev = devdata->dev; | |
++ | |
++ u16 rpm = 0; | |
++ int i = 0; | |
++ int retval = 0; | |
++ | |
++ dev_dbg(dev, "emc2301_fan_set_cur_state %ld\n", state); | |
++ | |
++ if (devdata->i2c == NULL) { | |
++ dev_err(dev, "ERROR: no i2c instance\n"); | |
++ return -EINVAL; | |
++ } | |
++ | |
++ if (state < 0 || state > EMC230X_MAX_COOLING_STATE) | |
++ return -EINVAL; | |
++ | |
++ for (i = 0; i < ARRAY_SIZE(devdata->cooling_step); i++) { | |
++ if (devdata->cooling_step[i] > 0) { | |
++ rpm = devdata->min_rpm[i] + ((state + 1) * devdata->cooling_step[i]); | |
++ devdata->current_cooling_state = state; | |
++ if (emc2301_set_fan_rpm(devdata, i, rpm)) { | |
++ retval = 1; | |
++ } | |
++ } | |
++ } | |
++ | |
++ return retval; | |
++} | |
++ | |
++struct thermal_cooling_device_ops emc2301_thermal_cooling_device = { | |
++ .get_max_state = emc2301_fan_get_max_state, | |
++ .get_cur_state = emc2301_fan_get_cur_state, | |
++ .set_cur_state = emc2301_fan_set_cur_state, | |
++}; | |
++ | |
++static int emc2301_read_fan_fault(struct device *dev, struct i2c_client *i2c, int channel, long *val) | |
++{ | |
++ u8 status_reg; | |
++ | |
++ if (channel > 1) { | |
++ return -EOPNOTSUPP; | |
++ } | |
++ status_reg = i2c_smbus_read_byte_data(i2c, 0x24); | |
++ dev_dbg(dev, "Channel %d status register %02X\n", channel, status_reg); | |
++ | |
++ if (status_reg & 0x7) { | |
++ *val = 1; | |
++ } else { | |
++ *val = 0; | |
++ } | |
++ | |
++ return 0; | |
++} | |
++ | |
++static int emc2301_read_fan_target(struct emc2301_data *devdata, int channel, long *val) | |
++{ | |
++ if (channel > devdata->num_fans) { | |
++ return -EINVAL; | |
++ } | |
++ *val = devdata->setpoint[channel]; | |
++ return 0; | |
++} | |
++ | |
++static int emc2301_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) | |
++{ | |
++ struct emc2301_data *data = dev_get_drvdata(dev); | |
++ struct i2c_client *i2c = data->i2c; | |
++ | |
++ if (type != hwmon_fan) { | |
++ return -EOPNOTSUPP; | |
++ } | |
++ if (channel > data->num_fans) { | |
++ return -ENOTSUPP; | |
++ } | |
++ switch (attr) { | |
++ case hwmon_fan_input: | |
++ return emc2301_read_fan_rpm(dev, channel, val); | |
++ case hwmon_fan_target: | |
++ return emc2301_read_fan_target(data, channel, val); | |
++ case hwmon_fan_fault: | |
++ return emc2301_read_fan_fault(dev, i2c, channel, val); | |
++ default: | |
++ return -ENOTSUPP; | |
++ break; | |
++ } | |
++ | |
++ return 0; | |
++} | |
++ | |
++static int emc2301_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) | |
++{ | |
++ struct emc2301_data *data = dev_get_drvdata(dev); | |
++ | |
++ if (channel > data->num_fans) | |
++ return -EINVAL; | |
++ | |
++ switch (type) { | |
++ case hwmon_fan: | |
++ switch (attr) { | |
++ case hwmon_fan_target: | |
++#if 0 | |
++ if (val < data->minimum_rpm[channel]) { | |
++ dev_err(dev, "RPM %ld is lower than channel minimum %ld\n", val, data->minimum_rpm[channel]); | |
++ return -EINVAL; | |
++ } | |
++#endif | |
++ | |
++ dev_dbg(dev, "emc2301_write hwmon_fan_target %ld\n", val); | |
++ return emc2301_set_fan_rpm(data, channel, val); | |
++ default: | |
++ return -EOPNOTSUPP; | |
++ } | |
++ default: | |
++ return -EOPNOTSUPP; | |
++ break; | |
++ } | |
++ return -EOPNOTSUPP; | |
++} | |
++ | |
++static const struct hwmon_channel_info *emc2301_info[] = { | |
++ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_FAULT | HWMON_F_TARGET), NULL | |
++}; | |
++ | |
++static umode_t emc2301_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr, int channel) | |
++{ | |
++ //const struct emc2301_data *data = drvdata; | |
++ | |
++ switch (type) { | |
++ case hwmon_fan: | |
++ switch (attr) { | |
++ case hwmon_fan_input: | |
++ case hwmon_fan_fault: | |
++ return S_IRUGO; | |
++ case hwmon_fan_target: | |
++ return S_IRUGO | S_IWUSR; | |
++ default: | |
++ break; | |
++ } | |
++ break; | |
++ default: | |
++ break; | |
++ } | |
++ return 0; | |
++} | |
++ | |
++static const struct hwmon_ops emc2301_ops = { | |
++ .is_visible = emc2301_is_visible, | |
++ .read = emc2301_read, | |
++ .write = emc2301_write, | |
++}; | |
++ | |
++static const struct hwmon_chip_info emc2301_chip_info = { | |
++ .ops = &emc2301_ops, | |
++ .info = emc2301_info, | |
++}; | |
++ | |
++static int emc2301_enable_rpm_control(struct emc2301_data *data, u16 fan_dev, bool enable) | |
++{ | |
++ u8 fan_config_reg_addr; | |
++ u8 fan_config_reg_val; | |
++ int ret = 0; | |
++ | |
++ // get current fan config reg value | |
++ fan_config_reg_addr = 0x32 + (fan_dev * 0x10); | |
++ fan_config_reg_val = i2c_smbus_read_byte_data(data->i2c, fan_config_reg_addr); | |
++ | |
++ // update config reg to enable/disable control as requested | |
++ if (enable) { | |
++ // set ENAx to enable drive | |
++ fan_config_reg_val |= (1 << 7); | |
++ // clear RNGx to set minRPM=500 | |
++ fan_config_reg_val &= ~(0b11 << 5); | |
++ } else { | |
++ // clear ENAx | |
++ fan_config_reg_val &= ~(1 << 7); | |
++ } | |
++ | |
++ dev_dbg(data->dev, "Writing 0x%02X to 0x%02X\n", fan_config_reg_val, fan_config_reg_addr); | |
++ | |
++ ret = i2c_smbus_write_byte_data(data->i2c, fan_config_reg_addr, fan_config_reg_val); | |
++ if (ret) { | |
++ dev_err(data->dev, "Unable to write fan configuration register %02X\n", fan_config_reg_addr); | |
++ return ret; | |
++ } | |
++ | |
++ if (!enable) { | |
++ ret = i2c_smbus_write_byte_data(data->i2c, (0x30 + (fan_dev * 0x10)), 0xFF); | |
++ } | |
++ return ret; | |
++}; | |
++ | |
++static int emc2301_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) | |
++{ | |
++ struct device *hwmon_dev; | |
++ struct device_node *of_node = i2c->dev.of_node; | |
++ struct device_node *child_node; | |
++ struct emc2301_data *data; | |
++ int8_t regval; | |
++ u8 i, retval; | |
++ u16 chan_val; | |
++ u16 range; | |
++ bool has_cooling_step = false; | |
++ int numchildren = 0; | |
++ | |
++ 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 emc2301_data), GFP_KERNEL); | |
++ if (unlikely(!data)) | |
++ return -ENODEV; | |
++ | |
++ data->dev = &i2c->dev; | |
++ data->i2c = i2c; | |
++ | |
++ regval = i2c_smbus_read_byte_data(i2c, EMC230X_REG_PRODUCT_ID); | |
++ switch (regval) { | |
++ case 0x34: /* EMC2305 */ | |
++ data->num_fans = 5; | |
++ break; | |
++ case 0x35: /* EMC2303 */ | |
++ data->num_fans = 3; | |
++ break; | |
++ case 0x36: /* EMC2302 */ | |
++ data->num_fans = 2; | |
++ break; | |
++ case 0x37: /* EMC2301 */ | |
++ data->num_fans = 1; | |
++ break; | |
++ default: | |
++ dev_err(&i2c->dev, "Unknown product ID %d\n", regval); | |
++ return -ENODEV; | |
++ } | |
++ dev_info(&i2c->dev, "EMC230%d detected\n", data->num_fans); | |
++ | |
++ memset(data->min_rpm, 0, sizeof(u16) * ARRAY_SIZE(data->min_rpm)); | |
++ memset(data->max_rpm, 0, sizeof(u16) * ARRAY_SIZE(data->max_rpm)); | |
++ | |
++ /* Read minimum and maximum RPM values from device | |
++ * tree, if specified. | |
++ * For example, the Noctua NF-A4x20 has a min | |
++ * RPM of 1200 and a max of 5000-5500. | |
++ * Without this, cooling actions with slower | |
++ * fans may not be effective | |
++ */ | |
++ if (of_node) { | |
++ numchildren = of_get_child_count(of_node); | |
++ dev_info(&i2c->dev, "Have %d fans configured in DT\n", numchildren); | |
++ if (numchildren > data->num_fans) { | |
++ dev_warn(&i2c->dev, | |
++ "%d fans are specified in DT, but only %d fans are supported by this device", | |
++ numchildren, data->num_fans); | |
++ numchildren = data->num_fans; | |
++ } | |
++ child_node = of_node->child; | |
++ | |
++ for (i = 0; i < numchildren; i++) { | |
++ retval = of_property_read_u16(child_node, "min-rpm", &chan_val); | |
++ if (!retval) { | |
++ data->min_rpm[i] = chan_val; | |
++ } | |
++ | |
++ retval = of_property_read_u16(child_node, "max-rpm", &chan_val); | |
++ if (!retval) { | |
++ data->max_rpm[i] = chan_val; | |
++ } | |
++ child_node = of_get_next_child(of_node, child_node); | |
++ } | |
++ } else { | |
++ dev_warn(&i2c->dev, "No device tree node found for this device"); | |
++ } | |
++ | |
++ for (i = 0; i < data->num_fans; i++) { | |
++ if (data->min_rpm[i] != 0 && data->max_rpm[i] != 0) { | |
++ range = data->max_rpm[i] - data->min_rpm[i]; | |
++ data->cooling_step[i] = range / (EMC230X_MAX_COOLING_STATE + 1); | |
++ dev_info(&i2c->dev, "Fan %i Cooling step is %d RPM, minimum %d, max %d RPM\n", i, | |
++ data->cooling_step[i], data->min_rpm[i], data->max_rpm[i]); | |
++ has_cooling_step = true; | |
++ emc2301_enable_rpm_control(data, i, true); | |
++ emc2301_set_fan_rpm(data, i, data->max_rpm[i]); | |
++ } else { | |
++ data->cooling_step[i] = 0; | |
++ } | |
++ } | |
++ | |
++ data->current_cooling_state = EMC230X_MAX_COOLING_STATE; | |
++ | |
++#if 0 | |
++ /* Read the fan minimum tach values */ | |
++ for(i=0; i<data->num_fans; i++) { | |
++ channel_reg = EMC230X_REG_MINTACH + (i * 10); | |
++ regval = i2c_smbus_read_byte_data(i2c, channel_reg); | |
++ min_tach = (FAN_RPM_FACTOR * FAN_TACH_MULTIPLIER) / (regval << 5); | |
++ data->minimum_rpm[i] = (FAN_RPM_FACTOR * FAN_TACH_MULTIPLIER) / min_tach; | |
++ dev_info(&i2c->dev, "Channel %d minimum RPM is %d", i, data->minimum_rpm[i]); | |
++ } | |
++#endif | |
++ hwmon_dev = | |
++ devm_hwmon_device_register_with_info(&i2c->dev, i2c->name, data, &emc2301_chip_info, NULL); | |
++ | |
++ if (IS_REACHABLE(CONFIG_THERMAL) && has_cooling_step && register_cdev == 1) { | |
++ dev_info(&i2c->dev, "registering a cooling device"); | |
++ data->cdev = devm_thermal_of_cooling_device_register(&i2c->dev, of_node, "emc2301_fan", data, | |
++ &emc2301_thermal_cooling_device); | |
++ if (IS_ERR(data->cdev)) { | |
++ dev_err(&i2c->dev, "Failed to register cooling device\n"); | |
++ return PTR_ERR(data->cdev); | |
++ } | |
++ } | |
++ | |
++ return PTR_ERR_OR_ZERO(hwmon_dev); | |
++} | |
++ | |
++static const struct i2c_device_id emc2301_i2c_id[] = { { "emc2305", 0 }, { "emc2304", 0 }, { "emc2303", 0 }, | |
++ { "emc2302", 0 }, { "emc2301", 0 }, {} }; | |
++ | |
++MODULE_DEVICE_TABLE(i2c, emc2301_i2c_id); | |
++ | |
++static struct i2c_driver emc2301_i2c_driver = { | |
++ .driver = { | |
++ .name = "emc2301", | |
++ }, | |
++ .probe = emc2301_i2c_probe, | |
++ .id_table = emc2301_i2c_id, | |
++}; | |
++ | |
++module_i2c_driver(emc2301_i2c_driver); | |
++ | |
++MODULE_DESCRIPTION("EMC2301 Fan controller driver"); | |
++MODULE_AUTHOR("Mathew McBride <[email protected]>"); | |
++MODULE_LICENSE("GPL v2"); | |
+-- | |
+2.25.1 | |
+ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment