[lm-sensors] [PATCH 2/2] hwmon: (ltc4245) expose all GPIO pins as analog voltages

Ira W. Snyder iws at ovro.caltech.edu
Wed May 26 17:42:26 CEST 2010


On Wed, May 26, 2010 at 01:31:44PM +0200, Jean Delvare wrote:
> Hi Ira,
> 
> Sorry for the late answer.
> 
> On Tue, 13 Apr 2010 15:59:29 -0700, Ira W. Snyder wrote:
> > 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.
> 
> A userspace application _or library_. In practice, most monitoring
> applications will rely on libsensors.
> 
> > 
> > Signed-off-by: Ira W. Snyder <iws at ovro.caltech.edu>
> > ---
> >  Documentation/hwmon/ltc4245 |   13 +++++-
> >  drivers/hwmon/Kconfig       |   10 +++++
> >  drivers/hwmon/ltc4245.c     |   95 ++++++++++++++++++++++++++++++++++++++++++-
> >  3 files changed, 116 insertions(+), 2 deletions(-)
> > 
> > diff --git a/Documentation/hwmon/ltc4245 b/Documentation/hwmon/ltc4245
> > index 86b5880..1c0599f 100644
> > --- a/Documentation/hwmon/ltc4245
> > +++ b/Documentation/hwmon/ltc4245
> > @@ -72,9 +72,20 @@ in6_min_alarm		5v  output undervoltage alarm
> >  in7_min_alarm		3v  output undervoltage alarm
> >  in8_min_alarm		Vee (-12v) output undervoltage alarm
> >  
> > -in9_input		GPIO voltage data
> > +in9_input		GPIO voltage data (see note 1)
> > +in10_input		GPIO voltage data (see note 1)
> > +in11_input		GPIO voltage data (see note 1)
> >  
> >  power1_input		12v power usage (mW)
> >  power2_input		5v  power usage (mW)
> >  power3_input		3v  power usage (mW)
> >  power4_input		Vee (-12v) power usage (mW)
> > +
> > +
> > +Note 1
> > +------
> > +
> > +If you have configured your kernel with CONFIG_SENSORS_LTC4245_EXTRA_GPIOS=y
> > +then all three GPIO voltage lines will be sampled in round-robin fashion. If
> > +the data becomes stale, -EAGAIN will be returned when you read the sysfs
> > +attribute containing the sensor reading.
> 
> Please also clarify the case CONFIG_SENSORS_LTC4245_EXTRA_GPIOS=n.
> 
> > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> > index e4595e6..e9c99cb 100644
> > --- a/drivers/hwmon/Kconfig
> > +++ b/drivers/hwmon/Kconfig
> > @@ -623,6 +623,16 @@ config SENSORS_LTC4245
> >  	  This driver can also be built as a module. If so, the module will
> >  	  be called ltc4245.
> >  
> > +config SENSORS_LTC4245_EXTRA_GPIOS
> > +	bool "LTC4245: sample all GPIO pins as analog voltages"
> > +	depends on SENSORS_LTC4245
> > +	default n
> > +	help
> > +	  If you say yes here, the LTC4245 driver will read all of the GPIO
> > +	  pins in rotation, reporting them as extra voltage channels.
> > +
> > +	  Please read the Documentation/hwmon/ltc4245 file for more details.
> > +
> 
> I have to admit that I am surprised that you made this a build-time
> configuration option. This isn't flexible. Distributions will have to
> make a decision for all their users at once. Not only this, but the
> behavior will have to be the same for all LTC4245 chips in a given
> system, even when using a custom kernel (not that I really expect
> multiple LTC4245 chips on the same board, but you never know...)
> 
> I presume that your idea was to make this feature as little intrusive
> as possible for regular users? I thank you for this, but I'm not quite
> sure this is the best approach. A module option, or a per-device
> attribute, would be more flexible. That being said, you're the one in
> need for this option, so I won't argue further. I'm fine taking
> whatever approach seems preferable to you.
> 

That's correct. I know you're concerned about code size, so I tried to
keep things as minimal as possible for what you consider to be the
correct use of the chip.

My only board with this chip is a powerpc, and uses the OpenFirmware
(OF) bindings to tell the driver which i2c address the chip is at. I'd
be happier to do this with platform data, but I have absolutely no idea
how to do that with the OF bindings. I'm not sure it is even possible.

