[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20190604144714.2009-4-glaroque@baylibre.com>
Date: Tue, 4 Jun 2019 16:47:14 +0200
From: Guillaume La Roque <glaroque@...libre.com>
To: jic23@...nel.org, khilman@...libre.com
Cc: linux-iio@...r.kernel.org, devicetree@...r.kernel.org,
linux-arm-kernel@...ts.infradead.org,
linux-amlogic@...ts.infradead.org, linux-kernel@...r.kernel.org
Subject: [PATCH 3/3] iio: temperature: add a driver for the temperature sensor found in Amlogic Meson G12 SoCs
The code is based on Amlogic source code. No public datasheet for this.
Currently the G12A SoCs are supported.
Supported features:
- possibility to set an automatic reboot temperature
Signed-off-by: Guillaume La Roque <glaroque@...libre.com>
---
drivers/iio/temperature/Kconfig | 11 +
drivers/iio/temperature/Makefile | 1 +
drivers/iio/temperature/meson_tsensor.c | 416 ++++++++++++++++++++++++
3 files changed, 428 insertions(+)
create mode 100644 drivers/iio/temperature/meson_tsensor.c
diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig
index 737faa0901fe..712a0062790d 100644
--- a/drivers/iio/temperature/Kconfig
+++ b/drivers/iio/temperature/Kconfig
@@ -34,6 +34,17 @@ config HID_SENSOR_TEMP
To compile this driver as a module, choose M here: the module
will be called hid-sensor-temperature.
+config MESON_TSENSOR
+ tristate "Amlogic Meson temperature sensor Support"
+ default ARCH_MESON
+ depends on OF && ARCH_MESON
+ help
+ If you say yess here you get support for Meson Temperature sensor
+ for G12 SoC Family.
+
+ This driver can also be built as a module. If so, the module will
+ be called meson_tsensor.
+
config MLX90614
tristate "MLX90614 contact-less infrared sensor"
depends on I2C
diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile
index baca4776ca0d..466d8c1c91d6 100644
--- a/drivers/iio/temperature/Makefile
+++ b/drivers/iio/temperature/Makefile
@@ -6,6 +6,7 @@
obj-$(CONFIG_HID_SENSOR_TEMP) += hid-sensor-temperature.o
obj-$(CONFIG_MAXIM_THERMOCOUPLE) += maxim_thermocouple.o
obj-$(CONFIG_MAX31856) += max31856.o
+obj-$(CONFIG_MESON_TSENSOR) += meson_tsensor.o
obj-$(CONFIG_MLX90614) += mlx90614.o
obj-$(CONFIG_MLX90632) += mlx90632.o
obj-$(CONFIG_TMP006) += tmp006.o
diff --git a/drivers/iio/temperature/meson_tsensor.c b/drivers/iio/temperature/meson_tsensor.c
new file mode 100644
index 000000000000..be0a8d073ba3
--- /dev/null
+++ b/drivers/iio/temperature/meson_tsensor.c
@@ -0,0 +1,416 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Amlogic Meson Temperature Sensor
+ *
+ * Copyright (C) 2017 Huan Biao <huan.biao@...ogic.com>
+ * Copyright (C) 2019 Guillaume La Roque <glaroque@...libre.com>
+ *
+ * Register value to celsius temperature formulas:
+ * Read_Val m * U
+ * U = ---------, Uptat = ---------
+ * 2^16 1 + n * U
+ *
+ * Temperature = A * ( Uptat + u_efuse / 2^16 )- B
+ *
+ * A B m n : calibration parameters
+ * u_efuse : fused calibration value, it's a signed 16 bits value
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/iio/iio.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define TSENSOR_CFG_REG1 0x4
+ #define TSENSOR_CFG_REG1_RSET_VBG BIT(12)
+ #define TSENSOR_CFG_REG1_RSET_ADC BIT(11)
+ #define TSENSOR_CFG_REG1_VCM_EN BIT(10)
+ #define TSENSOR_CFG_REG1_VBG_EN BIT(9)
+ #define TSENSOR_CFG_REG1_OUT_CTL BIT(6)
+ #define TSENSOR_CFG_REG1_FILTER_EN BIT(5)
+ #define TSENSOR_CFG_REG1_DEM_EN BIT(3)
+ #define TSENSOR_CFG_REG1_CH_SEL GENMASK(1, 0)
+ #define TSENSOR_CFG_REG1_ENABLE \
+ (TSENSOR_CFG_REG1_FILTER_EN | \
+ TSENSOR_CFG_REG1_VCM_EN | \
+ TSENSOR_CFG_REG1_VBG_EN | \
+ TSENSOR_CFG_REG1_DEM_EN | \
+ TSENSOR_CFG_REG1_CH_SEL)
+
+#define TSENSOR_CFG_REG2 0x8
+ #define TSENSOR_CFG_REG2_HITEMP_EN BIT(31)
+ #define TSENSOR_CFG_REG2_REBOOT_ALL_EN BIT(30)
+ #define TSENSOR_CFG_REG2_REBOOT_TIME GENMASK(25, 16)
+ #define TSENSOR_CFG_REG2_HITEMP_REBOOT_ENABLE \
+ (TSENSOR_CFG_REG2_HITEMP_EN | \
+ TSENSOR_CFG_REG2_REBOOT_ALL_EN | \
+ TSENSOR_CFG_REG2_REBOOT_TIME)
+ #define TSENSOR_CFG_REG2_HITEMP_REBOOT_ENABLE_MASK \
+ (GENMASK(31, 30) | GENMASK(25, 4))
+ #define TSENSOR_CFG_REG2_HITEMP_REBOOT_REG_MASK \
+ GENMASK(15, 4)
+ #define TSENSOR_CFG_REG2_HITEMP_REG_VAL(_reg_val) \
+ (FIELD_PREP(TSENSOR_CFG_REG2_HITEMP_REBOOT_REG_MASK, \
+ _reg_val) | \
+ TSENSOR_CFG_REG2_HITEMP_REBOOT_ENABLE)
+
+#define TSENSOR_CFG_REG3 0xC
+#define TSENSOR_CFG_REG4 0x10
+#define TSENSOR_CFG_REG5 0x14
+#define TSENSOR_CFG_REG6 0x18
+#define TSENSOR_CFG_REG7 0x1C
+#define TSENSOR_CFG_REG8 0x20
+
+#define TSENSOR_STAT0 0x40
+
+#define TSENSOR_STAT9 0x64
+
+#define TSENSOR_READ_TEMP_MASK GENMASK(15, 0)
+#define TSENSOR_TEMP_MASK GENMASK(11, 0)
+
+#define TSENSOR_TRIM_SIGN_MASK BIT(15)
+#define TSENSOR_TRIM_TEMP_MASK GENMASK(14, 0)
+#define TSENSOR_TRIM_VERSION_MASK GENMASK(31, 24)
+
+#define TSENSOR_TRIM_VERSION(_version) \
+ FIELD_GET(TSENSOR_TRIM_VERSION_MASK, _version)
+
+#define TSENSOR_TRIM_CALIB_VALID_MASK (GENMASK(3, 2) | BIT(7))
+
+#define TSENSOR_CALIB_OFFSET 1
+#define TSENSOR_CALIB_SHIFT 4
+
+static const struct iio_chan_spec temperature_channel[] = {
+ {
+ .type = IIO_TEMP,
+ .channel = 0,
+ .address = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ },
+};
+
+/**
+ * struct meson_tsensor_soc_data
+ * @A, B, m, n: calibration parameters
+ * This structure is required for configuration of meson tsensor driver.
+ */
+struct meson_tsensor_soc_data {
+ int A;
+ int B;
+ int m;
+ int n;
+};
+
+/**
+ * struct meson_tsensor_data
+ * @u_efuse_off: register offset to read fused calibration value
+ * @soc: calibration parameters structure pointer
+ * @regmap_config: regmap config for the device
+ * This structure is required for configuration of meson tsensor driver.
+ */
+struct meson_tsensor_data {
+ int u_efuse_off;
+ const struct meson_tsensor_soc_data *soc;
+ const struct regmap_config *regmap_config;
+};
+
+struct meson_tsensor {
+ int id;
+ const struct meson_tsensor_data *data;
+ struct regmap *regmap;
+ struct regmap *sec_ao_map;
+ struct clk *clk;
+ u32 trim_info;
+ void __iomem *base;
+ int reboot_temp;
+};
+
+/*
+ * tsensor treats temperature as a mapped temperature code.
+ * The temperature is converted differently depending on the calibration type.
+ */
+static u32 temp_to_code(struct meson_tsensor *priv, int temp)
+{
+ const struct meson_tsensor_soc_data *param = priv->data->soc;
+ s64 divisor, factor, uefuse;
+ u32 reg_code;
+
+ uefuse = priv->trim_info & TSENSOR_TRIM_SIGN_MASK ?
+ ~(priv->trim_info & TSENSOR_TRIM_TEMP_MASK) + 1 :
+ (priv->trim_info & TSENSOR_TRIM_TEMP_MASK);
+
+ factor = BIT(16) * (temp * 10 + param->B);
+ factor = div_s64(factor, param->A);
+ factor = factor + uefuse;
+
+ factor = factor * 100;
+
+ divisor = param->n * factor;
+ divisor = div_s64(divisor, BIT(16));
+ divisor = param->m - divisor;
+
+ reg_code = div_s64(factor, divisor);
+ reg_code = ((reg_code >> TSENSOR_CALIB_SHIFT) & TSENSOR_TEMP_MASK) +
+ TSENSOR_CALIB_OFFSET;
+
+ return reg_code;
+}
+
+/*
+ * Calculate a temperature value from a temperature code.
+ * The unit of the temperature is degree Celsius.
+ */
+static int code_to_temp(struct meson_tsensor *priv, int temp_code)
+{
+ const struct meson_tsensor_soc_data *param = priv->data->soc;
+ int temp;
+ s64 factor, Uptat, uefuse;
+
+ uefuse = priv->trim_info & TSENSOR_TRIM_SIGN_MASK ?
+ ~(priv->trim_info & TSENSOR_TRIM_TEMP_MASK) + 1 :
+ (priv->trim_info & TSENSOR_TRIM_TEMP_MASK);
+
+ factor = param->n * temp_code;
+ factor = div_s64(factor, 100);
+
+ Uptat = temp_code * param->m;
+ Uptat = div_s64(Uptat, 100);
+ Uptat = Uptat * BIT(16);
+ Uptat = div_s64(Uptat, BIT(16) + factor);
+
+ temp = (Uptat + uefuse) * param->A;
+ temp = div_s64(temp, BIT(16));
+ temp = (temp - param->B) * 100;
+
+ return temp;
+}
+
+static int meson_tsensor_initialize(struct iio_dev *indio_dev)
+{
+ struct meson_tsensor *priv = iio_priv(indio_dev);
+ u32 reg_val;
+ int ret = 0;
+ int ver;
+
+ regmap_read(priv->sec_ao_map, priv->data->u_efuse_off,
+ &priv->trim_info);
+
+ ver = TSENSOR_TRIM_VERSION(priv->trim_info);
+
+ if ((ver & TSENSOR_TRIM_CALIB_VALID_MASK) == 0) {
+ ret = -EINVAL;
+ dev_err(&indio_dev->dev,
+ "tsensor thermal calibration not supported: 0x%x!\n",
+ ver);
+ goto out;
+ }
+
+ /* init the ts reboot soc function */
+ if (priv->reboot_temp) {
+ /* register need value in celsius */
+ reg_val = temp_to_code(priv, priv->reboot_temp / 1000);
+ regmap_update_bits(priv->regmap, TSENSOR_CFG_REG2,
+ TSENSOR_CFG_REG2_HITEMP_REBOOT_ENABLE_MASK,
+ TSENSOR_CFG_REG2_HITEMP_REG_VAL(reg_val));
+ }
+
+out:
+ return ret;
+}
+
+static int meson_tsensor_enable(struct iio_dev *indio_dev)
+{
+ struct meson_tsensor *priv = iio_priv(indio_dev);
+
+ clk_prepare_enable(priv->clk);
+ regmap_update_bits(priv->regmap, TSENSOR_CFG_REG1,
+ TSENSOR_CFG_REG1_ENABLE, TSENSOR_CFG_REG1_ENABLE);
+
+ return 0;
+}
+
+static int meson_tsensor_disable(struct iio_dev *indio_dev)
+{
+ struct meson_tsensor *priv = iio_priv(indio_dev);
+
+ regmap_update_bits(priv->regmap, TSENSOR_CFG_REG1,
+ TSENSOR_CFG_REG1_ENABLE, 0);
+ clk_disable(priv->clk);
+
+ return 0;
+}
+
+static int meson_tsensor_read(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ unsigned int tvalue;
+ struct meson_tsensor *priv = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ regmap_read(priv->regmap, TSENSOR_STAT0, &tvalue);
+ *val = code_to_temp(priv,
+ tvalue & TSENSOR_READ_TEMP_MASK);
+
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info meson_tsensor_iio_info = {
+ .read_raw = &meson_tsensor_read,
+};
+
+static const struct regmap_config meson_tsensor_regmap_config_g12a = {
+ .reg_bits = 8,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = TSENSOR_STAT9,
+};
+
+static const struct meson_tsensor_soc_data meson_tsensor_g12a = {
+ .A = 9411,
+ .B = 3159,
+ .m = 424,
+ .n = 324,
+};
+
+static const struct meson_tsensor_data meson_tsensor_g12a_cpu_param = {
+ .u_efuse_off = 0x128,
+ .soc = &meson_tsensor_g12a,
+ .regmap_config = &meson_tsensor_regmap_config_g12a,
+};
+
+static const struct meson_tsensor_data meson_tsensor_g12a_ddr_param = {
+ .u_efuse_off = 0xF0,
+ .soc = &meson_tsensor_g12a,
+ .regmap_config = &meson_tsensor_regmap_config_g12a,
+};
+
+static const struct of_device_id meson_tsensor_of_match[] = {
+ {
+ .compatible = "amlogic,meson-g12a-ddr-tsensor",
+ .data = &meson_tsensor_g12a_ddr_param,
+ },
+ {
+ .compatible = "amlogic,meson-g12a-cpu-tsensor",
+ .data = &meson_tsensor_g12a_cpu_param,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, meson_tsensor_of_match);
+
+static int meson_tsensor_probe(struct platform_device *pdev)
+{
+ struct meson_tsensor *priv;
+ struct iio_dev *indio_dev;
+ struct resource *res;
+
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv));
+ if (!indio_dev) {
+ dev_err(&pdev->dev, "failed allocating iio device\n");
+ return -ENOMEM;
+ }
+
+ priv = iio_priv(indio_dev);
+ priv->data = of_device_get_match_data(&pdev->dev);
+ if (!priv->data) {
+ dev_err(&pdev->dev, "failed to get match data\n");
+ return -ENODEV;
+ }
+
+ indio_dev->channels = temperature_channel;
+ indio_dev->num_channels = ARRAY_SIZE(temperature_channel);
+ indio_dev->name = dev_name(&pdev->dev);
+ indio_dev->dev.parent = &pdev->dev;
+ indio_dev->dev.of_node = pdev->dev.of_node;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &meson_tsensor_iio_info;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->regmap = devm_regmap_init_mmio(&pdev->dev, priv->base,
+ priv->data->regmap_config);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ priv->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(priv->clk)) {
+ if (PTR_ERR(priv->clk) != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "failed to get clock\n");
+ return PTR_ERR(priv->clk);
+ }
+
+ if (of_property_read_u32(pdev->dev.of_node,
+ "amlogic,critical-temperature",
+ &priv->reboot_temp)) {
+ priv->reboot_temp = 0;
+ }
+
+ priv->sec_ao_map = syscon_regmap_lookup_by_phandle
+ (pdev->dev.of_node, "amlogic,ao-secure");
+ if (IS_ERR(priv->sec_ao_map)) {
+ dev_err(&pdev->dev, "syscon regmap lookup failed.\n");
+ return PTR_ERR(priv->sec_ao_map);
+ }
+
+ ret = meson_tsensor_initialize(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = meson_tsensor_enable(indio_dev);
+ if (ret)
+ goto err;
+
+ platform_set_drvdata(pdev, indio_dev);
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto err_hw;
+
+ return 0;
+
+err_hw:
+ meson_tsensor_disable(indio_dev);
+err:
+ clk_unprepare(priv->clk);
+
+ return ret;
+}
+
+static int meson_tsensor_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+
+ iio_device_unregister(indio_dev);
+
+ return meson_tsensor_disable(indio_dev);
+}
+
+static struct platform_driver meson_tsensor_driver = {
+ .probe = meson_tsensor_probe,
+ .remove = meson_tsensor_remove,
+ .driver = {
+ .name = "meson-tsensor",
+ .of_match_table = meson_tsensor_of_match,
+ },
+};
+
+module_platform_driver(meson_tsensor_driver);
+
+MODULE_AUTHOR("Guillaume La Roque <glaroque@...libre.com>");
+MODULE_DESCRIPTION("Amlogic Meson Temperature Sensor Driver");
+MODULE_LICENSE("GPL v2");
--
2.17.1
Powered by blists - more mailing lists