aboutsummaryrefslogtreecommitdiffstats
path: root/src/samd21/i2c.c
blob: 801c0e32d4b07a1518de680a03e208c1b7247c8d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// i2c support on samd21
//
// Copyright (C) 2018  Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.

#include "autoconf.h" // CONFIG_CLOCK_FREQ
#include "internal.h" // enable_pclock
#include "command.h" // shutdown
#include "gpio.h" // i2c_setup
#include "samd21.h" // SERCOM3
#include "sched.h" // sched_shutdown

#define TIME_RISE 125ULL // 125 nanoseconds
#define I2C_FREQ 100000

static void
i2c_init(void)
{
    static int have_run_init;
    if (have_run_init)
        return;
    have_run_init = 1;

    // Setup clock
    enable_pclock(SERCOM3_GCLK_ID_CORE, PM_APBCMASK_SERCOM3);

    // Configure SDA, SCL pins
    gpio_peripheral(GPIO('A', 22), 'C', 0);
    gpio_peripheral(GPIO('A', 23), 'C', 0);

    // Configure i2c
    SercomI2cm *si = &SERCOM3->I2CM;
    si->CTRLA.reg = 0;
    uint32_t areg = (SERCOM_I2CM_CTRLA_LOWTOUTEN
                     | SERCOM_I2CM_CTRLA_INACTOUT(3)
                     | SERCOM_I2CM_STATUS_SEXTTOUT
                     | SERCOM_I2CM_STATUS_MEXTTOUT
                     | SERCOM_I2CM_CTRLA_MODE_I2C_MASTER);
    si->CTRLA.reg = areg;
    uint32_t baud = (CONFIG_CLOCK_FREQ / I2C_FREQ
                     - 10 - CONFIG_CLOCK_FREQ*TIME_RISE/1000000000) / 2;
    si->BAUD.reg = baud;
    si->CTRLA.reg = areg | SERCOM_I2CM_CTRLA_ENABLE;
    while (si->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_ENABLE)
        ;

    // Go into idle mode
    si->STATUS.reg = SERCOM_I2CM_STATUS_BUSSTATE(1);
    while (si->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_SYSOP)
        ;
}

struct i2c_config
i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr)
{
    if (bus)
        shutdown("Unsupported i2c bus");
    i2c_init();
    return (struct i2c_config){ .addr=addr<<1 };
}

static void
i2c_wait(SercomI2cm *si)
{
    for (;;) {
        uint32_t intflag = si->INTFLAG.reg;
        if (!(intflag & SERCOM_I2CM_INTFLAG_MB)) {
            if (si->STATUS.reg & SERCOM_I2CM_STATUS_BUSERR)
                shutdown("i2c buserror");
            continue;
        }
        if (intflag & SERCOM_I2CM_INTFLAG_ERROR)
            shutdown("i2c error");
        break;
    }
}

static void
i2c_start(SercomI2cm *si, uint8_t addr)
{
    si->ADDR.reg = addr;
    i2c_wait(si);
}

static void
i2c_send_byte(SercomI2cm *si, uint8_t b)
{
    si->DATA.reg = b;
    i2c_wait(si);
}

static void
i2c_stop(SercomI2cm *si)
{
    si->CTRLB.reg = SERCOM_I2CM_CTRLB_CMD(3);
}

void
i2c_write(struct i2c_config config, uint8_t write_len, uint8_t *write)
{
    SercomI2cm *si = &SERCOM3->I2CM;
    i2c_start(si, config.addr);
    while (write_len--)
        i2c_send_byte(si, *write++);
    i2c_stop(si);
}

void
i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg
         , uint8_t read_len, uint8_t *read)
{
    shutdown("i2c_read not supported on samd21");
}