From: Kees Jongenburger Date: Mon, 28 Jan 2013 11:54:44 +0000 (+0100) Subject: gpio:Initial GPIO driver.(ARM) X-Git-Tag: v3.2.1~50 X-Git-Url: http://zhaoyanbai.com/repos/?a=commitdiff_plain;h=refs%2Fchanges%2F67%2F267%2F3;p=minix.git 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 --- diff --git a/distrib/sets/lists/minix/md.evbarm b/distrib/sets/lists/minix/md.evbarm index 85c5eecbe..cebc658ba 100644 --- a/distrib/sets/lists/minix/md.evbarm +++ b/distrib/sets/lists/minix/md.evbarm @@ -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 diff --git a/drivers/Makefile b/drivers/Makefile index da5efe001..37398ac6d 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -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" diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile new file mode 100644 index 000000000..a7f87f5a2 --- /dev/null +++ b/drivers/gpio/Makefile @@ -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 diff --git a/drivers/gpio/README.txt b/drivers/gpio/README.txt new file mode 100644 index 000000000..338dc7c47 --- /dev/null +++ b/drivers/gpio/README.txt @@ -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 chip’s. 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 + diff --git a/drivers/gpio/gpio.c b/drivers/gpio/gpio.c new file mode 100644 index 000000000..724b2664c --- /dev/null +++ b/drivers/gpio/gpio.c @@ -0,0 +1,239 @@ +/* + * GPIO driver. This driver acts as a file system to allow + * reading and toggling of GPIO's. + */ +/* kernel headers */ +#include +#include +#include + +/* system headers */ +#include +#include + +/* usr headers */ +#include +#include +#include +#include +#include +#include +#include + +/* 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; +} diff --git a/drivers/gpio/gpio.h b/drivers/gpio/gpio.h new file mode 100644 index 000000000..5984ca8a3 --- /dev/null +++ b/drivers/gpio/gpio.h @@ -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__ */ diff --git a/drivers/gpio/gpio_omap.c b/drivers/gpio/gpio_omap.c new file mode 100644 index 000000000..e2063a746 --- /dev/null +++ b/drivers/gpio/gpio_omap.c @@ -0,0 +1,236 @@ +/* kernel headers */ +#include +#include + +/* system headers */ +#include +#include + +/* usr headers */ +#include +#include +#include +#include +#include +#include + +/* 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; +} diff --git a/drivers/gpio/log.h b/drivers/gpio/log.h new file mode 100644 index 000000000..b3d0c1768 --- /dev/null +++ b/drivers/gpio/log.h @@ -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 diff --git a/drivers/gpio/mmio.h b/drivers/gpio/mmio.h new file mode 100644 index 000000000..f40262a96 --- /dev/null +++ b/drivers/gpio/mmio.h @@ -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); +} diff --git a/drivers/ramdisk/Makefile b/drivers/ramdisk/Makefile index 700f388ab..5d81eccf5 100644 --- a/drivers/ramdisk/Makefile +++ b/drivers/ramdisk/Makefile @@ -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 diff --git a/drivers/ramdisk/proto.arm.small b/drivers/ramdisk/proto.arm.small index df58d91b8..1fad54302 100644 --- a/drivers/ramdisk/proto.arm.small +++ b/drivers/ramdisk/proto.arm.small @@ -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 diff --git a/drivers/ramdisk/rc.arm b/drivers/ramdisk/rc.arm index 5f8de51e3..3a40acccf 100755 --- a/drivers/ramdisk/rc.arm +++ b/drivers/ramdisk/rc.arm @@ -12,5 +12,7 @@ exec