summaryrefslogtreecommitdiff
path: root/bsp/wch/risc-v/libraries/ch56x_drivers/ch56x_spi.c
blob: 43ea1d4be8074229057669d97018fe825d61a87d (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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
/*
 * Copyright (c) 2006-2023, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author            Notes
 * 2022-07-30     Emuzit            first version
 */
#include <rthw.h>
#include "drivers/dev_spi.h"
#include <drivers/dev_pin.h>
#include "ch56x_spi.h"
#include "ch56x_sys.h"

#if !defined(BSP_USING_SPI0) && !defined(BSP_USING_SPI1)
    #error "Please define at least one SPIx"
#endif

struct spi_bus
{
    struct rt_spi_bus parent;
    volatile struct spi_registers *reg_base;
    irq_number_t irqn;
    char *name;
    rt_base_t sck_pin;
    rt_base_t mosi_pin;
    rt_base_t miso_pin;
};

#ifdef BSP_USING_SPI0
static struct spi_bus spi_bus_0 =
{
    .reg_base = (struct spi_registers *)SPI0_REG_BASE,
    .irqn = SPI0_IRQn,
    .name = SPI0_BUS_NAME,
    .sck_pin = SPI0_SCK_PIN,
    .mosi_pin = SPI0_MOSI_PIN,
    .miso_pin = SPI0_MISO_PIN,
};
#endif
#ifdef BSP_USING_SPI1
static struct spi_bus spi_bus_1 =
{
    .reg_base = (struct spi_registers *)SPI1_REG_BASE,
    .irqn = SPI1_IRQn,
    .name = SPI1_BUS_NAME,
    .sck_pin = SPI1_SCK_PIN,
    .mosi_pin = SPI1_MOSI_PIN,
    .miso_pin = SPI1_MISO_PIN,
};
#endif

static uint8_t _spi_pin_config(struct rt_spi_device *device, struct rt_spi_configuration *config)
{
    struct spi_bus *spi_bus = (struct spi_bus *)device->bus;

    uint8_t mode;

    /* RT_SPI_3WIRE means SI/SO pin shared */
    mode = config->mode & (RT_SPI_MASTER | RT_SPI_SLAVE | RT_SPI_3WIRE);
    if (mode == RT_SPI_MASTER)
    {
        mode = RB_SPI_MOSI_OE | RB_SPI_SCK_OE;
        rt_pin_mode(spi_bus->mosi_pin, PIN_MODE_OUTPUT);
        rt_pin_mode(spi_bus->sck_pin, PIN_MODE_OUTPUT);
    }
    else if (mode == RT_SPI_SLAVE)
    {
        mode = RB_SPI_MISO_OE | RB_SPI_MODE_SLAVE;
        rt_pin_mode(spi_bus->miso_pin, PIN_MODE_OUTPUT);
    }
    else if (mode == RT_SPI_MASTER | RT_SPI_3WIRE)
    {
        mode = RB_SPI_2WIRE_MOD | RB_SPI_MISO_OE | RB_SPI_SCK_OE;
        rt_pin_mode(spi_bus->miso_pin, PIN_MODE_INPUT);
        rt_pin_mode(spi_bus->sck_pin, PIN_MODE_OUTPUT);
    }
    else
    {
        mode = RB_SPI_2WIRE_MOD | RB_SPI_MISO_OE | RB_SPI_MODE_SLAVE;
        rt_pin_mode(spi_bus->miso_pin, PIN_MODE_INPUT);
    }

    return mode;
}

static rt_err_t spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *config)
{
    volatile struct spi_registers *sxreg;
    struct rt_spi_device *owner;

    union _spi_ctrl_mod ctrl_mod;
    uint8_t mode;
    uint32_t Fsys;
    uint32_t div;

    rt_base_t cs_pin;
    int inactive;

    RT_ASSERT(device != RT_NULL);

    /* ch56x SPI supports only 8-bit data */
    if (config->data_width != 8)
        return -RT_EINVAL;

    ctrl_mod.reg = _spi_pin_config(device, config);

    /* ch56x SPI supports only mode 0 & mode 3 */
    mode = config->mode & (RT_SPI_CPOL | RT_SPI_CPHA);
    if (mode == RT_SPI_MODE_0)
        ctrl_mod.mst_sck_mod = MST_SCK_MOD_0;
    else if (mode == RT_SPI_MODE_3)
        ctrl_mod.mst_sck_mod = MST_SCK_MOD_3;
    else
        return -RT_EINVAL;

    /* CLOCK_DIV is 8-bit, reject excessively low max_hz */
    Fsys = sys_hclk_get();
    if (config->max_hz < Fsys / 255)
        return -RT_EINVAL;

    /* minimum allowable CLOCK_DIV is 2 */
    div = (Fsys + config->max_hz - 1) / config->max_hz;
    if (div < 2)
        div = 2;

    sxreg = ((struct spi_bus *)device->bus)->reg_base;
    sxreg->CLOCK_DIV = div;

    mode = config->mode & (RT_SPI_MSB | RT_SPI_LSB);
    sxreg->CTRL_CFG.reg = (mode == RT_SPI_MSB) ? 0 : RB_SPI_BIT_ORDER;
    sxreg->INTER_EN.reg = 0;

    ctrl_mod.all_clear = 1;
    sxreg->CTRL_MOD.reg = ctrl_mod.reg;
    ctrl_mod.all_clear = 0;
    sxreg->CTRL_MOD.reg = ctrl_mod.reg;

    mode = config->mode & (RT_SPI_MASTER | RT_SPI_SLAVE);
    if (mode == RT_SPI_MASTER)
    {
        /* get bus owner before this configure */
        owner = device->bus->owner;
        if (owner && owner != device)
        {
            /* make sure predecessor's CS is deactived */
            inactive = (owner->config.mode & RT_SPI_CS_HIGH) ? PIN_LOW : PIN_HIGH;
            cs_pin = (rt_base_t)owner->parent.user_data;
            rt_pin_write(cs_pin, inactive);
        }
        /* bus owner is maintained by upper layer, do not update here */

        inactive = (config->mode & RT_SPI_CS_HIGH) ? PIN_LOW : PIN_HIGH;
        cs_pin = (rt_base_t)device->parent.user_data;
        rt_pin_write(cs_pin, inactive);
        rt_pin_mode(cs_pin, PIN_MODE_OUTPUT);
    }

    /* `config` is actually `device->config` : spi_core.c */
    //device->config = *config;

    return RT_EOK;
}

