This is an automated email from the ASF dual-hosted git repository. jerzy pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mynewt-core.git
commit f6522eb4dda62c8221b48671bb62646ebaf1d374 Author: Jerzy Kasenberg <jerzy.kasenb...@codecoup.pl> AuthorDate: Fri Jun 7 15:14:21 2019 +0200 da1469x: Add I2C bus driver I2C bus driver that uses interrupts and DMA. --- .../i2c_da1469x/include/bus/drivers/i2c_da1469x.h | 68 ++ hw/bus/drivers/i2c_da1469x/pkg.yml | 31 + hw/bus/drivers/i2c_da1469x/src/i2c_da1469x.c | 724 +++++++++++++++++++++ hw/bus/drivers/i2c_da1469x/syscfg.yml | 35 + 4 files changed, 858 insertions(+) diff --git a/hw/bus/drivers/i2c_da1469x/include/bus/drivers/i2c_da1469x.h b/hw/bus/drivers/i2c_da1469x/include/bus/drivers/i2c_da1469x.h new file mode 100644 index 0000000..4e438fd --- /dev/null +++ b/hw/bus/drivers/i2c_da1469x/include/bus/drivers/i2c_da1469x.h @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef HW_BUS_DRIVERS_I2C_DA1469X_DMA_H_ +#define HW_BUS_DRIVERS_I2C_DA1469X_DMA_H_ + +#include <stddef.h> +#include <stdint.h> +#include "os/os_dev.h" +#include "bus/drivers/i2c_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Initialize os_dev as I2C bus device for DMA usage + * + * This can be passed as a parameter to os_dev_create() when creating os_dev + * object for I2C device, however it's recommended to create devices using helper + * like bus_i2c_hal_dev_create(). + * + * @param node Node device object + * @param arg Node configuration struct (struct bus_node_cfg) + */ +int bus_i2c_da1469x_dev_init_func(struct os_dev *odev, void *arg); + +/** + * Create I2C bus device for DMA + * + * This is a convenient helper and recommended way to create os_dev for bus I2C + * device instead of calling os_dev_create() directly. + * + * @param name Name of device + * @param dev Device state object + * @param cfg Configuration + */ +static inline int +bus_i2c_da1469x_dev_create(const char *name, struct bus_i2c_dev *dev, + struct bus_i2c_dev_cfg *cfg) +{ + struct os_dev *odev = (struct os_dev *)dev; + + return os_dev_create(odev, name, OS_DEV_INIT_PRIMARY, 0, + bus_i2c_da1469x_dev_init_func, cfg); +} + +#ifdef __cplusplus +} +#endif + +#endif /* HW_BUS_DRIVERS_I2C_DA1469X_DMA_H_ */ diff --git a/hw/bus/drivers/i2c_da1469x/pkg.yml b/hw/bus/drivers/i2c_da1469x/pkg.yml new file mode 100644 index 0000000..6f9b6f7 --- /dev/null +++ b/hw/bus/drivers/i2c_da1469x/pkg.yml @@ -0,0 +1,31 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +pkg.name: hw/bus/drivers/i2c_da1469x +pkg.description: I2C bus driver that uses interrupts and DMA +pkg.author: "Apache Mynewt <d...@mynewt.apache.org>" +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + +pkg.deps: + - hw/bus + - hw/bus/drivers/i2c_common + +pkg.req_apis.I2C_DA1469X_STAT: + - stats diff --git a/hw/bus/drivers/i2c_da1469x/src/i2c_da1469x.c b/hw/bus/drivers/i2c_da1469x/src/i2c_da1469x.c new file mode 100644 index 0000000..6735bda --- /dev/null +++ b/hw/bus/drivers/i2c_da1469x/src/i2c_da1469x.c @@ -0,0 +1,724 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <assert.h> +#include <mcu/da1469x_pd.h> +#include "defs/error.h" +#include "hal/hal_gpio.h" +#include "bus/bus.h" +#include "bus/bus_debug.h" +#include "bus/bus_driver.h" +#include "bus/drivers/i2c_common.h" +#include "mcu/mcu.h" + +#if MYNEWT_VAL(I2C_DA1469X_BUS_DRIVER) +#include "mcu/da1469x_dma.h" +#endif + +#if MYNEWT_VAL(I2C_DA1469X_STAT) +#include "stats/stats.h" +#endif + +#if MYNEWT_VAL(I2C_DA1469X_STAT) +STATS_SECT_START(i2c_da1469x_stats_section) + STATS_SECT_ENTRY(dma_read_bytes) + STATS_SECT_ENTRY(dma_written_bytes) + STATS_SECT_ENTRY(i2c_errors) +STATS_SECT_END + +STATS_NAME_START(i2c_da1469x_stats_section) + STATS_NAME(i2c_da1469x_stats_section, dma_read_bytes) + STATS_NAME(i2c_da1469x_stats_section, dma_written_bytes) + STATS_NAME(i2c_da1469x_stats_section, i2c_errors) +STATS_NAME_END(i2c_da1469x_stats_section) +#endif + +#define I2C_COUNT 2 +/* Minimum transfer size when DMA should be used, for shorter transfers + * interrupts are used + */ +#define MIN_DMA_SIZE 2 + +static void i2c_da1469x_i2c0_isr(void); +static void i2c_da1469x_i2c1_isr(void); + +struct da1469x_i2c_hw { + I2C_Type *regs; + uint8_t scl_func; + uint8_t sda_func; + IRQn_Type irqn; + /* DMA peripheral index */ + uint8_t dma_periph; + /* Bit number for clock selection in CLK_COM_REG */ + uint8_t clk_src_bit; + /* Bit number for clock enable in CLK_COM_REG */ + uint8_t clk_ena_bit; + void (* isr)(void); +}; + +static const struct da1469x_i2c_hw da1469x_i2c[2] = { + { + .regs = (void *)I2C_BASE, + .scl_func = MCU_GPIO_FUNC_I2C_SCL, + .sda_func = MCU_GPIO_FUNC_I2C_SDA, + .irqn = I2C_IRQn, + .dma_periph = MCU_DMA_PERIPH_I2C, + .clk_src_bit = CRG_COM_CLK_COM_REG_I2C_CLK_SEL_Pos, + .clk_ena_bit = CRG_COM_CLK_COM_REG_I2C_ENABLE_Pos, + .isr = i2c_da1469x_i2c0_isr, + }, + { + .regs = (void *)I2C2_BASE, + .scl_func = MCU_GPIO_FUNC_I2C2_SCL, + .sda_func = MCU_GPIO_FUNC_I2C2_SDA, + .irqn = I2C2_IRQn, + .dma_periph = MCU_DMA_PERIPH_I2C2, + .clk_src_bit = CRG_COM_CLK_COM_REG_I2C2_CLK_SEL_Pos, + .clk_ena_bit = CRG_COM_CLK_COM_REG_I2C2_ENABLE_Pos, + .isr = i2c_da1469x_i2c1_isr, + } +}; + +struct da1469x_transfer { + uint8_t *data; + uint16_t wlen; + uint16_t rlen; + uint8_t nostop:1; + uint8_t write:1; + uint8_t dma:1; + uint8_t started:1; +}; + +_Static_assert(MYNEWT_VAL(I2C_DA1469X_TX_DMA_BUFFER_SIZE) > 0, "Value must be 1 or more"); + +struct i2c_da1469x_dev_data { + struct bus_i2c_dev *dev; + struct os_sem sem; + uint32_t errorsrc; + struct da1469x_dma_regs *dma_chans[2]; + struct da1469x_transfer transfer; + void (*i2c_isr)(I2C_Type *i2c_regs, struct i2c_da1469x_dev_data *dd); + +#if MYNEWT_VAL(I2C_DA1469X_STAT) + STATS_SECT_DECL(i2c_da1469x_stats_section) stats; +#endif + uint16_t tx_dma_buf[MYNEWT_VAL(I2C_DA1469X_TX_DMA_BUFFER_SIZE)]; +}; + +static struct i2c_da1469x_dev_data i2c_dev_data[I2C_COUNT]; + +static void +i2c_da1469x_clear_int(I2C_Type *i2c_regs) +{ + (void)i2c_regs->I2C_CLR_INTR_REG; +} + +static void +i2c_da1469x_dma_rx_isr(struct bus_i2c_dev *dev) +{ + struct i2c_da1469x_dev_data *dd; + int transferred; + struct da1469x_transfer *transfer; + + dd = &i2c_dev_data[dev->cfg.i2c_num]; + transferred = (int)(dd->dma_chans[0]->DMA_IDX_REG + 1); + transfer = &dd->transfer; + + transfer->data += transferred; + transfer->rlen -= transferred; + +#if MYNEWT_VAL(I2C_DA1469X_STAT) + STATS_INCN(dd->stats, dma_read_bytes, transferred); +#endif + assert(transfer->rlen == 0); + + transfer->started = 0; + + os_sem_release(&dd->sem); +} + +void +i2c_da1469x_fill_tx_dma_buffer(struct i2c_da1469x_dev_data *dd) +{ + uint16_t i; + uint16_t length = dd->transfer.wlen; + + if (dd->transfer.write) { + if (length > MYNEWT_VAL(I2C_DA1469X_TX_DMA_BUFFER_SIZE)) { + length = MYNEWT_VAL(I2C_DA1469X_TX_DMA_BUFFER_SIZE); + } + /* Byte array converted to word array */ + for (i = 0; i < length; ++i) { + dd->tx_dma_buf[i] = dd->transfer.data[i]; + } + /* Add stop request is required */ + if (!dd->transfer.nostop && i == dd->transfer.wlen) { + dd->tx_dma_buf[i - 1] |= I2C_I2C_DATA_CMD_REG_I2C_STOP_Msk; + } + } else { + if (dd->transfer.nostop) { + /* No stop, all can be transmitted with one DMA request */ + dd->tx_dma_buf[0] = I2C_I2C_DATA_CMD_REG_I2C_CMD_Msk; + } else if (length == 1) { + /* Stop requested and only one byte left, add stop */ + dd->tx_dma_buf[0] = I2C_I2C_DATA_CMD_REG_I2C_CMD_Msk | + I2C_I2C_DATA_CMD_REG_I2C_STOP_Msk; + } else { + /* Stop requested more send lentght - 1 without stop */ + dd->tx_dma_buf[0] = I2C_I2C_DATA_CMD_REG_I2C_CMD_Msk; + --length; + } + } + + dd->dma_chans[1]->DMA_A_START_REG = (uint32_t)dd->tx_dma_buf; + dd->dma_chans[1]->DMA_LEN_REG = (uint32_t)(length - 1); + dd->dma_chans[1]->DMA_INT_REG = (uint32_t)(length - 1); + dd->dma_chans[1]->DMA_CTRL_REG = (0U << DMA_DMA0_CTRL_REG_DMA_INIT_Pos) | + (3U << DMA_DMA0_CTRL_REG_DMA_PRIO_Pos) | + /* Increment source for writes, for read read same data */ + (dd->transfer.write << DMA_DMA0_CTRL_REG_AINC_Pos) | + (0U << DMA_DMA0_CTRL_REG_BINC_Pos) | + (1U << DMA_DMA0_CTRL_REG_DREQ_MODE_Pos) | + (1U << DMA_DMA0_CTRL_REG_BW_Pos) | + (1U << DMA_DMA0_CTRL_REG_DMA_ON_Pos); +} + +static void +i2c_da1469x_dma_tx_isr(struct bus_i2c_dev *dev) +{ + struct i2c_da1469x_dev_data *dd; + int transferred; + struct da1469x_transfer *transfer; + + dd = &i2c_dev_data[dev->cfg.i2c_num]; + transferred = (int)(dd->dma_chans[1]->DMA_IDX_REG + 1); + transfer = &dd->transfer; + + /* Update transfer data */ + if (transfer->write) { + transfer->data += transferred; + } + transfer->wlen -= transferred; +#if MYNEWT_VAL(I2C_DA1469X_STAT) + STATS_INCN(dd->stats, dma_written_bytes, transferred); +#endif + + /* More data to transfer */ + if (transfer->wlen) { + i2c_da1469x_fill_tx_dma_buffer(dd); + } else if (transfer->write && transfer->started && transfer->nostop) { + transfer->started = 0; + os_sem_release(&dd->sem); + } +} + +static void +i2c_da1469x_fill_fifo_for_rx(I2C_Type *i2c_regs, struct i2c_da1469x_dev_data *dd) +{ + uint32_t intr_stat; + struct da1469x_transfer *transfer; + + transfer = &dd->transfer; + + intr_stat = i2c_regs->I2C_INTR_STAT_REG; + + if (intr_stat & I2C_I2C_INTR_MASK_REG_M_TX_ABRT_Msk) { + dd->errorsrc = i2c_regs->I2C_TX_ABRT_SOURCE_REG; + (void)i2c_regs->I2C_CLR_TX_ABRT_REG; +#if MYNEWT_VAL(I2C_DA1469X_STAT) + STATS_INC(dd->stats, i2c_errors); +#endif + i2c_da1469x_clear_int(i2c_regs); + /* Stop DMA */ + dd->dma_chans[1]->DMA_CTRL_REG &= ~(1U << DMA_DMA0_CTRL_REG_DMA_ON_Pos); + dd->dma_chans[0]->DMA_CTRL_REG &= ~(1U << DMA_DMA0_CTRL_REG_DMA_ON_Pos); + transfer->started = 0; + i2c_regs->I2C_INTR_MASK_REG = 0; + + /* Finish transaction */ + os_sem_release(&dd->sem); + return; + } + + i2c_da1469x_clear_int(i2c_regs); + + while ((transfer->wlen > 0) && + i2c_regs->I2C_STATUS_REG & I2C_I2C_STATUS_REG_TFNF_Msk) { + transfer->wlen--; + if (transfer->wlen) { + i2c_regs->I2C_DATA_CMD_REG = (1U << I2C_I2C_DATA_CMD_REG_I2C_CMD_Pos); + } else { + i2c_regs->I2C_DATA_CMD_REG = (1U << I2C_I2C_DATA_CMD_REG_I2C_CMD_Pos) | + (1U << I2C_I2C_DATA_CMD_REG_I2C_STOP_Pos); + } + } + + if (transfer->dma == 0) { + while ((transfer->rlen > 0) && + i2c_regs->I2C_STATUS_REG & I2C_I2C_STATUS_REG_RFNE_Msk) { + *transfer->data++ = (uint8_t)i2c_regs->I2C_DATA_CMD_REG; + transfer->rlen--; + if (transfer->rlen == 0) { + i2c_regs->I2C_INTR_MASK_REG = 0; + i2c_da1469x_clear_int(i2c_regs); + transfer->started = 0; + os_sem_release(&dd->sem); + } + } + } + + if (intr_stat & I2C_I2C_INTR_MASK_REG_M_STOP_DET_Msk) { + i2c_regs->I2C_INTR_MASK_REG = 0; + assert(transfer->started == 0 && transfer->rlen == 0); + if (transfer->started || transfer->rlen) { + transfer->started = 0; + transfer->rlen = 0; + dd->errorsrc = I2C_I2C_TX_ABRT_SOURCE_REG_ABRT_USER_ABRT_Msk; + os_sem_release(&dd->sem); + } + return; + } + + /* All data in tx buffer, no need for TX interrupt any more */ + if (transfer->wlen == 0) { + i2c_regs->I2C_INTR_MASK_REG &= ~I2C_I2C_INTR_MASK_REG_M_TX_EMPTY_Msk; + } +} + +static void +i2c_da1469x_fill_fifo_for_tx(I2C_Type *i2c_regs, struct i2c_da1469x_dev_data *dd) +{ + uint32_t intr_stat; + struct da1469x_transfer *transfer = &dd->transfer; + + intr_stat = i2c_regs->I2C_INTR_STAT_REG; + + /* If RX FIFO is not empty read all since this is write only stage */ + if (intr_stat & I2C_I2C_INTR_MASK_REG_M_RX_FULL_Msk) { + while (i2c_regs->I2C_STATUS_REG & I2C2_I2C2_STATUS_REG_RFNE_Msk) { + /* Read and ignore from RX FIFO */ + if (i2c_regs->I2C_DATA_CMD_REG) {} + } + } + + if (intr_stat & I2C_I2C_INTR_MASK_REG_M_TX_ABRT_Msk) { + dd->errorsrc = i2c_regs->I2C_TX_ABRT_SOURCE_REG; + (void)i2c_regs->I2C_CLR_TX_ABRT_REG; +#if MYNEWT_VAL(I2C_DA1469X_STAT) + STATS_INC(dd->stats, i2c_errors); +#endif + i2c_da1469x_clear_int(i2c_regs); + if (transfer->dma) { + dd->dma_chans[1]->DMA_CTRL_REG &= ~(1U << DMA_DMA0_CTRL_REG_DMA_ON_Pos); + } + i2c_regs->I2C_INTR_MASK_REG = 0; + os_sem_release(&dd->sem); + return; + } + + i2c_da1469x_clear_int(i2c_regs); + + while ((transfer->wlen > 0) && + i2c_regs->I2C_STATUS_REG & I2C_I2C_STATUS_REG_TFNF_Msk) { + transfer->wlen--; + if (transfer->wlen != 0 || transfer->nostop) { + i2c_regs->I2C_DATA_CMD_REG = *transfer->data++; + } else { + i2c_regs->I2C_DATA_CMD_REG = *transfer->data++ | + (1U << I2C_I2C_DATA_CMD_REG_I2C_STOP_Pos); + } + if (transfer->wlen == 0) { + if (transfer->nostop) { + i2c_regs->I2C_INTR_MASK_REG = 0; + i2c_da1469x_clear_int(i2c_regs); + transfer->started = 0; + os_sem_release(&dd->sem); + } + } + } + if (transfer->wlen == 0) { + i2c_regs->I2C_INTR_MASK_REG &= ~I2C_I2C_INTR_MASK_REG_M_TX_EMPTY_Msk; + } + + if (intr_stat & I2C_I2C_INTR_MASK_REG_M_STOP_DET_Msk) { + i2c_regs->I2C_INTR_MASK_REG = 0; + if (transfer->started) { + transfer->started = 0; + os_sem_release(&dd->sem); + } + } +} + +static void +i2c_da1469x_i2c0_isr(void) +{ + os_trace_isr_enter(); + + i2c_dev_data[0].i2c_isr((I2C_Type *)I2C_BASE, &i2c_dev_data[0]); + + os_trace_isr_exit(); +} + +static void +i2c_da1469x_i2c1_isr(void) +{ + os_trace_isr_enter(); + + i2c_dev_data[1].i2c_isr((I2C_Type *)I2C2_BASE, &i2c_dev_data[1]); + + os_trace_isr_exit(); +} + +static int +i2c_da1469x_init_node(struct bus_dev *bdev, struct bus_node *bnode, void *arg) +{ + struct bus_i2c_node *node = (struct bus_i2c_node *)bnode; + struct bus_i2c_node_cfg *cfg = arg; + + BUS_DEBUG_POISON_NODE(node); + + node->freq = cfg->freq; + node->addr = cfg->addr; + node->quirks = cfg->quirks; + + return 0; +} + +static int +i2c_da1469x_enable(struct bus_dev *bdev) +{ + struct bus_i2c_dev *dev = (struct bus_i2c_dev *)bdev; + I2C_Type *i2c_regs; + + BUS_DEBUG_VERIFY_DEV(dev); + + i2c_regs = da1469x_i2c[dev->cfg.i2c_num].regs; + + /* Disable I2C */ + i2c_regs->I2C_ENABLE_REG |= (1U << I2C_I2C_ENABLE_REG_I2C_EN_Pos); + + return 0; +} + +static int +i2c_da1469x_configure(struct bus_dev *bdev, struct bus_node *bnode) +{ + struct bus_i2c_dev *dev = (struct bus_i2c_dev *)bdev; + struct bus_i2c_node *node = (struct bus_i2c_node *)bnode; + struct bus_i2c_node *current_node = (struct bus_i2c_node *)bdev->configured_for; + I2C_Type *i2c_regs; + uint32_t speed; + int rc = 0; + + BUS_DEBUG_VERIFY_DEV(dev); + BUS_DEBUG_VERIFY_NODE(node); + + i2c_regs = da1469x_i2c[dev->cfg.i2c_num].regs; + + if (i2c_regs->I2C_ENABLE_REG & I2C_I2C_ENABLE_REG_I2C_EN_Msk) { + i2c_regs->I2C_ENABLE_REG &= ~(1U << I2C_I2C_ENABLE_REG_I2C_EN_Pos); + } + + i2c_regs->I2C_TAR_REG = node->addr & I2C_I2C_TAR_REG_IC_TAR_Msk; + + if (current_node && (current_node->freq == node->freq)) { + goto end; + } + + switch (node->freq) { + case 100: + speed = 1; + break; + case 400: + speed = 2; + break; + case 3400: + speed = 3; + break; + default: + rc = SYS_EIO; + goto end; + } + i2c_regs->I2C_CON_REG = (i2c_regs->I2C_CON_REG & ~I2C_I2C_CON_REG_I2C_SPEED_Msk) | + (speed << I2C_I2C_CON_REG_I2C_SPEED_Pos); +end: + return rc; +} + +static int +i2c_da1469x_translate_abort(uint32_t abort_code) +{ + if (abort_code & + (I2C_I2C_TX_ABRT_SOURCE_REG_ABRT_7B_ADDR_NOACK_Msk | + I2C_I2C_TX_ABRT_SOURCE_REG_ABRT_10ADDR1_NOACK_Msk | + I2C_I2C_TX_ABRT_SOURCE_REG_ABRT_10ADDR2_NOACK_Msk)) { + return SYS_ENOENT; + } else if (abort_code & I2C_I2C_TX_ABRT_SOURCE_REG_ABRT_TXDATA_NOACK_Msk) { + return SYS_EREMOTEIO; + } else { + return SYS_EIO; + } +} + +static int +i2c_da1469x_read(struct bus_dev *bdev, struct bus_node *bnode, + uint8_t *buf, uint16_t length, os_time_t timeout, + uint16_t flags) +{ + struct bus_i2c_dev *dev = (struct bus_i2c_dev *)bdev; + struct bus_i2c_node *node = (struct bus_i2c_node *)bnode; + struct i2c_da1469x_dev_data *dd; + I2C_Type *i2c_regs; + int rc; + + BUS_DEBUG_VERIFY_DEV(dev); + BUS_DEBUG_VERIFY_NODE(node); + + i2c_regs = da1469x_i2c[dev->cfg.i2c_num].regs; + dd = &i2c_dev_data[dev->cfg.i2c_num]; + + i2c_regs->I2C_INTR_MASK_REG = 0; + dd->errorsrc = 0; + dd->transfer.rlen = length; + dd->transfer.wlen = length; + dd->transfer.data = buf; + dd->transfer.nostop = (flags & BUS_F_NOSTOP) != 0; + dd->transfer.write = 0; + dd->transfer.started = 1; + dd->i2c_isr = i2c_da1469x_fill_fifo_for_rx; + if (length >= MIN_DMA_SIZE) { + /* + * To read I2C controller with DMA output FIFO must be fed with + * read requests. In this case only one value is needed if DMA source + * is not incremented. + */ + dd->transfer.dma = 1; + dd->dma_chans[0]->DMA_B_START_REG = (uint32_t)buf; + /* + * Read is for all requested bytes, length - 1 means that DMA will + * perform length transfers. + */ + dd->dma_chans[0]->DMA_LEN_REG = (uint32_t)length - 1; + dd->dma_chans[0]->DMA_INT_REG = (uint32_t)length - 1; + dd->dma_chans[0]->DMA_CTRL_REG |= (1U << DMA_DMA0_CTRL_REG_DMA_ON_Pos); + + i2c_da1469x_fill_tx_dma_buffer(dd); + + i2c_regs->I2C_DMA_CR_REG = (1U << I2C_I2C_DMA_CR_REG_TDMAE_Pos) | + (1U << I2C_I2C_DMA_CR_REG_RDMAE_Pos); + i2c_regs->I2C_INTR_MASK_REG = I2C_I2C_INTR_MASK_REG_M_TX_ABRT_Msk | + I2C_I2C_INTR_MASK_REG_M_STOP_DET_Msk | + I2C_I2C_INTR_MASK_REG_M_SCL_STUCK_AT_LOW_Msk; + } else { + dd->transfer.dma = 0; + i2c_regs->I2C_INTR_MASK_REG = I2C_I2C_INTR_MASK_REG_M_TX_ABRT_Msk | + I2C_I2C_INTR_MASK_REG_M_STOP_DET_Msk | + I2C_I2C_INTR_MASK_REG_M_TX_EMPTY_Msk | + I2C_I2C_INTR_MASK_REG_M_RX_FULL_Msk | + I2C_I2C_INTR_MASK_REG_M_SCL_STUCK_AT_LOW_Msk; + } + i2c_da1469x_clear_int(i2c_regs); + i2c_regs->I2C_ENABLE_REG = 1; + + rc = os_sem_pend(&dd->sem, timeout); + if (rc == OS_TIMEOUT) { + rc = SYS_ETIMEOUT; + } else if (rc) { + rc = SYS_EUNKNOWN; + } else if (dd->errorsrc) { + rc = i2c_da1469x_translate_abort(dd->errorsrc); + } + + return rc; +} + +static int +i2c_da1469x_write(struct bus_dev *bdev, struct bus_node *bnode, + const uint8_t *buf, uint16_t length, os_time_t timeout, + uint16_t flags) +{ + struct bus_i2c_dev *dev = (struct bus_i2c_dev *)bdev; + struct bus_i2c_node *node = (struct bus_i2c_node *)bnode; + struct i2c_da1469x_dev_data *dd; + I2C_Type *i2c_regs; + int rc; + + BUS_DEBUG_VERIFY_DEV(dev); + BUS_DEBUG_VERIFY_NODE(node); + + + i2c_regs = da1469x_i2c[dev->cfg.i2c_num].regs; + dd = &i2c_dev_data[dev->cfg.i2c_num]; + + dd->errorsrc = 0; + dd->transfer.rlen = 0; + dd->transfer.wlen = length; + dd->transfer.data = (uint8_t *)buf; + dd->transfer.nostop = (flags & BUS_F_NOSTOP) ? 1 : 0; + dd->transfer.write = 1; + dd->transfer.started = 1; + dd->i2c_isr = i2c_da1469x_fill_fifo_for_tx; + if (length >= MIN_DMA_SIZE) { + dd->transfer.dma = 1; + i2c_regs->I2C_DMA_CR_REG = 0; + i2c_da1469x_fill_tx_dma_buffer(dd); + i2c_regs->I2C_DMA_CR_REG = (1U << I2C_I2C_DMA_CR_REG_TDMAE_Pos); + i2c_regs->I2C_INTR_MASK_REG = I2C_I2C_INTR_MASK_REG_M_TX_ABRT_Msk | + I2C_I2C_INTR_MASK_REG_M_STOP_DET_Msk | + I2C_I2C_INTR_MASK_REG_M_SCL_STUCK_AT_LOW_Msk; + } else { + dd->transfer.dma = 0; + i2c_regs->I2C_INTR_MASK_REG = I2C_I2C_INTR_MASK_REG_M_TX_ABRT_Msk | + I2C_I2C_INTR_MASK_REG_M_STOP_DET_Msk | + I2C_I2C_INTR_MASK_REG_M_TX_EMPTY_Msk | + I2C_I2C_INTR_MASK_REG_M_RX_FULL_Msk | + I2C_I2C_INTR_MASK_REG_M_SCL_STUCK_AT_LOW_Msk; + } + i2c_da1469x_clear_int(i2c_regs); + i2c_regs->I2C_ENABLE_REG = 1; + + rc = os_sem_pend(&dd->sem, timeout); + dd->dma_chans[1]->DMA_CTRL_REG &= ~(1U << DMA_DMA0_CTRL_REG_DMA_ON_Pos); + + if (rc == OS_TIMEOUT) { + rc = SYS_ETIMEOUT; + } else if (rc) { + rc = SYS_EUNKNOWN; + } else if (dd->errorsrc) { + rc = i2c_da1469x_translate_abort(dd->errorsrc); + } + + return rc; +} + +static int +i2c_da1469x_disable(struct bus_dev *bdev) +{ + struct bus_i2c_dev *dev = (struct bus_i2c_dev *)bdev; + I2C_Type *i2c_regs; + + BUS_DEBUG_VERIFY_DEV(dev); + + i2c_regs = da1469x_i2c[dev->cfg.i2c_num].regs; + + /* Disable I2C */ + i2c_regs->I2C_ENABLE_REG &= ~(1U << I2C_I2C_ENABLE_REG_I2C_EN_Pos); + + return 0; +} + +static const struct bus_dev_ops bus_i2c_da1469x_dma_ops = { + .init_node = i2c_da1469x_init_node, + .enable = i2c_da1469x_enable, + .configure = i2c_da1469x_configure, + .read = i2c_da1469x_read, + .write = i2c_da1469x_write, + .disable = i2c_da1469x_disable, +}; + +int +bus_i2c_da1469x_dev_init_func(struct os_dev *odev, void *arg) +{ + struct bus_i2c_dev *dev = (struct bus_i2c_dev *)odev; + struct bus_i2c_dev_cfg *cfg = arg; + struct i2c_da1469x_dev_data *dd; +#if MYNEWT_VAL(I2C_DA1469X_STAT) + char *stats_name; +#endif + int rc; + const struct da1469x_i2c_hw *i2c_hw; + static const struct da1469x_dma_config rx_cfg = { + .src_inc = false, + .dst_inc = true, + .priority = 0, + .burst_mode = 0, + .bus_width = 0, + }; + static const struct da1469x_dma_config tx_cfg = { + .src_inc = true, + .dst_inc = false, + .priority = 0, + .burst_mode = 0, + .bus_width = 0, + }; + + BUS_DEBUG_POISON_DEV(dev); + + if (i2c_dev_data[cfg->i2c_num].dev) { + return SYS_EALREADY; + } + + i2c_dev_data[cfg->i2c_num].dev = dev; + i2c_hw = &da1469x_i2c[cfg->i2c_num]; + dd = &i2c_dev_data[cfg->i2c_num]; + + dev->cfg = *cfg; + + da1469x_pd_acquire(MCU_PD_DOMAIN_COM); + + /* Select DIVN clock */ + CRG_COM->RESET_CLK_COM_REG = (1U << i2c_hw->clk_src_bit); + /* Enable clock */ + CRG_COM->SET_CLK_COM_REG = (1U << i2c_hw->clk_ena_bit); + + /* Abort ongoing transactions and disable I2C */ + i2c_hw->regs->I2C_ENABLE_REG |= (1 << I2C_I2C_ENABLE_REG_I2C_ABORT_Pos); + i2c_hw->regs->I2C_ENABLE_REG &= ~(1 << I2C_I2C_ENABLE_REG_I2C_EN_Pos); + while (i2c_hw->regs->I2C_ENABLE_STATUS_REG & I2C_I2C_ENABLE_STATUS_REG_IC_EN_Msk); + + i2c_hw->regs->I2C_CON_REG = (1U << I2C_I2C_CON_REG_I2C_MASTER_MODE_Pos) | + (1U << I2C_I2C_CON_REG_I2C_SPEED_Pos) | + (1U << I2C_I2C_CON_REG_I2C_RESTART_EN_Pos); + + i2c_hw->regs->I2C_INTR_MASK_REG = 0; + i2c_da1469x_clear_int(i2c_hw->regs); + da1469x_dma_acquire_periph(-1, i2c_hw->dma_periph, dd->dma_chans); + da1469x_dma_configure(dd->dma_chans[0], &rx_cfg, + (da1469x_dma_interrupt_cb)i2c_da1469x_dma_rx_isr, dev); + dd->dma_chans[0]->DMA_A_START_REG = (uint32_t)&i2c_hw->regs->I2C_DATA_CMD_REG; + da1469x_dma_configure(dd->dma_chans[1], &tx_cfg, + (da1469x_dma_interrupt_cb)i2c_da1469x_dma_tx_isr, dev); + dd->dma_chans[1]->DMA_B_START_REG = (uint32_t)&i2c_hw->regs->I2C_DATA_CMD_REG; + + mcu_gpio_set_pin_function(cfg->pin_scl, MCU_GPIO_MODE_OUTPUT_OPEN_DRAIN, + i2c_hw->scl_func); + mcu_gpio_set_pin_function(cfg->pin_sda, MCU_GPIO_MODE_OUTPUT_OPEN_DRAIN, + i2c_hw->sda_func); + + NVIC_DisableIRQ(i2c_hw->irqn); + NVIC_SetVector(i2c_hw->irqn, (uint32_t)i2c_hw->isr); + NVIC_ClearPendingIRQ(i2c_hw->irqn); + NVIC_EnableIRQ(i2c_hw->irqn); + + os_sem_init(&dd->sem, 0); + +#if MYNEWT_VAL(I2C_DA1469X_STAT) + asprintf(&stats_name, "i2c_da1469x_%d", cfg->i2c_num); + /* XXX should we assert or return error on failure? */ + stats_init_and_reg(STATS_HDR(dd->stats), + STATS_SIZE_INIT_PARMS(dd->stats, STATS_SIZE_32), + STATS_NAME_INIT_PARMS(i2c_da1469x_stats_section), + stats_name); +#endif + + rc = bus_dev_init_func(odev, (void*)&bus_i2c_da1469x_dma_ops); + assert(rc == 0); + + return 0; +} diff --git a/hw/bus/drivers/i2c_da1469x/syscfg.yml b/hw/bus/drivers/i2c_da1469x/syscfg.yml new file mode 100644 index 0000000..de8fe9b --- /dev/null +++ b/hw/bus/drivers/i2c_da1469x/syscfg.yml @@ -0,0 +1,35 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +syscfg.defs: + I2C_DA1469X_BUS_DRIVER: + description: 'Constant value set to 1 when package is included' + value: 1 + I2C_DA1469X_STAT: + description: > + Enable statistics for driver errors and applied workarounds. + value: 0 + I2C_DA1469X_TX_DMA_BUFFER_SIZE: + description: > + Buffer size (in 16 bits words) used for DMA transfers. + If set to 1 DMA is used only for reads. If set to greater + value static buffer will be used that will hold outgoing data + for DMA. + value: 1 + restrictions: + - '(I2C_DA1469X_TX_DMA_BUFFER_SIZE > 0)'