sonic-buildimage/platform/broadcom/sonic-platform-modules-cel/haliburton/modules/emc2305.c
Ramasamy Chandramouli c436ce20be [PR:16737, PR:16739] platform-modules: pddf, broadcom/cel: adapt for kernel 6.1 and bookworm (#16954)
* sonic-platform-modules-cel: broadcom: adapt for kernel 6.1 and bookworm

The i2c_driver->remove API declaration has been updated to return void instead
of int, as part of cleanup patches in 6.1. More details can be referred from
here: [1]. Update the remove API definition in the modules accordingly and
cleanup variables that go unused from the remove API.

Update python build commands for bookworm. The packaging based on calling
setup.py is deprecated and using build module/pip utility is the recommended
method for python packaging/installation. Further details can be referred to
from here: [2], [3]. The build module is picky about the package information file,
which needs to be either setup.py or pyproject.toml.

Additionally, fix formatting inconsistencies in debian/changelog reported by
`dh_installchangelogs` during the build.

Tested the changes by compiling the changes as below:

    make sonic-slave-bash NOBUSTER=1 NOBULLSEYE=1
    sudo dpkg -i target/debs/bookworm/linux-headers-6.1.0-11-2-*.deb
    cd platform/broadcom/sonic-platform-modules-cel
    KVERSION=6.1.0-11-2-amd64 dpkg-buildpackage

Also verified the python scripts under the sonic-platform-modules-cel with
pyflakes to ensure no new errors are flagged (with exception of unused modules).

References:
   [1] - https://github.com/torvalds/linux/commit/ed5c2f5f
   [2] - https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.htm
   [3] - 0b20a4863 (Update Python build commands for Bookworm, 2023-09-07)

Signed-off-by: Ramasamy Chandramouli <rachandr@celestica.com>

* platform/pddf: i2c: adapt for kernel 6.1 and bookworm

   * Fixup i2c_driver->remove API due to changes in the function
     prototype (ref: [1]).

   * Cleanup `MODULE_SUPPORTED_DEVICE` macros that were cleaned up in
     the upstream (ref: [2]).

   * Sanitize python packaging and installation using the `build` module
   instead of calling the setup.py directly (ref: [3]. [4]).

Tested the changes by compiling pddf module as below:

     make sonic-slave-bash NOBUSTER=1 NOBULLSEYE=1
     sudo dpkg -i target/debs/bookworm/linux-headers-6.1.0-11-2-*.deb
     cd platform/pddf/i2c
     KVERSION=6.1.0-11-2-amd64 dpkg-buildpackage

References:
    [1] - https://github.com/torvalds/linux/commit/ed5c2f5f
    [2] - https://github.com/torvalds/linux/commit/6417f031
    [2] - https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.htm
    [3] - 0b20a4863 (Update Python build commands for Bookworm, 2023-09-07)

Signed-off-by: Ramasamy Chandramouli <rachandr@celestica.com>

* platform/broadcom: include platform-modules-cel in builds

With pddf modules patched for 6.1, platform-modules-cel can be compiled
and included in the final image.

Testing by building sonic-broadcom.bin/sonic-broadcom-dnx.bin.

Signed-off-by: Ramasamy Chandramouli <rachandr@celestica.com>

* pddf/i2c: revert correct rootdir for pip install

The pip install directory has been set to test-pkg1/ for testing the build and
incorrectly retained as is. Revert this to the correct path $(PACKAGE_PRE_NAME).

Signed-off-by: Ramasamy Chandramouli <rachandr@celestica.com>

* platform/broadcom: include pddf/modules-cel in the base package

Without this change, the modules were built but not packaged in the final .bin.

The final sonic-broadcom.bin has been tested for bootup on Celestica's
Silverstone platform.

   admin@sonic:~$ uname -a
   Linux sonic 6.1.0-11-2-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.38-4 (2023-08-08) x86_64 GNU/Linux
   admin@sonic:~$ show platform summary
   Platform: x86_64-cel_silverstone-r0
   HwSKU: Silverstone
   ASIC: broadcom
   ASIC Count: 1
   Serial Number: R4009B2F062504LK200024
   Model Number: N/A
   Hardware Revision: N/A
   admin@sonic:~$ show version | head

   SONiC Software Version: SONiC.g0aad6c67c-rachandr
   SONiC OS Version: 12
   Distribution: Debian 12.2
   Kernel: 6.1.0-11-2-amd64
   Build commit: 0aad6c67c
   Build date: Thu Oct 26 07:13:47 UTC 2023
   Built by: rachandr@AZUHPS14

   Platform: x86_64-cel_silverstone-r0

Signed-off-by: Ramasamy Chandramouli <rachandr@celestica.com>

---------

Signed-off-by: Ramasamy Chandramouli <rachandr@celestica.com>
2023-11-21 18:53:15 -08:00

909 lines
22 KiB
C

/*
* emc2305.c - hwmon driver for SMSC EMC2305 fan controller
* (C) Copyright 2013
* Reinhard Pfau, Guntermann & Drunck GmbH <pfau@gdsys.de>
*
* Based on emc2103 driver by SMSC.
*
* Datasheet available at:
* http://www.smsc.com/Downloads/SMSC/Downloads_Public/Data_Sheets/2305.pdf
*
* Also supports the EMC2303 fan controller which has the same functionality
* and register layout as EMC2305, but supports only up to 3 fans instead of 5.
*
* Also supports EMC2302 (up to 2 fans) and EMC2301 (1 fan) fan controller.
*
* 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.
*/
/*
* TODO / IDEAS:
* - expose more of the configuration and features
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/of.h>
/*
* Addresses scanned.
* Listed in the same order as they appear in the EMC2305, EMC2303 data sheets.
*
* Note: these are the I2C adresses which are possible for EMC2305 and EMC2303
* chips.
* The EMC2302 supports only 0x2e (EMC2302-1) and 0x2f (EMC2302-2).
* The EMC2301 supports only 0x2f.
*/
static const unsigned short i2c_adresses[] = {
0x2E,
0x2F,
0x2C,
0x2D,
0x4C,
0x4D,
I2C_CLIENT_END
};
/*
* global registers
*/
enum {
REG_CONFIGURATION = 0x20,
REG_FAN_STATUS = 0x24,
REG_FAN_STALL_STATUS = 0x25,
REG_FAN_SPIN_STATUS = 0x26,
REG_DRIVE_FAIL_STATUS = 0x27,
REG_FAN_INTERRUPT_ENABLE = 0x29,
REG_PWM_POLARITY_CONFIG = 0x2a,
REG_PWM_OUTPUT_CONFIG = 0x2b,
REG_PWM_BASE_FREQ_1 = 0x2c,
REG_PWM_BASE_FREQ_2 = 0x2d,
REG_SOFTWARE_LOCK = 0xef,
REG_PRODUCT_FEATURES = 0xfc,
REG_PRODUCT_ID = 0xfd,
REG_MANUFACTURER_ID = 0xfe,
REG_REVISION = 0xff
};
/*
* fan specific registers
*/
enum {
REG_FAN_SETTING = 0x30,
REG_PWM_DIVIDE = 0x31,
REG_FAN_CONFIGURATION_1 = 0x32,
REG_FAN_CONFIGURATION_2 = 0x33,
REG_GAIN = 0x35,
REG_FAN_SPIN_UP_CONFIG = 0x36,
REG_FAN_MAX_STEP = 0x37,
REG_FAN_MINIMUM_DRIVE = 0x38,
REG_FAN_VALID_TACH_COUNT = 0x39,
REG_FAN_DRIVE_FAIL_BAND_LOW = 0x3a,
REG_FAN_DRIVE_FAIL_BAND_HIGH = 0x3b,
REG_TACH_TARGET_LOW = 0x3c,
REG_TACH_TARGET_HIGH = 0x3d,
REG_TACH_READ_HIGH = 0x3e,
REG_TACH_READ_LOW = 0x3f,
};
#define SEL_FAN(fan, reg) (reg + fan * 0x10)
/*
* 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
struct emc2305_fan_data {
bool enabled;
bool valid;
unsigned long last_updated;
bool rpm_control;
u8 multiplier;
u8 poles;
u16 target;
u16 tach;
u16 rpm_factor;
u8 pwm;
};
struct emc2305_data {
struct device *hwmon_dev;
struct mutex update_lock;
int fans;
struct emc2305_fan_data fan[5];
};
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_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, int fan,
u16 new_target)
{
const u8 lo_reg = SEL_FAN(fan, REG_TACH_TARGET_LOW);
const u8 hi_reg = SEL_FAN(fan, REG_TACH_TARGET_HIGH);
u8 high_byte = (new_target & 0x1fe0) >> 5;
u8 low_byte = (new_target & 0x001f) << 3;
i2c_smbus_write_byte_data(client, lo_reg, low_byte);
i2c_smbus_write_byte_data(client, hi_reg, high_byte);
}
static void read_fan_config_from_i2c(struct i2c_client *client, int fan)
{
struct emc2305_data *data = i2c_get_clientdata(client);
u8 conf1;
if (read_u8_from_i2c(client, SEL_FAN(fan, REG_FAN_CONFIGURATION_1),
&conf1) < 0)
return;
data->fan[fan].rpm_control = (conf1 & 0x80) != 0;
data->fan[fan].multiplier = 1 << ((conf1 & 0x60) >> 5);
data->fan[fan].poles = ((conf1 & 0x18) >> 3) + 1;
}
static void read_fan_setting(struct i2c_client *client, int fan)
{
struct emc2305_data *data = i2c_get_clientdata(client);
u8 setting;
if (read_u8_from_i2c(client, SEL_FAN(fan, REG_FAN_SETTING),
&setting) < 0)
return;
data->fan[fan].pwm = setting;
}
static void read_fan_data(struct i2c_client *client, int fan_idx)
{
struct emc2305_data *data = i2c_get_clientdata(client);
read_fan_from_i2c(client, &data->fan[fan_idx].target,
SEL_FAN(fan_idx, REG_TACH_TARGET_HIGH),
SEL_FAN(fan_idx, REG_TACH_TARGET_LOW));
read_fan_from_i2c(client, &data->fan[fan_idx].tach,
SEL_FAN(fan_idx, REG_TACH_READ_HIGH),
SEL_FAN(fan_idx, REG_TACH_READ_LOW));
}
static struct emc2305_fan_data *
emc2305_update_fan(struct i2c_client *client, int fan_idx)
{
struct emc2305_data *data = i2c_get_clientdata(client);
struct emc2305_fan_data *fan_data = &data->fan[fan_idx];
mutex_lock(&data->update_lock);
if (time_after(jiffies, fan_data->last_updated + HZ + HZ / 2)
|| !fan_data->valid) {
read_fan_config_from_i2c(client, fan_idx);
read_fan_data(client, fan_idx);
read_fan_setting(client, fan_idx);
fan_data->valid = true;
fan_data->last_updated = jiffies;
}
mutex_unlock(&data->update_lock);
return fan_data;
}
static struct emc2305_fan_data *
emc2305_update_device_fan(struct device *dev, struct device_attribute *da)
{
struct i2c_client *client = to_i2c_client(dev);
int fan_idx = to_sensor_dev_attr(da)->index;
return emc2305_update_fan(client, fan_idx);
}
/*
* set/ config functions
*/
/*
* 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 int
emc2305_set_fan_div(struct i2c_client *client, int fan_idx, long new_div)
{
struct emc2305_data *data = i2c_get_clientdata(client);
struct emc2305_fan_data *fan = emc2305_update_fan(client, fan_idx);
const u8 reg_conf1 = SEL_FAN(fan_idx, REG_FAN_CONFIGURATION_1);
int new_range_bits, old_div = 8 / fan->multiplier;
int status = 0;
if (new_div == old_div) /* No change */
return 0;
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_conf1);
if (status < 0) {
dev_dbg(&client->dev, "reg 0x%02x, err %d\n",
reg_conf1, status);
status = -EIO;
goto exit_unlock;
}
status &= 0x9F;
status |= (new_range_bits << 5);
status = i2c_smbus_write_byte_data(client, reg_conf1, status);
if (status < 0) {
status = -EIO;
goto exit_invalidate;
}
fan->multiplier = 8 / new_div;
/* update fan target if high byte is not disabled */
if ((fan->target & 0x1fe0) != 0x1fe0) {
u16 new_target = (fan->target * old_div) / new_div;
fan->target = min_t(u16, new_target, 0x1fff);
write_fan_target_to_i2c(client, fan_idx, fan->target);
}
exit_invalidate:
/* invalidate fan data to force re-read from hardware */
fan->valid = false;
exit_unlock:
mutex_unlock(&data->update_lock);
return status;
}
static int
emc2305_set_fan_target(struct i2c_client *client, int fan_idx, long rpm_target)
{
struct emc2305_data *data = i2c_get_clientdata(client);
struct emc2305_fan_data *fan = emc2305_update_fan(client, fan_idx);
/*
* Datasheet states 16000 as maximum RPM target
* (table 2.2 and section 4.3)
*/
if ((rpm_target < 0) || (rpm_target > 16000))
return -EINVAL;
mutex_lock(&data->update_lock);
if (rpm_target == 0)
fan->target = 0x1fff;
else
fan->target = clamp_val(
(FAN_RPM_FACTOR * fan->multiplier) / rpm_target,
0, 0x1fff);
write_fan_target_to_i2c(client, fan_idx, fan->target);
mutex_unlock(&data->update_lock);
return 0;
}
static int
emc2305_set_pwm_enable(struct i2c_client *client, int fan_idx, long enable)
{
struct emc2305_data *data = i2c_get_clientdata(client);
struct emc2305_fan_data *fan = emc2305_update_fan(client, fan_idx);
const u8 reg_fan_conf1 = SEL_FAN(fan_idx, REG_FAN_CONFIGURATION_1);
int status = 0;
u8 conf_reg;
mutex_lock(&data->update_lock);
switch (enable) {
case 0:
fan->rpm_control = false;
break;
case 3:
fan->rpm_control = true;
break;
default:
status = -EINVAL;
goto exit_unlock;
}
status = read_u8_from_i2c(client, reg_fan_conf1, &conf_reg);
if (status < 0) {
status = -EIO;
goto exit_unlock;
}
if (fan->rpm_control)
conf_reg |= 0x80;
else
conf_reg &= ~0x80;
status = i2c_smbus_write_byte_data(client, reg_fan_conf1, conf_reg);
if (status < 0)
status = -EIO;
exit_unlock:
mutex_unlock(&data->update_lock);
return status;
}
static int
emc2305_set_pwm(struct i2c_client *client, int fan_idx, long pwm)
{
struct emc2305_data *data = i2c_get_clientdata(client);
struct emc2305_fan_data *fan = emc2305_update_fan(client, fan_idx);
const u8 reg_fan_setting = SEL_FAN(fan_idx, REG_FAN_SETTING);
int status = 0;
/*
* Datasheet states 255 as maximum PWM
* (section 5.7)
*/
if ((pwm < 0) || (pwm > 255))
return -EINVAL;
fan->pwm = pwm;
mutex_lock(&data->update_lock);
status = i2c_smbus_write_byte_data(client, reg_fan_setting, fan->pwm);
mutex_unlock(&data->update_lock);
return status;
}
static int
emc2305_enable_timeout(struct i2c_client *client, bool enable)
{
struct emc2305_data *data = i2c_get_clientdata(client);
int status = 0;
u8 conf_val = 0;
mutex_lock(&data->update_lock);
status = read_u8_from_i2c(client, REG_CONFIGURATION, &conf_val);
if (status < 0) {
mutex_unlock(&data->update_lock);
return status;
}
// Section 6.2: CONFIG REGISTER DIS_TO bit(bit 6)
if (enable) {
conf_val &= ~(1 << 6);
} else {
conf_val |= (1 << 6);
}
status = i2c_smbus_write_byte_data(client, REG_CONFIGURATION, conf_val);
mutex_unlock(&data->update_lock);
return status;
}
/*
* sysfs callback functions
*
* Note:
* Naming of the funcs is modelled after the naming scheme described in
* Documentation/hwmon/sysfs-interface:
*
* For a sysfs file <type><number>_<item> the functions are named like this:
* the show function: show_<type>_<item>
* the store function: set_<type>_<item>
* For read only (RO) attributes of course only the show func is required.
*
* This convention allows us to define the sysfs attributes by using macros.
*/
static ssize_t
show_fan_input(struct device *dev, struct device_attribute *da, char *buf)
{
struct emc2305_fan_data *fan = emc2305_update_device_fan(dev, da);
int rpm = 0;
if (fan->tach != 0)
rpm = (FAN_RPM_FACTOR * fan->multiplier) / fan->tach;
return sprintf(buf, "%d\n", rpm);
}
static ssize_t
show_fan_fault(struct device *dev, struct device_attribute *da, char *buf)
{
struct emc2305_fan_data *fan = emc2305_update_device_fan(dev, da);
bool fault = ((fan->tach & 0x1fe0) == 0x1fe0);
return sprintf(buf, "%d\n", fault ? 1 : 0);
}
static ssize_t
show_fan_div(struct device *dev, struct device_attribute *da, char *buf)
{
struct emc2305_fan_data *fan = emc2305_update_device_fan(dev, da);
int fan_div = 8 / fan->multiplier;
return sprintf(buf, "%d\n", fan_div);
}
static ssize_t
set_fan_div(struct device *dev, struct device_attribute *da,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
int fan_idx = to_sensor_dev_attr(da)->index;
long new_div;
int status;
status = kstrtol(buf, 10, &new_div);
if (status < 0)
return -EINVAL;
status = emc2305_set_fan_div(client, fan_idx, new_div);
if (status < 0)
return status;
return count;
}
static ssize_t
show_fan_target(struct device *dev, struct device_attribute *da, char *buf)
{
struct emc2305_fan_data *fan = emc2305_update_device_fan(dev, da);
int rpm = 0;
/* high byte of 0xff indicates disabled so return 0 */
if ((fan->target != 0) && ((fan->target & 0x1fe0) != 0x1fe0))
rpm = (FAN_RPM_FACTOR * fan->multiplier)
/ fan->target;
return sprintf(buf, "%d\n", rpm);
}
static ssize_t set_fan_target(struct device *dev, struct device_attribute *da,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
int fan_idx = to_sensor_dev_attr(da)->index;
long rpm_target;
int status;
status = kstrtol(buf, 10, &rpm_target);
if (status < 0)
return -EINVAL;
status = emc2305_set_fan_target(client, fan_idx, rpm_target);
if (status < 0)
return status;
return count;
}
static ssize_t
show_pwm_enable(struct device *dev, struct device_attribute *da, char *buf)
{
struct emc2305_fan_data *fan = emc2305_update_device_fan(dev, da);
return sprintf(buf, "%d\n", fan->rpm_control ? 3 : 0);
}
static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *da,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
int fan_idx = to_sensor_dev_attr(da)->index;
long new_value;
int status;
status = kstrtol(buf, 10, &new_value);
if (status < 0)
return -EINVAL;
status = emc2305_set_pwm_enable(client, fan_idx, new_value);
return count;
}
static ssize_t show_pwm(struct device *dev, struct device_attribute *da,
char *buf)
{
struct emc2305_fan_data *fan = emc2305_update_device_fan(dev, da);
return sprintf(buf, "%d\n", fan->pwm);
}
static ssize_t set_pwm(struct device *dev, struct device_attribute *da,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
int fan_idx = to_sensor_dev_attr(da)->index;
unsigned long val;
int ret;
int status;
ret = kstrtoul(buf, 10, &val);
if (ret)
return ret;
if (val > 255)
return -EINVAL;
status = emc2305_set_pwm(client, fan_idx, val);
return count;
}
/* define a read only attribute */
#define EMC2305_ATTR_RO(_type, _item, _num) \
SENSOR_ATTR(_type ## _num ## _ ## _item, S_IRUGO, \
show_## _type ## _ ## _item, NULL, _num - 1)
/* define a read/write attribute */
#define EMC2305_ATTR_RW(_type, _item, _num) \
SENSOR_ATTR(_type ## _num ## _ ## _item, S_IRUGO | S_IWUSR, \
show_## _type ##_ ## _item, \
set_## _type ## _ ## _item, _num - 1)
/*
* TODO: Ugly hack, but temporary as this whole logic needs
* to be rewritten as per standard HWMON sysfs registration
*/
/* define a read/write attribute */
#define EMC2305_ATTR_RW2(_type, _num) \
SENSOR_ATTR(_type ## _num, S_IRUGO | S_IWUSR, \
show_## _type, set_## _type, _num - 1)
/* defines the attributes for a single fan */
#define EMC2305_DEFINE_FAN_ATTRS(_num) \
static const \
struct sensor_device_attribute emc2305_attr_fan ## _num[] = { \
EMC2305_ATTR_RO(fan, input, _num), \
EMC2305_ATTR_RO(fan, fault, _num), \
EMC2305_ATTR_RW(fan, div, _num), \
EMC2305_ATTR_RW(fan, target, _num), \
EMC2305_ATTR_RW(pwm, enable, _num), \
EMC2305_ATTR_RW2(pwm, _num) \
}
#define EMC2305_NUM_FAN_ATTRS ARRAY_SIZE(emc2305_attr_fan1)
/* common attributes for EMC2303 and EMC2305 */
static const struct sensor_device_attribute emc2305_attr_common[] = {
};
/* fan attributes for the single fans */
EMC2305_DEFINE_FAN_ATTRS(1);
EMC2305_DEFINE_FAN_ATTRS(2);
EMC2305_DEFINE_FAN_ATTRS(3);
EMC2305_DEFINE_FAN_ATTRS(4);
EMC2305_DEFINE_FAN_ATTRS(5);
EMC2305_DEFINE_FAN_ATTRS(6);
/* fan attributes */
static const struct sensor_device_attribute *emc2305_fan_attrs[] = {
emc2305_attr_fan1,
emc2305_attr_fan2,
emc2305_attr_fan3,
emc2305_attr_fan4,
emc2305_attr_fan5,
};
/*
* driver interface
*/
static void emc2305_remove(struct i2c_client *client)
{
struct emc2305_data *data = i2c_get_clientdata(client);
int fan_idx, i;
hwmon_device_unregister(data->hwmon_dev);
for (fan_idx = 0; fan_idx < data->fans; ++fan_idx)
for (i = 0; i < EMC2305_NUM_FAN_ATTRS; ++i)
device_remove_file(
&client->dev,
&emc2305_fan_attrs[fan_idx][i].dev_attr);
for (i = 0; i < ARRAY_SIZE(emc2305_attr_common); ++i)
device_remove_file(&client->dev,
&emc2305_attr_common[i].dev_attr);
kfree(data);
}
#ifdef CONFIG_OF
/*
* device tree support
*/
struct of_fan_attribute {
const char *name;
int (*set)(struct i2c_client*, int, long);
};
struct of_fan_attribute of_fan_attributes[] = {
{"fan-div", emc2305_set_fan_div},
{"fan-target", emc2305_set_fan_target},
{"pwm-enable", emc2305_set_pwm_enable},
{NULL, NULL}
};
static int emc2305_config_of(struct i2c_client *client)
{
struct emc2305_data *data = i2c_get_clientdata(client);
struct device_node *node;
unsigned int fan_idx;
if (!client->dev.of_node)
return -EINVAL;
if (!of_get_next_child(client->dev.of_node, NULL))
return 0;
for (fan_idx = 0; fan_idx < data->fans; ++fan_idx)
data->fan[fan_idx].enabled = false;
for_each_child_of_node(client->dev.of_node, node) {
const __be32 *property;
int len;
struct of_fan_attribute *attr;
property = of_get_property(node, "reg", &len);
if (!property || len != sizeof(int)) {
dev_err(&client->dev, "invalid reg on %s\n",
node->full_name);
continue;
}
fan_idx = be32_to_cpup(property);
if (fan_idx >= data->fans) {
dev_err(&client->dev,
"invalid fan index %d on %s\n",
fan_idx, node->full_name);
continue;
}
data->fan[fan_idx].enabled = true;
for (attr = of_fan_attributes; attr->name; ++attr) {
int status = 0;
long value;
property = of_get_property(node, attr->name, &len);
if (!property)
continue;
if (len != sizeof(int)) {
dev_err(&client->dev, "invalid %s on %s\n",
attr->name, node->full_name);
continue;
}
value = be32_to_cpup(property);
status = attr->set(client, fan_idx, value);
if (status == -EINVAL) {
dev_err(&client->dev,
"invalid value for %s on %s\n",
attr->name, node->full_name);
}
}
}
return 0;
}
#endif
static void emc2305_get_config(struct i2c_client *client)
{
int i;
struct emc2305_data *data = i2c_get_clientdata(client);
for (i = 0; i < data->fans; ++i) {
data->fan[i].enabled = true;
emc2305_update_fan(client, i);
}
#ifdef CONFIG_OF
emc2305_config_of(client);
#endif
}
static int
emc2305_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct emc2305_data *data;
int status;
int i;
int fan_idx;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;
data = kzalloc(sizeof(struct emc2305_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
i2c_set_clientdata(client, data);
mutex_init(&data->update_lock);
// Enable SMBus timeout feature
emc2305_enable_timeout(client, true);
status = i2c_smbus_read_byte_data(client, REG_PRODUCT_ID);
switch (status) {
case 0x34: /* EMC2305 */
data->fans = 5;
break;
case 0x35: /* EMC2303 */
data->fans = 3;
break;
case 0x36: /* EMC2302 */
data->fans = 2;
break;
case 0x37: /* EMC2301 */
data->fans = 1;
break;
default:
if (status >= 0)
status = -EINVAL;
goto exit_free;
}
emc2305_get_config(client);
for (i = 0; i < ARRAY_SIZE(emc2305_attr_common); ++i) {
status = device_create_file(&client->dev,
&emc2305_attr_common[i].dev_attr);
if (status)
goto exit_remove;
}
for (fan_idx = 0; fan_idx < data->fans; ++fan_idx)
for (i = 0; i < EMC2305_NUM_FAN_ATTRS; ++i) {
if (!data->fan[fan_idx].enabled)
continue;
status = device_create_file(
&client->dev,
&emc2305_fan_attrs[fan_idx][i].dev_attr);
if (status)
goto exit_remove_fans;
}
data->hwmon_dev = hwmon_device_register(&client->dev);
if (IS_ERR(data->hwmon_dev)) {
status = PTR_ERR(data->hwmon_dev);
goto exit_remove_fans;
}
dev_info(&client->dev, "%s: sensor '%s'\n",
dev_name(data->hwmon_dev), client->name);
return 0;
exit_remove_fans:
for (fan_idx = 0; fan_idx < data->fans; ++fan_idx)
for (i = 0; i < EMC2305_NUM_FAN_ATTRS; ++i)
device_remove_file(
&client->dev,
&emc2305_fan_attrs[fan_idx][i].dev_attr);
exit_remove:
for (i = 0; i < ARRAY_SIZE(emc2305_attr_common); ++i)
device_remove_file(&client->dev,
&emc2305_attr_common[i].dev_attr);
exit_free:
kfree(data);
return status;
}
static const struct i2c_device_id emc2305_id[] = {
{ "emc2305", 0 },
{ "emc2303", 0 },
{ "emc2302", 0 },
{ "emc2301", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, emc2305_id);
/* Return 0 if detection is successful, -ENODEV otherwise */
static int
emc2305_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_MANUFACTURER_ID);
if (manufacturer != 0x5D)
return -ENODEV;
product = i2c_smbus_read_byte_data(new_client, REG_PRODUCT_ID);
switch (product) {
case 0x34:
strlcpy(info->type, "emc2305", I2C_NAME_SIZE);
break;
case 0x35:
strlcpy(info->type, "emc2303", I2C_NAME_SIZE);
break;
case 0x36:
strlcpy(info->type, "emc2302", I2C_NAME_SIZE);
break;
case 0x37:
strlcpy(info->type, "emc2301", I2C_NAME_SIZE);
break;
default:
return -ENODEV;
}
return 0;
}
static struct i2c_driver emc2305_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "emc2305",
},
.probe = emc2305_probe,
.remove = emc2305_remove,
.id_table = emc2305_id,
/*
.detect = emc2305_detect,
.address_list = i2c_adresses,
*/
};
module_i2c_driver(emc2305_driver);
MODULE_AUTHOR("Reinhard Pfau <pfau@gdsys.de>");
MODULE_DESCRIPTION("SMSC EMC2305 hwmon driver");
MODULE_LICENSE("GPL");