Commit 5950ec8d3e47a08ec0b678a0e0ba5d1b9b62dd8e

Authored by Ira W. Snyder
Committed by Jean Delvare
1 parent b9783dcebe

hwmon: (ltc4245) Expose all GPIO pins as analog voltages

Add support for exposing all GPIO pins as analog voltages. Though this is
not an ideal use of the chip, some hardware engineers may decide that the
LTC4245 meets their design requirements when studying the datasheet.

The GPIO pins are sampled in round-robin fashion, meaning that a slow
reader will see stale data. A userspace application can detect this,
because it will get -EAGAIN when reading from a sysfs file which contains
stale data.

Users can choose to use this feature on a per-chip basis by using either
platform data or the OF device tree (where applicable).

Signed-off-by: Ira W. Snyder <iws@ovro.caltech.edu>
Signed-off-by: Jean Delvare <khali@linux-fr.org>

Showing 3 changed files with 211 additions and 11 deletions Side-by-side Diff

Documentation/hwmon/ltc4245
... ... @@ -72,10 +72,32 @@
72 72 in7_min_alarm 3v output undervoltage alarm
73 73 in8_min_alarm Vee (-12v) output undervoltage alarm
74 74  
75   -in9_input GPIO voltage data
  75 +in9_input GPIO voltage data (see note 1)
  76 +in10_input GPIO voltage data (see note 1)
  77 +in11_input GPIO voltage data (see note 1)
76 78  
77 79 power1_input 12v power usage (mW)
78 80 power2_input 5v power usage (mW)
79 81 power3_input 3v power usage (mW)
80 82 power4_input Vee (-12v) power usage (mW)
  83 +
  84 +
  85 +Note 1
  86 +------
  87 +
  88 +If you have NOT configured the driver to sample all GPIO pins as analog
  89 +voltages, then the in10_input and in11_input sysfs attributes will not be
  90 +created. The driver will sample the GPIO pin that is currently connected to the
  91 +ADC as an analog voltage, and report the value in in9_input.
  92 +
  93 +If you have configured the driver to sample all GPIO pins as analog voltages,
  94 +then they will be sampled in round-robin fashion. If userspace reads too
  95 +slowly, -EAGAIN will be returned when you read the sysfs attribute containing
  96 +the sensor reading.
  97 +
  98 +The LTC4245 chip can be configured to sample all GPIO pins with two methods:
  99 +1) platform data -- see include/linux/i2c/ltc4245.h
  100 +2) OF device tree -- add the "ltc4245,use-extra-gpios" property to each chip
  101 +
  102 +The default mode of operation is to sample a single GPIO pin.
drivers/hwmon/ltc4245.c
... ... @@ -21,6 +21,7 @@
21 21 #include <linux/i2c.h>
22 22 #include <linux/hwmon.h>
23 23 #include <linux/hwmon-sysfs.h>
  24 +#include <linux/i2c/ltc4245.h>
24 25  
25 26 /* Here are names of the chip's registers (a.k.a. commands) */
26 27 enum ltc4245_cmd {
27 28  
... ... @@ -60,8 +61,72 @@
60 61  
61 62 /* Voltage registers */
62 63 u8 vregs[0x0d];
  64 +
  65 + /* GPIO ADC registers */
  66 + bool use_extra_gpios;
  67 + int gpios[3];
63 68 };
64 69  
  70 +/*
  71 + * Update the readings from the GPIO pins. If the driver has been configured to
  72 + * sample all GPIO's as analog voltages, a round-robin sampling method is used.
  73 + * Otherwise, only the configured GPIO pin is sampled.
  74 + *
  75 + * LOCKING: must hold data->update_lock
  76 + */
  77 +static void ltc4245_update_gpios(struct device *dev)
  78 +{
  79 + struct i2c_client *client = to_i2c_client(dev);
  80 + struct ltc4245_data *data = i2c_get_clientdata(client);
  81 + u8 gpio_curr, gpio_next, gpio_reg;
  82 + int i;
  83 +
  84 + /* no extra gpio support, we're basically done */
  85 + if (!data->use_extra_gpios) {
  86 + data->gpios[0] = data->vregs[LTC4245_GPIOADC - 0x10];
  87 + return;
  88 + }
  89 +
  90 + /*
  91 + * If the last reading was too long ago, then we mark all old GPIO
  92 + * readings as stale by setting them to -EAGAIN
  93 + */
  94 + if (time_after(jiffies, data->last_updated + 5 * HZ)) {
  95 + dev_dbg(&client->dev, "Marking GPIOs invalid\n");
  96 + for (i = 0; i < ARRAY_SIZE(data->gpios); i++)
  97 + data->gpios[i] = -EAGAIN;
  98 + }
  99 +
  100 + /*
  101 + * Get the current GPIO pin
  102 + *
  103 + * The datasheet calls these GPIO[1-3], but we'll calculate the zero
  104 + * based array index instead, and call them GPIO[0-2]. This is much
  105 + * easier to think about.
  106 + */
  107 + gpio_curr = (data->cregs[LTC4245_GPIO] & 0xc0) >> 6;
  108 + if (gpio_curr > 0)
  109 + gpio_curr -= 1;
  110 +
  111 + /* Read the GPIO voltage from the GPIOADC register */
  112 + data->gpios[gpio_curr] = data->vregs[LTC4245_GPIOADC - 0x10];
  113 +
  114 + /* Find the next GPIO pin to read */
  115 + gpio_next = (gpio_curr + 1) % ARRAY_SIZE(data->gpios);
  116 +
  117 + /*
  118 + * Calculate the correct setting for the GPIO register so it will
  119 + * sample the next GPIO pin
  120 + */
  121 + gpio_reg = (data->cregs[LTC4245_GPIO] & 0x3f) | ((gpio_next + 1) << 6);
  122 +
  123 + /* Update the GPIO register */
  124 + i2c_smbus_write_byte_data(client, LTC4245_GPIO, gpio_reg);
  125 +
  126 + /* Update saved data */
  127 + data->cregs[LTC4245_GPIO] = gpio_reg;
  128 +}
  129 +
