android_kernel_sony_sdm845/drivers/misc/tcs3490.c
sjllls b6a5eeb5e8 drivers: tcs3490: Fix warning misleading indentation
Change-Id: I7b45cc0f7bdb81060fe753a7245c213f7feef6d3
Signed-off-by: Sorayukii <sorayukii69@gmail.com>
2026-05-15 17:48:15 +07:00

1663 lines
45 KiB
C

/*
* Device driver for monitoring ambient light intensity in (lux), RGB, and
* color temperature (in kelvin) within the AMS-TAOS TCS family of devices.
*
* Copyright (c) 2016, AMS-TAOS USA, Inc.
* Copyright (C) 2017 Sony Mobile Communications Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <linux/uaccess.h>
#include <linux/kobject.h>
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/mutex.h>
#include <linux/unistd.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/pm.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/regulator/consumer.h>
#include <linux/tcs3490.h>
#include <linux/types.h>
#include <linux/ktime.h>
//
// Debug options
//
// Create an ABI for dumping CHIP_ID, REV_ID, and colorbin register contents?
#define CHIP_ID_ABI 1
// Print info re lux calculations (printed per sample, potentially voluminous)?
//#define LUX_MESSAGES 1
//
// Configurational options
//
// Define this as nonzero, or not, depending on your system configuration.
// The color-bin correction factors need to be applied at a different
// point in the computation with inked glass vs. air or clear glass.
#define INKED_GLASS 0
//
// Constants
//
#define TCS3490_CMD_ALS_INT_CLR 0xE6
#define TCS3490_CMD_ALL_INT_CLR 0xE7
#define TCS3490_CHANNEL 0xC0
#define TCS3490_MAX_INTEGRATION_CYCLES (256)
#define CENTI_MSEC_PER_ATIME_TICK 278
#define CENTI_MSEC_PER_MSEC 100
#define I2C_ADDR_OFFSET 0x80
#define GAIN1 0
#define GAIN4 1
#define GAIN16 2
#define GAIN64 3
//
// pertaining to the Color Bins register, used for improved color accuracy
//
#define _Q 16 // fixed point arithmetic shift
#define COLOR_BINS_MSK_COEFF (1 << 15)
#define CONFIG_SPARE_2_MSK (1 << 3)
#define COLOR_BINS_SHFT_RED 11
#define COLOR_BINS_MSK_RED (((1 << 4) - 1) << COLOR_BINS_SHFT_RED)
#define COLOR_BINS_INT_MSK_RED ((1 << (4-1)) - 1)
#define COLOR_BINS_SHFT_GRN 6
#define COLOR_BINS_MSK_GRN (((1 << 5) - 1) << COLOR_BINS_SHFT_GRN)
#define COLOR_BINS_INT_MSK_GRN ((1 << (5-1)) - 1)
#define COLOR_BINS_SHFT_BLU 0
#define COLOR_BINS_MSK_BLU (((1 << 6) - 1) << COLOR_BINS_SHFT_BLU)
#define COLOR_BINS_INT_MSK_BLU ((1 << (6-1)) - 1)
// These are processed only at compile time; there is no floating point
// arithmetic at runtime
#define SLOPE_RED (-0.0111)
#define OFFSET_RED (1.0174)
#define SLOPE_GRN (-0.0110)
#define OFFSET_GRN (1.0602)
#define SLOPE_BLU (-0.0074)
#define OFFSET_BLU (1.0631)
#define RGBCIR_SENSOR_SYSFS_LINK_NAME "rgbcir_sensor"
#define RGBCIR_SENSOR_PINCTRL_IRQ_ACTIVE "rgbcir_irq_active"
#define RGBCIR_SENSOR_PINCTRL_IRQ_SUSPEND "rgbcir_irq_suspend"
#define RGBCIR_SENSOR_REGULATOR_NOTIFY_PRIORITY (1000)
enum tcs3490_regs {
TCS3490_CONTROL,
TCS3490_ALS_TIME, // 0x81
TCS3490_RESV_1,
TCS3490_WAIT_TIME, // 0x83
TCS3490_ALS_MINTHRESHLO, // 0x84
TCS3490_ALS_MINTHRESHHI, // 0x85
TCS3490_ALS_MAXTHRESHLO, // 0x86
TCS3490_ALS_MAXTHRESHHI, // 0x87
TCS3490_RESV_2, // 0x88
TCS3490_PRX_MINTHRESHLO, // 0x89 -> Not used for TCS3490
TCS3490_RESV_3, // 0x8A
TCS3490_PRX_MAXTHRESHHI, // 0x8B -> Not used for TCS3490
TCS3490_PERSISTENCE, // 0x8C
TCS3490_CONFIG, // 0x8D
TCS3490_PRX_PULSE_COUNT, // 0x8E -> Not used for TCS3490
TCS3490_GAIN, // 0x8F : Gain Control Register
TCS3490_AUX, // 0x90
TCS3490_REVID,
TCS3490_CHIPID,
TCS3490_STATUS, // 0x93
TCS3490_CLR_CHANLO, // 0x94
TCS3490_CLR_CHANHI, // 0x95
TCS3490_RED_CHANLO, // 0x96
TCS3490_RED_CHANHI, // 0x97
TCS3490_GRN_CHANLO, // 0x98
TCS3490_GRN_CHANHI, // 0x99
TCS3490_BLU_CHANLO, // 0x9A
TCS3490_BLU_CHANHI, // 0x9B
TCS3490_PRX_HI, // 0x9C
TCS3490_PRX_LO, // 0x9D
TCS3490_PRX_OFFSET, // 0x9E
TCS3490_RESV_4, // 0x9F
TCS3490_IRBEAM_CFG, // 0xA0
TCS3490_IRBEAM_CARR, // 0xA1
TCS3490_IRBEAM_NS, // 0xA2
TCS3490_IRBEAM_ISD, // 0xA3
TCS3490_IRBEAM_NP, // 0xA4
TCS3490_IRBEAM_IPD, // 0xA5
TCS3490_IRBEAM_DIV, // 0xA6
TCS3490_IRBEAM_LEN, // 0xA7
TCS3490_IRBEAM_STAT, // 0xA8
TCS3490_REG_COLOR_BINLO=0x55, // 0xD5
TCS3490_REG_COLOR_BINHI=0x56, // 0xD6
TCS3490_REG_MAX,
};
enum tcs3490_en_reg {
TCS3490_EN_PWR_ON = (1 << 0),
TCS3490_EN_ALS = (1 << 1),
TCS3490_EN_PRX = (1 << 2),
TCS3490_EN_WAIT = (1 << 3),
TCS3490_EN_ALS_IRQ = (1 << 4),
TCS3490_EN_PRX_IRQ = (1 << 5),
TCS3490_EN_IRQ_PWRDN = (1 << 6),
TCS3490_EN_BEAM = (1 << 7),
};
enum tcs3490_status {
TCS3490_ST_ALS_VALID = (1 << 0),
TCS3490_ST_PRX_VALID = (1 << 1),
TCS3490_ST_BEAM_IRQ = (1 << 3),
TCS3490_ST_ALS_IRQ = (1 << 4),
TCS3490_ST_PRX_IRQ = (1 << 5),
TCS3490_ST_PRX_SAT = (1 << 6),
};
enum {
TCS3490_ALS_GAIN_MASK = (3 << 0),
TCS3490_PRX_GAIN_MASK = (3 << 2),
TCS3490_ALS_AGL_MASK = (1 << 2),
TCS3490_ALS_AGL_SHIFT = 2,
TCS3490_ATIME_PER_100 = 273,
TCS3490_ATIME_DEFAULT_MS = 50,
SCALE_SHIFT = 11,
RATIO_SHIFT = 10,
MAX_ALS_VALUE = 0xffff,
MIN_ALS_VALUE = 10,
GAIN_SWITCH_LEVEL = 100,
GAIN_AUTO_INIT_VALUE = AGAIN_16,
};
static u8 const restorable_regs[] = {
TCS3490_ALS_TIME,
TCS3490_PERSISTENCE,
TCS3490_GAIN,
};
static u8 const als_gains[] = {
1,
4,
16,
64
};
struct tcs3490_als_info {
u32 saturation;
uint16_t clear_red_raw;
uint16_t clear_green_raw;
uint16_t clear_blue_raw;
u16 clear_raw;
u16 red_raw;
u16 green_raw;
u16 blue_raw;
uint16_t ir_raw;
uint64_t timestamp;
};
struct tcs3490_chip {
struct mutex lock;
struct i2c_client *client;
struct tcs3490_als_info als_inf;
struct tcs3490_parameters params;
struct tcs3490_i2c_platform_data *pdata;
u8 shadow[42];
struct input_dev *a_idev;
int in_suspend;
int wake_irq;
int irq_pending;
bool unpowered;
bool als_enabled;
bool is_register_regulator_cam_vio_notify;
int als_thres_enabled;
int als_switch_ch_enabled;
int vdd_supply_enable;
int gpio_vdd_enable;
int vio_supply_enable;
bool als_gain_auto;
uint8_t als_channel;
u8 device_index;
struct regulator *vdd;
struct regulator *gpio_vdd;
struct regulator *vio;
struct pinctrl *pinctrl;
struct pinctrl_state *gpio_state_active;
struct pinctrl_state *gpio_state_suspend;
struct notifier_block cam_io_regulator_cb;
};
static int tcs3490_power_on(struct tcs3490_chip *chip);
static int tcs3490_pltf_power_off(struct tcs3490_chip *chip);
static int tcs3490_pinctrl_init(struct tcs3490_chip *data)
{
data->pinctrl = devm_pinctrl_get(&data->client->dev);
if (IS_ERR_OR_NULL(data->pinctrl)) {
dev_err(&data->client->dev,
"%s:%d Getting pinctrl handle failed\n",
__func__, __LINE__);
return -EINVAL;
}
data->gpio_state_active =
pinctrl_lookup_state(data->pinctrl,
RGBCIR_SENSOR_PINCTRL_IRQ_ACTIVE);
if (IS_ERR_OR_NULL(data->gpio_state_active)) {
dev_err(&data->client->dev,
"%s:Failed to get the active state pinctrl handle\n",
__func__);
return -EINVAL;
}
data->gpio_state_suspend =
pinctrl_lookup_state(data->pinctrl,
RGBCIR_SENSOR_PINCTRL_IRQ_SUSPEND);
if (IS_ERR_OR_NULL(data->gpio_state_suspend)) {
dev_err(&data->client->dev,
"%s:Failed to get the suspend state pinctrl handle\n",
__func__);
return -EINVAL;
}
return 0;
}
static int tcs3490_i2c_blk_read(struct tcs3490_chip *chip,
u8 reg, u8 *val, int size)
{
s32 ret;
struct i2c_client *client = chip->client;
uint8_t w_buf[1];
uint8_t *r_buf;
struct i2c_msg msg[2];
reg |= I2C_ADDR_OFFSET;
w_buf[0] = reg;
msg[0].addr = (client->addr) >> 1;
msg[0].flags = 0;
msg[0].buf = w_buf;
msg[0].len = 1;
r_buf = kmalloc(size + 1, GFP_KERNEL);
if (!r_buf)
return -ENOMEM;
memset(r_buf, 0, size);
msg[1].addr = (client->addr) >> 1;
msg[1].flags = I2C_M_RD;
msg[1].buf = r_buf;
msg[1].len = size;
ret = i2c_transfer(client->adapter, msg, 2);
if (ret != 2) {
usleep_range(3000, 4000);
ret = i2c_transfer(client->adapter, msg, 2);
if (ret != 2) {
dev_err(&client->dev,
"%s: i2c ERROR !! reg=0x%x ret=%d\n",
__func__, reg, ret);
kfree(r_buf);
return ret;
}
} else {
ret = 0;
}
memcpy(val, r_buf, sizeof(u8) * size);
kfree(r_buf);
return ret;
}
static int tcs3490_i2c_read(struct tcs3490_chip *chip, u8 reg, u8 *val)
{
int ret;
struct i2c_client *client = chip->client;
uint8_t w_buf[1];
uint8_t r_buf[1];
struct i2c_msg msg[2];
reg |= I2C_ADDR_OFFSET;
w_buf[0] = reg;
msg[0].addr = (client->addr) >> 1;
msg[0].flags = 0;
msg[0].buf = w_buf;
msg[0].len = 1;
r_buf[0] = 0x0;
msg[1].addr = (client->addr) >> 1;
msg[1].flags = I2C_M_RD;
msg[1].buf = r_buf;
msg[1].len = 1;
ret = i2c_transfer(client->adapter, msg, 2);
if (ret != 2) {
usleep_range(3000, 4000);
ret = i2c_transfer(client->adapter, msg, 2);
if (ret != 2) {
dev_err(&client->dev,
"%s: i2c ERROR !! reg=0x%x ret=%d\n",
__func__, reg, ret);
return ret;
}
} else {
ret = 0;
}
*val = r_buf[0];
return 0;
}
static int tcs3490_i2c_write(struct tcs3490_chip *chip, u8 reg, u8 val)
{
int ret;
struct i2c_client *client = chip->client;
uint8_t w_buf[2];
struct i2c_msg msg[1];
if (reg == TCS3490_CONTROL) {
dev_dbg(&client->dev, "%s: CONTRL addr[0x%x] data[0x%x]",
__func__, reg, val);
}
reg |= I2C_ADDR_OFFSET;
w_buf[0] = reg;
w_buf[1] = val;
msg[0].addr = (client->addr) >> 1;
msg[0].flags = 0;
msg[0].buf = w_buf;
msg[0].len = 2;
ret = i2c_transfer(client->adapter, msg, 1);
if (ret != 1) {
usleep_range(3000, 4000);
ret = i2c_transfer(client->adapter, msg, 1);
if (ret != 1) {
dev_err(&client->dev,
"%s: i2c ERROR !! reg=0x%x ret=%d\n",
__func__, reg, ret);
ret = -1;
}
} else {
ret = 0;
}
return ret;
}
static int tcs3490_i2c_reg_blk_write(struct tcs3490_chip *chip,
u8 reg, u8 *val, int size)
{
s32 ret;
struct i2c_client *client = chip->client;
uint8_t *w_buf;
struct i2c_msg msg[2];
reg |= I2C_ADDR_OFFSET;
w_buf = kmalloc(size + 1, GFP_KERNEL);
if (!w_buf) {
dev_err(&client->dev, "%s: failed to allocate buffer",
__func__);
return -ENOMEM;
}
w_buf[0] = reg;
memmove(&w_buf[1], val, size);
msg[0].addr = (client->addr) >> 1;
msg[0].flags = 0;
msg[0].buf = w_buf;
msg[0].len = size + 1;
ret = i2c_transfer(client->adapter, msg, 1);
if (ret != 1) {
usleep_range(3000, 4000);
ret = i2c_transfer(client->adapter, msg, 1);
if (ret != 1) {
dev_err(&client->dev,
"%s: i2c ERROR !! reg=0x%x ret=%d\n",
__func__, reg, ret);
}
} else {
ret = 0;
}
kfree(w_buf);
return ret;
}
static int tcs3490_flush_regs(struct tcs3490_chip *chip)
{
unsigned i;
int rc;
u8 reg;
for (i = 0; i < ARRAY_SIZE(restorable_regs); i++) {
reg = restorable_regs[i];
rc = tcs3490_i2c_write(chip, reg, chip->shadow[reg]);
if (rc) {
dev_err(&chip->client->dev, "%s: err on reg 0x%02x\n",
__func__, reg);
break;
}
}
return rc;
}
static int tcs3490_update_enable_reg(struct tcs3490_chip *chip)
{
dev_info(&chip->client->dev, "%s: Writing CONTROL, val[0x%x]",
__func__, chip->shadow[TCS3490_CONTROL]);
return tcs3490_i2c_write(chip, TCS3490_CONTROL,
chip->shadow[TCS3490_CONTROL]);
}
static int tcs3490_set_als_gain(struct tcs3490_chip *chip, int gain)
{
int rc;
u8 ctrl_reg = chip->shadow[TCS3490_GAIN] & ~TCS3490_ALS_GAIN_MASK;
switch (gain) {
case 1:
ctrl_reg |= AGAIN_1;
break;
case 4:
ctrl_reg |= AGAIN_4;
break;
case 16:
ctrl_reg |= AGAIN_16;
break;
case 64:
ctrl_reg |= AGAIN_64;
break;
default:
dev_err(&chip->client->dev, "%s: wrong als gain %d\n",
__func__, gain);
return -EINVAL;
}
rc = tcs3490_i2c_write(chip, TCS3490_GAIN, ctrl_reg);
if (!rc) {
chip->shadow[TCS3490_GAIN] = ctrl_reg;
chip->params.als_gain = gain;
dev_info(&chip->client->dev, "%s: new als gain %d\n",
__func__, gain);
}
return rc;
}
static int tcs3490_irq_clr(struct tcs3490_chip *chip, u8 int2clr)
{
int ret;
ret = tcs3490_i2c_write(chip, int2clr, 0);
if (ret) {
dev_err(&chip->client->dev, "%s: failed 2x, int to clr %02x\n",
__func__, int2clr);
}
return ret;
}
static void tcs3490_get_als_setup_next(struct tcs3490_chip *chip)
{
uint8_t *buf;
uint32_t sat;
int rc;
uint8_t atime = 0;
uint8_t cur_channel = 0xFF;
mutex_lock(&chip->lock);
buf = &chip->shadow[TCS3490_CLR_CHANLO];
atime = chip->shadow[TCS3490_ALS_TIME];
mutex_unlock(&chip->lock);
tcs3490_i2c_read(chip, TCS3490_CHANNEL, &cur_channel);
if ((chip->als_switch_ch_enabled == 1) && (cur_channel == 0x00)) {
/* RGBC */
/* extract raw channel data */
mutex_lock(&chip->lock);
chip->als_inf.clear_raw =
le16_to_cpup((const __le16 *)&buf[0]);
chip->als_inf.clear_red_raw =
le16_to_cpup((const __le16 *)&buf[2]);
chip->als_inf.clear_green_raw =
le16_to_cpup((const __le16 *)&buf[4]);
chip->als_inf.clear_blue_raw =
le16_to_cpup((const __le16 *)&buf[6]);
mutex_unlock(&chip->lock);
/* Switch to RGB-IR */
rc = tcs3490_i2c_write(chip, TCS3490_CHANNEL, 0x80);
if (!rc) {
dev_dbg(&chip->client->dev,
"%s: Channel: RGB-IR", __func__);
mutex_lock(&chip->lock);
chip->als_channel = 0x80;
mutex_unlock(&chip->lock);
}
dev_dbg(&chip->client->dev,
"%s: Changed ALS channel from RGBC to 0x%x\n",
__func__, chip->als_channel);
} else if ((chip->als_switch_ch_enabled == 1) &&
(cur_channel == 0x80)) {
/* RGBC-IR */
/* extract ir channel data */
mutex_lock(&chip->lock);
chip->als_inf.ir_raw =
le16_to_cpup((const __le16 *)&buf[0]);
chip->als_inf.red_raw =
le16_to_cpup((const __le16 *)&buf[2]);
chip->als_inf.green_raw =
le16_to_cpup((const __le16 *)&buf[4]);
chip->als_inf.blue_raw =
le16_to_cpup((const __le16 *)&buf[6]);
mutex_unlock(&chip->lock);
/* Switch to RGBC */
rc = tcs3490_i2c_write(chip, TCS3490_CHANNEL, 0x00);
if (!rc) {
dev_dbg(&chip->client->dev, "%s: Channel: RGBC",
__func__);
mutex_lock(&chip->lock);
chip->als_channel = 0x00;
mutex_unlock(&chip->lock);
}
chip->als_inf.timestamp = ktime_get_boot_ns();
dev_dbg(&chip->client->dev,
"%s: Changed channel from RGBC-IR to 0x%x Time %llu\n",
__func__, chip->als_channel, chip->als_inf.timestamp);
} else {
if (cur_channel == 0x00) {
mutex_lock(&chip->lock);
chip->als_inf.clear_raw =
le16_to_cpup((const __le16 *)&buf[0]);
mutex_unlock(&chip->lock);
} else {
mutex_lock(&chip->lock);
chip->als_inf.ir_raw =
le16_to_cpup((const __le16 *)&buf[0]);
mutex_unlock(&chip->lock);
}
mutex_lock(&chip->lock);
chip->als_inf.red_raw =
le16_to_cpup((const __le16 *)&buf[2]);
chip->als_inf.green_raw =
le16_to_cpup((const __le16 *)&buf[4]);
chip->als_inf.blue_raw =
le16_to_cpup((const __le16 *)&buf[6]);
mutex_unlock(&chip->lock);
chip->als_inf.timestamp = ktime_get_boot_ns();
}
sat = min_t(uint32_t, MAX_ALS_VALUE,
(uint32_t)(TCS3490_MAX_INTEGRATION_CYCLES - atime) << 10);
sat = sat * 8 / 10;
chip->als_inf.saturation = sat;
dev_dbg(&chip->client->dev,
"%s: raw c/ir,r,g,b: %d, %d, %d, %d\n",
__func__,
(cur_channel == 0x00) ?
chip->als_inf.clear_raw : chip->als_inf.ir_raw,
chip->als_inf.red_raw,
chip->als_inf.green_raw,
chip->als_inf.blue_raw);
}
static int tcs3490_read_all(struct tcs3490_chip *chip)
{
int ret = 0;
mutex_lock(&chip->lock);
tcs3490_i2c_read(chip, TCS3490_STATUS,
&chip->shadow[TCS3490_STATUS]);
ret = tcs3490_i2c_blk_read(chip, TCS3490_CLR_CHANLO,
&chip->shadow[TCS3490_CLR_CHANLO], 2);
ret = tcs3490_i2c_blk_read(chip, TCS3490_RED_CHANLO,
&chip->shadow[TCS3490_RED_CHANLO], 2);
ret = tcs3490_i2c_blk_read(chip, TCS3490_GRN_CHANLO,
&chip->shadow[TCS3490_GRN_CHANLO], 2);
ret = tcs3490_i2c_blk_read(chip, TCS3490_BLU_CHANLO,
&chip->shadow[TCS3490_BLU_CHANLO], 2);
mutex_unlock(&chip->lock);
return (ret < 0) ? ret : 0;
}
static int tcs3490_update_als_thres(struct tcs3490_chip *chip, bool on_enable)
{
s32 ret;
u8 *buf = &chip->shadow[TCS3490_ALS_MINTHRESHLO];
u16 deltaP = chip->params.als_deltaP;
u16 from, to, cur;
u16 saturation = chip->als_inf.saturation;
mutex_lock(&chip->lock);
cur = chip->als_inf.clear_raw;
mutex_unlock(&chip->lock);
if (!on_enable)
/* move deltaP far away from current position to force an irq */
from = to = cur > saturation / 2 ? 0 : saturation;
else {
deltaP = cur * deltaP / 100;
if (!deltaP)
deltaP = 1;
if (cur > deltaP)
from = cur - deltaP;
else
from = 0;
if (cur < (saturation - deltaP))
to = cur + deltaP;
else
to = saturation;
}
*buf++ = from & 0xff;
*buf++ = from >> 8;
*buf++ = to & 0xff;
*buf++ = to >> 8;
mutex_lock(&chip->lock);
ret = tcs3490_i2c_reg_blk_write(chip, TCS3490_ALS_MINTHRESHLO,
&chip->shadow[TCS3490_ALS_MINTHRESHLO],
TCS3490_ALS_MAXTHRESHHI - TCS3490_ALS_MINTHRESHLO + 1);
mutex_unlock(&chip->lock);
dev_info(&chip->client->dev,
"%s: on_enable[%s] cur[%u] deltaP[%u] from[%u] to[%u]",
__func__, on_enable ? "TRUE" : "FALSE", cur, deltaP, from, to);
return (ret < 0) ? ret : 0;
}
static int tcs3490_check_and_report(struct tcs3490_chip *chip)
{
u8 status;
u8 saturation;
int ret = tcs3490_read_all(chip);
if (ret) {
goto exit_clr;
}
mutex_lock(&chip->lock);
status = chip->shadow[TCS3490_STATUS];
mutex_unlock(&chip->lock);
saturation = chip->als_inf.saturation;
if ((status & (TCS3490_ST_ALS_VALID | TCS3490_ST_ALS_IRQ)) ==
(TCS3490_ST_ALS_VALID | TCS3490_ST_ALS_IRQ)) {
tcs3490_get_als_setup_next(chip);
tcs3490_irq_clr(chip, TCS3490_CMD_ALS_INT_CLR);
if (chip->als_thres_enabled)
tcs3490_update_als_thres(chip, 1);
}
exit_clr:
tcs3490_irq_clr(chip, TCS3490_CMD_ALL_INT_CLR);
return ret;
}
static irqreturn_t tcs3490_irq(int irq, void *handle)
{
struct tcs3490_chip *chip = handle;
(void)tcs3490_check_and_report(chip);
sysfs_notify(&chip->a_idev->dev.kobj, NULL, "notify");
return IRQ_HANDLED;
}
static void tcs3490_set_defaults(struct tcs3490_chip *chip)
{
u8 *sh = chip->shadow;
struct device *dev = &chip->client->dev;
dev_info(dev, "%s: use defaults\n", __func__);
mutex_lock(&chip->lock);
sh[TCS3490_ALS_TIME] = 0xFE; /* 0xEE; */ /* integration time : 254 ms */
mutex_unlock(&chip->lock);
sh[TCS3490_WAIT_TIME] = 0xFF; /* wait time : 2.4 ms */
sh[TCS3490_PERSISTENCE] = ALS_PERSIST(0);
sh[TCS3490_PRX_PULSE_COUNT] = 8;
sh[TCS3490_GAIN] = AGAIN_1;/* AGAIN_16; */
sh[TCS3490_AUX] = 0x00; /* No saturation int */
chip->als_gain_auto = false;
mutex_lock(&chip->lock);
chip->als_channel = 0x00; /* Clear channel */
mutex_unlock(&chip->lock);
chip->als_switch_ch_enabled = 1; /* Obtain RGBC and IR in turn */
}
static ssize_t tcs3490_chip_pow_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tcs3490_chip *chip = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", chip->unpowered ? 0 : 1);
}
static ssize_t tcs3490_chip_pow_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct tcs3490_chip *chip = dev_get_drvdata(dev);
int rc = 0;
bool value;
dev_info(&chip->client->dev, "CHK 1 %s\n", __func__);
if (strtobool(buf, &value))
return -EINVAL;
dev_info(&chip->client->dev, "CHK 2 %s: value = %s\n",
__func__, value ? "TRUE" : "FALSE");
if (value) {
if (!rc && chip->unpowered)
tcs3490_power_on(chip);
if (!rc && chip->pinctrl && chip->gpio_state_active) {
rc = pinctrl_select_state(chip->pinctrl,
chip->gpio_state_active);
if (rc)
dev_err(&chip->client->dev,
"%s: cannot set pin to active state",
__func__);
}
if (!rc) {
dev_info(&chip->client->dev,
"tcs3490: Request threaded IRQ\n");
rc = request_threaded_irq(chip->client->irq, NULL,
&tcs3490_irq,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
dev_name(&chip->client->dev), chip);
if (rc) {
dev_info(&chip->client->dev,
"Failed to request irq %d\n",
chip->client->irq);
(void)pinctrl_select_state(chip->pinctrl,
chip->gpio_state_suspend);
}
}
if (rc)
tcs3490_pltf_power_off(chip);
} else {
if (chip->pinctrl && chip->gpio_state_suspend) {
free_irq(chip->client->irq, chip);
rc = pinctrl_select_state(chip->pinctrl,
chip->gpio_state_suspend);
if (rc)
dev_err(&chip->client->dev,
"%s: cannot set pin to active state",
__func__);
}
if (!chip->unpowered)
tcs3490_pltf_power_off(chip);
}
return size;
}
static int tcs3490_als_enable(struct tcs3490_chip *chip, int on)
{
int rc;
dev_info(&chip->client->dev, "%s: on = %d\n", __func__, on);
if (on) {
tcs3490_irq_clr(chip, TCS3490_CMD_ALS_INT_CLR);
tcs3490_update_als_thres(chip, 1);
chip->shadow[TCS3490_CONTROL] |=
(TCS3490_EN_PWR_ON | TCS3490_EN_ALS |
TCS3490_EN_ALS_IRQ);
rc = tcs3490_update_enable_reg(chip);
if (rc)
return rc;
mdelay(3);
} else {
chip->shadow[TCS3490_CONTROL] &=
~(TCS3490_EN_ALS_IRQ);
if (!(chip->shadow[TCS3490_CONTROL] & TCS3490_EN_PRX))
chip->shadow[TCS3490_CONTROL] &= ~TCS3490_EN_PWR_ON;
rc = tcs3490_update_enable_reg(chip);
if (rc)
return rc;
chip->als_inf.timestamp = 0;
tcs3490_irq_clr(chip, TCS3490_CMD_ALS_INT_CLR);
}
if (!rc)
chip->als_enabled = on;
return rc;
}
static ssize_t tcs3490_als_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tcs3490_chip *chip = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", chip->als_enabled);
}
static ssize_t tcs3490_als_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct tcs3490_chip *chip = dev_get_drvdata(dev);
bool value;
if (strtobool(buf, &value))
return -EINVAL;
if (value)
tcs3490_als_enable(chip, 1);
else
tcs3490_als_enable(chip, 0);
return size;
}
static ssize_t tcs3490_auto_gain_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tcs3490_chip *chip = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%s\n",
chip->als_gain_auto ? "auto" : "manual");
}
static ssize_t tcs3490_auto_gain_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct tcs3490_chip *chip = dev_get_drvdata(dev);
bool value;
if (strtobool(buf, &value))
return -EINVAL;
if (value)
chip->als_gain_auto = true;
else
chip->als_gain_auto = false;
return size;
}
static ssize_t tcs3490_als_gain_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tcs3490_chip *chip = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%d\n",
chip->params.als_gain);
}
static ssize_t tcs3490_als_all_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t ret;
struct tcs3490_chip *chip = dev_get_drvdata(dev);
mutex_lock(&chip->lock);
ret = snprintf(buf, PAGE_SIZE, "%u,%u,%u,%u,%u,%u,%u,%u,%llu",
chip->als_inf.clear_red_raw,
chip->als_inf.clear_green_raw,
chip->als_inf.clear_blue_raw,
chip->als_inf.clear_raw,
chip->als_inf.red_raw,
chip->als_inf.green_raw,
chip->als_inf.blue_raw,
chip->als_inf.ir_raw,
chip->als_inf.timestamp);
mutex_unlock(&chip->lock);
return ret;
}
static ssize_t tcs3490_als_red_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t ret;
struct tcs3490_chip *chip = dev_get_drvdata(dev);
mutex_lock(&chip->lock);
ret = snprintf(buf, PAGE_SIZE, "%d", chip->als_inf.red_raw);
mutex_lock(&chip->lock);
return ret;
}
static ssize_t tcs3490_als_green_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t ret;
struct tcs3490_chip *chip = dev_get_drvdata(dev);
mutex_lock(&chip->lock);
ret = snprintf(buf, PAGE_SIZE, "%d", chip->als_inf.green_raw);
mutex_unlock(&chip->lock);
return ret;
}
static ssize_t tcs3490_als_blue_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t ret;
struct tcs3490_chip *chip = dev_get_drvdata(dev);
mutex_lock(&chip->lock);
ret = snprintf(buf, PAGE_SIZE, "%d", chip->als_inf.blue_raw);
mutex_unlock(&chip->lock);
return ret;
}
static ssize_t tcs3490_als_clear_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t ret;
struct tcs3490_chip *chip = dev_get_drvdata(dev);
mutex_lock(&chip->lock);
ret = snprintf(buf, PAGE_SIZE, "%d", chip->als_inf.clear_raw);
mutex_unlock(&chip->lock);
return ret;
}
static ssize_t tcs3490_als_gain_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
unsigned long gain;
int rc;
struct tcs3490_chip *chip = dev_get_drvdata(dev);
rc = kstrtoul(buf, 10, &gain);
if (rc)
return -EINVAL;
if (gain != 0 && gain != 1 && gain != 4 && gain != 16 &&
gain != 60 && gain != 64) {
return -EINVAL;
}
mutex_lock(&chip->lock);
if (gain) {
chip->als_gain_auto = false;
rc = tcs3490_set_als_gain(chip, gain);
} else {
chip->als_gain_auto = true;
}
tcs3490_flush_regs(chip);
mutex_unlock(&chip->lock);
return rc ? rc : size;
}
static ssize_t tcs3490_als_persist_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tcs3490_chip *chip = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%d",
(((chip->params.persist) & 0x0f)));
}
static ssize_t tcs3490_als_persist_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
long persist;
int rc;
struct tcs3490_chip *chip = dev_get_drvdata(dev);
rc = kstrtoul(buf, 10, &persist);
if (rc)
return -EINVAL;
mutex_lock(&chip->lock);
chip->shadow[TCS3490_PERSISTENCE] &= 0xF0;
chip->shadow[TCS3490_PERSISTENCE] |= ((u8)persist & 0x0F);
rc = tcs3490_flush_regs(chip);
if (!rc)
chip->params.persist = chip->shadow[TCS3490_PERSISTENCE];
mutex_unlock(&chip->lock);
return size;
}
static ssize_t tcs3490_als_itime_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tcs3490_chip *chip = dev_get_drvdata(dev);
int t;
mutex_lock(&chip->lock);
t = TCS3490_MAX_INTEGRATION_CYCLES - chip->params.als_time;
mutex_unlock(&chip->lock);
return snprintf(buf, PAGE_SIZE, "%d\n", t);
}
static ssize_t tcs3490_als_itime_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
long itime;
int rc;
struct tcs3490_chip *chip = dev_get_drvdata(dev);
rc = kstrtoul(buf, 10, &itime);
if (rc) {
return -EINVAL;
}
mutex_lock(&chip->lock);
chip->shadow[TCS3490_ALS_TIME] =
TCS3490_MAX_INTEGRATION_CYCLES - (u8)itime;
rc = tcs3490_flush_regs(chip);
if (!rc)
chip->params.als_time = chip->shadow[TCS3490_ALS_TIME];
mutex_unlock(&chip->lock);
return size;
}
static ssize_t tcs3490_als_thres_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tcs3490_chip *chip = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%d", chip->als_thres_enabled);
}
static ssize_t tcs3490_als_thres_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct tcs3490_chip *chip = dev_get_drvdata(dev);
int rc;
int on_enable;
rc = kstrtoint(buf, 10, &on_enable);
if (rc)
return -EINVAL;
if (on_enable == 1)
tcs3490_update_als_thres(chip, 1);
else
tcs3490_update_als_thres(chip, 0);
chip->als_thres_enabled = 1;
return size;
}
static ssize_t tcs3490_als_deltaP_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tcs3490_chip *chip = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE,
"%d (in %%)", chip->params.als_deltaP);
}
static ssize_t tcs3490_als_deltaP_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
unsigned long deltaP;
int rc;
struct tcs3490_chip *chip = dev_get_drvdata(dev);
rc = kstrtoul(buf, 10, &deltaP);
if (rc || deltaP > 100)
return -EINVAL;
mutex_lock(&chip->lock);
chip->params.als_deltaP = deltaP;
mutex_unlock(&chip->lock);
dev_info(&chip->client->dev,
"%s: Changed ALS deltaP, deltaP[%lu]",
__func__, deltaP);
return size;
}
/*=========== Switch IR/Clear channel ==================*/
static ssize_t tcs3490_als_channel_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t ret;
struct tcs3490_chip *chip = dev_get_drvdata(dev);
mutex_lock(&chip->lock);
ret = snprintf(buf, PAGE_SIZE, "%d",
((chip->als_channel & 0x80) == 0x00) ? 0 : 1);
mutex_unlock(&chip->lock);
return ret;
}
static ssize_t tcs3490_als_channel_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
unsigned long channel;
uint8_t ch_val = 0;
int rc;
struct tcs3490_chip *chip = dev_get_drvdata(dev);
rc = kstrtoul(buf, 10, &channel);
if (rc || channel > 2)
return -EINVAL;
ch_val = (channel == 0) ? 0x00 : 0x80;
rc = tcs3490_i2c_write(chip, TCS3490_CHANNEL, ch_val);
if (!rc) {
dev_info(&chip->client->dev, "%s: new channel %s\n",
__func__, ((ch_val & 0x80) == 0x00) ? "clear" : "ir");
mutex_lock(&chip->lock);
chip->als_channel = ch_val;
mutex_unlock(&chip->lock);
}
return size;
}
/*======================================================*/
static ssize_t tcs3490_chip_id_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tcs3490_chip *chip = dev_get_drvdata(dev);
uint8_t id;
tcs3490_i2c_read(chip, TCS3490_CHIPID, &id);
chip->shadow[TCS3490_CHIPID] = id;
return snprintf(buf,
PAGE_SIZE,
"0x%02x",
chip->shadow[TCS3490_CHIPID]);
}
static ssize_t tcs3490_notify(struct device *dev,
struct device_attribute *attr, char *buf)
{
dev_dbg(dev, "%s: Notify is called", __func__);
return snprintf(buf, PAGE_SIZE, "%d", 1);
}
/*=========== DEFINE DEVICE_ATTR ==================*/
static DEVICE_ATTR(chip_pow, S_IRUGO | S_IWUSR | S_IWGRP,
tcs3490_chip_pow_show, tcs3490_chip_pow_store);
static DEVICE_ATTR(als_Itime, S_IRUGO | S_IWUSR | S_IWGRP,
tcs3490_als_itime_show, tcs3490_als_itime_store);
static DEVICE_ATTR(als_thres, S_IRUGO | S_IWUSR | S_IWGRP,
tcs3490_als_thres_show, tcs3490_als_thres_store);
static DEVICE_ATTR(als_red, S_IRUGO, tcs3490_als_red_show, NULL);
static DEVICE_ATTR(als_green, S_IRUGO, tcs3490_als_green_show, NULL);
static DEVICE_ATTR(als_blue, S_IRUGO, tcs3490_als_blue_show, NULL);
static DEVICE_ATTR(als_clear, S_IRUGO, tcs3490_als_clear_show, NULL);
static DEVICE_ATTR(als_all, S_IRUGO, tcs3490_als_all_show, NULL);
static DEVICE_ATTR(als_gain, S_IRUGO | S_IWUSR | S_IWGRP,
tcs3490_als_gain_show, tcs3490_als_gain_store);
static DEVICE_ATTR(als_thresh_deltaP, S_IRUGO | S_IWUSR | S_IWGRP,
tcs3490_als_deltaP_show, tcs3490_als_deltaP_store);
static DEVICE_ATTR(als_auto_gain, S_IRUGO | S_IWUSR | S_IWGRP,
tcs3490_auto_gain_enable_show,
tcs3490_auto_gain_enable_store);
static DEVICE_ATTR(als_power_state, S_IRUGO | S_IWUSR | S_IWGRP,
tcs3490_als_enable_show, tcs3490_als_enable_store);
static DEVICE_ATTR(als_persist, S_IRUGO | S_IWUSR | S_IWGRP,
tcs3490_als_persist_show, tcs3490_als_persist_store);
static DEVICE_ATTR(als_channel, S_IRUGO | S_IWUSR | S_IWGRP,
tcs3490_als_channel_show, tcs3490_als_channel_store);
static DEVICE_ATTR(chip_id, S_IRUGO, tcs3490_chip_id_show, NULL);
static DEVICE_ATTR(notify, S_IRUGO, tcs3490_notify, NULL);
static struct attribute *tcs3490_attributes[] = {
&dev_attr_chip_pow.attr,
&dev_attr_als_Itime.attr,
&dev_attr_als_thres.attr,
&dev_attr_als_red.attr,
&dev_attr_als_green.attr,
&dev_attr_als_blue.attr,
&dev_attr_als_clear.attr,
&dev_attr_als_all.attr,
&dev_attr_als_gain.attr,
&dev_attr_als_thresh_deltaP.attr,
&dev_attr_als_auto_gain.attr,
&dev_attr_als_power_state.attr,
&dev_attr_als_persist.attr,
&dev_attr_als_channel.attr,
&dev_attr_chip_id.attr,
&dev_attr_notify.attr,
NULL
};
static const struct attribute_group tcs3490_attr_group = {
.attrs = tcs3490_attributes,
};
static int tcs3490_sensor_cam_io_notifier_call(struct notifier_block *self,
unsigned long event, void *data)
{
struct tcs3490_chip *chip;
uint32_t rc = 0;
if (event & REGULATOR_EVENT_DISABLE) {
chip = container_of(self, struct tcs3490_chip, cam_io_regulator_cb);
dev_info(&chip->client->dev,
"%s REGULATOR_EVENT_DISABLE occurred event \n", __func__);
if (chip->vdd_supply_enable)
rc = regulator_disable(chip->vdd);
else if (chip->gpio_vdd_enable)
rc = regulator_disable(chip->gpio_vdd);
if (!rc) {
chip->unpowered = true;
dev_info(&chip->client->dev,
"%s: Regulator vdd disable OK", __func__);
} else {
dev_err(&chip->client->dev,
"%s: Regulator vdd disable failed", __func__);
}
}
return NOTIFY_OK;
}
static int tcs3490_pltf_power_on(struct tcs3490_chip *chip)
{
int rc = 0;
mutex_lock(&chip->lock);
if (chip->vdd_supply_enable)
rc = regulator_enable(chip->vdd);
else if (chip->gpio_vdd_enable)
rc = regulator_enable(chip->gpio_vdd);
if (rc) {
dev_err(&chip->client->dev,
"Regulator vdd enable failed rc=%d\n", rc);
chip->unpowered = true;
mutex_unlock(&chip->lock);
} else {
if (chip->is_register_regulator_cam_vio_notify) {
regulator_unregister_notifier(chip->vio,
&(chip->cam_io_regulator_cb));
chip->is_register_regulator_cam_vio_notify = false;
}
if (chip->vio_supply_enable)
rc = regulator_enable(chip->vio);
if (rc) {
dev_err(&chip->client->dev,
"%s: Regulator vio enable failed rc=%d\n", __func__, rc);
chip->unpowered = true;
mutex_unlock(&chip->lock);
}
}
if (!rc) {
dev_dbg(&chip->client->dev,
"%s: rgbcir, power init, regulator enable OK\n", __func__);
/* Enable Oscillator */
usleep_range(1000, 1000);
tcs3490_i2c_write(chip, TCS3490_CONTROL, 0x01);
mutex_unlock(&chip->lock);
usleep_range(10000, 11000);
}
return rc;
}
static int tcs3490_power_on(struct tcs3490_chip *chip)
{
int rc = 0;
rc = tcs3490_pltf_power_on(chip);
if (rc)
return rc;
dev_info(&chip->client->dev, "%s: chip was off, restoring regs\n",
__func__);
dev_info(&chip->client->dev, "tcs3490: Setting defaults\n");
tcs3490_set_defaults(chip);
rc = tcs3490_flush_regs(chip);
if (!rc) {
chip->unpowered = false;
} else {
tcs3490_pltf_power_off(chip);
}
return rc;
}
static int tcs3490_pltf_power_off(struct tcs3490_chip *chip)
{
int rc = 0;
int rc2 = 0;
mutex_lock(&chip->lock);
/* Disable Oscillator */
tcs3490_i2c_write(chip, TCS3490_CONTROL, 0x00);
usleep_range(3000, 4000);
if (chip->vio_supply_enable && !chip->is_register_regulator_cam_vio_notify) {
chip->cam_io_regulator_cb.notifier_call = tcs3490_sensor_cam_io_notifier_call;
chip->cam_io_regulator_cb.priority = RGBCIR_SENSOR_REGULATOR_NOTIFY_PRIORITY;
if (regulator_register_notifier(chip->vio, &(chip->cam_io_regulator_cb)))
dev_err(&chip->client->dev,
"%s Regulator notification registration failed!\n", __func__);
else
chip->is_register_regulator_cam_vio_notify = true;
}
if (chip->vio_supply_enable) {
rc2 = regulator_disable(chip->vio);
if (rc2)
dev_err(&chip->client->dev,
"%s: Regulator vio disable failed", __func__);
else
dev_info(&chip->client->dev,
"%s: Regulator vio disable OK", __func__);
}
if (!chip->is_register_regulator_cam_vio_notify) {
if (chip->vdd_supply_enable)
rc = regulator_disable(chip->vdd);
else if (chip->gpio_vdd_enable)
rc = regulator_disable(chip->gpio_vdd);
if (!rc && !rc2)
chip->unpowered = true;
}
mutex_unlock(&chip->lock);
return rc;
}
static int tcs3490_power_init(struct tcs3490_chip *chip)
{
int rc = 0;
if (!chip)
return -EINVAL;
chip->unpowered = true;
if (chip->vdd_supply_enable) {
chip->vdd = regulator_get(&chip->client->dev, "rgbcir_vdd");
if (IS_ERR(chip->vdd)) {
rc = PTR_ERR(chip->vdd);
dev_err(&chip->client->dev,
"Regulator get failed, vdd, rc = %d\n", rc);
return rc;
}
} else if (chip->gpio_vdd_enable) {
chip->gpio_vdd = regulator_get(&chip->client->dev, "rgbcir_gpio_vdd");
if (IS_ERR(chip->gpio_vdd)) {
rc = PTR_ERR(chip->gpio_vdd);
dev_err(&chip->client->dev,
"Gpio-Regulator get failed,gpio_vdd, rc = %d\n", rc);
return rc;
}
}
if (chip->vio_supply_enable) {
chip->vio = regulator_get(&chip->client->dev, "rgbcir_vio");
if (IS_ERR(chip->vio)) {
rc = PTR_ERR(chip->vio);
dev_err(&chip->client->dev,
"%s: Regulator get failed,vio, rc = %d\n", __func__, rc);
return rc;
}
}
chip->is_register_regulator_cam_vio_notify = false;
dev_info(&chip->client->dev,
"%s: rgbcir, power init, regulator get OK vdd=%d, gpio_vdd=%d, vio=%d\n",
__func__, chip->vdd_supply_enable, chip->gpio_vdd_enable, chip->vio_supply_enable);
return rc;
}
static int tcs3490_power_deinit(struct tcs3490_chip *chip)
{
int rc = 0;
if (!chip)
return -EINVAL;
chip->unpowered = true;
if (chip->vio_supply_enable && chip->vio) {
regulator_put(chip->vio);
}
if (chip->is_register_regulator_cam_vio_notify) {
regulator_unregister_notifier(chip->vio, &(chip->cam_io_regulator_cb));
chip->is_register_regulator_cam_vio_notify = false;
}
if (chip->vdd_supply_enable && chip->vdd) {
regulator_put(chip->vdd);
} else if (chip->gpio_vdd_enable && chip->gpio_vdd) {
regulator_put(chip->gpio_vdd);
}
return rc;
}
static int tcs3490_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct tcs3490_chip *chip;
int rc = 0;
uint32_t val_u32;
dev_info(&client->dev, "start probing tcs3490\n");
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(&client->dev,
"%s: check functionality failed", __func__);
return -EIO;
}
dev_info(&client->dev, "tcs3490: Checking IRQ number %d\n",
client->irq);
if (client->irq < 0) {
dev_err(&client->dev, "%s: no reason to run.\n", __func__);
rc = -EINVAL;
goto init_failed;
} else {
dev_info(&client->dev, "%s: client->irq = %d\n",
__func__, client->irq);
}
chip = kzalloc(sizeof(struct tcs3490_chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
i2c_set_clientdata(client, chip);
chip->client = client;
chip->device_index = 0; /* use default */
if (chip->client->dev.of_node) {
rc = tcs3490_pinctrl_init(chip);
if (rc) {
dev_err(&client->dev,
"%s: failed to pinctrl init\n", __func__);
goto pinctrl_init_failed;
}
}
dev_info(&chip->client->dev, "tcs3490: Initializing mutex\n");
mutex_init(&chip->lock);
rc = of_property_read_u32(client->dev.of_node,
"ams,rgbcir-vdd-supply", &val_u32);
if (rc < 0) {
dev_err(&client->dev, "%s failed %d\n", __func__, __LINE__);
goto pinctrl_init_failed;
}
chip->vdd_supply_enable = val_u32;
rc = of_property_read_u32(client->dev.of_node,
"ams,rgbcir-gpio-vdd", &val_u32);
if (rc < 0) {
dev_err(&client->dev, "%s failed %d\n", __func__, __LINE__);
goto pinctrl_init_failed;
}
chip->gpio_vdd_enable = val_u32;
rc = of_property_read_u32(client->dev.of_node,
"ams,rgbcir-vio-supply", &val_u32);
if (rc < 0) {
dev_err(&client->dev, "%s failed %d\n", __func__, __LINE__);
goto pinctrl_init_failed;
}
chip->vio_supply_enable = val_u32;
rc = tcs3490_power_init(chip);
if (rc)
goto pinctrl_init_failed;
chip->a_idev = input_allocate_device();
if (!chip->a_idev) {
rc = -ENOMEM;
dev_err(&client->dev,
"%s: failed to allocate input device", __func__);
goto allocate_device_failed;
}
chip->a_idev->name = "AMS TCS3490 Sensor";
dev_info(&chip->client->dev, "tcs3490: set bit EV_ABS\n");
set_bit(EV_ABS, chip->a_idev->evbit);
dev_info(&chip->client->dev, "tcs3490: set bit ABS_MISC\n");
set_bit(ABS_MISC, chip->a_idev->absbit);
dev_info(&chip->client->dev, "tcs3490: Set ABS params\n");
input_set_abs_params(chip->a_idev, ABS_MISC, 0, 65535, 0, 0); /* check */
dev_info(&chip->client->dev,
"tcs3490: assigning open/close functions\n");
rc = input_register_device(chip->a_idev);
if (rc) {
dev_err(&client->dev,
"failed to register input device");
goto register_device_failed;
}
input_set_drvdata(chip->a_idev, chip);
dev_info(&chip->client->dev, "tcs3490: i2c nr %d\n",
client->adapter->nr);
rc = sysfs_create_group(&chip->a_idev->dev.kobj,
&tcs3490_attr_group);
if (rc) {
rc = -ENOMEM;
dev_err(&client->dev,
"%s: failed to create sysfs group", __func__);
goto create_group_failed;
}
rc = sysfs_create_link(chip->a_idev->dev.kobj.parent,
&chip->a_idev->dev.kobj, RGBCIR_SENSOR_SYSFS_LINK_NAME);
if (rc) {
rc = -ENOMEM;
dev_err(&client->dev,
"%s: failed to create sysfs link", __func__);
goto create_link_failed;
}
dev_info(&client->dev, "Probe ok.\n");
return 0;
create_link_failed:
sysfs_remove_group(&chip->a_idev->dev.kobj,
&tcs3490_attr_group);
create_group_failed:
input_unregister_device(chip->a_idev);
register_device_failed:
input_free_device(chip->a_idev);
allocate_device_failed:
tcs3490_power_deinit(chip);
pinctrl_init_failed:
kfree(chip);
init_failed:
dev_err(&client->dev, "Probe failed.\n");
return rc;
}
static int tcs3490_remove(struct i2c_client *client)
{
struct tcs3490_chip *chip = i2c_get_clientdata(client);
sysfs_remove_link(&chip->a_idev->dev.kobj,
RGBCIR_SENSOR_SYSFS_LINK_NAME);
sysfs_remove_group(&chip->a_idev->dev.kobj,
&tcs3490_attr_group);
free_irq(client->irq, chip);
if (chip->a_idev) {
input_unregister_device(chip->a_idev);
}
i2c_set_clientdata(client, NULL);
kfree(chip);
return 0;
}
static const struct i2c_device_id tcs3490_idtable[] = {
{ "tcs3490", 0 },
{}
};
MODULE_DEVICE_TABLE(i2c, tcs3490_idtable);
static const struct of_device_id tcs3490_dt_match[] = {
{ .compatible = "ams,tcs3490", },
{ },
};
MODULE_DEVICE_TABLE(of, tcs3490_dt_match);
static struct i2c_driver tcs3490_driver = {
.driver = {
.name = "tcs3490",
.owner = THIS_MODULE,
.of_match_table = tcs3490_dt_match,
},
.id_table = tcs3490_idtable,
.probe = tcs3490_probe,
.remove = tcs3490_remove,
};
static int __init tcs3490_init(void)
{
int rc = 0;
pr_info("Initialize TCS3490 driver");
rc = i2c_add_driver(&tcs3490_driver);
pr_info("TCS3490 added i2c driver rc = %d", rc);
return rc;
}
static void __exit tcs3490_exit(void)
{
pr_info("Delete TCS3490 driver");
i2c_del_driver(&tcs3490_driver);
}
module_init(tcs3490_init);
module_exit(tcs3490_exit);
MODULE_DESCRIPTION("AMS tcs3490 driver");
MODULE_LICENSE("GPL v2");