diff options
Diffstat (limited to 'drivers/video/via/via-gpio.c')
-rw-r--r-- | drivers/video/via/via-gpio.c | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/drivers/video/via/via-gpio.c b/drivers/video/via/via-gpio.c new file mode 100644 index 000000000000..595516aea691 --- /dev/null +++ b/drivers/video/via/via-gpio.c | |||
@@ -0,0 +1,285 @@ | |||
1 | /* | ||
2 | * Support for viafb GPIO ports. | ||
3 | * | ||
4 | * Copyright 2009 Jonathan Corbet <corbet@lwn.net> | ||
5 | * Distributable under version 2 of the GNU General Public License. | ||
6 | */ | ||
7 | |||
8 | #include <linux/spinlock.h> | ||
9 | #include <linux/gpio.h> | ||
10 | #include <linux/platform_device.h> | ||
11 | #include <linux/via-core.h> | ||
12 | #include <linux/via-gpio.h> | ||
13 | |||
14 | /* | ||
15 | * The ports we know about. Note that the port-25 gpios are not | ||
16 | * mentioned in the datasheet. | ||
17 | */ | ||
18 | |||
19 | struct viafb_gpio { | ||
20 | char *vg_name; /* Data sheet name */ | ||
21 | u16 vg_io_port; | ||
22 | u8 vg_port_index; | ||
23 | int vg_mask_shift; | ||
24 | }; | ||
25 | |||
26 | static struct viafb_gpio viafb_all_gpios[] = { | ||
27 | { | ||
28 | .vg_name = "VGPIO0", /* Guess - not in datasheet */ | ||
29 | .vg_io_port = VIASR, | ||
30 | .vg_port_index = 0x25, | ||
31 | .vg_mask_shift = 1 | ||
32 | }, | ||
33 | { | ||
34 | .vg_name = "VGPIO1", | ||
35 | .vg_io_port = VIASR, | ||
36 | .vg_port_index = 0x25, | ||
37 | .vg_mask_shift = 0 | ||
38 | }, | ||
39 | { | ||
40 | .vg_name = "VGPIO2", /* aka DISPCLKI0 */ | ||
41 | .vg_io_port = VIASR, | ||
42 | .vg_port_index = 0x2c, | ||
43 | .vg_mask_shift = 1 | ||
44 | }, | ||
45 | { | ||
46 | .vg_name = "VGPIO3", /* aka DISPCLKO0 */ | ||
47 | .vg_io_port = VIASR, | ||
48 | .vg_port_index = 0x2c, | ||
49 | .vg_mask_shift = 0 | ||
50 | }, | ||
51 | { | ||
52 | .vg_name = "VGPIO4", /* DISPCLKI1 */ | ||
53 | .vg_io_port = VIASR, | ||
54 | .vg_port_index = 0x3d, | ||
55 | .vg_mask_shift = 1 | ||
56 | }, | ||
57 | { | ||
58 | .vg_name = "VGPIO5", /* DISPCLKO1 */ | ||
59 | .vg_io_port = VIASR, | ||
60 | .vg_port_index = 0x3d, | ||
61 | .vg_mask_shift = 0 | ||
62 | }, | ||
63 | }; | ||
64 | |||
65 | #define VIAFB_NUM_GPIOS ARRAY_SIZE(viafb_all_gpios) | ||
66 | |||
67 | /* | ||
68 | * This structure controls the active GPIOs, which may be a subset | ||
69 | * of those which are known. | ||
70 | */ | ||
71 | |||
72 | struct viafb_gpio_cfg { | ||
73 | struct gpio_chip gpio_chip; | ||
74 | struct viafb_dev *vdev; | ||
75 | struct viafb_gpio *active_gpios[VIAFB_NUM_GPIOS]; | ||
76 | char *gpio_names[VIAFB_NUM_GPIOS]; | ||
77 | }; | ||
78 | |||
79 | /* | ||
80 | * GPIO access functions | ||
81 | */ | ||
82 | static void via_gpio_set(struct gpio_chip *chip, unsigned int nr, | ||
83 | int value) | ||
84 | { | ||
85 | struct viafb_gpio_cfg *cfg = container_of(chip, | ||
86 | struct viafb_gpio_cfg, | ||
87 | gpio_chip); | ||
88 | u8 reg; | ||
89 | struct viafb_gpio *gpio; | ||
90 | unsigned long flags; | ||
91 | |||
92 | spin_lock_irqsave(&cfg->vdev->reg_lock, flags); | ||
93 | gpio = cfg->active_gpios[nr]; | ||
94 | reg = via_read_reg(VIASR, gpio->vg_port_index); | ||
95 | reg |= 0x40 << gpio->vg_mask_shift; /* output enable */ | ||
96 | if (value) | ||
97 | reg |= 0x10 << gpio->vg_mask_shift; | ||
98 | else | ||
99 | reg &= ~(0x10 << gpio->vg_mask_shift); | ||
100 | via_write_reg(VIASR, gpio->vg_port_index, reg); | ||
101 | spin_unlock_irqrestore(&cfg->vdev->reg_lock, flags); | ||
102 | } | ||
103 | |||
104 | static int via_gpio_dir_out(struct gpio_chip *chip, unsigned int nr, | ||
105 | int value) | ||
106 | { | ||
107 | via_gpio_set(chip, nr, value); | ||
108 | return 0; | ||
109 | } | ||
110 | |||
111 | /* | ||
112 | * Set the input direction. I'm not sure this is right; we should | ||
113 | * be able to do input without disabling output. | ||
114 | */ | ||
115 | static int via_gpio_dir_input(struct gpio_chip *chip, unsigned int nr) | ||
116 | { | ||
117 | struct viafb_gpio_cfg *cfg = container_of(chip, | ||
118 | struct viafb_gpio_cfg, | ||
119 | gpio_chip); | ||
120 | struct viafb_gpio *gpio; | ||
121 | unsigned long flags; | ||
122 | |||
123 | spin_lock_irqsave(&cfg->vdev->reg_lock, flags); | ||
124 | gpio = cfg->active_gpios[nr]; | ||
125 | via_write_reg_mask(VIASR, gpio->vg_port_index, 0, | ||
126 | 0x40 << gpio->vg_mask_shift); | ||
127 | spin_unlock_irqrestore(&cfg->vdev->reg_lock, flags); | ||
128 | return 0; | ||
129 | } | ||
130 | |||
131 | static int via_gpio_get(struct gpio_chip *chip, unsigned int nr) | ||
132 | { | ||
133 | struct viafb_gpio_cfg *cfg = container_of(chip, | ||
134 | struct viafb_gpio_cfg, | ||
135 | gpio_chip); | ||
136 | u8 reg; | ||
137 | struct viafb_gpio *gpio; | ||
138 | unsigned long flags; | ||
139 | |||
140 | spin_lock_irqsave(&cfg->vdev->reg_lock, flags); | ||
141 | gpio = cfg->active_gpios[nr]; | ||
142 | reg = via_read_reg(VIASR, gpio->vg_port_index); | ||
143 | spin_unlock_irqrestore(&cfg->vdev->reg_lock, flags); | ||
144 | return reg & (0x04 << gpio->vg_mask_shift); | ||
145 | } | ||
146 | |||
147 | |||
148 | static struct viafb_gpio_cfg gpio_config = { | ||
149 | .gpio_chip = { | ||
150 | .label = "VIAFB onboard GPIO", | ||
151 | .owner = THIS_MODULE, | ||
152 | .direction_output = via_gpio_dir_out, | ||
153 | .set = via_gpio_set, | ||
154 | .direction_input = via_gpio_dir_input, | ||
155 | .get = via_gpio_get, | ||
156 | .base = -1, | ||
157 | .ngpio = 0, | ||
158 | .can_sleep = 0 | ||
159 | } | ||
160 | }; | ||
161 | |||
162 | /* | ||
163 | * Manage the software enable bit. | ||
164 | */ | ||
165 | static void viafb_gpio_enable(struct viafb_gpio *gpio) | ||
166 | { | ||
167 | via_write_reg_mask(VIASR, gpio->vg_port_index, 0x02, 0x02); | ||
168 | } | ||
169 | |||
170 | static void viafb_gpio_disable(struct viafb_gpio *gpio) | ||
171 | { | ||
172 | via_write_reg_mask(VIASR, gpio->vg_port_index, 0, 0x02); | ||
173 | } | ||
174 | |||
175 | /* | ||
176 | * Look up a specific gpio and return the number it was assigned. | ||
177 | */ | ||
178 | int viafb_gpio_lookup(const char *name) | ||
179 | { | ||
180 | int i; | ||
181 | |||
182 | for (i = 0; i < gpio_config.gpio_chip.ngpio; i++) | ||
183 | if (!strcmp(name, gpio_config.active_gpios[i]->vg_name)) | ||
184 | return gpio_config.gpio_chip.base + i; | ||
185 | return -1; | ||
186 | } | ||
187 | EXPORT_SYMBOL_GPL(viafb_gpio_lookup); | ||
188 | |||
189 | /* | ||
190 | * Platform device stuff. | ||
191 | */ | ||
192 | static __devinit int viafb_gpio_probe(struct platform_device *platdev) | ||
193 | { | ||
194 | struct viafb_dev *vdev = platdev->dev.platform_data; | ||
195 | struct via_port_cfg *port_cfg = vdev->port_cfg; | ||
196 | int i, ngpio = 0, ret; | ||
197 | struct viafb_gpio *gpio; | ||
198 | unsigned long flags; | ||
199 | |||
200 | /* | ||
201 | * Set up entries for all GPIOs which have been configured to | ||
202 | * operate as such (as opposed to as i2c ports). | ||
203 | */ | ||
204 | for (i = 0; i < VIAFB_NUM_PORTS; i++) { | ||
205 | if (port_cfg[i].mode != VIA_MODE_GPIO) | ||
206 | continue; | ||
207 | for (gpio = viafb_all_gpios; | ||
208 | gpio < viafb_all_gpios + VIAFB_NUM_GPIOS; gpio++) | ||
209 | if (gpio->vg_port_index == port_cfg[i].ioport_index) { | ||
210 | gpio_config.active_gpios[ngpio] = gpio; | ||
211 | gpio_config.gpio_names[ngpio] = gpio->vg_name; | ||
212 | ngpio++; | ||
213 | } | ||
214 | } | ||
215 | gpio_config.gpio_chip.ngpio = ngpio; | ||
216 | gpio_config.gpio_chip.names = gpio_config.gpio_names; | ||
217 | gpio_config.vdev = vdev; | ||
218 | if (ngpio == 0) { | ||
219 | printk(KERN_INFO "viafb: no GPIOs configured\n"); | ||
220 | return 0; | ||
221 | } | ||
222 | /* | ||
223 | * Enable the ports. They come in pairs, with a single | ||
224 | * enable bit for both. | ||
225 | */ | ||
226 | spin_lock_irqsave(&gpio_config.vdev->reg_lock, flags); | ||
227 | for (i = 0; i < ngpio; i += 2) | ||
228 | viafb_gpio_enable(gpio_config.active_gpios[i]); | ||
229 | spin_unlock_irqrestore(&gpio_config.vdev->reg_lock, flags); | ||
230 | /* | ||
231 | * Get registered. | ||
232 | */ | ||
233 | gpio_config.gpio_chip.base = -1; /* Dynamic */ | ||
234 | ret = gpiochip_add(&gpio_config.gpio_chip); | ||
235 | if (ret) { | ||
236 | printk(KERN_ERR "viafb: failed to add gpios (%d)\n", ret); | ||
237 | gpio_config.gpio_chip.ngpio = 0; | ||
238 | } | ||
239 | return ret; | ||
240 | } | ||
241 | |||
242 | |||
243 | static int viafb_gpio_remove(struct platform_device *platdev) | ||
244 | { | ||
245 | unsigned long flags; | ||
246 | int ret = 0, i; | ||
247 | |||
248 | /* | ||
249 | * Get unregistered. | ||
250 | */ | ||
251 | if (gpio_config.gpio_chip.ngpio > 0) { | ||
252 | ret = gpiochip_remove(&gpio_config.gpio_chip); | ||
253 | if (ret) { /* Somebody still using it? */ | ||
254 | printk(KERN_ERR "Viafb: GPIO remove failed\n"); | ||
255 | return ret; | ||
256 | } | ||
257 | } | ||
258 | /* | ||
259 | * Disable the ports. | ||
260 | */ | ||
261 | spin_lock_irqsave(&gpio_config.vdev->reg_lock, flags); | ||
262 | for (i = 0; i < gpio_config.gpio_chip.ngpio; i += 2) | ||
263 | viafb_gpio_disable(gpio_config.active_gpios[i]); | ||
264 | gpio_config.gpio_chip.ngpio = 0; | ||
265 | spin_unlock_irqrestore(&gpio_config.vdev->reg_lock, flags); | ||
266 | return ret; | ||
267 | } | ||
268 | |||
269 | static struct platform_driver via_gpio_driver = { | ||
270 | .driver = { | ||
271 | .name = "viafb-gpio", | ||
272 | }, | ||
273 | .probe = viafb_gpio_probe, | ||
274 | .remove = viafb_gpio_remove, | ||
275 | }; | ||
276 | |||
277 | int viafb_gpio_init(void) | ||
278 | { | ||
279 | return platform_driver_register(&via_gpio_driver); | ||
280 | } | ||
281 | |||
282 | void viafb_gpio_exit(void) | ||
283 | { | ||
284 | platform_driver_unregister(&via_gpio_driver); | ||
285 | } | ||