tsl2550: driver for the TSL2550 light sensor

Change-Id: I9e1c87132404509ffec8bf22a8c6cc993df1aa73
This commit is contained in:
Thomas Cort 2013-08-19 06:57:27 -04:00
parent 3161c603a6
commit 845aabfe65
8 changed files with 647 additions and 2 deletions

View file

@ -30,7 +30,8 @@ case $#:$1 in
eepromb2s50 eepromb2s51 eepromb2s52 eepromb2s53 \
eepromb2s54 eepromb2s55 eepromb2s56 eepromb2s57 \
eepromb3s50 eepromb3s51 eepromb3s52 eepromb3s53 \
eepromb3s54 eepromb3s55 eepromb3s56 eepromb3s57
eepromb3s54 eepromb3s55 eepromb3s56 eepromb3s57 \
tsl2550b1s39 tsl2550b2s39 tsl2550b3s39
;;
0:|1:-\?)
cat >&2 <<EOF
@ -39,6 +40,7 @@ Where key is one of the following:
ram mem kmem null boot zero # One of these makes all these memory devices
fb0 # Make /dev/fb0
i2c-1 i2c-2 i2c-3 # Make /dev/i2c-[1-3]
tsl2550b{1,3}s39 # TSL2550 Ambient Light Sensors
fd0 fd1 ... # Floppy devices for drive 0, 1, ...
fd0p0 fd1p0 ... # Make floppy partitions fd0p[0-3], fd1p[0-3], ...
c0d0 c0d1 ... # Make disks c0d0, c0d1, ...
@ -307,6 +309,12 @@ do
$e mknod eepromb${b}s5${s} b ${m} 0
$e chmod 600 eepromb${b}s5${s}
;;
tsl2550b[1-3]s39)
b=`expr $dev : 'tsl2550b\\(.*\\)s39'` #bus number
m=`expr ${b} + 46`
$e mknod tsl2550b${b}s39 c ${m} 0
$e chmod 444 tsl2550b${b}s39
;;
*)
echo "$0: don't know about $dev" >&2
ex=1

View file

@ -114,5 +114,6 @@
./usr/sbin/tda19988 minix-sys
./usr/sbin/tps65217 minix-sys
./usr/sbin/tps65950 minix-sys
./usr/sbin/tsl2550 minix-sys
./usr/tests/minix-posix/mod minix-sys
./usr/tests/minix-posix/test63 minix-sys

View file

@ -24,7 +24,7 @@ SUBDIR= ahci amddev atl2 at_wini audio dec21140A dp8390 dpeth \
.if ${MACHINE_ARCH} == "earm"
SUBDIR= cat24c256 fb gpio i2c mmc lan8710a log readclock \
tda19988 tps65217 tps65950 tty random
tda19988 tps65217 tps65950 tsl2550 tty random
.endif
.endif # ${MKIMAGEONLY} != "yes"

14
drivers/tsl2550/Makefile Normal file
View file

@ -0,0 +1,14 @@
# Makefile for the tsl2550 ambient light sensor found on the Weather Cape.
PROG= tsl2550
SRCS= tsl2550.c
DPADD+= ${LIBI2CDRIVER} ${LIBCHARDRIVER} ${LIBSYS} ${LIBTIMERS}
LDADD+= -li2cdriver -lchardriver -lsys -ltimers
MAN=
BINDIR?= /usr/sbin
CPPFLAGS+= -I${NETBSDSRCDIR}
.include <minix.service.mk>

View file

@ -0,0 +1,46 @@
TSL2550 Driver (Ambient Light Sensor)
=====================================
Overview
--------
This is the driver for the ambient light sensor commonly found on the
WeatherCape expansion board for the BeagleBone.
Interface
---------
This driver implements the character device interface. It supports reading
through /dev/tsl2550b{1,3}s39. When read from, it returns a string containing
a data label, a colon, and the sensor value.
Example output of `cat /dev/tsl2550b3s39`:
ILLUMINANCE : 830
Illuminance is expressed in lux. Valid values are 0 to 1846.
Limitations
-----------
Extended mode isn't implemented. Normal mode should be sufficient for most
applications.
Testing the Code
----------------
The driver should have been started by a script in /etc/rc.capes/ If not,
this is how you start up an instance:
cd /dev && MAKEDEV tsl2550b3s39
/bin/service up /usr/sbin/tsl2550 -label tsl2550.3.39 -dev /dev/tsl2550b3s39 \
-args 'bus=3 address=0x39'
Getting the sensor value:
cat /dev/tsl2550b3s39
Killing an instance:
/bin/service down tsl2550.3.39

