[i2c] [patch 2.6.23.1, version 4] add support for the PCF8575 chip

Jean Delvare khali at linux-fr.org
Fri Oct 26 14:04:30 CEST 2007


Hi Bart,

On Thu, 25 Oct 2007 11:52:48 +0200, bart.vanassche at gmail.com wrote:
> I2C: add support for the PCF8575 chip.
> 
> Signed-off-by: Bart Van Assche <bart.vanassche at gmail.com>
> 
> diff -uprN -X orig/linux-2.6.23.1/Documentation/dontdiff -x vdso.lds orig/linux-2.6.23.1/Documentation/i2c/chips/pcf8575 linux-2.6.23.1/Documentation/i2c/chips/pcf8575
> --- orig/linux-2.6.23.1/Documentation/i2c/chips/pcf8575	1970-01-01 01:00:00.000000000 +0100
> +++ linux-2.6.23.1/Documentation/i2c/chips/pcf8575	2007-10-25 11:47:24.000000000 +0200
> @@ -0,0 +1,72 @@
> +About the PCF8575 chip and the pcf8575 kernel driver
> +====================================================
> +
> +The PCF8575 chip is produced by the following manufacturers:
> +
> +  * Philips NXP
> +    http://www.nxp.com/#/pip/cb=[type=product,path=50807/41735/41850,final=PCF8575_3]|pip=[pip=PCF8575_3][0]
> +
> +  * Texas Instruments
> +    http://focus.ti.com/docs/prod/folders/print/pcf8575.html

Amazing, that's the first time I see a clone I2C chip being sold under
the exact same name as the original.

> +
> +
> +Some vendors sell small PCB's with the PCF8575 mounted on it. You can connect
> +such a board to a Linux host via e.g. an USB to I2C interface. Examples of
> +PCB boards with a PCF8575:
> +
> +  * SFE Breakout Board for PCF8575 I2C Expander by RobotShop
> +    http://www.robotshop.ca/home/products/robot-parts/electronics/adapters-converters/sfe-pcf8575-i2c-expander-board.html
> +
> +  * Breakout Board for PCF8575 I2C Expander by Spark Fun Electronics
> +    http://www.sparkfun.com/commerce/product_info.php?products_id=8130
> +
> +
> +Description
> +-----------
> +The PCF8575 chip is a 16-bit I/O expander for the I2C bus. Up to eight of
> +these chips can be connected to the same I2C bus. You can find this
> +chip on some custom designed hardware, but you won't find it on PC
> +motherboards.
> +
> +The PCF8575 chip consists of a 16-bit quasi-bidirectional port and an I2C-bus
> +interface. Each of the sixteen I/O's can be independently used as an input or
> +an output. To set up an I/O pin as an input, you have to write a 1 to the
> +corresponding output.
> +
> +For more information please see the datasheet.
> +
> +
> +Detection
> +---------
> +
> +There is no method known to detect whether a chip on a given I2C address is
> +a PCF8575 or whether it is any other I2C device. So there are two alternatives
> +to let the driver find the installed PCF8575 devices:
> +- Load this driver after any other I2C driver for I2C devices with addresses
> +  in the range 0x20 .. 0x27.
> +- Pass the I2C bus and address of the installed PCF8575 devices explicitly to
> +  the driver at load time via the probe=... or force=... parameters.

With this last iteration of the driver, the second approach doesn't
actually work. The user needs to use ignore parameters to skip addresses
where non-PCF8575 chips live, rather than the other way around.

