mirror of https://github.com/hak5/openwrt-owl.git
add support for the GPIO expander used on the Cambria based on patches from Chris
SVN-Revision: 14244owl
parent
2a88ccfa08
commit
2acca60fd7
|
@ -0,0 +1,420 @@
|
|||
--- /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 */
|
|
@ -0,0 +1,107 @@
|
|||
--- a/arch/arm/mach-ixp4xx/cambria-setup.c
|
||||
+++ b/arch/arm/mach-ixp4xx/cambria-setup.c
|
||||
@@ -12,11 +12,14 @@
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
+#include <linux/gpio_buttons.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-gpio.h>
|
||||
#include <linux/i2c/at24.h>
|
||||
+#include <linux/i2c/gw_i2c_pld.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/init.h>
|
||||
+#include <linux/input.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/memory.h>
|
||||
@@ -323,6 +326,39 @@ static struct platform_device cambria_us
|
||||
},
|
||||
};
|
||||
|
||||
+static struct gw_i2c_pld_platform_data gw_i2c_pld_data0 = {
|
||||
+ .gpio_base = 16,
|
||||
+ .nr_gpio = 8,
|
||||
+};
|
||||
+
|
||||
+static struct gw_i2c_pld_platform_data gw_i2c_pld_data1 = {
|
||||
+ .gpio_base = 24,
|
||||
+ .nr_gpio = 2,
|
||||
+};
|
||||
+
|
||||
+
|
||||
+static struct gpio_button cambria_gpio_buttons[] = {
|
||||
+ {
|
||||
+ .desc = "user",
|
||||
+ .type = EV_KEY,
|
||||
+ .code = BTN_0,
|
||||
+ .threshold = 2,
|
||||
+ .gpio = 25,
|
||||
+ }
|
||||
+};
|
||||
+
|
||||
+static struct gpio_buttons_platform_data cambria_gpio_buttons_data = {
|
||||
+ .poll_interval = 500,
|
||||
+ .nbuttons = 1,
|
||||
+ .buttons = cambria_gpio_buttons,
|
||||
+};
|
||||
+
|
||||
+static struct platform_device cambria_gpio_buttons_device = {
|
||||
+ .name = "gpio-buttons",
|
||||
+ .id = -1,
|
||||
+ .dev.platform_data = &cambria_gpio_buttons_data,
|
||||
+};
|
||||
+
|
||||
static struct platform_device *cambria_devices[] __initdata = {
|
||||
&cambria_i2c_gpio,
|
||||
&cambria_flash,
|
||||
@@ -331,6 +367,11 @@ static struct platform_device *cambria_d
|
||||
|
||||
static void __init cambria_gw23xx_setup(void)
|
||||
{
|
||||
+ cambria_gpio_resources[0].start = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) |\
|
||||
+ (1 << 5) | (1 << 8) | (1 << 9) | (1 << 12);
|
||||
+ cambria_gpio_resources[0].end = cambria_gpio_resources[0].start;
|
||||
+
|
||||
+ platform_device_register(&cambria_gpio);
|
||||
platform_device_register(&cambria_npec_device);
|
||||
platform_device_register(&cambria_npea_device);
|
||||
}
|
||||
@@ -377,7 +418,8 @@ static void __init cambria_gw2358_setup(
|
||||
cambria_optional_uart_data[1].membase = (void __iomem *)ioremap(0x53F80000, 0x0fff);
|
||||
cambria_optional_uart_data[1].irq = IRQ_IXP4XX_GPIO4;
|
||||
|
||||
- cambria_gpio_resources[0].start = (1 << 14);
|
||||
+ cambria_gpio_resources[0].start = (1 << 14) | (1 << 16) | (1 << 17) | (1 << 18) |\
|
||||
+ (1 << 19) | (1 << 20) | (1 << 24) | (1 << 25);
|
||||
cambria_gpio_resources[0].end = cambria_gpio_resources[0].start;
|
||||
|
||||
platform_device_register(&cambria_gpio);
|
||||
@@ -391,7 +433,12 @@ static void __init cambria_gw2358_setup(
|
||||
|
||||
platform_device_register(&cambria_pata);
|
||||
|
||||
+ cambria_gpio_leds[0].gpio = 24;
|
||||
+ platform_device_register(&cambria_gpio_leds_device);
|
||||
+
|
||||
platform_device_register(&cambria_latch_leds_device);
|
||||
+
|
||||
+ platform_device_register(&cambria_gpio_buttons_device);
|
||||
}
|
||||
|
||||
static struct cambria_board_info cambria_boards[] __initdata = {
|
||||
@@ -460,6 +507,14 @@ static struct i2c_board_info __initdata
|
||||
I2C_BOARD_INFO("24c08", 0x51),
|
||||
.platform_data = &cambria_eeprom_info
|
||||
},
|
||||
+ {
|
||||
+ I2C_BOARD_INFO("gw_i2c_pld", 0x56),
|
||||
+ .platform_data = &gw_i2c_pld_data0,
|
||||
+ },
|
||||
+ {
|
||||
+ I2C_BOARD_INFO("gw_i2c_pld", 0x57),
|
||||
+ .platform_data = &gw_i2c_pld_data1,
|
||||
+ },
|
||||
};
|
||||
|
||||
static void __init cambria_init(void)
|
Loading…
Reference in New Issue