/* * GPIO interface for IT8761E Super I/O chip * * 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/ioport.h> #include <linux/gpio.h> #define SIO_CHIP_ID 0x8761 #define CHIP_ID_HIGH_BYTE 0x20 #define CHIP_ID_LOW_BYTE 0x21 static u8 ports[2] = { 0x2e, 0x4e }; static u8 port; static DEFINE_SPINLOCK(sio_lock); #define GPIO_NAME "it8761-gpio" #define GPIO_BA_HIGH_BYTE 0x60 #define GPIO_BA_LOW_BYTE 0x61 #define GPIO_IOSIZE 4 #define GPIO1X_IO 0xf0 #define GPIO2X_IO 0xf1 static u16 gpio_ba; static u8 read_reg(u8 addr, u8 port) { outb(addr, port); return inb(port + 1); } static void write_reg(u8 data, u8 addr, u8 port) { outb(addr, port); outb(data, port + 1); } static void enter_conf_mode(u8 port) { outb(0x87, port); outb(0x61, port); outb(0x55, port); outb((port == 0x2e) ? 0x55 : 0xaa, port); } static void exit_conf_mode(u8 port) { outb(0x2, port); outb(0x2, port + 1); } static void enter_gpio_mode(u8 port) { write_reg(0x2, 0x7, port); } static int it8761e_gpio_get(struct gpio_chip *gc, unsigned gpio_num) { u16 reg; u8 bit; bit = gpio_num % 8; reg = (gpio_num >= 8) ? gpio_ba + 1 : gpio_ba; return !!(inb(reg) & (1 << bit)); } static int it8761e_gpio_direction_in(struct gpio_chip *gc, unsigned gpio_num) { u8 curr_dirs; u8 io_reg, bit; bit = gpio_num % 8; io_reg = (gpio_num >= 8) ? GPIO2X_IO : GPIO1X_IO; spin_lock(&sio_lock); enter_conf_mode(port); enter_gpio_mode(port); curr_dirs = read_reg(io_reg, port); if (curr_dirs & (1 << bit)) write_reg(curr_dirs & ~(1 << bit), io_reg, port); exit_conf_mode(port); spin_unlock(&sio_lock); return 0; } static void it8761e_gpio_set(struct gpio_chip *gc, unsigned gpio_num, int val) { u8 curr_vals, bit; u16 reg; bit = gpio_num % 8; reg = (gpio_num >= 8) ? gpio_ba + 1 : gpio_ba; spin_lock(&sio_lock); curr_vals = inb(reg); if (val) outb(curr_vals | (1 << bit) , reg); else outb(curr_vals & ~(1 << bit), reg); spin_unlock(&sio_lock); } static int it8761e_gpio_direction_out(struct gpio_chip *gc, unsigned gpio_num, int val) { u8 curr_dirs, io_reg, bit; bit = gpio_num % 8; io_reg = (gpio_num >= 8) ? GPIO2X_IO : GPIO1X_IO; it8761e_gpio_set(gc, gpio_num, val); spin_lock(&sio_lock); enter_conf_mode(port); enter_gpio_mode(port); curr_dirs = read_reg(io_reg, port); if (!(curr_dirs & (1 << bit))) write_reg(curr_dirs | (1 << bit), io_reg, port); exit_conf_mode(port); spin_unlock(&sio_lock); return 0; } static struct gpio_chip it8761e_gpio_chip = { .label = GPIO_NAME, .owner = THIS_MODULE, .get = it8761e_gpio_get, .direction_input = it8761e_gpio_direction_in, .set = it8761e_gpio_set, .direction_output = it8761e_gpio_direction_out, }; static int __init it8761e_gpio_init(void) { int i, id, err; /* chip and port detection */ for (i = 0; i < ARRAY_SIZE(ports); i++) { spin_lock(&sio_lock); enter_conf_mode(ports[i]); id = (read_reg(CHIP_ID_HIGH_BYTE, ports[i]) << 8) + read_reg(CHIP_ID_LOW_BYTE, ports[i]); exit_conf_mode(ports[i]); spin_unlock(&sio_lock); if (id == SIO_CHIP_ID) { port = ports[i]; break; } } if (!port) return -ENODEV; /* fetch GPIO base address */ enter_conf_mode(port); enter_gpio_mode(port); gpio_ba = (read_reg(GPIO_BA_HIGH_BYTE, port) << 8) + read_reg(GPIO_BA_LOW_BYTE, port); exit_conf_mode(port); if (!request_region(gpio_ba, GPIO_IOSIZE, GPIO_NAME)) return -EBUSY; it8761e_gpio_chip.base = -1; it8761e_gpio_chip.ngpio = 16; err = gpiochip_add(&it8761e_gpio_chip); if (err < 0) goto gpiochip_add_err; return 0; gpiochip_add_err: release_region(gpio_ba, GPIO_IOSIZE); gpio_ba = 0; return err; } static void __exit it8761e_gpio_exit(void) { if (gpio_ba) { int ret = gpiochip_remove(&it8761e_gpio_chip); WARN(ret, "%s(): gpiochip_remove() failed, ret=%d\n", __func__, ret); release_region(gpio_ba, GPIO_IOSIZE); gpio_ba = 0; } } module_init(it8761e_gpio_init); module_exit(it8761e_gpio_exit); MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>"); MODULE_DESCRIPTION("GPIO interface for IT8761E Super I/O chip"); MODULE_LICENSE("GPL");