mirror of
https://github.com/torvalds/linux.git
synced 2026-05-25 23:52:08 +02:00
Merge branch 'ib-iio-thermal-qcom-pmic5' into togreg
Immutable branch to allow this base work to be merged into thermal.
This commit is contained in:
commit
c99ccbba91
|
|
@ -0,0 +1,151 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/adc/qcom,spmi-adc5-gen3.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Qualcomm's SPMI PMIC ADC5 Gen3
|
||||
|
||||
maintainers:
|
||||
- Jishnu Prakash <jishnu.prakash@oss.qualcomm.com>
|
||||
|
||||
description: |
|
||||
SPMI PMIC5 Gen3 voltage ADC (ADC) provides interface to clients to read
|
||||
voltage. It is a 16-bit sigma-delta ADC. It also performs the same thermal
|
||||
monitoring function as the existing ADC_TM devices.
|
||||
|
||||
The interface is implemented on SDAM (Shared Direct Access Memory) peripherals
|
||||
on the master PMIC rather than a dedicated ADC peripheral. The number of PMIC
|
||||
SDAM peripherals allocated for ADC is not correlated with the PMIC used, it is
|
||||
programmed in FW (PBS) and is fixed per SOC, based on the SOC requirements.
|
||||
All boards using a particular (SOC + master PMIC) combination will have the
|
||||
same number of ADC SDAMs supported on that PMIC.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: qcom,spmi-adc5-gen3
|
||||
|
||||
reg:
|
||||
items:
|
||||
- description: SDAM0 base address in the SPMI PMIC register map
|
||||
- description: SDAM1 base address
|
||||
minItems: 1
|
||||
|
||||
"#address-cells":
|
||||
const: 1
|
||||
|
||||
"#size-cells":
|
||||
const: 0
|
||||
|
||||
"#io-channel-cells":
|
||||
const: 1
|
||||
|
||||
"#thermal-sensor-cells":
|
||||
const: 1
|
||||
|
||||
interrupts:
|
||||
items:
|
||||
- description: SDAM0 end of conversion (EOC) interrupt
|
||||
- description: SDAM1 EOC interrupt
|
||||
minItems: 1
|
||||
|
||||
patternProperties:
|
||||
"^channel@[0-9a-f]+$":
|
||||
type: object
|
||||
unevaluatedProperties: false
|
||||
$ref: /schemas/iio/adc/qcom,spmi-vadc-common.yaml
|
||||
description:
|
||||
Represents the external channels which are connected to the ADC.
|
||||
|
||||
properties:
|
||||
qcom,decimation:
|
||||
enum: [ 85, 340, 1360 ]
|
||||
default: 1360
|
||||
|
||||
qcom,hw-settle-time:
|
||||
enum: [ 15, 100, 200, 300, 400, 500, 600, 700,
|
||||
1000, 2000, 4000, 8000, 16000, 32000, 64000, 128000 ]
|
||||
default: 15
|
||||
|
||||
qcom,avg-samples:
|
||||
enum: [ 1, 2, 4, 8, 16 ]
|
||||
default: 1
|
||||
|
||||
qcom,adc-tm:
|
||||
description:
|
||||
ADC_TM is a threshold monitoring feature in HW which can be enabled
|
||||
on any ADC channel, to trigger an IRQ for threshold violation. In
|
||||
earlier ADC generations, it was implemented in a separate device
|
||||
(documented in Documentation/devicetree/bindings/thermal/qcom-spmi-adc-tm5.yaml.)
|
||||
In Gen3, this feature can be enabled in the same ADC device for any
|
||||
channel and threshold monitoring and IRQ triggering are handled in FW
|
||||
(PBS) instead of another dedicated HW block.
|
||||
This property indicates ADC_TM monitoring is done on this channel.
|
||||
type: boolean
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- "#address-cells"
|
||||
- "#size-cells"
|
||||
- "#io-channel-cells"
|
||||
- interrupts
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
|
||||
pmic {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
adc@9000 {
|
||||
compatible = "qcom,spmi-adc5-gen3";
|
||||
reg = <0x9000>, <0x9100>;
|
||||
interrupts = <0x0 0x90 0x1 IRQ_TYPE_EDGE_RISING>,
|
||||
<0x0 0x91 0x1 IRQ_TYPE_EDGE_RISING>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
#io-channel-cells = <1>;
|
||||
#thermal-sensor-cells = <1>;
|
||||
|
||||
/* PMK8550 Channel nodes */
|
||||
channel@3 {
|
||||
reg = <0x3>;
|
||||
label = "pmk8550_die_temp";
|
||||
qcom,pre-scaling = <1 1>;
|
||||
};
|
||||
|
||||
channel@44 {
|
||||
reg = <0x44>;
|
||||
label = "pmk8550_xo_therm";
|
||||
qcom,pre-scaling = <1 1>;
|
||||
qcom,ratiometric;
|
||||
qcom,hw-settle-time = <200>;
|
||||
qcom,adc-tm;
|
||||
};
|
||||
|
||||
/* PM8550 Channel nodes */
|
||||
channel@103 {
|
||||
reg = <0x103>;
|
||||
label = "pm8550_die_temp";
|
||||
qcom,pre-scaling = <1 1>;
|
||||
};
|
||||
|
||||
/* PM8550B Channel nodes */
|
||||
channel@78f {
|
||||
reg = <0x78f>;
|
||||
label = "pm8550b_vbat_sns_qbg";
|
||||
qcom,pre-scaling = <1 3>;
|
||||
};
|
||||
|
||||
/* PM8550VS_C Channel nodes */
|
||||
channel@203 {
|
||||
reg = <0x203>;
|
||||
label = "pm8550vs_c_die_temp";
|
||||
qcom,pre-scaling = <1 1>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/adc/qcom,spmi-vadc-common.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Qualcomm Technologies, Inc. SPMI PMIC ADC channels
|
||||
|
||||
maintainers:
|
||||
- Jishnu Prakash <jishnu.prakash@oss.qualcomm.com>
|
||||
|
||||
description:
|
||||
This defines the common properties used to define Qualcomm VADC channels.
|
||||
|
||||
properties:
|
||||
reg:
|
||||
description:
|
||||
ADC channel number (PMIC-specific for versions after PMIC5 ADC).
|
||||
maxItems: 1
|
||||
|
||||
label:
|
||||
description:
|
||||
ADC input of the platform as seen in the schematics.
|
||||
For thermistor inputs connected to generic AMUX or GPIO inputs
|
||||
these can vary across platform for the same pins. Hence select
|
||||
the platform schematics name for this channel.
|
||||
|
||||
qcom,decimation:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description:
|
||||
This parameter is used to decrease ADC sampling rate.
|
||||
Quicker measurements can be made by reducing decimation ratio.
|
||||
|
||||
qcom,pre-scaling:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32-array
|
||||
description:
|
||||
Used for scaling the channel input signal before the signal is
|
||||
fed to VADC. The configuration for this node is to know the
|
||||
pre-determined ratio and use it for post scaling. It is a pair of
|
||||
integers, denoting the numerator and denominator of the fraction by which
|
||||
input signal is multiplied. For example, <1 3> indicates the signal is scaled
|
||||
down to 1/3 of its value before ADC measurement.
|
||||
If property is not found default value depending on chip will be used.
|
||||
oneOf:
|
||||
- items:
|
||||
- const: 1
|
||||
- enum: [ 1, 3, 4, 6, 20, 8, 10, 16 ]
|
||||
- items:
|
||||
- const: 10
|
||||
- const: 81
|
||||
|
||||
qcom,ratiometric:
|
||||
type: boolean
|
||||
description: |
|
||||
Channel calibration type.
|
||||
- For compatible property "qcom,spmi-vadc", if this property is
|
||||
specified VADC will use the VDD reference (1.8V) and GND for
|
||||
channel calibration. If property is not found, channel will be
|
||||
calibrated with 0.625V and 1.25V reference channels, also
|
||||
known as absolute calibration.
|
||||
- For other compatible properties, if this property is specified
|
||||
VADC will use the VDD reference (1.875V) and GND for channel
|
||||
calibration. If property is not found, channel will be calibrated
|
||||
with 0V and 1.25V reference channels, also known as absolute calibration.
|
||||
|
||||
qcom,hw-settle-time:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: |
|
||||
Time between AMUX getting configured and the ADC starting
|
||||
conversion. The 'hw_settle_time' is an index used from valid values
|
||||
and programmed in hardware to achieve the hardware settling delay.
|
||||
|
||||
qcom,avg-samples:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: |
|
||||
Number of samples to be used for measurement.
|
||||
Averaging provides the option to obtain a single measurement
|
||||
from the ADC that is an average of multiple samples. The value
|
||||
selected is 2^(value).
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
additionalProperties: true
|
||||
|
|
@ -15,6 +15,8 @@ description: |
|
|||
voltage. The VADC is a 15-bit sigma-delta ADC.
|
||||
SPMI PMIC5/PMIC7 voltage ADC (ADC) provides interface to clients to read
|
||||
voltage. The VADC is a 16-bit sigma-delta ADC.
|
||||
Note that PMIC7 ADC is the generation between PMIC5 and PMIC5 Gen3 ADC,
|
||||
it can be considered like PMIC5 Gen2.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
|
|
@ -56,7 +58,7 @@ required:
|
|||
patternProperties:
|
||||
"^channel@[0-9a-f]+$":
|
||||
type: object
|
||||
additionalProperties: false
|
||||
unevaluatedProperties: false
|
||||
description: |
|
||||
Represents the external channels which are connected to the ADC.
|
||||
For compatible property "qcom,spmi-vadc" following channels, also known as
|
||||
|
|
@ -64,79 +66,7 @@ patternProperties:
|
|||
configuration nodes should be defined:
|
||||
VADC_REF_625MV and/or VADC_SPARE1(based on PMIC version) VADC_REF_1250MV,
|
||||
VADC_GND_REF and VADC_VDD_VADC.
|
||||
|
||||
properties:
|
||||
reg:
|
||||
maxItems: 1
|
||||
description: |
|
||||
ADC channel number.
|
||||
See include/dt-bindings/iio/qcom,spmi-vadc.h
|
||||
For PMIC7 ADC, the channel numbers are specified separately per PMIC
|
||||
in the PMIC-specific files in include/dt-bindings/iio/.
|
||||
|
||||
label:
|
||||
description: |
|
||||
ADC input of the platform as seen in the schematics.
|
||||
For thermistor inputs connected to generic AMUX or GPIO inputs
|
||||
these can vary across platform for the same pins. Hence select
|
||||
the platform schematics name for this channel.
|
||||
|
||||
qcom,decimation:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: |
|
||||
This parameter is used to decrease ADC sampling rate.
|
||||
Quicker measurements can be made by reducing decimation ratio.
|
||||
|
||||
qcom,pre-scaling:
|
||||
description: |
|
||||
Used for scaling the channel input signal before the signal is
|
||||
fed to VADC. The configuration for this node is to know the
|
||||
pre-determined ratio and use it for post scaling. It is a pair of
|
||||
integers, denoting the numerator and denominator of the fraction by which
|
||||
input signal is multiplied. For example, <1 3> indicates the signal is scaled
|
||||
down to 1/3 of its value before ADC measurement.
|
||||
If property is not found default value depending on chip will be used.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32-array
|
||||
oneOf:
|
||||
- items:
|
||||
- const: 1
|
||||
- enum: [ 1, 3, 4, 6, 20, 8, 10, 16 ]
|
||||
- items:
|
||||
- const: 10
|
||||
- const: 81
|
||||
|
||||
qcom,ratiometric:
|
||||
description: |
|
||||
Channel calibration type.
|
||||
- For compatible property "qcom,spmi-vadc", if this property is
|
||||
specified VADC will use the VDD reference (1.8V) and GND for
|
||||
channel calibration. If property is not found, channel will be
|
||||
calibrated with 0.625V and 1.25V reference channels, also
|
||||
known as absolute calibration.
|
||||
- For compatible property "qcom,spmi-adc5", "qcom,spmi-adc7" and
|
||||
"qcom,spmi-adc-rev2", if this property is specified VADC will use
|
||||
the VDD reference (1.875V) and GND for channel calibration. If
|
||||
property is not found, channel will be calibrated with 0V and 1.25V
|
||||
reference channels, also known as absolute calibration.
|
||||
type: boolean
|
||||
|
||||
qcom,hw-settle-time:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: |
|
||||
Time between AMUX getting configured and the ADC starting
|
||||
conversion. The 'hw_settle_time' is an index used from valid values
|
||||
and programmed in hardware to achieve the hardware settling delay.
|
||||
|
||||
qcom,avg-samples:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: |
|
||||
Number of samples to be used for measurement.
|
||||
Averaging provides the option to obtain a single measurement
|
||||
from the ADC that is an average of multiple samples. The value
|
||||
selected is 2^(value).
|
||||
|
||||
required:
|
||||
- reg
|
||||
$ref: /schemas/iio/adc/qcom,spmi-vadc-common.yaml
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ patternProperties:
|
|||
"^adc@[0-9a-f]+$":
|
||||
type: object
|
||||
oneOf:
|
||||
- $ref: /schemas/iio/adc/qcom,spmi-adc5-gen3.yaml#
|
||||
- $ref: /schemas/iio/adc/qcom,spmi-iadc.yaml#
|
||||
- $ref: /schemas/iio/adc/qcom,spmi-rradc.yaml#
|
||||
- $ref: /schemas/iio/adc/qcom,spmi-vadc.yaml#
|
||||
|
|
|
|||
|
|
@ -1366,6 +1366,32 @@ config QCOM_SPMI_ADC5
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called qcom-spmi-adc5.
|
||||
|
||||
config QCOM_SPMI_ADC5_GEN3
|
||||
tristate "Qualcomm Technologies Inc. SPMI PMIC5 GEN3 ADC"
|
||||
depends on SPMI && THERMAL
|
||||
select REGMAP_SPMI
|
||||
select QCOM_VADC_COMMON
|
||||
select AUXILIARY_BUS
|
||||
help
|
||||
IIO Voltage PMIC5 Gen3 ADC driver for Qualcomm Technologies Inc.
|
||||
|
||||
The driver supports reading multiple channels. The ADC is a 16-bit
|
||||
sigma-delta ADC. The hardware supports calibrated results for
|
||||
conversion requests and clients include reading phone power supply
|
||||
voltage, on board system thermistors connected to the PMIC ADC,
|
||||
PMIC die temperature, charger temperature, battery current, USB
|
||||
voltage input and voltage signals connected to supported PMIC GPIO
|
||||
pins. The hardware supports internal pull-up for thermistors and can
|
||||
choose between a 30k, 100k or 400k ohm pull up using the ADC channels.
|
||||
|
||||
In addition, the same driver supports ADC thermal monitoring devices
|
||||
too. They appear as thermal zones with multiple trip points. A thermal
|
||||
client sets threshold temperature for both warm and cool trips and
|
||||
gets updated when a threshold is reached.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called qcom-spmi-adc5-gen3.
|
||||
|
||||
config RCAR_GYRO_ADC
|
||||
tristate "Renesas R-Car GyroADC driver"
|
||||
depends on ARCH_RCAR_GEN2 || COMPILE_TEST
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ obj-$(CONFIG_PAC1934) += pac1934.o
|
|||
obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
|
||||
obj-$(CONFIG_QCOM_PM8XXX_XOADC) += qcom-pm8xxx-xoadc.o
|
||||
obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o
|
||||
obj-$(CONFIG_QCOM_SPMI_ADC5_GEN3) += qcom-spmi-adc5-gen3.o
|
||||
obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
|
||||
obj-$(CONFIG_QCOM_SPMI_RRADC) += qcom-spmi-rradc.o
|
||||
obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
|
||||
|
|
|
|||
860
drivers/iio/adc/qcom-spmi-adc5-gen3.c
Normal file
860
drivers/iio/adc/qcom-spmi-adc5-gen3.c
Normal file
|
|
@ -0,0 +1,860 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
|
||||
*/
|
||||
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/device/devres.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/iio/adc/qcom-adc5-gen3-common.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/unaligned.h>
|
||||
|
||||
#define ADC5_GEN3_VADC_SDAM 0x0
|
||||
|
||||
struct adc5_chip;
|
||||
|
||||
/**
|
||||
* struct adc5_channel_prop - ADC channel structure
|
||||
* @common_props: structure with ADC channel properties (common to TM usage).
|
||||
* @adc_tm: indicates TM type if the channel is used for TM measurements.
|
||||
* @chip: pointer to top-level ADC device structure.
|
||||
*/
|
||||
struct adc5_channel_prop {
|
||||
struct adc5_channel_common_prop common_props;
|
||||
int adc_tm;
|
||||
struct adc5_chip *chip;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adc5_chip - ADC private structure.
|
||||
* @dev: SPMI ADC5 Gen3 device.
|
||||
* @dev_data: Top-level ADC device data.
|
||||
* @nchannels: number of ADC channels.
|
||||
* @chan_props: array of ADC channel properties.
|
||||
* @iio_chans: array of IIO channels specification.
|
||||
* @complete: ADC result notification after interrupt is received.
|
||||
* @lock: ADC lock for access to the peripheral, to prevent concurrent
|
||||
* requests from multiple clients.
|
||||
* @data: software configuration data.
|
||||
* @n_tm_channels: number of ADC channels used for TM measurements.
|
||||
* @handler: TM callback to be called for threshold violation interrupt
|
||||
* on first SDAM.
|
||||
* @tm_aux: pointer to auxiliary TM device.
|
||||
*/
|
||||
struct adc5_chip {
|
||||
struct device *dev;
|
||||
struct adc5_device_data dev_data;
|
||||
unsigned int nchannels;
|
||||
struct adc5_channel_prop *chan_props;
|
||||
struct iio_chan_spec *iio_chans;
|
||||
struct completion complete;
|
||||
struct mutex lock;
|
||||
const struct adc5_data *data;
|
||||
unsigned int n_tm_channels;
|
||||
void (*handler)(struct auxiliary_device *tm_aux);
|
||||
struct auxiliary_device *tm_aux;
|
||||
};
|
||||
|
||||
int adc5_gen3_read(struct adc5_device_data *adc, unsigned int sdam_index,
|
||||
u16 offset, u8 *data, int len)
|
||||
{
|
||||
return regmap_bulk_read(adc->regmap,
|
||||
adc->base[sdam_index].base_addr + offset,
|
||||
data, len);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(adc5_gen3_read, "QCOM_SPMI_ADC5_GEN3");
|
||||
|
||||
int adc5_gen3_write(struct adc5_device_data *adc, unsigned int sdam_index,
|
||||
u16 offset, u8 *data, int len)
|
||||
{
|
||||
return regmap_bulk_write(adc->regmap,
|
||||
adc->base[sdam_index].base_addr + offset,
|
||||
data, len);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(adc5_gen3_write, "QCOM_SPMI_ADC5_GEN3");
|
||||
|
||||
static int adc5_gen3_read_voltage_data(struct adc5_chip *adc, u16 *data)
|
||||
{
|
||||
u8 rslt[2];
|
||||
int ret;
|
||||
|
||||
ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
|
||||
ADC5_GEN3_CH_DATA0(0), rslt, sizeof(rslt));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*data = get_unaligned_le16(rslt);
|
||||
|
||||
if (*data == ADC5_USR_DATA_CHECK) {
|
||||
dev_err(adc->dev, "Invalid data:%#x\n", *data);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_dbg(adc->dev, "voltage raw code:%#x\n", *data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void adc5_gen3_update_dig_param(struct adc5_channel_common_prop *prop, u8 *data)
|
||||
{
|
||||
/* Update calibration select and decimation ratio select */
|
||||
*data &= ~(ADC5_GEN3_DIG_PARAM_CAL_SEL_MASK | ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_MASK);
|
||||
*data |= FIELD_PREP(ADC5_GEN3_DIG_PARAM_CAL_SEL_MASK, prop->cal_method);
|
||||
*data |= FIELD_PREP(ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_MASK, prop->decimation);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(adc5_gen3_update_dig_param, "QCOM_SPMI_ADC5_GEN3");
|
||||
|
||||
#define ADC5_GEN3_READ_CONFIG_REGS 7
|
||||
|
||||
static int adc5_gen3_configure(struct adc5_chip *adc,
|
||||
struct adc5_channel_common_prop *prop)
|
||||
{
|
||||
u8 buf[ADC5_GEN3_READ_CONFIG_REGS];
|
||||
u8 conv_req = 0;
|
||||
int ret;
|
||||
|
||||
ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, ADC5_GEN3_SID,
|
||||
buf, sizeof(buf));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Write SID */
|
||||
buf[0] = FIELD_PREP(ADC5_GEN3_SID_MASK, prop->sid);
|
||||
|
||||
/*
|
||||
* Use channel 0 by default for immediate conversion and to indicate
|
||||
* there is an actual conversion request
|
||||
*/
|
||||
buf[1] = ADC5_GEN3_CHAN_CONV_REQ | 0;
|
||||
|
||||
buf[2] = ADC5_GEN3_TIME_IMMEDIATE;
|
||||
|
||||
/* Digital param selection */
|
||||
adc5_gen3_update_dig_param(prop, &buf[3]);
|
||||
|
||||
/* Update fast average sample value */
|
||||
buf[4] = FIELD_PREP(ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK,
|
||||
prop->avg_samples) | ADC5_GEN3_FAST_AVG_CTL_EN;
|
||||
|
||||
/* Select ADC channel */
|
||||
buf[5] = prop->channel;
|
||||
|
||||
/* Select HW settle delay for channel */
|
||||
buf[6] = FIELD_PREP(ADC5_GEN3_HW_SETTLE_DELAY_MASK,
|
||||
prop->hw_settle_time_us);
|
||||
|
||||
reinit_completion(&adc->complete);
|
||||
|
||||
ret = adc5_gen3_write(&adc->dev_data, ADC5_GEN3_VADC_SDAM, ADC5_GEN3_SID,
|
||||
buf, sizeof(buf));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
conv_req = ADC5_GEN3_CONV_REQ_REQ;
|
||||
return adc5_gen3_write(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
|
||||
ADC5_GEN3_CONV_REQ, &conv_req, sizeof(conv_req));
|
||||
}
|
||||
|
||||
/*
|
||||
* Worst case delay from PBS in readying handshake bit can be up to 15ms, when
|
||||
* PBS is busy running other simultaneous transactions, while in the best case,
|
||||
* it is already ready at this point. Assigning polling delay and retry count
|
||||
* accordingly.
|
||||
*/
|
||||
|
||||
#define ADC5_GEN3_HS_DELAY_US 100
|
||||
#define ADC5_GEN3_HS_RETRY_COUNT 150
|
||||
|
||||
int adc5_gen3_poll_wait_hs(struct adc5_device_data *adc,
|
||||
unsigned int sdam_index)
|
||||
{
|
||||
u8 conv_req = ADC5_GEN3_CONV_REQ_REQ;
|
||||
int ret, count;
|
||||
u8 status = 0;
|
||||
|
||||
for (count = 0; count < ADC5_GEN3_HS_RETRY_COUNT; count++) {
|
||||
ret = adc5_gen3_read(adc, sdam_index, ADC5_GEN3_HS, &status, sizeof(status));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (status == ADC5_GEN3_HS_READY) {
|
||||
ret = adc5_gen3_read(adc, sdam_index, ADC5_GEN3_CONV_REQ,
|
||||
&conv_req, sizeof(conv_req));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!conv_req)
|
||||
return 0;
|
||||
}
|
||||
|
||||
fsleep(ADC5_GEN3_HS_DELAY_US);
|
||||
}
|
||||
|
||||
pr_err("Setting HS ready bit timed out, sdam_index:%d, status:%#x\n",
|
||||
sdam_index, status);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(adc5_gen3_poll_wait_hs, "QCOM_SPMI_ADC5_GEN3");
|
||||
|
||||
int adc5_gen3_status_clear(struct adc5_device_data *adc,
|
||||
int sdam_index, u16 offset, u8 *val, int len)
|
||||
{
|
||||
u8 value;
|
||||
int ret;
|
||||
|
||||
ret = adc5_gen3_write(adc, sdam_index, offset, val, len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* To indicate conversion request is only to clear a status */
|
||||
value = 0;
|
||||
ret = adc5_gen3_write(adc, sdam_index, ADC5_GEN3_PERPH_CH, &value,
|
||||
sizeof(value));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
value = ADC5_GEN3_CONV_REQ_REQ;
|
||||
return adc5_gen3_write(adc, sdam_index, ADC5_GEN3_CONV_REQ, &value,
|
||||
sizeof(value));
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(adc5_gen3_status_clear, "QCOM_SPMI_ADC5_GEN3");
|
||||
|
||||
/*
|
||||
* Worst case delay from PBS for conversion time can be up to 500ms, when PBS
|
||||
* has timed out twice, once for the initial attempt and once for a retry of
|
||||
* the same transaction.
|
||||
*/
|
||||
|
||||
#define ADC5_GEN3_CONV_TIMEOUT_MS 501
|
||||
|
||||
static int adc5_gen3_do_conversion(struct adc5_chip *adc,
|
||||
struct adc5_channel_common_prop *prop,
|
||||
u16 *data_volt)
|
||||
{
|
||||
unsigned long rc;
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
guard(mutex)(&adc->lock);
|
||||
ret = adc5_gen3_poll_wait_hs(&adc->dev_data, ADC5_GEN3_VADC_SDAM);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = adc5_gen3_configure(adc, prop);
|
||||
if (ret) {
|
||||
dev_err(adc->dev, "ADC configure failed with %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* No support for polling mode at present */
|
||||
rc = wait_for_completion_timeout(&adc->complete,
|
||||
msecs_to_jiffies(ADC5_GEN3_CONV_TIMEOUT_MS));
|
||||
if (!rc) {
|
||||
dev_err(adc->dev, "Reading ADC channel %s timed out\n",
|
||||
prop->label);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
ret = adc5_gen3_read_voltage_data(adc, data_volt);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = BIT(0);
|
||||
return adc5_gen3_status_clear(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
|
||||
ADC5_GEN3_EOC_CLR, &val, 1);
|
||||
}
|
||||
|
||||
static irqreturn_t adc5_gen3_isr(int irq, void *dev_id)
|
||||
{
|
||||
struct adc5_chip *adc = dev_id;
|
||||
struct device *dev = adc->dev;
|
||||
struct auxiliary_device *adev;
|
||||
u8 status, eoc_status, val;
|
||||
u8 tm_status[2];
|
||||
int ret;
|
||||
|
||||
ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
|
||||
ADC5_GEN3_STATUS1, &status, sizeof(status));
|
||||
if (ret) {
|
||||
dev_err(dev, "adc read status1 failed with %d\n", ret);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
|
||||
ADC5_GEN3_EOC_STS, &eoc_status, sizeof(eoc_status));
|
||||
if (ret) {
|
||||
dev_err(dev, "adc read eoc status failed with %d\n", ret);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
if (status & ADC5_GEN3_STATUS1_CONV_FAULT) {
|
||||
dev_err_ratelimited(dev,
|
||||
"Unexpected conversion fault, status:%#x, eoc_status:%#x\n",
|
||||
status, eoc_status);
|
||||
val = ADC5_GEN3_CONV_ERR_CLR_REQ;
|
||||
adc5_gen3_status_clear(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
|
||||
ADC5_GEN3_CONV_ERR_CLR, &val, 1);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/* CHAN0 is the preconfigured channel for immediate conversion */
|
||||
if (eoc_status & ADC5_GEN3_EOC_CHAN_0)
|
||||
complete(&adc->complete);
|
||||
|
||||
ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
|
||||
ADC5_GEN3_TM_HIGH_STS, tm_status, sizeof(tm_status));
|
||||
if (ret) {
|
||||
dev_err(dev, "adc read TM status failed with %d\n", ret);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "Interrupt status:%#x, EOC status:%#x, high:%#x, low:%#x\n",
|
||||
status, eoc_status, tm_status[0], tm_status[1]);
|
||||
|
||||
if (tm_status[0] || tm_status[1]) {
|
||||
adev = adc->tm_aux;
|
||||
if (!adev || !adev->dev.driver) {
|
||||
dev_err(dev, "adc_tm auxiliary device not initialized\n");
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
adc->handler(adev);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int adc5_gen3_fwnode_xlate(struct iio_dev *indio_dev,
|
||||
const struct fwnode_reference_args *iiospec)
|
||||
{
|
||||
struct adc5_chip *adc = iio_priv(indio_dev);
|
||||
int i, v_channel;
|
||||
|
||||
for (i = 0; i < adc->nchannels; i++) {
|
||||
v_channel = ADC5_GEN3_V_CHAN(adc->chan_props[i].common_props);
|
||||
if (v_channel == iiospec->args[0])
|
||||
return i;
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static int adc5_gen3_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int *val,
|
||||
int *val2, long mask)
|
||||
{
|
||||
struct adc5_chip *adc = iio_priv(indio_dev);
|
||||
struct adc5_channel_common_prop *prop;
|
||||
u16 adc_code_volt;
|
||||
int ret;
|
||||
|
||||
prop = &adc->chan_props[chan->address].common_props;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_PROCESSED:
|
||||
ret = adc5_gen3_do_conversion(adc, prop, &adc_code_volt);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = qcom_adc5_hw_scale(prop->scale_fn_type, prop->prescale,
|
||||
adc->data, adc_code_volt, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return IIO_VAL_INT;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int adc5_gen3_read_label(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, char *label)
|
||||
{
|
||||
struct adc5_chip *adc = iio_priv(indio_dev);
|
||||
struct adc5_channel_prop *prop;
|
||||
|
||||
prop = &adc->chan_props[chan->address];
|
||||
return sprintf(label, "%s\n", prop->common_props.label);
|
||||
}
|
||||
|
||||
static const struct iio_info adc5_gen3_info = {
|
||||
.read_raw = adc5_gen3_read_raw,
|
||||
.read_label = adc5_gen3_read_label,
|
||||
.fwnode_xlate = adc5_gen3_fwnode_xlate,
|
||||
};
|
||||
|
||||
struct adc5_channels {
|
||||
unsigned int prescale_index;
|
||||
enum iio_chan_type type;
|
||||
long info_mask;
|
||||
enum vadc_scale_fn_type scale_fn_type;
|
||||
};
|
||||
|
||||
/* In these definitions, _pre refers to an index into adc5_prescale_ratios. */
|
||||
#define ADC5_CHAN(_type, _mask, _pre, _scale) \
|
||||
{ \
|
||||
.prescale_index = _pre, \
|
||||
.type = _type, \
|
||||
.info_mask = _mask, \
|
||||
.scale_fn_type = _scale, \
|
||||
}, \
|
||||
|
||||
#define ADC5_CHAN_TEMP(_pre, _scale) \
|
||||
ADC5_CHAN(IIO_TEMP, BIT(IIO_CHAN_INFO_PROCESSED), _pre, _scale) \
|
||||
|
||||
#define ADC5_CHAN_VOLT(_pre, _scale) \
|
||||
ADC5_CHAN(IIO_VOLTAGE, BIT(IIO_CHAN_INFO_PROCESSED), _pre, _scale) \
|
||||
|
||||
#define ADC5_CHAN_CUR(_pre, _scale) \
|
||||
ADC5_CHAN(IIO_CURRENT, BIT(IIO_CHAN_INFO_PROCESSED), _pre, _scale) \
|
||||
|
||||
static const struct adc5_channels adc5_gen3_chans_pmic[ADC5_MAX_CHANNEL] = {
|
||||
[ADC5_GEN3_REF_GND] = ADC5_CHAN_VOLT(0, SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_GEN3_1P25VREF] = ADC5_CHAN_VOLT(0, SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_GEN3_VPH_PWR] = ADC5_CHAN_VOLT(1, SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_GEN3_VBAT_SNS_QBG] = ADC5_CHAN_VOLT(1, SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_GEN3_USB_SNS_V_16] = ADC5_CHAN_TEMP(8, SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_GEN3_VIN_DIV16_MUX] = ADC5_CHAN_TEMP(8, SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_GEN3_DIE_TEMP] = ADC5_CHAN_TEMP(0,
|
||||
SCALE_HW_CALIB_PMIC_THERM_PM7)
|
||||
[ADC5_GEN3_AMUX1_THM_100K_PU] = ADC5_CHAN_TEMP(0,
|
||||
SCALE_HW_CALIB_THERM_100K_PU_PM7)
|
||||
[ADC5_GEN3_AMUX2_THM_100K_PU] = ADC5_CHAN_TEMP(0,
|
||||
SCALE_HW_CALIB_THERM_100K_PU_PM7)
|
||||
[ADC5_GEN3_AMUX3_THM_100K_PU] = ADC5_CHAN_TEMP(0,
|
||||
SCALE_HW_CALIB_THERM_100K_PU_PM7)
|
||||
[ADC5_GEN3_AMUX4_THM_100K_PU] = ADC5_CHAN_TEMP(0,
|
||||
SCALE_HW_CALIB_THERM_100K_PU_PM7)
|
||||
[ADC5_GEN3_AMUX5_THM_100K_PU] = ADC5_CHAN_TEMP(0,
|
||||
SCALE_HW_CALIB_THERM_100K_PU_PM7)
|
||||
[ADC5_GEN3_AMUX6_THM_100K_PU] = ADC5_CHAN_TEMP(0,
|
||||
SCALE_HW_CALIB_THERM_100K_PU_PM7)
|
||||
[ADC5_GEN3_AMUX1_GPIO_100K_PU] = ADC5_CHAN_TEMP(0,
|
||||
SCALE_HW_CALIB_THERM_100K_PU_PM7)
|
||||
[ADC5_GEN3_AMUX2_GPIO_100K_PU] = ADC5_CHAN_TEMP(0,
|
||||
SCALE_HW_CALIB_THERM_100K_PU_PM7)
|
||||
[ADC5_GEN3_AMUX3_GPIO_100K_PU] = ADC5_CHAN_TEMP(0,
|
||||
SCALE_HW_CALIB_THERM_100K_PU_PM7)
|
||||
[ADC5_GEN3_AMUX4_GPIO_100K_PU] = ADC5_CHAN_TEMP(0,
|
||||
SCALE_HW_CALIB_THERM_100K_PU_PM7)
|
||||
};
|
||||
|
||||
static int adc5_gen3_get_fw_channel_data(struct adc5_chip *adc,
|
||||
struct adc5_channel_prop *prop,
|
||||
struct fwnode_handle *fwnode)
|
||||
{
|
||||
const char *name = fwnode_get_name(fwnode);
|
||||
const struct adc5_data *data = adc->data;
|
||||
struct device *dev = adc->dev;
|
||||
const char *channel_name;
|
||||
u32 chan, value, sid;
|
||||
u32 varr[2];
|
||||
int ret;
|
||||
|
||||
ret = fwnode_property_read_u32(fwnode, "reg", &chan);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "invalid channel number %s\n",
|
||||
name);
|
||||
|
||||
/*
|
||||
* Value read from "reg" is virtual channel number
|
||||
* virtual channel number = sid << 8 | channel number
|
||||
*/
|
||||
sid = FIELD_GET(ADC5_GEN3_VIRTUAL_SID_MASK, chan);
|
||||
chan = FIELD_GET(ADC5_GEN3_CHANNEL_MASK, chan);
|
||||
|
||||
if (chan > ADC5_MAX_CHANNEL)
|
||||
return dev_err_probe(dev, -EINVAL,
|
||||
"%s invalid channel number %d\n",
|
||||
name, chan);
|
||||
|
||||
prop->common_props.channel = chan;
|
||||
prop->common_props.sid = sid;
|
||||
|
||||
if (!adc->data->adc_chans[chan].info_mask)
|
||||
return dev_err_probe(dev, -EINVAL, "Channel %#x not supported\n", chan);
|
||||
|
||||
channel_name = name;
|
||||
fwnode_property_read_string(fwnode, "label", &channel_name);
|
||||
prop->common_props.label = channel_name;
|
||||
|
||||
value = data->decimation[ADC5_DECIMATION_DEFAULT];
|
||||
fwnode_property_read_u32(fwnode, "qcom,decimation", &value);
|
||||
ret = qcom_adc5_decimation_from_dt(value, data->decimation);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "%#x invalid decimation %d\n",
|
||||
chan, value);
|
||||
prop->common_props.decimation = ret;
|
||||
|
||||
prop->common_props.prescale = adc->data->adc_chans[chan].prescale_index;
|
||||
ret = fwnode_property_read_u32_array(fwnode, "qcom,pre-scaling", varr, 2);
|
||||
if (!ret) {
|
||||
ret = qcom_adc5_prescaling_from_dt(varr[0], varr[1]);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret,
|
||||
"%#x invalid pre-scaling <%d %d>\n",
|
||||
chan, varr[0], varr[1]);
|
||||
prop->common_props.prescale = ret;
|
||||
}
|
||||
|
||||
value = data->hw_settle_1[VADC_DEF_HW_SETTLE_TIME];
|
||||
fwnode_property_read_u32(fwnode, "qcom,hw-settle-time", &value);
|
||||
ret = qcom_adc5_hw_settle_time_from_dt(value, data->hw_settle_1);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret,
|
||||
"%#x invalid hw-settle-time %d us\n",
|
||||
chan, value);
|
||||
prop->common_props.hw_settle_time_us = ret;
|
||||
|
||||
value = BIT(VADC_DEF_AVG_SAMPLES);
|
||||
fwnode_property_read_u32(fwnode, "qcom,avg-samples", &value);
|
||||
ret = qcom_adc5_avg_samples_from_dt(value);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "%#x invalid avg-samples %d\n",
|
||||
chan, value);
|
||||
prop->common_props.avg_samples = ret;
|
||||
|
||||
if (fwnode_property_read_bool(fwnode, "qcom,ratiometric"))
|
||||
prop->common_props.cal_method = ADC5_RATIOMETRIC_CAL;
|
||||
else
|
||||
prop->common_props.cal_method = ADC5_ABSOLUTE_CAL;
|
||||
|
||||
prop->adc_tm = fwnode_property_read_bool(fwnode, "qcom,adc-tm");
|
||||
if (prop->adc_tm) {
|
||||
adc->n_tm_channels++;
|
||||
if (adc->n_tm_channels > (adc->dev_data.num_sdams * 8 - 1))
|
||||
return dev_err_probe(dev, -EINVAL,
|
||||
"Number of TM nodes %u greater than channels supported:%u\n",
|
||||
adc->n_tm_channels,
|
||||
adc->dev_data.num_sdams * 8 - 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct adc5_data adc5_gen3_data_pmic = {
|
||||
.full_scale_code_volt = 0x70e4,
|
||||
.adc_chans = adc5_gen3_chans_pmic,
|
||||
.info = &adc5_gen3_info,
|
||||
.decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
|
||||
{ 85, 340, 1360 },
|
||||
.hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
|
||||
{ 15, 100, 200, 300,
|
||||
400, 500, 600, 700,
|
||||
1000, 2000, 4000, 8000,
|
||||
16000, 32000, 64000, 128000 },
|
||||
};
|
||||
|
||||
static const struct of_device_id adc5_match_table[] = {
|
||||
{
|
||||
.compatible = "qcom,spmi-adc5-gen3",
|
||||
.data = &adc5_gen3_data_pmic,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, adc5_match_table);
|
||||
|
||||
static int adc5_get_fw_data(struct adc5_chip *adc)
|
||||
{
|
||||
const struct adc5_channels *adc_chan;
|
||||
struct adc5_channel_prop *chan_props;
|
||||
struct iio_chan_spec *iio_chan;
|
||||
struct device *dev = adc->dev;
|
||||
unsigned int index = 0;
|
||||
int ret;
|
||||
|
||||
adc->nchannels = device_get_child_node_count(dev);
|
||||
if (!adc->nchannels)
|
||||
return dev_err_probe(dev, -EINVAL, "No ADC channels found\n");
|
||||
|
||||
adc->iio_chans = devm_kcalloc(dev, adc->nchannels,
|
||||
sizeof(*adc->iio_chans), GFP_KERNEL);
|
||||
if (!adc->iio_chans)
|
||||
return -ENOMEM;
|
||||
|
||||
adc->chan_props = devm_kcalloc(dev, adc->nchannels,
|
||||
sizeof(*adc->chan_props), GFP_KERNEL);
|
||||
if (!adc->chan_props)
|
||||
return -ENOMEM;
|
||||
|
||||
chan_props = adc->chan_props;
|
||||
adc->n_tm_channels = 0;
|
||||
iio_chan = adc->iio_chans;
|
||||
adc->data = device_get_match_data(dev);
|
||||
|
||||
device_for_each_child_node_scoped(dev, child) {
|
||||
ret = adc5_gen3_get_fw_channel_data(adc, chan_props, child);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
chan_props->chip = adc;
|
||||
adc_chan = &adc->data->adc_chans[chan_props->common_props.channel];
|
||||
chan_props->common_props.scale_fn_type = adc_chan->scale_fn_type;
|
||||
|
||||
iio_chan->channel = ADC5_GEN3_V_CHAN(chan_props->common_props);
|
||||
iio_chan->info_mask_separate = adc_chan->info_mask;
|
||||
iio_chan->type = adc_chan->type;
|
||||
iio_chan->address = index;
|
||||
iio_chan->indexed = 1;
|
||||
iio_chan++;
|
||||
chan_props++;
|
||||
index++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void adc5_gen3_uninit_aux(void *data)
|
||||
{
|
||||
auxiliary_device_uninit(data);
|
||||
}
|
||||
|
||||
static void adc5_gen3_delete_aux(void *data)
|
||||
{
|
||||
auxiliary_device_delete(data);
|
||||
}
|
||||
|
||||
static void adc5_gen3_aux_device_release(struct device *dev) {}
|
||||
|
||||
static int adc5_gen3_add_aux_tm_device(struct adc5_chip *adc)
|
||||
{
|
||||
struct tm5_aux_dev_wrapper *aux_device;
|
||||
int i, ret, i_tm = 0;
|
||||
|
||||
aux_device = devm_kzalloc(adc->dev, sizeof(*aux_device), GFP_KERNEL);
|
||||
if (!aux_device)
|
||||
return -ENOMEM;
|
||||
|
||||
aux_device->aux_dev.name = "adc5_tm_gen3";
|
||||
aux_device->aux_dev.dev.parent = adc->dev;
|
||||
aux_device->aux_dev.dev.release = adc5_gen3_aux_device_release;
|
||||
|
||||
aux_device->tm_props = devm_kcalloc(adc->dev, adc->n_tm_channels,
|
||||
sizeof(*aux_device->tm_props),
|
||||
GFP_KERNEL);
|
||||
if (!aux_device->tm_props)
|
||||
return -ENOMEM;
|
||||
|
||||
aux_device->dev_data = &adc->dev_data;
|
||||
|
||||
for (i = 0; i < adc->nchannels; i++) {
|
||||
if (!adc->chan_props[i].adc_tm)
|
||||
continue;
|
||||
aux_device->tm_props[i_tm] = adc->chan_props[i].common_props;
|
||||
i_tm++;
|
||||
}
|
||||
|
||||
device_set_of_node_from_dev(&aux_device->aux_dev.dev, adc->dev);
|
||||
|
||||
aux_device->n_tm_channels = adc->n_tm_channels;
|
||||
|
||||
ret = auxiliary_device_init(&aux_device->aux_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(adc->dev, adc5_gen3_uninit_aux,
|
||||
&aux_device->aux_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = auxiliary_device_add(&aux_device->aux_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = devm_add_action_or_reset(adc->dev, adc5_gen3_delete_aux,
|
||||
&aux_device->aux_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
adc->tm_aux = &aux_device->aux_dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void adc5_gen3_mutex_lock(struct device *dev)
|
||||
__acquires(&adc->lock)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev->parent);
|
||||
struct adc5_chip *adc = iio_priv(indio_dev);
|
||||
|
||||
mutex_lock(&adc->lock);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(adc5_gen3_mutex_lock, "QCOM_SPMI_ADC5_GEN3");
|
||||
|
||||
void adc5_gen3_mutex_unlock(struct device *dev)
|
||||
__releases(&adc->lock)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev->parent);
|
||||
struct adc5_chip *adc = iio_priv(indio_dev);
|
||||
|
||||
mutex_unlock(&adc->lock);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(adc5_gen3_mutex_unlock, "QCOM_SPMI_ADC5_GEN3");
|
||||
|
||||
int adc5_gen3_get_scaled_reading(struct device *dev,
|
||||
struct adc5_channel_common_prop *common_props,
|
||||
int *val)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev->parent);
|
||||
struct adc5_chip *adc = iio_priv(indio_dev);
|
||||
u16 adc_code_volt;
|
||||
int ret;
|
||||
|
||||
ret = adc5_gen3_do_conversion(adc, common_props, &adc_code_volt);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return qcom_adc5_hw_scale(common_props->scale_fn_type,
|
||||
common_props->prescale,
|
||||
adc->data, adc_code_volt, val);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(adc5_gen3_get_scaled_reading, "QCOM_SPMI_ADC5_GEN3");
|
||||
|
||||
int adc5_gen3_therm_code_to_temp(struct device *dev,
|
||||
struct adc5_channel_common_prop *common_props,
|
||||
u16 code, int *val)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev->parent);
|
||||
struct adc5_chip *adc = iio_priv(indio_dev);
|
||||
|
||||
return qcom_adc5_hw_scale(common_props->scale_fn_type,
|
||||
common_props->prescale,
|
||||
adc->data, code, val);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(adc5_gen3_therm_code_to_temp, "QCOM_SPMI_ADC5_GEN3");
|
||||
|
||||
void adc5_gen3_register_tm_event_notifier(struct device *dev,
|
||||
void (*handler)(struct auxiliary_device *))
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev->parent);
|
||||
struct adc5_chip *adc = iio_priv(indio_dev);
|
||||
|
||||
adc->handler = handler;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(adc5_gen3_register_tm_event_notifier, "QCOM_SPMI_ADC5_GEN3");
|
||||
|
||||
static int adc5_gen3_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct iio_dev *indio_dev;
|
||||
struct adc5_chip *adc;
|
||||
struct regmap *regmap;
|
||||
int ret, i;
|
||||
u32 *reg;
|
||||
|
||||
regmap = dev_get_regmap(dev->parent, NULL);
|
||||
if (!regmap)
|
||||
return -ENODEV;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
adc = iio_priv(indio_dev);
|
||||
adc->dev_data.regmap = regmap;
|
||||
adc->dev = dev;
|
||||
|
||||
ret = device_property_count_u32(dev, "reg");
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
adc->dev_data.num_sdams = ret;
|
||||
|
||||
reg = devm_kcalloc(dev, adc->dev_data.num_sdams, sizeof(u32),
|
||||
GFP_KERNEL);
|
||||
if (!reg)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = device_property_read_u32_array(dev, "reg", reg,
|
||||
adc->dev_data.num_sdams);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Failed to read reg property\n");
|
||||
|
||||
adc->dev_data.base = devm_kcalloc(dev, adc->dev_data.num_sdams,
|
||||
sizeof(*adc->dev_data.base),
|
||||
GFP_KERNEL);
|
||||
if (!adc->dev_data.base)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
init_completion(&adc->complete);
|
||||
ret = devm_mutex_init(dev, &adc->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < adc->dev_data.num_sdams; i++) {
|
||||
adc->dev_data.base[i].base_addr = reg[i];
|
||||
|
||||
ret = platform_get_irq(pdev, i);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Getting IRQ %d failed\n", i);
|
||||
|
||||
adc->dev_data.base[i].irq = ret;
|
||||
|
||||
adc->dev_data.base[i].irq_name = devm_kasprintf(dev, GFP_KERNEL,
|
||||
"sdam%d", i);
|
||||
if (!adc->dev_data.base[i].irq_name)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(dev, adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq,
|
||||
adc5_gen3_isr, 0,
|
||||
adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq_name,
|
||||
adc);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Failed to request SDAM%d irq\n",
|
||||
ADC5_GEN3_VADC_SDAM);
|
||||
|
||||
ret = adc5_get_fw_data(adc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (adc->n_tm_channels > 0) {
|
||||
ret = adc5_gen3_add_aux_tm_device(adc);
|
||||
if (ret)
|
||||
dev_err_probe(dev, ret,
|
||||
"Failed to add auxiliary TM device\n");
|
||||
}
|
||||
|
||||
indio_dev->name = "spmi-adc5-gen3";
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->info = &adc5_gen3_info;
|
||||
indio_dev->channels = adc->iio_chans;
|
||||
indio_dev->num_channels = adc->nchannels;
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
|
||||
static struct platform_driver adc5_gen3_driver = {
|
||||
.driver = {
|
||||
.name = "qcom-spmi-adc5-gen3",
|
||||
.of_match_table = adc5_match_table,
|
||||
},
|
||||
.probe = adc5_gen3_probe,
|
||||
};
|
||||
module_platform_driver(adc5_gen3_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Qualcomm Technologies Inc. PMIC5 Gen3 ADC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("QCOM_SPMI_ADC5_GEN3");
|
||||
211
include/linux/iio/adc/qcom-adc5-gen3-common.h
Normal file
211
include/linux/iio/adc/qcom-adc5-gen3-common.h
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
|
||||
*
|
||||
* Code used in the main and auxiliary Qualcomm PMIC voltage ADCs
|
||||
* of type ADC5 Gen3.
|
||||
*/
|
||||
|
||||
#ifndef QCOM_ADC5_GEN3_COMMON_H
|
||||
#define QCOM_ADC5_GEN3_COMMON_H
|
||||
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/iio/adc/qcom-vadc-common.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define ADC5_GEN3_HS 0x45
|
||||
#define ADC5_GEN3_HS_BUSY BIT(7)
|
||||
#define ADC5_GEN3_HS_READY BIT(0)
|
||||
|
||||
#define ADC5_GEN3_STATUS1 0x46
|
||||
#define ADC5_GEN3_STATUS1_CONV_FAULT BIT(7)
|
||||
#define ADC5_GEN3_STATUS1_THR_CROSS BIT(6)
|
||||
#define ADC5_GEN3_STATUS1_EOC BIT(0)
|
||||
|
||||
#define ADC5_GEN3_TM_EN_STS 0x47
|
||||
#define ADC5_GEN3_TM_HIGH_STS 0x48
|
||||
#define ADC5_GEN3_TM_LOW_STS 0x49
|
||||
|
||||
#define ADC5_GEN3_EOC_STS 0x4a
|
||||
#define ADC5_GEN3_EOC_CHAN_0 BIT(0)
|
||||
|
||||
#define ADC5_GEN3_EOC_CLR 0x4b
|
||||
#define ADC5_GEN3_TM_HIGH_STS_CLR 0x4c
|
||||
#define ADC5_GEN3_TM_LOW_STS_CLR 0x4d
|
||||
#define ADC5_GEN3_CONV_ERR_CLR 0x4e
|
||||
#define ADC5_GEN3_CONV_ERR_CLR_REQ BIT(0)
|
||||
|
||||
#define ADC5_GEN3_SID 0x4f
|
||||
#define ADC5_GEN3_SID_MASK GENMASK(3, 0)
|
||||
|
||||
#define ADC5_GEN3_PERPH_CH 0x50
|
||||
#define ADC5_GEN3_CHAN_CONV_REQ BIT(7)
|
||||
|
||||
#define ADC5_GEN3_TIMER_SEL 0x51
|
||||
#define ADC5_GEN3_TIME_IMMEDIATE 0x1
|
||||
|
||||
#define ADC5_GEN3_DIG_PARAM 0x52
|
||||
#define ADC5_GEN3_DIG_PARAM_CAL_SEL_MASK GENMASK(5, 4)
|
||||
#define ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_MASK GENMASK(3, 2)
|
||||
|
||||
#define ADC5_GEN3_FAST_AVG 0x53
|
||||
#define ADC5_GEN3_FAST_AVG_CTL_EN BIT(7)
|
||||
#define ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK GENMASK(2, 0)
|
||||
|
||||
#define ADC5_GEN3_ADC_CH_SEL_CTL 0x54
|
||||
#define ADC5_GEN3_DELAY_CTL 0x55
|
||||
#define ADC5_GEN3_HW_SETTLE_DELAY_MASK GENMASK(3, 0)
|
||||
|
||||
#define ADC5_GEN3_CH_EN 0x56
|
||||
#define ADC5_GEN3_HIGH_THR_INT_EN BIT(1)
|
||||
#define ADC5_GEN3_LOW_THR_INT_EN BIT(0)
|
||||
|
||||
#define ADC5_GEN3_LOW_THR0 0x57
|
||||
#define ADC5_GEN3_LOW_THR1 0x58
|
||||
#define ADC5_GEN3_HIGH_THR0 0x59
|
||||
#define ADC5_GEN3_HIGH_THR1 0x5a
|
||||
|
||||
#define ADC5_GEN3_CH_DATA0(channel) (0x5c + (channel) * 2)
|
||||
#define ADC5_GEN3_CH_DATA1(channel) (0x5d + (channel) * 2)
|
||||
|
||||
#define ADC5_GEN3_CONV_REQ 0xe5
|
||||
#define ADC5_GEN3_CONV_REQ_REQ BIT(0)
|
||||
|
||||
#define ADC5_GEN3_VIRTUAL_SID_MASK GENMASK(15, 8)
|
||||
#define ADC5_GEN3_CHANNEL_MASK GENMASK(7, 0)
|
||||
#define ADC5_GEN3_V_CHAN(x) \
|
||||
(FIELD_PREP(ADC5_GEN3_VIRTUAL_SID_MASK, (x).sid) | (x).channel)
|
||||
|
||||
/* ADC channels for PMIC5 Gen3 */
|
||||
#define ADC5_GEN3_REF_GND 0x00
|
||||
#define ADC5_GEN3_1P25VREF 0x01
|
||||
#define ADC5_GEN3_DIE_TEMP 0x03
|
||||
#define ADC5_GEN3_USB_SNS_V_16 0x11
|
||||
#define ADC5_GEN3_VIN_DIV16_MUX 0x12
|
||||
#define ADC5_GEN3_VPH_PWR 0x8e
|
||||
#define ADC5_GEN3_VBAT_SNS_QBG 0x8f
|
||||
/* 100k pull-up channels */
|
||||
#define ADC5_GEN3_AMUX1_THM_100K_PU 0x44
|
||||
#define ADC5_GEN3_AMUX2_THM_100K_PU 0x45
|
||||
#define ADC5_GEN3_AMUX3_THM_100K_PU 0x46
|
||||
#define ADC5_GEN3_AMUX4_THM_100K_PU 0x47
|
||||
#define ADC5_GEN3_AMUX5_THM_100K_PU 0x48
|
||||
#define ADC5_GEN3_AMUX6_THM_100K_PU 0x49
|
||||
#define ADC5_GEN3_AMUX1_GPIO_100K_PU 0x4a
|
||||
#define ADC5_GEN3_AMUX2_GPIO_100K_PU 0x4b
|
||||
#define ADC5_GEN3_AMUX3_GPIO_100K_PU 0x4c
|
||||
#define ADC5_GEN3_AMUX4_GPIO_100K_PU 0x4d
|
||||
|
||||
#define ADC5_MAX_CHANNEL 0xc0
|
||||
|
||||
enum adc5_cal_method {
|
||||
ADC5_NO_CAL = 0,
|
||||
ADC5_RATIOMETRIC_CAL,
|
||||
ADC5_ABSOLUTE_CAL,
|
||||
};
|
||||
|
||||
enum adc5_time_select {
|
||||
MEAS_INT_DISABLE = 0,
|
||||
MEAS_INT_IMMEDIATE,
|
||||
MEAS_INT_50MS,
|
||||
MEAS_INT_100MS,
|
||||
MEAS_INT_1S,
|
||||
MEAS_INT_NONE,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adc5_sdam_data - data per SDAM allocated for adc usage
|
||||
* @base_addr: base address for the ADC SDAM peripheral.
|
||||
* @irq_name: ADC IRQ name.
|
||||
* @irq: ADC IRQ number.
|
||||
*/
|
||||
struct adc5_sdam_data {
|
||||
u16 base_addr;
|
||||
const char *irq_name;
|
||||
int irq;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adc5_device_data - Top-level ADC device data
|
||||
* @regmap: ADC peripheral register map field.
|
||||
* @base: array of SDAM data.
|
||||
* @num_sdams: number of ADC SDAM peripherals.
|
||||
*/
|
||||
struct adc5_device_data {
|
||||
struct regmap *regmap;
|
||||
struct adc5_sdam_data *base;
|
||||
int num_sdams;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adc5_channel_common_prop - ADC channel properties (common to ADC and TM).
|
||||
* @channel: channel number, refer to the channel list.
|
||||
* @cal_method: calibration method.
|
||||
* @decimation: sampling rate supported for the channel.
|
||||
* @sid: ID of PMIC owning the channel.
|
||||
* @label: Channel name used in device tree.
|
||||
* @prescale: channel scaling performed on the input signal.
|
||||
* @hw_settle_time_us: the time between AMUX being configured and the
|
||||
* start of conversion in uS.
|
||||
* @avg_samples: ability to provide single result from the ADC
|
||||
* that is an average of multiple measurements.
|
||||
* @scale_fn_type: Represents the scaling function to convert voltage
|
||||
* physical units desired by the client for the channel.
|
||||
*/
|
||||
struct adc5_channel_common_prop {
|
||||
unsigned int channel;
|
||||
enum adc5_cal_method cal_method;
|
||||
unsigned int decimation;
|
||||
unsigned int sid;
|
||||
const char *label;
|
||||
unsigned int prescale;
|
||||
unsigned int hw_settle_time_us;
|
||||
unsigned int avg_samples;
|
||||
enum vadc_scale_fn_type scale_fn_type;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct tm5_aux_dev_wrapper - wrapper structure around TM auxiliary device
|
||||
* @aux_dev: TM auxiliary device structure.
|
||||
* @dev_data: Top-level ADC device data.
|
||||
* @tm_props: Array of common ADC channel properties for TM channels.
|
||||
* @n_tm_channels: number of TM channels.
|
||||
*/
|
||||
struct tm5_aux_dev_wrapper {
|
||||
struct auxiliary_device aux_dev;
|
||||
struct adc5_device_data *dev_data;
|
||||
struct adc5_channel_common_prop *tm_props;
|
||||
unsigned int n_tm_channels;
|
||||
};
|
||||
|
||||
int adc5_gen3_read(struct adc5_device_data *adc, unsigned int sdam_index,
|
||||
u16 offset, u8 *data, int len);
|
||||
|
||||
int adc5_gen3_write(struct adc5_device_data *adc, unsigned int sdam_index,
|
||||
u16 offset, u8 *data, int len);
|
||||
|
||||
int adc5_gen3_poll_wait_hs(struct adc5_device_data *adc,
|
||||
unsigned int sdam_index);
|
||||
|
||||
void adc5_gen3_update_dig_param(struct adc5_channel_common_prop *prop,
|
||||
u8 *data);
|
||||
|
||||
int adc5_gen3_status_clear(struct adc5_device_data *adc,
|
||||
int sdam_index, u16 offset, u8 *val, int len);
|
||||
|
||||
void adc5_gen3_mutex_lock(struct device *dev);
|
||||
void adc5_gen3_mutex_unlock(struct device *dev);
|
||||
int adc5_gen3_get_scaled_reading(struct device *dev,
|
||||
struct adc5_channel_common_prop *common_props,
|
||||
int *val);
|
||||
int adc5_gen3_therm_code_to_temp(struct device *dev,
|
||||
struct adc5_channel_common_prop *common_props,
|
||||
u16 code, int *val);
|
||||
void adc5_gen3_register_tm_event_notifier(struct device *dev,
|
||||
void (*handler)(struct auxiliary_device *));
|
||||
|
||||
#endif /* QCOM_ADC5_GEN3_COMMON_H */
|
||||
Loading…
Reference in New Issue
Block a user