mirror of
				git://git.openwrt.org/openwrt/openwrt.git
				synced 2025-10-30 13:34:27 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			421 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			421 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| --- /dev/null
 | |
| +++ b/drivers/gpio/gw_i2c_pld.c
 | |
| @@ -0,0 +1,370 @@
 | |
| +/*
 | |
| + * Gateworks I2C PLD GPIO expander
 | |
| + *
 | |
| + * Copyright (C) 2009 Gateworks Corporation
 | |
| + *
 | |
| + * 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/kernel.h>
 | |
| +#include <linux/slab.h>
 | |
| +#include <linux/i2c.h>
 | |
| +#include <linux/i2c/gw_i2c_pld.h>
 | |
| +#include <asm/gpio.h>
 | |
| +
 | |
| +static const struct i2c_device_id gw_i2c_pld_id[] = {
 | |
| +	{ "gw_i2c_pld", 8 },
 | |
| +	{ }
 | |
| +};
 | |
| +MODULE_DEVICE_TABLE(i2c, gw_i2c_pld_id);
 | |
| +
 | |
| +/*
 | |
| + * The Gateworks I2C PLD chip only expose one read and one
 | |
| + * write register.  Writing a "one" bit (to match the reset state) lets
 | |
| + * that pin be used as an input. It is an open-drain model.
 | |
| + */
 | |
| +
 | |
| +struct gw_i2c_pld {
 | |
| +	struct gpio_chip	chip;
 | |
| +	struct i2c_client	*client;
 | |
| +	unsigned		out;		/* software latch */
 | |
| +};
 | |
| +
 | |
| +/*-------------------------------------------------------------------------*/
 | |
| +
 | |
| +/*
 | |
| + * The Gateworks I2C PLD chip does not properly send the acknowledge bit
 | |
| + * thus we cannot use standard i2c_smbus functions. We have recreated
 | |
| + * our own here, but we still use the mutex_lock to lock the i2c_bus
 | |
| + * as the device still exists on the I2C bus.
 | |
| +*/
 | |
| +
 | |
| +#define PLD_SCL_GPIO 6
 | |
| +#define PLD_SDA_GPIO 7
 | |
| +
 | |
| +#define SCL_LO()  gpio_line_set(PLD_SCL_GPIO, IXP4XX_GPIO_LOW)
 | |
| +#define SCL_HI()  gpio_line_set(PLD_SCL_GPIO, IXP4XX_GPIO_HIGH)
 | |
| +#define SCL_EN()  gpio_line_config(PLD_SCL_GPIO, IXP4XX_GPIO_OUT)
 | |
| +#define SDA_LO()  gpio_line_set(PLD_SDA_GPIO, IXP4XX_GPIO_LOW)
 | |
| +#define SDA_HI()  gpio_line_set(PLD_SDA_GPIO, IXP4XX_GPIO_HIGH)
 | |
| +#define SDA_EN()  gpio_line_config(PLD_SDA_GPIO, IXP4XX_GPIO_OUT)
 | |
| +#define SDA_DIS() gpio_line_config(PLD_SDA_GPIO, IXP4XX_GPIO_IN)
 | |
| +#define SDA_IN(x) gpio_line_get(PLD_SDA_GPIO, &x);
 | |
| +
 | |
| +static int i2c_pld_write_byte(int address, int byte)
 | |