567
drivers/tsl2550/tsl2550.c Normal file
View file

@ -0,0 +1,567 @@
/* Driver for the TSL2550 Ambient Light Sensor */
#include <minix/ds.h>
#include <minix/drivers.h>
#include <minix/i2c.h>
#include <minix/i2cdriver.h>
#include <minix/chardriver.h>
#include <minix/log.h>
#include <minix/type.h>
#include <minix/spin.h>
/*
* Device Commands
*/
#define CMD_PWR_DOWN 0x00
#define CMD_PWR_UP 0x03
#define CMD_EXT_RANGE 0x1d
#define CMD_NORM_RANGE 0x18
#define CMD_READ_ADC0 0x43
#define CMD_READ_ADC1 0x83
/* When powered up and communicating, the register should have this value */
#define EXPECTED_PWR_UP_TEST_VAL 0x03
/* Maximum Lux value in Standard Mode */
#define MAX_LUX_STD_MODE 1846
/* Bit Masks for ADC Data */
#define ADC_VALID_MASK (1<<7)
#define ADC_CHORD_MASK ((1<<6)|(1<<5)|(1<<4))
#define ADC_STEP_MASK ((1<<3)|(1<<2)|(1<<1)|(1<<0))
#define ADC_VAL_IS_VALID(x) ((x & ADC_VALID_MASK) == ADC_VALID_MASK)
#define ADC_VAL_TO_CHORD_BITS(x) ((x & ADC_CHORD_MASK) >> 4)
#define ADC_VAL_TO_STEP_BITS(x) (x & ADC_STEP_MASK)
/* logging - use with log_warn(), log_info(), log_debug(), log_trace(), etc */
static struct log log = {
.name = "tsl2550",
.log_level = LEVEL_INFO,
.log_func = default_log
};
/* The slave address is hardwired to 0x39 and cannot be changed. */
static i2c_addr_t valid_addrs[2] = {
0x39, 0x00
};
/* Buffer to store output string returned when reading from device file. */
#define BUFFER_LEN 32
char buffer[BUFFER_LEN + 1];
/* the bus that this device is on (counting starting at 1) */
static uint32_t bus;
/* slave address of the device */
static i2c_addr_t address;
/* endpoint for the driver for the bus itself. */
static endpoint_t bus_endpoint;
/* register access functions */
static int reg_read(uint8_t * val);
static int reg_write(uint8_t val);
/* main driver functions */
static int tsl2550_init(void);
static int adc_read(int adc, uint8_t * val);
static int measure_lux(uint32_t * lux);
/* libchardriver callbacks */
static struct device *tsl2550_prepare(dev_t UNUSED(dev));
static int tsl2550_transfer(endpoint_t endpt, int opcode, u64_t position,
iovec_t * iov, unsigned nr_req, endpoint_t UNUSED(user_endpt),
unsigned int UNUSED(flags));
static int tsl2550_other(message * m);
/* SEF functions */
static int sef_cb_lu_state_save(int);
static int lu_state_restore(void);
static int sef_cb_init(int type, sef_init_info_t * info);
static void sef_local_startup(void);
/* Entry points to this driver from libchardriver. */
static struct chardriver tsl2550_tab = {
.cdr_open = do_nop,
.cdr_close = do_nop,
.cdr_ioctl = nop_ioctl,
.cdr_prepare = tsl2550_prepare,
.cdr_transfer = tsl2550_transfer,
.cdr_cleanup = nop_cleanup,
.cdr_alarm = nop_alarm,
.cdr_cancel = nop_cancel,
.cdr_select = nop_select,
.cdr_other = tsl2550_other
};
static struct device tsl2550_device = {
.dv_base = 0,
.dv_size = 0
};
/*
* These two lookup tables and the formulas used in measure_lux() are from
* 'TAOS INTELLIGENT OPTO SENSOR DESIGNER'S NOTEBOOK' Number 9
* 'Simplified TSL2550 Lux Calculation for Embedded and Micro Controllers'.
*
* The tables and formulas eliminate the need for floating point math and
* functions from libm. It also speeds up the calculations.
*/
/* Look up table for converting ADC values to ADC counts */
static const uint32_t adc_counts_lut[128] = {
0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15,
16, 18, 20, 22, 24, 26, 28, 30,
32, 34, 36, 38, 40, 42, 44, 46,
49, 53, 57, 61, 65, 69, 73, 77,
81, 85, 89, 93, 97, 101, 105, 109,
115, 123, 131, 139, 147, 155, 163, 171,
179, 187, 195, 203, 211, 219, 227, 235,
247, 263, 279, 295, 311, 327, 343, 359,
375, 391, 407, 423, 439, 455, 471, 487,
511, 543, 575, 607, 639, 671, 703, 735,
767, 799, 831, 863, 895, 927, 959, 991,
1039, 1103, 1167, 1231, 1295, 1359, 1423, 1487,
1551, 1615, 1679, 1743, 1807, 1871, 1935, 1999,
2095, 2223, 2351, 2479, 2607, 2735, 2863, 2991,
3119, 3247, 3375, 3503, 3631, 3759, 3887, 4015
};
/* Look up table of scaling factors */
static const uint32_t ratio_lut[129] = {
100, 100, 100, 100, 100, 100, 100, 100,
100, 100, 100, 100, 100, 100, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 98, 98, 98, 98, 98,
98, 98, 97, 97, 97, 97, 97, 96,
96, 96, 96, 95, 95, 95, 94, 94,
93, 93, 93, 92, 92, 91, 91, 90,
89, 89, 88, 87, 87, 86, 85, 84,
83, 82, 81, 80, 79, 78, 77, 75,
74, 73, 71, 69, 68, 66, 64, 62,
60, 58, 56, 54, 52, 49, 47, 44,
42, 41, 40, 40, 39, 39, 38, 38,
37, 37, 37, 36, 36, 36, 35, 35,
35, 35, 34, 34, 34, 34, 33, 33,
33, 33, 32, 32, 32, 32, 32, 31,
31, 31, 31, 31, 30, 30, 30, 30,
30
};
static int
measure_lux(uint32_t * lux)
{
int r;
uint8_t adc0_val, adc1_val;
uint32_t adc0_cnt, adc1_cnt;
uint32_t ratio;
r = adc_read(0, &adc0_val);
if (r != OK) {
return -1;
}
r = adc_read(1, &adc1_val);
if (r != OK) {
return -1;
}
/* Look up the adc count, drop the MSB to put in range 0-127. */
adc0_cnt = adc_counts_lut[adc0_val & ~ADC_VALID_MASK];
adc1_cnt = adc_counts_lut[adc1_val & ~ADC_VALID_MASK];
/* default scaling factor */
ratio = 128;
/* calculate ratio - avoid div by 0, ensure cnt1 <= cnt0 */
if ((adc0_cnt != 0) && (adc1_cnt <= adc0_cnt)) {
ratio = (adc1_cnt * 128 / adc0_cnt);
}
/* ensure ratio isn't outside ratio_lut[] */
if (ratio > 128) {
ratio = 128;
}
/* calculate lux */
*lux = ((adc0_cnt - adc1_cnt) * ratio_lut[ratio]) / 256;
/* range check */
if (*lux > MAX_LUX_STD_MODE) {
*lux = MAX_LUX_STD_MODE;
}
return OK;
}
static int
adc_read(int adc, uint8_t * val)
{
int r;
spin_t spin;
if (adc != 0 && adc != 1) {
log_warn(&log, "Invalid ADC number %d, expected 0 or 1.\n",
adc);
return EINVAL;
}
if (val == NULL) {
log_warn(&log, "Read called with a NULL pointer.\n");
return EINVAL;
}
*val = (adc == 0) ? CMD_READ_ADC0 : CMD_READ_ADC1;
/* Select the ADC to read from */
r = reg_write(*val);
if (r != OK) {
log_warn(&log, "Failed to write ADC read command.\n");
return -1;
}
*val = 0;
/* Repeatedly read until the value is valid (i.e. the conversion
* finishes). Depending on the timing, the data sheet says this
* could take up to 400ms.
*/
spin_init(&spin, 400000);
do {
r = reg_read(val);
if (r != OK) {
log_warn(&log, "Failed to read ADC%d value.\n", adc);
return -1;
}
if (ADC_VAL_IS_VALID(*val)) {
return OK;
}
} while (spin_check(&spin));
/* Final read attempt. If the bus was really busy with other requests
* and the timing of things happened in the worst possible case,
* there is a chance that the loop above only did 1 read (slightly
* before 400 ms) and left the loop. To ensure there is a final read
* at or after the 400 ms mark, we try one last time here.
*/
r = reg_read(val);
if (r != OK) {
log_warn(&log, "Failed to read ADC%d value.\n", adc);
return -1;
}
if (ADC_VAL_IS_VALID(*val)) {
return OK;
} else {
log_warn(&log, "ADC%d never returned a valid result.\n", adc);
return EIO;
}
}
static int
reg_read(uint8_t * val)
{
int r;
minix_i2c_ioctl_exec_t ioctl_exec;
if (val == NULL) {
log_warn(&log, "Read called with a NULL pointer.\n");
return EINVAL;
}
memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
/* Read from chip */
ioctl_exec.iie_op = I2C_OP_READ_WITH_STOP;
ioctl_exec.iie_addr = address;
/* No register address to write */
ioctl_exec.iie_cmdlen = 0;
/* Read one byte */
ioctl_exec.iie_buflen = 1;
r = i2cdriver_exec(bus_endpoint, &ioctl_exec);
if (r != OK) {
log_warn(&log, "reg_read() failed (r=%d)\n", r);
return -1;
}
*val = ioctl_exec.iie_buf[0];
log_trace(&log, "Read 0x%x from reg\n", *val);
return OK;
}
static int
reg_write(uint8_t val)
{
int r;
minix_i2c_ioctl_exec_t ioctl_exec;
switch (val) {
case CMD_PWR_DOWN:
case CMD_PWR_UP:
case CMD_EXT_RANGE:
case CMD_NORM_RANGE:
case CMD_READ_ADC0:
case CMD_READ_ADC1:
/* Command is valid */
break;
default:
log_warn(&log,
"reg_write() called with invalid command 0x%x\n", val);
return EINVAL;
}
memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
/* Write to chip */
ioctl_exec.iie_op = I2C_OP_WRITE_WITH_STOP;
ioctl_exec.iie_addr = address;
/* No command bytes for writing to this chip */
ioctl_exec.iie_cmdlen = 0;
/* Set the byte to write */
ioctl_exec.iie_buf[0] = val;
ioctl_exec.iie_buflen = 1;
r = i2cdriver_exec(bus_endpoint, &ioctl_exec);
if (r != OK) {
log_warn(&log, "reg_write() failed (r=%d)\n", r);
return -1;
}
log_trace(&log, "Wrote 0x%x to reg\n", val);
return OK;
}
static int
tsl2550_init(void)
{
int r;
uint8_t val;
/* Power on the device */
r = reg_write(CMD_PWR_UP);
if (r != OK) {
log_warn(&log, "Power-up command failed.\n");
return -1;
}
/* Read power on test value */
r = reg_read(&val);
if (r != OK) {
log_warn(&log, "Failed to read power on test value.\n");
return -1;
}
/* Check power on test value */
if (val != EXPECTED_PWR_UP_TEST_VAL) {
log_warn(&log, "Bad test value. Got 0x%x, expected 0x%x\n",
val, EXPECTED_PWR_UP_TEST_VAL);
return -1;
}
/* Set range to normal */
r = reg_write(CMD_NORM_RANGE);
if (r != OK) {
log_warn(&log, "Normal range command failed.\n");
return -1;
}
return OK;
}
static struct device *
tsl2550_prepare(dev_t UNUSED(dev))
{
return &tsl2550_device;
}
static int
tsl2550_transfer(endpoint_t endpt, int opcode, u64_t position,
iovec_t * iov, unsigned nr_req, endpoint_t UNUSED(user_endpt),
unsigned int UNUSED(flags))
{
int bytes, r;
uint32_t lux;
r = measure_lux(&lux);
if (r != OK) {
return EIO;
}
memset(buffer, '\0', BUFFER_LEN + 1);
snprintf(buffer, BUFFER_LEN, "%-16s: %d\n", "ILLUMINANCE", lux);
bytes = strlen(buffer) - position < iov->iov_size ?
strlen(buffer) - position : iov->iov_size;
if (bytes <= 0) {
return OK;
}
switch (opcode) {
case DEV_GATHER_S:
r = sys_safecopyto(endpt, (cp_grant_id_t) iov->iov_addr, 0,
(vir_bytes) (buffer + position), bytes);
iov->iov_size -= bytes;
break;
default:
return EINVAL;
}
return r;
}
static int
tsl2550_other(message * m)
{
int r;
switch (m->m_type) {
case NOTIFY_MESSAGE:
if (m->m_source == DS_PROC_NR) {
log_debug(&log,
"bus driver changed state, update endpoint\n");
i2cdriver_handle_bus_update(&bus_endpoint, bus,
address);
}
r = OK;
break;
default:
log_warn(&log, "Invalid message type (0x%x)\n", m->m_type);
r = EINVAL;
break;
}
return r;
}
static int
sef_cb_lu_state_save(int UNUSED(state))
{
ds_publish_u32("bus", bus, DSF_OVERWRITE);
ds_publish_u32("address", address, DSF_OVERWRITE);
return OK;
}
static int
lu_state_restore(void)
{
/* Restore the state. */
u32_t value;
ds_retrieve_u32("bus", &value);
ds_delete_u32("bus");
bus = (int) value;
ds_retrieve_u32("address", &value);
ds_delete_u32("address");
address = (int) value;
return OK;
}
static int
sef_cb_init(int type, sef_init_info_t * UNUSED(info))
{
int r;
if (type == SEF_INIT_LU) {
/* Restore the state. */
lu_state_restore();
}
/* look-up the endpoint for the bus driver */
bus_endpoint = i2cdriver_bus_endpoint(bus);
if (bus_endpoint == 0) {
log_warn(&log, "Couldn't find bus driver.\n");
return EXIT_FAILURE;
}
/* claim the device */
r = i2cdriver_reserve_device(bus_endpoint, address);
if (r != OK) {
log_warn(&log, "Couldn't reserve device 0x%x (r=%d)\n",
address, r);
return EXIT_FAILURE;
}
r = tsl2550_init();
if (r != OK) {
log_warn(&log, "Device Init Failed\n");
return EXIT_FAILURE;
}
if (type != SEF_INIT_LU) {
/* sign up for updates about the i2c bus going down/up */
r = i2cdriver_subscribe_bus_updates(bus);
if (r != OK) {
log_warn(&log, "Couldn't subscribe to bus updates\n");
return EXIT_FAILURE;
}
i2cdriver_announce(bus);
log_debug(&log, "announced\n");
}
return OK;
}
static void
sef_local_startup(void)
{
/*
* Register init callbacks. Use the same function for all event types
*/
sef_setcb_init_fresh(sef_cb_init);
sef_setcb_init_lu(sef_cb_init);
sef_setcb_init_restart(sef_cb_init);
/*
* Register live update callbacks.
*/
/* Agree to update immediately when LU is requested in a valid state. */
sef_setcb_lu_prepare(sef_cb_lu_prepare_always_ready);
/* Support live update starting from any standard state. */
sef_setcb_lu_state_isvalid(sef_cb_lu_state_isvalid_standard);
/* Register a custom routine to save the state. */
sef_setcb_lu_state_save(sef_cb_lu_state_save);
/* Let SEF perform startup. */
sef_startup();
}
int
main(int argc, char *argv[])
{
int r;
env_setargs(argc, argv);
r = i2cdriver_env_parse(&bus, &address, valid_addrs);
if (r < 0) {
log_warn(&log, "Expecting -args 'bus=X address=0xYY'\n");
log_warn(&log, "Example -args 'bus=1 address=0x39'\n");
return EXIT_FAILURE;
} else if (r > 0) {
log_warn(&log,
"Invalid slave address for device, expecting 0x39\n");
return EXIT_FAILURE;
}
sef_local_startup();
chardriver_task(&tsl2550_tab, CHARDRIVER_SYNC);
return 0;
}

View file

@ -636,6 +636,11 @@ service tps65950
ipc SYSTEM RS DS i2c readclock.drv;
};
service tsl2550
{
ipc SYSTEM RS DS i2c;
};
service vbox
{
system

View file

@ -66,6 +66,10 @@ enum dev_style { STYLE_NDEV, STYLE_DEV, STYLE_DEVA, STYLE_TTY, STYLE_CTTY,
#define EEPROMB3S55_MAJOR 44 /* 44 = /dev/eepromb3s55 (cat24c256) */
#define EEPROMB3S56_MAJOR 45 /* 45 = /dev/eepromb3s56 (cat24c256) */
#define EEPROMB3S57_MAJOR 46 /* 46 = /dev/eepromb3s57 (cat24c256) */
#define TSL2550B1S39_MAJOR 47 /* 47 = /dev/tsl2550b1s39 (tsl2550) */
#define TSL2550B2S39_MAJOR 48 /* 48 = /dev/tsl2550b2s39 (tsl2550) */
#define TSL2550B3S39_MAJOR 49 /* 49 = /dev/tsl2550b3s39 (tsl2550) */
/* Minor device numbers for memory driver. */
# define RAM_DEV_OLD 0 /* minor device for /dev/ram */