Issues creating a Linux device driver to manage NanoPI TWI (I2C) registers - c

I'm trying to develop a Linux device driver to manage the GPIO registers of a NanoPI Neo card (Allwinner H3).
I'm using a simple approach to understand the GPIO registers behaviour by means the use of only two driver functions: open and ioctl.
My driver implementation, at now, is able to manage a lot of registers such as RTC and CPU-PORT and to read write some other registers.
But I'm having issues in using/managing TWI registers (I2C).
The blocking me issue is that whathever register I read or read after writing any value to the register itself always returns 0x00000000 and nothing seems to happen at pin HW level (PA11/PA12 see below)
I read on the CPU datasheet (see: paragraph 8.1 of the document) the registers value and base address to manage TWI0, base address should be 0x01C2AC00.
I can't find any AllWinner H3 GPIO programmer's reference and I'm not sure if specific operations are required to activate the TWI register functionality. The only operations I have done are to set registers PA11 and PA12 to be the I/O of TWI0_SCK and TWI0_SDA.
Questions:
Do you have any news of an "AllWinner H3 GPIO Programmer's Reference"?
Do you know which GPIO register I have to set / modify to get TWI enabled or at least give me "signs of life"?
A very strong simplification, aimed at using only TWI0 registers, of the mapping and ioctl functions of the driver I wrote might be the following code, but my code is much more sophisticated and I know it works with many other registers.
#define TWI_IOBASE(n) (0x01C2AC00 + 0x400*((n)&3))
#define TWI_PAGESIZE 0x400
#define IO_ADDRESS_MSK 0x0000FFFFUL
#define IO_CMD_MSK 0xF0000000UL
#define IO_CMD_READ 0x10000000UL
#define IO_CMD_WRITE 0x20000000UL
static unsigned char * vmaddr;
inline static int drv_init_twi_vm(void)
{
vmaddr=ioremap(TWI_IOBASE(0), TWI_PAGESIZE);
}
static long drv_ioctl_twi_rw(struct file *file, unsigned int cmd, unsigned long * arg)
{
long retval = 1;
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:
if ( copy_to_user((void *)arg, x, 4) ) {
retval = -EFAULT;
}
break;
case IO_CMD_WRITE:
*(unsigned long *)x=*arg;
break;
default:
retval=-EFAULT;
break;
}
return retval;
}

Related

TM4C123G launchpad: How to modify one pin (e.g. PE1) without knowing its GPIO and its position in the byte

Please allow me to clarify the title:
I'm writing a function to connect 16x2 LCD pins to TM4C123G pins. Users should be able to select any TM4C123G pin they want. As the function writer, I need the user to pass the information of that pin to me so that I can connect the pins.
Now I know there are two ways to modify a pin:
Method 1: Reference the full register and AND/OR with a certain value:
// Want to set PE1 to high
#define GPIO_PORTE_DATA_R (*((volatile unsigned long *)0x400243FC))
GPIO_PORTE_DATA_R |= 0x02;
Method 2: Use bit-specific addressing and reference PE1:
// Want to set PE1 to high
#define PE1 (*((volatile unsigned long *)0x40024008))
PE1 = 0x02;
Now consider the function I need to write: the user has to pass two pieces of information to it -- 1) Which GPIO is used (Port E), and 2) Which bit is used (PE1 the second bit from low end).
My question: Is there a way for the user to just pass me a memory address and I can simply set it to 0x01 for high and 0x00 for low?
This is actually a generic question independent of its platform. The solution is also opinion-based. Anyway, below are my suggestions :)
As the user will only manage the GPIO, s/he doesn't need to be aware of the implementation details that cover the control of the underlying peripheral at a lower level. Hence, you may want to hide the implementation details by just providing some basic functions to the user as below
/* Header File */
int enableGpio(int gpioPort, int index);
int disableGpio(int gpioPort, int index);
You can also hide the macros that you use to handle the logic behind the operation by declaring them inside the source file.
/* Source File */
#define GPIO_PORTE_DATA_R (*((volatile unsigned long *)0x400243FC))
#define PE1 (*((volatile unsigned long *)0x40024008))
int enableGpio(int gpioPort, int index) { /* Implementation*/ }
int disableGpio(int gpioPort, int index) { /* Implementation*/ }
I would also recommend using enumerations for declaring GPIO ports instead of integers. By that, you can prevent making undefined calls to your functions.
That's all for now :)

