/* * 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;
};
/* There is currently no board with more than one sensor per channel */ staticint 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;
/* 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;
/* * 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)) return -EIO;
return 0;
}
staticint armada_read_sensor(struct armada_thermal_priv *priv, int *temp)
{
u32 reg, div;
s64 sample, b, m;
regmap_read(priv->syscon, priv->data->syscon_status_off, ®);
reg = (reg >> priv->data->temp_shift) & priv->data->temp_mask; if (priv->data->signed_sample) /* The most significant bit is the sign bit */
sample = sign_extend32(reg, fls(priv->data->temp_mask) - 1); else
sample = reg;
/* Get formula coeficients */
b = priv->data->coef_b;
m = priv->data->coef_m;
div = priv->data->coef_div;
staticint armada_get_temp(struct thermal_zone_device *tz, int *temp)
{ struct armada_thermal_sensor *sensor = thermal_zone_device_priv(tz); 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 */
ret = armada_read_sensor(priv, temp); if (ret) goto unlock_mutex;
/* * Select back the interrupt source channel from which a potential * critical trip point has been set.
*/
ret = armada_select_channel(priv, priv->interrupt_source);
staticunsignedint armada_mc_to_reg_hyst(struct armada_thermal_data *data, unsignedint hyst_mc)
{ int i;
/* * We will always take the smallest possible hysteresis to avoid risking * the hardware integrity by enlarging the threshold by +8°C in the * worst case.
*/ for (i = ARRAY_SIZE(hyst_levels_mc) - 1; i > 0; i--) if (hyst_mc >= hyst_levels_mc[i]) break;
static irqreturn_t armada_overheat_isr(int irq, void *blob)
{ /* * Disable the IRQ and continue in thread context (thermal core * notification and temperature monitoring).
*/
disable_irq_nosync(irq);
return IRQ_WAKE_THREAD;
}
static irqreturn_t armada_overheat_isr_thread(int irq, void *blob)
{ struct armada_thermal_priv *priv = blob; int low_threshold = priv->current_threshold - priv->current_hysteresis; int temperature;
u32 dummy; int ret;
/* Notify the core in thread context */
thermal_zone_device_update(priv->overheat_sensor,
THERMAL_EVENT_UNSPECIFIED);
/* * The overheat interrupt must be cleared by reading the DFX interrupt * cause _after_ the temperature has fallen down to the low threshold. * Otherwise future interrupts might not be served.
*/ do {
msleep(OVERHEAT_INT_POLL_DELAY_MS);
mutex_lock(&priv->update_lock);
ret = armada_read_sensor(priv, &temperature);
mutex_unlock(&priv->update_lock); if (ret) goto enable_irq;
} while (temperature >= low_threshold);
/* First memory region points towards the status register */
base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); if (IS_ERR(base)) return PTR_ERR(base);
/* * Fix up from the old individual DT register specification to * cover all the registers. We do this by adjusting the ioremap() * result, which should be fine as ioremap() deals with pages. * However, validate that we do not cross a page boundary while * making this adjustment.
*/ if (((unsignedlong)base & ~PAGE_MASK) < data->syscon_status_off) return -EINVAL;
base -= data->syscon_status_off;
if (strlen(name) > THERMAL_NAME_LENGTH) { /* * When inside a system controller, the device name has the * form: f06f8000.system-controller:ap-thermal so stripping * after the ':' should give us a shorter but meaningful name.
*/
name = strrchr(name, ':'); if (!name)
name = "armada_thermal"; else
name++;
}
/* Save the name locally */
strscpy(priv->zone_name, name, THERMAL_NAME_LENGTH);
/* Then ensure there are no '-' or hwmon core will complain */
strreplace(priv->zone_name, '-', '_');
}
/* * The IP can manage to trigger interrupts on overheat situation from all the * sensors. However, the interrupt source changes along with the last selected * source (ie. the last read sensor), which is an inconsistent behavior. Avoid * possible glitches by always selecting back only one channel (arbitrarily: the * first in the DT which has a critical trip point). We also disable sensor * switch during overheat situations.
*/ staticint armada_configure_overheat_int(struct armada_thermal_priv *priv, struct thermal_zone_device *tz, int sensor_id)
{ /* Retrieve the critical trip point to enable the overheat interrupt */ int temperature; int ret;
ret = thermal_zone_get_crit_temp(tz, &temperature); if (ret) return ret;
ret = armada_select_channel(priv, sensor_id); if (ret) return ret;
/* * A critical temperature does not have a hysteresis
*/
armada_set_overheat_thresholds(priv, temperature, 0);
priv->overheat_sensor = tz;
priv->interrupt_source = sensor_id;
armada_enable_overheat_interrupt(priv);
/* * Legacy DT bindings only described "control1" register (also referred * as "control MSB" on old documentation). Then, bindings moved to cover * "control0/control LSB" and "control1/control MSB" registers within * the same resource, which was then of size 8 instead of 4. * * The logic of defining sporadic registers is broken. For instance, it * blocked the addition of the overheat interrupt feature that needed * another resource somewhere else in the same memory area. One solution * is to define an overall system controller and put the thermal node * into it, which requires the use of regmaps across all the driver.
*/ if (IS_ERR(syscon_node_to_regmap(pdev->dev.parent->of_node))) { /* Ensure device name is correct for the thermal core */
armada_set_sane_name(pdev, priv);
ret = armada_thermal_probe_legacy(pdev, priv); if (ret) return ret;
priv->data->init(pdev, priv);
/* Wait the sensors to be valid */
armada_wait_sensor_validity(priv);
tz = thermal_tripless_zone_device_register(priv->zone_name,
priv, &legacy_ops,
NULL); if (IS_ERR(tz)) {
dev_err(&pdev->dev, "Failed to register thermal zone device\n"); return PTR_ERR(tz);
}
ret = thermal_zone_device_enable(tz); if (ret) {
thermal_zone_device_unregister(tz); return ret;
}
irq = platform_get_irq(pdev, 0); if (irq == -EPROBE_DEFER) return irq;
/* The overheat interrupt feature is not mandatory */ if (irq > 0) {
ret = devm_request_threaded_irq(&pdev->dev, irq,
armada_overheat_isr,
armada_overheat_isr_thread,
0, NULL, priv); if (ret) {
dev_err(&pdev->dev, "Cannot request threaded IRQ %d\n",
irq); return ret;
}
}
/* * 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;
/* * The first channel that has a critical trip point registered * in the DT will serve as interrupt source. Others possible * critical trip points will simply be ignored by the driver.
*/ if (irq > 0 && !priv->overheat_sensor)
armada_configure_overheat_int(priv, tz, sensor->id);
}
/* Just complain if no overheat interrupt was set up */ if (!priv->overheat_sensor)
dev_warn(&pdev->dev, "Overheat interrupt not available\n");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.