cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

al3010.c (5787B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/*
      3 * AL3010 - Dyna Image Ambient Light Sensor
      4 *
      5 * Copyright (c) 2014, Intel Corporation.
      6 * Copyright (c) 2016, Dyna-Image Corp.
      7 * Copyright (c) 2020, David Heidelberg, Michał Mirosław, Dmitry Osipenko
      8 *
      9 * IIO driver for AL3010 (7-bit I2C slave address 0x1C).
     10 *
     11 * TODO: interrupt support, thresholds
     12 * When the driver will get support for interrupt handling, then interrupt
     13 * will need to be disabled before turning sensor OFF in order to avoid
     14 * potential races with the interrupt handling.
     15 */
     16
     17#include <linux/bitfield.h>
     18#include <linux/i2c.h>
     19#include <linux/module.h>
     20#include <linux/of.h>
     21
     22#include <linux/iio/iio.h>
     23#include <linux/iio/sysfs.h>
     24
     25#define AL3010_DRV_NAME "al3010"
     26
     27#define AL3010_REG_SYSTEM		0x00
     28#define AL3010_REG_DATA_LOW		0x0c
     29#define AL3010_REG_CONFIG		0x10
     30
     31#define AL3010_CONFIG_DISABLE		0x00
     32#define AL3010_CONFIG_ENABLE		0x01
     33
     34#define AL3010_GAIN_MASK		GENMASK(6,4)
     35
     36#define AL3010_SCALE_AVAILABLE "1.1872 0.2968 0.0742 0.018"
     37
     38enum al3xxxx_range {
     39	AL3XXX_RANGE_1, /* 77806 lx */
     40	AL3XXX_RANGE_2, /* 19542 lx */
     41	AL3XXX_RANGE_3, /*  4863 lx */
     42	AL3XXX_RANGE_4  /*  1216 lx */
     43};
     44
     45static const int al3010_scales[][2] = {
     46	{0, 1187200}, {0, 296800}, {0, 74200}, {0, 18600}
     47};
     48
     49struct al3010_data {
     50	struct i2c_client *client;
     51};
     52
     53static const struct iio_chan_spec al3010_channels[] = {
     54	{
     55		.type	= IIO_LIGHT,
     56		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
     57				      BIT(IIO_CHAN_INFO_SCALE),
     58	}
     59};
     60
     61static IIO_CONST_ATTR(in_illuminance_scale_available, AL3010_SCALE_AVAILABLE);
     62
     63static struct attribute *al3010_attributes[] = {
     64	&iio_const_attr_in_illuminance_scale_available.dev_attr.attr,
     65	NULL,
     66};
     67
     68static const struct attribute_group al3010_attribute_group = {
     69	.attrs = al3010_attributes,
     70};
     71
     72static int al3010_set_pwr(struct i2c_client *client, bool pwr)
     73{
     74	u8 val = pwr ? AL3010_CONFIG_ENABLE : AL3010_CONFIG_DISABLE;
     75	return i2c_smbus_write_byte_data(client, AL3010_REG_SYSTEM, val);
     76}
     77
     78static void al3010_set_pwr_off(void *_data)
     79{
     80	struct al3010_data *data = _data;
     81
     82	al3010_set_pwr(data->client, false);
     83}
     84
     85static int al3010_init(struct al3010_data *data)
     86{
     87	int ret;
     88
     89	ret = al3010_set_pwr(data->client, true);
     90
     91	if (ret < 0)
     92		return ret;
     93
     94	ret = i2c_smbus_write_byte_data(data->client, AL3010_REG_CONFIG,
     95					FIELD_PREP(AL3010_GAIN_MASK,
     96						   AL3XXX_RANGE_3));
     97	if (ret < 0)
     98		return ret;
     99
    100	return 0;
    101}
    102
    103static int al3010_read_raw(struct iio_dev *indio_dev,
    104			   struct iio_chan_spec const *chan, int *val,
    105			   int *val2, long mask)
    106{
    107	struct al3010_data *data = iio_priv(indio_dev);
    108	int ret;
    109
    110	switch (mask) {
    111	case IIO_CHAN_INFO_RAW:
    112		/*
    113		 * ALS ADC value is stored in two adjacent registers:
    114		 * - low byte of output is stored at AL3010_REG_DATA_LOW
    115		 * - high byte of output is stored at AL3010_REG_DATA_LOW + 1
    116		 */
    117		ret = i2c_smbus_read_word_data(data->client,
    118					       AL3010_REG_DATA_LOW);
    119		if (ret < 0)
    120			return ret;
    121		*val = ret;
    122		return IIO_VAL_INT;
    123	case IIO_CHAN_INFO_SCALE:
    124		ret = i2c_smbus_read_byte_data(data->client,
    125					       AL3010_REG_CONFIG);
    126		if (ret < 0)
    127			return ret;
    128
    129		ret = FIELD_GET(AL3010_GAIN_MASK, ret);
    130		*val = al3010_scales[ret][0];
    131		*val2 = al3010_scales[ret][1];
    132
    133		return IIO_VAL_INT_PLUS_MICRO;
    134	}
    135	return -EINVAL;
    136}
    137
    138static int al3010_write_raw(struct iio_dev *indio_dev,
    139			    struct iio_chan_spec const *chan, int val,
    140			    int val2, long mask)
    141{
    142	struct al3010_data *data = iio_priv(indio_dev);
    143	int i;
    144
    145	switch (mask) {
    146	case IIO_CHAN_INFO_SCALE:
    147		for (i = 0; i < ARRAY_SIZE(al3010_scales); i++) {
    148			if (val != al3010_scales[i][0] ||
    149			    val2 != al3010_scales[i][1])
    150				continue;
    151
    152			return i2c_smbus_write_byte_data(data->client,
    153					AL3010_REG_CONFIG,
    154					FIELD_PREP(AL3010_GAIN_MASK, i));
    155		}
    156		break;
    157	}
    158	return -EINVAL;
    159}
    160
    161static const struct iio_info al3010_info = {
    162	.read_raw	= al3010_read_raw,
    163	.write_raw	= al3010_write_raw,
    164	.attrs		= &al3010_attribute_group,
    165};
    166
    167static int al3010_probe(struct i2c_client *client,
    168			const struct i2c_device_id *id)
    169{
    170	struct al3010_data *data;
    171	struct iio_dev *indio_dev;
    172	int ret;
    173
    174	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
    175	if (!indio_dev)
    176		return -ENOMEM;
    177
    178	data = iio_priv(indio_dev);
    179	i2c_set_clientdata(client, indio_dev);
    180	data->client = client;
    181
    182	indio_dev->info = &al3010_info;
    183	indio_dev->name = AL3010_DRV_NAME;
    184	indio_dev->channels = al3010_channels;
    185	indio_dev->num_channels = ARRAY_SIZE(al3010_channels);
    186	indio_dev->modes = INDIO_DIRECT_MODE;
    187
    188	ret = al3010_init(data);
    189	if (ret < 0) {
    190		dev_err(&client->dev, "al3010 chip init failed\n");
    191		return ret;
    192	}
    193
    194	ret = devm_add_action_or_reset(&client->dev,
    195					al3010_set_pwr_off,
    196					data);
    197	if (ret < 0)
    198		return ret;
    199
    200	return devm_iio_device_register(&client->dev, indio_dev);
    201}
    202
    203static int __maybe_unused al3010_suspend(struct device *dev)
    204{
    205	return al3010_set_pwr(to_i2c_client(dev), false);
    206}
    207
    208static int __maybe_unused al3010_resume(struct device *dev)
    209{
    210	return al3010_set_pwr(to_i2c_client(dev), true);
    211}
    212
    213static SIMPLE_DEV_PM_OPS(al3010_pm_ops, al3010_suspend, al3010_resume);
    214
    215static const struct i2c_device_id al3010_id[] = {
    216	{"al3010", },
    217	{}
    218};
    219MODULE_DEVICE_TABLE(i2c, al3010_id);
    220
    221static const struct of_device_id al3010_of_match[] = {
    222	{ .compatible = "dynaimage,al3010", },
    223	{},
    224};
    225MODULE_DEVICE_TABLE(of, al3010_of_match);
    226
    227static struct i2c_driver al3010_driver = {
    228	.driver = {
    229		.name = AL3010_DRV_NAME,
    230		.of_match_table = al3010_of_match,
    231		.pm = &al3010_pm_ops,
    232	},
    233	.probe		= al3010_probe,
    234	.id_table	= al3010_id,
    235};
    236module_i2c_driver(al3010_driver);
    237
    238MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>");
    239MODULE_AUTHOR("David Heidelberg <david@ixit.cz>");
    240MODULE_DESCRIPTION("AL3010 Ambient Light Sensor driver");
    241MODULE_LICENSE("GPL v2");