Enable GPIO on AM335x in C

I have the following function initGPIO. The goal is to enable the GPIOs 0, 1 and 2 on beaglebone using am335x. How can I enable the corresponding GPIO set to the reg_GPIO that are given in the header file? I have the header file GPIO.h which contains the GPIO's numbers, register number and register structure. I have tried to set the GPIO in the function initGPIO. Does it make sense?
gpio.h
#include <stdint.h>
#include <stdbool.h>
#define GPIO0 0 /*!< GPIO 0 number */
#define GPIO1 1 /*!< GPIO 1 number */
#define GPIO2 2 /*!< GPIO 2 number */
#define GPIO3 3 /*!< GPIO 3 number */
// Base address for each gpio hardware module.
#define GPIO0_REG 0x44E07000 //<! gpio0 hardware module address.
#define GPIO1_REG 0x4804C000 //<! gpio1 hardware module address.
#define GPIO2_REG 0x481AC000 //<! gpio2 hardware module address.
#define GPIO3_REG 0x481AE000 //<! gpio3 hardware module address.
// Register Structure
typedef struct
{
volatile uint32_t irqstatus_set_0; // Offset 0x34 - Enables specific interrupt event to trigger.
volatile uint32_t irqstatus_set_1; // Offset 0x38 - Enables specific interrupt event to trigger.
volatile uint32_t irqwaken_0; // Offset 0x44 - Enables wakeup events on an interrupt.
volatile uint32_t irqwaken_1; // Offset 0x48 - Enables wakeup events on an interrupt.
volatile uint32_t ctrl; // Offset 0x130 - Controls clock gating functionality, i.e. enables module.
volatile uint32_t oe; // Offset 0x134 – Output Enable pin (clear bit to 0) output capability.
volatile uint32_t datain; // Offset 0x138 - Registers data read from the GPIO pins.
volatile uint32_t dataout; // Offset 0x13c - Sets value of GPIO output pins.
volatile uint32_t cleardataout; // Offset 0x190 - Clears to 0 bits in dataout
volatile uint32_t setdataout; // Offset 0x194 - Sets to 1 bits in dataout
} GPIO_REGS;
void initGPIO();
gpio.c
/*!
* \brief Initialize GPIOs.
*
* Enables GPIO0, GPIO1, and GPIO2 (GPIO3 not used in IDP. Also configures the output pins
* used in the IDP to control relays and address the ADC's on relays.
*
*****************************************************************************************/
void initGPIO()
{
//enable GPIOs
GPIO_REGS gpio_regs;
//might need to change ctrl
gpio_regs.datain |= (GPIO0 << GPIO0_REG );
gpio_regs.datain |= (GPIO1 << GPIO1_REG );
gpio_regs.datain |= (GPIO2 << GPIO2_REG );
}
By default Beaglebones come with Debian Linux. Unless you've decided to drop that, the kernel has a GPIO driver which assumes control of all GPIO. You should not try to access raw GPIO registers directly, but rather talk to the kernel driver. The simplest way to do that is to install libgpiod (which may be installed by default on recent Beagles) and call its API.
For bare bone platforms you may be used to do something like this to access hardware addresses:
volatile GPIO_REGS* gpio1_regs = (GPIO_REGS*)0x44E07000;
volatile GPIO_REGS* gpio2_regs = (GPIO_REGS*)0x4804C000;
Now your code isn't doing this either, but I'm going to assume that is what you meant.
Except inside an OS the memory offsets in you user-space application don't map one-on-one to hardware address offsets and this simply won't work.
If you want direct register access to things like this, you'll have to do something like Memory Mapping using /dev/mem or if your platform supports it, /dev/gpiomem (I know Raspberry Pi supports this, I'm not sure about the BBB). This will check if you are allowed to access the hardware address range you want, as well as make sure the address range is mapped correctly in your user-space.
For example:
/* open /dev/mem */
int fd = open("/dev/mem", O_RDWR|O_SYNC);
if (fd >= 0) {
/* mmap GPIO */
volatile GPIO_REGS* gpio1_regs = mmap(NULL, getpagesize(), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0x44E07000);
close(fd); /* No need to keep fd open after mmap */
/* Check for MAP_FAILED error */
if (gpio1_regs != (GPIO_REGS*)-1) {
/* Do whatever */
(The last argument to mmap is where you place your hardware address, I'm using 0x44E07000 in this example.)
For further reading, see also:
https://man7.org/linux/man-pages/man2/mmap.2.html
https://pagefault.blog/2017/03/14/access-hardware-from-userspace-with-mmap/

How to printout string in qemu gumstix (connex/PXA255) emulation?

From this site: http://balau82.wordpress.com/2010/02/28/hello-world-for-bare-metal-arm-using-qemu/
I can use C code to print out a string in qemu simulator.
volatile unsigned int * const UART0DR = (unsigned int *)0x101f1000;
void print_uart0(const char *s) {
while(*s != '\0') { /* Loop until end of string */
*UART0DR = (unsigned int)(*s); /* Transmit char */
s++; /* Next char */
}
}
I need to do the same thing in C code with gumstix connex board in qemu (with -M connex option), which uses 0x40100000 or 0x40700000 for the memory mapped uart address, but nothing is shown in the screen.
I tried with some data checking code, but it doesn't still work.
volatile unsigned int * const UART0DR = (unsigned int *)0x40100000;
volatile unsigned int * const UART_LSR = (unsigned int *)0x40100014;
#define LSR_TDRQ (1 << 5) // Transmit Data Request
void print_uart0(const char *s) {
while(*s != '\0') { /* Loop until end of string */
while(( *UART_LSR & LSR_TDRQ ) == 0 );
*UART0DR = (unsigned int)(*s); /* Transmit char */
s++; /* Next char */
}
}
What might be wrong? Is PXA255 uses different way to use uart?
I searched the source code of pxa, and gumstix, maybe gumstix may use different methods to do hart communication in qemu.
From your linked page you may have noticed the following text:
The code that emulates the serial port inside QEMU (here in the source repository) implements a subset of the functionalities of the PL011 Prime Cell UART from ARM
and
The QEMU model of the PL011 serial port ignores the transmit FIFO capabilities; in a real system on chip the “Transmit FIFO Full” flag must be checked in the UARTFR register before writing on the UARTDR register.
You won't be able to use the same code on both QEMU and the PXA255 since the implementation of the UART is different.
To have the UART function correctly on the PXA255 board will require a lot more setup and would typically involve the following:
Configuration of the clock subsystem registers to ensure that the UART peripheral is receiving a clock from the main clock system on the CPU.
Configuration of the UART peripheral registers according to the desired use. You may need to configure registers which control baud rate register, parity control, number of data bits.
Modification of your code which writes to the UART. UART peripherals typically contain a FIFO (sometimes just a single byte) which is used during transmission and reception. To transmit a character you first have to ensure that the previous character has finished transmission before placing the next character for transmit in the output data register.
There is no substitute for reading the UART data sheet in detail and following all the information listed there.
qemu should be relaxed about the accurate emulation of the UART.
Below code works:
startup.s:
.global startup
startup:
ldr sp, startup_stack_top
bl c_startup
b .
startup_stack_top:
.word 0xa4000000
os.c:
void write_uart(char *str)
{
char c;
volatile char *uart = (char *)0x40100000;
while (1) {
c = *str;
if (!c)
break;
*uart = c;
str++;
}
}
void c_startup()
{
write_uart("hello\r\n");
}
linker script os.ld:
ENTRY(startup)
SECTIONS
{
. = 0x0;
.startup : { startup.o(.text) }
.text : { *(.text) }
.rodata : { *(.rodata) }
.data : { *(.data) }
.bss : { *(.bss COMMON) }
}
Build commands:
#pfx=arm-none-eabi-
$(pfx)as -g -march=armv5te startup.s -o startup.o
$(pfx)gcc -g -c -march=armv5te os.c -o os.o
$(pfx)ld -T os.ld os.o startup.o -o os.elf
$(pfx)objcopy -O binary os.elf os.bin
dd of=flash.img bs=128k count=128 if=/dev/zero
dd of=flash.img bs=128k conv=notrunc if=os.bin
Run command:
qemu-system-arm -M connex -m 128M -snapshot -pflash flash.img
Unless there's a bunch of other code you aren't showing, you're missing several important things here:
Clocks and power for the UART module must be enabled before it will function. See section 3 of the PXA255 manual.
The GPIOs used by the UART must be configured before the UART will work correctly (e.g, by setting appropriate pin directions and alternate functions). See section 4.1 of the PXA255 manual.
The UART must be configured (e.g, baud rate, etc.) before you start writing data to it. The PXA255 manual does not explicitly include information on these registers; you will need to cross-reference the 16550 datasheet.
While writing data to the UART, you must ensure the UART is in an appropriate state to receive data (e.g, that the transmit buffer is not full), and wait for it to enter an appropriate state if it is not. Refer to the 16550 datasheet, or to a general tutorial on use of this UART.
The UART implementation in QEMU is intended as a debugging tool, not as a full emulation of the UART in a real device. Just because something works in QEMU doesn't mean it will work on real hardware!
As Austin Phillips pointed out, it requires a lot of setup code to make it work with UART serial communication. What confused me before was that I could make the UART communication work without any setup with qemu-system-arm -M versatilepb -m 128M -nographic -kernel test.bin, but this is versatilepb board emulation. My guess is that for versatilepb board, UART setup is just finished before my binary runs.
Mimicking that, what I did was to use a uboot, which does all of the UART setup, and load my binary image from the uboat, and everything works fine.

AVR atmega128 Serial comminucation

I have a problem in connecting ATmega128 serial port to computer via USB-Serial converter. The USB-Serial converter is verified as I have connected computer to CDMA modem using it. However when I try to connect it with atmega128 I can't figure out the problem. I have connected it to serial LCD (CLCD) and it works fine.Even in simulation with virtual terminal there is no problem. I would like to know if I have missed anything related to serial port. I have already checked baud rate in hardware options and in virtual terminal.
Here is the code.
#include<avr/io.h>
#include<util/delay.h>
char str1[]="AT\r\n";
char str2[]="AT+CMGF=1\r\n";
char str3[]="AT+CMGS=\"01068685673\"\r\n";
char str4[]="hello\x1A\r\n";
int i;
void TX_CHAR(char ch)
{
while(!(UCSR1A&0x20));
UDR1=ch;
}
int main()
{
UBRR1H=0; UBRR1L=103; UCSR1B=0x08;
UCSR1C=0b00000110;
while(1)
{
i=0; while(str1[i])TX_CHAR(str1[i++]);
_delay_ms(200);
i=0; while(str2[i])TX_CHAR(str2[i++]);
_delay_ms(200);
i=0; while(str3[i])TX_CHAR(str3[i++]);
_delay_ms(200);
i=0; while(str4[i])TX_CHAR(str4[i++]);
_delay_ms(3000);
}
}
Things to check:
hardware - wiring
value of M103C fuse (= compatibility mode)
XTAL frequency and prescalers, as the formula for BAUD depends on it: BAUD = Fosc/16(UBRR+1)
USART double speed flag (UCSRA)
frame format
UDREn flag set
You also may get better insight into your code if you use predefined symbolic values, e.g.
/* Enable receiver and transmitter */
UCSRB = (1<<RXEN)|(1<<TXEN);
see the examples in the data sheet (pg 176ff)
On the frame format I understand you are set to async, 8-bit (UCSR1B:2 = 0, UCSR1C:2,1 = 11), parity disabled, 1 stop bit
In void TX_CHAR(char ch) I understand you are checking the status of bit 7 (RXC1) by using mask 0x20H. On the other hand you dont have the RX enabled (RXEN1 meaning UCSR1B:4=0)
I wonder if you shouldn't better check bit 6 (TXC1). Again ... using the symbolic values would help to better understand the code.
Hope this helps ...

Writing Device Drivers for Microcontrollers, where to define IO Port pins?

I always seem to encounter this dilemma when writing low level code for MCU's.
I never know where to declare pin definitions so as to make the code as reusable as possible.
In this case Im writing a driver to interface an 8051 to a MCP4922 12bit serial DAC.
Im unsure how/where I should declare the pin definitions for The CS(chip select) and LDAC(data latch) for the DAC. At the moment there declared in the header file for the driver.
Iv done a lot of research trying to figure out the best approach but havent really found anything.
Im basically want to know what the best practices... if there are some books worth reading or online information, examples etc, any recommendations would be welcome.
Just a snippet of the driver so you get the idea
/**
#brief This function is used to write a 16bit data word to DAC B -12 data bit plus 4 configuration bits
#param dac_data A 12bit word
#param ip_buf_unbuf_select Input Buffered/unbuffered select bit. Buffered = 1; Unbuffered = 0
#param gain_select Output Gain Selection bit. 1 = 1x (VOUT = VREF * D/4096). 0 =2x (VOUT = 2 * VREF * D/4096)
*/
void MCP4922_DAC_B_TX_word(unsigned short int dac_data, bit ip_buf_unbuf_select, bit gain_select)
{
unsigned char low_byte=0, high_byte=0;
CS = 0; /**Select the chip*/
high_byte |= ((0x01 << 7) | (0x01 << 4)); /**Set bit to select DAC A and Set SHDN bit high for DAC A active operation*/
if(ip_buf_unbuf_select) high_byte |= (0x01 << 6);
if(gain_select) high_byte |= (0x01 << 5);
high_byte |= ((dac_data >> 8) & 0x0F);
low_byte |= dac_data;
SPI_master_byte(high_byte);
SPI_master_byte(low_byte);
CS = 1;
LDAC = 0; /**Latch the Data*/
LDAC = 1;
}
This is what I did in a similar case, this example is for writing an I²C driver:
// Structure holding information about an I²C bus
struct IIC_BUS
{
int pin_index_sclk;
int pin_index_sdat;
};
// Initialize I²C bus structure with pin indices
void iic_init_bus( struct IIC_BUS* iic, int idx_sclk, int idx_sdat );
// Write data to an I²C bus, toggling the bits
void iic_write( struct IIC_BUS* iic, uint8_t iicAddress, uint8_t* data, uint8_t length );
All pin indices are declared in an application-dependent header file to allow quick overview, e.g.:
// ...
#define MY_IIC_BUS_SCLK_PIN 12
#define MY_IIC_BUS_SCLK_PIN 13
#define OTHER_PIN 14
// ...
In this example, the I²C bus implementation is completely portable. It only depends on an API that can write to the chip's pins by index.
Edit:
This driver is used like this:
// main.c
#include "iic.h"
#include "pin-declarations.h"
main()
{
struct IIC_BUS mybus;
iic_init_bus( &mybus, MY_IIC_BUS_SCLK_PIN, MY_IIC_BUS_SDAT_PIN );
// ...
iic_write( &mybus, 0x42, some_data_buffer, buffer_length );
}
In one shop I worked at, the pin definitions were put into a processor specific header file. At another shop, I broke the header files into themes associated with modules in the processor, such as DAC, DMA and USB. A master include file for the processor included all of these themed header files. We could model different varieties of the same processor by include different module header files in the processor file.
You could create an implementation header file. This file would define I/O pins in terms of the processor header file. This gives you one layer of abstraction between your application and the hardware. The idea is to loosely couple the application from hardware as much as possible.
If only the driver needs to know about the CS pin, then the declaration should not appear in the header, but within the driver module itself. Code re-use is best served by hiding data at the most restrictive scope possible.
In the event that an external module needs to control CS, add an access function to the device driver module so that you have single point control. This is useful if during debugging you need to know where and when an I/O pin is being asserted; you only have one point to apply instrumentation or breakpoints.
The answer with the run-time configuration will work for a decent CPU like ARM, PowerPC...but the author is running a 8051 here. #define is probably the best way to go. Here's how I would break it down:
blah.h:
#define CSN_LOW() CS = 0
#define CSN_HI() CS = 1
#define LATCH_STROBE() \
do { LDAC = 0; LDAC = 1; } while (0)
blah.c:
#include <blah.h>
void blah_update( U8 high, U8 low )
{
CSN_LOW();
SPI_master_byte(high);
SPI_master_byte(low);
CSN_HI();
LATCH_STROBE();
}
If you need to change the pin definition, or moved to a different CPU, it should be obvious where you need to update. And it's also helps when you have to adjust the timing on the bus (ie. insert a delay here and there) as you don't need to change all over the place. Hope it helps.

Resources