> +
> +/sys interface
> +--------------
> +
> +For each address on which a PCF8575 chip was found or forced the following
> +files will be created under /sys:
> +* /sys/bus/i2c/devices/<bus>-<address>/read
> +* /sys/bus/i2c/devices/<bus>-<address>/write
> +where bus is the I2C bus number (0, 1, ...) and address is the four-digit
> +hexadecimal representation of the 7-bit I2C address of the PCF8575
> +(0020 .. 0027).
> +
> +The read file is read-only. Reading it will trigger an I2C read and will hence
> +report the current input state for the pins configured as inputs, and the
> +current output value for the pins configured as outputs.
> +
> +The write file is read-write. Writing a value to it will configure all pins
> +as output for which the corresponding bit is zero. Reading the write file will
> +return the value last written, or -EAGAIN if no value has yet been written to
> +the write file.
> +
> +On module initialization the configuration of the chip is not changed -- the
> +chip is left in the state it was already configured in through either power-up
> +or through previous I2C write actions.
> diff -uprN -X orig/linux-2.6.23.1/Documentation/dontdiff -x vdso.lds orig/linux-2.6.23.1/drivers/i2c/chips/Kconfig linux-2.6.23.1/drivers/i2c/chips/Kconfig
> --- orig/linux-2.6.23.1/drivers/i2c/chips/Kconfig	2007-10-12 18:43:44.000000000 +0200
> +++ linux-2.6.23.1/drivers/i2c/chips/Kconfig	2007-10-24 11:57:44.000000000 +0200
> @@ -57,7 +57,7 @@ config SENSORS_PCF8574
>  	default n
>  	help
>  	  If you say yes here you get support for Philips PCF8574 and 
> -	  PCF8574A chips.
> +	  PCF8574A chips. These chips are 8-bit I/O expanders for the I2C bus.
>  
>  	  This driver can also be built as a module.  If so, the module
>  	  will be called pcf8574.
> @@ -65,6 +65,20 @@ config SENSORS_PCF8574
>  	  These devices are hard to detect and rarely found on mainstream
>  	  hardware.  If unsure, say N.
>  
> +config PCF8575
> +	tristate "Philips PCF8575"
> +	default n
> +	help
> +	  If you say yes here you get support for Philips PCF8575 chip.
> +	  This chip is a 16-bit I/O expander for the I2C bus.  Several other
> +	  chip manufacturers sell equivalent chips, e.g. Texas Instruments.
> +
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called pcf8575.
> +
> +	  This device is hard to detect and is rarely found on mainstream
> +	  hardware.  If unsure, say N.
> +
>  config SENSORS_PCA9539
>  	tristate "Philips PCA9539 16-bit I/O port"
>  	depends on EXPERIMENTAL
> diff -uprN -X orig/linux-2.6.23.1/Documentation/dontdiff -x vdso.lds orig/linux-2.6.23.1/drivers/i2c/chips/Makefile linux-2.6.23.1/drivers/i2c/chips/Makefile
> --- orig/linux-2.6.23.1/drivers/i2c/chips/Makefile	2007-10-12 18:43:44.000000000 +0200
> +++ linux-2.6.23.1/drivers/i2c/chips/Makefile	2007-10-24 11:57:47.000000000 +0200
> @@ -10,6 +10,7 @@ obj-$(CONFIG_SENSORS_MAX6875)	+= max6875
>  obj-$(CONFIG_SENSORS_M41T00)	+= m41t00.o
>  obj-$(CONFIG_SENSORS_PCA9539)	+= pca9539.o
>  obj-$(CONFIG_SENSORS_PCF8574)	+= pcf8574.o
> +obj-$(CONFIG_PCF8575)		+= pcf8575.o
>  obj-$(CONFIG_SENSORS_PCF8591)	+= pcf8591.o
>  obj-$(CONFIG_ISP1301_OMAP)	+= isp1301_omap.o
>  obj-$(CONFIG_TPS65010)		+= tps65010.o
> diff -uprN -X orig/linux-2.6.23.1/Documentation/dontdiff -x vdso.lds orig/linux-2.6.23.1/drivers/i2c/chips/pcf8575.c linux-2.6.23.1/drivers/i2c/chips/pcf8575.c
> --- orig/linux-2.6.23.1/drivers/i2c/chips/pcf8575.c	1970-01-01 01:00:00.000000000 +0100
> +++ linux-2.6.23.1/drivers/i2c/chips/pcf8575.c	2007-10-25 11:44:37.000000000 +0200
> @@ -0,0 +1,219 @@
> +/*
> +  pcf8575.c
> +
> +  About the PCF8575 chip: the PCF8575 is a 16-bit I/O expander for the I2C bus
> +  produced by a.o. Philips Semiconductors.
> +
> +  Copyright (C) 2006 Michael Hennerich, Analog Devices Inc.
> +  <hennerich at blackfin.uclinux.org>
> +  Based on pcf8574.c.
> +
> +  Copyright (c) 2007 Bart Van Assche <bart.vanassche at gmail.com>.
> +  Ported this driver from ucLinux to the mainstream Linux kernel.
> +
> +  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.
> +*/
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/i2c.h>
> +#include <linux/slab.h>  /* kzalloc() */
> +#include <linux/sysfs.h> /* sysfs_create_group() */
> +
> +/* Addresses to scan */
> +static unsigned short normal_i2c[] = {
> +  0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
> +  I2C_CLIENT_END
> +};
> +
> +/* Insmod parameters */
> +I2C_CLIENT_INSMOD;
> +
> +
> +/* Each client has this additional data */
> +struct pcf8575_data {
> +	struct i2c_client client;
> +	/* The lower 16 bits are the last written value, and the upper 16  */
> +	/* bits indicate wether (0x0000) or not (0xffff) the lower 16 bits */
> +	/* may be returned to userspace.                                   */

This comment is pretty confusing. The point is whether the value is
negative or not, not whether high bits this or low bits that.

> +	s32 write;
> +};
> +
> +static int pcf8575_attach_adapter(struct i2c_adapter *adapter);
> +static int pcf8575_detect(struct i2c_adapter *adapter, int address, int kind);
> +static int pcf8575_detach_client(struct i2c_client *client);
> +
> +/* This is the driver that will be inserted */
> +static struct i2c_driver pcf8575_driver = {
> +	.driver = {
> +		.owner	= THIS_MODULE,
> +		.name	= "pcf8575",
> +	},
> +	.attach_adapter	= pcf8575_attach_adapter,
> +	.detach_client	= pcf8575_detach_client,
> +};
> +
> +/* following are the sysfs callback functions */
> +static ssize_t show_read(struct device *dev, struct device_attribute *attr,
> +			 char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	u16 val;
> +	u8 iopin_state[2];
> +
> +	i2c_master_recv(client, iopin_state, 2);
> +
> +	val = iopin_state[0];
> +	val |= iopin_state[1] << 8;
> +
> +	return sprintf(buf, "%u\n", val);
> +}
> +
> +static DEVICE_ATTR(read, S_IRUGO, show_read, NULL);
> +
> +static ssize_t show_write(struct device *dev, struct device_attribute *attr,
> +			  char *buf)
> +{
> +	struct pcf8575_data *data = dev_get_drvdata(dev);
> +	if (data->write < 0)
> +		return data->write;
> +	return sprintf(buf, "%d\n", data->write);
> +}
> +
> +static ssize_t set_write(struct device *dev, struct device_attribute *attr,
> +			 const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct pcf8575_data *data = i2c_get_clientdata(client);
> +	unsigned long val = simple_strtoul(buf, NULL, 10);
> +	u8 iopin_state[2];
> +
> +	if (val > 0xffff)
> +		return -EINVAL;
> +
> +	data->write = val;
> +
> +	iopin_state[0] = val & 0xFF;
> +	iopin_state[1] = val >> 8;
> +
> +	i2c_master_send(client, iopin_state, 2);
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR(write, S_IWUSR | S_IRUGO, show_write, set_write);
> +
> +static struct attribute *pcf8575_attributes[] = {
> +	&dev_attr_read.attr,
> +	&dev_attr_write.attr,
> +	NULL
> +};
> +
> +static const struct attribute_group pcf8575_attr_group = {
> +	.attrs = pcf8575_attributes,
> +};
> +
> +/*
> + * Real code
> + */
> +
> +static int pcf8575_attach_adapter(struct i2c_adapter *adapter)
> +{
> +	return i2c_probe(adapter, &addr_data, pcf8575_detect);
> +}
> +
> +/* This function is called by i2c_probe */
> +static int pcf8575_detect(struct i2c_adapter *adapter, int address, int kind)
> +{
> +	struct i2c_client *client;
> +	struct pcf8575_data *data;
> +	int err = 0;
> +
> +	if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) {
> +		err = -EIO;

This change is not correct. If the underlying bus doesn't support raw
I2C transactions, it means that no PCF8575 chip can be there. That's
not an error. Returning an error at this point will cause i2c-core to
stop probing, which is not what you want. So I'll revert this change.

> +		goto exit;
> +	}
> +
> +	/* OK. For now, we presume we have a valid client. We now create the
> +	   client structure, even though we cannot fill it completely yet. */
> +	data = kzalloc(sizeof(struct pcf8575_data), GFP_KERNEL);
> +	if (!data) {
> +		err = -ENOMEM;
> +		goto exit;
> +	}
> +
> +	client = &data->client;
> +	i2c_set_clientdata(client,  data);
> +	client->addr = address;
> +	client->adapter = adapter;
> +	client->driver = &pcf8575_driver;
> +	strlcpy(client->name, "pcf8575", I2C_NAME_SIZE);
> +	data->write = -EAGAIN;
> +
> +	/* This is the place to detect whether the chip at the specified
> +	   address really is a PCF8575 chip. However, there is no method known
> +	   to detect whether an I2C chip is a PCF8575 or any other I2C chip. */

You could try reading from the chip once and see if you get an error or
not. This would rule out chips which do not support 2-byte reads.

> +
> +	/* Tell the I2C layer a new client has arrived */
> +	err = i2c_attach_client(client);
> +	if (err)
> +		goto exit_free;
> +
> +	/* Register sysfs hooks */
> +	err = sysfs_create_group(&client->dev.kobj, &pcf8575_attr_group);
> +	if (err)
> +		goto exit_detach;
> +
> +	return 0;
> +
> +exit_detach:
> +	i2c_detach_client(client);
> +exit_free:
> +	kfree(data);
> +exit:
> +	return err;
> +}
> +
> +static int pcf8575_detach_client(struct i2c_client *client)
> +{
> +	int err;
> +
> +	sysfs_remove_group(&client->dev.kobj, &pcf8575_attr_group);
> +
> +	err = i2c_detach_client(client);
> +	if (err)
> +		return err;
> +
> +	kfree(i2c_get_clientdata(client));
> +	return 0;
> +}
> +
> +static int __init pcf8575_init(void)
> +{
> +	return i2c_add_driver(&pcf8575_driver);
> +}
> +
> +static void __exit pcf8575_exit(void)
> +{
> +	i2c_del_driver(&pcf8575_driver);
> +}
> +
> +MODULE_AUTHOR("Michael Hennerich <hennerich at blackfin.uclinux.org>, "
> +	      "Bart Van Assche <bart.vanassche at gmail.com>");
> +MODULE_DESCRIPTION("pcf8575 driver");
> +MODULE_LICENSE("GPL");
> +
> +module_init(pcf8575_init);
> +module_exit(pcf8575_exit);

Patch applied, thanks. The exact version I applied can be seen here:
http://khali.linux-fr.org/devel/linux-2.6/jdelvare-i2c/i2c-pcf8575-new-driver.patch
Any further change to the driver or its documentation should be
submitted as an incremental patch.

-- 
Jean Delvare



More information about the i2c mailing list