gpio:Initial GPIO driver.(ARM)

Small GPIO driver that exports a few pins using a virtual file
system. Currently the two user leds and the user button are exported.

Change-Id: I001d017ae27cd17b635587873f7da981054da459
This commit is contained in:
Kees Jongenburger 2013-01-28 12:54:44 +01:00
parent 5139afee6b
commit e641d42a37
13 changed files with 714 additions and 2 deletions

View file

@ -90,4 +90,5 @@
./usr/include/evbarm/vmparam.h minix-sys
./usr/include/evbarm/wchar_limits.h minix-sys
./usr/include/i386 minix-sys obsolete
./usr/sbin/gpio minix-sys
./usr/mdec minix-sys

View file

@ -23,7 +23,7 @@ SUBDIR= ahci amddev atl2 at_wini audio dec21140A dp8390 dpeth \
.endif
.if ${MACHINE_ARCH} == "earm"
SUBDIR= mmc log tty
SUBDIR= gpio mmc log tty
.endif
.endif # ${MKIMAGEONLY} != "yes"

16
drivers/gpio/Makefile Normal file
View file

@ -0,0 +1,16 @@
# Makefile for the gpio driver.
PROG= gpio
SRCS= gpio.c gpio_omap.c
DPADD+= ${LIBBLOCKDRIVER} ${LIBSYS}
LDADD+= -lvtreefs -lsys
#
# This is a system driver.
CPPFLAGS+= -D_SYSTEM=1
MAN=
BINDIR?= /usr/sbin
.include <minix.service.mk>

35
drivers/gpio/README.txt Normal file
View file

@ -0,0 +1,35 @@
General Purpose Input and Output
To make MINIX more usable on embedded hardware we need some way to access the
GPIO features of the system on chips. Generally System on Chips (SoC) designs
provide some way configure pads to perform basic Input/Output configuration on
selected ports. These ports are also usually grouped into a bank. The end
result is that you have a functional general input output block where you need
to configure some the following functions.
Functional requirements
We envision that the short term usage of the GPIO library will be booth input
and output handling. Input handling as we want to be able to listen to button
presses and genrate key events and output handling because we want to be able
to control leds.
GPIO required functionality
-Configure pins as input or output.
-Configure the impedance of the pins.
-Get or set the values of the pins(possibly in a single call).
-Configure interrupt levels for input pins.
Configure debouncing of pins.
Additional kernel requirements
-Manage the GPIO resources (who may access what)
-Access the GPIO pins from within driver (for the keyboard)
-Access the GPIO pins from within userland (for toggeling leds)
Usage:
You have to manualy mount the gpio fs using the following command
# mount -t gpio none /gpio

239
drivers/gpio/gpio.c Normal file
View file

