From dfc55c9bbee7b3f677c30763702623fa72f34ea2 Mon Sep 17 00:00:00 2001 From: dechao_gong Date: Fri, 17 Jul 2026 10:10:18 +0800 Subject: [PATCH] arch/arm/rtl8721dx: add shared Ameba I2C driver Add a shared NuttX I2C master lower-half for the Realtek Ameba I2C controllers (I2C0/I2C1) in arch/arm/src/common/ameba, driven through the SDK fwlib in polling mode. Per-chip wiring (controller count, register bases, clock masks, crossbar pad-mux codes and the fwlib I2C_InitTypeDef layout) lives in arch/arm/src/rtl8721dx/ameba_i2c_chip.h so a port to the other Ameba chips only supplies a same-named header. Each controller registers as /dev/i2cN from pke8721daf bring-up through the stock I2C character driver; a dedicated `i2c` defconfig drives the i2ctool for validation. Assisted-by: Claude Signed-off-by: dechao_gong --- arch/arm/src/common/ameba/Kconfig | 14 + arch/arm/src/common/ameba/ameba_i2c.c | 600 ++++++++++++++++++ arch/arm/src/common/ameba/ameba_i2c.h | 84 +++ arch/arm/src/rtl8721dx/CMakeLists.txt | 4 + arch/arm/src/rtl8721dx/Make.defs | 4 + arch/arm/src/rtl8721dx/ameba_board.mk | 9 + arch/arm/src/rtl8721dx/ameba_i2c_chip.h | 103 +++ .../pke8721daf/configs/i2c/defconfig | 51 ++ .../rtl8721dx/pke8721daf/src/CMakeLists.txt | 8 +- boards/arm/rtl8721dx/pke8721daf/src/Makefile | 9 + .../pke8721daf/src/rtl8721dx_bringup.c | 10 + .../rtl8721dx/pke8721daf/src/rtl8721dx_i2c.c | 102 +++ .../pke8721daf/src/rtl8721dx_pke8721daf.h | 13 + tools/nxstyle.c | 1 + 14 files changed, 1011 insertions(+), 1 deletion(-) create mode 100644 arch/arm/src/common/ameba/ameba_i2c.c create mode 100644 arch/arm/src/common/ameba/ameba_i2c.h create mode 100644 arch/arm/src/rtl8721dx/ameba_i2c_chip.h create mode 100644 boards/arm/rtl8721dx/pke8721daf/configs/i2c/defconfig create mode 100644 boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_i2c.c diff --git a/arch/arm/src/common/ameba/Kconfig b/arch/arm/src/common/ameba/Kconfig index b3391e049cf..ab27649ee54 100644 --- a/arch/arm/src/common/ameba/Kconfig +++ b/arch/arm/src/common/ameba/Kconfig @@ -55,4 +55,18 @@ config AMEBA_UART_TXBUFSIZE endif # AMEBA_UART +config AMEBA_I2C + bool "I2C" + default n + select I2C + select I2C_DRIVER + ---help--- + Expose the Ameba I2C controllers (I2C0/I2C1) as NuttX I2C master + buses at /dev/i2cN. The board selects which controller is used and + its SCL/SDA pads in its bring-up code. + + The driver (arch/arm/src/common/ameba/ameba_i2c.c) sits on the SDK + fwlib register layer and drives the DesignWare I2C block in polling + mode. + endmenu # Ameba Peripheral Support diff --git a/arch/arm/src/common/ameba/ameba_i2c.c b/arch/arm/src/common/ameba/ameba_i2c.c new file mode 100644 index 00000000000..66ac13c3509 --- /dev/null +++ b/arch/arm/src/common/ameba/ameba_i2c.c @@ -0,0 +1,600 @@ +/**************************************************************************** + * arch/arm/src/common/ameba/ameba_i2c.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +/* NuttX I2C master lower half for the Realtek Ameba I2C controllers (I2C0 + * and I2C1). Each controller is registered from board bring-up and appears + * as /dev/i2cN through the stock I2C character driver (i2c_register()). + * + * The controller is a Synopsys DesignWare I2C block programmed through the + * SDK fwlib I2C API in polling mode (no interrupts): I2C_Init() programs the + * speed/target, and I2C_MasterWrite()/Read()/RepeatRead() drive the FIFO and + * block until the transfer completes or the poll times out. + * + * Unlike the UART fwlib, which runs from on-chip ROM and resolves the secure + * register alias itself via TrustZone_IsSecure(), the I2C fwlib routines are + * compiled from ram_common/ameba_i2c.c (see AMEBA_FWLIB_SRCS) and use the + * register pointer they are handed without any secure conversion. NuttX + * runs on the KM4 core in the SECURE state, yet the I2C block only responds + * on its NON-secure alias (see the note at AMEBA_I2C_BASES in + * ameba_i2c_chip.h), so this driver hands the fwlib those non-secure bases. + * + * The chip-specific wiring (controller count, register bases, clock masks, + * pad-mux codes and the fwlib I2C_InitTypeDef layout) lives in the per-chip + * ameba_i2c_chip.h. To keep the vendor headers out of the NuttX include + * world, the few fwlib symbols and that struct layout used here are declared + * locally rather than pulled in from . + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ameba_i2c.h" +#include "ameba_i2c_chip.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* The register bases, peripheral-clock masks, crossbar pad-mux codes, + * controller count (AMEBA_NI2C) and I2C_InitTypeDef layout + * (AMEBA_I2C_HAS_DMA_FIELDS) all come from the per-chip ameba_i2c_chip.h. + * Everything below is common to every current Ameba chip. + */ + +#define AMEBA_GPIO_PUPD_UP 0x2 /* GPIO_PuPd_UP */ + +/* fwlib I2C_InitTypeDef field values (I2C_ADDR_*, I2C_*_MODE, ameba_i2c.h). + * These are identical on every Ameba chip audited, so they stay here rather + * than in the per-chip header. + */ + +#define AMEBA_I2C_ADDR_7BIT 0x0 /* I2C_ADDR_7BIT */ +#define AMEBA_I2C_ADDR_10BIT 0x1 /* I2C_ADDR_10BIT */ + +#define AMEBA_I2C_SS_MODE 0x1 /* I2C_SS_MODE (<= 100 kHz) */ +#define AMEBA_I2C_FS_MODE 0x2 /* I2C_FS_MODE (<= 400 kHz) */ +#define AMEBA_I2C_HS_MODE 0x3 /* I2C_HS_MODE (I2C1 only) */ + +#define AMEBA_I2C_MASTER_MODE 0x1 /* I2C_MASTER_MODE */ + +/* IC_RAW_INTR_STAT.TX_ABRT and the matching IC_CLR_TX_ABRT selector for + * I2C_ClearINT(). A transmit abort latches whenever an address or data + * byte goes unacknowledged (among other causes); the fwlib master helpers + * can miss it and still return the full byte count, so this driver checks + * it after each transfer to surface a NAK. It sits at bit 6 of both + * registers on every Ameba chip audited (amebadplus/smart/lite/green2/ + * RTL8720F), so it stays here rather than in the per-chip header. + */ + +#define AMEBA_I2C_TX_ABRT (1u << 6) /* IC_RAW_INTR_STAT TX_ABRT */ +#define AMEBA_I2C_R_TX_ABRT (1u << 6) /* I2C_ClearINT() TX_ABRT sel. */ + +/* IC_STATUS.TFNF (transmit FIFO not full) and the bounded spin used to wait + * for a free FIFO slot while pushing the leading bytes of a chained write + * (an I2C_M_NOSTOP segment). TFNF sits at bit 1 on every Ameba chip. + */ + +#define AMEBA_I2C_TFNF (1u << 1) /* IC_STATUS TFNF */ +#define AMEBA_I2C_FIFO_TIMEOUT 100000 /* ~loop guard, ample for 16 FIFO */ + +/* Second/third argument to fwlib "state" style APIs. */ + +#define AMEBA_DISABLE 0x0 +#define AMEBA_ENABLE 0x1 + +/* Default bus frequency used until the first transfer requests one. */ + +#define AMEBA_I2C_DEFAULT_FREQ I2C_SPEED_STANDARD + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Layout-compatible mirror of the fwlib I2C_InitTypeDef (all u32, same + * order); passed by address to I2C_StructInit()/I2C_Init(). The leading + * I2CIdx field is present on every Ameba chip audited (amebadplus/ + * amebasmart/amebalite/amebagreen2/RTL8720F), so it stays unconditional. A + * single per-chip layout switch keeps this struct byte-for-byte identical to + * the fwlib one: AMEBA_I2C_HAS_DMA_FIELDS gates the three DMA request-level + * fields between I2CFilter and I2CAckAddr1, which only some chips carry. + * See ameba_i2c_chip.h for the per-chip value. + */ + +struct ameba_i2c_init_s +{ + uint32_t idx; /* I2CIdx */ + uint32_t master; /* I2CMaster */ + uint32_t addrmod; /* I2CAddrMod */ + uint32_t spdmod; /* I2CSpdMod */ + uint32_t rxtl; /* I2CRXTL */ + uint32_t txtl; /* I2CTXTL */ + uint32_t mstrestr; /* I2CMstReSTR */ + uint32_t mstgc; /* I2CMstGC */ + uint32_t mststartb; /* I2CMstStartB */ + uint32_t slvnoack; /* I2CSlvNoAck */ + uint32_t slvackgc; /* I2CSlvAckGC */ + uint32_t ackaddr; /* I2CAckAddr */ + uint32_t slvsetup; /* I2CSlvSetup */ + uint32_t sdahd; /* I2CSdaHd */ + uint32_t clk; /* I2CClk (kHz) */ + uint32_t ipclk; /* I2CIPClk (Hz) */ + uint32_t filter; /* I2CFilter */ +#ifdef AMEBA_I2C_HAS_DMA_FIELDS + uint32_t txdmarqlv; /* I2CTxDMARqLv */ + uint32_t rxdmarqlv; /* I2CRxDMARqLv */ + uint32_t dmamod; /* I2CDMAMod */ +#endif + uint32_t ackaddr1; /* I2CAckAddr1 */ +}; + +struct ameba_i2c_dev_s +{ + struct i2c_master_s dev; /* I2C master lower half (must be first) */ + uintptr_t base; /* Non-secure I2C register base address */ + uint32_t periph; /* APBPeriph function mask (RCC arg 1) */ + uint32_t clk; /* APBPeriph clock mask (RCC arg 2) */ + uint32_t frequency; /* Currently programmed bus frequency (Hz) */ + uint16_t address; /* Currently programmed target address */ + uint8_t addrmod; /* Currently programmed AMEBA_I2C_ADDR_*BIT */ + uint8_t sclpin; /* SCL pad (AMEBA_PA()/AMEBA_PB() encoding) */ + uint8_t sdapin; /* SDA pad (AMEBA_PA()/AMEBA_PB() encoding) */ + uint8_t sclfid; /* Pin mux function code for the SCL pad */ + uint8_t sdafid; /* Pin mux function code for the SDA pad */ + mutex_t lock; /* Serializes bus access */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* SDK fwlib I2C/pin/clock API. The pin/clock helpers resolve to the on-chip + * ROM symbol table; the I2C helpers are compiled into libameba_fwlib.a from + * ram_common/ameba_i2c.c (see AMEBA_FWLIB_SRCS). + */ + +extern void RCC_PeriphClockCmd(uint32_t periph, uint32_t clock, + uint8_t newstate); +extern void Pinmux_Config(uint8_t pin, uint32_t func); +extern void PAD_PullCtrl(uint8_t pin, uint8_t pull); +extern void I2C_StructInit(struct ameba_i2c_init_s *init); +extern void I2C_Init(void *i2cx, struct ameba_i2c_init_s *init); +extern void I2C_Cmd(void *i2cx, uint8_t newstate); +extern uint32_t I2C_MasterWrite(void *i2cx, uint8_t *buf, uint32_t len); +extern uint32_t I2C_MasterRead(void *i2cx, uint8_t *buf, uint32_t len); +extern uint32_t I2C_MasterRepeatRead(void *i2cx, uint8_t *wbuf, + uint32_t wlen, uint8_t *rbuf, + uint32_t rlen); +extern uint32_t I2C_GetRawINT(void *i2cx); +extern uint32_t I2C_ClearINT(void *i2cx, uint32_t intrbit); +extern uint8_t I2C_CheckFlagState(void *i2cx, uint32_t flag); +extern void I2C_MasterSend(void *i2cx, uint8_t *buf, uint8_t cmd, + uint8_t stop, uint8_t restart); + +/* I2C master lower-half operations. */ + +static int ameba_i2c_transfer(struct i2c_master_s *dev, + struct i2c_msg_s *msgs, int count); +static int ameba_i2c_setup(struct i2c_master_s *dev); +static int ameba_i2c_shutdown(struct i2c_master_s *dev); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct i2c_ops_s g_ameba_i2c_ops = +{ + .transfer = ameba_i2c_transfer, + .setup = ameba_i2c_setup, + .shutdown = ameba_i2c_shutdown, +}; + +/* Per-controller register base, peripheral function/clock masks and crossbar + * pad-mux codes, indexed by controller number and supplied by the per-chip + * ameba_i2c_chip.h. g_i2c_periph feeds the "function" arg and g_i2c_clk + * the "clock" argument of RCC_PeriphClockCmd() (equal on this chip, distinct + * on others). + */ + +static const uintptr_t g_i2c_base[AMEBA_NI2C] = AMEBA_I2C_BASES; +static const uint32_t g_i2c_periph[AMEBA_NI2C] = AMEBA_I2C_APBPERIPH; +static const uint32_t g_i2c_clk[AMEBA_NI2C] = AMEBA_I2C_APBPERIPH_CLK; +static const uint8_t g_i2c_sclfid[AMEBA_NI2C] = AMEBA_I2C_SCLFID; +static const uint8_t g_i2c_sdafid[AMEBA_NI2C] = AMEBA_I2C_SDAFID; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ameba_i2c_reconfigure + * + * Description: + * (Re)program the controller for a given target address, addressing mode + * and bus frequency, and enable it. This is a comparatively heavy DW-IP + * reconfiguration (the block must be disabled to change IC_TAR/speed), so + * it runs only when one of those parameters changes between transfers. + * + ****************************************************************************/ + +static void ameba_i2c_reconfigure(struct ameba_i2c_dev_s *priv, + uint16_t address, uint8_t addrmod, + uint32_t frequency) +{ + struct ameba_i2c_init_s init; + void *i2cx = (void *)priv->base; + + memset(&init, 0, sizeof(init)); + I2C_StructInit(&init); + + init.master = AMEBA_I2C_MASTER_MODE; + init.addrmod = addrmod; + init.ackaddr = address; + + /* Enable RESTART so a combined write-then-read (I2C_MasterRepeatRead()) + * emits a true repeated START; the DW IP ignores the RESTART command bit + * unless IC_CON.IC_RESTART_EN is set. + */ + + init.mstrestr = AMEBA_ENABLE; + + if (frequency <= I2C_SPEED_STANDARD) + { + init.spdmod = AMEBA_I2C_SS_MODE; + } + else if (frequency <= I2C_SPEED_FAST) + { + init.spdmod = AMEBA_I2C_FS_MODE; + } + else + { + init.spdmod = AMEBA_I2C_HS_MODE; + } + + init.clk = frequency / 1000; /* fwlib expects the bus clock in kHz */ + + I2C_Cmd(i2cx, AMEBA_DISABLE); + I2C_Init(i2cx, &init); + I2C_Cmd(i2cx, AMEBA_ENABLE); + + priv->address = address; + priv->addrmod = addrmod; + priv->frequency = frequency; +} + +/**************************************************************************** + * Name: ameba_i2c_aborted + * + * Description: + * Return true if the controller latched a transmit abort during the last + * transfer. The fwlib master helpers poll IC_STATUS.TFE, which the DW IP + * also sets when it flushes the TX FIFO on an abort, so they can return + * the full byte count even though the address (or a data byte) was never + * acknowledged. Checking IC_RAW_INTR_STAT.TX_ABRT directly is the only + * reliable way to detect that NAK. The abort is cleared here (reading + * IC_CLR_TX_ABRT) so the block is left in a clean state. + * + ****************************************************************************/ + +static bool ameba_i2c_aborted(struct ameba_i2c_dev_s *priv) +{ + void *i2cx = (void *)priv->base; + + if ((I2C_GetRawINT(i2cx) & AMEBA_I2C_TX_ABRT) != 0) + { + I2C_ClearINT(i2cx, AMEBA_I2C_R_TX_ABRT); + return true; + } + + return false; +} + +/**************************************************************************** + * Name: ameba_i2c_writeprefix + * + * Description: + * Push the bytes of a non-terminating write segment (one flagged + * I2C_M_NOSTOP) into the TX FIFO without asserting STOP. Because the DW + * IP only issues a STOP when a byte carries the STOP bit, leaving it clear + * keeps the current transaction open so the following segment continues + * without a repeated START -- this is how I2C_M_NOSTOP/I2C_M_NOSTART are + * honoured for chained writes such as the "register address + data" pair + * emitted by "i2c set". The terminating segment is handled by the tested + * I2C_MasterWrite(), which supplies the STOP and the final TFE wait. + * + ****************************************************************************/ + +static int ameba_i2c_writeprefix(struct ameba_i2c_dev_s *priv, + const uint8_t *buf, uint32_t len) +{ + void *i2cx = (void *)priv->base; + uint32_t i; + uint32_t to; + + for (i = 0; i < len; i++) + { + uint8_t byte = buf[i]; + + /* Wait for a free TX FIFO slot before pushing the next byte. */ + + for (to = AMEBA_I2C_FIFO_TIMEOUT; + to > 0 && I2C_CheckFlagState(i2cx, AMEBA_I2C_TFNF) == 0; to--); + + if (to == 0) + { + return -ETIMEDOUT; + } + + /* Write, no STOP, no RESTART: leave the transaction open. */ + + I2C_MasterSend(i2cx, &byte, 0, 0, 0); + } + + return OK; +} + +/**************************************************************************** + * Name: ameba_i2c_transfer + * + * Description: + * Run a sequence of I2C messages on the bus. A write message immediately + * followed by a read message to the same target is fused into a single + * combined transaction with a repeated START. A write flagged + * I2C_M_NOSTOP is chained to the following write segment(s) without an + * intervening STOP (e.g. register address + data from "i2c set"). Every + * other message is a standalone START..STOP write or read. + * + ****************************************************************************/ + +static int ameba_i2c_transfer(struct i2c_master_s *dev, + struct i2c_msg_s *msgs, int count) +{ + struct ameba_i2c_dev_s *priv = (struct ameba_i2c_dev_s *)dev; + void *i2cx = (void *)priv->base; + int ret = OK; + int i; + + if (msgs == NULL || count < 1) + { + return -EINVAL; + } + + nxmutex_lock(&priv->lock); + + for (i = 0; i < count; ) + { + struct i2c_msg_s *msg = &msgs[i]; + uint8_t addrmod = (msg->flags & I2C_M_TEN) ? + AMEBA_I2C_ADDR_10BIT : AMEBA_I2C_ADDR_7BIT; + uint32_t freq = (msg->frequency != 0) ? msg->frequency : + AMEBA_I2C_DEFAULT_FREQ; + uint32_t done; + + /* Reprogram the controller only when the target, addressing mode or + * bus frequency differs from the running configuration. + */ + + if (msg->addr != priv->address || addrmod != priv->addrmod || + freq != priv->frequency) + { + ameba_i2c_reconfigure(priv, msg->addr, addrmod, freq); + } + + if ((msg->flags & I2C_M_READ) == 0 && (i + 1) < count && + (msgs[i + 1].flags & I2C_M_READ) != 0 && + msgs[i + 1].addr == msg->addr) + { + /* Write followed by read to the same target: one transaction with + * a repeated START between the two phases. + */ + + struct i2c_msg_s *rd = &msgs[i + 1]; + + done = I2C_MasterRepeatRead(i2cx, msg->buffer, msg->length, + rd->buffer, rd->length); + if (ameba_i2c_aborted(priv)) + { + ret = -ENXIO; + break; + } + + if (done != (uint32_t)rd->length) + { + ret = -EIO; + break; + } + + i += 2; + } + else if ((msg->flags & I2C_M_READ) != 0) + { + done = I2C_MasterRead(i2cx, msg->buffer, msg->length); + + if (ameba_i2c_aborted(priv)) + { + ret = -ENXIO; + break; + } + + if (done != (uint32_t)msg->length) + { + ret = -EIO; + break; + } + + i += 1; + } + else if ((msg->flags & I2C_M_NOSTOP) != 0) + { + /* Leading segment of a chained write (e.g. the register address + * before the data in "i2c set"): push its bytes but keep the + * transaction open so the next segment continues without a STOP. + */ + + ret = ameba_i2c_writeprefix(priv, msg->buffer, msg->length); + if (ret < 0) + { + break; + } + + i += 1; + } + else + { + done = I2C_MasterWrite(i2cx, msg->buffer, msg->length); + if (ameba_i2c_aborted(priv)) + { + ret = -ENXIO; + break; + } + + if (done != (uint32_t)msg->length) + { + ret = -EIO; + break; + } + + i += 1; + } + } + + /* A failed transfer can leave the DW IP with a latched TX abort; force a + * full reconfiguration before the next transfer to clear it. + */ + + if (ret < 0) + { + priv->frequency = 0; + priv->address = 0xffff; + } + + nxmutex_unlock(&priv->lock); + return ret; +} + +/**************************************************************************** + * Name: ameba_i2c_setup + * + * Description: + * Called by the I2C character driver on the first open. Gate the + * peripheral clock and route the SCL/SDA pads to this controller; the + * speed/target are programmed lazily on the first transfer. + * + ****************************************************************************/ + +static int ameba_i2c_setup(struct i2c_master_s *dev) +{ + struct ameba_i2c_dev_s *priv = (struct ameba_i2c_dev_s *)dev; + + RCC_PeriphClockCmd(priv->periph, priv->clk, AMEBA_ENABLE); + + Pinmux_Config(priv->sclpin, priv->sclfid); + Pinmux_Config(priv->sdapin, priv->sdafid); + PAD_PullCtrl(priv->sclpin, AMEBA_GPIO_PUPD_UP); + PAD_PullCtrl(priv->sdapin, AMEBA_GPIO_PUPD_UP); + + /* Force the first transfer to program the controller. */ + + priv->frequency = 0; + priv->address = 0xffff; + return OK; +} + +/**************************************************************************** + * Name: ameba_i2c_shutdown + * + * Description: + * Called by the I2C character driver on the last close. Disable the + * controller and gate its peripheral clock off. + * + ****************************************************************************/ + +static int ameba_i2c_shutdown(struct i2c_master_s *dev) +{ + struct ameba_i2c_dev_s *priv = (struct ameba_i2c_dev_s *)dev; + + I2C_Cmd((void *)priv->base, AMEBA_DISABLE); + RCC_PeriphClockCmd(priv->periph, priv->clk, AMEBA_DISABLE); + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ameba_i2c_register + * + * Description: + * See ameba_i2c.h. + * + ****************************************************************************/ + +int ameba_i2c_register(int bus, uint8_t sclpin, uint8_t sdapin) +{ + struct ameba_i2c_dev_s *priv; + int ret; + + if (bus < 0 || bus >= AMEBA_NI2C) + { + return -EINVAL; + } + + priv = kmm_zalloc(sizeof(struct ameba_i2c_dev_s)); + if (priv == NULL) + { + return -ENOMEM; + } + + priv->dev.ops = &g_ameba_i2c_ops; + priv->base = g_i2c_base[bus]; + priv->periph = g_i2c_periph[bus]; + priv->clk = g_i2c_clk[bus]; + priv->sclpin = sclpin; + priv->sdapin = sdapin; + priv->sclfid = g_i2c_sclfid[bus]; + priv->sdafid = g_i2c_sdafid[bus]; + priv->address = 0xffff; + nxmutex_init(&priv->lock); + + ret = i2c_register(&priv->dev, bus); + if (ret < 0) + { + _err("ERROR: i2c_register(/dev/i2c%d) failed: %d\n", bus, ret); + nxmutex_destroy(&priv->lock); + kmm_free(priv); + } + + return ret; +} diff --git a/arch/arm/src/common/ameba/ameba_i2c.h b/arch/arm/src/common/ameba/ameba_i2c.h new file mode 100644 index 00000000000..1c7435722f9 --- /dev/null +++ b/arch/arm/src/common/ameba/ameba_i2c.h @@ -0,0 +1,84 @@ +/**************************************************************************** + * arch/arm/src/common/ameba/ameba_i2c.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_I2C_H +#define __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_I2C_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* The Ameba I2C controllers exposed to NuttX as I2C master buses. The SCL + * and SDA pads are given with the same AMEBA_PA()/AMEBA_PB() PinName code + * used by the GPIO driver (see ameba_gpio.h); any pad can be routed to an + * I2C bus through the pin mux. + */ + +#define AMEBA_I2C0 0 +#define AMEBA_I2C1 1 + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: ameba_i2c_register + * + * Description: + * Configure one Ameba I2C controller as a master bus and register it with + * the NuttX I2C character driver at /dev/i2cN, where N is the bus number. + * + * Input Parameters: + * bus - The controller index, AMEBA_I2C0 or AMEBA_I2C1. Also used as + * the /dev/i2cN minor number. + * sclpin - The SCL pad, encoded with AMEBA_PA()/AMEBA_PB(). + * sdapin - The SDA pad, encoded with AMEBA_PA()/AMEBA_PB(). + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int ameba_i2c_register(int bus, uint8_t sclpin, uint8_t sdapin); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_I2C_H */ diff --git a/arch/arm/src/rtl8721dx/CMakeLists.txt b/arch/arm/src/rtl8721dx/CMakeLists.txt index a0127883991..bcf9450f9a3 100644 --- a/arch/arm/src/rtl8721dx/CMakeLists.txt +++ b/arch/arm/src/rtl8721dx/CMakeLists.txt @@ -48,6 +48,10 @@ if(CONFIG_AMEBA_UART) list(APPEND SRCS ${AMEBA_COMMON}/ameba_uart.c) endif() +if(CONFIG_AMEBA_I2C) + list(APPEND SRCS ${AMEBA_COMMON}/ameba_i2c.c) +endif() + target_include_directories(arch PRIVATE ${AMEBA_COMMON}) target_sources(arch PRIVATE ${SRCS}) diff --git a/arch/arm/src/rtl8721dx/Make.defs b/arch/arm/src/rtl8721dx/Make.defs index 65420574bf6..fbe728e46f4 100644 --- a/arch/arm/src/rtl8721dx/Make.defs +++ b/arch/arm/src/rtl8721dx/Make.defs @@ -58,6 +58,10 @@ ifeq ($(CONFIG_AMEBA_UART),y) CHIP_CSRCS += ameba_uart.c endif +ifeq ($(CONFIG_AMEBA_I2C),y) +CHIP_CSRCS += ameba_i2c.c +endif + ############################################################################ # Realtek RTL8721Dx SDK integration # diff --git a/arch/arm/src/rtl8721dx/ameba_board.mk b/arch/arm/src/rtl8721dx/ameba_board.mk index 2af16d519f8..1a59dce5832 100644 --- a/arch/arm/src/rtl8721dx/ameba_board.mk +++ b/arch/arm/src/rtl8721dx/ameba_board.mk @@ -137,6 +137,15 @@ ifeq ($(CONFIG_AMEBA_UART),y) AMEBA_FWLIB_SRCS += $(AMEBA_SOC)/fwlib/ram_common/ameba_uart.c endif +# I2C register layer. Unlike UART, the fwlib I2C API is NOT in ROM: the I2C +# driver (arch/.../common/ameba/ameba_i2c.c) calls I2C_Init/StructInit/Cmd and +# I2C_MasterWrite/Read/RepeatRead, all of which are compiled from this RAM +# source and must be linked in (--gc-sections drops the unused DMA/interrupt +# helpers). +ifeq ($(CONFIG_AMEBA_I2C),y) +AMEBA_FWLIB_SRCS += $(AMEBA_SOC)/fwlib/ram_common/ameba_i2c.c +endif + # -Wno-int-conversion: the vendored SDK passes NULL to irq_register()'s u32 # "Data" (interrupt context) argument in many places -- an intentional # NULL-as-context idiom. Silence -Wint-conversion for the SDK fwlib sources diff --git a/arch/arm/src/rtl8721dx/ameba_i2c_chip.h b/arch/arm/src/rtl8721dx/ameba_i2c_chip.h new file mode 100644 index 00000000000..7d5bf209a27 --- /dev/null +++ b/arch/arm/src/rtl8721dx/ameba_i2c_chip.h @@ -0,0 +1,103 @@ +/**************************************************************************** + * arch/arm/src/rtl8721dx/ameba_i2c_chip.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 __ARCH_ARM_SRC_RTL8721DX_AMEBA_I2C_CHIP_H +#define __ARCH_ARM_SRC_RTL8721DX_AMEBA_I2C_CHIP_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Per-chip I2C wiring for RTL8721DX (amebadplus). The shared driver + * (arch/arm/src/common/ameba/ameba_i2c.c) includes this header to learn how + * many I2C controllers the chip exposes and, for each, its register base, + * peripheral-clock masks and crossbar pad-mux codes. It also learns the + * chip's I2C_InitTypeDef layout through AMEBA_I2C_HAS_DMA_FIELDS. + * + * Contract for the other Ameba chips (amebalite / amebasmart / amebagreen2 / + * RTL8720F): supply a same-named header on the chip include path with the + * macros below. What differs per chip, from the fwlib audit: + * + * 1. Controller count and bases: 2 controllers on amebadplus / amebalite / + * amebagreen2 / RTL8720F, 3 on amebasmart (its I2C0 lives in the LP + * domain). The bases below are the NON-secure peripheral aliases; see + * the note in ameba_i2c.c on why the secure alias must not be used. + * + * 2. APBPeriph "function" and "clock" masks are two separate lists because + * RCC_PeriphClockCmd() takes them as distinct arguments. They are + * equal on every current chip, but amebasmart encodes the bits + * differently (bit25/26/27, group bit30=0) versus (bit30|bit10/11) + * here, so each chip must supply its own values. + * + * 3. Pad mux: amebadplus / amebalite / amebagreen2 / RTL8720F use a + * crossbar with a distinct function code per SCL/SDA signal (supplied + * as two lists). amebasmart instead has a single generic + * PINMUX_FUNCTION_I2C code (7) shared by every I2C pad; that chip fills + * both the SCL and SDA lists with 7 and the driver needs no change. + * + * 4. I2C_InitTypeDef layout: amebadplus / amebagreen2 / RTL8720F carry + * three DMA request-level fields (I2CTxDMARqLv / I2CRxDMARqLv / + * I2CDMAMod) between I2CFilter and I2CAckAddr1 (21 u32 fields); + * amebalite and amebasmart omit them (18 fields). Define + * AMEBA_I2C_HAS_DMA_FIELDS to 1 when present so the driver's mirror + * struct stays byte-for-byte identical to the fwlib struct that + * I2C_StructInit()/I2C_Init() use. + */ + +#define AMEBA_NI2C 2 + +/* NON-secure I2C register bases (I2C0_REG_BASE / I2C1_REG_BASE). */ + +#define AMEBA_I2C_BASES { 0x41108000ul, 0x4110a000ul } + +/* APBPeriph_I2Cx (function) and APBPeriph_I2Cx_CLOCK masks. Equal on this + * chip; kept as two lists so chips where they differ can supply both. + */ + +#define AMEBA_I2C_APBPERIPH \ + { (((uint32_t)1 << 30) | ((uint32_t)1 << 10)), \ + (((uint32_t)1 << 30) | ((uint32_t)1 << 11)) } + +#define AMEBA_I2C_APBPERIPH_CLK \ + { (((uint32_t)1 << 30) | ((uint32_t)1 << 10)), \ + (((uint32_t)1 << 30) | ((uint32_t)1 << 11)) } + +/* Crossbar pad-mux function codes (PINMUX_FUNCTION_I2Cx_SCL/SDA), indexed by + * controller. On a chip with one generic I2C code, set both to that code. + */ + +#define AMEBA_I2C_SCLFID { 48, 50 } /* I2C0_SCL, I2C1_SCL */ +#define AMEBA_I2C_SDAFID { 49, 51 } /* I2C0_SDA, I2C1_SDA */ + +/* The amebadplus I2C_InitTypeDef carries the DMA request-level fields. */ + +#define AMEBA_I2C_HAS_DMA_FIELDS 1 + +#endif /* __ARCH_ARM_SRC_RTL8721DX_AMEBA_I2C_CHIP_H */ diff --git a/boards/arm/rtl8721dx/pke8721daf/configs/i2c/defconfig b/boards/arm/rtl8721dx/pke8721daf/configs/i2c/defconfig new file mode 100644 index 00000000000..6dc0c975d42 --- /dev/null +++ b/boards/arm/rtl8721dx/pke8721daf/configs/i2c/defconfig @@ -0,0 +1,51 @@ +# +# This file is autogenerated: PLEASE DO NOT EDIT IT. +# +# You can use "make menuconfig" to make any modifications to the installed .config file. +# You can then do "make savedefconfig" to generate a new defconfig file that includes your +# modifications. +# +# CONFIG_DEBUG_WARN is not set +CONFIG_AMEBA_I2C=y +CONFIG_ARCH="arm" +CONFIG_ARCH_BOARD="pke8721daf" +CONFIG_ARCH_BOARD_PKE8721DAF=y +CONFIG_ARCH_CHIP="rtl8721dx" +CONFIG_ARCH_CHIP_RTL8721DX=y +CONFIG_ARCH_INTERRUPTSTACK=2048 +CONFIG_ARCH_STACKDUMP=y +CONFIG_ARMV8M_SYSTICK=y +CONFIG_BUILTIN=y +CONFIG_DEBUG_ASSERTIONS=y +CONFIG_DEBUG_FEATURES=y +CONFIG_DEBUG_FULLOPT=y +CONFIG_DEBUG_SYMBOLS=y +CONFIG_DEFAULT_TASK_STACKSIZE=4096 +CONFIG_EXAMPLES_HELLO=y +CONFIG_FS_PROCFS=y +CONFIG_FS_TMPFS=y +CONFIG_IDLETHREAD_STACKSIZE=4096 +CONFIG_INIT_ENTRYPOINT="nsh_main" +CONFIG_LIBC_MEMFD_ERROR=y +CONFIG_MM_DEFAULT_ALIGNMENT=32 +CONFIG_NSH_BUILTIN_APPS=y +CONFIG_NSH_FILEIOSIZE=512 +CONFIG_NSH_READLINE=y +CONFIG_PREALLOC_TIMERS=4 +CONFIG_RAM_SIZE=294912 +CONFIG_RAM_START=0x20020000 +CONFIG_RR_INTERVAL=200 +CONFIG_RTL8721DX_FLASH_FS=y +CONFIG_SCHED_HPWORK=y +CONFIG_SCHED_HPWORKPRIORITY=192 +CONFIG_SCHED_LPWORK=y +CONFIG_STACK_COLORATION=y +CONFIG_START_DAY=16 +CONFIG_START_MONTH=6 +CONFIG_START_YEAR=2026 +CONFIG_SYSTEM_I2CTOOL=y +CONFIG_SYSTEM_NSH=y +CONFIG_SYSTEM_NSH_STACKSIZE=2500 +CONFIG_TIMER=y +CONFIG_TIMER_ARCH=y +CONFIG_USEC_PER_TICK=1000 diff --git a/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt b/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt index a513559f9c7..2ccfdc647a5 100644 --- a/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt +++ b/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt @@ -30,9 +30,15 @@ if(CONFIG_AMEBA_UART) list(APPEND SRCS rtl8721dx_uart.c) endif() +if(CONFIG_AMEBA_I2C) + list(APPEND SRCS rtl8721dx_i2c.c) +endif() + target_sources(board PRIVATE ${SRCS}) -if(CONFIG_AMEBA_GPIO OR CONFIG_AMEBA_UART) +if(CONFIG_AMEBA_GPIO + OR CONFIG_AMEBA_UART + OR CONFIG_AMEBA_I2C) # The board pin/UART tables pull in the shared driver's public headers from # arch/arm/src/common/ameba/, not on the default board include path. target_include_directories(board diff --git a/boards/arm/rtl8721dx/pke8721daf/src/Makefile b/boards/arm/rtl8721dx/pke8721daf/src/Makefile index 60e3a2ca1fe..ed80ab1a32e 100644 --- a/boards/arm/rtl8721dx/pke8721daf/src/Makefile +++ b/boards/arm/rtl8721dx/pke8721daf/src/Makefile @@ -42,4 +42,13 @@ CSRCS += rtl8721dx_uart.c CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)arm$(DELIM)src$(DELIM)common$(DELIM)ameba endif +ifeq ($(CONFIG_AMEBA_I2C),y) +CSRCS += rtl8721dx_i2c.c + +# The board I2C table pulls in the shared driver's public header from +# arch/arm/src/common/ameba/, which is not on the default board include path. + +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)arm$(DELIM)src$(DELIM)common$(DELIM)ameba +endif + include $(TOPDIR)/boards/Board.mk diff --git a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c index 0bd11927d27..ab034d08ff7 100644 --- a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c +++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c @@ -128,6 +128,16 @@ int rtl8721dx_bringup(void) } #endif +#ifdef CONFIG_AMEBA_I2C + /* Register the board's I2C master buses at /dev/i2cN. */ + + ret = rtl8721dx_i2c_initialize(); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: rtl8721dx_i2c_initialize failed: %d\n", ret); + } +#endif + /* Install the inter-core HW IPC-semaphore RTOS hooks LAST -- after all the * flash / WHC bring-up above, and just before this (board_late_initialize) * path returns and nx_start() hands off to the init task. diff --git a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_i2c.c b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_i2c.c new file mode 100644 index 00000000000..a73c61ff2a2 --- /dev/null +++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_i2c.c @@ -0,0 +1,102 @@ +/**************************************************************************** + * boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_i2c.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include "ameba_gpio.h" +#include "ameba_i2c.h" +#include "rtl8721dx_pke8721daf.h" + +#ifdef CONFIG_AMEBA_I2C + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* One entry per I2C bus exposed to NuttX at /dev/i2cN. The SCL/SDA pads are + * examples used by the `i2c` config (system/i2c i2ctool) -- any pad can be + * routed to an I2C controller through the pin mux, so adjust them to match + * your board's wiring. + */ + +struct rtl8721dx_i2c_s +{ + int bus; /* Controller index (AMEBA_I2C0/AMEBA_I2C1) */ + uint8_t sclpin; /* SCL pad (AMEBA_PA()/AMEBA_PB() encoding) */ + uint8_t sdapin; /* SDA pad (AMEBA_PA()/AMEBA_PB() encoding) */ +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct rtl8721dx_i2c_s g_i2c_buses[] = +{ + { + AMEBA_I2C0, AMEBA_PB(20), AMEBA_PB(21) + }, + { + AMEBA_I2C1, AMEBA_PB(18), AMEBA_PB(19) + }, +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rtl8721dx_i2c_initialize + * + * Description: + * Register the board's I2C master buses at /dev/i2cN. + * + ****************************************************************************/ + +int rtl8721dx_i2c_initialize(void) +{ + int ret; + int i; + + for (i = 0; i < (int)nitems(g_i2c_buses); i++) + { + ret = ameba_i2c_register(g_i2c_buses[i].bus, g_i2c_buses[i].sclpin, + g_i2c_buses[i].sdapin); + if (ret < 0) + { + syslog(LOG_ERR, + "ERROR: ameba_i2c_register(/dev/i2c%d) failed: %d\n", + g_i2c_buses[i].bus, ret); + return ret; + } + } + + return OK; +} + +#endif /* CONFIG_AMEBA_I2C */ diff --git a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h index 8d57a1e09b3..52be521d3d9 100644 --- a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h +++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h @@ -95,6 +95,19 @@ int rtl8721dx_gpio_initialize(void); int rtl8721dx_uart_initialize(void); #endif +#ifdef CONFIG_AMEBA_I2C +/**************************************************************************** + * Name: rtl8721dx_i2c_initialize + * + * Description: + * Register the board's I2C master buses at /dev/i2cN + * (boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_i2c.c). + * + ****************************************************************************/ + +int rtl8721dx_i2c_initialize(void); +#endif + #ifdef CONFIG_RTL8721DX_FLASH_FS /**************************************************************************** * Name: ameba_flash_fs_initialize diff --git a/tools/nxstyle.c b/tools/nxstyle.c index 2e9ae212af8..f5ddef85124 100644 --- a/tools/nxstyle.c +++ b/tools/nxstyle.c @@ -275,6 +275,7 @@ static const char *g_white_prefix[] = "FLASH_", "GPIO_", "Get_OSC131_", /* Get_OSC131_STATE — Ameba SDK RTC accessor */ + "I2C_", /* I2C_Init, I2C_MasterWrite, I2C_InitTypeDef, etc. */ "IPC_", "LOGUART_", "OSC2M_",