diff options
Diffstat (limited to 'drivers/spi')
-rw-r--r-- | drivers/spi/spi-bcm63xx.c | 134 |
1 files changed, 106 insertions, 28 deletions
diff --git a/drivers/spi/spi-bcm63xx.c b/drivers/spi/spi-bcm63xx.c index 27667c17ce90..9578af782a77 100644 --- a/drivers/spi/spi-bcm63xx.c +++ b/drivers/spi/spi-bcm63xx.c | |||
@@ -37,6 +37,8 @@ | |||
37 | 37 | ||
38 | #define PFX KBUILD_MODNAME | 38 | #define PFX KBUILD_MODNAME |
39 | 39 | ||
40 | #define BCM63XX_SPI_MAX_PREPEND 15 | ||
41 | |||
40 | struct bcm63xx_spi { | 42 | struct bcm63xx_spi { |
41 | struct completion done; | 43 | struct completion done; |
42 | 44 | ||
@@ -169,13 +171,17 @@ static int bcm63xx_spi_setup(struct spi_device *spi) | |||
169 | return 0; | 171 | return 0; |
170 | } | 172 | } |
171 | 173 | ||
172 | static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) | 174 | static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *first, |
175 | unsigned int num_transfers) | ||
173 | { | 176 | { |
174 | struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); | 177 | struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master); |
175 | u16 msg_ctl; | 178 | u16 msg_ctl; |
176 | u16 cmd; | 179 | u16 cmd; |
177 | u8 rx_tail; | 180 | u8 rx_tail; |
178 | unsigned int timeout = 0; | 181 | unsigned int i, timeout = 0, prepend_len = 0, len = 0; |
182 | struct spi_transfer *t = first; | ||
183 | bool do_rx = false; | ||
184 | bool do_tx = false; | ||
179 | 185 | ||
180 | /* Disable the CMD_DONE interrupt */ | 186 | /* Disable the CMD_DONE interrupt */ |
181 | bcm_spi_writeb(bs, 0, SPI_INT_MASK); | 187 | bcm_spi_writeb(bs, 0, SPI_INT_MASK); |
@@ -183,19 +189,45 @@ static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) | |||
183 | dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n", | 189 | dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n", |
184 | t->tx_buf, t->rx_buf, t->len); | 190 | t->tx_buf, t->rx_buf, t->len); |
185 | 191 | ||
186 | if (t->tx_buf) | 192 | if (num_transfers > 1 && t->tx_buf && t->len <= BCM63XX_SPI_MAX_PREPEND) |
187 | memcpy_toio(bs->tx_io, t->tx_buf, t->len); | 193 | prepend_len = t->len; |
194 | |||
195 | /* prepare the buffer */ | ||
196 | for (i = 0; i < num_transfers; i++) { | ||
197 | if (t->tx_buf) { | ||
198 | do_tx = true; | ||
199 | memcpy_toio(bs->tx_io + len, t->tx_buf, t->len); | ||
200 | |||
201 | /* don't prepend more than one tx */ | ||
202 | if (t != first) | ||
203 | prepend_len = 0; | ||
204 | } | ||
205 | |||
206 | if (t->rx_buf) { | ||
207 | do_rx = true; | ||
208 | /* prepend is half-duplex write only */ | ||
209 | if (t == first) | ||
210 | prepend_len = 0; | ||
211 | } | ||
212 | |||
213 | len += t->len; | ||
214 | |||
215 | t = list_entry(t->transfer_list.next, struct spi_transfer, | ||
216 | transfer_list); | ||
217 | } | ||
218 | |||
219 | len -= prepend_len; | ||
188 | 220 | ||
189 | init_completion(&bs->done); | 221 | init_completion(&bs->done); |
190 | 222 | ||
191 | /* Fill in the Message control register */ | 223 | /* Fill in the Message control register */ |
192 | msg_ctl = (t->len << SPI_BYTE_CNT_SHIFT); | 224 | msg_ctl = (len << SPI_BYTE_CNT_SHIFT); |
193 | 225 | ||
194 | if (t->rx_buf && t->tx_buf) | 226 | if (do_rx && do_tx && prepend_len == 0) |
195 | msg_ctl |= (SPI_FD_RW << bs->msg_type_shift); | 227 | msg_ctl |= (SPI_FD_RW << bs->msg_type_shift); |
196 | else if (t->rx_buf) | 228 | else if (do_rx) |
197 | msg_ctl |= (SPI_HD_R << bs->msg_type_shift); | 229 | msg_ctl |= (SPI_HD_R << bs->msg_type_shift); |
198 | else if (t->tx_buf) | 230 | else if (do_tx) |
199 | msg_ctl |= (SPI_HD_W << bs->msg_type_shift); | 231 | msg_ctl |= (SPI_HD_W << bs->msg_type_shift); |
200 | 232 | ||
201 | switch (bs->msg_ctl_width) { | 233 | switch (bs->msg_ctl_width) { |
@@ -209,7 +241,7 @@ static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) | |||
209 | 241 | ||
210 | /* Issue the transfer */ | 242 | /* Issue the transfer */ |
211 | cmd = SPI_CMD_START_IMMEDIATE; | 243 | cmd = SPI_CMD_START_IMMEDIATE; |
212 | cmd |= (0 << SPI_CMD_PREPEND_BYTE_CNT_SHIFT); | 244 | cmd |= (prepend_len << SPI_CMD_PREPEND_BYTE_CNT_SHIFT); |
213 | cmd |= (spi->chip_select << SPI_CMD_DEVICE_ID_SHIFT); | 245 | cmd |= (spi->chip_select << SPI_CMD_DEVICE_ID_SHIFT); |
214 | bcm_spi_writew(bs, cmd, SPI_CMD); | 246 | bcm_spi_writew(bs, cmd, SPI_CMD); |
215 | 247 | ||
@@ -223,9 +255,25 @@ static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) | |||
223 | /* read out all data */ | 255 | /* read out all data */ |
224 | rx_tail = bcm_spi_readb(bs, SPI_RX_TAIL); | 256 | rx_tail = bcm_spi_readb(bs, SPI_RX_TAIL); |
225 | 257 | ||
258 | if (do_rx && rx_tail != len) | ||
259 | return -EIO; | ||
260 | |||
261 | if (!rx_tail) | ||
262 | return 0; | ||
263 | |||
264 | len = 0; | ||
265 | t = first; | ||
226 | /* Read out all the data */ | 266 | /* Read out all the data */ |
227 | if (rx_tail) | 267 | for (i = 0; i < num_transfers; i++) { |
228 | memcpy_fromio(t->rx_ptr, bs->rx_io, rx_tail); | 268 | if (t->rx_buf) |
269 | memcpy_fromio(t->rx_buf, bs->rx_io + len, t->len); | ||
270 | |||
271 | if (t != first || prepend_len == 0) | ||
272 | len += t->len; | ||
273 | |||
274 | t = list_entry(t->transfer_list.next, struct spi_transfer, | ||
275 | transfer_list); | ||
276 | } | ||
229 | 277 | ||
230 | return 0; | 278 | return 0; |
231 | } | 279 | } |
@@ -252,46 +300,76 @@ static int bcm63xx_spi_transfer_one(struct spi_master *master, | |||
252 | struct spi_message *m) | 300 | struct spi_message *m) |
253 | { | 301 | { |
254 | struct bcm63xx_spi *bs = spi_master_get_devdata(master); | 302 | struct bcm63xx_spi *bs = spi_master_get_devdata(master); |
255 | struct spi_transfer *t; | 303 | struct spi_transfer *t, *first = NULL; |
256 | struct spi_device *spi = m->spi; | 304 | struct spi_device *spi = m->spi; |
257 | int status = 0; | 305 | int status = 0; |
258 | 306 | unsigned int n_transfers = 0, total_len = 0; | |
307 | bool can_use_prepend = false; | ||
308 | |||
309 | /* | ||
310 | * This SPI controller does not support keeping CS active after a | ||
311 | * transfer. | ||
312 | * Work around this by merging as many transfers we can into one big | ||
313 | * full-duplex transfers. | ||
314 | */ | ||
259 | list_for_each_entry(t, &m->transfers, transfer_list) { | 315 | list_for_each_entry(t, &m->transfers, transfer_list) { |
260 | status = bcm63xx_spi_check_transfer(spi, t); | 316 | status = bcm63xx_spi_check_transfer(spi, t); |
261 | if (status < 0) | 317 | if (status < 0) |
262 | goto exit; | 318 | goto exit; |
263 | 319 | ||
320 | if (!first) | ||
321 | first = t; | ||
322 | |||
323 | n_transfers++; | ||
324 | total_len += t->len; | ||
325 | |||
326 | if (n_transfers == 2 && !first->rx_buf && !t->tx_buf && | ||
327 | first->len <= BCM63XX_SPI_MAX_PREPEND) | ||
328 | can_use_prepend = true; | ||
329 | else if (can_use_prepend && t->tx_buf) | ||
330 | can_use_prepend = false; | ||
331 | |||
264 | /* we can only transfer one fifo worth of data */ | 332 | /* we can only transfer one fifo worth of data */ |
265 | if (t->len > bs->fifo_size) { | 333 | if ((can_use_prepend && |
334 | total_len > (bs->fifo_size + BCM63XX_SPI_MAX_PREPEND)) || | ||
335 | (!can_use_prepend && total_len > bs->fifo_size)) { | ||
266 | dev_err(&spi->dev, "unable to do transfers larger than FIFO size (%i > %i)\n", | 336 | dev_err(&spi->dev, "unable to do transfers larger than FIFO size (%i > %i)\n", |
267 | t->len, bs->fifo_size); | 337 | total_len, bs->fifo_size); |
268 | status = -EINVAL; | 338 | status = -EINVAL; |
269 | goto exit; | 339 | goto exit; |
270 | } | 340 | } |
271 | 341 | ||
272 | /* CS will be deasserted directly after transfer */ | 342 | /* all combined transfers have to have the same speed */ |
273 | if (t->delay_usecs) { | 343 | if (t->speed_hz != first->speed_hz) { |
274 | dev_err(&spi->dev, "unable to keep CS asserted after transfer\n"); | 344 | dev_err(&spi->dev, "unable to change speed between transfers\n"); |
275 | status = -EINVAL; | 345 | status = -EINVAL; |
276 | goto exit; | 346 | goto exit; |
277 | } | 347 | } |
278 | 348 | ||
279 | if (!t->cs_change && | 349 | /* CS will be deasserted directly after transfer */ |
280 | !list_is_last(&t->transfer_list, &m->transfers)) { | 350 | if (t->delay_usecs) { |
281 | dev_err(&spi->dev, "unable to keep CS asserted between transfers\n"); | 351 | dev_err(&spi->dev, "unable to keep CS asserted after transfer\n"); |
282 | status = -EINVAL; | 352 | status = -EINVAL; |
283 | goto exit; | 353 | goto exit; |
284 | } | 354 | } |
285 | 355 | ||
286 | /* configure adapter for a new transfer */ | 356 | if (t->cs_change || |
287 | bcm63xx_spi_setup_transfer(spi, t); | 357 | list_is_last(&t->transfer_list, &m->transfers)) { |
358 | /* configure adapter for a new transfer */ | ||
359 | bcm63xx_spi_setup_transfer(spi, first); | ||
288 | 360 | ||
289 | /* send the data */ | 361 | /* send the data */ |
290 | status = bcm63xx_txrx_bufs(spi, t); | 362 | status = bcm63xx_txrx_bufs(spi, first, n_transfers); |
291 | if (status) | 363 | if (status) |
292 | goto exit; | 364 | goto exit; |
365 | |||
366 | m->actual_length += total_len; | ||
293 | 367 | ||
294 | m->actual_length += t->len; | 368 | first = NULL; |
369 | n_transfers = 0; | ||
370 | total_len = 0; | ||
371 | can_use_prepend = false; | ||
372 | } | ||
295 | } | 373 | } |
296 | exit: | 374 | exit: |
297 | m->status = status; | 375 | m->status = status; |