开发NanoPI Neo(Allwinner H3)Linux设备驱动时TWI(I2C)寄存器操作异常问题咨询
Hey there, let's tackle your NanoPI Neo (Allwinner H3) TWI register access problems step by step. I've worked with sunxi chips before, so I'll share what I know to get your I2C pins responding.
1. Allwinner H3 GPIO & TWI Programming Documentation
Allwinner doesn't officially publish full, detailed GPIO/TWI programming docs, but you have reliable, community-backed resources to work with:
- Sunxi Community Wiki: The sunxi community maintains comprehensive hardware documentation for Allwinner chips, including the H3. Look for sections on GPIO multiplexing, CCU (Clock Control Unit), and TWI peripherals—this is where most developers get their register details.
- Linux Kernel Source Code: The official Linux kernel has production-ready drivers for H3 GPIO (
drivers/gpio/gpio-sunxi.c) and TWI (drivers/i2c/busses/i2c-sunxi.c). These are the most authoritative references, since they're designed to work directly with the hardware. You can study how the kernel enables clocks, configures pin muxing, and initializes TWI registers. - Third-Party Register Manuals: Many developers have compiled H3 register maps into shareable docs. These often include exact addresses and bit definitions for GPIO mux, CCU, and TWI registers—you can find these on embedded Linux forums or open-source repositories focused on sunxi devices.
2. Required Register Settings to Activate TWI Functionality
Configuring just the pin mux isn't enough—you need to enable clocks, reset the peripheral, and initialize core TWI registers. Here's what you need to do:
Step 1: Verify GPIO Multiplexing
You've set PA11/PA12 to TWI0_SCK/SDA, but double-check the mux register to be sure:
- The PA port mux register is at
0x01C20800(GPIO_PA_MUX_REG)- PA11 corresponds to bits 22-23: set to
01(binary) to enable TWI0_SCK - PA12 corresponds to bits 24-25: set to
01(binary) to enable TWI0_SDA
- PA11 corresponds to bits 22-23: set to
- Make sure you're using
iowrite32to modify this register (direct pointer writes can be unreliable for IO memory)
Step 2: Enable TWI0 Clock (CCU Module)
TWI peripherals won't respond without clock input:
- Open the clock gate for TWI0: Write
1to bit 16 ofCCU_BUS_CLK_GATING_REG0(address0x01C20060) - Configure TWI0 clock frequency: Use
CCU_TWI0_CLK_REG(address0x01C201BC) to set the clock divider. For 100kHz I2C, you'll need to calculate the correct divider based on the parent clock (usually 24MHz or 48MHz for H3).
Step 3: Soft-Reset TWI0 Peripheral
After enabling the clock, reset the TWI module to ensure it starts in a clean state:
- Write
1to bit 16 ofCCU_BUS_SOFT_RST_REG0(address0x01C20020), then write0back to complete the reset.
Step 4: Initialize TWI Core Registers
Once the hardware is enabled, initialize the TWI control register:
- Access
TWI_CONTROL_REG(offset0x00from TWI0 base address0x01C2AC00)- Set bit 7 (
EN) to1to enable the TWI peripheral - Configure the clock divider field (
CLK_DIV) to match your desired I2C speed - Disable interrupts (bit 6,
INT_EN) if you're not using them for now
- Set bit 7 (
Code Fixes for Reliable Register Access
Your current code uses direct pointer access for IO memory, which can cause issues (compiler optimizations, missing memory barriers). Replace the raw pointer writes/reads with kernel-provided IO functions:
#include <linux/io.h> // Make sure to include this header static long drv_ioctl_twi_rw(struct file *file, unsigned int cmd, unsigned long * arg) { long retval = 0; // Changed default retval to 0 for success unsigned c; uint32_t r; void *x; r = cmd & IO_ADDRESS_MSK; x = vmaddr + r; c = cmd & IO_CMD_MSK; switch(c) { case IO_CMD_READ: r = ioread32(x); // Use kernel's IO read function if (copy_to_user((void *)arg, &r, sizeof(r))) { retval = -EFAULT; } break; case IO_CMD_WRITE: iowrite32(*arg, x); // Use kernel's IO write function break; default: retval = -ENOTTY; // Better error code for unrecognized cmd break; } return retval; }
Also, make sure your drv_init_twi_vm function checks the return value of ioremap—if it fails, you should return an error instead of proceeding:
inline static int drv_init_twi_vm(void) { vmaddr = ioremap(TWI_IOBASE(0), TWI_PAGESIZE); if (!vmaddr) { return -ENOMEM; } return 0; }
Once you implement these steps—enable clocks, reset the peripheral, fix the IO access code—your TWI registers should stop returning 0, and you'll start seeing activity on PA11/PA12.
内容的提问来源于stack exchange,提问作者Sir Jo Black

