]>
Commit | Line | Data |
---|---|---|
ead6db08 MG |
1 | /* |
2 | * 74Hx164 - Generic serial-in/parallel-out 8-bits shift register GPIO driver | |
3 | * | |
4 | * Copyright (C) 2010 Gabor Juhos <juhosg@openwrt.org> | |
5 | * Copyright (C) 2010 Miguel Gaio <miguel.gaio@efixo.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License version 2 as | |
9 | * published by the Free Software Foundation. | |
10 | */ | |
11 | ||
12 | #include <linux/init.h> | |
13 | #include <linux/mutex.h> | |
14 | #include <linux/spi/spi.h> | |
15 | #include <linux/spi/74x164.h> | |
16 | #include <linux/gpio.h> | |
17 | #include <linux/slab.h> | |
18 | ||
19 | #define GEN_74X164_GPIO_COUNT 8 | |
20 | ||
21 | ||
22 | struct gen_74x164_chip { | |
23 | struct spi_device *spi; | |
24 | struct gpio_chip gpio_chip; | |
25 | struct mutex lock; | |
26 | u8 port_config; | |
27 | }; | |
28 | ||
29 | static void gen_74x164_set_value(struct gpio_chip *, unsigned, int); | |
30 | ||
31 | static struct gen_74x164_chip *gpio_to_chip(struct gpio_chip *gc) | |
32 | { | |
33 | return container_of(gc, struct gen_74x164_chip, gpio_chip); | |
34 | } | |
35 | ||
36 | static int __gen_74x164_write_config(struct gen_74x164_chip *chip) | |
37 | { | |
38 | return spi_write(chip->spi, | |
39 | &chip->port_config, sizeof(chip->port_config)); | |
40 | } | |
41 | ||
42 | static int gen_74x164_direction_output(struct gpio_chip *gc, | |
43 | unsigned offset, int val) | |
44 | { | |
45 | gen_74x164_set_value(gc, offset, val); | |
46 | return 0; | |
47 | } | |
48 | ||
49 | static int gen_74x164_get_value(struct gpio_chip *gc, unsigned offset) | |
50 | { | |
51 | struct gen_74x164_chip *chip = gpio_to_chip(gc); | |
52 | int ret; | |
53 | ||
54 | mutex_lock(&chip->lock); | |
55 | ret = (chip->port_config >> offset) & 0x1; | |
56 | mutex_unlock(&chip->lock); | |
57 | ||
58 | return ret; | |
59 | } | |
60 | ||
61 | static void gen_74x164_set_value(struct gpio_chip *gc, | |
62 | unsigned offset, int val) | |
63 | { | |
64 | struct gen_74x164_chip *chip = gpio_to_chip(gc); | |
65 | ||
66 | mutex_lock(&chip->lock); | |
67 | if (val) | |
68 | chip->port_config |= (1 << offset); | |
69 | else | |
70 | chip->port_config &= ~(1 << offset); | |
71 | ||
72 | __gen_74x164_write_config(chip); | |
73 | mutex_unlock(&chip->lock); | |
74 | } | |
75 | ||
76 | static int __devinit gen_74x164_probe(struct spi_device *spi) | |
77 | { | |
78 | struct gen_74x164_chip *chip; | |
79 | struct gen_74x164_chip_platform_data *pdata; | |
80 | int ret; | |
81 | ||
82 | pdata = spi->dev.platform_data; | |
83 | if (!pdata || !pdata->base) { | |
84 | dev_dbg(&spi->dev, "incorrect or missing platform data\n"); | |
85 | return -EINVAL; | |
86 | } | |
87 | ||
88 | /* | |
89 | * bits_per_word cannot be configured in platform data | |
90 | */ | |
91 | spi->bits_per_word = 8; | |
92 | ||
93 | ret = spi_setup(spi); | |
94 | if (ret < 0) | |
95 | return ret; | |
96 | ||
97 | chip = kzalloc(sizeof(*chip), GFP_KERNEL); | |
98 | if (!chip) | |
99 | return -ENOMEM; | |
100 | ||
101 | mutex_init(&chip->lock); | |
102 | ||
103 | dev_set_drvdata(&spi->dev, chip); | |
104 | ||
105 | chip->spi = spi; | |
106 | ||
107 | chip->gpio_chip.label = GEN_74X164_DRIVER_NAME, | |
108 | chip->gpio_chip.direction_output = gen_74x164_direction_output; | |
109 | chip->gpio_chip.get = gen_74x164_get_value; | |
110 | chip->gpio_chip.set = gen_74x164_set_value; | |
111 | chip->gpio_chip.base = pdata->base; | |
112 | chip->gpio_chip.ngpio = GEN_74X164_GPIO_COUNT; | |
113 | chip->gpio_chip.can_sleep = 1; | |
114 | chip->gpio_chip.dev = &spi->dev; | |
115 | chip->gpio_chip.owner = THIS_MODULE; | |
116 | ||
117 | ret = __gen_74x164_write_config(chip); | |
118 | if (ret) { | |
119 | dev_err(&spi->dev, "Failed writing: %d\n", ret); | |
120 | goto exit_destroy; | |
121 | } | |
122 | ||
123 | ret = gpiochip_add(&chip->gpio_chip); | |
124 | if (ret) | |
125 | goto exit_destroy; | |
126 | ||
127 | return ret; | |
128 | ||
129 | exit_destroy: | |
130 | dev_set_drvdata(&spi->dev, NULL); | |
131 | mutex_destroy(&chip->lock); | |
132 | kfree(chip); | |
133 | return ret; | |
134 | } | |
135 | ||
136 | static int gen_74x164_remove(struct spi_device *spi) | |
137 | { | |
138 | struct gen_74x164_chip *chip; | |
139 | int ret; | |
140 | ||
141 | chip = dev_get_drvdata(&spi->dev); | |
142 | if (chip == NULL) | |
143 | return -ENODEV; | |
144 | ||
145 | dev_set_drvdata(&spi->dev, NULL); | |
146 | ||
147 | ret = gpiochip_remove(&chip->gpio_chip); | |
148 | if (!ret) { | |
149 | mutex_destroy(&chip->lock); | |
150 | kfree(chip); | |
151 | } else | |
152 | dev_err(&spi->dev, "Failed to remove the GPIO controller: %d\n", | |
153 | ret); | |
154 | ||
155 | return ret; | |
156 | } | |
157 | ||
158 | static struct spi_driver gen_74x164_driver = { | |
159 | .driver = { | |
160 | .name = GEN_74X164_DRIVER_NAME, | |
161 | .owner = THIS_MODULE, | |
162 | }, | |
163 | .probe = gen_74x164_probe, | |
164 | .remove = __devexit_p(gen_74x164_remove), | |
165 | }; | |
166 | ||
167 | static int __init gen_74x164_init(void) | |
168 | { | |
169 | return spi_register_driver(&gen_74x164_driver); | |
170 | } | |
171 | subsys_initcall(gen_74x164_init); | |
172 | ||
173 | static void __exit gen_74x164_exit(void) | |
174 | { | |
175 | spi_unregister_driver(&gen_74x164_driver); | |
176 | } | |
177 | module_exit(gen_74x164_exit); | |
178 | ||
179 | MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); | |
180 | MODULE_AUTHOR("Miguel Gaio <miguel.gaio@efixo.com>"); | |
181 | MODULE_DESCRIPTION("GPIO expander driver for 74X164 8-bits shift register"); | |
182 | MODULE_LICENSE("GPL v2"); |