65 130 static struct ltc4245_data *ltc4245_update_device(struct device *dev)
66 131 {
67 132 struct i2c_client *client = to_i2c_client(dev);
... ... @@ -93,6 +158,9 @@
93 158 data->vregs[i] = val;
94 159 }
95 160  
  161 + /* Update GPIO readings */
  162 + ltc4245_update_gpios(dev);
  163 +
96 164 data->last_updated = jiffies;
97 165 data->valid = 1;
98 166 }
... ... @@ -233,6 +301,22 @@
233 301 return snprintf(buf, PAGE_SIZE, "%u\n", (reg & mask) ? 1 : 0);
234 302 }
235 303  
  304 +static ssize_t ltc4245_show_gpio(struct device *dev,
  305 + struct device_attribute *da,
  306 + char *buf)
  307 +{
  308 + struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
  309 + struct ltc4245_data *data = ltc4245_update_device(dev);
  310 + int val = data->gpios[attr->index];
  311 +
  312 + /* handle stale GPIO's */
  313 + if (val < 0)
  314 + return val;
  315 +
  316 + /* Convert to millivolts and print */
  317 + return snprintf(buf, PAGE_SIZE, "%u\n", val * 10);
  318 +}
  319 +
236 320 /* These macros are used below in constructing device attribute objects
237 321 * for use with sysfs_create_group() to make a sysfs device file
238 322 * for each register.
... ... @@ -254,6 +338,10 @@
254 338 static SENSOR_DEVICE_ATTR_2(name, S_IRUGO, \
255 339 ltc4245_show_alarm, NULL, (mask), reg)
256 340  
  341 +#define LTC4245_GPIO_VOLTAGE(name, gpio_num) \
  342 + static SENSOR_DEVICE_ATTR(name, S_IRUGO, \
  343 + ltc4245_show_gpio, NULL, gpio_num)
  344 +
257 345 /* Construct a sensor_device_attribute structure for each register */
258 346  
259 347 /* Input voltages */
... ... @@ -293,7 +381,9 @@
293 381 LTC4245_ALARM(in8_min_alarm, (1 << 3), LTC4245_FAULT2);
294 382  
295 383 /* GPIO voltages */
296   -LTC4245_VOLTAGE(in9_input, LTC4245_GPIOADC);
  384 +LTC4245_GPIO_VOLTAGE(in9_input, 0);
  385 +LTC4245_GPIO_VOLTAGE(in10_input, 1);
  386 +LTC4245_GPIO_VOLTAGE(in11_input, 2);
