// SPDX-License-Identifier: GPL-2.0+
/*
* apds9960.c - Support for Avago APDS9960 gesture/RGB/ALS/proximity sensor
*
* Copyright (C) 2015, 2018
* Author: Matt Ranostay <matt.ranostay@konsulko.com>
*
* TODO: gesture + proximity calib offsets
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/irq.h>
#include <linux/i2c.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/iio/iio.h>
#include <linux/iio/buffer.h>
#include <linux/iio/events.h>
#include <linux/iio/kfifo_buf.h>
#include <linux/iio/sysfs.h>
#define APDS9960_REGMAP_NAME "apds9960_regmap"
#define APDS9960_DRV_NAME "apds9960"
#define APDS9960_REG_RAM_START 0x00
#define APDS9960_REG_RAM_END 0x7f
#define APDS9960_REG_ENABLE 0x80
#define APDS9960_REG_ATIME 0x81
#define APDS9960_REG_WTIME 0x83
#define APDS9960_REG_AILTL 0x84
#define APDS9960_REG_AILTH 0x85
#define APDS9960_REG_AIHTL 0x86
#define APDS9960_REG_AIHTH 0x87
#define APDS9960_REG_PILT 0x89
#define APDS9960_REG_PIHT 0x8b
#define APDS9960_REG_PERS 0x8c
#define APDS9960_REG_CONFIG_1 0x8d
#define APDS9960_REG_PPULSE 0x8e
#define APDS9960_REG_CONTROL 0x8f
#define APDS9960_REG_CONTROL_AGAIN_MASK 0x03
#define APDS9960_REG_CONTROL_PGAIN_MASK 0x0c
#define APDS9960_REG_CONTROL_AGAIN_MASK_SHIFT 0
#define APDS9960_REG_CONTROL_PGAIN_MASK_SHIFT 2
#define APDS9960_REG_CONFIG_2 0x90
#define APDS9960_REG_CONFIG_2_GGAIN_MASK 0x60
#define APDS9960_REG_CONFIG_2_GGAIN_MASK_SHIFT 5
#define APDS9960_REG_ID 0x92
#define APDS9960_REG_STATUS 0x93
#define APDS9960_REG_STATUS_PS_INT BIT(5)
#define APDS9960_REG_STATUS_ALS_INT BIT(4)
#define APDS9960_REG_STATUS_GINT BIT(2)
#define APDS9960_REG_PDATA 0x9c
#define APDS9960_REG_POFFSET_UR 0x9d
#define APDS9960_REG_POFFSET_DL 0x9e
#define APDS9960_REG_CONFIG_3 0x9f
#define APDS9960_REG_GPENTH 0xa0
#define APDS9960_REG_GEXTH 0xa1
#define APDS9960_REG_GCONF_1 0xa2
#define APDS9960_REG_GCONF_1_GFIFO_THRES_MASK 0xc0
#define APDS9960_REG_GCONF_1_GFIFO_THRES_MASK_SHIFT 6
#define APDS9960_REG_GCONF_2 0xa3
#define APDS9960_REG_GOFFSET_U 0xa4
#define APDS9960_REG_GOFFSET_D 0xa5
#define APDS9960_REG_GPULSE 0xa6
#define APDS9960_REG_GOFFSET_L 0xa7
#define APDS9960_REG_GOFFSET_R 0xa9
#define APDS9960_REG_GCONF_3 0xaa
#define APDS9960_REG_GCONF_4 0xab
#define APDS9960_REG_GFLVL 0xae
#define APDS9960_REG_GSTATUS 0xaf
#define APDS9960_REG_IFORCE 0xe4
#define APDS9960_REG_PICLEAR 0xe5
#define APDS9960_REG_CICLEAR 0xe6
#define APDS9960_REG_AICLEAR 0xe7
#define APDS9960_DEFAULT_PERS 0x33
#define APDS9960_DEFAULT_GPENTH 0x50
#define APDS9960_DEFAULT_GEXTH 0x40
#define APDS9960_MAX_PXS_THRES_VAL 255
#define APDS9960_MAX_ALS_THRES_VAL 0xffff
#define APDS9960_MAX_INT_TIME_IN_US 1000000
enum apds9960_als_channel_idx {
IDX_ALS_CLEAR, IDX_ALS_RED, IDX_ALS_GREEN, IDX_ALS_BLUE,
};
#define APDS9960_REG_ALS_BASE 0x94
#define APDS9960_REG_ALS_CHANNEL(_colour) \
(APDS9960_REG_ALS_BASE + (IDX_ALS_##_colour * 2))
enum apds9960_gesture_channel_idx {
IDX_DIR_UP, IDX_DIR_DOWN, IDX_DIR_LEFT, IDX_DIR_RIGHT,
};
#define APDS9960_REG_GFIFO_BASE 0xfc
#define APDS9960_REG_GFIFO_DIR(_dir) \
(APDS9960_REG_GFIFO_BASE + IDX_DIR_##_dir)
struct apds9960_data {
struct i2c_client *client;
struct iio_dev *indio_dev;
struct mutex lock;
/* regmap fields */
struct regmap *regmap;
struct regmap_field *reg_int_als;
struct regmap_field *reg_int_ges;
struct regmap_field *reg_int_pxs;
struct regmap_field *reg_enable_als;
struct regmap_field *reg_enable_ges;
struct regmap_field *reg_enable_pxs;
/* state */
int als_int;
int pxs_int;
int gesture_mode_running;
/* gain values */
int als_gain;
int pxs_gain;
/* integration time value in us */
int als_adc_int_us;
/* gesture buffer */
u8 buffer[4]; /* 4 8-bit channels */
};
static const struct reg_default apds9960_reg_defaults[] = {
/* Default ALS integration time = 2.48ms */
{ APDS9960_REG_ATIME, 0xff },
};
static const struct regmap_range apds9960_volatile_ranges[] = {
regmap_reg_range(APDS9960_REG_STATUS,
APDS9960_REG_PDATA),
regmap_reg_range(APDS9960_REG_GFLVL,
APDS9960_REG_GSTATUS),
regmap_reg_range(APDS9960_REG_GFIFO_DIR(UP),
APDS9960_REG_GFIFO_DIR(RIGHT)),
regmap_reg_range(APDS9960_REG_IFORCE,
APDS9960_REG_AICLEAR),
};
static const struct regmap_access_table apds9960_volatile_table = {
.yes_ranges = apds9960_volatile_ranges,
.n_yes_ranges = ARRAY_SIZE(apds9960_volatile_ranges),
};
static const struct regmap_range apds9960_precious_ranges[] = {
regmap_reg_range(APDS9960_REG_RAM_START, APDS9960_REG_RAM_END),
};
static const struct regmap_access_table apds9960_precious_table = {
.yes_ranges = apds9960_precious_ranges,
.n_yes_ranges = ARRAY_SIZE(apds9960_precious_ranges),
};
static const struct regmap_range apds9960_readable_ranges[] = {
regmap_reg_range(APDS9960_REG_ENABLE,
APDS9960_REG_GSTATUS),
regmap_reg_range(APDS9960_REG_GFIFO_DIR(UP),
APDS9960_REG_GFIFO_DIR(RIGHT)),
};
static const struct regmap_access_table apds9960_readable_table = {
.yes_ranges = apds9960_readable_ranges,
.n_yes_ranges = ARRAY_SIZE(apds9960_readable_ranges),
};
static const struct regmap_range apds9960_writeable_ranges[] = {
regmap_reg_range(APDS9960_REG_ENABLE, APDS9960_REG_CONFIG_2),
regmap_reg_range(APDS9960_REG_POFFSET_UR, APDS9960_REG_GCONF_4),
regmap_reg_range(APDS9960_REG_IFORCE, APDS9960_REG_AICLEAR),