@ -0,0 +1,239 @@
/*
* GPIO driver. This driver acts as a file system to allow
* reading and toggling of GPIO's.
*/
/* kernel headers */
#include <minix/driver.h>
#include <minix/drvlib.h>
#include <minix/vtreefs.h>
/* system headers */
#include <sys/stat.h>
#include <sys/queue.h>
/* usr headers */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
/* local headers */
#include "log.h"
#include "mmio.h"
#include "gpio.h"
/* used for logging */
static struct log log = {
.name = "gpio",
.log_level = LEVEL_INFO,
.log_func = default_log
};
#define GPIO_CB_READ 0
#define GPIO_CB_ON 1
#define GPIO_CB_OFF 2
/* The vtreefs library provides callback data when calling
* the read function of inode. gpio_cbdata is used here to
* map between inodes and gpio's. VTreeFS is read-only. to work
* around that issue for a single GPIO we create multiple virtual
* files that can be *read* to read the gpio value and power on
* and off the gpio.
*/
struct gpio_cbdata
{
struct gpio *gpio; /* obtained from the driver */
int type; /* read=0/on=1/off=2 */
TAILQ_ENTRY(gpio_cbdata) next;
};
/* list of inodes used in this driver */
TAILQ_HEAD(gpio_cbdata_head, gpio_cbdata)
gpio_cbdata_list = TAILQ_HEAD_INITIALIZER(gpio_cbdata_list);
static struct gpio_driver drv;
/* Sane file stats for a directory */
static struct inode_stat default_file_stat = {
.mode = S_IFREG | 04,
.uid = 0,
.gid = 0,
.size = 0,
.dev = NO_DEV,
};
int
add_gpio_inode(char *name, int nr, int mode)
{
/* Create 2 files nodes for "name" "nameon" and "nameoff" to read and
* set values as we don't support writing yet */
char tmpname[200];
struct gpio_cbdata *cb;
struct gpio *gpio;
/* claim and configure the gpio */
if (drv.claim("gpiofs", nr, &gpio)) {
log_warn(&log, "Failed to claim GPIO %d\n", nr);
return EIO;
}
assert(gpio != NULL);
if (drv.pin_mode(gpio, mode)) {
log_warn(&log, "Failed to switch GPIO %d to mode %d\n", nr,
mode);
return EIO;
}
/* read value */
cb = malloc(sizeof(struct gpio_cbdata));
if (cb == NULL) {
return ENOMEM;
}
memset(cb, 0, sizeof(*cb));
cb->type = GPIO_CB_READ;
cb->gpio = gpio;
snprintf(tmpname, 200, "%s", name);
add_inode(get_root_inode(), tmpname, NO_INDEX, &default_file_stat, 0,
(cbdata_t) cb);
TAILQ_INSERT_HEAD(&gpio_cbdata_list, cb, next);
if (mode == GPIO_MODE_OUTPUT) {
/* if we configured the GPIO pin as output mode also create
* two additional files to turn on and off the GPIO. */
/* turn on */
cb = malloc(sizeof(struct gpio_cbdata));
if (cb == NULL) {
return ENOMEM;
}
memset(cb, 0, sizeof(*cb));
cb->type = GPIO_CB_ON;
cb->gpio = gpio;
snprintf(tmpname, 200, "%son", name);
add_inode(get_root_inode(), tmpname, NO_INDEX,
&default_file_stat, 0, (cbdata_t) cb);
TAILQ_INSERT_HEAD(&gpio_cbdata_list, cb, next);
/* turn off */
cb = malloc(sizeof(struct gpio_cbdata));
if (cb == NULL) {
return ENOMEM;
}
memset(cb, 0, sizeof(*cb));
cb->type = GPIO_CB_OFF;
cb->gpio = gpio;
snprintf(tmpname, 200, "%soff", name);
add_inode(get_root_inode(), tmpname, NO_INDEX,
&default_file_stat, 0, (cbdata_t) cb);
TAILQ_INSERT_HEAD(&gpio_cbdata_list, cb, next);
}
return OK;
}
static void
init_hook(void)
{
/* This hook will be called once, after VTreeFS has initialized. */
if (omap_gpio_init(&drv)) {
log_warn(&log, "Failed to init gpio driver\n");
}
add_gpio_inode("USR0", 149, GPIO_MODE_OUTPUT);
add_gpio_inode("USR1", 150, GPIO_MODE_OUTPUT);
add_gpio_inode("Button", 4, GPIO_MODE_INPUT);
#if 0
add_gpio_inode("input1", 139, GPIO_MODE_INPUT);
add_gpio_inode("input2", 144, GPIO_MODE_INPUT);
#endif
}
static int
read_hook
(struct inode *inode, off_t offset, char **ptr, size_t * len,
cbdata_t cbdata)
{
/* This hook will be called every time a regular file is read. We use
* it to dyanmically generate the contents of our file. */
static char data[26];
int value;
struct gpio_cbdata *gpio_cbdata = (struct gpio_cbdata *) cbdata;
assert(gpio_cbdata->gpio != NULL);
if (gpio_cbdata->type == GPIO_CB_ON) {
/* turn on */
if (drv.set(gpio_cbdata->gpio, 1)) {
*len = 0;
return EIO;
}
*len = 0;
return OK;
} else if (gpio_cbdata->type == GPIO_CB_OFF) {
/* turn off */
if (drv.set(gpio_cbdata->gpio, 0)) {
*len = 0;
return EIO;
}
*len = 0;
return OK;
}
/* reading */
if (drv.read(gpio_cbdata->gpio, &value)) {
*len = 0;
return EIO;
}
snprintf(data, 26, "%d\n", value);
/* If the offset is beyond the end of the string, return EOF. */
if (offset > strlen(data)) {
*len = 0;
return OK;
}
/* Otherwise, return a pointer into 'data'. If necessary, bound the
* returned length to the length of the rest of the string. Note that
* 'data' has to be static, because it will be used after this
* function returns. */
*ptr = data + offset;
if (*len > strlen(data) - offset)
*len = strlen(data) - offset;
return OK;
}
int
main(int argc, char **argv)
{
struct fs_hooks hooks;
struct inode_stat root_stat;
/* Set and apply the environment */
env_setargs(argc, argv);
/* fill in the hooks */
memset(&hooks, 0, sizeof(hooks));
hooks.init_hook = init_hook;
hooks.read_hook = read_hook;
root_stat.mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
root_stat.uid = 0;
root_stat.gid = 0;
root_stat.size = 0;
root_stat.dev = NO_DEV;
/* limit the number of indexed entries */
start_vtreefs(&hooks, 10, &root_stat, 0);
return EXIT_SUCCESS;
}

