bcm63xx: add support for linux 3.8
[openwrt.git] / target / linux / brcm63xx / patches-3.8 / 107-spi-bcm63xx-fix-multi-transfer-messages.patch
1 From 725e81d507b1098cd275d4e3333c77c4b750fa79 Mon Sep 17 00:00:00 2001
2 From: Jonas Gorski <jogo@openwrt.org>
3 Date: Sun, 9 Dec 2012 01:53:05 +0100
4 Subject: [PATCH V2 2/2] spi/bcm63xx: work around inability to keep CS up
5
6 This SPI controller does not support keeping CS asserted after sending
7 a transfer.
8 Since messages expected on this SPI controller are rather short, we can
9 work around it for normal use cases by sending all transfers at once in
10 a big full duplex stream.
11
12 This means that we cannot change the speed between transfers if they
13 require CS to be kept asserted, but these would have been rejected
14 before anyway because of the inability of keeping CS asserted.
15
16 Signed-off-by: Jonas Gorski <jogo@openwrt.org>
17 ---
18 V1 -> V2:
19  * split out rejection logic into separate patch
20  * fixed return type of bcm63xx_txrx_bufs()
21  * slightly reworked bcm63xx_txrx_bufs, obsoleting one local variable
22
23  drivers/spi/spi-bcm63xx.c |  134 +++++++++++++++++++++++++++++++++++----------
24  1 file changed, 106 insertions(+), 28 deletions(-)
25
26 --- a/drivers/spi/spi-bcm63xx.c
27 +++ b/drivers/spi/spi-bcm63xx.c
28 @@ -37,6 +37,8 @@
29  
30  #define PFX            KBUILD_MODNAME
31  
32 +#define BCM63XX_SPI_MAX_PREPEND                15
33 +
34  struct bcm63xx_spi {
35         struct completion       done;
36  
37 @@ -169,13 +171,17 @@ static int bcm63xx_spi_setup(struct spi_
38         return 0;
39  }
40  
41 -static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *t)
42 +static int bcm63xx_txrx_bufs(struct spi_device *spi, struct spi_transfer *first,
43 +                               unsigned int num_transfers)
44  {
45         struct bcm63xx_spi *bs = spi_master_get_devdata(spi->master);
46         u16 msg_ctl;
47         u16 cmd;
48         u8 rx_tail;
49 -       unsigned int timeout = 0;
50 +       unsigned int i, timeout = 0, prepend_len = 0, len = 0;
51 +       struct spi_transfer *t = first;
52 +       bool do_rx = false;
53 +       bool do_tx = false;
54  
55         /* Disable the CMD_DONE interrupt */
56         bcm_spi_writeb(bs, 0, SPI_INT_MASK);
57 @@ -183,19 +189,45 @@ static int bcm63xx_txrx_bufs(struct spi_
58         dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n",
59                 t->tx_buf, t->rx_buf, t->len);
60  
61 -       if (t->tx_buf)
62 -               memcpy_toio(bs->tx_io, t->tx_buf, t->len);
63 +       if (num_transfers > 1 && t->tx_buf && t->len <= BCM63XX_SPI_MAX_PREPEND)
64 +               prepend_len = t->len;
65 +
66 +       /* prepare the buffer */
67 +       for (i = 0; i < num_transfers; i++) {
68 +               if (t->tx_buf) {
69 +                       do_tx = true;
70 +                       memcpy_toio(bs->tx_io + len, t->tx_buf, t->len);
71 +
72 +                       /* don't prepend more than one tx */
73 +                       if (t != first)
74 +                               prepend_len = 0;
75 +               }
76 +
77 +               if (t->rx_buf) {
78 +                       do_rx = true;
79 +                       /* prepend is half-duplex write only */
80 +                       if (t == first)
81 +                               prepend_len = 0;
82 +               }
83 +
84 +               len += t->len;
85 +
86 +               t = list_entry(t->transfer_list.next, struct spi_transfer,
87 +                              transfer_list);
88 +       }
89 +
90 +       len -= prepend_len;
91  
92         init_completion(&bs->done);
93  
94         /* Fill in the Message control register */
95 -       msg_ctl = (t->len << SPI_BYTE_CNT_SHIFT);
96 +       msg_ctl = (len << SPI_BYTE_CNT_SHIFT);
97  
98 -       if (t->rx_buf && t->tx_buf)
99 +       if (do_rx && do_tx && prepend_len == 0)
100                 msg_ctl |= (SPI_FD_RW << bs->msg_type_shift);
101 -       else if (t->rx_buf)
102 +       else if (do_rx)
103                 msg_ctl |= (SPI_HD_R << bs->msg_type_shift);
104 -       else if (t->tx_buf)
105 +       else if (do_tx)
106                 msg_ctl |= (SPI_HD_W << bs->msg_type_shift);
107  
108         switch (bs->msg_ctl_width) {
109 @@ -209,7 +241,7 @@ static int bcm63xx_txrx_bufs(struct spi_
110  
111         /* Issue the transfer */
112         cmd = SPI_CMD_START_IMMEDIATE;
113 -       cmd |= (0 << SPI_CMD_PREPEND_BYTE_CNT_SHIFT);
114 +       cmd |= (prepend_len << SPI_CMD_PREPEND_BYTE_CNT_SHIFT);
115         cmd |= (spi->chip_select << SPI_CMD_DEVICE_ID_SHIFT);
116         bcm_spi_writew(bs, cmd, SPI_CMD);
117  
118 @@ -223,9 +255,25 @@ static int bcm63xx_txrx_bufs(struct spi_
119         /* read out all data */
120         rx_tail = bcm_spi_readb(bs, SPI_RX_TAIL);
121  
122 +       if (do_rx && rx_tail != len)
123 +               return -EIO;
124 +
125 +       if (!rx_tail)
126 +               return 0;
127 +
128 +       len = 0;
129 +       t = first;
130         /* Read out all the data */
131 -       if (rx_tail)
132 -               memcpy_fromio(t->rx_ptr, bs->rx_io, rx_tail);
133 +       for (i = 0; i < num_transfers; i++) {
134 +               if (t->rx_buf)
135 +                       memcpy_fromio(t->rx_buf, bs->rx_io + len, t->len);
136 +
137 +               if (t != first || prepend_len == 0)
138 +                       len += t->len;
139 +
140 +               t = list_entry(t->transfer_list.next, struct spi_transfer,
141 +                              transfer_list);
142 +       }
143  
144         return 0;
145  }
146 @@ -252,46 +300,76 @@ static int bcm63xx_spi_transfer_one(stru
147                                         struct spi_message *m)
148  {
149         struct bcm63xx_spi *bs = spi_master_get_devdata(master);
150 -       struct spi_transfer *t;
151 +       struct spi_transfer *t, *first = NULL;
152         struct spi_device *spi = m->spi;
153         int status = 0;
154 +       unsigned int n_transfers = 0, total_len = 0;
155 +       bool can_use_prepend = false;
156  
157 +       /*
158 +        * This SPI controller does not support keeping CS active after a
159 +        * transfer.
160 +        * Work around this by merging as many transfers we can into one big
161 +        * full-duplex transfers.
162 +        */
163         list_for_each_entry(t, &m->transfers, transfer_list) {
164                 status = bcm63xx_spi_check_transfer(spi, t);
165                 if (status < 0)
166                         goto exit;
167  
168 +               if (!first)
169 +                       first = t;
170 +
171 +               n_transfers++;
172 +               total_len += t->len;
173 +
174 +               if (n_transfers == 2 && !first->rx_buf && !t->tx_buf &&
175 +                   first->len <= BCM63XX_SPI_MAX_PREPEND)
176 +                       can_use_prepend = true;
177 +               else if (can_use_prepend && t->tx_buf)
178 +                       can_use_prepend = false;
179 +
180                 /* we can only transfer one fifo worth of data */
181 -               if (t->len > bs->fifo_size) {
182 +               if ((can_use_prepend &&
183 +                    total_len > (bs->fifo_size + BCM63XX_SPI_MAX_PREPEND)) ||
184 +                   (!can_use_prepend && total_len > bs->fifo_size)) {
185                         dev_err(&spi->dev, "unable to do transfers larger than FIFO size (%i > %i)\n",
186 -                               t->len, bs->fifo_size);
187 +                               total_len, bs->fifo_size);
188                         status = -EINVAL;
189                         goto exit;
190                 }
191  
192 -               /* CS will be deasserted directly after transfer */
193 -               if (t->delay_usecs) {
194 -                       dev_err(&spi->dev, "unable to keep CS asserted after transfer\n");
195 +               /* all combined transfers have to have the same speed */
196 +               if (t->speed_hz != first->speed_hz) {
197 +                       dev_err(&spi->dev, "unable to change speed between transfers\n");
198                         status = -EINVAL;
199                         goto exit;
200                 }
201  
202 -               if (!t->cs_change &&
203 -                   !list_is_last(&t->transfer_list, &m->transfers)) {
204 -                       dev_err(&spi->dev, "unable to keep CS asserted between transfers\n");
205 +               /* CS will be deasserted directly after transfer */
206 +               if (t->delay_usecs) {
207 +                       dev_err(&spi->dev, "unable to keep CS asserted after transfer\n");
208                         status = -EINVAL;
209                         goto exit;
210                 }
211  
212 -               /* configure adapter for a new transfer */
213 -               bcm63xx_spi_setup_transfer(spi, t);
214 -
215 -               /* send the data */
216 -               status = bcm63xx_txrx_bufs(spi, t);
217 -               if (status)
218 -                       goto exit;
219 -
220 -               m->actual_length += t->len;
221 +               if (t->cs_change ||
222 +                   list_is_last(&t->transfer_list, &m->transfers)) {
223 +                       /* configure adapter for a new transfer */
224 +                       bcm63xx_spi_setup_transfer(spi, first);
225 +
226 +                       /* send the data */
227 +                       status = bcm63xx_txrx_bufs(spi, first, n_transfers);
228 +                       if (status)
229 +                               goto exit;
230 +
231 +                       m->actual_length += total_len;
232 +
233 +                       first = NULL;
234 +                       n_transfers = 0;
235 +                       total_len = 0;
236 +                       can_use_prepend = false;
237 +               }
238         }
239  exit:
240         m->status = status;