diff options
-rw-r--r-- | drivers/gpio/Kconfig | 10 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpio/janz-ttl.c | 258 |
3 files changed, 269 insertions, 0 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 56eb0c59c512..0e9c16b5bbce 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig | |||
@@ -325,4 +325,14 @@ config GPIO_UCB1400 | |||
325 | To compile this driver as a module, choose M here: the | 325 | To compile this driver as a module, choose M here: the |
326 | module will be called ucb1400_gpio. | 326 | module will be called ucb1400_gpio. |
327 | 327 | ||
328 | comment "MODULbus GPIO expanders:" | ||
329 | |||
330 | config GPIO_JANZ_TTL | ||
331 | tristate "Janz VMOD-TTL Digital IO Module" | ||
332 | depends on MFD_JANZ_CMODIO | ||
333 | help | ||
334 | This enables support for the Janz VMOD-TTL Digital IO module. | ||
335 | This driver provides support for driving the pins in output | ||
336 | mode only. Input mode is not supported. | ||
337 | |||
328 | endif | 338 | endif |
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index d3226d395a90..94a96c51c741 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile | |||
@@ -29,3 +29,4 @@ obj-$(CONFIG_GPIO_WM8350) += wm8350-gpiolib.o | |||
29 | obj-$(CONFIG_GPIO_WM8994) += wm8994-gpio.o | 29 | obj-$(CONFIG_GPIO_WM8994) += wm8994-gpio.o |
30 | obj-$(CONFIG_GPIO_SCH) += sch_gpio.o | 30 | obj-$(CONFIG_GPIO_SCH) += sch_gpio.o |
31 | obj-$(CONFIG_GPIO_RDC321X) += rdc321x-gpio.o | 31 | obj-$(CONFIG_GPIO_RDC321X) += rdc321x-gpio.o |
32 | obj-$(CONFIG_GPIO_JANZ_TTL) += janz-ttl.o | ||
diff --git a/drivers/gpio/janz-ttl.c b/drivers/gpio/janz-ttl.c new file mode 100644 index 000000000000..813ac077e5d7 --- /dev/null +++ b/drivers/gpio/janz-ttl.c | |||
@@ -0,0 +1,258 @@ | |||
1 | /* | ||
2 | * Janz MODULbus VMOD-TTL GPIO Driver | ||
3 | * | ||
4 | * Copyright (c) 2010 Ira W. Snyder <iws@ovro.caltech.edu> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms of the GNU General Public License as published by the | ||
8 | * Free Software Foundation; either version 2 of the License, or (at your | ||
9 | * option) any later version. | ||
10 | */ | ||
11 | |||
12 | #include <linux/kernel.h> | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/init.h> | ||
15 | #include <linux/interrupt.h> | ||
16 | #include <linux/delay.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/io.h> | ||
19 | #include <linux/gpio.h> | ||
20 | #include <linux/slab.h> | ||
21 | |||
22 | #include <linux/mfd/janz.h> | ||
23 | |||
24 | #define DRV_NAME "janz-ttl" | ||
25 | |||
26 | #define PORTA_DIRECTION 0x23 | ||
27 | #define PORTB_DIRECTION 0x2B | ||
28 | #define PORTC_DIRECTION 0x06 | ||
29 | #define PORTA_IOCTL 0x24 | ||
30 | #define PORTB_IOCTL 0x2C | ||
31 | #define PORTC_IOCTL 0x07 | ||
32 | |||
33 | #define MASTER_INT_CTL 0x00 | ||
34 | #define MASTER_CONF_CTL 0x01 | ||
35 | |||
36 | #define CONF_PAE (1 << 2) | ||
37 | #define CONF_PBE (1 << 7) | ||
38 | #define CONF_PCE (1 << 4) | ||
39 | |||
40 | struct ttl_control_regs { | ||
41 | __be16 portc; | ||
42 | __be16 portb; | ||
43 | __be16 porta; | ||
44 | __be16 control; | ||
45 | }; | ||
46 | |||
47 | struct ttl_module { | ||
48 | struct gpio_chip gpio; | ||
49 | |||
50 | /* base address of registers */ | ||
51 | struct ttl_control_regs __iomem *regs; | ||
52 | |||
53 | u8 portc_shadow; | ||
54 | u8 portb_shadow; | ||
55 | u8 porta_shadow; | ||
56 | |||
57 | spinlock_t lock; | ||
58 | }; | ||
59 | |||
60 | static int ttl_get_value(struct gpio_chip *gpio, unsigned offset) | ||
61 | { | ||
62 | struct ttl_module *mod = dev_get_drvdata(gpio->dev); | ||
63 | u8 *shadow; | ||
64 | int ret; | ||
65 | |||
66 | if (offset < 8) { | ||
67 | shadow = &mod->porta_shadow; | ||
68 | } else if (offset < 16) { | ||
69 | shadow = &mod->portb_shadow; | ||
70 | offset -= 8; | ||
71 | } else { | ||
72 | shadow = &mod->portc_shadow; | ||
73 | offset -= 16; | ||
74 | } | ||
75 | |||
76 | spin_lock(&mod->lock); | ||
77 | ret = *shadow & (1 << offset); | ||
78 | spin_unlock(&mod->lock); | ||
79 | return ret; | ||
80 | } | ||
81 | |||
82 | static void ttl_set_value(struct gpio_chip *gpio, unsigned offset, int value) | ||
83 | { | ||
84 | struct ttl_module *mod = dev_get_drvdata(gpio->dev); | ||
85 | void __iomem *port; | ||
86 | u8 *shadow; | ||
87 | |||
88 | if (offset < 8) { | ||
89 | port = &mod->regs->porta; | ||
90 | shadow = &mod->porta_shadow; | ||
91 | } else if (offset < 16) { | ||
92 | port = &mod->regs->portb; | ||
93 | shadow = &mod->portb_shadow; | ||
94 | offset -= 8; | ||
95 | } else { | ||
96 | port = &mod->regs->portc; | ||
97 | shadow = &mod->portc_shadow; | ||
98 | offset -= 16; | ||
99 | } | ||
100 | |||
101 | spin_lock(&mod->lock); | ||
102 | if (value) | ||
103 | *shadow |= (1 << offset); | ||
104 | else | ||
105 | *shadow &= ~(1 << offset); | ||
106 | |||
107 | iowrite16be(*shadow, port); | ||
108 | spin_unlock(&mod->lock); | ||
109 | } | ||
110 | |||
111 | static void __devinit ttl_write_reg(struct ttl_module *mod, u8 reg, u16 val) | ||
112 | { | ||
113 | iowrite16be(reg, &mod->regs->control); | ||
114 | iowrite16be(val, &mod->regs->control); | ||
115 | } | ||
116 | |||
117 | static void __devinit ttl_setup_device(struct ttl_module *mod) | ||
118 | { | ||
119 | /* reset the device to a known state */ | ||
120 | iowrite16be(0x0000, &mod->regs->control); | ||
121 | iowrite16be(0x0001, &mod->regs->control); | ||
122 | iowrite16be(0x0000, &mod->regs->control); | ||
123 | |||
124 | /* put all ports in open-drain mode */ | ||
125 | ttl_write_reg(mod, PORTA_IOCTL, 0x00ff); | ||
126 | ttl_write_reg(mod, PORTB_IOCTL, 0x00ff); | ||
127 | ttl_write_reg(mod, PORTC_IOCTL, 0x000f); | ||
128 | |||
129 | /* set all ports as outputs */ | ||
130 | ttl_write_reg(mod, PORTA_DIRECTION, 0x0000); | ||
131 | ttl_write_reg(mod, PORTB_DIRECTION, 0x0000); | ||
132 | ttl_write_reg(mod, PORTC_DIRECTION, 0x0000); | ||
133 | |||
134 | /* set all ports to drive zeroes */ | ||
135 | iowrite16be(0x0000, &mod->regs->porta); | ||
136 | iowrite16be(0x0000, &mod->regs->portb); | ||
137 | iowrite16be(0x0000, &mod->regs->portc); | ||
138 | |||
139 | /* enable all ports */ | ||
140 | ttl_write_reg(mod, MASTER_CONF_CTL, CONF_PAE | CONF_PBE | CONF_PCE); | ||
141 | } | ||
142 | |||
143 | static int __devinit ttl_probe(struct platform_device *pdev) | ||
144 | { | ||
145 | struct janz_platform_data *pdata; | ||
146 | struct device *dev = &pdev->dev; | ||
147 | struct ttl_module *mod; | ||
148 | struct gpio_chip *gpio; | ||
149 | struct resource *res; | ||
150 | int ret; | ||
151 | |||
152 | pdata = pdev->dev.platform_data; | ||
153 | if (!pdata) { | ||
154 | dev_err(dev, "no platform data\n"); | ||
155 | ret = -ENXIO; | ||
156 | goto out_return; | ||
157 | } | ||
158 | |||
159 | mod = kzalloc(sizeof(*mod), GFP_KERNEL); | ||
160 | if (!mod) { | ||
161 | dev_err(dev, "unable to allocate private data\n"); | ||
162 | ret = -ENOMEM; | ||
163 | goto out_return; | ||
164 | } | ||
165 | |||
166 | platform_set_drvdata(pdev, mod); | ||
167 | spin_lock_init(&mod->lock); | ||
168 | |||
169 | /* get access to the MODULbus registers for this module */ | ||
170 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
171 | if (!res) { | ||
172 | dev_err(dev, "MODULbus registers not found\n"); | ||
173 | ret = -ENODEV; | ||
174 | goto out_free_mod; | ||
175 | } | ||
176 | |||
177 | mod->regs = ioremap(res->start, resource_size(res)); | ||
178 | if (!mod->regs) { | ||
179 | dev_err(dev, "MODULbus registers not ioremap\n"); | ||
180 | ret = -ENOMEM; | ||
181 | goto out_free_mod; | ||
182 | } | ||
183 | |||
184 | ttl_setup_device(mod); | ||
185 | |||
186 | /* Initialize the GPIO data structures */ | ||
187 | gpio = &mod->gpio; | ||
188 | gpio->dev = &pdev->dev; | ||
189 | gpio->label = pdev->name; | ||
190 | gpio->get = ttl_get_value; | ||
191 | gpio->set = ttl_set_value; | ||
192 | gpio->owner = THIS_MODULE; | ||
193 | |||
194 | /* request dynamic allocation */ | ||
195 | gpio->base = -1; | ||
196 | gpio->ngpio = 20; | ||
197 | |||
198 | ret = gpiochip_add(gpio); | ||
199 | if (ret) { | ||
200 | dev_err(dev, "unable to add GPIO chip\n"); | ||
201 | goto out_iounmap_regs; | ||
202 | } | ||
203 | |||
204 | dev_info(&pdev->dev, "module %d: registered GPIO device\n", | ||
205 | pdata->modno); | ||
206 | return 0; | ||
207 | |||
208 | out_iounmap_regs: | ||
209 | iounmap(mod->regs); | ||
210 | out_free_mod: | ||
211 | kfree(mod); | ||
212 | out_return: | ||
213 | return ret; | ||
214 | } | ||
215 | |||
216 | static int __devexit ttl_remove(struct platform_device *pdev) | ||
217 | { | ||
218 | struct ttl_module *mod = platform_get_drvdata(pdev); | ||
219 | struct device *dev = &pdev->dev; | ||
220 | int ret; | ||
221 | |||
222 | ret = gpiochip_remove(&mod->gpio); | ||
223 | if (ret) { | ||
224 | dev_err(dev, "unable to remove GPIO chip\n"); | ||
225 | return ret; | ||
226 | } | ||
227 | |||
228 | iounmap(mod->regs); | ||
229 | kfree(mod); | ||
230 | return 0; | ||
231 | } | ||
232 | |||
233 | static struct platform_driver ttl_driver = { | ||
234 | .driver = { | ||
235 | .name = DRV_NAME, | ||
236 | .owner = THIS_MODULE, | ||
237 | }, | ||
238 | .probe = ttl_probe, | ||
239 | .remove = __devexit_p(ttl_remove), | ||
240 | }; | ||
241 | |||
242 | static int __init ttl_init(void) | ||
243 | { | ||
244 | return platform_driver_register(&ttl_driver); | ||
245 | } | ||
246 | |||
247 | static void __exit ttl_exit(void) | ||
248 | { | ||
249 | platform_driver_unregister(&ttl_driver); | ||
250 | } | ||
251 | |||
252 | MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>"); | ||
253 | MODULE_DESCRIPTION("Janz MODULbus VMOD-TTL Driver"); | ||
254 | MODULE_LICENSE("GPL"); | ||
255 | MODULE_ALIAS("platform:janz-ttl"); | ||
256 | |||
257 | module_init(ttl_init); | ||
258 | module_exit(ttl_exit); | ||