From 9da4c28e6a7963f67e2ca05098683445a1571538 Mon Sep 17 00:00:00 2001 From: Ondrej Jirman Date: Tue, 14 Nov 2017 17:15:54 +0100 Subject: [PATCH 05/82] thermal: sun8i: Add support for A83T thermal sensors Signed-off-by: Ondrej Jirman --- drivers/thermal/sun8i_ths.c | 371 ++++++++++++++++++++++++++---------- 1 file changed, 274 insertions(+), 97 deletions(-) diff --git a/drivers/thermal/sun8i_ths.c b/drivers/thermal/sun8i_ths.c index cfe7d1073b8c..2fda940da4cc 100644 --- a/drivers/thermal/sun8i_ths.c +++ b/drivers/thermal/sun8i_ths.c @@ -1,5 +1,5 @@ /* - * Thermal sensor driver for Allwinner H3 SoC + * Thermal sensor driver for Allwinner SUN8I SoC * * Copyright (C) 2016 Ondřej Jirman * Based on the work of Josef Gajdusek @@ -26,79 +26,174 @@ #include #include -#define THS_H3_CTRL0 0x00 -#define THS_H3_CTRL2 0x40 -#define THS_H3_INT_CTRL 0x44 -#define THS_H3_STAT 0x48 -#define THS_H3_FILTER 0x70 -#define THS_H3_CDATA 0x74 -#define THS_H3_DATA 0x80 - -#define THS_H3_CTRL0_SENSOR_ACQ0(x) (x) -#define THS_H3_CTRL2_SENSE_EN BIT(0) -#define THS_H3_CTRL2_SENSOR_ACQ1(x) ((x) << 16) -#define THS_H3_INT_CTRL_DATA_IRQ_EN BIT(8) -#define THS_H3_INT_CTRL_THERMAL_PER(x) ((x) << 12) -#define THS_H3_STAT_DATA_IRQ_STS BIT(8) -#define THS_H3_FILTER_TYPE(x) ((x) << 0) -#define THS_H3_FILTER_EN BIT(2) - -#define THS_H3_CLK_IN 40000000 /* Hz */ -#define THS_H3_DATA_PERIOD 330 /* ms */ - -#define THS_H3_FILTER_TYPE_VALUE 2 /* average over 2^(n+1) samples */ -#define THS_H3_FILTER_DIV (1 << (THS_H3_FILTER_TYPE_VALUE + 1)) -#define THS_H3_INT_CTRL_THERMAL_PER_VALUE \ - (THS_H3_DATA_PERIOD * (THS_H3_CLK_IN / 1000) / THS_H3_FILTER_DIV / 4096 - 1) -#define THS_H3_CTRL0_SENSOR_ACQ0_VALUE 0x3f /* 16us */ -#define THS_H3_CTRL2_SENSOR_ACQ1_VALUE 0x3f +#define THS_SUN8I_CTRL0 0x00 +#define THS_SUN8I_CTRL2 0x40 +#define THS_SUN8I_INT_CTRL 0x44 +#define THS_SUN8I_STAT 0x48 +#define THS_SUN8I_FILTER 0x70 +#define THS_SUN8I_CDATA01 0x74 +#define THS_SUN8I_CDATA2 0x78 +#define THS_SUN8I_DATA0 0x80 +#define THS_SUN8I_DATA1 0x84 +#define THS_SUN8I_DATA2 0x88 + +#define THS_SUN8I_CTRL0_SENSOR_ACQ0(x) (x) +#define THS_SUN8I_CTRL2_SENSE_EN0 BIT(0) +#define THS_SUN8I_CTRL2_SENSE_EN1 BIT(1) +#define THS_SUN8I_CTRL2_SENSE_EN2 BIT(2) +#define THS_SUN8I_CTRL2_SENSOR_ACQ1(x) ((x) << 16) +#define THS_SUN8I_INT_CTRL_DATA0_IRQ_EN BIT(8) +#define THS_SUN8I_INT_CTRL_DATA1_IRQ_EN BIT(9) +#define THS_SUN8I_INT_CTRL_DATA2_IRQ_EN BIT(10) +#define THS_SUN8I_INT_CTRL_THERMAL_PER(x) ((x) << 12) +#define THS_SUN8I_STAT_DATA0_IRQ_STS BIT(8) +#define THS_SUN8I_STAT_DATA1_IRQ_STS BIT(9) +#define THS_SUN8I_STAT_DATA2_IRQ_STS BIT(10) +#define THS_SUN8I_STAT_CLEAR 0x777 +#define THS_SUN8I_FILTER_TYPE(x) ((x) << 0) +#define THS_SUN8I_FILTER_EN BIT(2) + +#define THS_SUN8I_CLK_IN 40000000 /* Hz */ +#define THS_SUN8I_DATA_PERIOD 330 /* ms */ +#define THS_SUN8I_FILTER_TYPE_VALUE 2 /* average over 2^(n+1) samples */ + +//XXX: this formula doesn't work for A83T very well +//XXX: A83T is getting slower readings out of this (1s interval?) +//perhaps configure this in sun8i_ths_desc +#define THS_SUN8I_FILTER_DIV (1 << (THS_SUN8I_FILTER_TYPE_VALUE + 1)) +#define THS_SUN8I_INT_CTRL_THERMAL_PER_VALUE \ + (THS_SUN8I_DATA_PERIOD * (THS_SUN8I_CLK_IN / 1000) / \ + THS_SUN8I_FILTER_DIV / 4096 - 1) + +#define THS_SUN8I_CTRL0_SENSOR_ACQ0_VALUE 0x3f /* 16us */ +#define THS_SUN8I_CTRL2_SENSOR_ACQ1_VALUE 0x3f + +#define SUN8I_THS_MAX_TZDS 3 + +struct sun8i_ths_sensor_desc { + u32 data_int_en; + u32 data_int_flag; + u32 data_offset; + u32 sense_en; +}; + +struct sun8i_ths_desc { + int num_sensors; + struct sun8i_ths_sensor_desc *sensors; + int (*calc_temp)(u32 reg_val); + bool has_cal1; +}; + +struct sun8i_ths_tzd { + struct sun8i_ths_data *data; + struct thermal_zone_device *tzd; + u32 temp; +}; struct sun8i_ths_data { + struct device *dev; struct reset_control *reset; struct clk *clk; struct clk *busclk; void __iomem *regs; - struct thermal_zone_device *tzd; - u32 temp; + void __iomem *cal_regs; + struct sun8i_ths_desc *desc; + struct sun8i_ths_tzd tzds[SUN8I_THS_MAX_TZDS]; }; +static int sun8i_ths_calc_temp_h3(u32 reg_val) +{ + uint64_t temp = (uint64_t)reg_val * 1000000ll; + + do_div(temp, 8253); + + return 217000 - (int)temp; +} + +static int sun8i_ths_calc_temp_a83t(u32 reg_val) +{ + uint64_t temp = (uint64_t)reg_val * 1000000ll; + + do_div(temp, 14186); + + return 192000 - (int)temp; +} + static int sun8i_ths_get_temp(void *_data, int *out) { - struct sun8i_ths_data *data = _data; + struct sun8i_ths_tzd *tzd = _data; + struct sun8i_ths_data *data = tzd->data; - if (data->temp == 0) + if (tzd->temp == 0) return -EBUSY; - /* Formula and parameters from the Allwinner 3.4 kernel */ - *out = 217000 - (int)((data->temp * 1000000) / 8253); + *out = data->desc->calc_temp(tzd->temp); return 0; } static irqreturn_t sun8i_ths_irq_thread(int irq, void *_data) { struct sun8i_ths_data *data = _data; - - writel(THS_H3_STAT_DATA_IRQ_STS, data->regs + THS_H3_STAT); - - data->temp = readl(data->regs + THS_H3_DATA); - if (data->temp) - thermal_zone_device_update(data->tzd, THERMAL_EVENT_TEMP_SAMPLE); + struct sun8i_ths_tzd *tzd; + struct sun8i_ths_sensor_desc *zdesc; + int i; + u32 status; + + status = readl(data->regs + THS_SUN8I_STAT); + writel(THS_SUN8I_STAT_CLEAR, data->regs + THS_SUN8I_STAT); + + for (i = 0; i < data->desc->num_sensors; i++) { + tzd = &data->tzds[i]; + zdesc = &data->desc->sensors[i]; + + if (status & zdesc->data_int_flag) { + tzd->temp = readl(data->regs + zdesc->data_offset); + if (tzd->temp) + thermal_zone_device_update(tzd->tzd, + THERMAL_EVENT_TEMP_SAMPLE); + } + } return IRQ_HANDLED; } -static void sun8i_ths_h3_init(struct sun8i_ths_data *data) +static void sun8i_ths_init(struct sun8i_ths_data *data) { - writel(THS_H3_CTRL0_SENSOR_ACQ0(THS_H3_CTRL0_SENSOR_ACQ0_VALUE), - data->regs + THS_H3_CTRL0); - writel(THS_H3_FILTER_EN | THS_H3_FILTER_TYPE(THS_H3_FILTER_TYPE_VALUE), - data->regs + THS_H3_FILTER); - writel(THS_H3_CTRL2_SENSOR_ACQ1(THS_H3_CTRL2_SENSOR_ACQ1_VALUE) | - THS_H3_CTRL2_SENSE_EN, - data->regs + THS_H3_CTRL2); - writel(THS_H3_INT_CTRL_THERMAL_PER(THS_H3_INT_CTRL_THERMAL_PER_VALUE) | - THS_H3_INT_CTRL_DATA_IRQ_EN, - data->regs + THS_H3_INT_CTRL); + int i; + u32 int_ctrl = 0; + u32 ctrl2 = 0; + + writel(THS_SUN8I_CTRL0_SENSOR_ACQ0(THS_SUN8I_CTRL0_SENSOR_ACQ0_VALUE), + data->regs + THS_SUN8I_CTRL0); + writel(THS_SUN8I_FILTER_EN | THS_SUN8I_FILTER_TYPE(THS_SUN8I_FILTER_TYPE_VALUE), + data->regs + THS_SUN8I_FILTER); + + ctrl2 |= THS_SUN8I_CTRL2_SENSOR_ACQ1(THS_SUN8I_CTRL2_SENSOR_ACQ1_VALUE); + int_ctrl |= THS_SUN8I_INT_CTRL_THERMAL_PER(THS_SUN8I_INT_CTRL_THERMAL_PER_VALUE); + + for (i = 0; i < data->desc->num_sensors; i++) { + ctrl2 |= data->desc->sensors[i].sense_en; + int_ctrl |= data->desc->sensors[i].data_int_en; + } + + if (data->cal_regs) { + u32 cal0, cal1; + + cal0 = readl(data->cal_regs); + if (cal0) + writel(cal0, data->regs + THS_SUN8I_CDATA01); + + if (data->desc->has_cal1) { + cal1 = readl(data->cal_regs + 4); + if (cal1) + writel(cal1, data->regs + THS_SUN8I_CDATA2); + } + } + + writel(ctrl2, data->regs + THS_SUN8I_CTRL2); + + /* enable interrupts */ + writel(int_ctrl, data->regs + THS_SUN8I_INT_CTRL); } static const struct thermal_zone_of_device_ops sun8i_ths_thermal_ops = { @@ -108,100 +203,135 @@ static const struct thermal_zone_of_device_ops sun8i_ths_thermal_ops = { static int sun8i_ths_probe(struct platform_device *pdev) { struct sun8i_ths_data *data; + struct device *dev = &pdev->dev; struct resource *res; - int ret; - int irq; + int ret, irq, i; - data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->desc = (struct sun8i_ths_desc *)of_device_get_match_data(dev); + if (data->desc == NULL) + return -EINVAL; + + data->dev = dev; + platform_set_drvdata(pdev, data); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ths"); if (!res) { - dev_err(&pdev->dev, "no memory resources defined\n"); + dev_err(dev, "no memory resources defined\n"); return -EINVAL; } - data->regs = devm_ioremap_resource(&pdev->dev, res); + data->regs = devm_ioremap_resource(dev, res); if (IS_ERR(data->regs)) { ret = PTR_ERR(data->regs); - dev_err(&pdev->dev, "failed to ioremap THS registers: %d\n", ret); + dev_err(dev, "failed to ioremap THS registers: %d\n", ret); return ret; } + /*XXX: use SRAM device in the future, instead of direct access to regs */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "calibration"); + if (res) { + data->cal_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(data->cal_regs)) { + ret = PTR_ERR(data->cal_regs); + dev_err(dev, "failed to ioremap calibration SRAM: %d\n", ret); + return ret; + } + } + irq = platform_get_irq(pdev, 0); if (irq < 0) { - dev_err(&pdev->dev, "failed to get IRQ: %d\n", irq); + dev_err(dev, "failed to get IRQ: %d\n", irq); return irq; } - ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + ret = devm_request_threaded_irq(dev, irq, NULL, sun8i_ths_irq_thread, IRQF_ONESHOT, - dev_name(&pdev->dev), data); + dev_name(dev), data); if (ret) return ret; - data->busclk = devm_clk_get(&pdev->dev, "ahb"); + data->busclk = devm_clk_get(dev, "ahb"); if (IS_ERR(data->busclk)) { ret = PTR_ERR(data->busclk); - dev_err(&pdev->dev, "failed to get ahb clk: %d\n", ret); - return ret; + if (ret != -ENOENT) { + dev_err(dev, "failed to get ahb clk: %d\n", ret); + return ret; + } + + data->busclk = NULL; } - data->clk = devm_clk_get(&pdev->dev, "ths"); + data->clk = devm_clk_get(dev, "ths"); if (IS_ERR(data->clk)) { ret = PTR_ERR(data->clk); - dev_err(&pdev->dev, "failed to get ths clk: %d\n", ret); - return ret; + if (ret != -ENOENT) { + dev_err(dev, "failed to get ths clk: %d\n", ret); + return ret; + } + + data->clk = NULL; } - data->reset = devm_reset_control_get(&pdev->dev, "ahb"); + data->reset = devm_reset_control_get_optional(dev, "ahb"); if (IS_ERR(data->reset)) { ret = PTR_ERR(data->reset); - dev_err(&pdev->dev, "failed to get reset: %d\n", ret); + dev_err(dev, "failed to get reset: %d\n", ret); return ret; } ret = reset_control_deassert(data->reset); if (ret) { - dev_err(&pdev->dev, "reset deassert failed: %d\n", ret); + dev_err(dev, "reset deassert failed: %d\n", ret); return ret; } - ret = clk_prepare_enable(data->busclk); - if (ret) { - dev_err(&pdev->dev, "failed to enable bus clk: %d\n", ret); - goto err_assert_reset; + if (data->busclk) { + ret = clk_prepare_enable(data->busclk); + if (ret) { + dev_err(dev, "failed to enable bus clk: %d\n", ret); + goto err_assert_reset; + } } - ret = clk_prepare_enable(data->clk); - if (ret) { - dev_err(&pdev->dev, "failed to enable ths clk: %d\n", ret); - goto err_disable_bus; - } + if (data->clk) { + ret = clk_prepare_enable(data->clk); + if (ret) { + dev_err(dev, "failed to enable ths clk: %d\n", ret); + goto err_disable_bus; + } - ret = clk_set_rate(data->clk, THS_H3_CLK_IN); - if (ret) - goto err_disable_ths; - - data->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, data, - &sun8i_ths_thermal_ops); - if (IS_ERR(data->tzd)) { - ret = PTR_ERR(data->tzd); - dev_err(&pdev->dev, "failed to register thermal zone: %d\n", - ret); - goto err_disable_ths; + ret = clk_set_rate(data->clk, THS_SUN8I_CLK_IN); + if (ret) + goto err_disable_ths; } - sun8i_ths_h3_init(data); + for (i = 0; i < data->desc->num_sensors; i++) { + data->tzds[i].data = data; + data->tzds[i].tzd = + devm_thermal_zone_of_sensor_register(dev, i, + &data->tzds[i], + &sun8i_ths_thermal_ops); + if (IS_ERR(data->tzds[i].tzd)) { + ret = PTR_ERR(data->tzds[i].tzd); + dev_err(dev, + "failed to register thermal zone: %d\n", ret); + goto err_disable_ths; + } + } - platform_set_drvdata(pdev, data); + sun8i_ths_init(data); return 0; err_disable_ths: - clk_disable_unprepare(data->clk); + if (data->clk) + clk_disable_unprepare(data->clk); err_disable_bus: - clk_disable_unprepare(data->busclk); + if (data->busclk) + clk_disable_unprepare(data->busclk); err_assert_reset: reset_control_assert(data->reset); return ret; @@ -212,13 +342,60 @@ static int sun8i_ths_remove(struct platform_device *pdev) struct sun8i_ths_data *data = platform_get_drvdata(pdev); reset_control_assert(data->reset); - clk_disable_unprepare(data->clk); - clk_disable_unprepare(data->busclk); + if (data->clk) + clk_disable_unprepare(data->clk); + if (data->busclk) + clk_disable_unprepare(data->busclk); return 0; } +struct sun8i_ths_sensor_desc sun8i_ths_h3_sensors[] = { + { + .data_int_en = THS_SUN8I_INT_CTRL_DATA0_IRQ_EN, + .data_int_flag = THS_SUN8I_STAT_DATA0_IRQ_STS, + .data_offset = THS_SUN8I_DATA0, + .sense_en = THS_SUN8I_CTRL2_SENSE_EN0, + }, +}; + +struct sun8i_ths_sensor_desc sun8i_ths_a83t_sensors[] = { + { + .data_int_en = THS_SUN8I_INT_CTRL_DATA0_IRQ_EN, + .data_int_flag = THS_SUN8I_STAT_DATA0_IRQ_STS, + .data_offset = THS_SUN8I_DATA0, + .sense_en = THS_SUN8I_CTRL2_SENSE_EN0, + }, + { + .data_int_en = THS_SUN8I_INT_CTRL_DATA1_IRQ_EN, + .data_int_flag = THS_SUN8I_STAT_DATA1_IRQ_STS, + .data_offset = THS_SUN8I_DATA1, + .sense_en = THS_SUN8I_CTRL2_SENSE_EN1, + }, + { + .data_int_en = THS_SUN8I_INT_CTRL_DATA2_IRQ_EN, + .data_int_flag = THS_SUN8I_STAT_DATA2_IRQ_STS, + .data_offset = THS_SUN8I_DATA2, + .sense_en = THS_SUN8I_CTRL2_SENSE_EN2, + }, +}; + +static const struct sun8i_ths_desc sun8i_ths_h3_desc = { + .num_sensors = ARRAY_SIZE(sun8i_ths_h3_sensors), + .sensors = sun8i_ths_h3_sensors, + .calc_temp = sun8i_ths_calc_temp_h3, + .has_cal1 = false, +}; + +static const struct sun8i_ths_desc sun8i_ths_a83t_desc = { + .num_sensors = ARRAY_SIZE(sun8i_ths_a83t_sensors), + .sensors = sun8i_ths_a83t_sensors, + .calc_temp = sun8i_ths_calc_temp_a83t, + .has_cal1 = true, +}; + static const struct of_device_id sun8i_ths_id_table[] = { - { .compatible = "allwinner,sun8i-h3-ths", }, + { .compatible = "allwinner,sun8i-h3-ths", .data = &sun8i_ths_h3_desc }, + { .compatible = "allwinner,sun8i-a83t-ths", .data = &sun8i_ths_a83t_desc }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, sun8i_ths_id_table); @@ -235,5 +412,5 @@ static struct platform_driver sun8i_ths_driver = { module_platform_driver(sun8i_ths_driver); MODULE_AUTHOR("Ondřej Jirman "); -MODULE_DESCRIPTION("Thermal sensor driver for Allwinner H3 SoC"); +MODULE_DESCRIPTION("Thermal sensor driver for Allwinner SUN8I SoCs"); MODULE_LICENSE("GPL v2"); -- 2.20.1