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 <noreply@anthropic.com>
Signed-off-by: dechao_gong <dechao_gong@realsil.com.cn>
This commit is contained in:
dechao_gong 2026-07-17 10:10:18 +08:00 committed by Xiang Xiao
parent 9b02dead5d
commit dfc55c9bbe
14 changed files with 1011 additions and 1 deletions

View file

@ -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

View file

@ -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 <ameba_i2c.h>.
*/
#include <nuttx/config.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <debug.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>
#include <nuttx/i2c/i2c_master.h>
#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;
}

View file

@ -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 <nuttx/config.h>
#include <stdint.h>
/****************************************************************************
* 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 */

View file

@ -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})

View file

@ -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
#

View file

@ -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

View file

@ -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 <nuttx/config.h>
#include <stdint.h>
/****************************************************************************
* 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 */

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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 <nuttx/config.h>
#include <sys/param.h>
#include <syslog.h>
#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 */

View file

@ -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

View file

@ -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_",