diff options
author | Lars-Peter Clausen <lars@metafoo.de> | 2012-07-19 12:44:07 -0400 |
---|---|---|
committer | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2012-07-20 06:08:44 -0400 |
commit | b316590043372c2ca0040567a480396eb4bb2f0b (patch) | |
tree | d256f01dc1cce66321c2e82f5c551b8f2a4806ab /drivers/spi | |
parent | 8b17e0559fe728b89b45f4d88b4b30b498862709 (diff) |
spi: Add AD-FMCOMMS1-EBZ I2C-SPI bridge driver
This patch adds support for the I2C-SPI bridge which can be found on the Analog
Devices AD-FMCOMMS1-EBZ board.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'drivers/spi')
-rw-r--r-- | drivers/spi/Kconfig | 7 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/spi-xcomm.c | 276 |
3 files changed, 284 insertions, 0 deletions
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 0a7256924cf3..12468e5bfef8 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig | |||
@@ -384,6 +384,13 @@ config SPI_TXX9 | |||
384 | help | 384 | help |
385 | SPI driver for Toshiba TXx9 MIPS SoCs | 385 | SPI driver for Toshiba TXx9 MIPS SoCs |
386 | 386 | ||
387 | config SPI_XCOMM | ||
388 | tristate "Analog Devices AD-FMCOMMS1-EBZ SPI-I2C-bridge driver" | ||
389 | depends on I2C | ||
390 | help | ||
391 | Support for the SPI-I2C bridge found on the Analog Devices | ||
392 | AD-FMCOMMS1-EBZ board. | ||
393 | |||
387 | config SPI_XILINX | 394 | config SPI_XILINX |
388 | tristate "Xilinx SPI controller common module" | 395 | tristate "Xilinx SPI controller common module" |
389 | depends on HAS_IOMEM && EXPERIMENTAL | 396 | depends on HAS_IOMEM && EXPERIMENTAL |
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 9d75d2198ff5..273f50d1127a 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile | |||
@@ -61,5 +61,6 @@ obj-$(CONFIG_SPI_TI_SSP) += spi-ti-ssp.o | |||
61 | obj-$(CONFIG_SPI_TLE62X0) += spi-tle62x0.o | 61 | obj-$(CONFIG_SPI_TLE62X0) += spi-tle62x0.o |
62 | obj-$(CONFIG_SPI_TOPCLIFF_PCH) += spi-topcliff-pch.o | 62 | obj-$(CONFIG_SPI_TOPCLIFF_PCH) += spi-topcliff-pch.o |
63 | obj-$(CONFIG_SPI_TXX9) += spi-txx9.o | 63 | obj-$(CONFIG_SPI_TXX9) += spi-txx9.o |
64 | obj-$(CONFIG_SPI_XCOMM) += spi-xcomm.o | ||
64 | obj-$(CONFIG_SPI_XILINX) += spi-xilinx.o | 65 | obj-$(CONFIG_SPI_XILINX) += spi-xilinx.o |
65 | 66 | ||
diff --git a/drivers/spi/spi-xcomm.c b/drivers/spi/spi-xcomm.c new file mode 100644 index 000000000000..266a847e2992 --- /dev/null +++ b/drivers/spi/spi-xcomm.c | |||
@@ -0,0 +1,276 @@ | |||
1 | /* | ||
2 | * Analog Devices AD-FMCOMMS1-EBZ board I2C-SPI bridge driver | ||
3 | * | ||
4 | * Copyright 2012 Analog Devices Inc. | ||
5 | * Author: Lars-Peter Clausen <lars@metafoo.de> | ||
6 | * | ||
7 | * Licensed under the GPL-2 or later. | ||
8 | */ | ||
9 | |||
10 | #include <linux/kernel.h> | ||
11 | #include <linux/init.h> | ||
12 | #include <linux/module.h> | ||
13 | #include <linux/delay.h> | ||
14 | #include <linux/i2c.h> | ||
15 | #include <linux/spi/spi.h> | ||
16 | #include <asm/unaligned.h> | ||
17 | |||
18 | #define SPI_XCOMM_SETTINGS_LEN_OFFSET 10 | ||
19 | #define SPI_XCOMM_SETTINGS_3WIRE BIT(6) | ||
20 | #define SPI_XCOMM_SETTINGS_CS_HIGH BIT(5) | ||
21 | #define SPI_XCOMM_SETTINGS_SAMPLE_END BIT(4) | ||
22 | #define SPI_XCOMM_SETTINGS_CPHA BIT(3) | ||
23 | #define SPI_XCOMM_SETTINGS_CPOL BIT(2) | ||
24 | #define SPI_XCOMM_SETTINGS_CLOCK_DIV_MASK 0x3 | ||
25 | #define SPI_XCOMM_SETTINGS_CLOCK_DIV_64 0x2 | ||
26 | #define SPI_XCOMM_SETTINGS_CLOCK_DIV_16 0x1 | ||
27 | #define SPI_XCOMM_SETTINGS_CLOCK_DIV_4 0x0 | ||
28 | |||
29 | #define SPI_XCOMM_CMD_UPDATE_CONFIG 0x03 | ||
30 | #define SPI_XCOMM_CMD_WRITE 0x04 | ||
31 | |||
32 | #define SPI_XCOMM_CLOCK 48000000 | ||
33 | |||
34 | struct spi_xcomm { | ||
35 | struct i2c_client *i2c; | ||
36 | |||
37 | uint16_t settings; | ||
38 | uint16_t chipselect; | ||
39 | |||
40 | unsigned int current_speed; | ||
41 | |||
42 | uint8_t buf[63]; | ||
43 | }; | ||
44 | |||
45 | static int spi_xcomm_sync_config(struct spi_xcomm *spi_xcomm, unsigned int len) | ||
46 | { | ||
47 | uint16_t settings; | ||
48 | uint8_t *buf = spi_xcomm->buf; | ||
49 | |||
50 | settings = spi_xcomm->settings; | ||
51 | settings |= len << SPI_XCOMM_SETTINGS_LEN_OFFSET; | ||
52 | |||
53 | buf[0] = SPI_XCOMM_CMD_UPDATE_CONFIG; | ||
54 | put_unaligned_be16(settings, &buf[1]); | ||
55 | put_unaligned_be16(spi_xcomm->chipselect, &buf[3]); | ||
56 | |||
57 | return i2c_master_send(spi_xcomm->i2c, buf, 5); | ||
58 | } | ||
59 | |||
60 | static void spi_xcomm_chipselect(struct spi_xcomm *spi_xcomm, | ||
61 | struct spi_device *spi, int is_active) | ||
62 | { | ||
63 | unsigned long cs = spi->chip_select; | ||
64 | uint16_t chipselect = spi_xcomm->chipselect; | ||
65 | |||
66 | if (is_active) | ||
67 | chipselect |= BIT(cs); | ||
68 | else | ||
69 | chipselect &= ~BIT(cs); | ||
70 | |||
71 | spi_xcomm->chipselect = chipselect; | ||
72 | } | ||
73 | |||
74 | static int spi_xcomm_setup_transfer(struct spi_xcomm *spi_xcomm, | ||
75 | struct spi_device *spi, struct spi_transfer *t, unsigned int *settings) | ||
76 | { | ||
77 | unsigned int speed; | ||
78 | |||
79 | if ((t->bits_per_word && t->bits_per_word != 8) || t->len > 62) | ||
80 | return -EINVAL; | ||
81 | |||
82 | speed = t->speed_hz ? t->speed_hz : spi->max_speed_hz; | ||
83 | |||
84 | if (speed != spi_xcomm->current_speed) { | ||
85 | unsigned int divider = DIV_ROUND_UP(SPI_XCOMM_CLOCK, speed); | ||
86 | if (divider >= 64) | ||
87 | *settings |= SPI_XCOMM_SETTINGS_CLOCK_DIV_64; | ||
88 | else if (divider >= 16) | ||
89 | *settings |= SPI_XCOMM_SETTINGS_CLOCK_DIV_16; | ||
90 | else | ||
91 | *settings |= SPI_XCOMM_SETTINGS_CLOCK_DIV_4; | ||
92 | |||
93 | spi_xcomm->current_speed = speed; | ||
94 | } | ||
95 | |||
96 | if (spi->mode & SPI_CPOL) | ||
97 | *settings |= SPI_XCOMM_SETTINGS_CPOL; | ||
98 | else | ||
99 | *settings &= ~SPI_XCOMM_SETTINGS_CPOL; | ||
100 | |||
101 | if (spi->mode & SPI_CPHA) | ||
102 | *settings &= ~SPI_XCOMM_SETTINGS_CPHA; | ||
103 | else | ||
104 | *settings |= SPI_XCOMM_SETTINGS_CPHA; | ||
105 | |||
106 | if (spi->mode & SPI_3WIRE) | ||
107 | *settings |= SPI_XCOMM_SETTINGS_3WIRE; | ||
108 | else | ||
109 | *settings &= ~SPI_XCOMM_SETTINGS_3WIRE; | ||
110 | |||
111 | return 0; | ||
112 | } | ||
113 | |||
114 | static int spi_xcomm_txrx_bufs(struct spi_xcomm *spi_xcomm, | ||
115 | struct spi_device *spi, struct spi_transfer *t) | ||
116 | { | ||
117 | int ret; | ||
118 | |||
119 | if (t->tx_buf) { | ||
120 | spi_xcomm->buf[0] = SPI_XCOMM_CMD_WRITE; | ||
121 | memcpy(spi_xcomm->buf + 1, t->tx_buf, t->len); | ||
122 | |||
123 | ret = i2c_master_send(spi_xcomm->i2c, spi_xcomm->buf, t->len + 1); | ||
124 | if (ret < 0) | ||
125 | return ret; | ||
126 | else if (ret != t->len + 1) | ||
127 | return -EIO; | ||
128 | } else if (t->rx_buf) { | ||
129 | ret = i2c_master_recv(spi_xcomm->i2c, t->rx_buf, t->len); | ||
130 | if (ret < 0) | ||
131 | return ret; | ||
132 | else if (ret != t->len) | ||
133 | return -EIO; | ||
134 | } | ||
135 | |||
136 | return t->len; | ||
137 | } | ||
138 | |||
139 | static int spi_xcomm_transfer_one(struct spi_master *master, | ||
140 | struct spi_message *msg) | ||
141 | { | ||
142 | struct spi_xcomm *spi_xcomm = spi_master_get_devdata(master); | ||
143 | unsigned int settings = spi_xcomm->settings; | ||
144 | struct spi_device *spi = msg->spi; | ||
145 | unsigned cs_change = 0; | ||
146 | struct spi_transfer *t; | ||
147 | bool is_first = true; | ||
148 | int status = 0; | ||
149 | bool is_last; | ||
150 | |||
151 | is_first = true; | ||
152 | |||
153 | spi_xcomm_chipselect(spi_xcomm, spi, true); | ||
154 | |||
155 | list_for_each_entry(t, &msg->transfers, transfer_list) { | ||
156 | |||
157 | if (!t->tx_buf && !t->rx_buf && t->len) { | ||
158 | status = -EINVAL; | ||
159 | break; | ||
160 | } | ||
161 | |||
162 | status = spi_xcomm_setup_transfer(spi_xcomm, spi, t, &settings); | ||
163 | if (status < 0) | ||
164 | break; | ||
165 | |||
166 | is_last = list_is_last(&t->transfer_list, &msg->transfers); | ||
167 | cs_change = t->cs_change; | ||
168 | |||
169 | if (cs_change ^ is_last) | ||
170 | settings |= BIT(5); | ||
171 | else | ||
172 | settings &= ~BIT(5); | ||
173 | |||
174 | if (t->rx_buf) { | ||
175 | spi_xcomm->settings = settings; | ||
176 | status = spi_xcomm_sync_config(spi_xcomm, t->len); | ||
177 | if (status < 0) | ||
178 | break; | ||
179 | } else if (settings != spi_xcomm->settings || is_first) { | ||
180 | spi_xcomm->settings = settings; | ||
181 | status = spi_xcomm_sync_config(spi_xcomm, 0); | ||
182 | if (status < 0) | ||
183 | break; | ||
184 | } | ||
185 | |||
186 | if (t->len) { | ||
187 | status = spi_xcomm_txrx_bufs(spi_xcomm, spi, t); | ||
188 | |||
189 | if (status < 0) | ||
190 | break; | ||
191 | |||
192 | if (status > 0) | ||
193 | msg->actual_length += status; | ||
194 | } | ||
195 | status = 0; | ||
196 | |||
197 | if (t->delay_usecs) | ||
198 | udelay(t->delay_usecs); | ||
199 | |||
200 | is_first = false; | ||
201 | } | ||
202 | |||
203 | if (status != 0 || !cs_change) | ||
204 | spi_xcomm_chipselect(spi_xcomm, spi, false); | ||
205 | |||
206 | msg->status = status; | ||
207 | spi_finalize_current_message(master); | ||
208 | |||
209 | return status; | ||
210 | } | ||
211 | |||
212 | static int spi_xcomm_setup(struct spi_device *spi) | ||
213 | { | ||
214 | if (spi->bits_per_word != 8) | ||
215 | return -EINVAL; | ||
216 | |||
217 | return 0; | ||
218 | } | ||
219 | |||
220 | static int __devinit spi_xcomm_probe(struct i2c_client *i2c, | ||
221 | const struct i2c_device_id *id) | ||
222 | { | ||
223 | struct spi_xcomm *spi_xcomm; | ||
224 | struct spi_master *master; | ||
225 | int ret; | ||
226 | |||
227 | master = spi_alloc_master(&i2c->dev, sizeof(*spi_xcomm)); | ||
228 | if (!master) | ||
229 | return -ENOMEM; | ||
230 | |||
231 | spi_xcomm = spi_master_get_devdata(master); | ||
232 | spi_xcomm->i2c = i2c; | ||
233 | |||
234 | master->num_chipselect = 16; | ||
235 | master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_3WIRE; | ||
236 | master->flags = SPI_MASTER_HALF_DUPLEX; | ||
237 | master->setup = spi_xcomm_setup; | ||
238 | master->transfer_one_message = spi_xcomm_transfer_one; | ||
239 | master->dev.of_node = i2c->dev.of_node; | ||
240 | i2c_set_clientdata(i2c, master); | ||
241 | |||
242 | ret = spi_register_master(master); | ||
243 | if (ret < 0) | ||
244 | spi_master_put(master); | ||
245 | |||
246 | return ret; | ||
247 | } | ||
248 | |||
249 | static int __devexit spi_xcomm_remove(struct i2c_client *i2c) | ||
250 | { | ||
251 | struct spi_master *master = i2c_get_clientdata(i2c); | ||
252 | |||
253 | spi_unregister_master(master); | ||
254 | |||
255 | return 0; | ||
256 | } | ||
257 | |||
258 | static const struct i2c_device_id spi_xcomm_ids[] = { | ||
259 | { "spi-xcomm" }, | ||
260 | { }, | ||
261 | }; | ||
262 | |||
263 | static struct i2c_driver spi_xcomm_driver = { | ||
264 | .driver = { | ||
265 | .name = "spi-xcomm", | ||
266 | .owner = THIS_MODULE, | ||
267 | }, | ||
268 | .id_table = spi_xcomm_ids, | ||
269 | .probe = spi_xcomm_probe, | ||
270 | .remove = __devexit_p(spi_xcomm_remove), | ||
271 | }; | ||
272 | module_i2c_driver(spi_xcomm_driver); | ||
273 | |||
274 | MODULE_LICENSE("GPL"); | ||
275 | MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); | ||
276 | MODULE_DESCRIPTION("Analog Devices AD-FMCOMMS1-EBZ board I2C-SPI bridge driver"); | ||