// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2014 - 2018, NVIDIA CORPORATION. All rights reserved. * * Author: * Mikko Perttunen <mperttunen@nvidia.com> * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. *
*/
/** * ccroc_writel() - writes a value to a CCROC register * @ts: pointer to a struct tegra_soctherm * @value: the value to write * @reg: the register offset * * Writes @v to @reg. No return value.
*/ staticinlinevoid ccroc_writel(struct tegra_soctherm *ts, u32 value, u32 reg)
{
writel(value, (ts->ccroc_regs + reg));
}
/** * ccroc_readl() - reads specified register from CCROC IP block * @ts: pointer to a struct tegra_soctherm * @reg: register address to be read * * Return: the value of the register
*/ staticinline u32 ccroc_readl(struct tegra_soctherm *ts, u32 reg)
{ return readl(ts->ccroc_regs + reg);
}
val = sensor->config->tall << SENSOR_CONFIG0_TALL_SHIFT;
writel(val, base + SENSOR_CONFIG0);
val = (sensor->config->tsample - 1) << SENSOR_CONFIG1_TSAMPLE_SHIFT;
val |= sensor->config->tiddq_en << SENSOR_CONFIG1_TIDDQ_EN_SHIFT;
val |= sensor->config->ten_count << SENSOR_CONFIG1_TEN_COUNT_SHIFT;
val |= SENSOR_CONFIG1_TEMP_ENABLE;
writel(val, base + SENSOR_CONFIG1);
writel(tegra->calib[i], base + SENSOR_CONFIG2);
}
/* * Translate from soctherm readback format to millicelsius. * The soctherm readback format in bits is as follows: * TTTTTTTT H______N * where T's contain the temperature in Celsius, * H denotes an addition of 0.5 Celsius and N denotes negation * of the final value.
*/ staticint translate_temp(u16 val)
{ int t;
t = ((val & READBACK_VALUE_MASK) >> READBACK_VALUE_SHIFT) * 1000; if (val & READBACK_ADD_HALF)
t += 500; if (val & READBACK_NEGATE)
t *= -1;
val = readl(zone->reg);
val = REG_GET_MASK(val, zone->sg->sensor_temp_mask);
*out_temp = translate_temp(val);
return 0;
}
/** * enforce_temp_range() - check and enforce temperature range [min, max] * @dev: struct device * of the SOC_THERM instance * @trip_temp: the trip temperature to check * * Checks and enforces the permitted temperature range that SOC_THERM * HW can support This is * done while taking care of precision. * * Return: The precision adjusted capped temperature in millicelsius.
*/ staticint enforce_temp_range(struct device *dev, int trip_temp)
{ int temp;
temp = clamp_val(trip_temp, min_low_temp, max_high_temp); if (temp != trip_temp)
dev_dbg(dev, "soctherm: trip temperature %d forced to %d\n",
trip_temp, temp); return temp;
}
/** * thermtrip_program() - Configures the hardware to shut down the * system if a given sensor group reaches a given temperature * @dev: ptr to the struct device for the SOC_THERM IP block * @sg: pointer to the sensor group to set the thermtrip temperature for * @trip_temp: the temperature in millicelsius to trigger the thermal trip at * * Sets the thermal trip threshold of the given sensor group to be the * @trip_temp. If this threshold is crossed, the hardware will shut * down. * * Note that, although @trip_temp is specified in millicelsius, the * hardware is programmed in degrees Celsius. * * Return: 0 upon success, or %-EINVAL upon failure.
*/ staticint thermtrip_program(struct device *dev, conststruct tegra_tsensor_group *sg, int trip_temp)
{ struct tegra_soctherm *ts = dev_get_drvdata(dev); int temp;
u32 r;
if (!sg || !sg->thermtrip_threshold_mask) return -EINVAL;
r = readl(ts->regs + THERMCTL_THERMTRIP_CTL);
r = REG_SET_MASK(r, sg->thermtrip_threshold_mask, temp);
r = REG_SET_MASK(r, sg->thermtrip_enable_mask, 1);
r = REG_SET_MASK(r, sg->thermtrip_any_en_mask, 0);
writel(r, ts->regs + THERMCTL_THERMTRIP_CTL);
return 0;
}
/** * throttrip_program() - Configures the hardware to throttle the * pulse if a given sensor group reaches a given temperature * @dev: ptr to the struct device for the SOC_THERM IP block * @sg: pointer to the sensor group to set the thermtrip temperature for * @stc: pointer to the throttle need to be triggered * @trip_temp: the temperature in millicelsius to trigger the thermal trip at * * Sets the thermal trip threshold and throttle event of the given sensor * group. If this threshold is crossed, the hardware will trigger the * throttle. * * Note that, although @trip_temp is specified in millicelsius, the * hardware is programmed in degrees Celsius. * * Return: 0 upon success, or %-EINVAL upon failure.
*/ staticint throttrip_program(struct device *dev, conststruct tegra_tsensor_group *sg, struct soctherm_throt_cfg *stc, int trip_temp)
{ struct tegra_soctherm *ts = dev_get_drvdata(dev); int temp, cpu_throt, gpu_throt; unsignedint throt;
u32 r, reg_off;
if (trip->type == THERMAL_TRIP_CRITICAL) { /* * If thermtrips property is set in DT, * doesn't need to program critical type trip to HW, * if not, program critical trip to HW.
*/ if (min_low_temp == tsensor_group_thermtrip_get(ts, sg->id)) return thermtrip_program(dev, sg, temp); else return 0;
} elseif (trip->type == THERMAL_TRIP_HOT) { int i;
for (i = 0; i < THROTTLE_SIZE; i++) { struct thermal_cooling_device *cdev; struct soctherm_throt_cfg *stc;
/* multiple zones could be handling and setting trips at once */
mutex_lock(&zn->ts->thermctl_lock);
r = readl(zn->ts->regs + THERMCTL_INTR_ENABLE);
r = REG_SET_MASK(r, zn->sg->thermctl_isr_mask, TH_INTR_UP_DN_EN);
writel(r, zn->ts->regs + THERMCTL_INTR_ENABLE);
mutex_unlock(&zn->ts->thermctl_lock);
}
/* multiple zones could be handling and setting trips at once */
mutex_lock(&zn->ts->thermctl_lock);
r = readl(zn->ts->regs + THERMCTL_INTR_DISABLE);
r = REG_SET_MASK(r, zn->sg->thermctl_isr_mask, 0);
writel(r, zn->ts->regs + THERMCTL_INTR_DISABLE);
mutex_unlock(&zn->ts->thermctl_lock);
}
staticint tegra_thermctl_set_trips(struct thermal_zone_device *tz, int lo, int hi)
{ struct tegra_thermctl_zone *zone = thermal_zone_device_priv(tz);
u32 r;
thermal_irq_disable(zone);
r = readl(zone->ts->regs + zone->sg->thermctl_lvl0_offset);
r = REG_SET_MASK(r, THERMCTL_LVL0_CPU0_EN_MASK, 0);
writel(r, zone->ts->regs + zone->sg->thermctl_lvl0_offset);
lo = enforce_temp_range(zone->dev, lo) / zone->ts->soc->thresh_grain;
hi = enforce_temp_range(zone->dev, hi) / zone->ts->soc->thresh_grain;
dev_dbg(zone->dev, "%s hi:%d, lo:%d\n", __func__, hi, lo);
r = REG_SET_MASK(r, zone->sg->thermctl_lvl0_up_thresh_mask, hi);
r = REG_SET_MASK(r, zone->sg->thermctl_lvl0_dn_thresh_mask, lo);
r = REG_SET_MASK(r, THERMCTL_LVL0_CPU0_EN_MASK, 1);
writel(r, zone->ts->regs + zone->sg->thermctl_lvl0_offset);
/** * tegra_soctherm_set_hwtrips() - set HW trip point from DT data * @dev: struct device * of the SOC_THERM instance * @sg: pointer to the sensor group to set the thermtrip temperature for * @tz: struct thermal_zone_device * * * Configure the SOC_THERM HW trip points, setting "THERMTRIP" * "THROTTLE" trip points , using "thermtrips", "critical" or "hot" * type trip_temp * from thermal zone. * After they have been configured, THERMTRIP or THROTTLE will take * action when the configured SoC thermal sensor group reaches a * certain temperature. * * Return: 0 upon success, or a negative error code on failure. * "Success" does not mean that trips was enabled; it could also * mean that no node was found in DT. * THERMTRIP has been enabled successfully when a message similar to * this one appears on the serial console: * "thermtrip: will shut down when sensor group XXX reaches YYYYYY mC" * THROTTLE has been enabled successfully when a message similar to * this one appears on the serial console: * ""throttrip: will throttle when sensor group XXX reaches YYYYYY mC"
*/ staticint tegra_soctherm_set_hwtrips(struct device *dev, conststruct tegra_tsensor_group *sg, struct thermal_zone_device *tz)
{ struct tegra_soctherm *ts = dev_get_drvdata(dev); conststruct thermal_trip *hot_trip; struct soctherm_throt_cfg *stc; int i, temperature, ret;
/* Get thermtrips. If missing, try to get critical trips. */
temperature = tsensor_group_thermtrip_get(ts, sg->id); if (min_low_temp == temperature) if (thermal_zone_get_crit_temp(tz, &temperature))
temperature = max_high_temp;
ret = thermtrip_program(dev, sg, temperature); if (ret) {
dev_err(dev, "thermtrip: %s: error during enable\n", sg->name); return ret;
}
dev_info(dev, "thermtrip: will shut down when %s reaches %d mC\n",
sg->name, temperature);
hot_trip = get_hot_trip(tz); if (!hot_trip) {
dev_info(dev, "throttrip: %s: missing hot temperature\n",
sg->name); return 0;
}
for (i = 0; i < THROTTLE_OC1; i++) { struct thermal_cooling_device *cdev;
/* Case for no lock: * Although interrupts are enabled in set_trips, there is still no need * to lock here because the interrupts are disabled before programming * new trip points. Hence there cant be a interrupt on the same sensor. * An interrupt can however occur on a sensor while trips are being * programmed on a different one. This beign a LEVEL interrupt won't * cause a new interrupt but this is taken care of by the re-reading of * the STATUS register in the thread function.
*/
r = readl(ts->regs + THERMCTL_INTR_STATUS);
writel(r, ts->regs + THERMCTL_INTR_DISABLE);
return IRQ_WAKE_THREAD;
}
/** * soctherm_thermal_isr_thread() - Handles a thermal interrupt request * @irq: The interrupt number being requested; not used * @dev_id: Opaque pointer to tegra_soctherm; * * Clears the interrupt status register if there are expected * interrupt bits set. * The interrupt(s) are then handled by updating the corresponding * thermal zones. * * An error is logged if any unexpected interrupt bits are set. * * Disabled interrupts are re-enabled. * * Return: %IRQ_HANDLED. Interrupt was handled and no further processing * is needed.
*/ static irqreturn_t soctherm_thermal_isr_thread(int irq, void *dev_id)
{ struct tegra_soctherm *ts = dev_id; struct thermal_zone_device *tz;
u32 st, ex = 0, cp = 0, gp = 0, pl = 0, me = 0;
st = readl(ts->regs + THERMCTL_INTR_STATUS);
/* deliberately clear expected interrupts handled in SW */
cp |= st & TH_INTR_CD0_MASK;
cp |= st & TH_INTR_CU0_MASK;
gp |= st & TH_INTR_GD0_MASK;
gp |= st & TH_INTR_GU0_MASK;
pl |= st & TH_INTR_PD0_MASK;
pl |= st & TH_INTR_PU0_MASK;
me |= st & TH_INTR_MD0_MASK;
me |= st & TH_INTR_MU0_MASK;
ex |= cp | gp | pl | me; if (ex) {
writel(ex, ts->regs + THERMCTL_INTR_STATUS);
st &= ~ex;
if (cp) {
tz = ts->thermctl_tzs[TEGRA124_SOCTHERM_SENSOR_CPU];
thermal_zone_device_update(tz,
THERMAL_EVENT_UNSPECIFIED);
}
if (gp) {
tz = ts->thermctl_tzs[TEGRA124_SOCTHERM_SENSOR_GPU];
thermal_zone_device_update(tz,
THERMAL_EVENT_UNSPECIFIED);
}
if (pl) {
tz = ts->thermctl_tzs[TEGRA124_SOCTHERM_SENSOR_PLLX];
thermal_zone_device_update(tz,
THERMAL_EVENT_UNSPECIFIED);
}
if (me) {
tz = ts->thermctl_tzs[TEGRA124_SOCTHERM_SENSOR_MEM];
thermal_zone_device_update(tz,
THERMAL_EVENT_UNSPECIFIED);
}
}
/* deliberately ignore expected interrupts NOT handled in SW */
ex |= TH_INTR_IGNORE_MASK;
st &= ~ex;
if (st) { /* Whine about any other unexpected INTR bits still set */
pr_err("soctherm: Ignored unexpected INTRs 0x%08x\n", st);
writel(st, ts->regs + THERMCTL_INTR_STATUS);
}
return IRQ_HANDLED;
}
/** * soctherm_oc_intr_enable() - Enables the soctherm over-current interrupt * @ts: pointer to a struct tegra_soctherm * @alarm: The soctherm throttle id * @enable: Flag indicating enable the soctherm over-current * interrupt or disable it * * Enables a specific over-current pins @alarm to raise an interrupt if the flag * is set and the alarm corresponds to OC1, OC2, OC3, or OC4.
*/ staticvoid soctherm_oc_intr_enable(struct tegra_soctherm *ts, enum soctherm_throttle_id alarm, bool enable)
{
u32 r;
if (!enable) return;
r = readl(ts->regs + OC_INTR_ENABLE); switch (alarm) { case THROTTLE_OC1:
r = REG_SET_MASK(r, OC_INTR_OC1_MASK, 1); break; case THROTTLE_OC2:
r = REG_SET_MASK(r, OC_INTR_OC2_MASK, 1); break; case THROTTLE_OC3:
r = REG_SET_MASK(r, OC_INTR_OC3_MASK, 1); break; case THROTTLE_OC4:
r = REG_SET_MASK(r, OC_INTR_OC4_MASK, 1); break; default:
r = 0; break;
}
writel(r, ts->regs + OC_INTR_ENABLE);
}
/** * soctherm_handle_alarm() - Handles soctherm alarms * @alarm: The soctherm throttle id * * "Handles" over-current alarms (OC1, OC2, OC3, and OC4) by printing * a warning or informative message. * * Return: -EINVAL for @alarm = THROTTLE_OC3, otherwise 0 (success).
*/ staticint soctherm_handle_alarm(enum soctherm_throttle_id alarm)
{ int rv = -EINVAL;
if (rv)
pr_err("soctherm: ERROR in handling %s alarm\n",
throt_names[alarm]);
return rv;
}
/** * soctherm_edp_isr_thread() - log an over-current interrupt request * @irq: OC irq number. Currently not being used. See description * @arg: a void pointer for callback, currently not being used * * Over-current events are handled in hardware. This function is called to log * and handle any OC events that happened. Additionally, it checks every * over-current interrupt registers for registers are set but * was not expected (i.e. any discrepancy in interrupt status) by the function, * the discrepancy will logged. * * Return: %IRQ_HANDLED
*/ static irqreturn_t soctherm_edp_isr_thread(int irq, void *arg)
{ struct tegra_soctherm *ts = arg;
u32 st, ex, oc1, oc2, oc3, oc4;
st = readl(ts->regs + OC_INTR_STATUS);
/* deliberately clear expected interrupts handled in SW */
oc1 = st & OC_INTR_OC1_MASK;
oc2 = st & OC_INTR_OC2_MASK;
oc3 = st & OC_INTR_OC3_MASK;
oc4 = st & OC_INTR_OC4_MASK;
ex = oc1 | oc2 | oc3 | oc4;
pr_err("soctherm: OC ALARM 0x%08x\n", ex); if (ex) {
writel(st, ts->regs + OC_INTR_STATUS);
st &= ~ex;
if (oc1 && !soctherm_handle_alarm(THROTTLE_OC1))
soctherm_oc_intr_enable(ts, THROTTLE_OC1, true);
if (oc2 && !soctherm_handle_alarm(THROTTLE_OC2))
soctherm_oc_intr_enable(ts, THROTTLE_OC2, true);
if (oc3 && !soctherm_handle_alarm(THROTTLE_OC3))
soctherm_oc_intr_enable(ts, THROTTLE_OC3, true);
if (oc4 && !soctherm_handle_alarm(THROTTLE_OC4))
soctherm_oc_intr_enable(ts, THROTTLE_OC4, true);
if (oc1 && soc_irq_cdata.irq_enable & BIT(0))
handle_nested_irq(
irq_find_mapping(soc_irq_cdata.domain, 0));
if (oc2 && soc_irq_cdata.irq_enable & BIT(1))
handle_nested_irq(
irq_find_mapping(soc_irq_cdata.domain, 1));
if (oc3 && soc_irq_cdata.irq_enable & BIT(2))
handle_nested_irq(
irq_find_mapping(soc_irq_cdata.domain, 2));
if (oc4 && soc_irq_cdata.irq_enable & BIT(3))
handle_nested_irq(
irq_find_mapping(soc_irq_cdata.domain, 3));
}
/** * soctherm_edp_isr() - Disables any active interrupts * @irq: The interrupt request number * @arg: Opaque pointer to an argument * * Writes to the OC_INTR_DISABLE register the over current interrupt status, * masking any asserted interrupts. Doing this prevents the same interrupts * from triggering this isr repeatedly. The thread woken by this isr will * handle asserted interrupts and subsequently unmask/re-enable them. * * The OC_INTR_DISABLE register indicates which OC interrupts * have been disabled. * * Return: %IRQ_WAKE_THREAD, handler requests to wake the handler thread
*/ static irqreturn_t soctherm_edp_isr(int irq, void *arg)
{ struct tegra_soctherm *ts = arg;
u32 r;
if (!ts) return IRQ_NONE;
r = readl(ts->regs + OC_INTR_STATUS);
writel(r, ts->regs + OC_INTR_DISABLE);
return IRQ_WAKE_THREAD;
}
/** * soctherm_oc_irq_lock() - locks the over-current interrupt request * @data: Interrupt request data * * Looks up the chip data from @data and locks the mutex associated with * a particular over-current interrupt request.
*/ staticvoid soctherm_oc_irq_lock(struct irq_data *data)
{ struct soctherm_oc_irq_chip_data *d = irq_data_get_irq_chip_data(data);
mutex_lock(&d->irq_lock);
}
/** * soctherm_oc_irq_sync_unlock() - Unlocks the OC interrupt request * @data: Interrupt request data * * Looks up the interrupt request data @data and unlocks the mutex associated * with a particular over-current interrupt request.
*/ staticvoid soctherm_oc_irq_sync_unlock(struct irq_data *data)
{ struct soctherm_oc_irq_chip_data *d = irq_data_get_irq_chip_data(data);
mutex_unlock(&d->irq_lock);
}
/** * soctherm_oc_irq_enable() - Enables the SOC_THERM over-current interrupt queue * @data: irq_data structure of the chip * * Sets the irq_enable bit of SOC_THERM allowing SOC_THERM * to respond to over-current interrupts. *
*/ staticvoid soctherm_oc_irq_enable(struct irq_data *data)
{ struct soctherm_oc_irq_chip_data *d = irq_data_get_irq_chip_data(data);
d->irq_enable |= BIT(data->hwirq);
}
/** * soctherm_oc_irq_disable() - Disables overcurrent interrupt requests * @data: The interrupt request information * * Clears the interrupt request enable bit of the overcurrent * interrupt request chip data. * * Return: Nothing is returned (void)
*/ staticvoid soctherm_oc_irq_disable(struct irq_data *data)
{ struct soctherm_oc_irq_chip_data *d = irq_data_get_irq_chip_data(data);
/** * soctherm_oc_irq_map() - SOC_THERM interrupt request domain mapper * @h: Interrupt request domain * @virq: Virtual interrupt request number * @hw: Hardware interrupt request number * * Mapping callback function for SOC_THERM's irq_domain. When a SOC_THERM * interrupt request is called, the irq_domain takes the request's virtual * request number (much like a virtual memory address) and maps it to a * physical hardware request number. * * When a mapping doesn't already exist for a virtual request number, the * irq_domain calls this function to associate the virtual request number with * a hardware request number. * * Return: 0
*/ staticint soctherm_oc_irq_map(struct irq_domain *h, unsignedint virq,
irq_hw_number_t hw)
{ struct soctherm_oc_irq_chip_data *data = h->host_data;
/** * soctherm_irq_domain_xlate_twocell() - xlate for soctherm interrupts * @d: Interrupt request domain * @ctrlr: Controller device tree node * @intspec: Array of u32s from DTs "interrupt" property * @intsize: Number of values inside the intspec array * @out_hwirq: HW IRQ value associated with this interrupt * @out_type: The IRQ SENSE type for this interrupt. * * This Device Tree IRQ specifier translation function will translate a * specific "interrupt" as defined by 2 DT values where the cell values map * the hwirq number + 1 and linux irq flags. Since the output is the hwirq * number, this function will subtract 1 from the value listed in DT. * * Return: 0
*/ staticint soctherm_irq_domain_xlate_twocell(struct irq_domain *d, struct device_node *ctrlr, const u32 *intspec, unsignedint intsize,
irq_hw_number_t *out_hwirq, unsignedint *out_type)
{ if (WARN_ON(intsize < 2)) return -EINVAL;
/* * The HW value is 1 index less than the DT IRQ values. * i.e. OC4 goes to HW index 3.
*/
*out_hwirq = intspec[0] - 1;
*out_type = intspec[1] & IRQ_TYPE_SENSE_MASK; return 0;
}
/** * soctherm_oc_int_init() - Initial enabling of the over * current interrupts * @fwnode: The devicetree node for soctherm * @num_irqs: The number of new interrupt requests * * Sets the over current interrupt request chip data * * Return: 0 on success or if overcurrent interrupts are not enabled, * -ENOMEM (out of memory), or irq_base if the function failed to * allocate the irqs
*/ staticint soctherm_oc_int_init(struct fwnode_handle *fwnode, int num_irqs)
{ if (!num_irqs) {
pr_info("%s(): OC interrupts are not enabled\n", __func__); return 0;
}
for (i = 0; i < ts->soc->num_tsensors; i++) {
r = readl(ts->regs + tsensors[i].base + SENSOR_CONFIG1);
state = REG_GET_MASK(r, SENSOR_CONFIG1_TEMP_ENABLE);
mask = THERMCTL_LVL0_CPU0_STATUS_MASK;
state = REG_GET_MASK(r, mask);
seq_printf(s, "Status(%s)\n",
state == 0 ? "LO" :
state == 1 ? "In" :
state == 2 ? "Res" : "HI");
}
}
r = readl(ts->regs + THERMCTL_STATS_CTL);
seq_printf(s, "STATS: Up(%s) Dn(%s)\n",
r & STATS_CTL_EN_UP ? "En" : "--",
r & STATS_CTL_EN_DN ? "En" : "--");
for (level = 0; level < 4; level++) {
u16 off;
off = THERMCTL_LVL0_UP_STATS;
r = readl(ts->regs + THERMCTL_LVL_REG(off, level));
seq_printf(s, " Level_%d Up(%d) ", level, r);
off = THERMCTL_LVL0_DN_STATS;
r = readl(ts->regs + THERMCTL_LVL_REG(off, level));
seq_printf(s, "Dn(%d)\n", r);
}
r = readl(ts->regs + THERMCTL_THERMTRIP_CTL);
state = REG_GET_MASK(r, ttgs[0]->thermtrip_any_en_mask);
seq_printf(s, "Thermtrip Any En(%d)\n", state); for (i = 0; i < ts->soc->num_ttgs; i++) {
state = REG_GET_MASK(r, ttgs[i]->thermtrip_enable_mask);
seq_printf(s, " %s En(%d) ", ttgs[i]->name, state);
state = REG_GET_MASK(r, ttgs[i]->thermtrip_threshold_mask);
state *= ts->soc->thresh_grain;
seq_printf(s, "Thresh(%d)\n", state);
}
n = of_property_count_u32_elems(dev->of_node, "nvidia,thermtrips"); if (n <= 0) {
dev_info(dev, "missing thermtrips, will use critical trips as shut down temp\n"); return n;
}
n = min(max_num_prop, n);
tlb = devm_kcalloc(&pdev->dev, max_num_prop, sizeof(u32), GFP_KERNEL); if (!tlb) return -ENOMEM;
ret = of_property_read_u32_array(dev->of_node, "nvidia,thermtrips",
tlb, n); if (ret) {
dev_err(dev, "invalid num ele: thermtrips:%d\n", ret); return ret;
}
i = 0; for (j = 0; j < n; j = j + 2) { if (tlb[j] >= TEGRA124_SOCTHERM_SENSOR_NUM) continue;
/** * throttlectl_cpu_level_cfg() - programs CCROC NV_THERM level config * @ts: pointer to a struct tegra_soctherm * @level: describing the level LOW/MED/HIGH of throttling * * It's necessary to set up the CPU-local CCROC NV_THERM instance with * the M/N values desired for each level. This function does this. * * This function pre-programs the CCROC NV_THERM levels in terms of * pre-configured "Low", "Medium" or "Heavy" throttle levels which are * mapped to THROT_LEVEL_LOW, THROT_LEVEL_MED and THROT_LEVEL_HVY.
*/ staticvoid throttlectl_cpu_level_cfg(struct tegra_soctherm *ts, int level)
{
u8 depth, dividend;
u32 r;
switch (level) { case TEGRA_SOCTHERM_THROT_LEVEL_LOW:
depth = 50; break; case TEGRA_SOCTHERM_THROT_LEVEL_MED:
depth = 75; break; case TEGRA_SOCTHERM_THROT_LEVEL_HIGH:
depth = 80; break; case TEGRA_SOCTHERM_THROT_LEVEL_NONE: return; default: return;
}
dividend = THROT_DEPTH_DIVIDEND(depth);
/* setup PSKIP in ccroc nv_therm registers */
r = ccroc_readl(ts, CCROC_THROT_PSKIP_RAMP_CPU_REG(level));
r = REG_SET_MASK(r, CCROC_THROT_PSKIP_RAMP_DURATION_MASK, 0xff);
r = REG_SET_MASK(r, CCROC_THROT_PSKIP_RAMP_STEP_MASK, 0xf);
ccroc_writel(ts, r, CCROC_THROT_PSKIP_RAMP_CPU_REG(level));
r = ccroc_readl(ts, CCROC_THROT_PSKIP_CTRL_CPU_REG(level));
r = REG_SET_MASK(r, CCROC_THROT_PSKIP_CTRL_ENB_MASK, 1);
r = REG_SET_MASK(r, CCROC_THROT_PSKIP_CTRL_DIVIDEND_MASK, dividend);
r = REG_SET_MASK(r, CCROC_THROT_PSKIP_CTRL_DIVISOR_MASK, 0xff);
ccroc_writel(ts, r, CCROC_THROT_PSKIP_CTRL_CPU_REG(level));
}
/** * throttlectl_cpu_level_select() - program CPU pulse skipper config * @ts: pointer to a struct tegra_soctherm * @throt: the LIGHT/HEAVY of throttle event id * * Pulse skippers are used to throttle clock frequencies. This * function programs the pulse skippers based on @throt and platform * data. This function is used on SoCs which have CPU-local pulse * skipper control, such as T13x. It programs soctherm's interface to * Denver:CCROC NV_THERM in terms of Low, Medium and HIGH throttling * vectors. PSKIP_BYPASS mode is set as required per HW spec.
*/ staticvoid throttlectl_cpu_level_select(struct tegra_soctherm *ts, enum soctherm_throttle_id throt)
{
u32 r, throt_vect;
r = readl(ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU));
r = REG_SET_MASK(r, THROT_PSKIP_CTRL_ENABLE_MASK, 1);
r = REG_SET_MASK(r, THROT_PSKIP_CTRL_VECT_CPU_MASK, throt_vect);
r = REG_SET_MASK(r, THROT_PSKIP_CTRL_VECT2_CPU_MASK, throt_vect);
writel(r, ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU));
/* bypass sequencer in soc_therm as it is programmed in ccroc */
r = REG_SET_MASK(0, THROT_PSKIP_RAMP_SEQ_BYPASS_MODE_MASK, 1);
writel(r, ts->regs + THROT_PSKIP_RAMP(throt, THROTTLE_DEV_CPU));
}
/** * throttlectl_cpu_mn() - program CPU pulse skipper configuration * @ts: pointer to a struct tegra_soctherm * @throt: the LIGHT/HEAVY of throttle event id * * Pulse skippers are used to throttle clock frequencies. This * function programs the pulse skippers based on @throt and platform * data. This function is used for CPUs that have "remote" pulse * skipper control, e.g., the CPU pulse skipper is controlled by the * SOC_THERM IP block. (SOC_THERM is located outside the CPU * complex.)
*/ staticvoid throttlectl_cpu_mn(struct tegra_soctherm *ts, enum soctherm_throttle_id throt)
{
u32 r; int depth;
u8 dividend;
/* configure LOW, MED and HIGH levels for CCROC NV_THERM */ if (ts->soc->use_ccroc) {
throttlectl_cpu_level_cfg(ts, TEGRA_SOCTHERM_THROT_LEVEL_LOW);
throttlectl_cpu_level_cfg(ts, TEGRA_SOCTHERM_THROT_LEVEL_MED);
throttlectl_cpu_level_cfg(ts, TEGRA_SOCTHERM_THROT_LEVEL_HIGH);
}
/* Thermal HW throttle programming */ for (i = 0; i < THROTTLE_SIZE; i++)
soctherm_throttle_program(ts, i);
v = REG_SET_MASK(0, THROT_GLOBAL_ENB_MASK, 1); if (ts->soc->use_ccroc) {
ccroc_writel(ts, v, CCROC_GLOBAL_CFG);
v = ccroc_readl(ts, CCROC_SUPER_CCLKG_DIVIDER);
v = REG_SET_MASK(v, CDIVG_USE_THERM_CONTROLS_MASK, 1);
ccroc_writel(ts, v, CCROC_SUPER_CCLKG_DIVIDER);
} else {
writel(v, ts->regs + THROT_GLOBAL_CFG);
v = readl(ts->clk_regs + CAR_SUPER_CCLKG_DIVIDER);
v = REG_SET_MASK(v, CDIVG_USE_THERM_CONTROLS_MASK, 1);
writel(v, ts->clk_regs + CAR_SUPER_CCLKG_DIVIDER);
}
tegra->regs = devm_platform_ioremap_resource_byname(pdev, "soctherm-reg"); if (IS_ERR(tegra->regs)) {
dev_err(&pdev->dev, "can't get soctherm registers"); return PTR_ERR(tegra->regs);
}
if (!tegra->soc->use_ccroc) {
tegra->clk_regs = devm_platform_ioremap_resource_byname(pdev, "car-reg"); if (IS_ERR(tegra->clk_regs)) {
dev_err(&pdev->dev, "can't get car clk registers"); return PTR_ERR(tegra->clk_regs);
}
} else {
tegra->ccroc_regs = devm_platform_ioremap_resource_byname(pdev, "ccroc-reg"); if (IS_ERR(tegra->ccroc_regs)) {
dev_err(&pdev->dev, "can't get ccroc registers"); return PTR_ERR(tegra->ccroc_regs);
}
}
tegra->reset = devm_reset_control_get(&pdev->dev, "soctherm"); if (IS_ERR(tegra->reset)) {
dev_err(&pdev->dev, "can't get soctherm reset\n"); return PTR_ERR(tegra->reset);
}
tegra->clock_tsensor = devm_clk_get(&pdev->dev, "tsensor"); if (IS_ERR(tegra->clock_tsensor)) {
dev_err(&pdev->dev, "can't get tsensor clock\n"); return PTR_ERR(tegra->clock_tsensor);
}
tegra->clock_soctherm = devm_clk_get(&pdev->dev, "soctherm"); if (IS_ERR(tegra->clock_soctherm)) {
dev_err(&pdev->dev, "can't get soctherm clock\n"); return PTR_ERR(tegra->clock_soctherm);
}
tegra->calib = devm_kcalloc(&pdev->dev,
soc->num_tsensors, sizeof(u32),
GFP_KERNEL); if (!tegra->calib) return -ENOMEM;
/* calculate shared calibration data */
err = tegra_calc_shared_calib(soc->tfuse, &shared_calib); if (err) return err;
/* calculate tsensor calibration data */ for (i = 0; i < soc->num_tsensors; ++i) {
err = tegra_calc_tsensor_calib(&soc->tsensors[i],
&shared_calib,
&tegra->calib[i]); if (err) return err;
}
tegra->thermctl_tzs = devm_kcalloc(&pdev->dev,
soc->num_ttgs, sizeof(z),
GFP_KERNEL); if (!tegra->thermctl_tzs) return -ENOMEM;
err = soctherm_clk_enable(pdev, true); if (err) return err;
soctherm_thermtrips_parse(pdev);
soctherm_init_hw_throt_cdev(pdev);
soctherm_init(pdev);
for (i = 0; i < soc->num_ttgs; ++i) { struct tegra_thermctl_zone *zone =
devm_kzalloc(&pdev->dev, sizeof(*zone), GFP_KERNEL); if (!zone) {
err = -ENOMEM; goto disable_clocks;
}
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.