source: rtems/bsps/arm/atsam/i2c/atsam_i2c_bus.c @ 5cb23f4b

5
Last change on this file since 5cb23f4b was 5cb23f4b, checked in by Sebastian Huber <sebastian.huber@…>, on 10/01/18 at 06:59:14

bsp/atsam: Use binary semaphore for I2C

Remove superfluous sleep before each I2C transfer. Reset I2C module
after transfer timeouts.

Update #3534.

  • Property mode set to 100644
File size: 9.1 KB
Line 
1/*
2 * Copyright (c) 2016 embedded brains GmbH.  All rights reserved.
3 *
4 *  embedded brains GmbH
5 *  Dornierstr. 4
6 *  82178 Puchheim
7 *  Germany
8 *  <info@embedded-brains.de>
9 *
10 * The license and distribution terms for this file may be
11 * found in the file LICENSE in this distribution or at
12 * http://www.rtems.org/license/LICENSE.
13 */
14
15#include <bsp/atsam-clock-config.h>
16#include <bsp/atsam-i2c.h>
17
18#include <rtems/irq-extension.h>
19
20#define ATSAMV_I2C_IRQ_ERROR \
21        (TWIHS_IDR_ARBLST \
22        | TWIHS_IDR_TOUT \
23        | TWIHS_IDR_OVRE \
24        | TWIHS_IDR_UNRE \
25        | TWIHS_IDR_NACK)
26
27#define TEN_BIT_MASK 0xFC00
28#define SEVEN_BIT_MASK 0xFF80
29#define TEN_BIT_START_ADDR_MASK 0x78
30#define LAST_TWO_BITS_MASK 0x03
31#define LAST_BYTE_MASK 0x00FF
32
33static void
34atsam_i2c_disable_interrupts(Twihs *regs)
35{
36        regs->TWIHS_IDR = 0xFFFFFFFF;
37}
38
39static void
40atsam_i2c_set_transfer_status(transfer_desc *trans_desc, transfer_state state)
41{
42        trans_desc->trans_state = state;
43}
44
45static void
46atsam_i2c_continue_read(Twihs *regs, transfer_desc *trans_desc)
47{
48        trans_desc->data[trans_desc->already_transferred] = TWI_ReadByte(regs);
49        trans_desc->already_transferred++;
50
51        /* check for transfer finish */
52        if (trans_desc->already_transferred == trans_desc->data_size) {
53                if (trans_desc->stop_request){
54                        TWI_DisableIt(regs, TWIHS_IDR_RXRDY);
55                        TWI_EnableIt(regs, TWIHS_IER_TXCOMP);
56                        atsam_i2c_set_transfer_status(trans_desc, TX_RX_STOP_SENT);
57                } else {
58                        atsam_i2c_set_transfer_status(trans_desc, RX_CONT_MESSAGE_NEEDED);
59                }
60        }
61        /* Last byte? */
62        else if ((trans_desc->already_transferred == (trans_desc->data_size - 1))
63            && (trans_desc->stop_request)){
64                TWI_Stop(regs);
65        }
66}
67
68static bool
69atsam_i2c_is_state(transfer_desc *trans_desc, transfer_state state)
70{
71        return (trans_desc->trans_state == state);
72}
73
74static void
75atsam_i2c_continue_write(Twihs *regs, transfer_desc *trans_desc)
76{
77        /* Transfer finished ? */
78        if (trans_desc->already_transferred == trans_desc->data_size) {
79                TWI_DisableIt(regs, TWIHS_IDR_TXRDY);
80                if (trans_desc->stop_request){
81                        TWI_EnableIt(regs, TWIHS_IER_TXCOMP);
82                        TWI_SendSTOPCondition(regs);
83                        atsam_i2c_set_transfer_status(trans_desc, TX_RX_STOP_SENT);
84                } else {
85                        atsam_i2c_set_transfer_status(trans_desc, TX_CONT_MESSAGE_NEEDED);
86                }
87        }
88        /* Bytes remaining */
89        else {
90                TWI_WriteByte(regs,
91                    trans_desc->data[trans_desc->already_transferred]);
92                trans_desc->already_transferred++;
93        }
94}
95
96static void
97atsam_i2c_finish_write_transfer(Twihs *regs, transfer_desc *trans_desc)
98{
99                TWI_ReadByte(regs);
100                TWI_DisableIt(regs, TWIHS_IDR_TXCOMP);
101                trans_desc->status = 0;
102}
103
104static void
105atsam_i2c_next_packet(atsam_i2c_bus *bus)
106{
107        i2c_msg *msg;
108
109        ++bus->msgs;
110        --bus->msg_todo;
111
112        msg = &bus->msgs[0];
113
114        bus->current_msg_todo = msg->len;
115        bus->current_msg_byte = msg->buf;
116}
117
118static void
119atsam_i2c_set_td(atsam_i2c_bus *bus, uint32_t already_transferred,
120    bool stop_needed)
121{
122        transfer_desc *trans_desc = &bus->trans_desc;
123
124        trans_desc->status = ASYNC_STATUS_PENDING;
125        trans_desc->data = bus->current_msg_byte;
126        trans_desc->data_size = bus->current_msg_todo;
127        trans_desc->already_transferred = already_transferred;
128        trans_desc->stop_request = stop_needed;
129}
130
131static bool
132atsam_i2c_set_address_size(const i2c_msg *msg)
133{
134        bool rv = ((msg->flags & I2C_M_TEN) == 0) ? false : true;
135        return rv;
136}
137
138static void
139atsam_i2c_set_address_regs(Twihs *regs, uint16_t address, bool ten_bit_addr,
140    bool read_transfer)
141{
142        /* No internal addresses used */
143        uint32_t mmr_temp = 0;
144        uint32_t iadr_temp = 0;
145
146        assert(regs != NULL);
147        if (ten_bit_addr){
148                uint8_t addr_temp = TEN_BIT_START_ADDR_MASK;
149                assert(address & TEN_BIT_MASK);
150                mmr_temp = (1u << 8) | ((addr_temp & LAST_TWO_BITS_MASK) << 16);
151                iadr_temp = (addr_temp & LAST_BYTE_MASK);
152        } else {
153                assert((address & SEVEN_BIT_MASK) == 0);
154                mmr_temp = (address << 16);
155        }
156
157        if (read_transfer){
158                mmr_temp |= TWIHS_MMR_MREAD;
159        }
160
161        /* Set slave and number of internal address bytes */
162        regs->TWIHS_MMR = 0;
163        regs->TWIHS_MMR = mmr_temp;
164
165        /* Set internal address bytes */
166        regs->TWIHS_IADR = 0;
167        regs->TWIHS_IADR = iadr_temp;
168}
169
170static void
171atsam_i2c_setup_read_transfer(Twihs *regs, bool ctrl, uint16_t slave_addr,
172    bool send_stop)
173{
174        TWI_EnableIt(regs, TWIHS_IER_RXRDY);
175        atsam_i2c_set_address_regs(regs, slave_addr, ctrl, true);
176        if (send_stop){
177                regs->TWIHS_CR = TWIHS_CR_START | TWIHS_CR_STOP;
178        } else {
179                regs->TWIHS_CR = TWIHS_CR_START;
180        }
181}
182
183static void
184atsam_i2c_setup_write_transfer(atsam_i2c_bus *bus, Twihs *regs, bool ctrl,
185    uint16_t slave_addr)
186{
187        atsam_i2c_set_address_regs(regs, slave_addr, ctrl, false);
188        TWI_WriteByte(regs, *bus->current_msg_byte);
189        TWI_EnableIt(regs, TWIHS_IER_TXRDY);
190}
191
192static void
193atsam_i2c_setup_transfer(atsam_i2c_bus *bus, Twihs *regs)
194{
195        const i2c_msg *msgs = bus->msgs;
196        bool send_stop = false;
197        uint32_t msg_todo = bus->msg_todo;
198        uint16_t slave_addr;
199        bool ten_bit_addr = false;
200        uint32_t already_transferred;
201        bool stop_needed = true;
202
203        ten_bit_addr = atsam_i2c_set_address_size(msgs);
204
205        if ((msg_todo > 1) && ((msgs[1].flags & I2C_M_NOSTART) != 0)){
206                stop_needed = false;
207        }
208
209        bus->read = (msgs->flags & I2C_M_RD) != 0;
210        slave_addr = msgs->addr;
211        already_transferred = (bus->read == true) ? 0 : 1;
212        atsam_i2c_set_td(bus, already_transferred, stop_needed);
213
214        transfer_desc *trans_desc = &bus->trans_desc;
215
216        if (bus->read){
217                if (bus->current_msg_todo == 1){
218                        send_stop = true;
219                }
220                atsam_i2c_set_transfer_status(trans_desc, RX_SEND_DATA);
221                atsam_i2c_setup_read_transfer(regs, ten_bit_addr,
222                    slave_addr, send_stop);
223        } else {
224                atsam_i2c_set_transfer_status(trans_desc, TX_SEND_DATA);
225                atsam_i2c_setup_write_transfer(bus, regs, ten_bit_addr,
226                    slave_addr);
227        }
228}
229
230static void
231atsam_i2c_interrupt(void *arg)
232{
233        atsam_i2c_bus *bus = arg;
234        uint32_t irqstatus;
235        bool done = false;
236        transfer_desc *trans_desc;
237
238        Twihs *regs = bus->regs;
239
240        /* read interrupts */
241        irqstatus = regs->TWIHS_SR;
242
243        if((irqstatus & (TWIHS_SR_ARBLST | TWIHS_SR_NACK)) != 0) {
244                done = true;
245        }
246
247        trans_desc = &bus->trans_desc;
248
249        if (((irqstatus & TWIHS_SR_RXRDY) != 0) &&
250            (atsam_i2c_is_state(trans_desc, RX_SEND_DATA))){
251                /* carry on read transfer */
252                atsam_i2c_continue_read(regs, trans_desc);
253        } else if (((irqstatus & TWIHS_SR_TXCOMP) != 0) &&
254            (atsam_i2c_is_state(trans_desc, TX_RX_STOP_SENT))){
255                atsam_i2c_finish_write_transfer(regs, trans_desc);
256                done = true;
257        } else if (((irqstatus & TWIHS_SR_TXRDY) != 0) &&
258            (atsam_i2c_is_state(trans_desc, TX_SEND_DATA))){
259                atsam_i2c_continue_write(regs, trans_desc);
260                if (trans_desc->trans_state == TX_CONT_MESSAGE_NEEDED){
261                        done = true;
262                }
263        }
264
265        if(done){
266                uint32_t err = irqstatus & ATSAMV_I2C_IRQ_ERROR;
267
268                atsam_i2c_next_packet(bus);
269                if (bus->msg_todo == 0 || err != 0) {
270                        atsam_i2c_disable_interrupts(regs);
271                        rtems_binary_semaphore_post(&bus->sem);
272                } else {
273                        atsam_i2c_setup_transfer(bus, regs);
274                }
275        }
276}
277
278static int
279atsam_i2c_transfer(i2c_bus *base, i2c_msg *msgs, uint32_t msg_count)
280{
281        atsam_i2c_bus *bus = (atsam_i2c_bus *)base;
282        Twihs *regs;
283        uint32_t i;
284        int eno;
285
286        if (msg_count < 1){
287                return 0;
288        }
289
290        for (i = 0; i < msg_count; ++i) {
291                if ((msgs[i].flags & I2C_M_RECV_LEN) != 0) {
292                        return -EINVAL;
293                }
294        }
295
296        bus->msgs = &msgs[0];
297        bus->msg_todo = msg_count;
298        bus->current_msg_todo = msgs[0].len;
299        bus->current_msg_byte = msgs[0].buf;
300
301        regs = bus->regs;
302
303        atsam_i2c_setup_transfer(bus, regs);
304
305        regs->TWIHS_IER = ATSAMV_I2C_IRQ_ERROR;
306
307        eno = rtems_binary_semaphore_wait_timed_ticks(
308                &bus->sem,
309                bus->base.timeout
310        );
311        if (eno != 0) {
312                TWI_ConfigureMaster(bus->regs, bus->output_clock, BOARD_MCK);
313                rtems_binary_semaphore_try_wait(&bus->sem);
314                return -ETIMEDOUT;
315        }
316
317        return 0;
318}
319
320static int
321atsam_i2c_set_clock(i2c_bus *base, unsigned long clock)
322{
323        atsam_i2c_bus *bus = (atsam_i2c_bus *)base;
324        bus->output_clock = clock;
325        TWI_ConfigureMaster(bus->regs, clock, BOARD_MCK);
326        return 0;
327}
328
329static void
330atsam_i2c_destroy(i2c_bus *base)
331{
332        atsam_i2c_bus *bus = (atsam_i2c_bus *)base;
333        rtems_status_code sc;
334
335        sc = rtems_interrupt_handler_remove(bus->irq, atsam_i2c_interrupt, bus);
336        assert(sc == RTEMS_SUCCESSFUL);
337
338        i2c_bus_destroy_and_free(&bus->base);
339}
340
341static void
342atsam_i2c_init(atsam_i2c_bus *bus, uint32_t board_id, const Pin *pins)
343{
344
345        /* Initialize the TWI */
346        PIO_Configure(pins, TWI_AMOUNT_PINS);
347
348        /* Enable the TWI clock */
349        ENABLE_PERIPHERAL(board_id);
350
351        TWI_ConfigureMaster(bus->regs, bus->output_clock, BOARD_MCK);
352}
353
354int
355i2c_bus_register_atsam(
356    const char *bus_path,
357    Twihs *register_base,
358    rtems_vector_number irq,
359    const Pin pins[TWI_AMOUNT_PINS]
360)
361{
362        atsam_i2c_bus *bus;
363        rtems_status_code sc;
364
365        bus = (atsam_i2c_bus *) i2c_bus_alloc_and_init(sizeof(*bus));
366        if (bus == NULL){
367                return -1;
368        }
369
370        rtems_binary_semaphore_init(&bus->sem, "ATSAM I2C");
371        bus->regs = register_base;
372        bus->irq = irq;
373        bus->output_clock = I2C_BUS_CLOCK_DEFAULT;
374
375        atsam_i2c_init(bus, irq, pins);
376
377        sc = rtems_interrupt_handler_install(
378            irq,
379            "ATSAM I2C",
380            RTEMS_INTERRUPT_UNIQUE,
381            atsam_i2c_interrupt,
382            bus
383        );
384        if(sc != RTEMS_SUCCESSFUL){
385                (*bus->base.destroy)(&bus->base);
386
387                rtems_set_errno_and_return_minus_one(EIO);
388        }
389
390        bus->base.transfer = atsam_i2c_transfer;
391        bus->base.set_clock = atsam_i2c_set_clock;
392        bus->base.destroy = atsam_i2c_destroy;
393
394        return i2c_bus_register(&bus->base, bus_path);
395}
Note: See TracBrowser for help on using the repository browser.