/* * AMS AS3722 THERMAL. * * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. * * Author: Bibek Basu * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define AS3722_MAX_TEMP 145000 #define AS3722_ALARM_TEMP 110000 enum sensor_id { SD0 = 0, SD1, SD6, SD_MAX, }; struct as3722_therm_zone { struct device *dev; struct as3722 *as3722; struct delayed_work timeout_work; struct thermal_zone_device *tzd[SD_MAX]; long cur_temp[SD_MAX]; long high_limit[SD_MAX]; struct mutex update_lock; int irq[SD_MAX]; bool irq_masked[SD_MAX]; u32 adc_ch[SD_MAX]; }; static void as3722_timeout_work(struct work_struct *work) { struct as3722_therm_zone *ptherm_zone = container_of(work, struct as3722_therm_zone, timeout_work.work); int i; mutex_lock(&ptherm_zone->update_lock); for (i = 0; i < SD_MAX; i++) { if (ptherm_zone->irq_masked[i]) { enable_irq(ptherm_zone->irq[i]); ptherm_zone->irq_masked[i] = false; } } mutex_unlock(&ptherm_zone->update_lock); } static int as3722_therm_get_temp(struct as3722_therm_zone *ptherm_zone, long *temp, int channel) { struct as3722 *as3722 = ptherm_zone->as3722; int tries = 10; int ret; int result; u32 val; /* Configure adc1 */ ret = as3722_update_bits(as3722, AS3722_ADC1_CONTROL_REG, AS3722_ADC1_SOURCE_SELECT_MASK, channel); if (ret < 0) { dev_err(ptherm_zone->dev, "ADC1_CONTROL channel write failed %d\n", ret); return ret; } do { int timeout = 10; /* Start ADC */ ret = as3722_update_bits(as3722, AS3722_ADC1_CONTROL_REG, AS3722_ADC1_CONV_START, AS3722_ADC1_CONV_START); if (ret < 0) { dev_err(ptherm_zone->dev, "Reg 0x%02x write failed: %d\n", AS3722_ADC1_CONTROL_REG, ret); return ret; } /* Wait for conversion */ do { ret = as3722_read(as3722, AS3722_ADC1_MSB_RESULT_REG, &val); if (ret < 0) { dev_err(ptherm_zone->dev, "Reg 0x%02x read failed: %d\n", AS3722_ADC1_MSB_RESULT_REG, ret); return ret; } if (!(val & AS3722_ADC1_CONV_NOTREADY)) break; udelay(100); } while (--timeout > 0); if (!timeout) continue; /* ADC Result = {MSB[6:0], LSB[3:0]} */ result = ((val & AS3722_ADC_MSB_VAL_MASK) << 3); ret = as3722_read(as3722, AS3722_ADC1_LSB_RESULT_REG, &val); if (ret < 0) { dev_err(ptherm_zone->dev, "Reg 0x%02x read failed: %d\n", AS3722_ADC1_LSB_RESULT_REG, ret); return ret; } result |= val & AS3722_ADC_LSB_VAL_MASK; /* Temp (mC) = 326500 - (ADC result * 3734) / 10 */ *temp = 326500 - (result * 3734) / 10; if (*temp <= AS3722_MAX_TEMP) return 0; } while (--tries > 0); return -ETIMEDOUT; } static int as3722_therm_get_sd0_temp(void *dev, long *temp) { struct as3722_therm_zone *ptherm_zone = dev; int ret; ret = as3722_therm_get_temp(ptherm_zone, temp, ptherm_zone->adc_ch[SD0]); return ret; } static int as3722_therm_get_sd1_temp(void *dev, long *temp) { struct as3722_therm_zone *ptherm_zone = dev; int ret; ret = as3722_therm_get_temp(ptherm_zone, temp, ptherm_zone->adc_ch[SD1]); return ret; } static int as3722_therm_get_sd6_temp(void *dev, long *temp) { struct as3722_therm_zone *ptherm_zone = dev; int ret; ret = as3722_therm_get_temp(ptherm_zone, temp, ptherm_zone->adc_ch[SD6]); return ret; } static void as3722_therm_update_limits(struct as3722_therm_zone *ptherm_zone, int sensor) { long high_temp = AS3722_MAX_TEMP; long trip_temp; int i; for (i = 0; i < ptherm_zone->tzd[sensor]->trips; i++) { ptherm_zone->tzd[sensor]->ops->get_trip_temp( ptherm_zone->tzd[sensor], i, &trip_temp); high_temp = min(high_temp, trip_temp); } ptherm_zone->high_limit[sensor] = high_temp; } static irqreturn_t as3722_thermal_irq(int irq, void *data) { struct as3722_therm_zone *ptherm_zone = data; int i; for (i = 0; i < SD_MAX; i++) { if (irq != ptherm_zone->irq[i]) continue; if (thermal_zone_get_temp(ptherm_zone->tzd[i], &ptherm_zone->cur_temp[i])) ptherm_zone->cur_temp[i] = AS3722_ALARM_TEMP; if (ptherm_zone->cur_temp[i] >= ptherm_zone->high_limit[i]) { /* * Interrupt is disabled to stop flooding of * interrupts due to high temp alarm. */ mutex_lock(&ptherm_zone->update_lock); disable_irq_nosync(irq); ptherm_zone->irq_masked[i] = true; mutex_unlock(&ptherm_zone->update_lock); thermal_zone_device_update(ptherm_zone->tzd[i]); /* When the timer expires, interrupt is re-enabled */ mod_delayed_work(system_freezable_wq, &ptherm_zone->timeout_work, jiffies + msecs_to_jiffies(200)); dev_info(ptherm_zone->dev, "Thermal alarm, current temp %ldmC\n", ptherm_zone->cur_temp[i]); } } return IRQ_HANDLED; } struct as3722_therm_zone *as3722_therm_get_pdata(struct platform_device *pdev) { struct as3722_therm_zone *ptherm_zone = NULL; struct device_node *np = pdev->dev.parent->of_node; if (!np) return ptherm_zone; else ptherm_zone = devm_kzalloc(&pdev->dev, sizeof(*ptherm_zone), GFP_KERNEL); if (!ptherm_zone) { dev_err(&pdev->dev, "No available free memory\n"); return ptherm_zone; } if (of_property_read_u32(np, "ams,sd0-adc-ch", &ptherm_zone->adc_ch[SD0])) goto err; if (of_property_read_u32(np, "ams,sd1-adc-ch", &ptherm_zone->adc_ch[SD1])) goto err; if (of_property_read_u32(np, "ams,sd6-adc-ch", &ptherm_zone->adc_ch[SD6])) goto err; return ptherm_zone; err: return NULL; } static int as3722_thermal_probe(struct platform_device *pdev) { struct as3722 *as3722 = dev_get_drvdata(pdev->dev.parent); struct as3722_therm_zone *ptherm_zone; int i, ret; ptherm_zone = as3722_therm_get_pdata(pdev); if (!ptherm_zone) return -ENOMEM; ptherm_zone->dev = &pdev->dev; ptherm_zone->as3722 = as3722; mutex_init(&ptherm_zone->update_lock); INIT_DELAYED_WORK(&ptherm_zone->timeout_work, as3722_timeout_work); platform_set_drvdata(pdev, ptherm_zone); ptherm_zone->tzd[SD0] = thermal_zone_of_sensor_register(as3722->dev, SD0, ptherm_zone, as3722_therm_get_sd0_temp, NULL); if (IS_ERR(ptherm_zone->tzd[SD0])) { ret = PTR_ERR(ptherm_zone->tzd[SD0]); goto err; } ptherm_zone->tzd[SD1] = thermal_zone_of_sensor_register(as3722->dev, SD1, ptherm_zone, as3722_therm_get_sd1_temp, NULL); if (IS_ERR(ptherm_zone->tzd[SD1])) { ret = PTR_ERR(ptherm_zone->tzd[SD1]); goto err; } ptherm_zone->tzd[SD6] = thermal_zone_of_sensor_register(as3722->dev, SD6, ptherm_zone, as3722_therm_get_sd6_temp, NULL); if (IS_ERR(ptherm_zone->tzd[SD6])) { ret = PTR_ERR(ptherm_zone->tzd[SD6]); goto err; } for (i = 0; i < SD_MAX; i++) { as3722_therm_update_limits(ptherm_zone, i); thermal_update_governor(ptherm_zone->tzd[i], "pid_thermal_gov"); if (thermal_zone_get_temp(ptherm_zone->tzd[i], &ptherm_zone->cur_temp[i])) ptherm_zone->cur_temp[i] = 25000; ptherm_zone->irq[i] = platform_get_irq(pdev, i); ret = devm_request_threaded_irq(&pdev->dev, ptherm_zone->irq[i], NULL, as3722_thermal_irq, IRQF_ONESHOT, dev_name(&pdev->dev), ptherm_zone); if (ret < 0) { dev_err(&pdev->dev, "Request irq %d failed: %d\nn", ptherm_zone->irq[i], ret); goto err; } } return 0; err: cancel_delayed_work_sync(&ptherm_zone->timeout_work); for (i = 0; i < SD_MAX; i++) { if (!IS_ERR(ptherm_zone->tzd[i])) thermal_zone_of_sensor_unregister(as3722->dev, ptherm_zone->tzd[i]); } return ret; } static int as3722_thermal_remove(struct platform_device *pdev) { struct as3722_therm_zone *ptherm_zone = platform_get_drvdata(pdev); struct as3722 *as3722 = dev_get_drvdata(pdev->dev.parent); int i; cancel_delayed_work_sync(&ptherm_zone->timeout_work); for (i = 0; i < SD_MAX; i++) thermal_zone_of_sensor_unregister(as3722->dev, ptherm_zone->tzd[i]); return 0; } static struct platform_driver as3722_thermal_driver = { .probe = as3722_thermal_probe, .remove = as3722_thermal_remove, .driver = { .name = "as3722-thermal", .owner = THIS_MODULE, }, }; module_platform_driver(as3722_thermal_driver); MODULE_DESCRIPTION("AS3722 Thermal driver"); MODULE_AUTHOR("Bibek Basu "); MODULE_ALIAS("platform:as3722-thermal"); MODULE_LICENSE("GPL v2");