297 387  
298 388 /* Power Consumption (virtual) */
299 389 LTC4245_POWER(power1_input, LTC4245_12VSENSE);
... ... @@ -304,7 +394,7 @@
304 394 /* Finally, construct an array of pointers to members of the above objects,
305 395 * as required for sysfs_create_group()
306 396 */
307   -static struct attribute *ltc4245_attributes[] = {
  397 +static struct attribute *ltc4245_std_attributes[] = {
308 398 &sensor_dev_attr_in1_input.dev_attr.attr,
309 399 &sensor_dev_attr_in2_input.dev_attr.attr,
310 400 &sensor_dev_attr_in3_input.dev_attr.attr,
311 401  
... ... @@ -345,10 +435,77 @@
345 435 NULL,
346 436 };
347 437  
348   -static const struct attribute_group ltc4245_group = {
349   - .attrs = ltc4245_attributes,
  438 +static struct attribute *ltc4245_gpio_attributes[] = {
  439 + &sensor_dev_attr_in10_input.dev_attr.attr,
  440 + &sensor_dev_attr_in11_input.dev_attr.attr,
  441 + NULL,
350 442 };
351 443  
  444 +static const struct attribute_group ltc4245_std_group = {
  445 + .attrs = ltc4245_std_attributes,
  446 +};
  447 +
  448 +static const struct attribute_group ltc4245_gpio_group = {
  449 + .attrs = ltc4245_gpio_attributes,
  450 +};
  451 +
  452 +static int ltc4245_sysfs_create_groups(struct i2c_client *client)
  453 +{
  454 + struct ltc4245_data *data = i2c_get_clientdata(client);
  455 + struct device *dev = &client->dev;
  456 + int ret;
  457 +
  458 + /* register the standard sysfs attributes */
  459 + ret = sysfs_create_group(&dev->kobj, &ltc4245_std_group);
  460 + if (ret) {
  461 + dev_err(dev, "unable to register standard attributes\n");
  462 + return ret;
  463 + }
  464 +
  465 + /* if we're using the extra gpio support, register it's attributes */
  466 + if (data->use_extra_gpios) {
  467 + ret = sysfs_create_group(&dev->kobj, &ltc4245_gpio_group);
  468 + if (ret) {
  469 + dev_err(dev, "unable to register gpio attributes\n");
  470 + sysfs_remove_group(&dev->kobj, &ltc4245_std_group);
  471 + return ret;
  472 + }
  473 + }
  474 +
  475 + return 0;
  476 +}
  477 +
  478 +static void ltc4245_sysfs_remove_groups(struct i2c_client *client)
  479 +{
  480 + struct ltc4245_data *data = i2c_get_clientdata(client);
  481 + struct device *dev = &client->dev;
  482 +
  483 + if (data->use_extra_gpios)
  484 + sysfs_remove_group(&dev->kobj, &ltc4245_gpio_group);
  485 +
  486 + sysfs_remove_group(&dev->kobj, &ltc4245_std_group);
  487 +}
  488 +
  489 +static bool ltc4245_use_extra_gpios(struct i2c_client *client)
  490 +{
  491 + struct ltc4245_platform_data *pdata = dev_get_platdata(&client->dev);
  492 +#ifdef CONFIG_OF
  493 + struct device_node *np = client->dev.of_node;
  494 +#endif
  495 +
  496 + /* prefer platform data */
  497 + if (pdata)
  498 + return pdata->use_extra_gpios;
  499 +
  500 +#ifdef CONFIG_OF
  501 + /* fallback on OF */
  502 + if (of_find_property(np, "ltc4245,use-extra-gpios", NULL))
  503 + return true;
  504 +#endif
  505 +
  506 + return false;
  507 +}
  508 +
352 509 static int ltc4245_probe(struct i2c_client *client,
353 510 const struct i2c_device_id *id)
354 511 {
355 512  
356 513  
... ... @@ -367,15 +524,16 @@
367 524  
368 525 i2c_set_clientdata(client, data);
369 526 mutex_init(&data->update_lock);
  527 + data->use_extra_gpios = ltc4245_use_extra_gpios(client);
370 528  
371 529 /* Initialize the LTC4245 chip */
372 530 i2c_smbus_write_byte_data(client, LTC4245_FAULT1, 0x00);
373 531 i2c_smbus_write_byte_data(client, LTC4245_FAULT2, 0x00);
374 532  
375 533 /* Register sysfs hooks */
376   - ret = sysfs_create_group(&client->dev.kobj, &ltc4245_group);
  534 + ret = ltc4245_sysfs_create_groups(client);
377 535 if (ret)
378   - goto out_sysfs_create_group;
  536 + goto out_sysfs_create_groups;
379 537  
380 538 data->hwmon_dev = hwmon_device_register(&client->dev);
381 539 if (IS_ERR(data->hwmon_dev)) {
... ... @@ -386,8 +544,8 @@
386 544 return 0;
387 545  
388 546 out_hwmon_device_register:
389   - sysfs_remove_group(&client->dev.kobj, &ltc4245_group);
390   -out_sysfs_create_group:
  547 + ltc4245_sysfs_remove_groups(client);
  548 +out_sysfs_create_groups:
391 549 kfree(data);
392 550 out_kzalloc:
393 551 return ret;
... ... @@ -398,8 +556,7 @@
398 556 struct ltc4245_data *data = i2c_get_clientdata(client);
399 557  
400 558 hwmon_device_unregister(data->hwmon_dev);
401   - sysfs_remove_group(&client->dev.kobj, &ltc4245_group);
402   -
  559 + ltc4245_sysfs_remove_groups(client);
403 560 kfree(data);
404 561  
405 562 return 0;
include/linux/i2c/ltc4245.h
  1 +/*
  2 + * Platform Data for LTC4245 hardware monitor chip
  3 + *
  4 + * Copyright (c) 2010 Ira W. Snyder <iws@ovro.caltech.edu>
  5 + *
  6 + * This program is free software; you can redistribute it and/or modify it
  7 + * under the terms of the GNU General Public License as published by the
  8 + * Free Software Foundation; either version 2 of the License, or (at your
  9 + * option) any later version.
  10 + */
  11 +
  12 +#ifndef LINUX_LTC4245_H
  13 +#define LINUX_LTC4245_H
  14 +
  15 +#include <linux/types.h>
  16 +
  17 +struct ltc4245_platform_data {
  18 + bool use_extra_gpios;
  19 +};
  20 +
  21 +#endif /* LINUX_LTC4245_H */