597151d963
The new API now covers the entire character driver protocol, while hiding all the message details. It should therefore be used by all new character drivers. All existing drivers that already made use of libchardriver have been changed to use the new API. As one of the most important API changes, support for scatter and gather transfers has been removed, as several key drivers already did not support this, and it could be supported at the safecopy level instead (for a future readv/writev). Additional changes include: - respond to block device open requests to avoid hanging VFS threads; - add support for sef_cancel. Change-Id: I1bab6c1cb66916c71b87aeb1db54a9bdf171fe6b
446 lines
11 KiB
C
446 lines
11 KiB
C
/* 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;
|
|
|
|
/* 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 ssize_t tsl2550_read(devminor_t minor, u64_t position, endpoint_t endpt,
|
|
cp_grant_id_t grant, size_t size, int flags, cdev_id_t id);
|
|
static void tsl2550_other(message * m, int ipc_status);
|
|
|
|
/* 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_read = tsl2550_read,
|
|
.cdr_other = tsl2550_other
|
|
};
|
|
|
|
/*
|
|
* 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 = i2creg_raw_write8(bus_endpoint, address, *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 = i2creg_raw_read8(bus_endpoint, address, 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 = i2creg_raw_read8(bus_endpoint, address, 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
|
|
tsl2550_init(void)
|
|
{
|
|
int r;
|
|
uint8_t val;
|
|
|
|
/* Power on the device */
|
|
r = i2creg_raw_write8(bus_endpoint, address, CMD_PWR_UP);
|
|
if (r != OK) {
|
|
log_warn(&log, "Power-up command failed.\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Read power on test value */
|
|
r = i2creg_raw_read8(bus_endpoint, address, &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 = i2creg_raw_write8(bus_endpoint, address, CMD_NORM_RANGE);
|
|
if (r != OK) {
|
|
log_warn(&log, "Normal range command failed.\n");
|
|
return -1;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
static ssize_t
|
|
tsl2550_read(devminor_t UNUSED(minor), u64_t position, endpoint_t endpt,
|
|
cp_grant_id_t grant, size_t size, int UNUSED(flags), cdev_id_t UNUSED(id))
|
|
{
|
|
u64_t dev_size;
|
|
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);
|
|
|
|
dev_size = (u64_t)strlen(buffer);
|
|
if (position >= dev_size) return 0;
|
|
if (position + size > dev_size)
|
|
size = (size_t)(dev_size - position);
|
|
|
|
r = sys_safecopyto(endpt, grant, 0,
|
|
(vir_bytes)(buffer + (size_t)position), size);
|
|
|
|
return (r != OK) ? r : size;
|
|
}
|
|
|
|
static void
|
|
tsl2550_other(message * m, int ipc_status)
|
|
{
|
|
int r;
|
|
|
|
if (is_ipc_notify(ipc_status)) {
|
|
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);
|
|
}
|
|
return;
|
|
}
|
|
|
|
log_warn(&log, "Invalid message type (0x%x)\n", m->m_type);
|
|
}
|
|
|
|
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);
|
|
|
|
return 0;
|
|
}
|