| /* | |
| * sch_gpio.c - GPIO interface for Intel Poulsbo SCH | |
| * | |
| * Copyright (c) 2010 CompuLab Ltd | |
| * Author: Denis Turischev <denis@compulab.co.il> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License 2 as published | |
| * by the Free Software Foundation. | |
| * | |
| * 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; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| */ | |
| #include <linux/init.h> | |
| #include <linux/kernel.h> | |
| #include <linux/module.h> | |
| #include <linux/io.h> | |
| #include <linux/errno.h> | |
| #include <linux/acpi.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/pci_ids.h> | |
| #include <linux/gpio.h> | |
| static DEFINE_SPINLOCK(gpio_lock); | |
| #define CGEN (0x00) | |
| #define CGIO (0x04) | |
| #define CGLV (0x08) | |
| #define RGEN (0x20) | |
| #define RGIO (0x24) | |
| #define RGLV (0x28) | |
| static unsigned short gpio_ba; | |
| static int sch_gpio_core_direction_in(struct gpio_chip *gc, unsigned gpio_num) | |
| { | |
| u8 curr_dirs; | |
| unsigned short offset, bit; | |
| spin_lock(&gpio_lock); | |
| offset = CGIO + gpio_num / 8; | |
| bit = gpio_num % 8; | |
| curr_dirs = inb(gpio_ba + offset); | |
| if (!(curr_dirs & (1 << bit))) | |
| outb(curr_dirs | (1 << bit), gpio_ba + offset); | |
| spin_unlock(&gpio_lock); | |
| return 0; | |
| } | |
| static int sch_gpio_core_get(struct gpio_chip *gc, unsigned gpio_num) | |
| { | |
| int res; | |
| unsigned short offset, bit; | |
| offset = CGLV + gpio_num / 8; | |
| bit = gpio_num % 8; | |
| res = !!(inb(gpio_ba + offset) & (1 << bit)); | |
| return res; | |
| } | |
| static void sch_gpio_core_set(struct gpio_chip *gc, unsigned gpio_num, int val) | |
| { | |
| u8 curr_vals; | |
| unsigned short offset, bit; | |
| spin_lock(&gpio_lock); | |
| offset = CGLV + gpio_num / 8; | |
| bit = gpio_num % 8; | |
| curr_vals = inb(gpio_ba + offset); | |
| if (val) | |
| outb(curr_vals | (1 << bit), gpio_ba + offset); | |
| else | |
| outb((curr_vals & ~(1 << bit)), gpio_ba + offset); | |
| spin_unlock(&gpio_lock); | |
| } | |
| static int sch_gpio_core_direction_out(struct gpio_chip *gc, | |
| unsigned gpio_num, int val) | |
| { | |
| u8 curr_dirs; | |
| unsigned short offset, bit; | |
| sch_gpio_core_set(gc, gpio_num, val); | |
| spin_lock(&gpio_lock); | |
| offset = CGIO + gpio_num / 8; | |
| bit = gpio_num % 8; | |
| curr_dirs = inb(gpio_ba + offset); | |
| if (curr_dirs & (1 << bit)) | |
| outb(curr_dirs & ~(1 << bit), gpio_ba + offset); | |
| spin_unlock(&gpio_lock); | |
| return 0; | |
| } | |
| static struct gpio_chip sch_gpio_core = { | |
| .label = "sch_gpio_core", | |
| .owner = THIS_MODULE, | |
| .direction_input = sch_gpio_core_direction_in, | |
| .get = sch_gpio_core_get, | |
| .direction_output = sch_gpio_core_direction_out, | |
| .set = sch_gpio_core_set, | |
| }; | |
| static int sch_gpio_resume_direction_in(struct gpio_chip *gc, | |
| unsigned gpio_num) | |
| { | |
| u8 curr_dirs; | |
| spin_lock(&gpio_lock); | |
| curr_dirs = inb(gpio_ba + RGIO); | |
| if (!(curr_dirs & (1 << gpio_num))) | |
| outb(curr_dirs | (1 << gpio_num) , gpio_ba + RGIO); | |
| spin_unlock(&gpio_lock); | |
| return 0; | |
| } | |
| static int sch_gpio_resume_get(struct gpio_chip *gc, unsigned gpio_num) | |
| { | |
| return !!(inb(gpio_ba + RGLV) & (1 << gpio_num)); | |
| } | |
| static void sch_gpio_resume_set(struct gpio_chip *gc, | |
| unsigned gpio_num, int val) | |
| { | |
| u8 curr_vals; | |
| spin_lock(&gpio_lock); | |
| curr_vals = inb(gpio_ba + RGLV); | |
| if (val) | |
| outb(curr_vals | (1 << gpio_num), gpio_ba + RGLV); | |
| else | |
| outb((curr_vals & ~(1 << gpio_num)), gpio_ba + RGLV); | |
| spin_unlock(&gpio_lock); | |
| } | |
| static int sch_gpio_resume_direction_out(struct gpio_chip *gc, | |
| unsigned gpio_num, int val) | |
| { | |
| u8 curr_dirs; | |
| sch_gpio_resume_set(gc, gpio_num, val); | |
| spin_lock(&gpio_lock); | |
| curr_dirs = inb(gpio_ba + RGIO); | |
| if (curr_dirs & (1 << gpio_num)) | |
| outb(curr_dirs & ~(1 << gpio_num), gpio_ba + RGIO); | |
| spin_unlock(&gpio_lock); | |
| return 0; | |
| } | |
| static struct gpio_chip sch_gpio_resume = { | |
| .label = "sch_gpio_resume", | |
| .owner = THIS_MODULE, | |
| .direction_input = sch_gpio_resume_direction_in, | |
| .get = sch_gpio_resume_get, | |
| .direction_output = sch_gpio_resume_direction_out, | |
| .set = sch_gpio_resume_set, | |
| }; | |
| static int __devinit sch_gpio_probe(struct platform_device *pdev) | |
| { | |
| struct resource *res; | |
| int err, id; | |
| id = pdev->id; | |
| if (!id) | |
| return -ENODEV; | |
| res = platform_get_resource(pdev, IORESOURCE_IO, 0); | |
| if (!res) | |
| return -EBUSY; | |
| if (!request_region(res->start, resource_size(res), pdev->name)) | |
| return -EBUSY; | |
| gpio_ba = res->start; | |
| switch (id) { | |
| case PCI_DEVICE_ID_INTEL_SCH_LPC: | |
| sch_gpio_core.base = 0; | |
| sch_gpio_core.ngpio = 10; | |
| sch_gpio_resume.base = 10; | |
| sch_gpio_resume.ngpio = 4; | |
| /* | |
| * GPIO[6:0] enabled by default | |
| * GPIO7 is configured by the CMC as SLPIOVR | |
| * Enable GPIO[9:8] core powered gpios explicitly | |
| */ | |
| outb(0x3, gpio_ba + CGEN + 1); | |
| /* | |
| * SUS_GPIO[2:0] enabled by default | |
| * Enable SUS_GPIO3 resume powered gpio explicitly | |
| */ | |
| outb(0x8, gpio_ba + RGEN); | |
| break; | |
| case PCI_DEVICE_ID_INTEL_ITC_LPC: | |
| sch_gpio_core.base = 0; | |
| sch_gpio_core.ngpio = 5; | |
| sch_gpio_resume.base = 5; | |
| sch_gpio_resume.ngpio = 9; | |
| break; | |
| default: | |
| return -ENODEV; | |
| } | |
| sch_gpio_core.dev = &pdev->dev; | |
| sch_gpio_resume.dev = &pdev->dev; | |
| err = gpiochip_add(&sch_gpio_core); | |
| if (err < 0) | |
| goto err_sch_gpio_core; | |
| err = gpiochip_add(&sch_gpio_resume); | |
| if (err < 0) | |
| goto err_sch_gpio_resume; | |
| return 0; | |
| err_sch_gpio_resume: | |
| err = gpiochip_remove(&sch_gpio_core); | |
| if (err) | |
| dev_err(&pdev->dev, "%s failed, %d\n", | |
| "gpiochip_remove()", err); | |
| err_sch_gpio_core: | |
| release_region(res->start, resource_size(res)); | |
| gpio_ba = 0; | |
| return err; | |
| } | |
| static int __devexit sch_gpio_remove(struct platform_device *pdev) | |
| { | |
| struct resource *res; | |
| if (gpio_ba) { | |
| int err; | |
| err = gpiochip_remove(&sch_gpio_core); | |
| if (err) | |
| dev_err(&pdev->dev, "%s failed, %d\n", | |
| "gpiochip_remove()", err); | |
| err = gpiochip_remove(&sch_gpio_resume); | |
| if (err) | |
| dev_err(&pdev->dev, "%s failed, %d\n", | |
| "gpiochip_remove()", err); | |
| res = platform_get_resource(pdev, IORESOURCE_IO, 0); | |
| release_region(res->start, resource_size(res)); | |
| gpio_ba = 0; | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static struct platform_driver sch_gpio_driver = { | |
| .driver = { | |
| .name = "sch_gpio", | |
| .owner = THIS_MODULE, | |
| }, | |
| .probe = sch_gpio_probe, | |
| .remove = __devexit_p(sch_gpio_remove), | |
| }; | |
| static int __init sch_gpio_init(void) | |
| { | |
| return platform_driver_register(&sch_gpio_driver); | |
| } | |
| static void __exit sch_gpio_exit(void) | |
| { | |
| platform_driver_unregister(&sch_gpio_driver); | |
| } | |
| module_init(sch_gpio_init); | |
| module_exit(sch_gpio_exit); | |
| MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>"); | |
| MODULE_DESCRIPTION("GPIO interface for Intel Poulsbo SCH"); | |
| MODULE_LICENSE("GPL"); | |
| MODULE_ALIAS("platform:sch_gpio"); |