/**
 * @brief   Transfer SPI data for single message.
 *          Message traversing is done by rt_spi_message().
 *
 * @param   device is pointer to the rt_spi_device device.
 *
 * @param   message is a link list for data/control information,
 *          only the first entry is processed.
 *          Note: ch56x can't do SPI send & recv at the same time.
 *
 * @return  `message->length1 if successful, 0 otherwise.
 */
static rt_uint32_t spi_xfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
    struct spi_bus *spi_bus = (struct spi_bus *)device->bus;
    volatile struct spi_registers *sxreg = spi_bus->reg_base;

    union _spi_ctrl_mod ctrl_mod;

    uint8_t *data;
    uint32_t size;

    rt_base_t cs_pin;
    int cs_high;

    size = message->length;
    if (size == 0 || size > 4095)
        return 0;

    ctrl_mod.reg = sxreg->CTRL_MOD.reg | RB_SPI_ALL_CLEAR;

    /* ch56x can't do SPI send & recv at the same time */
    if (message->send_buf && !message->recv_buf)
    {
        data = (uint8_t *)message->send_buf;
        ctrl_mod.fifo_dir = SPI_FIFO_DIR_OUTPUT;
    }
    else if (!message->send_buf && message->recv_buf)
    {
        data = (uint8_t *)message->recv_buf;
        ctrl_mod.fifo_dir = SPI_FIFO_DIR_INPUT;
    }
    else
    {
        return 0;
    }

    sxreg->CTRL_MOD.reg = ctrl_mod.reg;
    ctrl_mod.all_clear = 0;
    sxreg->CTRL_MOD.reg = ctrl_mod.reg;

    /* set MISO pin direction to match xfer if shared SI/SO pin */
    if (device->config.mode & RT_SPI_3WIRE)
    {
        rt_base_t mode = message->send_buf ? PIN_MODE_OUTPUT : PIN_MODE_INPUT;
        rt_pin_mode(spi_bus->miso_pin, mode);
    }

    cs_pin = (rt_base_t)device->parent.user_data;
    cs_high = device->config.mode & RT_SPI_CS_HIGH;

    if (message->cs_take)
    {
        /* take/activate CS */
        rt_pin_write(cs_pin, cs_high ? PIN_HIGH : PIN_LOW);
    }

    sxreg->TOTAL_COUNT = size;

    if (size > SPI_FIFO_SIZE)
    {
        sxreg->DMA_BIG = (uint32_t)data;
        sxreg->DMA_END = (uint32_t)(data + size);
        sxreg->CTRL_CFG.dma_enable = 1;
        /* mark no need to read FIFO */
        size = 0;
    }
    else
    {
        if (message->send_buf)
        {
            /* keep sending, won't overflow */
            while (size)
            {
                sxreg->FIFO = *data++;
                size--;
            }
        }
    }

    /* wait for transfer done */
    while (sxreg->TOTAL_COUNT > 0);
    /* disable DMA, anyway */
    sxreg->CTRL_CFG.dma_enable = 0;

    /* non-DMA recv => read data from FIFO */
    if (size > 0)
    {
        while (size--)
            *data++ = sxreg->FIFO;
    }

    /* set MISO as input after xfer if shared SI/SO pin */
    if (device->config.mode & RT_SPI_3WIRE)
    {
        rt_pin_mode(spi_bus->miso_pin, PIN_MODE_INPUT);
    }

    if (message->cs_release)
    {
        /* release/deactivate CS */
        rt_pin_write(cs_pin, cs_high ? PIN_LOW : PIN_HIGH);
    }

    return message->length;
}

static const struct rt_spi_ops spi_ops =
{
    .configure = spi_configure,
    .xfer = spi_xfer,
};

static int rt_hw_spi_init(void)
{
    struct spi_bus *devices[2];

    rt_err_t res, ret = RT_EOK;

    int n = 0;

#ifdef BSP_USING_SPI1
    devices[n++] = &spi_bus_1;
#endif
#ifdef BSP_USING_SPI0
    devices[n++] = &spi_bus_0;
#endif

    while (--n >= 0)
    {
        struct spi_bus *spi_bus = devices[n];

        sys_clk_off_by_irqn(spi_bus->irqn, SYS_SLP_CLK_ON);

        res = rt_spi_bus_register(&spi_bus->parent, spi_bus->name, &spi_ops);
        if (res != RT_EOK)
        {
            ret = res;
        }
    };

    return ret;
}
INIT_DEVICE_EXPORT(rt_hw_spi_init);