| /* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only 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. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/jiffies.h> |
| #include <linux/i2c.h> |
| #include <linux/i2c-dev.h> |
| #include <linux/miscdevice.h> |
| #include <linux/mutex.h> |
| #include <linux/mm.h> |
| #include <linux/device.h> |
| #include <linux/fs.h> |
| #include <linux/delay.h> |
| #include <linux/sysctl.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/input.h> |
| #include <linux/regmap.h> |
| #include <linux/gpio.h> |
| #include <linux/of_gpio.h> |
| #include <linux/interrupt.h> |
| #include <linux/sensors.h> |
| #include <linux/pm_wakeup.h> |
| #include <linux/uaccess.h> |
| #include <linux/atomic.h> |
| |
| #define LTR553_I2C_NAME "ltr553" |
| #define LTR553_LIGHT_INPUT_NAME "ltr553-light" |
| #define LTR553_PROXIMITY_INPUT_NAME "ltr553-proximity" |
| |
| #define LTR553_REG_ALS_CTL 0x80 |
| #define LTR553_REG_PS_CTL 0x81 |
| #define LTR553_REG_PS_LED 0x82 |
| #define LTR553_REG_PS_N_PULSES 0x83 |
| #define LTR553_REG_PS_MEAS_RATE 0x84 |
| #define LTR553_REG_ALS_MEAS_RATE 0x85 |
| #define LTR553_REG_PART_ID 0x86 |
| #define LTR553_REG_ALS_DATA_CH1_0 0x88 |
| #define LTR553_REG_ALS_DATA_CH1_1 0x89 |
| #define LTR553_REG_ALS_DATA_CH0_0 0x8A |
| #define LTR553_REG_ALS_DATA_CH0_1 0x8B |
| #define LTR553_REG_ALS_PS_STATUS 0x8C |
| #define LTR553_REG_PS_DATA_0 0x8D |
| #define LTR553_REG_INTERRUPT 0x8F |
| #define LTR553_REG_PS_THRES_UP_0 0x90 |
| #define LTR553_REG_PS_OFFSET_1 0x94 |
| #define LTR553_REG_PS_OFFSET_0 0x95 |
| #define LTR553_REG_ALS_THRES_UP_0 0x97 |
| #define LTR553_REG_INTERRUPT_PERSIST 0x9E |
| #define LTR553_REG_MAGIC 0xFF |
| |
| #define LTR553_PART_ID 0x92 |
| |
| #define LTR553_ALS_SENSITIVITY 70 |
| |
| #define LTR553_BOOT_TIME_MS 120 |
| #define LTR553_WAKE_TIME_MS 10 |
| |
| #define LTR553_PS_SATURATE_MASK 0x8000 |
| #define LTR553_ALS_INT_MASK 0x08 |
| #define LTR553_PS_INT_MASK 0x02 |
| |
| #define LTR553_ALS_MEASURE_MASK 0x38 |
| #define LTR553_ALS_GAIN_MASK 0x1c |
| |
| /* default measurement rate is 100 ms */ |
| #define LTR553_ALS_DEFAULT_MEASURE_RATE 0x01 |
| #define LTR553_PS_MEASUREMENT_RATE_10MS 0x08 |
| |
| #define LTR553_CALIBRATE_SAMPLES 15 |
| |
| #define ALS_GAIN_SWITCH_THRESHOLD 60000 |
| |
| #define LTR553_ALS_INVALID(value) (value & 0x80) |
| |
| /* LTR553 ALS data is 16 bit */ |
| #define ALS_DATA_MASK 0xffff |
| #define ALS_LOW_BYTE(data) ((data) & 0xff) |
| #define ALS_HIGH_BYTE(data) (((data) >> 8) & 0xff) |
| |
| /* LTR553 PS data is 11 bit */ |
| #define PS_DATA_MASK 0x7ff |
| #define PS_LOW_BYTE(data) ((data) & 0xff) |
| #define PS_HIGH_BYTE(data) (((data) >> 8) & 0x7) |
| |
| /* Calculated by 10% transmittance */ |
| #define LTR553_MAX_LUX (ALS_DATA_MASK * 10) |
| |
| /* both als and ps interrupt are enabled */ |
| #define LTR553_INTERRUPT_SETTING 0x03 |
| |
| /* Any proximity distance change will wakeup SoC */ |
| #define LTR553_WAKEUP_ANY_CHANGE 0xff |
| |
| #define CAL_BUF_LEN 16 |
| enum { |
| CMD_WRITE = 0, |
| CMD_READ = 1, |
| }; |
| |
| struct regulator_map { |
| struct regulator *regulator; |
| int min_uv; |
| int max_uv; |
| char *supply; |
| }; |
| |
| struct pinctrl_config { |
| struct pinctrl *pinctrl; |
| struct pinctrl_state *state[2]; |
| char *name[2]; |
| }; |
| |
| struct ltr553_data { |
| struct i2c_client *i2c; |
| struct regmap *regmap; |
| struct regulator *config; |
| struct input_dev *input_light; |
| struct input_dev *input_proximity; |
| struct workqueue_struct *workqueue; |
| |
| struct sensors_classdev als_cdev; |
| struct sensors_classdev ps_cdev; |
| struct mutex ops_lock; |
| ktime_t last_als_ts; |
| ktime_t last_ps_ts; |
| struct work_struct report_work; |
| struct work_struct als_enable_work; |
| struct work_struct als_disable_work; |
| struct work_struct ps_enable_work; |
| struct work_struct ps_disable_work; |
| atomic_t wake_count; |
| |
| int irq_gpio; |
| int irq; |
| bool als_enabled; |
| bool ps_enabled; |
| u32 irq_flags; |
| int als_delay; |
| int ps_delay; |
| int als_cal; |
| int ps_cal; |
| int als_gain; |
| int als_persist; |
| int als_integration_time; |
| int als_measure_rate; |
| int ps_led; |
| int ps_pulses; |
| int ps_measure_rate; |
| int als_ps_persist; |
| int ps_wakeup_threshold; |
| |
| int last_als; |
| int last_ps; |
| int flush_count; |
| int power_enabled; |
| |
| unsigned int reg_addr; |
| char calibrate_buf[CAL_BUF_LEN]; |
| unsigned int bias; |
| }; |
| |
| struct als_coeff { |
| int ch0_coeff_i; |
| int ch1_coeff_i; |
| int ch0_coeff_f; |
| int ch1_coeff_f; |
| int win_fac; |
| int sign; |
| } __attribute__((__packed__)); |
| |
| static struct regulator_map power_config[] = { |
| {.supply = "vdd", .min_uv = 2000000, .max_uv = 3300000, }, |
| {.supply = "vio", .min_uv = 1750000, .max_uv = 1950000, }, |
| }; |
| |
| static struct pinctrl_config pin_config = { |
| .name = { "default", "sleep" }, |
| }; |
| |
| static struct als_coeff eqtn_map[] = { |
| { |
| .ch0_coeff_i = 1, |
| .ch1_coeff_i = 1, |
| .ch0_coeff_f = 7743, |
| .ch1_coeff_f = 1059, |
| .win_fac = 44, |
| .sign = 1, |
| }, |
| { |
| .ch0_coeff_i = 4, |
| .ch1_coeff_i = 1, |
| .ch0_coeff_f = 2785, |
| .ch1_coeff_f = 9548, |
| .win_fac = 50, |
| .sign = -1, |
| }, |
| { |
| .ch0_coeff_i = 0, |
| .ch1_coeff_i = 0, |
| .ch0_coeff_f = 5926, |
| .ch1_coeff_f = 1185, |
| .win_fac = 40, |
| .sign = 1, |
| }, |
| { |
| .ch0_coeff_i = 0, |
| .ch1_coeff_i = 0, |
| .ch0_coeff_f = 0, |
| .ch1_coeff_f = 0, |
| .win_fac = 1, |
| .sign = 1, |
| }, |
| }; |
| |
| /* ALS integration time in 10ms */ |
| static int als_int_fac_table[] = { 10, 5, 20, 40, 15, 25, 30, 35 }; |
| /* ALS gain table, index 4 & 5 are reserved */ |
| static int als_gain_table[] = {1, 2, 4, 8, 1, 1, 48, 96}; |
| /* ALS measurement repeat rate in ms */ |
| static int als_mrr_table[] = {50, 100, 200, 500, 1000, 2000, 2000, 2000}; |
| /* PS measurement repeat rate in ms */ |
| static int ps_mrr_table[] = { 50, 70, 100, 200, 500, 1000, 2000, 10, |
| 10, 10, 10, 10, 10, 10, 10, 10}; |
| |
| /* Tuned for devices with rubber */ |
| static int ps_distance_table[] = { 790, 337, 195, 114, 78, 62, 50 }; |
| |
| static int sensitivity_table[] = {150, 150, 100, 100, 0, 0, 100, 1}; |
| |
| static struct sensors_classdev als_cdev = { |
| .name = "ltr553-light", |
| .vendor = "Lite-On Technology Corp", |
| .version = 1, |
| .handle = SENSORS_LIGHT_HANDLE, |
| .type = SENSOR_TYPE_LIGHT, |
| .max_range = "65536", |
| .resolution = "1.0", |
| .sensor_power = "0.25", |
| .min_delay = 50000, |
| .max_delay = 2000, |
| .fifo_reserved_event_count = 0, |
| .fifo_max_event_count = 0, |
| .flags = 2, |
| .enabled = 0, |
| .delay_msec = 50, |
| .sensors_enable = NULL, |
| .sensors_poll_delay = NULL, |
| }; |
| |
| static struct sensors_classdev ps_cdev = { |
| .name = "ltr553-proximity", |
| .vendor = "Lite-On Technology Corp", |
| .version = 1, |
| .handle = SENSORS_PROXIMITY_HANDLE, |
| .type = SENSOR_TYPE_PROXIMITY, |
| .max_range = "7", |
| .resolution = "1.0", |
| .sensor_power = "0.25", |
| .min_delay = 10000, |
| .max_delay = 2000, |
| .fifo_reserved_event_count = 0, |
| .fifo_max_event_count = 0, |
| .flags = 3, |
| .enabled = 0, |
| .delay_msec = 50, |
| .sensors_enable = NULL, |
| .sensors_poll_delay = NULL, |
| }; |
| |
| static int sensor_power_init(struct device *dev, struct regulator_map *map, |
| int size) |
| { |
| int rc; |
| int i; |
| |
| for (i = 0; i < size; i++) { |
| map[i].regulator = devm_regulator_get(dev, map[i].supply); |
| if (IS_ERR(map[i].regulator)) { |
| rc = PTR_ERR(map[i].regulator); |
| dev_err(dev, "Regualtor get failed vdd rc=%d\n", rc); |
| goto exit; |
| } |
| if (regulator_count_voltages(map[i].regulator) > 0) { |
| rc = regulator_set_voltage(map[i].regulator, |
| map[i].min_uv, map[i].max_uv); |
| if (rc) { |
| dev_err(dev, "Regulator set failed vdd rc=%d\n", |
| rc); |
| goto exit; |
| } |
| } |
| } |
| |
| return 0; |
| |
| exit: |
| /* Regulator not set correctly */ |
| for (i = i - 1; i >= 0; i--) { |
| if (regulator_count_voltages(map[i].regulator)) |
| regulator_set_voltage(map[i].regulator, 0, |
| map[i].max_uv); |
| } |
| |
| return rc; |
| } |
| |
| static int sensor_power_deinit(struct device *dev, struct regulator_map *map, |
| int size) |
| { |
| int i; |
| |
| for (i = 0; i < size; i++) { |
| if (!IS_ERR_OR_NULL(map[i].regulator)) { |
| if (regulator_count_voltages(map[i].regulator) > 0) |
| regulator_set_voltage(map[i].regulator, 0, |
| map[i].max_uv); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int sensor_power_config(struct device *dev, struct regulator_map *map, |
| int size, bool enable) |
| { |
| int i; |
| int rc = 0; |
| |
| if (enable) { |
| for (i = 0; i < size; i++) { |
| rc = regulator_enable(map[i].regulator); |
| if (rc) { |
| dev_err(dev, "enable %s failed.\n", |
| map[i].supply); |
| goto exit_enable; |
| } |
| } |
| } else { |
| for (i = 0; i < size; i++) { |
| rc = regulator_disable(map[i].regulator); |
| if (rc) { |
| dev_err(dev, "disable %s failed.\n", |
| map[i].supply); |
| goto exit_disable; |
| } |
| } |
| } |
| |
| return 0; |
| |
| exit_enable: |
| for (i = i - 1; i >= 0; i--) |
| regulator_disable(map[i].regulator); |
| |
| return rc; |
| |
| exit_disable: |
| for (i = i - 1; i >= 0; i--) |
| if (regulator_enable(map[i].regulator)) |
| dev_err(dev, "enable %s failed\n", map[i].supply); |
| |
| return rc; |
| } |
| |
| static int sensor_pinctrl_init(struct device *dev, |
| struct pinctrl_config *config) |
| { |
| config->pinctrl = devm_pinctrl_get(dev); |
| if (IS_ERR_OR_NULL(config->pinctrl)) { |
| dev_err(dev, "Failed to get pinctrl\n"); |
| return PTR_ERR(config->pinctrl); |
| } |
| |
| config->state[0] = |
| pinctrl_lookup_state(config->pinctrl, config->name[0]); |
| if (IS_ERR_OR_NULL(config->state[0])) { |
| dev_err(dev, "Failed to look up %s\n", config->name[0]); |
| return PTR_ERR(config->state[0]); |
| } |
| |
| config->state[1] = |
| pinctrl_lookup_state(config->pinctrl, config->name[1]); |
| if (IS_ERR_OR_NULL(config->state[1])) { |
| dev_err(dev, "Failed to look up %s\n", config->name[1]); |
| return PTR_ERR(config->state[1]); |
| } |
| |
| return 0; |
| } |
| |
| static int ltr553_parse_dt(struct device *dev, struct ltr553_data *ltr) |
| { |
| struct device_node *dp = dev->of_node; |
| u32 value; |
| int rc; |
| int i; |
| |
| rc = of_get_named_gpio_flags(dp, "liteon,irq-gpio", 0, |
| <r->irq_flags); |
| if (rc < 0) { |
| dev_err(dev, "unable to read irq gpio\n"); |
| return rc; |
| } |
| ltr->irq_gpio = rc; |
| |
| /* als ps persist */ |
| rc = of_property_read_u32(dp, "liteon,als-ps-persist", &value); |
| if (rc) { |
| dev_err(dev, "read liteon,als-ps-persist failed\n"); |
| return rc; |
| } |
| ltr->als_ps_persist = value; |
| |
| /* ps led */ |
| rc = of_property_read_u32(dp, "liteon,ps-led", &value); |
| if (rc) { |
| dev_err(dev, "read liteon,ps-led failed\n"); |
| return rc; |
| } |
| ltr->ps_led = value; |
| |
| /* ps pulses */ |
| rc = of_property_read_u32(dp, "liteon,ps-pulses", &value); |
| if (rc) { |
| dev_err(dev, "read liteon,ps-pulses failed\n"); |
| return rc; |
| } |
| if (value > 0x7) { |
| dev_err(dev, "liteon,ps-pulses out of range\n"); |
| return -EINVAL; |
| } |
| ltr->ps_pulses = value; |
| |
| /* als integration time */ |
| rc = of_property_read_u32(dp, "liteon,als-integration-time", &value); |
| if (rc) { |
| dev_err(dev, "read liteon,als-integration-time failed\n"); |
| return rc; |
| } |
| if (value > 0x7) { |
| dev_err(dev, "liteon,als-integration-time out of range\n"); |
| return -EINVAL; |
| } |
| ltr->als_integration_time = value; |
| |
| /* ps wakeup threshold */ |
| rc = of_property_read_u32(dp, "liteon,wakeup-threshold", &value); |
| if (rc) { |
| dev_err(dev, "liteon,wakeup-threshold incorrect, drop to default\n"); |
| value = LTR553_WAKEUP_ANY_CHANGE; |
| } |
| if ((value >= ARRAY_SIZE(ps_distance_table)) && |
| (value != LTR553_WAKEUP_ANY_CHANGE)) { |
| dev_err(dev, "wakeup threshold too big\n"); |
| return -EINVAL; |
| } |
| ltr->ps_wakeup_threshold = value; |
| |
| /* ps distance table */ |
| rc = of_property_read_u32_array(dp, "liteon,ps-distance-table", |
| ps_distance_table, ARRAY_SIZE(ps_distance_table)); |
| if ((rc == -ENODATA) || (rc == -EOVERFLOW)) { |
| dev_warn(dev, "liteon,ps-distance-table not correctly set\n"); |
| return rc; |
| } |
| |
| for (i = 1; i < ARRAY_SIZE(ps_distance_table); i++) { |
| if (ps_distance_table[i - 1] < ps_distance_table[i]) { |
| dev_err(dev, "ps distance table should in descend order\n"); |
| return -EINVAL; |
| } |
| } |
| |
| if (ps_distance_table[0] > PS_DATA_MASK) { |
| dev_err(dev, "distance table out of range\n"); |
| return -EINVAL; |
| } |
| |
| /* als gain */ |
| rc = of_property_read_u32(dp, "liteon,als-gain", &value); |
| if (rc) { |
| dev_err(dev, "read liteon,als-gain failed. Drop to default\n"); |
| value = 0; |
| } |
| /* 4 & 5 are reserved */ |
| if ((value > 0x7) || (value == 0x4) || (value == 0x5)) { |
| dev_err(dev, "liteon,als-gain invalid\n"); |
| return -EINVAL; |
| } |
| ltr->als_gain = value; |
| |
| /* als sensitivity */ |
| rc = of_property_read_u32_array(dp, "liteon,als-sensitivity", |
| sensitivity_table, ARRAY_SIZE(sensitivity_table)); |
| if (rc) |
| dev_info(dev, "read liteon,als-sensitivity failed. Drop to default\n"); |
| |
| /* als equation map */ |
| rc = of_property_read_u32_array(dp, "liteon,als-equation-0", |
| &eqtn_map[0].ch0_coeff_i, 6); |
| if (rc) |
| dev_warn(dev, "read liteon,als-equation-0 failed. Drop to default\n"); |
| |
| rc = of_property_read_u32_array(dp, "liteon,als-equation-0", |
| &eqtn_map[1].ch0_coeff_i, 6); |
| if (rc) |
| dev_warn(dev, "read liteon,als-equation-1 failed. Drop to default\n"); |
| |
| rc = of_property_read_u32_array(dp, "liteon,als-equation-0", |
| &eqtn_map[2].ch0_coeff_i, 6); |
| if (rc) |
| dev_warn(dev, "read liteon,als-equation-2 failed. Drop to default\n"); |
| |
| rc = of_property_read_u32_array(dp, "liteon,als-equation-3", |
| &eqtn_map[3].ch0_coeff_i, 6); |
| if (rc) |
| dev_warn(dev, "read liteon,als-equation-3 failed. Drop to default\n"); |
| |
| return 0; |
| } |
| |
| static int ltr553_check_device(struct ltr553_data *ltr) |
| { |
| unsigned int part_id; |
| int rc; |
| |
| rc = regmap_read(ltr->regmap, LTR553_REG_PART_ID, &part_id); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read reg %d failed.(%d)\n", |
| LTR553_REG_PART_ID, rc); |
| return rc; |
| } |
| |
| if (part_id != LTR553_PART_ID) |
| return -ENODEV; |
| |
| return 0; |
| } |
| |
| static int ltr553_init_input(struct ltr553_data *ltr) |
| { |
| struct input_dev *input; |
| int status; |
| |
| input = devm_input_allocate_device(<r->i2c->dev); |
| if (!input) { |
| dev_err(<r->i2c->dev, "allocate light input device failed\n"); |
| return -ENOMEM; |
| } |
| |
| input->name = LTR553_LIGHT_INPUT_NAME; |
| input->phys = "ltr553/input0"; |
| input->id.bustype = BUS_I2C; |
| |
| input_set_capability(input, EV_ABS, ABS_MISC); |
| input_set_abs_params(input, ABS_MISC, 0, LTR553_MAX_LUX, 0, 0); |
| |
| status = input_register_device(input); |
| if (status) { |
| dev_err(<r->i2c->dev, "register light input device failed.\n"); |
| return status; |
| } |
| |
| ltr->input_light = input; |
| |
| input = devm_input_allocate_device(<r->i2c->dev); |
| if (!input) { |
| dev_err(<r->i2c->dev, "allocate proximity input device failed\n"); |
| return -ENOMEM; |
| } |
| |
| input->name = LTR553_PROXIMITY_INPUT_NAME; |
| input->phys = "ltr553/input1"; |
| input->id.bustype = BUS_I2C; |
| |
| input_set_capability(input, EV_ABS, ABS_DISTANCE); |
| input_set_abs_params(input, ABS_DISTANCE, 0, |
| ARRAY_SIZE(ps_distance_table), 0, 0); |
| |
| status = input_register_device(input); |
| if (status) { |
| dev_err(<r->i2c->dev, "register proxmity input device failed.\n"); |
| return status; |
| } |
| |
| ltr->input_proximity = input; |
| |
| return 0; |
| } |
| |
| static int ltr553_init_device(struct ltr553_data *ltr) |
| { |
| int rc; |
| unsigned int tmp; |
| |
| /* Enable als/ps interrupt */ |
| rc = regmap_write(ltr->regmap, LTR553_REG_INTERRUPT, |
| LTR553_INTERRUPT_SETTING); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d register failed\n", |
| LTR553_REG_INTERRUPT); |
| return rc; |
| } |
| |
| rc = regmap_write(ltr->regmap, LTR553_REG_INTERRUPT_PERSIST, |
| ltr->als_ps_persist); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d register failed\n", |
| LTR553_REG_INTERRUPT_PERSIST); |
| return rc; |
| } |
| |
| rc = regmap_write(ltr->regmap, LTR553_REG_PS_N_PULSES, ltr->ps_pulses); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d register failed\n", |
| LTR553_REG_PS_N_PULSES); |
| return rc; |
| } |
| |
| rc = regmap_write(ltr->regmap, LTR553_REG_ALS_MEAS_RATE, |
| (ltr->als_integration_time << 3) | (ltr->als_measure_rate)); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed\n", |
| LTR553_REG_ALS_MEAS_RATE); |
| return rc; |
| } |
| |
| rc = regmap_write(ltr->regmap, LTR553_REG_PS_MEAS_RATE, |
| ltr->ps_measure_rate); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed\n", |
| LTR553_REG_PS_MEAS_RATE); |
| return rc; |
| } |
| |
| /* Set calibration parameter low byte */ |
| rc = regmap_write(ltr->regmap, LTR553_REG_PS_OFFSET_0, |
| PS_LOW_BYTE(ltr->bias)); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d register failed\n", |
| LTR553_REG_PS_OFFSET_0); |
| return rc; |
| } |
| |
| /* Set calibration parameter high byte */ |
| rc = regmap_write(ltr->regmap, LTR553_REG_PS_OFFSET_1, |
| PS_HIGH_BYTE(ltr->bias)); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d register failed\n", |
| LTR553_REG_PS_OFFSET_1); |
| return rc; |
| } |
| |
| /* set up als gain */ |
| rc = regmap_read(ltr->regmap, LTR553_REG_ALS_CTL, &tmp); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read %d register failed\n", |
| LTR553_REG_ALS_CTL); |
| return rc; |
| } |
| rc = regmap_write(ltr->regmap, LTR553_REG_ALS_CTL, |
| (tmp & (~0x1c)) | (ltr->als_gain << 2)); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d register failed\n", |
| LTR553_REG_ALS_CTL); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /* Calculate the lux value based on ADC data */ |
| static int ltr553_calc_lux(int ch0data, int ch1data, int gain, int als_int_fac) |
| { |
| int ratio; |
| int lux_i; |
| int lux_f; |
| int lux; |
| struct als_coeff *eqtn; |
| |
| /* avoid divided by 0 */ |
| if ((ch0data == 0) && (ch1data == 0)) |
| return 0; |
| |
| ratio = ch1data * 100 / (ch0data + ch1data); |
| if (ratio < 45) |
| eqtn = &eqtn_map[0]; |
| else if ((ratio >= 45) && (ratio < 68)) |
| eqtn = &eqtn_map[1]; |
| else if ((ratio >= 68) && (ratio < 99)) |
| eqtn = &eqtn_map[2]; |
| else |
| eqtn = &eqtn_map[3]; |
| |
| lux_i = (ch0data * eqtn->ch0_coeff_i + ch1data * eqtn->ch1_coeff_i * |
| eqtn->sign) * eqtn->win_fac; |
| lux_f = (ch0data * eqtn->ch0_coeff_f + ch1data * eqtn->ch1_coeff_f * |
| eqtn->sign) / 100 * eqtn->win_fac; |
| |
| lux = (lux_i + abs(lux_f) / 100) / (gain * als_int_fac); |
| |
| return lux; |
| } |
| |
| /* Calculate adc value based on lux. Return value is positive */ |
| static int ltr553_calc_adc(int ratio, int lux, int gain, int als_int_fac) |
| { |
| int divisor_i; |
| int divisor_f; |
| int dividend; |
| struct als_coeff *eqtn; |
| int result; |
| |
| /* avoid devided by 0 */ |
| if (ratio == 0) |
| return 0; |
| |
| if (ratio < 45) |
| eqtn = &eqtn_map[0]; |
| else if ((ratio >= 45) && (ratio < 68)) |
| eqtn = &eqtn_map[1]; |
| else if ((ratio >= 68) && (ratio < 99)) |
| eqtn = &eqtn_map[2]; |
| else |
| eqtn = &eqtn_map[3]; |
| |
| dividend = lux * gain * als_int_fac; |
| divisor_i = ((100 - ratio) * eqtn->ch0_coeff_i / ratio + |
| eqtn->ch1_coeff_i * eqtn->sign) * eqtn->win_fac; |
| divisor_f = abs((100 - ratio) * eqtn->ch0_coeff_f / ratio + |
| eqtn->ch1_coeff_f * eqtn->sign) * eqtn->win_fac / 10000; |
| |
| /* avoid divided by 0 */ |
| if ((divisor_i + divisor_f) == 0) |
| return 0; |
| |
| result = dividend / (divisor_i + divisor_f); |
| |
| return result <= 0 ? 1 : result; |
| } |
| |
| /* update als gain and threshold */ |
| static int ltr553_als_update_setting(struct ltr553_data *ltr, |
| int ch0data, int ch1data, int als_int_fac) |
| { |
| int gain_index; |
| unsigned int config; |
| unsigned int ratio; |
| unsigned int adc_base; |
| int rc; |
| int adc; |
| int i; |
| u8 als_data[4]; |
| |
| for (i = ARRAY_SIZE(als_gain_table) - 1; i >= 0; i--) { |
| if ((i == 4) || (i == 5)) |
| continue; |
| |
| if ((ch0data + ch1data) * als_gain_table[i] / |
| als_gain_table[ltr->als_gain] < |
| ALS_GAIN_SWITCH_THRESHOLD) |
| break; |
| } |
| |
| gain_index = i < 0 ? 0 : i; |
| |
| /* |
| * Disable als and enable it again to avoid incorrect value. |
| * Updating als gain during als measurement cycle will cause |
| * incorrect light sensor adc value. The logic here is to handle |
| * this scenario. |
| */ |
| |
| if (ltr->als_gain != gain_index) { |
| rc = regmap_read(ltr->regmap, LTR553_REG_ALS_CTL, &config); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read %d failed.(%d)\n", |
| LTR553_REG_ALS_CTL, rc); |
| return rc; |
| } |
| |
| /* disable als sensor */ |
| rc = regmap_write(ltr->regmap, LTR553_REG_ALS_CTL, |
| config & (~0x1)); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_ALS_CTL, rc); |
| return rc; |
| } |
| |
| /* write new als gain */ |
| rc = regmap_write(ltr->regmap, LTR553_REG_ALS_CTL, |
| (config & (~0x1c)) | (gain_index << 2)); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d register failed\n", |
| LTR553_REG_ALS_CTL); |
| return rc; |
| } |
| } |
| |
| if ((ch0data == 0) && (ch1data == 0)) { |
| adc = 1; |
| } else { |
| ratio = ch1data * 100 / (ch0data + ch1data); |
| dev_dbg(<r->i2c->dev, "ratio:%d\n", ratio); |
| adc = ltr553_calc_adc(ratio, sensitivity_table[gain_index], |
| als_gain_table[gain_index], als_int_fac); |
| } |
| |
| dev_dbg(<r->i2c->dev, "adc:%d\n", adc); |
| |
| /* catch'ya! */ |
| adc_base = ch0data * als_gain_table[gain_index] / |
| als_gain_table[ltr->als_gain]; |
| |
| /* upper threshold */ |
| if (adc_base + adc > ALS_DATA_MASK) { |
| als_data[0] = 0xff; |
| als_data[1] = 0xff; |
| } else { |
| als_data[0] = ALS_LOW_BYTE(adc_base + adc); |
| als_data[1] = ALS_HIGH_BYTE(adc_base + adc); |
| } |
| |
| /* lower threshold */ |
| if (adc_base < adc) { |
| als_data[2] = 0x0; |
| als_data[3] = 0x0; |
| } else { |
| als_data[2] = ALS_LOW_BYTE(adc_base - adc); |
| als_data[3] = ALS_HIGH_BYTE(adc_base - adc); |
| } |
| |
| rc = regmap_bulk_write(ltr->regmap, LTR553_REG_ALS_THRES_UP_0, |
| als_data, 4); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_ALS_THRES_UP_0, rc); |
| return rc; |
| } |
| |
| if (ltr->als_gain != gain_index) { |
| /* enable als_sensor */ |
| rc = regmap_write(ltr->regmap, LTR553_REG_ALS_CTL, |
| (config & (~0x1c)) | (gain_index << 2) | 0x1); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_ALS_CTL, rc); |
| return rc; |
| } |
| |
| ltr->als_gain = gain_index; |
| } |
| |
| return 0; |
| } |
| |
| static int ltr553_process_data(struct ltr553_data *ltr, int als_ps) |
| { |
| int als_int_fac; |
| ktime_t timestamp; |
| int rc = 0; |
| |
| unsigned int tmp; |
| u8 als_data[4]; |
| int lux; |
| int ch0data; |
| int ch1data; |
| |
| u8 ps_data[4]; |
| int i; |
| int distance; |
| |
| timestamp = ktime_get(); |
| |
| if (als_ps) { /* process als data */ |
| /* Read data */ |
| rc = regmap_bulk_read(ltr->regmap, LTR553_REG_ALS_DATA_CH1_0, |
| als_data, 4); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read %d failed.(%d)\n", |
| LTR553_REG_ALS_DATA_CH1_0, rc); |
| goto exit; |
| } |
| ch0data = als_data[2] | (als_data[3] << 8); |
| ch1data = als_data[0] | (als_data[1] << 8); |
| |
| rc = regmap_read(ltr->regmap, LTR553_REG_ALS_MEAS_RATE, &tmp); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read %d failed.(%d)\n", |
| LTR553_REG_ALS_MEAS_RATE, rc); |
| goto exit; |
| } |
| |
| tmp = (tmp & LTR553_ALS_MEASURE_MASK) >> 3; |
| als_int_fac = als_int_fac_table[tmp]; |
| lux = ltr553_calc_lux(ch0data, ch1data, |
| als_gain_table[ltr->als_gain], als_int_fac); |
| |
| dev_dbg(<r->i2c->dev, "lux:%d als_data:0x%x-0x%x-0x%x-0x%x\n", |
| lux, als_data[0], als_data[1], |
| als_data[2], als_data[3]); |
| |
| rc = regmap_read(ltr->regmap, LTR553_REG_ALS_PS_STATUS, &tmp); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read %d failed.(%d)\n", |
| LTR553_REG_ALS_PS_STATUS, rc); |
| goto exit; |
| } |
| |
| |
| if ((lux != ltr->last_als) && (!LTR553_ALS_INVALID(tmp))) { |
| input_report_abs(ltr->input_light, ABS_MISC, lux); |
| input_event(ltr->input_light, EV_SYN, SYN_TIME_SEC, |
| ktime_to_timespec(timestamp).tv_sec); |
| input_event(ltr->input_light, EV_SYN, SYN_TIME_NSEC, |
| ktime_to_timespec(timestamp).tv_nsec); |
| input_sync(ltr->input_light); |
| |
| ltr->last_als_ts = timestamp; |
| } |
| |
| ltr->last_als = lux; |
| |
| dev_dbg(<r->i2c->dev, "previous als_gain:%d\n", |
| ltr->als_gain); |
| |
| rc = ltr553_als_update_setting(ltr, ch0data, ch1data, |
| als_int_fac); |
| if (rc) { |
| dev_err(<r->i2c->dev, "update setting failed\n"); |
| goto exit; |
| } |
| |
| dev_dbg(<r->i2c->dev, "new als_gain:%d\n", |
| ltr->als_gain); |
| |
| |
| } else { /* process ps value */ |
| rc = regmap_bulk_read(ltr->regmap, LTR553_REG_PS_DATA_0, |
| ps_data, 2); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read %d failed.(%d)\n", |
| LTR553_REG_PS_DATA_0, rc); |
| goto exit; |
| } |
| |
| dev_dbg(<r->i2c->dev, "ps data: 0x%x 0x%x\n", |
| ps_data[0], ps_data[1]); |
| |
| tmp = (ps_data[1] << 8) | ps_data[0]; |
| if (tmp & LTR553_PS_SATURATE_MASK) |
| distance = 0; |
| else { |
| for (i = 0; i < ARRAY_SIZE(ps_distance_table); i++) { |
| if (tmp > ps_distance_table[i]) { |
| distance = i; |
| break; |
| } |
| } |
| distance = i; |
| } |
| |
| if (distance != ltr->last_ps) { |
| input_report_abs(ltr->input_proximity, ABS_DISTANCE, |
| distance); |
| input_event(ltr->input_proximity, EV_SYN, SYN_TIME_SEC, |
| ktime_to_timespec(timestamp).tv_sec); |
| input_event(ltr->input_proximity, EV_SYN, SYN_TIME_NSEC, |
| ktime_to_timespec(timestamp).tv_nsec); |
| input_sync(ltr->input_proximity); |
| |
| ltr->last_ps_ts = timestamp; |
| } |
| |
| ltr->last_ps = distance; |
| |
| /* lower threshold */ |
| if (distance < ARRAY_SIZE(ps_distance_table)) |
| tmp = ps_distance_table[distance]; |
| else |
| tmp = 0; |
| |
| ps_data[2] = PS_LOW_BYTE(tmp); |
| ps_data[3] = PS_HIGH_BYTE(tmp); |
| |
| /* upper threshold */ |
| if (distance > 0) |
| tmp = ps_distance_table[distance - 1]; |
| else |
| tmp = PS_DATA_MASK; |
| |
| ps_data[0] = PS_LOW_BYTE(tmp); |
| ps_data[1] = PS_HIGH_BYTE(tmp); |
| |
| dev_dbg(<r->i2c->dev, "ps threshold: 0x%x 0x%x 0x%x 0x%x\n", |
| ps_data[0], ps_data[1], ps_data[2], ps_data[3]); |
| |
| rc = regmap_bulk_write(ltr->regmap, LTR553_REG_PS_THRES_UP_0, |
| ps_data, 4); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_PS_THRES_UP_0, rc); |
| goto exit; |
| } |
| } |
| exit: |
| return rc; |
| } |
| |
| static irqreturn_t ltr553_irq_handler(int irq, void *data) |
| { |
| struct ltr553_data *ltr = data; |
| bool rc; |
| |
| rc = queue_work(ltr->workqueue, <r->report_work); |
| /* wake up event should hold a wake lock until reported */ |
| if (rc && (atomic_inc_return(<r->wake_count) == 1)) |
| pm_stay_awake(<r->i2c->dev); |
| |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void ltr553_report_work(struct work_struct *work) |
| { |
| struct ltr553_data *ltr = container_of(work, struct ltr553_data, |
| report_work); |
| int rc; |
| unsigned int status; |
| u8 buf[7]; |
| |
| mutex_lock(<r->ops_lock); |
| |
| /* avoid fake interrupt */ |
| if (!ltr->power_enabled) { |
| dev_dbg(<r->i2c->dev, "fake interrupt triggered\n"); |
| goto exit; |
| } |
| |
| /* read status */ |
| rc = regmap_read(ltr->regmap, LTR553_REG_ALS_PS_STATUS, &status); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read %d failed.(%d)\n", |
| LTR553_REG_ALS_PS_STATUS, rc); |
| status |= LTR553_PS_INT_MASK; |
| goto exit; |
| } |
| |
| dev_dbg(<r->i2c->dev, "interrupt issued status=0x%x.\n", status); |
| |
| /* als interrupt issueed */ |
| if ((status & LTR553_ALS_INT_MASK) && (ltr->als_enabled)) { |
| rc = ltr553_process_data(ltr, 1); |
| if (rc) |
| goto exit; |
| dev_dbg(<r->i2c->dev, "process als done!\n"); |
| } |
| |
| if ((status & LTR553_PS_INT_MASK) && (ltr->ps_enabled)) { |
| rc = ltr553_process_data(ltr, 0); |
| if (rc) |
| goto exit; |
| dev_dbg(<r->i2c->dev, "process ps data done!\n"); |
| pm_wakeup_event(<r->input_proximity->dev, 200); |
| } |
| |
| exit: |
| if (atomic_dec_and_test(<r->wake_count)) { |
| pm_relax(<r->i2c->dev); |
| dev_dbg(<r->i2c->dev, "wake lock released\n"); |
| } |
| |
| /* clear interrupt */ |
| if (regmap_bulk_read(ltr->regmap, LTR553_REG_ALS_DATA_CH1_0, |
| buf, ARRAY_SIZE(buf))) |
| dev_err(<r->i2c->dev, "clear interrupt failed\n"); |
| |
| mutex_unlock(<r->ops_lock); |
| } |
| |
| static int ltr553_enable_ps(struct ltr553_data *ltr, int enable) |
| { |
| unsigned int config; |
| unsigned int tmp; |
| int rc = 0; |
| u8 buf[7]; |
| |
| rc = regmap_read(ltr->regmap, LTR553_REG_PS_CTL, &config); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read %d failed.(%d)\n", |
| LTR553_REG_PS_CTL, rc); |
| return rc; |
| } |
| |
| if (enable) { |
| /* Enable ps sensor */ |
| rc = regmap_write(ltr->regmap, LTR553_REG_PS_CTL, |
| config | 0x02); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_PS_CTL, rc); |
| goto exit; |
| } |
| |
| rc = regmap_read(ltr->regmap, LTR553_REG_PS_MEAS_RATE, &tmp); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read %d failed.(%d)\n", |
| LTR553_REG_PS_MEAS_RATE, rc); |
| goto exit; |
| } |
| |
| /* Wait for data ready */ |
| msleep(ps_mrr_table[tmp & 0xf] + LTR553_WAKE_TIME_MS); |
| |
| /* clear last ps value */ |
| ltr->last_ps = -1; |
| |
| rc = ltr553_process_data(ltr, 0); |
| if (rc) { |
| dev_err(<r->i2c->dev, "process ps data failed\n"); |
| goto exit; |
| } |
| |
| /* clear interrupt */ |
| rc = regmap_bulk_read(ltr->regmap, LTR553_REG_ALS_DATA_CH1_0, |
| buf, ARRAY_SIZE(buf)); |
| if (rc) { |
| dev_err(<r->i2c->dev, "clear interrupt failed\n"); |
| goto exit; |
| } |
| |
| ltr->ps_enabled = true; |
| |
| } else { |
| /* disable ps_sensor */ |
| rc = regmap_write(ltr->regmap, LTR553_REG_PS_CTL, |
| config & (~0x02)); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_PS_CTL, rc); |
| goto exit; |
| } |
| |
| ltr->ps_enabled = false; |
| } |
| exit: |
| return rc; |
| } |
| |
| static int ltr553_enable_als(struct ltr553_data *ltr, int enable) |
| { |
| int rc = 0; |
| unsigned int config; |
| unsigned int tmp; |
| u8 buf[7]; |
| |
| rc = regmap_read(ltr->regmap, LTR553_REG_ALS_CTL, &config); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read %d failed.(%d)\n", |
| LTR553_REG_ALS_CTL, rc); |
| goto exit; |
| } |
| |
| if (enable) { |
| /* enable als_sensor */ |
| rc = regmap_write(ltr->regmap, LTR553_REG_ALS_CTL, |
| config | 0x1); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_ALS_CTL, rc); |
| goto exit; |
| } |
| |
| rc = regmap_read(ltr->regmap, LTR553_REG_ALS_MEAS_RATE, &tmp); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_ALS_MEAS_RATE, rc); |
| goto exit; |
| } |
| |
| /* Wait for data ready */ |
| msleep(als_mrr_table[tmp & 0x7] + LTR553_WAKE_TIME_MS); |
| |
| /* Clear last value and report even not change. */ |
| ltr->last_als = -1; |
| |
| rc = ltr553_process_data(ltr, 1); |
| if (rc) { |
| dev_err(<r->i2c->dev, "process als data failed\n"); |
| goto exit; |
| } |
| |
| /* clear interrupt */ |
| rc = regmap_bulk_read(ltr->regmap, LTR553_REG_ALS_DATA_CH1_0, |
| buf, ARRAY_SIZE(buf)); |
| if (rc) { |
| dev_err(<r->i2c->dev, "clear interrupt failed\n"); |
| goto exit; |
| } |
| |
| ltr->als_enabled = true; |
| } else { |
| /* disable als sensor */ |
| rc = regmap_write(ltr->regmap, LTR553_REG_ALS_CTL, |
| config & (~0x1)); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_ALS_CTL, rc); |
| goto exit; |
| } |
| |
| ltr->als_enabled = false; |
| } |
| |
| exit: |
| return rc; |
| } |
| |
| static int ltr553_als_sync_delay(struct ltr553_data *ltr, |
| unsigned int als_delay) |
| { |
| int index = 0; |
| int i; |
| unsigned int val; |
| int rc = 0; |
| int min; |
| |
| if (!ltr->power_enabled) { |
| dev_dbg(<r->i2c->dev, "power is not enabled\n"); |
| return 0; |
| } |
| |
| min = abs(als_delay - als_mrr_table[0]); |
| for (i = 0; i < ARRAY_SIZE(als_mrr_table); i++) { |
| if (als_mrr_table[i] >= 10 * |
| als_int_fac_table[ltr->als_integration_time]) { |
| if (als_delay == als_mrr_table[i]) { |
| index = i; |
| break; |
| } |
| if (min > abs(als_delay - als_mrr_table[i])) { |
| index = i; |
| min = abs(als_delay - als_mrr_table[i]); |
| } |
| } |
| } |
| |
| dev_dbg(<r->i2c->dev, "als delay %d ms\n", als_mrr_table[index]); |
| |
| rc = regmap_read(ltr->regmap, LTR553_REG_ALS_MEAS_RATE, &val); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read %d failed\n", |
| LTR553_REG_ALS_MEAS_RATE); |
| goto exit; |
| } |
| val &= ~0x7; |
| |
| ltr->als_measure_rate = index; |
| rc = regmap_write(ltr->regmap, LTR553_REG_ALS_MEAS_RATE, val | index); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed\n", |
| LTR553_REG_ALS_MEAS_RATE); |
| goto exit; |
| } |
| |
| exit: |
| return rc; |
| } |
| |
| |
| static int ltr553_ps_sync_delay(struct ltr553_data *ltr, unsigned int ps_delay) |
| { |
| int index = 0; |
| int i; |
| int rc = 0; |
| int min; |
| |
| if (!ltr->power_enabled) { |
| dev_dbg(<r->i2c->dev, "power is not enabled\n"); |
| return 0; |
| } |
| |
| min = abs(ps_delay - ps_mrr_table[0]); |
| for (i = 0; i < ARRAY_SIZE(ps_mrr_table); i++) { |
| if (ps_delay == ps_mrr_table[i]) { |
| index = i; |
| break; |
| } |
| if (min > abs(ps_delay - ps_mrr_table[i])) { |
| min = abs(ps_delay - ps_mrr_table[i]); |
| index = i; |
| } |
| } |
| |
| ltr->ps_measure_rate = index; |
| dev_dbg(<r->i2c->dev, "ps delay %d ms\n", ps_mrr_table[index]); |
| |
| rc = regmap_write(ltr->regmap, LTR553_REG_PS_MEAS_RATE, index); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed\n", |
| LTR553_REG_PS_MEAS_RATE); |
| goto exit; |
| } |
| |
| exit: |
| return rc; |
| } |
| |
| static void ltr553_als_enable_work(struct work_struct *work) |
| { |
| struct ltr553_data *ltr = container_of(work, struct ltr553_data, |
| als_enable_work); |
| |
| mutex_lock(<r->ops_lock); |
| if (!ltr->power_enabled) { /* new HAL? */ |
| if (sensor_power_config(<r->i2c->dev, power_config, |
| ARRAY_SIZE(power_config), true)) { |
| dev_err(<r->i2c->dev, "power up sensor failed.\n"); |
| goto exit; |
| } |
| |
| msleep(LTR553_BOOT_TIME_MS); |
| ltr->power_enabled = true; |
| if (ltr553_init_device(ltr)) { |
| dev_err(<r->i2c->dev, "init device failed\n"); |
| goto exit_power_off; |
| } |
| |
| ltr553_als_sync_delay(ltr, ltr->als_delay); |
| } |
| |
| if (ltr553_enable_als(ltr, 1)) { |
| dev_err(<r->i2c->dev, "enable als failed\n"); |
| goto exit_power_off; |
| } |
| |
| exit_power_off: |
| if ((!ltr->als_enabled) && (!ltr->ps_enabled) && |
| ltr->power_enabled) { |
| if (sensor_power_config(<r->i2c->dev, power_config, |
| ARRAY_SIZE(power_config), false)) { |
| dev_err(<r->i2c->dev, "power up sensor failed.\n"); |
| goto exit; |
| } |
| ltr->power_enabled = false; |
| } |
| exit: |
| mutex_unlock(<r->ops_lock); |
| } |
| |
| |
| static void ltr553_als_disable_work(struct work_struct *work) |
| { |
| struct ltr553_data *ltr = container_of(work, struct ltr553_data, |
| als_disable_work); |
| |
| mutex_lock(<r->ops_lock); |
| |
| if (ltr553_enable_als(ltr, 0)) { |
| dev_err(<r->i2c->dev, "disable als failed\n"); |
| goto exit; |
| } |
| |
| if ((!ltr->als_enabled) && (!ltr->ps_enabled) && ltr->power_enabled) { |
| if (sensor_power_config(<r->i2c->dev, power_config, |
| ARRAY_SIZE(power_config), false)) { |
| dev_err(<r->i2c->dev, "power up sensor failed.\n"); |
| goto exit; |
| } |
| |
| ltr->power_enabled = false; |
| } |
| |
| exit: |
| mutex_unlock(<r->ops_lock); |
| } |
| |
| static void ltr553_ps_enable_work(struct work_struct *work) |
| { |
| struct ltr553_data *ltr = container_of(work, struct ltr553_data, |
| ps_enable_work); |
| |
| mutex_lock(<r->ops_lock); |
| if (!ltr->power_enabled) { |
| if (sensor_power_config(<r->i2c->dev, power_config, |
| ARRAY_SIZE(power_config), true)) { |
| dev_err(<r->i2c->dev, "power up sensor failed.\n"); |
| goto exit; |
| } |
| |
| msleep(LTR553_BOOT_TIME_MS); |
| ltr->power_enabled = true; |
| |
| if (ltr553_init_device(ltr)) { |
| dev_err(<r->i2c->dev, "init device failed\n"); |
| goto exit_power_off; |
| } |
| |
| ltr553_ps_sync_delay(ltr, ltr->ps_delay); |
| } |
| |
| if (ltr553_enable_ps(ltr, 1)) { |
| dev_err(<r->i2c->dev, "enable ps failed\n"); |
| goto exit_power_off; |
| } |
| |
| exit_power_off: |
| if ((!ltr->als_enabled) && (!ltr->ps_enabled) && |
| ltr->power_enabled) { |
| if (sensor_power_config(<r->i2c->dev, power_config, |
| ARRAY_SIZE(power_config), false)) { |
| dev_err(<r->i2c->dev, "power up sensor failed.\n"); |
| goto exit; |
| } |
| ltr->power_enabled = false; |
| } |
| |
| exit: |
| mutex_unlock(<r->ops_lock); |
| } |
| |
| static void ltr553_ps_disable_work(struct work_struct *work) |
| { |
| struct ltr553_data *ltr = container_of(work, struct ltr553_data, |
| ps_disable_work); |
| |
| mutex_lock(<r->ops_lock); |
| |
| if (ltr553_enable_ps(ltr, 0)) { |
| dev_err(<r->i2c->dev, "ltrsable ps failed\n"); |
| goto exit; |
| } |
| |
| if ((!ltr->als_enabled) && (!ltr->ps_enabled) && ltr->power_enabled) { |
| if (sensor_power_config(<r->i2c->dev, power_config, |
| ARRAY_SIZE(power_config), false)) { |
| dev_err(<r->i2c->dev, "power up sensor failed.\n"); |
| goto exit; |
| } |
| |
| ltr->power_enabled = false; |
| } |
| exit: |
| mutex_unlock(<r->ops_lock); |
| } |
| |
| |
| static struct regmap_config ltr553_regmap_config = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| }; |
| |
| static int ltr553_cdev_enable_als(struct sensors_classdev *sensors_cdev, |
| unsigned int enable) |
| { |
| struct ltr553_data *ltr = container_of(sensors_cdev, |
| struct ltr553_data, als_cdev); |
| |
| mutex_lock(<r->ops_lock); |
| |
| if (enable) |
| queue_work(ltr->workqueue, <r->als_enable_work); |
| else |
| queue_work(ltr->workqueue, <r->als_disable_work); |
| |
| mutex_unlock(<r->ops_lock); |
| |
| return 0; |
| } |
| |
| static int ltr553_cdev_enable_ps(struct sensors_classdev *sensors_cdev, |
| unsigned int enable) |
| { |
| struct ltr553_data *ltr = container_of(sensors_cdev, |
| struct ltr553_data, ps_cdev); |
| |
| mutex_lock(<r->ops_lock); |
| |
| if (enable) |
| queue_work(ltr->workqueue, <r->ps_enable_work); |
| else |
| queue_work(ltr->workqueue, <r->ps_disable_work); |
| |
| mutex_unlock(<r->ops_lock); |
| |
| return 0; |
| } |
| |
| static int ltr553_cdev_set_als_delay(struct sensors_classdev *sensors_cdev, |
| unsigned int delay_msec) |
| { |
| struct ltr553_data *ltr = container_of(sensors_cdev, |
| struct ltr553_data, als_cdev); |
| int rc; |
| |
| mutex_lock(<r->ops_lock); |
| |
| ltr->als_delay = delay_msec; |
| rc = ltr553_als_sync_delay(ltr, delay_msec); |
| |
| mutex_unlock(<r->ops_lock); |
| |
| return rc; |
| } |
| |
| static int ltr553_cdev_set_ps_delay(struct sensors_classdev *sensors_cdev, |
| unsigned int delay_msec) |
| { |
| struct ltr553_data *ltr = container_of(sensors_cdev, |
| struct ltr553_data, ps_cdev); |
| int rc; |
| |
| mutex_lock(<r->ops_lock); |
| |
| ltr->ps_delay = delay_msec; |
| rc = ltr553_ps_sync_delay(ltr, delay_msec); |
| |
| mutex_unlock(<r->ops_lock); |
| |
| return 0; |
| } |
| |
| static int ltr553_cdev_ps_flush(struct sensors_classdev *sensors_cdev) |
| { |
| struct ltr553_data *ltr = container_of(sensors_cdev, |
| struct ltr553_data, ps_cdev); |
| |
| input_event(ltr->input_proximity, EV_SYN, SYN_CONFIG, |
| ltr->flush_count++); |
| input_sync(ltr->input_proximity); |
| |
| return 0; |
| } |
| |
| static int ltr553_cdev_als_flush(struct sensors_classdev *sensors_cdev) |
| { |
| struct ltr553_data *ltr = container_of(sensors_cdev, |
| struct ltr553_data, als_cdev); |
| |
| input_event(ltr->input_light, EV_SYN, SYN_CONFIG, ltr->flush_count++); |
| input_sync(ltr->input_light); |
| |
| return 0; |
| } |
| |
| /* This function should be called when sensor is disabled */ |
| static int ltr553_cdev_ps_calibrate(struct sensors_classdev *sensors_cdev, |
| int axis, int apply_now) |
| { |
| int rc; |
| int power; |
| unsigned int config; |
| unsigned int interrupt; |
| u16 min = PS_DATA_MASK; |
| u8 ps_data[2]; |
| int count = LTR553_CALIBRATE_SAMPLES; |
| struct ltr553_data *ltr = container_of(sensors_cdev, |
| struct ltr553_data, ps_cdev); |
| |
| |
| if (axis != AXIS_BIAS) |
| return 0; |
| |
| mutex_lock(<r->ops_lock); |
| |
| /* Ensure only be called when sensors in standy mode */ |
| if (ltr->als_enabled || ltr->ps_enabled) { |
| rc = -EPERM; |
| goto exit; |
| } |
| |
| power = ltr->power_enabled; |
| if (!power) { |
| rc = sensor_power_config(<r->i2c->dev, power_config, |
| ARRAY_SIZE(power_config), true); |
| if (rc) { |
| dev_err(<r->i2c->dev, "power up sensor failed.\n"); |
| goto exit; |
| } |
| |
| msleep(LTR553_BOOT_TIME_MS); |
| |
| rc = ltr553_init_device(ltr); |
| if (rc) { |
| dev_err(<r->i2c->dev, "init ltr553 failed\n"); |
| goto exit; |
| } |
| } |
| |
| rc = regmap_read(ltr->regmap, LTR553_REG_INTERRUPT, &interrupt); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read interrupt configuration failed\n"); |
| goto exit_power_off; |
| } |
| |
| /* disable interrupt */ |
| rc = regmap_write(ltr->regmap, LTR553_REG_INTERRUPT, 0x0); |
| if (rc) { |
| dev_err(<r->i2c->dev, "disable interrupt failed\n"); |
| goto exit_power_off; |
| } |
| |
| rc = regmap_read(ltr->regmap, LTR553_REG_PS_CTL, &config); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read %d failed.(%d)\n", |
| LTR553_REG_PS_CTL, rc); |
| goto exit_enable_interrupt; |
| } |
| |
| /* clear offset */ |
| ps_data[0] = 0; |
| ps_data[1] = 0; |
| rc = regmap_bulk_write(ltr->regmap, LTR553_REG_PS_OFFSET_1, |
| ps_data, 2); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_PS_OFFSET_1, rc); |
| goto exit_enable_interrupt; |
| } |
| |
| /* enable ps sensor */ |
| rc = regmap_write(ltr->regmap, LTR553_REG_PS_CTL, config | 0x02); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_PS_CTL, rc); |
| goto exit_enable_interrupt; |
| } |
| |
| /* ps measurement rate set to fastest rate */ |
| rc = regmap_write(ltr->regmap, LTR553_REG_PS_MEAS_RATE, |
| LTR553_PS_MEASUREMENT_RATE_10MS); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read %d failed.(%d)\n", |
| LTR553_REG_PS_MEAS_RATE, rc); |
| goto exit_enable_interrupt; |
| } |
| |
| msleep(LTR553_WAKE_TIME_MS); |
| |
| while (--count) { |
| /* the measurement rate is 10 ms */ |
| usleep_range(11000, 12000); |
| rc = regmap_bulk_read(ltr->regmap, LTR553_REG_PS_DATA_0, |
| ps_data, 2); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read PS data failed\n"); |
| break; |
| } |
| if (min > ((ps_data[1] << 8) | ps_data[0])) |
| min = (ps_data[1] << 8) | ps_data[0]; |
| } |
| |
| /* disable ps sensor */ |
| rc = regmap_write(ltr->regmap, LTR553_REG_PS_CTL, config); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_PS_CTL, rc); |
| goto exit_enable_interrupt; |
| } |
| |
| if (!count) { |
| if (min > (PS_DATA_MASK >> 1)) { |
| dev_err(<r->i2c->dev, "ps data out of range, check if shield\n"); |
| rc = -EINVAL; |
| goto exit_enable_interrupt; |
| } |
| |
| if (apply_now) { |
| ps_data[1] = PS_LOW_BYTE(min); |
| ps_data[0] = PS_HIGH_BYTE(min); |
| rc = regmap_bulk_write(ltr->regmap, |
| LTR553_REG_PS_OFFSET_1, ps_data, 2); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_PS_OFFSET_1, rc); |
| goto exit_enable_interrupt; |
| } |
| ltr->bias = min; |
| } |
| |
| snprintf(ltr->calibrate_buf, sizeof(ltr->calibrate_buf), |
| "0,0,%d", min); |
| dev_dbg(<r->i2c->dev, "result: %s\n", ltr->calibrate_buf); |
| } else { |
| dev_err(<r->i2c->dev, "calibration failed\n"); |
| rc = -EINVAL; |
| } |
| |
| exit_enable_interrupt: |
| if (regmap_write(ltr->regmap, LTR553_REG_INTERRUPT, interrupt)) { |
| dev_err(<r->i2c->dev, "enable interrupt failed\n"); |
| goto exit_power_off; |
| } |
| |
| exit_power_off: |
| if (!power) { |
| if (sensor_power_config(<r->i2c->dev, power_config, |
| ARRAY_SIZE(power_config), false)) { |
| dev_err(<r->i2c->dev, "power off sensor failed.\n"); |
| goto exit; |
| } |
| } |
| exit: |
| mutex_unlock(<r->ops_lock); |
| return rc; |
| } |
| |
| static int ltr553_cdev_ps_write_cal(struct sensors_classdev *sensors_cdev, |
| struct cal_result_t *cal_result) |
| { |
| int power; |
| u8 ps_data[2]; |
| int rc = 0; |
| struct ltr553_data *ltr = container_of(sensors_cdev, |
| struct ltr553_data, ps_cdev); |
| |
| mutex_lock(<r->ops_lock); |
| power = ltr->power_enabled; |
| if (!power) { |
| rc = sensor_power_config(<r->i2c->dev, power_config, |
| ARRAY_SIZE(power_config), true); |
| if (rc) { |
| dev_err(<r->i2c->dev, "power up sensor failed.\n"); |
| goto exit; |
| } |
| } |
| |
| ltr->bias = cal_result->bias; |
| ps_data[1] = PS_LOW_BYTE(cal_result->bias); |
| ps_data[0] = PS_HIGH_BYTE(cal_result->bias); |
| rc = regmap_bulk_write(ltr->regmap, LTR553_REG_PS_OFFSET_1, |
| ps_data, 2); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_PS_OFFSET_1, rc); |
| goto exit_power_off; |
| } |
| |
| snprintf(ltr->calibrate_buf, sizeof(ltr->calibrate_buf), "0,0,%d", |
| ltr->bias); |
| |
| exit_power_off: |
| if (!power) { |
| if (sensor_power_config(<r->i2c->dev, power_config, |
| ARRAY_SIZE(power_config), false)) { |
| dev_err(<r->i2c->dev, "power off sensor failed.\n"); |
| goto exit; |
| } |
| } |
| exit: |
| |
| mutex_unlock(<r->ops_lock); |
| return rc; |
| }; |
| |
| static ssize_t ltr553_register_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ltr553_data *ltr = dev_get_drvdata(dev); |
| unsigned int val; |
| int rc; |
| ssize_t count = 0; |
| int i; |
| |
| if (ltr->reg_addr == LTR553_REG_MAGIC) { |
| for (i = 0; i <= 0x1f; i++) { |
| rc = regmap_read(ltr->regmap, LTR553_REG_ALS_CTL + i, |
| &val); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read %d failed\n", |
| LTR553_REG_ALS_CTL + i); |
| break; |
| } |
| count += snprintf(&buf[count], PAGE_SIZE, |
| "0x%x: 0x%x\n", LTR553_REG_ALS_CTL + i, |
| val); |
| } |
| } else { |
| rc = regmap_read(ltr->regmap, ltr->reg_addr, &val); |
| if (rc) { |
| dev_err(<r->i2c->dev, "read %d failed\n", |
| ltr->reg_addr); |
| return rc; |
| } |
| count += snprintf(&buf[count], PAGE_SIZE, "0x%x:0x%x\n", |
| ltr->reg_addr, val); |
| } |
| |
| return count; |
| } |
| |
| static ssize_t ltr553_register_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t size) |
| { |
| struct ltr553_data *ltr = dev_get_drvdata(dev); |
| unsigned int reg; |
| unsigned int val; |
| unsigned int cmd; |
| int rc; |
| |
| if (sscanf(buf, "%u %u %u\n", &cmd, ®, &val) < 2) { |
| dev_err(<r->i2c->dev, "argument error\n"); |
| return -EINVAL; |
| } |
| |
| if (cmd == CMD_WRITE) { |
| rc = regmap_write(ltr->regmap, reg, val); |
| if (rc) { |
| dev_err(<r->i2c->dev, "write %d failed\n", reg); |
| return rc; |
| } |
| } else if (cmd == CMD_READ) { |
| ltr->reg_addr = reg; |
| dev_dbg(<r->i2c->dev, "register address set to 0x%x\n", reg); |
| } |
| |
| return size; |
| } |
| |
| static DEVICE_ATTR(register, S_IWUSR | S_IRUGO, |
| ltr553_register_show, |
| ltr553_register_store); |
| |
| static struct attribute *ltr553_attr[] = { |
| &dev_attr_register.attr, |
| NULL |
| }; |
| |
| static const struct attribute_group ltr553_attr_group = { |
| .attrs = ltr553_attr, |
| }; |
| |
| static int ltr553_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct ltr553_data *ltr; |
| int res = 0; |
| |
| dev_dbg(&client->dev, "probling ltr553...\n"); |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| dev_err(&client->dev, "ltr553 i2c check failed.\n"); |
| return -ENODEV; |
| } |
| |
| ltr = devm_kzalloc(&client->dev, sizeof(struct ltr553_data), |
| GFP_KERNEL); |
| if (!ltr) { |
| dev_err(&client->dev, "memory allocation failed,\n"); |
| return -ENOMEM; |
| } |
| |
| ltr->i2c = client; |
| |
| if (client->dev.of_node) { |
| res = ltr553_parse_dt(&client->dev, ltr); |
| if (res) { |
| dev_err(&client->dev, |
| "unable to parse device tree.(%d)\n", res); |
| goto out; |
| } |
| } else { |
| dev_err(&client->dev, "device tree not found.\n"); |
| res = -ENODEV; |
| goto out; |
| } |
| |
| dev_set_drvdata(&client->dev, ltr); |
| mutex_init(<r->ops_lock); |
| |
| ltr->regmap = devm_regmap_init_i2c(client, <r553_regmap_config); |
| if (IS_ERR(ltr->regmap)) { |
| dev_err(&client->dev, "init regmap failed.(%ld)\n", |
| PTR_ERR(ltr->regmap)); |
| res = PTR_ERR(ltr->regmap); |
| goto out; |
| } |
| |
| res = sensor_power_init(&client->dev, power_config, |
| ARRAY_SIZE(power_config)); |
| if (res) { |
| dev_err(&client->dev, "init power failed.\n"); |
| goto out; |
| } |
| |
| res = sensor_power_config(&client->dev, power_config, |
| ARRAY_SIZE(power_config), true); |
| if (res) { |
| dev_err(&client->dev, "power up sensor failed.\n"); |
| goto err_power_config; |
| } |
| |
| res = sensor_pinctrl_init(&client->dev, &pin_config); |
| if (res) { |
| dev_err(&client->dev, "init pinctrl failed.\n"); |
| goto err_pinctrl_init; |
| } |
| |
| msleep(LTR553_BOOT_TIME_MS); |
| |
| res = ltr553_check_device(ltr); |
| if (res) { |
| dev_err(&client->dev, "check device failed.\n"); |
| goto err_check_device; |
| } |
| |
| ltr->als_measure_rate = LTR553_ALS_DEFAULT_MEASURE_RATE; |
| |
| res = ltr553_init_device(ltr); |
| if (res) { |
| dev_err(&client->dev, "check device failed.\n"); |
| goto err_init_device; |
| } |
| |
| /* configure interrupt */ |
| if (gpio_is_valid(ltr->irq_gpio)) { |
| res = gpio_request(ltr->irq_gpio, "ltr553_interrupt"); |
| if (res) { |
| dev_err(&client->dev, |
| "unable to request interrupt gpio %d\n", |
| ltr->irq_gpio); |
| goto err_request_gpio; |
| } |
| |
| res = gpio_direction_input(ltr->irq_gpio); |
| if (res) { |
| dev_err(&client->dev, |
| "unable to set direction for gpio %d\n", |
| ltr->irq_gpio); |
| goto err_set_direction; |
| } |
| |
| ltr->irq = gpio_to_irq(ltr->irq_gpio); |
| |
| res = devm_request_irq(&client->dev, ltr->irq, |
| ltr553_irq_handler, |
| ltr->irq_flags | IRQF_ONESHOT, |
| "ltr553", ltr); |
| |
| if (res) { |
| dev_err(&client->dev, |
| "request irq %d failed(%d),\n", |
| ltr->irq, res); |
| goto err_request_irq; |
| } |
| |
| /* device wakeup initialization */ |
| device_init_wakeup(&client->dev, 1); |
| |
| ltr->workqueue = alloc_workqueue("ltr553_workqueue", |
| WQ_NON_REENTRANT | WQ_FREEZABLE, 0); |
| INIT_WORK(<r->report_work, ltr553_report_work); |
| INIT_WORK(<r->als_enable_work, ltr553_als_enable_work); |
| INIT_WORK(<r->als_disable_work, ltr553_als_disable_work); |
| INIT_WORK(<r->ps_enable_work, ltr553_ps_enable_work); |
| INIT_WORK(<r->ps_disable_work, ltr553_ps_disable_work); |
| |
| } else { |
| res = -ENODEV; |
| goto err_init_device; |
| } |
| |
| res = sysfs_create_group(&client->dev.kobj, <r553_attr_group); |
| if (res) { |
| dev_err(&client->dev, "sysfs create group failed\n"); |
| goto err_create_group; |
| } |
| |
| res = ltr553_init_input(ltr); |
| if (res) { |
| dev_err(&client->dev, "init input failed.\n"); |
| goto err_init_input; |
| } |
| |
| ltr->als_cdev = als_cdev; |
| ltr->als_cdev.sensors_enable = ltr553_cdev_enable_als; |
| ltr->als_cdev.sensors_poll_delay = ltr553_cdev_set_als_delay; |
| ltr->als_cdev.sensors_flush = ltr553_cdev_als_flush; |
| res = sensors_classdev_register(&client->dev, <r->als_cdev); |
| if (res) { |
| dev_err(&client->dev, "sensors class register failed.\n"); |
| goto err_register_als_cdev; |
| } |
| |
| ltr->ps_cdev = ps_cdev; |
| ltr->ps_cdev.sensors_enable = ltr553_cdev_enable_ps; |
| ltr->ps_cdev.sensors_poll_delay = ltr553_cdev_set_ps_delay; |
| ltr->ps_cdev.sensors_flush = ltr553_cdev_ps_flush; |
| ltr->ps_cdev.sensors_calibrate = ltr553_cdev_ps_calibrate; |
| ltr->ps_cdev.sensors_write_cal_params = ltr553_cdev_ps_write_cal; |
| ltr->ps_cdev.params = ltr->calibrate_buf; |
| res = sensors_classdev_register(&client->dev, <r->ps_cdev); |
| if (res) { |
| dev_err(&client->dev, "sensors class register failed.\n"); |
| goto err_register_ps_cdev; |
| } |
| |
| sensor_power_config(&client->dev, power_config, |
| ARRAY_SIZE(power_config), false); |
| |
| dev_dbg(&client->dev, "ltr553 successfully probed!\n"); |
| |
| return 0; |
| |
| err_register_ps_cdev: |
| sensors_classdev_unregister(<r->als_cdev); |
| err_register_als_cdev: |
| err_init_input: |
| sysfs_remove_group(&client->dev.kobj, <r553_attr_group); |
| err_create_group: |
| err_request_irq: |
| err_set_direction: |
| gpio_free(ltr->irq_gpio); |
| err_request_gpio: |
| err_init_device: |
| device_init_wakeup(&client->dev, 0); |
| err_check_device: |
| err_pinctrl_init: |
| sensor_power_config(&client->dev, power_config, |
| ARRAY_SIZE(power_config), false); |
| err_power_config: |
| sensor_power_deinit(&client->dev, power_config, |
| ARRAY_SIZE(power_config)); |
| out: |
| return res; |
| } |
| |
| static int ltr553_remove(struct i2c_client *client) |
| { |
| struct ltr553_data *ltr = dev_get_drvdata(&client->dev); |
| |
| sensors_classdev_unregister(<r->ps_cdev); |
| sensors_classdev_unregister(<r->als_cdev); |
| |
| if (ltr->input_light) |
| input_unregister_device(ltr->input_light); |
| |
| if (ltr->input_proximity) |
| input_unregister_device(ltr->input_proximity); |
| |
| destroy_workqueue(ltr->workqueue); |
| device_init_wakeup(<r->i2c->dev, 0); |
| sensor_power_config(&client->dev, power_config, |
| ARRAY_SIZE(power_config), false); |
| sensor_power_deinit(&client->dev, power_config, |
| ARRAY_SIZE(power_config)); |
| return 0; |
| } |
| |
| static int ltr553_suspend(struct device *dev) |
| { |
| int res = 0; |
| struct ltr553_data *ltr = dev_get_drvdata(dev); |
| u8 ps_data[4]; |
| unsigned int config; |
| int idx = ltr->ps_wakeup_threshold; |
| |
| dev_dbg(dev, "suspending ltr553..."); |
| |
| mutex_lock(<r->ops_lock); |
| |
| /* proximity is enabled */ |
| if (ltr->ps_enabled) { |
| /* disable als sensor to avoid wake up by als interrupt */ |
| if (ltr->als_enabled) { |
| res = regmap_read(ltr->regmap, LTR553_REG_ALS_CTL, |
| &config); |
| if (res) { |
| dev_err(<r->i2c->dev, "read %d failed.(%d)\n", |
| LTR553_REG_ALS_CTL, res); |
| return res; |
| } |
| |
| res = regmap_write(ltr->regmap, LTR553_REG_ALS_CTL, |
| config & (~0x1)); |
| if (res) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_ALS_CTL, res); |
| goto exit; |
| } |
| } |
| |
| /* Don't power off sensor because proximity is a |
| * wake up sensor. |
| */ |
| if (device_may_wakeup(<r->i2c->dev)) { |
| dev_dbg(<r->i2c->dev, "enable irq wake\n"); |
| enable_irq_wake(ltr->irq); |
| } |
| |
| /* Setup threshold to avoid frequent wakeup */ |
| if (device_may_wakeup(<r->i2c->dev) && |
| (idx != LTR553_WAKEUP_ANY_CHANGE)) { |
| dev_dbg(<r->i2c->dev, "last ps: %d\n", ltr->last_ps); |
| if (ltr->last_ps > idx) { |
| ps_data[2] = 0x0; |
| ps_data[3] = 0x0; |
| ps_data[0] = |
| PS_LOW_BYTE(ps_distance_table[idx]); |
| ps_data[1] = |
| PS_HIGH_BYTE(ps_distance_table[idx]); |
| } else { |
| ps_data[2] = |
| PS_LOW_BYTE(ps_distance_table[idx]); |
| ps_data[3] = |
| PS_HIGH_BYTE(ps_distance_table[idx]); |
| ps_data[0] = PS_LOW_BYTE(PS_DATA_MASK); |
| ps_data[1] = PS_HIGH_BYTE(PS_DATA_MASK); |
| } |
| |
| res = regmap_bulk_write(ltr->regmap, |
| LTR553_REG_PS_THRES_UP_0, ps_data, 4); |
| if (res) { |
| dev_err(<r->i2c->dev, "set up threshold failed\n"); |
| goto exit; |
| } |
| } |
| } else { |
| /* power off */ |
| disable_irq(ltr->irq); |
| if (ltr->power_enabled) { |
| res = sensor_power_config(dev, power_config, |
| ARRAY_SIZE(power_config), false); |
| if (res) { |
| dev_err(dev, "failed to suspend ltr553\n"); |
| enable_irq(ltr->irq); |
| goto exit; |
| } |
| } |
| pinctrl_select_state(pin_config.pinctrl, pin_config.state[1]); |
| } |
| exit: |
| mutex_unlock(<r->ops_lock); |
| return res; |
| } |
| |
| static int ltr553_resume(struct device *dev) |
| { |
| int res = 0; |
| struct ltr553_data *ltr = dev_get_drvdata(dev); |
| unsigned int config; |
| |
| dev_dbg(dev, "resuming ltr553..."); |
| if (ltr->ps_enabled) { |
| if (device_may_wakeup(<r->i2c->dev)) { |
| dev_dbg(<r->i2c->dev, "disable irq wake\n"); |
| disable_irq_wake(ltr->irq); |
| } |
| |
| if (ltr->als_enabled) { |
| res = regmap_read(ltr->regmap, LTR553_REG_ALS_CTL, |
| &config); |
| if (res) { |
| dev_err(<r->i2c->dev, "read %d failed.(%d)\n", |
| LTR553_REG_ALS_CTL, res); |
| goto exit; |
| } |
| |
| res = regmap_write(ltr->regmap, LTR553_REG_ALS_CTL, |
| config | 0x1); |
| if (res) { |
| dev_err(<r->i2c->dev, "write %d failed.(%d)\n", |
| LTR553_REG_ALS_CTL, res); |
| goto exit; |
| } |
| } |
| } else { |
| pinctrl_select_state(pin_config.pinctrl, pin_config.state[0]); |
| /* Power up sensor */ |
| if (ltr->power_enabled) { |
| res = sensor_power_config(dev, power_config, |
| ARRAY_SIZE(power_config), true); |
| if (res) { |
| dev_err(dev, "failed to power up ltr553\n"); |
| goto exit; |
| } |
| msleep(LTR553_BOOT_TIME_MS); |
| |
| res = ltr553_init_device(ltr); |
| if (res) { |
| dev_err(dev, "failed to init ltr553\n"); |
| goto exit_power_off; |
| } |
| } |
| |
| if (ltr->als_enabled) { |
| res = ltr553_enable_als(ltr, ltr->als_enabled); |
| if (res) { |
| dev_err(dev, "failed to enable ltr553\n"); |
| goto exit_power_off; |
| } |
| } |
| |
| enable_irq(ltr->irq); |
| } |
| |
| return res; |
| |
| exit_power_off: |
| if ((!ltr->als_enabled) && (!ltr->ps_enabled) && |
| ltr->power_enabled) { |
| if (sensor_power_config(<r->i2c->dev, power_config, |
| ARRAY_SIZE(power_config), false)) { |
| dev_err(<r->i2c->dev, "power up sensor failed.\n"); |
| goto exit; |
| } |
| ltr->power_enabled = false; |
| } |
| |
| exit: |
| return res; |
| } |
| |
| static const struct i2c_device_id ltr553_id[] = { |
| { LTR553_I2C_NAME, 0 }, |
| { } |
| }; |
| |
| static struct of_device_id ltr553_match_table[] = { |
| { .compatible = "liteon,ltr553", }, |
| { }, |
| }; |
| |
| static const struct dev_pm_ops ltr553_pm_ops = { |
| .suspend = ltr553_suspend, |
| .resume = ltr553_resume, |
| }; |
| |
| static struct i2c_driver ltr553_driver = { |
| .probe = ltr553_probe, |
| .remove = ltr553_remove, |
| .id_table = ltr553_id, |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = LTR553_I2C_NAME, |
| .of_match_table = ltr553_match_table, |
| .pm = <r553_pm_ops, |
| }, |
| }; |
| |
| module_i2c_driver(ltr553_driver); |
| |
| MODULE_DESCRIPTION("LTR-553ALPS Driver"); |
| MODULE_LICENSE("GPL v2"); |