30
drivers/gpio/gpio.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef __INCLUDE_GPIO_H__
#define __INCLUDE_GPIO_H__
struct gpio
{
int nr; /* GPIO number */
int mode; /* GPIO mode (input=0/output=1) */
void *data; /* data pointer (not used in the omap driver) */
};
#define GPIO_MODE_INPUT 0
#define GPIO_MODE_OUTPUT 1
struct gpio_driver
{
/* request access to a gpio */
int (*claim) (char *owner, int nr, struct gpio ** gpio);
/* Configure the GPIO for a certain purpose */
int (*pin_mode) (struct gpio * gpio, int mode);
/* Set the value for a GPIO */
int (*set) (struct gpio * gpio, int value);
/* Read the value of the GPIO */
int (*read) (struct gpio * gpio, int *value);
};
int omap_gpio_init(struct gpio_driver *gpio_driver);
#endif /* __INCLUDE_GPIO_H__ */

236
drivers/gpio/gpio_omap.c Normal file
View file

@ -0,0 +1,236 @@
/* kernel headers */
#include <minix/syslib.h>
#include <minix/drvlib.h>
/* system headers */
#include <sys/mman.h>
#include <sys/types.h>
/* usr headers */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
/* local headers */
#include "log.h"
#include "mmio.h"
#include "gpio.h"
/* used for logging */
static struct log log = {
.name = "gpio_omap",
.log_level = LEVEL_INFO,
.log_func = default_log
};
struct omap_gpio_bank
{
const char *name;
uint32_t register_address;
uint32_t base_address;
uint32_t disabled;
};
static struct omap_gpio_bank omap_gpio_banks[] = {
{"GPIO1", 0x48310000, 0, 0},
{"GPIO2", 0x49050000, 0, 0},
{"GPIO3", 0x49052000, 0, 0},
{"GPIO4", 0x49054000, 0, 0},
{"GPIO5", 0x49056000, 0, 0},
{"GPIO6", 0x49058000, 0, 0},
{NULL, 0, 0}
};
#define GPIO_REVISION 0x00
#define GPIO_REVISION_MAJOR(X) ((X & 0xF0) >> 4)
#define GPIO_REVISION_MINOR(X) (X & 0XF)
#define GPIO_DATAOUT 0x3c
#define GPIO_DATAIN 0x38
#define GPIO_OE 0x34 /* Output Data Enable */
#define GPIO_CLEARDATAOUT 0x90
#define GPIO_SETDATAOUT 0x94
#define LED_USR0 (1 << 21)
#define LED_USR1 (1 << 22)
struct omap_gpio_bank *
omap_gpio_bank_get(int gpio_nr)
{
struct omap_gpio_bank *bank;
assert(gpio_nr >= 0 && gpio_nr <= 32 * 6);
bank = &omap_gpio_banks[gpio_nr / 32];
return bank;
}
int
omap_gpio_claim(char *owner, int nr, struct gpio **gpio)
{
log_trace(&log, "%s s claiming %d\n", owner, nr);
if (nr < 0 && nr >= 32 * 6) {
log_warn(&log, "%s is claiming unknown GPIO number %d\n", owner,
nr);
return EINVAL;
}
if ( omap_gpio_bank_get(nr)->disabled == 1) {
log_warn(&log, "%s is claiming GPIO %d from disabled bank\n", owner,
nr);
return EINVAL;
}
struct gpio *tmp = malloc(sizeof(struct gpio));
memset(tmp, 0, sizeof(*tmp));
tmp->nr = nr;
*gpio = tmp;
return OK;
}
int
omap_gpio_pin_mode(struct gpio *gpio, int mode)
{
struct omap_gpio_bank *bank;
assert(gpio != NULL);
gpio->mode = mode;
bank = omap_gpio_bank_get(gpio->nr);
log_debug(&log,
"pin mode bank %s, base address 0x%x -> register address (0x%x,0x%x,0x%x)\n",
bank->name, bank->base_address, bank->register_address, GPIO_OE,
bank->register_address + GPIO_OE);
if (mode == GPIO_MODE_OUTPUT) {
set32(bank->base_address + GPIO_OE, BIT(gpio->nr % 32), 0);
} else {
set32(bank->base_address + GPIO_OE, BIT(gpio->nr % 32),
0xffffffff);
}
return 0;
}
int
omap_gpio_set(struct gpio *gpio, int value)
{
struct omap_gpio_bank *bank;
assert(gpio != NULL);
assert(gpio->nr >= 0 && gpio->nr <= 32 * 6);
bank = omap_gpio_bank_get(gpio->nr);
if (value == 1) {
write32(bank->base_address + GPIO_SETDATAOUT,
BIT(gpio->nr % 32));
} else {
write32(bank->base_address + GPIO_CLEARDATAOUT,
BIT(gpio->nr % 32));
}
return OK;
}
int
omap_gpio_read(struct gpio *gpio, int *value)
{
struct omap_gpio_bank *bank;
assert(gpio != NULL);
assert(gpio->nr >= 0 && gpio->nr <= 32 * 6);
bank = omap_gpio_bank_get(gpio->nr);
log_trace(&log, "mode=%d OU/IN 0x%08x 0x%08x\n", gpio->mode,
read32(bank->base_address + GPIO_DATAIN),
read32(bank->base_address + GPIO_DATAOUT));
if (gpio->mode == GPIO_MODE_INPUT) {
*value =
(read32(bank->base_address +
GPIO_DATAIN) >> (gpio->nr % 32)) & 0x1;
} else {
*value =
(read32(bank->base_address +
GPIO_DATAOUT) >> (gpio->nr % 32)) & 0x1;
}
return OK;
}
int
omap_gpio_init(struct gpio_driver *drv)
{
u32_t revision;
int i;
struct minix_mem_range mr;
struct omap_gpio_bank *bank;
bank = &omap_gpio_banks[0];
for (i = 0; omap_gpio_banks[i].name != NULL; i++) {
bank = &omap_gpio_banks[i];
mr.mr_base = bank->register_address;
mr.mr_limit = bank->register_address + 0x400;
if (sys_privctl(SELF, SYS_PRIV_ADD_MEM, &mr) != 0) {
log_warn(&log,
"Unable to request permission to map memory\n");
return EPERM; /* fixme */
}
/* Set the base address to use */
bank->base_address =
(uint32_t) vm_map_phys(SELF,
(void *) bank->register_address, 0x400);
if (bank->base_address == (uint32_t) MAP_FAILED) {
log_warn(&log, "Unable to map GPIO memory\n");
return EPERM; /* fixme */
}
revision = 0;
revision = read32(bank->base_address + GPIO_REVISION);
/* test if we can access it */
if (GPIO_REVISION_MAJOR(revision) != 2
|| GPIO_REVISION_MINOR(revision) != 5) {
log_warn(&log,
"Failed to read the revision of GPIO bank %s.. disabling\n",
bank->name);
bank->disabled = 1;
}
bank->disabled = 0;
log_trace(&log, "bank %s mapped on 0x%x\n", bank->name,
bank->base_address);
}
/* the following code need to move to a power management/clock service */
#define CM_BASE 0x48004000
#define CM_FCLKEN_WKUP 0xC00
#define CM_ICLKEN_WKUP 0xC10
u32_t base;
mr.mr_base = CM_BASE;
mr.mr_limit = CM_BASE + 0x1000;
if (sys_privctl(SELF, SYS_PRIV_ADD_MEM, &mr) != 0) {
log_warn(&log, "Unable to request permission to map memory\n");
return EPERM;
}
base = (uint32_t) vm_map_phys(SELF, (void *) CM_BASE, 0x1000);
if (base == (uint32_t) MAP_FAILED) {
log_warn(&log, "Unable to map GPIO memory\n");
return EPERM;
}
/* enable the interface and functional clock on GPIO bank 1 */
set32(base + CM_FCLKEN_WKUP, BIT(3), 0xffffffff);
set32(base + CM_ICLKEN_WKUP, BIT(3), 0xffffffff);
/* end power management/clock service stuff */
drv->claim = omap_gpio_claim;
drv->pin_mode = omap_gpio_pin_mode;
drv->set = omap_gpio_set;
drv->read = omap_gpio_read;
return 0;
}