> >  config SENSORS_LM95241
> >  	tristate "National Semiconductor LM95241 sensor chip"
> >  	depends on I2C
> > diff --git a/drivers/hwmon/ltc4245.c b/drivers/hwmon/ltc4245.c
> > index 84e6f32..33fdc02 100644
> > --- a/drivers/hwmon/ltc4245.c
> > +++ b/drivers/hwmon/ltc4245.c
> > @@ -60,8 +60,70 @@ struct ltc4245_data {
> >  
> >  	/* Voltage registers */
> >  	u8 vregs[0x0d];
> > +
> > +	/* GPIO ADC registers */
> > +	unsigned int last_gpio;
> 
> This field is never initialized, meaning it starts with value 0
> (GPIO1). There is, however, no guarantee that the chip has GPIO1
> routed to the ADC when the driver is loaded. OTOH, you read the GPIO
> configuration in cregs[LTC4245_GPIO] already, so last_gpio is somewhat
> redundant, and you could instead use cregs[LTC4245_GPIO] directly.
> 

I'll make an updated patch which uses the register instead.

> > +	int gpios[3];
> >  };
> >  
> > +#ifdef CONFIG_SENSORS_LTC4245_EXTRA_GPIOS
> > +/*
> > + * Update the readings from all three GPIO pins. This means that the old
> > + * readings will be delayed.
> > + *
> > + * 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, ctl;
> > +	int i;
> > +
> > +	/*
> > +	 * If the last reading was 10 seconds ago, then we mark all old GPIO
> > +	 * readings as stale by setting them to zero volts
> > +	 */
> > +	if (time_after(jiffies, data->last_updated + 10 * HZ)) {
> 
> 10 seconds is much. If one reads the values every 10 seconds, you will
> report 20-seconds old readings without complaining. I would lower this
> delay to 5 seconds, tops.
> 

Ok, 5 seconds it is.

> > +		dev_dbg(&client->dev, "Marking GPIOs invalid\n");
> > +		for (i = 0; i < ARRAY_SIZE(data->gpios); i++)
> > +			data->gpios[i] = -EAGAIN;
> > +	}
> > +
> > +	/* Read the GPIO voltage from the GPIOADC register */
> > +	gpio_curr = data->last_gpio;
> > +	data->gpios[gpio_curr] = data->vregs[LTC4245_GPIOADC - 0x10];
> > +
> > +	/* Find the next GPIO pin to read */
> > +	gpio_next = (data->last_gpio + 1) % ARRAY_SIZE(data->gpios);
> > +
> > +	/*
> > +	 * Calculate the correct setting for the GPIO register so it will
> > +	 * sample the next GPIO pin
> > +	 */
> > +	gpio = (data->cregs[LTC4245_GPIO] & 0x3f) | ((gpio_next + 1) << 6);
> > +	ctl = data->cregs[LTC4245_CONTROL];
> > +
> > +	/* Update the GPIO register */
> > +	i2c_smbus_write_byte_data(client, LTC4245_CONTROL, ctl | 0x80);
> > +	i2c_smbus_write_byte_data(client, LTC4245_GPIO, gpio);
> > +	i2c_smbus_write_byte_data(client, LTC4245_CONTROL, ctl);
> 
> Not sure why you toggle bit C7? The datasheet says that it must be
> toggled for writing to registers 0x10 to 0x1F, but LTC4245_GPIO is
> register 0x06. Is there any other reason I'm missing?
> 

Nope, you're exactly right. I'll remove the write to the LTC4245_CONTROL
register.

I guess I read the note about setting/clearing the bit for the
LTC4245_GPIOADC (0x1c) register, and then my mind carried it over to the
LTC4245_GPIO (0x06) register too. Good catch.

> > +
> > +	/* Update saved data */
> > +	data->cregs[LTC4245_GPIO] = gpio;
> > +	data->last_gpio = gpio_next;
> > +}
> > +#else
> > +static void ltc4245_update_gpios(struct device *dev)
> > +{
> > +	struct i2c_client *client = to_i2c_client(dev);
> > +	struct ltc4245_data *data = i2c_get_clientdata(client);
> > +
> > +	/* Just copy the data from the the GPIOADC register */
> > +	data->gpios[0] = data->vregs[LTC4245_GPIOADC - 0x10];
> > +}
> > +#endif
> 
> Not sure why you define this stub function. If you want your patch to
> be as little intrusive as possible, you can skip ltc4245_update_gpios
> altogether in the general case, and handle in9 as every other voltage
> input as the driver was doing before your patch.
> 

I did it so ltc4245_update_gpios() could be called in both cases,
without any conditional logic. See the hunk below. I just call
ltc4245_update_gpios() without any #ifdef, it "just works" in both
cases.

This is the usual thing Linux does with headers. For example, see
include/linux/pci.h pci_enable_msix() function. When CONFIG_PCI_MSI=n
they stub out the function so it does the right thing: return an error.

> > +
> >  static struct ltc4245_data *ltc4245_update_device(struct device *dev)
> >  {
> >  	struct i2c_client *client = to_i2c_client(dev);
> > @@ -93,6 +155,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 +298,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 +335,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(name, gpio_num) \
> > +	static SENSOR_DEVICE_ATTR(name, S_IRUGO, \
> > +	ltc4245_show_gpio, NULL, gpio_num)
> 
> You already have an enum value by that name, this is confusing. That's
> what you get for prefixing your register names with only LTC4245_
> instead of LTC4245_REG_ as most drivers are doing.
> 

The driver works, so I guess the compiler isn't confused. How about I
rename the macro? Is there a different name you would suggest?

> > +
> >  /* Construct a sensor_device_attribute structure for each register */
> >  
> >  /* Input voltages */
> > @@ -293,7 +378,11 @@ 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(in9_input,				0);
> > +#ifdef CONFIG_SENSORS_LTC4245_EXTRA_GPIOS
> > +LTC4245_GPIO(in10_input,			1);
> > +LTC4245_GPIO(in11_input,			2);
> > +#endif
> >  
> >  /* Power Consumption (virtual) */
> >  LTC4245_POWER(power1_input,			LTC4245_12VSENSE);
> > @@ -336,6 +425,10 @@ static struct attribute *ltc4245_attributes[] = {
> >  	&sensor_dev_attr_in8_min_alarm.dev_attr.attr,
> >  
> >  	&sensor_dev_attr_in9_input.dev_attr.attr,
> > +#ifdef CONFIG_SENSORS_LTC4245_EXTRA_GPIOS
> > +	&sensor_dev_attr_in10_input.dev_attr.attr,
> > +	&sensor_dev_attr_in11_input.dev_attr.attr,
> > +#endif
> >  
> >  	&sensor_dev_attr_power1_input.dev_attr.attr,
> >  	&sensor_dev_attr_power2_input.dev_attr.attr,
> 

One more question: if I use a module parameter or platform data to
enable the extra GPIOs, how would I handle this array? I don't see how I
can make it work other than generating two different (mostly redundant)
arrays and registering a different one for each configuration.

Ira




More information about the lm-sensors mailing list