* [PATCHv2 0/5] BH1770GLC, SFH7770 and APDS990x als / proximity sensor drivers
@ 2010-10-08 13:41 Samu Onkalo
2010-10-08 13:42 ` [PATCHv2 1/5] misc: Driver for bh1770glc / sfh7770 ALS and proximity sensor Samu Onkalo
` (4 more replies)
0 siblings, 5 replies; 16+ messages in thread
From: Samu Onkalo @ 2010-10-08 13:41 UTC (permalink / raw)
To: linux-i2c, linux-kernel, jic23, alan
Patch set contains two drivers. One for BH1770GLC / SFH7770 chips and
one for APDS990X chip.
Both drivers have similar sysfs based interface. Both supports
pm_runtime and regulator frame work. There is short documentation
for both drivers.
Unfortunately I can't promise data sheets to public access.
Changes in patch set version 2:
- sysfs naming convention changed
- sysfs sensor_range entries added
- "xxx yyy" sysfs entries changes to two separate entries
- 10x sysfs entry replaced with %d.%d
- const added to tables
- meaning of rate entries changed to Hertz
- various mutex corrections and comments
- cleaned up bracket
- rate selection selects closest one instead strict match
- Documentation updated to match changes
- Some bug fixes which I noticed in tests
- Some code cleanup
- EAGAIN is not returned in sysfs read. Instead wait for result or
timeout with -EIO (something is broken if timeout happens)
Tested on top of 2.6.36-rc5
Samu Onkalo (5):
misc: Driver for bh1770glc / sfh7770 ALS and proximity sensor
misc: update bhsfh driver to Kconfig and Makefile
misc: Driver for APDS990X ALS and proximity sensors
misc: update apds990x driver to Kconfig and Makefile
Documentation: Short descriptions for bhsfh and apds990x drivers
Documentation/misc-devices/apds990x.txt | 152 ++++
Documentation/misc-devices/bhsfh.txt | 148 ++++
drivers/misc/Kconfig | 21 +
drivers/misc/Makefile | 2 +
drivers/misc/apds990x.c | 1303 ++++++++++++++++++++++++++++
drivers/misc/bhsfh.c | 1443 +++++++++++++++++++++++++++++++
include/linux/i2c/apds990x.h | 58 ++
include/linux/i2c/bhsfh.h | 42 +
8 files changed, 3169 insertions(+), 0 deletions(-)
create mode 100644 Documentation/misc-devices/apds990x.txt
create mode 100644 Documentation/misc-devices/bhsfh.txt
create mode 100644 drivers/misc/apds990x.c
create mode 100644 drivers/misc/bhsfh.c
create mode 100644 include/linux/i2c/apds990x.h
create mode 100644 include/linux/i2c/bhsfh.h
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCHv2 1/5] misc: Driver for bh1770glc / sfh7770 ALS and proximity sensor 2010-10-08 13:41 [PATCHv2 0/5] BH1770GLC, SFH7770 and APDS990x als / proximity sensor drivers Samu Onkalo @ 2010-10-08 13:42 ` Samu Onkalo 2010-10-08 14:35 ` Jonathan Cameron 2010-10-08 13:42 ` [PATCHv2 2/5] misc: update bhsfh driver to Kconfig and Makefile Samu Onkalo ` (3 subsequent siblings) 4 siblings, 1 reply; 16+ messages in thread From: Samu Onkalo @ 2010-10-08 13:42 UTC (permalink / raw) To: linux-i2c, linux-kernel, jic23, alan This is a driver for ROHM BH1770GLC and OSRAM SFH7770 combined ALS and proximity sensor. Interface is sysfs based. The driver uses interrupts to provide new data. The driver supports pm_runtime and regulator frameworks. See Documentation/misc-devices/bhsfh.txt for details Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com> --- drivers/misc/bhsfh.c | 1443 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/i2c/bhsfh.h | 42 ++ 2 files changed, 1485 insertions(+), 0 deletions(-) create mode 100644 drivers/misc/bhsfh.c create mode 100644 include/linux/i2c/bhsfh.h diff --git a/drivers/misc/bhsfh.c b/drivers/misc/bhsfh.c new file mode 100644 index 0000000..e82fadb --- /dev/null +++ b/drivers/misc/bhsfh.c @@ -0,0 +1,1443 @@ +/* + * This file is part of the ROHM BH1770GLC / OSRAM SFH7770 sensor driver. + * Chip is combined proximity and ambient light sensor. + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * Contact: Samu Onkalo <samu.p.onkalo@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/i2c/bhsfh.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_runtime.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/slab.h> + +#define BHSFH_ALS_CONTROL 0x80 /* ALS operation mode control */ +#define BHSFH_PS_CONTROL 0x81 /* PS operation mode control */ +#define BHSFH_I_LED 0x82 /* active LED and LED1, LED2 current */ +#define BHSFH_I_LED3 0x83 /* LED3 current setting */ +#define BHSFH_ALS_PS_MEAS 0x84 /* Forced mode trigger */ +#define BHSFH_PS_MEAS_RATE 0x85 /* PS meas. rate at stand alone mode */ +#define BHSFH_ALS_MEAS_RATE 0x86 /* ALS meas. rate at stand alone mode */ +#define BHSFH_PART_ID 0x8a /* Part number and revision ID */ +#define BHSFH_MANUFACT_ID 0x8b /* Manufacturerer ID */ +#define BHSFH_ALS_DATA_0 0x8c /* ALS DATA low byte */ +#define BHSFH_ALS_DATA_1 0x8d /* ALS DATA high byte */ +#define BHSFH_ALS_PS_STATUS 0x8e /* Measurement data and int status */ +#define BHSFH_PS_DATA_LED1 0x8f /* PS data from LED1 */ +#define BHSFH_PS_DATA_LED2 0x90 /* PS data from LED2 */ +#define BHSFH_PS_DATA_LED3 0x91 /* PS data from LED3 */ +#define BHSFH_INTERRUPT 0x92 /* Interrupt setting */ +#define BHSFH_PS_TH_LED1 0x93 /* PS interrupt threshold for LED1 */ +#define BHSFH_PS_TH_LED2 0x94 /* PS interrupt threshold for LED2 */ +#define BHSFH_PS_TH_LED3 0x95 /* PS interrupt threshold for LED3 */ +#define BHSFH_ALS_TH_UP_0 0x96 /* ALS upper threshold low byte */ +#define BHSFH_ALS_TH_UP_1 0x97 /* ALS upper threshold high byte */ +#define BHSFH_ALS_TH_LOW_0 0x98 /* ALS lower threshold low byte */ +#define BHSFH_ALS_TH_LOW_1 0x99 /* ALS lower threshold high byte */ + +/* MANUFACT_ID */ +#define BHSFH_MANUFACT_ROHM 0x01 +#define BHSFH_MANUFACT_OSRAM 0x03 + +/* PART_ID */ +#define BHSFH_PART 0x90 +#define BHSFH_PART_MASK 0xf0 +#define BHSFH_REV_MASK 0x0f +#define BHSFH_REV_SHIFT 0 +#define BHSFH_REV_0 0x00 +#define BHSFH_REV_1 0x01 + +/* ALS_MEAS_RATE */ +#define BHSFH_ALS_MAX_RATE 9 + +/* PS_MEAS_RATE */ +#define BHSFH_PS_MAX_RATE 4 + +/* Operating modes for both */ +#define BHSFH_STANDBY 0x00 +#define BHSFH_FORCED 0x02 +#define BHSFH_STANDALONE 0x03 +#define BHSFH_SWRESET (0x01 << 2) + +#define BHSFH_PS_TRIG_MEAS (1 << 0) +#define BHSFH_ALS_TRIG_MEAS (1 << 1) + +/* Interrupt control */ +#define BHSFH_INT_OUTPUT_MODE (1 << 3) /* 0 = latched */ +#define BHSFH_INT_POLARITY (1 << 2) /* 1 = active high */ +#define BHSFH_INT_ALS_ENA (1 << 1) +#define BHSFH_INT_PS_ENA (1 << 0) + +/* Interrupt status */ +#define BHSFH_INT_LED1_DATA (1 << 0) +#define BHSFH_INT_LED1_INT (1 << 1) +#define BHSFH_INT_LED2_DATA (1 << 2) +#define BHSFH_INT_LED2_INT (1 << 3) +#define BHSFH_INT_LED3_DATA (1 << 4) +#define BHSFH_INT_LED3_INT (1 << 5) +#define BHSFH_INT_LEDS_INT ((1 << 1) | (1 << 3) | (1 << 5)) +#define BHSFH_INT_ALS_DATA (1 << 6) +#define BHSFH_INT_ALS_INT (1 << 7) + +/* Led channels */ +#define BHSFH_LED1 0x00 + +#define BHSFH_DISABLE 0 +#define BHSFH_ENABLE 1 +#define BHSFH_PROX_CHANNELS 1 + +#define BHSFH_LUX_DEFAULT_RATE 1 /* Index to lux rate table */ +#define BHSFH_PROX_DEFAULT_RATE 50 /* in Hz */ +#define BHSFH_PROX_DEF_RATE_THRESH 5 /* in Hz */ +#define BHSFH_STARTUP_DELAY 50 +#define BHSFH_RESET_TIME 10 +#define BHSFH_TIMEOUT 2100 /* Timeout in 2.1 seconds */ + +#define BHSFH_LUX_RANGE 65535 +#define BHSFH_PROX_RANGE 255 +#define BHSFH_COEF_SCALER 1024 +#define BHSFH_CALIB_SCALER 8192 +#define BHSFH_LUX_NEUTRAL_CALIB_VALUE (1 * BHSFH_CALIB_SCALER) +#define BHSFH_LUX_DEF_THRES 1000 +#define BHSFH_PROX_DEF_THRES 70 +#define BHSFH_PROX_DEF_ABS_THRES 100 +#define BHSFH_DEFAULT_PERSISTENCE 10 +#define BHSFH_PROX_MAX_PERSISTENCE 50 +#define BHSFH_LUX_GA_SCALE 16384 +#define BHSFH_LUX_CF_SCALE 2048 /* CF ChipFactor */ +#define BHSFH_NEUTRAL_CF BHSFH_LUX_CF_SCALE +#define BHSFH_LUX_CORR_SCALE 4096 + +#define PROX_ABOVE_THRESHOLD 1 +#define PROX_BELOW_THRESHOLD 0 + +#define PROX_IGNORE_LUX_LIMIT 500 + +struct bhsfh_chip { + struct bhsfh_platform_data *pdata; + char chipname[10]; + u8 revision; + struct i2c_client *client; + struct regulator_bulk_data regs[2]; + struct mutex mutex; /* avoid parallel access */ + wait_queue_head_t wait; + + bool int_mode_prox; + bool int_mode_lux; + struct delayed_work prox_work; + u32 lux_cf; /* Chip specific factor */ + u32 lux_ga; + u32 lux_calib; + int lux_rate_index; + u32 lux_corr; + u16 lux_data_raw; + u16 lux_threshold_hi; + u16 lux_threshold_lo; + u16 lux_thres_hi_onchip; + u16 lux_thres_lo_onchip; + bool lux_wait_result; + + int prox_enable_count; + u16 prox_coef; + u16 prox_const; + int prox_rate; + int prox_rate_threshold; + u8 prox_persistence; + u8 prox_persistence_counter; + u8 prox_data; + u8 prox_threshold; + u8 prox_threshold_hw; + bool prox_force_update; + u8 prox_abs_thres; + u8 prox_led; + u8 prox_channels; /* nbr of leds */ +}; + +static const char reg_vcc[] = "Vcc"; +static const char reg_vleds[] = "Vleds"; + +/* + * Supported stand alone rates in ms from chip data sheet + * {10, 20, 30, 40, 70, 100, 200, 500, 1000, 2000}; + */ +static const s16 prox_rates_hz[] = {100, 50, 33, 25, 14, 10, 5, 2}; +static const s16 prox_rates_ms[] = {10, 20, 30, 40, 70, 100, 200, 500}; + +/* Supported IR-led currents in mA */ +static const u8 prox_curr_ma[] = {5, 10, 20, 50, 100, 150, 200}; + +/* + * Supported stand alone rates in ms from chip data sheet + * {100, 200, 500, 1000, 2000}; + */ +static const s16 lux_rates_hz[] = {10, 5, 2, 1, 0}; + +/* + * interrupt control functions are called while keeping chip->mutex + * excluding module probe / remove + */ +static inline int bhsfh_lux_interrupt_control(struct bhsfh_chip *chip, + int lux) +{ + chip->int_mode_lux = lux; + /* Set interrupt modes, interrupt active low, latched */ + return i2c_smbus_write_byte_data(chip->client, + BHSFH_INTERRUPT, + (lux << 1) | chip->int_mode_prox); +} + +static inline int bhsfh_prox_interrupt_control(struct bhsfh_chip *chip, + int ps) +{ + chip->int_mode_prox = ps; + return i2c_smbus_write_byte_data(chip->client, + BHSFH_INTERRUPT, + (chip->int_mode_lux << 1) | (ps << 0)); +} + +/* chip->mutex is always kept here */ +static int bhsfh_lux_rate(struct bhsfh_chip *chip, int rate_index) +{ + /* sysfs may call this when the chip is powered off */ + if (pm_runtime_suspended(&chip->client->dev)) + return 0; + + /* Proper proximity response needs fastest lux rate (100ms) */ + if (chip->prox_enable_count) + rate_index = 0; + + return i2c_smbus_write_byte_data(chip->client, + BHSFH_ALS_MEAS_RATE, + rate_index); +} + +static int bhsfh_prox_rate(struct bhsfh_chip *chip, int mode) +{ + int rate; + + rate = (mode == PROX_ABOVE_THRESHOLD) ? + chip->prox_rate_threshold : chip->prox_rate; + + return i2c_smbus_write_byte_data(chip->client, + BHSFH_PS_MEAS_RATE, + rate); +} + +/* InfraredLED is controlled by the chip during proximity scanning */ +static inline int bhsfh_led_cfg(struct bhsfh_chip *chip) +{ + /* LED cfg, current for leds 1 and 2 */ + return i2c_smbus_write_byte_data(chip->client, + BHSFH_I_LED, + (BHSFH_LED1 << 6) | + (BHSFH_LED_5mA << 3) | + chip->prox_led); +} + +/* + * Following two functions converts raw ps values from HW to normalized + * values. Purpose is to compensate differences between different sensor + * versions and variants so that result means about the same between + * versions. + */ +static inline u8 bhsfh_psraw_to_adjusted(struct bhsfh_chip *chip, u8 psraw) +{ + u16 adjusted; + adjusted = (u16)(((u32)(psraw + chip->prox_const) * chip->prox_coef) / + BHSFH_COEF_SCALER); + if (adjusted > BHSFH_PROX_RANGE) + adjusted = BHSFH_PROX_RANGE; + return adjusted; +} + +static inline u8 bhsfh_psadjusted_to_raw(struct bhsfh_chip *chip, u8 ps) +{ + u16 raw; + + raw = (((u32)ps * BHSFH_COEF_SCALER) / chip->prox_coef); + if (raw > chip->prox_const) + raw = raw - chip->prox_const; + else + raw = 0; + return raw; +} + +/* + * Following two functions converts raw lux values from HW to normalized + * values. Purpose is to compensate differences between different sensor + * versions and variants so that result means about the same between + * versions. Chip->mutex is kept when this is called. + */ +static int bhsfh_prox_set_threshold(struct bhsfh_chip *chip) +{ + u8 tmp = 0; + + /* sysfs may call this when the chip is powered off */ + if (pm_runtime_suspended(&chip->client->dev)) + return 0; + + tmp = bhsfh_psadjusted_to_raw(chip, chip->prox_threshold); + chip->prox_threshold_hw = tmp; + + return i2c_smbus_write_byte_data(chip->client, BHSFH_PS_TH_LED1, + tmp); +} + +static inline u16 bhsfh_lux_raw_to_adjusted(struct bhsfh_chip *chip, u16 raw) +{ + u32 lux; + lux = ((u32)raw * chip->lux_corr) / BHSFH_LUX_CORR_SCALE; + return min(lux, (u32)BHSFH_LUX_RANGE); +} + +static inline u16 bhsfh_lux_adjusted_to_raw(struct bhsfh_chip *chip, + u16 adjusted) +{ + return (u32)adjusted * BHSFH_LUX_CORR_SCALE / chip->lux_corr; +} + +/* chip->mutex is kept when this is called */ +static int bhsfh_lux_update_thresholds(struct bhsfh_chip *chip, + u16 threshold_hi, u16 threshold_lo) +{ + u8 data[4]; + int ret; + + /* sysfs may call this when the chip is powered off */ + if (pm_runtime_suspended(&chip->client->dev)) + return 0; + + /* + * Compensate threshold values with the correction factors if not + * set to minimum or maximum. + * Min & max values disables interrupts. + */ + if (threshold_hi != BHSFH_LUX_RANGE && threshold_hi != 0) + threshold_hi = bhsfh_lux_adjusted_to_raw(chip, threshold_hi); + + if (threshold_lo != BHSFH_LUX_RANGE && threshold_lo != 0) + threshold_lo = bhsfh_lux_adjusted_to_raw(chip, threshold_lo); + + if (chip->lux_thres_hi_onchip == threshold_hi && + chip->lux_thres_lo_onchip == threshold_lo) + return 0; + + chip->lux_thres_hi_onchip = threshold_hi; + chip->lux_thres_lo_onchip = threshold_lo; + + data[0] = threshold_hi; + data[1] = threshold_hi >> 8; + data[2] = threshold_lo; + data[3] = threshold_lo >> 8; + + ret = i2c_smbus_write_i2c_block_data(chip->client, + BHSFH_ALS_TH_UP_0, + ARRAY_SIZE(data), + data); + return ret; +} + +static int bhsfh_lux_get_result(struct bhsfh_chip *chip) +{ + u16 data; + int ret; + + ret = i2c_smbus_read_byte_data(chip->client, BHSFH_ALS_DATA_0); + if (ret < 0) + return ret; + + data = ret & 0xff; + ret = i2c_smbus_read_byte_data(chip->client, BHSFH_ALS_DATA_1); + if (ret < 0) + return ret; + + chip->lux_data_raw = data | ((ret & 0xff) << 8); + + return 0; +} + +/* Calculate correction value which contains chip and device specific parts */ +static u32 bhsfh_get_corr_value(struct bhsfh_chip *chip) +{ + u32 tmp; + /* Impact of glass attenuation correction */ + tmp = (BHSFH_LUX_CORR_SCALE * chip->lux_ga) / BHSFH_LUX_GA_SCALE; + /* Impact of chip factor correction */ + tmp = (tmp * chip->lux_cf) / BHSFH_LUX_CF_SCALE; + /* Impact of Device specific calibration correction */ + tmp = (tmp * chip->lux_calib) / BHSFH_CALIB_SCALER; + return tmp; +} + +static int bhsfh_lux_read_result(struct bhsfh_chip *chip) +{ + bhsfh_lux_get_result(chip); + return bhsfh_lux_raw_to_adjusted(chip, chip->lux_data_raw); +} + +/* + * Chip on / off functions are called while keeping mutex except probe + * or remove phase + */ +static int bhsfh_chip_on(struct bhsfh_chip *chip) +{ + int ret = regulator_bulk_enable(ARRAY_SIZE(chip->regs), + chip->regs); + if (ret < 0) + return ret; + + usleep_range(BHSFH_STARTUP_DELAY, BHSFH_STARTUP_DELAY * 2); + + /* Reset the chip */ + i2c_smbus_write_byte_data(chip->client, BHSFH_ALS_CONTROL, + BHSFH_SWRESET); + usleep_range(BHSFH_RESET_TIME, BHSFH_RESET_TIME * 2); + + /* + * ALS is started always since proximity needs als results + * for realibility estimation. + * Let's assume dark until the first ALS measurement is ready. + */ + chip->lux_data_raw = 0; + ret = i2c_smbus_write_byte_data(chip->client, + BHSFH_ALS_CONTROL, BHSFH_STANDALONE); + + /* Assume reset defaults */ + chip->lux_thres_hi_onchip = BHSFH_LUX_RANGE; + chip->lux_thres_lo_onchip = 0; + + return ret; +} + +static void bhsfh_chip_off(struct bhsfh_chip *chip) +{ + i2c_smbus_write_byte_data(chip->client, + BHSFH_INTERRUPT, BHSFH_DISABLE); + i2c_smbus_write_byte_data(chip->client, + BHSFH_ALS_CONTROL, BHSFH_STANDBY); + i2c_smbus_write_byte_data(chip->client, + BHSFH_PS_CONTROL, BHSFH_STANDBY); + regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs); +} + +/* chip->mutex is kept when this is called */ +static int bhsfh_prox_mode_control(struct bhsfh_chip *chip) +{ + if (chip->prox_enable_count) { + chip->prox_force_update = true; /* Force immediate update */ + + bhsfh_lux_rate(chip, chip->lux_rate_index); + bhsfh_prox_set_threshold(chip); + bhsfh_led_cfg(chip); + bhsfh_prox_rate(chip, PROX_BELOW_THRESHOLD); + bhsfh_prox_interrupt_control(chip, BHSFH_ENABLE); + i2c_smbus_write_byte_data(chip->client, + BHSFH_PS_CONTROL, BHSFH_STANDALONE); + } else { + chip->prox_data = 0; + bhsfh_lux_rate(chip, chip->lux_rate_index); + bhsfh_prox_interrupt_control(chip, BHSFH_DISABLE); + i2c_smbus_write_byte_data(chip->client, + BHSFH_PS_CONTROL, BHSFH_STANDBY); + } + return 0; +} + +static void bhsfh_prox_rate_above(struct bhsfh_chip *chip, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(prox_rates_hz); i++) + if (rate >= prox_rates_hz[i]) + break; + + if (i > BHSFH_PS_MAX_RATE) + i = BHSFH_PS_MAX_RATE; + + chip->prox_rate_threshold = i; +} + +static void bhsfh_prox_rate_below(struct bhsfh_chip *chip, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(prox_rates_hz); i++) + if (rate >= prox_rates_hz[i]) + break; + + if (i > BHSFH_PS_MAX_RATE) + i = BHSFH_PS_MAX_RATE; + + chip->prox_rate = i; +} + +/* chip->mutex is kept when this is called */ +static int bhsfh_prox_read_result(struct bhsfh_chip *chip) +{ + int ret; + bool above; + u8 mode; + + ret = i2c_smbus_read_byte_data(chip->client, BHSFH_PS_DATA_LED1); + if (ret < 0) + goto out; + + if (ret > chip->prox_threshold_hw) + above = true; + else + above = false; + + /* + * when ALS levels goes above limit, proximity result may be + * false proximity. Thus ignore the result. With real proximity + * there is a shadow causing low als levels. + */ + if (chip->lux_data_raw > PROX_IGNORE_LUX_LIMIT) + ret = 0; + + chip->prox_data = bhsfh_psraw_to_adjusted(chip, ret); + + /* Strong proximity level or force mode requires immediate response */ + if (chip->prox_data >= chip->prox_abs_thres || + chip->prox_force_update) + chip->prox_persistence_counter = chip->prox_persistence; + + chip->prox_force_update = false; + + /* Persistence filttering to reduce false proximity events */ + if (likely(above)) { + if (chip->prox_persistence_counter < chip->prox_persistence) { + chip->prox_persistence_counter++; + ret = -ENODATA; + } else { + mode = PROX_ABOVE_THRESHOLD; + ret = 0; + } + } else { + chip->prox_persistence_counter = 0; + mode = PROX_BELOW_THRESHOLD; + chip->prox_data = 0; + ret = 0; + } + + /* Set proximity detection rate based on above or below value */ + if (ret == 0) { + bhsfh_prox_rate(chip, mode); + sysfs_notify(&chip->client->dev.kobj, NULL, "prox0_raw"); + } +out: + return ret; +} + +static int bhsfh_detect(struct bhsfh_chip *chip) +{ + struct i2c_client *client = chip->client; + s32 ret; + u8 manu, part; + + ret = i2c_smbus_read_byte_data(client, BHSFH_MANUFACT_ID); + if (ret < 0) + goto error; + manu = (u8)ret; + + ret = i2c_smbus_read_byte_data(client, BHSFH_PART_ID); + if (ret < 0) + goto error; + part = (u8)ret; + + chip->revision = (part & BHSFH_REV_MASK) >> BHSFH_REV_SHIFT; + chip->prox_coef = BHSFH_COEF_SCALER; + chip->prox_const = 0; + chip->lux_cf = BHSFH_NEUTRAL_CF; + + if ((manu == BHSFH_MANUFACT_ROHM) && + ((part & BHSFH_PART_MASK) == BHSFH_PART)) { + snprintf(chip->chipname, sizeof(chip->chipname), "BH1770GLC"); + return 0; + } + + if ((manu == BHSFH_MANUFACT_OSRAM) && + ((part & BHSFH_PART_MASK) == BHSFH_PART)) { + snprintf(chip->chipname, sizeof(chip->chipname), "SFH7770"); + /* Values selected by comparing different versions */ + chip->prox_coef = 819; /* 0.8 * BHSFH_COEF_SCALER */ + chip->prox_const = 40; + return 0; + } + + ret = -ENODEV; +error: + dev_dbg(&client->dev, "BHSFH or SFH7770 not found\n"); + + return ret; +} + +/* + * This work is re-scheduled at every proximity interrupt. + * If this work is running, it means that there hasn't been any + * proximity interrupt in time. Situation is handled as no-proximity. + * It would be nice to have low-threshold interrupt or interrupt + * when measurement and hi-threshold are both 0. But neither of those exists. + * This is a workaroud for missing HW feature. + */ + +static void bhsfh_prox_work(struct work_struct *work) +{ + struct bhsfh_chip *chip = + container_of(work, struct bhsfh_chip, prox_work.work); + + mutex_lock(&chip->mutex); + bhsfh_prox_read_result(chip); + mutex_unlock(&chip->mutex); +} + +/* This is threaded irq handler */ +static irqreturn_t bhsfh_irq(int irq, void *data) +{ + struct bhsfh_chip *chip = data; + int status; + int rate = 0; + + mutex_lock(&chip->mutex); + status = i2c_smbus_read_byte_data(chip->client, BHSFH_ALS_PS_STATUS); + + /* Acknowledge interrupt by reading this register */ + i2c_smbus_read_byte_data(chip->client, BHSFH_INTERRUPT); + + /* + * Check if there is fresh data available for als. + * If this is the very first data, update thresholds after that. + */ + if (status & BHSFH_INT_ALS_DATA) { + bhsfh_lux_get_result(chip); + if (unlikely(chip->lux_wait_result)) { + chip->lux_wait_result = false; + wake_up(&chip->wait); + bhsfh_lux_update_thresholds(chip, + chip->lux_threshold_hi, + chip->lux_threshold_lo); + } + } + + /* Disable interrupt logic to guarantee acknowledgement */ + i2c_smbus_write_byte_data(chip->client, BHSFH_INTERRUPT, + (0 << 1) | (0 << 0)); + + if ((status & BHSFH_INT_ALS_INT)) + sysfs_notify(&chip->client->dev.kobj, NULL, "lux0_input"); + + if (chip->int_mode_prox) + if (status & BHSFH_INT_LEDS_INT) { + rate = prox_rates_ms[chip->prox_rate_threshold]; + bhsfh_prox_read_result(chip); + } + + /* Re-enable interrupt logic */ + i2c_smbus_write_byte_data(chip->client, BHSFH_INTERRUPT, + (chip->int_mode_lux << 1) | + (chip->int_mode_prox << 0)); + mutex_unlock(&chip->mutex); + + /* + * Can't cancel work while keeping mutex since the work uses the + * same mutex. + */ + if (rate) { + /* + * Simulate missing no-proximity interrupt 50ms after the + * next expected interrupt time. + */ + cancel_delayed_work_sync(&chip->prox_work); + schedule_delayed_work(&chip->prox_work, + msecs_to_jiffies(rate + 50)); + } + return IRQ_HANDLED; +} + +static ssize_t bhsfh_power_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + unsigned long value; + size_t ret; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + mutex_lock(&chip->mutex); + if (value) { + pm_runtime_get_sync(dev); + + ret = bhsfh_lux_rate(chip, chip->lux_rate_index); + ret |= bhsfh_lux_interrupt_control(chip, BHSFH_ENABLE); + + if (ret < 0) { + pm_runtime_put(dev); + goto leave; + } + + /* This causes interrupt after the next measurement cycle */ + bhsfh_lux_update_thresholds(chip, BHSFH_LUX_DEF_THRES, + BHSFH_LUX_DEF_THRES); + /* Inform that we are waiting for a result from ALS */ + chip->lux_wait_result = true; + } else { + if (!pm_runtime_suspended(dev)) + pm_runtime_put(dev); + } + bhsfh_prox_mode_control(chip); + ret = count; +leave: + mutex_unlock(&chip->mutex); + return ret; +} + +static ssize_t bhsfh_power_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", !pm_runtime_suspended(dev)); +} + +static ssize_t bhsfh_lux_result_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + ssize_t ret; + long timeout; + + if (pm_runtime_suspended(dev)) + return -EIO; /* Chip is not enabled at all */ + + timeout = wait_event_interruptible_timeout(chip->wait, + !chip->lux_wait_result, + msecs_to_jiffies(BHSFH_TIMEOUT)); + if (!timeout) + return -EIO; + + mutex_lock(&chip->mutex); + ret = sprintf(buf, "%d\n", bhsfh_lux_read_result(chip)); + mutex_unlock(&chip->mutex); + + return ret; +} + +static ssize_t bhsfh_lux_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", BHSFH_LUX_RANGE); +} + +static ssize_t bhsfh_prox_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + unsigned long value; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + mutex_lock(&chip->mutex); + if (value) { + /* Assume no proximity. Sensor will tell real state soon */ + if (!chip->prox_enable_count) + chip->prox_data = 0; + chip->prox_enable_count++; + } else if (chip->prox_enable_count > 0) { + chip->prox_enable_count--; + } else { + goto leave; + } + /* Run control only when chip is power on */ + if (!pm_runtime_suspended(dev)) + bhsfh_prox_mode_control(chip); +leave: + mutex_unlock(&chip->mutex); + return count; +} + +static ssize_t bhsfh_prox_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + ssize_t len; + + mutex_lock(&chip->mutex); + len = sprintf(buf, "%d\n", chip->prox_enable_count); + mutex_unlock(&chip->mutex); + return len; +} + +static ssize_t bhsfh_prox_result_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + ssize_t ret; + + mutex_lock(&chip->mutex); + if (chip->prox_enable_count && !pm_runtime_suspended(dev)) + ret = sprintf(buf, "%d\n", chip->prox_data); + else + ret = -EIO; + mutex_unlock(&chip->mutex); + return ret; +} + +static ssize_t bhsfh_prox_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", BHSFH_PROX_RANGE); +} + +static ssize_t bhsfh_get_prox_rate_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + int pos = 0; + for (i = 0; i < ARRAY_SIZE(prox_rates_hz); i++) + pos += sprintf(buf + pos, "%d ", prox_rates_hz[i]); + sprintf(buf + pos - 1, "\n"); + return pos; +} + +static ssize_t bhsfh_get_prox_rate_above(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", prox_rates_hz[chip->prox_rate_threshold]); +} + +static ssize_t bhsfh_get_prox_rate_below(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", prox_rates_hz[chip->prox_rate]); +} + +static ssize_t bhsfh_set_prox_rate_above(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + unsigned long value; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + mutex_lock(&chip->mutex); + bhsfh_prox_rate_above(chip, value); + mutex_unlock(&chip->mutex); + return count; +} + +static ssize_t bhsfh_set_prox_rate_below(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + unsigned long value; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + mutex_lock(&chip->mutex); + bhsfh_prox_rate_below(chip, value); + mutex_unlock(&chip->mutex); + return count; +} + +static ssize_t bhsfh_get_prox_thres(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", chip->prox_threshold); +} + +static ssize_t bhsfh_set_prox_thres(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + unsigned long value; + int ret; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + if (value > BHSFH_PROX_RANGE) + return -EINVAL; + + mutex_lock(&chip->mutex); + chip->prox_threshold = value; + ret = bhsfh_prox_set_threshold(chip); + mutex_unlock(&chip->mutex); + if (ret < 0) + return ret; + return count; +} + +static ssize_t bhsfh_prox_persistence_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", chip->prox_persistence); +} + +static ssize_t bhsfh_prox_persistence_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + unsigned long value; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + if (value > BHSFH_PROX_MAX_PERSISTENCE) + return -EINVAL; + + chip->prox_persistence = value; + + return len; +} + +static ssize_t bhsfh_prox_abs_thres_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + return snprintf(buf, PAGE_SIZE, "%u\n", chip->prox_abs_thres); +} + +static ssize_t bhsfh_prox_abs_thres_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + unsigned long value; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + if (value > BHSFH_PROX_RANGE) + return -EINVAL; + + chip->prox_abs_thres = value; + + return len; +} + +static ssize_t bhsfh_chip_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + return sprintf(buf, "%s rev %d\n", chip->chipname, chip->revision); +} + +static ssize_t bhsfh_lux_calib_default_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", BHSFH_CALIB_SCALER); +} + +static ssize_t bhsfh_lux_calib_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + ssize_t len; + + mutex_lock(&chip->mutex); + len = snprintf(buf, PAGE_SIZE, "%u\n", chip->lux_calib); + mutex_unlock(&chip->mutex); + return len; +} + +static ssize_t bhsfh_lux_calib_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + unsigned long value; + u32 old_calib; + u32 new_corr; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + mutex_lock(&chip->mutex); + old_calib = chip->lux_calib; + chip->lux_calib = value; + new_corr = bhsfh_get_corr_value(chip); + if (new_corr == 0) { + chip->lux_calib = old_calib; + mutex_unlock(&chip->mutex); + return -EINVAL; + } + chip->lux_corr = new_corr; + /* Refresh thresholds on HW after changing correction value */ + bhsfh_lux_update_thresholds(chip, chip->lux_threshold_hi, + chip->lux_threshold_lo); + + mutex_unlock(&chip->mutex); + + return len; +} + +static ssize_t bhsfh_get_lux_rate_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + int pos = 0; + for (i = 0; i < ARRAY_SIZE(lux_rates_hz); i++) + pos += sprintf(buf + pos, "%d ", lux_rates_hz[i]); + sprintf(buf + pos - 1, "\n"); + return pos; +} + +static ssize_t bhsfh_get_lux_rate(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", lux_rates_hz[chip->lux_rate_index]); +} + +static ssize_t bhsfh_set_lux_rate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + unsigned long rate_hz; + int ret, i; + + if (strict_strtoul(buf, 0, &rate_hz)) + return -EINVAL; + + mutex_lock(&chip->mutex); + + for (i = 0; i < ARRAY_SIZE(lux_rates_hz); i++) + if (rate_hz >= lux_rates_hz[i]) + break; + + if (i > BHSFH_ALS_MAX_RATE) + i = BHSFH_ALS_MAX_RATE; + + chip->lux_rate_index = i; + ret = bhsfh_lux_rate(chip, i); + mutex_unlock(&chip->mutex); + + if (ret < 0) + return ret; + + return count; +} + +static ssize_t bhsfh_get_lux_thresh_above(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", chip->lux_threshold_hi); +} + +static ssize_t bhsfh_get_lux_thresh_below(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", chip->lux_threshold_lo); +} + +static ssize_t bhsfh_set_lux_thresh(struct bhsfh_chip *chip, u16 *target, + const char *buf) +{ + int ret = 0; + unsigned long thresh; + + if (strict_strtoul(buf, 0, &thresh)) + return -EINVAL; + + if (thresh > BHSFH_LUX_RANGE) + return -EINVAL; + + mutex_lock(&chip->mutex); + *target = thresh; + /* + * Don't update values in HW if we are still waiting for + * first interrupt to come after device handle open call. + */ + if (!chip->lux_wait_result) + ret = bhsfh_lux_update_thresholds(chip, chip->lux_threshold_hi, + chip->lux_threshold_lo); + mutex_unlock(&chip->mutex); + return ret; + +} + +static ssize_t bhsfh_set_lux_thresh_above(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + int ret = bhsfh_set_lux_thresh(chip, &chip->lux_threshold_hi, buf); + if (ret < 0) + return ret; + return len; +} + +static ssize_t bhsfh_set_lux_thresh_below(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct bhsfh_chip *chip = dev_get_drvdata(dev); + int ret = bhsfh_set_lux_thresh(chip, &chip->lux_threshold_lo, buf); + if (ret < 0) + return ret; + return len; +} + +static DEVICE_ATTR(prox0_raw_en, S_IRUGO | S_IWUSR, bhsfh_prox_enable_show, + bhsfh_prox_enable_store); +static DEVICE_ATTR(prox0_thresh_above1_value, S_IRUGO | S_IWUSR, + bhsfh_prox_abs_thres_show, + bhsfh_prox_abs_thres_store); +static DEVICE_ATTR(prox0_thresh_above0_value, S_IRUGO | S_IWUSR, + bhsfh_get_prox_thres, + bhsfh_set_prox_thres); +static DEVICE_ATTR(prox0_raw, S_IRUGO, bhsfh_prox_result_show, NULL); +static DEVICE_ATTR(prox0_sensor_range, S_IRUGO, bhsfh_prox_range_show, NULL); +static DEVICE_ATTR(prox0_thresh_above_count, S_IRUGO | S_IWUSR, + bhsfh_prox_persistence_show, + bhsfh_prox_persistence_store); +static DEVICE_ATTR(prox0_rate_above, S_IRUGO | S_IWUSR, + bhsfh_get_prox_rate_above, + bhsfh_set_prox_rate_above); +static DEVICE_ATTR(prox0_rate_below, S_IRUGO | S_IWUSR, + bhsfh_get_prox_rate_below, + bhsfh_set_prox_rate_below); +static DEVICE_ATTR(prox0_rate_avail, S_IRUGO, bhsfh_get_prox_rate_avail, NULL); + +static DEVICE_ATTR(lux0_calibscale, S_IRUGO | S_IWUSR, bhsfh_lux_calib_show, + bhsfh_lux_calib_store); +static DEVICE_ATTR(lux0_calibscale_default, S_IRUGO, + bhsfh_lux_calib_default_show, + NULL); +static DEVICE_ATTR(lux0_input, S_IRUGO, bhsfh_lux_result_show, NULL); +static DEVICE_ATTR(lux0_sensor_range, S_IRUGO, bhsfh_lux_range_show, NULL); +static DEVICE_ATTR(lux0_rate, S_IRUGO | S_IWUSR, bhsfh_get_lux_rate, + bhsfh_set_lux_rate); +static DEVICE_ATTR(lux0_rate_avail, S_IRUGO, bhsfh_get_lux_rate_avail, NULL); +static DEVICE_ATTR(lux0_thresh_above_value, S_IRUGO | S_IWUSR, + bhsfh_get_lux_thresh_above, + bhsfh_set_lux_thresh_above); +static DEVICE_ATTR(lux0_thresh_below_value, S_IRUGO | S_IWUSR, + bhsfh_get_lux_thresh_below, + bhsfh_set_lux_thresh_below); +static DEVICE_ATTR(chip_id, S_IRUGO, bhsfh_chip_id_show, NULL); +static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, bhsfh_power_state_show, + bhsfh_power_state_store); + + +static struct attribute *sysfs_attrs[] = { + &dev_attr_lux0_calibscale.attr, + &dev_attr_lux0_calibscale_default.attr, + &dev_attr_lux0_input.attr, + &dev_attr_lux0_sensor_range.attr, + &dev_attr_lux0_rate.attr, + &dev_attr_lux0_rate_avail.attr, + &dev_attr_lux0_thresh_above_value.attr, + &dev_attr_lux0_thresh_below_value.attr, + &dev_attr_prox0_raw.attr, + &dev_attr_prox0_sensor_range.attr, + &dev_attr_prox0_raw_en.attr, + &dev_attr_prox0_thresh_above_count.attr, + &dev_attr_prox0_rate_above.attr, + &dev_attr_prox0_rate_below.attr, + &dev_attr_prox0_rate_avail.attr, + &dev_attr_prox0_thresh_above0_value.attr, + &dev_attr_prox0_thresh_above1_value.attr, + &dev_attr_chip_id.attr, + &dev_attr_power_state.attr, + NULL +}; + +static struct attribute_group bhsfh_attribute_group = { + .attrs = sysfs_attrs +}; + +static int __devinit bhsfh_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bhsfh_chip *chip; + int err; + + chip = kzalloc(sizeof *chip, GFP_KERNEL); + if (!chip) + return -ENOMEM; + + i2c_set_clientdata(client, chip); + chip->client = client; + + mutex_init(&chip->mutex); + init_waitqueue_head(&chip->wait); + INIT_DELAYED_WORK(&chip->prox_work, bhsfh_prox_work); + + if (client->dev.platform_data == NULL) { + dev_err(&client->dev, "platform data is mandatory\n"); + err = -EINVAL; + goto fail1; + } + + chip->pdata = client->dev.platform_data; + chip->lux_calib = BHSFH_LUX_NEUTRAL_CALIB_VALUE; + chip->lux_rate_index = BHSFH_LUX_DEFAULT_RATE; + chip->lux_threshold_lo = BHSFH_LUX_DEF_THRES; + chip->lux_threshold_hi = BHSFH_LUX_DEF_THRES; + + if (chip->pdata->glass_attenuation == 0) + chip->lux_ga = BHFSH_NEUTRAL_GA; + else + chip->lux_ga = chip->pdata->glass_attenuation; + + chip->prox_threshold = BHSFH_PROX_DEF_THRES; + chip->prox_led = chip->pdata->led_def_curr; + chip->prox_abs_thres = BHSFH_PROX_DEF_ABS_THRES; + chip->prox_persistence = BHSFH_DEFAULT_PERSISTENCE; + chip->prox_data = 0; + + + bhsfh_prox_rate_below(chip, BHSFH_PROX_DEFAULT_RATE); + bhsfh_prox_rate_above(chip, BHSFH_PROX_DEF_RATE_THRESH); + + chip->regs[0].supply = reg_vcc; + chip->regs[1].supply = reg_vleds; + + err = regulator_bulk_get(&client->dev, + ARRAY_SIZE(chip->regs), chip->regs); + if (err < 0) { + dev_err(&client->dev, "Cannot get regulators\n"); + goto fail1; + } + + err = regulator_bulk_enable(ARRAY_SIZE(chip->regs), + chip->regs); + if (err < 0) { + dev_err(&client->dev, "Cannot enable regulators\n"); + goto fail2; + } + + usleep_range(BHSFH_STARTUP_DELAY, BHSFH_STARTUP_DELAY * 2); + err = bhsfh_detect(chip); + if (err < 0) + goto fail3; + + /* Start chip */ + bhsfh_chip_on(chip); + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + + chip->lux_corr = bhsfh_get_corr_value(chip); + if (chip->lux_corr == 0) { + dev_err(&client->dev, "Improper correction values\n"); + err = -EINVAL; + goto fail3; + } + + if (chip->pdata->setup_resources) { + err = chip->pdata->setup_resources(); + if (err) { + err = -EINVAL; + goto fail3; + } + } + + err = sysfs_create_group(&chip->client->dev.kobj, + &bhsfh_attribute_group); + if (err < 0) { + dev_err(&chip->client->dev, "Sysfs registration failed\n"); + goto fail4; + } + + /* + * Chip needs level triggered interrupt to work. However, + * level triggering doesn't work always correctly with power + * management. Select both + */ + err = request_threaded_irq(client->irq, NULL, + bhsfh_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT | + IRQF_TRIGGER_LOW, + "bhsfh", chip); + if (err) { + dev_err(&client->dev, "could not get IRQ %d\n", + client->irq); + goto fail5; + } + regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs); + return err; +fail5: + sysfs_remove_group(&chip->client->dev.kobj, + &bhsfh_attribute_group); +fail4: + if (chip->pdata->release_resources) + chip->pdata->release_resources(); +fail3: + regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs); +fail2: + regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs); +fail1: + kfree(chip); + return err; +} + +static int __devexit bhsfh_remove(struct i2c_client *client) +{ + struct bhsfh_chip *chip = i2c_get_clientdata(client); + + free_irq(client->irq, chip); + + sysfs_remove_group(&chip->client->dev.kobj, + &bhsfh_attribute_group); + + if (chip->pdata->release_resources) + chip->pdata->release_resources(); + + cancel_delayed_work_sync(&chip->prox_work); + + if (!pm_runtime_suspended(&client->dev)) + bhsfh_chip_off(chip); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs); + kfree(chip); + return 0; +} + +#ifdef CONFIG_PM +static int bhsfh_suspend(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct bhsfh_chip *chip = i2c_get_clientdata(client); + + bhsfh_chip_off(chip); + + return 0; +} + +static int bhsfh_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct bhsfh_chip *chip = i2c_get_clientdata(client); + int ret = 0; + + bhsfh_chip_on(chip); + + if (!pm_runtime_suspended(dev)) { + /* + * If we were enabled at suspend time, it is expected + * everything works nice and smoothly + */ + ret = bhsfh_lux_rate(chip, chip->lux_rate_index); + ret |= bhsfh_lux_interrupt_control(chip, BHSFH_ENABLE); + + /* This causes interrupt after the next measurement cycle */ + bhsfh_lux_update_thresholds(chip, BHSFH_LUX_DEF_THRES, + BHSFH_LUX_DEF_THRES); + /* Inform that we are waiting for a result from ALS */ + chip->lux_wait_result = true; + bhsfh_prox_mode_control(chip); + } + return ret; +} + +#else +#define bhsfh_suspend NULL +#define bhsfh_shutdown NULL +#define bhsfh_resume NULL +#endif + +#ifdef CONFIG_PM_RUNTIME +static int bhsfh_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct bhsfh_chip *chip = i2c_get_clientdata(client); + + bhsfh_chip_off(chip); + + return 0; +} + +static int bhsfh_runtime_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct bhsfh_chip *chip = i2c_get_clientdata(client); + + bhsfh_chip_on(chip); + + return 0; +} +#endif + +static const struct i2c_device_id bhsfh_id[] = { + {"bh1770glc", 0 }, + {"sfh7770", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, bhsfh_id); + +static const struct dev_pm_ops bhsfh_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(bhsfh_suspend, bhsfh_resume) + SET_RUNTIME_PM_OPS(bhsfh_runtime_suspend, bhsfh_runtime_resume, NULL) +}; + +static struct i2c_driver bhsfh_driver = { + .driver = { + .name = "bhsfh", + .owner = THIS_MODULE, + .pm = &bhsfh_pm_ops, + }, + .probe = bhsfh_probe, + .remove = __devexit_p(bhsfh_remove), + .id_table = bhsfh_id, +}; + +static int __init bhsfh_init(void) +{ + return i2c_add_driver(&bhsfh_driver); +} + +static void __exit bhsfh_exit(void) +{ + i2c_del_driver(&bhsfh_driver); +} + +MODULE_DESCRIPTION("BH1770GLC / SFH7770 combined ALS and proximity sensor"); +MODULE_AUTHOR("Samu Onkalo, Nokia Corporation"); +MODULE_LICENSE("GPL v2"); + +module_init(bhsfh_init); +module_exit(bhsfh_exit); diff --git a/include/linux/i2c/bhsfh.h b/include/linux/i2c/bhsfh.h new file mode 100644 index 0000000..a19e791 --- /dev/null +++ b/include/linux/i2c/bhsfh.h @@ -0,0 +1,42 @@ +/* + * This file is part of the ROHM BH1770GLC / OSRAM SFH7770 sensor driver. + * Chip is combined proximity and ambient light sensor. + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * Contact: Samu Onkalo <samu.p.onkalo@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __BHSFH_H__ +#define __BHSFH_H__ + +struct bhsfh_platform_data { +#define BHSFH_LED_5mA 0 +#define BHSFH_LED_10mA 1 +#define BHSFH_LED_20mA 2 +#define BHSFH_LED_50mA 3 +#define BHSFH_LED_100mA 4 +#define BHSFH_LED_150mA 5 +#define BHSFH_LED_200mA 6 + __u8 led_def_curr; +#define BHFSH_NEUTRAL_GA 16384 /* 16384 / 16384 = 1 */ + __u32 glass_attenuation; + int (*setup_resources)(void); + int (*release_resources)(void); +}; +#endif -- 1.6.0.4 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCHv2 1/5] misc: Driver for bh1770glc / sfh7770 ALS and proximity sensor 2010-10-08 13:42 ` [PATCHv2 1/5] misc: Driver for bh1770glc / sfh7770 ALS and proximity sensor Samu Onkalo @ 2010-10-08 14:35 ` Jonathan Cameron 2010-10-11 6:16 ` Onkalo Samu 0 siblings, 1 reply; 16+ messages in thread From: Jonathan Cameron @ 2010-10-08 14:35 UTC (permalink / raw) To: Samu Onkalo; +Cc: linux-i2c, linux-kernel, alan On 10/08/10 14:42, Samu Onkalo wrote: > This is a driver for ROHM BH1770GLC and OSRAM SFH7770 combined > ALS and proximity sensor. > > Interface is sysfs based. The driver uses interrupts to provide new data. > The driver supports pm_runtime and regulator frameworks. > > See Documentation/misc-devices/bhsfh.txt for details Couple of nitpicks / formatting suggestions inline. > > Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com> > --- > drivers/misc/bhsfh.c | 1443 +++++++++++++++++++++++++++++++++++++++++++++ > include/linux/i2c/bhsfh.h | 42 ++ > 2 files changed, 1485 insertions(+), 0 deletions(-) > create mode 100644 drivers/misc/bhsfh.c > create mode 100644 include/linux/i2c/bhsfh.h > > diff --git a/drivers/misc/bhsfh.c b/drivers/misc/bhsfh.c > new file mode 100644 > index 0000000..e82fadb > --- /dev/null > +++ b/drivers/misc/bhsfh.c > @@ -0,0 +1,1443 @@ > +/* > + * This file is part of the ROHM BH1770GLC / OSRAM SFH7770 sensor driver. > + * Chip is combined proximity and ambient light sensor. > + * > + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). > + * > + * Contact: Samu Onkalo <samu.p.onkalo@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + * > + */ > + > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/i2c.h> > +#include <linux/interrupt.h> > +#include <linux/mutex.h> > +#include <linux/i2c/bhsfh.h> > +#include <linux/regulator/consumer.h> > +#include <linux/pm_runtime.h> > +#include <linux/workqueue.h> > +#include <linux/delay.h> > +#include <linux/wait.h> > +#include <linux/slab.h> > + > +#define BHSFH_ALS_CONTROL 0x80 /* ALS operation mode control */ > +#define BHSFH_PS_CONTROL 0x81 /* PS operation mode control */ > +#define BHSFH_I_LED 0x82 /* active LED and LED1, LED2 current */ > +#define BHSFH_I_LED3 0x83 /* LED3 current setting */ > +#define BHSFH_ALS_PS_MEAS 0x84 /* Forced mode trigger */ > +#define BHSFH_PS_MEAS_RATE 0x85 /* PS meas. rate at stand alone mode */ > +#define BHSFH_ALS_MEAS_RATE 0x86 /* ALS meas. rate at stand alone mode */ > +#define BHSFH_PART_ID 0x8a /* Part number and revision ID */ > +#define BHSFH_MANUFACT_ID 0x8b /* Manufacturerer ID */ > +#define BHSFH_ALS_DATA_0 0x8c /* ALS DATA low byte */ > +#define BHSFH_ALS_DATA_1 0x8d /* ALS DATA high byte */ > +#define BHSFH_ALS_PS_STATUS 0x8e /* Measurement data and int status */ > +#define BHSFH_PS_DATA_LED1 0x8f /* PS data from LED1 */ > +#define BHSFH_PS_DATA_LED2 0x90 /* PS data from LED2 */ > +#define BHSFH_PS_DATA_LED3 0x91 /* PS data from LED3 */ > +#define BHSFH_INTERRUPT 0x92 /* Interrupt setting */ > +#define BHSFH_PS_TH_LED1 0x93 /* PS interrupt threshold for LED1 */ > +#define BHSFH_PS_TH_LED2 0x94 /* PS interrupt threshold for LED2 */ > +#define BHSFH_PS_TH_LED3 0x95 /* PS interrupt threshold for LED3 */ > +#define BHSFH_ALS_TH_UP_0 0x96 /* ALS upper threshold low byte */ > +#define BHSFH_ALS_TH_UP_1 0x97 /* ALS upper threshold high byte */ > +#define BHSFH_ALS_TH_LOW_0 0x98 /* ALS lower threshold low byte */ > +#define BHSFH_ALS_TH_LOW_1 0x99 /* ALS lower threshold high byte */ > + > +/* MANUFACT_ID */ > +#define BHSFH_MANUFACT_ROHM 0x01 > +#define BHSFH_MANUFACT_OSRAM 0x03 > + > +/* PART_ID */ > +#define BHSFH_PART 0x90 > +#define BHSFH_PART_MASK 0xf0 > +#define BHSFH_REV_MASK 0x0f > +#define BHSFH_REV_SHIFT 0 > +#define BHSFH_REV_0 0x00 > +#define BHSFH_REV_1 0x01 > + > +/* ALS_MEAS_RATE */ > +#define BHSFH_ALS_MAX_RATE 9 > + > +/* PS_MEAS_RATE */ > +#define BHSFH_PS_MAX_RATE 4 > + > +/* Operating modes for both */ > +#define BHSFH_STANDBY 0x00 > +#define BHSFH_FORCED 0x02 > +#define BHSFH_STANDALONE 0x03 > +#define BHSFH_SWRESET (0x01 << 2) > + > +#define BHSFH_PS_TRIG_MEAS (1 << 0) > +#define BHSFH_ALS_TRIG_MEAS (1 << 1) > + > +/* Interrupt control */ > +#define BHSFH_INT_OUTPUT_MODE (1 << 3) /* 0 = latched */ > +#define BHSFH_INT_POLARITY (1 << 2) /* 1 = active high */ > +#define BHSFH_INT_ALS_ENA (1 << 1) > +#define BHSFH_INT_PS_ENA (1 << 0) > + > +/* Interrupt status */ > +#define BHSFH_INT_LED1_DATA (1 << 0) > +#define BHSFH_INT_LED1_INT (1 << 1) > +#define BHSFH_INT_LED2_DATA (1 << 2) > +#define BHSFH_INT_LED2_INT (1 << 3) > +#define BHSFH_INT_LED3_DATA (1 << 4) > +#define BHSFH_INT_LED3_INT (1 << 5) > +#define BHSFH_INT_LEDS_INT ((1 << 1) | (1 << 3) | (1 << 5)) > +#define BHSFH_INT_ALS_DATA (1 << 6) > +#define BHSFH_INT_ALS_INT (1 << 7) > + > +/* Led channels */ > +#define BHSFH_LED1 0x00 > + > +#define BHSFH_DISABLE 0 > +#define BHSFH_ENABLE 1 > +#define BHSFH_PROX_CHANNELS 1 > + > +#define BHSFH_LUX_DEFAULT_RATE 1 /* Index to lux rate table */ > +#define BHSFH_PROX_DEFAULT_RATE 50 /* in Hz */ > +#define BHSFH_PROX_DEF_RATE_THRESH 5 /* in Hz */ > +#define BHSFH_STARTUP_DELAY 50 > +#define BHSFH_RESET_TIME 10 > +#define BHSFH_TIMEOUT 2100 /* Timeout in 2.1 seconds */ > + > +#define BHSFH_LUX_RANGE 65535 > +#define BHSFH_PROX_RANGE 255 > +#define BHSFH_COEF_SCALER 1024 > +#define BHSFH_CALIB_SCALER 8192 > +#define BHSFH_LUX_NEUTRAL_CALIB_VALUE (1 * BHSFH_CALIB_SCALER) > +#define BHSFH_LUX_DEF_THRES 1000 > +#define BHSFH_PROX_DEF_THRES 70 > +#define BHSFH_PROX_DEF_ABS_THRES 100 > +#define BHSFH_DEFAULT_PERSISTENCE 10 > +#define BHSFH_PROX_MAX_PERSISTENCE 50 > +#define BHSFH_LUX_GA_SCALE 16384 > +#define BHSFH_LUX_CF_SCALE 2048 /* CF ChipFactor */ > +#define BHSFH_NEUTRAL_CF BHSFH_LUX_CF_SCALE > +#define BHSFH_LUX_CORR_SCALE 4096 > + > +#define PROX_ABOVE_THRESHOLD 1 > +#define PROX_BELOW_THRESHOLD 0 > + > +#define PROX_IGNORE_LUX_LIMIT 500 > + > +struct bhsfh_chip { > + struct bhsfh_platform_data *pdata; > + char chipname[10]; > + u8 revision; > + struct i2c_client *client; > + struct regulator_bulk_data regs[2]; > + struct mutex mutex; /* avoid parallel access */ > + wait_queue_head_t wait; > + > + bool int_mode_prox; > + bool int_mode_lux; > + struct delayed_work prox_work; > + u32 lux_cf; /* Chip specific factor */ > + u32 lux_ga; > + u32 lux_calib; > + int lux_rate_index; > + u32 lux_corr; > + u16 lux_data_raw; > + u16 lux_threshold_hi; > + u16 lux_threshold_lo; > + u16 lux_thres_hi_onchip; > + u16 lux_thres_lo_onchip; > + bool lux_wait_result; > + > + int prox_enable_count; > + u16 prox_coef; > + u16 prox_const; > + int prox_rate; > + int prox_rate_threshold; > + u8 prox_persistence; > + u8 prox_persistence_counter; > + u8 prox_data; > + u8 prox_threshold; > + u8 prox_threshold_hw; > + bool prox_force_update; > + u8 prox_abs_thres; > + u8 prox_led; Not used as far as I can tell. > + u8 prox_channels; /* nbr of leds */ > +}; > + > +static const char reg_vcc[] = "Vcc"; > +static const char reg_vleds[] = "Vleds"; > + > +/* > + * Supported stand alone rates in ms from chip data sheet > + * {10, 20, 30, 40, 70, 100, 200, 500, 1000, 2000}; > + */ > +static const s16 prox_rates_hz[] = {100, 50, 33, 25, 14, 10, 5, 2}; > +static const s16 prox_rates_ms[] = {10, 20, 30, 40, 70, 100, 200, 500}; > + > +/* Supported IR-led currents in mA */ > +static const u8 prox_curr_ma[] = {5, 10, 20, 50, 100, 150, 200}; > + > +/* > + * Supported stand alone rates in ms from chip data sheet > + * {100, 200, 500, 1000, 2000}; > + */ > +static const s16 lux_rates_hz[] = {10, 5, 2, 1, 0}; > + > +/* > + * interrupt control functions are called while keeping chip->mutex > + * excluding module probe / remove > + */ > +static inline int bhsfh_lux_interrupt_control(struct bhsfh_chip *chip, > + int lux) > +{ > + chip->int_mode_lux = lux; > + /* Set interrupt modes, interrupt active low, latched */ > + return i2c_smbus_write_byte_data(chip->client, > + BHSFH_INTERRUPT, > + (lux << 1) | chip->int_mode_prox); > +} > + > +static inline int bhsfh_prox_interrupt_control(struct bhsfh_chip *chip, > + int ps) > +{ > + chip->int_mode_prox = ps; > + return i2c_smbus_write_byte_data(chip->client, > + BHSFH_INTERRUPT, > + (chip->int_mode_lux << 1) | (ps << 0)); > +} > + > +/* chip->mutex is always kept here */ > +static int bhsfh_lux_rate(struct bhsfh_chip *chip, int rate_index) > +{ > + /* sysfs may call this when the chip is powered off */ > + if (pm_runtime_suspended(&chip->client->dev)) > + return 0; > + > + /* Proper proximity response needs fastest lux rate (100ms) */ > + if (chip->prox_enable_count) > + rate_index = 0; > + > + return i2c_smbus_write_byte_data(chip->client, > + BHSFH_ALS_MEAS_RATE, > + rate_index); > +} > + > +static int bhsfh_prox_rate(struct bhsfh_chip *chip, int mode) > +{ > + int rate; > + > + rate = (mode == PROX_ABOVE_THRESHOLD) ? > + chip->prox_rate_threshold : chip->prox_rate; > + > + return i2c_smbus_write_byte_data(chip->client, > + BHSFH_PS_MEAS_RATE, > + rate); > +} > + > +/* InfraredLED is controlled by the chip during proximity scanning */ > +static inline int bhsfh_led_cfg(struct bhsfh_chip *chip) > +{ > + /* LED cfg, current for leds 1 and 2 */ > + return i2c_smbus_write_byte_data(chip->client, > + BHSFH_I_LED, > + (BHSFH_LED1 << 6) | > + (BHSFH_LED_5mA << 3) | > + chip->prox_led); > +} > + > +/* > + * Following two functions converts raw ps values from HW to normalized > + * values. Purpose is to compensate differences between different sensor > + * versions and variants so that result means about the same between > + * versions. > + */ > +static inline u8 bhsfh_psraw_to_adjusted(struct bhsfh_chip *chip, u8 psraw) > +{ > + u16 adjusted; > + adjusted = (u16)(((u32)(psraw + chip->prox_const) * chip->prox_coef) / > + BHSFH_COEF_SCALER); > + if (adjusted > BHSFH_PROX_RANGE) > + adjusted = BHSFH_PROX_RANGE; > + return adjusted; > +} > + > +static inline u8 bhsfh_psadjusted_to_raw(struct bhsfh_chip *chip, u8 ps) > +{ > + u16 raw; > + > + raw = (((u32)ps * BHSFH_COEF_SCALER) / chip->prox_coef); > + if (raw > chip->prox_const) > + raw = raw - chip->prox_const; > + else > + raw = 0; > + return raw; > +} > + > +/* > + * Following two functions converts raw lux values from HW to normalized > + * values. Purpose is to compensate differences between different sensor > + * versions and variants so that result means about the same between > + * versions. Chip->mutex is kept when this is called. > + */ > +static int bhsfh_prox_set_threshold(struct bhsfh_chip *chip) > +{ > + u8 tmp = 0; > + > + /* sysfs may call this when the chip is powered off */ > + if (pm_runtime_suspended(&chip->client->dev)) > + return 0; > + > + tmp = bhsfh_psadjusted_to_raw(chip, chip->prox_threshold); > + chip->prox_threshold_hw = tmp; > + > + return i2c_smbus_write_byte_data(chip->client, BHSFH_PS_TH_LED1, > + tmp); > +} > + > +static inline u16 bhsfh_lux_raw_to_adjusted(struct bhsfh_chip *chip, u16 raw) > +{ > + u32 lux; > + lux = ((u32)raw * chip->lux_corr) / BHSFH_LUX_CORR_SCALE; > + return min(lux, (u32)BHSFH_LUX_RANGE); > +} > + > +static inline u16 bhsfh_lux_adjusted_to_raw(struct bhsfh_chip *chip, > + u16 adjusted) > +{ > + return (u32)adjusted * BHSFH_LUX_CORR_SCALE / chip->lux_corr; > +} > + > +/* chip->mutex is kept when this is called */ > +static int bhsfh_lux_update_thresholds(struct bhsfh_chip *chip, > + u16 threshold_hi, u16 threshold_lo) > +{ > + u8 data[4]; u8 data[4] = { threshold_hi, threshold_hi >> 8, threshold_lo, threshold_low >> 8}; and loose the below will give same result. > + int ret; > + > + /* sysfs may call this when the chip is powered off */ > + if (pm_runtime_suspended(&chip->client->dev)) > + return 0; > + > + /* > + * Compensate threshold values with the correction factors if not > + * set to minimum or maximum. > + * Min & max values disables interrupts. > + */ > + if (threshold_hi != BHSFH_LUX_RANGE && threshold_hi != 0) > + threshold_hi = bhsfh_lux_adjusted_to_raw(chip, threshold_hi); > + > + if (threshold_lo != BHSFH_LUX_RANGE && threshold_lo != 0) > + threshold_lo = bhsfh_lux_adjusted_to_raw(chip, threshold_lo); > + > + if (chip->lux_thres_hi_onchip == threshold_hi && > + chip->lux_thres_lo_onchip == threshold_lo) > + return 0; > + > + chip->lux_thres_hi_onchip = threshold_hi; > + chip->lux_thres_lo_onchip = threshold_lo; > + > + data[0] = threshold_hi; > + data[1] = threshold_hi >> 8; > + data[2] = threshold_lo; > + data[3] = threshold_lo >> 8; > + > + ret = i2c_smbus_write_i2c_block_data(chip->client, > + BHSFH_ALS_TH_UP_0, > + ARRAY_SIZE(data), > + data); > + return ret; > +} > + > +static int bhsfh_lux_get_result(struct bhsfh_chip *chip) > +{ > + u16 data; > + int ret; > + > + ret = i2c_smbus_read_byte_data(chip->client, BHSFH_ALS_DATA_0); > + if (ret < 0) > + return ret; > + > + data = ret & 0xff; > + ret = i2c_smbus_read_byte_data(chip->client, BHSFH_ALS_DATA_1); > + if (ret < 0) > + return ret; > + > + chip->lux_data_raw = data | ((ret & 0xff) << 8); > + > + return 0; > +} > + > +/* Calculate correction value which contains chip and device specific parts */ > +static u32 bhsfh_get_corr_value(struct bhsfh_chip *chip) > +{ > + u32 tmp; > + /* Impact of glass attenuation correction */ > + tmp = (BHSFH_LUX_CORR_SCALE * chip->lux_ga) / BHSFH_LUX_GA_SCALE; > + /* Impact of chip factor correction */ > + tmp = (tmp * chip->lux_cf) / BHSFH_LUX_CF_SCALE; > + /* Impact of Device specific calibration correction */ > + tmp = (tmp * chip->lux_calib) / BHSFH_CALIB_SCALER; > + return tmp; > +} > + > +static int bhsfh_lux_read_result(struct bhsfh_chip *chip) > +{ > + bhsfh_lux_get_result(chip); > + return bhsfh_lux_raw_to_adjusted(chip, chip->lux_data_raw); > +} > + > +/* > + * Chip on / off functions are called while keeping mutex except probe > + * or remove phase > + */ > +static int bhsfh_chip_on(struct bhsfh_chip *chip) > +{ > + int ret = regulator_bulk_enable(ARRAY_SIZE(chip->regs), > + chip->regs); > + if (ret < 0) > + return ret; > + > + usleep_range(BHSFH_STARTUP_DELAY, BHSFH_STARTUP_DELAY * 2); > + > + /* Reset the chip */ > + i2c_smbus_write_byte_data(chip->client, BHSFH_ALS_CONTROL, > + BHSFH_SWRESET); > + usleep_range(BHSFH_RESET_TIME, BHSFH_RESET_TIME * 2); > + > + /* > + * ALS is started always since proximity needs als results > + * for realibility estimation. > + * Let's assume dark until the first ALS measurement is ready. > + */ > + chip->lux_data_raw = 0; > + ret = i2c_smbus_write_byte_data(chip->client, > + BHSFH_ALS_CONTROL, BHSFH_STANDALONE); > + > + /* Assume reset defaults */ > + chip->lux_thres_hi_onchip = BHSFH_LUX_RANGE; > + chip->lux_thres_lo_onchip = 0; > + > + return ret; > +} > + > +static void bhsfh_chip_off(struct bhsfh_chip *chip) > +{ > + i2c_smbus_write_byte_data(chip->client, > + BHSFH_INTERRUPT, BHSFH_DISABLE); > + i2c_smbus_write_byte_data(chip->client, > + BHSFH_ALS_CONTROL, BHSFH_STANDBY); > + i2c_smbus_write_byte_data(chip->client, > + BHSFH_PS_CONTROL, BHSFH_STANDBY); > + regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs); > +} > + > +/* chip->mutex is kept when this is called */ > +static int bhsfh_prox_mode_control(struct bhsfh_chip *chip) > +{ > + if (chip->prox_enable_count) { > + chip->prox_force_update = true; /* Force immediate update */ > + > + bhsfh_lux_rate(chip, chip->lux_rate_index); > + bhsfh_prox_set_threshold(chip); > + bhsfh_led_cfg(chip); > + bhsfh_prox_rate(chip, PROX_BELOW_THRESHOLD); > + bhsfh_prox_interrupt_control(chip, BHSFH_ENABLE); > + i2c_smbus_write_byte_data(chip->client, > + BHSFH_PS_CONTROL, BHSFH_STANDALONE); > + } else { > + chip->prox_data = 0; > + bhsfh_lux_rate(chip, chip->lux_rate_index); > + bhsfh_prox_interrupt_control(chip, BHSFH_DISABLE); > + i2c_smbus_write_byte_data(chip->client, > + BHSFH_PS_CONTROL, BHSFH_STANDBY); > + } > + return 0; > +} > + Surely these next two can just return the value and let the caller say which value it goes in? The code is otherwise identical. > +static void bhsfh_prox_rate_above(struct bhsfh_chip *chip, int rate) > +{ > + int i; > + > + for (i = 0; i < ARRAY_SIZE(prox_rates_hz); i++) > + if (rate >= prox_rates_hz[i]) > + break; > + > + if (i > BHSFH_PS_MAX_RATE) > + i = BHSFH_PS_MAX_RATE; > + > + chip->prox_rate_threshold = i; > +} > + > +static void bhsfh_prox_rate_below(struct bhsfh_chip *chip, int rate) > +{ > + int i; > + > + for (i = 0; i < ARRAY_SIZE(prox_rates_hz); i++) > + if (rate >= prox_rates_hz[i]) > + break; > + > + if (i > BHSFH_PS_MAX_RATE) > + i = BHSFH_PS_MAX_RATE; > + > + chip->prox_rate = i; > +} > + > +/* chip->mutex is kept when this is called */ > +static int bhsfh_prox_read_result(struct bhsfh_chip *chip) > +{ > + int ret; > + bool above; > + u8 mode; > + > + ret = i2c_smbus_read_byte_data(chip->client, BHSFH_PS_DATA_LED1); > + if (ret < 0) > + goto out; > + > + if (ret > chip->prox_threshold_hw) > + above = true; > + else > + above = false; > + > + /* > + * when ALS levels goes above limit, proximity result may be > + * false proximity. Thus ignore the result. With real proximity > + * there is a shadow causing low als levels. > + */ > + if (chip->lux_data_raw > PROX_IGNORE_LUX_LIMIT) > + ret = 0; > + > + chip->prox_data = bhsfh_psraw_to_adjusted(chip, ret); > + > + /* Strong proximity level or force mode requires immediate response */ > + if (chip->prox_data >= chip->prox_abs_thres || > + chip->prox_force_update) > + chip->prox_persistence_counter = chip->prox_persistence; > + > + chip->prox_force_update = false; > + > + /* Persistence filttering to reduce false proximity events */ > + if (likely(above)) { > + if (chip->prox_persistence_counter < chip->prox_persistence) { > + chip->prox_persistence_counter++; > + ret = -ENODATA; > + } else { > + mode = PROX_ABOVE_THRESHOLD; > + ret = 0; > + } > + } else { > + chip->prox_persistence_counter = 0; > + mode = PROX_BELOW_THRESHOLD; > + chip->prox_data = 0; > + ret = 0; > + } > + > + /* Set proximity detection rate based on above or below value */ > + if (ret == 0) { > + bhsfh_prox_rate(chip, mode); > + sysfs_notify(&chip->client->dev.kobj, NULL, "prox0_raw"); > + } > +out: > + return ret; > +} > + > +static int bhsfh_detect(struct bhsfh_chip *chip) > +{ > + struct i2c_client *client = chip->client; > + s32 ret; > + u8 manu, part; > + > + ret = i2c_smbus_read_byte_data(client, BHSFH_MANUFACT_ID); > + if (ret < 0) > + goto error; > + manu = (u8)ret; > + > + ret = i2c_smbus_read_byte_data(client, BHSFH_PART_ID); > + if (ret < 0) > + goto error; > + part = (u8)ret; > + > + chip->revision = (part & BHSFH_REV_MASK) >> BHSFH_REV_SHIFT; > + chip->prox_coef = BHSFH_COEF_SCALER; > + chip->prox_const = 0; > + chip->lux_cf = BHSFH_NEUTRAL_CF; > + > + if ((manu == BHSFH_MANUFACT_ROHM) && > + ((part & BHSFH_PART_MASK) == BHSFH_PART)) { > + snprintf(chip->chipname, sizeof(chip->chipname), "BH1770GLC"); > + return 0; > + } > + > + if ((manu == BHSFH_MANUFACT_OSRAM) && > + ((part & BHSFH_PART_MASK) == BHSFH_PART)) { > + snprintf(chip->chipname, sizeof(chip->chipname), "SFH7770"); > + /* Values selected by comparing different versions */ > + chip->prox_coef = 819; /* 0.8 * BHSFH_COEF_SCALER */ > + chip->prox_const = 40; > + return 0; > + } > + > + ret = -ENODEV; > +error: > + dev_dbg(&client->dev, "BHSFH or SFH7770 not found\n"); > + > + return ret; > +} > + > +/* > + * This work is re-scheduled at every proximity interrupt. > + * If this work is running, it means that there hasn't been any > + * proximity interrupt in time. Situation is handled as no-proximity. > + * It would be nice to have low-threshold interrupt or interrupt > + * when measurement and hi-threshold are both 0. But neither of those exists. > + * This is a workaroud for missing HW feature. > + */ > + > +static void bhsfh_prox_work(struct work_struct *work) > +{ > + struct bhsfh_chip *chip = > + container_of(work, struct bhsfh_chip, prox_work.work); > + > + mutex_lock(&chip->mutex); > + bhsfh_prox_read_result(chip); > + mutex_unlock(&chip->mutex); > +} > + > +/* This is threaded irq handler */ > +static irqreturn_t bhsfh_irq(int irq, void *data) > +{ > + struct bhsfh_chip *chip = data; > + int status; > + int rate = 0; > + > + mutex_lock(&chip->mutex); > + status = i2c_smbus_read_byte_data(chip->client, BHSFH_ALS_PS_STATUS); > + > + /* Acknowledge interrupt by reading this register */ > + i2c_smbus_read_byte_data(chip->client, BHSFH_INTERRUPT); > + > + /* > + * Check if there is fresh data available for als. > + * If this is the very first data, update thresholds after that. > + */ > + if (status & BHSFH_INT_ALS_DATA) { > + bhsfh_lux_get_result(chip); > + if (unlikely(chip->lux_wait_result)) { > + chip->lux_wait_result = false; > + wake_up(&chip->wait); > + bhsfh_lux_update_thresholds(chip, > + chip->lux_threshold_hi, > + chip->lux_threshold_lo); > + } > + } > + > + /* Disable interrupt logic to guarantee acknowledgement */ > + i2c_smbus_write_byte_data(chip->client, BHSFH_INTERRUPT, > + (0 << 1) | (0 << 0)); > + > + if ((status & BHSFH_INT_ALS_INT)) > + sysfs_notify(&chip->client->dev.kobj, NULL, "lux0_input"); > + > + if (chip->int_mode_prox) > + if (status & BHSFH_INT_LEDS_INT) { Could make the above if (chip->int && (status & BHSFH_INT_LEDS_INT)) > + rate = prox_rates_ms[chip->prox_rate_threshold]; > + bhsfh_prox_read_result(chip); > + } > + > + /* Re-enable interrupt logic */ > + i2c_smbus_write_byte_data(chip->client, BHSFH_INTERRUPT, > + (chip->int_mode_lux << 1) | > + (chip->int_mode_prox << 0)); > + mutex_unlock(&chip->mutex); > + > + /* > + * Can't cancel work while keeping mutex since the work uses the > + * same mutex. > + */ > + if (rate) { > + /* > + * Simulate missing no-proximity interrupt 50ms after the > + * next expected interrupt time. > + */ > + cancel_delayed_work_sync(&chip->prox_work); > + schedule_delayed_work(&chip->prox_work, > + msecs_to_jiffies(rate + 50)); > + } > + return IRQ_HANDLED; > +} > + > +static ssize_t bhsfh_power_state_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + unsigned long value; > + size_t ret; > + > + if (strict_strtoul(buf, 0, &value)) > + return -EINVAL; > + > + mutex_lock(&chip->mutex); > + if (value) { > + pm_runtime_get_sync(dev); > + > + ret = bhsfh_lux_rate(chip, chip->lux_rate_index); > + ret |= bhsfh_lux_interrupt_control(chip, BHSFH_ENABLE); > + > + if (ret < 0) { > + pm_runtime_put(dev); > + goto leave; > + } > + > + /* This causes interrupt after the next measurement cycle */ > + bhsfh_lux_update_thresholds(chip, BHSFH_LUX_DEF_THRES, > + BHSFH_LUX_DEF_THRES); > + /* Inform that we are waiting for a result from ALS */ > + chip->lux_wait_result = true; > + } else { > + if (!pm_runtime_suspended(dev)) > + pm_runtime_put(dev); Turn this into an else if statement? > + } > + bhsfh_prox_mode_control(chip); > + ret = count; > +leave: > + mutex_unlock(&chip->mutex); > + return ret; > +} > + > +static ssize_t bhsfh_power_state_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + return sprintf(buf, "%d\n", !pm_runtime_suspended(dev)); > +} > + > +static ssize_t bhsfh_lux_result_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + ssize_t ret; > + long timeout; > + > + if (pm_runtime_suspended(dev)) > + return -EIO; /* Chip is not enabled at all */ > + > + timeout = wait_event_interruptible_timeout(chip->wait, > + !chip->lux_wait_result, > + msecs_to_jiffies(BHSFH_TIMEOUT)); > + if (!timeout) > + return -EIO; > + > + mutex_lock(&chip->mutex); > + ret = sprintf(buf, "%d\n", bhsfh_lux_read_result(chip)); > + mutex_unlock(&chip->mutex); > + > + return ret; > +} > + > +static ssize_t bhsfh_lux_range_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + return sprintf(buf, "%d\n", BHSFH_LUX_RANGE); > +} > + > +static ssize_t bhsfh_prox_enable_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + unsigned long value; > + > + if (strict_strtoul(buf, 0, &value)) > + return -EINVAL; > + > + mutex_lock(&chip->mutex); > + if (value) { > + /* Assume no proximity. Sensor will tell real state soon */ > + if (!chip->prox_enable_count) > + chip->prox_data = 0; > + chip->prox_enable_count++; > + } else if (chip->prox_enable_count > 0) { > + chip->prox_enable_count--; > + } else { > + goto leave; > + } > + /* Run control only when chip is power on */ > + if (!pm_runtime_suspended(dev)) > + bhsfh_prox_mode_control(chip); > +leave: > + mutex_unlock(&chip->mutex); > + return count; > +} > + > +static ssize_t bhsfh_prox_enable_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + ssize_t len; > + > + mutex_lock(&chip->mutex); > + len = sprintf(buf, "%d\n", chip->prox_enable_count); > + mutex_unlock(&chip->mutex); > + return len; > +} > + > +static ssize_t bhsfh_prox_result_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + ssize_t ret; > + > + mutex_lock(&chip->mutex); > + if (chip->prox_enable_count && !pm_runtime_suspended(dev)) > + ret = sprintf(buf, "%d\n", chip->prox_data); > + else > + ret = -EIO; > + mutex_unlock(&chip->mutex); > + return ret; > +} > + > +static ssize_t bhsfh_prox_range_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + return sprintf(buf, "%d\n", BHSFH_PROX_RANGE); > +} > + > +static ssize_t bhsfh_get_prox_rate_avail(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + int i; > + int pos = 0; > + for (i = 0; i < ARRAY_SIZE(prox_rates_hz); i++) > + pos += sprintf(buf + pos, "%d ", prox_rates_hz[i]); > + sprintf(buf + pos - 1, "\n"); > + return pos; > +} > + > +static ssize_t bhsfh_get_prox_rate_above(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + return sprintf(buf, "%d\n", prox_rates_hz[chip->prox_rate_threshold]); > +} > + > +static ssize_t bhsfh_get_prox_rate_below(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + return sprintf(buf, "%d\n", prox_rates_hz[chip->prox_rate]); > +} > + > +static ssize_t bhsfh_set_prox_rate_above(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + unsigned long value; > + > + if (strict_strtoul(buf, 0, &value)) > + return -EINVAL; > + > + mutex_lock(&chip->mutex); > + bhsfh_prox_rate_above(chip, value); > + mutex_unlock(&chip->mutex); > + return count; > +} > + > +static ssize_t bhsfh_set_prox_rate_below(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + unsigned long value; > + > + if (strict_strtoul(buf, 0, &value)) > + return -EINVAL; > + > + mutex_lock(&chip->mutex); > + bhsfh_prox_rate_below(chip, value); > + mutex_unlock(&chip->mutex); > + return count; > +} > + > +static ssize_t bhsfh_get_prox_thres(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + return sprintf(buf, "%d\n", chip->prox_threshold); > +} > + > +static ssize_t bhsfh_set_prox_thres(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + unsigned long value; > + int ret; > + > + if (strict_strtoul(buf, 0, &value)) > + return -EINVAL; > + if (value > BHSFH_PROX_RANGE) > + return -EINVAL; > + > + mutex_lock(&chip->mutex); > + chip->prox_threshold = value; > + ret = bhsfh_prox_set_threshold(chip); > + mutex_unlock(&chip->mutex); > + if (ret < 0) > + return ret; > + return count; > +} > + > +static ssize_t bhsfh_prox_persistence_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + > + return snprintf(buf, PAGE_SIZE, "%u\n", chip->prox_persistence); > +} > + > +static ssize_t bhsfh_prox_persistence_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + unsigned long value; > + > + if (strict_strtoul(buf, 0, &value)) > + return -EINVAL; > + > + if (value > BHSFH_PROX_MAX_PERSISTENCE) > + return -EINVAL; > + > + chip->prox_persistence = value; > + > + return len; > +} > + > +static ssize_t bhsfh_prox_abs_thres_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + return snprintf(buf, PAGE_SIZE, "%u\n", chip->prox_abs_thres); > +} > + > +static ssize_t bhsfh_prox_abs_thres_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + unsigned long value; > + > + if (strict_strtoul(buf, 0, &value)) > + return -EINVAL; > + > + if (value > BHSFH_PROX_RANGE) > + return -EINVAL; > + > + chip->prox_abs_thres = value; > + > + return len; > +} > + > +static ssize_t bhsfh_chip_id_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + return sprintf(buf, "%s rev %d\n", chip->chipname, chip->revision); > +} > + > +static ssize_t bhsfh_lux_calib_default_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + return snprintf(buf, PAGE_SIZE, "%u\n", BHSFH_CALIB_SCALER); The PAGE_SIZE limit is rather paranoid for a single integer followed by a new line! Doesn't do any harm though... > +} > + > +static ssize_t bhsfh_lux_calib_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + ssize_t len; > + > + mutex_lock(&chip->mutex); > + len = snprintf(buf, PAGE_SIZE, "%u\n", chip->lux_calib); > + mutex_unlock(&chip->mutex); > + return len; > +} > + > +static ssize_t bhsfh_lux_calib_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + unsigned long value; > + u32 old_calib; > + u32 new_corr; > + > + if (strict_strtoul(buf, 0, &value)) > + return -EINVAL; > + > + mutex_lock(&chip->mutex); > + old_calib = chip->lux_calib; > + chip->lux_calib = value; > + new_corr = bhsfh_get_corr_value(chip); > + if (new_corr == 0) { > + chip->lux_calib = old_calib; > + mutex_unlock(&chip->mutex); > + return -EINVAL; > + } > + chip->lux_corr = new_corr; > + /* Refresh thresholds on HW after changing correction value */ > + bhsfh_lux_update_thresholds(chip, chip->lux_threshold_hi, > + chip->lux_threshold_lo); > + > + mutex_unlock(&chip->mutex); > + > + return len; > +} > + > +static ssize_t bhsfh_get_lux_rate_avail(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + int i; > + int pos = 0; > + for (i = 0; i < ARRAY_SIZE(lux_rates_hz); i++) > + pos += sprintf(buf + pos, "%d ", lux_rates_hz[i]); > + sprintf(buf + pos - 1, "\n"); > + return pos; > +} > + > +static ssize_t bhsfh_get_lux_rate(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + return sprintf(buf, "%d\n", lux_rates_hz[chip->lux_rate_index]); > +} > + > +static ssize_t bhsfh_set_lux_rate(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + unsigned long rate_hz; > + int ret, i; > + > + if (strict_strtoul(buf, 0, &rate_hz)) > + return -EINVAL; > + > + mutex_lock(&chip->mutex); > + Why does the search need to occur under the mutex? > + for (i = 0; i < ARRAY_SIZE(lux_rates_hz); i++) > + if (rate_hz >= lux_rates_hz[i]) > + break; > + > + if (i > BHSFH_ALS_MAX_RATE) > + i = BHSFH_ALS_MAX_RATE; > + > + chip->lux_rate_index = i; > + ret = bhsfh_lux_rate(chip, i); > + mutex_unlock(&chip->mutex); > + > + if (ret < 0) > + return ret; > + > + return count; > +} > + > +static ssize_t bhsfh_get_lux_thresh_above(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + return sprintf(buf, "%d\n", chip->lux_threshold_hi); > +} > + > +static ssize_t bhsfh_get_lux_thresh_below(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + return sprintf(buf, "%d\n", chip->lux_threshold_lo); > +} > + > +static ssize_t bhsfh_set_lux_thresh(struct bhsfh_chip *chip, u16 *target, > + const char *buf) > +{ > + int ret = 0; > + unsigned long thresh; > + > + if (strict_strtoul(buf, 0, &thresh)) > + return -EINVAL; > + > + if (thresh > BHSFH_LUX_RANGE) > + return -EINVAL; > + > + mutex_lock(&chip->mutex); > + *target = thresh; > + /* > + * Don't update values in HW if we are still waiting for > + * first interrupt to come after device handle open call. > + */ > + if (!chip->lux_wait_result) > + ret = bhsfh_lux_update_thresholds(chip, chip->lux_threshold_hi, > + chip->lux_threshold_lo); > + mutex_unlock(&chip->mutex); > + return ret; > + > +} > + > +static ssize_t bhsfh_set_lux_thresh_above(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + int ret = bhsfh_set_lux_thresh(chip, &chip->lux_threshold_hi, buf); > + if (ret < 0) > + return ret; > + return len; > +} > + > +static ssize_t bhsfh_set_lux_thresh_below(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct bhsfh_chip *chip = dev_get_drvdata(dev); > + int ret = bhsfh_set_lux_thresh(chip, &chip->lux_threshold_lo, buf); > + if (ret < 0) > + return ret; > + return len; > +} > + > +static DEVICE_ATTR(prox0_raw_en, S_IRUGO | S_IWUSR, bhsfh_prox_enable_show, > + bhsfh_prox_enable_store); > +static DEVICE_ATTR(prox0_thresh_above1_value, S_IRUGO | S_IWUSR, > + bhsfh_prox_abs_thres_show, > + bhsfh_prox_abs_thres_store); > +static DEVICE_ATTR(prox0_thresh_above0_value, S_IRUGO | S_IWUSR, > + bhsfh_get_prox_thres, > + bhsfh_set_prox_thres); > +static DEVICE_ATTR(prox0_raw, S_IRUGO, bhsfh_prox_result_show, NULL); > +static DEVICE_ATTR(prox0_sensor_range, S_IRUGO, bhsfh_prox_range_show, NULL); > +static DEVICE_ATTR(prox0_thresh_above_count, S_IRUGO | S_IWUSR, > + bhsfh_prox_persistence_show, > + bhsfh_prox_persistence_store); > +static DEVICE_ATTR(prox0_rate_above, S_IRUGO | S_IWUSR, > + bhsfh_get_prox_rate_above, > + bhsfh_set_prox_rate_above); > +static DEVICE_ATTR(prox0_rate_below, S_IRUGO | S_IWUSR, > + bhsfh_get_prox_rate_below, > + bhsfh_set_prox_rate_below); > +static DEVICE_ATTR(prox0_rate_avail, S_IRUGO, bhsfh_get_prox_rate_avail, NULL); > + > +static DEVICE_ATTR(lux0_calibscale, S_IRUGO | S_IWUSR, bhsfh_lux_calib_show, > + bhsfh_lux_calib_store); > +static DEVICE_ATTR(lux0_calibscale_default, S_IRUGO, > + bhsfh_lux_calib_default_show, > + NULL); > +static DEVICE_ATTR(lux0_input, S_IRUGO, bhsfh_lux_result_show, NULL); > +static DEVICE_ATTR(lux0_sensor_range, S_IRUGO, bhsfh_lux_range_show, NULL); > +static DEVICE_ATTR(lux0_rate, S_IRUGO | S_IWUSR, bhsfh_get_lux_rate, > + bhsfh_set_lux_rate); > +static DEVICE_ATTR(lux0_rate_avail, S_IRUGO, bhsfh_get_lux_rate_avail, NULL); > +static DEVICE_ATTR(lux0_thresh_above_value, S_IRUGO | S_IWUSR, > + bhsfh_get_lux_thresh_above, > + bhsfh_set_lux_thresh_above); > +static DEVICE_ATTR(lux0_thresh_below_value, S_IRUGO | S_IWUSR, > + bhsfh_get_lux_thresh_below, > + bhsfh_set_lux_thresh_below); > +static DEVICE_ATTR(chip_id, S_IRUGO, bhsfh_chip_id_show, NULL); > +static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, bhsfh_power_state_show, > + bhsfh_power_state_store); > + > + > +static struct attribute *sysfs_attrs[] = { > + &dev_attr_lux0_calibscale.attr, > + &dev_attr_lux0_calibscale_default.attr, > + &dev_attr_lux0_input.attr, > + &dev_attr_lux0_sensor_range.attr, > + &dev_attr_lux0_rate.attr, > + &dev_attr_lux0_rate_avail.attr, > + &dev_attr_lux0_thresh_above_value.attr, > + &dev_attr_lux0_thresh_below_value.attr, > + &dev_attr_prox0_raw.attr, > + &dev_attr_prox0_sensor_range.attr, > + &dev_attr_prox0_raw_en.attr, > + &dev_attr_prox0_thresh_above_count.attr, > + &dev_attr_prox0_rate_above.attr, > + &dev_attr_prox0_rate_below.attr, > + &dev_attr_prox0_rate_avail.attr, > + &dev_attr_prox0_thresh_above0_value.attr, > + &dev_attr_prox0_thresh_above1_value.attr, > + &dev_attr_chip_id.attr, > + &dev_attr_power_state.attr, > + NULL > +}; > + > +static struct attribute_group bhsfh_attribute_group = { > + .attrs = sysfs_attrs > +}; > + > +static int __devinit bhsfh_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct bhsfh_chip *chip; > + int err; > + > + chip = kzalloc(sizeof *chip, GFP_KERNEL); > + if (!chip) > + return -ENOMEM; > + > + i2c_set_clientdata(client, chip); > + chip->client = client; > + > + mutex_init(&chip->mutex); > + init_waitqueue_head(&chip->wait); > + INIT_DELAYED_WORK(&chip->prox_work, bhsfh_prox_work); > + > + if (client->dev.platform_data == NULL) { > + dev_err(&client->dev, "platform data is mandatory\n"); > + err = -EINVAL; > + goto fail1; > + } > + > + chip->pdata = client->dev.platform_data; > + chip->lux_calib = BHSFH_LUX_NEUTRAL_CALIB_VALUE; > + chip->lux_rate_index = BHSFH_LUX_DEFAULT_RATE; > + chip->lux_threshold_lo = BHSFH_LUX_DEF_THRES; > + chip->lux_threshold_hi = BHSFH_LUX_DEF_THRES; > + > + if (chip->pdata->glass_attenuation == 0) > + chip->lux_ga = BHFSH_NEUTRAL_GA; > + else > + chip->lux_ga = chip->pdata->glass_attenuation; > + > + chip->prox_threshold = BHSFH_PROX_DEF_THRES; > + chip->prox_led = chip->pdata->led_def_curr; > + chip->prox_abs_thres = BHSFH_PROX_DEF_ABS_THRES; > + chip->prox_persistence = BHSFH_DEFAULT_PERSISTENCE; > + chip->prox_data = 0; > + > + > + bhsfh_prox_rate_below(chip, BHSFH_PROX_DEFAULT_RATE); > + bhsfh_prox_rate_above(chip, BHSFH_PROX_DEF_RATE_THRESH); > + > + chip->regs[0].supply = reg_vcc; > + chip->regs[1].supply = reg_vleds; > + > + err = regulator_bulk_get(&client->dev, > + ARRAY_SIZE(chip->regs), chip->regs); > + if (err < 0) { > + dev_err(&client->dev, "Cannot get regulators\n"); > + goto fail1; > + } > + > + err = regulator_bulk_enable(ARRAY_SIZE(chip->regs), > + chip->regs); > + if (err < 0) { > + dev_err(&client->dev, "Cannot enable regulators\n"); > + goto fail2; > + } > + > + usleep_range(BHSFH_STARTUP_DELAY, BHSFH_STARTUP_DELAY * 2); > + err = bhsfh_detect(chip); > + if (err < 0) > + goto fail3; > + > + /* Start chip */ > + bhsfh_chip_on(chip); > + pm_runtime_set_active(&client->dev); > + pm_runtime_enable(&client->dev); > + > + chip->lux_corr = bhsfh_get_corr_value(chip); > + if (chip->lux_corr == 0) { > + dev_err(&client->dev, "Improper correction values\n"); > + err = -EINVAL; > + goto fail3; > + } > + > + if (chip->pdata->setup_resources) { > + err = chip->pdata->setup_resources(); > + if (err) { > + err = -EINVAL; > + goto fail3; > + } > + } > + > + err = sysfs_create_group(&chip->client->dev.kobj, > + &bhsfh_attribute_group); > + if (err < 0) { > + dev_err(&chip->client->dev, "Sysfs registration failed\n"); > + goto fail4; > + } > + > + /* > + * Chip needs level triggered interrupt to work. However, > + * level triggering doesn't work always correctly with power > + * management. Select both > + */ > + err = request_threaded_irq(client->irq, NULL, > + bhsfh_irq, > + IRQF_TRIGGER_FALLING | IRQF_ONESHOT | > + IRQF_TRIGGER_LOW, > + "bhsfh", chip); > + if (err) { > + dev_err(&client->dev, "could not get IRQ %d\n", > + client->irq); > + goto fail5; > + } > + regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs); > + return err; > +fail5: > + sysfs_remove_group(&chip->client->dev.kobj, > + &bhsfh_attribute_group); > +fail4: > + if (chip->pdata->release_resources) > + chip->pdata->release_resources(); > +fail3: > + regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs); > +fail2: > + regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs); > +fail1: > + kfree(chip); > + return err; > +} > + > +static int __devexit bhsfh_remove(struct i2c_client *client) > +{ > + struct bhsfh_chip *chip = i2c_get_clientdata(client); > + > + free_irq(client->irq, chip); > + > + sysfs_remove_group(&chip->client->dev.kobj, > + &bhsfh_attribute_group); > + > + if (chip->pdata->release_resources) > + chip->pdata->release_resources(); > + > + cancel_delayed_work_sync(&chip->prox_work); > + > + if (!pm_runtime_suspended(&client->dev)) > + bhsfh_chip_off(chip); > + > + pm_runtime_disable(&client->dev); > + pm_runtime_set_suspended(&client->dev); > + > + regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs); > + kfree(chip); > + return 0; > +} > + > +#ifdef CONFIG_PM > +static int bhsfh_suspend(struct device *dev) > +{ > + struct i2c_client *client = container_of(dev, struct i2c_client, dev); > + struct bhsfh_chip *chip = i2c_get_clientdata(client); > + > + bhsfh_chip_off(chip); > + > + return 0; > +} > + > +static int bhsfh_resume(struct device *dev) > +{ > + struct i2c_client *client = container_of(dev, struct i2c_client, dev); > + struct bhsfh_chip *chip = i2c_get_clientdata(client); > + int ret = 0; > + > + bhsfh_chip_on(chip); > + > + if (!pm_runtime_suspended(dev)) { > + /* > + * If we were enabled at suspend time, it is expected > + * everything works nice and smoothly > + */ > + ret = bhsfh_lux_rate(chip, chip->lux_rate_index); > + ret |= bhsfh_lux_interrupt_control(chip, BHSFH_ENABLE); > + > + /* This causes interrupt after the next measurement cycle */ > + bhsfh_lux_update_thresholds(chip, BHSFH_LUX_DEF_THRES, > + BHSFH_LUX_DEF_THRES); > + /* Inform that we are waiting for a result from ALS */ > + chip->lux_wait_result = true; > + bhsfh_prox_mode_control(chip); > + } > + return ret; > +} > + > +#else > +#define bhsfh_suspend NULL > +#define bhsfh_shutdown NULL > +#define bhsfh_resume NULL > +#endif > + > +#ifdef CONFIG_PM_RUNTIME > +static int bhsfh_runtime_suspend(struct device *dev) > +{ > + struct i2c_client *client = container_of(dev, struct i2c_client, dev); > + struct bhsfh_chip *chip = i2c_get_clientdata(client); > + > + bhsfh_chip_off(chip); > + > + return 0; > +} > + > +static int bhsfh_runtime_resume(struct device *dev) > +{ > + struct i2c_client *client = container_of(dev, struct i2c_client, dev); > + struct bhsfh_chip *chip = i2c_get_clientdata(client); > + > + bhsfh_chip_on(chip); > + > + return 0; > +} > +#endif > + > +static const struct i2c_device_id bhsfh_id[] = { > + {"bh1770glc", 0 }, > + {"sfh7770", 0 }, > + {} > +}; > + > +MODULE_DEVICE_TABLE(i2c, bhsfh_id); > + > +static const struct dev_pm_ops bhsfh_pm_ops = { > + SET_SYSTEM_SLEEP_PM_OPS(bhsfh_suspend, bhsfh_resume) > + SET_RUNTIME_PM_OPS(bhsfh_runtime_suspend, bhsfh_runtime_resume, NULL) > +}; > + > +static struct i2c_driver bhsfh_driver = { > + .driver = { > + .name = "bhsfh", > + .owner = THIS_MODULE, > + .pm = &bhsfh_pm_ops, > + }, > + .probe = bhsfh_probe, > + .remove = __devexit_p(bhsfh_remove), > + .id_table = bhsfh_id, > +}; > + > +static int __init bhsfh_init(void) > +{ > + return i2c_add_driver(&bhsfh_driver); > +} > + > +static void __exit bhsfh_exit(void) > +{ > + i2c_del_driver(&bhsfh_driver); > +} > + > +MODULE_DESCRIPTION("BH1770GLC / SFH7770 combined ALS and proximity sensor"); > +MODULE_AUTHOR("Samu Onkalo, Nokia Corporation"); > +MODULE_LICENSE("GPL v2"); > + > +module_init(bhsfh_init); > +module_exit(bhsfh_exit); > diff --git a/include/linux/i2c/bhsfh.h b/include/linux/i2c/bhsfh.h > new file mode 100644 > index 0000000..a19e791 > --- /dev/null > +++ b/include/linux/i2c/bhsfh.h > @@ -0,0 +1,42 @@ > +/* > + * This file is part of the ROHM BH1770GLC / OSRAM SFH7770 sensor driver. > + * Chip is combined proximity and ambient light sensor. > + * > + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). > + * > + * Contact: Samu Onkalo <samu.p.onkalo@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + * > + */ > + > +#ifndef __BHSFH_H__ > +#define __BHSFH_H__ > + > +struct bhsfh_platform_data { > +#define BHSFH_LED_5mA 0 > +#define BHSFH_LED_10mA 1 > +#define BHSFH_LED_20mA 2 > +#define BHSFH_LED_50mA 3 > +#define BHSFH_LED_100mA 4 > +#define BHSFH_LED_150mA 5 > +#define BHSFH_LED_200mA 6 > + __u8 led_def_curr; > +#define BHFSH_NEUTRAL_GA 16384 /* 16384 / 16384 = 1 */ > + __u32 glass_attenuation; > + int (*setup_resources)(void); > + int (*release_resources)(void); > +}; > +#endif ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCHv2 1/5] misc: Driver for bh1770glc / sfh7770 ALS and proximity sensor 2010-10-08 14:35 ` Jonathan Cameron @ 2010-10-11 6:16 ` Onkalo Samu 2010-10-11 13:35 ` Jonathan Cameron 0 siblings, 1 reply; 16+ messages in thread From: Onkalo Samu @ 2010-10-11 6:16 UTC (permalink / raw) To: ext Jonathan Cameron Cc: linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, alan@lxorguk.ukuu.org.uk I have a question about one of your comment On Fri, 2010-10-08 at 16:35 +0200, ext Jonathan Cameron wrote: > On 10/08/10 14:42, Samu Onkalo wrote: > > This is a driver for ROHM BH1770GLC and OSRAM SFH7770 combined > > ALS and proximity sensor. > > > > Interface is sysfs based. The driver uses interrupts to provide new data. > > The driver supports pm_runtime and regulator frameworks. > > > > See Documentation/misc-devices/bhsfh.txt for details > > Couple of nitpicks / formatting suggestions inline. > > > > > +/* chip->mutex is kept when this is called */ > > +static int bhsfh_lux_update_thresholds(struct bhsfh_chip *chip, > > + u16 threshold_hi, u16 threshold_lo) > > +{ > > + u8 data[4]; > > u8 data[4] = { threshold_hi, > threshold_hi >> 8, > threshold_lo, > threshold_low >> 8}; > and loose the below will give same result. What do you mean by that? Threshold_lo / threshold_hi parameters can be modified before they are stored to HW. > > + int ret; > > + > > + /* sysfs may call this when the chip is powered off */ > > + if (pm_runtime_suspended(&chip->client->dev)) > > + return 0; > > + > > + /* > > + * Compensate threshold values with the correction factors if not > > + * set to minimum or maximum. > > + * Min & max values disables interrupts. > > + */ > > + if (threshold_hi != BHSFH_LUX_RANGE && threshold_hi != 0) > > + threshold_hi = bhsfh_lux_adjusted_to_raw(chip, threshold_hi); > > + > > + if (threshold_lo != BHSFH_LUX_RANGE && threshold_lo != 0) > > + threshold_lo = bhsfh_lux_adjusted_to_raw(chip, threshold_lo); Code above can modify values. > > + > > + if (chip->lux_thres_hi_onchip == threshold_hi && > > + chip->lux_thres_lo_onchip == threshold_lo) > > + return 0; > > + > > + chip->lux_thres_hi_onchip = threshold_hi; > > + chip->lux_thres_lo_onchip = threshold_lo; > > + > > + data[0] = threshold_hi; > > + data[1] = threshold_hi >> 8; > > + data[2] = threshold_lo; > > + data[3] = threshold_lo >> 8; > > + > > + ret = i2c_smbus_write_i2c_block_data(chip->client, > > + BHSFH_ALS_TH_UP_0, > > + ARRAY_SIZE(data), > > + data); > > + return ret; > > +} > > + ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCHv2 1/5] misc: Driver for bh1770glc / sfh7770 ALS and proximity sensor 2010-10-11 6:16 ` Onkalo Samu @ 2010-10-11 13:35 ` Jonathan Cameron 0 siblings, 0 replies; 16+ messages in thread From: Jonathan Cameron @ 2010-10-11 13:35 UTC (permalink / raw) To: samu.p.onkalo Cc: linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, alan@lxorguk.ukuu.org.uk On 10/11/10 07:16, Onkalo Samu wrote: > > I have a question about one of your comment > > On Fri, 2010-10-08 at 16:35 +0200, ext Jonathan Cameron wrote: >> On 10/08/10 14:42, Samu Onkalo wrote: >>> This is a driver for ROHM BH1770GLC and OSRAM SFH7770 combined >>> ALS and proximity sensor. >>> >>> Interface is sysfs based. The driver uses interrupts to provide new data. >>> The driver supports pm_runtime and regulator frameworks. >>> >>> See Documentation/misc-devices/bhsfh.txt for details >> >> Couple of nitpicks / formatting suggestions inline. >> >>> >>> +/* chip->mutex is kept when this is called */ >>> +static int bhsfh_lux_update_thresholds(struct bhsfh_chip *chip, >>> + u16 threshold_hi, u16 threshold_lo) >>> +{ >>> + u8 data[4]; >> >> u8 data[4] = { threshold_hi, >> threshold_hi >> 8, >> threshold_lo, >> threshold_low >> 8}; >> and loose the below will give same result. > > What do you mean by that? Threshold_lo / threshold_hi parameters can be > modified before they are stored to HW. > >>> + int ret; >>> + >>> + /* sysfs may call this when the chip is powered off */ >>> + if (pm_runtime_suspended(&chip->client->dev)) >>> + return 0; >>> + >>> + /* >>> + * Compensate threshold values with the correction factors if not >>> + * set to minimum or maximum. >>> + * Min & max values disables interrupts. >>> + */ >>> + if (threshold_hi != BHSFH_LUX_RANGE && threshold_hi != 0) >>> + threshold_hi = bhsfh_lux_adjusted_to_raw(chip, threshold_hi); >>> + >>> + if (threshold_lo != BHSFH_LUX_RANGE && threshold_lo != 0) >>> + threshold_lo = bhsfh_lux_adjusted_to_raw(chip, threshold_lo); > > Code above can modify values. ah. Sorry, I was clearly half asleep when I reviewed this! > >>> + >>> + if (chip->lux_thres_hi_onchip == threshold_hi && >>> + chip->lux_thres_lo_onchip == threshold_lo) >>> + return 0; >>> + >>> + chip->lux_thres_hi_onchip = threshold_hi; >>> + chip->lux_thres_lo_onchip = threshold_lo; >>> + >>> + data[0] = threshold_hi; >>> + data[1] = threshold_hi >> 8; >>> + data[2] = threshold_lo; >>> + data[3] = threshold_lo >> 8; >>> + >>> + ret = i2c_smbus_write_i2c_block_data(chip->client, >>> + BHSFH_ALS_TH_UP_0, >>> + ARRAY_SIZE(data), >>> + data); >>> + return ret; >>> +} >>> + > > > > ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCHv2 2/5] misc: update bhsfh driver to Kconfig and Makefile 2010-10-08 13:41 [PATCHv2 0/5] BH1770GLC, SFH7770 and APDS990x als / proximity sensor drivers Samu Onkalo 2010-10-08 13:42 ` [PATCHv2 1/5] misc: Driver for bh1770glc / sfh7770 ALS and proximity sensor Samu Onkalo @ 2010-10-08 13:42 ` Samu Onkalo 2010-10-08 14:25 ` Jonathan Cameron 2010-10-09 23:43 ` Randy Dunlap 2010-10-08 13:42 ` [PATCHv2 3/5] misc: Driver for APDS990X ALS and proximity sensors Samu Onkalo ` (2 subsequent siblings) 4 siblings, 2 replies; 16+ messages in thread From: Samu Onkalo @ 2010-10-08 13:42 UTC (permalink / raw) To: linux-i2c, linux-kernel, jic23, alan Compilation support for bhsfh driver Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com> --- drivers/misc/Kconfig | 10 ++++++++++ drivers/misc/Makefile | 1 + 2 files changed, 11 insertions(+), 0 deletions(-) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index b743312..f2e8065 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -314,6 +314,16 @@ config SENSORS_BH1780 This driver can also be built as a module. If so, the module will be called bh1780gli. +config SENSORS_BHSFH + tristate "BH1770GLC / SFH7770 combined ALS / Proximity sensor"; + depends on I2C + ---help--- + Say Y here if you want to build a driver for BH1770GLC / SFH7770 + combined ambient light and proximity sensor chip + + To compile this driver as a module, choose M here: the + module will be called bhsfh. If unsure, say N here. + config HMC6352 tristate "Honeywell HMC6352 compass" depends on I2C diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 42eab95..fd5a4b7 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_TIFM_CORE) += tifm_core.o obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o obj-$(CONFIG_PHANTOM) += phantom.o obj-$(CONFIG_SENSORS_BH1780) += bh1780gli.o +obj-$(CONFIG_SENSORS_BHSFH) += bhsfh.o obj-$(CONFIG_SGI_IOC4) += ioc4.o obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o obj-$(CONFIG_KGDB_TESTS) += kgdbts.o -- 1.6.0.4 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCHv2 2/5] misc: update bhsfh driver to Kconfig and Makefile 2010-10-08 13:42 ` [PATCHv2 2/5] misc: update bhsfh driver to Kconfig and Makefile Samu Onkalo @ 2010-10-08 14:25 ` Jonathan Cameron 2010-10-11 6:17 ` Onkalo Samu 2010-10-09 23:43 ` Randy Dunlap 1 sibling, 1 reply; 16+ messages in thread From: Jonathan Cameron @ 2010-10-08 14:25 UTC (permalink / raw) To: Samu Onkalo; +Cc: linux-i2c, linux-kernel, alan On 10/08/10 14:42, Samu Onkalo wrote: > Compilation support for bhsfh driver > > Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com> > --- > drivers/misc/Kconfig | 10 ++++++++++ > drivers/misc/Makefile | 1 + > 2 files changed, 11 insertions(+), 0 deletions(-) > > diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig > index b743312..f2e8065 100644 > --- a/drivers/misc/Kconfig > +++ b/drivers/misc/Kconfig > @@ -314,6 +314,16 @@ config SENSORS_BH1780 > This driver can also be built as a module. If so, the module > will be called bh1780gli. > > +config SENSORS_BHSFH > + tristate "BH1770GLC / SFH7770 combined ALS / Proximity sensor"; > + depends on I2C > + ---help--- > + Say Y here if you want to build a driver for BH1770GLC / SFH7770 > + combined ambient light and proximity sensor chip > + > + To compile this driver as a module, choose M here: the > + module will be called bhsfh. If unsure, say N here. > + Really not keen on the naming. The above comment made me think this was a single chip integrating both of the parts. Reading the code makes it clear that they just share an interface. So please name it after one of them. Lots of reasons not to invent combined names like this. * non obvious thing to grep for. * 3rd part comes along with a different name. Do you change the driver name? So best bet is always just to pick a supported part and name the driver after that. The device table stuff and clear descriptions in kconfig will make it obvious what the part supports. > config HMC6352 > tristate "Honeywell HMC6352 compass" > depends on I2C > diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile > index 42eab95..fd5a4b7 100644 > --- a/drivers/misc/Makefile > +++ b/drivers/misc/Makefile > @@ -16,6 +16,7 @@ obj-$(CONFIG_TIFM_CORE) += tifm_core.o > obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o > obj-$(CONFIG_PHANTOM) += phantom.o > obj-$(CONFIG_SENSORS_BH1780) += bh1780gli.o > +obj-$(CONFIG_SENSORS_BHSFH) += bhsfh.o > obj-$(CONFIG_SGI_IOC4) += ioc4.o > obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o > obj-$(CONFIG_KGDB_TESTS) += kgdbts.o ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCHv2 2/5] misc: update bhsfh driver to Kconfig and Makefile 2010-10-08 14:25 ` Jonathan Cameron @ 2010-10-11 6:17 ` Onkalo Samu 2010-10-11 13:36 ` Jonathan Cameron 0 siblings, 1 reply; 16+ messages in thread From: Onkalo Samu @ 2010-10-11 6:17 UTC (permalink / raw) To: ext Jonathan Cameron Cc: linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, alan@lxorguk.ukuu.org.uk On Fri, 2010-10-08 at 16:25 +0200, ext Jonathan Cameron wrote: > On 10/08/10 14:42, Samu Onkalo wrote: > > Compilation support for bhsfh driver > > > > Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com> > > --- > > drivers/misc/Kconfig | 10 ++++++++++ > > drivers/misc/Makefile | 1 + > > 2 files changed, 11 insertions(+), 0 deletions(-) > > > > diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig > > index b743312..f2e8065 100644 > > --- a/drivers/misc/Kconfig > > +++ b/drivers/misc/Kconfig > > @@ -314,6 +314,16 @@ config SENSORS_BH1780 > > This driver can also be built as a module. If so, the module > > will be called bh1780gli. > > > > +config SENSORS_BHSFH > > + tristate "BH1770GLC / SFH7770 combined ALS / Proximity sensor"; > > + depends on I2C > > + ---help--- > > + Say Y here if you want to build a driver for BH1770GLC / SFH7770 > > + combined ambient light and proximity sensor chip > > + > > + To compile this driver as a module, choose M here: the > > + module will be called bhsfh. If unsure, say N here. > > + > Really not keen on the naming. The above comment made me think this was > a single chip integrating both of the parts. Reading the code makes it clear > that they just share an interface. So please name it after one of them. > Lots of reasons not to invent combined names like this. > * non obvious thing to grep for. > * 3rd part comes along with a different name. Do you change the driver name? > > So best bet is always just to pick a supported part and name the driver after > that. The device table stuff and clear descriptions in kconfig will make > it obvious what the part supports. ok, bhsfh.* > bh1770glc.* What do you think, is it ok to still keep BHSFH in definitions and in function names? > > config HMC6352 > > tristate "Honeywell HMC6352 compass" > > depends on I2C > > diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile > > index 42eab95..fd5a4b7 100644 > > --- a/drivers/misc/Makefile > > +++ b/drivers/misc/Makefile > > @@ -16,6 +16,7 @@ obj-$(CONFIG_TIFM_CORE) += tifm_core.o > > obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o > > obj-$(CONFIG_PHANTOM) += phantom.o > > obj-$(CONFIG_SENSORS_BH1780) += bh1780gli.o > > +obj-$(CONFIG_SENSORS_BHSFH) += bhsfh.o > > obj-$(CONFIG_SGI_IOC4) += ioc4.o > > obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o > > obj-$(CONFIG_KGDB_TESTS) += kgdbts.o > ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCHv2 2/5] misc: update bhsfh driver to Kconfig and Makefile 2010-10-11 6:17 ` Onkalo Samu @ 2010-10-11 13:36 ` Jonathan Cameron 0 siblings, 0 replies; 16+ messages in thread From: Jonathan Cameron @ 2010-10-11 13:36 UTC (permalink / raw) To: samu.p.onkalo Cc: linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, alan@lxorguk.ukuu.org.uk On 10/11/10 07:17, Onkalo Samu wrote: > On Fri, 2010-10-08 at 16:25 +0200, ext Jonathan Cameron wrote: >> On 10/08/10 14:42, Samu Onkalo wrote: >>> Compilation support for bhsfh driver >>> >>> Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com> >>> --- >>> drivers/misc/Kconfig | 10 ++++++++++ >>> drivers/misc/Makefile | 1 + >>> 2 files changed, 11 insertions(+), 0 deletions(-) >>> >>> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig >>> index b743312..f2e8065 100644 >>> --- a/drivers/misc/Kconfig >>> +++ b/drivers/misc/Kconfig >>> @@ -314,6 +314,16 @@ config SENSORS_BH1780 >>> This driver can also be built as a module. If so, the module >>> will be called bh1780gli. >>> >>> +config SENSORS_BHSFH >>> + tristate "BH1770GLC / SFH7770 combined ALS / Proximity sensor"; >>> + depends on I2C >>> + ---help--- >>> + Say Y here if you want to build a driver for BH1770GLC / SFH7770 >>> + combined ambient light and proximity sensor chip >>> + >>> + To compile this driver as a module, choose M here: the >>> + module will be called bhsfh. If unsure, say N here. >>> + >> Really not keen on the naming. The above comment made me think this was >> a single chip integrating both of the parts. Reading the code makes it clear >> that they just share an interface. So please name it after one of them. >> Lots of reasons not to invent combined names like this. >> * non obvious thing to grep for. >> * 3rd part comes along with a different name. Do you change the driver name? >> >> So best bet is always just to pick a supported part and name the driver after >> that. The device table stuff and clear descriptions in kconfig will make >> it obvious what the part supports. > > ok, bhsfh.* > bh1770glc.* > What do you think, is it ok to still keep BHSFH in definitions and in > function names? I'd say it's fine to leave function names alone. If anyone else cares, they can post a patch changing the lot. Jonathan > > > >>> config HMC6352 >>> tristate "Honeywell HMC6352 compass" >>> depends on I2C >>> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile >>> index 42eab95..fd5a4b7 100644 >>> --- a/drivers/misc/Makefile >>> +++ b/drivers/misc/Makefile >>> @@ -16,6 +16,7 @@ obj-$(CONFIG_TIFM_CORE) += tifm_core.o >>> obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o >>> obj-$(CONFIG_PHANTOM) += phantom.o >>> obj-$(CONFIG_SENSORS_BH1780) += bh1780gli.o >>> +obj-$(CONFIG_SENSORS_BHSFH) += bhsfh.o >>> obj-$(CONFIG_SGI_IOC4) += ioc4.o >>> obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o >>> obj-$(CONFIG_KGDB_TESTS) += kgdbts.o >> > > ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCHv2 2/5] misc: update bhsfh driver to Kconfig and Makefile 2010-10-08 13:42 ` [PATCHv2 2/5] misc: update bhsfh driver to Kconfig and Makefile Samu Onkalo 2010-10-08 14:25 ` Jonathan Cameron @ 2010-10-09 23:43 ` Randy Dunlap 1 sibling, 0 replies; 16+ messages in thread From: Randy Dunlap @ 2010-10-09 23:43 UTC (permalink / raw) To: Samu Onkalo; +Cc: linux-i2c, linux-kernel, jic23, alan On Fri, 8 Oct 2010 16:42:01 +0300 Samu Onkalo wrote: > Compilation support for bhsfh driver > > Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com> > --- > drivers/misc/Kconfig | 10 ++++++++++ > drivers/misc/Makefile | 1 + > 2 files changed, 11 insertions(+), 0 deletions(-) > > diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig > index b743312..f2e8065 100644 > --- a/drivers/misc/Kconfig > +++ b/drivers/misc/Kconfig > @@ -314,6 +314,16 @@ config SENSORS_BH1780 > This driver can also be built as a module. If so, the module > will be called bh1780gli. > > +config SENSORS_BHSFH > + tristate "BH1770GLC / SFH7770 combined ALS / Proximity sensor"; Please drop the trailing ';'. It may work, but it's quite "different." > + depends on I2C > + ---help--- > + Say Y here if you want to build a driver for BH1770GLC / SFH7770 > + combined ambient light and proximity sensor chip end above sentence with period ('.'). > + > + To compile this driver as a module, choose M here: the > + module will be called bhsfh. If unsure, say N here. > + > config HMC6352 > tristate "Honeywell HMC6352 compass" > depends on I2C > diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile > index 42eab95..fd5a4b7 100644 > --- a/drivers/misc/Makefile > +++ b/drivers/misc/Makefile > @@ -16,6 +16,7 @@ obj-$(CONFIG_TIFM_CORE) += tifm_core.o > obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o > obj-$(CONFIG_PHANTOM) += phantom.o > obj-$(CONFIG_SENSORS_BH1780) += bh1780gli.o > +obj-$(CONFIG_SENSORS_BHSFH) += bhsfh.o > obj-$(CONFIG_SGI_IOC4) += ioc4.o > obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o > obj-$(CONFIG_KGDB_TESTS) += kgdbts.o > -- --- ~Randy *** Remember to use Documentation/SubmitChecklist when testing your code *** ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCHv2 3/5] misc: Driver for APDS990X ALS and proximity sensors 2010-10-08 13:41 [PATCHv2 0/5] BH1770GLC, SFH7770 and APDS990x als / proximity sensor drivers Samu Onkalo 2010-10-08 13:42 ` [PATCHv2 1/5] misc: Driver for bh1770glc / sfh7770 ALS and proximity sensor Samu Onkalo 2010-10-08 13:42 ` [PATCHv2 2/5] misc: update bhsfh driver to Kconfig and Makefile Samu Onkalo @ 2010-10-08 13:42 ` Samu Onkalo 2010-10-08 14:46 ` Jonathan Cameron 2010-10-08 13:42 ` [PATCHv2 4/5] misc: update apds990x driver to Kconfig and Makefile Samu Onkalo 2010-10-08 13:42 ` [PATCHv2 5/5] Documentation: Short descriptions for bhsfh and apds990x drivers Samu Onkalo 4 siblings, 1 reply; 16+ messages in thread From: Samu Onkalo @ 2010-10-08 13:42 UTC (permalink / raw) To: linux-i2c, linux-kernel, jic23, alan This is a driver for Avago APDS990X combined ALS and proximity sensor. Interface is sysfs based. The driver uses interrupts to provide new data. The driver supports pm_runtime and regulator frameworks. See Documentation/misc-devices/apds990x.txt for details Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com> --- drivers/misc/apds990x.c | 1303 ++++++++++++++++++++++++++++++++++++++++++ include/linux/i2c/apds990x.h | 58 ++ 2 files changed, 1361 insertions(+), 0 deletions(-) create mode 100644 drivers/misc/apds990x.c create mode 100644 include/linux/i2c/apds990x.h diff --git a/drivers/misc/apds990x.c b/drivers/misc/apds990x.c new file mode 100644 index 0000000..658c5c4 --- /dev/null +++ b/drivers/misc/apds990x.c @@ -0,0 +1,1303 @@ +/* + * This file is part of the APDS990x sensor driver. + * Chip is combined proximity and ambient light sensor. + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * Contact: Samu Onkalo <samu.p.onkalo@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_runtime.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/slab.h> +#include <linux/i2c/apds990x.h> + +/* Register map */ +#define APDS990X_ENABLE 0x00 /* Enable of states and interrupts */ +#define APDS990X_ATIME 0x01 /* ALS ADC time */ +#define APDS990X_PTIME 0x02 /* Proximity ADC time */ +#define APDS990X_WTIME 0x03 /* Wait time */ +#define APDS990X_AILTL 0x04 /* ALS interrupt low threshold low byte */ +#define APDS990X_AILTH 0x05 /* ALS interrupt low threshold hi byte */ +#define APDS990X_AIHTL 0x06 /* ALS interrupt hi threshold low byte */ +#define APDS990X_AIHTH 0x07 /* ALS interrupt hi threshold hi byte */ +#define APDS990X_PILTL 0x08 /* Proximity interrupt low threshold low byte */ +#define APDS990X_PILTH 0x09 /* Proximity interrupt low threshold hi byte */ +#define APDS990X_PIHTL 0x0a /* Proximity interrupt hi threshold low byte */ +#define APDS990X_PIHTH 0x0b /* Proximity interrupt hi threshold hi byte */ +#define APDS990X_PERS 0x0c /* Interrupt persistence filters */ +#define APDS990X_CONFIG 0x0d /* Configuration */ +#define APDS990X_PPCOUNT 0x0e /* Proximity pulse count */ +#define APDS990X_CONTROL 0x0f /* Gain control register */ +#define APDS990X_REV 0x11 /* Revision Number */ +#define APDS990X_ID 0x12 /* Device ID */ +#define APDS990X_STATUS 0x13 /* Device status */ +#define APDS990X_CDATAL 0x14 /* Clear ADC low data register */ +#define APDS990X_CDATAH 0x15 /* Clear ADC high data register */ +#define APDS990X_IRDATAL 0x16 /* IR ADC low data register */ +#define APDS990X_IRDATAH 0x17 /* IR ADC high data register */ +#define APDS990X_PDATAL 0x18 /* Proximity ADC low data register */ +#define APDS990X_PDATAH 0x19 /* Proximity ADC high data register */ + +/* Control */ +#define APDS990X_MAX_AGAIN 3 + +/* Enable register */ +#define APDS990X_EN_PIEN (0x1 << 5) +#define APDS990X_EN_AIEN (0x1 << 4) +#define APDS990X_EN_WEN (0x1 << 3) +#define APDS990X_EN_PEN (0x1 << 2) +#define APDS990X_EN_AEN (0x1 << 1) +#define APDS990X_EN_PON (0x1 << 0) +#define APDS990X_EN_DISABLE_ALL 0 + +/* Status register */ +#define APDS990X_ST_PINT (0x1 << 5) +#define APDS990X_ST_AINT (0x1 << 4) + +/* I2C access types */ +#define APDS990x_CMD_TYPE_MASK (0x03 << 5) +#define APDS990x_CMD_TYPE_RB (0x00 << 5) /* Repeated byte */ +#define APDS990x_CMD_TYPE_INC (0x01 << 5) /* Auto increment */ +#define APDS990x_CMD_TYPE_SPE (0x03 << 5) /* Special function */ + +#define APDS990x_ADDR_SHIFT 0 +#define APDS990x_CMD 0x80 + +/* Interrupt ack commands */ +#define APDS990X_INT_ACK_ALS 0x6 +#define APDS990X_INT_ACK_PS 0x5 +#define APDS990X_INT_ACK_BOTH 0x7 + +/* ptime */ +#define APDS990X_PTIME_DEFAULT 0xff /* Recommended conversion time 2.7ms*/ + +/* wtime */ +#define APDS990X_WTIME_DEFAULT 0xee /* ~50ms wait time */ + +#define APDS990X_TIME_TO_ADC 1024 /* One timetick as ADC count value */ + +/* Persistence */ +#define APDS990X_APERS_SHIFT 0 +#define APDS990X_PPERS_SHIFT 4 + +/* Supported ID:s */ +#define APDS990X_ID_0 0x0 +#define APDS990X_ID_4 0x4 +#define APDS990X_ID_29 0x29 + +/* pgain and pdiode settings */ +#define APDS_PGAIN_1X 0x0 +#define APDS_PDIODE_IR 0x2 + +#define APDS990X_LUX_OUTPUT_SCALE 10 + +/* Reverse chip factors for threshold calculation */ +struct reverse_factors { + u32 afactor; + int cf1; + int irf1; + int cf2; + int irf2; +}; + +struct apds990x_chip { + struct apds990x_platform_data *pdata; + struct i2c_client *client; + struct mutex mutex; /* avoid parallel access */ + struct regulator_bulk_data regs[2]; + wait_queue_head_t wait; + + int prox_en; + bool prox_continuous_mode; + bool lux_wait_fresh_res; + + /* Chip parameters */ + struct apds990x_chip_factors cf; + struct reverse_factors rcf; + u16 atime; /* als integration time */ + u16 arate; /* als reporting rate */ + u16 fsm_rate; /* state machine rotations in second */ + u16 fsm_time; /* state machine loop time */ + u16 a_max_result; /* Max possible ADC value with current atime */ + u8 again_meas; /* Gain used in last measurement */ + u8 again_next; /* Next calculated gain */ + u8 pgain; + u8 pdiode; + u8 pdrive; + u8 lux_persistence; + u8 prox_persistence; + + u32 lux_raw; + u32 lux; + u16 lux_clear; + u16 lux_ir; + u16 lux_calib; + u32 lux_thres_hi; + u32 lux_thres_lo; + + u32 prox_thres; + u16 prox_data; + u16 prox_calib; + + char chipname[10]; + u8 revision; +}; + +#define APDS_CALIB_SCALER 8192 +#define APDS_LUX_NEUTRAL_CALIB_VALUE (1 * APDS_CALIB_SCALER) +#define APDS_PROX_NEUTRAL_CALIB_VALUE (1 * APDS_CALIB_SCALER) + +#define APDS_PROX_DEF_THRES 600 +#define APDS_PROX_HYSTERESIS 50 +#define APDS_LUX_DEF_THRES_HI 101 +#define APDS_LUX_DEF_THRES_LO 100 +#define APDS_DEFAULT_PROX_PERS 1 + +#define APDS_TIMEOUT 2000 +#define APDS_STARTUP_DELAY 25000 /* us */ +#define APDS_RANGE 65535 +#define APDS_PROX_RANGE 1023 +#define APDS_LUX_GAIN_LO_LIMIT 100 +#define APDS_LUX_GAIN_LO_LIMIT_STRICT 25 + +#define TIMESTEP 87 /* 2.7ms is about 87 / 32 */ +#define TIME_STEP_SCALER 32 + +#define APDS_LUX_AVERAGING_TIME 50 /* tolerates 50/60Hz ripple */ +#define APDS_LUX_DEFAULT_RATE 200 + +static const u8 again[] = {1, 8, 16, 120}; /* ALS gain steps */ +static const u8 ir_currents[] = {100, 50, 25, 12}; /* IRled currents in mA */ + +/* Following two tables must match i.e 10Hz rate means 1 as persistence value */ +static const u16 arates_hz[] = {10, 5, 2, 1}; +static const u8 apersis[] = {1, 2, 4, 5}; + +/* Regulators */ +static const char reg_vcc[] = "Vdd"; +static const char reg_vled[] = "Vled"; + +static int apds990x_read_byte(struct apds990x_chip *chip, u8 reg, u8 *data) +{ + struct i2c_client *client = chip->client; + s32 ret; + + reg &= ~APDS990x_CMD_TYPE_MASK; + reg |= APDS990x_CMD | APDS990x_CMD_TYPE_RB; + + ret = i2c_smbus_read_byte_data(client, reg); + *data = ret; + return (int)ret; +} + +static int apds990x_read_word(struct apds990x_chip *chip, u8 reg, u16 *data) +{ + struct i2c_client *client = chip->client; + s32 ret; + + reg &= ~APDS990x_CMD_TYPE_MASK; + reg |= APDS990x_CMD | APDS990x_CMD_TYPE_INC; + + ret = i2c_smbus_read_word_data(client, reg); + *data = ret; + return (int)ret; +} + +static int apds990x_write_byte(struct apds990x_chip *chip, u8 reg, u8 data) +{ + struct i2c_client *client = chip->client; + s32 ret; + + reg &= ~APDS990x_CMD_TYPE_MASK; + reg |= APDS990x_CMD | APDS990x_CMD_TYPE_RB; + + ret = i2c_smbus_write_byte_data(client, reg, data); + return (int)ret; +} + +static int apds990x_write_word(struct apds990x_chip *chip, u8 reg, u16 data) +{ + struct i2c_client *client = chip->client; + s32 ret; + + reg &= ~APDS990x_CMD_TYPE_MASK; + reg |= APDS990x_CMD | APDS990x_CMD_TYPE_INC; + + ret = i2c_smbus_write_word_data(client, reg, data); + return (int)ret; +} + +static int apds990x_mode_on(struct apds990x_chip *chip) +{ + /* ALS is mandatory, proximity optional */ + u8 reg = APDS990X_EN_AIEN | APDS990X_EN_PON | APDS990X_EN_AEN | + APDS990X_EN_WEN; + + if (chip->prox_en) + reg |= APDS990X_EN_PIEN | APDS990X_EN_PEN; + + return apds990x_write_byte(chip, APDS990X_ENABLE, reg); +} + +static u16 apds990x_lux_to_threshold(struct apds990x_chip *chip, u32 lux) +{ + u32 thres; + u32 cpl; + u32 ir; + + if (lux == 0) + return 0; + else if (lux == APDS_RANGE) + return APDS_RANGE; + + /* + * Reported LUX value is a combination of the IR and CLEAR channel + * values. However, interrupt threshold is only for clear channel. + * This function approximates needed HW threshold value for a given + * LUX value in the current lightning type. + * IR level compared to visible light varies heavily depending on the + * source of the light + * + * Calculate threshold value for the next measurement period. + * Math: threshold = lux * cpl where + * cpl = atime * again / (glass_attenuation * device_factor) + * (count-per-lux) + * + * First remove calibration. Division by four is to avoid overflow + */ + lux = lux * (APDS_CALIB_SCALER / 4) / (chip->lux_calib / 4); + + /* Multiplication by 64 is to increase accuracy */ + cpl = ((u32)chip->atime * (u32)again[chip->again_next] * + APDS_PARAM_SCALE * 64) / (chip->cf.ga * chip->cf.df); + + thres = lux * cpl / 64; + /* + * Convert IR light from the latest result to match with + * new gain step. This helps to adapt with the current + * source of light. + */ + ir = (u32)chip->lux_ir * (u32)again[chip->again_next] / + (u32)again[chip->again_meas]; + + /* + * Compensate count with IR light impact + * IAC1 > IAC2 (see apds990x_get_lux for formulas) + */ + if (chip->lux_clear * APDS_PARAM_SCALE >= + chip->rcf.afactor * chip->lux_ir) + thres = (chip->rcf.cf1 * thres + chip->rcf.irf1 * ir) / + APDS_PARAM_SCALE; + else + thres = (chip->rcf.cf2 * thres + chip->rcf.irf2 * ir) / + APDS_PARAM_SCALE; + + if (thres >= chip->a_max_result) + thres = chip->a_max_result - 1; + return thres; +} + +static inline int apds990x_set_atime(struct apds990x_chip *chip, u32 time_ms) +{ + u8 reg_value; + + chip->atime = time_ms; + /* Formula is specified in the data sheet */ + reg_value = 256 - ((time_ms * TIME_STEP_SCALER) / TIMESTEP); + /* Calculate max ADC value for given integration time */ + chip->a_max_result = (u16)(256 - reg_value) * APDS990X_TIME_TO_ADC; + return apds990x_write_byte(chip, APDS990X_ATIME, reg_value); +} + +/* Called always with mutex locked */ +static int apds990x_refresh_pthres(struct apds990x_chip *chip, int data) +{ + int ret, lo, hi; + + /* If the chip is not in use, don't try to access it */ + if (pm_runtime_suspended(&chip->client->dev)) + return 0; + + if (data < chip->prox_thres) { + lo = 0; + hi = chip->prox_thres; + } else { + lo = chip->prox_thres - APDS_PROX_HYSTERESIS; + if (chip->prox_continuous_mode) + hi = chip->prox_thres; + else + hi = APDS_RANGE; + } + + ret = apds990x_write_word(chip, APDS990X_PILTL, lo); + ret |= apds990x_write_word(chip, APDS990X_PIHTL, hi); + return ret; +} + +/* Called always with mutex locked */ +static int apds990x_refresh_athres(struct apds990x_chip *chip) +{ + int ret; + /* If the chip is not in use, don't try to access it */ + if (pm_runtime_suspended(&chip->client->dev)) + return 0; + + ret = apds990x_write_word(chip, APDS990X_AILTL, + apds990x_lux_to_threshold(chip, chip->lux_thres_lo)); + ret |= apds990x_write_word(chip, APDS990X_AIHTL, + apds990x_lux_to_threshold(chip, chip->lux_thres_hi)); + + return ret; +} + +/* Called always with mutex locked */ +static void apds990x_force_a_refresh(struct apds990x_chip *chip) +{ + /* This will force ALS interrupt after the next measurement. */ + apds990x_write_word(chip, APDS990X_AILTL, APDS_LUX_DEF_THRES_LO); + apds990x_write_word(chip, APDS990X_AIHTL, APDS_LUX_DEF_THRES_HI); +} + +/* Called always with mutex locked */ +static void apds990x_force_p_refresh(struct apds990x_chip *chip) +{ + /* This will force proximity interrupt after the next measurement. */ + apds990x_write_word(chip, APDS990X_PILTL, APDS_PROX_DEF_THRES - 1); + apds990x_write_word(chip, APDS990X_PIHTL, APDS_PROX_DEF_THRES); +} + +/* Called always with mutex locked */ +static int apds990x_calc_again(struct apds990x_chip *chip) +{ + int curr_again = chip->again_meas; + int next_again = chip->again_meas; + int ret = 0; + + /* Calculate suitable als gain */ + if (chip->lux_clear == chip->a_max_result) + next_again -= 2; /* ALS saturated. Decrease gain by 2 steps */ + else if (chip->lux_clear > chip->a_max_result / 2) + next_again--; + else if (chip->lux_clear < APDS_LUX_GAIN_LO_LIMIT_STRICT) + next_again += 2; /* Too dark. Increase gain by 2 steps */ + else if (chip->lux_clear < APDS_LUX_GAIN_LO_LIMIT) + next_again++; + + /* Limit gain to available range */ + if (next_again < 0) + next_again = 0; + else if (next_again > APDS990X_MAX_AGAIN) + next_again = APDS990X_MAX_AGAIN; + + /* Let's check can we trust the measured result */ + if (chip->lux_clear == chip->a_max_result) { + /* Result can be totally garbage due to saturation */ + ret = -ERANGE; + } else if (next_again != curr_again && + chip->lux_clear < APDS_LUX_GAIN_LO_LIMIT_STRICT) { + /* + * Gain is changed and measurement result is very small. + * Result can be totally garbage due to underflow + */ + ret = -ERANGE; + } + + chip->again_next = next_again; + apds990x_write_byte(chip, APDS990X_CONTROL, + (chip->pdrive << 6) | + (chip->pdiode << 4) | + (chip->pgain << 2) | + (chip->again_next << 0)); + + /* + * Error means bad result -> re-measurement is needed. The forced + * refresh uses fastest possible persistence setting to get result + * as soon as possible. + */ + if (ret < 0) + apds990x_force_a_refresh(chip); + else + apds990x_refresh_athres(chip); + + return ret; +} + +/* Called always with mutex locked */ +static int apds990x_get_lux(struct apds990x_chip *chip, int clear, int ir) +{ + int iac, iac1, iac2; /* IR adjusted counts */ + u32 lpc; /* Lux per count */ + + /* Formulas: + * iac1 = CF1 * CLEAR_CH - IRF1 * IR_CH + * iac2 = CF2 * CLEAR_CH - IRF2 * IR_CH + */ + iac1 = (chip->cf.cf1 * clear - chip->cf.irf1 * ir) / APDS_PARAM_SCALE; + iac2 = (chip->cf.cf2 * clear - chip->cf.irf2 * ir) / APDS_PARAM_SCALE; + + iac = max(iac1, iac2); + iac = max(iac, 0); + + lpc = APDS990X_LUX_OUTPUT_SCALE * (chip->cf.df * chip->cf.ga) / + (u32)(again[chip->again_meas] * (u32)chip->atime); + + return (iac * lpc) / APDS_PARAM_SCALE; +} + +static int apds990x_ack_int(struct apds990x_chip *chip, u8 mode) +{ + struct i2c_client *client = chip->client; + s32 ret; + u8 reg = APDS990x_CMD | APDS990x_CMD_TYPE_SPE; + + switch (mode & (APDS990X_ST_AINT | APDS990X_ST_PINT)) { + case APDS990X_ST_AINT: + reg |= APDS990X_INT_ACK_ALS; + break; + case APDS990X_ST_PINT: + reg |= APDS990X_INT_ACK_PS; + break; + default: + reg |= APDS990X_INT_ACK_BOTH; + break; + } + + ret = i2c_smbus_read_byte_data(client, reg); + return (int)ret; +} + +static irqreturn_t apds990x_irq(int irq, void *data) +{ + struct apds990x_chip *chip = data; + u8 status; + + apds990x_read_byte(chip, APDS990X_STATUS, &status); + apds990x_ack_int(chip, status); + + mutex_lock(&chip->mutex); + if (!pm_runtime_suspended(&chip->client->dev)) { + if (status & APDS990X_ST_AINT) { + apds990x_read_word(chip, APDS990X_CDATAL, + &chip->lux_clear); + apds990x_read_word(chip, APDS990X_IRDATAL, + &chip->lux_ir); + /* Store used gain for calculations */ + chip->again_meas = chip->again_next; + + chip->lux_raw = apds990x_get_lux(chip, + chip->lux_clear, + chip->lux_ir); + + if (apds990x_calc_again(chip) == 0) { + /* Result is valid */ + chip->lux = chip->lux_raw; + chip->lux_wait_fresh_res = false; + wake_up(&chip->wait); + sysfs_notify(&chip->client->dev.kobj, + NULL, "lux0_input"); + } + } + + if (status & APDS990X_ST_PINT) + if (chip->prox_en) { + u16 clr_ch; + + apds990x_read_word(chip, APDS990X_CDATAL, + &clr_ch); + /* + * If ALS channel is saturated at min gain, + * proximity gives false posivite values. + * Just ignore them. + */ + if (chip->again_meas == 0 && + clr_ch == chip->a_max_result) { + chip->prox_data = 0; + } else { + apds990x_read_word(chip, + APDS990X_PDATAL, + &chip->prox_data); + } + apds990x_refresh_pthres(chip, chip->prox_data); + if (chip->prox_data < chip->prox_thres) + chip->prox_data = 0; + else if (!chip->prox_continuous_mode) + chip->prox_data = APDS_PROX_RANGE; + sysfs_notify(&chip->client->dev.kobj, + NULL, "prox0_raw"); + } + } + mutex_unlock(&chip->mutex); + return IRQ_HANDLED; +} + +static int apds990x_configure(struct apds990x_chip *chip) +{ + /* It is recommended to use disabled mode during these operations */ + apds990x_write_byte(chip, APDS990X_ENABLE, APDS990X_EN_DISABLE_ALL); + + /* conversion and wait times for different state machince states */ + apds990x_write_byte(chip, APDS990X_PTIME, APDS990X_PTIME_DEFAULT); + apds990x_write_byte(chip, APDS990X_WTIME, APDS990X_WTIME_DEFAULT); + apds990x_set_atime(chip, APDS_LUX_AVERAGING_TIME); + + apds990x_write_byte(chip, APDS990X_CONFIG, 0); + + /* + * Total time for one FSM round without proximity. + * One bit in wait is about 2.7 ms = 27 / 10. + * rate = 1000 ms / fsm loop time in ms. + */ + chip->fsm_time = APDS_LUX_AVERAGING_TIME + + (256 - APDS990X_WTIME_DEFAULT) * 27 / 10; + chip->fsm_rate = 1000 / chip->fsm_time; + + /* Persistence levels */ + apds990x_write_byte(chip, APDS990X_PERS, + (chip->lux_persistence << APDS990X_APERS_SHIFT) | + (chip->prox_persistence << APDS990X_PPERS_SHIFT)); + + apds990x_write_byte(chip, APDS990X_PPCOUNT, chip->pdata->ppcount); + + /* Start with relatively small gain */ + chip->again_meas = 1; + chip->again_next = 1; + apds990x_write_byte(chip, APDS990X_CONTROL, + (chip->pdrive << 6) | + (chip->pdiode << 4) | + (chip->pgain << 2) | + (chip->again_next << 0)); + return 0; +} + +static int apds990x_detect(struct apds990x_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + u8 id; + + ret = apds990x_read_byte(chip, APDS990X_ID, &id); + if (ret < 0) { + dev_err(&client->dev, "ID read failed\n"); + return ret; + } + + ret = apds990x_read_byte(chip, APDS990X_REV, &chip->revision); + if (ret < 0) { + dev_err(&client->dev, "REV read failed\n"); + return ret; + } + + switch (id) { + case APDS990X_ID_0: + case APDS990X_ID_4: + case APDS990X_ID_29: + snprintf(chip->chipname, sizeof(chip->chipname), "APDS-990x"); + break; + default: + ret = -ENODEV; + break; + } + return ret; +} + +static int apds990x_chip_on(struct apds990x_chip *chip) +{ + int err = regulator_bulk_enable(ARRAY_SIZE(chip->regs), + chip->regs); + if (err < 0) + return err; + + usleep_range(APDS_STARTUP_DELAY, 2 * APDS_STARTUP_DELAY); + + /* Refresh all configs in case of regulators were off */ + apds990x_configure(chip); + apds990x_mode_on(chip); + return 0; +} + +static int apds990x_chip_off(struct apds990x_chip *chip) +{ + apds990x_write_byte(chip, APDS990X_ENABLE, APDS990X_EN_DISABLE_ALL); + regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs); + return 0; +} + +static ssize_t apds990x_lux_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + ssize_t ret; + u32 result; + long timeout; + + if (pm_runtime_suspended(dev)) + return -EIO; + + timeout = wait_event_interruptible_timeout(chip->wait, + !chip->lux_wait_fresh_res, + msecs_to_jiffies(APDS_TIMEOUT)); + if (!timeout) + return -EIO; + + mutex_lock(&chip->mutex); + result = (chip->lux * chip->lux_calib) / APDS_CALIB_SCALER; + if (result > (APDS_RANGE * APDS990X_LUX_OUTPUT_SCALE)) + result = APDS_RANGE * APDS990X_LUX_OUTPUT_SCALE; + + ret = snprintf(buf, PAGE_SIZE, "%d.%d\n", + result / APDS990X_LUX_OUTPUT_SCALE, + result % APDS990X_LUX_OUTPUT_SCALE); + mutex_unlock(&chip->mutex); + return ret; +} + +static DEVICE_ATTR(lux0_input, S_IRUGO, apds990x_lux_show, NULL); + +static ssize_t apds990x_lux_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", APDS_RANGE); +} + +static DEVICE_ATTR(lux0_sensor_range, S_IRUGO, apds990x_lux_range_show, NULL); + +static ssize_t apds990x_lux_calib_format_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", APDS_CALIB_SCALER); +} + +static DEVICE_ATTR(lux0_calibscale_default, S_IRUGO, + apds990x_lux_calib_format_show, NULL); + +static ssize_t apds990x_lux_calib_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", chip->lux_calib); +} + +static ssize_t apds990x_lux_calib_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + unsigned long value; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + if (chip->lux_calib > APDS_RANGE) + return -EINVAL; + + chip->lux_calib = value; + + return len; +} + +static DEVICE_ATTR(lux0_calibscale, S_IRUGO | S_IWUSR, apds990x_lux_calib_show, + apds990x_lux_calib_store); + +static ssize_t apds990x_rate_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + int pos = 0; + for (i = 0; i < ARRAY_SIZE(arates_hz); i++) + pos += sprintf(buf + pos, "%d ", arates_hz[i]); + sprintf(buf + pos - 1, "\n"); + return pos; +} + +static ssize_t apds990x_rate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", chip->arate); +} + +static int apds990x_set_arate(struct apds990x_chip *chip, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(arates_hz); i++) + if (rate >= arates_hz[i]) + break; + + if (i == ARRAY_SIZE(arates_hz)) + return -EINVAL; + + /* Pick up corresponding persistence value */ + chip->lux_persistence = apersis[i]; + chip->arate = arates_hz[i]; + + /* If the chip is not in use, don't try to access it */ + if (pm_runtime_suspended(&chip->client->dev)) + return 0; + + /* Persistence levels */ + return apds990x_write_byte(chip, APDS990X_PERS, + (chip->lux_persistence << APDS990X_APERS_SHIFT) | + (chip->prox_persistence << APDS990X_PPERS_SHIFT)); +} + +static ssize_t apds990x_rate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + unsigned long value; + int ret; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + mutex_lock(&chip->mutex); + ret = apds990x_set_arate(chip, value); + mutex_unlock(&chip->mutex); + + if (ret < 0) + return ret; + return len; +} + +static DEVICE_ATTR(lux0_rate_avail, S_IRUGO, apds990x_rate_avail, NULL); + +static DEVICE_ATTR(lux0_rate, S_IRUGO | S_IWUSR, apds990x_rate_show, + apds990x_rate_store); + +static ssize_t apds990x_prox_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret; + struct apds990x_chip *chip = dev_get_drvdata(dev); + if (pm_runtime_suspended(dev) || !chip->prox_en) + return -EIO; + + mutex_lock(&chip->mutex); + ret = snprintf(buf, PAGE_SIZE, "%d\n", chip->prox_data); + mutex_unlock(&chip->mutex); + return ret; +} + +static DEVICE_ATTR(prox0_raw, S_IRUGO, apds990x_prox_show, NULL); + +static ssize_t apds990x_prox_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", APDS_PROX_RANGE); +} + +static DEVICE_ATTR(prox0_sensor_range, S_IRUGO, apds990x_prox_range_show, NULL); + +static ssize_t apds990x_prox_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", chip->prox_en); +} + +static ssize_t apds990x_prox_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + unsigned long value; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + mutex_lock(&chip->mutex); + if (value) { + chip->prox_en++; + } else { + if (chip->prox_en > 0) + chip->prox_en--; + } + + if (!pm_runtime_suspended(dev)) + apds990x_mode_on(chip); + mutex_unlock(&chip->mutex); + return len; +} + +static DEVICE_ATTR(prox0_raw_en, S_IRUGO | S_IWUSR, apds990x_prox_enable_show, + apds990x_prox_enable_store); + +static const char reporting_modes[][9] = {"trigger", "periodic"}; + +static ssize_t apds990x_prox_reporting_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", + reporting_modes[!!chip->prox_continuous_mode]); +} + +static ssize_t apds990x_prox_reporting_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + + if (sysfs_streq(buf, reporting_modes[0])) + chip->prox_continuous_mode = 0; + else if (sysfs_streq(buf, reporting_modes[1])) + chip->prox_continuous_mode = 1; + else + return -EINVAL; + return len; +} + +static DEVICE_ATTR(prox0_reporting_mode, S_IRUGO | S_IWUSR, + apds990x_prox_reporting_mode_show, + apds990x_prox_reporting_mode_store); + +static ssize_t apds990x_prox_reporting_avail_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s %s\n", reporting_modes[0], reporting_modes[1]); +} + +static DEVICE_ATTR(prox0_reporting_mode_avail, S_IRUGO | S_IWUSR, + apds990x_prox_reporting_avail_show, NULL); + + +static ssize_t apds990x_lux_thresh_above_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", chip->lux_thres_hi); +} + +static ssize_t apds990x_lux_thresh_below_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", chip->lux_thres_lo); +} + +static ssize_t apds990x_set_lux_thresh(struct apds990x_chip *chip, u32 *target, + const char *buf) +{ + int ret = 0; + unsigned long thresh; + + if (strict_strtoul(buf, 0, &thresh)) + return -EINVAL; + + if (thresh > APDS_RANGE) + return -EINVAL; + + mutex_lock(&chip->mutex); + *target = thresh; + /* + * Don't update values in HW if we are still waiting for + * first interrupt to come after device handle open call. + */ + if (!chip->lux_wait_fresh_res) + apds990x_refresh_athres(chip); + mutex_unlock(&chip->mutex); + return ret; + +} + +static ssize_t apds990x_lux_thresh_above_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + int ret = apds990x_set_lux_thresh(chip, &chip->lux_thres_hi, buf); + if (ret < 0) + return ret; + return len; +} + +static ssize_t apds990x_lux_thresh_below_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + int ret = apds990x_set_lux_thresh(chip, &chip->lux_thres_lo, buf); + if (ret < 0) + return ret; + return len; +} + +static DEVICE_ATTR(lux0_thresh_above_value, S_IRUGO | S_IWUSR, + apds990x_lux_thresh_above_show, + apds990x_lux_thresh_above_store); + +static DEVICE_ATTR(lux0_thresh_below_value, S_IRUGO | S_IWUSR, + apds990x_lux_thresh_below_show, + apds990x_lux_thresh_below_store); + +static ssize_t apds990x_prox_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", chip->prox_thres); +} + +static ssize_t apds990x_prox_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + unsigned long value; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + if ((value > APDS_RANGE) || (value == 0) || + (value < APDS_PROX_HYSTERESIS)) + return -EINVAL; + + mutex_lock(&chip->mutex); + chip->prox_thres = value; + + apds990x_force_p_refresh(chip); + mutex_unlock(&chip->mutex); + return len; +} + +static DEVICE_ATTR(prox0_thresh_above_value, S_IRUGO | S_IWUSR, + apds990x_prox_threshold_show, + apds990x_prox_threshold_store); + +static ssize_t apds990x_power_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", !pm_runtime_suspended(dev)); + return 0; +} + +static ssize_t apds990x_power_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + unsigned long value; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + if (value) { + pm_runtime_get_sync(dev); + mutex_lock(&chip->mutex); + chip->lux_wait_fresh_res = true; + apds990x_force_a_refresh(chip); + apds990x_force_p_refresh(chip); + mutex_unlock(&chip->mutex); + } else { + if (!pm_runtime_suspended(dev)) + pm_runtime_put(dev); + } + return len; +} + +static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, + apds990x_power_state_show, + apds990x_power_state_store); + +static ssize_t apds990x_chip_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apds990x_chip *chip = dev_get_drvdata(dev); + return sprintf(buf, "%s %d\n", chip->chipname, chip->revision); +} + +static DEVICE_ATTR(chip_id, S_IRUGO, apds990x_chip_id_show, NULL); + +static struct attribute *sysfs_attrs_ctrl[] = { + &dev_attr_lux0_calibscale.attr, + &dev_attr_lux0_calibscale_default.attr, + &dev_attr_lux0_input.attr, + &dev_attr_lux0_sensor_range.attr, + &dev_attr_lux0_rate.attr, + &dev_attr_lux0_rate_avail.attr, + &dev_attr_lux0_thresh_above_value.attr, + &dev_attr_lux0_thresh_below_value.attr, + &dev_attr_prox0_raw_en.attr, + &dev_attr_prox0_raw.attr, + &dev_attr_prox0_sensor_range.attr, + &dev_attr_prox0_thresh_above_value.attr, + &dev_attr_prox0_reporting_mode.attr, + &dev_attr_prox0_reporting_mode_avail.attr, + &dev_attr_chip_id.attr, + &dev_attr_power_state.attr, + NULL +}; + +static struct attribute_group apds990x_attribute_group[] = { + {.attrs = sysfs_attrs_ctrl }, +}; + +static int __devinit apds990x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct apds990x_chip *chip; + int err; + + chip = kzalloc(sizeof *chip, GFP_KERNEL); + if (!chip) + return -ENOMEM; + + i2c_set_clientdata(client, chip); + chip->client = client; + + init_waitqueue_head(&chip->wait); + mutex_init(&chip->mutex); + chip->pdata = client->dev.platform_data; + + if (chip->pdata == NULL) { + dev_err(&client->dev, "platform data is mandatory\n"); + err = -EINVAL; + goto fail1; + } + + if (chip->pdata->cf.ga == 0) { + /* set uncovered sensor default parameters */ + chip->cf.ga = 1966; /* 0.48 * APDS_PARAM_SCALE */ + chip->cf.cf1 = 4096; /* 1.00 * APDS_PARAM_SCALE */ + chip->cf.irf1 = 9134; /* 2.23 * APDS_PARAM_SCALE */ + chip->cf.cf2 = 2867; /* 0.70 * APDS_PARAM_SCALE */ + chip->cf.irf2 = 5816; /* 1.42 * APDS_PARAM_SCALE */ + chip->cf.df = 52; + } else { + chip->cf = chip->pdata->cf; + } + + /* precalculate inverse chip factors for threshold control */ + chip->rcf.afactor = + (chip->cf.irf1 - chip->cf.irf2) * APDS_PARAM_SCALE / + (chip->cf.cf1 - chip->cf.cf2); + chip->rcf.cf1 = APDS_PARAM_SCALE * APDS_PARAM_SCALE / + chip->cf.cf1; + chip->rcf.irf1 = chip->cf.irf1 * APDS_PARAM_SCALE / + chip->cf.cf1; + chip->rcf.cf2 = APDS_PARAM_SCALE * APDS_PARAM_SCALE / + chip->cf.cf2; + chip->rcf.irf2 = chip->cf.irf2 * APDS_PARAM_SCALE / + chip->cf.cf2; + + /* Set something to start with */ + chip->lux_thres_hi = APDS_LUX_DEF_THRES_HI; + chip->lux_thres_lo = APDS_LUX_DEF_THRES_LO; + chip->lux_calib = APDS_LUX_NEUTRAL_CALIB_VALUE; + + chip->prox_thres = APDS_PROX_DEF_THRES; + chip->pdrive = chip->pdata->pdrive; + chip->pdiode = APDS_PDIODE_IR; + chip->pgain = APDS_PGAIN_1X; + chip->prox_calib = APDS_PROX_NEUTRAL_CALIB_VALUE; + chip->prox_persistence = APDS_DEFAULT_PROX_PERS; + chip->prox_continuous_mode = false; + + chip->regs[0].supply = reg_vcc; + chip->regs[1].supply = reg_vled; + + err = regulator_bulk_get(&client->dev, + ARRAY_SIZE(chip->regs), chip->regs); + if (err < 0) { + dev_err(&client->dev, "Cannot get regulators\n"); + goto fail1; + } + + err = regulator_bulk_enable(ARRAY_SIZE(chip->regs), chip->regs); + if (err < 0) { + dev_err(&client->dev, "Cannot enable regulators\n"); + goto fail2; + } + + usleep_range(APDS_STARTUP_DELAY, 2 * APDS_STARTUP_DELAY); + + err = apds990x_detect(chip); + if (err < 0) { + dev_err(&client->dev, "APDS990X not found\n"); + goto fail3; + } + + pm_runtime_set_active(&client->dev); + + apds990x_configure(chip); + apds990x_set_arate(chip, APDS_LUX_DEFAULT_RATE); + apds990x_mode_on(chip); + + pm_runtime_enable(&client->dev); + + if (chip->pdata->setup_resources) { + err = chip->pdata->setup_resources(); + if (err) { + err = -EINVAL; + goto fail3; + } + } + + err = sysfs_create_group(&chip->client->dev.kobj, + apds990x_attribute_group); + if (err < 0) { + dev_err(&chip->client->dev, "Sysfs registration failed\n"); + goto fail4; + } + + err = request_threaded_irq(client->irq, NULL, + apds990x_irq, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_LOW | + IRQF_ONESHOT, + "apds990x", chip); + if (err) { + dev_err(&client->dev, "could not get IRQ %d\n", + client->irq); + goto fail5; + } + return err; +fail5: + sysfs_remove_group(&chip->client->dev.kobj, + &apds990x_attribute_group[0]); +fail4: + if (chip->pdata && chip->pdata->release_resources) + chip->pdata->release_resources(); +fail3: + regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs); +fail2: + regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs); +fail1: + kfree(chip); + return err; +} + +static int __devexit apds990x_remove(struct i2c_client *client) +{ + struct apds990x_chip *chip = i2c_get_clientdata(client); + + free_irq(client->irq, chip); + sysfs_remove_group(&chip->client->dev.kobj, + apds990x_attribute_group); + + if (chip->pdata && chip->pdata->release_resources) + chip->pdata->release_resources(); + + if (!pm_runtime_suspended(&client->dev)) + apds990x_chip_off(chip); + + regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs); + + kfree(chip); + return 0; +} + +#ifdef CONFIG_PM +static int apds990x_suspend(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct apds990x_chip *chip = i2c_get_clientdata(client); + + apds990x_chip_off(chip); + return 0; +} + +static int apds990x_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct apds990x_chip *chip = i2c_get_clientdata(client); + + /* + * If we were enabled at suspend time, it is expected + * everything works nice and smoothly. Chip_on is enough + */ + apds990x_chip_on(chip); + + return 0; +} +#else +#define apds990x_suspend NULL +#define apds990x_resume NULL +#define apds990x_shutdown NULL +#endif + +#ifdef CONFIG_PM_RUNTIME +static int apds990x_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct apds990x_chip *chip = i2c_get_clientdata(client); + + apds990x_chip_off(chip); + return 0; +} + +static int apds990x_runtime_resume(struct device *dev) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct apds990x_chip *chip = i2c_get_clientdata(client); + + apds990x_chip_on(chip); + return 0; +} + +#endif + +static const struct i2c_device_id apds990x_id[] = { + {"apds990x", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, apds990x_id); + +static const struct dev_pm_ops apds990x_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(apds990x_suspend, apds990x_resume) + SET_RUNTIME_PM_OPS(apds990x_runtime_suspend, + apds990x_runtime_resume, + NULL) +}; + +static struct i2c_driver apds990x_driver = { + .driver = { + .name = "apds990x", + .owner = THIS_MODULE, + .pm = &apds990x_pm_ops, + }, + .probe = apds990x_probe, + .remove = __devexit_p(apds990x_remove), + .id_table = apds990x_id, +}; + +static int __init apds990x_init(void) +{ + return i2c_add_driver(&apds990x_driver); +} + +static void __exit apds990x_exit(void) +{ + i2c_del_driver(&apds990x_driver); +} + +MODULE_DESCRIPTION("APDS990X combined ALS and proximity sensor"); +MODULE_AUTHOR("Samu Onkalo, Nokia Corporation"); +MODULE_LICENSE("GPL v2"); + +module_init(apds990x_init); +module_exit(apds990x_exit); diff --git a/include/linux/i2c/apds990x.h b/include/linux/i2c/apds990x.h new file mode 100644 index 0000000..7d521e3 --- /dev/null +++ b/include/linux/i2c/apds990x.h @@ -0,0 +1,58 @@ +/* + * This file is part of the APDS990x sensor driver. + * Chip is combined proximity and ambient light sensor. + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * Contact: Samu Onkalo <samu.p.onkalo@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __APDS990X_H__ +#define __APDS990X_H__ + + +#define APDS_IRLED_CURR_12mA 0x3 +#define APDS_IRLED_CURR_25mA 0x2 +#define APDS_IRLED_CURR_50mA 0x1 +#define APDS_IRLED_CURR_100mA 0x0 + +/* + * Structure for tuning ALS calculation to match with environment. + * There depends on the material above the sensor and the sensor + * itself. If the GA is zero, driver will use uncovered sensor default values + * format: decimal value * APDS_PARAM_SCALE + */ +#define APDS_PARAM_SCALE 4096 +struct apds990x_chip_factors { + int ga; /* Glass attenuation */ + int cf1; /* Clear channel factor 1 */ + int irf1; /* Ir channel factor 1 */ + int cf2; /* Clear channel factor 2 */ + int irf2; /* Ir channel factor 2 */ + int df; /* Device factor. Decimal number */ +}; + +struct apds990x_platform_data { + struct apds990x_chip_factors cf; + u8 pdrive; + u8 ppcount; + int (*setup_resources)(void); + int (*release_resources)(void); +}; + +#endif -- 1.6.0.4 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCHv2 3/5] misc: Driver for APDS990X ALS and proximity sensors 2010-10-08 13:42 ` [PATCHv2 3/5] misc: Driver for APDS990X ALS and proximity sensors Samu Onkalo @ 2010-10-08 14:46 ` Jonathan Cameron 0 siblings, 0 replies; 16+ messages in thread From: Jonathan Cameron @ 2010-10-08 14:46 UTC (permalink / raw) To: Samu Onkalo; +Cc: linux-i2c, linux-kernel, alan On 10/08/10 14:42, Samu Onkalo wrote: > This is a driver for Avago APDS990X combined ALS and proximity sensor. > > Interface is sysfs based. The driver uses interrupts to provide new data. > The driver supports pm_runtime and regulator frameworks. > > See Documentation/misc-devices/apds990x.txt for details Couple of nitpicks inline based on a quick read. > > Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com> > --- > drivers/misc/apds990x.c | 1303 ++++++++++++++++++++++++++++++++++++++++++ > include/linux/i2c/apds990x.h | 58 ++ > 2 files changed, 1361 insertions(+), 0 deletions(-) > create mode 100644 drivers/misc/apds990x.c > create mode 100644 include/linux/i2c/apds990x.h > > diff --git a/drivers/misc/apds990x.c b/drivers/misc/apds990x.c > new file mode 100644 > index 0000000..658c5c4 > --- /dev/null > +++ b/drivers/misc/apds990x.c > @@ -0,0 +1,1303 @@ > +/* > + * This file is part of the APDS990x sensor driver. > + * Chip is combined proximity and ambient light sensor. > + * > + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). > + * > + * Contact: Samu Onkalo <samu.p.onkalo@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + * > + */ > + > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/i2c.h> > +#include <linux/interrupt.h> > +#include <linux/mutex.h> > +#include <linux/regulator/consumer.h> > +#include <linux/pm_runtime.h> > +#include <linux/delay.h> > +#include <linux/wait.h> > +#include <linux/slab.h> > +#include <linux/i2c/apds990x.h> > + > +/* Register map */ > +#define APDS990X_ENABLE 0x00 /* Enable of states and interrupts */ > +#define APDS990X_ATIME 0x01 /* ALS ADC time */ > +#define APDS990X_PTIME 0x02 /* Proximity ADC time */ > +#define APDS990X_WTIME 0x03 /* Wait time */ > +#define APDS990X_AILTL 0x04 /* ALS interrupt low threshold low byte */ > +#define APDS990X_AILTH 0x05 /* ALS interrupt low threshold hi byte */ > +#define APDS990X_AIHTL 0x06 /* ALS interrupt hi threshold low byte */ > +#define APDS990X_AIHTH 0x07 /* ALS interrupt hi threshold hi byte */ > +#define APDS990X_PILTL 0x08 /* Proximity interrupt low threshold low byte */ > +#define APDS990X_PILTH 0x09 /* Proximity interrupt low threshold hi byte */ > +#define APDS990X_PIHTL 0x0a /* Proximity interrupt hi threshold low byte */ > +#define APDS990X_PIHTH 0x0b /* Proximity interrupt hi threshold hi byte */ > +#define APDS990X_PERS 0x0c /* Interrupt persistence filters */ > +#define APDS990X_CONFIG 0x0d /* Configuration */ > +#define APDS990X_PPCOUNT 0x0e /* Proximity pulse count */ > +#define APDS990X_CONTROL 0x0f /* Gain control register */ > +#define APDS990X_REV 0x11 /* Revision Number */ > +#define APDS990X_ID 0x12 /* Device ID */ > +#define APDS990X_STATUS 0x13 /* Device status */ > +#define APDS990X_CDATAL 0x14 /* Clear ADC low data register */ > +#define APDS990X_CDATAH 0x15 /* Clear ADC high data register */ > +#define APDS990X_IRDATAL 0x16 /* IR ADC low data register */ > +#define APDS990X_IRDATAH 0x17 /* IR ADC high data register */ > +#define APDS990X_PDATAL 0x18 /* Proximity ADC low data register */ > +#define APDS990X_PDATAH 0x19 /* Proximity ADC high data register */ > + > +/* Control */ > +#define APDS990X_MAX_AGAIN 3 > + > +/* Enable register */ > +#define APDS990X_EN_PIEN (0x1 << 5) > +#define APDS990X_EN_AIEN (0x1 << 4) > +#define APDS990X_EN_WEN (0x1 << 3) > +#define APDS990X_EN_PEN (0x1 << 2) > +#define APDS990X_EN_AEN (0x1 << 1) > +#define APDS990X_EN_PON (0x1 << 0) > +#define APDS990X_EN_DISABLE_ALL 0 > + > +/* Status register */ > +#define APDS990X_ST_PINT (0x1 << 5) > +#define APDS990X_ST_AINT (0x1 << 4) > + > +/* I2C access types */ > +#define APDS990x_CMD_TYPE_MASK (0x03 << 5) > +#define APDS990x_CMD_TYPE_RB (0x00 << 5) /* Repeated byte */ > +#define APDS990x_CMD_TYPE_INC (0x01 << 5) /* Auto increment */ > +#define APDS990x_CMD_TYPE_SPE (0x03 << 5) /* Special function */ > + > +#define APDS990x_ADDR_SHIFT 0 > +#define APDS990x_CMD 0x80 > + > +/* Interrupt ack commands */ > +#define APDS990X_INT_ACK_ALS 0x6 > +#define APDS990X_INT_ACK_PS 0x5 > +#define APDS990X_INT_ACK_BOTH 0x7 > + > +/* ptime */ > +#define APDS990X_PTIME_DEFAULT 0xff /* Recommended conversion time 2.7ms*/ > + > +/* wtime */ > +#define APDS990X_WTIME_DEFAULT 0xee /* ~50ms wait time */ > + > +#define APDS990X_TIME_TO_ADC 1024 /* One timetick as ADC count value */ > + > +/* Persistence */ > +#define APDS990X_APERS_SHIFT 0 > +#define APDS990X_PPERS_SHIFT 4 > + > +/* Supported ID:s */ > +#define APDS990X_ID_0 0x0 > +#define APDS990X_ID_4 0x4 > +#define APDS990X_ID_29 0x29 > + > +/* pgain and pdiode settings */ > +#define APDS_PGAIN_1X 0x0 > +#define APDS_PDIODE_IR 0x2 > + > +#define APDS990X_LUX_OUTPUT_SCALE 10 > + > +/* Reverse chip factors for threshold calculation */ > +struct reverse_factors { > + u32 afactor; > + int cf1; > + int irf1; > + int cf2; > + int irf2; > +}; > + > +struct apds990x_chip { > + struct apds990x_platform_data *pdata; > + struct i2c_client *client; > + struct mutex mutex; /* avoid parallel access */ > + struct regulator_bulk_data regs[2]; > + wait_queue_head_t wait; > + > + int prox_en; > + bool prox_continuous_mode; > + bool lux_wait_fresh_res; > + > + /* Chip parameters */ > + struct apds990x_chip_factors cf; > + struct reverse_factors rcf; > + u16 atime; /* als integration time */ > + u16 arate; /* als reporting rate */ This is nicely set to a value but then it doesn't seem to be used so don't bother! > + u16 fsm_rate; /* state machine rotations in second */ Likewise. > + u16 fsm_time; /* state machine loop time */ > + u16 a_max_result; /* Max possible ADC value with current atime */ > + u8 again_meas; /* Gain used in last measurement */ > + u8 again_next; /* Next calculated gain */ > + u8 pgain; > + u8 pdiode; > + u8 pdrive; > + u8 lux_persistence; > + u8 prox_persistence; > + > + u32 lux_raw; > + u32 lux; > + u16 lux_clear; > + u16 lux_ir; > + u16 lux_calib; > + u32 lux_thres_hi; > + u32 lux_thres_lo; > + > + u32 prox_thres; > + u16 prox_data; > + u16 prox_calib; > + > + char chipname[10]; > + u8 revision; > +}; > + > +#define APDS_CALIB_SCALER 8192 > +#define APDS_LUX_NEUTRAL_CALIB_VALUE (1 * APDS_CALIB_SCALER) > +#define APDS_PROX_NEUTRAL_CALIB_VALUE (1 * APDS_CALIB_SCALER) > + > +#define APDS_PROX_DEF_THRES 600 > +#define APDS_PROX_HYSTERESIS 50 > +#define APDS_LUX_DEF_THRES_HI 101 > +#define APDS_LUX_DEF_THRES_LO 100 > +#define APDS_DEFAULT_PROX_PERS 1 > + > +#define APDS_TIMEOUT 2000 > +#define APDS_STARTUP_DELAY 25000 /* us */ > +#define APDS_RANGE 65535 > +#define APDS_PROX_RANGE 1023 > +#define APDS_LUX_GAIN_LO_LIMIT 100 > +#define APDS_LUX_GAIN_LO_LIMIT_STRICT 25 > + > +#define TIMESTEP 87 /* 2.7ms is about 87 / 32 */ > +#define TIME_STEP_SCALER 32 > + > +#define APDS_LUX_AVERAGING_TIME 50 /* tolerates 50/60Hz ripple */ > +#define APDS_LUX_DEFAULT_RATE 200 > + > +static const u8 again[] = {1, 8, 16, 120}; /* ALS gain steps */ > +static const u8 ir_currents[] = {100, 50, 25, 12}; /* IRled currents in mA */ > + > +/* Following two tables must match i.e 10Hz rate means 1 as persistence value */ > +static const u16 arates_hz[] = {10, 5, 2, 1}; > +static const u8 apersis[] = {1, 2, 4, 5}; > + > +/* Regulators */ > +static const char reg_vcc[] = "Vdd"; > +static const char reg_vled[] = "Vled"; > + > +static int apds990x_read_byte(struct apds990x_chip *chip, u8 reg, u8 *data) > +{ > + struct i2c_client *client = chip->client; > + s32 ret; > + > + reg &= ~APDS990x_CMD_TYPE_MASK; > + reg |= APDS990x_CMD | APDS990x_CMD_TYPE_RB; > + > + ret = i2c_smbus_read_byte_data(client, reg); > + *data = ret; > + return (int)ret; > +} > + > +static int apds990x_read_word(struct apds990x_chip *chip, u8 reg, u16 *data) > +{ > + struct i2c_client *client = chip->client; > + s32 ret; > + > + reg &= ~APDS990x_CMD_TYPE_MASK; > + reg |= APDS990x_CMD | APDS990x_CMD_TYPE_INC; > + > + ret = i2c_smbus_read_word_data(client, reg); > + *data = ret; > + return (int)ret; > +} > + > +static int apds990x_write_byte(struct apds990x_chip *chip, u8 reg, u8 data) > +{ > + struct i2c_client *client = chip->client; > + s32 ret; > + > + reg &= ~APDS990x_CMD_TYPE_MASK; > + reg |= APDS990x_CMD | APDS990x_CMD_TYPE_RB; > + > + ret = i2c_smbus_write_byte_data(client, reg, data); > + return (int)ret; > +} > + > +static int apds990x_write_word(struct apds990x_chip *chip, u8 reg, u16 data) > +{ > + struct i2c_client *client = chip->client; > + s32 ret; > + > + reg &= ~APDS990x_CMD_TYPE_MASK; > + reg |= APDS990x_CMD | APDS990x_CMD_TYPE_INC; > + > + ret = i2c_smbus_write_word_data(client, reg, data); > + return (int)ret; > +} > + > +static int apds990x_mode_on(struct apds990x_chip *chip) > +{ > + /* ALS is mandatory, proximity optional */ > + u8 reg = APDS990X_EN_AIEN | APDS990X_EN_PON | APDS990X_EN_AEN | > + APDS990X_EN_WEN; > + > + if (chip->prox_en) > + reg |= APDS990X_EN_PIEN | APDS990X_EN_PEN; > + > + return apds990x_write_byte(chip, APDS990X_ENABLE, reg); > +} > + > +static u16 apds990x_lux_to_threshold(struct apds990x_chip *chip, u32 lux) > +{ > + u32 thres; > + u32 cpl; > + u32 ir; > + > + if (lux == 0) > + return 0; > + else if (lux == APDS_RANGE) > + return APDS_RANGE; > + > + /* > + * Reported LUX value is a combination of the IR and CLEAR channel > + * values. However, interrupt threshold is only for clear channel. > + * This function approximates needed HW threshold value for a given > + * LUX value in the current lightning type. > + * IR level compared to visible light varies heavily depending on the > + * source of the light > + * > + * Calculate threshold value for the next measurement period. > + * Math: threshold = lux * cpl where > + * cpl = atime * again / (glass_attenuation * device_factor) > + * (count-per-lux) > + * > + * First remove calibration. Division by four is to avoid overflow > + */ > + lux = lux * (APDS_CALIB_SCALER / 4) / (chip->lux_calib / 4); > + > + /* Multiplication by 64 is to increase accuracy */ > + cpl = ((u32)chip->atime * (u32)again[chip->again_next] * > + APDS_PARAM_SCALE * 64) / (chip->cf.ga * chip->cf.df); > + > + thres = lux * cpl / 64; > + /* > + * Convert IR light from the latest result to match with > + * new gain step. This helps to adapt with the current > + * source of light. > + */ > + ir = (u32)chip->lux_ir * (u32)again[chip->again_next] / > + (u32)again[chip->again_meas]; > + > + /* > + * Compensate count with IR light impact > + * IAC1 > IAC2 (see apds990x_get_lux for formulas) > + */ > + if (chip->lux_clear * APDS_PARAM_SCALE >= > + chip->rcf.afactor * chip->lux_ir) > + thres = (chip->rcf.cf1 * thres + chip->rcf.irf1 * ir) / > + APDS_PARAM_SCALE; > + else > + thres = (chip->rcf.cf2 * thres + chip->rcf.irf2 * ir) / > + APDS_PARAM_SCALE; > + > + if (thres >= chip->a_max_result) > + thres = chip->a_max_result - 1; > + return thres; > +} > + > +static inline int apds990x_set_atime(struct apds990x_chip *chip, u32 time_ms) > +{ > + u8 reg_value; > + > + chip->atime = time_ms; > + /* Formula is specified in the data sheet */ > + reg_value = 256 - ((time_ms * TIME_STEP_SCALER) / TIMESTEP); > + /* Calculate max ADC value for given integration time */ > + chip->a_max_result = (u16)(256 - reg_value) * APDS990X_TIME_TO_ADC; > + return apds990x_write_byte(chip, APDS990X_ATIME, reg_value); > +} > + > +/* Called always with mutex locked */ > +static int apds990x_refresh_pthres(struct apds990x_chip *chip, int data) > +{ > + int ret, lo, hi; > + > + /* If the chip is not in use, don't try to access it */ > + if (pm_runtime_suspended(&chip->client->dev)) > + return 0; > + > + if (data < chip->prox_thres) { > + lo = 0; > + hi = chip->prox_thres; > + } else { > + lo = chip->prox_thres - APDS_PROX_HYSTERESIS; > + if (chip->prox_continuous_mode) > + hi = chip->prox_thres; > + else > + hi = APDS_RANGE; > + } > + > + ret = apds990x_write_word(chip, APDS990X_PILTL, lo); > + ret |= apds990x_write_word(chip, APDS990X_PIHTL, hi); > + return ret; > +} > + > +/* Called always with mutex locked */ > +static int apds990x_refresh_athres(struct apds990x_chip *chip) > +{ > + int ret; > + /* If the chip is not in use, don't try to access it */ > + if (pm_runtime_suspended(&chip->client->dev)) > + return 0; > + > + ret = apds990x_write_word(chip, APDS990X_AILTL, > + apds990x_lux_to_threshold(chip, chip->lux_thres_lo)); > + ret |= apds990x_write_word(chip, APDS990X_AIHTL, > + apds990x_lux_to_threshold(chip, chip->lux_thres_hi)); > + > + return ret; > +} > + > +/* Called always with mutex locked */ > +static void apds990x_force_a_refresh(struct apds990x_chip *chip) > +{ > + /* This will force ALS interrupt after the next measurement. */ > + apds990x_write_word(chip, APDS990X_AILTL, APDS_LUX_DEF_THRES_LO); > + apds990x_write_word(chip, APDS990X_AIHTL, APDS_LUX_DEF_THRES_HI); > +} > + > +/* Called always with mutex locked */ > +static void apds990x_force_p_refresh(struct apds990x_chip *chip) > +{ > + /* This will force proximity interrupt after the next measurement. */ > + apds990x_write_word(chip, APDS990X_PILTL, APDS_PROX_DEF_THRES - 1); > + apds990x_write_word(chip, APDS990X_PIHTL, APDS_PROX_DEF_THRES); > +} > + > +/* Called always with mutex locked */ > +static int apds990x_calc_again(struct apds990x_chip *chip) > +{ > + int curr_again = chip->again_meas; > + int next_again = chip->again_meas; > + int ret = 0; > + > + /* Calculate suitable als gain */ > + if (chip->lux_clear == chip->a_max_result) > + next_again -= 2; /* ALS saturated. Decrease gain by 2 steps */ > + else if (chip->lux_clear > chip->a_max_result / 2) > + next_again--; > + else if (chip->lux_clear < APDS_LUX_GAIN_LO_LIMIT_STRICT) > + next_again += 2; /* Too dark. Increase gain by 2 steps */ > + else if (chip->lux_clear < APDS_LUX_GAIN_LO_LIMIT) > + next_again++; > + > + /* Limit gain to available range */ > + if (next_again < 0) > + next_again = 0; > + else if (next_again > APDS990X_MAX_AGAIN) > + next_again = APDS990X_MAX_AGAIN; > + > + /* Let's check can we trust the measured result */ Excess brackets. Be it disguised by the lines of comments. > + if (chip->lux_clear == chip->a_max_result) { > + /* Result can be totally garbage due to saturation */ > + ret = -ERANGE; > + } else if (next_again != curr_again && > + chip->lux_clear < APDS_LUX_GAIN_LO_LIMIT_STRICT) { > + /* > + * Gain is changed and measurement result is very small. > + * Result can be totally garbage due to underflow > + */ > + ret = -ERANGE; > + } > + > + chip->again_next = next_again; > + apds990x_write_byte(chip, APDS990X_CONTROL, > + (chip->pdrive << 6) | > + (chip->pdiode << 4) | > + (chip->pgain << 2) | > + (chip->again_next << 0)); > + > + /* > + * Error means bad result -> re-measurement is needed. The forced > + * refresh uses fastest possible persistence setting to get result > + * as soon as possible. > + */ > + if (ret < 0) > + apds990x_force_a_refresh(chip); > + else > + apds990x_refresh_athres(chip); > + > + return ret; > +} > + > +/* Called always with mutex locked */ > +static int apds990x_get_lux(struct apds990x_chip *chip, int clear, int ir) > +{ > + int iac, iac1, iac2; /* IR adjusted counts */ > + u32 lpc; /* Lux per count */ > + > + /* Formulas: > + * iac1 = CF1 * CLEAR_CH - IRF1 * IR_CH > + * iac2 = CF2 * CLEAR_CH - IRF2 * IR_CH > + */ > + iac1 = (chip->cf.cf1 * clear - chip->cf.irf1 * ir) / APDS_PARAM_SCALE; > + iac2 = (chip->cf.cf2 * clear - chip->cf.irf2 * ir) / APDS_PARAM_SCALE; > + > + iac = max(iac1, iac2); > + iac = max(iac, 0); > + > + lpc = APDS990X_LUX_OUTPUT_SCALE * (chip->cf.df * chip->cf.ga) / > + (u32)(again[chip->again_meas] * (u32)chip->atime); > + > + return (iac * lpc) / APDS_PARAM_SCALE; > +} > + > +static int apds990x_ack_int(struct apds990x_chip *chip, u8 mode) > +{ > + struct i2c_client *client = chip->client; > + s32 ret; > + u8 reg = APDS990x_CMD | APDS990x_CMD_TYPE_SPE; > + > + switch (mode & (APDS990X_ST_AINT | APDS990X_ST_PINT)) { > + case APDS990X_ST_AINT: > + reg |= APDS990X_INT_ACK_ALS; > + break; > + case APDS990X_ST_PINT: > + reg |= APDS990X_INT_ACK_PS; > + break; > + default: > + reg |= APDS990X_INT_ACK_BOTH; > + break; > + } > + > + ret = i2c_smbus_read_byte_data(client, reg); > + return (int)ret; > +} > + > +static irqreturn_t apds990x_irq(int irq, void *data) > +{ > + struct apds990x_chip *chip = data; > + u8 status; > + > + apds990x_read_byte(chip, APDS990X_STATUS, &status); > + apds990x_ack_int(chip, status); > + > + mutex_lock(&chip->mutex); > + if (!pm_runtime_suspended(&chip->client->dev)) { > + if (status & APDS990X_ST_AINT) { > + apds990x_read_word(chip, APDS990X_CDATAL, > + &chip->lux_clear); > + apds990x_read_word(chip, APDS990X_IRDATAL, > + &chip->lux_ir); > + /* Store used gain for calculations */ > + chip->again_meas = chip->again_next; > + > + chip->lux_raw = apds990x_get_lux(chip, > + chip->lux_clear, > + chip->lux_ir); > + > + if (apds990x_calc_again(chip) == 0) { > + /* Result is valid */ > + chip->lux = chip->lux_raw; > + chip->lux_wait_fresh_res = false; > + wake_up(&chip->wait); > + sysfs_notify(&chip->client->dev.kobj, > + NULL, "lux0_input"); > + } > + } > + > + if (status & APDS990X_ST_PINT) > + if (chip->prox_en) { if ((status & APDS990X_ST_PINT) && chip->prox_en) perhaps? Gives you a bit more space for longer lines in here if nothing else. > + u16 clr_ch; > + > + apds990x_read_word(chip, APDS990X_CDATAL, > + &clr_ch); > + /* > + * If ALS channel is saturated at min gain, > + * proximity gives false posivite values. > + * Just ignore them. > + */ > + if (chip->again_meas == 0 && > + clr_ch == chip->a_max_result) { > + chip->prox_data = 0; > + } else { > + apds990x_read_word(chip, > + APDS990X_PDATAL, > + &chip->prox_data); > + } > + apds990x_refresh_pthres(chip, chip->prox_data); > + if (chip->prox_data < chip->prox_thres) > + chip->prox_data = 0; > + else if (!chip->prox_continuous_mode) > + chip->prox_data = APDS_PROX_RANGE; > + sysfs_notify(&chip->client->dev.kobj, > + NULL, "prox0_raw"); > + } > + } > + mutex_unlock(&chip->mutex); > + return IRQ_HANDLED; > +} > + > +static int apds990x_configure(struct apds990x_chip *chip) > +{ > + /* It is recommended to use disabled mode during these operations */ > + apds990x_write_byte(chip, APDS990X_ENABLE, APDS990X_EN_DISABLE_ALL); > + > + /* conversion and wait times for different state machince states */ > + apds990x_write_byte(chip, APDS990X_PTIME, APDS990X_PTIME_DEFAULT); > + apds990x_write_byte(chip, APDS990X_WTIME, APDS990X_WTIME_DEFAULT); > + apds990x_set_atime(chip, APDS_LUX_AVERAGING_TIME); > + > + apds990x_write_byte(chip, APDS990X_CONFIG, 0); > + > + /* > + * Total time for one FSM round without proximity. > + * One bit in wait is about 2.7 ms = 27 / 10. > + * rate = 1000 ms / fsm loop time in ms. > + */ > + chip->fsm_time = APDS_LUX_AVERAGING_TIME + > + (256 - APDS990X_WTIME_DEFAULT) * 27 / 10; > + chip->fsm_rate = 1000 / chip->fsm_time; > + > + /* Persistence levels */ > + apds990x_write_byte(chip, APDS990X_PERS, > + (chip->lux_persistence << APDS990X_APERS_SHIFT) | > + (chip->prox_persistence << APDS990X_PPERS_SHIFT)); > + > + apds990x_write_byte(chip, APDS990X_PPCOUNT, chip->pdata->ppcount); > + > + /* Start with relatively small gain */ > + chip->again_meas = 1; > + chip->again_next = 1; > + apds990x_write_byte(chip, APDS990X_CONTROL, > + (chip->pdrive << 6) | > + (chip->pdiode << 4) | > + (chip->pgain << 2) | > + (chip->again_next << 0)); > + return 0; > +} > + > +static int apds990x_detect(struct apds990x_chip *chip) > +{ > + struct i2c_client *client = chip->client; > + int ret; > + u8 id; > + > + ret = apds990x_read_byte(chip, APDS990X_ID, &id); > + if (ret < 0) { > + dev_err(&client->dev, "ID read failed\n"); > + return ret; > + } > + > + ret = apds990x_read_byte(chip, APDS990X_REV, &chip->revision); > + if (ret < 0) { > + dev_err(&client->dev, "REV read failed\n"); > + return ret; > + } > + > + switch (id) { > + case APDS990X_ID_0: > + case APDS990X_ID_4: > + case APDS990X_ID_29: > + snprintf(chip->chipname, sizeof(chip->chipname), "APDS-990x"); > + break; > + default: > + ret = -ENODEV; > + break; > + } > + return ret; > +} > + > +static int apds990x_chip_on(struct apds990x_chip *chip) > +{ > + int err = regulator_bulk_enable(ARRAY_SIZE(chip->regs), > + chip->regs); > + if (err < 0) > + return err; > + > + usleep_range(APDS_STARTUP_DELAY, 2 * APDS_STARTUP_DELAY); > + > + /* Refresh all configs in case of regulators were off */ > + apds990x_configure(chip); > + apds990x_mode_on(chip); > + return 0; > +} > + > +static int apds990x_chip_off(struct apds990x_chip *chip) > +{ > + apds990x_write_byte(chip, APDS990X_ENABLE, APDS990X_EN_DISABLE_ALL); > + regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs); > + return 0; > +} > + > +static ssize_t apds990x_lux_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + ssize_t ret; > + u32 result; > + long timeout; > + > + if (pm_runtime_suspended(dev)) > + return -EIO; > + > + timeout = wait_event_interruptible_timeout(chip->wait, > + !chip->lux_wait_fresh_res, > + msecs_to_jiffies(APDS_TIMEOUT)); > + if (!timeout) > + return -EIO; > + > + mutex_lock(&chip->mutex); > + result = (chip->lux * chip->lux_calib) / APDS_CALIB_SCALER; > + if (result > (APDS_RANGE * APDS990X_LUX_OUTPUT_SCALE)) > + result = APDS_RANGE * APDS990X_LUX_OUTPUT_SCALE; > + > + ret = snprintf(buf, PAGE_SIZE, "%d.%d\n", > + result / APDS990X_LUX_OUTPUT_SCALE, > + result % APDS990X_LUX_OUTPUT_SCALE); > + mutex_unlock(&chip->mutex); > + return ret; > +} > + > +static DEVICE_ATTR(lux0_input, S_IRUGO, apds990x_lux_show, NULL); > + > +static ssize_t apds990x_lux_range_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + return snprintf(buf, PAGE_SIZE, "%u\n", APDS_RANGE); > +} > + > +static DEVICE_ATTR(lux0_sensor_range, S_IRUGO, apds990x_lux_range_show, NULL); > + > +static ssize_t apds990x_lux_calib_format_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + return snprintf(buf, PAGE_SIZE, "%u\n", APDS_CALIB_SCALER); > +} Some nice paranoid length parameters... not really necessary but don't do much harm. > + > +static DEVICE_ATTR(lux0_calibscale_default, S_IRUGO, > + apds990x_lux_calib_format_show, NULL); > + > +static ssize_t apds990x_lux_calib_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + > + return snprintf(buf, PAGE_SIZE, "%u\n", chip->lux_calib); > +} > + > +static ssize_t apds990x_lux_calib_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + unsigned long value; > + > + if (strict_strtoul(buf, 0, &value)) > + return -EINVAL; > + > + if (chip->lux_calib > APDS_RANGE) > + return -EINVAL; > + > + chip->lux_calib = value; > + > + return len; > +} > + > +static DEVICE_ATTR(lux0_calibscale, S_IRUGO | S_IWUSR, apds990x_lux_calib_show, > + apds990x_lux_calib_store); > + > +static ssize_t apds990x_rate_avail(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + int i; > + int pos = 0; > + for (i = 0; i < ARRAY_SIZE(arates_hz); i++) > + pos += sprintf(buf + pos, "%d ", arates_hz[i]); > + sprintf(buf + pos - 1, "\n"); > + return pos; > +} > + > +static ssize_t apds990x_rate_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + return sprintf(buf, "%d\n", chip->arate); > +} > + > +static int apds990x_set_arate(struct apds990x_chip *chip, int rate) > +{ > + int i; > + > + for (i = 0; i < ARRAY_SIZE(arates_hz); i++) > + if (rate >= arates_hz[i]) > + break; > + > + if (i == ARRAY_SIZE(arates_hz)) > + return -EINVAL; > + > + /* Pick up corresponding persistence value */ > + chip->lux_persistence = apersis[i]; > + chip->arate = arates_hz[i]; > + > + /* If the chip is not in use, don't try to access it */ > + if (pm_runtime_suspended(&chip->client->dev)) > + return 0; > + > + /* Persistence levels */ > + return apds990x_write_byte(chip, APDS990X_PERS, > + (chip->lux_persistence << APDS990X_APERS_SHIFT) | > + (chip->prox_persistence << APDS990X_PPERS_SHIFT)); > +} > + > +static ssize_t apds990x_rate_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + unsigned long value; > + int ret; > + > + if (strict_strtoul(buf, 0, &value)) > + return -EINVAL; > + > + mutex_lock(&chip->mutex); > + ret = apds990x_set_arate(chip, value); > + mutex_unlock(&chip->mutex); > + > + if (ret < 0) > + return ret; > + return len; > +} > + > +static DEVICE_ATTR(lux0_rate_avail, S_IRUGO, apds990x_rate_avail, NULL); > + > +static DEVICE_ATTR(lux0_rate, S_IRUGO | S_IWUSR, apds990x_rate_show, > + apds990x_rate_store); > + > +static ssize_t apds990x_prox_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + ssize_t ret; > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + if (pm_runtime_suspended(dev) || !chip->prox_en) > + return -EIO; > + > + mutex_lock(&chip->mutex); > + ret = snprintf(buf, PAGE_SIZE, "%d\n", chip->prox_data); > + mutex_unlock(&chip->mutex); > + return ret; > +} > + > +static DEVICE_ATTR(prox0_raw, S_IRUGO, apds990x_prox_show, NULL); > + > +static ssize_t apds990x_prox_range_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + return snprintf(buf, PAGE_SIZE, "%u\n", APDS_PROX_RANGE); > +} > + > +static DEVICE_ATTR(prox0_sensor_range, S_IRUGO, apds990x_prox_range_show, NULL); > + > +static ssize_t apds990x_prox_enable_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + return sprintf(buf, "%d\n", chip->prox_en); > +} > + > +static ssize_t apds990x_prox_enable_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + unsigned long value; > + > + if (strict_strtoul(buf, 0, &value)) > + return -EINVAL; > + > + mutex_lock(&chip->mutex); > + if (value) { > + chip->prox_en++; Perhaps use else if and scrap the excess brackets. > + } else { > + if (chip->prox_en > 0) > + chip->prox_en--; > + } > + > + if (!pm_runtime_suspended(dev)) > + apds990x_mode_on(chip); > + mutex_unlock(&chip->mutex); > + return len; > +} > + > +static DEVICE_ATTR(prox0_raw_en, S_IRUGO | S_IWUSR, apds990x_prox_enable_show, > + apds990x_prox_enable_store); > + > +static const char reporting_modes[][9] = {"trigger", "periodic"}; > + > +static ssize_t apds990x_prox_reporting_mode_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + return sprintf(buf, "%s\n", > + reporting_modes[!!chip->prox_continuous_mode]); > +} > + > +static ssize_t apds990x_prox_reporting_mode_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + > + if (sysfs_streq(buf, reporting_modes[0])) > + chip->prox_continuous_mode = 0; > + else if (sysfs_streq(buf, reporting_modes[1])) > + chip->prox_continuous_mode = 1; > + else > + return -EINVAL; > + return len; > +} > + > +static DEVICE_ATTR(prox0_reporting_mode, S_IRUGO | S_IWUSR, > + apds990x_prox_reporting_mode_show, > + apds990x_prox_reporting_mode_store); > + > +static ssize_t apds990x_prox_reporting_avail_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + return sprintf(buf, "%s %s\n", reporting_modes[0], reporting_modes[1]); > +} > + > +static DEVICE_ATTR(prox0_reporting_mode_avail, S_IRUGO | S_IWUSR, > + apds990x_prox_reporting_avail_show, NULL); > + > + > +static ssize_t apds990x_lux_thresh_above_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + return sprintf(buf, "%d\n", chip->lux_thres_hi); > +} > + > +static ssize_t apds990x_lux_thresh_below_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + return sprintf(buf, "%d\n", chip->lux_thres_lo); > +} > + > +static ssize_t apds990x_set_lux_thresh(struct apds990x_chip *chip, u32 *target, > + const char *buf) > +{ > + int ret = 0; > + unsigned long thresh; > + > + if (strict_strtoul(buf, 0, &thresh)) > + return -EINVAL; > + > + if (thresh > APDS_RANGE) > + return -EINVAL; > + > + mutex_lock(&chip->mutex); > + *target = thresh; > + /* > + * Don't update values in HW if we are still waiting for > + * first interrupt to come after device handle open call. > + */ > + if (!chip->lux_wait_fresh_res) > + apds990x_refresh_athres(chip); > + mutex_unlock(&chip->mutex); > + return ret; > + > +} > + > +static ssize_t apds990x_lux_thresh_above_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + int ret = apds990x_set_lux_thresh(chip, &chip->lux_thres_hi, buf); > + if (ret < 0) > + return ret; > + return len; > +} > + > +static ssize_t apds990x_lux_thresh_below_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + int ret = apds990x_set_lux_thresh(chip, &chip->lux_thres_lo, buf); > + if (ret < 0) > + return ret; > + return len; > +} > + > +static DEVICE_ATTR(lux0_thresh_above_value, S_IRUGO | S_IWUSR, > + apds990x_lux_thresh_above_show, > + apds990x_lux_thresh_above_store); > + > +static DEVICE_ATTR(lux0_thresh_below_value, S_IRUGO | S_IWUSR, > + apds990x_lux_thresh_below_show, > + apds990x_lux_thresh_below_store); > + > +static ssize_t apds990x_prox_threshold_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + return sprintf(buf, "%d\n", chip->prox_thres); > +} > + > +static ssize_t apds990x_prox_threshold_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + unsigned long value; > + > + if (strict_strtoul(buf, 0, &value)) > + return -EINVAL; > + > + if ((value > APDS_RANGE) || (value == 0) || > + (value < APDS_PROX_HYSTERESIS)) > + return -EINVAL; > + > + mutex_lock(&chip->mutex); > + chip->prox_thres = value; > + > + apds990x_force_p_refresh(chip); > + mutex_unlock(&chip->mutex); > + return len; > +} > + > +static DEVICE_ATTR(prox0_thresh_above_value, S_IRUGO | S_IWUSR, > + apds990x_prox_threshold_show, > + apds990x_prox_threshold_store); > + > +static ssize_t apds990x_power_state_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + return sprintf(buf, "%d\n", !pm_runtime_suspended(dev)); > + return 0; > +} > + > +static ssize_t apds990x_power_state_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + unsigned long value; > + > + if (strict_strtoul(buf, 0, &value)) > + return -EINVAL; > + if (value) { > + pm_runtime_get_sync(dev); > + mutex_lock(&chip->mutex); > + chip->lux_wait_fresh_res = true; > + apds990x_force_a_refresh(chip); > + apds990x_force_p_refresh(chip); > + mutex_unlock(&chip->mutex); > + } else { > + if (!pm_runtime_suspended(dev)) > + pm_runtime_put(dev); > + } > + return len; > +} > + > +static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, > + apds990x_power_state_show, > + apds990x_power_state_store); > + > +static ssize_t apds990x_chip_id_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct apds990x_chip *chip = dev_get_drvdata(dev); > + return sprintf(buf, "%s %d\n", chip->chipname, chip->revision); > +} > + > +static DEVICE_ATTR(chip_id, S_IRUGO, apds990x_chip_id_show, NULL); > + > +static struct attribute *sysfs_attrs_ctrl[] = { > + &dev_attr_lux0_calibscale.attr, > + &dev_attr_lux0_calibscale_default.attr, > + &dev_attr_lux0_input.attr, > + &dev_attr_lux0_sensor_range.attr, > + &dev_attr_lux0_rate.attr, > + &dev_attr_lux0_rate_avail.attr, > + &dev_attr_lux0_thresh_above_value.attr, > + &dev_attr_lux0_thresh_below_value.attr, > + &dev_attr_prox0_raw_en.attr, > + &dev_attr_prox0_raw.attr, > + &dev_attr_prox0_sensor_range.attr, > + &dev_attr_prox0_thresh_above_value.attr, > + &dev_attr_prox0_reporting_mode.attr, > + &dev_attr_prox0_reporting_mode_avail.attr, > + &dev_attr_chip_id.attr, > + &dev_attr_power_state.attr, > + NULL > +}; > + > +static struct attribute_group apds990x_attribute_group[] = { > + {.attrs = sysfs_attrs_ctrl }, > +}; > + > +static int __devinit apds990x_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct apds990x_chip *chip; > + int err; > + > + chip = kzalloc(sizeof *chip, GFP_KERNEL); > + if (!chip) > + return -ENOMEM; > + > + i2c_set_clientdata(client, chip); > + chip->client = client; > + > + init_waitqueue_head(&chip->wait); > + mutex_init(&chip->mutex); > + chip->pdata = client->dev.platform_data; > + > + if (chip->pdata == NULL) { > + dev_err(&client->dev, "platform data is mandatory\n"); > + err = -EINVAL; > + goto fail1; > + } > + > + if (chip->pdata->cf.ga == 0) { > + /* set uncovered sensor default parameters */ > + chip->cf.ga = 1966; /* 0.48 * APDS_PARAM_SCALE */ > + chip->cf.cf1 = 4096; /* 1.00 * APDS_PARAM_SCALE */ > + chip->cf.irf1 = 9134; /* 2.23 * APDS_PARAM_SCALE */ > + chip->cf.cf2 = 2867; /* 0.70 * APDS_PARAM_SCALE */ > + chip->cf.irf2 = 5816; /* 1.42 * APDS_PARAM_SCALE */ > + chip->cf.df = 52; > + } else { > + chip->cf = chip->pdata->cf; > + } > + > + /* precalculate inverse chip factors for threshold control */ > + chip->rcf.afactor = > + (chip->cf.irf1 - chip->cf.irf2) * APDS_PARAM_SCALE / > + (chip->cf.cf1 - chip->cf.cf2); > + chip->rcf.cf1 = APDS_PARAM_SCALE * APDS_PARAM_SCALE / > + chip->cf.cf1; > + chip->rcf.irf1 = chip->cf.irf1 * APDS_PARAM_SCALE / > + chip->cf.cf1; > + chip->rcf.cf2 = APDS_PARAM_SCALE * APDS_PARAM_SCALE / > + chip->cf.cf2; > + chip->rcf.irf2 = chip->cf.irf2 * APDS_PARAM_SCALE / > + chip->cf.cf2; > + > + /* Set something to start with */ > + chip->lux_thres_hi = APDS_LUX_DEF_THRES_HI; > + chip->lux_thres_lo = APDS_LUX_DEF_THRES_LO; > + chip->lux_calib = APDS_LUX_NEUTRAL_CALIB_VALUE; > + > + chip->prox_thres = APDS_PROX_DEF_THRES; > + chip->pdrive = chip->pdata->pdrive; > + chip->pdiode = APDS_PDIODE_IR; > + chip->pgain = APDS_PGAIN_1X; > + chip->prox_calib = APDS_PROX_NEUTRAL_CALIB_VALUE; > + chip->prox_persistence = APDS_DEFAULT_PROX_PERS; > + chip->prox_continuous_mode = false; > + > + chip->regs[0].supply = reg_vcc; > + chip->regs[1].supply = reg_vled; > + > + err = regulator_bulk_get(&client->dev, > + ARRAY_SIZE(chip->regs), chip->regs); > + if (err < 0) { > + dev_err(&client->dev, "Cannot get regulators\n"); > + goto fail1; > + } > + > + err = regulator_bulk_enable(ARRAY_SIZE(chip->regs), chip->regs); > + if (err < 0) { > + dev_err(&client->dev, "Cannot enable regulators\n"); > + goto fail2; > + } > + > + usleep_range(APDS_STARTUP_DELAY, 2 * APDS_STARTUP_DELAY); > + > + err = apds990x_detect(chip); > + if (err < 0) { > + dev_err(&client->dev, "APDS990X not found\n"); > + goto fail3; > + } > + > + pm_runtime_set_active(&client->dev); > + > + apds990x_configure(chip); > + apds990x_set_arate(chip, APDS_LUX_DEFAULT_RATE); > + apds990x_mode_on(chip); > + > + pm_runtime_enable(&client->dev); > + > + if (chip->pdata->setup_resources) { > + err = chip->pdata->setup_resources(); > + if (err) { > + err = -EINVAL; > + goto fail3; > + } > + } > + > + err = sysfs_create_group(&chip->client->dev.kobj, > + apds990x_attribute_group); > + if (err < 0) { > + dev_err(&chip->client->dev, "Sysfs registration failed\n"); > + goto fail4; > + } > + > + err = request_threaded_irq(client->irq, NULL, > + apds990x_irq, > + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_LOW | > + IRQF_ONESHOT, > + "apds990x", chip); > + if (err) { > + dev_err(&client->dev, "could not get IRQ %d\n", > + client->irq); > + goto fail5; > + } > + return err; > +fail5: > + sysfs_remove_group(&chip->client->dev.kobj, > + &apds990x_attribute_group[0]); > +fail4: > + if (chip->pdata && chip->pdata->release_resources) > + chip->pdata->release_resources(); > +fail3: > + regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs); > +fail2: > + regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs); > +fail1: > + kfree(chip); > + return err; > +} > + > +static int __devexit apds990x_remove(struct i2c_client *client) > +{ > + struct apds990x_chip *chip = i2c_get_clientdata(client); > + > + free_irq(client->irq, chip); > + sysfs_remove_group(&chip->client->dev.kobj, > + apds990x_attribute_group); > + > + if (chip->pdata && chip->pdata->release_resources) > + chip->pdata->release_resources(); > + > + if (!pm_runtime_suspended(&client->dev)) > + apds990x_chip_off(chip); > + > + regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs); > + > + kfree(chip); > + return 0; > +} > + > +#ifdef CONFIG_PM > +static int apds990x_suspend(struct device *dev) > +{ > + struct i2c_client *client = container_of(dev, struct i2c_client, dev); > + struct apds990x_chip *chip = i2c_get_clientdata(client); > + > + apds990x_chip_off(chip); > + return 0; > +} > + > +static int apds990x_resume(struct device *dev) > +{ > + struct i2c_client *client = container_of(dev, struct i2c_client, dev); > + struct apds990x_chip *chip = i2c_get_clientdata(client); > + > + /* > + * If we were enabled at suspend time, it is expected > + * everything works nice and smoothly. Chip_on is enough > + */ > + apds990x_chip_on(chip); > + > + return 0; > +} > +#else > +#define apds990x_suspend NULL > +#define apds990x_resume NULL > +#define apds990x_shutdown NULL > +#endif > + > +#ifdef CONFIG_PM_RUNTIME > +static int apds990x_runtime_suspend(struct device *dev) > +{ > + struct i2c_client *client = container_of(dev, struct i2c_client, dev); > + struct apds990x_chip *chip = i2c_get_clientdata(client); > + > + apds990x_chip_off(chip); > + return 0; > +} > + > +static int apds990x_runtime_resume(struct device *dev) > +{ > + struct i2c_client *client = container_of(dev, struct i2c_client, dev); > + struct apds990x_chip *chip = i2c_get_clientdata(client); > + > + apds990x_chip_on(chip); > + return 0; > +} > + > +#endif > + > +static const struct i2c_device_id apds990x_id[] = { > + {"apds990x", 0 }, > + {} > +}; > + > +MODULE_DEVICE_TABLE(i2c, apds990x_id); > + > +static const struct dev_pm_ops apds990x_pm_ops = { > + SET_SYSTEM_SLEEP_PM_OPS(apds990x_suspend, apds990x_resume) > + SET_RUNTIME_PM_OPS(apds990x_runtime_suspend, > + apds990x_runtime_resume, > + NULL) > +}; > + > +static struct i2c_driver apds990x_driver = { > + .driver = { > + .name = "apds990x", > + .owner = THIS_MODULE, > + .pm = &apds990x_pm_ops, > + }, > + .probe = apds990x_probe, > + .remove = __devexit_p(apds990x_remove), > + .id_table = apds990x_id, > +}; > + > +static int __init apds990x_init(void) > +{ > + return i2c_add_driver(&apds990x_driver); > +} > + > +static void __exit apds990x_exit(void) > +{ > + i2c_del_driver(&apds990x_driver); > +} > + > +MODULE_DESCRIPTION("APDS990X combined ALS and proximity sensor"); > +MODULE_AUTHOR("Samu Onkalo, Nokia Corporation"); > +MODULE_LICENSE("GPL v2"); > + > +module_init(apds990x_init); > +module_exit(apds990x_exit); > diff --git a/include/linux/i2c/apds990x.h b/include/linux/i2c/apds990x.h > new file mode 100644 > index 0000000..7d521e3 > --- /dev/null > +++ b/include/linux/i2c/apds990x.h > @@ -0,0 +1,58 @@ > +/* > + * This file is part of the APDS990x sensor driver. > + * Chip is combined proximity and ambient light sensor. > + * > + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). > + * > + * Contact: Samu Onkalo <samu.p.onkalo@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + * > + */ > + > +#ifndef __APDS990X_H__ > +#define __APDS990X_H__ > + > + > +#define APDS_IRLED_CURR_12mA 0x3 > +#define APDS_IRLED_CURR_25mA 0x2 > +#define APDS_IRLED_CURR_50mA 0x1 > +#define APDS_IRLED_CURR_100mA 0x0 > + > +/* > + * Structure for tuning ALS calculation to match with environment. > + * There depends on the material above the sensor and the sensor > + * itself. If the GA is zero, driver will use uncovered sensor default values > + * format: decimal value * APDS_PARAM_SCALE > + */ > +#define APDS_PARAM_SCALE 4096 > +struct apds990x_chip_factors { > + int ga; /* Glass attenuation */ > + int cf1; /* Clear channel factor 1 */ > + int irf1; /* Ir channel factor 1 */ > + int cf2; /* Clear channel factor 2 */ > + int irf2; /* Ir channel factor 2 */ > + int df; /* Device factor. Decimal number */ > +}; > + > +struct apds990x_platform_data { > + struct apds990x_chip_factors cf; > + u8 pdrive; > + u8 ppcount; > + int (*setup_resources)(void); > + int (*release_resources)(void); > +}; > + > +#endif ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCHv2 4/5] misc: update apds990x driver to Kconfig and Makefile 2010-10-08 13:41 [PATCHv2 0/5] BH1770GLC, SFH7770 and APDS990x als / proximity sensor drivers Samu Onkalo ` (2 preceding siblings ...) 2010-10-08 13:42 ` [PATCHv2 3/5] misc: Driver for APDS990X ALS and proximity sensors Samu Onkalo @ 2010-10-08 13:42 ` Samu Onkalo 2010-10-09 23:43 ` Randy Dunlap 2010-10-08 13:42 ` [PATCHv2 5/5] Documentation: Short descriptions for bhsfh and apds990x drivers Samu Onkalo 4 siblings, 1 reply; 16+ messages in thread From: Samu Onkalo @ 2010-10-08 13:42 UTC (permalink / raw) To: linux-i2c, linux-kernel, jic23, alan Compilation support for apds990x driver Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com> --- drivers/misc/Kconfig | 11 +++++++++++ drivers/misc/Makefile | 1 + 2 files changed, 12 insertions(+), 0 deletions(-) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index f2e8065..cc7c363 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -324,6 +324,17 @@ config SENSORS_BHSFH To compile this driver as a module, choose M here: the module will be called bhsfh. If unsure, say N here. +config SENSORS_APDS990X + tristate "APDS990X combined als and proximity sensors" + depends on I2C + default n + ---help--- + Say Y here if you want to build a driver for Avago APDS990x + combined ambient light and proximity sensor chip + + To compile this driver as a module, choose M here: the + module will be called apds990x. If unsure, say N here. + config HMC6352 tristate "Honeywell HMC6352 compass" depends on I2C diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index fd5a4b7..b90cea7 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o obj-$(CONFIG_PHANTOM) += phantom.o obj-$(CONFIG_SENSORS_BH1780) += bh1780gli.o obj-$(CONFIG_SENSORS_BHSFH) += bhsfh.o +obj-$(CONFIG_SENSORS_APDS990X) += apds990x.o obj-$(CONFIG_SGI_IOC4) += ioc4.o obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o obj-$(CONFIG_KGDB_TESTS) += kgdbts.o -- 1.6.0.4 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCHv2 4/5] misc: update apds990x driver to Kconfig and Makefile 2010-10-08 13:42 ` [PATCHv2 4/5] misc: update apds990x driver to Kconfig and Makefile Samu Onkalo @ 2010-10-09 23:43 ` Randy Dunlap 0 siblings, 0 replies; 16+ messages in thread From: Randy Dunlap @ 2010-10-09 23:43 UTC (permalink / raw) To: Samu Onkalo; +Cc: linux-i2c, linux-kernel, jic23, alan On Fri, 8 Oct 2010 16:42:03 +0300 Samu Onkalo wrote: > Compilation support for apds990x driver > > Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com> > --- > drivers/misc/Kconfig | 11 +++++++++++ > drivers/misc/Makefile | 1 + > 2 files changed, 12 insertions(+), 0 deletions(-) > > diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig > index f2e8065..cc7c363 100644 > --- a/drivers/misc/Kconfig > +++ b/drivers/misc/Kconfig > @@ -324,6 +324,17 @@ config SENSORS_BHSFH > To compile this driver as a module, choose M here: the > module will be called bhsfh. If unsure, say N here. > > +config SENSORS_APDS990X > + tristate "APDS990X combined als and proximity sensors" > + depends on I2C > + default n > + ---help--- > + Say Y here if you want to build a driver for Avago APDS990x > + combined ambient light and proximity sensor chip end sentence with period ('.') > + > + To compile this driver as a module, choose M here: the > + module will be called apds990x. If unsure, say N here. > + > config HMC6352 > tristate "Honeywell HMC6352 compass" > depends on I2C > diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile > index fd5a4b7..b90cea7 100644 > --- a/drivers/misc/Makefile > +++ b/drivers/misc/Makefile > @@ -17,6 +17,7 @@ obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o > obj-$(CONFIG_PHANTOM) += phantom.o > obj-$(CONFIG_SENSORS_BH1780) += bh1780gli.o > obj-$(CONFIG_SENSORS_BHSFH) += bhsfh.o > +obj-$(CONFIG_SENSORS_APDS990X) += apds990x.o > obj-$(CONFIG_SGI_IOC4) += ioc4.o > obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o > obj-$(CONFIG_KGDB_TESTS) += kgdbts.o > -- --- ~Randy *** Remember to use Documentation/SubmitChecklist when testing your code *** ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCHv2 5/5] Documentation: Short descriptions for bhsfh and apds990x drivers 2010-10-08 13:41 [PATCHv2 0/5] BH1770GLC, SFH7770 and APDS990x als / proximity sensor drivers Samu Onkalo ` (3 preceding siblings ...) 2010-10-08 13:42 ` [PATCHv2 4/5] misc: update apds990x driver to Kconfig and Makefile Samu Onkalo @ 2010-10-08 13:42 ` Samu Onkalo 2010-10-08 14:07 ` Jonathan Cameron 4 siblings, 1 reply; 16+ messages in thread From: Samu Onkalo @ 2010-10-08 13:42 UTC (permalink / raw) To: linux-i2c, linux-kernel, jic23, alan Add short documentation for two ALS / proximity chip drivers. bhsfh document update Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com> --- Documentation/misc-devices/apds990x.txt | 152 +++++++++++++++++++++++++++++++ Documentation/misc-devices/bhsfh.txt | 148 ++++++++++++++++++++++++++++++ 2 files changed, 300 insertions(+), 0 deletions(-) create mode 100644 Documentation/misc-devices/apds990x.txt create mode 100644 Documentation/misc-devices/bhsfh.txt diff --git a/Documentation/misc-devices/apds990x.txt b/Documentation/misc-devices/apds990x.txt new file mode 100644 index 0000000..7b61495 --- /dev/null +++ b/Documentation/misc-devices/apds990x.txt @@ -0,0 +1,152 @@ +Kernel driver apds990x +====================== + +Supported chips: +Avago APDS990X + +Data sheet: +Not freely available + +Author: +Samu Onkalo <samu.p.onkalo@nokia.com> + +Description +----------- + +APDS990x is a combined ambient light and proximity sensor. ALS and proximity +functionality are highly connected. ALS measurement path must be running +while the proximity functionality is enabled. + +ALS produces only raw measurement values. Further processing is needed +to get sensible LUX values. Vice versa, HW threshold control has nothing +to do with LUX values. Threshold detection triggs to raw conversion values. +Driver makes necessary conversions to both directions so that user handles +only LUX values. ALS contains 4 different gain steps. Driver automatically +selects suitable gain step. After each measurement, reliability of the results +is estimated and new measurement is trigged if necessary. + +Platform data can provide tuned values to the conversion formulas if +values are known. Otherwise plain sensor default values are used. + +Proximity side is little bit simpler. There is no need for complex conversions. +It produces directly usable values. + +Driver controls chip operational state using pm_runtime framework. +Voltage regulators are controlled based on chip operational state. + +SYSFS +----- + + +chip_id + RO - shows detected chip type and version + +power_state + RW - enable / disable chip. Uses counting logic + 1 enables the chip + 0 disables the chip +lux0_input + RO - measured LUX value + sysfs_notify called when threshold interrupt occurs + +lux0_sensor_range + RO - lux0_input max value. Actually never reaches since sensor tends + to saturate much before that. Real max value varies depending + on the light spectrum etc. + + +lux0_rate + RW - measurement rate in Hz + +lux0_rate_avail + RO - supported measurement rates + +lux0_calibscale + RW - calibration value. Set to neutral value by default. + Output results are multiplied with calibscale / calibscale_default + value. + +lux0_calibscale_default + RO - neutral calibration value + +lux0_thresh_above_value + RW - HI level threshold value. All results above the value + trigs an interrupt. 65535 (i.e. sensor_range) disables the above + interrupt. + +lux0_thresh_below_value + RW - LO level threshold value. All results below the value + trigs an interrupt. 0 disables the below interrupt. + +prox0_raw + RO - measured proximity value + sysfs_notify called when threshold interrupt occurs + +prox0_sensor_range + RO - prox0_raw max value (1023) + +prox0_raw_en + RW - enable / disable proximity - uses counting logic + 1 enables the proximity + 0 disables the proximity + +prox0_reporting_mode + RW - trigger / periodic. In "trigger" mode the driver tells two possible + values: 0 or prox0_sensor_range value. 0 means no proximity, + 1023 means proximity. This causes minimal number of interrupts. + In "periodic" mode the driver reports all values above + prox0_thresh_above. This causes more interrupts, but it can give + _rough_ estimate about the distance. + +prox0_reporting_mode_avail + RO - accepted values to prox0_reporting_mode (trigger, periodic) + +prox0_thresh_above_value + RW - threshold level which trigs proximity events. + +Platform data +------------- + + +#define APDS_IRLED_CURR_12mA 0x3 +#define APDS_IRLED_CURR_25mA 0x2 +#define APDS_IRLED_CURR_50mA 0x1 +#define APDS_IRLED_CURR_100mA 0x0 + +/* + * Structure for tuning ALS calculation to match with environment. + * There depends on the material above the sensor and the sensor + * itself. If the GA is zero, driver will use uncovered sensor default values + * format: decimal value * APDS_PARAM_SCALE + */ +#define APDS_PARAM_SCALE 4096 +struct apds990x_chip_factors { + int ga; /* Glass attenuation */ + int cf1; /* Clear channel factor 1 */ + int irf1; /* Ir channel factor 1 */ + int cf2; /* Clear channel factor 2 */ + int irf2; /* Ir channel factor 2 */ + int df; /* Device factor. Decimal number */ +}; + +struct apds990x_platform_data { + struct apds990x_chip_factors cf; + u8 pdrive; + u8 ppcount; + int (*setup_resources)(void); + int (*release_resources)(void); +}; + +chip factors are specific to for example dark plastic window +above the sensor. Driver uses plain (uncovered) sensor default values +if the platform data contains zero ga factor. + +"pdrive" is the IR led driver current + +"ppcount" is the number of the IR pulses used for proximity detection. +5 is for example quite good value. + +Proximity detection range and overall behaviour is heavily affected +by cover window, IR led driving strength and ppcount. + +setup / release resources handles interrupt line configurations. diff --git a/Documentation/misc-devices/bhsfh.txt b/Documentation/misc-devices/bhsfh.txt new file mode 100644 index 0000000..84568fc --- /dev/null +++ b/Documentation/misc-devices/bhsfh.txt @@ -0,0 +1,148 @@ +Kernel driver bhsfh +=================== + +Supported chips: +ROHM BH1770GLC +OSRAM SFH7770 + +Data sheet: +Not freely available + +Author: +Samu Onkalo <samu.p.onkalo@nokia.com> + +Description +----------- +BH1770GLC and SFH7770 (I'm calling them as bhsfh) are combined ambient +light and proximity sensors. ALS and proximity parts operates on their own, +but they shares common I2C interface and interrupt logic. + +ALS produces 16 bit LUX values. The chip contains interrupt logic to produce +low and high threshold interrupts. + +Proximity part contains IR-led driver up to 3 IR leds. The chip measures +amount of reflected IR light and produces proximity result. Resolution is +8 bit. Driver supports only one channel. Driver uses ALS results to estimate +realibility of the proximity results. Thus ALS is always running while +proximity detection is needed. + +Driver uses threshold interrupts to avoid need for polling the values. +Proximity low interrupt doesn't exists in the chip. This is simulated +by using a delayed work. + +Chip state is controlled via runtime pm framework when enabled in config. + +Calibscale factor is used to hide differences between the chips. By default +value set to neutral state meaning factor of 1.00. To get proper values, +calibrated source of light is needed as a reference. Calibscale factor is set +so that measurement produces about the expected lux value. + +SYSFS +----- + +chip_id + RO - shows detected chip type and version + +power_state + RW - enable / disable chip. Uses counting logic + 1 enables the chip + 0 disables the chip + +lux0_input + RO - measured LUX value + sysfs_notify called when threshold interrupt occurs + +lux0_sensor_range + RO - lux0_input max value + +lux0_rate + RW - measurement rate in Hz + +lux0_rate_avail + RO - supported measurement rates + +lux0_thresh_above_value + RW - HI level threshold value. All results above the value + trigs an interrupt. 65535 (i.e. sensor_range) disables the above + interrupt. + +lux0_thresh_below_value + RW - LO level threshold value. All results below the value + trigs an interrupt. 0 disables the below interrupt. + +lux0_calibscale + RW - calibration value. Set to neutral value by default. + Output results are multiplied with calibscale / calibscale_default + value. + +lux0_calibscale_default + RO - neutral calibration value + +prox0_raw + RO - measured proximity value + sysfs_notify called when threshold interrupt occurs + +prox0_sensor_range + RO - prox0_raw max value + +prox0_raw_en + RW - enable / disable proximity - uses counting logic + 1 enables the proximity + 0 disables the proximity + +prox0_thresh_above_count + RW - number of proximity interrupts needed before triggering the event + +prox0_rate_above + RW - Measurement rate (in Hz) when the level is above threshold + i.e. when proximity on has been reported. + +prox0_rate_below + RW - Measurement rate (in Hz) when the level is below threshold + i.e. when proximity off has been reported. + +prox0_rate_avail + RO - Supported proxity measurement rates in Hz + + +prox0_thresh_above0_value + RW - threshold level which trigs proximity events. + Filtered by persistence filter (prox0_thresh_above_count) + +prox0_thresh_above1_value + RW - threshold level which trigs event immediately + + +Platform data: +-------------- + +struct bhsfh_platform_data { +#define BHSFH_LED_5mA 0 +#define BHSFH_LED_10mA 1 +#define BHSFH_LED_20mA 2 +#define BHSFH_LED_50mA 3 +#define BHSFH_LED_100mA 4 +#define BHSFH_LED_150mA 5 +#define BHSFH_LED_200mA 6 + __u8 led_def_curr; +#define BHFSH_NEUTRAL_GA 16384 /* 16384 / 16384 = 1 */ + __u32 glass_attenuation; + int (*setup_resources)(void); + int (*release_resources)(void); +}; + +led_def_curr controls IR led driving current +glass_attenuation tells darkness of the covering window +setup / release resources are call back functions for controlling +interrupt line. + +Example: +static struct bhsfh_platform_data rm680_bhsfh_data = { + .led_def_curr = BHSFH_LED_50mA, + .glass_attenuation = (16384 * 385) / 100, + .setup_resources = bhsfh_setup, + .release_resources = bhsfh_release, +}; + +glass_attenuation: 385 in above formula means 3.85 in decimal. +light_above_sensors = light_above_cover_window / 3.85 -- 1.6.0.4 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCHv2 5/5] Documentation: Short descriptions for bhsfh and apds990x drivers 2010-10-08 13:42 ` [PATCHv2 5/5] Documentation: Short descriptions for bhsfh and apds990x drivers Samu Onkalo @ 2010-10-08 14:07 ` Jonathan Cameron 0 siblings, 0 replies; 16+ messages in thread From: Jonathan Cameron @ 2010-10-08 14:07 UTC (permalink / raw) To: Samu Onkalo; +Cc: linux-i2c, linux-kernel, alan On 10/08/10 14:42, Samu Onkalo wrote: > Add short documentation for two ALS / proximity chip drivers. > > bhsfh document update Hi Samu, Couple of clarity related questions below and some typos. Otherwise pretty much fine. I wonder if it would be useful to put some of the platform data docs in the headers in kernel doc format as well (or instead of here)? I still personally prefer proximity rather than prox (and the isl29018 driver under IIO uses that and is in staging-next so will merge in the next window). Then again, I also prefer illuminance and I seem to have lost that one for now ;) Fix the typos and feel free to add, Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > > Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com> > --- > Documentation/misc-devices/apds990x.txt | 152 +++++++++++++++++++++++++++++++ > Documentation/misc-devices/bhsfh.txt | 148 ++++++++++++++++++++++++++++++ > 2 files changed, 300 insertions(+), 0 deletions(-) > create mode 100644 Documentation/misc-devices/apds990x.txt > create mode 100644 Documentation/misc-devices/bhsfh.txt > > diff --git a/Documentation/misc-devices/apds990x.txt b/Documentation/misc-devices/apds990x.txt > new file mode 100644 > index 0000000..7b61495 > --- /dev/null > +++ b/Documentation/misc-devices/apds990x.txt > @@ -0,0 +1,152 @@ > +Kernel driver apds990x > +====================== > + > +Supported chips: > +Avago APDS990X > + > +Data sheet: > +Not freely available > + > +Author: > +Samu Onkalo <samu.p.onkalo@nokia.com> > + > +Description > +----------- > + > +APDS990x is a combined ambient light and proximity sensor. ALS and proximity > +functionality are highly connected. ALS measurement path must be running > +while the proximity functionality is enabled. > + > +ALS produces only raw measurement values. Further processing is needed > +to get sensible LUX values. Vice versa, HW threshold control has nothing > +to do with LUX values. Threshold detection triggs to raw conversion values. Not quite clear what the above means. Perhaps "Threshold detection is a comparison with the raw conversion value."? > +Driver makes necessary conversions to both directions so that user handles in both directions > +only LUX values. ALS contains 4 different gain steps. Driver automatically > +selects suitable gain step. After each measurement, reliability of the results > +is estimated and new measurement is trigged if necessary. > + > +Platform data can provide tuned values to the conversion formulas if > +values are known. Otherwise plain sensor default values are used. > + > +Proximity side is little bit simpler. There is no need for complex conversions. > +It produces directly usable values. > + > +Driver controls chip operational state using pm_runtime framework. > +Voltage regulators are controlled based on chip operational state. > + > +SYSFS > +----- > + > + > +chip_id > + RO - shows detected chip type and version > + > +power_state > + RW - enable / disable chip. Uses counting logic > + 1 enables the chip > + 0 disables the chip > +lux0_input > + RO - measured LUX value > + sysfs_notify called when threshold interrupt occurs > + > +lux0_sensor_range > + RO - lux0_input max value. Actually never reaches since sensor tends > + to saturate much before that. Real max value varies depending > + on the light spectrum etc. (aside: That's why I'm anti range and favour 'scale' as the means of controlling this - fine here though and a nice explanation) > + > + > +lux0_rate > + RW - measurement rate in Hz > + > +lux0_rate_avail > + RO - supported measurement rates > + > +lux0_calibscale > + RW - calibration value. Set to neutral value by default. > + Output results are multiplied with calibscale / calibscale_default > + value. > + > +lux0_calibscale_default > + RO - neutral calibration value > + > +lux0_thresh_above_value > + RW - HI level threshold value. All results above the value > + trigs an interrupt. 65535 (i.e. sensor_range) disables the above > + interrupt. > + > +lux0_thresh_below_value > + RW - LO level threshold value. All results below the value > + trigs an interrupt. 0 disables the below interrupt. > + > +prox0_raw > + RO - measured proximity value > + sysfs_notify called when threshold interrupt occurs > + > +prox0_sensor_range > + RO - prox0_raw max value (1023) > + > +prox0_raw_en > + RW - enable / disable proximity - uses counting logic > + 1 enables the proximity > + 0 disables the proximity > + > +prox0_reporting_mode > + RW - trigger / periodic. In "trigger" mode the driver tells two possible > + values: 0 or prox0_sensor_range value. 0 means no proximity, > + 1023 means proximity. This causes minimal number of interrupts. > + In "periodic" mode the driver reports all values above > + prox0_thresh_above. This causes more interrupts, but it can give > + _rough_ estimate about the distance. > + > +prox0_reporting_mode_avail > + RO - accepted values to prox0_reporting_mode (trigger, periodic) > + > +prox0_thresh_above_value > + RW - threshold level which trigs proximity events. > + > +Platform data > +------------- > + > + > +#define APDS_IRLED_CURR_12mA 0x3 > +#define APDS_IRLED_CURR_25mA 0x2 > +#define APDS_IRLED_CURR_50mA 0x1 > +#define APDS_IRLED_CURR_100mA 0x0 > + > +/* > + * Structure for tuning ALS calculation to match with environment. > + * There depends on the material above the sensor and the sensor > + * itself. If the GA is zero, driver will use uncovered sensor default values > + * format: decimal value * APDS_PARAM_SCALE > + */ > +#define APDS_PARAM_SCALE 4096 > +struct apds990x_chip_factors { > + int ga; /* Glass attenuation */ > + int cf1; /* Clear channel factor 1 */ > + int irf1; /* Ir channel factor 1 */ > + int cf2; /* Clear channel factor 2 */ > + int irf2; /* Ir channel factor 2 */ > + int df; /* Device factor. Decimal number */ > +}; > + > +struct apds990x_platform_data { > + struct apds990x_chip_factors cf; > + u8 pdrive; > + u8 ppcount; > + int (*setup_resources)(void); > + int (*release_resources)(void); > +}; > + > +chip factors are specific to for example dark plastic window > +above the sensor. Driver uses plain (uncovered) sensor default values > +if the platform data contains zero ga factor. > + > +"pdrive" is the IR led driver current > + > +"ppcount" is the number of the IR pulses used for proximity detection. > +5 is for example quite good value. > + > +Proximity detection range and overall behaviour is heavily affected > +by cover window, IR led driving strength and ppcount. As an aside. A lot of this stuff could also be usefully added to the header in kernel doc format. That is typically where people go looking for details on what platform data is needed rather than a documentation file. > + > +setup / release resources handles interrupt line configurations. > diff --git a/Documentation/misc-devices/bhsfh.txt b/Documentation/misc-devices/bhsfh.txt > new file mode 100644 > index 0000000..84568fc > --- /dev/null > +++ b/Documentation/misc-devices/bhsfh.txt > @@ -0,0 +1,148 @@ > +Kernel driver bhsfh > +=================== > + > +Supported chips: > +ROHM BH1770GLC > +OSRAM SFH7770 > + > +Data sheet: > +Not freely available > + > +Author: > +Samu Onkalo <samu.p.onkalo@nokia.com> > + > +Description > +----------- > +BH1770GLC and SFH7770 (I'm calling them as bhsfh) are combined ambient > +light and proximity sensors. ALS and proximity parts operates on their own, > +but they shares common I2C interface and interrupt logic. Out of curiosity, can they exist separately? E.g. would this driver work (with minor changes) for a part that only contains one of the pair? > + > +ALS produces 16 bit LUX values. The chip contains interrupt logic to produce > +low and high threshold interrupts. > + > +Proximity part contains IR-led driver up to 3 IR leds. The chip measures > +amount of reflected IR light and produces proximity result. Resolution is > +8 bit. Driver supports only one channel. Driver uses ALS results to estimate > +realibility of the proximity results. Thus ALS is always running while typo realibility -> reliability? > +proximity detection is needed. > + > +Driver uses threshold interrupts to avoid need for polling the values. > +Proximity low interrupt doesn't exists in the chip. This is simulated > +by using a delayed work. I'm not clear why this is in driver? Is it just to maintain the same interface as for the other part? > + > +Chip state is controlled via runtime pm framework when enabled in config. > + > +Calibscale factor is used to hide differences between the chips. By default > +value set to neutral state meaning factor of 1.00. To get proper values, > +calibrated source of light is needed as a reference. Calibscale factor is set > +so that measurement produces about the expected lux value. > + > +SYSFS > +----- > + > +chip_id > + RO - shows detected chip type and version > + > +power_state > + RW - enable / disable chip. Uses counting logic > + 1 enables the chip > + 0 disables the chip > + > +lux0_input > + RO - measured LUX value > + sysfs_notify called when threshold interrupt occurs > + > +lux0_sensor_range > + RO - lux0_input max value > + > +lux0_rate > + RW - measurement rate in Hz > + > +lux0_rate_avail > + RO - supported measurement rates > + > +lux0_thresh_above_value > + RW - HI level threshold value. All results above the value > + trigs an interrupt. 65535 (i.e. sensor_range) disables the above > + interrupt. > + > +lux0_thresh_below_value > + RW - LO level threshold value. All results below the value > + trigs an interrupt. 0 disables the below interrupt. > + > +lux0_calibscale > + RW - calibration value. Set to neutral value by default. > + Output results are multiplied with calibscale / calibscale_default > + value. > + > +lux0_calibscale_default > + RO - neutral calibration value > + > +prox0_raw > + RO - measured proximity value > + sysfs_notify called when threshold interrupt occurs > + > +prox0_sensor_range > + RO - prox0_raw max value > + > +prox0_raw_en > + RW - enable / disable proximity - uses counting logic > + 1 enables the proximity > + 0 disables the proximity > + > +prox0_thresh_above_count > + RW - number of proximity interrupts needed before triggering the event > + > +prox0_rate_above > + RW - Measurement rate (in Hz) when the level is above threshold > + i.e. when proximity on has been reported. > + > +prox0_rate_below > + RW - Measurement rate (in Hz) when the level is below threshold > + i.e. when proximity off has been reported. > + > +prox0_rate_avail > + RO - Supported proxity measurement rates in Hz proxity -> proximity > + > + > +prox0_thresh_above0_value > + RW - threshold level which trigs proximity events. > + Filtered by persistence filter (prox0_thresh_above_count) > + > +prox0_thresh_above1_value > + RW - threshold level which trigs event immediately > + > + > +Platform data: > +-------------- > + > +struct bhsfh_platform_data { > +#define BHSFH_LED_5mA 0 > +#define BHSFH_LED_10mA 1 > +#define BHSFH_LED_20mA 2 > +#define BHSFH_LED_50mA 3 > +#define BHSFH_LED_100mA 4 > +#define BHSFH_LED_150mA 5 > +#define BHSFH_LED_200mA 6 > + __u8 led_def_curr; > +#define BHFSH_NEUTRAL_GA 16384 /* 16384 / 16384 = 1 */ > + __u32 glass_attenuation; > + int (*setup_resources)(void); > + int (*release_resources)(void); > +}; > + > +led_def_curr controls IR led driving current > +glass_attenuation tells darkness of the covering window > +setup / release resources are call back functions for controlling > +interrupt line. > + > +Example: > +static struct bhsfh_platform_data rm680_bhsfh_data = { > + .led_def_curr = BHSFH_LED_50mA, > + .glass_attenuation = (16384 * 385) / 100, > + .setup_resources = bhsfh_setup, > + .release_resources = bhsfh_release, > +}; > + > +glass_attenuation: 385 in above formula means 3.85 in decimal. > +light_above_sensors = light_above_cover_window / 3.85 ^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2010-10-11 13:31 UTC | newest] Thread overview: 16+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2010-10-08 13:41 [PATCHv2 0/5] BH1770GLC, SFH7770 and APDS990x als / proximity sensor drivers Samu Onkalo 2010-10-08 13:42 ` [PATCHv2 1/5] misc: Driver for bh1770glc / sfh7770 ALS and proximity sensor Samu Onkalo 2010-10-08 14:35 ` Jonathan Cameron 2010-10-11 6:16 ` Onkalo Samu 2010-10-11 13:35 ` Jonathan Cameron 2010-10-08 13:42 ` [PATCHv2 2/5] misc: update bhsfh driver to Kconfig and Makefile Samu Onkalo 2010-10-08 14:25 ` Jonathan Cameron 2010-10-11 6:17 ` Onkalo Samu 2010-10-11 13:36 ` Jonathan Cameron 2010-10-09 23:43 ` Randy Dunlap 2010-10-08 13:42 ` [PATCHv2 3/5] misc: Driver for APDS990X ALS and proximity sensors Samu Onkalo 2010-10-08 14:46 ` Jonathan Cameron 2010-10-08 13:42 ` [PATCHv2 4/5] misc: update apds990x driver to Kconfig and Makefile Samu Onkalo 2010-10-09 23:43 ` Randy Dunlap 2010-10-08 13:42 ` [PATCHv2 5/5] Documentation: Short descriptions for bhsfh and apds990x drivers Samu Onkalo 2010-10-08 14:07 ` Jonathan Cameron
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox