| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * AWINIC aw96103 proximity sensor driver |
| * |
| * Author: Wang Shuaijie <wangshuaijie@awinic.com> |
| * |
| * Copyright (c) 2024 awinic Technology CO., LTD |
| */ |
| #include <linux/bits.h> |
| #include <linux/bitfield.h> |
| #include <linux/delay.h> |
| #include <linux/firmware.h> |
| #include <linux/i2c.h> |
| #include <linux/interrupt.h> |
| #include <linux/iio/events.h> |
| #include <linux/iio/iio.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/regmap.h> |
| #include <linux/slab.h> |
| #include <linux/unaligned.h> |
| |
| #define AW_DATA_PROCESS_FACTOR 1024 |
| #define AW96103_CHIP_ID 0xa961 |
| #define AW96103_BIN_VALID_DATA_OFFSET 64 |
| #define AW96103_BIN_DATA_LEN_OFFSET 16 |
| #define AW96103_BIN_DATA_REG_NUM_SIZE 4 |
| #define AW96103_BIN_CHIP_TYPE_SIZE 8 |
| #define AW96103_BIN_CHIP_TYPE_OFFSET 24 |
| |
| #define AW96103_REG_SCANCTRL0 0x0000 |
| #define AW96103_REG_STAT0 0x0090 |
| #define AW96103_REG_BLFILT_CH0 0x00A8 |
| #define AW96103_REG_BLRSTRNG_CH0 0x00B4 |
| #define AW96103_REG_DIFF_CH0 0x0240 |
| #define AW96103_REG_FWVER2 0x0410 |
| #define AW96103_REG_CMD 0xF008 |
| #define AW96103_REG_IRQSRC 0xF080 |
| #define AW96103_REG_IRQEN 0xF084 |
| #define AW96103_REG_RESET 0xFF0C |
| #define AW96103_REG_CHIPID 0xFF10 |
| #define AW96103_REG_EEDA0 0x0408 |
| #define AW96103_REG_EEDA1 0x040C |
| #define AW96103_REG_PROXCTRL_CH0 0x00B0 |
| #define AW96103_REG_PROXTH0_CH0 0x00B8 |
| #define AW96103_PROXTH_CH_STEP 0x3C |
| #define AW96103_THHYST_MASK GENMASK(13, 12) |
| #define AW96103_INDEB_MASK GENMASK(11, 10) |
| #define AW96103_OUTDEB_MASK GENMASK(9, 8) |
| #define AW96103_INITOVERIRQ_MASK BIT(0) |
| #define AW96103_BLFILT_CH_STEP 0x3C |
| #define AW96103_BLRSTRNG_MASK GENMASK(5, 0) |
| #define AW96103_CHIPID_MASK GENMASK(31, 16) |
| #define AW96103_BLERRTRIG_MASK BIT(25) |
| #define AW96103_CHAN_EN_MASK GENMASK(5, 0) |
| #define AW96103_REG_PROXCTRL_CH(x) \ |
| (AW96103_REG_PROXCTRL_CH0 + (x) * AW96103_PROXTH_CH_STEP) |
| |
| #define AW96103_REG_PROXTH0_CH(x) \ |
| (AW96103_REG_PROXTH0_CH0 + (x) * AW96103_PROXTH_CH_STEP) |
| |
| /** |
| * struct aw_bin - Store the data obtained from parsing the configuration file. |
| * @chip_type: Frame header information-chip type |
| * @valid_data_len: Length of valid data obtained after parsing |
| * @valid_data_addr: The offset address of the valid data obtained |
| * after parsing relative to info |
| * @len: The size of the bin file obtained from the firmware |
| * @data: Store the bin file obtained from the firmware |
| */ |
| struct aw_bin { |
| unsigned char chip_type[8]; |
| unsigned int valid_data_len; |
| unsigned int valid_data_addr; |
| unsigned int len; |
| unsigned char data[] __counted_by(len); |
| }; |
| |
| enum aw96103_sar_vers { |
| AW96103 = 2, |
| AW96103A = 6, |
| AW96103B = 0xa, |
| }; |
| |
| enum aw96103_operation_mode { |
| AW96103_ACTIVE_MODE = 1, |
| AW96103_SLEEP_MODE = 2, |
| AW96103_DEEPSLEEP_MODE = 3, |
| AW96103B_DEEPSLEEP_MODE = 4, |
| }; |
| |
| enum aw96103_sensor_type { |
| AW96103_VAL, |
| AW96105_VAL, |
| }; |
| |
| struct aw_channels_info { |
| bool used; |
| unsigned int old_irq_status; |
| }; |
| |
| struct aw_chip_info { |
| const char *name; |
| struct iio_chan_spec const *channels; |
| int num_channels; |
| }; |
| |
| struct aw96103 { |
| unsigned int hostirqen; |
| struct regmap *regmap; |
| struct device *dev; |
| /* |
| * There is one more logical channel than the actual channels, |
| * and the extra logical channel is used for temperature detection |
| * but not for status detection. The specific channel used for |
| * temperature detection is determined by the register configuration. |
| */ |
| struct aw_channels_info channels_arr[6]; |
| unsigned int max_channels; |
| unsigned int chan_en; |
| }; |
| |
| static const unsigned int aw96103_reg_default[] = { |
| 0x0000, 0x00003f3f, 0x0004, 0x00000064, 0x0008, 0x0017c11e, |
| 0x000c, 0x05000000, 0x0010, 0x00093ffd, 0x0014, 0x19240009, |
| 0x0018, 0xd81c0207, 0x001c, 0xff000000, 0x0020, 0x00241900, |
| 0x0024, 0x00093ff7, 0x0028, 0x58020009, 0x002c, 0xd81c0207, |
| 0x0030, 0xff000000, 0x0034, 0x00025800, 0x0038, 0x00093fdf, |
| 0x003c, 0x7d3b0009, 0x0040, 0xd81c0207, 0x0044, 0xff000000, |
| 0x0048, 0x003b7d00, 0x004c, 0x00093f7f, 0x0050, 0xe9310009, |
| 0x0054, 0xd81c0207, 0x0058, 0xff000000, 0x005c, 0x0031e900, |
| 0x0060, 0x00093dff, 0x0064, 0x1a0c0009, 0x0068, 0xd81c0207, |
| 0x006c, 0xff000000, 0x0070, 0x000c1a00, 0x0074, 0x80093fff, |
| 0x0078, 0x043d0009, 0x007c, 0xd81c0207, 0x0080, 0xff000000, |
| 0x0084, 0x003d0400, 0x00a0, 0xe6400000, 0x00a4, 0x00000000, |
| 0x00a8, 0x010408d2, 0x00ac, 0x00000000, 0x00b0, 0x00000000, |
| 0x00b8, 0x00005fff, 0x00bc, 0x00000000, 0x00c0, 0x00000000, |
| 0x00c4, 0x00000000, 0x00c8, 0x00000000, 0x00cc, 0x00000000, |
| 0x00d0, 0x00000000, 0x00d4, 0x00000000, 0x00d8, 0x00000000, |
| 0x00dc, 0xe6447800, 0x00e0, 0x78000000, 0x00e4, 0x010408d2, |
| 0x00e8, 0x00000000, 0x00ec, 0x00000000, 0x00f4, 0x00005fff, |
| 0x00f8, 0x00000000, 0x00fc, 0x00000000, 0x0100, 0x00000000, |
| 0x0104, 0x00000000, 0x0108, 0x00000000, 0x010c, 0x02000000, |
| 0x0110, 0x00000000, 0x0114, 0x00000000, 0x0118, 0xe6447800, |
| 0x011c, 0x78000000, 0x0120, 0x010408d2, 0x0124, 0x00000000, |
| 0x0128, 0x00000000, 0x0130, 0x00005fff, 0x0134, 0x00000000, |
| 0x0138, 0x00000000, 0x013c, 0x00000000, 0x0140, 0x00000000, |
| 0x0144, 0x00000000, 0x0148, 0x02000000, 0x014c, 0x00000000, |
| 0x0150, 0x00000000, 0x0154, 0xe6447800, 0x0158, 0x78000000, |
| 0x015c, 0x010408d2, 0x0160, 0x00000000, 0x0164, 0x00000000, |
| 0x016c, 0x00005fff, 0x0170, 0x00000000, 0x0174, 0x00000000, |
| 0x0178, 0x00000000, 0x017c, 0x00000000, 0x0180, 0x00000000, |
| 0x0184, 0x02000000, 0x0188, 0x00000000, 0x018c, 0x00000000, |
| 0x0190, 0xe6447800, 0x0194, 0x78000000, 0x0198, 0x010408d2, |
| 0x019c, 0x00000000, 0x01a0, 0x00000000, 0x01a8, 0x00005fff, |
| 0x01ac, 0x00000000, 0x01b0, 0x00000000, 0x01b4, 0x00000000, |
| 0x01b8, 0x00000000, 0x01bc, 0x00000000, 0x01c0, 0x02000000, |
| 0x01c4, 0x00000000, 0x01c8, 0x00000000, 0x01cc, 0xe6407800, |
| 0x01d0, 0x78000000, 0x01d4, 0x010408d2, 0x01d8, 0x00000000, |
| 0x01dc, 0x00000000, 0x01e4, 0x00005fff, 0x01e8, 0x00000000, |
| 0x01ec, 0x00000000, 0x01f0, 0x00000000, 0x01f4, 0x00000000, |
| 0x01f8, 0x00000000, 0x01fc, 0x02000000, 0x0200, 0x00000000, |
| 0x0204, 0x00000000, 0x0208, 0x00000008, 0x020c, 0x0000000d, |
| 0x41fc, 0x00000000, 0x4400, 0x00000000, 0x4410, 0x00000000, |
| 0x4420, 0x00000000, 0x4430, 0x00000000, 0x4440, 0x00000000, |
| 0x4450, 0x00000000, 0x4460, 0x00000000, 0x4470, 0x00000000, |
| 0xf080, 0x00003018, 0xf084, 0x00000fff, 0xf800, 0x00000000, |
| 0xf804, 0x00002e00, 0xf8d0, 0x00000001, 0xf8d4, 0x00000000, |
| 0xff00, 0x00000301, 0xff0c, 0x01000000, 0xffe0, 0x00000000, |
| 0xfff4, 0x00004011, 0x0090, 0x00000000, 0x0094, 0x00000000, |
| 0x0098, 0x00000000, 0x009c, 0x3f3f3f3f, |
| }; |
| |
| static const struct iio_event_spec aw_common_events[3] = { |
| { |
| .type = IIO_EV_TYPE_THRESH, |
| .dir = IIO_EV_DIR_RISING, |
| .mask_separate = BIT(IIO_EV_INFO_PERIOD), |
| }, |
| { |
| .type = IIO_EV_TYPE_THRESH, |
| .dir = IIO_EV_DIR_FALLING, |
| .mask_separate = BIT(IIO_EV_INFO_PERIOD), |
| }, |
| { |
| .type = IIO_EV_TYPE_THRESH, |
| .dir = IIO_EV_DIR_EITHER, |
| .mask_separate = BIT(IIO_EV_INFO_ENABLE) | |
| BIT(IIO_EV_INFO_HYSTERESIS) | |
| BIT(IIO_EV_INFO_VALUE), |
| } |
| }; |
| |
| #define AW_IIO_CHANNEL(idx) \ |
| { \ |
| .type = IIO_PROXIMITY, \ |
| .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
| .indexed = 1, \ |
| .channel = idx, \ |
| .event_spec = aw_common_events, \ |
| .num_event_specs = ARRAY_SIZE(aw_common_events), \ |
| } \ |
| |
| static const struct iio_chan_spec aw96103_channels[] = { |
| AW_IIO_CHANNEL(0), |
| AW_IIO_CHANNEL(1), |
| AW_IIO_CHANNEL(2), |
| AW_IIO_CHANNEL(3), |
| }; |
| |
| static const struct iio_chan_spec aw96105_channels[] = { |
| AW_IIO_CHANNEL(0), |
| AW_IIO_CHANNEL(1), |
| AW_IIO_CHANNEL(2), |
| AW_IIO_CHANNEL(3), |
| AW_IIO_CHANNEL(4), |
| AW_IIO_CHANNEL(5), |
| }; |
| |
| static const struct aw_chip_info aw_chip_info_tbl[] = { |
| [AW96103_VAL] = { |
| .name = "aw96103_sensor", |
| .channels = aw96103_channels, |
| .num_channels = ARRAY_SIZE(aw96103_channels), |
| }, |
| [AW96105_VAL] = { |
| .name = "aw96105_sensor", |
| .channels = aw96105_channels, |
| .num_channels = ARRAY_SIZE(aw96105_channels), |
| }, |
| }; |
| |
| static void aw96103_parsing_bin_file(struct aw_bin *bin) |
| { |
| bin->valid_data_addr = AW96103_BIN_VALID_DATA_OFFSET; |
| bin->valid_data_len = |
| *(unsigned int *)(bin->data + AW96103_BIN_DATA_LEN_OFFSET) - |
| AW96103_BIN_DATA_REG_NUM_SIZE; |
| memcpy(bin->chip_type, bin->data + AW96103_BIN_CHIP_TYPE_OFFSET, |
| AW96103_BIN_CHIP_TYPE_SIZE); |
| } |
| |
| static const struct regmap_config aw96103_regmap_confg = { |
| .reg_bits = 16, |
| .val_bits = 32, |
| }; |
| |
| static int aw96103_get_diff_raw(struct aw96103 *aw96103, unsigned int chan, |
| int *buf) |
| { |
| u32 data; |
| int ret; |
| |
| ret = regmap_read(aw96103->regmap, |
| AW96103_REG_DIFF_CH0 + chan * 4, &data); |
| if (ret) |
| return ret; |
| *buf = (int)(data / AW_DATA_PROCESS_FACTOR); |
| |
| return 0; |
| } |
| |
| static int aw96103_read_raw(struct iio_dev *indio_dev, |
| const struct iio_chan_spec *chan, |
| int *val, int *val2, long mask) |
| { |
| struct aw96103 *aw96103 = iio_priv(indio_dev); |
| int ret; |
| |
| switch (mask) { |
| case IIO_CHAN_INFO_RAW: |
| ret = aw96103_get_diff_raw(aw96103, chan->channel, val); |
| if (ret) |
| return ret; |
| |
| return IIO_VAL_INT; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int aw96103_read_thresh(struct aw96103 *aw96103, |
| const struct iio_chan_spec *chan, int *val) |
| { |
| int ret; |
| |
| ret = regmap_read(aw96103->regmap, |
| AW96103_REG_PROXTH0_CH(chan->channel), val); |
| if (ret) |
| return ret; |
| |
| return IIO_VAL_INT; |
| } |
| |
| static int aw96103_read_out_debounce(struct aw96103 *aw96103, |
| const struct iio_chan_spec *chan, |
| int *val) |
| { |
| unsigned int reg_val; |
| int ret; |
| |
| ret = regmap_read(aw96103->regmap, |
| AW96103_REG_PROXCTRL_CH(chan->channel), ®_val); |
| if (ret) |
| return ret; |
| *val = FIELD_GET(AW96103_OUTDEB_MASK, reg_val); |
| |
| return IIO_VAL_INT; |
| } |
| |
| static int aw96103_read_in_debounce(struct aw96103 *aw96103, |
| const struct iio_chan_spec *chan, int *val) |
| { |
| unsigned int reg_val; |
| int ret; |
| |
| ret = regmap_read(aw96103->regmap, |
| AW96103_REG_PROXCTRL_CH(chan->channel), ®_val); |
| if (ret) |
| return ret; |
| *val = FIELD_GET(AW96103_INDEB_MASK, reg_val); |
| |
| return IIO_VAL_INT; |
| } |
| |
| static int aw96103_read_hysteresis(struct aw96103 *aw96103, |
| const struct iio_chan_spec *chan, int *val) |
| { |
| unsigned int reg_val; |
| int ret; |
| |
| ret = regmap_read(aw96103->regmap, |
| AW96103_REG_PROXCTRL_CH(chan->channel), ®_val); |
| if (ret) |
| return ret; |
| *val = FIELD_GET(AW96103_THHYST_MASK, reg_val); |
| |
| return IIO_VAL_INT; |
| } |
| |
| static int aw96103_read_event_val(struct iio_dev *indio_dev, |
| const struct iio_chan_spec *chan, |
| enum iio_event_type type, |
| enum iio_event_direction dir, |
| enum iio_event_info info, |
| int *val, int *val2) |
| { |
| struct aw96103 *aw96103 = iio_priv(indio_dev); |
| |
| if (chan->type != IIO_PROXIMITY) |
| return -EINVAL; |
| |
| switch (info) { |
| case IIO_EV_INFO_VALUE: |
| return aw96103_read_thresh(aw96103, chan, val); |
| case IIO_EV_INFO_PERIOD: |
| switch (dir) { |
| case IIO_EV_DIR_RISING: |
| return aw96103_read_out_debounce(aw96103, chan, val); |
| case IIO_EV_DIR_FALLING: |
| return aw96103_read_in_debounce(aw96103, chan, val); |
| default: |
| return -EINVAL; |
| } |
| case IIO_EV_INFO_HYSTERESIS: |
| return aw96103_read_hysteresis(aw96103, chan, val); |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int aw96103_write_event_val(struct iio_dev *indio_dev, |
| const struct iio_chan_spec *chan, |
| enum iio_event_type type, |
| enum iio_event_direction dir, |
| enum iio_event_info info, int val, int val2) |
| { |
| struct aw96103 *aw96103 = iio_priv(indio_dev); |
| |
| if (chan->type != IIO_PROXIMITY) |
| return -EINVAL; |
| |
| switch (info) { |
| case IIO_EV_INFO_VALUE: |
| return regmap_write(aw96103->regmap, |
| AW96103_REG_PROXTH0_CH(chan->channel), val); |
| case IIO_EV_INFO_PERIOD: |
| switch (dir) { |
| case IIO_EV_DIR_RISING: |
| return regmap_update_bits(aw96103->regmap, |
| AW96103_REG_PROXCTRL_CH(chan->channel), |
| AW96103_OUTDEB_MASK, |
| FIELD_PREP(AW96103_OUTDEB_MASK, val)); |
| |
| case IIO_EV_DIR_FALLING: |
| return regmap_update_bits(aw96103->regmap, |
| AW96103_REG_PROXCTRL_CH(chan->channel), |
| AW96103_INDEB_MASK, |
| FIELD_PREP(AW96103_INDEB_MASK, val)); |
| default: |
| return -EINVAL; |
| } |
| case IIO_EV_INFO_HYSTERESIS: |
| return regmap_update_bits(aw96103->regmap, |
| AW96103_REG_PROXCTRL_CH(chan->channel), |
| AW96103_THHYST_MASK, |
| FIELD_PREP(AW96103_THHYST_MASK, val)); |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int aw96103_read_event_config(struct iio_dev *indio_dev, |
| const struct iio_chan_spec *chan, |
| enum iio_event_type type, |
| enum iio_event_direction dir) |
| { |
| struct aw96103 *aw96103 = iio_priv(indio_dev); |
| |
| return aw96103->channels_arr[chan->channel].used; |
| } |
| |
| static int aw96103_write_event_config(struct iio_dev *indio_dev, |
| const struct iio_chan_spec *chan, |
| enum iio_event_type type, |
| enum iio_event_direction dir, int state) |
| { |
| struct aw96103 *aw96103 = iio_priv(indio_dev); |
| |
| aw96103->channels_arr[chan->channel].used = !!state; |
| |
| return regmap_update_bits(aw96103->regmap, AW96103_REG_SCANCTRL0, |
| BIT(chan->channel), |
| state ? BIT(chan->channel) : 0); |
| } |
| |
| static struct iio_info iio_info = { |
| .read_raw = aw96103_read_raw, |
| .read_event_value = aw96103_read_event_val, |
| .write_event_value = aw96103_write_event_val, |
| .read_event_config = aw96103_read_event_config, |
| .write_event_config = aw96103_write_event_config, |
| }; |
| |
| static int aw96103_channel_scan_start(struct aw96103 *aw96103) |
| { |
| int ret; |
| |
| ret = regmap_write(aw96103->regmap, AW96103_REG_CMD, |
| AW96103_ACTIVE_MODE); |
| if (ret) |
| return ret; |
| |
| return regmap_write(aw96103->regmap, AW96103_REG_IRQEN, |
| aw96103->hostirqen); |
| } |
| |
| static int aw96103_reg_version_comp(struct aw96103 *aw96103, |
| struct aw_bin *aw_bin) |
| { |
| u32 blfilt1_data, fw_ver; |
| unsigned char i; |
| int ret; |
| |
| ret = regmap_read(aw96103->regmap, AW96103_REG_FWVER2, &fw_ver); |
| if (ret) |
| return ret; |
| /* |
| * If the chip version is AW96103A and the loaded register |
| * configuration file is for AW96103, special handling of the |
| * AW96103_REG_BLRSTRNG_CH0 register is required. |
| */ |
| if ((fw_ver != AW96103A) || (aw_bin->chip_type[7] != '\0')) |
| return 0; |
| |
| for (i = 0; i < aw96103->max_channels; i++) { |
| ret = regmap_read(aw96103->regmap, |
| AW96103_REG_BLFILT_CH0 + (AW96103_BLFILT_CH_STEP * i), |
| &blfilt1_data); |
| if (ret) |
| return ret; |
| if (FIELD_GET(AW96103_BLERRTRIG_MASK, blfilt1_data) != 1) |
| return 0; |
| |
| ret = regmap_update_bits(aw96103->regmap, |
| AW96103_REG_BLRSTRNG_CH0 + (AW96103_BLFILT_CH_STEP * i), |
| AW96103_BLRSTRNG_MASK, 1 << i); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int aw96103_bin_valid_loaded(struct aw96103 *aw96103, |
| struct aw_bin *aw_bin_data_s) |
| { |
| unsigned int start_addr = aw_bin_data_s->valid_data_addr; |
| u32 i, reg_data; |
| u16 reg_addr; |
| int ret; |
| |
| for (i = 0; i < aw_bin_data_s->valid_data_len; |
| i += 6, start_addr += 6) { |
| reg_addr = get_unaligned_le16(aw_bin_data_s->data + start_addr); |
| reg_data = get_unaligned_le32(aw_bin_data_s->data + |
| start_addr + 2); |
| if ((reg_addr == AW96103_REG_EEDA0) || |
| (reg_addr == AW96103_REG_EEDA1)) |
| continue; |
| if (reg_addr == AW96103_REG_IRQEN) { |
| aw96103->hostirqen = reg_data; |
| continue; |
| } |
| if (reg_addr == AW96103_REG_SCANCTRL0) |
| aw96103->chan_en = FIELD_GET(AW96103_CHAN_EN_MASK, |
| reg_data); |
| |
| ret = regmap_write(aw96103->regmap, reg_addr, reg_data); |
| if (ret < 0) |
| return ret; |
| } |
| |
| ret = aw96103_reg_version_comp(aw96103, aw_bin_data_s); |
| if (ret) |
| return ret; |
| |
| return aw96103_channel_scan_start(aw96103); |
| } |
| |
| static int aw96103_para_loaded(struct aw96103 *aw96103) |
| { |
| int i, ret; |
| |
| for (i = 0; i < ARRAY_SIZE(aw96103_reg_default); i += 2) { |
| ret = regmap_write(aw96103->regmap, |
| (u16)aw96103_reg_default[i], |
| (u32)aw96103_reg_default[i + 1]); |
| if (ret) |
| return ret; |
| if (aw96103_reg_default[i] == AW96103_REG_IRQEN) |
| aw96103->hostirqen = aw96103_reg_default[i + 1]; |
| else if (aw96103_reg_default[i] == AW96103_REG_SCANCTRL0) |
| aw96103->chan_en = FIELD_GET(AW96103_CHAN_EN_MASK, |
| aw96103_reg_default[i + 1]); |
| } |
| |
| return aw96103_channel_scan_start(aw96103); |
| } |
| |
| static int aw96103_cfg_all_loaded(const struct firmware *cont, |
| struct aw96103 *aw96103) |
| { |
| if (!cont) |
| return -EINVAL; |
| |
| struct aw_bin *aw_bin __free(kfree) = |
| kzalloc(cont->size + sizeof(*aw_bin), GFP_KERNEL); |
| if (!aw_bin) |
| return -ENOMEM; |
| |
| aw_bin->len = cont->size; |
| memcpy(aw_bin->data, cont->data, cont->size); |
| release_firmware(cont); |
| aw96103_parsing_bin_file(aw_bin); |
| |
| return aw96103_bin_valid_loaded(aw96103, aw_bin); |
| } |
| |
| static void aw96103_cfg_update(const struct firmware *fw, void *data) |
| { |
| struct aw96103 *aw96103 = data; |
| int ret, i; |
| |
| if (!fw || !fw->data) { |
| dev_err(aw96103->dev, "No firmware.\n"); |
| return; |
| } |
| |
| ret = aw96103_cfg_all_loaded(fw, aw96103); |
| /* |
| * If loading the register configuration file fails, |
| * load the default register configuration in the driver to |
| * ensure the basic functionality of the device. |
| */ |
| if (ret) { |
| ret = aw96103_para_loaded(aw96103); |
| if (ret) { |
| dev_err(aw96103->dev, "load param error.\n"); |
| return; |
| } |
| } |
| |
| for (i = 0; i < aw96103->max_channels; i++) { |
| if ((aw96103->chan_en >> i) & 0x01) |
| aw96103->channels_arr[i].used = true; |
| else |
| aw96103->channels_arr[i].used = false; |
| } |
| } |
| |
| static int aw96103_sw_reset(struct aw96103 *aw96103) |
| { |
| int ret; |
| |
| ret = regmap_write(aw96103->regmap, AW96103_REG_RESET, 0); |
| /* |
| * After reset, the initialization process starts to perform and |
| * it will last for a bout 20ms. |
| */ |
| msleep(20); |
| |
| return ret; |
| } |
| |
| enum aw96103_irq_trigger_position { |
| FAR = 0, |
| TRIGGER_TH0 = 0x01, |
| TRIGGER_TH1 = 0x03, |
| TRIGGER_TH2 = 0x07, |
| TRIGGER_TH3 = 0x0f, |
| }; |
| |
| static irqreturn_t aw96103_irq(int irq, void *data) |
| { |
| unsigned int irq_status, curr_status_val, curr_status; |
| struct iio_dev *indio_dev = data; |
| struct aw96103 *aw96103 = iio_priv(indio_dev); |
| int ret, i; |
| |
| ret = regmap_read(aw96103->regmap, AW96103_REG_IRQSRC, &irq_status); |
| if (ret) |
| return IRQ_HANDLED; |
| |
| ret = regmap_read(aw96103->regmap, AW96103_REG_STAT0, &curr_status_val); |
| if (ret) |
| return IRQ_HANDLED; |
| |
| /* |
| * Iteratively analyze the interrupt status of different channels, |
| * with each channel having 4 interrupt states. |
| */ |
| for (i = 0; i < aw96103->max_channels; i++) { |
| if (!aw96103->channels_arr[i].used) |
| continue; |
| |
| curr_status = (((curr_status_val >> (24 + i)) & 0x1)) | |
| (((curr_status_val >> (16 + i)) & 0x1) << 1) | |
| (((curr_status_val >> (8 + i)) & 0x1) << 2) | |
| (((curr_status_val >> i) & 0x1) << 3); |
| if (aw96103->channels_arr[i].old_irq_status == curr_status) |
| continue; |
| |
| switch (curr_status) { |
| case FAR: |
| iio_push_event(indio_dev, |
| IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, i, |
| IIO_EV_TYPE_THRESH, |
| IIO_EV_DIR_RISING), |
| iio_get_time_ns(indio_dev)); |
| break; |
| case TRIGGER_TH0: |
| case TRIGGER_TH1: |
| case TRIGGER_TH2: |
| case TRIGGER_TH3: |
| iio_push_event(indio_dev, |
| IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, i, |
| IIO_EV_TYPE_THRESH, |
| IIO_EV_DIR_FALLING), |
| iio_get_time_ns(indio_dev)); |
| break; |
| default: |
| return IRQ_HANDLED; |
| } |
| aw96103->channels_arr[i].old_irq_status = curr_status; |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int aw96103_interrupt_init(struct iio_dev *indio_dev, |
| struct i2c_client *i2c) |
| { |
| struct aw96103 *aw96103 = iio_priv(indio_dev); |
| unsigned int irq_status; |
| int ret; |
| |
| ret = regmap_write(aw96103->regmap, AW96103_REG_IRQEN, 0); |
| if (ret) |
| return ret; |
| ret = regmap_read(aw96103->regmap, AW96103_REG_IRQSRC, &irq_status); |
| if (ret) |
| return ret; |
| ret = devm_request_threaded_irq(aw96103->dev, i2c->irq, NULL, |
| aw96103_irq, IRQF_ONESHOT, |
| "aw96103_irq", indio_dev); |
| if (ret) |
| return ret; |
| |
| return regmap_write(aw96103->regmap, AW96103_REG_IRQEN, |
| aw96103->hostirqen); |
| } |
| |
| static int aw96103_wait_chip_init(struct aw96103 *aw96103) |
| { |
| unsigned int cnt = 20; |
| u32 reg_data; |
| int ret; |
| |
| while (cnt--) { |
| /* |
| * The device should generate an initialization completion |
| * interrupt within 20ms. |
| */ |
| ret = regmap_read(aw96103->regmap, AW96103_REG_IRQSRC, |
| ®_data); |
| if (ret) |
| return ret; |
| |
| if (FIELD_GET(AW96103_INITOVERIRQ_MASK, reg_data)) |
| return 0; |
| fsleep(1000); |
| } |
| |
| return -ETIMEDOUT; |
| } |
| |
| static int aw96103_read_chipid(struct aw96103 *aw96103) |
| { |
| unsigned char cnt = 0; |
| u32 reg_val = 0; |
| int ret; |
| |
| while (cnt < 3) { |
| /* |
| * This retry mechanism and the subsequent delay are just |
| * attempts to read the chip ID as much as possible, |
| * preventing occasional communication failures from causing |
| * the chip ID read to fail. |
| */ |
| ret = regmap_read(aw96103->regmap, AW96103_REG_CHIPID, |
| ®_val); |
| if (ret < 0) { |
| cnt++; |
| fsleep(2000); |
| continue; |
| } |
| break; |
| } |
| if (cnt == 3) |
| return -ETIMEDOUT; |
| |
| if (FIELD_GET(AW96103_CHIPID_MASK, reg_val) != AW96103_CHIP_ID) |
| dev_info(aw96103->dev, |
| "unexpected chipid, id=0x%08X\n", reg_val); |
| |
| return 0; |
| } |
| |
| static int aw96103_i2c_probe(struct i2c_client *i2c) |
| { |
| const struct aw_chip_info *chip_info; |
| struct iio_dev *indio_dev; |
| struct aw96103 *aw96103; |
| int ret; |
| |
| indio_dev = devm_iio_device_alloc(&i2c->dev, sizeof(*aw96103)); |
| if (!indio_dev) |
| return -ENOMEM; |
| |
| aw96103 = iio_priv(indio_dev); |
| aw96103->dev = &i2c->dev; |
| chip_info = i2c_get_match_data(i2c); |
| aw96103->max_channels = chip_info->num_channels; |
| |
| aw96103->regmap = devm_regmap_init_i2c(i2c, &aw96103_regmap_confg); |
| if (IS_ERR(aw96103->regmap)) |
| return PTR_ERR(aw96103->regmap); |
| |
| ret = devm_regulator_get_enable(aw96103->dev, "vcc"); |
| if (ret < 0) |
| return ret; |
| |
| ret = aw96103_read_chipid(aw96103); |
| if (ret) |
| return ret; |
| |
| ret = aw96103_sw_reset(aw96103); |
| if (ret) |
| return ret; |
| |
| ret = aw96103_wait_chip_init(aw96103); |
| if (ret) |
| return ret; |
| |
| ret = request_firmware_nowait(THIS_MODULE, true, "aw96103_0.bin", |
| aw96103->dev, GFP_KERNEL, aw96103, |
| aw96103_cfg_update); |
| if (ret) |
| return ret; |
| |
| ret = aw96103_interrupt_init(indio_dev, i2c); |
| if (ret) |
| return ret; |
| |
| indio_dev->modes = INDIO_DIRECT_MODE; |
| indio_dev->num_channels = chip_info->num_channels; |
| indio_dev->channels = chip_info->channels; |
| indio_dev->info = &iio_info; |
| indio_dev->name = chip_info->name; |
| |
| return devm_iio_device_register(aw96103->dev, indio_dev); |
| } |
| |
| static const struct of_device_id aw96103_dt_match[] = { |
| { |
| .compatible = "awinic,aw96103", |
| .data = &aw_chip_info_tbl[AW96103_VAL] |
| }, |
| { |
| .compatible = "awinic,aw96105", |
| .data = &aw_chip_info_tbl[AW96105_VAL] |
| }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, aw96103_dt_match); |
| |
| static const struct i2c_device_id aw96103_i2c_id[] = { |
| { "aw96103", (kernel_ulong_t)&aw_chip_info_tbl[AW96103_VAL] }, |
| { "aw96105", (kernel_ulong_t)&aw_chip_info_tbl[AW96105_VAL] }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, aw96103_i2c_id); |
| |
| static struct i2c_driver aw96103_i2c_driver = { |
| .driver = { |
| .name = "aw96103_sensor", |
| .of_match_table = aw96103_dt_match, |
| }, |
| .probe = aw96103_i2c_probe, |
| .id_table = aw96103_i2c_id, |
| }; |
| module_i2c_driver(aw96103_i2c_driver); |
| |
| MODULE_AUTHOR("Wang Shuaijie <wangshuaijie@awinic.com>"); |
| MODULE_DESCRIPTION("Driver for Awinic AW96103 proximity sensor"); |
| MODULE_LICENSE("GPL v2"); |