/* * palmas_thermal.c -- TI PALMAS THERMAL. * * Copyright (c) 2013, NVIDIA Corporation. All rights reserved. * * Author: Pradeep Goudagunta * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation version 2. * * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind, * whether express or implied; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307, USA */ #include #include #include #include #include #include #include #include #include #include #define PALMAS_NORMAL_OPERATING_TEMP 100000 #define PALMAS_CRITICAL_DEFUALT_TEMP 108000 struct palmas_therm_zone { struct device *dev; struct palmas *palmas; struct thermal_zone_device *tz_device; int irq; int is_crit_temp; }; struct palmas_trip_point { unsigned long temp; enum thermal_trip_type type; }; static struct palmas_trip_point palmas_tpoint = { .temp = PALMAS_CRITICAL_DEFUALT_TEMP, .type = THERMAL_TRIP_CRITICAL, }; static int palmas_thermal_get_crit_temp(struct thermal_zone_device *tz_device, unsigned long *temp) { *temp = palmas_tpoint.temp; return 0; } static int palmas_thermal_get_temp(struct thermal_zone_device *tz_device, unsigned long *temp) { struct palmas_therm_zone *ptherm_zone = tz_device->devdata; if (ptherm_zone->is_crit_temp) { /* * Set temperature greater than Critical trip * temp to trigger orderly power down sequence */ palmas_thermal_get_crit_temp(tz_device, temp); *temp += 1; return 0; } *temp = PALMAS_NORMAL_OPERATING_TEMP; return 0; } static int palmas_thermal_get_trip_type(struct thermal_zone_device *tz_device, int trip, enum thermal_trip_type *type) { if (trip >= 1) return -EINVAL; *type = palmas_tpoint.type; return 0; } static int palmas_thermal_get_trip_temp(struct thermal_zone_device *tz_device, int trip, unsigned long *temp) { if (trip >= 1) return -EINVAL; *temp = palmas_tpoint.temp; return 0; } static struct thermal_zone_device_ops palmas_tz_ops = { .get_temp = palmas_thermal_get_temp, .get_crit_temp = palmas_thermal_get_crit_temp, .get_trip_type = palmas_thermal_get_trip_type, .get_trip_temp = palmas_thermal_get_trip_temp, }; static irqreturn_t palmas_thermal_irq(int irq, void *data) { struct palmas_therm_zone *ptherm_zone = data; ptherm_zone->is_crit_temp = 1; /* * Necessary action can be taken here * e.g: thermal_zone_device_update(pz->tz_device); * will trigger orderly power down sequence. */ return IRQ_HANDLED; } static int palmas_thermal_probe(struct platform_device *pdev) { struct palmas_therm_zone *ptherm_zone; struct palmas_platform_data *pdata; struct palmas *palmas = dev_get_drvdata(pdev->dev.parent); char *default_tz_name = "palmas-junc-tz"; int ret; u8 val; pdata = dev_get_platdata(pdev->dev.parent); if (!pdata || !(pdata->hd_threshold_temp)) { dev_err(&pdev->dev, "No platform data\n"); return -ENODEV; } ptherm_zone = devm_kzalloc(&pdev->dev, sizeof(*ptherm_zone), GFP_KERNEL); if (!ptherm_zone) { dev_err(&pdev->dev, "No available free memory\n"); return -ENOMEM; } platform_set_drvdata(pdev, ptherm_zone); ptherm_zone->dev = &pdev->dev; if (!(pdata->tz_name)) pdata->tz_name = default_tz_name; ptherm_zone->tz_device = thermal_zone_device_register(pdata->tz_name, 1, 0, ptherm_zone, &palmas_tz_ops, NULL, 0, 0); if (IS_ERR_OR_NULL(ptherm_zone->tz_device)) { dev_err(ptherm_zone->dev, "Register thermal zone device failed.\n"); return PTR_ERR(ptherm_zone->tz_device); } palmas_tpoint.temp = pdata->hd_threshold_temp; ptherm_zone->irq = platform_get_irq(pdev, 0); ret = request_threaded_irq(ptherm_zone->irq, NULL, palmas_thermal_irq, IRQF_ONESHOT, dev_name(&pdev->dev), ptherm_zone); if (ret < 0) { dev_err(&pdev->dev, "request irq %d failed: %dn", ptherm_zone->irq, ret); goto int_req_failed; } switch (palmas_tpoint.temp) { case 108000: val = 0; break; case 112000: val = 1; break; case 116000: val = 2; break; case 120000: val = 3; break; default: dev_err(&pdev->dev, "%ld threshold is not supported", palmas_tpoint.temp); ret = -EINVAL; goto error; } val <<= PALMAS_OSC_THERM_CTRL_THERM_HD_SEL_SHIFT; ret = palmas_update_bits(palmas, PALMAS_PMU_CONTROL_BASE, PALMAS_OSC_THERM_CTRL, PALMAS_OSC_THERM_CTRL_THERM_HD_SEL_MASK, val); if (ret < 0) { dev_err(&pdev->dev, "osc_therm_ctrl reg update failed.\n"); goto error; } return 0; error: free_irq(ptherm_zone->irq, ptherm_zone); int_req_failed: thermal_zone_device_unregister(ptherm_zone->tz_device); return ret; } static int palmas_thermal_remove(struct platform_device *pdev) { struct palmas_therm_zone *ptherm_zone = platform_get_drvdata(pdev); thermal_zone_device_unregister(ptherm_zone->tz_device); free_irq(ptherm_zone->irq, ptherm_zone); kfree(ptherm_zone); return 0; } static struct platform_driver palmas_thermal_driver = { .probe = palmas_thermal_probe, .remove = palmas_thermal_remove, .driver = { .name = "palmas-thermal", .owner = THIS_MODULE, }, }; static int __init palmas_thermal_init(void) { return platform_driver_register(&palmas_thermal_driver); } module_init(palmas_thermal_init); static void __exit palmas_thermal_exit(void) { platform_driver_unregister(&palmas_thermal_driver); } module_exit(palmas_thermal_exit); MODULE_DESCRIPTION("Palmas Thermal driver"); MODULE_AUTHOR("Pradeep Goudagunta"); MODULE_ALIAS("platform:palmas-thermal"); MODULE_LICENSE("GPL v2");