iio: potentiometer: Add support for the Renesas X9250 potentiometers
The Renesas X9250 integrates four digitally controlled potentiometers. On each potentiometer, the X9250T has a 100 kOhms total resistance and the X9250U has a 50 kOhms total resistance. Signed-off-by: Herve Codina <herve.codina@bootlin.com> Link: https://lore.kernel.org/r/20230509160852.158101-3-herve.codina@bootlin.com Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
This commit is contained in:
parent
dd731888d1
commit
66bfc528a6
@ -136,4 +136,14 @@ config TPL0102
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called tpl0102.
|
||||
|
||||
config X9250
|
||||
tristate "Renesas X9250 quad controlled potentiometers"
|
||||
depends on SPI
|
||||
help
|
||||
Enable support for the Renesas X9250 quad controlled
|
||||
potentiometers.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called x9250.
|
||||
|
||||
endmenu
|
||||
|
@ -15,3 +15,4 @@ obj-$(CONFIG_MCP4131) += mcp4131.o
|
||||
obj-$(CONFIG_MCP4531) += mcp4531.o
|
||||
obj-$(CONFIG_MCP41010) += mcp41010.o
|
||||
obj-$(CONFIG_TPL0102) += tpl0102.o
|
||||
obj-$(CONFIG_X9250) += x9250.o
|
||||
|
220
drivers/iio/potentiometer/x9250.c
Normal file
220
drivers/iio/potentiometer/x9250.c
Normal file
@ -0,0 +1,220 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
*
|
||||
* x9250.c -- Renesas X9250 potentiometers IIO driver
|
||||
*
|
||||
* Copyright 2023 CS GROUP France
|
||||
*
|
||||
* Author: Herve Codina <herve.codina@bootlin.com>
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
struct x9250_cfg {
|
||||
const char *name;
|
||||
int kohms;
|
||||
};
|
||||
|
||||
struct x9250 {
|
||||
struct spi_device *spi;
|
||||
const struct x9250_cfg *cfg;
|
||||
struct gpio_desc *wp_gpio;
|
||||
};
|
||||
|
||||
#define X9250_ID 0x50
|
||||
#define X9250_CMD_RD_WCR(_p) (0x90 | (_p))
|
||||
#define X9250_CMD_WR_WCR(_p) (0xa0 | (_p))
|
||||
|
||||
static int x9250_write8(struct x9250 *x9250, u8 cmd, u8 val)
|
||||
{
|
||||
u8 txbuf[3];
|
||||
|
||||
txbuf[0] = X9250_ID;
|
||||
txbuf[1] = cmd;
|
||||
txbuf[2] = val;
|
||||
|
||||
return spi_write_then_read(x9250->spi, txbuf, ARRAY_SIZE(txbuf), NULL, 0);
|
||||
}
|
||||
|
||||
static int x9250_read8(struct x9250 *x9250, u8 cmd, u8 *val)
|
||||
{
|
||||
u8 txbuf[2];
|
||||
|
||||
txbuf[0] = X9250_ID;
|
||||
txbuf[1] = cmd;
|
||||
|
||||
return spi_write_then_read(x9250->spi, txbuf, ARRAY_SIZE(txbuf), val, 1);
|
||||
}
|
||||
|
||||
#define X9250_CHANNEL(ch) { \
|
||||
.type = IIO_RESISTANCE, \
|
||||
.indexed = 1, \
|
||||
.output = 1, \
|
||||
.channel = (ch), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_RAW), \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec x9250_channels[] = {
|
||||
X9250_CHANNEL(0),
|
||||
X9250_CHANNEL(1),
|
||||
X9250_CHANNEL(2),
|
||||
X9250_CHANNEL(3),
|
||||
};
|
||||
|
||||
static int x9250_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct x9250 *x9250 = iio_priv(indio_dev);
|
||||
int ch = chan->channel;
|
||||
int ret;
|
||||
u8 v;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = x9250_read8(x9250, X9250_CMD_RD_WCR(ch), &v);
|
||||
if (ret)
|
||||
return ret;
|
||||
*val = v;
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 1000 * x9250->cfg->kohms;
|
||||
*val2 = U8_MAX;
|
||||
return IIO_VAL_FRACTIONAL;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int x9250_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
|
||||
const int **vals, int *type, int *length, long mask)
|
||||
{
|
||||
static const int range[] = {0, 1, 255}; /* min, step, max */
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
*length = ARRAY_SIZE(range);
|
||||
*vals = range;
|
||||
*type = IIO_VAL_INT;
|
||||
return IIO_AVAIL_RANGE;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int x9250_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct x9250 *x9250 = iio_priv(indio_dev);
|
||||
int ch = chan->channel;
|
||||
int ret;
|
||||
|
||||
if (mask != IIO_CHAN_INFO_RAW)
|
||||
return -EINVAL;
|
||||
|
||||
if (val > U8_MAX || val < 0)
|
||||
return -EINVAL;
|
||||
|
||||
gpiod_set_value_cansleep(x9250->wp_gpio, 0);
|
||||
ret = x9250_write8(x9250, X9250_CMD_WR_WCR(ch), val);
|
||||
gpiod_set_value_cansleep(x9250->wp_gpio, 1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_info x9250_info = {
|
||||
.read_raw = x9250_read_raw,
|
||||
.read_avail = x9250_read_avail,
|
||||
.write_raw = x9250_write_raw,
|
||||
};
|
||||
|
||||
enum x9250_type {
|
||||
X9250T,
|
||||
X9250U,
|
||||
};
|
||||
|
||||
static const struct x9250_cfg x9250_cfg[] = {
|
||||
[X9250T] = { .name = "x9250t", .kohms = 100, },
|
||||
[X9250U] = { .name = "x9250u", .kohms = 50, },
|
||||
};
|
||||
|
||||
static const char *const x9250_regulator_names[] = {
|
||||
"vcc",
|
||||
"avp",
|
||||
"avn",
|
||||
};
|
||||
|
||||
static int x9250_probe(struct spi_device *spi)
|
||||
{
|
||||
struct iio_dev *indio_dev;
|
||||
struct x9250 *x9250;
|
||||
int ret;
|
||||
|
||||
ret = devm_regulator_bulk_get_enable(&spi->dev, ARRAY_SIZE(x9250_regulator_names),
|
||||
x9250_regulator_names);
|
||||
if (ret)
|
||||
return dev_err_probe(&spi->dev, ret, "Failed to get regulators\n");
|
||||
|
||||
/*
|
||||
* The x9250 needs a 5ms maximum delay after the power-supplies are set
|
||||
* before performing the first write (1ms for the first read).
|
||||
*/
|
||||
usleep_range(5000, 6000);
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*x9250));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
x9250 = iio_priv(indio_dev);
|
||||
x9250->spi = spi;
|
||||
x9250->cfg = spi_get_device_match_data(spi);
|
||||
x9250->wp_gpio = devm_gpiod_get_optional(&spi->dev, "wp", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(x9250->wp_gpio))
|
||||
return dev_err_probe(&spi->dev, PTR_ERR(x9250->wp_gpio),
|
||||
"failed to get wp gpio\n");
|
||||
|
||||
indio_dev->info = &x9250_info;
|
||||
indio_dev->channels = x9250_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(x9250_channels);
|
||||
indio_dev->name = x9250->cfg->name;
|
||||
|
||||
return devm_iio_device_register(&spi->dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id x9250_of_match[] = {
|
||||
{ .compatible = "renesas,x9250t", .data = &x9250_cfg[X9250T]},
|
||||
{ .compatible = "renesas,x9250u", .data = &x9250_cfg[X9250U]},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, x9250_of_match);
|
||||
|
||||
static const struct spi_device_id x9250_id_table[] = {
|
||||
{ "x9250t", (kernel_ulong_t)&x9250_cfg[X9250T] },
|
||||
{ "x9250u", (kernel_ulong_t)&x9250_cfg[X9250U] },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, x9250_id_table);
|
||||
|
||||
static struct spi_driver x9250_spi_driver = {
|
||||
.driver = {
|
||||
.name = "x9250",
|
||||
.of_match_table = x9250_of_match,
|
||||
},
|
||||
.id_table = x9250_id_table,
|
||||
.probe = x9250_probe,
|
||||
};
|
||||
|
||||
module_spi_driver(x9250_spi_driver);
|
||||
|
||||
MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
|
||||
MODULE_DESCRIPTION("X9250 ALSA SoC driver");
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Reference in New Issue
Block a user