diff options
-rw-r--r-- | drivers/thermal/armada_thermal.c | 135 |
1 files changed, 114 insertions, 21 deletions
diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c index 414329e94f8c..e20d4f954f21 100644 --- a/drivers/thermal/armada_thermal.c +++ b/drivers/thermal/armada_thermal.c @@ -49,8 +49,13 @@ #define CONTROL0_TSEN_RESET BIT(1) #define CONTROL0_TSEN_ENABLE BIT(2) #define CONTROL0_TSEN_AVG_BYPASS BIT(6) +#define CONTROL0_TSEN_CHAN_SHIFT 13 +#define CONTROL0_TSEN_CHAN_MASK 0xF #define CONTROL0_TSEN_OSR_SHIFT 24 #define CONTROL0_TSEN_OSR_MAX 0x3 +#define CONTROL0_TSEN_MODE_SHIFT 30 +#define CONTROL0_TSEN_MODE_EXTERNAL 0x2 +#define CONTROL0_TSEN_MODE_MASK 0x3 #define CONTROL1_TSEN_AVG_SHIFT 0 #define CONTROL1_TSEN_AVG_MASK 0x7 @@ -67,7 +72,10 @@ struct armada_thermal_priv { struct device *dev; struct regmap *syscon; char zone_name[THERMAL_NAME_LENGTH]; + /* serialize temperature reads/updates */ + struct mutex update_lock; struct armada_thermal_data *data; + int current_channel; }; struct armada_thermal_data { @@ -94,6 +102,9 @@ struct armada_thermal_data { unsigned int syscon_control0_off; unsigned int syscon_control1_off; unsigned int syscon_status_off; + + /* One sensor is in the thermal IC, the others are in the CPUs if any */ + unsigned int cpu_nr; }; struct armada_drvdata { @@ -111,9 +122,11 @@ struct armada_drvdata { * struct armada_thermal_sensor - hold the information of one thermal sensor * @thermal: pointer to the local private structure * @tzd: pointer to the thermal zone device + * @id: identifier of the thermal sensor */ struct armada_thermal_sensor { struct armada_thermal_priv *priv; + int id; }; static void armadaxp_init(struct platform_device *pdev, @@ -181,14 +194,15 @@ static void armada375_init(struct platform_device *pdev, msleep(50); } -static void armada_wait_sensor_validity(struct armada_thermal_priv *priv) +static int armada_wait_sensor_validity(struct armada_thermal_priv *priv) { u32 reg; - regmap_read_poll_timeout(priv->syscon, priv->data->syscon_status_off, - reg, reg & priv->data->is_valid_bit, - STATUS_POLL_PERIOD_US, - STATUS_POLL_TIMEOUT_US); + return regmap_read_poll_timeout(priv->syscon, + priv->data->syscon_status_off, reg, + reg & priv->data->is_valid_bit, + STATUS_POLL_PERIOD_US, + STATUS_POLL_TIMEOUT_US); } static void armada380_init(struct platform_device *pdev, @@ -264,6 +278,58 @@ static bool armada_is_valid(struct armada_thermal_priv *priv) return reg & priv->data->is_valid_bit; } +/* There is currently no board with more than one sensor per channel */ +static int armada_select_channel(struct armada_thermal_priv *priv, int channel) +{ + struct armada_thermal_data *data = priv->data; + u32 ctrl0; + + if (channel < 0 || channel > priv->data->cpu_nr) + return -EINVAL; + + if (priv->current_channel == channel) + return 0; + + /* Stop the measurements */ + regmap_read(priv->syscon, data->syscon_control0_off, &ctrl0); + ctrl0 &= ~CONTROL0_TSEN_START; + regmap_write(priv->syscon, data->syscon_control0_off, ctrl0); + + /* Reset the mode, internal sensor will be automatically selected */ + ctrl0 &= ~(CONTROL0_TSEN_MODE_MASK << CONTROL0_TSEN_MODE_SHIFT); + + /* Other channels are external and should be selected accordingly */ + if (channel) { + /* Change the mode to external */ + ctrl0 |= CONTROL0_TSEN_MODE_EXTERNAL << + CONTROL0_TSEN_MODE_SHIFT; + /* Select the sensor */ + ctrl0 &= ~(CONTROL0_TSEN_CHAN_MASK << CONTROL0_TSEN_CHAN_SHIFT); + ctrl0 |= (channel - 1) << CONTROL0_TSEN_CHAN_SHIFT; + } + + /* Actually set the mode/channel */ + regmap_write(priv->syscon, data->syscon_control0_off, ctrl0); + priv->current_channel = channel; + + /* Re-start the measurements */ + ctrl0 |= CONTROL0_TSEN_START; + regmap_write(priv->syscon, data->syscon_control0_off, ctrl0); + + /* + * The IP has a latency of ~15ms, so after updating the selected source, + * we must absolutely wait for the sensor validity bit to ensure we read + * actual data. + */ + if (armada_wait_sensor_validity(priv)) { + dev_err(priv->dev, + "Temperature sensor reading not valid\n"); + return -EIO; + } + + return 0; +} + static int armada_read_sensor(struct armada_thermal_priv *priv, int *temp) { u32 reg, div; @@ -317,9 +383,22 @@ static int armada_get_temp(void *_sensor, int *temp) { struct armada_thermal_sensor *sensor = _sensor; struct armada_thermal_priv *priv = sensor->priv; + int ret; + + mutex_lock(&priv->update_lock); + + /* Select the desired channel */ + ret = armada_select_channel(priv, sensor->id); + if (ret) + goto unlock_mutex; /* Do the actual reading */ - return armada_read_sensor(priv, temp); + ret = armada_read_sensor(priv, temp); + +unlock_mutex: + mutex_unlock(&priv->update_lock); + + return ret; } static struct thermal_zone_of_device_ops of_ops = { @@ -393,6 +472,7 @@ static const struct armada_thermal_data armada_ap806_data = { .syscon_control0_off = 0x84, .syscon_control1_off = 0x88, .syscon_status_off = 0x8C, + .cpu_nr = 4, }; static const struct armada_thermal_data armada_cp110_data = { @@ -526,10 +606,11 @@ static void armada_set_sane_name(struct platform_device *pdev, static int armada_thermal_probe(struct platform_device *pdev) { struct thermal_zone_device *tz; - struct armada_thermal_sensor *sensors; + struct armada_thermal_sensor *sensor; struct armada_drvdata *drvdata; const struct of_device_id *match; struct armada_thermal_priv *priv; + int sensor_id; int ret; match = of_match_device(armada_thermal_id_table, &pdev->dev); @@ -547,6 +628,8 @@ static int armada_thermal_probe(struct platform_device *pdev) priv->dev = &pdev->dev; priv->data = (struct armada_thermal_data *)match->data; + mutex_init(&priv->update_lock); + /* * Legacy DT bindings only described "control1" register (also referred * as "control MSB" on old documentation). Then, bindings moved to cover @@ -588,25 +671,35 @@ static int armada_thermal_probe(struct platform_device *pdev) if (ret) return ret; + priv->current_channel = -1; priv->data->init(pdev, priv); drvdata->type = SYSCON; drvdata->data.priv = priv; platform_set_drvdata(pdev, drvdata); - sensors = devm_kzalloc(&pdev->dev, sizeof(struct armada_thermal_sensor), - GFP_KERNEL); - if (!sensors) - return -ENOMEM; - - sensors->priv = priv; - - tz = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, sensors, - &of_ops); - if (IS_ERR(tz)) { - dev_err(&pdev->dev, - "Failed to register thermal sensor (err: %ld)\n", - PTR_ERR(tz)); - return PTR_ERR(tz); + /* + * There is one channel for the IC and one per CPU (if any), each + * channel has one sensor. + */ + for (sensor_id = 0; sensor_id <= priv->data->cpu_nr; sensor_id++) { + sensor = devm_kzalloc(&pdev->dev, + sizeof(struct armada_thermal_sensor), + GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + /* Register the sensor */ + sensor->priv = priv; + sensor->id = sensor_id; + tz = devm_thermal_zone_of_sensor_register(&pdev->dev, + sensor->id, sensor, + &of_ops); + if (IS_ERR(tz)) { + dev_info(&pdev->dev, "Thermal sensor %d unavailable\n", + sensor_id); + devm_kfree(&pdev->dev, sensor); + continue; + } } return 0; |