diff options
author | Denis Turischev <denis@compulab.co.il> | 2010-03-05 16:44:39 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2010-03-06 14:26:48 -0500 |
commit | 9cc0cb3c7d54f320b9eede6f4a49072ecadd864d (patch) | |
tree | 4de2d427038a5450caeed1a9b1508587d328a6c9 /drivers/gpio/it8761e_gpio.c | |
parent | a8a5164c297c16c2f4be776714ca47dba252cc3d (diff) |
gpio: introduce it8761e_gpio driver for IT8761E Super I/O chip
Signed-off-by: Denis Turischev <denis@compulab.co.il>
Cc: David Brownell <david-b@pacbell.net>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/gpio/it8761e_gpio.c')
-rw-r--r-- | drivers/gpio/it8761e_gpio.c | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/drivers/gpio/it8761e_gpio.c b/drivers/gpio/it8761e_gpio.c new file mode 100644 index 000000000000..753219cf993a --- /dev/null +++ b/drivers/gpio/it8761e_gpio.c | |||
@@ -0,0 +1,231 @@ | |||
1 | /* | ||
2 | * it8761_gpio.c - GPIO interface for IT8761E Super I/O chip | ||
3 | * | ||
4 | * Author: Denis Turischev <denis@compulab.co.il> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License 2 as published | ||
8 | * by the Free Software Foundation. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License | ||
16 | * along with this program; see the file COPYING. If not, write to | ||
17 | * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | ||
18 | */ | ||
19 | |||
20 | #include <linux/init.h> | ||
21 | #include <linux/kernel.h> | ||
22 | #include <linux/module.h> | ||
23 | #include <linux/io.h> | ||
24 | #include <linux/errno.h> | ||
25 | #include <linux/ioport.h> | ||
26 | |||
27 | #include <linux/gpio.h> | ||
28 | |||
29 | #define SIO_CHIP_ID 0x8761 | ||
30 | #define CHIP_ID_HIGH_BYTE 0x20 | ||
31 | #define CHIP_ID_LOW_BYTE 0x21 | ||
32 | |||
33 | static u8 ports[2] = { 0x2e, 0x4e }; | ||
34 | static u8 port; | ||
35 | |||
36 | static DEFINE_SPINLOCK(sio_lock); | ||
37 | |||
38 | #define GPIO_NAME "it8761-gpio" | ||
39 | #define GPIO_BA_HIGH_BYTE 0x60 | ||
40 | #define GPIO_BA_LOW_BYTE 0x61 | ||
41 | #define GPIO_IOSIZE 4 | ||
42 | #define GPIO1X_IO 0xf0 | ||
43 | #define GPIO2X_IO 0xf1 | ||
44 | |||
45 | static u16 gpio_ba; | ||
46 | |||
47 | static u8 read_reg(u8 addr, u8 port) | ||
48 | { | ||
49 | outb(addr, port); | ||
50 | return inb(port + 1); | ||
51 | } | ||
52 | |||
53 | static void write_reg(u8 data, u8 addr, u8 port) | ||
54 | { | ||
55 | outb(addr, port); | ||
56 | outb(data, port + 1); | ||
57 | } | ||
58 | |||
59 | static void enter_conf_mode(u8 port) | ||
60 | { | ||
61 | outb(0x87, port); | ||
62 | outb(0x61, port); | ||
63 | outb(0x55, port); | ||
64 | outb((port == 0x2e) ? 0x55 : 0xaa, port); | ||
65 | } | ||
66 | |||
67 | static void exit_conf_mode(u8 port) | ||
68 | { | ||
69 | outb(0x2, port); | ||
70 | outb(0x2, port + 1); | ||
71 | } | ||
72 | |||
73 | static void enter_gpio_mode(u8 port) | ||
74 | { | ||
75 | write_reg(0x2, 0x7, port); | ||
76 | } | ||
77 | |||
78 | static int it8761e_gpio_get(struct gpio_chip *gc, unsigned gpio_num) | ||
79 | { | ||
80 | u16 reg; | ||
81 | u8 bit; | ||
82 | |||
83 | bit = gpio_num % 7; | ||
84 | reg = (gpio_num >= 7) ? gpio_ba + 1 : gpio_ba; | ||
85 | |||
86 | return !!(inb(reg) & (1 << bit)); | ||
87 | } | ||
88 | |||
89 | static int it8761e_gpio_direction_in(struct gpio_chip *gc, unsigned gpio_num) | ||
90 | { | ||
91 | u8 curr_dirs; | ||
92 | u8 io_reg, bit; | ||
93 | |||
94 | bit = gpio_num % 7; | ||
95 | io_reg = (gpio_num >= 7) ? GPIO2X_IO : GPIO1X_IO; | ||
96 | |||
97 | spin_lock(&sio_lock); | ||
98 | |||
99 | enter_conf_mode(port); | ||
100 | enter_gpio_mode(port); | ||
101 | |||
102 | curr_dirs = read_reg(io_reg, port); | ||
103 | |||
104 | if (curr_dirs & (1 << bit)) | ||
105 | write_reg(curr_dirs & ~(1 << bit), io_reg, port); | ||
106 | |||
107 | exit_conf_mode(port); | ||
108 | |||
109 | spin_unlock(&sio_lock); | ||
110 | return 0; | ||
111 | } | ||
112 | |||
113 | static void it8761e_gpio_set(struct gpio_chip *gc, | ||
114 | unsigned gpio_num, int val) | ||
115 | { | ||
116 | u8 curr_vals, bit; | ||
117 | u16 reg; | ||
118 | |||
119 | bit = gpio_num % 7; | ||
120 | reg = (gpio_num >= 7) ? gpio_ba + 1 : gpio_ba; | ||
121 | |||
122 | spin_lock(&sio_lock); | ||
123 | |||
124 | curr_vals = inb(reg); | ||
125 | if (val) | ||
126 | outb(curr_vals | (1 << bit) , reg); | ||
127 | else | ||
128 | outb(curr_vals & ~(1 << bit), reg); | ||
129 | |||
130 | spin_unlock(&sio_lock); | ||
131 | } | ||
132 | |||
133 | static int it8761e_gpio_direction_out(struct gpio_chip *gc, | ||
134 | unsigned gpio_num, int val) | ||
135 | { | ||
136 | u8 curr_dirs, io_reg, bit; | ||
137 | |||
138 | bit = gpio_num % 7; | ||
139 | io_reg = (gpio_num >= 7) ? GPIO2X_IO : GPIO1X_IO; | ||
140 | |||
141 | it8761e_gpio_set(gc, gpio_num, val); | ||
142 | |||
143 | spin_lock(&sio_lock); | ||
144 | |||
145 | enter_conf_mode(port); | ||
146 | enter_gpio_mode(port); | ||
147 | |||
148 | curr_dirs = read_reg(io_reg, port); | ||
149 | |||
150 | if (!(curr_dirs & (1 << bit))) | ||
151 | write_reg(curr_dirs | (1 << bit), io_reg, port); | ||
152 | |||
153 | exit_conf_mode(port); | ||
154 | |||
155 | spin_unlock(&sio_lock); | ||
156 | return 0; | ||
157 | } | ||
158 | |||
159 | static struct gpio_chip it8761e_gpio_chip = { | ||
160 | .label = GPIO_NAME, | ||
161 | .owner = THIS_MODULE, | ||
162 | .get = it8761e_gpio_get, | ||
163 | .direction_input = it8761e_gpio_direction_in, | ||
164 | .set = it8761e_gpio_set, | ||
165 | .direction_output = it8761e_gpio_direction_out, | ||
166 | }; | ||
167 | |||
168 | static int __init it8761e_gpio_init(void) | ||
169 | { | ||
170 | int i, id, err; | ||
171 | |||
172 | /* chip and port detection */ | ||
173 | for (i = 0; i < ARRAY_SIZE(ports); i++) { | ||
174 | spin_lock(&sio_lock); | ||
175 | enter_conf_mode(ports[i]); | ||
176 | |||
177 | id = (read_reg(CHIP_ID_HIGH_BYTE, ports[i]) << 8) + | ||
178 | read_reg(CHIP_ID_LOW_BYTE, ports[i]); | ||
179 | |||
180 | exit_conf_mode(ports[i]); | ||
181 | spin_unlock(&sio_lock); | ||
182 | |||
183 | if (id == SIO_CHIP_ID) { | ||
184 | port = ports[i]; | ||
185 | break; | ||
186 | } | ||
187 | } | ||
188 | |||
189 | if (!port) | ||
190 | return -ENODEV; | ||
191 | |||
192 | /* fetch GPIO base address */ | ||
193 | enter_conf_mode(port); | ||
194 | enter_gpio_mode(port); | ||
195 | gpio_ba = (read_reg(GPIO_BA_HIGH_BYTE, port) << 8) + | ||
196 | read_reg(GPIO_BA_LOW_BYTE, port); | ||
197 | exit_conf_mode(port); | ||
198 | |||
199 | if (!request_region(gpio_ba, GPIO_IOSIZE, GPIO_NAME)) | ||
200 | return -EBUSY; | ||
201 | |||
202 | it8761e_gpio_chip.base = -1; | ||
203 | it8761e_gpio_chip.ngpio = 14; | ||
204 | |||
205 | err = gpiochip_add(&it8761e_gpio_chip); | ||
206 | if (err < 0) | ||
207 | goto gpiochip_add_err; | ||
208 | |||
209 | return 0; | ||
210 | |||
211 | gpiochip_add_err: | ||
212 | release_region(gpio_ba, GPIO_IOSIZE); | ||
213 | gpio_ba = 0; | ||
214 | return err; | ||
215 | } | ||
216 | |||
217 | static void __exit it8761e_gpio_exit(void) | ||
218 | { | ||
219 | if (gpio_ba) { | ||
220 | gpiochip_remove(&it8761e_gpio_chip); | ||
221 | |||
222 | release_region(gpio_ba, GPIO_IOSIZE); | ||
223 | gpio_ba = 0; | ||
224 | } | ||
225 | } | ||
226 | module_init(it8761e_gpio_init); | ||
227 | module_exit(it8761e_gpio_exit); | ||
228 | |||
229 | MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>"); | ||
230 | MODULE_DESCRIPTION("GPIO interface for IT8761E Super I/O chip"); | ||
231 | MODULE_LICENSE("GPL"); | ||