|
@@ -21,6 +21,7 @@
|
|
|
#include <linux/i2c.h>
|
|
|
#include <linux/hwmon.h>
|
|
|
#include <linux/hwmon-sysfs.h>
|
|
|
+#include <linux/i2c/ltc4245.h>
|
|
|
|
|
|
/* Here are names of the chip's registers (a.k.a. commands) */
|
|
|
enum ltc4245_cmd {
|
|
@@ -60,8 +61,72 @@ struct ltc4245_data {
|
|
|
|
|
|
/* 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 i2c_client *client = to_i2c_client(dev);
|
|
|
+ struct ltc4245_data *data = i2c_get_clientdata(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)) {
|
|
|
+ dev_dbg(&client->dev, "Marking GPIOs invalid\n");
|
|
|
+ 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 i2c_client *client = to_i2c_client(dev);
|
|
@@ -93,6 +158,9 @@ static struct ltc4245_data *ltc4245_update_device(struct device *dev)
|
|
|
data->vregs[i] = val;
|
|
|
}
|
|
|
|
|
|
+ /* Update GPIO readings */
|
|
|
+ ltc4245_update_gpios(dev);
|
|
|
+
|
|
|
data->last_updated = jiffies;
|
|
|
data->valid = 1;
|
|
|
}
|
|
@@ -233,6 +301,22 @@ static ssize_t ltc4245_show_alarm(struct device *dev,
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", (reg & mask) ? 1 : 0);
|
|
|
}
|
|
|
|
|
|
+static ssize_t ltc4245_show_gpio(struct device *dev,
|
|
|
+ struct device_attribute *da,
|
|
|
+ char *buf)
|
|
|
+{
|
|
|
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
|
|
+ struct ltc4245_data *data = ltc4245_update_device(dev);
|
|
|
+ int val = data->gpios[attr->index];
|
|
|
+
|
|
|
+ /* handle stale GPIO's */
|
|
|
+ if (val < 0)
|
|
|
+ return val;
|
|
|
+
|
|
|
+ /* Convert to millivolts and print */
|
|
|
+ return snprintf(buf, PAGE_SIZE, "%u\n", val * 10);
|
|
|
+}
|
|
|
+
|
|
|
/* 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.
|
|
@@ -254,6 +338,10 @@ static ssize_t ltc4245_show_alarm(struct device *dev,
|
|
|
static SENSOR_DEVICE_ATTR_2(name, S_IRUGO, \
|
|
|
ltc4245_show_alarm, NULL, (mask), reg)
|
|
|
|
|
|
+#define LTC4245_GPIO_VOLTAGE(name, gpio_num) \
|
|
|
+ static SENSOR_DEVICE_ATTR(name, S_IRUGO, \
|
|
|
+ ltc4245_show_gpio, NULL, gpio_num)
|
|
|
+
|
|
|
/* Construct a sensor_device_attribute structure for each register */
|
|
|
|
|
|
/* Input voltages */
|
|
@@ -293,7 +381,9 @@ LTC4245_ALARM(in7_min_alarm, (1 << 2), LTC4245_FAULT2);
|
|
|
LTC4245_ALARM(in8_min_alarm, (1 << 3), LTC4245_FAULT2);
|
|
|
|
|
|
/* GPIO voltages */
|
|
|
-LTC4245_VOLTAGE(in9_input, LTC4245_GPIOADC);
|
|
|
+LTC4245_GPIO_VOLTAGE(in9_input, 0);
|
|
|
+LTC4245_GPIO_VOLTAGE(in10_input, 1);
|
|
|
+LTC4245_GPIO_VOLTAGE(in11_input, 2);
|
|
|
|
|
|
/* Power Consumption (virtual) */
|
|
|
LTC4245_POWER(power1_input, LTC4245_12VSENSE);
|
|
@@ -304,7 +394,7 @@ LTC4245_POWER(power4_input, LTC4245_VEESENSE);
|
|
|
/* Finally, construct an array of pointers to members of the above objects,
|
|
|
* as required for sysfs_create_group()
|
|
|
*/
|
|
|
-static struct attribute *ltc4245_attributes[] = {
|
|
|
+static struct attribute *ltc4245_std_attributes[] = {
|
|
|
&sensor_dev_attr_in1_input.dev_attr.attr,
|
|
|
&sensor_dev_attr_in2_input.dev_attr.attr,
|
|
|
&sensor_dev_attr_in3_input.dev_attr.attr,
|
|
@@ -345,10 +435,77 @@ static struct attribute *ltc4245_attributes[] = {
|
|
|
NULL,
|
|
|
};
|
|
|
|
|
|
-static const struct attribute_group ltc4245_group = {
|
|
|
- .attrs = ltc4245_attributes,
|
|
|
+static struct attribute *ltc4245_gpio_attributes[] = {
|
|
|
+ &sensor_dev_attr_in10_input.dev_attr.attr,
|
|
|
+ &sensor_dev_attr_in11_input.dev_attr.attr,
|
|
|
+ NULL,
|
|
|
+};
|
|
|
+
|
|
|
+static const struct attribute_group ltc4245_std_group = {
|
|
|
+ .attrs = ltc4245_std_attributes,
|
|
|
+};
|
|
|
+
|
|
|
+static const struct attribute_group ltc4245_gpio_group = {
|
|
|
+ .attrs = ltc4245_gpio_attributes,
|
|
|
};
|
|
|
|
|
|
+static int ltc4245_sysfs_create_groups(struct i2c_client *client)
|
|
|
+{
|
|
|
+ struct ltc4245_data *data = i2c_get_clientdata(client);
|
|
|
+ struct device *dev = &client->dev;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* register the standard sysfs attributes */
|
|
|
+ ret = sysfs_create_group(&dev->kobj, <c4245_std_group);
|
|
|
+ if (ret) {
|
|
|
+ dev_err(dev, "unable to register standard attributes\n");
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* if we're using the extra gpio support, register it's attributes */
|
|
|
+ if (data->use_extra_gpios) {
|
|
|
+ ret = sysfs_create_group(&dev->kobj, <c4245_gpio_group);
|
|
|
+ if (ret) {
|
|
|
+ dev_err(dev, "unable to register gpio attributes\n");
|
|
|
+ sysfs_remove_group(&dev->kobj, <c4245_std_group);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void ltc4245_sysfs_remove_groups(struct i2c_client *client)
|
|
|
+{
|
|
|
+ struct ltc4245_data *data = i2c_get_clientdata(client);
|
|
|
+ struct device *dev = &client->dev;
|
|
|
+
|
|
|
+ if (data->use_extra_gpios)
|
|
|
+ sysfs_remove_group(&dev->kobj, <c4245_gpio_group);
|
|
|
+
|
|
|
+ sysfs_remove_group(&dev->kobj, <c4245_std_group);
|
|
|
+}
|
|
|
+
|
|
|
+static bool ltc4245_use_extra_gpios(struct i2c_client *client)
|
|
|
+{
|
|
|
+ struct ltc4245_platform_data *pdata = dev_get_platdata(&client->dev);
|
|
|
+#ifdef CONFIG_OF
|
|
|
+ struct device_node *np = client->dev.of_node;
|
|
|
+#endif
|
|
|
+
|
|
|
+ /* prefer platform data */
|
|
|
+ if (pdata)
|
|
|
+ return pdata->use_extra_gpios;
|
|
|
+
|
|
|
+#ifdef CONFIG_OF
|
|
|
+ /* fallback on OF */
|
|
|
+ if (of_find_property(np, "ltc4245,use-extra-gpios", NULL))
|
|
|
+ return true;
|
|
|
+#endif
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
static int ltc4245_probe(struct i2c_client *client,
|
|
|
const struct i2c_device_id *id)
|
|
|
{
|
|
@@ -367,15 +524,16 @@ static int ltc4245_probe(struct i2c_client *client,
|
|
|
|
|
|
i2c_set_clientdata(client, data);
|
|
|
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);
|
|
|
|
|
|
/* Register sysfs hooks */
|
|
|
- ret = sysfs_create_group(&client->dev.kobj, <c4245_group);
|
|
|
+ ret = ltc4245_sysfs_create_groups(client);
|
|
|
if (ret)
|
|
|
- goto out_sysfs_create_group;
|
|
|
+ goto out_sysfs_create_groups;
|
|
|
|
|
|
data->hwmon_dev = hwmon_device_register(&client->dev);
|
|
|
if (IS_ERR(data->hwmon_dev)) {
|
|
@@ -386,8 +544,8 @@ static int ltc4245_probe(struct i2c_client *client,
|
|
|
return 0;
|
|
|
|
|
|
out_hwmon_device_register:
|
|
|
- sysfs_remove_group(&client->dev.kobj, <c4245_group);
|
|
|
-out_sysfs_create_group:
|
|
|
+ ltc4245_sysfs_remove_groups(client);
|
|
|
+out_sysfs_create_groups:
|
|
|
kfree(data);
|
|
|
out_kzalloc:
|
|
|
return ret;
|
|
@@ -398,8 +556,7 @@ static int ltc4245_remove(struct i2c_client *client)
|
|
|
struct ltc4245_data *data = i2c_get_clientdata(client);
|
|
|
|
|
|
hwmon_device_unregister(data->hwmon_dev);
|
|
|
- sysfs_remove_group(&client->dev.kobj, <c4245_group);
|
|
|
-
|
|
|
+ ltc4245_sysfs_remove_groups(client);
|
|
|
kfree(data);
|
|
|
|
|
|
return 0;
|