Files
rtems/cpukit/libi2c/libi2c.c
Till Straumann d2ff24c22d 2007-11-17 Till Straumann <strauman@slac.stanford.edu>
* libi2c/libi2c.c, libi2c/libi2c.h, libi2c/README_libi2c:
	Added checks so that use of 'stdio' is avoided (falling
	back to 'printk') before the system is up.
	Publish driver entry points so that the libi2c driver could
	be added to the applications 'device driver table'.
	This is not fully implemented yet, though, since in addition to
	initializing libi2c the low-level i2c bus drivers as well
	as high-level i2c device drivers need to be registered
	with the library.
	Updated README_libi2c accordingly.
2007-11-21 06:20:49 +00:00

755 lines
18 KiB
C

/* $Id$ */
/* libi2c Implementation */
/*
* Authorship
* ----------
* This software was created by
* Till Straumann <strauman@slac.stanford.edu>, 2005,
* Stanford Linear Accelerator Center, Stanford University.
*
* Acknowledgement of sponsorship
* ------------------------------
* This software was produced by
* the Stanford Linear Accelerator Center, Stanford University,
* under Contract DE-AC03-76SFO0515 with the Department of Energy.
*
* Government disclaimer of liability
* ----------------------------------
* Neither the United States nor the United States Department of Energy,
* nor any of their employees, makes any warranty, express or implied, or
* assumes any legal liability or responsibility for the accuracy,
* completeness, or usefulness of any data, apparatus, product, or process
* disclosed, or represents that its use would not infringe privately owned
* rights.
*
* Stanford disclaimer of liability
* --------------------------------
* Stanford University makes no representations or warranties, express or
* implied, nor assumes any liability for the use of this software.
*
* Stanford disclaimer of copyright
* --------------------------------
* Stanford University, owner of the copyright, hereby disclaims its
* copyright and all other rights in this software. Hence, anyone may
* freely use it for any purpose without restriction.
*
* Maintenance of notices
* ----------------------
* In the interest of clarity regarding the origin and status of this
* SLAC software, this and all the preceding Stanford University notices
* are to remain affixed to any copy or derivative of this software made
* or distributed by the recipient and are to be affixed to any copy of
* software made or distributed by the recipient that contains a copy or
* derivative of this software.
*
* ------------------ SLAC Software Notices, Set 4 OTT.002a, 2004 FEB 03
*/
/*
* adaptations to also handle SPI devices
* by Thomas Doerfler, embedded brains GmbH, Puchheim, Germany
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <stdarg.h>
#include <rtems.h>
#include <rtems/error.h>
#include <rtems/bspIo.h>
#include <rtems/libio.h>
#include <rtems/libi2c.h>
#define DRVNM "libi2c:"
#define MAX_NO_BUSSES 8 /* Also limited by the macro building minor numbers */
#define MAX_NO_DRIVERS 16 /* Number of high level drivers we support */
#define MINOR2ADDR(minor) ((minor)&((1<<10)-1))
#define MINOR2BUS(minor) (((minor)>>10)&7)
#define MINOR2DRV(minor) ((minor)>>13)
/* Check the 'minor' argument, i.e., verify that
* we have a driver connected
*/
#define DECL_CHECKED_BH(b, bh, m, s)\
unsigned b = MINOR2BUS(m); \
rtems_libi2c_bus_t *bh; \
if ( b >= MAX_NO_BUSSES || 0 == (bh=busses[b].bush) ) { \
return s RTEMS_INVALID_NUMBER; \
}
#define DECL_CHECKED_DRV(d, b, m) \
unsigned d = MINOR2DRV(m); \
unsigned b = MINOR2BUS(m); \
if ( b >= MAX_NO_BUSSES || 0 == busses[b].bush \
|| d > MAX_NO_DRIVERS || (d && 0 == drvs[d-1].drv )) {\
return RTEMS_INVALID_NUMBER; \
}
#define DISPATCH(rval, entry, dflt) \
do { \
rtems_driver_address_table *ops = drvs[--drv].drv->ops; \
rval = ops->entry ? ops->entry(major,minor,arg) : dflt; \
} while (0)
rtems_device_major_number rtems_libi2c_major;
static boolean is_initialized = FALSE;
static struct i2cbus
{
rtems_libi2c_bus_t *bush;
volatile rtems_id mutex; /* lock this across start -> stop */
volatile short waiting;
volatile char started;
char *name;
} busses[MAX_NO_BUSSES] = { { 0 } };
static struct
{
rtems_libi2c_drv_t *drv;
} drvs[MAX_NO_DRIVERS] = { { 0} };
static rtems_id libmutex = 0;
#define LOCK(m) assert(!rtems_semaphore_obtain((m), RTEMS_WAIT, RTEMS_NO_TIMEOUT))
#define UNLOCK(m) rtems_semaphore_release((m))
#define LIBLOCK() LOCK(libmutex)
#define LIBUNLOCK() UNLOCK(libmutex)
#define MUTEX_ATTS \
( RTEMS_PRIORITY \
| RTEMS_BINARY_SEMAPHORE \
|RTEMS_INHERIT_PRIORITY \
|RTEMS_NO_PRIORITY_CEILING \
|RTEMS_LOCAL )
/* During early stages of life, stdio is not available */
static void
safe_printf (const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if ( _System_state_Is_up( _System_state_Get() ) )
vfprintf( stderr, fmt, ap );
else
vprintk( fmt, ap );
va_end(ap);
}
static rtems_status_code
mutexCreate (rtems_name nm, rtems_id *pm)
{
rtems_status_code sc;
if (RTEMS_SUCCESSFUL !=
(sc = rtems_semaphore_create (nm, 1, MUTEX_ATTS, 0, pm))) {
if ( _System_state_Is_up( _System_state_Get() ) )
rtems_error (sc, DRVNM " unable to create mutex\n");
else
printk (DRVNM " unable to crate mutex (status code %i)\n", sc);
}
return sc;
}
/* Lock a bus avoiding to have a mutex, which is mostly
* unused, hanging around all the time. We just create
* and delete it on the fly...
*
* ASSUMES: argument checked by caller
*/
static void
lock_bus (int busno)
{
rtems_status_code sc;
struct i2cbus *bus = &busses[busno];
LIBLOCK ();
if (!bus->waiting) {
rtems_id m;
/* nobody is holding the bus mutex - it's not there. Create it on the fly */
sc = mutexCreate (rtems_build_name ('i', '2', 'c', '0' + busno), &m);
if ( RTEMS_SUCCESSFUL != sc ) {
LIBUNLOCK ();
rtems_panic (DRVNM " unable to create bus lock");
} else {
bus->mutex = m;
}
}
/* count number of people waiting on this bus; only the last one deletes the mutex */
bus->waiting++;
LIBUNLOCK ();
/* Now lock this bus */
LOCK (bus->mutex);
}
static void
unlock_bus (int busno)
{
struct i2cbus *bus = &busses[busno];
LIBLOCK ();
UNLOCK (bus->mutex);
if (!--bus->waiting) {
rtems_semaphore_delete (bus->mutex);
}
LIBUNLOCK ();
}
/* Note that 'arg' is always passed in as NULL */
rtems_status_code
rtems_i2c_init (rtems_device_major_number major, rtems_device_minor_number minor,
void *arg)
{
rtems_status_code rval;
/* No busses or drivers can be registered at this point;
* avoid the macro aborting with an error
DECL_CHECKED_DRV (drv, busno, minor)
*/
rval = mutexCreate (rtems_build_name ('l', 'I', '2', 'C'), &libmutex);
if ( RTEMS_SUCCESSFUL == rval ) {
is_initialized = TRUE;
rtems_libi2c_major = major;
} else {
libmutex = 0;
}
return rval;
}
rtems_status_code
rtems_i2c_open (rtems_device_major_number major, rtems_device_minor_number minor,
void *arg)
{
rtems_status_code rval;
DECL_CHECKED_DRV (drv, busno, minor)
if (0 == drv) {
rval = RTEMS_SUCCESSFUL;
} else {
DISPATCH (rval, open_entry, RTEMS_SUCCESSFUL);
}
return rval;
}
rtems_status_code
rtems_i2c_close (rtems_device_major_number major, rtems_device_minor_number minor,
void *arg)
{
rtems_status_code rval;
DECL_CHECKED_DRV (drv, busno, minor)
if (0 == drv) {
rval = RTEMS_SUCCESSFUL;
} else {
DISPATCH (rval, close_entry, RTEMS_SUCCESSFUL);
}
return rval;
}
rtems_status_code
rtems_i2c_read (rtems_device_major_number major, rtems_device_minor_number minor,
void *arg)
{
int rval; /* int so we can check for negative value */
rtems_libio_rw_args_t *rwargs = arg;
DECL_CHECKED_DRV (drv, busno, minor)
if (0 == rwargs->count) {
rwargs->bytes_moved = 0;
return RTEMS_SUCCESSFUL;
}
if (0 == drv) {
rval =
rtems_libi2c_start_read_bytes (minor, (unsigned char *) rwargs->buffer,
rwargs->count);
if (rval >= 0) {
rwargs->bytes_moved = rval;
rtems_libi2c_send_stop (minor);
rval = RTEMS_SUCCESSFUL;
} else {
rval = -rval;
}
} else {
DISPATCH (rval, read_entry, RTEMS_NOT_IMPLEMENTED);
}
return rval;
}
rtems_status_code
rtems_i2c_write (rtems_device_major_number major, rtems_device_minor_number minor,
void *arg)
{
int rval; /* int so we can check for negative value */
rtems_libio_rw_args_t *rwargs = arg;
DECL_CHECKED_DRV (drv, busno, minor)
if (0 == rwargs->count) {
rwargs->bytes_moved = 0;
return RTEMS_SUCCESSFUL;
}
if (0 == drv) {
rval =
rtems_libi2c_start_write_bytes (minor, (unsigned char *) rwargs->buffer,
rwargs->count);
if (rval >= 0) {
rwargs->bytes_moved = rval;
rtems_libi2c_send_stop (minor);
rval = RTEMS_SUCCESSFUL;
} else {
rval = -rval;
}
} else {
DISPATCH (rval, write_entry, RTEMS_NOT_IMPLEMENTED);
}
return rval;
}
rtems_status_code
rtems_i2c_ioctl (rtems_device_major_number major, rtems_device_minor_number minor,
void *arg)
{
rtems_status_code rval;
DECL_CHECKED_DRV (drv, busno, minor)
if (0 == drv) {
rval = RTEMS_NOT_IMPLEMENTED;
} else {
DISPATCH (rval, control_entry, RTEMS_NOT_IMPLEMENTED);
}
return rval;
}
/* Our ops just dispatch to the registered drivers */
rtems_driver_address_table rtems_libi2c_io_ops = {
initialization_entry: rtems_i2c_init,
open_entry: rtems_i2c_open,
close_entry: rtems_i2c_close,
read_entry: rtems_i2c_read,
write_entry: rtems_i2c_write,
control_entry: rtems_i2c_ioctl,
};
int
rtems_libi2c_initialize ()
{
rtems_status_code sc;
if (is_initialized) {
/*
* already called before? then skip this step
*/
return 0;
}
/* rtems_io_register_driver does NOT currently check nor report back
* the return code of the 'init' operation, so we cannot
* rely on return code since it may seem OK even if the driver 'init;
* op failed.
* Let 'init' handle 'is_initialized'...
*/
sc = rtems_io_register_driver (0, &rtems_libi2c_io_ops, &rtems_libi2c_major);
if (RTEMS_SUCCESSFUL != sc) {
safe_printf(
DRVNM " Claiming driver slot failed (rtems status code %i)\n",
sc);
if ( libmutex )
rtems_semaphore_delete (libmutex);
libmutex = 0;
is_initialized = FALSE;
return -1;
}
return 0;
}
int
rtems_libi2c_register_bus (char *name, rtems_libi2c_bus_t * bus)
{
int i;
rtems_status_code err;
char *nmcpy = malloc (name ? strlen (name) + 1 : 20);
char tmp, *chpt;
struct stat sbuf;
strcpy (nmcpy, name ? name : "/dev/i2c");
/* check */
if ('/' != *nmcpy) {
safe_printf ( DRVNM "Bad name; must be an absolute path starting with '/'\n");
return -RTEMS_INVALID_NAME;
}
/* file must not exist */
if (!stat (nmcpy, &sbuf)) {
safe_printf ( DRVNM "Bad name; file exists already\n");
return -RTEMS_INVALID_NAME;
}
/* we already verified that there is at least one '/' */
chpt = strrchr (nmcpy, '/') + 1;
tmp = *chpt;
*chpt = 0;
i = stat (nmcpy, &sbuf);
*chpt = tmp;
if (i) {
safe_printf ( DRVNM "Get %s status failed: %s\n",
nmcpy, strerror(errno));
return -RTEMS_INVALID_NAME;
}
/* should be a directory since name terminates in '/' */
if (!libmutex) {
safe_printf ( DRVNM " library not initialized\n");
return -RTEMS_NOT_DEFINED;
}
if (bus->size < sizeof (*bus)) {
safe_printf ( DRVNM " bus-ops size too small -- misconfiguration?\n");
return -RTEMS_NOT_CONFIGURED;
}
LIBLOCK ();
for (i = 0; i < MAX_NO_BUSSES; i++) {
if (!busses[i].bush) {
/* found a free slot */
busses[i].bush = bus;
busses[i].mutex = 0;
busses[i].waiting = 0;
busses[i].started = 0;
if (!name)
sprintf (nmcpy + strlen (nmcpy), "%i", i);
if ((err = busses[i].bush->ops->init (busses[i].bush))) {
/* initialization failed */
i = -err;
} else {
busses[i].name = nmcpy;;
nmcpy = 0;
}
break;
}
}
LIBUNLOCK ();
if (i >= MAX_NO_BUSSES) {
i = -RTEMS_TOO_MANY;
}
free (nmcpy);
return i;
}
static int
not_started (int busno)
{
int rval;
lock_bus (busno);
rval = !busses[busno].started;
unlock_bus (busno);
return rval;
}
rtems_status_code
rtems_libi2c_send_start (rtems_device_minor_number minor)
{
int rval;
DECL_CHECKED_BH (busno, bush, minor, +)
lock_bus (busno);
rval = bush->ops->send_start (bush);
/* if this failed or is not the first start, unlock */
if (rval || busses[busno].started) {
/* HMM - what to do if the 1st start failed ?
* try to reset...
*/
if (!busses[busno].started) {
/* just in case the bus driver fiddles with errno */
int errno_saved = errno;
bush->ops->init (bush);
errno = errno_saved;
} else if (rval) {
/* failed restart */
rtems_libi2c_send_stop (minor);
}
unlock_bus (busno);
} else {
/* successful 1st start; keep bus locked until stop is sent */
busses[busno].started = 1;
}
return rval;
}
rtems_status_code
rtems_libi2c_send_stop (rtems_device_minor_number minor)
{
rtems_status_code rval;
DECL_CHECKED_BH (busno, bush, minor, +)
if (not_started (busno))
return RTEMS_NOT_OWNER_OF_RESOURCE;
rval = bush->ops->send_stop (bush);
busses[busno].started = 0;
unlock_bus (busno);
return rval;
}
rtems_status_code
rtems_libi2c_send_addr (rtems_device_minor_number minor, int rw)
{
rtems_status_code sc;
DECL_CHECKED_BH (busno, bush, minor, +)
if (not_started (busno))
return RTEMS_NOT_OWNER_OF_RESOURCE;
sc = bush->ops->send_addr (bush, MINOR2ADDR (minor), rw);
if (RTEMS_SUCCESSFUL != sc)
rtems_libi2c_send_stop (minor);
return sc;
}
int
rtems_libi2c_read_bytes (rtems_device_minor_number minor,
unsigned char *bytes,
int nbytes)
{
int sc;
DECL_CHECKED_BH (busno, bush, minor, -)
if (not_started (busno))
return -RTEMS_NOT_OWNER_OF_RESOURCE;
sc = bush->ops->read_bytes (bush, bytes, nbytes);
if (sc < 0)
rtems_libi2c_send_stop (minor);
return sc;
}
int
rtems_libi2c_write_bytes (rtems_device_minor_number minor,
unsigned char *bytes,
int nbytes)
{
int sc;
DECL_CHECKED_BH (busno, bush, minor, -)
if (not_started (busno))
return -RTEMS_NOT_OWNER_OF_RESOURCE;
sc = bush->ops->write_bytes (bush, bytes, nbytes);
if (sc < 0)
rtems_libi2c_send_stop (minor);
return sc;
}
int
rtems_libi2c_ioctl (rtems_device_minor_number minor,
int cmd,
...)
{
va_list ap;
int sc = 0;
void *args;
DECL_CHECKED_BH (busno, bush, minor, -)
if (not_started (busno))
return -RTEMS_NOT_OWNER_OF_RESOURCE;
va_start(ap, cmd);
args = va_arg(ap, void *);
switch(cmd) {
/*
* add ioctls defined for this level here:
*/
case RTEMS_LIBI2C_IOCTL_START_TFM_READ_WRITE:
/*
* address device, then set transfer mode and perform read_write transfer
*/
/*
* perform start/address
*/
if (sc == 0) {
sc = rtems_libi2c_send_start (minor);
}
/*
* set tfr mode
*/
if (sc == 0) {
sc = bush->ops->ioctl
(bush,
RTEMS_LIBI2C_IOCTL_SET_TFRMODE,
&((rtems_libi2c_tfm_read_write_t *)args)->tfr_mode);
}
/*
* perform read_write
*/
if (sc == 0) {
sc = bush->ops->ioctl
(bush,
RTEMS_LIBI2C_IOCTL_READ_WRITE,
&((rtems_libi2c_tfm_read_write_t *)args)->rd_wr);
}
break;
default:
sc = bush->ops->ioctl (bush, cmd, args);
break;
}
if (sc < 0)
rtems_libi2c_send_stop (minor);
return sc;
}
static int
do_s_rw (rtems_device_minor_number minor,
unsigned char *bytes,
int nbytes,
int rw)
{
rtems_status_code sc;
rtems_libi2c_bus_t *bush;
if ((sc = rtems_libi2c_send_start (minor)))
return -sc;
/* at this point, we hold the bus and are sure the minor number is valid */
bush = busses[MINOR2BUS (minor)].bush;
if ((sc = bush->ops->send_addr (bush, MINOR2ADDR (minor), rw))) {
rtems_libi2c_send_stop (minor);
return -sc;
}
if (rw)
sc = bush->ops->read_bytes (bush, bytes, nbytes);
else
sc = bush->ops->write_bytes (bush, bytes, nbytes);
if (sc < 0) {
rtems_libi2c_send_stop (minor);
}
return sc;
}
int
rtems_libi2c_start_read_bytes (rtems_device_minor_number minor,
unsigned char *bytes,
int nbytes)
{
return do_s_rw (minor, bytes, nbytes, 1);
}
int
rtems_libi2c_start_write_bytes (rtems_device_minor_number minor,
unsigned char *bytes,
int nbytes)
{
return do_s_rw (minor, bytes, nbytes, 0);
}
int
rtems_libi2c_register_drv (char *name, rtems_libi2c_drv_t * drvtbl,
unsigned busno, unsigned i2caddr)
{
int i;
rtems_status_code err;
rtems_device_minor_number minor;
if (!libmutex) {
safe_printf ( DRVNM " library not initialized\n");
return -RTEMS_NOT_DEFINED;
}
if (name && strchr (name, '/')) {
safe_printf ( DRVNM "Invalid name: '%s' -- must not contain '/'\n", name);
return -RTEMS_INVALID_NAME;
}
if (busno >= MAX_NO_BUSSES || !busses[busno].bush || i2caddr >= 1 << 10) {
errno = EINVAL;
return -RTEMS_INVALID_NUMBER;
}
if (drvtbl->size < sizeof (*drvtbl)) {
safe_printf ( DRVNM " drv-ops size too small -- misconfiguration?\n");
return -RTEMS_NOT_CONFIGURED;
}
/* allocate slot */
LIBLOCK ();
for (i = 0; i < MAX_NO_DRIVERS; i++) {
/* driver # 0 is special, it is the built-in raw driver */
if (!drvs[i].drv) {
char *str;
dev_t dev;
uint32_t mode;
/* found a free slot; encode slot + 1 ! */
minor = ((i + 1) << 13) | RTEMS_LIBI2C_MAKE_MINOR (busno, i2caddr);
if (name) {
str = malloc (strlen (busses[busno].name) + strlen (name) + 2);
sprintf (str, "%s.%s", busses[busno].name, name);
dev = rtems_filesystem_make_dev_t (rtems_libi2c_major, minor);
mode = 0111 | S_IFCHR;
if (drvtbl->ops->read_entry)
mode |= 0444;
if (drvtbl->ops->write_entry)
mode |= 0222;
/* note that 'umask' is applied to 'mode' */
if (mknod (str, mode, dev)) {
safe_printf( DRVNM
"Creating device node failed: %s; you can try to do it manually...\n",
strerror (errno));
}
free (str);
}
drvs[i].drv = drvtbl;
if (drvtbl->ops->initialization_entry)
err =
drvs[i].drv->ops->initialization_entry (rtems_libi2c_major, minor,
0);
else
err = RTEMS_SUCCESSFUL;
LIBUNLOCK ();
return err ? -err : minor;
}
}
LIBUNLOCK ();
return -RTEMS_TOO_MANY;
}