mirror of
https://github.com/coolsnowwolf/lede.git
synced 2025-04-16 04:13:31 +00:00
377 lines
8.1 KiB
C
377 lines
8.1 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) 2021 Sartura Ltd.
|
|
*
|
|
* Driver for the SMSC/Microchip EMC2301/2/3/5 fan controller.
|
|
*
|
|
* Author: Robert Marko <robert.marko@sartura.hr>
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/hwmon-sysfs.h>
|
|
#include <linux/hwmon.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/regmap.h>
|
|
|
|
#define MANUFACTURER_ID_REG 0xfe
|
|
#define SMSC_MANUFACTURER_ID 0x5d
|
|
|
|
#define PRODUCT_ID_REG 0xfd
|
|
#define EMC2305_PRODUCT_ID 0x34
|
|
#define EMC2303_PRODUCT_ID 0x35
|
|
#define EMC2302_PRODUCT_ID 0x36
|
|
#define EMC2301_PRODUCT_ID 0x37
|
|
|
|
#define TACH1_HIGH_BYTE 0x3e
|
|
#define TACH1_LOW_BYTE 0x3f
|
|
|
|
#define FAN1_CONFIG 0x32
|
|
#define FAN_TACH_RANGE_MASK GENMASK(6, 5)
|
|
#define FAN_TACH_MULTIPLIER_8 3
|
|
#define FAN_TACH_MULTIPLIER_4 2
|
|
#define FAN_TACH_MULTIPLIER_2 1
|
|
#define FAN_TACH_MULTIPLIER_1 0
|
|
#define FAN_TACH_CONSTANT 3932160
|
|
#define FAN_TACH_READING_MASK GENMASK(15,3)
|
|
|
|
#define TACH1_TARGET_LOW_BYTE 0x3c
|
|
#define TACH1_TARGET_HIGH_BYTE 0x3d
|
|
#define TACH_HIGH_MASK GENMASK(12,5)
|
|
#define TACH_LOW_MASK GENMASK(4,0)
|
|
|
|
#define FANX_OFFSET 0x10
|
|
#define FAN_NUM_MAX 5
|
|
|
|
struct emc2305_fan_data {
|
|
u32 min_rpm;
|
|
u32 max_rpm;
|
|
u32 target_rpm;
|
|
};
|
|
|
|
struct emc2305_data {
|
|
struct regmap *regmap;
|
|
struct i2c_client *client;
|
|
struct emc2305_fan_data fan_data[FAN_NUM_MAX];
|
|
};
|
|
|
|
static struct regmap_config emc2305_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
.max_register = 0xff,
|
|
};
|
|
|
|
static int emc2305_read_fan(struct emc2305_data *data, int channel,
|
|
long *val)
|
|
{
|
|
unsigned int regval, high_byte, low_byte;
|
|
u8 range, multiplier;
|
|
int ret;
|
|
|
|
ret = regmap_read(data->regmap,
|
|
FAN1_CONFIG + channel * FANX_OFFSET,
|
|
®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
range = FIELD_GET(FAN_TACH_RANGE_MASK, regval);
|
|
|
|
switch (range) {
|
|
case FAN_TACH_MULTIPLIER_8:
|
|
multiplier = 8;
|
|
break;
|
|
case FAN_TACH_MULTIPLIER_4:
|
|
multiplier = 4;
|
|
break;
|
|
case FAN_TACH_MULTIPLIER_2:
|
|
multiplier = 2;
|
|
break;
|
|
case FAN_TACH_MULTIPLIER_1:
|
|
multiplier = 1;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = regmap_read(data->regmap,
|
|
TACH1_HIGH_BYTE + channel * FANX_OFFSET,
|
|
&high_byte);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = regmap_read(data->regmap,
|
|
TACH1_LOW_BYTE + channel * FANX_OFFSET,
|
|
&low_byte);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
regval = (u8) high_byte << 8 | (u8) low_byte;
|
|
|
|
*val = (FAN_TACH_CONSTANT * multiplier) / FIELD_GET(FAN_TACH_READING_MASK, regval);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int emc2305_read_fan_target(struct emc2305_data *data, int channel,
|
|
long *val)
|
|
{
|
|
unsigned int regval;
|
|
int ret;
|
|
|
|
ret = regmap_bulk_read(data->regmap,
|
|
TACH1_TARGET_LOW_BYTE + channel * FANX_OFFSET,
|
|
®val,
|
|
2);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
*val = regval;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int emc2305_set_fan_target(struct emc2305_data *data, int channel,
|
|
long val)
|
|
{
|
|
int ret;
|
|
|
|
ret = regmap_write(data->regmap,
|
|
TACH1_TARGET_LOW_BYTE + channel * FANX_OFFSET,
|
|
val & TACH_LOW_MASK);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = regmap_write(data->regmap,
|
|
TACH1_TARGET_HIGH_BYTE + channel * FANX_OFFSET,
|
|
(val & TACH_HIGH_MASK) >> 5);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int emc2305_write(struct device *dev, enum hwmon_sensor_types type,
|
|
u32 attr, int channel, long val)
|
|
{
|
|
struct emc2305_data *data = dev_get_drvdata(dev);
|
|
int err;
|
|
|
|
switch (type) {
|
|
case hwmon_fan:
|
|
switch (attr) {
|
|
case hwmon_fan_target:
|
|
err = emc2305_set_fan_target(data, channel, val);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int emc2305_read(struct device *dev, enum hwmon_sensor_types type,
|
|
u32 attr, int channel, long *val)
|
|
{
|
|
struct emc2305_data *data = dev_get_drvdata(dev);
|
|
int err;
|
|
|
|
switch (type) {
|
|
case hwmon_fan:
|
|
switch (attr) {
|
|
case hwmon_fan_input:
|
|
err = emc2305_read_fan(data, channel, val);
|
|
break;
|
|
case hwmon_fan_target:
|
|
err = emc2305_read_fan_target(data, channel, val);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static const char * const emc2305_fan_label[] = {
|
|
"Fan1",
|
|
"Fan2",
|
|
"Fan3",
|
|
"Fan4",
|
|
"Fan5",
|
|
};
|
|
|
|
static int emc2305_read_string(struct device *dev,
|
|
enum hwmon_sensor_types type,
|
|
u32 attr, int channel, const char **str)
|
|
{
|
|
switch (type) {
|
|
case hwmon_fan:
|
|
*str = emc2305_fan_label[channel];
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static umode_t emc2305_is_visible(const void *data, enum hwmon_sensor_types type,
|
|
u32 attr, int channel)
|
|
{
|
|
switch (type) {
|
|
case hwmon_fan:
|
|
switch (attr) {
|
|
case hwmon_fan_input:
|
|
case hwmon_fan_label:
|
|
return 0444;
|
|
case hwmon_fan_target:
|
|
return 0644;
|
|
default:
|
|
return 0;
|
|
}
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static const struct hwmon_channel_info *emc2301_info[] = {
|
|
HWMON_CHANNEL_INFO(fan,
|
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_LABEL),
|
|
NULL
|
|
};
|
|
|
|
static const struct hwmon_channel_info *emc2302_info[] = {
|
|
HWMON_CHANNEL_INFO(fan,
|
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_LABEL,
|
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_LABEL),
|
|
NULL
|
|
};
|
|
|
|
static const struct hwmon_channel_info *emc2303_info[] = {
|
|
HWMON_CHANNEL_INFO(fan,
|
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_LABEL,
|
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_LABEL,
|
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_LABEL),
|
|
NULL
|
|
};
|
|
|
|
static const struct hwmon_channel_info *emc2305_info[] = {
|
|
HWMON_CHANNEL_INFO(fan,
|
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_LABEL,
|
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_LABEL,
|
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_LABEL,
|
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_LABEL,
|
|
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_LABEL),
|
|
NULL
|
|
};
|
|
|
|
static const struct hwmon_ops emc2305_hwmon_ops = {
|
|
.is_visible = emc2305_is_visible,
|
|
.write = emc2305_write,
|
|
.read = emc2305_read,
|
|
.read_string = emc2305_read_string,
|
|
};
|
|
|
|
static struct hwmon_chip_info emc2305_chip_info = {
|
|
.ops = &emc2305_hwmon_ops,
|
|
};
|
|
|
|
static int emc2305_probe(struct i2c_client *client)
|
|
{
|
|
struct device *dev = &client->dev;
|
|
struct emc2305_data *data;
|
|
struct device *hwmon_dev;
|
|
const char *model_name;
|
|
unsigned int regval;
|
|
int ret;
|
|
|
|
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
data->client = client;
|
|
i2c_set_clientdata(client, data);
|
|
|
|
data->regmap = devm_regmap_init_i2c(client, &emc2305_regmap_config);
|
|
if (IS_ERR(data->regmap)) {
|
|
dev_err(dev, "failed to allocate register map\n");
|
|
return PTR_ERR(data->regmap);
|
|
}
|
|
|
|
ret = regmap_read(data->regmap, MANUFACTURER_ID_REG, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (regval != SMSC_MANUFACTURER_ID) {
|
|
dev_err(dev, "Invalid manufacturer id: 0x%x\n", regval);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = regmap_read(data->regmap, PRODUCT_ID_REG, ®val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
switch (regval) {
|
|
case EMC2305_PRODUCT_ID:
|
|
model_name = "emc2305";
|
|
emc2305_chip_info.info = emc2305_info;
|
|
break;
|
|
case EMC2303_PRODUCT_ID:
|
|
model_name = "emc2303";
|
|
emc2305_chip_info.info = emc2303_info;
|
|
break;
|
|
case EMC2302_PRODUCT_ID:
|
|
model_name = "emc2302";
|
|
emc2305_chip_info.info = emc2302_info;
|
|
break;
|
|
case EMC2301_PRODUCT_ID:
|
|
model_name = "emc2301";
|
|
emc2305_chip_info.info = emc2301_info;
|
|
break;
|
|
default:
|
|
dev_err(dev, "Unknown ID detected: 0x%x\n", regval);
|
|
return -ENODEV;
|
|
}
|
|
|
|
dev_info(dev, "%s detected\n", model_name);
|
|
|
|
hwmon_dev = devm_hwmon_device_register_with_info(dev, model_name,
|
|
data, &emc2305_chip_info,
|
|
NULL);
|
|
if (IS_ERR(hwmon_dev))
|
|
return PTR_ERR(hwmon_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id emc2305_of_match[] = {
|
|
{ .compatible = "smsc,emc2301", },
|
|
{ .compatible = "smsc,emc2302", },
|
|
{ .compatible = "smsc,emc2303", },
|
|
{ .compatible = "smsc,emc2305", },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, emc2305_of_match);
|
|
|
|
static struct i2c_driver emc2305_driver = {
|
|
.probe_new = emc2305_probe,
|
|
.driver = {
|
|
.name = "emc2305",
|
|
.of_match_table = of_match_ptr(emc2305_of_match),
|
|
},
|
|
};
|
|
module_i2c_driver(emc2305_driver);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Robert Marko <robert.marko@sartura.hr>");
|
|
MODULE_DESCRIPTION("SMSC EMC2301/2/3/5 fan controller");
|