i2c-htcpld: Add the HTCPLD driver
Subsystems (2)
PATCH v3 1/1 i2c-htcpld: Add the HTCPLD driver
This change introduces a driver for the HTC PLD chip found on some smartphones, such as the HTC Wizard and HTC Herald. It works through the I2C bus and acts as a GPIO extender. Specifically:
* it can have several sub-devices, each with its own I2C address
* Each sub-device provides 8 output and 8 input pins
* The chip attaches to one GPIO to signal when any of the input GPIOs change -- at which point all chips must be scanned for changes
This driver implements the GPIOs throught the kernel's GPIO and IRQ framework. This allows any GPIO-servicing drivers to operate on htcpld pins, such as the gpio-keys and gpio-leds drivers.
--- drivers/mfd/Kconfig | 9 + drivers/mfd/Makefile | 1 + drivers/mfd/htc-i2cpld.c | 591 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/htcpld.h | 24 ++ 4 files changed, 625 insertions(+), 0 deletions(-) create mode 100644 drivers/mfd/htc-i2cpld.c create mode 100644 include/linux/htcpld.h
drivers/mfd/Kconfig +9 -0
@@ -60,6 +60,15 @@ config HTC_PASIC3 HTC Magician devices, respectively. Actual functionality is handled by the leds-pasic3 and ds1wm drivers. +config HTC_I2CPLD + bool "HTC I2C PLD chip support" + depends on I2C + help + If you say yes here you get support for the supposed CPLD + found on omap850 HTC devices like the HTC Wizard and HTC Herald. + This device provides input and output GPIOs through an I2C + interface to one or more sub-chips. + config UCB1400_CORE tristate "Philips UCB1400 Core driver" depends on AC97_BUS
drivers/mfd/Makefile +1 -0
@@ -7,6 +7,7 @@ obj-$(CONFIG_MFD_ASIC3) += asic3.o obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o +obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o
drivers/mfd/htc-i2cpld.c new file +591 -0
591 lines not on this page · raw message
include/linux/htcpld.h new file +24 -0
@@ -0,0 +1,24 @@ +#ifndef __LINUX_HTCPLD_H +#define __LINUX_HTCPLD_H + +struct htcpld_chip_platform_data { +unsigned int addr; +unsigned int reset; +unsigned int num_gpios; +unsigned int gpio_out_base; +unsigned int gpio_in_base; +unsigned int irq_base; +unsigned int num_irqs; +}; + +struct htcpld_core_platform_data { +unsigned int int_reset_gpio_hi; +unsigned int int_reset_gpio_lo; +unsigned int i2c_adapter_id; + +struct htcpld_chip_platform_data *chip; +unsigned int num_chip; +}; + +#endif /* __LINUX_HTCPLD_H */ +
Signature · 1 line
1.6.3.3
A message that is not in the archive (sent off-list or not yet received): 1260841135-6680-1-git-send-email-darkstar6262-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org
Hi Cory,
late review, sorry about that...
Quoted 12 lines · Cory Maccarrone
This change introduces a driver for the HTC PLD chip found on some smartphones, such as the HTC Wizard and HTC Herald. It works through the I2C bus and acts as a GPIO extender. Specifically:
* it can have several sub-devices, each with its own I2C address
* Each sub-device provides 8 output and 8 input pins
* The chip attaches to one GPIO to signal when any of the input GPIOs change -- at which point all chips must be scanned for changes
Quoted 4 lines
This driver implements the GPIOs throught the kernel's GPIO and IRQ framework. This allows any GPIO-servicing drivers to operate on htcpld pins, such as the gpio-keys and gpio-leds drivers.
The driver looks fine to me, but I get 23 checkpatch warnings and 5 errors for it.
Could you please fix them, keeping in mind that I'm fine with printk/dev_* strings being more than 80 chars.
A couple of additional comments:
drivers/mfd/htc-i2cpld.c line 3
Quoted 9 lines
drivers/mfd/htc-i2cpld.c new file +3
-0@@ -0,0 +1,591 @@ +/* +* htc-i2cpld.c +* Chip driver for a ?cpld? found on omap850 HTC devices like the
?cpld? ??
drivers/mfd/htc-i2cpld.c line 135
Quoted 10 lines
drivers/mfd/htc-i2cpld.c new file +10
-0+static irqreturn_t htcpld_handler (int irq,void *dev) +{ +struct htcpld_data *htcpld = dev; +unsigned int i; +unsigned long flags; +int irqpin; +struct irq_desc *desc; + +if (!htcpld) { +printk (KERN_INFO "htcpld is null\n" );
Please use pr_* instead. It seems you're using it already, so let's be consistent.
drivers/mfd/htc-i2cpld.c line 325
Quoted 2 lines
drivers/mfd/htc-i2cpld.c new file +2
-0+static int __devinit htcpld_core_probe (struct platform_device *pdev) +{
This routine is fairly big, and could be more readable by calling some subroutines. The chips initialisation part could be extracted out for example.
Quoted 40 lines
drivers/mfd/htc-i2cpld.c new file +40
-0+struct htcpld_data *htcpld; +struct device *dev = &pdev->dev; +struct htcpld_core_platform_data *pdata; +struct resource *res; +int i, ret =0 ; +unsigned int irq, irq_end; + +if (!dev) +return -ENODEV ; + + pdata = dev->platform_data; +if (!pdata) { +printk (KERN_ERR "Platform data not found for htcpld core!\n" ); +return -ENXIO ; + } + + htcpld =kzalloc (sizeof (struct htcpld_data ),GFP_KERNEL ); +if (!htcpld) +return -ENOMEM ; + +/* Find chained irq */ + ret = -EINVAL ; + res =platform_get_resource (pdev,IORESOURCE_IRQ ,0 ); +if (res) + htcpld->chained_irq = res->start; + +platform_set_drvdata (pdev, htcpld); + + htcpld->int_reset_gpio_hi = pdata->int_reset_gpio_hi; + htcpld->int_reset_gpio_lo = pdata->int_reset_gpio_lo; + +/* Setup each chip's output GPIOs */ + htcpld->nchips = pdata->num_chip; + htcpld->chip =kzalloc (sizeof (struct htcpld_chip ) * htcpld->nchips,GFP_KERNEL ); +if (!htcpld->chip) { + ret = -ENOMEM ; + goto fail; + } + +for (i =0 ; i < htcpld->nchips; i++) {
drivers/mfd/htc-i2cpld.c line 375
Quoted 10 lines
drivers/mfd/htc-i2cpld.c new file +10
-0+struct i2c_adapter *adapter; +struct i2c_client *client; +struct i2c_board_info info; +struct gpio_chip *chip; +int ret; + +/* Setup the HTCPLD chips */ + htcpld->chip[i].reset = pdata->chip[i].reset; + htcpld->chip[i].cache_out = pdata->chip[i].reset; + htcpld->chip[1 ].cache_in =0 ;
Shouldnt it be: htcpld->chip[i].cache_in = 0; instead ?
Quoted 18 lines
drivers/mfd/htc-i2cpld.c new file +18
-0+ htcpld->chip[i].dev = dev; + htcpld->chip[i].irq_start = pdata->chip[i].irq_base; + htcpld->chip[i].nirqs = pdata->chip[i].num_irqs; + +INIT_WORK (&(htcpld->chip[i].set_val_work), &htcpld_chip_set_ni); +spin_lock_init (&(htcpld->chip[i].lock)); + +/* Setup the IRQs */ +if (htcpld->chained_irq) { +int error =0 , flags; + +/* Setup irq handlers */ + irq_end = htcpld->chip[i].irq_start + htcpld->chip[i].nirqs; +for (irq = htcpld->chip[i].irq_start; irq < irq_end; irq++) { +set_irq_chip (irq, &htcpld_muxed_chip); +set_irq_chip_data (irq, &htcpld->chip[i]); +set_irq_handler (irq, handle_simple_irq); +set_irq_flags (irq,IRQF_VALID |IRQF_PROBE );
drivers/mfd/htc-i2cpld.c line 403
Quoted 10 lines
drivers/mfd/htc-i2cpld.c new file +10
-0+ } + + flags =IRQF_TRIGGER_FALLING |IRQF_TRIGGER_RISING |IRQF_SAMPLE_RANDOM ; + error =request_threaded_irq ( + htcpld->chained_irq, +NULL , + htcpld_handler, + flags, + pdev->name, + htcpld);
You could have a nicer indentation here:
error = request_threaded_irq(htcpld->chained_irq, NULL, htcpld_handler, flags, pdev->name, htcpld);
Cheers,
Samuel.
Signature · 2 lines
Intel Open Source Technology Centre http://oss.intel.com/
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 9 lines · Samuel Ortiz
Hi Cory,
late review, sorry about that...
The driver looks fine to me, but I get 23 checkpatch warnings and 5 errors for it.
Could you please fix them, keeping in mind that I'm fine with printk/dev_* strings being more than 80 chars.
Yup, no problem.
Quoted 3 lines
A couple of additional comments:
?cpld? ??
That should probably be rephrased to say "unknown model CPLD". I'll fix that.
Quoted 2 lines
Please use pr_* instead. It seems you're using it already, so let's be consistent.
Right.
Quoted 2 lines
This routine is fairly big, and could be more readable by calling some subroutines. The chips initialisation part could be extracted out for example.
OK.
Quoted 1 line
Shouldnt it be: htcpld->chip[i].cache_in = 0; instead ?
Yes, good catch.
Quoted 5 lines
You could have a nicer indentation here:
error =request_threaded_irq(htcpld->chained_irq, NULL, htcpld_handler, flags, pdev->name, htcpld);
OK.
I'll make those fixes and resubmit. Thanks for reviewing it!
- Cory
A message that is not in the archive (sent off-list or not yet received): 6cb013311001060647s3bce5e63mbae1a10036a14ae1-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org
PATCH v3 1/1 i2c-htcpld: Add the HTCPLD driver
This change introduces a driver for the HTC PLD chip found on some smartphones, such as the HTC Wizard and HTC Herald. It works through the I2C bus and acts as a GPIO extender. Specifically:
* it can have several sub-devices, each with its own I2C address
* Each sub-device provides 8 output and 8 input pins
* The chip attaches to one GPIO to signal when any of the input GPIOs change -- at which point all chips must be scanned for changes
This driver implements the GPIOs throught the kernel's GPIO and IRQ framework. This allows any GPIO-servicing drivers to operate on htcpld pins, such as the gpio-keys and gpio-leds drivers.
--- drivers/mfd/Kconfig | 9 + drivers/mfd/Makefile | 1 + drivers/mfd/htc-i2cpld.c | 698 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/htcpld.h | 24 ++ 4 files changed, 732 insertions(+), 0 deletions(-) create mode 100644 drivers/mfd/htc-i2cpld.c create mode 100644 include/linux/htcpld.h
Differences from v2 to v3 include various cleanups, including splitting out the various parts of the probe() function into subfunctions for readability and fixing checkpatch errors.
drivers/mfd/Kconfig +9 -0
@@ -68,6 +68,15 @@ config HTC_PASIC3 HTC Magician devices, respectively. Actual functionality is handled by the leds-pasic3 and ds1wm drivers. +config HTC_I2CPLD + bool "HTC I2C PLD chip support" + depends on I2C + help + If you say yes here you get support for the supposed CPLD + found on omap850 HTC devices like the HTC Wizard and HTC Herald. + This device provides input and output GPIOs through an I2C + interface to one or more sub-chips. + config UCB1400_CORE tristate "Philips UCB1400 Core driver" depends on AC97_BUS
drivers/mfd/Makefile +1 -0
@@ -8,6 +8,7 @@ obj-$(CONFIG_MFD_SH_MOBILE_SDHI) += sh_mobile_sdhi.o obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o +obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o
drivers/mfd/htc-i2cpld.c new file +698 -0
698 lines not on this page · raw message
include/linux/htcpld.h new file +24 -0
@@ -0,0 +1,24 @@ +#ifndef __LINUX_HTCPLD_H +#define __LINUX_HTCPLD_H + +struct htcpld_chip_platform_data { +unsigned int addr; +unsigned int reset; +unsigned int num_gpios; +unsigned int gpio_out_base; +unsigned int gpio_in_base; +unsigned int irq_base; +unsigned int num_irqs; +}; + +struct htcpld_core_platform_data { +unsigned int int_reset_gpio_hi; +unsigned int int_reset_gpio_lo; +unsigned int i2c_adapter_id; + +struct htcpld_chip_platform_data *chip; +unsigned int num_chip; +}; + +#endif /* __LINUX_HTCPLD_H */ +
Signature · 1 line
1.6.3.3
A message that is not in the archive (sent off-list or not yet received): 1263056154-20085-1-git-send-email-darkstar6262-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org
Samuel Ortiz
AppliedHi Cory,
Quoted 12 lines · Cory Maccarrone
This change introduces a driver for the HTC PLD chip found on some smartphones, such as the HTC Wizard and HTC Herald. It works through the I2C bus and acts as a GPIO extender. Specifically:
* it can have several sub-devices, each with its own I2C address
* Each sub-device provides 8 output and 8 input pins
* The chip attaches to one GPIO to signal when any of the input GPIOs change -- at which point all chips must be scanned for changes
Quoted 4 lines
This driver implements the GPIOs throught the kernel's GPIO and IRQ framework. This allows any GPIO-servicing drivers to operate on htcpld pins, such as the gpio-keys and gpio-leds drivers.
I applied this patch, thanks a lot.
In the future, I'd like to see the GPIO handling from this patch moved to drivers/gpio. Could you please look at that ?
Cheers,
Samuel.
Quoted 783 lines
Signed-off-by Cory Maccarrone <[email protected]>--- drivers/mfd/Kconfig | 9 + drivers/mfd/Makefile | 1 + drivers/mfd/htc-i2cpld.c | 698 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/htcpld.h | 24 ++ 4 files changed, 732 insertions(+), 0 deletions(-) create mode 100644 drivers/mfd/htc-i2cpld.c create mode 100644 include/linux/htcpld.hDifferences from v2 to v3 include various cleanups, including splitting out the various parts of the
probe()function into subfunctions for readability and fixing checkpatch errors.drivers/mfd/Kconfig +9
-0@@ -68,6 +68,15 @@ config HTC_PASIC3 HTC Magician devices, respectively. Actual functionality is handled by the leds-pasic3 and ds1wm drivers. +config HTC_I2CPLD + bool "HTC I2C PLD chip support" + depends on I2C + help + If you say yes here you get support for the supposed CPLD + found on omap850 HTC devices like the HTC Wizard and HTC Herald. + This device provides input and output GPIOs through an I2C + interface to one or more sub-chips. + config UCB1400_CORE tristate "Philips UCB1400 Core driver" depends on AC97_BUSdrivers/mfd/Makefile +1
-0@@ -8,6 +8,7 @@ obj-$(CONFIG_MFD_SH_MOBILE_SDHI) += sh_mobile_sdhi.o obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o +obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.odrivers/mfd/htc-i2cpld.c new file +698
-0@@ -0,0 +1,698 @@ +/* +* htc-i2cpld.c +* Chip driver for an unknown CPLD chip found on omap850 HTC devices like +* the HTC Wizard and HTC Herald. +* The cpld is located on the i2c bus and acts as an input/output GPIO +* extender. +* +* Copyright (C) 2009 Cory Maccarrone <[email protected]> +* +* Based on work done in the linwizard project +* Copyright (C) 2008-2009 Angelo Arrifano <[email protected]> +* +* 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/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/spinlock.h> +#include <linux/htcpld.h> +#include <linux/gpio.h> + +struct htcpld_chip { +spinlock_t lock; + +/* chip info */ +u8 reset; +u8 addr; +struct device *dev; +struct i2c_client *client; + +/* Output details */ +u8 cache_out; +struct gpio_chip chip_out; + +/* Input details */ +u8 cache_in; +struct gpio_chip chip_in; + +u16 irqs_enabled; +uint irq_start; +int nirqs; + +/* +* Work structure to allow for setting values outside of any +* possible interrupt context +*/ +struct work_struct set_val_work; +}; + +struct htcpld_data { +/* irq info */ +u16 irqs_enabled; +uint irq_start; +int nirqs; +uint chained_irq; +unsigned int int_reset_gpio_hi; +unsigned int int_reset_gpio_lo; + +/* htcpld info */ +struct htcpld_chip *chip; +unsigned int nchips; +}; + +/* There does not appear to be a way to proactively mask interrupts +* on the htcpld chip itself. So, we simply ignore interrupts that +* aren't desired. */ +static void htcpld_mask (unsigned int irq) +{ +struct htcpld_chip *chip =get_irq_chip_data (irq); + chip->irqs_enabled &= ~(1 << (irq - chip->irq_start)); +pr_debug ("HTCPLD mask %d %04x\n" , irq, chip->irqs_enabled); +} +static void htcpld_unmask (unsigned int irq) +{ +struct htcpld_chip *chip =get_irq_chip_data (irq); + chip->irqs_enabled |=1 << (irq - chip->irq_start); +pr_debug ("HTCPLD unmask %d %04x\n" , irq, chip->irqs_enabled); +} + +static int htcpld_set_type (unsigned int irq,unsigned int flags) +{ +if (flags & ~IRQ_TYPE_SENSE_MASK ) +return -EINVAL ; + +/* We only allow edge triggering */ +if (flags & (IRQ_TYPE_LEVEL_LOW |IRQ_TYPE_LEVEL_HIGH )) +return -EINVAL ; + + irq_desc[irq].status &= ~IRQ_TYPE_SENSE_MASK ; + irq_desc[irq].status |= flags; + +return 0 ; +} + +static struct irq_chip htcpld_muxed_chip = { + .name ="htcpld" , + .mask = htcpld_mask, + .unmask = htcpld_unmask, + .set_type = htcpld_set_type, +}; + +/* To properly dispatch IRQ events, we need to read from the +* chip. This is an I2C action that could possibly sleep +* (which is bad in interrupt context) -- so we use a threaded +* interrupt handler to get around that. +*/ +static irqreturn_t htcpld_handler (int irq,void *dev) +{ +struct htcpld_data *htcpld = dev; +unsigned int i; +unsigned long flags; +int irqpin; +struct irq_desc *desc; + +if (!htcpld) { +pr_debug ("htcpld is null in ISR\n" ); +return IRQ_HANDLED ; + } + +/* +* For each chip, do a read of the chip and trigger any interrupts +* desired. The interrupts will be triggered from LSB to MSB (i.e. +* bit 0 first, then bit 1, etc.) +* +* For chips that have no interrupt range specified, just skip 'em. +*/ +for (i =0 ; i < htcpld->nchips; i++) { +struct htcpld_chip *chip = &htcpld->chip[i]; +struct i2c_client *client; +int val; +unsigned long uval, old_val; + +if (!chip) { +pr_debug ("chip %d is null in ISR\n" , i); +continue ; + } + +if (chip->nirqs ==0 ) +continue ; + + client = chip->client; +if (!client) { +pr_debug ("client %d is null in ISR\n" , i); +continue ; + } + +/* Scan the chip */ + val =i2c_smbus_read_byte_data (client, chip->cache_out); +if (val <0 ) { +/* Throw a warning and skip this chip */ +dev_warn (chip->dev,"Unable to read from chip: %d\n" , + val); +continue ; + } + + uval = (unsigned long )val; + +spin_lock_irqsave (&chip->lock, flags); + +/* Save away the old value so we can compare it */ + old_val = chip->cache_in; + +/* Write the new value */ + chip->cache_in = uval; + +spin_unlock_irqrestore (&chip->lock, flags); + +/* +* For each bit in the data (starting at bit 0), trigger +* associated interrupts. +*/ +for (irqpin =0 ; irqpin < chip->nirqs; irqpin++) { +unsigned oldb, newb; +int flags; + + irq = chip->irq_start + irqpin; + desc =irq_to_desc (irq); + flags = desc->status; + +/* Run the IRQ handler, but only if the bit value +* changed, and the proper flags are set */ + oldb = (old_val >> irqpin) &1 ; + newb = (uval >> irqpin) &1 ; + +if ((!oldb && newb && (flags &IRQ_TYPE_EDGE_RISING )) || + (oldb && !newb && (flags &IRQ_TYPE_EDGE_FALLING ))) { +pr_debug ("fire IRQ %d\n" , irqpin); + desc->handle_irq (irq, desc); + } + } + } + +/* +* In order to continue receiving interrupts, the int_reset_gpio must +* be asserted. +*/ +if (htcpld->int_reset_gpio_hi) +gpio_set_value (htcpld->int_reset_gpio_hi,1 ); +if (htcpld->int_reset_gpio_lo) +gpio_set_value (htcpld->int_reset_gpio_lo,0 ); + +return IRQ_HANDLED ; +} + +/* +* The GPIO set routines can be called from interrupt context, especially if, +* for example they're attached to the led-gpio framework and a trigger is +* enabled. As such, we declared work above in the htcpld_chip structure, +* and that work is scheduled in the set routine. The kernel can then run +* the I2C functions, which will sleep, in process context. +*/ +void htcpld_chip_set (struct gpio_chip *chip,unsigned offset,int val) +{ +struct i2c_client *client; +struct htcpld_chip *chip_data; +unsigned long flags; + + chip_data = container_of(chip, struct htcpld_chip, chip_out); +if (!chip_data) +return ; + + client = chip_data->client; +if (client ==NULL ) +return ; + +spin_lock_irqsave (&chip_data->lock, flags); +if (val) + chip_data->cache_out |= (1 << offset); +else + chip_data->cache_out &= ~(1 << offset); +spin_unlock_irqrestore (&chip_data->lock, flags); + +schedule_work (&(chip_data->set_val_work)); +} + +void htcpld_chip_set_ni (struct work_struct *work) +{ +struct htcpld_chip *chip_data; +struct i2c_client *client; + + chip_data = container_of(work, struct htcpld_chip, set_val_work); + client = chip_data->client; +i2c_smbus_read_byte_data (client, chip_data->cache_out); +} + +int htcpld_chip_get (struct gpio_chip *chip,unsigned offset) +{ +struct htcpld_chip *chip_data; +int val =0 ; +int is_input =0 ; + +/* Try out first */ + chip_data = container_of(chip, struct htcpld_chip, chip_out); +if (!chip_data) { +/* Try in */ + is_input =1 ; + chip_data = container_of(chip, struct htcpld_chip, chip_in); +if (!chip_data) +return -EINVAL ; + } + +/* Determine if this is an input or output GPIO */ +if (!is_input) +/* Use the output cache */ + val = (chip_data->cache_out >> offset) &1 ; +else +/* Use the input cache */ + val = (chip_data->cache_in >> offset) &1 ; + +if (val) +return 1 ; +else +return 0 ; +} + +static int htcpld_direction_output (struct gpio_chip *chip, +unsigned offset,int value) +{ +htcpld_chip_set (chip, offset, value); +return 0 ; +} + +static int htcpld_direction_input (struct gpio_chip *chip, +unsigned offset) +{ +/* +* No-op: this function can only be called on the input chip. +* We do however make sure the offset is within range. +*/ +return (offset < chip->ngpio) ?0 : -EINVAL ; +} + +int htcpld_chip_to_irq (struct gpio_chip *chip,unsigned offset) +{ +struct htcpld_chip *chip_data; + + chip_data = container_of(chip, struct htcpld_chip, chip_in); + +if (offset < chip_data->nirqs) +return chip_data->irq_start + offset; +else +return -EINVAL ; +} + +void htcpld_chip_reset (struct i2c_client *client) +{ +struct htcpld_chip *chip_data =i2c_get_clientdata (client); +if (!chip_data) +return ; + +i2c_smbus_read_byte_data ( + client, (chip_data->cache_out = chip_data->reset)); +} + +static int __devinit htcpld_setup_chip_irq ( +struct platform_device *pdev, +int chip_index) +{ +struct htcpld_data *htcpld; +struct device *dev = &pdev->dev; +struct htcpld_core_platform_data *pdata; +struct htcpld_chip *chip; +struct htcpld_chip_platform_data *plat_chip_data; +unsigned int irq, irq_end; +int ret =0 ; + +/* Get the platform and driver data */ + pdata = dev->platform_data; + htcpld =platform_get_drvdata (pdev); + chip = &htcpld->chip[chip_index]; + plat_chip_data = &pdata->chip[chip_index]; + +/* Setup irq handlers */ + irq_end = chip->irq_start + chip->nirqs; +for (irq = chip->irq_start; irq < irq_end; irq++) { +set_irq_chip (irq, &htcpld_muxed_chip); +set_irq_chip_data (irq, chip); +set_irq_handler (irq, handle_simple_irq); +set_irq_flags (irq,IRQF_VALID |IRQF_PROBE ); + } + +return ret; +} + +static int __devinit htcpld_register_chip_i2c ( +struct platform_device *pdev, +int chip_index) +{ +struct htcpld_data *htcpld; +struct device *dev = &pdev->dev; +struct htcpld_core_platform_data *pdata; +struct htcpld_chip *chip; +struct htcpld_chip_platform_data *plat_chip_data; +struct i2c_adapter *adapter; +struct i2c_client *client; +struct i2c_board_info info; + +/* Get the platform and driver data */ + pdata = dev->platform_data; + htcpld =platform_get_drvdata (pdev); + chip = &htcpld->chip[chip_index]; + plat_chip_data = &pdata->chip[chip_index]; + + adapter =i2c_get_adapter (pdata->i2c_adapter_id); +if (adapter ==NULL ) { +/* Eek, no such I2C adapter! Bail out. */ +dev_warn (dev,"Chip at i2c address 0x%x: Invalid i2c adapter %d\n" , + plat_chip_data->addr, pdata->i2c_adapter_id); +return -ENODEV ; + } + +if (!i2c_check_functionality (adapter,I2C_FUNC_SMBUS_READ_BYTE_DATA )) { +dev_warn (dev,"i2c adapter %d non-functional\n" , + pdata->i2c_adapter_id); +return -EINVAL ; + } + +memset (&info,0 ,sizeof (struct i2c_board_info )); + info.addr = plat_chip_data->addr; +strlcpy (info.type,"htcpld-chip" ,I2C_NAME_SIZE ); + info.platform_data = chip; + +/* Add the I2C device. This calls the probe() function. */ + client =i2c_new_device (adapter, &info); +if (!client) { +/* I2C device registration failed, contineu with the next */ +dev_warn (dev,"Unable to add I2C device for 0x%x\n" , + plat_chip_data->addr); +return -ENODEV ; + } + +i2c_set_clientdata (client, chip); +snprintf (client->name,I2C_NAME_SIZE ,"Chip_0x%d" , client->addr); + chip->client = client; + +/* Reset the chip */ +htcpld_chip_reset (client); + chip->cache_in =i2c_smbus_read_byte_data (client, chip->cache_out); + +return 0 ; +} + +static void __devinit htcpld_unregister_chip_i2c ( +struct platform_device *pdev, +int chip_index) +{ +struct htcpld_data *htcpld; +struct htcpld_chip *chip; + +/* Get the platform and driver data */ + htcpld =platform_get_drvdata (pdev); + chip = &htcpld->chip[chip_index]; + +if (chip->client) +i2c_unregister_device (chip->client); +} + +static int __devinit htcpld_register_chip_gpio ( +struct platform_device *pdev, +int chip_index) +{ +struct htcpld_data *htcpld; +struct device *dev = &pdev->dev; +struct htcpld_core_platform_data *pdata; +struct htcpld_chip *chip; +struct htcpld_chip_platform_data *plat_chip_data; +struct gpio_chip *gpio_chip; +int ret =0 ; + +/* Get the platform and driver data */ + pdata = dev->platform_data; + htcpld =platform_get_drvdata (pdev); + chip = &htcpld->chip[chip_index]; + plat_chip_data = &pdata->chip[chip_index]; + +/* Setup the GPIO chips */ + gpio_chip = &(chip->chip_out); + gpio_chip->label ="htcpld-out" ; + gpio_chip->dev = dev; + gpio_chip->owner =THIS_MODULE ; + gpio_chip->get = htcpld_chip_get; + gpio_chip->set = htcpld_chip_set; + gpio_chip->direction_input =NULL ; + gpio_chip->direction_output = htcpld_direction_output; + gpio_chip->base = plat_chip_data->gpio_out_base; + gpio_chip->ngpio = plat_chip_data->num_gpios; + + gpio_chip = &(chip->chip_in); + gpio_chip->label ="htcpld-in" ; + gpio_chip->dev = dev; + gpio_chip->owner =THIS_MODULE ; + gpio_chip->get = htcpld_chip_get; + gpio_chip->set =NULL ; + gpio_chip->direction_input = htcpld_direction_input; + gpio_chip->direction_output =NULL ; + gpio_chip->to_irq = htcpld_chip_to_irq; + gpio_chip->base = plat_chip_data->gpio_in_base; + gpio_chip->ngpio = plat_chip_data->num_gpios; + +/* Add the GPIO chips */ + ret =gpiochip_add (&(chip->chip_out)); +if (ret) { +dev_warn (dev,"Unable to register output GPIOs for 0x%x: %d\n" , + plat_chip_data->addr, ret); +return ret; + } + + ret =gpiochip_add (&(chip->chip_in)); +if (ret) { +int error; + +dev_warn (dev,"Unable to register input GPIOs for 0x%x: %d\n" , + plat_chip_data->addr, ret); + + error =gpiochip_remove (&(chip->chip_out)); +if (error) +dev_warn (dev,"Error while trying to unregister gpio chip: %d\n" , error); + +return ret; + } + +return 0 ; +} + +static int __devinit htcpld_setup_chips (struct platform_device *pdev) +{ +struct htcpld_data *htcpld; +struct device *dev = &pdev->dev; +struct htcpld_core_platform_data *pdata; +int i; + +/* Get the platform and driver data */ + pdata = dev->platform_data; + htcpld =platform_get_drvdata (pdev); + +/* Setup each chip's output GPIOs */ + htcpld->nchips = pdata->num_chip; + htcpld->chip =kzalloc (sizeof (struct htcpld_chip ) * htcpld->nchips, +GFP_KERNEL ); +if (!htcpld->chip) { +dev_warn (dev,"Unable to allocate memory for chips\n" ); +return -ENOMEM ; + } + +/* Add the chips as best we can */ +for (i =0 ; i < htcpld->nchips; i++) { +int ret; + +/* Setup the HTCPLD chips */ + htcpld->chip[i].reset = pdata->chip[i].reset; + htcpld->chip[i].cache_out = pdata->chip[i].reset; + htcpld->chip[i].cache_in =0 ; + htcpld->chip[i].dev = dev; + htcpld->chip[i].irq_start = pdata->chip[i].irq_base; + htcpld->chip[i].nirqs = pdata->chip[i].num_irqs; + +INIT_WORK (&(htcpld->chip[i].set_val_work), &htcpld_chip_set_ni); +spin_lock_init (&(htcpld->chip[i].lock)); + +/* Setup the interrupts for the chip */ +if (htcpld->chained_irq) { + ret =htcpld_setup_chip_irq (pdev, i); +if (ret) +continue ; + } + +/* Register the chip with I2C */ + ret =htcpld_register_chip_i2c (pdev, i); +if (ret) +continue ; + + +/* Register the chips with the GPIO subsystem */ + ret =htcpld_register_chip_gpio (pdev, i); +if (ret) { +/* Unregister the chip from i2c and continue */ +htcpld_unregister_chip_i2c (pdev, i); +continue ; + } + +dev_info (dev,"Registered chip at 0x%x\n" , pdata->chip[i].addr); + } + +return 0 ; +} + +static int __devinit htcpld_core_probe (struct platform_device *pdev) +{ +struct htcpld_data *htcpld; +struct device *dev = &pdev->dev; +struct htcpld_core_platform_data *pdata; +struct resource *res; +int ret =0 ; + +if (!dev) +return -ENODEV ; + + pdata = dev->platform_data; +if (!pdata) { +dev_warn (dev,"Platform data not found for htcpld core!\n" ); +return -ENXIO ; + } + + htcpld =kzalloc (sizeof (struct htcpld_data ),GFP_KERNEL ); +if (!htcpld) +return -ENOMEM ; + +/* Find chained irq */ + ret = -EINVAL ; + res =platform_get_resource (pdev,IORESOURCE_IRQ ,0 ); +if (res) { +int flags; + htcpld->chained_irq = res->start; + +/* Setup the chained interrupt handler */ + flags =IRQF_TRIGGER_FALLING |IRQF_TRIGGER_RISING ; + ret =request_threaded_irq (htcpld->chained_irq, +NULL , htcpld_handler, + flags, pdev->name, htcpld); +if (ret) { +dev_warn (dev,"Unable to setup chained irq handler: %d\n" , ret); + goto fail; + }else +device_init_wakeup (dev,0 ); + } + +/* Set the driver data */ +platform_set_drvdata (pdev, htcpld); + +/* Setup the htcpld chips */ + ret =htcpld_setup_chips (pdev); +if (ret) + goto fail; + +/* Request the GPIO(s) for the int reset and set them up */ +if (pdata->int_reset_gpio_hi) { + ret =gpio_request (pdata->int_reset_gpio_hi,"htcpld-core" ); +if (ret) { +/* +* If it failed, that sucks, but we can probably +* continue on without it. +*/ +dev_warn (dev,"Unable to request int_reset_gpio_hi -- interrupts may not work\n" ); + htcpld->int_reset_gpio_hi =0 ; + }else { + htcpld->int_reset_gpio_hi = pdata->int_reset_gpio_hi; +gpio_set_value (htcpld->int_reset_gpio_hi,1 ); + } + } + +if (pdata->int_reset_gpio_lo) { + ret =gpio_request (pdata->int_reset_gpio_lo,"htcpld-core" ); +if (ret) { +/* +* If it failed, that sucks, but we can probably +* continue on without it. +*/ +dev_warn (dev,"Unable to request int_reset_gpio_lo -- interrupts may not work\n" ); + htcpld->int_reset_gpio_lo =0 ; + }else { + htcpld->int_reset_gpio_lo = pdata->int_reset_gpio_lo; +gpio_set_value (htcpld->int_reset_gpio_lo,0 ); + } + } + +dev_info (dev,"Initialized successfully\n" ); +return 0 ; + +fail: +kfree (htcpld); +return ret; +} + +/* The I2C Driver -- used internally */ +static const struct i2c_device_id htcpld_chip_id[] = { + {"htcpld-chip" ,0 }, + { } +}; +MODULE_DEVICE_TABLE (i2c, htcpld_chip_id); + + +static struct i2c_driver htcpld_chip_driver = { + .driver = { + .name ="htcpld-chip" , + }, + .id_table = htcpld_chip_id, +}; + +/* The Core Driver */ +static struct platform_driver htcpld_core_driver = { + .driver = { + .name ="i2c-htcpld" , + }, +}; + +static int __init htcpld_core_init (void ) +{ +int ret; + +/* Register the I2C Chip driver */ + ret =i2c_add_driver (&htcpld_chip_driver); +if (ret) +return ret; + +/* Probe for our chips */ +return platform_driver_probe (&htcpld_core_driver, htcpld_core_probe); +} + +static void __exit htcpld_core_exit(void) +{ +i2c_del_driver (&htcpld_chip_driver); +platform_driver_unregister (&htcpld_core_driver); +} + +module_init (htcpld_core_init); +module_exit (htcpld_core_exit); + +MODULE_AUTHOR ("Cory Maccarrone <[email protected]>" ); +MODULE_DESCRIPTION ("I2C HTC PLD Driver" ); +MODULE_LICENSE ("GPL" ); +include/linux/htcpld.h new file +24
-0@@ -0,0 +1,24 @@ +#ifndef __LINUX_HTCPLD_H +#define __LINUX_HTCPLD_H + +struct htcpld_chip_platform_data { +unsigned int addr; +unsigned int reset; +unsigned int num_gpios; +unsigned int gpio_out_base; +unsigned int gpio_in_base; +unsigned int irq_base; +unsigned int num_irqs; +}; + +struct htcpld_core_platform_data { +unsigned int int_reset_gpio_hi; +unsigned int int_reset_gpio_lo; +unsigned int i2c_adapter_id; + +struct htcpld_chip_platform_data *chip; +unsigned int num_chip; +}; + +#endif /* __LINUX_HTCPLD_H */ +Signature · 1 line
1.6.3.3
Signature · 2 lines
Intel Open Source Technology Centre http://oss.intel.com/
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 15 lines · Samuel Ortiz
Hi Cory,
Quoted 12 lines · Cory Maccarrone
This change introduces a driver for the HTC PLD chip found on some smartphones, such as the HTC Wizard and HTC Herald. It works through the I2C bus and acts as a GPIO extender. Specifically:
* it can have several sub-devices, each with its own I2C address
* Each sub-device provides 8 output and 8 input pins
* The chip attaches to one GPIO to signal when any of the input GPIOs change -- at which point all chips must be scanned for changes
Quoted 7 lines
Quoted 4 lines
This driver implements the GPIOs throught the kernel's GPIO and IRQ framework. This allows any GPIO-servicing drivers to operate on htcpld pins, such as the gpio-keys and gpio-leds drivers.
I applied this patch, thanks a lot.
In the future, I'd like to see the GPIO handling from this patch moved todrivers/gpio. Could you please look at that ?
I forgot to add that I also made it build on non ARM patforms. This line:set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
became:
#ifdef CONFIG_ARM set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); #else set_irq_probe(irq);
#endif
Cheers,
Samuel.
Quoted 791 lines
Cheers,
Samuel.Quoted 783 lines
Signed-off-by Cory Maccarrone <[email protected]>--- drivers/mfd/Kconfig | 9 + drivers/mfd/Makefile | 1 + drivers/mfd/htc-i2cpld.c | 698 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/htcpld.h | 24 ++ 4 files changed, 732 insertions(+), 0 deletions(-) create mode 100644 drivers/mfd/htc-i2cpld.c create mode 100644 include/linux/htcpld.hDifferences from v2 to v3 include various cleanups, including splitting out the various parts of the
probe()function into subfunctions for readability and fixing checkpatch errors.drivers/mfd/Kconfig +9
-0@@ -68,6 +68,15 @@ config HTC_PASIC3 HTC Magician devices, respectively. Actual functionality is handled by the leds-pasic3 and ds1wm drivers. +config HTC_I2CPLD + bool "HTC I2C PLD chip support" + depends on I2C + help + If you say yes here you get support for the supposed CPLD + found on omap850 HTC devices like the HTC Wizard and HTC Herald. + This device provides input and output GPIOs through an I2C + interface to one or more sub-chips. + config UCB1400_CORE tristate "Philips UCB1400 Core driver" depends on AC97_BUSdrivers/mfd/Makefile +1
-0@@ -8,6 +8,7 @@ obj-$(CONFIG_MFD_SH_MOBILE_SDHI) += sh_mobile_sdhi.o obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o +obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.odrivers/mfd/htc-i2cpld.c new file +698
-0@@ -0,0 +1,698 @@ +/* +* htc-i2cpld.c +* Chip driver for an unknown CPLD chip found on omap850 HTC devices like +* the HTC Wizard and HTC Herald. +* The cpld is located on the i2c bus and acts as an input/output GPIO +* extender. +* +* Copyright (C) 2009 Cory Maccarrone <[email protected]> +* +* Based on work done in the linwizard project +* Copyright (C) 2008-2009 Angelo Arrifano <[email protected]> +* +* 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/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/spinlock.h> +#include <linux/htcpld.h> +#include <linux/gpio.h> + +struct htcpld_chip { +spinlock_t lock; + +/* chip info */ +u8 reset; +u8 addr; +struct device *dev; +struct i2c_client *client; + +/* Output details */ +u8 cache_out; +struct gpio_chip chip_out; + +/* Input details */ +u8 cache_in; +struct gpio_chip chip_in; + +u16 irqs_enabled; +uint irq_start; +int nirqs; + +/* +* Work structure to allow for setting values outside of any +* possible interrupt context +*/ +struct work_struct set_val_work; +}; + +struct htcpld_data { +/* irq info */ +u16 irqs_enabled; +uint irq_start; +int nirqs; +uint chained_irq; +unsigned int int_reset_gpio_hi; +unsigned int int_reset_gpio_lo; + +/* htcpld info */ +struct htcpld_chip *chip; +unsigned int nchips; +}; + +/* There does not appear to be a way to proactively mask interrupts +* on the htcpld chip itself. So, we simply ignore interrupts that +* aren't desired. */ +static void htcpld_mask (unsigned int irq) +{ +struct htcpld_chip *chip =get_irq_chip_data (irq); + chip->irqs_enabled &= ~(1 << (irq - chip->irq_start)); +pr_debug ("HTCPLD mask %d %04x\n" , irq, chip->irqs_enabled); +} +static void htcpld_unmask (unsigned int irq) +{ +struct htcpld_chip *chip =get_irq_chip_data (irq); + chip->irqs_enabled |=1 << (irq - chip->irq_start); +pr_debug ("HTCPLD unmask %d %04x\n" , irq, chip->irqs_enabled); +} + +static int htcpld_set_type (unsigned int irq,unsigned int flags) +{ +if (flags & ~IRQ_TYPE_SENSE_MASK ) +return -EINVAL ; + +/* We only allow edge triggering */ +if (flags & (IRQ_TYPE_LEVEL_LOW |IRQ_TYPE_LEVEL_HIGH )) +return -EINVAL ; + + irq_desc[irq].status &= ~IRQ_TYPE_SENSE_MASK ; + irq_desc[irq].status |= flags; + +return 0 ; +} + +static struct irq_chip htcpld_muxed_chip = { + .name ="htcpld" , + .mask = htcpld_mask, + .unmask = htcpld_unmask, + .set_type = htcpld_set_type, +}; + +/* To properly dispatch IRQ events, we need to read from the +* chip. This is an I2C action that could possibly sleep +* (which is bad in interrupt context) -- so we use a threaded +* interrupt handler to get around that. +*/ +static irqreturn_t htcpld_handler (int irq,void *dev) +{ +struct htcpld_data *htcpld = dev; +unsigned int i; +unsigned long flags; +int irqpin; +struct irq_desc *desc; + +if (!htcpld) { +pr_debug ("htcpld is null in ISR\n" ); +return IRQ_HANDLED ; + } + +/* +* For each chip, do a read of the chip and trigger any interrupts +* desired. The interrupts will be triggered from LSB to MSB (i.e. +* bit 0 first, then bit 1, etc.) +* +* For chips that have no interrupt range specified, just skip 'em. +*/ +for (i =0 ; i < htcpld->nchips; i++) { +struct htcpld_chip *chip = &htcpld->chip[i]; +struct i2c_client *client; +int val; +unsigned long uval, old_val; + +if (!chip) { +pr_debug ("chip %d is null in ISR\n" , i); +continue ; + } + +if (chip->nirqs ==0 ) +continue ; + + client = chip->client; +if (!client) { +pr_debug ("client %d is null in ISR\n" , i); +continue ; + } + +/* Scan the chip */ + val =i2c_smbus_read_byte_data (client, chip->cache_out); +if (val <0 ) { +/* Throw a warning and skip this chip */ +dev_warn (chip->dev,"Unable to read from chip: %d\n" , + val); +continue ; + } + + uval = (unsigned long )val; + +spin_lock_irqsave (&chip->lock, flags); + +/* Save away the old value so we can compare it */ + old_val = chip->cache_in; + +/* Write the new value */ + chip->cache_in = uval; + +spin_unlock_irqrestore (&chip->lock, flags); + +/* +* For each bit in the data (starting at bit 0), trigger +* associated interrupts. +*/ +for (irqpin =0 ; irqpin < chip->nirqs; irqpin++) { +unsigned oldb, newb; +int flags; + + irq = chip->irq_start + irqpin; + desc =irq_to_desc (irq); + flags = desc->status; + +/* Run the IRQ handler, but only if the bit value +* changed, and the proper flags are set */ + oldb = (old_val >> irqpin) &1 ; + newb = (uval >> irqpin) &1 ; + +if ((!oldb && newb && (flags &IRQ_TYPE_EDGE_RISING )) || + (oldb && !newb && (flags &IRQ_TYPE_EDGE_FALLING ))) { +pr_debug ("fire IRQ %d\n" , irqpin); + desc->handle_irq (irq, desc); + } + } + } + +/* +* In order to continue receiving interrupts, the int_reset_gpio must +* be asserted. +*/ +if (htcpld->int_reset_gpio_hi) +gpio_set_value (htcpld->int_reset_gpio_hi,1 ); +if (htcpld->int_reset_gpio_lo) +gpio_set_value (htcpld->int_reset_gpio_lo,0 ); + +return IRQ_HANDLED ; +} + +/* +* The GPIO set routines can be called from interrupt context, especially if, +* for example they're attached to the led-gpio framework and a trigger is +* enabled. As such, we declared work above in the htcpld_chip structure, +* and that work is scheduled in the set routine. The kernel can then run +* the I2C functions, which will sleep, in process context. +*/ +void htcpld_chip_set (struct gpio_chip *chip,unsigned offset,int val) +{ +struct i2c_client *client; +struct htcpld_chip *chip_data; +unsigned long flags; + + chip_data = container_of(chip, struct htcpld_chip, chip_out); +if (!chip_data) +return ; + + client = chip_data->client; +if (client ==NULL ) +return ; + +spin_lock_irqsave (&chip_data->lock, flags); +if (val) + chip_data->cache_out |= (1 << offset); +else + chip_data->cache_out &= ~(1 << offset); +spin_unlock_irqrestore (&chip_data->lock, flags); + +schedule_work (&(chip_data->set_val_work)); +} + +void htcpld_chip_set_ni (struct work_struct *work) +{ +struct htcpld_chip *chip_data; +struct i2c_client *client; + + chip_data = container_of(work, struct htcpld_chip, set_val_work); + client = chip_data->client; +i2c_smbus_read_byte_data (client, chip_data->cache_out); +} + +int htcpld_chip_get (struct gpio_chip *chip,unsigned offset) +{ +struct htcpld_chip *chip_data; +int val =0 ; +int is_input =0 ; + +/* Try out first */ + chip_data = container_of(chip, struct htcpld_chip, chip_out); +if (!chip_data) { +/* Try in */ + is_input =1 ; + chip_data = container_of(chip, struct htcpld_chip, chip_in); +if (!chip_data) +return -EINVAL ; + } + +/* Determine if this is an input or output GPIO */ +if (!is_input) +/* Use the output cache */ + val = (chip_data->cache_out >> offset) &1 ; +else +/* Use the input cache */ + val = (chip_data->cache_in >> offset) &1 ; + +if (val) +return 1 ; +else +return 0 ; +} + +static int htcpld_direction_output (struct gpio_chip *chip, +unsigned offset,int value) +{ +htcpld_chip_set (chip, offset, value); +return 0 ; +} + +static int htcpld_direction_input (struct gpio_chip *chip, +unsigned offset) +{ +/* +* No-op: this function can only be called on the input chip. +* We do however make sure the offset is within range. +*/ +return (offset < chip->ngpio) ?0 : -EINVAL ; +} + +int htcpld_chip_to_irq (struct gpio_chip *chip,unsigned offset) +{ +struct htcpld_chip *chip_data; + + chip_data = container_of(chip, struct htcpld_chip, chip_in); + +if (offset < chip_data->nirqs) +return chip_data->irq_start + offset; +else +return -EINVAL ; +} + +void htcpld_chip_reset (struct i2c_client *client) +{ +struct htcpld_chip *chip_data =i2c_get_clientdata (client); +if (!chip_data) +return ; + +i2c_smbus_read_byte_data ( + client, (chip_data->cache_out = chip_data->reset)); +} + +static int __devinit htcpld_setup_chip_irq ( +struct platform_device *pdev, +int chip_index) +{ +struct htcpld_data *htcpld; +struct device *dev = &pdev->dev; +struct htcpld_core_platform_data *pdata; +struct htcpld_chip *chip; +struct htcpld_chip_platform_data *plat_chip_data; +unsigned int irq, irq_end; +int ret =0 ; + +/* Get the platform and driver data */ + pdata = dev->platform_data; + htcpld =platform_get_drvdata (pdev); + chip = &htcpld->chip[chip_index]; + plat_chip_data = &pdata->chip[chip_index]; + +/* Setup irq handlers */ + irq_end = chip->irq_start + chip->nirqs; +for (irq = chip->irq_start; irq < irq_end; irq++) { +set_irq_chip (irq, &htcpld_muxed_chip); +set_irq_chip_data (irq, chip); +set_irq_handler (irq, handle_simple_irq); +set_irq_flags (irq,IRQF_VALID |IRQF_PROBE ); + } + +return ret; +} + +static int __devinit htcpld_register_chip_i2c ( +struct platform_device *pdev, +int chip_index) +{ +struct htcpld_data *htcpld; +struct device *dev = &pdev->dev; +struct htcpld_core_platform_data *pdata; +struct htcpld_chip *chip; +struct htcpld_chip_platform_data *plat_chip_data; +struct i2c_adapter *adapter; +struct i2c_client *client; +struct i2c_board_info info; + +/* Get the platform and driver data */ + pdata = dev->platform_data; + htcpld =platform_get_drvdata (pdev); + chip = &htcpld->chip[chip_index]; + plat_chip_data = &pdata->chip[chip_index]; + + adapter =i2c_get_adapter (pdata->i2c_adapter_id); +if (adapter ==NULL ) { +/* Eek, no such I2C adapter! Bail out. */ +dev_warn (dev,"Chip at i2c address 0x%x: Invalid i2c adapter %d\n" , + plat_chip_data->addr, pdata->i2c_adapter_id); +return -ENODEV ; + } + +if (!i2c_check_functionality (adapter,I2C_FUNC_SMBUS_READ_BYTE_DATA )) { +dev_warn (dev,"i2c adapter %d non-functional\n" , + pdata->i2c_adapter_id); +return -EINVAL ; + } + +memset (&info,0 ,sizeof (struct i2c_board_info )); + info.addr = plat_chip_data->addr; +strlcpy (info.type,"htcpld-chip" ,I2C_NAME_SIZE ); + info.platform_data = chip; + +/* Add the I2C device. This calls the probe() function. */ + client =i2c_new_device (adapter, &info); +if (!client) { +/* I2C device registration failed, contineu with the next */ +dev_warn (dev,"Unable to add I2C device for 0x%x\n" , + plat_chip_data->addr); +return -ENODEV ; + } + +i2c_set_clientdata (client, chip); +snprintf (client->name,I2C_NAME_SIZE ,"Chip_0x%d" , client->addr); + chip->client = client; + +/* Reset the chip */ +htcpld_chip_reset (client); + chip->cache_in =i2c_smbus_read_byte_data (client, chip->cache_out); + +return 0 ; +} + +static void __devinit htcpld_unregister_chip_i2c ( +struct platform_device *pdev, +int chip_index) +{ +struct htcpld_data *htcpld; +struct htcpld_chip *chip; + +/* Get the platform and driver data */ + htcpld =platform_get_drvdata (pdev); + chip = &htcpld->chip[chip_index]; + +if (chip->client) +i2c_unregister_device (chip->client); +} + +static int __devinit htcpld_register_chip_gpio ( +struct platform_device *pdev, +int chip_index) +{ +struct htcpld_data *htcpld; +struct device *dev = &pdev->dev; +struct htcpld_core_platform_data *pdata; +struct htcpld_chip *chip; +struct htcpld_chip_platform_data *plat_chip_data; +struct gpio_chip *gpio_chip; +int ret =0 ; + +/* Get the platform and driver data */ + pdata = dev->platform_data; + htcpld =platform_get_drvdata (pdev); + chip = &htcpld->chip[chip_index]; + plat_chip_data = &pdata->chip[chip_index]; + +/* Setup the GPIO chips */ + gpio_chip = &(chip->chip_out); + gpio_chip->label ="htcpld-out" ; + gpio_chip->dev = dev; + gpio_chip->owner =THIS_MODULE ; + gpio_chip->get = htcpld_chip_get; + gpio_chip->set = htcpld_chip_set; + gpio_chip->direction_input =NULL ; + gpio_chip->direction_output = htcpld_direction_output; + gpio_chip->base = plat_chip_data->gpio_out_base; + gpio_chip->ngpio = plat_chip_data->num_gpios; + + gpio_chip = &(chip->chip_in); + gpio_chip->label ="htcpld-in" ; + gpio_chip->dev = dev; + gpio_chip->owner =THIS_MODULE ; + gpio_chip->get = htcpld_chip_get; + gpio_chip->set =NULL ; + gpio_chip->direction_input = htcpld_direction_input; + gpio_chip->direction_output =NULL ; + gpio_chip->to_irq = htcpld_chip_to_irq; + gpio_chip->base = plat_chip_data->gpio_in_base; + gpio_chip->ngpio = plat_chip_data->num_gpios; + +/* Add the GPIO chips */ + ret =gpiochip_add (&(chip->chip_out)); +if (ret) { +dev_warn (dev,"Unable to register output GPIOs for 0x%x: %d\n" , + plat_chip_data->addr, ret); +return ret; + } + + ret =gpiochip_add (&(chip->chip_in)); +if (ret) { +int error; + +dev_warn (dev,"Unable to register input GPIOs for 0x%x: %d\n" , + plat_chip_data->addr, ret); + + error =gpiochip_remove (&(chip->chip_out)); +if (error) +dev_warn (dev,"Error while trying to unregister gpio chip: %d\n" , error); + +return ret; + } + +return 0 ; +} + +static int __devinit htcpld_setup_chips (struct platform_device *pdev) +{ +struct htcpld_data *htcpld; +struct device *dev = &pdev->dev; +struct htcpld_core_platform_data *pdata; +int i; + +/* Get the platform and driver data */ + pdata = dev->platform_data; + htcpld =platform_get_drvdata (pdev); + +/* Setup each chip's output GPIOs */ + htcpld->nchips = pdata->num_chip; + htcpld->chip =kzalloc (sizeof (struct htcpld_chip ) * htcpld->nchips, +GFP_KERNEL ); +if (!htcpld->chip) { +dev_warn (dev,"Unable to allocate memory for chips\n" ); +return -ENOMEM ; + } + +/* Add the chips as best we can */ +for (i =0 ; i < htcpld->nchips; i++) { +int ret; + +/* Setup the HTCPLD chips */ + htcpld->chip[i].reset = pdata->chip[i].reset; + htcpld->chip[i].cache_out = pdata->chip[i].reset; + htcpld->chip[i].cache_in =0 ; + htcpld->chip[i].dev = dev; + htcpld->chip[i].irq_start = pdata->chip[i].irq_base; + htcpld->chip[i].nirqs = pdata->chip[i].num_irqs; + +INIT_WORK (&(htcpld->chip[i].set_val_work), &htcpld_chip_set_ni); +spin_lock_init (&(htcpld->chip[i].lock)); + +/* Setup the interrupts for the chip */ +if (htcpld->chained_irq) { + ret =htcpld_setup_chip_irq (pdev, i); +if (ret) +continue ; + } + +/* Register the chip with I2C */ + ret =htcpld_register_chip_i2c (pdev, i); +if (ret) +continue ; + + +/* Register the chips with the GPIO subsystem */ + ret =htcpld_register_chip_gpio (pdev, i); +if (ret) { +/* Unregister the chip from i2c and continue */ +htcpld_unregister_chip_i2c (pdev, i); +continue ; + } + +dev_info (dev,"Registered chip at 0x%x\n" , pdata->chip[i].addr); + } + +return 0 ; +} + +static int __devinit htcpld_core_probe (struct platform_device *pdev) +{ +struct htcpld_data *htcpld; +struct device *dev = &pdev->dev; +struct htcpld_core_platform_data *pdata; +struct resource *res; +int ret =0 ; + +if (!dev) +return -ENODEV ; + + pdata = dev->platform_data; +if (!pdata) { +dev_warn (dev,"Platform data not found for htcpld core!\n" ); +return -ENXIO ; + } + + htcpld =kzalloc (sizeof (struct htcpld_data ),GFP_KERNEL ); +if (!htcpld) +return -ENOMEM ; + +/* Find chained irq */ + ret = -EINVAL ; + res =platform_get_resource (pdev,IORESOURCE_IRQ ,0 ); +if (res) { +int flags; + htcpld->chained_irq = res->start; + +/* Setup the chained interrupt handler */ + flags =IRQF_TRIGGER_FALLING |IRQF_TRIGGER_RISING ; + ret =request_threaded_irq (htcpld->chained_irq, +NULL , htcpld_handler, + flags, pdev->name, htcpld); +if (ret) { +dev_warn (dev,"Unable to setup chained irq handler: %d\n" , ret); + goto fail; + }else +device_init_wakeup (dev,0 ); + } + +/* Set the driver data */ +platform_set_drvdata (pdev, htcpld); + +/* Setup the htcpld chips */ + ret =htcpld_setup_chips (pdev); +if (ret) + goto fail; + +/* Request the GPIO(s) for the int reset and set them up */ +if (pdata->int_reset_gpio_hi) { + ret =gpio_request (pdata->int_reset_gpio_hi,"htcpld-core" ); +if (ret) { +/* +* If it failed, that sucks, but we can probably +* continue on without it. +*/ +dev_warn (dev,"Unable to request int_reset_gpio_hi -- interrupts may not work\n" ); + htcpld->int_reset_gpio_hi =0 ; + }else { + htcpld->int_reset_gpio_hi = pdata->int_reset_gpio_hi; +gpio_set_value (htcpld->int_reset_gpio_hi,1 ); + } + } + +if (pdata->int_reset_gpio_lo) { + ret =gpio_request (pdata->int_reset_gpio_lo,"htcpld-core" ); +if (ret) { +/* +* If it failed, that sucks, but we can probably +* continue on without it. +*/ +dev_warn (dev,"Unable to request int_reset_gpio_lo -- interrupts may not work\n" ); + htcpld->int_reset_gpio_lo =0 ; + }else { + htcpld->int_reset_gpio_lo = pdata->int_reset_gpio_lo; +gpio_set_value (htcpld->int_reset_gpio_lo,0 ); + } + } + +dev_info (dev,"Initialized successfully\n" ); +return 0 ; + +fail: +kfree (htcpld); +return ret; +} + +/* The I2C Driver -- used internally */ +static const struct i2c_device_id htcpld_chip_id[] = { + {"htcpld-chip" ,0 }, + { } +}; +MODULE_DEVICE_TABLE (i2c, htcpld_chip_id); + + +static struct i2c_driver htcpld_chip_driver = { + .driver = { + .name ="htcpld-chip" , + }, + .id_table = htcpld_chip_id, +}; + +/* The Core Driver */ +static struct platform_driver htcpld_core_driver = { + .driver = { + .name ="i2c-htcpld" , + }, +}; + +static int __init htcpld_core_init (void ) +{ +int ret; + +/* Register the I2C Chip driver */ + ret =i2c_add_driver (&htcpld_chip_driver); +if (ret) +return ret; + +/* Probe for our chips */ +return platform_driver_probe (&htcpld_core_driver, htcpld_core_probe); +} + +static void __exit htcpld_core_exit(void) +{ +i2c_del_driver (&htcpld_chip_driver); +platform_driver_unregister (&htcpld_core_driver); +} + +module_init (htcpld_core_init); +module_exit (htcpld_core_exit); + +MODULE_AUTHOR ("Cory Maccarrone <[email protected]>" ); +MODULE_DESCRIPTION ("I2C HTC PLD Driver" ); +MODULE_LICENSE ("GPL" ); +include/linux/htcpld.h new file +24
-0@@ -0,0 +1,24 @@ +#ifndef __LINUX_HTCPLD_H +#define __LINUX_HTCPLD_H + +struct htcpld_chip_platform_data { +unsigned int addr; +unsigned int reset; +unsigned int num_gpios; +unsigned int gpio_out_base; +unsigned int gpio_in_base; +unsigned int irq_base; +unsigned int num_irqs; +}; + +struct htcpld_core_platform_data { +unsigned int int_reset_gpio_hi; +unsigned int int_reset_gpio_lo; +unsigned int i2c_adapter_id; + +struct htcpld_chip_platform_data *chip; +unsigned int num_chip; +}; + +#endif /* __LINUX_HTCPLD_H */ +Signature · 1 line
1.6.3.3Signature · 2 lines
Intel Open Source Technology Centre http://oss.intel.com/
Signature · 2 lines
Intel Open Source Technology Centre http://oss.intel.com/
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 6 lines · Samuel Ortiz
Quoted 5 lines · Samuel Ortiz
Hi Cory,
I applied this patch, thanks a lot.
In the future, I'd like to see the GPIO handling from this patch moved todrivers/gpio. Could you please look at that ?
Absolutely, thanks for applying it! I'll send out a follow-on patch as soon as I've got that change.
Quoted 9 lines
I forgot to add that I also made it build on non ARM patforms. This line:
set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
became:
#ifdefCONFIG_ARM
set_irq_flags(irq,IRQF_VALID|IRQF_PROBE); #else
set_irq_probe(irq);
#endif
Ah, cool, thanks!
- Cory
A message that is not in the archive (sent off-list or not yet received): 6cb013311001181323q79aba15dif165ff41d79734a7-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org
Hi Cory,
Quoted 21 lines · Cory Maccarrone
Quoted 6 lines · Samuel Ortiz
Quoted 5 lines · Samuel Ortiz
Hi Cory,
I applied this patch, thanks a lot.
In the future, I'd like to see the GPIO handling from this patch moved todrivers/gpio. Could you please look at that ?Absolutely, thanks for applying it! I'll send out a follow-on patch as soon as I've got that change.
Quoted 9 lines
I forgot to add that I also made it build on non ARM patforms. This line:
set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
became:
#ifdefCONFIG_ARM
set_irq_flags(irq,IRQF_VALID|IRQF_PROBE); #else
set_irq_probe(irq);
#endif
Quoted 1 line
Ah, cool, thanks!
After the build failure reports from linux-next, I also had to fix a couple more things:
1) htcpld_set_type(): You're not supposed to access irq_desc directly, you should use irq_to_desc() instead
2) Kconfig: we need to depend on I2C=y, to avoid the problematic case of having I2C_CORE=m and HTC_I2CPLD=y
Please let me know if you're not ok with those changes. You can grab them from the for-next branch of my kernel.org tree, atgit://git.kernel.org/pub/scm/linux/kernel/git/sameo/mfd-2.6.git for-next
Cheers,
Samuel.
Quoted 1 line
- Cory
Signature · 2 lines
Intel Open Source Technology Centre http://oss.intel.com/
A message that is not in the archive (sent off-list or not yet received): [email protected]
Hi Samuel,
Quoted 2 lines · Samuel Ortiz
2)
Kconfig: we need to depend on I2C=y, to avoid the problematic case of having I2C_CORE=m and HTC_I2CPLD=y
??
There is no config option named I2C_CORE. The main option for I2C is, well, I2C, and if you depend on I2C, then you can't be built into the kernel if I2C is built as a module. What exact problem are you trying to solve?
Signature · 1 line
Jean Delvare
A message that is not in the archive (sent off-list or not yet received): [email protected]
Hi Jean,
Quoted 10 lines · Jean Delvare
Hi Samuel,
Quoted 2 lines · Samuel Ortiz
2)
Kconfig: we need to depend on I2C=y, to avoid the problematic case of having I2C_CORE=m and HTC_I2CPLD=y??
There is no config option named
I2C_CORE. The main option for I2C is, well, I2C,
Sorry, I meant I2C, not I2C_CORE.
Quoted 2 lines
and if you depend on I2C, then you can't be built into the kernel if I2C is built as a module.
I have:
drivers/mfd/Kconfig:
config HTC_I2CPLD
bool "HTC I2C PLD chip support"
depends on I2C
Then make menuconfig allows me to build a .config with
CONFIG_I2C=m and CONFIG_HTC_I2CPLD=y
and that obviously fails to build. If I'm a bool depending on I2C, it has to be a built-in I2C.
Cheers,
Samuel.
Quoted 4 lines
What exact problem are you trying to solve?
Signature · 1 line
Jean Delvare
Signature · 2 lines
Intel Open Source Technology Centre http://oss.intel.com/
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 25 lines · Samuel Ortiz
Hi Jean,
Quoted 10 lines · Jean Delvare
Hi Samuel,
Quoted 2 lines · Samuel Ortiz
2)
Kconfig: we need to depend on I2C=y, to avoid the problematic case of having I2C_CORE=m and HTC_I2CPLD=y??
There is no config option named
I2C_CORE. The main option for I2C is, well, I2C,Sorry, I meant I2C, not
I2C_CORE.Quoted 2 lines
and if you depend on I2C, then you can't be built into the kernel if I2C is built as a module.
I have:
drivers/mfd/Kconfig:
configHTC_I2CPLD
bool "HTC I2C PLD chip support"
depends on I2C
Quoted 6 lines
Then make menuconfig allows me to build a .config with
CONFIG_I2C=mandCONFIG_HTC_I2CPLD=yand that obviously fails to build. If I'm a bool depending on I2C, it has to be a built-in I2C.
Ah, my bad, I totally missed that HTC_I2CPLD was a bool. Please just ignore me, sorry for the noise. You are correct, it should depend on I2C=y.
Signature · 1 line
Jean Delvare
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 28 lines · Jean Delvare
Quoted 27 lines · Samuel Ortiz
Hi Jean,
Quoted 10 lines · Jean Delvare
Hi Samuel,
Quoted 2 lines · Samuel Ortiz
2)
Kconfig: we need to depend on I2C=y, to avoid the problematic case of having I2C_CORE=m and HTC_I2CPLD=y??
There is no config option named
I2C_CORE. The main option for I2C is, well, I2C,Sorry, I meant I2C, not
I2C_CORE.Quoted 2 lines
and if you depend on I2C, then you can't be built into the kernel if I2C is built as a module.
I have:
drivers/mfd/Kconfig:
configHTC_I2CPLD
bool "HTC I2C PLD chip support"
depends on I2CThen make menuconfig allows me to build a .config with
Quoted 9 lines
Quoted 4 lines
CONFIG_I2C=mandCONFIG_HTC_I2CPLD=yand that obviously fails to build. If I'm a bool depending on I2C, it has to be a built-in I2C.
Ah, my bad, I totally missed that
HTC_I2CPLDwas a bool. Please just ignore me, sorry for the noise. You are correct, it should depend on I2C=y.
All these changes sound fine to me, thanks for doing that! Sorry for the compile mess, I'll keep all this in mind for later work.
- Cory
A message that is not in the archive (sent off-list or not yet received): 6cb013311001190710q186405c7m1193af79e18a09e3-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org
Quoted 39 lines · Cory Maccarrone
Quoted 28 lines · Jean Delvare
Quoted 27 lines · Samuel Ortiz
Hi Jean,
Quoted 10 lines · Jean Delvare
Hi Samuel,
Quoted 2 lines · Samuel Ortiz
2)
Kconfig: we need to depend on I2C=y, to avoid the problematic case of having I2C_CORE=m and HTC_I2CPLD=y??
There is no config option named
I2C_CORE. The main option for I2C is, well, I2C,Sorry, I meant I2C, not
I2C_CORE.Quoted 2 lines
and if you depend on I2C, then you can't be built into the kernel if I2C is built as a module.
I have:
drivers/mfd/Kconfig:
configHTC_I2CPLD
bool "HTC I2C PLD chip support"
depends on I2CThen make menuconfig allows me to build a .config with
Quoted 9 lines
Quoted 4 lines
CONFIG_I2C=mandCONFIG_HTC_I2CPLD=yand that obviously fails to build. If I'm a bool depending on I2C, it has to be a built-in I2C.
Ah, my bad, I totally missed that
HTC_I2CPLDwas a bool. Please just ignore me, sorry for the noise. You are correct, it should depend on I2C=y.
Quoted 5 lines
All these changes sound fine to me, thanks for doing that! Sorry for the compile mess, I'll keep all this in mind for later work.
- Cory
Has this driver not gone upstream? I was under the impression from Samuel that it would a month or so ago...
If not, is there something more I need to do to get it in?
Thanks
- Cory