112
drivers/gpio/log.h Normal file
View file

@ -0,0 +1,112 @@
#ifndef __LOG_H__
#define __LOG_H__
/*
* Simple logging functions
*/
/*
* LEVEL_NONE do not log anything.
* LEVEL_WARN Information that needs to be known.
* LEVEL_INFO Basic information like startup messages and occasional events.
* LEVEL_DEBUG debug statements about things happening that are less expected.
* LEVEL_TRACE Way to much information for anybody.
*/
#define LEVEL_NONE 0
#define LEVEL_WARN 1
#define LEVEL_INFO 2
#define LEVEL_DEBUG 3
#define LEVEL_TRACE 4
static const char *level_string[5] = {
"none",
"warn",
"info",
"debug",
"trace"
};
/*
* struct to be initialized by the user of the logging system.
*
* name: The name attribute is used in logging statements do differentiate
* drivers
*
* log_level The level attribute describes the requested logging level. a level
* of 1 will only print warnings while a level of 4 will print all the trace
* information.
*
* log_func The logging function to use to log, log.h provides default_log
* to display information on the kernel output buffer. As a bonus if the
* requested log level is debug or trace the method , file and line number will
* be printed to the steam.
*/
struct log { const char *name; int log_level;
/* the logging function itself */
void (*log_func) (struct log * driver,
int level,
const char *file,
const char *function, int line, const char *fmt, ...);
};
#define __log(driver,log_level, fmt, args...) \
((driver)->log_func(driver,log_level, \
__FILE__, __FUNCTION__, __LINE__,\
fmt, ## args))
/* Log a warning */
#define log_warn(driver, fmt, args...) \
__log(driver, LEVEL_WARN, fmt, ## args)
/* Log an information message */
#define log_info(driver, fmt, args...) \
__log(driver, LEVEL_INFO, fmt, ## args)
/* log debugging output */
#define log_debug(driver, fmt, args...) \
__log(driver, LEVEL_DEBUG, fmt, ## args)
/* log trace output */
#define log_trace(driver, fmt, args...) \
__log(driver, LEVEL_TRACE, fmt, ## args)
#endif /* __LOG_H__ */
static void
default_log(struct log *driver,
int level,
const char *file, const char *function, int line, const char *fmt, ...)
{
va_list args;
if (level > driver->log_level) {
return;
}
/* If the wanted level is debug also display line/method information */
if (driver->log_level >= LEVEL_DEBUG) {
fprintf(stderr, "%s(%s):%s+%d(%s):", driver->name,
level_string[level], file, line, function);
} else {
fprintf(stderr, "%s(%s)", driver->name, level_string[level]);
}
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
}
#ifdef hacks
static void
hexdump(unsigned char *d, unsigned int size)
{
int s;
for (s = 0; s < size; s += 4) {
fprintf(stdout, "0x%04x 0x%02X%02X%02X%02X %c%c%c%c\n", s,
(unsigned int) d[s], (unsigned int) d[s + 1],
(unsigned int) d[s + 2], (unsigned int) d[s + 3], d[s],
d[s + 1], d[s + 2], d[s + 3]);
}
}
#endif

30
drivers/gpio/mmio.h Normal file
View file

@ -0,0 +1,30 @@
#define REG(x)(*((volatile uint32_t *)(x)))
#define BIT(x)(0x1 << x)
/* Write a uint32_t value to a memory address. */
static inline void
write32(uint32_t address, uint32_t value)
{
REG(address) = value;
}
/* Read an uint32_t from a memory address */
static inline uint32_t
read32(uint32_t address)
{
return REG(address);
}
/* Set a 32 bits value depending on a mask */
static inline void
set32(uint32_t address, uint32_t mask, uint32_t value)
{
uint32_t val;
val = read32(address);
/* clear the bits */
val &= ~(mask);
/* apply the value using the mask */
val |= (value & mask);
write32(address, val);
}

View file

@ -57,7 +57,7 @@ PROG_DRIVERS+= acpi
.if ${MACHINE_ARCH} == "earm"
EXTRA+= rc.arm mylogin.sh ttys
PROG_DRIVERS+= mmc tty
PROG_DRIVERS+= mmc tty gpio
PROG_COMMANDS+= cp dd getty ls time sync sleep stty umount
PROG_BIN+= cat rm
PROTO= proto.arm.small

View file

@ -20,9 +20,13 @@ d--755 0 0
sbin d--755 0 0
mmc ---755 0 0 mmc
mfs ---755 0 0 mfs
gpio ---755 0 0 gpio
$
mnt d--755 0 0
$
gpio d--755 0 0
$
usr d--755 0 0
bin d--755 0 0
login ---755 0 0 mylogin.sh

View file

@ -12,5 +12,7 @@ exec </dev/null
#/bin/service -c up /sbin/mmc -dev /dev/c0d0
#/bin/fsck.mfs -p /dev/c0d0p1
#/bin/mount /dev/c0d0p1 /mnt
#gpio
#mount -t gpio none /gpio
exit

View file

@ -552,6 +552,13 @@ service mmc
;
};
service gpio
{
system
PRIVCTL # 4
;
};
service vbox
{
system