This patch adds a new GPIO driver for the RDC321x SoC GPIO controller. Signed-off-by: Florian Fainelli <florian@openwrt.org> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>tirimbino
parent
e090d506c3
commit
9956d02d6e
@ -0,0 +1,245 @@ |
||||
/*
|
||||
* RDC321x GPIO driver |
||||
* |
||||
* Copyright (C) 2008, Volker Weiss <dev@tintuc.de> |
||||
* Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org> |
||||
* |
||||
* 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/kernel.h> |
||||
#include <linux/init.h> |
||||
#include <linux/spinlock.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/pci.h> |
||||
#include <linux/gpio.h> |
||||
#include <linux/mfd/rdc321x.h> |
||||
|
||||
struct rdc321x_gpio { |
||||
spinlock_t lock; |
||||
struct pci_dev *sb_pdev; |
||||
u32 data_reg[2]; |
||||
int reg1_ctrl_base; |
||||
int reg1_data_base; |
||||
int reg2_ctrl_base; |
||||
int reg2_data_base; |
||||
struct gpio_chip chip; |
||||
}; |
||||
|
||||
/* read GPIO pin */ |
||||
static int rdc_gpio_get_value(struct gpio_chip *chip, unsigned gpio) |
||||
{ |
||||
struct rdc321x_gpio *gpch; |
||||
u32 value = 0; |
||||
int reg; |
||||
|
||||
gpch = container_of(chip, struct rdc321x_gpio, chip); |
||||
reg = gpio < 32 ? gpch->reg1_data_base : gpch->reg2_data_base; |
||||
|
||||
spin_lock(&gpch->lock); |
||||
pci_write_config_dword(gpch->sb_pdev, reg, |
||||
gpch->data_reg[gpio < 32 ? 0 : 1]); |
||||
pci_read_config_dword(gpch->sb_pdev, reg, &value); |
||||
spin_unlock(&gpch->lock); |
||||
|
||||
return (1 << (gpio & 0x1f)) & value ? 1 : 0; |
||||
} |
||||
|
||||
static void rdc_gpio_set_value_impl(struct gpio_chip *chip, |
||||
unsigned gpio, int value) |
||||
{ |
||||
struct rdc321x_gpio *gpch; |
||||
int reg = (gpio < 32) ? 0 : 1; |
||||
|
||||
gpch = container_of(chip, struct rdc321x_gpio, chip); |
||||
|
||||
if (value) |
||||
gpch->data_reg[reg] |= 1 << (gpio & 0x1f); |
||||
else |
||||
gpch->data_reg[reg] &= ~(1 << (gpio & 0x1f)); |
||||
|
||||
pci_write_config_dword(gpch->sb_pdev, |
||||
reg ? gpch->reg1_data_base : gpch->reg2_data_base, |
||||
gpch->data_reg[reg]); |
||||
} |
||||
|
||||
/* set GPIO pin to value */ |
||||
static void rdc_gpio_set_value(struct gpio_chip *chip, |
||||
unsigned gpio, int value) |
||||
{ |
||||
struct rdc321x_gpio *gpch; |
||||
|
||||
gpch = container_of(chip, struct rdc321x_gpio, chip); |
||||
spin_lock(&gpch->lock); |
||||
rdc_gpio_set_value_impl(chip, gpio, value); |
||||
spin_unlock(&gpch->lock); |
||||
} |
||||
|
||||
static int rdc_gpio_config(struct gpio_chip *chip, |
||||
unsigned gpio, int value) |
||||
{ |
||||
struct rdc321x_gpio *gpch; |
||||
int err; |
||||
u32 reg; |
||||
|
||||
gpch = container_of(chip, struct rdc321x_gpio, chip); |
||||
|
||||
spin_lock(&gpch->lock); |
||||
err = pci_read_config_dword(gpch->sb_pdev, gpio < 32 ? |
||||
gpch->reg1_ctrl_base : gpch->reg2_ctrl_base, ®); |
||||
if (err) |
||||
goto unlock; |
||||
|
||||
reg |= 1 << (gpio & 0x1f); |
||||
|
||||
err = pci_write_config_dword(gpch->sb_pdev, gpio < 32 ? |
||||
gpch->reg1_ctrl_base : gpch->reg2_ctrl_base, reg); |
||||
if (err) |
||||
goto unlock; |
||||
|
||||
rdc_gpio_set_value_impl(chip, gpio, value); |
||||
|
||||
unlock: |
||||
spin_unlock(&gpch->lock); |
||||
|
||||
return err; |
||||
} |
||||
|
||||
/* configure GPIO pin as input */ |
||||
static int rdc_gpio_direction_input(struct gpio_chip *chip, unsigned gpio) |
||||
{ |
||||
return rdc_gpio_config(chip, gpio, 1); |
||||
} |
||||
|
||||
/*
|
||||
* Cache the initial value of both GPIO data registers |
||||
*/ |
||||
static int __devinit rdc321x_gpio_probe(struct platform_device *pdev) |
||||
{ |
||||
int err; |
||||
struct resource *r; |
||||
struct rdc321x_gpio *rdc321x_gpio_dev; |
||||
struct rdc321x_gpio_pdata *pdata; |
||||
|
||||
pdata = pdev->dev.platform_data; |
||||
if (!pdata) { |
||||
dev_err(&pdev->dev, "no platform data supplied\n"); |
||||
return -ENODEV; |
||||
} |
||||
|
||||
rdc321x_gpio_dev = kzalloc(sizeof(struct rdc321x_gpio), GFP_KERNEL); |
||||
if (!rdc321x_gpio_dev) { |
||||
dev_err(&pdev->dev, "failed to allocate private data\n"); |
||||
return -ENOMEM; |
||||
} |
||||
|
||||
r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gpio-reg1"); |
||||
if (!r) { |
||||
dev_err(&pdev->dev, "failed to get gpio-reg1 resource\n"); |
||||
err = -ENODEV; |
||||
goto out_free; |
||||
} |
||||
|
||||
spin_lock_init(&rdc321x_gpio_dev->lock); |
||||
rdc321x_gpio_dev->sb_pdev = pdata->sb_pdev; |
||||
rdc321x_gpio_dev->reg1_ctrl_base = r->start; |
||||
rdc321x_gpio_dev->reg1_data_base = r->start + 0x4; |
||||
|
||||
r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gpio-reg2"); |
||||
if (!r) { |
||||
dev_err(&pdev->dev, "failed to get gpio-reg2 resource\n"); |
||||
err = -ENODEV; |
||||
goto out_free; |
||||
} |
||||
|
||||
rdc321x_gpio_dev->reg2_ctrl_base = r->start; |
||||
rdc321x_gpio_dev->reg2_data_base = r->start + 0x4; |
||||
|
||||
rdc321x_gpio_dev->chip.label = "rdc321x-gpio"; |
||||
rdc321x_gpio_dev->chip.direction_input = rdc_gpio_direction_input; |
||||
rdc321x_gpio_dev->chip.direction_output = rdc_gpio_config; |
||||
rdc321x_gpio_dev->chip.get = rdc_gpio_get_value; |
||||
rdc321x_gpio_dev->chip.set = rdc_gpio_set_value; |
||||
rdc321x_gpio_dev->chip.base = 0; |
||||
rdc321x_gpio_dev->chip.ngpio = pdata->max_gpios; |
||||
|
||||
platform_set_drvdata(pdev, rdc321x_gpio_dev); |
||||
|
||||
/* This might not be, what others (BIOS, bootloader, etc.)
|
||||
wrote to these registers before, but it's a good guess. Still |
||||
better than just using 0xffffffff. */ |
||||
err = pci_read_config_dword(rdc321x_gpio_dev->sb_pdev, |
||||
rdc321x_gpio_dev->reg1_data_base, |
||||
&rdc321x_gpio_dev->data_reg[0]); |
||||
if (err) |
||||
goto out_drvdata; |
||||
|
||||
err = pci_read_config_dword(rdc321x_gpio_dev->sb_pdev, |
||||
rdc321x_gpio_dev->reg2_data_base, |
||||
&rdc321x_gpio_dev->data_reg[1]); |
||||
if (err) |
||||
goto out_drvdata; |
||||
|
||||
dev_info(&pdev->dev, "registering %d GPIOs\n", |
||||
rdc321x_gpio_dev->chip.ngpio); |
||||
return gpiochip_add(&rdc321x_gpio_dev->chip); |
||||
|
||||
out_drvdata: |
||||
platform_set_drvdata(pdev, NULL); |
||||
out_free: |
||||
kfree(rdc321x_gpio_dev); |
||||
return err; |
||||
} |
||||
|
||||
static int __devexit rdc321x_gpio_remove(struct platform_device *pdev) |
||||
{ |
||||
int ret; |
||||
struct rdc321x_gpio *rdc321x_gpio_dev = platform_get_drvdata(pdev); |
||||
|
||||
ret = gpiochip_remove(&rdc321x_gpio_dev->chip); |
||||
if (ret) |
||||
dev_err(&pdev->dev, "failed to unregister chip\n"); |
||||
|
||||
kfree(rdc321x_gpio_dev); |
||||
platform_set_drvdata(pdev, NULL); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
static struct platform_driver rdc321x_gpio_driver = { |
||||
.driver.name = "rdc321x-gpio", |
||||
.driver.owner = THIS_MODULE, |
||||
.probe = rdc321x_gpio_probe, |
||||
.remove = __devexit_p(rdc321x_gpio_remove), |
||||
}; |
||||
|
||||
static int __init rdc321x_gpio_init(void) |
||||
{ |
||||
return platform_driver_register(&rdc321x_gpio_driver); |
||||
} |
||||
|
||||
static void __exit rdc321x_gpio_exit(void) |
||||
{ |
||||
platform_driver_unregister(&rdc321x_gpio_driver); |
||||
} |
||||
|
||||
module_init(rdc321x_gpio_init); |
||||
module_exit(rdc321x_gpio_exit); |
||||
|
||||
MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); |
||||
MODULE_DESCRIPTION("RDC321x GPIO driver"); |
||||
MODULE_LICENSE("GPL"); |
||||
MODULE_ALIAS("platform:rdc321x-gpio"); |
Loading…
Reference in new issue