tsl2550: driver for the TSL2550 light sensor
Change-Id: I9e1c87132404509ffec8bf22a8c6cc993df1aa73
This commit is contained in:
		
							parent
							
								
									3161c603a6
								
							
						
					
					
						commit
						845aabfe65
					
				@ -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
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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
									
								
							
							
						
						
									
										14
									
								
								drivers/tsl2550/Makefile
									
									
									
									
									
										Normal 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>
 | 
			
		||||
							
								
								
									
										46
									
								
								drivers/tsl2550/README.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								drivers/tsl2550/README.txt
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										567
									
								
								drivers/tsl2550/tsl2550.c
									
									
									
									
									
										Normal 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;
 | 
			
		||||
}
 | 
			
		||||
@ -636,6 +636,11 @@ service tps65950
 | 
			
		||||
	ipc SYSTEM RS DS i2c readclock.drv;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
service tsl2550
 | 
			
		||||
{
 | 
			
		||||
	ipc SYSTEM RS DS i2c;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
service vbox
 | 
			
		||||
{
 | 
			
		||||
	system
 | 
			
		||||
 | 
			
		||||
@ -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 */
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user