| +{
 | |
| +	int i;
 | |
| +
 | |
| +	address = (address << 1) & ~0x1;
 | |
| +
 | |
| +	SDA_HI();
 | |
| +	SDA_EN();
 | |
| +	SCL_EN();
 | |
| +	SCL_HI();
 | |
| +	SDA_LO();
 | |
| +	SCL_LO();
 | |
| +
 | |
| +	for (i = 7; i >= 0; i--)
 | |
| +	{
 | |
| +		if (address & (1 << i))
 | |
| +			SDA_HI();
 | |
| +		else
 | |
| +			SDA_LO();
 | |
| +
 | |
| +		SCL_HI();
 | |
| +		SCL_LO();
 | |
| +	}
 | |
| +
 | |
| +	SDA_DIS();
 | |
| +	SCL_HI();
 | |
| +	SDA_IN(i);
 | |
| +	SCL_LO();
 | |
| +	SDA_EN();
 | |
| +
 | |
| +	for (i = 7; i >= 0; i--)
 | |
| +	{
 | |
| +		if (byte & (1 << i))
 | |
| +      SDA_HI();
 | |
| +		else
 | |
| +			SDA_LO();
 | |
| +		SCL_HI();
 | |
| +		SCL_LO();
 | |
| +	}
 | |
| +
 | |
| +	SDA_DIS();
 | |
| +	SCL_HI();
 | |
| +	SDA_IN(i);
 | |
| +	SCL_LO();
 | |
| +
 | |
| +	SDA_HI();
 | |
| +	SDA_EN();
 | |
| +
 | |
| +	SDA_LO();
 | |
| +	SCL_HI();
 | |
| +	SDA_HI();
 | |
| +	SCL_LO();
 | |
| +	SCL_HI();
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static unsigned int i2c_pld_read_byte(int address)
 | |
| +{
 | |
| +	int i = 0, byte = 0;
 | |
| +	int bit;
 | |
| +
 | |
| +	address = (address << 1) | 0x1;
 | |
| +
 | |
| +	SDA_HI();
 | |
| +	SDA_EN();
 | |
| +	SCL_EN();
 | |
| +	SCL_HI();
 | |
| +	SDA_LO();
 | |
| +	SCL_LO();
 | |
| +
 | |
| +	for (i = 7; i >= 0; i--)
 | |
| +	{
 | |
| +		if (address & (1 << i))
 | |
| +			SDA_HI();
 | |
| +		else
 | |
| +			SDA_LO();
 | |
| +
 | |
| +		SCL_HI();
 | |
| +		SCL_LO();
 | |
| +	}
 | |
| +
 | |
| +	SDA_DIS();
 | |
| +	SCL_HI();
 | |
| +	SDA_IN(i);
 | |
| +	SCL_LO();
 | |
| +	SDA_EN();
 | |
| +
 | |
| +	SDA_DIS();
 | |
| +	for (i = 7; i >= 0; i--)
 | |
| +	{
 | |
| +		SCL_HI();
 | |
| +		SDA_IN(bit);
 | |
| +		byte |= bit << i;
 | |
| +		SCL_LO();
 | |
| +	}
 | |
| +
 | |
| +	SDA_LO();
 | |
| +	SCL_HI();
 | |
| +	SDA_HI();
 | |
| +	SCL_LO();
 | |
| +	SCL_HI();
 | |
| +
 | |
| +	return byte;
 | |
| +}
 | |
| +
 | |
| +
 | |
| +static int gw_i2c_pld_input8(struct gpio_chip *chip, unsigned offset)
 | |
| +{
 | |
| +	int ret;
 | |
| +	struct gw_i2c_pld *gpio = container_of(chip, struct gw_i2c_pld, chip);
 | |
| +	struct i2c_adapter *adap = gpio->client->adapter;
 | |
| +	
 | |
| +	if (in_atomic() || irqs_disabled()) {
 | |
| +		ret = mutex_trylock(&adap->bus_lock);
 | |
| +		if (!ret)
 | |
| +			/* I2C activity is ongoing. */
 | |
| +			return -EAGAIN;
 | |
| +	} else {
 | |
| +		mutex_lock_nested(&adap->bus_lock, adap->level);
 | |
| +	}
 | |
| +
 | |
| +	gpio->out |= (1 << offset);
 | |
| +
 | |
| +	ret = i2c_pld_write_byte(gpio->client->addr, gpio->out);
 | |
| +
 | |
| +	mutex_unlock(&adap->bus_lock);
 | |
| +
 | |
| +	return ret;
 | |
| +}
 | |
| +
 | |
| +static int gw_i2c_pld_get8(struct gpio_chip *chip, unsigned offset)
 | |
| +{
 | |
| +	int ret;
 | |
| +	s32	value;
 | |
| +	struct gw_i2c_pld *gpio = container_of(chip, struct gw_i2c_pld, chip);
 | |
| +	struct i2c_adapter *adap = gpio->client->adapter;
 | |
| +	
 | |
| +	if (in_atomic() || irqs_disabled()) {
 | |
| +		ret = mutex_trylock(&adap->bus_lock);
 | |
| +		if (!ret)
 | |
| +			/* I2C activity is ongoing. */
 | |
| +			return -EAGAIN;
 | |
| +	} else {
 | |
| +		mutex_lock_nested(&adap->bus_lock, adap->level);
 | |
| +	}
 | |
| +
 | |
| +	value = i2c_pld_read_byte(gpio->client->addr);
 | |
| +
 | |
| +	mutex_unlock(&adap->bus_lock);
 | |
| +
 | |
| +	return (value < 0) ? 0 : (value & (1 << offset));
 | |
| +}
 | |
| +
 | |
| +static int gw_i2c_pld_output8(struct gpio_chip *chip, unsigned offset, int value)
 | |
| +{
 | |
| +	int ret;
 | |
| +
 | |
| +	struct gw_i2c_pld *gpio = container_of(chip, struct gw_i2c_pld, chip);
 | |
| +	struct i2c_adapter *adap = gpio->client->adapter;
 | |
| +
 | |
| +	unsigned bit = 1 << offset;
 | |
| +	
 | |
| +	if (in_atomic() || irqs_disabled()) {
 | |
| +		ret = mutex_trylock(&adap->bus_lock);
 | |
| +		if (!ret)
 | |
| +			/* I2C activity is ongoing. */
 | |
| +			return -EAGAIN;
 | |
| +	} else {
 | |
| +		mutex_lock_nested(&adap->bus_lock, adap->level);
 | |
| +	}
 | |
| +
 | |
| +
 | |
| +	if (value)
 | |
| +		gpio->out |= bit;
 | |
| +	else
 | |
| +		gpio->out &= ~bit;
 | |
| +
 | |
| +	ret = i2c_pld_write_byte(gpio->client->addr, gpio->out);
 | |
| +
 | |
| +	mutex_unlock(&adap->bus_lock);
 | |
| +
 | |
| +	return ret;
 | |
| +}
 | |
| +
 | |
| +static void gw_i2c_pld_set8(struct gpio_chip *chip, unsigned offset, int value)
 | |
| +{
 | |
| +	gw_i2c_pld_output8(chip, offset, value);
 | |
| +}
 | |
| +
 | |
| +/*-------------------------------------------------------------------------*/
 | |
| +
 | |
| +static int gw_i2c_pld_probe(struct i2c_client *client,
 | |
| +			 const struct i2c_device_id *id)
 | |
| +{
 | |
| +	struct gw_i2c_pld_platform_data	*pdata;
 | |
| +	struct gw_i2c_pld *gpio;
 | |
| +	int status;
 | |
| +
 | |
| +	pdata = client->dev.platform_data;
 | |
| +	if (!pdata)
 | |
| +		return -ENODEV;
 | |
| +
 | |
| +	/* Allocate, initialize, and register this gpio_chip. */
 | |
| +	gpio = kzalloc(sizeof *gpio, GFP_KERNEL);
 | |
| +	if (!gpio)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	gpio->chip.base = pdata->gpio_base;
 | |
| +	gpio->chip.can_sleep = 1;
 | |
| +	gpio->chip.dev = &client->dev;
 | |
| +	gpio->chip.owner = THIS_MODULE;
 | |
| +
 | |
| +	gpio->chip.ngpio = pdata->nr_gpio;
 | |
| +	gpio->chip.direction_input = gw_i2c_pld_input8;
 | |
| +	gpio->chip.get = gw_i2c_pld_get8;
 | |
| +	gpio->chip.direction_output = gw_i2c_pld_output8;
 | |
| +	gpio->chip.set = gw_i2c_pld_set8;
 | |
| +
 | |
| +	gpio->chip.label = client->name;
 | |
| +
 | |
| +	gpio->client = client;
 | |
| +	i2c_set_clientdata(client, gpio);
 | |
| +
 | |
| +	gpio->out = 0xFF;
 | |
| +
 | |
| +	status = gpiochip_add(&gpio->chip);
 | |
| +	if (status < 0)
 | |
| +		goto fail;
 | |
| +
 | |
| +	dev_info(&client->dev, "gpios %d..%d on a %s%s\n",
 | |
| +			gpio->chip.base,
 | |
| +			gpio->chip.base + gpio->chip.ngpio - 1,
 | |
| +			client->name,
 | |
| +			client->irq ? " (irq ignored)" : "");
 | |
| +
 | |
| +	/* Let platform code set up the GPIOs and their users.
 | |
| +	 * Now is the first time anyone could use them.
 | |
| +	 */
 | |
| +	if (pdata->setup) {
 | |
| +		status = pdata->setup(client,
 | |
| +				gpio->chip.base, gpio->chip.ngpio,
 | |
| +				pdata->context);
 | |
| +		if (status < 0)
 | |
| +			dev_warn(&client->dev, "setup --> %d\n", status);
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +
 | |
| +fail:
 | |
| +	dev_dbg(&client->dev, "probe error %d for '%s'\n",
 | |
| +			status, client->name);
 | |
| +	kfree(gpio);
 | |
| +	return status;
 | |
| +}
 | |
| +
 | |
| +static int gw_i2c_pld_remove(struct i2c_client *client)
 | |
| +{
 | |
| +	struct gw_i2c_pld_platform_data	*pdata = client->dev.platform_data;
 | |
| +	struct gw_i2c_pld *gpio = i2c_get_clientdata(client);
 | |
| +	int				status = 0;
 | |
| +
 | |
| +	if (pdata->teardown) {
 | |
| +		status = pdata->teardown(client,
 | |
| +				gpio->chip.base, gpio->chip.ngpio,
 | |
| +				pdata->context);
 | |
| +		if (status < 0) {
 | |
| +			dev_err(&client->dev, "%s --> %d\n",
 | |
| +					"teardown", status);
 | |
| +			return status;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	status = gpiochip_remove(&gpio->chip);
 | |
| +	if (status == 0)
 | |
| +		kfree(gpio);
 | |
| +	else
 | |
| +		dev_err(&client->dev, "%s --> %d\n", "remove", status);
 | |
| +	return status;
 | |
| +}
 | |
| +
 | |
| +static struct i2c_driver gw_i2c_pld_driver = {
 | |
| +	.driver = {
 | |
| +		.name	= "gw_i2c_pld",
 | |
| +		.owner	= THIS_MODULE,
 | |
| +	},
 | |
| +	.probe	= gw_i2c_pld_probe,
 | |
| +	.remove	= gw_i2c_pld_remove,
 | |
| +	.id_table = gw_i2c_pld_id,
 | |
| +};
 | |
| +
 | |
| +static int __init gw_i2c_pld_init(void)
 | |
| +{
 | |
| +	return i2c_add_driver(&gw_i2c_pld_driver);
 | |
| +}
 | |
| +module_init(gw_i2c_pld_init);
 | |
| +
 | |
| +static void __exit gw_i2c_pld_exit(void)
 | |
| +{
 | |
| +	i2c_del_driver(&gw_i2c_pld_driver);
 | |
| +}
 | |
| +module_exit(gw_i2c_pld_exit);
 | |
| +
 | |
| +MODULE_LICENSE("GPL");
 | |
| +MODULE_AUTHOR("Chris Lang");
 | |
| --- a/drivers/gpio/Kconfig
 | |
| +++ b/drivers/gpio/Kconfig
 | |
| @@ -160,6 +160,14 @@ config GPIO_BT8XX
 | |
|  
 | |
|  	  If unsure, say N.
 | |
|  
 | |
| +config GPIO_GW_I2C_PLD
 | |
| +	tristate "Gateworks I2C PLD GPIO Expander"
 | |
| +	depends on I2C
 | |
| +	help
 | |
| +		Say yes here to provide access to the Gateworks I2C PLD GPIO
 | |
| +		Expander. This is used at least on the GW2358-4.
 | |
| +
 | |
| +
 | |
|  comment "SPI GPIO expanders:"
 | |
|  
 | |
|  config GPIO_MAX7301
 | |
| --- a/drivers/gpio/Makefile
 | |
| +++ b/drivers/gpio/Makefile
 | |
| @@ -12,3 +12,4 @@ obj-$(CONFIG_GPIO_PCF857X)	+= pcf857x.o
 | |
|  obj-$(CONFIG_GPIO_TWL4030)	+= twl4030-gpio.o
 | |
|  obj-$(CONFIG_GPIO_XILINX)	+= xilinx_gpio.o
 | |
|  obj-$(CONFIG_GPIO_BT8XX)	+= bt8xxgpio.o
 | |
| +obj-$(CONFIG_GPIO_GW_I2C_PLD)	+= gw_i2c_pld.o
 | |
| --- /dev/null
 | |
| +++ b/include/linux/i2c/gw_i2c_pld.h
 | |
| @@ -0,0 +1,20 @@
 | |
| +#ifndef __LINUX_GW_I2C_PLD_H
 | |
| +#define __LINUX_GW_I2C_PLD_H
 | |
| +
 | |
| +/**
 | |
| + * The Gateworks I2C PLD Implements an additional 8 bits of GPIO through the PLD
 | |
| + */
 | |
| +
 | |
| +struct gw_i2c_pld_platform_data {
 | |
| +	unsigned gpio_base;
 | |
| +	unsigned nr_gpio;
 | |
| +	int		(*setup)(struct i2c_client *client,
 | |
| +					int gpio, unsigned ngpio,
 | |
| +					void *context);
 | |
| +	int		(*teardown)(struct i2c_client *client,
 | |
| +					int gpio, unsigned ngpio,
 | |
| +					void *context);
 | |
| +	void		*context;
 | |
| +};
 | |
| +
 | |
| +#endif /* __LINUX_GW_I2C_PLD_H */
 |