From df041e737a38a316976273281f66fbce2ec4b397 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 7 Apr 2021 23:51:47 +0530 Subject: iio: temperature: add driver support for ti tmp117 TMP117 is a Digital temperature sensor with integrated Non-Volatile memory. Add support for tmp117 driver in iio subsystem. Datasheet: https://www.ti.com/lit/gpn/tmp117 Signed-off-by: Puranjay Mohan Reviewed-by: Andy Shevchenko Reviewed-by: Lars-Peter Clausen Link: https://lore.kernel.org/r/20210407182147.77221-3-puranjay12@gmail.com Signed-off-by: Jonathan Cameron --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 008fcad7ac00..f6191b0e55b1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18094,6 +18094,13 @@ F: Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml F: Documentation/hwmon/tps23861.rst F: drivers/hwmon/tps23861.c +TEXAS INSTRUMENTS' TMP117 TEMPERATURE SENSOR DRIVER +M: Puranjay Mohan +L: linux-iio@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/iio/temperature/ti,tmp117.yaml +F: drivers/iio/temperature/tmp117.c + THANKO'S RAREMONO AM/FM/SW RADIO RECEIVER USB DRIVER M: Hans Verkuil L: linux-media@vger.kernel.org -- cgit v1.2.3-59-g8ed1b From 8f3f130852785dac0759843835ca97c3bacc2b10 Mon Sep 17 00:00:00 2001 From: Tomasz Duszynski Date: Mon, 3 May 2021 08:00:12 +0200 Subject: iio: sps30: separate core and interface specific code Move code responsible for handling i2c communication to a separate file. Rationale for this change is preparation for adding support for serial communication. Signed-off-by: Tomasz Duszynski Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/chemical/Kconfig | 16 ++- drivers/iio/chemical/Makefile | 1 + drivers/iio/chemical/sps30.c | 269 +++++++-------------------------------- drivers/iio/chemical/sps30.h | 35 +++++ drivers/iio/chemical/sps30_i2c.c | 258 +++++++++++++++++++++++++++++++++++++ 6 files changed, 354 insertions(+), 226 deletions(-) create mode 100644 drivers/iio/chemical/sps30.h create mode 100644 drivers/iio/chemical/sps30_i2c.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index f6191b0e55b1..642638d7aca0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16484,6 +16484,7 @@ M: Tomasz Duszynski S: Maintained F: Documentation/devicetree/bindings/iio/chemical/sensirion,sps30.yaml F: drivers/iio/chemical/sps30.c +F: drivers/iio/chemical/sps30_i2c.c SERIAL DEVICE BUS M: Rob Herring diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig index 10bb431bc3ce..2b45a76ab7bc 100644 --- a/drivers/iio/chemical/Kconfig +++ b/drivers/iio/chemical/Kconfig @@ -132,17 +132,21 @@ config SENSIRION_SGP30 module will be called sgp30. config SPS30 - tristate "SPS30 particulate matter sensor" - depends on I2C - select CRC8 + tristate select IIO_BUFFER select IIO_TRIGGERED_BUFFER + +config SPS30_I2C + tristate "SPS30 particulate matter sensor I2C driver" + depends on I2C + select SPS30 + select CRC8 help - Say Y here to build support for the Sensirion SPS30 particulate - matter sensor. + Say Y here to build support for the Sensirion SPS30 I2C interface + driver. To compile this driver as a module, choose M here: the module will - be called sps30. + be called sps30_i2c. config VZ89X tristate "SGX Sensortech MiCS VZ89X VOC sensor" diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile index fef63dd5bf92..41c264a229c0 100644 --- a/drivers/iio/chemical/Makefile +++ b/drivers/iio/chemical/Makefile @@ -17,4 +17,5 @@ obj-$(CONFIG_SCD30_I2C) += scd30_i2c.o obj-$(CONFIG_SCD30_SERIAL) += scd30_serial.o obj-$(CONFIG_SENSIRION_SGP30) += sgp30.o obj-$(CONFIG_SPS30) += sps30.o +obj-$(CONFIG_SPS30_I2C) += sps30_i2c.o obj-$(CONFIG_VZ89X) += vz89x.o diff --git a/drivers/iio/chemical/sps30.c b/drivers/iio/chemical/sps30.c index 7486591588c3..d51314505115 100644 --- a/drivers/iio/chemical/sps30.c +++ b/drivers/iio/chemical/sps30.c @@ -3,11 +3,8 @@ * Sensirion SPS30 particulate matter sensor driver * * Copyright (c) Tomasz Duszynski - * - * I2C slave address: 0x69 */ -#include #include #include #include @@ -19,27 +16,14 @@ #include #include -#define SPS30_CRC8_POLYNOMIAL 0x31 -/* max number of bytes needed to store PM measurements or serial string */ -#define SPS30_MAX_READ_SIZE 48 +#include "sps30.h" + /* sensor measures reliably up to 3000 ug / m3 */ #define SPS30_MAX_PM 3000 /* minimum and maximum self cleaning periods in seconds */ #define SPS30_AUTO_CLEANING_PERIOD_MIN 0 #define SPS30_AUTO_CLEANING_PERIOD_MAX 604800 -/* SPS30 commands */ -#define SPS30_START_MEAS 0x0010 -#define SPS30_STOP_MEAS 0x0104 -#define SPS30_RESET 0xd304 -#define SPS30_READ_DATA_READY_FLAG 0x0202 -#define SPS30_READ_DATA 0x0300 -#define SPS30_READ_SERIAL 0xd033 -#define SPS30_START_FAN_CLEANING 0x5607 -#define SPS30_AUTO_CLEANING_PERIOD 0x8004 -/* not a sensor command per se, used only to distinguish write from read */ -#define SPS30_READ_AUTO_CLEANING_PERIOD 0x8005 - enum { PM1, PM2P5, @@ -52,114 +36,9 @@ enum { MEASURING, }; -struct sps30_state { - struct i2c_client *client; - /* - * Guards against concurrent access to sensor registers. - * Must be held whenever sequence of commands is to be executed. - */ - struct mutex lock; - int state; -}; - -DECLARE_CRC8_TABLE(sps30_crc8_table); - -static int sps30_write_then_read(struct sps30_state *state, u8 *txbuf, - int txsize, u8 *rxbuf, int rxsize) -{ - int ret; - - /* - * Sensor does not support repeated start so instead of - * sending two i2c messages in a row we just send one by one. - */ - ret = i2c_master_send(state->client, txbuf, txsize); - if (ret != txsize) - return ret < 0 ? ret : -EIO; - - if (!rxbuf) - return 0; - - ret = i2c_master_recv(state->client, rxbuf, rxsize); - if (ret != rxsize) - return ret < 0 ? ret : -EIO; - - return 0; -} - -static int sps30_do_cmd(struct sps30_state *state, u16 cmd, u8 *data, int size) -{ - /* - * Internally sensor stores measurements in a following manner: - * - * PM1: upper two bytes, crc8, lower two bytes, crc8 - * PM2P5: upper two bytes, crc8, lower two bytes, crc8 - * PM4: upper two bytes, crc8, lower two bytes, crc8 - * PM10: upper two bytes, crc8, lower two bytes, crc8 - * - * What follows next are number concentration measurements and - * typical particle size measurement which we omit. - */ - u8 buf[SPS30_MAX_READ_SIZE] = { cmd >> 8, cmd }; - int i, ret = 0; - - switch (cmd) { - case SPS30_START_MEAS: - buf[2] = 0x03; - buf[3] = 0x00; - buf[4] = crc8(sps30_crc8_table, &buf[2], 2, CRC8_INIT_VALUE); - ret = sps30_write_then_read(state, buf, 5, NULL, 0); - break; - case SPS30_STOP_MEAS: - case SPS30_RESET: - case SPS30_START_FAN_CLEANING: - ret = sps30_write_then_read(state, buf, 2, NULL, 0); - break; - case SPS30_READ_AUTO_CLEANING_PERIOD: - buf[0] = SPS30_AUTO_CLEANING_PERIOD >> 8; - buf[1] = (u8)(SPS30_AUTO_CLEANING_PERIOD & 0xff); - fallthrough; - case SPS30_READ_DATA_READY_FLAG: - case SPS30_READ_DATA: - case SPS30_READ_SERIAL: - /* every two data bytes are checksummed */ - size += size / 2; - ret = sps30_write_then_read(state, buf, 2, buf, size); - break; - case SPS30_AUTO_CLEANING_PERIOD: - buf[2] = data[0]; - buf[3] = data[1]; - buf[4] = crc8(sps30_crc8_table, &buf[2], 2, CRC8_INIT_VALUE); - buf[5] = data[2]; - buf[6] = data[3]; - buf[7] = crc8(sps30_crc8_table, &buf[5], 2, CRC8_INIT_VALUE); - ret = sps30_write_then_read(state, buf, 8, NULL, 0); - break; - } - - if (ret) - return ret; - - /* validate received data and strip off crc bytes */ - for (i = 0; i < size; i += 3) { - u8 crc = crc8(sps30_crc8_table, &buf[i], 2, CRC8_INIT_VALUE); - - if (crc != buf[i + 2]) { - dev_err(&state->client->dev, - "data integrity check failed\n"); - return -EIO; - } - - *data++ = buf[i]; - *data++ = buf[i + 1]; - } - - return 0; -} - -static s32 sps30_float_to_int_clamped(const u8 *fp) +static s32 sps30_float_to_int_clamped(__be32 *fp) { - int val = get_unaligned_be32(fp); + int val = be32_to_cpup(fp); int mantissa = val & GENMASK(22, 0); /* this is fine since passed float is always non-negative */ int exp = val >> 23; @@ -188,38 +67,35 @@ static s32 sps30_float_to_int_clamped(const u8 *fp) static int sps30_do_meas(struct sps30_state *state, s32 *data, int size) { - int i, ret, tries = 5; - u8 tmp[16]; + int i, ret; if (state->state == RESET) { - ret = sps30_do_cmd(state, SPS30_START_MEAS, NULL, 0); + ret = state->ops->start_meas(state); if (ret) return ret; state->state = MEASURING; } - while (tries--) { - ret = sps30_do_cmd(state, SPS30_READ_DATA_READY_FLAG, tmp, 2); - if (ret) - return -EIO; + ret = state->ops->read_meas(state, (__be32 *)data, size); + if (ret) + return ret; - /* new measurements ready to be read */ - if (tmp[1] == 1) - break; + for (i = 0; i < size; i++) + data[i] = sps30_float_to_int_clamped((__be32 *)&data[i]); - msleep_interruptible(300); - } + return 0; +} - if (tries == -1) - return -ETIMEDOUT; +static int sps30_do_reset(struct sps30_state *state) +{ + int ret; - ret = sps30_do_cmd(state, SPS30_READ_DATA, tmp, sizeof(int) * size); + ret = state->ops->reset(state); if (ret) return ret; - for (i = 0; i < size; i++) - data[i] = sps30_float_to_int_clamped(&tmp[4 * i]); + state->state = RESET; return 0; } @@ -310,24 +186,6 @@ static int sps30_read_raw(struct iio_dev *indio_dev, return -EINVAL; } -static int sps30_do_cmd_reset(struct sps30_state *state) -{ - int ret; - - ret = sps30_do_cmd(state, SPS30_RESET, NULL, 0); - msleep(300); - /* - * Power-on-reset causes sensor to produce some glitch on i2c bus and - * some controllers end up in error state. Recover simply by placing - * some data on the bus, for example STOP_MEAS command, which - * is NOP in this case. - */ - sps30_do_cmd(state, SPS30_STOP_MEAS, NULL, 0); - state->state = RESET; - - return ret; -} - static ssize_t start_cleaning_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) @@ -340,7 +198,7 @@ static ssize_t start_cleaning_store(struct device *dev, return -EINVAL; mutex_lock(&state->lock); - ret = sps30_do_cmd(state, SPS30_START_FAN_CLEANING, NULL, 0); + ret = state->ops->clean_fan(state); mutex_unlock(&state->lock); if (ret) return ret; @@ -349,31 +207,29 @@ static ssize_t start_cleaning_store(struct device *dev, } static ssize_t cleaning_period_show(struct device *dev, - struct device_attribute *attr, - char *buf) + struct device_attribute *attr, + char *buf) { struct iio_dev *indio_dev = dev_to_iio_dev(dev); struct sps30_state *state = iio_priv(indio_dev); - u8 tmp[4]; + __be32 val; int ret; mutex_lock(&state->lock); - ret = sps30_do_cmd(state, SPS30_READ_AUTO_CLEANING_PERIOD, tmp, 4); + ret = state->ops->read_cleaning_period(state, &val); mutex_unlock(&state->lock); if (ret) return ret; - return sprintf(buf, "%d\n", get_unaligned_be32(tmp)); + return sprintf(buf, "%d\n", be32_to_cpu(val)); } -static ssize_t cleaning_period_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len) +static ssize_t cleaning_period_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) { struct iio_dev *indio_dev = dev_to_iio_dev(dev); struct sps30_state *state = iio_priv(indio_dev); int val, ret; - u8 tmp[4]; if (kstrtoint(buf, 0, &val)) return -EINVAL; @@ -382,10 +238,8 @@ static ssize_t cleaning_period_store(struct device *dev, (val > SPS30_AUTO_CLEANING_PERIOD_MAX)) return -EINVAL; - put_unaligned_be32(val, tmp); - mutex_lock(&state->lock); - ret = sps30_do_cmd(state, SPS30_AUTO_CLEANING_PERIOD, tmp, 0); + ret = state->ops->write_cleaning_period(state, cpu_to_be32(val)); if (ret) { mutex_unlock(&state->lock); return ret; @@ -397,7 +251,7 @@ static ssize_t cleaning_period_store(struct device *dev, * sensor requires reset in order to return up to date self cleaning * period */ - ret = sps30_do_cmd_reset(state); + ret = sps30_do_reset(state); if (ret) dev_warn(dev, "period changed but reads will return the old value\n"); @@ -460,90 +314,65 @@ static const struct iio_chan_spec sps30_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(4), }; -static void sps30_stop_meas(void *data) +static void sps30_devm_stop_meas(void *data) { struct sps30_state *state = data; - sps30_do_cmd(state, SPS30_STOP_MEAS, NULL, 0); + if (state->state == MEASURING) + state->ops->stop_meas(state); } static const unsigned long sps30_scan_masks[] = { 0x0f, 0x00 }; -static int sps30_probe(struct i2c_client *client) +int sps30_probe(struct device *dev, const char *name, void *priv, const struct sps30_ops *ops) { struct iio_dev *indio_dev; struct sps30_state *state; - u8 buf[32]; int ret; - if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) - return -EOPNOTSUPP; - - indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*state)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*state)); if (!indio_dev) return -ENOMEM; + dev_set_drvdata(dev, indio_dev); + state = iio_priv(indio_dev); - i2c_set_clientdata(client, indio_dev); - state->client = client; - state->state = RESET; + state->dev = dev; + state->priv = priv; + state->ops = ops; + mutex_init(&state->lock); + indio_dev->info = &sps30_info; - indio_dev->name = client->name; + indio_dev->name = name; indio_dev->channels = sps30_channels; indio_dev->num_channels = ARRAY_SIZE(sps30_channels); indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->available_scan_masks = sps30_scan_masks; - mutex_init(&state->lock); - crc8_populate_msb(sps30_crc8_table, SPS30_CRC8_POLYNOMIAL); - - ret = sps30_do_cmd_reset(state); + ret = sps30_do_reset(state); if (ret) { - dev_err(&client->dev, "failed to reset device\n"); + dev_err(dev, "failed to reset device\n"); return ret; } - ret = sps30_do_cmd(state, SPS30_READ_SERIAL, buf, sizeof(buf)); + ret = state->ops->show_info(state); if (ret) { - dev_err(&client->dev, "failed to read serial number\n"); + dev_err(dev, "failed to read device info\n"); return ret; } - /* returned serial number is already NUL terminated */ - dev_info(&client->dev, "serial number: %s\n", buf); - ret = devm_add_action_or_reset(&client->dev, sps30_stop_meas, state); + ret = devm_add_action_or_reset(dev, sps30_devm_stop_meas, state); if (ret) return ret; - ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, sps30_trigger_handler, NULL); if (ret) return ret; - return devm_iio_device_register(&client->dev, indio_dev); + return devm_iio_device_register(dev, indio_dev); } - -static const struct i2c_device_id sps30_id[] = { - { "sps30" }, - { } -}; -MODULE_DEVICE_TABLE(i2c, sps30_id); - -static const struct of_device_id sps30_of_match[] = { - { .compatible = "sensirion,sps30" }, - { } -}; -MODULE_DEVICE_TABLE(of, sps30_of_match); - -static struct i2c_driver sps30_driver = { - .driver = { - .name = "sps30", - .of_match_table = sps30_of_match, - }, - .id_table = sps30_id, - .probe_new = sps30_probe, -}; -module_i2c_driver(sps30_driver); +EXPORT_SYMBOL_GPL(sps30_probe); MODULE_AUTHOR("Tomasz Duszynski "); MODULE_DESCRIPTION("Sensirion SPS30 particulate matter sensor driver"); diff --git a/drivers/iio/chemical/sps30.h b/drivers/iio/chemical/sps30.h new file mode 100644 index 000000000000..a58ee43cf45d --- /dev/null +++ b/drivers/iio/chemical/sps30.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _SPS30_H +#define _SPS30_H + +#include + +struct sps30_state; +struct sps30_ops { + int (*start_meas)(struct sps30_state *state); + int (*stop_meas)(struct sps30_state *state); + int (*read_meas)(struct sps30_state *state, __be32 *meas, size_t num); + int (*reset)(struct sps30_state *state); + int (*clean_fan)(struct sps30_state *state); + int (*read_cleaning_period)(struct sps30_state *state, __be32 *period); + int (*write_cleaning_period)(struct sps30_state *state, __be32 period); + int (*show_info)(struct sps30_state *state); +}; + +struct sps30_state { + /* serialize access to the device */ + struct mutex lock; + struct device *dev; + int state; + /* + * priv pointer is solely for serdev driver private data. We keep it + * here because driver_data inside dev has been already used for iio and + * struct serdev_device doesn't have one. + */ + void *priv; + const struct sps30_ops *ops; +}; + +int sps30_probe(struct device *dev, const char *name, void *priv, const struct sps30_ops *ops); + +#endif diff --git a/drivers/iio/chemical/sps30_i2c.c b/drivers/iio/chemical/sps30_i2c.c new file mode 100644 index 000000000000..d33560ed7184 --- /dev/null +++ b/drivers/iio/chemical/sps30_i2c.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sensirion SPS30 particulate matter sensor i2c driver + * + * Copyright (c) 2020 Tomasz Duszynski + * + * I2C slave address: 0x69 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sps30.h" + +#define SPS30_I2C_CRC8_POLYNOMIAL 0x31 +/* max number of bytes needed to store PM measurements or serial string */ +#define SPS30_I2C_MAX_BUF_SIZE 48 + +DECLARE_CRC8_TABLE(sps30_i2c_crc8_table); + +#define SPS30_I2C_START_MEAS 0x0010 +#define SPS30_I2C_STOP_MEAS 0x0104 +#define SPS30_I2C_READ_MEAS 0x0300 +#define SPS30_I2C_MEAS_READY 0x0202 +#define SPS30_I2C_RESET 0xd304 +#define SPS30_I2C_CLEAN_FAN 0x5607 +#define SPS30_I2C_PERIOD 0x8004 +#define SPS30_I2C_READ_SERIAL 0xd033 +#define SPS30_I2C_READ_VERSION 0xd100 + +static int sps30_i2c_xfer(struct sps30_state *state, unsigned char *txbuf, size_t txsize, + unsigned char *rxbuf, size_t rxsize) +{ + struct i2c_client *client = to_i2c_client(state->dev); + int ret; + + /* + * Sensor does not support repeated start so instead of + * sending two i2c messages in a row we just send one by one. + */ + ret = i2c_master_send(client, txbuf, txsize); + if (ret < 0) + return ret; + if (ret != txsize) + return -EIO; + + if (!rxsize) + return 0; + + ret = i2c_master_recv(client, rxbuf, rxsize); + if (ret < 0) + return ret; + if (ret != rxsize) + return -EIO; + + return 0; +} + +static int sps30_i2c_command(struct sps30_state *state, u16 cmd, void *arg, size_t arg_size, + void *rsp, size_t rsp_size) +{ + /* + * Internally sensor stores measurements in a following manner: + * + * PM1: upper two bytes, crc8, lower two bytes, crc8 + * PM2P5: upper two bytes, crc8, lower two bytes, crc8 + * PM4: upper two bytes, crc8, lower two bytes, crc8 + * PM10: upper two bytes, crc8, lower two bytes, crc8 + * + * What follows next are number concentration measurements and + * typical particle size measurement which we omit. + */ + unsigned char buf[SPS30_I2C_MAX_BUF_SIZE]; + unsigned char *tmp; + unsigned char crc; + size_t i; + int ret; + + put_unaligned_be16(cmd, buf); + i = 2; + + if (rsp) { + /* each two bytes are followed by a crc8 */ + rsp_size += rsp_size / 2; + } else { + tmp = arg; + + while (arg_size) { + buf[i] = *tmp++; + buf[i + 1] = *tmp++; + buf[i + 2] = crc8(sps30_i2c_crc8_table, buf + i, 2, CRC8_INIT_VALUE); + arg_size -= 2; + i += 3; + } + } + + ret = sps30_i2c_xfer(state, buf, i, buf, rsp_size); + if (ret) + return ret; + + /* validate received data and strip off crc bytes */ + tmp = rsp; + for (i = 0; i < rsp_size; i += 3) { + crc = crc8(sps30_i2c_crc8_table, buf + i, 2, CRC8_INIT_VALUE); + if (crc != buf[i + 2]) { + dev_err(state->dev, "data integrity check failed\n"); + return -EIO; + } + + *tmp++ = buf[i]; + *tmp++ = buf[i + 1]; + } + + return 0; +} + +static int sps30_i2c_start_meas(struct sps30_state *state) +{ + /* request BE IEEE754 formatted data */ + unsigned char buf[] = { 0x03, 0x00 }; + + return sps30_i2c_command(state, SPS30_I2C_START_MEAS, buf, sizeof(buf), NULL, 0); +} + +static int sps30_i2c_stop_meas(struct sps30_state *state) +{ + return sps30_i2c_command(state, SPS30_I2C_STOP_MEAS, NULL, 0, NULL, 0); +} + +static int sps30_i2c_reset(struct sps30_state *state) +{ + int ret; + + ret = sps30_i2c_command(state, SPS30_I2C_RESET, NULL, 0, NULL, 0); + msleep(500); + /* + * Power-on-reset causes sensor to produce some glitch on i2c bus and + * some controllers end up in error state. Recover simply by placing + * some data on the bus, for example STOP_MEAS command, which + * is NOP in this case. + */ + sps30_i2c_stop_meas(state); + + return ret; +} + +static bool sps30_i2c_meas_ready(struct sps30_state *state) +{ + unsigned char buf[2]; + int ret; + + ret = sps30_i2c_command(state, SPS30_I2C_MEAS_READY, NULL, 0, buf, sizeof(buf)); + if (ret) + return false; + + return buf[1]; +} + +static int sps30_i2c_read_meas(struct sps30_state *state, __be32 *meas, size_t num) +{ + /* measurements are ready within a second */ + if (msleep_interruptible(1000)) + return -EINTR; + + if (!sps30_i2c_meas_ready(state)) + return -ETIMEDOUT; + + return sps30_i2c_command(state, SPS30_I2C_READ_MEAS, NULL, 0, meas, sizeof(num) * num); +} + +static int sps30_i2c_clean_fan(struct sps30_state *state) +{ + return sps30_i2c_command(state, SPS30_I2C_CLEAN_FAN, NULL, 0, NULL, 0); +} + +static int sps30_i2c_read_cleaning_period(struct sps30_state *state, __be32 *period) +{ + return sps30_i2c_command(state, SPS30_I2C_PERIOD, NULL, 0, period, sizeof(*period)); +} + +static int sps30_i2c_write_cleaning_period(struct sps30_state *state, __be32 period) +{ + return sps30_i2c_command(state, SPS30_I2C_PERIOD, &period, sizeof(period), NULL, 0); +} + +static int sps30_i2c_show_info(struct sps30_state *state) +{ + /* extra nul just in case */ + unsigned char buf[32 + 1] = { 0x00 }; + int ret; + + ret = sps30_i2c_command(state, SPS30_I2C_READ_SERIAL, NULL, 0, buf, sizeof(buf) - 1); + if (ret) + return ret; + + dev_info(state->dev, "serial number: %s\n", buf); + + ret = sps30_i2c_command(state, SPS30_I2C_READ_VERSION, NULL, 0, buf, 2); + if (ret) + return ret; + + dev_info(state->dev, "fw version: %u.%u\n", buf[0], buf[1]); + + return 0; +} + +static const struct sps30_ops sps30_i2c_ops = { + .start_meas = sps30_i2c_start_meas, + .stop_meas = sps30_i2c_stop_meas, + .read_meas = sps30_i2c_read_meas, + .reset = sps30_i2c_reset, + .clean_fan = sps30_i2c_clean_fan, + .read_cleaning_period = sps30_i2c_read_cleaning_period, + .write_cleaning_period = sps30_i2c_write_cleaning_period, + .show_info = sps30_i2c_show_info, +}; + +static int sps30_i2c_probe(struct i2c_client *client) +{ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + crc8_populate_msb(sps30_i2c_crc8_table, SPS30_I2C_CRC8_POLYNOMIAL); + + return sps30_probe(&client->dev, client->name, NULL, &sps30_i2c_ops); +} + +static const struct i2c_device_id sps30_i2c_id[] = { + { "sps30" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sps30_i2c_id); + +static const struct of_device_id sps30_i2c_of_match[] = { + { .compatible = "sensirion,sps30" }, + { } +}; +MODULE_DEVICE_TABLE(of, sps30_i2c_of_match); + +static struct i2c_driver sps30_i2c_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = sps30_i2c_of_match, + }, + .id_table = sps30_i2c_id, + .probe_new = sps30_i2c_probe, +}; +module_i2c_driver(sps30_i2c_driver); + +MODULE_AUTHOR("Tomasz Duszynski "); +MODULE_DESCRIPTION("Sensirion SPS30 particulate matter sensor i2c driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From b2e171f5a5c6003bd2e9a8f7dfd8acf714af429b Mon Sep 17 00:00:00 2001 From: Tomasz Duszynski Date: Mon, 3 May 2021 08:00:13 +0200 Subject: iio: sps30: add support for serial interface Sensor has support for both i2c and serial communication interfaces. Both offer very similar set of features. Minor differences don't impact overall functionality like doing measurements, etc. Support for i2c have already been added, this patch adds support for the latter ie. serial interface. Signed-off-by: Tomasz Duszynski Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/chemical/Kconfig | 11 + drivers/iio/chemical/Makefile | 1 + drivers/iio/chemical/sps30_serial.c | 431 ++++++++++++++++++++++++++++++++++++ 4 files changed, 444 insertions(+) create mode 100644 drivers/iio/chemical/sps30_serial.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 642638d7aca0..4fd93da5b784 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16485,6 +16485,7 @@ S: Maintained F: Documentation/devicetree/bindings/iio/chemical/sensirion,sps30.yaml F: drivers/iio/chemical/sps30.c F: drivers/iio/chemical/sps30_i2c.c +F: drivers/iio/chemical/sps30_serial.c SERIAL DEVICE BUS M: Rob Herring diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig index 2b45a76ab7bc..a4920646e9be 100644 --- a/drivers/iio/chemical/Kconfig +++ b/drivers/iio/chemical/Kconfig @@ -148,6 +148,17 @@ config SPS30_I2C To compile this driver as a module, choose M here: the module will be called sps30_i2c. +config SPS30_SERIAL + tristate "SPS30 particulate matter sensor serial driver" + depends on SERIAL_DEV_BUS + select SPS30 + help + Say Y here to build support for the Sensirion SPS30 serial interface + driver. + + To compile this driver as a module, choose M here: the module will + be called sps30_serial. + config VZ89X tristate "SGX Sensortech MiCS VZ89X VOC sensor" depends on I2C diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile index 41c264a229c0..4898690cc155 100644 --- a/drivers/iio/chemical/Makefile +++ b/drivers/iio/chemical/Makefile @@ -18,4 +18,5 @@ obj-$(CONFIG_SCD30_SERIAL) += scd30_serial.o obj-$(CONFIG_SENSIRION_SGP30) += sgp30.o obj-$(CONFIG_SPS30) += sps30.o obj-$(CONFIG_SPS30_I2C) += sps30_i2c.o +obj-$(CONFIG_SPS30_SERIAL) += sps30_serial.o obj-$(CONFIG_VZ89X) += vz89x.o diff --git a/drivers/iio/chemical/sps30_serial.c b/drivers/iio/chemical/sps30_serial.c new file mode 100644 index 000000000000..3f311d50087c --- /dev/null +++ b/drivers/iio/chemical/sps30_serial.c @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sensirion SPS30 particulate matter sensor serial driver + * + * Copyright (c) 2021 Tomasz Duszynski + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sps30.h" + +#define SPS30_SERIAL_DEV_NAME "sps30" + +#define SPS30_SERIAL_SOF_EOF 0x7e +#define SPS30_SERIAL_TIMEOUT msecs_to_jiffies(20) +#define SPS30_SERIAL_MAX_BUF_SIZE 263 +#define SPS30_SERIAL_ESCAPE_CHAR 0x7d + +#define SPS30_SERIAL_FRAME_MIN_SIZE 7 +#define SPS30_SERIAL_FRAME_ADR_OFFSET 1 +#define SPS30_SERIAL_FRAME_CMD_OFFSET 2 +#define SPS30_SERIAL_FRAME_MOSI_LEN_OFFSET 3 +#define SPS30_SERIAL_FRAME_MISO_STATE_OFFSET 3 +#define SPS30_SERIAL_FRAME_MISO_LEN_OFFSET 4 +#define SPS30_SERIAL_FRAME_MISO_DATA_OFFSET 5 + +#define SPS30_SERIAL_START_MEAS 0x00 +#define SPS30_SERIAL_STOP_MEAS 0x01 +#define SPS30_SERIAL_READ_MEAS 0x03 +#define SPS30_SERIAL_RESET 0xd3 +#define SPS30_SERIAL_CLEAN_FAN 0x56 +#define SPS30_SERIAL_PERIOD 0x80 +#define SPS30_SERIAL_DEV_INFO 0xd0 +#define SPS30_SERIAL_READ_VERSION 0xd1 + +struct sps30_serial_priv { + struct completion new_frame; + unsigned char buf[SPS30_SERIAL_MAX_BUF_SIZE]; + size_t num; + bool escaped; + bool done; +}; + +static int sps30_serial_xfer(struct sps30_state *state, const unsigned char *buf, size_t size) +{ + struct serdev_device *serdev = to_serdev_device(state->dev); + struct sps30_serial_priv *priv = state->priv; + int ret; + + priv->num = 0; + priv->escaped = false; + priv->done = false; + + ret = serdev_device_write(serdev, buf, size, SPS30_SERIAL_TIMEOUT); + if (ret < 0) + return ret; + if (ret != size) + return -EIO; + + ret = wait_for_completion_interruptible_timeout(&priv->new_frame, SPS30_SERIAL_TIMEOUT); + if (ret < 0) + return ret; + if (!ret) + return -ETIMEDOUT; + + return 0; +} + +static const struct { + unsigned char byte; + unsigned char byte2; +} sps30_serial_bytes[] = { + { 0x11, 0x31 }, + { 0x13, 0x33 }, + { 0x7e, 0x5e }, + { 0x7d, 0x5d }, +}; + +static int sps30_serial_put_byte(unsigned char *buf, unsigned char byte) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sps30_serial_bytes); i++) { + if (sps30_serial_bytes[i].byte != byte) + continue; + + buf[0] = SPS30_SERIAL_ESCAPE_CHAR; + buf[1] = sps30_serial_bytes[i].byte2; + + return 2; + } + + buf[0] = byte; + + return 1; +} + +static char sps30_serial_get_byte(bool escaped, unsigned char byte2) +{ + int i; + + if (!escaped) + return byte2; + + for (i = 0; i < ARRAY_SIZE(sps30_serial_bytes); i++) { + if (sps30_serial_bytes[i].byte2 != byte2) + continue; + + return sps30_serial_bytes[i].byte; + } + + return 0; +} + +static unsigned char sps30_serial_calc_chksum(const unsigned char *buf, size_t num) +{ + unsigned int chksum = 0; + size_t i; + + for (i = 0; i < num; i++) + chksum += buf[i]; + + return ~chksum; +} + +static int sps30_serial_prep_frame(unsigned char *buf, unsigned char cmd, + const unsigned char *arg, size_t arg_size) +{ + unsigned char chksum; + int num = 0; + size_t i; + + buf[num++] = SPS30_SERIAL_SOF_EOF; + buf[num++] = 0; + num += sps30_serial_put_byte(buf + num, cmd); + num += sps30_serial_put_byte(buf + num, arg_size); + + for (i = 0; i < arg_size; i++) + num += sps30_serial_put_byte(buf + num, arg[i]); + + /* SOF isn't checksummed */ + chksum = sps30_serial_calc_chksum(buf + 1, num - 1); + num += sps30_serial_put_byte(buf + num, chksum); + buf[num++] = SPS30_SERIAL_SOF_EOF; + + return num; +} + +static bool sps30_serial_frame_valid(struct sps30_state *state, const unsigned char *buf) +{ + struct sps30_serial_priv *priv = state->priv; + unsigned char chksum; + + if ((priv->num < SPS30_SERIAL_FRAME_MIN_SIZE) || + (priv->num != SPS30_SERIAL_FRAME_MIN_SIZE + + priv->buf[SPS30_SERIAL_FRAME_MISO_LEN_OFFSET])) { + dev_err(state->dev, "frame has invalid number of bytes\n"); + return false; + } + + if ((priv->buf[SPS30_SERIAL_FRAME_ADR_OFFSET] != buf[SPS30_SERIAL_FRAME_ADR_OFFSET]) || + (priv->buf[SPS30_SERIAL_FRAME_CMD_OFFSET] != buf[SPS30_SERIAL_FRAME_CMD_OFFSET])) { + dev_err(state->dev, "frame has wrong ADR and CMD bytes\n"); + return false; + } + + if (priv->buf[SPS30_SERIAL_FRAME_MISO_STATE_OFFSET]) { + dev_err(state->dev, "frame with non-zero state received (0x%02x)\n", + priv->buf[SPS30_SERIAL_FRAME_MISO_STATE_OFFSET]); + return false; + } + + /* SOF, checksum and EOF are not checksummed */ + chksum = sps30_serial_calc_chksum(priv->buf + 1, priv->num - 3); + if (priv->buf[priv->num - 2] != chksum) { + dev_err(state->dev, "frame integrity check failed\n"); + return false; + } + + return true; +} + +static int sps30_serial_command(struct sps30_state *state, unsigned char cmd, + const void *arg, size_t arg_size, void *rsp, size_t rsp_size) +{ + struct sps30_serial_priv *priv = state->priv; + unsigned char buf[SPS30_SERIAL_MAX_BUF_SIZE]; + int ret, size; + + size = sps30_serial_prep_frame(buf, cmd, arg, arg_size); + ret = sps30_serial_xfer(state, buf, size); + if (ret) + return ret; + + if (!sps30_serial_frame_valid(state, buf)) + return -EIO; + + if (rsp) { + rsp_size = min_t(size_t, priv->buf[SPS30_SERIAL_FRAME_MISO_LEN_OFFSET], rsp_size); + memcpy(rsp, &priv->buf[SPS30_SERIAL_FRAME_MISO_DATA_OFFSET], rsp_size); + } + + return rsp_size; +} + +static int sps30_serial_receive_buf(struct serdev_device *serdev, + const unsigned char *buf, size_t size) +{ + struct iio_dev *indio_dev = dev_get_drvdata(&serdev->dev); + struct sps30_serial_priv *priv; + struct sps30_state *state; + unsigned char byte; + size_t i; + + if (!indio_dev) + return 0; + + state = iio_priv(indio_dev); + priv = state->priv; + + /* just in case device put some unexpected data on the bus */ + if (priv->done) + return size; + + /* wait for the start of frame */ + if (!priv->num && size && buf[0] != SPS30_SERIAL_SOF_EOF) + return 1; + + if (priv->num + size >= ARRAY_SIZE(priv->buf)) + size = ARRAY_SIZE(priv->buf) - priv->num; + + for (i = 0; i < size; i++) { + byte = buf[i]; + /* remove stuffed bytes on-the-fly */ + if (byte == SPS30_SERIAL_ESCAPE_CHAR) { + priv->escaped = true; + continue; + } + + byte = sps30_serial_get_byte(priv->escaped, byte); + if (priv->escaped && !byte) + dev_warn(state->dev, "unrecognized escaped char (0x%02x)\n", byte); + + priv->buf[priv->num++] = byte; + + /* EOF received */ + if (!priv->escaped && byte == SPS30_SERIAL_SOF_EOF) { + if (priv->num < SPS30_SERIAL_FRAME_MIN_SIZE) + continue; + + priv->done = true; + complete(&priv->new_frame); + i++; + break; + } + + priv->escaped = false; + } + + return i; +} + +static const struct serdev_device_ops sps30_serial_device_ops = { + .receive_buf = sps30_serial_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +static int sps30_serial_start_meas(struct sps30_state *state) +{ + /* request BE IEEE754 formatted data */ + unsigned char buf[] = { 0x01, 0x03 }; + + return sps30_serial_command(state, SPS30_SERIAL_START_MEAS, buf, sizeof(buf), NULL, 0); +} + +static int sps30_serial_stop_meas(struct sps30_state *state) +{ + return sps30_serial_command(state, SPS30_SERIAL_STOP_MEAS, NULL, 0, NULL, 0); +} + +static int sps30_serial_reset(struct sps30_state *state) +{ + int ret; + + ret = sps30_serial_command(state, SPS30_SERIAL_RESET, NULL, 0, NULL, 0); + msleep(500); + + return ret; +} + +static int sps30_serial_read_meas(struct sps30_state *state, __be32 *meas, size_t num) +{ + int ret; + + /* measurements are ready within a second */ + if (msleep_interruptible(1000)) + return -EINTR; + + ret = sps30_serial_command(state, SPS30_SERIAL_READ_MEAS, NULL, 0, meas, num * sizeof(num)); + if (ret < 0) + return ret; + /* if measurements aren't ready sensor returns empty frame */ + if (ret == SPS30_SERIAL_FRAME_MIN_SIZE) + return -ETIMEDOUT; + if (ret != num * sizeof(*meas)) + return -EIO; + + return 0; +} + +static int sps30_serial_clean_fan(struct sps30_state *state) +{ + return sps30_serial_command(state, SPS30_SERIAL_CLEAN_FAN, NULL, 0, NULL, 0); +} + +static int sps30_serial_read_cleaning_period(struct sps30_state *state, __be32 *period) +{ + unsigned char buf[] = { 0x00 }; + int ret; + + ret = sps30_serial_command(state, SPS30_SERIAL_PERIOD, buf, sizeof(buf), + period, sizeof(*period)); + if (ret < 0) + return ret; + if (ret != sizeof(*period)) + return -EIO; + + return 0; +} + +static int sps30_serial_write_cleaning_period(struct sps30_state *state, __be32 period) +{ + unsigned char buf[5] = { 0x00 }; + + memcpy(buf + 1, &period, sizeof(period)); + + return sps30_serial_command(state, SPS30_SERIAL_PERIOD, buf, sizeof(buf), NULL, 0); +} + +static int sps30_serial_show_info(struct sps30_state *state) +{ + /* + * tell device do return serial number and add extra nul byte just in case + * serial number isn't a valid string + */ + unsigned char buf[32 + 1] = { 0x03 }; + struct device *dev = state->dev; + int ret; + + ret = sps30_serial_command(state, SPS30_SERIAL_DEV_INFO, buf, 1, buf, sizeof(buf) - 1); + if (ret < 0) + return ret; + if (ret != sizeof(buf) - 1) + return -EIO; + + dev_info(dev, "serial number: %s\n", buf); + + ret = sps30_serial_command(state, SPS30_SERIAL_READ_VERSION, NULL, 0, buf, sizeof(buf) - 1); + if (ret < 0) + return ret; + if (ret < 2) + return -EIO; + + dev_info(dev, "fw version: %u.%u\n", buf[0], buf[1]); + + return 0; +} + +static const struct sps30_ops sps30_serial_ops = { + .start_meas = sps30_serial_start_meas, + .stop_meas = sps30_serial_stop_meas, + .read_meas = sps30_serial_read_meas, + .reset = sps30_serial_reset, + .clean_fan = sps30_serial_clean_fan, + .read_cleaning_period = sps30_serial_read_cleaning_period, + .write_cleaning_period = sps30_serial_write_cleaning_period, + .show_info = sps30_serial_show_info, +}; + +static int sps30_serial_probe(struct serdev_device *serdev) +{ + struct device *dev = &serdev->dev; + struct sps30_serial_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + init_completion(&priv->new_frame); + serdev_device_set_client_ops(serdev, &sps30_serial_device_ops); + + ret = devm_serdev_device_open(dev, serdev); + if (ret) + return ret; + + serdev_device_set_baudrate(serdev, 115200); + serdev_device_set_flow_control(serdev, false); + + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + if (ret) + return ret; + + return sps30_probe(dev, SPS30_SERIAL_DEV_NAME, priv, &sps30_serial_ops); +} + +static const struct of_device_id sps30_serial_of_match[] = { + { .compatible = "sensirion,sps30" }, + { } +}; +MODULE_DEVICE_TABLE(of, sps30_serial_of_match); + +static struct serdev_device_driver sps30_serial_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = sps30_serial_of_match, + }, + .probe = sps30_serial_probe, +}; +module_serdev_device_driver(sps30_serial_driver); + +MODULE_AUTHOR("Tomasz Duszynski "); +MODULE_DESCRIPTION("Sensirion SPS30 particulate matter sensor serial driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From 9374e8f5a38defe90bc65b2decf317c1c62d91dd Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Wed, 28 Apr 2021 09:32:08 +0200 Subject: iio: adc: add ADC driver for the TI TSC2046 controller Basically the TI TSC2046 touchscreen controller is 8 channel ADC optimized for the touchscreen use case. By implementing it as an IIO ADC device, we can make use of resistive-adc-touch and iio-hwmon drivers. Polled readings are currently not implemented to keep this patch small, so iio-hwmon will not work out of the box for now. So far, this driver was tested with a custom version of resistive-adc-touch driver, since it needs to be extended to make use of Z1 and Z2 channels. The X/Y are working without additional changes. Signed-off-by: Oleksij Rempel Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210428073208.19570-4-o.rempel@pengutronix.de Signed-off-by: Jonathan Cameron --- MAINTAINERS | 8 + drivers/iio/adc/Kconfig | 12 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ti-tsc2046.c | 714 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 735 insertions(+) create mode 100644 drivers/iio/adc/ti-tsc2046.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 4fd93da5b784..fc771d2aacef 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18332,6 +18332,14 @@ S: Supported F: Documentation/devicetree/bindings/net/nfc/trf7970a.txt F: drivers/nfc/trf7970a.c +TI TSC2046 ADC DRIVER +M: Oleksij Rempel +R: kernel@pengutronix.de +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/adc/ti,tsc2046.yaml +F: drivers/iio/adc/ti-tsc2046.c + TI TWL4030 SERIES SOC CODEC DRIVER M: Peter Ujfalusi L: alsa-devel@alsa-project.org (moderated for non-subscribers) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index c7946c439612..db0c8fb60515 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1190,6 +1190,18 @@ config TI_TLC4541 This driver can also be built as a module. If so, the module will be called ti-tlc4541. +config TI_TSC2046 + tristate "Texas Instruments TSC2046 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for ADC functionality of Texas + Instruments TSC2046 touch screen controller. + + This driver can also be built as a module. If so, the module will be + called ti-tsc2046. + config TWL4030_MADC tristate "TWL4030 MADC (Monitoring A/D Converter)" depends on TWL4030_CORE diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index a226657d19c0..f70d877c555a 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -106,6 +106,7 @@ obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o obj-$(CONFIG_TI_TLC4541) += ti-tlc4541.o +obj-$(CONFIG_TI_TSC2046) += ti-tsc2046.o obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o obj-$(CONFIG_VF610_ADC) += vf610_adc.o diff --git a/drivers/iio/adc/ti-tsc2046.c b/drivers/iio/adc/ti-tsc2046.c new file mode 100644 index 000000000000..89a818b653b4 --- /dev/null +++ b/drivers/iio/adc/ti-tsc2046.c @@ -0,0 +1,714 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Texas Instruments TSC2046 SPI ADC driver + * + * Copyright (c) 2021 Oleksij Rempel , Pengutronix + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +/* + * The PENIRQ of TSC2046 controller is implemented as level shifter attached to + * the X+ line. If voltage of the X+ line reaches a specific level the IRQ will + * be activated or deactivated. + * To make this kind of IRQ reusable as trigger following additions were + * implemented: + * - rate limiting: + * For typical touchscreen use case, we need to trigger about each 10ms. + * - hrtimer: + * Continue triggering at least once after the IRQ was deactivated. Then + * deactivate this trigger to stop sampling in order to reduce power + * consumption. + */ + +#define TI_TSC2046_NAME "tsc2046" + +/* This driver doesn't aim at the peak continuous sample rate */ +#define TI_TSC2046_MAX_SAMPLE_RATE 125000 +#define TI_TSC2046_SAMPLE_BITS \ + BITS_PER_TYPE(struct tsc2046_adc_atom) +#define TI_TSC2046_MAX_CLK_FREQ \ + (TI_TSC2046_MAX_SAMPLE_RATE * TI_TSC2046_SAMPLE_BITS) + +#define TI_TSC2046_SAMPLE_INTERVAL_US 10000 + +#define TI_TSC2046_START BIT(7) +#define TI_TSC2046_ADDR GENMASK(6, 4) +#define TI_TSC2046_ADDR_TEMP1 7 +#define TI_TSC2046_ADDR_AUX 6 +#define TI_TSC2046_ADDR_X 5 +#define TI_TSC2046_ADDR_Z2 4 +#define TI_TSC2046_ADDR_Z1 3 +#define TI_TSC2046_ADDR_VBAT 2 +#define TI_TSC2046_ADDR_Y 1 +#define TI_TSC2046_ADDR_TEMP0 0 + +/* + * The mode bit sets the resolution of the ADC. With this bit low, the next + * conversion has 12-bit resolution, whereas with this bit high, the next + * conversion has 8-bit resolution. This driver is optimized for 12-bit mode. + * So, for this driver, this bit should stay zero. + */ +#define TI_TSC2046_8BIT_MODE BIT(3) + +/* + * SER/DFR - The SER/DFR bit controls the reference mode, either single-ended + * (high) or differential (low). + */ +#define TI_TSC2046_SER BIT(2) + +/* + * If VREF_ON and ADC_ON are both zero, then the chip operates in + * auto-wake/suspend mode. In most case this bits should stay zero. + */ +#define TI_TSC2046_PD1_VREF_ON BIT(1) +#define TI_TSC2046_PD0_ADC_ON BIT(0) + +/* + * All supported devices can do 8 or 12bit resolution. This driver + * supports only 12bit mode, here we have a 16bit data transfer, where + * the MSB and the 3 LSB are 0. + */ +#define TI_TSC2046_DATA_12BIT GENMASK(14, 3) + +#define TI_TSC2046_MAX_CHAN 8 + +/* Represents a HW sample */ +struct tsc2046_adc_atom { + /* + * Command transmitted to the controller. This field is empty on the RX + * buffer. + */ + u8 cmd; + /* + * Data received from the controller. This field is empty for the TX + * buffer + */ + __be16 data; +} __packed; + +/* Layout of atomic buffers within big buffer */ +struct tsc2046_adc_group_layout { + /* Group offset within the SPI RX buffer */ + unsigned int offset; + /* + * Amount of tsc2046_adc_atom structs within the same command gathered + * within same group. + */ + unsigned int count; + /* + * Settling samples (tsc2046_adc_atom structs) which should be skipped + * before good samples will start. + */ + unsigned int skip; +}; + +struct tsc2046_adc_dcfg { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +struct tsc2046_adc_ch_cfg { + unsigned int settling_time_us; + unsigned int oversampling_ratio; +}; + +struct tsc2046_adc_priv { + struct spi_device *spi; + const struct tsc2046_adc_dcfg *dcfg; + + struct iio_trigger *trig; + struct hrtimer trig_timer; + spinlock_t trig_lock; + unsigned int trig_more_count; + + struct spi_transfer xfer; + struct spi_message msg; + + struct { + /* Scan data for each channel */ + u16 data[TI_TSC2046_MAX_CHAN]; + /* Timestamp */ + s64 ts __aligned(8); + } scan_buf; + + /* + * Lock to protect the layout and the SPI transfer buffer. + * tsc2046_adc_group_layout can be changed within update_scan_mode(), + * in this case the l[] and tx/rx buffer will be out of sync to each + * other. + */ + struct mutex slock; + struct tsc2046_adc_group_layout l[TI_TSC2046_MAX_CHAN]; + struct tsc2046_adc_atom *rx; + struct tsc2046_adc_atom *tx; + + struct tsc2046_adc_atom *rx_one; + struct tsc2046_adc_atom *tx_one; + + unsigned int count; + unsigned int groups; + u32 effective_speed_hz; + u32 scan_interval_us; + u32 time_per_scan_us; + u32 time_per_bit_ns; + + struct tsc2046_adc_ch_cfg ch_cfg[TI_TSC2046_MAX_CHAN]; +}; + +#define TI_TSC2046_V_CHAN(index, bits, name) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .datasheet_name = "#name", \ + .scan_index = index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = bits, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +#define DECLARE_TI_TSC2046_8_CHANNELS(name, bits) \ +const struct iio_chan_spec name ## _channels[] = { \ + TI_TSC2046_V_CHAN(0, bits, TEMP0), \ + TI_TSC2046_V_CHAN(1, bits, Y), \ + TI_TSC2046_V_CHAN(2, bits, VBAT), \ + TI_TSC2046_V_CHAN(3, bits, Z1), \ + TI_TSC2046_V_CHAN(4, bits, Z2), \ + TI_TSC2046_V_CHAN(5, bits, X), \ + TI_TSC2046_V_CHAN(6, bits, AUX), \ + TI_TSC2046_V_CHAN(7, bits, TEMP1), \ + IIO_CHAN_SOFT_TIMESTAMP(8), \ +} + +static DECLARE_TI_TSC2046_8_CHANNELS(tsc2046_adc, 12); + +static const struct tsc2046_adc_dcfg tsc2046_adc_dcfg_tsc2046e = { + .channels = tsc2046_adc_channels, + .num_channels = ARRAY_SIZE(tsc2046_adc_channels), +}; + +/* + * Convert time to a number of samples which can be transferred within this + * time. + */ +static unsigned int tsc2046_adc_time_to_count(struct tsc2046_adc_priv *priv, + unsigned long time) +{ + unsigned int bit_count, sample_count; + + bit_count = DIV_ROUND_UP(time * NSEC_PER_USEC, priv->time_per_bit_ns); + sample_count = DIV_ROUND_UP(bit_count, TI_TSC2046_SAMPLE_BITS); + + dev_dbg(&priv->spi->dev, "Effective speed %u, time per bit: %u, count bits: %u, count samples: %u\n", + priv->effective_speed_hz, priv->time_per_bit_ns, + bit_count, sample_count); + + return sample_count; +} + +static u8 tsc2046_adc_get_cmd(struct tsc2046_adc_priv *priv, int ch_idx, + bool keep_power) +{ + u32 pd; + + /* + * if PD bits are 0, controller will automatically disable ADC, VREF and + * enable IRQ. + */ + if (keep_power) + pd = TI_TSC2046_PD0_ADC_ON; + else + pd = 0; + + return TI_TSC2046_START | FIELD_PREP(TI_TSC2046_ADDR, ch_idx) | pd; +} + +static u16 tsc2046_adc_get_value(struct tsc2046_adc_atom *buf) +{ + return FIELD_GET(TI_TSC2046_DATA_12BIT, get_unaligned_be16(&buf->data)); +} + +static int tsc2046_adc_read_one(struct tsc2046_adc_priv *priv, int ch_idx, + u32 *effective_speed_hz) +{ + struct spi_transfer xfer; + struct spi_message msg; + int ret; + + memset(&xfer, 0, sizeof(xfer)); + priv->tx_one->cmd = tsc2046_adc_get_cmd(priv, ch_idx, false); + priv->tx_one->data = 0; + xfer.tx_buf = priv->tx_one; + xfer.rx_buf = priv->rx_one; + xfer.len = sizeof(*priv->tx_one); + spi_message_init_with_transfers(&msg, &xfer, 1); + + /* + * We aren't using spi_write_then_read() because we need to be able + * to get hold of the effective_speed_hz from the xfer + */ + ret = spi_sync(priv->spi, &msg); + if (ret) { + dev_err_ratelimited(&priv->spi->dev, "SPI transfer failed %pe\n", + ERR_PTR(ret)); + return ret; + } + + if (effective_speed_hz) + *effective_speed_hz = xfer.effective_speed_hz; + + return tsc2046_adc_get_value(priv->rx_one); +} + +static size_t tsc2046_adc_group_set_layout(struct tsc2046_adc_priv *priv, + unsigned int group, + unsigned int ch_idx) +{ + struct tsc2046_adc_ch_cfg *ch = &priv->ch_cfg[ch_idx]; + struct tsc2046_adc_group_layout *cur; + unsigned int max_count, count_skip; + unsigned int offset = 0; + + if (group) + offset = priv->l[group - 1].offset + priv->l[group - 1].count; + + count_skip = tsc2046_adc_time_to_count(priv, ch->settling_time_us); + max_count = count_skip + ch->oversampling_ratio; + + cur = &priv->l[group]; + cur->offset = offset; + cur->count = max_count; + cur->skip = count_skip; + + return sizeof(*priv->tx) * max_count; +} + +static void tsc2046_adc_group_set_cmd(struct tsc2046_adc_priv *priv, + unsigned int group, int ch_idx) +{ + struct tsc2046_adc_group_layout *l = &priv->l[group]; + unsigned int i; + u8 cmd; + + /* + * Do not enable automatic power down on working samples. Otherwise the + * plates will never be completely charged. + */ + cmd = tsc2046_adc_get_cmd(priv, ch_idx, true); + + for (i = 0; i < l->count - 1; i++) + priv->tx[l->offset + i].cmd = cmd; + + /* automatically power down on last sample */ + priv->tx[l->offset + i].cmd = tsc2046_adc_get_cmd(priv, ch_idx, false); +} + +static u16 tsc2046_adc_get_val(struct tsc2046_adc_priv *priv, int group) +{ + struct tsc2046_adc_group_layout *l; + unsigned int val, val_normalized = 0; + int valid_count, i; + + l = &priv->l[group]; + valid_count = l->count - l->skip; + + for (i = 0; i < valid_count; i++) { + val = tsc2046_adc_get_value(&priv->rx[l->offset + l->skip + i]); + val_normalized += val; + } + + return DIV_ROUND_UP(val_normalized, valid_count); +} + +static int tsc2046_adc_scan(struct iio_dev *indio_dev) +{ + struct tsc2046_adc_priv *priv = iio_priv(indio_dev); + struct device *dev = &priv->spi->dev; + int group; + int ret; + + ret = spi_sync(priv->spi, &priv->msg); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI transfer failed: %pe\n", ERR_PTR(ret)); + return ret; + } + + for (group = 0; group < priv->groups; group++) + priv->scan_buf.data[group] = tsc2046_adc_get_val(priv, group); + + ret = iio_push_to_buffers_with_timestamp(indio_dev, &priv->scan_buf, + iio_get_time_ns(indio_dev)); + /* If the consumer is kfifo, we may get a EBUSY here - ignore it. */ + if (ret < 0 && ret != -EBUSY) { + dev_err_ratelimited(dev, "Failed to push scan buffer %pe\n", + ERR_PTR(ret)); + + return ret; + } + + return 0; +} + +static irqreturn_t tsc2046_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct tsc2046_adc_priv *priv = iio_priv(indio_dev); + + mutex_lock(&priv->slock); + tsc2046_adc_scan(indio_dev); + mutex_unlock(&priv->slock); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int tsc2046_adc_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *active_scan_mask) +{ + struct tsc2046_adc_priv *priv = iio_priv(indio_dev); + unsigned int ch_idx, group = 0; + size_t size; + + mutex_lock(&priv->slock); + + size = 0; + for_each_set_bit(ch_idx, active_scan_mask, indio_dev->num_channels) { + size += tsc2046_adc_group_set_layout(priv, group, ch_idx); + tsc2046_adc_group_set_cmd(priv, group, ch_idx); + group++; + } + + priv->groups = group; + priv->xfer.len = size; + priv->time_per_scan_us = size * 8 * priv->time_per_bit_ns / NSEC_PER_USEC; + + if ((priv->scan_interval_us - priv->time_per_scan_us) < 0) + dev_warn(&priv->spi->dev, "The scan interval (%d) is less then calculated scan time (%d)\n", + priv->scan_interval_us, priv->time_per_scan_us); + + mutex_unlock(&priv->slock); + + return 0; +} + +static const struct iio_info tsc2046_adc_info = { + .update_scan_mode = tsc2046_adc_update_scan_mode, +}; + +static enum hrtimer_restart tsc2046_adc_trig_more(struct hrtimer *hrtimer) +{ + struct tsc2046_adc_priv *priv = container_of(hrtimer, + struct tsc2046_adc_priv, + trig_timer); + unsigned long flags; + + spin_lock_irqsave(&priv->trig_lock, flags); + + disable_irq_nosync(priv->spi->irq); + + priv->trig_more_count++; + iio_trigger_poll(priv->trig); + + spin_unlock_irqrestore(&priv->trig_lock, flags); + + return HRTIMER_NORESTART; +} + +static irqreturn_t tsc2046_adc_irq(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct tsc2046_adc_priv *priv = iio_priv(indio_dev); + + spin_lock(&priv->trig_lock); + + hrtimer_try_to_cancel(&priv->trig_timer); + + priv->trig_more_count = 0; + disable_irq_nosync(priv->spi->irq); + iio_trigger_poll(priv->trig); + + spin_unlock(&priv->trig_lock); + + return IRQ_HANDLED; +} + +static void tsc2046_adc_reenable_trigger(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct tsc2046_adc_priv *priv = iio_priv(indio_dev); + unsigned long flags; + int delta; + + /* + * We can sample it as fast as we can, but usually we do not need so + * many samples. Reduce the sample rate for default (touchscreen) use + * case. + * Currently we do not need a highly precise sample rate. It is enough + * to have calculated numbers. + */ + delta = priv->scan_interval_us - priv->time_per_scan_us; + if (delta > 0) + fsleep(delta); + + spin_lock_irqsave(&priv->trig_lock, flags); + + /* + * We need to trigger at least one extra sample to detect state + * difference on ADC side. + */ + if (!priv->trig_more_count) { + int timeout_ms = DIV_ROUND_UP(priv->scan_interval_us, + USEC_PER_MSEC); + + hrtimer_start(&priv->trig_timer, ms_to_ktime(timeout_ms), + HRTIMER_MODE_REL_SOFT); + } + + enable_irq(priv->spi->irq); + + spin_unlock_irqrestore(&priv->trig_lock, flags); +} + +static int tsc2046_adc_set_trigger_state(struct iio_trigger *trig, bool enable) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct tsc2046_adc_priv *priv = iio_priv(indio_dev); + + if (enable) { + enable_irq(priv->spi->irq); + } else { + disable_irq(priv->spi->irq); + hrtimer_try_to_cancel(&priv->trig_timer); + } + + return 0; +} + +static const struct iio_trigger_ops tsc2046_adc_trigger_ops = { + .set_trigger_state = tsc2046_adc_set_trigger_state, + .reenable = tsc2046_adc_reenable_trigger, +}; + +static int tsc2046_adc_setup_spi_msg(struct tsc2046_adc_priv *priv) +{ + unsigned int ch_idx; + size_t size; + int ret; + + priv->tx_one = devm_kzalloc(&priv->spi->dev, sizeof(*priv->tx_one), + GFP_KERNEL); + if (!priv->tx_one) + return -ENOMEM; + + priv->rx_one = devm_kzalloc(&priv->spi->dev, sizeof(*priv->rx_one), + GFP_KERNEL); + if (!priv->rx_one) + return -ENOMEM; + + /* + * Make dummy read to set initial power state and get real SPI clock + * freq. It seems to be not important which channel is used for this + * case. + */ + ret = tsc2046_adc_read_one(priv, TI_TSC2046_ADDR_TEMP0, + &priv->effective_speed_hz); + if (ret < 0) + return ret; + + /* + * In case SPI controller do not report effective_speed_hz, use + * configure value and hope it will match. + */ + if (!priv->effective_speed_hz) + priv->effective_speed_hz = priv->spi->max_speed_hz; + + + priv->scan_interval_us = TI_TSC2046_SAMPLE_INTERVAL_US; + priv->time_per_bit_ns = DIV_ROUND_UP(NSEC_PER_SEC, + priv->effective_speed_hz); + + /* + * Calculate and allocate maximal size buffer if all channels are + * enabled. + */ + size = 0; + for (ch_idx = 0; ch_idx < priv->dcfg->num_channels; ch_idx++) + size += tsc2046_adc_group_set_layout(priv, ch_idx, ch_idx); + + priv->tx = devm_kzalloc(&priv->spi->dev, size, GFP_KERNEL); + if (!priv->tx) + return -ENOMEM; + + priv->rx = devm_kzalloc(&priv->spi->dev, size, GFP_KERNEL); + if (!priv->rx) + return -ENOMEM; + + priv->xfer.tx_buf = priv->tx; + priv->xfer.rx_buf = priv->rx; + priv->xfer.len = size; + spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1); + + return 0; +} + +static void tsc2046_adc_parse_fwnode(struct tsc2046_adc_priv *priv) +{ + struct fwnode_handle *child; + struct device *dev = &priv->spi->dev; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(priv->ch_cfg); i++) { + priv->ch_cfg[i].settling_time_us = 1; + priv->ch_cfg[i].oversampling_ratio = 1; + } + + device_for_each_child_node(dev, child) { + u32 stl, overs, reg; + int ret; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) { + dev_err(dev, "invalid reg on %pfw, err: %pe\n", child, + ERR_PTR(ret)); + continue; + } + + if (reg >= ARRAY_SIZE(priv->ch_cfg)) { + dev_err(dev, "%pfw: Unsupported reg value: %i, max supported is: %zu.\n", + child, reg, ARRAY_SIZE(priv->ch_cfg)); + continue; + } + + ret = fwnode_property_read_u32(child, "settling-time-us", &stl); + if (!ret) + priv->ch_cfg[reg].settling_time_us = stl; + + ret = fwnode_property_read_u32(child, "oversampling-ratio", + &overs); + if (!ret) + priv->ch_cfg[reg].oversampling_ratio = overs; + } +} + +static int tsc2046_adc_probe(struct spi_device *spi) +{ + const struct tsc2046_adc_dcfg *dcfg; + struct device *dev = &spi->dev; + struct tsc2046_adc_priv *priv; + struct iio_dev *indio_dev; + struct iio_trigger *trig; + int ret; + + if (spi->max_speed_hz > TI_TSC2046_MAX_CLK_FREQ) { + dev_err(dev, "SPI max_speed_hz is too high: %d Hz. Max supported freq is %zu Hz\n", + spi->max_speed_hz, TI_TSC2046_MAX_CLK_FREQ); + return -EINVAL; + } + + dcfg = device_get_match_data(dev); + if (!dcfg) + return -EINVAL; + + spi->bits_per_word = 8; + spi->mode &= ~SPI_MODE_X_MASK; + spi->mode |= SPI_MODE_0; + ret = spi_setup(spi); + if (ret < 0) + return dev_err_probe(dev, ret, "Error in SPI setup\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + priv->dcfg = dcfg; + + spi_set_drvdata(spi, indio_dev); + + priv->spi = spi; + + indio_dev->name = TI_TSC2046_NAME; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_TRIGGERED; + indio_dev->channels = dcfg->channels; + indio_dev->num_channels = dcfg->num_channels; + indio_dev->info = &tsc2046_adc_info; + + tsc2046_adc_parse_fwnode(priv); + + ret = tsc2046_adc_setup_spi_msg(priv); + if (ret) + return ret; + + mutex_init(&priv->slock); + + ret = devm_request_irq(dev, spi->irq, &tsc2046_adc_irq, + IRQF_NO_AUTOEN, indio_dev->name, indio_dev); + if (ret) + return ret; + + trig = devm_iio_trigger_alloc(dev, "touchscreen-%s", indio_dev->name); + if (!trig) + return -ENOMEM; + + priv->trig = trig; + iio_trigger_set_drvdata(trig, indio_dev); + trig->ops = &tsc2046_adc_trigger_ops; + + spin_lock_init(&priv->trig_lock); + hrtimer_init(&priv->trig_timer, CLOCK_MONOTONIC, + HRTIMER_MODE_REL_SOFT); + priv->trig_timer.function = tsc2046_adc_trig_more; + + ret = devm_iio_trigger_register(dev, trig); + if (ret) { + dev_err(dev, "failed to register trigger\n"); + return ret; + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + &tsc2046_adc_trigger_handler, NULL); + if (ret) { + dev_err(dev, "Failed to setup triggered buffer\n"); + return ret; + } + + /* set default trigger */ + indio_dev->trig = iio_trigger_get(priv->trig); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ads7950_of_table[] = { + { .compatible = "ti,tsc2046e-adc", .data = &tsc2046_adc_dcfg_tsc2046e }, + { } +}; +MODULE_DEVICE_TABLE(of, ads7950_of_table); + +static struct spi_driver tsc2046_adc_driver = { + .driver = { + .name = "tsc2046", + .of_match_table = ads7950_of_table, + }, + .probe = tsc2046_adc_probe, +}; +module_spi_driver(tsc2046_adc_driver); + +MODULE_AUTHOR("Oleksij Rempel "); +MODULE_DESCRIPTION("TI TSC2046 ADC"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From b711f687a1c1c14c2da589e84e4f61b975196951 Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Wed, 2 Jun 2021 14:32:59 +0300 Subject: counter: Add support for Intel Quadrature Encoder Peripheral Add support for Intel Quadrature Encoder Peripheral found on Intel Elkhart Lake platform. Initial implementation was done by Felipe Balbi while he was working at Intel with later changes from Raymond Tan and me. Co-developed-by: Felipe Balbi (Intel) Signed-off-by: Felipe Balbi (Intel) Co-developed-by: Raymond Tan Signed-off-by: Raymond Tan Signed-off-by: Jarkko Nikula Acked-by: William Breathitt Gray Link: https://lore.kernel.org/r/20210602113259.158674-1-jarkko.nikula@linux.intel.com Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-counter | 9 + MAINTAINERS | 5 + drivers/counter/Kconfig | 10 + drivers/counter/Makefile | 1 + drivers/counter/intel-qep.c | 546 ++++++++++++++++++++++++++++ 5 files changed, 571 insertions(+) create mode 100644 drivers/counter/intel-qep.c (limited to 'MAINTAINERS') diff --git a/Documentation/ABI/testing/sysfs-bus-counter b/Documentation/ABI/testing/sysfs-bus-counter index 566bd99fe0a5..e9d9e50f03be 100644 --- a/Documentation/ABI/testing/sysfs-bus-counter +++ b/Documentation/ABI/testing/sysfs-bus-counter @@ -193,6 +193,15 @@ Description: both edges: Any state transition. +What: /sys/bus/counter/devices/counterX/countY/spike_filter_ns +KernelVersion: 5.14 +Contact: linux-iio@vger.kernel.org +Description: + If the counter device supports programmable spike filter this + attribute indicates the value in nanoseconds where noise pulses + shorter or equal to configured value are ignored. Value 0 means + filter is disabled. + What: /sys/bus/counter/devices/counterX/name KernelVersion: 5.2 Contact: linux-iio@vger.kernel.org diff --git a/MAINTAINERS b/MAINTAINERS index fc771d2aacef..e679d422b472 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9364,6 +9364,11 @@ L: linux-pm@vger.kernel.org S: Supported F: drivers/cpufreq/intel_pstate.c +INTEL QUADRATURE ENCODER PERIPHERAL DRIVER +M: Jarkko Nikula +L: linux-iio@vger.kernel.org +F: drivers/counter/intel-qep.c + INTEL RDMA RNIC DRIVER M: Faisal Latif M: Shiraz Saleem diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig index 5328705aa09c..d5d2540b30c2 100644 --- a/drivers/counter/Kconfig +++ b/drivers/counter/Kconfig @@ -91,4 +91,14 @@ config MICROCHIP_TCB_CAPTURE To compile this driver as a module, choose M here: the module will be called microchip-tcb-capture. +config INTEL_QEP + tristate "Intel Quadrature Encoder Peripheral driver" + depends on PCI + help + Select this option to enable the Intel Quadrature Encoder Peripheral + driver. + + To compile this driver as a module, choose M here: the module + will be called intel-qep. + endif # COUNTER diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile index cb646ed2f039..19742e6f5e3e 100644 --- a/drivers/counter/Makefile +++ b/drivers/counter/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_STM32_LPTIMER_CNT) += stm32-lptimer-cnt.o obj-$(CONFIG_TI_EQEP) += ti-eqep.o obj-$(CONFIG_FTM_QUADDEC) += ftm-quaddec.o obj-$(CONFIG_MICROCHIP_TCB_CAPTURE) += microchip-tcb-capture.o +obj-$(CONFIG_INTEL_QEP) += intel-qep.o diff --git a/drivers/counter/intel-qep.c b/drivers/counter/intel-qep.c new file mode 100644 index 000000000000..ab10ba33f46a --- /dev/null +++ b/drivers/counter/intel-qep.c @@ -0,0 +1,546 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Quadrature Encoder Peripheral driver + * + * Copyright (C) 2019-2021 Intel Corporation + * + * Author: Felipe Balbi (Intel) + * Author: Jarkko Nikula + * Author: Raymond Tan + */ +#include +#include +#include +#include +#include +#include +#include + +#define INTEL_QEPCON 0x00 +#define INTEL_QEPFLT 0x04 +#define INTEL_QEPCOUNT 0x08 +#define INTEL_QEPMAX 0x0c +#define INTEL_QEPWDT 0x10 +#define INTEL_QEPCAPDIV 0x14 +#define INTEL_QEPCNTR 0x18 +#define INTEL_QEPCAPBUF 0x1c +#define INTEL_QEPINT_STAT 0x20 +#define INTEL_QEPINT_MASK 0x24 + +/* QEPCON */ +#define INTEL_QEPCON_EN BIT(0) +#define INTEL_QEPCON_FLT_EN BIT(1) +#define INTEL_QEPCON_EDGE_A BIT(2) +#define INTEL_QEPCON_EDGE_B BIT(3) +#define INTEL_QEPCON_EDGE_INDX BIT(4) +#define INTEL_QEPCON_SWPAB BIT(5) +#define INTEL_QEPCON_OP_MODE BIT(6) +#define INTEL_QEPCON_PH_ERR BIT(7) +#define INTEL_QEPCON_COUNT_RST_MODE BIT(8) +#define INTEL_QEPCON_INDX_GATING_MASK GENMASK(10, 9) +#define INTEL_QEPCON_INDX_GATING(n) (((n) & 3) << 9) +#define INTEL_QEPCON_INDX_PAL_PBL INTEL_QEPCON_INDX_GATING(0) +#define INTEL_QEPCON_INDX_PAL_PBH INTEL_QEPCON_INDX_GATING(1) +#define INTEL_QEPCON_INDX_PAH_PBL INTEL_QEPCON_INDX_GATING(2) +#define INTEL_QEPCON_INDX_PAH_PBH INTEL_QEPCON_INDX_GATING(3) +#define INTEL_QEPCON_CAP_MODE BIT(11) +#define INTEL_QEPCON_FIFO_THRE_MASK GENMASK(14, 12) +#define INTEL_QEPCON_FIFO_THRE(n) ((((n) - 1) & 7) << 12) +#define INTEL_QEPCON_FIFO_EMPTY BIT(15) + +/* QEPFLT */ +#define INTEL_QEPFLT_MAX_COUNT(n) ((n) & 0x1fffff) + +/* QEPINT */ +#define INTEL_QEPINT_FIFOCRIT BIT(5) +#define INTEL_QEPINT_FIFOENTRY BIT(4) +#define INTEL_QEPINT_QEPDIR BIT(3) +#define INTEL_QEPINT_QEPRST_UP BIT(2) +#define INTEL_QEPINT_QEPRST_DOWN BIT(1) +#define INTEL_QEPINT_WDT BIT(0) + +#define INTEL_QEPINT_MASK_ALL GENMASK(5, 0) + +#define INTEL_QEP_CLK_PERIOD_NS 10 + +#define INTEL_QEP_COUNTER_EXT_RW(_name) \ +{ \ + .name = #_name, \ + .read = _name##_read, \ + .write = _name##_write, \ +} + +struct intel_qep { + struct counter_device counter; + struct mutex lock; + struct device *dev; + void __iomem *regs; + bool enabled; + /* Context save registers */ + u32 qepcon; + u32 qepflt; + u32 qepmax; +}; + +static inline u32 intel_qep_readl(struct intel_qep *qep, u32 offset) +{ + return readl(qep->regs + offset); +} + +static inline void intel_qep_writel(struct intel_qep *qep, + u32 offset, u32 value) +{ + writel(value, qep->regs + offset); +} + +static void intel_qep_init(struct intel_qep *qep) +{ + u32 reg; + + reg = intel_qep_readl(qep, INTEL_QEPCON); + reg &= ~INTEL_QEPCON_EN; + intel_qep_writel(qep, INTEL_QEPCON, reg); + qep->enabled = false; + /* + * Make sure peripheral is disabled by flushing the write with + * a dummy read + */ + reg = intel_qep_readl(qep, INTEL_QEPCON); + + reg &= ~(INTEL_QEPCON_OP_MODE | INTEL_QEPCON_FLT_EN); + reg |= INTEL_QEPCON_EDGE_A | INTEL_QEPCON_EDGE_B | + INTEL_QEPCON_EDGE_INDX | INTEL_QEPCON_COUNT_RST_MODE; + intel_qep_writel(qep, INTEL_QEPCON, reg); + intel_qep_writel(qep, INTEL_QEPINT_MASK, INTEL_QEPINT_MASK_ALL); +} + +static int intel_qep_count_read(struct counter_device *counter, + struct counter_count *count, + unsigned long *val) +{ + struct intel_qep *const qep = counter->priv; + + pm_runtime_get_sync(qep->dev); + *val = intel_qep_readl(qep, INTEL_QEPCOUNT); + pm_runtime_put(qep->dev); + + return 0; +} + +static const enum counter_count_function intel_qep_count_functions[] = { + COUNTER_COUNT_FUNCTION_QUADRATURE_X4, +}; + +static int intel_qep_function_get(struct counter_device *counter, + struct counter_count *count, + size_t *function) +{ + *function = 0; + + return 0; +} + +static const enum counter_synapse_action intel_qep_synapse_actions[] = { + COUNTER_SYNAPSE_ACTION_BOTH_EDGES, +}; + +static int intel_qep_action_get(struct counter_device *counter, + struct counter_count *count, + struct counter_synapse *synapse, + size_t *action) +{ + *action = 0; + return 0; +} + +static const struct counter_ops intel_qep_counter_ops = { + .count_read = intel_qep_count_read, + .function_get = intel_qep_function_get, + .action_get = intel_qep_action_get, +}; + +#define INTEL_QEP_SIGNAL(_id, _name) { \ + .id = (_id), \ + .name = (_name), \ +} + +static struct counter_signal intel_qep_signals[] = { + INTEL_QEP_SIGNAL(0, "Phase A"), + INTEL_QEP_SIGNAL(1, "Phase B"), + INTEL_QEP_SIGNAL(2, "Index"), +}; + +#define INTEL_QEP_SYNAPSE(_signal_id) { \ + .actions_list = intel_qep_synapse_actions, \ + .num_actions = ARRAY_SIZE(intel_qep_synapse_actions), \ + .signal = &intel_qep_signals[(_signal_id)], \ +} + +static struct counter_synapse intel_qep_count_synapses[] = { + INTEL_QEP_SYNAPSE(0), + INTEL_QEP_SYNAPSE(1), + INTEL_QEP_SYNAPSE(2), +}; + +static ssize_t ceiling_read(struct counter_device *counter, + struct counter_count *count, + void *priv, char *buf) +{ + struct intel_qep *qep = counter->priv; + u32 reg; + + pm_runtime_get_sync(qep->dev); + reg = intel_qep_readl(qep, INTEL_QEPMAX); + pm_runtime_put(qep->dev); + + return sysfs_emit(buf, "%u\n", reg); +} + +static ssize_t ceiling_write(struct counter_device *counter, + struct counter_count *count, + void *priv, const char *buf, size_t len) +{ + struct intel_qep *qep = counter->priv; + u32 max; + int ret; + + ret = kstrtou32(buf, 0, &max); + if (ret < 0) + return ret; + + mutex_lock(&qep->lock); + if (qep->enabled) { + ret = -EBUSY; + goto out; + } + + pm_runtime_get_sync(qep->dev); + intel_qep_writel(qep, INTEL_QEPMAX, max); + pm_runtime_put(qep->dev); + ret = len; + +out: + mutex_unlock(&qep->lock); + return ret; +} + +static ssize_t enable_read(struct counter_device *counter, + struct counter_count *count, + void *priv, char *buf) +{ + struct intel_qep *qep = counter->priv; + + return sysfs_emit(buf, "%u\n", qep->enabled); +} + +static ssize_t enable_write(struct counter_device *counter, + struct counter_count *count, + void *priv, const char *buf, size_t len) +{ + struct intel_qep *qep = counter->priv; + u32 reg; + bool val, changed; + int ret; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + mutex_lock(&qep->lock); + changed = val ^ qep->enabled; + if (!changed) + goto out; + + pm_runtime_get_sync(qep->dev); + reg = intel_qep_readl(qep, INTEL_QEPCON); + if (val) { + /* Enable peripheral and keep runtime PM always on */ + reg |= INTEL_QEPCON_EN; + pm_runtime_get_noresume(qep->dev); + } else { + /* Let runtime PM be idle and disable peripheral */ + pm_runtime_put_noidle(qep->dev); + reg &= ~INTEL_QEPCON_EN; + } + intel_qep_writel(qep, INTEL_QEPCON, reg); + pm_runtime_put(qep->dev); + qep->enabled = val; + +out: + mutex_unlock(&qep->lock); + return len; +} + +static ssize_t spike_filter_ns_read(struct counter_device *counter, + struct counter_count *count, + void *priv, char *buf) +{ + struct intel_qep *qep = counter->priv; + u32 reg; + + pm_runtime_get_sync(qep->dev); + reg = intel_qep_readl(qep, INTEL_QEPCON); + if (!(reg & INTEL_QEPCON_FLT_EN)) { + pm_runtime_put(qep->dev); + return sysfs_emit(buf, "0\n"); + } + reg = INTEL_QEPFLT_MAX_COUNT(intel_qep_readl(qep, INTEL_QEPFLT)); + pm_runtime_put(qep->dev); + + return sysfs_emit(buf, "%u\n", (reg + 2) * INTEL_QEP_CLK_PERIOD_NS); +} + +static ssize_t spike_filter_ns_write(struct counter_device *counter, + struct counter_count *count, + void *priv, const char *buf, size_t len) +{ + struct intel_qep *qep = counter->priv; + u32 reg, length; + bool enable; + int ret; + + ret = kstrtou32(buf, 0, &length); + if (ret < 0) + return ret; + + /* + * Spike filter length is (MAX_COUNT + 2) clock periods. + * Disable filter when userspace writes 0, enable for valid + * nanoseconds values and error out otherwise. + */ + length /= INTEL_QEP_CLK_PERIOD_NS; + if (length == 0) { + enable = false; + length = 0; + } else if (length >= 2) { + enable = true; + length -= 2; + } else { + return -EINVAL; + } + + if (length > INTEL_QEPFLT_MAX_COUNT(length)) + return -EINVAL; + + mutex_lock(&qep->lock); + if (qep->enabled) { + ret = -EBUSY; + goto out; + } + + pm_runtime_get_sync(qep->dev); + reg = intel_qep_readl(qep, INTEL_QEPCON); + if (enable) + reg |= INTEL_QEPCON_FLT_EN; + else + reg &= ~INTEL_QEPCON_FLT_EN; + intel_qep_writel(qep, INTEL_QEPFLT, length); + intel_qep_writel(qep, INTEL_QEPCON, reg); + pm_runtime_put(qep->dev); + ret = len; + +out: + mutex_unlock(&qep->lock); + return ret; +} + +static ssize_t preset_enable_read(struct counter_device *counter, + struct counter_count *count, + void *priv, char *buf) +{ + struct intel_qep *qep = counter->priv; + u32 reg; + + pm_runtime_get_sync(qep->dev); + reg = intel_qep_readl(qep, INTEL_QEPCON); + pm_runtime_put(qep->dev); + return sysfs_emit(buf, "%u\n", !(reg & INTEL_QEPCON_COUNT_RST_MODE)); +} + +static ssize_t preset_enable_write(struct counter_device *counter, + struct counter_count *count, + void *priv, const char *buf, size_t len) +{ + struct intel_qep *qep = counter->priv; + u32 reg; + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + mutex_lock(&qep->lock); + if (qep->enabled) { + ret = -EBUSY; + goto out; + } + + pm_runtime_get_sync(qep->dev); + reg = intel_qep_readl(qep, INTEL_QEPCON); + if (val) + reg &= ~INTEL_QEPCON_COUNT_RST_MODE; + else + reg |= INTEL_QEPCON_COUNT_RST_MODE; + + intel_qep_writel(qep, INTEL_QEPCON, reg); + pm_runtime_put(qep->dev); + ret = len; + +out: + mutex_unlock(&qep->lock); + + return ret; +} + +static const struct counter_count_ext intel_qep_count_ext[] = { + INTEL_QEP_COUNTER_EXT_RW(ceiling), + INTEL_QEP_COUNTER_EXT_RW(enable), + INTEL_QEP_COUNTER_EXT_RW(spike_filter_ns), + INTEL_QEP_COUNTER_EXT_RW(preset_enable) +}; + +static struct counter_count intel_qep_counter_count[] = { + { + .id = 0, + .name = "Channel 1 Count", + .functions_list = intel_qep_count_functions, + .num_functions = ARRAY_SIZE(intel_qep_count_functions), + .synapses = intel_qep_count_synapses, + .num_synapses = ARRAY_SIZE(intel_qep_count_synapses), + .ext = intel_qep_count_ext, + .num_ext = ARRAY_SIZE(intel_qep_count_ext), + }, +}; + +static int intel_qep_probe(struct pci_dev *pci, const struct pci_device_id *id) +{ + struct intel_qep *qep; + struct device *dev = &pci->dev; + void __iomem *regs; + int ret; + + qep = devm_kzalloc(dev, sizeof(*qep), GFP_KERNEL); + if (!qep) + return -ENOMEM; + + ret = pcim_enable_device(pci); + if (ret) + return ret; + + pci_set_master(pci); + + ret = pcim_iomap_regions(pci, BIT(0), pci_name(pci)); + if (ret) + return ret; + + regs = pcim_iomap_table(pci)[0]; + if (!regs) + return -ENOMEM; + + qep->dev = dev; + qep->regs = regs; + mutex_init(&qep->lock); + + intel_qep_init(qep); + pci_set_drvdata(pci, qep); + + qep->counter.name = pci_name(pci); + qep->counter.parent = dev; + qep->counter.ops = &intel_qep_counter_ops; + qep->counter.counts = intel_qep_counter_count; + qep->counter.num_counts = ARRAY_SIZE(intel_qep_counter_count); + qep->counter.signals = intel_qep_signals; + qep->counter.num_signals = ARRAY_SIZE(intel_qep_signals); + qep->counter.priv = qep; + qep->enabled = false; + + pm_runtime_put(dev); + pm_runtime_allow(dev); + + return devm_counter_register(&pci->dev, &qep->counter); +} + +static void intel_qep_remove(struct pci_dev *pci) +{ + struct intel_qep *qep = pci_get_drvdata(pci); + struct device *dev = &pci->dev; + + pm_runtime_forbid(dev); + if (!qep->enabled) + pm_runtime_get(dev); + + intel_qep_writel(qep, INTEL_QEPCON, 0); +} + +#ifdef CONFIG_PM +static int intel_qep_suspend(struct device *dev) +{ + struct pci_dev *pdev = container_of(dev, struct pci_dev, dev); + struct intel_qep *qep = pci_get_drvdata(pdev); + + qep->qepcon = intel_qep_readl(qep, INTEL_QEPCON); + qep->qepflt = intel_qep_readl(qep, INTEL_QEPFLT); + qep->qepmax = intel_qep_readl(qep, INTEL_QEPMAX); + + return 0; +} + +static int intel_qep_resume(struct device *dev) +{ + struct pci_dev *pdev = container_of(dev, struct pci_dev, dev); + struct intel_qep *qep = pci_get_drvdata(pdev); + + /* + * Make sure peripheral is disabled when restoring registers and + * control register bits that are writable only when the peripheral + * is disabled + */ + intel_qep_writel(qep, INTEL_QEPCON, 0); + intel_qep_readl(qep, INTEL_QEPCON); + + intel_qep_writel(qep, INTEL_QEPFLT, qep->qepflt); + intel_qep_writel(qep, INTEL_QEPMAX, qep->qepmax); + intel_qep_writel(qep, INTEL_QEPINT_MASK, INTEL_QEPINT_MASK_ALL); + + /* Restore all other control register bits except enable status */ + intel_qep_writel(qep, INTEL_QEPCON, qep->qepcon & ~INTEL_QEPCON_EN); + intel_qep_readl(qep, INTEL_QEPCON); + + /* Restore enable status */ + intel_qep_writel(qep, INTEL_QEPCON, qep->qepcon); + + return 0; +} +#endif + +static UNIVERSAL_DEV_PM_OPS(intel_qep_pm_ops, + intel_qep_suspend, intel_qep_resume, NULL); + +static const struct pci_device_id intel_qep_id_table[] = { + /* EHL */ + { PCI_VDEVICE(INTEL, 0x4bc3), }, + { PCI_VDEVICE(INTEL, 0x4b81), }, + { PCI_VDEVICE(INTEL, 0x4b82), }, + { PCI_VDEVICE(INTEL, 0x4b83), }, + { } /* Terminating Entry */ +}; +MODULE_DEVICE_TABLE(pci, intel_qep_id_table); + +static struct pci_driver intel_qep_driver = { + .name = "intel-qep", + .id_table = intel_qep_id_table, + .probe = intel_qep_probe, + .remove = intel_qep_remove, + .driver = { + .pm = &intel_qep_pm_ops, + } +}; + +module_pci_driver(intel_qep_driver); + +MODULE_AUTHOR("Felipe Balbi (Intel)"); +MODULE_AUTHOR("Jarkko Nikula "); +MODULE_AUTHOR("Raymond Tan "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Intel Quadrature Encoder Peripheral driver"); -- cgit v1.2.3-59-g8ed1b From 769841c966fdb36b41d968d96d3e6fb7f7abd63c Mon Sep 17 00:00:00 2001 From: William Breathitt Gray Date: Wed, 9 Jun 2021 10:31:04 +0900 Subject: docs: counter: Consolidate Counter sysfs attributes documentation Duplicate ABIs are not valid, so let's consolidate these sysfs attributes into the main sysfs-bus-counter documentation file. Cc: Patrick Havelange Reviewed-by: David Lechner Signed-off-by: William Breathitt Gray Link: https://lore.kernel.org/r/c651ec1c541754ad108160839e2b8425ad089819.1623201081.git.vilhelm.gray@gmail.com Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-counter | 76 +++++++++++++++++++++- .../ABI/testing/sysfs-bus-counter-104-quad-8 | 61 ----------------- .../ABI/testing/sysfs-bus-counter-ftm-quaddec | 16 ----- MAINTAINERS | 4 +- 4 files changed, 75 insertions(+), 82 deletions(-) delete mode 100644 Documentation/ABI/testing/sysfs-bus-counter-104-quad-8 delete mode 100644 Documentation/ABI/testing/sysfs-bus-counter-ftm-quaddec (limited to 'MAINTAINERS') diff --git a/Documentation/ABI/testing/sysfs-bus-counter b/Documentation/ABI/testing/sysfs-bus-counter index e9d9e50f03be..20fe5afd4f9e 100644 --- a/Documentation/ABI/testing/sysfs-bus-counter +++ b/Documentation/ABI/testing/sysfs-bus-counter @@ -57,6 +57,7 @@ Description: What: /sys/bus/counter/devices/counterX/countY/count_mode_available What: /sys/bus/counter/devices/counterX/countY/error_noise_available What: /sys/bus/counter/devices/counterX/countY/function_available +What: /sys/bus/counter/devices/counterX/countY/prescaler_available What: /sys/bus/counter/devices/counterX/countY/signalZ_action_available KernelVersion: 5.2 Contact: linux-iio@vger.kernel.org @@ -154,6 +155,15 @@ Description: Count Y. If possible, this should match the name of the respective channel as it appears in the device datasheet. +What: /sys/bus/counter/devices/counterX/countY/prescaler +KernelVersion: 5.2 +Contact: linux-iio@vger.kernel.org +Description: + Configure the prescaler value associated with Count Y. + On the FlexTimer, the counter clock source passes through a + prescaler (i.e. a counter). This acts like a clock + divider. + What: /sys/bus/counter/devices/counterX/countY/preset KernelVersion: 5.2 Contact: linux-iio@vger.kernel.org @@ -224,11 +234,45 @@ Description: Read-only attribute that indicates the total number of Signals belonging to the Counter. -What: /sys/bus/counter/devices/counterX/signalY/signal +What: /sys/bus/counter/devices/counterX/signalY/cable_fault +KernelVersion: 5.7 +Contact: linux-iio@vger.kernel.org +Description: + Read-only attribute that indicates whether a differential + encoder cable fault (not connected or loose wires) is detected + for the respective channel of Signal Y. Valid attribute values + are boolean. Detection must first be enabled via the + corresponding cable_fault_enable attribute. + +What: /sys/bus/counter/devices/counterX/signalY/cable_fault_enable +KernelVersion: 5.7 +Contact: linux-iio@vger.kernel.org +Description: + Whether detection of differential encoder cable faults for the + respective channel of Signal Y is enabled. Valid attribute + values are boolean. + +What: /sys/bus/counter/devices/counterX/signalY/filter_clock_prescaler +KernelVersion: 5.7 +Contact: linux-iio@vger.kernel.org +Description: + Filter clock factor for input Signal Y. This prescaler value + affects the inputs of both quadrature pair signals. + +What: /sys/bus/counter/devices/counterX/signalY/index_polarity KernelVersion: 5.2 Contact: linux-iio@vger.kernel.org Description: - Signal data of Signal Y represented as a string. + Active level of index input Signal Y; irrelevant in + non-synchronous load mode. + +What: /sys/bus/counter/devices/counterX/signalY/index_polarity_available +What: /sys/bus/counter/devices/counterX/signalY/synchronous_mode_available +KernelVersion: 5.2 +Contact: linux-iio@vger.kernel.org +Description: + Discrete set of available values for the respective Signal Y + configuration are listed in this file. What: /sys/bus/counter/devices/counterX/signalY/name KernelVersion: 5.2 @@ -237,3 +281,31 @@ Description: Read-only attribute that indicates the device-specific name of Signal Y. If possible, this should match the name of the respective signal as it appears in the device datasheet. + +What: /sys/bus/counter/devices/counterX/signalY/signal +KernelVersion: 5.2 +Contact: linux-iio@vger.kernel.org +Description: + Signal data of Signal Y represented as a string. + +What: /sys/bus/counter/devices/counterX/signalY/synchronous_mode +KernelVersion: 5.2 +Contact: linux-iio@vger.kernel.org +Description: + Configure the counter associated with Signal Y for + non-synchronous or synchronous load mode. Synchronous load mode + cannot be selected in non-quadrature (Pulse-Direction) clock + mode. + + non-synchronous: + A logic low level is the active level at this index + input. The index function (as enabled via preset_enable) + is performed directly on the active level of the index + input. + + synchronous: + Intended for interfacing with encoder Index output in + quadrature clock mode. The active level is configured + via index_polarity. The index function (as enabled via + preset_enable) is performed synchronously with the + quadrature clock on the active level of the index input. diff --git a/Documentation/ABI/testing/sysfs-bus-counter-104-quad-8 b/Documentation/ABI/testing/sysfs-bus-counter-104-quad-8 deleted file mode 100644 index eac32180c40d..000000000000 --- a/Documentation/ABI/testing/sysfs-bus-counter-104-quad-8 +++ /dev/null @@ -1,61 +0,0 @@ -What: /sys/bus/counter/devices/counterX/signalY/cable_fault -KernelVersion: 5.7 -Contact: linux-iio@vger.kernel.org -Description: - Read-only attribute that indicates whether a differential - encoder cable fault (not connected or loose wires) is detected - for the respective channel of Signal Y. Valid attribute values - are boolean. Detection must first be enabled via the - corresponding cable_fault_enable attribute. - -What: /sys/bus/counter/devices/counterX/signalY/cable_fault_enable -KernelVersion: 5.7 -Contact: linux-iio@vger.kernel.org -Description: - Whether detection of differential encoder cable faults for the - respective channel of Signal Y is enabled. Valid attribute - values are boolean. - -What: /sys/bus/counter/devices/counterX/signalY/filter_clock_prescaler -KernelVersion: 5.7 -Contact: linux-iio@vger.kernel.org -Description: - Filter clock factor for input Signal Y. This prescaler value - affects the inputs of both quadrature pair signals. - -What: /sys/bus/counter/devices/counterX/signalY/index_polarity -KernelVersion: 5.2 -Contact: linux-iio@vger.kernel.org -Description: - Active level of index input Signal Y; irrelevant in - non-synchronous load mode. - -What: /sys/bus/counter/devices/counterX/signalY/index_polarity_available -What: /sys/bus/counter/devices/counterX/signalY/synchronous_mode_available -KernelVersion: 5.2 -Contact: linux-iio@vger.kernel.org -Description: - Discrete set of available values for the respective Signal Y - configuration are listed in this file. - -What: /sys/bus/counter/devices/counterX/signalY/synchronous_mode -KernelVersion: 5.2 -Contact: linux-iio@vger.kernel.org -Description: - Configure the counter associated with Signal Y for - non-synchronous or synchronous load mode. Synchronous load mode - cannot be selected in non-quadrature (Pulse-Direction) clock - mode. - - non-synchronous: - A logic low level is the active level at this index - input. The index function (as enabled via preset_enable) - is performed directly on the active level of the index - input. - - synchronous: - Intended for interfacing with encoder Index output in - quadrature clock mode. The active level is configured - via index_polarity. The index function (as enabled via - preset_enable) is performed synchronously with the - quadrature clock on the active level of the index input. diff --git a/Documentation/ABI/testing/sysfs-bus-counter-ftm-quaddec b/Documentation/ABI/testing/sysfs-bus-counter-ftm-quaddec deleted file mode 100644 index 7d2e7b363467..000000000000 --- a/Documentation/ABI/testing/sysfs-bus-counter-ftm-quaddec +++ /dev/null @@ -1,16 +0,0 @@ -What: /sys/bus/counter/devices/counterX/countY/prescaler_available -KernelVersion: 5.2 -Contact: linux-iio@vger.kernel.org -Description: - Discrete set of available values for the respective Count Y - configuration are listed in this file. Values are delimited by - newline characters. - -What: /sys/bus/counter/devices/counterX/countY/prescaler -KernelVersion: 5.2 -Contact: linux-iio@vger.kernel.org -Description: - Configure the prescaler value associated with Count Y. - On the FlexTimer, the counter clock source passes through a - prescaler (i.e. a counter). This acts like a clock - divider. diff --git a/MAINTAINERS b/MAINTAINERS index 9c55fdcc1514..c34389e91931 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -299,7 +299,6 @@ M: William Breathitt Gray M: Syed Nayyar Waris L: linux-iio@vger.kernel.org S: Maintained -F: Documentation/ABI/testing/sysfs-bus-counter-104-quad-8 F: drivers/counter/104-quad-8.c ACCES PCI-IDIO-16 GPIO DRIVER @@ -4683,7 +4682,7 @@ COUNTER SUBSYSTEM M: William Breathitt Gray L: linux-iio@vger.kernel.org S: Maintained -F: Documentation/ABI/testing/sysfs-bus-counter* +F: Documentation/ABI/testing/sysfs-bus-counter F: Documentation/driver-api/generic-counter.rst F: drivers/counter/ F: include/linux/counter.h @@ -7094,7 +7093,6 @@ FLEXTIMER FTM-QUADDEC DRIVER M: Patrick Havelange L: linux-iio@vger.kernel.org S: Maintained -F: Documentation/ABI/testing/sysfs-bus-counter-ftm-quaddec F: Documentation/devicetree/bindings/counter/ftm-quaddec.txt F: drivers/counter/ftm-quaddec.c -- cgit v1.2.3-59-g8ed1b From 8de6b7edd493af5587e479afa85706d6fe9c5725 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Fri, 25 Jun 2021 09:45:53 +0200 Subject: phy: phy-hi3670-usb3: move driver from staging into phy The phy USB3 driver for Hisilicon 970 (hi3670) is ready for mainstream. Mode it from staging into the main driver's phy/ directory. Acked-by: Rob Herring Acked-By: Vinod Koul Signed-off-by: Mauro Carvalho Chehab Link: https://lore.kernel.org/r/dcf66a01aaeaab93cda52f9a283ecbdf9fa71bb8.1624606660.git.mchehab+huawei@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../bindings/phy/hisilicon,hi3670-usb3.yaml | 73 +++ MAINTAINERS | 9 +- drivers/phy/hisilicon/Kconfig | 10 + drivers/phy/hisilicon/Makefile | 1 + drivers/phy/hisilicon/phy-hi3670-usb3.c | 661 +++++++++++++++++++++ drivers/staging/hikey9xx/Kconfig | 11 - drivers/staging/hikey9xx/Makefile | 2 - drivers/staging/hikey9xx/phy-hi3670-usb3.c | 661 --------------------- drivers/staging/hikey9xx/phy-hi3670-usb3.yaml | 73 --- 9 files changed, 753 insertions(+), 748 deletions(-) create mode 100644 Documentation/devicetree/bindings/phy/hisilicon,hi3670-usb3.yaml create mode 100644 drivers/phy/hisilicon/phy-hi3670-usb3.c delete mode 100644 drivers/staging/hikey9xx/phy-hi3670-usb3.c delete mode 100644 drivers/staging/hikey9xx/phy-hi3670-usb3.yaml (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/phy/hisilicon,hi3670-usb3.yaml b/Documentation/devicetree/bindings/phy/hisilicon,hi3670-usb3.yaml new file mode 100644 index 000000000000..ebd78acfe2de --- /dev/null +++ b/Documentation/devicetree/bindings/phy/hisilicon,hi3670-usb3.yaml @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/hisilicon,hi3670-usb3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Hisilicon Kirin970 USB PHY + +maintainers: + - Mauro Carvalho Chehab + +description: |+ + Bindings for USB3 PHY on HiSilicon Kirin 970. + +properties: + compatible: + const: hisilicon,hi3670-usb-phy + + "#phy-cells": + const: 0 + + hisilicon,pericrg-syscon: + $ref: '/schemas/types.yaml#/definitions/phandle' + description: phandle of syscon used to control iso refclk. + + hisilicon,pctrl-syscon: + $ref: '/schemas/types.yaml#/definitions/phandle' + description: phandle of syscon used to control usb tcxo. + + hisilicon,sctrl-syscon: + $ref: '/schemas/types.yaml#/definitions/phandle' + description: phandle of syscon used to control phy deep sleep. + + hisilicon,eye-diagram-param: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Eye diagram for phy. + + hisilicon,tx-vboost-lvl: + $ref: /schemas/types.yaml#/definitions/uint32 + description: TX level vboost for phy. + +required: + - compatible + - hisilicon,pericrg-syscon + - hisilicon,pctrl-syscon + - hisilicon,sctrl-syscon + - hisilicon,eye-diagram-param + - hisilicon,tx-vboost-lvl + - "#phy-cells" + +additionalProperties: false + +examples: + - | + bus { + #address-cells = <2>; + #size-cells = <2>; + + usb3_otg_bc: usb3_otg_bc@ff200000 { + compatible = "syscon", "simple-mfd"; + reg = <0x0 0xff200000 0x0 0x1000>; + + usb_phy { + compatible = "hisilicon,hi3670-usb-phy"; + #phy-cells = <0>; + hisilicon,pericrg-syscon = <&crg_ctrl>; + hisilicon,pctrl-syscon = <&pctrl>; + hisilicon,sctrl-syscon = <&sctrl>; + hisilicon,eye-diagram-param = <0xfdfee4>; + hisilicon,tx-vboost-lvl = <0x5>; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 9bf553e53f0f..c5730484254c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18878,7 +18878,7 @@ L: linux-usb@vger.kernel.org S: Maintained F: drivers/usb/roles/intel-xhci-usb-role-switch.c -USB IP DRIVER FOR HISILICON KIRIN +USB IP DRIVER FOR HISILICON KIRIN 960 M: Yu Chen M: Binghui Wang L: linux-usb@vger.kernel.org @@ -18886,6 +18886,13 @@ S: Maintained F: Documentation/devicetree/bindings/phy/hisilicon,hi3660-usb3.yaml F: drivers/phy/hisilicon/phy-hi3660-usb3.c +USB IP DRIVER FOR HISILICON KIRIN 970 +M: Mauro Carvalho Chehab +L: linux-usb@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/phy/hisilicon,hi3670-usb3.yaml +F: drivers/phy/hisilicon/phy-kirin970-usb3.c + USB ISP116X DRIVER M: Olav Kongas L: linux-usb@vger.kernel.org diff --git a/drivers/phy/hisilicon/Kconfig b/drivers/phy/hisilicon/Kconfig index 1c73053bcc98..4d008cfc279c 100644 --- a/drivers/phy/hisilicon/Kconfig +++ b/drivers/phy/hisilicon/Kconfig @@ -23,6 +23,16 @@ config PHY_HI3660_USB To compile this driver as a module, choose M here. +config PHY_HI3670_USB + tristate "hi3670 USB PHY support" + depends on (ARCH_HISI && ARM64) || COMPILE_TEST + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the HISILICON HI3670 USB PHY. + + To compile this driver as a module, choose M here. + config PHY_HISTB_COMBPHY tristate "HiSilicon STB SoCs COMBPHY support" depends on (ARCH_HISI && ARM64) || COMPILE_TEST diff --git a/drivers/phy/hisilicon/Makefile b/drivers/phy/hisilicon/Makefile index 92e874ae9c74..51729868145b 100644 --- a/drivers/phy/hisilicon/Makefile +++ b/drivers/phy/hisilicon/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_PHY_HI6220_USB) += phy-hi6220-usb.o obj-$(CONFIG_PHY_HI3660_USB) += phy-hi3660-usb3.o +obj-$(CONFIG_PHY_HI3670_USB) += phy-hi3670-usb3.o obj-$(CONFIG_PHY_HISTB_COMBPHY) += phy-histb-combphy.o obj-$(CONFIG_PHY_HISI_INNO_USB2) += phy-hisi-inno-usb2.o obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o diff --git a/drivers/phy/hisilicon/phy-hi3670-usb3.c b/drivers/phy/hisilicon/phy-hi3670-usb3.c new file mode 100644 index 000000000000..b9ffe08abaab --- /dev/null +++ b/drivers/phy/hisilicon/phy-hi3670-usb3.c @@ -0,0 +1,661 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Phy provider for USB 3.1 controller on HiSilicon Kirin970 platform + * + * Copyright (C) 2017-2020 Hilisicon Electronics Co., Ltd. + * http://www.huawei.com + * + * Authors: Yu Chen + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SCTRL_SCDEEPSLEEPED (0x0) +#define USB_CLK_SELECTED BIT(20) + +#define PERI_CRG_PEREN0 (0x00) +#define PERI_CRG_PERDIS0 (0x04) +#define PERI_CRG_PEREN4 (0x40) +#define PERI_CRG_PERDIS4 (0x44) +#define PERI_CRG_PERRSTEN4 (0x90) +#define PERI_CRG_PERRSTDIS4 (0x94) +#define PERI_CRG_ISODIS (0x148) +#define PERI_CRG_PEREN6 (0x410) +#define PERI_CRG_PERDIS6 (0x414) + +#define USB_REFCLK_ISO_EN BIT(25) + +#define GT_CLK_USB2PHY_REF BIT(19) + +#define PCTRL_PERI_CTRL3 (0x10) +#define PCTRL_PERI_CTRL3_MSK_START (16) +#define USB_TCXO_EN BIT(1) + +#define PCTRL_PERI_CTRL24 (0x64) +#define SC_CLK_USB3PHY_3MUX1_SEL BIT(25) + +#define USB3OTG_CTRL0 (0x00) +#define USB3OTG_CTRL3 (0x0c) +#define USB3OTG_CTRL4 (0x10) +#define USB3OTG_CTRL5 (0x14) +#define USB3OTG_CTRL7 (0x1c) +#define USB_MISC_CFG50 (0x50) +#define USB_MISC_CFG54 (0x54) +#define USB_MISC_CFG58 (0x58) +#define USB_MISC_CFG5C (0x5c) +#define USB_MISC_CFGA0 (0xa0) +#define TCA_CLK_RST (0x200) +#define TCA_INTR_EN (0x204) +#define TCA_INTR_STS (0x208) +#define TCA_GCFG (0x210) +#define TCA_TCPC (0x214) +#define TCA_SYSMODE_CFG (0x218) +#define TCA_VBUS_CTRL (0x240) + +#define CTRL0_USB3_VBUSVLD BIT(7) +#define CTRL0_USB3_VBUSVLD_SEL BIT(6) + +#define CTRL3_USB2_VBUSVLDEXT0 BIT(6) +#define CTRL3_USB2_VBUSVLDEXTSEL0 BIT(5) + +#define CTRL5_USB2_SIDDQ BIT(0) + +#define CTRL7_USB2_REFCLKSEL_MASK GENMASK(4, 3) +#define CTRL7_USB2_REFCLKSEL_ABB (BIT(4) | BIT(3)) +#define CTRL7_USB2_REFCLKSEL_PAD BIT(4) + +#define CFG50_USB3_PHY_TEST_POWERDOWN BIT(23) + +#define CFG54_USB31PHY_CR_ADDR_MASK GENMASK(31, 16) + +#define CFG54_USB3PHY_REF_USE_PAD BIT(12) +#define CFG54_PHY0_PMA_PWR_STABLE BIT(11) +#define CFG54_PHY0_PCS_PWR_STABLE BIT(9) +#define CFG54_USB31PHY_CR_ACK BIT(7) +#define CFG54_USB31PHY_CR_WR_EN BIT(5) +#define CFG54_USB31PHY_CR_SEL BIT(4) +#define CFG54_USB31PHY_CR_RD_EN BIT(3) +#define CFG54_USB31PHY_CR_CLK BIT(2) +#define CFG54_USB3_PHY0_ANA_PWR_EN BIT(1) + +#define CFG58_USB31PHY_CR_DATA_MASK GENMASK(31, 16) + +#define CFG5C_USB3_PHY0_SS_MPLLA_SSC_EN BIT(1) + +#define CFGA0_VAUX_RESET BIT(9) +#define CFGA0_USB31C_RESET BIT(8) +#define CFGA0_USB2PHY_REFCLK_SELECT BIT(4) +#define CFGA0_USB3PHY_RESET BIT(1) +#define CFGA0_USB2PHY_POR BIT(0) + +#define INTR_EN_XA_TIMEOUT_EVT_EN BIT(1) +#define INTR_EN_XA_ACK_EVT_EN BIT(0) + +#define CLK_RST_TCA_REF_CLK_EN BIT(1) +#define CLK_RST_SUSPEND_CLK_EN BIT(0) + +#define GCFG_ROLE_HSTDEV BIT(4) +#define GCFG_OP_MODE GENMASK(1, 0) +#define GCFG_OP_MODE_CTRL_SYNC_MODE BIT(0) + +#define TCPC_VALID BIT(4) +#define TCPC_LOW_POWER_EN BIT(3) +#define TCPC_MUX_CONTROL_MASK GENMASK(1, 0) +#define TCPC_MUX_CONTROL_USB31 BIT(0) + +#define SYSMODE_CFG_TYPEC_DISABLE BIT(3) + +#define VBUS_CTRL_POWERPRESENT_OVERRD GENMASK(3, 2) +#define VBUS_CTRL_VBUSVALID_OVERRD GENMASK(1, 0) + +#define KIRIN970_USB_DEFAULT_PHY_PARAM (0xfdfee4) +#define KIRIN970_USB_DEFAULT_PHY_VBOOST (0x5) + +#define TX_VBOOST_LVL_REG (0xf) +#define TX_VBOOST_LVL_START (6) +#define TX_VBOOST_LVL_ENABLE BIT(9) + +struct hi3670_priv { + struct device *dev; + struct regmap *peri_crg; + struct regmap *pctrl; + struct regmap *sctrl; + struct regmap *usb31misc; + + u32 eye_diagram_param; + u32 tx_vboost_lvl; + + u32 peri_crg_offset; + u32 pctrl_offset; + u32 usb31misc_offset; +}; + +static int hi3670_phy_cr_clk(struct regmap *usb31misc) +{ + int ret; + + /* Clock up */ + ret = regmap_update_bits(usb31misc, USB_MISC_CFG54, + CFG54_USB31PHY_CR_CLK, CFG54_USB31PHY_CR_CLK); + if (ret) + return ret; + + /* Clock down */ + return regmap_update_bits(usb31misc, USB_MISC_CFG54, + CFG54_USB31PHY_CR_CLK, 0); +} + +static int hi3670_phy_cr_set_sel(struct regmap *usb31misc) +{ + return regmap_update_bits(usb31misc, USB_MISC_CFG54, + CFG54_USB31PHY_CR_SEL, CFG54_USB31PHY_CR_SEL); +} + +static int hi3670_phy_cr_start(struct regmap *usb31misc, int direction) +{ + int ret, reg; + + if (direction) + reg = CFG54_USB31PHY_CR_WR_EN; + else + reg = CFG54_USB31PHY_CR_RD_EN; + + ret = regmap_update_bits(usb31misc, USB_MISC_CFG54, reg, reg); + + if (ret) + return ret; + + ret = hi3670_phy_cr_clk(usb31misc); + if (ret) + return ret; + + return regmap_update_bits(usb31misc, USB_MISC_CFG54, + CFG54_USB31PHY_CR_RD_EN | CFG54_USB31PHY_CR_WR_EN, 0); +} + +static int hi3670_phy_cr_wait_ack(struct regmap *usb31misc) +{ + u32 reg; + int retry = 10; + int ret; + + while (retry-- > 0) { + ret = regmap_read(usb31misc, USB_MISC_CFG54, ®); + if (ret) + return ret; + if ((reg & CFG54_USB31PHY_CR_ACK) == CFG54_USB31PHY_CR_ACK) + return 0; + + ret = hi3670_phy_cr_clk(usb31misc); + if (ret) + return ret; + + usleep_range(10, 20); + } + + return -ETIMEDOUT; +} + +static int hi3670_phy_cr_set_addr(struct regmap *usb31misc, u32 addr) +{ + u32 reg; + int ret; + + ret = regmap_read(usb31misc, USB_MISC_CFG54, ®); + if (ret) + return ret; + + reg = FIELD_PREP(CFG54_USB31PHY_CR_ADDR_MASK, addr); + + return regmap_update_bits(usb31misc, USB_MISC_CFG54, + CFG54_USB31PHY_CR_ADDR_MASK, reg); +} + +static int hi3670_phy_cr_read(struct regmap *usb31misc, u32 addr, u32 *val) +{ + int reg, i, ret; + + for (i = 0; i < 100; i++) { + ret = hi3670_phy_cr_clk(usb31misc); + if (ret) + return ret; + } + + ret = hi3670_phy_cr_set_sel(usb31misc); + if (ret) + return ret; + + ret = hi3670_phy_cr_set_addr(usb31misc, addr); + if (ret) + return ret; + + ret = hi3670_phy_cr_start(usb31misc, 0); + if (ret) + return ret; + + ret = hi3670_phy_cr_wait_ack(usb31misc); + if (ret) + return ret; + + ret = regmap_read(usb31misc, USB_MISC_CFG58, ®); + if (ret) + return ret; + + *val = FIELD_GET(CFG58_USB31PHY_CR_DATA_MASK, reg); + + return 0; +} + +static int hi3670_phy_cr_write(struct regmap *usb31misc, u32 addr, u32 val) +{ + int i; + int ret; + + for (i = 0; i < 100; i++) { + ret = hi3670_phy_cr_clk(usb31misc); + if (ret) + return ret; + } + + ret = hi3670_phy_cr_set_sel(usb31misc); + if (ret) + return ret; + + ret = hi3670_phy_cr_set_addr(usb31misc, addr); + if (ret) + return ret; + + ret = regmap_write(usb31misc, USB_MISC_CFG58, + FIELD_PREP(CFG58_USB31PHY_CR_DATA_MASK, val)); + if (ret) + return ret; + + ret = hi3670_phy_cr_start(usb31misc, 1); + if (ret) + return ret; + + return hi3670_phy_cr_wait_ack(usb31misc); +} + +static int hi3670_phy_set_params(struct hi3670_priv *priv) +{ + u32 reg; + int ret; + int retry = 3; + + ret = regmap_write(priv->usb31misc, USB3OTG_CTRL4, + priv->eye_diagram_param); + if (ret) { + dev_err(priv->dev, "set USB3OTG_CTRL4 failed\n"); + return ret; + } + + while (retry-- > 0) { + ret = hi3670_phy_cr_read(priv->usb31misc, + TX_VBOOST_LVL_REG, ®); + if (!ret) + break; + + if (ret != -ETIMEDOUT) { + dev_err(priv->dev, "read TX_VBOOST_LVL_REG failed\n"); + return ret; + } + } + if (ret) + return ret; + + reg |= (TX_VBOOST_LVL_ENABLE | (priv->tx_vboost_lvl << TX_VBOOST_LVL_START)); + ret = hi3670_phy_cr_write(priv->usb31misc, TX_VBOOST_LVL_REG, reg); + if (ret) + dev_err(priv->dev, "write TX_VBOOST_LVL_REG failed\n"); + + return ret; +} + +static bool hi3670_is_abbclk_selected(struct hi3670_priv *priv) +{ + u32 reg; + + if (!priv->sctrl) { + dev_err(priv->dev, "priv->sctrl is null!\n"); + return false; + } + + if (regmap_read(priv->sctrl, SCTRL_SCDEEPSLEEPED, ®)) { + dev_err(priv->dev, "SCTRL_SCDEEPSLEEPED read failed!\n"); + return false; + } + + if ((reg & USB_CLK_SELECTED) == 0) + return false; + + return true; +} + +static int hi3670_config_phy_clock(struct hi3670_priv *priv) +{ + u32 val, mask; + int ret; + + if (!hi3670_is_abbclk_selected(priv)) { + /* usb refclk iso disable */ + ret = regmap_write(priv->peri_crg, PERI_CRG_ISODIS, + USB_REFCLK_ISO_EN); + if (ret) + goto out; + + /* enable usb_tcxo_en */ + ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3, + USB_TCXO_EN | + (USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START)); + + /* select usbphy clk from abb */ + mask = SC_CLK_USB3PHY_3MUX1_SEL; + ret = regmap_update_bits(priv->pctrl, + PCTRL_PERI_CTRL24, mask, 0); + if (ret) + goto out; + + ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, + CFGA0_USB2PHY_REFCLK_SELECT, 0); + if (ret) + goto out; + + ret = regmap_read(priv->usb31misc, USB3OTG_CTRL7, &val); + if (ret) + goto out; + val &= ~CTRL7_USB2_REFCLKSEL_MASK; + val |= CTRL7_USB2_REFCLKSEL_ABB; + ret = regmap_write(priv->usb31misc, USB3OTG_CTRL7, val); + if (ret) + goto out; + + return 0; + } + + ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG54, + CFG54_USB3PHY_REF_USE_PAD, + CFG54_USB3PHY_REF_USE_PAD); + if (ret) + goto out; + + ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, + CFGA0_USB2PHY_REFCLK_SELECT, + CFGA0_USB2PHY_REFCLK_SELECT); + if (ret) + goto out; + + ret = regmap_read(priv->usb31misc, USB3OTG_CTRL7, &val); + if (ret) + goto out; + val &= ~CTRL7_USB2_REFCLKSEL_MASK; + val |= CTRL7_USB2_REFCLKSEL_PAD; + ret = regmap_write(priv->usb31misc, USB3OTG_CTRL7, val); + if (ret) + goto out; + + ret = regmap_write(priv->peri_crg, + PERI_CRG_PEREN6, GT_CLK_USB2PHY_REF); + if (ret) + goto out; + + return 0; +out: + dev_err(priv->dev, "failed to config phy clock ret: %d\n", ret); + return ret; +} + +static int hi3670_config_tca(struct hi3670_priv *priv) +{ + u32 val, mask; + int ret; + + ret = regmap_write(priv->usb31misc, TCA_INTR_STS, 0xffff); + if (ret) + goto out; + + ret = regmap_write(priv->usb31misc, TCA_INTR_EN, + INTR_EN_XA_TIMEOUT_EVT_EN | INTR_EN_XA_ACK_EVT_EN); + if (ret) + goto out; + + mask = CLK_RST_TCA_REF_CLK_EN | CLK_RST_SUSPEND_CLK_EN; + ret = regmap_update_bits(priv->usb31misc, TCA_CLK_RST, mask, 0); + if (ret) + goto out; + + ret = regmap_update_bits(priv->usb31misc, TCA_GCFG, + GCFG_ROLE_HSTDEV | GCFG_OP_MODE, + GCFG_ROLE_HSTDEV | GCFG_OP_MODE_CTRL_SYNC_MODE); + if (ret) + goto out; + + ret = regmap_update_bits(priv->usb31misc, TCA_SYSMODE_CFG, + SYSMODE_CFG_TYPEC_DISABLE, 0); + if (ret) + goto out; + + ret = regmap_read(priv->usb31misc, TCA_TCPC, &val); + if (ret) + goto out; + val &= ~(TCPC_VALID | TCPC_LOW_POWER_EN | TCPC_MUX_CONTROL_MASK); + val |= (TCPC_VALID | TCPC_MUX_CONTROL_USB31); + ret = regmap_write(priv->usb31misc, TCA_TCPC, val); + if (ret) + goto out; + + ret = regmap_write(priv->usb31misc, TCA_VBUS_CTRL, + VBUS_CTRL_POWERPRESENT_OVERRD | VBUS_CTRL_VBUSVALID_OVERRD); + if (ret) + goto out; + + return 0; +out: + dev_err(priv->dev, "failed to config phy clock ret: %d\n", ret); + return ret; +} + +static int hi3670_phy_init(struct phy *phy) +{ + struct hi3670_priv *priv = phy_get_drvdata(phy); + u32 val; + int ret; + + /* assert controller */ + val = CFGA0_VAUX_RESET | CFGA0_USB31C_RESET | + CFGA0_USB3PHY_RESET | CFGA0_USB2PHY_POR; + ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, val, 0); + if (ret) + goto out; + + ret = hi3670_config_phy_clock(priv); + if (ret) + goto out; + + /* Exit from IDDQ mode */ + ret = regmap_update_bits(priv->usb31misc, USB3OTG_CTRL5, + CTRL5_USB2_SIDDQ, 0); + if (ret) + goto out; + + /* Release USB31 PHY out of TestPowerDown mode */ + ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG50, + CFG50_USB3_PHY_TEST_POWERDOWN, 0); + if (ret) + goto out; + + /* Deassert phy */ + val = CFGA0_USB3PHY_RESET | CFGA0_USB2PHY_POR; + ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, val, val); + if (ret) + goto out; + + usleep_range(100, 120); + + /* Tell the PHY power is stable */ + val = CFG54_USB3_PHY0_ANA_PWR_EN | CFG54_PHY0_PCS_PWR_STABLE | + CFG54_PHY0_PMA_PWR_STABLE; + ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG54, + val, val); + if (ret) + goto out; + + ret = hi3670_config_tca(priv); + if (ret) + goto out; + + /* Enable SSC */ + ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG5C, + CFG5C_USB3_PHY0_SS_MPLLA_SSC_EN, + CFG5C_USB3_PHY0_SS_MPLLA_SSC_EN); + if (ret) + goto out; + + /* Deassert controller */ + val = CFGA0_VAUX_RESET | CFGA0_USB31C_RESET; + ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, val, val); + if (ret) + goto out; + + usleep_range(100, 120); + + /* Set fake vbus valid signal */ + val = CTRL0_USB3_VBUSVLD | CTRL0_USB3_VBUSVLD_SEL; + ret = regmap_update_bits(priv->usb31misc, USB3OTG_CTRL0, val, val); + if (ret) + goto out; + + val = CTRL3_USB2_VBUSVLDEXT0 | CTRL3_USB2_VBUSVLDEXTSEL0; + ret = regmap_update_bits(priv->usb31misc, USB3OTG_CTRL3, val, val); + if (ret) + goto out; + + usleep_range(100, 120); + + ret = hi3670_phy_set_params(priv); + if (ret) + goto out; + + return 0; +out: + dev_err(priv->dev, "failed to init phy ret: %d\n", ret); + return ret; +} + +static int hi3670_phy_exit(struct phy *phy) +{ + struct hi3670_priv *priv = phy_get_drvdata(phy); + u32 mask; + int ret; + + /* Assert phy */ + mask = CFGA0_USB3PHY_RESET | CFGA0_USB2PHY_POR; + ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, mask, 0); + if (ret) + goto out; + + if (!hi3670_is_abbclk_selected(priv)) { + /* disable usb_tcxo_en */ + ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3, + USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START); + } else { + ret = regmap_write(priv->peri_crg, PERI_CRG_PERDIS6, + GT_CLK_USB2PHY_REF); + if (ret) + goto out; + } + + return 0; +out: + dev_err(priv->dev, "failed to exit phy ret: %d\n", ret); + return ret; +} + +static const struct phy_ops hi3670_phy_ops = { + .init = hi3670_phy_init, + .exit = hi3670_phy_exit, + .owner = THIS_MODULE, +}; + +static int hi3670_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct phy *phy; + struct hi3670_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->peri_crg = syscon_regmap_lookup_by_phandle(dev->of_node, + "hisilicon,pericrg-syscon"); + if (IS_ERR(priv->peri_crg)) { + dev_err(dev, "no hisilicon,pericrg-syscon\n"); + return PTR_ERR(priv->peri_crg); + } + + priv->pctrl = syscon_regmap_lookup_by_phandle(dev->of_node, + "hisilicon,pctrl-syscon"); + if (IS_ERR(priv->pctrl)) { + dev_err(dev, "no hisilicon,pctrl-syscon\n"); + return PTR_ERR(priv->pctrl); + } + + priv->sctrl = syscon_regmap_lookup_by_phandle(dev->of_node, + "hisilicon,sctrl-syscon"); + if (IS_ERR(priv->sctrl)) { + dev_err(dev, "no hisilicon,sctrl-syscon\n"); + return PTR_ERR(priv->sctrl); + } + + /* node of hi3670 phy is a sub-node of usb3_otg_bc */ + priv->usb31misc = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(priv->usb31misc)) { + dev_err(dev, "no hisilicon,usb3-otg-bc-syscon\n"); + return PTR_ERR(priv->usb31misc); + } + + if (of_property_read_u32(dev->of_node, "hisilicon,eye-diagram-param", + &priv->eye_diagram_param)) + priv->eye_diagram_param = KIRIN970_USB_DEFAULT_PHY_PARAM; + + if (of_property_read_u32(dev->of_node, "hisilicon,tx-vboost-lvl", + &priv->tx_vboost_lvl)) + priv->tx_vboost_lvl = KIRIN970_USB_DEFAULT_PHY_VBOOST; + + phy = devm_phy_create(dev, NULL, &hi3670_phy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_drvdata(phy, priv); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id hi3670_phy_of_match[] = { + { .compatible = "hisilicon,hi3670-usb-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, hi3670_phy_of_match); + +static struct platform_driver hi3670_phy_driver = { + .probe = hi3670_phy_probe, + .driver = { + .name = "hi3670-usb-phy", + .of_match_table = hi3670_phy_of_match, + } +}; +module_platform_driver(hi3670_phy_driver); + +MODULE_AUTHOR("Yu Chen "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Hilisicon Kirin970 USB31 PHY Driver"); diff --git a/drivers/staging/hikey9xx/Kconfig b/drivers/staging/hikey9xx/Kconfig index c4dc1016edf2..3ceda03f2864 100644 --- a/drivers/staging/hikey9xx/Kconfig +++ b/drivers/staging/hikey9xx/Kconfig @@ -1,16 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -# to be placed at drivers/phy -config PHY_HI3670_USB - tristate "hi3670 USB PHY support" - depends on (ARCH_HISI && ARM64) || COMPILE_TEST - select GENERIC_PHY - select MFD_SYSCON - help - Enable this to support the HISILICON HI3670 USB PHY. - - To compile this driver as a module, choose M here. - # to be placed at drivers/spmi config SPMI_HISI3670 tristate "Hisilicon 3670 SPMI Controller" diff --git a/drivers/staging/hikey9xx/Makefile b/drivers/staging/hikey9xx/Makefile index 9103735d8377..42d486104525 100644 --- a/drivers/staging/hikey9xx/Makefile +++ b/drivers/staging/hikey9xx/Makefile @@ -1,6 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_PHY_HI3670_USB) += phy-hi3670-usb3.o - obj-$(CONFIG_SPMI_HISI3670) += hisi-spmi-controller.o obj-$(CONFIG_MFD_HI6421_SPMI) += hi6421-spmi-pmic.o diff --git a/drivers/staging/hikey9xx/phy-hi3670-usb3.c b/drivers/staging/hikey9xx/phy-hi3670-usb3.c deleted file mode 100644 index b9ffe08abaab..000000000000 --- a/drivers/staging/hikey9xx/phy-hi3670-usb3.c +++ /dev/null @@ -1,661 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Phy provider for USB 3.1 controller on HiSilicon Kirin970 platform - * - * Copyright (C) 2017-2020 Hilisicon Electronics Co., Ltd. - * http://www.huawei.com - * - * Authors: Yu Chen - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SCTRL_SCDEEPSLEEPED (0x0) -#define USB_CLK_SELECTED BIT(20) - -#define PERI_CRG_PEREN0 (0x00) -#define PERI_CRG_PERDIS0 (0x04) -#define PERI_CRG_PEREN4 (0x40) -#define PERI_CRG_PERDIS4 (0x44) -#define PERI_CRG_PERRSTEN4 (0x90) -#define PERI_CRG_PERRSTDIS4 (0x94) -#define PERI_CRG_ISODIS (0x148) -#define PERI_CRG_PEREN6 (0x410) -#define PERI_CRG_PERDIS6 (0x414) - -#define USB_REFCLK_ISO_EN BIT(25) - -#define GT_CLK_USB2PHY_REF BIT(19) - -#define PCTRL_PERI_CTRL3 (0x10) -#define PCTRL_PERI_CTRL3_MSK_START (16) -#define USB_TCXO_EN BIT(1) - -#define PCTRL_PERI_CTRL24 (0x64) -#define SC_CLK_USB3PHY_3MUX1_SEL BIT(25) - -#define USB3OTG_CTRL0 (0x00) -#define USB3OTG_CTRL3 (0x0c) -#define USB3OTG_CTRL4 (0x10) -#define USB3OTG_CTRL5 (0x14) -#define USB3OTG_CTRL7 (0x1c) -#define USB_MISC_CFG50 (0x50) -#define USB_MISC_CFG54 (0x54) -#define USB_MISC_CFG58 (0x58) -#define USB_MISC_CFG5C (0x5c) -#define USB_MISC_CFGA0 (0xa0) -#define TCA_CLK_RST (0x200) -#define TCA_INTR_EN (0x204) -#define TCA_INTR_STS (0x208) -#define TCA_GCFG (0x210) -#define TCA_TCPC (0x214) -#define TCA_SYSMODE_CFG (0x218) -#define TCA_VBUS_CTRL (0x240) - -#define CTRL0_USB3_VBUSVLD BIT(7) -#define CTRL0_USB3_VBUSVLD_SEL BIT(6) - -#define CTRL3_USB2_VBUSVLDEXT0 BIT(6) -#define CTRL3_USB2_VBUSVLDEXTSEL0 BIT(5) - -#define CTRL5_USB2_SIDDQ BIT(0) - -#define CTRL7_USB2_REFCLKSEL_MASK GENMASK(4, 3) -#define CTRL7_USB2_REFCLKSEL_ABB (BIT(4) | BIT(3)) -#define CTRL7_USB2_REFCLKSEL_PAD BIT(4) - -#define CFG50_USB3_PHY_TEST_POWERDOWN BIT(23) - -#define CFG54_USB31PHY_CR_ADDR_MASK GENMASK(31, 16) - -#define CFG54_USB3PHY_REF_USE_PAD BIT(12) -#define CFG54_PHY0_PMA_PWR_STABLE BIT(11) -#define CFG54_PHY0_PCS_PWR_STABLE BIT(9) -#define CFG54_USB31PHY_CR_ACK BIT(7) -#define CFG54_USB31PHY_CR_WR_EN BIT(5) -#define CFG54_USB31PHY_CR_SEL BIT(4) -#define CFG54_USB31PHY_CR_RD_EN BIT(3) -#define CFG54_USB31PHY_CR_CLK BIT(2) -#define CFG54_USB3_PHY0_ANA_PWR_EN BIT(1) - -#define CFG58_USB31PHY_CR_DATA_MASK GENMASK(31, 16) - -#define CFG5C_USB3_PHY0_SS_MPLLA_SSC_EN BIT(1) - -#define CFGA0_VAUX_RESET BIT(9) -#define CFGA0_USB31C_RESET BIT(8) -#define CFGA0_USB2PHY_REFCLK_SELECT BIT(4) -#define CFGA0_USB3PHY_RESET BIT(1) -#define CFGA0_USB2PHY_POR BIT(0) - -#define INTR_EN_XA_TIMEOUT_EVT_EN BIT(1) -#define INTR_EN_XA_ACK_EVT_EN BIT(0) - -#define CLK_RST_TCA_REF_CLK_EN BIT(1) -#define CLK_RST_SUSPEND_CLK_EN BIT(0) - -#define GCFG_ROLE_HSTDEV BIT(4) -#define GCFG_OP_MODE GENMASK(1, 0) -#define GCFG_OP_MODE_CTRL_SYNC_MODE BIT(0) - -#define TCPC_VALID BIT(4) -#define TCPC_LOW_POWER_EN BIT(3) -#define TCPC_MUX_CONTROL_MASK GENMASK(1, 0) -#define TCPC_MUX_CONTROL_USB31 BIT(0) - -#define SYSMODE_CFG_TYPEC_DISABLE BIT(3) - -#define VBUS_CTRL_POWERPRESENT_OVERRD GENMASK(3, 2) -#define VBUS_CTRL_VBUSVALID_OVERRD GENMASK(1, 0) - -#define KIRIN970_USB_DEFAULT_PHY_PARAM (0xfdfee4) -#define KIRIN970_USB_DEFAULT_PHY_VBOOST (0x5) - -#define TX_VBOOST_LVL_REG (0xf) -#define TX_VBOOST_LVL_START (6) -#define TX_VBOOST_LVL_ENABLE BIT(9) - -struct hi3670_priv { - struct device *dev; - struct regmap *peri_crg; - struct regmap *pctrl; - struct regmap *sctrl; - struct regmap *usb31misc; - - u32 eye_diagram_param; - u32 tx_vboost_lvl; - - u32 peri_crg_offset; - u32 pctrl_offset; - u32 usb31misc_offset; -}; - -static int hi3670_phy_cr_clk(struct regmap *usb31misc) -{ - int ret; - - /* Clock up */ - ret = regmap_update_bits(usb31misc, USB_MISC_CFG54, - CFG54_USB31PHY_CR_CLK, CFG54_USB31PHY_CR_CLK); - if (ret) - return ret; - - /* Clock down */ - return regmap_update_bits(usb31misc, USB_MISC_CFG54, - CFG54_USB31PHY_CR_CLK, 0); -} - -static int hi3670_phy_cr_set_sel(struct regmap *usb31misc) -{ - return regmap_update_bits(usb31misc, USB_MISC_CFG54, - CFG54_USB31PHY_CR_SEL, CFG54_USB31PHY_CR_SEL); -} - -static int hi3670_phy_cr_start(struct regmap *usb31misc, int direction) -{ - int ret, reg; - - if (direction) - reg = CFG54_USB31PHY_CR_WR_EN; - else - reg = CFG54_USB31PHY_CR_RD_EN; - - ret = regmap_update_bits(usb31misc, USB_MISC_CFG54, reg, reg); - - if (ret) - return ret; - - ret = hi3670_phy_cr_clk(usb31misc); - if (ret) - return ret; - - return regmap_update_bits(usb31misc, USB_MISC_CFG54, - CFG54_USB31PHY_CR_RD_EN | CFG54_USB31PHY_CR_WR_EN, 0); -} - -static int hi3670_phy_cr_wait_ack(struct regmap *usb31misc) -{ - u32 reg; - int retry = 10; - int ret; - - while (retry-- > 0) { - ret = regmap_read(usb31misc, USB_MISC_CFG54, ®); - if (ret) - return ret; - if ((reg & CFG54_USB31PHY_CR_ACK) == CFG54_USB31PHY_CR_ACK) - return 0; - - ret = hi3670_phy_cr_clk(usb31misc); - if (ret) - return ret; - - usleep_range(10, 20); - } - - return -ETIMEDOUT; -} - -static int hi3670_phy_cr_set_addr(struct regmap *usb31misc, u32 addr) -{ - u32 reg; - int ret; - - ret = regmap_read(usb31misc, USB_MISC_CFG54, ®); - if (ret) - return ret; - - reg = FIELD_PREP(CFG54_USB31PHY_CR_ADDR_MASK, addr); - - return regmap_update_bits(usb31misc, USB_MISC_CFG54, - CFG54_USB31PHY_CR_ADDR_MASK, reg); -} - -static int hi3670_phy_cr_read(struct regmap *usb31misc, u32 addr, u32 *val) -{ - int reg, i, ret; - - for (i = 0; i < 100; i++) { - ret = hi3670_phy_cr_clk(usb31misc); - if (ret) - return ret; - } - - ret = hi3670_phy_cr_set_sel(usb31misc); - if (ret) - return ret; - - ret = hi3670_phy_cr_set_addr(usb31misc, addr); - if (ret) - return ret; - - ret = hi3670_phy_cr_start(usb31misc, 0); - if (ret) - return ret; - - ret = hi3670_phy_cr_wait_ack(usb31misc); - if (ret) - return ret; - - ret = regmap_read(usb31misc, USB_MISC_CFG58, ®); - if (ret) - return ret; - - *val = FIELD_GET(CFG58_USB31PHY_CR_DATA_MASK, reg); - - return 0; -} - -static int hi3670_phy_cr_write(struct regmap *usb31misc, u32 addr, u32 val) -{ - int i; - int ret; - - for (i = 0; i < 100; i++) { - ret = hi3670_phy_cr_clk(usb31misc); - if (ret) - return ret; - } - - ret = hi3670_phy_cr_set_sel(usb31misc); - if (ret) - return ret; - - ret = hi3670_phy_cr_set_addr(usb31misc, addr); - if (ret) - return ret; - - ret = regmap_write(usb31misc, USB_MISC_CFG58, - FIELD_PREP(CFG58_USB31PHY_CR_DATA_MASK, val)); - if (ret) - return ret; - - ret = hi3670_phy_cr_start(usb31misc, 1); - if (ret) - return ret; - - return hi3670_phy_cr_wait_ack(usb31misc); -} - -static int hi3670_phy_set_params(struct hi3670_priv *priv) -{ - u32 reg; - int ret; - int retry = 3; - - ret = regmap_write(priv->usb31misc, USB3OTG_CTRL4, - priv->eye_diagram_param); - if (ret) { - dev_err(priv->dev, "set USB3OTG_CTRL4 failed\n"); - return ret; - } - - while (retry-- > 0) { - ret = hi3670_phy_cr_read(priv->usb31misc, - TX_VBOOST_LVL_REG, ®); - if (!ret) - break; - - if (ret != -ETIMEDOUT) { - dev_err(priv->dev, "read TX_VBOOST_LVL_REG failed\n"); - return ret; - } - } - if (ret) - return ret; - - reg |= (TX_VBOOST_LVL_ENABLE | (priv->tx_vboost_lvl << TX_VBOOST_LVL_START)); - ret = hi3670_phy_cr_write(priv->usb31misc, TX_VBOOST_LVL_REG, reg); - if (ret) - dev_err(priv->dev, "write TX_VBOOST_LVL_REG failed\n"); - - return ret; -} - -static bool hi3670_is_abbclk_selected(struct hi3670_priv *priv) -{ - u32 reg; - - if (!priv->sctrl) { - dev_err(priv->dev, "priv->sctrl is null!\n"); - return false; - } - - if (regmap_read(priv->sctrl, SCTRL_SCDEEPSLEEPED, ®)) { - dev_err(priv->dev, "SCTRL_SCDEEPSLEEPED read failed!\n"); - return false; - } - - if ((reg & USB_CLK_SELECTED) == 0) - return false; - - return true; -} - -static int hi3670_config_phy_clock(struct hi3670_priv *priv) -{ - u32 val, mask; - int ret; - - if (!hi3670_is_abbclk_selected(priv)) { - /* usb refclk iso disable */ - ret = regmap_write(priv->peri_crg, PERI_CRG_ISODIS, - USB_REFCLK_ISO_EN); - if (ret) - goto out; - - /* enable usb_tcxo_en */ - ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3, - USB_TCXO_EN | - (USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START)); - - /* select usbphy clk from abb */ - mask = SC_CLK_USB3PHY_3MUX1_SEL; - ret = regmap_update_bits(priv->pctrl, - PCTRL_PERI_CTRL24, mask, 0); - if (ret) - goto out; - - ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, - CFGA0_USB2PHY_REFCLK_SELECT, 0); - if (ret) - goto out; - - ret = regmap_read(priv->usb31misc, USB3OTG_CTRL7, &val); - if (ret) - goto out; - val &= ~CTRL7_USB2_REFCLKSEL_MASK; - val |= CTRL7_USB2_REFCLKSEL_ABB; - ret = regmap_write(priv->usb31misc, USB3OTG_CTRL7, val); - if (ret) - goto out; - - return 0; - } - - ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG54, - CFG54_USB3PHY_REF_USE_PAD, - CFG54_USB3PHY_REF_USE_PAD); - if (ret) - goto out; - - ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, - CFGA0_USB2PHY_REFCLK_SELECT, - CFGA0_USB2PHY_REFCLK_SELECT); - if (ret) - goto out; - - ret = regmap_read(priv->usb31misc, USB3OTG_CTRL7, &val); - if (ret) - goto out; - val &= ~CTRL7_USB2_REFCLKSEL_MASK; - val |= CTRL7_USB2_REFCLKSEL_PAD; - ret = regmap_write(priv->usb31misc, USB3OTG_CTRL7, val); - if (ret) - goto out; - - ret = regmap_write(priv->peri_crg, - PERI_CRG_PEREN6, GT_CLK_USB2PHY_REF); - if (ret) - goto out; - - return 0; -out: - dev_err(priv->dev, "failed to config phy clock ret: %d\n", ret); - return ret; -} - -static int hi3670_config_tca(struct hi3670_priv *priv) -{ - u32 val, mask; - int ret; - - ret = regmap_write(priv->usb31misc, TCA_INTR_STS, 0xffff); - if (ret) - goto out; - - ret = regmap_write(priv->usb31misc, TCA_INTR_EN, - INTR_EN_XA_TIMEOUT_EVT_EN | INTR_EN_XA_ACK_EVT_EN); - if (ret) - goto out; - - mask = CLK_RST_TCA_REF_CLK_EN | CLK_RST_SUSPEND_CLK_EN; - ret = regmap_update_bits(priv->usb31misc, TCA_CLK_RST, mask, 0); - if (ret) - goto out; - - ret = regmap_update_bits(priv->usb31misc, TCA_GCFG, - GCFG_ROLE_HSTDEV | GCFG_OP_MODE, - GCFG_ROLE_HSTDEV | GCFG_OP_MODE_CTRL_SYNC_MODE); - if (ret) - goto out; - - ret = regmap_update_bits(priv->usb31misc, TCA_SYSMODE_CFG, - SYSMODE_CFG_TYPEC_DISABLE, 0); - if (ret) - goto out; - - ret = regmap_read(priv->usb31misc, TCA_TCPC, &val); - if (ret) - goto out; - val &= ~(TCPC_VALID | TCPC_LOW_POWER_EN | TCPC_MUX_CONTROL_MASK); - val |= (TCPC_VALID | TCPC_MUX_CONTROL_USB31); - ret = regmap_write(priv->usb31misc, TCA_TCPC, val); - if (ret) - goto out; - - ret = regmap_write(priv->usb31misc, TCA_VBUS_CTRL, - VBUS_CTRL_POWERPRESENT_OVERRD | VBUS_CTRL_VBUSVALID_OVERRD); - if (ret) - goto out; - - return 0; -out: - dev_err(priv->dev, "failed to config phy clock ret: %d\n", ret); - return ret; -} - -static int hi3670_phy_init(struct phy *phy) -{ - struct hi3670_priv *priv = phy_get_drvdata(phy); - u32 val; - int ret; - - /* assert controller */ - val = CFGA0_VAUX_RESET | CFGA0_USB31C_RESET | - CFGA0_USB3PHY_RESET | CFGA0_USB2PHY_POR; - ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, val, 0); - if (ret) - goto out; - - ret = hi3670_config_phy_clock(priv); - if (ret) - goto out; - - /* Exit from IDDQ mode */ - ret = regmap_update_bits(priv->usb31misc, USB3OTG_CTRL5, - CTRL5_USB2_SIDDQ, 0); - if (ret) - goto out; - - /* Release USB31 PHY out of TestPowerDown mode */ - ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG50, - CFG50_USB3_PHY_TEST_POWERDOWN, 0); - if (ret) - goto out; - - /* Deassert phy */ - val = CFGA0_USB3PHY_RESET | CFGA0_USB2PHY_POR; - ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, val, val); - if (ret) - goto out; - - usleep_range(100, 120); - - /* Tell the PHY power is stable */ - val = CFG54_USB3_PHY0_ANA_PWR_EN | CFG54_PHY0_PCS_PWR_STABLE | - CFG54_PHY0_PMA_PWR_STABLE; - ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG54, - val, val); - if (ret) - goto out; - - ret = hi3670_config_tca(priv); - if (ret) - goto out; - - /* Enable SSC */ - ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG5C, - CFG5C_USB3_PHY0_SS_MPLLA_SSC_EN, - CFG5C_USB3_PHY0_SS_MPLLA_SSC_EN); - if (ret) - goto out; - - /* Deassert controller */ - val = CFGA0_VAUX_RESET | CFGA0_USB31C_RESET; - ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, val, val); - if (ret) - goto out; - - usleep_range(100, 120); - - /* Set fake vbus valid signal */ - val = CTRL0_USB3_VBUSVLD | CTRL0_USB3_VBUSVLD_SEL; - ret = regmap_update_bits(priv->usb31misc, USB3OTG_CTRL0, val, val); - if (ret) - goto out; - - val = CTRL3_USB2_VBUSVLDEXT0 | CTRL3_USB2_VBUSVLDEXTSEL0; - ret = regmap_update_bits(priv->usb31misc, USB3OTG_CTRL3, val, val); - if (ret) - goto out; - - usleep_range(100, 120); - - ret = hi3670_phy_set_params(priv); - if (ret) - goto out; - - return 0; -out: - dev_err(priv->dev, "failed to init phy ret: %d\n", ret); - return ret; -} - -static int hi3670_phy_exit(struct phy *phy) -{ - struct hi3670_priv *priv = phy_get_drvdata(phy); - u32 mask; - int ret; - - /* Assert phy */ - mask = CFGA0_USB3PHY_RESET | CFGA0_USB2PHY_POR; - ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, mask, 0); - if (ret) - goto out; - - if (!hi3670_is_abbclk_selected(priv)) { - /* disable usb_tcxo_en */ - ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3, - USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START); - } else { - ret = regmap_write(priv->peri_crg, PERI_CRG_PERDIS6, - GT_CLK_USB2PHY_REF); - if (ret) - goto out; - } - - return 0; -out: - dev_err(priv->dev, "failed to exit phy ret: %d\n", ret); - return ret; -} - -static const struct phy_ops hi3670_phy_ops = { - .init = hi3670_phy_init, - .exit = hi3670_phy_exit, - .owner = THIS_MODULE, -}; - -static int hi3670_phy_probe(struct platform_device *pdev) -{ - struct phy_provider *phy_provider; - struct device *dev = &pdev->dev; - struct phy *phy; - struct hi3670_priv *priv; - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->dev = dev; - priv->peri_crg = syscon_regmap_lookup_by_phandle(dev->of_node, - "hisilicon,pericrg-syscon"); - if (IS_ERR(priv->peri_crg)) { - dev_err(dev, "no hisilicon,pericrg-syscon\n"); - return PTR_ERR(priv->peri_crg); - } - - priv->pctrl = syscon_regmap_lookup_by_phandle(dev->of_node, - "hisilicon,pctrl-syscon"); - if (IS_ERR(priv->pctrl)) { - dev_err(dev, "no hisilicon,pctrl-syscon\n"); - return PTR_ERR(priv->pctrl); - } - - priv->sctrl = syscon_regmap_lookup_by_phandle(dev->of_node, - "hisilicon,sctrl-syscon"); - if (IS_ERR(priv->sctrl)) { - dev_err(dev, "no hisilicon,sctrl-syscon\n"); - return PTR_ERR(priv->sctrl); - } - - /* node of hi3670 phy is a sub-node of usb3_otg_bc */ - priv->usb31misc = syscon_node_to_regmap(dev->parent->of_node); - if (IS_ERR(priv->usb31misc)) { - dev_err(dev, "no hisilicon,usb3-otg-bc-syscon\n"); - return PTR_ERR(priv->usb31misc); - } - - if (of_property_read_u32(dev->of_node, "hisilicon,eye-diagram-param", - &priv->eye_diagram_param)) - priv->eye_diagram_param = KIRIN970_USB_DEFAULT_PHY_PARAM; - - if (of_property_read_u32(dev->of_node, "hisilicon,tx-vboost-lvl", - &priv->tx_vboost_lvl)) - priv->tx_vboost_lvl = KIRIN970_USB_DEFAULT_PHY_VBOOST; - - phy = devm_phy_create(dev, NULL, &hi3670_phy_ops); - if (IS_ERR(phy)) - return PTR_ERR(phy); - - phy_set_drvdata(phy, priv); - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id hi3670_phy_of_match[] = { - { .compatible = "hisilicon,hi3670-usb-phy" }, - { }, -}; -MODULE_DEVICE_TABLE(of, hi3670_phy_of_match); - -static struct platform_driver hi3670_phy_driver = { - .probe = hi3670_phy_probe, - .driver = { - .name = "hi3670-usb-phy", - .of_match_table = hi3670_phy_of_match, - } -}; -module_platform_driver(hi3670_phy_driver); - -MODULE_AUTHOR("Yu Chen "); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Hilisicon Kirin970 USB31 PHY Driver"); diff --git a/drivers/staging/hikey9xx/phy-hi3670-usb3.yaml b/drivers/staging/hikey9xx/phy-hi3670-usb3.yaml deleted file mode 100644 index ebd78acfe2de..000000000000 --- a/drivers/staging/hikey9xx/phy-hi3670-usb3.yaml +++ /dev/null @@ -1,73 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/phy/hisilicon,hi3670-usb3.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: Hisilicon Kirin970 USB PHY - -maintainers: - - Mauro Carvalho Chehab - -description: |+ - Bindings for USB3 PHY on HiSilicon Kirin 970. - -properties: - compatible: - const: hisilicon,hi3670-usb-phy - - "#phy-cells": - const: 0 - - hisilicon,pericrg-syscon: - $ref: '/schemas/types.yaml#/definitions/phandle' - description: phandle of syscon used to control iso refclk. - - hisilicon,pctrl-syscon: - $ref: '/schemas/types.yaml#/definitions/phandle' - description: phandle of syscon used to control usb tcxo. - - hisilicon,sctrl-syscon: - $ref: '/schemas/types.yaml#/definitions/phandle' - description: phandle of syscon used to control phy deep sleep. - - hisilicon,eye-diagram-param: - $ref: /schemas/types.yaml#/definitions/uint32 - description: Eye diagram for phy. - - hisilicon,tx-vboost-lvl: - $ref: /schemas/types.yaml#/definitions/uint32 - description: TX level vboost for phy. - -required: - - compatible - - hisilicon,pericrg-syscon - - hisilicon,pctrl-syscon - - hisilicon,sctrl-syscon - - hisilicon,eye-diagram-param - - hisilicon,tx-vboost-lvl - - "#phy-cells" - -additionalProperties: false - -examples: - - | - bus { - #address-cells = <2>; - #size-cells = <2>; - - usb3_otg_bc: usb3_otg_bc@ff200000 { - compatible = "syscon", "simple-mfd"; - reg = <0x0 0xff200000 0x0 0x1000>; - - usb_phy { - compatible = "hisilicon,hi3670-usb-phy"; - #phy-cells = <0>; - hisilicon,pericrg-syscon = <&crg_ctrl>; - hisilicon,pctrl-syscon = <&pctrl>; - hisilicon,sctrl-syscon = <&sctrl>; - hisilicon,eye-diagram-param = <0xfdfee4>; - hisilicon,tx-vboost-lvl = <0x5>; - }; - }; - }; -- cgit v1.2.3-59-g8ed1b From e562cf3aea3e1ea46566907f7627e5512840a2b4 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Fri, 25 Jun 2021 09:45:54 +0200 Subject: spmi: hisi-spmi-controller: move driver from staging The Hisilicon 6421v600 SPMI driver is ready for mainstream. So, move it from staging. Acked-by: Stephen Boyd Signed-off-by: Mauro Carvalho Chehab Link: https://lore.kernel.org/r/35b9f9169889c1f4d51eff8bf2035450c9e02576.1624606660.git.mchehab+huawei@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../spmi/hisilicon,hisi-spmi-controller.yaml | 73 ++++ MAINTAINERS | 7 + drivers/spmi/Kconfig | 9 + drivers/spmi/Makefile | 1 + drivers/spmi/hisi-spmi-controller.c | 367 +++++++++++++++++++++ drivers/staging/hikey9xx/Kconfig | 11 - drivers/staging/hikey9xx/Makefile | 1 - drivers/staging/hikey9xx/hisi-spmi-controller.c | 367 --------------------- .../hikey9xx/hisilicon,hi6421-spmi-pmic.yaml | 2 +- .../hikey9xx/hisilicon,hisi-spmi-controller.yaml | 73 ---- 10 files changed, 458 insertions(+), 453 deletions(-) create mode 100644 Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml create mode 100644 drivers/spmi/hisi-spmi-controller.c delete mode 100644 drivers/staging/hikey9xx/hisi-spmi-controller.c delete mode 100644 drivers/staging/hikey9xx/hisilicon,hisi-spmi-controller.yaml (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml b/Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml new file mode 100644 index 000000000000..f882903769f9 --- /dev/null +++ b/Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spmi/hisilicon,hisi-spmi-controller.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: HiSilicon SPMI controller + +maintainers: + - Mauro Carvalho Chehab + +description: | + The HiSilicon SPMI BUS controller is found on some Kirin-based designs. + It is a MIPI System Power Management (SPMI) controller. + + The PMIC part is provided by + ./Documentation/devicetree/bindings/mfd/hisilicon,hi6421-spmi-pmic.yaml. + +allOf: + - $ref: spmi.yaml# + +properties: + + $nodename: + pattern: "spmi@[0-9a-f]" + + compatible: + const: hisilicon,kirin970-spmi-controller + + reg: + maxItems: 1 + + hisilicon,spmi-channel: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + number of the Kirin 970 SPMI channel where the SPMI devices are connected. + +required: + - compatible + - reg + - hisilicon,spmi-channel + +patternProperties: + "@[0-9a-f]$": + type: object + + description: | + PMIC properties, which are specific to the used SPMI PMIC device(s). + When used in combination with HiSilicon 6421v600, the properties + are documented at + drivers/staging/hikey9xx/hisilicon,hi6421-spmi-pmic.yaml. + +unevaluatedProperties: false + +examples: + - | + bus { + #address-cells = <2>; + #size-cells = <2>; + + spmi: spmi@fff24000 { + compatible = "hisilicon,kirin970-spmi-controller"; + #address-cells = <2>; + #size-cells = <0>; + reg = <0x0 0xfff24000 0x0 0x1000>; + hisilicon,spmi-channel = <2>; + + pmic@0 { + reg = <0 0>; + /* pmic properties */ + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index c5730484254c..13339228f065 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8282,6 +8282,13 @@ S: Maintained W: http://www.hisilicon.com F: drivers/spi/spi-hisi-kunpeng.c +HISILICON SPMI CONTROLLER DRIVER FOR HIKEY 970 +M: Mauro Carvalho Chehab +L: linux-kernel@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml +F: drivers/spmi/hisi-spmi-controller.c + HISILICON STAGING DRIVERS FOR HIKEY 960/970 M: Mauro Carvalho Chehab S: Maintained diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig index a53bad541f1a..2874b6c26028 100644 --- a/drivers/spmi/Kconfig +++ b/drivers/spmi/Kconfig @@ -11,6 +11,15 @@ menuconfig SPMI if SPMI +config SPMI_HISI3670 + tristate "Hisilicon 3670 SPMI Controller" + select IRQ_DOMAIN_HIERARCHY + depends on HAS_IOMEM + help + If you say yes to this option, support will be included for the + built-in SPMI PMIC Arbiter interface on Hisilicon 3670 + processors. + config SPMI_MSM_PMIC_ARB tristate "Qualcomm MSM SPMI Controller (PMIC Arbiter)" select IRQ_DOMAIN_HIERARCHY diff --git a/drivers/spmi/Makefile b/drivers/spmi/Makefile index 55a94cadeffe..6e092e6f290c 100644 --- a/drivers/spmi/Makefile +++ b/drivers/spmi/Makefile @@ -4,4 +4,5 @@ # obj-$(CONFIG_SPMI) += spmi.o +obj-$(CONFIG_SPMI_HISI3670) += hisi-spmi-controller.o obj-$(CONFIG_SPMI_MSM_PMIC_ARB) += spmi-pmic-arb.o diff --git a/drivers/spmi/hisi-spmi-controller.c b/drivers/spmi/hisi-spmi-controller.c new file mode 100644 index 000000000000..5bd23262abd6 --- /dev/null +++ b/drivers/spmi/hisi-spmi-controller.c @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * SPMI register addr + */ +#define SPMI_CHANNEL_OFFSET 0x0300 +#define SPMI_SLAVE_OFFSET 0x20 + +#define SPMI_APB_SPMI_CMD_BASE_ADDR 0x0100 + +#define SPMI_APB_SPMI_WDATA0_BASE_ADDR 0x0104 +#define SPMI_APB_SPMI_WDATA1_BASE_ADDR 0x0108 +#define SPMI_APB_SPMI_WDATA2_BASE_ADDR 0x010c +#define SPMI_APB_SPMI_WDATA3_BASE_ADDR 0x0110 + +#define SPMI_APB_SPMI_STATUS_BASE_ADDR 0x0200 + +#define SPMI_APB_SPMI_RDATA0_BASE_ADDR 0x0204 +#define SPMI_APB_SPMI_RDATA1_BASE_ADDR 0x0208 +#define SPMI_APB_SPMI_RDATA2_BASE_ADDR 0x020c +#define SPMI_APB_SPMI_RDATA3_BASE_ADDR 0x0210 + +#define SPMI_PER_DATAREG_BYTE 4 +/* + * SPMI cmd register + */ +#define SPMI_APB_SPMI_CMD_EN BIT(31) +#define SPMI_APB_SPMI_CMD_TYPE_OFFSET 24 +#define SPMI_APB_SPMI_CMD_LENGTH_OFFSET 20 +#define SPMI_APB_SPMI_CMD_SLAVEID_OFFSET 16 +#define SPMI_APB_SPMI_CMD_ADDR_OFFSET 0 + +/* Command Opcodes */ + +enum spmi_controller_cmd_op_code { + SPMI_CMD_REG_ZERO_WRITE = 0, + SPMI_CMD_REG_WRITE = 1, + SPMI_CMD_REG_READ = 2, + SPMI_CMD_EXT_REG_WRITE = 3, + SPMI_CMD_EXT_REG_READ = 4, + SPMI_CMD_EXT_REG_WRITE_L = 5, + SPMI_CMD_EXT_REG_READ_L = 6, + SPMI_CMD_REG_RESET = 7, + SPMI_CMD_REG_SLEEP = 8, + SPMI_CMD_REG_SHUTDOWN = 9, + SPMI_CMD_REG_WAKEUP = 10, +}; + +/* + * SPMI status register + */ +#define SPMI_APB_TRANS_DONE BIT(0) +#define SPMI_APB_TRANS_FAIL BIT(2) + +/* Command register fields */ +#define SPMI_CONTROLLER_CMD_MAX_BYTE_COUNT 16 + +/* Maximum number of support PMIC peripherals */ +#define SPMI_CONTROLLER_TIMEOUT_US 1000 +#define SPMI_CONTROLLER_MAX_TRANS_BYTES 16 + +struct spmi_controller_dev { + struct spmi_controller *controller; + struct device *dev; + void __iomem *base; + spinlock_t lock; + u32 channel; +}; + +static int spmi_controller_wait_for_done(struct device *dev, + struct spmi_controller_dev *ctrl_dev, + void __iomem *base, u8 sid, u16 addr) +{ + u32 timeout = SPMI_CONTROLLER_TIMEOUT_US; + u32 status, offset; + + offset = SPMI_APB_SPMI_STATUS_BASE_ADDR; + offset += SPMI_CHANNEL_OFFSET * ctrl_dev->channel + SPMI_SLAVE_OFFSET * sid; + + do { + status = readl(base + offset); + + if (status & SPMI_APB_TRANS_DONE) { + if (status & SPMI_APB_TRANS_FAIL) { + dev_err(dev, "%s: transaction failed (0x%x)\n", + __func__, status); + return -EIO; + } + dev_dbg(dev, "%s: status 0x%x\n", __func__, status); + return 0; + } + udelay(1); + } while (timeout--); + + dev_err(dev, "%s: timeout, status 0x%x\n", __func__, status); + return -ETIMEDOUT; +} + +static int spmi_read_cmd(struct spmi_controller *ctrl, + u8 opc, u8 slave_id, u16 slave_addr, u8 *__buf, size_t bc) +{ + struct spmi_controller_dev *spmi_controller = dev_get_drvdata(&ctrl->dev); + u32 chnl_ofst = SPMI_CHANNEL_OFFSET * spmi_controller->channel; + unsigned long flags; + u8 *buf = __buf; + u32 cmd, data; + int rc; + u8 op_code, i; + + if (bc > SPMI_CONTROLLER_MAX_TRANS_BYTES) { + dev_err(&ctrl->dev, + "spmi_controller supports 1..%d bytes per trans, but:%zu requested\n", + SPMI_CONTROLLER_MAX_TRANS_BYTES, bc); + return -EINVAL; + } + + switch (opc) { + case SPMI_CMD_READ: + op_code = SPMI_CMD_REG_READ; + break; + case SPMI_CMD_EXT_READ: + op_code = SPMI_CMD_EXT_REG_READ; + break; + case SPMI_CMD_EXT_READL: + op_code = SPMI_CMD_EXT_REG_READ_L; + break; + default: + dev_err(&ctrl->dev, "invalid read cmd 0x%x\n", opc); + return -EINVAL; + } + + cmd = SPMI_APB_SPMI_CMD_EN | + (op_code << SPMI_APB_SPMI_CMD_TYPE_OFFSET) | + ((bc - 1) << SPMI_APB_SPMI_CMD_LENGTH_OFFSET) | + ((slave_id & 0xf) << SPMI_APB_SPMI_CMD_SLAVEID_OFFSET) | /* slvid */ + ((slave_addr & 0xffff) << SPMI_APB_SPMI_CMD_ADDR_OFFSET); /* slave_addr */ + + spin_lock_irqsave(&spmi_controller->lock, flags); + + writel(cmd, spmi_controller->base + chnl_ofst + SPMI_APB_SPMI_CMD_BASE_ADDR); + + rc = spmi_controller_wait_for_done(&ctrl->dev, spmi_controller, + spmi_controller->base, slave_id, slave_addr); + if (rc) + goto done; + + for (i = 0; bc > i * SPMI_PER_DATAREG_BYTE; i++) { + data = readl(spmi_controller->base + chnl_ofst + + SPMI_SLAVE_OFFSET * slave_id + + SPMI_APB_SPMI_RDATA0_BASE_ADDR + + i * SPMI_PER_DATAREG_BYTE); + data = be32_to_cpu((__be32 __force)data); + if ((bc - i * SPMI_PER_DATAREG_BYTE) >> 2) { + memcpy(buf, &data, sizeof(data)); + buf += sizeof(data); + } else { + memcpy(buf, &data, bc % SPMI_PER_DATAREG_BYTE); + buf += (bc % SPMI_PER_DATAREG_BYTE); + } + } + +done: + spin_unlock_irqrestore(&spmi_controller->lock, flags); + if (rc) + dev_err(&ctrl->dev, + "spmi read wait timeout op:0x%x slave_id:%d slave_addr:0x%x bc:%zu\n", + opc, slave_id, slave_addr, bc + 1); + else + dev_dbg(&ctrl->dev, "%s: id:%d slave_addr:0x%x, read value: %*ph\n", + __func__, slave_id, slave_addr, (int)bc, __buf); + + return rc; +} + +static int spmi_write_cmd(struct spmi_controller *ctrl, + u8 opc, u8 slave_id, u16 slave_addr, const u8 *__buf, size_t bc) +{ + struct spmi_controller_dev *spmi_controller = dev_get_drvdata(&ctrl->dev); + u32 chnl_ofst = SPMI_CHANNEL_OFFSET * spmi_controller->channel; + const u8 *buf = __buf; + unsigned long flags; + u32 cmd, data; + int rc; + u8 op_code, i; + + if (bc > SPMI_CONTROLLER_MAX_TRANS_BYTES) { + dev_err(&ctrl->dev, + "spmi_controller supports 1..%d bytes per trans, but:%zu requested\n", + SPMI_CONTROLLER_MAX_TRANS_BYTES, bc); + return -EINVAL; + } + + switch (opc) { + case SPMI_CMD_WRITE: + op_code = SPMI_CMD_REG_WRITE; + break; + case SPMI_CMD_EXT_WRITE: + op_code = SPMI_CMD_EXT_REG_WRITE; + break; + case SPMI_CMD_EXT_WRITEL: + op_code = SPMI_CMD_EXT_REG_WRITE_L; + break; + default: + dev_err(&ctrl->dev, "invalid write cmd 0x%x\n", opc); + return -EINVAL; + } + + cmd = SPMI_APB_SPMI_CMD_EN | + (op_code << SPMI_APB_SPMI_CMD_TYPE_OFFSET) | + ((bc - 1) << SPMI_APB_SPMI_CMD_LENGTH_OFFSET) | + ((slave_id & 0xf) << SPMI_APB_SPMI_CMD_SLAVEID_OFFSET) | + ((slave_addr & 0xffff) << SPMI_APB_SPMI_CMD_ADDR_OFFSET); + + /* Write data to FIFOs */ + spin_lock_irqsave(&spmi_controller->lock, flags); + + for (i = 0; bc > i * SPMI_PER_DATAREG_BYTE; i++) { + data = 0; + if ((bc - i * SPMI_PER_DATAREG_BYTE) >> 2) { + memcpy(&data, buf, sizeof(data)); + buf += sizeof(data); + } else { + memcpy(&data, buf, bc % SPMI_PER_DATAREG_BYTE); + buf += (bc % SPMI_PER_DATAREG_BYTE); + } + + writel((u32 __force)cpu_to_be32(data), + spmi_controller->base + chnl_ofst + + SPMI_APB_SPMI_WDATA0_BASE_ADDR + + SPMI_PER_DATAREG_BYTE * i); + } + + /* Start the transaction */ + writel(cmd, spmi_controller->base + chnl_ofst + SPMI_APB_SPMI_CMD_BASE_ADDR); + + rc = spmi_controller_wait_for_done(&ctrl->dev, spmi_controller, + spmi_controller->base, slave_id, + slave_addr); + spin_unlock_irqrestore(&spmi_controller->lock, flags); + + if (rc) + dev_err(&ctrl->dev, "spmi write wait timeout op:0x%x slave_id:%d slave_addr:0x%x bc:%zu\n", + opc, slave_id, slave_addr, bc); + else + dev_dbg(&ctrl->dev, "%s: id:%d slave_addr:0x%x, wrote value: %*ph\n", + __func__, slave_id, slave_addr, (int)bc, __buf); + + return rc; +} + +static int spmi_controller_probe(struct platform_device *pdev) +{ + struct spmi_controller_dev *spmi_controller; + struct spmi_controller *ctrl; + struct resource *iores; + int ret; + + ctrl = spmi_controller_alloc(&pdev->dev, sizeof(*spmi_controller)); + if (!ctrl) { + dev_err(&pdev->dev, "can not allocate spmi_controller data\n"); + return -ENOMEM; + } + spmi_controller = spmi_controller_get_drvdata(ctrl); + spmi_controller->controller = ctrl; + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!iores) { + dev_err(&pdev->dev, "can not get resource!\n"); + ret = -EINVAL; + goto err_put_controller; + } + + spmi_controller->base = devm_ioremap(&pdev->dev, iores->start, + resource_size(iores)); + if (!spmi_controller->base) { + dev_err(&pdev->dev, "can not remap base addr!\n"); + ret = -EADDRNOTAVAIL; + goto err_put_controller; + } + + ret = of_property_read_u32(pdev->dev.of_node, "hisilicon,spmi-channel", + &spmi_controller->channel); + if (ret) { + dev_err(&pdev->dev, "can not get channel\n"); + ret = -ENODEV; + goto err_put_controller; + } + + platform_set_drvdata(pdev, spmi_controller); + dev_set_drvdata(&ctrl->dev, spmi_controller); + + spin_lock_init(&spmi_controller->lock); + + ctrl->nr = spmi_controller->channel; + ctrl->dev.parent = pdev->dev.parent; + ctrl->dev.of_node = of_node_get(pdev->dev.of_node); + + /* Callbacks */ + ctrl->read_cmd = spmi_read_cmd; + ctrl->write_cmd = spmi_write_cmd; + + ret = spmi_controller_add(ctrl); + if (ret) { + dev_err(&pdev->dev, "spmi_controller_add failed with error %d!\n", ret); + goto err_put_controller; + } + + return 0; + +err_put_controller: + spmi_controller_put(ctrl); + return ret; +} + +static int spmi_del_controller(struct platform_device *pdev) +{ + struct spmi_controller *ctrl = platform_get_drvdata(pdev); + + spmi_controller_remove(ctrl); + spmi_controller_put(ctrl); + return 0; +} + +static const struct of_device_id spmi_controller_match_table[] = { + { + .compatible = "hisilicon,kirin970-spmi-controller", + }, + {} +}; +MODULE_DEVICE_TABLE(of, spmi_controller_match_table); + +static struct platform_driver spmi_controller_driver = { + .probe = spmi_controller_probe, + .remove = spmi_del_controller, + .driver = { + .name = "hisi_spmi_controller", + .of_match_table = spmi_controller_match_table, + }, +}; + +static int __init spmi_controller_init(void) +{ + return platform_driver_register(&spmi_controller_driver); +} +postcore_initcall(spmi_controller_init); + +static void __exit spmi_controller_exit(void) +{ + platform_driver_unregister(&spmi_controller_driver); +} +module_exit(spmi_controller_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:spmi_controller"); diff --git a/drivers/staging/hikey9xx/Kconfig b/drivers/staging/hikey9xx/Kconfig index 3ceda03f2864..9f53df9068fe 100644 --- a/drivers/staging/hikey9xx/Kconfig +++ b/drivers/staging/hikey9xx/Kconfig @@ -1,16 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -# to be placed at drivers/spmi -config SPMI_HISI3670 - tristate "Hisilicon 3670 SPMI Controller" - select IRQ_DOMAIN_HIERARCHY - depends on HAS_IOMEM - depends on SPMI - help - If you say yes to this option, support will be included for the - built-in SPMI PMIC Arbiter interface on Hisilicon 3670 - processors. - # to be placed at drivers/mfd config MFD_HI6421_SPMI tristate "HiSilicon Hi6421v600 SPMI PMU/Codec IC" diff --git a/drivers/staging/hikey9xx/Makefile b/drivers/staging/hikey9xx/Makefile index 42d486104525..e3108d7dd849 100644 --- a/drivers/staging/hikey9xx/Makefile +++ b/drivers/staging/hikey9xx/Makefile @@ -1,4 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_SPMI_HISI3670) += hisi-spmi-controller.o obj-$(CONFIG_MFD_HI6421_SPMI) += hi6421-spmi-pmic.o diff --git a/drivers/staging/hikey9xx/hisi-spmi-controller.c b/drivers/staging/hikey9xx/hisi-spmi-controller.c deleted file mode 100644 index 5bd23262abd6..000000000000 --- a/drivers/staging/hikey9xx/hisi-spmi-controller.c +++ /dev/null @@ -1,367 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * SPMI register addr - */ -#define SPMI_CHANNEL_OFFSET 0x0300 -#define SPMI_SLAVE_OFFSET 0x20 - -#define SPMI_APB_SPMI_CMD_BASE_ADDR 0x0100 - -#define SPMI_APB_SPMI_WDATA0_BASE_ADDR 0x0104 -#define SPMI_APB_SPMI_WDATA1_BASE_ADDR 0x0108 -#define SPMI_APB_SPMI_WDATA2_BASE_ADDR 0x010c -#define SPMI_APB_SPMI_WDATA3_BASE_ADDR 0x0110 - -#define SPMI_APB_SPMI_STATUS_BASE_ADDR 0x0200 - -#define SPMI_APB_SPMI_RDATA0_BASE_ADDR 0x0204 -#define SPMI_APB_SPMI_RDATA1_BASE_ADDR 0x0208 -#define SPMI_APB_SPMI_RDATA2_BASE_ADDR 0x020c -#define SPMI_APB_SPMI_RDATA3_BASE_ADDR 0x0210 - -#define SPMI_PER_DATAREG_BYTE 4 -/* - * SPMI cmd register - */ -#define SPMI_APB_SPMI_CMD_EN BIT(31) -#define SPMI_APB_SPMI_CMD_TYPE_OFFSET 24 -#define SPMI_APB_SPMI_CMD_LENGTH_OFFSET 20 -#define SPMI_APB_SPMI_CMD_SLAVEID_OFFSET 16 -#define SPMI_APB_SPMI_CMD_ADDR_OFFSET 0 - -/* Command Opcodes */ - -enum spmi_controller_cmd_op_code { - SPMI_CMD_REG_ZERO_WRITE = 0, - SPMI_CMD_REG_WRITE = 1, - SPMI_CMD_REG_READ = 2, - SPMI_CMD_EXT_REG_WRITE = 3, - SPMI_CMD_EXT_REG_READ = 4, - SPMI_CMD_EXT_REG_WRITE_L = 5, - SPMI_CMD_EXT_REG_READ_L = 6, - SPMI_CMD_REG_RESET = 7, - SPMI_CMD_REG_SLEEP = 8, - SPMI_CMD_REG_SHUTDOWN = 9, - SPMI_CMD_REG_WAKEUP = 10, -}; - -/* - * SPMI status register - */ -#define SPMI_APB_TRANS_DONE BIT(0) -#define SPMI_APB_TRANS_FAIL BIT(2) - -/* Command register fields */ -#define SPMI_CONTROLLER_CMD_MAX_BYTE_COUNT 16 - -/* Maximum number of support PMIC peripherals */ -#define SPMI_CONTROLLER_TIMEOUT_US 1000 -#define SPMI_CONTROLLER_MAX_TRANS_BYTES 16 - -struct spmi_controller_dev { - struct spmi_controller *controller; - struct device *dev; - void __iomem *base; - spinlock_t lock; - u32 channel; -}; - -static int spmi_controller_wait_for_done(struct device *dev, - struct spmi_controller_dev *ctrl_dev, - void __iomem *base, u8 sid, u16 addr) -{ - u32 timeout = SPMI_CONTROLLER_TIMEOUT_US; - u32 status, offset; - - offset = SPMI_APB_SPMI_STATUS_BASE_ADDR; - offset += SPMI_CHANNEL_OFFSET * ctrl_dev->channel + SPMI_SLAVE_OFFSET * sid; - - do { - status = readl(base + offset); - - if (status & SPMI_APB_TRANS_DONE) { - if (status & SPMI_APB_TRANS_FAIL) { - dev_err(dev, "%s: transaction failed (0x%x)\n", - __func__, status); - return -EIO; - } - dev_dbg(dev, "%s: status 0x%x\n", __func__, status); - return 0; - } - udelay(1); - } while (timeout--); - - dev_err(dev, "%s: timeout, status 0x%x\n", __func__, status); - return -ETIMEDOUT; -} - -static int spmi_read_cmd(struct spmi_controller *ctrl, - u8 opc, u8 slave_id, u16 slave_addr, u8 *__buf, size_t bc) -{ - struct spmi_controller_dev *spmi_controller = dev_get_drvdata(&ctrl->dev); - u32 chnl_ofst = SPMI_CHANNEL_OFFSET * spmi_controller->channel; - unsigned long flags; - u8 *buf = __buf; - u32 cmd, data; - int rc; - u8 op_code, i; - - if (bc > SPMI_CONTROLLER_MAX_TRANS_BYTES) { - dev_err(&ctrl->dev, - "spmi_controller supports 1..%d bytes per trans, but:%zu requested\n", - SPMI_CONTROLLER_MAX_TRANS_BYTES, bc); - return -EINVAL; - } - - switch (opc) { - case SPMI_CMD_READ: - op_code = SPMI_CMD_REG_READ; - break; - case SPMI_CMD_EXT_READ: - op_code = SPMI_CMD_EXT_REG_READ; - break; - case SPMI_CMD_EXT_READL: - op_code = SPMI_CMD_EXT_REG_READ_L; - break; - default: - dev_err(&ctrl->dev, "invalid read cmd 0x%x\n", opc); - return -EINVAL; - } - - cmd = SPMI_APB_SPMI_CMD_EN | - (op_code << SPMI_APB_SPMI_CMD_TYPE_OFFSET) | - ((bc - 1) << SPMI_APB_SPMI_CMD_LENGTH_OFFSET) | - ((slave_id & 0xf) << SPMI_APB_SPMI_CMD_SLAVEID_OFFSET) | /* slvid */ - ((slave_addr & 0xffff) << SPMI_APB_SPMI_CMD_ADDR_OFFSET); /* slave_addr */ - - spin_lock_irqsave(&spmi_controller->lock, flags); - - writel(cmd, spmi_controller->base + chnl_ofst + SPMI_APB_SPMI_CMD_BASE_ADDR); - - rc = spmi_controller_wait_for_done(&ctrl->dev, spmi_controller, - spmi_controller->base, slave_id, slave_addr); - if (rc) - goto done; - - for (i = 0; bc > i * SPMI_PER_DATAREG_BYTE; i++) { - data = readl(spmi_controller->base + chnl_ofst + - SPMI_SLAVE_OFFSET * slave_id + - SPMI_APB_SPMI_RDATA0_BASE_ADDR + - i * SPMI_PER_DATAREG_BYTE); - data = be32_to_cpu((__be32 __force)data); - if ((bc - i * SPMI_PER_DATAREG_BYTE) >> 2) { - memcpy(buf, &data, sizeof(data)); - buf += sizeof(data); - } else { - memcpy(buf, &data, bc % SPMI_PER_DATAREG_BYTE); - buf += (bc % SPMI_PER_DATAREG_BYTE); - } - } - -done: - spin_unlock_irqrestore(&spmi_controller->lock, flags); - if (rc) - dev_err(&ctrl->dev, - "spmi read wait timeout op:0x%x slave_id:%d slave_addr:0x%x bc:%zu\n", - opc, slave_id, slave_addr, bc + 1); - else - dev_dbg(&ctrl->dev, "%s: id:%d slave_addr:0x%x, read value: %*ph\n", - __func__, slave_id, slave_addr, (int)bc, __buf); - - return rc; -} - -static int spmi_write_cmd(struct spmi_controller *ctrl, - u8 opc, u8 slave_id, u16 slave_addr, const u8 *__buf, size_t bc) -{ - struct spmi_controller_dev *spmi_controller = dev_get_drvdata(&ctrl->dev); - u32 chnl_ofst = SPMI_CHANNEL_OFFSET * spmi_controller->channel; - const u8 *buf = __buf; - unsigned long flags; - u32 cmd, data; - int rc; - u8 op_code, i; - - if (bc > SPMI_CONTROLLER_MAX_TRANS_BYTES) { - dev_err(&ctrl->dev, - "spmi_controller supports 1..%d bytes per trans, but:%zu requested\n", - SPMI_CONTROLLER_MAX_TRANS_BYTES, bc); - return -EINVAL; - } - - switch (opc) { - case SPMI_CMD_WRITE: - op_code = SPMI_CMD_REG_WRITE; - break; - case SPMI_CMD_EXT_WRITE: - op_code = SPMI_CMD_EXT_REG_WRITE; - break; - case SPMI_CMD_EXT_WRITEL: - op_code = SPMI_CMD_EXT_REG_WRITE_L; - break; - default: - dev_err(&ctrl->dev, "invalid write cmd 0x%x\n", opc); - return -EINVAL; - } - - cmd = SPMI_APB_SPMI_CMD_EN | - (op_code << SPMI_APB_SPMI_CMD_TYPE_OFFSET) | - ((bc - 1) << SPMI_APB_SPMI_CMD_LENGTH_OFFSET) | - ((slave_id & 0xf) << SPMI_APB_SPMI_CMD_SLAVEID_OFFSET) | - ((slave_addr & 0xffff) << SPMI_APB_SPMI_CMD_ADDR_OFFSET); - - /* Write data to FIFOs */ - spin_lock_irqsave(&spmi_controller->lock, flags); - - for (i = 0; bc > i * SPMI_PER_DATAREG_BYTE; i++) { - data = 0; - if ((bc - i * SPMI_PER_DATAREG_BYTE) >> 2) { - memcpy(&data, buf, sizeof(data)); - buf += sizeof(data); - } else { - memcpy(&data, buf, bc % SPMI_PER_DATAREG_BYTE); - buf += (bc % SPMI_PER_DATAREG_BYTE); - } - - writel((u32 __force)cpu_to_be32(data), - spmi_controller->base + chnl_ofst + - SPMI_APB_SPMI_WDATA0_BASE_ADDR + - SPMI_PER_DATAREG_BYTE * i); - } - - /* Start the transaction */ - writel(cmd, spmi_controller->base + chnl_ofst + SPMI_APB_SPMI_CMD_BASE_ADDR); - - rc = spmi_controller_wait_for_done(&ctrl->dev, spmi_controller, - spmi_controller->base, slave_id, - slave_addr); - spin_unlock_irqrestore(&spmi_controller->lock, flags); - - if (rc) - dev_err(&ctrl->dev, "spmi write wait timeout op:0x%x slave_id:%d slave_addr:0x%x bc:%zu\n", - opc, slave_id, slave_addr, bc); - else - dev_dbg(&ctrl->dev, "%s: id:%d slave_addr:0x%x, wrote value: %*ph\n", - __func__, slave_id, slave_addr, (int)bc, __buf); - - return rc; -} - -static int spmi_controller_probe(struct platform_device *pdev) -{ - struct spmi_controller_dev *spmi_controller; - struct spmi_controller *ctrl; - struct resource *iores; - int ret; - - ctrl = spmi_controller_alloc(&pdev->dev, sizeof(*spmi_controller)); - if (!ctrl) { - dev_err(&pdev->dev, "can not allocate spmi_controller data\n"); - return -ENOMEM; - } - spmi_controller = spmi_controller_get_drvdata(ctrl); - spmi_controller->controller = ctrl; - - iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!iores) { - dev_err(&pdev->dev, "can not get resource!\n"); - ret = -EINVAL; - goto err_put_controller; - } - - spmi_controller->base = devm_ioremap(&pdev->dev, iores->start, - resource_size(iores)); - if (!spmi_controller->base) { - dev_err(&pdev->dev, "can not remap base addr!\n"); - ret = -EADDRNOTAVAIL; - goto err_put_controller; - } - - ret = of_property_read_u32(pdev->dev.of_node, "hisilicon,spmi-channel", - &spmi_controller->channel); - if (ret) { - dev_err(&pdev->dev, "can not get channel\n"); - ret = -ENODEV; - goto err_put_controller; - } - - platform_set_drvdata(pdev, spmi_controller); - dev_set_drvdata(&ctrl->dev, spmi_controller); - - spin_lock_init(&spmi_controller->lock); - - ctrl->nr = spmi_controller->channel; - ctrl->dev.parent = pdev->dev.parent; - ctrl->dev.of_node = of_node_get(pdev->dev.of_node); - - /* Callbacks */ - ctrl->read_cmd = spmi_read_cmd; - ctrl->write_cmd = spmi_write_cmd; - - ret = spmi_controller_add(ctrl); - if (ret) { - dev_err(&pdev->dev, "spmi_controller_add failed with error %d!\n", ret); - goto err_put_controller; - } - - return 0; - -err_put_controller: - spmi_controller_put(ctrl); - return ret; -} - -static int spmi_del_controller(struct platform_device *pdev) -{ - struct spmi_controller *ctrl = platform_get_drvdata(pdev); - - spmi_controller_remove(ctrl); - spmi_controller_put(ctrl); - return 0; -} - -static const struct of_device_id spmi_controller_match_table[] = { - { - .compatible = "hisilicon,kirin970-spmi-controller", - }, - {} -}; -MODULE_DEVICE_TABLE(of, spmi_controller_match_table); - -static struct platform_driver spmi_controller_driver = { - .probe = spmi_controller_probe, - .remove = spmi_del_controller, - .driver = { - .name = "hisi_spmi_controller", - .of_match_table = spmi_controller_match_table, - }, -}; - -static int __init spmi_controller_init(void) -{ - return platform_driver_register(&spmi_controller_driver); -} -postcore_initcall(spmi_controller_init); - -static void __exit spmi_controller_exit(void) -{ - platform_driver_unregister(&spmi_controller_driver); -} -module_exit(spmi_controller_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_VERSION("1.0"); -MODULE_ALIAS("platform:spmi_controller"); diff --git a/drivers/staging/hikey9xx/hisilicon,hi6421-spmi-pmic.yaml b/drivers/staging/hikey9xx/hisilicon,hi6421-spmi-pmic.yaml index 3b23ad56b31a..d57316c57d7b 100644 --- a/drivers/staging/hikey9xx/hisilicon,hi6421-spmi-pmic.yaml +++ b/drivers/staging/hikey9xx/hisilicon,hi6421-spmi-pmic.yaml @@ -17,7 +17,7 @@ description: | node. The SPMI controller part is provided by - drivers/staging/hikey9xx/hisilicon,hisi-spmi-controller.yaml. + Documentation/devicetree/bindings/mfd/hisilicon,hi6421-spmi-pmic.yaml properties: $nodename: diff --git a/drivers/staging/hikey9xx/hisilicon,hisi-spmi-controller.yaml b/drivers/staging/hikey9xx/hisilicon,hisi-spmi-controller.yaml deleted file mode 100644 index f882903769f9..000000000000 --- a/drivers/staging/hikey9xx/hisilicon,hisi-spmi-controller.yaml +++ /dev/null @@ -1,73 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/spmi/hisilicon,hisi-spmi-controller.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: HiSilicon SPMI controller - -maintainers: - - Mauro Carvalho Chehab - -description: | - The HiSilicon SPMI BUS controller is found on some Kirin-based designs. - It is a MIPI System Power Management (SPMI) controller. - - The PMIC part is provided by - ./Documentation/devicetree/bindings/mfd/hisilicon,hi6421-spmi-pmic.yaml. - -allOf: - - $ref: spmi.yaml# - -properties: - - $nodename: - pattern: "spmi@[0-9a-f]" - - compatible: - const: hisilicon,kirin970-spmi-controller - - reg: - maxItems: 1 - - hisilicon,spmi-channel: - $ref: /schemas/types.yaml#/definitions/uint32 - description: | - number of the Kirin 970 SPMI channel where the SPMI devices are connected. - -required: - - compatible - - reg - - hisilicon,spmi-channel - -patternProperties: - "@[0-9a-f]$": - type: object - - description: | - PMIC properties, which are specific to the used SPMI PMIC device(s). - When used in combination with HiSilicon 6421v600, the properties - are documented at - drivers/staging/hikey9xx/hisilicon,hi6421-spmi-pmic.yaml. - -unevaluatedProperties: false - -examples: - - | - bus { - #address-cells = <2>; - #size-cells = <2>; - - spmi: spmi@fff24000 { - compatible = "hisilicon,kirin970-spmi-controller"; - #address-cells = <2>; - #size-cells = <0>; - reg = <0x0 0xfff24000 0x0 0x1000>; - hisilicon,spmi-channel = <2>; - - pmic@0 { - reg = <0 0>; - /* pmic properties */ - }; - }; - }; -- cgit v1.2.3-59-g8ed1b